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: env:
BASE_IMAGE_NAME: php BASE_IMAGE_NAME: php
BASE_IMAGE_VERSION: "8.1" BASE_IMAGE_VERSION: "8.2"
jobs: jobs:
build-cli: build-cli:
@ -24,12 +24,18 @@ jobs:
id: buildx id: buildx
uses: docker/setup-buildx-action@v2 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 - name: Log into registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Build cli image - name: Build cli image
run: | 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 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
@ -47,12 +53,18 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@v2 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 - name: Log into registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Build apache image - name: Build apache image
run: | 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 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 id: buildx
uses: docker/setup-buildx-action@v2 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 - name: Log into registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Build base image - name: Build base image
run: | 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]') IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
@ -49,16 +55,16 @@ jobs:
echo IMAGE_ID=$IMAGE_ID echo IMAGE_ID=$IMAGE_ID
echo VERSION=$VERSION 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 - name: Build MariaDB primary image
run: | 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 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 - name: Build MariaDB event image
run: | 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 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> <tr>
<td class="float-center" align="center" valign="top"> <td class="float-center" align="center" valign="top">
<center> <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> </center>
</td> </td>
</tr> </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)_ _[Русский](README_RU.md)_
@ -6,7 +6,7 @@ _[Русский](README_RU.md)_
VKontakte belongs to Pavel Durov and VK Group. 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? ## 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. 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 ### Installation procedure
1. Install PHP 7.4, web-server, Composer, Node.js, Yarn and [Chandler](https://github.com/openvk/chandler) 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` * **Password**: `admin`
* It is recommended to change the password of the built-in account or disable it. * 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? ### Looking for Docker or Kubernetes deployment?
See `install/automated/docker/README.md` and `install/automated/kubernetes/README.md` for Docker and Kubernetes deployment instructions. 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). 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: You may reach out to us via:
* [Bug Tracker](https://github.com/openvk/openvk/projects/1) * [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. * Telegram Chat: Go to [our channel](https://t.me/openvkenglish) and open discussion in our channel menu.
* [Reddit](https://www.reddit.com/r/openvk/) * [Reddit](https://www.reddit.com/r/openvk/)
* [GitHub Discussions](https://github.com/openvk/openvk/discussions) * [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)_ _[English](README.md)_
@ -6,7 +6,7 @@ _[English](README.md)_
ВКонтакте принадлежит Павлу Дурову и VK Group. ВКонтакте принадлежит Павлу Дурову и 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` * **Пароль**: `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 # Установка в Docker/Kubernetes
Подробные иструкции можно найти в `install/automated/docker/README.md` и `install/automated/kubernetes/README.md` соответственно. Подробные иструкции можно найти в `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) * [Баг-трекер](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) и откройте обсуждение в меню нашего канала. * Telegram-чат: Перейдите на [наш канал](https://t.me/openvk) и откройте обсуждение в меню нашего канала.
* [Reddit](https://www.reddit.com/r/openvk/) * [Reddit](https://www.reddit.com/r/openvk/)
* [GitHub Discussions](https://github.com/openvk/openvk/discussions) * [GitHub Discussions](https://github.com/openvk/openvk/discussions)

View file

@ -1,8 +1,11 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\ServiceAPI; namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\APIToken;
use openvk\Web\Models\Entities\User; use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\APITokens;
use openvk\Web\Models\Repositories\Applications; use openvk\Web\Models\Repositories\Applications;
use WhichBrowser;
class Apps implements Handler class Apps implements Handler
{ {
@ -89,4 +92,25 @@ class Apps implements Handler
$app->withdrawCoins(); $app->withdrawCoins();
$resolve($coins); $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 function getProfileInfo(): object
{ {
$this->requireUser(); $this->requireUser();
$user = $this->getUser();
return (object) [ $return_object = (object) [
"first_name" => $this->getUser()->getFirstName(), "first_name" => $user->getFirstName(),
"id" => $this->getUser()->getId(), "photo_200" => $user->getAvatarURL("normal"),
"last_name" => $this->getUser()->getLastName(), "nickname" => $user->getPseudo(),
"home_town" => $this->getUser()->getHometown(), "is_service_account" => false,
"status" => $this->getUser()->getStatus(), "id" => $user->getId(),
"audio_status" => is_null($this->getUser()->getCurrentAudioStatus()) ? NULL : $this->getUser()->getCurrentAudioStatus()->toVkApiStruct($this->getUser()), "is_verified" => $user->isVerified(),
"bdate" => is_null($this->getUser()->getBirthday()) ? '01.01.1970' : $this->getUser()->getBirthday()->format('%e.%m.%Y'), "verification_status" => $user->isVerified() ? 'verified' : 'unverified',
"bdate_visibility" => $this->getUser()->getBirthdayPrivacy(), "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 "phone" => "+420 ** *** 228", # TODO
"relation" => $this->getUser()->getMaritalStatus(), "relation" => $user->getMaritalStatus(),
"sex" => $this->getUser()->isFemale() ? 1 : 2 "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 function getInfo(): object
@ -152,4 +164,30 @@ final class Account extends VKAPIRequestHandler
return (object) $output; 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

@ -485,7 +485,7 @@ final class Audio extends VKAPIRequestHandler
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
if(!is_null($album_id)) if(!is_null($album_id))
$this->fail(10, "album_id not implemented"); $this->fail(10, "album_id not implemented");
// TODO get rid of dups // TODO get rid of dups
@ -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(); $this->requireUser();
$playlists = []; $playlists = [];
$search = (new Audios)->searchPlaylists($query)->offsetLimit($offset, $limit); $params = [];
foreach($search as $playlist) { $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(!$playlist->canBeViewedBy($this->getUser())) {
if($drop_private == 0) if($drop_private == 0)
$playlists[] = NULL; $playlists[] = NULL;
@ -599,7 +604,7 @@ final class Audio extends VKAPIRequestHandler
} }
return (object) [ return (object) [
"count" => sizeof($playlists), "count" => $search->size(),
"items" => $playlists, "items" => $playlists,
]; ];
} }

View file

@ -147,7 +147,7 @@ final class Friends extends VKAPIRequestHandler
return $response; 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) if ($count >= 1000)
$this->fail(100, "One of the required parameters was not passed or is invalid."); $this->fail(100, "One of the required parameters was not passed or is invalid.");
@ -158,9 +158,18 @@ final class Friends extends VKAPIRequestHandler
$offset++; $offset++;
$followers = []; $followers = [];
foreach($this->getUser()->getFollowers($offset, $count) as $follower) { if ($out != 0) {
$followers[$i] = $follower->getId(); foreach($this->getUser()->getFollowers($offset, $count) as $follower) {
$i++; $followers[$i] = $follower->getId();
$i++;
}
}
else
{
foreach($this->getUser()->getRequests($offset, $count) as $follower) {
$followers[$i] = $follower->getId();
$i++;
}
} }
$response = $followers; $response = $followers;

View file

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

View file

@ -88,6 +88,10 @@ final class Groups extends VKAPIRequestHandler
case "can_suggest": case "can_suggest":
$rClubs[$i]->can_suggest = !$usr->canBeModifiedBy($this->getUser()) && $usr->getWallType() == 2; $rClubs[$i]->can_suggest = !$usr->canBeModifiedBy($this->getUser()) && $usr->getWallType() == 2;
break; break;
case "background":
$backgrounds = $usr->getBackDropPictureURLs();
$rClubs[$i]->background = $backgrounds;
break;
# unstandard feild # unstandard feild
case "suggested_count": case "suggested_count":
if($usr->getWallType() != 2) { if($usr->getWallType() != 2) {
@ -208,6 +212,10 @@ final class Groups extends VKAPIRequestHandler
case "can_suggest": case "can_suggest":
$response[$i]->can_suggest = !$clb->canBeModifiedBy($this->getUser()) && $clb->getWallType() == 2; $response[$i]->can_suggest = !$clb->canBeModifiedBy($this->getUser()) && $clb->getWallType() == 2;
break; break;
case "background":
$backgrounds = $clb->getBackDropPictureURLs();
$response[$i]->background = $backgrounds;
break;
# unstandard feild # unstandard feild
case "suggested_count": case "suggested_count":
if($clb->getWallType() != 2) { if($clb->getWallType() != 2) {
@ -244,23 +252,30 @@ final class Groups extends VKAPIRequestHandler
return $response; 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; $clubs = new ClubsRepo;
$array = []; $array = [];
$find = $clubs->find($q); $find = $clubs->find($q);
foreach ($find as $group) foreach ($find->offsetLimit($offset, $count) as $group)
$array[] = $group->getId(); $array[] = $group->getId();
if(!$array || sizeof($array) < 1) {
return (object) [
"count" => 0,
"items" => [],
];
}
return (object) [ return (object) [
"count" => $find->size(), "count" => $find->size(),
"items" => $this->getById(implode(',', $array), "", "is_admin,is_member,is_advertiser,photo_50,photo_100,photo_200", $offset, $count) "items" => $this->getById(implode(',', $array), "", $fields)
/*
* 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
*/
]; ];
} }
@ -347,7 +362,10 @@ final class Groups extends VKAPIRequestHandler
!empty($topics) ? $club->setEveryone_Can_Create_Topics($topics) : NULL; !empty($topics) ? $club->setEveryone_Can_Create_Topics($topics) : NULL;
!empty($adminlist) ? $club->setAdministrators_List_Display($adminlist) : NULL; !empty($adminlist) ? $club->setAdministrators_List_Display($adminlist) : NULL;
!empty($topicsAboveWall) ? $club->setDisplay_Topics_Above_Wall($topicsAboveWall) : NULL; !empty($topicsAboveWall) ? $club->setDisplay_Topics_Above_Wall($topicsAboveWall) : NULL;
!empty($hideFromGlobalFeed) ? $club->setHide_From_Global_Feed($hideFromGlobalFeed) : 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; 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": case "music":
if(!$canView) { if(!$canView) {
$response[$i]->music = "secret";
break; break;
} }
@ -159,7 +158,6 @@ final class Users extends VKAPIRequestHandler
break; break;
case "movies": case "movies":
if(!$canView) { if(!$canView) {
$response[$i]->movies = "secret";
break; break;
} }
@ -167,7 +165,6 @@ final class Users extends VKAPIRequestHandler
break; break;
case "tv": case "tv":
if(!$canView) { if(!$canView) {
$response[$i]->tv = "secret";
break; break;
} }
@ -175,7 +172,6 @@ final class Users extends VKAPIRequestHandler
break; break;
case "books": case "books":
if(!$canView) { if(!$canView) {
$response[$i]->books = "secret";
break; break;
} }
@ -183,7 +179,6 @@ final class Users extends VKAPIRequestHandler
break; break;
case "city": case "city":
if(!$canView) { if(!$canView) {
$response[$i]->city = "Воскресенск";
break; break;
} }
@ -191,7 +186,6 @@ final class Users extends VKAPIRequestHandler
break; break;
case "interests": case "interests":
if(!$canView) { if(!$canView) {
$response[$i]->interests = "secret";
break; break;
} }
@ -199,7 +193,6 @@ final class Users extends VKAPIRequestHandler
break; break;
case "quotes": case "quotes":
if(!$canView) { if(!$canView) {
$response[$i]->quotes = "secret";
break; break;
} }
@ -207,7 +200,6 @@ final class Users extends VKAPIRequestHandler
break; break;
case "email": case "email":
if(!$canView) { if(!$canView) {
$response[$i]->email = "secret@gmail.com";
break; break;
} }
@ -215,7 +207,6 @@ final class Users extends VKAPIRequestHandler
break; break;
case "telegram": case "telegram":
if(!$canView) { if(!$canView) {
$response[$i]->telegram = "@secret";
break; break;
} }
@ -223,7 +214,6 @@ final class Users extends VKAPIRequestHandler
break; break;
case "about": case "about":
if(!$canView) { if(!$canView) {
$response[$i]->about = "secret";
break; break;
} }
@ -231,7 +221,6 @@ final class Users extends VKAPIRequestHandler
break; break;
case "rating": case "rating":
if(!$canView) { if(!$canView) {
$response[$i]->rating = 22;
break; break;
} }
@ -240,12 +229,43 @@ final class Users extends VKAPIRequestHandler
case "counters": case "counters":
$response[$i]->counters = (object) [ $response[$i]->counters = (object) [
"friends_count" => $usr->getFriendsCount(), "friends_count" => $usr->getFriendsCount(),
"photos_count" => (new Albums)->getUserPhotosCount($usr), "photos_count" => (new Photos)->getUserPhotosCount($usr),
"videos_count" => (new Videos)->getUserVideosCount($usr), "videos_count" => (new Videos)->getUserVideosCount($usr),
"audios_count" => (new Audios)->getUserCollectionSize($usr), "audios_count" => (new Audios)->getUserCollectionSize($usr),
"notes_count" => (new Notes)->getUserNotesCount($usr) "notes_count" => (new Notes)->getUserNotesCount($usr)
]; ];
break; 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, int $count = 100,
string $city = "", string $city = "",
string $hometown = "", string $hometown = "",
int $sex = 2, int $sex = 3,
int $status = 0, # это про marital status int $status = 0, # marital_status
bool $online = false, bool $online = false,
# дальше идут параметры которых нету в vkapi но есть на сайте # non standart params:
string $profileStatus = "", # а это уже нормальный статус
int $sort = 0, int $sort = 0,
int $before = 0, int $polit_views = 0,
int $politViews = 0,
int $after = 0,
string $interests = "",
string $fav_music = "", string $fav_music = "",
string $fav_films = "", string $fav_films = "",
string $fav_shows = "", string $fav_shows = "",
string $fav_books = "", string $fav_books = ""
string $fav_quotes = ""
) )
{ {
$users = new UsersRepo; if($count > 100) {
$this->fail(100, "One of the parameters specified was missing or invalid: count should be less or equal to 100");
$sortg = "id ASC"; }
$nfilds = $fields; $users = new UsersRepo;
$output_sort = ['type' => 'id', 'invert' => false];
$output_params = [
"ignore_private" => true,
];
switch($sort) { switch($sort) {
default:
case 0: case 0:
$sortg = "id DESC"; $output_sort = ['type' => 'id', 'invert' => false];
break; break;
case 1: case 1:
$sortg = "id ASC"; $output_sort = ['type' => 'id', 'invert' => true];
break;
case 2:
$sortg = "first_name DESC";
break;
case 3:
$sortg = "first_name ASC";
break; break;
case 4: case 4:
$sortg = "rating DESC"; $output_sort = ['type' => 'rating', 'invert' => false];
if(!str_contains($nfilds, "rating")) {
$nfilds .= "rating";
}
break;
case 5:
$sortg = "rating DESC";
if(!str_contains($nfilds, "rating")) {
$nfilds .= "rating";
}
break; break;
} }
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 = []; $array = [];
$find = $users->find($q, $output_params, $output_sort);
$parameters = [ foreach ($find->offsetLimit($offset, $count) as $user)
"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,
];
$find = $users->find($q, $parameters, $sortg);
foreach ($find as $user)
$array[] = $user->getId(); $array[] = $user->getId();
if(!$array || sizeof($array) < 1) {
return (object) [
"count" => 0,
"items" => [],
];
}
return (object) [ return (object) [
"count" => $find->size(), "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")), "object_id" => (int) substr($screen_name, strlen("club")),
"type" => "group" "type" => "group"
]; ];
} } else $this->fail(104, "Not found");
} else { } else {
$user = (new Users)->getByShortURL($screen_name); $user = (new Users)->getByShortURL($screen_name);
if($user) { if($user) {
@ -39,8 +39,17 @@ final class Utils extends VKAPIRequestHandler
"type" => "group" "type" => "group"
]; ];
} }
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 else
$profiles[] = $attachment->getOwner()->getId(); $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[] = [ $repost[] = [
"id" => $attachment->getVirtualId(), "id" => $attachment->getVirtualId(),
"owner_id" => $attachment->isPostedOnBehalfOfGroup() ? $attachment->getOwner()->getId() * -1 : $attachment->getOwner()->getId(), "owner_id" => $attachment->isPostedOnBehalfOfGroup() ? $attachment->getOwner()->getId() * -1 : $attachment->getOwner()->getId(),
"from_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(), "date" => $attachment->getPublicationTime()->timestamp(),
"post_type" => "post", "post_type" => $attachment->getVkApiType(),
"text" => $attachment->getText(false), "text" => $attachment->getText(false),
"attachments" => $repostAttachments, "attachments" => $repostAttachments,
"post_source" => $post_source, "post_source" => $attachment->getPostSourceInfo(),
]; ];
if ($attachment->getVirtualId() > 0) if ($attachment->getTargetWall() > 0)
$profiles[] = $attachment->getVirtualId(); $profiles[] = $attachment->getTargetWall();
else else
$groups[] = $attachment->getVirtualId(); $groups[] = abs($attachment->getTargetWall());
if($post->isSigned()) if($post->isSigned())
$profiles[] = $attachment->getOwner()->getId(); $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; $signerId = NULL;
if($post->getSuggestionType() != 0)
$postType = "suggest";
if($post->isSigned()) { if($post->isSigned()) {
$actualAuthor = $post->getOwner(false); $actualAuthor = $post->getOwner(false);
$signerId = $actualAuthor->getId(); $signerId = $actualAuthor->getId();
} }
$items[] = (object)[ # TODO "can_pin", "copy_history" и прочее не должны возвращаться, если равны null или false
# Ну и ещё всё надо перенести в toVkApiStruct, а то слишком много дублированного кода
$post_temp_obj = (object)[
"id" => $post->getVirtualId(), "id" => $post->getVirtualId(),
"from_id" => $from_id, "from_id" => $from_id,
"owner_id" => $post->getTargetWall(), "owner_id" => $post->getTargetWall(),
"date" => $post->getPublicationTime()->timestamp(), "date" => $post->getPublicationTime()->timestamp(),
"post_type" => $postType, "post_type" => $post->getVkApiType(),
"text" => $post->getText(false), "text" => $post->getText(false),
"copy_history" => $repost, "copy_history" => $repost,
"can_edit" => $post->canBeEditedBy($this->getUser()), "can_edit" => $post->canBeEditedBy($this->getUser()),
@ -195,8 +171,7 @@ final class Wall extends VKAPIRequestHandler
"is_pinned" => $post->isPinned(), "is_pinned" => $post->isPinned(),
"is_explicit" => $post->isExplicit(), "is_explicit" => $post->isExplicit(),
"attachments" => $attachments, "attachments" => $attachments,
"post_source" => $post_source, "post_source" => $post->getPostSourceInfo(),
"signer_id" => $signerId,
"comments" => (object)[ "comments" => (object)[
"count" => $post->getCommentsCount(), "count" => $post->getCommentsCount(),
"can_post" => 1 "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) if ($from_id > 0)
$profiles[] = $from_id; $profiles[] = $from_id;
else else
@ -332,17 +315,6 @@ final class Wall extends VKAPIRequestHandler
else else
$profiles[] = $attachment->getOwner()->getId(); $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[] = [ $repost[] = [
"id" => $attachment->getVirtualId(), "id" => $attachment->getVirtualId(),
"owner_id" => $attachment->isPostedOnBehalfOfGroup() ? $attachment->getOwner()->getId() * -1 : $attachment->getOwner()->getId(), "owner_id" => $attachment->isPostedOnBehalfOfGroup() ? $attachment->getOwner()->getId() * -1 : $attachment->getOwner()->getId(),
@ -351,47 +323,29 @@ final class Wall extends VKAPIRequestHandler
"post_type" => "post", "post_type" => "post",
"text" => $attachment->getText(false), "text" => $attachment->getText(false),
"attachments" => $repostAttachments, "attachments" => $repostAttachments,
"post_source" => $post_source, "post_source" => $attachment->getPostSourceInfo(),
]; ];
if ($attachment->getVirtualId() > 0) if ($attachment->getTargetWall() > 0)
$profiles[] = $attachment->getVirtualId(); $profiles[] = $attachment->getTargetWall();
else else
$groups[] = $attachment->getVirtualId(); $groups[] = abs($attachment->getTargetWall());
if($post->isSigned()) if($post->isSigned())
$profiles[] = $attachment->getOwner()->getId(); $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()) { if($post->isSigned()) {
$actualAuthor = $post->getOwner(false); $actualAuthor = $post->getOwner(false);
$signerId = $actualAuthor->getId(); $signerId = $actualAuthor->getId();
} }
$items[] = (object)[ $post_temp_obj = (object)[
"id" => $post->getVirtualId(), "id" => $post->getVirtualId(),
"from_id" => $from_id, "from_id" => $from_id,
"owner_id" => $post->getTargetWall(), "owner_id" => $post->getTargetWall(),
"date" => $post->getPublicationTime()->timestamp(), "date" => $post->getPublicationTime()->timestamp(),
"post_type" => $postType, "post_type" => $post->getVkApiType(),
"text" => $post->getText(false), "text" => $post->getText(false),
"copy_history" => $repost, "copy_history" => $repost,
"can_edit" => $post->canBeEditedBy($this->getUser()), "can_edit" => $post->canBeEditedBy($this->getUser()),
@ -401,8 +355,7 @@ final class Wall extends VKAPIRequestHandler
"is_archived" => false, "is_archived" => false,
"is_pinned" => $post->isPinned(), "is_pinned" => $post->isPinned(),
"is_explicit" => $post->isExplicit(), "is_explicit" => $post->isExplicit(),
"post_source" => $post_source, "post_source" => $post->getPostSourceInfo(),
"signer_id" => $signerId,
"attachments" => $attachments, "attachments" => $attachments,
"comments" => (object)[ "comments" => (object)[
"count" => $post->getCommentsCount(), "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) if ($from_id > 0)
$profiles[] = $from_id; $profiles[] = $from_id;
else else
@ -792,6 +753,9 @@ final class Wall extends VKAPIRequestHandler
] ]
]; ];
if($comment->isFromPostAuthor($post))
$item['is_from_post_author'] = true;
if($need_likes == true) if($need_likes == true)
$item['likes'] = [ $item['likes'] = [
"can_like" => 1, "can_like" => 1,
@ -875,6 +839,9 @@ final class Wall extends VKAPIRequestHandler
] ]
]; ];
if($comment->isFromPostAuthor())
$item['is_from_post_author'] = true;
if($extended == true) if($extended == true)
$profiles[] = $comment->getOwner()->getId(); $profiles[] = $comment->getOwner()->getId();
@ -890,8 +857,6 @@ final class Wall extends VKAPIRequestHandler
$response['profiles'] = (!empty($profiles) ? (new Users)->get(implode(',', $profiles), $fields) : []); $response['profiles'] = (!empty($profiles) ? (new Users)->get(implode(',', $profiles), $fields) : []);
} }
return $response; return $response;
} }

View file

@ -5,7 +5,7 @@ exceptions. It is still a work-in-progress functionality.
**Note**: requests to API are routed through **Note**: requests to API are routed through
openvk.Web.Presenters.VKAPIPresenter, this dir contains only handlers. 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 ## Implementing API methods

View file

@ -17,7 +17,7 @@ class Audio extends Media
# Taken from winamp :D # Taken from winamp :D
const genres = [ 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 # 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(); return $this->getPerformer() . "" . $this->getTitle();
} }
function getDownloadName(): string
{
return preg_replace('/[\\/:*?"<>|]/', '_', str_replace(' ', '_', $this->getName()));
}
function getGenre(): ?string function getGenre(): ?string
{ {
return $this->getRecord()->genre; return $this->getRecord()->genre;

View file

@ -152,6 +152,11 @@ class Club extends RowModel
return (bool) $this->getRecord()->hide_from_global_feed; return (bool) $this->getRecord()->hide_from_global_feed;
} }
function isHidingFromGlobalFeedEnforced(): bool
{
return (bool) $this->getRecord()->enforce_hiding_from_global_feed;
}
function getType(): int function getType(): int
{ {
return $this->getRecord()->type; return $this->getRecord()->type;
@ -432,7 +437,7 @@ class Club extends RowModel
return (new \openvk\Web\Models\Repositories\Audios)->getClubCollectionSize($this); 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) []; $res = (object) [];

View file

@ -103,6 +103,22 @@ class Comment extends Post
return $this->getTarget()->canBeViewedBy($user); 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() function toNotifApiStruct()
{ {
@ -124,4 +140,31 @@ class Comment extends Post
return $user->getId() == $this->getOwner(false)->getId(); 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); $dateTime = new DateTime($this->getRecord()->created);
if($dateTime->format("%d.%m.%y") == ovk_strftime_safe("%d.%m.%y", time())) { if($dateTime->format("%d.%m.%y") == ovk_strftime_safe("%d.%m.%y", time())) {
return $dateTime->format("%T %p"); return $dateTime->format("%T");
} else { } else {
return $dateTime->format("%d.%m.%y"); return $dateTime->format("%d.%m.%y");
} }

View file

@ -114,7 +114,7 @@ class Photo extends Media
return true; 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"])) if(isset($this->changes["hash"]))
$hash = $this->changes["hash"]; $hash = $this->changes["hash"];

View file

@ -41,6 +41,21 @@ class Playlist extends MediaCollection
{ {
return $this->getRecord()->length; 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 function getAudios(int $offset = 0, ?int $limit = NULL, ?int $shuffleSeed = NULL): \Traversable
{ {
@ -162,6 +177,7 @@ class Playlist extends MediaCollection
"bookmarked" => $this->isBookmarkedBy($user), "bookmarked" => $this->isBookmarkedBy($user),
"listens" => $this->getListens(), "listens" => $this->getListens(),
"cover_url" => $this->getCoverURL(), "cover_url" => $this->getCoverURL(),
"searchable" => !$this->isUnlisted(),
]; ];
} }
@ -199,6 +215,11 @@ class Playlist extends MediaCollection
{ {
return $this->getRecord()->cover_photo_id; return $this->getRecord()->cover_photo_id;
} }
function getCoverPhoto(): ?Photo
{
return (new Photos)->get((int) $this->getRecord()->cover_photo_id);
}
function canBeModifiedBy(User $user): bool function canBeModifiedBy(User $user): bool
{ {
@ -253,4 +274,9 @@ class Playlist extends MediaCollection
return implode("", $props); return implode("", $props);
} }
function isUnlisted(): bool
{
return (bool)$this->getRecord()->unlisted;
}
} }

View file

@ -133,6 +133,10 @@ class Post extends Postable
case 'openvk_legacy_ios': case 'openvk_legacy_ios':
return 'iphone'; return 'iphone';
break; break;
case 'windows_phone':
return 'wphone';
break;
case 'vika_touch': // кика хохотач ахахахаххахахахахах case 'vika_touch': // кика хохотач ахахахаххахахахахах
case 'vk4me': case 'vk4me':
@ -175,6 +179,31 @@ class Post extends Postable
"img" => NULL "img" => NULL
]; ];
} }
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 function pin(): void
{ {

View file

@ -80,7 +80,7 @@ class Report extends RowModel
function getAuthor(): RowModel function getAuthor(): RowModel
{ {
return (new Posts)->get($this->getContentId())->getOwner(); return $this->getContentObject()->getOwner();
} }
function getReportAuthor(): User 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) { $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]); $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);
$text = $this->formatEmojis($text); $text = $this->formatEmojis($text);

View file

@ -39,4 +39,25 @@ trait TSubscribable
$sub->delete(); $sub->delete();
return false; 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"); 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 function getSubscriptions(int $page = 1, int $limit = 6): \Traversable
{ {
return $this->_abstractRelationGenerator("get-subscriptions-user", $page, $limit); return $this->_abstractRelationGenerator("get-subscriptions-user", $page, $limit);
@ -1214,6 +1224,11 @@ class User extends RowModel
return (bool) $this->getRecord()->activated; return (bool) $this->getRecord()->activated;
} }
function isDead(): bool
{
return $this->onlineStatus() == 2;
}
function getUnbanTime(): ?string function getUnbanTime(): ?string
{ {
$ban = (new Bans)->get((int) $this->getRecord()->block_reason); $ban = (new Bans)->get((int) $this->getRecord()->block_reason);
@ -1306,17 +1321,22 @@ class User extends RowModel
return true; return true;
} }
function isClosed() function isClosed(): bool
{ {
return (bool) $this->getProfileType(); return (bool) $this->getProfileType();
} }
function isHideFromGlobalFeedEnabled(): bool
{
return $this->isClosed();
}
function getRealId() function getRealId()
{ {
return $this->getId(); return $this->getId();
} }
function toVkApiStruct(?User $user = NULL): object function toVkApiStruct(?User $user = NULL, string $fields = ''): object
{ {
$res = (object) []; $res = (object) [];
@ -1328,12 +1348,21 @@ class User extends RowModel
$res->photo_100 = $this->getAvatarURL("tiny"); $res->photo_100 = $this->getAvatarURL("tiny");
$res->photo_200 = $this->getAvatarURL("normal"); $res->photo_200 = $this->getAvatarURL("normal");
$res->photo_id = !is_null($this->getAvatarPhoto()) ? $this->getAvatarPhoto()->getPrettyId() : NULL; $res->photo_id = !is_null($this->getAvatarPhoto()) ? $this->getAvatarPhoto()->getPrettyId() : NULL;
# TODO: Perenesti syuda vsyo ostalnoyie
$res->is_closed = $this->isClosed(); $res->is_closed = $this->isClosed();
if(!is_null($user)) { if(!is_null($user))
$res->can_access_closed = (bool)$this->canBeViewedBy($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; 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)) { if(preg_match(file_get_contents(__DIR__ . "/../VideoDrivers/regex/youtube.txt"), $link, $matches)) {
$pointer = "YouTube:$matches[1]"; $pointer = "YouTube:$matches[1]";
} else if(preg_match(file_get_contents(__DIR__ . "/../VideoDrivers/regex/vimeo.txt"), $link, $matches)) { /*} else if(preg_match(file_get_contents(__DIR__ . "/../VideoDrivers/regex/vimeo.txt"), $link, $matches)) {
$pointer = "Vimeo:$matches[1]"; $pointer = "Vimeo:$matches[1]";*/
} else { } else {
throw new ISE("Invalid link"); throw new ISE("Invalid link");
} }

View file

@ -23,4 +23,13 @@ class APITokens extends Repository
return $token; 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())); 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%"; $query = "%$query%";
$result = $this->apps->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("enabled", 1); $result = $this->apps->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("enabled", 1);
$order_str = 'id';
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->order("$sort")); return new Util\EntityStream("Application", $result);
} }
} }

View file

@ -208,7 +208,7 @@ class Audios
$search = $this->audios->where([ $search = $this->audios->where([
"unlisted" => 0, "unlisted" => 0,
"deleted" => 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) if($withLyrics)
$search = $search->where("lyrics IS NOT NULL"); $search = $search->where("lyrics IS NOT NULL");
@ -219,6 +219,7 @@ class Audios
function searchPlaylists(string $query): EntityStream function searchPlaylists(string $query): EntityStream
{ {
$search = $this->playlists->where([ $search = $this->playlists->where([
"unlisted" => 0,
"deleted" => 0, "deleted" => 0,
])->where("MATCH (`name`, `description`) AGAINST (? IN BOOLEAN MODE)", $query); ])->where("MATCH (`name`, `description`) AGAINST (? IN BOOLEAN MODE)", $query);
@ -243,53 +244,72 @@ class Audios
])->fetch()); ])->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%"; $query = "%$query%";
$result = $this->audios->where([ $result = $this->audios->where([
"unlisted" => 0, "unlisted" => 0,
"deleted" => 0, "deleted" => 0,
]); ]);
$order_str = (in_array($order['type'], ['id', 'length', 'listens']) ? $order['type'] : 'id') . ' ' . ($order['invert'] ? 'ASC' : 'DESC');;
$notNullParams = []; if($params["only_performers"] == "1") {
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") {
$result->where("performer LIKE ?", $query); $result->where("performer LIKE ?", $query);
} else { } else {
$result->where("name LIKE ? OR performer LIKE ?", $query, $query); $result->where("name LIKE ? OR performer LIKE ?", $query, $query);
} }
if($nnparamsCount > 0) { foreach($params as $paramName => $paramValue) {
foreach($notNullParams as $paramName => $paramValue) { if(is_null($paramValue) || $paramValue == '') continue;
switch($paramName) {
case "before": switch($paramName) {
$result->where("created < ?", $paramValue); case "before":
break; $result->where("created < ?", $paramValue);
case "after": break;
$result->where("created > ?", $paramValue); case "after":
break; $result->where("created > ?", $paramValue);
case "with_lyrics": break;
$result->where("lyrics IS NOT NULL"); case "with_lyrics":
break; $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%"; $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); return new Util\EntityStream("Playlist", $result);
} }

View file

@ -42,18 +42,30 @@ class Clubs
{ {
return $this->toClub($this->clubs->get($id)); 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%"; $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 function getCount(): int
{ {
return sizeof(clone $this->clubs); return (clone $this->clubs)->count('*');
} }
function getPopularClubs(): \Traversable function getPopularClubs(): \Traversable

View file

@ -60,34 +60,31 @@ 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) foreach($params as $paramName => $paramValue) {
if($paramName != "before" && $paramName != "after") switch($paramName) {
$paramValue != NULL ? $notNullParams+=["$paramName" => "%$paramValue%"] : NULL; case "before":
else $result->where("created < ?", $paramValue);
$paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL; break;
case "after":
$result = $this->comments->where("content LIKE ?", $query)->where("deleted", 0); $result->where("created > ?", $paramValue);
$nnparamsCount = sizeof($notNullParams); break;
if($nnparamsCount > 0) {
foreach($notNullParams as $paramName => $paramValue) {
switch($paramName) {
case "before":
$result->where("created < ?", $paramValue);
break;
case "after":
$result->where("created > ?", $paramValue);
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); 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 = "SELECT " . ($count ? "COUNT(*) AS cnt" : "*") . " FROM notifications WHERE recipientType=0 ";
$query .= "AND timestamp " . ($archived ? "<" : ">") . "$offset AND recipientId=" . $user->getId(); $query .= "AND timestamp " . ($archived ? "<" : ">") . "$offset AND recipientId=" . $user->getId();

View file

@ -154,36 +154,45 @@ 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%"; $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); $result = $this->posts->where("content LIKE ?", $query)->where("deleted", 0)->where("suggested", 0);
$nnparamsCount = sizeof($notNullParams); $order_str = 'id';
if($nnparamsCount > 0) { switch($order['type']) {
foreach($notNullParams as $paramName => $paramValue) { case 'id':
switch($paramName) { $order_str = 'created ' . ($order['invert'] ? 'ASC' : 'DESC');
case "before": break;
$result->where("created < ?", $paramValue); }
break;
case "after": foreach($params as $paramName => $paramValue) {
$result->where("created > ?", $paramValue); if(is_null($paramValue) || $paramValue == '') continue;
break;
} switch($paramName) {
case "before":
$result->where("created < ?", $paramValue);
break;
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 function getPostCountOnUserWall(int $user): int
@ -252,6 +261,6 @@ class Posts
function getCount(): int function getCount(): int
{ {
return sizeof(clone $this->posts); return (clone $this->posts)->count('*');
} }
} }

View file

@ -44,107 +44,94 @@ class Users
return $alias->getUser(); 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
{ {
$query = "%$query%"; 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); $result = $this->users->where("CONCAT_WS(' ', first_name, last_name, pseudo, shortcode) LIKE ?", $query)->where("deleted", 0);
$order_str = 'id';
$notNullParams = [];
$nnparamsCount = 0;
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;
$nnparamsCount = sizeof($notNullParams); switch($order['type']) {
case 'id':
case 'reg_date':
$order_str = 'id ' . ($order['invert'] ? 'ASC' : 'DESC');
break;
case 'rating':
$order_str = 'rating DESC';
break;
}
if($nnparamsCount > 0) { foreach($params as $paramName => $paramValue) {
foreach($notNullParams as $paramName => $paramValue) { if(is_null($paramValue) || $paramValue == '') continue;
switch($paramName) {
case "hometown": switch($paramName) {
$result->where("hometown LIKE ?", $paramValue); case "hometown":
break; $result->where("hometown LIKE ?", "%$paramValue%");
case "city": break;
$result->where("city LIKE ?", $paramValue); case "city":
break; $result->where("city LIKE ?", "%$paramValue%");
case "maritalstatus": break;
$result->where("marital_status ?", $paramValue); case "marital_status":
break; $result->where("marital_status ?", $paramValue);
case "status": break;
$result->where("status LIKE ?", $paramValue); case "polit_views":
break; $result->where("polit_views ?", $paramValue);
case "politViews": break;
$result->where("polit_views ?", $paramValue); case "is_online":
break; $result->where("online >= ?", time() - 900);
case "email": break;
$result->where("email_contact LIKE ?", $paramValue); case "fav_mus":
break; $result->where("fav_music LIKE ?", "%$paramValue%");
case "telegram": break;
$result->where("telegram LIKE ?", $paramValue); case "fav_films":
break; $result->where("fav_films LIKE ?", "%$paramValue%");
case "site": break;
$result->where("telegram LIKE ?", $paramValue); case "fav_shows":
break; $result->where("fav_shows LIKE ?", "%$paramValue%");
case "address": break;
$result->where("address LIKE ?", $paramValue); case "fav_books":
break; $result->where("fav_books LIKE ?", "%$paramValue%");
case "is_online": break;
$result->where("online >= ?", time() - 900); case "before":
break; $result->where("UNIX_TIMESTAMP(since) < ?", $paramValue);
case "interests": break;
$result->where("interests LIKE ?", $paramValue); case "after":
break; $result->where("UNIX_TIMESTAMP(since) > ?", $paramValue);
case "fav_mus": break;
$result->where("fav_music LIKE ?", $paramValue); case "gender":
break; if((int) $paramValue == 3) break;
case "fav_films": $result->where("sex ?", (int) $paramValue);
$result->where("fav_films LIKE ?", $paramValue); break;
break; case "ignore_id":
case "fav_shows": $result->where("id != ?", $paramValue);
$result->where("fav_shows LIKE ?", $paramValue); break;
break; case "ignore_private":
case "fav_books": $result->where("profile_type", 0);
$result->where("fav_books LIKE ?", $paramValue); break;
break;
case "fav_quote":
$result->where("fav_quote LIKE ?", $paramValue);
break;
case "before":
$result->where("UNIX_TIMESTAMP(since) < ?", $paramValue);
break;
case "after":
$result->where("UNIX_TIMESTAMP(since) > ?", $paramValue);
break;
case "gender":
$result->where("sex ?", $paramValue);
break;
case "doNotSearchMe":
$result->where("id !=", $paramValue);
break;
case "doNotSearchPrivate":
$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 function getStatistics(): object
{ {
return (object) [ return (object) [
"all" => sizeof(clone $this->users), "all" => (clone $this->users)->count('*'),
"active" => sizeof((clone $this->users)->where("online > 0")), "active" => (clone $this->users)->where("online > 0")->count('*'),
"online" => sizeof((clone $this->users)->where("online >= ?", time() - 900)), "online" => (clone $this->users)->where("online >= ?", time() - 900)->count('*'),
]; ];
} }

View file

@ -46,36 +46,37 @@ class Videos
return sizeof($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])); 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%"; $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); $result = $this->videos->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("deleted", 0);
$nnparamsCount = sizeof($notNullParams); $order_str = 'id';
if($nnparamsCount > 0) { switch($order['type']) {
foreach($notNullParams as $paramName => $paramValue) { case 'id':
switch($paramName) { $order_str = 'id ' . ($order['invert'] ? 'ASC' : 'DESC');
case "before": break;
$result->where("created < ?", $paramValue); }
break;
case "after": foreach($params as $paramName => $paramValue) {
$result->where("created > ?", $paramValue); switch($paramName) {
break; case "before":
} $result->where("created < ?", $paramValue);
break;
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) function getLastVideo(User $user)

View file

@ -1,5 +1,5 @@
(SELECT DISTINCT(follower) AS __id FROM (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 LEFT JOIN
(SELECT target FROM subscriptions WHERE follower=? AND model="openvk\\Web\\Models\\Entities\\User") u1 (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 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 function renderDev(): void
{ {
$this->redirect("https://docs.openvk.uk/"); $this->redirect("https://docs.ovk.to/");
} }
} }

View file

@ -48,6 +48,13 @@ final class AdminPresenter extends OpenVKPresenter
if(!OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"]) if(!OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"])
$this->flash("warn", tr("admin_commerce_disabled"), tr("admin_commerce_disabled_desc")); $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) private function searchResults(object $repo, &$count)
{ {
@ -76,7 +83,7 @@ final class AdminPresenter extends OpenVKPresenter
function renderIndex(): void function renderIndex(): void
{ {
$this->warnIfLongpoolBroken();
} }
function renderUsers(): void function renderUsers(): void
@ -154,6 +161,7 @@ final class AdminPresenter extends OpenVKPresenter
$club->setShortCode($this->postParam("shortcode")); $club->setShortCode($this->postParam("shortcode"));
$club->setVerified(empty($this->postParam("verify") ? 0 : 1)); $club->setVerified(empty($this->postParam("verify") ? 0 : 1));
$club->setHide_From_Global_Feed(empty($this->postParam("hide_from_global_feed") ? 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(); $club->save();
break; break;
case "ban": case "ban":
@ -681,7 +689,8 @@ final class AdminPresenter extends OpenVKPresenter
$this->template->obj_type = $obj_type; $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(); $this->template->object_types = (new Logs)->getTypes();
} }
} }

View file

@ -75,7 +75,7 @@ final class AudioPresenter extends OpenVKPresenter
if (!$entity || $entity->isBanned()) if (!$entity || $entity->isBanned())
$this->redirect("/playlists" . $this->user->id); $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); $playlistsCount = $this->audios->getClubPlaylistsCount($entity);
} else { } else {
$entity = (new Users)->get($owner); $entity = (new Users)->get($owner);
@ -85,7 +85,7 @@ final class AudioPresenter extends OpenVKPresenter
if(!$entity->getPrivacyPermission("audios.read", $this->user->identity)) if(!$entity->getPrivacyPermission("audios.read", $this->user->identity))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment")); $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); $playlistsCount = $this->audios->getUserPlaylistsCount($entity);
} }
@ -109,8 +109,8 @@ final class AudioPresenter extends OpenVKPresenter
$this->template->mode = $mode; $this->template->mode = $mode;
$this->template->page = $page; $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); $this->template->friendsAudios = $this->user->identity->getBroadcastList("all", true);
} }
@ -142,7 +142,13 @@ final class AudioPresenter extends OpenVKPresenter
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
$group = NULL; $group = NULL;
$playlist = NULL;
$isAjax = $this->postParam("ajax", false) == 1; $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"))) { if(!is_null($this->queryParam("gid"))) {
$gid = (int) $this->queryParam("gid"); $gid = (int) $this->queryParam("gid");
$group = (new Clubs)->get($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); $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; $this->template->group = $group;
if($_SERVER["REQUEST_METHOD"] !== "POST") if($_SERVER["REQUEST_METHOD"] !== "POST")
@ -196,6 +215,8 @@ final class AudioPresenter extends OpenVKPresenter
$lyrics = $this->postParam("lyrics"); $lyrics = $this->postParam("lyrics");
$genre = empty($this->postParam("genre")) ? "Other" : $this->postParam("genre"); $genre = empty($this->postParam("genre")) ? "Other" : $this->postParam("genre");
$nsfw = ($this->postParam("explicit") ?? "off") === "on"; $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 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); $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->setLyrics(empty($lyrics) ? NULL : $lyrics);
$audio->setGenre($genre); $audio->setGenre($genre);
$audio->setExplicit($nsfw); $audio->setExplicit($nsfw);
$audio->setUnlisted($is_unlisted);
try { try {
$audio->setFile($upload); $audio->setFile($upload);
@ -215,13 +237,18 @@ final class AudioPresenter extends OpenVKPresenter
} catch(\RuntimeException $ex) { } catch(\RuntimeException $ex) {
$this->flashFail("err", tr("error"), tr("ffmpeg_timeout"), null, $isAjax); $this->flashFail("err", tr("error"), tr("ffmpeg_timeout"), null, $isAjax);
} catch(\BadMethodCallException $ex) { } 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) { } catch(\Exception $ex) {
$this->flashFail("err", tr("error"), tr("ffmpeg_not_installed"), null, $isAjax); $this->flashFail("err", tr("error"), tr("ffmpeg_not_installed"), null, $isAjax);
} }
$audio->save(); $audio->save();
$audio->add($group ?? $this->user->identity);
if($playlist) {
$playlist->add($audio);
} else {
$audio->add($group ?? $this->user->identity);
}
if(!$isAjax) if(!$isAjax)
$this->redirect(is_null($group) ? "/audios" . $this->user->id : "/audios-" . $group->getId()); $this->redirect(is_null($group) ? "/audios" . $this->user->id : "/audios-" . $group->getId());
@ -233,9 +260,9 @@ final class AudioPresenter extends OpenVKPresenter
else else
$redirectLink .= $this->user->id; $redirectLink .= $this->user->id;
$pagesCount = (int)ceil((new Audios)->getCollectionSizeByEntityId(isset($group) ? $group->getRealId() : $this->user->id) / 10); if($playlist)
$redirectLink .= "?p=".$pagesCount; $redirectLink = "/playlist" . $playlist->getPrettyId();
$this->returnJson([ $this->returnJson([
"success" => true, "success" => true,
"redirect_link" => $redirectLink, "redirect_link" => $redirectLink,
@ -279,7 +306,7 @@ final class AudioPresenter extends OpenVKPresenter
function renderSearch(): void function renderSearch(): void
{ {
$this->redirect("/search?type=audios"); $this->redirect("/search?section=audios");
} }
function renderNewPlaylist(): void function renderNewPlaylist(): void
@ -304,6 +331,8 @@ final class AudioPresenter extends OpenVKPresenter
if ($_SERVER["REQUEST_METHOD"] === "POST") { if ($_SERVER["REQUEST_METHOD"] === "POST") {
$title = $this->postParam("title"); $title = $this->postParam("title");
$description = $this->postParam("description"); $description = $this->postParam("description");
$is_unlisted = (int)$this->postParam('is_unlisted');
$audios = !empty($this->postParam("audios")) ? array_slice(explode(",", $this->postParam("audios")), 0, 1000) : []; $audios = !empty($this->postParam("audios")) ? array_slice(explode(",", $this->postParam("audios")), 0, 1000) : [];
if(empty($title) || iconv_strlen($title) < 1) if(empty($title) || iconv_strlen($title) < 1)
@ -313,7 +342,9 @@ final class AudioPresenter extends OpenVKPresenter
$playlist->setOwner($owner); $playlist->setOwner($owner);
$playlist->setName(substr($title, 0, 125)); $playlist->setName(substr($title, 0, 125));
$playlist->setDescription(substr($description, 0, 2045)); $playlist->setDescription(substr($description, 0, 2045));
if($is_unlisted == 1)
$playlist->setUnlisted(true);
if($_FILES["cover"]["error"] === UPLOAD_ERR_OK) { if($_FILES["cover"]["error"] === UPLOAD_ERR_OK) {
if(!str_starts_with($_FILES["cover"]["type"], "image")) if(!str_starts_with($_FILES["cover"]["type"], "image"))
$this->flashFail("err", tr("error"), tr("not_a_photo")); $this->flashFail("err", tr("error"), tr("not_a_photo"));
@ -427,6 +458,7 @@ final class AudioPresenter extends OpenVKPresenter
$title = $this->postParam("title"); $title = $this->postParam("title");
$description = $this->postParam("description"); $description = $this->postParam("description");
$is_unlisted = (int)$this->postParam('is_unlisted');
$new_audios = !empty($this->postParam("audios")) ? explode(",", rtrim($this->postParam("audios"), ",")) : []; $new_audios = !empty($this->postParam("audios")) ? explode(",", rtrim($this->postParam("audios"), ",")) : [];
if(empty($title) || iconv_strlen($title) < 1) if(empty($title) || iconv_strlen($title) < 1)
@ -436,6 +468,7 @@ final class AudioPresenter extends OpenVKPresenter
$playlist->setDescription(ovk_proc_strtr($description, 2045)); $playlist->setDescription(ovk_proc_strtr($description, 2045));
$playlist->setEdited(time()); $playlist->setEdited(time());
$playlist->resetLength(); $playlist->resetLength();
$playlist->setUnlisted((bool)$is_unlisted);
if($_FILES["new_cover"]["error"] === UPLOAD_ERR_OK) { if($_FILES["new_cover"]["error"] === UPLOAD_ERR_OK) {
if(!str_starts_with($_FILES["new_cover"]["type"], "image")) if(!str_starts_with($_FILES["new_cover"]["type"], "image"))
@ -475,12 +508,15 @@ final class AudioPresenter extends OpenVKPresenter
$this->template->playlist = $playlist; $this->template->playlist = $playlist;
$this->template->page = $page; $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->audios = iterator_to_array($playlist->fetch($page, 10));
$this->template->ownerId = $owner_id; $this->template->ownerId = $owner_id;
$this->template->owner = $playlist->getOwner(); $this->template->owner = $playlist->getOwner();
$this->template->isBookmarked = $this->user->identity && $playlist->isBookmarkedBy($this->user->identity); $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->isMy = $this->user->identity && $playlist->getOwner()->getId() === $this->user->id;
$this->template->canEdit = $this->user->identity && $playlist->canBeModifiedBy($this->user->identity); $this->template->canEdit = $this->user->identity && $playlist->canBeModifiedBy($this->user->identity);
$this->template->count = $playlist->size();
} }
function renderAction(int $audio_id): void function renderAction(int $audio_id): void
@ -531,16 +567,65 @@ final class AudioPresenter extends OpenVKPresenter
break; break;
case "add_to_club": case "add_to_club":
$club = (new Clubs)->get((int)$this->postParam("club")); $detailed = [];
if($audio->isWithdrawn())
if(!$club || !$club->canBeModifiedBy($this->user->identity)) $this->flashFail("err", "error", tr("invalid_audio"), null, true);
$this->flashFail("err", "error", tr("access_denied"), null, true);
if(empty($this->postParam("clubs")))
if(!$audio->isInLibraryOf($club)) $this->flashFail("err", "error", 'clubs not passed', null, true);
$audio->add($club);
else $clubs_arr = explode(',', $this->postParam("clubs"));
$this->flashFail("err", "error", tr("group_has_audio"), null, true); $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))
continue;
if(!$audio->isInLibraryOf($club)) {
$detailed[$club_id] = true;
$audio->add($club);
} 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; break;
case "delete": case "delete":
if($audio->canBeModifiedBy($this->user->identity)) if($audio->canBeModifiedBy($this->user->identity))
@ -653,6 +738,28 @@ final class AudioPresenter extends OpenVKPresenter
$audios = $stream->page($page, 10); $audios = $stream->page($page, 10);
$audiosCount = $stream->size(); $audiosCount = $stream->size();
break; 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); $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->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->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->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") ?? ""; $website = $this->postParam("website") ?? "";
if(empty($website)) if(empty($website))
@ -279,48 +282,99 @@ final class GroupPresenter extends OpenVKPresenter
function renderSetAvatar(int $id) function renderSetAvatar(int $id)
{ {
$photo = new Photo; $this->assertUserLoggedIn();
$this->willExecuteWriteAction();
$club = $this->clubs->get($id); $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 { try {
$photo = new Photo;
$anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"]; $anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"];
if($anon && $this->user->id === $club->getOwner()->getId()) if($anon && $this->user->id === $club->getOwner()->getId())
$anon = $club->isOwnerHidden(); $anon = $club->isOwnerHidden();
else if($anon) else if($anon)
$anon = $club->getManager($this->user->identity)->isHidden(); $anon = $club->getManager($this->user->identity)->isHidden();
$photo->setOwner($this->user->id); $photo->setOwner($this->user->id);
$photo->setDescription("Club image"); $photo->setDescription("Club image");
$photo->setFile($_FILES["ava"]); $photo->setFile($_FILES["blob"]);
$photo->setCreated(time()); $photo->setCreated(time());
$photo->setAnonymous($anon); $photo->setAnonymous($anon);
$photo->save(); $photo->save();
(new Albums)->getClubAvatarAlbum($club)->addPhoto($photo); (new Albums)->getClubAvatarAlbum($club)->addPhoto($photo);
$flags = 0; if($this->postParam("on_wall") == 1) {
$flags |= 0b00010000; $post = new Post;
$flags |= 0b10000000;
$post->setOwner($this->user->id);
$post->setWall($club->getId() * -1);
$post->setCreated(time());
$post->setContent("");
$post = new Post; $flags = 0;
$post->setOwner($this->user->id); $flags |= 0b00010000;
$post->setWall($club->getId()*-1); $flags |= 0b10000000;
$post->setCreated(time());
$post->setContent("");
$post->setFlags($flags);
$post->save();
$post->attach($photo);
} catch(ISE $ex) { $post->setFlags($flags);
$name = $album->getName(); $post->save();
$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(),
]);
} else {
return " ";
} }
$this->returnJson([
"url" => $photo->getURL(),
"id" => $photo->getPrettyId()
]);
} }
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 function renderEditBackdrop(int $id): void
{ {
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();

View file

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

View file

@ -1,7 +1,7 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\Web\Presenters; namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{User, Club}; 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; use Chandler\Database\DatabaseConnection;
final class SearchPresenter extends OpenVKPresenter final class SearchPresenter extends OpenVKPresenter
@ -9,21 +9,17 @@ final class SearchPresenter extends OpenVKPresenter
private $users; private $users;
private $clubs; private $clubs;
private $posts; private $posts;
private $comments;
private $videos; private $videos;
private $apps; private $apps;
private $notes;
private $audios; private $audios;
function __construct(Users $users, Clubs $clubs) function __construct()
{ {
$this->users = $users; $this->users = new Users;
$this->clubs = $clubs; $this->clubs = new Clubs;
$this->posts = new Posts; $this->posts = new Posts;
$this->comments = new Comments;
$this->videos = new Videos; $this->videos = new Videos;
$this->apps = new Applications; $this->apps = new Applications;
$this->notes = new Notes;
$this->audios = new Audios; $this->audios = new Audios;
parent::__construct(); parent::__construct();
@ -33,85 +29,101 @@ final class SearchPresenter extends OpenVKPresenter
{ {
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
$query = $this->queryParam("query") ?? ""; $query = $this->queryParam("q") ?? "";
$type = $this->queryParam("type") ?? "users"; $section = $this->queryParam("section") ?? "users";
$sorter = $this->queryParam("sort") ?? "id"; $order = $this->queryParam("order") ?? "id";
$invert = $this->queryParam("invert") == 1 ? "ASC" : "DESC"; $invert = (int) ($this->queryParam("invert") ?? 0) == 1;
$page = (int) ($this->queryParam("p") ?? 1); $page = (int) ($this->queryParam("p") ?? 1);
# https://youtu.be/pSAWM5YuXx8 # https://youtu.be/pSAWM5YuXx8
# https://youtu.be/FfNZRhIn2Vk
$repos = [ $repos = [
"groups" => "clubs", "groups" => "clubs",
"users" => "users", "users" => "users",
"posts" => "posts", "posts" => "posts",
"comments" => "comments",
"videos" => "videos", "videos" => "videos",
"audios" => "audios", "audios" => "audios",
"apps" => "apps", "apps" => "apps",
"notes" => "notes" "audios_playlists" => "audios"
];
$parameters = [
"ignore_private" => true,
]; ];
switch($sorter) { foreach($_REQUEST as $param_name => $param_value) {
default: if(is_null($param_value)) continue;
case "id":
$sort = "id " . $invert; switch($param_name) {
break; default:
case "name": $parameters[$param_name] = $param_value;
$sort = "first_name " . $invert; break;
break; case 'marital_status':
case "rating": case 'polit_views':
$sort = "rating " . $invert; if((int) $param_value == 0) continue;
break; $parameters[$param_name] = $param_value;
case "length":
if($type != "audios") break; break;
case 'is_online':
$sort = "length " . $invert; if((int) $param_value == 1)
break; $parameters['is_online'] = 1;
case "listens":
if($type != "audios") break; break;
case 'only_performers':
$sort = "listens " . $invert; if((int) $param_value == 1 || $param_value == 'on')
break; $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;
}
} }
$parameters = [ $repo = $repos[$section] or $this->throwError(400, "Bad Request", "Invalid search entity $section.");
"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); $results = NULL;
$iterator = $results->page($page, 14); 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;
}
$iterator = $results->page($page, OPENVK_DEFAULT_PER_PAGE);
$count = $results->size(); $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->count = $count;
$this->template->type = $type; $this->template->section = $section;
$this->template->page = $page; $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")); $user = $this->users->get((int) $this->postParam("id"));
if(!$user) exit("Invalid state"); if(!$user) exit("Invalid state");
$user->toggleSubscription($this->user->identity); 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->assertUserLoggedIn();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
@ -346,8 +348,8 @@ final class UserPresenter extends OpenVKPresenter
$photo->setFile($_FILES["blob"]); $photo->setFile($_FILES["blob"]);
$photo->setCreated(time()); $photo->setCreated(time());
$photo->save(); $photo->save();
} catch(ISE $ex) { } catch(\Throwable $ex) {
$this->flashFail("err", tr("error"), tr("error_upload_failed")); $this->flashFail("err", tr("error"), tr("error_upload_failed"), NULL, (int)$this->postParam("ajax", true) == 1);
} }
$album = (new Albums)->getUserAvatarAlbum($this->user->identity); $album = (new Albums)->getUserAvatarAlbum($this->user->identity);
@ -358,23 +360,57 @@ final class UserPresenter extends OpenVKPresenter
$flags = 0; $flags = 0;
$flags |= 0b00010000; $flags |= 0b00010000;
$post = new Post; if($this->postParam("on_wall") == 1) {
$post->setOwner($this->user->id); $post = new Post;
$post->setWall($this->user->id); $post->setOwner($this->user->id);
$post->setCreated(time()); $post->setWall($this->user->id);
$post->setContent(""); $post->setCreated(time());
$post->setFlags($flags); $post->setContent("");
$post->save(); $post->setFlags($flags);
$post->attach($photo); $post->save();
if($this->postParam("ava", true) == (int)1) {
$post->attach($photo);
}
if((int)$this->postParam("ajax", true) == 1) {
$this->returnJson([ $this->returnJson([
"url" => $photo->getURL(), "success" => true,
"id" => $photo->getPrettyId() "new_photo" => $photo->getPrettyId(),
"url" => $photo->getURL(),
]); ]);
} else { } else {
$this->flashFail("succ", tr("photo_saved"), tr("photo_saved_comment")); $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 function renderSettings(): void
{ {

View file

@ -186,8 +186,12 @@ final class VKAPIPresenter extends OpenVKPresenter
function renderRoute(string $object, string $method): void function renderRoute(string $object, string $method): void
{ {
$callback = $this->queryParam("callback");
$authMechanism = $this->queryParam("auth_mechanism") ?? "token"; $authMechanism = $this->queryParam("auth_mechanism") ?? "token";
if($authMechanism === "roaming") { if($authMechanism === "roaming") {
if($callback)
$this->fail(-1, "User authorization failed: roaming mechanism is unavailable with jsonp.", $object, $method);
if(!$this->user->identity) if(!$this->user->identity)
$this->fail(5, "User authorization failed: roaming mechanism is selected, but user is not logged in.", $object, $method); $this->fail(5, "User authorization failed: roaming mechanism is selected, but user is not logged in.", $object, $method);
else else
@ -234,8 +238,14 @@ final class VKAPIPresenter extends OpenVKPresenter
} }
try { try {
settype($val, $parameter->getType()->getName()); // Проверка типа параметра
$params[] = $val; $type = $parameter->getType();
if (($type && !$type->isBuiltin()) || is_null($val)) {
$params[] = $val;
} else {
settype($val, $parameter->getType()->getName());
$params[] = $val;
}
} catch (\Throwable $e) { } catch (\Throwable $e) {
// Just ignore the exception, since // Just ignore the exception, since
// some args are intended for internal use // some args are intended for internal use
@ -253,10 +263,16 @@ final class VKAPIPresenter extends OpenVKPresenter
$result = json_encode([ $result = json_encode([
"response" => $res, "response" => $res,
]); ]);
if($callback) {
$result = $callback . '(' . $result . ')';
header('Content-Type: application/javascript');
} else
header("Content-Type: application/json");
$size = strlen($result); $size = strlen($result);
header("Content-Type: application/json");
header("Content-Length: $size"); header("Content-Length: $size");
exit($result); exit($result);
} }
@ -286,17 +302,31 @@ final class VKAPIPresenter extends OpenVKPresenter
$this->fail(28, "Invalid 2FA code", "internal", "acquireToken"); $this->fail(28, "Invalid 2FA code", "internal", "acquireToken");
} }
$platform = $this->requestParam("client_name"); $token = NULL;
$tokenIsStale = true;
$token = new APIToken; $platform = $this->requestParam("client_name");
$token->setUser($user); $acceptsStale = $this->requestParam("accepts_stale");
$token->setPlatform($platform ?? (new WhichBrowser\Parser(getallheaders()))->toString()); if($acceptsStale == "1") {
$token->save(); 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([ $payload = json_encode([
"access_token" => $token->getFormattedToken(), "access_token" => $token->getFormattedToken(),
"expires_in" => 0, "expires_in" => 0,
"user_id" => $uId, "user_id" => $uId,
"is_stale" => $tokenIsStale,
]); ]);
$size = strlen($payload); $size = strlen($payload);
@ -304,4 +334,42 @@ final class VKAPIPresenter extends OpenVKPresenter
header("Content-Length: $size"); header("Content-Length: $size");
exit($payload); 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/l10n.js"}
{script "js/openvk.cls.js"} {script "js/openvk.cls.js"}
{script "js/node_modules/dashjs/dist/dash.all.min.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"} {script "js/al_music.js"}
{css "js/node_modules/tippy.js/dist/backdrop.css"} {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/border.css"}
{css "js/node_modules/tippy.js/dist/svg-arrow.css"} {css "js/node_modules/tippy.js/dist/svg-arrow.css"}
{css "js/node_modules/tippy.js/themes/light.css"} {css "js/node_modules/tippy.js/themes/light.css"}
@ -85,7 +87,7 @@
<div class="layout"> <div class="layout">
<div id="xhead" class="dm"></div> <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> <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"> <div n:if="isset($thisUser) ? (!$thisUser->isBanned() XOR !$thisUser->isActivated()) : true" class="header_navigation">
{ifset $thisUser} {ifset $thisUser}
@ -94,67 +96,45 @@
<a href="/logout?hash={urlencode($csrfToken)}">{_header_log_out}</a> <a href="/logout?hash={urlencode($csrfToken)}">{_header_log_out}</a>
</div> </div>
{else} {else}
<div class="link dec"> <div class="link">
<a href="/">{_header_home}</a> <a href="/">{_header_home}</a>
</div> </div>
<div class="link dec"> <div class="link">
<a href="/search?type=groups">{_header_groups}</a> <a href="/search?section=groups">{_header_groups}</a>
</div> </div>
<div class="link dec"> <div class="link">
<a href="/search">{_header_search}</a> <a href="/search?q=&section=users&order=rating">{_header_search}</a>
</div> </div>
<div class="link dec"> <div class="link">
<a href="/invite">{_header_invite}</a> <a href="/invite">{_header_invite}</a>
</div> </div>
<div class="link dec"> <div class="link">
<a href="/support">{_header_help} <b n:if="$ticketAnsweredCount > 0">({$ticketAnsweredCount})</b></a> <a href="/support">{_header_help} <b n:if="$ticketAnsweredCount > 0">({$ticketAnsweredCount})</b></a>
</div> </div>
<div class="link dec"> <div class="link">
<a href="/logout?hash={urlencode($csrfToken)}">{_header_log_out}</a> <a href="/logout?hash={urlencode($csrfToken)}">{_header_log_out}</a>
</div> </div>
{var $atSearch = str_contains($_SERVER['REQUEST_URI'], "/search")} <div id="search_box" class='header_divider_stick'>
<div id="srch" class="{if $atSearch}nodivider{else}link{/if}"> <div id="search_box_fr">
<form id='search_form' action="/search" method="get">
{if !$atSearch} <div id='search_and_one_more_wrapper'>
<form action="/search" method="get" id="searcher" style="position:relative;"> <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" />
<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 name="section">
<select onchange="checkSearchTips()" id="typer" name="type" class="whatFind" style="display:none;top: 0px;"> <option n:attr="selected => $_REQUEST['section'] == 'users'" value="users">{_s_by_people}</option>
<option value="users">{_s_by_people}</option> <option n:attr="selected => $_REQUEST['section'] == 'groups'" value="groups">{_s_by_groups}</option>
<option value="groups">{_s_by_groups}</option> <option n:attr="selected => $_REQUEST['section'] == 'posts'" value="posts">{_s_by_posts}</option>
<option value="posts">{_s_by_posts}</option> <option n:attr="selected => $_REQUEST['section'] == 'videos'" value="videos">{_s_by_videos}</option>
<option value="comments">{_s_by_comments}</option> <option n:attr="selected => $_REQUEST['section'] == 'apps'" value="apps">{_s_by_apps}</option>
<option value="videos">{_s_by_videos}</option> <option n:attr="selected => $_REQUEST['section'] == 'audios'" value="audios">{_s_by_audios}</option>
<option value="apps">{_s_by_apps}</option> <option n:attr="selected => $_REQUEST['section'] == 'audios_playlists'" value="audios_playlists">{_s_by_audios_playlists}</option>
<option value="audios">{_s_by_audios}</option> </select>
</select> </div>
<button class="search_box_button">
<span>{_header_search}</span>
</button>
</form> </form>
<div class="searchTips" id="srcht" hidden> </div>
<table style="border:none;border-spacing: 0;"> <div id="searchBoxFastTips"></div>
<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>
</form>
<script>
let els = document.querySelectorAll("div.dec")
for(const element of els) {
element.style.display = "none"
}
</script>
{/if}
</div> </div>
{/if} {/if}
{else} {else}
@ -178,9 +158,9 @@
<a href="/edit" class="link edit-button">{_edit_button}</a> <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="{$thisUser->getURL()}" class="link" title="{_my_page} [Alt+Shift+.]" accesskey=".">{_my_page}</a>
<a href="/friends{$thisUser->getId()}" class="link">{_my_friends} <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"> <a href="/friends{$thisUser->getId()}?act=incoming" class="linkunderline">
(<b>{$thisUser->getFollowersCount()}</b>) (<b>{$thisUser->getRequestsCount()}</b>)
</a> </a>
</object> </object>
</a> </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('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 n:if="$thisUser->getLeftMenuItemStatus('news')" href="/feed" class="link" title="{_my_feed} [Alt+Shift+,]" accesskey=",">{_my_feed}</a>
<a href="/notifications" class="link" title="{_my_feedback} [Alt+Shift+N]" accesskey="n">{_my_feedback} <a href="/notifications" class="link" title="{_my_feedback} [Alt+Shift+N]" accesskey="n">{_my_feedback}
{if $thisUser->getNotificationsCount() > 0} <object type="internal/link" n:if="$thisUser->getNotificationsCount() > 0">
(<b>{$thisUser->getNotificationsCount()}</b>) (<b>{$thisUser->getNotificationsCount()}</b>)
{/if} </object>
</a> </a>
<a n:if="$thisUser->getLeftMenuItemStatus('apps')" href="/apps?act=installed" class="link">{_my_apps}</a> <a n:if="$thisUser->getLeftMenuItemStatus('apps')" href="/apps?act=installed" class="link">{_my_apps}</a>
<a href="/settings" class="link">{_my_settings}</a> <a href="/settings" class="link">{_my_settings}</a>
@ -398,19 +378,13 @@
{script "js/al_mentions.js"} {script "js/al_mentions.js"}
{script "js/al_polls.js"} {script "js/al_polls.js"}
{script "js/al_suggestions.js"} {script "js/al_suggestions.js"}
{script "js/al_navigation.js"}
{ifset $thisUser} {ifset $thisUser}
{script "js/al_notifs.js"} {script "js/al_notifs.js"}
{script "js/al_feed.js"} {script "js/al_feed.js"}
{/ifset} {/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>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> <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> <script>
window.openvk = { window.openvk = {
"audio_genres": {\openvk\Web\Models\Entities\Audio::genres} "audio_genres": {\openvk\Web\Models\Entities\Audio::genres},
"at_search": {$atSearch ?? false},
} }
</script> </script>

View file

@ -49,7 +49,7 @@
{include description, x => $dat} {include description, x => $dat}
{/ifset} {/ifset}
</td> </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} {include actions, x => $dat}
</td> </td>
</tr> </tr>

View file

@ -255,17 +255,6 @@
Disabled Disabled
</td> </td>
</tr> </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> <tr>
<td class="e"> <td class="e">
NDA Test Label NDA Test Label
@ -282,7 +271,7 @@
Number verification Number verification
</td> </td>
<td class="v"> <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>
<td class="v"> <td class="v">
Disabled Disabled
@ -364,21 +353,21 @@
Vladimir Barinov, Konstantin Kichulkin and Daniel Myslivets Vladimir Barinov, Konstantin Kichulkin and Daniel Myslivets
</td> </td>
</tr> </tr>
<tr n:foreach="$themes as $theme"> <tr n:foreach="$themes as $themeEntry">
<td class="e"> <td class="e">
{$theme->getName()} {$themeEntry->getName()}
</td> </td>
<td class="v"> <td class="v">
{$theme->isEnabled() ? "Enabled" : "Installed"} {$themeEntry->isEnabled() ? "Enabled" : "Installed"}
</td> </td>
<td class="v"> <td class="v">
{$theme->getVersion()} {$themeEntry->getVersion()}
</td> </td>
<td class="v"> <td class="v">
{$theme->getDescription()|truncate:20} {$themeEntry->getDescription()|truncate:20}
</td> </td>
<td class="v"> <td class="v">
{$theme->getAuthor()} {$themeEntry->getAuthor()}
</td> </td>
</tr> </tr>
</tbody> </tbody>

View file

@ -58,13 +58,17 @@
</div> </div>
<br/> <br/>
<div class="group"> <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> <label for="verify">{_admin_verification}</label>
</div> </div>
<div class="group"> <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> <label for="hide_from_global_feed">{_admin_club_excludeglobalfeed}</label>
</div> </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/> <hr/>
<div class="buttons-container"> <div class="buttons-container">
<div class="buttons"> <div class="buttons">

View file

@ -12,7 +12,8 @@
{block content} {block content}
<center> <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> </center>
<div n:if="!is_null($news)" id="news"> <div n:if="!is_null($news)" id="news">

View file

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

View file

@ -59,66 +59,47 @@
<input n:if="$mode == 'popular'" type="hidden" name="bigplayer_context" data-type="popular_audios" data-entity="0" data-page="1"> <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 class="bigPlayerDetector"></div>
<div style="width: 100%;display: flex;margin-bottom: -10px;" class="audiosDiv"> <div class="audiosDiv">
<div style="width: 74%;" class="audiosContainer" n:if="$mode != 'playlists'"> <div style="width: 74%;" class="audiosContainer audiosPaddingContainer" n:if="$mode != 'playlists'">
<div style="padding: 8px;"> <div n:if="$audiosCount <= 0" style='height: 50%;'>
<div n:if="$audiosCount <= 0"> {include "../components/content_error.xml", description => $ownerId > 0 ? ($ownerId == $thisUser->getId() ? tr("no_audios_thisuser") : tr("no_audios_user")) : tr("no_audios_club")}
{include "../components/error.xml", description => $ownerId > 0 ? ($ownerId == $thisUser->getId() ? tr("no_audios_thisuser") : tr("no_audios_user")) : tr("no_audios_club")} </div>
</div> <div n:if="$audiosCount > 0" class="infContainer">
<div n:if="$audiosCount > 0" class="infContainer"> <div class="infObj" n:foreach="$audios as $audio">
<div class="infObj" n:foreach="$audios as $audio"> {include "player.xml", audio => $audio, club => $club}
{include "player.xml", audio => $audio, club => $club}
</div>
</div> </div>
</div>
<div n:if="$mode != 'new' && $mode != 'popular'"> <div n:if="$mode != 'new' && $mode != 'popular'">
{include "../components/paginator.xml", conf => (object) [ {include "../components/paginator.xml", conf => (object) [
"page" => $page, "page" => $page,
"count" => $audiosCount, "count" => $audiosCount,
"amount" => sizeof($audios), "amount" => sizeof($audios),
"perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE, "perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE,
"atBottom" => true, "atBottom" => true,
]} ]}
</div>
</div> </div>
</div> </div>
<div style="width: 74%;" n:if="$mode == 'playlists'"> <div style="width: 71.8%;" class="audiosPaddingContainer audiosPaddingContainer" n:if="$mode == 'playlists'">
<div style="padding: 8px;"> <div n:if="$playlistsCount <= 0" style='height: 100%;'>
<div n:if="$playlistsCount <= 0"> {include "../components/content_error.xml", description => $ownerId > 0 ? ($ownerId == $thisUser->getId() ? tr("no_playlists_thisuser") : tr("no_playlists_user")) : tr("no_playlists_club")}
{include "../components/error.xml", description => $ownerId > 0 ? ($ownerId == $thisUser->getId() ? tr("no_playlists_thisuser") : tr("no_playlists_user")) : tr("no_playlists_club")} </div>
</div>
<div class="infContainer playlistContainer" n:if="$playlistsCount > 0"> <div class="infContainer playlistContainer" n:if="$playlistsCount > 0">
<div class="infObj" n:foreach="$playlists as $playlist"> {foreach $playlists as $playlist}
<a href="/playlist{$playlist->getPrettyId()}"> {include 'playlistListView.xml', playlist => $playlist}
<div class="playlistCover"> {/foreach}
<img src="{$playlist->getCoverURL()}" alt="{_playlist_cover}"> </div>
</div>
</a>
<div>
<div class="playlistInfo"> {include "../components/paginator.xml", conf => (object) [
<a href="/playlist{$playlist->getPrettyId()}"> "page" => $page,
<span style="font-size: 12px" class="playlistName"> "count" => $playlistsCount,
{ovk_proc_strtr($playlist->getName(), 15)} "amount" => sizeof($playlists),
</span> "perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE,
</a> "atBottom" => true,
]}
<a href="{$playlist->getOwner()->getURL()}">{ovk_proc_strtr($playlist->getOwner()->getCanonicalName(), 20)}</a>
</div>
</div>
</div>
<div>
{include "../components/paginator.xml", conf => (object) [
"page" => $page,
"count" => $playlistsCount,
"amount" => sizeof($playlists),
"perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE,
"atBottom" => true,
]}
</div>
</div> </div>
</div> </div>
{include "tabs.xml"} {include "tabs.xml"}

View file

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

View file

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

View file

@ -5,14 +5,22 @@
{/block} {/block}
{block header} {block header}
{if !is_null($group)} {if !$playlist}
<a href="{$group->getURL()}">{$group->getCanonicalName()}</a> {if !is_null($group)}
» <a href="{$group->getURL()}">{$group->getCanonicalName()}</a>
<a href="/audios-{$group->getId()}">{_audios}</a> »
<a href="/audios-{$group->getId()}">{_audios}</a>
{else}
<a href="{$thisUser->getURL()}">{$thisUser->getCanonicalName()}</a>
»
<a href="/audios{$thisUser->getId()}">{_audios}</a>
{/if}
{else} {else}
<a href="{$thisUser->getURL()}">{$thisUser->getCanonicalName()}</a> <a href="{$owner->getURL()}">{$owner->getCanonicalName()}</a>
» »
<a href="/audios{$thisUser->getId()}">{_audios}</a> <a href="/playlists{$ownerId}">{_playlists}</a>
»
<a href="/playlist{$playlist->getPrettyId()}">{_playlist}</a>
{/if} {/if}
» »
@ -36,6 +44,7 @@
<input type="hidden" name="lyrics" /> <input type="hidden" name="lyrics" />
<input type="hidden" name="genre" /> <input type="hidden" name="genre" />
<input type="hidden" name="explicit" /> <input type="hidden" name="explicit" />
<input type="hidden" name="unlisted" />
<input type="hidden" name="hash" value="{$csrfToken}" /> <input type="hidden" name="hash" value="{$csrfToken}" />
<input id="audio_input" type="file" name="blob" accept="audio/*" style="display:none" /> <input id="audio_input" type="file" name="blob" accept="audio/*" style="display:none" />
@ -43,20 +52,20 @@
</form> </form>
</div><br/> </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>
<div id="lastStep" style="display:none;"> <div id="lastStep" style="display:none;">
<table cellspacing="7" cellpadding="0" border="0" align="center"> <table cellspacing="7" cellpadding="0" border="0" align="center">
<tbody> <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> <tr>
<td width="120" valign="top"><span class="nobold">{_performer}:</span></td> <td width="120" valign="top"><span class="nobold">{_performer}:</span></td>
<td><input name="performer" type="text" autocomplete="off" maxlength="80" /></td> <td><input name="performer" type="text" autocomplete="off" maxlength="80" /></td>
</tr> </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> <tr>
<td width="120" valign="top"><span class="nobold">{_genre}:</span></td> <td width="120" valign="top"><span class="nobold">{_genre}:</span></td>
<td> <td>
@ -74,7 +83,8 @@
<tr> <tr>
<td width="120" valign="top"></td> <td width="120" valign="top"></td>
<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> </td>
</tr> </tr>
<tr> <tr>
@ -100,8 +110,24 @@
document.querySelector("#firstStep").style.display = "none" document.querySelector("#firstStep").style.display = "none"
document.querySelector("#lastStep").style.display = "block" document.querySelector("#lastStep").style.display = "block"
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 = await id3.fromFile(files[0]); let tags = null
try {
tags = await id3.fromFile(files[0]);
} catch(e) {
console.error(e)
}
console.log(tags)
if(tags != null) { if(tags != null) {
console.log("ID" + tags.kind + " detected, setting values..."); console.log("ID" + tags.kind + " detected, setting values...");
@ -116,17 +142,31 @@
document.querySelector("#lastStep input[name=performer]").value = tr("track_unknown"); document.querySelector("#lastStep input[name=performer]").value = tr("track_unknown");
if(tags.genre != null) { if(tags.genre != null) {
if(document.querySelector("#lastStep select[name=genre] > option[value='" + tags.genre + "']") != null) { // if there are more than one genre
document.querySelector("#lastStep select[name=genre]").value = tags.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 { } else {
console.warn("Unknown genre: " + tags.genre); if(document.querySelector("#lastStep select[name=genre] > option[value='" + tags.genre + "']") != null) {
document.querySelector("#lastStep select[name=genre]").value = "Other" document.querySelector("#lastStep select[name=genre]").value = tags.genre;
} else {
console.warn("Unknown genre: " + tags.genre);
document.querySelector("#lastStep select[name=genre]").value = "Other"
}
} }
} else {
document.querySelector("#lastStep select[name=genre]").value = "Other"
} }
if(tags.comments != null)
document.querySelector("#lastStep textarea[name=lyrics]").value = tags.comments
} else { } else {
document.querySelector("#lastStep input[name=name]").value = files[0].name fallback()
document.querySelector("#lastStep select[name=genre]").value = "Other"
document.querySelector("#lastStep input[name=performer]").value = tr("track_unknown");
} }
}); });
@ -147,12 +187,14 @@
var genre_ = document.querySelector("#audio_upload input[name=genre]"); var genre_ = document.querySelector("#audio_upload input[name=genre]");
var lyrics_ = document.querySelector("#audio_upload input[name=lyrics]"); var lyrics_ = document.querySelector("#audio_upload input[name=lyrics]");
var explicit_ = document.querySelector("#audio_upload input[name=explicit]"); 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 name_.value = document.querySelector("#lastStep input[name=name]").value
perf_.value = document.querySelector("#lastStep input[name=performer]").value perf_.value = document.querySelector("#lastStep input[name=performer]").value
genre_.value = document.querySelector("#lastStep select[name=genre]").value genre_.value = document.querySelector("#lastStep select[name=genre]").value
lyrics_.value = document.querySelector("#lastStep textarea[name=lyrics]").value lyrics_.value = document.querySelector("#lastStep textarea[name=lyrics]").value
explicit_.value = document.querySelector("#lastStep input[name=explicit]").checked ? "on" : "off" 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"); $("#audio_upload > form").trigger("submit");
}) })

View file

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

View file

@ -1,7 +1,8 @@
{php $id = $audio->getId() . rand(0, 1000)} {php $id = $audio->getId() . rand(0, 1000)}
{php $isWithdrawn = $audio->isWithdrawn()} {php $isWithdrawn = $audio->isWithdrawn()}
{php $isAvailable = $audio->isAvailable()}
{php $editable = isset($thisUser) && $audio->canBeModifiedBy($thisUser)} {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" /> <audio class="audio" />
<div id="miniplayer" class="audioEntry" style="min-height: 39px;"> <div id="miniplayer" class="audioEntry" style="min-height: 39px;">
@ -11,29 +12,32 @@
</div> </div>
<div class="status" style="margin-top: 12px;"> <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"> <div class="info">
<strong class="performer"> <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> </strong>
<span class="title {if !empty($audio->getLyrics())}withLyrics{/if}">{$audio->getTitle()}</span> <span class="title {if !empty($audio->getLyrics())}withLyrics{/if}">{$audio->getTitle()}</span>
</div> </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> </div>
<div class="volume" style="display: flex; flex-direction: column;width:14%;"> <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> <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)} {php $hasAudio = isset($thisUser) && $audio->isInLibraryOf($thisUser)}
{if !$hideButtons} {if !$hideButtons}
<div class="remove-icon musicIcon" data-id="{$audio->getId()}" n:if="isset($thisUser) && $hasAudio" ></div> <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="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="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="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> <div class="report-icon musicIcon" data-id="{$audio->getId()}" n:if="isset($thisUser) && !$editable && !$isWithdrawn" ></div>
{/if} {/if}
@ -41,20 +45,20 @@
</div> </div>
</div> </div>
<div class="subTracks"> <div class="subTracks">
<div style="width: 100%;"> <div class="lengthTrackWrapper">
<div class="track lengthTrack" style="margin-top: 3px;display:none"> <div class="track lengthTrack">
<div class="selectableTrack" style="width: 100%;" n:attr="style => $isWithdrawn ? 'display: none;' : ''"> <div class="selectableTrack" n:attr="style => $isWithdrawn ? 'display: none;' : ''">
<div style="position: relative;width: calc(100% - 18px);"> <div class="selectableTrackSlider">
<div class="slider"></div> <div class="slider"></div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div style="width: 81px;margin-left: 16px;"> <div class="volumeTrackWrapper">
<div class="track volumeTrack" style="margin-top: 3px;display:none"> <div class="track volumeTrack">
<div class="selectableTrack" style="width: 100%;" n:attr="style => $isWithdrawn ? 'display: none;' : ''"> <div class="selectableTrack" n:attr="style => $isWithdrawn ? 'display: none;' : ''">
<div style="position: relative;width: calc(100% - 18px);"> <div class="selectableTrackSlider">
<div class="slider"></div> <div class="slider"></div>
</div> </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,35 +1,35 @@
<div class="searchOptions newer"> <div class='verticalGrayTabsWrapper'>
<div class="searchList" style="margin-top:10px"> <div class="verticalGrayTabs">
<a n:attr="id => $mode === 'list' && $isMy ? 'used' : 'ki'" href="/audios{$thisUser->getId()}" n:if="isset($thisUser)">{_my_music}</a> <div class='with_padding'>
<a href="/player/upload{if $isMyClub}?gid={abs($ownerId)}{/if}" n:if="isset($thisUser)">{_upload_audio}</a> <a n:if="isset($thisUser)" n:attr="id => $mode === 'list' && $isMy ? 'used' : 'ki'" href="/audios{$thisUser->getId()}">{_my_music}</a>
<a n:attr="id => $mode === 'new' ? 'used' : 'ki'" href="/audios/new">{_audio_new}</a> <a n:if="isset($thisUser)" href="/player/upload{if $isMyClub}?gid={abs($ownerId)}{/if}">{_upload_audio}</a>
<a n:attr="id => $mode === 'popular' ? 'used' : 'ki'" href="/audios/popular">{_audio_popular}</a> <a n:if="isset($thisUser)" n:attr="id => $mode === 'new' ? 'used' : 'ki'" href="/search?section=audios">{_audio_new}</a>
<a href="/search?type=audios" n:if="isset($thisUser)">{_audio_search}</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)"> <hr n:if="isset($thisUser)">
<a n:attr="id => $mode === 'playlists' && $ownerId == $thisUser->getId() ? 'used' : 'ki'" href="/playlists{$thisUser->getId()}" n:if="isset($thisUser)">{_my_playlists}</a> <a n:attr="id => $mode === 'playlists' && $ownerId == $thisUser->getId() ? 'used' : 'ki'" href="/playlists{$thisUser->getId()}" n:if="isset($thisUser)">{_my_playlists}</a>
<a n:if="isset($thisUser)" href="/audios/newPlaylist">{_new_playlist}</a> <a n:if="isset($thisUser)" href="/audios/newPlaylist">{_new_playlist}</a>
{if !$isMy && $mode !== 'popular' && $mode !== 'new'} {if !$isMy && $mode !== 'popular' && $mode !== 'new'}
<hr> <hr>
<a n:if="!$isMy" n:attr="id => $mode === 'list' ? 'used' : 'ki'" href="/audios{$ownerId}">{if $ownerId > 0}{_music_user}{else}{_music_club}{/if}</a> <a n:if="!$isMy" n:attr="id => $mode === 'list' ? 'used' : 'ki'" href="/audios{$ownerId}">{if $ownerId > 0}{_music_user}{else}{_music_club}{/if}</a>
<a href="/player/upload?gid={abs($ownerId)}" n:if="isset($thisUser) && isset($club) && $club->canUploadAudio($thisUser)">{_upload_audio}</a> <a href="/player/upload?gid={abs($ownerId)}" n:if="isset($thisUser) && isset($club) && $club->canUploadAudio($thisUser)">{_upload_audio}</a>
<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 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> <a href="/audios/newPlaylist{if $isMyClub}?gid={abs($ownerId)}{/if}" n:if="isset($thisUser) && $isMyClub">{_new_playlist}</a>
{/if} {/if}
</div>
{if $friendsAudios} {if $friendsAudios}
<div class="friendsAudiosList"> <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"> <div class="elem">
<img src="{$fr->getAvatarURL()}" /> <img src="{$fr->getAvatarURL()}" />
<div class="additionalInfo"> <div class="additionalInfo">
{php $audioStatus = $fr->getCurrentAudioStatus()} <span class="name noOverflow">{$fr->getCanonicalName()}</span>
<span class="name">{$fr->getCanonicalName()}</span>
<span class="desc">{$audioStatus ? $audioStatus->getName() : tr("audios_count", $fr->getAudiosCollectionSize())}</span> <span class="desc">{$audioStatus ? $audioStatus->getName() : tr("audios_count", $fr->getAudiosCollectionSize())}</span>
</div> </div>
</div> </div>

View file

@ -17,7 +17,7 @@
{/block} {/block}
{block preview} {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}
{block name} {block name}
@ -26,4 +26,4 @@
{block description} {block description}
{$x->getDescription(tr("__lang"))} {$x->getDescription(tr("__lang"))}
{/block} {/block}

View file

@ -14,8 +14,8 @@
{block content} {block content}
<div class="gift_grid"> <div class="gift_grid">
<div n:foreach="$gifts as $gift" n:class="gift_sel, !$gift->canUse($thisUser) ? disabled" data-gift="{$gift->getId()}"> <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"> <strong class="gift_price">
{if $gift->isFree()} {if $gift->isFree()}
{_free_gift} {_free_gift}
@ -23,7 +23,7 @@
{tr('coins', $gift->getPrice())} {tr('coins', $gift->getPrice())}
{/if} {/if}
</strong> </strong>
<strong class="gift_limit"> <strong class="gift_limit">
{if $gift->getUsagesLeft($thisUser) !== INF} {if $gift->getUsagesLeft($thisUser) !== INF}
{tr("gifts_left", $gift->getUsagesLeft($thisUser))} {tr("gifts_left", $gift->getUsagesLeft($thisUser))}
@ -31,7 +31,7 @@
</strong> </strong>
</div> </div>
</div> </div>
<div style="padding: 8px;"> <div style="padding: 8px;">
{include "../components/paginator.xml", conf => (object) [ {include "../components/paginator.xml", conf => (object) [
"page" => $page, "page" => $page,
@ -48,11 +48,11 @@
let el = $(this); let el = $(this);
if(el.hasClass("disabled")) if(el.hasClass("disabled"))
return false; return false;
let link = "/gifts?act=confirm&user={$user->getId()}&pack={$cat->getId()}&elid="; let link = "/gifts?act=confirm&user={$user->getId()}&pack={$cat->getId()}&elid=";
let gift = el.data("gift"); let gift = el.data("gift");
window.location.assign(link + gift); window.location.assign(link + gift);
}); });
</script> </script>
{/block} {/block}

View file

@ -16,7 +16,7 @@
{/block} {/block}
{block preview} {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}
{block name} {block name}
@ -40,4 +40,4 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
{/block} {/block}

View file

@ -83,7 +83,7 @@
<option value="0" n:attr="selected => $club->getWallType() == 0" /> {_group_closed_post}</option> <option value="0" n:attr="selected => $club->getWallType() == 0" /> {_group_closed_post}</option>
<select> <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> </td>
</tr> </tr>
<tr> <tr>

View file

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

View file

@ -127,25 +127,19 @@
<div class="right_small_block"> <div class="right_small_block">
{var $avatarPhoto = $club->getAvatarPhoto()} {var $avatarPhoto = $club->getAvatarPhoto()}
{var $avatarLink = ((is_null($avatarPhoto) ? FALSE : $avatarPhoto->isAnonymous()) ? "/photo" . ("s/" . base_convert((string) $avatarPhoto->getId(), 10, 32)) : $club->getAvatarLink())} {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;"> <div class="avatar_block" style="position:relative;" data-club="{$club->getId()}">
{var $hasAvatar = !str_contains($club->getAvatarUrl('miniscule'), "/assets/packages/static/openvk/img/camera_200.png")} {if $thisUser && $club->canBeModifiedBy($thisUser)}
{if !is_null($thisUser) && $hasAvatar == false && $club->canBeModifiedBy($thisUser)} <a {if $avatarPhoto}style="display:none"{/if} class="add_image_text" id="add_image">{_add_image}</a>
<a href="javascript:addAvatarImage(true, {$club->getId()})" class="text_add_image">{_add_image_group}</a> <div {if !$avatarPhoto}style="display:none"{/if} class="avatar_controls">
{elseif !is_null($thisUser) && $hasAvatar == true && $club->canBeModifiedBy($thisUser)} <div class="avatarDelete hoverable"></div>
<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_variants"> <div class="avatar_variants">
<div class="variant"> <a class="_add_image hoverable" id="add_image"><span>{_upload_new_picture}</span></a>
<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>
</div> </div>
</div> </div>
{/if} {/if}
<a href="{$avatarLink|nocheck}"> <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> </a>
</div> </div>
<div n:ifset="$thisUser" id="profile_links"> <div n:ifset="$thisUser" id="profile_links">
@ -292,7 +286,7 @@
<img <img
src="{is_null($cover)?'/assets/packages/static/openvk/img/camera_200.png':$cover->getURL()}" 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>
<div> <div>
<b><a href="/album{$album->getPrettyId()}">{$album->getName()}</a></b><br> <b><a href="/album{$album->getPrettyId()}">{$album->getName()}</a></b><br>

View file

@ -9,13 +9,13 @@
<a id="act_tab_a" href="javascript:false">{_all_messages}</a> <a id="act_tab_a" href="javascript:false">{_all_messages}</a>
</div> </div>
</div> </div>
<div class="container_gray"> <div class="container_gray">
<form action="/im/search" method="POST" style="margin: 0;"> <form action="/im/search" method="POST" style="margin: 0;">
<input type="text" name="pattern" placeholder="{_search_messages}" required /> <input type="text" name="pattern" placeholder="{_search_messages}" required />
</form> </form>
</div> </div>
{if sizeof($corresps) > 0} {if sizeof($corresps) > 0}
<div class="crp-list"> <div class="crp-list">
<div n:foreach="$corresps as $coresp" <div n:foreach="$corresps as $coresp"
@ -23,10 +23,10 @@
onmousedown="window.location.href = {$coresp->getURL()};" > onmousedown="window.location.href = {$coresp->getURL()};" >
{var $recipient = $coresp->getCorrespondents()[1]} {var $recipient = $coresp->getCorrespondents()[1]}
{var $lastMsg = $coresp->getPreviewMessage()} {var $lastMsg = $coresp->getPreviewMessage()}
<div class="crp-entry--image"> <div class="crp-entry--image">
<img src="{$recipient->getAvatarURL('miniscule')}" <img src="{$recipient->getAvatarURL('miniscule')}"
alt="Фотография пользователя" /> alt="Фотография пользователя" loading=lazy />
</div> </div>
<div class="crp-entry--info"> <div class="crp-entry--info">
<a href="{$recipient->getURL()}">{$recipient->getCanonicalName()}</a><br/> <a href="{$recipient->getURL()}">{$recipient->getCanonicalName()}</a><br/>
@ -34,7 +34,7 @@
</div> </div>
<div n:class="crp-entry--message, $lastMsg->getUnreadState() ? unread"> <div n:class="crp-entry--message, $lastMsg->getUnreadState() ? unread">
{var $_author = $lastMsg->getSender()} {var $_author = $lastMsg->getSender()}
<div class="crp-entry--message---av" n:if="$_author->getId() === $thisUser->getId()"> <div class="crp-entry--message---av" n:if="$_author->getId() === $thisUser->getId()">
<img src="{$_author->getAvatarURL('miniscule')}" <img src="{$_author->getAvatarURL('miniscule')}"
alt="Фотография пользователя" /> alt="Фотография пользователя" />
@ -53,4 +53,4 @@
<br/> <br/>
<center>{_no_messages}</center> <center>{_no_messages}</center>
{/if} {/if}
{/block} {/block}

View file

@ -41,7 +41,7 @@
</a> </a>
<a href="/photo{$photo->getPrettyId()}?from=album{$album->getId()}"> <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> </a>
</div> </div>
{/foreach} {/foreach}
@ -50,4 +50,4 @@
{else} {else}
{include "../components/nothing.xml"} {include "../components/nothing.xml"}
{/if} {/if}
{/block} {/block}

View file

@ -23,7 +23,7 @@
{else} {else}
{tr("albums", $count)} {tr("albums", $count)}
{/if} {/if}
<span n:if="$canEdit" style="float: right;"> <span n:if="$canEdit" style="float: right;">
&nbsp;|&nbsp; &nbsp;|&nbsp;
{var $isClub = ($owner instanceof \openvk\Web\Models\Entities\Club)} {var $isClub = ($owner instanceof \openvk\Web\Models\Entities\Club)}
@ -34,7 +34,7 @@
{/block} {/block}
{block actions} {block actions}
{/block} {/block}
{* BEGIN ELEMENTS DESCRIPTION *} {* BEGIN ELEMENTS DESCRIPTION *}
@ -46,9 +46,9 @@
{block preview} {block preview}
{var $cover = $x->getCoverPhoto()} {var $cover = $x->getCoverPhoto()}
{var $preview = is_null($cover) ? "/assets/packages/static/openvk/img/camera_200.png" : $cover->getURLBySizeId("normal")} {var $preview = is_null($cover) ? "/assets/packages/static/openvk/img/camera_200.png" : $cover->getURLBySizeId("normal")}
<a href="/album{$x->getPrettyId()}"> <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> </a>
{/block} {/block}
@ -61,4 +61,4 @@
<span style="color: grey;">{$x->getPhotosCount()} {_photos}</span><br /> <span style="color: grey;">{$x->getPhotosCount()} {_photos}</span><br />
<span style="color: grey;">{tr("updated_at", $x->getEditTime() ?? $x->getCreationTime())}</span><br /> <span style="color: grey;">{tr("updated_at", $x->getEditTime() ?? $x->getCreationTime())}</span><br />
<span style="color: grey;">{_created} {$x->getCreationTime()}</span><br /> <span style="color: grey;">{_created} {$x->getCreationTime()}</span><br />
{/block} {/block}

View file

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

View file

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

View file

@ -1,378 +1,405 @@
{extends "../@layout.xml"} {extends "../@layout.xml"}
{block title} {block title}
{tr("search_for_$type")} {tr("search_for_$section")}
{if $_GET['query']} {if $_REQUEST['q']}
- {$_GET['query']} - {$_REQUEST['q']}
{/if} {/if}
{/block} {/block}
{block header} {block header}
{=OPENVK_ROOT_CONF["openvk"]["appearance"]["name"]} » {tr("search_for_$section")}
{tr("search_for_$type")}
{/block} {/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} <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 $type != "apps"}
{$x->getURL()}
{else}
/app{$x->getId()}
{/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}
{if !is_null($_GET['city']) && $_GET['city'] != "" && $x->getPrivacySetting("page.info.read") > 1} <div class='summaryBar summaryBarFlex padding'>
<tr> <div class='summary'>
<td><span class="nobold">{_city}:</span></td> <b>{tr("results", $count)} {tr("showing_x_y", $page, $count)}</b>
<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>
<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">
<table>
<tbody>
<tr>
<td valign="top">
<a href="{include link, x => $dat}">
{include preview, x => $dat}
</a>
</td>
<td valign="top" style="width: 100%">
{ifset infotable}
{include infotable, x => $dat}
{else}
<a href="{include link, x => $dat}">
<b>
{include name, x => $dat}
</b>
</a>
<br/>
{include description, x => $dat}
{/ifset}
</td>
<td n:ifset="actions" valign="top" class="action_links" style="width: 150px;">
{include actions, x => $dat}
</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)}
{_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">
{include "../components/video.xml", video => $dat}
</div> </div>
{/foreach}
{elseif $type == "audios"} {include "../components/paginator.xml", conf => $paginatorConf}
{foreach $data as $dat} </div>
{include "../Audio/player.xml", audio => $dat}
{/foreach} <div class='page_wrap_content' id='search_page'>
{/if} <div n:class='page_wrap_content_main, $section == "audios" && $count > 0 ? audios_padding'>
{else} {if $count > 0}
{ifset customErrorMessage} {if $section === 'users'}
{include customErrorMessage} <div class='search_content def_row_content' n:foreach="$data as $dat">
{else} <table>
{include "../components/nothing.xml"} <tbody>
{/ifset} <tr>
{/if} <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 == "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>
<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>
{$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>
<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>
<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>
<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}
</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}
{include "../components/content_error.xml", description => tr("no_results_by_this_query")}
{/if}
</div>
<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> </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>
{/block} {/block}
{block searchOptions} {block searchOptions}
<div class="searchOptions"> <div class="search_option">
<ul class="searchList"> <div class="search_option_name">
<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> <div class='search_option_name_ico'></div>
<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> {_s_order_by}
<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> </div>
<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> <div class="search_option_content">
<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> <select name="order" form="search_form" data-default='id'>
<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> {if $section == "users"}
<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> <option value="id" n:attr="selected => $order == 'id'">{_s_order_by_reg_date}</option>
</ul>
{if OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"]}
<div class="searchOption"> <option value="rating" n:attr="selected => $order == 'rating'">{_s_order_by_rating}</option>
<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>
{/if}
{/if} {/if}
{elseif $section == "posts"}
{if $type == "audios"} <option value="id" n:attr="selected => $order == 'id'">{_s_order_by_publishing_date}</option>
<option value="length" n:attr="selected => $_GET['sort'] == 'length'">{_s_order_by_length}</option> {elseif $section == "audios"}
<option value="listens" n:attr="selected => $_GET['sort'] == 'listens'">{_s_order_by_listens}</option> <option value="id" n:attr="selected => $order == 'id'">{_s_order_by_upload_date}</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>
</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} {else}
<p id="bydate">{_s_registered_before}:<br> <option value="id" n:attr="selected => $order == 'id'">{_s_order_by_creation_date}</option>
{/if} {/if}
<input type="date" name="datebefore"
form="searcher" {if $section == "audios" || $section == "audios_playlists"}
id="bydate" <option value="length" n:attr="selected => $order == 'length'">{_s_order_by_length}</option>
style="width:100%" <option value="listens" n:attr="selected => $order == 'listens'">{_s_order_by_listens}</option>
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} {/if}
<input type="date" name="dateafter" </select>
form="searcher"
style="width:100%" <label n:if="$order != 'rating'">
id="bydate" <input type="checkbox" name="invert" value="1" form="search_form" n:attr="checked => $_REQUEST['invert'] == '1'">
value="{if !is_null($_GET['dateafter'])}{$_GET['dateafter']}{/if}" {_s_order_invert}
min="2019-11-18" </label>
max={date('Y-m-d')}></p>
</div>
</div> </div>
{/if} </div>
{if $type === "users"} <div n:if="$section == 'users'" class="search_option">
<div class="searchOption"> <div class="search_option_name">
<div class="searchOptionName" id="n_main" onclick="hideParams('main')"><img src="/assets/packages/static/openvk/img/hide.png" class="searchHide">{_main}</div> <div class='search_option_name_ico'></div>
<div class="searchOptionBlock" id="s_main"> {_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}
</div>
</div> </div>
<div class="search_option_content">
<!-- <div class="searchOption"> <input type="text" n:attr="value => $_REQUEST['city']" form="search_form" placeholder="{_city}" name="city">
<div class="searchOptionName" id="n_gender" onclick="hideParams('gender')"><img src="/assets/packages/static/openvk/img/hide.png" class="searchHide">{_gender}</div> <input type="text" n:attr="value => $_REQUEST['hometown']" form="search_form" placeholder="{_hometown}" name="hometown">
<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> <label>
<p><input type="radio" form="searcher" id="gend1"{if $_GET['gender'] == 1}checked{/if} name="gender" value="1">{_female}</p> <input name="is_online" type="checkbox" n:attr="checked => $_REQUEST['is_online'] == '1'" form="search_form" value="1">
<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> {_s_now_on_site}
</div> </label>
</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>
</div> </div>
</div>
<div class="searchOption"> <div n:if="$section == 'users'" class="search_option">
<div class="searchOptionName" id="n_politViews" onclick="hideParams('politViews')"><img src="/assets/packages/static/openvk/img/hide.png" class="searchHide">{_politViews}</div> <div class="search_option_name">
<div class="searchOptionBlock" id="s_politViews"> <div class='search_option_name_ico'></div>
<select name="politViews" form="searcher"> {_pronouns}
<option n:foreach="range(0, 9) as $i" value="{$i}" {if $_GET['politViews'] == $i}selected{/if} form="searcher"> </div>
<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 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)} {tr("politViews_".$i)}
</option> </option>
</select> </select>
</div> </label>
</div>
<div class="searchOption"> <label>
<div class="searchOptionName" id="n_contacts" onclick="hideParams('contacts')"><img src="/assets/packages/static/openvk/img/hide.png" class="searchHide">{_contacts}</div> {_relationship}
<div class="searchOptionBlock" id="s_contacts"> <select name="marital_status" form="search_form" data-default='0'>
<input type="text" name="email" value="{if !is_null($_GET['email'])}{$_GET['email']}{/if}" form="searcher" placeholder="{_email}"> <option n:foreach="range(0, 8) as $i" value="{$i}" n:attr="selected => $_REQUEST['marital_status'] == $i">
<input type="text" name="telegram" value="{if !is_null($_GET['telegram'])}{$_GET['telegram']}{/if}" form="searcher" placeholder="{_telegram}"><br id="contacts"> {tr("relationship_".$i)}
<input type="text" name="site" value="{if !is_null($_GET['site'])}{$_GET['site']}{/if}" form="searcher" placeholder="{_personal_website}"><br id="contacts"> </option>
<input type="text" name="address" value="{if !is_null($_GET['address'])}{$_GET['address']}{/if}" form="searcher" placeholder="{_address}"> </select>
</div> </label>
</div> </div>
</div>
<div class="searchOption"> <div n:if="$section == 'videos'" class="search_option">
<div class="searchOptionName" id="n_interests" onclick="hideParams('interests')"><img src="/assets/packages/static/openvk/img/hide.png" class="searchHide">{_interests}</div> <div class="search_option_name">
<div class="searchOptionBlock" id="s_interests"> <div class='search_option_name_ico'></div>
<input type="text" value="{if !is_null($_GET['interests'])}{$_GET['interests']}{/if}" form="searcher" placeholder="{_interests}" name="interests"> {_s_main}
<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> </div>
{/if} <div class="search_option_content">
<label>
{if $type == "audios"} <input type="checkbox" value='1' name="only_youtube" n:attr="checked => !empty($_REQUEST['only_youtube'])" form="search_form">{_s_only_youtube}
<div class="searchOption"> </label>
<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>
<div class="searchOptionBlock" id="s_main_audio"> </div>
<label><input type="checkbox" name="only_performers" n:attr="checked => !empty($_GET['only_performers'])" form="searcher">{_s_only_performers}</label><br> <div n:if="$section == 'audios'" class="search_option">
<label><input type="checkbox" name="with_lyrics" n:attr="checked => !empty($_GET['with_lyrics'])" form="searcher">{_s_with_lyrics}</label> <div class="search_option_name">
</div> <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>
{/if}
<input class="button" type="button" id="dnt" value="{_reset}" onclick="resetSearch()">
</div> </div>
<input class="button" id="search_reset" type="button" value="{_reset}">
{/block} {/block}

View file

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

View file

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

View file

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

View file

@ -69,27 +69,22 @@
{else} {else}
<div class="left_small_block"> <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")} {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> {if $thisUser && $user->getId() == $thisUser->getId()}
{elseif !is_null($thisUser) && $user && $hasAvatar == true && $user->getId() == $thisUser->getId()} <a {if $hasAvatar}style="display:none"{/if} class="add_image_text" id="add_image">{_add_image}</a>
<div class="avatar_controls"> <div {if !$hasAvatar}style="display:none"{/if} class="avatar_controls">
<div class="avatarDelete"> <div class="avatarDelete hoverable"></div>
<a id="upl" href="javascript:deleteAvatar('{$user->getAvatarPhoto()->getPrettyId()}')"><img src="/assets/packages/static/openvk/img/delete.png"/></a>
</div>
<div class="avatar_variants"> <div class="avatar_variants">
<div class="variant"> <a class="_add_image hoverable" id="add_image"><span>{_upload_new_picture}</span></a>
<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>
</div> </div>
</div> </div>
{/if} {/if}
<a href="{$user->getAvatarLink()|nocheck}"> <a href="{$user->getAvatarLink()|nocheck}">
<img src="{$user->getAvatarUrl('normal')}" <img src="{$user->getAvatarUrl('normal')}"
alt="{$user->getCanonicalName()}" alt="{$user->getCanonicalName()}"
id="thisUserAvatar" id="bigAvatar"
style="width: 100%; image-rendering: -webkit-optimize-contrast;" /> style="width: 100%; image-rendering: -webkit-optimize-contrast;" />
</a> </a>
</div> </div>
@ -307,7 +302,7 @@
<img <img
src="{is_null($cover)?'/assets/packages/static/openvk/img/camera_200.png':$cover->getURLBySizeId('small')}" 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>
<div style="overflow: hidden; overflow-wrap: break-word;"> <div style="overflow: hidden; overflow-wrap: break-word;">
<b><a href="/album{$album->getPrettyId()}">{$album->getName()}</a></b><br> <b><a href="/album{$album->getPrettyId()}">{$album->getName()}</a></b><br>
@ -475,11 +470,11 @@
</tr> </tr>
<tr n:if="!is_null($user->getHometown())"> <tr n:if="!is_null($user->getHometown())">
<td class="label"><span class="nobold">{_hometown}:</span></td> <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>
<tr> <tr>
<td class="label"><span class="nobold">{_politViews}:</span></td> <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>
<tr n:if="!is_null($user->getBirthday())"> <tr n:if="!is_null($user->getBirthday())">
<td class="label"><span class="nobold">{_birth_date}:</span></td> <td class="label"><span class="nobold">{_birth_date}:</span></td>
@ -525,7 +520,7 @@
</tr> </tr>
<tr n:if="!is_null($user->getCity())"> <tr n:if="!is_null($user->getCity())">
<td class="label"><span class="nobold">{_city}:</span></td> <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>
<tr n:if="!is_null($user->getPhysicalAddress())"> <tr n:if="!is_null($user->getPhysicalAddress())">
<td class="label"><span class="nobold">{_address}:</span></td> <td class="label"><span class="nobold">{_address}:</span></td>
@ -543,7 +538,7 @@
{var $interests = explode(", ", $user->getInterests())} {var $interests = explode(", ", $user->getInterests())}
{foreach $interests as $interest} {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} {/foreach}
</td> </td>
</tr> </tr>
@ -554,7 +549,7 @@
{var $musics = explode(", ", $user->getFavoriteMusic())} {var $musics = explode(", ", $user->getFavoriteMusic())}
{foreach $musics as $music} {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} {/foreach}
</td> </td>
</tr> </tr>
@ -564,7 +559,7 @@
{var $films = explode(", ", $user->getFavoriteFilms())} {var $films = explode(", ", $user->getFavoriteFilms())}
{foreach $films as $film} {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} {/foreach}
</td> </td>
</tr> </tr>
@ -574,7 +569,7 @@
{var $shows = explode(", ", $user->getFavoriteShows())} {var $shows = explode(", ", $user->getFavoriteShows())}
{foreach $shows as $show} {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} {/foreach}
</td> </td>
</tr> </tr>
@ -584,7 +579,7 @@
{var $books = explode(", ", $user->getFavoriteBooks())} {var $books = explode(", ", $user->getFavoriteBooks())}
{foreach $books as $book} {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} {/foreach}
</td> </td>
</tr> </tr>
@ -659,7 +654,7 @@
<img style="width: 70px; max-height: 70px;" <img style="width: 70px; max-height: 70px;"
src="{$giftDescriptor->gift->getImage(2)}" src="{$giftDescriptor->gift->getImage(2)}"
alt="{$hideInfo ? tr('gift') : ($giftDescriptor->caption ?? tr('gift'))}" 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> </a>
</div> </div>
</div> </div>
@ -769,8 +764,6 @@
<script n:if="isset($thisUser) && $user->getId() == $thisUser->getId()" n:syntax="off"> <script n:if="isset($thisUser) && $user->getId() == $thisUser->getId()" n:syntax="off">
function setStatusEditorShown(shown) { function setStatusEditorShown(shown) {
document.getElementById("status_editor").style.display = shown ? "block" : "none"; 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 => { document.addEventListener("click", event => {
@ -823,4 +816,4 @@
{block bodyScripts} {block bodyScripts}
{script "js/al_despacito_wall.js"} {script "js/al_despacito_wall.js"}
{/block} {/block}

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

View file

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

View file

@ -2,7 +2,7 @@
{if !$attachment->isDeleted()} {if !$attachment->isDeleted()}
{var $link = "/photo" . ($attachment->isAnonymous() ? ("s/" . base_convert((string) $attachment->getId(), 10, 32)) : $attachment->getPrettyId())} {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})"> <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> </a>
{else} {else}
<a href="javascript:alert('{_attach_no_longer_available}');"> <a href="javascript:alert('{_attach_no_longer_available}');">
@ -58,4 +58,4 @@
<span style="color:red;">{_version_incompatibility}</span> <span style="color:red;">{_version_incompatibility}</span>
{/if} {/if}
{php $GLOBALS["_nesAttGloCou"] = NULL} {php $GLOBALS["_nesAttGloCou"] = NULL}

View file

@ -36,21 +36,26 @@
</div> </div>
</div> </div>
<div n:if="isset($thisUser) &&! ($compact ?? false)" class="post-menu"> <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> <span n:if="$comment->getEditTime()" class="edited editedMark">({_edited_short})</span>
</a> </a>
{if !$timeOnly} {if !$timeOnly}
&nbsp;| &nbsp;|
{if $comment->canBeDeletedBy($thisUser)} {if $comment->canBeDeletedBy($thisUser)}
<a href="/comment{$comment->getId()}/delete">{_delete}</a>&nbsp;| <a href="/comment{$comment->getId()}/delete">{_delete}</a>
{/if} {/if}
{if $comment->canBeEditedBy($thisUser)} {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}
<a class="comment-reply">{_reply}</a> {if !$no_reply_button}
{if $thisUser->getId() != $comment->getOwner()->getId()} |
<a class="comment-reply">{_reply}</a>
{/if}
{if $thisUser->getId() != $author->getRealId()}
|
{var $canReport = true} {var $canReport = true}
| <a href="javascript:reportComment()">{_report}</a> <a href="javascript:reportComment()">{_report}</a>
{/if} {/if}
<div style="float: right; font-size: .7rem;"> <div style="float: right; font-size: .7rem;">
<a class="post-like-button" href="/comment{$comment->getId()}/like?hash={rawurlencode($csrfToken)}"> <a class="post-like-button" href="/comment{$comment->getId()}/like?hash={rawurlencode($csrfToken)}">
@ -59,29 +64,11 @@
</a> </a>
</div> </div>
{/if} {/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"> <span n:if="$compact ?? false">
|&nbsp;<a |&nbsp;<a
{if is_null($linkW)} href="#_comment{$comment->getId()}"
href="#_comment{$comment->getId()}"
{else}
href="{$target}{!is_null($comment->getTarget()) ? $comment->getTarget()->getPrettyId() : $comment->getOwner()->getId()}#_comment{$comment->getId()}"
{/if}
class="date" class="date"
>{$comment->getPublicationTime()}</a> >{$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)} {var $pageCount = ceil($conf->count / $conf->perPage)}
<div n:if="!($conf->page === 1 && $conf->count <= $conf->perPage)" style="padding: 8px;"> <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"> <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> <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++} {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> <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> <tr>
<td width="54" valign="top"> <td width="54" valign="top">
<a href="{$author->getURL()}"> <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> <span n:if="!$post->isPostedOnBehalfOfGroup() && !($compact ?? false) && $author->isOnline()" class="post-online">{_online}</span>
</a> </a>
</td> </td>
@ -165,4 +165,4 @@
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View file

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

View file

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

View file

@ -22,21 +22,21 @@ class DateTime
$now = date_create(); $now = date_create();
$diff = date_diff($now, $then); $diff = date_diff($now, $then);
if($diff->invert === 0) 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($this->timestamp >= strtotime("midnight")) { # Today
if($diff->h >= 1) 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) else if($diff->i < 2)
return tr("time_just_now"); return tr("time_just_now");
else else
return $diff->i === 5 ? tr("time_exactly_five_minutes_ago") : tr("time_minutes_ago", $diff->i); return $diff->i === 5 ? tr("time_exactly_five_minutes_ago") : tr("time_minutes_ago", $diff->i);
} else if($this->timestamp >= strtotime("-1day midnight")) { # Yesterday } 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 } 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 { } 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 = [ $result->tiles = [
new ThumbTile(1, 3, $wCover, $maxHeight), new ThumbTile(1, 1, $w, $h0), 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, $h1), new ThumbTile(1, 1, $w, $h2),
]; ];
} }
break; break;

View file

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

View file

@ -4,9 +4,8 @@
white-space: nowrap; white-space: nowrap;
} }
.overflowedName { .audiosPaddingContainer {
position: absolute; padding: 8px;
z-index: 99;
} }
.musicIcon { .musicIcon {
@ -15,10 +14,6 @@
cursor: pointer; cursor: pointer;
} }
.musicIcon:hover {
filter: brightness(99%);
}
.musicIcon.pressed { .musicIcon.pressed {
filter: brightness(150%); filter: brightness(150%);
} }
@ -31,7 +26,15 @@
height: 46px; height: 46px;
border-bottom: 1px solid #d8d8d8; border-bottom: 1px solid #d8d8d8;
box-shadow: 1px 0px 8px 0px rgba(34, 60, 80, 0.2); 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 { .bigPlayer.floating {
@ -171,11 +174,16 @@
font-size: 10px; font-size: 10px;
} }
.bigPlayer .paddingLayer .trackInfo .timer .elapsedTime {
cursor: pointer;
}
.bigPlayer .paddingLayer .trackInfo .trackName { .bigPlayer .paddingLayer .trackInfo .trackName {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
width: 81%; width: 81%;
height: 13px;
display: inline-block; display: inline-block;
} }
@ -183,11 +191,26 @@
font-size: 10px; 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; text-decoration: underline;
cursor: pointer; 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 { .audioEmbed .track > .selectableTrack, .bigPlayer .selectableTrack {
margin-top: 3px; margin-top: 3px;
width: calc(100% - 8px); width: calc(100% - 8px);
@ -214,7 +237,7 @@
.audioEntry.nowPlaying { .audioEntry.nowPlaying {
background: #606060; background: #606060;
border: 1px solid #4f4f4f; outline: 1px solid #4f4f4f;
box-sizing: border-box; box-sizing: border-box;
} }
@ -224,6 +247,7 @@
.audioEntry.nowPlaying:hover { .audioEntry.nowPlaying:hover {
background: #4e4e4e !important; background: #4e4e4e !important;
border-radius: inherit !important;
} }
.audioEntry.nowPlaying .performer a { .audioEntry.nowPlaying .performer a {
@ -246,8 +270,32 @@
color: white !important; color: white !important;
} }
.audioEntry.nowPlaying .buttons .musicIcon, .audioEntry.nowPlaying .explicitMark { .audioEntry.nowPlaying .explicitMark path {
filter: brightness(187%) opacity(72%); 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 { .audioEntry {
@ -264,12 +312,16 @@
} }
.audioEntry .subTracks { .audioEntry .subTracks {
display: flex; display: none;
padding-bottom: 5px; padding-bottom: 5px;
padding-left: 8px; padding-left: 8px;
padding-right: 12px; padding-right: 12px;
} }
.audioEntry .subTracks.shown {
display: flex;
}
.audioEntry .playerButton .playIcon { .audioEntry .playerButton .playIcon {
background-image: url('/assets/packages/static/openvk/img/play_buttons.gif'); background-image: url('/assets/packages/static/openvk/img/play_buttons.gif');
cursor: pointer; cursor: pointer;
@ -290,16 +342,45 @@
height: 23px; height: 23px;
} }
.audioEntry .status .mediaInfo {
cursor: pointer;
display: flex;
width: 100%;
}
.overflowedName {
position: absolute;
z-index: 99;
width: 80% !important;
}
.audioEntry .status strong { .audioEntry .status strong {
color: #4C4C4C; color: #4C4C4C;
} }
.audioEmbed .track { .audioEmbed .track {
display: none; padding: 0px 0;
padding: 4px 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; display: unset;
} }
@ -309,7 +390,7 @@
} }
.audioEntry:hover .buttons { .audioEntry:hover .buttons {
display: block; display: flex;
} }
.audioEntry:hover .volume .hideOnHover { .audioEntry:hover .volume .hideOnHover {
@ -318,11 +399,16 @@
.audioEntry .buttons { .audioEntry .buttons {
display: none; display: none;
flex-direction: row-reverse;
gap: 5px;
align-items: center;
justify-content: flex-start;
width: 62px; width: 62px;
height: 20px; height: 20px;
position: absolute; position: absolute;
right: 3%; right: 3%;
top: 2px; top: 2px;
margin-top: 7px;
/* чтоб избежать заедания во время ховера кнопки добавления */ /* чтоб избежать заедания во время ховера кнопки добавления */
clip-path: inset(0 0 0 0); clip-path: inset(0 0 0 0);
} }
@ -331,18 +417,21 @@
width: 11px; width: 11px;
height: 11px; height: 11px;
float: right; float: right;
margin-right: 4px;
margin-top: 3px;
background-position: -137px -51px; background-position: -137px -51px;
} }
.audioEntry .buttons .download-icon {
width: 11px;
height: 11px;
float: right;
background-position: -136px -67px;
}
.audioEntry .buttons .add-icon { .audioEntry .buttons .add-icon {
width: 11px; width: 11px;
height: 11px; height: 11px;
float: right; float: right;
background-position: -80px -52px; background-position: -80px -52px;
margin-top: 3px;
margin-left: 2px;
} }
.audioEntry .buttons .add-icon-group { .audioEntry .buttons .add-icon-group {
@ -350,7 +439,6 @@
height: 11px; height: 11px;
float: right; float: right;
background-position: -94px -52px; background-position: -94px -52px;
margin-top: 3px;
transition: margin-right 0.1s ease-out, opacity 0.1s ease-out; transition: margin-right 0.1s ease-out, opacity 0.1s ease-out;
} }
@ -358,9 +446,7 @@
width: 12px; width: 12px;
height: 11px; height: 11px;
float: right; float: right;
background-position: -67px -51px; background-position: -50px -67px;
margin-top: 3px;
margin-right: 3px;
} }
.add-icon-noaction { .add-icon-noaction {
@ -374,22 +460,17 @@
} }
.audioEntry .buttons .remove-icon { .audioEntry .buttons .remove-icon {
margin-top: 3px;
width: 11px; width: 11px;
height: 11px; height: 11px;
margin-left: 2px;
float: right; float: right;
background-position: -108px -52px; background-position: -108px -52px;
} }
.audioEntry .buttons .remove-icon-group { .audioEntry .buttons .remove-icon-group {
margin-top: 3px;
width: 13px; width: 13px;
height: 11px; height: 11px;
float: right; float: right;
background-position: -122px -52px; background-position: -122px -52px;
margin-left: 3px;
margin-right: 3px;
} }
.audioEmbed .lyrics { .audioEmbed .lyrics {
@ -410,12 +491,12 @@
text-decoration: underline; 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; pointer-events: none;
} }
.audioEmbed.withdrawn { .audioEmbed.withdrawn {
filter: opacity(0.8); opacity: 0.8;
} }
.playlistCover img { .playlistCover img {
@ -427,25 +508,55 @@
margin-top: 14px; margin-top: 14px;
} }
.playlistContainer { .playlistBlock .playlistWrapper {
display: grid; float: left;
grid-template-columns: repeat(3, 146px); padding-left: 13px;
gap: 18px 10px; width:75%
} }
.playlistContainer .playlistCover { .playlistCover .profile_links .profile_link {
width: 111px; width: 100%;
height: 111px; }
/* playlist listview */
.playlistListView {
display: flex; display: flex;
background: #c4c4c4; padding: 7px;
gap: 9px;
cursor: pointer;
} }
.playlistContainer .playlistCover img { .playlistListView:hover, .playlistListView .playlistCover {
max-width: 111px; background: #ebebeb;
max-height: 111px;
margin: auto;
} }
.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 { .ovk-diag-body .searchBox {
background: #e6e6e6; background: #e6e6e6;
padding-top: 10px; padding-top: 10px;
@ -490,17 +601,16 @@
cursor: pointer; cursor: pointer;
} }
.playlistCover img {
cursor: pointer;
}
.explicitMark { .explicitMark {
margin-top: 2px; margin-top: 2px;
margin-left: 3px; margin-left: 3px;
width: 11px; width: 11px;
height: 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 { .audioStatus span {
@ -517,8 +627,14 @@
padding-bottom: 3px; padding-bottom: 3px;
} }
.audiosDiv {
width: 103.1%;
display: flex;
margin: 0px 0px -10px -12px;
}
/* <center> 🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣*/ /* <center> 🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣*/
.audiosDiv center span { .audiosDiv center span, .error_full_wrapper center span {
color: #707070; color: #707070;
margin: 120px 0px !important; margin: 120px 0px !important;
display: block; display: block;
@ -528,15 +644,6 @@
margin-left: -10px; margin-left: -10px;
} }
.playlistInfo {
display: flex;
flex-direction: column;
}
.playlistInfo .playlistName {
font-weight: 600;
}
.searchList.floating { .searchList.floating {
position: fixed; position: fixed;
z-index: 199; z-index: 199;
@ -583,10 +690,13 @@
} }
.friendsAudiosList { .friendsAudiosList {
margin-left: -7px;
margin-top: 8px; margin-top: 8px;
} }
.friendsAudiosList > a {
padding-left: 8px;
}
.friendsAudiosList .elem { .friendsAudiosList .elem {
display: flex; display: flex;
padding: 1px 1px; padding: 1px 1px;
@ -659,3 +769,27 @@
.addToPlaylist { .addToPlaylist {
width: 22%; 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; display: inline-block;
height: 29px; height: 29px;
padding: 11px 4px 0 7px; padding: 11px 4px 0 7px;
}
.header_navigation .link, .header_navigation .header_divider_stick {
background: url('../img/divider.png') no-repeat; background: url('../img/divider.png') no-repeat;
background-size: 1.5px 41px; background-size: 1.5px 41px;
} }
.header_navigation .nodivider { .page_header.search_expanded .header_navigation .header_divider_stick {
display: inline-block; background: unset;
height: 29px;
padding: 11px 4px 0 7px;
background: none;
background-size: 1.5px 41px;
} }
.header_navigation .link a { .header_navigation .link a {
@ -427,6 +426,22 @@ h1 {
text-transform: lowercase; 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>a,
.profile_link.disable { .profile_link.disable {
cursor: not-allowed; cursor: not-allowed;
@ -587,6 +602,11 @@ input[type=radio]:hover {
background-position: 0 -28px; background-position: 0 -28px;
} }
input[type=checkbox]:disabled {
background-position: 0 -28px;
cursor: initial;
}
input[type=checkbox]:checked, input[type=checkbox]:checked,
input[type=radio]:checked { input[type=radio]:checked {
background-position: 0 -14px; background-position: 0 -14px;
@ -597,6 +617,10 @@ input[type=radio]:checked:hover {
background-position: 0 -42px; background-position: 0 -42px;
} }
input[type=checkbox]:checked:disabled {
background-position: 0 -42px;
}
#auth { #auth {
padding: 10px; padding: 10px;
} }
@ -1827,6 +1851,10 @@ body.scrolled .toTop:hover {
margin-top: -1px; margin-top: -1px;
} }
.paginator.tidy {
float: unset;
}
.paginator-at-bottom { .paginator-at-bottom {
margin-top: -6px; margin-top: -6px;
} }
@ -1970,6 +1998,18 @@ body.scrolled .toTop:hover {
line-height: normal; 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 { .summaryBar .summary {
color: #45688E; color: #45688E;
font-weight: bold; font-weight: bold;
@ -2059,18 +2099,16 @@ table td[width="120"] {
font-weight: bold; font-weight: bold;
margin-right: 10px; margin-right: 10px;
display: inline-block; display: inline-block;
border-radius: 2px;
} }
.mb_tab div { .mb_tab div, .mb_tab > a {
padding: 3px 7px; padding: 5px 9px;
display: block;
} }
.mb_tab#active { .mb_tab#active {
background-color: #898989; background-color: #606060;
}
.mb_tab#active div {
border: 2px solid #5f5f5f;
} }
.mb_tab#active a { .mb_tab#active a {
@ -2476,159 +2514,332 @@ a.poll-retract-vote {
display: none; display: none;
} }
.searchOptions { /* Da search */
overflow: hidden;
width:25.5%; /* Header part */
border-top:1px solid #E5E7E6;
float:right; .header_navigation #search_box {
scrollbar-width: none; display: inline-block;
font-size:12px; height: 29px;
background-color:#f7f7f7; padding: 11px 4px 0 7px;
margin-right: -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; border: solid 1px #575757;
background-color: #696969; background-color: #696969;
color:white; color: #ffffff;
margin-left: -11px; padding: 1px 0px 1px 0px;
padding-bottom:2px; width: 80px;
width:80px;
cursor: pointer; cursor: pointer;
box-shadow: 0px 2px 0px 0px rgba(255, 255, 255, 0.18) inset; 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; border: solid 1px #666666;
background-color: #696969; background-color: #696969;
color:white; color:white;
box-shadow: 0px -2px 0px 0px rgba(255, 255, 255, 0.18) inset; 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 { .searchHide {
padding-right: 5px; padding-right: 5px;
} }
.searchList li, .searchList a /* 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; display: block;
margin-left:0px; font-size: 12px;
color: #2B587A !important; color: #2B587A !important;
cursor:pointer; cursor: pointer;
padding:2px; padding: 5px 9px 5px 9px;
padding-top:5px;
padding-bottom:5px;
margin-bottom:2px;
padding-left:9px;
} }
.searchList li a { .searchList a:hover, .verticalGrayTabs a:hover {
min-width:100%;
}
.searchList a {
min-width: 88%;
}
.searchList a:hover {
margin-left: 0px; margin-left: 0px;
color: #2B587A !important; color: #2B587A !important;
background: #ebebeb; background: #ebebeb;
padding: 2px;
padding-top: 5px;
padding-bottom: 5px;
margin-bottom: 2px;
padding-left: 9px;
width: 89.9%;
} }
.whatFind { .highlight {
color:rgb(128, 128, 128); background: #ffea6d;
background:none; border-bottom: 1px solid #c7c727;
border:none; font-weight: bolder;
position:absolute; padding: 0px 1px;
width:150px;
cursor:pointer;
right:80px;
text-align:right;
margin-top: 0.5px;
} }
.searchOptionName { /* Options */
cursor:pointer;
background-color: #EAEAEA; .page_wrap_content .page_search_options {
padding-left:5px; padding: 0px 4px 4px 4px;
padding-top:5px;
padding-bottom:5px;
width: 90%;
font-weight: 600;
color: #585858;
border-bottom: 2px solid #E4E4E4;
} }
.searchOption { .page_wrap_content .page_search_options .search_option {
user-select: none; 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; margin-top: -5px;
padding-top:10px; padding-top: 10px;
max-width:92%; padding-bottom: 6px;
padding-bottom:6px;
} }
.searchOptionBlock select .page_wrap_content .page_search_options .search_option .search_option_content label {
{ display: block;
padding-left:7px;
padding-top:3px;
padding-bottom:3px;
cursor:pointer;
} }
.searchOptionBlock input[type=text], input[type=date] .page_wrap_content .page_search_options .search_option .search_option_content select {
{ padding-left: 3px;
margin-bottom:5px; padding-top: 3px;
padding-bottom: 3px;
width: 100%;
} }
.borderup .page_wrap_content .page_search_options #search_reset {
{ margin-top: 5px;
border-top:1px solid #E5E7E6;
} }
#searchInput .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;
transition: .3s linear;
} }
.content_page_error {
background: white;
border: #DEDEDE solid 1px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.content_page_error span {
color: #707070;
display: block;
}
/* Da search end. */
#standaloneCommentBox { #standaloneCommentBox {
position: sticky; position: sticky;
top: 0; top: 0;
@ -2646,52 +2857,6 @@ a.poll-retract-vote {
width: 100%; 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 { .attachment_note_icon {
max-width: 9px; max-width: 9px;
} }
@ -3066,12 +3231,6 @@ hr {
height: 1px; height: 1px;
} }
.searchList hr {
width: 153px;
margin-left: 0px;
margin-top: 6px;
}
.showMore, .showMoreAudiosPlaylist { .showMore, .showMoreAudiosPlaylist {
width: 100%; width: 100%;
text-align: center; text-align: center;
@ -3089,3 +3248,54 @@ hr {
background-repeat: no-repeat !important; background-repeat: no-repeat !important;
background-position: 50% !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