Merge from master branch

This commit is contained in:
veselcraft 2022-02-11 17:14:47 +03:00
commit 45db4188cc
No known key found for this signature in database
GPG key ID: AED66BC1AC628A4E
125 changed files with 4892 additions and 1516 deletions

1
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1 @@
custom: "https://openvk.su/donate"

View file

@ -94,7 +94,7 @@
<tr>
<td>
<center>
<a href="http://{$_SERVER['HTTP_HOST']}/restore.pl?act=finish&key={rawurlencode($key)}" align="center" class="float-center">Сбросить пароль!</a>
<a href="http://{$_SERVER['HTTP_HOST']}/restore?act=finish&key={rawurlencode($key)}" align="center" class="float-center">Сбросить пароль!</a>
</center>
</td>
</tr>
@ -118,8 +118,8 @@
<table class="callout">
<tr>
<th class="callout-inner primary">
<a href="http://{$_SERVER['HTTP_HOST']}/restore.pl?act=finish&key={$key}" style="color: #000; text-decoration: none;">
http://{$_SERVER['HTTP_HOST']}/restore.pl?act=finish&key={$key}
<a href="http://{$_SERVER['HTTP_HOST']}/restore?act=finish&key={$key}" style="color: #000; text-decoration: none;">
http://{$_SERVER['HTTP_HOST']}/restore?act=finish&key={$key}
</a>
</th>
</tr>

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

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

View file

@ -1,5 +1,7 @@
# <img align="right" src="https://github.com/openvk/openvk/raw/master/Web/static/img/logo_shadow.png" alt="openvk" title="openvk" width="15%">OpenVK
_[Русский](README_RU.md)_
**OpenVK** is an attempt to create a simple CMS that ~~cosplays~~ imitates old VK. Code provided here is not stable yet.
VKontakte belongs to Pavel Durov and VK Group.
@ -16,28 +18,37 @@ Updating the source code is done with this command: `git pull`
* **[openvk.su](https://openvk.su/)**
* [social.fetbuk.ru](http://social.fetbuk.ru/)
* [openvk.zavsc.pw](https://openvk.zavsc.pw/)
## Can I create my own OpenVK instance?
Yes! And you're very welcome to.
However, OVK makes use of Chandler Application Server. This software requires extensions, that may not be provided by your hosting provider (namely, sodium and yaml. this extensions are available on most of ISPManager hostings).
If you want, you can add your instance to the list above so that people can register there.
### Installation procedure
1. Install PHP 7, web-server, Composer, Node.js, Yarn and [Chandler](https://github.com/openvk/chandler)
* PHP 8 has **not** yet been tested, so you should not expect it to work.
1. Install PHP 7.4, web-server, Composer, Node.js, Yarn and [Chandler](https://github.com/openvk/chandler)
* PHP 8 has **not** yet been tested, so you should not expect it to work.
2. Install [commitcaptcha](https://github.com/openvk/commitcaptcha) and OpenVK as Chandler extensions like this:
```
```bash
git clone https://github.com/openvk/openvk /path/to/chandler/extensions/available/openvk
git clone https://github.com/openvk/commitcaptcha /path/to/chandler/extensions/available/commitcaptcha
```
3. And enable them:
```
```bash
ln -s /path/to/chandler/extensions/available/commitcaptcha /path/to/chandler/extensions/enabled/
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
@ -46,27 +57,31 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions
10. Set `openvk` as your root app in `chandler.yml`
Once you are done, you can login as a system administrator on the network itself (no registration required):
* **Login**: `admin@localhost.localdomain6`
* **Password**: `admin`
* It is recommended to change the password before using the built-in account.
* It is recommended to change the password before using the built-in account.
Full example installation instruction for CentOS 8 is also available [here](docs/centos8_install.md).
Full example installation instruction for CentOS 8 is also available [here](https://docs.openvk.su/openvk_engine/centos8_installation/).
### If my website uses OpenVK, should I publish it's sources?
You are encouraged to do so. We don't enforce this though. You can keep your sources to yourself (unless you distribute your OpenVK distro to other people).
You also not required to publish source texts of your themepacks and plugins.
## Where can I get assistance?
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">

87
README_RU.md Normal file
View file

@ -0,0 +1,87 @@
# <img align="right" src="https://github.com/openvk/openvk/raw/master/Web/static/img/logo_shadow.png" alt="openvk" title="openvk" width="15%">OpenVK
_[English](README.md)_
**OpenVK** это попытка создать простую CMS, которая ~~косплеит~~ имитирует старый ВКонтакте. Представленный здесь код пока не стабилен.
ВКонтакте принадлежит Павлу Дурову и VK Group.
Честно говоря, мы даже не знаем, работает ли она вообще. Однако, эта версия поддерживается, и мы будем рады принять ваши сообщения об ошибках [в нашем баг-трекере](https://github.com/openvk/openvk/projects/1). Вы также можете отправлять их через [вкладку "Помощь"](https://openvk.su/support?act=new) (для этого вам понадобится учетная запись OVK).
## Когда релиз?
Пожалуйста, используйте ветку master, так как в ней больше всего изменений.
Обновление исходного кода выполняется с помощью этой команды: `git pull`.
## Инстанции
* **[openvk.su](https://openvk.su/)**
* [social.fetbuk.ru](http://social.fetbuk.ru/)
* [openvk.zavsc.pw](https://openvk.zavsc.pw/)
## Могу ли я создать свою собственную инстанцию OpenVK?
Да! И всегда пожалуйста.
Однако, OVK использует Chandler Application Server. Это программное обеспечение требует расширений, которые могут быть не предоставлены вашим хостинг-провайдером (а именно, sodium и yaml. эти расширения доступны на большинстве хостингов ISPManager).
Если вы хотите, вы можете добавить вашу инстанцию в список выше, чтобы люди могли зарегистрироваться там.
### Процедура установки
1. Установите PHP 7.4, веб-сервер, Composer, Node.js, Yarn и [Chandler](https://github.com/openvk/chandler)
* PHP 8 еще **не** тестировался, поэтому не стоит ожидать, что он будет работать.
2. Установите [commitcaptcha](https://github.com/openvk/commitcaptcha) и OpenVK в качестве расширений Chandler следующим образом:
```bash
git clone https://github.com/openvk/openvk /path/to/chandler/extensions/available/openvk
git clone https://github.com/openvk/commitcaptcha /path/to/chandler/extensions/available/commitcaptcha
```
3. И включите их:
```bash
ln -s /path/to/chandler/extensions/available/commitcaptcha /path/to/chandler/extensions/enabled/
ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions/enabled/
```
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
8. Перейдите в `Web/static/js` и выполните `yarn install`
9. Установите `openvk` в качестве корневого приложения в файле `chandler.yml`
После этого вы можете войти как системный администратор в саму сеть (регистрация не требуется):
* **Логин**: `admin@localhost.localdomain6`
* **Пароль**: `admin`
* Перед использованием встроенной учетной записи рекомендуется сменить пароль.
Полный пример инструкции по установке CentOS 8 также доступен [здесь](https://docs.openvk.su/openvk_engine/centos8_installation/).
### Если мой сайт использует OpenVK, должен ли я публиковать его исходные тексты?
Вам рекомендуется это делать. Однако мы не следим за этим. Вы можете держать свои исходные тексты при себе (если только вы не распространяете свой дистрибутив OpenVK среди других людей).
Вы также не обязаны публиковать исходные тексты ваших тематических пакетов и плагинов.
## Где я могу получить помощь?
Вы можете связаться с нами через:
* [Баг-трекер](https://github.com/openvk/openvk/projects/1)
* [Помощь в OVK](https://openvk.su/support?act=new)
* Telegram-чат: Перейдите на [наш канал](https://t.me/openvkch) и откройте обсуждение в меню нашего канала.
* [Reddit](https://www.reddit.com/r/openvk/)
* [Обсуждения](https://github.com/openvk/openvk/discussions)
* Чат в Matrix: #ovk:matrix.org
**Внимание**: баг-трекер, телеграм- и 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">
</a>

View file

@ -110,7 +110,7 @@ final class Wall extends VKAPIRequestHandler
];
}
function post(string $owner_id, string $message, int $from_group = 0, int $signed = 0): object
function post(string $owner_id, string $message = "", int $from_group = 0, int $signed = 0): object
{
$this->requireUser();
@ -130,12 +130,46 @@ final class Wall extends VKAPIRequestHandler
if($canPost == false) $this->fail(15, "Access denied");
$anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"];
if($wallOwner instanceof Club && $from_group == 1 && $signed != 1 && $anon) {
$manager = $wallOwner->getManager($this->getUser());
if($manager)
$anon = $manager->isHidden();
elseif($this->getUser()->getId() === $wallOwner->getOwner()->getId())
$anon = $wallOwner->isOwnerHidden();
} else {
$anon = false;
}
$flags = 0;
if($from_group == 1)
if($from_group == 1 && $wallOwner instanceof Club && $wallOwner->canBeModifiedBy($this->getUser()))
$flags |= 0b10000000;
if($signed == 1)
$flags |= 0b01000000;
// TODO: Compatible implementation of this
try {
$photo = null;
$video = null;
if($_FILES["photo"]["error"] === UPLOAD_ERR_OK) {
$album = null;
if(!$anon && $owner_id > 0 && $owner_id === $this->getUser()->getId())
$album = (new AlbumsRepo)->getUserWallAlbum($wallOwner);
$photo = Photo::fastMake($this->getUser()->getId(), $message, $_FILES["photo"], $album, $anon);
}
if($_FILES["video"]["error"] === UPLOAD_ERR_OK)
$video = Video::fastMake($this->getUser()->getId(), $message, $_FILES["video"], $anon);
} catch(\DomainException $ex) {
$this->fail(-156, "The media file is corrupted");
} catch(ISE $ex) {
$this->fail(-156, "The media file is corrupted or too large ");
}
if(empty($message) && !$photo && !$video)
$this->fail(100, "Required parameter 'message' missing.");
try {
$post = new Post;
$post->setOwner($this->getUser()->getId());
@ -148,6 +182,12 @@ final class Wall extends VKAPIRequestHandler
$this->fail(100, "One of the parameters specified was missing or invalid");
}
if(!is_null($photo))
$post->attach($photo);
if(!is_null($video))
$post->attach($video);
if($wall > 0 && $wall !== $this->user->identity->getId())
(new WallPostNotification($wallOwner, $post, $this->user->identity))->emit();

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
{
@ -140,6 +130,11 @@ class Club extends RowModel
return (bool) $this->getRecord()->display_topics_above_wall;
}
function isHideFromGlobalFeedEnabled(): bool
{
return (bool) $this->getRecord()->hide_from_global_feed;
}
function getType(): int
{
return $this->getRecord()->type;

View file

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

View file

@ -94,4 +94,9 @@ class Note extends Postable
return $cached;
}
function getSource(): string
{
return $this->getRecord()->source;
}
}

View file

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

View file

@ -127,5 +127,10 @@ class TicketComment extends RowModel
return $mark === 1;
}
function isDeleted(): bool
{
return (bool) $this->getRecord()->deleted;
}
use Traits\TRichText;
}

View file

@ -32,10 +32,12 @@ trait TRichText
private function formatLinks(string &$text): string
{
return preg_replace_callback(
"%(([A-z]++):\/\/(\S*?\.\S*?))([\s)\[\]{},;\"\'<]|\.\s|$)%",
"%(([A-z]++):\/\/(\S*?\.\S*?))([\s)\[\]{},\"\'<]|\.\s|$)%",
(function (array $matches): string {
$href = str_replace("#", "&num;", $matches[1]);
$href = str_replace(";", "&#59;", $matches[1]);
$link = str_replace("#", "&num;", $matches[3]);
$link = str_replace(";", "&#59;", $matches[3]);
$rel = $this->isAd() ? "sponsored" : "ugc";
return "<a href='$href' rel='$rel' target='_blank'>$link</a>" . htmlentities($matches[4]);

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;
@ -111,7 +112,7 @@ class User extends RowModel
function getAvatarUrl(bool $nullForDel = false): ?string
{
$serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"];
$serverUrl = ovk_scheme(true) . $_SERVER["HTTP_HOST"];
if($this->getRecord()->deleted)
return $nullForDel ? null : "$serverUrl/assets/packages/static/openvk/img/camera_200.png";
@ -362,6 +363,7 @@ class User extends RowModel
"groups",
"news",
"links",
"poster",
],
])->get($id);
}
@ -380,6 +382,7 @@ class User extends RowModel
"friends.read",
"friends.add",
"wall.write",
"messages.write",
],
])->get($id);
}
@ -745,6 +748,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{Mn}?(?:\p{L&}\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{Mn}?(\p{L&}\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);
@ -764,6 +788,7 @@ class User extends RowModel
"friends.read",
"friends.add",
"wall.write",
"messages.write",
],
])->set($id, $status)->toInteger());
}
@ -780,6 +805,7 @@ class User extends RowModel
"groups",
"news",
"links",
"poster",
],
])->set($id, (int) $status)->toInteger();
@ -854,10 +880,10 @@ class User extends RowModel
function isDeleted(): bool
{
if ($this->getRecord()->deleted == 1)
return TRUE;
else
return FALSE;
if ($this->getRecord()->deleted == 1)
return TRUE;
else
return FALSE;
}
/**
@ -886,6 +912,12 @@ class User extends RowModel
{
return $this->getRecord()->website;
}
// ты устрица
function isActivated(): bool
{
return (bool) $this->getRecord()->activated;
}
use Traits\TSubscribable;
}

View file

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

View file

@ -37,6 +37,24 @@ class Clubs
return new Util\EntityStream("Club", $result);
}
function getCount(): int
{
return sizeof(clone $this->clubs);
}
function getPopularClubs(): \Traversable
{
$query = "SELECT ROW_NUMBER() OVER (ORDER BY `subscriptions` DESC) as `place`, `target` as `id`, COUNT(`follower`) as `subscriptions` FROM `subscriptions` WHERE `model` = \"openvk\\\Web\\\Models\\\Entities\\\Club\" GROUP BY `target` ORDER BY `subscriptions` DESC, `id` LIMIT 10;";
$entries = DatabaseConnection::i()->getConnection()->query($query);
foreach($entries as $entry)
yield (object) [
"place" => $entry["place"],
"club" => $this->get($entry["id"]),
"subscriptions" => $entry["subscriptions"],
];
}
use \Nette\SmartObject;
}

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

@ -106,8 +106,8 @@ class Posts
return sizeof($this->posts->where(["wall" => $user, "deleted" => 0]));
}
function getCountOfAllPosts(): int
function getCount(): int
{
return sizeof($this->posts->where(["deleted" => 0]));
return sizeof(clone $this->posts);
}
}

View file

@ -4,7 +4,6 @@ namespace openvk\Web\Models\Repositories;
// use openvk\Web\Models\Entities\User;
// use openvk\Web\Models\Repositories\Users;
use openvk\Web\Models\Entities\TicketComment;
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection;
class TicketComments
@ -20,7 +19,7 @@ class TicketComments
function getCommentsById(int $ticket_id): \Traversable
{
foreach($this->comments->where(['ticket_id' => $ticket_id]) as $comment) yield new TicketComment($comment);
foreach($this->comments->where(['ticket_id' => $ticket_id, 'deleted' => 0]) as $comment) yield new TicketComment($comment);
}
// private function toTicket(?ActiveRow $ar): ?Ticket

View file

@ -22,8 +22,8 @@ class Tickets
function getTickets(int $state = 0, int $page = 1): \Traversable
{
foreach($this->tickets->where(["deleted" => 0, "type" => $state])->order("created DESC")->page($page, OPENVK_DEFAULT_PER_PAGE) as $t)
yield new Ticket($t);
foreach($this->tickets->where(["deleted" => 0, "type" => $state])->order("created DESC")->page($page, OPENVK_DEFAULT_PER_PAGE) as $ticket)
yield new Ticket($ticket);
}
function getTicketCount(int $state = 0): int
@ -31,21 +31,23 @@ class Tickets
return sizeof($this->tickets->where(["deleted" => 0, "type" => $state]));
}
function getTicketsByuId(int $user_id): \Traversable
function getTicketsByUserId(int $userId, int $page = 1): \Traversable
{
foreach($this->tickets->where(['user_id' => $user_id, 'deleted' => 0])->order("created DESC") as $ticket) yield new Ticket($ticket);
foreach($this->tickets->where(["user_id" => $userId, "deleted" => 0])->order("created DESC")->page($page, OPENVK_DEFAULT_PER_PAGE) as $ticket) yield new Ticket($ticket);
}
function getTicketsCountByuId(int $user_id, int $type = 0): int
function getTicketsCountByUserId(int $userId, int $type = NULL): int
{
return sizeof($this->tickets->where(['user_id' => $user_id, 'deleted' => 0, 'type' => $type]));
if(is_null($type))
return sizeof($this->tickets->where(["user_id" => $userId, "deleted" => 0]));
else
return sizeof($this->tickets->where(["user_id" => $userId, "deleted" => 0, "type" => $type]));
}
function getRequestById(int $req_id): ?Ticket
function getRequestById(int $requestId): ?Ticket
{
$requests = $this->tickets->where(['id' => $req_id])->fetch();
$requests = $this->tickets->where(["id" => $requestId])->fetch();
if(!is_null($requests))
return new Req($requests);
else
return null;

View file

@ -51,7 +51,7 @@ class Users
function find(string $query): Util\EntityStream
{
$query = "%$query%";
$result = $this->users->where("CONCAT_WS(' ', first_name, last_name) LIKE ?", $query);
$result = $this->users->where("CONCAT_WS(' ', first_name, last_name) LIKE ?", $query)->where("deleted", 0);
return new Util\EntityStream("User", $result);
}
@ -83,6 +83,24 @@ class Users
return $this->getByShortUrl($address);
}
/**
* If you need to check if the user is an instance administrator, use `$user->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)`.
* This method is more suitable for instance administrators lists
*/
function getInstanceAdmins(bool $excludeHidden = true): \Traversable
{
$query = "SELECT DISTINCT(`profiles`.`id`) FROM `ChandlerACLRelations` JOIN `profiles` ON `ChandlerACLRelations`.`user` = `profiles`.`user` COLLATE utf8mb4_unicode_520_ci WHERE `ChandlerACLRelations`.`group` IN (SELECT `group` FROM `ChandlerACLGroupsPermissions` WHERE `model` = \"admin\" AND `permission` = \"access\")";
if($excludeHidden)
$query .= " AND `ChandlerACLRelations`.`user` NOT IN (SELECT `user` FROM `ChandlerACLRelations` WHERE `group` IN (SELECT `group` FROM `ChandlerACLGroupsPermissions` WHERE `model` = \"hidden_admin\" AND `permission` = \"be\"))";
$query .= " ORDER BY `profiles`.`id`;";
$result = DatabaseConnection::i()->getConnection()->query($query);
foreach($result as $entry)
yield $this->get($entry->id);
}
use \Nette\SmartObject;
}

View file

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

View file

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

View file

@ -1,13 +1,14 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use openvk\Web\Themes\Themepacks;
use openvk\Web\Models\Repositories\{Users, Managers};
use openvk\Web\Models\Repositories\{Users, Managers, Clubs, Posts};
use openvk\Web\Util\Localizator;
use Chandler\Session\Session;
final class AboutPresenter extends OpenVKPresenter
{
protected $banTolerant = true;
protected $activationTolerant = true;
function renderIndex(): void
{
@ -57,6 +58,15 @@ final class AboutPresenter extends OpenVKPresenter
$this->template->themes = Themepacks::i()->getAllThemes();
$this->template->languages = getLanguages();
}
function renderAboutInstance(): void
{
$this->template->usersStats = (new Users)->getStatistics();
$this->template->clubsCount = (new Clubs)->getCount();
$this->template->postsCount = (new Posts)->getCount();
$this->template->popularClubs = iterator_to_array((new Clubs)->getPopularClubs());
$this->template->admins = iterator_to_array((new Users)->getInstanceAdmins());
}
function renderLanguage(): void
{
@ -83,4 +93,37 @@ final class AboutPresenter extends OpenVKPresenter
{
$this->template->languages = getLanguages();
}
function renderRobotsTxt(): void
{
$text = "# robots.txt file for openvk\n"
. "#\n"
. "# this includes only those links that are not in any way\n"
. "# covered from unauthorized persons (for example, due to\n"
. "# lack of rights to access the admin panel)\n\n"
. "User-Agent: *\n"
. "Disallow: /rpc\n"
. "Disallow: /language\n"
. "Disallow: /badbrowser.php\n"
. "Disallow: /logout\n"
. "Disallow: /away.php\n"
. "Disallow: /im?\n"
. "Disallow: *query=\n"
. "Disallow: *?lg=\n"
. "Disallow: *hash=\n"
. "Disallow: *?jReturnTo=\n"
. "Disallow: /method/*\n"
. "Disallow: /token*";
header("Content-Type: text/plain");
exit($text);
}
function renderHumansTxt(): void
{
// :D
header("HTTP/1.1 302 Found");
header("Location: https://github.com/openvk/openvk#readme");
exit;
}
}

View file

@ -72,6 +72,8 @@ final class AdminPresenter extends OpenVKPresenter
$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->save();
break;
@ -107,6 +109,7 @@ final class AdminPresenter extends OpenVKPresenter
$club->setAbout($this->postParam("about"));
$club->setShortCode($this->postParam("shortcode"));
$club->setVerified(empty($this->postParam("verify") ? 0 : 1));
$club->setHide_From_Global_Feed(empty($this->postParam("hide_from_global_feed") ? 0 : 1));
$club->save();
break;
case "ban":

View file

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

View file

@ -122,7 +122,7 @@ final class CommentPresenter extends OpenVKPresenter
$this->flashFail(
"succ",
"Успешно",
"Этот комментарий больше не будет показыватся.<br/><a href='/al_comments.pl/spam?$id'>Отметить как спам</a>?"
"Этот комментарий больше не будет показыватся.<br/><a href='/al_comments/spam?$id'>Отметить как спам</a>?"
);
}
}

View file

@ -207,8 +207,9 @@ final class GroupPresenter extends OpenVKPresenter
$club->setShortcode(empty($this->postParam("shortcode")) ? NULL : $this->postParam("shortcode"));
$club->setWall(empty($this->postParam("wall")) ? 0 : 1);
$club->setAdministrators_List_Display(empty($this->postParam("administrators_list_display")) ? 0 : $this->postParam("administrators_list_display"));
$club->setEveryone_Can_Create_Topics(empty($this->postParam("everyone_can_create_topics")) ? 0 : 1);
$club->setDisplay_Topics_Above_Wall(empty($this->postParam("display_topics_above_wall")) ? 0 : 1);;
$club->setEveryone_Can_Create_Topics(empty($this->postParam("everyone_can_create_topics")) ? 0 : 1);
$club->setDisplay_Topics_Above_Wall(empty($this->postParam("display_topics_above_wall")) ? 0 : 1);
$club->setHide_From_Global_Feed(empty($this->postParam("hide_from_global_feed")) ? 0 : 1);
$website = $this->postParam("website") ?? "";
if(empty($website))

View file

@ -1,6 +1,7 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use MessagePack\MessagePack;
use Chandler\Session\Session;
final class InternalAPIPresenter extends OpenVKPresenter
{
@ -68,4 +69,28 @@ final class InternalAPIPresenter extends OpenVKPresenter
$this->fail(-32603, "Uncaught " . get_class($ex));
}
}
function renderTimezone() {
if($_SERVER["REQUEST_METHOD"] !== "POST")
exit("ты дебил это метод апи");
$sessionOffset = Session::i()->get("_timezoneOffset");
if(is_numeric($this->postParam("timezone", false))) {
$postTZ = intval($this->postParam("timezone", false));
if ($postTZ != $sessionOffset || $sessionOffset == null) {
Session::i()->set("_timezoneOffset", $postTZ ? $postTZ : 3 * MINUTE );
$this->returnJson([
"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)
]);
}
} else {
$this->returnJson([
"success" => 0
]);
}
}
}

View file

@ -124,7 +124,7 @@ final class MessengerPresenter extends OpenVKPresenter
}
$sel = $this->getCorrespondent($sel);
if($sel->getId() !== $this->user->id && $sel->getSubscriptionStatus($this->user->identity) !== 3)
if($sel->getId() !== $this->user->id && !$sel->getPrivacyPermission('messages.write', $this->user->identity))
exit(header("HTTP/1.1 403 Forbidden"));
$cor = new Correspondence($this->user->identity, $sel);

View file

@ -67,6 +67,35 @@ final class NotesPresenter extends OpenVKPresenter
$note->setCreated(time());
$note->setName($this->postParam("name"));
$note->setSource($this->postParam("html"));
$note->setEdited(time());
$note->save();
$this->redirect("/note" . $this->user->id . "_" . $note->getVirtualId());
}
}
function renderEdit(int $owner, int $note_id): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
$note = $this->notes->getNoteById($owner, $note_id);
if(!$note || $note->getOwner()->getId() !== $owner || $note->isDeleted())
$this->notFound();
if(is_null($this->user) || !$note->canBeModifiedBy($this->user->identity))
$this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
$this->template->note = $note;
if($_SERVER["REQUEST_METHOD"] === "POST") {
if(empty($this->postParam("name"))) {
$this->flashFail("err", tr("error"), tr("error_segmentation"));
}
$note->setName($this->postParam("name"));
$note->setSource($this->postParam("html"));
$note->setCached_Content(NULL);
$note->setEdited(time());
$note->save();
$this->redirect("/note" . $this->user->id . "_" . $note->getVirtualId());

View file

@ -8,11 +8,12 @@ use Latte\Engine as TemplatingEngine;
use openvk\Web\Models\Entities\IP;
use openvk\Web\Themes\Themepacks;
use openvk\Web\Models\Repositories\{IPs, Users, APITokens, Tickets};
use Nette\InvalidStateException as ISE;
use WhichBrowser;
abstract class OpenVKPresenter extends SimplePresenter
{
protected $banTolerant = false;
protected $activationTolerant = false;
protected $errorTemplate = "@error";
protected $user = NULL;
@ -35,9 +36,12 @@ abstract class OpenVKPresenter extends SimplePresenter
]));
}
protected function setTempTheme(string $theme): void
protected function setSessionTheme(string $theme, bool $once = false): void
{
Session::i()->set("_tempTheme", $theme);
if($once)
Session::i()->set("_tempTheme", $theme);
else
Session::i()->set("_sessionTheme", $theme);
}
protected function flashFail(string $type, string $title, ?string $message = NULL, ?int $code = NULL, bool $json = false): void
@ -198,6 +202,7 @@ abstract class OpenVKPresenter extends SimplePresenter
$user = Authenticator::i()->getUser();
$this->template->isXmas = intval(date('d')) >= 1 && date('m') == 12 || intval(date('d')) <= 15 && date('m') == 1 ? true : false;
$this->template->isTimezoned = Session::i()->get("_timezoneOffset");
if(!is_null($user)) {
$this->user = (object) [];
@ -206,12 +211,52 @@ abstract class OpenVKPresenter extends SimplePresenter
$this->user->id = $this->user->identity->getId();
$this->template->thisUser = $this->user->identity;
$this->template->userTainted = $user->isTainted();
if($this->user->identity->isDeleted()) {
/*
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⠶⠶⣶⠶⠶⠶⠶⠶⠶⠶⠶⠶⢶⠶⠶⠶⠤⠤⠤⠤⣄⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⣠⡾⠋⠀⠀⠊⠀⠀⠀⠀⠀⠀⠀⠀⠒⠒⠒⠀⠀⠀⠀⠤⢤⣤⣄⠉⠉⠛⠛⠷⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣰⠟⠀⠀⠀⠀⠀⠐⠋⢑⣤⣶⣶⣤⡢⡀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣄⡂⠀⠀⠶⢄⠙⢷⣤⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⣸⡿⠚⠉⡀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⢢⠀⠀⡀⣰⣿⣿⣿⣿⣦⡀⠀⠀⠡⡀⢹⡆⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⢀⣴⠏⠀⣀⣀⣀⡤⢤⣄⣠⣿⣿⣿⣿⣻⣿⣿⣷⠀⢋⣾⠈⠙⣶⠒⢿⣿⣿⣿⣿⡿⠟⠃⠀⡀⠡⠼⣧⡀⠀⠀⠀⠀⠀⠀
⠀⠀⢀⣴⣿⢃⡴⢊⢽⣶⣤⣀⠀⠊⠉⠉⡛⢿⣿⣿⣿⠿⠋⢀⡀⠁⠀⠀⢸⣁⣀⣉⣉⣉⡉⠀⠩⡡⠀⣩⣦⠀⠈⠻⣦⡀⠀⠀⠀⠀
⠀⢠⡟⢡⠇⡞⢀⠆⠀⢻⣿⣿⣷⣄⠀⢀⠈⠂⠈⢁⡤⠚⡟⠉⠀⣀⣀⠀⠈⠳⣍⠓⢆⢀⡠⢀⣨⣴⣿⣿⡏⢀⡆⠀⢸⡇⠀⠀⠀⠀
⠀⣾⠁⢸⠀⠀⢸⠀⠀⠀⠹⣿⣿⣿⣿⣶⣬⣦⣤⡈⠀⠀⠇⠀⠛⠉⣩⣤⣤⣤⣿⣤⣤⣴⣾⣿⣿⣿⣿⣿⣧⠞⠀⠀⢸⡇⠀⠀⠀⠀
⠀⢹⣆⠸⠀⠀⢸⠀⠀⠀⠀⠘⢿⣿⣿⣿⣿⣿⣿⣟⣛⠛⠛⣛⡛⠛⠛⣛⣋⡉⠉⣡⠶⢾⣿⣿⣿⣿⣿⣿⡇⠀⠀⢀⣾⠃⠀⠀⠀⠀
⠀⠀⠻⣆⡀⠀⠈⢂⠀⠀⠀⠠⡈⢻⣿⣿⣿⣿⡟⠁⠈⢧⡼⠉⠙⣆⡞⠁⠈⢹⣴⠃⠀⢸⣿⣿⣿⣿⣿⣿⠃⠀⡆⣾⠃⠀⠀⠀⠀⠀
⠀⠀⠀⠈⢻⣇⠀⠀⠀⠀⠀⠀⢡⠀⠹⣿⣿⣿⣷⡀⠀⣸⡇⠀⠀⣿⠁⠀⠀⠘⣿⠀⠀⠘⣿⣿⣿⣿⣿⣿⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠹⣇⠀⠠⠀⠀⠀⠀⠡⠐⢬⡻⣿⣿⣿⣿⣿⣷⣶⣶⣿⣦⣤⣤⣤⣿⣦⣶⣿⣿⣿⣿⣿⣿⣿⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠹⣧⡀⠡⡀⠀⠀⠀⠑⠄⠙⢎⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⠀⢿⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠈⠳⣤⡐⡄⠀⠀⠀⠈⠂⠀⠱⣌⠻⣿⣿⣿⣿⣿⣿⣿⠿⣿⠟⢻⡏⢻⣿⣿⣿⣿⣿⣿⣿⠀⢸⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⢮⣦⡀⠂⠀⢀⠀⠀⠈⠳⣈⠻⣿⣿⣿⡇⠘⡄⢸⠀⠀⣇⠀⣻⣿⣿⣿⣿⣿⡏⠀⠸⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⢶⣤⣄⡑⠄⠀⠀⠈⠑⠢⠙⠻⢷⣶⣵⣞⣑⣒⣋⣉⣁⣻⣿⠿⠟⠱⠃⡸⠀⣧⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠻⣷⣄⡀⠐⠢⣄⣀⡀⠀⠉⠉⠉⠉⠛⠙⠭⠭⠄⠒⠈⠀⠐⠁⢀⣿⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠷⢦⣤⣤⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣒⡠⠄⣠⡾⠃⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠙⠛⠷⠶⣦⣤⣭⣤⣬⣭⣭⣴⠶⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀
*/
Authenticator::i()->logout();
Session::i()->set("_su", NULL);
$this->flashFail("err", tr("error"), tr("profile_not_found"));
$this->redirect("/", static::REDIRECT_TEMPORARY);
}
if($this->user->identity->isBanned() && !$this->banTolerant) {
header("HTTP/1.1 403 Forbidden");
$this->getTemplatingEngine()->render(__DIR__ . "/templates/@banned.xml", [
"thisUser" => $this->user->identity,
"csrfToken" => $GLOBALS["csrfToken"],
"isTimezoned" => Session::i()->get("_timezoneOffset"),
]);
exit;
}
// ето для емейл уже надо (и по хорошему надо бы избавится от повторяющегося кода мда)
if(!$this->user->identity->isActivated() && !$this->activationTolerant) {
header("HTTP/1.1 403 Forbidden");
$this->getTemplatingEngine()->render(__DIR__ . "/templates/@email.xml", [
"thisUser" => $this->user->identity,
"csrfToken" => $GLOBALS["csrfToken"],
"isTimezoned" => Session::i()->get("_timezoneOffset"),
]);
exit;
}
@ -221,7 +266,7 @@ abstract class OpenVKPresenter extends SimplePresenter
$this->user->identity->save();
}
$this->template->ticketAnsweredCount = (new Tickets)->getTicketsCountByuId($this->user->id, 1);
$this->template->ticketAnsweredCount = (new Tickets)->getTicketsCountByUserId($this->user->id, 1);
if($user->can("write")->model("openvk\Web\Models\Entities\TicketReply")->whichBelongsTo(0))
$this->template->helpdeskTicketNotAnsweredCount = (new Tickets)->getTicketCount(0);
}
@ -235,10 +280,17 @@ abstract class OpenVKPresenter extends SimplePresenter
{
parent::onBeforeRender();
$whichbrowser = new WhichBrowser\Parser(getallheaders());
$mobiletheme = OPENVK_ROOT_CONF["openvk"]["preferences"]["defaultMobileTheme"];
if($mobiletheme && $whichbrowser->isType('mobile') && Session::i()->get("_tempTheme") == null)
$this->setSessionTheme($mobiletheme);
$theme = NULL;
if(Session::i()->get("_tempTheme")) {
$theme = Themepacks::i()[Session::i()->get("_tempTheme", "ovk")];
Session::i()->set("_tempTheme", NULL);
} else if(Session::i()->get("_sessionTheme")) {
$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()) {

View file

@ -70,9 +70,9 @@ final class PhotosPresenter extends OpenVKPresenter
}
if($_SERVER["REQUEST_METHOD"] === "POST") {
if(empty($this->postParam("name"))) {
if(empty($this->postParam("name")))
$this->flashFail("err", tr("error"), tr("error_segmentation"));
}
$album = new Album;
$album->setOwner(isset($club) ? $club->getId() * -1 : $this->user->id);
$album->setName($this->postParam("name"));
@ -80,7 +80,10 @@ final class PhotosPresenter extends OpenVKPresenter
$album->setCreated(time());
$album->save();
$this->redirect("/album" . $album->getOwner()->getId() . "_" . $album->getId(), static::REDIRECT_TEMPORARY);
if(isset($club))
$this->redirect("/album-" . $album->getOwner()->getId() . "_" . $album->getId(), static::REDIRECT_TEMPORARY);
else
$this->redirect("/album" . $album->getOwner()->getId() . "_" . $album->getId(), static::REDIRECT_TEMPORARY);
}
}
@ -118,10 +121,11 @@ final class PhotosPresenter extends OpenVKPresenter
if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity))
$this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
$name = $album->getName();
$name = $album->getName();
$owner = $album->getOwner();
$album->delete();
$this->flash("succ", "Альбом удалён", "Альбом $name был успешно удалён.");
$this->redirect("/albums" . $this->user->id);
$this->redirect("/albums" . ($owner instanceof Club ? "-" : "") . $owner->getId());
}
function renderAlbum(int $owner, int $id): void

View file

@ -6,7 +6,7 @@ use openvk\Web\Models\Entities\TicketComment;
use openvk\Web\Models\Repositories\TicketComments;
use openvk\Web\Util\Telegram;
use Chandler\Session\Session;
use Netcarver\Textile;
use Parsedown;
final class SupportPresenter extends OpenVKPresenter
{
@ -28,17 +28,19 @@ final class SupportPresenter extends OpenVKPresenter
$this->assertUserLoggedIn();
$this->template->mode = in_array($this->queryParam("act"), ["faq", "new", "list"]) ? $this->queryParam("act") : "faq";
$tickets = $this->tickets->getTicketsByuId($this->user->id);
if($tickets)
$this->template->tickets = $tickets;
$this->template->count = $this->tickets->getTicketsCountByUserId($this->user->id);
if($this->template->mode === "list") {
$this->template->page = (int) ($this->queryParam("p") ?? 1);
$this->template->tickets = $this->tickets->getTicketsByUserId($this->user->id, $this->template->page);
}
if($_SERVER["REQUEST_METHOD"] === "POST") {
if(!empty($this->postParam("name")) && !empty($this->postParam("text"))) {
$this->assertNoCSRF();
$this->willExecuteWriteAction();
$ticket = new Ticket;
$ticket->setType(0);
$ticket->setUser_id($this->user->id);
$ticket->setUser_Id($this->user->id);
$ticket->setName($this->postParam("name"));
$ticket->setText($this->postParam("text"));
$ticket->setcreated(time());
@ -111,11 +113,11 @@ final class SupportPresenter extends OpenVKPresenter
if(!$ticket || $ticket->isDeleted() != 0 || $ticket->getUserId() !== $this->user->id && !$this->hasPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0)) {
$this->notFound();
} else {
header("HTTP/1.1 302 Found");
if($ticket->getUserId() !== $this->user->id && $this->hasPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0))
header("Location: /support/tickets");
$this->redirect("/support/tickets");
else
header("Location: /support");
$this->redirect("/support");
$ticket->delete();
}
}
@ -135,8 +137,7 @@ final class SupportPresenter extends OpenVKPresenter
if(!empty($this->postParam("text"))) {
$ticket->setType(0);
$ticket->save();
$this->assertNoCSRF();
$this->willExecuteWriteAction();
$comment = new TicketComment;
@ -182,13 +183,12 @@ final class SupportPresenter extends OpenVKPresenter
if(!empty($this->postParam("text")) && !empty($this->postParam("status"))) {
$ticket->setType($this->postParam("status"));
$ticket->save();
$this->assertNoCSRF();
$comment = new TicketComment;
$comment->setUser_id($this->user->id);
$comment->setUser_type(1);
$comment->setText($this->postParam("text"));
$comment->setTicket_id($id);
$comment->setTicket_Id($id);
$comment->setCreated(time());
$comment->save();
} elseif(empty($this->postParam("text"))) {
@ -204,10 +204,10 @@ final class SupportPresenter extends OpenVKPresenter
{
$lang = Session::i()->get("lang", "ru");
$base = OPENVK_ROOT . "/data/knowledgebase";
if(file_exists("$base/$name.$lang.textile"))
$file = "$base/$name.$lang.textile";
else if(file_exists("$base/$name.textile"))
$file = "$base/$name.textile";
if(file_exists("$base/$name.$lang.md"))
$file = "$base/$name.$lang.md";
else if(file_exists("$base/$name.md"))
$file = "$base/$name.md";
else
$this->notFound();
@ -219,11 +219,34 @@ final class SupportPresenter extends OpenVKPresenter
array_shift($lines);
}
$content = implode("\r\n", $lines);
$content = implode($lines);
$parser = new Textile\Parser;
$parser = new Parsedown();
$this->template->heading = $heading;
$this->template->content = $parser->parse($content);
$this->template->content = $parser->text($content);
}
function renderDeleteComment(int $id): void
{
$this->assertUserLoggedIn();
$this->assertNoCSRF();
$comment = $this->comments->get($id);
if(is_null($comment))
$this->notFound();
$ticket = $comment->getTicket();
if($ticket->isDeleted())
$this->notFound();
if(!($ticket->getUserId() === $this->user->id && $comment->getUType() === 0))
$this->assertPermission("openvk\Web\Models\Entities\TicketReply", "write", 0);
$this->willExecuteWriteAction();
$comment->delete();
$this->flashFail("succ", tr("ticket_changed"), tr("ticket_changed_comment"));
}
function renderRateAnswer(int $id, int $mark): void

View file

@ -9,6 +9,7 @@ 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 Chandler\Security\Authenticator;
@ -180,10 +181,18 @@ final class UserPresenter extends OpenVKPresenter
$this->willExecuteWriteAction($_GET['act'] === "status");
if($_GET['act'] === "main" || $_GET['act'] == NULL) {
$user->setFirst_Name(empty($this->postParam("first_name")) ? $user->getFirstName() : $this->postParam("first_name"));
$user->setLast_Name(empty($this->postParam("last_name")) ? "" : $this->postParam("last_name"));
try {
$user->setFirst_Name(empty($this->postParam("first_name")) ? $user->getFirstName() : $this->postParam("first_name"));
$user->setLast_Name(empty($this->postParam("last_name")) ? "" : $this->postParam("last_name"));
} catch(InvalidUserNameException $ex) {
$this->flashFail("err", tr("error"), tr("invalid_real_name"));
}
$user->setPseudo(empty($this->postParam("pseudo")) ? NULL : $this->postParam("pseudo"));
$user->setStatus(empty($this->postParam("status")) ? NULL : $this->postParam("status"));
$user->setHometown(empty($this->postParam("hometown")) ? NULL : $this->postParam("hometown"));
if (strtotime($this->postParam("birthday")) < time())
$user->setBirthday(strtotime($this->postParam("birthday")));
@ -222,7 +231,7 @@ final class UserPresenter extends OpenVKPresenter
$user->setCity(empty($this->postParam("city")) ? NULL : $this->postParam("city"));
$user->setAddress(empty($this->postParam("address")) ? NULL : $this->postParam("address"));
$website = $this->postParam("website") ?? "";
if(empty($website))
$user->setWebsite(NULL);
@ -372,6 +381,7 @@ final class UserPresenter extends OpenVKPresenter
"friends.read",
"friends.add",
"wall.write",
"messages.write",
];
foreach($settings as $setting) {
$input = $this->postParam(str_replace(".", "_", $setting));
@ -395,8 +405,8 @@ final class UserPresenter extends OpenVKPresenter
} else if($_GET['act'] === "interface") {
if (isset(Themepacks::i()[$this->postParam("style")]) || $this->postParam("style") === Themepacks::DEFAULT_THEME_ID)
{
$user->setStyle($this->postParam("style"));
$this->setTempTheme($this->postParam("style"));
if ($this->postParam("theme_for_session") != "1") $user->setStyle($this->postParam("style"));
$this->setSessionTheme($this->postParam("style"));
}
if ($this->postParam("style_avatar") <= 2 && $this->postParam("style_avatar") >= 0)
@ -412,13 +422,14 @@ final class UserPresenter extends OpenVKPresenter
$user->setNsfwTolerance((int) $this->postParam("nsfw"));
} else if($_GET['act'] === "lMenu") {
$settings = [
"menu_bildoj" => "photos",
"menu_filmetoj" => "videos",
"menu_mesagoj" => "messages",
"menu_notatoj" => "notes",
"menu_grupoj" => "groups",
"menu_novajoj" => "news",
"menu_ligiloj" => "links",
"menu_bildoj" => "photos",
"menu_filmetoj" => "videos",
"menu_mesagoj" => "messages",
"menu_notatoj" => "notes",
"menu_grupoj" => "groups",
"menu_novajoj" => "news",
"menu_ligiloj" => "links",
"menu_standardo" => "poster",
];
foreach($settings as $checkbox => $setting)
$user->setLeftMenuItemStatus($setting, $this->checkbox($checkbox));
@ -489,11 +500,16 @@ final class UserPresenter extends OpenVKPresenter
$this->template->secret = $secret;
}
$issuer = OPENVK_ROOT_CONF["openvk"]["appearance"]["name"];
$email = $this->user->identity->getEmail();
$this->template->qrCode = substr((new QRCode(new QROptions([
// 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();
$qrCode = explode("base64,", (new QRCode(new QROptions([
"imageTransparent" => false
])))->render("otpauth://totp/$issuer:$email?secret=$secret&issuer=$issuer"), 22);
])))->render("otpauth://totp/$issuer:$email?secret=$secret&issuer=$issuer"));
$this->template->qrCodeType = substr($qrCode[0], 5);
$this->template->qrCodeData = $qrCode[1];
}
function renderDisableTwoFactorAuth(): void

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
{
@ -52,14 +55,14 @@ final class WallPresenter extends OpenVKPresenter
$canPost = $owner->getPrivacyPermission("wall.write", $this->user->identity);
else
$this->flashFail("err", tr("error"), "Ошибка доступа");
} else if($user < 0) {
} else if($user < 0) {
if($owner->canBeModifiedBy($this->user->identity))
$canPost = true;
else
$canPost = $owner->canPost();
} else {
$canPost = false;
}
}
if ($embedded == true) $this->template->_template = "components/wall.xml";
$this->template->oObj = $owner;
@ -82,6 +85,49 @@ final class WallPresenter extends OpenVKPresenter
{
$this->renderWall($user, true);
}
function renderRSS(int $user): void
{
if(false)
exit("Ошибка доступа: " . (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
{
@ -122,24 +168,24 @@ final class WallPresenter extends OpenVKPresenter
$page = (int) ($_GET["p"] ?? 1);
$pPage = min((int) ($_GET["posts"] ?? OPENVK_DEFAULT_PER_PAGE), 50);
$posts = DatabaseConnection::i()
->getContext()
->table("posts")
->where("deleted", 0)
->order("created DESC");
$queryBase = "FROM `posts` LEFT JOIN `groups` ON GREATEST(`posts`.`wall`, 0) = 0 AND `groups`.`id` = ABS(`posts`.`wall`) WHERE (`groups`.`hide_from_global_feed` = 0 OR `groups`.`name` IS NULL) AND `posts`.`deleted` = 0";
if($this->user->identity->getNsfwTolerance() === User::NSFW_INTOLERANT)
$posts = $posts->where("nsfw", false);
$queryBase .= " AND `nsfw` = 0";
$posts = DatabaseConnection::i()->getConnection()->query("SELECT `posts`.`id` " . $queryBase . " ORDER BY `created` DESC LIMIT " . $pPage . " OFFSET " . ($page - 1) * $pPage);
$count = DatabaseConnection::i()->getConnection()->query("SELECT COUNT(*) " . $queryBase)->fetch()->{"COUNT(*)"};
$this->template->_template = "Wall/Feed.xml";
$this->template->globalFeed = true;
$this->template->paginatorConf = (object) [
"count" => sizeof($posts),
"count" => $count,
"page" => (int) ($_GET["p"] ?? 1),
"amount" => sizeof($posts->page($page, $pPage)),
"amount" => sizeof($posts),
"perPage" => $pPage,
];
foreach($posts->page($page, $pPage) as $post)
foreach($posts as $post)
$this->template->posts[] = $this->posts->get($post->id);
}

View file

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

View file

@ -1,24 +1,26 @@
{var instance_name = OPENVK_ROOT_CONF['openvk']['appearance']['name']}
<!DOCTYPE html>
<html>
<head>
<title>Произошёл троллинг... | OpenVK</title>
<title>An error occurred - {$instance_name}</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<style>
@keyframes gradient {
0% {
background-position:0% 50%
background-position: 0% 50%;
}
50% {
background-position:100% 50%
background-position: 100% 50%;
}
100% {
background-position:0% 50%
background-position: 0% 50%;
}
}
body {
background: linear-gradient(-30deg, #2b2b2b, #f479ff);
color: #fff;
@ -27,25 +29,25 @@
animation: gradient 30s ease-in-out infinite;
background-size: 400% 400%;
}
#error {
width: 35rem;
max-width: 85vw;
}
main {
display: flex;
justify-content: space-evenly;
align-items: center;
height: 97vh;
}
@media screen and (max-width: 768px) {
main {
flex-direction: column;
justify-content: center;
}
main > p:first-of-type {
display: none;
}
@ -56,7 +58,7 @@
<!-- OpenVK ERROR REPORT: -->
<!-- HTTP {$code} -->
<!-- {$msg} -->
<main>
<p>{$desc}</p>
<a href="/"><img src="/assets/packages/static/openvk/img/oof.apng" alt="Ouch" id="error" /></a>

View file

@ -1,5 +1,6 @@
{var instance_name = OPENVK_ROOT_CONF['openvk']['appearance']['name']}
<!DOCTYPE html>
<html n:if="!isset($parentModule) || substr($parentModule, 0, 21) === 'libchandler:absolute.'">
<head>
<title>
@ -9,28 +10,35 @@
<link rel="shortcut icon" href="/assets/packages/static/openvk/img/icon.ico" />
<meta name="application-name" content="{$instance_name}" />
<meta n:ifset="$csrfToken" name="csrf" value="{$csrfToken}" />
<script src="/language/{php echo getLanguage()}.js" crossorigin="anonymous"></script>
{script "js/node_modules/jquery/dist/jquery.min.js"}
{script "js/node_modules/jquery/dist/jquery.min.js"}
{script "js/node_modules/umbrellajs/umbrella.min.js"}
{script "js/l10n.js"}
{script "js/openvk.cls.js"}
{if $isTimezoned == null}
{script "js/timezone.js"}
{/if}
{ifset $thisUser}
{if $thisUser->getNsfwTolerance() < 2}
{css "css/nsfw-posts.css"}
{/if}
{if $theme !== null}
{if $theme->inheritDefault()}
{if $theme->inheritDefault()}
{css "css/style.css"}
{css "css/dialog.css"}
{css "css/notifications.css"}
{if $isXmas}
{css "css/xmas.css"}
{/if}
{/if}
<link rel="stylesheet" href="/themepack/{$theme->getId()}/{$theme->getVersion()}/stylesheet/styles.css" />
{if $isXmas}
<link rel="stylesheet" href="/themepack/{$theme->getId()}/{$theme->getVersion()}/resource/xmas.css" />
{/if}
@ -38,34 +46,37 @@
{css "css/style.css"}
{css "css/dialog.css"}
{css "css/notifications.css"}
{if $isXmas}
{css "css/xmas.css"}
{/if}
{/if}
{if $thisUser->getStyleAvatar() == 1}
{css "css/avatar.1.css"}
{/if}
{if $thisUser->getStyleAvatar() == 2}
{css "css/avatar.2.css"}
{css "css/avatar.2.css"}
{/if}
{if $thisUser->hasMicroblogEnabled() == 1}
{css "css/microblog.css"}
{css "css/microblog.css"}
{/if}
{else}
{css "css/style.css"}
{css "css/dialog.css"}
{css "css/nsfw-posts.css"}
{css "css/notifications.css"}
{if $isXmas}
{css "css/xmas.css"}
{/if}
{/ifset}
{ifset headIncludes}{include headIncludes}{/ifset}
{ifset headIncludes}
{include headIncludes}
{/ifset}
</head>
<body>
<div id="sudo-banner" n:if="isset($thisUser) && $userTainted">
@ -75,198 +86,175 @@
Нажмите <a href="/setSID/unset?hash={rawurlencode($csrfToken)}">здесь</a>, чтобы выйти.
</p>
</div>
<div n:if="OPENVK_ROOT_CONF['openvk']['preferences']['bellsAndWhistles']['testLabel']" id="test-label">FOR TESTING PURPOSES ONLY</div>
<div class="notifications_global_wrap"></div>
<div class="notifications_global_wrap"></div>
<div class="dimmer"></div>
<div class="toTop">
Вверх
{_to_top}
</div>
<div class="layout">
<div id="xhead" class="dm"></div>
<div class="page_header {if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME}page_custom_header{/if}">
<a href="/" class="home_button {if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME}home_button_custom{/if}" title="{$instance_name}">{$instance_name}</a>
<div n:if="isset($thisUser) ? !$thisUser->isBanned() : true" class="header_navigation">
<div n:if="isset($thisUser) ? (!$thisUser->isBanned() XOR !$thisUser->isActivated()) : true" class="header_navigation">
{ifset $thisUser}
<div class="link">
<a href="/">{_"header_home"}</a>
</div>
<div class="link">
<a href="/search?type=groups">{_"header_groups"}</a>
</div>
<div class="link">
<a href="/search">{_"header_search"}</a>
</div>
<div class="link">
<a href="/invite">
{_"header_invite"}
</a>
</div>
<div class="link">
<a href="/support">
{_"header_help"}
<b n:if="$ticketAnsweredCount > 0">({$ticketAnsweredCount})</b>
</a>
</div>
<div class="link">
<a href="/logout?hash={urlencode($csrfToken)}">{_"header_log_out"}</a>
</div>
<div class="link">
<form action="/search" method="get">
<input type="search" name="query" placeholder="{_"header_search"}" style="height: 20px;background: url('/assets/packages/static/openvk/img/search_icon.png') no-repeat 3px 4px; background-color: #fff; padding-left: 18px;width: 120px;" />
</form>
</div>
<div class="link">
<a href="/">{_header_home}</a>
</div>
<div class="link">
<a href="/search?type=groups">{_header_groups}</a>
</div>
<div class="link">
<a href="/search">{_header_search}</a>
</div>
<div class="link">
<a href="/invite">{_header_invite}</a>
</div>
<div class="link">
<a href="/support">{_header_help} <b n:if="$ticketAnsweredCount > 0">({$ticketAnsweredCount})</b></a>
</div>
<div class="link">
<a href="/logout?hash={urlencode($csrfToken)}">{_header_log_out}</a>
</div>
<div class="link">
<form action="/search" method="get">
<input type="search" name="query" placeholder="{_header_search}" style="height: 20px;background: url('/assets/packages/static/openvk/img/search_icon.png') no-repeat 3px 4px; background-color: #fff; padding-left: 18px;width: 120px;" />
</form>
</div>
{else}
<div class="link">
<a href="/login">{_"header_login"}</a>
<a href="/login">{_header_login}</a>
</div>
<div n:if="OPENVK_ROOT_CONF['openvk']['preferences']['registration']['enable']" class="link">
<a href="/reg">{_"header_registration"}</a>
<a href="/reg">{_header_registration}</a>
</div>
<div class="link">
<a href="/support">{_"header_help"}</a>
<a href="/support">{_header_help}</a>
</div>
{/ifset}
</div>
</div>
<div class="sidebar">
<div class="navigation">
{ifset $thisUser}
{if !$thisUser->isBanned()}
<a href="/edit" class="link edit-button">{_"edit_button"}</a>
<a href="{$thisUser->getURL()}" class="link">{_"my_page"}</a>
<a href="/friends{$thisUser->getId()}" class="link">{_"my_friends"}
{if $thisUser->getFollowersCount() > 0}
<object type="internal/link">
<a href="/friends{$thisUser->getId()}?act=incoming">
(<b>{$thisUser->getFollowersCount()}</b>)
{ifset $thisUser}
{if !$thisUser->isBanned() XOR !$thisUser->isActivated()}
<a href="/edit" class="link edit-button">{_edit_button}</a>
<a href="{$thisUser->getURL()}" class="link">{_my_page}</a>
<a href="/friends{$thisUser->getId()}" class="link">{_my_friends}
<object type="internal/link" n:if="$thisUser->getFollowersCount() > 0">
<a href="/friends{$thisUser->getId()}?act=incoming">
(<b>{$thisUser->getFollowersCount()}</b>)
</a>
</object>
</a>
</object>
{/if}</a>
<a n:if="$thisUser->getLeftMenuItemStatus('photos')" href="/albums{$thisUser->getId()}" class="link">{_"my_photos"}</a>
<a n:if="$thisUser->getLeftMenuItemStatus('videos')" href="/videos{$thisUser->getId()}" class="link">{_"my_videos"}</a>
<a n:if="$thisUser->getLeftMenuItemStatus('messages')" href="/im" class="link">{_"my_messages"}
{if $thisUser->getUnreadMessagesCount() > 0}
<object type="internal/link">
(<b>{$thisUser->getUnreadMessagesCount()}</b>)
</object>
{/if}
</a>
<a n:if="$thisUser->getLeftMenuItemStatus('notes')" href="/notes{$thisUser->getId()}" class="link">{_"my_notes"}</a>
<a n:if="$thisUser->getLeftMenuItemStatus('groups')" href="/groups{$thisUser->getId()}" class="link">{_"my_groups"}</a>
<a n:if="$thisUser->getLeftMenuItemStatus('news')" href="/feed" class="link">{_"my_feed"}</a>
<a href="/notifications" class="link">{_my_feedback}
{if $thisUser->getNotificationsCount() > 0}
(<b>{$thisUser->getNotificationsCount()}</b>)
{/if}
</a>
<a href="/settings" class="link">{_"my_settings"}</a>
{var canAccessAdminPanel = $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)}
{var canAccessHelpdesk = $thisUser->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
{var menuLinksAvaiable = sizeof(OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links']) > 0 && $thisUser->getLeftMenuItemStatus('links')}
<div n:if="$canAccessAdminPanel || $canAccessHelpdesk || $menuLinksAvaiable" class="menu_divider"></div>
{if $canAccessAdminPanel}
<a href="/admin" class="link">Админ-панель</a>
{/if}
{if $canAccessHelpdesk}
<a href="/support/tickets" class="link">Helpdesk
<a n:if="$thisUser->getLeftMenuItemStatus('photos')" href="/albums{$thisUser->getId()}" class="link">{_my_photos}</a>
<a n:if="$thisUser->getLeftMenuItemStatus('videos')" href="/videos{$thisUser->getId()}" class="link">{_my_videos}</a>
<a n:if="$thisUser->getLeftMenuItemStatus('messages')" href="/im" class="link">{_my_messages}
<object type="internal/link" n:if="$thisUser->getUnreadMessagesCount() > 0">
(<b>{$thisUser->getUnreadMessagesCount()}</b>)
</object>
</a>
<a n:if="$thisUser->getLeftMenuItemStatus('notes')" href="/notes{$thisUser->getId()}" class="link">{_my_notes}</a>
<a n:if="$thisUser->getLeftMenuItemStatus('groups')" href="/groups{$thisUser->getId()}" class="link">{_my_groups}</a>
<a n:if="$thisUser->getLeftMenuItemStatus('news')" href="/feed" class="link">{_my_feed}</a>
<a href="/notifications" class="link">{_my_feedback}
{if $thisUser->getNotificationsCount() > 0}
(<b>{$thisUser->getNotificationsCount()}</b>)
{/if}
</a>
<a href="/settings" class="link">{_my_settings}</a>
{var canAccessAdminPanel = $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)}
{var canAccessHelpdesk = $thisUser->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
{var menuLinksAvaiable = sizeof(OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links']) > 0 && $thisUser->getLeftMenuItemStatus('links')}
<div n:if="$canAccessAdminPanel || $canAccessHelpdesk || $menuLinksAvaiable" class="menu_divider"></div>
<a href="/admin" class="link" n:if="$canAccessAdminPanel">Админ-панель</a>
<a href="/support/tickets" class="link" n:if="$canAccessHelpdesk">Helpdesk
{if $helpdeskTicketNotAnsweredCount > 0}
(<b>{$helpdeskTicketNotAnsweredCount}</b>)
{/if}
</a>
{/if}
<a
n:if="$thisUser->getLeftMenuItemStatus('links')"
n:foreach="OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links'] as $menuItem"
href="{$menuItem['url']}"
target="_blank"
class="link">{strpos($menuItem["name"], "@") === 0 ? tr(substr($menuItem["name"], 1)) : $menuItem["name"]}</a>
<div id="_groupListPinnedGroups">
<div n:if="$thisUser->getPinnedClubCount() > 0" class="menu_divider"></div>
<a
n:foreach="$thisUser->getPinnedClubs() as $club"
href="{$club->getURL()}"
class="link group_link">{$club->getName()}</a>
</div>
<a
n:if="OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['enable']"
href="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['link']}" >
<img
src="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['src']}"
alt="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['caption']}"
class="psa-poster"
style="max-width: 100%; margin-top: 50px;" />
</a>
<a n:if="$thisUser->getLeftMenuItemStatus('links')" n:foreach="OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links'] as $menuItem" href="{$menuItem['url']}" target="_blank" class="link">{strpos($menuItem["name"], "@") === 0 ? tr(substr($menuItem["name"], 1)) : $menuItem["name"]}</a>
<div id="_groupListPinnedGroups">
<div n:if="$thisUser->getPinnedClubCount() > 0" class="menu_divider"></div>
<a n:foreach="$thisUser->getPinnedClubs() as $club" href="{$club->getURL()}" class="link group_link">{$club->getName()}</a>
</div>
<a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['enable'] && $thisUser->getLeftMenuItemStatus('poster')" href="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['link']}" >
<img src="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['src']}" alt="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['caption']}" class="psa-poster" style="max-width: 100%; margin-top: 50px;" />
</a>
{elseif !$thisUser->isActivated()}
<a href="/logout?hash={urlencode($csrfToken)}" class="link">{_menu_logout}</a>
{else}
<a href="/support" class="link">{_menu_support}
{if $ticketAnsweredCount > 0}
(<b>{$ticketAnsweredCount}</b>)
{/if}
</a>
<a href="/logout?hash={urlencode($csrfToken)}" class="link">{_menu_logout}</a>
{/if}
{else}
<a href="/support" class="link">{_"menu_support"}</a>
<a href="/logout?hash={urlencode($csrfToken)}" class="link">{_"menu_logout"}</a>
{/if}
{else}
<form id="fastLogin" action="/login" method="POST" enctype="multipart/form-data">
<label for="login"><span>{_"email"}:</span></label>
<input id="login" type="text" name="login" required />
<label for="password"><span>{_"password"}:</span></label>
<input id="password" type="password" name="password" required />
<input type="hidden" name="jReturnTo" value="{$_SERVER['REQUEST_URI']}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" value="{_'log_in'}" class="button" style="display: inline-block;" />
<a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['registration']['enable']" href="/reg" class="button" style="display: inline-block;" >{_registration}</a><br><br>
<a href="/restore.pl">{_"forgot_password"}</a>
</form>
{/ifset}
<form id="fastLogin" action="/login" method="POST" enctype="multipart/form-data">
<label for="login"><span>{_email}:</span></label>
<input id="login" type="text" name="login" required />
<label for="password"><span>{_password}:</span></label>
<input id="password" type="password" name="password" required />
<input type="hidden" name="jReturnTo" value="{$_SERVER['REQUEST_URI']}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" value="{_log_in}" class="button" style="display: inline-block;" />
<a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['registration']['enable']" href="/reg" class="button" style="display: inline-block;">{_registration}</a><br><br>
<a href="/restore">{_forgot_password}</a>
</form>
{/ifset}
</div>
</div>
<div class="page_body">
<div id="wrapH">
<div id="wrapHI">
<div n:ifcontent class="page_yellowheader">
{include header}
{include header}
</div>
</div>
</div>
{ifset wrap}
{ifset $flashMessage}
<div class="msg msg_{$flashMessage->type}">
{ifset wrap}
<div class="msg msg_{$flashMessage->type}" n:ifset="$flashMessage">
<b>{$flashMessage->title}</b><br/>
{$flashMessage->msg|noescape}
</div>
{include wrap}
{else}
<div class="wrap2">
<div class="wrap1">
<div id="auth" class="page-wrap">
<div class="page_content">
<div class="msg msg_{$flashMessage->type}" n:ifset="$flashMessage">
<b>{$flashMessage->title}</b><br/>
{$flashMessage->msg|noescape}
</div>
{/ifset}
{include wrap}
{else}
<div class="wrap2">
<div class="wrap1">
<div id="auth" class="page-wrap">
<div class="page_content">
{ifset $flashMessage}
<div class="msg msg_{$flashMessage->type}">
<b>{$flashMessage->title}</b><br/>
{$flashMessage->msg|noescape}
</div>
{/ifset}
{include content}
</div>
</div>
</div>
{include content}
</div>
{/ifset}
</div>
</div>
</div>
{/ifset}
</div>
</div>
<div class="page_footer">
{var dbVersion = \Chandler\Database\DatabaseConnection::i()->getConnection()->getPdo()->getAttribute(\PDO::ATTR_SERVER_VERSION)}
<div class="navigation_footer">
<a href="/about" class="link">{_footer_about_instance}</a>
<a href="/blog" class="link">{_footer_blog}</a>
<a href="/support" class="link">{_footer_help}</a>
<a href="/dev" target="_blank" class="link">{_footer_developers}</a>
@ -274,11 +262,11 @@
<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>
{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"}
@ -287,20 +275,20 @@
{script "js/scroll.js"}
{script "js/al_wall.js"}
{script "js/al_api.js"}
{ifset $thisUser}
{script "js/al_notifs.js"}
{/ifset}
<script src="https://unpkg.com/fartscroll@1.0.0/fartscroll.js"></script>
<script n:if="OPENVK_ROOT_CONF['openvk']['preferences']['bellsAndWhistles']['fartscroll']">
fartscroll(400);
</script>
<script n:if="OPENVK_ROOT_CONF['openvk']['telemetry']['plausible']['enable']"
async defer data-domain="{php echo OPENVK_ROOT_CONF['openvk']['telemetry']['plausible']['domain']}"
src="{php echo OPENVK_ROOT_CONF['openvk']['telemetry']['plausible']['server']}js/plausible.js"></script>
{if OPENVK_ROOT_CONF['openvk']['preferences']['bellsAndWhistles']['fartscroll']}
<script src="https://unpkg.com/fartscroll@1.0.0/fartscroll.js"></script>
<script>
fartscroll(400);
</script>
{/if}
<script n:if="OPENVK_ROOT_CONF['openvk']['telemetry']['plausible']['enable']" async defer data-domain="{php echo OPENVK_ROOT_CONF['openvk']['telemetry']['plausible']['domain']}" src="{php echo OPENVK_ROOT_CONF['openvk']['telemetry']['plausible']['server']}js/plausible.js"></script>
{ifset bodyScripts}
{include bodyScripts}
{/ifset}
@ -309,6 +297,6 @@
{if isset($parentModule) && substr($parentModule, 0, 21) !== 'libchandler:absolute.'}
<!-- INCLUDING TEMPLATE FROM PARENTMODULE: {$parentModule} -->
{include content}
{/if}

View file

@ -2,57 +2,69 @@
{block wrap}
<div class="page_wrap">
<div n:ifset="tabs" class="tabs">
<div n:ifset="tabs" n:ifcontent class="tabs">
{include tabs}
</div>
<div class="container_gray">
{var data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
{if sizeof($data) > 0}
<div class="content" n:foreach="$data as $dat">
<table>
<tbody>
<tr>
<td valign="top">
<a href="{include link, x => $dat}">
{include preview, x => $dat}
</a>
</td>
<td valign="top" style="width: 100%">
<a href="{include link, x => $dat}">
<b>
{include name, x => $dat}
</b>
</a>
<br/>
{include description, x => $dat}
</td>
<td n:ifset="actions" valign="top" class="action_links" style="width: 150px">
{include actions, x => $dat}
</td>
</tr>
</tbody>
</table>
</div>
<div style="padding: 8px;">
{include "components/paginator.xml", conf => (object) [
"page" => $page,
"count" => $count,
"amount" => sizeof($data),
"perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE,
"atBottom" => true,
]}
</div>
{else}
{ifset customErrorMessage}
{include customErrorMessage}
{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)}
{if sizeof($data) > 0}
<div class="content" n:foreach="$data as $dat">
<table>
<tbody>
<tr>
<td valign="top">
<a href="{include link, x => $dat}">
{include preview, x => $dat}
</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; text-transform: lowercase;">
{include actions, x => $dat}
</td>
</tr>
</tbody>
</table>
</div>
{include "components/paginator.xml", conf => (object) [
"page" => $page,
"count" => $count,
"amount" => sizeof($data),
"perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE,
"atBottom" => true,
]}
{else}
{include "components/nothing.xml"}
{/ifset}
{/if}
</div>
{ifset customErrorMessage}
{include customErrorMessage}
{else}
{include "components/nothing.xml"}
{/ifset}
{/if}
</div>
{/ifset}
{ifset bottom}
{include bottom}
{/ifset}
</div>
{/block}
{/block}

View file

@ -0,0 +1,61 @@
{extends "../@layout.xml"}
{block title}{_about_this_instance}{/block}
{block header}
{_about_this_instance}
{/block}
{block content}
<table width="100%" cellspacing="0" cellpadding="0">
<tbody>
<tr valign="top">
<td width="250" {if sizeof($admins) > 0}style="padding-right: 10px;"{/if}>
<h4>{_statistics}</h4>
<div style="margin-top: 5px;">
{_on_this_instance_are}
<ul>
<li><span>{tr("about_users", $usersStats->all)|noescape}</span></li>
<li><span>{tr("about_online_users", $usersStats->online)|noescape}</span></li>
<li><span>{tr("about_active_users", $usersStats->active)|noescape}</span></li>
<li><span>{tr("about_groups", $clubsCount)|noescape}</span></li>
<li><span>{tr("about_wall_posts", $postsCount)|noescape}</span></li>
</ul>
</div>
</td>
<td n:if="sizeof($admins) > 0">
<h4>{_administrators}</h4>
<div style="margin-left: 5px; margin-top: 5px;">
<div n:foreach="$admins as $admin" class="avatar-list-item">
<div class="avatar">
<a href="{$admin->getURL()}">
<img class="ava" src="{$admin->getAvatarUrl()}" />
</a>
</div>
{* Это наверное костыль, ну да ладно *}
<div n:class="info, mb_strlen($admin->getCanonicalName()) < 22 ? info-centered">
<a href="{$admin->getURL()}" class="title">{$admin->getCanonicalName()}</a>
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
{if sizeof($popularClubs) !== 0}
<h4>{_most_popular_groups}</h4>
<ol>
<li n:foreach="$popularClubs as $entry" style="margin-top: 5px;">
<a href="{$entry->club->getURL()}">{$entry->club->getName()}</a>
<div>
{tr("participants", $entry->subscriptions)}
</div>
</li>
</ol>
{/if}
<h4>{_rules}</h4>
<div style="margin-top: 16px;">
{presenter "openvk!Support->knowledgeBaseArticle", "rules"}
</div>
{/block}

View file

@ -6,9 +6,50 @@
{/block}
{block content}
<div class="navigation">
{foreach $languages as $language}
<a href="language?lg={$language['code']}&hash={urlencode($csrfToken)}" class="link"><img src="/assets/packages/static/openvk/img/flags/{$language['flag']}.gif"> {$language['native_name']}</a>
{/foreach}
<style>
.navigation-lang {
display: grid;
grid-gap: 10px;
grid-template-columns: repeat(5, 1fr);
}
.navigation-lang .link_new {
display: inline-block;
padding: 25px 25px 20px 25px;
text-decoration: none;
border-top: 1px solid #fff;
color: #000;
border-bottom: 0;
border-left: 0;
border-right: 0;
text-align: center;
font-size: 11px;
cursor: pointer;
background: none;
margin-bottom: 1px;
}
.navigation-lang .link_new:hover {
background-color: #E4E4E4;
border-top: 1px solid #CCCCCC;
}
</style>
<div class="navigation-lang">
{foreach $languages as $language}
{var $result = preg_match("/(.+)\((.+)\)/", $language['native_name'], $name)}
<a href="language?lg={$language['code']}&hash={urlencode($csrfToken)}" class="link_new" rel="nofollow">
<center><img src="/assets/packages/static/openvk/img/flags/{$language['flag']}.gif"></center>
<br>
{if $result == 1}
{$name[1]}
<br>
<small>{$name[2]}</small>
{else}
{$language['native_name']}
{/if}
</a>
{/foreach}
</div>
{/block}

View file

@ -28,8 +28,7 @@
text-align: center !important;
}
td,
th {
td, th {
border: 1px solid #666;
vertical-align: baseline;
padding: 4px 5px;
@ -62,7 +61,7 @@
.h {
background-color: #13599f;
font-weight: bold;
color: #fff
color: #fff;
}
.v {
@ -90,7 +89,7 @@
height: 1px;
}
</style>
<div class="center">
<table>
<tbody>
@ -102,7 +101,7 @@
</tr>
</tbody>
</table>
<table>
<tbody>
<tr>
@ -127,22 +126,22 @@
</tr>
</tbody>
</table>
<table>
<tbody>
<tr class="v">
<td>
This program makes use of the Chandler open-source web application server:<br/>
libchandler {=CHANDLER_VER} by rem-pai
libchandler {=CHANDLER_VER} by Celestora
</td>
</tr>
</tbody>
</table>
<hr/>
<h1>Configuration</h1>
<table>
<tbody>
<tr class="h">
@ -313,13 +312,13 @@
</tr>
</tbody>
</table>
<hr/>
<h1>Extensions</h1>
<h2>Core</h2>
<table>
<tbody>
<tr>
@ -332,13 +331,13 @@
</tr>
</tbody>
</table>
{* TODO: add hook to let extensions to register themselves here *}
<hr/>
<h1>Themepacks</h1>
<table>
<tbody>
<tr class="h">
@ -362,7 +361,7 @@
Default OpenVK look and feel.
</td>
<td class="v">
Vladimir Barinov, Konstantin Kichulkin and Daniil Myslivets
Vladimir Barinov, Konstantin Kichulkin and Daniel Myslivets
</td>
</tr>
<tr n:foreach="$themes as $theme">
@ -384,11 +383,11 @@
</tr>
</tbody>
</table>
<hr/>
<h1>OpenVK Credits</h1>
<table>
<tbody>
<tr class="h">
@ -396,14 +395,14 @@
</tr>
<tr>
<td class="e">
Vladimir Barinov (veselcraft), Alexandra Katunina (rem-pai), Konstantin Kichulkin (kosfurler),
Nikita Volkov (sup_ban), Daniel Myslivets (myslivets), Alexander Kotov (l-lacker),
Vladimir Barinov (veselcraft), Celestora, Konstantin Kichulkin (kosfurler),
Nikita Volkov (sup_ban), Daniel Myslivets, Alexander Kotov (l-lacker),
Alexey Assemblerov (BiosNod), Ilya Prokopenko (dsrev) and Maxim Leshchenko (maksales / maksalees)
</td>
</tr>
</tbody>
</table>
<table>
<tbody>
<tr class="h">
@ -417,7 +416,7 @@
</tr>
</tbody>
</table>
<table>
<tbody>
<tr class="h">
@ -443,7 +442,7 @@
{/foreach}
</tbody>
</table>
<table>
<tbody>
<tr class="h">
@ -459,7 +458,7 @@
</tr>
<tr>
<td class="e">Initial hosting</td>
<td class="v">Ilya Prokopenko (dsrev) and Alexandra Katunina (rem-pai)</td>
<td class="v">Ilya Prokopenko (dsrev) and Celestora</td>
</tr>
<tr>
<td class="e">Initial bug-tracker hosting</td>
@ -467,27 +466,23 @@
</tr>
<tr>
<td class="e">Images</td>
<td class="v">Vladimir Barinov (veselcraft), Konstantin Kichulkin (kosfurler) and Daniil Myslivets (myslivets)</td>
<td class="v">Vladimir Barinov (veselcraft), Konstantin Kichulkin (kosfurler) and Daniel Myslivets</td>
</tr>
<tr>
<td class="e">Illustrations</td>
<td class="v">Ash Defenders</td>
</tr>
<tr>
<td class="e">Soundtrack</td>
<td class="v">Ash Defenders</td>
<td class="v">Ash Defenders, Polina Katunina (RousPhaul)</td>
</tr>
<tr>
<td class="e">Best barmaid</td>
<td class="v">Jill</td>
<td class="v">Jill</td> {* I can agree ~~ dsrev *}
</tr>
<tr>
<td class="e">Helpdesk implementation</td>
<td class="e">Initial Helpdesk implementation</td>
<td class="v">Nikita Volkov (sup_ban)</td>
</tr>
</tbody>
</table>
<table>
<tbody>
<tr class="h">
@ -495,17 +490,19 @@
</tr>
<tr class="e">
<td>
Vladimir Lapskiy (0x7d5), Alexander Minkin (WerySkok), Polina Katunina (RousPhaul),
Egor Shevchenko, Dmitriy Daemon and Ilya Prokopenko (dsrev).
kovaltim, Vladimir Lapskiy (0x7d5), Alexander Minkin (WerySkok), Polina Katunina (RousPhaul), veth,
Egor Shevchenko, Vadim Korovin (yuni), Ash Defenders,
Pavel Silaev, Dmitriy Daemon, Ilya Prokopenko (dsrev),
cmed404 and unknown tester, who disappeared shortly after trying to upload post with cat.
</td>
</tr>
</tbody>
</table>
<hr/>
<h1>OpenVK License</h1>
<table>
<tbody>
<tr class="v">

View file

@ -6,7 +6,7 @@
{var css = file_get_contents(OPENVK_ROOT . "/Web/static/js/node_modules/@atlassian/aui/dist/aui/aui-prototyping.css")}
{str_replace("fonts/", "/assets/packages/static/openvk/js/node_modules/@atlassian/aui/dist/aui/fonts/", $css)|noescape}
</style>
<title>{include title}</title>
<title>{include title} - Админ-панель {=OPENVK_ROOT_CONF['openvk']['appearance']['name']}</title>
</head>
<body>
<div id="page">
@ -16,7 +16,7 @@
<div class="aui-header-primary">
<h1 id="logo" class="aui-header-logo aui-header-logo-textonly">
<a href="/admin">
<span class="aui-header-logo-device">OVK</span>
<span class="aui-header-logo-device">{=OPENVK_ROOT_CONF['openvk']['appearance']['name']}</span>
</a>
</h1>
</div>
@ -163,9 +163,7 @@
</div>
<footer id="footer" role="contentinfo">
<section class="footer-body">
<div id="footer-logo">
OpenVK
</div>
OpenVK <a href="/about:openvk">{php echo OPENVK_VERSION}</a> | PHP: {phpversion()} | DB: {\Chandler\Database\DatabaseConnection::i()->getConnection()->getPdo()->getAttribute(\PDO::ATTR_SERVER_VERSION)}
</section>
</footer>
</div>

View file

@ -79,6 +79,12 @@
Верификация
</label>
</div>
<div class="group">
<input class="toggle-large" type="checkbox" id="hide_from_global_feed" name="hide_from_global_feed" value="1" {if $club->isHideFromGlobalFeedEnabled()} checked {/if} />
<label for="hide_from_global_feed">
Не отображать записи в глобальной ленте
</label>
</div>
<hr/>
<div class="buttons-container">
<div class="buttons">

View file

@ -63,6 +63,12 @@
</label>
<input class="text medium-field" type="email" id="email" name="email" value="{$user->getEmail()}" />
</div>
<div class="field-group">
<label for="shortcode">
Адрес страницы
</label>
<input class="text medium-field" type="text" id="shortcode" name="shortcode" value="{$user->getShortCode()}" />
</div>
<hr>
<div class="field-group">
<label for="city">

View file

@ -7,7 +7,7 @@
{block content}
<div class="container_gray">
<form method="POST" enctype="multipart/form-data" action="/club{$club->getId()}/setAdmin.jsp">
<form method="POST" enctype="multipart/form-data" action="/club{$club->getId()}/setAdmin">
<table cellspacing="7" cellpadding="0" width="40%" border="0" align="center">
<tbody>
<tr>

View file

@ -74,7 +74,8 @@
<span class="nobold">{_wall}: </span>
</td>
<td>
<input type="checkbox" name="wall" value="1" {if $club->canPost()}checked{/if}/> {_group_allow_post_for_everyone}
<input type="checkbox" name="wall" value="1" n:attr="checked => $club->canPost()" /> {_group_allow_post_for_everyone}<br>
<input type="checkbox" name="hide_from_global_feed" value="1" n:attr="checked => $club->isHideFromGlobalFeedEnabled()" /> {_group_hide_from_global_feed}
</td>
</tr>
<tr>

View file

@ -109,7 +109,7 @@
{var user = $x instanceof $Manager ? $x->getUser() : $x}
{var manager = $x instanceof $Manager ? $x : $club->getManager($user, !$club->canBeModifiedBy($thisUser))}
{if $club->canBeModifiedBy($thisUser ?? NULL)}
<a class="profile_link" href="/club{$club->getId()}/setAdmin.jsp?user={$user->getId()}&hash={rawurlencode($csrfToken)}" n:if="$club->getOwner()->getId() !== $user->getId()">
<a class="profile_link" href="/club{$club->getId()}/setAdmin?user={$user->getId()}&hash={rawurlencode($csrfToken)}" n:if="$club->getOwner()->getId() !== $user->getId()">
{if $manager}
{_devote}
{else}
@ -130,12 +130,12 @@
{_set_comment}
</a>
{if $manager}
<a class="profile_link" href="/club{$club->getId()}/setAdmin.jsp?user={$user->getId()}&hidden={(int) !$manager->isHidden()}&hash={rawurlencode($csrfToken)}">
<a class="profile_link" href="/club{$club->getId()}/setAdmin?user={$user->getId()}&hidden={(int) !$manager->isHidden()}&hash={rawurlencode($csrfToken)}">
{if $manager->isHidden()}{_hidden_yes}{else}{_hidden_no}{/if}
</a>
{/if}
{if $club->getOwner()->getId() == $user->getId()}
<a class="profile_link" href="/club{$club->getId()}/setAdmin.jsp?user={$user->getId()}&hidden={(int) !$club->isOwnerHidden()}&hash={rawurlencode($csrfToken)}">
<a class="profile_link" href="/club{$club->getId()}/setAdmin?user={$user->getId()}&hidden={(int) !$club->isOwnerHidden()}&hash={rawurlencode($csrfToken)}">
{if $club->isOwnerHidden()}{_hidden_yes}{else}{_hidden_no}{/if}
</a>
{/if}

View file

@ -189,7 +189,7 @@
</div>
</div>
</div>
<div n:if="$albumsCount > 0">
<div n:if="$albumsCount > 0 || ($thisUser && $club->canBeModifiedBy($thisUser))">
<div class="content_title_expanded" onclick="hidePanel(this, {$albumsCount});">
{_"albums"}
</div>

View file

@ -5,7 +5,7 @@
<a href="/im">{_my_messages}</a> »
<a href="{$correspondent->getURL()}">{$correspondent->getCanonicalName()}</a>
<div n:if="($online = $correspondent->getOnline()->timestamp()) < time() + 2678400" style="float: right;">
<div n:if="($online = $correspondent->getOnline()->timestamp()) + 2505600 > time()" style="float: right;">
{var diff = date_diff(date_create(), date_create('@' . $online))}
{if 5 >= $diff->i}
<span><b>{_online}</b></span>
@ -43,7 +43,7 @@
</div>
</div>
<div class="messenger-app--input">
{if $correspondent->getId() === $thisUser->getId() || ($correspondent->getSubscriptionStatus($thisUser) === 3)}
{if $correspondent->getId() === $thisUser->getId() || $correspondent->getPrivacyPermission('messages.write', $thisUser)}
<img class="ava" src="{$thisUser->getAvatarUrl()}" alt="{$thisUser->getCanonicalName()}" />
<div class="messenger-app--input---messagebox">
<textarea
@ -54,7 +54,7 @@
</div>
<img class="ava" src="{$correspondent->getAvatarUrl()}" alt="{$correspondent->getCanonicalName()}" />
{else}
<div class="blocked" data-localized-text="Вы не можете писать сообщения {$correspondent->getCanonicalName()}, так как его нет в вашем списке друзей."></div>
<div class="blocked" data-localized-text="Вы не можете писать сообщения {$correspondent->getCanonicalName()} из-за его настроек приватности."></div>
{/if}
</div>
</div>

View file

@ -11,7 +11,7 @@
</div>
<div class="container_gray">
<form action="/im/search.pl" method="POST" style="margin: 0;">
<form action="/im/search" method="POST" style="margin: 0;">
<input type="text" name="pattern" placeholder="Поиск сообщений" required />
</form>
</div>

View file

@ -0,0 +1,49 @@
{extends "../@layout.xml"}
{block title}{_edit_note}{/block}
{block header}
{var author = $note->getOwner()}
<a href="{$author->getURL()}">{$author->getCanonicalName()}</a>
»
<a href="/notes{$author->getId()}">{_notes}</a>
»
<a href="/note{$author->getId()}_{$note->getVirtualId()}">{$note->getName()}</a>
{/block}
{block content}
<form id="noteFactory" method="POST">
<input type="text" name="name" placeholder="{_name_note}" style="width:603px;" value="{$note->getName()}" />
<br/><br/>
<textarea name="html" style="display:none;"></textarea>
<div id="editor" style="width:600px;height:300px;border:1px solid grey"></div>
<p><i><a href="/kb/notes">Кое-что</a> из (X)HTML поддерживается.</i></p>
<input type="hidden" name="hash" value="{$csrfToken}" />
<button class="button">{_save}</button>
&nbsp;
<a href="/note{$note->getOwner()->getId()}_{$note->getVirtualId()}" class="button">{_cancel}</a>
</form>
{script "js/node_modules/monaco-editor/min/vs/loader.js"}
{script "js/node_modules/requirejs/bin/r.js"}
<script>
require.config({
paths: {
'vs': '/assets/packages/static/openvk/js/node_modules/monaco-editor/min/vs'
}
});
require(['vs/editor/editor.main'], function() {
window._editor = monaco.editor.create(document.getElementById('editor'), {
value: {$note->getSource()},
lineNumbers: "off",
language: "html"
});
});
document.querySelector("#noteFactory").addEventListener("submit", function() {
document.querySelector("textarea").value = window._editor.getValue();
});
</script>
{/block}

View file

@ -5,12 +5,25 @@
{block title}{_notes}{/block}
{block header}
<a href="{$owner->getURL()}">{$owner->getCanonicalName()}</a>
»
{_notes}
<div n:if="isset($thisUser) && $thisUser->getId() == $owner->getId()" style="float:right;">
<a href="/notes/create">{_create_note}</a>
{if isset($thisUser) && $thisUser->getId() == $owner->getId()}
{_my_notes}
{else}
<a href="{$owner->getURL()}">
{$owner->getCanonicalName()}</a>
»
{_notes}
{/if}
{/block}
{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

@ -12,46 +12,67 @@
{/block}
{block content}
{var author = $note->getOwner()}
<style>
#userContent img {
max-width: 245pt;
max-height: 200pt;
}
#userContent blockquote {
background-color: #f3f3f3;
border-bottom: 5px solid #969696;
padding: 1;
}
#userContent cite {
margin-top: 1em;
display: block;
}
#userContent cite::before {
content: "— ";
}
</style>
<article id="userContent" style="min-height: 300pt;">
{$note->getText()|noescape}
</article>
<div style="width: 100%; min-height: 100px;">
<div style="float: left; min-height: 100px; width: 70%;">
{include "../components/comments.xml",
comments => $comments,
count => $cCount,
page => $cPage,
model => "notes",
parent => $note}
</div>
<div style="float: right; min-height: 100px; width: 30%;">
<h4>{_actions}</h4>
<div n:if="isset($thisUser) && $thisUser->getId() === $note->getOwner()->getId()">
<a id="_noteDelete" href="/note{$note->getOwner()->getId()}_{$note->getId()}/delete" class="profile_link" style="display:block;width:96%;">{_delete}</a>
<article id="userContent" style="margin: 10px 10px 0;">
<div class="note_header">
<div class="note_title">
<div class="note_title">
<a>{$note->getName()}</a>
</div>
</div>
<div class="byline">
<span><a href="{$author->getURL()}">{$author->getCanonicalName()}</a></span> {$note->getPublicationTime()}
<span n:if="$note->getEditTime() > $note->getPublicationTime()">({_edited} {$note->getEditTime()})</span>
</div>
</div>
<div style="margin-left: 6px; width: 535px;">
{$note->getText()|noescape}
</div>
</article>
<div class="note_footer" style="margin: 10px 10px 0;">
<div class="comments_count">
{if sizeof($comments) > 0}
{_comments} ({$note->getCommentsCount()})
{else}
{_no_comments}
{/if}
<span n:if="isset($thisUser) && $thisUser->getId() === $note->getOwner()->getId()">&nbsp;|&nbsp;
<a id="_noteDelete" href="/note{$note->getOwner()->getId()}_{$note->getId()}/delete">{_delete}</a>
&nbsp;|&nbsp;
<a href="/note{$note->getOwner()->getId()}_{$note->getVirtualId()}/edit">{_edit}</a>
</span>
</div>
</div>
<div style="margin: 6px 10px 10px;border-top: 1px solid #ddd;">
{include "../components/comments.xml",
comments => $comments,
count => $cCount,
page => $cPage,
model => "notes",
parent => $note,
showTitle => false}
</div>
{/block}

View file

@ -35,7 +35,7 @@
<div class="album-photo">
<a
n:if="!is_null($thisUser) && $album->canBeModifiedBy($thisUser)"
href="/album{$album->getPrettyId()}/remove_photo.pl/{$photo->getId()}" class="album-photo--delete">
href="/album{$album->getPrettyId()}/remove_photo/{$photo->getId()}" class="album-photo--delete">
&times;
</a>

View file

@ -28,11 +28,9 @@
{var cover = $x->getCoverPhoto()}
{var preview = is_null($cover) ? "/assets/packages/static/openvk/img/camera_200.png" : $cover->getURL()}
<center style="height: 54pt; width: 140px;">
<a href="/album{$x->getPrettyId()}">
<img src="{$preview}" alt="{$x->getName()}" style="max-height: 100%; max-width: 100%;" />
</a>
</center>
<a href="/album{$x->getPrettyId()}">
<img src="{$preview}" alt="{$x->getName()}" style="height: 130px; width: 170px; object-fit: cover" />
</a>
{/block}
{block name}
@ -40,8 +38,8 @@
{/block}
{block description}
<span>{$x->getDescription() ?? $x->getName()}</span><br/>
<span style="color: grey;">{$x->getPhotosCount()} фотографий</span><br/>
<span style="color: grey;">Создан {$x->getCreationTime()}</span><br/>
<span style="color: grey;">Изменён {$x->getEditTime() ?? $x->getCreationTime()}</span>
<span>{$x->getDescription() ?? $x->getName()}</span><br />
<span style="color: grey;">{$x->getPhotosCount()} фотографий</span><br />
<span style="color: grey;">Обновлен {$x->getEditTime() ?? $x->getCreationTime()}</span><br />
<span style="color: grey;">Создан {$x->getCreationTime()}</span><br />
{/block}

View file

@ -39,6 +39,6 @@
</form>
<hr/>
<center>
{_"you_can_also"} <a href="/album{$album->getOwner()->getId()}_{$album->getId()}/delete.pl?hash={rawurlencode($csrfToken)}">{_"delete_album"}</a>.
{_"you_can_also"} <a href="/album{$album->getOwner() instanceof openvk\Web\Models\Entities\Club ? '-' : ''}{$album->getOwner()->getId()}_{$album->getId()}/delete?hash={rawurlencode($csrfToken)}">{_"delete_album"}</a>.
</center>
{/block}

View file

@ -6,6 +6,9 @@
{else}
{tr("search_for_groups")}
{/if}
{if $_GET['query']}
- {$_GET['query']}
{/if}
{/block}
{block header}
@ -21,12 +24,12 @@
{block tabs}
<div {if $type === "users"}id="activetabs"{/if} class="tab">
<a {if $type === "users"}id="act_tab_a"{/if} href="/search?type=users">
<a {if $type === "users"}id="act_tab_a"{/if} href="/search?type=users{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}">
{_users}
</a>
</div>
<div {if $type === "groups"}id="activetabs"{/if} class="tab">
<a {if $type === "groups"}id="act_tab_a"{/if} href="/search?type=groups">
<a {if $type === "groups"}id="act_tab_a"{/if} href="/search?type=groups{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}">
{_groups}
</a>
</div>

View file

@ -6,138 +6,127 @@
{/block}
{block content}
<div class="post-author">
<a href="#" style="font-size:13px;">
<b>
{$ticket->getName()}
</b>
</a>
<br></b>{_author}: <a href="/id{$ticket->getUser()->getId()}">{$ticket->getUser()->getFullName()}</a> | {$ticket->getUser()->getRegistrationIP()} | {_status}: {$ticket->getStatus()}.
</div>
<div class="text" style="padding-top: 10px;border-bottom: #ECECEC solid 1px;">
{$ticket->getText()|noescape}
<br></br>
</div>
<div style="padding-top: 5px;">
{$ticket->getTime()}&nbsp;|&nbsp;
<a href="/support/delete/{$id}?hash={$csrfToken}">{_delete}</a>
</div><br/>
<div>
<form action="/al_comments.pl/create/support/reply/{$id}" method="post" style="margin:0;">
<textarea name="text" id="answer_text" style="width: 100%;resize: vertical;"></textarea>
<div>
<!-- padding to fix <br/> bug -->
</div>
<input type="hidden" name="hash" value="{$csrfToken}" />
<br />
<div style="float: left;">
<input type="submit" value="{_write}" class="button">
<select name="status" style="width: unset;">
<option value="1">{_support_status_1}</option>
<option value="2">{_support_status_2}</option>
<option value="0">{_support_status_0}</option>
</select>
</div>
<div n:if="!is_null($fastAnswers)" style="float: right;">
<a class="button" href="javascript:showSupportFastAnswerDialog(fastAnswers)">{_fast_answers}</a>
</div>
<br />
</form>
</div>
<br/>
<p n:if="!$comments">{_no_comments}</p>
{var $printedSupportGreeting = false}
<table n:foreach="$comments as $comment" border="0" style="font-size: 11px;" class="post">
<tbody>
<tr>
{if $comment->getUType() === 0}
<td width="54" valign="top">
<img src="{$comment->getUser()->getAvatarUrl()}" width="50">
</td>
{else}
<td width="54" valign="top">
<img
src="{$comment->getAvatar()}"
style="max-width: 50px; filter: hue-rotate({$comment->getColorRotation()}deg);" />
</td>
{/if}
<td width="645" valign="top">
{if $comment->getUType() === 0}
<div class="post-author">
<a href="{$comment->getUser()->getURL()}"><b>
{$comment->getUser()->getFullName()}
</b></a> {($comment->getUser()->isFemale() ? tr("post_writes_f") : tr("post_writes_m"))}<br>
<a href="#" class="date">{$comment->getTime()}</a>
<div class="post-author">
<a href="#" style="font-size: 13px;"><b>{$ticket->getName()}</b></a><br />
{_author}: <a href="/id{$ticket->getUser()->getId()}">{$ticket->getUser()->getFullName()}</a> | {$ticket->getUser()->getRegistrationIP()} | {_status}: {$ticket->getStatus()}.
</div>
<div class="text" style="padding-top: 10px; border-bottom: #ECECEC solid 1px;">
{$ticket->getText()|noescape}
<br /><br />
</div>
<div style="padding-top: 5px;">
{$ticket->getTime()}&nbsp;|&nbsp;
<a href="/support/delete/{$id}?hash={$csrfToken}">{_delete}</a>
</div>
<br />
<div>
<form action="/al_comments/create/support/reply/{$id}" method="post" style="margin: 0;">
<textarea name="text" id="answer_text" style="width: 100%; resize: vertical;"></textarea>
<br />
<input type="hidden" name="hash" value="{$csrfToken}" />
<br />
<div style="float: left;">
<input type="submit" value="{_write}" class="button" />
<select name="status" style="width: unset;">
<option value="1">{_support_status_1}</option>
<option value="2">{_support_status_2}</option>
<option value="0">{_support_status_0}</option>
</select>
</div>
{elseif ($comment->getUType() === 1)}
<div class="post-author">
<a href="javascript:false">
<b>
{$comment->getAuthorName()}
</b>
</a>
{if $thisUser->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
<a href="{$comment->getUser()->getURL()}">
<span class="nobold">
{var lastName = $comment->getUser()->getLastName()}
{if empty(trim($lastName))}
({$comment->getUser()->getFirstName()})
{else}
({$comment->getUser()->getFirstName()} {iconv_substr($lastName, 0, 1)}.)
{/if}
</span>
</a>
{/if}
{_post_writes_m}<br>
<a href="#" class="date">{$comment->getTime()}</a>
<div n:if="!is_null($fastAnswers)" style="float: right;">
<a class="button" href="javascript:showSupportFastAnswerDialog(fastAnswers)">{_fast_answers}</a>
</div>
{/if}
<div class="post-content" id="{$comment->getId()}">
<div class="text" id="text{$comment->getId()}">
{if $comment->getUType() === 1 && !$printedSupportGreeting}
{var $printedSupportGreeting = true}
{tr("support_greeting_hi", $ticket->getUser()->getFullName())}
<br/>
<br/>
<br />
</form>
</div>
<br />
<p n:if="!$comments">{_no_comments}</p>
{var $printedSupportGreeting = false}
<table n:foreach="$comments as $comment" border="0" style="font-size: 11px;" class="post">
<tbody>
<tr>
{if $comment->getUType() === 0}
<td width="54" valign="top">
<img src="{$comment->getUser()->getAvatarUrl()}" width="50" />
</td>
{else}
<td width="54" valign="top">
<img
src="{$comment->getAvatar()}"
style="max-width: 50px; filter: hue-rotate({$comment->getColorRotation()}deg);" />
</td>
{/if}
<td width="645" valign="top">
{if $comment->getUType() === 0}
<div class="post-author">
<a href="{$comment->getUser()->getURL()}"><b>
{$comment->getUser()->getFullName()}
</b></a> {($comment->getUser()->isFemale() ? tr("post_writes_f") : tr("post_writes_m"))}<br />
<a href="#" class="date">{$comment->getTime()}</a>
</div>
{elseif ($comment->getUType() === 1)}
<div class="post-author">
<a><b>{$comment->getAuthorName()}</b></a>
{if $thisUser->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
<a href="{$comment->getUser()->getURL()}">
<span class="nobold">
{var lastName = $comment->getUser()->getLastName()}
{if empty(trim($lastName))}
({$comment->getUser()->getFirstName()})
{else}
({$comment->getUser()->getFirstName()} {iconv_substr($lastName, 0, 1)}.)
{/if}
</span>
</a>
{/if}
{_post_writes_m}<br />
<a href="#" class="date">{$comment->getTime()}</a>
</div>
{/if}
<div class="post-content" id="{$comment->getId()}">
<div class="text" id="text{$comment->getId()}">
{if $comment->getUType() === 1 && !$printedSupportGreeting}
{var $printedSupportGreeting = true}
{tr("support_greeting_hi", $ticket->getUser()->getFullName())}
<br />
<br />
{$comment->getText()|noescape}
<br/>
<br/>
{$comment->getText()|noescape}
<br />
<br />
{tr("support_greeting_regards", OPENVK_ROOT_CONF["openvk"]["appearance"]["name"])|noescape}
{else}
{$comment->getText()|noescape}
{/if}
</div>
{if $comment->getUType() === 0}
<div class="post-menu">
<a href="/support/comment/{$comment->getId()}/delete">{_delete}</a>
</div>
{/if}
{if $comment->getUType() === 1 && !is_null($comment->isLikedByUser())}
<div class="post-menu">
<strong>
{if $comment->isLikedByUser()}
{_support_good_answer_agent}
{else}
{_support_bad_answer_agent}
{tr("support_greeting_regards", OPENVK_ROOT_CONF["openvk"]["appearance"]["name"])|noescape}
{else}
{$comment->getText()|noescape}
{/if}
</div>
<div class="post-menu">
<a href="/support/comment/{$comment->getId()}/delete?hash={urlencode($csrfToken)}">{_delete}</a>
</div>
{if $comment->getUType() === 1 && !is_null($comment->isLikedByUser())}
<div class="post-menu">
<strong>
{if $comment->isLikedByUser()}
{_support_good_answer_agent}
{else}
{_support_bad_answer_agent}
{/if}
</strong>
</div>
{/if}
</strong>
</div>
{/if}
</div>
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</tbody>
</table>
<script>
const fastAnswers = [
{foreach $fastAnswers as $answer}
{$answer},
{/foreach}
];
</script>
<script>
const fastAnswers = [
{foreach $fastAnswers as $answer}
{$answer},
{/foreach}
];
</script>
{/block}

View file

@ -6,63 +6,82 @@
{/block}
{block content}
{var isMain = $mode === 'faq'}
{var isNew = $mode === 'new'}
{var isList = $mode === 'list'}
{var isMain = $mode === 'faq'}
{var isNew = $mode === 'new'}
{var isList = $mode === 'list'}
{if $thisUser}
<div class="tabs">
<div n:attr="id => ($isMain ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($isMain ? 'act_tab_a' : 'ki')" href="/support">{_support_faq}</a>
</div>
<div n:attr="id => ($isList ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($isList ? 'act_tab_a' : 'ki')" href="/support?act=list">{_support_list}</a>
</div>
<div n:attr="id => ($isNew ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($isNew ? 'act_tab_a' : 'ki')" href="/support?act=new">{_support_new}</a>
</div>
</div>
<br>
{if $isNew}
<div class="new">
<form action="/support" method="post" style="margin:0;">
<center><input name="name" style="width: 80%;resize: vertical;" placeholder="{_support_new_title}"></center><br>
<center><textarea name="text" style="width: 80%;resize: vertical;" placeholder="{_support_new_content}"></textarea></center><br>
<input type="hidden" name="hash" value="{$csrfToken}" />
<center><input type="submit" value="{_write}" class="button" style="margin-left:70%;"></center><br>
</form>
</div>
{/if}{/if}
{if $isMain}
<h4>{_support_faq}</h4><br>
<div class="faq">
<div id="faqhead">{_support_faq_title}</div>
<div id="faqcontent">{_support_faq_content}</div>
</div>
{/if}
{if $isList}
<table n:foreach="$tickets as $ticket" border="0" style="font-size:11px;width: 610px;" class="post">
<tbody>
<tr>
<td width="54" valign="top">
<center><img src="/assets/packages/static/openvk/img/note_icon.png" alt="{_support_ticket}" style="margin-top: 17px;"></center>
</td>
<td width="345" valign="top">
<div class="post-author">
<a href="/support/view/{$ticket->getId()}">
<b>{$ticket->getName()}</b>
</a>
{if $thisUser}
<div class="tabs">
<div n:attr="id => ($isMain ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($isMain ? 'act_tab_a' : 'ki')" href="/support">{_support_faq}</a>
</div>
<div class="post-content" style="padding: 4px;font-size: 11px;">
{_status}: {$ticket->getStatus()}
<div n:if="$count > 0" n:attr="id => ($isList ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($isList ? 'act_tab_a' : 'ki')" href="/support?act=list">{_support_list}</a>
</div>
</td>
</tr>
</tbody>
</table>
{/if}
<div n:attr="id => ($isNew ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($isNew ? 'act_tab_a' : 'ki')" href="/support?act=new">{_support_new}</a>
</div>
</div>
<br />
{if $isNew}
<div class="new">
<form action="/support" method="post" style="margin:0;">
<center>
<input name="name" style="width: 80%; resize: vertical;" placeholder="{_support_new_title}" /><br /><br />
<textarea name="text" style="width: 80%; resize: vertical;" placeholder="{_support_new_content}"></textarea><br /><br />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" value="{_write}" class="button" style="margin-left: 70%;" /><br /><br />
</center>
</form>
</div>
{/if}
{/if}
{if $isMain}
<h4>{_support_faq}</h4><br />
<div class="faq">
<div id="faqhead">{_support_faq_title}</div>
<div id="faqcontent">{_support_faq_content}</div>
</div>
{/if}
{if $isList}
<table n:foreach="$tickets as $ticket" border="0" style="font-size: 11px; width: 610px;" class="post">
<tbody>
<tr>
<td width="54" valign="top">
<center>
<img src="/assets/packages/static/openvk/img/note_icon.png" alt="{_support_ticket}" style="margin-top: 17px;" />
</center>
</td>
<td width="345" valign="top">
<div class="post-author">
<a href="/support/view/{$ticket->getId()}">
<b>{$ticket->getName()}</b>
</a>
</div>
<div class="post-content" style="padding: 4px; font-size: 11px;">
{_status}: {$ticket->getStatus()}
</div>
</td>
</tr>
</tbody>
</table>
{if $count < 1}
{include "../components/nothing.xml"}
{/if}
<div style="padding: 8px;">
{include "../components/paginator.xml", conf => (object) [
"page" => $page,
"count" => $count,
"amount" => sizeof($tickets),
"perPage" => OPENVK_DEFAULT_PER_PAGE,
"atBottom" => true,
]}
</div>
{/if}
{/block}

View file

@ -6,144 +6,132 @@
{/block}
{block content}
<script>
function markAnswer(id, mark) {
let url = "/support/comment/" + id + "/rate/" + mark + "?hash=" + {urlencode($csrfToken)};
$.ajax(url, {
error: errorHandler,
success: success(id, mark)
});
}
<script>
function markAnswer(id, mark) {
let url = "/support/comment/" + id + "/rate/" + mark + "?hash=" + {urlencode($csrfToken)};
$.ajax(url, {
error: errorHandler,
success: success(id, mark)
});
}
function success(id, mark) {
if(mark == 1)
document.getElementById("markText-" + id).innerHTML = {_support_good_answer_user};
else
document.getElementById("markText-" + id).innerHTML = {_support_bad_answer_user};
function success(id, mark) {
if(mark == 1)
document.getElementById("markText-" + id).innerHTML = {_support_good_answer_user};
else
document.getElementById("markText-" + id).innerHTML = {_support_bad_answer_user};
document.getElementById("markLinks-" + id).remove();
}
document.getElementById("markLinks-" + id).remove();
}
function errorHandler(id, mark) {
document.getElementById("markText-" + id).innerHTML = {_error};
}
</script>
{if $ticket->isDeleted() == 0 }
<div class="post-author">
<a href="#" style="font-size:13px;">
<b>
{$ticket->getName()}
</b>
</a>
<br></b>{_status}: {$ticket->getStatus()}
</div>
<div class="text" style="padding-top: 10px;border-bottom: #ECECEC solid 1px;">
{$ticket->getText()|noescape}
<br></br>
</div>
<div style="padding-top: 5px;">
{$ticket->getTime()}&nbsp;|&nbsp;
<a href="/support/delete/{$id}?hash={$csrfToken}">{_delete}</a>
</div>
{if $ticket->getType() !== 2}
<br>
<div>
<form action="/al_comments.pl/create/support/{$id}" method="post" style="margin:0;">
<textarea name="text" style="width: 100%;resize: vertical;"></textarea>
<div>
<!-- padding to fix <br/> bug -->
</div>
<input type="hidden" name="hash" value="{$csrfToken}" />
<br>
<input type="submit" value="{_write}" class="button">
</form>
function errorHandler(id, mark) {
document.getElementById("markText-" + id).innerHTML = {_error};
}
</script>
{if $ticket->isDeleted() == 0}
<div class="post-author">
<a href="#" style="font-size:13px;"><b>{$ticket->getName()}</b></a>
<br />{_status}: {$ticket->getStatus()}
</div>
{/if}
</br>
<p n:if="!$comments">{_no_comments}</p>
{var $printedSupportGreeting = false}
<table n:foreach="$comments as $comment" border="0" style="font-size: 11px;" class="post">
<tbody>
<tr>
{if $comment->getUType() === 0}
<td width="54" valign="top">
<img src="{$comment->getUser()->getAvatarUrl()}" width="50">
</td>
{else}
<td width="54" valign="top">
<img
src="{$comment->getAvatar()}"
style="max-width: 50px; filter: hue-rotate({$comment->getColorRotation()}deg);" />
</td>
{/if}
<td width="645" valign="top">
{if $comment->getUType() === 0}
<div class="post-author">
<a href="{$comment->getUser()->getURL()}"><b>
{$comment->getUser()->getFullName()}
</b></a> {($comment->getUser()->isFemale() ? tr("post_writes_f") : tr("post_writes_m"))}<br>
<a href="#" class="date">{$comment->getTime()}</a>
<div class="text" style="padding-top: 10px; border-bottom: #ECECEC solid 1px;">
{$ticket->getText()|noescape}
<br /></br>
</div>
<div style="padding-top: 5px;">
{$ticket->getTime()}&nbsp;|&nbsp;
<a href="/support/delete/{$id}?hash={$csrfToken}">{_delete}</a>
</div>
{if $ticket->getType() !== 2}
<br />
<div>
<form action="/al_comments/create/support/{$id}" method="post" style="margin:0;">
<textarea name="text" style="width: 100%;resize: vertical;"></textarea><br />
<input type="hidden" name="hash" value="{$csrfToken}" /><br />
<input type="submit" value="{_write}" class="button" />
</form>
</div>
{elseif ($comment->getUType() === 1)}
<div class="post-author">
<a href="javascript:false">
<b>
{$comment->getAuthorName()}
</b>
</a>
{_post_writes_m}<br>
<a href="#" class="date">{$comment->getTime()}</a>
</div>
{/if}
<div class="post-content" id="{$comment->getId()}">
<div class="text" id="text{$comment->getId()}">
{if $comment->getUType() === 1 && !$printedSupportGreeting}
{var $printedSupportGreeting = true}
{tr("support_greeting_hi", $ticket->getUser()->getFullName())}
<br/>
<br/>
{$comment->getText()|noescape}
<br/>
<br/>
{tr("support_greeting_regards", OPENVK_ROOT_CONF["openvk"]["appearance"]["name"])|noescape}
{else}
{$comment->getText()|noescape}
{/if}
</div>
{if $comment->getUType() === 0}
<div class="post-menu">
<a href="/support/comment/{$comment->getId()}/delete">{_delete}</a>
</div>
{/if}
{if $comment->getUType() === 1}
<div class="post-menu">
{var isLikedByUser = $comment->isLikedByUser()}
<strong id="markText-{$comment->getId()}">
{if !is_null($isLikedByUser)}
{if $comment->isLikedByUser()}
{_support_good_answer_user}
{else}
{_support_bad_answer_user}
{/if}
<br />
<p n:if="!$comments">{_no_comments}</p>
{var $printedSupportGreeting = false}
<table n:foreach="$comments as $comment" border="0" style="font-size: 11px;" class="post">
<tbody>
<tr>
{if $comment->getUType() === 0}
<td width="54" valign="top">
<img src="{$comment->getUser()->getAvatarUrl()}" width="50" />
</td>
{else}
<td width="54" valign="top">
<img
src="{$comment->getAvatar()}"
style="max-width: 50px; filter: hue-rotate({$comment->getColorRotation()}deg);" />
</td>
{/if}
<td width="645" valign="top">
{if $comment->getUType() === 0}
<div class="post-author">
<a href="{$comment->getUser()->getURL()}"><b>{$comment->getUser()->getFullName()}</b></a>
{($comment->getUser()->isFemale() ? tr("post_writes_f") : tr("post_writes_m"))}<br />
<a href="#" class="date">{$comment->getTime()}</a>
</div>
{elseif ($comment->getUType() === 1)}
<div class="post-author">
<a><b>{$comment->getAuthorName()}</b></a>
{_post_writes_m}<br />
<a href="#" class="date">{$comment->getTime()}</a>
</div>
{/if}
{/if}
</strong>
<div id="markLinks-{$comment->getId()}">
{if is_null($isLikedByUser)}
<a onClick="markAnswer({$comment->getId()}, 1)">{_support_rate_good_answer}</a>
|
<a onClick="markAnswer({$comment->getId()}, 2)">{_support_rate_bad_answer}</a>
{/if}
</div>
{/if}
</div>
</div>
</td>
</tr>
</tbody>
</table>
{/if}
<div class="post-content" id="{$comment->getId()}">
<div class="text" id="text{$comment->getId()}">
{if $comment->getUType() === 1 && !$printedSupportGreeting}
{var $printedSupportGreeting = true}
{tr("support_greeting_hi", $ticket->getUser()->getFullName())}
<br />
<br />
{$comment->getText()|noescape}
<br />
<br />
{tr("support_greeting_regards", OPENVK_ROOT_CONF["openvk"]["appearance"]["name"])|noescape}
{else}
{$comment->getText()|noescape}
{/if}
</div>
{if $comment->getUType() === 0}
<div class="post-menu">
<a href="/support/comment/{$comment->getId()}/delete?hash={urlencode($csrfToken)}">{_delete}</a>
</div>
{/if}
{if $comment->getUType() === 1}
<div class="post-menu">
{var isLikedByUser = $comment->isLikedByUser()}
<strong id="markText-{$comment->getId()}">
{if !is_null($isLikedByUser)}
{if $comment->isLikedByUser()}
{_support_good_answer_user}
{else}
{_support_bad_answer_user}
{/if}
{/if}
</strong>
<div id="markLinks-{$comment->getId()}">
{if is_null($isLikedByUser)}
<a onClick="markAnswer({$comment->getId()}, 1)">{_support_rate_good_answer}</a>
|
<a onClick="markAnswer({$comment->getId()}, 2)">{_support_rate_bad_answer}</a>
{/if}
</div>
</div>
{/if}
</div>
</td>
</tr>
</tbody>
</table>
{/if}
{/block}

View file

@ -14,8 +14,8 @@
{block tabs}
<form style="margin-left: 12px;">
<input name="query" class="header_search_input" placeholder="{_"header_search"}" value="{$_GET['query'] ?? ''}" style="width: 90%" />
<input type="submit" class="button" value="{_"search_button"}" style="width: 7%" />
<input name="query" class="header_search_input" placeholder="{_"header_search"}" value="{$_GET['query'] ?? ''}" style="width: 86%" />
<input type="submit" class="button" value="{_"search_button"}" style="width: 7.5%" />
</form>
<p style="margin-left: 15px;">

View file

@ -13,7 +13,7 @@
{var isAvatar = $mode === 'avatar'}
<div n:if="$user->hasPendingNumberChange()" class="msg">
<b>Подтверждение номера телефона</b><br/>
Введите код для подтверждения смены номера: <a href="/edit/verify_phone.pl">ввести код</a>.
Введите код для подтверждения смены номера: <a href="/edit/verify_phone">ввести код</a>.
</div>
<div class="tabs">
@ -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>
@ -288,7 +296,7 @@
{elseif $isAvatar}
<h4>{_"profile_picture"}</h4>
<form action="/al_avatars.pl" method="POST" enctype="multipart/form-data">
<form action="/al_avatars" method="POST" enctype="multipart/form-data">
<table cellspacing="7" cellpadding="0" width="60%" border="0" align="center">
<tbody>
<tr>

View file

@ -2,37 +2,45 @@
{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()}">
{_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">
{_managed}
</a>
</div>
{/if}
{if !is_null($thisUser) && $user->getId() === $thisUser->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 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}
@ -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,13 +74,47 @@
{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 $clubPinned}
{_remove_from_left_menu}
{else}
{_add_to_left_menu}
{/if}
{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}
{_add_to_left_menu}
{/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

@ -316,6 +316,18 @@
</select>
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">{_privacy_setting_write_messages}</span>
</td>
<td>
<select name="messages.write", style="width: 164px;">
<option value="2" {if $user->getPrivacySetting('messages.write') == 2}selected{/if}>{_privacy_value_anybody}</option>
<option value="1" {if $user->getPrivacySetting('messages.write') == 1}selected{/if}>{_privacy_value_friends}</option>
<option value="0" {if $user->getPrivacySetting('messages.write') == 0}selected{/if}>{_privacy_value_nobody}</option>
</select>
</td>
</tr>
<tr>
<td>
@ -450,6 +462,14 @@
</select>
</td>
</tr>
<tr>
<td width="120" valign="top" align="right">
<input type="checkbox" name="theme_for_session" value="1">
</td>
<td>
{_apply_style_for_this_device}
</td>
</tr>
<tr>
<td>
@ -540,6 +560,16 @@
<td>
<span class="nobold">{_additional_links}</span>
</td>
</tr><tr n:if="OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['enable']">
<td width="120" valign="top" align="right" align="right">
<input
n:attr="checked => $user->getLeftMenuItemStatus('poster')"
type="checkbox"
name="menu_standardo" />
</td>
<td>
<span class="nobold">{_ad_poster}</span>
</td>
</tr>
<tr>
<td>

View file

@ -9,7 +9,7 @@
{_"two_factor_authentication_settings_1"|noescape}
<p>{_"two_factor_authentication_settings_2"}</p>
<div style="text-align: center;">
<img src="data:image/png;base64,{$qrCode}">
<img width="225" height="225" src="data:{$qrCodeType};base64,{$qrCodeData}">
</div>
<p>{tr("two_factor_authentication_settings_3", $secret)|noescape}</p>
<p>{_"two_factor_authentication_settings_4"}</p>

View file

@ -92,6 +92,7 @@
{/if}
<a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['commerce'] && $user->getGiftCount() == 0" href="/gifts?act=pick&user={$user->getId()}" class="profile_link">{_send_gift}</a>
<a n:if="$user->getPrivacyPermission('messages.write', $thisUser)" href="/im?sel={$user->getId()}" class="profile_link">{_"send_message"}</a>
{var subStatus = $user->getSubscriptionStatus($thisUser)}
{if $subStatus === 0}
@ -116,7 +117,6 @@
<input type="submit" class="profile_link" value="{_"friends_reject"}" />
</form>
{elseif $subStatus === 3}
<a href="/im?sel={$user->getId()}" class="profile_link">{_"send_message"}</a>
<form action="/setSub/user" method="post" class="profile_link_form">
<input type="hidden" name="act" value="rem" />
<input type="hidden" name="id" value="{$user->getId()}" />
@ -313,7 +313,7 @@
<div class="right_big_block">
<div class="page_info">
<div n:if="!is_null($alert = $user->getAlert())" class="user-alert">{$alert}</div>
<div n:if="!is_null($alert = $user->getAlert())" class="user-alert">{strpos($alert, "@") === 0 ? tr(substr($alert, 1)) : $alert}</div>
{var thatIsThisUser = isset($thisUser) && $user->getId() == $thisUser->getId()}
<div n:if="$thatIsThisUser" class="page_status_popup" id="status_editor" style="display: none;">
<form name="status_popup_form" onsubmit="changeStatus(); return false;">
@ -507,7 +507,7 @@
(function() {
res = document.querySelector("#uBanMsgInput").value;
xhr = new XMLHttpRequest();
xhr.open("GET", "/admin/ban.pl/" + {$user->getId()} + "?reason=" + res + "&hash=" + {rawurlencode($csrfToken)}, true);
xhr.open("GET", "/admin/ban/" + {$user->getId()} + "?reason=" + res + "&hash=" + {rawurlencode($csrfToken)}, true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("reason") === -1)
MessageBox("Ошибка", "Не удалось забанить пользователя...", ["OK"], [Function.noop]);
@ -529,7 +529,7 @@
(function() {
res = document.querySelector("#uWarnMsgInput").value;
xhr = new XMLHttpRequest();
xhr.open("GET", "/admin/warn.pl/" + {$user->getId()} + "?message=" + res + "&hash=" + {rawurlencode($csrfToken)}, true);
xhr.open("GET", "/admin/warn/" + {$user->getId()} + "?message=" + res + "&hash=" + {rawurlencode($csrfToken)}, true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("message") === -1)
MessageBox("Ошибка", "Не удалось отправить предупреждение...", ["OK"], [Function.noop]);

View file

@ -8,9 +8,17 @@
{block header}
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a>
» {_"videos"}
<div n:if="isset($thisUser) && $thisUser->getId() == $user->getId()" style="float: right;">
<a href="/videos/upload">{_"upload_video"}</a>
{/block}
{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

@ -4,10 +4,14 @@
{block header}
{_"feed"}
<div n:if="!isset($globalFeed)" style="float: right;">
<div style="float: right;">
<span>
<b>
<a href="/feed/all">{_"all_news"}</a>
{if !isset($globalFeed)}
<a href="/feed/all">{_"all_news"}</a>
{else}
<a href="/feed">{_"my_feed"}</a>
{/if}
</b>
</span>
</div>

View file

@ -7,16 +7,19 @@
<tbody>
<tr>
<td width="30" valign="top">
<img
src="{$author->getAvatarURL()}"
width="30"
class="cCompactAvatars" />
<a href="{$author->getURL()}">
<img
src="{$author->getAvatarURL()}"
width="30"
class="cCompactAvatars" />
</a>
</td>
<td width="100%" valign="top">
<div class="post-author">
<a href="{$author->getURL()}"><b>
{$author->getCanonicalName()}
</b></a><br/>
</b></a>
{if $author->isVerified()}<img class="name-checkmark" src="/assets/packages/static/openvk/img/checkmark.png">{/if}<br/>
</div>
<div class="post-content" id="{$comment->getId()}">
<div class="text" id="text{$comment->getId()}">

View file

@ -1,7 +1,7 @@
<h4 n:if="$showTitle ?? true">{_"comments"} ({$count})</h4>
<div n:ifset="$thisUser">
{var commentsURL = "/al_comments.pl/create/$model/" . $parent->getId()}
{var commentsURL = "/al_comments/create/$model/" . $parent->getId()}
{var club = $parent instanceof \openvk\Web\Models\Entities\Post && $parent->getTargetWall() < 0 ? (new openvk\Web\Models\Repositories\Clubs)->get(abs($parent->getTargetWall())) : $club}
{if !$readOnly}
{include "textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), club => $club}

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>
</center>
</span>
</center>

View file

@ -1,16 +1,18 @@
{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 ? 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}
{for $j = $conf->page - ($space-1); $j <= $conf->page + ($space-1); $j++}
{if $j > 0 && $j <= $pageCount}
<a n:attr="class => ($conf->page === $j ? 'active')" href="?{http_build_query(array_merge($_GET, ['p' => $j]), 'k', '&', PHP_QUERY_RFC3986)}">{$j}</a>
<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}
{/for}
{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}
{for $j = $conf->page - ($space-1); $j <= $conf->page + ($space-1); $j++}
{if $j > 0 && $j <= $pageCount}
<a n:attr="class => ($conf->page === $j ? 'active')" href="?{http_build_query(array_merge($_GET, ['p' => $j]), 'k', '&', PHP_QUERY_RFC3986)}">{$j}</a>
{/if}
{/for}
{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,15 +8,17 @@
<tbody>
<tr>
<td width="54" valign="top">
<img
src="{$author->getAvatarURL()}"
width="{ifset $compact}25{else}50{/ifset}"
{ifset $compact}class="cCompactAvatars"{/ifset} />
{if !$post->isPostedOnBehalfOfGroup() && !$compact}
<span n:if="$author->isOnline()" class="post-online">
{_online}
</span>
{/if}
<a href="{$author->getURL()}">
<img
src="{$author->getAvatarURL()}"
width="{ifset $compact}25{else}50{/ifset}"
{ifset $compact}class="cCompactAvatars"{/ifset} />
{if !$post->isPostedOnBehalfOfGroup() && !$compact}
<span n:if="$author->isOnline()" class="post-online">
{_online}
</span>
{/if}
</a>
</td>
<td width="100%" valign="top">
<div class="post-author">
@ -128,8 +130,9 @@
{include "../comment.xml", comment => $comment, $compact => true}
{/foreach}
<div n:ifset="$thisUser" id="commentTextArea{$commentTextAreaId}" n:attr="style => ($commentsCount == 0 ? 'display: none;')" class="commentsTextFieldWrap">
{var commentsURL = "/al_comments.pl/create/posts/" . $post->getId()}
{include "../textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), post => $post}
{var commentsURL = "/al_comments/create/posts/" . $post->getId()}
{var club = is_null($club) ? ($post->getTargetWall() < 0 ? (new openvk\Web\Models\Repositories\Clubs)->get(abs($post->getTargetWall())) : NULL) : $club}
{include "../textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), post => $post, club => $club}
</div>
</div>
{/if}

View file

@ -4,14 +4,17 @@
<tbody>
<tr>
<td width="54" valign="top">
<img
src="{$author->getAvatarURL()}"
width="50" />
{if !$post->isPostedOnBehalfOfGroup() && !$compact}
<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

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

View file

@ -25,17 +25,17 @@ class DateTime
if($this->timestamp >= strtotime("midnight")) { # Today
if($diff->h >= 1)
return tr("time_today") . tr("time_at_sp") . ovk_strftime_safe("%X", $this->timestamp);
return tr("time_today") . tr("time_at_sp") . ovk_strftime_safe(" %R %p", $this->timestamp);
else if($diff->i < 2)
return tr("time_just_now");
else
return $diff->i === 5 ? tr("time_exactly_five_minutes_ago") : tr("time_minutes_ago", $diff->i);
} else if($this->timestamp >= strtotime("-1day midnight")) { # Yesterday
return tr("time_yesterday") . tr("time_at_sp") . ovk_strftime_safe("%X", $this->timestamp);
} else if(ovk_strftime_safe("%Y", $this->timestamp) === ovk_strftime_safe("%Y")) { # In this year
return tr("time_yesterday") . tr("time_at_sp") . ovk_strftime_safe(" %R %p", $this->timestamp);
} else if(ovk_strftime_safe("%Y", $this->timestamp) === ovk_strftime_safe("%Y", time())) { # In this year
return ovk_strftime_safe("%e %h ", $this->timestamp) . tr("time_at_sp") . ovk_strftime_safe(" %R %p", $this->timestamp);
} else {
return ovk_strftime_safe("%e %B %Y ", $this->timestamp) . tr("time_at_sp") . ovk_strftime_safe(" %X", $this->timestamp);
return ovk_strftime_safe("%e %B %Y ", $this->timestamp) . tr("time_at_sp") . ovk_strftime_safe(" %R %p", $this->timestamp);
}
}

View file

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

View file

@ -9,6 +9,8 @@ routes:
handler: "About->rules"
- url: "/rpc"
handler: "InternalAPI->route"
- url: "/iapi/timezone"
handler: "InternalAPI->timezone"
- url: "/support"
handler: "Support->index"
- url: "/support/tickets"
@ -19,10 +21,14 @@ routes:
handler: "Support->view"
- url: "/support/comment/{num}/rate/{num}"
handler: "Support->rateAnswer"
- url: "/al_comments.pl/create/support/{num}"
- url: "/al_comments/create/support/{num}"
handler: "Support->makeComment"
- url: "/al_comments.pl/create/support/reply/{num}"
- url: "/al_comments/create/support/reply/{num}"
handler: "Support->AnswerTicketReply"
- url: "/support/comment/{num}/delete"
handler: "Support->deleteComment"
- url: "/al_comments/create/{text}/{num}"
handler: "Comment->makeComment"
- url: "/support/delete/{num}"
handler: "Support->delete"
- url: "/language"
@ -37,6 +43,8 @@ routes:
handler: "About->version"
placeholders:
productName: "openvk[2]?|libresoc"
- url: "/about"
handler: "About->aboutInstance"
- url: "/privacy"
handler: "About->Privacy"
- url: "/badbrowser.php"
@ -47,10 +55,14 @@ routes:
handler: "Auth->register"
- url: "/logout"
handler: "Auth->logout"
- url: "/restore.pl"
- url: "/restore"
handler: "Auth->restore"
- url: "/restore.pl/internal-finish"
- url: "/restore/internal-finish"
handler: "Auth->finishRestoringPassword"
- url: "/reg/resend"
handler: "Auth->resendEmail"
- url: "/regFinish"
handler: "Auth->verifyEmail"
- url: "/setSID/{slug}"
handler: "Auth->su"
- url: "/settings"
@ -67,7 +79,7 @@ routes:
handler: "User->friends"
- url: "/edit"
handler: "User->edit"
- url: "/edit/verify_phone.pl"
- url: "/edit/verify_phone"
handler: "User->verifyPhone"
- url: "/setSub/user"
handler: "User->sub"
@ -75,8 +87,6 @@ routes:
handler: "Group->sub"
- url: "/setSub/v4/club"
handler: "Group->attend"
- url: "/al_comments.pl/create/{text}/{num}"
handler: "Comment->makeComment"
- url: "/groups/{num}/setNewOwner/{num}"
handler: "Group->changeOwner"
- url: "/comment{num}/like"
@ -95,6 +105,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}"
@ -123,9 +135,9 @@ routes:
handler: "Photos->album"
- url: "/album{num}_{num}/edit"
handler: "Photos->editAlbum"
- url: "/album{num}_{num}/delete.pl"
- url: "/album{num}_{num}/delete"
handler: "Photos->deleteAlbum"
- url: "/album{num}_{num}/remove_photo.pl/{num}"
- url: "/album{num}_{num}/remove_photo/{num}"
handler: "Photos->unlinkPhoto"
- url: "/photos/upload"
handler: "Photos->uploadPhoto"
@ -137,7 +149,7 @@ routes:
handler: "Photos->editPhoto"
- url: "/photo{num}_{num}/delete"
handler: "Photos->deletePhoto"
- url: "/al_avatars.pl"
- url: "/al_avatars"
handler: "User->setAvatar"
- url: "/videos{num}"
handler: "Videos->list"
@ -161,7 +173,7 @@ routes:
handler: "Group->followers"
- url: "/club{num}/followers/{num}"
handler: "Group->admin"
- url: "/club{num}/setAdmin.jsp"
- url: "/club{num}/setAdmin"
handler: "Group->modifyAdmin"
- url: "/groups{num}"
handler: "User->groups"
@ -219,6 +231,8 @@ routes:
handler: "Notes->view"
- url: "/notes/create"
handler: "Notes->create"
- url: "/note{num}_{num}/edit"
handler: "Notes->edit"
- url: "/note{num}_{num}/delete"
handler: "Notes->delete"
- url: "/invite"
@ -253,15 +267,15 @@ routes:
handler: "Admin->giftCategory"
- url: "/admin/gifts/{slug}.{num}/"
handler: "Admin->gifts"
- url: "/admin/ban.pl/{num}"
- url: "/admin/ban/{num}"
handler: "Admin->quickBan"
- url: "/admin/warn.pl/{num}"
- url: "/admin/warn/{num}"
handler: "Admin->quickWarn"
- url: "/method/{text}.{text}"
handler: "VKAPI->route"
- url: "/token"
handler: "VKAPI->tokenLogin"
- url: "/sandbox_cocksex"
- url: "/admin/sandbox"
handler: "About->sandbox"
- url: "/internal/wall{num}"
handler: "Wall->wallEmbedded"
@ -271,6 +285,10 @@ routes:
uri: ".+"
- url: "/nodeinfo/2.0"
handler: "ActivityPub->nodeinfo"
- url: "/robots.txt"
handler: "About->robotsTxt"
- url: "/humans.txt"
handler: "About->humansTxt"
- url: "/{?shortCode}"
handler: "UnknownTextRouteStrategy->delegate"
placeholders:

View file

@ -214,7 +214,7 @@ a {
}
.page_yellowheader a {
color: #C8BF85;
color: #696029;
}
.page_content {
@ -563,6 +563,7 @@ input[type="text"], input[type="password"], input[type~="text"], input[type~="pa
font-size: 11px;
font-family: tahoma, verdana, arial, sans-serif;
width: 100%;
box-sizing: border-box;
}
h4 {
@ -794,9 +795,8 @@ table.User {
.tabs {
border-bottom: 1px solid #707070;
margin-left: -10px;
margin-right: -6px;
width: 627px;
margin-right: -12px;
margin-left: -12px;
}
#activetabs {
@ -889,7 +889,6 @@ table.User {
display: flex;
padding: 8px;
cursor: pointer;
width: 611px;
margin-left: 1px;
border-bottom: 1px solid #d6d6d6;
}
@ -923,10 +922,9 @@ table.User {
}
.crp-entry--message.unread {
background-color: #dcdcdc;
background-color: #ededed;
padding: 5px;
width: 346px;
border-radius: 3px;
}
.messenger-app--messages---message.unread {
@ -945,8 +943,6 @@ table.User {
}
.messenger-app--messages---message.unread:last-of-type {
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
padding-bottom: 5px;
margin-bottom: 1.2rem;
}
@ -1153,6 +1149,7 @@ textarea {
width: 100%;
padding: 4px;
resize: none;
box-sizing: border-box;
}
#faqhead {
@ -1584,11 +1581,10 @@ body.scrolled .toTop:hover {
}
.postFeedWrapper {
margin: -10px;
width: 611px;
padding: 4px 8px;
background-color: rgb(240, 240, 240);
border-bottom: 1px solid #ccc;
border-top: 1px solid #ccc;
}
.user-alert {
@ -1789,3 +1785,104 @@ body.scrolled .toTop:hover {
.hover-box:hover {
background-color: #C0CAD5;
}
.summaryBar {
border-bottom: 1px solid #DAE2E8;
clear: both;
padding: 11px 10px;
color: black;
font-weight: normal;
line-height: normal;
margin-left: -12px;
margin-right: -12px;
}
.summaryBar .summary {
color: #45688E;
font-weight: bold;
padding-top: 3px;
padding-bottom: 4px;
display: inline-block;
}
.note_header {
background: #f7f7f7;
border-bottom: solid 1px #DAE1E8;
border-top: solid 1px #45688E;
padding: 4px 6px 5px 6px;
}
.note_header .note_title {
color: #45688E;
font-size: 13px;
font-weight: bold;
line-height: 15px;
margin: 0;
padding: 0 0 1px 0;
}
.note_footer {
border-top: 1px solid #ddd;
clear: both;
margin-top: 10px;
padding: 0px 2px 0px 6px;
}
.comments_count {
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;
padding: 20px;
}
.center {
margin: 0 auto;
}

BIN
Web/static/img/flags/su.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

View file

@ -5,7 +5,9 @@ u(".comment-reply").on("click", function(e) {
let fromGroup = Boolean(comment.data("from-group"));
let postId = comment.data("post-id");
let inputbox = postId == null ? u("#write textarea") : u("#wall-post-input" + (postId || ""));
let mention = ("[" + (fromGroup ? "club" : "id") + authorId + "|" + authorNm + "], ");
inputbox.text("[" + (fromGroup ? "club" : "id") + authorId + "|" + authorNm + "], ");
// Substitute pervious mention if present, prepend otherwise
inputbox.nodes[0].value = inputbox.nodes[0].value.replace(/(^\[([A-Za-z0-9]+)\|([\p{L} 0-9@]+)\], |^)/u, mention);
inputbox.trigger("focusin");
});

View file

@ -35,6 +35,8 @@ function handleUpload(id) {
u("span", indicator.nodes[0]).text(trim(file.name) + " (" + humanFileSize(file.size, false) + ")");
indicator.attr("style", "display: block;");
}
document.querySelector("#post-buttons" + id + " #wallAttachmentMenu").classList.add("hidden");
}
function initGraffiti(id) {
@ -108,7 +110,7 @@ function setupWallPostInputHandlers(id) {
var textArea = e.target;
textArea.style.height = "5px";
var newHeight = textArea.scrollHeight;
textArea.style.height = newHeight + boost;
textArea.style.height = newHeight + boost + "px";
return;
// revert to original size if it is larger (possibly changed by user)

View file

@ -185,7 +185,7 @@ function repostPost(id, hash) {
function setClubAdminComment(clubId, adminId, hash) {
MessageBox("Изменить комментарий к администратору", `
<form action="/club${clubId}/setAdmin.jsp" method="post" id="uClubAdminCommentForm_${clubId}_${adminId}">
<form action="/club${clubId}/setAdmin" method="post" id="uClubAdminCommentForm_${clubId}_${adminId}">
<input type="hidden" name="user" value="${adminId}">
<input type="hidden" name="hash" value="${hash}">
<input type="hidden" name="removeComment" id="uClubAdminCommentRemoveCommentInput_${clubId}_${adminId}" value="0">

View file

@ -1,5 +1,5 @@
window.addEventListener("scroll", function(e) {
if(document.body.scrollTop < 100) {
if(window.scrollY < 100) {
document.body.classList.toggle("scrolled", false);
} else {
document.body.classList.toggle("scrolled", true);

11
Web/static/js/timezone.js Executable file
View file

@ -0,0 +1,11 @@
// This file is included only when there is no info about timezone in users's chandler session
xhr = new XMLHttpRequest();
xhr.open("POST", "/iapi/timezone", true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onload = (response) => {
if(JSON.parse(response.originalTarget.responseText).success == 1) {
window.location.reload();
}
};
xhr.send('timezone=' + new Date().getTimezoneOffset());

View file

@ -167,7 +167,8 @@ function ovk_proc_strtrim(string $string, int $length = 0): string
function ovk_strftime_safe(string $format, ?int $timestamp = NULL): string
{
$str = strftime($format, $timestamp ?? time());
$sessionOffset = intval(Session::i()->get("_timezoneOffset"));
$str = strftime($format, $timestamp + ($sessionOffset * MINUTE) * -1 ?? time() + ($sessionOffset * MINUTE) * -1);
if(PHP_SHLIB_SUFFIX === "dll") {
$enc = tr("__WinEncoding");
if($enc === "@__WinEncoding")
@ -230,7 +231,7 @@ return (function() {
if(is_dir($gitDir = OPENVK_ROOT . "/.git") && $showCommitHash)
$ver = trim(`git --git-dir="$gitDir" log --pretty="%h" -n1 HEAD` ?? "Unknown version") . "-nightly";
else
$ver = "Build 15";
$ver = "Public Technical Preview 3";
// Unix time constants
define('MINUTE', 60);

View file

@ -7,13 +7,14 @@
"james-heinrich/getid3": "^1.9@dev",
"rybakit/msgpack": "dev-master",
"wapmorgan/binary-stream": "dev-master",
"netcarver/textile": "^3.7@dev",
"al/emoji-detector": "dev-master",
"ezyang/htmlpurifier": "dev-master",
"scssphp/scssphp": "dev-master",
"lfkeitel/phptotp": "dev-master",
"chillerlan/php-qrcode": "dev-main",
"vearutop/php-obscene-censor-rus": "dev-master"
"vearutop/php-obscene-censor-rus": "dev-master",
"erusev/parsedown": "dev-master",
"bhaktaraz/php-rss-generator": "dev-master"
},
"minimum-stability": "dev"
}

274
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": "770bb7b5fdc8074bb03f5c2a762914fe",
"content-hash": "2c94032cae911ca438bbcfc46c346961",
"packages": [
{
"name": "al/emoji-detector",
@ -52,18 +52,67 @@
},
"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",
"source": {
"type": "git",
"url": "https://github.com/chillerlan/php-qrcode.git",
"reference": "0c1f322476a090b945108e6df960ee381a8c352e"
"reference": "06730361508c283a02bbf9cbc9e4b55e7b3bc88b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/0c1f322476a090b945108e6df960ee381a8c352e",
"reference": "0c1f322476a090b945108e6df960ee381a8c352e",
"url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/06730361508c283a02bbf9cbc9e4b55e7b3bc88b",
"reference": "06730361508c283a02bbf9cbc9e4b55e7b3bc88b",
"shasum": ""
},
"require": {
@ -138,7 +187,7 @@
"type": "ko_fi"
}
],
"time": "2021-12-12T23:34:10+00:00"
"time": "2021-12-14T15:11:20+00:00"
},
{
"name": "chillerlan/php-settings-container",
@ -203,26 +252,74 @@
],
"time": "2021-09-06T15:17:01+00:00"
},
{
"name": "erusev/parsedown",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
"reference": "6598f3860c2698fe2f0f1bc98212fc01d0a1893c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/6598f3860c2698fe2f0f1bc98212fc01d0a1893c",
"reference": "6598f3860c2698fe2f0f1bc98212fc01d0a1893c",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35"
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-0": {
"Parsedown": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Emanuil Rusev",
"email": "hello@erusev.com",
"homepage": "http://erusev.com"
}
],
"description": "Parser for Markdown.",
"homepage": "http://parsedown.org",
"keywords": [
"markdown",
"parser"
],
"support": {
"issues": "https://github.com/erusev/parsedown/issues",
"source": "https://github.com/erusev/parsedown/tree/master"
},
"time": "2020-08-09T14:12:21+00:00"
},
{
"name": "ezyang/htmlpurifier",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/ezyang/htmlpurifier.git",
"reference": "996eaf43310edf1d908bc0030a18b37b8cf6eefd"
"reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/996eaf43310edf1d908bc0030a18b37b8cf6eefd",
"reference": "996eaf43310edf1d908bc0030a18b37b8cf6eefd",
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/12ab42bd6e742c70c0a52f7b82477fcd44e64b75",
"reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75",
"shasum": ""
},
"require": {
"php": ">=5.2"
},
"require-dev": {
"simpletest/simpletest": "dev-master#72de02a7b80c6bb8864ef9bf66d41d2f58f826bd"
},
"default-branch": true,
"type": "library",
"autoload": {
@ -254,9 +351,9 @@
],
"support": {
"issues": "https://github.com/ezyang/htmlpurifier/issues",
"source": "https://github.com/ezyang/htmlpurifier/tree/master"
"source": "https://github.com/ezyang/htmlpurifier/tree/v4.14.0"
},
"time": "2021-09-07T18:16:55+00:00"
"time": "2021-12-25T01:21:49+00:00"
},
{
"name": "guzzlehttp/guzzle",
@ -357,12 +454,12 @@
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "136a635e2b4a49b9d79e9c8fee267ffb257fdba0"
"reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/136a635e2b4a49b9d79e9c8fee267ffb257fdba0",
"reference": "136a635e2b4a49b9d79e9c8fee267ffb257fdba0",
"url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
"reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
"shasum": ""
},
"require": {
@ -418,7 +515,7 @@
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
"source": "https://github.com/guzzle/promises/tree/1.5.0"
"source": "https://github.com/guzzle/promises/tree/1.5.1"
},
"funding": [
{
@ -434,7 +531,7 @@
"type": "tidelift"
}
],
"time": "2021-10-07T13:05:22+00:00"
"time": "2021-10-22T20:56:57+00:00"
},
{
"name": "guzzlehttp/psr7",
@ -472,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": [
@ -552,12 +649,12 @@
"source": {
"type": "git",
"url": "https://github.com/JamesHeinrich/getID3.git",
"reference": "a440175a329a83dbfad991e67b5e5f3a1ff51bd9"
"reference": "2279f7caca2d761dfc580dd02b401e7a1ff69dfe"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/JamesHeinrich/getID3/zipball/a440175a329a83dbfad991e67b5e5f3a1ff51bd9",
"reference": "a440175a329a83dbfad991e67b5e5f3a1ff51bd9",
"url": "https://api.github.com/repos/JamesHeinrich/getID3/zipball/2279f7caca2d761dfc580dd02b401e7a1ff69dfe",
"reference": "2279f7caca2d761dfc580dd02b401e7a1ff69dfe",
"shasum": ""
},
"require": {
@ -612,7 +709,7 @@
"issues": "https://github.com/JamesHeinrich/getID3/issues",
"source": "https://github.com/JamesHeinrich/getID3/tree/master"
},
"time": "2021-10-07T12:08:13+00:00"
"time": "2022-02-03T17:07:51+00:00"
},
{
"name": "komeiji-satori/curl",
@ -706,66 +803,6 @@
},
"time": "2017-02-06T17:46:14+00:00"
},
{
"name": "netcarver/textile",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/textile/php-textile.git",
"reference": "d64e1f8424afd600cc2732f4f99f262aef55fca2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/textile/php-textile/zipball/d64e1f8424afd600cc2732f4f99f262aef55fca2",
"reference": "d64e1f8424afd600cc2732f4f99f262aef55fca2",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"php-coveralls/php-coveralls": "2.1.*",
"phpunit/phpunit": "5.7.*",
"squizlabs/php_codesniffer": "3.*",
"symfony/yaml": "2.4.*"
},
"default-branch": true,
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.7-dev"
}
},
"autoload": {
"psr-4": {
"Netcarver\\Textile\\": "src/Netcarver/Textile/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "Textile markup language parser",
"homepage": "https://github.com/textile/php-textile",
"keywords": [
"document",
"format",
"html",
"language",
"markup",
"parser",
"php-textile",
"plaintext",
"textile"
],
"support": {
"irc": "irc://irc.freenode.net/textile",
"issues": "https://github.com/textile/php-textile/issues",
"source": "https://github.com/textile/php-textile",
"wiki": "https://github.com/textile/php-textile/wiki"
},
"time": "2020-10-01T18:21:03+00:00"
},
{
"name": "psr/cache",
"version": "1.0.1",
@ -919,12 +956,12 @@
"source": {
"type": "git",
"url": "https://github.com/rybakit/msgpack.php.git",
"reference": "66ca2c3948d72068ca0c8b9a4f599f822a7fe2c3"
"reference": "1e9eda511520e7494c241622671816a83a76e149"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/rybakit/msgpack.php/zipball/66ca2c3948d72068ca0c8b9a4f599f822a7fe2c3",
"reference": "66ca2c3948d72068ca0c8b9a4f599f822a7fe2c3",
"url": "https://api.github.com/repos/rybakit/msgpack.php/zipball/1e9eda511520e7494c241622671816a83a76e149",
"reference": "1e9eda511520e7494c241622671816a83a76e149",
"shasum": ""
},
"require": {
@ -933,7 +970,8 @@
"require-dev": {
"ext-gmp": "*",
"friendsofphp/php-cs-fixer": "^2.14",
"phpunit/phpunit": "^7.1|^8|^9"
"phpunit/phpunit": "^7.1|^8|^9",
"vimeo/psalm": "^3.9|^4"
},
"suggest": {
"ext-decimal": "For converting overflowed integers to Decimal objects",
@ -973,7 +1011,7 @@
"type": "github"
}
],
"time": "2021-07-08T19:01:22+00:00"
"time": "2021-12-17T22:02:51+00:00"
},
{
"name": "scssphp/scssphp",
@ -981,12 +1019,12 @@
"source": {
"type": "git",
"url": "https://github.com/scssphp/scssphp.git",
"reference": "fd4fc9edc49f5a4465e11e03b7d6198404adbc22"
"reference": "c800975c7408e923309fbb51346b84e83dd05700"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/scssphp/scssphp/zipball/fd4fc9edc49f5a4465e11e03b7d6198404adbc22",
"reference": "fd4fc9edc49f5a4465e11e03b7d6198404adbc22",
"url": "https://api.github.com/repos/scssphp/scssphp/zipball/c800975c7408e923309fbb51346b84e83dd05700",
"reference": "c800975c7408e923309fbb51346b84e83dd05700",
"shasum": ""
},
"require": {
@ -1002,7 +1040,7 @@
"symfony/phpunit-bridge": "^5.1",
"thoughtbot/bourbon": "^7.0",
"twbs/bootstrap": "~5.0",
"twbs/bootstrap4": "4.6.0",
"twbs/bootstrap4": "4.6.1",
"zurb/foundation": "~6.5"
},
"suggest": {
@ -1045,11 +1083,11 @@
"issues": "https://github.com/scssphp/scssphp/issues",
"source": "https://github.com/scssphp/scssphp/tree/master"
},
"time": "2021-10-02T12:51:54+00:00"
"time": "2022-01-06T19:41:32+00:00"
},
{
"name": "symfony/polyfill-intl-idn",
"version": "dev-main",
"version": "v1.24.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git",
@ -1069,7 +1107,6 @@
"suggest": {
"ext-intl": "For best performance"
},
"default-branch": true,
"type": "library",
"extra": {
"branch-alias": {
@ -1081,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": [
@ -1117,7 +1154,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-idn/tree/main"
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.24.0"
},
"funding": [
{
@ -1137,7 +1174,7 @@
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "dev-main",
"version": "v1.24.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
@ -1155,7 +1192,6 @@
"suggest": {
"ext-intl": "For best performance"
},
"default-branch": true,
"type": "library",
"extra": {
"branch-alias": {
@ -1167,12 +1203,12 @@
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"classmap": [
"Resources/stubs"
]
@ -1202,7 +1238,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.23.0"
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.24.0"
},
"funding": [
{
@ -1222,7 +1258,7 @@
},
{
"name": "symfony/polyfill-php72",
"version": "dev-main",
"version": "v1.24.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php72.git",
@ -1237,7 +1273,6 @@
"require": {
"php": ">=7.1"
},
"default-branch": true,
"type": "library",
"extra": {
"branch-alias": {
@ -1249,12 +1284,12 @@
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php72\\": ""
},
"files": [
"bootstrap.php"
]
],
"psr-4": {
"Symfony\\Polyfill\\Php72\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -1279,7 +1314,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php72/tree/v1.23.0"
"source": "https://github.com/symfony/polyfill-php72/tree/v1.24.0"
},
"funding": [
{
@ -1520,17 +1555,18 @@
"james-heinrich/getid3": 20,
"rybakit/msgpack": 20,
"wapmorgan/binary-stream": 20,
"netcarver/textile": 20,
"al/emoji-detector": 20,
"ezyang/htmlpurifier": 20,
"scssphp/scssphp": 20,
"lfkeitel/phptotp": 20,
"chillerlan/php-qrcode": 20,
"vearutop/php-obscene-censor-rus": 20
"vearutop/php-obscene-censor-rus": 20,
"erusev/parsedown": 20,
"bhaktaraz/php-rss-generator": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.1.0"
"plugin-api-version": "2.0.0"
}

View file

@ -1,4 +1,4 @@
*OpenVK is a universal colleague search tool based on the VKontakte structure.*
**OpenVK is a universal colleague search tool based on the VKontakte structure.**
We want friends, classmates, classmates, neighbors and colleagues to always be in touch.

View file

@ -1,6 +1,6 @@
OpenVK-KB-Heading: Добро пожаловать
*OpenVK - универсальное средство поиска коллег основанное на структуре ВКонтакте.*
**OpenVK - универсальное средство поиска коллег основанное на структуре ВКонтакте.**
Мы хотим, чтобы друзья, однокурсники, одноклассники, соседи и коллеги всегда могли быть в контакте.

View file

@ -1,6 +1,7 @@
OpenVK-KB-Heading: Editing notes
OpenVK wiki-markup is basically XHTML1.0 Transitional. The only difference is that we removed tags that are not needed or may harm OpenVK and it's users.
Allowed tags:
* All headers from level 3 to 6 (h3-h6)
* Paragraphs (&lt;p&gt;)
@ -8,12 +9,13 @@ Allowed tags:
* &lt;sup&gt;, &lt;sub&gt;, &lt;ins&gt;
* Everything related to tables
* Links and images (&lt;a&gt;, &lt;img&gt;)
* Lists (и &lt;ol&gt; и &lt;ul&gt;)
* Lists (and &lt;ol&gt; and &lt;ul&gt;)
* Line feed and horizontal rule (hr)
* Blockquotes (&lt;blockquote&gt; и &lt;cite&gt;)
* Blockquotes (&lt;blockquote&gt; and &lt;cite&gt;)
* &lt;acronym&gt;
*Please note*: images can't have sourcemap and their source must be a file that is hosted on this OpenVK instance. This restrictions does not apply to links. Links can link to everything (except for data: and javascript: pseudoprotocols). They will be derefered though.
**Please note**: images can't have sourcemap and their source must be a file that is hosted on this OpenVK instance. This restrictions does not apply to links. Links can link to everything (except for data: and javascript: pseudoprotocols). They will be derefered though.
You may also have noticed, that &lt;style&gt; is note in the allowlist, however, we do support styling &lt;div&gt; and &lt;img&gt; tags using style attribute. This CSS properties are allowed:
* float
* height
@ -21,4 +23,5 @@ You may also have noticed, that &lt;style&gt; is note in the allowlist, however,
* max-height
* max-width
* font-weight
If property is a size property it can only accept pixels as value (no %, pt, pc, em, rem, vw or vh).

View file

@ -1,6 +1,7 @@
OpenVK-KB-Heading: Справка по редактированию заметок
Вики-разметка OpenVK это тоже самое, что и XHTML1.0 Transitional. Единственное изменение заключается в том, что мы убрали некоторые теги, которые могут принести вред OpenVK или не нужны.
Список разрешённых тегов:
* Все заголовки 3-6 уровней (h3-h6)
* Параграфы (&lt;p&gt;)
@ -12,7 +13,9 @@ OpenVK-KB-Heading: Справка по редактированию замето
* Перевод строки и горизонтальная линия (hr)
* Цитаты (&lt;blockquote&gt; и &lt;cite&gt;)
* &lt;acronym&gt;
Обратите внимание, источником изображения могут быть только файлы из OpenVK. Это ограничение не распространяется на ссылки, где href может быть любой (в целях безопасности наших пользователей, ссылка будет автоматически заменена на редирект через away.php)
Вы могли заметить, что в списке разрешённых тегов нету &lt;style&gt;, но ничего страшного, вы можете применять аттрибут style к тегам &lt;div&gt; и &lt;img&gt;. В перечень поддерживаемых свойств CSS входят:
* float
* height
@ -20,4 +23,5 @@ OpenVK-KB-Heading: Справка по редактированию замето
* max-height
* max-width
* font-weight
Обратите внимание на то, что поддерживаются только значения в пикселях.

View file

@ -1,7 +1,7 @@
OpenVK-KB-Heading: About points
h4. What are points?
#### What are points?
Points are OpenVK internal currency. You can use it to buy stickers and gifts.
h4. How can I buy points?
#### How can I buy points?
This is example knowledgebase article and it does not have the details about this topic. If you are administrator of this social network, please change this article to fit your needs. If you are a user you can ask your admin directly about this.

View file

@ -1,7 +1,7 @@
OpenVK-KB-Heading: Про голоса
h4. Что такое голоса?
#### Что такое голоса?
Голоса это внутреняя валюта OpenVK. За неё можно купить стикеры или подарки другим пользователям.
h4. Как купить голоса?
Это пример статьи о голосах и из-за этого в ней нет таких деталей. Если вы администратор этой социальной сети, отредактируйте файл data/knowledgebase/points.ru.textile, чтобы он соответствовал вашим нуждам. Если вы простой пользователь, уведомите администратора о том, что он забыл отредактировать файлы и спросите у него, как же вам купить голоса.
#### Как купить голоса?
Это пример статьи о голосах и из-за этого в ней нет таких деталей. Если вы администратор этой социальной сети, отредактируйте файл data/knowledgebase/points.ru.md, чтобы он соответствовал вашим нуждам. Если вы простой пользователь, уведомите администратора о том, что он забыл отредактировать файлы и спросите у него, как же вам купить голоса.

View file

@ -1,31 +1,31 @@
OpenVK-KB-Heading: Privacy Policy
h2. What information do we collect?
## What information do we collect?
* _Basic account information_: If you register on this server, you may be asked to enter your real name, last name, an email address, and a password. You may also enter additional profile information such as a nickname, status, biography, interests, etc. Your first name, last name, a nickname, a status, and a profile picture are always listed publicly.
* _Posts, private messages and other information_: All information is processed and stored on the server. When you submit any content, the date and time are stored with the post, including attachments. Messages may contain media attachments, such as pictures and videos. Posts are available publicly. Personal messages are only delivered to users to whom you have personally sent messages. _Please do not share any dangerous information over OpenVK._
* _IPs and other metadata_: When you log in, we may record the IP address you log in from, as well as the name of your browser application. We also may retain server logs which include the IP address of every request to our server.
h2. What do we use your information for?
## What do we use your information for?
Any of the information we collect from you may be used in the following ways:
* To provide the core functionality of OpenVK. You can only interact with other people's content and post your own content when you are logged in.
* To aid moderation of the community, for example comparing your IP address with other known ones to determine ban evasion or other violations.
* The email address you provide may be used to regain access to your account by changing your password.
h2. How do we protect your information?
## How do we protect your information?
We implement a variety of security measures to maintain the safety of your personal information when you enter, submit, or access your personal information. Among other things, your browser session, as well as the traffic between your applications and the API, are secured with SSL, and your password is hashed using a strong one-way algorithm.
h2. Do we use cookies?
## Do we use cookies?
Yes. Cookies are small files that a site or its service provider transfers to your computer's hard drive through your Web browser (if you allow). These cookies enable the site to recognize your browser and, if you have a registered account, associate it with your registered account.
We use cookies to understand and save your preferences for future visits.
h2. Do we disclose any information to outside parties?
## Do we disclose any information to outside parties?
We do not sell, trade, or otherwise transfer to outside parties your personally identifiable information. This does not include trusted third parties who assist us in operating our site, or servicing you, so long as those parties agree to keep this information confidential. We may also release your information when we believe release is appropriate to comply with the law, enforce our site policies, or protect ours or others rights, property, or safety.
h2. Changes to our Privacy Policy
## Changes to our Privacy Policy
If we decide to change our privacy policy, we will post those changes on this page.
This document is CC-BY-SA. It was last updated October 8, 2021.
Originally adapted from the "Mastodon privacy policy":https://mastodon.social/terms.
Originally adapted from the [Mastodon privacy policy](https://mastodon.social/terms).

View file

@ -1,31 +1,31 @@
OpenVK-KB-Heading: Политика Конфиденциальности
h2. Какую информацию мы собираем?
## Какую информацию мы собираем?
* _Основная информация_: Если у вас есть желание зарегистрироваться на данном сайте, вас могут попросить ввести реальное имя, фамилию, адрес электронной почты и пароль. Также вы можете ввести дополнительную информацию профиля, например, псевдоним, статус, биографию, интересы, и т.д. Имя, фамилия, псевдоним, статус и фото профиля будут общедоступными.
* _Записи, личные сообщения и другая информация_: Вся информация обрабатывается и хранится на сервере. Когда вы отправляете любой контент, дата и время сохраняются с записью, включая вложения. Сообщения могут включать медиа вложения, к примеру изображения и видео. Записи являются общедоступной информацией. Личные сообщения доставляются только тем пользователями, которым Вы лично отправили сообщения. _Пожалуйста, не делитесь любой вредоносной информации через OpenVK._
* _IP-адреса и другие метаданные_: Когда Вы выполняете вход в свой аккаунт, мы можем записывать IP-адрес, с которого был произведён вход, и название вашего веб-браузера. Мы также можем сохранять журналы сервера, которые включают IP-адрес каждого запроса к серверу.
h2. Для чего мы используем вашу информацию?
## Для чего мы используем вашу информацию?
Любую собранную нами информацию мы используем для следующих целей:
* Для предоставления базового функционала OpenVK. Вы можете взаимодействовать с чужим контентом и размещать собственный контент только тогда, когда Вы вошли в систему.
* Чтобы помочь модерации сообщества, например, сравнить ваш IP-адрес с другими известными адресами, чтобы определить уклонения от блокировки или других нарушений.
* Предоставленный вами адрес электронной почты может быть использован для восстановления доступа к вашему аккаунту при помощи смены пароля.
h2. Как мы защищаем вашу информацию?
## Как мы защищаем вашу информацию?
Мы применяем различные меры безопасности для обеспечения сохранности вашей личной информации, когда вы вводите, отправляете или получаете доступ к своей личной информации. Среди прочего, сессия вашего браузера, а также трафик между вашими приложениями и API защищены протоколом SSL, а ваш пароль хэшируется с помощью надежного одностороннего алгоритма.
h2. Используем ли мы файлы cookies?
## Используем ли мы файлы cookies?
Да. Cookies - это небольшие файлы, которые сайт или его поставщик услуг передает на жесткий диск вашего компьютера через ваш веб-браузер (если вы разрешаете). Эти файлы cookie позволяют сайту распознать ваш браузер и, если у вас есть зарегистрированная учетная запись, связать ее с вашей зарегистрированной учетной записью.
Мы используем файлы cookies, чтобы понять и сохранить ваши настройки для будущих посещений.
h2. Раскрываем ли мы какую-либо информацию сторонним лицам?
## Раскрываем ли мы какую-либо информацию сторонним лицам?
Мы не продаем, не обмениваем и не передаем посторонним лицам вашу личную информацию. Это не относится к доверенным третьим лицам, которые помогают нам управлять нашим сайтом или обслуживать вас, если эти лица согласны сохранять конфиденциальность этой информации. Мы также можем раскрыть вашу информацию, если считаем, что это необходимо для соблюдения закона, исполнения правил нашего сайта или защиты наших или чужих прав, собственности или безопасности.
h2. Изменения в нашей Политике конфиденциальности
## Изменения в нашей Политике конфиденциальности
Если мы решим изменить нашу политику конфиденциальности, мы опубликуем эти изменения на данной странице.
Данный документ лицензирован по CC-BY-SA. В последний раз обновлялся 8 октября 2021.
Первоначально адаптировано из "Политики конфиденциальности Mastodon":https://mastodon.social/terms.
Первоначально адаптировано из [Политики конфиденциальности Mastodon](https://mastodon.social/terms).

Some files were not shown because too many files have changed in this diff Show more