openvk/Web/Models/Entities/Post.php
Alexander Minkin 6ec54a379d
feat: add linting of code (#1220)
* feat(lint): add php-cs-fixer for linting

Removing previous CODE_STYLE as it was not enforced anyway and using PER-CS 2.0.

This is not the reformatting commit.

* style: format code according to PER-CS 2.0 with php-cs-fixer

* ci(actions): add lint action

Resolves #1132.
2025-01-31 18:20:13 +03:00

532 lines
16 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use Chandler\Database\DatabaseConnection as DB;
use openvk\Web\Models\Repositories\{Clubs, Users};
use openvk\Web\Models\RowModel;
use openvk\Web\Models\Entities\Notifications\LikeNotification;
class Post extends Postable
{
use Traits\TRichText;
protected $tableName = "posts";
protected $upperNodeReferenceColumnName = "wall";
private function setLikeRecursively(bool $liked, User $user, int $depth): void
{
$searchData = [
"origin" => $user->getId(),
"model" => static::class,
"target" => $this->getRecord()->id,
];
if ((sizeof(DB::i()->getContext()->table("likes")->where($searchData)) > 0) !== $liked) {
if ($this->getOwner(false)->getId() !== $user->getId() && !($this->getOwner() instanceof Club) && !$this instanceof Comment) {
(new LikeNotification($this->getOwner(false), $this, $user))->emit();
}
parent::setLike($liked, $user);
}
if ($depth < ovkGetQuirk("wall.repost-liking-recursion-limit")) {
foreach ($this->getChildren() as $attachment) {
if ($attachment instanceof Post) {
$attachment->setLikeRecursively($liked, $user, $depth + 1);
}
}
}
}
/**
* May return fake owner (group), if flags are [1, (*)]
*
* @param bool $honourFlags - check flags
*/
public function getOwner(bool $honourFlags = true, bool $real = false): RowModel
{
if ($honourFlags && $this->isPostedOnBehalfOfGroup()) {
if ($this->getRecord()->wall < 0) {
return (new Clubs())->get(abs($this->getRecord()->wall));
}
}
return parent::getOwner($real);
}
public function getPrettyId(): string
{
return $this->getRecord()->wall . "_" . $this->getVirtualId();
}
public function getTargetWall(): int
{
return $this->getRecord()->wall;
}
public function getWallOwner()
{
$w = $this->getRecord()->wall;
if ($w < 0) {
return (new Clubs())->get(abs($w));
}
return (new Users())->get($w);
}
public function getRepostCount(): int
{
return sizeof(
$this->getRecord()
->related("attachments.attachable_id")
->where("attachable_type", get_class($this))
);
}
public function isPinned(): bool
{
return (bool) $this->getRecord()->pinned;
}
public function hasSource(): bool
{
return $this->getRecord()->source != null;
}
public function getSource(bool $format = false)
{
$orig_source = $this->getRecord()->source;
if (!str_contains($orig_source, "https://") && !str_contains($orig_source, "http://")) {
$orig_source = "https://" . $orig_source;
}
if (!$format) {
return $orig_source;
}
return $this->formatLinks($orig_source);
}
public function setSource(string $source)
{
$result = check_copyright_link($source);
$this->stateChanges("source", $source);
}
public function resetSource()
{
$this->stateChanges("source", null);
}
public function getVkApiCopyright(): object
{
return (object) [
'id' => 0,
'link' => $this->getSource(false),
'name' => $this->getSource(false),
'type' => 'link',
];
}
public function isAd(): bool
{
return (bool) $this->getRecord()->ad;
}
public function isPostedOnBehalfOfGroup(): bool
{
return ($this->getRecord()->flags & 0b10000000) > 0;
}
public function isSigned(): bool
{
return ($this->getRecord()->flags & 0b01000000) > 0;
}
public function isDeactivationMessage(): bool
{
return (($this->getRecord()->flags & 0b00100000) > 0) && ($this->getRecord()->owner > 0);
}
public function isUpdateAvatarMessage(): bool
{
return (($this->getRecord()->flags & 0b00010000) > 0) && ($this->getRecord()->owner > 0);
}
public function isExplicit(): bool
{
return (bool) $this->getRecord()->nsfw;
}
public function isDeleted(): bool
{
return (bool) $this->getRecord()->deleted;
}
public function getOwnerPost(): int
{
return $this->getOwner(false)->getId();
}
public 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 'windows_phone':
return 'wphone';
break;
case 'vika_touch': // кика хохотач ахахахаххахахахахах
case 'vk4me':
return 'mobile';
break;
case null:
return null;
break;
default:
return 'api';
break;
}
} else {
return $platform;
}
}
public 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,
];
}
public function getPostSourceInfo(): array
{
$post_source = ["type" => "vk"];
if ($this->getPlatform(true) !== null) {
$post_source = [
"type" => "api",
"platform" => $this->getPlatform(true),
];
}
if ($this->isUpdateAvatarMessage()) {
$post_source['data'] = 'profile_photo';
}
return $post_source;
}
public function getVkApiType(): string
{
$type = 'post';
if ($this->getSuggestionType() != 0) {
$type = 'suggest';
}
return $type;
}
public function pin(): void
{
DB::i()
->getContext()
->table("posts")
->where([
"wall" => $this->getTargetWall(),
"pinned" => true,
])
->update(["pinned" => false]);
$this->stateChanges("pinned", true);
$this->save();
}
public function unpin(): void
{
$this->stateChanges("pinned", false);
$this->save();
}
public function canBePinnedBy(User $user = null): bool
{
if (!$user) {
return false;
}
if ($this->getTargetWall() < 0) {
return (new Clubs())->get(abs($this->getTargetWall()))->canBeModifiedBy($user);
}
return $this->getTargetWall() === $user->getId();
}
public function canBeDeletedBy(User $user = null): bool
{
if (!$user) {
return false;
}
if ($this->getTargetWall() < 0 && !$this->getWallOwner()->canBeModifiedBy($user) && $this->getWallOwner()->getWallType() != 1 && $this->getSuggestionType() == 0) {
return false;
}
return $this->getOwnerPost() === $user->getId() || $this->canBePinnedBy($user);
}
public function setContent(string $content): void
{
if (ctype_space($content)) {
throw new \LengthException("Content length must be at least 1 character (not counting whitespaces).");
} elseif (iconv_strlen($content) > OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["maxSize"]) {
throw new \LengthException("Content is too large.");
}
$this->stateChanges("content", $content);
}
public function toggleLike(User $user): bool
{
$liked = parent::toggleLike($user);
if (!$user->isPrivateLikes() && $this->getOwner(false)->getId() !== $user->getId() && !($this->getOwner() instanceof Club) && !$this instanceof Comment) {
(new LikeNotification($this->getOwner(false), $this, $user))->emit();
}
foreach ($this->getChildren() as $attachment) {
if ($attachment instanceof Post) {
$attachment->setLikeRecursively($liked, $user, 2);
}
}
return $liked;
}
public function setLike(bool $liked, User $user): void
{
$this->setLikeRecursively($liked, $user, 1);
}
public function deletePost(): void
{
$this->setDeleted(1);
$this->unwire();
$this->save();
}
public function canBeViewedBy(?User $user = null): bool
{
if ($this->isDeleted()) {
return false;
}
return $this->getWallOwner()->canBeViewedBy($user);
}
public function getSuggestionType()
{
return $this->getRecord()->suggested;
}
public function getPageURL(): string
{
return "/wall" . $this->getPrettyId();
}
public function toNotifApiStruct()
{
$res = (object) [];
$res->id = $this->getVirtualId();
$res->to_id = $this->getOwner() instanceof Club ? $this->getOwner()->getId() * -1 : $this->getOwner()->getId();
$res->from_id = $res->to_id;
$res->date = $this->getPublicationTime()->timestamp();
$res->text = $this->getText(false);
$res->attachments = []; # todo
$res->copy_owner_id = null; # todo
$res->copy_post_id = null; # todo
return $res;
}
public function canBeEditedBy(?User $user = null): bool
{
if (!$user) {
return false;
}
if ($this->isDeactivationMessage() || $this->isUpdateAvatarMessage()) {
return false;
}
if ($this->getTargetWall() > 0) {
return $this->getPublicationTime()->timestamp() + WEEK > time() && $user->getId() == $this->getOwner(false)->getId();
} else {
if ($this->isPostedOnBehalfOfGroup()) {
return $this->getWallOwner()->canBeModifiedBy($user);
} else {
return $user->getId() == $this->getOwner(false)->getId();
}
}
return $user->getId() == $this->getOwner(false)->getId();
}
public function toRss(): \Bhaktaraz\RSSGenerator\Item
{
$domain = ovk_scheme(true) . $_SERVER["HTTP_HOST"];
$description = $this->getText(false);
$title = str_replace("\n", "", ovk_proc_strtr($description, 79));
$description_html = $description;
$url = $domain . "/wall" . $this->getPrettyId();
if ($this->isUpdateAvatarMessage()) {
$title = tr('upd_in_general');
}
if ($this->isDeactivationMessage()) {
$title = tr('post_deact_in_general');
}
$author = $this->getOwner();
$target_wall = $this->getWallOwner();
$author_name = escape_html($author->getCanonicalName());
if ($this->isExplicit()) {
$title = 'NSFW: ' . $title;
}
foreach ($this->getChildren() as $child) {
if ($child instanceof Photo) {
$child_page = $domain . $child->getPageURL();
$child_url = $child->getURL();
$description_html .= "<br /><a href='$child_page'><img src='$child_url'></a><br />";
} elseif ($child instanceof Video) {
$child_page = $domain . '/video' . $child->getPrettyId();
if ($child->getType() != 1) {
$description_html .= "" .
"<br />" .
"<video width=\"320\" height=\"240\" controls><source src=\"" . $child->getURL() . "\" type=\"video/mp4\"></video><br />" .
"<b>" . escape_html($child->getName()) . "</b><br />";
} else {
$description_html .= "" .
"<br />" .
"<a href=\"" . $child->getVideoDriver()->getURL() . "\"><b>" . escape_html($child->getName()) . "</b></a><br />";
}
} elseif ($child instanceof Audio) {
if (!$child->isWithdrawn()) {
$description_html .= "<br />"
. "<b>" . escape_html($child->getName()) . "</b>:"
. "<br />"
. "<audio controls>"
. "<source src=\"" . $child->getOriginalURL() . "\" type=\"audio/mpeg\"></audio>"
. "<br />";
}
} elseif ($child instanceof Poll) {
$description_html .= "<br />" . tr('poll') . ": " . escape_html($child->getTitle());
} elseif ($child instanceof Note) {
$description_html .= "<br />" . tr('note') . ": " . escape_html($child->getName());
}
}
$description_html .= "<br />" . tr('author') . ": <img width='15px' src='" . $author->getAvatarURL() . "'><a href='" . $author->getURL() . "'>" . $author_name . "</a>";
if ($target_wall->getRealId() != $author->getRealId()) {
$description_html .= "<br />" . tr('on_wall') . ": <img width='15px' src='" . $target_wall->getAvatarURL() . "'><a href='" . $target_wall->getURL() . "'>" . escape_html($target_wall->getCanonicalName()) . "</a>";
}
if ($this->isSigned()) {
$signer = $this->getOwner(false);
$description_html .= "<br />" . tr('sign_short') . ": <img width='15px' src='" . $signer->getAvatarURL() . "'><a href='" . $signer->getURL() . "'>" . escape_html($signer->getCanonicalName()) . "</a>";
}
if ($this->hasSource()) {
$description_html .= "<br />" . tr('source') . ": " . escape_html($this->getSource());
}
$item = new \Bhaktaraz\RSSGenerator\Item();
$item->title($title)
->url($url)
->guid($url)
->creator($author_name)
->pubDate($this->getPublicationTime()->timestamp())
->content(str_replace("\n", "<br />", $description_html));
return $item;
}
public function getGeo(): ?object
{
if (!$this->getRecord()->geo) {
return null;
}
return (object) json_decode($this->getRecord()->geo, true, JSON_UNESCAPED_UNICODE);
}
public function setGeo($encoded_object): void
{
$final_geo = $encoded_object['name'];
$neutral_names = ["Россия", "Russia", "Росія", "Россія", "Украина", "Ukraine", "Україна", "Украіна"];
foreach ($neutral_names as $name) {
if (str_contains($final_geo, $name . ", ")) {
$final_geo = str_replace($name . ", ", "", $final_geo);
}
}
$encoded_object['name'] = ovk_proc_strtr($final_geo, 255);
$encoded = json_encode($encoded_object);
$this->stateChanges("geo", $encoded);
}
public function getLat(): ?float
{
return (float) $this->getRecord()->geo_lat ?? null;
}
public function getLon(): ?float
{
return (float) $this->getRecord()->geo_lon ?? null;
}
public function getVkApiGeo(): object
{
return (object) [
'type' => 'point',
'coordinates' => $this->getLat() . ',' . $this->getLon(),
'name' => $this->getGeo()->name,
];
}
}