diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index ddc730d6..04a1b344 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1 @@ -custom: "https://openvk.su/donate" \ No newline at end of file +custom: "https://ovk.to/donate" diff --git a/.github/workflows/build-base.yaml b/.github/workflows/build-base.yaml index 0a98503f..b053d398 100644 --- a/.github/workflows/build-base.yaml +++ b/.github/workflows/build-base.yaml @@ -6,7 +6,7 @@ on: env: BASE_IMAGE_NAME: php - BASE_IMAGE_VERSION: "8.1" + BASE_IMAGE_VERSION: "8.2" jobs: build-cli: @@ -24,12 +24,18 @@ jobs: id: buildx uses: docker/setup-buildx-action@v2 + - name: Change repository string to lowercase + id: repositorystring + uses: Entepotenz/change-string-case-action-min-dependencies@v1.1.0 + with: + string: ${{ github.repository }} + - name: Log into registry run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - name: Build cli image run: | - IMAGE_NAME=ghcr.io/${{ github.repository }}/$BASE_IMAGE_NAME:$BASE_IMAGE_VERSION-cli + IMAGE_NAME=ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/$BASE_IMAGE_NAME:$BASE_IMAGE_VERSION-cli docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_NAME . --push -f install/automated/docker/base-php-cli.Dockerfile --build-arg VERSION=$BASE_IMAGE_VERSION @@ -47,12 +53,18 @@ jobs: - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@v2 + + - name: Change repository string to lowercase + id: repositorystring + uses: Entepotenz/change-string-case-action-min-dependencies@v1.1.0 + with: + string: ${{ github.repository }} - name: Log into registry run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - + - name: Build apache image run: | - IMAGE_NAME=ghcr.io/${{ github.repository }}/$BASE_IMAGE_NAME:$BASE_IMAGE_VERSION-apache + IMAGE_NAME=ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/$BASE_IMAGE_NAME:$BASE_IMAGE_VERSION-apache docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_NAME . --push -f install/automated/docker/base-php-apache.Dockerfile --build-arg VERSION=$BASE_IMAGE_VERSION diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d4645520..24363f97 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -36,12 +36,18 @@ jobs: id: buildx uses: docker/setup-buildx-action@v2 + - name: Change repository string to lowercase + id: repositorystring + uses: Entepotenz/change-string-case-action-min-dependencies@v1.1.0 + with: + string: ${{ github.repository }} + - name: Log into registry run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - name: Build base image run: | - IMAGE_ID=ghcr.io/${{ github.repository }}/$BASE_IMAGE_NAME + IMAGE_ID=ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/$BASE_IMAGE_NAME IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') @@ -49,16 +55,16 @@ jobs: echo IMAGE_ID=$IMAGE_ID echo VERSION=$VERSION - docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_ID:$VERSION . --push -f install/automated/docker/openvk.Dockerfile --build-arg GITREPO=${{ github.repository }} + docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_ID:$VERSION . --push -f install/automated/docker/openvk.Dockerfile --build-arg GITREPO=${{ steps.repositorystring.outputs.lowercase }} - name: Build MariaDB primary image run: | - IMAGE_NAME=ghcr.io/${{ github.repository }}/$DB_IMAGE_NAME:$DB_VERSION-primary + IMAGE_NAME=ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/$DB_IMAGE_NAME:$DB_VERSION-primary docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_NAME . --push -f install/automated/docker/mariadb-primary.Dockerfile --build-arg VERSION=$DB_VERSION - name: Build MariaDB event image run: | - IMAGE_NAME=ghcr.io/${{ github.repository }}/$EVENT_IMAGE_NAME:$DB_VERSION-eventdb + IMAGE_NAME=ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/$EVENT_IMAGE_NAME:$DB_VERSION-eventdb docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_NAME . --push -f install/automated/docker/mariadb-eventdb.Dockerfile --build-arg VERSION=$DB_VERSION \ No newline at end of file diff --git a/Email/hello.eml.latte b/Email/hello.eml.latte index 48fd562e..8ae66499 100644 --- a/Email/hello.eml.latte +++ b/Email/hello.eml.latte @@ -12,7 +12,7 @@
- Добро пожаловать в OpenVK! Приятного времяприпровождения, надеюсь вам понравится.

Если появились вопросы, касаемые нашего сайта, пишите сюда + Добро пожаловать в OpenVK! Приятного времяприпровождения, надеюсь вам понравится.

Если появились вопросы, касаемые нашего сайта, пишите сюда
diff --git a/README.md b/README.md index 934ff0b7..82df13e7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# openvkOpenVK +# openvkOpenVK _[Русский](README_RU.md)_ @@ -6,7 +6,7 @@ _[Русский](README_RU.md)_ VKontakte belongs to Pavel Durov and VK Group. -To be honest, we don't know whether if it even works. However, this version is maintained and we will be happy to accept your bugreports [in our bug tracker](https://github.com/openvk/openvk/projects/1). You should also be able to submit them using [ticketing system](https://openvk.su/support?act=new) (you will need an OpenVK account for this). +To be honest, we don't know whether if it even works. However, this version is maintained and we will be happy to accept your bugreports [in our bug tracker](https://github.com/openvk/openvk/projects/1). You should also be able to submit them using [ticketing system](https://ovk.to/support?act=new) (you will need an OpenVK account for this). ## When's the release? @@ -26,6 +26,14 @@ However, OVK makes use of Chandler Application Server. This software requires ex If you want, you can add your instance to the list above so that people can register there. +### System requirements + +Here is our minimum hardware recommendation: + +* **CPU: Recent** (AMD Zen2 or equivalent) quad-core 2GHz+ CPU +* **RAM:** At least 2GB RAM (we recommend 6GB or 8GB for OpenVK with Kafka) +* **Minimum database space:** 10GB + ### Installation procedure 1. Install PHP 7.4, web-server, Composer, Node.js, Yarn and [Chandler](https://github.com/openvk/chandler) @@ -66,12 +74,12 @@ Once you are done, you can login as a system administrator on the network itself * **Password**: `admin` * It is recommended to change the password of the built-in account or disable it. -💡Confused? Full installation walkthrough is available [here](https://docs.openvk.uk/openvk_engine/centos8_installation/) (CentOS 8 [and](https://almalinux.org/) [family](https://yum.oracle.com/oracle-linux-isos.html)). +💡 Confused? Full installation walkthrough is available [here](https://docs.ovk.to/openvk_engine/centos8_installation/) (CentOS 8 [and](https://almalinux.org/) [family](https://yum.oracle.com/oracle-linux-isos.html)). ### Looking for Docker or Kubernetes deployment? See `install/automated/docker/README.md` and `install/automated/kubernetes/README.md` for Docker and Kubernetes deployment instructions. -### If my website uses OpenVK, should I release it's sources? +### If my website uses OpenVK, should I release its sources? It depends. You can keep the sources to yourself if you do not plan to distribute your website binaries. If your website software must be distributed, it can stay non-OSS provided the OpenVK is not used as a primary application and is not modified. If you modified OpenVK for your needs or your work is based on it and you are planning to redistribute this, then you should license it under terms of any LGPL-compatible license (like OSL, GPL, LGPL etc). @@ -80,7 +88,7 @@ It depends. You can keep the sources to yourself if you do not plan to distribut You may reach out to us via: * [Bug Tracker](https://github.com/openvk/openvk/projects/1) -* [Ticketing System](https://openvk.su/support?act=new) +* [Ticketing System](https://ovk.to/support?act=new) * Telegram Chat: Go to [our channel](https://t.me/openvkenglish) and open discussion in our channel menu. * [Reddit](https://www.reddit.com/r/openvk/) * [GitHub Discussions](https://github.com/openvk/openvk/discussions) diff --git a/README_RU.md b/README_RU.md index 7de91c39..fa09cabe 100644 --- a/README_RU.md +++ b/README_RU.md @@ -1,4 +1,4 @@ -# openvkOpenVK +# openvkOpenVK _[English](README.md)_ @@ -6,7 +6,7 @@ _[English](README.md)_ ВКонтакте принадлежит Павлу Дурову и VK Group. -Честно говоря, мы даже не знаем, работает ли она вообще. Однако, эта версия поддерживается, и мы будем рады принять ваши сообщения об ошибках [в нашем баг-трекере](https://github.com/openvk/openvk/projects/1). Вы также можете отправлять их через [вкладку "Помощь"](https://openvk.su/support?act=new) (для этого вам понадобится учетная запись OpenVK). +Честно говоря, мы даже не знаем, работает ли она вообще. Однако, эта версия поддерживается, и мы будем рады принять ваши сообщения об ошибках [в нашем баг-трекере](https://github.com/openvk/openvk/projects/1). Вы также можете отправлять их через [вкладку "Помощь"](https://ovk.to/support?act=new) (для этого вам понадобится учетная запись OpenVK). ## Когда выйдет релизная версия? @@ -66,7 +66,7 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions * **Пароль**: `admin` * Перед использованием встроенной учетной записи рекомендуется сменить пароль или отключить её. -💡Запутались? Полное руководство по установке доступно [здесь](https://docs.openvk.uk/openvk_engine/centos8_installation/) (CentOS 8 [и](https://almalinux.org/ru/) [семейство](https://yum.oracle.com/oracle-linux-isos.html)). +💡Запутались? Полное руководство по установке доступно [здесь](https://docs.ovk.to/openvk_engine/centos8_installation/) (CentOS 8 [и](https://almalinux.org/ru/) [семейство](https://yum.oracle.com/oracle-linux-isos.html)). # Установка в Docker/Kubernetes Подробные иструкции можно найти в `install/automated/docker/README.md` и `install/automated/kubernetes/README.md` соответственно. @@ -80,7 +80,7 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions Вы можете связаться с нами через: * [Баг-трекер](https://github.com/openvk/openvk/projects/1) -* [Помощь в OVK](https://openvk.su/support?act=new) +* [Помощь в OVK](https://ovk.to/support?act=new) * Telegram-чат: Перейдите на [наш канал](https://t.me/openvk) и откройте обсуждение в меню нашего канала. * [Reddit](https://www.reddit.com/r/openvk/) * [GitHub Discussions](https://github.com/openvk/openvk/discussions) diff --git a/ServiceAPI/Apps.php b/ServiceAPI/Apps.php index 6504c23e..a09dc49f 100644 --- a/ServiceAPI/Apps.php +++ b/ServiceAPI/Apps.php @@ -1,8 +1,11 @@ withdrawCoins(); $resolve($coins); } + + function getRegularToken(string $clientName, bool $acceptsStale, callable $resolve, callable $reject): void + { + $token = NULL; + $stale = true; + if($acceptsStale) + $token = (new APITokens)->getStaleByUser($this->user->getId(), $clientName); + + if(is_null($token)) { + $stale = false; + $token = new APIToken; + $token->setUser($this->user); + $token->setPlatform($clientName ?? (new WhichBrowser\Parser(getallheaders()))->toString()); + $token->save(); + } + + $resolve([ + 'is_stale' => $stale, + 'token' => $token->getFormattedToken(), + ]); + } } \ No newline at end of file diff --git a/ServiceAPI/Search.php b/ServiceAPI/Search.php deleted file mode 100644 index de0f9d2c..00000000 --- a/ServiceAPI/Search.php +++ /dev/null @@ -1,76 +0,0 @@ -user = $user; - $this->users = new Users; - $this->clubs = new Clubs; - $this->videos = new Videos; - } - - function fastSearch(string $query, string $type = "users", callable $resolve, callable $reject) - { - if($query == "" || strlen($query) < 3) - $reject(12, "No input or input < 3"); - - $repo; - $sort; - - switch($type) { - default: - case "users": - $repo = (new Users); - $sort = "rating DESC"; - - break; - case "groups": - $repo = (new Clubs); - $sort = "id ASC"; - - break; - case "videos": - $repo = (new Videos); - $sort = "created ASC"; - - break; - } - - $res = $repo->find($query, ["doNotSearchMe" => $this->user->getId(), "doNotSearchPrivate" => true,], $sort); - - $results = array_slice(iterator_to_array($res), 0, 5); - - $count = sizeof($results); - - $arr = [ - "count" => $count, - "items" => [] - ]; - - if(sizeof($results) < 1) { - $reject(2, "No results"); - } - - foreach($results as $res) { - $arr["items"][] = [ - "id" => $res->getId(), - "name" => $type == "users" ? $res->getCanonicalName() : $res->getName(), - "avatar" => $type != "videos" ? $res->getAvatarUrl() : $res->getThumbnailURL(), - "url" => $type != "videos" ? $res->getUrl() : "/video".$res->getPrettyId(), - "description" => ovk_proc_strtr($res->getDescription() ?? "...", 40) - ]; - } - - $resolve($arr); - } -} diff --git a/VKAPI/Handlers/Account.php b/VKAPI/Handlers/Account.php index 16308b97..8c0cb343 100644 --- a/VKAPI/Handlers/Account.php +++ b/VKAPI/Handlers/Account.php @@ -7,20 +7,32 @@ final class Account extends VKAPIRequestHandler function getProfileInfo(): object { $this->requireUser(); - - return (object) [ - "first_name" => $this->getUser()->getFirstName(), - "id" => $this->getUser()->getId(), - "last_name" => $this->getUser()->getLastName(), - "home_town" => $this->getUser()->getHometown(), - "status" => $this->getUser()->getStatus(), - "audio_status" => is_null($this->getUser()->getCurrentAudioStatus()) ? NULL : $this->getUser()->getCurrentAudioStatus()->toVkApiStruct($this->getUser()), - "bdate" => is_null($this->getUser()->getBirthday()) ? '01.01.1970' : $this->getUser()->getBirthday()->format('%e.%m.%Y'), - "bdate_visibility" => $this->getUser()->getBirthdayPrivacy(), + $user = $this->getUser(); + $return_object = (object) [ + "first_name" => $user->getFirstName(), + "photo_200" => $user->getAvatarURL("normal"), + "nickname" => $user->getPseudo(), + "is_service_account" => false, + "id" => $user->getId(), + "is_verified" => $user->isVerified(), + "verification_status" => $user->isVerified() ? 'verified' : 'unverified', + "last_name" => $user->getLastName(), + "home_town" => $user->getHometown(), + "status" => $user->getStatus(), + "bdate" => is_null($user->getBirthday()) ? '01.01.1970' : $user->getBirthday()->format('%e.%m.%Y'), + "bdate_visibility" => $user->getBirthdayPrivacy(), "phone" => "+420 ** *** 228", # TODO - "relation" => $this->getUser()->getMaritalStatus(), - "sex" => $this->getUser()->isFemale() ? 1 : 2 + "relation" => $user->getMaritalStatus(), + "screen_name" => $user->getShortCode(), + "sex" => $user->isFemale() ? 1 : 2, + #"email" => $user->getEmail(), ]; + + $audio_status = $user->getCurrentAudioStatus(); + if(!is_null($audio_status)) + $return_object->audio_status = $audio_status->toVkApiStruct($user); + + return $return_object; } function getInfo(): object @@ -152,4 +164,30 @@ final class Account extends VKAPIRequestHandler return (object) $output; } + + function getBalance(): object + { + $this->requireUser(); + if(!OPENVK_ROOT_CONF['openvk']['preferences']['commerce']) + $this->fail(105, "Commerce is disabled on this instance"); + + return (object) ['votes' => $this->getUser()->getCoins()]; + } + + function getOvkSettings(): object + { + $this->requireUser(); + $user = $this->getUser(); + + $settings_list = (object)[ + 'avatar_style' => $user->getStyleAvatar(), + 'style' => $user->getStyle(), + 'show_rating' => !$user->prefersNotToSeeRating(), + 'nsfw_tolerance' => $user->getNsfwTolerance(), + 'post_view' => $user->hasMicroblogEnabled() ? 'microblog' : 'old', + 'main_page' => $user->getMainPage() == 0 ? 'my_page' : 'news', + ]; + + return $settings_list; + } } diff --git a/VKAPI/Handlers/Audio.php b/VKAPI/Handlers/Audio.php index 413a2a3a..e0991af9 100644 --- a/VKAPI/Handlers/Audio.php +++ b/VKAPI/Handlers/Audio.php @@ -485,7 +485,7 @@ final class Audio extends VKAPIRequestHandler $this->requireUser(); $this->willExecuteWriteAction(); - if(!is_null($album_id)) + if(!is_null($album_id)) $this->fail(10, "album_id not implemented"); // TODO get rid of dups @@ -581,13 +581,18 @@ final class Audio extends VKAPIRequestHandler ]; } - function searchAlbums(string $query, int $offset = 0, int $limit = 25, int $drop_private = 0): object + function searchAlbums(string $query = '', int $offset = 0, int $limit = 25, int $drop_private = 0, int $order = 0, int $from_me = 0): object { $this->requireUser(); $playlists = []; - $search = (new Audios)->searchPlaylists($query)->offsetLimit($offset, $limit); - foreach($search as $playlist) { + $params = []; + $order_str = (['id', 'length', 'listens'][$order] ?? 'id'); + if($from_me === 1) + $params['from_me'] = $this->getUser()->getId(); + + $search = (new Audios)->findPlaylists($query, $params, ['type' => $order_str, 'invert' => false]); + foreach($search->offsetLimit($offset, $limit) as $playlist) { if(!$playlist->canBeViewedBy($this->getUser())) { if($drop_private == 0) $playlists[] = NULL; @@ -599,7 +604,7 @@ final class Audio extends VKAPIRequestHandler } return (object) [ - "count" => sizeof($playlists), + "count" => $search->size(), "items" => $playlists, ]; } diff --git a/VKAPI/Handlers/Friends.php b/VKAPI/Handlers/Friends.php index 56af8d11..51046be4 100644 --- a/VKAPI/Handlers/Friends.php +++ b/VKAPI/Handlers/Friends.php @@ -147,7 +147,7 @@ final class Friends extends VKAPIRequestHandler return $response; } - function getRequests(string $fields = "", int $offset = 0, int $count = 100, int $extended = 0): object + function getRequests(string $fields = "", int $out = 0, int $offset = 0, int $count = 100, int $extended = 0): object { if ($count >= 1000) $this->fail(100, "One of the required parameters was not passed or is invalid."); @@ -158,9 +158,18 @@ final class Friends extends VKAPIRequestHandler $offset++; $followers = []; - foreach($this->getUser()->getFollowers($offset, $count) as $follower) { - $followers[$i] = $follower->getId(); - $i++; + if ($out != 0) { + foreach($this->getUser()->getFollowers($offset, $count) as $follower) { + $followers[$i] = $follower->getId(); + $i++; + } + } + else + { + foreach($this->getUser()->getRequests($offset, $count) as $follower) { + $followers[$i] = $follower->getId(); + $i++; + } } $response = $followers; diff --git a/VKAPI/Handlers/Gifts.php b/VKAPI/Handlers/Gifts.php index dd6fa07a..0d1fc598 100644 --- a/VKAPI/Handlers/Gifts.php +++ b/VKAPI/Handlers/Gifts.php @@ -6,15 +6,18 @@ use openvk\Web\Models\Entities\Notifications\GiftNotification; final class Gifts extends VKAPIRequestHandler { - function get(int $user_id, int $count = 10, int $offset = 0) + function get(int $user_id = NULL, int $count = 10, int $offset = 0) { $this->requireUser(); $i = 0; - $i += $offset; + $server_url = ovk_scheme(true) . $_SERVER["HTTP_HOST"]; - $user = (new UsersRepo)->get($user_id); + if($user_id) + $user = (new UsersRepo)->get($user_id); + else + $user = $this->getUser(); if(!$user || $user->isDeleted()) $this->fail(177, "Invalid user"); @@ -47,9 +50,9 @@ final class Gifts extends VKAPIRequestHandler "date" => $gift->sent->timestamp(), "gift" => [ "id" => $gift->gift->getId(), - "thumb_256" => $gift->gift->getImage(2), - "thumb_96" => $gift->gift->getImage(2), - "thumb_48" => $gift->gift->getImage(2) + "thumb_256" => $server_url. $gift->gift->getImage(2), + "thumb_96" => $server_url . $gift->gift->getImage(2), + "thumb_48" => $server_url . $gift->gift->getImage(2) ], "privacy" => 0 ]; @@ -125,12 +128,13 @@ final class Gifts extends VKAPIRequestHandler $this->fail(501, "Not implemented"); } - # этих методов не было в ВК, но я их добавил чтобы можно было отобразить список подарков + # в vk кстати называется gifts.getCatalog function getCategories(bool $extended = false, int $page = 1) { $cats = (new GiftsRepo)->getCategories($page); $categ = []; $i = 0; + $server_url = ovk_scheme(true) . $_SERVER["HTTP_HOST"]; if(!OPENVK_ROOT_CONF['openvk']['preferences']['commerce']) $this->fail(105, "Commerce is disabled on this instance"); @@ -140,8 +144,8 @@ final class Gifts extends VKAPIRequestHandler "name" => $cat->getName(), "description" => $cat->getDescription(), "id" => $cat->getId(), - "thumbnail" => $cat->getThumbnailURL(), - ]; + "thumbnail" => $server_url . $cat->getThumbnailURL(), + ]; if($extended == true) { $categ[$i]["localizations"] = []; @@ -178,7 +182,7 @@ final class Gifts extends VKAPIRequestHandler "name" => $gift->getName(), "image" => $gift->getImage(2), "usages_left" => (int)$gift->getUsagesLeft($this->getUser()), - "price" => $gift->getPrice(), # голосов + "price" => $gift->getPrice(), "is_free" => $gift->isFree() ]; } diff --git a/VKAPI/Handlers/Groups.php b/VKAPI/Handlers/Groups.php index ffa4fedd..f8706191 100644 --- a/VKAPI/Handlers/Groups.php +++ b/VKAPI/Handlers/Groups.php @@ -88,6 +88,10 @@ final class Groups extends VKAPIRequestHandler case "can_suggest": $rClubs[$i]->can_suggest = !$usr->canBeModifiedBy($this->getUser()) && $usr->getWallType() == 2; break; + case "background": + $backgrounds = $usr->getBackDropPictureURLs(); + $rClubs[$i]->background = $backgrounds; + break; # unstandard feild case "suggested_count": if($usr->getWallType() != 2) { @@ -208,6 +212,10 @@ final class Groups extends VKAPIRequestHandler case "can_suggest": $response[$i]->can_suggest = !$clb->canBeModifiedBy($this->getUser()) && $clb->getWallType() == 2; break; + case "background": + $backgrounds = $clb->getBackDropPictureURLs(); + $response[$i]->background = $backgrounds; + break; # unstandard feild case "suggested_count": if($clb->getWallType() != 2) { @@ -244,23 +252,30 @@ final class Groups extends VKAPIRequestHandler return $response; } - function search(string $q, int $offset = 0, int $count = 100) + function search(string $q, int $offset = 0, int $count = 100, string $fields = "screen_name,is_admin,is_member,is_advertiser,photo_50,photo_100,photo_200") { + if($count > 100) { + $this->fail(100, "One of the parameters specified was missing or invalid: count should be less or equal to 100"); + } + $clubs = new ClubsRepo; $array = []; $find = $clubs->find($q); - foreach ($find as $group) + foreach ($find->offsetLimit($offset, $count) as $group) $array[] = $group->getId(); + + if(!$array || sizeof($array) < 1) { + return (object) [ + "count" => 0, + "items" => [], + ]; + } return (object) [ "count" => $find->size(), - "items" => $this->getById(implode(',', $array), "", "is_admin,is_member,is_advertiser,photo_50,photo_100,photo_200", $offset, $count) - /* - * As there is no thing as "fields" by the original documentation - * i'll just bake this param by the example shown here: https://dev.vk.com/method/groups.search - */ + "items" => $this->getById(implode(',', $array), "", $fields) ]; } @@ -347,7 +362,10 @@ final class Groups extends VKAPIRequestHandler !empty($topics) ? $club->setEveryone_Can_Create_Topics($topics) : NULL; !empty($adminlist) ? $club->setAdministrators_List_Display($adminlist) : NULL; !empty($topicsAboveWall) ? $club->setDisplay_Topics_Above_Wall($topicsAboveWall) : NULL; - !empty($hideFromGlobalFeed) ? $club->setHide_From_Global_Feed($hideFromGlobalFeed) : NULL; + + if (!$club->isHidingFromGlobalFeedEnforced()) { + !empty($hideFromGlobalFeed) ? $club->setHide_From_Global_Feed($hideFromGlobalFeed) : NULL; + } in_array($audio, [0, 1]) ? $club->setEveryone_can_upload_audios($audio) : NULL; diff --git a/VKAPI/Handlers/Users.php b/VKAPI/Handlers/Users.php index 961d0c5f..704823be 100644 --- a/VKAPI/Handlers/Users.php +++ b/VKAPI/Handlers/Users.php @@ -151,7 +151,6 @@ final class Users extends VKAPIRequestHandler } case "music": if(!$canView) { - $response[$i]->music = "secret"; break; } @@ -159,7 +158,6 @@ final class Users extends VKAPIRequestHandler break; case "movies": if(!$canView) { - $response[$i]->movies = "secret"; break; } @@ -167,7 +165,6 @@ final class Users extends VKAPIRequestHandler break; case "tv": if(!$canView) { - $response[$i]->tv = "secret"; break; } @@ -175,7 +172,6 @@ final class Users extends VKAPIRequestHandler break; case "books": if(!$canView) { - $response[$i]->books = "secret"; break; } @@ -183,7 +179,6 @@ final class Users extends VKAPIRequestHandler break; case "city": if(!$canView) { - $response[$i]->city = "Воскресенск"; break; } @@ -191,7 +186,6 @@ final class Users extends VKAPIRequestHandler break; case "interests": if(!$canView) { - $response[$i]->interests = "secret"; break; } @@ -199,7 +193,6 @@ final class Users extends VKAPIRequestHandler break; case "quotes": if(!$canView) { - $response[$i]->quotes = "secret"; break; } @@ -207,7 +200,6 @@ final class Users extends VKAPIRequestHandler break; case "email": if(!$canView) { - $response[$i]->email = "secret@gmail.com"; break; } @@ -215,7 +207,6 @@ final class Users extends VKAPIRequestHandler break; case "telegram": if(!$canView) { - $response[$i]->telegram = "@secret"; break; } @@ -223,7 +214,6 @@ final class Users extends VKAPIRequestHandler break; case "about": if(!$canView) { - $response[$i]->about = "secret"; break; } @@ -231,7 +221,6 @@ final class Users extends VKAPIRequestHandler break; case "rating": if(!$canView) { - $response[$i]->rating = 22; break; } @@ -240,12 +229,43 @@ final class Users extends VKAPIRequestHandler case "counters": $response[$i]->counters = (object) [ "friends_count" => $usr->getFriendsCount(), - "photos_count" => (new Albums)->getUserPhotosCount($usr), + "photos_count" => (new Photos)->getUserPhotosCount($usr), "videos_count" => (new Videos)->getUserVideosCount($usr), "audios_count" => (new Audios)->getUserCollectionSize($usr), "notes_count" => (new Notes)->getUserNotesCount($usr) ]; break; + case "correct_counters": + $response[$i]->counters = (object) [ + "friends" => $usr->getFriendsCount(), + "photos" => (new Photos)->getUserPhotosCount($usr), + "videos" => (new Videos)->getUserVideosCount($usr), + "audios" => (new Audios)->getUserCollectionSize($usr), + "notes" => (new Notes)->getUserNotesCount($usr), + "groups" => $usr->getClubCount(), + "online_friends" => $usr->getFriendsOnlineCount(), + ]; + break; + case "guid": + $response[$i]->guid = $usr->getChandlerGUID(); + break; + case 'background': + $backgrounds = $usr->getBackDropPictureURLs(); + $response[$i]->background = $backgrounds; + break; + case 'reg_date': + if(!$canView) { + break; + } + + $response[$i]->reg_date = $usr->getRegistrationTime()->timestamp(); + break; + case 'is_dead': + $response[$i]->is_dead = $usr->isDead(); + break; + case 'nickname': + $response[$i]->nickname = $usr->getPseudo(); + break; } } @@ -297,89 +317,90 @@ final class Users extends VKAPIRequestHandler int $count = 100, string $city = "", string $hometown = "", - int $sex = 2, - int $status = 0, # это про marital status + int $sex = 3, + int $status = 0, # marital_status bool $online = false, - # дальше идут параметры которых нету в vkapi но есть на сайте - string $profileStatus = "", # а это уже нормальный статус + # non standart params: int $sort = 0, - int $before = 0, - int $politViews = 0, - int $after = 0, - string $interests = "", + int $polit_views = 0, string $fav_music = "", string $fav_films = "", string $fav_shows = "", - string $fav_books = "", - string $fav_quotes = "" + string $fav_books = "" ) { - $users = new UsersRepo; - - $sortg = "id ASC"; + if($count > 100) { + $this->fail(100, "One of the parameters specified was missing or invalid: count should be less or equal to 100"); + } - $nfilds = $fields; + $users = new UsersRepo; + $output_sort = ['type' => 'id', 'invert' => false]; + $output_params = [ + "ignore_private" => true, + ]; switch($sort) { + default: case 0: - $sortg = "id DESC"; + $output_sort = ['type' => 'id', 'invert' => false]; break; case 1: - $sortg = "id ASC"; - break; - case 2: - $sortg = "first_name DESC"; - break; - case 3: - $sortg = "first_name ASC"; + $output_sort = ['type' => 'id', 'invert' => true]; break; case 4: - $sortg = "rating DESC"; - - if(!str_contains($nfilds, "rating")) { - $nfilds .= "rating"; - } - - break; - case 5: - $sortg = "rating DESC"; - - if(!str_contains($nfilds, "rating")) { - $nfilds .= "rating"; - } - + $output_sort = ['type' => 'rating', 'invert' => false]; 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 = []; + $find = $users->find($q, $output_params, $output_sort); - $parameters = [ - "city" => !empty($city) ? $city : NULL, - "hometown" => !empty($hometown) ? $hometown : NULL, - "gender" => $sex < 2 ? $sex : NULL, - "maritalstatus" => (bool)$status ? $status : NULL, - "politViews" => (bool)$politViews ? $politViews : NULL, - "is_online" => $online ? 1 : NULL, - "status" => !empty($profileStatus) ? $profileStatus : NULL, - "before" => $before != 0 ? $before : NULL, - "after" => $after != 0 ? $after : NULL, - "interests" => !empty($interests) ? $interests : NULL, - "fav_music" => !empty($fav_music) ? $fav_music : NULL, - "fav_films" => !empty($fav_films) ? $fav_films : NULL, - "fav_shows" => !empty($fav_shows) ? $fav_shows : NULL, - "fav_books" => !empty($fav_books) ? $fav_books : NULL, - "fav_quotes" => !empty($fav_quotes) ? $fav_quotes : NULL, - "doNotSearchPrivate" => true, - ]; - - $find = $users->find($q, $parameters, $sortg); - - foreach ($find as $user) + foreach ($find->offsetLimit($offset, $count) as $user) $array[] = $user->getId(); + if(!$array || sizeof($array) < 1) { + return (object) [ + "count" => 0, + "items" => [], + ]; + } + return (object) [ - "count" => $find->size(), - "items" => $this->get(implode(',', $array), $nfilds, $offset, $count) + "count" => $find->size(), + "items" => $this->get(implode(',', $array), $fields) ]; } diff --git a/VKAPI/Handlers/Utils.php b/VKAPI/Handlers/Utils.php index 5350a64f..a1c4f3b5 100644 --- a/VKAPI/Handlers/Utils.php +++ b/VKAPI/Handlers/Utils.php @@ -22,7 +22,7 @@ final class Utils extends VKAPIRequestHandler "object_id" => (int) substr($screen_name, strlen("club")), "type" => "group" ]; - } + } else $this->fail(104, "Not found"); } else { $user = (new Users)->getByShortURL($screen_name); if($user) { @@ -39,8 +39,17 @@ final class Utils extends VKAPIRequestHandler "type" => "group" ]; } - - return (object) []; + + $this->fail(104, "Not found"); } } + + function resolveGuid(string $guid): object + { + $user = (new Users)->getByChandlerUserId($guid); + if (is_null($user)) + $this->fail(104, "Not found"); + + return $user->toVkApiStruct($this->getUser()); + } } diff --git a/VKAPI/Handlers/Video.php b/VKAPI/Handlers/Video.php index f468686d..d933f728 100755 --- a/VKAPI/Handlers/Video.php +++ b/VKAPI/Handlers/Video.php @@ -60,4 +60,60 @@ final class Video extends VKAPIRequestHandler ]; } } + + function search(string $q = '', int $sort = 0, int $offset = 0, int $count = 10, bool $extended = false, string $fields = ''): object + { + $this->requireUser(); + + $params = []; + $db_sort = ['type' => 'id', 'invert' => false]; + $videos = (new VideosRepo)->find($q, $params, $db_sort); + $items = iterator_to_array($videos->offsetLimit($offset, $count)); + $count = $videos->size(); + + $return_items = []; + $profiles = []; + $groups = []; + foreach($items as $item) + $return_item = $item->getApiStructure($this->getUser()); + $return_item = $return_item->video; + $return_items[] = $return_item; + + if($return_item['owner_id']) { + if($return_item['owner_id'] > 0) + $profiles[] = $return_item['owner_id']; + else + $groups[] = abs($return_item['owner_id']); + } + + if($extended) { + $profiles = array_unique($profiles); + $groups = array_unique($groups); + + $profilesFormatted = []; + $groupsFormatted = []; + + foreach($profiles as $prof) { + $profile = (new UsersRepo)->get($prof); + $profilesFormatted[] = $profile->toVkApiStruct($this->getUser(), $fields); + } + + foreach($groups as $gr) { + $group = (new ClubsRepo)->get($gr); + $groupsFormatted[] = $group->toVkApiStruct($this->getUser(), $fields); + } + + return (object) [ + "count" => $count, + "items" => $return_items, + "profiles" => $profilesFormatted, + "groups" => $groupsFormatted, + ]; + } + + return (object) [ + "count" => $count, + "items" => $return_items, + ]; + } } diff --git a/VKAPI/Handlers/Wall.php b/VKAPI/Handlers/Wall.php index 3cbc7648..a305fde6 100644 --- a/VKAPI/Handlers/Wall.php +++ b/VKAPI/Handlers/Wall.php @@ -126,65 +126,41 @@ final class Wall extends VKAPIRequestHandler else $profiles[] = $attachment->getOwner()->getId(); - $post_source = []; - - if($attachment->getPlatform(true) === NULL) { - $post_source = (object)["type" => "vk"]; - } else { - $post_source = (object)[ - "type" => "api", - "platform" => $attachment->getPlatform(true) - ]; - } - $repost[] = [ "id" => $attachment->getVirtualId(), "owner_id" => $attachment->isPostedOnBehalfOfGroup() ? $attachment->getOwner()->getId() * -1 : $attachment->getOwner()->getId(), "from_id" => $attachment->isPostedOnBehalfOfGroup() ? $attachment->getOwner()->getId() * -1 : $attachment->getOwner()->getId(), "date" => $attachment->getPublicationTime()->timestamp(), - "post_type" => "post", + "post_type" => $attachment->getVkApiType(), "text" => $attachment->getText(false), "attachments" => $repostAttachments, - "post_source" => $post_source, + "post_source" => $attachment->getPostSourceInfo(), ]; - if ($attachment->getVirtualId() > 0) - $profiles[] = $attachment->getVirtualId(); + if ($attachment->getTargetWall() > 0) + $profiles[] = $attachment->getTargetWall(); else - $groups[] = $attachment->getVirtualId(); + $groups[] = abs($attachment->getTargetWall()); if($post->isSigned()) $profiles[] = $attachment->getOwner()->getId(); } } - $post_source = []; - - if($post->getPlatform(true) === NULL) { - $post_source = (object)["type" => "vk"]; - } else { - $post_source = (object)[ - "type" => "api", - "platform" => $post->getPlatform(true) - ]; - } - - $postType = "post"; $signerId = NULL; - if($post->getSuggestionType() != 0) - $postType = "suggest"; - - if($post->isSigned()) { $actualAuthor = $post->getOwner(false); $signerId = $actualAuthor->getId(); } - $items[] = (object)[ + # TODO "can_pin", "copy_history" и прочее не должны возвращаться, если равны null или false + # Ну и ещё всё надо перенести в toVkApiStruct, а то слишком много дублированного кода + + $post_temp_obj = (object)[ "id" => $post->getVirtualId(), "from_id" => $from_id, "owner_id" => $post->getTargetWall(), "date" => $post->getPublicationTime()->timestamp(), - "post_type" => $postType, + "post_type" => $post->getVkApiType(), "text" => $post->getText(false), "copy_history" => $repost, "can_edit" => $post->canBeEditedBy($this->getUser()), @@ -195,8 +171,7 @@ final class Wall extends VKAPIRequestHandler "is_pinned" => $post->isPinned(), "is_explicit" => $post->isExplicit(), "attachments" => $attachments, - "post_source" => $post_source, - "signer_id" => $signerId, + "post_source" => $post->getPostSourceInfo(), "comments" => (object)[ "count" => $post->getCommentsCount(), "can_post" => 1 @@ -213,6 +188,14 @@ final class Wall extends VKAPIRequestHandler ] ]; + if($signerId) + $post_temp_obj->signer_id = $signerId; + + if($post->isDeactivationMessage()) + $post_temp_obj->final_post = 1; + + $items[] = $post_temp_obj; + if ($from_id > 0) $profiles[] = $from_id; else @@ -332,17 +315,6 @@ final class Wall extends VKAPIRequestHandler else $profiles[] = $attachment->getOwner()->getId(); - $post_source = []; - - if($attachment->getPlatform(true) === NULL) { - $post_source = (object)["type" => "vk"]; - } else { - $post_source = (object)[ - "type" => "api", - "platform" => $attachment->getPlatform(true) - ]; - } - $repost[] = [ "id" => $attachment->getVirtualId(), "owner_id" => $attachment->isPostedOnBehalfOfGroup() ? $attachment->getOwner()->getId() * -1 : $attachment->getOwner()->getId(), @@ -351,47 +323,29 @@ final class Wall extends VKAPIRequestHandler "post_type" => "post", "text" => $attachment->getText(false), "attachments" => $repostAttachments, - "post_source" => $post_source, + "post_source" => $attachment->getPostSourceInfo(), ]; - if ($attachment->getVirtualId() > 0) - $profiles[] = $attachment->getVirtualId(); + if ($attachment->getTargetWall() > 0) + $profiles[] = $attachment->getTargetWall(); else - $groups[] = $attachment->getVirtualId(); + $groups[] = abs($attachment->getTargetWall()); if($post->isSigned()) $profiles[] = $attachment->getOwner()->getId(); } } - $post_source = []; - - if($post->getPlatform(true) === NULL) { - $post_source = (object)["type" => "vk"]; - } else { - $post_source = (object)[ - "type" => "api", - "platform" => $post->getPlatform(true) - ]; - } - - # TODO: $post->getVkApiType() - $postType = "post"; - $signerId = NULL; - if($post->getSuggestionType() != 0) - $postType = "suggest"; - - if($post->isSigned()) { $actualAuthor = $post->getOwner(false); $signerId = $actualAuthor->getId(); } - $items[] = (object)[ + $post_temp_obj = (object)[ "id" => $post->getVirtualId(), "from_id" => $from_id, "owner_id" => $post->getTargetWall(), "date" => $post->getPublicationTime()->timestamp(), - "post_type" => $postType, + "post_type" => $post->getVkApiType(), "text" => $post->getText(false), "copy_history" => $repost, "can_edit" => $post->canBeEditedBy($this->getUser()), @@ -401,8 +355,7 @@ final class Wall extends VKAPIRequestHandler "is_archived" => false, "is_pinned" => $post->isPinned(), "is_explicit" => $post->isExplicit(), - "post_source" => $post_source, - "signer_id" => $signerId, + "post_source" => $post->getPostSourceInfo(), "attachments" => $attachments, "comments" => (object)[ "count" => $post->getCommentsCount(), @@ -420,6 +373,14 @@ final class Wall extends VKAPIRequestHandler ] ]; + if($signerId) + $post_temp_obj->signer_id = $signerId; + + if($post->isDeactivationMessage()) + $post_temp_obj->final_post = 1; + + $items[] = $post_temp_obj; + if ($from_id > 0) $profiles[] = $from_id; else @@ -792,6 +753,9 @@ final class Wall extends VKAPIRequestHandler ] ]; + if($comment->isFromPostAuthor($post)) + $item['is_from_post_author'] = true; + if($need_likes == true) $item['likes'] = [ "can_like" => 1, @@ -875,6 +839,9 @@ final class Wall extends VKAPIRequestHandler ] ]; + if($comment->isFromPostAuthor()) + $item['is_from_post_author'] = true; + if($extended == true) $profiles[] = $comment->getOwner()->getId(); @@ -890,8 +857,6 @@ final class Wall extends VKAPIRequestHandler $response['profiles'] = (!empty($profiles) ? (new Users)->get(implode(',', $profiles), $fields) : []); } - - return $response; } diff --git a/VKAPI/README.md b/VKAPI/README.md index 75918afc..08c8408b 100644 --- a/VKAPI/README.md +++ b/VKAPI/README.md @@ -5,7 +5,7 @@ exceptions. It is still a work-in-progress functionality. **Note**: requests to API are routed through openvk.Web.Presenters.VKAPIPresenter, this dir contains only handlers. -[Documentation for API clients](https://docs.openvk.uk/openvk_engine/api/description/) +[Documentation for API clients](https://docs.ovk.to/openvk_engine/api/description/) ## Implementing API methods diff --git a/Web/Models/Entities/Audio.php b/Web/Models/Entities/Audio.php index 11d8502b..3b998f32 100644 --- a/Web/Models/Entities/Audio.php +++ b/Web/Models/Entities/Audio.php @@ -17,7 +17,7 @@ class Audio extends Media # Taken from winamp :D const genres = [ - 'Blues','Big Band','Classic Rock','Chorus','Country','Easy Listening','Dance','Acoustic','Disco','Humour','Funk','Speech','Grunge','Chanson','Hip-Hop','Opera','Jazz','Chamber Music','Metal','Sonata','New Age','Symphony','Oldies','Booty Bass','Other','Primus','Pop','Porn Groove','R&B','Satire','Rap','Slow Jam','Reggae','Club','Rock','Tango','Techno','Samba','Industrial','Folklore','Alternative','Ballad','Ska','Power Ballad','Death Metal','Rhythmic Soul','Pranks','Freestyle','Soundtrack','Duet','Euro-Techno','Punk Rock','Ambient','Drum Solo','Trip-Hop','A Cappella','Vocal','Euro-House','Jazz+Funk','Dance Hall','Fusion','Goa','Trance','Drum & Bass','Classical','Club-House','Instrumental','Hardcore','Acid','Terror','House','Indie','Game','BritPop','Sound Clip','Negerpunk','Gospel','Polsk Punk','Noise','Beat','AlternRock','Christian Gangsta Rap','Bass','Heavy Metal','Soul','Black Metal','Punk','Crossover','Space','Contemporary Christian','Meditative','Christian Rock','Instrumental Pop','Merengue','Instrumental Rock','Salsa','Ethnic','Thrash Metal','Gothic','Anime','Darkwave','JPop','Techno-Industrial','Synthpop','Electronic','Abstract','Pop-Folk','Art Rock','Eurodance','Baroque','Dream','Bhangra','Southern Rock','Big Beat','Comedy','Breakbeat','Cult','Chillout','Gangsta Rap','Downtempo','Top 40','Dub','Christian Rap','EBM','Pop / Funk','Eclectic','Jungle','Electro','Native American','Electroclash','Cabaret','Emo','New Wave','Experimental','Psychedelic','Garage','Rave','Global','Showtunes','IDM','Trailer','Illbient','Lo-Fi','Industro-Goth','Tribal','Jam Band','Acid Punk','Krautrock','Acid Jazz','Leftfield','Polka','Lounge','Retro','Math Rock','Musical','New Romantic','Rock & Roll','Nu-Breakz','Hard Rock','Post-Punk','Folk','Post-Rock','Folk-Rock','Psytrance','National Folk','Shoegaze','Swing','Space Rock','Fast Fusion','Trop Rock','Bebob','World Music','Latin','Neoclassical','Revival','Audiobook','Celtic','Audio Theatre','Bluegrass','Neue Deutsche Welle','Avantgarde','Podcast','Gothic Rock','Indie Rock','Progressive Rock','G-Funk','Psychedelic Rock','Dubstep','Symphonic Rock','Garage Rock','Slow Rock','Psybient','Psychobilly','Touhou' + 'A Cappella', 'Abstract', 'Acid', 'Acid Jazz', 'Acid Punk', 'Acoustic', 'AlternRock', 'Alternative', 'Ambient', 'Anime', 'Art Rock', 'Audio Theatre', 'Audiobook', 'Avantgarde', 'Ballad', 'Baroque', 'Bass', 'Beat', 'Bebob', 'Bhangra', 'Big Band', 'Big Beat', 'Black Metal', 'Bluegrass', 'Blues', 'Booty Bass', 'Breakbeat', 'BritPop', 'Cabaret', 'Celtic', 'Chamber Music', 'Chanson', 'Chillout', 'Chorus', 'Christian Gangsta Rap', 'Christian Rap', 'Christian Rock', 'Classic Rock', 'Classical', 'Club', 'Club-House', 'Comedy', 'Contemporary Christian', 'Country', 'Crossover', 'Cult', 'Dance', 'Dance Hall', 'Darkwave', 'Death Metal', 'Disco', 'Downtempo', 'Dream', 'Drum & Bass', 'Drum Solo', 'Dub', 'Dubstep', 'Duet', 'EBM', 'Easy Listening', 'Eclectic', 'Electro', 'Electroclash', 'Electronic', 'Emo', 'Ethnic', 'Euro-House', 'Euro-Techno', 'Eurodance', 'Experimental', 'Fast Fusion', 'Folk', 'Folk-Rock', 'Folklore', 'Freestyle', 'Funk', 'Fusion', 'G-Funk', 'Game', 'Gangsta Rap', 'Garage', 'Garage Rock', 'Global', 'Goa', 'Gospel', 'Gothic', 'Gothic Rock', 'Grunge', 'Hard Rock', 'Hardcore', 'Heavy Metal', 'Hip-Hop', 'House', 'Humour', 'IDM', 'Illbient', 'Indie', 'Indie Rock', 'Industrial', 'Industro-Goth', 'Instrumental', 'Instrumental Pop', 'Instrumental Rock', 'JPop', 'Jam Band', 'Jazz', 'Jazz+Funk', 'Jungle', 'Krautrock', 'Latin', 'Leftfield', 'Lo-Fi', 'Lounge', 'Math Rock', 'Meditative', 'Merengue', 'Metal', 'Musical', 'National Folk', 'Native American', 'Negerpunk', 'Neoclassical', 'Neue Deutsche Welle', 'New Age', 'New Romantic', 'New Wave', 'Noise', 'Nu-Breakz', 'Oldies', 'Opera', 'Other', 'Podcast', 'Polka', 'Polsk Punk', 'Pop', 'Pop / Funk', 'Pop-Folk', 'Porn Groove', 'Post-Punk', 'Post-Rock', 'Power Ballad', 'Pranks', 'Primus', 'Progressive Rock', 'Psybient', 'Psychedelic', 'Psychedelic Rock', 'Psychobilly', 'Psytrance', 'Punk', 'Punk Rock', 'R&B', 'Rap', 'Rave', 'Reggae', 'Retro', 'Revival', 'Rhythmic Soul', 'Rock', 'Rock & Roll', 'Salsa', 'Samba', 'Satire', 'Shoegaze', 'Showtunes', 'Ska', 'Slow Jam', 'Slow Rock', 'Sonata', 'Soul', 'Sound Clip', 'Soundtrack', 'Southern Rock', 'Space', 'Space Rock', 'Speech', 'Swing', 'Symphonic Rock', 'Symphony', 'Synthpop', 'Tango', 'Techno', 'Techno-Industrial', 'Terror', 'Thrash Metal', 'Top 40', 'Touhou', 'Trailer', 'Trance', 'Tribal', 'Trip-Hop', 'Trop Rock', 'Vocal', 'World Music' ]; # Taken from: https://web.archive.org/web/20220322153107/https://dev.vk.com/reference/objects/audio-genres @@ -152,6 +152,11 @@ class Audio extends Media return $this->getPerformer() . " — " . $this->getTitle(); } + function getDownloadName(): string + { + return preg_replace('/[\\/:*?"<>|]/', '_', str_replace(' ', '_', $this->getName())); + } + function getGenre(): ?string { return $this->getRecord()->genre; diff --git a/Web/Models/Entities/Club.php b/Web/Models/Entities/Club.php index cbbe622b..de5cb007 100644 --- a/Web/Models/Entities/Club.php +++ b/Web/Models/Entities/Club.php @@ -152,6 +152,11 @@ class Club extends RowModel return (bool) $this->getRecord()->hide_from_global_feed; } + function isHidingFromGlobalFeedEnforced(): bool + { + return (bool) $this->getRecord()->enforce_hiding_from_global_feed; + } + function getType(): int { return $this->getRecord()->type; @@ -432,7 +437,7 @@ class Club extends RowModel return (new \openvk\Web\Models\Repositories\Audios)->getClubCollectionSize($this); } - function toVkApiStruct(?User $user = NULL): object + function toVkApiStruct(?User $user = NULL, string $fields = ''): object { $res = (object) []; diff --git a/Web/Models/Entities/Comment.php b/Web/Models/Entities/Comment.php index 24fb2c1b..fd32c0b8 100644 --- a/Web/Models/Entities/Comment.php +++ b/Web/Models/Entities/Comment.php @@ -103,6 +103,22 @@ class Comment extends Post return $this->getTarget()->canBeViewedBy($user); } + + function isFromPostAuthor($target = NULL) + { + if(!$target) + $target = $this->getTarget(); + + $target_owner = $target->getOwner(); + $comment_owner = $this->getOwner(); + + if($target_owner->getRealId() === $comment_owner->getRealId()) + return true; + + # TODO: make it work with signer_id + + return false; + } function toNotifApiStruct() { @@ -124,4 +140,31 @@ class Comment extends Post return $user->getId() == $this->getOwner(false)->getId(); } + + function getTargetURL(): string + { + $target = $this->getTarget(); + $target_name = 'wall'; + + if(!$target) { + return '/404'; + } + + switch(get_class($target)) { + case 'openvk\Web\Models\Entities\Note': + $target_name = 'note'; + break; + case 'openvk\Web\Models\Entities\Photo': + $target_name = 'photo'; + break; + case 'openvk\Web\Models\Entities\Video': + $target_name = 'video'; + break; + case 'openvk\Web\Models\Entities\Topic': + $target_name = 'topic'; + break; + } + + return $target_name . $target->getPrettyId(); + } } diff --git a/Web/Models/Entities/Message.php b/Web/Models/Entities/Message.php index 00de6e98..29e6578e 100644 --- a/Web/Models/Entities/Message.php +++ b/Web/Models/Entities/Message.php @@ -66,7 +66,7 @@ class Message extends RowModel $dateTime = new DateTime($this->getRecord()->created); if($dateTime->format("%d.%m.%y") == ovk_strftime_safe("%d.%m.%y", time())) { - return $dateTime->format("%T %p"); + return $dateTime->format("%T"); } else { return $dateTime->format("%d.%m.%y"); } diff --git a/Web/Models/Entities/Photo.php b/Web/Models/Entities/Photo.php index 4cba7734..3ea46939 100644 --- a/Web/Models/Entities/Photo.php +++ b/Web/Models/Entities/Photo.php @@ -114,7 +114,7 @@ class Photo extends Media return true; } - function crop(real $left, real $top, real $width, real $height): void + function crop(float $left, float $top, float $width, float $height): void { if(isset($this->changes["hash"])) $hash = $this->changes["hash"]; diff --git a/Web/Models/Entities/Playlist.php b/Web/Models/Entities/Playlist.php index c027a038..ac36b4f3 100644 --- a/Web/Models/Entities/Playlist.php +++ b/Web/Models/Entities/Playlist.php @@ -41,6 +41,21 @@ class Playlist extends MediaCollection { return $this->getRecord()->length; } + + function fetchClassic(int $offset = 0, ?int $limit = NULL): \Traversable + { + $related = $this->getRecord()->related("$this->relTableName.collection") + ->limit($limit ?? OPENVK_DEFAULT_PER_PAGE, $offset) + ->order("index ASC"); + + foreach($related as $rel) { + $media = $rel->ref($this->entityTableName, "media"); + if(!$media) + continue; + + yield new $this->entityClassName($media); + } + } function getAudios(int $offset = 0, ?int $limit = NULL, ?int $shuffleSeed = NULL): \Traversable { @@ -162,6 +177,7 @@ class Playlist extends MediaCollection "bookmarked" => $this->isBookmarkedBy($user), "listens" => $this->getListens(), "cover_url" => $this->getCoverURL(), + "searchable" => !$this->isUnlisted(), ]; } @@ -199,6 +215,11 @@ class Playlist extends MediaCollection { return $this->getRecord()->cover_photo_id; } + + function getCoverPhoto(): ?Photo + { + return (new Photos)->get((int) $this->getRecord()->cover_photo_id); + } function canBeModifiedBy(User $user): bool { @@ -253,4 +274,9 @@ class Playlist extends MediaCollection return implode(" • ", $props); } + + function isUnlisted(): bool + { + return (bool)$this->getRecord()->unlisted; + } } diff --git a/Web/Models/Entities/Post.php b/Web/Models/Entities/Post.php index 51b42c07..5a75d99b 100644 --- a/Web/Models/Entities/Post.php +++ b/Web/Models/Entities/Post.php @@ -133,6 +133,10 @@ class Post extends Postable case 'openvk_legacy_ios': return 'iphone'; break; + + case 'windows_phone': + return 'wphone'; + break; case 'vika_touch': // кика хохотач ахахахаххахахахахах case 'vk4me': @@ -175,6 +179,31 @@ class Post extends Postable "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 { diff --git a/Web/Models/Entities/Report.php b/Web/Models/Entities/Report.php index 5487056f..1b36678c 100644 --- a/Web/Models/Entities/Report.php +++ b/Web/Models/Entities/Report.php @@ -80,7 +80,7 @@ class Report extends RowModel function getAuthor(): RowModel { - return (new Posts)->get($this->getContentId())->getOwner(); + return $this->getContentObject()->getOwner(); } function getReportAuthor(): User diff --git a/Web/Models/Entities/Traits/TRichText.php b/Web/Models/Entities/Traits/TRichText.php index dc78a034..0f02a8ab 100644 --- a/Web/Models/Entities/Traits/TRichText.php +++ b/Web/Models/Entities/Traits/TRichText.php @@ -123,7 +123,7 @@ trait TRichText $text = preg_replace_callback("%([\n\r\s]|^)(\#([\p{L}_0-9][\p{L}_0-9\(\)\-\']+[\p{L}_0-9\(\)]|[\p{L}_0-9]{1,2}))%Xu", function($m) { $slug = rawurlencode($m[3]); - return "$m[1]$m[2]"; + return "$m[1]$m[2]"; }, $text); $text = $this->formatEmojis($text); diff --git a/Web/Models/Entities/Traits/TSubscribable.php b/Web/Models/Entities/Traits/TSubscribable.php index 802bc427..7d1c0d28 100644 --- a/Web/Models/Entities/Traits/TSubscribable.php +++ b/Web/Models/Entities/Traits/TSubscribable.php @@ -39,4 +39,25 @@ trait TSubscribable $sub->delete(); return false; } + + function changeFlags(User $user, int $flags, bool $reverse): bool + { + $ctx = DatabaseConnection::i()->getContext(); + $data = [ + "follower" => $reverse ? $this->getId() : $user->getId(), + "model" => static::class, + "target" => $reverse ? $user->getId() : $this->getId(), + ]; + $sub = $ctx->table("subscriptions")->where($data); + + bdump($data); + + if (!$sub) + return false; + + $sub->update([ + 'flags' => $flags + ]); + return true; + } } diff --git a/Web/Models/Entities/User.php b/Web/Models/Entities/User.php index 697abdde..64053145 100644 --- a/Web/Models/Entities/User.php +++ b/Web/Models/Entities/User.php @@ -592,6 +592,16 @@ class User extends RowModel return $this->_abstractRelationCount("get-followers"); } + function getRequests(int $page = 1, int $limit = 6): \Traversable + { + return $this->_abstractRelationGenerator("get-requests", $page, $limit); + } + + function getRequestsCount(): int + { + return $this->_abstractRelationCount("get-requests"); + } + function getSubscriptions(int $page = 1, int $limit = 6): \Traversable { return $this->_abstractRelationGenerator("get-subscriptions-user", $page, $limit); @@ -1214,6 +1224,11 @@ class User extends RowModel return (bool) $this->getRecord()->activated; } + function isDead(): bool + { + return $this->onlineStatus() == 2; + } + function getUnbanTime(): ?string { $ban = (new Bans)->get((int) $this->getRecord()->block_reason); @@ -1306,17 +1321,22 @@ class User extends RowModel return true; } - function isClosed() + function isClosed(): bool { return (bool) $this->getProfileType(); } + + function isHideFromGlobalFeedEnabled(): bool + { + return $this->isClosed(); + } function getRealId() { return $this->getId(); } - function toVkApiStruct(?User $user = NULL): object + function toVkApiStruct(?User $user = NULL, string $fields = ''): object { $res = (object) []; @@ -1328,12 +1348,21 @@ class User extends RowModel $res->photo_100 = $this->getAvatarURL("tiny"); $res->photo_200 = $this->getAvatarURL("normal"); $res->photo_id = !is_null($this->getAvatarPhoto()) ? $this->getAvatarPhoto()->getPrettyId() : NULL; - # TODO: Perenesti syuda vsyo ostalnoyie $res->is_closed = $this->isClosed(); - if(!is_null($user)) { + if(!is_null($user)) $res->can_access_closed = (bool)$this->canBeViewedBy($user); + + if(!is_array($fields)) + $fields = explode(',', $fields); + + foreach($fields as $field) { + switch($field) { + case 'is_dead': + $res->is_dead = $user->isDead(); + break; + } } return $res; diff --git a/Web/Models/Entities/Video.php b/Web/Models/Entities/Video.php index 2c1c0b05..a4d0b898 100644 --- a/Web/Models/Entities/Video.php +++ b/Web/Models/Entities/Video.php @@ -181,8 +181,8 @@ class Video extends Media { if(preg_match(file_get_contents(__DIR__ . "/../VideoDrivers/regex/youtube.txt"), $link, $matches)) { $pointer = "YouTube:$matches[1]"; - } else if(preg_match(file_get_contents(__DIR__ . "/../VideoDrivers/regex/vimeo.txt"), $link, $matches)) { - $pointer = "Vimeo:$matches[1]"; + /*} else if(preg_match(file_get_contents(__DIR__ . "/../VideoDrivers/regex/vimeo.txt"), $link, $matches)) { + $pointer = "Vimeo:$matches[1]";*/ } else { throw new ISE("Invalid link"); } diff --git a/Web/Models/Repositories/APITokens.php b/Web/Models/Repositories/APITokens.php index 592688a8..8a9f2870 100644 --- a/Web/Models/Repositories/APITokens.php +++ b/Web/Models/Repositories/APITokens.php @@ -23,4 +23,13 @@ class APITokens extends Repository return $token; } + + function getStaleByUser(int $userId, string $platform, bool $withRevoked = false): ?APIToken + { + return $this->toEntity($this->table->where([ + 'user' => $userId, + 'platform' => $platform, + 'deleted' => $withRevoked, + ])->fetch()); + } } diff --git a/Web/Models/Repositories/Applications.php b/Web/Models/Repositories/Applications.php index 0687856e..c0906067 100644 --- a/Web/Models/Repositories/Applications.php +++ b/Web/Models/Repositories/Applications.php @@ -67,11 +67,21 @@ class Applications return sizeof($this->appRels->where("user", $user->getId())); } - function find(string $query, array $pars = [], string $sort = "id"): Util\EntityStream + function find(string $query = "", array $params = [], array $order = ['type' => 'id', 'invert' => false]): Util\EntityStream { - $query = "%$query%"; + $query = "%$query%"; $result = $this->apps->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("enabled", 1); + $order_str = 'id'; + + switch($order['type']) { + case 'id': + $order_str = 'id ' . ($order['invert'] ? 'ASC' : 'DESC'); + break; + } + + if($order_str) + $result->order($order_str); - return new Util\EntityStream("Application", $result->order("$sort")); + return new Util\EntityStream("Application", $result); } } \ No newline at end of file diff --git a/Web/Models/Repositories/Audios.php b/Web/Models/Repositories/Audios.php index 64457299..73f5ac29 100644 --- a/Web/Models/Repositories/Audios.php +++ b/Web/Models/Repositories/Audios.php @@ -208,7 +208,7 @@ class Audios $search = $this->audios->where([ "unlisted" => 0, "deleted" => 0, - ])->where("MATCH ($columns) AGAINST (? WITH QUERY EXPANSION)", $query)->order($order); + ])->where("MATCH ($columns) AGAINST (? IN BOOLEAN MODE)", "%$query%")->order($order); if($withLyrics) $search = $search->where("lyrics IS NOT NULL"); @@ -219,6 +219,7 @@ class Audios function searchPlaylists(string $query): EntityStream { $search = $this->playlists->where([ + "unlisted" => 0, "deleted" => 0, ])->where("MATCH (`name`, `description`) AGAINST (? IN BOOLEAN MODE)", $query); @@ -243,53 +244,72 @@ class Audios ])->fetch()); } - function find(string $query, array $pars = [], string $sort = "id DESC", int $page = 1, ?int $perPage = NULL): \Traversable + function find(string $query, array $params = [], array $order = ['type' => 'id', 'invert' => false], int $page = 1, ?int $perPage = NULL): \Traversable { - $query = "%$query%"; + $query = "%$query%"; $result = $this->audios->where([ "unlisted" => 0, "deleted" => 0, ]); + $order_str = (in_array($order['type'], ['id', 'length', 'listens']) ? $order['type'] : 'id') . ' ' . ($order['invert'] ? 'ASC' : 'DESC');; - $notNullParams = []; - - foreach($pars as $paramName => $paramValue) - if($paramName != "before" && $paramName != "after" && $paramName != "only_performers") - $paramValue != NULL ? $notNullParams+=["$paramName" => "%$paramValue%"] : NULL; - else - $paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL; - - $nnparamsCount = sizeof($notNullParams); - - if($notNullParams["only_performers"] == "1") { + if($params["only_performers"] == "1") { $result->where("performer LIKE ?", $query); } else { $result->where("name LIKE ? OR performer LIKE ?", $query, $query); } - if($nnparamsCount > 0) { - foreach($notNullParams as $paramName => $paramValue) { - 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; - } + 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; } } - return new Util\EntityStream("Audio", $result->order($sort)); + if($order_str) + $result->order($order_str); + + return new Util\EntityStream("Audio", $result); } - function findPlaylists(string $query, int $page = 1, ?int $perPage = NULL): \Traversable + function findPlaylists(string $query, array $params = [], array $order = ['type' => 'id', 'invert' => false]): \Traversable { - $query = "%$query%"; - $result = $this->playlists->where("name LIKE ?", $query); + $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); } diff --git a/Web/Models/Repositories/Clubs.php b/Web/Models/Repositories/Clubs.php index 04bb30ab..b393952f 100644 --- a/Web/Models/Repositories/Clubs.php +++ b/Web/Models/Repositories/Clubs.php @@ -42,18 +42,30 @@ class Clubs { return $this->toClub($this->clubs->get($id)); } - - function find(string $query, array $pars = [], string $sort = "id DESC", int $page = 1, ?int $perPage = NULL): \Traversable + + function find(string $query, array $params = [], array $order = ['type' => 'id', 'invert' => false], int $page = 1, ?int $perPage = NULL): \Traversable { - $query = "%$query%"; - $result = $this->clubs->where("name LIKE ? OR about LIKE ?", $query, $query); - - return new Util\EntityStream("Club", $result->order($sort)); + $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 { - return sizeof(clone $this->clubs); + return (clone $this->clubs)->count('*'); } function getPopularClubs(): \Traversable diff --git a/Web/Models/Repositories/Comments.php b/Web/Models/Repositories/Comments.php index f4b8e5ac..811d1358 100644 --- a/Web/Models/Repositories/Comments.php +++ b/Web/Models/Repositories/Comments.php @@ -60,34 +60,31 @@ class Comments ])); } - function find(string $query = "", array $pars = [], string $sort = "id"): Util\EntityStream + function find(string $query, array $params = [], array $order = ['type' => 'id', 'invert' => false]): Util\EntityStream { - $query = "%$query%"; + $result = $this->comments->where("content LIKE ?", "%$query%")->where("deleted", 0); + $order_str = 'id'; - $notNullParams = []; + switch($order['type']) { + case 'id': + $order_str = 'created ' . ($order['invert'] ? 'ASC' : 'DESC'); + break; + } - foreach($pars as $paramName => $paramValue) - if($paramName != "before" && $paramName != "after") - $paramValue != NULL ? $notNullParams+=["$paramName" => "%$paramValue%"] : NULL; - else - $paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL; - - $result = $this->comments->where("content LIKE ?", $query)->where("deleted", 0); - $nnparamsCount = sizeof($notNullParams); - - if($nnparamsCount > 0) { - foreach($notNullParams as $paramName => $paramValue) { - switch($paramName) { - case "before": - $result->where("created < ?", $paramValue); - break; - case "after": - $result->where("created > ?", $paramValue); - break; - } + foreach($params as $paramName => $paramValue) { + switch($paramName) { + case "before": + $result->where("created < ?", $paramValue); + break; + case "after": + $result->where("created > ?", $paramValue); + break; } } - return new Util\EntityStream("Comment", $result->order("$sort")); + if($order_str) + $result->order($order_str); + + return new Util\EntityStream("Comment", $result); } } diff --git a/Web/Models/Repositories/Notifications.php b/Web/Models/Repositories/Notifications.php index bec0b04f..ccbf7c27 100644 --- a/Web/Models/Repositories/Notifications.php +++ b/Web/Models/Repositories/Notifications.php @@ -30,7 +30,7 @@ class Notifications return (new $repoClassName)->get($id); } - private function getQuery(User $user, bool $count = false, int $offset, bool $archived = false, int $page = 1, ?int $perPage = NULL): string + private function getQuery(User $user, bool $count, int $offset, bool $archived = false, int $page = 1, ?int $perPage = NULL): string { $query = "SELECT " . ($count ? "COUNT(*) AS cnt" : "*") . " FROM notifications WHERE recipientType=0 "; $query .= "AND timestamp " . ($archived ? "<" : ">") . "$offset AND recipientId=" . $user->getId(); diff --git a/Web/Models/Repositories/Posts.php b/Web/Models/Repositories/Posts.php index 89ee58ea..36082f24 100644 --- a/Web/Models/Repositories/Posts.php +++ b/Web/Models/Repositories/Posts.php @@ -154,36 +154,45 @@ class Posts } - function find(string $query = "", array $pars = [], string $sort = "id"): Util\EntityStream + function find(string $query = "", array $params = [], array $order = ['type' => 'id', 'invert' => false]): Util\EntityStream { - $query = "%$query%"; - - $notNullParams = []; - - foreach($pars as $paramName => $paramValue) - if($paramName != "before" && $paramName != "after") - $paramValue != NULL ? $notNullParams+=["$paramName" => "%$paramValue%"] : NULL; - else - $paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL; - + $query = "%$query%"; $result = $this->posts->where("content LIKE ?", $query)->where("deleted", 0)->where("suggested", 0); - $nnparamsCount = sizeof($notNullParams); + $order_str = 'id'; - if($nnparamsCount > 0) { - foreach($notNullParams as $paramName => $paramValue) { - switch($paramName) { - case "before": - $result->where("created < ?", $paramValue); - break; - case "after": - $result->where("created > ?", $paramValue); - break; - } + switch($order['type']) { + case 'id': + $order_str = 'created ' . ($order['invert'] ? 'ASC' : 'DESC'); + break; + } + + 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 'die_in_agony': + $result->where("nsfw", 1); + break; + case 'ads': + $result->where("ad", 1); + break;*/ + # БУДЬ МАКСИМАЛЬНО АККУРАТЕН С ДАННЫМ ПАРАМЕТРОМ + case 'from_me': + $result->where("owner", $paramValue); + break; } } + if($order_str) + $result->order($order_str); - return new Util\EntityStream("Post", $result->order("$sort")); + return new Util\EntityStream("Post", $result); } function getPostCountOnUserWall(int $user): int @@ -252,6 +261,6 @@ class Posts function getCount(): int { - return sizeof(clone $this->posts); + return (clone $this->posts)->count('*'); } } diff --git a/Web/Models/Repositories/Users.php b/Web/Models/Repositories/Users.php index d2f4500e..cc75b50f 100644 --- a/Web/Models/Repositories/Users.php +++ b/Web/Models/Repositories/Users.php @@ -44,107 +44,94 @@ class Users return $alias->getUser(); } - function getByChandlerUser(?ChandlerUser $user): ?User + function getByChandlerUserId(string $cid): ?User { - return $user ? $this->toUser($this->users->where("user", $user->getId())->fetch()) : NULL; + return $this->toUser($this->users->where("user", $cid)->fetch()); } - function find(string $query, array $pars = [], string $sort = "id DESC"): Util\EntityStream + function getByChandlerUser(?ChandlerUser $user): ?User { - $query = "%$query%"; + return $user ? $this->getByChandlerUserId($user->getId()) : NULL; + } + + function find(string $query, array $params = [], array $order = ['type' => 'id', 'invert' => false]): Util\EntityStream + { + $query = "%$query%"; $result = $this->users->where("CONCAT_WS(' ', first_name, last_name, pseudo, shortcode) LIKE ?", $query)->where("deleted", 0); - - $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; + $order_str = 'id'; - $nnparamsCount = sizeof($notNullParams); + switch($order['type']) { + case 'id': + case 'reg_date': + $order_str = 'id ' . ($order['invert'] ? 'ASC' : 'DESC'); + break; + case 'rating': + $order_str = 'rating DESC'; + break; + } - if($nnparamsCount > 0) { - foreach($notNullParams as $paramName => $paramValue) { - switch($paramName) { - case "hometown": - $result->where("hometown LIKE ?", $paramValue); - break; - case "city": - $result->where("city LIKE ?", $paramValue); - break; - case "maritalstatus": - $result->where("marital_status ?", $paramValue); - break; - case "status": - $result->where("status LIKE ?", $paramValue); - break; - case "politViews": - $result->where("polit_views ?", $paramValue); - break; - case "email": - $result->where("email_contact LIKE ?", $paramValue); - break; - case "telegram": - $result->where("telegram LIKE ?", $paramValue); - break; - case "site": - $result->where("telegram LIKE ?", $paramValue); - break; - case "address": - $result->where("address LIKE ?", $paramValue); - break; - case "is_online": - $result->where("online >= ?", time() - 900); - break; - case "interests": - $result->where("interests LIKE ?", $paramValue); - break; - case "fav_mus": - $result->where("fav_music LIKE ?", $paramValue); - break; - case "fav_films": - $result->where("fav_films LIKE ?", $paramValue); - break; - case "fav_shows": - $result->where("fav_shows LIKE ?", $paramValue); - break; - case "fav_books": - $result->where("fav_books LIKE ?", $paramValue); - break; - case "fav_quote": - $result->where("fav_quote LIKE ?", $paramValue); - break; - case "before": - $result->where("UNIX_TIMESTAMP(since) < ?", $paramValue); - break; - case "after": - $result->where("UNIX_TIMESTAMP(since) > ?", $paramValue); - break; - case "gender": - $result->where("sex ?", $paramValue); - break; - case "doNotSearchMe": - $result->where("id !=", $paramValue); - break; - case "doNotSearchPrivate": - $result->where("profile_type", 0); - break; - } + foreach($params as $paramName => $paramValue) { + if(is_null($paramValue) || $paramValue == '') continue; + + switch($paramName) { + case "hometown": + $result->where("hometown LIKE ?", "%$paramValue%"); + break; + case "city": + $result->where("city LIKE ?", "%$paramValue%"); + break; + case "marital_status": + $result->where("marital_status ?", $paramValue); + break; + case "polit_views": + $result->where("polit_views ?", $paramValue); + break; + case "is_online": + $result->where("online >= ?", time() - 900); + break; + case "fav_mus": + $result->where("fav_music LIKE ?", "%$paramValue%"); + break; + case "fav_films": + $result->where("fav_films LIKE ?", "%$paramValue%"); + break; + case "fav_shows": + $result->where("fav_shows LIKE ?", "%$paramValue%"); + break; + case "fav_books": + $result->where("fav_books LIKE ?", "%$paramValue%"); + break; + case "before": + $result->where("UNIX_TIMESTAMP(since) < ?", $paramValue); + break; + case "after": + $result->where("UNIX_TIMESTAMP(since) > ?", $paramValue); + break; + case "gender": + if((int) $paramValue == 3) break; + $result->where("sex ?", (int) $paramValue); + break; + case "ignore_id": + $result->where("id != ?", $paramValue); + break; + case "ignore_private": + $result->where("profile_type", 0); + break; } } + if($order_str) + $result->order($order_str); - return new Util\EntityStream("User", $result->order($sort)); + return new Util\EntityStream("User", $result); } function getStatistics(): object { return (object) [ - "all" => sizeof(clone $this->users), - "active" => sizeof((clone $this->users)->where("online > 0")), - "online" => sizeof((clone $this->users)->where("online >= ?", time() - 900)), + "all" => (clone $this->users)->count('*'), + "active" => (clone $this->users)->where("online > 0")->count('*'), + "online" => (clone $this->users)->where("online >= ?", time() - 900)->count('*'), ]; } diff --git a/Web/Models/Repositories/Videos.php b/Web/Models/Repositories/Videos.php index 2d41c3f9..e91c226d 100644 --- a/Web/Models/Repositories/Videos.php +++ b/Web/Models/Repositories/Videos.php @@ -46,36 +46,37 @@ class Videos return sizeof($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])); } - function find(string $query = "", array $pars = [], string $sort = "id"): Util\EntityStream + function find(string $query = "", array $params = [], array $order = ['type' => 'id', 'invert' => false]): Util\EntityStream { - $query = "%$query%"; - - $notNullParams = []; - - foreach($pars as $paramName => $paramValue) - if($paramName != "before" && $paramName != "after") - $paramValue != NULL ? $notNullParams+=["$paramName" => "%$paramValue%"] : NULL; - else - $paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL; - + $query = "%$query%"; $result = $this->videos->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("deleted", 0); - $nnparamsCount = sizeof($notNullParams); + $order_str = 'id'; - if($nnparamsCount > 0) { - foreach($notNullParams as $paramName => $paramValue) { - switch($paramName) { - case "before": - $result->where("created < ?", $paramValue); - break; - case "after": - $result->where("created > ?", $paramValue); - break; - } + switch($order['type']) { + case 'id': + $order_str = 'id ' . ($order['invert'] ? 'ASC' : 'DESC'); + break; + } + + foreach($params as $paramName => $paramValue) { + switch($paramName) { + case "before": + $result->where("created < ?", $paramValue); + break; + case "after": + $result->where("created > ?", $paramValue); + break; + case 'only_youtube': + if((int) $paramValue != 1) break; + $result->where("link != ?", 'NULL'); + break; } } + if($order_str) + $result->order($order_str); - return new Util\EntityStream("Video", $result->order("$sort")); + return new Util\EntityStream("Video", $result); } function getLastVideo(User $user) diff --git a/Web/Models/sql/get-followers.tsql b/Web/Models/sql/get-followers.tsql index 552aafb8..b07b1c69 100644 --- a/Web/Models/sql/get-followers.tsql +++ b/Web/Models/sql/get-followers.tsql @@ -1,5 +1,5 @@ (SELECT DISTINCT(follower) AS __id FROM - (SELECT follower FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0 + (SELECT follower, flags FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0 LEFT JOIN (SELECT target FROM subscriptions WHERE follower=? AND model="openvk\\Web\\Models\\Entities\\User") u1 ON u0.follower = u1.target WHERE u1.target IS NULL) u2 diff --git a/Web/Models/sql/get-requests.tsql b/Web/Models/sql/get-requests.tsql new file mode 100755 index 00000000..0220e340 --- /dev/null +++ b/Web/Models/sql/get-requests.tsql @@ -0,0 +1,6 @@ + (SELECT DISTINCT(follower) AS __id FROM + (SELECT follower FROM subscriptions WHERE target=? AND flags=0 AND model="openvk\\Web\\Models\\Entities\\User") u0 + LEFT JOIN + (SELECT target FROM subscriptions WHERE follower=? AND flags=0 AND model="openvk\\Web\\Models\\Entities\\User") u1 + ON u0.follower = u1.target WHERE u1.target IS NULL) u2 +INNER JOIN profiles ON profiles.id = u2.__id \ No newline at end of file diff --git a/Web/Presenters/AboutPresenter.php b/Web/Presenters/AboutPresenter.php index 73e4da7f..49ff1c32 100644 --- a/Web/Presenters/AboutPresenter.php +++ b/Web/Presenters/AboutPresenter.php @@ -145,6 +145,6 @@ final class AboutPresenter extends OpenVKPresenter function renderDev(): void { - $this->redirect("https://docs.openvk.uk/"); + $this->redirect("https://docs.ovk.to/"); } } diff --git a/Web/Presenters/AdminPresenter.php b/Web/Presenters/AdminPresenter.php index 3d864e56..b8f480ca 100644 --- a/Web/Presenters/AdminPresenter.php +++ b/Web/Presenters/AdminPresenter.php @@ -48,6 +48,13 @@ final class AdminPresenter extends OpenVKPresenter if(!OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"]) $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) { @@ -76,7 +83,7 @@ final class AdminPresenter extends OpenVKPresenter function renderIndex(): void { - + $this->warnIfLongpoolBroken(); } function renderUsers(): void @@ -154,6 +161,7 @@ final class AdminPresenter extends OpenVKPresenter $club->setShortCode($this->postParam("shortcode")); $club->setVerified(empty($this->postParam("verify") ? 0 : 1)); $club->setHide_From_Global_Feed(empty($this->postParam("hide_from_global_feed") ? 0 : 1)); + $club->setEnforce_Hiding_From_Global_Feed(empty($this->postParam("enforce_hiding_from_global_feed") ? 0 : 1)); $club->save(); break; case "ban": @@ -681,7 +689,8 @@ final class AdminPresenter extends OpenVKPresenter $this->template->obj_type = $obj_type; } - $this->template->logs = (new Logs)->search($filter); + $logs = iterator_to_array((new Logs)->search($filter)); + $this->template->logs = $logs; $this->template->object_types = (new Logs)->getTypes(); } } diff --git a/Web/Presenters/AudioPresenter.php b/Web/Presenters/AudioPresenter.php index 8ec17012..7e2825c1 100644 --- a/Web/Presenters/AudioPresenter.php +++ b/Web/Presenters/AudioPresenter.php @@ -75,7 +75,7 @@ final class AudioPresenter extends OpenVKPresenter if (!$entity || $entity->isBanned()) $this->redirect("/playlists" . $this->user->id); - $playlists = $this->audios->getPlaylistsByClub($entity, $page, 10); + $playlists = $this->audios->getPlaylistsByClub($entity, $page, OPENVK_DEFAULT_PER_PAGE); $playlistsCount = $this->audios->getClubPlaylistsCount($entity); } else { $entity = (new Users)->get($owner); @@ -85,7 +85,7 @@ final class AudioPresenter extends OpenVKPresenter if(!$entity->getPrivacyPermission("audios.read", $this->user->identity)) $this->flashFail("err", tr("forbidden"), tr("forbidden_comment")); - $playlists = $this->audios->getPlaylistsByUser($entity, $page, 9); + $playlists = $this->audios->getPlaylistsByUser($entity, $page, OPENVK_DEFAULT_PER_PAGE); $playlistsCount = $this->audios->getUserPlaylistsCount($entity); } @@ -109,8 +109,8 @@ final class AudioPresenter extends OpenVKPresenter $this->template->mode = $mode; $this->template->page = $page; - - if(in_array($mode, ["list", "new", "popular"]) && $this->user->identity) + + if(in_array($mode, ["list", "new", "popular"]) && $this->user->identity && $page < 2) $this->template->friendsAudios = $this->user->identity->getBroadcastList("all", true); } @@ -142,7 +142,13 @@ final class AudioPresenter extends OpenVKPresenter $this->assertUserLoggedIn(); $group = NULL; + $playlist = NULL; $isAjax = $this->postParam("ajax", false) == 1; + + if(!is_null($this->queryParam("gid")) && !is_null($this->queryParam("playlist"))) { + $this->flashFail("err", tr("forbidden"), tr("not_enough_permissions_comment"), null, $isAjax); + } + if(!is_null($this->queryParam("gid"))) { $gid = (int) $this->queryParam("gid"); $group = (new Clubs)->get($gid); @@ -153,6 +159,19 @@ final class AudioPresenter extends OpenVKPresenter $this->flashFail("err", tr("forbidden"), tr("not_enough_permissions_comment"), null, $isAjax); } + if(!is_null($this->queryParam("playlist"))) { + $playlist_id = (int)$this->queryParam("playlist"); + $playlist = (new Audios)->getPlaylist($playlist_id); + if(!$playlist || $playlist->isDeleted()) + $this->flashFail("err", tr("forbidden"), tr("not_enough_permissions_comment"), null, $isAjax); + + if(!$playlist->canBeModifiedBy($this->user->identity)) + $this->flashFail("err", tr("forbidden"), tr("not_enough_permissions_comment"), null, $isAjax); + + $this->template->playlist = $playlist; + $this->template->owner = $playlist->getOwner(); + } + $this->template->group = $group; if($_SERVER["REQUEST_METHOD"] !== "POST") @@ -196,6 +215,8 @@ final class AudioPresenter extends OpenVKPresenter $lyrics = $this->postParam("lyrics"); $genre = empty($this->postParam("genre")) ? "Other" : $this->postParam("genre"); $nsfw = ($this->postParam("explicit") ?? "off") === "on"; + $is_unlisted = ($this->postParam("unlisted") ?? "off") === "on"; + if(empty($performer) || empty($name) || iconv_strlen($performer . $name) > 128) # FQN of audio must not be more than 128 chars $this->flashFail("err", tr("error"), tr("error_insufficient_info"), null, $isAjax); @@ -206,6 +227,7 @@ final class AudioPresenter extends OpenVKPresenter $audio->setLyrics(empty($lyrics) ? NULL : $lyrics); $audio->setGenre($genre); $audio->setExplicit($nsfw); + $audio->setUnlisted($is_unlisted); try { $audio->setFile($upload); @@ -215,13 +237,18 @@ final class AudioPresenter extends OpenVKPresenter } catch(\RuntimeException $ex) { $this->flashFail("err", tr("error"), tr("ffmpeg_timeout"), null, $isAjax); } catch(\BadMethodCallException $ex) { - $this->flashFail("err", tr("error"), "Загрузка аудио под Linux на данный момент не реализована. Следите за обновлениями: https://github.com/openvk/openvk/pull/512/commits", null, $isAjax); + $this->flashFail("err", tr("error"), "хз", null, $isAjax); } catch(\Exception $ex) { $this->flashFail("err", tr("error"), tr("ffmpeg_not_installed"), null, $isAjax); } $audio->save(); - $audio->add($group ?? $this->user->identity); + + if($playlist) { + $playlist->add($audio); + } else { + $audio->add($group ?? $this->user->identity); + } if(!$isAjax) $this->redirect(is_null($group) ? "/audios" . $this->user->id : "/audios-" . $group->getId()); @@ -233,9 +260,9 @@ final class AudioPresenter extends OpenVKPresenter else $redirectLink .= $this->user->id; - $pagesCount = (int)ceil((new Audios)->getCollectionSizeByEntityId(isset($group) ? $group->getRealId() : $this->user->id) / 10); - $redirectLink .= "?p=".$pagesCount; - + if($playlist) + $redirectLink = "/playlist" . $playlist->getPrettyId(); + $this->returnJson([ "success" => true, "redirect_link" => $redirectLink, @@ -279,7 +306,7 @@ final class AudioPresenter extends OpenVKPresenter function renderSearch(): void { - $this->redirect("/search?type=audios"); + $this->redirect("/search?section=audios"); } function renderNewPlaylist(): void @@ -304,6 +331,8 @@ final class AudioPresenter extends OpenVKPresenter if ($_SERVER["REQUEST_METHOD"] === "POST") { $title = $this->postParam("title"); $description = $this->postParam("description"); + $is_unlisted = (int)$this->postParam('is_unlisted'); + $audios = !empty($this->postParam("audios")) ? array_slice(explode(",", $this->postParam("audios")), 0, 1000) : []; if(empty($title) || iconv_strlen($title) < 1) @@ -313,7 +342,9 @@ final class AudioPresenter extends OpenVKPresenter $playlist->setOwner($owner); $playlist->setName(substr($title, 0, 125)); $playlist->setDescription(substr($description, 0, 2045)); - + if($is_unlisted == 1) + $playlist->setUnlisted(true); + if($_FILES["cover"]["error"] === UPLOAD_ERR_OK) { if(!str_starts_with($_FILES["cover"]["type"], "image")) $this->flashFail("err", tr("error"), tr("not_a_photo")); @@ -427,6 +458,7 @@ final class AudioPresenter extends OpenVKPresenter $title = $this->postParam("title"); $description = $this->postParam("description"); + $is_unlisted = (int)$this->postParam('is_unlisted'); $new_audios = !empty($this->postParam("audios")) ? explode(",", rtrim($this->postParam("audios"), ",")) : []; if(empty($title) || iconv_strlen($title) < 1) @@ -436,6 +468,7 @@ final class AudioPresenter extends OpenVKPresenter $playlist->setDescription(ovk_proc_strtr($description, 2045)); $playlist->setEdited(time()); $playlist->resetLength(); + $playlist->setUnlisted((bool)$is_unlisted); if($_FILES["new_cover"]["error"] === UPLOAD_ERR_OK) { if(!str_starts_with($_FILES["new_cover"]["type"], "image")) @@ -475,12 +508,15 @@ final class AudioPresenter extends OpenVKPresenter $this->template->playlist = $playlist; $this->template->page = $page; + $this->template->cover = $playlist->getCoverPhoto(); + $this->template->cover_url = $this->template->cover ? $this->template->cover->getURL() : "/assets/packages/static/openvk/img/song.jpg"; $this->template->audios = iterator_to_array($playlist->fetch($page, 10)); $this->template->ownerId = $owner_id; $this->template->owner = $playlist->getOwner(); $this->template->isBookmarked = $this->user->identity && $playlist->isBookmarkedBy($this->user->identity); $this->template->isMy = $this->user->identity && $playlist->getOwner()->getId() === $this->user->id; - $this->template->canEdit = $this->user->identity && $playlist->canBeModifiedBy($this->user->identity); + $this->template->canEdit = $this->user->identity && $playlist->canBeModifiedBy($this->user->identity); + $this->template->count = $playlist->size(); } function renderAction(int $audio_id): void @@ -531,16 +567,65 @@ final class AudioPresenter extends OpenVKPresenter break; case "add_to_club": - $club = (new Clubs)->get((int)$this->postParam("club")); - - if(!$club || !$club->canBeModifiedBy($this->user->identity)) - $this->flashFail("err", "error", tr("access_denied"), null, true); - - if(!$audio->isInLibraryOf($club)) - $audio->add($club); - else - $this->flashFail("err", "error", tr("group_has_audio"), null, true); - + $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)) @@ -653,6 +738,28 @@ final class AudioPresenter extends OpenVKPresenter $audios = $stream->page($page, 10); $audiosCount = $stream->size(); break; + case "classic_search_context": + $data = json_decode($this->postParam("context_entity"), true); + + $params = []; + $order = [ + "type" => $data['order'] ?? 'id', + "invert" => (int)$data['invert'] == 1 ? true : false + ]; + + if($data['genre'] && $data['genre'] != 'any') + $params['genre'] = $data['genre']; + + if($data['only_performers'] && (int)$data['only_performers'] == 1) + $params['only_performers'] = '1'; + + if($data['with_lyrics'] && (int)$data['with_lyrics'] == 1) + $params['with_lyrics'] = '1'; + + $stream = $this->audios->find($data['query'], $params, $order); + $audios = $stream->page($page, 10); + $audiosCount = $stream->size(); + break; } $pagesCount = ceil($audiosCount / $perPage); diff --git a/Web/Presenters/GroupPresenter.php b/Web/Presenters/GroupPresenter.php index beeede13..88844fbe 100644 --- a/Web/Presenters/GroupPresenter.php +++ b/Web/Presenters/GroupPresenter.php @@ -233,7 +233,10 @@ final class GroupPresenter extends OpenVKPresenter $club->setEveryone_Can_Create_Topics(empty($this->postParam("everyone_can_create_topics")) ? 0 : 1); $club->setDisplay_Topics_Above_Wall(empty($this->postParam("display_topics_above_wall")) ? 0 : 1); $club->setEveryone_can_upload_audios(empty($this->postParam("upload_audios")) ? 0 : 1); - $club->setHide_From_Global_Feed(empty($this->postParam("hide_from_global_feed")) ? 0 : 1); + + if (!$club->isHidingFromGlobalFeedEnforced()) { + $club->setHide_From_Global_Feed(empty($this->postParam("hide_from_global_feed") ? 0 : 1)); + } $website = $this->postParam("website") ?? ""; if(empty($website)) @@ -279,48 +282,99 @@ final class GroupPresenter extends OpenVKPresenter function renderSetAvatar(int $id) { - $photo = new Photo; + $this->assertUserLoggedIn(); + $this->willExecuteWriteAction(); + $club = $this->clubs->get($id); - if ($club->isBanned()) $this->flashFail("err", tr("error"), tr("forbidden")); - if($_SERVER["REQUEST_METHOD"] === "POST" && $_FILES["ava"]["error"] === UPLOAD_ERR_OK) { + + if(!$club || $club->isBanned() || !$club->canBeModifiedBy($this->user->identity)) + $this->flashFail("err", tr("error"), tr("forbidden"), NULL, true); + + if($_SERVER["REQUEST_METHOD"] === "POST" && $_FILES["blob"]["error"] === UPLOAD_ERR_OK) { try { + $photo = new Photo; + $anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"]; if($anon && $this->user->id === $club->getOwner()->getId()) $anon = $club->isOwnerHidden(); else if($anon) $anon = $club->getManager($this->user->identity)->isHidden(); + $photo->setOwner($this->user->id); $photo->setDescription("Club image"); - $photo->setFile($_FILES["ava"]); + $photo->setFile($_FILES["blob"]); $photo->setCreated(time()); $photo->setAnonymous($anon); $photo->save(); (new Albums)->getClubAvatarAlbum($club)->addPhoto($photo); - $flags = 0; - $flags |= 0b00010000; - $flags |= 0b10000000; + if($this->postParam("on_wall") == 1) { + $post = new Post; + + $post->setOwner($this->user->id); + $post->setWall($club->getId() * -1); + $post->setCreated(time()); + $post->setContent(""); - $post = new Post; - $post->setOwner($this->user->id); - $post->setWall($club->getId()*-1); - $post->setCreated(time()); - $post->setContent(""); - $post->setFlags($flags); - $post->save(); - $post->attach($photo); + $flags = 0; + $flags |= 0b00010000; + $flags |= 0b10000000; - } catch(ISE $ex) { - $name = $album->getName(); - $this->flashFail("err", tr("error"), tr("error_when_uploading_photo")); + $post->setFlags($flags); + $post->save(); + + $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 { $this->assertUserLoggedIn(); diff --git a/Web/Presenters/OpenVKPresenter.php b/Web/Presenters/OpenVKPresenter.php index a171c2e3..a5a76011 100644 --- a/Web/Presenters/OpenVKPresenter.php +++ b/Web/Presenters/OpenVKPresenter.php @@ -274,7 +274,7 @@ abstract class OpenVKPresenter extends SimplePresenter setlocale(LC_TIME, ...(explode(";", tr("__locale")))); if (!OPENVK_ROOT_CONF["openvk"]["preferences"]["maintenanceMode"]["all"]) { - if (OPENVK_ROOT_CONF["openvk"]["preferences"]["maintenanceMode"][$this->presenterName]) { + if ($this->presenterName && OPENVK_ROOT_CONF["openvk"]["preferences"]["maintenanceMode"][$this->presenterName]) { $this->pass("openvk!Maintenance->section", $this->presenterName); } } else { @@ -307,7 +307,7 @@ abstract class OpenVKPresenter extends SimplePresenter $theme = Themepacks::i()[Session::i()->get("_sessionTheme", "ovk")]; } else if($this->requestParam("themePreview")) { $theme = Themepacks::i()[$this->requestParam("themePreview")]; - } else if($this->user->identity !== NULL && $this->user->identity->getTheme()) { + } else if($this->user !== NULL && $this->user->identity !== NULL && $this->user->identity->getTheme()) { $theme = $this->user->identity->getTheme(); } diff --git a/Web/Presenters/SearchPresenter.php b/Web/Presenters/SearchPresenter.php index 02394503..72c34e89 100644 --- a/Web/Presenters/SearchPresenter.php +++ b/Web/Presenters/SearchPresenter.php @@ -1,7 +1,7 @@ users = $users; - $this->clubs = $clubs; + $this->users = new Users; + $this->clubs = new Clubs; $this->posts = new Posts; - $this->comments = new Comments; $this->videos = new Videos; $this->apps = new Applications; - $this->notes = new Notes; $this->audios = new Audios; parent::__construct(); @@ -33,85 +29,101 @@ final class SearchPresenter extends OpenVKPresenter { $this->assertUserLoggedIn(); - $query = $this->queryParam("query") ?? ""; - $type = $this->queryParam("type") ?? "users"; - $sorter = $this->queryParam("sort") ?? "id"; - $invert = $this->queryParam("invert") == 1 ? "ASC" : "DESC"; - $page = (int) ($this->queryParam("p") ?? 1); - + $query = $this->queryParam("q") ?? ""; + $section = $this->queryParam("section") ?? "users"; + $order = $this->queryParam("order") ?? "id"; + $invert = (int) ($this->queryParam("invert") ?? 0) == 1; + $page = (int) ($this->queryParam("p") ?? 1); + # https://youtu.be/pSAWM5YuXx8 + # https://youtu.be/FfNZRhIn2Vk $repos = [ "groups" => "clubs", "users" => "users", "posts" => "posts", - "comments" => "comments", "videos" => "videos", "audios" => "audios", "apps" => "apps", - "notes" => "notes" + "audios_playlists" => "audios" + ]; + $parameters = [ + "ignore_private" => true, ]; - switch($sorter) { - default: - case "id": - $sort = "id " . $invert; - break; - case "name": - $sort = "first_name " . $invert; - break; - case "rating": - $sort = "rating " . $invert; - break; - case "length": - if($type != "audios") break; - - $sort = "length " . $invert; - break; - case "listens": - if($type != "audios") break; - - $sort = "listens " . $invert; - break; + foreach($_REQUEST as $param_name => $param_value) { + 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; + } } - $parameters = [ - "type" => $this->queryParam("type"), - "city" => $this->queryParam("city") != "" ? $this->queryParam("city") : NULL, - "maritalstatus" => $this->queryParam("maritalstatus") != 0 ? $this->queryParam("maritalstatus") : NULL, - "with_photo" => $this->queryParam("with_photo"), - "status" => $this->queryParam("status") != "" ? $this->queryParam("status") : NULL, - "politViews" => $this->queryParam("politViews") != 0 ? $this->queryParam("politViews") : NULL, - "email" => $this->queryParam("email"), - "telegram" => $this->queryParam("telegram"), - "site" => $this->queryParam("site") != "" ? "https://".$this->queryParam("site") : NULL, - "address" => $this->queryParam("address"), - "is_online" => $this->queryParam("is_online") == 1 ? 1 : NULL, - "interests" => $this->queryParam("interests") != "" ? $this->queryParam("interests") : NULL, - "fav_mus" => $this->queryParam("fav_mus") != "" ? $this->queryParam("fav_mus") : NULL, - "fav_films" => $this->queryParam("fav_films") != "" ? $this->queryParam("fav_films") : NULL, - "fav_shows" => $this->queryParam("fav_shows") != "" ? $this->queryParam("fav_shows") : NULL, - "fav_books" => $this->queryParam("fav_books") != "" ? $this->queryParam("fav_books") : NULL, - "fav_quote" => $this->queryParam("fav_quote") != "" ? $this->queryParam("fav_quote") : NULL, - "hometown" => $this->queryParam("hometown") != "" ? $this->queryParam("hometown") : NULL, - "before" => $this->queryParam("datebefore") != "" ? strtotime($this->queryParam("datebefore")) : NULL, - "after" => $this->queryParam("dateafter") != "" ? strtotime($this->queryParam("dateafter")) : NULL, - "gender" => $this->queryParam("gender") != "" && $this->queryParam("gender") != 2 ? $this->queryParam("gender") : NULL, - "doNotSearchPrivate" => true, - "only_performers" => $this->queryParam("only_performers") == "on" ? "1" : NULL, - "with_lyrics" => $this->queryParam("with_lyrics") == "on" ? true : NULL, - ]; - - $repo = $repos[$type] or $this->throwError(400, "Bad Request", "Invalid search entity $type."); + $repo = $repos[$section] or $this->throwError(400, "Bad Request", "Invalid search entity $section."); - $results = $this->{$repo}->find($query, $parameters, $sort); - $iterator = $results->page($page, 14); + $results = NULL; + switch($section) { + default: + $results = $this->{$repo}->find($query, $parameters, ['type' => $order, 'invert' => $invert]); + break; + case 'audios_playlists': + $results = $this->{$repo}->findPlaylists($query, $parameters, ['type' => $order, 'invert' => $invert]); + break; + } + + $iterator = $results->page($page, OPENVK_DEFAULT_PER_PAGE); $count = $results->size(); - $this->template->iterator = iterator_to_array($iterator); + $this->template->order = $order; + $this->template->invert = $invert; + $this->template->data = $this->template->iterator = iterator_to_array($iterator); $this->template->count = $count; - $this->template->type = $type; + $this->template->section = $section; $this->template->page = $page; - $this->template->perPage = 14; + $this->template->perPage = OPENVK_DEFAULT_PER_PAGE; + $this->template->query = $query; + $this->template->atSearch = true; + + $this->template->paginatorConf = (object) [ + "page" => $page, + "count" => $count, + "amount" => sizeof($this->template->data), + "perPage" => $this->template->perPage, + "atBottom" => false, + "tidy" => true, + "space" => 6, + 'pageCount' => ceil($count / $this->template->perPage), + ]; + $this->template->extendedPaginatorConf = clone $this->template->paginatorConf; + $this->template->extendedPaginatorConf->space = 12; } } diff --git a/Web/Presenters/UserPresenter.php b/Web/Presenters/UserPresenter.php index 3785e47b..baf8f046 100644 --- a/Web/Presenters/UserPresenter.php +++ b/Web/Presenters/UserPresenter.php @@ -329,13 +329,15 @@ final class UserPresenter extends OpenVKPresenter $user = $this->users->get((int) $this->postParam("id")); if(!$user) exit("Invalid state"); - $user->toggleSubscription($this->user->identity); + if ($this->postParam("act") == "rej") + $user->changeFlags($this->user->identity, 0b10000000, true); + else + $user->toggleSubscription($this->user->identity); - $this->redirect($user->getURL()); + $this->redirect($_SERVER['HTTP_REFERER']); } - function renderSetAvatar() - { + function renderSetAvatar() { $this->assertUserLoggedIn(); $this->willExecuteWriteAction(); @@ -346,8 +348,8 @@ final class UserPresenter extends OpenVKPresenter $photo->setFile($_FILES["blob"]); $photo->setCreated(time()); $photo->save(); - } catch(ISE $ex) { - $this->flashFail("err", tr("error"), tr("error_upload_failed")); + } catch(\Throwable $ex) { + $this->flashFail("err", tr("error"), tr("error_upload_failed"), NULL, (int)$this->postParam("ajax", true) == 1); } $album = (new Albums)->getUserAvatarAlbum($this->user->identity); @@ -358,23 +360,57 @@ final class UserPresenter extends OpenVKPresenter $flags = 0; $flags |= 0b00010000; - $post = new Post; - $post->setOwner($this->user->id); - $post->setWall($this->user->id); - $post->setCreated(time()); - $post->setContent(""); - $post->setFlags($flags); - $post->save(); - $post->attach($photo); - if($this->postParam("ava", true) == (int)1) { + if($this->postParam("on_wall") == 1) { + $post = new Post; + $post->setOwner($this->user->id); + $post->setWall($this->user->id); + $post->setCreated(time()); + $post->setContent(""); + $post->setFlags($flags); + $post->save(); + + $post->attach($photo); + } + + if((int)$this->postParam("ajax", true) == 1) { $this->returnJson([ - "url" => $photo->getURL(), - "id" => $photo->getPrettyId() + "success" => true, + "new_photo" => $photo->getPrettyId(), + "url" => $photo->getURL(), ]); } else { $this->flashFail("succ", tr("photo_saved"), tr("photo_saved_comment")); } } + + function renderDeleteAvatar() { + $this->assertUserLoggedIn(); + $this->willExecuteWriteAction(); + + $avatar = $this->user->identity->getAvatarPhoto(); + + if(!$avatar) + $this->flashFail("succ", tr("error"), "no avatar bro", NULL, true); + + $avatar->isolate(); + + $newAvatar = $this->user->identity->getAvatarPhoto(); + + if(!$newAvatar) + $this->returnJson([ + "success" => true, + "has_new_photo" => false, + "new_photo" => NULL, + "url" => "/assets/packages/static/openvk/img/camera_200.png", + ]); + else + $this->returnJson([ + "success" => true, + "has_new_photo" => true, + "new_photo" => $newAvatar->getPrettyId(), + "url" => $newAvatar->getURL(), + ]); + } function renderSettings(): void { diff --git a/Web/Presenters/VKAPIPresenter.php b/Web/Presenters/VKAPIPresenter.php index 963c9ccc..9eb4565a 100644 --- a/Web/Presenters/VKAPIPresenter.php +++ b/Web/Presenters/VKAPIPresenter.php @@ -186,8 +186,12 @@ final class VKAPIPresenter extends OpenVKPresenter function renderRoute(string $object, string $method): void { + $callback = $this->queryParam("callback"); $authMechanism = $this->queryParam("auth_mechanism") ?? "token"; if($authMechanism === "roaming") { + if($callback) + $this->fail(-1, "User authorization failed: roaming mechanism is unavailable with jsonp.", $object, $method); + if(!$this->user->identity) $this->fail(5, "User authorization failed: roaming mechanism is selected, but user is not logged in.", $object, $method); else @@ -234,8 +238,14 @@ final class VKAPIPresenter extends OpenVKPresenter } try { - 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 @@ -253,10 +263,16 @@ final class VKAPIPresenter extends OpenVKPresenter $result = json_encode([ "response" => $res, ]); + + if($callback) { + $result = $callback . '(' . $result . ')'; + header('Content-Type: application/javascript'); + } else + header("Content-Type: application/json"); $size = strlen($result); - header("Content-Type: application/json"); header("Content-Length: $size"); + exit($result); } @@ -286,17 +302,31 @@ final class VKAPIPresenter extends OpenVKPresenter $this->fail(28, "Invalid 2FA code", "internal", "acquireToken"); } - $platform = $this->requestParam("client_name"); - - $token = new APIToken; - $token->setUser($user); - $token->setPlatform($platform ?? (new WhichBrowser\Parser(getallheaders()))->toString()); - $token->save(); + $token = NULL; + $tokenIsStale = true; + $platform = $this->requestParam("client_name"); + $acceptsStale = $this->requestParam("accepts_stale"); + if($acceptsStale == "1") { + if(is_null($platform)) + $this->fail(101, "accepts_stale can only be used with explicitly set client_name", "internal", "acquireToken"); + + $token = (new APITokens)->getStaleByUser($uId, $platform); + } + + if(is_null($token)) { + $tokenIsStale = false; + + $token = new APIToken; + $token->setUser($user); + $token->setPlatform($platform ?? (new WhichBrowser\Parser(getallheaders()))->toString()); + $token->save(); + } $payload = json_encode([ "access_token" => $token->getFormattedToken(), "expires_in" => 0, "user_id" => $uId, + "is_stale" => $tokenIsStale, ]); $size = strlen($payload); @@ -304,4 +334,42 @@ final class VKAPIPresenter extends OpenVKPresenter header("Content-Length: $size"); exit($payload); } + + function renderOAuthLogin() { + $this->assertUserLoggedIn(); + + $client = $this->queryParam("client_name"); + $postmsg = $this->queryParam("prefers_postMessage") ?? '0'; + $stale = $this->queryParam("accepts_stale") ?? '0'; + $origin = NULL; + $url = $this->queryParam("redirect_uri"); + if(is_null($url) || is_null($client)) + exit("Error: redirect_uri and client_name params are required."); + + if($url != "about:blank") { + if(!filter_var($url, FILTER_VALIDATE_URL)) + exit("Error: Invalid URL passed to redirect_uri."); + + $parsedUrl = (object) parse_url($url); + if($parsedUrl->scheme != 'https' && $parsedUrl->scheme != 'http') + exit("Error: 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("Error: 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; + } } diff --git a/Web/Presenters/templates/@layout.xml b/Web/Presenters/templates/@layout.xml index e7d62d4b..7edecea5 100644 --- a/Web/Presenters/templates/@layout.xml +++ b/Web/Presenters/templates/@layout.xml @@ -18,9 +18,11 @@ {script "js/l10n.js"} {script "js/openvk.cls.js"} {script "js/node_modules/dashjs/dist/dash.all.min.js"} + {script "js/al_music.js"} {css "js/node_modules/tippy.js/dist/backdrop.css"} + {css "js/node_modules/cropperjs/dist/cropper.css"} {css "js/node_modules/tippy.js/dist/border.css"} {css "js/node_modules/tippy.js/dist/svg-arrow.css"} {css "js/node_modules/tippy.js/themes/light.css"} @@ -85,7 +87,7 @@
-
+
{if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME}{$instance_name}{/if}
{ifset $thisUser} @@ -94,67 +96,45 @@ {_header_log_out}
{else} -