Compare commits

..

No commits in common. "7197863f00283a5f3d6eec2e8892554042d7052e" and "00f5b53ea8ddeb32b50528c90f0cf88b9a1f6b59" have entirely different histories.

337 changed files with 5125 additions and 30358 deletions

2
.github/FUNDING.yml vendored
View file

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

View file

@ -2,12 +2,11 @@ name: Build base images
on: on:
schedule: schedule:
- cron: "0 0 * * *" - cron: '0 0 * * *'
workflow_dispatch:
env: env:
BASE_IMAGE_NAME: php BASE_IMAGE_NAME: php
BASE_IMAGE_VERSION: "8.2" BASE_IMAGE_VERSION: "8.1"
jobs: jobs:
build-cli: build-cli:
@ -17,29 +16,23 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
lfs: false lfs: false
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- 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 cli image - name: Build cli image
run: | run: |
IMAGE_NAME=ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/$BASE_IMAGE_NAME:$BASE_IMAGE_VERSION-cli IMAGE_NAME=ghcr.io/${{ github.repository }}/$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
build-apache: build-apache:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -47,25 +40,19 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
lfs: false lfs: false
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- 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/${{ steps.repositorystring.outputs.lowercase }}/$BASE_IMAGE_NAME:$BASE_IMAGE_VERSION-apache IMAGE_NAME=ghcr.io/${{ github.repository }}/$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

@ -1,6 +1,14 @@
name: Build images name: Build images
on: [push, pull_request] on:
push:
# Publish `master` as Docker `latest` image.
branches:
- master
# Publish `v1.2.3` tags as releases.
tags:
- v*
env: env:
BASE_IMAGE_NAME: openvk BASE_IMAGE_NAME: openvk
@ -9,136 +17,48 @@ env:
DB_VERSION: "10.9" DB_VERSION: "10.9"
jobs: jobs:
buildbase: build:
name: Build base images runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
platform: [amd64, arm64] arch: ['x86_64']
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps: steps:
- uses: actions/checkout@v3
with:
lfs: false
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@v3 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: Base image meta
id: basemeta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/${{env.BASE_IMAGE_NAME}}
labels: |
org.opencontainers.image.documentation=https://github.com/OpenVK/openvk/blob/master/install/automated/docker/Readme.md
tags: |
type=sha
type=ref,event=branch
type=ref,event=pr
type=ref,event=tag
type=raw,value=latest,enable={{is_default_branch}}
- name: Log into registry - name: Log into registry
if: github.event_name != 'pull_request'
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
uses: docker/build-push-action@v6 run: |
with: IMAGE_ID=ghcr.io/${{ github.repository }}/$BASE_IMAGE_NAME
platforms: linux/${{matrix.platform}} IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
file: install/automated/docker/openvk.Dockerfile VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
tags: ${{ steps.basemeta.outputs.tags }} [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
labels: ${{ steps.basemeta.outputs.labels }} [ "$VERSION" == "master" ] && VERSION=latest
push: ${{ github.event_name != 'pull_request' }} echo IMAGE_ID=$IMAGE_ID
build-args: | echo VERSION=$VERSION
GITREPO=${{ steps.repositorystring.outputs.lowercase }}
builddb: docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_ID:$VERSION . --push -f install/automated/docker/openvk.Dockerfile --build-arg GITREPO=${{ github.repository }}
name: Build DB images
strategy:
matrix:
platform: [amd64, arm64]
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Change repository string to lowercase
id: repositorystring
uses: Entepotenz/change-string-case-action-min-dependencies@v1.1.0
with:
string: ${{ github.repository }}
- name: MariaDB primary meta
id: db-primarymeta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/${{env.DB_IMAGE_NAME}}
labels: |
org.opencontainers.image.title=OpenVK MariaDB (Primary)
org.opencontainers.image.description=OpenVK's image for MariaDB for primary database.
org.opencontainers.image.documentation=https://github.com/OpenVK/openvk/blob/master/install/automated/docker/Readme.md
tags: |
type=sha,prefix=${{env.DB_VERSION}}-primary-sha-
type=ref,event=branch,prefix=${{env.DB_VERSION}}-primary-
type=ref,event=pr,prefix=${{env.DB_VERSION}}-primary-pr-
type=ref,event=tag,prefix=${{env.DB_VERSION}}-primary-
type=raw,value=${{env.DB_VERSION}}-primary,enable={{is_default_branch}}
- name: MariaDB event meta
id: db-eventmeta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/${{env.DB_IMAGE_NAME}}
labels: |
org.opencontainers.image.title=OpenVK MariaDB (EventDB)
org.opencontainers.image.description=OpenVK's image for MariaDB for event database.
org.opencontainers.image.documentation=https://github.com/OpenVK/openvk/blob/master/install/automated/docker/Readme.md
tags: |
type=sha,prefix=${{env.DB_VERSION}}-eventdb-sha-
type=ref,event=branch,prefix=${{env.DB_VERSION}}-eventdb-
type=ref,event=pr,prefix=${{env.DB_VERSION}}-eventdb-pr-
type=ref,event=tag,prefix=${{env.DB_VERSION}}-eventdb-
type=raw,value=${{env.DB_VERSION}}-eventdb,enable={{is_default_branch}}
- name: Log into registry
if: github.event_name != 'pull_request'
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Build MariaDB primary image - name: Build MariaDB primary image
uses: docker/build-push-action@v6 run: |
with: IMAGE_NAME=ghcr.io/${{ github.repository }}/$DB_IMAGE_NAME:$DB_VERSION-primary
push: ${{ github.event_name != 'pull_request' }}
platforms: linux/${{matrix.platform}} docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_NAME . --push -f install/automated/docker/mariadb-primary.Dockerfile --build-arg VERSION=$DB_VERSION
file: install/automated/docker/mariadb-primary.Dockerfile
tags: ${{ steps.db-primarymeta.outputs.tags }}
labels: ${{ steps.db-primarymeta.outputs.labels }}
build-args: |
VERSION=${{env.DB_VERSION}}
- name: Build MariaDB event image - name: Build MariaDB event image
uses: docker/build-push-action@v6 run: |
with: IMAGE_NAME=ghcr.io/${{ github.repository }}/$EVENT_IMAGE_NAME:$DB_VERSION-eventdb
push: ${{ github.event_name != 'pull_request' }}
platforms: linux/${{matrix.platform}} docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_NAME . --push -f install/automated/docker/mariadb-eventdb.Dockerfile --build-arg VERSION=$DB_VERSION
file: install/automated/docker/mariadb-eventdb.Dockerfile
tags: ${{ steps.db-eventmeta.outputs.tags }}
labels: ${{ steps.db-eventmeta.outputs.labels }}
build-args: |
VERSION=${{env.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://ovk.to/support?act=new">сюда</a> Добро пожаловать в OpenVK! Приятного времяприпровождения, надеюсь вам понравится.<br><br>Если появились вопросы, касаемые нашего сайта, пишите <a href="https://openvk.su/support?act=new">сюда</a>
</center> </center>
</td> </td>
</tr> </tr>

View file

@ -1,12 +1,12 @@
# <img align="right" src="/Web/static/img/logo_shadow.png" alt="openvk" title="openvk" width="15%">OpenVK # <img align="right" src="https://github.com/openvk/openvk/raw/master/Web/static/img/logo_shadow.png" alt="openvk" title="openvk" width="15%">OpenVK
_[Русский](README_RU.md)_ _[Русский](README_RU.md)_
**OpenVK** is an attempt to create a simple CMS that ~~cosplays~~ imitates old VKontakte. Code provided here is not stable yet. **OpenVK** is an attempt to create a simple CMS that ~~cosplays~~ imitates old VKontakte. Code provided here is not stable yet.
VKontakte belongs to VK (formerly Mail.ru 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://ovk.to/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://openvk.su/support?act=new) (you will need an OpenVK account for this).
## When's the release? ## When's the release?
@ -26,19 +26,11 @@ 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, NPM 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)
* PHP 8 is still being tested; the functionality of the engine on this version of PHP is not yet guaranteed. * PHP 8.1 is supported too, however it was not tested carefully, so be aware.
2. Install MySQL-compatible database. 2. Install MySQL-compatible database.
@ -65,7 +57,7 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions
7. Copy `openvk-example.yml` to `openvk.yml` and change options to your liking 7. Copy `openvk-example.yml` to `openvk.yml` and change options to your liking
8. Run `composer install` in OpenVK directory 8. Run `composer install` in OpenVK directory
9. Run `composer install` in commitcaptcha directory 9. Run `composer install` in commitcaptcha directory
10. Move to `Web/static/js` and execute `npm install` 10. Move to `Web/static/js` and execute `yarn install`
11. Set `openvk` as your root app in `chandler.yml` 11. Set `openvk` as your root app in `chandler.yml`
Once you are done, you can login as a system administrator on the network itself (no registration required): Once you are done, you can login as a system administrator on the network itself (no registration required):
@ -74,12 +66,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.ovk.to/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.openvk.uk/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 its sources? ### If my website uses OpenVK, should I release it's 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).
@ -88,7 +80,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://ovk.to/support?act=new) * [Ticketing System](https://openvk.su/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,12 +1,12 @@
# <img align="right" src="/Web/static/img/logo_shadow.png" alt="openvk" title="openvk" width="15%">OpenVK # <img align="right" src="https://github.com/openvk/openvk/raw/master/Web/static/img/logo_shadow.png" alt="openvk" title="openvk" width="15%">OpenVK
_[English](README.md)_ _[English](README.md)_
**OpenVK** — это попытка создать простую CMS, которая ~~косплеит~~ имитирует старый ВКонтакте. На данный момент, представленный здесь исходный код проекта пока не является стабильным. **OpenVK** — это попытка создать простую CMS, которая ~~косплеит~~ имитирует старый ВКонтакте. На данный момент, представленный здесь исходный код проекта пока не является стабильным.
ВКонтакте принадлежит VK (в прошлом Mail.ru Group). ВКонтакте принадлежит Павлу Дурову и VK Group.
Честно говоря, мы даже не знаем, работает ли она вообще. Однако, эта версия поддерживается, и мы будем рады принять ваши сообщения об ошибках [в нашем баг-трекере](https://github.com/openvk/openvk/projects/1). Вы также можете отправлять их через [вкладку "Помощь"](https://ovk.to/support?act=new) (для этого вам понадобится учетная запись OpenVK). Честно говоря, мы даже не знаем, работает ли она вообще. Однако, эта версия поддерживается, и мы будем рады принять ваши сообщения об ошибках [в нашем баг-трекере](https://github.com/openvk/openvk/projects/1). Вы также можете отправлять их через [вкладку "Помощь"](https://openvk.su/support?act=new) (для этого вам понадобится учетная запись OpenVK).
## Когда выйдет релизная версия? ## Когда выйдет релизная версия?
@ -28,9 +28,9 @@ _[English](README.md)_
### Процедура установки ### Процедура установки
1. Установите PHP 7.4, веб-сервер, Composer, Node.js, NPM и [Chandler](https://github.com/openvk/chandler) 1. Установите PHP 7.4, веб-сервер, Composer, Node.js, Yarn и [Chandler](https://github.com/openvk/chandler)
* PHP 8 пока ещё тестируется, работоспособность движка на этой версии PHP пока не гарантируется. * PHP 8 еще **не** тестировался, поэтому не стоит ожидать, что он будет работать (UPD: он не работает).
2. Установите MySQL-совместимую базу данных. 2. Установите MySQL-совместимую базу данных.
@ -57,7 +57,7 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions
7. Скопируйте `openvk-example.yml` в `openvk.yml` и измените параметры под свои нужды 7. Скопируйте `openvk-example.yml` в `openvk.yml` и измените параметры под свои нужды
8. Запустите `composer install` в директории OpenVK 8. Запустите `composer install` в директории OpenVK
9. Запустите `composer install` в директории commitcaptcha 9. Запустите `composer install` в директории commitcaptcha
10. Перейдите в `Web/static/js` и выполните `npm install` 10. Перейдите в `Web/static/js` и выполните `yarn install`
11. Установите `openvk` в качестве корневого приложения в файле `chandler.yml` 11. Установите `openvk` в качестве корневого приложения в файле `chandler.yml`
После этого вы можете войти как системный администратор в саму сеть (регистрация не требуется): После этого вы можете войти как системный администратор в саму сеть (регистрация не требуется):
@ -66,7 +66,7 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions
* **Пароль**: `admin` * **Пароль**: `admin`
* Перед использованием встроенной учетной записи рекомендуется сменить пароль или отключить её. * Перед использованием встроенной учетной записи рекомендуется сменить пароль или отключить её.
💡Запутались? Полное руководство по установке доступно [здесь](https://docs.ovk.to/openvk_engine/centos8_installation/) (CentOS 8 [и](https://almalinux.org/ru/) [семейство](https://yum.oracle.com/oracle-linux-isos.html)). 💡Запутались? Полное руководство по установке доступно [здесь](https://docs.openvk.uk/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://ovk.to/support?act=new) * [Помощь в OVK](https://openvk.su/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,11 +1,8 @@
<?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
{ {
@ -92,25 +89,4 @@ 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

@ -25,9 +25,6 @@ class Notes implements Handler
assert($noteOwner instanceof User); assert($noteOwner instanceof User);
if(!$noteOwner->getPrivacyPermission("notes.read", $this->user)) if(!$noteOwner->getPrivacyPermission("notes.read", $this->user))
$reject(160, "You don't have permission to access this note"); $reject(160, "You don't have permission to access this note");
if(!$note->canBeViewedBy($this->user))
$reject(15, "Access to note denied");
$resolve([ $resolve([
"title" => $note->getName(), "title" => $note->getName(),

76
ServiceAPI/Search.php Normal file
View file

@ -0,0 +1,76 @@
<?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()], $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

@ -2,7 +2,7 @@
namespace openvk\ServiceAPI; namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\Post; use openvk\Web\Models\Entities\Post;
use openvk\Web\Models\Entities\User; use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\{Posts, Notes, Videos}; use openvk\Web\Models\Repositories\{Posts, Notes};
class Wall implements Handler class Wall implements Handler
{ {
@ -15,21 +15,14 @@ class Wall implements Handler
$this->user = $user; $this->user = $user;
$this->posts = new Posts; $this->posts = new Posts;
$this->notes = new Notes; $this->notes = new Notes;
$this->videos = new Videos;
} }
function getPost(int $id, callable $resolve, callable $reject): void function getPost(int $id, callable $resolve, callable $reject): void
{ {
$post = $this->posts->get($id); $post = $this->posts->get($id);
if(!$post || $post->isDeleted()) if(!$post || $post->isDeleted())
$reject(53, "No post with id=$id"); $reject("No post with id=$id");
if($post->getSuggestionType() != 0)
$reject(25, "Can't get suggested post");
if(!$post->canBeViewedBy($this->user))
$reject(12, "Access denied");
$res = (object) []; $res = (object) [];
$res->id = $post->getId(); $res->id = $post->getId();
$res->wall = $post->getTargetWall(); $res->wall = $post->getTargetWall();
@ -80,4 +73,26 @@ class Wall implements Handler
$resolve($post->getId()); $resolve($post->getId());
} }
function getMyNotes(callable $resolve, callable $reject)
{
$count = $this->notes->getUserNotesCount($this->user);
$myNotes = $this->notes->getUserNotes($this->user, 1, $count);
$arr = [
"count" => $count,
"closed" => $this->user->getPrivacySetting("notes.read"),
"items" => [],
];
foreach($myNotes as $note) {
$arr["items"][] = [
"id" => $note->getId(),
"name" => ovk_proc_strtr($note->getName(), 30),
#"preview" => $note->getPreview()
];
}
$resolve($arr);
}
} }

View file

@ -7,32 +7,19 @@ final class Account extends VKAPIRequestHandler
function getProfileInfo(): object function getProfileInfo(): object
{ {
$this->requireUser(); $this->requireUser();
$user = $this->getUser();
$return_object = (object) [ return (object) [
"first_name" => $user->getFirstName(), "first_name" => $this->getUser()->getFirstName(),
"photo_200" => $user->getAvatarURL("normal"), "id" => $this->getUser()->getId(),
"nickname" => $user->getPseudo(), "last_name" => $this->getUser()->getLastName(),
"is_service_account" => false, "home_town" => $this->getUser()->getHometown(),
"id" => $user->getId(), "status" => $this->getUser()->getStatus(),
"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" => $user->getMaritalStatus(), "relation" => $this->getUser()->getMaritalStatus(),
"screen_name" => $user->getShortCode(), "sex" => $this->getUser()->isFemale() ? 1 : 2
"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
@ -164,68 +151,4 @@ 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;
}
function sendVotes(int $receiver, int $value, string $message = ""): object
{
$this->requireUser();
$this->willExecuteWriteAction();
if(!OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"])
$this->fail(-105, "Commerce is disabled on this instance");
if($receiver < 0)
$this->fail(-248, "Invalid receiver id");
if($value < 1)
$this->fail(-248, "Invalid value");
if(iconv_strlen($message) > 255)
$this->fail(-249, "Message is too long");
if($this->getUser()->getCoins() < $value)
$this->fail(-252, "Not enough votes");
$receiver_entity = (new \openvk\Web\Models\Repositories\Users)->get($receiver);
if(!$receiver_entity || $receiver_entity->isDeleted())
$this->fail(-250, "Invalid receiver");
if($receiver_entity->getId() === $this->getUser()->getId())
$this->fail(-251, "Can't transfer votes to yourself");
$this->getUser()->setCoins($this->getUser()->getCoins() - $value);
$this->getUser()->save();
$receiver_entity->setCoins($receiver_entity->getCoins() + $value);
$receiver_entity->save();
(new \openvk\Web\Models\Entities\Notifications\CoinsTransferNotification($receiver_entity, $this->getUser(), $value, $message))->emit();
return (object) ['votes' => $this->getUser()->getCoins()];
}
} }

View file

@ -1,793 +1,22 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers; namespace openvk\VKAPI\Handlers;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Entities\Audio as AEntity;
use openvk\Web\Models\Entities\Playlist;
use openvk\Web\Models\Repositories\Audios;
use openvk\Web\Models\Repositories\Clubs;
use openvk\Web\Models\Repositories\Util\EntityStream;
final class Audio extends VKAPIRequestHandler final class Audio extends VKAPIRequestHandler
{ {
private function toSafeAudioStruct(?AEntity $audio, ?string $hash = NULL, bool $need_user = false): object function get(): object
{
if(!$audio)
$this->fail(0404, "Audio not found");
else if(!$audio->canBeViewedBy($this->getUser()))
$this->fail(201, "Access denied to audio(" . $audio->getPrettyId() . ")");
# рофлан ебало
$privApi = $hash && $GLOBALS["csrfCheck"];
$audioObj = $audio->toVkApiStruct($this->getUser());
if(!$privApi) {
$audioObj->manifest = false;
$audioObj->keys = false;
}
if($need_user) {
$user = (new \openvk\Web\Models\Repositories\Users)->get($audio->getOwner()->getId());
$audioObj->user = (object) [
"id" => $user->getId(),
"photo" => $user->getAvatarUrl(),
"name" => $user->getCanonicalName(),
"name_gen" => $user->getCanonicalName(),
];
}
return $audioObj;
}
private function streamToResponse(EntityStream $es, int $offset, int $count, ?string $hash = NULL): object
{
$items = [];
foreach($es->offsetLimit($offset, $count) as $audio) {
$items[] = $this->toSafeAudioStruct($audio, $hash);
}
return (object) [
"count" => sizeof($items),
"items" => $items,
];
}
private function validateGenre(?string& $genre_str, ?int $genre_id): void
{
if(!is_null($genre_str)) {
if(!in_array($genre_str, AEntity::genres))
$this->fail(8, "Invalid genre_str");
} else if(!is_null($genre_id)) {
$genre_str = array_flip(AEntity::vkGenres)[$genre_id] ?? NULL;
if(!$genre_str)
$this->fail(8, "Invalid genre ID $genre_id");
}
}
private function audioFromAnyId(string $id): ?AEntity
{
$descriptor = explode("_", $id);
if(sizeof($descriptor) === 1) {
if(ctype_digit($descriptor[0])) {
$audio = (new Audios)->get((int) $descriptor[0]);
} else {
$aid = base64_decode($descriptor[0], true);
if(!$aid)
$this->fail(8, "Invalid audio $id");
$audio = (new Audios)->get((int) $aid);
}
} else if(sizeof($descriptor) === 2) {
$audio = (new Audios)->getByOwnerAndVID((int) $descriptor[0], (int) $descriptor[1]);
} else {
$this->fail(8, "Invalid audio $id");
}
return $audio;
}
function getById(string $audios, ?string $hash = NULL, int $need_user = 0): object
{
$this->requireUser();
$audioIds = array_unique(explode(",", $audios));
if(sizeof($audioIds) === 1) {
$audio = $this->audioFromAnyId($audioIds[0]);
return (object) [
"count" => 1,
"items" => [
$this->toSafeAudioStruct($audio, $hash, (bool) $need_user),
],
];
} else if(sizeof($audioIds) > 6000) {
$this->fail(1980, "Can't get more than 6000 audios at once");
}
$audios = [];
foreach($audioIds as $id)
$audios[] = $this->getById($id, $hash)->items[0];
return (object) [
"count" => sizeof($audios),
"items" => $audios,
];
}
function isLagtrain(string $audio_id): int
{
$this->requireUser();
$audio = $this->audioFromAnyId($audio_id);
if(!$audio)
$this->fail(0404, "Audio not found");
# Possible information disclosure risks are acceptable :D
return (int) (strpos($audio->getName(), "Lagtrain") !== false);
}
// TODO stub
function getRecommendations(): object
{
return (object) [
"count" => 0,
"items" => [],
];
}
function getPopular(?int $genre_id = NULL, ?string $genre_str = NULL, int $offset = 0, int $count = 100, ?string $hash = NULL): object
{
$this->requireUser();
$this->validateGenre($genre_str, $genre_id);
$results = (new Audios)->getGlobal(Audios::ORDER_POPULAR, $genre_str);
return $this->streamToResponse($results, $offset, $count, $hash);
}
function getFeed(?int $genre_id = NULL, ?string $genre_str = NULL, int $offset = 0, int $count = 100, ?string $hash = NULL): object
{
$this->requireUser();
$this->validateGenre($genre_str, $genre_id);
$results = (new Audios)->getGlobal(Audios::ORDER_NEW, $genre_str);
return $this->streamToResponse($results, $offset, $count, $hash);
}
function search(string $q, int $auto_complete = 0, int $lyrics = 0, int $performer_only = 0, int $sort = 2, int $search_own = 0, int $offset = 0, int $count = 30, ?string $hash = NULL): object
{
$this->requireUser();
if(($auto_complete + $search_own) != 0)
$this->fail(10, "auto_complete and search_own are not supported");
else if($count > 300 || $count < 1)
$this->fail(8, "count is invalid: $count");
$results = (new Audios)->search($q, $sort, (bool) $performer_only, (bool) $lyrics);
return $this->streamToResponse($results, $offset, $count, $hash);
}
function getCount(int $owner_id, int $uploaded_only = 0): int
{
$this->requireUser();
if($owner_id < 0) {
$owner_id *= -1;
$group = (new Clubs)->get($owner_id);
if(!$group)
$this->fail(0404, "Group not found");
return (new Audios)->getClubCollectionSize($group);
}
$user = (new \openvk\Web\Models\Repositories\Users)->get($owner_id);
if(!$user)
$this->fail(0404, "User not found");
if(!$user->getPrivacyPermission("audios.read", $this->getUser()))
$this->fail(15, "Access denied");
if($uploaded_only) {
return DatabaseConnection::i()->getContext()->table("audios")
->where([
"deleted" => false,
"owner" => $owner_id,
])->count();
}
return (new Audios)->getUserCollectionSize($user);
}
function get(int $owner_id = 0, int $album_id = 0, string $audio_ids = '', int $need_user = 1, int $offset = 0, int $count = 100, int $uploaded_only = 0, int $need_seed = 0, ?string $shuffle_seed = NULL, int $shuffle = 0, ?string $hash = NULL): object
{ {
$this->requireUser(); $serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"];
$shuffleSeed = NULL; return (object) [
$shuffleSeedStr = NULL; "count" => 1,
if($shuffle == 1) { "items" => [(object) [
if(!$shuffle_seed) { "id" => 1,
if($need_seed == 1) { "owner_id" => 1,
$shuffleSeed = openssl_random_pseudo_bytes(6); "artist" => "В ОВК ПОКА НЕТ МУЗЫКИ",
$shuffleSeedStr = base64_encode($shuffleSeed); "title" => "ЖДИТЕ :)))",
$shuffleSeed = hexdec(bin2hex($shuffleSeed)); "duration" => 22,
} else { "url" => $serverUrl . "/assets/packages/static/openvk/audio/nomusic.mp3"
$hOffset = ((int) date("i") * 60) + (int) date("s"); ]]
$thisHour = time() - $hOffset; ];
$shuffleSeed = $thisHour + $this->getUser()->getId();
$shuffleSeedStr = base64_encode(hex2bin(dechex($shuffleSeed)));
}
} else {
$shuffleSeed = hexdec(bin2hex(base64_decode($shuffle_seed)));
$shuffleSeedStr = $shuffle_seed;
}
}
if($album_id != 0) {
$album = (new Audios)->getPlaylist($album_id);
if(!$album)
$this->fail(0404, "album_id invalid");
else if(!$album->canBeViewedBy($this->getUser()))
$this->fail(600, "Can't open this album for reading");
$songs = [];
$list = $album->getAudios($offset, $count, $shuffleSeed);
foreach($list as $song)
$songs[] = $this->toSafeAudioStruct($song, $hash, $need_user == 1);
$response = (object) [
"count" => sizeof($songs),
"items" => $songs,
];
if(!is_null($shuffleSeed))
$response->shuffle_seed = $shuffleSeedStr;
return $response;
}
if(!empty($audio_ids)) {
$audio_ids = explode(",", $audio_ids);
if(!$audio_ids)
$this->fail(10, "Audio::get@L0d186:explode(string): Unknown error");
else if(sizeof($audio_ids) < 1)
$this->fail(8, "Invalid audio_ids syntax");
if(!is_null($shuffleSeed))
$audio_ids = knuth_shuffle($audio_ids, $shuffleSeed);
$obj = $this->getById(implode(",", $audio_ids), $hash, $need_user);
if(!is_null($shuffleSeed))
$obj->shuffle_seed = $shuffleSeedStr;
return $obj;
}
$dbCtx = DatabaseConnection::i()->getContext();
if($uploaded_only == 1) {
if($owner_id <= 0)
$this->fail(8, "uploaded_only can only be used with owner_id > 0");
$user = (new \openvk\Web\Models\Repositories\Users)->get($owner_id);
if(!$user)
$this->fail(0602, "Invalid user");
if(!$user->getPrivacyPermission("audios.read", $this->getUser()))
$this->fail(15, "Access denied: this user chose to hide his audios");
if(!is_null($shuffleSeed)) {
$audio_ids = [];
$query = $dbCtx->table("audios")->select("virtual_id")->where([
"owner" => $owner_id,
"deleted" => 0,
]);
foreach($query as $res)
$audio_ids[] = $res->virtual_id;
$audio_ids = knuth_shuffle($audio_ids, $shuffleSeed);
$audio_ids = array_slice($audio_ids, $offset, $count);
$audio_q = ""; # audio.getById query
foreach($audio_ids as $aid)
$audio_q .= ",$owner_id" . "_$aid";
$obj = $this->getById(substr($audio_q, 1), $hash, $need_user);
$obj->shuffle_seed = $shuffleSeedStr;
return $obj;
}
$res = (new Audios)->getByUploader((new \openvk\Web\Models\Repositories\Users)->get($owner_id));
return $this->streamToResponse($res, $offset, $count, $hash, $need_user);
}
$query = $dbCtx->table("audio_relations")->select("audio")->where("entity", $owner_id);
if(!is_null($shuffleSeed)) {
$audio_ids = [];
foreach($query as $aid)
$audio_ids[] = $aid->audio;
$audio_ids = knuth_shuffle($audio_ids, $shuffleSeed);
$audio_ids = array_slice($audio_ids, $offset, $count);
$audio_q = "";
foreach($audio_ids as $aid)
$audio_q .= ",$aid";
$obj = $this->getById(substr($audio_q, 1), $hash, $need_user);
$obj->shuffle_seed = $shuffleSeedStr;
return $obj;
}
$items = [];
if($owner_id > 0) {
$user = (new \openvk\Web\Models\Repositories\Users)->get($owner_id);
if(!$user)
$this->fail(50, "Invalid user");
if(!$user->getPrivacyPermission("audios.read", $this->getUser()))
$this->fail(15, "Access denied: this user chose to hide his audios");
}
$audios = (new Audios)->getByEntityID($owner_id, $offset, $count);
foreach($audios as $audio)
$items[] = $this->toSafeAudioStruct($audio, $hash, $need_user == 1);
return (object) [
"count" => sizeof($items),
"items" => $items,
];
} }
function getLyrics(int $lyrics_id): object
{
$this->requireUser();
$audio = (new Audios)->get($lyrics_id);
if(!$audio || !$audio->getLyrics())
$this->fail(0404, "Not found");
if(!$audio->canBeViewedBy($this->getUser()))
$this->fail(201, "Access denied to lyrics");
return (object) [
"lyrics_id" => $lyrics_id,
"text" => preg_replace("%\r\n?%", "\n", $audio->getLyrics()),
];
}
function beacon(int $aid, ?int $gid = NULL): int
{
$this->requireUser();
$this->willExecuteWriteAction();
$audio = (new Audios)->get($aid);
if(!$audio)
$this->fail(0404, "Not Found");
else if(!$audio->canBeViewedBy($this->getUser()))
$this->fail(201, "Insufficient permissions to listen this audio");
$group = NULL;
if(!is_null($gid)) {
$group = (new Clubs)->get($gid);
if(!$group)
$this->fail(0404, "Not Found");
else if(!$group->canBeModifiedBy($this->getUser()))
$this->fail(203, "Insufficient rights to this group");
}
return (int) $audio->listen($group ?? $this->getUser());
}
function setBroadcast(string $audio, string $target_ids): array
{
$this->requireUser();
[$owner, $aid] = explode("_", $audio);
$song = (new Audios)->getByOwnerAndVID((int) $owner, (int) $aid);
$ids = [];
foreach(explode(",", $target_ids) as $id) {
$id = (int) $id;
if($id > 0) {
if ($id != $this->getUser()->getId()) {
$this->fail(600, "Can't listen on behalf of $id");
} else {
$ids[] = $id;
$this->beacon($song->getId());
continue;
}
}
$group = (new Clubs)->get($id * -1);
if(!$group)
$this->fail(0404, "Not Found");
else if(!$group->canBeModifiedBy($this->getUser()))
$this->fail(203,"Insufficient rights to this group");
$ids[] = $id;
$this->beacon($song ? $song->getId() : 0, $id * -1);
}
return $ids;
}
function getBroadcastList(string $filter = "all", int $active = 0, ?string $hash = NULL): object
{
$this->requireUser();
if(!in_array($filter, ["all", "friends", "groups"]))
$this->fail(8, "Invalid filter $filter");
$broadcastList = $this->getUser()->getBroadcastList($filter);
$items = [];
foreach($broadcastList as $res) {
$struct = $res->toVkApiStruct();
$status = $res->getCurrentAudioStatus();
$struct->status_audio = $status ? $this->toSafeAudioStruct($status) : NULL;
$items[] = $struct;
}
return (object) [
"count" => sizeof($items),
"items" => $items,
];
}
function edit(int $owner_id, int $audio_id, ?string $artist = NULL, ?string $title = NULL, ?string $text = NULL, ?int $genre_id = NULL, ?string $genre_str = NULL, int $no_search = 0): int
{
$this->requireUser();
$this->willExecuteWriteAction();
$audio = (new Audios)->getByOwnerAndVID($owner_id, $audio_id);
if(!$audio)
$this->fail(0404, "Not Found");
else if(!$audio->canBeModifiedBy($this->getUser()))
$this->fail(201, "Insufficient permissions to edit this audio");
if(!is_null($genre_id)) {
$genre = array_flip(AEntity::vkGenres)[$genre_id] ?? NULL;
if(!$genre)
$this->fail(8, "Invalid genre ID $genre_id");
$audio->setGenre($genre);
} else if(!is_null($genre_str)) {
if(!in_array($genre_str, AEntity::genres))
$this->fail(8, "Invalid genre ID $genre_str");
$audio->setGenre($genre_str);
}
$lyrics = 0;
if(!is_null($text)) {
$audio->setLyrics($text);
$lyrics = $audio->getId();
}
if(!is_null($artist))
$audio->setPerformer($artist);
if(!is_null($title))
$audio->setName($title);
$audio->setSearchability(!((bool) $no_search));
$audio->setEdited(time());
$audio->save();
return $lyrics;
}
function add(int $audio_id, int $owner_id, ?int $group_id = NULL, ?int $album_id = NULL): string
{
$this->requireUser();
$this->willExecuteWriteAction();
if(!is_null($album_id))
$this->fail(10, "album_id not implemented");
// TODO get rid of dups
$to = $this->getUser();
if(!is_null($group_id)) {
$group = (new Clubs)->get($group_id);
if(!$group)
$this->fail(0404, "Invalid group_id");
else if(!$group->canBeModifiedBy($this->getUser()))
$this->fail(203, "Insufficient rights to this group");
$to = $group;
}
$audio = (new Audios)->getByOwnerAndVID($owner_id, $audio_id);
if(!$audio)
$this->fail(0404, "Not found");
else if(!$audio->canBeViewedBy($this->getUser()))
$this->fail(201, "Access denied to audio(owner=$owner_id, vid=$audio_id)");
try {
$audio->add($to);
} catch(\OverflowException $ex) {
$this->fail(300, "Album is full");
}
return $audio->getPrettyId();
}
function delete(int $audio_id, int $owner_id, ?int $group_id = NULL): int
{
$this->requireUser();
$this->willExecuteWriteAction();
$from = $this->getUser();
if(!is_null($group_id)) {
$group = (new Clubs)->get($group_id);
if(!$group)
$this->fail(0404, "Invalid group_id");
else if(!$group->canBeModifiedBy($this->getUser()))
$this->fail(203, "Insufficient rights to this group");
$from = $group;
}
$audio = (new Audios)->getByOwnerAndVID($owner_id, $audio_id);
if(!$audio)
$this->fail(0404, "Not found");
$audio->remove($from);
return 1;
}
function restore(int $audio_id, int $owner_id, ?int $group_id = NULL, ?string $hash = NULL): object
{
$this->requireUser();
$vid = $this->add($audio_id, $owner_id, $group_id);
return $this->getById($vid, $hash)->items[0];
}
function getAlbums(int $owner_id = 0, int $offset = 0, int $count = 50, int $drop_private = 1): object
{
$this->requireUser();
$owner_id = $owner_id == 0 ? $this->getUser()->getId() : $owner_id;
$playlists = [];
if($owner_id > 0 && $owner_id != $this->getUser()->getId()) {
$user = (new \openvk\Web\Models\Repositories\Users)->get($owner_id);
if(!$user->getPrivacyPermission("audios.read", $this->getUser()))
$this->fail(50, "Access to playlists denied");
}
foreach((new Audios)->getPlaylistsByEntityId($owner_id, $offset, $count) as $playlist) {
if(!$playlist->canBeViewedBy($this->getUser())) {
if($drop_private == 1)
continue;
$playlists[] = NULL;
continue;
}
$playlists[] = $playlist->toVkApiStruct($this->getUser());
}
return (object) [
"count" => sizeof($playlists),
"items" => $playlists,
];
}
function searchAlbums(string $query = '', int $offset = 0, int $limit = 25, int $drop_private = 0, int $order = 0, int $from_me = 0): object
{
$this->requireUser();
$playlists = [];
$params = [];
$order_str = (['id', 'length', 'listens'][$order] ?? 'id');
if($from_me === 1)
$params['from_me'] = $this->getUser()->getId();
$search = (new Audios)->findPlaylists($query, $params, ['type' => $order_str, 'invert' => false]);
foreach($search->offsetLimit($offset, $limit) as $playlist) {
if(!$playlist->canBeViewedBy($this->getUser())) {
if($drop_private == 0)
$playlists[] = NULL;
continue;
}
$playlists[] = $playlist->toVkApiStruct($this->getUser());
}
return (object) [
"count" => $search->size(),
"items" => $playlists,
];
}
function addAlbum(string $title, ?string $description = NULL, int $group_id = 0): int
{
$this->requireUser();
$this->willExecuteWriteAction();
$group = NULL;
if($group_id != 0) {
$group = (new Clubs)->get($group_id);
if(!$group)
$this->fail(0404, "Invalid group_id");
else if(!$group->canBeModifiedBy($this->getUser()))
$this->fail(600, "Insufficient rights to this group");
}
$album = new Playlist;
$album->setName($title);
if(!is_null($group))
$album->setOwner($group_id * -1);
else
$album->setOwner($this->getUser()->getId());
if(!is_null($description))
$album->setDescription($description);
$album->save();
if(!is_null($group))
$album->bookmark($group);
else
$album->bookmark($this->getUser());
return $album->getId();
}
function editAlbum(int $album_id, ?string $title = NULL, ?string $description = NULL): int
{
$this->requireUser();
$this->willExecuteWriteAction();
$album = (new Audios)->getPlaylist($album_id);
if(!$album)
$this->fail(0404, "Album not found");
else if(!$album->canBeModifiedBy($this->getUser()))
$this->fail(600, "Insufficient rights to this album");
if(!is_null($title))
$album->setName($title);
if(!is_null($description))
$album->setDescription($description);
$album->setEdited(time());
$album->save();
return (int) !(!$title && !$description);
}
function deleteAlbum(int $album_id): int
{
$this->requireUser();
$this->willExecuteWriteAction();
$album = (new Audios)->getPlaylist($album_id);
if(!$album)
$this->fail(0404, "Album not found");
else if(!$album->canBeModifiedBy($this->getUser()))
$this->fail(600, "Insufficient rights to this album");
$album->delete();
return 1;
}
function moveToAlbum(int $album_id, string $audio_ids): int
{
$this->requireUser();
$this->willExecuteWriteAction();
$album = (new Audios)->getPlaylist($album_id);
if(!$album)
$this->fail(0404, "Album not found");
else if(!$album->canBeModifiedBy($this->getUser()))
$this->fail(600, "Insufficient rights to this album");
$audios = [];
$audio_ids = array_unique(explode(",", $audio_ids));
if(sizeof($audio_ids) < 1 || sizeof($audio_ids) > 1000)
$this->fail(8, "audio_ids must contain at least 1 audio and at most 1000");
foreach($audio_ids as $audio_id) {
$audio = $this->audioFromAnyId($audio_id);
if(!$audio)
continue;
else if(!$audio->canBeViewedBy($this->getUser()))
continue;
$audios[] = $audio;
}
if(sizeof($audios) < 1)
return 0;
$res = 1;
try {
foreach ($audios as $audio)
$res = min($res, (int) $album->add($audio));
} catch(\OutOfBoundsException $ex) {
return 0;
}
return $res;
}
function removeFromAlbum(int $album_id, string $audio_ids): int
{
$this->requireUser();
$this->willExecuteWriteAction();
$album = (new Audios)->getPlaylist($album_id);
if(!$album)
$this->fail(0404, "Album not found");
else if(!$album->canBeModifiedBy($this->getUser()))
$this->fail(600, "Insufficient rights to this album");
$audios = [];
$audio_ids = array_unique(explode(",", $audio_ids));
if(sizeof($audio_ids) < 1 || sizeof($audio_ids) > 1000)
$this->fail(8, "audio_ids must contain at least 1 audio and at most 1000");
foreach($audio_ids as $audio_id) {
$audio = $this->audioFromAnyId($audio_id);
if(!$audio)
continue;
else if($audio->canBeViewedBy($this->getUser()))
continue;
$audios[] = $audio;
}
if(sizeof($audios) < 1)
return 0;
foreach($audios as $audio)
$album->remove($audio);
return 1;
}
function copyToAlbum(int $album_id, string $audio_ids): int
{
return $this->moveToAlbum($album_id, $audio_ids);
}
function bookmarkAlbum(int $id): int
{
$this->requireUser();
$this->willExecuteWriteAction();
$album = (new Audios)->getPlaylist($id);
if(!$album)
$this->fail(0404, "Not found");
if(!$album->canBeViewedBy($this->getUser()))
$this->fail(600, "Access error");
return (int) $album->bookmark($this->getUser());
}
function unBookmarkAlbum(int $id): int
{
$this->requireUser();
$this->willExecuteWriteAction();
$album = (new Audios)->getPlaylist($id);
if(!$album)
$this->fail(0404, "Not found");
if(!$album->canBeViewedBy($this->getUser()))
$this->fail(600, "Access error");
return (int) $album->unbookmark($this->getUser());
}
} }

View file

@ -287,6 +287,7 @@ final class Board extends VKAPIRequestHandler
{ {
# start_comment_id ne robit # start_comment_id ne robit
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction();
$topic = (new TopicsRepo)->getTopicById($group_id, $topic_id); $topic = (new TopicsRepo)->getTopicById($group_id, $topic_id);
@ -320,6 +321,7 @@ final class Board extends VKAPIRequestHandler
{ {
# order и extended ничё не делают # order и extended ничё не делают
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction();
$arr = []; $arr = [];
$club = (new ClubsRepo)->get($group_id); $club = (new ClubsRepo)->get($group_id);
@ -328,7 +330,7 @@ final class Board extends VKAPIRequestHandler
$arr["count"] = (new TopicsRepo)->getClubTopicsCount($club); $arr["count"] = (new TopicsRepo)->getClubTopicsCount($club);
$arr["items"] = []; $arr["items"] = [];
$arr["default_order"] = $order; $arr["default_order"] = $order;
$arr["can_add_topics"] = $club->canBeModifiedBy($this->getUser()) ? true : ($club->isEveryoneCanCreateTopics() ? true : false); $arr["can_add_topics"] = $club->canBeModifiedBy($this->getUser()) ? true : $club->isEveryoneCanCreateTopics() ? true : false;
$arr["profiles"] = []; $arr["profiles"] = [];
if(empty($topic_ids)) { if(empty($topic_ids)) {

View file

@ -4,7 +4,7 @@ use openvk\Web\Models\Repositories\Users as UsersRepo;
final class Friends extends VKAPIRequestHandler final class Friends extends VKAPIRequestHandler
{ {
function get(int $user_id = 0, string $fields = "", int $offset = 0, int $count = 100): object function get(int $user_id, string $fields = "", int $offset = 0, int $count = 100): object
{ {
$i = 0; $i = 0;
$offset++; $offset++;
@ -13,23 +13,11 @@ final class Friends extends VKAPIRequestHandler
$users = new UsersRepo; $users = new UsersRepo;
$this->requireUser(); $this->requireUser();
if ($user_id == 0) {
$user_id = $this->getUser()->getId();
}
$user = $users->get($user_id);
if(!$user || $user->isDeleted()) foreach($users->get($user_id)->getFriends($offset, $count) as $friend) {
$this->fail(100, "Invalid user"); $friends[$i] = $friend->getId();
$i++;
if(!$user->getPrivacyPermission("friends.read", $this->getUser())) }
$this->fail(15, "Access denied: this user chose to hide his friends.");
foreach($user->getFriends($offset, $count) as $friend) {
$friends[$i] = $friend->getId();
$i++;
}
$response = $friends; $response = $friends;
@ -147,7 +135,7 @@ final class Friends extends VKAPIRequestHandler
return $response; return $response;
} }
function getRequests(string $fields = "", int $out = 0, int $offset = 0, int $count = 100, int $extended = 0): object function getRequests(string $fields = "", 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,18 +146,9 @@ final class Friends extends VKAPIRequestHandler
$offset++; $offset++;
$followers = []; $followers = [];
if ($out != 0) { foreach($this->getUser()->getFollowers($offset, $count) as $follower) {
foreach($this->getUser()->getFollowers($offset, $count) as $follower) { $followers[$i] = $follower->getId();
$followers[$i] = $follower->getId(); $i++;
$i++;
}
}
else
{
foreach($this->getUser()->getRequests($offset, $count) as $follower) {
$followers[$i] = $follower->getId();
$i++;
}
} }
$response = $followers; $response = $followers;

View file

@ -6,33 +6,19 @@ use openvk\Web\Models\Entities\Notifications\GiftNotification;
final class Gifts extends VKAPIRequestHandler final class Gifts extends VKAPIRequestHandler
{ {
function get(int $user_id = NULL, int $count = 10, int $offset = 0) function get(int $user_id, int $count = 10, int $offset = 0)
{ {
$this->requireUser(); $this->requireUser();
$i = 0; $i = 0;
$i += $offset;
$server_url = ovk_scheme(true) . $_SERVER["HTTP_HOST"];
if($user_id) $i += $offset;
$user = (new UsersRepo)->get($user_id);
else $user = (new UsersRepo)->get($user_id);
$user = $this->getUser();
if(!$user || $user->isDeleted()) if(!$user || $user->isDeleted())
$this->fail(177, "Invalid user"); $this->fail(177, "Invalid user");
if(!$user->canBeViewedBy($this->getUser()))
$this->fail(15, "Access denied");
/*
if(!$user->getPrivacyPermission('gifts.read', $this->getUser()))
$this->fail(15, "Access denied: this user chose to hide his gifts");*/
if(!$user->canBeViewedBy($this->getUser()))
$this->fail(15, "Access denied");
$gift_item = []; $gift_item = [];
$userGifts = array_slice(iterator_to_array($user->getGifts(1, $count, false)), $offset); $userGifts = array_slice(iterator_to_array($user->getGifts(1, $count, false)), $offset);
@ -50,9 +36,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" => $server_url. $gift->gift->getImage(2), "thumb_256" => $gift->gift->getImage(2),
"thumb_96" => $server_url . $gift->gift->getImage(2), "thumb_96" => $gift->gift->getImage(2),
"thumb_48" => $server_url . $gift->gift->getImage(2) "thumb_48" => $gift->gift->getImage(2)
], ],
"privacy" => 0 "privacy" => 0
]; ];
@ -76,9 +62,6 @@ final class Gifts extends VKAPIRequestHandler
if(!$user || $user->isDeleted()) if(!$user || $user->isDeleted())
$this->fail(177, "Invalid user"); $this->fail(177, "Invalid user");
if(!$user->canBeViewedBy($this->getUser()))
$this->fail(15, "Access denied");
$gift = (new GiftsRepo)->get($gift_id); $gift = (new GiftsRepo)->get($gift_id);
if(!$gift) if(!$gift)
@ -128,13 +111,12 @@ 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");
@ -144,8 +126,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" => $server_url . $cat->getThumbnailURL(), "thumbnail" => $cat->getThumbnailURL(),
]; ];
if($extended == true) { if($extended == true) {
$categ[$i]["localizations"] = []; $categ[$i]["localizations"] = [];
@ -182,7 +164,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

@ -2,36 +2,26 @@
namespace openvk\VKAPI\Handlers; namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Repositories\Clubs as ClubsRepo; use openvk\Web\Models\Repositories\Clubs as ClubsRepo;
use openvk\Web\Models\Repositories\Users as UsersRepo; use openvk\Web\Models\Repositories\Users as UsersRepo;
use openvk\Web\Models\Repositories\Posts as PostsRepo;
use openvk\Web\Models\Entities\Club; use openvk\Web\Models\Entities\Club;
final class Groups extends VKAPIRequestHandler final class Groups extends VKAPIRequestHandler
{ {
function get(int $user_id = 0, string $fields = "", int $offset = 0, int $count = 6, bool $online = false, string $filter = "groups"): object function get(int $user_id = 0, string $fields = "", int $offset = 0, int $count = 6, bool $online = false): object
{ {
$this->requireUser(); $this->requireUser();
# InfoApp fix
if($filter == "admin" && ($user_id != 0 && $user_id != $this->getUser()->getId())) {
$this->fail(15, 'Access denied: filter admin is available only for current user');
}
$clbs = [];
if($user_id == 0) { if($user_id == 0) {
foreach($this->getUser()->getClubs($offset, $filter == "admin", $count, true) as $club) foreach($this->getUser()->getClubs($offset, false, $count, true) as $club)
$clbs[] = $club; $clbs[] = $club;
$clbsCount = $this->getUser()->getClubCount(); $clbsCount = $this->getUser()->getClubCount();
} else { } else {
$users = new UsersRepo; $users = new UsersRepo;
$user = $users->get($user_id); $user = $users->get($user_id);
if(is_null($user) || $user->isDeleted()) if(is_null($user))
$this->fail(15, "Access denied"); $this->fail(15, "Access denied");
if(!$user->getPrivacyPermission('groups.read', $this->getUser())) foreach($user->getClubs($offset, false, $count, true) as $club)
$this->fail(15, "Access denied: this user chose to hide his groups.");
foreach($user->getClubs($offset, $filter == "admin", $count, true) as $club)
$clbs[] = $club; $clbs[] = $club;
$clbsCount = $user->getClubCount(); $clbsCount = $user->getClubCount();
@ -90,23 +80,6 @@ final class Groups extends VKAPIRequestHandler
break; break;
case "members_count": case "members_count":
$rClubs[$i]->members_count = $usr->getFollowersCount(); $rClubs[$i]->members_count = $usr->getFollowersCount();
break;
case "can_suggest":
$rClubs[$i]->can_suggest = !$usr->canBeModifiedBy($this->getUser()) && $usr->getWallType() == 2;
break;
case "background":
$backgrounds = $usr->getBackDropPictureURLs();
$rClubs[$i]->background = $backgrounds;
break;
# unstandard feild
case "suggested_count":
if($usr->getWallType() != 2) {
$rClubs[$i]->suggested_count = NULL;
break;
}
$rClubs[$i]->suggested_count = $usr->getSuggestedPostsCount($this->getUser());
break; break;
} }
} }
@ -215,23 +188,7 @@ final class Groups extends VKAPIRequestHandler
case "description": case "description":
$response[$i]->description = $clb->getDescription(); $response[$i]->description = $clb->getDescription();
break; break;
case "can_suggest": case "contacts":
$response[$i]->can_suggest = !$clb->canBeModifiedBy($this->getUser()) && $clb->getWallType() == 2;
break;
case "background":
$backgrounds = $clb->getBackDropPictureURLs();
$response[$i]->background = $backgrounds;
break;
# unstandard feild
case "suggested_count":
if($clb->getWallType() != 2) {
$response[$i]->suggested_count = NULL;
break;
}
$response[$i]->suggested_count = $clb->getSuggestedPostsCount($this->getUser());
break;
case "contacts":
$contacts; $contacts;
$contactTmp = $clb->getManagers(1, true); $contactTmp = $clb->getManagers(1, true);
@ -258,30 +215,23 @@ final class Groups extends VKAPIRequestHandler
return $response; return $response;
} }
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") function search(string $q, int $offset = 0, int $count = 100)
{ {
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->offsetLimit($offset, $count) as $group) foreach ($find 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), "", $fields) "items" => $this->getById(implode(',', $array), "", "is_admin,is_member,is_advertiser,photo_50,photo_100,photo_200", $offset, $count)
/*
* As there is no thing as "fields" by the original documentation
* i'll just bake this param by the example shown here: https://dev.vk.com/method/groups.search
*/
]; ];
} }
@ -338,12 +288,11 @@ final class Groups extends VKAPIRequestHandler
string $description = NULL, string $description = NULL,
string $screen_name = NULL, string $screen_name = NULL,
string $website = NULL, string $website = NULL,
int $wall = -1, int $wall = NULL,
int $topics = NULL, int $topics = NULL,
int $adminlist = NULL, int $adminlist = NULL,
int $topicsAboveWall = NULL, int $topicsAboveWall = NULL,
int $hideFromGlobalFeed = NULL, int $hideFromGlobalFeed = NULL)
int $audio = NULL)
{ {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
@ -354,34 +303,17 @@ final class Groups extends VKAPIRequestHandler
if(!$club || !$club->canBeModifiedBy($this->getUser())) $this->fail(15, "You can't modify this group."); if(!$club || !$club->canBeModifiedBy($this->getUser())) $this->fail(15, "You can't modify this group.");
if(!empty($screen_name) && !$club->setShortcode($screen_name)) $this->fail(103, "Invalid shortcode."); if(!empty($screen_name) && !$club->setShortcode($screen_name)) $this->fail(103, "Invalid shortcode.");
!empty($title) ? $club->setName($title) : NULL; !is_null($title) ? $club->setName($title) : NULL;
!empty($description) ? $club->setAbout($description) : NULL; !is_null($description) ? $club->setAbout($description) : NULL;
!empty($screen_name) ? $club->setShortcode($screen_name) : NULL; !is_null($screen_name) ? $club->setShortcode($screen_name) : NULL;
!empty($website) ? $club->setWebsite((!parse_url($website, PHP_URL_SCHEME) ? "https://" : "") . $website) : NULL; !is_null($website) ? $club->setWebsite((!parse_url($website, PHP_URL_SCHEME) ? "https://" : "") . $website) : NULL;
!is_null($wall) ? $club->setWall($wall) : NULL;
try { !is_null($topics) ? $club->setEveryone_Can_Create_Topics($topics) : NULL;
$wall != -1 ? $club->setWall($wall) : NULL; !is_null($adminlist) ? $club->setAdministrators_List_Display($adminlist) : NULL;
} catch(\Exception $e) { !is_null($topicsAboveWall) ? $club->setDisplay_Topics_Above_Wall($topicsAboveWall) : NULL;
$this->fail(50, "Invalid wall value"); !is_null($hideFromGlobalFeed) ? $club->setHide_From_Global_Feed($hideFromGlobalFeed) : NULL;
}
!empty($topics) ? $club->setEveryone_Can_Create_Topics($topics) : NULL;
!empty($adminlist) ? $club->setAdministrators_List_Display($adminlist) : NULL;
!empty($topicsAboveWall) ? $club->setDisplay_Topics_Above_Wall($topicsAboveWall) : NULL;
if (!$club->isHidingFromGlobalFeedEnforced()) { $club->save();
!empty($hideFromGlobalFeed) ? $club->setHide_From_Global_Feed($hideFromGlobalFeed) : NULL;
}
in_array($audio, [0, 1]) ? $club->setEveryone_can_upload_audios($audio) : NULL;
try {
$club->save();
} catch(\TypeError $e) {
$this->fail(15, "Nothing changed");
} catch(\Exception $e) {
$this->fail(18, "An unknown error occurred: maybe you set an incorrect value?");
}
return 1; return 1;
} }
@ -427,15 +359,9 @@ final class Groups extends VKAPIRequestHandler
]; ];
foreach($filds as $fild) { foreach($filds as $fild) {
$canView = $member->canBeViewedBy($this->getUser());
switch($fild) { switch($fild) {
case "bdate": case "bdate":
if(!$canView) { $arr->items[$i]->bdate = $member->getBirthday()->format('%e.%m.%Y');
$arr->items[$i]->bdate = "01.01.1970";
break;
}
$arr->items[$i]->bdate = $member->getBirthday() ? $member->getBirthday()->format('%e.%m.%Y') : NULL;
break; break;
case "can_post": case "can_post":
$arr->items[$i]->can_post = $club->canBeModifiedBy($member); $arr->items[$i]->can_post = $club->canBeModifiedBy($member);
@ -444,7 +370,7 @@ final class Groups extends VKAPIRequestHandler
$arr->items[$i]->can_see_all_posts = 1; $arr->items[$i]->can_see_all_posts = 1;
break; break;
case "can_see_audio": case "can_see_audio":
$arr->items[$i]->can_see_audio = 1; $arr->items[$i]->can_see_audio = 0;
break; break;
case "can_write_private_message": case "can_write_private_message":
$arr->items[$i]->can_write_private_message = 0; $arr->items[$i]->can_write_private_message = 0;
@ -456,11 +382,6 @@ final class Groups extends VKAPIRequestHandler
$arr->items[$i]->connections = 1; $arr->items[$i]->connections = 1;
break; break;
case "contacts": case "contacts":
if(!$canView) {
$arr->items[$i]->contacts = "secret@gmail.com";
break;
}
$arr->items[$i]->contacts = $member->getContactEmail(); $arr->items[$i]->contacts = $member->getContactEmail();
break; break;
case "country": case "country":
@ -476,30 +397,15 @@ final class Groups extends VKAPIRequestHandler
$arr->items[$i]->has_mobile = false; $arr->items[$i]->has_mobile = false;
break; break;
case "last_seen": case "last_seen":
if(!$canView) {
$arr->items[$i]->last_seen = 0;
break;
}
$arr->items[$i]->last_seen = $member->getOnline()->timestamp(); $arr->items[$i]->last_seen = $member->getOnline()->timestamp();
break; break;
case "lists": case "lists":
$arr->items[$i]->lists = ""; $arr->items[$i]->lists = "";
break; break;
case "online": case "online":
if(!$canView) {
$arr->items[$i]->online = false;
break;
}
$arr->items[$i]->online = $member->isOnline(); $arr->items[$i]->online = $member->isOnline();
break; break;
case "online_mobile": case "online_mobile":
if(!$canView) {
$arr->items[$i]->online_mobile = false;
break;
}
$arr->items[$i]->online_mobile = $member->getOnlinePlatform() == "android" || $member->getOnlinePlatform() == "iphone" || $member->getOnlinePlatform() == "mobile"; $arr->items[$i]->online_mobile = $member->getOnlinePlatform() == "android" || $member->getOnlinePlatform() == "iphone" || $member->getOnlinePlatform() == "mobile";
break; break;
case "photo_100": case "photo_100":
@ -530,27 +436,12 @@ final class Groups extends VKAPIRequestHandler
$arr->items[$i]->schools = 0; $arr->items[$i]->schools = 0;
break; break;
case "sex": case "sex":
if(!$canView) {
$arr->items[$i]->sex = -1;
break;
}
$arr->items[$i]->sex = $member->isFemale() ? 1 : 2; $arr->items[$i]->sex = $member->isFemale() ? 1 : 2;
break; break;
case "site": case "site":
if(!$canView) {
$arr->items[$i]->site = NULL;
break;
}
$arr->items[$i]->site = $member->getWebsite(); $arr->items[$i]->site = $member->getWebsite();
break; break;
case "status": case "status":
if(!$canView) {
$arr->items[$i]->status = "r";
break;
}
$arr->items[$i]->status = $member->getStatus(); $arr->items[$i]->status = $member->getStatus();
break; break;
case "universities": case "universities":
@ -575,10 +466,10 @@ final class Groups extends VKAPIRequestHandler
"title" => $club->getName(), "title" => $club->getName(),
"description" => $club->getDescription() != NULL ? $club->getDescription() : "", "description" => $club->getDescription() != NULL ? $club->getDescription() : "",
"address" => $club->getShortcode(), "address" => $club->getShortcode(),
"wall" => $club->getWallType(), # отличается от вкшных но да ладно "wall" => $club->canPost() == true ? 1 : 0,
"photos" => 1, "photos" => 1,
"video" => 0, "video" => 0,
"audio" => $club->isEveryoneCanUploadAudios() ? 1 : 0, "audio" => 0,
"docs" => 0, "docs" => 0,
"topics" => $club->isEveryoneCanCreateTopics() == true ? 1 : 0, "topics" => $club->isEveryoneCanCreateTopics() == true ? 1 : 0,
"wiki" => 0, "wiki" => 0,

View file

@ -2,205 +2,70 @@
namespace openvk\VKAPI\Handlers; namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Repositories\Users as UsersRepo; use openvk\Web\Models\Repositories\Users as UsersRepo;
use openvk\Web\Models\Repositories\Posts as PostsRepo; use openvk\Web\Models\Repositories\Posts as PostsRepo;
use openvk\Web\Models\Repositories\Comments as CommentsRepo;
use openvk\Web\Models\Repositories\Videos as VideosRepo;
use openvk\Web\Models\Repositories\Photos as PhotosRepo;
use openvk\Web\Models\Repositories\Notes as NotesRepo;
final class Likes extends VKAPIRequestHandler final class Likes extends VKAPIRequestHandler
{ {
function add(string $type, int $owner_id, int $item_id): object function add(string $type, int $owner_id, int $item_id): object
{ {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
$postable = NULL;
switch($type) { switch($type) {
case "post": case "post":
$post = (new PostsRepo)->getPostById($owner_id, $item_id); $post = (new PostsRepo)->getPostById($owner_id, $item_id);
$postable = $post; if(is_null($post))
break; $this->fail(100, "One of the parameters specified was missing or invalid: object not found");
case "comment":
$comment = (new CommentsRepo)->get($item_id); $post->setLike(true, $this->getUser());
$postable = $comment;
break; return (object) [
case "video": "likes" => $post->getLikesCount()
$video = (new VideosRepo)->getByOwnerAndVID($owner_id, $item_id); ];
$postable = $video;
break;
case "photo":
$photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $item_id);
$postable = $photo;
break;
case "note":
$note = (new NotesRepo)->getNoteById($owner_id, $item_id);
$postable = $note;
break;
default: default:
$this->fail(100, "One of the parameters specified was missing or invalid: incorrect type"); $this->fail(100, "One of the parameters specified was missing or invalid: incorrect type");
} }
if(is_null($postable) || $postable->isDeleted())
$this->fail(100, "One of the parameters specified was missing or invalid: object not found");
if(!$postable->canBeViewedBy($this->getUser() ?? NULL)) {
$this->fail(2, "Access to postable denied");
}
$postable->setLike(true, $this->getUser());
return (object) [
"likes" => $postable->getLikesCount()
];
}
function delete(string $type, int $owner_id, int $item_id): object
{
$this->requireUser();
$this->willExecuteWriteAction();
$postable = NULL;
switch($type) {
case "post":
$post = (new PostsRepo)->getPostById($owner_id, $item_id);
$postable = $post;
break;
case "comment":
$comment = (new CommentsRepo)->get($item_id);
$postable = $comment;
break;
case "video":
$video = (new VideosRepo)->getByOwnerAndVID($owner_id, $item_id);
$postable = $video;
break;
case "photo":
$photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $item_id);
$postable = $photo;
break;
case "note":
$note = (new NotesRepo)->getNoteById($owner_id, $item_id);
$postable = $note;
break;
default:
$this->fail(100, "One of the parameters specified was missing or invalid: incorrect type");
}
if(is_null($postable) || $postable->isDeleted())
$this->fail(100, "One of the parameters specified was missing or invalid: object not found");
if(!$postable->canBeViewedBy($this->getUser() ?? NULL)) {
$this->fail(2, "Access to postable denied");
}
if(!is_null($postable)) {
$postable->setLike(false, $this->getUser());
return (object) [
"likes" => $postable->getLikesCount()
];
}
}
function isLiked(int $user_id, string $type, int $owner_id, int $item_id): object
{
$this->requireUser();
$user = (new UsersRepo)->get($user_id);
if(is_null($user) || $user->isDeleted())
$this->fail(100, "One of the parameters specified was missing or invalid: user not found");
if(!$user->canBeViewedBy($this->getUser())) {
$this->fail(1984, "Access denied: you can't see this user");
}
$postable = NULL;
switch($type) {
case "post":
$post = (new PostsRepo)->getPostById($owner_id, $item_id);
$postable = $post;
break;
case "comment":
$comment = (new CommentsRepo)->get($item_id);
$postable = $comment;
break;
case "video":
$video = (new VideosRepo)->getByOwnerAndVID($owner_id, $item_id);
$postable = $video;
break;
case "photo":
$photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $item_id);
$postable = $photo;
break;
case "note":
$note = (new NotesRepo)->getNoteById($owner_id, $item_id);
$postable = $note;
break;
default:
$this->fail(100, "One of the parameters specified was missing or invalid: incorrect type");
}
if(is_null($postable) || $postable->isDeleted())
$this->fail(100, "One of the parameters specified was missing or invalid: object not found");
if(!$postable->canBeViewedBy($this->getUser())) {
$this->fail(665, "Access to postable denied");
}
return (object) [
"liked" => (int) $postable->hasLikeFrom($user),
"copied" => 0
];
} }
function getList(string $type, int $owner_id, int $item_id, bool $extended = false, int $offset = 0, int $count = 10, bool $skip_own = false) function delete(string $type, int $owner_id, int $item_id): object
{ {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction();
$object = NULL;
switch($type) { switch($type) {
case "post": case "post":
$object = (new PostsRepo)->getPostById($owner_id, $item_id); $post = (new PostsRepo)->getPostById($owner_id, $item_id);
break; if (is_null($post))
case "comment": $this->fail(100, "One of the parameters specified was missing or invalid: object not found");
$object = (new CommentsRepo)->get($item_id);
break; $post->setLike(false, $this->getUser());
case "photo": return (object) [
$object = (new PhotosRepo)->getByOwnerAndVID($owner_id, $item_id); "likes" => $post->getLikesCount()
break; ];
case "video":
$object = (new VideosRepo)->getByOwnerAndVID($owner_id, $item_id);
break;
default: default:
$this->fail(58, "Invalid type"); $this->fail(100, "One of the parameters specified was missing or invalid: incorrect type");
break;
} }
}
function isLiked(int $user_id, string $type, int $owner_id, int $item_id): object
{
$this->requireUser();
if(!$object || $object->isDeleted()) switch($type) {
$this->fail(56, "Invalid postable"); case "post":
$user = (new UsersRepo)->get($user_id);
if (is_null($user))
$this->fail(100, "One of the parameters specified was missing or invalid: user not found");
if(!$object->canBeViewedBy($this->getUser())) $post = (new PostsRepo)->getPostById($owner_id, $item_id);
$this->fail(665, "Access to postable denied"); if (is_null($post))
$this->fail(100, "One of the parameters specified was missing or invalid: object not found");
$res = (object)[
"count" => $object->getLikesCount(), return (object) [
"items" => [] "liked" => (int) $post->hasLikeFrom($user),
]; "copied" => 0 # TODO: handle this
];
$likers = array_slice(iterator_to_array($object->getLikers(1, $offset + $count)), $offset); default:
$this->fail(100, "One of the parameters specified was missing or invalid: incorrect type");
foreach($likers as $liker) {
if($skip_own && $liker->getId() == $this->getUser()->getId())
continue;
if(!$extended)
$res->items[] = $liker->getId();
else
$res->items[] = $liker->toVkApiStruct(NULL, 'photo_50');
} }
}
return $res;
}
} }

View file

@ -65,8 +65,7 @@ final class Messages extends VKAPIRequestHandler
]; ];
} }
function send(int $user_id = -1, int $peer_id = -1, string $domain = "", int $chat_id = -1, string $user_ids = "", string $message = "", int $sticker_id = -1, int $forGodSakePleaseDoNotReportAboutMyOnlineActivity = 0, function send(int $user_id = -1, int $peer_id = -1, string $domain = "", int $chat_id = -1, string $user_ids = "", string $message = "", int $sticker_id = -1, int $forGodSakePleaseDoNotReportAboutMyOnlineActivity = 0)
string $attachment = "") # интересно почему не attachments
{ {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
@ -80,8 +79,7 @@ final class Messages extends VKAPIRequestHandler
$this->fail(946, "Chats are not implemented"); $this->fail(946, "Chats are not implemented");
else if($sticker_id !== -1) else if($sticker_id !== -1)
$this->fail(-151, "Stickers are not implemented"); $this->fail(-151, "Stickers are not implemented");
else if(empty($message))
if(empty($message) && empty($attachment))
$this->fail(100, "Message text is empty or invalid"); $this->fail(100, "Message text is empty or invalid");
# lol recursion # lol recursion
@ -119,21 +117,6 @@ final class Messages extends VKAPIRequestHandler
if(!$msg) if(!$msg)
$this->fail(950, "Internal error"); $this->fail(950, "Internal error");
else else
if(!empty($attachment)) {
$attachs = parseAttachments($attachment);
# Работают только фотки, остальное просто не будет отображаться.
if(sizeof($attachs) >= 10)
$this->fail(15, "Too many attachments");
foreach($attachs as $attach) {
if($attach && !$attach->isDeleted() && $attach->getOwner()->getId() == $this->getUser()->getId())
$msg->attach($attach);
else
$this->fail(52, "One of the attachments is invalid");
}
}
return $msg->getId(); return $msg->getId();
} }
@ -410,49 +393,4 @@ final class Messages extends VKAPIRequestHandler
return $res; return $res;
} }
function edit(int $message_id, string $message = "", string $attachment = "", int $peer_id = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
$msg = (new MSGRepo)->get($message_id);
if(empty($message) && empty($attachment))
$this->fail(100, "Required parameter 'message' missing.");
if(!$msg || $msg->isDeleted())
$this->fail(102, "Invalid message");
if($msg->getSender()->getId() != $this->getUser()->getId())
$this->fail(15, "Access to message denied");
if(!empty($message))
$msg->setContent($message);
$msg->setEdited(time());
$msg->save(true);
if(!empty($attachment)) {
$attachs = parseAttachments($attachment);
$newAttachmentsCount = sizeof($attachs);
$postsAttachments = iterator_to_array($msg->getChildren());
if(sizeof($postsAttachments) >= 10)
$this->fail(15, "Message have too many attachments");
if(($newAttachmentsCount + sizeof($postsAttachments)) > 10)
$this->fail(158, "Message will have too many attachments");
foreach($attachs as $attach) {
if($attach && !$attach->isDeleted() && $attach->getOwner()->getId() == $this->getUser()->getId())
$msg->attach($attach);
else
$this->fail(52, "One of the attachments is invalid");
}
}
return 1;
}
} }

View file

@ -32,7 +32,6 @@ final class Newsfeed extends VKAPIRequestHandler
->select("id") ->select("id")
->where("wall IN (?)", $ids) ->where("wall IN (?)", $ids)
->where("deleted", 0) ->where("deleted", 0)
->where("suggested", 0)
->where("id < (?)", empty($start_from) ? PHP_INT_MAX : $start_from) ->where("id < (?)", empty($start_from) ? PHP_INT_MAX : $start_from)
->where("? <= created", empty($start_time) ? 0 : $start_time) ->where("? <= created", empty($start_time) ? 0 : $start_time)
->where("? >= created", empty($end_time) ? PHP_INT_MAX : $end_time) ->where("? >= created", empty($end_time) ? PHP_INT_MAX : $end_time)
@ -48,24 +47,14 @@ final class Newsfeed extends VKAPIRequestHandler
return $response; return $response;
} }
function getGlobal(string $fields = "", int $start_from = 0, int $start_time = 0, int $end_time = 0, int $offset = 0, int $count = 30, int $extended = 0, int $rss = 0) function getGlobal(string $fields = "", int $start_from = 0, int $start_time = 0, int $end_time = 0, int $offset = 0, int $count = 30, int $extended = 0)
{ {
$this->requireUser(); $this->requireUser();
$queryBase = "FROM `posts` LEFT JOIN `groups` ON GREATEST(`posts`.`wall`, 0) = 0 AND `groups`.`id` = ABS(`posts`.`wall`) LEFT JOIN `profiles` ON LEAST(`posts`.`wall`, 0) = 0 AND `profiles`.`id` = ABS(`posts`.`wall`)"; $queryBase = "FROM `posts` LEFT JOIN `groups` ON GREATEST(`posts`.`wall`, 0) = 0 AND `groups`.`id` = ABS(`posts`.`wall`) WHERE (`groups`.`hide_from_global_feed` = 0 OR `groups`.`name` IS NULL) AND `posts`.`deleted` = 0";
$queryBase .= "WHERE (`groups`.`hide_from_global_feed` = 0 OR `groups`.`name` IS NULL) AND (`profiles`.`profile_type` = 0 OR `profiles`.`first_name` IS NULL) AND `posts`.`deleted` = 0 AND `posts`.`suggested` = 0";
if($this->getUser()->getNsfwTolerance() === User::NSFW_INTOLERANT) if($this->getUser()->getNsfwTolerance() === User::NSFW_INTOLERANT)
$queryBase .= " AND `nsfw` = 0"; $queryBase .= " AND `nsfw` = 0";
if($return_banned == 0) {
$ignored_sources_ids = $this->getUser()->getIgnoredSources(0, OPENVK_ROOT_CONF['openvk']['preferences']['newsfeed']['ignoredSourcesLimit'] ?? 50, true);
if(sizeof($ignored_sources_ids) > 0) {
$imploded_ids = implode("', '", $ignored_sources_ids);
$queryBase .= " AND `posts`.`wall` NOT IN ('$imploded_ids')";
}
}
$start_from = empty($start_from) ? PHP_INT_MAX : $start_from; $start_from = empty($start_from) ? PHP_INT_MAX : $start_from;
$start_time = empty($start_time) ? 0 : $start_time; $start_time = empty($start_time) ? 0 : $start_time;
@ -74,25 +63,6 @@ final class Newsfeed extends VKAPIRequestHandler
$rposts = []; $rposts = [];
$ids = []; $ids = [];
if($rss == 1) {
$channel = new \Bhaktaraz\RSSGenerator\Channel();
$channel->title("Global Feed — " . OPENVK_ROOT_CONF['openvk']['appearance']['name'])
->description('OVK Global feed')
->url(ovk_scheme(true) . $_SERVER["HTTP_HOST"] . "/feed/all");
foreach($posts as $item) {
$post = (new PostsRepo)->get($item->id);
if(!$post || $post->isDeleted()) {
continue;
}
$output = $post->toRss();
$output->appendTo($channel);
}
return $channel;
}
foreach($posts as $post) { foreach($posts as $post) {
$rposts[] = (new PostsRepo)->get($post->id)->getPrettyId(); $rposts[] = (new PostsRepo)->get($post->id)->getPrettyId();
$ids[] = $post->id; $ids[] = $post->id;
@ -103,152 +73,4 @@ final class Newsfeed extends VKAPIRequestHandler
return $response; return $response;
} }
function getByType(string $feed_type = 'top', string $fields = "", int $start_from = 0, int $start_time = 0, int $end_time = 0, int $offset = 0, int $count = 30, int $extended = 0, int $return_banned = 0)
{
$this->requireUser();
switch($feed_type) {
case 'top':
return $this->getGlobal($fields, $start_from, $start_time, $end_time, $offset, $count, $extended, $return_banned);
break;
default:
return $this->get($fields, $start_from, $start_time, $end_time, $offset, $count, $extended);
break;
}
}
function getBanned(int $extended = 0, string $fields = "", string $name_case = "nom", int $merge = 0): object
{
$this->requireUser();
$offset = 0;
$count = OPENVK_ROOT_CONF['openvk']['preferences']['newsfeed']['ignoredSourcesLimit'] ?? 50;
$banned = $this->getUser()->getIgnoredSources($offset, $count, ($extended != 1));
$return_object = (object) [
'groups' => [],
'members' => [],
];
if($extended == 0) {
foreach($banned as $ban) {
if($ban > 0)
$return_object->members[] = $ban;
else
$return_object->groups[] = $ban;
}
} else {
if($merge == 1) {
$return_object = (object) [
'count' => sizeof($banned),
'items' => [],
];
foreach($banned as $ban) {
$return_object->items[] = $ban->toVkApiStruct($this->getUser(), $fields);
}
} else {
$return_object = (object) [
'groups' => [],
'profiles' => [],
];
foreach($banned as $ban) {
if($ban->getRealId() > 0)
$return_object->profiles[] = $ban->toVkApiStruct($this->getUser(), $fields);
else
$return_object->groups[] = $ban->toVkApiStruct($this->getUser(), $fields);
}
}
}
return $return_object;
}
function addBan(string $user_ids = "", string $group_ids = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
# Formatting input ids
if(!empty($user_ids)) {
$user_ids = array_map(function($el) {
return (int)$el;
}, explode(',', $user_ids));
$user_ids = array_unique($user_ids);
} else
$user_ids = [];
if(!empty($group_ids)) {
$group_ids = array_map(function($el) {
return abs((int)$el) * -1;
}, explode(',', $group_ids));
$group_ids = array_unique($group_ids);
} else
$group_ids = [];
$ids = array_merge($user_ids, $group_ids);
if(sizeof($ids) < 1)
return 0;
if(sizeof($ids) > 10)
$this->fail(-10, "Limit of 'ids' is 10");
$config_limit = OPENVK_ROOT_CONF['openvk']['preferences']['newsfeed']['ignoredSourcesLimit'] ?? 50;
$user_ignores = $this->getUser()->getIgnoredSourcesCount();
if(($user_ignores + sizeof($ids)) > $config_limit) {
$this->fail(-50, "Ignoring limit exceeded");
}
$entities = get_entities($ids);
$successes = 0;
foreach($entities as $entity) {
if(!$entity || $entity->getRealId() === $this->getUser()->getRealId() || $entity->isHideFromGlobalFeedEnabled() || $entity->isIgnoredBy($this->getUser())) continue;
$entity->addIgnore($this->getUser());
$successes += 1;
}
return 1;
}
function deleteBan(string $user_ids = "", string $group_ids = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
if(!empty($user_ids)) {
$user_ids = array_map(function($el) {
return (int)$el;
}, explode(',', $user_ids));
$user_ids = array_unique($user_ids);
} else
$user_ids = [];
if(!empty($group_ids)) {
$group_ids = array_map(function($el) {
return abs((int)$el) * -1;
}, explode(',', $group_ids));
$group_ids = array_unique($group_ids);
} else
$group_ids = [];
$ids = array_merge($user_ids, $group_ids);
if(sizeof($ids) < 1)
return 0;
if(sizeof($ids) > 10)
$this->fail(-10, "Limit of ids is 10");
$entities = get_entities($ids);
$successes = 0;
foreach($entities as $entity) {
if(!$entity || $entity->getRealId() === $this->getUser()->getRealId() || !$entity->isIgnoredBy($this->getUser())) continue;
$entity->removeIgnore($this->getUser());
$successes += 1;
}
return 1;
}
} }

View file

@ -40,9 +40,6 @@ final class Notes extends VKAPIRequestHandler
if($note->getOwner()->isDeleted()) if($note->getOwner()->isDeleted())
$this->fail(403, "Owner is deleted"); $this->fail(403, "Owner is deleted");
if(!$note->canBeViewedBy($this->getUser()))
$this->fail(15, "Access denied");
if(!$note->getOwner()->getPrivacyPermission('notes.read', $this->getUser())) if(!$note->getOwner()->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(43, "No access"); $this->fail(43, "No access");
@ -121,6 +118,21 @@ final class Notes extends VKAPIRequestHandler
return 1; return 1;
} }
function deleteComment(int $comment_id, int $owner_id = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
$comment = (new CommentsRepo)->get($comment_id);
if(!$comment || !$comment->canBeDeletedBy($this->getUser()))
$this->fail(403, "Access to comment denied");
$comment->delete();
return 1;
}
function edit(string $note_id, string $title = "", string $text = "", int $privacy = 0, int $comment_privacy = 0, string $privacy_view = "", string $privacy_comment = "") function edit(string $note_id, string $title = "", string $text = "", int $privacy = 0, int $comment_privacy = 0, string $privacy_view = "", string $privacy_comment = "")
{ {
$this->requireUser(); $this->requireUser();
@ -147,6 +159,25 @@ final class Notes extends VKAPIRequestHandler
return 1; return 1;
} }
function editComment(int $comment_id, string $message, int $owner_id = NULL)
{
/*
$this->requireUser();
$this->willExecuteWriteAction();
$comment = (new CommentsRepo)->get($comment_id);
if($comment->getOwner() != $this->getUser()->getId())
$this->fail(15, "Access to comment denied");
$comment->setContent($message);
$comment->setEdited(time());
$comment->save();
*/
return 1;
}
function get(int $user_id, string $note_ids = "", int $offset = 0, int $count = 10, int $sort = 0) function get(int $user_id, string $note_ids = "", int $offset = 0, int $count = 10, int $sort = 0)
{ {
$this->requireUser(); $this->requireUser();
@ -156,10 +187,7 @@ final class Notes extends VKAPIRequestHandler
$this->fail(15, "Invalid user"); $this->fail(15, "Invalid user");
if(!$user->getPrivacyPermission('notes.read', $this->getUser())) if(!$user->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(15, "Access denied: this user chose to hide his notes"); $this->fail(43, "Access denied: this user chose to hide his notes");
if(!$user->canBeViewedBy($this->getUser()))
$this->fail(15, "Access denied");
if(empty($note_ids)) { if(empty($note_ids)) {
$notes = array_slice(iterator_to_array((new NotesRepo)->getUserNotes($user, 1, $count + $offset, $sort == 0 ? "ASC" : "DESC")), $offset); $notes = array_slice(iterator_to_array((new NotesRepo)->getUserNotes($user, 1, $count + $offset, $sort == 0 ? "ASC" : "DESC")), $offset);
@ -183,7 +211,7 @@ final class Notes extends VKAPIRequestHandler
$items = []; $items = [];
$note = (new NotesRepo)->getNoteById((int)$id[0], (int)$id[1]); $note = (new NotesRepo)->getNoteById((int)$id[0], (int)$id[1]);
if($note && !$note->isDeleted()) { if($note) {
$nodez->notes[] = $note->toVkApiStruct(); $nodez->notes[] = $note->toVkApiStruct();
} }
} }
@ -210,9 +238,6 @@ final class Notes extends VKAPIRequestHandler
if(!$note->getOwner()->getPrivacyPermission('notes.read', $this->getUser())) if(!$note->getOwner()->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(40, "Access denied: this user chose to hide his notes"); $this->fail(40, "Access denied: this user chose to hide his notes");
if(!$note->canBeViewedBy($this->getUser()))
$this->fail(15, "Access to note denied");
return $note->toVkApiStruct(); return $note->toVkApiStruct();
} }
@ -233,9 +258,6 @@ final class Notes extends VKAPIRequestHandler
if(!$note->getOwner()->getPrivacyPermission('notes.read', $this->getUser())) if(!$note->getOwner()->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(14, "No access"); $this->fail(14, "No access");
if(!$note->canBeViewedBy($this->getUser()))
$this->fail(15, "Access to note denied");
$arr = (object) [ $arr = (object) [
"count" => $note->getCommentsCount(), "count" => $note->getCommentsCount(),

View file

@ -1,83 +0,0 @@
<?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Entities\Club;
use openvk\Web\Models\Repositories\{Notifications as Notifs, Clubs, Users};
final class Notifications extends VKAPIRequestHandler
{
function get(int $count = 10,
string $from = "",
int $offset = 0,
string $start_from = "",
string $filters = "",
int $start_time = 0,
int $end_time = 0,
int $archived = 0)
{
$this->requireUser();
$res = (object)[
"items" => [],
"profiles" => [],
"groups" => [],
"last_viewed" => $this->getUser()->getNotificationOffset()
];
if($count > 100)
$this->fail(125, "Count is too big");
if(!eventdb())
$this->fail(1289, "EventDB is disabled on this instance");
$notifs = array_slice(iterator_to_array((new Notifs)->getNotificationsByUser($this->getUser(), $this->getUser()->getNotificationOffset(), (bool)$archived, 1, $offset + $count)), $offset);
$tmpProfiles = [];
foreach($notifs as $notif) {
$sxModel = $notif->getModel(1);
if(!method_exists($sxModel, "getAvatarUrl"))
$sxModel = $notif->getModel(0);
$tmpProfiles[] = $sxModel instanceof Club ? $sxModel->getId() * -1 : $sxModel->getId();
$res->items[] = $notif->toVkApiStruct();
}
foreach(array_unique($tmpProfiles) as $id) {
if($id > 0) {
$sxModel = (new Users)->get($id);
$result = (object)[
"uid" => $sxModel->getId(),
"first_name" => $sxModel->getFirstName(),
"last_name" => $sxModel->getLastName(),
"photo" => $sxModel->getAvatarUrl(),
"photo_medium_rec" => $sxModel->getAvatarUrl("tiny"),
"screen_name" => $sxModel->getShortCode()
];
$res->profiles[] = $result;
} else {
$sxModel = (new Clubs)->get(abs($id));
$result = $sxModel->toVkApiStruct($this->getUser());
$res->groups[] = $result;
}
}
return $res;
}
function markAsViewed()
{
$this->requireUser();
$this->willExecuteWriteAction();
try {
$this->getUser()->updateNotificationOffset();
$this->getUser()->save();
} catch(\Throwable $e) {
return 0;
}
return 1;
}
}

View file

@ -288,6 +288,7 @@ final class Photos extends VKAPIRequestHandler
function getAlbums(int $owner_id, string $album_ids = "", int $offset = 0, int $count = 100, bool $need_system = true, bool $need_covers = true, bool $photo_sizes = false) function getAlbums(int $owner_id, string $album_ids = "", int $offset = 0, int $count = 100, bool $need_system = true, bool $need_covers = true, bool $photo_sizes = false)
{ {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction();
$res = []; $res = [];
@ -303,6 +304,7 @@ final class Photos extends VKAPIRequestHandler
if(!$user || $user->isDeleted()) if(!$user || $user->isDeleted())
$this->fail(2, "Invalid user"); $this->fail(2, "Invalid user");
if(!$user->getPrivacyPermission('photos.read', $this->getUser())) if(!$user->getPrivacyPermission('photos.read', $this->getUser()))
$this->fail(21, "This user chose to hide his albums."); $this->fail(21, "This user chose to hide his albums.");
@ -359,22 +361,28 @@ final class Photos extends VKAPIRequestHandler
function getAlbumsCount(int $user_id = 0, int $group_id = 0) function getAlbumsCount(int $user_id = 0, int $group_id = 0)
{ {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction();
if($user_id == 0 && $group_id == 0 || $user_id > 0 && $group_id > 0) if($user_id == 0 && $group_id == 0 || $user_id > 0 && $group_id > 0) {
$this->fail(21, "Select user_id or group_id"); $this->fail(21, "Select user_id or group_id");
}
if($user_id > 0) { if($user_id > 0) {
$us = (new UsersRepo)->get($user_id); $us = (new UsersRepo)->get($user_id);
if(!$us || $us->isDeleted()) if(!$us || $us->isDeleted()) {
$this->fail(21, "Invalid user"); $this->fail(21, "Invalid user");
}
if(!$us->getPrivacyPermission('photos.read', $this->getUser())) if(!$us->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "This user chose to hide his albums."); $this->fail(21, "This user chose to hide his albums.");
}
return (new Albums)->getUserAlbumsCount($us); return (new Albums)->getUserAlbumsCount($us);
} }
if($group_id > 0) { if($group_id > 0)
{
$cl = (new Clubs)->get($group_id); $cl = (new Clubs)->get($group_id);
if(!$cl) { if(!$cl) {
$this->fail(21, "Invalid club"); $this->fail(21, "Invalid club");
@ -387,6 +395,7 @@ final class Photos extends VKAPIRequestHandler
function getById(string $photos, bool $extended = false, bool $photo_sizes = false) function getById(string $photos, bool $extended = false, bool $photo_sizes = false)
{ {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction();
$phts = explode(",", $photos); $phts = explode(",", $photos);
$res = []; $res = [];
@ -395,11 +404,17 @@ final class Photos extends VKAPIRequestHandler
$ph = explode("_", $phota); $ph = explode("_", $phota);
$photo = (new PhotosRepo)->getByOwnerAndVID((int)$ph[0], (int)$ph[1]); $photo = (new PhotosRepo)->getByOwnerAndVID((int)$ph[0], (int)$ph[1]);
if(!$photo || $photo->isDeleted()) if(!$photo || $photo->isDeleted()) {
$this->fail(21, "Invalid photo"); $this->fail(21, "Invalid photo");
}
if(!$photo->canBeViewedBy($this->getUser())) if($photo->getOwner()->isDeleted()) {
$this->fail(15, "Access denied"); $this->fail(21, "Owner of this photo is deleted");
}
if(!$photo->getOwner()->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "This user chose to hide his photos.");
}
$res[] = $photo->toVkApiStruct($photo_sizes, $extended); $res[] = $photo->toVkApiStruct($photo_sizes, $extended);
} }
@ -410,20 +425,23 @@ final class Photos extends VKAPIRequestHandler
function get(int $owner_id, int $album_id, string $photo_ids = "", bool $extended = false, bool $photo_sizes = false, int $offset = 0, int $count = 10) function get(int $owner_id, int $album_id, string $photo_ids = "", bool $extended = false, bool $photo_sizes = false, int $offset = 0, int $count = 10)
{ {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction();
$res = []; $res = [];
if(empty($photo_ids)) { if(empty($photo_ids)) {
$album = (new Albums)->getAlbumByOwnerAndId($owner_id, $album_id); $album = (new Albums)->getAlbumByOwnerAndId($owner_id, $album_id);
if(!$album || $album->isDeleted()) if(!$album->getOwner()->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "This user chose to hide his albums.");
}
if(!$album || $album->isDeleted()) {
$this->fail(21, "Invalid album"); $this->fail(21, "Invalid album");
}
if(!$album->canBeViewedBy($this->getUser()))
$this->fail(15, "Access denied");
$photos = array_slice(iterator_to_array($album->getPhotos(1, $count + $offset)), $offset); $photos = array_slice(iterator_to_array($album->getPhotos(1, $count + $offset)), $offset);
$res["count"] = $album->size(); $res["count"] = sizeof($photos);
foreach($photos as $photo) { foreach($photos as $photo) {
if(!$photo || $photo->isDeleted()) continue; if(!$photo || $photo->isDeleted()) continue;
@ -438,11 +456,12 @@ final class Photos extends VKAPIRequestHandler
"items" => [] "items" => []
]; ];
foreach($photos as $photo) { foreach($photos as $photo)
{
$id = explode("_", $photo); $id = explode("_", $photo);
$phot = (new PhotosRepo)->getByOwnerAndVID((int)$id[0], (int)$id[1]); $phot = (new PhotosRepo)->getByOwnerAndVID((int)$id[0], (int)$id[1]);
if($phot && !$phot->isDeleted() && $phot->canBeViewedBy($this->getUser())) { if($phot && !$phot->isDeleted()) {
$res["items"][] = $phot->toVkApiStruct($photo_sizes, $extended); $res["items"][] = $phot->toVkApiStruct($photo_sizes, $extended);
} }
} }
@ -458,11 +477,13 @@ final class Photos extends VKAPIRequestHandler
$album = (new Albums)->get($album_id); $album = (new Albums)->get($album_id);
if(!$album || $album->canBeModifiedBy($this->getUser())) if(!$album || $album->canBeModifiedBy($this->getUser())) {
$this->fail(21, "Invalid album"); $this->fail(21, "Invalid album");
}
if($album->isDeleted()) if($album->isDeleted()) {
$this->fail(22, "Album already deleted"); $this->fail(22, "Album already deleted");
}
$album->delete(); $album->delete();
@ -476,11 +497,13 @@ final class Photos extends VKAPIRequestHandler
$photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id); $photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id);
if(!$photo) if(!$photo) {
$this->fail(21, "Invalid photo"); $this->fail(21, "Invalid photo");
}
if($photo->isDeleted()) if($photo->isDeleted()) {
$this->fail(21, "Photo is deleted"); $this->fail(21, "Photo is deleted");
}
if(!empty($caption)) { if(!empty($caption)) {
$photo->setDescription($caption); $photo->setDescription($caption);
@ -498,14 +521,17 @@ final class Photos extends VKAPIRequestHandler
if(empty($photos)) { if(empty($photos)) {
$photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id); $photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id);
if($this->getUser()->getId() !== $photo->getOwner()->getId()) if($this->getUser()->getId() !== $photo->getOwner()->getId()) {
$this->fail(21, "You can't delete another's photo"); $this->fail(21, "You can't delete another's photo");
}
if(!$photo) if(!$photo) {
$this->fail(21, "Invalid photo"); $this->fail(21, "Invalid photo");
}
if($photo->isDeleted()) if($photo->isDeleted()) {
$this->fail(21, "Photo is already deleted"); $this->fail(21, "Photo already deleted");
}
$photo->delete(); $photo->delete();
} else { } else {
@ -517,14 +543,17 @@ final class Photos extends VKAPIRequestHandler
$phot = (new PhotosRepo)->getByOwnerAndVID((int)$id[0], (int)$id[1]); $phot = (new PhotosRepo)->getByOwnerAndVID((int)$id[0], (int)$id[1]);
if($this->getUser()->getId() !== $phot->getOwner()->getId()) if($this->getUser()->getId() !== $phot->getOwner()->getId()) {
$this->fail(21, "You can't delete another's photo"); $this->fail(21, "You can't delete another's photo");
}
if(!$phot) if(!$phot) {
$this->fail(21, "Invalid photo"); $this->fail(21, "Invalid photo");
}
if($phot->isDeleted()) if($phot->isDeleted()) {
$this->fail(21, "Photo already deleted"); $this->fail(21, "Photo already deleted");
}
$phot->delete(); $phot->delete();
} }
@ -544,11 +573,17 @@ final class Photos extends VKAPIRequestHandler
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
$comment = (new CommentsRepo)->get($comment_id); $comment = (new CommentsRepo)->get($comment_id);
if(!$comment) if(!$comment) {
$this->fail(21, "Invalid comment"); $this->fail(21, "Invalid comment");
}
if(!$comment->canBeModifiedBy($this->getUser())) if(!$comment->canBeModifiedBy($this->getUser())) {
$this->fail(21, "Access denied"); $this->fail(21, "Forbidden");
}
if($comment->isDeleted()) {
$this->fail(4, "Comment already deleted");
}
$comment->delete(); $comment->delete();
@ -560,16 +595,20 @@ final class Photos extends VKAPIRequestHandler
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
if(empty($message) && empty($attachments)) if(empty($message) && empty($attachments)) {
$this->fail(100, "Required parameter 'message' missing."); $this->fail(100, "Required parameter 'message' missing.");
}
$photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id); $photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id);
if(!$photo || $photo->isDeleted()) if(!$photo->getAlbum()->getOwner()->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(180, "Invalid photo"); $this->fail(21, "This user chose to hide his albums.");
}
if(!$photo->canBeViewedBy($this->getUser())) if(!$photo)
$this->fail(15, "Access to photo denied"); $this->fail(180, "Photo not found");
if($photo->isDeleted())
$this->fail(189, "Photo is deleted");
$comment = new Comment; $comment = new Comment;
$comment->setOwner($this->getUser()->getId()); $comment->setOwner($this->getUser()->getId());
@ -628,22 +667,24 @@ final class Photos extends VKAPIRequestHandler
function getAll(int $owner_id, bool $extended = false, int $offset = 0, int $count = 100, bool $photo_sizes = false) function getAll(int $owner_id, bool $extended = false, int $offset = 0, int $count = 100, bool $photo_sizes = false)
{ {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction();
if($owner_id < 0) if($owner_id < 0) {
$this->fail(4, "This method doesn't works with clubs"); $this->fail(4, "This method doesn't works with clubs");
}
$user = (new UsersRepo)->get($owner_id); $user = (new UsersRepo)->get($owner_id);
if(!$user)
$this->fail(4, "Invalid user");
if(!$user->getPrivacyPermission('photos.read', $this->getUser()))
$this->fail(21, "This user chose to hide his albums.");
$photos = (new PhotosRepo)->getEveryUserPhoto($user, $offset, $count); if(!$user) {
$res = [ $this->fail(4, "Invalid user");
"count" => (new PhotosRepo)->getUserPhotosCount($user), }
"items" => [],
]; if(!$user->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "This user chose to hide his albums.");
}
$photos = array_slice(iterator_to_array((new PhotosRepo)->getEveryUserPhoto($user, 1, $count + $offset)), $offset);
$res = [];
foreach($photos as $photo) { foreach($photos as $photo) {
if(!$photo || $photo->isDeleted()) continue; if(!$photo || $photo->isDeleted()) continue;
@ -656,15 +697,22 @@ final class Photos extends VKAPIRequestHandler
function getComments(int $owner_id, int $photo_id, bool $need_likes = false, int $offset = 0, int $count = 100, bool $extended = false, string $fields = "") function getComments(int $owner_id, int $photo_id, bool $need_likes = false, int $offset = 0, int $count = 100, bool $extended = false, string $fields = "")
{ {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction();
$photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id); $photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id);
$comms = array_slice(iterator_to_array($photo->getComments(1, $offset + $count)), $offset); $comms = array_slice(iterator_to_array($photo->getComments(1, $offset + $count)), $offset);
if(!$photo || $photo->isDeleted()) if(!$photo) {
$this->fail(4, "Invalid photo"); $this->fail(4, "Invalid photo");
}
if(!$photo->canBeViewedBy($this->getUser())) if(!$photo->getAlbum()->getOwner()->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "Access denied"); $this->fail(21, "This user chose to hide his photos.");
}
if($photo->isDeleted()) {
$this->fail(4, "Photo is deleted");
}
$res = [ $res = [
"count" => sizeof($comms), "count" => sizeof($comms),
@ -682,4 +730,4 @@ final class Photos extends VKAPIRequestHandler
return $res; return $res;
} }
} }

View file

@ -104,67 +104,4 @@ final class Polls extends VKAPIRequestHandler
$this->fail(8, "how.to. ook.bacon.in.microwova."); $this->fail(8, "how.to. ook.bacon.in.microwova.");
} }
} }
function getVoters(int $poll_id, int $answer_ids, int $offset = 0, int $count = 6)
{
$this->requireUser();
$poll = (new PollsRepo)->get($poll_id);
if(!$poll)
$this->fail(251, "Invalid poll");
if($poll->isAnonymous())
$this->fail(251, "Access denied: poll is anonymous.");
$voters = array_slice($poll->getVoters($answer_ids, 1, $offset + $count), $offset);
$res = (object)[
"answer_id" => $answer_ids,
"users" => []
];
foreach($voters as $voter)
$res->users[] = $voter->toVkApiStruct();
return $res;
}
function create(string $question, string $add_answers, bool $disable_unvote = false, bool $is_anonymous = false, bool $is_multiple = false, int $end_date = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
$options = json_decode($add_answers);
if(!$options || empty($options))
$this->fail(62, "Invalid options");
if(sizeof($options) > ovkGetQuirk("polls.max-opts"))
$this->fail(51, "Too many options");
$poll = new Poll;
$poll->setOwner($this->getUser());
$poll->setTitle($question);
$poll->setMultipleChoice($is_multiple);
$poll->setAnonymity($is_anonymous);
$poll->setRevotability(!$disable_unvote);
$poll->setOptions($options);
if($end_date > time()) {
if($end_date > time() + (DAY * 365))
$this->fail(89, "End date is too big");
$poll->setEndDate($end_date);
}
$poll->save();
return $this->getById($poll->getId());
}
function edit()
{
#todo
return 1;
}
} }

View file

@ -5,49 +5,37 @@ use openvk\Web\Models\Repositories\Reports as ReportsRepo;
final class Reports extends VKAPIRequestHandler final class Reports extends VKAPIRequestHandler
{ {
function add(int $owner_id = 0, string $comment = "", int $reason = 0, string $type = "", string $report_source = ""): int function add(string $type, int $id, string $reason): object
{ {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
$allowed_types = ["post", "photo", "video", "group", "comment", "note", "app", "user", "audio"]; if ($id <= 0) {
if($type == "" || !in_array($type, $allowed_types)) { $this->fail(100, "ID must be a positive number");
$this->fail(100, "One of the parameters specified was missing or invalid: type should be ".implode(", ", $allowed_types));
} }
if($owner_id <= 0) { if(mb_strlen(trim($reason)) === 0) {
$this->fail(100, "One of the parameters specified was missing or invalid: Bad input"); $this->fail(100, "Reason can't be empty");
} }
if(mb_strlen($comment) === 0) { if ($type === "user" && $id === $this->getUser()->getId()) {
$this->fail(100, "One of the parameters specified was missing or invalid: Comment can't be empty"); $this->fail(100, "You can't report yourself");
} }
if($type == "user" && $owner_id == $this->getUser()->getId()) { if(in_array($type, ["post", "photo", "video", "group", "comment", "note", "app", "user"])) {
return 1; if (count(iterator_to_array((new ReportsRepo)->getDuplicates($type, $id, NULL, $this->getUser()->getId()))) <= 0) {
$report = new Report;
$report->setUser_id($this->getUser()->getId());
$report->setTarget_id($id);
$report->setType($type);
$report->setReason($reason);
$report->setCreated(time());
$report->save();
}
return (object) [ "reason" => $reason ];
} else {
$this->fail(3, "Unable to submit a report on this content type");
} }
if($this->getUser()->isBannedInSupport()) {
return 0;
}
if(sizeof(iterator_to_array((new ReportsRepo)->getDuplicates($type, $owner_id, NULL, $this->getUser()->getId()))) > 0) {
return 1;
}
try {
$report = new Report;
$report->setUser_id($this->getUser()->getId());
$report->setTarget_id($owner_id);
$report->setType($type);
$report->setReason($comment);
$report->setCreated(time());
$report->save();
} catch(\Throwable $e) {
$this->fail(-1, "Unknown error failed");
}
return 1;
} }
} }

View file

@ -8,27 +8,13 @@ final class Status extends VKAPIRequestHandler
function get(int $user_id = 0, int $group_id = 0) function get(int $user_id = 0, int $group_id = 0)
{ {
$this->requireUser(); $this->requireUser();
if($user_id == 0 && $group_id == 0) {
if($user_id == 0 && $group_id == 0) return $this->getUser()->getStatus();
$user_id = $this->getUser()->getId(); } else {
if($group_id > 0)
if($group_id > 0) $this->fail(501, "Group statuses are not implemented");
$this->fail(501, "Group statuses are not implemented"); else
else { return (new UsersRepo)->get($user_id)->getStatus();
$user = (new UsersRepo)->get($user_id);
if(!$user || $user->isDeleted() || !$user->canBeViewedBy($this->getUser()))
$this->fail(15, "Invalid user");
$audioStatus = $user->getCurrentAudioStatus();
if($audioStatus) {
return [
"status" => $user->getStatus(),
"audio" => $audioStatus->toVkApiStruct(),
];
}
return $user->getStatus();
} }
} }

View file

@ -1,9 +1,7 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers; namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Entities\{User, Report}; use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\Users as UsersRepo; use openvk\Web\Models\Repositories\Users as UsersRepo;
use openvk\Web\Models\Repositories\{Photos, Clubs, Albums, Videos, Notes, Audios};
use openvk\Web\Models\Repositories\Reports;
final class Users extends VKAPIRequestHandler final class Users extends VKAPIRequestHandler
{ {
@ -38,8 +36,8 @@ final class Users extends VKAPIRequestHandler
} else if($usr->isBanned()) { } else if($usr->isBanned()) {
$response[$i] = (object)[ $response[$i] = (object)[
"id" => $usr->getId(), "id" => $usr->getId(),
"first_name" => $usr->getFirstName(true), "first_name" => $usr->getFirstName(),
"last_name" => $usr->getLastName(true), "last_name" => $usr->getLastName(),
"deactivated" => "banned", "deactivated" => "banned",
"ban_reason" => $usr->getBanReason() "ban_reason" => $usr->getBanReason()
]; ];
@ -48,21 +46,21 @@ final class Users extends VKAPIRequestHandler
} else { } else {
$response[$i] = (object)[ $response[$i] = (object)[
"id" => $usr->getId(), "id" => $usr->getId(),
"first_name" => $usr->getFirstName(true), "first_name" => $usr->getFirstName(),
"last_name" => $usr->getLastName(true), "last_name" => $usr->getLastName(),
"is_closed" => $usr->isClosed(), "is_closed" => false,
"can_access_closed" => (bool)$usr->canBeViewedBy($this->getUser()), "can_access_closed" => true,
]; ];
$flds = explode(',', $fields); $flds = explode(',', $fields);
$canView = $usr->canBeViewedBy($this->getUser());
foreach($flds as $field) { foreach($flds as $field) {
switch($field) { switch($field) {
case "verified": case "verified":
$response[$i]->verified = intval($usr->isVerified()); $response[$i]->verified = intval($usr->isVerified());
break; break;
case "sex": case "sex":
$response[$i]->sex = $usr->isFemale() ? 1 : ($usr->isNeutral() ? 0 : 2); $response[$i]->sex = $usr->isFemale() ? 1 : 2;
break; break;
case "has_photo": case "has_photo":
$response[$i]->has_photo = is_null($usr->getAvatarPhoto()) ? 0 : 1; $response[$i]->has_photo = is_null($usr->getAvatarPhoto()) ? 0 : 1;
@ -97,12 +95,6 @@ final class Users extends VKAPIRequestHandler
case "status": case "status":
if($usr->getStatus() != NULL) if($usr->getStatus() != NULL)
$response[$i]->status = $usr->getStatus(); $response[$i]->status = $usr->getStatus();
$audioStatus = $usr->getCurrentAudioStatus();
if($audioStatus)
$response[$i]->status_audio = $audioStatus->toVkApiStruct();
break; break;
case "screen_name": case "screen_name":
if($usr->getShortCode() != NULL) if($usr->getShortCode() != NULL)
@ -150,122 +142,26 @@ final class Users extends VKAPIRequestHandler
]; ];
} }
case "music": case "music":
if(!$canView) {
break;
}
$response[$i]->music = $usr->getFavoriteMusic(); $response[$i]->music = $usr->getFavoriteMusic();
break; break;
case "movies": case "movies":
if(!$canView) {
break;
}
$response[$i]->movies = $usr->getFavoriteFilms(); $response[$i]->movies = $usr->getFavoriteFilms();
break; break;
case "tv": case "tv":
if(!$canView) {
break;
}
$response[$i]->tv = $usr->getFavoriteShows(); $response[$i]->tv = $usr->getFavoriteShows();
break; break;
case "books": case "books":
if(!$canView) {
break;
}
$response[$i]->books = $usr->getFavoriteBooks(); $response[$i]->books = $usr->getFavoriteBooks();
break; break;
case "city": case "city":
if(!$canView) {
break;
}
$response[$i]->city = $usr->getCity(); $response[$i]->city = $usr->getCity();
break; break;
case "interests": case "interests":
if(!$canView) {
break;
}
$response[$i]->interests = $usr->getInterests(); $response[$i]->interests = $usr->getInterests();
break; break;
case "quotes":
if(!$canView) {
break;
}
$response[$i]->quotes = $usr->getFavoriteQuote();
break;
case "email":
if(!$canView) {
break;
}
$response[$i]->email = $usr->getContactEmail();
break;
case "telegram":
if(!$canView) {
break;
}
$response[$i]->telegram = $usr->getTelegram();
break;
case "about":
if(!$canView) {
break;
}
$response[$i]->about = $usr->getDescription();
break;
case "rating": case "rating":
if(!$canView) {
break;
}
$response[$i]->rating = $usr->getRating(); $response[$i]->rating = $usr->getRating();
break; break;
case "counters":
$response[$i]->counters = (object) [
"friends_count" => $usr->getFriendsCount(),
"photos_count" => (new Photos)->getUserPhotosCount($usr),
"videos_count" => (new Videos)->getUserVideosCount($usr),
"audios_count" => (new Audios)->getUserCollectionSize($usr),
"notes_count" => (new Notes)->getUserNotesCount($usr)
];
break;
case "correct_counters":
$response[$i]->counters = (object) [
"friends" => $usr->getFriendsCount(),
"photos" => (new Photos)->getUserPhotosCount($usr),
"videos" => (new Videos)->getUserVideosCount($usr),
"audios" => (new Audios)->getUserCollectionSize($usr),
"notes" => (new Notes)->getUserNotesCount($usr),
"groups" => $usr->getClubCount(),
"online_friends" => $usr->getFriendsOnlineCount(),
];
break;
case "guid":
$response[$i]->guid = $usr->getChandlerGUID();
break;
case 'background':
$backgrounds = $usr->getBackDropPictureURLs();
$response[$i]->background = $backgrounds;
break;
case 'reg_date':
if(!$canView) {
break;
}
$response[$i]->reg_date = $usr->getRegistrationTime()->timestamp();
break;
case 'is_dead':
$response[$i]->is_dead = $usr->isDead();
break;
case 'nickname':
$response[$i]->nickname = $usr->getPseudo();
break;
} }
} }
@ -289,14 +185,6 @@ final class Users extends VKAPIRequestHandler
$this->requireUser(); $this->requireUser();
$user = $users->get($user_id);
if(!$user || $user->isDeleted())
$this->fail(14, "Invalid user");
if(!$user->canBeViewedBy($this->getUser()))
$this->fail(15, "Access denied");
foreach($users->get($user_id)->getFollowers($offset, $count) as $follower) foreach($users->get($user_id)->getFollowers($offset, $count) as $follower)
$followers[] = $follower->getId(); $followers[] = $follower->getId();
@ -317,112 +205,88 @@ final class Users extends VKAPIRequestHandler
int $count = 100, int $count = 100,
string $city = "", string $city = "",
string $hometown = "", string $hometown = "",
int $sex = 3, int $sex = 2,
int $status = 0, # marital_status int $status = 0, # это про marital status
bool $online = false, bool $online = false,
# non standart params: # дальше идут параметры которых нету в vkapi но есть на сайте
string $profileStatus = "", # а это уже нормальный статус
int $sort = 0, int $sort = 0,
int $polit_views = 0, int $before = 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 = ""
) )
{ {
if($count > 100) {
$this->fail(100, "One of the parameters specified was missing or invalid: count should be less or equal to 100");
}
$users = new UsersRepo; $users = new UsersRepo;
$output_sort = ['type' => 'id', 'invert' => false];
$output_params = [ $sortg = "id ASC";
"ignore_private" => true,
]; $nfilds = $fields;
switch($sort) { switch($sort) {
default:
case 0: case 0:
$output_sort = ['type' => 'id', 'invert' => false]; $sortg = "id DESC";
break; break;
case 1: case 1:
$output_sort = ['type' => 'id', 'invert' => true]; $sortg = "id ASC";
break;
case 2:
$sortg = "first_name DESC";
break;
case 3:
$sortg = "first_name ASC";
break; break;
case 4: case 4:
$output_sort = ['type' => 'rating', 'invert' => false]; $sortg = "rating DESC";
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);
foreach ($find->offsetLimit($offset, $count) as $user) $parameters = [
"city" => !empty($city) ? $city : NULL,
"hometown" => !empty($hometown) ? $hometown : NULL,
"gender" => $sex < 2 ? $sex : NULL,
"maritalstatus" => (bool)$status ? $status : NULL,
"politViews" => (bool)$politViews ? $politViews : NULL,
"is_online" => $online ? 1 : NULL,
"status" => !empty($profileStatus) ? $profileStatus : NULL,
"before" => $before != 0 ? $before : NULL,
"after" => $after != 0 ? $after : NULL,
"interests" => !empty($interests) ? $interests : NULL,
"fav_music" => !empty($fav_music) ? $fav_music : NULL,
"fav_films" => !empty($fav_films) ? $fav_films : NULL,
"fav_shows" => !empty($fav_shows) ? $fav_shows : NULL,
"fav_books" => !empty($fav_books) ? $fav_books : NULL,
"fav_quotes" => !empty($fav_quotes) ? $fav_quotes : NULL,
];
$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), $fields) "items" => $this->get(implode(',', $array), $nfilds, $offset, $count)
]; ];
} }
function report(int $user_id, string $type = "spam", string $comment = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
if($user_id == $this->getUser()->getId())
$this->fail(12, "Can't report yourself.");
if(sizeof(iterator_to_array((new Reports)->getDuplicates("user", $user_id, NULL, $this->getUser()->getId()))) > 0)
return 1;
$report = new Report;
$report->setUser_id($this->getUser()->getId());
$report->setTarget_id($user_id);
$report->setType("user");
$report->setReason($comment);
$report->setCreated(time());
$report->save();
return 1;
}
} }

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,17 +39,8 @@ final class Utils extends VKAPIRequestHandler
"type" => "group" "type" => "group"
]; ];
} }
$this->fail(104, "Not found"); return (object) [];
} }
} }
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

@ -28,7 +28,7 @@ abstract class VKAPIRequestHandler
protected function getPlatform(): ?string protected function getPlatform(): ?string
{ {
return $this->platform ?? ""; return $this->platform;
} }
protected function userAuthorized(): bool protected function userAuthorized(): bool

View file

@ -11,55 +11,24 @@ use openvk\Web\Models\Repositories\Comments as CommentsRepo;
final class Video extends VKAPIRequestHandler final class Video extends VKAPIRequestHandler
{ {
function get(int $owner_id = 0, string $videos = "", string $fields = "", int $offset = 0, int $count = 30, int $extended = 0): object function get(int $owner_id, string $videos, int $offset = 0, int $count = 30, int $extended = 0): object
{ {
$this->requireUser(); $this->requireUser();
if(!empty($videos)) { if ($videos) {
$vids = explode(',', $videos); $vids = explode(',', $videos);
$profiles = [];
$groups = []; foreach($vids as $vid)
foreach($vids as $vid) { {
$id = explode("_", $vid); $id = explode("_", $vid);
$items = []; $items = [];
$video = (new VideosRepo)->getByOwnerAndVID(intval($id[0]), intval($id[1])); $video = (new VideosRepo)->getByOwnerAndVID(intval($id[0]), intval($id[1]));
if($video && !$video->isDeleted()) { if($video) {
$out_video = $video->getApiStructure($this->getUser())->video; $items[] = $video->getApiStructure();
$items[] = $out_video;
if($out_video['owner_id']) {
if($out_video['owner_id'] > 0)
$profiles[] = $out_video['owner_id'];
else
$groups[] = abs($out_video['owner_id']);
}
} }
} }
if($extended == 1) {
$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" => sizeof($items),
"items" => $items,
"profiles" => $profilesFormatted,
"groups" => $groupsFormatted,
];
}
return (object) [ return (object) [
"count" => count($items), "count" => count($items),
@ -67,56 +36,16 @@ final class Video extends VKAPIRequestHandler
]; ];
} else { } else {
if ($owner_id > 0) if ($owner_id > 0)
$user = (new UsersRepo)->get($owner_id); $user = (new UsersRepo)->get($owner_id);
else else
$this->fail(1, "Not implemented"); $this->fail(1, "Not implemented");
if(!$user || $user->isDeleted()) $videos = (new VideosRepo)->getByUser($user, $offset + 1, $count);
$this->fail(14, "Invalid user");
if(!$user->getPrivacyPermission('videos.read', $this->getUser()))
$this->fail(21, "This user chose to hide his videos.");
$videos = (new VideosRepo)->getByUserLimit($user, $offset, $count);
$videosCount = (new VideosRepo)->getUserVideosCount($user); $videosCount = (new VideosRepo)->getUserVideosCount($user);
$items = []; $items = [];
$profiles = []; foreach ($videos as $video) {
$groups = []; $items[] = $video->getApiStructure();
foreach($videos as $video) {
$video = $video->getApiStructure($this->getUser())->video;
$items[] = $video;
if($video['owner_id']) {
if($video['owner_id'] > 0)
$profiles[] = $video['owner_id'];
else
$groups[] = abs($video['owner_id']);
}
}
if($extended == 1) {
$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" => $videosCount,
"items" => $items,
"profiles" => $profilesFormatted,
"groups" => $groupsFormatted,
];
} }
return (object) [ return (object) [
@ -125,61 +54,4 @@ 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,
];
}
} }

File diff suppressed because it is too large Load diff

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.ovk.to/openvk_engine/api/description/) [Documentation for API clients](https://docs.openvk.uk/openvk_engine/api/description/)
## Implementing API methods ## Implementing API methods

View file

@ -48,7 +48,7 @@ class APIToken extends RowModel
$this->delete(); $this->delete();
} }
function save(?bool $log = false): void function save(): void
{ {
if(is_null($this->getRecord())) if(is_null($this->getRecord()))
$this->stateChanges("secret", bin2hex(openssl_random_pseudo_bytes(36))); $this->stateChanges("secret", bin2hex(openssl_random_pseudo_bytes(36)));

View file

@ -67,27 +67,11 @@ class Album extends MediaCollection
return $this->has($photo); return $this->has($photo);
} }
function canBeViewedBy(?User $user = NULL): bool
{
if($this->isDeleted()) {
return false;
}
$owner = $this->getOwner();
if(get_class($owner) == "openvk\\Web\\Models\\Entities\\User") {
return $owner->canBeViewedBy($user) && $owner->getPrivacyPermission('photos.read', $user);
} else {
return $owner->canBeViewedBy($user);
}
}
function toVkApiStruct(?User $user = NULL, bool $need_covers = false, bool $photo_sizes = false): object function toVkApiStruct(?User $user = NULL, bool $need_covers = false, bool $photo_sizes = false): object
{ {
$res = (object) []; $res = (object) [];
$res->id = $this->getPrettyId(); $res->id = $this->getPrettyId();
$res->vid = $this->getId();
$res->thumb_id = !is_null($this->getCoverPhoto()) ? $this->getCoverPhoto()->getPrettyId() : 0; $res->thumb_id = !is_null($this->getCoverPhoto()) ? $this->getCoverPhoto()->getPrettyId() : 0;
$res->owner_id = $this->getOwner()->getId(); $res->owner_id = $this->getOwner()->getId();
$res->title = $this->getName(); $res->title = $this->getName();

View file

@ -1,479 +0,0 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Util\Shell\Exceptions\UnknownCommandException;
use openvk\Web\Util\Shell\Shell;
/**
* @method setName(string)
* @method setPerformer(string)
* @method setLyrics(string)
* @method setExplicit(bool)
*/
class Audio extends Media
{
protected $tableName = "audios";
protected $fileExtension = "mpd";
# Taken from winamp :D
const genres = [
'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
const vkGenres = [
"Rock" => 1,
"Pop" => 2,
"Rap" => 3,
"Hip-Hop" => 3, # VK API lists №3 as Rap & Hip-Hop, but these genres are distinct in OpenVK
"Easy Listening" => 4,
"House" => 5,
"Dance" => 5,
"Instrumental" => 6,
"Metal" => 7,
"Alternative" => 21,
"Dubstep" => 8,
"Jazz" => 1001,
"Blues" => 1001,
"Drum & Bass" => 10,
"Trance" => 11,
"Chanson" => 12,
"Ethnic" => 13,
"Acoustic" => 14,
"Vocal" => 14,
"Reggae" => 15,
"Classical" => 16,
"Indie Pop" => 17,
"Speech" => 19,
"Disco" => 22,
"Other" => 18,
];
private function fileLength(string $filename): int
{
if(!Shell::commandAvailable("ffmpeg") || !Shell::commandAvailable("ffprobe"))
throw new \Exception();
$error = NULL;
$streams = Shell::ffprobe("-i", $filename, "-show_streams", "-select_streams a", "-loglevel error")->execute($error);
if($error !== 0)
throw new \DomainException("$filename is not recognized as media container");
else if(empty($streams) || ctype_space($streams))
throw new \DomainException("$filename does not contain any audio streams");
$vstreams = Shell::ffprobe("-i", $filename, "-show_streams", "-select_streams v", "-loglevel error")->execute($error);
# check if audio has cover (attached_pic)
preg_match("%attached_pic=([0-1])%", $vstreams, $hasCover);
if(!empty($vstreams) && !ctype_space($vstreams) && ((int)($hasCover[1]) !== 1))
throw new \DomainException("$filename is a video");
$durations = [];
preg_match_all('%duration=([0-9\.]++)%', $streams, $durations);
if(sizeof($durations[1]) === 0)
throw new \DomainException("$filename does not contain any meaningful audio streams");
$length = 0;
foreach($durations[1] as $duration) {
$duration = floatval($duration);
if($duration < 1.0 || $duration > 65536.0)
throw new \DomainException("$filename does not contain any meaningful audio streams");
else
$length = max($length, $duration);
}
return (int) round($length, 0, PHP_ROUND_HALF_EVEN);
}
/**
* @throws \Exception
*/
protected function saveFile(string $filename, string $hash): bool
{
$duration = $this->fileLength($filename);
$kid = openssl_random_pseudo_bytes(16);
$key = openssl_random_pseudo_bytes(16);
$tok = openssl_random_pseudo_bytes(28);
$ss = ceil($duration / 15);
$this->stateChanges("kid", $kid);
$this->stateChanges("key", $key);
$this->stateChanges("token", $tok);
$this->stateChanges("segment_size", $ss);
$this->stateChanges("length", $duration);
try {
$args = [
str_replace("enabled", "available", OPENVK_ROOT),
str_replace("enabled", "available", $this->getBaseDir()),
$hash,
$filename,
bin2hex($kid),
bin2hex($key),
bin2hex($tok),
$ss,
];
if(Shell::isPowershell()) {
Shell::powershell("-executionpolicy bypass", "-File", __DIR__ . "/../shell/processAudio.ps1", ...$args)
->start();
} else {
Shell::bash(__DIR__ . "/../shell/processAudio.sh", ...$args) // Pls workkkkk
->start(); // idk, not tested :")
}
# Wait until processAudio will consume the file
$start = time();
while(file_exists($filename))
if(time() - $start > 5)
throw new \RuntimeException("Timed out waiting FFMPEG");
} catch(UnknownCommandException $ucex) {
exit(OPENVK_ROOT_CONF["openvk"]["debug"] ? "bash/pwsh is not installed" : VIDEOS_FRIENDLY_ERROR);
}
return true;
}
function getTitle(): string
{
return $this->getRecord()->name;
}
function getPerformer(): string
{
return $this->getRecord()->performer;
}
function getPerformers(): array
{
return explode(", ", $this->getRecord()->performer);
}
function getName(): string
{
return $this->getPerformer() . "" . $this->getTitle();
}
function getDownloadName(): string
{
return preg_replace('/[\\/:*?"<>|]/', '_', str_replace(' ', '_', $this->getName()));
}
function getGenre(): ?string
{
return $this->getRecord()->genre;
}
function getLyrics(): ?string
{
return !is_null($this->getRecord()->lyrics) ? htmlspecialchars($this->getRecord()->lyrics, ENT_DISALLOWED | ENT_XHTML) : NULL;
}
function getLength(): int
{
return $this->getRecord()->length;
}
function getFormattedLength(): string
{
$len = $this->getLength();
$mins = floor($len / 60);
$secs = $len - ($mins * 60);
return (
str_pad((string) $mins, 2, "0", STR_PAD_LEFT)
. ":" .
str_pad((string) $secs, 2, "0", STR_PAD_LEFT)
);
}
function getSegmentSize(): float
{
return $this->getRecord()->segment_size;
}
function getListens(): int
{
return $this->getRecord()->listens;
}
function getOriginalURL(bool $force = false): string
{
$disallowed = !OPENVK_ROOT_CONF["openvk"]["preferences"]["music"]["exposeOriginalURLs"] && !$force;
if(!$this->isAvailable() || $disallowed)
return ovk_scheme(true)
. $_SERVER["HTTP_HOST"] . ":"
. $_SERVER["HTTP_PORT"]
. "/assets/packages/static/openvk/audio/nomusic.mp3";
$key = bin2hex($this->getRecord()->token);
return str_replace(".mpd", "_fragments", $this->getURL()) . "/original_$key.mp3";
}
function getURL(?bool $force = false): string
{
if ($this->isWithdrawn()) return "";
return parent::getURL();
}
function getKeys(): array
{
$keys[bin2hex($this->getRecord()->kid)] = bin2hex($this->getRecord()->key);
return $keys;
}
function isAnonymous(): bool
{
return false;
}
function isExplicit(): bool
{
return (bool) $this->getRecord()->explicit;
}
function isWithdrawn(): bool
{
return (bool) $this->getRecord()->withdrawn;
}
function isUnlisted(): bool
{
return (bool) $this->getRecord()->unlisted;
}
# NOTICE may flush model to DB if it was just processed
function isAvailable(): bool
{
if($this->getRecord()->processed)
return true;
# throttle requests to isAvailable to prevent DoS attack if filesystem is actually an S3 storage
if(time() - $this->getRecord()->checked < 5)
return false;
try {
$fragments = str_replace(".mpd", "_fragments", $this->getFileName());
$original = "original_" . bin2hex($this->getRecord()->token) . ".mp3";
if(file_exists("$fragments/$original")) {
# Original gets uploaded after fragments
$this->stateChanges("processed", 0x01);
return true;
}
} finally {
$this->stateChanges("checked", time());
$this->save();
}
return false;
}
function isInLibraryOf($entity): bool
{
return sizeof(DatabaseConnection::i()->getContext()->table("audio_relations")->where([
"entity" => $entity->getId() * ($entity instanceof Club ? -1 : 1),
"audio" => $this->getId(),
])) != 0;
}
function add($entity): bool
{
if($this->isInLibraryOf($entity))
return false;
$entityId = $entity->getId() * ($entity instanceof Club ? -1 : 1);
$audioRels = DatabaseConnection::i()->getContext()->table("audio_relations");
if(sizeof($audioRels->where("entity", $entityId)) > 65536)
throw new \OverflowException("Can't have more than 65536 audios in a playlist");
$audioRels->insert([
"entity" => $entityId,
"audio" => $this->getId(),
]);
return true;
}
function remove($entity): bool
{
if(!$this->isInLibraryOf($entity))
return false;
DatabaseConnection::i()->getContext()->table("audio_relations")->where([
"entity" => $entity->getId() * ($entity instanceof Club ? -1 : 1),
"audio" => $this->getId(),
])->delete();
return true;
}
function listen($entity, Playlist $playlist = NULL): bool
{
$listensTable = DatabaseConnection::i()->getContext()->table("audio_listens");
$lastListen = $listensTable->where([
"entity" => $entity->getRealId(),
"audio" => $this->getId(),
])->order("index DESC")->fetch();
if(!$lastListen || (time() - $lastListen->time >= $this->getLength())) {
$listensTable->insert([
"entity" => $entity->getRealId(),
"audio" => $this->getId(),
"time" => time(),
"playlist" => $playlist ? $playlist->getId() : NULL,
]);
if($entity instanceof User) {
$this->stateChanges("listens", ($this->getListens() + 1));
$this->save();
if($playlist) {
$playlist->incrementListens();
$playlist->save();
}
}
$entity->setLast_played_track($this->getId());
$entity->save();
return true;
}
$lastListen->update([
"time" => time(),
]);
return false;
}
/**
* Returns compatible with VK API 4.x, 5.x structure.
*
* Always sets album(_id) to NULL at this time.
* If genre is not present in VK genre list, fallbacks to "Other".
* The url and manifest properties will be set to false if the audio can't be played (processing, removed).
*
* Aside from standard VK properties, this method will also return some OVK extended props:
* 1. added - Is in the library of $user?
* 2. editable - Can be edited by $user?
* 3. withdrawn - Removed due to copyright request?
* 4. ready - Can be played at this time?
* 5. genre_str - Full name of genre, NULL if it's undefined
* 6. manifest - URL to MPEG-DASH manifest
* 7. keys - ClearKey DRM keys
* 8. explicit - Marked as NSFW?
* 9. searchable - Can be found via search?
* 10. unique_id - Unique ID of audio
*
* @notice that in case if exposeOriginalURLs is set to false in config, "url" will always contain link to nomusic.mp3,
* unless $forceURLExposure is set to true.
*
* @notice may trigger db flush if the audio is not processed yet, use with caution on unsaved models.
*
* @param ?User $user user, relative to whom "added", "editable" will be set
* @param bool $forceURLExposure force set "url" regardless of config
*/
function toVkApiStruct(?User $user = NULL, bool $forceURLExposure = false): object
{
$obj = (object) [];
$obj->unique_id = base64_encode((string) $this->getId());
$obj->id = $obj->aid = $this->getVirtualId();
$obj->artist = $this->getPerformer();
$obj->title = $this->getTitle();
$obj->duration = $this->getLength();
$obj->album_id = $obj->album = NULL; # i forgor to implement
$obj->url = false;
$obj->manifest = false;
$obj->keys = false;
$obj->genre_id = $obj->genre = self::vkGenres[$this->getGenre() ?? ""] ?? 18; # return Other if no match
$obj->genre_str = $this->getGenre();
$obj->owner_id = $this->getOwner()->getId();
if($this->getOwner() instanceof Club)
$obj->owner_id *= -1;
$obj->lyrics = NULL;
if(!is_null($this->getLyrics()))
$obj->lyrics = $this->getId();
$obj->added = $user && $this->isInLibraryOf($user);
$obj->editable = $user && $this->canBeModifiedBy($user);
$obj->searchable = !$this->isUnlisted();
$obj->explicit = $this->isExplicit();
$obj->withdrawn = $this->isWithdrawn();
$obj->ready = $this->isAvailable() && !$obj->withdrawn;
if($obj->ready) {
$obj->url = $this->getOriginalURL($forceURLExposure);
$obj->manifest = $this->getURL();
$obj->keys = $this->getKeys();
}
return $obj;
}
function setOwner(int $oid): void
{
# WARNING: API implementation won't be able to handle groups like that, don't remove
if($oid <= 0)
throw new \OutOfRangeException("Only users can be owners of audio!");
$this->stateChanges("owner", $oid);
}
function setGenre(string $genre): void
{
if(!in_array($genre, Audio::genres)) {
$this->stateChanges("genre", NULL);
return;
}
$this->stateChanges("genre", $genre);
}
function setCopyrightStatus(bool $withdrawn = true): void {
$this->stateChanges("withdrawn", $withdrawn);
}
function setSearchability(bool $searchable = true): void {
$this->stateChanges("unlisted", !$searchable);
}
function setToken(string $tok): void {
throw new \LogicException("Changing keys is not supported.");
}
function setKid(string $kid): void {
throw new \LogicException("Changing keys is not supported.");
}
function setKey(string $key): void {
throw new \LogicException("Changing keys is not supported.");
}
function setLength(int $len): void {
throw new \LogicException("Changing length is not supported.");
}
function setSegment_Size(int $len): void {
throw new \LogicException("Changing length is not supported.");
}
function delete(bool $softly = true): void
{
$ctx = DatabaseConnection::i()->getContext();
$ctx->table("audio_relations")->where("audio", $this->getId())
->delete();
$ctx->table("audio_listens")->where("audio", $this->getId())
->delete();
$ctx->table("playlist_relations")->where("media", $this->getId())
->delete();
parent::delete($softly);
}
}

View file

@ -3,7 +3,7 @@ namespace openvk\Web\Models\Entities;
use openvk\Web\Util\DateTime; use openvk\Web\Util\DateTime;
use openvk\Web\Models\RowModel; use openvk\Web\Models\RowModel;
use openvk\Web\Models\Entities\{User, Manager}; use openvk\Web\Models\Entities\{User, Manager};
use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Managers, Posts}; use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Managers};
use Nette\Database\Table\{ActiveRow, GroupedSelection}; use Nette\Database\Table\{ActiveRow, GroupedSelection};
use Chandler\Database\DatabaseConnection as DB; use Chandler\Database\DatabaseConnection as DB;
use Chandler\Security\User as ChandlerUser; use Chandler\Security\User as ChandlerUser;
@ -23,10 +23,6 @@ class Club extends RowModel
const NOT_RELATED = 0; const NOT_RELATED = 0;
const SUBSCRIBED = 1; const SUBSCRIBED = 1;
const REQUEST_SENT = 2; const REQUEST_SENT = 2;
const WALL_CLOSED = 0;
const WALL_OPEN = 1;
const WALL_LIMITED = 2;
function getId(): int function getId(): int
{ {
@ -42,19 +38,13 @@ class Club extends RowModel
return iterator_to_array($avPhotos)[0] ?? NULL; return iterator_to_array($avPhotos)[0] ?? NULL;
} }
function getAvatarUrl(string $size = "miniscule", $avPhoto = NULL): string function getAvatarUrl(string $size = "miniscule"): string
{ {
$serverUrl = ovk_scheme(true) . $_SERVER["HTTP_HOST"]; $serverUrl = ovk_scheme(true) . $_SERVER["HTTP_HOST"];
if(!$avPhoto) $avPhoto = $this->getAvatarPhoto();
$avPhoto = $this->getAvatarPhoto();
return is_null($avPhoto) ? "$serverUrl/assets/packages/static/openvk/img/camera_200.png" : $avPhoto->getURLBySizeId($size); return is_null($avPhoto) ? "$serverUrl/assets/packages/static/openvk/img/camera_200.png" : $avPhoto->getURLBySizeId($size);
} }
function getWallType(): int
{
return $this->getRecord()->wall;
}
function getAvatarLink(): string function getAvatarLink(): string
{ {
@ -153,11 +143,6 @@ 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;
@ -197,14 +182,6 @@ class Club extends RowModel
$this->stateChanges("shortcode", $code); $this->stateChanges("shortcode", $code);
return true; return true;
} }
function setWall(int $type)
{
if($type > 2 || $type < 0)
throw new \LogicException("Invalid wall");
$this->stateChanges("wall", $type);
}
function isSubscriptionAccepted(User $user): bool function isSubscriptionAccepted(User $user): bool
{ {
@ -247,7 +224,7 @@ class Club extends RowModel
"shape" => "spline", "shape" => "spline",
"color" => "#597da3", "color" => "#597da3",
], ],
"name" => $unique ? tr("full_coverage") : tr("all_views"), "name" => $unique ? "Полный охват" : "Все просмотры",
], ],
"subs" => [ "subs" => [
"x" => array_reverse(range(1, 7)), "x" => array_reverse(range(1, 7)),
@ -258,7 +235,7 @@ class Club extends RowModel
"color" => "#b05c91", "color" => "#b05c91",
], ],
"fill" => "tozeroy", "fill" => "tozeroy",
"name" => $unique ? tr("subs_coverage") : tr("subs_views"), "name" => $unique ? "Охват подписчиков" : "Просмотры подписчиков",
], ],
"viral" => [ "viral" => [
"x" => array_reverse(range(1, 7)), "x" => array_reverse(range(1, 7)),
@ -269,7 +246,7 @@ class Club extends RowModel
"color" => "#4d9fab", "color" => "#4d9fab",
], ],
"fill" => "tozeroy", "fill" => "tozeroy",
"name" => $unique ? tr("viral_coverage") : tr("viral_views"), "name" => $unique ? "Виральный охват" : "Виральные просмотры",
], ],
]; ];
} }
@ -314,21 +291,6 @@ class Club extends RowModel
yield $rel; yield $rel;
} }
} }
function getSuggestedPostsCount(User $user = NULL)
{
$count = 0;
if(is_null($user))
return NULL;
if($this->canBeModifiedBy($user))
$count = (new Posts)->getSuggestedPostsCount($this->getId());
else
$count = (new Posts)->getSuggestedPostsCountByUser($this->getId(), $user->getId());
return $count;
}
function getManagers(int $page = 1, bool $ignoreHidden = false): \Traversable function getManagers(int $page = 1, bool $ignoreHidden = false): \Traversable
{ {
@ -405,96 +367,40 @@ class Club extends RowModel
$this->save(); $this->save();
} }
function canBeViewedBy(?User $user = NULL)
{
return is_null($this->getBanReason());
}
function getAlert(): ?string function getAlert(): ?string
{ {
return $this->getRecord()->alert; return $this->getRecord()->alert;
} }
function getRealId(): int
{
return $this->getId() * -1;
}
function isEveryoneCanUploadAudios(): bool
{
return (bool) $this->getRecord()->everyone_can_upload_audios;
}
function canUploadAudio(?User $user): bool
{
if(!$user)
return NULL;
return $this->isEveryoneCanUploadAudios() || $this->canBeModifiedBy($user);
}
function getAudiosCollectionSize()
{
return (new \openvk\Web\Models\Repositories\Audios)->getClubCollectionSize($this);
}
function toVkApiStruct(?User $user = NULL, string $fields = ''): object function toVkApiStruct(?User $user = NULL): object
{ {
$res = (object) []; $res = [];
$res->id = $this->getId(); $res->id = $this->getId();
$res->name = $this->getName(); $res->name = $this->getName();
$res->screen_name = $this->getShortCode() ?? "club".$this->getId(); $res->screen_name = $this->getShortCode();
$res->is_closed = false; $res->is_closed = 0;
$res->type = 'group';
$res->is_member = $user ? (int)$this->getSubscriptionStatus($user) : 0;
$res->deactivated = NULL; $res->deactivated = NULL;
$res->can_access_closed = true; $res->is_admin = $this->canBeModifiedBy($user);
if(!is_array($fields)) if($this->canBeModifiedBy($user)) {
$fields = explode(',', $fields); $res->admin_level = 3;
$avatar_photo = $this->getAvatarPhoto();
foreach($fields as $field) {
switch($field) {
case 'verified':
$res->verified = (int)$this->isVerified();
break;
case 'site':
$res->site = $this->getWebsite();
break;
case 'description':
$res->description = $this->getDescription();
break;
case 'background':
$res->background = $this->getBackDropPictureURLs();
break;
case 'photo_50':
$res->photo_50 = $this->getAvatarUrl('miniscule', $avatar_photo);
break;
case 'photo_100':
$res->photo_100 = $this->getAvatarUrl('tiny', $avatar_photo);
break;
case 'photo_200':
$res->photo_200 = $this->getAvatarUrl('normal', $avatar_photo);
break;
case 'photo_max':
$res->photo_max = $this->getAvatarUrl('original', $avatar_photo);
break;
case 'members_count':
$res->members_count = $this->getFollowersCount();
break;
case 'real_id':
$res->real_id = $this->getRealId();
break;
}
} }
return $res; $res->is_member = $this->getSubscriptionStatus($user) ? 1 : 0;
$res->type = "group";
$res->photo_50 = $this->getAvatarUrl("miniscule");
$res->photo_100 = $this->getAvatarUrl("tiny");
$res->photo_200 = $this->getAvatarUrl("normal");
$res->can_create_topic = $this->canBeModifiedBy($user) ? 1 : ($this->isEveryoneCanCreateTopics() ? 1 : 0);
$res->can_post = $this->canBeModifiedBy($user) ? 1 : ($this->canPost() ? 1 : 0);
return (object) $res;
} }
use Traits\TBackDrops; use Traits\TBackDrops;
use Traits\TSubscribable; use Traits\TSubscribable;
use Traits\TAudioStatuses;
use Traits\TIgnorable;
} }

View file

@ -11,7 +11,7 @@ class Comment extends Post
function getPrettyId(): string function getPrettyId(): string
{ {
return (string)$this->getRecord()->id; return $this->getRecord()->id;
} }
function getVirtualId(): int function getVirtualId(): int
@ -28,11 +28,6 @@ class Comment extends Post
return $entity; return $entity;
} }
function getPageURL(): string
{
return '#';
}
/** /**
* May return fake owner (group), if flags are [1, (*)] * May return fake owner (group), if flags are [1, (*)]
* *
@ -51,11 +46,8 @@ class Comment extends Post
return parent::getOwner($honourFlags, $real); return parent::getOwner($honourFlags, $real);
} }
function canBeDeletedBy(User $user = NULL): bool function canBeDeletedBy(User $user): bool
{ {
if(!$user)
return false;
return $this->getOwner()->getId() == $user->getId() || return $this->getOwner()->getId() == $user->getId() ||
$this->getTarget()->getOwner()->getId() == $user->getId() || $this->getTarget()->getOwner()->getId() == $user->getId() ||
$this->getTarget() instanceof Post && $this->getTarget()->getTargetWall() < 0 && (new Clubs)->get(abs($this->getTarget()->getTargetWall()))->canBeModifiedBy($user) || $this->getTarget() instanceof Post && $this->getTarget()->getTargetWall() < 0 && (new Clubs)->get(abs($this->getTarget()->getTargetWall()))->canBeModifiedBy($user) ||
@ -82,12 +74,8 @@ class Comment extends Post
foreach($this->getChildren() as $attachment) { foreach($this->getChildren() as $attachment) {
if($attachment->isDeleted()) if($attachment->isDeleted())
continue; continue;
if($attachment instanceof \openvk\Web\Models\Entities\Photo) { $res->attachments[] = $attachment->toVkApiStruct();
$res->attachments[] = $attachment->toVkApiStruct();
} else if($attachment instanceof \openvk\Web\Models\Entities\Video) {
$res->attachments[] = $attachment->toVkApiStruct($this->getUser());
}
} }
if($need_likes) { if($need_likes) {
@ -102,77 +90,4 @@ class Comment extends Post
{ {
return "/wall" . $this->getTarget()->getPrettyId() . "#_comment" . $this->getId(); return "/wall" . $this->getTarget()->getPrettyId() . "#_comment" . $this->getId();
} }
function canBeViewedBy(?User $user = NULL): bool
{
if($this->isDeleted() || $this->getTarget()->isDeleted()) {
return false;
}
return $this->getTarget()->canBeViewedBy($user);
}
function isFromPostAuthor($target = NULL)
{
if(!$target)
$target = $this->getTarget();
$target_owner = $target->getOwner();
$comment_owner = $this->getOwner();
if($target_owner->getRealId() === $comment_owner->getRealId())
return true;
# TODO: make it work with signer_id
return false;
}
function toNotifApiStruct()
{
$res = (object)[];
$res->id = $this->getId();
$res->owner_id = $this->getOwner()->getId();
$res->date = $this->getPublicationTime()->timestamp();
$res->text = $this->getText(false);
$res->post = NULL; # todo
return $res;
}
function canBeEditedBy(?User $user = NULL): bool
{
if(!$user)
return false;
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

@ -131,7 +131,7 @@ class Correspondence
*/ */
function getPreviewMessage(): ?Message function getPreviewMessage(): ?Message
{ {
$messages = $this->getMessages(1, NULL, 1, 0); $messages = $this->getMessages(1, NULL, 1);
return $messages[0] ?? NULL; return $messages[0] ?? NULL;
} }

View file

@ -105,7 +105,7 @@ class IP extends RowModel
$this->stateChanges("ip", $ip); $this->stateChanges("ip", $ip);
} }
function save(?bool $log = false): void function save($log): void
{ {
if(is_null($this->getRecord())) if(is_null($this->getRecord()))
$this->stateChanges("first_seen", time()); $this->stateChanges("first_seen", time());

View file

@ -121,14 +121,14 @@ abstract class Media extends Postable
$this->stateChanges("hash", $hash); $this->stateChanges("hash", $hash);
} }
function save(?bool $log = false): void function save(): void
{ {
if(!is_null($this->processingPlaceholder) && is_null($this->getRecord())) { if(!is_null($this->processingPlaceholder) && is_null($this->getRecord())) {
$this->stateChanges("processed", 0); $this->stateChanges("processed", 0);
$this->stateChanges("last_checked", time()); $this->stateChanges("last_checked", time());
} }
parent::save($log); parent::save();
} }
function delete(bool $softly = true): void function delete(bool $softly = true): void

View file

@ -17,17 +17,7 @@ abstract class MediaCollection extends RowModel
protected $specialNames = []; protected $specialNames = [];
protected $relations; private $relations;
/**
* Maximum amount of items Collection can have
*/
const MAX_ITEMS = INF;
/**
* Maximum amount of Collections with same "owner" allowed
*/
const MAX_COUNT = INF;
function __construct(?ActiveRow $ar = NULL) function __construct(?ActiveRow $ar = NULL)
{ {
@ -80,29 +70,18 @@ abstract class MediaCollection extends RowModel
} }
abstract function getCoverURL(): ?string; abstract function getCoverURL(): ?string;
function fetchClassic(int $offset = 0, ?int $limit = NULL): \Traversable function fetch(int $page = 1, ?int $perPage = NULL): \Traversable
{ {
$related = $this->getRecord()->related("$this->relTableName.collection") $related = $this->getRecord()->related("$this->relTableName.collection")->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE)->order("media ASC");
->limit($limit ?? OPENVK_DEFAULT_PER_PAGE, $offset)
->order("media ASC");
foreach($related as $rel) { foreach($related as $rel) {
$media = $rel->ref($this->entityTableName, "media"); $media = $rel->ref($this->entityTableName, "media");
if(!$media) if(!$media)
continue; continue;
yield new $this->entityClassName($media); yield new $this->entityClassName($media);
} }
} }
function fetch(int $page = 1, ?int $perPage = NULL): \Traversable
{
$page = max(1, $page);
$perPage ??= OPENVK_DEFAULT_PER_PAGE;
return $this->fetchClassic($perPage * ($page - 1), $perPage);
}
function size(): int function size(): int
{ {
@ -131,7 +110,7 @@ abstract class MediaCollection extends RowModel
{ {
return $this->getRecord()->special_type !== 0; return $this->getRecord()->special_type !== 0;
} }
function add(RowModel $entity): bool function add(RowModel $entity): bool
{ {
$this->entitySuitable($entity); $this->entitySuitable($entity);
@ -139,10 +118,6 @@ abstract class MediaCollection extends RowModel
if(!$this->allowDuplicates) if(!$this->allowDuplicates)
if($this->has($entity)) if($this->has($entity))
return false; return false;
if(self::MAX_ITEMS != INF)
if(sizeof($this->relations->where("collection", $this->getId())) > self::MAX_ITEMS)
throw new \OutOfBoundsException("Collection is full");
$this->relations->insert([ $this->relations->insert([
"collection" => $this->getId(), "collection" => $this->getId(),
@ -152,14 +127,14 @@ abstract class MediaCollection extends RowModel
return true; return true;
} }
function remove(RowModel $entity): bool function remove(RowModel $entity): void
{ {
$this->entitySuitable($entity); $this->entitySuitable($entity);
return $this->relations->where([ $this->relations->where([
"collection" => $this->getId(), "collection" => $this->getId(),
"media" => $entity->getId(), "media" => $entity->getId(),
])->delete() > 0; ])->delete();
} }
function has(RowModel $entity): bool function has(RowModel $entity): bool
@ -173,33 +148,6 @@ abstract class MediaCollection extends RowModel
return !is_null($rel); return !is_null($rel);
} }
function save(?bool $log = false): void
{
$thisTable = DatabaseConnection::i()->getContext()->table($this->tableName);
if(self::MAX_COUNT != INF)
if(isset($this->changes["owner"]))
if(sizeof($thisTable->where("owner", $this->changes["owner"])) > self::MAX_COUNT)
throw new \OutOfBoundsException("Maximum amount of collections");
if(is_null($this->getRecord()))
if(!isset($this->changes["created"]))
$this->stateChanges("created", time());
else
$this->stateChanges("edited", time());
parent::save($log);
}
function delete(bool $softly = true): void
{
if(!$softly) {
$this->relations->where("collection", $this->getId())
->delete();
}
parent::delete($softly);
}
use Traits\TOwnable; use Traits\TOwnable;
} }

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"); return $dateTime->format("%T %p");
} else { } else {
return $dateTime->format("%d.%m.%y"); return $dateTime->format("%d.%m.%y");
} }
@ -123,11 +123,7 @@ class Message extends RowModel
], ],
]; ];
} else { } else {
$attachments[] = [ throw new \Exception("Unknown attachment type: " . get_class($attachment));
"type" => "unknown"
];
# throw new \Exception("Unknown attachment type: " . get_class($attachment));
} }
} }

View file

@ -118,29 +118,20 @@ class Note extends Postable
{ {
return $this->getRecord()->source; return $this->getRecord()->source;
} }
function canBeViewedBy(?User $user = NULL): bool
{
if($this->isDeleted() || $this->getOwner()->isDeleted()) {
return false;
}
return $this->getOwner()->getPrivacyPermission('notes.read', $user) && $this->getOwner()->canBeViewedBy($user);
}
function toVkApiStruct(): object function toVkApiStruct(): object
{ {
$res = (object) []; $res = (object) [];
$res->type = "note"; $res->type = "note";
$res->id = $this->getVirtualId(); $res->id = $this->getId();
$res->owner_id = $this->getOwner()->getId(); $res->owner_id = $this->getOwner()->getId();
$res->title = $this->getName(); $res->title = $this->getName();
$res->text = $this->getText(); $res->text = $this->getText();
$res->date = $this->getPublicationTime()->timestamp(); $res->date = $this->getPublicationTime()->timestamp();
$res->comments = $this->getCommentsCount(); $res->comments = $this->getCommentsCount();
$res->read_comments = $this->getCommentsCount(); $res->read_comments = $this->getCommentsCount();
$res->view_url = "/note".$this->getOwner()->getId()."_".$this->getVirtualId(); $res->view_url = "/note".$this->getOwner()->getId()."_".$this->getId();
$res->privacy_view = 1; $res->privacy_view = 1;
$res->can_comment = 1; $res->can_comment = 1;
$res->text_wiki = "r"; $res->text_wiki = "r";

View file

@ -1,13 +0,0 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Notifications;
use openvk\Web\Models\Entities\{User, Club};
final class NewSuggestedPostsNotification extends Notification
{
protected $actionCode = 7;
function __construct(User $owner, Club $group)
{
parent::__construct($owner, $owner, $group, time(), "");
}
}

View file

@ -132,138 +132,4 @@ QUERY;
return true; return true;
} }
function getVkApiInfo()
{
$origin_m = $this->encodeType($this->originModel);
$target_m = $this->encodeType($this->targetModel);
$info = [
"type" => "",
"parent" => NULL,
"feedback" => NULL,
];
switch($this->getActionCode()) {
case 0:
$info["type"] = "like_post";
$info["parent"] = $this->getModel(0)->toNotifApiStruct();
$info["feedback"] = $this->getModel(1)->toVkApiStruct();
break;
case 1:
$info["type"] = "copy_post";
$info["parent"] = $this->getModel(0)->toNotifApiStruct();
$info["feedback"] = NULL; # todo
break;
case 2:
switch($origin_m) {
case 19:
$info["type"] = "comment_video";
$info["parent"] = $this->getModel(0)->toNotifApiStruct();
$info["feedback"] = NULL; # айди коммента не сохраняется в бд( ну пиздец блять
break;
case 13:
$info["type"] = "comment_photo";
$info["parent"] = $this->getModel(0)->toNotifApiStruct();
$info["feedback"] = NULL;
break;
# unstandart (vk forgor about notes)
case 10:
$info["type"] = "comment_note";
$info["parent"] = $this->getModel(0)->toVkApiStruct();
$info["feedback"] = NULL;
break;
case 14:
$info["type"] = "comment_post";
$info["parent"] = $this->getModel(0)->toNotifApiStruct();
$info["feedback"] = NULL;
break;
# unused (users don't have topics bruh)
case 21:
$info["type"] = "comment_topic";
$info["parent"] = $this->getModel(0)->toVkApiStruct(0, 90);
break;
default:
$info["type"] = "comment_unknown";
break;
}
break;
case 3:
$info["type"] = "wall";
$info["feedback"] = $this->getModel(0)->toNotifApiStruct();
break;
case 4:
switch($target_m) {
case 14:
$info["type"] = "mention";
$info["feedback"] = $this->getModel(1)->toNotifApiStruct();
break;
case 19:
$info["type"] = "mention_comment_video";
$info["parent"] = $this->getModel(1)->toNotifApiStruct();
break;
case 13:
$info["type"] = "mention_comment_photo";
$info["parent"] = $this->getModel(1)->toNotifApiStruct();
break;
# unstandart
case 10:
$info["type"] = "mention_comment_note";
$info["parent"] = $this->getModel(1)->toVkApiStruct();
break;
case 21:
$info["type"] = "mention_comments";
break;
default:
$info["type"] = "mention_comment_unknown";
break;
}
break;
case 5:
$info["type"] = "make_you_admin";
$info["parent"] = $this->getModel(0)->toVkApiStruct($this->getModel(1));
break;
# Нужно доделать после мержа #935
case 6:
$info["type"] = "wall_publish";
break;
# В вк не было такого уведомления, так что unstandart
case 7:
$info["type"] = "new_posts_in_club";
break;
# В вк при передаче подарков приходит сообщение, а не уведомление, так что unstandart
case 9601:
$info["type"] = "sent_gift";
$info["parent"] = $this->getModel(1)->toVkApiStruct($this->getModel(1));
break;
case 9602:
$info["type"] = "voices_transfer";
$info["parent"] = $this->getModel(1)->toVkApiStruct($this->getModel(1));
break;
case 9603:
$info["type"] = "up_rating";
$info["parent"] = $this->getModel(1)->toVkApiStruct($this->getModel(1));
$info["parent"]->count = $this->getData();
break;
default:
$info["type"] = NULL;
break;
}
return $info;
}
function toVkApiStruct()
{
$res = (object)[];
$info = $this->getVkApiInfo();
$res->type = $info["type"];
$res->date = $this->getDateTime()->timestamp();
$res->parent = $info["parent"];
$res->feedback = $info["feedback"];
$res->reply = NULL; # Ответы на комментарии не реализованы
return $res;
}
} }

View file

@ -1,13 +0,0 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Notifications;
use openvk\Web\Models\Entities\{User, Club, Post};
final class PostAcceptedNotification extends Notification
{
protected $actionCode = 6;
function __construct(User $author, Post $post, Club $group)
{
parent::__construct($author, $post, $group, time(), "");
}
}

View file

@ -54,11 +54,11 @@ class PasswordReset extends RowModel
} }
} }
function save(?bool $log = false): void function save(): void
{ {
$this->stateChanges("key", base64_encode(openssl_random_pseudo_bytes(46))); $this->stateChanges("key", base64_encode(openssl_random_pseudo_bytes(46)));
$this->stateChanges("timestamp", time()); $this->stateChanges("timestamp", time());
parent::save($log); parent::save();
} }
} }

View file

@ -114,7 +114,7 @@ class Photo extends Media
return true; return true;
} }
function crop(float $left, float $top, float $width, float $height): void function crop(real $left, real $top, real $width, real $height): void
{ {
if(isset($this->changes["hash"])) if(isset($this->changes["hash"]))
$hash = $this->changes["hash"]; $hash = $this->changes["hash"];
@ -308,7 +308,7 @@ class Photo extends Media
$res->date = $res->created = $this->getPublicationTime()->timestamp(); $res->date = $res->created = $this->getPublicationTime()->timestamp();
if($photo_sizes) { if($photo_sizes) {
$res->sizes = array_values($this->getVkApiSizes()); $res->sizes = $this->getVkApiSizes();
$res->src_small = $res->photo_75 = $this->getURLBySizeId("miniscule"); $res->src_small = $res->photo_75 = $this->getURLBySizeId("miniscule");
$res->src = $res->photo_130 = $this->getURLBySizeId("tiny"); $res->src = $res->photo_130 = $this->getURLBySizeId("tiny");
$res->src_big = $res->photo_604 = $this->getURLBySizeId("normal"); $res->src_big = $res->photo_604 = $this->getURLBySizeId("normal");
@ -328,19 +328,6 @@ class Photo extends Media
return $res; return $res;
} }
function canBeViewedBy(?User $user = NULL): bool
{
if($this->isDeleted() || $this->getOwner()->isDeleted()) {
return false;
}
if(!is_null($this->getAlbum())) {
return $this->getAlbum()->canBeViewedBy($user);
} else {
return $this->getOwner()->canBeViewedBy($user);
}
}
static function fastMake(int $owner, string $description = "", array $file, ?Album $album = NULL, bool $anon = false): Photo static function fastMake(int $owner, string $description = "", array $file, ?Album $album = NULL, bool $anon = false): Photo
{ {
@ -360,20 +347,4 @@ class Photo extends Media
return $photo; return $photo;
} }
function toNotifApiStruct()
{
$res = (object)[];
$res->id = $this->getVirtualId();
$res->owner_id = $this->getOwner()->getId();
$res->aid = 0;
$res->src = $this->getURLBySizeId("tiny");
$res->src_big = $this->getURLBySizeId("normal");
$res->src_small = $this->getURLBySizeId("miniscule");
$res->text = $this->getDescription();
$res->created = $this->getPublicationTime()->timestamp();
return $res;
}
} }

View file

@ -1,282 +0,0 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use Chandler\Database\DatabaseConnection;
use Nette\Database\Table\ActiveRow;
use openvk\Web\Models\Repositories\Audios;
use openvk\Web\Models\Repositories\Photos;
use openvk\Web\Models\RowModel;
use openvk\Web\Models\Entities\Photo;
/**
* @method setName(string $name)
* @method setDescription(?string $desc)
*/
class Playlist extends MediaCollection
{
protected $tableName = "playlists";
protected $relTableName = "playlist_relations";
protected $entityTableName = "audios";
protected $entityClassName = 'openvk\Web\Models\Entities\Audio';
protected $allowDuplicates = false;
private $importTable;
const MAX_COUNT = 1000;
const MAX_ITEMS = 10000;
function __construct(?ActiveRow $ar = NULL)
{
parent::__construct($ar);
$this->importTable = DatabaseConnection::i()->getContext()->table("playlist_imports");
}
function getCoverURL(string $size = "normal"): ?string
{
$photo = (new Photos)->get((int) $this->getRecord()->cover_photo_id);
return is_null($photo) ? "/assets/packages/static/openvk/img/song.jpg" : $photo->getURLBySizeId($size);
}
function getLength(): int
{
return $this->getRecord()->length;
}
function fetchClassic(int $offset = 0, ?int $limit = NULL): \Traversable
{
$related = $this->getRecord()->related("$this->relTableName.collection")
->limit($limit ?? OPENVK_DEFAULT_PER_PAGE, $offset)
->order("index ASC");
foreach($related as $rel) {
$media = $rel->ref($this->entityTableName, "media");
if(!$media)
continue;
yield new $this->entityClassName($media);
}
}
function getAudios(int $offset = 0, ?int $limit = NULL, ?int $shuffleSeed = NULL): \Traversable
{
if(!$shuffleSeed) {
foreach ($this->fetchClassic($offset, $limit) as $e)
yield $e; # No, I can't return, it will break with []
return;
}
$ids = [];
foreach($this->relations->select("media AS i")->where("collection", $this->getId()) as $rel)
$ids[] = $rel->i;
$ids = knuth_shuffle($ids, $shuffleSeed);
$ids = array_slice($ids, $offset, $limit ?? OPENVK_DEFAULT_PER_PAGE);
foreach($ids as $id)
yield (new Audios)->get($id);
}
function add(RowModel $audio): bool
{
if($res = parent::add($audio)) {
$this->stateChanges("length", $this->getRecord()->length + $audio->getLength());
$this->save();
}
return $res;
}
function remove(RowModel $audio): bool
{
if($res = parent::remove($audio)) {
$this->stateChanges("length", $this->getRecord()->length - $audio->getLength());
$this->save();
}
return $res;
}
function isBookmarkedBy(RowModel $entity): bool
{
$id = $entity->getId();
if($entity instanceof Club)
$id *= -1;
return !is_null($this->importTable->where([
"entity" => $id,
"playlist" => $this->getId(),
])->fetch());
}
function bookmark(RowModel $entity): bool
{
if($this->isBookmarkedBy($entity))
return false;
$id = $entity->getId();
if($entity instanceof Club)
$id *= -1;
if($this->importTable->where("entity", $id)->count() > self::MAX_COUNT)
throw new \OutOfBoundsException("Maximum amount of playlists");
$this->importTable->insert([
"entity" => $id,
"playlist" => $this->getId(),
]);
return true;
}
function unbookmark(RowModel $entity): bool
{
$id = $entity->getId();
if($entity instanceof Club)
$id *= -1;
$count = $this->importTable->where([
"entity" => $id,
"playlist" => $this->getId(),
])->delete();
return $count > 0;
}
function getDescription(): ?string
{
return $this->getRecord()->description;
}
function getDescriptionHTML(): ?string
{
return htmlspecialchars($this->getRecord()->description, ENT_DISALLOWED | ENT_XHTML);
}
function getListens()
{
return $this->getRecord()->listens;
}
function toVkApiStruct(?User $user = NULL): object
{
$oid = $this->getOwner()->getId();
if($this->getOwner() instanceof Club)
$oid *= -1;
return (object) [
"id" => $this->getId(),
"owner_id" => $oid,
"title" => $this->getName(),
"description" => $this->getDescription(),
"size" => $this->size(),
"length" => $this->getLength(),
"created" => $this->getCreationTime()->timestamp(),
"modified" => $this->getEditTime() ? $this->getEditTime()->timestamp() : NULL,
"accessible" => $this->canBeViewedBy($user),
"editable" => $this->canBeModifiedBy($user),
"bookmarked" => $this->isBookmarkedBy($user),
"listens" => $this->getListens(),
"cover_url" => $this->getCoverURL(),
"searchable" => !$this->isUnlisted(),
];
}
function setLength(): void
{
throw new \LogicException("Can't set length of playlist manually");
}
function resetLength(): bool
{
$this->stateChanges("length", 0);
return true;
}
function delete(bool $softly = true): void
{
$ctx = DatabaseConnection::i()->getContext();
$ctx->table("playlist_imports")->where("playlist", $this->getId())
->delete();
parent::delete($softly);
}
function hasAudio(Audio $audio): bool
{
$ctx = DatabaseConnection::i()->getContext();
return !is_null($ctx->table("playlist_relations")->where([
"collection" => $this->getId(),
"media" => $audio->getId()
])->fetch());
}
function getCoverPhotoId(): ?int
{
return $this->getRecord()->cover_photo_id;
}
function getCoverPhoto(): ?Photo
{
return (new Photos)->get((int) $this->getRecord()->cover_photo_id);
}
function canBeModifiedBy(User $user): bool
{
if(!$user)
return false;
if($this->getOwner() instanceof User)
return $user->getId() == $this->getOwner()->getId();
else
return $this->getOwner()->canBeModifiedBy($user);
}
function getLengthInMinutes(): int
{
return (int)round($this->getLength() / 60, PHP_ROUND_HALF_DOWN);
}
function fastMakeCover(int $owner, array $file)
{
$cover = new Photo;
$cover->setOwner($owner);
$cover->setDescription("Playlist cover image");
$cover->setFile($file);
$cover->setCreated(time());
$cover->save();
$this->setCover_photo_id($cover->getId());
return $cover;
}
function getURL(): string
{
return "/playlist" . $this->getOwner()->getRealId() . "_" . $this->getId();
}
function incrementListens()
{
$this->stateChanges("listens", ($this->getListens() + 1));
}
function getMetaDescription(): string
{
$length = $this->getLengthInMinutes();
$props = [];
$props[] = tr("audios_count", $this->size());
$props[] = "<span id='listensCount'>" . tr("listens_count", $this->getListens()) . "</span>";
if($length > 0) $props[] = tr("minutes_count", $length);
$props[] = tr("created_playlist") . " " . $this->getPublicationTime();
# if($this->getEditTime()) $props[] = tr("updated_playlist") . " " . $this->getEditTime();
return implode("", $props);
}
function isUnlisted(): bool
{
return (bool)$this->getRecord()->unlisted;
}
}

View file

@ -4,7 +4,7 @@ use openvk\Web\Models\Exceptions\TooMuchOptionsException;
use openvk\Web\Util\DateTime; use openvk\Web\Util\DateTime;
use \UnexpectedValueException; use \UnexpectedValueException;
use Nette\InvalidStateException; use Nette\InvalidStateException;
use openvk\Web\Models\Repositories\{Users, Posts}; use openvk\Web\Models\Repositories\Users;
use Chandler\Database\DatabaseConnection; use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Exceptions\PollLockedException; use openvk\Web\Models\Exceptions\PollLockedException;
use openvk\Web\Models\Exceptions\AlreadyVotedException; use openvk\Web\Models\Exceptions\AlreadyVotedException;
@ -165,7 +165,7 @@ class Poll extends Attachable
function canVote(User $user): bool function canVote(User $user): bool
{ {
return !$this->hasEnded() && !$this->hasVoted($user) && !is_null($this->getAttachedPost()) && $this->getAttachedPost()->getSuggestionType() == 0; return !$this->hasEnded() && !$this->hasVoted($user);
} }
function vote(User $user, array $optionIds): void function vote(User $user, array $optionIds): void
@ -278,24 +278,13 @@ class Poll extends Attachable
return $poll; return $poll;
} }
function canBeViewedBy(?User $user = NULL): bool
{
# waiting for #935 :(
/*if(!is_null($this->getAttachedPost())) {
return $this->getAttachedPost()->canBeViewedBy($user);
} else {*/
return true;
#}
}
function save(?bool $log = false): void function save(): void
{ {
if(empty($this->choicesToPersist)) if(empty($this->choicesToPersist))
throw new InvalidStateException; throw new InvalidStateException;
parent::save($log); parent::save();
foreach($this->choicesToPersist as $option) { foreach($this->choicesToPersist as $option) {
DatabaseConnection::i()->getContext()->table("poll_options")->insert([ DatabaseConnection::i()->getContext()->table("poll_options")->insert([
"poll" => $this->getId(), "poll" => $this->getId(),
@ -303,17 +292,4 @@ class Poll extends Attachable
]); ]);
} }
} }
function getAttachedPost()
{
$post = DatabaseConnection::i()->getContext()->table("attachments")
->where(
["attachable_type" => static::class,
"attachable_id" => $this->getId()])->fetch();
if(!is_null($post->target_id))
return (new Posts)->get($post->target_id);
else
return NULL;
}
} }

View file

@ -78,45 +78,6 @@ class Post extends Postable
{ {
return (bool) $this->getRecord()->pinned; return (bool) $this->getRecord()->pinned;
} }
function hasSource(): bool
{
return $this->getRecord()->source != NULL;
}
function getSource(bool $format = false)
{
$orig_source = $this->getRecord()->source;
if(!str_contains($orig_source, "https://") && !str_contains($orig_source, "http://"))
$orig_source = "https://" . $orig_source;
if(!$format)
return $orig_source;
return $this->formatLinks($orig_source);
}
function setSource(string $source)
{
$result = check_copyright_link($source);
$this->stateChanges("source", $source);
}
function resetSource()
{
$this->stateChanges("source", NULL);
}
function getVkApiCopyright(): object
{
return (object)[
'id' => 0,
'link' => $this->getSource(false),
'name' => $this->getSource(false),
'type' => 'link',
];
}
function isAd(): bool function isAd(): bool
{ {
@ -172,10 +133,6 @@ 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':
@ -218,31 +175,6 @@ 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
{ {
@ -265,25 +197,16 @@ class Post extends Postable
$this->save(); $this->save();
} }
function canBePinnedBy(User $user = NULL): bool function canBePinnedBy(User $user): bool
{ {
if(!$user)
return false;
if($this->getTargetWall() < 0) if($this->getTargetWall() < 0)
return (new Clubs)->get(abs($this->getTargetWall()))->canBeModifiedBy($user); return (new Clubs)->get(abs($this->getTargetWall()))->canBeModifiedBy($user);
return $this->getTargetWall() === $user->getId(); return $this->getTargetWall() === $user->getId();
} }
function canBeDeletedBy(User $user = NULL): bool function canBeDeletedBy(User $user): bool
{ {
if(!$user)
return false;
if($this->getTargetWall() < 0 && !$this->getWallOwner()->canBeModifiedBy($user) && $this->getWallOwner()->getWallType() != 1 && $this->getSuggestionType() == 0)
return false;
return $this->getOwnerPost() === $user->getId() || $this->canBePinnedBy($user); return $this->getOwnerPost() === $user->getId() || $this->canBePinnedBy($user);
} }
@ -301,7 +224,7 @@ class Post extends Postable
{ {
$liked = parent::toggleLike($user); $liked = parent::toggleLike($user);
if(!$user->isPrivateLikes() && $this->getOwner(false)->getId() !== $user->getId() && !($this->getOwner() instanceof Club) && !$this instanceof Comment) if($this->getOwner(false)->getId() !== $user->getId() && !($this->getOwner() instanceof Club) && !$this instanceof Comment)
(new LikeNotification($this->getOwner(false), $this, $user))->emit(); (new LikeNotification($this->getOwner(false), $this, $user))->emit();
foreach($this->getChildren() as $attachment) foreach($this->getChildren() as $attachment)
@ -322,139 +245,6 @@ class Post extends Postable
$this->unwire(); $this->unwire();
$this->save(); $this->save();
} }
function canBeViewedBy(?User $user = NULL): bool
{
if($this->isDeleted()) {
return false;
}
return $this->getWallOwner()->canBeViewedBy($user);
}
function getSuggestionType()
{
return $this->getRecord()->suggested;
}
function getPageURL(): string
{
return "/wall".$this->getPrettyId();
}
function toNotifApiStruct()
{
$res = (object)[];
$res->id = $this->getVirtualId();
$res->to_id = $this->getOwner() instanceof Club ? $this->getOwner()->getId() * -1 : $this->getOwner()->getId();
$res->from_id = $res->to_id;
$res->date = $this->getPublicationTime()->timestamp();
$res->text = $this->getText(false);
$res->attachments = []; # todo
$res->copy_owner_id = NULL; # todo
$res->copy_post_id = NULL; # todo
return $res;
}
function canBeEditedBy(?User $user = NULL): bool
{
if(!$user)
return false;
if($this->isDeactivationMessage() || $this->isUpdateAvatarMessage())
return false;
if($this->getTargetWall() > 0)
return $this->getPublicationTime()->timestamp() + WEEK > time() && $user->getId() == $this->getOwner(false)->getId();
else {
if($this->isPostedOnBehalfOfGroup())
return $this->getWallOwner()->canBeModifiedBy($user);
else
return $user->getId() == $this->getOwner(false)->getId();
}
return $user->getId() == $this->getOwner(false)->getId();
}
function toRss(): \Bhaktaraz\RSSGenerator\Item
{
$domain = ovk_scheme(true).$_SERVER["HTTP_HOST"];
$description = $this->getText(false);
$title = str_replace("\n", "", ovk_proc_strtr($description, 79));
$description_html = $description;
$url = $domain."/wall".$this->getPrettyId();
if($this->isUpdateAvatarMessage())
$title = tr('upd_in_general');
if($this->isDeactivationMessage())
$title = tr('post_deact_in_general');
$author = $this->getOwner();
$target_wall = $this->getWallOwner();
$author_name = escape_html($author->getCanonicalName());
if($this->isExplicit())
$title = 'NSFW: ' . $title;
foreach($this->getChildren() as $child) {
if($child instanceof Photo) {
$child_page = $domain.$child->getPageURL();
$child_url = $child->getURL();
$description_html .= "<br /><a href='$child_page'><img src='$child_url'></a><br />";
} elseif($child instanceof Video) {
$child_page = $domain.'/video'.$child->getPrettyId();
if($child->getType() != 1) {
$description_html .= "".
"<br />".
"<video width=\"320\" height=\"240\" controls><source src=\"".$child->getURL()."\" type=\"video/mp4\"></video><br />".
"<b>".escape_html($child->getName())."</b><br />";
} else {
$description_html .= "".
"<br />".
"<a href=\"".$child->getVideoDriver()->getURL()."\"><b>".escape_html($child->getName())."</b></a><br />";
}
} elseif($child instanceof Audio) {
if(!$child->isWithdrawn()) {
$description_html .= "<br />"
."<b>".escape_html($child->getName())."</b>:"
."<br />"
."<audio controls>"
."<source src=\"".$child->getOriginalURL()."\" type=\"audio/mpeg\"></audio>"
."<br />";
}
} elseif($child instanceof Poll) {
$description_html .= "<br />".tr('poll').": ".escape_html($child->getTitle());
} elseif($child instanceof Note) {
$description_html .= "<br />".tr('note').": ".escape_html($child->getName());
}
}
$description_html .= "<br />".tr('author').": <img width='15px' src='".$author->getAvatarURL()."'><a href='".$author->getURL()."'>" . $author_name . "</a>";
if($target_wall->getRealId() != $author->getRealId())
$description_html .= "<br />".tr('on_wall').": <img width='15px' src='".$target_wall->getAvatarURL()."'><a href='".$target_wall->getURL()."'>" . escape_html($target_wall->getCanonicalName()) . "</a>";
if($this->isSigned()) {
$signer = $this->getOwner(false);
$description_html .= "<br />".tr('sign_short').": <img width='15px' src='".$signer->getAvatarURL()."'><a href='".$signer->getURL()."'>" . escape_html($signer->getCanonicalName()) . "</a>";
}
if($this->hasSource())
$description_html .= "<br />".tr('source').": ".escape_html($this->getSource());
$item = new \Bhaktaraz\RSSGenerator\Item();
$item->title($title)
->url($url)
->guid($url)
->creator($author_name)
->pubDate($this->getPublicationTime()->timestamp())
->content(str_replace("\n", "<br />", $description_html));
return $item;
}
use Traits\TRichText; use Traits\TRichText;
} }

View file

@ -33,7 +33,7 @@ abstract class Postable extends Attachable
{ {
$oid = (int) $this->getRecord()->owner; $oid = (int) $this->getRecord()->owner;
if(!$real && $this->isAnonymous()) if(!$real && $this->isAnonymous())
$oid = (int) OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["account"]; $oid = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["account"];
$oid = abs($oid); $oid = abs($oid);
if($oid > 0) if($oid > 0)
@ -88,23 +88,16 @@ abstract class Postable extends Attachable
])->group("origin")); ])->group("origin"));
} }
function getLikers(int $page = 1, ?int $perPage = NULL): \Traversable # TODO add pagination
function getLikers(): \Traversable
{ {
$perPage ??= OPENVK_DEFAULT_PER_PAGE;
$sel = DB::i()->getContext()->table("likes")->where([ $sel = DB::i()->getContext()->table("likes")->where([
"model" => static::class, "model" => static::class,
"target" => $this->getRecord()->id, "target" => $this->getRecord()->id,
])->page($page, $perPage); ]);
foreach($sel as $like) { foreach($sel as $like)
$user = (new Users)->get($like->origin); yield (new Users)->get($like->origin);
if($user->isPrivateLikes() && OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"]) {
$user = (new Users)->get((int) OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["account"]);
}
yield $user;
}
} }
function isAnonymous(): bool function isAnonymous(): bool
@ -137,15 +130,10 @@ abstract class Postable extends Attachable
"target" => $this->getRecord()->id, "target" => $this->getRecord()->id,
]; ];
if($liked) { if($liked)
if(!$this->hasLikeFrom($user)) { DB::i()->getContext()->table("likes")->insert($searchData);
DB::i()->getContext()->table("likes")->insert($searchData); else
} DB::i()->getContext()->table("likes")->where($searchData)->delete();
} else {
if($this->hasLikeFrom($user)) {
DB::i()->getContext()->table("likes")->where($searchData)->delete();
}
}
} }
function hasLikeFrom(User $user): bool function hasLikeFrom(User $user): bool
@ -164,7 +152,7 @@ abstract class Postable extends Attachable
throw new ISE("Setting virtual id manually is forbidden"); throw new ISE("Setting virtual id manually is forbidden");
} }
function save(?bool $log = false): void function save(): void
{ {
$vref = $this->upperNodeReferenceColumnName; $vref = $this->upperNodeReferenceColumnName;
@ -179,11 +167,11 @@ abstract class Postable extends Attachable
$this->stateChanges("created", time()); $this->stateChanges("created", time());
$this->stateChanges("virtual_id", $pCount + 1); $this->stateChanges("virtual_id", $pCount + 1);
} /*else { } else {
$this->stateChanges("edited", time()); $this->stateChanges("edited", time());
}*/ }
parent::save($log); parent::save();
} }
use Traits\TAttachmentHost; use Traits\TAttachmentHost;

View file

@ -3,9 +3,8 @@ namespace openvk\Web\Models\Entities;
use openvk\Web\Util\DateTime; use openvk\Web\Util\DateTime;
use Nette\Database\Table\ActiveRow; use Nette\Database\Table\ActiveRow;
use openvk\Web\Models\RowModel; use openvk\Web\Models\RowModel;
use openvk\Web\Models\Entities\Club;
use Chandler\Database\DatabaseConnection; use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Repositories\{Applications, Comments, Notes, Reports, Audios, Users, Posts, Photos, Videos, Clubs}; use openvk\Web\Models\Repositories\{Applications, Comments, Notes, Reports, Users, Posts, Photos, Videos, Clubs};
use Chandler\Database\DatabaseConnection as DB; use Chandler\Database\DatabaseConnection as DB;
use Nette\InvalidStateException as ISE; use Nette\InvalidStateException as ISE;
use Nette\Database\Table\Selection; use Nette\Database\Table\Selection;
@ -74,13 +73,12 @@ class Report extends RowModel
else if ($this->getContentType() == "note") return (new Notes)->get($this->getContentId()); else if ($this->getContentType() == "note") return (new Notes)->get($this->getContentId());
else if ($this->getContentType() == "app") return (new Applications)->get($this->getContentId()); else if ($this->getContentType() == "app") return (new Applications)->get($this->getContentId());
else if ($this->getContentType() == "user") return (new Users)->get($this->getContentId()); else if ($this->getContentType() == "user") return (new Users)->get($this->getContentId());
else if ($this->getContentType() == "audio") return (new Audios)->get($this->getContentId());
else return null; else return null;
} }
function getAuthor(): RowModel function getAuthor(): RowModel
{ {
return $this->getContentObject()->getOwner(); return (new Posts)->get($this->getContentId())->getOwner();
} }
function getReportAuthor(): User function getReportAuthor(): User
@ -98,19 +96,8 @@ class Report extends RowModel
{ {
if ($this->getContentType() !== "user") { if ($this->getContentType() !== "user") {
$pubTime = $this->getContentObject()->getPublicationTime(); $pubTime = $this->getContentObject()->getPublicationTime();
if (method_exists($this->getContentObject(), "getName")) { $name = $this->getContentObject()->getName();
$name = $this->getContentObject()->getName(); $this->getAuthor()->adminNotify("Ваш контент, который вы опубликовали $pubTime ($name) был удалён модераторами инстанса. За повторные или серьёзные нарушения вас могут заблокировать.");
$placeholder = "$pubTime ($name)";
} else {
$placeholder = "$pubTime";
}
if ($this->getAuthor() instanceof Club) {
$name = $this->getAuthor()->getName();
$this->getAuthor()->getOwner()->adminNotify("Ваш контент, который опубликовали $placeholder в созданной вами группе \"$name\" был удалён модераторами инстанса. За повторные или серьёзные нарушения группу могут заблокировать.");
} else {
$this->getAuthor()->adminNotify("Ваш контент, который вы опубликовали $placeholder был удалён модераторами инстанса. За повторные или серьёзные нарушения вас могут заблокировать.");
}
$this->getContentObject()->delete($this->getContentType() !== "app"); $this->getContentObject()->delete($this->getContentType() !== "app");
} }
@ -134,13 +121,8 @@ class Report extends RowModel
function getContentName(): string function getContentName(): string
{ {
$content_object = $this->getContentObject(); if (method_exists($this->getContentObject(), "getCanonicalName"))
if(!$content_object) { return $this->getContentObject()->getCanonicalName();
return 'unknown';
}
if (method_exists($content_object, "getCanonicalName"))
return $content_object->getCanonicalName();
return $this->getContentType() . " #" . $this->getContentId(); return $this->getContentType() . " #" . $this->getContentId();
} }

View file

@ -1,7 +1,6 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Traits; namespace openvk\Web\Models\Entities\Traits;
use openvk\Web\Models\Entities\{Attachable, Photo, Video}; use openvk\Web\Models\Entities\Attachable;
use openvk\Web\Util\Makima\Makima;
use Chandler\Database\DatabaseConnection; use Chandler\Database\DatabaseConnection;
trait TAttachmentHost trait TAttachmentHost
@ -30,46 +29,6 @@ trait TAttachmentHost
yield $repo->get($rel->attachable_id); yield $repo->get($rel->attachable_id);
} }
} }
function getChildrenWithLayout(int $w, int $h = -1): object
{
if($h < 0)
$h = $w;
$children = iterator_to_array($this->getChildren());
$skipped = $photos = $result = [];
foreach($children as $child) {
if($child instanceof Photo || $child instanceof Video && $child->getDimensions()) {
$photos[] = $child;
continue;
}
$skipped[] = $child;
}
$height = "unset";
$width = $w;
if(sizeof($photos) < 2) {
if(isset($photos[0]))
$result[] = ["100%", "unset", $photos[0], "unset"];
} else {
$mak = new Makima($photos);
$layout = $mak->computeMasonryLayout($w, $h);
$height = $layout->height;
$width = $layout->width;
for($i = 0; $i < sizeof($photos); $i++) {
$tile = $layout->tiles[$i];
$result[] = [$tile->width . "px", $tile->height . "px", $photos[$i], "left"];
}
}
return (object) [
"width" => $width . "px",
"height" => $height . "px",
"tiles" => $result,
"extras" => $skipped,
];
}
function attach(Attachable $attachment): void function attach(Attachable $attachment): void
{ {

View file

@ -1,38 +0,0 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Traits;
use openvk\Web\Models\Repositories\Audios;
use Chandler\Database\DatabaseConnection;
trait TAudioStatuses
{
function isBroadcastEnabled(): bool
{
if($this->getRealId() < 0) return true;
return (bool) $this->getRecord()->audio_broadcast_enabled;
}
function getCurrentAudioStatus()
{
if(!$this->isBroadcastEnabled()) return NULL;
$audioId = $this->getRecord()->last_played_track;
if(!$audioId) return NULL;
$audio = (new Audios)->get($audioId);
if(!$audio || $audio->isDeleted())
return NULL;
$listensTable = DatabaseConnection::i()->getContext()->table("audio_listens");
$lastListen = $listensTable->where([
"entity" => $this->getRealId(),
"audio" => $audio->getId(),
"time >" => (time() - $audio->getLength()) - 10,
])->fetch();
if($lastListen)
return $audio;
return NULL;
}
}

View file

@ -1,55 +0,0 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Traits;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Entities\User;
trait TIgnorable
{
function isIgnoredBy(User $user = NULL): bool
{
if(!$user)
return false;
$ctx = DatabaseConnection::i()->getContext();
$data = [
"owner" => $user->getId(),
"source" => $this->getRealId(),
];
$sub = $ctx->table("ignored_sources")->where($data);
return $sub->count() > 0;
}
function addIgnore(User $for_user): bool
{
DatabaseConnection::i()->getContext()->table("ignored_sources")->insert([
"owner" => $for_user->getId(),
"source" => $this->getRealId(),
]);
return true;
}
function removeIgnore(User $for_user): bool
{
DatabaseConnection::i()->getContext()->table("ignored_sources")->where([
"owner" => $for_user->getId(),
"source" => $this->getRealId(),
])->delete();
return true;
}
function toggleIgnore(User $for_user): bool
{
if($this->isIgnoredBy($for_user)) {
$this->removeIgnore($for_user);
return false;
} else {
$this->addIgnore($for_user);
return true;
}
}
}

View file

@ -4,16 +4,6 @@ use openvk\Web\Models\Entities\User;
trait TOwnable trait TOwnable
{ {
function canBeViewedBy(?User $user = NULL): bool
{
# TODO: #950
if($this->isDeleted()) {
return false;
}
return true;
}
function canBeModifiedBy(User $user): bool function canBeModifiedBy(User $user): bool
{ {
if(method_exists($this, "isCreatedBySystem")) if(method_exists($this, "isCreatedBySystem"))

View file

@ -38,19 +38,8 @@ trait TRichText
$href = str_replace("#", "&num;", $matches[1]); $href = str_replace("#", "&num;", $matches[1]);
$href = rawurlencode(str_replace(";", "&#59;", $href)); $href = rawurlencode(str_replace(";", "&#59;", $href));
$link = str_replace("#", "&num;", $matches[3]); $link = str_replace("#", "&num;", $matches[3]);
# this string breaks ampersands
$link = str_replace(";", "&#59;", $link); $link = str_replace(";", "&#59;", $link);
$rel = $this->isAd() ? "sponsored" : "ugc"; $rel = $this->isAd() ? "sponsored" : "ugc";
$server_domain = str_replace(':' . $_SERVER['SERVER_PORT'], '', $_SERVER['HTTP_HOST']);
if(str_contains($link, $server_domain)) {
$replaced_link = str_replace(':' . $_SERVER['SERVER_PORT'], '', $link);
$replaced_link = str_replace($server_domain, '', $replaced_link);
return "<a href='$replaced_link' rel='$rel'>$link</a>" . htmlentities($matches[4]);
}
$link = htmlentities(urldecode($link));
return "<a href='/away.php?to=$href' rel='$rel' target='_blank'>$link</a>" . htmlentities($matches[4]); return "<a href='/away.php?to=$href' rel='$rel' target='_blank'>$link</a>" . htmlentities($matches[4]);
}), }),
@ -134,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='/search?section=posts&q=%23$slug'>$m[2]</a>"; return "$m[1]<a href='/feed/hashtag/$slug'>$m[2]</a>";
}, $text); }, $text);
$text = $this->formatEmojis($text); $text = $this->formatEmojis($text);

View file

@ -39,25 +39,4 @@ 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

@ -4,7 +4,7 @@ use morphos\Gender;
use openvk\Web\Themes\{Themepack, Themepacks}; use openvk\Web\Themes\{Themepack, Themepacks};
use openvk\Web\Util\DateTime; use openvk\Web\Util\DateTime;
use openvk\Web\Models\RowModel; use openvk\Web\Models\RowModel;
use openvk\Web\Models\Entities\{Photo, Message, Correspondence, Gift, Audio}; use openvk\Web\Models\Entities\{Photo, Message, Correspondence, Gift};
use openvk\Web\Models\Repositories\{Applications, Bans, Comments, Notes, Posts, Users, Clubs, Albums, Gifts, Notifications, Videos, Photos}; use openvk\Web\Models\Repositories\{Applications, Bans, Comments, Notes, Posts, Users, Clubs, Albums, Gifts, Notifications, Videos, Photos};
use openvk\Web\Models\Exceptions\InvalidUserNameException; use openvk\Web\Models\Exceptions\InvalidUserNameException;
use Nette\Database\Table\ActiveRow; use Nette\Database\Table\ActiveRow;
@ -112,7 +112,7 @@ class User extends RowModel
return "/id" . $this->getId(); return "/id" . $this->getId();
} }
function getAvatarUrl(string $size = "miniscule", $avPhoto = NULL): string function getAvatarUrl(string $size = "miniscule"): string
{ {
$serverUrl = ovk_scheme(true) . $_SERVER["HTTP_HOST"]; $serverUrl = ovk_scheme(true) . $_SERVER["HTTP_HOST"];
@ -121,9 +121,7 @@ class User extends RowModel
else if($this->isBanned()) else if($this->isBanned())
return "$serverUrl/assets/packages/static/openvk/img/banned.jpg"; return "$serverUrl/assets/packages/static/openvk/img/banned.jpg";
if(!$avPhoto) $avPhoto = $this->getAvatarPhoto();
$avPhoto = $this->getAvatarPhoto();
if(is_null($avPhoto)) if(is_null($avPhoto))
return "$serverUrl/assets/packages/static/openvk/img/camera_200.png"; return "$serverUrl/assets/packages/static/openvk/img/camera_200.png";
else else
@ -192,7 +190,7 @@ class User extends RowModel
function getMorphedName(string $case = "genitive", bool $fullName = true): string function getMorphedName(string $case = "genitive", bool $fullName = true): string
{ {
$name = $fullName ? ($this->getLastName() . " " . $this->getFirstName()) : $this->getFirstName(); $name = $fullName ? ($this->getLastName() . " " . $this->getFirstName()) : $this->getFirstName();
if(!preg_match("%[А-яё\-]+$%", $name)) if(!preg_match("%^[А-яё\-]+$%", $name))
return $name; # name is probably not russian return $name; # name is probably not russian
$inflected = inflectName($name, $case, $this->isFemale() ? Gender::FEMALE : Gender::MALE); $inflected = inflectName($name, $case, $this->isFemale() ? Gender::FEMALE : Gender::MALE);
@ -350,11 +348,10 @@ class User extends RowModel
return $this->getRecord()->marital_status; return $this->getRecord()->marital_status;
} }
function getLocalizedMaritalStatus(?bool $prefix = false): string function getLocalizedMaritalStatus(): string
{ {
$status = $this->getMaritalStatus(); $status = $this->getMaritalStatus();
$string = "relationship_$status"; $string = "relationship_$status";
if ($prefix) $string .= "_prefix";
if($this->isFemale()) { if($this->isFemale()) {
$res = tr($string . "_fem"); $res = tr($string . "_fem");
if($res != ("@" . $string . "_fem")) if($res != ("@" . $string . "_fem"))
@ -364,17 +361,6 @@ class User extends RowModel
return tr($string); return tr($string);
} }
function getMaritalStatusUser(): ?User
{
if (!$this->getRecord()->marital_status_user) return NULL;
return (new Users)->get($this->getRecord()->marital_status_user);
}
function getMaritalStatusUserPrefix(): ?string
{
return $this->getLocalizedMaritalStatus(true);
}
function getContactEmail(): ?string function getContactEmail(): ?string
{ {
return $this->getRecord()->email_contact; return $this->getRecord()->email_contact;
@ -469,7 +455,6 @@ class User extends RowModel
"length" => 1, "length" => 1,
"mappings" => [ "mappings" => [
"photos", "photos",
"audios",
"videos", "videos",
"messages", "messages",
"notes", "notes",
@ -477,7 +462,6 @@ class User extends RowModel
"news", "news",
"links", "links",
"poster", "poster",
"apps",
], ],
])->get($id); ])->get($id);
} }
@ -497,8 +481,6 @@ class User extends RowModel
"friends.add", "friends.add",
"wall.write", "wall.write",
"messages.write", "messages.write",
"audios.read",
"likes.read",
], ],
])->get($id); ])->get($id);
} }
@ -511,9 +493,6 @@ class User extends RowModel
else if($user->getId() === $this->getId()) else if($user->getId() === $this->getId())
return true; return true;
if($permission != "messages.write" && !$this->canBeViewedBy($user))
return false;
switch($permStatus) { switch($permStatus) {
case User::PRIVACY_ONLY_FRIENDS: case User::PRIVACY_ONLY_FRIENDS:
return $this->getSubscriptionStatus($user) === User::SUBSCRIPTION_MUTUAL; return $this->getSubscriptionStatus($user) === User::SUBSCRIPTION_MUTUAL;
@ -595,16 +574,6 @@ 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);
@ -750,8 +719,8 @@ class User extends RowModel
for($i = 0; $i < 10 - $this->get2faBackupCodeCount(); $i++) { for($i = 0; $i < 10 - $this->get2faBackupCodeCount(); $i++) {
$codes[] = [ $codes[] = [
"owner" => $this->getId(), owner => $this->getId(),
"code" => random_int(10000000, 99999999) code => random_int(10000000, 99999999)
]; ];
} }
@ -811,29 +780,7 @@ class User extends RowModel
function isFemale(): bool function isFemale(): bool
{ {
return $this->getRecord()->sex == 1; return (bool) $this->getRecord()->sex;
}
function isNeutral(): bool
{
return (bool) $this->getRecord()->sex == 2;
}
function getLocalizedPronouns(): string
{
switch ($this->getRecord()->sex) {
case 0:
return tr('male');
case 1:
return tr('female');
case 2:
return tr('neutral');
}
}
function getPronouns(): int
{
return $this->getRecord()->sex;
} }
function isVerified(): bool function isVerified(): bool
@ -1062,8 +1009,6 @@ class User extends RowModel
"friends.add", "friends.add",
"wall.write", "wall.write",
"messages.write", "messages.write",
"audios.read",
"likes.read",
], ],
])->set($id, $status)->toInteger()); ])->set($id, $status)->toInteger());
} }
@ -1074,7 +1019,6 @@ class User extends RowModel
"length" => 1, "length" => 1,
"mappings" => [ "mappings" => [
"photos", "photos",
"audios",
"videos", "videos",
"messages", "messages",
"notes", "notes",
@ -1082,7 +1026,6 @@ class User extends RowModel
"news", "news",
"links", "links",
"poster", "poster",
"apps",
], ],
])->set($id, (int) $status)->toInteger(); ])->set($id, (int) $status)->toInteger();
@ -1228,11 +1171,6 @@ 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);
@ -1282,70 +1220,8 @@ class User extends RowModel
} }
return $response; return $response;
} }
function getProfileType(): int
{
# 0 — открытый профиль, 1 — закрытый
return $this->getRecord()->profile_type;
}
function canBeViewedBy(?User $user = NULL): bool function toVkApiStruct(): object
{
if(!is_null($user)) {
if($this->getId() == $user->getId()) {
return true;
}
if($user->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)) {
return true;
}
if($this->getProfileType() == 0) {
return true;
} else {
if($user->getSubscriptionStatus($this) == User::SUBSCRIPTION_MUTUAL) {
return true;
} else {
return false;
}
}
} else {
if($this->getProfileType() == 0) {
if($this->getPrivacySetting("page.read") == 3) {
return true;
} else {
return false;
}
} else {
return false;
}
}
return true;
}
function isClosed(): bool
{
return (bool) $this->getProfileType();
}
function isHideFromGlobalFeedEnabled(): bool
{
return $this->isClosed();
}
function getRealId()
{
return $this->getId();
}
function isPrivateLikes(): bool
{
return $this->getPrivacySetting("likes.read") == User::PRIVACY_NO_ONE;
}
function toVkApiStruct(?User $user = NULL, string $fields = ''): object
{ {
$res = (object) []; $res = (object) [];
@ -1353,141 +1229,15 @@ class User extends RowModel
$res->first_name = $this->getFirstName(); $res->first_name = $this->getFirstName();
$res->last_name = $this->getLastName(); $res->last_name = $this->getLastName();
$res->deactivated = $this->isDeactivated(); $res->deactivated = $this->isDeactivated();
$res->is_closed = $this->isClosed(); $res->photo_50 = $this->getAvatarURL();
$res->photo_100 = $this->getAvatarURL("tiny");
if(!is_null($user)) $res->photo_200 = $this->getAvatarURL("normal");
$res->can_access_closed = (bool)$this->canBeViewedBy($user); $res->photo_id = !is_null($this->getAvatarPhoto()) ? $this->getAvatarPhoto()->getPrettyId() : NULL;
# TODO: Perenesti syuda vsyo ostalnoyie
if(!is_array($fields))
$fields = explode(',', $fields);
$avatar_photo = $this->getAvatarPhoto();
foreach($fields as $field) {
switch($field) {
case 'is_dead':
$res->is_dead = $this->isDead();
break;
case 'verified':
$res->verified = (int)$this->isVerified();
break;
case 'sex':
$res->sex = $this->isFemale() ? 1 : ($this->isNeutral() ? 0 : 2);
break;
case 'photo_50':
$res->photo_50 = $this->getAvatarUrl('miniscule', $avatar_photo);
break;
case 'photo_100':
$res->photo_100 = $this->getAvatarUrl('tiny', $avatar_photo);
break;
case 'photo_200':
$res->photo_200 = $this->getAvatarUrl('normal', $avatar_photo);
break;
case 'photo_max':
$res->photo_max = $this->getAvatarUrl('original', $avatar_photo);
break;
case 'photo_id':
$res->photo_id = $avatar_photo ? $avatar_photo->getPrettyId() : NULL;
break;
case 'background':
$res->background = $this->getBackDropPictureURLs();
break;
case 'reg_date':
$res->reg_date = $this->getRegistrationTime()->timestamp();
break;
case 'nickname':
$res->nickname = $this->getPseudo();
break;
case 'rating':
$res->rating = $this->getRating();
break;
case 'status':
$res->status = $this->getStatus();
break;
case 'screen_name':
$res->screen_name = $this->getShortCode() ?? "id".$this->getId();
break;
case 'real_id':
$res->real_id = $this->getRealId();
break;
}
}
return $res; return $res;
} }
function getAudiosCollectionSize()
{
return (new \openvk\Web\Models\Repositories\Audios)->getUserCollectionSize($this);
}
function getBroadcastList(string $filter = "friends", bool $shuffle = false)
{
$dbContext = DatabaseConnection::i()->getContext();
$entityIds = [];
$query = $dbContext->table("subscriptions")->where("follower", $this->getRealId());
if($filter != "all")
$query = $query->where("model = ?", "openvk\\Web\\Models\\Entities\\" . ($filter == "groups" ? "Club" : "User"));
foreach($query as $_rel) {
$entityIds[] = $_rel->model == "openvk\\Web\\Models\\Entities\\Club" ? $_rel->target * -1 : $_rel->target;
}
if($shuffle) {
$shuffleSeed = openssl_random_pseudo_bytes(6);
$shuffleSeed = hexdec(bin2hex($shuffleSeed));
$entityIds = knuth_shuffle($entityIds, $shuffleSeed);
}
$entityIds = array_slice($entityIds, 0, 10);
$returnArr = [];
foreach($entityIds as $id) {
$entit = $id > 0 ? (new Users)->get($id) : (new Clubs)->get(abs($id));
if($id > 0 && $entit->isDeleted()) continue;
$returnArr[] = $entit;
}
return $returnArr;
}
function getIgnoredSources(int $offset = 0, int $limit = 10, bool $onlyIds = false)
{
$sources = DatabaseConnection::i()->getContext()->table("ignored_sources")->where("owner", $this->getId())->limit($limit, $offset)->order('id DESC');
$output_array = [];
foreach($sources as $source) {
if($onlyIds) {
$output_array[] = (int)$source->source;
} else {
$ignored_source_model = NULL;
$ignored_source_id = (int)$source->source;
if($ignored_source_id > 0)
$ignored_source_model = (new Users)->get($ignored_source_id);
else
$ignored_source_model = (new Clubs)->get(abs($ignored_source_id));
if(!$ignored_source_model)
continue;
$output_array[] = $ignored_source_model;
}
}
return $output_array;
}
function getIgnoredSourcesCount()
{
return DatabaseConnection::i()->getContext()->table("ignored_sources")->where("owner", $this->getId())->count();
}
use Traits\TBackDrops; use Traits\TBackDrops;
use Traits\TSubscribable; use Traits\TSubscribable;
use Traits\TAudioStatuses;
use Traits\TIgnorable;
} }

View file

@ -1,7 +1,7 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\Web\Models\Entities; namespace openvk\Web\Models\Entities;
use openvk\Web\Util\Shell\Shell; use openvk\Web\Util\Shell\Shell;
use openvk\Web\Util\Shell\Exceptions\{ShellUnavailableException, UnknownCommandException}; use openvk\Web\Util\Shell\Shell\Exceptions\{ShellUnavailableException, UnknownCommandException};
use openvk\Web\Models\VideoDrivers\VideoDriver; use openvk\Web\Models\VideoDrivers\VideoDriver;
use Nette\InvalidStateException as ISE; use Nette\InvalidStateException as ISE;
@ -34,23 +34,9 @@ class Video extends Media
if(sizeof($durations[1]) === 0) if(sizeof($durations[1]) === 0)
throw new \DomainException("$filename does not contain any meaningful video streams"); throw new \DomainException("$filename does not contain any meaningful video streams");
$length = 0; foreach($durations[1] as $duration)
foreach($durations[1] as $duration) { if(floatval($duration) < 1.0)
$duration = floatval($duration);
if($duration < 1.0)
throw new \DomainException("$filename does not contain any meaningful video streams"); throw new \DomainException("$filename does not contain any meaningful video streams");
else
$length = max($length, $duration);
}
$this->stateChanges("length", (int) round($length, 0, PHP_ROUND_HALF_EVEN));
preg_match('%width=([0-9\.]++)%', $streams, $width);
preg_match('%height=([0-9\.]++)%', $streams, $height);
if(!empty($width) && !empty($height)) {
$this->stateChanges("width", $width[1]);
$this->stateChanges("height", $height[1]);
}
try { try {
if(!is_dir($dirId = dirname($this->pathFromHash($hash)))) if(!is_dir($dirId = dirname($this->pathFromHash($hash))))
@ -129,23 +115,22 @@ class Video extends Media
return $this->getRecord()->owner; return $this->getRecord()->owner;
} }
function getApiStructure(?User $user = NULL): object function getApiStructure(): object
{ {
$fromYoutube = $this->getType() == Video::TYPE_EMBED; $fromYoutube = $this->getType() == Video::TYPE_EMBED;
$dimensions = $this->getDimensions(); return (object)[
$res = (object)[
"type" => "video", "type" => "video",
"video" => [ "video" => [
"can_comment" => 1, "can_comment" => 1,
"can_like" => 1, // we don't h-have wikes in videos "can_like" => 0, // we don't h-have wikes in videos
"can_repost" => 1, "can_repost" => 0,
"can_subscribe" => 1, "can_subscribe" => 1,
"can_add_to_faves" => 0, "can_add_to_faves" => 0,
"can_add" => 0, "can_add" => 0,
"comments" => $this->getCommentsCount(), "comments" => $this->getCommentsCount(),
"date" => $this->getPublicationTime()->timestamp(), "date" => $this->getPublicationTime()->timestamp(),
"description" => $this->getDescription(), "description" => $this->getDescription(),
"duration" => $this->getLength(), "duration" => 0, // я хуй знает как получить длину видео
"image" => [ "image" => [
[ [
"url" => $this->getThumbnailURL(), "url" => $this->getThumbnailURL(),
@ -154,8 +139,8 @@ class Video extends Media
"with_padding" => 1 "with_padding" => 1
] ]
], ],
"width" => $dimensions ? $dimensions[0] : 640, "width" => 640,
"height" => $dimensions ? $dimensions[1] : 480, "height" => 480,
"id" => $this->getVirtualId(), "id" => $this->getVirtualId(),
"owner_id" => $this->getOwner()->getId(), "owner_id" => $this->getOwner()->getId(),
"user_id" => $this->getOwner()->getId(), "user_id" => $this->getOwner()->getId(),
@ -170,35 +155,29 @@ class Video extends Media
"repeat" => 0, "repeat" => 0,
"type" => "video", "type" => "video",
"views" => 0, "views" => 0,
"is_processed" => $this->isProcessed(), "likes" => [
"count" => 0,
"user_likes" => 0
],
"reposts" => [ "reposts" => [
"count" => 0, "count" => 0,
"user_reposted" => 0 "user_reposted" => 0
] ]
] ]
]; ];
if(!is_null($user)) {
$res->video["likes"] = [
"count" => $this->getLikesCount(),
"user_likes" => $this->hasLikeFrom($user)
];
}
return $res;
} }
function toVkApiStruct(?User $user): object function toVkApiStruct(): object
{ {
return $this->getApiStructure($user); return $this->getApiStructure();
} }
function setLink(string $link): string function setLink(string $link): string
{ {
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");
} }
@ -240,110 +219,4 @@ class Video extends Media
return $video; return $video;
} }
function fillDimensions()
{
$hash = $this->getRecord()->hash;
$path = $this->pathFromHash($hash);
if(!file_exists($path)) {
$this->stateChanges("width", 0);
$this->stateChanges("height", 0);
$this->stateChanges("length", 0);
$this->save();
return false;
}
$streams = Shell::ffprobe("-i", $path, "-show_streams", "-select_streams v", "-loglevel error")->execute($error);
$durations = [];
preg_match_all('%duration=([0-9\.]++)%', $streams, $durations);
$length = 0;
foreach($durations[1] as $duration) {
$duration = floatval($duration);
if($duration < 1.0)
continue;
else
$length = max($length, $duration);
}
$this->stateChanges("length", (int) round($length, 0, PHP_ROUND_HALF_EVEN));
preg_match('%width=([0-9\.]++)%', $streams, $width);
preg_match('%height=([0-9\.]++)%', $streams, $height);
if(!empty($width) && !empty($height)) {
$this->stateChanges("width", $width[1]);
$this->stateChanges("height", $height[1]);
}
$this->save();
return true;
}
function getDimensions()
{
if($this->getType() == Video::TYPE_EMBED) return [320, 180];
$width = $this->getRecord()->width;
$height = $this->getRecord()->height;
if(!$width) return NULL;
return $width != 0 ? [$width, $height] : NULL;
}
function getLength()
{
return $this->getRecord()->length;
}
function getFormattedLength(): string
{
$len = $this->getLength();
if(!$len) return "00:00";
$mins = floor($len / 60);
$secs = $len - ($mins * 60);
return (
str_pad((string) $mins, 2, "0", STR_PAD_LEFT)
. ":" .
str_pad((string) $secs, 2, "0", STR_PAD_LEFT)
);
}
function getPageURL(): string
{
return "/video".$this->getPrettyId();
}
function canBeViewedBy(?User $user = NULL): bool
{
if($this->isDeleted() || $this->getOwner()->isDeleted()) {
return false;
}
if(get_class($this->getOwner()) == "openvk\\Web\\Models\\Entities\\User") {
return $this->getOwner()->canBeViewedBy($user) && $this->getOwner()->getPrivacyPermission('videos.read', $user);
} else {
# Groups doesn't have videos but ok
return $this->getOwner()->canBeViewedBy($user);
}
}
function toNotifApiStruct()
{
$fromYoutube = $this->getType() == Video::TYPE_EMBED;
$res = (object)[];
$res->id = $this->getVirtualId();
$res->owner_id = $this->getOwner()->getId();
$res->title = $this->getName();
$res->description = $this->getDescription();
$res->duration = $this->getLength();
$res->link = "/video".$this->getOwner()->getId()."_".$this->getVirtualId();
$res->image = $this->getThumbnailURL();
$res->date = $this->getPublicationTime()->timestamp();
$res->views = 0;
$res->player = !$fromYoutube ? $this->getURL() : $this->getVideoDriver()->getURL();
return $res;
}
} }

View file

@ -23,13 +23,4 @@ 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

@ -130,7 +130,7 @@ class Albums
"owner" => $owner, "owner" => $owner,
"id" => $id "id" => $id
])->fetch(); ])->fetch();
return $album ? new Album($album) : NULL; return new Album($album);
} }
} }

View file

@ -67,21 +67,11 @@ class Applications
return sizeof($this->appRels->where("user", $user->getId())); return sizeof($this->appRels->where("user", $user->getId()));
} }
function find(string $query = "", array $params = [], array $order = ['type' => 'id', 'invert' => false]): Util\EntityStream function find(string $query, array $pars = [], string $sort = "id"): 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); return new Util\EntityStream("Application", $result->order("$sort"));
} }
} }

View file

@ -1,318 +0,0 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Entities\Audio;
use openvk\Web\Models\Entities\Club;
use openvk\Web\Models\Entities\Playlist;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\Util\EntityStream;
class Audios
{
private $context;
private $audios;
private $rels;
private $playlists;
private $playlistImports;
private $playlistRels;
const ORDER_NEW = 0;
const ORDER_POPULAR = 1;
const VK_ORDER_NEW = 0;
const VK_ORDER_LENGTH = 1;
const VK_ORDER_POPULAR = 2;
function __construct()
{
$this->context = DatabaseConnection::i()->getContext();
$this->audios = $this->context->table("audios");
$this->rels = $this->context->table("audio_relations");
$this->playlists = $this->context->table("playlists");
$this->playlistImports = $this->context->table("playlist_imports");
$this->playlistRels = $this->context->table("playlist_relations");
}
function get(int $id): ?Audio
{
$audio = $this->audios->get($id);
if(!$audio)
return NULL;
return new Audio($audio);
}
function getPlaylist(int $id): ?Playlist
{
$playlist = $this->playlists->get($id);
if(!$playlist)
return NULL;
return new Playlist($playlist);
}
function getByOwnerAndVID(int $owner, int $vId): ?Audio
{
$audio = $this->audios->where([
"owner" => $owner,
"virtual_id" => $vId,
])->fetch();
if(!$audio) return NULL;
return new Audio($audio);
}
function getPlaylistByOwnerAndVID(int $owner, int $vId): ?Playlist
{
$playlist = $this->playlists->where([
"owner" => $owner,
"id" => $vId,
])->fetch();
if(!$playlist) return NULL;
return new Playlist($playlist);
}
function getByEntityID(int $entity, int $offset = 0, ?int $limit = NULL, ?int& $deleted = nullptr): \Traversable
{
$limit ??= OPENVK_DEFAULT_PER_PAGE;
$iter = $this->rels->where("entity", $entity)->limit($limit, $offset)->order("index DESC");
foreach($iter as $rel) {
$audio = $this->get($rel->audio);
if(!$audio || $audio->isDeleted()) {
$deleted++;
continue;
}
yield $audio;
}
}
function getPlaylistsByEntityId(int $entity, int $offset = 0, ?int $limit = NULL, ?int& $deleted = nullptr): \Traversable
{
$limit ??= OPENVK_DEFAULT_PER_PAGE;
$iter = $this->playlistImports->where("entity", $entity)->limit($limit, $offset);
foreach($iter as $rel) {
$playlist = $this->getPlaylist($rel->playlist);
if(!$playlist || $playlist->isDeleted()) {
$deleted++;
continue;
}
yield $playlist;
}
}
function getByUser(User $user, int $page = 1, ?int $perPage = NULL, ?int& $deleted = nullptr): \Traversable
{
return $this->getByEntityID($user->getId(), ($perPage * ($page - 1)), $perPage, $deleted);
}
function getRandomThreeAudiosByEntityId(int $id): Array
{
$iter = $this->rels->where("entity", $id);
$ids = [];
foreach($iter as $it)
$ids[] = $it->audio;
$shuffleSeed = openssl_random_pseudo_bytes(6);
$shuffleSeed = hexdec(bin2hex($shuffleSeed));
$ids = knuth_shuffle($ids, $shuffleSeed);
$ids = array_slice($ids, 0, 3);
$audios = [];
foreach($ids as $id) {
$audio = $this->get((int)$id);
if(!$audio || $audio->isDeleted())
continue;
$audios[] = $audio;
}
return $audios;
}
function getByClub(Club $club, int $page = 1, ?int $perPage = NULL, ?int& $deleted = nullptr): \Traversable
{
return $this->getByEntityID($club->getId() * -1, ($perPage * ($page - 1)), $perPage, $deleted);
}
function getPlaylistsByUser(User $user, int $page = 1, ?int $perPage = NULL, ?int& $deleted = nullptr): \Traversable
{
return $this->getPlaylistsByEntityId($user->getId(), ($perPage * ($page - 1)), $perPage, $deleted);
}
function getPlaylistsByClub(Club $club, int $page = 1, ?int $perPage = NULL, ?int& $deleted = nullptr): \Traversable
{
return $this->getPlaylistsByEntityId($club->getId() * -1, ($perPage * ($page - 1)), $perPage, $deleted);
}
function getCollectionSizeByEntityId(int $id): int
{
return sizeof($this->rels->where("entity", $id));
}
function getUserCollectionSize(User $user): int
{
return sizeof($this->rels->where("entity", $user->getId()));
}
function getClubCollectionSize(Club $club): int
{
return sizeof($this->rels->where("entity", $club->getId() * -1));
}
function getUserPlaylistsCount(User $user): int
{
return sizeof($this->playlistImports->where("entity", $user->getId()));
}
function getClubPlaylistsCount(Club $club): int
{
return sizeof($this->playlistImports->where("entity", $club->getId() * -1));
}
function getByUploader(User $user): EntityStream
{
$search = $this->audios->where([
"owner" => $user->getId(),
"deleted" => 0,
]);
return new EntityStream("Audio", $search);
}
function getGlobal(int $order, ?string $genreId = NULL): EntityStream
{
$search = $this->audios->where([
"deleted" => 0,
"unlisted" => 0,
"withdrawn" => 0,
])->order($order == Audios::ORDER_NEW ? "created DESC" : "listens DESC");
if(!is_null($genreId))
$search = $search->where("genre", $genreId);
return new EntityStream("Audio", $search);
}
function search(string $query, int $sortMode = 0, bool $performerOnly = false, bool $withLyrics = false): EntityStream
{
$columns = $performerOnly ? "performer" : "performer, name";
$order = (["created", "length", "listens"][$sortMode] ?? "") . " DESC";
$search = $this->audios->where([
"unlisted" => 0,
"deleted" => 0,
])->where("MATCH ($columns) AGAINST (? IN BOOLEAN MODE)", "%$query%")->order($order);
if($withLyrics)
$search = $search->where("lyrics IS NOT NULL");
return new EntityStream("Audio", $search);
}
function searchPlaylists(string $query): EntityStream
{
$search = $this->playlists->where([
"unlisted" => 0,
"deleted" => 0,
])->where("MATCH (`name`, `description`) AGAINST (? IN BOOLEAN MODE)", $query);
return new EntityStream("Playlist", $search);
}
function getNew(): EntityStream
{
return new EntityStream("Audio", $this->audios->where("created >= " . (time() - 259200))->where(["withdrawn" => 0, "deleted" => 0, "unlisted" => 0])->order("created DESC")->limit(25));
}
function getPopular(): EntityStream
{
return new EntityStream("Audio", $this->audios->where("listens > 0")->where(["withdrawn" => 0, "deleted" => 0, "unlisted" => 0])->order("listens DESC")->limit(25));
}
function isAdded(int $user_id, int $audio_id): bool
{
return !is_null($this->rels->where([
"entity" => $user_id,
"audio" => $audio_id
])->fetch());
}
function find(string $query, array $params = [], array $order = ['type' => 'id', 'invert' => false], int $page = 1, ?int $perPage = NULL): \Traversable
{
$query = "%$query%";
$result = $this->audios->where([
"unlisted" => 0,
"deleted" => 0,
/*"withdrawn" => 0,
"processed" => 1,*/
]);
$order_str = (in_array($order['type'], ['id', 'length', 'listens']) ? $order['type'] : 'id') . ' ' . ($order['invert'] ? 'ASC' : 'DESC');;
if($params["only_performers"] == "1") {
$result->where("performer LIKE ?", $query);
} else {
$result->where("CONCAT_WS(' ', performer, name) LIKE ?", $query);
}
foreach($params as $paramName => $paramValue) {
if(is_null($paramValue) || $paramValue == '') continue;
switch($paramName) {
case "before":
$result->where("created < ?", $paramValue);
break;
case "after":
$result->where("created > ?", $paramValue);
break;
case "with_lyrics":
$result->where("lyrics IS NOT NULL");
break;
case 'genre':
if($paramValue == 'any') break;
$result->where("genre", $paramValue);
break;
}
}
if($order_str)
$result->order($order_str);
return new Util\EntityStream("Audio", $result);
}
function findPlaylists(string $query, array $params = [], array $order = ['type' => 'id', 'invert' => false]): \Traversable
{
$query = "%$query%";
$result = $this->playlists->where([
"deleted" => 0,
])->where("CONCAT_WS(' ', name, description) LIKE ?", $query);
$order_str = (in_array($order['type'], ['id', 'length', 'listens']) ? $order['type'] : 'id') . ' ' . ($order['invert'] ? 'ASC' : 'DESC');
if(is_null($params['from_me']) || empty($params['from_me']))
$result->where(["unlisted" => 0]);
foreach($params as $paramName => $paramValue) {
if(is_null($paramValue) || $paramValue == '') continue;
switch($paramName) {
# БУДЬ МАКСИМАЛЬНО АККУРАТЕН С ДАННЫМ ПАРАМЕТРОМ
case "from_me":
$result->where("owner", $paramValue);
break;
}
}
if($order_str)
$result->order($order_str);
return new Util\EntityStream("Playlist", $result);
}
}

View file

@ -45,9 +45,4 @@ class ChandlerGroups
{ {
foreach($this->perms->where("group", $UUID) as $perm) yield $perm; foreach($this->perms->where("group", $UUID) as $perm) yield $perm;
} }
function isUserAMember(string $GID, string $UID): bool
{
return ($this->context->query("SELECT * FROM `ChandlerACLRelations` WHERE `group` = ? AND `user` = ?", $GID, $UID)) !== NULL;
}
} }

View file

@ -42,42 +42,18 @@ class Clubs
{ {
return $this->toClub($this->clubs->get($id)); return $this->toClub($this->clubs->get($id));
} }
function getByIds(array $ids = []): array function find(string $query, array $pars = [], string $sort = "id DESC", int $page = 1, ?int $perPage = NULL): \Traversable
{ {
$clubs = $this->clubs->select('*')->where('id IN (?)', $ids); $query = "%$query%";
$clubs_array = []; $result = $this->clubs->where("name LIKE ? OR about LIKE ?", $query, $query);
foreach($clubs as $club) { return new Util\EntityStream("Club", $result->order($sort));
$clubs_array[] = $this->toClub($club);
}
return $clubs_array;
}
function find(string $query, array $params = [], array $order = ['type' => 'id', 'invert' => false], int $page = 1, ?int $perPage = NULL): \Traversable
{
$query = "%$query%";
$result = $this->clubs;
$order_str = 'id';
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 (clone $this->clubs)->count('*'); return sizeof(clone $this->clubs);
} }
function getPopularClubs(): \Traversable function getPopularClubs(): \Traversable

View file

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

View file

@ -42,10 +42,4 @@ class Gifts
foreach($cats as $cat) foreach($cats as $cat)
yield new GiftCategory($cat); yield new GiftCategory($cat);
} }
function getCategoriesCount(): int
{
$cats = $this->cats->where("deleted", false);
return $cats->count();
}
} }

View file

@ -52,6 +52,7 @@ class Messages
$query = file_get_contents(__DIR__ . "/../sql/get-correspondencies-count.tsql"); $query = file_get_contents(__DIR__ . "/../sql/get-correspondencies-count.tsql");
DatabaseConnection::i()->getConnection()->query(file_get_contents(__DIR__ . "/../sql/mysql-msg-fix.tsql")); DatabaseConnection::i()->getConnection()->query(file_get_contents(__DIR__ . "/../sql/mysql-msg-fix.tsql"));
$count = DatabaseConnection::i()->getConnection()->query($query, $id, $class, $id, $class)->fetch()->cnt; $count = DatabaseConnection::i()->getConnection()->query($query, $id, $class, $id, $class)->fetch()->cnt;
bdump($count);
return $count; return $count;
} }
} }

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, int $offset, bool $archived = false, int $page = 1, ?int $perPage = NULL): string private function getQuery(User $user, bool $count = false, 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

@ -33,26 +33,14 @@ class Photos
return new Photo($photo); return new Photo($photo);
} }
function getEveryUserPhoto(User $user, int $offset = 0, int $limit = 10): \Traversable function getEveryUserPhoto(User $user): \Traversable
{ {
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE;
$photos = $this->photos->where([ $photos = $this->photos->where([
"owner" => $user->getId(), "owner" => $user->getId()
"deleted" => 0 ]);
])->order("id DESC");
foreach($photos->limit($limit, $offset) as $photo) { foreach($photos as $photo) {
yield new Photo($photo); yield new Photo($photo);
} }
} }
function getUserPhotosCount(User $user)
{
$photos = $this->photos->where([
"owner" => $user->getId(),
"deleted" => 0
]);
return sizeof($photos);
}
} }

View file

@ -53,64 +53,19 @@ class Posts
$offset--; $offset--;
} }
} }
} else if(!is_null($offset) && $pinPost) { } else if(!is_null($offset)) {
$offset--; $offset--;
} }
$sel = $this->posts->where([ $sel = $this->posts->where([
"wall" => $user, "wall" => $user,
"pinned" => false, "pinned" => false,
"deleted" => false, "deleted" => false,
"suggested" => 0,
])->order("created DESC")->limit($perPage, $offset); ])->order("created DESC")->limit($perPage, $offset);
foreach($sel as $post) foreach($sel as $post)
yield new Post($post); yield new Post($post);
} }
function getOwnersPostsFromWall(int $user, int $page = 1, ?int $perPage = NULL, ?int $offset = NULL): \Traversable
{
$perPage ??= OPENVK_DEFAULT_PER_PAGE;
$offset ??= $perPage * ($page - 1);
$sel = $this->posts->where([
"wall" => $user,
"deleted" => false,
"suggested" => 0,
]);
if($user > 0)
$sel->where("owner", $user);
else
$sel->where("flags !=", 0);
$sel->order("created DESC")->limit($perPage, $offset);
foreach($sel as $post)
yield new Post($post);
}
function getOthersPostsFromWall(int $user, int $page = 1, ?int $perPage = NULL, ?int $offset = NULL): \Traversable
{
$perPage ??= OPENVK_DEFAULT_PER_PAGE;
$offset ??= $perPage * ($page - 1);
$sel = $this->posts->where([
"wall" => $user,
"deleted" => false,
"suggested" => 0,
]);
if($user > 0)
$sel->where("owner !=", $user);
else
$sel->where("flags", 0);
$sel->order("created DESC")->limit($perPage, $offset);
foreach($sel as $post)
yield new Post($post);
}
function getPostsByHashtag(string $hashtag, int $page = 1, ?int $perPage = NULL): \Traversable function getPostsByHashtag(string $hashtag, int $page = 1, ?int $perPage = NULL): \Traversable
{ {
@ -119,7 +74,6 @@ class Posts
->where("MATCH (content) AGAINST (? IN BOOLEAN MODE)", "+$hashtag") ->where("MATCH (content) AGAINST (? IN BOOLEAN MODE)", "+$hashtag")
->where("deleted", 0) ->where("deleted", 0)
->order("created DESC") ->order("created DESC")
->where("suggested", 0)
->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE); ->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE);
foreach($sel as $post) foreach($sel as $post)
@ -131,22 +85,14 @@ class Posts
$hashtag = "#$hashtag"; $hashtag = "#$hashtag";
$sel = $this->posts $sel = $this->posts
->where("content LIKE ?", "%$hashtag%") ->where("content LIKE ?", "%$hashtag%")
->where("deleted", 0) ->where("deleted", 0);
->where("suggested", 0);
return sizeof($sel); return sizeof($sel);
} }
function getPostById(int $wall, int $post, bool $forceSuggestion = false): ?Post function getPostById(int $wall, int $post): ?Post
{ {
$post = $this->posts->where(['wall' => $wall, 'virtual_id' => $post]); $post = $this->posts->where(['wall' => $wall, 'virtual_id' => $post])->fetch();
if(!$forceSuggestion) {
$post->where("suggested", 0);
}
$post = $post->fetch();
if(!is_null($post)) if(!is_null($post))
return new Post($post); return new Post($post);
else else
@ -154,113 +100,45 @@ class Posts
} }
function find(string $query = "", array $params = [], array $order = ['type' => 'id', 'invert' => false]): Util\EntityStream function find(string $query = "", array $pars = [], string $sort = "id"): Util\EntityStream
{ {
$query = "%$query%"; $query = "%$query%";
$result = $this->posts->where("content LIKE ?", $query)->where("deleted", 0)->where("suggested", 0);
$order_str = 'id';
switch($order['type']) { $notNullParams = [];
case 'id':
$order_str = 'created ' . ($order['invert'] ? 'ASC' : 'DESC');
break;
}
foreach($params as $paramName => $paramValue) { foreach($pars as $paramName => $paramValue)
if(is_null($paramValue) || $paramValue == '') continue; if($paramName != "before" && $paramName != "after")
$paramValue != NULL ? $notNullParams+=["$paramName" => "%$paramValue%"] : NULL;
else
$paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL;
switch($paramName) { $result = $this->posts->where("content LIKE ?", $query)->where("deleted", 0);
case "before": $nnparamsCount = sizeof($notNullParams);
$result->where("created < ?", $paramValue);
break; if($nnparamsCount > 0) {
case "after": foreach($notNullParams as $paramName => $paramValue) {
$result->where("created > ?", $paramValue); switch($paramName) {
break; case "before":
/*case 'die_in_agony': $result->where("created < ?", $paramValue);
$result->where("nsfw", 1); break;
break; case "after":
case 'ads': $result->where("created > ?", $paramValue);
$result->where("ad", 1); break;
break;*/ }
# БУДЬ МАКСИМАЛЬНО АККУРАТЕН С ДАННЫМ ПАРАМЕТРОМ
case 'from_me':
$result->where("owner", $paramValue);
break;
} }
} }
if($order_str)
$result->order($order_str);
return new Util\EntityStream("Post", $result); return new Util\EntityStream("Post", $result->order("$sort"));
} }
function getPostCountOnUserWall(int $user): int function getPostCountOnUserWall(int $user): int
{ {
return sizeof($this->posts->where(["wall" => $user, "deleted" => 0, "suggested" => 0])); return sizeof($this->posts->where(["wall" => $user, "deleted" => 0]));
}
function getOwnersCountOnUserWall(int $user): int
{
if($user > 0)
return sizeof($this->posts->where(["wall" => $user, "deleted" => 0, "owner" => $user]));
else
return sizeof($this->posts->where(["wall" => $user, "deleted" => 0, "suggested" => 0])->where("flags !=", 0));
}
function getOthersCountOnUserWall(int $user): int
{
if($user > 0)
return sizeof($this->posts->where(["wall" => $user, "deleted" => 0])->where("owner !=", $user));
else
return sizeof($this->posts->where(["wall" => $user, "deleted" => 0, "suggested" => 0])->where("flags", 0));
}
function getSuggestedPosts(int $club, int $page = 1, ?int $perPage = NULL, ?int $offset = NULL): \Traversable
{
$perPage ??= OPENVK_DEFAULT_PER_PAGE;
$offset ??= $perPage * ($page - 1);
$sel = $this->posts
->where("deleted", 0)
->where("wall", $club * -1)
->order("created DESC")
->where("suggested", 1)
->limit($perPage, $offset);
foreach($sel as $post)
yield new Post($post);
}
function getSuggestedPostsCount(int $club)
{
return sizeof($this->posts->where(["wall" => $club * -1, "deleted" => 0, "suggested" => 1]));
}
function getSuggestedPostsByUser(int $club, int $user, int $page = 1, ?int $perPage = NULL, ?int $offset = NULL): \Traversable
{
$perPage ??= OPENVK_DEFAULT_PER_PAGE;
$offset ??= $perPage * ($page - 1);
$sel = $this->posts
->where("deleted", 0)
->where("wall", $club * -1)
->where("owner", $user)
->order("created DESC")
->where("suggested", 1)
->limit($perPage, $offset);
foreach($sel as $post)
yield new Post($post);
}
function getSuggestedPostsCountByUser(int $club, int $user): int
{
return sizeof($this->posts->where(["wall" => $club * -1, "deleted" => 0, "suggested" => 1, "owner" => $user]));
} }
function getCount(): int function getCount(): int
{ {
return (clone $this->posts)->count('*'); return sizeof(clone $this->posts);
} }
} }

View file

@ -28,18 +28,6 @@ class Users
{ {
return $this->toUser($this->users->get($id)); return $this->toUser($this->users->get($id));
} }
function getByIds(array $ids = []): array
{
$users = $this->users->select('*')->where('id IN (?)', $ids);
$users_array = [];
foreach($users as $user) {
$users_array[] = $this->toUser($user);
}
return $users_array;
}
function getByShortURL(string $url): ?User function getByShortURL(string $url): ?User
{ {
@ -56,94 +44,104 @@ class Users
return $alias->getUser(); return $alias->getUser();
} }
function getByChandlerUserId(string $cid): ?User
{
return $this->toUser($this->users->where("user", $cid)->fetch());
}
function getByChandlerUser(?ChandlerUser $user): ?User function getByChandlerUser(?ChandlerUser $user): ?User
{ {
return $user ? $this->getByChandlerUserId($user->getId()) : NULL; return $user ? $this->toUser($this->users->where("user", $user->getId())->fetch()) : NULL;
} }
function find(string $query, array $params = [], array $order = ['type' => 'id', 'invert' => false]): Util\EntityStream function find(string $query, array $pars = [], string $sort = "id DESC"): Util\EntityStream
{ {
$query = "%$query%"; $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;
switch($order['type']) { $nnparamsCount = sizeof($notNullParams);
case 'id':
case 'reg_date':
$order_str = 'id ' . ($order['invert'] ? 'ASC' : 'DESC');
break;
case 'rating':
$order_str = 'rating DESC';
break;
}
foreach($params as $paramName => $paramValue) { if($nnparamsCount > 0) {
if(is_null($paramValue) || $paramValue == '') continue; foreach($notNullParams as $paramName => $paramValue) {
switch($paramName) {
switch($paramName) { case "hometown":
case "hometown": $result->where("hometown LIKE ?", $paramValue);
$result->where("hometown LIKE ?", "%$paramValue%"); break;
break; case "city":
case "city": $result->where("city LIKE ?", $paramValue);
$result->where("city LIKE ?", "%$paramValue%"); break;
break; case "maritalstatus":
case "marital_status": $result->where("marital_status ?", $paramValue);
$result->where("marital_status ?", $paramValue); break;
break; case "status":
case "polit_views": $result->where("status LIKE ?", $paramValue);
$result->where("polit_views ?", $paramValue); break;
break; case "politViews":
case "is_online": $result->where("polit_views ?", $paramValue);
$result->where("online >= ?", time() - 900); break;
break; case "email":
case "fav_mus": $result->where("email_contact LIKE ?", $paramValue);
$result->where("fav_music LIKE ?", "%$paramValue%"); break;
break; case "telegram":
case "fav_films": $result->where("telegram LIKE ?", $paramValue);
$result->where("fav_films LIKE ?", "%$paramValue%"); break;
break; case "site":
case "fav_shows": $result->where("telegram LIKE ?", $paramValue);
$result->where("fav_shows LIKE ?", "%$paramValue%"); break;
break; case "address":
case "fav_books": $result->where("address LIKE ?", $paramValue);
$result->where("fav_books LIKE ?", "%$paramValue%"); break;
break; case "is_online":
case "before": $result->where("online >= ?", time() - 900);
$result->where("UNIX_TIMESTAMP(since) < ?", $paramValue); break;
break; case "interests":
case "after": $result->where("interests LIKE ?", $paramValue);
$result->where("UNIX_TIMESTAMP(since) > ?", $paramValue); break;
break; case "fav_mus":
case "gender": $result->where("fav_music LIKE ?", $paramValue);
if((int) $paramValue == 3) break; break;
$result->where("sex ?", (int) $paramValue); case "fav_films":
break; $result->where("fav_films LIKE ?", $paramValue);
case "ignore_id": break;
$result->where("id != ?", $paramValue); case "fav_shows":
break; $result->where("fav_shows LIKE ?", $paramValue);
case "ignore_private": break;
$result->where("profile_type", 0); case "fav_books":
break; $result->where("fav_books LIKE ?", $paramValue);
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;
}
} }
} }
if($order_str)
$result->order($order_str);
return new Util\EntityStream("User", $result); return new Util\EntityStream("User", $result->order($sort));
} }
function getStatistics(): object function getStatistics(): object
{ {
return (object) [ return (object) [
"all" => (clone $this->users)->count('*'), "all" => sizeof(clone $this->users),
"active" => (clone $this->users)->where("online > 0")->count('*'), "active" => sizeof((clone $this->users)->where("online > 0")),
"online" => (clone $this->users)->where("online >= ?", time() - 900)->count('*'), "online" => sizeof((clone $this->users)->where("online >= ?", time() - 900)),
]; ];
} }

View file

@ -39,13 +39,6 @@ class Videos
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE; $perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE;
foreach($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])->page($page, $perPage)->order("created DESC") as $video) foreach($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])->page($page, $perPage)->order("created DESC") as $video)
yield new Video($video); yield new Video($video);
}
function getByUserLimit(User $user, int $offset = 0, int $limit = 10): \Traversable
{
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE;
foreach($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])->limit($limit, $offset)->order("created DESC") as $video)
yield new Video($video);
} }
function getUserVideosCount(User $user): int function getUserVideosCount(User $user): int
@ -53,43 +46,35 @@ 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 $params = [], array $order = ['type' => 'id', 'invert' => false]): Util\EntityStream function find(string $query = "", array $pars = [], string $sort = "id"): Util\EntityStream
{ {
$query = "%$query%"; $query = "%$query%";
$result = $this->videos->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("deleted", 0)->where("unlisted", 0);
$order_str = 'id';
switch($order['type']) { $notNullParams = [];
case 'id':
$order_str = 'id ' . ($order['invert'] ? 'ASC' : 'DESC');
break;
}
foreach($params as $paramName => $paramValue) { foreach($pars as $paramName => $paramValue)
switch($paramName) { if($paramName != "before" && $paramName != "after")
case "before": $paramValue != NULL ? $notNullParams+=["$paramName" => "%$paramValue%"] : NULL;
$result->where("created < ?", $paramValue); else
break; $paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL;
case "after":
$result->where("created > ?", $paramValue); $result = $this->videos->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("deleted", 0);
break; $nnparamsCount = sizeof($notNullParams);
case 'only_youtube':
if((int) $paramValue != 1) break; if($nnparamsCount > 0) {
$result->where("link != ?", 'NULL'); foreach($notNullParams as $paramName => $paramValue) {
break; switch($paramName) {
case "before":
$result->where("created < ?", $paramValue);
break;
case "after":
$result->where("created > ?", $paramValue);
break;
}
} }
} }
if($order_str)
$result->order($order_str);
return new Util\EntityStream("Video", $result); return new Util\EntityStream("Video", $result->order("$sort"));
}
function getLastVideo(User $user)
{
$video = $this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])->order("id DESC")->fetch();
return new Video($video);
} }
} }

View file

@ -1,39 +0,0 @@
$ovkRoot = $args[0]
$storageDir = $args[1]
$fileHash = $args[2]
$hashPart = $fileHash.substring(0, 2)
$filename = $args[3]
$audioFile = [System.IO.Path]::GetTempFileName()
$temp = [System.IO.Path]::GetTempFileName()
$keyID = $args[4]
$key = $args[5]
$token = $args[6]
$seg = $args[7]
$shell = Get-WmiObject Win32_process -filter "ProcessId = $PID"
$shell.SetPriority(16384) # because there's no "nice" program in Windows we just set a lower priority for entire tree
Remove-Item $temp
Remove-Item $audioFile
New-Item -ItemType "directory" $temp
New-Item -ItemType "directory" ("$temp/$fileHash" + '_fragments')
New-Item -ItemType "directory" ("$storageDir/$hashPart/$fileHash" + '_fragments')
Set-Location -Path $temp
Move-Item $filename $audioFile
ffmpeg -i $audioFile -f dash -encryption_scheme cenc-aes-ctr -encryption_key $key `
-encryption_kid $keyID -map 0:a -vn -c:a aac -ar 44100 -seg_duration $seg `
-use_timeline 1 -use_template 1 -init_seg_name ($fileHash + '_fragments/0_0.$ext$') `
-media_seg_name ($fileHash + '_fragments/chunk$Number%06d$_$RepresentationID$.$ext$') -adaptation_sets 'id=0,streams=a' `
"$fileHash.mpd"
ffmpeg -i $audioFile -vn -ar 44100 "original_$token.mp3"
Move-Item "original_$token.mp3" ($fileHash + '_fragments')
Get-ChildItem -Path ($fileHash + '_fragments/*') | Move-Item -Destination ("$storageDir/$hashPart/$fileHash" + '_fragments')
Move-Item -Path ("$fileHash.mpd") -Destination "$storageDir/$hashPart"
cd ..
Remove-Item -Recurse $temp
Remove-Item $audioFile

View file

@ -1,35 +0,0 @@
ovkRoot=$1
storageDir=$2
fileHash=$3
hashPart=$(echo $fileHash | cut -c1-2)
filename=$4
audioFile=$(mktemp)
temp=$(mktemp -d)
keyID=$5
key=$6
token=$7
seg=$8
trap 'rm -f "$temp" "$audioFile"' EXIT
mkdir -p "$temp/$fileHash"_fragments
mkdir -p "$storageDir/$hashPart/$fileHash"_fragments
cd "$temp"
mv "$filename" "$audioFile"
ffmpeg -i "$audioFile" -f dash -encryption_scheme cenc-aes-ctr -encryption_key "$key" \
-encryption_kid "$keyID" -map 0 -vn -c:a aac -ar 44100 -seg_duration "$seg" \
-use_timeline 1 -use_template 1 -init_seg_name "$fileHash"_fragments/0_0."\$ext\$" \
-media_seg_name "$fileHash"_fragments/chunk"\$Number"%06d\$_"\$RepresentationID\$"."\$ext\$" -adaptation_sets 'id=0,streams=a' \
"$fileHash.mpd"
ffmpeg -i "$audioFile" -vn -ar 44100 "original_$token.mp3"
mv "original_$token.mp3" "$fileHash"_fragments
mv "$fileHash"_fragments "$storageDir/$hashPart"
mv "$fileHash.mpd" "$storageDir/$hashPart"
cd ..
rm -rf "$temp"
rm -f "$audioFile"

View file

@ -13,7 +13,7 @@ Move-Item $file $temp
# video stub logic was implicitly deprecated, so we start processing at once # video stub logic was implicitly deprecated, so we start processing at once
ffmpeg -i $temp -ss 00:00:01.000 -vframes 1 "$dir$hashT/$hash.gif" ffmpeg -i $temp -ss 00:00:01.000 -vframes 1 "$dir$hashT/$hash.gif"
ffmpeg -i $temp -c:v libx264 -q:v 7 -c:a libmp3lame -q:a 4 -tune zerolatency -vf "scale=480:-1,setsar=1" -y $temp2 ffmpeg -i $temp -c:v libx264 -q:v 7 -c:a libmp3lame -q:a 4 -tune zerolatency -vf "scale=640:480:force_original_aspect_ratio=decrease,pad=640:480:(ow-iw)/2:(oh-ih)/2,setsar=1" -y $temp2
Move-Item $temp2 "$dir$hashT/$hash.mp4" Move-Item $temp2 "$dir$hashT/$hash.mp4"
Remove-Item $temp Remove-Item $temp

View file

@ -3,7 +3,7 @@ tmpfile="$RANDOM-$(date +%s%N)"
cp $2 "/tmp/vid_$tmpfile.bin" cp $2 "/tmp/vid_$tmpfile.bin"
nice ffmpeg -i "/tmp/vid_$tmpfile.bin" -ss 00:00:01.000 -vframes 1 $3${4:0:2}/$4.gif nice ffmpeg -i "/tmp/vid_$tmpfile.bin" -ss 00:00:01.000 -vframes 1 $3${4:0:2}/$4.gif
nice -n 20 ffmpeg -i "/tmp/vid_$tmpfile.bin" -c:v libx264 -q:v 7 -c:a libmp3lame -q:a 4 -tune zerolatency -vf "scale=480:-1,setsar=1" -y "/tmp/ffmOi$tmpfile.mp4" nice -n 20 ffmpeg -i "/tmp/vid_$tmpfile.bin" -c:v libx264 -q:v 7 -c:a libmp3lame -q:a 4 -tune zerolatency -vf "scale=640:480:force_original_aspect_ratio=decrease,pad=640:480:(ow-iw)/2:(oh-ih)/2,setsar=1" -y "/tmp/ffmOi$tmpfile.mp4"
rm -rf $3${4:0:2}/$4.mp4 rm -rf $3${4:0:2}/$4.mp4
mv "/tmp/ffmOi$tmpfile.mp4" $3${4:0:2}/$4.mp4 mv "/tmp/ffmOi$tmpfile.mp4" $3${4:0:2}/$4.mp4

View file

@ -1,5 +1,5 @@
(SELECT DISTINCT(follower) AS __id FROM (SELECT DISTINCT(follower) AS __id FROM
(SELECT follower, flags FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0 (SELECT follower 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

@ -1,6 +0,0 @@
(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

@ -109,10 +109,6 @@ final class AboutPresenter extends OpenVKPresenter
. "# lack of rights to access the admin panel)\n\n" . "# lack of rights to access the admin panel)\n\n"
. "User-Agent: *\n" . "User-Agent: *\n"
. "Disallow: /albums/create\n" . "Disallow: /albums/create\n"
. "Disallow: /assets/packages/static/openvk/img/banned.jpg\n"
. "Disallow: /assets/packages/static/openvk/img/camera_200.png\n"
. "Disallow: /assets/packages/static/openvk/img/flags/\n"
. "Disallow: /assets/packages/static/openvk/img/oof.apng\n"
. "Disallow: /videos/upload\n" . "Disallow: /videos/upload\n"
. "Disallow: /invite\n" . "Disallow: /invite\n"
. "Disallow: /groups_create\n" . "Disallow: /groups_create\n"
@ -145,6 +141,6 @@ final class AboutPresenter extends OpenVKPresenter
function renderDev(): void function renderDev(): void
{ {
$this->redirect("https://docs.ovk.to/"); $this->redirect("https://docs.openvk.uk/");
} }
} }

View file

@ -3,19 +3,7 @@ namespace openvk\Web\Presenters;
use Chandler\Database\Log; use Chandler\Database\Log;
use Chandler\Database\Logs; use Chandler\Database\Logs;
use openvk\Web\Models\Entities\{Voucher, Gift, GiftCategory, User, BannedLink}; use openvk\Web\Models\Entities\{Voucher, Gift, GiftCategory, User, BannedLink};
use openvk\Web\Models\Repositories\{Audios, use openvk\Web\Models\Repositories\{Bans, ChandlerGroups, ChandlerUsers, Photos, Posts, Users, Clubs, Videos, Vouchers, Gifts, BannedLinks};
ChandlerGroups,
ChandlerUsers,
Users,
Clubs,
Util\EntityStream,
Vouchers,
Gifts,
BannedLinks,
Bans,
Photos,
Posts,
Videos};
use Chandler\Database\DatabaseConnection; use Chandler\Database\DatabaseConnection;
final class AdminPresenter extends OpenVKPresenter final class AdminPresenter extends OpenVKPresenter
@ -26,10 +14,9 @@ final class AdminPresenter extends OpenVKPresenter
private $gifts; private $gifts;
private $bannedLinks; private $bannedLinks;
private $chandlerGroups; private $chandlerGroups;
private $audios;
private $logs; private $logs;
function __construct(Users $users, Clubs $clubs, Vouchers $vouchers, Gifts $gifts, BannedLinks $bannedLinks, ChandlerGroups $chandlerGroups, Audios $audios) function __construct(Users $users, Clubs $clubs, Vouchers $vouchers, Gifts $gifts, BannedLinks $bannedLinks, ChandlerGroups $chandlerGroups)
{ {
$this->users = $users; $this->users = $users;
$this->clubs = $clubs; $this->clubs = $clubs;
@ -37,9 +24,8 @@ final class AdminPresenter extends OpenVKPresenter
$this->gifts = $gifts; $this->gifts = $gifts;
$this->bannedLinks = $bannedLinks; $this->bannedLinks = $bannedLinks;
$this->chandlerGroups = $chandlerGroups; $this->chandlerGroups = $chandlerGroups;
$this->audios = $audios;
$this->logs = DatabaseConnection::i()->getContext()->table("ChandlerLogs"); $this->logs = DatabaseConnection::i()->getContext()->table("ChandlerLogs");
parent::__construct(); parent::__construct();
} }
@ -48,13 +34,6 @@ 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)
{ {
@ -64,15 +43,6 @@ final class AdminPresenter extends OpenVKPresenter
$count = $repo->find($query)->size(); $count = $repo->find($query)->size();
return $repo->find($query)->page($page, 20); return $repo->find($query)->page($page, 20);
} }
private function searchPlaylists(&$count)
{
$query = $this->queryParam("q") ?? "";
$page = (int) ($this->queryParam("p") ?? 1);
$count = $this->audios->findPlaylists($query)->size();
return $this->audios->findPlaylists($query)->page($page, 20);
}
function onStartup(): void function onStartup(): void
{ {
@ -83,7 +53,7 @@ final class AdminPresenter extends OpenVKPresenter
function renderIndex(): void function renderIndex(): void
{ {
$this->warnIfLongpoolBroken();
} }
function renderUsers(): void function renderUsers(): void
@ -117,10 +87,8 @@ final class AdminPresenter extends OpenVKPresenter
if($user->onlineStatus() != $this->postParam("online")) $user->setOnline(intval($this->postParam("online"))); if($user->onlineStatus() != $this->postParam("online")) $user->setOnline(intval($this->postParam("online")));
$user->setVerified(empty($this->postParam("verify") ? 0 : 1)); $user->setVerified(empty($this->postParam("verify") ? 0 : 1));
if($this->postParam("add-to-group")) { if($this->postParam("add-to-group")) {
if (!(new ChandlerGroups)->isUserAMember($user->getChandlerGUID(), $this->postParam("add-to-group"))) { $query = "INSERT INTO `ChandlerACLRelations` (`user`, `group`) VALUES ('" . $user->getChandlerGUID() . "', '" . $this->postParam("add-to-group") . "')";
$query = "INSERT INTO `ChandlerACLRelations` (`user`, `group`) VALUES ('" . $user->getChandlerGUID() . "', '" . $this->postParam("add-to-group") . "')"; DatabaseConnection::i()->getConnection()->query($query);
DatabaseConnection::i()->getConnection()->query($query);
}
} }
if($this->postParam("password")) { if($this->postParam("password")) {
$user->getChandlerUser()->updatePassword($this->postParam("password")); $user->getChandlerUser()->updatePassword($this->postParam("password"));
@ -161,7 +129,6 @@ 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":
@ -316,7 +283,7 @@ final class AdminPresenter extends OpenVKPresenter
$this->notFound(); $this->notFound();
$gift->delete(); $gift->delete();
$this->flashFail("succ", tr("admin_gift_moved_successfully"), tr("admin_gift_moved_to_recycle")); $this->flashFail("succ", "Gift moved successfully", "This gift will now be in <b>Recycle Bin</b>.");
break; break;
case "copy": case "copy":
case "move": case "move":
@ -335,7 +302,7 @@ final class AdminPresenter extends OpenVKPresenter
$catTo->addGift($gift); $catTo->addGift($gift);
$name = $catTo->getName(); $name = $catTo->getName();
$this->flash("succ", tr("admin_gift_moved_successfully"), "This gift will now be in <b>$name</b>."); $this->flash("succ", "Gift moved successfully", "This gift will now be in <b>$name</b>.");
$this->redirect("/admin/gifts/" . $catTo->getSlug() . "." . $catTo->getId() . "/"); $this->redirect("/admin/gifts/" . $catTo->getSlug() . "." . $catTo->getId() . "/");
break; break;
default: default:
@ -366,10 +333,10 @@ final class AdminPresenter extends OpenVKPresenter
$gift->setUsages((int) $this->postParam("usages")); $gift->setUsages((int) $this->postParam("usages"));
if(isset($_FILES["pic"]) && $_FILES["pic"]["error"] === UPLOAD_ERR_OK) { if(isset($_FILES["pic"]) && $_FILES["pic"]["error"] === UPLOAD_ERR_OK) {
if(!$gift->setImage($_FILES["pic"]["tmp_name"])) if(!$gift->setImage($_FILES["pic"]["tmp_name"]))
$this->flashFail("err", tr("error_when_saving_gift"), tr("error_when_saving_gift_bad_image")); $this->flashFail("err", "Не удалось сохранить подарок", "Изображение подарка кривое.");
} else if($gen) { } else if($gen) {
# If there's no gift pic but it's newly created # If there's no gift pic but it's newly created
$this->flashFail("err", tr("error_when_saving_gift"), tr("error_when_saving_gift_no_image")); $this->flashFail("err", "Не удалось сохранить подарок", "Пожалуйста, загрузите изображение подарка.");
} }
$gift->save(); $gift->save();
@ -396,7 +363,7 @@ final class AdminPresenter extends OpenVKPresenter
if (str_contains($this->queryParam("reason"), "*")) if (str_contains($this->queryParam("reason"), "*"))
exit(json_encode([ "error" => "Incorrect reason" ])); exit(json_encode([ "error" => "Incorrect reason" ]));
$unban_time = strtotime($this->queryParam("date")) ?: "permanent"; $unban_time = strtotime($this->queryParam("date")) ?: NULL;
$user = $this->users->get($id); $user = $this->users->get($id);
if(!$user) if(!$user)
@ -611,54 +578,6 @@ final class AdminPresenter extends OpenVKPresenter
$this->redirect("/admin/users/id" . $user->getId()); $this->redirect("/admin/users/id" . $user->getId());
} }
function renderMusic(): void
{
$this->template->mode = in_array($this->queryParam("act"), ["audios", "playlists"]) ? $this->queryParam("act") : "audios";
if ($this->template->mode === "audios")
$this->template->audios = $this->searchResults($this->audios, $this->template->count);
else
$this->template->playlists = $this->searchPlaylists($this->template->count);
}
function renderEditMusic(int $audio_id): void
{
$audio = $this->audios->get($audio_id);
$this->template->audio = $audio;
try {
$this->template->owner = $audio->getOwner()->getId();
} catch(\Throwable $e) {
$this->template->owner = 1;
}
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$audio->setName($this->postParam("name"));
$audio->setPerformer($this->postParam("performer"));
$audio->setLyrics($this->postParam("text"));
$audio->setGenre($this->postParam("genre"));
$audio->setOwner((int) $this->postParam("owner"));
$audio->setExplicit(!empty($this->postParam("explicit")));
$audio->setDeleted(!empty($this->postParam("deleted")));
$audio->setWithdrawn(!empty($this->postParam("withdrawn")));
$audio->save();
}
}
function renderEditPlaylist(int $playlist_id): void
{
$playlist = $this->audios->getPlaylist($playlist_id);
$this->template->playlist = $playlist;
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$playlist->setName($this->postParam("name"));
$playlist->setDescription($this->postParam("description"));
$playlist->setCover_Photo_Id((int) $this->postParam("photo"));
$playlist->setOwner((int) $this->postParam("owner"));
$playlist->setDeleted(!empty($this->postParam("deleted")));
$playlist->save();
}
}
function renderLogs(): void function renderLogs(): void
{ {
$filter = []; $filter = [];
@ -689,8 +608,7 @@ final class AdminPresenter extends OpenVKPresenter
$this->template->obj_type = $obj_type; $this->template->obj_type = $obj_type;
} }
$logs = iterator_to_array((new Logs)->search($filter)); $this->template->logs = (new Logs)->search($filter);
$this->template->logs = $logs;
$this->template->object_types = (new Logs)->getTypes(); $this->template->object_types = (new Logs)->getTypes();
} }
} }

View file

@ -1,827 +0,0 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Entities\Audio;
use openvk\Web\Models\Entities\Club;
use openvk\Web\Models\Entities\Photo;
use openvk\Web\Models\Entities\Playlist;
use openvk\Web\Models\Repositories\Audios;
use openvk\Web\Models\Repositories\Clubs;
use openvk\Web\Models\Repositories\Users;
final class AudioPresenter extends OpenVKPresenter
{
private $audios;
protected $presenterName = "audios";
const MAX_AUDIO_SIZE = 25000000;
function __construct(Audios $audios)
{
$this->audios = $audios;
}
function renderPopular(): void
{
$this->renderList(NULL, "popular");
}
function renderNew(): void
{
$this->renderList(NULL, "new");
}
function renderList(?int $owner = NULL, ?string $mode = "list"): void
{
$this->assertUserLoggedIn();
$this->template->_template = "Audio/List.xml";
$page = (int)($this->queryParam("p") ?? 1);
$audios = [];
if ($mode === "list") {
$entity = NULL;
if ($owner < 0) {
$entity = (new Clubs)->get($owner * -1);
if (!$entity || $entity->isBanned())
$this->redirect("/audios" . $this->user->id);
$audios = $this->audios->getByClub($entity, $page, 10);
$audiosCount = $this->audios->getClubCollectionSize($entity);
} else {
$entity = (new Users)->get($owner);
if (!$entity || $entity->isDeleted() || $entity->isBanned())
$this->redirect("/audios" . $this->user->id);
if(!$entity->getPrivacyPermission("audios.read", $this->user->identity))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
$audios = $this->audios->getByUser($entity, $page, 10);
$audiosCount = $this->audios->getUserCollectionSize($entity);
}
if (!$entity)
$this->notFound();
$this->template->owner = $entity;
$this->template->ownerId = $owner;
$this->template->club = $owner < 0 ? $entity : NULL;
$this->template->isMy = ($owner > 0 && ($entity->getId() === $this->user->id));
$this->template->isMyClub = ($owner < 0 && $entity->canBeModifiedBy($this->user->identity));
} else if ($mode === "new") {
$audios = $this->audios->getNew();
$audiosCount = $audios->size();
} else if ($mode === "playlists") {
if($owner < 0) {
$entity = (new Clubs)->get(abs($owner));
if (!$entity || $entity->isBanned())
$this->redirect("/playlists" . $this->user->id);
$playlists = $this->audios->getPlaylistsByClub($entity, $page, OPENVK_DEFAULT_PER_PAGE);
$playlistsCount = $this->audios->getClubPlaylistsCount($entity);
} else {
$entity = (new Users)->get($owner);
if (!$entity || $entity->isDeleted() || $entity->isBanned())
$this->redirect("/playlists" . $this->user->id);
if(!$entity->getPrivacyPermission("audios.read", $this->user->identity))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
$playlists = $this->audios->getPlaylistsByUser($entity, $page, OPENVK_DEFAULT_PER_PAGE);
$playlistsCount = $this->audios->getUserPlaylistsCount($entity);
}
$this->template->playlists = iterator_to_array($playlists);
$this->template->playlistsCount = $playlistsCount;
$this->template->owner = $entity;
$this->template->ownerId = $owner;
$this->template->club = $owner < 0 ? $entity : NULL;
$this->template->isMy = ($owner > 0 && ($entity->getId() === $this->user->id));
$this->template->isMyClub = ($owner < 0 && $entity->canBeModifiedBy($this->user->identity));
} else if ($mode === 'alone_audio') {
$audios = [$this->template->alone_audio];
$audiosCount = 1;
$this->template->owner = $this->user->identity;
$this->template->ownerId = $this->user->id;
}
// $this->renderApp("owner=$owner");
if ($audios !== []) {
$this->template->audios = iterator_to_array($audios);
$this->template->audiosCount = $audiosCount;
}
$this->template->mode = $mode;
$this->template->page = $page;
if(in_array($mode, ["list", "new", "popular"]) && $this->user->identity && $page < 2)
$this->template->friendsAudios = $this->user->identity->getBroadcastList("all", true);
}
function renderEmbed(int $owner, int $id): void
{
$audio = $this->audios->getByOwnerAndVID($owner, $id);
if(!$audio) {
header("HTTP/1.1 404 Not Found");
exit("<b>" . tr("audio_embed_not_found") . ".</b>");
} else if($audio->isDeleted()) {
header("HTTP/1.1 410 Not Found");
exit("<b>" . tr("audio_embed_deleted") . ".</b>");
} else if($audio->isWithdrawn()) {
header("HTTP/1.1 451 Unavailable for legal reasons");
exit("<b>" . tr("audio_embed_withdrawn") . ".</b>");
} else if(!$audio->canBeViewedBy(NULL)) {
header("HTTP/1.1 403 Forbidden");
exit("<b>" . tr("audio_embed_forbidden") . ".</b>");
} else if(!$audio->isAvailable()) {
header("HTTP/1.1 425 Too Early");
exit("<b>" . tr("audio_embed_processing") . ".</b>");
}
$this->template->audio = $audio;
}
function renderUpload(): void
{
$this->assertUserLoggedIn();
$group = NULL;
$playlist = NULL;
$isAjax = $this->postParam("ajax", false) == 1;
if(!is_null($this->queryParam("gid")) && !is_null($this->queryParam("playlist"))) {
$this->flashFail("err", tr("forbidden"), tr("not_enough_permissions_comment"), null, $isAjax);
}
if(!is_null($this->queryParam("gid"))) {
$gid = (int) $this->queryParam("gid");
$group = (new Clubs)->get($gid);
if(!$group)
$this->flashFail("err", tr("forbidden"), tr("not_enough_permissions_comment"), null, $isAjax);
if(!$group->canUploadAudio($this->user->identity))
$this->flashFail("err", tr("forbidden"), tr("not_enough_permissions_comment"), null, $isAjax);
}
if(!is_null($this->queryParam("playlist"))) {
$playlist_id = (int)$this->queryParam("playlist");
$playlist = (new Audios)->getPlaylist($playlist_id);
if(!$playlist || $playlist->isDeleted())
$this->flashFail("err", tr("forbidden"), tr("not_enough_permissions_comment"), null, $isAjax);
if(!$playlist->canBeModifiedBy($this->user->identity))
$this->flashFail("err", tr("forbidden"), tr("not_enough_permissions_comment"), null, $isAjax);
$this->template->playlist = $playlist;
$this->template->owner = $playlist->getOwner();
}
$this->template->group = $group;
if($_SERVER["REQUEST_METHOD"] !== "POST")
return;
$upload = $_FILES["blob"];
if(isset($upload) && file_exists($upload["tmp_name"])) {
if($upload["size"] > self::MAX_AUDIO_SIZE)
$this->flashFail("err", tr("error"), tr("media_file_corrupted_or_too_large"), null, $isAjax);
} else {
$err = !isset($upload) ? 65536 : $upload["error"];
$err = str_pad(dechex($err), 9, "0", STR_PAD_LEFT);
$readableError = tr("error_generic");
switch($upload["error"]) {
default:
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
$readableError = tr("file_too_big");
break;
case UPLOAD_ERR_PARTIAL:
$readableError = tr("file_loaded_partially");
break;
case UPLOAD_ERR_NO_FILE:
$readableError = tr("file_not_uploaded");
break;
case UPLOAD_ERR_NO_TMP_DIR:
$readableError = "Missing a temporary folder.";
break;
case UPLOAD_ERR_CANT_WRITE:
case UPLOAD_ERR_EXTENSION:
$readableError = "Failed to write file to disk. ";
break;
}
$this->flashFail("err", tr("error"), $readableError . " " . tr("error_code", $err), null, $isAjax);
}
$performer = $this->postParam("performer");
$name = $this->postParam("name");
$lyrics = $this->postParam("lyrics");
$genre = empty($this->postParam("genre")) ? "Other" : $this->postParam("genre");
$nsfw = ($this->postParam("explicit") ?? "off") === "on";
$is_unlisted = ($this->postParam("unlisted") ?? "off") === "on";
if(empty($performer) || empty($name) || iconv_strlen($performer . $name) > 128) # FQN of audio must not be more than 128 chars
$this->flashFail("err", tr("error"), tr("error_insufficient_info"), null, $isAjax);
$audio = new Audio;
$audio->setOwner($this->user->id);
$audio->setName($name);
$audio->setPerformer($performer);
$audio->setLyrics(empty($lyrics) ? NULL : $lyrics);
$audio->setGenre($genre);
$audio->setExplicit($nsfw);
$audio->setUnlisted($is_unlisted);
try {
$audio->setFile($upload);
} catch(\DomainException $ex) {
$e = $ex->getMessage();
$this->flashFail("err", tr("error"), tr("media_file_corrupted_or_too_large") . " $e.", null, $isAjax);
} catch(\RuntimeException $ex) {
$this->flashFail("err", tr("error"), tr("ffmpeg_timeout"), null, $isAjax);
} catch(\BadMethodCallException $ex) {
$this->flashFail("err", tr("error"), "хз", null, $isAjax);
} catch(\Exception $ex) {
$this->flashFail("err", tr("error"), tr("ffmpeg_not_installed"), null, $isAjax);
}
$audio->save();
if($playlist) {
$playlist->add($audio);
} else {
$audio->add($group ?? $this->user->identity);
}
if(!$isAjax)
$this->redirect(is_null($group) ? "/audios" . $this->user->id : "/audios-" . $group->getId());
else {
$redirectLink = "/audios";
if(!is_null($group))
$redirectLink .= $group->getRealId();
else
$redirectLink .= $this->user->id;
if($playlist)
$redirectLink = "/playlist" . $playlist->getPrettyId();
$this->returnJson([
"success" => true,
"redirect_link" => $redirectLink,
]);
}
}
function renderAloneAudio(int $owner_id, int $audio_id): void
{
$this->assertUserLoggedIn();
$found_audio = $this->audios->get($audio_id);
if(!$found_audio || $found_audio->isDeleted() || !$found_audio->canBeViewedBy($this->user->identity)) {
$this->notFound();
}
$this->template->alone_audio = $found_audio;
$this->renderList(NULL, 'alone_audio');
}
function renderListen(int $id): void
{
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$this->assertNoCSRF();
if(is_null($this->user))
$this->returnJson(["success" => false]);
$audio = $this->audios->get($id);
if ($audio && !$audio->isDeleted() && !$audio->isWithdrawn()) {
if(!empty($this->postParam("playlist"))) {
$playlist = (new Audios)->getPlaylist((int)$this->postParam("playlist"));
if(!$playlist || $playlist->isDeleted() || !$playlist->canBeViewedBy($this->user->identity) || !$playlist->hasAudio($audio))
$playlist = NULL;
}
$listen = $audio->listen($this->user->identity, $playlist);
$returnArr = ["success" => $listen];
if($playlist)
$returnArr["new_playlists_listens"] = $playlist->getListens();
$this->returnJson($returnArr);
}
$this->returnJson(["success" => false]);
} else {
$this->redirect("/");
}
}
function renderSearch(): void
{
$this->redirect("/search?section=audios");
}
function renderNewPlaylist(): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction(true);
$owner = $this->user->id;
if ($this->requestParam("gid")) {
$club = (new Clubs)->get((int) abs((int)$this->requestParam("gid")));
if (!$club || $club->isBanned() || !$club->canBeModifiedBy($this->user->identity))
$this->redirect("/audios" . $this->user->id);
$owner = ($club->getId() * -1);
$this->template->club = $club;
}
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$title = $this->postParam("title");
$description = $this->postParam("description");
$is_unlisted = (int)$this->postParam('is_unlisted');
$is_ajax = (int)$this->postParam('ajax') == 1;
$audios = array_slice(explode(",", $this->postParam("audios")), 0, 1000);
if(empty($title) || iconv_strlen($title) < 1)
$this->flashFail("err", tr("error"), tr("set_playlist_name"), NULL, $is_ajax);
$playlist = new Playlist;
$playlist->setOwner($owner);
$playlist->setName(substr($title, 0, 125));
$playlist->setDescription(substr($description, 0, 2045));
if($is_unlisted == 1)
$playlist->setUnlisted(true);
if($_FILES["cover"]["error"] === UPLOAD_ERR_OK) {
if(!str_starts_with($_FILES["cover"]["type"], "image"))
$this->flashFail("err", tr("error"), tr("not_a_photo"), NULL, $is_ajax);
try {
$playlist->fastMakeCover($this->user->id, $_FILES["cover"]);
} catch(\Throwable $e) {
$this->flashFail("err", tr("error"), tr("invalid_cover_photo"), NULL, $is_ajax);
}
}
$playlist->save();
foreach($audios as $audio) {
$audio = $this->audios->get((int)$audio);
if(!$audio || $audio->isDeleted())
continue;
$playlist->add($audio);
}
$playlist->bookmark(isset($club) ? $club : $this->user->identity);
if($is_ajax) {
$this->returnJson([
'success' => true,
'redirect' => '/playlist' . $owner . "_" . $playlist->getId()
]);
}
$this->redirect("/playlist" . $owner . "_" . $playlist->getId());
} else {
$this->template->owner = $owner;
}
}
function renderPlaylistAction(int $id) {
$this->assertUserLoggedIn();
$this->willExecuteWriteAction(true);
$this->assertNoCSRF();
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
header("HTTP/1.1 405 Method Not Allowed");
$this->redirect("/");
}
$playlist = $this->audios->getPlaylist($id);
if(!$playlist || $playlist->isDeleted())
$this->flashFail("err", "error", tr("invalid_playlist"), null, true);
switch ($this->queryParam("act")) {
case "bookmark":
if(!$playlist->isBookmarkedBy($this->user->identity))
$playlist->bookmark($this->user->identity);
else
$this->flashFail("err", "error", tr("playlist_already_bookmarked"), null, true);
break;
case "unbookmark":
if($playlist->isBookmarkedBy($this->user->identity))
$playlist->unbookmark($this->user->identity);
else
$this->flashFail("err", "error", tr("playlist_not_bookmarked"), null, true);
break;
case "delete":
if($playlist->canBeModifiedBy($this->user->identity)) {
$tmOwner = $playlist->getOwner();
$playlist->delete();
} else
$this->flashFail("err", "error", tr("access_denied"), null, true);
$this->returnJson(["success" => true, "id" => $tmOwner->getRealId()]);
break;
default:
break;
}
$this->returnJson(["success" => true]);
}
function renderEditPlaylist(int $owner_id, int $virtual_id)
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
$playlist = $this->audios->getPlaylistByOwnerAndVID($owner_id, $virtual_id);
if (!$playlist || $playlist->isDeleted() || !$playlist->canBeModifiedBy($this->user->identity))
$this->notFound();
$this->template->playlist = $playlist;
$audios = iterator_to_array($playlist->fetch(1, $playlist->size()));
$this->template->audios = array_slice($audios, 0, 1000);
$this->template->ownerId = $owner_id;
$this->template->owner = $playlist->getOwner();
if($_SERVER["REQUEST_METHOD"] !== "POST")
return;
$is_ajax = (int)$this->postParam('ajax') == 1;
$title = $this->postParam("title");
$description = $this->postParam("description");
$is_unlisted = (int)$this->postParam('is_unlisted');
$new_audios = !empty($this->postParam("audios")) ? explode(",", rtrim($this->postParam("audios"), ",")) : [];
if(empty($title) || iconv_strlen($title) < 1)
$this->flashFail("err", tr("error"), tr("set_playlist_name"));
$playlist->setName(ovk_proc_strtr($title, 125));
$playlist->setDescription(ovk_proc_strtr($description, 2045));
$playlist->setEdited(time());
$playlist->resetLength();
$playlist->setUnlisted((bool)$is_unlisted);
if($_FILES["cover"]["error"] === UPLOAD_ERR_OK) {
if(!str_starts_with($_FILES["cover"]["type"], "image"))
$this->flashFail("err", tr("error"), tr("not_a_photo"));
try {
$playlist->fastMakeCover($this->user->id, $_FILES["cover"]);
} catch(\Throwable $e) {
$this->flashFail("err", tr("error"), tr("invalid_cover_photo"));
}
}
$playlist->save();
DatabaseConnection::i()->getContext()->table("playlist_relations")->where([
"collection" => $playlist->getId()
])->delete();
foreach ($new_audios as $new_audio) {
$audio = (new Audios)->get((int)$new_audio);
if(!$audio || $audio->isDeleted())
continue;
$playlist->add($audio);
}
if($is_ajax) {
$this->returnJson([
'success' => true,
'redirect' => '/playlist' . $playlist->getPrettyId()
]);
}
$this->redirect("/playlist".$playlist->getPrettyId());
}
function renderPlaylist(int $owner_id, int $virtual_id): void
{
$this->assertUserLoggedIn();
$playlist = $this->audios->getPlaylistByOwnerAndVID($owner_id, $virtual_id);
$page = (int)($this->queryParam("p") ?? 1);
if (!$playlist || $playlist->isDeleted())
$this->notFound();
$this->template->playlist = $playlist;
$this->template->page = $page;
$this->template->cover = $playlist->getCoverPhoto();
$this->template->cover_url = $this->template->cover ? $this->template->cover->getURL() : "/assets/packages/static/openvk/img/song.jpg";
$this->template->audios = iterator_to_array($playlist->fetch($page, 10));
$this->template->ownerId = $owner_id;
$this->template->owner = $playlist->getOwner();
$this->template->isBookmarked = $this->user->identity && $playlist->isBookmarkedBy($this->user->identity);
$this->template->isMy = $this->user->identity && $playlist->getOwner()->getId() === $this->user->id;
$this->template->canEdit = $this->user->identity && $playlist->canBeModifiedBy($this->user->identity);
$this->template->count = $playlist->size();
}
function renderAction(int $audio_id): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction(true);
$this->assertNoCSRF();
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
header("HTTP/1.1 405 Method Not Allowed");
$this->redirect("/");
}
$audio = $this->audios->get($audio_id);
if(!$audio || $audio->isDeleted())
$this->flashFail("err", "error", tr("invalid_audio"), null, true);
switch ($this->queryParam("act")) {
case "add":
if($audio->isWithdrawn())
$this->flashFail("err", "error", tr("invalid_audio"), null, true);
if(!$audio->isInLibraryOf($this->user->identity))
$audio->add($this->user->identity);
else
$this->flashFail("err", "error", tr("do_have_audio"), null, true);
break;
case "remove":
if($audio->isInLibraryOf($this->user->identity))
$audio->remove($this->user->identity);
else
$this->flashFail("err", "error", tr("do_not_have_audio"), null, true);
break;
case "remove_club":
$club = (new Clubs)->get((int)$this->postParam("club"));
if(!$club || !$club->canBeModifiedBy($this->user->identity))
$this->flashFail("err", "error", tr("access_denied"), null, true);
if($audio->isInLibraryOf($club))
$audio->remove($club);
else
$this->flashFail("err", "error", tr("group_hasnt_audio"), null, true);
break;
case "add_to_club":
$detailed = [];
if($audio->isWithdrawn())
$this->flashFail("err", "error", tr("invalid_audio"), null, true);
if(empty($this->postParam("clubs")))
$this->flashFail("err", "error", 'clubs not passed', null, true);
$clubs_arr = explode(',', $this->postParam("clubs"));
$count = sizeof($clubs_arr);
if($count < 1 || $count > 10) {
$this->flashFail("err", "error", tr('too_many_or_to_lack'), null, true);
}
foreach($clubs_arr as $club_id) {
$club = (new Clubs)->get((int)$club_id);
if(!$club || !$club->canBeModifiedBy($this->user->identity))
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;
case "delete":
if($audio->canBeModifiedBy($this->user->identity))
$audio->delete();
else
$this->flashFail("err", "error", tr("access_denied"), null, true);
break;
case "edit":
$audio = $this->audios->get($audio_id);
if (!$audio || $audio->isDeleted() || $audio->isWithdrawn())
$this->flashFail("err", "error", tr("invalid_audio"), null, true);
if ($audio->getOwner()->getId() !== $this->user->id)
$this->flashFail("err", "error", tr("access_denied"), null, true);
$performer = $this->postParam("performer");
$name = $this->postParam("name");
$lyrics = $this->postParam("lyrics");
$genre = empty($this->postParam("genre")) ? "undefined" : $this->postParam("genre");
$nsfw = (int)($this->postParam("explicit") ?? 0) === 1;
$unlisted = (int)($this->postParam("unlisted") ?? 0) === 1;
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, true);
$audio->setName($name);
$audio->setPerformer($performer);
$audio->setLyrics(empty($lyrics) ? NULL : $lyrics);
$audio->setGenre($genre);
$audio->setExplicit($nsfw);
$audio->setSearchability($unlisted);
$audio->setEdited(time());
$audio->save();
$this->returnJson(["success" => true, "new_info" => [
"name" => ovk_proc_strtr($audio->getTitle(), 40),
"performer" => ovk_proc_strtr($audio->getPerformer(), 40),
"lyrics" => nl2br($audio->getLyrics() ?? ""),
"lyrics_unformatted" => $audio->getLyrics() ?? "",
"explicit" => $audio->isExplicit(),
"genre" => $audio->getGenre(),
"unlisted" => $audio->isUnlisted(),
]]);
break;
default:
break;
}
$this->returnJson(["success" => true]);
}
function renderPlaylists(int $owner)
{
$this->renderList($owner, "playlists");
}
function renderApiGetContext()
{
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
header("HTTP/1.1 405 Method Not Allowed");
$this->redirect("/");
}
$ctx_type = $this->postParam("context");
$ctx_id = (int)($this->postParam("context_entity"));
$page = (int)($this->postParam("page") ?? 1);
$perPage = 10;
switch($ctx_type) {
default:
case "entity_audios":
if($ctx_id >= 0) {
$entity = $ctx_id != 0 ? (new Users)->get($ctx_id) : $this->user->identity;
if(!$entity || !$entity->getPrivacyPermission("audios.read", $this->user->identity))
$this->flashFail("err", "Error", "Can't get queue", 80, true);
$audios = $this->audios->getByUser($entity, $page, $perPage);
$audiosCount = $this->audios->getUserCollectionSize($entity);
} else {
$entity = (new Clubs)->get(abs($ctx_id));
if(!$entity || $entity->isBanned())
$this->flashFail("err", "Error", "Can't get queue", 80, true);
$audios = $this->audios->getByClub($entity, $page, $perPage);
$audiosCount = $this->audios->getClubCollectionSize($entity);
}
break;
case "new_audios":
$audios = $this->audios->getNew();
$audiosCount = $audios->size();
break;
case "popular_audios":
$audios = $this->audios->getPopular();
$audiosCount = $audios->size();
break;
case "playlist_context":
$playlist = $this->audios->getPlaylist($ctx_id);
if (!$playlist || $playlist->isDeleted())
$this->flashFail("err", "Error", "Can't get queue", 80, true);
$audios = $playlist->fetch($page, 10);
$audiosCount = $playlist->size();
break;
case "search_context":
$stream = $this->audios->search($this->postParam("query"), 2, $this->postParam("type") === "by_performer");
$audios = $stream->page($page, 10);
$audiosCount = $stream->size();
break;
case "classic_search_context":
$data = json_decode($this->postParam("context_entity"), true);
$params = [];
$order = [
"type" => $data['order'] ?? 'id',
"invert" => (int)$data['invert'] == 1 ? true : false
];
if($data['genre'] && $data['genre'] != 'any')
$params['genre'] = $data['genre'];
if($data['only_performers'] && (int)$data['only_performers'] == 1)
$params['only_performers'] = '1';
if($data['with_lyrics'] && (int)$data['with_lyrics'] == 1)
$params['with_lyrics'] = '1';
$stream = $this->audios->find($data['query'], $params, $order);
$audios = $stream->page($page, 10);
$audiosCount = $stream->size();
break;
case 'alone_audio':
$found_audio = $this->audios->get($ctx_id);
if(!$found_audio || $found_audio->isDeleted() || !$found_audio->canBeViewedBy($this->user->identity)) {
$this->flashFail("err", "Error", "Not found", 89, true);
}
$audios = [$found_audio];
$audiosCount = 1;
break;
}
$pagesCount = ceil($audiosCount / $perPage);
# костылёк для получения плееров в пикере аудиозаписей
if((int)($this->postParam("returnPlayers")) === 1) {
$this->template->audios = $audios;
$this->template->page = $page;
$this->template->pagesCount = $pagesCount;
$this->template->count = $audiosCount;
return 0;
}
$audiosArr = [];
foreach($audios as $audio) {
$output_array = [];
$output_array['id'] = $audio->getId();
$output_array['name'] = $audio->getTitle();
$output_array['performer'] = $audio->getPerformer();
if(!$audio->isWithdrawn()) {
$output_array['keys'] = $audio->getKeys();
$output_array['url'] = $audio->getUrl();
}
$output_array['length'] = $audio->getLength();
$output_array['available'] = $audio->isAvailable();
$output_array['withdrawn'] = $audio->isWithdrawn();
$audiosArr[] = $output_array;
}
$resultArr = [
"success" => true,
"page" => $page,
"perPage" => $perPage,
"pagesCount" => $pagesCount,
"count" => $audiosCount,
"items" => $audiosArr,
];
$this->returnJson($resultArr);
}
}

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\{IP, User, PasswordReset, EmailVerification}; use openvk\Web\Models\Entities\{IP, User, PasswordReset, EmailVerification};
use openvk\Web\Models\Repositories\{Bans, IPs, Users, Restores, Verifications}; use openvk\Web\Models\Repositories\{Bans, IPs, Users, Restores, Verifications, Logs};
use openvk\Web\Models\Exceptions\InvalidUserNameException; use openvk\Web\Models\Exceptions\InvalidUserNameException;
use openvk\Web\Util\Validator; use openvk\Web\Util\Validator;
use Chandler\Session\Session; use Chandler\Session\Session;
@ -95,17 +95,7 @@ final class AuthPresenter extends OpenVKPresenter
$user = new User; $user = new User;
$user->setFirst_Name($this->postParam("first_name")); $user->setFirst_Name($this->postParam("first_name"));
$user->setLast_Name($this->postParam("last_name")); $user->setLast_Name($this->postParam("last_name"));
switch ($this->postParam("pronouns")) { $user->setSex((int)($this->postParam("sex") === "female"));
case 'male':
$user->setSex(0);
break;
case 'female':
$user->setSex(1);
break;
case 'neutral':
$user->setSex(2);
break;
}
$user->setEmail($this->postParam("email")); $user->setEmail($this->postParam("email"));
$user->setSince(date("Y-m-d H:i:s")); $user->setSince(date("Y-m-d H:i:s"));
$user->setRegistering_Ip(CONNECTING_IP); $user->setRegistering_Ip(CONNECTING_IP);
@ -140,6 +130,7 @@ final class AuthPresenter extends OpenVKPresenter
} }
$this->authenticator->authenticate($chUser->getId()); $this->authenticator->authenticate($chUser->getId());
(new Logs)->create($user->getId(), "profiles", "openvk\\Web\\Models\\Entities\\User", 0, $user, $user, $_SERVER["REMOTE_ADDR"], $_SERVER["HTTP_USER_AGENT"]);
$this->redirect("/id" . $user->getId()); $this->redirect("/id" . $user->getId());
$user->save(); $user->save();
} }
@ -228,7 +219,6 @@ final class AuthPresenter extends OpenVKPresenter
return; return;
} }
$this->template->disable_ajax = 1;
$this->template->is2faEnabled = $request->getUser()->is2faEnabled(); $this->template->is2faEnabled = $request->getUser()->is2faEnabled();
if($_SERVER["REQUEST_METHOD"] === "POST") { if($_SERVER["REQUEST_METHOD"] === "POST") {

View file

@ -18,8 +18,6 @@ final class BlobPresenter extends OpenVKPresenter
function renderFile(/*string*/ $dir, string $name, string $format) function renderFile(/*string*/ $dir, string $name, string $format)
{ {
header("Access-Control-Allow-Origin: *");
$dir = $this->getDirName($dir); $dir = $this->getDirName($dir);
$base = realpath(OPENVK_ROOT . "/storage/$dir"); $base = realpath(OPENVK_ROOT . "/storage/$dir");
$path = realpath(OPENVK_ROOT . "/storage/$dir/$name.$format"); $path = realpath(OPENVK_ROOT . "/storage/$dir/$name.$format");
@ -39,5 +37,5 @@ final class BlobPresenter extends OpenVKPresenter
readfile($path); readfile($path);
exit; exit;
} }
} }

View file

@ -2,7 +2,7 @@
namespace openvk\Web\Presenters; namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Comment, Notifications\MentionNotification, Photo, Video, User, Topic, Post}; use openvk\Web\Models\Entities\{Comment, Notifications\MentionNotification, Photo, Video, User, Topic, Post};
use openvk\Web\Models\Entities\Notifications\CommentNotification; use openvk\Web\Models\Entities\Notifications\CommentNotification;
use openvk\Web\Models\Repositories\{Comments, Clubs, Videos, Photos, Audios}; use openvk\Web\Models\Repositories\{Comments, Clubs};
final class CommentPresenter extends OpenVKPresenter final class CommentPresenter extends OpenVKPresenter
{ {
@ -27,12 +27,7 @@ final class CommentPresenter extends OpenVKPresenter
$this->flashFail("err", tr("error"), tr("forbidden")); $this->flashFail("err", tr("error"), tr("forbidden"));
if(!is_null($this->user)) $comment->toggleLike($this->user->identity); if(!is_null($this->user)) $comment->toggleLike($this->user->identity);
if($_SERVER["REQUEST_METHOD"] === "POST") {
$this->returnJson([
'success' => true,
]);
}
$this->redirect($_SERVER["HTTP_REFERER"]); $this->redirect($_SERVER["HTTP_REFERER"]);
} }
@ -48,10 +43,6 @@ final class CommentPresenter extends OpenVKPresenter
$entity = $repo->get($eId); $entity = $repo->get($eId);
if(!$entity) $this->notFound(); if(!$entity) $this->notFound();
if(!$entity->canBeViewedBy($this->user->identity)) {
$this->flashFail("err", tr("error"), tr("forbidden"));
}
if($entity instanceof Topic && $entity->isClosed()) if($entity instanceof Topic && $entity->isClosed())
$this->notFound(); $this->notFound();
@ -63,6 +54,9 @@ final class CommentPresenter extends OpenVKPresenter
if ($entity instanceof Post && $entity->getWallOwner()->isBanned()) if ($entity instanceof Post && $entity->getWallOwner()->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden")); $this->flashFail("err", tr("error"), tr("forbidden"));
if($_FILES["_vid_attachment"] && OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading'])
$this->flashFail("err", tr("error"), "Video uploads are disabled by the system administrator.");
$flags = 0; $flags = 0;
if($this->postParam("as_group") === "on" && !is_null($club) && $club->canBeModifiedBy($this->user->identity)) if($this->postParam("as_group") === "on" && !is_null($club) && $club->canBeModifiedBy($this->user->identity))
$flags |= 0b10000000; $flags |= 0b10000000;
@ -72,28 +66,31 @@ final class CommentPresenter extends OpenVKPresenter
try { try {
$photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"]); $photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"]);
} catch(ISE $ex) { } catch(ISE $ex) {
$this->flashFail("err", tr("error_when_publishing_comment"), tr("error_when_publishing_comment_description")); $this->flashFail("err", "Не удалось опубликовать пост", "Файл изображения повреждён, слишком велик или одна сторона изображения в разы больше другой.");
}
}
$horizontal_attachments = [];
$vertical_attachments = [];
if(!empty($this->postParam("horizontal_attachments"))) {
$horizontal_attachments_array = array_slice(explode(",", $this->postParam("horizontal_attachments")), 0, OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["maxAttachments"]);
if(sizeof($horizontal_attachments_array) > 0) {
$horizontal_attachments = parseAttachments($horizontal_attachments_array, ['photo', 'video']);
}
}
if(!empty($this->postParam("vertical_attachments"))) {
$vertical_attachments_array = array_slice(explode(",", $this->postParam("vertical_attachments")), 0, OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["maxAttachments"]);
if(sizeof($vertical_attachments_array) > 0) {
$vertical_attachments = parseAttachments($vertical_attachments_array, ['audio', 'note']);
} }
} }
if(empty($this->postParam("text")) && sizeof($horizontal_attachments) < 1 && sizeof($vertical_attachments) < 1) # TODO move to trait
$this->flashFail("err", tr("error_when_publishing_comment"), tr("error_comment_empty")); try {
$photo = NULL;
$video = NULL;
if($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) {
$album = NULL;
if($wall > 0 && $wall === $this->user->id)
$album = (new Albums)->getUserWallAlbum($wallOwner);
$photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"], $album);
}
if($_FILES["_vid_attachment"]["error"] === UPLOAD_ERR_OK) {
$video = Video::fastMake($this->user->id, $_FILES["_vid_attachment"]["name"], $this->postParam("text"), $_FILES["_vid_attachment"]);
}
} catch(ISE $ex) {
$this->flashFail("err", "Не удалось опубликовать комментарий", "Файл медиаконтента повреждён или слишком велик.");
}
if(empty($this->postParam("text")) && !$photo && !$video)
$this->flashFail("err", "Не удалось опубликовать комментарий", "Комментарий пустой или слишком большой.");
try { try {
$comment = new Comment; $comment = new Comment;
@ -105,24 +102,14 @@ final class CommentPresenter extends OpenVKPresenter
$comment->setFlags($flags); $comment->setFlags($flags);
$comment->save(); $comment->save();
} catch (\LengthException $ex) { } catch (\LengthException $ex) {
$this->flashFail("err", tr("error_when_publishing_comment"), tr("error_comment_too_big")); $this->flashFail("err", "Не удалось опубликовать комментарий", "Комментарий слишком большой.");
} }
foreach($horizontal_attachments as $horizontal_attachment) { if(!is_null($photo))
if(!$horizontal_attachment || $horizontal_attachment->isDeleted() || !$horizontal_attachment->canBeViewedBy($this->user->identity)) { $comment->attach($photo);
continue;
} if(!is_null($video))
$comment->attach($video);
$comment->attach($horizontal_attachment);
}
foreach($vertical_attachments as $vertical_attachment) {
if(!$vertical_attachment || $vertical_attachment->isDeleted() || !$vertical_attachment->canBeViewedBy($this->user->identity)) {
continue;
}
$comment->attach($vertical_attachment);
}
if($entity->getOwner()->getId() !== $this->user->identity->getId()) if($entity->getOwner()->getId() !== $this->user->identity->getId())
if(($owner = $entity->getOwner()) instanceof User) if(($owner = $entity->getOwner()) instanceof User)
@ -137,7 +124,7 @@ final class CommentPresenter extends OpenVKPresenter
if($mentionee instanceof User) if($mentionee instanceof User)
(new MentionNotification($mentionee, $entity, $comment->getOwner(), strip_tags($comment->getText())))->emit(); (new MentionNotification($mentionee, $entity, $comment->getOwner(), strip_tags($comment->getText())))->emit();
$this->flashFail("succ", tr("comment_is_added"), tr("comment_is_added_desc")); $this->flashFail("succ", "Комментарий добавлен", "Ваш комментарий появится на странице.");
} }
function renderDeleteComment(int $id): void function renderDeleteComment(int $id): void
@ -148,15 +135,15 @@ final class CommentPresenter extends OpenVKPresenter
$comment = (new Comments)->get($id); $comment = (new Comments)->get($id);
if(!$comment) $this->notFound(); if(!$comment) $this->notFound();
if(!$comment->canBeDeletedBy($this->user->identity)) if(!$comment->canBeDeletedBy($this->user->identity))
$this->throwError(403, "Forbidden", tr("error_access_denied")); $this->throwError(403, "Forbidden", "У вас недостаточно прав чтобы редактировать этот ресурс.");
if ($comment->getTarget() instanceof Post && $comment->getTarget()->getWallOwner()->isBanned()) if ($comment->getTarget() instanceof Post && $comment->getTarget()->getWallOwner()->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden")); $this->flashFail("err", tr("error"), tr("forbidden"));
$comment->delete(); $comment->delete();
$this->flashFail( $this->flashFail(
"succ", "succ",
tr("success"), "Успешно",
tr("comment_will_not_appear") "Этот комментарий больше не будет показыватся.<br/><a href='/al_comments/spam?$id'>Отметить как спам</a>?"
); );
} }
} }

View file

@ -20,12 +20,9 @@ final class GiftsPresenter extends OpenVKPresenter
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
$user = $this->users->get($user); $user = $this->users->get($user);
if(!$user || $user->isDeleted()) if(!$user)
$this->notFound(); $this->notFound();
if(!$user->canBeViewedBy($this->user->identity ?? NULL))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
$this->template->user = $user; $this->template->user = $user;
$this->template->page = $page = (int) ($this->queryParam("p") ?? 1); $this->template->page = $page = (int) ($this->queryParam("p") ?? 1);
$this->template->count = $user->getGiftCount(); $this->template->count = $user->getGiftCount();
@ -44,7 +41,6 @@ final class GiftsPresenter extends OpenVKPresenter
$this->template->user = $user; $this->template->user = $user;
$this->template->iterator = $cats; $this->template->iterator = $cats;
$this->template->count = $this->gifts->getCategoriesCount();
$this->template->_template = "Gifts/Menu.xml"; $this->template->_template = "Gifts/Menu.xml";
} }
@ -53,10 +49,7 @@ final class GiftsPresenter extends OpenVKPresenter
$user = $this->users->get((int) ($this->queryParam("user") ?? 0)); $user = $this->users->get((int) ($this->queryParam("user") ?? 0));
$cat = $this->gifts->getCat((int) ($this->queryParam("pack") ?? 0)); $cat = $this->gifts->getCat((int) ($this->queryParam("pack") ?? 0));
if(!$user || !$cat) if(!$user || !$cat)
$this->flashFail("err", tr("error_when_gifting"), tr("error_user_not_exists")); $this->flashFail("err", "Не удалось подарить", "Пользователь или набор не существуют.");
if(!$user->canBeViewedBy($this->user->identity))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
$this->template->page = $page = (int) ($this->queryParam("p") ?? 1); $this->template->page = $page = (int) ($this->queryParam("p") ?? 1);
$gifts = $cat->getGifts($page, null, $this->template->count); $gifts = $cat->getGifts($page, null, $this->template->count);
@ -73,17 +66,14 @@ final class GiftsPresenter extends OpenVKPresenter
$gift = $this->gifts->get((int) ($this->queryParam("elid") ?? 0)); $gift = $this->gifts->get((int) ($this->queryParam("elid") ?? 0));
$cat = $this->gifts->getCat((int) ($this->queryParam("pack") ?? 0)); $cat = $this->gifts->getCat((int) ($this->queryParam("pack") ?? 0));
if(!$user || !$cat || !$gift || !$cat->hasGift($gift)) if(!$user || !$cat || !$gift || !$cat->hasGift($gift))
$this->flashFail("err", tr("error_when_gifting"), tr("error_no_rights_gifts")); $this->flashFail("err", "Не удалось подарить", "Не удалось подтвердить права на подарок.");
if(!$gift->canUse($this->user->identity)) if(!$gift->canUse($this->user->identity))
$this->flashFail("err", tr("error_when_gifting"), tr("error_no_more_gifts")); $this->flashFail("err", "Не удалось подарить", "У вас больше не осталось таких подарков.");
if(!$user->canBeViewedBy($this->user->identity ?? NULL))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
$coinsLeft = $this->user->identity->getCoins() - $gift->getPrice(); $coinsLeft = $this->user->identity->getCoins() - $gift->getPrice();
if($coinsLeft < 0) if($coinsLeft < 0)
$this->flashFail("err", tr("error_when_gifting"), tr("error_no_money")); $this->flashFail("err", "Не удалось подарить", "Ору нищ не пук.");
$this->template->_template = "Gifts/Confirm.xml"; $this->template->_template = "Gifts/Confirm.xml";
if($_SERVER["REQUEST_METHOD"] !== "POST") { if($_SERVER["REQUEST_METHOD"] !== "POST") {
@ -101,7 +91,7 @@ final class GiftsPresenter extends OpenVKPresenter
$user->gift($this->user->identity, $gift, $comment, !is_null($this->postParam("anonymous"))); $user->gift($this->user->identity, $gift, $comment, !is_null($this->postParam("anonymous")));
$gift->used(); $gift->used();
$this->flash("succ", tr("gift_sent"), tr("gift_sent_desc", $user->getFirstName(), $gift->getPrice())); $this->flash("succ", "Подарок отправлен", "Вы отправили подарок <b>" . $user->getFirstName() . "</b> за " . $gift->getPrice() . " голосов.");
$this->redirect($user->getURL()); $this->redirect($user->getURL());
} }

View file

@ -3,7 +3,7 @@ namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Club, Photo, Post}; use openvk\Web\Models\Entities\{Club, Photo, Post};
use Nette\InvalidStateException; use Nette\InvalidStateException;
use openvk\Web\Models\Entities\Notifications\ClubModeratorNotification; use openvk\Web\Models\Entities\Notifications\ClubModeratorNotification;
use openvk\Web\Models\Repositories\{Clubs, Users, Albums, Managers, Topics, Audios, Posts}; use openvk\Web\Models\Repositories\{Clubs, Users, Albums, Managers, Topics};
use Chandler\Security\Authenticator; use Chandler\Security\Authenticator;
final class GroupPresenter extends OpenVKPresenter final class GroupPresenter extends OpenVKPresenter
@ -31,19 +31,9 @@ final class GroupPresenter extends OpenVKPresenter
$this->template->albumsCount = (new Albums)->getClubAlbumsCount($club); $this->template->albumsCount = (new Albums)->getClubAlbumsCount($club);
$this->template->topics = (new Topics)->getLastTopics($club, 3); $this->template->topics = (new Topics)->getLastTopics($club, 3);
$this->template->topicsCount = (new Topics)->getClubTopicsCount($club); $this->template->topicsCount = (new Topics)->getClubTopicsCount($club);
$this->template->audios = (new Audios)->getRandomThreeAudiosByEntityId($club->getRealId());
$this->template->audiosCount = (new Audios)->getClubCollectionSize($club);
}
if(!is_null($this->user->identity) && $club->getWallType() == 2) {
if(!$club->canBeModifiedBy($this->user->identity))
$this->template->suggestedPostsCountByUser = (new Posts)->getSuggestedPostsCountByUser($club->getId(), $this->user->id);
else
$this->template->suggestedPostsCountByEveryone = (new Posts)->getSuggestedPostsCount($club->getId());
} }
$this->template->club = $club; $this->template->club = $club;
$this->template->ignore_status = $club->isIgnoredBy($this->user->identity);
} }
} }
@ -64,7 +54,7 @@ final class GroupPresenter extends OpenVKPresenter
$club->save(); $club->save();
} catch(\PDOException $ex) { } catch(\PDOException $ex) {
if($ex->getCode() == 23000) if($ex->getCode() == 23000)
$this->flashFail("err", tr("error"), tr("error_on_server_side")); $this->flashFail("err", "Ошибка", "Произошла ошибка на стороне сервера. Обратитесь к системному администратору.");
else else
throw $ex; throw $ex;
} }
@ -72,7 +62,7 @@ final class GroupPresenter extends OpenVKPresenter
$club->toggleSubscription($this->user->identity); $club->toggleSubscription($this->user->identity);
$this->redirect("/club" . $club->getId()); $this->redirect("/club" . $club->getId());
}else{ }else{
$this->flashFail("err", tr("error"), tr("error_no_group_name")); $this->flashFail("err", "Ошибка", "Вы не ввели название группы.");
} }
} }
} }
@ -142,7 +132,7 @@ final class GroupPresenter extends OpenVKPresenter
$this->notFound(); $this->notFound();
if(!$club->canBeModifiedBy($this->user->identity ?? NULL)) if(!$club->canBeModifiedBy($this->user->identity ?? NULL))
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); $this->flashFail("err", "Ошибка доступа", "У вас недостаточно прав, чтобы изменять этот ресурс.");
if(!is_null($hidden)) { if(!is_null($hidden)) {
if($club->getOwner()->getId() == $user->getId()) { if($club->getOwner()->getId() == $user->getId()) {
@ -160,9 +150,9 @@ final class GroupPresenter extends OpenVKPresenter
} }
if($hidden) { if($hidden) {
$this->flashFail("succ", tr("success_action"), tr("x_is_now_hidden", $user->getCanonicalName())); $this->flashFail("succ", "Операция успешна", "Теперь " . $user->getCanonicalName() . " будет показываться как обычный подписчик всем кроме других администраторов");
} else { } else {
$this->flashFail("succ", tr("success_action"), tr("x_is_now_showed", $user->getCanonicalName())); $this->flashFail("succ", "Операция успешна", "Теперь все будут знать про то что " . $user->getCanonicalName() . " - администратор");
} }
} elseif($removeComment) { } elseif($removeComment) {
if($club->getOwner()->getId() == $user->getId()) { if($club->getOwner()->getId() == $user->getId()) {
@ -174,11 +164,11 @@ final class GroupPresenter extends OpenVKPresenter
$manager->save(); $manager->save();
} }
$this->flashFail("succ", tr("success_action"), tr("comment_is_deleted")); $this->flashFail("succ", "Операция успешна", "Комментарий к администратору удален");
} elseif($comment) { } elseif($comment) {
if(mb_strlen($comment) > 36) { if(mb_strlen($comment) > 36) {
$commentLength = (string) mb_strlen($comment); $commentLength = (string) mb_strlen($comment);
$this->flashFail("err", tr("error"), tr("comment_is_too_long", $commentLength)); $this->flashFail("err", "Ошибка", "Комментарий слишком длинный ($commentLength символов вместо 36 символов)");
} }
if($club->getOwner()->getId() == $user->getId()) { if($club->getOwner()->getId() == $user->getId()) {
@ -190,16 +180,16 @@ final class GroupPresenter extends OpenVKPresenter
$manager->save(); $manager->save();
} }
$this->flashFail("succ", tr("success_action"), tr("comment_is_changed")); $this->flashFail("succ", "Операция успешна", "Комментарий к администратору изменён");
}else{ }else{
if($club->canBeModifiedBy($user)) { if($club->canBeModifiedBy($user)) {
$club->removeManager($user); $club->removeManager($user);
$this->flashFail("succ", tr("success_action"), tr("x_no_more_admin", $user->getCanonicalName())); $this->flashFail("succ", "Операция успешна", $user->getCanonicalName() . " более не администратор.");
} else { } else {
$club->addManager($user); $club->addManager($user);
(new ClubModeratorNotification($user, $club, $this->user->identity))->emit(); (new ClubModeratorNotification($user, $club, $this->user->identity))->emit();
$this->flashFail("succ", tr("success_action"), tr("x_is_admin", $user->getCanonicalName())); $this->flashFail("succ", "Операция успешна", $user->getCanonicalName() . " назначен(а) администратором.");
} }
} }
@ -224,20 +214,11 @@ final class GroupPresenter extends OpenVKPresenter
$club->setName((empty($this->postParam("name")) || mb_strlen(trim($this->postParam("name"))) === 0) ? $club->getName() : $this->postParam("name")); $club->setName((empty($this->postParam("name")) || mb_strlen(trim($this->postParam("name"))) === 0) ? $club->getName() : $this->postParam("name"));
$club->setAbout(empty($this->postParam("about")) ? NULL : $this->postParam("about")); $club->setAbout(empty($this->postParam("about")) ? NULL : $this->postParam("about"));
try { $club->setWall(empty($this->postParam("wall")) ? 0 : 1);
$club->setWall(empty($this->postParam("wall")) ? 0 : (int)$this->postParam("wall"));
} catch(\Exception $e) {
$this->flashFail("err", tr("error"), tr("error_invalid_wall_value"));
}
$club->setAdministrators_List_Display(empty($this->postParam("administrators_list_display")) ? 0 : $this->postParam("administrators_list_display")); $club->setAdministrators_List_Display(empty($this->postParam("administrators_list_display")) ? 0 : $this->postParam("administrators_list_display"));
$club->setEveryone_Can_Create_Topics(empty($this->postParam("everyone_can_create_topics")) ? 0 : 1); $club->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->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))
@ -264,7 +245,7 @@ final class GroupPresenter extends OpenVKPresenter
(new Albums)->getClubAvatarAlbum($club)->addPhoto($photo); (new Albums)->getClubAvatarAlbum($club)->addPhoto($photo);
} catch(ISE $ex) { } catch(ISE $ex) {
$name = $album->getName(); $name = $album->getName();
$this->flashFail("err", tr("error"), tr("error_when_uploading_photo")); $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию.");
} }
} }
@ -272,110 +253,59 @@ final class GroupPresenter extends OpenVKPresenter
$club->save(); $club->save();
} catch(\PDOException $ex) { } catch(\PDOException $ex) {
if($ex->getCode() == 23000) if($ex->getCode() == 23000)
$this->flashFail("err", tr("error"), tr("error_on_server_side")); $this->flashFail("err", "Ошибка", "Произошла ошибка на стороне сервера. Обратитесь к системному администратору.");
else else
throw $ex; throw $ex;
} }
$this->flash("succ", tr("changes_saved"), tr("new_changes_desc")); $this->flash("succ", "Изменения сохранены", "Новые данные появятся в вашей группе.");
} }
} }
function renderSetAvatar(int $id) function renderSetAvatar(int $id)
{ {
$this->assertUserLoggedIn(); $photo = new Photo;
$this->willExecuteWriteAction();
$club = $this->clubs->get($id); $club = $this->clubs->get($id);
if ($club->isBanned()) $this->flashFail("err", tr("error"), tr("forbidden"));
if(!$club || $club->isBanned() || !$club->canBeModifiedBy($this->user->identity)) if($_SERVER["REQUEST_METHOD"] === "POST" && $_FILES["ava"]["error"] === UPLOAD_ERR_OK) {
$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["blob"]); $photo->setFile($_FILES["ava"]);
$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);
if($this->postParam("on_wall") == 1) { $flags = 0;
$post = new Post; $flags |= 0b00010000;
$flags |= 0b10000000;
$post->setOwner($this->user->id);
$post->setWall($club->getId() * -1);
$post->setCreated(time());
$post->setContent("");
$flags = 0; $post = new Post;
$flags |= 0b00010000; $post->setOwner($this->user->id);
$flags |= 0b10000000; $post->setWall($club->getId()*-1);
$post->setCreated(time());
$post->setContent("");
$post->setFlags($flags);
$post->save();
$post->attach($photo);
$post->setFlags($flags); } catch(ISE $ex) {
$post->save(); $name = $album->getName();
$this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию.");
$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();
@ -420,7 +350,7 @@ final class GroupPresenter extends OpenVKPresenter
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
if(!eventdb()) if(!eventdb())
$this->flashFail("err", tr("connection_error"), tr("connection_error_desc")); $this->flashFail("err", "Ошибка подключения", "Не удалось подключится к службе телеметрии.");
$club = $this->clubs->get($id); $club = $this->clubs->get($id);
if(!$club->canBeModifiedBy($this->user->identity)) if(!$club->canBeModifiedBy($this->user->identity))
@ -481,37 +411,4 @@ final class GroupPresenter extends OpenVKPresenter
$this->flashFail("succ", tr("information_-1"), tr("group_owner_setted", $newOwner->getCanonicalName(), $club->getName())); $this->flashFail("succ", tr("information_-1"), tr("group_owner_setted", $newOwner->getCanonicalName(), $club->getName()));
} }
function renderSuggested(int $id): void
{
$this->assertUserLoggedIn();
$club = $this->clubs->get($id);
if(!$club)
$this->notFound();
else
$this->template->club = $club;
if($club->getWallType() == 0) {
$this->flash("err", tr("error_suggestions"), tr("error_suggestions_closed"));
$this->redirect("/club".$club->getId());
}
if($club->getWallType() == 1) {
$this->flash("err", tr("error_suggestions"), tr("error_suggestions_open"));
$this->redirect("/club".$club->getId());
}
if(!$club->canBeModifiedBy($this->user->identity)) {
$this->template->posts = iterator_to_array((new Posts)->getSuggestedPostsByUser($club->getId(), $this->user->id, (int) ($this->queryParam("p") ?? 1)));
$this->template->count = (new Posts)->getSuggestedPostsCountByUser($club->getId(), $this->user->id);
$this->template->type = "my";
} else {
$this->template->posts = iterator_to_array((new Posts)->getSuggestedPosts($club->getId(), (int) ($this->queryParam("p") ?? 1)));
$this->template->count = (new Posts)->getSuggestedPostsCount($club->getId());
$this->template->type = "everyone";
}
$this->template->page = (int) ($this->queryParam("p") ?? 1);
}
} }

View file

@ -1,6 +1,5 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\Web\Presenters; namespace openvk\Web\Presenters;
use openvk\Web\Models\Repositories\{Posts, Comments};
use MessagePack\MessagePack; use MessagePack\MessagePack;
use Chandler\Session\Session; use Chandler\Session\Session;
@ -96,72 +95,4 @@ final class InternalAPIPresenter extends OpenVKPresenter
]); ]);
} }
} }
function renderGetPhotosFromPost(int $owner_id, int $post_id) {
if($_SERVER["REQUEST_METHOD"] !== "POST") {
header("HTTP/1.1 405 Method Not Allowed");
exit("иди нахуй заебал");
}
if($this->postParam("parentType", false) == "post") {
$post = (new Posts)->getPostById($owner_id, $post_id, true);
} else {
$post = (new Comments)->get($post_id);
}
if(is_null($post)) {
$this->returnJson([
"success" => 0
]);
} else {
$response = [];
$attachments = $post->getChildren();
foreach($attachments as $attachment)
{
if($attachment instanceof \openvk\Web\Models\Entities\Photo)
{
$response[$attachment->getPrettyId()] = [
"url" => $attachment->getURLBySizeId('larger'),
"id" => $attachment->getPrettyId(),
];
}
}
$this->returnJson([
"success" => 1,
"body" => $response
]);
}
}
function renderGetPostTemplate(int $owner_id, int $post_id) {
if($_SERVER["REQUEST_METHOD"] !== "POST") {
header("HTTP/1.1 405 Method Not Allowed");
exit("ты‍ не по адресу");
}
$type = $this->queryParam("type", false);
if($type == "post") {
$post = (new Posts)->getPostById($owner_id, $post_id, true);
} else {
$post = (new Comments)->get($post_id);
}
if(!$post || !$post->canBeEditedBy($this->user->identity)) {
exit('');
}
header("Content-Type: text/plain");
if($type == 'post') {
$this->template->_template = 'components/post.xml';
$this->template->post = $post;
$this->template->commentSection = false;
} elseif($type == 'comment') {
$this->template->_template = 'components/comment.xml';
$this->template->comment = $post;
} else {
exit('');
}
}
} }

View file

@ -63,7 +63,6 @@ final class MessengerPresenter extends OpenVKPresenter
$this->flash("err", tr("warning"), tr("user_may_not_reply")); $this->flash("err", tr("warning"), tr("user_may_not_reply"));
} }
$this->template->disable_ajax = 1;
$this->template->selId = $sel; $this->template->selId = $sel;
$this->template->correspondent = $correspondent; $this->template->correspondent = $correspondent;
} }
@ -129,7 +128,7 @@ final class MessengerPresenter extends OpenVKPresenter
$messages = []; $messages = [];
$correspondence = new Correspondence($this->user->identity, $correspondent); $correspondence = new Correspondence($this->user->identity, $correspondent);
foreach($correspondence->getMessages(1, $lastMsg === 0 ? NULL : $lastMsg, NULL, 0) as $message) foreach($correspondence->getMessages(1, $lastMsg === 0 ? NULL : $lastMsg) as $message)
$messages[] = $message->simplify(); $messages[] = $message->simplify();
header("Content-Type: application/json"); header("Content-Type: application/json");

View file

@ -38,7 +38,6 @@ final class NoSpamPresenter extends OpenVKPresenter
if ($mode === "form") { if ($mode === "form") {
$this->template->_template = "NoSpam/Index"; $this->template->_template = "NoSpam/Index";
$this->template->disable_ajax = 1;
$foundClasses = []; $foundClasses = [];
foreach (Finder::findFiles('*.php')->from($targetDir) as $file) { foreach (Finder::findFiles('*.php')->from($targetDir) as $file) {
$content = file_get_contents($file->getPathname()); $content = file_get_contents($file->getPathname());
@ -68,7 +67,6 @@ final class NoSpamPresenter extends OpenVKPresenter
$this->template->models = $models; $this->template->models = $models;
} else if ($mode === "templates") { } else if ($mode === "templates") {
$this->template->_template = "NoSpam/Templates.xml"; $this->template->_template = "NoSpam/Templates.xml";
$this->template->disable_ajax = 1;
$filter = []; $filter = [];
if ($this->queryParam("id")) { if ($this->queryParam("id")) {
$filter["id"] = (int)$this->queryParam("id"); $filter["id"] = (int)$this->queryParam("id");
@ -179,25 +177,26 @@ final class NoSpamPresenter extends OpenVKPresenter
if ($conditions) { if ($conditions) {
$logs = $db->query("SELECT * FROM `ChandlerLogs` $whereStart $conditions GROUP BY `object_id`, `object_model`"); $logs = $db->query("SELECT * FROM `ChandlerLogs` $whereStart $conditions GROUP BY `object_id`, `object_model`");
foreach ($logs as $log) { if (!$where) {
$log = (new Logs)->get($log->id); foreach ($logs as $log) {
$object = $log->getObject()->unwrap(); $log = (new Logs)->get($log->id);
$response[] = $log->getObject()->unwrap();
}
} else {
foreach ($logs as $log) {
$log = (new Logs)->get($log->id);
$object = $log->getObject()->unwrap();
if (!$object) continue; if (!$object) continue;
if ($where) {
if (str_starts_with($where, " AND")) { if (str_starts_with($where, " AND")) {
$where = substr_replace($where, "", 0, strlen(" AND")); $where = substr_replace($where, "", 0, strlen(" AND"));
} }
$a = $db->query("SELECT * FROM `$table` WHERE $where")->fetchAll(); foreach ($db->query("SELECT * FROM `$table` WHERE $where")->fetchAll() as $o) {
foreach ($a as $o) { if ($object->id === $o["id"]) {
if ($object->id == $o["id"]) {
$response[] = $object; $response[] = $object;
} }
} }
} else {
$response[] = $object;
} }
} }
} }
@ -207,72 +206,70 @@ final class NoSpamPresenter extends OpenVKPresenter
} }
try { try {
$response = []; $response = [];
$processed = 0; $processed = 0;
$where = $this->postParam("where"); $where = $this->postParam("where");
$ip = addslashes($this->postParam("ip")); $ip = $this->postParam("ip");
$useragent = addslashes($this->postParam("useragent")); $useragent = $this->postParam("useragent");
$searchTerm = addslashes($this->postParam("q")); $searchTerm = $this->postParam("q");
$ts = (int)$this->postParam("ts"); $ts = (int)$this->postParam("ts");
$te = (int)$this->postParam("te"); $te = (int)$this->postParam("te");
$user = addslashes($this->postParam("user")); $user = $this->postParam("user");
if ($where) { if (!$ip && !$useragent && !$searchTerm && !$ts && !$te && !$where && !$searchTerm && !$user)
$where = explode(";", $where)[0]; $this->returnJson(["success" => false, "error" => "Нет запроса. Заполните поле \"подстрока\" или введите запрос \"WHERE\" в поле под ним."]);
$models = explode(",", $this->postParam("models"));
foreach ($models as $_model) {
$model_name = NoSpamPresenter::ENTITIES_NAMESPACE . "\\" . $_model;
if (!class_exists($model_name)) {
continue;
} }
if (!$ip && !$useragent && !$searchTerm && !$ts && !$te && !$where && !$searchTerm && !$user) $model = new $model_name;
$this->returnJson(["success" => false, "error" => "Нет запроса. Заполните поле \"подстрока\" или введите запрос \"WHERE\" в поле под ним."]);
$models = explode(",", $this->postParam("models")); $c = new \ReflectionClass($model_name);
if ($c->isAbstract() || $c->getName() == NoSpamPresenter::ENTITIES_NAMESPACE . "\\Correspondence") {
continue;
}
foreach ($models as $_model) { $db = DatabaseConnection::i()->getContext();
$model_name = NoSpamPresenter::ENTITIES_NAMESPACE . "\\" . $_model; $table = $model->getTableName();
if (!class_exists($model_name)) { $columns = $db->getStructure()->getColumns($table);
continue;
if ($searchTerm) {
$conditions = [];
$need_deleted = false;
foreach ($columns as $column) {
if ($column["name"] == "deleted") {
$need_deleted = true;
} else {
$conditions[] = "`$column[name]` REGEXP '$searchTerm'";
}
} }
$conditions = implode(" OR ", $conditions);
$model = new $model_name; $where = ($this->postParam("where") ? " AND ($conditions)" : "($conditions)");
if ($need_deleted) $where .= " AND (`deleted` = 0)";
}
$c = new \ReflectionClass($model_name); $rows = [];
if ($c->isAbstract() || $c->getName() == NoSpamPresenter::ENTITIES_NAMESPACE . "\\Correspondence") { if ($ip || $useragent || $ts || $te || $user) {
continue; $rows = searchByAdditionalParams($table, $where, $ip, $useragent, $ts, $te, $user);
} }
$db = DatabaseConnection::i()->getContext(); if (count($rows) === 0) {
$table = $model->getTableName(); if (!$searchTerm) {
$columns = $db->getStructure()->getColumns($table); if (str_starts_with($where, " AND")) {
if ($searchTerm && !$this->postParam("where")) {
if ($searchTerm) { $where = substr_replace($where, "", 0, strlen(" AND"));
$conditions = [];
$need_deleted = false;
foreach ($columns as $column) {
if ($column["name"] == "deleted") {
$need_deleted = true;
} else { } else {
$conditions[] = "`$column[name]` REGEXP '$searchTerm'"; $where = "(" . $this->postParam("where") . ")" . $where;
} }
} }
$conditions = implode(" OR ", $conditions);
$where = ($this->postParam("where") ? " AND ($conditions)" : "($conditions)");
if ($need_deleted) $where .= " AND (`deleted` = 0)";
}
$rows = [];
if (str_starts_with($where, " AND")) {
if ($searchTerm && !$this->postParam("where")) {
$where = substr_replace($where, "", 0, strlen(" AND"));
} else {
$where = "(" . $this->postParam("where") . ")" . $where;
}
}
if ($ip || $useragent || $ts || $te || $user) {
$rows = searchByAdditionalParams($table, $where, $ip, $useragent, $ts, $te, $user);
} else {
if (!$where) { if (!$where) {
$rows = []; $rows = [];
} else { } else {
@ -280,105 +277,99 @@ final class NoSpamPresenter extends OpenVKPresenter
$rows = $result->fetchAll(); $rows = $result->fetchAll();
} }
} }
if (!in_array((int)$this->postParam("ban"), [1, 2, 3])) {
foreach ($rows as $key => $object) {
$object = (array)$object;
$_obj = [];
foreach ($object as $key => $value) {
foreach ($columns as $column) {
if ($column["name"] === $key && in_array(strtoupper($column["nativetype"]), ["BLOB", "BINARY", "VARBINARY", "TINYBLOB", "MEDIUMBLOB", "LONGBLOB"])) {
$value = "[BINARY]";
break;
}
}
$_obj[$key] = $value;
$_obj["__model_name"] = $_model;
}
$response[] = $_obj;
}
} else {
$ids = [];
foreach ($rows as $object) {
$object = new $model_name($db->table($table)->get($object->id));
if (!$object) continue;
$ids[] = $object->getId();
}
$log = new NoSpamLog;
$log->setUser($this->user->id);
$log->setModel($_model);
if ($searchTerm) {
$log->setRegex($searchTerm);
} else {
$log->setRequest($where);
}
$log->setBan_Type((int)$this->postParam("ban"));
$log->setCount(count($rows));
$log->setTime(time());
$log->setItems(implode(",", $ids));
$log->save();
$banned_ids = [];
foreach ($rows as $object) {
$object = new $model_name($db->table($table)->get($object->id));
if (!$object) continue;
$owner = NULL;
$methods = ["getOwner", "getUser", "getRecipient", "getInitiator"];
if (method_exists($object, "ban")) {
$owner = $object;
} else {
foreach ($methods as $method) {
if (method_exists($object, $method)) {
$owner = $object->$method();
break;
}
}
}
if ($owner instanceof User && $owner->getId() === $this->user->id) {
if (count($rows) === 1) {
$this->returnJson(["success" => false, "error" => "\"Производственная травма\" — Вы не можете блокировать или удалять свой же контент"]);
} else {
continue;
}
}
if (in_array((int)$this->postParam("ban"), [2, 3])) {
$reason = mb_strlen(trim($this->postParam("ban_reason"))) > 0 ? addslashes($this->postParam("ban_reason")) : ("**content-noSpamTemplate-" . $log->getId() . "**");
$is_forever = (string)$this->postParam("is_forever") === "true";
$unban_time = $is_forever ? 0 : (int)$this->postParam("unban_time") ?? NULL;
if ($owner) {
$_id = ($owner instanceof Club ? $owner->getId() * -1 : $owner->getId());
if (!in_array($_id, $banned_ids)) {
if ($owner instanceof User) {
if (!$unban_time && !$is_forever)
$unban_time = time() + $owner->getNewBanTime();
$owner->ban($reason, false, $unban_time, $this->user->id);
} else {
$owner->ban("Подозрительная активность");
}
$banned_ids[] = $_id;
}
}
}
if (in_array((int)$this->postParam("ban"), [1, 3]))
$object->delete();
}
$processed++;
}
} }
$this->returnJson(["success" => true, "processed" => $processed, "count" => count($response), "list" => $response]); if (!in_array((int)$this->postParam("ban"), [1, 2, 3])) {
foreach ($rows as $key => $object) {
$object = (array)$object;
$_obj = [];
foreach ($object as $key => $value) {
foreach ($columns as $column) {
if ($column["name"] === $key && in_array(strtoupper($column["nativetype"]), ["BLOB", "BINARY", "VARBINARY", "TINYBLOB", "MEDIUMBLOB", "LONGBLOB"])) {
$value = "[BINARY]";
break;
}
}
$_obj[$key] = $value;
$_obj["__model_name"] = $_model;
}
$response[] = $_obj;
}
} else {
$ids = [];
foreach ($rows as $object) {
$object = new $model_name($db->table($table)->get($object->id));
if (!$object) continue;
$ids[] = $object->getId();
}
$log = new NoSpamLog;
$log->setUser($this->user->id);
$log->setModel($_model);
if ($searchTerm) {
$log->setRegex($searchTerm);
} else {
$log->setRequest($where);
}
$log->setBan_Type((int)$this->postParam("ban"));
$log->setCount(count($rows));
$log->setTime(time());
$log->setItems(implode(",", $ids));
$log->save();
$banned_ids = [];
foreach ($rows as $object) {
$object = new $model_name($db->table($table)->get($object->id));
if (!$object) continue;
$owner = NULL;
$methods = ["getOwner", "getUser", "getRecipient", "getInitiator"];
if (method_exists($object, "ban")) {
$owner = $object;
} else {
foreach ($methods as $method) {
if (method_exists($object, $method)) {
$owner = $object->$method();
break;
}
}
}
if ($owner instanceof User && $owner->getId() === $this->user->id) {
if (count($rows) === 1) {
$this->returnJson(["success" => false, "error" => "\"Производственная травма\" — Вы не можете блокировать или удалять свой же контент"]);
} else {
continue;
}
}
if (in_array((int)$this->postParam("ban"), [2, 3])) {
if ($owner) {
$_id = ($owner instanceof Club ? $owner->getId() * -1 : $owner->getId());
if (!in_array($_id, $banned_ids)) {
if ($owner instanceof User) {
$owner->ban("**content-noSpamTemplate-" . $log->getId() . "**", false, time() + $owner->getNewBanTime(), $this->user->id);
} else {
$owner->ban("Подозрительная активность");
}
$banned_ids[] = $_id;
}
}
}
if (in_array((int)$this->postParam("ban"), [1, 3]))
$object->delete();
}
$processed++;
}
}
$this->returnJson(["success" => true, "processed" => $processed, "count" => count($response), "list" => $response]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->returnJson(["success" => false, "error" => $e->getMessage()]); $this->returnJson(["success" => false, "error" => $e->getMessage()]);
} }

View file

@ -22,10 +22,15 @@ final class NotesPresenter extends OpenVKPresenter
if(!$user->getPrivacyPermission('notes.read', $this->user->identity ?? NULL)) if(!$user->getPrivacyPermission('notes.read', $this->user->identity ?? NULL))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment")); $this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
$this->template->page = (int)($this->queryParam("p") ?? 1); $this->template->notes = $this->notes->getUserNotes($user, (int)($this->queryParam("p") ?? 1));
$this->template->notes = $this->notes->getUserNotes($user, $this->template->page);
$this->template->count = $this->notes->getUserNotesCount($user); $this->template->count = $this->notes->getUserNotesCount($user);
$this->template->owner = $user; $this->template->owner = $user;
$this->template->paginatorConf = (object) [
"count" => $this->template->count,
"page" => $this->queryParam("p") ?? 1,
"amount" => NULL,
"perPage" => OPENVK_DEFAULT_PER_PAGE,
];
} }
function renderView(int $owner, int $note_id): void function renderView(int $owner, int $note_id): void
@ -35,8 +40,6 @@ final class NotesPresenter extends OpenVKPresenter
$this->notFound(); $this->notFound();
if(!$note->getOwner()->getPrivacyPermission('notes.read', $this->user->identity ?? NULL)) if(!$note->getOwner()->getPrivacyPermission('notes.read', $this->user->identity ?? NULL))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment")); $this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
if(!$note->canBeViewedBy($this->user->identity))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
$this->template->cCount = $note->getCommentsCount(); $this->template->cCount = $note->getCommentsCount();
$this->template->cPage = (int) ($this->queryParam("p") ?? 1); $this->template->cPage = (int) ($this->queryParam("p") ?? 1);
@ -104,7 +107,7 @@ final class NotesPresenter extends OpenVKPresenter
if(!$note || $note->getOwner()->getId() !== $owner || $note->isDeleted()) if(!$note || $note->getOwner()->getId() !== $owner || $note->isDeleted())
$this->notFound(); $this->notFound();
if(is_null($this->user) || !$note->canBeModifiedBy($this->user->identity)) if(is_null($this->user) || !$note->canBeModifiedBy($this->user->identity))
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
$this->template->note = $note; $this->template->note = $note;
if($_SERVER["REQUEST_METHOD"] === "POST") { if($_SERVER["REQUEST_METHOD"] === "POST") {
@ -132,11 +135,11 @@ final class NotesPresenter extends OpenVKPresenter
if(!$note) $this->notFound(); if(!$note) $this->notFound();
if($note->getOwner()->getId() . "_" . $note->getId() !== $owner . "_" . $id || $note->isDeleted()) $this->notFound(); if($note->getOwner()->getId() . "_" . $note->getId() !== $owner . "_" . $id || $note->isDeleted()) $this->notFound();
if(is_null($this->user) || !$note->canBeModifiedBy($this->user->identity)) if(is_null($this->user) || !$note->canBeModifiedBy($this->user->identity))
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
$name = $note->getName(); $name = $note->getName();
$note->delete(); $note->delete();
$this->flash("succ", tr("note_is_deleted"), tr("note_x_is_now_deleted", $name)); $this->flash("succ", "Заметка удалена", "Заметка \"$name\" была успешно удалена.");
$this->redirect("/notes" . $this->user->id); $this->redirect("/notes" . $this->user->id);
} }
} }

View file

@ -198,9 +198,6 @@ abstract class OpenVKPresenter extends SimplePresenter
{ {
$user = Authenticator::i()->getUser(); $user = Authenticator::i()->getUser();
if(!$this->template)
$this->template = new \stdClass;
$this->template->isXmas = intval(date('d')) >= 1 && date('m') == 12 || intval(date('d')) <= 15 && date('m') == 1 ? true : false; $this->template->isXmas = intval(date('d')) >= 1 && date('m') == 12 || intval(date('d')) <= 15 && date('m') == 1 ? true : false;
$this->template->isTimezoned = Session::i()->get("_timezoneOffset"); $this->template->isTimezoned = Session::i()->get("_timezoneOffset");
@ -256,7 +253,7 @@ abstract class OpenVKPresenter extends SimplePresenter
$userValidated = 1; $userValidated = 1;
$cacheTime = 0; # Force no cache $cacheTime = 0; # Force no cache
if(!property_exists($this, 'silent') && $this->user->identity->onlineStatus() == 0 && !($this->user->identity->isDeleted() || $this->user->identity->isBanned())) { if($this->user->identity->onlineStatus() == 0 && !($this->user->identity->isDeleted() || $this->user->identity->isBanned())) {
$this->user->identity->setOnline(time()); $this->user->identity->setOnline(time());
$this->user->identity->setClient_name(NULL); $this->user->identity->setClient_name(NULL);
$this->user->identity->save(false); $this->user->identity->save(false);
@ -274,7 +271,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 ($this->presenterName && OPENVK_ROOT_CONF["openvk"]["preferences"]["maintenanceMode"][$this->presenterName]) { if (OPENVK_ROOT_CONF["openvk"]["preferences"]["maintenanceMode"][$this->presenterName]) {
$this->pass("openvk!Maintenance->section", $this->presenterName); $this->pass("openvk!Maintenance->section", $this->presenterName);
} }
} else { } else {
@ -282,11 +279,6 @@ abstract class OpenVKPresenter extends SimplePresenter
$this->redirect("/maintenances/"); $this->redirect("/maintenances/");
} }
} }
if($_SERVER['HTTP_X_OPENVK_AJAX_QUERY'] == '1' && $this->user->identity) {
error_reporting(0);
header('Content-Type: text/plain; charset=UTF-8');
}
parent::onStartup(); parent::onStartup();
} }
@ -312,7 +304,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 !== NULL && $this->user->identity !== NULL && $this->user->identity->getTheme()) { } else if($this->user->identity !== NULL && $this->user->identity->getTheme()) {
$theme = $this->user->identity->getTheme(); $theme = $this->user->identity->getTheme();
} }

View file

@ -1,6 +1,6 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\Web\Presenters; namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Club, Photo, Album, User}; use openvk\Web\Models\Entities\{Club, Photo, Album};
use openvk\Web\Models\Repositories\{Photos, Albums, Users, Clubs}; use openvk\Web\Models\Repositories\{Photos, Albums, Users, Clubs};
use Nette\InvalidStateException as ISE; use Nette\InvalidStateException as ISE;
@ -27,7 +27,7 @@ final class PhotosPresenter extends OpenVKPresenter
if(!$user) $this->notFound(); if(!$user) $this->notFound();
if (!$user->getPrivacyPermission('photos.read', $this->user->identity ?? NULL)) if (!$user->getPrivacyPermission('photos.read', $this->user->identity ?? NULL))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment")); $this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
$this->template->albums = $this->albums->getUserAlbums($user, (int)($this->queryParam("p") ?? 1)); $this->template->albums = $this->albums->getUserAlbums($user, $this->queryParam("p") ?? 1);
$this->template->count = $this->albums->getUserAlbumsCount($user); $this->template->count = $this->albums->getUserAlbumsCount($user);
$this->template->owner = $user; $this->template->owner = $user;
$this->template->canEdit = false; $this->template->canEdit = false;
@ -36,7 +36,7 @@ final class PhotosPresenter extends OpenVKPresenter
} else { } else {
$club = (new Clubs)->get(abs($owner)); $club = (new Clubs)->get(abs($owner));
if(!$club) $this->notFound(); if(!$club) $this->notFound();
$this->template->albums = $this->albums->getClubAlbums($club, (int)($this->queryParam("p") ?? 1)); $this->template->albums = $this->albums->getClubAlbums($club, $this->queryParam("p") ?? 1);
$this->template->count = $this->albums->getClubAlbumsCount($club); $this->template->count = $this->albums->getClubAlbumsCount($club);
$this->template->owner = $club; $this->template->owner = $club;
$this->template->canEdit = false; $this->template->canEdit = false;
@ -46,7 +46,7 @@ final class PhotosPresenter extends OpenVKPresenter
$this->template->paginatorConf = (object) [ $this->template->paginatorConf = (object) [
"count" => $this->template->count, "count" => $this->template->count,
"page" => (int)($this->queryParam("p") ?? 1), "page" => $this->queryParam("p") ?? 1,
"amount" => NULL, "amount" => NULL,
"perPage" => OPENVK_DEFAULT_PER_PAGE, "perPage" => OPENVK_DEFAULT_PER_PAGE,
]; ];
@ -94,7 +94,7 @@ final class PhotosPresenter extends OpenVKPresenter
if(!$album) $this->notFound(); if(!$album) $this->notFound();
if($album->getPrettyId() !== $owner . "_" . $id || $album->isDeleted()) $this->notFound(); if($album->getPrettyId() !== $owner . "_" . $id || $album->isDeleted()) $this->notFound();
if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity) || $album->isDeleted()) if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity) || $album->isDeleted())
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
$this->template->album = $album; $this->template->album = $album;
if($_SERVER["REQUEST_METHOD"] === "POST") { if($_SERVER["REQUEST_METHOD"] === "POST") {
@ -106,7 +106,7 @@ final class PhotosPresenter extends OpenVKPresenter
$album->setEdited(time()); $album->setEdited(time());
$album->save(); $album->save();
$this->flash("succ", tr("changes_saved"), tr("new_data_accepted")); $this->flash("succ", "Изменения сохранены", "Новые данные приняты.");
} }
} }
@ -120,13 +120,13 @@ final class PhotosPresenter extends OpenVKPresenter
if(!$album) $this->notFound(); if(!$album) $this->notFound();
if($album->getPrettyId() !== $owner . "_" . $id || $album->isDeleted()) $this->notFound(); if($album->getPrettyId() !== $owner . "_" . $id || $album->isDeleted()) $this->notFound();
if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity)) if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity))
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
$name = $album->getName(); $name = $album->getName();
$owner = $album->getOwner(); $owner = $album->getOwner();
$album->delete(); $album->delete();
$this->flash("succ", tr("album_is_deleted"), tr("album_x_is_deleted", $name)); $this->flash("succ", "Альбом удалён", "Альбом $name был успешно удалён.");
$this->redirect("/albums" . ($owner instanceof Club ? "-" : "") . $owner->getId()); $this->redirect("/albums" . ($owner instanceof Club ? "-" : "") . $owner->getId());
} }
@ -136,9 +136,6 @@ final class PhotosPresenter extends OpenVKPresenter
if(!$album) $this->notFound(); if(!$album) $this->notFound();
if($album->getPrettyId() !== $owner . "_" . $id || $album->isDeleted()) if($album->getPrettyId() !== $owner . "_" . $id || $album->isDeleted())
$this->notFound(); $this->notFound();
if(!$album->canBeViewedBy($this->user->identity))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
if($owner > 0 /* bc we currently don't have perms for clubs */) { if($owner > 0 /* bc we currently don't have perms for clubs */) {
$ownerObject = (new Users)->get($owner); $ownerObject = (new Users)->get($owner);
@ -150,7 +147,7 @@ final class PhotosPresenter extends OpenVKPresenter
$this->template->photos = iterator_to_array( $album->getPhotos( (int) ($this->queryParam("p") ?? 1), 20) ); $this->template->photos = iterator_to_array( $album->getPhotos( (int) ($this->queryParam("p") ?? 1), 20) );
$this->template->paginatorConf = (object) [ $this->template->paginatorConf = (object) [
"count" => $album->getPhotosCount(), "count" => $album->getPhotosCount(),
"page" => (int)($this->queryParam("p") ?? 1), "page" => $this->queryParam("p") ?? 1,
"amount" => sizeof($this->template->photos), "amount" => sizeof($this->template->photos),
"perPage" => 20, "perPage" => 20,
"atBottom" => true "atBottom" => true
@ -161,8 +158,7 @@ final class PhotosPresenter extends OpenVKPresenter
{ {
$photo = $this->photos->getByOwnerAndVID($ownerId, $photoId); $photo = $this->photos->getByOwnerAndVID($ownerId, $photoId);
if(!$photo || $photo->isDeleted()) $this->notFound(); if(!$photo || $photo->isDeleted()) $this->notFound();
if(!$photo->canBeViewedBy($this->user->identity))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
if(!is_null($this->queryParam("from"))) { if(!is_null($this->queryParam("from"))) {
if(preg_match("%^album([0-9]++)$%", $this->queryParam("from"), $matches) === 1) { if(preg_match("%^album([0-9]++)$%", $this->queryParam("from"), $matches) === 1) {
$album = $this->albums->get((int) $matches[1]); $album = $this->albums->get((int) $matches[1]);
@ -176,7 +172,6 @@ final class PhotosPresenter extends OpenVKPresenter
$this->template->cCount = $photo->getCommentsCount(); $this->template->cCount = $photo->getCommentsCount();
$this->template->cPage = (int) ($this->queryParam("p") ?? 1); $this->template->cPage = (int) ($this->queryParam("p") ?? 1);
$this->template->comments = iterator_to_array($photo->getComments($this->template->cPage)); $this->template->comments = iterator_to_array($photo->getComments($this->template->cPage));
$this->template->owner = $photo->getOwner();
} }
function renderAbsolutePhoto($id): void function renderAbsolutePhoto($id): void
@ -210,13 +205,13 @@ final class PhotosPresenter extends OpenVKPresenter
$photo = $this->photos->getByOwnerAndVID($ownerId, $photoId); $photo = $this->photos->getByOwnerAndVID($ownerId, $photoId);
if(!$photo) $this->notFound(); if(!$photo) $this->notFound();
if(is_null($this->user) || $this->user->id != $ownerId) if(is_null($this->user) || $this->user->id != $ownerId)
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
if($_SERVER["REQUEST_METHOD"] === "POST") { if($_SERVER["REQUEST_METHOD"] === "POST") {
$photo->setDescription(empty($this->postParam("desc")) ? NULL : $this->postParam("desc")); $photo->setDescription(empty($this->postParam("desc")) ? NULL : $this->postParam("desc"));
$photo->save(); $photo->save();
$this->flash("succ", tr("changes_saved"), tr("new_description_will_appear")); $this->flash("succ", "Изменения сохранены", "Обновлённое описание появится на странице с фоткой.");
$this->redirect("/photo" . $photo->getPrettyId()); $this->redirect("/photo" . $photo->getPrettyId());
} }
@ -226,83 +221,39 @@ final class PhotosPresenter extends OpenVKPresenter
function renderUploadPhoto(): void function renderUploadPhoto(): void
{ {
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
$this->willExecuteWriteAction(true); $this->willExecuteWriteAction();
if(is_null($this->queryParam("album"))) { if(is_null($this->queryParam("album")))
$album = $this->albums->getUserWallAlbum($this->user->identity); $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в <b>DELETED</b>.");
} else {
[$owner, $id] = explode("_", $this->queryParam("album")); [$owner, $id] = explode("_", $this->queryParam("album"));
$album = $this->albums->get((int) $id); $album = $this->albums->get((int) $id);
}
if(!$album) if(!$album)
$this->flashFail("err", tr("error"), tr("error_adding_to_deleted"), 500, true); $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в <b>DELETED</b>.");
if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity))
# Для быстрой загрузки фоток из пикера фотографий нужен альбом, но юзер не может загружать фото $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
# в системные альбомы, так что так.
if(is_null($this->user) || !is_null($this->queryParam("album")) && !$album->canBeModifiedBy($this->user->identity))
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"), 500, true);
if($_SERVER["REQUEST_METHOD"] === "POST") { if($_SERVER["REQUEST_METHOD"] === "POST") {
if($this->queryParam("act") == "finish") { if(!isset($_FILES["blob"]))
$result = json_decode($this->postParam("photos"), true); $this->flashFail("err", "Нету фотографии", "Выберите файл.");
foreach($result as $photoId => $description) {
$phot = $this->photos->get($photoId);
if(!$phot || $phot->isDeleted() || $phot->getOwner()->getId() != $this->user->id)
continue;
if(iconv_strlen($description) > 255)
$this->flashFail("err", tr("error"), tr("description_too_long"), 500, true);
$phot->setDescription($description);
$phot->save();
$album = $phot->getAlbum();
}
$this->returnJson(["success" => true,
"album" => $album->getId(),
"owner" => $album->getOwner() instanceof User ? $album->getOwner()->getId() : $album->getOwner()->getId() * -1]);
}
if(!isset($_FILES))
$this->flashFail("err", tr("no_photo"), tr("select_file"), 500, true);
$photos = []; try {
if((int)$this->postParam("count") > 10) $photo = new Photo;
$this->flashFail("err", tr("no_photo"), "ты еблан", 500, true); $photo->setOwner($this->user->id);
$photo->setDescription($this->postParam("desc"));
for($i = 0; $i < $this->postParam("count"); $i++) { $photo->setFile($_FILES["blob"]);
try { $photo->setCreated(time());
$photo = new Photo; $photo->save();
$photo->setOwner($this->user->id); } catch(ISE $ex) {
$photo->setDescription(""); $name = $album->getName();
$photo->setFile($_FILES["photo_".$i]); $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в <b>$name</b>.");
$photo->setCreated(time());
$photo->save();
$photos[] = [
"url" => $photo->getURLBySizeId("tiny"),
"id" => $photo->getId(),
"vid" => $photo->getVirtualId(),
"owner" => $photo->getOwner()->getId(),
"link" => $photo->getURL(),
"pretty_id" => $photo->getPrettyId(),
];
} catch(ISE $ex) {
$name = $album->getName();
$this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в $name.", 500, true);
}
$album->addPhoto($photo);
$album->setEdited(time());
$album->save();
} }
$album->addPhoto($photo);
$album->setEdited(time());
$album->save();
$this->returnJson(["success" => true, $this->redirect("/photo" . $photo->getPrettyId() . "?from=album" . $album->getId());
"photos" => $photos]);
} else { } else {
$this->template->album = $album; $this->template->album = $album;
} }
@ -318,7 +269,7 @@ final class PhotosPresenter extends OpenVKPresenter
if(!$album || !$photo) $this->notFound(); if(!$album || !$photo) $this->notFound();
if(!$album->hasPhoto($photo)) $this->notFound(); if(!$album->hasPhoto($photo)) $this->notFound();
if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity)) if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity))
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
if($_SERVER["REQUEST_METHOD"] === "POST") { if($_SERVER["REQUEST_METHOD"] === "POST") {
$this->assertNoCSRF(); $this->assertNoCSRF();
@ -326,7 +277,7 @@ final class PhotosPresenter extends OpenVKPresenter
$album->setEdited(time()); $album->setEdited(time());
$album->save(); $album->save();
$this->flash("succ", tr("photo_is_deleted"), tr("photo_is_deleted_desc")); $this->flash("succ", "Фотография удалена", "Эта фотография была успешно удалена.");
$this->redirect("/album" . $album->getPrettyId()); $this->redirect("/album" . $album->getPrettyId());
} }
} }
@ -334,48 +285,18 @@ final class PhotosPresenter extends OpenVKPresenter
function renderDeletePhoto(int $ownerId, int $photoId): void function renderDeletePhoto(int $ownerId, int $photoId): void
{ {
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
$this->willExecuteWriteAction($_SERVER["REQUEST_METHOD"] === "POST"); $this->willExecuteWriteAction();
$this->assertNoCSRF(); $this->assertNoCSRF();
$photo = $this->photos->getByOwnerAndVID($ownerId, $photoId); $photo = $this->photos->getByOwnerAndVID($ownerId, $photoId);
if(!$photo) $this->notFound(); if(!$photo) $this->notFound();
if(is_null($this->user) || $this->user->id != $ownerId) if(is_null($this->user) || $this->user->id != $ownerId)
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
if(!is_null($album = $photo->getAlbum()))
$redirect = $album->getOwner() instanceof User ? "/id0" : "/club" . $ownerId;
else
$redirect = "/id0";
$photo->isolate(); $photo->isolate();
$photo->delete(); $photo->delete();
if($_SERVER["REQUEST_METHOD"] === "POST") $this->flash("succ", "Фотография удалена", "Эта фотография была успешно удалена.");
$this->returnJson(["success" => true]); $this->redirect("/id0");
$this->flash("succ", tr("photo_is_deleted"), tr("photo_is_deleted_desc"));
$this->redirect($redirect);
}
function renderLike(int $wall, int $post_id): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
$this->assertNoCSRF();
$photo = $this->photos->getByOwnerAndVID($wall, $post_id);
if(!$photo || $photo->isDeleted() || !$photo->canBeViewedBy($this->user->identity)) $this->notFound();
if(!is_null($this->user)) {
$photo->toggleLike($this->user->identity);
}
if($_SERVER["REQUEST_METHOD"] === "POST") {
$this->returnJson([
'success' => true,
]);
}
$this->redirect("$_SERVER[HTTP_REFERER]");
} }
} }

View file

@ -23,7 +23,7 @@ final class ReportPresenter extends OpenVKPresenter
if ($_SERVER["REQUEST_METHOD"] === "POST") if ($_SERVER["REQUEST_METHOD"] === "POST")
$this->assertNoCSRF(); $this->assertNoCSRF();
$act = in_array($this->queryParam("act"), ["post", "photo", "video", "group", "comment", "note", "app", "user", "audio"]) ? $this->queryParam("act") : NULL; $act = in_array($this->queryParam("act"), ["post", "photo", "video", "group", "comment", "note", "app", "user"]) ? $this->queryParam("act") : NULL;
if (!$this->queryParam("orig")) { if (!$this->queryParam("orig")) {
$this->template->reports = $this->reports->getReports(0, (int)($this->queryParam("p") ?? 1), $act, $_SERVER["REQUEST_METHOD"] !== "POST"); $this->template->reports = $this->reports->getReports(0, (int)($this->queryParam("p") ?? 1), $act, $_SERVER["REQUEST_METHOD"] !== "POST");
@ -43,7 +43,6 @@ final class ReportPresenter extends OpenVKPresenter
"perPage" => 15, "perPage" => 15,
]; ];
$this->template->mode = $act ?? "all"; $this->template->mode = $act ?? "all";
$this->template->disable_ajax = 1;
if ($_SERVER["REQUEST_METHOD"] === "POST") { if ($_SERVER["REQUEST_METHOD"] === "POST") {
$reports = []; $reports = [];
@ -79,7 +78,6 @@ final class ReportPresenter extends OpenVKPresenter
$this->notFound(); $this->notFound();
$this->template->report = $report; $this->template->report = $report;
$this->template->disable_ajax = 1;
} }
function renderCreate(int $id): void function renderCreate(int $id): void
@ -89,11 +87,11 @@ final class ReportPresenter extends OpenVKPresenter
if(!$id) if(!$id)
exit(json_encode([ "error" => tr("error_segmentation") ])); exit(json_encode([ "error" => tr("error_segmentation") ]));
if ($this->queryParam("type") === "user" && $id === $this->user->id)
exit(json_encode([ "error" => "You can't report yourself" ]));
if(in_array($this->queryParam("type"), ["post", "photo", "video", "group", "comment", "note", "app", "user", "audio"])) { if ($this->queryParam("type") === "user" && $id === $this->user->id)
exit(json_encode(["reason" => "You can't report yourself"]));
if(in_array($this->queryParam("type"), ["post", "photo", "video", "group", "comment", "note", "app", "user"])) {
if (count(iterator_to_array($this->reports->getDuplicates($this->queryParam("type"), $id, NULL, $this->user->id))) <= 0) { if (count(iterator_to_array($this->reports->getDuplicates($this->queryParam("type"), $id, NULL, $this->user->id))) <= 0) {
$report = new Report; $report = new Report;
$report->setUser_id($this->user->id); $report->setUser_id($this->user->id);
@ -123,22 +121,22 @@ final class ReportPresenter extends OpenVKPresenter
$report->deleteContent(); $report->deleteContent();
$report->banUser($this->user->identity->getId()); $report->banUser($this->user->identity->getId());
$this->flash("suc", tr("death"), tr("user_successfully_banned")); $this->flash("suc", "Смэрть...", "Пользователь успешно забанен.");
} else if ($this->postParam("delete")) { } else if ($this->postParam("delete")) {
$report->deleteContent(); $report->deleteContent();
$this->flash("suc", tr("nehay"), tr("content_is_deleted")); $this->flash("suc", "Нехай живе!", "Контент удалён, а пользователю прилетело предупреждение.");
} else if ($this->postParam("ignore")) { } else if ($this->postParam("ignore")) {
$report->delete(); $report->delete();
$this->flash("suc", tr("nehay"), tr("report_is_ignored")); $this->flash("suc", "Нехай живе!", "Жалоба проигнорирована.");
} else if ($this->postParam("banClubOwner") || $this->postParam("banClub")) { } else if ($this->postParam("banClubOwner") || $this->postParam("banClub")) {
if ($report->getContentType() !== "group") if ($report->getContentType() !== "group")
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
$club = $report->getContentObject(); $club = $report->getContentObject();
if (!$club || $club->isBanned()) if (!$club || $club->isBanned())
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
if ($this->postParam("banClubOwner")) { if ($this->postParam("banClubOwner")) {
$club->getOwner()->ban("**content-" . $report->getContentType() . "-" . $report->getContentId() . "**", false, $club->getOwner()->getNewBanTime(), $this->user->identity->getId()); $club->getOwner()->ban("**content-" . $report->getContentType() . "-" . $report->getContentId() . "**", false, $club->getOwner()->getNewBanTime(), $this->user->identity->getId());
@ -148,7 +146,7 @@ final class ReportPresenter extends OpenVKPresenter
$report->delete(); $report->delete();
$this->flash("suc", tr("death"), ($this->postParam("banClubOwner") ? tr("group_owner_is_banned") : tr("group_is_banned"))); $this->flash("suc", "Смэрть...", ($this->postParam("banClubOwner") ? "Создатель сообщества успешно забанен." : "Сообщество успешно забанено"));
} }
$this->redirect("/scumfeed"); $this->redirect("/scumfeed");

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, Videos, Applications, Audios}; use openvk\Web\Models\Repositories\{Users, Clubs, Posts, Comments, Videos, Applications, Notes};
use Chandler\Database\DatabaseConnection; use Chandler\Database\DatabaseConnection;
final class SearchPresenter extends OpenVKPresenter final class SearchPresenter extends OpenVKPresenter
@ -9,122 +9,95 @@ 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 $audios; private $notes;
function __construct() function __construct(Users $users, Clubs $clubs)
{ {
$this->users = new Users; $this->users = $users;
$this->clubs = new Clubs; $this->clubs = $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->audios = new Audios; $this->notes = new Notes;
parent::__construct(); parent::__construct();
} }
function renderIndex(): void function renderIndex(): void
{ {
$this->assertUserLoggedIn(); $query = $this->queryParam("query") ?? "";
$type = $this->queryParam("type") ?? "users";
$query = $this->queryParam("q") ?? ""; $sorter = $this->queryParam("sort") ?? "id";
$section = $this->queryParam("section") ?? "users"; $invert = $this->queryParam("invert") == 1 ? "ASC" : "DESC";
$order = $this->queryParam("order") ?? "id"; $page = (int) ($this->queryParam("p") ?? 1);
$invert = (int) ($this->queryParam("invert") ?? 0) == 1;
$page = (int) ($this->queryParam("p") ?? 1); $this->willExecuteWriteAction();
if($query != "")
$this->assertUserLoggedIn();
# 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" => "posts",
"apps" => "apps", "apps" => "apps",
"audios_playlists" => "audios" "notes" => "notes"
];
$parameters = [
"ignore_private" => true,
]; ];
foreach($_REQUEST as $param_name => $param_value) { switch($sorter) {
if(is_null($param_value)) continue;
switch($param_name) {
default:
$parameters[$param_name] = $param_value;
break;
case 'marital_status':
case 'polit_views':
if((int) $param_value == 0) continue;
$parameters[$param_name] = $param_value;
break;
case 'is_online':
if((int) $param_value == 1)
$parameters['is_online'] = 1;
break;
case 'only_performers':
if((int) $param_value == 1 || $param_value == 'on')
$parameters['only_performers'] = true;
break;
case 'with_lyrics':
if($param_value == 'on' || $param_value == '1')
$parameters['with_lyrics'] = true;
break;
# дай бог работал этот case
case 'from_me':
if((int) $param_value != 1) continue;
$parameters['from_me'] = $this->user->id;
break;
}
}
$repo = $repos[$section] or $this->throwError(400, "Bad Request", "Invalid search entity $section.");
$results = NULL;
switch($section) {
default: default:
$results = $this->{$repo}->find($query, $parameters, ['type' => $order, 'invert' => $invert]); case "id":
break; $sort = "id " . $invert;
case 'audios_playlists':
$results = $this->{$repo}->findPlaylists($query, $parameters, ['type' => $order, 'invert' => $invert]);
break; break;
case "name":
$sort = "first_name " . $invert;
break;
case "rating":
$sort = "rating " . $invert;
break;
} }
$parameters = [
"type" => $this->queryParam("type"),
"city" => $this->queryParam("city") != "" ? $this->queryParam("city") : NULL,
"maritalstatus" => $this->queryParam("maritalstatus") != 0 ? $this->queryParam("maritalstatus") : NULL,
"with_photo" => $this->queryParam("with_photo"),
"status" => $this->queryParam("status") != "" ? $this->queryParam("status") : NULL,
"politViews" => $this->queryParam("politViews") != 0 ? $this->queryParam("politViews") : NULL,
"email" => $this->queryParam("email"),
"telegram" => $this->queryParam("telegram"),
"site" => $this->queryParam("site") != "" ? "https://".$this->queryParam("site") : NULL,
"address" => $this->queryParam("address"),
"is_online" => $this->queryParam("is_online") == 1 ? 1 : NULL,
"interests" => $this->queryParam("interests") != "" ? $this->queryParam("interests") : NULL,
"fav_mus" => $this->queryParam("fav_mus") != "" ? $this->queryParam("fav_mus") : NULL,
"fav_films" => $this->queryParam("fav_films") != "" ? $this->queryParam("fav_films") : NULL,
"fav_shows" => $this->queryParam("fav_shows") != "" ? $this->queryParam("fav_shows") : NULL,
"fav_books" => $this->queryParam("fav_books") != "" ? $this->queryParam("fav_books") : NULL,
"fav_quote" => $this->queryParam("fav_quote") != "" ? $this->queryParam("fav_quote") : NULL,
"hometown" => $this->queryParam("hometown") != "" ? $this->queryParam("hometown") : NULL,
"before" => $this->queryParam("datebefore") != "" ? strtotime($this->queryParam("datebefore")) : NULL,
"after" => $this->queryParam("dateafter") != "" ? strtotime($this->queryParam("dateafter")) : NULL,
"gender" => $this->queryParam("gender") != "" && $this->queryParam("gender") != 2 ? $this->queryParam("gender") : NULL
];
$repo = $repos[$type] or $this->throwError(400, "Bad Request", "Invalid search entity $type.");
$iterator = $results->page($page, OPENVK_DEFAULT_PER_PAGE); $results = $this->{$repo}->find($query, $parameters, $sort);
$iterator = $results->page($page);
$count = $results->size(); $count = $results->size();
$this->template->order = $order; $this->template->iterator = iterator_to_array($iterator);
$this->template->invert = $invert;
$this->template->data = $this->template->iterator = iterator_to_array($iterator);
$this->template->count = $count; $this->template->count = $count;
$this->template->section = $section; $this->template->type = $type;
$this->template->page = $page; $this->template->page = $page;
$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 = 11;
$this->template->paginatorConf->atTop = true;
} }
} }

View file

@ -67,7 +67,7 @@ final class SupportPresenter extends OpenVKPresenter
$this->template->count = $this->tickets->getTicketsCountByUserId($this->user->id); $this->template->count = $this->tickets->getTicketsCountByUserId($this->user->id);
if($this->template->mode === "list") { if($this->template->mode === "list") {
$this->template->page = (int) ($this->queryParam("p") ?? 1); $this->template->page = (int) ($this->queryParam("p") ?? 1);
$this->template->tickets = iterator_to_array($this->tickets->getTicketsByUserId($this->user->id, $this->template->page)); $this->template->tickets = $this->tickets->getTicketsByUserId($this->user->id, $this->template->page);
} }
if($this->template->mode === "new") if($this->template->mode === "new")

View file

@ -111,7 +111,7 @@ final class TopicsPresenter extends OpenVKPresenter
$video = Video::fastMake($this->user->id, $_FILES["_vid_attachment"]["name"], $this->postParam("text"), $_FILES["_vid_attachment"]); $video = Video::fastMake($this->user->id, $_FILES["_vid_attachment"]["name"], $this->postParam("text"), $_FILES["_vid_attachment"]);
} }
} catch(ISE $ex) { } catch(ISE $ex) {
$this->flash("err", tr("error_when_publishing_comment"), tr("error_comment_file_too_big")); $this->flash("err", "Не удалось опубликовать комментарий", "Файл медиаконтента повреждён или слишком велик.");
$this->redirect("/topic" . $topic->getPrettyId()); $this->redirect("/topic" . $topic->getPrettyId());
} }
@ -126,7 +126,7 @@ final class TopicsPresenter extends OpenVKPresenter
$comment->setFlags($flags); $comment->setFlags($flags);
$comment->save(); $comment->save();
} catch (\LengthException $ex) { } catch (\LengthException $ex) {
$this->flash("err", tr("error_when_publishing_comment"), tr("error_comment_too_big")); $this->flash("err", "Не удалось опубликовать комментарий", "Комментарий слишком большой.");
$this->redirect("/topic" . $topic->getPrettyId()); $this->redirect("/topic" . $topic->getPrettyId());
} }

View file

@ -5,7 +5,7 @@ use openvk\Web\Util\Sms;
use openvk\Web\Themes\Themepacks; use openvk\Web\Themes\Themepacks;
use openvk\Web\Models\Entities\{Photo, Post, EmailChangeVerification}; use openvk\Web\Models\Entities\{Photo, Post, EmailChangeVerification};
use openvk\Web\Models\Entities\Notifications\{CoinsTransferNotification, RatingUpNotification}; use openvk\Web\Models\Entities\Notifications\{CoinsTransferNotification, RatingUpNotification};
use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Videos, Notes, Vouchers, EmailChangeVerifications, Audios}; use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Videos, Notes, Vouchers, EmailChangeVerifications};
use openvk\Web\Models\Exceptions\InvalidUserNameException; use openvk\Web\Models\Exceptions\InvalidUserNameException;
use openvk\Web\Util\Validator; use openvk\Web\Util\Validator;
use Chandler\Security\Authenticator; use Chandler\Security\Authenticator;
@ -29,14 +29,10 @@ final class UserPresenter extends OpenVKPresenter
function renderView(int $id): void function renderView(int $id): void
{ {
$user = $this->users->get($id); $user = $this->users->get($id);
if(!$user || $user->isDeleted() || !$user->canBeViewedBy($this->user->identity)) { if(!$user || $user->isDeleted()) {
if(!is_null($user) && $user->isDeactivated()) { if(!is_null($user) && $user->isDeactivated()) {
$this->template->_template = "User/deactivated.xml"; $this->template->_template = "User/deactivated.xml";
$this->template->user = $user;
} else if(!is_null($user) && !$user->canBeViewedBy($this->user->identity)) {
$this->template->_template = "User/private.xml";
$this->template->user = $user; $this->template->user = $user;
} else { } else {
$this->template->_template = "User/deleted.xml"; $this->template->_template = "User/deleted.xml";
@ -49,15 +45,8 @@ final class UserPresenter extends OpenVKPresenter
$this->template->videosCount = (new Videos)->getUserVideosCount($user); $this->template->videosCount = (new Videos)->getUserVideosCount($user);
$this->template->notes = (new Notes)->getUserNotes($user, 1, 4); $this->template->notes = (new Notes)->getUserNotes($user, 1, 4);
$this->template->notesCount = (new Notes)->getUserNotesCount($user); $this->template->notesCount = (new Notes)->getUserNotesCount($user);
$this->template->audios = (new Audios)->getRandomThreeAudiosByEntityId($user->getId());
$this->template->audiosCount = (new Audios)->getUserCollectionSize($user);
$this->template->audioStatus = $user->getCurrentAudioStatus();
$this->template->user = $user; $this->template->user = $user;
if($id !== $this->user->id) {
$this->template->ignore_status = $user->isIgnoredBy($this->user->identity);
}
} }
} }
@ -66,7 +55,7 @@ final class UserPresenter extends OpenVKPresenter
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
$user = $this->users->get($id); $user = $this->users->get($id);
$page = abs((int)($this->queryParam("p") ?? 1)); $page = abs($this->queryParam("p") ?? 1);
if(!$user) if(!$user)
$this->notFound(); $this->notFound();
elseif (!$user->getPrivacyPermission('friends.read', $this->user->identity ?? NULL)) elseif (!$user->getPrivacyPermission('friends.read', $this->user->identity ?? NULL))
@ -83,7 +72,7 @@ final class UserPresenter extends OpenVKPresenter
if(!is_null($this->user)) { if(!is_null($this->user)) {
if($this->template->mode !== "friends" && $this->user->id !== $id) { if($this->template->mode !== "friends" && $this->user->id !== $id) {
$name = $user->getFullName(); $name = $user->getFullName();
$this->flash("err", tr("error_access_denied_short"), tr("error_viewing_subs", $name)); $this->flash("err", "Ошибка доступа", "Вы не можете просматривать полный список подписок $name.");
$this->redirect($user->getURL()); $this->redirect($user->getURL());
} }
@ -118,11 +107,11 @@ final class UserPresenter extends OpenVKPresenter
$this->notFound(); $this->notFound();
if(!$club->canBeModifiedBy($this->user->identity ?? NULL)) if(!$club->canBeModifiedBy($this->user->identity ?? NULL))
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"), NULL, true); $this->flashFail("err", "Ошибка доступа", "У вас недостаточно прав, чтобы изменять этот ресурс.", NULL, true);
$isClubPinned = $this->user->identity->isClubPinned($club); $isClubPinned = $this->user->identity->isClubPinned($club);
if(!$isClubPinned && $this->user->identity->getPinnedClubCount() > 10) if(!$isClubPinned && $this->user->identity->getPinnedClubCount() > 10)
$this->flashFail("err", tr("error"), tr("error_max_pinned_clubs"), NULL, true); $this->flashFail("err", "Ошибка", "Находится в левом меню могут максимум 10 групп", NULL, true);
if($club->getOwner()->getId() === $this->user->identity->getId()) { if($club->getOwner()->getId() === $this->user->identity->getId()) {
$club->setOwner_Club_Pinned(!$isClubPinned); $club->setOwner_Club_Pinned(!$isClubPinned);
@ -174,36 +163,12 @@ final class UserPresenter extends OpenVKPresenter
if ($this->postParam("marialstatus") <= 8 && $this->postParam("marialstatus") >= 0) if ($this->postParam("marialstatus") <= 8 && $this->postParam("marialstatus") >= 0)
$user->setMarital_Status($this->postParam("marialstatus")); $user->setMarital_Status($this->postParam("marialstatus"));
if ($this->postParam("maritalstatus-user")) {
if (in_array((int) $this->postParam("marialstatus"), [0, 1, 8])) {
$user->setMarital_Status_User(NULL);
} else {
$mUser = (new Users)->getByAddress($this->postParam("maritalstatus-user"));
if ($mUser) {
if ($mUser->getId() !== $this->user->id) {
$user->setMarital_Status_User($mUser->getId());
}
}
}
}
if ($this->postParam("politViews") <= 9 && $this->postParam("politViews") >= 0) if ($this->postParam("politViews") <= 9 && $this->postParam("politViews") >= 0)
$user->setPolit_Views($this->postParam("politViews")); $user->setPolit_Views($this->postParam("politViews"));
if ($this->postParam("pronouns") <= 2 && $this->postParam("pronouns") >= 0) if ($this->postParam("gender") <= 1 && $this->postParam("gender") >= 0)
switch ($this->postParam("pronouns")) { $user->setSex($this->postParam("gender"));
case '0':
$user->setSex(0);
break;
case '1':
$user->setSex(1);
break;
case '2':
$user->setSex(2);
break;
}
$user->setAudio_broadcast_enabled($this->checkbox("broadcast_music"));
if(!empty($this->postParam("phone")) && $this->postParam("phone") !== $user->getPhone()) { if(!empty($this->postParam("phone")) && $this->postParam("phone") !== $user->getPhone()) {
if(!OPENVK_ROOT_CONF["openvk"]["credentials"]["smsc"]["enable"]) if(!OPENVK_ROOT_CONF["openvk"]["credentials"]["smsc"]["enable"])
@ -272,11 +237,10 @@ final class UserPresenter extends OpenVKPresenter
} elseif($_GET['act'] === "status") { } elseif($_GET['act'] === "status") {
if(mb_strlen($this->postParam("status")) > 255) { if(mb_strlen($this->postParam("status")) > 255) {
$statusLength = (string) mb_strlen($this->postParam("status")); $statusLength = (string) mb_strlen($this->postParam("status"));
$this->flashFail("err", tr("error"), tr("error_status_too_long", $statusLength), NULL, true); $this->flashFail("err", "Ошибка", "Статус слишком длинный ($statusLength символов вместо 255 символов)", NULL, true);
} }
$user->setStatus(empty($this->postParam("status")) ? NULL : $this->postParam("status")); $user->setStatus(empty($this->postParam("status")) ? NULL : $this->postParam("status"));
$user->setAudio_broadcast_enabled($this->postParam("broadcast") == 1);
$user->save(); $user->save();
$this->returnJson([ $this->returnJson([
@ -317,7 +281,7 @@ final class UserPresenter extends OpenVKPresenter
if($_SERVER["REQUEST_METHOD"] === "POST") { if($_SERVER["REQUEST_METHOD"] === "POST") {
if(!$user->verifyNumber($this->postParam("code") ?? 0)) if(!$user->verifyNumber($this->postParam("code") ?? 0))
$this->flashFail("err", tr("error"), tr("invalid_code")); $this->flashFail("err", "Ошибка", "Не удалось подтвердить номер телефона: неверный код.");
$this->flash("succ", tr("changes_saved"), tr("changes_saved_comment")); $this->flash("succ", tr("changes_saved"), tr("changes_saved_comment"));
} }
@ -333,15 +297,13 @@ 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");
if ($this->postParam("act") == "rej") $user->toggleSubscription($this->user->identity);
$user->changeFlags($this->user->identity, 0b10000000, true);
else
$user->toggleSubscription($this->user->identity);
$this->redirect($_SERVER['HTTP_REFERER']); $this->redirect($user->getURL());
} }
function renderSetAvatar() { function renderSetAvatar()
{
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
@ -352,8 +314,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(\Throwable $ex) { } catch(ISE $ex) {
$this->flashFail("err", tr("error"), tr("error_upload_failed"), NULL, (int)$this->postParam("ajax", true) == 1); $this->flashFail("err", tr("error"), tr("error_upload_failed"));
} }
$album = (new Albums)->getUserAvatarAlbum($this->user->identity); $album = (new Albums)->getUserAvatarAlbum($this->user->identity);
@ -364,57 +326,23 @@ final class UserPresenter extends OpenVKPresenter
$flags = 0; $flags = 0;
$flags |= 0b00010000; $flags |= 0b00010000;
if($this->postParam("on_wall") == 1) { $post = new Post;
$post = new Post; $post->setOwner($this->user->id);
$post->setOwner($this->user->id); $post->setWall($this->user->id);
$post->setWall($this->user->id); $post->setCreated(time());
$post->setCreated(time()); $post->setContent("");
$post->setContent(""); $post->setFlags($flags);
$post->setFlags($flags); $post->save();
$post->save(); $post->attach($photo);
if($this->postParam("ava", true) == (int)1) {
$post->attach($photo);
}
if((int)$this->postParam("ajax", true) == 1) {
$this->returnJson([ $this->returnJson([
"success" => true, "url" => $photo->getURL(),
"new_photo" => $photo->getPrettyId(), "id" => $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
{ {
@ -502,17 +430,11 @@ final class UserPresenter extends OpenVKPresenter
"friends.add", "friends.add",
"wall.write", "wall.write",
"messages.write", "messages.write",
"audios.read",
"likes.read",
]; ];
foreach($settings as $setting) { foreach($settings as $setting) {
$input = $this->postParam(str_replace(".", "_", $setting)); $input = $this->postParam(str_replace(".", "_", $setting));
$user->setPrivacySetting($setting, min(3, (int)abs((int)$input ?? $user->getPrivacySetting($setting)))); $user->setPrivacySetting($setting, min(3, abs($input ?? $user->getPrivacySetting($setting))));
} }
$prof = $this->postParam("profile_type") == 1 || $this->postParam("profile_type") == 0 ? (int)$this->postParam("profile_type") : 0;
$user->setProfile_type($prof);
} else if($_GET['act'] === "finance.top-up") { } else if($_GET['act'] === "finance.top-up") {
$token = $this->postParam("key0") . $this->postParam("key1") . $this->postParam("key2") . $this->postParam("key3"); $token = $this->postParam("key0") . $this->postParam("key1") . $this->postParam("key2") . $this->postParam("key3");
$voucher = (new Vouchers)->getByToken($token); $voucher = (new Vouchers)->getByToken($token);
@ -552,7 +474,6 @@ final class UserPresenter extends OpenVKPresenter
} else if($_GET['act'] === "lMenu") { } else if($_GET['act'] === "lMenu") {
$settings = [ $settings = [
"menu_bildoj" => "photos", "menu_bildoj" => "photos",
"menu_muziko" => "audios",
"menu_filmetoj" => "videos", "menu_filmetoj" => "videos",
"menu_mesagoj" => "messages", "menu_mesagoj" => "messages",
"menu_notatoj" => "notes", "menu_notatoj" => "notes",
@ -560,7 +481,6 @@ final class UserPresenter extends OpenVKPresenter
"menu_novajoj" => "news", "menu_novajoj" => "news",
"menu_ligiloj" => "links", "menu_ligiloj" => "links",
"menu_standardo" => "poster", "menu_standardo" => "poster",
"menu_aplikoj" => "apps"
]; ];
foreach($settings as $checkbox => $setting) foreach($settings as $checkbox => $setting)
$user->setLeftMenuItemStatus($setting, $this->checkbox($checkbox)); $user->setLeftMenuItemStatus($setting, $this->checkbox($checkbox));

View file

@ -10,7 +10,6 @@ use WhichBrowser;
final class VKAPIPresenter extends OpenVKPresenter final class VKAPIPresenter extends OpenVKPresenter
{ {
protected $silent = true;
private function logRequest(string $object, string $method): void private function logRequest(string $object, string $method): void
{ {
$date = date(DATE_COOKIE); $date = date(DATE_COOKIE);
@ -187,12 +186,8 @@ 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
@ -223,13 +218,9 @@ final class VKAPIPresenter extends OpenVKPresenter
if(!is_callable([$handler, $method])) if(!is_callable([$handler, $method]))
$this->badMethod($object, $method); $this->badMethod($object, $method);
$has_rss = false;
$route = new \ReflectionMethod($handler, $method); $route = new \ReflectionMethod($handler, $method);
$params = []; $params = [];
foreach($route->getParameters() as $parameter) { foreach($route->getParameters() as $parameter) {
if($parameter->getName() == 'rss')
$has_rss = true;
$val = $this->requestParam($parameter->getName()); $val = $this->requestParam($parameter->getName());
if(is_null($val)) { if(is_null($val)) {
if($parameter->allowsNull()) if($parameter->allowsNull())
@ -242,19 +233,8 @@ final class VKAPIPresenter extends OpenVKPresenter
$this->badMethodCall($object, $method, $parameter->getName()); $this->badMethodCall($object, $method, $parameter->getName());
} }
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) {
// Just ignore the exception, since
// some args are intended for internal use
}
} }
define("VKAPI_DECL_VER", $this->requestParam("v") ?? "4.100", false); define("VKAPI_DECL_VER", $this->requestParam("v") ?? "4.100", false);
@ -265,31 +245,13 @@ final class VKAPIPresenter extends OpenVKPresenter
$this->fail($ex->getCode(), $ex->getMessage(), $object, $method); $this->fail($ex->getCode(), $ex->getMessage(), $object, $method);
} }
$result = NULL; $result = json_encode([
"response" => $res,
if($this->queryParam("rss") == '1' && $has_rss) { ]);
$feed = new \Bhaktaraz\RSSGenerator\Feed();
$res->appendTo($feed);
$result = strval($feed);
header("Content-Type: application/rss+xml;charset=UTF-8");
} else {
$result = json_encode([
"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);
} }
@ -319,31 +281,17 @@ final class VKAPIPresenter extends OpenVKPresenter
$this->fail(28, "Invalid 2FA code", "internal", "acquireToken"); $this->fail(28, "Invalid 2FA code", "internal", "acquireToken");
} }
$token = NULL; $platform = $this->requestParam("client_name");
$tokenIsStale = true;
$platform = $this->requestParam("client_name"); $token = new APIToken;
$acceptsStale = $this->requestParam("accepts_stale"); $token->setUser($user);
if($acceptsStale == "1") { $token->setPlatform($platform ?? (new WhichBrowser\Parser(getallheaders()))->toString());
if(is_null($platform)) $token->save();
$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);
@ -351,42 +299,4 @@ 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;
}
} }

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