Compare commits

...

289 commits

Author SHA1 Message Date
n1rwana
c8f0ebf44b Merge branch 'master' of https://github.com/openvk/openvk into pr/675 2023-07-28 00:50:27 +03:00
n1rwana
9b4c2cee50 Revert "Merge branch 'master' of https://github.com/openvk/openvk into pr/675"
This reverts commit faef489220.
2023-07-28 00:47:03 +03:00
n1rwana
faef489220 Merge branch 'master' of https://github.com/openvk/openvk into pr/675 2023-07-28 00:44:05 +03:00
Anonymous
a2384cc231
Delete .idea directory (#920) 2023-07-13 23:19:30 +03:00
celestora
7b92ed58e7
Fix bug in photo vk api structure
I forgor that `pid` refers to virtual, not absolute, id
2023-07-13 14:27:07 +03:00
celestora
3db545f91a Fix bugs in photos.getUploadServer and photos.save 2023-07-12 20:49:55 +03:00
Dmitry Tretyakov
79ae72f15a VKAPI: Add NSFW boolean key in Wall.get/Newsfeed.get/Newsfeed.getGlobal 2023-07-12 22:38:01 +07:00
veselcraft
2018131934
Admin: Add password changer for user and password generator
localization coming soon (i hope @lumaeris will do this 4 me)
2023-07-12 02:35:45 +03:00
celestora
ef7b4b6c3e Fix firefox data uri popup cocksex in notes/create 2023-07-07 15:03:38 +03:00
celestora
a4454ab7a0 Re: Fix navigation to "[object Object]" after closing note 2023-07-07 14:52:10 +03:00
celestora
3a1acaeeeb Fix incorrect rejection invocation in Notes.getNote 2023-07-07 14:46:20 +03:00
celestora
c53dce2bea Fix navigation to "[object Object]" after closing note 2023-07-07 14:45:50 +03:00
celestora
49e449e478 Notes: add new note viewing UI for wall 2023-07-06 13:20:49 +03:00
celestora
5ca9e2808c Fix premature iterator closure error in Wall.getMyNotes 2023-07-06 11:47:53 +03:00
celestora
070ea16536 Fix permissions reporting (can_create_topic, can_post) for clubs in vk api 2023-07-06 11:44:46 +03:00
lalka2018
2e76ca16df
Wall: add notes attachments to posts (#907)
* Posts: add notes attachments
2023-07-05 14:54:58 +03:00
Dmitry Tretyakov
b35b87567b VKAPI: Add banned (not only deleted!) user stub
Final fix #622
2023-07-02 13:11:49 +07:00
Österreich Luminota
3ae9def10a
VKAPI: Wall.getById hotfix (for real now)
Resolves GH-909
2023-06-29 22:36:01 +07:00
Vladimir Barinov
050ab43889
API: hotfix 2023-06-29 16:43:56 +03:00
celestora
3d90eb89ec
Make messenger box larger
Addresses #906
2023-06-22 15:56:53 +03:00
lalka2018
59554172d6
Поддержка видео с YouTube в API и фикс board.php (#905) 2023-06-22 14:22:07 +03:00
lalka2018
2fed1d9fef
Подсказки в поиске (#901) 2023-06-22 12:39:25 +03:00
celestora
53fa14d89e
Use UA description instead of "api" in default API platform
OpenVK will try to guess OS and name of library which was used to make token generation request to more accurately represent.. stuff.
2023-06-17 14:25:21 +03:00
Dmitry Tretyakov
d9b595d250 VKAPI: Split group member names in Groups.getMembers 2023-06-16 16:11:05 +07:00
Dmitry Tretyakov
da5ce0a53c Add "Hide" & "Show details" strings for OpenVK Mobile and correct "Like" strings 2023-06-16 11:12:59 +07:00
Dmitry Tretyakov
a9d686b3a5 теперь я из гх десктопа, я очень надеюсь, что бот закончит спамить 2023-06-15 19:43:09 +07:00
Dmitry Tretyakov
46eaba1cb2
Update uk.strings 2023-06-15 19:20:53 +07:00
Dmitry Tretyakov
2db815d004
Fix syntax 2023-06-15 19:20:05 +07:00
Dmitry Tretyakov
32bcf1f77f
Add Russian strings for OpenVK Mobile 2023-06-15 19:19:04 +07:00
Dmitry Tretyakov
0d8b7b8181
Add "Settings" strings for OpenVK Mobile 2023-06-15 19:18:13 +07:00
Dmitry Tretyakov
0d167ac18b
New localization strings for OpenVK Mobile web theme (#897)
* New localization strings for OpenVK Mobile web theme

* New Russian localization strings for OpenVK Mobile web theme

* New Ukrainian localization strings for OpenVK Mobile web theme

* Move some English strings to 'Mobile' category

* Move Russian strings to 'Mobile' category

* Move Ukrainian strings to 'Mobile' category

* Add missing strings

* Add missing EN strings

* Update uk.strings
2023-06-15 12:15:15 +03:00
aqukie
0a4032eaed
Locales: Update Ukrainian & Belorusian (#895)
* Add translation of Search

* Fixed typo and stupid translation

* Translate of Belorusian locale (1\2)

* Translate Search on Belorusian (narkomovka)

* Да монав я робити назву, ща тіко птушка чекне, чи є помилки і мержайте

* хотфікс типу

* Знаешь знаешь чем харьковские отличаються от днепропетровских?

* Знаю, 200 километров разницы между ними

* 290... Но отличаются они тем, что днепропетровские думают как нажить, а харьковские где их oбъeбyют
2023-06-14 11:34:23 +03:00
Dmitry Tretyakov
851c4e29f7
Lazyfixdown: Increase margin between right of sidebar and left of content 2023-06-14 15:29:58 +07:00
Mikita Wiśniewski
fdb0735041
OpenVK Modern: fix search options (#899) 2023-06-14 08:22:24 +00:00
lalka2018
3e69d06474
VKAPI: методы для подарков, заметок, статусов, обсуждений и немного для фоток и групп (#876)
* API methods for gifts, notes, statuses

* Some fixes

Строки локализации у gifts.send теперь не костыльные и можно прикрепить до 10 аттачей к посту

* Small imp

Пофиксил пагинацию у заметков и подарок
Перенёс структуру заметок
Добавил аттачи к комментариям
Добавил проверку на удалённость аттача
Ну и пофиксил сортировку заметок

* VKAPI: Some methods for topics and photos

Добавлены методы для обсуждений (addTopic, closeTopic(), createComment(), deleteComment(), deleteTopic(), editTopic(), fixTopic(), getComments(), getTopics(), openTopic(), unfixTopic())
и для фотографий (createAlbum(), editAlbum(), getAlbums(), getAlbumsCount(), getById(), get(), deleteAlbum(), edit(), delete(), deleteComment(), createComment(), getAll(), getComments())

* fixsex
2023-06-13 21:03:43 +03:00
lalka2018
dd97ded326
Search: some fixes (#896) 2023-06-13 20:57:49 +03:00
celestora
05bac50057
Fix adapative resizing on club pages 2023-06-11 14:48:59 +03:00
Österreich Luminota
0b1d2d577a
SQL: Fix agent card editing 2023-06-11 16:29:36 +07:00
Österreich Luminota
1423006bf1
Global: Change docs URL 2023-06-11 15:33:15 +07:00
Österreich Luminota
a5c1db0106
Admin: Add dark more toggle
yay dark mode

The switch can be turned off after a page reload even if the dark theme
is enabled. But hey, at least it works!

After updating the code, go to the "Web/static/js" folder and run the
`yarn install` command.
2023-06-11 14:02:59 +07:00
Österreich Luminota
1da9ff312c
JS: Bump jQuery ver to 3.0.0
Hopefully resolves GHSA-gxr4-xjj5-5px2 and GHSA-rmxg-73gg-4p98

After updating the code, PLEASE go to the "Web/static/js" folder and run
the `yarn install` command.
2023-06-11 13:59:34 +07:00
Österreich Luminota
6d276e061a
OpenVK Midnight: Resolve new issues, add README 2023-06-11 00:23:55 +07:00
NoPlagiarism
04bca818b8
VKAPI: Re-add Utils.resolveScreenName (#886) 2023-06-10 15:56:07 +00:00
lalka2018
f9f6270da0
Search: Add search with parameters (#880)
* Search with parameters

* Small improvements

* Small improvements no.2

Поиск теперь нормально выглядит на других темах
При поиске по группам отображается количество участников там
Костыль с пропуском постов возвращён но немного изменён
Добавлен костыль к комментариям чтобы не показывались комменты с удалённых постов и не было бага как в вепуровке
Добавлены ключи в советском и имперском языке для моих прошлых пуллов (ну и этого конечно)

* Fix debilny oshibky

Убрал лишние ключи локализации, исправил панель поеска и исправил hometown

* fiksy bagiv

* ok

* ok 2

* ok

* rrrrrrrrrrrrrrrr

Добавил параметры в vkapi users.search(), пофиксил проверку коммерции, сузил параметры ещё больше и добавил анимацию выдвижения поиска чтобы красиво было
2023-06-10 18:54:02 +03:00
ayato
8fb1be6be6
locales: Kazakh: Update locale (#894) 2023-06-10 18:07:50 +03:00
lalka2018
f898e973a2
Fix 500 on repost in vkapi (#893) 2023-06-08 15:36:05 +03:00
Österreich Luminota
7962a4efd7
Videos: Remove unnecessary attribute in the video tag 2023-06-06 11:40:21 +00:00
Vladimir Barinov
8c1a680f84
Revert "[WIP] Add Utils.resolveScreenName (#884)" (#885)
This reverts commit 051fb316a4.
2023-06-03 11:14:39 +03:00
NoPlagiarism
051fb316a4
[WIP] Add Utils.resolveScreenName (#884)
* Add Utils.resolveScreenName

* Codestyle fix in Utils.resolveScreenName
2023-06-03 00:37:08 +03:00
NoPlagiarism
ef89309a89
[API] Implement timestamp filter in Newsfeed API (#809)
* Implement timestamp filter in Newsfeed API

* Merge new parameter into Newsfeed.get
2023-06-01 13:04:18 +03:00
NoPlagiarism
12c41dcdd6
[API] Fix null user in Likes.isLiked + requireUser in some funcs + return 1 in Polls (#801)
* Fix returning 200 response for non-existing user

* requireUser in getCounters and createComment

* return 1, where it should in Polls API
2023-05-30 11:41:03 +03:00
Österreich Luminota
803a57ef4b
OpenVK Midnight: Fix for new sticky comment box 2023-05-30 12:18:33 +07:00
Österreich Luminota
8d67aaba8a
Auth: Autofocus 2FA key input field 2023-05-30 12:16:23 +07:00
celestora
ccb392b228 Make "copy video link to clipboard" button work 2023-05-28 20:20:28 +03:00
aqukie
9e80ce16f1
UK locale update pack (#882)
* Update uk.strings

Translate 866d6a8c45 commit

* Update uk.strings

Я забув, які там коміти, приймайте коротше
2023-05-28 15:30:08 +03:00
Ilya Prokopenko
c803a6ff62
Videos: Add upload disabling 2023-05-27 13:31:20 +07:00
celestora
d365bb6b95 Hide small right/left block once they're scrolled out of view 2023-05-26 18:21:56 +03:00
celestora
43541b9136 Make comment box sticky 2023-05-26 18:13:47 +03:00
Ilya Prokopenko
95a62f56e0
Auth: Add disabling the password reset page 2023-05-26 16:27:40 +07:00
Ilya Prokopenko
29716660c7
OpenVK Midnight: Change the example backdrop image 2023-05-26 16:27:04 +07:00
Ilya Prokopenko
8f23720c1f
Auth: Hide captcha when commitcaptcha is disabled 2023-05-26 16:26:19 +07:00
Vladimir Barinov
4213217063
L10n: Ukrainian: Add missing strings 2023-05-26 00:42:58 +03:00
Ilya Prokopenko
4d996fddac
reason -> disablingReason and some fixes 2023-05-23 20:31:32 +07:00
Ilya Prokopenko
4bfdeddd22
Video: Fast addition of a video with its file name
For example, how would you know there were spoilers for Steins;Gate if
the video was titled "Unnamed Video.ogv"?
2023-05-23 19:28:21 +07:00
Ilya Prokopenko
8c98359904
OpenVK Midnight: Add changes related to the player 2023-05-22 21:38:31 +07:00
Ilya Prokopenko
9eeb710e47
BSDN: Some fixes 2023-05-22 21:37:37 +07:00
lalka2018
8ccb98473f
Репост в группу (#873) 2023-05-21 18:38:39 +03:00
lalka2018
cb6578228e
Нормальная смена аватарок как в старом вк (#874)
* Fast avatar changing

* Fixed changing avatar from settings

* fixed otstup
2023-05-14 23:49:33 +03:00
ayato
e7f5c203b6
English: Tour: Minor fixes (#875)
Grammar stufffffffffffffffffffffffffffff
2023-05-14 23:48:02 +03:00
Ilya Prokopenko
5ec34267cc
Rename style.css to main.css
That's more accurate.
2023-05-08 09:09:00 +07:00
Ilya Prokopenko
dfc4f932cb
Add z-index to .cookies-popup 2023-05-08 09:06:18 +07:00
Dmitry Tretyakov
3f5849c2a9 focus-pocus formatinus еще раз 2023-05-01 17:54:25 +07:00
Dmitry Tretyakov
34971d6217 Rules link fix 2023-05-01 17:27:44 +07:00
Dmitry Tretyakov
6fc61717bb Removing instance rules to /terms, leaving a link to them 2023-05-01 17:12:55 +07:00
Dmitry Tretyakov
06c384205d Add "Rules" link in page footer 2023-05-01 16:51:10 +07:00
Mikita Wiśniewski
1b6e45f969
Groups: move Statistics from Edit tab to its' own profile link (#844)
* Groups: move Statistics from Edit tab to its' own profile link

after all, it doesn't have anything to do with changing the groups' admins, removing followers and changing name/description

* Update EditBackdrop.xml

oops
2023-04-30 17:40:06 +03:00
Anonymous
3fb97d5f09
Fix link to terms in locales (#850) 2023-04-30 17:34:23 +03:00
Dmitry Tretyakov
eb227db6a9 VKAPI: Add profiles and groups verification in Newsfeed.get and Wall.get 2023-04-29 23:33:58 +07:00
veselcraft
725e68d0e5
VKAPI: Add owner_id support to video.get (not sure if this works cuz it's hot outside and i'm tired) 2023-04-29 16:05:37 +03:00
Ilya Prokopenko
27fb0f1d90
Move the instance list to the wiki (RU) 2023-04-27 01:47:52 +00:00
Ilya Prokopenko
4a366b987a
Move the instance list to the wiki 2023-04-27 01:46:38 +00:00
Dmitry Tretyakov
62b93a3cf2
File picker buttons redesign (#870)
* Refactoring login and register button in sidebar

For Firefox 10.0 and other old 2000s browsers compatibillity

* CSS friends grid workaround for 2000s browsers

* CSS friends grid full fix

* Revert "CSS friends grid full fix"

This reverts commit 9d64cd2d2b.

* restyling file pickers button

* redefined some strings

* да блин

* Translating "Browse" string

* locale syntax and logic fixes

* Update uk.strings

* Added redesign in Photo section

* Structuring commits and adding redisgn in Video section
2023-04-24 22:50:26 +07:00
Dmitry Tretyakov
252ff0ead0
Fix weak password string syntax error 2023-04-20 11:00:08 +07:00
Dmitry Tretyakov
0ceac912ef
(Полу)фикс отображения краткого списка друзей по сетке для Firefox 10.0 и Opera 8.53 (#865)
* Refactoring login and register button in sidebar

For Firefox 10.0 and other old 2000s browsers compatibillity

* CSS friends grid workaround for 2000s browsers
2023-04-19 19:47:05 +03:00
Локоток
2f8612bb24
Проверка на надежный пароль (#862)
* Проверка на надежный пароль

* fixed typos in locales/ru.strings

Co-authored-by: Alexander Minkin <weryskok@gmail.com>

* подправил локаль

---------

Co-authored-by: Alexander Minkin <weryskok@gmail.com>
2023-04-19 14:02:33 +03:00
Dmitry Tretyakov
b89bbdcec2
Update README.md 2023-04-19 17:44:01 +07:00
Dmitry Tretyakov
34b585747f
Refactoring login and register button in sidebar (#864)
For Firefox 10.0 and other old 2000s browsers compatibillity
2023-04-19 11:12:05 +03:00
Dmitry Tretyakov
027a38a69c
Unlined Tinelix OVK Instance without TLS 2023-04-19 14:39:22 +07:00
Dmitry Tretyakov
770e73fc72
Added Tinelix OVK Instance 2023-04-19 14:37:37 +07:00
Ilya Prokopenko
1a56b09430
Change notify sound metadata and reduce its size 2023-04-11 15:43:30 +07:00
veselcraft
940da6eb99
Global: Fix annoying Tracy errors and php notices 2023-03-29 01:08:01 +03:00
ayato
7cf4082691
Grammary Grammar Thingies Fix (Now INSIDE OpenVK (wow)) (#857)
ProTip! Great commit summaries contain fewer than 50 characters. Place extra information in the extended description.
2023-03-28 01:21:26 +03:00
veselcraft
eb439b278c
ёбаный в рот а файл забыл добавить 2023-03-13 17:46:54 +03:00
veselcraft
2ffa4fd916
VKAPI: Add force online activity reporting on Newsfeed.get and Messages.send functions (can be disabled trough forGodSakePleaseDoNotReportAboutMyOnlineActivity parameter) 2023-03-13 17:45:45 +03:00
veselcraft
05a423b30b
лее братан эта че за хуйня (оно 500 ошибку вызывало) 2023-03-09 13:49:36 +03:00
veselcraft
a3db17fc36
whoops, i forgor to add localized string 2023-03-03 21:32:56 +03:00
veselcraft
866d6a8c45
Messenger: Add a warning about privacy settings if user to whom he sends a message cannot reply (it annoys me a lot) 2023-03-03 21:30:36 +03:00
veselcraft
543c46696d
VKAPI: 500 error hotfix 2023-03-02 12:09:33 +03:00
Ilya Prokopenko
299e5c6354
Locales: uk hotfix 2023-03-01 20:43:27 +07:00
aqukie
04444c8354
Ukrainian localization: Updates and fixes (#856)
* Translate

* Added myself as a translator & corrected the name

* Видалено зайвий пробіл

* Update uk.strings
2023-02-28 21:07:02 +02:00
ayato
4a5f1f0019
Readme Grammar Thingies Fix (#855)
ржу
2023-02-27 13:03:57 +00:00
celestora
6abdb0d593
Further improvements to TRichText
Fix link parsing + remove zalgo completely
2023-02-25 18:08:17 +02:00
celestora
56fe715bce
Fix ';' error in posts 2023-02-25 18:07:29 +02:00
veselcraft
185c007b50
BSDN: Tweak design 2023-02-24 21:06:34 +03:00
Ilya Prokopenko
699996210d
Require BOTH imagick and gd
Confused? So am I. gd is still used to process a photo, such as when
creating a new wall post.
2023-02-23 21:46:12 +07:00
veselcraft
9d6e81f990
oops 2023-02-21 00:59:52 +03:00
veselcraft
54092eb6c0
Video: Add section for full url to video, also little refactor and fix compatibility for VK4ME 2023-02-21 00:35:10 +03:00
celestora
0da679fae6
Use simpler regex in TRichText::removeZalgo
lmao i forgor character class selectors are a thing
2023-02-17 14:53:45 +02:00
Ilya Prokopenko
668d4f2ada
Require imagick instead of gd 2023-02-11 16:24:15 +07:00
celestora
cee1b4c8c1
Fix another funny xss thing (not tested sinve since im bisexual 💀) 2023-02-10 08:50:21 +02:00
Vladimir Barinov
cf558d57c5
Fix funny xss thing (not tested sinve since im in hospital 💀) 2023-02-10 08:46:29 +02:00
celestora
0f2a88aa68 Add rate limits for API too 2023-02-08 13:20:50 +02:00
celestora
01bd8f938c Disallow API access to banned users
lmao??
2023-02-08 13:14:47 +02:00
celestora
cbec4b549f Minor improvements in video attachments 2023-02-07 22:45:30 +02:00
veselcraft
7ed870c3c6
whoops 2023-02-03 14:23:22 +03:00
veselcraft
c6c8e8e175
Global: add OpenVK Refresh info 2023-02-03 14:22:19 +03:00
veselcraft
06b77ebad8
VKAPI: Add support for attachments for comments 2023-01-31 14:49:12 +03:00
veselcraft
43e18a9173
VKAPI: Add support for Videos 2023-01-31 14:23:34 +03:00
veselcraft
700dcc121d
VKAPI: descuido del desarrollador momento, number tres
Somehow for Friends.getRequests extended parameter was required to use fiels. Now it's not
2023-01-31 02:40:29 +03:00
celestora
aca48e726e 4 2023-01-29 19:16:10 +02:00
celestora
beb8b4eaa6 Move photo page url resolution to model
мб пофиксит 842 янеипу
2023-01-29 19:14:29 +02:00
celestora
5401871b38 name translit: Add global filter to leave CJK characters intact
Fixes #834
2023-01-29 18:57:19 +02:00
celestora
c6a77de234 Cleanup useless copies in processVideo.sh + completely change everything to mp4 2023-01-28 23:22:01 +02:00
celestora
4845f8f318 Fix Video::getThumbnailURL regex meow
Fixes #841
2023-01-28 23:09:05 +02:00
celestora
934bc9b25c Fix some more contextmenu prikoli in bsdnplayer 2023-01-28 23:05:49 +02:00
celestora
344ba53acd
Смешной мясной плеер (#839)
* Add video title under post attachment box

* Add VK-styled player

* bideoplayer on video page now

sosiski

* Fix context menu display on Chrome

* Videos now use H.264 instead of Theora

hazbin hotel nude mo

а вообще тут мог бы быть AV1, но пидорасы из купертино фанаты libx264
2023-01-28 22:25:00 +02:00
dependabot[bot]
5436656aed
Bump ua-parser-js from 0.7.28 to 0.7.33 in /Web/static/js (#838)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-28 08:22:04 +00:00
ayato
db433b627f
Locales: English: Fix grammar (#833) 2023-01-28 08:20:09 +00:00
Ilya Prokopenko
4cb8d2d69d
Push changes when any branch is changed 2023-01-28 08:10:26 +00:00
Ilya Prokopenko
6d26269873
Change the secret name for the workflow 2023-01-28 08:05:02 +00:00
celestora
63f302b2ab Use preg_match_all in Video::saveFile
На самом деле там должен был быть и так preg_match_all но видимо какао((
2023-01-28 00:27:16 +02:00
veselcraft
8dffa8a3f5
User: Add 'about' translated string 2023-01-18 22:08:18 +03:00
Vitaly Orekhov
37d720c906
General fixes to controls (#831)
* General fixes to controls

Purpose: make input controls consistent with default OpenVK color scheme (which is grayscale)

Additional fixes:
- checkboxes are 15x14, not 14x14.

* Change tints to more UX-friendly

- checkboxes and radio buttons are blue when checked
- enables hover-checked state use in atlas
2023-01-18 22:05:33 +03:00
Vitaly Orekhov
80f3a7810c
Improve default theme assets consistency (#830)
Loading animation should be gray instead of blue, to match with default OpenVK theme palette.
2023-01-14 21:21:21 +03:00
Ilya Prokopenko
437fba393d
Add an action for proper repository mirroring
Do roughly the same with your repositories if you have exactly the same
copies of the repositories on other forges (GitLab, Codeberg, etc.).

:^)
2023-01-13 08:50:45 +07:00
veselcraft
e492f89327
Locale: Revert stupid ru locale changes 2023-01-11 13:48:51 +03:00
Weblate (bot)
f25235a7bb
Translations update from Hosted Weblate (#819)
* Translated using Weblate (Kazakh)

Currently translated at 97.9% (948 of 968 strings)

Translation: OpenVK/OpenVK
Translate-URL: https://hosted.weblate.org/projects/openvk/openvk/kk/

* Translated using Weblate (Russian)

Currently translated at 99.8% (967 of 968 strings)

Translation: OpenVK/OpenVK
Translate-URL: https://hosted.weblate.org/projects/openvk/openvk/ru/

* Translated using Weblate (Russian)

Currently translated at 99.9% (1098 of 1099 strings)

Translation: OpenVK/OpenVK
Translate-URL: https://hosted.weblate.org/projects/openvk/openvk/ru/

* Translated using Weblate (Russian)

Currently translated at 99.9% (1098 of 1099 strings)

Translation: OpenVK/OpenVK
Translate-URL: https://hosted.weblate.org/projects/openvk/openvk/ru/

Co-authored-by: ayaao <myrgdream@gmail.com>
Co-authored-by: Артём Котлубай <artemkotlubai@yandex.ru>
2023-01-11 13:33:13 +03:00
Evgeniy Khramov
4f3167195b
Fixed rounded avatars in .tippy-content (#826) 2023-01-09 10:50:47 +00:00
celestora
ea3f869473
Remove hardcoded instance url from Tour.xml
капелька кетчуп
2023-01-08 15:15:16 +02:00
Daniel
6c8b6e4aa9
Site Tour: sdyuhugjhltrisijdgotdrjksfljgdfhkjg 2023-01-08 13:43:04 +03:00
Daniel
d02bc91507
Locales: Site Tour (EN) 2023-01-08 12:44:29 +03:00
Daniel
d8a8da6c62
Site Tour: Update (2)
Попросили
2023-01-08 12:16:48 +03:00
Daniel
3edf0587a2
Site Tour: add demo pics (3)
Попросили
2023-01-08 12:14:46 +03:00
Daniel
b2cbb20624
Site Tour. Locales: Update ru.strings 2023-01-08 11:18:56 +03:00
Daniel
f229e11dea
Site Tour: Update 2023-01-08 11:18:09 +03:00
veselcraft
ed028d1a2e
funny hot🥵🔥🔥🔥🔥🔥fix 2023-01-08 01:01:48 +03:00
veselcraft
504ec0dd2d
Global: Add ~~euro~~tour to the website
Co-authored-by: Daniel <60743585+myslivets@users.noreply.github.com>
2023-01-08 00:53:25 +03:00
Mikita Wiśniewski
5293af6c7f
Quick-fix for 'Login failed' error box displaying wrongly (fixes #820) (#821)
This removes the negative margin-top for .knowledgeBaseArticle and adds a margin-top:0 property for h1 tag (which is used only once IIRC)
2023-01-07 23:04:11 +03:00
ZAZiOs
c67607c3f9
Страница регистрации из 2007г (#788)
* Update @layout.xml

* Update @layout.xml

* Update Register.xml

* Update Login.xml

* Update style.css

* Update en.strings

* Update ru.strings

* Update ru_old.strings

* Update ru_sov.strings

* h2 -> .header2

* h2 + header2 styling

* Update Register.xml

* Update Register.xml

* Update Login.xml

* Welcome message addiction

* трахать

* +reg-welcome

* +reg-welcome

* +reg-welcome

* +reg-welcome (mixed up that with old rus one)

* +reg-welcome

* Adding UK localization

* Delete settings.json

* На всякий и тут обновлю на id в layout

Нужно для изменения иконок при создании тем.

* Update @layout.xml

* Minor changes

* minor changes

* Fixes requested by celestora

* пепяо с табами

* пепяо с табами
2023-01-07 22:52:50 +03:00
Daniel
90b2b0acac
Site Tour: JS 2023-01-07 19:24:35 +03:00
Daniel
5660a370a8
Site Tour: update CSS 2023-01-07 19:23:16 +03:00
Daniel
63f402d179
Site Tour: Add demo pics (2)
I forgot English WordArt version of "примеры тем" xddddd
2023-01-06 22:42:39 +03:00
Daniel
d6c8ba5319
Site Tour: Add demo pictures 2023-01-06 22:29:47 +03:00
Daniel
9f1aeb0db9
Site Tour: Add right sidebar icons 2023-01-06 16:08:20 +03:00
Daniel
3156ce2ecc
Site Tour: CSS 2023-01-06 14:31:39 +03:00
waeknowing
7111554de5
Update list.yml - serbian native name fix (#822)
Sprski --> Srpski
Спрски --> Српски
2023-01-05 22:35:35 +03:00
Ilya Prokopenko
dbff40d44b
OpenVK Midnight: forgot about that 2022-12-30 14:44:28 +07:00
Ilya Prokopenko
7e4df2971c
OpenVK Midnight: A xmas hat is back! 2022-12-30 14:38:38 +07:00
Ilya Prokopenko
7d6a52a7e0
Update messages.svg 2022-12-30 07:20:20 +00:00
veselcraft
3b0a69ebaa
VKAPI: Wall.get now require user 2022-12-22 01:05:49 +03:00
celestora
e834fae0fe
Update Photo.php 2022-12-21 01:26:00 +02:00
celestora
3c4f41e58c
Return more absolute url in Photo::getSizes::type::url 2022-12-21 01:20:17 +02:00
Ilya Prokopenko
687f2937d2
Locales: learn_more 2022-12-18 13:25:52 +07:00
Ilya Prokopenko
9a9c74f127
OpenVK Midnight: Fix-up tippy box 2022-12-18 13:23:46 +07:00
Ilya Prokopenko
9f6cec83a8
OpenVK Midnight: Make icons visible again 2022-12-18 13:03:00 +07:00
Dmitry Tretyakov
2d0d6b9ff1
Fix typo in Groups.getByID (#811) 2022-12-17 14:31:37 +03:00
veselcraft
c546ac9f9c
Wall: fix
Тгинки викъки я-я
Иъки ыиъикм
Этсуисбияки
Гртани ляпакаыртии
Тгинки викъки я-я
Иъки ыиъикм
Этсуисбияки
ъ
Й
ъ
Й
Абвлщжеэйкащащштрире
Лищтяйтюатеащфвщтеречтые
Атоаэтыхатахщш хитыре
Эретпеальтащат
Ящпщатакалте кштылфапатал
Эщтебыжоио гргырылыблыбла
Шаткаватыпафалываказылыв
Абывдажывжфдтиывладлвыолбя
А, б, в, г, д, е, ё
Жлзгырапааыэ
Ггинки викъки я-я
Иъки ыиъикм
Этсуисбияки
Гртани ляпакаыртии
Тгинки викъки я-я
Иъки ыиъикм
Этсуисбияки
ъ
Й
Кырдылырбыробаыв
Сарывгртибыоодгыла
Эййэйисщастилисанки
Кэрппэлыпвдиыэрты
Ха-ха-ха
Прарпррарпар
Даыдаэйфкей
Ыфдкеывке
Эфщщкыфи
Эрэ-эрэ-эрэ-эрэ
Гартыфрафыра
Рыфвпывоотвы
Ывоапвылропв
Ырлоптвыалофыо
Фждыподлфы
Пррфыплдовт
ъ
2022-12-17 03:20:14 +03:00
veselcraft
462d667e53
Global: Implement showing platform 2022-12-17 02:03:02 +03:00
celestora
768f834ea1 Use hermite filter instead of point for image resize
Fixes terrific image distortion during downscaling
2022-12-14 23:10:15 +02:00
ayaao
fb8007c47f
OpenVK Modern: Fix appearence of profile bg images (#807) 2022-12-14 23:18:16 +03:00
celestora
65a232b6ef Experiment(Photo): add "quick" image saving method to increase teh upload speedz
если это сработает то я буду ржать :)
2022-12-14 22:15:29 +02:00
celestora
8caf57d7b0
Fix avatar link in notifs 2022-12-13 18:57:02 +02:00
celestora
ea1c618489 Revert "Experiment(Photo): use imagick+cloning instead of gd2"
коммит говно мы его удаляем
2022-12-13 01:03:54 +02:00
celestora
918ff0e0f0 Experiment(Photo): use imagick+cloning instead of gd2
АААААААААААА
2022-12-13 00:47:25 +02:00
Ilya Prokopenko
c387a0ff0a
OpenVK Midnight: Support backdrops and some fixes
aerowalk
2022-12-12 16:43:32 +07:00
Ilya Prokopenko
af8b564d29
Locales: Hotfix new backdrop strings 2022-12-12 16:26:12 +07:00
Daniel
c359a57059
Update style.css
Смешные ошибки стрелочек в блоках, выпуск №3
2022-12-12 11:06:46 +03:00
celestora
f9083edfc4
Add l-lacker social-styled page backrops (#805)
чокопай
2022-12-12 01:23:42 +02:00
Daniel
8700ad8179
Update style.css
Add ".navigation .link_soon" class
Понадобится в будущем ;)
2022-12-12 00:36:49 +03:00
celestora
d321b524fd Make comment notifications more informative
Quote length increased to 400 also tags are being stripped now
2022-12-11 16:27:46 +02:00
celestora
25be996256 Send less useless mention notifications
Users will now receive mention notifs only if they're mentioned outside of their content
2022-12-11 16:26:12 +02:00
celestora
8a893daec0 Add mention notifications
Draft implementation of mention notif
2022-12-10 21:33:13 +02:00
celestora
475f637413
uncockify youtube embed 2022-12-09 00:14:15 +02:00
Daniel
a058605be8
Fixing action buttons under users avatar
Сделал все кнопки (которые находятся под аватаром пользователя) единой ширины.
Мои глаза просто устали видеть неравномерную ширину кнопок :c
2022-12-07 22:10:14 +03:00
ayaao
c77f8fdb49
locales: Kazakh: Update (#784)
6e17c446bf
dc6d0e7374
83be7eed10
a935b2ca31
125c6b1b63
753be2aaff
c09694b568
e9e72ff237
f2ca6be4d5
f5b1890645
1eb74bbafd
2022-12-05 16:45:17 +03:00
celestora
b70710e164
Return valid id for Comment in wall.getComments
Fixes #790, probably?
2022-11-28 17:20:27 +02:00
ZAZiOs
a6ab5d38bb
Add margin to comment field for vids
Этот коммит добавляет небольшое расстояние между блоком с Комментариями и Информацией. Так смотрится в разы лучше!
2022-11-21 22:13:46 +02:00
Daniel
e4cdef23b4
Update donate.ru.md 2022-11-21 11:10:47 +03:00
Daniel
52a2e2222d
Update for the least visited page of the site
"i forgor" moment
2022-11-21 11:09:58 +03:00
Daniel
bea3d42c6c
Update for the least visited page of the site
lmao lol
2022-11-21 11:08:18 +03:00
sdzkHelince
02c300cb3a
Russian Soviet: Fixed typos (#785)
"my_page", "privacy_setting_access_page", "voucher_bad", "voucher_good"
2022-11-17 20:21:04 +02:00
celestora
ff1452dacf
Декоммунизация ru_sov.strings 2022-11-17 20:14:01 +02:00
celestora
b1534afd22
Replace old scary ternary operator with if/else
time to do the fani
2022-11-16 23:45:06 +02:00
celestora
bf1a443c99
Explicitly forbid posts made by clubs be a deactivation message 2022-11-16 23:39:48 +02:00
veselcraft
d33a8f5c1d
Page Edit: Fix PHP 7.4 compatibility 2022-11-16 18:40:37 +03:00
Ilya Prokopenko
f420d2381d
Locales: proper string names for appjs I think
gpg: signing failed: Timeout
gpg: [stdin]: clear-sign failed: Timeout
2022-11-16 22:27:11 +07:00
celestora
d09e647d1d
amogus fix
should work now with weblate xddd
2022-11-14 17:01:56 +02:00
celestora
55ec291650
Allow __transNames to be empty 2022-11-14 16:47:34 +02:00
celestora
3c632f3910
фикс дюпа алмазов в служебном апи) 2022-11-14 16:22:23 +02:00
Ilya Prokopenko
0a4b7225ea
Translated using Weblate (Russian)
Currently translated at 99.8% (947 of 948 strings)

Translation: OpenVK/OpenVK
Translate-URL: https://hosted.weblate.org/projects/openvk/openvk/ru/
2022-11-14 15:07:31 +01:00
AlesAlte
d279ed08e9
Translated using Weblate (Ukrainian)
Currently translated at 99.8% (947 of 948 strings)

Translation: OpenVK/OpenVK
Translate-URL: https://hosted.weblate.org/projects/openvk/openvk/uk/
2022-11-14 15:06:04 +01:00
Evgeniy Khramov
7c05d252b7
Translated using Weblate (Russian)
Currently translated at 100.0% (948 of 948 strings)

Translation: OpenVK/OpenVK
Translate-URL: https://hosted.weblate.org/projects/openvk/openvk/ru/
2022-11-14 15:06:04 +01:00
Vladimír
0fc0670c7c
Translated using Weblate (Russian)
Currently translated at 100.0% (948 of 948 strings)

Translation: OpenVK/OpenVK
Translate-URL: https://hosted.weblate.org/projects/openvk/openvk/ru/
2022-11-14 15:06:04 +01:00
Evgeniy Khramov
be0a0aed89
Translated using Weblate (Russian)
Currently translated at 100.0% (948 of 948 strings)

Translation: OpenVK/OpenVK
Translate-URL: https://hosted.weblate.org/projects/openvk/openvk/ru/
2022-11-13 09:48:32 +01:00
Ilya Prokopenko
eb73b2dc5a
Rename knowledgebase files for Armenian to .hy
forgot about it somehow
2022-11-12 19:59:58 +07:00
Vladimir Lapskiy
24b2b1d7f0
VKAPI: new method account.saveProfileInfo (again) (#777) 2022-11-10 15:27:54 +02:00
Dmitry Tretyakov
559619f7c8
Add API documentation link for clients (#779)
* Add API documentation link for clients

* API abbr in upper
2022-11-10 15:25:10 +03:00
Ilya Prokopenko
c73974ad0d
Update German translation files
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
2022-11-10 10:16:40 +00:00
149a6aa61d
Locales: Ukrainian: AlesAlte moment
Ukrainian locale now inherits Russian locale again.
2022-11-10 12:46:48 +03:00
Ilya Prokopenko
4c5a2dabfa
Locales: su > ru_sov 2022-11-08 23:01:06 +07:00
Ilya Prokopenko
a2a0cfb2ad
Locales: am > hy
To comply with the ISO 639-1 standard, it was decided to rename the
Armenian localization file name to "hy.strings" to avoid confusion with
Amharic.

Don't ask why.
2022-11-08 22:59:29 +07:00
Ilya Prokopenko
790fb20fd8
Admin: Rename duplicated string for voucher rating
xylitol
2022-11-08 21:51:23 +07:00
n1rwana
1eb74bbafd
Карточка Агента Поддержки (#717) 2022-11-07 23:36:07 +02:00
Jaroslaw
882b2e5d39
ukie moment 2022-11-07 23:11:45 +02:00
veselcraft
ab51482558
VKAPI: Join/leave group 2022-11-06 16:06:57 +03:00
veselcraft
aaf24b166e
VKAPI: Global feed trough Newsfeed.getGlobal func 2022-11-06 14:38:21 +03:00
Ilya Prokopenko
fbb26ef256
Display the first three languages in the footer
I also changed the flag for English to Great Britain

Closes #769
2022-11-06 10:20:00 +07:00
46d3e97e05
CI: Add rdkafka support to base apache image 2022-11-02 20:56:52 +03:00
n1rwana
e8c0fe8c05
Fixed table names in the Chandler editor (#772) 2022-11-02 15:48:35 +03:00
n1rwana
f5b1890645
Редактор Chandler (#721)
* Chandler Editor

* Features
2022-11-02 13:45:49 +03:00
lvl
403ee44b8e
Create points.am.md (#762)
Points Armenian
2022-11-02 13:36:34 +03:00
lvl
8d1a3a6d44
Create rules.am.md (#765)
Rules Armenian
2022-11-02 13:36:29 +03:00
lvl
4ff82d4850
Create privacy.am.md (#761)
Privacy Armenian
2022-11-02 13:36:08 +03:00
lvl
f71785c4a9
Create faq.am.md (#763)
FAQ Armenian
2022-11-02 13:36:01 +03:00
lvl
5ab846eebc
Create notes.am.md (#764)
Notes Armenian
2022-11-02 13:35:56 +03:00
lvl
1f0f4b5d0b
Create about.am.md (#766)
About Armenian
2022-11-02 13:35:50 +03:00
Artem Vetrov
677e147688
CI for OpenVK, Kubernetes and Docker deployments (#735)
* Kubernetes deployment

* Update kubernetes deployment

* Fix rewrite module load

* Fix mysql-primary bootstrap

* Fix mysql init-db apply order

* Fix init-db.sql permissions

* Fix MySQL missing *.sql import

* Switch from MySQL to MariaDB

* [skip ci] Example deployment update

* Set root app in chandler configmap

* Update missing php extension in base images

* Update missing dependency in apache image

* Remove default site configuration

* [skip ci] Split Kubernetes deployments by type

* Explicitly set persistent volume for openvk storage

* [skip ci] Add README for Kubernetes

* Replace old docker(-compose) files w/ new ones

* Add README for docker usage

* [skip ci] Update README.md and README_RU.md

* [skip ci] Fix eventdb DB name

* [skip ci] Kubernetes configmap: missing namespace

* [skip ci] Fix typo

* [skip ci] Ignore chandler.yml

* [skip ci] Missing /var/log/openvk volume

* [skip ci] Workaround for Docker <=20.10.6

* [skip ci] Handle permissions for apache2

* [skip ci] Initial Kafka support

* [skip ci] Kafka values for Kubernetes
2022-10-30 14:44:14 +03:00
Vitaly Orekhov
d2b2e41328
Shut up notification listener on false triggers (#760)
За-йо-бал трещать ложными уведомлениями. Использует подкостыль из (https://github.com/openvk/openvk/issues/694#issuecomment-1229165601)[комментария].
2022-10-27 12:44:46 +03:00
veselcraft
6b2a6ae8cd
asdjioashjgvkiushfgniukowrehjgiulwrhboi 2022-10-25 13:18:19 +03:00
celestora
6c29d40099
Fix nullpointerexception in user::renderView 2022-10-24 23:09:11 +03:00
veselcraft
a63833c9fa
VKAPI: لقد سئمت من الخروج بتعليقات للالتزامات 2022-10-24 19:05:41 +03:00
veselcraft
7c516a8089
Messages: @celestora had forgotten about existing of deleted messages for correspondencies sql file, so i fixed it in her place 2022-10-23 12:38:51 +03:00
veselcraft
944d338034
minecraft sex mode 2022-10-22 17:30:35 +03:00
veselcraft
c3610cdd4d
VKAPI: Fix 500 error if some peers_id is invalid 2022-10-22 16:52:19 +03:00
veselcraft
ca27e0588a
VKAPI: Typo in Friends.delete 2022-10-22 16:41:54 +03:00
veselcraft
98260e8d0a
даже не знаю как это комментировать, наверно потому-что какой-то девушке залили в пизду бензин и родился камаз 2022-10-17 23:24:10 +03:00
veselcraft
890d320d43
L18n: Russian: Change UNIX local settings since ru_UA is not really needed 2022-10-15 21:03:49 +03:00
veselcraft
28d74f528d
L18n: Russian: Fix grammar errors, он-лайн >> онлайн, тульпы >> друзья 2022-10-15 21:02:37 +03:00
veselcraft
f3e69c107e
VKAPI: Add new methods: Polls.getById, Polls.addVote, Polls.deleteVote 2022-10-14 17:37:36 +03:00
celestora
02ef397561
Create qqx.strings 2022-10-12 17:28:55 +03:00
FineWork
5d47d51a44
Update ua.strings: polls (#747)
Я додав переклад для опитувань
2022-10-12 15:00:20 +03:00
Ilya Prokopenko
cc88db2cc1
OpenVK Midnight: Polls! 2022-10-12 17:40:47 +07:00
veselcraft
30f930c04c
bruh 2022-10-12 12:25:09 +03:00
veselcraft
51d9aba5a6
VKAPI: Fix newsfeed 2022-10-12 12:23:57 +03:00
veselcraft
dffa6d799e
Polls: Fix compatibility with old PHP 2022-10-12 01:01:25 +03:00
veselcraft
9e3b80dcb4
VKAPI: Add support for polls in wall funcs 2022-10-12 01:00:52 +03:00
celestora
efbb6a74e7 Allow unregistered users to see ended poll results 2022-10-11 21:26:08 +03:00
celestora
6345a62744 Mark selected options in poll with bolder font 2022-10-11 21:20:36 +03:00
celestora
5b5f64997b Fix wall bugs spawned after recent change in chandler 2022-10-11 21:08:21 +03:00
celestora
499ab3f758 Fix 404 error when opening voters list with tabs 2022-10-11 20:32:56 +03:00
celestora
0e22d1866f Fix certain poll functions
Polls can now be set to quiz mode correctly, weird characters in options no longer crash OpenVK
2022-10-11 20:24:34 +03:00
celestora
5203229e9e
Fix pelmeni moment 2022-10-11 19:34:28 +03:00
celestora
f2ca6be4d5
Add polls (#743) 2022-10-11 19:04:43 +03:00
veselcraft
d8a8dd920a
Global: add mobile theme support for dumbphones
By setting defaultFeaturePhoneTheme, you can enable very basic html theme for old phones, like Nokia 6680 or iPhone 3GS. I'm currently making this theme, but it would take a time to make it ofc
2022-10-11 13:42:48 +03:00
Ilya Prokopenko
3f87bab3f2
OpenVK Midnight: FULLY color the tippy.js boxes
I didn't see that coming
2022-10-11 17:35:42 +07:00
veselcraft
699df76619
VKAPI: Make offset actually work in groups.get method
Users: add count and offset (acting like switch for literal offset) param to getClubs
2022-10-11 03:46:49 +03:00
veselcraft
7b1182f4e8
VKAPI: Fix wall.get unworkability if owner id is less than zero
I was wrong about last commit. I just fixed is_member field to show up by force
2022-10-11 03:25:20 +03:00
veselcraft
307b7709cc
VKAPI: Fix Groups.get unworkability if owner id is less than zero 2022-10-11 03:23:02 +03:00
Ilya Prokopenko
da41006ddb
OpenVK Midnight: Color the tippy.js boxes 2022-10-10 17:55:24 +07:00
Jaroslaw
e9e72ff237
English, Ukrainian localization: Added and corrected spelling errors (#741) 2022-10-09 23:18:14 +03:00
ayaao
c1ce514869
/themepacks/readme.md: fix grammar (#740) 2022-10-09 15:47:23 +00:00
veselcraft
13606493b6
VKAPI: Add new method Groups.Search 2022-10-09 18:06:43 +03:00
veselcraft
b3aa8e41b1
VKAPI: Fix some stupid code quirks 2022-10-09 17:39:22 +03:00
celestora
8c314adf6c Fix popup colors in signatures 2022-10-09 12:31:19 +03:00
celestora
dfee425deb Add hovers for post signatures and wall ids
Resolves #739
2022-10-09 11:34:57 +03:00
celestora
c09694b568 Localize "posted in ..." messages
Also preliminary solved #739
2022-10-08 23:47:21 +03:00
celestora
a950a2d706 y7tughtuy 2022-10-08 23:25:09 +03:00
Ilya Prokopenko
2704ba0ec3
OpenVK Midnight: Fix-up inputs, add new stuff 2022-10-04 21:18:55 +07:00
celestora
6907c55b4a
Delete composer.lock 2022-10-03 21:57:50 +03:00
veselcraft
56da94358f
VKAPI: Fix 500 error when birthday is not set at all 2022-10-01 13:13:50 +03:00
Ilya Prokopenko
1370c68b11
Imbibe themepack "Midnight" into master (#733)
wiwajapanigobugarin
2022-10-02 19:47:34 +03:00
lvl
d1ceb0da25
Update am.strings (#737)
исправлен недоперевод (543 строка)

Co-authored-by: Vladimir Barinov <veselcraft@icloud.com>
2022-09-30 16:53:01 +03:00
lvl
1921c7224c
Update am.strings (#736) 2022-09-30 16:52:32 +03:00
Maxim Leshchenko
7d72cd182b
Users: Do not show the request tab in the list of other user's friends
Closes #605
2022-09-23 17:10:29 +02:00
Maxim Leshchenko
729784ecff
Groups: Display links not only for managed groups in My Groups
Closes #664
2022-09-23 16:32:17 +02:00
Maxim Leshchenko
6eeb05236e
Russian: Fix typo in page edit 2022-09-20 16:16:34 +02:00
veselcraft
b38f4f2346
Longpoll: Add wait parameter as in original VK 2022-09-19 17:13:37 +03:00
veselcraft
697a365205
Longpool: Update format for use in modern clients 2022-09-19 16:56:04 +03:00
Vladimir Barinov
283d8c1c2d
Install SQLs: Fix MariaDB compatibility 2022-09-17 00:25:44 +03:00
n1rwana
5b3a1f50e9
Shortcode aliases (#690)
* Shortcode aliases

* Fixes

* Newline

* Fixes + SQLs renamed

* Fixed clubs aliases
2022-09-17 00:21:29 +03:00
n1rwana
753be2aaff
Maintenance (#719) 2022-09-17 00:19:46 +03:00
Vladimir Lapskiy
ed7faa4a16
VKAPI: Friends.getRequests added some parameters that i forgot about and fix count requests in the response (#724)
* VKAPI: Friends.getRequests method did not give more than 6 elements

* VKAPI: Friends.getRequests i'm forget add some parameters LOL
2022-09-17 00:05:25 +03:00
Jaroslaw
1aed452a12
Ukrainian: translation of new functions (#728)
* Correction of localization errors

* Переклад: Блокування посилань

* Add @AlesAlte to contributor Ukrainian locale

* Update ua.strings

* Small fix
2022-09-17 00:04:35 +03:00
n1rwana
2cdcb0dc4e
Ticket deletion fixed (#727)
* Ticket deletion fixed

* ?act=list
2022-09-14 13:27:48 +03:00
celestora
384c0da78b Revert Clubs::getPopularClubs change
This functionality is so unstable it causes majority of installations to fail miserably out of the box. The performance also is a major concern. Might re-add later. Sometime.

btw i amended
2022-09-12 20:51:41 +03:00
n1rwana
e28964c813 Now only unnecessary PhpStorm files are removed👉🏻👈🏻 2022-09-08 00:22:15 +03:00
n1rwana
38e51a2b9d Deleted the files added by PhpStorm in the previous commit 2022-09-08 00:06:17 +03:00
Артём
89db5e2192
The user must agree to the rules in order to register. (#716) 2022-09-08 00:02:11 +03:00
n1rwana
585616c88f
Fix of #667 (#714) 2022-09-05 23:02:43 +03:00
361 changed files with 19056 additions and 4965 deletions

58
.github/workflows/build-base.yaml vendored Normal file
View file

@ -0,0 +1,58 @@
name: Build base images
on:
schedule:
- cron: '0 0 * * *'
env:
BASE_IMAGE_NAME: php
BASE_IMAGE_VERSION: "8.1"
jobs:
build-cli:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
lfs: false
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2
- 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
docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_NAME . --push -f install/automated/docker/base-php-cli.Dockerfile --build-arg VERSION=$BASE_IMAGE_VERSION
build-apache:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
lfs: false
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2
- 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
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

64
.github/workflows/build.yaml vendored Normal file
View file

@ -0,0 +1,64 @@
name: Build images
on:
push:
# Publish `master` as Docker `latest` image.
branches:
- master
# Publish `v1.2.3` tags as releases.
tags:
- v*
env:
BASE_IMAGE_NAME: openvk
DB_IMAGE_NAME: mariadb
EVENT_IMAGE_NAME: mariadb
DB_VERSION: "10.9"
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
arch: ['x86_64']
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v3
with:
lfs: false
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2
- 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=$(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//')
[ "$VERSION" == "master" ] && VERSION=latest
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 }}
- name: Build MariaDB primary image
run: |
IMAGE_NAME=ghcr.io/${{ github.repository }}/$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
docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_NAME . --push -f install/automated/docker/mariadb-eventdb.Dockerfile --build-arg VERSION=$DB_VERSION

15
.github/workflows/codeberg-mirror.yml vendored Normal file
View file

@ -0,0 +1,15 @@
name: Codeberg Mirroring
on: push
jobs:
to_codeberg:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: pixta-dev/repository-mirroring-action@v1
with:
target_repo_url: "git@codeberg.org:openvk/openvk.git"
ssh_private_key: ${{ secrets.CODEBERG_MIRRORSSH }}

4
.gitignore vendored
View file

@ -1,5 +1,6 @@
vendor
openvk.yml
chandler.yml
update.pid
update.pid.old
Web/static/js/node_modules
@ -10,5 +11,8 @@ tmp/*
themepacks/*
!themepacks/.gitkeep
!themepacks/openvk_modern
!themepacks/midnight
storage/*
!storage/.gitkeep
.idea

8
.idea/.gitignore vendored
View file

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View file

@ -1,46 +0,0 @@
<?xml encoding="UTF-8"?>
<!ELEMENT latte (tags,filters,variables,functions)>
<!ATTLIST latte vendor #REQUIRED>
<!ATTLIST latte version #REQUIRED>
<!ELEMENT tags (tag)+>
<!ELEMENT tag (arguments)?>
<!ATTLIST tag name CDATA #REQUIRED>
<!ATTLIST tag type (PAIR|UNPAIRED|UNPAIRED_ATTR|ATTR_ONLY|AUTO_EMPTY) #REQUIRED>
<!ATTLIST tag allowedFilters (true|false) #IMPLIED>
<!ATTLIST tag arguments CDATA #IMPLIED>
<!ATTLIST tag deprecatedMessage CDATA #IMPLIED>
<!ATTLIST tag multiLine (true|false) #IMPLIED>
<!ELEMENT arguments (argument)+>
<!ELEMENT argument EMPTY>
<!ATTLIST argument name #REQUIRED>
<!ATTLIST argument types CDATA #REQUIRED>
<!ATTLIST argument repeatable (true|false) #IMPLIED>
<!ATTLIST argument required (true|false) #IMPLIED>
<!ATTLIST argument validType #IMPLIED>
<!ELEMENT filters (filter)+>
<!ELEMENT filter EMPTY>
<!ATTLIST filter name #REQUIRED>
<!ATTLIST filter description CDATA #IMPLIED>
<!ATTLIST filter arguments CDATA #IMPLIED>
<!ATTLIST filter insertColons #IMPLIED>
<!ELEMENT variables (variable)+>
<!ELEMENT variable EMPTY>
<!ATTLIST variable name #REQUIRED>
<!ATTLIST variable type CDATA #REQUIRED>
<!ELEMENT functions (function)+>
<!ELEMENT function EMPTY>
<!ATTLIST function name #REQUIRED>
<!ATTLIST function arguments CDATA #REQUIRED>
<!ATTLIST function returnType #REQUIRED>
<!ATTLIST function description CDATA #IMPLIED>

View file

@ -1,290 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE latte PUBLIC "-//LATTE//Latte plugin XML V0.0.1//EN" "Latte.dtd">
<latte vendor="latte" version="1">
<tags>
<tag name="_" type="AUTO_EMPTY" allowedFilters="true">
<arguments>
<argument name="expression" types="PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="=" type="UNPAIRED" allowedFilters="true">
<arguments>
<argument name="expression" types="PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="block" type="AUTO_EMPTY" allowedFilters="true" multiLine="true">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="breakIf" type="UNPAIRED">
<arguments>
<argument name="condition" types="PHP_CONDITION" validType="bool" required="true" />
</arguments>
</tag>
<tag name="capture" type="PAIR" allowedFilters="true" multiLine="true">
<arguments>
<argument name="variable" types="VARIABLE_DEFINITION" required="true" />
</arguments>
</tag>
<tag name="case" type="UNPAIRED">
<arguments>
<argument name="condition" types="PHP_CONDITION" required="true" repeatable="true" />
</arguments>
</tag>
<tag name="catch" type="UNPAIRED">
<arguments>
<argument name="condition" types="PHP_CONDITION" validType="bool" required="true" />
</arguments>
</tag>
<tag name="contentType" type="UNPAIRED">
<arguments>
<argument name="content-type" types="CONTENT_TYPE" validType="string" required="true" />
</arguments>
</tag>
<tag name="continueIf" type="UNPAIRED">
<arguments>
<argument name="condition" types="PHP_CONDITION" validType="bool" required="true" />
</arguments>
</tag>
<tag name="debugbreak" type="UNPAIRED">
<arguments>
<argument name="expression" types="PHP_EXPRESSION" required="true" />
</arguments>
</tag>
<tag name="default" type="UNPAIRED">
<arguments>
<argument name="variable" types="VARIABLE_DEFINITION_EXPRESSION" required="true" repeatable="true" />
</arguments>
</tag>
<tag name="define" type="PAIR" multiLine="true">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" required="true" />
<argument name="variable" types="VARIABLE_DEFINITION_ITEM" repeatable="true" />
</arguments>
</tag>
<tag name="do" type="UNPAIRED">
<arguments>
<argument name="expression" types="PHP_EXPRESSION" required="true" />
</arguments>
</tag>
<tag name="dump" type="UNPAIRED">
<arguments>
<argument name="expression" types="PHP_EXPRESSION" required="true" />
</arguments>
</tag>
<tag name="else" type="UNPAIRED_ATTR" />
<tag name="elseif" type="UNPAIRED">
<arguments>
<argument name="condition" types="PHP_CONDITION" validType="bool" required="true" />
</arguments>
</tag>
<tag name="elseifset" type="UNPAIRED">
<arguments>
<argument name="var" types="VARIABLE,BLOCK" validType="string" required="true" />
</arguments>
</tag>
<tag name="extends" type="UNPAIRED">
<arguments>
<argument name="file" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION,NONE" validType="string" required="true" />
</arguments>
</tag>
<tag name="first" type="PAIR">
<arguments>
<argument name="width" types="PHP_IDENTIFIER,PHP_EXPRESSION" validType="int" required="true" />
</arguments>
</tag>
<tag name="for" type="PAIR" arguments="initialization; condition; afterthought" multiLine="true" />
<tag name="foreach" type="PAIR" arguments="expression as [$key =>] $value" allowedFilters="true" multiLine="true" />
<tag name="if" type="PAIR">
<arguments>
<argument name="condition" types="PHP_CONDITION" validType="bool" required="true" />
</arguments>
</tag>
<tag name="ifset" type="PAIR">
<arguments>
<argument name="var" types="VARIABLE,BLOCK,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="import" type="UNPAIRED">
<arguments>
<argument name="file" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="include" type="UNPAIRED" allowedFilters="true">
<arguments>
<argument name="file" types="BLOCK,IDENTIFIER,PHP_EXPRESSION" validType="string" required="true" />
<argument name="arguments" types="KEY_VALUE" repeatable="true" />
</arguments>
</tag>
<tag name="includeblock" type="UNPAIRED">
<arguments>
<argument name="file" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="l" type="UNPAIRED" />
<tag name="last" type="PAIR">
<arguments>
<argument name="width" types="PHP_IDENTIFIER,PHP_EXPRESSION" validType="int" required="true" />
</arguments>
</tag>
<tag name="layout" type="UNPAIRED">
<arguments>
<argument name="file" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION,NONE" validType="string" required="true" />
</arguments>
</tag>
<tag name="class" type="ATTR_ONLY" arguments="class" />
<tag name="attr" type="ATTR_ONLY" arguments="attr" />
<tag name="ifcontent" type="ATTR_ONLY" />
<tag name="php" type="UNPAIRED">
<arguments>
<argument name="expression" types="PHP_EXPRESSION" required="true" />
</arguments>
</tag>
<tag name="r" type="UNPAIRED" />
<tag name="sandbox" type="UNPAIRED">
<arguments>
<argument name="file" types="BLOCK,PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" validType="string" required="true" />
<argument name="key-value" types="KEY_VALUE" repeatable="true" />
</arguments>
</tag>
<tag name="sep" type="PAIR">
<arguments>
<argument name="width" types="PHP_IDENTIFIER,PHP_EXPRESSION" validType="int" />
</arguments>
</tag>
<tag name="snippet" type="PAIR" multiLine="true">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" validType="string" />
</arguments>
</tag>
<tag name="snippetArea" type="PAIR" multiLine="true">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="spaceless" type="PAIR" />
<tag name="switch" type="PAIR" multiLine="true">
<arguments>
<argument name="expression" types="PHP_EXPRESSION" />
</arguments>
</tag>
<tag name="syntax" type="PAIR" arguments="off | double | latte" multiLine="true" />
<tag name="templatePrint" type="UNPAIRED">
<arguments>
<argument name="class-name" types="PHP_CLASS_NAME" />
</arguments>
</tag>
<tag name="templateType" type="UNPAIRED">
<arguments>
<argument name="class-name" types="PHP_CLASS_NAME" required="true" />
</arguments>
</tag>
<tag name="try" type="PAIR" />
<tag name="rollback" type="UNPAIRED" />
<tag name="tag" type="ATTR_ONLY">
<arguments>
<argument name="expression" types="PHP_EXPRESSION" required="true" validType="string" repeatable="true" />
</arguments>
</tag>
<tag name="ifchanged" type="PAIR">
<arguments>
<argument name="expression" types="PHP_EXPRESSION" required="true" repeatable="true" />
</arguments>
</tag>
<tag name="skipIf" type="UNPAIRED">
<arguments>
<argument name="condition" types="PHP_CONDITION" validType="bool" required="true" />
</arguments>
</tag>
<tag name="var" type="UNPAIRED">
<arguments>
<argument name="variable" types="VARIABLE_DEFINITION_EXPRESSION" required="true" repeatable="true" />
</arguments>
</tag>
<tag name="trace" type="UNPAIRED" />
<tag name="varPrint" type="UNPAIRED" arguments="all" />
<tag name="varType" type="UNPAIRED">
<arguments>
<argument name="file" types="PHP_TYPE" required="true" />
<argument name="variable" types="VARIABLE_DEFINITION" required="true" />
</arguments>
</tag>
<tag name="while" type="PAIR" multiLine="true">
<arguments>
<argument name="condition" types="PHP_CONDITION" validType="bool" required="true" />
</arguments>
</tag>
<tag name="iterateWhile" type="PAIR" multiLine="true" />
<tag name="embed" type="PAIR" multiLine="true">
<arguments>
<argument name="file" types="BLOCK_USAGE,PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" validType="string" required="true" />
<argument name="key-value" types="KEY_VALUE" repeatable="true" />
</arguments>
</tag>
<!-- @deprecated - latte -->
<tag name="assign" type="UNPAIRED" arguments="$variable = expr" />
<tag name="truncate" type="UNPAIRED" arguments="expression" deprecatedMessage="Tag {? ...} is deprecated in Latte 2.4. For variable definitions use {var ...} or {php ...} in other cases." />
</tags>
<filters>
<filter name="truncate" arguments=":($length, $append = '…')" description="shortens the length preserving whole words" insertColons=":" />
<filter name="substr" arguments=":($offset [, $length])" description="returns part of the string" insertColons=":" />
<filter name="trim" arguments=":($charset = mezery)" description="strips whitespace or other characters from the beginning and end of the string" />
<filter name="stripHtml" arguments="" description="removes HTML tags and converts HTML entities to text" />
<filter name="strip" arguments="" description="removes whitespace" />
<filter name="indent" arguments=":($level = 1, $char = '\t')" description="indents the text from left with number of tabs" />
<filter name="replace" arguments=":($search, $replace = '')" description="replaces all occurrences of the search string with the replacement" insertColons=":" />
<filter name="replaceRE" arguments=":($pattern, $replace = '')" description="replaces all occurrences according to regular expression" insertColons=":" />
<filter name="padLeft" arguments=":($length, $pad = ' ')" description="completes the string to given length from left" insertColons=":" />
<filter name="padRight" arguments=":($length, $pad = ' ')" description="completes the string to given length from right" insertColons=":" />
<filter name="repeat" arguments=":($count)" description="repeats the string" insertColons=":" />
<filter name="implode" arguments=":($glue = '')" description="joins an array to a string" />
<filter name="webalize" description="adjusts the UTF-8 string to the shape used in the URL" />
<filter name="breaklines" description="inserts HTML line breaks before all newlines" />
<filter name="reverse" description="reverse an UTF-8 string or array" />
<filter name="length" description="returns length of a string or array" />
<filter name="sort" description="simply sorts array" />
<filter name="reverse" description="array sorted in reverse order (used with |sort)" />
<filter name="batch" arguments=":($array, $length [, $item])" description="returns length of a string or array" insertColons="::" />
<filter name="clamp" description="returns value clamped to the inclusive range of min and max." insertColons="::" />
<filter name="lower" description="makes a string lower case" />
<filter name="upper" description="makes a string upper case" />
<filter name="firstUpper" description="makes the first letter upper case" />
<filter name="capitalize" description="lower case, the first letter of each word upper case" />
<filter name="date" arguments=":($format)" description="formats date" insertColons=":" />
<filter name="number" arguments=":($decimals = 0, $decPoint = '.', $thousandsSep = ',')" description="format number" />
<filter name="bytes" arguments=":($precision = 2)" description="formats size in bytes" />
<filter name="dataStream" arguments=":($mimetype = 'detect')" description="Data URI protocol conversion" />
<filter name="noescape" description="prints a variable without escaping" />
<filter name="escapeurl" description="escapes parameter in URL" />
<filter name="nocheck" description="prevents automatic URL sanitization" />
<filter name="checkurl" description="sanitizes string for use inside href attribute" />
<filter name="query" description="generates a query string in the URL" />
<filter name="ceil" arguments=":(int $precision = 0)" description="rounds a number up to a given precision" />
<filter name="explode" arguments=":(string $separator = '')" description="splits a string by the given delimiter" />
<filter name="first" description="returns first element of array or character of string" />
<filter name="floor" arguments=":(int $precision = 0)" description="rounds a number down to a given precision" />
<filter name="join" arguments=":(string $glue = '')" description="joins an array to a string" />
<filter name="last" description="returns last element of array or character of string" />
<filter name="random" description="returns random element of array or character of string" />
<filter name="round" arguments=":(int $precision = 0)" description="rounds a number to a given precision" />
<filter name="slice" arguments=":(int $start, int $length = null, bool $preserveKeys = false)" description="extracts a slice of an array or a string" insertColons=":" />
<filter name="spaceless" description="removes whitespace" />
<filter name="split" arguments=":(string $separator = '')" description="splits a string by the given delimiter" />
</filters>
<functions>
<function name="clamp" returnType="int|float" arguments="(int|float $value, int|float $min, int|float $max)" description="clamps value to the inclusive range of min and max" />
<function name="divisibleBy" returnType="bool" arguments="(int $value)" description="checks if a variable is divisible by a number" />
<function name="even" returnType="bool" arguments="(int $value)" description="checks if the given number is even" />
<function name="first" returnType="mixed" arguments="(string|array $value)" description="returns first element of array or character of string" />
<function name="last" returnType="mixed" arguments="(string|array $value)" description="returns last element of array or character of string" />
<function name="odd" returnType="bool" arguments="(int $value)" description="checks if the given number is odd" />
<function name="slice" returnType="string|array" arguments="(string|array $value, int $start, int $length = null, bool $preserveKeys = false)" description="extracts a slice of an array or a string" />
</functions>
</latte>

View file

@ -1,59 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE latte PUBLIC "-//LATTE//Latte plugin configuration XML V0.0.1//EN" "Latte.dtd">
<latte version="1" vendor="nette/application">
<tags>
<!-- nette/application tags -->
<tag name="cache" type="PAIR" arguments="if => expr, key, …">
<arguments>
<argument name="name[:part]" types="KEY_VALUE" validType="string" required="true" />
<argument name="arguments" types="PHP_EXPRESSION" repeatable="true" />
</arguments>
</tag>
<tag name="control" type="UNPAIRED">
<arguments>
<argument name="name[:part]" types="PHP_IDENTIFIER,PHP_EXPRESSION" validType="string" required="true" />
<argument name="arguments" types="PHP_EXPRESSION" repeatable="true" />
</arguments>
</tag>
<tag name="link" type="UNPAIRED">
<arguments>
<argument name="destination" types="LINK_DESTINATION,PHP_EXPRESSION" validType="string" required="true" />
<argument name="arguments" types="LINK_PARAMETERS" repeatable="true" />
</arguments>
</tag>
<tag name="href" type="ATTR_ONLY">
<arguments>
<argument name="destination" types="LINK_DESTINATION,PHP_EXPRESSION" validType="string" required="true" />
<argument name="arguments" types="LINK_PARAMETERS" repeatable="true" />
</arguments>
</tag>
<tag name="nonce" type="ATTR_ONLY" />
<tag name="plink" type="UNPAIRED">
<arguments>
<argument name="destination" types="LINK_DESTINATION,PHP_EXPRESSION" validType="string" required="true" />
<argument name="arguments" types="LINK_PARAMETERS" repeatable="true" />
</arguments>
</tag>
<!-- @deprecated - nette/application -->
<tag name="ifCurrent" type="PAIR" deprecatedMessage="Tag {ifCurrent} is deprecated in Latte 2.6. Use custom function isLinkCurrent() instead.">
<arguments>
<argument name="destination" types="LINK_DESTINATION,PHP_EXPRESSION" validType="string" required="true" />
<argument name="arguments" types="LINK_PARAMETERS" repeatable="true" />
</arguments>
</tag>
</tags>
<variables>
<variable name="control" type="\Nette\Application\UI\Control" />
<variable name="basePath" type="string" />
<variable name="baseUrl" type="string" />
<variable name="flashes" type="mixed[]" />
<variable name="presenter" type="\Nette\Application\UI\Presenter" />
<variable name="iterator" type="\Latte\Runtime\CachingIterator" />
<variable name="form" type="\Nette\Application\UI\Form" />
<variable name="user" type="\Nette\Security\User" />
</variables>
<functions>
<function name="isLinkCurrent" returnType="bool" arguments="(string $destination = null, $args = [])" />
<function name="isModuleCurrent" returnType="bool" arguments="(string $moduleName)" />
</functions>
</latte>

View file

@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE latte PUBLIC "-//LATTE//Latte plugin configuration XML V0.0.1//EN" "Latte.dtd">
<latte version="1" vendor="nette/forms">
<tags>
<tag name="form" type="PAIR" multiLine="true">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="formContainer" type="PAIR" multiLine="true">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="formPrint" type="UNPAIRED">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="input" type="UNPAIRED">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,CONTROL,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="inputError" type="UNPAIRED">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,CONTROL,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="label" type="AUTO_EMPTY">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,CONTROL,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="name" type="ATTR_ONLY">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,CONTROL,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
</tags>
</latte>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/openvk.iml" filepath="$PROJECT_DIR$/.idea/openvk.iml" />
</modules>
</component>
</project>

View file

@ -1,46 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/vendor/rybakit/msgpack" />
<excludeFolder url="file://$MODULE_DIR$/vendor/chillerlan/php-qrcode" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/cache" />
<excludeFolder url="file://$MODULE_DIR$/vendor/chillerlan/php-settings-container" />
<excludeFolder url="file://$MODULE_DIR$/vendor/vearutop/php-obscene-censor-rus" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-message" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php72" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-idn" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/scssphp/scssphp" />
<excludeFolder url="file://$MODULE_DIR$/vendor/bhaktaraz/php-rss-generator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/erusev/parsedown" />
<excludeFolder url="file://$MODULE_DIR$/vendor/ezyang/htmlpurifier" />
<excludeFolder url="file://$MODULE_DIR$/vendor/whichbrowser/parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/komeiji-satori/curl" />
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/james-heinrich/getid3" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/promises" />
<excludeFolder url="file://$MODULE_DIR$/vendor/ralouphie/getallheaders" />
<excludeFolder url="file://$MODULE_DIR$/vendor/wapmorgan/binary-stream" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/psr7" />
<excludeFolder url="file://$MODULE_DIR$/vendor/al/emoji-detector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/guzzle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/lfkeitel/phptotp" />
<excludeFolder url="file://$MODULE_DIR$/vendor/zadarma/user-api-v1" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-ctype" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/string" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/deprecation-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php73" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/container" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-mbstring" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/console" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php80" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/service-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/wapmorgan/morphos" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View file

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PhpIncludePathManager">
<include_path>
<path value="$PROJECT_DIR$/vendor/rybakit/msgpack" />
<path value="$PROJECT_DIR$/vendor/chillerlan/php-qrcode" />
<path value="$PROJECT_DIR$/vendor/psr/cache" />
<path value="$PROJECT_DIR$/vendor/chillerlan/php-settings-container" />
<path value="$PROJECT_DIR$/vendor/vearutop/php-obscene-censor-rus" />
<path value="$PROJECT_DIR$/vendor/psr/http-message" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php72" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-idn" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<path value="$PROJECT_DIR$/vendor/scssphp/scssphp" />
<path value="$PROJECT_DIR$/vendor/bhaktaraz/php-rss-generator" />
<path value="$PROJECT_DIR$/vendor/erusev/parsedown" />
<path value="$PROJECT_DIR$/vendor/ezyang/htmlpurifier" />
<path value="$PROJECT_DIR$/vendor/whichbrowser/parser" />
<path value="$PROJECT_DIR$/vendor/komeiji-satori/curl" />
<path value="$PROJECT_DIR$/vendor/composer" />
<path value="$PROJECT_DIR$/vendor/james-heinrich/getid3" />
<path value="$PROJECT_DIR$/vendor/guzzlehttp/promises" />
<path value="$PROJECT_DIR$/vendor/ralouphie/getallheaders" />
<path value="$PROJECT_DIR$/vendor/wapmorgan/binary-stream" />
<path value="$PROJECT_DIR$/vendor/guzzlehttp/psr7" />
<path value="$PROJECT_DIR$/vendor/al/emoji-detector" />
<path value="$PROJECT_DIR$/vendor/guzzlehttp/guzzle" />
<path value="$PROJECT_DIR$/vendor/lfkeitel/phptotp" />
<path value="$PROJECT_DIR$/vendor/zadarma/user-api-v1" />
<path value="$PROJECT_DIR$/../../../chandler" />
<path value="$PROJECT_DIR$/../../../vendor" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-ctype" />
<path value="$PROJECT_DIR$/vendor/symfony/string" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php73" />
<path value="$PROJECT_DIR$/vendor/psr/container" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
<path value="$PROJECT_DIR$/vendor/symfony/console" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php80" />
<path value="$PROJECT_DIR$/vendor/symfony/service-contracts" />
<path value="$PROJECT_DIR$/vendor/wapmorgan/morphos" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="7.4">
<option name="suggestChangeDefaultLanguageLevel" value="false" />
</component>
</project>

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
</set>
</option>
</component>
</project>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View file

@ -1,82 +0,0 @@
FROM fedora:33
#update and install httpd
RUN dnf -y update && dnf -y autoremove && dnf install -y httpd
#Let's install Remi repos for PHP 7.4:
RUN dnf -y install https://rpms.remirepo.net/fedora/remi-release-$(rpm -E %fedora).rpm
#Then enable modules that we need:
RUN dnf -y module enable php:remi-7.4 && \
dnf -y module enable nodejs:14
#And install dependencies:
RUN dnf -y --skip-broken install php php-cli php-common unzip php-zip php-yaml php-gd php-pdo_mysql nodejs git
#Don't forget about Yarn and Composer:
RUN npm i -g yarn && \
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
php composer-setup.php --filename=composer2 --install-dir=/bin --snapshot && \
rm composer-setup.php
#We will use Mariadb for DB:
RUN dnf -y install mysql mysql-server && \
systemctl enable mariadb && \
echo 'skip-grant-tables' >> /etc/my.cnf
#Additionally, you can install ffmpeg for processing videos.
#You will need to use RPMFusion repo to install it:
RUN dnf -y install --nogpgcheck https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm && \
dnf -y install --nogpgcheck https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm
#Then install SDL2 and ffmpeg:
RUN dnf -y install --nogpgcheck SDL2 ffmpeg
#Install Chandler and OpenVk/Capcha-extention in /opt:
RUN cd /opt && \
git clone https://github.com/samukhin/chandler.git && \
cd chandler/ && \
composer2 install && \
mv chandler-example.yml chandler.yml && \
cd extensions/available/ && \
git clone https://github.com/samukhin/commitcaptcha.git && \
cd commitcaptcha/ && \
composer2 install && \
cd .. && \
git clone https://github.com/samukhin/openvk.git && \
cd openvk/ && \
composer2 install && \
cd Web/static/js && \
yarn install && \
cd ../../../ && \
mv openvk-example.yml openvk.yml && \
ln -s /opt/chandler/extensions/available/commitcaptcha/ /opt/chandler/extensions/enabled/commitcaptcha && \
ln -s /opt/chandler/extensions/available/openvk/ /opt/chandler/extensions/enabled/openvk
#Create database
RUN cp /opt/chandler/extensions/available/openvk/install/automated/common/create_db.service /etc/systemd/system/ && \
chmod 644 /etc/systemd/system/create_db.service && \
chmod 777 /opt/chandler/extensions/available/openvk/install/automated/common/autoexec && \
systemctl enable create_db
#Make the user apache owner of the chandler folder:
RUN cd /opt && \
chown -R apache: chandler/
#Now let's create config file /etc/httpd/conf.d/10-openvk.conf and
#Also enable rewrite_module by creating /etc/httpd/conf.modules.d/02-rewrite.conf
RUN cp /opt/chandler/extensions/available/openvk/install/automated/common/10-openvk.conf /etc/httpd/conf.d/ && \
cp /opt/chandler/extensions/available/openvk/install/automated/common/02-rewrite.conf /etc/httpd/conf.modules.d/
#Make directory for OpenVK logs and make the user apache owner of it:
RUN mkdir /var/log/openvk && \
chown apache: /var/log/openvk/
#And start Apache:
#RUN systemctl enable httpd
#For login
RUN dnf -y install passwd && passwd -d root
#Start systemd
CMD ["/sbin/init"]

View file

@ -2,29 +2,25 @@
_[Русский](README_RU.md)_
**OpenVK** is an attempt to create a simple CMS that ~~cosplays~~ imitates old VK. Code provided here is not stable yet.
**OpenVK** is an attempt to create a simple CMS that ~~cosplays~~ imitates old VKontakte. Code provided here is not stable yet.
VKontakte belongs to Pavel Durov and VK Group.
To be honest, we don't know whether 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 OVK account for this).
To be honest, we don't know whether if it even works. However, this version is maintained and we will be happy to accept your bugreports [in our bug tracker](https://github.com/openvk/openvk/projects/1). You should also be able to submit them using [ticketing system](https://openvk.su/support?act=new) (you will need an OpenVK account for this).
## When's the release?
We will release OpenVK as soon as it's ready. As for now you can:
We will release OpenVK as soon as it's ready. As for now, you can:
* `git clone` this repo's master branch (use `git pull` to update)
* Grab a prebuilt OpenVK distro from [GitHub artifacts](https://nightly.link/openvk/archive/workflows/nightly/master/OpenVK%20Archive.zip)
## Instances
* **[openvk.su](https://openvk.su/)**
* **[openvk.uk](https://openvk.uk)** - official mirror of openvk.su (<https://t.me/openvk/1609>)
* **[openvk.co](http://openvk.co)** - yet another official mirror of openvk.su without TLS (<https://t.me/openvk/1654>)
* [social.fetbuk.ru](http://social.fetbuk.ru/)
* [vepurovk.xyz](http://vepurovk.xyz/)
A list of instances can be found in [our wiki of this repository](https://github.com/openvk/openvk/wiki/Instances).
## Can I create my own OpenVK instance?
Yes! And you're very welcome to.
Yes! And you are very welcome to.
However, OVK makes use of Chandler Application Server. This software requires extensions, that may not be provided by your hosting provider (namely, sodium and yaml. these extensions are available on most of ISPManager hostings).
@ -34,12 +30,12 @@ If you want, you can add your instance to the list above so that people can regi
1. Install PHP 7.4, web-server, Composer, Node.js, Yarn and [Chandler](https://github.com/openvk/chandler)
* PHP 8.1 is supported, but it was not tested carefully, be aware of that.
* PHP 8.1 is supported too, however it was not tested carefully, so be aware.
2. Install MySQL-compatible database.
* We recommend using Percona Server, but any MySQL-compatible server should work
* Server should be compatible with at least MySQL 5.6, MySQL 8.0+ recommended.
* We recommend using Percona Server, but any MySQL-compatible server should work too.
* Server should be compatible with at least MySQL 5.6, MySQL 8.0+ is recommended.
* Support for MySQL 4.1+ is WIP, replace `utf8mb4` and `utf8mb4_unicode_520_ci` with `utf8` and `utf8_unicode_ci` in SQLs.
3. Install [commitcaptcha](https://github.com/openvk/commitcaptcha) and OpenVK as Chandler extensions like this:
@ -70,24 +66,27 @@ 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.su/openvk_engine/centos8_installation/) (CentOS 8 [and](https://almalinux.org/) [family](https://yum.oracle.com/oracle-linux-isos.html)).
💡Confused? Full installation walkthrough is available [here](https://docs.openvk.uk/openvk_engine/centos8_installation/) (CentOS 8 [and](https://almalinux.org/) [family](https://yum.oracle.com/oracle-linux-isos.html)).
### Looking for Docker or Kubernetes deployment?
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?
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're planning to redistribute this, then you should license it under terms of any LGPL-compatible license (like OSL, GPL, LGPL etc).
It depends. You can keep the sources to yourself if you do not plan to distribute your website binaries. If your website software must be distributed, it can stay non-OSS provided the OpenVK is not used as a primary application and is not modified. If you modified OpenVK for your needs or your work is based on it and you are planning to redistribute this, then you should license it under terms of any LGPL-compatible license (like OSL, GPL, LGPL etc).
## Where can I get assistance?
You may reach out to us via:
* [Bug-tracker](https://github.com/openvk/openvk/projects/1)
* [Ticketing system](https://openvk.su/support?act=new)
* Telegram chat: Go to [our channel](https://t.me/openvkenglish) and open discussion in our channel menu.
* [Bug Tracker](https://github.com/openvk/openvk/projects/1)
* [Ticketing System](https://openvk.su/support?act=new)
* Telegram Chat: Go to [our channel](https://t.me/openvkenglish) and open discussion in our channel menu.
* [Reddit](https://www.reddit.com/r/openvk/)
* [Discussions](https://github.com/openvk/openvk/discussions)
* Matrix chat: #openvk:matrix.org
* [GitHub Discussions](https://github.com/openvk/openvk/discussions)
* Matrix Chat: #openvk:matrix.org
**Attention**: bug tracker, board, telegram and matrix chat are public places. And ticketing system is being served by volunteers. If you need to report something, that shouldn't be immediately disclosed to general public (for instance, vulnerability report), please use contact us directly at this email: **openvk [at] tutanota [dot] com**
**Attention**: bug tracker, board, Telegram and Matrix chat are public places, ticketing system is being served by volunteers. If you need to report something that should not be immediately disclosed to general public (for instance, a vulnerability), please contact us directly via this email: **openvk [at] tutanota [dot] com**
<a href="https://codeberg.org/OpenVK/openvk">
<img alt="Get it on Codeberg" src="https://codeberg.org/Codeberg/GetItOnCodeberg/media/branch/main/get-it-on-blue-on-white.png" height="60">

View file

@ -2,11 +2,11 @@
_[English](README.md)_
**OpenVK** - это попытка создать простую CMS, которая ~~косплеит~~ имитирует старый ВКонтакте. На данный момент представленный здесь исходный код проекта пока не является стабильным.
**OpenVK** это попытка создать простую CMS, которая ~~косплеит~~ имитирует старый ВКонтакте. На данный момент, представленный здесь исходный код проекта пока не является стабильным.
ВКонтакте принадлежит Павлу Дурову и VK Group.
Честно говоря, мы даже не знаем, работает ли она вообще. Однако, эта версия поддерживается, и мы будем рады принять ваши сообщения об ошибках [в нашем баг-трекере](https://github.com/openvk/openvk/projects/1). Вы также можете отправлять их через [вкладку "Помощь"](https://openvk.su/support?act=new) (для этого вам понадобится учетная запись OVK).
Честно говоря, мы даже не знаем, работает ли она вообще. Однако, эта версия поддерживается, и мы будем рады принять ваши сообщения об ошибках [в нашем баг-трекере](https://github.com/openvk/openvk/projects/1). Вы также можете отправлять их через [вкладку "Помощь"](https://openvk.su/support?act=new) (для этого вам понадобится учетная запись OpenVK).
## Когда выйдет релизная версия?
@ -16,19 +16,15 @@ _[English](README.md)_
## Инстанции
* **[openvk.su](https://openvk.su/)**
* **[openvk.uk](https://openvk.uk)** - официальное зеркало openvk.su (<https://t.me/openvk/1609>)
* **[openvk.co](http://openvk.co)** - ещё одно официальное зеркало openvk.su без TLS (<https://t.me/openvk/1654>)
* [social.fetbuk.ru](http://social.fetbuk.ru/)
* [vepurovk.xyz](http://vepurovk.xyz/)
Список инстанций находится в [нашей вики этого репозитория](https://github.com/openvk/openvk/wiki/Instances-(RU)).
## Могу ли я создать свою собственную инстанцию OpenVK?
Да! И всегда пожалуйста.
Однако, OVK использует Chandler Application Server. Это программное обеспечение требует расширений, которые могут быть не предоставлены вашим хостинг-провайдером (а именно, sodium и yaml. эти расширения доступны на большинстве хостингов ISPManager).
Однако, OpenVK использует Chandler Application Server. Это программное обеспечение требует расширений, которые могут быть не предоставлены вашим хостинг-провайдером (а именно, sodium и yaml. Эти расширения доступны на большинстве хостингов ISPManager).
Если вы хотите, вы можете добавить вашу инстанцию в список выше, чтобы люди могли зарегистрироваться там.
Если хотите, вы можете добавить вашу инстанцию в список выше, чтобы люди могли зарегистрироваться там.
### Процедура установки
@ -38,7 +34,7 @@ _[English](README.md)_
2. Установите MySQL-совместимую базу данных.
* Мы рекомендуем использовать Persona Server, но любая MySQL-совместимая база данных должна работать
* Мы рекомендуем использовать Persona Server, но любая MySQL-совместимая база данных должна работать.
* Сервер должен поддерживать хотя бы MySQL 5.6, рекомендуется использовать MySQL 8.0+.
* Поддержка для MySQL 4.1+ находится в процессе, а пока замените `utf8mb4` и `utf8mb4_unicode_520_ci` на `utf8` и `utf8_unicode_ci` в SQL-файлах, соответственно.
@ -70,7 +66,10 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions
* **Пароль**: `admin`
* Перед использованием встроенной учетной записи рекомендуется сменить пароль или отключить её.
💡Запутались? Полное руководство по установке доступно [здесь](https://docs.openvk.su/openvk_engine/centos8_installation/) (CentOS 8 [и](https://almalinux.org/ru/) [семейство](https://yum.oracle.com/oracle-linux-isos.html)).
💡Запутались? Полное руководство по установке доступно [здесь](https://docs.openvk.uk/openvk_engine/centos8_installation/) (CentOS 8 [и](https://almalinux.org/ru/) [семейство](https://yum.oracle.com/oracle-linux-isos.html)).
# Установка в Docker/Kubernetes
Подробные иструкции можно найти в `install/automated/docker/README.md` и `install/automated/kubernetes/README.md` соответственно.
### Если мой сайт использует OpenVK, должен ли я публиковать его исходные тексты?
@ -84,10 +83,10 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions
* [Помощь в OVK](https://openvk.su/support?act=new)
* Telegram-чат: Перейдите на [наш канал](https://t.me/openvk) и откройте обсуждение в меню нашего канала.
* [Reddit](https://www.reddit.com/r/openvk/)
* [Обсуждения](https://github.com/openvk/openvk/discussions)
* [GitHub Discussions](https://github.com/openvk/openvk/discussions)
* Чат в Matrix: #ovk:matrix.org
**Внимание**: баг-трекер, форум, телеграм- и matrix-чат являются публичными местами, и жалобы в OVK обслуживается волонтерами. Если вам нужно сообщить о чем-то, что не должно быть раскрыто широкой публике (например, сообщение об уязвимости), пожалуйста, свяжитесь с нами напрямую по этому адресу: **openvk [собака] tutanota [точка] com**.
**Внимание**: баг-трекер, форум, Telegram- и Matrix-чат являются публичными местами, и жалобы в OVK обслуживается волонтерами. Если вам нужно сообщить о чем-то, что не должно быть раскрыто широкой публике (например, сообщение об уязвимости), пожалуйста, свяжитесь с нами напрямую по этому адресу: **openvk [собачка] tutanota [точка] com**.
<a href="https://codeberg.org/OpenVK/openvk">
<img alt="Get it on Codeberg" src="https://codeberg.org/Codeberg/GetItOnCodeberg/media/branch/main/get-it-on-blue-on-white.png" height="60">

View file

@ -54,6 +54,11 @@ class Apps implements Handler
$reject("No application with this id found");
return;
}
if($amount < 0) {
$reject(552, "Payment amount is invalid");
return;
}
$coinsLeft = $this->user->getCoins() - $amount;
if($coinsLeft < 0) {

39
ServiceAPI/Groups.php Normal file
View file

@ -0,0 +1,39 @@
<?php declare(strict_types=1);
namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\Clubs;
class Groups implements Handler
{
protected $user;
protected $groups;
function __construct(?User $user)
{
$this->user = $user;
$this->groups = new Clubs;
}
function getWriteableClubs(callable $resolve, callable $reject)
{
$clubs = [];
$wclubs = $this->groups->getWriteableClubs($this->user->getId());
$count = $this->groups->getWriteableClubsCount($this->user->getId());
if(!$count) {
$reject("You don't have any groups with write access");
return;
}
foreach($wclubs as $club) {
$clubs[] = [
"name" => $club->getName(),
"id" => $club->getId(),
"avatar" => $club->getAvatarUrl() # если в овк когда-нибудь появится крутой список с аватарками, то можно использовать это поле
];
}
$resolve($clubs);
}
}

50
ServiceAPI/Mentions.php Normal file
View file

@ -0,0 +1,50 @@
<?php declare(strict_types=1);
namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\{Users, Clubs};
class Mentions implements Handler
{
protected $user;
function __construct(?User $user)
{
$this->user = $user;
}
function resolve(int $id, callable $resolve, callable $reject): void
{
if($id > 0) {
$user = (new Users)->get($id);
if(!$user) {
$reject("Not found");
return;
}
$resolve([
"url" => $user->getURL(),
"name" => $user->getFullName(),
"ava" => $user->getAvatarURL("miniscule"),
"about" => $user->getStatus() ?? "",
"online" => ($user->isFemale() ? tr("was_online_f") : tr("was_online_m")) . " " . $user->getOnline(),
"verif" => $user->isVerified(),
]);
return;
}
$club = (new Clubs)->get(abs($id));
if(!$club) {
$reject("Not found");
return;
}
$resolve([
"url" => $club->getURL(),
"name" => $club->getName(),
"ava" => $club->getAvatarURL("miniscule"),
"about" => $club->getDescription() ?? "",
"online" => tr("participants", $club->getFollowersCount()),
"verif" => $club->isVerified(),
]);
}
}

41
ServiceAPI/Notes.php Normal file
View file

@ -0,0 +1,41 @@
<?php
namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\Notes as NoteRepo;
class Notes implements Handler
{
protected $user;
protected $notes;
function __construct(?User $user)
{
$this->user = $user;
$this->notes = new NoteRepo;
}
function getNote(int $noteId, callable $resolve, callable $reject): void
{
$note = $this->notes->get($noteId);
if(!$note || $note->isDeleted())
$reject(83, "Note is gone");
$noteOwner = $note->getOwner();
assert($noteOwner instanceof User);
if(!$noteOwner->getPrivacyPermission("notes.read", $this->user))
$reject(160, "You don't have permission to access this note");
$resolve([
"title" => $note->getName(),
"link" => "/note" . $note->getPrettyId(),
"html" => $note->getText(),
"created" => (string) $note->getPublicationTime(),
"author" => [
"name" => $noteOwner->getCanonicalName(),
"ava" => $noteOwner->getAvatarUrl(),
"link" => $noteOwner->getURL(),
],
]);
}
}

70
ServiceAPI/Polls.php Normal file
View file

@ -0,0 +1,70 @@
<?php declare(strict_types=1);
namespace openvk\ServiceAPI;
use Chandler\MVC\Routing\Router;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Exceptions\{AlreadyVotedException, InvalidOptionException, PollLockedException};
use openvk\Web\Models\Repositories\Polls as PollRepo;
use UnexpectedValueException;
class Polls implements Handler
{
protected $user;
protected $polls;
function __construct(?User $user)
{
$this->user = $user;
$this->polls = new PollRepo;
}
private function getPollHtml(int $poll): string
{
return Router::i()->execute("/poll$poll", "SAPI");
}
function vote(int $pollId, string $options, callable $resolve, callable $reject): void
{
$poll = $this->polls->get($pollId);
if(!$poll) {
$reject("Poll not found");
return;
}
try {
$options = explode(",", $options);
$poll->vote($this->user, $options);
} catch(AlreadyVotedException $ex) {
$reject("Poll state changed: user has already voted.");
return;
} catch(PollLockedException $ex) {
$reject("Poll state changed: poll has ended.");
return;
} catch(InvalidOptionException $ex) {
$reject("Foreign options passed.");
return;
} catch(UnexpectedValueException $ex) {
$reject("Too much options passed.");
return;
}
$resolve(["html" => $this->getPollHtml($pollId)]);
}
function unvote(int $pollId, callable $resolve, callable $reject): void
{
$poll = $this->polls->get($pollId);
if(!$poll) {
$reject("Poll not found");
return;
}
try {
$poll->revokeVote($this->user);
} catch(PollLockedException $ex) {
$reject("Votes can't be revoked from this poll.");
return;
}
$resolve(["html" => $this->getPollHtml($pollId)]);
}
}

76
ServiceAPI/Search.php Normal file
View file

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

View file

@ -2,17 +2,19 @@
namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\Post;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\Posts;
use openvk\Web\Models\Repositories\{Posts, Notes};
class Wall implements Handler
{
protected $user;
protected $posts;
protected $notes;
function __construct(?User $user)
{
$this->user = $user;
$this->posts = new Posts;
$this->notes = new Notes;
}
function getPost(int $id, callable $resolve, callable $reject): void
@ -71,4 +73,26 @@ class Wall implements Handler
$resolve($post->getId());
}
function getMyNotes(callable $resolve, callable $reject)
{
$count = $this->notes->getUserNotesCount($this->user);
$myNotes = $this->notes->getUserNotes($this->user, 1, $count);
$arr = [
"count" => $count,
"closed" => $this->user->getPrivacySetting("notes.read"),
"items" => [],
];
foreach($myNotes as $note) {
$arr["items"][] = [
"id" => $note->getId(),
"name" => ovk_proc_strtr($note->getName(), 30),
#"preview" => $note->getPreview()
];
}
$resolve($arr);
}
}

View file

@ -1,5 +1,6 @@
<?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Exceptions\InvalidUserNameException;
final class Account extends VKAPIRequestHandler
{
@ -13,7 +14,7 @@ final class Account extends VKAPIRequestHandler
"last_name" => $this->getUser()->getLastName(),
"home_town" => $this->getUser()->getHometown(),
"status" => $this->getUser()->getStatus(),
"bdate" => $this->getUser()->getBirthday()->format('%e.%m.%Y'),
"bdate" => is_null($this->getUser()->getBirthday()) ? '01.01.1970' : $this->getUser()->getBirthday()->format('%e.%m.%Y'),
"bdate_visibility" => $this->getUser()->getBirthdayPrivacy(),
"phone" => "+420 ** *** 228", # TODO
"relation" => $this->getUser()->getMaritalStatus(),
@ -44,13 +45,12 @@ final class Account extends VKAPIRequestHandler
{
$this->requireUser();
$this->getUser()->setOnline(time());
$this->getUser()->save();
$this->getUser()->updOnline($this->getPlatform());
return 1;
}
function setOffline(): object
function setOffline(): int
{
$this->requireUser();
@ -66,6 +66,8 @@ final class Account extends VKAPIRequestHandler
function getCounters(string $filter = ""): object
{
$this->requireUser();
return (object) [
"friends" => $this->getUser()->getFollowersCount(),
"notifications" => $this->getUser()->getNotificationsCount(),
@ -74,4 +76,79 @@ final class Account extends VKAPIRequestHandler
# TODO: Filter
}
function saveProfileInfo(string $first_name = "", string $last_name = "", string $screen_name = "", int $sex = -1, int $relation = -1, string $bdate = "", int $bdate_visibility = -1, string $home_town = "", string $status = ""): object
{
$this->requireUser();
$this->willExecuteWriteAction();
$user = $this->getUser();
$output = [
"changed" => 0,
];
if(!empty($first_name) || !empty($last_name)) {
$output["name_request"] = [
"id" => random_int(1, 2048), # For compatibility with original VK API
"status" => "success",
"first_name" => !empty($first_name) ? $first_name : $user->getFirstName(),
"last_name" => !empty($last_name) ? $last_name : $user->getLastName(),
];
try {
if(!empty($first_name))
$user->setFirst_name($first_name);
if(!empty($last_name))
$user->setLast_Name($last_name);
} catch (InvalidUserNameException $e) {
$output["name_request"]["status"] = "declined";
return (object) $output;
}
}
if(!empty($screen_name))
if (!$user->setShortCode($screen_name))
$this->fail(1260, "Invalid screen name");
# For compatibility with original VK API
if($sex > 0)
$user->setSex($sex == 1 ? 1 : 0);
if($relation > -1)
$user->setMarital_Status($relation);
if(!empty($bdate)) {
$birthday = strtotime($bdate);
if (!is_int($birthday))
$this->fail(100, "invalid value of bdate.");
$user->setBirthday($birthday);
}
# For compatibility with original VK API
switch($bdate_visibility) {
case 0:
$this->fail(946, "Hiding date of birth is not implemented.");
break;
case 1:
$user->setBirthday_privacy(0);
break;
case 2:
$user->setBirthday_privacy(1);
}
if(!empty($home_town))
$user->setHometown($home_town);
if(!empty($status))
$user->setStatus($status);
if($sex > 0 || $relation > -1 || $bdate_visibility > 1 || !empty("$first_name$last_name$screen_name$bdate$home_town$status")) {
$output["changed"] = 1;
$user->save();
}
return (object) $output;
}
}

431
VKAPI/Handlers/Board.php Normal file
View file

@ -0,0 +1,431 @@
<?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers;
use openvk\VKAPI\Handlers\Wall;
use openvk\Web\Models\Repositories\Topics as TopicsRepo;
use openvk\Web\Models\Repositories\Clubs as ClubsRepo;
use openvk\Web\Models\Repositories\Photos as PhotosRepo;
use openvk\Web\Models\Repositories\Videos as VideosRepo;
use openvk\Web\Models\Repositories\Comments as CommentsRepo;
use openvk\Web\Models\Entities\{Topic, Comment, User, Photo, Video};
final class Board extends VKAPIRequestHandler
{
# 13/13
function addTopic(int $group_id, string $title, string $text = "", bool $from_group = true, string $attachments = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
$club = (new ClubsRepo)->get($group_id);
if(!$club) {
$this->fail(403, "Invalid club");
}
if(!$club->canBeModifiedBy($this->getUser()) && !$club->isEveryoneCanCreateTopics()) {
$this->fail(403, "Access to club denied");
}
$flags = 0;
if($from_group == true && $club->canBeModifiedBy($this->getUser()))
$flags |= 0b10000000;
$topic = new Topic;
$topic->setGroup($club->getId());
$topic->setOwner($this->getUser()->getId());
$topic->setTitle(ovk_proc_strtr($title, 127));
$topic->setCreated(time());
$topic->setFlags($flags);
$topic->save();
if(!empty($text)) {
$comment = new Comment;
$comment->setOwner($this->getUser()->getId());
$comment->setModel(get_class($topic));
$comment->setTarget($topic->getId());
$comment->setContent($text);
$comment->setCreated(time());
$comment->setFlags($flags);
$comment->save();
if(!empty($attachments)) {
$attachmentsArr = explode(",", $attachments);
# блин а мне это везде копировать типа
if(sizeof($attachmentsArr) > 10)
$this->fail(50, "Error: too many attachments");
foreach($attachmentsArr as $attac) {
$attachmentType = NULL;
if(str_contains($attac, "photo"))
$attachmentType = "photo";
elseif(str_contains($attac, "video"))
$attachmentType = "video";
else
$this->fail(205, "Unknown attachment type");
$attachment = str_replace($attachmentType, "", $attac);
$attachmentOwner = (int)explode("_", $attachment)[0];
$attachmentId = (int)end(explode("_", $attachment));
$attacc = NULL;
if($attachmentType == "photo") {
$attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Photo does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this photo");
$comment->attach($attacc);
} elseif($attachmentType == "video") {
$attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Video does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this video");
$comment->attach($attacc);
}
}
}
}
return $topic->getId();
}
function closeTopic(int $group_id, int $topic_id)
{
$this->requireUser();
$this->willExecuteWriteAction();
$topic = (new TopicsRepo)->getTopicById($group_id, $topic_id);
if(!$topic || !$topic->getClub() || !$topic->getClub()->canBeModifiedBy($this->getUser())) {
return 0;
}
if(!$topic->isClosed()) {
$topic->setClosed(1);
$topic->save();
}
return 1;
}
function createComment(int $group_id, int $topic_id, string $message = "", string $attachments = "", bool $from_group = true)
{
$this->requireUser();
$this->willExecuteWriteAction();
if(empty($message) && empty($attachments)) {
$this->fail(100, "Required parameter 'message' missing.");
}
$topic = (new TopicsRepo)->getTopicById($group_id, $topic_id);
if(!$topic || $topic->isDeleted() || $topic->isClosed()) {
$this->fail(100, "Topic is deleted, closed or invalid.");
}
$flags = 0;
if($from_group != 0 && !is_null($topic->getClub()) && $topic->getClub()->canBeModifiedBy($this->user))
$flags |= 0b10000000;
if(strlen($message) > 300) {
$this->fail(20, "Comment is too long.");
}
$comment = new Comment;
$comment->setOwner($this->getUser()->getId());
$comment->setModel(get_class($topic));
$comment->setTarget($topic->getId());
$comment->setContent($message);
$comment->setCreated(time());
$comment->setFlags($flags);
$comment->save();
if(!empty($attachments)) {
$attachmentsArr = explode(",", $attachments);
if(sizeof($attachmentsArr) > 10)
$this->fail(50, "Error: too many attachments");
foreach($attachmentsArr as $attac) {
$attachmentType = NULL;
if(str_contains($attac, "photo"))
$attachmentType = "photo";
elseif(str_contains($attac, "video"))
$attachmentType = "video";
else
$this->fail(205, "Unknown attachment type");
$attachment = str_replace($attachmentType, "", $attac);
$attachmentOwner = (int)explode("_", $attachment)[0];
$attachmentId = (int)end(explode("_", $attachment));
$attacc = NULL;
if($attachmentType == "photo") {
$attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Photo does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this photo");
$comment->attach($attacc);
} elseif($attachmentType == "video") {
$attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Video does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this video");
$comment->attach($attacc);
}
}
}
return $comment->getId();
}
function deleteComment(int $comment_id, int $group_id = 0, int $topic_id = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
$comment = (new CommentsRepo)->get($comment_id);
if($comment->isDeleted() || !$comment || !$comment->canBeDeletedBy($this->getUser()))
$this->fail(403, "Access to comment denied");
$comment->delete();
return 1;
}
function deleteTopic(int $group_id, int $topic_id)
{
$this->requireUser();
$this->willExecuteWriteAction();
$topic = (new TopicsRepo)->getTopicById($group_id, $topic_id);
if(!$topic || !$topic->getClub() || $topic->isDeleted() || !$topic->getClub()->canBeModifiedBy($this->getUser())) {
return 0;
}
$topic->deleteTopic();
return 1;
}
function editComment(int $comment_id, int $group_id = 0, int $topic_id = 0, string $message, string $attachments)
{
/*
$this->requireUser();
$this->willExecuteWriteAction();
$comment = (new CommentsRepo)->get($comment_id);
if($comment->getOwner() != $this->getUser()->getId())
$this->fail(15, "Access to comment denied");
$comment->setContent($message);
$comment->setEdited(time());
$comment->save();
*/
return 1;
}
function editTopic(int $group_id, int $topic_id, string $title)
{
$this->requireUser();
$this->willExecuteWriteAction();
$topic = (new TopicsRepo)->getTopicById($group_id, $topic_id);
if(!$topic || !$topic->getClub() || $topic->isDeleted() || !$topic->getClub()->canBeModifiedBy($this->getUser())) {
return 0;
}
$topic->setTitle(ovk_proc_strtr($title, 127));
$topic->save();
return 1;
}
function fixTopic(int $group_id, int $topic_id)
{
$this->requireUser();
$this->willExecuteWriteAction();
$topic = (new TopicsRepo)->getTopicById($group_id, $topic_id);
if(!$topic || !$topic->getClub() || !$topic->getClub()->canBeModifiedBy($this->getUser())) {
return 0;
}
$topic->setPinned(1);
$topic->save();
return 1;
}
function getComments(int $group_id, int $topic_id, bool $need_likes = false, int $start_comment_id = 0, int $offset = 0, int $count = 40, bool $extended = false, string $sort = "asc")
{
# start_comment_id ne robit
$this->requireUser();
$this->willExecuteWriteAction();
$topic = (new TopicsRepo)->getTopicById($group_id, $topic_id);
if(!$topic || !$topic->getClub() || $topic->isDeleted()) {
$this->fail(5, "Invalid topic");
}
$arr = [
"items" => []
];
$comms = array_slice(iterator_to_array($topic->getComments(1, $count + $offset)), $offset);
foreach($comms as $comm) {
$arr["items"][] = $this->getApiBoardComment($comm, $need_likes);
if($extended) {
if($comm->getOwner() instanceof \openvk\Web\Models\Entities\User) {
$arr["profiles"][] = $comm->getOwner()->toVkApiStruct();
}
if($comm->getOwner() instanceof \openvk\Web\Models\Entities\Club) {
$arr["groups"][] = $comm->getOwner()->toVkApiStruct();
}
}
}
return $arr;
}
function getTopics(int $group_id, string $topic_ids = "", int $order = 1, int $offset = 0, int $count = 40, bool $extended = false, int $preview = 0, int $preview_length = 90)
{
# order и extended ничё не делают
$this->requireUser();
$this->willExecuteWriteAction();
$arr = [];
$club = (new ClubsRepo)->get($group_id);
$topics = array_slice(iterator_to_array((new TopicsRepo)->getClubTopics($club, 1, $count + $offset)), $offset);
$arr["count"] = (new TopicsRepo)->getClubTopicsCount($club);
$arr["items"] = [];
$arr["default_order"] = $order;
$arr["can_add_topics"] = $club->canBeModifiedBy($this->getUser()) ? true : $club->isEveryoneCanCreateTopics() ? true : false;
$arr["profiles"] = [];
if(empty($topic_ids)) {
foreach($topics as $topic) {
if($topic->isDeleted()) continue;
$arr["items"][] = $topic->toVkApiStruct($preview, $preview_length > 1 ? $preview_length : 90);
}
} else {
$topics = explode(',', $topic_ids);
foreach($topics as $topic) {
$id = explode("_", $topic);
$topicy = (new TopicsRepo)->getTopicById((int)$id[0], (int)$id[1]);
if($topicy && !$topicy->isDeleted()) {
$arr["items"][] = $topicy->toVkApiStruct($preview, $preview_length > 1 ? $preview_length : 90);
}
}
}
return $arr;
}
function openTopic(int $group_id, int $topic_id)
{
$this->requireUser();
$this->willExecuteWriteAction();
$topic = (new TopicsRepo)->getTopicById($group_id, $topic_id);
if(!$topic || !$topic->getClub() || !$topic->isDeleted() || !$topic->getClub()->canBeModifiedBy($this->getUser())) {
return 0;
}
if($topic->isClosed()) {
$topic->setClosed(0);
$topic->save();
}
return 1;
}
function restoreComment(int $group_id, int $topic_id, int $comment_id)
{
$this->fail(501, "Not implemented");
}
function unfixTopic(int $group_id, int $topic_id)
{
$this->requireUser();
$this->willExecuteWriteAction();
$topic = (new TopicsRepo)->getTopicById($group_id, $topic_id);
if(!$topic || !$topic->getClub() || !$topic->getClub()->canBeModifiedBy($this->getUser())) {
return 0;
}
if($topic->isPinned()) {
$topic->setClosed(0);
$topic->save();
}
$topic->setPinned(0);
$topic->save();
return 1;
}
private function getApiBoardComment(?Comment $comment, bool $need_likes = false)
{
$res = (object) [];
$res->id = $comment->getId();
$res->from_id = $comment->getOwner()->getId();
$res->date = $comment->getPublicationTime()->timestamp();
$res->text = $comment->getText(false);
$res->attachments = [];
$res->likes = [];
if($need_likes) {
$res->likes = [
"count" => $comment->getLikesCount(),
"user_likes" => (int) $comment->hasLikeFrom($this->getUser()),
"can_like" => 1 # а чё типо не может ахахаххахах
];
}
foreach($comment->getChildren() as $attachment) {
if($attachment->isDeleted())
continue;
$res->attachments[] = $attachment->toVkApiStruct();
}
return $res;
}
}

View file

@ -66,6 +66,7 @@ final class Friends extends VKAPIRequestHandler
function add(string $user_id): int
{
$this->requireUser();
$this->willExecuteWriteAction();
$users = new UsersRepo;
$user = $users->get(intval($user_id));
@ -96,6 +97,7 @@ final class Friends extends VKAPIRequestHandler
function delete(string $user_id): int
{
$this->requireUser();
$this->willExecuteWriteAction();
$users = new UsersRepo;
@ -107,7 +109,7 @@ final class Friends extends VKAPIRequestHandler
return 1;
default:
fail(15, "Access denied: No friend or friend request found.");
$this->fail(15, "Access denied: No friend or friend request found.");
}
}
@ -133,15 +135,18 @@ final class Friends extends VKAPIRequestHandler
return $response;
}
function getRequests(string $fields = "", int $offset = 0, int $count = 100): object
function getRequests(string $fields = "", 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.");
$this->requireUser();
$i = 0;
$offset++;
$followers = [];
foreach($this->getUser()->getFollowers() as $follower) {
foreach($this->getUser()->getFollowers($offset, $count) as $follower) {
$followers[$i] = $follower->getId();
$i++;
}
@ -149,8 +154,7 @@ final class Friends extends VKAPIRequestHandler
$response = $followers;
$usersApi = new Users($this->getUser());
if(!is_null($fields))
$response = $usersApi->get(implode(',', $followers), $fields, 0, $count); # FIXME
$response = $usersApi->get(implode(',', $followers), $fields, 0, $count);
foreach($response as $user)
$user->user_id = $user->id;

174
VKAPI/Handlers/Gifts.php Normal file
View file

@ -0,0 +1,174 @@
<?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Repositories\Users as UsersRepo;
use openvk\Web\Models\Repositories\Gifts as GiftsRepo;
use openvk\Web\Models\Entities\Notifications\GiftNotification;
final class Gifts extends VKAPIRequestHandler
{
function get(int $user_id, int $count = 10, int $offset = 0)
{
$this->requireUser();
$i = 0;
$i += $offset;
$user = (new UsersRepo)->get($user_id);
if(!$user || $user->isDeleted())
$this->fail(177, "Invalid user");
$gift_item = [];
$userGifts = array_slice(iterator_to_array($user->getGifts(1, $count, false)), $offset);
if(sizeof($userGifts) < 0) {
return NULL;
}
foreach($userGifts as $gift) {
if($i < $count) {
$gift_item[] = [
"id" => $i,
"from_id" => $gift->anon == true ? 0 : $gift->sender->getId(),
"message" => $gift->caption == NULL ? "" : $gift->caption,
"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)
],
"privacy" => 0
];
}
$i+=1;
}
return $gift_item;
}
function send(int $user_ids, int $gift_id, string $message = "", int $privacy = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
$user = (new UsersRepo)->get((int) $user_ids);
if(!OPENVK_ROOT_CONF['openvk']['preferences']['commerce'])
$this->fail(105, "Commerce is disabled on this instance");
if(!$user || $user->isDeleted())
$this->fail(177, "Invalid user");
$gift = (new GiftsRepo)->get($gift_id);
if(!$gift)
$this->fail(165, "Invalid gift");
$price = $gift->getPrice();
$coinsLeft = $this->getUser()->getCoins() - $price;
if(!$gift->canUse($this->getUser()))
return (object)
[
"success" => 0,
"user_ids" => $user_ids,
"error" => "You don't have any more of these gifts."
];
if($coinsLeft < 0)
return (object)
[
"success" => 0,
"user_ids" => $user_ids,
"error" => "You don't have enough voices."
];
$user->gift($this->getUser(), $gift, $message);
$gift->used();
$this->getUser()->setCoins($coinsLeft);
$this->getUser()->save();
$notification = new GiftNotification($user, $this->getUser(), $gift, $message);
$notification->emit();
return (object)
[
"success" => 1,
"user_ids" => $user_ids,
"withdraw_votes" => $price
];
}
function delete()
{
$this->requireUser();
$this->willExecuteWriteAction();
$this->fail(501, "Not implemented");
}
# этих методов не было в ВК, но я их добавил чтобы можно было отобразить список подарков
function getCategories(bool $extended = false, int $page = 1)
{
$cats = (new GiftsRepo)->getCategories($page);
$categ = [];
$i = 0;
if(!OPENVK_ROOT_CONF['openvk']['preferences']['commerce'])
$this->fail(105, "Commerce is disabled on this instance");
foreach($cats as $cat) {
$categ[$i] = [
"name" => $cat->getName(),
"description" => $cat->getDescription(),
"id" => $cat->getId(),
"thumbnail" => $cat->getThumbnailURL(),
];
if($extended == true) {
$categ[$i]["localizations"] = [];
foreach(getLanguages() as $lang) {
$code = $lang["code"];
$categ[$i]["localizations"][$code] =
[
"name" => $cat->getName($code),
"desc" => $cat->getDescription($code),
];
}
}
$i++;
}
return $categ;
}
function getGiftsInCategory(int $id, int $page = 1)
{
$this->requireUser();
if(!OPENVK_ROOT_CONF['openvk']['preferences']['commerce'])
$this->fail(105, "Commerce is disabled on this instance");
if(!(new GiftsRepo)->getCat($id))
$this->fail(177, "Category not found");
$giftz = ((new GiftsRepo)->getCat($id))->getGifts($page);
$gifts = [];
foreach($giftz as $gift) {
$gifts[] = [
"name" => $gift->getName(),
"image" => $gift->getImage(2),
"usages_left" => (int)$gift->getUsagesLeft($this->getUser()),
"price" => $gift->getPrice(), # голосов
"is_free" => $gift->isFree()
];
}
return $gifts;
}
}

View file

@ -2,6 +2,7 @@
namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Repositories\Clubs as ClubsRepo;
use openvk\Web\Models\Repositories\Users as UsersRepo;
use openvk\Web\Models\Entities\Club;
final class Groups extends VKAPIRequestHandler
{
@ -10,7 +11,7 @@ final class Groups extends VKAPIRequestHandler
$this->requireUser();
if($user_id == 0) {
foreach($this->getUser()->getClubs($offset+1) as $club)
foreach($this->getUser()->getClubs($offset, false, $count, true) as $club)
$clbs[] = $club;
$clbsCount = $this->getUser()->getClubCount();
} else {
@ -20,7 +21,7 @@ final class Groups extends VKAPIRequestHandler
if(is_null($user))
$this->fail(15, "Access denied");
foreach($user->getClubs($offset+1) as $club)
foreach($user->getClubs($offset, false, $count, true) as $club)
$clbs[] = $club;
$clbsCount = $user->getClubCount();
@ -33,17 +34,9 @@ final class Groups extends VKAPIRequestHandler
$ic = $count;
if(!empty($clbs)) {
$clbs = array_slice($clbs, $offset * $count);
for($i=0; $i < $ic; $i++) {
$usr = $clbs[$i];
if(is_null($usr)) {
$rClubs[$i] = (object)[
"id" => $clbs[$i],
"name" => "DELETED",
"deactivated" => "deleted"
];
} else if($clbs[$i] == NULL) {
if(is_null($usr)) {
} else {
$rClubs[$i] = (object) [
@ -102,23 +95,32 @@ final class Groups extends VKAPIRequestHandler
];
}
function getById(string $group_ids = "", string $group_id = "", string $fields = ""): ?array
function getById(string $group_ids = "", string $group_id = "", string $fields = "", int $offset = 0, int $count = 500): ?array
{
/* Both offset and count SHOULD be used only in OpenVK code,
not in your app or script, since it's not oficially documented by VK */
$clubs = new ClubsRepo;
if($group_ids == NULL && $group_id != NULL)
if(empty($group_ids) && !empty($group_id))
$group_ids = $group_id;
if($group_ids == NULL && $group_id == NULL)
if(empty($group_ids) && empty($group_id))
$this->fail(100, "One of the parameters specified was missing or invalid: group_ids is undefined");
$clbs = explode(',', $group_ids);
$response;
$response = array();
$ic = sizeof($clbs);
if(sizeof($clbs) > $count)
$ic = $count;
$clbs = array_slice($clbs, $offset * $count);
for($i=0; $i < $ic; $i++) {
if($i > 500)
if($i > 500 || $clbs[$i] == 0)
break;
if($clbs[$i] < 0)
@ -142,6 +144,7 @@ final class Groups extends VKAPIRequestHandler
"screen_name" => $clb->getShortCode() ?? "club".$clb->getId(),
"is_closed" => false,
"type" => "group",
"is_member" => !is_null($this->getUser()) ? (int) $clb->getSubscriptionStatus($this->getUser()) : 0,
"can_access_closed" => true,
];
@ -183,7 +186,7 @@ final class Groups extends VKAPIRequestHandler
$response[$i]->site = $clb->getWebsite();
break;
case "description":
$response[$i]->desctiption = $clb->getDescription();
$response[$i]->description = $clb->getDescription();
break;
case "contacts":
$contacts;
@ -204,10 +207,6 @@ final class Groups extends VKAPIRequestHandler
else
$response[$i]->can_post = $clb->canPost();
break;
case "is_member":
if(!is_null($this->getUser()))
$response[$i]->is_member = (int) $clb->getSubscriptionStatus($this->getUser());
break;
}
}
}
@ -215,4 +214,322 @@ final class Groups extends VKAPIRequestHandler
return $response;
}
function search(string $q, int $offset = 0, int $count = 100)
{
$clubs = new ClubsRepo;
$array = [];
$find = $clubs->find($q);
foreach ($find as $group)
$array[] = $group->getId();
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
*/
];
}
function join(int $group_id)
{
$this->requireUser();
$this->willExecuteWriteAction();
$club = (new ClubsRepo)->get($group_id);
$isMember = !is_null($this->getUser()) ? (int) $club->getSubscriptionStatus($this->getUser()) : 0;
if($isMember == 0)
$club->toggleSubscription($this->getUser());
return 1;
}
function leave(int $group_id)
{
$this->requireUser();
$this->willExecuteWriteAction();
$club = (new ClubsRepo)->get($group_id);
$isMember = !is_null($this->getUser()) ? (int) $club->getSubscriptionStatus($this->getUser()) : 0;
if($isMember == 1)
$club->toggleSubscription($this->getUser());
return 1;
}
function create(string $title, string $description = "", string $type = "group", int $public_category = 1, int $public_subcategory = 1, int $subtype = 1)
{
$this->requireUser();
$this->willExecuteWriteAction();
$club = new Club;
$club->setName($title);
$club->setAbout($description);
$club->setOwner($this->getUser()->getId());
$club->save();
$club->toggleSubscription($this->getUser());
return $this->getById((string)$club->getId());
}
function edit(
int $group_id,
string $title = NULL,
string $description = NULL,
string $screen_name = NULL,
string $website = NULL,
int $wall = NULL,
int $topics = NULL,
int $adminlist = NULL,
int $topicsAboveWall = NULL,
int $hideFromGlobalFeed = NULL)
{
$this->requireUser();
$this->willExecuteWriteAction();
$club = (new ClubsRepo)->get($group_id);
if(!$club) $this->fail(203, "Club not found");
if(!$club || !$club->canBeModifiedBy($this->getUser())) $this->fail(15, "You can't modify this group.");
if(!empty($screen_name) && !$club->setShortcode($screen_name)) $this->fail(103, "Invalid shortcode.");
!is_null($title) ? $club->setName($title) : NULL;
!is_null($description) ? $club->setAbout($description) : NULL;
!is_null($screen_name) ? $club->setShortcode($screen_name) : NULL;
!is_null($website) ? $club->setWebsite((!parse_url($website, PHP_URL_SCHEME) ? "https://" : "") . $website) : NULL;
!is_null($wall) ? $club->setWall($wall) : NULL;
!is_null($topics) ? $club->setEveryone_Can_Create_Topics($topics) : NULL;
!is_null($adminlist) ? $club->setAdministrators_List_Display($adminlist) : NULL;
!is_null($topicsAboveWall) ? $club->setDisplay_Topics_Above_Wall($topicsAboveWall) : NULL;
!is_null($hideFromGlobalFeed) ? $club->setHide_From_Global_Feed($hideFromGlobalFeed) : NULL;
$club->save();
return 1;
}
function getMembers(string $group_id, string $sort = "id_asc", int $offset = 0, int $count = 100, string $fields = "", string $filter = "any")
{
# bdate,can_post,can_see_all_posts,can_see_audio,can_write_private_message,city,common_count,connections,contacts,country,domain,education,has_mobile,last_seen,lists,online,online_mobile,photo_100,photo_200,photo_200_orig,photo_400_orig,photo_50,photo_max,photo_max_orig,relation,relatives,schools,sex,site,status,universities
$club = (new ClubsRepo)->get((int) $group_id);
if(!$club)
$this->fail(125, "Invalid group id");
$sorter = "follower ASC";
switch($sort) {
default:
case "time_asc":
case "id_asc":
$sorter = "follower ASC";
break;
case "time_desc":
case "id_desc":
$sorter = "follower DESC";
break;
}
$members = array_slice(iterator_to_array($club->getFollowers(1, $count, $sorter)), $offset);
$arr = (object) [
"count" => count($members),
"items" => array()];
$filds = explode(",", $fields);
$i = 0;
foreach($members as $member) {
if($i > $count) {
break;
}
$arr->items[] = (object) [
"id" => $member->getId(),
"first_name" => $member->getFirstName(),
"last_name" => $member->getLastName(),
];
foreach($filds as $fild) {
switch($fild) {
case "bdate":
$arr->items[$i]->bdate = $member->getBirthday()->format('%e.%m.%Y');
break;
case "can_post":
$arr->items[$i]->can_post = $club->canBeModifiedBy($member);
break;
case "can_see_all_posts":
$arr->items[$i]->can_see_all_posts = 1;
break;
case "can_see_audio":
$arr->items[$i]->can_see_audio = 0;
break;
case "can_write_private_message":
$arr->items[$i]->can_write_private_message = 0;
break;
case "common_count":
$arr->items[$i]->common_count = 420;
break;
case "connections":
$arr->items[$i]->connections = 1;
break;
case "contacts":
$arr->items[$i]->contacts = $member->getContactEmail();
break;
case "country":
$arr->items[$i]->country = 1;
break;
case "domain":
$arr->items[$i]->domain = "";
break;
case "education":
$arr->items[$i]->education = "";
break;
case "has_mobile":
$arr->items[$i]->has_mobile = false;
break;
case "last_seen":
$arr->items[$i]->last_seen = $member->getOnline()->timestamp();
break;
case "lists":
$arr->items[$i]->lists = "";
break;
case "online":
$arr->items[$i]->online = $member->isOnline();
break;
case "online_mobile":
$arr->items[$i]->online_mobile = $member->getOnlinePlatform() == "android" || $member->getOnlinePlatform() == "iphone" || $member->getOnlinePlatform() == "mobile";
break;
case "photo_100":
$arr->items[$i]->photo_100 = $member->getAvatarURL("tiny");
break;
case "photo_200":
$arr->items[$i]->photo_200 = $member->getAvatarURL("normal");
break;
case "photo_200_orig":
$arr->items[$i]->photo_200_orig = $member->getAvatarURL("normal");
break;
case "photo_400_orig":
$arr->items[$i]->photo_400_orig = $member->getAvatarURL("normal");
break;
case "photo_max":
$arr->items[$i]->photo_max = $member->getAvatarURL("original");
break;
case "photo_max_orig":
$arr->items[$i]->photo_max_orig = $member->getAvatarURL();
break;
case "relation":
$arr->items[$i]->relation = $member->getMaritalStatus();
break;
case "relatives":
$arr->items[$i]->relatives = 0;
break;
case "schools":
$arr->items[$i]->schools = 0;
break;
case "sex":
$arr->items[$i]->sex = $member->isFemale() ? 1 : 2;
break;
case "site":
$arr->items[$i]->site = $member->getWebsite();
break;
case "status":
$arr->items[$i]->status = $member->getStatus();
break;
case "universities":
$arr->items[$i]->universities = 0;
break;
}
}
$i++;
}
return $arr;
}
function getSettings(string $group_id)
{
$this->requireUser();
$club = (new ClubsRepo)->get((int)$group_id);
if(!$club || !$club->canBeModifiedBy($this->getUser()))
$this->fail(15, "You can't get settings of this group.");
$arr = (object) [
"title" => $club->getName(),
"description" => $club->getDescription() != NULL ? $club->getDescription() : "",
"address" => $club->getShortcode(),
"wall" => $club->canPost() == true ? 1 : 0,
"photos" => 1,
"video" => 0,
"audio" => 0,
"docs" => 0,
"topics" => $club->isEveryoneCanCreateTopics() == true ? 1 : 0,
"wiki" => 0,
"messages" => 0,
"obscene_filter" => 0,
"obscene_stopwords" => 0,
"obscene_words" => "",
"access" => 1,
"subject" => 1,
"subject_list" => [
0 => "в",
1 => "опенвк",
2 => "нет",
3 => "категорий",
4 => "групп",
],
"rss" => "/club".$club->getId()."/rss",
"website" => $club->getWebsite(),
"age_limits" => 0,
"market" => [],
];
return $arr;
}
function isMember(string $group_id, int $user_id, string $user_ids = "", bool $extended = false)
{
$this->requireUser();
$id = $user_id != NULL ? $user_id : explode(",", $user_ids);
if($group_id < 0)
$this->fail(228, "Remove the minus from group_id");
$club = (new ClubsRepo)->get((int)$group_id);
$usver = (new UsersRepo)->get((int)$id);
if(!$club || $group_id == 0)
$this->fail(203, "Invalid club");
if(!$usver || $usver->isDeleted() || $user_id == 0)
$this->fail(30, "Invalid user");
if($extended == false) {
return $club->getSubscriptionStatus($usver) ? 1 : 0;
} else {
return (object)
[
"member" => $club->getSubscriptionStatus($usver) ? 1 : 0,
"request" => 0,
"invitation" => 0,
"can_invite" => 0,
"can_recall" => 0
];
}
}
function remove(int $group_id, int $user_id)
{
$this->requireUser();
$this->fail(501, "Not implemented");
}
}

View file

@ -8,6 +8,7 @@ final class Likes extends VKAPIRequestHandler
function add(string $type, int $owner_id, int $item_id): object
{
$this->requireUser();
$this->willExecuteWriteAction();
switch($type) {
case "post":
@ -28,6 +29,7 @@ final class Likes extends VKAPIRequestHandler
function delete(string $type, int $owner_id, int $item_id): object
{
$this->requireUser();
$this->willExecuteWriteAction();
switch($type) {
case "post":
@ -52,11 +54,7 @@ final class Likes extends VKAPIRequestHandler
case "post":
$user = (new UsersRepo)->get($user_id);
if (is_null($user))
return (object) [
"liked" => 0,
"copied" => 0,
"sex" => 0
];
$this->fail(100, "One of the parameters specified was missing or invalid: user not found");
$post = (new PostsRepo)->getPostById($owner_id, $item_id);
if (is_null($post))

View file

@ -65,10 +65,16 @@ final class Messages extends VKAPIRequestHandler
];
}
function send(int $user_id = -1, int $peer_id = -1, string $domain = "", int $chat_id = -1, string $user_ids = "", string $message = "", int $sticker_id = -1)
function send(int $user_id = -1, int $peer_id = -1, string $domain = "", int $chat_id = -1, string $user_ids = "", string $message = "", int $sticker_id = -1, int $forGodSakePleaseDoNotReportAboutMyOnlineActivity = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
if($forGodSakePleaseDoNotReportAboutMyOnlineActivity == 0)
{
$this->getUser()->updOnline($this->getPlatform());
}
if($chat_id !== -1)
$this->fail(946, "Chats are not implemented");
else if($sticker_id !== -1)
@ -117,6 +123,7 @@ final class Messages extends VKAPIRequestHandler
function delete(string $message_ids, int $spam = 0, int $delete_for_all = 0): object
{
$this->requireUser();
$this->willExecuteWriteAction();
$msgs = new MSGRepo;
$ids = preg_split("%, ?%", $message_ids);
@ -136,6 +143,7 @@ final class Messages extends VKAPIRequestHandler
function restore(int $message_id): int
{
$this->requireUser();
$this->willExecuteWriteAction();
$msg = (new MSGRepo)->get($message_id);
if(!$msg)
@ -247,32 +255,34 @@ final class Messages extends VKAPIRequestHandler
$user = (new USRRepo)->get((int) $peer);
$dialogue = new Correspondence($this->getUser(), $user);
$iterator = $dialogue->getMessages(Correspondence::CAP_BEHAVIOUR_START_MESSAGE_ID, 0, 1, 0, false);
$msg = $iterator[0]->unwrap(); // шоб удобнее было
$output['items'][] = [
"peer" => [
"id" => $user->getId(),
"type" => "user",
"local_id" => $user->getId()
],
"last_message_id" => $msg->id,
"in_read" => $msg->id,
"out_read" => $msg->id,
"sort_id" => [
"major_id" => 0,
"minor_id" => $msg->id, // КОНЕЧНО ЖЕ
],
"last_conversation_message_id" => $user->getId(),
"in_read_cmid" => $user->getId(),
"out_read_cmid" => $user->getId(),
"is_marked_unread" => $iterator[0]->isUnread(),
"important" => false, // целестора когда релиз
"can_write" => [
"allowed" => ($user->getId() === $this->getUser()->getId() || $user->getPrivacyPermission('messages.write', $this->getUser()) === true)
]
];
$userslist[] = $user->getId();
if($user) {
$dialogue = new Correspondence($this->getUser(), $user);
$iterator = $dialogue->getMessages(Correspondence::CAP_BEHAVIOUR_START_MESSAGE_ID, 0, 1, 0, false);
$msg = $iterator[0]->unwrap(); // шоб удобнее было
$output['items'][] = [
"peer" => [
"id" => $user->getId(),
"type" => "user",
"local_id" => $user->getId()
],
"last_message_id" => $msg->id,
"in_read" => $msg->id,
"out_read" => $msg->id,
"sort_id" => [
"major_id" => 0,
"minor_id" => $msg->id, // КОНЕЧНО ЖЕ
],
"last_conversation_message_id" => $user->getId(),
"in_read_cmid" => $user->getId(),
"out_read_cmid" => $user->getId(),
"is_marked_unread" => $iterator[0]->isUnread(),
"important" => false, // целестора когда релиз
"can_write" => [
"allowed" => ($user->getId() === $this->getUser()->getId() || $user->getPrivacyPermission('messages.write', $this->getUser()) === true)
]
];
$userslist[] = $user->getId();
}
}
if($extended == 1) {

View file

@ -2,13 +2,19 @@
namespace openvk\VKAPI\Handlers;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Repositories\Posts as PostsRepo;
use openvk\Web\Models\Entities\User;
use openvk\VKAPI\Handlers\Wall;
final class Newsfeed extends VKAPIRequestHandler
{
function get(string $fields = "", int $start_from = 0, int $offset = 0, int $count = 30, int $extended = 0)
function get(string $fields = "", int $start_from = 0, int $start_time = 0, int $end_time = 0, int $offset = 0, int $count = 30, int $extended = 0, int $forGodSakePleaseDoNotReportAboutMyOnlineActivity = 0)
{
$this->requireUser();
if($forGodSakePleaseDoNotReportAboutMyOnlineActivity == 0)
{
$this->getUser()->updOnline($this->getPlatform());
}
$id = $this->getUser()->getId();
$subs = DatabaseConnection::i()
@ -26,7 +32,9 @@ final class Newsfeed extends VKAPIRequestHandler
->select("id")
->where("wall IN (?)", $ids)
->where("deleted", 0)
->where("id < (?)", empty($start_from) ? time()+1 : $start_from)
->where("id < (?)", empty($start_from) ? PHP_INT_MAX : $start_from)
->where("? <= created", empty($start_time) ? 0 : $start_time)
->where("? >= created", empty($end_time) ? PHP_INT_MAX : $end_time)
->order("created DESC");
$rposts = [];
@ -35,6 +43,34 @@ final class Newsfeed extends VKAPIRequestHandler
$response = (new Wall)->getById(implode(',', $rposts), $extended, $fields, $this->getUser());
$response->next_from = end(end($posts->page((int) ($offset + 1), $count))); // ну и костыли пиздец конечно)
return $response;
}
function getGlobal(string $fields = "", int $start_from = 0, int $start_time = 0, int $end_time = 0, int $offset = 0, int $count = 30, int $extended = 0)
{
$this->requireUser();
$queryBase = "FROM `posts` LEFT JOIN `groups` ON GREATEST(`posts`.`wall`, 0) = 0 AND `groups`.`id` = ABS(`posts`.`wall`) WHERE (`groups`.`hide_from_global_feed` = 0 OR `groups`.`name` IS NULL) AND `posts`.`deleted` = 0";
if($this->getUser()->getNsfwTolerance() === User::NSFW_INTOLERANT)
$queryBase .= " AND `nsfw` = 0";
$start_from = empty($start_from) ? PHP_INT_MAX : $start_from;
$start_time = empty($start_time) ? 0 : $start_time;
$end_time = empty($end_time) ? PHP_INT_MAX : $end_time;
$posts = DatabaseConnection::i()->getConnection()->query("SELECT `posts`.`id` " . $queryBase . " AND `posts`.`id` <= " . $start_from . " AND " . $start_time . " <= `posts`.`created` AND `posts`.`created` <= " . $end_time . " ORDER BY `created` DESC LIMIT " . $count . " OFFSET " . $offset);
$rposts = [];
$ids = [];
foreach($posts as $post) {
$rposts[] = (new PostsRepo)->get($post->id)->getPrettyId();
$ids[] = $post->id;
}
$response = (new Wall)->getById(implode(',', $rposts), $extended, $fields, $this->getUser());
$response->next_from = end($ids);
return $response;
}
}

283
VKAPI/Handlers/Notes.php Normal file
View file

@ -0,0 +1,283 @@
<?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Repositories\Notes as NotesRepo;
use openvk\Web\Models\Repositories\Users as UsersRepo;
use openvk\Web\Models\Repositories\Comments as CommentsRepo;
use openvk\Web\Models\Repositories\Photos as PhotosRepo;
use openvk\Web\Models\Repositories\Videos as VideosRepo;
use openvk\Web\Models\Entities\{Note, Comment};
final class Notes extends VKAPIRequestHandler
{
function add(string $title, string $text, int $privacy = 0, int $comment_privacy = 0, string $privacy_view = "", string $privacy_comment = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
$note = new Note;
$note->setOwner($this->getUser()->getId());
$note->setCreated(time());
$note->setName($title);
$note->setSource($text);
$note->setEdited(time());
$note->save();
return $note->getVirtualId();
}
function createComment(string $note_id, int $owner_id, string $message, int $reply_to = 0, string $attachments = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
$note = (new NotesRepo)->getNoteById((int)$owner_id, (int)$note_id);
if(!$note)
$this->fail(180, "Note not found");
if($note->isDeleted())
$this->fail(189, "Note is deleted");
if($note->getOwner()->isDeleted())
$this->fail(403, "Owner is deleted");
if(!$note->getOwner()->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(43, "No access");
if(empty($message) && empty($attachments))
$this->fail(100, "Required parameter 'message' missing.");
$comment = new Comment;
$comment->setOwner($this->getUser()->getId());
$comment->setModel(get_class($note));
$comment->setTarget($note->getId());
$comment->setContent($message);
$comment->setCreated(time());
$comment->save();
if(!empty($attachments)) {
$attachmentsArr = explode(",", $attachments);
if(sizeof($attachmentsArr) > 10)
$this->fail(50, "Error: too many attachments");
foreach($attachmentsArr as $attac) {
$attachmentType = NULL;
if(str_contains($attac, "photo"))
$attachmentType = "photo";
elseif(str_contains($attac, "video"))
$attachmentType = "video";
else
$this->fail(205, "Unknown attachment type");
$attachment = str_replace($attachmentType, "", $attac);
$attachmentOwner = (int)explode("_", $attachment)[0];
$attachmentId = (int)end(explode("_", $attachment));
$attacc = NULL;
if($attachmentType == "photo") {
$attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Photo does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this photo");
$comment->attach($attacc);
} elseif($attachmentType == "video") {
$attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Video does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this video");
$comment->attach($attacc);
}
}
}
return $comment->getId();
}
function delete(string $note_id)
{
$this->requireUser();
$this->willExecuteWriteAction();
$note = (new NotesRepo)->get((int)$note_id);
if(!$note)
$this->fail(180, "Note not found");
if(!$note->canBeModifiedBy($this->getUser()))
$this->fail(15, "Access to note denied");
$note->delete();
return 1;
}
function deleteComment(int $comment_id, int $owner_id = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
$comment = (new CommentsRepo)->get($comment_id);
if(!$comment || !$comment->canBeDeletedBy($this->getUser()))
$this->fail(403, "Access to comment denied");
$comment->delete();
return 1;
}
function edit(string $note_id, string $title = "", string $text = "", int $privacy = 0, int $comment_privacy = 0, string $privacy_view = "", string $privacy_comment = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
$note = (new NotesRepo)->getNoteById($this->getUser()->getId(), (int)$note_id);
if(!$note)
$this->fail(180, "Note not found");
if($note->isDeleted())
$this->fail(189, "Note is deleted");
if(!$note->canBeModifiedBy($this->getUser()))
$this->fail(403, "No access");
!empty($title) ? $note->setName($title) : NULL;
!empty($text) ? $note->setSource($text) : NULL;
$note->setCached_Content(NULL);
$note->setEdited(time());
$note->save();
return 1;
}
function editComment(int $comment_id, string $message, int $owner_id = NULL)
{
/*
$this->requireUser();
$this->willExecuteWriteAction();
$comment = (new CommentsRepo)->get($comment_id);
if($comment->getOwner() != $this->getUser()->getId())
$this->fail(15, "Access to comment denied");
$comment->setContent($message);
$comment->setEdited(time());
$comment->save();
*/
return 1;
}
function get(int $user_id, string $note_ids = "", int $offset = 0, int $count = 10, int $sort = 0)
{
$this->requireUser();
$user = (new UsersRepo)->get($user_id);
if(!$user || $user->isDeleted())
$this->fail(15, "Invalid user");
if(!$user->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(43, "Access denied: this user chose to hide his notes");
if(empty($note_ids)) {
$notes = array_slice(iterator_to_array((new NotesRepo)->getUserNotes($user, 1, $count + $offset, $sort == 0 ? "ASC" : "DESC")), $offset);
$nodez = (object) [
"count" => (new NotesRepo)->getUserNotesCount((new UsersRepo)->get($user_id)),
"notes" => []
];
foreach($notes as $note) {
if($note->isDeleted()) continue;
$nodez->notes[] = $note->toVkApiStruct();
}
} else {
$notes = explode(',', $note_ids);
foreach($notes as $note)
{
$id = explode("_", $note);
$items = [];
$note = (new NotesRepo)->getNoteById((int)$id[0], (int)$id[1]);
if($note) {
$nodez->notes[] = $note->toVkApiStruct();
}
}
}
return $nodez;
}
function getById(int $note_id, int $owner_id, bool $need_wiki = false)
{
$this->requireUser();
$note = (new NotesRepo)->getNoteById($owner_id, $note_id);
if(!$note)
$this->fail(180, "Note not found");
if($note->isDeleted())
$this->fail(189, "Note is deleted");
if(!$note->getOwner() || $note->getOwner()->isDeleted())
$this->fail(177, "Owner does not exists");
if(!$note->getOwner()->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(40, "Access denied: this user chose to hide his notes");
return $note->toVkApiStruct();
}
function getComments(int $note_id, int $owner_id, int $sort = 1, int $offset = 0, int $count = 100)
{
$this->requireUser();
$note = (new NotesRepo)->getNoteById($owner_id, $note_id);
if(!$note)
$this->fail(180, "Note not found");
if($note->isDeleted())
$this->fail(189, "Note is deleted");
if(!$note->getOwner())
$this->fail(177, "Owner does not exists");
if(!$note->getOwner()->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(14, "No access");
$arr = (object) [
"count" => $note->getCommentsCount(),
"comments" => []];
$comments = array_slice(iterator_to_array($note->getComments(1, $count + $offset)), $offset);
foreach($comments as $comment) {
$arr->comments[] = $comment->toVkApiStruct($this->getUser(), false, false, $note);
}
return $arr;
}
function getFriendsNotes(int $offset = 0, int $count = 0)
{
$this->fail(501, "Not implemented");
}
function restoreComment(int $comment_id = 0, int $owner_id = 0)
{
$this->fail(501, "Not implemented");
}
}

View file

@ -3,9 +3,12 @@ namespace openvk\VKAPI\Handlers;
use Nette\InvalidStateException;
use Nette\Utils\ImageException;
use openvk\Web\Models\Entities\Photo;
use openvk\Web\Models\Entities\{Photo, Album, Comment};
use openvk\Web\Models\Repositories\Albums;
use openvk\Web\Models\Repositories\Photos as PhotosRepo;
use openvk\Web\Models\Repositories\Clubs;
use openvk\Web\Models\Repositories\Users as UsersRepo;
use openvk\Web\Models\Repositories\Comments as CommentsRepo;
final class Photos extends VKAPIRequestHandler
{
@ -58,7 +61,7 @@ final class Photos extends VKAPIRequestHandler
}
return (object) [
"upload_url" => $this->getPhotoUploadUrl("photo", isset($club) ? 0 : $club->getId()),
"upload_url" => $this->getPhotoUploadUrl("photo", !isset($club) ? 0 : $club->getId()),
];
}
@ -227,4 +230,504 @@ final class Photos extends VKAPIRequestHandler
"items" => $images,
];
}
function createAlbum(string $title, int $group_id = 0, string $description = "", int $privacy = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
if($group_id != 0) {
$club = (new Clubs)->get((int) $group_id);
if(!$club || !$club->canBeModifiedBy($this->getUser())) {
$this->fail(20, "Invalid club");
}
}
$album = new Album;
$album->setOwner(isset($club) ? $club->getId() * -1 : $this->getUser()->getId());
$album->setName($title);
$album->setDescription($description);
$album->setCreated(time());
$album->save();
return $album->toVkApiStruct($this->getUser());
}
function editAlbum(int $album_id, int $owner_id, string $title, string $description = "", int $privacy = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
$album = (new Albums)->getAlbumByOwnerAndId($owner_id, $album_id);
if(!$album || $album->isDeleted()) {
$this->fail(2, "Invalid album");
}
if(empty($title)) {
$this->fail(25, "Title is empty");
}
if($album->isCreatedBySystem()) {
$this->fail(40, "You can't change system album");
}
if(!$album->canBeModifiedBy($this->getUser())) {
$this->fail(2, "Access to album denied");
}
$album->setName($title);
$album->setDescription($description);
$album->save();
return $album->toVkApiStruct($this->getUser());
}
function getAlbums(int $owner_id, string $album_ids = "", int $offset = 0, int $count = 100, bool $need_system = true, bool $need_covers = true, bool $photo_sizes = false)
{
$this->requireUser();
$this->willExecuteWriteAction();
$res = [];
if(empty($album_ids)) {
if($owner_id > 0) {
$user = (new UsersRepo)->get($owner_id);
$res = [
"count" => (new Albums)->getUserAlbumsCount($user),
"items" => []
];
if(!$user || $user->isDeleted())
$this->fail(2, "Invalid user");
if(!$user->getPrivacyPermission('photos.read', $this->getUser()))
$this->fail(21, "This user chose to hide his albums.");
$albums = array_slice(iterator_to_array((new Albums)->getUserAlbums($user, 1, $count + $offset)), $offset);
foreach($albums as $album) {
if(!$need_system && $album->isCreatedBySystem()) continue;
$res["items"][] = $album->toVkApiStruct($this->getUser(), $need_covers, $photo_sizes);
}
}
else {
$club = (new Clubs)->get($owner_id * -1);
$res = [
"count" => (new Albums)->getClubAlbumsCount($club),
"items" => []
];
if(!$club)
$this->fail(2, "Invalid club");
$albums = array_slice(iterator_to_array((new Albums)->getClubAlbums($club, 1, $count + $offset)), $offset);
foreach($albums as $album) {
if(!$need_system && $album->isCreatedBySystem()) continue;
$res["items"][] = $album->toVkApiStruct($this->getUser(), $need_covers, $photo_sizes);
}
}
} else {
$albums = explode(',', $album_ids);
$res = [
"count" => sizeof($albums),
"items" => []
];
foreach($albums as $album)
{
$id = explode("_", $album);
$album = (new Albums)->getAlbumByOwnerAndId((int)$id[0], (int)$id[1]);
if($album && !$album->isDeleted()) {
if(!$need_system && $album->isCreatedBySystem()) continue;
$res["items"][] = $album->toVkApiStruct($this->getUser(), $need_covers, $photo_sizes);
}
}
}
return $res;
}
function getAlbumsCount(int $user_id = 0, int $group_id = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
if($user_id == 0 && $group_id == 0 || $user_id > 0 && $group_id > 0) {
$this->fail(21, "Select user_id or group_id");
}
if($user_id > 0) {
$us = (new UsersRepo)->get($user_id);
if(!$us || $us->isDeleted()) {
$this->fail(21, "Invalid user");
}
if(!$us->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "This user chose to hide his albums.");
}
return (new Albums)->getUserAlbumsCount($us);
}
if($group_id > 0)
{
$cl = (new Clubs)->get($group_id);
if(!$cl) {
$this->fail(21, "Invalid club");
}
return (new Albums)->getClubAlbumsCount($cl);
}
}
function getById(string $photos, bool $extended = false, bool $photo_sizes = false)
{
$this->requireUser();
$this->willExecuteWriteAction();
$phts = explode(",", $photos);
$res = [];
foreach($phts as $phota) {
$ph = explode("_", $phota);
$photo = (new PhotosRepo)->getByOwnerAndVID((int)$ph[0], (int)$ph[1]);
if(!$photo || $photo->isDeleted()) {
$this->fail(21, "Invalid photo");
}
if($photo->getOwner()->isDeleted()) {
$this->fail(21, "Owner of this photo is deleted");
}
if(!$photo->getOwner()->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "This user chose to hide his photos.");
}
$res[] = $photo->toVkApiStruct($photo_sizes, $extended);
}
return $res;
}
function get(int $owner_id, int $album_id, string $photo_ids = "", bool $extended = false, bool $photo_sizes = false, int $offset = 0, int $count = 10)
{
$this->requireUser();
$this->willExecuteWriteAction();
$res = [];
if(empty($photo_ids)) {
$album = (new Albums)->getAlbumByOwnerAndId($owner_id, $album_id);
if(!$album->getOwner()->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "This user chose to hide his albums.");
}
if(!$album || $album->isDeleted()) {
$this->fail(21, "Invalid album");
}
$photos = array_slice(iterator_to_array($album->getPhotos(1, $count + $offset)), $offset);
$res["count"] = sizeof($photos);
foreach($photos as $photo) {
if(!$photo || $photo->isDeleted()) continue;
$res["items"][] = $photo->toVkApiStruct($photo_sizes, $extended);
}
} else {
$photos = explode(',', $photo_ids);
$res = [
"count" => sizeof($photos),
"items" => []
];
foreach($photos as $photo)
{
$id = explode("_", $photo);
$phot = (new PhotosRepo)->getByOwnerAndVID((int)$id[0], (int)$id[1]);
if($phot && !$phot->isDeleted()) {
$res["items"][] = $phot->toVkApiStruct($photo_sizes, $extended);
}
}
}
return $res;
}
function deleteAlbum(int $album_id, int $group_id = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
$album = (new Albums)->get($album_id);
if(!$album || $album->canBeModifiedBy($this->getUser())) {
$this->fail(21, "Invalid album");
}
if($album->isDeleted()) {
$this->fail(22, "Album already deleted");
}
$album->delete();
return 1;
}
function edit(int $owner_id, int $photo_id, string $caption = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
$photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id);
if(!$photo) {
$this->fail(21, "Invalid photo");
}
if($photo->isDeleted()) {
$this->fail(21, "Photo is deleted");
}
if(!empty($caption)) {
$photo->setDescription($caption);
$photo->save();
}
return 1;
}
function delete(int $owner_id, int $photo_id, string $photos = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
if(empty($photos)) {
$photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id);
if($this->getUser()->getId() !== $photo->getOwner()->getId()) {
$this->fail(21, "You can't delete another's photo");
}
if(!$photo) {
$this->fail(21, "Invalid photo");
}
if($photo->isDeleted()) {
$this->fail(21, "Photo already deleted");
}
$photo->delete();
} else {
$photozs = explode(',', $photos);
foreach($photozs as $photo)
{
$id = explode("_", $photo);
$phot = (new PhotosRepo)->getByOwnerAndVID((int)$id[0], (int)$id[1]);
if($this->getUser()->getId() !== $phot->getOwner()->getId()) {
$this->fail(21, "You can't delete another's photo");
}
if(!$phot) {
$this->fail(21, "Invalid photo");
}
if($phot->isDeleted()) {
$this->fail(21, "Photo already deleted");
}
$phot->delete();
}
}
return 1;
}
function getAllComments(int $owner_id, int $album_id, bool $need_likes = false, int $offset = 0, int $count = 100)
{
$this->fail(501, "Not implemented");
}
function deleteComment(int $comment_id, int $owner_id = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
$comment = (new CommentsRepo)->get($comment_id);
if(!$comment) {
$this->fail(21, "Invalid comment");
}
if(!$comment->canBeModifiedBy($this->getUser())) {
$this->fail(21, "Forbidden");
}
if($comment->isDeleted()) {
$this->fail(4, "Comment already deleted");
}
$comment->delete();
return 1;
}
function createComment(int $owner_id, int $photo_id, string $message = "", string $attachments = "", bool $from_group = false)
{
$this->requireUser();
$this->willExecuteWriteAction();
if(empty($message) && empty($attachments)) {
$this->fail(100, "Required parameter 'message' missing.");
}
$photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id);
if(!$photo->getAlbum()->getOwner()->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "This user chose to hide his albums.");
}
if(!$photo)
$this->fail(180, "Photo not found");
if($photo->isDeleted())
$this->fail(189, "Photo is deleted");
$comment = new Comment;
$comment->setOwner($this->getUser()->getId());
$comment->setModel(get_class($photo));
$comment->setTarget($photo->getId());
$comment->setContent($message);
$comment->setCreated(time());
$comment->save();
if(!empty($attachments)) {
$attachmentsArr = explode(",", $attachments);
if(sizeof($attachmentsArr) > 10)
$this->fail(50, "Error: too many attachments");
foreach($attachmentsArr as $attac) {
$attachmentType = NULL;
if(str_contains($attac, "photo"))
$attachmentType = "photo";
elseif(str_contains($attac, "video"))
$attachmentType = "video";
else
$this->fail(205, "Unknown attachment type");
$attachment = str_replace($attachmentType, "", $attac);
$attachmentOwner = (int)explode("_", $attachment)[0];
$attachmentId = (int)end(explode("_", $attachment));
$attacc = NULL;
if($attachmentType == "photo") {
$attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Photo does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this photo");
$comment->attach($attacc);
} elseif($attachmentType == "video") {
$attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Video does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this video");
$comment->attach($attacc);
}
}
}
return $comment->getId();
}
function getAll(int $owner_id, bool $extended = false, int $offset = 0, int $count = 100, bool $photo_sizes = false)
{
$this->requireUser();
$this->willExecuteWriteAction();
if($owner_id < 0) {
$this->fail(4, "This method doesn't works with clubs");
}
$user = (new UsersRepo)->get($owner_id);
if(!$user) {
$this->fail(4, "Invalid user");
}
if(!$user->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "This user chose to hide his albums.");
}
$photos = array_slice(iterator_to_array((new PhotosRepo)->getEveryUserPhoto($user, 1, $count + $offset)), $offset);
$res = [];
foreach($photos as $photo) {
if(!$photo || $photo->isDeleted()) continue;
$res["items"][] = $photo->toVkApiStruct($photo_sizes, $extended);
}
return $res;
}
function getComments(int $owner_id, int $photo_id, bool $need_likes = false, int $offset = 0, int $count = 100, bool $extended = false, string $fields = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
$photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id);
$comms = array_slice(iterator_to_array($photo->getComments(1, $offset + $count)), $offset);
if(!$photo) {
$this->fail(4, "Invalid photo");
}
if(!$photo->getAlbum()->getOwner()->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "This user chose to hide his photos.");
}
if($photo->isDeleted()) {
$this->fail(4, "Photo is deleted");
}
$res = [
"count" => sizeof($comms),
"items" => []
];
foreach($comms as $comment) {
$res["items"][] = $comment->toVkApiStruct($this->getUser(), $need_likes, $extended);
if($extended) {
if($comment->getOwner() instanceof \openvk\Web\Models\Entities\User) {
$res["profiles"][] = $comment->getOwner()->toVkApiStruct();
}
}
}
return $res;
}
}

107
VKAPI/Handlers/Polls.php Executable file
View file

@ -0,0 +1,107 @@
<?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\Users as UsersRepo;
use openvk\Web\Models\Entities\Poll;
use openvk\Web\Models\Exceptions\AlreadyVotedException;
use openvk\Web\Models\Exceptions\InvalidOptionException;
use openvk\Web\Models\Exceptions\PollLockedException;
use openvk\Web\Models\Repositories\Polls as PollsRepo;
final class Polls extends VKAPIRequestHandler
{
function getById(int $poll_id, bool $extended = false, string $fields = "sex,screen_name,photo_50,photo_100,online_info,online")
{
$poll = (new PollsRepo)->get($poll_id);
if (!$poll)
$this->fail(100, "One of the parameters specified was missing or invalid: poll_id is incorrect");
$users = array();
$answers = array();
foreach($poll->getResults()->options as $answer) {
$answers[] = (object)[
"id" => $answer->id,
"rate" => $answer->pct,
"text" => $answer->name,
"votes" => $answer->votes
];
}
$userVote = array();
foreach($poll->getUserVote($this->getUser()) as $vote)
$userVote[] = $vote[0];
$response = [
"multiple" => $poll->isMultipleChoice(),
"end_date" => $poll->endsAt() == NULL ? 0 : $poll->endsAt()->timestamp(),
"closed" => $poll->hasEnded(),
"is_board" => false,
"can_edit" => false,
"can_vote" => $poll->canVote($this->getUser()),
"can_report" => false,
"can_share" => true,
"created" => 0,
"id" => $poll->getId(),
"owner_id" => $poll->getOwner()->getId(),
"question" => $poll->getTitle(),
"votes" => $poll->getVoterCount(),
"disable_unvote" => $poll->isRevotable(),
"anonymous" => $poll->isAnonymous(),
"answer_ids" => $userVote,
"answers" => $answers,
"author_id" => $poll->getOwner()->getId(),
];
if ($extended) {
$response["profiles"] = (new Users)->get(strval($poll->getOwner()->getId()), $fields, 0, 1);
/* Currently there is only one person that can be shown trough "Extended" param.
* As "friends" param will be implemented, "profiles" will show more users
*/
}
return (object) $response;
}
function addVote(int $poll_id, string $answers_ids)
{
$this->requireUser();
$this->willExecuteWriteAction();
$poll = (new PollsRepo)->get($poll_id);
if(!$poll)
$this->fail(251, "Invalid poll id");
try {
$poll->vote($this->getUser(), explode(",", $answers_ids));
return 1;
} catch(AlreadyVotedException $ex) {
return 0;
} catch(PollLockedException $ex) {
return 0;
} catch(InvalidOptionException $ex) {
$this->fail(8, "бдсм вибратор купить в киеве");
}
}
function deleteVote(int $poll_id)
{
$this->requireUser();
$this->willExecuteWriteAction();
$poll = (new PollsRepo)->get($poll_id);
if(!$poll)
$this->fail(251, "Invalid poll id");
try {
$poll->revokeVote($this->getUser());
return 1;
} catch(PollLockedException $ex) {
$this->fail(15, "Access denied: Poll is locked or isn't revotable");
} catch(InvalidOptionException $ex) {
$this->fail(8, "how.to. ook.bacon.in.microwova.");
}
}
}

35
VKAPI/Handlers/Status.php Normal file
View file

@ -0,0 +1,35 @@
<?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\Users as UsersRepo;
final class Status extends VKAPIRequestHandler
{
function get(int $user_id = 0, int $group_id = 0)
{
$this->requireUser();
if($user_id == 0 && $group_id == 0) {
return $this->getUser()->getStatus();
} else {
if($group_id > 0)
$this->fail(501, "Group statuses are not implemented");
else
return (new UsersRepo)->get($user_id)->getStatus();
}
}
function set(string $text, int $group_id = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
if($group_id > 0) {
$this->fail(501, "Group statuses are not implemented");
} else {
$this->getUser()->setStatus($text);
$this->getUser()->save();
return 1;
}
}
}

View file

@ -32,7 +32,15 @@ final class Users extends VKAPIRequestHandler
"first_name" => "DELETED",
"last_name" => "",
"deactivated" => "deleted"
];
];
} else if($usr->isBanned()) {
$response[$i] = (object)[
"id" => $usr->getId(),
"first_name" => $usr->getFirstName(),
"last_name" => $usr->getLastName(),
"deactivated" => "banned",
"ban_reason" => $usr->getBanReason()
];
} else if($usrs[$i] == NULL) {
} else {
@ -108,11 +116,31 @@ final class Users extends VKAPIRequestHandler
}
break;
case "last_seen":
if ($usr->onlineStatus() == 0)
if ($usr->onlineStatus() == 0) {
$platform = $usr->getOnlinePlatform(true);
switch ($platform) {
case 'iphone':
$platform = 2;
break;
case 'android':
$platform = 4;
break;
case NULL:
$platform = 7;
break;
default:
$platform = 1;
break;
}
$response[$i]->last_seen = (object) [
"platform" => 1,
"platform" => $platform,
"time" => $usr->getOnline()->timestamp()
];
}
case "music":
$response[$i]->music = $usr->getFavoriteMusic();
break;
@ -130,7 +158,10 @@ final class Users extends VKAPIRequestHandler
break;
case "interests":
$response[$i]->interests = $usr->getInterests();
break;
break;
case "rating":
$response[$i]->rating = $usr->getRating();
break;
}
}
@ -168,19 +199,94 @@ final class Users extends VKAPIRequestHandler
];
}
function search(string $q, string $fields = "", int $offset = 0, int $count = 100)
function search(string $q,
string $fields = "",
int $offset = 0,
int $count = 100,
string $city = "",
string $hometown = "",
int $sex = 2,
int $status = 0, # это про marital status
bool $online = false,
# дальше идут параметры которых нету в vkapi но есть на сайте
string $profileStatus = "", # а это уже нормальный статус
int $sort = 0,
int $before = 0,
int $politViews = 0,
int $after = 0,
string $interests = "",
string $fav_music = "",
string $fav_films = "",
string $fav_shows = "",
string $fav_books = "",
string $fav_quotes = ""
)
{
$users = new UsersRepo;
$sortg = "id ASC";
$nfilds = $fields;
switch($sort) {
case 0:
$sortg = "id DESC";
break;
case 1:
$sortg = "id ASC";
break;
case 2:
$sortg = "first_name DESC";
break;
case 3:
$sortg = "first_name ASC";
break;
case 4:
$sortg = "rating DESC";
if(!str_contains($nfilds, "rating")) {
$nfilds .= "rating";
}
break;
case 5:
$sortg = "rating DESC";
if(!str_contains($nfilds, "rating")) {
$nfilds .= "rating";
}
break;
}
$array = [];
$find = $users->find($q);
$parameters = [
"city" => !empty($city) ? $city : NULL,
"hometown" => !empty($hometown) ? $hometown : NULL,
"gender" => $sex < 2 ? $sex : NULL,
"maritalstatus" => (bool)$status ? $status : NULL,
"politViews" => (bool)$politViews ? $politViews : NULL,
"is_online" => $online ? 1 : NULL,
"status" => !empty($profileStatus) ? $profileStatus : NULL,
"before" => $before != 0 ? $before : NULL,
"after" => $after != 0 ? $after : NULL,
"interests" => !empty($interests) ? $interests : NULL,
"fav_music" => !empty($fav_music) ? $fav_music : NULL,
"fav_films" => !empty($fav_films) ? $fav_films : NULL,
"fav_shows" => !empty($fav_shows) ? $fav_shows : NULL,
"fav_books" => !empty($fav_books) ? $fav_books : NULL,
"fav_quotes" => !empty($fav_quotes) ? $fav_quotes : NULL,
];
$find = $users->find($q, $parameters, $sortg);
foreach ($find as $user)
$array[] = $user->getId();
return (object) [
"count" => $find->size(),
"items" => $this->get(implode(',', $array), $fields, $offset, $count)
"items" => $this->get(implode(',', $array), $nfilds, $offset, $count)
];
}
}

View file

@ -1,5 +1,6 @@
<?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Repositories\{Users, Clubs};
final class Utils extends VKAPIRequestHandler
{
@ -7,4 +8,39 @@ final class Utils extends VKAPIRequestHandler
{
return time();
}
function resolveScreenName(string $screen_name): object
{
if(\Chandler\MVC\Routing\Router::i()->getMatchingRoute("/$screen_name")[0]->presenter !== "UnknownTextRouteStrategy") {
if(substr($screen_name, 0, strlen("id")) === "id") {
return (object) [
"object_id" => (int) substr($screen_name, strlen("id")),
"type" => "user"
];
} else if(substr($screen_name, 0, strlen("club")) === "club") {
return (object) [
"object_id" => (int) substr($screen_name, strlen("club")),
"type" => "group"
];
}
} else {
$user = (new Users)->getByShortURL($screen_name);
if($user) {
return (object) [
"object_id" => $user->getId(),
"type" => "user"
];
}
$club = (new Clubs)->getByShortURL($screen_name);
if($club) {
return (object) [
"object_id" => $club->getId(),
"type" => "group"
];
}
return (object) [];
}
}
}

View file

@ -1,15 +1,19 @@
<?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers;
use openvk\VKAPI\Exceptions\APIErrorException;
use openvk\Web\Models\Entities\IP;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\IPs;
abstract class VKAPIRequestHandler
{
protected $user;
protected $platform;
function __construct(?User $user = NULL)
function __construct(?User $user = NULL, ?string $platform = NULL)
{
$this->user = $user;
$this->user = $user;
$this->platform = $platform;
}
protected function fail(int $code, string $message): void
@ -22,6 +26,11 @@ abstract class VKAPIRequestHandler
return $this->user;
}
protected function getPlatform(): ?string
{
return $this->platform;
}
protected function userAuthorized(): bool
{
return !is_null($this->getUser());
@ -32,4 +41,19 @@ abstract class VKAPIRequestHandler
if(!$this->userAuthorized())
$this->fail(5, "User authorization failed: no access_token passed.");
}
protected function willExecuteWriteAction(): void
{
$ip = (new IPs)->get(CONNECTING_IP);
$res = $ip->rateLimit();
if(!($res === IP::RL_RESET || $res === IP::RL_CANEXEC)) {
if($res === IP::RL_BANNED && OPENVK_ROOT_CONF["openvk"]["preferences"]["security"]["rateLimits"]["autoban"]) {
$this->user->ban("User account has been suspended for breaking API terms of service", false);
$this->fail(18, "User account has been suspended due to repeated violation of API rate limits.");
}
$this->fail(29, "You have been rate limited.");
}
}
}

57
VKAPI/Handlers/Video.php Executable file
View file

@ -0,0 +1,57 @@
<?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\Users as UsersRepo;
use openvk\Web\Models\Entities\Club;
use openvk\Web\Models\Repositories\Clubs as ClubsRepo;
use openvk\Web\Models\Entities\Video as VideoEntity;
use openvk\Web\Models\Repositories\Videos as VideosRepo;
use openvk\Web\Models\Entities\Comment;
use openvk\Web\Models\Repositories\Comments as CommentsRepo;
final class Video extends VKAPIRequestHandler
{
function get(int $owner_id, string $videos, int $offset = 0, int $count = 30, int $extended = 0): object
{
$this->requireUser();
if ($videos) {
$vids = explode(',', $videos);
foreach($vids as $vid)
{
$id = explode("_", $vid);
$items = [];
$video = (new VideosRepo)->getByOwnerAndVID(intval($id[0]), intval($id[1]));
if($video) {
$items[] = $video->getApiStructure();
}
}
return (object) [
"count" => count($items),
"items" => $items
];
} else {
if ($owner_id > 0)
$user = (new UsersRepo)->get($owner_id);
else
$this->fail(1, "Not implemented");
$videos = (new VideosRepo)->getByUser($user, $offset + 1, $count);
$videosCount = (new VideosRepo)->getUserVideosCount($user);
$items = [];
foreach ($videos as $video) {
$items[] = $video->getApiStructure();
}
return (object) [
"count" => $videosCount,
"items" => $items
];
}
}
}

View file

@ -9,11 +9,19 @@ use openvk\Web\Models\Entities\Post;
use openvk\Web\Models\Repositories\Posts as PostsRepo;
use openvk\Web\Models\Entities\Comment;
use openvk\Web\Models\Repositories\Comments as CommentsRepo;
use openvk\Web\Models\Entities\Photo;
use openvk\Web\Models\Repositories\Photos as PhotosRepo;
use openvk\Web\Models\Entities\Video;
use openvk\Web\Models\Repositories\Videos as VideosRepo;
use openvk\Web\Models\Entities\Note;
use openvk\Web\Models\Repositories\Notes as NotesRepo;
final class Wall extends VKAPIRequestHandler
{
function get(int $owner_id, string $domain = "", int $offset = 0, int $count = 30, int $extended = 0): object
{
$this->requireUser();
$posts = new PostsRepo;
$items = [];
@ -21,10 +29,17 @@ final class Wall extends VKAPIRequestHandler
$groups = [];
$cnt = $posts->getPostCountOnUserWall($owner_id);
$wallOnwer = (new UsersRepo)->get($owner_id);
if ($owner_id > 0)
$wallOnwer = (new UsersRepo)->get($owner_id);
else
$wallOnwer = (new ClubsRepo)->get($owner_id * -1);
if(!$wallOnwer || $wallOnwer->isDeleted() || $wallOnwer->isDeleted())
$this->fail(18, "User was deleted or banned");
if ($owner_id > 0)
if(!$wallOnwer || $wallOnwer->isDeleted())
$this->fail(18, "User was deleted or banned");
else
if(!$wallOnwer)
$this->fail(15, "Access denied: wall is disabled"); // Don't search for logic here pls
foreach($posts->getPostsFromUsersWall($owner_id, 1, $count, $offset) as $post) {
$from_id = get_class($post->getOwner()) == "openvk\Web\Models\Entities\Club" ? $post->getOwner()->getId() * (-1) : $post->getOwner()->getId();
@ -35,21 +50,43 @@ final class Wall extends VKAPIRequestHandler
if($attachment instanceof \openvk\Web\Models\Entities\Photo) {
if($attachment->isDeleted())
continue;
$attachments[] = $this->getApiPhoto($attachment);
} else if($attachment instanceof \openvk\Web\Models\Entities\Poll) {
$attachments[] = $this->getApiPoll($attachment, $this->getUser());
} else if ($attachment instanceof \openvk\Web\Models\Entities\Video) {
$attachments[] = $attachment->getApiStructure();
} else if ($attachment instanceof \openvk\Web\Models\Entities\Note) {
$attachments[] = $attachment->toVkApiStruct();
} else if ($attachment instanceof \openvk\Web\Models\Entities\Post) {
$repostAttachments = [];
foreach($attachment->getChildren() as $repostAttachment) {
if($repostAttachment instanceof \openvk\Web\Models\Entities\Photo) {
if($attachment->isDeleted())
if($repostAttachment->isDeleted())
continue;
$repostAttachments[] = $this->getApiPhoto($repostAttachment);
/* Рекурсии, сука! Заказывали? */
}
}
if ($attachment->isPostedOnBehalfOfGroup())
$groups[] = $attachment->getOwner()->getId();
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(),
@ -58,13 +95,22 @@ final class Wall extends VKAPIRequestHandler
"post_type" => "post",
"text" => $attachment->getText(false),
"attachments" => $repostAttachments,
"post_source" => [
"type" => "vk"
],
"post_source" => $post_source,
];
}
}
$post_source = [];
if($post->getPlatform(true) === NULL) {
$post_source = (object)["type" => "vk"];
} else {
$post_source = (object)[
"type" => "api",
"platform" => $post->getPlatform(true)
];
}
$items[] = (object)[
"id" => $post->getVirtualId(),
"from_id" => $from_id,
@ -79,8 +125,9 @@ final class Wall extends VKAPIRequestHandler
"can_archive" => false, # TODO MAYBE
"is_archived" => false,
"is_pinned" => $post->isPinned(),
"is_explicit" => $post->isExplicit(),
"attachments" => $attachments,
"post_source" => (object)["type" => "vk"],
"post_source" => $post_source,
"comments" => (object)[
"count" => $post->getCommentsCount(),
"can_post" => 1
@ -96,7 +143,7 @@ final class Wall extends VKAPIRequestHandler
"user_reposted" => 0
]
];
if ($from_id > 0)
$profiles[] = $from_id;
else
@ -124,7 +171,8 @@ final class Wall extends VKAPIRequestHandler
"screen_name" => $user->getShortCode(),
"photo_50" => $user->getAvatarUrl(),
"photo_100" => $user->getAvatarUrl(),
"online" => $user->isOnline()
"online" => $user->isOnline(),
"verified" => $user->isVerified()
];
}
@ -139,6 +187,7 @@ final class Wall extends VKAPIRequestHandler
"photo_50" => $group->getAvatarUrl(),
"photo_100" => $group->getAvatarUrl(),
"photo_200" => $group->getAvatarUrl(),
"verified" => $group->isVerified()
];
}
@ -171,13 +220,19 @@ final class Wall extends VKAPIRequestHandler
foreach($psts as $pst) {
$id = explode("_", $pst);
$post = (new PostsRepo)->getPostById(intval($id[0]), intval($id[1]));
if($post) {
if($post && !$post->isDeleted()) {
$from_id = get_class($post->getOwner()) == "openvk\Web\Models\Entities\Club" ? $post->getOwner()->getId() * (-1) : $post->getOwner()->getId();
$attachments = [];
$repost = []; // чел высрал семь сигарет 😳 помянем 🕯
foreach($post->getChildren() as $attachment) {
if($attachment instanceof \openvk\Web\Models\Entities\Photo) {
$attachments[] = $this->getApiPhoto($attachment);
} else if($attachment instanceof \openvk\Web\Models\Entities\Poll) {
$attachments[] = $this->getApiPoll($attachment, $user);
} else if ($attachment instanceof \openvk\Web\Models\Entities\Video) {
$attachments[] = $attachment->getApiStructure();
} else if ($attachment instanceof \openvk\Web\Models\Entities\Note) {
$attachments[] = $attachment->toVkApiStruct();
} else if ($attachment instanceof \openvk\Web\Models\Entities\Post) {
$repostAttachments = [];
@ -185,11 +240,27 @@ final class Wall extends VKAPIRequestHandler
if($repostAttachment instanceof \openvk\Web\Models\Entities\Photo) {
if($attachment->isDeleted())
continue;
$repostAttachments[] = $this->getApiPhoto($repostAttachment);
/* Рекурсии, сука! Заказывали? */
}
}
}
if ($attachment->isPostedOnBehalfOfGroup())
$groups[] = $attachment->getOwner()->getId();
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(),
@ -199,13 +270,22 @@ final class Wall extends VKAPIRequestHandler
"post_type" => "post",
"text" => $attachment->getText(false),
"attachments" => $repostAttachments,
"post_source" => [
"type" => "vk"
],
"post_source" => $post_source,
];
}
}
$post_source = [];
if($post->getPlatform(true) === NULL) {
$post_source = (object)["type" => "vk"];
} else {
$post_source = (object)[
"type" => "api",
"platform" => $post->getPlatform(true)
];
}
$items[] = (object)[
"id" => $post->getVirtualId(),
"from_id" => $from_id,
@ -220,7 +300,8 @@ final class Wall extends VKAPIRequestHandler
"can_archive" => false, # TODO MAYBE
"is_archived" => false,
"is_pinned" => $post->isPinned(),
"post_source" => (object)["type" => "vk"],
"is_explicit" => $post->isExplicit(),
"post_source" => $post_source,
"attachments" => $attachments,
"comments" => (object)[
"count" => $post->getCommentsCount(),
@ -237,7 +318,7 @@ final class Wall extends VKAPIRequestHandler
"user_reposted" => 0
]
];
if ($from_id > 0)
$profiles[] = $from_id;
else
@ -267,7 +348,8 @@ final class Wall extends VKAPIRequestHandler
"screen_name" => $user->getShortCode(),
"photo_50" => $user->getAvatarUrl(),
"photo_100" => $user->getAvatarUrl(),
"online" => $user->isOnline()
"online" => $user->isOnline(),
"verified" => $user->isVerified()
];
}
@ -282,6 +364,7 @@ final class Wall extends VKAPIRequestHandler
"photo_50" => $group->getAvatarUrl(),
"photo_100" => $group->getAvatarUrl(),
"photo_200" => $group->getAvatarUrl(),
"verified" => $group->isVerified()
];
}
@ -296,12 +379,13 @@ final class Wall extends VKAPIRequestHandler
];
}
function post(string $owner_id, string $message = "", int $from_group = 0, int $signed = 0): object
function post(string $owner_id, string $message = "", int $from_group = 0, int $signed = 0, string $attachments = ""): object
{
$this->requireUser();
$this->willExecuteWriteAction();
$owner_id = intval($owner_id);
$wallOwner = ($owner_id > 0 ? (new UsersRepo)->get($owner_id) : (new ClubsRepo)->get($owner_id * -1))
?? $this->fail(18, "User was deleted or banned");
if($owner_id > 0)
@ -312,7 +396,7 @@ final class Wall extends VKAPIRequestHandler
else
$canPost = $wallOwner->canPost();
else
$canPost = false;
$canPost = false;
if($canPost == false) $this->fail(15, "Access denied");
@ -333,27 +417,7 @@ final class Wall extends VKAPIRequestHandler
if($signed == 1)
$flags |= 0b01000000;
# TODO: Compatible implementation of this
try {
$photo = NULL;
$video = NULL;
if($_FILES["photo"]["error"] === UPLOAD_ERR_OK) {
$album = NULL;
if(!$anon && $owner_id > 0 && $owner_id === $this->getUser()->getId())
$album = (new AlbumsRepo)->getUserWallAlbum($wallOwner);
$photo = Photo::fastMake($this->getUser()->getId(), $message, $_FILES["photo"], $album, $anon);
}
if($_FILES["video"]["error"] === UPLOAD_ERR_OK)
$video = Video::fastMake($this->getUser()->getId(), $message, $_FILES["video"], $anon);
} catch(\DomainException $ex) {
$this->fail(-156, "The media file is corrupted");
} catch(ISE $ex) {
$this->fail(-156, "The media file is corrupted or too large ");
}
if(empty($message) && !$photo && !$video)
if(empty($message) && empty($attachments))
$this->fail(100, "Required parameter 'message' missing.");
try {
@ -363,16 +427,69 @@ final class Wall extends VKAPIRequestHandler
$post->setCreated(time());
$post->setContent($message);
$post->setFlags($flags);
$post->setApi_Source_Name($this->getPlatform());
$post->save();
} catch(\LogicException $ex) {
$this->fail(100, "One of the parameters specified was missing or invalid");
}
if(!is_null($photo))
$post->attach($photo);
if(!empty($attachments)) {
$attachmentsArr = explode(",", $attachments);
# Аттачи такого вида: [тип][id владельца]_[id вложения]
# Пример: photo1_1
if(!is_null($video))
$post->attach($video);
if(sizeof($attachmentsArr) > 10)
$this->fail(50, "Error: too many attachments");
foreach($attachmentsArr as $attac) {
$attachmentType = NULL;
if(str_contains($attac, "photo"))
$attachmentType = "photo";
elseif(str_contains($attac, "video"))
$attachmentType = "video";
elseif(str_contains($attac, "note"))
$attachmentType = "note";
else
$this->fail(205, "Unknown attachment type");
$attachment = str_replace($attachmentType, "", $attac);
$attachmentOwner = (int)explode("_", $attachment)[0];
$attachmentId = (int)end(explode("_", $attachment));
$attacc = NULL;
if($attachmentType == "photo") {
$attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Photo does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this photo");
$post->attach($attacc);
} elseif($attachmentType == "video") {
$attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Video does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this video");
$post->attach($attacc);
} elseif($attachmentType == "note") {
$attacc = (new NotesRepo)->getNoteById($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Note does not exist");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this note");
if($attacc->getOwner()->getPrivacySetting("notes.read") < 1)
$this->fail(11, "You can't attach note to post, because your notes list is closed. Change it in privacy settings in web-version.");
$post->attach($attacc);
}
}
}
if($wall > 0 && $wall !== $this->user->identity->getId())
(new WallPostNotification($wallOwner, $post, $this->user->identity))->emit();
@ -380,8 +497,9 @@ final class Wall extends VKAPIRequestHandler
return (object)["post_id" => $post->getVirtualId()];
}
function repost(string $object, string $message = "") {
function repost(string $object, string $message = "", int $group_id = 0) {
$this->requireUser();
$this->willExecuteWriteAction();
$postArray;
if(preg_match('/wall((?:-?)[0-9]+)_([0-9]+)/', $object, $postArray) == 0)
@ -392,13 +510,27 @@ final class Wall extends VKAPIRequestHandler
$nPost = new Post;
$nPost->setOwner($this->user->getId());
$nPost->setWall($this->user->getId());
if($group_id > 0) {
$club = (new ClubsRepo)->get($group_id);
if(!$club)
$this->fail(42, "Invalid group");
if(!$club->canBeModifiedBy($this->user))
$this->fail(16, "Access to group denied");
$nPost->setWall($group_id * -1);
} else {
$nPost->setWall($this->user->getId());
}
$nPost->setContent($message);
$nPost->setApi_Source_Name($this->getPlatform());
$nPost->save();
$nPost->attach($post);
if($post->getOwner(false)->getId() !== $this->user->getId() && !($post->getOwner() instanceof Club))
(new RepostNotification($post->getOwner(false), $post, $this->user->identity))->emit();
(new RepostNotification($post->getOwner(false), $post, $this->user))->emit();
return (object) [
"success" => 1, // 👍
@ -408,6 +540,7 @@ final class Wall extends VKAPIRequestHandler
];
}
function getComments(int $owner_id, int $post_id, bool $need_likes = true, int $offset = 0, int $count = 10, string $fields = "sex,screen_name,photo_50,photo_100,online_info,online", string $sort = "asc", bool $extended = false) {
$this->requireUser();
@ -415,19 +548,35 @@ final class Wall extends VKAPIRequestHandler
if(!$post || $post->isDeleted()) $this->fail(100, "One of the parameters specified was missing or invalid");
$comments = (new CommentsRepo)->getCommentsByTarget($post, $offset+1, $count, $sort == "desc" ? "DESC" : "ASC");
$items = [];
$profiles = [];
foreach($comments as $comment) {
$owner = $comment->getOwner();
$oid = $owner->getId();
if($owner instanceof Club)
$oid *= -1;
$attachments = [];
foreach($comment->getChildren() as $attachment) {
if($attachment instanceof \openvk\Web\Models\Entities\Photo) {
$attachments[] = $this->getApiPhoto($attachment);
} elseif($attachment instanceof \openvk\Web\Models\Entities\Note) {
$attachments[] = $attachment->toVkApiStruct();
}
}
$item = [
"id" => $comment->getId(),
"from_id" => $comment->getOwner()->getId(),
"from_id" => $oid,
"date" => $comment->getPublicationTime()->timestamp(),
"text" => $comment->getText(false),
"post_id" => $post->getVirtualId(),
"owner_id" => $post->isPostedOnBehalfOfGroup() ? $post->getOwner()->getId() * -1 : $post->getOwner()->getId(),
"parents_stack" => [],
"attachments" => $attachments,
"thread" => [
"count" => 0,
"items" => [],
@ -444,10 +593,13 @@ final class Wall extends VKAPIRequestHandler
"user_likes" => (int) $comment->hasLikeFrom($this->getUser()),
"can_publish" => 1
];
$items[] = $item;
if($extended == true)
$profiles[] = $comment->getOwner()->getId();
$attachments = null;
// Reset $attachments to not duplicate prikols
}
$response = [
@ -470,10 +622,18 @@ final class Wall extends VKAPIRequestHandler
function getComment(int $owner_id, int $comment_id, bool $extended = false, string $fields = "sex,screen_name,photo_50,photo_100,online_info,online") {
$this->requireUser();
$comment = (new CommentsRepo)->get($comment_id); // один хуй айди всех комментов общий
$comment = (new CommentsRepo)->get($comment_id); # один хуй айди всех комментов общий
$profiles = [];
$attachments = [];
foreach($comment->getChildren() as $attachment) {
if($attachment instanceof \openvk\Web\Models\Entities\Photo) {
$attachments[] = $this->getApiPhoto($attachment);
}
}
$item = [
"id" => $comment->getId(),
"from_id" => $comment->getOwner()->getId(),
@ -482,6 +642,7 @@ final class Wall extends VKAPIRequestHandler
"post_id" => $comment->getTarget()->getVirtualId(),
"owner_id" => $comment->getTarget()->isPostedOnBehalfOfGroup() ? $comment->getTarget()->getOwner()->getId() * -1 : $comment->getTarget()->getOwner()->getId(),
"parents_stack" => [],
"attachments" => $attachments,
"likes" => [
"can_like" => 1,
"count" => $comment->getLikesCount(),
@ -496,7 +657,7 @@ final class Wall extends VKAPIRequestHandler
"groups_can_post" => false,
]
];
if($extended == true)
$profiles[] = $comment->getOwner()->getId();
@ -512,20 +673,29 @@ final class Wall extends VKAPIRequestHandler
$response['profiles'] = (!empty($profiles) ? (new Users)->get(implode(',', $profiles), $fields) : []);
}
return $response;
}
function createComment(int $owner_id, int $post_id, string $message, int $from_group = 0) {
function createComment(int $owner_id, int $post_id, string $message, int $from_group = 0, string $attachments = "") {
$this->requireUser();
$this->willExecuteWriteAction();
$post = (new PostsRepo)->getPostById($owner_id, $post_id);
if(!$post || $post->isDeleted()) $this->fail(100, "One of the parameters specified was missing or invalid");
if(!$post || $post->isDeleted()) $this->fail(100, "Invalid post");
if($post->getTargetWall() < 0)
$club = (new ClubsRepo)->get(abs($post->getTargetWall()));
if(empty($message) && empty($attachments)) {
$this->fail(100, "Required parameter 'message' missing.");
}
$flags = 0;
if($from_group != 0 && !is_null($club) && $club->canBeModifiedBy($this->user))
$flags |= 0b10000000;
try {
$comment = new Comment;
$comment->setOwner($this->user->getId());
@ -538,11 +708,54 @@ final class Wall extends VKAPIRequestHandler
} catch (\LengthException $ex) {
$this->fail(1, "ошибка про то что коммент большой слишком");
}
if(!empty($attachments)) {
$attachmentsArr = explode(",", $attachments);
if(sizeof($attachmentsArr) > 10)
$this->fail(50, "Error: too many attachments");
foreach($attachmentsArr as $attac) {
$attachmentType = NULL;
if(str_contains($attac, "photo"))
$attachmentType = "photo";
elseif(str_contains($attac, "video"))
$attachmentType = "video";
else
$this->fail(205, "Unknown attachment type");
$attachment = str_replace($attachmentType, "", $attac);
$attachmentOwner = (int)explode("_", $attachment)[0];
$attachmentId = (int)end(explode("_", $attachment));
$attacc = NULL;
if($attachmentType == "photo") {
$attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Photo does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this photo");
$comment->attach($attacc);
} elseif($attachmentType == "video") {
$attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Video does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this video");
$comment->attach($attacc);
}
}
}
if($post->getOwner()->getId() !== $this->user->getId())
if(($owner = $post->getOwner()) instanceof User)
(new CommentNotification($owner, $comment, $post, $this->user))->emit();
return (object) [
"comment_id" => $comment->getId(),
"parents_stack" => []
@ -551,14 +764,15 @@ final class Wall extends VKAPIRequestHandler
function deleteComment(int $comment_id) {
$this->requireUser();
$this->willExecuteWriteAction();
$comment = (new CommentsRepo)->get($comment_id);
if(!$comment) $this->fail(100, "One of the parameters specified was missing or invalid");;
if(!$comment->canBeDeletedBy($this->user))
$this->fail(7, "Access denied");
$comment->delete();
return 1;
}
@ -570,10 +784,50 @@ final class Wall extends VKAPIRequestHandler
"date" => $attachment->getPublicationTime()->timestamp(),
"id" => $attachment->getVirtualId(),
"owner_id" => $attachment->getOwner()->getId(),
"sizes" => array_values($attachment->getVkApiSizes()),
"sizes" => !is_null($attachment->getVkApiSizes()) ? array_values($attachment->getVkApiSizes()) : NULL,
"text" => "",
"has_tags" => false
]
];
}
private function getApiPoll($attachment, $user) {
$answers = array();
foreach($attachment->getResults()->options as $answer) {
$answers[] = (object)[
"id" => $answer->id,
"rate" => $answer->pct,
"text" => $answer->name,
"votes" => $answer->votes
];
}
$userVote = array();
foreach($attachment->getUserVote($user) as $vote)
$userVote[] = $vote[0];
return [
"type" => "poll",
"poll" => [
"multiple" => $attachment->isMultipleChoice(),
"end_date" => $attachment->endsAt() == NULL ? 0 : $attachment->endsAt()->timestamp(),
"closed" => $attachment->hasEnded(),
"is_board" => false,
"can_edit" => false,
"can_vote" => $attachment->canVote($user),
"can_report" => false,
"can_share" => true,
"created" => 0,
"id" => $attachment->getId(),
"owner_id" => $attachment->getOwner()->getId(),
"question" => $attachment->getTitle(),
"votes" => $attachment->getVoterCount(),
"disable_unvote" => $attachment->isRevotable(),
"anonymous" => $attachment->isAnonymous(),
"answer_ids" => $userVote,
"answers" => $answers,
"author_id" => $attachment->getOwner()->getId(),
]
];
}
}

View file

@ -1,10 +1,12 @@
# VK API Compatability layer for OpenVK
This directory contains VK api handlers, structures and relared
This directory contains VK API handlers, structures and relared
exceptions. It is still a work-in-progress functionality.
**Note**: requests to api are routed through
**Note**: requests to API are routed through
openvk.Web.Presenters.VKAPIPresenter, this dir contains only handlers.
[Documentation for API clients](https://docs.openvk.uk/openvk_engine/api/description/)
## Implementing API methods
VK API methods have names like this: `example.test`. To implement a

View file

@ -27,14 +27,23 @@ class NewMessageEvent implements ILPEmitable
if($peer === $userId)
$peer = $msg->getRecipient()->getId();
/*
* Source:
* https://github.com/danyadev/longpoll-doc
*/
return [
4, # event type
$msg->getId(), # messageId
256, # checked for spam flag
$peer, # TODO calculate peer correctly
$msg->getSendTime()->timestamp(), # creation time in unix
$msg->getText(), # text (formatted)
[], # empty additional info
[], # empty attachments
$msg->getId() << 2, # id as random_id
$peer, # conversation id
0 # not edited yet
];
}
}

View file

@ -22,6 +22,11 @@ class APIToken extends RowModel
{
return $this->getId() . "-" . chunk_split($this->getSecret(), 8, "-") . "jill";
}
function getPlatform(): ?string
{
return $this->getRecord()->platform;
}
function isRevoked(): bool
{

View file

@ -66,4 +66,31 @@ class Album extends MediaCollection
{
return $this->has($photo);
}
function toVkApiStruct(?User $user = NULL, bool $need_covers = false, bool $photo_sizes = false): object
{
$res = (object) [];
$res->id = $this->getPrettyId();
$res->thumb_id = !is_null($this->getCoverPhoto()) ? $this->getCoverPhoto()->getPrettyId() : 0;
$res->owner_id = $this->getOwner()->getId();
$res->title = $this->getName();
$res->description = $this->getDescription();
$res->created = $this->getCreationTime()->timestamp();
$res->updated = $this->getEditTime() ? $this->getEditTime()->timestamp() : NULL;
$res->size = $this->size();
$res->privacy_comment = 1;
$res->upload_by_admins_only = 1;
$res->comments_disabled = 0;
$res->can_upload = $this->canBeModifiedBy($user); # thisUser недоступен в entities
if($need_covers) {
$res->thumb_src = $this->getCoverURL();
if($photo_sizes) {
$res->sizes = !is_null($this->getCoverPhoto()) ? $this->getCoverPhoto()->getVkApiSizes() : NULL;
}
}
return $res;
}
}

View file

@ -0,0 +1,34 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use openvk\Web\Models\RowModel;
use openvk\Web\Models\Entities\{User, Club};
use openvk\Web\Models\Repositories\{Users, Clubs};
class Alias extends RowModel
{
protected $tableName = "aliases";
function getOwnerId(): int
{
return $this->getRecord()->owner_id;
}
function getType(): string
{
if ($this->getOwnerId() < 0)
return "club";
return "user";
}
function getUser(): ?User
{
return (new Users)->get($this->getOwnerId());
}
function getClub(): ?Club
{
return (new Clubs)->get($this->getOwnerId() * -1);
}
}

View file

@ -160,7 +160,7 @@ class Club extends RowModel
function canPost(): bool
{
return (bool) $this->getRecord()->wall;
return (bool) $this->getRecord()->wall;
}
@ -262,12 +262,12 @@ class Club extends RowModel
return $subbed && ($this->getOpennesStatus() === static::CLOSED ? $this->isSubscriptionAccepted($user) : true);
}
function getFollowersQuery(): GroupedSelection
function getFollowersQuery(string $sort = "follower ASC"): GroupedSelection
{
$query = $this->getRecord()->related("subscriptions.target");
if($this->getOpennesStatus() === static::OPEN) {
$query = $query->where("model", "openvk\\Web\\Models\\Entities\\Club");
$query = $query->where("model", "openvk\\Web\\Models\\Entities\\Club")->order($sort);
} else {
return false;
}
@ -280,9 +280,9 @@ class Club extends RowModel
return sizeof($this->getFollowersQuery());
}
function getFollowers(int $page = 1): \Traversable
function getFollowers(int $page = 1, int $perPage = 6, string $sort = "follower ASC"): \Traversable
{
$rels = $this->getFollowersQuery()->page($page, 6);
$rels = $this->getFollowersQuery($sort)->page($page, $perPage);
foreach($rels as $rel) {
$rel = (new Users)->get($rel->follower);
@ -360,5 +360,35 @@ class Club extends RowModel
return $this->getRecord()->alert;
}
function toVkApiStruct(?User $user = NULL): object
{
$res = [];
$res->id = $this->getId();
$res->name = $this->getName();
$res->screen_name = $this->getShortCode();
$res->is_closed = 0;
$res->deactivated = NULL;
$res->is_admin = $this->canBeModifiedBy($user);
if($this->canBeModifiedBy($user)) {
$res->admin_level = 3;
}
$res->is_member = $this->getSubscriptionStatus($user) ? 1 : 0;
$res->type = "group";
$res->photo_50 = $this->getAvatarUrl("miniscule");
$res->photo_100 = $this->getAvatarUrl("tiny");
$res->photo_200 = $this->getAvatarUrl("normal");
$res->can_create_topic = $this->canBeModifiedBy($user) ? 1 : ($this->isEveryoneCanCreateTopics() ? 1 : 0);
$res->can_post = $this->canBeModifiedBy($user) ? 1 : ($this->canPost() ? 1 : 0);
return (object) $res;
}
use Traits\TBackDrops;
use Traits\TSubscribable;
}

View file

@ -2,6 +2,7 @@
namespace openvk\Web\Models\Entities;
use openvk\Web\Models\Repositories\Clubs;
use openvk\Web\Models\RowModel;
use openvk\Web\Models\Entities\{Note};
class Comment extends Post
{
@ -52,4 +53,36 @@ class Comment extends Post
$this->getTarget() instanceof Post && $this->getTarget()->getTargetWall() < 0 && (new Clubs)->get(abs($this->getTarget()->getTargetWall()))->canBeModifiedBy($user) ||
$this->getTarget() instanceof Topic && $this->getTarget()->canBeModifiedBy($user);
}
function toVkApiStruct(?User $user = NULL, bool $need_likes = false, bool $extended = false, ?Note $note = NULL): object
{
$res = (object) [];
$res->id = $this->getId();
$res->from_id = $this->getOwner()->getId();
$res->date = $this->getPublicationTime()->timestamp();
$res->text = $this->getText(false);
$res->attachments = [];
$res->parents_stack = [];
if(!is_null($note)) {
$res->uid = $this->getOwner()->getId();
$res->nid = $note->getId();
$res->oid = $note->getOwner()->getId();
}
foreach($this->getChildren() as $attachment) {
if($attachment->isDeleted())
continue;
$res->attachments[] = $attachment->toVkApiStruct();
}
if($need_likes) {
$res->count = $this->getLikesCount();
$res->user_likes = (int)$this->hasLikeFrom($user);
$res->can_like = 1;
}
return $res;
}
}

View file

@ -118,4 +118,24 @@ class Note extends Postable
{
return $this->getRecord()->source;
}
function toVkApiStruct(): object
{
$res = (object) [];
$res->type = "note";
$res->id = $this->getId();
$res->owner_id = $this->getOwner()->getId();
$res->title = $this->getName();
$res->text = $this->getText();
$res->date = $this->getPublicationTime()->timestamp();
$res->comments = $this->getCommentsCount();
$res->read_comments = $this->getCommentsCount();
$res->view_url = "/note".$this->getOwner()->getId()."_".$this->getId();
$res->privacy_view = 1;
$res->can_comment = 1;
$res->text_wiki = "r";
return $res;
}
}

View file

@ -8,6 +8,6 @@ final class CommentNotification extends Notification
function __construct(User $recipient, Comment $comment, $postable, User $commenter)
{
parent::__construct($recipient, $postable, $commenter, time(), ovk_proc_strtr($comment->getText(), 10));
parent::__construct($recipient, $postable, $commenter, time(), ovk_proc_strtr(strip_tags($comment->getText()), 400));
}
}

View file

@ -1,13 +1,14 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Notifications;
use openvk\Web\Models\Entities\Postable;
use openvk\Web\Models\Entities\User;
final class MentionNotification extends Notification
{
protected $actionCode = 4;
function __construct(User $recipient, User $target, User $mentioner)
function __construct(User $recipient, Postable $discussionHost, $mentioner, string $quote = "")
{
parent::__construct($recipient, $target, $mentioner, time(), "");
parent::__construct($recipient, $mentioner, $discussionHost, time(), $quote);
}
}

View file

@ -30,6 +30,11 @@ class Notification
return (int) json_decode(file_get_contents(__DIR__ . "/../../../../data/modelCodes.json"), true)[get_class($model)];
}
function reverseModelOrder(): bool
{
return false;
}
function getActionCode(): int
{
return $this->actionCode;

View file

@ -1,6 +1,8 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use MessagePack\MessagePack;
use Nette\Utils\ImageException;
use Nette\Utils\UnknownImageFileException;
use openvk\Web\Models\Entities\Album;
use openvk\Web\Models\Repositories\Albums;
use Chandler\Database\DatabaseConnection as DB;
@ -13,48 +15,62 @@ class Photo extends Media
protected $fileExtension = "jpeg";
const ALLOWED_SIDE_MULTIPLIER = 7;
private function resizeImage(string $filename, string $outputDir, \SimpleXMLElement $size): array
/**
* @throws \ImagickException
* @throws ImageException
* @throws UnknownImageFileException
*/
private function resizeImage(\Imagick $image, string $outputDir, \SimpleXMLElement $size): array
{
$res = [false];
$image = Image::fromFile($filename);
$res = [false];
$requiresProportion = ((string) $size["requireProp"]) != "none";
if($requiresProportion) {
$props = explode(":", (string) $size["requireProp"]);
$px = (int) $props[0];
$py = (int) $props[1];
if(($image->getWidth() / $image->getHeight()) > ($px / $py)) {
# For some weird reason using resize with EXACT flag causes system to consume an unholy amount of RAM
$image->crop(0, 0, "100%", (int) ceil(($px * $image->getWidth()) / $py));
if(($image->getImageWidth() / $image->getImageHeight()) > ($px / $py)) {
$height = (int) ceil(($px * $image->getImageWidth()) / $py);
$image->cropImage($image->getImageWidth(), $height, 0, 0);
$res[0] = true;
}
}
if(isset($size["maxSize"])) {
$maxSize = (int) $size["maxSize"];
$image->resize($maxSize, $maxSize, Image::SHRINK_ONLY | Image::FIT);
$sizes = Image::calculateSize($image->getImageWidth(), $image->getImageHeight(), $maxSize, $maxSize, Image::SHRINK_ONLY | Image::FIT);
$image->resizeImage($sizes[0], $sizes[1], \Imagick::FILTER_HERMITE, 1);
} else if(isset($size["maxResolution"])) {
$resolution = explode("x", (string) $size["maxResolution"]);
$image->resize((int) $resolution[0], (int) $resolution[1], Image::SHRINK_ONLY | Image::FIT);
$sizes = Image::calculateSize(
$image->getImageWidth(), $image->getImageHeight(), (int) $resolution[0], (int) $resolution[1], Image::SHRINK_ONLY | Image::FIT
);
$image->resizeImage($sizes[0], $sizes[1], \Imagick::FILTER_HERMITE, 1);
} else {
throw new \RuntimeException("Malformed size description: " . (string) $size["id"]);
}
$res[1] = $image->getWidth();
$res[2] = $image->getHeight();
$res[1] = $image->getImageWidth();
$res[2] = $image->getImageHeight();
if($res[1] <= 300 || $res[2] <= 300)
$image->save("$outputDir/" . (string) $size["id"] . ".gif");
$image->writeImage("$outputDir/$size[id].gif");
else
$image->save("$outputDir/" . (string) $size["id"] . ".jpeg");
imagedestroy($image->getImageResource());
$image->writeImage("$outputDir/$size[id].jpeg");
$res[3] = true;
$image->destroy();
unset($image);
return $res;
}
private function saveImageResizedCopies(string $filename, string $hash): void
private function saveImageResizedCopies(?\Imagick $image, string $filename, string $hash): void
{
if(!$image) {
$image = new \Imagick;
$image->readImage($filename);
}
$dir = dirname($this->pathFromHash($hash));
$dir = "$dir/$hash" . "_cropped";
if(!is_dir($dir)) {
@ -67,8 +83,13 @@ class Photo extends Media
throw new \RuntimeException("Could not load photosizes.xml!");
$sizesMeta = [];
foreach($sizes->Size as $size)
$sizesMeta[(string) $size["id"]] = $this->resizeImage($filename, $dir, $size);
if(OPENVK_ROOT_CONF["openvk"]["preferences"]["photos"]["photoSaving"] === "quick") {
foreach($sizes->Size as $size)
$sizesMeta[(string)$size["id"]] = [false, false, false, false];
} else {
foreach($sizes->Size as $size)
$sizesMeta[(string)$size["id"]] = $this->resizeImage(clone $image, $dir, $size);
}
$sizesMeta = MessagePack::pack($sizesMeta);
$this->stateChanges("sizes", $sizesMeta);
@ -76,13 +97,19 @@ class Photo extends Media
protected function saveFile(string $filename, string $hash): bool
{
$image = Image::fromFile($filename);
if(($image->height >= ($image->width * Photo::ALLOWED_SIDE_MULTIPLIER)) || ($image->width >= ($image->height * Photo::ALLOWED_SIDE_MULTIPLIER)))
$image = new \Imagick;
$image->readImage($filename);
$h = $image->getImageHeight();
$w = $image->getImageWidth();
if(($h >= ($w * Photo::ALLOWED_SIDE_MULTIPLIER)) || ($w >= ($h * Photo::ALLOWED_SIDE_MULTIPLIER)))
throw new ISE("Invalid layout: image is too wide/short");
$image->resize(8192, 4320, Image::SHRINK_ONLY | Image::FIT);
$image->save($this->pathFromHash($hash), 92, Image::JPEG);
$this->saveImageResizedCopies($filename, $hash);
$sizes = Image::calculateSize(
$image->getImageWidth(), $image->getImageHeight(), 8192, 4320, Image::SHRINK_ONLY | Image::FIT
);
$image->resizeImage($sizes[0], $sizes[1], \Imagick::FILTER_HERMITE, 1);
$image->writeImage($this->pathFromHash($hash));
$this->saveImageResizedCopies($image, $filename, $hash);
return true;
}
@ -114,8 +141,8 @@ class Photo extends Media
$sizes = $this->getRecord()->sizes;
if(!$sizes || $forceUpdate) {
if($forceUpdate || $upgrade || OPENVK_ROOT_CONF["openvk"]["preferences"]["photos"]["upgradeStructure"]) {
$hash = $this->getRecord()->hash;
$this->saveImageResizedCopies($this->pathFromHash($hash), $hash);
$hash = $this->getRecord()->hash;
$this->saveImageResizedCopies(NULL, $this->pathFromHash($hash), $hash);
$this->save();
return $this->getSizes();
@ -127,6 +154,16 @@ class Photo extends Media
$res = [];
$sizes = MessagePack::unpack($sizes);
foreach($sizes as $id => $meta) {
if(isset($meta[3]) && !$meta[3]) {
$res[$id] = (object) [
"url" => ovk_scheme(true) . $_SERVER["HTTP_HOST"] . "/photos/thumbnails/" . $this->getId() . "_$id.jpeg",
"width" => NULL,
"height" => NULL,
"crop" => NULL
];
continue;
}
$url = $this->getURL();
$url = str_replace(".$this->fileExtension", "_cropped/$id.", $url);
$url .= ($meta[1] <= 300 || $meta[2] <= 300) ? "gif" : "jpeg";
@ -149,6 +186,47 @@ class Photo extends Media
return $res;
}
function forceSize(string $sizeName): bool
{
$hash = $this->getRecord()->hash;
$sizes = MessagePack::unpack($this->getRecord()->sizes);
$size = $sizes[$sizeName] ?? false;
if(!$size)
return $size;
if(!isset($size[3]) || $size[3] === true)
return true;
$path = $this->pathFromHash($hash);
$dir = dirname($this->pathFromHash($hash));
$dir = "$dir/$hash" . "_cropped";
if(!is_dir($dir)) {
@unlink($dir);
mkdir($dir);
}
$sizeMetas = simplexml_load_file(OPENVK_ROOT . "/data/photosizes.xml");
if(!$sizeMetas)
throw new \RuntimeException("Could not load photosizes.xml!");
$sizeInfo = NULL;
foreach($sizeMetas->Size as $size)
if($size["id"] == $sizeName)
$sizeInfo = $size;
if(!$sizeInfo)
return false;
$pic = new \Imagick;
$pic->readImage($path);
$sizes[$sizeName] = $this->resizeImage($pic, $dir, $sizeInfo);
$this->stateChanges("sizes", MessagePack::pack($sizes));
$this->save();
return $sizes[$sizeName][3];
}
function getVkApiSizes(): ?array
{
@ -205,30 +283,48 @@ class Photo extends Media
return [$x, $y];
}
function getPageURL(): string
{
if($this->isAnonymous())
return "/photos/" . base_convert((string) $this->getId(), 10, 32);
return "/photo" . $this->getPrettyId();
}
function getAlbum(): ?Album
{
return (new Albums)->getAlbumByPhotoId($this);
}
function toVkApiStruct(): object
function toVkApiStruct(bool $photo_sizes = true, bool $extended = false): object
{
$res = (object) [];
$res->id = $res->pid = $this->getId();
$res->owner_id = $res->user_id = $this->getOwner()->getId()->getId();
$res->id = $res->pid = $this->getVirtualId();
$res->owner_id = $res->user_id = $this->getOwner()->getId();
$res->aid = $res->album_id = NULL;
$res->width = $this->getDimensions()[0];
$res->height = $this->getDimensions()[1];
$res->date = $res->created = $this->getPublicationTime()->timestamp();
$res->sizes = $this->getVkApiSizes();
$res->src_small = $res->photo_75 = $this->getURLBySizeId("miniscule");
$res->src = $res->photo_130 = $this->getURLBySizeId("tiny");
$res->src_big = $res->photo_604 = $this->getURLBySizeId("normal");
$res->src_xbig = $res->photo_807 = $this->getURLBySizeId("large");
$res->src_xxbig = $res->photo_1280 = $this->getURLBySizeId("larger");
$res->src_xxxbig = $res->photo_2560 = $this->getURLBySizeId("original");
$res->src_original = $res->url = $this->getURLBySizeId("UPLOADED_MAXRES");
if($photo_sizes) {
$res->sizes = $this->getVkApiSizes();
$res->src_small = $res->photo_75 = $this->getURLBySizeId("miniscule");
$res->src = $res->photo_130 = $this->getURLBySizeId("tiny");
$res->src_big = $res->photo_604 = $this->getURLBySizeId("normal");
$res->src_xbig = $res->photo_807 = $this->getURLBySizeId("large");
$res->src_xxbig = $res->photo_1280 = $this->getURLBySizeId("larger");
$res->src_xxxbig = $res->photo_2560 = $this->getURLBySizeId("original");
$res->src_original = $res->url = $this->getURLBySizeId("UPLOADED_MAXRES");
}
if($extended) {
$res->likes = $this->getLikesCount(); # их нету но пусть будут
$res->comments = $this->getCommentsCount();
$res->tags = 0;
$res->can_comment = 1;
$res->can_repost = 0;
}
return $res;
}

View file

@ -0,0 +1,295 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use openvk\Web\Models\Exceptions\TooMuchOptionsException;
use openvk\Web\Util\DateTime;
use \UnexpectedValueException;
use Nette\InvalidStateException;
use openvk\Web\Models\Repositories\Users;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Exceptions\PollLockedException;
use openvk\Web\Models\Exceptions\AlreadyVotedException;
use openvk\Web\Models\Exceptions\InvalidOptionException;
class Poll extends Attachable
{
protected $tableName = "polls";
private $choicesToPersist = [];
function getTitle(): string
{
return $this->getRecord()->title;
}
function getMetaDescription(): string
{
$props = [];
$props[] = tr($this->isAnonymous() ? "poll_anon" : "poll_public");
if($this->isMultipleChoice()) $props[] = tr("poll_multi");
if(!$this->isRevotable()) $props[] = tr("poll_lock");
if(!is_null($this->endsAt())) $props[] = tr("poll_until", $this->endsAt());
return implode("", $props);
}
function getOwner(): User
{
return (new Users)->get($this->getRecord()->owner);
}
function getOptions(): array
{
$options = $this->getRecord()->related("poll_options.poll");
$res = [];
foreach($options as $opt)
$res[$opt->id] = $opt->name;
return $res;
}
function getUserVote(User $user): ?array
{
$ctx = DatabaseConnection::i()->getContext();
$votedOpts = $ctx->table("poll_votes")
->where(["user" => $user->getId(), "poll" => $this->getId()]);
if($votedOpts->count() == 0)
return NULL;
$res = [];
foreach($votedOpts as $votedOpt) {
$option = $ctx->table("poll_options")->get($votedOpt->option);
$res[] = [$option->id, $option->name];
}
return $res;
}
function getVoters(int $optionId, int $page = 1, ?int $perPage = NULL): array
{
$res = [];
$ctx = DatabaseConnection::i()->getContext();
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE;
$voters = $ctx->table("poll_votes")->where(["poll" => $this->getId(), "option" => $optionId]);
foreach($voters->page($page, $perPage) as $vote)
$res[] = (new Users)->get($vote->user);
return $res;
}
function getVoterCount(?int $optionId = NULL): int
{
$votes = DatabaseConnection::i()->getContext()->table("poll_votes");
if(!$optionId)
return $votes->select("COUNT(DISTINCT user) AS c")->where("poll", $this->getId())->fetch()->c;
return $votes->where(["poll" => $this->getId(), "option" => $optionId])->count();
}
function getResults(?User $user = NULL): object
{
$ctx = DatabaseConnection::i()->getContext();
$voted = NULL;
if(!is_null($user))
$voted = $this->getUserVote($user);
$result = (object) [];
$result->totalVotes = $this->getVoterCount();
$unsOptions = [];
foreach($this->getOptions() as $id => $title) {
$option = (object) [];
$option->id = $id;
$option->name = $title;
$option->votes = $this->getVoterCount($id);
$option->pct = $result->totalVotes == 0 ? 0 : min(100, floor(($option->votes / $result->totalVotes) * 100));
$option->voters = $this->getVoters($id, 1, 10);
if(!$user || !$voted)
$option->voted = NULL;
else
$option->voted = in_array([$id, $title], $voted);
$unsOptions[$id] = $option;
}
$optionsC = sizeof($unsOptions);
$sOptions = $unsOptions;
usort($sOptions, function($a, $b) { return $a->votes <=> $b->votes; });
for($i = 0; $i < $optionsC; $i++)
$unsOptions[$id]->rate = $optionsC - $i - 1;
$result->options = array_values($unsOptions);
return $result;
}
function isAnonymous(): bool
{
return (bool) $this->getRecord()->is_anonymous;
}
function isMultipleChoice(): bool
{
return (bool) $this->getRecord()->allows_multiple;
}
function isRevotable(): bool
{
return (bool) $this->getRecord()->can_revote;
}
function endsAt(): ?DateTime
{
if(!$this->getRecord()->until)
return NULL;
return new DateTime($this->getRecord()->until);
}
function hasEnded(): bool
{
if($this->getRecord()->ended)
return true;
if(!is_null($this->getRecord()->until))
return time() >= $this->getRecord()->until;
return false;
}
function hasVoted(User $user): bool
{
return !is_null($this->getUserVote($user));
}
function canVote(User $user): bool
{
return !$this->hasEnded() && !$this->hasVoted($user);
}
function vote(User $user, array $optionIds): void
{
if($this->hasEnded())
throw new PollLockedException;
if($this->hasVoted($user))
throw new AlreadyVotedException;
$optionIds = array_map(function($x) { return (int) $x; }, array_unique($optionIds));
$validOpts = array_keys($this->getOptions());
if(empty($optionIds) || (sizeof($optionIds) > 1 && !$this->isMultipleChoice()))
throw new UnexpectedValueException;
if(sizeof(array_diff($optionIds, $validOpts)) > 0)
throw new InvalidOptionException;
foreach($optionIds as $opt) {
DatabaseConnection::i()->getContext()->table("poll_votes")->insert([
"user" => $user->getId(),
"poll" => $this->getId(),
"option" => $opt,
]);
}
}
function revokeVote(User $user): void
{
if(!$this->isRevotable())
throw new PollLockedException;
$this->getRecord()->related("poll_votes.poll")
->where("user", $user->getId())->delete();
}
function setOwner(User $owner): void
{
$this->stateChanges("owner", $owner->getId());
}
function setEndDate(int $timestamp): void
{
if(!is_null($this->getRecord()))
throw new PollLockedException;
$this->stateChanges("until", $timestamp);
}
function setEnded(): void
{
$this->stateChanges("ended", 1);
}
function setOptions(array $options): void
{
if(!is_null($this->getRecord()))
throw new PollLockedException;
if(sizeof($options) > ovkGetQuirk("polls.max-opts"))
throw new TooMuchOptionsException;
$this->choicesToPersist = $options;
}
function setRevotability(bool $canReVote): void
{
if(!is_null($this->getRecord()))
throw new PollLockedException;
$this->stateChanges("can_revote", $canReVote);
}
function setAnonymity(bool $anonymous): void
{
$this->stateChanges("is_anonymous", $anonymous);
}
function setMultipleChoice(bool $mc): void
{
$this->stateChanges("allows_multiple", $mc);
}
function importXML(User $owner, string $xml): void
{
$xml = simplexml_load_string($xml);
$this->setOwner($owner);
$this->setTitle($xml["title"] ?? "Untitled");
$this->setMultipleChoice(($xml["multiple"] ?? "no") == "yes");
$this->setAnonymity(($xml["anonymous"] ?? "no") == "yes");
$this->setRevotability(($xml["locked"] ?? "no") == "no");
if(ctype_digit((string) ($xml["duration"] ?? "")))
$this->setEndDate(time() + ((86400 * (int) $xml["duration"])));
$options = [];
foreach($xml->options->option as $opt)
$options[] = (string) $opt;
if(empty($options))
throw new UnexpectedValueException;
$this->setOptions($options);
}
static function import(User $owner, string $xml): Poll
{
$poll = new Poll;
$poll->importXML($owner, $xml);
$poll->save();
return $poll;
}
function save(): void
{
if(empty($this->choicesToPersist))
throw new InvalidStateException;
parent::save();
foreach($this->choicesToPersist as $option) {
DatabaseConnection::i()->getContext()->table("poll_options")->insert([
"poll" => $this->getId(),
"name" => $option,
]);
}
}
}

View file

@ -1,7 +1,7 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use Chandler\Database\DatabaseConnection as DB;
use openvk\Web\Models\Repositories\Clubs;
use openvk\Web\Models\Repositories\{Clubs, Users};
use openvk\Web\Models\RowModel;
use openvk\Web\Models\Entities\Notifications\LikeNotification;
@ -55,6 +55,15 @@ class Post extends Postable
{
return $this->getRecord()->wall;
}
function getWallOwner()
{
$w = $this->getRecord()->wall;
if($w < 0)
return (new Clubs)->get(abs($w));
return (new Users)->get($w);
}
function getRepostCount(): int
{
@ -87,9 +96,14 @@ class Post extends Postable
function isDeactivationMessage(): bool
{
return ($this->getRecord()->flags & 0b00100000) > 0;
return (($this->getRecord()->flags & 0b00100000) > 0) && ($this->getRecord()->owner > 0);
}
function isUpdateAvatarMessage(): bool
{
return (($this->getRecord()->flags & 0b00010000) > 0) && ($this->getRecord()->owner > 0);
}
function isExplicit(): bool
{
return (bool) $this->getRecord()->nsfw;
@ -104,6 +118,63 @@ class Post extends Postable
{
return $this->getOwner(false)->getId();
}
function getPlatform(bool $forAPI = false): ?string
{
$platform = $this->getRecord()->api_source_name;
if($forAPI) {
switch ($platform) {
case 'openvk_refresh_android':
case 'openvk_legacy_android':
return 'android';
break;
case 'openvk_ios':
case 'openvk_legacy_ios':
return 'iphone';
break;
case 'vika_touch': // кика хохотач ахахахаххахахахахах
case 'vk4me':
return 'mobile';
break;
case NULL:
return NULL;
break;
default:
return 'api';
break;
}
} else {
return $platform;
}
}
function getPlatformDetails(): array
{
$clients = simplexml_load_file(OPENVK_ROOT . "/data/clients.xml");
foreach($clients as $client) {
if($client['tag'] == $this->getPlatform()) {
return [
"tag" => $client['tag'],
"name" => $client['name'],
"url" => $client['url'],
"img" => $client['img']
];
break;
}
}
return [
"tag" => $this->getPlatform(),
"name" => NULL,
"url" => NULL,
"img" => NULL
];
}
function pin(): void
{

View file

@ -0,0 +1,39 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use openvk\Web\Models\Repositories\Users;
use openvk\Web\Models\RowModel;
class SupportAgent extends RowModel
{
protected $tableName = "support_names";
function getAgentId(): int
{
return $this->getRecord()->agent;
}
function getName(): ?string
{
return $this->getRecord()->name;
}
function getCanonicalName(): string
{
return $this->getName();
}
function getAvatarURL(): ?string
{
return $this->getRecord()->icon;
}
function isShowNumber(): int
{
return $this->getRecord()->numerate;
}
function getRealName(): string
{
return (new Users)->get($this->getAgentId())->getCanonicalName();
}
}

View file

@ -42,7 +42,7 @@ class TicketComment extends RowModel
$alias = $this->getSupportAlias();
if(!$alias)
return OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["supportName"] . "" . $this->getAgentNumber();
return tr("helpdesk_agent") . " #" . $this->getAgentNumber();
$name = $alias->getName();
if($alias->shouldAppendNumber())

View file

@ -68,6 +68,12 @@ class Topic extends Postable
return isset($array[0]) ? $array[0] : NULL;
}
function getFirstComment(): ?Comment
{
$array = iterator_to_array($this->getComments(1));
return $array[0] ?? NULL;
}
function getUpdateTime(): DateTime
{
$lastComment = $this->getLastComment();
@ -83,4 +89,40 @@ class Topic extends Postable
$this->unwire();
$this->save();
}
function toVkApiStruct(int $preview = 0, int $preview_length = 90): object
{
$res = (object)[];
$res->id = $this->getId();
$res->title = $this->getTitle();
$res->created = $this->getPublicationTime()->timestamp();
if($this->getOwner() instanceof User) {
$res->created_by = $this->getOwner()->getId();
} else {
$res->created_by = $this->getOwner()->getId() * -1;
}
$res->updated = $this->getUpdateTime()->timestamp();
if($this->getLastComment()) {
if($this->getLastComment()->getOwner() instanceof User) {
$res->updated_by = $this->getLastComment()->getOwner()->getId();
} else {
$res->updated_by = $this->getLastComment()->getOwner()->getId() * -1;
}
}
$res->is_closed = (int)$this->isClosed();
$res->is_fixed = (int)$this->isPinned();
$res->comments = $this->getCommentsCount();
if($preview == 1) {
$res->first_comment = $this->getFirstComment() ? ovk_proc_strtr($this->getFirstComment()->getText(false), $preview_length) : NULL;
$res->last_comment = $this->getLastComment() ? ovk_proc_strtr($this->getLastComment()->getText(false), $preview_length) : NULL;
}
return $res;
}
}

View file

@ -0,0 +1,44 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Traits;
use openvk\Web\Models\Entities\Photo;
use openvk\Web\Models\Repositories\Photos;
trait TBackDrops {
function getBackDropPictureURLs(): ?array
{
$photo1 = $this->getRecord()->backdrop_1;
$photo2 = $this->getRecord()->backdrop_2;
if(is_null($photo1) && is_null($photo2))
return NULL;
$photo1obj = $photo2obj = NULL;
if(!is_null($photo1))
$photo1obj = (new Photos)->get($photo1);
if(!is_null($photo2))
$photo2obj = (new Photos)->get($photo2);
if(is_null($photo1obj) && is_null($photo2obj))
return NULL;
return [
is_null($photo1obj) ? "" : $photo1obj->getURL(),
is_null($photo2obj) ? "" : $photo2obj->getURL(),
];
}
function setBackDropPictures(?Photo $first, ?Photo $second): void
{
if(!is_null($first))
$this->stateChanges("backdrop_1", $first->getId());
if(!is_null($second))
$this->stateChanges("backdrop_2", $second->getId());
}
function unsetBackDropPictures(): void
{
$this->stateChanges("backdrop_1", NULL);
$this->stateChanges("backdrop_2", NULL);
}
}

View file

@ -1,5 +1,6 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Traits;
use openvk\Web\Models\Repositories\{Users, Clubs};
use Wkhooy\ObsceneCensorRus;
trait TRichText
@ -35,9 +36,9 @@ trait TRichText
"%(([A-z]++):\/\/(\S*?\.\S*?))([\s)\[\]{},\"\'<]|\.\s|$)%",
(function (array $matches): string {
$href = str_replace("#", "&num;", $matches[1]);
$href = rawurlencode(str_replace(";", "&#59;", $matches[1]));
$href = rawurlencode(str_replace(";", "&#59;", $href));
$link = str_replace("#", "&num;", $matches[3]);
$link = str_replace(";", "&#59;", $matches[3]);
$link = str_replace(";", "&#59;", $link);
$rel = $this->isAd() ? "sponsored" : "ugc";
return "<a href='/away.php?to=$href' rel='$rel' target='_blank'>$link</a>" . htmlentities($matches[4]);
@ -48,7 +49,63 @@ trait TRichText
private function removeZalgo(string $text): string
{
return preg_replace("%[\x{0300}-\x{036F}]{3,}%Xu", "<EFBFBD>", $text);
return preg_replace("%\p{M}{3,}%Xu", "", $text);
}
function resolveMentions(array $skipUsers = []): \Traversable
{
$contentColumn = property_exists($this, "overrideContentColumn") ? $this->overrideContentColumn : "content";
$text = $this->getRecord()->{$contentColumn};
$text = preg_replace("%@([A-Za-z0-9]++) \(((?:[\p{L&}\p{Lo} 0-9]\p{Mn}?)++)\)%Xu", "[$1|$2]", $text);
$text = preg_replace("%([\n\r\s]|^)(@([A-Za-z0-9]++))%Xu", "$1[$3|@$3]", $text);
$resolvedUsers = $skipUsers;
$resolvedClubs = [];
preg_match_all("%\[([A-Za-z0-9]++)\|((?:[\p{L&}\p{Lo} 0-9@]\p{Mn}?)++)\]%Xu", $text, $links, PREG_PATTERN_ORDER);
foreach($links[1] as $link) {
if(preg_match("%^id([0-9]++)$%", $link, $match)) {
$uid = (int) $match[1];
if(in_array($uid, $resolvedUsers))
continue;
$resolvedUsers[] = $uid;
$maybeUser = (new Users)->get($uid);
if($maybeUser)
yield $maybeUser;
} else if(preg_match("%^(?:club|public|event)([0-9]++)$%", $link, $match)) {
$cid = (int) $match[1];
if(in_array($cid, $resolvedClubs))
continue;
$resolvedClubs[] = $cid;
$maybeClub = (new Clubs)->get($cid);
if($maybeClub)
yield $maybeClub;
} else {
$maybeUser = (new Users)->getByShortURL($link);
if($maybeUser) {
$uid = $maybeUser->getId();
if(in_array($uid, $resolvedUsers))
continue;
else
$resolvedUsers[] = $uid;
yield $maybeUser;
continue;
}
$maybeClub = (new Clubs)->getByShortURL($link);
if($maybeClub) {
$cid = $maybeClub->getId();
if(in_array($cid, $resolvedClubs))
continue;
else
$resolvedClubs[] = $cid;
yield $maybeClub;
}
}
}
}
function getText(bool $html = true): string
@ -59,7 +116,6 @@ trait TRichText
$proc = iconv_strlen($this->getRecord()->{$contentColumn}) <= OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["processingLimit"];
if($html) {
if($proc) {
$rel = $this->isAd() ? "sponsored" : "ugc";
$text = $this->formatLinks($text);
$text = preg_replace("%@([A-Za-z0-9]++) \(((?:[\p{L&}\p{Lo} 0-9]\p{Mn}?)++)\)%Xu", "[$1|$2]", $text);
$text = preg_replace("%([\n\r\s]|^)(@([A-Za-z0-9]++))%Xu", "$1[$3|@$3]", $text);

View file

@ -5,7 +5,7 @@ use openvk\Web\Themes\{Themepack, Themepacks};
use openvk\Web\Util\DateTime;
use openvk\Web\Models\RowModel;
use openvk\Web\Models\Entities\{Photo, Message, Correspondence, Gift};
use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Gifts, Notifications};
use openvk\Web\Models\Repositories\{Photos, Users, Clubs, Albums, Gifts, Notifications};
use openvk\Web\Models\Exceptions\InvalidUserNameException;
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection;
@ -148,8 +148,9 @@ class User extends RowModel
function getFirstName(bool $pristine = false): string
{
$name = ($this->isDeleted() && !$this->isDeactivated() ? "DELETED" : mb_convert_case($this->getRecord()->first_name, MB_CASE_TITLE));
if((($ts = tr("__transNames")) !== "@__transNames") && !$pristine)
return mb_convert_case(transliterator_transliterate($ts, $name), MB_CASE_TITLE);
$tsn = tr("__transNames");
if(( $tsn !== "@__transNames" && !empty($tsn) ) && !$pristine)
return mb_convert_case(transliterator_transliterate($tsn, $name), MB_CASE_TITLE);
else
return $name;
}
@ -157,8 +158,9 @@ class User extends RowModel
function getLastName(bool $pristine = false): string
{
$name = ($this->isDeleted() && !$this->isDeactivated() ? "DELETED" : mb_convert_case($this->getRecord()->last_name, MB_CASE_TITLE));
if((($ts = tr("__transNames")) !== "@__transNames") && !$pristine)
return mb_convert_case(transliterator_transliterate($ts, $name), MB_CASE_TITLE);
$tsn = tr("__transNames");
if(( $tsn !== "@__transNames" && !empty($tsn) ) && !$pristine)
return mb_convert_case(transliterator_transliterate($tsn, $name), MB_CASE_TITLE);
else
return $name;
}
@ -535,12 +537,15 @@ class User extends RowModel
return sizeof(DatabaseConnection::i()->getContext()->table("messages")->where(["recipient_id" => $this->getId(), "unread" => 1]));
}
function getClubs(int $page = 1, bool $admin = false): \Traversable
function getClubs(int $page = 1, bool $admin = false, int $count = OPENVK_DEFAULT_PER_PAGE, bool $offset = false): \Traversable
{
if(!$offset)
$page = ($page - 1) * $count;
if($admin) {
$id = $this->getId();
$query = "SELECT `id` FROM `groups` WHERE `owner` = ? UNION SELECT `club` as `id` FROM `group_coadmins` WHERE `user` = ?";
$query .= " LIMIT " . OPENVK_DEFAULT_PER_PAGE . " OFFSET " . ($page - 1) * OPENVK_DEFAULT_PER_PAGE;
$query .= " LIMIT " . $count . " OFFSET " . $page;
$sel = DatabaseConnection::i()->getConnection()->query($query, $id, $id);
foreach($sel as $target) {
@ -550,7 +555,7 @@ class User extends RowModel
yield $target;
}
} else {
$sel = $this->getRecord()->related("subscriptions.follower")->page($page, OPENVK_DEFAULT_PER_PAGE);
$sel = $this->getRecord()->related("subscriptions.follower")->limit($count, $page);
foreach($sel->where("model", "openvk\\Web\\Models\\Entities\\Club") as $target) {
$target = (new Clubs)->get($target->target);
if(!$target) continue;
@ -746,6 +751,63 @@ class User extends RowModel
return time() - $this->getRecord()->online <= 300;
}
function getOnlinePlatform(bool $forAPI = false): ?string
{
$platform = $this->getRecord()->client_name;
if($forAPI) {
switch ($platform) {
case 'openvk_refresh_android':
case 'openvk_legacy_android':
return 'android';
break;
case 'openvk_ios':
case 'openvk_legacy_ios':
return 'iphone';
break;
case 'vika_touch': // кика хохотач ахахахаххахахахахах
case 'vk4me':
return 'mobile';
break;
case NULL:
return NULL;
break;
default:
return 'api';
break;
}
} else {
return $platform;
}
}
function getOnlinePlatformDetails(): array
{
$clients = simplexml_load_file(OPENVK_ROOT . "/data/clients.xml");
foreach($clients as $client) {
if($client['tag'] == $this->getOnlinePlatform()) {
return [
"tag" => $client['tag'],
"name" => $client['name'],
"url" => $client['url'],
"img" => $client['img']
];
break;
}
}
return [
"tag" => $this->getOnlinePlatform(),
"name" => NULL,
"url" => NULL,
"img" => NULL
];
}
function prefersNotToSeeRating(): bool
{
return !((bool) $this->getRecord()->show_rating);
@ -908,6 +970,10 @@ class User extends RowModel
$pClub = DatabaseConnection::i()->getContext()->table("groups")->where("shortcode", $code)->fetch();
if(!is_null($pClub))
return false;
$pAlias = DatabaseConnection::i()->getContext()->table("aliases")->where("shortcode", $code)->fetch();
if(!is_null($pAlias))
return false;
}
$this->stateChanges("shortcode", $code);
@ -943,6 +1009,15 @@ class User extends RowModel
return true;
}
function updOnline(string $platform): bool
{
$this->setOnline(time());
$this->setClient_name($platform);
$this->save();
return true;
}
function changeEmail(string $email): void
{
DatabaseConnection::i()->getContext()->table("ChandlerUsers")
@ -1049,6 +1124,24 @@ class User extends RowModel
return true;
}
function toVkApiStruct(): object
{
$res = (object) [];
$res->id = $this->getId();
$res->first_name = $this->getFirstName();
$res->last_name = $this->getLastName();
$res->deactivated = $this->isDeactivated();
$res->photo_50 = $this->getAvatarURL();
$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
return $res;
}
use Traits\TSubscribable;
use Traits\TBackDrops;
use Traits\TSubscribable;
}

View file

@ -13,7 +13,7 @@ class Video extends Media
const TYPE_EMBED = 1;
protected $tableName = "videos";
protected $fileExtension = "ogv";
protected $fileExtension = "mp4";
protected $processingPlaceholder = "video/rendering";
@ -30,7 +30,7 @@ class Video extends Media
throw new \DomainException("$filename does not contain any video streams");
$durations = [];
preg_match('%duration=([0-9\.]++)%', $streams, $durations);
preg_match_all('%duration=([0-9\.]++)%', $streams, $durations);
if(sizeof($durations[1]) === 0)
throw new \DomainException("$filename does not contain any meaningful video streams");
@ -104,7 +104,7 @@ class Video extends Media
if(!$this->isProcessed())
return "/assets/packages/static/openvk/video/rendering.apng";
return preg_replace("%\.[A-z]++$%", ".gif", $this->getURL());
return preg_replace("%\.[A-z0-9]++$%", ".gif", $this->getURL());
} else {
return $this->getVideoDriver()->getThumbnailURL();
}
@ -114,7 +114,64 @@ class Video extends Media
{
return $this->getRecord()->owner;
}
function getApiStructure(): object
{
$fromYoutube = $this->getType() == Video::TYPE_EMBED;
return (object)[
"type" => "video",
"video" => [
"can_comment" => 1,
"can_like" => 0, // we don't h-have wikes in videos
"can_repost" => 0,
"can_subscribe" => 1,
"can_add_to_faves" => 0,
"can_add" => 0,
"comments" => $this->getCommentsCount(),
"date" => $this->getPublicationTime()->timestamp(),
"description" => $this->getDescription(),
"duration" => 0, // я хуй знает как получить длину видео
"image" => [
[
"url" => $this->getThumbnailURL(),
"width" => 320,
"height" => 240,
"with_padding" => 1
]
],
"width" => 640,
"height" => 480,
"id" => $this->getVirtualId(),
"owner_id" => $this->getOwner()->getId(),
"user_id" => $this->getOwner()->getId(),
"title" => $this->getName(),
"is_favorite" => false,
"player" => !$fromYoutube ? $this->getURL() : $this->getVideoDriver()->getURL(),
"files" => !$fromYoutube ? [
"mp4_480" => $this->getURL()
] : NULL,
"platform" => $fromYoutube ? "youtube" : NULL,
"added" => 0,
"repeat" => 0,
"type" => "video",
"views" => 0,
"likes" => [
"count" => 0,
"user_likes" => 0
],
"reposts" => [
"count" => 0,
"user_reposted" => 0
]
]
];
}
function toVkApiStruct(): object
{
return $this->getApiStructure();
}
function setLink(string $link): string
{
if(preg_match(file_get_contents(__DIR__ . "/../VideoDrivers/regex/youtube.txt"), $link, $matches)) {
@ -145,11 +202,14 @@ class Video extends Media
$this->save();
}
static function fastMake(int $owner, string $description = "", array $file, bool $unlisted = true, bool $anon = false): Video
static function fastMake(int $owner, string $name = "Unnamed Video.ogv", string $description = "", array $file, bool $unlisted = true, bool $anon = false): Video
{
if(OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading'])
exit(VIDEOS_FRIENDLY_ERROR);
$video = new Video;
$video->setOwner($owner);
$video->setName("Unnamed Video.ogv");
$video->setName(ovk_proc_strtr($name, 61));
$video->setDescription(ovk_proc_strtr($description, 300));
$video->setAnonymous($anon);
$video->setCreated(time());

View file

@ -0,0 +1,7 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Exceptions;
final class AlreadyVotedException extends \RuntimeException
{
}

View file

@ -0,0 +1,7 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Exceptions;
final class InvalidOptionException extends \UnexpectedValueException
{
}

View file

@ -0,0 +1,8 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Exceptions;
use Nette\InvalidStateException;
final class PollLockedException extends InvalidStateException
{
}

View file

@ -0,0 +1,7 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Exceptions;
final class TooMuchOptionsException extends \UnexpectedValueException
{
}

View file

@ -123,4 +123,14 @@ class Albums
return $dbalbum->collection ? $this->get($dbalbum->collection) : null;
}
function getAlbumByOwnerAndId(int $owner, int $id)
{
$album = $this->albums->where([
"owner" => $owner,
"id" => $id
])->fetch();
return new Album($album);
}
}

View file

@ -0,0 +1,35 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use openvk\Web\Models\Entities\Alias;
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection as DB;
use openvk\Web\Models\Entities\{Club, User};
use openvk\Web\Models\Repositories\{Clubs, Users};
class Aliases
{
private $context;
private $aliases;
function __construct()
{
$this->context = DB::i()->getContext();
$this->aliases = $this->context->table("aliases");
}
private function toAlias(?ActiveRow $ar): ?Alias
{
return is_null($ar) ? NULL : new Alias($ar);
}
function get(int $id): ?Alias
{
return $this->toAlias($this->aliases->get($id));
}
function getByShortcode(string $shortcode): ?Alias
{
return $this->toAlias($this->aliases->where("shortcode", $shortcode)->fetch());
}
}

View file

@ -66,4 +66,12 @@ class Applications
{
return sizeof($this->appRels->where("user", $user->getId()));
}
function find(string $query, array $pars = [], string $sort = "id"): Util\EntityStream
{
$query = "%$query%";
$result = $this->apps->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("enabled", 1);
return new Util\EntityStream("Application", $result->order("$sort"));
}
}

View file

@ -0,0 +1,48 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection as DB;
use openvk\Web\Models\Entities\User;
use Chandler\Security\User as ChandlerUser;
class ChandlerGroups
{
private $context;
private $groups;
public function __construct()
{
$this->context = DB::i()->getContext();
$this->groups = $this->context->table("ChandlerGroups");
$this->members = $this->context->table("ChandlerACLRelations");
$this->perms = $this->context->table("ChandlerACLGroupsPermissions");
}
function get(string $UUID): ?ActiveRow
{
return $this->groups->where("id", $UUID)->fetch();
}
function getList(): \Traversable
{
foreach($this->groups as $group) yield $group;
}
function getMembersById(string $UUID): \Traversable
{
foreach($this->members->where("group", $UUID) as $member)
yield (new Users)->getByChandlerUser(
new ChandlerUser($this->context->table("ChandlerUsers")->where("id", $member->user)->fetch())
);
}
function getUsersMemberships(string $UUID): \Traversable
{
foreach($this->members->where("user", $UUID) as $member) yield $member;
}
function getPermissionsById(string $UUID): \Traversable
{
foreach($this->perms->where("group", $UUID) as $perm) yield $perm;
}
}

View file

@ -0,0 +1,39 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection as DB;
use openvk\Web\Models\Entities\User;
use Chandler\Security\User as ChandlerUser;
class ChandlerUsers
{
private $context;
private $users;
public function __construct()
{
$this->context = DB::i()->getContext();
$this->users = $this->context->table("ChandlerUsers");
}
private function toUser(?ActiveRow $ar): ?ChandlerUser
{
return is_null($ar) ? NULL : (new User($ar))->getChandlerUser();
}
function get(int $id): ?ChandlerUser
{
return (new Users)->get($id)->getChandlerUser();
}
function getById(string $UUID): ?ChandlerUser
{
return new ChandlerUser($this->users->where("id", $UUID)->fetch());
}
function getList(int $page = 1): \Traversable
{
foreach($this->users as $user)
yield new ChandlerUser($user);
}
}

View file

@ -1,6 +1,7 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use openvk\Web\Models\Entities\Club;
use openvk\Web\Models\Entities\{Club, Manager};
use openvk\Web\Models\Repositories\{Aliases, Users};
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection;
@ -8,11 +9,13 @@ class Clubs
{
private $context;
private $clubs;
private $coadmins;
function __construct()
{
$this->context = DatabaseConnection::i()->getContext();
$this->clubs = $this->context->table("groups");
$this->context = DatabaseConnection::i()->getContext();
$this->clubs = $this->context->table("groups");
$this->coadmins = $this->context->table("group_coadmins");
}
private function toClub(?ActiveRow $ar): ?Club
@ -22,7 +25,17 @@ class Clubs
function getByShortURL(string $url): ?Club
{
return $this->toClub($this->clubs->where("shortcode", $url)->fetch());
$shortcode = $this->toClub($this->clubs->where("shortcode", $url)->fetch());
if ($shortcode)
return $shortcode;
$alias = (new Aliases)->getByShortcode($url);
if (!$alias) return NULL;
if ($alias->getType() !== "club") return NULL;
return $alias->getClub();
}
function get(int $id): ?Club
@ -30,12 +43,12 @@ class Clubs
return $this->toClub($this->clubs->get($id));
}
function find(string $query, int $page = 1, ?int $perPage = NULL): \Traversable
function find(string $query, array $pars = [], string $sort = "id DESC", 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);
return new Util\EntityStream("Club", $result->order($sort));
}
function getCount(): int
@ -45,6 +58,9 @@ class Clubs
function getPopularClubs(): \Traversable
{
// TODO rewrite
/*
$query = "SELECT ROW_NUMBER() OVER (ORDER BY `subscriptions` DESC) as `place`, `target` as `id`, COUNT(`follower`) as `subscriptions` FROM `subscriptions` WHERE `model` = \"openvk\\\Web\\\Models\\\Entities\\\Club\" GROUP BY `target` ORDER BY `subscriptions` DESC, `id` LIMIT 30;";
$entries = DatabaseConnection::i()->getConnection()->query($query);
@ -54,7 +70,28 @@ class Clubs
"club" => $this->get($entry["id"]),
"subscriptions" => $entry["subscriptions"],
];
*/
}
function getWriteableClubs(int $id): \Traversable
{
$result = $this->clubs->where("owner", $id);
$coadmins = $this->coadmins->where("user", $id);
foreach($result as $entry) {
yield new Club($entry);
}
foreach($coadmins as $coadmin) {
$cl = new Manager($coadmin);
yield $cl->getClub();
}
}
function getWriteableClubsCount(int $id): int
{
return sizeof($this->clubs->where("owner", $id)) + sizeof($this->coadmins->where("user", $id));
}
use \Nette\SmartObject;
}

View file

@ -59,4 +59,35 @@ class Comments
"deleted" => false,
]));
}
function find(string $query = "", array $pars = [], string $sort = "id"): Util\EntityStream
{
$query = "%$query%";
$notNullParams = [];
foreach($pars as $paramName => $paramValue)
if($paramName != "before" && $paramName != "after")
$paramValue != NULL ? $notNullParams+=["$paramName" => "%$paramValue%"] : NULL;
else
$paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL;
$result = $this->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;
}
}
}
return new Util\EntityStream("Comment", $result->order("$sort"));
}
}

View file

@ -26,10 +26,10 @@ class Notes
return $this->toNote($this->notes->get($id));
}
function getUserNotes(User $user, int $page = 1, ?int $perPage = NULL): \Traversable
function getUserNotes(User $user, int $page = 1, ?int $perPage = NULL, string $sort = "DESC"): \Traversable
{
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE;
foreach($this->notes->where("owner", $user->getId())->where("deleted", 0)->order("created DESC")->page($page, $perPage) as $album)
foreach($this->notes->where("owner", $user->getId())->where("deleted", 0)->order("created $sort")->page($page, $perPage) as $album)
yield new Note($album);
}

View file

@ -1,6 +1,6 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use openvk\Web\Models\Entities\Photo;
use openvk\Web\Models\Entities\{Photo, User};
use Chandler\Database\DatabaseConnection;
class Photos
@ -32,4 +32,15 @@ class Photos
return new Photo($photo);
}
function getEveryUserPhoto(User $user): \Traversable
{
$photos = $this->photos->where([
"owner" => $user->getId()
]);
foreach($photos as $photo) {
yield new Photo($photo);
}
}
}

View file

@ -0,0 +1,23 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Entities\Poll;
class Polls
{
private $polls;
function __construct()
{
$this->polls = DatabaseConnection::i()->getContext()->table("polls");
}
function get(int $id): ?Poll
{
$poll = $this->polls->get($id);
if(!$poll)
return NULL;
return new Poll($poll);
}
}

View file

@ -99,7 +99,39 @@ class Posts
return NULL;
}
function find(string $query = "", array $pars = [], string $sort = "id"): Util\EntityStream
{
$query = "%$query%";
$notNullParams = [];
foreach($pars as $paramName => $paramValue)
if($paramName != "before" && $paramName != "after")
$paramValue != NULL ? $notNullParams+=["$paramName" => "%$paramValue%"] : NULL;
else
$paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL;
$result = $this->posts->where("content LIKE ?", $query)->where("deleted", 0);
$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;
}
}
}
return new Util\EntityStream("Post", $result->order("$sort"));
}
function getPostCountOnUserWall(int $user): int
{
return sizeof($this->posts->where(["wall" => $user, "deleted" => 0]));

View file

@ -0,0 +1,32 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Entities\{User, SupportAgent};
class SupportAgents
{
private $context;
private $tickets;
function __construct()
{
$this->context = DatabaseConnection::i()->getContext();
$this->agents = $this->context->table("support_names");
}
private function toAgent(?ActiveRow $ar)
{
return is_null($ar) ? NULL : new SupportAgent($ar);
}
function get(int $id): ?SupportAgent
{
return $this->toAgent($this->agents->where("agent", $id)->fetch());
}
function isExists(int $id): bool
{
return !is_null($this->get($id));
}
}

View file

@ -27,6 +27,13 @@ class TicketComments
else
return NULL;
}
function getCountByAgent(int $agent_id, int $mark = NULL): int
{
$filter = ['user_id' => $agent_id, 'user_type' => 1];
$mark && $filter['mark'] = $mark;
return sizeof($this->comments->where($filter));
}
use \Nette\SmartObject;
}

View file

@ -1,6 +1,7 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\Aliases;
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection;
use Chandler\Security\User as ChandlerUser;
@ -9,11 +10,13 @@ class Users
{
private $context;
private $users;
private $aliases;
function __construct()
{
$this->context = DatabaseConnection::i()->getContext();
$this->users = $this->context->table("profiles");
$this->aliases = $this->context->table("aliases");
}
private function toUser(?ActiveRow $ar): ?User
@ -28,7 +31,17 @@ class Users
function getByShortURL(string $url): ?User
{
return $this->toUser($this->users->where("shortcode", $url)->fetch());
$shortcode = $this->toUser($this->users->where("shortcode", $url)->fetch());
if ($shortcode)
return $shortcode;
$alias = (new Aliases)->getByShortcode($url);
if (!$alias) return NULL;
if ($alias->getType() !== "user") return NULL;
return $alias->getUser();
}
function getByChandlerUser(ChandlerUser $user): ?User
@ -36,12 +49,91 @@ class Users
return $this->toUser($this->users->where("user", $user->getId())->fetch());
}
function find(string $query): Util\EntityStream
function find(string $query, array $pars = [], string $sort = "id DESC"): Util\EntityStream
{
$query = "%$query%";
$result = $this->users->where("CONCAT_WS(' ', first_name, last_name, pseudo, shortcode) LIKE ?", $query)->where("deleted", 0);
return new Util\EntityStream("User", $result);
$notNullParams = [];
$nnparamsCount = 0;
foreach($pars as $paramName => $paramValue)
if($paramName != "before" && $paramName != "after" && $paramName != "gender" && $paramName != "maritalstatus" && $paramName != "politViews" && $paramName != "doNotSearchMe")
$paramValue != NULL ? $notNullParams += ["$paramName" => "%$paramValue%"] : NULL;
else
$paramValue != NULL ? $notNullParams += ["$paramName" => "$paramValue"] : NULL;
$nnparamsCount = sizeof($notNullParams);
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;
}
}
}
return new Util\EntityStream("User", $result->order($sort));
}
function getStatistics(): object

View file

@ -45,4 +45,36 @@ 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
{
$query = "%$query%";
$notNullParams = [];
foreach($pars as $paramName => $paramValue)
if($paramName != "before" && $paramName != "after")
$paramValue != NULL ? $notNullParams+=["$paramName" => "%$paramValue%"] : NULL;
else
$paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL;
$result = $this->videos->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("deleted", 0);
$nnparamsCount = sizeof($notNullParams);
if($nnparamsCount > 0) {
foreach($notNullParams as $paramName => $paramValue) {
switch($paramName) {
case "before":
$result->where("created < ?", $paramValue);
break;
case "after":
$result->where("created > ?", $paramValue);
break;
}
}
}
return new Util\EntityStream("Video", $result->order("$sort"));
}
}

View file

@ -14,5 +14,5 @@ abstract class VideoDriver
abstract function getURL(): string;
abstract function getEmbed(): string;
abstract function getEmbed(string $w = "600", string $h = "340"): string;
}

View file

@ -13,13 +13,13 @@ final class YouTubeVideoDriver extends VideoDriver
return "https://youtu.be/$this->id";
}
function getEmbed(): string
function getEmbed(string $w = "600", string $h = "340"): string
{
return <<<CODE
<iframe
width="600"
height="340"
src="https://www.youtube.com/embed/$this->id"
width="$w"
height="$h"
src="https://www.youtube-nocookie.com/embed/$this->id"
frameborder="0"
sandbox="allow-same-origin allow-scripts allow-popups"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"

View file

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

View file

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

View file

@ -10,6 +10,8 @@ SELECT DISTINCT id, class FROM
sender_id = ?
AND
sender_type = ?
AND
deleted = 0
) UNION (
SELECT
sender_id AS id,
@ -20,6 +22,8 @@ SELECT DISTINCT id, class FROM
recipient_id = ?
AND
recipient_type = ?
AND
deleted = 0
)
ORDER BY
time

View file

@ -37,6 +37,9 @@ final class AboutPresenter extends OpenVKPresenter
function renderBB(): void
{}
function renderTour(): void
{}
function renderInvite(): void
{
@ -64,7 +67,7 @@ final class AboutPresenter extends OpenVKPresenter
$this->template->usersStats = (new Users)->getStatistics();
$this->template->clubsCount = (new Clubs)->getCount();
$this->template->postsCount = (new Posts)->getCount();
$this->template->popularClubs = iterator_to_array((new Clubs)->getPopularClubs());
$this->template->popularClubs = [];
$this->template->admins = iterator_to_array((new Users)->getInstanceAdmins());
}
@ -76,6 +79,9 @@ final class AboutPresenter extends OpenVKPresenter
$this->assertNoCSRF();
setLanguage($_GET['lg']);
}
if(!is_null($_GET['jReturnTo']))
$this->redirect(rawurldecode($_GET['jReturnTo']));
}
function renderExportJSLanguage($lg = NULL): void
@ -135,6 +141,6 @@ final class AboutPresenter extends OpenVKPresenter
function renderDev(): void
{
$this->redirect("https://docs.openvk.su/");
$this->redirect("https://docs.openvk.uk/");
}
}

View file

@ -1,7 +1,7 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Voucher, Gift, GiftCategory, User, BannedLink};
use openvk\Web\Models\Repositories\{Users, Clubs, Vouchers, Gifts, BannedLinks};
use openvk\Web\Models\Repositories\{ChandlerGroups, ChandlerUsers, Users, Clubs, Vouchers, Gifts, BannedLinks};
use Chandler\Database\DatabaseConnection;
final class AdminPresenter extends OpenVKPresenter
@ -11,14 +11,16 @@ final class AdminPresenter extends OpenVKPresenter
private $vouchers;
private $gifts;
private $bannedLinks;
function __construct(Users $users, Clubs $clubs, Vouchers $vouchers, Gifts $gifts, BannedLinks $bannedLinks)
private $chandlerGroups;
function __construct(Users $users, Clubs $clubs, Vouchers $vouchers, Gifts $gifts, BannedLinks $bannedLinks, ChandlerGroups $chandlerGroups)
{
$this->users = $users;
$this->clubs = $clubs;
$this->vouchers = $vouchers;
$this->gifts = $gifts;
$this->bannedLinks = $bannedLinks;
$this->chandlerGroups = $chandlerGroups;
parent::__construct();
}
@ -62,7 +64,9 @@ final class AdminPresenter extends OpenVKPresenter
$this->notFound();
$this->template->user = $user;
$this->template->c_groups_list = (new ChandlerGroups)->getList();
$this->template->c_memberships = $this->chandlerGroups->getUsersMemberships($user->getChandlerGUID());
if($_SERVER["REQUEST_METHOD"] !== "POST")
return;
@ -78,8 +82,16 @@ final class AdminPresenter extends OpenVKPresenter
$user->changeEmail($this->postParam("email"));
if($user->onlineStatus() != $this->postParam("online")) $user->setOnline(intval($this->postParam("online")));
$user->setVerified(empty($this->postParam("verify") ? 0 : 1));
if($this->postParam("add-to-group")) {
$query = "INSERT INTO `ChandlerACLRelations` (`user`, `group`) VALUES ('" . $user->getChandlerGUID() . "', '" . $this->postParam("add-to-group") . "')";
DatabaseConnection::i()->getConnection()->query($query);
}
if($this->postParam("password")) {
$user->getChandlerUser()->updatePassword($this->postParam("password"));
}
$user->save();
break;
}
}
@ -447,4 +459,95 @@ final class AdminPresenter extends OpenVKPresenter
$this->redirect("/admin/bannedLinks");
}
function renderChandlerGroups(): void
{
$this->template->groups = (new ChandlerGroups)->getList();
if($_SERVER["REQUEST_METHOD"] !== "POST")
return;
$req = "INSERT INTO `ChandlerGroups` (`name`) VALUES ('" . $this->postParam("name") . "')";
DatabaseConnection::i()->getConnection()->query($req);
}
function renderChandlerGroup(string $UUID): void
{
$DB = DatabaseConnection::i()->getConnection();
if(is_null($DB->query("SELECT * FROM `ChandlerGroups` WHERE `id` = '$UUID'")->fetch()))
$this->flashFail("err", tr("error"), tr("c_group_not_found"));
$this->template->group = (new ChandlerGroups)->get($UUID);
$this->template->mode = in_array(
$this->queryParam("act"),
[
"main",
"members",
"permissions",
"removeMember",
"removePermission",
"delete"
]) ? $this->queryParam("act") : "main";
$this->template->members = (new ChandlerGroups)->getMembersById($UUID);
$this->template->perms = (new ChandlerGroups)->getPermissionsById($UUID);
if($this->template->mode == "removeMember") {
$where = "`user` = '" . $this->queryParam("uid") . "' AND `group` = '$UUID'";
if(is_null($DB->query("SELECT * FROM `ChandlerACLRelations` WHERE " . $where)->fetch()))
$this->flashFail("err", tr("error"), tr("c_user_is_not_in_group"));
$DB->query("DELETE FROM `ChandlerACLRelations` WHERE " . $where);
$this->flashFail("succ", tr("changes_saved"), tr("c_user_removed_from_group"));
} elseif($this->template->mode == "removePermission") {
$where = "`model` = '" . trim(addslashes($this->queryParam("model"))) . "' AND `permission` = '". $this->queryParam("perm") ."' AND `group` = '$UUID'";
if(is_null($DB->query("SELECT * FROM `ChandlerACLGroupsPermissions WHERE $where`")))
$this->flashFail("err", tr("error"), tr("c_permission_not_found"));
$DB->query("DELETE FROM `ChandlerACLGroupsPermissions` WHERE $where");
$this->flashFail("succ", tr("changes_saved"), tr("c_permission_removed_from_group"));
} elseif($this->template->mode == "delete") {
$DB->query("DELETE FROM `ChandlerGroups` WHERE `id` = '$UUID'");
$DB->query("DELETE FROM `ChandlerACLGroupsPermissions` WHERE `group` = '$UUID'");
$DB->query("DELETE FROM `ChandlerACLRelations` WHERE `group` = '$UUID'");
$this->flashFail("succ", tr("changes_saved"), tr("c_group_removed"));
}
if ($_SERVER["REQUEST_METHOD"] !== "POST") return;
$req = "";
if($this->template->mode == "main")
if($this->postParam("delete"))
$req = "DELETE FROM `ChandlerGroups` WHERE `id`='$UUID'";
else
$req = "UPDATE `ChandlerGroups` SET `name`='". $this->postParam('name') ."' , `color`='". $this->postParam("color") ."' WHERE `id`='$UUID'";
if($this->template->mode == "members")
if($this->postParam("uid"))
if(!is_null($DB->query("SELECT * FROM `ChandlerACLRelations` WHERE `user` = '" . $this->postParam("uid") . "'")))
$this->flashFail("err", tr("error"), tr("c_user_is_already_in_group"));
$req = "INSERT INTO `ChandlerACLRelations` (`user`, `group`, `priority`) VALUES ('". $this->postParam("uid") ."', '$UUID', 32)";
if($this->template->mode == "permissions")
$req = "INSERT INTO `ChandlerACLGroupsPermissions` (`group`, `model`, `permission`, `context`) VALUES ('$UUID', '". trim(addslashes($this->postParam("model"))) ."', '". $this->postParam("permission") ."', 0)";
$DB->query($req);
$this->flashFail("succ", tr("changes_saved"));
}
function renderChandlerUser(string $UUID): void
{
if(!$UUID) $this->notFound();
$c_user = (new ChandlerUsers())->getById($UUID);
$user = $this->users->getByChandlerUser($c_user);
if(!$user) $this->notFound();
$this->redirect("/admin/users/id" . $user->getId());
}
}

View file

@ -6,7 +6,7 @@ use openvk\Web\Models\Repositories\Applications;
final class AppsPresenter extends OpenVKPresenter
{
private $apps;
protected $presenterName = "apps";
function __construct(Applications $apps)
{
$this->apps = $apps;

View file

@ -80,10 +80,17 @@ final class AuthPresenter extends OpenVKPresenter
if(!Validator::i()->emailValid($this->postParam("email")))
$this->flashFail("err", tr("invalid_email_address"), tr("invalid_email_address_comment"));
if(OPENVK_ROOT_CONF['openvk']['preferences']['security']['forceStrongPassword'])
if(!Validator::i()->passwordStrong($this->postParam("password")))
$this->flashFail("err", tr("error"), tr("error_weak_password"));
if (strtotime($this->postParam("birthday")) > time())
$this->flashFail("err", tr("invalid_birth_date"), tr("invalid_birth_date_comment"));
if (!$this->postParam("confirmation"))
$this->flashFail("err", tr("error"), tr("checkbox_in_registration_unchecked"));
try {
$user = new User;
$user->setFirst_Name($this->postParam("first_name"));
@ -200,6 +207,9 @@ final class AuthPresenter extends OpenVKPresenter
function renderFinishRestoringPassword(): void
{
if(OPENVK_ROOT_CONF['openvk']['preferences']['security']['disablePasswordRestoring'])
$this->notFound();
$request = $this->restores->getByToken(str_replace(" ", "+", $this->queryParam("key")));
if(!$request || !$request->isStillValid()) {
$this->flash("err", tr("token_manipulation_error"), tr("token_manipulation_error_comment"));
@ -234,6 +244,9 @@ final class AuthPresenter extends OpenVKPresenter
function renderRestore(): void
{
if(OPENVK_ROOT_CONF['openvk']['preferences']['security']['disablePasswordRestoring'])
$this->notFound();
if(!is_null($this->user))
$this->redirect($this->user->identity->getURL());

View file

@ -1,11 +1,12 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Comment, Photo, Video, User, Topic, Post};
use openvk\Web\Models\Entities\{Comment, Notifications\MentionNotification, Photo, Video, User, Topic, Post};
use openvk\Web\Models\Entities\Notifications\CommentNotification;
use openvk\Web\Models\Repositories\{Comments, Clubs};
final class CommentPresenter extends OpenVKPresenter
{
protected $presenterName = "comment";
private $models = [
"posts" => "openvk\\Web\\Models\\Repositories\\Posts",
"photos" => "openvk\\Web\\Models\\Repositories\\Photos",
@ -46,6 +47,9 @@ final class CommentPresenter extends OpenVKPresenter
$club = (new Clubs)->get(abs($entity->getTargetWall()));
else if($entity instanceof Topic)
$club = $entity->getClub();
if($_FILES["_vid_attachment"] && OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading'])
$this->flashFail("err", tr("error"), "Video uploads are disabled by the system administrator.");
$flags = 0;
if($this->postParam("as_group") === "on" && !is_null($club) && $club->canBeModifiedBy($this->user->identity))
@ -73,7 +77,7 @@ final class CommentPresenter extends OpenVKPresenter
}
if($_FILES["_vid_attachment"]["error"] === UPLOAD_ERR_OK) {
$video = Video::fastMake($this->user->id, $this->postParam("text"), $_FILES["_vid_attachment"]);
$video = Video::fastMake($this->user->id, $_FILES["_vid_attachment"]["name"], $this->postParam("text"), $_FILES["_vid_attachment"]);
}
} catch(ISE $ex) {
$this->flashFail("err", "Не удалось опубликовать комментарий", "Файл медиаконтента повреждён или слишком велик.");
@ -104,6 +108,15 @@ final class CommentPresenter extends OpenVKPresenter
if($entity->getOwner()->getId() !== $this->user->identity->getId())
if(($owner = $entity->getOwner()) instanceof User)
(new CommentNotification($owner, $comment, $entity, $this->user->identity))->emit();
$excludeMentions = [$this->user->identity->getId()];
if(($owner = $entity->getOwner()) instanceof User)
$excludeMentions[] = $owner->getId();
$mentions = iterator_to_array($comment->resolveMentions($excludeMentions));
foreach($mentions as $mentionee)
if($mentionee instanceof User)
(new MentionNotification($mentionee, $entity, $comment->getOwner(), strip_tags($comment->getText())))->emit();
$this->flashFail("succ", "Комментарий добавлен", "Ваш комментарий появится на странице.");
}

View file

@ -7,6 +7,7 @@ final class GiftsPresenter extends OpenVKPresenter
{
private $gifts;
private $users;
protected $presenterName = "gifts";
function __construct(Gifts $gifts, Users $users)
{

View file

@ -1,6 +1,7 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Club, Photo};
use openvk\Web\Models\Entities\{Club, Photo, Post};
use Nette\InvalidStateException;
use openvk\Web\Models\Entities\Notifications\ClubModeratorNotification;
use openvk\Web\Models\Repositories\{Clubs, Users, Albums, Managers, Topics};
use Chandler\Security\Authenticator;
@ -8,7 +9,8 @@ use Chandler\Security\Authenticator;
final class GroupPresenter extends OpenVKPresenter
{
private $clubs;
protected $presenterName = "group";
function __construct(Clubs $clubs)
{
$this->clubs = $clubs;
@ -190,7 +192,7 @@ final class GroupPresenter extends OpenVKPresenter
$this->willExecuteWriteAction();
$club = $this->clubs->get($id);
if(!$club->canBeModifiedBy($this->user->identity))
if(!$club || !$club->canBeModifiedBy($this->user->identity))
$this->notFound();
else
$this->template->club = $club;
@ -249,6 +251,88 @@ final class GroupPresenter extends OpenVKPresenter
}
}
function renderSetAvatar(int $id)
{
$photo = new Photo;
$club = $this->clubs->get($id);
if($_SERVER["REQUEST_METHOD"] === "POST" && $_FILES["ava"]["error"] === UPLOAD_ERR_OK) {
try {
$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->setCreated(time());
$photo->setAnonymous($anon);
$photo->save();
(new Albums)->getClubAvatarAlbum($club)->addPhoto($photo);
$flags = 0;
$flags |= 0b00010000;
$flags |= 0b10000000;
$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);
} catch(ISE $ex) {
$name = $album->getName();
$this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию.");
}
}
$this->returnJson([
"url" => $photo->getURL(),
"id" => $photo->getPrettyId()
]);
}
function renderEditBackdrop(int $id): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
$club = $this->clubs->get($id);
if(!$club || !$club->canBeModifiedBy($this->user->identity))
$this->notFound();
else
$this->template->club = $club;
if($_SERVER["REQUEST_METHOD"] !== "POST")
return;
if($this->postParam("subact") === "remove") {
$club->unsetBackDropPictures();
$club->save();
$this->flashFail("succ", tr("backdrop_succ_rem"), tr("backdrop_succ_desc")); # will exit
}
$pic1 = $pic2 = NULL;
try {
if($_FILES["backdrop1"]["error"] !== UPLOAD_ERR_NO_FILE)
$pic1 = Photo::fastMake($this->user->id, "Profile backdrop (system)", $_FILES["backdrop1"]);
if($_FILES["backdrop2"]["error"] !== UPLOAD_ERR_NO_FILE)
$pic2 = Photo::fastMake($this->user->id, "Profile backdrop (system)", $_FILES["backdrop2"]);
} catch(InvalidStateException $e) {
$this->flashFail("err", tr("backdrop_error_title"), tr("backdrop_error_no_media"));
}
if($pic1 == $pic2 && is_null($pic1))
$this->flashFail("err", tr("backdrop_error_title"), tr("backdrop_error_no_media"));
$club->setBackDropPictures($pic1, $pic2);
$club->save();
$this->flashFail("succ", tr("backdrop_succ"), tr("backdrop_succ_desc"));
}
function renderStatistics(int $id): void
{
$this->assertUserLoggedIn();

View file

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace openvk\Web\Presenters;
final class MaintenancePresenter extends OpenVKPresenter
{
protected $presenterName = "maintenance";
function renderSection(string $name): void
{
if(!OPENVK_ROOT_CONF["openvk"]["preferences"]["maintenanceMode"][$name])
$this->flashFail("err", tr("error"), tr("forbidden"));
$this->template->name = [
"photos" => tr("my_photos"),
"videos" => tr("my_videos"),
"messenger" => tr("my_messages"),
"user" => tr("users"),
"group" => tr("my_groups"),
"comment" => tr("comments"),
"gifts" => tr("gifts"),
"apps" => tr("apps"),
"notes" => tr("my_notes"),
"notification" => tr("my_feedback"),
"support" => tr("menu_support"),
"topics" => tr("topics")
][$name] ?? $name;
}
function renderAll(): void
{
}
}

View file

@ -9,11 +9,13 @@ final class MessengerPresenter extends OpenVKPresenter
{
private $messages;
private $signaler;
protected $presenterName = "messenger";
function __construct(Messages $messages)
{
$this->messages = $messages;
$this->signaler = SignalManager::i();
parent::__construct();
}
@ -30,7 +32,7 @@ final class MessengerPresenter extends OpenVKPresenter
function renderIndex(): void
{
$this->assertUserLoggedIn();
if(isset($_GET["sel"]))
$this->pass("openvk!Messenger->app", $_GET["sel"]);
@ -55,6 +57,11 @@ final class MessengerPresenter extends OpenVKPresenter
$correspondent = $this->getCorrespondent($sel);
if(!$correspondent)
$this->notFound();
if(!$this->user->identity->getPrivacyPermission('messages.write', $correspondent))
{
$this->flash("err", tr("warning"), tr("user_may_not_reply"));
}
$this->template->selId = $sel;
$this->template->correspondent = $correspondent;
@ -93,6 +100,13 @@ final class MessengerPresenter extends OpenVKPresenter
}
$legacy = $this->queryParam("version") < 3;
$time = intval($this->queryParam("wait"));
if($time > 60)
$time = 60;
elseif($time == 0)
$time = 25; // default
$this->signaler->listen(function($event, $eId) use ($id) {
exit(json_encode([
@ -101,7 +115,7 @@ final class MessengerPresenter extends OpenVKPresenter
$event->getVKAPISummary($id),
],
]));
}, $id);
}, $id, $time);
}
function renderApiGetMessages(int $sel, int $lastMsg): void

View file

@ -6,7 +6,8 @@ use openvk\Web\Models\Entities\Note;
final class NotesPresenter extends OpenVKPresenter
{
private $notes;
protected $presenterName = "notes";
function __construct(Notes $notes)
{
$this->notes = $notes;

View file

@ -3,6 +3,8 @@ namespace openvk\Web\Presenters;
final class NotificationPresenter extends OpenVKPresenter
{
protected $presenterName = "notification";
function renderFeed(): void
{
$this->assertUserLoggedIn();

View file

@ -17,7 +17,8 @@ abstract class OpenVKPresenter extends SimplePresenter
protected $deactivationTolerant = false;
protected $errorTemplate = "@error";
protected $user = NULL;
protected $presenterName;
private function calculateQueryString(array $data): string
{
$rawUrl = "tcp+stratum://fakeurl.net$_SERVER[REQUEST_URI]"; #HTTP_HOST can be tainted
@ -196,12 +197,13 @@ abstract class OpenVKPresenter extends SimplePresenter
function onStartup(): void
{
$user = Authenticator::i()->getUser();
$this->template->isXmas = intval(date('d')) >= 1 && date('m') == 12 || intval(date('d')) <= 15 && date('m') == 1 ? true : false;
$this->template->isTimezoned = Session::i()->get("_timezoneOffset");
$userValidated = 0;
$cacheTime = OPENVK_ROOT_CONF["openvk"]["preferences"]["nginxCacheTime"] ?? 0;
if(!is_null($user)) {
$this->user = (object) [];
$this->user->raw = $user;
@ -226,7 +228,7 @@ abstract class OpenVKPresenter extends SimplePresenter
}
exit;
}
if($this->user->identity->isBanned() && !$this->banTolerant) {
header("HTTP/1.1 403 Forbidden");
$this->getTemplatingEngine()->render(__DIR__ . "/templates/@banned.xml", [
@ -247,23 +249,34 @@ abstract class OpenVKPresenter extends SimplePresenter
]);
exit;
}
$userValidated = 1;
$cacheTime = 0; # Force no cache
if($this->user->identity->onlineStatus() == 0 && !($this->user->identity->isDeleted() || $this->user->identity->isBanned())) {
$this->user->identity->setOnline(time());
$this->user->identity->setClient_name(NULL);
$this->user->identity->save();
}
$this->template->ticketAnsweredCount = (new Tickets)->getTicketsCountByUserId($this->user->id, 1);
if($user->can("write")->model("openvk\Web\Models\Entities\TicketReply")->whichBelongsTo(0))
$this->template->helpdeskTicketNotAnsweredCount = (new Tickets)->getTicketCount(0);
}
header("X-OpenVK-User-Validated: $userValidated");
header("X-Accel-Expires: $cacheTime");
setlocale(LC_TIME, ...(explode(";", tr("__locale"))));
if (!OPENVK_ROOT_CONF["openvk"]["preferences"]["maintenanceMode"]["all"]) {
if (OPENVK_ROOT_CONF["openvk"]["preferences"]["maintenanceMode"][$this->presenterName]) {
$this->pass("openvk!Maintenance->section", $this->presenterName);
}
} else {
if ($this->presenterName != "maintenance") {
$this->redirect("/maintenances/");
}
}
parent::onStartup();
}
@ -272,10 +285,14 @@ abstract class OpenVKPresenter extends SimplePresenter
parent::onBeforeRender();
$whichbrowser = new WhichBrowser\Parser(getallheaders());
$featurephonetheme = OPENVK_ROOT_CONF["openvk"]["preferences"]["defaultFeaturePhoneTheme"];
$mobiletheme = OPENVK_ROOT_CONF["openvk"]["preferences"]["defaultMobileTheme"];
if($mobiletheme && $whichbrowser->isType('mobile') && Session::i()->get("_tempTheme") == NULL)
if($featurephonetheme && $this->isOldThing($whichbrowser) && Session::i()->get("_tempTheme") == NULL) {
$this->setSessionTheme($featurephonetheme);
} elseif($mobiletheme && $whichbrowser->isType('mobile') && Session::i()->get("_tempTheme") == NULL)
$this->setSessionTheme($mobiletheme);
$theme = NULL;
if(Session::i()->get("_tempTheme")) {
$theme = Themepacks::i()[Session::i()->get("_tempTheme", "ovk")];
@ -306,4 +323,33 @@ abstract class OpenVKPresenter extends SimplePresenter
header("Content-Length: $size");
exit($payload);
}
protected function isOldThing($whichbrowser) {
if($whichbrowser->isOs('Series60') ||
$whichbrowser->isOs('Series40') ||
$whichbrowser->isOs('Series80') ||
$whichbrowser->isOs('Windows CE') ||
$whichbrowser->isOs('Windows Mobile') ||
$whichbrowser->isOs('Nokia Asha Platform') ||
$whichbrowser->isOs('UIQ') ||
$whichbrowser->isEngine('NetFront') || // PSP and other japanese portable systems
$whichbrowser->isOs('Android') ||
$whichbrowser->isOs('iOS') ||
$whichbrowser->isBrowser('Internet Explorer', '<=', '8')) {
// yeah, it's old, but ios and android are?
if($whichbrowser->isOs('iOS') && $whichbrowser->isOs('iOS', '<=', '9'))
return true;
elseif($whichbrowser->isOs('iOS') && $whichbrowser->isOs('iOS', '>', '9'))
return false;
if($whichbrowser->isOs('Android') && $whichbrowser->isOs('Android', '<=', '5'))
return true;
elseif($whichbrowser->isOs('Android') && $whichbrowser->isOs('Android', '>', '5'))
return false;
return true;
} else {
return false;
}
}
}

View file

@ -9,7 +9,8 @@ final class PhotosPresenter extends OpenVKPresenter
private $users;
private $photos;
private $albums;
protected $presenterName = "photos";
function __construct(Photos $photos, Albums $albums, Users $users)
{
$this->users = $users;
@ -184,6 +185,18 @@ final class PhotosPresenter extends OpenVKPresenter
$this->renderPhoto($photo->getOwner(true)->getId(), $photo->getVirtualId());
}
function renderThumbnail($id, $size): void
{
$photo = $this->photos->get($id);
if(!$photo || $photo->isDeleted())
$this->notFound();
if(!$photo->forceSize($size))
chandler_http_panic(588, "Gone", "This thumbnail cannot be generated due to server misconfiguration");
$this->redirect($photo->getURLBySizeId($size), 8);
}
function renderEditPhoto(int $ownerId, int $photoId): void
{
$this->assertUserLoggedIn();

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