From def76226b7323128c929a68214e0eda21cf1fda1 Mon Sep 17 00:00:00 2001 From: Alexander Minkin Date: Sun, 9 Mar 2025 16:03:33 +0300 Subject: [PATCH] feat(core): add phpstan for static analysis (#1223) * feat: add phpstan for static analysis * ci(actions): add phpstan action * ci(actions): do analysing inside docker container * fix(FetchToncoinTransactions): add var declaration * fix(ServiceAPI/Wall): add var declaration * fix(bootstrap): remove case-insensitive false vars * fix(VKAPI/Handlers/Board): change parameters order * fix(VKAPIRequestHandler): set fail's return type as never * fix(VKAPI/Handlers/Groups): add array declaration * fix(VKAPI/Handlers/Newsfeed): add return_banned declaration * fix(VKAPI/Handlers/Notes): move $nodez declaration up * fix(phpstan): most of the things and stupid lines of code * fix(lint) * fix(phpstan): less errors * fix(lint): again. cuz i forgot about it * fix(stan): all errors are gone now =3 --------- Co-authored-by: veselcraft --- .github/workflows/analyse.yaml | 36 +++++ CLI/FetchToncoinTransactions.php | 1 + ServiceAPI/Wall.php | 1 + VKAPI/Handlers/Board.php | 3 +- VKAPI/Handlers/Groups.php | 2 +- VKAPI/Handlers/Newsfeed.php | 2 +- VKAPI/Handlers/Notes.php | 11 +- VKAPI/Handlers/Photos.php | 1 + VKAPI/Handlers/Users.php | 7 +- VKAPI/Handlers/VKAPIRequestHandler.php | 2 +- VKAPI/Handlers/Wall.php | 6 +- Web/Models/Entities/Document.php | 4 +- Web/Models/Entities/Gift.php | 1 - Web/Models/Entities/Message.php | 6 +- .../Entities/Notifications/Notification.php | 4 +- Web/Models/Entities/Photo.php | 4 +- Web/Models/Entities/Report.php | 6 +- Web/Models/Entities/User.php | 5 +- Web/Models/Entities/Video.php | 12 +- Web/Models/Repositories/ChandlerGroups.php | 2 + Web/Models/Repositories/Clubs.php | 4 +- Web/Models/Repositories/Conversations.php | 53 ------- Web/Models/Repositories/Documents.php | 2 +- Web/Models/Repositories/SupportAgents.php | 2 +- Web/Models/Repositories/Tickets.php | 2 +- Web/Presenters/AdminPresenter.php | 2 +- Web/Presenters/BlobPresenter.php | 3 +- Web/Presenters/CommentPresenter.php | 1 + Web/Presenters/ContentSearchPresenter.php | 8 +- Web/Presenters/GroupPresenter.php | 2 +- Web/Presenters/MessengerPresenter.php | 9 +- Web/Presenters/NoSpamPresenter.php | 144 +++++++++--------- Web/Presenters/OpenVKPresenter.php | 4 +- Web/Presenters/SearchPresenter.php | 4 +- Web/Presenters/SupportPresenter.php | 9 +- Web/Presenters/TopicsPresenter.php | 4 +- Web/Presenters/VKAPIPresenter.php | 2 +- Web/Presenters/WallPresenter.php | 14 +- Web/Themes/Themepack.php | 2 +- Web/Themes/Themepacks.php | 19 --- Web/Util/Bitmask.php | 4 +- Web/Util/DateTime.php | 1 + Web/Util/Makima/Makima.php | 8 +- bootstrap.php | 8 +- chandler_loader.php | 10 ++ composer.json | 6 +- composer.lock | 60 +++++++- openvkctl | 4 +- phpstan.neon | 14 ++ 49 files changed, 299 insertions(+), 222 deletions(-) create mode 100644 .github/workflows/analyse.yaml delete mode 100644 Web/Models/Repositories/Conversations.php create mode 100644 chandler_loader.php create mode 100644 phpstan.neon diff --git a/.github/workflows/analyse.yaml b/.github/workflows/analyse.yaml new file mode 100644 index 00000000..e0ae6755 --- /dev/null +++ b/.github/workflows/analyse.yaml @@ -0,0 +1,36 @@ +name: Static analysis + +on: + push: + pull_request: + +jobs: + phpstan: + name: PHPStan + runs-on: ubuntu-20.04 + + # 'push' runs on inner branches, 'pull_request' will run only on outer PRs + if: > + github.event_name == 'push' + || (github.event_name == 'pull_request' + && github.event.pull_request.head.repo.full_name != github.repository) + + steps: + - name: Code Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and start Docker container + working-directory: install/automated/docker + run: | + docker build -t openvk ../../.. -f openvk.Dockerfile + + - name: Run Docker container with PHPStan + working-directory: install/automated/docker + run: | + docker container run --rm \ + -v ./chandler.example.yml:/opt/chandler/chandler.yml \ + -v ./openvk.example.yml:/opt/chandler/extensions/available/openvk/openvk.yml \ + openvk vendor/bin/phpstan analyse --memory-limit 1G diff --git a/CLI/FetchToncoinTransactions.php b/CLI/FetchToncoinTransactions.php index 9de2c998..8342197f 100755 --- a/CLI/FetchToncoinTransactions.php +++ b/CLI/FetchToncoinTransactions.php @@ -18,6 +18,7 @@ define("NANOTON", 1000000000); class FetchToncoinTransactions extends Command { private $images; + private $transactions; protected static $defaultName = "fetch-ton"; diff --git a/ServiceAPI/Wall.php b/ServiceAPI/Wall.php index 4066d4f3..db6c32b6 100644 --- a/ServiceAPI/Wall.php +++ b/ServiceAPI/Wall.php @@ -13,6 +13,7 @@ class Wall implements Handler protected $user; protected $posts; protected $notes; + protected $videos; public function __construct(?User $user) { diff --git a/VKAPI/Handlers/Board.php b/VKAPI/Handlers/Board.php index f2f3ec76..28328586 100644 --- a/VKAPI/Handlers/Board.php +++ b/VKAPI/Handlers/Board.php @@ -248,8 +248,9 @@ final class Board extends VKAPIRequestHandler return 1; } - public function editComment(int $comment_id, int $group_id = 0, int $topic_id = 0, string $message, string $attachments) + public function editComment(string $message, string $attachments, int $comment_id, int $group_id = 0, int $topic_id = 0) { + # FIXME /* $this->requireUser(); $this->willExecuteWriteAction(); diff --git a/VKAPI/Handlers/Groups.php b/VKAPI/Handlers/Groups.php index baf78210..7f80d108 100644 --- a/VKAPI/Handlers/Groups.php +++ b/VKAPI/Handlers/Groups.php @@ -45,7 +45,7 @@ final class Groups extends VKAPIRequestHandler $clbsCount = $user->getClubCount(); } - $rClubs; + $rClubs = []; $ic = sizeof($clbs); if (sizeof($clbs) > $count) { diff --git a/VKAPI/Handlers/Newsfeed.php b/VKAPI/Handlers/Newsfeed.php index 346d5312..5700ada7 100644 --- a/VKAPI/Handlers/Newsfeed.php +++ b/VKAPI/Handlers/Newsfeed.php @@ -52,7 +52,7 @@ final class Newsfeed extends VKAPIRequestHandler return $response; } - public function getGlobal(string $fields = "", int $start_from = 0, int $start_time = 0, int $end_time = 0, int $offset = 0, int $count = 30, int $extended = 0, int $rss = 0) + public function getGlobal(string $fields = "", int $start_from = 0, int $start_time = 0, int $end_time = 0, int $offset = 0, int $count = 30, int $extended = 0, int $rss = 0, int $return_banned = 0) { $this->requireUser(); diff --git a/VKAPI/Handlers/Notes.php b/VKAPI/Handlers/Notes.php index cdf5aa45..bde9df75 100644 --- a/VKAPI/Handlers/Notes.php +++ b/VKAPI/Handlers/Notes.php @@ -185,12 +185,14 @@ final class Notes extends VKAPIRequestHandler $this->fail(15, "Access denied"); } + $nodez = (object) [ + "count" => 0, + "notes" => [], + ]; if (empty($note_ids)) { + $nodez->count = (new NotesRepo())->getUserNotesCount($user); + $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()) { @@ -210,6 +212,7 @@ final class Notes extends VKAPIRequestHandler $note = (new NotesRepo())->getNoteById((int) $id[0], (int) $id[1]); if ($note && !$note->isDeleted()) { $nodez->notes[] = $note->toVkApiStruct(); + $nodez->count++; } } } diff --git a/VKAPI/Handlers/Photos.php b/VKAPI/Handlers/Photos.php index 1863daaf..d13d3fc3 100644 --- a/VKAPI/Handlers/Photos.php +++ b/VKAPI/Handlers/Photos.php @@ -9,6 +9,7 @@ use Nette\Utils\ImageException; 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\Videos as VideosRepo; use openvk\Web\Models\Repositories\Clubs; use openvk\Web\Models\Repositories\Users as UsersRepo; use openvk\Web\Models\Repositories\Comments as CommentsRepo; diff --git a/VKAPI/Handlers/Users.php b/VKAPI/Handlers/Users.php index e3d27073..bafce3d8 100644 --- a/VKAPI/Handlers/Users.php +++ b/VKAPI/Handlers/Users.php @@ -292,14 +292,14 @@ final class Users extends VKAPIRequestHandler break; case 'blacklisted_by_me': if (!$authuser) { - continue; + break; } $response[$i]->blacklisted_by_me = (int) $usr->isBlacklistedBy($this->getUser()); break; case 'blacklisted': if (!$authuser) { - continue; + break; } $response[$i]->blacklisted = (int) $this->getUser()->isBlacklistedBy($usr); @@ -383,7 +383,8 @@ final class Users extends VKAPIRequestHandler string $fav_music = "", string $fav_films = "", string $fav_shows = "", - string $fav_books = "" + string $fav_books = "", + string $interests = "" ) { if ($count > 100) { $this->fail(100, "One of the parameters specified was missing or invalid: count should be less or equal to 100"); diff --git a/VKAPI/Handlers/VKAPIRequestHandler.php b/VKAPI/Handlers/VKAPIRequestHandler.php index b67c4797..4804a8dd 100644 --- a/VKAPI/Handlers/VKAPIRequestHandler.php +++ b/VKAPI/Handlers/VKAPIRequestHandler.php @@ -20,7 +20,7 @@ abstract class VKAPIRequestHandler $this->platform = $platform; } - protected function fail(int $code, string $message): void + protected function fail(int $code, string $message): never { throw new APIErrorException($message, $code); } diff --git a/VKAPI/Handlers/Wall.php b/VKAPI/Handlers/Wall.php index 3bc56b93..a906596b 100644 --- a/VKAPI/Handlers/Wall.php +++ b/VKAPI/Handlers/Wall.php @@ -53,7 +53,7 @@ final class Wall extends VKAPIRequestHandler $this->fail(15, "Access denied: wall is disabled"); } // Don't search for logic here pls - $iteratorv; + $iteratorv = null; switch ($filter) { case "all": @@ -722,7 +722,7 @@ final class Wall extends VKAPIRequestHandler $post->attach($attachment); } - if ($wall > 0 && $wall !== $this->user->identity->getId()) { + if ($owner_id > 0 && $owner_id !== $this->user->identity->getId()) { (new WallPostNotification($wallOwner, $post, $this->user->identity))->emit(); } @@ -734,7 +734,7 @@ final class Wall extends VKAPIRequestHandler $this->requireUser(); $this->willExecuteWriteAction(); - $postArray; + $postArray = []; if (preg_match('/(wall|video|photo)((?:-?)[0-9]+)_([0-9]+)/', $object, $postArray) == 0) { $this->fail(100, "One of the parameters specified was missing or invalid: object is incorrect"); } diff --git a/Web/Models/Entities/Document.php b/Web/Models/Entities/Document.php index 286f9c0b..a264ad71 100644 --- a/Web/Models/Entities/Document.php +++ b/Web/Models/Entities/Document.php @@ -351,7 +351,7 @@ class Document extends Media return $this->getRecord()->owner; } - public function toApiPreview(): object + public function toApiPreview(): ?object { $preview = $this->getPreview(); if ($preview instanceof Photo) { @@ -360,6 +360,8 @@ class Document extends Media "sizes" => array_values($preview->getVkApiSizes()), ], ]; + } else { + return null; } } diff --git a/Web/Models/Entities/Gift.php b/Web/Models/Entities/Gift.php index f63b7b81..f4a76b35 100644 --- a/Web/Models/Entities/Gift.php +++ b/Web/Models/Entities/Gift.php @@ -112,7 +112,6 @@ class Gift extends RowModel public function setImage(string $file): bool { - $imgBlob; try { $image = Image::fromFile($file); $image->resize(512, 512, Image::SHRINK_ONLY); diff --git a/Web/Models/Entities/Message.php b/Web/Models/Entities/Message.php index 4e9b8455..1d3364c5 100644 --- a/Web/Models/Entities/Message.php +++ b/Web/Models/Entities/Message.php @@ -33,6 +33,8 @@ class Message extends RowModel return (new Users())->get($this->getRecord()->sender_id); } elseif ($this->getRecord()->sender_type === 'openvk\Web\Models\Entities\Club') { return (new Clubs())->get($this->getRecord()->sender_id); + } else { + return null; } } @@ -49,6 +51,8 @@ class Message extends RowModel return (new Users())->get($this->getRecord()->recipient_id); } elseif ($this->getRecord()->recipient_type === 'openvk\Web\Models\Entities\Club') { return (new Clubs())->get($this->getRecord()->recipient_id); + } else { + return null; } } @@ -147,7 +151,7 @@ class Message extends RowModel "id" => $author->getId(), "link" => $_SERVER['REQUEST_SCHEME'] . "://" . $_SERVER['HTTP_HOST'] . $author->getURL(), "avatar" => $author->getAvatarUrl(), - "name" => $author->getFirstName() . $unreadmsg, + "name" => $author->getFirstName(), ], "timing" => [ "sent" => (string) $this->getSendTimeHumanized(), diff --git a/Web/Models/Entities/Notifications/Notification.php b/Web/Models/Entities/Notifications/Notification.php index 67d36497..6a6866c1 100644 --- a/Web/Models/Entities/Notifications/Notification.php +++ b/Web/Models/Entities/Notifications/Notification.php @@ -64,13 +64,15 @@ class Notification return $this->recipient; } - public function getModel(int $index): RowModel + public function getModel(int $index): ?RowModel { switch ($index) { case 0: return $this->originModel; case 1: return $this->targetModel; + default: + return null; } } diff --git a/Web/Models/Entities/Photo.php b/Web/Models/Entities/Photo.php index 1eb42110..470a0a8a 100644 --- a/Web/Models/Entities/Photo.php +++ b/Web/Models/Entities/Photo.php @@ -385,9 +385,9 @@ class Photo extends Media } } - public static function fastMake(int $owner, string $description = "", array $file, ?Album $album = null, bool $anon = false): Photo + public static function fastMake(int $owner, string $description, array $file, ?Album $album = null, bool $anon = false): Photo { - $photo = new static(); + $photo = new Photo(); $photo->setOwner($owner); $photo->setDescription(iconv_substr($description, 0, 36) . "..."); $photo->setAnonymous($anon); diff --git a/Web/Models/Entities/Report.php b/Web/Models/Entities/Report.php index db8d38e6..1d345fd4 100644 --- a/Web/Models/Entities/Report.php +++ b/Web/Models/Entities/Report.php @@ -45,11 +45,7 @@ class Report extends RowModel public function isDeleted(): bool { - if ($this->getRecord()->deleted === 0) { - return false; - } elseif ($this->getRecord()->deleted === 1) { - return true; - } + return $this->getRecord()->deleted === 1; } public function authorId(): int diff --git a/Web/Models/Entities/User.php b/Web/Models/Entities/User.php index 31d3d4fe..5532f2f8 100644 --- a/Web/Models/Entities/User.php +++ b/Web/Models/Entities/User.php @@ -932,6 +932,7 @@ class User extends RowModel case 1: return tr('female'); case 2: + default: return tr('neutral'); } } @@ -1559,14 +1560,14 @@ class User extends RowModel break; case "blacklisted_by_me": if (!$user) { - continue; + break; } $res->blacklisted_by_me = (int) $this->isBlacklistedBy($user); break; case "blacklisted": if (!$user) { - continue; + break; } $res->blacklisted = (int) $user->isBlacklistedBy($this); diff --git a/Web/Models/Entities/Video.php b/Web/Models/Entities/Video.php index b8a9fcca..8dbfb7a1 100644 --- a/Web/Models/Entities/Video.php +++ b/Web/Models/Entities/Video.php @@ -9,12 +9,13 @@ use openvk\Web\Util\Shell\Exceptions\{ShellUnavailableException, UnknownCommandE use openvk\Web\Models\VideoDrivers\VideoDriver; use Nette\InvalidStateException as ISE; -define("VIDEOS_FRIENDLY_ERROR", "Uploads are disabled on this instance :<", false); +define("VIDEOS_FRIENDLY_ERROR", "Uploads are disabled on this instance :<"); class Video extends Media { - public const TYPE_DIRECT = 0; - public const TYPE_EMBED = 1; + public const TYPE_DIRECT = 0; + public const TYPE_EMBED = 1; + public const TYPE_UNKNOWN = -1; protected $tableName = "videos"; protected $fileExtension = "mp4"; @@ -108,6 +109,7 @@ class Video extends Media } elseif (!is_null($this->getRecord()->link)) { return Video::TYPE_EMBED; } + return Video::TYPE_UNKNOWN; } public function getVideoDriver(): ?VideoDriver @@ -238,7 +240,7 @@ class Video extends Media $this->save(); } - public static function fastMake(int $owner, string $name = "Unnamed Video.ogv", string $description = "", array $file, bool $unlisted = true, bool $anon = false): Video + public static function fastMake(int $owner, string $name, string $description, array $file, bool $unlisted = true, bool $anon = false): Video { if (OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading']) { exit(VIDEOS_FRIENDLY_ERROR); @@ -269,7 +271,7 @@ class Video extends Media return false; } - $streams = Shell::ffprobe("-i", $path, "-show_streams", "-select_streams v", "-loglevel error")->execute($error); + $streams = Shell::ffprobe("-i", $path, "-show_streams", "-select_streams v", "-loglevel error")->execute(); $durations = []; preg_match_all('%duration=([0-9\.]++)%', $streams, $durations); diff --git a/Web/Models/Repositories/ChandlerGroups.php b/Web/Models/Repositories/ChandlerGroups.php index 3c7c62de..ad43da2e 100644 --- a/Web/Models/Repositories/ChandlerGroups.php +++ b/Web/Models/Repositories/ChandlerGroups.php @@ -13,6 +13,8 @@ class ChandlerGroups { private $context; private $groups; + private $members; + private $perms; public function __construct() { diff --git a/Web/Models/Repositories/Clubs.php b/Web/Models/Repositories/Clubs.php index b425c858..fcc9d5f8 100644 --- a/Web/Models/Repositories/Clubs.php +++ b/Web/Models/Repositories/Clubs.php @@ -91,7 +91,7 @@ class Clubs return (clone $this->clubs)->count('*'); } - public function getPopularClubs(): \Traversable + public function getPopularClubs(): ?\Traversable { // TODO rewrite @@ -106,6 +106,8 @@ class Clubs "subscriptions" => $entry["subscriptions"], ]; */ + trigger_error("Clubs::getPopularClubs() is currently commented out and returns null", E_USER_WARNING); + return null; } public function getWriteableClubs(int $id): \Traversable diff --git a/Web/Models/Repositories/Conversations.php b/Web/Models/Repositories/Conversations.php deleted file mode 100644 index e354411b..00000000 --- a/Web/Models/Repositories/Conversations.php +++ /dev/null @@ -1,53 +0,0 @@ -context = DB::i()->getContext(); - $this->convos = $this->context->table("conversations"); - } - - private function toConversation(?ActiveRow $ar): ?M\AbstractConversation - { - if (is_null($ar)) { - return null; - } elseif ($ar->is_pm) { - return new M\PrivateConversation($ar); - } else { - return new M\Conversation($ar); - } - } - - public function get(int $id): ?M\AbstractConversation - { - return $this->toConversation($this->convos->get($id)); - } - - public function getConversationsByUser(User $user, int $page = 1, ?int $perPage = null): \Traversable - { - $rels = $this->context->table("conversation_members")->where([ - "deleted" => false, - "user" => $user->getId(), - ])->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE); - foreach ($rels as $rel) { - yield $this->get($rel->conversation); - } - } - - public function getPrivateConversation(User $user, int $peer): M\PrivateConversation - { - ; - } -} diff --git a/Web/Models/Repositories/Documents.php b/Web/Models/Repositories/Documents.php index cda95abc..db98fc5d 100644 --- a/Web/Models/Repositories/Documents.php +++ b/Web/Models/Repositories/Documents.php @@ -152,7 +152,7 @@ class Documents switch ($paramName) { case "type": if ($paramValue < 1 || $paramValue > 8) { - continue; + break; } $result->where("type", $paramValue); break; diff --git a/Web/Models/Repositories/SupportAgents.php b/Web/Models/Repositories/SupportAgents.php index b0dfcbbc..d83d1974 100644 --- a/Web/Models/Repositories/SupportAgents.php +++ b/Web/Models/Repositories/SupportAgents.php @@ -11,7 +11,7 @@ use openvk\Web\Models\Entities\{User, SupportAgent}; class SupportAgents { private $context; - private $tickets; + private $agents; public function __construct() { diff --git a/Web/Models/Repositories/Tickets.php b/Web/Models/Repositories/Tickets.php index dd7379c8..6728931e 100644 --- a/Web/Models/Repositories/Tickets.php +++ b/Web/Models/Repositories/Tickets.php @@ -57,7 +57,7 @@ class Tickets { $requests = $this->tickets->where(["id" => $requestId])->fetch(); if (!is_null($requests)) { - return new Req($requests); + return new Ticket($requests); } else { return null; } diff --git a/Web/Presenters/AdminPresenter.php b/Web/Presenters/AdminPresenter.php index f0f2b04d..8fad1052 100644 --- a/Web/Presenters/AdminPresenter.php +++ b/Web/Presenters/AdminPresenter.php @@ -255,7 +255,7 @@ final class AdminPresenter extends OpenVKPresenter { $this->warnIfNoCommerce(); - $cat; + $cat = null; $gen = false; if ($id !== 0) { $cat = $this->gifts->getCat($id); diff --git a/Web/Presenters/BlobPresenter.php b/Web/Presenters/BlobPresenter.php index 99b57816..619d9d54 100644 --- a/Web/Presenters/BlobPresenter.php +++ b/Web/Presenters/BlobPresenter.php @@ -34,7 +34,8 @@ final class BlobPresenter extends OpenVKPresenter } if (isset($_SERVER["HTTP_IF_NONE_MATCH"])) { - exit(header("HTTP/1.1 304 Not Modified")); + header("HTTP/1.1 304 Not Modified"); + exit(); } header("Content-Type: " . mime_content_type($path)); diff --git a/Web/Presenters/CommentPresenter.php b/Web/Presenters/CommentPresenter.php index 0353acf8..f9317cff 100644 --- a/Web/Presenters/CommentPresenter.php +++ b/Web/Presenters/CommentPresenter.php @@ -7,6 +7,7 @@ namespace openvk\Web\Presenters; 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, Videos, Photos, Audios}; +use Nette\InvalidStateException as ISE; final class CommentPresenter extends OpenVKPresenter { diff --git a/Web/Presenters/ContentSearchPresenter.php b/Web/Presenters/ContentSearchPresenter.php index 6fadd687..e22f34fa 100644 --- a/Web/Presenters/ContentSearchPresenter.php +++ b/Web/Presenters/ContentSearchPresenter.php @@ -8,17 +8,17 @@ use openvk\Web\Models\Repositories\ContentSearchRepository; final class ContentSearchPresenter extends OpenVKPresenter { - private $repo; + protected $repo; - public function __construct(ContentSearchRepository $repo) + public function __construct(ContentSearchRepository $repository) { - $this->repo = $repo; + $this->repo = $repository; } public function renderIndex(): void { if ($_SERVER["REQUEST_METHOD"] === "POST") { - $this->template->results = $repo->find([ + $this->template->results = $this->repo->find([ "query" => $this->postParam("query"), ]); } diff --git a/Web/Presenters/GroupPresenter.php b/Web/Presenters/GroupPresenter.php index 1aa14213..c9e6e9f2 100644 --- a/Web/Presenters/GroupPresenter.php +++ b/Web/Presenters/GroupPresenter.php @@ -9,6 +9,7 @@ use Nette\InvalidStateException; use openvk\Web\Models\Entities\Notifications\ClubModeratorNotification; use openvk\Web\Models\Repositories\{Clubs, Users, Albums, Managers, Topics, Audios, Posts, Documents}; use Chandler\Security\Authenticator; +use Nette\InvalidStateException as ISE; final class GroupPresenter extends OpenVKPresenter { @@ -288,7 +289,6 @@ final class GroupPresenter extends OpenVKPresenter (new Albums())->getClubAvatarAlbum($club)->addPhoto($photo); } catch (ISE $ex) { - $name = $album->getName(); $this->flashFail("err", tr("error"), tr("error_when_uploading_photo")); } } diff --git a/Web/Presenters/MessengerPresenter.php b/Web/Presenters/MessengerPresenter.php index 70054620..5c8c6a6e 100644 --- a/Web/Presenters/MessengerPresenter.php +++ b/Web/Presenters/MessengerPresenter.php @@ -93,9 +93,11 @@ final class MessengerPresenter extends OpenVKPresenter header("Content-Type: application/json"); if ($this->queryParam("act") !== "a_check") { - exit(header("HTTP/1.1 400 Bad Request")); + header("HTTP/1.1 400 Bad Request"); + exit(); } elseif (!$this->queryParam("key")) { - exit(header("HTTP/1.1 403 Forbidden")); + header("HTTP/1.1 403 Forbidden"); + exit(); } $key = $this->queryParam("key"); @@ -158,7 +160,8 @@ final class MessengerPresenter extends OpenVKPresenter $sel = $this->getCorrespondent($sel); if ($sel->getId() !== $this->user->id && !$sel->getPrivacyPermission('messages.write', $this->user->identity)) { - exit(header("HTTP/1.1 403 Forbidden")); + header("HTTP/1.1 403 Forbidden"); + exit(); } $cor = new Correspondence($this->user->identity, $sel); diff --git a/Web/Presenters/NoSpamPresenter.php b/Web/Presenters/NoSpamPresenter.php index 4f49d3b1..e6da910b 100644 --- a/Web/Presenters/NoSpamPresenter.php +++ b/Web/Presenters/NoSpamPresenter.php @@ -151,77 +151,6 @@ final class NoSpamPresenter extends OpenVKPresenter $this->assertNoCSRF(); $this->willExecuteWriteAction(); - function searchByAdditionalParams(?string $table = null, ?string $where = null, ?string $ip = null, ?string $useragent = null, ?int $ts = null, ?int $te = null, $user = null) - { - $db = DatabaseConnection::i()->getContext(); - if ($table && ($ip || $useragent || $ts || $te || $user)) { - $conditions = []; - - if ($ip) { - $conditions[] = "`ip` REGEXP '$ip'"; - } - if ($useragent) { - $conditions[] = "`useragent` REGEXP '$useragent'"; - } - if ($ts) { - $conditions[] = "`ts` < $ts"; - } - if ($te) { - $conditions[] = "`ts` > $te"; - } - if ($user) { - $users = new Users(); - - $_user = $users->getByChandlerUser((new ChandlerUsers())->getById($user)) - ?? $users->get((int) $user) - ?? $users->getByAddress($user) - ?? null; - - if ($_user) { - $conditions[] = "`user` = '" . $_user->getChandlerGUID() . "'"; - } - } - - $whereStart = "WHERE `object_table` = '$table'"; - if ($table === "profiles") { - $whereStart .= "AND `type` = 0"; - } - - $conditions = count($conditions) > 0 ? "AND (" . implode(" AND ", $conditions) . ")" : ""; - $response = []; - - if ($conditions) { - $logs = $db->query("SELECT * FROM `ChandlerLogs` $whereStart $conditions GROUP BY `object_id`, `object_model`"); - - foreach ($logs as $log) { - $log = (new Logs())->get($log->id); - $object = $log->getObject()->unwrap(); - - if (!$object) { - continue; - } - if ($where) { - if (str_starts_with($where, " AND")) { - $where = substr_replace($where, "", 0, strlen(" AND")); - } - - $a = $db->query("SELECT * FROM `$table` WHERE $where")->fetchAll(); - foreach ($a as $o) { - if ($object->id == $o["id"]) { - $response[] = $object; - } - } - - } else { - $response[] = $object; - } - } - } - - return $response; - } - } - try { $response = []; $processed = 0; @@ -290,7 +219,7 @@ final class NoSpamPresenter extends OpenVKPresenter } if ($ip || $useragent || $ts || $te || $user) { - $rows = searchByAdditionalParams($table, $where, $ip, $useragent, $ts, $te, $user); + $rows = $this->searchByAdditionalParams($table, $where, $ip, $useragent, $ts, $te, $user); } else { if (!$where) { $rows = []; @@ -408,4 +337,75 @@ final class NoSpamPresenter extends OpenVKPresenter $this->returnJson(["success" => false, "error" => $e->getMessage()]); } } + + private function searchByAdditionalParams(?string $table = null, ?string $where = null, ?string $ip = null, ?string $useragent = null, ?int $ts = null, ?int $te = null, $user = null) + { + $db = DatabaseConnection::i()->getContext(); + if ($table && ($ip || $useragent || $ts || $te || $user)) { + $conditions = []; + + if ($ip) { + $conditions[] = "`ip` REGEXP '$ip'"; + } + if ($useragent) { + $conditions[] = "`useragent` REGEXP '$useragent'"; + } + if ($ts) { + $conditions[] = "`ts` < $ts"; + } + if ($te) { + $conditions[] = "`ts` > $te"; + } + if ($user) { + $users = new Users(); + + $_user = $users->getByChandlerUser((new ChandlerUsers())->getById($user)) + ?? $users->get((int) $user) + ?? $users->getByAddress($user) + ?? null; + + if ($_user) { + $conditions[] = "`user` = '" . $_user->getChandlerGUID() . "'"; + } + } + + $whereStart = "WHERE `object_table` = '$table'"; + if ($table === "profiles") { + $whereStart .= "AND `type` = 0"; + } + + $conditions = count($conditions) > 0 ? "AND (" . implode(" AND ", $conditions) . ")" : ""; + $response = []; + + if ($conditions) { + $logs = $db->query("SELECT * FROM `ChandlerLogs` $whereStart $conditions GROUP BY `object_id`, `object_model`"); + + foreach ($logs as $log) { + $log = (new Logs())->get($log->id); + $object = $log->getObject()->unwrap(); + + if (!$object) { + continue; + } + if ($where) { + if (str_starts_with($where, " AND")) { + $where = substr_replace($where, "", 0, strlen(" AND")); + } + + $a = $db->query("SELECT * FROM `$table` WHERE $where")->fetchAll(); + foreach ($a as $o) { + if ($object->id == $o["id"]) { + $response[] = $object; + } + } + + } else { + $response[] = $object; + } + } + } + + return $response; + } + } } diff --git a/Web/Presenters/OpenVKPresenter.php b/Web/Presenters/OpenVKPresenter.php index 122e0966..478670f0 100644 --- a/Web/Presenters/OpenVKPresenter.php +++ b/Web/Presenters/OpenVKPresenter.php @@ -9,6 +9,7 @@ use Chandler\MVC\SimplePresenter; use Chandler\Session\Session; use Chandler\Security\Authenticator; use Latte\Engine as TemplatingEngine; +use Nette\InvalidStateException as ISE; use openvk\Web\Models\Entities\IP; use openvk\Web\Themes\Themepacks; use openvk\Web\Models\Repositories\{IPs, Users, APITokens, Tickets, Reports, CurrentUser, Posts}; @@ -74,7 +75,6 @@ abstract class OpenVKPresenter extends SimplePresenter protected function logInUserWithToken(): void { $header = $_SERVER["HTTP_AUTHORIZATION"] ?? ""; - $token; preg_match("%Bearer (.*)$%", $header, $matches); $token = $matches[1] ?? ""; @@ -130,7 +130,7 @@ abstract class OpenVKPresenter extends SimplePresenter } if ($throw) { - throw new SecurityPolicyViolationException("Permission error"); + throw new ISE("Permission error"); } else { $this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment")); } diff --git a/Web/Presenters/SearchPresenter.php b/Web/Presenters/SearchPresenter.php index 102a6435..ee1b62a0 100644 --- a/Web/Presenters/SearchPresenter.php +++ b/Web/Presenters/SearchPresenter.php @@ -70,7 +70,7 @@ final class SearchPresenter extends OpenVKPresenter case 'marital_status': case 'polit_views': if ((int) $param_value == 0) { - continue; + break; } $parameters[$param_name] = $param_value; @@ -96,7 +96,7 @@ final class SearchPresenter extends OpenVKPresenter # дай бог работал этот case case 'from_me': if ((int) $param_value != 1) { - continue; + break; } $parameters['from_me'] = $this->user->id; diff --git a/Web/Presenters/SupportPresenter.php b/Web/Presenters/SupportPresenter.php index 2ab2de2b..c9267b18 100644 --- a/Web/Presenters/SupportPresenter.php +++ b/Web/Presenters/SupportPresenter.php @@ -314,17 +314,20 @@ final class SupportPresenter extends OpenVKPresenter $comment = $this->comments->get($id); if ($this->user->id !== $comment->getTicket()->getUser()->getId()) { - exit(header("HTTP/1.1 403 Forbidden")); + header("HTTP/1.1 403 Forbidden"); + exit(); } if ($mark !== 1 && $mark !== 2) { - exit(header("HTTP/1.1 400 Bad Request")); + header("HTTP/1.1 400 Bad Request"); + exit(); } $comment->setMark($mark); $comment->save(); - exit(header("HTTP/1.1 200 OK")); + header("HTTP/1.1 200 OK"); + exit(); } public function renderQuickBanInSupport(int $id): void diff --git a/Web/Presenters/TopicsPresenter.php b/Web/Presenters/TopicsPresenter.php index dadbb300..04987b6e 100644 --- a/Web/Presenters/TopicsPresenter.php +++ b/Web/Presenters/TopicsPresenter.php @@ -6,6 +6,7 @@ namespace openvk\Web\Presenters; use openvk\Web\Models\Entities\{Topic, Club, Comment, Photo, Video}; use openvk\Web\Models\Repositories\{Topics, Clubs}; +use Nette\InvalidStateException as ISE; final class TopicsPresenter extends OpenVKPresenter { @@ -112,9 +113,6 @@ final class TopicsPresenter extends OpenVKPresenter $video = null; if ($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) { $album = null; - if ($wall > 0 && $wall === $this->user->id) { - $album = (new Albums())->getUserWallAlbum($wallOwner); - } $photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"], $album); } diff --git a/Web/Presenters/VKAPIPresenter.php b/Web/Presenters/VKAPIPresenter.php index 61ee33fa..2ab0818a 100644 --- a/Web/Presenters/VKAPIPresenter.php +++ b/Web/Presenters/VKAPIPresenter.php @@ -273,7 +273,7 @@ final class VKAPIPresenter extends OpenVKPresenter } } - define("VKAPI_DECL_VER", $this->requestParam("v") ?? "4.100", false); + define("VKAPI_DECL_VER", $this->requestParam("v") ?? "4.100"); try { $res = $handler->{$method}(...$params); diff --git a/Web/Presenters/WallPresenter.php b/Web/Presenters/WallPresenter.php index b64c2af9..79b36c2e 100644 --- a/Web/Presenters/WallPresenter.php +++ b/Web/Presenters/WallPresenter.php @@ -265,8 +265,11 @@ final class WallPresenter extends OpenVKPresenter $this->assertUserLoggedIn(); $this->willExecuteWriteAction(); - $wallOwner = ($wall > 0 ? (new Users())->get($wall) : (new Clubs())->get($wall * -1)) - ?? $this->flashFail("err", tr("failed_to_publish_post"), tr("error_4")); + $wallOwner = ($wall > 0 ? (new Users())->get($wall) : (new Clubs())->get($wall * -1)); + + if ($wallOwner === null) { + $this->flashFail("err", tr("failed_to_publish_post"), tr("error_4")); + } if ($wallOwner->isBanned()) { $this->flashFail("err", tr("error"), tr("forbidden")); @@ -568,8 +571,11 @@ final class WallPresenter extends OpenVKPresenter } $user = $this->user->id; - $wallOwner = ($wall > 0 ? (new Users())->get($wall) : (new Clubs())->get($wall * -1)) - ?? $this->flashFail("err", tr("failed_to_delete_post"), tr("error_4")); + $wallOwner = ($wall > 0 ? (new Users())->get($wall) : (new Clubs())->get($wall * -1)); + + if ($wallOwner === null) { + $this->flashFail("err", tr("failed_to_delete_post"), tr("error_4")); + } if ($wallOwner->isBanned()) { $this->flashFail("err", tr("error"), tr("forbidden")); diff --git a/Web/Themes/Themepack.php b/Web/Themes/Themepack.php index be2b2c11..01e41a3b 100644 --- a/Web/Themes/Themepack.php +++ b/Web/Themes/Themepack.php @@ -128,6 +128,6 @@ class Themepack throw new Exceptions\IncompatibleThemeException("Theme is built for newer OVK (themeEngine" . $manifest->openvk_version . ")"); } - return new static($manifest->id, $manifest->version, (bool) ($manifest->inherit_master ?? true), (bool) ($manifest->override_templates ?? false), (bool) ($manifest->enabled ?? true), (object) $manifest->metadata); + return new Themepack($manifest->id, $manifest->version, (bool) ($manifest->inherit_master ?? true), (bool) ($manifest->override_templates ?? false), (bool) ($manifest->enabled ?? true), (object) $manifest->metadata); } } diff --git a/Web/Themes/Themepacks.php b/Web/Themes/Themepacks.php index 1907553a..7acc9dcd 100644 --- a/Web/Themes/Themepacks.php +++ b/Web/Themes/Themepacks.php @@ -88,25 +88,6 @@ class Themepacks implements \ArrayAccess /* /ArrayAccess */ - public function install(string $archivePath): bool - { - if (!file_exists($archivePath)) { - return false; - } - - $tmpDir = mkdir(tempnam(OPENVK_ROOT . "/tmp/themepack_artifacts/", "themex_")); - try { - $archive = new \CabArchive($archivePath); - $archive->extract($tmpDir); - - return $this->installUnpacked($tmpDir); - } catch (\Exception $e) { - return false; - } finally { - rmdir($tmpDir); - } - } - public function uninstall(string $id): bool { if (!isset($loadedThemepacks[$id])) { diff --git a/Web/Util/Bitmask.php b/Web/Util/Bitmask.php index c34fcdc6..c8fb7504 100644 --- a/Web/Util/Bitmask.php +++ b/Web/Util/Bitmask.php @@ -78,7 +78,7 @@ class Bitmask } elseif (gettype($key) === "int") { $this->setByOffset($key, $data); } else { - throw new TypeError("Key must be either offset (int) or a string index"); + throw new \TypeError("Key must be either offset (int) or a string index"); } return $this; @@ -89,7 +89,7 @@ class Bitmask if (gettype($key) === "string") { $key = $this->getOffsetByKey($key); } elseif (gettype($key) !== "int") { - throw new TypeError("Key must be either offset (int) or a string index"); + throw new \TypeError("Key must be either offset (int) or a string index"); } return $this->length === 1 ? $this->getBoolByOffset($key) : $this->getNumberByOffset($key); diff --git a/Web/Util/DateTime.php b/Web/Util/DateTime.php index 6a82fe88..102312ed 100644 --- a/Web/Util/DateTime.php +++ b/Web/Util/DateTime.php @@ -66,6 +66,7 @@ class DateTime case static::RELATIVE_FORMAT_LOWER: return $this->zmdate(); case static::RELATIVE_FORMAT_SHORT: + default: return ""; } } diff --git a/Web/Util/Makima/Makima.php b/Web/Util/Makima/Makima.php index 92bb277d..e903ad65 100644 --- a/Web/Util/Makima/Makima.php +++ b/Web/Util/Makima/Makima.php @@ -188,9 +188,9 @@ class Makima $tries = []; - $firstLine; - $secondLine; - $thirdLine; + $firstLine = null; + $secondLine = null; + $thirdLine = null; # Try one line: $tries[$firstLine = $count] = [$this->calculateMultiThumbsHeight($ratiosCropped, $maxWidth, $marginWidth)]; @@ -234,7 +234,7 @@ class Makima } } - if (!$optimalConfiguration || $confDigff < $optimalDifference) { + if (!$optimalConfiguration || $confDiff < $optimalDifference) { $optimalConfiguration = $config; $optimalDifference = $confDiff; } diff --git a/bootstrap.php b/bootstrap.php index 42241ca4..3fac8214 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -478,8 +478,8 @@ return (function () { define('YEAR', 365 * DAY); define("nullptr", null); - define("OPENVK_DEFAULT_INSTANCE_NAME", "OpenVK", false); - define("OPENVK_VERSION", "Altair Preview ($ver)", false); - define("OPENVK_DEFAULT_PER_PAGE", 10, false); - define("__OPENVK_ERROR_CLOCK_IN_FUTURE", "Server clock error: FK1200-DTF", false); + define("OPENVK_DEFAULT_INSTANCE_NAME", "OpenVK"); + define("OPENVK_VERSION", "Altair Preview ($ver)"); + define("OPENVK_DEFAULT_PER_PAGE", 10); + define("__OPENVK_ERROR_CLOCK_IN_FUTURE", "Server clock error: FK1200-DTF"); }); diff --git a/chandler_loader.php b/chandler_loader.php new file mode 100644 index 00000000..7566aecc --- /dev/null +++ b/chandler_loader.php @@ -0,0 +1,10 @@ +#!/usr/bin/env php +ignite(true); diff --git a/composer.json b/composer.json index 1020a4f7..8148e237 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,8 @@ { "scripts": { "fix": "php-cs-fixer fix", - "lint": "php-cs-fixer fix --dry-run --diff --verbose" + "lint": "php-cs-fixer fix --dry-run --diff --verbose", + "analyse": "phpstan analyse --memory-limit 1G" }, "require": { "php": "~7.3||~8.1", @@ -28,6 +29,7 @@ }, "minimum-stability": "beta", "require-dev": { - "friendsofphp/php-cs-fixer": "^3.68" + "friendsofphp/php-cs-fixer": "^3.68", + "phpstan/phpstan": "^2.1" } } diff --git a/composer.lock b/composer.lock index 3e7e2d72..9b27d119 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b92d2ddd207f394a31c429c65d1785a7", + "content-hash": "fe88a04383a75cc5c6591abac3128201", "packages": [ { "name": "al/emoji-detector", @@ -3092,6 +3092,64 @@ ], "time": "2025-01-30T17:00:50+00:00" }, + { + "name": "phpstan/phpstan", + "version": "2.1.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "7d08f569e582ade182a375c366cbd896eccadd3a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7d08f569e582ade182a375c366cbd896eccadd3a", + "reference": "7d08f569e582ade182a375c366cbd896eccadd3a", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-01-21T14:54:06+00:00" + }, { "name": "psr/event-dispatcher", "version": "1.0.0", diff --git a/openvkctl b/openvkctl index 1ed5f3be..39edfecf 100755 --- a/openvkctl +++ b/openvkctl @@ -7,9 +7,7 @@ namespace openvk; use Symfony\Component\Console\Application; -$_SERVER["HTTP_ACCEPT_LANGUAGE"] = false; -$bootstrap = require(__DIR__ . "/../../../chandler/Bootstrap.php"); -$bootstrap->ignite(true); +require(__DIR__ . "/chandler_loader.php"); $application = new Application(); $application->add(new CLI\RebuildImagesCommand()); diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..4831b315 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,14 @@ +parameters: + level: 0 + paths: + - CLI + - ServiceAPI + - VKAPI + - Web + - bootstrap.php + - openvkctl + - chandler_loader.php + + bootstrapFiles: + - chandler_loader.php + - bootstrap.php