Merge branch 'master' into ignored-sources

This commit is contained in:
Vladimir Barinov 2024-10-25 17:12:17 +03:00 committed by GitHub
commit a99ffbdafe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
139 changed files with 7152 additions and 2180 deletions

2
.github/FUNDING.yml vendored
View file

@ -1 +1 @@
custom: "https://openvk.su/donate"
custom: "https://ovk.to/donate"

View file

@ -6,7 +6,7 @@ on:
env:
BASE_IMAGE_NAME: php
BASE_IMAGE_VERSION: "8.1"
BASE_IMAGE_VERSION: "8.2"
jobs:
build-cli:
@ -24,12 +24,18 @@ jobs:
id: buildx
uses: docker/setup-buildx-action@v2
- name: Change repository string to lowercase
id: repositorystring
uses: Entepotenz/change-string-case-action-min-dependencies@v1.1.0
with:
string: ${{ github.repository }}
- name: Log into registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Build cli image
run: |
IMAGE_NAME=ghcr.io/${{ github.repository }}/$BASE_IMAGE_NAME:$BASE_IMAGE_VERSION-cli
IMAGE_NAME=ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/$BASE_IMAGE_NAME:$BASE_IMAGE_VERSION-cli
docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_NAME . --push -f install/automated/docker/base-php-cli.Dockerfile --build-arg VERSION=$BASE_IMAGE_VERSION
@ -48,11 +54,17 @@ jobs:
id: buildx
uses: docker/setup-buildx-action@v2
- name: Change repository string to lowercase
id: repositorystring
uses: Entepotenz/change-string-case-action-min-dependencies@v1.1.0
with:
string: ${{ github.repository }}
- name: Log into registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Build apache image
run: |
IMAGE_NAME=ghcr.io/${{ github.repository }}/$BASE_IMAGE_NAME:$BASE_IMAGE_VERSION-apache
IMAGE_NAME=ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/$BASE_IMAGE_NAME:$BASE_IMAGE_VERSION-apache
docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_NAME . --push -f install/automated/docker/base-php-apache.Dockerfile --build-arg VERSION=$BASE_IMAGE_VERSION

View file

@ -36,12 +36,18 @@ jobs:
id: buildx
uses: docker/setup-buildx-action@v2
- name: Change repository string to lowercase
id: repositorystring
uses: Entepotenz/change-string-case-action-min-dependencies@v1.1.0
with:
string: ${{ github.repository }}
- name: Log into registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Build base image
run: |
IMAGE_ID=ghcr.io/${{ github.repository }}/$BASE_IMAGE_NAME
IMAGE_ID=ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/$BASE_IMAGE_NAME
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
@ -49,16 +55,16 @@ jobs:
echo IMAGE_ID=$IMAGE_ID
echo VERSION=$VERSION
docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_ID:$VERSION . --push -f install/automated/docker/openvk.Dockerfile --build-arg GITREPO=${{ github.repository }}
docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_ID:$VERSION . --push -f install/automated/docker/openvk.Dockerfile --build-arg GITREPO=${{ steps.repositorystring.outputs.lowercase }}
- name: Build MariaDB primary image
run: |
IMAGE_NAME=ghcr.io/${{ github.repository }}/$DB_IMAGE_NAME:$DB_VERSION-primary
IMAGE_NAME=ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/$DB_IMAGE_NAME:$DB_VERSION-primary
docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_NAME . --push -f install/automated/docker/mariadb-primary.Dockerfile --build-arg VERSION=$DB_VERSION
- name: Build MariaDB event image
run: |
IMAGE_NAME=ghcr.io/${{ github.repository }}/$EVENT_IMAGE_NAME:$DB_VERSION-eventdb
IMAGE_NAME=ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/$EVENT_IMAGE_NAME:$DB_VERSION-eventdb
docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_NAME . --push -f install/automated/docker/mariadb-eventdb.Dockerfile --build-arg VERSION=$DB_VERSION

View file

@ -12,7 +12,7 @@
<tr>
<td class="float-center" align="center" valign="top">
<center>
Добро пожаловать в OpenVK! Приятного времяприпровождения, надеюсь вам понравится.<br><br>Если появились вопросы, касаемые нашего сайта, пишите <a href="https://openvk.su/support?act=new">сюда</a>
Добро пожаловать в OpenVK! Приятного времяприпровождения, надеюсь вам понравится.<br><br>Если появились вопросы, касаемые нашего сайта, пишите <a href="https://ovk.to/support?act=new">сюда</a>
</center>
</td>
</tr>

View file

@ -1,4 +1,4 @@
# <img align="right" src="https://github.com/openvk/openvk/raw/master/Web/static/img/logo_shadow.png" alt="openvk" title="openvk" width="15%">OpenVK
# <img align="right" src="/Web/static/img/logo_shadow.png" alt="openvk" title="openvk" width="15%">OpenVK
_[Русский](README_RU.md)_
@ -6,7 +6,7 @@ _[Русский](README_RU.md)_
VKontakte belongs to Pavel Durov and VK Group.
To be honest, we don't know whether if it even works. However, this version is maintained and we will be happy to accept your bugreports [in our bug tracker](https://github.com/openvk/openvk/projects/1). You should also be able to submit them using [ticketing system](https://openvk.su/support?act=new) (you will need an OpenVK account for this).
To be honest, we don't know whether if it even works. However, this version is maintained and we will be happy to accept your bugreports [in our bug tracker](https://github.com/openvk/openvk/projects/1). You should also be able to submit them using [ticketing system](https://ovk.to/support?act=new) (you will need an OpenVK account for this).
## When's the release?
@ -26,6 +26,14 @@ However, OVK makes use of Chandler Application Server. This software requires ex
If you want, you can add your instance to the list above so that people can register there.
### System requirements
Here is our minimum hardware recommendation:
* **CPU: Recent** (AMD Zen2 or equivalent) quad-core 2GHz+ CPU
* **RAM:** At least 2GB RAM (we recommend 6GB or 8GB for OpenVK with Kafka)
* **Minimum database space:** 10GB
### Installation procedure
1. Install PHP 7.4, web-server, Composer, Node.js, Yarn and [Chandler](https://github.com/openvk/chandler)
@ -66,12 +74,12 @@ Once you are done, you can login as a system administrator on the network itself
* **Password**: `admin`
* It is recommended to change the password of the built-in account or disable it.
💡Confused? Full installation walkthrough is available [here](https://docs.openvk.uk/openvk_engine/centos8_installation/) (CentOS 8 [and](https://almalinux.org/) [family](https://yum.oracle.com/oracle-linux-isos.html)).
💡 Confused? Full installation walkthrough is available [here](https://docs.ovk.to/openvk_engine/centos8_installation/) (CentOS 8 [and](https://almalinux.org/) [family](https://yum.oracle.com/oracle-linux-isos.html)).
### Looking for Docker or Kubernetes deployment?
See `install/automated/docker/README.md` and `install/automated/kubernetes/README.md` for Docker and Kubernetes deployment instructions.
### If my website uses OpenVK, should I release it's sources?
### If my website uses OpenVK, should I release its sources?
It depends. You can keep the sources to yourself if you do not plan to distribute your website binaries. If your website software must be distributed, it can stay non-OSS provided the OpenVK is not used as a primary application and is not modified. If you modified OpenVK for your needs or your work is based on it and you are planning to redistribute this, then you should license it under terms of any LGPL-compatible license (like OSL, GPL, LGPL etc).
@ -80,7 +88,7 @@ It depends. You can keep the sources to yourself if you do not plan to distribut
You may reach out to us via:
* [Bug Tracker](https://github.com/openvk/openvk/projects/1)
* [Ticketing System](https://openvk.su/support?act=new)
* [Ticketing System](https://ovk.to/support?act=new)
* Telegram Chat: Go to [our channel](https://t.me/openvkenglish) and open discussion in our channel menu.
* [Reddit](https://www.reddit.com/r/openvk/)
* [GitHub Discussions](https://github.com/openvk/openvk/discussions)

View file

@ -1,4 +1,4 @@
# <img align="right" src="https://github.com/openvk/openvk/raw/master/Web/static/img/logo_shadow.png" alt="openvk" title="openvk" width="15%">OpenVK
# <img align="right" src="/Web/static/img/logo_shadow.png" alt="openvk" title="openvk" width="15%">OpenVK
_[English](README.md)_
@ -6,7 +6,7 @@ _[English](README.md)_
ВКонтакте принадлежит Павлу Дурову и VK Group.
Честно говоря, мы даже не знаем, работает ли она вообще. Однако, эта версия поддерживается, и мы будем рады принять ваши сообщения об ошибках [в нашем баг-трекере](https://github.com/openvk/openvk/projects/1). Вы также можете отправлять их через [вкладку "Помощь"](https://openvk.su/support?act=new) (для этого вам понадобится учетная запись OpenVK).
Честно говоря, мы даже не знаем, работает ли она вообще. Однако, эта версия поддерживается, и мы будем рады принять ваши сообщения об ошибках [в нашем баг-трекере](https://github.com/openvk/openvk/projects/1). Вы также можете отправлять их через [вкладку "Помощь"](https://ovk.to/support?act=new) (для этого вам понадобится учетная запись OpenVK).
## Когда выйдет релизная версия?
@ -66,7 +66,7 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions
* **Пароль**: `admin`
* Перед использованием встроенной учетной записи рекомендуется сменить пароль или отключить её.
💡Запутались? Полное руководство по установке доступно [здесь](https://docs.openvk.uk/openvk_engine/centos8_installation/) (CentOS 8 [и](https://almalinux.org/ru/) [семейство](https://yum.oracle.com/oracle-linux-isos.html)).
💡Запутались? Полное руководство по установке доступно [здесь](https://docs.ovk.to/openvk_engine/centos8_installation/) (CentOS 8 [и](https://almalinux.org/ru/) [семейство](https://yum.oracle.com/oracle-linux-isos.html)).
# Установка в Docker/Kubernetes
Подробные иструкции можно найти в `install/automated/docker/README.md` и `install/automated/kubernetes/README.md` соответственно.
@ -80,7 +80,7 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions
Вы можете связаться с нами через:
* [Баг-трекер](https://github.com/openvk/openvk/projects/1)
* [Помощь в OVK](https://openvk.su/support?act=new)
* [Помощь в OVK](https://ovk.to/support?act=new)
* Telegram-чат: Перейдите на [наш канал](https://t.me/openvk) и откройте обсуждение в меню нашего канала.
* [Reddit](https://www.reddit.com/r/openvk/)
* [GitHub Discussions](https://github.com/openvk/openvk/discussions)

View file

@ -1,8 +1,11 @@
<?php declare(strict_types=1);
namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\APIToken;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\APITokens;
use openvk\Web\Models\Repositories\Applications;
use WhichBrowser;
class Apps implements Handler
{
@ -89,4 +92,25 @@ class Apps implements Handler
$app->withdrawCoins();
$resolve($coins);
}
function getRegularToken(string $clientName, bool $acceptsStale, callable $resolve, callable $reject): void
{
$token = NULL;
$stale = true;
if($acceptsStale)
$token = (new APITokens)->getStaleByUser($this->user->getId(), $clientName);
if(is_null($token)) {
$stale = false;
$token = new APIToken;
$token->setUser($this->user);
$token->setPlatform($clientName ?? (new WhichBrowser\Parser(getallheaders()))->toString());
$token->save();
}
$resolve([
'is_stale' => $stale,
'token' => $token->getFormattedToken(),
]);
}
}

View file

@ -1,76 +0,0 @@
<?php declare(strict_types=1);
namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\{User, Club};
use openvk\Web\Models\Repositories\{Users, Clubs, Videos};
use Chandler\Database\DatabaseConnection;
class Search implements Handler
{
protected $user;
private $users;
private $clubs;
private $videos;
function __construct(?User $user)
{
$this->user = $user;
$this->users = new Users;
$this->clubs = new Clubs;
$this->videos = new Videos;
}
function fastSearch(string $query, string $type = "users", callable $resolve, callable $reject)
{
if($query == "" || strlen($query) < 3)
$reject(12, "No input or input < 3");
$repo;
$sort;
switch($type) {
default:
case "users":
$repo = (new Users);
$sort = "rating DESC";
break;
case "groups":
$repo = (new Clubs);
$sort = "id ASC";
break;
case "videos":
$repo = (new Videos);
$sort = "created ASC";
break;
}
$res = $repo->find($query, ["doNotSearchMe" => $this->user->getId(), "doNotSearchPrivate" => true,], $sort);
$results = array_slice(iterator_to_array($res), 0, 5);
$count = sizeof($results);
$arr = [
"count" => $count,
"items" => []
];
if(sizeof($results) < 1) {
$reject(2, "No results");
}
foreach($results as $res) {
$arr["items"][] = [
"id" => $res->getId(),
"name" => $type == "users" ? $res->getCanonicalName() : $res->getName(),
"avatar" => $type != "videos" ? $res->getAvatarUrl() : $res->getThumbnailURL(),
"url" => $type != "videos" ? $res->getUrl() : "/video".$res->getPrettyId(),
"description" => ovk_proc_strtr($res->getDescription() ?? "...", 40)
];
}
$resolve($arr);
}
}

View file

@ -7,20 +7,32 @@ final class Account extends VKAPIRequestHandler
function getProfileInfo(): object
{
$this->requireUser();
return (object) [
"first_name" => $this->getUser()->getFirstName(),
"id" => $this->getUser()->getId(),
"last_name" => $this->getUser()->getLastName(),
"home_town" => $this->getUser()->getHometown(),
"status" => $this->getUser()->getStatus(),
"audio_status" => is_null($this->getUser()->getCurrentAudioStatus()) ? NULL : $this->getUser()->getCurrentAudioStatus()->toVkApiStruct($this->getUser()),
"bdate" => is_null($this->getUser()->getBirthday()) ? '01.01.1970' : $this->getUser()->getBirthday()->format('%e.%m.%Y'),
"bdate_visibility" => $this->getUser()->getBirthdayPrivacy(),
$user = $this->getUser();
$return_object = (object) [
"first_name" => $user->getFirstName(),
"photo_200" => $user->getAvatarURL("normal"),
"nickname" => $user->getPseudo(),
"is_service_account" => false,
"id" => $user->getId(),
"is_verified" => $user->isVerified(),
"verification_status" => $user->isVerified() ? 'verified' : 'unverified',
"last_name" => $user->getLastName(),
"home_town" => $user->getHometown(),
"status" => $user->getStatus(),
"bdate" => is_null($user->getBirthday()) ? '01.01.1970' : $user->getBirthday()->format('%e.%m.%Y'),
"bdate_visibility" => $user->getBirthdayPrivacy(),
"phone" => "+420 ** *** 228", # TODO
"relation" => $this->getUser()->getMaritalStatus(),
"sex" => $this->getUser()->isFemale() ? 1 : 2
"relation" => $user->getMaritalStatus(),
"screen_name" => $user->getShortCode(),
"sex" => $user->isFemale() ? 1 : 2,
#"email" => $user->getEmail(),
];
$audio_status = $user->getCurrentAudioStatus();
if(!is_null($audio_status))
$return_object->audio_status = $audio_status->toVkApiStruct($user);
return $return_object;
}
function getInfo(): object
@ -152,4 +164,30 @@ final class Account extends VKAPIRequestHandler
return (object) $output;
}
function getBalance(): object
{
$this->requireUser();
if(!OPENVK_ROOT_CONF['openvk']['preferences']['commerce'])
$this->fail(105, "Commerce is disabled on this instance");
return (object) ['votes' => $this->getUser()->getCoins()];
}
function getOvkSettings(): object
{
$this->requireUser();
$user = $this->getUser();
$settings_list = (object)[
'avatar_style' => $user->getStyleAvatar(),
'style' => $user->getStyle(),
'show_rating' => !$user->prefersNotToSeeRating(),
'nsfw_tolerance' => $user->getNsfwTolerance(),
'post_view' => $user->hasMicroblogEnabled() ? 'microblog' : 'old',
'main_page' => $user->getMainPage() == 0 ? 'my_page' : 'news',
];
return $settings_list;
}
}

View file

@ -581,13 +581,18 @@ final class Audio extends VKAPIRequestHandler
];
}
function searchAlbums(string $query, int $offset = 0, int $limit = 25, int $drop_private = 0): object
function searchAlbums(string $query = '', int $offset = 0, int $limit = 25, int $drop_private = 0, int $order = 0, int $from_me = 0): object
{
$this->requireUser();
$playlists = [];
$search = (new Audios)->searchPlaylists($query)->offsetLimit($offset, $limit);
foreach($search as $playlist) {
$params = [];
$order_str = (['id', 'length', 'listens'][$order] ?? 'id');
if($from_me === 1)
$params['from_me'] = $this->getUser()->getId();
$search = (new Audios)->findPlaylists($query, $params, ['type' => $order_str, 'invert' => false]);
foreach($search->offsetLimit($offset, $limit) as $playlist) {
if(!$playlist->canBeViewedBy($this->getUser())) {
if($drop_private == 0)
$playlists[] = NULL;
@ -599,7 +604,7 @@ final class Audio extends VKAPIRequestHandler
}
return (object) [
"count" => sizeof($playlists),
"count" => $search->size(),
"items" => $playlists,
];
}

View file

@ -147,7 +147,7 @@ final class Friends extends VKAPIRequestHandler
return $response;
}
function getRequests(string $fields = "", int $offset = 0, int $count = 100, int $extended = 0): object
function getRequests(string $fields = "", int $out = 0, int $offset = 0, int $count = 100, int $extended = 0): object
{
if ($count >= 1000)
$this->fail(100, "One of the required parameters was not passed or is invalid.");
@ -158,10 +158,19 @@ final class Friends extends VKAPIRequestHandler
$offset++;
$followers = [];
if ($out != 0) {
foreach($this->getUser()->getFollowers($offset, $count) as $follower) {
$followers[$i] = $follower->getId();
$i++;
}
}
else
{
foreach($this->getUser()->getRequests($offset, $count) as $follower) {
$followers[$i] = $follower->getId();
$i++;
}
}
$response = $followers;
$usersApi = new Users($this->getUser());

View file

@ -6,15 +6,18 @@ use openvk\Web\Models\Entities\Notifications\GiftNotification;
final class Gifts extends VKAPIRequestHandler
{
function get(int $user_id, int $count = 10, int $offset = 0)
function get(int $user_id = NULL, int $count = 10, int $offset = 0)
{
$this->requireUser();
$i = 0;
$i += $offset;
$server_url = ovk_scheme(true) . $_SERVER["HTTP_HOST"];
if($user_id)
$user = (new UsersRepo)->get($user_id);
else
$user = $this->getUser();
if(!$user || $user->isDeleted())
$this->fail(177, "Invalid user");
@ -47,9 +50,9 @@ final class Gifts extends VKAPIRequestHandler
"date" => $gift->sent->timestamp(),
"gift" => [
"id" => $gift->gift->getId(),
"thumb_256" => $gift->gift->getImage(2),
"thumb_96" => $gift->gift->getImage(2),
"thumb_48" => $gift->gift->getImage(2)
"thumb_256" => $server_url. $gift->gift->getImage(2),
"thumb_96" => $server_url . $gift->gift->getImage(2),
"thumb_48" => $server_url . $gift->gift->getImage(2)
],
"privacy" => 0
];
@ -125,12 +128,13 @@ final class Gifts extends VKAPIRequestHandler
$this->fail(501, "Not implemented");
}
# этих методов не было в ВК, но я их добавил чтобы можно было отобразить список подарков
# в vk кстати называется gifts.getCatalog
function getCategories(bool $extended = false, int $page = 1)
{
$cats = (new GiftsRepo)->getCategories($page);
$categ = [];
$i = 0;
$server_url = ovk_scheme(true) . $_SERVER["HTTP_HOST"];
if(!OPENVK_ROOT_CONF['openvk']['preferences']['commerce'])
$this->fail(105, "Commerce is disabled on this instance");
@ -140,7 +144,7 @@ final class Gifts extends VKAPIRequestHandler
"name" => $cat->getName(),
"description" => $cat->getDescription(),
"id" => $cat->getId(),
"thumbnail" => $cat->getThumbnailURL(),
"thumbnail" => $server_url . $cat->getThumbnailURL(),
];
if($extended == true) {
@ -178,7 +182,7 @@ final class Gifts extends VKAPIRequestHandler
"name" => $gift->getName(),
"image" => $gift->getImage(2),
"usages_left" => (int)$gift->getUsagesLeft($this->getUser()),
"price" => $gift->getPrice(), # голосов
"price" => $gift->getPrice(),
"is_free" => $gift->isFree()
];
}

View file

@ -88,6 +88,10 @@ final class Groups extends VKAPIRequestHandler
case "can_suggest":
$rClubs[$i]->can_suggest = !$usr->canBeModifiedBy($this->getUser()) && $usr->getWallType() == 2;
break;
case "background":
$backgrounds = $usr->getBackDropPictureURLs();
$rClubs[$i]->background = $backgrounds;
break;
# unstandard feild
case "suggested_count":
if($usr->getWallType() != 2) {
@ -208,6 +212,10 @@ final class Groups extends VKAPIRequestHandler
case "can_suggest":
$response[$i]->can_suggest = !$clb->canBeModifiedBy($this->getUser()) && $clb->getWallType() == 2;
break;
case "background":
$backgrounds = $clb->getBackDropPictureURLs();
$response[$i]->background = $backgrounds;
break;
# unstandard feild
case "suggested_count":
if($clb->getWallType() != 2) {
@ -244,23 +252,30 @@ final class Groups extends VKAPIRequestHandler
return $response;
}
function search(string $q, int $offset = 0, int $count = 100)
function search(string $q, int $offset = 0, int $count = 100, string $fields = "screen_name,is_admin,is_member,is_advertiser,photo_50,photo_100,photo_200")
{
if($count > 100) {
$this->fail(100, "One of the parameters specified was missing or invalid: count should be less or equal to 100");
}
$clubs = new ClubsRepo;
$array = [];
$find = $clubs->find($q);
foreach ($find as $group)
foreach ($find->offsetLimit($offset, $count) as $group)
$array[] = $group->getId();
if(!$array || sizeof($array) < 1) {
return (object) [
"count" => 0,
"items" => [],
];
}
return (object) [
"count" => $find->size(),
"items" => $this->getById(implode(',', $array), "", "is_admin,is_member,is_advertiser,photo_50,photo_100,photo_200", $offset, $count)
/*
* As there is no thing as "fields" by the original documentation
* i'll just bake this param by the example shown here: https://dev.vk.com/method/groups.search
*/
"items" => $this->getById(implode(',', $array), "", $fields)
];
}
@ -347,7 +362,10 @@ final class Groups extends VKAPIRequestHandler
!empty($topics) ? $club->setEveryone_Can_Create_Topics($topics) : NULL;
!empty($adminlist) ? $club->setAdministrators_List_Display($adminlist) : NULL;
!empty($topicsAboveWall) ? $club->setDisplay_Topics_Above_Wall($topicsAboveWall) : NULL;
if (!$club->isHidingFromGlobalFeedEnforced()) {
!empty($hideFromGlobalFeed) ? $club->setHide_From_Global_Feed($hideFromGlobalFeed) : NULL;
}
in_array($audio, [0, 1]) ? $club->setEveryone_can_upload_audios($audio) : NULL;

View file

@ -151,7 +151,6 @@ final class Users extends VKAPIRequestHandler
}
case "music":
if(!$canView) {
$response[$i]->music = "secret";
break;
}
@ -159,7 +158,6 @@ final class Users extends VKAPIRequestHandler
break;
case "movies":
if(!$canView) {
$response[$i]->movies = "secret";
break;
}
@ -167,7 +165,6 @@ final class Users extends VKAPIRequestHandler
break;
case "tv":
if(!$canView) {
$response[$i]->tv = "secret";
break;
}
@ -175,7 +172,6 @@ final class Users extends VKAPIRequestHandler
break;
case "books":
if(!$canView) {
$response[$i]->books = "secret";
break;
}
@ -183,7 +179,6 @@ final class Users extends VKAPIRequestHandler
break;
case "city":
if(!$canView) {
$response[$i]->city = "Воскресенск";
break;
}
@ -191,7 +186,6 @@ final class Users extends VKAPIRequestHandler
break;
case "interests":
if(!$canView) {
$response[$i]->interests = "secret";
break;
}
@ -199,7 +193,6 @@ final class Users extends VKAPIRequestHandler
break;
case "quotes":
if(!$canView) {
$response[$i]->quotes = "secret";
break;
}
@ -207,7 +200,6 @@ final class Users extends VKAPIRequestHandler
break;
case "email":
if(!$canView) {
$response[$i]->email = "secret@gmail.com";
break;
}
@ -215,7 +207,6 @@ final class Users extends VKAPIRequestHandler
break;
case "telegram":
if(!$canView) {
$response[$i]->telegram = "@secret";
break;
}
@ -223,7 +214,6 @@ final class Users extends VKAPIRequestHandler
break;
case "about":
if(!$canView) {
$response[$i]->about = "secret";
break;
}
@ -231,7 +221,6 @@ final class Users extends VKAPIRequestHandler
break;
case "rating":
if(!$canView) {
$response[$i]->rating = 22;
break;
}
@ -240,12 +229,43 @@ final class Users extends VKAPIRequestHandler
case "counters":
$response[$i]->counters = (object) [
"friends_count" => $usr->getFriendsCount(),
"photos_count" => (new Albums)->getUserPhotosCount($usr),
"photos_count" => (new Photos)->getUserPhotosCount($usr),
"videos_count" => (new Videos)->getUserVideosCount($usr),
"audios_count" => (new Audios)->getUserCollectionSize($usr),
"notes_count" => (new Notes)->getUserNotesCount($usr)
];
break;
case "correct_counters":
$response[$i]->counters = (object) [
"friends" => $usr->getFriendsCount(),
"photos" => (new Photos)->getUserPhotosCount($usr),
"videos" => (new Videos)->getUserVideosCount($usr),
"audios" => (new Audios)->getUserCollectionSize($usr),
"notes" => (new Notes)->getUserNotesCount($usr),
"groups" => $usr->getClubCount(),
"online_friends" => $usr->getFriendsOnlineCount(),
];
break;
case "guid":
$response[$i]->guid = $usr->getChandlerGUID();
break;
case 'background':
$backgrounds = $usr->getBackDropPictureURLs();
$response[$i]->background = $backgrounds;
break;
case 'reg_date':
if(!$canView) {
break;
}
$response[$i]->reg_date = $usr->getRegistrationTime()->timestamp();
break;
case 'is_dead':
$response[$i]->is_dead = $usr->isDead();
break;
case 'nickname':
$response[$i]->nickname = $usr->getPseudo();
break;
}
}
@ -297,89 +317,90 @@ final class Users extends VKAPIRequestHandler
int $count = 100,
string $city = "",
string $hometown = "",
int $sex = 2,
int $status = 0, # это про marital status
int $sex = 3,
int $status = 0, # marital_status
bool $online = false,
# дальше идут параметры которых нету в vkapi но есть на сайте
string $profileStatus = "", # а это уже нормальный статус
# non standart params:
int $sort = 0,
int $before = 0,
int $politViews = 0,
int $after = 0,
string $interests = "",
int $polit_views = 0,
string $fav_music = "",
string $fav_films = "",
string $fav_shows = "",
string $fav_books = "",
string $fav_quotes = ""
string $fav_books = ""
)
{
if($count > 100) {
$this->fail(100, "One of the parameters specified was missing or invalid: count should be less or equal to 100");
}
$users = new UsersRepo;
$sortg = "id ASC";
$nfilds = $fields;
switch($sort) {
case 0:
$sortg = "id DESC";
break;
case 1:
$sortg = "id ASC";
break;
case 2:
$sortg = "first_name DESC";
break;
case 3:
$sortg = "first_name ASC";
break;
case 4:
$sortg = "rating DESC";
if(!str_contains($nfilds, "rating")) {
$nfilds .= "rating";
}
break;
case 5:
$sortg = "rating DESC";
if(!str_contains($nfilds, "rating")) {
$nfilds .= "rating";
}
break;
}
$array = [];
$parameters = [
"city" => !empty($city) ? $city : NULL,
"hometown" => !empty($hometown) ? $hometown : NULL,
"gender" => $sex < 2 ? $sex : NULL,
"maritalstatus" => (bool)$status ? $status : NULL,
"politViews" => (bool)$politViews ? $politViews : NULL,
"is_online" => $online ? 1 : NULL,
"status" => !empty($profileStatus) ? $profileStatus : NULL,
"before" => $before != 0 ? $before : NULL,
"after" => $after != 0 ? $after : NULL,
"interests" => !empty($interests) ? $interests : NULL,
"fav_music" => !empty($fav_music) ? $fav_music : NULL,
"fav_films" => !empty($fav_films) ? $fav_films : NULL,
"fav_shows" => !empty($fav_shows) ? $fav_shows : NULL,
"fav_books" => !empty($fav_books) ? $fav_books : NULL,
"fav_quotes" => !empty($fav_quotes) ? $fav_quotes : NULL,
"doNotSearchPrivate" => true,
$output_sort = ['type' => 'id', 'invert' => false];
$output_params = [
"ignore_private" => true,
];
$find = $users->find($q, $parameters, $sortg);
switch($sort) {
default:
case 0:
$output_sort = ['type' => 'id', 'invert' => false];
break;
case 1:
$output_sort = ['type' => 'id', 'invert' => true];
break;
case 4:
$output_sort = ['type' => 'rating', 'invert' => false];
break;
}
foreach ($find as $user)
if(!empty($city))
$output_params['city'] = $city;
if(!empty($hometown))
$output_params['hometown'] = $hometown;
if($sex != 3)
$output_params['gender'] = $sex;
if($status != 0)
$output_params['marital_status'] = $status;
if($polit_views != 0)
$output_params['polit_views'] = $polit_views;
if(!empty($interests))
$output_params['interests'] = $interests;
if(!empty($fav_music))
$output_params['fav_music'] = $fav_music;
if(!empty($fav_films))
$output_params['fav_films'] = $fav_films;
if(!empty($fav_shows))
$output_params['fav_shows'] = $fav_shows;
if(!empty($fav_books))
$output_params['fav_books'] = $fav_books;
if($online)
$output_params['is_online'] = 1;
$array = [];
$find = $users->find($q, $output_params, $output_sort);
foreach ($find->offsetLimit($offset, $count) as $user)
$array[] = $user->getId();
if(!$array || sizeof($array) < 1) {
return (object) [
"count" => 0,
"items" => [],
];
}
return (object) [
"count" => $find->size(),
"items" => $this->get(implode(',', $array), $nfilds, $offset, $count)
"items" => $this->get(implode(',', $array), $fields)
];
}

View file

@ -22,7 +22,7 @@ final class Utils extends VKAPIRequestHandler
"object_id" => (int) substr($screen_name, strlen("club")),
"type" => "group"
];
}
} else $this->fail(104, "Not found");
} else {
$user = (new Users)->getByShortURL($screen_name);
if($user) {
@ -40,7 +40,16 @@ final class Utils extends VKAPIRequestHandler
];
}
return (object) [];
$this->fail(104, "Not found");
}
}
function resolveGuid(string $guid): object
{
$user = (new Users)->getByChandlerUserId($guid);
if (is_null($user))
$this->fail(104, "Not found");
return $user->toVkApiStruct($this->getUser());
}
}

View file

@ -60,4 +60,60 @@ final class Video extends VKAPIRequestHandler
];
}
}
function search(string $q = '', int $sort = 0, int $offset = 0, int $count = 10, bool $extended = false, string $fields = ''): object
{
$this->requireUser();
$params = [];
$db_sort = ['type' => 'id', 'invert' => false];
$videos = (new VideosRepo)->find($q, $params, $db_sort);
$items = iterator_to_array($videos->offsetLimit($offset, $count));
$count = $videos->size();
$return_items = [];
$profiles = [];
$groups = [];
foreach($items as $item)
$return_item = $item->getApiStructure($this->getUser());
$return_item = $return_item->video;
$return_items[] = $return_item;
if($return_item['owner_id']) {
if($return_item['owner_id'] > 0)
$profiles[] = $return_item['owner_id'];
else
$groups[] = abs($return_item['owner_id']);
}
if($extended) {
$profiles = array_unique($profiles);
$groups = array_unique($groups);
$profilesFormatted = [];
$groupsFormatted = [];
foreach($profiles as $prof) {
$profile = (new UsersRepo)->get($prof);
$profilesFormatted[] = $profile->toVkApiStruct($this->getUser(), $fields);
}
foreach($groups as $gr) {
$group = (new ClubsRepo)->get($gr);
$groupsFormatted[] = $group->toVkApiStruct($this->getUser(), $fields);
}
return (object) [
"count" => $count,
"items" => $return_items,
"profiles" => $profilesFormatted,
"groups" => $groupsFormatted,
];
}
return (object) [
"count" => $count,
"items" => $return_items,
];
}
}

View file

@ -126,65 +126,41 @@ final class Wall extends VKAPIRequestHandler
else
$profiles[] = $attachment->getOwner()->getId();
$post_source = [];
if($attachment->getPlatform(true) === NULL) {
$post_source = (object)["type" => "vk"];
} else {
$post_source = (object)[
"type" => "api",
"platform" => $attachment->getPlatform(true)
];
}
$repost[] = [
"id" => $attachment->getVirtualId(),
"owner_id" => $attachment->isPostedOnBehalfOfGroup() ? $attachment->getOwner()->getId() * -1 : $attachment->getOwner()->getId(),
"from_id" => $attachment->isPostedOnBehalfOfGroup() ? $attachment->getOwner()->getId() * -1 : $attachment->getOwner()->getId(),
"date" => $attachment->getPublicationTime()->timestamp(),
"post_type" => "post",
"post_type" => $attachment->getVkApiType(),
"text" => $attachment->getText(false),
"attachments" => $repostAttachments,
"post_source" => $post_source,
"post_source" => $attachment->getPostSourceInfo(),
];
if ($attachment->getVirtualId() > 0)
$profiles[] = $attachment->getVirtualId();
if ($attachment->getTargetWall() > 0)
$profiles[] = $attachment->getTargetWall();
else
$groups[] = $attachment->getVirtualId();
$groups[] = abs($attachment->getTargetWall());
if($post->isSigned())
$profiles[] = $attachment->getOwner()->getId();
}
}
$post_source = [];
if($post->getPlatform(true) === NULL) {
$post_source = (object)["type" => "vk"];
} else {
$post_source = (object)[
"type" => "api",
"platform" => $post->getPlatform(true)
];
}
$postType = "post";
$signerId = NULL;
if($post->getSuggestionType() != 0)
$postType = "suggest";
if($post->isSigned()) {
$actualAuthor = $post->getOwner(false);
$signerId = $actualAuthor->getId();
}
$items[] = (object)[
# TODO "can_pin", "copy_history" и прочее не должны возвращаться, если равны null или false
# Ну и ещё всё надо перенести в toVkApiStruct, а то слишком много дублированного кода
$post_temp_obj = (object)[
"id" => $post->getVirtualId(),
"from_id" => $from_id,
"owner_id" => $post->getTargetWall(),
"date" => $post->getPublicationTime()->timestamp(),
"post_type" => $postType,
"post_type" => $post->getVkApiType(),
"text" => $post->getText(false),
"copy_history" => $repost,
"can_edit" => $post->canBeEditedBy($this->getUser()),
@ -195,8 +171,7 @@ final class Wall extends VKAPIRequestHandler
"is_pinned" => $post->isPinned(),
"is_explicit" => $post->isExplicit(),
"attachments" => $attachments,
"post_source" => $post_source,
"signer_id" => $signerId,
"post_source" => $post->getPostSourceInfo(),
"comments" => (object)[
"count" => $post->getCommentsCount(),
"can_post" => 1
@ -213,6 +188,14 @@ final class Wall extends VKAPIRequestHandler
]
];
if($signerId)
$post_temp_obj->signer_id = $signerId;
if($post->isDeactivationMessage())
$post_temp_obj->final_post = 1;
$items[] = $post_temp_obj;
if ($from_id > 0)
$profiles[] = $from_id;
else
@ -332,17 +315,6 @@ final class Wall extends VKAPIRequestHandler
else
$profiles[] = $attachment->getOwner()->getId();
$post_source = [];
if($attachment->getPlatform(true) === NULL) {
$post_source = (object)["type" => "vk"];
} else {
$post_source = (object)[
"type" => "api",
"platform" => $attachment->getPlatform(true)
];
}
$repost[] = [
"id" => $attachment->getVirtualId(),
"owner_id" => $attachment->isPostedOnBehalfOfGroup() ? $attachment->getOwner()->getId() * -1 : $attachment->getOwner()->getId(),
@ -351,47 +323,29 @@ final class Wall extends VKAPIRequestHandler
"post_type" => "post",
"text" => $attachment->getText(false),
"attachments" => $repostAttachments,
"post_source" => $post_source,
"post_source" => $attachment->getPostSourceInfo(),
];
if ($attachment->getVirtualId() > 0)
$profiles[] = $attachment->getVirtualId();
if ($attachment->getTargetWall() > 0)
$profiles[] = $attachment->getTargetWall();
else
$groups[] = $attachment->getVirtualId();
$groups[] = abs($attachment->getTargetWall());
if($post->isSigned())
$profiles[] = $attachment->getOwner()->getId();
}
}
$post_source = [];
if($post->getPlatform(true) === NULL) {
$post_source = (object)["type" => "vk"];
} else {
$post_source = (object)[
"type" => "api",
"platform" => $post->getPlatform(true)
];
}
# TODO: $post->getVkApiType()
$postType = "post";
$signerId = NULL;
if($post->getSuggestionType() != 0)
$postType = "suggest";
if($post->isSigned()) {
$actualAuthor = $post->getOwner(false);
$signerId = $actualAuthor->getId();
}
$items[] = (object)[
$post_temp_obj = (object)[
"id" => $post->getVirtualId(),
"from_id" => $from_id,
"owner_id" => $post->getTargetWall(),
"date" => $post->getPublicationTime()->timestamp(),
"post_type" => $postType,
"post_type" => $post->getVkApiType(),
"text" => $post->getText(false),
"copy_history" => $repost,
"can_edit" => $post->canBeEditedBy($this->getUser()),
@ -401,8 +355,7 @@ final class Wall extends VKAPIRequestHandler
"is_archived" => false,
"is_pinned" => $post->isPinned(),
"is_explicit" => $post->isExplicit(),
"post_source" => $post_source,
"signer_id" => $signerId,
"post_source" => $post->getPostSourceInfo(),
"attachments" => $attachments,
"comments" => (object)[
"count" => $post->getCommentsCount(),
@ -420,6 +373,14 @@ final class Wall extends VKAPIRequestHandler
]
];
if($signerId)
$post_temp_obj->signer_id = $signerId;
if($post->isDeactivationMessage())
$post_temp_obj->final_post = 1;
$items[] = $post_temp_obj;
if ($from_id > 0)
$profiles[] = $from_id;
else
@ -792,6 +753,9 @@ final class Wall extends VKAPIRequestHandler
]
];
if($comment->isFromPostAuthor($post))
$item['is_from_post_author'] = true;
if($need_likes == true)
$item['likes'] = [
"can_like" => 1,
@ -875,6 +839,9 @@ final class Wall extends VKAPIRequestHandler
]
];
if($comment->isFromPostAuthor())
$item['is_from_post_author'] = true;
if($extended == true)
$profiles[] = $comment->getOwner()->getId();
@ -890,8 +857,6 @@ final class Wall extends VKAPIRequestHandler
$response['profiles'] = (!empty($profiles) ? (new Users)->get(implode(',', $profiles), $fields) : []);
}
return $response;
}

View file

@ -5,7 +5,7 @@ exceptions. It is still a work-in-progress functionality.
**Note**: requests to API are routed through
openvk.Web.Presenters.VKAPIPresenter, this dir contains only handlers.
[Documentation for API clients](https://docs.openvk.uk/openvk_engine/api/description/)
[Documentation for API clients](https://docs.ovk.to/openvk_engine/api/description/)
## Implementing API methods

View file

@ -17,7 +17,7 @@ class Audio extends Media
# Taken from winamp :D
const genres = [
'Blues','Big Band','Classic Rock','Chorus','Country','Easy Listening','Dance','Acoustic','Disco','Humour','Funk','Speech','Grunge','Chanson','Hip-Hop','Opera','Jazz','Chamber Music','Metal','Sonata','New Age','Symphony','Oldies','Booty Bass','Other','Primus','Pop','Porn Groove','R&B','Satire','Rap','Slow Jam','Reggae','Club','Rock','Tango','Techno','Samba','Industrial','Folklore','Alternative','Ballad','Ska','Power Ballad','Death Metal','Rhythmic Soul','Pranks','Freestyle','Soundtrack','Duet','Euro-Techno','Punk Rock','Ambient','Drum Solo','Trip-Hop','A Cappella','Vocal','Euro-House','Jazz+Funk','Dance Hall','Fusion','Goa','Trance','Drum & Bass','Classical','Club-House','Instrumental','Hardcore','Acid','Terror','House','Indie','Game','BritPop','Sound Clip','Negerpunk','Gospel','Polsk Punk','Noise','Beat','AlternRock','Christian Gangsta Rap','Bass','Heavy Metal','Soul','Black Metal','Punk','Crossover','Space','Contemporary Christian','Meditative','Christian Rock','Instrumental Pop','Merengue','Instrumental Rock','Salsa','Ethnic','Thrash Metal','Gothic','Anime','Darkwave','JPop','Techno-Industrial','Synthpop','Electronic','Abstract','Pop-Folk','Art Rock','Eurodance','Baroque','Dream','Bhangra','Southern Rock','Big Beat','Comedy','Breakbeat','Cult','Chillout','Gangsta Rap','Downtempo','Top 40','Dub','Christian Rap','EBM','Pop / Funk','Eclectic','Jungle','Electro','Native American','Electroclash','Cabaret','Emo','New Wave','Experimental','Psychedelic','Garage','Rave','Global','Showtunes','IDM','Trailer','Illbient','Lo-Fi','Industro-Goth','Tribal','Jam Band','Acid Punk','Krautrock','Acid Jazz','Leftfield','Polka','Lounge','Retro','Math Rock','Musical','New Romantic','Rock & Roll','Nu-Breakz','Hard Rock','Post-Punk','Folk','Post-Rock','Folk-Rock','Psytrance','National Folk','Shoegaze','Swing','Space Rock','Fast Fusion','Trop Rock','Bebob','World Music','Latin','Neoclassical','Revival','Audiobook','Celtic','Audio Theatre','Bluegrass','Neue Deutsche Welle','Avantgarde','Podcast','Gothic Rock','Indie Rock','Progressive Rock','G-Funk','Psychedelic Rock','Dubstep','Symphonic Rock','Garage Rock','Slow Rock','Psybient','Psychobilly','Touhou'
'A Cappella', 'Abstract', 'Acid', 'Acid Jazz', 'Acid Punk', 'Acoustic', 'AlternRock', 'Alternative', 'Ambient', 'Anime', 'Art Rock', 'Audio Theatre', 'Audiobook', 'Avantgarde', 'Ballad', 'Baroque', 'Bass', 'Beat', 'Bebob', 'Bhangra', 'Big Band', 'Big Beat', 'Black Metal', 'Bluegrass', 'Blues', 'Booty Bass', 'Breakbeat', 'BritPop', 'Cabaret', 'Celtic', 'Chamber Music', 'Chanson', 'Chillout', 'Chorus', 'Christian Gangsta Rap', 'Christian Rap', 'Christian Rock', 'Classic Rock', 'Classical', 'Club', 'Club-House', 'Comedy', 'Contemporary Christian', 'Country', 'Crossover', 'Cult', 'Dance', 'Dance Hall', 'Darkwave', 'Death Metal', 'Disco', 'Downtempo', 'Dream', 'Drum & Bass', 'Drum Solo', 'Dub', 'Dubstep', 'Duet', 'EBM', 'Easy Listening', 'Eclectic', 'Electro', 'Electroclash', 'Electronic', 'Emo', 'Ethnic', 'Euro-House', 'Euro-Techno', 'Eurodance', 'Experimental', 'Fast Fusion', 'Folk', 'Folk-Rock', 'Folklore', 'Freestyle', 'Funk', 'Fusion', 'G-Funk', 'Game', 'Gangsta Rap', 'Garage', 'Garage Rock', 'Global', 'Goa', 'Gospel', 'Gothic', 'Gothic Rock', 'Grunge', 'Hard Rock', 'Hardcore', 'Heavy Metal', 'Hip-Hop', 'House', 'Humour', 'IDM', 'Illbient', 'Indie', 'Indie Rock', 'Industrial', 'Industro-Goth', 'Instrumental', 'Instrumental Pop', 'Instrumental Rock', 'JPop', 'Jam Band', 'Jazz', 'Jazz+Funk', 'Jungle', 'Krautrock', 'Latin', 'Leftfield', 'Lo-Fi', 'Lounge', 'Math Rock', 'Meditative', 'Merengue', 'Metal', 'Musical', 'National Folk', 'Native American', 'Negerpunk', 'Neoclassical', 'Neue Deutsche Welle', 'New Age', 'New Romantic', 'New Wave', 'Noise', 'Nu-Breakz', 'Oldies', 'Opera', 'Other', 'Podcast', 'Polka', 'Polsk Punk', 'Pop', 'Pop / Funk', 'Pop-Folk', 'Porn Groove', 'Post-Punk', 'Post-Rock', 'Power Ballad', 'Pranks', 'Primus', 'Progressive Rock', 'Psybient', 'Psychedelic', 'Psychedelic Rock', 'Psychobilly', 'Psytrance', 'Punk', 'Punk Rock', 'R&B', 'Rap', 'Rave', 'Reggae', 'Retro', 'Revival', 'Rhythmic Soul', 'Rock', 'Rock & Roll', 'Salsa', 'Samba', 'Satire', 'Shoegaze', 'Showtunes', 'Ska', 'Slow Jam', 'Slow Rock', 'Sonata', 'Soul', 'Sound Clip', 'Soundtrack', 'Southern Rock', 'Space', 'Space Rock', 'Speech', 'Swing', 'Symphonic Rock', 'Symphony', 'Synthpop', 'Tango', 'Techno', 'Techno-Industrial', 'Terror', 'Thrash Metal', 'Top 40', 'Touhou', 'Trailer', 'Trance', 'Tribal', 'Trip-Hop', 'Trop Rock', 'Vocal', 'World Music'
];
# Taken from: https://web.archive.org/web/20220322153107/https://dev.vk.com/reference/objects/audio-genres
@ -152,6 +152,11 @@ class Audio extends Media
return $this->getPerformer() . "" . $this->getTitle();
}
function getDownloadName(): string
{
return preg_replace('/[\\/:*?"<>|]/', '_', str_replace(' ', '_', $this->getName()));
}
function getGenre(): ?string
{
return $this->getRecord()->genre;

View file

@ -152,6 +152,11 @@ class Club extends RowModel
return (bool) $this->getRecord()->hide_from_global_feed;
}
function isHidingFromGlobalFeedEnforced(): bool
{
return (bool) $this->getRecord()->enforce_hiding_from_global_feed;
}
function getType(): int
{
return $this->getRecord()->type;
@ -432,7 +437,7 @@ class Club extends RowModel
return (new \openvk\Web\Models\Repositories\Audios)->getClubCollectionSize($this);
}
function toVkApiStruct(?User $user = NULL): object
function toVkApiStruct(?User $user = NULL, string $fields = ''): object
{
$res = (object) [];

View file

@ -104,6 +104,22 @@ class Comment extends Post
return $this->getTarget()->canBeViewedBy($user);
}
function isFromPostAuthor($target = NULL)
{
if(!$target)
$target = $this->getTarget();
$target_owner = $target->getOwner();
$comment_owner = $this->getOwner();
if($target_owner->getRealId() === $comment_owner->getRealId())
return true;
# TODO: make it work with signer_id
return false;
}
function toNotifApiStruct()
{
$res = (object)[];
@ -124,4 +140,31 @@ class Comment extends Post
return $user->getId() == $this->getOwner(false)->getId();
}
function getTargetURL(): string
{
$target = $this->getTarget();
$target_name = 'wall';
if(!$target) {
return '/404';
}
switch(get_class($target)) {
case 'openvk\Web\Models\Entities\Note':
$target_name = 'note';
break;
case 'openvk\Web\Models\Entities\Photo':
$target_name = 'photo';
break;
case 'openvk\Web\Models\Entities\Video':
$target_name = 'video';
break;
case 'openvk\Web\Models\Entities\Topic':
$target_name = 'topic';
break;
}
return $target_name . $target->getPrettyId();
}
}

View file

@ -66,7 +66,7 @@ class Message extends RowModel
$dateTime = new DateTime($this->getRecord()->created);
if($dateTime->format("%d.%m.%y") == ovk_strftime_safe("%d.%m.%y", time())) {
return $dateTime->format("%T %p");
return $dateTime->format("%T");
} else {
return $dateTime->format("%d.%m.%y");
}

View file

@ -114,7 +114,7 @@ class Photo extends Media
return true;
}
function crop(real $left, real $top, real $width, real $height): void
function crop(float $left, float $top, float $width, float $height): void
{
if(isset($this->changes["hash"]))
$hash = $this->changes["hash"];

View file

@ -42,6 +42,21 @@ class Playlist extends MediaCollection
return $this->getRecord()->length;
}
function fetchClassic(int $offset = 0, ?int $limit = NULL): \Traversable
{
$related = $this->getRecord()->related("$this->relTableName.collection")
->limit($limit ?? OPENVK_DEFAULT_PER_PAGE, $offset)
->order("index ASC");
foreach($related as $rel) {
$media = $rel->ref($this->entityTableName, "media");
if(!$media)
continue;
yield new $this->entityClassName($media);
}
}
function getAudios(int $offset = 0, ?int $limit = NULL, ?int $shuffleSeed = NULL): \Traversable
{
if(!$shuffleSeed) {
@ -162,6 +177,7 @@ class Playlist extends MediaCollection
"bookmarked" => $this->isBookmarkedBy($user),
"listens" => $this->getListens(),
"cover_url" => $this->getCoverURL(),
"searchable" => !$this->isUnlisted(),
];
}
@ -200,6 +216,11 @@ class Playlist extends MediaCollection
return $this->getRecord()->cover_photo_id;
}
function getCoverPhoto(): ?Photo
{
return (new Photos)->get((int) $this->getRecord()->cover_photo_id);
}
function canBeModifiedBy(User $user): bool
{
if(!$user)
@ -253,4 +274,9 @@ class Playlist extends MediaCollection
return implode("", $props);
}
function isUnlisted(): bool
{
return (bool)$this->getRecord()->unlisted;
}
}

View file

@ -134,6 +134,10 @@ class Post extends Postable
return 'iphone';
break;
case 'windows_phone':
return 'wphone';
break;
case 'vika_touch': // кика хохотач ахахахаххахахахахах
case 'vk4me':
return 'mobile';
@ -176,6 +180,31 @@ class Post extends Postable
];
}
function getPostSourceInfo(): array
{
$post_source = ["type" => "vk"];
if($this->getPlatform(true) !== NULL) {
$post_source = [
"type" => "api",
"platform" => $this->getPlatform(true)
];
}
if($this->isUpdateAvatarMessage())
$post_source['data'] = 'profile_photo';
return $post_source;
}
function getVkApiType(): string
{
$type = 'post';
if($this->getSuggestionType() != 0)
$type = 'suggest';
return $type;
}
function pin(): void
{
DB::i()

View file

@ -80,7 +80,7 @@ class Report extends RowModel
function getAuthor(): RowModel
{
return (new Posts)->get($this->getContentId())->getOwner();
return $this->getContentObject()->getOwner();
}
function getReportAuthor(): User

View file

@ -123,7 +123,7 @@ trait TRichText
$text = preg_replace_callback("%([\n\r\s]|^)(\#([\p{L}_0-9][\p{L}_0-9\(\)\-\']+[\p{L}_0-9\(\)]|[\p{L}_0-9]{1,2}))%Xu", function($m) {
$slug = rawurlencode($m[3]);
return "$m[1]<a href='/feed/hashtag/$slug'>$m[2]</a>";
return "$m[1]<a href='/search?section=posts&q=%23$slug'>$m[2]</a>";
}, $text);
$text = $this->formatEmojis($text);

View file

@ -39,4 +39,25 @@ trait TSubscribable
$sub->delete();
return false;
}
function changeFlags(User $user, int $flags, bool $reverse): bool
{
$ctx = DatabaseConnection::i()->getContext();
$data = [
"follower" => $reverse ? $this->getId() : $user->getId(),
"model" => static::class,
"target" => $reverse ? $user->getId() : $this->getId(),
];
$sub = $ctx->table("subscriptions")->where($data);
bdump($data);
if (!$sub)
return false;
$sub->update([
'flags' => $flags
]);
return true;
}
}

View file

@ -592,6 +592,16 @@ class User extends RowModel
return $this->_abstractRelationCount("get-followers");
}
function getRequests(int $page = 1, int $limit = 6): \Traversable
{
return $this->_abstractRelationGenerator("get-requests", $page, $limit);
}
function getRequestsCount(): int
{
return $this->_abstractRelationCount("get-requests");
}
function getSubscriptions(int $page = 1, int $limit = 6): \Traversable
{
return $this->_abstractRelationGenerator("get-subscriptions-user", $page, $limit);
@ -1214,6 +1224,11 @@ class User extends RowModel
return (bool) $this->getRecord()->activated;
}
function isDead(): bool
{
return $this->onlineStatus() == 2;
}
function getUnbanTime(): ?string
{
$ban = (new Bans)->get((int) $this->getRecord()->block_reason);
@ -1306,17 +1321,22 @@ class User extends RowModel
return true;
}
function isClosed()
function isClosed(): bool
{
return (bool) $this->getProfileType();
}
function isHideFromGlobalFeedEnabled(): bool
{
return $this->isClosed();
}
function getRealId()
{
return $this->getId();
}
function toVkApiStruct(?User $user = NULL): object
function toVkApiStruct(?User $user = NULL, string $fields = ''): object
{
$res = (object) [];
@ -1328,12 +1348,21 @@ class User extends RowModel
$res->photo_100 = $this->getAvatarURL("tiny");
$res->photo_200 = $this->getAvatarURL("normal");
$res->photo_id = !is_null($this->getAvatarPhoto()) ? $this->getAvatarPhoto()->getPrettyId() : NULL;
# TODO: Perenesti syuda vsyo ostalnoyie
$res->is_closed = $this->isClosed();
if(!is_null($user)) {
if(!is_null($user))
$res->can_access_closed = (bool)$this->canBeViewedBy($user);
if(!is_array($fields))
$fields = explode(',', $fields);
foreach($fields as $field) {
switch($field) {
case 'is_dead':
$res->is_dead = $user->isDead();
break;
}
}
return $res;

View file

@ -181,8 +181,8 @@ class Video extends Media
{
if(preg_match(file_get_contents(__DIR__ . "/../VideoDrivers/regex/youtube.txt"), $link, $matches)) {
$pointer = "YouTube:$matches[1]";
} else if(preg_match(file_get_contents(__DIR__ . "/../VideoDrivers/regex/vimeo.txt"), $link, $matches)) {
$pointer = "Vimeo:$matches[1]";
/*} else if(preg_match(file_get_contents(__DIR__ . "/../VideoDrivers/regex/vimeo.txt"), $link, $matches)) {
$pointer = "Vimeo:$matches[1]";*/
} else {
throw new ISE("Invalid link");
}

View file

@ -23,4 +23,13 @@ class APITokens extends Repository
return $token;
}
function getStaleByUser(int $userId, string $platform, bool $withRevoked = false): ?APIToken
{
return $this->toEntity($this->table->where([
'user' => $userId,
'platform' => $platform,
'deleted' => $withRevoked,
])->fetch());
}
}

View file

@ -67,11 +67,21 @@ class Applications
return sizeof($this->appRels->where("user", $user->getId()));
}
function find(string $query, array $pars = [], string $sort = "id"): Util\EntityStream
function find(string $query = "", array $params = [], array $order = ['type' => 'id', 'invert' => false]): Util\EntityStream
{
$query = "%$query%";
$result = $this->apps->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("enabled", 1);
$order_str = 'id';
return new Util\EntityStream("Application", $result->order("$sort"));
switch($order['type']) {
case 'id':
$order_str = 'id ' . ($order['invert'] ? 'ASC' : 'DESC');
break;
}
if($order_str)
$result->order($order_str);
return new Util\EntityStream("Application", $result);
}
}

View file

@ -208,7 +208,7 @@ class Audios
$search = $this->audios->where([
"unlisted" => 0,
"deleted" => 0,
])->where("MATCH ($columns) AGAINST (? WITH QUERY EXPANSION)", $query)->order($order);
])->where("MATCH ($columns) AGAINST (? IN BOOLEAN MODE)", "%$query%")->order($order);
if($withLyrics)
$search = $search->where("lyrics IS NOT NULL");
@ -219,6 +219,7 @@ class Audios
function searchPlaylists(string $query): EntityStream
{
$search = $this->playlists->where([
"unlisted" => 0,
"deleted" => 0,
])->where("MATCH (`name`, `description`) AGAINST (? IN BOOLEAN MODE)", $query);
@ -243,32 +244,24 @@ class Audios
])->fetch());
}
function find(string $query, array $pars = [], string $sort = "id DESC", int $page = 1, ?int $perPage = NULL): \Traversable
function find(string $query, array $params = [], array $order = ['type' => 'id', 'invert' => false], int $page = 1, ?int $perPage = NULL): \Traversable
{
$query = "%$query%";
$result = $this->audios->where([
"unlisted" => 0,
"deleted" => 0,
]);
$order_str = (in_array($order['type'], ['id', 'length', 'listens']) ? $order['type'] : 'id') . ' ' . ($order['invert'] ? 'ASC' : 'DESC');;
$notNullParams = [];
foreach($pars as $paramName => $paramValue)
if($paramName != "before" && $paramName != "after" && $paramName != "only_performers")
$paramValue != NULL ? $notNullParams+=["$paramName" => "%$paramValue%"] : NULL;
else
$paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL;
$nnparamsCount = sizeof($notNullParams);
if($notNullParams["only_performers"] == "1") {
if($params["only_performers"] == "1") {
$result->where("performer LIKE ?", $query);
} else {
$result->where("name LIKE ? OR performer LIKE ?", $query, $query);
}
if($nnparamsCount > 0) {
foreach($notNullParams as $paramName => $paramValue) {
foreach($params as $paramName => $paramValue) {
if(is_null($paramValue) || $paramValue == '') continue;
switch($paramName) {
case "before":
$result->where("created < ?", $paramValue);
@ -279,17 +272,44 @@ class Audios
case "with_lyrics":
$result->where("lyrics IS NOT NULL");
break;
}
case 'genre':
if($paramValue == 'any') break;
$result->where("genre", $paramValue);
break;
}
}
return new Util\EntityStream("Audio", $result->order($sort));
if($order_str)
$result->order($order_str);
return new Util\EntityStream("Audio", $result);
}
function findPlaylists(string $query, int $page = 1, ?int $perPage = NULL): \Traversable
function findPlaylists(string $query, array $params = [], array $order = ['type' => 'id', 'invert' => false]): \Traversable
{
$query = "%$query%";
$result = $this->playlists->where("name LIKE ?", $query);
$result = $this->playlists->where([
"deleted" => 0,
])->where("CONCAT_WS(' ', name, description) LIKE ?", $query);
$order_str = (in_array($order['type'], ['id', 'length', 'listens']) ? $order['type'] : 'id') . ' ' . ($order['invert'] ? 'ASC' : 'DESC');
if(is_null($params['from_me']) || empty($params['from_me']))
$result->where(["unlisted" => 0]);
foreach($params as $paramName => $paramValue) {
if(is_null($paramValue) || $paramValue == '') continue;
switch($paramName) {
# БУДЬ МАКСИМАЛЬНО АККУРАТЕН С ДАННЫМ ПАРАМЕТРОМ
case "from_me":
$result->where("owner", $paramValue);
break;
}
}
if($order_str)
$result->order($order_str);
return new Util\EntityStream("Playlist", $result);
}

View file

@ -43,17 +43,29 @@ class Clubs
return $this->toClub($this->clubs->get($id));
}
function find(string $query, array $pars = [], string $sort = "id DESC", int $page = 1, ?int $perPage = NULL): \Traversable
function find(string $query, array $params = [], array $order = ['type' => 'id', 'invert' => false], int $page = 1, ?int $perPage = NULL): \Traversable
{
$query = "%$query%";
$result = $this->clubs->where("name LIKE ? OR about LIKE ?", $query, $query);
$result = $this->clubs;
$order_str = 'id';
return new Util\EntityStream("Club", $result->order($sort));
switch($order['type']) {
case 'id':
$order_str = 'id ' . ($order['invert'] ? 'ASC' : 'DESC');
break;
}
$result = $result->where("name LIKE ? OR about LIKE ?", $query, $query);
if($order_str)
$result->order($order_str);
return new Util\EntityStream("Club", $result);
}
function getCount(): int
{
return sizeof(clone $this->clubs);
return (clone $this->clubs)->count('*');
}
function getPopularClubs(): \Traversable

View file

@ -60,23 +60,18 @@ class Comments
]));
}
function find(string $query = "", array $pars = [], string $sort = "id"): Util\EntityStream
function find(string $query, array $params = [], array $order = ['type' => 'id', 'invert' => false]): Util\EntityStream
{
$query = "%$query%";
$result = $this->comments->where("content LIKE ?", "%$query%")->where("deleted", 0);
$order_str = 'id';
$notNullParams = [];
switch($order['type']) {
case 'id':
$order_str = 'created ' . ($order['invert'] ? 'ASC' : 'DESC');
break;
}
foreach($pars as $paramName => $paramValue)
if($paramName != "before" && $paramName != "after")
$paramValue != NULL ? $notNullParams+=["$paramName" => "%$paramValue%"] : NULL;
else
$paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL;
$result = $this->comments->where("content LIKE ?", $query)->where("deleted", 0);
$nnparamsCount = sizeof($notNullParams);
if($nnparamsCount > 0) {
foreach($notNullParams as $paramName => $paramValue) {
foreach($params as $paramName => $paramValue) {
switch($paramName) {
case "before":
$result->where("created < ?", $paramValue);
@ -86,8 +81,10 @@ class Comments
break;
}
}
}
return new Util\EntityStream("Comment", $result->order("$sort"));
if($order_str)
$result->order($order_str);
return new Util\EntityStream("Comment", $result);
}
}

View file

@ -30,7 +30,7 @@ class Notifications
return (new $repoClassName)->get($id);
}
private function getQuery(User $user, bool $count = false, int $offset, bool $archived = false, int $page = 1, ?int $perPage = NULL): string
private function getQuery(User $user, bool $count, int $offset, bool $archived = false, int $page = 1, ?int $perPage = NULL): string
{
$query = "SELECT " . ($count ? "COUNT(*) AS cnt" : "*") . " FROM notifications WHERE recipientType=0 ";
$query .= "AND timestamp " . ($archived ? "<" : ">") . "$offset AND recipientId=" . $user->getId();

View file

@ -154,23 +154,21 @@ class Posts
}
function find(string $query = "", array $pars = [], string $sort = "id"): Util\EntityStream
function find(string $query = "", array $params = [], array $order = ['type' => 'id', 'invert' => false]): Util\EntityStream
{
$query = "%$query%";
$notNullParams = [];
foreach($pars as $paramName => $paramValue)
if($paramName != "before" && $paramName != "after")
$paramValue != NULL ? $notNullParams+=["$paramName" => "%$paramValue%"] : NULL;
else
$paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL;
$result = $this->posts->where("content LIKE ?", $query)->where("deleted", 0)->where("suggested", 0);
$nnparamsCount = sizeof($notNullParams);
$order_str = 'id';
switch($order['type']) {
case 'id':
$order_str = 'created ' . ($order['invert'] ? 'ASC' : 'DESC');
break;
}
foreach($params as $paramName => $paramValue) {
if(is_null($paramValue) || $paramValue == '') continue;
if($nnparamsCount > 0) {
foreach($notNullParams as $paramName => $paramValue) {
switch($paramName) {
case "before":
$result->where("created < ?", $paramValue);
@ -178,12 +176,23 @@ class Posts
case "after":
$result->where("created > ?", $paramValue);
break;
}
/*case 'die_in_agony':
$result->where("nsfw", 1);
break;
case 'ads':
$result->where("ad", 1);
break;*/
# БУДЬ МАКСИМАЛЬНО АККУРАТЕН С ДАННЫМ ПАРАМЕТРОМ
case 'from_me':
$result->where("owner", $paramValue);
break;
}
}
if($order_str)
$result->order($order_str);
return new Util\EntityStream("Post", $result->order("$sort"));
return new Util\EntityStream("Post", $result);
}
function getPostCountOnUserWall(int $user): int
@ -252,6 +261,6 @@ class Posts
function getCount(): int
{
return sizeof(clone $this->posts);
return (clone $this->posts)->count('*');
}
}

View file

@ -44,77 +44,62 @@ class Users
return $alias->getUser();
}
function getByChandlerUser(?ChandlerUser $user): ?User
function getByChandlerUserId(string $cid): ?User
{
return $user ? $this->toUser($this->users->where("user", $user->getId())->fetch()) : NULL;
return $this->toUser($this->users->where("user", $cid)->fetch());
}
function find(string $query, array $pars = [], string $sort = "id DESC"): Util\EntityStream
function getByChandlerUser(?ChandlerUser $user): ?User
{
return $user ? $this->getByChandlerUserId($user->getId()) : NULL;
}
function find(string $query, array $params = [], array $order = ['type' => 'id', 'invert' => false]): Util\EntityStream
{
$query = "%$query%";
$result = $this->users->where("CONCAT_WS(' ', first_name, last_name, pseudo, shortcode) LIKE ?", $query)->where("deleted", 0);
$order_str = 'id';
$notNullParams = [];
$nnparamsCount = 0;
switch($order['type']) {
case 'id':
case 'reg_date':
$order_str = 'id ' . ($order['invert'] ? 'ASC' : 'DESC');
break;
case 'rating':
$order_str = 'rating DESC';
break;
}
foreach($pars as $paramName => $paramValue)
if($paramName != "before" && $paramName != "after" && $paramName != "gender" && $paramName != "maritalstatus" && $paramName != "politViews" && $paramName != "doNotSearchMe")
$paramValue != NULL ? $notNullParams += ["$paramName" => "%$paramValue%"] : NULL;
else
$paramValue != NULL ? $notNullParams += ["$paramName" => "$paramValue"] : NULL;
foreach($params as $paramName => $paramValue) {
if(is_null($paramValue) || $paramValue == '') continue;
$nnparamsCount = sizeof($notNullParams);
if($nnparamsCount > 0) {
foreach($notNullParams as $paramName => $paramValue) {
switch($paramName) {
case "hometown":
$result->where("hometown LIKE ?", $paramValue);
$result->where("hometown LIKE ?", "%$paramValue%");
break;
case "city":
$result->where("city LIKE ?", $paramValue);
$result->where("city LIKE ?", "%$paramValue%");
break;
case "maritalstatus":
case "marital_status":
$result->where("marital_status ?", $paramValue);
break;
case "status":
$result->where("status LIKE ?", $paramValue);
break;
case "politViews":
case "polit_views":
$result->where("polit_views ?", $paramValue);
break;
case "email":
$result->where("email_contact LIKE ?", $paramValue);
break;
case "telegram":
$result->where("telegram LIKE ?", $paramValue);
break;
case "site":
$result->where("telegram LIKE ?", $paramValue);
break;
case "address":
$result->where("address LIKE ?", $paramValue);
break;
case "is_online":
$result->where("online >= ?", time() - 900);
break;
case "interests":
$result->where("interests LIKE ?", $paramValue);
break;
case "fav_mus":
$result->where("fav_music LIKE ?", $paramValue);
$result->where("fav_music LIKE ?", "%$paramValue%");
break;
case "fav_films":
$result->where("fav_films LIKE ?", $paramValue);
$result->where("fav_films LIKE ?", "%$paramValue%");
break;
case "fav_shows":
$result->where("fav_shows LIKE ?", $paramValue);
$result->where("fav_shows LIKE ?", "%$paramValue%");
break;
case "fav_books":
$result->where("fav_books LIKE ?", $paramValue);
break;
case "fav_quote":
$result->where("fav_quote LIKE ?", $paramValue);
$result->where("fav_books LIKE ?", "%$paramValue%");
break;
case "before":
$result->where("UNIX_TIMESTAMP(since) < ?", $paramValue);
@ -123,28 +108,30 @@ class Users
$result->where("UNIX_TIMESTAMP(since) > ?", $paramValue);
break;
case "gender":
$result->where("sex ?", $paramValue);
if((int) $paramValue == 3) break;
$result->where("sex ?", (int) $paramValue);
break;
case "doNotSearchMe":
$result->where("id !=", $paramValue);
case "ignore_id":
$result->where("id != ?", $paramValue);
break;
case "doNotSearchPrivate":
case "ignore_private":
$result->where("profile_type", 0);
break;
}
}
}
if($order_str)
$result->order($order_str);
return new Util\EntityStream("User", $result->order($sort));
return new Util\EntityStream("User", $result);
}
function getStatistics(): object
{
return (object) [
"all" => sizeof(clone $this->users),
"active" => sizeof((clone $this->users)->where("online > 0")),
"online" => sizeof((clone $this->users)->where("online >= ?", time() - 900)),
"all" => (clone $this->users)->count('*'),
"active" => (clone $this->users)->where("online > 0")->count('*'),
"online" => (clone $this->users)->where("online >= ?", time() - 900)->count('*'),
];
}

View file

@ -46,23 +46,19 @@ class Videos
return sizeof($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0]));
}
function find(string $query = "", array $pars = [], string $sort = "id"): Util\EntityStream
function find(string $query = "", array $params = [], array $order = ['type' => 'id', 'invert' => false]): Util\EntityStream
{
$query = "%$query%";
$notNullParams = [];
foreach($pars as $paramName => $paramValue)
if($paramName != "before" && $paramName != "after")
$paramValue != NULL ? $notNullParams+=["$paramName" => "%$paramValue%"] : NULL;
else
$paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL;
$result = $this->videos->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("deleted", 0);
$nnparamsCount = sizeof($notNullParams);
$order_str = 'id';
if($nnparamsCount > 0) {
foreach($notNullParams as $paramName => $paramValue) {
switch($order['type']) {
case 'id':
$order_str = 'id ' . ($order['invert'] ? 'ASC' : 'DESC');
break;
}
foreach($params as $paramName => $paramValue) {
switch($paramName) {
case "before":
$result->where("created < ?", $paramValue);
@ -70,12 +66,17 @@ class Videos
case "after":
$result->where("created > ?", $paramValue);
break;
}
case 'only_youtube':
if((int) $paramValue != 1) break;
$result->where("link != ?", 'NULL');
break;
}
}
if($order_str)
$result->order($order_str);
return new Util\EntityStream("Video", $result->order("$sort"));
return new Util\EntityStream("Video", $result);
}
function getLastVideo(User $user)

View file

@ -1,5 +1,5 @@
(SELECT DISTINCT(follower) AS __id FROM
(SELECT follower FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0
(SELECT follower, flags FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0
LEFT JOIN
(SELECT target FROM subscriptions WHERE follower=? AND model="openvk\\Web\\Models\\Entities\\User") u1
ON u0.follower = u1.target WHERE u1.target IS NULL) u2

View file

@ -0,0 +1,6 @@
(SELECT DISTINCT(follower) AS __id FROM
(SELECT follower FROM subscriptions WHERE target=? AND flags=0 AND model="openvk\\Web\\Models\\Entities\\User") u0
LEFT JOIN
(SELECT target FROM subscriptions WHERE follower=? AND flags=0 AND model="openvk\\Web\\Models\\Entities\\User") u1
ON u0.follower = u1.target WHERE u1.target IS NULL) u2
INNER JOIN profiles ON profiles.id = u2.__id

View file

@ -145,6 +145,6 @@ final class AboutPresenter extends OpenVKPresenter
function renderDev(): void
{
$this->redirect("https://docs.openvk.uk/");
$this->redirect("https://docs.ovk.to/");
}
}

View file

@ -49,6 +49,13 @@ final class AdminPresenter extends OpenVKPresenter
$this->flash("warn", tr("admin_commerce_disabled"), tr("admin_commerce_disabled_desc"));
}
private function warnIfLongpoolBroken(): void
{
bdump(is_writable(CHANDLER_ROOT . '/tmp/events.bin'));
if(file_exists(CHANDLER_ROOT . '/tmp/events.bin') == false || is_writable(CHANDLER_ROOT . '/tmp/events.bin') == false)
$this->flash("warn", tr("admin_longpool_broken"), tr("admin_longpool_broken_desc", CHANDLER_ROOT . '/tmp/events.bin'));
}
private function searchResults(object $repo, &$count)
{
$query = $this->queryParam("q") ?? "";
@ -76,7 +83,7 @@ final class AdminPresenter extends OpenVKPresenter
function renderIndex(): void
{
$this->warnIfLongpoolBroken();
}
function renderUsers(): void
@ -154,6 +161,7 @@ final class AdminPresenter extends OpenVKPresenter
$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->setEnforce_Hiding_From_Global_Feed(empty($this->postParam("enforce_hiding_from_global_feed") ? 0 : 1));
$club->save();
break;
case "ban":
@ -681,7 +689,8 @@ final class AdminPresenter extends OpenVKPresenter
$this->template->obj_type = $obj_type;
}
$this->template->logs = (new Logs)->search($filter);
$logs = iterator_to_array((new Logs)->search($filter));
$this->template->logs = $logs;
$this->template->object_types = (new Logs)->getTypes();
}
}

View file

@ -75,7 +75,7 @@ final class AudioPresenter extends OpenVKPresenter
if (!$entity || $entity->isBanned())
$this->redirect("/playlists" . $this->user->id);
$playlists = $this->audios->getPlaylistsByClub($entity, $page, 10);
$playlists = $this->audios->getPlaylistsByClub($entity, $page, OPENVK_DEFAULT_PER_PAGE);
$playlistsCount = $this->audios->getClubPlaylistsCount($entity);
} else {
$entity = (new Users)->get($owner);
@ -85,7 +85,7 @@ final class AudioPresenter extends OpenVKPresenter
if(!$entity->getPrivacyPermission("audios.read", $this->user->identity))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
$playlists = $this->audios->getPlaylistsByUser($entity, $page, 9);
$playlists = $this->audios->getPlaylistsByUser($entity, $page, OPENVK_DEFAULT_PER_PAGE);
$playlistsCount = $this->audios->getUserPlaylistsCount($entity);
}
@ -110,7 +110,7 @@ final class AudioPresenter extends OpenVKPresenter
$this->template->mode = $mode;
$this->template->page = $page;
if(in_array($mode, ["list", "new", "popular"]) && $this->user->identity)
if(in_array($mode, ["list", "new", "popular"]) && $this->user->identity && $page < 2)
$this->template->friendsAudios = $this->user->identity->getBroadcastList("all", true);
}
@ -142,7 +142,13 @@ final class AudioPresenter extends OpenVKPresenter
$this->assertUserLoggedIn();
$group = NULL;
$playlist = NULL;
$isAjax = $this->postParam("ajax", false) == 1;
if(!is_null($this->queryParam("gid")) && !is_null($this->queryParam("playlist"))) {
$this->flashFail("err", tr("forbidden"), tr("not_enough_permissions_comment"), null, $isAjax);
}
if(!is_null($this->queryParam("gid"))) {
$gid = (int) $this->queryParam("gid");
$group = (new Clubs)->get($gid);
@ -153,6 +159,19 @@ final class AudioPresenter extends OpenVKPresenter
$this->flashFail("err", tr("forbidden"), tr("not_enough_permissions_comment"), null, $isAjax);
}
if(!is_null($this->queryParam("playlist"))) {
$playlist_id = (int)$this->queryParam("playlist");
$playlist = (new Audios)->getPlaylist($playlist_id);
if(!$playlist || $playlist->isDeleted())
$this->flashFail("err", tr("forbidden"), tr("not_enough_permissions_comment"), null, $isAjax);
if(!$playlist->canBeModifiedBy($this->user->identity))
$this->flashFail("err", tr("forbidden"), tr("not_enough_permissions_comment"), null, $isAjax);
$this->template->playlist = $playlist;
$this->template->owner = $playlist->getOwner();
}
$this->template->group = $group;
if($_SERVER["REQUEST_METHOD"] !== "POST")
@ -196,6 +215,8 @@ final class AudioPresenter extends OpenVKPresenter
$lyrics = $this->postParam("lyrics");
$genre = empty($this->postParam("genre")) ? "Other" : $this->postParam("genre");
$nsfw = ($this->postParam("explicit") ?? "off") === "on";
$is_unlisted = ($this->postParam("unlisted") ?? "off") === "on";
if(empty($performer) || empty($name) || iconv_strlen($performer . $name) > 128) # FQN of audio must not be more than 128 chars
$this->flashFail("err", tr("error"), tr("error_insufficient_info"), null, $isAjax);
@ -206,6 +227,7 @@ final class AudioPresenter extends OpenVKPresenter
$audio->setLyrics(empty($lyrics) ? NULL : $lyrics);
$audio->setGenre($genre);
$audio->setExplicit($nsfw);
$audio->setUnlisted($is_unlisted);
try {
$audio->setFile($upload);
@ -215,13 +237,18 @@ final class AudioPresenter extends OpenVKPresenter
} catch(\RuntimeException $ex) {
$this->flashFail("err", tr("error"), tr("ffmpeg_timeout"), null, $isAjax);
} catch(\BadMethodCallException $ex) {
$this->flashFail("err", tr("error"), "Загрузка аудио под Linux на данный момент не реализована. Следите за обновлениями: <a href='https://github.com/openvk/openvk/pull/512/commits'>https://github.com/openvk/openvk/pull/512/commits</a>", null, $isAjax);
$this->flashFail("err", tr("error"), "хз", null, $isAjax);
} catch(\Exception $ex) {
$this->flashFail("err", tr("error"), tr("ffmpeg_not_installed"), null, $isAjax);
}
$audio->save();
if($playlist) {
$playlist->add($audio);
} else {
$audio->add($group ?? $this->user->identity);
}
if(!$isAjax)
$this->redirect(is_null($group) ? "/audios" . $this->user->id : "/audios-" . $group->getId());
@ -233,8 +260,8 @@ final class AudioPresenter extends OpenVKPresenter
else
$redirectLink .= $this->user->id;
$pagesCount = (int)ceil((new Audios)->getCollectionSizeByEntityId(isset($group) ? $group->getRealId() : $this->user->id) / 10);
$redirectLink .= "?p=".$pagesCount;
if($playlist)
$redirectLink = "/playlist" . $playlist->getPrettyId();
$this->returnJson([
"success" => true,
@ -279,7 +306,7 @@ final class AudioPresenter extends OpenVKPresenter
function renderSearch(): void
{
$this->redirect("/search?type=audios");
$this->redirect("/search?section=audios");
}
function renderNewPlaylist(): void
@ -304,6 +331,8 @@ final class AudioPresenter extends OpenVKPresenter
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$title = $this->postParam("title");
$description = $this->postParam("description");
$is_unlisted = (int)$this->postParam('is_unlisted');
$audios = !empty($this->postParam("audios")) ? array_slice(explode(",", $this->postParam("audios")), 0, 1000) : [];
if(empty($title) || iconv_strlen($title) < 1)
@ -313,6 +342,8 @@ final class AudioPresenter extends OpenVKPresenter
$playlist->setOwner($owner);
$playlist->setName(substr($title, 0, 125));
$playlist->setDescription(substr($description, 0, 2045));
if($is_unlisted == 1)
$playlist->setUnlisted(true);
if($_FILES["cover"]["error"] === UPLOAD_ERR_OK) {
if(!str_starts_with($_FILES["cover"]["type"], "image"))
@ -427,6 +458,7 @@ final class AudioPresenter extends OpenVKPresenter
$title = $this->postParam("title");
$description = $this->postParam("description");
$is_unlisted = (int)$this->postParam('is_unlisted');
$new_audios = !empty($this->postParam("audios")) ? explode(",", rtrim($this->postParam("audios"), ",")) : [];
if(empty($title) || iconv_strlen($title) < 1)
@ -436,6 +468,7 @@ final class AudioPresenter extends OpenVKPresenter
$playlist->setDescription(ovk_proc_strtr($description, 2045));
$playlist->setEdited(time());
$playlist->resetLength();
$playlist->setUnlisted((bool)$is_unlisted);
if($_FILES["new_cover"]["error"] === UPLOAD_ERR_OK) {
if(!str_starts_with($_FILES["new_cover"]["type"], "image"))
@ -475,12 +508,15 @@ final class AudioPresenter extends OpenVKPresenter
$this->template->playlist = $playlist;
$this->template->page = $page;
$this->template->cover = $playlist->getCoverPhoto();
$this->template->cover_url = $this->template->cover ? $this->template->cover->getURL() : "/assets/packages/static/openvk/img/song.jpg";
$this->template->audios = iterator_to_array($playlist->fetch($page, 10));
$this->template->ownerId = $owner_id;
$this->template->owner = $playlist->getOwner();
$this->template->isBookmarked = $this->user->identity && $playlist->isBookmarkedBy($this->user->identity);
$this->template->isMy = $this->user->identity && $playlist->getOwner()->getId() === $this->user->id;
$this->template->canEdit = $this->user->identity && $playlist->canBeModifiedBy($this->user->identity);
$this->template->count = $playlist->size();
}
function renderAction(int $audio_id): void
@ -531,16 +567,65 @@ final class AudioPresenter extends OpenVKPresenter
break;
case "add_to_club":
$club = (new Clubs)->get((int)$this->postParam("club"));
$detailed = [];
if($audio->isWithdrawn())
$this->flashFail("err", "error", tr("invalid_audio"), null, true);
if(empty($this->postParam("clubs")))
$this->flashFail("err", "error", 'clubs not passed', null, true);
$clubs_arr = explode(',', $this->postParam("clubs"));
$count = sizeof($clubs_arr);
if($count < 1 || $count > 10) {
$this->flashFail("err", "error", tr('too_many_or_to_lack'), null, true);
}
foreach($clubs_arr as $club_id) {
$club = (new Clubs)->get((int)$club_id);
if(!$club || !$club->canBeModifiedBy($this->user->identity))
$this->flashFail("err", "error", tr("access_denied"), null, true);
continue;
if(!$audio->isInLibraryOf($club))
if(!$audio->isInLibraryOf($club)) {
$detailed[$club_id] = true;
$audio->add($club);
else
$this->flashFail("err", "error", tr("group_has_audio"), null, true);
} else {
$detailed[$club_id] = false;
continue;
}
}
$this->returnJson(["success" => true, 'detailed' => $detailed]);
break;
case "add_to_playlist":
$detailed = [];
if($audio->isWithdrawn())
$this->flashFail("err", "error", tr("invalid_audio"), null, true);
if(empty($this->postParam("playlists")))
$this->flashFail("err", "error", 'playlists not passed', null, true);
$playlists_arr = explode(',', $this->postParam("playlists"));
$count = sizeof($playlists_arr);
if($count < 1 || $count > 10) {
$this->flashFail("err", "error", tr('too_many_or_to_lack'), null, true);
}
foreach($playlists_arr as $playlist_id) {
$pid = explode('_', $playlist_id);
$playlist = (new Audios)->getPlaylistByOwnerAndVID((int)$pid[0], (int)$pid[1]);
if(!$playlist || !$playlist->canBeModifiedBy($this->user->identity))
continue;
if(!$playlist->hasAudio($audio)) {
$playlist->add($audio);
$detailed[$playlist_id] = true;
} else {
$detailed[$playlist_id] = false;
continue;
}
}
$this->returnJson(["success" => true, 'detailed' => $detailed]);
break;
case "delete":
if($audio->canBeModifiedBy($this->user->identity))
@ -653,6 +738,28 @@ final class AudioPresenter extends OpenVKPresenter
$audios = $stream->page($page, 10);
$audiosCount = $stream->size();
break;
case "classic_search_context":
$data = json_decode($this->postParam("context_entity"), true);
$params = [];
$order = [
"type" => $data['order'] ?? 'id',
"invert" => (int)$data['invert'] == 1 ? true : false
];
if($data['genre'] && $data['genre'] != 'any')
$params['genre'] = $data['genre'];
if($data['only_performers'] && (int)$data['only_performers'] == 1)
$params['only_performers'] = '1';
if($data['with_lyrics'] && (int)$data['with_lyrics'] == 1)
$params['with_lyrics'] = '1';
$stream = $this->audios->find($data['query'], $params, $order);
$audios = $stream->page($page, 10);
$audiosCount = $stream->size();
break;
}
$pagesCount = ceil($audiosCount / $perPage);

View file

@ -233,7 +233,10 @@ final class GroupPresenter extends OpenVKPresenter
$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_upload_audios(empty($this->postParam("upload_audios")) ? 0 : 1);
$club->setHide_From_Global_Feed(empty($this->postParam("hide_from_global_feed")) ? 0 : 1);
if (!$club->isHidingFromGlobalFeedEnforced()) {
$club->setHide_From_Global_Feed(empty($this->postParam("hide_from_global_feed") ? 0 : 1));
}
$website = $this->postParam("website") ?? "";
if(empty($website))
@ -279,48 +282,99 @@ final class GroupPresenter extends OpenVKPresenter
function renderSetAvatar(int $id)
{
$photo = new Photo;
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
$club = $this->clubs->get($id);
if ($club->isBanned()) $this->flashFail("err", tr("error"), tr("forbidden"));
if($_SERVER["REQUEST_METHOD"] === "POST" && $_FILES["ava"]["error"] === UPLOAD_ERR_OK) {
if(!$club || $club->isBanned() || !$club->canBeModifiedBy($this->user->identity))
$this->flashFail("err", tr("error"), tr("forbidden"), NULL, true);
if($_SERVER["REQUEST_METHOD"] === "POST" && $_FILES["blob"]["error"] === UPLOAD_ERR_OK) {
try {
$photo = new Photo;
$anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"];
if($anon && $this->user->id === $club->getOwner()->getId())
$anon = $club->isOwnerHidden();
else if($anon)
$anon = $club->getManager($this->user->identity)->isHidden();
$photo->setOwner($this->user->id);
$photo->setDescription("Club image");
$photo->setFile($_FILES["ava"]);
$photo->setFile($_FILES["blob"]);
$photo->setCreated(time());
$photo->setAnonymous($anon);
$photo->save();
(new Albums)->getClubAvatarAlbum($club)->addPhoto($photo);
$flags = 0;
$flags |= 0b00010000;
$flags |= 0b10000000;
if($this->postParam("on_wall") == 1) {
$post = new Post;
$post->setOwner($this->user->id);
$post->setWall($club->getId() * -1);
$post->setCreated(time());
$post->setContent("");
$flags = 0;
$flags |= 0b00010000;
$flags |= 0b10000000;
$post->setFlags($flags);
$post->save();
$post->attach($photo);
} catch(ISE $ex) {
$name = $album->getName();
$this->flashFail("err", tr("error"), tr("error_when_uploading_photo"));
$post->attach($photo);
}
} catch(\Throwable $ex) {
$this->flashFail("err", tr("error"), tr("error_when_uploading_photo"), NULL, true);
}
$this->returnJson([
"success" => true,
"new_photo" => $photo->getPrettyId(),
"url" => $photo->getURL(),
"id" => $photo->getPrettyId()
]);
} else {
return " ";
}
}
function renderDeleteAvatar(int $id) {
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
$club = $this->clubs->get($id);
if(!$club || $club->isBanned() || !$club->canBeModifiedBy($this->user->identity))
$this->flashFail("err", tr("error"), tr("forbidden"), NULL, true);
$avatar = $club->getAvatarPhoto();
if(!$avatar)
$this->flashFail("succ", tr("error"), "no avatar bro", NULL, true);
$avatar->isolate();
$newAvatar = $club->getAvatarPhoto();
if(!$newAvatar)
$this->returnJson([
"success" => true,
"has_new_photo" => false,
"new_photo" => NULL,
"url" => "/assets/packages/static/openvk/img/camera_200.png",
]);
else
$this->returnJson([
"success" => true,
"has_new_photo" => true,
"new_photo" => $newAvatar->getPrettyId(),
"url" => $newAvatar->getURL(),
]);
}
function renderEditBackdrop(int $id): void
{
$this->assertUserLoggedIn();

View file

@ -274,7 +274,7 @@ abstract class OpenVKPresenter extends SimplePresenter
setlocale(LC_TIME, ...(explode(";", tr("__locale"))));
if (!OPENVK_ROOT_CONF["openvk"]["preferences"]["maintenanceMode"]["all"]) {
if (OPENVK_ROOT_CONF["openvk"]["preferences"]["maintenanceMode"][$this->presenterName]) {
if ($this->presenterName && OPENVK_ROOT_CONF["openvk"]["preferences"]["maintenanceMode"][$this->presenterName]) {
$this->pass("openvk!Maintenance->section", $this->presenterName);
}
} else {
@ -307,7 +307,7 @@ abstract class OpenVKPresenter extends SimplePresenter
$theme = Themepacks::i()[Session::i()->get("_sessionTheme", "ovk")];
} else if($this->requestParam("themePreview")) {
$theme = Themepacks::i()[$this->requestParam("themePreview")];
} else if($this->user->identity !== NULL && $this->user->identity->getTheme()) {
} else if($this->user !== NULL && $this->user->identity !== NULL && $this->user->identity->getTheme()) {
$theme = $this->user->identity->getTheme();
}

View file

@ -1,7 +1,7 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{User, Club};
use openvk\Web\Models\Repositories\{Users, Clubs, Posts, Comments, Videos, Applications, Notes, Audios};
use openvk\Web\Models\Repositories\{Users, Clubs, Posts, Videos, Applications, Audios};
use Chandler\Database\DatabaseConnection;
final class SearchPresenter extends OpenVKPresenter
@ -9,21 +9,17 @@ final class SearchPresenter extends OpenVKPresenter
private $users;
private $clubs;
private $posts;
private $comments;
private $videos;
private $apps;
private $notes;
private $audios;
function __construct(Users $users, Clubs $clubs)
function __construct()
{
$this->users = $users;
$this->clubs = $clubs;
$this->users = new Users;
$this->clubs = new Clubs;
$this->posts = new Posts;
$this->comments = new Comments;
$this->videos = new Videos;
$this->apps = new Applications;
$this->notes = new Notes;
$this->audios = new Audios;
parent::__construct();
@ -33,85 +29,101 @@ final class SearchPresenter extends OpenVKPresenter
{
$this->assertUserLoggedIn();
$query = $this->queryParam("query") ?? "";
$type = $this->queryParam("type") ?? "users";
$sorter = $this->queryParam("sort") ?? "id";
$invert = $this->queryParam("invert") == 1 ? "ASC" : "DESC";
$query = $this->queryParam("q") ?? "";
$section = $this->queryParam("section") ?? "users";
$order = $this->queryParam("order") ?? "id";
$invert = (int) ($this->queryParam("invert") ?? 0) == 1;
$page = (int) ($this->queryParam("p") ?? 1);
# https://youtu.be/pSAWM5YuXx8
# https://youtu.be/FfNZRhIn2Vk
$repos = [
"groups" => "clubs",
"users" => "users",
"posts" => "posts",
"comments" => "comments",
"videos" => "videos",
"audios" => "audios",
"apps" => "apps",
"notes" => "notes"
"audios_playlists" => "audios"
];
$parameters = [
"ignore_private" => true,
];
switch($sorter) {
foreach($_REQUEST as $param_name => $param_value) {
if(is_null($param_value)) continue;
switch($param_name) {
default:
case "id":
$sort = "id " . $invert;
$parameters[$param_name] = $param_value;
break;
case "name":
$sort = "first_name " . $invert;
break;
case "rating":
$sort = "rating " . $invert;
break;
case "length":
if($type != "audios") break;
case 'marital_status':
case 'polit_views':
if((int) $param_value == 0) continue;
$parameters[$param_name] = $param_value;
$sort = "length " . $invert;
break;
case "listens":
if($type != "audios") break;
case 'is_online':
if((int) $param_value == 1)
$parameters['is_online'] = 1;
$sort = "listens " . $invert;
break;
case 'only_performers':
if((int) $param_value == 1 || $param_value == 'on')
$parameters['only_performers'] = true;
break;
case 'with_lyrics':
if($param_value == 'on' || $param_value == '1')
$parameters['with_lyrics'] = true;
break;
# дай бог работал этот case
case 'from_me':
if((int) $param_value != 1) continue;
$parameters['from_me'] = $this->user->id;
break;
}
}
$repo = $repos[$section] or $this->throwError(400, "Bad Request", "Invalid search entity $section.");
$results = NULL;
switch($section) {
default:
$results = $this->{$repo}->find($query, $parameters, ['type' => $order, 'invert' => $invert]);
break;
case 'audios_playlists':
$results = $this->{$repo}->findPlaylists($query, $parameters, ['type' => $order, 'invert' => $invert]);
break;
}
$parameters = [
"type" => $this->queryParam("type"),
"city" => $this->queryParam("city") != "" ? $this->queryParam("city") : NULL,
"maritalstatus" => $this->queryParam("maritalstatus") != 0 ? $this->queryParam("maritalstatus") : NULL,
"with_photo" => $this->queryParam("with_photo"),
"status" => $this->queryParam("status") != "" ? $this->queryParam("status") : NULL,
"politViews" => $this->queryParam("politViews") != 0 ? $this->queryParam("politViews") : NULL,
"email" => $this->queryParam("email"),
"telegram" => $this->queryParam("telegram"),
"site" => $this->queryParam("site") != "" ? "https://".$this->queryParam("site") : NULL,
"address" => $this->queryParam("address"),
"is_online" => $this->queryParam("is_online") == 1 ? 1 : NULL,
"interests" => $this->queryParam("interests") != "" ? $this->queryParam("interests") : NULL,
"fav_mus" => $this->queryParam("fav_mus") != "" ? $this->queryParam("fav_mus") : NULL,
"fav_films" => $this->queryParam("fav_films") != "" ? $this->queryParam("fav_films") : NULL,
"fav_shows" => $this->queryParam("fav_shows") != "" ? $this->queryParam("fav_shows") : NULL,
"fav_books" => $this->queryParam("fav_books") != "" ? $this->queryParam("fav_books") : NULL,
"fav_quote" => $this->queryParam("fav_quote") != "" ? $this->queryParam("fav_quote") : NULL,
"hometown" => $this->queryParam("hometown") != "" ? $this->queryParam("hometown") : NULL,
"before" => $this->queryParam("datebefore") != "" ? strtotime($this->queryParam("datebefore")) : NULL,
"after" => $this->queryParam("dateafter") != "" ? strtotime($this->queryParam("dateafter")) : NULL,
"gender" => $this->queryParam("gender") != "" && $this->queryParam("gender") != 2 ? $this->queryParam("gender") : NULL,
"doNotSearchPrivate" => true,
"only_performers" => $this->queryParam("only_performers") == "on" ? "1" : NULL,
"with_lyrics" => $this->queryParam("with_lyrics") == "on" ? true : NULL,
];
$repo = $repos[$type] or $this->throwError(400, "Bad Request", "Invalid search entity $type.");
$results = $this->{$repo}->find($query, $parameters, $sort);
$iterator = $results->page($page, 14);
$iterator = $results->page($page, OPENVK_DEFAULT_PER_PAGE);
$count = $results->size();
$this->template->iterator = iterator_to_array($iterator);
$this->template->order = $order;
$this->template->invert = $invert;
$this->template->data = $this->template->iterator = iterator_to_array($iterator);
$this->template->count = $count;
$this->template->type = $type;
$this->template->section = $section;
$this->template->page = $page;
$this->template->perPage = 14;
$this->template->perPage = OPENVK_DEFAULT_PER_PAGE;
$this->template->query = $query;
$this->template->atSearch = true;
$this->template->paginatorConf = (object) [
"page" => $page,
"count" => $count,
"amount" => sizeof($this->template->data),
"perPage" => $this->template->perPage,
"atBottom" => false,
"tidy" => true,
"space" => 6,
'pageCount' => ceil($count / $this->template->perPage),
];
$this->template->extendedPaginatorConf = clone $this->template->paginatorConf;
$this->template->extendedPaginatorConf->space = 12;
}
}

View file

@ -329,13 +329,15 @@ final class UserPresenter extends OpenVKPresenter
$user = $this->users->get((int) $this->postParam("id"));
if(!$user) exit("Invalid state");
if ($this->postParam("act") == "rej")
$user->changeFlags($this->user->identity, 0b10000000, true);
else
$user->toggleSubscription($this->user->identity);
$this->redirect($user->getURL());
$this->redirect($_SERVER['HTTP_REFERER']);
}
function renderSetAvatar()
{
function renderSetAvatar() {
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
@ -346,8 +348,8 @@ final class UserPresenter extends OpenVKPresenter
$photo->setFile($_FILES["blob"]);
$photo->setCreated(time());
$photo->save();
} catch(ISE $ex) {
$this->flashFail("err", tr("error"), tr("error_upload_failed"));
} catch(\Throwable $ex) {
$this->flashFail("err", tr("error"), tr("error_upload_failed"), NULL, (int)$this->postParam("ajax", true) == 1);
}
$album = (new Albums)->getUserAvatarAlbum($this->user->identity);
@ -358,6 +360,7 @@ final class UserPresenter extends OpenVKPresenter
$flags = 0;
$flags |= 0b00010000;
if($this->postParam("on_wall") == 1) {
$post = new Post;
$post->setOwner($this->user->id);
$post->setWall($this->user->id);
@ -365,17 +368,50 @@ final class UserPresenter extends OpenVKPresenter
$post->setContent("");
$post->setFlags($flags);
$post->save();
$post->attach($photo);
if($this->postParam("ava", true) == (int)1) {
}
if((int)$this->postParam("ajax", true) == 1) {
$this->returnJson([
"success" => true,
"new_photo" => $photo->getPrettyId(),
"url" => $photo->getURL(),
"id" => $photo->getPrettyId()
]);
} else {
$this->flashFail("succ", tr("photo_saved"), tr("photo_saved_comment"));
}
}
function renderDeleteAvatar() {
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
$avatar = $this->user->identity->getAvatarPhoto();
if(!$avatar)
$this->flashFail("succ", tr("error"), "no avatar bro", NULL, true);
$avatar->isolate();
$newAvatar = $this->user->identity->getAvatarPhoto();
if(!$newAvatar)
$this->returnJson([
"success" => true,
"has_new_photo" => false,
"new_photo" => NULL,
"url" => "/assets/packages/static/openvk/img/camera_200.png",
]);
else
$this->returnJson([
"success" => true,
"has_new_photo" => true,
"new_photo" => $newAvatar->getPrettyId(),
"url" => $newAvatar->getURL(),
]);
}
function renderSettings(): void
{
$this->assertUserLoggedIn();

View file

@ -186,8 +186,12 @@ final class VKAPIPresenter extends OpenVKPresenter
function renderRoute(string $object, string $method): void
{
$callback = $this->queryParam("callback");
$authMechanism = $this->queryParam("auth_mechanism") ?? "token";
if($authMechanism === "roaming") {
if($callback)
$this->fail(-1, "User authorization failed: roaming mechanism is unavailable with jsonp.", $object, $method);
if(!$this->user->identity)
$this->fail(5, "User authorization failed: roaming mechanism is selected, but user is not logged in.", $object, $method);
else
@ -234,8 +238,14 @@ final class VKAPIPresenter extends OpenVKPresenter
}
try {
// Проверка типа параметра
$type = $parameter->getType();
if (($type && !$type->isBuiltin()) || is_null($val)) {
$params[] = $val;
} else {
settype($val, $parameter->getType()->getName());
$params[] = $val;
}
} catch (\Throwable $e) {
// Just ignore the exception, since
// some args are intended for internal use
@ -254,9 +264,15 @@ final class VKAPIPresenter extends OpenVKPresenter
"response" => $res,
]);
$size = strlen($result);
if($callback) {
$result = $callback . '(' . $result . ')';
header('Content-Type: application/javascript');
} else
header("Content-Type: application/json");
$size = strlen($result);
header("Content-Length: $size");
exit($result);
}
@ -286,17 +302,31 @@ final class VKAPIPresenter extends OpenVKPresenter
$this->fail(28, "Invalid 2FA code", "internal", "acquireToken");
}
$token = NULL;
$tokenIsStale = true;
$platform = $this->requestParam("client_name");
$acceptsStale = $this->requestParam("accepts_stale");
if($acceptsStale == "1") {
if(is_null($platform))
$this->fail(101, "accepts_stale can only be used with explicitly set client_name", "internal", "acquireToken");
$token = (new APITokens)->getStaleByUser($uId, $platform);
}
if(is_null($token)) {
$tokenIsStale = false;
$token = new APIToken;
$token->setUser($user);
$token->setPlatform($platform ?? (new WhichBrowser\Parser(getallheaders()))->toString());
$token->save();
}
$payload = json_encode([
"access_token" => $token->getFormattedToken(),
"expires_in" => 0,
"user_id" => $uId,
"is_stale" => $tokenIsStale,
]);
$size = strlen($payload);
@ -304,4 +334,42 @@ final class VKAPIPresenter extends OpenVKPresenter
header("Content-Length: $size");
exit($payload);
}
function renderOAuthLogin() {
$this->assertUserLoggedIn();
$client = $this->queryParam("client_name");
$postmsg = $this->queryParam("prefers_postMessage") ?? '0';
$stale = $this->queryParam("accepts_stale") ?? '0';
$origin = NULL;
$url = $this->queryParam("redirect_uri");
if(is_null($url) || is_null($client))
exit("<b>Error:</b> redirect_uri and client_name params are required.");
if($url != "about:blank") {
if(!filter_var($url, FILTER_VALIDATE_URL))
exit("<b>Error:</b> Invalid URL passed to redirect_uri.");
$parsedUrl = (object) parse_url($url);
if($parsedUrl->scheme != 'https' && $parsedUrl->scheme != 'http')
exit("<b>Error:</b> redirect_uri should either point to about:blank or to a web resource.");
$origin = "$parsedUrl->scheme://$parsedUrl->host";
if(!is_null($parsedUrl->port ?? NULL))
$origin .= ":$parsedUrl->port";
$url .= strpos($url, '?') === false ? '?' : '&';
} else {
$url .= "#";
if($postmsg == '1') {
exit("<b>Error:</b> prefers_postMessage can only be set if redirect_uri is not about:blank");
}
}
$this->template->clientName = $client;
$this->template->usePostMessage = $postmsg == '1';
$this->template->acceptsStale = $stale == '1';
$this->template->origin = $origin;
$this->template->redirectUri = $url;
}
}

View file

@ -18,9 +18,11 @@
{script "js/l10n.js"}
{script "js/openvk.cls.js"}
{script "js/node_modules/dashjs/dist/dash.all.min.js"}
<script src="/assets/packages/static/openvk/js/node_modules/cropperjs/dist/cropper.js" type="module"></script>
{script "js/al_music.js"}
{css "js/node_modules/tippy.js/dist/backdrop.css"}
{css "js/node_modules/cropperjs/dist/cropper.css"}
{css "js/node_modules/tippy.js/dist/border.css"}
{css "js/node_modules/tippy.js/dist/svg-arrow.css"}
{css "js/node_modules/tippy.js/themes/light.css"}
@ -85,7 +87,7 @@
<div class="layout">
<div id="xhead" class="dm"></div>
<div class="page_header{if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME} page_custom_header{/if}">
<div class="page_header{if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME} page_custom_header{/if}{if $atSearch} search_expanded search_expanded_at_all{/if}">
<a href="/" class="home_button{if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME} home_button_custom{/if}" title="{$instance_name}">{if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME}{$instance_name}{/if}</a>
<div n:if="isset($thisUser) ? (!$thisUser->isBanned() XOR !$thisUser->isActivated()) : true" class="header_navigation">
{ifset $thisUser}
@ -94,67 +96,45 @@
<a href="/logout?hash={urlencode($csrfToken)}">{_header_log_out}</a>
</div>
{else}
<div class="link dec">
<div class="link">
<a href="/">{_header_home}</a>
</div>
<div class="link dec">
<a href="/search?type=groups">{_header_groups}</a>
<div class="link">
<a href="/search?section=groups">{_header_groups}</a>
</div>
<div class="link dec">
<a href="/search">{_header_search}</a>
<div class="link">
<a href="/search?q=&section=users&order=rating">{_header_search}</a>
</div>
<div class="link dec">
<div class="link">
<a href="/invite">{_header_invite}</a>
</div>
<div class="link dec">
<div class="link">
<a href="/support">{_header_help} <b n:if="$ticketAnsweredCount > 0">({$ticketAnsweredCount})</b></a>
</div>
<div class="link dec">
<div class="link">
<a href="/logout?hash={urlencode($csrfToken)}">{_header_log_out}</a>
</div>
{var $atSearch = str_contains($_SERVER['REQUEST_URI'], "/search")}
<div id="srch" class="{if $atSearch}nodivider{else}link{/if}">
{if !$atSearch}
<form action="/search" method="get" id="searcher" style="position:relative;">
<input autocomplete="off" id="searchInput" oninput="checkSearchTips()" onfocus="expandSearch()" onblur="decreaseSearch()" class="sr" 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;" title="{_header_search} [Alt+Shift+F]" accesskey="f" />
<select onchange="checkSearchTips()" id="typer" name="type" class="whatFind" style="display:none;top: 0px;">
<option value="users">{_s_by_people}</option>
<option value="groups">{_s_by_groups}</option>
<option value="posts">{_s_by_posts}</option>
<option value="comments">{_s_by_comments}</option>
<option value="videos">{_s_by_videos}</option>
<option value="apps">{_s_by_apps}</option>
<option value="audios">{_s_by_audios}</option>
<div id="search_box" class='header_divider_stick'>
<div id="search_box_fr">
<form id='search_form' action="/search" method="get">
<div id='search_and_one_more_wrapper'>
<input n:attr="value => $_REQUEST['q'] ? $_REQUEST['q'] : NULL" autocomplete="off" type="search" maxlength="79" name="q" placeholder="{_header_search}" title="{_header_search} [Alt+Shift+F]" accesskey="f" />
<select name="section">
<option n:attr="selected => $_REQUEST['section'] == 'users'" value="users">{_s_by_people}</option>
<option n:attr="selected => $_REQUEST['section'] == 'groups'" value="groups">{_s_by_groups}</option>
<option n:attr="selected => $_REQUEST['section'] == 'posts'" value="posts">{_s_by_posts}</option>
<option n:attr="selected => $_REQUEST['section'] == 'videos'" value="videos">{_s_by_videos}</option>
<option n:attr="selected => $_REQUEST['section'] == 'apps'" value="apps">{_s_by_apps}</option>
<option n:attr="selected => $_REQUEST['section'] == 'audios'" value="audios">{_s_by_audios}</option>
<option n:attr="selected => $_REQUEST['section'] == 'audios_playlists'" value="audios_playlists">{_s_by_audios_playlists}</option>
</select>
</form>
<div class="searchTips" id="srcht" hidden>
<table style="border:none;border-spacing: 0;">
<tbody id="srchrr">
</tbody>
</table>
</div>
{else}
<form action="/search" method="get" id="searcher" style="margin-top: -1px;position:relative;">
<input id="searchInput" value="{$_GET['query'] ?? ''}" type="search" class="sr" name="query" placeholder="{_header_search}" style="height: 20px; background-color: #fff; padding-left: 6px;width: 555px;" title="{_header_search} [Alt+Shift+F]" accesskey="f" />
<select name="type" class="whatFind">
<option value="users" {if str_contains($_SERVER['REQUEST_URI'], "type=users")}selected{/if}>{_s_by_people}</option>
<option value="groups" {if str_contains($_SERVER['REQUEST_URI'], "type=groups")}selected{/if}>{_s_by_groups}</option>
<option value="posts" {if str_contains($_SERVER['REQUEST_URI'], "type=posts")}selected{/if}>{_s_by_posts}</option>
<option value="comments" {if str_contains($_SERVER['REQUEST_URI'], "type=comments")}selected{/if}>{_s_by_comments}</option>
<option value="videos" {if str_contains($_SERVER['REQUEST_URI'], "type=videos")}selected{/if}>{_s_by_videos}</option>
<option value="apps" {if str_contains($_SERVER['REQUEST_URI'], "type=apps")}selected{/if}>{_s_by_apps}</option>
<option value="audios" {if str_contains($_SERVER['REQUEST_URI'], "type=audios")}selected{/if}>{_s_by_audios}</option>
</select>
<button class="searchBtn"><span style="color:white;font-weight: 600;font-size:12px;">{_header_search}</span></button>
<button class="search_box_button">
<span>{_header_search}</span>
</button>
</form>
<script>
let els = document.querySelectorAll("div.dec")
for(const element of els) {
element.style.display = "none"
}
</script>
{/if}
</div>
<div id="searchBoxFastTips"></div>
</div>
{/if}
{else}
@ -178,9 +158,9 @@
<a href="/edit" class="link edit-button">{_edit_button}</a>
<a href="{$thisUser->getURL()}" class="link" title="{_my_page} [Alt+Shift+.]" accesskey=".">{_my_page}</a>
<a href="/friends{$thisUser->getId()}" class="link">{_my_friends}
<object type="internal/link" n:if="$thisUser->getFollowersCount() > 0">
<object type="internal/link" n:if="$thisUser->getRequestsCount() > 0">
<a href="/friends{$thisUser->getId()}?act=incoming" class="linkunderline">
(<b>{$thisUser->getFollowersCount()}</b>)
(<b>{$thisUser->getRequestsCount()}</b>)
</a>
</object>
</a>
@ -196,9 +176,9 @@
<a n:if="$thisUser->getLeftMenuItemStatus('groups')" href="/groups{$thisUser->getId()}" class="link">{_my_groups}</a>
<a n:if="$thisUser->getLeftMenuItemStatus('news')" href="/feed" class="link" title="{_my_feed} [Alt+Shift+,]" accesskey=",">{_my_feed}</a>
<a href="/notifications" class="link" title="{_my_feedback} [Alt+Shift+N]" accesskey="n">{_my_feedback}
{if $thisUser->getNotificationsCount() > 0}
<object type="internal/link" n:if="$thisUser->getNotificationsCount() > 0">
(<b>{$thisUser->getNotificationsCount()}</b>)
{/if}
</object>
</a>
<a n:if="$thisUser->getLeftMenuItemStatus('apps')" href="/apps?act=installed" class="link">{_my_apps}</a>
<a href="/settings" class="link">{_my_settings}</a>
@ -398,19 +378,13 @@
{script "js/al_mentions.js"}
{script "js/al_polls.js"}
{script "js/al_suggestions.js"}
{script "js/al_navigation.js"}
{ifset $thisUser}
{script "js/al_notifs.js"}
{script "js/al_feed.js"}
{/ifset}
{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>bsdnHydrate();</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>
@ -448,7 +422,8 @@
<script>
window.openvk = {
"audio_genres": {\openvk\Web\Models\Entities\Audio::genres}
"audio_genres": {\openvk\Web\Models\Entities\Audio::genres},
"at_search": {$atSearch ?? false},
}
</script>

View file

@ -49,7 +49,7 @@
{include description, x => $dat}
{/ifset}
</td>
<td n:ifset="actions" valign="top" class="action_links" style="width: 150px;">
<td n:ifset="actions" valign="top" class="action_links" style="min-width: 150px;">
{include actions, x => $dat}
</td>
</tr>

View file

@ -255,17 +255,6 @@
Disabled
</td>
</tr>
<tr>
<td class="e">
Fartscroll
</td>
<td class="v">
{php echo OPENVK_ROOT_CONF["openvk"]["preferences"]["bellsAndWhistles"]["fartscroll"] ? "Enabled" : "Disabled"}
</td>
<td class="v">
Disabled
</td>
</tr>
<tr>
<td class="e">
NDA Test Label
@ -282,7 +271,7 @@
Number verification
</td>
<td class="v">
{php echo OPENVK_ROOT_CONF["openvk"]["credentials"]["zadarma"]["enable"] ? "SMS (Zadarma)" : "Disabled"}
{php echo OPENVK_ROOT_CONF["openvk"]["credentials"]["smsc"]["enable"] ? "SMS" : "Disabled"}
</td>
<td class="v">
Disabled
@ -364,21 +353,21 @@
Vladimir Barinov, Konstantin Kichulkin and Daniel Myslivets
</td>
</tr>
<tr n:foreach="$themes as $theme">
<tr n:foreach="$themes as $themeEntry">
<td class="e">
{$theme->getName()}
{$themeEntry->getName()}
</td>
<td class="v">
{$theme->isEnabled() ? "Enabled" : "Installed"}
{$themeEntry->isEnabled() ? "Enabled" : "Installed"}
</td>
<td class="v">
{$theme->getVersion()}
{$themeEntry->getVersion()}
</td>
<td class="v">
{$theme->getDescription()|truncate:20}
{$themeEntry->getDescription()|truncate:20}
</td>
<td class="v">
{$theme->getAuthor()}
{$themeEntry->getAuthor()}
</td>
</tr>
</tbody>

View file

@ -58,13 +58,17 @@
</div>
<br/>
<div class="group">
<input class="toggle-large" type="checkbox" id="verify" name="verify" value="1" {if $club->isVerified()} checked {/if} />
<input class="toggle-large" type="checkbox" id="verify" name="verify" value="1" n:attr="checked => $club->isVerified()" />
<label for="verify">{_admin_verification}</label>
</div>
<div class="group">
<input class="toggle-large" type="checkbox" id="hide_from_global_feed" name="hide_from_global_feed" value="1" {if $club->isHideFromGlobalFeedEnabled()} checked {/if} />
<input class="toggle-large" type="checkbox" id="hide_from_global_feed" name="hide_from_global_feed" value="1" n:attr="checked => $club->isHideFromGlobalFeedEnabled()" />
<label for="hide_from_global_feed">{_admin_club_excludeglobalfeed}</label>
</div>
<div class="group">
<input class="toggle-large" type="checkbox" id="enforce_hiding_from_global_feed" name="enforce_hiding_from_global_feed" value="1" n:attr="checked => $club->isHidingFromGlobalFeedEnforced()" />
<label for="enforce_hiding_from_global_feed">{_admin_club_enforceexcludeglobalfeed}</label>
</div>
<hr/>
<div class="buttons-container">
<div class="buttons">

View file

@ -12,7 +12,8 @@
{block content}
<center>
<iframe id="appFrame" referrerpolicy="unsafe-url" sandbox="allow-scripts" frameBorder="0" src="{$url}" height="600" width="600"></iframe>
<iframe id="appFrame" referrerpolicy="unsafe-url" frameBorder="0" src="{$url}" height="600" width="600"
sandbox="allow-scripts allow-same-origin allow-pointer-lock allow-forms allow-downloads-without-user-activation"></iframe>
</center>
<div n:if="!is_null($news)" id="news">

View file

@ -5,7 +5,7 @@
{block header}
<a href="{$owner->getURL()}">{$owner->getCanonicalName()}</a>
»
<a href="/audios{$ownerId}">{_audios}</a>
<a href="/playlists{$ownerId}">{_playlists}</a>
»
<a href="/playlist{$playlist->getPrettyId()}">{_playlist}</a>
»
@ -30,8 +30,13 @@
</div>
<div class="moreInfo">
<textarea name="description" maxlength="2045" style="margin-top: 11px;">{$playlist->getDescription()}</textarea>
<textarea placeholder="{_description}" name="description" maxlength="2045" style="margin-top: 11px;">{$playlist->getDescription()}</textarea>
</div>
<label>
<input type='checkbox' name='is_unlisted' n:attr='checked => $playlist->isUnlisted()'>
{_playlist_hide_from_search}
</label>
</div>
</div>
@ -56,6 +61,7 @@
<form method="post" id="editPlaylistForm" data-id="{$playlist->getId()}" enctype="multipart/form-data">
<input type="hidden" name="title" maxlength="128" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="hidden" name="is_unlisted" value="0" />
<textarea style="display:none;" name="description" maxlength="2048" />
<input type="hidden" name="audios">
<input type="file" style="display:none;" name="new_cover" accept=".jpg,.png">
@ -71,6 +77,7 @@
u("#editPlaylistForm").on("submit", (e) => {
document.querySelector("#editPlaylistForm input[name='title']").value = document.querySelector(".playlistInfo input[name='title']").value
document.querySelector("#editPlaylistForm textarea[name='description']").value = document.querySelector(".playlistBlock textarea[name='description']").value
document.querySelector("#editPlaylistForm input[name='is_unlisted']").value = document.querySelector("input[name='is_unlisted']").checked ? 1 : 0
})
u("#editPlaylistForm input[name='new_cover']").on("change", (e) => {

View file

@ -59,11 +59,10 @@
<input n:if="$mode == 'popular'" type="hidden" name="bigplayer_context" data-type="popular_audios" data-entity="0" data-page="1">
<div class="bigPlayerDetector"></div>
<div style="width: 100%;display: flex;margin-bottom: -10px;" class="audiosDiv">
<div style="width: 74%;" class="audiosContainer" n:if="$mode != 'playlists'">
<div style="padding: 8px;">
<div n:if="$audiosCount <= 0">
{include "../components/error.xml", description => $ownerId > 0 ? ($ownerId == $thisUser->getId() ? tr("no_audios_thisuser") : tr("no_audios_user")) : tr("no_audios_club")}
<div class="audiosDiv">
<div style="width: 74%;" class="audiosContainer audiosPaddingContainer" n:if="$mode != 'playlists'">
<div n:if="$audiosCount <= 0" style='height: 50%;'>
{include "../components/content_error.xml", description => $ownerId > 0 ? ($ownerId == $thisUser->getId() ? tr("no_audios_thisuser") : tr("no_audios_user")) : tr("no_audios_club")}
</div>
<div n:if="$audiosCount > 0" class="infContainer">
<div class="infObj" n:foreach="$audios as $audio">
@ -81,33 +80,16 @@
]}
</div>
</div>
</div>
<div style="width: 74%;" n:if="$mode == 'playlists'">
<div style="padding: 8px;">
<div n:if="$playlistsCount <= 0">
{include "../components/error.xml", description => $ownerId > 0 ? ($ownerId == $thisUser->getId() ? tr("no_playlists_thisuser") : tr("no_playlists_user")) : tr("no_playlists_club")}
<div style="width: 71.8%;" class="audiosPaddingContainer audiosPaddingContainer" n:if="$mode == 'playlists'">
<div n:if="$playlistsCount <= 0" style='height: 100%;'>
{include "../components/content_error.xml", description => $ownerId > 0 ? ($ownerId == $thisUser->getId() ? tr("no_playlists_thisuser") : tr("no_playlists_user")) : tr("no_playlists_club")}
</div>
<div class="infContainer playlistContainer" n:if="$playlistsCount > 0">
<div class="infObj" n:foreach="$playlists as $playlist">
<a href="/playlist{$playlist->getPrettyId()}">
<div class="playlistCover">
<img src="{$playlist->getCoverURL()}" alt="{_playlist_cover}">
</div>
</a>
<div class="playlistInfo">
<a href="/playlist{$playlist->getPrettyId()}">
<span style="font-size: 12px" class="playlistName">
{ovk_proc_strtr($playlist->getName(), 15)}
</span>
</a>
<a href="{$playlist->getOwner()->getURL()}">{ovk_proc_strtr($playlist->getOwner()->getCanonicalName(), 20)}</a>
</div>
</div>
{foreach $playlists as $playlist}
{include 'playlistListView.xml', playlist => $playlist}
{/foreach}
</div>
<div>
@ -120,7 +102,6 @@
]}
</div>
</div>
</div>
{include "tabs.xml"}
</div>
{/block}

View file

@ -8,11 +8,11 @@
{if !$_GET["gid"]}
<a href="{$thisUser->getURL()}">{$thisUser->getCanonicalName()}</a>
»
<a href="/audios{$thisUser->getId()}">{_audios}</a>
<a href="/playlists{$thisUser->getId()}">{_playlists}</a>
{else}
<a href="{$club->getURL()}">{$club->getCanonicalName()}</a>
»
<a href="/audios-{$club->getId()}">{_audios}</a>
<a href="/playlists-{$club->getId()}">{_playlists}</a>
{/if}
»
{_new_playlist}
@ -44,6 +44,10 @@
<div class="moreInfo" style="margin-top: 11px;">
<textarea placeholder="{_description}" name="description" maxlength="2045" />
</div>
<label>
<input type='checkbox' name='is_unlisted'>
{_playlist_hide_from_search}
</label>
</div>
</div>
@ -68,6 +72,7 @@
<form method="post" id="newPlaylistForm" enctype="multipart/form-data">
<input type="hidden" name="title" maxlength="125" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="hidden" name="is_unlisted" value="0" />
<textarea style="display:none;" name="description" maxlength="2045" />
<input type="hidden" name="audios">
<input type="file" style="display:none;" name="cover" accept=".jpg,.png">
@ -83,6 +88,7 @@
u("#newPlaylistForm").on("submit", (e) => {
document.querySelector("#newPlaylistForm input[name='title']").value = document.querySelector(".plinfo input[name='title']").value
document.querySelector("#newPlaylistForm textarea[name='description']").value = document.querySelector(".plinfo textarea[name='description']").value
document.querySelector("#newPlaylistForm input[name='is_unlisted']").value = document.querySelector(".plinfo input[name='is_unlisted']").checked ? 1 : 0
})
u("#newPlaylistForm input[name='cover']").on("change", (e) => {

View file

@ -1,13 +1,16 @@
{extends "../@layout.xml"}
{block title}{_playlist}{/block}
{block title}
{_playlist}
{$playlist->getName()}
{/block}
{block headIncludes}
<meta property="og:type" content="music.album">
<meta property="og:title" content="{$playlist->getName()}">
<meta property="og:url" content="{$playlist->getURL()}">
<meta property="og:description" content="{$playlist->getDescription()}">
<meta property="og:image" content="{$playlist->getCoverURL()}">
<meta property="og:image" content="{$cover_url}">
<script type="application/ld+json">
{
@ -22,7 +25,7 @@
{block header}
<a href="{$owner->getURL()}">{$owner->getCanonicalName()}</a>
»
<a href="/audios{$ownerId}">{_audios}</a>
<a href="/playlists{$ownerId}">{_playlists}</a>
»
{_playlist}
{/block}
@ -30,23 +33,28 @@
{block content}
{include "bigplayer.xml"}
{php $count = $playlist->size()}
<input type="hidden" name="bigplayer_context" data-type="playlist_context" data-entity="{$playlist->getId()}" data-page="{$page}">
<div class="playlistBlock">
<div class="playlistCover" style="float: left;">
<a href="{$playlist->getCoverURL()}" target="_blank">
<img src="{$playlist->getCoverURL('normal')}" alt="{_playlist_cover}">
{if $cover}
<a href="{$cover_url}" target="_blank">
<img onclick="OpenMiniature(event, {$cover_url}, null, {$cover->getPrettyId()}, null)" src="{$cover_url}" alt="{_playlist_cover}">
</a>
{else}
<a>
<img src="{$cover_url}" alt="{_playlist_cover}">
</a>
{/if}
<div class="profile_links" style="width: 139px;" n:if="isset($thisUser)">
<a class="profile_link" style="width: 98%;" href="/playlist{$playlist->getPrettyId()}/edit" n:if="$playlist->canBeModifiedBy($thisUser)">{_edit_playlist}</a>
<a class="profile_link" style="width: 98%;" id="bookmarkPlaylist" data-id="{$playlist->getId()}" n:if="!$isBookmarked">{_bookmark}</a>
<a class="profile_link" style="width: 98%;" id="unbookmarkPlaylist" data-id="{$playlist->getId()}" n:if="$isBookmarked">{_unbookmark}</a>
<div class="profile_links" n:if="isset($thisUser)">
<a class="profile_link" href="/player/upload?playlist={$playlist->getId()}" n:if="$canEdit">{_upload_audio}</a>
<a class="profile_link" href="/playlist{$playlist->getPrettyId()}/edit" n:if="$canEdit">{_edit_playlist}</a>
<a class="profile_link" id="bookmarkPlaylist" data-id="{$playlist->getId()}" n:if="!$isBookmarked">{_bookmark}</a>
<a class="profile_link" id="unbookmarkPlaylist" data-id="{$playlist->getId()}" n:if="$isBookmarked">{_unbookmark}</a>
</div>
</div>
<div style="float: left;padding-left: 13px;width:75%">
<div class='playlistWrapper'>
<div class="playlistInfo">
<h4 style="border-bottom:unset;">{$playlist->getName()}</h4>

View file

@ -5,6 +5,7 @@
{/block}
{block header}
{if !$playlist}
{if !is_null($group)}
<a href="{$group->getURL()}">{$group->getCanonicalName()}</a>
»
@ -14,6 +15,13 @@
»
<a href="/audios{$thisUser->getId()}">{_audios}</a>
{/if}
{else}
<a href="{$owner->getURL()}">{$owner->getCanonicalName()}</a>
»
<a href="/playlists{$ownerId}">{_playlists}</a>
»
<a href="/playlist{$playlist->getPrettyId()}">{_playlist}</a>
{/if}
»
{_upload_audio}
@ -36,6 +44,7 @@
<input type="hidden" name="lyrics" />
<input type="hidden" name="genre" />
<input type="hidden" name="explicit" />
<input type="hidden" name="unlisted" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input id="audio_input" type="file" name="blob" accept="audio/*" style="display:none" />
@ -43,20 +52,20 @@
</form>
</div><br/>
<span>{_you_can_also_add_audio_using} <b><a href="/search?type=audios">{_search_audio_inst}</a></b>.<span>
<span>{_you_can_also_add_audio_using} <b><a href="/search?section=audios">{_search_audio_inst}</a></b>.<span>
</div>
<div id="lastStep" style="display:none;">
<table cellspacing="7" cellpadding="0" border="0" align="center">
<tbody>
<tr>
<td width="120" valign="top"><span class="nobold">{_audio_name}:</span></td>
<td><input type="text" name="name" autocomplete="off" maxlength="80" /></td>
</tr>
<tr>
<td width="120" valign="top"><span class="nobold">{_performer}:</span></td>
<td><input name="performer" type="text" autocomplete="off" maxlength="80" /></td>
</tr>
<tr>
<td width="120" valign="top"><span class="nobold">{_audio_name}:</span></td>
<td><input type="text" name="name" autocomplete="off" maxlength="80" /></td>
</tr>
<tr>
<td width="120" valign="top"><span class="nobold">{_genre}:</span></td>
<td>
@ -74,7 +83,8 @@
<tr>
<td width="120" valign="top"></td>
<td>
<label><input type="checkbox" name="explicit">{_audios_explicit}</label>
<label style='display:block'><input type="checkbox" name="explicit">{_audios_explicit}</label>
<label><input type="checkbox" name="unlisted">{_audios_unlisted}</label>
</td>
</tr>
<tr>
@ -101,7 +111,23 @@
document.querySelector("#firstStep").style.display = "none"
document.querySelector("#lastStep").style.display = "block"
let tags = await id3.fromFile(files[0]);
function fallback() {
console.info('Tags not found, setting default values.')
document.querySelector("#lastStep input[name=name]").value = files[0].name
document.querySelector("#lastStep select[name=genre]").value = "Other"
document.querySelector("#lastStep input[name=performer]").value = tr("track_unknown");
}
let tags = null
try {
tags = await id3.fromFile(files[0]);
} catch(e) {
console.error(e)
}
console.log(tags)
if(tags != null) {
console.log("ID" + tags.kind + " detected, setting values...");
@ -116,6 +142,16 @@
document.querySelector("#lastStep input[name=performer]").value = tr("track_unknown");
if(tags.genre != null) {
// if there are more than one genre
if(tags.genre.split(', ').length > 1) {
const genres = tags.genre.split(', ')
genres.forEach(genre => {
if(document.querySelector("#lastStep select[name=genre] > option[value='" + genre + "']") != null) {
document.querySelector("#lastStep select[name=genre]").value = genre;
}
})
} else {
if(document.querySelector("#lastStep select[name=genre] > option[value='" + tags.genre + "']") != null) {
document.querySelector("#lastStep select[name=genre]").value = tags.genre;
} else {
@ -124,9 +160,13 @@
}
}
} else {
document.querySelector("#lastStep input[name=name]").value = files[0].name
document.querySelector("#lastStep select[name=genre]").value = "Other"
document.querySelector("#lastStep input[name=performer]").value = tr("track_unknown");
}
if(tags.comments != null)
document.querySelector("#lastStep textarea[name=lyrics]").value = tags.comments
} else {
fallback()
}
});
@ -147,12 +187,14 @@
var genre_ = document.querySelector("#audio_upload input[name=genre]");
var lyrics_ = document.querySelector("#audio_upload input[name=lyrics]");
var explicit_ = document.querySelector("#audio_upload input[name=explicit]");
var unlisted_ = document.querySelector("#audio_upload input[name=unlisted]");
name_.value = document.querySelector("#lastStep input[name=name]").value
perf_.value = document.querySelector("#lastStep input[name=performer]").value
genre_.value = document.querySelector("#lastStep select[name=genre]").value
lyrics_.value = document.querySelector("#lastStep textarea[name=lyrics]").value
explicit_.value = document.querySelector("#lastStep input[name=explicit]").checked ? "on" : "off"
unlisted_.value = document.querySelector("#lastStep input[name=unlisted]").checked ? "on" : "off"
$("#audio_upload > form").trigger("submit");
})

View file

@ -1,4 +1,4 @@
<div class="bigPlayer">
<div n:class="bigPlayer, $tidy ? tidy">
<audio class="audio" />
<div class="paddingLayer">
<div class="playButtons">
@ -18,21 +18,21 @@
<div class="trackPanel">
<div class="trackInfo">
<div class="trackName">
<b>{_track_unknown}</b> —
<a>{_track_unknown}</a> —
<span>{_track_noname}</span>
</div>
<div class="timer" style="float:right">
<span class="time">00:00</span>
<span>/</span>
<span class="elapsedTime" style="cursor:pointer">-00:00</span>
<span class="elapsedTime">-00:00</span>
</div>
</div>
<div class="track" style="margin-top: -2px;">
<div class="bigPlayerTip">00:00</div>
<div class="selectableTrack">
<div style="width: 95%;position: relative;">&nbsp;
<div id='bigPlayerLengthSliderWrapper'>&nbsp;
<div class="slider"></div>
</div>
</div>
@ -41,7 +41,7 @@
<div class="volumePanel">
<div class="selectableTrack">
<div style="position: relative;width:72%">&nbsp;
<div id='bigPlayerVolumeSliderWrapper'>&nbsp;
<div class="slider"></div>
</div>
</div>

View file

@ -1,7 +1,8 @@
{php $id = $audio->getId() . rand(0, 1000)}
{php $isWithdrawn = $audio->isWithdrawn()}
{php $isAvailable = $audio->isAvailable()}
{php $editable = isset($thisUser) && $audio->canBeModifiedBy($thisUser)}
<div id="audioEmbed-{$id}" data-realid="{$audio->getId()}" {if $hideButtons}data-prettyid="{$audio->getPrettyId()}" data-name="{$audio->getName()}"{/if} data-genre="{$audio->getGenre()}" class="audioEmbed {if !$audio->isAvailable()}processed{/if} {if $isWithdrawn}withdrawn{/if}" data-length="{$audio->getLength()}" data-keys="{json_encode($audio->getKeys())}" data-url="{$audio->getURL()}">
<div id="audioEmbed-{$id}" data-realid="{$audio->getId()}" {if $hideButtons}data-prettyid="{$audio->getPrettyId()}" data-name="{$audio->getName()}"{/if} data-genre="{$audio->getGenre()}" class="audioEmbed {if !$isAvailable}processed{/if} {if $isWithdrawn}withdrawn{/if}" data-length="{$audio->getLength()}" data-keys="{json_encode($audio->getKeys())}" data-url="{$audio->getURL()}">
<audio class="audio" />
<div id="miniplayer" class="audioEntry" style="min-height: 39px;">
@ -11,29 +12,32 @@
</div>
<div class="status" style="margin-top: 12px;">
<div class="mediaInfo noOverflow" style="margin-bottom: -8px; cursor: pointer;display:flex;width: 85%;">
<div class="mediaInfo noOverflow">
<div class="info">
<strong class="performer">
<a href="/search?query=&type=audios&sort=id&only_performers=on&query={$audio->getPerformer()}">{$audio->getPerformer()}</a>
<a href="/search?query=&section=audios&order=listens&only_performers=on&q={$audio->getPerformer()}">{$audio->getPerformer()}</a>
</strong>
<span class="title {if !empty($audio->getLyrics())}withLyrics{/if}">{$audio->getTitle()}</span>
</div>
<div class="explicitMark" n:if="$audio->isExplicit()"></div>
<svg n:if="$audio->isExplicit()" class="explicitMark" xmlns="http://www.w3.org/2000/svg" height="11" viewBox="0 0 11 11" width="11">
<path d="m1 2.506v5.988a1.5 1.5 0 0 0 1.491 1.506h6.019c.827 0 1.49-.674 1.49-1.506v-5.988a1.5 1.5 0 0 0 -1.491-1.506h-6.019c-.827 0-1.49.674-1.49 1.506zm4 2.494v-1h2v-1h-3v5h3v-1h-2v-1h2v-1zm-5-2.494a2.496 2.496 0 0 1 2.491-2.506h6.019a2.5 2.5 0 0 1 2.49 2.506v5.988a2.496 2.496 0 0 1 -2.491 2.506h-6.019a2.5 2.5 0 0 1 -2.49-2.506z" />
</svg>
</div>
</div>
<div class="volume" style="display: flex; flex-direction: column;width:14%;">
<span class="nobold {if !$hideButtons}hideOnHover{/if}" data-unformatted="{$audio->getLength()}" style="text-align: center;margin-top: 12px;">{$audio->getFormattedLength()}</span>
<div class="buttons" style="margin-top: 8px;">
<div class="buttons">
{php $hasAudio = isset($thisUser) && $audio->isInLibraryOf($thisUser)}
{if !$hideButtons}
<div class="remove-icon musicIcon" data-id="{$audio->getId()}" n:if="isset($thisUser) && $hasAudio" ></div>
<div class="add-icon musicIcon hovermeicon" data-id="{$audio->getId()}" n:if="isset($thisUser) && !$hasAudio && !$isWithdrawn" ></div>
<div class="remove-icon-group musicIcon" data-id="{$audio->getId()}" data-club="{$club->getId()}" n:if="isset($thisUser) && isset($club) && $club->canBeModifiedBy($thisUser)" ></div>
<div class="add-icon-group musicIcon" data-id="{$audio->getId()}" n:if="isset($thisUser) && !$isWithdrawn" ></div>
<div class="add-icon-group musicIcon hidden" data-id="{$audio->getId()}" n:if="isset($thisUser) && !$isWithdrawn" ></div>
<a class="download-icon musicIcon" n:if='isset($thisUser) && !$isWithdrawn && $isAvailable && OPENVK_ROOT_CONF["openvk"]["preferences"]["music"]["exposeOriginalURLs"]' href="{$audio->getOriginalURL()}" download="{$audio->getDownloadName()}"></a>
<div class="edit-icon musicIcon" data-lyrics="{$audio->getLyrics()}" data-title="{$audio->getTitle()}" data-performer="{$audio->getPerformer()}" data-explicit="{(int)$audio->isExplicit()}" data-searchable="{(int)!$audio->isUnlisted()}" n:if="isset($thisUser) && $editable && !$isWithdrawn" ></div>
<div class="report-icon musicIcon" data-id="{$audio->getId()}" n:if="isset($thisUser) && !$editable && !$isWithdrawn" ></div>
{/if}
@ -41,20 +45,20 @@
</div>
</div>
<div class="subTracks">
<div style="width: 100%;">
<div class="track lengthTrack" style="margin-top: 3px;display:none">
<div class="selectableTrack" style="width: 100%;" n:attr="style => $isWithdrawn ? 'display: none;' : ''">
<div style="position: relative;width: calc(100% - 18px);">
<div class="lengthTrackWrapper">
<div class="track lengthTrack">
<div class="selectableTrack" n:attr="style => $isWithdrawn ? 'display: none;' : ''">
<div class="selectableTrackSlider">
<div class="slider"></div>
</div>
</div>
</div>
</div>
<div style="width: 81px;margin-left: 16px;">
<div class="track volumeTrack" style="margin-top: 3px;display:none">
<div class="selectableTrack" style="width: 100%;" n:attr="style => $isWithdrawn ? 'display: none;' : ''">
<div style="position: relative;width: calc(100% - 18px);">
<div class="volumeTrackWrapper">
<div class="track volumeTrack">
<div class="selectableTrack" n:attr="style => $isWithdrawn ? 'display: none;' : ''">
<div class="selectableTrackSlider">
<div class="slider"></div>
</div>
</div>

View file

@ -0,0 +1,20 @@
<a href="/playlist{$playlist->getPrettyId()}" class='playlistListView'>
<div class="playlistCover">
<img src="{$playlist->getCoverURL('normal')}" alt="{_playlist_cover}">
</div>
<div class="playlistInfo">
<div class="playlistInfoTopPart">
<span class="playlistName noOverflow">
{$playlist->getName()}
</span>
<span n:if='!empty($playlist->getDescription())' class="playlistDesc noOverflow">
{$playlist->getDescription()}
</span>
</div>
<span class="playlistMeta">
{$playlist->getMetaDescription()|noescape}
</span>
</div>
</a>

View file

@ -1,10 +1,10 @@
<div class="searchOptions newer">
<div class="searchList" style="margin-top:10px">
<a n:attr="id => $mode === 'list' && $isMy ? 'used' : 'ki'" href="/audios{$thisUser->getId()}" n:if="isset($thisUser)">{_my_music}</a>
<a href="/player/upload{if $isMyClub}?gid={abs($ownerId)}{/if}" n:if="isset($thisUser)">{_upload_audio}</a>
<a n:attr="id => $mode === 'new' ? 'used' : 'ki'" href="/audios/new">{_audio_new}</a>
<a n:attr="id => $mode === 'popular' ? 'used' : 'ki'" href="/audios/popular">{_audio_popular}</a>
<a href="/search?type=audios" n:if="isset($thisUser)">{_audio_search}</a>
<div class='verticalGrayTabsWrapper'>
<div class="verticalGrayTabs">
<div class='with_padding'>
<a n:if="isset($thisUser)" n:attr="id => $mode === 'list' && $isMy ? 'used' : 'ki'" href="/audios{$thisUser->getId()}">{_my_music}</a>
<a n:if="isset($thisUser)" href="/player/upload{if $isMyClub}?gid={abs($ownerId)}{/if}">{_upload_audio}</a>
<a n:if="isset($thisUser)" n:attr="id => $mode === 'new' ? 'used' : 'ki'" href="/search?section=audios">{_audio_new}</a>
<a n:if="isset($thisUser)" n:attr="id => $mode === 'popular' ? 'used' : 'ki'" href="/search?section=audios&order=listens">{_audio_popular}</a>
<hr n:if="isset($thisUser)">
@ -20,16 +20,16 @@
<a n:attr="id => $mode === 'playlists' && $ownerId != $thisUser->getId() ? 'used' : 'ki'" href="/playlists{$ownerId}" n:if="isset($thisUser) && isset($ownerId) && !$isMy">{if $ownerId > 0}{_playlists_user}{else}{_playlists_club}{/if}</a>
<a href="/audios/newPlaylist{if $isMyClub}?gid={abs($ownerId)}{/if}" n:if="isset($thisUser) && $isMyClub">{_new_playlist}</a>
{/if}
</div>
{if $friendsAudios}
<div class="friendsAudiosList">
<a href="/audios{$fr->getRealId()}" style="width: 94%;padding-left: 10px;" n:foreach="$friendsAudios as $fr">
<a href="/audios{$fr->getRealId()}" n:foreach="$friendsAudios as $fr">
<div class="elem">
<img src="{$fr->getAvatarURL()}" />
<div class="additionalInfo">
{php $audioStatus = $fr->getCurrentAudioStatus()}
<span class="name">{$fr->getCanonicalName()}</span>
<span class="name noOverflow">{$fr->getCanonicalName()}</span>
<span class="desc">{$audioStatus ? $audioStatus->getName() : tr("audios_count", $fr->getAudiosCollectionSize())}</span>
</div>
</div>

View file

@ -17,7 +17,7 @@
{/block}
{block preview}
<img src="{$x->getThumbnailURL()}" width="75" alt="{$x->getName(tr('__lang'))}" />
<img src="{$x->getThumbnailURL()}" width="75" alt="{$x->getName(tr('__lang'))}" loading=lazy />
{/block}
{block name}

View file

@ -14,7 +14,7 @@
{block content}
<div class="gift_grid">
<div n:foreach="$gifts as $gift" n:class="gift_sel, !$gift->canUse($thisUser) ? disabled" data-gift="{$gift->getId()}">
<img class="gift_pic" src="{$gift->getImage(2)}" alt="{_gift}" />
<img class="gift_pic" src="{$gift->getImage(2)}" alt="{_gift}" loading=lazy />
<strong class="gift_price">
{if $gift->isFree()}

View file

@ -16,7 +16,7 @@
{/block}
{block preview}
<img src="{$x->gift->getImage(2)}" width="75" alt="{_gift}" />
<img src="{$x->gift->getImage(2)}" width="75" alt="{_gift}" loading=lazy />
{/block}
{block name}

View file

@ -83,7 +83,7 @@
<option value="0" n:attr="selected => $club->getWallType() == 0" /> {_group_closed_post}</option>
<select>
<input type="checkbox" name="hide_from_global_feed" value="1" n:attr="checked => $club->isHideFromGlobalFeedEnabled()" /> {_group_hide_from_global_feed}
<input type="checkbox" name="hide_from_global_feed" value="1" n:attr="checked => $club->isHideFromGlobalFeedEnabled(), disabled => $club->isHidingFromGlobalFeedEnforced()" /> {_group_hide_from_global_feed}
</td>
</tr>
<tr>

View file

@ -54,8 +54,8 @@
<table>
<tbody>
<tr>
<td width="120" valign="top"><span class="nobold">{_gender}: </span></td>
<td>{$user->isFemale() ? tr("female"): tr("male")}</td>
<td width="120" valign="top"><span class="nobold">{_pronouns}: </span></td>
<td>{$x->isFemale() ? tr("female") : ($x->isNeutral() ? tr("neutral") : tr("male"))}</td>
</tr>
<tr>
<td width="120" valign="top"><span class="nobold">{_registration_date}: </span></td>

View file

@ -127,25 +127,19 @@
<div class="right_small_block">
{var $avatarPhoto = $club->getAvatarPhoto()}
{var $avatarLink = ((is_null($avatarPhoto) ? FALSE : $avatarPhoto->isAnonymous()) ? "/photo" . ("s/" . base_convert((string) $avatarPhoto->getId(), 10, 32)) : $club->getAvatarLink())}
<div class="avatar_block" style="position:relative;">
{var $hasAvatar = !str_contains($club->getAvatarUrl('miniscule'), "/assets/packages/static/openvk/img/camera_200.png")}
{if !is_null($thisUser) && $hasAvatar == false && $club->canBeModifiedBy($thisUser)}
<a href="javascript:addAvatarImage(true, {$club->getId()})" class="text_add_image">{_add_image_group}</a>
{elseif !is_null($thisUser) && $hasAvatar == true && $club->canBeModifiedBy($thisUser)}
<div class="avatar_controls">
<div class="avatarDelete">
<a id="upl" href="javascript:deleteAvatar('{$club->getAvatarPhoto()->getPrettyId()}')"><img src="/assets/packages/static/openvk/img/delete.png"/></a>
</div>
<div class="avatar_block" style="position:relative;" data-club="{$club->getId()}">
{if $thisUser && $club->canBeModifiedBy($thisUser)}
<a {if $avatarPhoto}style="display:none"{/if} class="add_image_text" id="add_image">{_add_image}</a>
<div {if !$avatarPhoto}style="display:none"{/if} class="avatar_controls">
<div class="avatarDelete hoverable"></div>
<div class="avatar_variants">
<div class="variant">
<img src="/assets/packages/static/openvk/img/upload.png" style="margin-left:15px;height: 10px;">
<a href="javascript:addAvatarImage(true, {$club->getId()})"><p>{_upload_new_picture}</p></a>
</div>
<a class="_add_image hoverable" id="add_image"><span>{_upload_new_picture}</span></a>
</div>
</div>
{/if}
<a href="{$avatarLink|nocheck}">
<img src="{$club->getAvatarUrl('normal')}" id="thisGroupAvatar" style="width: 100%; image-rendering: -webkit-optimize-contrast;" />
<img src="{$club->getAvatarUrl('normal')}" id="bigAvatar" style="width: 100%; image-rendering: -webkit-optimize-contrast;" />
</a>
</div>
<div n:ifset="$thisUser" id="profile_links">
@ -292,7 +286,7 @@
<img
src="{is_null($cover)?'/assets/packages/static/openvk/img/camera_200.png':$cover->getURL()}"
style="max-width: 80px; max-height: 54pt;" />
style="max-width: 80px; max-height: 54pt;" loading=lazy />
</div>
<div>
<b><a href="/album{$album->getPrettyId()}">{$album->getName()}</a></b><br>

View file

@ -26,7 +26,7 @@
<div class="crp-entry--image">
<img src="{$recipient->getAvatarURL('miniscule')}"
alt="Фотография пользователя" />
alt="Фотография пользователя" loading=lazy />
</div>
<div class="crp-entry--info">
<a href="{$recipient->getURL()}">{$recipient->getCanonicalName()}</a><br/>

View file

@ -41,7 +41,7 @@
</a>
<a href="/photo{$photo->getPrettyId()}?from=album{$album->getId()}">
<img class="album-photo--image" src="{$photo->getURLBySizeId('tinier')}" alt="{$photo->getDescription()}" />
<img class="album-photo--image" src="{$photo->getURLBySizeId('tinier')}" alt="{$photo->getDescription()}" loading=lazy />
</a>
</div>
{/foreach}

View file

@ -48,7 +48,7 @@
{var $preview = is_null($cover) ? "/assets/packages/static/openvk/img/camera_200.png" : $cover->getURLBySizeId("normal")}
<a href="/album{$x->getPrettyId()}">
<img src="{$preview}" alt="{$x->getName()}" style="height: 130px; width: 170px; object-fit: cover" />
<img src="{$preview}" alt="{$x->getName()}" style="height: 130px; width: 170px; object-fit: cover" loading=lazy />
</a>
{/block}

View file

@ -22,7 +22,7 @@
{/block}
{block preview}
<img src="{$x->getAvatarUrl('miniscule')}" width="75" alt="Фотография пользователя" />
<img src="{$x->getAvatarUrl('miniscule')}" width="75" alt="Фотография пользователя" loading=lazy />
{/block}
{block name}

View file

@ -23,7 +23,7 @@
<form n:if="$report->getContentType() != 'group'" action="/admin/reportAction{$report->getId()}" method="post">
<input type="hidden" name="hash" value="{$csrfToken}"/>
<input type="submit" name="ban" value="{_ban_user_action}" class="button">
<input n:if="$report->getContentType() !== 'user'" type="submit" name="delete" value="{_delete}" class="button">
<input n:if="$report->getContentType() !== 'user'" type="submit" name="delete" value="{_delete_content}" class="button">
<input type="submit" name="ignore" value="{_ignore_report}" class="button">
</form>
<form n:if="$report->getContentType() == 'group'" action="/admin/reportAction{$report->getId()}" method="post">

View file

@ -1,378 +1,405 @@
{extends "../@layout.xml"}
{block title}
{tr("search_for_$type")}
{if $_GET['query']}
- {$_GET['query']}
{tr("search_for_$section")}
{if $_REQUEST['q']}
- {$_REQUEST['q']}
{/if}
{/block}
{block header}
{=OPENVK_ROOT_CONF["openvk"]["appearance"]["name"]} »
{tr("search_for_$type")}
{tr("search_for_$section")}
{/block}
{* BEGIN ELEMENTS DESCRIPTION *}
{block wrap}
<div class="wrap2">
<div class="wrap1">
<div class="page_wrap">
{if $section == 'audios' && $count > 1}
{include "../Audio/bigplayer.xml", tidy => true}
{block link|strip|stripHtml}
{if $type != "apps"}
{$x->getURL()}
{else}
/app{$x->getId()}
<input type="hidden" name="bigplayer_context" data-type="classic_search_context" data-entity='{"order":"{$order}","query":"{$query}","invert":{$invert ? 1 : 0},"only_performers":{$_REQUEST['only_performers'] ? 1 : 0},"genre":"{$_REQUEST['genre']}","with_lyrics":"{$_REQUEST['with_lyrics'] ? 1 : 0}"}' data-page="{$page}">
{/if}
{/block}
{block preview}
<img src="{$x->getAvatarUrl('miniscule')}" width="75" alt="{_photo}" />
{/block}
{block name}
{if $type != "apps"}
<text style="overflow: hidden;">&nbsp;{$x->getCanonicalName()}
{if $_GET['sort'] == "rating"}({$x->getRating()}%)
{elseif $_GET['sort'] == "id"}(id{$x->getId()}){/if}</text>
<img n:if="$x->isVerified()"
class="name-checkmark"
src="/assets/packages/static/openvk/img/checkmark.png"
/>
{else}
{$x->getName()}
{/if}
{/block}
{block description}
<table class="ugc-table">
<tbody>
{if $type === "users"}
{if !is_null($_GET['status']) && $_GET['status'] != ""}
<tr>
<td><span class="nobold">{_status}:</span></td>
<td>{$x->getStatus()}</td>
</tr>
{/if}
{if !is_null($_GET['city']) && $_GET['city'] != "" && $x->getPrivacySetting("page.info.read") > 1}
<tr>
<td><span class="nobold">{_city}:</span></td>
<td>{$x->getCity()}</td>
</tr>
{/if}
{if !is_null($_GET['city']) && $_GET['hometown'] != "" && $x->getPrivacySetting("page.info.read") > 1}
<tr>
<td><span class="nobold">{_hometown}:</span></td>
<td>{$x->getHometown()}</td>
</tr>
{/if}
{if !is_null($_GET['politViews']) && $_GET['politViews'] != 0 && $x->getPrivacySetting("page.info.read") > 1}
<tr>
<td><span class="nobold">{_politViews}:</span></td>
<td>{tr("politViews_".$x->getPoliticalViews())}</td>
</tr>
{/if}
{if !is_null($_GET['email']) && $_GET['email'] != "" && $x->getPrivacySetting("page.info.read") > 1}
<tr>
<td><span class="nobold">{_email}:</span></td>
<td>{$x->getContactEmail()}</td>
</tr>
{/if}
{if !is_null($_GET['telegram']) && $_GET['telegram'] != "" && $x->getPrivacySetting("page.info.read") > 1}
<tr>
<td><span class="nobold">{_telegram}:</span></td>
<td><a href="tg://resolve?domain={$x->getTelegram()}">@{$x->getTelegram()}</a></td>
</tr>
{/if}
{if !is_null($_GET['site']) && $_GET['site'] != "" && $x->getPrivacySetting("page.info.read") > 1}
<tr>
<td><span class="nobold">{_personal_website}:</span></td>
<td><a href="{$x->getWebsite()}">{$x->getWebsite()}</a></td>
</tr>
{/if}
{if !is_null($_GET['address']) && $_GET['address'] != "" && $x->getPrivacySetting("page.info.read") > 1}
<tr>
<td><span class="nobold">{_address}:</span></td>
<td>{$x->getPhysicalAddress()}</td>
</tr>
{/if}
{if $x->getPrivacySetting("page.info.read") > 1}
<tr>
<td><span class="nobold">{_pronouns}: </span></td>
<td>{$x->isFemale() ? tr("female") : ($x->isNeutral() ? tr("neutral") : tr("male"))}</td>
</tr>
<tr>
<td><span class="nobold">{_relationship}:</span></td>
<td>{$x->getLocalizedMaritalStatus()}</td>
</tr>
<tr>
<td><span class="nobold">{_registration_date}: </span></td>
<td>{$x->getRegistrationTime()}</td>
</tr>
{/if}
{/if}
<tr>
{if !empty($x->getDescription())}
{if $type != "apps"}
<td>
<span class="nobold">{_description}:</span>
</td>
{/if}
<td {if $type == "apps"}style="width:400px"{/if}>
{$x->getDescription() ?? '(' . tr("none") . ')'}
</td>
</tr>
{/if}
{if $type == "groups"}
<td>
<span class="nobold">{_size}:</span>
</td>
<td>
{tr("participants", $x->getFollowersCount())}
</td>
{/if}
</tbody>
</table>
{/block}
{block content}
<style>
.comment, .post-divider
{
border:none;
}
</style>
<div style="margin-top:-7px">
{var $data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
{include "../components/paginator.xml", conf => (object) [
"page" => $page,
"count" => $count,
"amount" => sizeof($data),
"perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE,
"atBottom" => false,
]}
<div class='summaryBar summaryBarFlex padding'>
<div class='summary'>
<b>{tr("results", $count)} {tr("showing_x_y", $page, $count)}</b>
</div>
<p style="margin-left: 15px; margin-top: 0;">
<b>{tr("results", $count)}</b>
</p>
<div>
{include searchOptions}
<div class="container_gray borderup" style="float:left;width:73.3%;">
{if sizeof($data) > 0}
{if $type == "users" || $type == "groups" || $type == "apps"}
<div class="content" n:foreach="$data as $dat">
{include "../components/paginator.xml", conf => $paginatorConf}
</div>
<div class='page_wrap_content' id='search_page'>
<div n:class='page_wrap_content_main, $section == "audios" && $count > 0 ? audios_padding'>
{if $count > 0}
{if $section === 'users'}
<div class='search_content def_row_content' n:foreach="$data as $dat">
<table>
<tbody>
<tr>
<td valign="top">
<a href="{include link, x => $dat}">
{include preview, x => $dat}
<a href="{$dat->getURL()}">
<img src="{$dat->getAvatarUrl('miniscule')}" width="75" alt="{_photo}" loading='lazy' />
</a>
</td>
<td valign="top" style="width: 100%">
{ifset infotable}
{include infotable, x => $dat}
{else}
<a href="{include link, x => $dat}">
<a href="{$dat->getURL()}">
<b>
{include name, x => $dat}
<text style="overflow: hidden;">&nbsp;{$dat->getCanonicalName()}
{if $order == "rating"}
({$dat->getProfileCompletenessReport()->total}%)
{/if}
</text>
<img n:if="$dat->isVerified()"
class="name-checkmark"
src="/assets/packages/static/openvk/img/checkmark.png"
/>
</b>
{if $dat->getId() == $thisUser->getId()}
({_s_it_is_you})
{/if}
</a>
<br/>
{include description, x => $dat}
{/ifset}
<table class="ugc-table">
<tbody>
{if $_REQUEST["order"] == "id"}
<tr>
<td><span class="nobold">ID:</span></td>
<td>{$dat->getId()}</td>
</tr>
{/if}
{if $dat->getPrivacySetting("page.info.read") > 1}
<tr>
<td><span class="nobold">{_pronouns}: </span></td>
<td>{$dat->isFemale() ? tr("female") : ($dat->isNeutral() ? tr("neutral") : tr("male"))}</td>
</tr>
<tr>
<td><span class="nobold">{_relationship}:</span></td>
<td>{$dat->getLocalizedMaritalStatus()}</td>
</tr>
<tr>
<td><span class="nobold">{_registration_date}: </span></td>
<td>{$dat->getRegistrationTime()}</td>
</tr>
{if !empty($dat->getDescription())}
<tr>
<td>
<span class="nobold">{_description}:</span>
</td>
<td n:ifset="actions" valign="top" class="action_links" style="width: 150px;">
{include actions, x => $dat}
<td>
{$dat->getDescription() ?? '(' . tr("none") . ')'}
</td>
</tr>
{/if}
{if !empty($_REQUEST["fav_mus"])}
<tr>
<td><span class="nobold">{_favorite_music}:</span></td>
<td>{$dat->getFavoriteMusic()}</td>
</tr>
{/if}
{if !empty($_REQUEST["fav_films"])}
<tr>
<td><span class="nobold">{_favorite_films}:</span></td>
<td>{$dat->getFavoriteFilms()}</td>
</tr>
{/if}
{if !empty($_REQUEST["fav_shows"])}
<tr>
<td><span class="nobold">{_favorite_shows}:</span></td>
<td>{$dat->getFavoriteShows()}</td>
</tr>
{/if}
{if !empty($_REQUEST["fav_books"])}
<tr>
<td><span class="nobold">{_favorite_books}:</span></td>
<td>{$dat->getFavoriteBooks()}</td>
</tr>
{/if}
{/if}
</tbody>
</table>
<br/>
</td>
</tr>
</tbody>
</table>
</div>
{elseif $type == "posts"}
<div n:foreach="$data as $dat" class="content">
{if !$dat || $dat->getTargetWall() < 0 && $dat->getWallOwner()->isHideFromGlobalFeedEnabled() || !$dat->canBeViewedBy($thisUser)}
<script n:if='$count > 0 && !empty($query)'>
highlightText({$query}, '.page_wrap_content_main', ['text'])
</script>
{elseif $section === 'groups'}
<div class='search_content def_row_content' n:foreach="$data as $dat">
<table>
<tbody>
<tr>
<td valign="top">
<a href="{$dat->getURL()}">
<img src="{$dat->getAvatarUrl('miniscule')}" width="75" alt="{_photo}" loading='lazy' />
</a>
</td>
<td valign="top" style="width: 100%">
<a href="{$dat->getURL()}">
<b>
<text style="overflow: hidden;">&nbsp;{$dat->getCanonicalName()}
{if $order == "id"}
(id{$dat->getId()})
{/if}
</text>
<img n:if="$dat->isVerified()"
class="name-checkmark"
src="/assets/packages/static/openvk/img/checkmark.png"
/>
</b>
</a>
<table class="ugc-table">
<tbody>
{if !empty($dat->getDescription())}
<tr>
<td>
<span class="nobold">{_description}:</span>
</td>
<td data-highlight='_clubDesc'>
{$dat->getDescription() ?? '(' . tr("none") . ')'}
</td>
</tr>
{/if}
<td>
<span class="nobold">{_size}:</span>
</td>
<td>
{tr("participants", $dat->getFollowersCount())}
</td>
</tbody>
</table>
<br/>
</td>
</tr>
</tbody>
</table>
</div>
<script n:if='$count > 0 && !empty($query)'>
highlightText({$query}, '.page_wrap_content_main', ['text', "td[data-highlight='_clubDesc']"])
</script>
{elseif $section === 'apps'}
<div class='search_content def_row_content' n:foreach="$data as $dat">
<table>
<tbody>
<tr>
<td valign="top">
<a href="/app{$dat->getId()}">
<img src="{$dat->getAvatarUrl('miniscule')}" width="75" alt="{_photo}" loading='lazy' />
</a>
</td>
<td valign="top" style="width: 100%">
<a href="/app{$dat->getId()}">
<b>
<text style="overflow: hidden;">
&nbsp;{$dat->getName()}
</text>
</b>
</a><br/>
<span style='margin-left: 2px;' data-highlight='_appDesc'>
{$dat->getDescription() ?? '(' . tr("none") . ')'}
</span>
</td>
</tr>
</tbody>
</table>
</div>
<script n:if='$count > 0 && !empty($query)'>
highlightText({$query}, '.page_wrap_content_main', ['text', "span[data-highlight='_appDesc']"])
</script>
{elseif $section === 'posts'}
<div class='search_content' n:foreach="$data as $dat">
{if !$dat || $dat->getWallOwner()->isHideFromGlobalFeedEnabled()}
{_closed_group_post}.
{else}
{include "../components/post.xml", post => $dat, commentSection => true, onWallOf => true}
{/if}
</div>
{elseif $type == "comments"}
<div n:foreach="$data as $dat" class="content">
{if !$dat->getTarget() || $dat->getTarget()->isDeleted()}
{_deleted_target_comment}.
{else}
{include "../components/comment.xml", comment => $dat, linkW => true}
{/if}
</div>
{elseif $type == "videos"}
{foreach $data as $dat}
<div class="content">
<script n:if='$count > 0 && !empty($query)'>
highlightText({$query}, '.page_wrap_content_main', [".post:not(.comment) > tbody > tr > td > .post-content > .text .really_text"])
</script>
{elseif $section === 'videos'}
<div class='search_content' n:foreach="$data as $dat">
{include "../components/video.xml", video => $dat}
</div>
{/foreach}
{elseif $type == "audios"}
{foreach $data as $dat}
<script n:if='$count > 0 && !empty($query)'>
highlightText({$query}, '.page_wrap_content_main', [".video_name", ".video_description"])
</script>
{elseif $section === 'audios'}
<div class='search_content' n:foreach="$data as $dat">
{include "../Audio/player.xml", audio => $dat}
{/foreach}
</div>
<script n:if="$count > 0 && !empty($query) && empty($_REQUEST['only_performers'])">
highlightText({$query}, '.page_wrap_content_main', [".mediaInfo .performer a", ".mediaInfo .title"])
</script>
{elseif $section === 'audios_playlists'}
<div class='search_content' n:foreach="$data as $dat">
{include "../Audio/playlistListView.xml", playlist => $dat}
</div>
<script n:if="$count > 0 && !empty($query) && empty($_REQUEST['only_performers'])">
highlightText({$query}, '.page_wrap_content_main', [".playlistName", ".playlistDesc"])
</script>
{/if}
{else}
{ifset customErrorMessage}
{include customErrorMessage}
{else}
{include "../components/nothing.xml"}
{/ifset}
{include "../components/content_error.xml", description => tr("no_results_by_this_query")}
{/if}
</div>
<script>
window.addEventListener("load", (event) => {
document.getElementsByClassName("container_gray")[0].style.minHeight = document.getElementsByClassName("searchOptions")[0].clientHeight+"px";
document.getElementsByClassName("searchOptions")[0].style.minHeight = document.getElementsByClassName("container_gray")[0].clientHeight-3+"px";
})
</script>
<div class='page_wrap_content_options verticalGrayTabsWrapper'>
<div class="page_wrap_content_options_list verticalGrayTabs with_padding">
<a n:attr="id => $section === 'users' ? 'used'" href="/search?section=users&q={urlencode($query)}"> {_s_people}</a>
<a n:attr="id => $section === 'groups' ? 'used'" href="/search?section=groups&q={urlencode($query)}"> {_s_groups}</a>
<a n:attr="id => $section === 'posts' ? 'used'" href="/search?section=posts&q={urlencode($query)}"> {_s_posts}</a>
<a n:attr="id => $section === 'videos' ? 'used'" href="/search?section=videos&q={urlencode($query)}"> {_s_videos}</a>
<a n:attr="id => $section === 'apps' ? 'used'" href="/search?section=apps&q={urlencode($query)}"> {_s_apps}</a>
<a n:attr="id => $section === 'audios' ? 'used'" href="/search?section=audios&q={urlencode($query)}"> {_s_audios}</a>
<a n:attr="id => $section === 'audios_playlists' ? 'used'" href="/search?section=audios_playlists&q={urlencode($query)}">{_s_audios_playlists}</a>
</div>
<div class='page_search_options'>
{include searchOptions}
</div>
</div>
</div>
<div n:if='$paginatorConf->pageCount > 1' class='page_content_paginator_bottom'>
{include "../components/paginator.xml", conf => $extendedPaginatorConf}
</div>
</div>
</div>
</div>
{/block}
{block searchOptions}
<div class="searchOptions">
<ul class="searchList">
<a {if $type === "users"} id="used"{/if} href="/search?type=users{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id"> {_s_people} {if $type === "users"} ({$count}){/if}</a>
<a {if $type === "groups"} id="used"{/if} href="/search?type=groups{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id"> {_s_groups} {if $type === "groups"} ({$count}){/if}</a>
<a {if $type === "comments"}id="used"{/if} href="/search?type=comments{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id">{_s_comments} {if $type === "comments"}({$count}){/if}</a>
<a {if $type === "posts"} id="used"{/if} href="/search?type=posts{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id"> {_s_posts} {if $type === "posts"} ({$count}){/if}</a>
<a {if $type === "videos"} id="used"{/if} href="/search?type=videos{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id"> {_s_videos} {if $type === "videos"} ({$count}){/if}</a>
<a {if $type === "apps"} id="used"{/if} href="/search?type=apps{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id"> {_s_apps} {if $type === "apps"} ({$count}){/if}</a>
<a {if $type === "audios"} id="used"{/if} href="/search?type=audios{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id"> {_s_audios} {if $type === "audios"} ({$count}){/if}</a>
</ul>
<div class="search_option">
<div class="search_option_name">
<div class='search_option_name_ico'></div>
{_s_order_by}
</div>
<div class="search_option_content">
<select name="order" form="search_form" data-default='id'>
{if $section == "users"}
<option value="id" n:attr="selected => $order == 'id'">{_s_order_by_reg_date}</option>
<div class="searchOption">
<div class="searchOptionName" id="n_sort" onclick="hideParams('sort')"><img src="/assets/packages/static/openvk/img/hide.png" class="searchHide">{_s_order_by}</div>
<div class="searchOptionBlock" id="s_sort">
<select name="sort" form="searcher" id="sortyor">
<option value="id" {if $_GET["sort"] == "name"}selected{/if}>{_s_order_by_id}</option>
{if $type == "users"}
<option value="name" {if $_GET["sort"] == "name"}selected{/if}>{_s_order_by_name}</option>
{if OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"]}
<option value="rating" {if $_GET["sort"] == "rating"}selected{/if}>{_s_order_by_rating}</option>
<option value="rating" n:attr="selected => $order == 'rating'">{_s_order_by_rating}</option>
{/if}
{elseif $section == "posts"}
<option value="id" n:attr="selected => $order == 'id'">{_s_order_by_publishing_date}</option>
{elseif $section == "audios"}
<option value="id" n:attr="selected => $order == 'id'">{_s_order_by_upload_date}</option>
{else}
<option value="id" n:attr="selected => $order == 'id'">{_s_order_by_creation_date}</option>
{/if}
{if $type == "audios"}
<option value="length" n:attr="selected => $_GET['sort'] == 'length'">{_s_order_by_length}</option>
<option value="listens" n:attr="selected => $_GET['sort'] == 'listens'">{_s_order_by_listens}</option>
{if $section == "audios" || $section == "audios_playlists"}
<option value="length" n:attr="selected => $order == 'length'">{_s_order_by_length}</option>
<option value="listens" n:attr="selected => $order == 'listens'">{_s_order_by_listens}</option>
{/if}
</select>
<div id="invertor">
<input type="checkbox" name="invert" value="1" form="searcher" {if !is_null($_GET['invert']) && $_GET['invert'] == "1"}checked{/if}>{_s_order_invert}
</div>
<label n:if="$order != 'rating'">
<input type="checkbox" name="invert" value="1" form="search_form" n:attr="checked => $_REQUEST['invert'] == '1'">
{_s_order_invert}
</label>
</div>
</div>
{if $type !== "groups" && $type !== "apps"} {* В OpenVK не сохраняется дата создания групп и приложений *}
<div class="searchOption">
<div class="searchOptionName" id="n_dates" onclick="hideParams('dates')"><img src="/assets/packages/static/openvk/img/hide.png" class="searchHide">{_s_by_date}</div>
<div class="searchOptionBlock" id="s_dates">
{if $type != "users"}
<p id="bydate">{_s_date_before}:<br>
{else}
<p id="bydate">{_s_registered_before}:<br>
{/if}
<input type="date" name="datebefore"
form="searcher"
id="bydate"
style="width:100%"
value="{if !is_null($_GET['datebefore'])}{$_GET['datebefore']}{/if}"
min="2019-11-19"
max={date('Y-m-d')}></p>
{if $type != "users"}
<p id="bydate">{_s_date_after}:<br>
{else}
<p id="bydate">{_s_registered_after}:<br>
{/if}
<input type="date" name="dateafter"
form="searcher"
style="width:100%"
id="bydate"
value="{if !is_null($_GET['dateafter'])}{$_GET['dateafter']}{/if}"
min="2019-11-18"
max={date('Y-m-d')}></p>
<div n:if="$section == 'users'" class="search_option">
<div class="search_option_name">
<div class='search_option_name_ico'></div>
{_main}
</div>
</div>
{/if}
<div class="search_option_content">
<input type="text" n:attr="value => $_REQUEST['city']" form="search_form" placeholder="{_city}" name="city">
<input type="text" n:attr="value => $_REQUEST['hometown']" form="search_form" placeholder="{_hometown}" name="hometown">
{if $type === "users"}
<div class="searchOption">
<div class="searchOptionName" id="n_main" onclick="hideParams('main')"><img src="/assets/packages/static/openvk/img/hide.png" class="searchHide">{_main}</div>
<div class="searchOptionBlock" id="s_main">
<input type="text" value="{if !is_null($_GET['status'])}{$_GET['status']}{/if}" form="searcher" placeholder="{_status}" name="status">
<input type="text" value="{if !is_null($_GET['city'])}{$_GET['city']}{/if}" form="searcher" placeholder="{_city}" name="city">
<input type="text" value="{if !is_null($_GET['hometown'])}{$_GET['hometown']}{/if}" form="searcher" placeholder="{_hometown}" name="hometown">
<input name="is_online" type="checkbox" {if !is_null($_GET['is_online']) && $_GET['is_online'] == "1"}checked{/if} form="searcher" value="1">{_s_now_on_site}
<label>
<input name="is_online" type="checkbox" n:attr="checked => $_REQUEST['is_online'] == '1'" form="search_form" value="1">
{_s_now_on_site}
</label>
</div>
</div>
<!-- <div class="searchOption">
<div class="searchOptionName" id="n_gender" onclick="hideParams('gender')"><img src="/assets/packages/static/openvk/img/hide.png" class="searchHide">{_gender}</div>
<div class="searchOptionBlock" id="s_gender">
<p><input type="radio" form="searcher" id="gend" {if $_GET['gender'] == 0}checked{/if} name="gender" value="0">{_male}</p>
<p><input type="radio" form="searcher" id="gend1"{if $_GET['gender'] == 1}checked{/if} name="gender" value="1">{_female}</p>
<p><input type="radio" form="searcher" id="gend2"{if $_GET['gender'] == 2 || is_null($_GET['gender'])}checked{/if} name="gender" value="2">{_s_any}</p>
<div n:if="$section == 'users'" class="search_option">
<div class="search_option_name">
<div class='search_option_name_ico'></div>
{_pronouns}
</div>
</div> -->
<div class="searchOption">
<div class="searchOptionName" id="n_relationship" onclick="hideParams('relationship')"><img src="/assets/packages/static/openvk/img/hide.png" class="searchHide">{ovk_proc_strtr(tr("relationship"), 14)}</div>
<div class="searchOptionBlock" id="s_relationship">
<select name="maritalstatus" form="searcher">
<option n:foreach="range(0, 8) as $i" value="{$i}" {if $_GET['maritalstatus'] == $i}selected{/if} form="searcher">
{tr("relationship_".$i)}
</option>
</select>
<div class="search_option_content">
<label><input type="radio" form="search_form" n:attr="checked => $_REQUEST['gender'] == 0" name="gender" value="0">{_male}</label>
<label><input type="radio" form="search_form" n:attr="checked => $_REQUEST['gender'] == 1" name="gender" value="1">{_female}</label>
<label><input type="radio" form="search_form" n:attr="checked => $_REQUEST['gender'] == 2" name="gender" value="2">{_neutral}</label>
<label><input type="radio" form="search_form" n:attr="checked => is_null($_REQUEST['gender']) || $_REQUEST['gender'] == 3" name="gender" data-default='1' value="3">{_s_any}</label>
</div>
</div>
<div class="searchOption">
<div class="searchOptionName" id="n_politViews" onclick="hideParams('politViews')"><img src="/assets/packages/static/openvk/img/hide.png" class="searchHide">{_politViews}</div>
<div class="searchOptionBlock" id="s_politViews">
<select name="politViews" form="searcher">
<option n:foreach="range(0, 9) as $i" value="{$i}" {if $_GET['politViews'] == $i}selected{/if} form="searcher">
<div n:if="$section == 'users'" n:class="search_option, !isset($_REQUEST['polit_views']) && !isset($_REQUEST['marital_status']) ? search_option_hidden">
<div class="search_option_name">
<div class='search_option_name_ico'></div>
{_s_additional}
</div>
<div class="search_option_content">
<label>
{_politViews}
<select name="polit_views" form="search_form" data-default='0'>
<option n:foreach="range(0, 9) as $i" value="{$i}" n:attr="selected => $_REQUEST['polit_views'] == $i">
{tr("politViews_".$i)}
</option>
</select>
</label>
<label>
{_relationship}
<select name="marital_status" form="search_form" data-default='0'>
<option n:foreach="range(0, 8) as $i" value="{$i}" n:attr="selected => $_REQUEST['marital_status'] == $i">
{tr("relationship_".$i)}
</option>
</select>
</label>
</div>
</div>
<div n:if="$section == 'videos'" class="search_option">
<div class="search_option_name">
<div class='search_option_name_ico'></div>
{_s_main}
</div>
<div class="search_option_content">
<label>
<input type="checkbox" value='1' name="only_youtube" n:attr="checked => !empty($_REQUEST['only_youtube'])" form="search_form">{_s_only_youtube}
</label>
</div>
</div>
<div n:if="$section == 'audios'" class="search_option">
<div class="search_option_name">
<div class='search_option_name_ico'></div>
{_s_main}
</div>
<div class="search_option_content">
<label>
<input type="checkbox" name="only_performers" n:attr="checked => !empty($_REQUEST['only_performers'])" form="search_form">{_s_only_performers}
</label>
<label>
<input type="checkbox" name="with_lyrics" n:attr="checked => !empty($_REQUEST['with_lyrics'])" form="search_form">{_s_with_lyrics}
</label>
<label>
{_genre}
<select name='genre' form="search_form" data-default='any'>
<option n:attr="selected: empty($_REQUEST['genre'])" value="any">{_s_any_single}</option>
<option n:foreach='\openvk\Web\Models\Entities\Audio::genres as $genre' n:attr="selected: $_REQUEST['genre'] == $genre" value="{$genre}">
{$genre}
</option>
</select>
</label>
</div>
</div>
<div class="searchOption">
<div class="searchOptionName" id="n_contacts" onclick="hideParams('contacts')"><img src="/assets/packages/static/openvk/img/hide.png" class="searchHide">{_contacts}</div>
<div class="searchOptionBlock" id="s_contacts">
<input type="text" name="email" value="{if !is_null($_GET['email'])}{$_GET['email']}{/if}" form="searcher" placeholder="{_email}">
<input type="text" name="telegram" value="{if !is_null($_GET['telegram'])}{$_GET['telegram']}{/if}" form="searcher" placeholder="{_telegram}"><br id="contacts">
<input type="text" name="site" value="{if !is_null($_GET['site'])}{$_GET['site']}{/if}" form="searcher" placeholder="{_personal_website}"><br id="contacts">
<input type="text" name="address" value="{if !is_null($_GET['address'])}{$_GET['address']}{/if}" form="searcher" placeholder="{_address}">
</div>
</div>
<div class="searchOption">
<div class="searchOptionName" id="n_interests" onclick="hideParams('interests')"><img src="/assets/packages/static/openvk/img/hide.png" class="searchHide">{_interests}</div>
<div class="searchOptionBlock" id="s_interests">
<input type="text" value="{if !is_null($_GET['interests'])}{$_GET['interests']}{/if}" form="searcher" placeholder="{_interests}" name="interests">
<input type="text" value="{if !is_null($_GET['fav_mus'])}{$_GET['fav_mus']}{/if}" form="searcher" placeholder="{_favorite_music}" name="fav_mus">
<input type="text" value="{if !is_null($_GET['fav_films'])}{$_GET['fav_films']}{/if}" form="searcher" placeholder="{_favorite_films}" name="fav_films">
<input type="text" value="{if !is_null($_GET['fav_shows'])}{$_GET['fav_shows']}{/if}" form="searcher" placeholder="{_favorite_shows}" name="fav_shows">
<input type="text" value="{if !is_null($_GET['fav_books'])}{$_GET['fav_books']}{/if}" form="searcher" placeholder="{_favorite_books}" name="fav_books">
<input type="text" value="{if !is_null($_GET['fav_quote'])}{$_GET['fav_quote']}{/if}" form="searcher" placeholder="{_favorite_quotes}" name="fav_quote">
</div>
</div>
{/if}
{if $type == "audios"}
<div class="searchOption">
<div class="searchOptionName" id="n_main_audio" onclick="hideParams('main_audio')"><img src="/assets/packages/static/openvk/img/hide.png" class="searchHide">{_s_main}</div>
<div class="searchOptionBlock" id="s_main_audio">
<label><input type="checkbox" name="only_performers" n:attr="checked => !empty($_GET['only_performers'])" form="searcher">{_s_only_performers}</label><br>
<label><input type="checkbox" name="with_lyrics" n:attr="checked => !empty($_GET['with_lyrics'])" form="searcher">{_s_with_lyrics}</label>
</div>
</div>
{/if}
<input class="button" type="button" id="dnt" value="{_reset}" onclick="resetSearch()">
</div>
<input class="button" id="search_reset" type="button" value="{_reset}">
{/block}

View file

@ -70,7 +70,7 @@
<input type="text" name="pseudo" value="{$user->getPseudo()}" />
</td>
</tr>
{if OPENVK_ROOT_CONF['openvk']['credentials']['zadarma']['enable']}
{if OPENVK_ROOT_CONF['openvk']['credentials']['smsc']['enable']}
<tr>
<td width="120" valign="top">
<span class="nobold">{_phone}: </span>

View file

@ -4,11 +4,14 @@
{var $act = $_GET["act"] ?? "friends"}
{if $act == "incoming"}
{var $iterator = iterator_to_array($user->getFollowers($page))}
{var $count = $user->getFollowersCount()}
{var $iterator = iterator_to_array($user->getRequests($page))}
{var $count = $user->getRequestsCount()}
{elseif $act == "outcoming"}
{var $iterator = iterator_to_array($user->getSubscriptions($page))}
{var $count = $user->getSubscriptionsCount()}
{elseif $act == "followers"}
{var $iterator = iterator_to_array($user->getFollowers($page))}
{var $count = $user->getFollowersCount()}
{elseif $act == "online"}
{var $iterator = iterator_to_array($user->getFriendsOnline($page))}
{var $count = $user->getFriendsOnlineCount()}
@ -22,6 +25,8 @@
{_incoming_req}
{elseif $act == "outcoming"}
{_outcoming_req}
{elseif $act == "followers"}
{_followers}
{elseif $act == "online"}
{_friends_online}
{else}
@ -38,6 +43,8 @@
{_incoming_req}
{elseif $act == "outcoming"}
{_outcoming_req}
{elseif $act == "followers"}
{_followers}
{elseif $act == "online"}
{_friends_online}
{else}
@ -53,18 +60,23 @@
<div n:attr="id => ($act === 'online' ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($act === 'online' ? 'act_tab_a' : 'ki')" href="?act=online">{_online}</a>
</div>
<div n:if="!is_null($thisUser) && $user->getId() === $thisUser->getId()" n:attr="id => ($act === 'incoming' || $act === 'outcoming' ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($act === 'incoming' || $act === 'outcoming' ? 'act_tab_a' : 'ki')" href="?act=incoming">{_req}</a>
<div n:if="!is_null($thisUser) && $user->getId() === $thisUser->getId()" n:attr="id => ($act === 'incoming' || $act === 'followers' || $act === 'outcoming' ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($act === 'incoming' || $act === 'followers' || $act === 'outcoming' ? 'act_tab_a' : 'ki')" href="?act=incoming">{_req}</a>
</div>
{/block}
{block size}
<div n:if="$act === 'incoming' || $act === 'outcoming'" class="mb_tabs">
<div n:if="$act === 'incoming' || $act === 'followers' || $act === 'outcoming'" class="mb_tabs">
<div n:attr="id => ($act === 'incoming' ? 'active' : 'ki')" class="mb_tab">
<div>
<a href="?act=incoming">{_incoming_req}</a>
</div>
</div>
<div n:attr="id => ($act === 'followers' ? 'active' : 'ki')" class="mb_tab">
<div>
<a href="?act=followers">{_followers}</a>
</div>
</div>
<div n:attr="id => ($act === 'outcoming' ? 'active' : 'ki')" class="mb_tab">
<div>
<a href="?act=outcoming">{_outcoming_req}</a>
@ -78,6 +90,8 @@
{tr("req", $count)}
{elseif $act == "outcoming"}
{tr("req", $count)}
{elseif $act == "followers"}
{tr("followers", $count)}
{elseif $act == "online"}
{tr("friends_list_online", $count)}
{else}
@ -97,7 +111,7 @@
{/block}
{block preview}
<img src="{$x->getAvatarUrl('miniscule')}" width="75" alt="Фотография пользователя" />
<img src="{$x->getAvatarUrl('miniscule')}" width="75" alt="Фотография пользователя" loading=lazy />
{/block}
{block name}
@ -112,8 +126,8 @@
<table>
<tbody>
<tr>
<td width="120" valign="top"><span class="nobold">{_gender}: </span></td>
<td>{$x->isFemale() ? tr("female") : tr("male")}</td>
<td width="120" valign="top"><span class="nobold">{_pronouns}: </span></td>
<td>{$x->isFemale() ? tr("female") : ($x->isNeutral() ? tr("neutral") : tr("male"))}</td>
</tr>
<tr>
<td width="120" valign="top"><span class="nobold">{_relationship}:</span></td>
@ -131,21 +145,29 @@
{if ($x->getId() !== $thisUser->getId()) && ($thisUser->getId() === $user->getId())}
{var $subStatus = $x->getSubscriptionStatus($thisUser)}
{if $subStatus === 0}
<form action="/setSub/user" method="post" class="profile_link_form">
<form action="/setSub/user" method="post" class="profile_link_form" id="_submitUserSubscriptionAction">
<input type="hidden" name="act" value="add" />
<input type="hidden" name="id" value="{$x->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" class="profile_link" value="{_friends_add}" />
</form>
{elseif $subStatus === 1}
<form action="/setSub/user" method="post" class="profile_link_form">
<form action="/setSub/user" method="post" class="profile_link_form" id="_submitUserSubscriptionAction">
<input type="hidden" name="act" value="add" />
<input type="hidden" name="id" value="{$x->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" class="profile_link" value="{_friends_accept}" />
</form>
{if $act !== 'followers'}
<form action="/setSub/user" method="post" class="profile_link_form" id="_submitUserSubscriptionAction">
<input type="hidden" name="act" value="rej" />
<input type="hidden" name="id" value="{$x->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" class="profile_link" value="{_friends_leave_in_flw}" />
</form>
{/if}
{elseif $subStatus === 2}
<form action="/setSub/user" method="post" class="profile_link_form">
<form action="/setSub/user" method="post" class="profile_link_form" id="_submitUserSubscriptionAction">
<input type="hidden" name="act" value="rem" />
<input type="hidden" name="id" value="{$x->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
@ -153,7 +175,7 @@
</form>
{elseif $subStatus === 3}
<a href="/im?sel={$x->getId()}" class="profile_link">{_send_message}</a>
<form action="/setSub/user" method="post" class="profile_link_form">
<form action="/setSub/user" method="post" class="profile_link_form" id="_submitUserSubscriptionAction">
<input type="hidden" name="act" value="rem" />
<input type="hidden" name="id" value="{$x->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />

View file

@ -48,7 +48,7 @@
{/block}
{block preview}
<img src="{$x->getAvatarUrl('miniscule')}" width="75" alt="Фотография группы" />
<img src="{$x->getAvatarUrl('miniscule')}" width="75" alt="Фотография группы" loading=lazy />
{/block}
{block name}{/block}

View file

@ -69,27 +69,22 @@
{else}
<div class="left_small_block">
<div style="margin-left: auto;margin-right: auto;display: table;position:relative;" class="avatar_block" id="av">
<div class="avatar_block">
{var $hasAvatar = !str_contains($user->getAvatarUrl('miniscule'), "/assets/packages/static/openvk/img/camera_200.png")}
{if !is_null($thisUser) && $hasAvatar == false && $user->getId() == $thisUser->getId()}
<a href="javascript:addAvatarImage(false)" class="text_add_image">{_add_image}</a>
{elseif !is_null($thisUser) && $user && $hasAvatar == true && $user->getId() == $thisUser->getId()}
<div class="avatar_controls">
<div class="avatarDelete">
<a id="upl" href="javascript:deleteAvatar('{$user->getAvatarPhoto()->getPrettyId()}')"><img src="/assets/packages/static/openvk/img/delete.png"/></a>
</div>
{if $thisUser && $user->getId() == $thisUser->getId()}
<a {if $hasAvatar}style="display:none"{/if} class="add_image_text" id="add_image">{_add_image}</a>
<div {if !$hasAvatar}style="display:none"{/if} class="avatar_controls">
<div class="avatarDelete hoverable"></div>
<div class="avatar_variants">
<div class="variant">
<img src="/assets/packages/static/openvk/img/upload.png" style="margin-left:15px;height: 10px;">
<a href="javascript:addAvatarImage(false)"><p>{_upload_new_picture}</p></a>
</div>
<a class="_add_image hoverable" id="add_image"><span>{_upload_new_picture}</span></a>
</div>
</div>
{/if}
<a href="{$user->getAvatarLink()|nocheck}">
<img src="{$user->getAvatarUrl('normal')}"
alt="{$user->getCanonicalName()}"
id="thisUserAvatar"
id="bigAvatar"
style="width: 100%; image-rendering: -webkit-optimize-contrast;" />
</a>
</div>
@ -307,7 +302,7 @@
<img
src="{is_null($cover)?'/assets/packages/static/openvk/img/camera_200.png':$cover->getURLBySizeId('small')}"
style="max-width: 80px; max-height: 54pt;" />
style="max-width: 80px; max-height: 54pt;" loading=lazy />
</div>
<div style="overflow: hidden; overflow-wrap: break-word;">
<b><a href="/album{$album->getPrettyId()}">{$album->getName()}</a></b><br>
@ -475,11 +470,11 @@
</tr>
<tr n:if="!is_null($user->getHometown())">
<td class="label"><span class="nobold">{_hometown}:</span></td>
<td class="data"><a href="/search?type=users&query=&hometown={urlencode($user->getHometown())}">{$user->getHometown()}</a></td>
<td class="data"><a href="/search?section=users&q=&hometown={urlencode($user->getHometown())}">{$user->getHometown()}</a></td>
</tr>
<tr>
<td class="label"><span class="nobold">{_politViews}:</span></td>
<td class="data"><a {if $user->getPoliticalViews() != 0}href="/search?type=users&query=&politViews={$user->getPoliticalViews()}"{/if}>{var $pviews = $user->getPoliticalViews()}{_"politViews_$pviews"}</a></td>
<td class="data">{var $pviews = $user->getPoliticalViews()}{_"politViews_$pviews"}</td>
</tr>
<tr n:if="!is_null($user->getBirthday())">
<td class="label"><span class="nobold">{_birth_date}:</span></td>
@ -525,7 +520,7 @@
</tr>
<tr n:if="!is_null($user->getCity())">
<td class="label"><span class="nobold">{_city}:</span></td>
<td class="data"><a href="/search?type=users&query=&city={$user->getCity()}">{$user->getCity()}</a></td>
<td class="data"><a href="/search?type=section&q=&city={$user->getCity()}">{$user->getCity()}</a></td>
</tr>
<tr n:if="!is_null($user->getPhysicalAddress())">
<td class="label"><span class="nobold">{_address}:</span></td>
@ -543,7 +538,7 @@
{var $interests = explode(", ", $user->getInterests())}
{foreach $interests as $interest}
<a href="/search?type=users&query=&interests={urlencode($interest)}">{$interest}</a>{if $interest != end($interests)},{/if}
<span>{$interest}</span>{if $interest != end($interests)},{/if}
{/foreach}
</td>
</tr>
@ -554,7 +549,7 @@
{var $musics = explode(", ", $user->getFavoriteMusic())}
{foreach $musics as $music}
<a href="/search?type=audios&query={urlencode($music)}">{$music}</a>{if $music != end($musics)},{/if}
<a href="/search?section=audios&q={urlencode($music)}">{$music}</a>{if $music != end($musics)},{/if}
{/foreach}
</td>
</tr>
@ -564,7 +559,7 @@
{var $films = explode(", ", $user->getFavoriteFilms())}
{foreach $films as $film}
<a href="/search?type=users&query=&fav_films={urlencode($film)}">{$film}</a>{if $film != end($films)},{/if}
<a href="/search?section=users&q=&fav_films={urlencode($film)}">{$film}</a>{if $film != end($films)},{/if}
{/foreach}
</td>
</tr>
@ -574,7 +569,7 @@
{var $shows = explode(", ", $user->getFavoriteShows())}
{foreach $shows as $show}
<a href="/search?type=users&query=&fav_shows={urlencode($show)}">{$show}</a>{if $show != end($shows)},{/if}
<a href="/search?section=users&q=&fav_shows={urlencode($show)}">{$show}</a>{if $show != end($shows)},{/if}
{/foreach}
</td>
</tr>
@ -584,7 +579,7 @@
{var $books = explode(", ", $user->getFavoriteBooks())}
{foreach $books as $book}
<a href="/search?type=users&query=&fav_books={urlencode($book)}">{$book}</a>{if $book != end($books)},{/if}
<a href="/search?section=users&q=&fav_books={urlencode($book)}">{$book}</a>{if $book != end($books)},{/if}
{/foreach}
</td>
</tr>
@ -659,7 +654,7 @@
<img style="width: 70px; max-height: 70px;"
src="{$giftDescriptor->gift->getImage(2)}"
alt="{$hideInfo ? tr('gift') : ($giftDescriptor->caption ?? tr('gift'))}"
title="{$hideInfo ? tr('gift') : ($giftDescriptor->caption ?? tr('gift'))}" />
title="{$hideInfo ? tr('gift') : ($giftDescriptor->caption ?? tr('gift'))}" loading=lazy />
</a>
</div>
</div>
@ -769,8 +764,6 @@
<script n:if="isset($thisUser) && $user->getId() == $thisUser->getId()" n:syntax="off">
function setStatusEditorShown(shown) {
document.getElementById("status_editor").style.display = shown ? "block" : "none";
if(!document.status_popup_form.submit.style.width)
document.status_popup_form.submit.style.width = document.status_popup_form.submit.offsetWidth + 4 + "px"
}
document.addEventListener("click", event => {

View file

@ -0,0 +1,193 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Получение доступа | OpenVK</title>
<style>
body {
margin: 0;
font-family: sans-serif;
background-color: #ebedf0;
}
header {
background-color: #fff;
box-shadow: 0 0 10px #d4d6d8;
padding: 13px 0;
margin-bottom: 12px;
}
header > div {
display: flex;
width: 100%;
max-width: 620px;
margin: auto;
align-items: center;
justify-content: space-between;
}
#ovkUser a:not(#pfpLink) {
color: #000;
text-decoration: none;
font-weight: 600;
font-size: 14px;
margin-right: 4px;
}
#ovkUser img {
width: 32px;
height: 32px;
border-radius: 100%;
vertical-align: middle;
object-fit: cover;
}
#ovkLogo a {
display: flex;
text-decoration: none;
color: #fff;
background: #606060;
width: 28px;
height: 28px;
justify-content: center;
align-items: center;
font-size: 21px;
font-weight: 600;
border-radius: 7px;
}
body > div {
width: 100%;
max-width: 620px;
margin: auto;
}
main {
border: 1px solid #e1e3e6;
background-color: #fff;
padding: 16px;
border-radius: 18px;
font-size: 15px;
}
#authHeading, #authExplainer {
color: #818c99;
padding-bottom: 12px;
}
:is(#authHeading, #authExplainer) b {
color: #000;
}
#authButtons {
display: flex;
gap: 10px;
align-items: center;
}
#authButtons a {
color: #818c99;
text-decoration: none;
}
#authButtons a:hover {
text-decoration: underline;
}
#authButtons button {
cursor: pointer;
appearance: none;
border: none;
background-color: #606060;
color: #fff;
font-weight: 600;
padding: 7px;
border-radius: 7px;
font-size: 15px;
}
</style>
</head>
<body>
<header>
<div>
<div style="width: 85px;">
</div>
<div id="ovkLogo">
<a href="/" target="_blank">O</a>
</div>
<div id="ovkUser">
<a href="/logout?hash={rawurlencode($csrfToken)}">{_header_log_out}</a>
<a id="pfpLink" href="/id0" target="_blank">
<img src="{$thisUser->getAvatarUrl('miniscule')}" />
</a>
</div>
</div>
</header>
<div>
<main>
<div id="authHeading">
{_app},
<b>
{if is_null($origin)}
{tr("identifies_itself_as", $clientName)}{else}
{tr("located_at_url", $origin)}{/if}</b>, {_wants_your_token}.
</div>
<div id="authExplainer">
<b>{_app_will_have_access_to}</b><br/>
{_oauth_scope_all|noescape}.
</div>
<div id="authButtons">
<button id="authAllow">{_oauth_grant}</button>
<a id="authDeny" href="javascript:void(0)">{_oauth_deny}</a>
</div>
</main>
</div>
{script "js/node_modules/msgpack-lite/dist/msgpack.min.js"}
{script "js/al_api.js"}
<script>
//<![CDATA[
let clientName = {$clientName};
let usePostMessage = {$usePostMessage} && window.opener != null;
let acceptsStale = {$acceptsStale};
let origin = {$origin};
let redirectUri = {$redirectUri};
document.querySelector("#authDeny").addEventListener("click", () => {
if (usePostMessage) {
window.opener.postMessage({
error: 'access_denied',
error_reason: 'user_denied',
error_description: 'User denied your request'
}, origin);
return;
}
window.location.href = redirectUri + 'error=access_denied&error_reason=user_denied&error_description=User%20denied%20your%20request';
});
document.querySelector("#authAllow").addEventListener("click", async () => {
let response = await API.Apps.getRegularToken(clientName, acceptsStale);
let ret = {
access_token: response.token,
expires_in: 0,
user_id: {$thisUser->getId()},
is_stale: response.is_stale
};
if (usePostMessage) {
window.opener.postMessage(ret, origin);
return;
}
window.location.href = redirectUri + (new URLSearchParams(ret)).toString();
});
//]]>
</script>
</body>
</html>

View file

@ -33,7 +33,7 @@
{/block}
{block preview}
<div class="video-preview" id="videoOpen" data-id="{$x->getId()}">
<div class="video-preview" data-id="{$x->getId()}">
<img src="{$x->getThumbnailURL()}"
alt="{$x->getName()}"
style="max-width: 170px; max-height: 127px; margin: auto;" />
@ -41,7 +41,7 @@
{/block}
{block name}
<span id="videoOpen" data-id="{$x->getId()}" style="color:unset;">{$x->getName()}</span>
<span data-id="{$x->getId()}" style="color:unset;">{$x->getName()}</span>
{/block}
{block description}
@ -51,7 +51,7 @@
<span style="color: grey;">{_video_uploaded} {$x->getPublicationTime()}</span><br/>
<span style="color: grey;">{_video_updated} {$x->getEditTime() ?? $x->getPublicationTime()}</span>
<p>
<a href="/video{$x->getPrettyId()}" id="videoOpen" data-id="{$x->getId()}">{_view_video}</a>
<a href="/video{$x->getPrettyId()}" data-id="{$x->getId()}">{_view_video}</a>
{if $x->getCommentsCount() > 0}| <a href="/video{$x->getPrettyId()}#comments">{_comments} ({$x->getCommentsCount()})</a>{/if}
</p>
{/block}

View file

@ -9,7 +9,7 @@
{css "css/bsdn.css"}
{css "css/dialog.css"}
{css "css/notifications.css"}
{css "css/avataredit.css"}
{css "css/avatar-edit.css"}
{css "css/audios.css"}
{if $isXmas}
@ -27,7 +27,7 @@
{css "css/bsdn.css"}
{css "css/dialog.css"}
{css "css/notifications.css"}
{css "css/avataredit.css"}
{css "css/avatar-edit.css"}
{css "css/audios.css"}
{if $isXmas}

View file

@ -2,7 +2,7 @@
{if !$attachment->isDeleted()}
{var $link = "/photo" . ($attachment->isAnonymous() ? ("s/" . base_convert((string) $attachment->getId(), 10, 32)) : $attachment->getPrettyId())}
<a href="{$link}" onclick="OpenMiniature(event, {$attachment->getURLBySizeId('normal')}, {$parent->getPrettyId()}, {$attachment->getPrettyId()}, {$parentType})">
<img class="media media_makima" src="{$attachment->getURLBySizeId('normal')}" alt="{$attachment->getDescription()}" />
<img class="media media_makima" src="{$attachment->getURLBySizeId('normal')}" alt="{$attachment->getDescription()}" loading=lazy />
</a>
{else}
<a href="javascript:alert('{_attach_no_longer_available}');">

View file

@ -36,21 +36,26 @@
</div>
</div>
<div n:if="isset($thisUser) &&! ($compact ?? false)" class="post-menu">
<a href="#_comment{$comment->getId()}" class="date">{$comment->getPublicationTime()}
<a href="{if $correctLink}{$comment->getTargetURL()}{/if}#_comment{$comment->getId()}" class="date">{$comment->getPublicationTime()}
<span n:if="$comment->getEditTime()" class="edited editedMark">({_edited_short})</span>
</a>
{if !$timeOnly}
&nbsp;|
{if $comment->canBeDeletedBy($thisUser)}
<a href="/comment{$comment->getId()}/delete">{_delete}</a>&nbsp;|
<a href="/comment{$comment->getId()}/delete">{_delete}</a>
{/if}
{if $comment->canBeEditedBy($thisUser)}
<a id="editPost" data-id="{$comment->getId()}">{_edit}</a>&nbsp;|
|
<a id="editPost" data-id="{$comment->getId()}">{_edit}</a>
{/if}
{if !$no_reply_button}
|
<a class="comment-reply">{_reply}</a>
{if $thisUser->getId() != $comment->getOwner()->getId()}
{/if}
{if $thisUser->getId() != $author->getRealId()}
|
{var $canReport = true}
| <a href="javascript:reportComment()">{_report}</a>
<a href="javascript:reportComment()">{_report}</a>
{/if}
<div style="float: right; font-size: .7rem;">
<a class="post-like-button" href="/comment{$comment->getId()}/like?hash={rawurlencode($csrfToken)}">
@ -59,29 +64,11 @@
</a>
</div>
{/if}
{var $target = "wall"}
{if get_class($comment->getTarget()) == "openvk\Web\Models\Entities\Note"}
{php $target = "note"}
{elseif get_class($comment->getTarget()) == "openvk\Web\Models\Entities\Post"}
{php $target = "wall"}
{elseif get_class($comment->getTarget()) == "openvk\Web\Models\Entities\Photo"}
{php $target = "photo"}
{elseif get_class($comment->getTarget()) == "openvk\Web\Models\Entities\Video"}
{php $target = "video"}
{elseif get_class($comment->getTarget()) == "openvk\Web\Models\Entities\Topic"}
{php $target = "topic"}
{/if}
<span n:if="$compact ?? false">
|&nbsp;<a
{if is_null($linkW)}
href="#_comment{$comment->getId()}"
{else}
href="{$target}{!is_null($comment->getTarget()) ? $comment->getTarget()->getPrettyId() : $comment->getOwner()->getId()}#_comment{$comment->getId()}"
{/if}
class="date"
>{$comment->getPublicationTime()}</a>

View file

@ -0,0 +1,6 @@
<div class='content_page_error'>
<span>
<b n:if="!empty($title)">{$title}<br><br></b>
{$description}
</span>
</div>

View file

@ -1,8 +1,8 @@
{var $space = 3}
{var $space = $conf->space ?? 3}
{var $pageCount = ceil($conf->count / $conf->perPage)}
<div n:if="!($conf->page === 1 && $conf->count <= $conf->perPage)" style="padding: 8px;">
<div n:class="paginator, ($conf->atBottom ?? false) ? paginator-at-bottom">
<div n:if="!($conf->page === 1 && $conf->count <= $conf->perPage)" n:attr="style => (!$conf->tidy ? 'padding: 8px;')">
<div n:class="paginator, ($conf->atBottom ?? false) ? paginator-at-bottom, ($conf->tidy ? 'tidy')">
<a n:if="$conf->page > $space" n:attr="class => ($conf->page === 1 ? 'active')" href="?{http_build_query(array_merge($_GET, ['p' => 1]), 'k', '&', PHP_QUERY_RFC3986)}">&laquo;</a>
{for $j = $conf->page - ($space-1); $j <= $conf->page + ($space-1); $j++}
<a n:if="$j > 0 && $j <= $pageCount" n:attr="class => ($conf->page === $j ? 'active')" href="?{http_build_query(array_merge($_GET, ['p' => $j]), 'k', '&', PHP_QUERY_RFC3986)}">{$j}</a>

View file

@ -14,7 +14,7 @@
<tr>
<td width="54" valign="top">
<a href="{$author->getURL()}">
<img src="{$author->getAvatarURL('miniscule')}" class="post-avatar" width="50" />
<img src="{$author->getAvatarURL('miniscule')}" class="post-avatar" width="50" loading=lazy />
<span n:if="!$post->isPostedOnBehalfOfGroup() && !($compact ?? false) && $author->isOnline()" class="post-online">{_online}</span>
</a>
</td>

View file

@ -4,7 +4,7 @@
<tr>
<td valign="top">
<div class="video-preview">
<a href="/video{$video->getPrettyId()}" id="videoOpen" data-id="{$video->getId()}">
<a href="/video{$video->getPrettyId()}" {$videoModal ? "id='videoOpen'" : ''} data-id="{$video->getId()}">
<img src="{$video->getThumbnailURL()}"
style="max-width: 170px; max-height: 127px; margin: auto;" >
</a>
@ -15,18 +15,18 @@
{include infotable, x => $dat}
{else}
<a href="/video{$video->getPrettyId()}">
<b id="videoOpen" data-id="{$video->getId()}">
<b class='video_name' {$videoModal ? "id='videoOpen'" : ''} data-id="{$video->getId()}">
{$video->getName()}
</b>
</a>
<br/>
<p>
<span>{$video->getDescription() ?? ""}</span>
<span class='video_description'>{$video->getDescription() ?? ""}</span>
</p>
<span style="color: grey;">{_video_uploaded} {$video->getPublicationTime()}</span><br/>
<p>
<a href="/video{$video->getPrettyId()}" id="videoOpen" data-id="{$video->getId()}">{_view_video}</a>
<a href="/video{$video->getPrettyId()}" {$videoModal ? "id='videoOpen'" : ''} data-id="{$video->getId()}">{_view_video}</a>
{if $video->getCommentsCount() > 0}| <a href="/video{$video->getPrettyId()}#comments">{_comments} ({$video->getCommentsCount()})</a>{/if}
</p>
{/ifset}

View file

@ -62,7 +62,7 @@ class Themepacks implements \ArrayAccess
return $offset === Themepacks::DEFAULT_THEME_ID ? false : isset($this->loadedThemepacks[$offset]);
}
function offsetGet($offset)
function offsetGet($offset) : mixed
{
return $this->loadedThemepacks[$offset];
}

View file

@ -22,21 +22,21 @@ class DateTime
$now = date_create();
$diff = date_diff($now, $then);
if($diff->invert === 0)
return ovk_strftime_safe("%e %B %Y ", $this->timestamp) . tr("time_at_sp") . ovk_strftime_safe(" %R %p", $this->timestamp);
return ovk_strftime_safe("%e %B %Y ", $this->timestamp) . tr("time_at_sp") . ovk_strftime_safe(" %R", $this->timestamp);
if($this->timestamp >= strtotime("midnight")) { # Today
if($diff->h >= 1)
return tr("time_today") . tr("time_at_sp") . ovk_strftime_safe(" %R %p", $this->timestamp);
return tr("time_today") . tr("time_at_sp") . ovk_strftime_safe(" %R", $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(" %R %p", $this->timestamp);
return tr("time_yesterday") . tr("time_at_sp") . ovk_strftime_safe(" %R", $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);
return ovk_strftime_safe("%e %h ", $this->timestamp) . tr("time_at_sp") . ovk_strftime_safe(" %R", $this->timestamp);
} else {
return ovk_strftime_safe("%e %B %Y ", $this->timestamp) . tr("time_at_sp") . ovk_strftime_safe(" %R %p", $this->timestamp);
return ovk_strftime_safe("%e %B %Y ", $this->timestamp) . tr("time_at_sp") . ovk_strftime_safe(" %R", $this->timestamp);
}
}

View file

@ -162,7 +162,7 @@ class Makima
$result->tiles = [
new ThumbTile(1, 3, $wCover, $maxHeight), new ThumbTile(1, 1, $w, $h0),
new ThumbTile(1, 1, $w, $h1),
new ThumbTile(1, 1, $w, $h1),
new ThumbTile(1, 1, $w, $h2),
];
}
break;

View file

@ -183,6 +183,8 @@ routes:
handler: "Photos->deletePhoto"
- url: "/al_avatars"
handler: "User->setAvatar"
- url: "/delete_avatar"
handler: "User->deleteAvatar"
- url: "/videos{num}"
handler: "Videos->list"
- url: "/videos/upload"
@ -231,6 +233,8 @@ routes:
handler: "Group->edit"
- url: "/club{num}/al_avatar"
handler: "Group->setAvatar"
- url: "/club{num}/delete_avatar"
handler: "Group->deleteAvatar"
- url: "/club{num}/backdrop"
handler: "Group->editBackdrop"
- url: "/club{num}/stats"
@ -375,6 +379,8 @@ routes:
handler: "VKAPI->route"
- url: "/token"
handler: "VKAPI->tokenLogin"
- url: "/authorize"
handler: "VKAPI->OAuthLogin"
- url: "/admin/sandbox"
handler: "About->sandbox"
- url: "/admin/chandler/groups"

View file

@ -4,9 +4,8 @@
white-space: nowrap;
}
.overflowedName {
position: absolute;
z-index: 99;
.audiosPaddingContainer {
padding: 8px;
}
.musicIcon {
@ -15,10 +14,6 @@
cursor: pointer;
}
.musicIcon:hover {
filter: brightness(99%);
}
.musicIcon.pressed {
filter: brightness(150%);
}
@ -31,7 +26,15 @@
height: 46px;
border-bottom: 1px solid #d8d8d8;
box-shadow: 1px 0px 8px 0px rgba(34, 60, 80, 0.2);
position: relative;
position: sticky;
top: 0px;
z-index: 1;
}
.bigPlayer.tidy {
width: 100%;
margin-left: unset;
margin-top: unset;
}
.bigPlayer.floating {
@ -171,11 +174,16 @@
font-size: 10px;
}
.bigPlayer .paddingLayer .trackInfo .timer .elapsedTime {
cursor: pointer;
}
.bigPlayer .paddingLayer .trackInfo .trackName {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 81%;
height: 13px;
display: inline-block;
}
@ -183,11 +191,26 @@
font-size: 10px;
}
.bigPlayer .paddingLayer .trackInfo b:hover {
.bigPlayer .paddingLayer .trackInfo a {
font-weight: bold;
color: black;
}
.bigPlayer .paddingLayer .trackInfo a:hover {
text-decoration: underline;
cursor: pointer;
}
.bigPlayer .paddingLayer .trackPanel .track .selectableTrack > div {
width: 95%;
position: relative;
}
.bigPlayer .paddingLayer .volumePanel .selectableTrack > div {
position: relative;
width:72%
}
.audioEmbed .track > .selectableTrack, .bigPlayer .selectableTrack {
margin-top: 3px;
width: calc(100% - 8px);
@ -214,7 +237,7 @@
.audioEntry.nowPlaying {
background: #606060;
border: 1px solid #4f4f4f;
outline: 1px solid #4f4f4f;
box-sizing: border-box;
}
@ -224,6 +247,7 @@
.audioEntry.nowPlaying:hover {
background: #4e4e4e !important;
border-radius: inherit !important;
}
.audioEntry.nowPlaying .performer a {
@ -246,8 +270,32 @@
color: white !important;
}
.audioEntry.nowPlaying .buttons .musicIcon, .audioEntry.nowPlaying .explicitMark {
filter: brightness(187%) opacity(72%);
.audioEntry.nowPlaying .explicitMark path {
fill: #ffffff;
}
.audioEntry.nowPlaying .buttons .musicIcon.edit-icon {
background-position: -152px -51px;
}
.audioEntry.nowPlaying .buttons .musicIcon.download-icon {
background-position: -151px -67px;
}
.audioEntry.nowPlaying .buttons .musicIcon.add-icon {
background-position: -94px -52px;
}
.audioEntry.nowPlaying .buttons .musicIcon.report-icon {
background-position: -66px -67px;
}
.audioEntry.nowPlaying .buttons .musicIcon.remove-icon-group {
background-position: -122px -67px;
}
.audioEntry.nowPlaying .buttons .musicIcon.remove-icon {
background-position: -108px -67px;
}
.audioEntry {
@ -264,12 +312,16 @@
}
.audioEntry .subTracks {
display: flex;
display: none;
padding-bottom: 5px;
padding-left: 8px;
padding-right: 12px;
}
.audioEntry .subTracks.shown {
display: flex;
}
.audioEntry .playerButton .playIcon {
background-image: url('/assets/packages/static/openvk/img/play_buttons.gif');
cursor: pointer;
@ -290,16 +342,45 @@
height: 23px;
}
.audioEntry .status .mediaInfo {
cursor: pointer;
display: flex;
width: 100%;
}
.overflowedName {
position: absolute;
z-index: 99;
width: 80% !important;
}
.audioEntry .status strong {
color: #4C4C4C;
}
.audioEmbed .track {
display: none;
padding: 4px 0;
padding: 0px 0;
}
.audioEmbed .track, .audioEmbed.playing .track {
.audioEmbed .track .selectableTrack {
width: 100%;
}
.audioEmbed .track .selectableTrack .selectableTrackSlider {
position: relative;
width: calc(100% - 18px);
}
.audioEmbed .subTracks .lengthTrackWrapper {
width: 100%;
}
.audioEmbed .subTracks .volumeTrackWrapper {
width: 81px;
margin-left: 16px;
}
.audioEmbed.playing .track {
display: unset;
}
@ -309,7 +390,7 @@
}
.audioEntry:hover .buttons {
display: block;
display: flex;
}
.audioEntry:hover .volume .hideOnHover {
@ -318,11 +399,16 @@
.audioEntry .buttons {
display: none;
flex-direction: row-reverse;
gap: 5px;
align-items: center;
justify-content: flex-start;
width: 62px;
height: 20px;
position: absolute;
right: 3%;
top: 2px;
margin-top: 7px;
/* чтоб избежать заедания во время ховера кнопки добавления */
clip-path: inset(0 0 0 0);
}
@ -331,18 +417,21 @@
width: 11px;
height: 11px;
float: right;
margin-right: 4px;
margin-top: 3px;
background-position: -137px -51px;
}
.audioEntry .buttons .download-icon {
width: 11px;
height: 11px;
float: right;
background-position: -136px -67px;
}
.audioEntry .buttons .add-icon {
width: 11px;
height: 11px;
float: right;
background-position: -80px -52px;
margin-top: 3px;
margin-left: 2px;
}
.audioEntry .buttons .add-icon-group {
@ -350,7 +439,6 @@
height: 11px;
float: right;
background-position: -94px -52px;
margin-top: 3px;
transition: margin-right 0.1s ease-out, opacity 0.1s ease-out;
}
@ -358,9 +446,7 @@
width: 12px;
height: 11px;
float: right;
background-position: -67px -51px;
margin-top: 3px;
margin-right: 3px;
background-position: -50px -67px;
}
.add-icon-noaction {
@ -374,22 +460,17 @@
}
.audioEntry .buttons .remove-icon {
margin-top: 3px;
width: 11px;
height: 11px;
margin-left: 2px;
float: right;
background-position: -108px -52px;
}
.audioEntry .buttons .remove-icon-group {
margin-top: 3px;
width: 13px;
height: 11px;
float: right;
background-position: -122px -52px;
margin-left: 3px;
margin-right: 3px;
}
.audioEmbed .lyrics {
@ -410,12 +491,12 @@
text-decoration: underline;
}
.audioEmbed.withdrawn .status > *, .audioEmbed.processed .status > *, .audioEmbed.withdrawn .playerButton > *, .audioEmbed.processed .playerButton > * {
.audioEmbed.withdrawn .status > *, .audioEmbed.processed .playerButton > *, .audioEmbed.withdrawn .playerButton > * {
pointer-events: none;
}
.audioEmbed.withdrawn {
filter: opacity(0.8);
opacity: 0.8;
}
.playlistCover img {
@ -427,25 +508,55 @@
margin-top: 14px;
}
.playlistContainer {
display: grid;
grid-template-columns: repeat(3, 146px);
gap: 18px 10px;
.playlistBlock .playlistWrapper {
float: left;
padding-left: 13px;
width:75%
}
.playlistContainer .playlistCover {
width: 111px;
height: 111px;
.playlistCover .profile_links .profile_link {
width: 100%;
}
/* playlist listview */
.playlistListView {
display: flex;
background: #c4c4c4;
padding: 7px;
gap: 9px;
cursor: pointer;
}
.playlistContainer .playlistCover img {
max-width: 111px;
max-height: 111px;
margin: auto;
.playlistListView:hover, .playlistListView .playlistCover {
background: #ebebeb;
}
.playlistListView .playlistCover img {
width: 100px;
height: 100px;
object-fit: contain;
}
.playlistListView .playlistInfo, .playlistListView .playlistInfo .playlistInfoTopPart {
display: flex;
flex-direction: column;
}
.playlistListView .playlistInfo {
gap: 2px;
overflow: hidden;
}
.playlistListView .playlistInfo .playlistName {
font-weight: 600;
}
.playlistListView .playlistInfo .playlistMeta, .playlistListView .playlistInfo .playlistMeta span {
color: #676767;
}
/* other */
.ovk-diag-body .searchBox {
background: #e6e6e6;
padding-top: 10px;
@ -490,17 +601,16 @@
cursor: pointer;
}
.playlistCover img {
cursor: pointer;
}
.explicitMark {
margin-top: 2px;
margin-left: 3px;
width: 11px;
height: 11px;
background: url('/assets/packages/static/openvk/img/explicit.svg');
background-repeat: no-repeat;
}
.explicitMark path {
fill: #828a99;
fill-opacity: .7;
}
.audioStatus span {
@ -517,8 +627,14 @@
padding-bottom: 3px;
}
.audiosDiv {
width: 103.1%;
display: flex;
margin: 0px 0px -10px -12px;
}
/* <center> 🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣*/
.audiosDiv center span {
.audiosDiv center span, .error_full_wrapper center span {
color: #707070;
margin: 120px 0px !important;
display: block;
@ -528,15 +644,6 @@
margin-left: -10px;
}
.playlistInfo {
display: flex;
flex-direction: column;
}
.playlistInfo .playlistName {
font-weight: 600;
}
.searchList.floating {
position: fixed;
z-index: 199;
@ -583,10 +690,13 @@
}
.friendsAudiosList {
margin-left: -7px;
margin-top: 8px;
}
.friendsAudiosList > a {
padding-left: 8px;
}
.friendsAudiosList .elem {
display: flex;
padding: 1px 1px;
@ -659,3 +769,27 @@
.addToPlaylist {
width: 22%;
}
#_addAudioAdditional {
padding: 6px;
}
#_addAudioAdditional #_tabs {
margin: 0px -6px;
}
#_addAudioAdditional #_tabs .mb_tabs {
background-color: unset;
border-bottom: unset;
padding: 0px 5px;
}
#_addAudioAdditional #_tip {
margin: 5px 0px;
display: block;
}
#_addAudioAdditional #_content {
margin-top: 6px;
padding: 1px;
}

View file

@ -0,0 +1,182 @@
.avatar_block {
position: relative;
clip-path: inset(0 0 0.7% 0);
}
.avatar_block .add_image_text {
position: absolute;
font-size: 12px;
color: #2B587A;
text-align: center;
width: 100%;
left: 0px;
bottom: 30px;
}
.avatar_block .add_image_text:hover {
text-decoration: underline;
}
#temp_uploadPic {
width: 40%;
margin-left: auto;
margin-right: auto;
display: block;
}
.hoverable {
transition: all 200ms ease-in-out;
opacity: 0.6;
cursor: pointer;
}
.hoverable:hover {
opacity: 1;
}
.avatar_block *, .avatar_block .avatarDelete::before {
-webkit-transition: all 200ms ease-in-out;
-moz-transition: all 200ms ease-in-out;
-o-transition: all 200ms ease-in-out;
transition: all 200ms ease-in-out;
}
._add_image::before {
margin-top: 2px;
content: ' ';
background: url('/assets/packages/static/openvk/img/upload.png');
width: 10px;
height: 10px;
display: inline-block;
margin-left: 15px;
}
.avatarDelete {
position: absolute;
right: 0%;
background-color: rgba(0, 0, 0, 0.75);
padding: 1px 0px 4px 3px;
border-radius: 0px 0px 0px 5px;
opacity: 0;
cursor: pointer;
width: 15px;
height: 13px;
}
.avatarDelete::before {
content: ' ';
background: url('/assets/packages/static/openvk/img/upload.png');
background-position: -10px 2px;
background-repeat: no-repeat;
width: 12px;
height: 14px;
display: inline-block;
opacity: 0.6;
}
div.avatar_block:hover .avatarDelete {
opacity: 1 !important;
}
div.avatar_block:hover .avatar_variants {
opacity: 1 !important;
margin-bottom: 1px;
}
.avatar_controls .avatar_variants {
opacity:0;
position:absolute;
background-color: rgba(0, 0, 0, 0.75);
width:100%;
bottom:0;
margin-bottom:-8px;
color:white;
padding-top: 2px;
padding-bottom: 2px;
}
.avatar_controls .avatar_variants a {
opacity: 60%;
display: flex;
user-select: none;
padding: 5px 0px;
}
.avatar_controls .avatar_variants span {
color: white;
margin-left: 6px;
}
.avatar_controls .avatar_variants a:hover, .avatarDelete:hover::before {
opacity: 100%;
}
.cropper-container.moving .cropper-point {
opacity: 1;
}
.cropper-modal {
opacity: 0.7;
background-color: #000;
}
.cropper-point {
transition: opacity 200ms ease-in-out;
background-color: #fff !important;
height: 7px !important;
width: 9px !important;
opacity: 0.6;
box-shadow: black 0px 0px 2px;
}
.cropper-line {
opacity: 0;
}
.cropper-container {
width: 421px;
height: 279px;
}
.cropper-view-box {
outline: none;
outline-color: unset;
}
.cropper-image-cont {
height: 100vh;
}
.cropper-image-cont img {
max-width: 100%;
}
.cropper-image-cont .rotateButtons {
height: 20px;
margin-top: -31px;
margin-right: 5px;
width: 45px;
float: right;
position: relative;
display: flex;
padding: 3px 5px 3px 2px;
background: rgba(0,0,0,0.5);
}
.cropper-image-cont .rotateButtons div {
background-repeat: no-repeat;
}
.cropper-image-cont .rotateButtons ._rotateLeft {
background: url(/assets/packages/static/openvk/img/upload.png);
height: 20px;
width: 23px;
background-position: -23px 0px;
}
.cropper-image-cont .rotateButtons ._rotateRight {
background: url(/assets/packages/static/openvk/img/upload.png);
height: 18px;
width: 22px;
background-position: -46px 0px;
}

View file

@ -1,88 +0,0 @@
.text_add_image
{
position:absolute;
font-size:12px;
color: #2B587A;
text-align:center;
width: 100%;
left: 0px;
bottom:30px;
}
.text_add_image:hover
{
color:rgb(48, 41, 141);
}
.text_add_image, .avatarDelete img, .avatarDelete, .avatar_variants, .variant {
-webkit-transition: all 200ms ease-in-out;
-moz-transition: all 200ms ease-in-out;
-o-transition: all 200ms ease-in-out;
transition: all 200ms ease-in-out;
}
.avatarDelete
{
position:absolute;
right:0%;
background-color: rgba(0, 0, 0, 0.75);
padding: 1px 0px 4px 3px;
border-radius: 0px 0px 0px 5px;
opacity:0;
cursor:pointer;
}
.avatarDelete img
{
width:77%;
opacity:60%;
vertical-align:middle;
}
.avatarDelete img:hover
{
opacity:100%
}
div.avatar_block:hover .avatarDelete
{
opacity:1 !important;
}
div.avatar_block:hover .avatar_variants
{
opacity:1 !important;
margin-bottom:1px;
}
.avatar_variants
{
opacity:0;
position:absolute;
background-color: rgba(0, 0, 0, 0.75);
width:100%;
bottom:0;
margin-bottom:-8px;
color:white;
padding-top: 2px;
padding-bottom: 2px;
}
.variant
{
opacity:60%;
display:flex;
user-select:none;
}
.variant p
{
color:white;
margin-left: 6px;
}
.variant img
{
color:white;
margin-top: 7px;
}
.variant:hover
{
opacity:100%;
}

View file

@ -97,16 +97,15 @@ h1 {
display: inline-block;
height: 29px;
padding: 11px 4px 0 7px;
}
.header_navigation .link, .header_navigation .header_divider_stick {
background: url('../img/divider.png') no-repeat;
background-size: 1.5px 41px;
}
.header_navigation .nodivider {
display: inline-block;
height: 29px;
padding: 11px 4px 0 7px;
background: none;
background-size: 1.5px 41px;
.page_header.search_expanded .header_navigation .header_divider_stick {
background: unset;
}
.header_navigation .link a {
@ -427,6 +426,22 @@ h1 {
text-transform: lowercase;
}
.action_links.disable>a,
.action_links.disable>form>input,
.action_links.disable {
cursor: not-allowed;
color: grey;
}
.action_links.loading::after {
content: "";
display: inline-block;
background-image: url('/assets/packages/static/openvk/img/loading_mini.gif');
width: 30px;
height: 7px;
margin-left: 5px;
}
.profile_link.disable>a,
.profile_link.disable {
cursor: not-allowed;
@ -587,6 +602,11 @@ input[type=radio]:hover {
background-position: 0 -28px;
}
input[type=checkbox]:disabled {
background-position: 0 -28px;
cursor: initial;
}
input[type=checkbox]:checked,
input[type=radio]:checked {
background-position: 0 -14px;
@ -597,6 +617,10 @@ input[type=radio]:checked:hover {
background-position: 0 -42px;
}
input[type=checkbox]:checked:disabled {
background-position: 0 -42px;
}
#auth {
padding: 10px;
}
@ -1827,6 +1851,10 @@ body.scrolled .toTop:hover {
margin-top: -1px;
}
.paginator.tidy {
float: unset;
}
.paginator-at-bottom {
margin-top: -6px;
}
@ -1970,6 +1998,18 @@ body.scrolled .toTop:hover {
line-height: normal;
}
.summaryBar.summaryBarFlex {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.summaryBar.padding {
padding-top: 10px;
padding-bottom: 8px;
height: 23px;
}
.summaryBar .summary {
color: #45688E;
font-weight: bold;
@ -2059,18 +2099,16 @@ table td[width="120"] {
font-weight: bold;
margin-right: 10px;
display: inline-block;
border-radius: 2px;
}
.mb_tab div {
padding: 3px 7px;
.mb_tab div, .mb_tab > a {
padding: 5px 9px;
display: block;
}
.mb_tab#active {
background-color: #898989;
}
.mb_tab#active div {
border: 2px solid #5f5f5f;
background-color: #606060;
}
.mb_tab#active a {
@ -2476,159 +2514,332 @@ a.poll-retract-vote {
display: none;
}
.searchOptions {
overflow: hidden;
width:25.5%;
border-top:1px solid #E5E7E6;
float:right;
scrollbar-width: none;
font-size:12px;
background-color:#f7f7f7;
margin-right: -7px;
/* Da search */
/* Header part */
.header_navigation #search_box {
display: inline-block;
height: 29px;
padding: 11px 4px 0 7px;
}
.searchBtn {
.header_navigation #search_box input[type='search'] {
height: 20px;
background: url('/assets/packages/static/openvk/img/search_icon.png') no-repeat 3px 4px;
background-color: #fff;
padding-left: 18px;
width: 120px;
}
.header_navigation #search_box input[type='search']::-webkit-search-cancel-button {
display: none;
}
.header_navigation #search_box #search_box_fr form, .header_navigation #search_box #search_box_fr #search_and_one_more_wrapper {
display: flex;
}
.header_navigation #search_box #searchBoxFastTips {
display: none;
position: absolute;
background: #fff;
border: 1px solid #d8d8d8;
border-top: unset;
margin-top: -1px;
box-shadow: 2px 3px 4px -1px rgba(34, 60, 80, 0.2);
}
.header_navigation #search_box #searchBoxFastTips a {
display: flex;
flex-direction: row;
gap: 6px;
padding: 5px;
cursor: pointer;
}
.header_navigation #search_box #searchBoxFastTips a b, .header_navigation #search_box #searchBoxFastTips a span {
display: block;
text-transform: initial;
line-height: 12px;
}
.header_navigation #search_box #searchBoxFastTips a:hover, .header_navigation #search_box #searchBoxFastTips a:focus {
background: #f3f3f3;
}
.header_navigation #search_box #searchBoxFastTips a .search_tip_info_block {
display: flex;
justify-content: center;
flex-direction: column;
align-items: baseline;
gap: 4px;
}
.header_navigation #search_box #searchBoxFastTips a img {
width: 33px;
height: 33px;
object-fit: cover;
}
.page_header.search_expanded .header_navigation #search_box #searchBoxFastTips.shown {
display: flex;
flex-direction: column;
z-index: 2;
}
.page_header.search_expanded .link {
display: none;
}
.page_header.search_expanded #search_and_one_more_wrapper {
width: 627px;
position: relative;
}
/* не говновёрстка, а пиксель-пёрфект) */
.page_header.search_expanded.search_expanded_at_all #search_and_one_more_wrapper {
width: 547px;
}
.page_header.search_expanded #search_box input[type='search'] {
width: 100%;
}
.header_navigation #search_box select[name='section'], .header_navigation #search_box .search_box_button {
display: none;
}
.page_header.search_expanded select[name='section'] {
display: inline-block !important;
}
.page_header.search_expanded_at_all .search_box_button {
display: inline-block !important;
}
.page_header select[name='section'] {
width: auto;
height: 20px;
position: absolute;
padding: 0px 0px;
right: 0;
text-align: right;
border-left: 0px;
background: transparent;
}
.header_navigation #search_box .search_box_button {
border: solid 1px #575757;
background-color: #696969;
color:white;
margin-left: -11px;
padding-bottom:2px;
color: #ffffff;
padding: 1px 0px 1px 0px;
width: 80px;
cursor: pointer;
box-shadow: 0px 2px 0px 0px rgba(255, 255, 255, 0.18) inset;
margin-top: 1px;
}
.searchBtn:active {
.header_navigation #search_box .search_box_button span {
color: white;
font-weight: 600;
font-size: 12px;
}
.header_navigation #search_box .search_box_button:active {
border: solid 1px #666666;
background-color: #696969;
color:white;
box-shadow: 0px -2px 0px 0px rgba(255, 255, 255, 0.18) inset;
}
.searchList {
list-style: none;
user-select: none;
padding-left:0px;
}
.searchList #used {
margin-left:0px;
color: white !important;
padding:2px;
padding-top:5px;
padding-bottom:5px;
border: solid 0.125rem #4F4F4F;
background: #606060;
margin-bottom:2px;
padding-left:9px;
width:87%;
}
.searchList #used a {
color: white;
}
.sr:focus {
outline:none;
}
.searchHide {
padding-right: 5px;
}
.searchList li, .searchList a
{
display: block;
/* Search layout */
#search_page {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.page_content_paginator_bottom {
background: #f7f7f7;
border-top: 1px solid #d8d8d8;
padding: 8px 8px;
display: flex;
justify-content: right;
}
.page_wrap_content_main {
padding: 3px 3px;
width: 74%;
}
.page_wrap_content_main.audios_padding {
padding: 8px 8px;
}
.page_wrap_content_main .def_row_content {
border-bottom: #ECECEC solid 1px;
padding: 3px 1px;
}
.page_wrap_content_main .def_row_content:first-of-type {
padding: 0px 1px 3px 1px;
}
.page_wrap_content_main .search_content:first-of-type .post {
border-top: unset;
padding-top: unset;
}
.page_wrap_content_main .search_content:last-of-type .post, .page_wrap_content_main .def_row_content:last-of-type {
border-bottom: unset;
padding-bottom: unset;
}
.verticalGrayTabsWrapper {
width: 158px;
border-top: 1px solid #E5E7E6;
border-left: 1px solid #d8d8d8;
scrollbar-width: none;
font-size: 12px;
background-color:#f7f7f7;
}
.searchList, .verticalGrayTabs {
list-style: none;
user-select: none;
padding-left: 0px;
display: flex;
flex-direction: column;
gap: 1px;
}
.searchList hr, .verticalGrayTabs hr {
width: 153px;
margin-left: 0px;
margin-top: 6px;
}
.verticalGrayTabs.with_padding, .verticalGrayTabs > .with_padding {
padding: 4px 4px 4px 4px;
}
.searchList #used, .verticalGrayTabs #used {
color: #ffffff !important;
padding: 5px 9px 5px 9px;
border: solid 0.125rem #4F4F4F;
background: #606060;
}
.searchList #used a, .verticalGrayTabs #used a {
color: white;
}
.searchList li, .searchList a, .verticalGrayTabs a {
display: block;
font-size: 12px;
color: #2B587A !important;
cursor: pointer;
padding:2px;
padding-top:5px;
padding-bottom:5px;
margin-bottom:2px;
padding-left:9px;
padding: 5px 9px 5px 9px;
}
.searchList li a {
min-width:100%;
}
.searchList a {
min-width: 88%;
}
.searchList a:hover {
.searchList a:hover, .verticalGrayTabs a:hover {
margin-left: 0px;
color: #2B587A !important;
background: #ebebeb;
padding: 2px;
padding-top: 5px;
padding-bottom: 5px;
margin-bottom: 2px;
padding-left: 9px;
width: 89.9%;
}
.whatFind {
color:rgb(128, 128, 128);
background:none;
border:none;
position:absolute;
width:150px;
cursor:pointer;
right:80px;
text-align:right;
margin-top: 0.5px;
.highlight {
background: #ffea6d;
border-bottom: 1px solid #c7c727;
font-weight: bolder;
padding: 0px 1px;
}
.searchOptionName {
cursor:pointer;
background-color: #EAEAEA;
padding-left:5px;
padding-top:5px;
padding-bottom:5px;
width: 90%;
font-weight: 600;
color: #585858;
border-bottom: 2px solid #E4E4E4;
/* Options */
.page_wrap_content .page_search_options {
padding: 0px 4px 4px 4px;
}
.searchOption {
.page_wrap_content .page_search_options .search_option {
user-select: none;
}
.searchOptionBlock
{
.page_wrap_content .page_search_options .search_option .search_option_name_ico {
width: 9px;
height: 8px;
display: inline-block;
background-repeat: no-repeat;
background: url('/assets/packages/static/openvk/img/arrows.png');
}
.page_wrap_content .page_search_options .search_option.search_option_hidden .search_option_name_ico {
background-position: -9px 0px;
}
.page_wrap_content .page_search_options .search_option.search_option_hidden .search_option_content {
display: none;
}
.page_wrap_content .page_search_options .search_option .search_option_name {
cursor: pointer;
background-color: #EAEAEA;
padding: 5px 5px 5px 5px;
font-weight: 600;
color: #585858;
border-bottom: 2px solid #E4E4E4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.page_wrap_content .page_search_options .search_option .search_option_content {
margin-top: -5px;
padding-top: 10px;
max-width:92%;
padding-bottom: 6px;
}
.searchOptionBlock select
{
padding-left:7px;
padding-top:3px;
padding-bottom:3px;
cursor:pointer;
.page_wrap_content .page_search_options .search_option .search_option_content label {
display: block;
}
.searchOptionBlock input[type=text], input[type=date]
{
.page_wrap_content .page_search_options .search_option .search_option_content select {
padding-left: 3px;
padding-top: 3px;
padding-bottom: 3px;
width: 100%;
}
.page_wrap_content .page_search_options #search_reset {
margin-top: 5px;
}
.page_wrap_content .page_search_options .search_option .search_option_content input[type=text], .page_wrap_content .page_search_options .search_option .search_option_content input[type=date] {
margin-bottom: 5px;
}
.borderup
{
border-top:1px solid #E5E7E6;
.content_page_error {
background: white;
border: #DEDEDE solid 1px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
#searchInput
{
transition: .3s linear;
.content_page_error span {
color: #707070;
display: block;
}
/* Da search end. */
#standaloneCommentBox {
position: sticky;
top: 0;
@ -2646,52 +2857,6 @@ a.poll-retract-vote {
width: 100%;
}
.searchTips
{
position: absolute;
background: white;
padding-left: 6px;
padding-right: 6px;
padding-bottom: 6px;
border: 1px solid #C0CAD5;
border-top: 0px;
width: fit-content;
z-index: 666666;
margin-top: -2px;
}
.searchTips td
{
color: black;
padding: 3px;
font-weight: lighter;
position:relative;
}
.searchTips td .desq
{
margin-top: -9px;
}
.restip td a:hover
{
color: black;
font-weight: lighter;
text-decoration: none;
}
.searchTips .restip
{
padding-right: 10px;
cursor: pointer;
user-select: none;
}
.searchTips .restip:hover
{
background: rgb(236, 235, 235);
}
.attachment_note_icon {
max-width: 9px;
}
@ -3066,12 +3231,6 @@ hr {
height: 1px;
}
.searchList hr {
width: 153px;
margin-left: 0px;
margin-top: 6px;
}
.showMore, .showMoreAudiosPlaylist {
width: 100%;
text-align: center;
@ -3089,3 +3248,54 @@ hr {
background-repeat: no-repeat !important;
background-position: 50% !important;
}
.entity_vertical_list {
display: flex;
flex-direction: column;
gap: 3px;
height: 197px;
overflow-y: auto;
}
.entity_vertical_list .entity_vertical_list_item {
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 4px;
}
.entity_vertical_list .entity_vertical_list_item .first_column {
display: flex;
flex-direction: row;
gap: 4px;
}
.entity_vertical_list .entity_vertical_list_item .avatar {
display: block;
}
.entity_vertical_list .entity_vertical_list_item img {
width: 50px;
height: 50px;
object-fit: cover;
}
.entity_vertical_list .entity_vertical_list_item .info {
width: 300px;
display: flex;
flex-direction: column;
justify-content: space-around;
}
.entity_vertical_list.mini .entity_vertical_list_item img {
width: 35px;
height: 35px;
}
#gif_loader {
content: "";
display: inline-block;
background-image: url('/assets/packages/static/openvk/img/loading_mini.gif');
width: 30px;
height: 7px;
}

View file

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="windowskey"
version="1.1"
width="13"
height="12"
viewBox="0 0 13 12"
sodipodi:docname="wphone.svg"
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs79" />
<sodipodi:namedview
id="namedview77"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="45.254834"
inkscape:cx="4.6514368"
inkscape:cy="5.0381358"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="windowskey" />
<g
id="g26"
transform="matrix(1.1345617,0,0,1.1369577,0.2820346,-0.32489569)"
style="stroke:none;fill:#b1bfcd;fill-opacity:1">
<path
id="rbyll"
d="M 10.7,5.95 V 10.8 L 5.13,9.93 V 5.94 Z"
style="fill:#b1bfcd;stroke-width:0;stroke:none;fill-opacity:1" />
<path
id="rtgrn"
d="M 5.13,1.13 10.7,0.326 V 5.1 H 5.13 Z"
style="fill:#b1bfcd;stroke-width:0;stroke:none;fill-opacity:1" />
<path
id="lbblu"
d="M 4.39,5.91 V 9.86 L 0.264,9.29 v -3.4 z"
style="fill:#b1bfcd;stroke-width:0;stroke:none;fill-opacity:1" />
<path
id="ltred"
d="M 0.261,1.77 4.39,1.2 V 5.15 H 0.263 Z"
style="fill:#b1bfcd;stroke-width:0;stroke:none;fill-opacity:1" />
</g>
<path
style="fill:#b1bfcd;stroke-width:0.019;stroke-dasharray:none;stroke:none;fill-opacity:1"
d="M 0.59279246,5.1581071 C 0.58639332,4.9590688 0.58035022,4.0999853 0.57936334,3.2490325 l -0.001794,-1.5471867 0.0538026,-0.010946 C 0.66096303,1.6848789 1.0412718,1.6310397 1.4765021,1.5712567 1.9117325,1.5114737 2.93877,1.3697978 3.7588077,1.2564213 L 5.2497853,1.0502822 V 3.2851385 5.5199949 H 2.9271062 0.60442726 Z"
id="path277" />
<path
style="fill:#b1bfcd;stroke-width:0.019;stroke-dasharray:none;stroke:none;fill-opacity:1"
d="m 6.118316,3.2135634 c 0,-1.2366911 0.00303,-2.24852933 0.00672,-2.24852933 0.0037,0 0.4575049,-0.0654202 1.0084606,-0.14537825 0.5509557,-0.079958 1.422975,-0.2061814 1.9378206,-0.2804964 0.5148457,-0.074315 1.4729638,-0.2129995 2.1291498,-0.30818778 0.656188,-0.0951883 1.196848,-0.1730696 1.201468,-0.1730696 0.0046,0 0.0084,1.21594296 0.0084,2.70209536 V 5.4620928 H 9.2643271 6.118316 Z"
id="path279" />
<path
style="fill:#b1bfcd;stroke-width:0;stroke-dasharray:none;stroke:none;fill-opacity:1"
d="M 11.802367,11.85061 C 11.483905,11.79947 10.658801,11.669367 9.9688019,11.561492 9.2788026,11.453616 8.1371252,11.274777 7.4317409,11.164071 6.7263567,11.053365 6.1422699,10.962787 6.1337701,10.962787 c -0.0085,0 -0.015454,-1.0161809 -0.015454,-2.2581797 V 6.4464276 h 3.1460111 3.1460108 v 2.7503471 c 0,1.5126913 -0.0065,2.7495523 -0.01447,2.7485813 -0.008,-9.71e-4 -0.275035,-0.04361 -0.593496,-0.09475 z"
id="path281" />
<path
style="fill:#b1bfcd;stroke-width:0.019;stroke-dasharray:none;stroke:none;fill-opacity:1"
d="M 4.8830724,10.826259 C 4.5998231,10.784852 3.2581997,10.598446 1.7756627,10.394512 1.1971248,10.31493 0.6955483,10.244822 0.66104834,10.238715 l -0.0627272,-0.0111 V 8.3080688 6.3885255 L 2.0989491,6.38855 c 0.8253454,1.36e-5 1.8719248,0.00577 2.3257321,0.012793 l 0.8251041,0.012769 v 2.2260859 2.2260861 l -0.062727,-0.0024 c -0.0345,-0.0013 -0.1712935,-0.01825 -0.3039857,-0.03765 z"
id="path283" />
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
Web/static/img/arrows.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 B

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