Merge branch 'master' into post_source

This commit is contained in:
lalka2018 2023-10-06 14:32:24 +03:00 committed by GitHub
commit 4ee3fcd154
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
155 changed files with 6476 additions and 595 deletions

140
DBEntity.updated.php Normal file
View file

@ -0,0 +1,140 @@
<?php declare(strict_types=1);
namespace Chandler\Database;
use Chandler\Database\DatabaseConnection;
use Nette\Database\Table\Selection;
use Nette\Database\Table\ActiveRow;
use Nette\InvalidStateException as ISE;
use openvk\Web\Models\Repositories\CurrentUser;
use openvk\Web\Models\Repositories\Logs;
abstract class DBEntity
{
protected $record;
protected $changes;
protected $deleted;
protected $user;
protected $tableName;
function __construct(?ActiveRow $row = NULL)
{
if(is_null($row)) return;
$_table = $row->getTable()->getName();
if($_table !== $this->tableName)
throw new ISE("Invalid data supplied for model: table $_table is not compatible with table" . $this->tableName);
$this->record = $row;
}
function __call(string $fName, array $args)
{
if(substr($fName, 0, 3) === "set") {
$field = mb_strtolower(substr($fName, 3));
$this->stateChanges($field, $args[0]);
} else {
throw new \Error("Call to undefined method " . get_class($this) . "::$fName");
}
}
private function getTable(): Selection
{
return DatabaseConnection::i()->getContext()->table($this->tableName);
}
protected function getRecord(): ?ActiveRow
{
return $this->record;
}
protected function stateChanges(string $column, $value): void
{
if(!is_null($this->record))
$t = $this->record->{$column}; #Test if column exists
$this->changes[$column] = $value;
}
function getId()
{
return $this->getRecord()->id;
}
function isDeleted(): bool
{
return (bool) $this->getRecord()->deleted;
}
function unwrap(): object
{
return (object) $this->getRecord()->toArray();
}
function delete(bool $softly = true): void
{
$user = CurrentUser::i()->getUser();
$user_id = is_null($user) ? (int) OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"] : $user->getId();
if(is_null($this->record))
throw new ISE("Can't delete a model, that hasn't been flushed to DB. Have you forgotten to call save() first?");
(new Logs)->create($user_id, $this->getTable()->getName(), get_class($this), 2, $this->record->toArray(), $this->changes);
if($softly) {
$this->record = $this->getTable()->where("id", $this->record->id)->update(["deleted" => true]);
} else {
$this->record->delete();
$this->deleted = true;
}
}
function undelete(): void
{
if(is_null($this->record))
throw new ISE("Can't undelete a model, that hasn't been flushed to DB. Have you forgotten to call save() first?");
$user = CurrentUser::i()->getUser();
$user_id = is_null($user) ? (int) OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"] : $user->getId();
(new Logs)->create($user_id, $this->getTable()->getName(), get_class($this), 3, $this->record->toArray(), ["deleted" => false]);
$this->getTable()->where("id", $this->record->id)->update(["deleted" => false]);
}
function save(?bool $log = true): void
{
if ($log) {
$user = CurrentUser::i();
$user_id = is_null($user) ? (int)OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"] : $user->getUser()->getId();
}
if(is_null($this->record)) {
$this->record = $this->getTable()->insert($this->changes);
if ($log && $this->getTable()->getName() !== "logs") {
(new Logs)->create($user_id, $this->getTable()->getName(), get_class($this), 0, $this->record->toArray(), $this->changes);
}
} else {
if ($log && $this->getTable()->getName() !== "logs") {
(new Logs)->create($user_id, $this->getTable()->getName(), get_class($this), 1, $this->record->toArray(), $this->changes);
}
if ($this->deleted) {
$this->record = $this->getTable()->insert((array)$this->record);
} else {
$this->getTable()->get($this->record->id)->update($this->changes);
$this->record = $this->getTable()->get($this->record->id);
}
}
$this->changes = [];
}
function getTableName(): string
{
return $this->getTable()->getName();
}
use \Nette\SmartObject;
}

92
ServiceAPI/Photos.php Normal file
View file

@ -0,0 +1,92 @@
<?php declare(strict_types=1);
namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\{Photos as PhotosRepo, Albums, Clubs};
class Photos implements Handler
{
protected $user;
protected $photos;
function __construct(?User $user)
{
$this->user = $user;
$this->photos = new PhotosRepo;
}
function getPhotos(int $page = 1, int $album = 0, callable $resolve, callable $reject)
{
if($album == 0) {
$photos = $this->photos->getEveryUserPhoto($this->user, $page, 24);
$count = $this->photos->getUserPhotosCount($this->user);
} else {
$album = (new Albums)->get($album);
if(!$album || $album->isDeleted())
$reject(55, "Invalid .");
if($album->getOwner() instanceof User) {
if($album->getOwner()->getId() != $this->user->getId())
$reject(555, "Access to album denied");
} else {
if(!$album->getOwner()->canBeModifiedBy($this->user))
$reject(555, "Access to album denied");
}
$photos = $album->getPhotos($page, 24);
$count = $album->size();
}
$arr = [
"count" => $count,
"items" => [],
];
foreach($photos as $photo) {
$res = json_decode(json_encode($photo->toVkApiStruct()), true);
$arr["items"][] = $res;
}
$resolve($arr);
}
function getAlbums(int $club, callable $resolve, callable $reject)
{
$albumsRepo = (new Albums);
$count = $albumsRepo->getUserAlbumsCount($this->user);
$albums = $albumsRepo->getUserAlbums($this->user, 1, $count);
$arr = [
"count" => $count,
"items" => [],
];
foreach($albums as $album) {
$res = ["id" => $album->getId(), "name" => $album->getName()];
$arr["items"][] = $res;
}
if($club > 0) {
$cluber = (new Clubs)->get($club);
if(!$cluber || !$cluber->canBeModifiedBy($this->user))
$reject(1337, "Invalid (club), or you can't modify him");
$clubCount = (new Albums)->getClubAlbumsCount($cluber);
$clubAlbums = (new Albums)->getClubAlbums($cluber, 1, $clubCount);
foreach($clubAlbums as $albumr) {
$res = ["id" => $albumr->getId(), "name" => $albumr->getName()];
$arr["items"][] = $res;
}
$arr["count"] = $arr["count"] + $clubCount;
}
$resolve($arr);
}
}

View file

@ -2,7 +2,7 @@
namespace openvk\ServiceAPI; namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\Post; use openvk\Web\Models\Entities\Post;
use openvk\Web\Models\Entities\User; use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\{Posts, Notes}; use openvk\Web\Models\Repositories\{Posts, Notes, Videos};
class Wall implements Handler class Wall implements Handler
{ {
@ -15,6 +15,7 @@ class Wall implements Handler
$this->user = $user; $this->user = $user;
$this->posts = new Posts; $this->posts = new Posts;
$this->notes = new Notes; $this->notes = new Notes;
$this->videos = new Videos;
} }
function getPost(int $id, callable $resolve, callable $reject): void function getPost(int $id, callable $resolve, callable $reject): void
@ -95,4 +96,45 @@ class Wall implements Handler
$resolve($arr); $resolve($arr);
} }
function getVideos(int $page = 1, callable $resolve, callable $reject)
{
$videos = $this->videos->getByUser($this->user, $page, 8);
$count = $this->videos->getUserVideosCount($this->user);
$arr = [
"count" => $count,
"items" => [],
];
foreach($videos as $video) {
$res = json_decode(json_encode($video->toVkApiStruct()), true);
$res["video"]["author_name"] = $video->getOwner()->getCanonicalName();
$arr["items"][] = $res;
}
$resolve($arr);
}
function searchVideos(int $page = 1, string $query, callable $resolve, callable $reject)
{
$dbc = $this->videos->find($query);
$videos = $dbc->page($page, 8);
$count = $dbc->size();
$arr = [
"count" => $count,
"items" => [],
];
foreach($videos as $video) {
$res = json_decode(json_encode($video->toVkApiStruct()), true);
$res["video"]["author_name"] = $video->getOwner()->getCanonicalName();
$arr["items"][] = $res;
}
$resolve($arr);
}
} }

View file

@ -211,7 +211,7 @@ final class Notes extends VKAPIRequestHandler
$items = []; $items = [];
$note = (new NotesRepo)->getNoteById((int)$id[0], (int)$id[1]); $note = (new NotesRepo)->getNoteById((int)$id[0], (int)$id[1]);
if($note) { if($note && !$note->isDeleted()) {
$nodez->notes[] = $note->toVkApiStruct(); $nodez->notes[] = $note->toVkApiStruct();
} }
} }

View file

@ -480,28 +480,25 @@ final class Wall extends VKAPIRequestHandler
if($attachmentType == "photo") { if($attachmentType == "photo") {
$attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); $attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted()) if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Photo does not exists"); $this->fail(100, "Invalid photo");
if($attacc->getOwner()->getId() != $this->getUser()->getId()) if(!$attacc->getOwner()->getPrivacyPermission('photos.read', $this->getUser()))
$this->fail(43, "You do not have access to this photo"); $this->fail(43, "Access to photo denied");
$post->attach($attacc); $post->attach($attacc);
} elseif($attachmentType == "video") { } elseif($attachmentType == "video") {
$attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); $attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted()) if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Video does not exists"); $this->fail(100, "Video does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId()) if(!$attacc->getOwner()->getPrivacyPermission('videos.read', $this->getUser()))
$this->fail(43, "You do not have access to this video"); $this->fail(43, "Access to video denied");
$post->attach($attacc); $post->attach($attacc);
} elseif($attachmentType == "note") { } elseif($attachmentType == "note") {
$attacc = (new NotesRepo)->getNoteById($attachmentOwner, $attachmentId); $attacc = (new NotesRepo)->getNoteById($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted()) if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Note does not exist"); $this->fail(100, "Note does not exist");
if($attacc->getOwner()->getId() != $this->getUser()->getId()) if(!$attacc->getOwner()->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(43, "You do not have access to this note"); $this->fail(11, "Access to note denied");
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); $post->attach($attacc);
} }
@ -695,7 +692,7 @@ final class Wall extends VKAPIRequestHandler
return $response; return $response;
} }
function createComment(int $owner_id, int $post_id, string $message, int $from_group = 0, string $attachments = "") { function createComment(int $owner_id, int $post_id, string $message = "", int $from_group = 0, string $attachments = "") {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
@ -753,16 +750,16 @@ final class Wall extends VKAPIRequestHandler
$attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); $attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted()) if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Photo does not exists"); $this->fail(100, "Photo does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId()) if(!$attacc->getOwner()->getPrivacyPermission('photos.read', $this->getUser()))
$this->fail(43, "You do not have access to this photo"); $this->fail(11, "Access to photo denied");
$comment->attach($attacc); $comment->attach($attacc);
} elseif($attachmentType == "video") { } elseif($attachmentType == "video") {
$attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); $attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted()) if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Video does not exists"); $this->fail(100, "Video does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId()) if(!$attacc->getOwner()->getPrivacyPermission('videos.read', $this->getUser()))
$this->fail(43, "You do not have access to this video"); $this->fail(11, "Access to video denied");
$comment->attach($attacc); $comment->attach($attacc);
} }

View file

@ -306,11 +306,14 @@ class Application extends RowModel
function delete(bool $softly = true): void function delete(bool $softly = true): void
{ {
if($softly) if($softly)
throw new \UnexpectedValueException("Can't delete apps softly."); throw new \UnexpectedValueException("Can't delete apps softly."); // why
$cx = DatabaseConnection::i()->getContext(); $cx = DatabaseConnection::i()->getContext();
$cx->table("app_users")->where("app", $this->getId())->delete(); $cx->table("app_users")->where("app", $this->getId())->delete();
parent::delete(false); parent::delete(false);
} }
function getPublicationTime(): string
{ return tr("recently"); }
} }

View file

@ -0,0 +1,66 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use openvk\Web\Models\RowModel;
use openvk\Web\Util\DateTime;
use openvk\Web\Models\Repositories\{Users};
use Nette\Database\Table\ActiveRow;
class Ban extends RowModel
{
protected $tableName = "bans";
function getId(): int
{
return $this->getRecord()->id;
}
function getReason(): ?string
{
return $this->getRecord()->reason;
}
function getUser(): ?User
{
return (new Users)->get($this->getRecord()->user);
}
function getInitiator(): ?User
{
return (new Users)->get($this->getRecord()->initiator);
}
function getStartTime(): int
{
return $this->getRecord()->iat;
}
function getEndTime(): int
{
return $this->getRecord()->exp;
}
function getTime(): int
{
return $this->getRecord()->time;
}
function isPermanent(): bool
{
return $this->getEndTime() === 0;
}
function isRemovedManually(): bool
{
return (bool) $this->getRecord()->removed_manually;
}
function isOver(): bool
{
return $this->isRemovedManually();
}
function whoRemoved(): ?User
{
return (new Users)->get($this->getRecord()->removed_by);
}
}

View file

@ -224,7 +224,7 @@ class Club extends RowModel
"shape" => "spline", "shape" => "spline",
"color" => "#597da3", "color" => "#597da3",
], ],
"name" => $unique ? "Полный охват" : "Все просмотры", "name" => $unique ? tr("full_coverage") : tr("all_views"),
], ],
"subs" => [ "subs" => [
"x" => array_reverse(range(1, 7)), "x" => array_reverse(range(1, 7)),
@ -235,7 +235,7 @@ class Club extends RowModel
"color" => "#b05c91", "color" => "#b05c91",
], ],
"fill" => "tozeroy", "fill" => "tozeroy",
"name" => $unique ? "Охват подписчиков" : "Просмотры подписчиков", "name" => $unique ? tr("subs_coverage") : tr("subs_views"),
], ],
"viral" => [ "viral" => [
"x" => array_reverse(range(1, 7)), "x" => array_reverse(range(1, 7)),
@ -246,7 +246,7 @@ class Club extends RowModel
"color" => "#4d9fab", "color" => "#4d9fab",
], ],
"fill" => "tozeroy", "fill" => "tozeroy",
"name" => $unique ? "Виральный охват" : "Виральные просмотры", "name" => $unique ? tr("viral_coverage") : tr("viral_views"),
], ],
]; ];
} }
@ -272,7 +272,7 @@ class Club extends RowModel
return false; return false;
} }
return $query; return $query->group("follower");
} }
function getFollowersCount(): int function getFollowersCount(): int
@ -355,6 +355,18 @@ class Club extends RowModel
return $this->getRecord()->website; return $this->getRecord()->website;
} }
function ban(string $reason): void
{
$this->setBlock_Reason($reason);
$this->save();
}
function unban(): void
{
$this->setBlock_Reason(null);
$this->save();
}
function getAlert(): ?string function getAlert(): ?string
{ {
return $this->getRecord()->alert; return $this->getRecord()->alert;

View file

@ -11,7 +11,7 @@ class Comment extends Post
function getPrettyId(): string function getPrettyId(): string
{ {
return $this->getRecord()->id; return (string)$this->getRecord()->id;
} }
function getVirtualId(): int function getVirtualId(): int
@ -85,4 +85,17 @@ class Comment extends Post
} }
return $res; return $res;
} }
function getURL(): string
{
return "/wall" . $this->getTarget()->getPrettyId() . "#_comment" . $this->getId();
}
function canBeEditedBy(?User $user = NULL): bool
{
if(!$user)
return false;
return $user->getId() == $this->getOwner(false)->getId();
}
} }

View file

@ -92,7 +92,7 @@ class IP extends RowModel
$this->stateChanges("rate_limit_counter", $aCounter); $this->stateChanges("rate_limit_counter", $aCounter);
$this->stateChanges("rate_limit_violation_counter_start", $vCounterSessionStart); $this->stateChanges("rate_limit_violation_counter_start", $vCounterSessionStart);
$this->stateChanges("rate_limit_violation_counter", $vCounter); $this->stateChanges("rate_limit_violation_counter", $vCounter);
$this->save(); $this->save(false);
} }
} }
@ -105,11 +105,11 @@ class IP extends RowModel
$this->stateChanges("ip", $ip); $this->stateChanges("ip", $ip);
} }
function save(): void function save(?bool $log = false): void
{ {
if(is_null($this->getRecord())) if(is_null($this->getRecord()))
$this->stateChanges("first_seen", time()); $this->stateChanges("first_seen", time());
parent::save(); parent::save($log);
} }
} }

View file

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

View file

@ -0,0 +1,71 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use openvk\Web\Models\RowModel;
use openvk\Web\Util\DateTime;
use openvk\Web\Models\Repositories\{Users};
use Nette\Database\Table\ActiveRow;
class NoSpamLog extends RowModel
{
protected $tableName = "noSpam_templates";
function getId(): int
{
return $this->getRecord()->id;
}
function getUser(): ?User
{
return (new Users)->get($this->getRecord()->user);
}
function getModel(): string
{
return $this->getRecord()->model;
}
function getRegex(): ?string
{
return $this->getRecord()->regex;
}
function getRequest(): ?string
{
return $this->getRecord()->request;
}
function getCount(): int
{
return $this->getRecord()->count;
}
function getTime(): DateTime
{
return new DateTime($this->getRecord()->time);
}
function getItems(): ?array
{
return explode(",", $this->getRecord()->items);
}
function getTypeRaw(): int
{
return $this->getRecord()->ban_type;
}
function getType(): string
{
switch ($this->getTypeRaw()) {
case 1: return "О";
case 2: return "Б";
case 3: return "ОБ";
default: return (string) $this->getTypeRaw();
}
}
function isRollbacked(): bool
{
return !is_null($this->getRecord()->rollback);
}
}

View file

@ -124,7 +124,7 @@ class Note extends Postable
$res = (object) []; $res = (object) [];
$res->type = "note"; $res->type = "note";
$res->id = $this->getId(); $res->id = $this->getVirtualId();
$res->owner_id = $this->getOwner()->getId(); $res->owner_id = $this->getOwner()->getId();
$res->title = $this->getName(); $res->title = $this->getName();
$res->text = $this->getText(); $res->text = $this->getText();

View file

@ -271,5 +271,19 @@ class Post extends Postable
$this->save(); $this->save();
} }
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();
return $user->getId() == $this->getOwner(false)->getId();
}
use Traits\TRichText; use Traits\TRichText;
} }

View file

@ -35,6 +35,7 @@ abstract class Postable extends Attachable
if(!$real && $this->isAnonymous()) if(!$real && $this->isAnonymous())
$oid = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["account"]; $oid = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["account"];
$oid = abs($oid);
if($oid > 0) if($oid > 0)
return (new Users)->get($oid); return (new Users)->get($oid);
else else
@ -84,7 +85,7 @@ abstract class Postable extends Attachable
return sizeof(DB::i()->getContext()->table("likes")->where([ return sizeof(DB::i()->getContext()->table("likes")->where([
"model" => static::class, "model" => static::class,
"target" => $this->getRecord()->id, "target" => $this->getRecord()->id,
])); ])->group("origin"));
} }
# TODO add pagination # TODO add pagination
@ -151,7 +152,7 @@ abstract class Postable extends Attachable
throw new ISE("Setting virtual id manually is forbidden"); throw new ISE("Setting virtual id manually is forbidden");
} }
function save(): void function save(?bool $log = false): void
{ {
$vref = $this->upperNodeReferenceColumnName; $vref = $this->upperNodeReferenceColumnName;
@ -166,11 +167,11 @@ abstract class Postable extends Attachable
$this->stateChanges("created", time()); $this->stateChanges("created", time());
$this->stateChanges("virtual_id", $pCount + 1); $this->stateChanges("virtual_id", $pCount + 1);
} else { } /*else {
$this->stateChanges("edited", time()); $this->stateChanges("edited", time());
} }*/
parent::save(); parent::save($log);
} }
use Traits\TAttachmentHost; use Traits\TAttachmentHost;

View file

@ -0,0 +1,154 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use openvk\Web\Util\DateTime;
use Nette\Database\Table\ActiveRow;
use openvk\Web\Models\RowModel;
use openvk\Web\Models\Entities\Club;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Repositories\{Applications, Comments, Notes, Reports, Users, Posts, Photos, Videos, Clubs};
use Chandler\Database\DatabaseConnection as DB;
use Nette\InvalidStateException as ISE;
use Nette\Database\Table\Selection;
class Report extends RowModel
{
protected $tableName = "reports";
function getId(): int
{
return $this->getRecord()->id;
}
function getStatus(): int
{
return $this->getRecord()->status;
}
function getContentType(): string
{
return $this->getRecord()->type;
}
function getReason(): string
{
return $this->getRecord()->reason;
}
function getTime(): DateTime
{
return new DateTime($this->getRecord()->date);
}
function isDeleted(): bool
{
if ($this->getRecord()->deleted === 0)
{
return false;
} elseif ($this->getRecord()->deleted === 1) {
return true;
}
}
function authorId(): int
{
return $this->getRecord()->user_id;
}
function getUser(): User
{
return (new Users)->get((int) $this->getRecord()->user_id);
}
function getContentId(): int
{
return (int) $this->getRecord()->target_id;
}
function getContentObject()
{
if ($this->getContentType() == "post") return (new Posts)->get($this->getContentId());
else if ($this->getContentType() == "photo") return (new Photos)->get($this->getContentId());
else if ($this->getContentType() == "video") return (new Videos)->get($this->getContentId());
else if ($this->getContentType() == "group") return (new Clubs)->get($this->getContentId());
else if ($this->getContentType() == "comment") return (new Comments)->get($this->getContentId());
else if ($this->getContentType() == "note") return (new Notes)->get($this->getContentId());
else if ($this->getContentType() == "app") return (new Applications)->get($this->getContentId());
else if ($this->getContentType() == "user") return (new Users)->get($this->getContentId());
else return null;
}
function getAuthor(): RowModel
{
return (new Posts)->get($this->getContentId())->getOwner();
}
function getReportAuthor(): User
{
return (new Users)->get($this->getRecord()->user_id);
}
function banUser($initiator)
{
$reason = $this->getContentType() !== "user" ? ("**content-" . $this->getContentType() . "-" . $this->getContentId() . "**") : ("Подозрительная активность");
$this->getAuthor()->ban($reason, false, time() + $this->getAuthor()->getNewBanTime(), $initiator);
}
function deleteContent()
{
if ($this->getContentType() !== "user") {
$pubTime = $this->getContentObject()->getPublicationTime();
if (method_exists($this->getContentObject(), "getName")) {
$name = $this->getContentObject()->getName();
$placeholder = "$pubTime ($name)";
} else {
$placeholder = "$pubTime";
}
if ($this->getAuthor() instanceof Club) {
$name = $this->getAuthor()->getName();
$this->getAuthor()->getOwner()->adminNotify("Ваш контент, который опубликовали $placeholder в созданной вами группе \"$name\" был удалён модераторами инстанса. За повторные или серьёзные нарушения группу могут заблокировать.");
} else {
$this->getAuthor()->adminNotify("Ваш контент, который вы опубликовали $placeholder был удалён модераторами инстанса. За повторные или серьёзные нарушения вас могут заблокировать.");
}
$this->getContentObject()->delete($this->getContentType() !== "app");
}
$this->delete();
}
function getDuplicates(): \Traversable
{
return (new Reports)->getDuplicates($this->getContentType(), $this->getContentId(), $this->getId());
}
function getDuplicatesCount(): int
{
return count(iterator_to_array($this->getDuplicates()));
}
function hasDuplicates(): bool
{
return $this->getDuplicatesCount() > 0;
}
function getContentName(): string
{
if (method_exists($this->getContentObject(), "getCanonicalName"))
return $this->getContentObject()->getCanonicalName();
return $this->getContentType() . " #" . $this->getContentId();
}
public function delete(bool $softly = true): void
{
if ($this->hasDuplicates()) {
foreach ($this->getDuplicates() as $duplicate) {
$duplicate->setDeleted(1);
$duplicate->save();
}
}
$this->setDeleted(1);
$this->save();
}
}

View file

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

View file

@ -5,7 +5,7 @@ use openvk\Web\Themes\{Themepack, Themepacks};
use openvk\Web\Util\DateTime; use openvk\Web\Util\DateTime;
use openvk\Web\Models\RowModel; use openvk\Web\Models\RowModel;
use openvk\Web\Models\Entities\{Photo, Message, Correspondence, Gift}; use openvk\Web\Models\Entities\{Photo, Message, Correspondence, Gift};
use openvk\Web\Models\Repositories\{Photos, Users, Clubs, Albums, Gifts, Notifications}; use openvk\Web\Models\Repositories\{Applications, Bans, Comments, Notes, Posts, Users, Clubs, Albums, Gifts, Notifications, Videos, Photos};
use openvk\Web\Models\Exceptions\InvalidUserNameException; use openvk\Web\Models\Exceptions\InvalidUserNameException;
use Nette\Database\Table\ActiveRow; use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection; use Chandler\Database\DatabaseConnection;
@ -39,10 +39,13 @@ class User extends RowModel
$query = "SELECT id FROM\n" . file_get_contents(__DIR__ . "/../sql/$filename.tsql"); $query = "SELECT id FROM\n" . file_get_contents(__DIR__ . "/../sql/$filename.tsql");
$query .= "\n LIMIT " . $limit . " OFFSET " . ( ($page - 1) * $limit ); $query .= "\n LIMIT " . $limit . " OFFSET " . ( ($page - 1) * $limit );
$ids = [];
$rels = DatabaseConnection::i()->getConnection()->query($query, $id, $id); $rels = DatabaseConnection::i()->getConnection()->query($query, $id, $id);
foreach($rels as $rel) { foreach($rels as $rel) {
$rel = (new Users)->get($rel->id); $rel = (new Users)->get($rel->id);
if(!$rel) continue; if(!$rel) continue;
if(in_array($rel->getId(), $ids)) continue;
$ids[] = $rel->getId();
yield $rel; yield $rel;
} }
@ -238,11 +241,60 @@ class User extends RowModel
return $this->getRecord()->alert; return $this->getRecord()->alert;
} }
function getBanReason(): ?string function getTextForContentBan(string $type): string
{
switch ($type) {
case "post": return "за размещение от Вашего лица таких <b>записей</b>:";
case "photo": return "за размещение от Вашего лица таких <b>фотографий</b>:";
case "video": return "за размещение от Вашего лица таких <b>видеозаписей</b>:";
case "group": return "за подозрительное вступление от Вашего лица <b>в группу:</b>";
case "comment": return "за размещение от Вашего лица таких <b>комментариев</b>:";
case "note": return "за размещение от Вашего лица таких <b>заметок</b>:";
case "app": return "за создание от Вашего имени <b>подозрительных приложений</b>.";
default: return "за размещение от Вашего лица такого <b>контента</b>:";
}
}
function getRawBanReason(): ?string
{ {
return $this->getRecord()->block_reason; return $this->getRecord()->block_reason;
} }
function getBanReason(?string $for = null)
{
$ban = (new Bans)->get((int) $this->getRecord()->block_reason);
if (!$ban || $ban->isOver()) return null;
$reason = $ban->getReason();
preg_match('/\*\*content-(post|photo|video|group|comment|note|app|noSpamTemplate|user)-(\d+)\*\*$/', $reason, $matches);
if (sizeof($matches) === 3) {
$content_type = $matches[1]; $content_id = (int) $matches[2];
if (in_array($content_type, ["noSpamTemplate", "user"])) {
$reason = "Подозрительная активность";
} else {
if ($for !== "banned") {
$reason = "Подозрительная активность";
} else {
$reason = [$this->getTextForContentBan($content_type), $content_type];
switch ($content_type) {
case "post": $reason[] = (new Posts)->get($content_id); break;
case "photo": $reason[] = (new Photos)->get($content_id); break;
case "video": $reason[] = (new Videos)->get($content_id); break;
case "group": $reason[] = (new Clubs)->get($content_id); break;
case "comment": $reason[] = (new Comments)->get($content_id); break;
case "note": $reason[] = (new Notes)->get($content_id); break;
case "app": $reason[] = (new Applications)->get($content_id); break;
case "user": $reason[] = (new Users)->get($content_id); break;
default: $reason[] = null;
}
}
}
}
return $reason;
}
function getBanInSupportReason(): ?string function getBanInSupportReason(): ?string
{ {
return $this->getRecord()->block_in_support_reason; return $this->getRecord()->block_in_support_reason;
@ -410,6 +462,7 @@ class User extends RowModel
"news", "news",
"links", "links",
"poster", "poster",
"apps"
], ],
])->get($id); ])->get($id);
} }
@ -830,7 +883,7 @@ class User extends RowModel
]); ]);
} }
function ban(string $reason, bool $deleteSubscriptions = true, ?int $unban_time = NULL): void function ban(string $reason, bool $deleteSubscriptions = true, $unban_time = NULL, ?int $initiator = NULL): void
{ {
if($deleteSubscriptions) { if($deleteSubscriptions) {
$subs = DatabaseConnection::i()->getContext()->table("subscriptions"); $subs = DatabaseConnection::i()->getContext()->table("subscriptions");
@ -843,8 +896,33 @@ class User extends RowModel
$subs->delete(); $subs->delete();
} }
$this->setBlock_Reason($reason); $iat = time();
$this->setUnblock_time($unban_time); $ban = new Ban;
$ban->setUser($this->getId());
$ban->setReason($reason);
$ban->setInitiator($initiator);
$ban->setIat($iat);
$ban->setExp($unban_time !== "permanent" ? $unban_time : 0);
$ban->setTime($unban_time === "permanent" ? 0 : ($unban_time ? ($unban_time - $iat) : 0));
$ban->save();
$this->setBlock_Reason($ban->getId());
// $this->setUnblock_time($unban_time);
$this->save();
}
function unban(int $removed_by): void
{
$ban = (new Bans)->get((int) $this->getRawBanReason());
if (!$ban || $ban->isOver())
return;
$ban->setRemoved_Manually(true);
$ban->setRemoved_By($removed_by);
$ban->save();
$this->setBlock_Reason(NULL);
// $user->setUnblock_time(NULL);
$this->save(); $this->save();
} }
@ -949,6 +1027,7 @@ class User extends RowModel
"news", "news",
"links", "links",
"poster", "poster",
"apps"
], ],
])->set($id, (int) $status)->toInteger(); ])->set($id, (int) $status)->toInteger();
@ -1013,7 +1092,7 @@ class User extends RowModel
{ {
$this->setOnline(time()); $this->setOnline(time());
$this->setClient_name($platform); $this->setClient_name($platform);
$this->save(); $this->save(false);
return true; return true;
} }
@ -1031,7 +1110,7 @@ class User extends RowModel
function adminNotify(string $message): bool function adminNotify(string $message): bool
{ {
$admId = OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"]; $admId = (int) OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"];
if(!$admId) if(!$admId)
return false; return false;
else if(is_null($admin = (new Users)->get($admId))) else if(is_null($admin = (new Users)->get($admId)))
@ -1096,7 +1175,11 @@ class User extends RowModel
function getUnbanTime(): ?string function getUnbanTime(): ?string
{ {
return !is_null($this->getRecord()->unblock_time) ? date('d.m.Y', $this->getRecord()->unblock_time) : NULL; $ban = (new Bans)->get((int) $this->getRecord()->block_reason);
if (!$ban || $ban->isOver() || $ban->isPermanent()) return null;
if ($this->canUnbanThemself()) return tr("today");
return date('d.m.Y', $ban->getEndTime());
} }
function canUnbanThemself(): bool function canUnbanThemself(): bool
@ -1104,10 +1187,40 @@ class User extends RowModel
if (!$this->isBanned()) if (!$this->isBanned())
return false; return false;
if ($this->getRecord()->unblock_time > time() || $this->getRecord()->unblock_time == 0) $ban = (new Bans)->get((int) $this->getRecord()->block_reason);
return false; if (!$ban || $ban->isOver() || $ban->isPermanent()) return false;
return true; return $ban->getEndTime() <= time() && !$ban->isPermanent();
}
function getNewBanTime()
{
$bans = iterator_to_array((new Bans)->getByUser($this->getid()));
if (!$bans || count($bans) === 0)
return 0;
$last_ban = end($bans);
if (!$last_ban) return 0;
if ($last_ban->isPermanent()) return "permanent";
$values = [0, 3600, 7200, 86400, 172800, 604800, 1209600, 3024000, 9072000];
$response = 0;
$i = 0;
foreach ($values as $value) {
$i++;
if ($last_ban->getTime() === 0 && $value === 0) continue;
if ($last_ban->getTime() < $value) {
$response = $value;
break;
} else if ($last_ban->getTime() >= $value) {
if ($i < count($values)) continue;
$response = "permanent";
break;
}
}
return $response;
} }
function toVkApiStruct(): object function toVkApiStruct(): object

View file

@ -0,0 +1,33 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use Chandler\Database\DatabaseConnection as DB;
use Nette\Database\Table\{ActiveRow, Selection};
use openvk\Web\Models\Entities\Ban;
class Bans
{
private $context;
private $bans;
function __construct()
{
$this->context = DB::i()->getContext();
$this->bans = $this->context->table("bans");
}
function toBan(?ActiveRow $ar): ?Ban
{
return is_null($ar) ? NULL : new Ban($ar);
}
function get(int $id): ?Ban
{
return $this->toBan($this->bans->get($id));
}
function getByUser(int $user_id): \Traversable
{
foreach ($this->bans->where("user", $user_id) as $ban)
yield new Ban($ban);
}
}

View file

@ -28,7 +28,8 @@ class ChandlerUsers
function getById(string $UUID): ?ChandlerUser function getById(string $UUID): ?ChandlerUser
{ {
return new ChandlerUser($this->users->where("id", $UUID)->fetch()); $user = $this->users->where("id", $UUID)->fetch();
return $user ? new ChandlerUser($user) : NULL;
} }
function getList(int $page = 1): \Traversable function getList(int $page = 1): \Traversable

View file

@ -0,0 +1,49 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use openvk\Web\Models\Entities\User;
class CurrentUser
{
private static $instance = null;
private $user;
private $ip;
private $useragent;
public function __construct(?User $user = NULL, ?string $ip = NULL, ?string $useragent = NULL)
{
if ($user)
$this->user = $user;
if ($ip)
$this->ip = $ip;
if ($useragent)
$this->useragent = $useragent;
}
public static function get($user, $ip, $useragent)
{
if (self::$instance === null) self::$instance = new self($user, $ip, $useragent);
return self::$instance;
}
public function getUser(): User
{
return $this->user;
}
public function getIP(): string
{
return $this->ip;
}
public function getUserAgent(): string
{
return $this->useragent;
}
public static function i()
{
return self::$instance;
}
}

View file

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

View file

@ -24,7 +24,7 @@ class IPs
if(!$res) { if(!$res) {
$res = new IP; $res = new IP;
$res->setIp($ip); $res->setIp($ip);
$res->save(); $res->save(false);
return $res; return $res;
} }

View file

@ -0,0 +1,34 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Entities\NoSpamLog;
use openvk\Web\Models\Entities\User;
use Nette\Database\Table\ActiveRow;
class NoSpamLogs
{
private $context;
private $noSpamLogs;
function __construct()
{
$this->context = DatabaseConnection::i()->getContext();
$this->noSpamLogs = $this->context->table("noSpam_templates");
}
private function toNoSpamLog(?ActiveRow $ar): ?NoSpamLog
{
return is_null($ar) ? NULL : new NoSpamLog($ar);
}
function get(int $id): ?NoSpamLog
{
return $this->toNoSpamLog($this->noSpamLogs->get($id));
}
function getList(array $filter = []): \Traversable
{
foreach ($this->noSpamLogs->where($filter)->order("`id` DESC") as $log)
yield new NoSpamLog($log);
}
}

View file

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

View file

@ -0,0 +1,67 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use openvk\Web\Models\Entities\Report;
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection;
class Reports
{
private $context;
private $reports;
function __construct()
{
$this->context = DatabaseConnection::i()->getContext();
$this->reports = $this->context->table("reports");
}
private function toReport(?ActiveRow $ar): ?Report
{
return is_null($ar) ? NULL : new Report($ar);
}
function getReports(int $state = 0, int $page = 1, ?string $type = NULL, ?bool $pagination = true): \Traversable
{
$filter = ["deleted" => 0];
if ($type) $filter["type"] = $type;
$reports = $this->reports->where($filter)->order("created DESC")->group("target_id, type");
if ($pagination)
$reports = $reports->page($page, 15);
foreach($reports as $t)
yield new Report($t);
}
function getReportsCount(int $state = 0): int
{
return sizeof($this->reports->where(["deleted" => 0, "type" => $state])->group("target_id, type"));
}
function get(int $id): ?Report
{
return $this->toReport($this->reports->get($id));
}
function getByContentId(int $id): ?Report
{
$post = $this->reports->where(["deleted" => 0, "content_id" => $id])->fetch();
if($post)
return new Report($post);
else
return null;
}
function getDuplicates(string $type, int $target_id, ?int $orig = NULL, ?int $user_id = NULL): \Traversable
{
$filter = ["deleted" => 0, "type" => $type, "target_id" => $target_id];
if ($orig) $filter[] = "id != $orig";
if ($user_id) $filter["user_id"] = $user_id;
foreach ($this->reports->where($filter) as $report)
yield new Report($report);
}
use \Nette\SmartObject;
}

View file

@ -44,9 +44,9 @@ class Users
return $alias->getUser(); return $alias->getUser();
} }
function getByChandlerUser(ChandlerUser $user): ?User function getByChandlerUser(?ChandlerUser $user): ?User
{ {
return $this->toUser($this->users->where("user", $user->getId())->fetch()); return $user ? $this->toUser($this->users->where("user", $user->getId())->fetch()) : NULL;
} }
function find(string $query, array $pars = [], string $sort = "id DESC"): Util\EntityStream function find(string $query, array $pars = [], string $sort = "id DESC"): Util\EntityStream

View file

@ -1,4 +1,4 @@
(SELECT follower AS __id FROM (SELECT DISTINCT(follower) AS __id FROM
(SELECT follower FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0 (SELECT follower FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0
LEFT JOIN LEFT JOIN
(SELECT target FROM subscriptions WHERE follower=? AND model="openvk\\Web\\Models\\Entities\\User") u1 (SELECT target FROM subscriptions WHERE follower=? AND model="openvk\\Web\\Models\\Entities\\User") u1

View file

@ -1,4 +1,4 @@
(SELECT follower AS __id FROM (SELECT DISTINCT(follower) AS __id FROM
(SELECT follower FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0 (SELECT follower FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0
INNER JOIN INNER JOIN
(SELECT target FROM subscriptions WHERE follower=? AND model="openvk\\Web\\Models\\Entities\\User") u1 (SELECT target FROM subscriptions WHERE follower=? AND model="openvk\\Web\\Models\\Entities\\User") u1

View file

@ -1,4 +1,4 @@
(SELECT follower AS __id FROM (SELECT DISTINCT(follower) AS __id FROM
(SELECT follower FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0 (SELECT follower FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0
INNER JOIN INNER JOIN
(SELECT target FROM subscriptions WHERE follower=? AND model="openvk\\Web\\Models\\Entities\\User") u1 (SELECT target FROM subscriptions WHERE follower=? AND model="openvk\\Web\\Models\\Entities\\User") u1

View file

@ -1,4 +1,4 @@
(SELECT target AS __id FROM (SELECT DISTINCT(target) AS __id FROM
(SELECT follower FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0 (SELECT follower FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0
RIGHT JOIN RIGHT JOIN
(SELECT target FROM subscriptions WHERE follower=? AND model="openvk\\Web\\Models\\Entities\\User") u1 (SELECT target FROM subscriptions WHERE follower=? AND model="openvk\\Web\\Models\\Entities\\User") u1

View file

@ -1,7 +1,9 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\Web\Presenters; namespace openvk\Web\Presenters;
use Chandler\Database\Log;
use Chandler\Database\Logs;
use openvk\Web\Models\Entities\{Voucher, Gift, GiftCategory, User, BannedLink}; use openvk\Web\Models\Entities\{Voucher, Gift, GiftCategory, User, BannedLink};
use openvk\Web\Models\Repositories\{ChandlerGroups, ChandlerUsers, Users, Clubs, Vouchers, Gifts, BannedLinks}; use openvk\Web\Models\Repositories\{Bans, ChandlerGroups, ChandlerUsers, Photos, Posts, Users, Clubs, Videos, Vouchers, Gifts, BannedLinks};
use Chandler\Database\DatabaseConnection; use Chandler\Database\DatabaseConnection;
final class AdminPresenter extends OpenVKPresenter final class AdminPresenter extends OpenVKPresenter
@ -12,6 +14,7 @@ final class AdminPresenter extends OpenVKPresenter
private $gifts; private $gifts;
private $bannedLinks; private $bannedLinks;
private $chandlerGroups; private $chandlerGroups;
private $logs;
function __construct(Users $users, Clubs $clubs, Vouchers $vouchers, Gifts $gifts, BannedLinks $bannedLinks, ChandlerGroups $chandlerGroups) function __construct(Users $users, Clubs $clubs, Vouchers $vouchers, Gifts $gifts, BannedLinks $bannedLinks, ChandlerGroups $chandlerGroups)
{ {
@ -21,6 +24,7 @@ final class AdminPresenter extends OpenVKPresenter
$this->gifts = $gifts; $this->gifts = $gifts;
$this->bannedLinks = $bannedLinks; $this->bannedLinks = $bannedLinks;
$this->chandlerGroups = $chandlerGroups; $this->chandlerGroups = $chandlerGroups;
$this->logs = DatabaseConnection::i()->getContext()->table("ChandlerLogs");
parent::__construct(); parent::__construct();
} }
@ -128,7 +132,8 @@ final class AdminPresenter extends OpenVKPresenter
$club->save(); $club->save();
break; break;
case "ban": case "ban":
$club->setBlock_reason($this->postParam("ban_reason")); $reason = mb_strlen(trim($this->postParam("ban_reason"))) > 0 ? $this->postParam("ban_reason") : NULL;
$club->setBlock_reason($reason);
$club->save(); $club->save();
break; break;
} }
@ -278,7 +283,7 @@ final class AdminPresenter extends OpenVKPresenter
$this->notFound(); $this->notFound();
$gift->delete(); $gift->delete();
$this->flashFail("succ", "Gift moved successfully", "This gift will now be in <b>Recycle Bin</b>."); $this->flashFail("succ", tr("admin_gift_moved_successfully"), tr("admin_gift_moved_to_recycle"));
break; break;
case "copy": case "copy":
case "move": case "move":
@ -297,7 +302,7 @@ final class AdminPresenter extends OpenVKPresenter
$catTo->addGift($gift); $catTo->addGift($gift);
$name = $catTo->getName(); $name = $catTo->getName();
$this->flash("succ", "Gift moved successfully", "This gift will now be in <b>$name</b>."); $this->flash("succ", tr("admin_gift_moved_successfully"), "This gift will now be in <b>$name</b>.");
$this->redirect("/admin/gifts/" . $catTo->getSlug() . "." . $catTo->getId() . "/"); $this->redirect("/admin/gifts/" . $catTo->getSlug() . "." . $catTo->getId() . "/");
break; break;
default: default:
@ -328,10 +333,10 @@ final class AdminPresenter extends OpenVKPresenter
$gift->setUsages((int) $this->postParam("usages")); $gift->setUsages((int) $this->postParam("usages"));
if(isset($_FILES["pic"]) && $_FILES["pic"]["error"] === UPLOAD_ERR_OK) { if(isset($_FILES["pic"]) && $_FILES["pic"]["error"] === UPLOAD_ERR_OK) {
if(!$gift->setImage($_FILES["pic"]["tmp_name"])) if(!$gift->setImage($_FILES["pic"]["tmp_name"]))
$this->flashFail("err", "Не удалось сохранить подарок", "Изображение подарка кривое."); $this->flashFail("err", tr("error_when_saving_gift"), tr("error_when_saving_gift_bad_image"));
} else if($gen) { } else if($gen) {
# If there's no gift pic but it's newly created # If there's no gift pic but it's newly created
$this->flashFail("err", "Не удалось сохранить подарок", "Пожалуйста, загрузите изображение подарка."); $this->flashFail("err", tr("error_when_saving_gift"), tr("error_when_saving_gift_no_image"));
} }
$gift->save(); $gift->save();
@ -355,13 +360,19 @@ final class AdminPresenter extends OpenVKPresenter
{ {
$this->assertNoCSRF(); $this->assertNoCSRF();
$unban_time = strtotime($this->queryParam("date")) ?: NULL; if (str_contains($this->queryParam("reason"), "*"))
exit(json_encode([ "error" => "Incorrect reason" ]));
$unban_time = strtotime($this->queryParam("date")) ?: "permanent";
$user = $this->users->get($id); $user = $this->users->get($id);
if(!$user) if(!$user)
exit(json_encode([ "error" => "User does not exist" ])); exit(json_encode([ "error" => "User does not exist" ]));
$user->ban($this->queryParam("reason"), true, $unban_time); if ($this->queryParam("incr"))
$unban_time = time() + $user->getNewBanTime();
$user->ban($this->queryParam("reason"), true, $unban_time, $this->user->identity->getId());
exit(json_encode([ "success" => true, "reason" => $this->queryParam("reason") ])); exit(json_encode([ "success" => true, "reason" => $this->queryParam("reason") ]));
} }
@ -373,8 +384,16 @@ final class AdminPresenter extends OpenVKPresenter
if(!$user) if(!$user)
exit(json_encode([ "error" => "User does not exist" ])); exit(json_encode([ "error" => "User does not exist" ]));
$ban = (new Bans)->get((int)$user->getRawBanReason());
if (!$ban || $ban->isOver())
exit(json_encode([ "error" => "User is not banned" ]));
$ban->setRemoved_Manually(true);
$ban->setRemoved_By($this->user->identity->getId());
$ban->save();
$user->setBlock_Reason(NULL); $user->setBlock_Reason(NULL);
$user->setUnblock_time(NULL); // $user->setUnblock_time(NULL);
$user->save(); $user->save();
exit(json_encode([ "success" => true ])); exit(json_encode([ "success" => true ]));
} }
@ -460,6 +479,14 @@ final class AdminPresenter extends OpenVKPresenter
$this->redirect("/admin/bannedLinks"); $this->redirect("/admin/bannedLinks");
} }
function renderBansHistory(int $user_id) :void
{
$user = (new Users)->get($user_id);
if (!$user) $this->notFound();
$this->template->bans = (new Bans)->getByUser($user_id);
}
function renderChandlerGroups(): void function renderChandlerGroups(): void
{ {
$this->template->groups = (new ChandlerGroups)->getList(); $this->template->groups = (new ChandlerGroups)->getList();
@ -550,4 +577,38 @@ final class AdminPresenter extends OpenVKPresenter
$this->redirect("/admin/users/id" . $user->getId()); $this->redirect("/admin/users/id" . $user->getId());
} }
function renderLogs(): void
{
$filter = [];
if ($this->queryParam("id")) {
$id = (int) $this->queryParam("id");
$filter["id"] = $id;
$this->template->id = $id;
}
if ($this->queryParam("type") !== NULL && $this->queryParam("type") !== "any") {
$type = in_array($this->queryParam("type"), [0, 1, 2, 3]) ? (int) $this->queryParam("type") : 0;
$filter["type"] = $type;
$this->template->type = $type;
}
if ($this->queryParam("uid")) {
$user = $this->queryParam("uid");
$filter["user"] = $user;
$this->template->user = $user;
}
if ($this->queryParam("obj_id")) {
$obj_id = (int) $this->queryParam("obj_id");
$filter["object_id"] = $obj_id;
$this->template->obj_id = $obj_id;
}
if ($this->queryParam("obj_type") !== NULL && $this->queryParam("obj_type") !== "any") {
$obj_type = "openvk\\Web\\Models\\Entities\\" . $this->queryParam("obj_type");
$filter["object_model"] = $obj_type;
$this->template->obj_type = $obj_type;
}
$this->template->logs = (new Logs)->search($filter);
$this->template->object_types = (new Logs)->getTypes();
}
} }

View file

@ -1,7 +1,7 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\Web\Presenters; namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{IP, User, PasswordReset, EmailVerification}; use openvk\Web\Models\Entities\{IP, User, PasswordReset, EmailVerification};
use openvk\Web\Models\Repositories\{IPs, Users, Restores, Verifications}; use openvk\Web\Models\Repositories\{Bans, IPs, Users, Restores, Verifications};
use openvk\Web\Models\Exceptions\InvalidUserNameException; use openvk\Web\Models\Exceptions\InvalidUserNameException;
use openvk\Web\Util\Validator; use openvk\Web\Util\Validator;
use Chandler\Session\Session; use Chandler\Session\Session;
@ -110,7 +110,7 @@ final class AuthPresenter extends OpenVKPresenter
$this->flashFail("err", tr("failed_to_register"), tr("user_already_exists")); $this->flashFail("err", tr("failed_to_register"), tr("user_already_exists"));
$user->setUser($chUser->getId()); $user->setUser($chUser->getId());
$user->save(); $user->save(false);
if(!is_null($referer)) { if(!is_null($referer)) {
$user->toggleSubscription($referer); $user->toggleSubscription($referer);
@ -131,6 +131,7 @@ final class AuthPresenter extends OpenVKPresenter
$this->authenticator->authenticate($chUser->getId()); $this->authenticator->authenticate($chUser->getId());
$this->redirect("/id" . $user->getId()); $this->redirect("/id" . $user->getId());
$user->save();
} }
} }
@ -345,9 +346,16 @@ final class AuthPresenter extends OpenVKPresenter
$this->flashFail("err", tr("error"), tr("forbidden")); $this->flashFail("err", tr("error"), tr("forbidden"));
$user = $this->users->get($this->user->id); $user = $this->users->get($this->user->id);
$ban = (new Bans)->get((int)$user->getRawBanReason());
if (!$ban || $ban->isOver() || $ban->isPermanent())
$this->flashFail("err", tr("error"), tr("forbidden"));
$ban->setRemoved_Manually(2);
$ban->setRemoved_By($this->user->identity->getId());
$ban->save();
$user->setBlock_Reason(NULL); $user->setBlock_Reason(NULL);
$user->setUnblock_Time(NULL); // $user->setUnblock_Time(NULL);
$user->save(); $user->save();
$this->flashFail("succ", tr("banned_unban_title"), tr("banned_unban_description")); $this->flashFail("succ", tr("banned_unban_title"), tr("banned_unban_description"));

View file

@ -3,6 +3,8 @@ namespace openvk\Web\Presenters;
final class BlobPresenter extends OpenVKPresenter final class BlobPresenter extends OpenVKPresenter
{ {
protected $banTolerant = true;
private function getDirName($dir): string private function getDirName($dir): string
{ {
if(gettype($dir) === "integer") { if(gettype($dir) === "integer") {

View file

@ -2,7 +2,7 @@
namespace openvk\Web\Presenters; namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Comment, Notifications\MentionNotification, Photo, Video, User, Topic, Post}; use openvk\Web\Models\Entities\{Comment, Notifications\MentionNotification, Photo, Video, User, Topic, Post};
use openvk\Web\Models\Entities\Notifications\CommentNotification; use openvk\Web\Models\Entities\Notifications\CommentNotification;
use openvk\Web\Models\Repositories\{Comments, Clubs}; use openvk\Web\Models\Repositories\{Comments, Clubs, Videos, Photos};
final class CommentPresenter extends OpenVKPresenter final class CommentPresenter extends OpenVKPresenter
{ {
@ -23,6 +23,9 @@ final class CommentPresenter extends OpenVKPresenter
$comment = (new Comments)->get($id); $comment = (new Comments)->get($id);
if(!$comment || $comment->isDeleted()) $this->notFound(); if(!$comment || $comment->isDeleted()) $this->notFound();
if ($comment->getTarget() instanceof Post && $comment->getTarget()->getWallOwner()->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
if(!is_null($this->user)) $comment->toggleLike($this->user->identity); if(!is_null($this->user)) $comment->toggleLike($this->user->identity);
$this->redirect($_SERVER["HTTP_REFERER"]); $this->redirect($_SERVER["HTTP_REFERER"]);
@ -48,8 +51,8 @@ final class CommentPresenter extends OpenVKPresenter
else if($entity instanceof Topic) else if($entity instanceof Topic)
$club = $entity->getClub(); $club = $entity->getClub();
if($_FILES["_vid_attachment"] && OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading']) if ($entity instanceof Post && $entity->getWallOwner()->isBanned())
$this->flashFail("err", tr("error"), "Video uploads are disabled by the system administrator."); $this->flashFail("err", tr("error"), tr("forbidden"));
$flags = 0; $flags = 0;
if($this->postParam("as_group") === "on" && !is_null($club) && $club->canBeModifiedBy($this->user->identity)) if($this->postParam("as_group") === "on" && !is_null($club) && $club->canBeModifiedBy($this->user->identity))
@ -60,31 +63,49 @@ final class CommentPresenter extends OpenVKPresenter
try { try {
$photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"]); $photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"]);
} catch(ISE $ex) { } catch(ISE $ex) {
$this->flashFail("err", "Не удалось опубликовать пост", "Файл изображения повреждён, слишком велик или одна сторона изображения в разы больше другой."); $this->flashFail("err", tr("error_when_publishing_comment"), tr("error_when_publishing_comment_description"));
} }
} }
# TODO move to trait $photos = [];
try { if(!empty($this->postParam("photos"))) {
$photo = NULL; $un = rtrim($this->postParam("photos"), ",");
$video = NULL; $arr = explode(",", $un);
if($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) {
$album = NULL;
if($wall > 0 && $wall === $this->user->id)
$album = (new Albums)->getUserWallAlbum($wallOwner);
$photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"], $album); if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$photo = (new Photos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$photo || $photo->isDeleted())
continue;
$photos[] = $photo;
}
}
} }
if($_FILES["_vid_attachment"]["error"] === UPLOAD_ERR_OK) { $videos = [];
$video = Video::fastMake($this->user->id, $_FILES["_vid_attachment"]["name"], $this->postParam("text"), $_FILES["_vid_attachment"]);
if(!empty($this->postParam("videos"))) {
$un = rtrim($this->postParam("videos"), ",");
$arr = explode(",", $un);
if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$video = (new Videos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$video || $video->isDeleted())
continue;
$videos[] = $video;
}
} }
} catch(ISE $ex) {
$this->flashFail("err", "Не удалось опубликовать комментарий", "Файл медиаконтента повреждён или слишком велик.");
} }
if(empty($this->postParam("text")) && !$photo && !$video) if(empty($this->postParam("text")) && sizeof($photos) < 1 && sizeof($videos) < 1)
$this->flashFail("err", "Не удалось опубликовать комментарий", "Комментарий пустой или слишком большой."); $this->flashFail("err", tr("error_when_publishing_comment"), tr("error_comment_empty"));
try { try {
$comment = new Comment; $comment = new Comment;
@ -96,14 +117,15 @@ final class CommentPresenter extends OpenVKPresenter
$comment->setFlags($flags); $comment->setFlags($flags);
$comment->save(); $comment->save();
} catch (\LengthException $ex) { } catch (\LengthException $ex) {
$this->flashFail("err", "Не удалось опубликовать комментарий", "Комментарий слишком большой."); $this->flashFail("err", tr("error_when_publishing_comment"), tr("error_comment_too_big"));
} }
if(!is_null($photo)) foreach($photos as $photo)
$comment->attach($photo); $comment->attach($photo);
if(!is_null($video)) if(sizeof($videos) > 0)
$comment->attach($video); foreach($videos as $vid)
$comment->attach($vid);
if($entity->getOwner()->getId() !== $this->user->identity->getId()) if($entity->getOwner()->getId() !== $this->user->identity->getId())
if(($owner = $entity->getOwner()) instanceof User) if(($owner = $entity->getOwner()) instanceof User)
@ -118,7 +140,7 @@ final class CommentPresenter extends OpenVKPresenter
if($mentionee instanceof User) if($mentionee instanceof User)
(new MentionNotification($mentionee, $entity, $comment->getOwner(), strip_tags($comment->getText())))->emit(); (new MentionNotification($mentionee, $entity, $comment->getOwner(), strip_tags($comment->getText())))->emit();
$this->flashFail("succ", "Комментарий добавлен", "Ваш комментарий появится на странице."); $this->flashFail("succ", tr("comment_is_added"), tr("comment_is_added_desc"));
} }
function renderDeleteComment(int $id): void function renderDeleteComment(int $id): void
@ -129,13 +151,15 @@ final class CommentPresenter extends OpenVKPresenter
$comment = (new Comments)->get($id); $comment = (new Comments)->get($id);
if(!$comment) $this->notFound(); if(!$comment) $this->notFound();
if(!$comment->canBeDeletedBy($this->user->identity)) if(!$comment->canBeDeletedBy($this->user->identity))
$this->throwError(403, "Forbidden", "У вас недостаточно прав чтобы редактировать этот ресурс."); $this->throwError(403, "Forbidden", tr("error_access_denied"));
if ($comment->getTarget() instanceof Post && $comment->getTarget()->getWallOwner()->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
$comment->delete(); $comment->delete();
$this->flashFail( $this->flashFail(
"succ", "succ",
"Успешно", tr("success"),
"Этот комментарий больше не будет показыватся.<br/><a href='/al_comments/spam?$id'>Отметить как спам</a>?" tr("comment_will_not_appear")
); );
} }
} }

View file

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

View file

@ -23,11 +23,15 @@ final class GroupPresenter extends OpenVKPresenter
$club = $this->clubs->get($id); $club = $this->clubs->get($id);
if(!$club) { if(!$club) {
$this->notFound(); $this->notFound();
} else {
if ($club->isBanned()) {
$this->template->_template = "Group/Banned.xml";
} else { } else {
$this->template->albums = (new Albums)->getClubAlbums($club, 1, 3); $this->template->albums = (new Albums)->getClubAlbums($club, 1, 3);
$this->template->albumsCount = (new Albums)->getClubAlbumsCount($club); $this->template->albumsCount = (new Albums)->getClubAlbumsCount($club);
$this->template->topics = (new Topics)->getLastTopics($club, 3); $this->template->topics = (new Topics)->getLastTopics($club, 3);
$this->template->topicsCount = (new Topics)->getClubTopicsCount($club); $this->template->topicsCount = (new Topics)->getClubTopicsCount($club);
}
$this->template->club = $club; $this->template->club = $club;
} }
@ -39,7 +43,7 @@ final class GroupPresenter extends OpenVKPresenter
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
if($_SERVER["REQUEST_METHOD"] === "POST") { if($_SERVER["REQUEST_METHOD"] === "POST") {
if(!empty($this->postParam("name"))) if(!empty($this->postParam("name")) && mb_strlen(trim($this->postParam("name"))) > 0)
{ {
$club = new Club; $club = new Club;
$club->setName($this->postParam("name")); $club->setName($this->postParam("name"));
@ -50,7 +54,7 @@ final class GroupPresenter extends OpenVKPresenter
$club->save(); $club->save();
} catch(\PDOException $ex) { } catch(\PDOException $ex) {
if($ex->getCode() == 23000) if($ex->getCode() == 23000)
$this->flashFail("err", "Ошибка", "Произошла ошибка на стороне сервера. Обратитесь к системному администратору."); $this->flashFail("err", tr("error"), tr("error_on_server_side"));
else else
throw $ex; throw $ex;
} }
@ -58,7 +62,7 @@ final class GroupPresenter extends OpenVKPresenter
$club->toggleSubscription($this->user->identity); $club->toggleSubscription($this->user->identity);
$this->redirect("/club" . $club->getId()); $this->redirect("/club" . $club->getId());
}else{ }else{
$this->flashFail("err", "Ошибка", "Вы не ввели название группы."); $this->flashFail("err", tr("error"), tr("error_no_group_name"));
} }
} }
} }
@ -72,6 +76,7 @@ final class GroupPresenter extends OpenVKPresenter
$club = $this->clubs->get((int) $this->postParam("id")); $club = $this->clubs->get((int) $this->postParam("id"));
if(!$club) exit("Invalid state"); if(!$club) exit("Invalid state");
if ($club->isBanned()) $this->flashFail("err", tr("error"), tr("forbidden"));
$club->toggleSubscription($this->user->identity); $club->toggleSubscription($this->user->identity);
@ -83,6 +88,8 @@ final class GroupPresenter extends OpenVKPresenter
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
$this->template->club = $this->clubs->get($id); $this->template->club = $this->clubs->get($id);
if ($this->template->club->isBanned()) $this->flashFail("err", tr("error"), tr("forbidden"));
$this->template->onlyShowManagers = $this->queryParam("onlyAdmins") == "1"; $this->template->onlyShowManagers = $this->queryParam("onlyAdmins") == "1";
if($this->template->onlyShowManagers) { if($this->template->onlyShowManagers) {
$this->template->followers = NULL; $this->template->followers = NULL;
@ -118,12 +125,14 @@ final class GroupPresenter extends OpenVKPresenter
$this->badRequest(); $this->badRequest();
$club = $this->clubs->get($id); $club = $this->clubs->get($id);
if ($club->isBanned()) $this->flashFail("err", tr("error"), tr("forbidden"));
$user = (new Users)->get((int) $user); $user = (new Users)->get((int) $user);
if(!$user || !$club) if(!$user || !$club)
$this->notFound(); $this->notFound();
if(!$club->canBeModifiedBy($this->user->identity ?? NULL)) if(!$club->canBeModifiedBy($this->user->identity ?? NULL))
$this->flashFail("err", "Ошибка доступа", "У вас недостаточно прав, чтобы изменять этот ресурс."); $this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"));
if(!is_null($hidden)) { if(!is_null($hidden)) {
if($club->getOwner()->getId() == $user->getId()) { if($club->getOwner()->getId() == $user->getId()) {
@ -141,9 +150,9 @@ final class GroupPresenter extends OpenVKPresenter
} }
if($hidden) { if($hidden) {
$this->flashFail("succ", "Операция успешна", "Теперь " . $user->getCanonicalName() . " будет показываться как обычный подписчик всем кроме других администраторов"); $this->flashFail("succ", tr("success_action"), tr("x_is_now_hidden", $user->getCanonicalName()));
} else { } else {
$this->flashFail("succ", "Операция успешна", "Теперь все будут знать про то что " . $user->getCanonicalName() . " - администратор"); $this->flashFail("succ", tr("success_action"), tr("x_is_now_showed", $user->getCanonicalName()));
} }
} elseif($removeComment) { } elseif($removeComment) {
if($club->getOwner()->getId() == $user->getId()) { if($club->getOwner()->getId() == $user->getId()) {
@ -155,11 +164,11 @@ final class GroupPresenter extends OpenVKPresenter
$manager->save(); $manager->save();
} }
$this->flashFail("succ", "Операция успешна", "Комментарий к администратору удален"); $this->flashFail("succ", tr("success_action"), tr("comment_is_deleted"));
} elseif($comment) { } elseif($comment) {
if(mb_strlen($comment) > 36) { if(mb_strlen($comment) > 36) {
$commentLength = (string) mb_strlen($comment); $commentLength = (string) mb_strlen($comment);
$this->flashFail("err", "Ошибка", "Комментарий слишком длинный ($commentLength символов вместо 36 символов)"); $this->flashFail("err", tr("error"), tr("comment_is_too_long", $commentLength));
} }
if($club->getOwner()->getId() == $user->getId()) { if($club->getOwner()->getId() == $user->getId()) {
@ -171,16 +180,16 @@ final class GroupPresenter extends OpenVKPresenter
$manager->save(); $manager->save();
} }
$this->flashFail("succ", "Операция успешна", "Комментарий к администратору изменён"); $this->flashFail("succ", tr("success_action"), tr("comment_is_changed"));
}else{ }else{
if($club->canBeModifiedBy($user)) { if($club->canBeModifiedBy($user)) {
$club->removeManager($user); $club->removeManager($user);
$this->flashFail("succ", "Операция успешна", $user->getCanonicalName() . " более не администратор."); $this->flashFail("succ", tr("success_action"), tr("x_no_more_admin", $user->getCanonicalName()));
} else { } else {
$club->addManager($user); $club->addManager($user);
(new ClubModeratorNotification($user, $club, $this->user->identity))->emit(); (new ClubModeratorNotification($user, $club, $this->user->identity))->emit();
$this->flashFail("succ", "Операция успешна", $user->getCanonicalName() . " назначен(а) администратором."); $this->flashFail("succ", tr("success_action"), tr("x_is_admin", $user->getCanonicalName()));
} }
} }
@ -194,6 +203,8 @@ final class GroupPresenter extends OpenVKPresenter
$club = $this->clubs->get($id); $club = $this->clubs->get($id);
if(!$club || !$club->canBeModifiedBy($this->user->identity)) if(!$club || !$club->canBeModifiedBy($this->user->identity))
$this->notFound(); $this->notFound();
else if ($club->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
else else
$this->template->club = $club; $this->template->club = $club;
@ -201,7 +212,7 @@ final class GroupPresenter extends OpenVKPresenter
if(!$club->setShortcode( empty($this->postParam("shortcode")) ? NULL : $this->postParam("shortcode") )) if(!$club->setShortcode( empty($this->postParam("shortcode")) ? NULL : $this->postParam("shortcode") ))
$this->flashFail("err", tr("error"), tr("error_shorturl_incorrect")); $this->flashFail("err", tr("error"), tr("error_shorturl_incorrect"));
$club->setName(empty($this->postParam("name")) ? $club->getName() : $this->postParam("name")); $club->setName((empty($this->postParam("name")) || mb_strlen(trim($this->postParam("name"))) === 0) ? $club->getName() : $this->postParam("name"));
$club->setAbout(empty($this->postParam("about")) ? NULL : $this->postParam("about")); $club->setAbout(empty($this->postParam("about")) ? NULL : $this->postParam("about"));
$club->setWall(empty($this->postParam("wall")) ? 0 : 1); $club->setWall(empty($this->postParam("wall")) ? 0 : 1);
$club->setAdministrators_List_Display(empty($this->postParam("administrators_list_display")) ? 0 : $this->postParam("administrators_list_display")); $club->setAdministrators_List_Display(empty($this->postParam("administrators_list_display")) ? 0 : $this->postParam("administrators_list_display"));
@ -234,7 +245,7 @@ final class GroupPresenter extends OpenVKPresenter
(new Albums)->getClubAvatarAlbum($club)->addPhoto($photo); (new Albums)->getClubAvatarAlbum($club)->addPhoto($photo);
} catch(ISE $ex) { } catch(ISE $ex) {
$name = $album->getName(); $name = $album->getName();
$this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию."); $this->flashFail("err", tr("error"), tr("error_when_uploading_photo"));
} }
} }
@ -242,12 +253,12 @@ final class GroupPresenter extends OpenVKPresenter
$club->save(); $club->save();
} catch(\PDOException $ex) { } catch(\PDOException $ex) {
if($ex->getCode() == 23000) if($ex->getCode() == 23000)
$this->flashFail("err", "Ошибка", "Произошла ошибка на стороне сервера. Обратитесь к системному администратору."); $this->flashFail("err", tr("error"), tr("error_on_server_side"));
else else
throw $ex; throw $ex;
} }
$this->flash("succ", "Изменения сохранены", "Новые данные появятся в вашей группе."); $this->flash("succ", tr("changes_saved"), tr("new_changes_desc"));
} }
} }
@ -255,6 +266,7 @@ final class GroupPresenter extends OpenVKPresenter
{ {
$photo = new Photo; $photo = new Photo;
$club = $this->clubs->get($id); $club = $this->clubs->get($id);
if ($club->isBanned()) $this->flashFail("err", tr("error"), tr("forbidden"));
if($_SERVER["REQUEST_METHOD"] === "POST" && $_FILES["ava"]["error"] === UPLOAD_ERR_OK) { if($_SERVER["REQUEST_METHOD"] === "POST" && $_FILES["ava"]["error"] === UPLOAD_ERR_OK) {
try { try {
$anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"]; $anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"];
@ -286,7 +298,7 @@ final class GroupPresenter extends OpenVKPresenter
} catch(ISE $ex) { } catch(ISE $ex) {
$name = $album->getName(); $name = $album->getName();
$this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию."); $this->flashFail("err", tr("error"), tr("error_when_uploading_photo"));
} }
} }
$this->returnJson([ $this->returnJson([
@ -338,11 +350,13 @@ final class GroupPresenter extends OpenVKPresenter
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
if(!eventdb()) if(!eventdb())
$this->flashFail("err", "Ошибка подключения", "Не удалось подключится к службе телеметрии."); $this->flashFail("err", tr("connection_error"), tr("connection_error_desc"));
$club = $this->clubs->get($id); $club = $this->clubs->get($id);
if(!$club->canBeModifiedBy($this->user->identity)) if(!$club->canBeModifiedBy($this->user->identity))
$this->notFound(); $this->notFound();
else if ($club->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
else else
$this->template->club = $club; $this->template->club = $club;
@ -375,6 +389,7 @@ final class GroupPresenter extends OpenVKPresenter
$this->flashFail("err", tr("error"), tr("incorrect_password")); $this->flashFail("err", tr("error"), tr("incorrect_password"));
$club = $this->clubs->get($id); $club = $this->clubs->get($id);
if ($club->isBanned()) $this->flashFail("err", tr("error"), tr("forbidden"));
$newOwner = (new Users)->get($newOwnerId); $newOwner = (new Users)->get($newOwnerId);
if($this->user->id !== $club->getOwner()->getId()) if($this->user->id !== $club->getOwner()->getId())
$this->flashFail("err", tr("error"), tr("forbidden")); $this->flashFail("err", tr("error"), tr("forbidden"));

View file

@ -1,5 +1,6 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\Web\Presenters; namespace openvk\Web\Presenters;
use openvk\Web\Models\Repositories\{Posts, Comments};
use MessagePack\MessagePack; use MessagePack\MessagePack;
use Chandler\Session\Session; use Chandler\Session\Session;
@ -95,4 +96,41 @@ final class InternalAPIPresenter extends OpenVKPresenter
]); ]);
} }
} }
function renderGetPhotosFromPost(int $owner_id, int $post_id) {
if($_SERVER["REQUEST_METHOD"] !== "POST") {
header("HTTP/1.1 405 Method Not Allowed");
exit("иди нахуй заебал");
}
if($this->postParam("parentType", false) == "post") {
$post = (new Posts)->getPostById($owner_id, $post_id);
} else {
$post = (new Comments)->get($post_id);
}
if(is_null($post)) {
$this->returnJson([
"success" => 0
]);
} else {
$response = [];
$attachments = $post->getChildren();
foreach($attachments as $attachment)
{
if($attachment instanceof \openvk\Web\Models\Entities\Photo)
{
$response[] = [
"url" => $attachment->getURLBySizeId('normal'),
"id" => $attachment->getPrettyId()
];
}
}
$this->returnJson([
"success" => 1,
"body" => $response
]);
}
}
} }

View file

@ -0,0 +1,384 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use Nette\Database\DriverException;
use Nette\Utils\Finder;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Entities\Club;
use openvk\Web\Models\Entities\Comment;
use Chandler\Database\Log;
use openvk\Web\Models\Entities\NoSpamLog;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\ChandlerUsers;
use Chandler\Database\Logs;
use openvk\Web\Models\Repositories\NoSpamLogs;
use openvk\Web\Models\Repositories\Users;
final class NoSpamPresenter extends OpenVKPresenter
{
protected $banTolerant = true;
protected $deactivationTolerant = true;
protected $presenterName = "nospam";
const ENTITIES_NAMESPACE = "openvk\\Web\\Models\\Entities";
function __construct()
{
parent::__construct();
}
function renderIndex(): void
{
$this->assertUserLoggedIn();
$this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
$targetDir = __DIR__ . '/../Models/Entities/';
$mode = in_array($this->queryParam("act"), ["form", "templates", "rollback", "reports"]) ? $this->queryParam("act") : "form";
if ($mode === "form") {
$this->template->_template = "NoSpam/Index";
$foundClasses = [];
foreach (Finder::findFiles('*.php')->from($targetDir) as $file) {
$content = file_get_contents($file->getPathname());
$namespacePattern = '/namespace\s+([^\s;]+)/';
$classPattern = '/class\s+([^\s{]+)/';
preg_match($namespacePattern, $content, $namespaceMatches);
preg_match($classPattern, $content, $classMatches);
if (isset($namespaceMatches[1]) && isset($classMatches[1])) {
$classNamespace = trim($namespaceMatches[1]);
$className = trim($classMatches[1]);
$fullClassName = $classNamespace . '\\' . $className;
if ($classNamespace === NoSpamPresenter::ENTITIES_NAMESPACE && class_exists($fullClassName)) {
$foundClasses[] = $className;
}
}
}
$models = [];
foreach ($foundClasses as $class) {
$r = new \ReflectionClass(NoSpamPresenter::ENTITIES_NAMESPACE . "\\$class");
if (!$r->isAbstract() && $r->getName() !== NoSpamPresenter::ENTITIES_NAMESPACE . "\\Correspondence")
$models[] = $class;
}
$this->template->models = $models;
} else if ($mode === "templates") {
$this->template->_template = "NoSpam/Templates.xml";
$filter = [];
if ($this->queryParam("id")) {
$filter["id"] = (int)$this->queryParam("id");
}
$this->template->templates = iterator_to_array((new NoSpamLogs)->getList($filter));
} else if ($mode === "reports") {
$this->redirect("/scumfeed");
} else {
$template = (new NoSpamLogs)->get((int)$this->postParam("id"));
if (!$template || $template->isRollbacked())
$this->returnJson(["success" => false, "error" => "Шаблон не найден"]);
$model = NoSpamPresenter::ENTITIES_NAMESPACE . "\\" . $template->getModel();
$items = $template->getItems();
if (count($items) > 0) {
$db = DatabaseConnection::i()->getContext();
$unbanned_ids = [];
foreach ($items as $_item) {
try {
$item = new $model;
$table_name = $item->getTableName();
$item = $db->table($table_name)->get((int)$_item);
if (!$item) continue;
$item = new $model($item);
if (key_exists("deleted", $item->unwrap()) && $item->isDeleted()) {
$item->setDeleted(0);
$item->save();
}
if (in_array($template->getTypeRaw(), [2, 3])) {
$owner = NULL;
$methods = ["getOwner", "getUser", "getRecipient", "getInitiator"];
if (method_exists($item, "ban")) {
$owner = $item;
} else {
foreach ($methods as $method) {
if (method_exists($item, $method)) {
$owner = $item->$method();
break;
}
}
}
$_id = ($owner instanceof Club ? $owner->getId() * -1 : $owner->getId());
if (!in_array($_id, $unbanned_ids)) {
$owner->unban($this->user->id);
$unbanned_ids[] = $_id;
}
}
} catch (\Throwable $e) {
$this->returnJson(["success" => false, "error" => $e->getMessage()]);
}
}
} else {
$this->returnJson(["success" => false, "error" => "Объекты не найдены"]);
}
$template->setRollback(true);
$template->save();
$this->returnJson(["success" => true]);
}
}
function renderSearch(): void
{
$this->assertUserLoggedIn();
$this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
$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;
$where = $this->postParam("where");
$ip = addslashes($this->postParam("ip"));
$useragent = addslashes($this->postParam("useragent"));
$searchTerm = addslashes($this->postParam("q"));
$ts = (int)$this->postParam("ts");
$te = (int)$this->postParam("te");
$user = addslashes($this->postParam("user"));
if ($where) {
$where = explode(";", $where)[0];
}
if (!$ip && !$useragent && !$searchTerm && !$ts && !$te && !$where && !$searchTerm && !$user)
$this->returnJson(["success" => false, "error" => "Нет запроса. Заполните поле \"подстрока\" или введите запрос \"WHERE\" в поле под ним."]);
$models = explode(",", $this->postParam("models"));
foreach ($models as $_model) {
$model_name = NoSpamPresenter::ENTITIES_NAMESPACE . "\\" . $_model;
if (!class_exists($model_name)) {
continue;
}
$model = new $model_name;
$c = new \ReflectionClass($model_name);
if ($c->isAbstract() || $c->getName() == NoSpamPresenter::ENTITIES_NAMESPACE . "\\Correspondence") {
continue;
}
$db = DatabaseConnection::i()->getContext();
$table = $model->getTableName();
$columns = $db->getStructure()->getColumns($table);
if ($searchTerm) {
$conditions = [];
$need_deleted = false;
foreach ($columns as $column) {
if ($column["name"] == "deleted") {
$need_deleted = true;
} else {
$conditions[] = "`$column[name]` REGEXP '$searchTerm'";
}
}
$conditions = implode(" OR ", $conditions);
$where = ($this->postParam("where") ? " AND ($conditions)" : "($conditions)");
if ($need_deleted) $where .= " AND (`deleted` = 0)";
}
$rows = [];
if (str_starts_with($where, " AND")) {
if ($searchTerm && !$this->postParam("where")) {
$where = substr_replace($where, "", 0, strlen(" AND"));
} else {
$where = "(" . $this->postParam("where") . ")" . $where;
}
}
if ($ip || $useragent || $ts || $te || $user) {
$rows = searchByAdditionalParams($table, $where, $ip, $useragent, $ts, $te, $user);
} else {
if (!$where) {
$rows = [];
} else {
$result = $db->query("SELECT * FROM `$table` WHERE $where");
$rows = $result->fetchAll();
}
}
if (!in_array((int)$this->postParam("ban"), [1, 2, 3])) {
foreach ($rows as $key => $object) {
$object = (array)$object;
$_obj = [];
foreach ($object as $key => $value) {
foreach ($columns as $column) {
if ($column["name"] === $key && in_array(strtoupper($column["nativetype"]), ["BLOB", "BINARY", "VARBINARY", "TINYBLOB", "MEDIUMBLOB", "LONGBLOB"])) {
$value = "[BINARY]";
break;
}
}
$_obj[$key] = $value;
$_obj["__model_name"] = $_model;
}
$response[] = $_obj;
}
} else {
$ids = [];
foreach ($rows as $object) {
$object = new $model_name($db->table($table)->get($object->id));
if (!$object) continue;
$ids[] = $object->getId();
}
$log = new NoSpamLog;
$log->setUser($this->user->id);
$log->setModel($_model);
if ($searchTerm) {
$log->setRegex($searchTerm);
} else {
$log->setRequest($where);
}
$log->setBan_Type((int)$this->postParam("ban"));
$log->setCount(count($rows));
$log->setTime(time());
$log->setItems(implode(",", $ids));
$log->save();
$banned_ids = [];
foreach ($rows as $object) {
$object = new $model_name($db->table($table)->get($object->id));
if (!$object) continue;
$owner = NULL;
$methods = ["getOwner", "getUser", "getRecipient", "getInitiator"];
if (method_exists($object, "ban")) {
$owner = $object;
} else {
foreach ($methods as $method) {
if (method_exists($object, $method)) {
$owner = $object->$method();
break;
}
}
}
if ($owner instanceof User && $owner->getId() === $this->user->id) {
if (count($rows) === 1) {
$this->returnJson(["success" => false, "error" => "\"Производственная травма\" — Вы не можете блокировать или удалять свой же контент"]);
} else {
continue;
}
}
if (in_array((int)$this->postParam("ban"), [2, 3])) {
$reason = mb_strlen(trim($this->postParam("ban_reason"))) > 0 ? addslashes($this->postParam("ban_reason")) : ("**content-noSpamTemplate-" . $log->getId() . "**");
$is_forever = (string)$this->postParam("is_forever") === "true";
$unban_time = $is_forever ? 0 : (int)$this->postParam("unban_time") ?? NULL;
if ($owner) {
$_id = ($owner instanceof Club ? $owner->getId() * -1 : $owner->getId());
if (!in_array($_id, $banned_ids)) {
if ($owner instanceof User) {
if (!$unban_time && !$is_forever)
$unban_time = time() + $owner->getNewBanTime();
$owner->ban($reason, false, $unban_time, $this->user->id);
} else {
$owner->ban("Подозрительная активность");
}
$banned_ids[] = $_id;
}
}
}
if (in_array((int)$this->postParam("ban"), [1, 3]))
$object->delete();
}
$processed++;
}
}
$this->returnJson(["success" => true, "processed" => $processed, "count" => count($response), "list" => $response]);
} catch (\Throwable $e) {
$this->returnJson(["success" => false, "error" => $e->getMessage()]);
}
}
}

View file

@ -107,7 +107,7 @@ final class NotesPresenter extends OpenVKPresenter
if(!$note || $note->getOwner()->getId() !== $owner || $note->isDeleted()) if(!$note || $note->getOwner()->getId() !== $owner || $note->isDeleted())
$this->notFound(); $this->notFound();
if(is_null($this->user) || !$note->canBeModifiedBy($this->user->identity)) if(is_null($this->user) || !$note->canBeModifiedBy($this->user->identity))
$this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса."); $this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"));
$this->template->note = $note; $this->template->note = $note;
if($_SERVER["REQUEST_METHOD"] === "POST") { if($_SERVER["REQUEST_METHOD"] === "POST") {
@ -135,11 +135,11 @@ final class NotesPresenter extends OpenVKPresenter
if(!$note) $this->notFound(); if(!$note) $this->notFound();
if($note->getOwner()->getId() . "_" . $note->getId() !== $owner . "_" . $id || $note->isDeleted()) $this->notFound(); if($note->getOwner()->getId() . "_" . $note->getId() !== $owner . "_" . $id || $note->isDeleted()) $this->notFound();
if(is_null($this->user) || !$note->canBeModifiedBy($this->user->identity)) if(is_null($this->user) || !$note->canBeModifiedBy($this->user->identity))
$this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса."); $this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"));
$name = $note->getName(); $name = $note->getName();
$note->delete(); $note->delete();
$this->flash("succ", "Заметка удалена", "Заметка \"$name\" была успешно удалена."); $this->flash("succ", tr("note_is_deleted"), tr("note_x_is_now_deleted", $name));
$this->redirect("/notes" . $this->user->id); $this->redirect("/notes" . $this->user->id);
} }
} }

9
Web/Presenters/OpenVKPresenter.php Executable file → Normal file
View file

@ -7,7 +7,7 @@ use Chandler\Security\Authenticator;
use Latte\Engine as TemplatingEngine; use Latte\Engine as TemplatingEngine;
use openvk\Web\Models\Entities\IP; use openvk\Web\Models\Entities\IP;
use openvk\Web\Themes\Themepacks; use openvk\Web\Themes\Themepacks;
use openvk\Web\Models\Repositories\{IPs, Users, APITokens, Tickets}; use openvk\Web\Models\Repositories\{IPs, Users, APITokens, Tickets, Reports, CurrentUser};
use WhichBrowser; use WhichBrowser;
abstract class OpenVKPresenter extends SimplePresenter abstract class OpenVKPresenter extends SimplePresenter
@ -211,6 +211,7 @@ abstract class OpenVKPresenter extends SimplePresenter
$this->user->id = $this->user->identity->getId(); $this->user->id = $this->user->identity->getId();
$this->template->thisUser = $this->user->identity; $this->template->thisUser = $this->user->identity;
$this->template->userTainted = $user->isTainted(); $this->template->userTainted = $user->isTainted();
CurrentUser::get($this->user->identity, $_SERVER["REMOTE_ADDR"], $_SERVER["HTTP_USER_AGENT"]);
if($this->user->identity->isDeleted() && !$this->deactivationTolerant) { if($this->user->identity->isDeleted() && !$this->deactivationTolerant) {
if($this->user->identity->isDeactivated()) { if($this->user->identity->isDeactivated()) {
@ -255,12 +256,14 @@ abstract class OpenVKPresenter extends SimplePresenter
if($this->user->identity->onlineStatus() == 0 && !($this->user->identity->isDeleted() || $this->user->identity->isBanned())) { if($this->user->identity->onlineStatus() == 0 && !($this->user->identity->isDeleted() || $this->user->identity->isBanned())) {
$this->user->identity->setOnline(time()); $this->user->identity->setOnline(time());
$this->user->identity->setClient_name(NULL); $this->user->identity->setClient_name(NULL);
$this->user->identity->save(); $this->user->identity->save(false);
} }
$this->template->ticketAnsweredCount = (new Tickets)->getTicketsCountByUserId($this->user->id, 1); $this->template->ticketAnsweredCount = (new Tickets)->getTicketsCountByUserId($this->user->id, 1);
if($user->can("write")->model("openvk\Web\Models\Entities\TicketReply")->whichBelongsTo(0)) if($user->can("write")->model("openvk\Web\Models\Entities\TicketReply")->whichBelongsTo(0)) {
$this->template->helpdeskTicketNotAnsweredCount = (new Tickets)->getTicketCount(0); $this->template->helpdeskTicketNotAnsweredCount = (new Tickets)->getTicketCount(0);
$this->template->reportNotAnsweredCount = (new Reports)->getReportsCount(0);
}
} }
header("X-OpenVK-User-Validated: $userValidated"); header("X-OpenVK-User-Validated: $userValidated");

View file

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

View file

@ -0,0 +1,151 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use openvk\Web\Models\Repositories\Users;
use openvk\Web\Models\Repositories\Reports;
use openvk\Web\Models\Repositories\Posts;
use openvk\Web\Models\Entities\Report;
final class ReportPresenter extends OpenVKPresenter
{
private $reports;
function __construct(Reports $reports)
{
$this->reports = $reports;
parent::__construct();
}
function renderList(): void
{
$this->assertUserLoggedIn();
$this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
if ($_SERVER["REQUEST_METHOD"] === "POST")
$this->assertNoCSRF();
$act = in_array($this->queryParam("act"), ["post", "photo", "video", "group", "comment", "note", "app", "user"]) ? $this->queryParam("act") : NULL;
if (!$this->queryParam("orig")) {
$this->template->reports = $this->reports->getReports(0, (int)($this->queryParam("p") ?? 1), $act, $_SERVER["REQUEST_METHOD"] !== "POST");
$this->template->count = $this->reports->getReportsCount();
} else {
$orig = $this->reports->get((int) $this->queryParam("orig"));
if (!$orig) $this->redirect("/scumfeed");
$this->template->reports = $orig->getDuplicates();
$this->template->count = $orig->getDuplicatesCount();
$this->template->orig = $orig->getId();
}
$this->template->paginatorConf = (object) [
"count" => $this->template->count,
"page" => $this->queryParam("p") ?? 1,
"amount" => NULL,
"perPage" => 15,
];
$this->template->mode = $act ?? "all";
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$reports = [];
foreach ($this->reports->getReports(0, 0, $act, false) as $report) {
$reports[] = [
"id" => $report->getId(),
"author" => [
"id" => $report->getReportAuthor()->getId(),
"url" => $report->getReportAuthor()->getURL(),
"name" => $report->getReportAuthor()->getCanonicalName(),
"is_female" => $report->getReportAuthor()->isFemale()
],
"content" => [
"name" => $report->getContentName(),
"type" => $report->getContentType(),
"id" => $report->getContentId(),
"url" => $report->getContentType() === "user" ? (new Users)->get((int) $report->getContentId())->getURL() : NULL
],
"duplicates" => $report->getDuplicatesCount(),
];
}
$this->returnJson(["reports" => $reports]);
}
}
function renderView(int $id): void
{
$this->assertUserLoggedIn();
$this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
$report = $this->reports->get($id);
if(!$report || $report->isDeleted())
$this->notFound();
$this->template->report = $report;
}
function renderCreate(int $id): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
if(!$id)
exit(json_encode([ "error" => tr("error_segmentation") ]));
if(in_array($this->queryParam("type"), ["post", "photo", "video", "group", "comment", "note", "app", "user"])) {
if (count(iterator_to_array($this->reports->getDuplicates($this->queryParam("type"), $id, NULL, $this->user->id))) <= 0) {
$report = new Report;
$report->setUser_id($this->user->id);
$report->setTarget_id($id);
$report->setType($this->queryParam("type"));
$report->setReason($this->queryParam("reason"));
$report->setCreated(time());
$report->save();
}
exit(json_encode([ "reason" => $this->queryParam("reason") ]));
} else {
exit(json_encode([ "error" => "Unable to submit a report on this content type" ]));
}
}
function renderAction(int $id): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
$this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
$report = $this->reports->get($id);
if(!$report || $report->isDeleted()) $this->notFound();
if ($this->postParam("ban")) {
$report->deleteContent();
$report->banUser($this->user->identity->getId());
$this->flash("suc", tr("death"), tr("user_successfully_banned"));
} else if ($this->postParam("delete")) {
$report->deleteContent();
$this->flash("suc", tr("nehay"), tr("content_is_deleted"));
} else if ($this->postParam("ignore")) {
$report->delete();
$this->flash("suc", tr("nehay"), tr("report_is_ignored"));
} else if ($this->postParam("banClubOwner") || $this->postParam("banClub")) {
if ($report->getContentType() !== "group")
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"));
$club = $report->getContentObject();
if (!$club || $club->isBanned())
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"));
if ($this->postParam("banClubOwner")) {
$club->getOwner()->ban("**content-" . $report->getContentType() . "-" . $report->getContentId() . "**", false, $club->getOwner()->getNewBanTime(), $this->user->identity->getId());
} else {
$club->ban("**content-" . $report->getContentType() . "-" . $report->getContentId() . "**");
}
$report->delete();
$this->flash("suc", tr("death"), ($this->postParam("banClubOwner") ? tr("group_owner_is_banned") : tr("group_is_banned")));
}
$this->redirect("/scumfeed");
}
}

View file

@ -385,7 +385,7 @@ final class SupportPresenter extends OpenVKPresenter
$agent->setNumerate((int) $this->postParam("number") ?? NULL); $agent->setNumerate((int) $this->postParam("number") ?? NULL);
$agent->setIcon($this->postParam("avatar")); $agent->setIcon($this->postParam("avatar"));
$agent->save(); $agent->save();
$this->flashFail("succ", "Успех", "Профиль отредактирован."); $this->flashFail("succ", tr("agent_profile_edited"));
} else { } else {
$agent = new SupportAgent; $agent = new SupportAgent;
$agent->setAgent($this->user->identity->getId()); $agent->setAgent($this->user->identity->getId());
@ -393,7 +393,27 @@ final class SupportPresenter extends OpenVKPresenter
$agent->setNumerate((int) $this->postParam("number") ?? NULL); $agent->setNumerate((int) $this->postParam("number") ?? NULL);
$agent->setIcon($this->postParam("avatar")); $agent->setIcon($this->postParam("avatar"));
$agent->save(); $agent->save();
$this->flashFail("succ", "Успех", "Профиль создан. Теперь пользователи видят Ваши псевдоним и аватарку вместо стандартных аватарки и номера."); $this->flashFail("succ", tr("agent_profile_created_1"), tr("agent_profile_created_2"));
} }
} }
function renderCloseTicket(int $id): void
{
$this->assertUserLoggedIn();
$this->assertNoCSRF();
$this->willExecuteWriteAction();
$ticket = $this->tickets->get($id);
if($ticket->isDeleted() === 1 || $ticket->getType() === 2 || $ticket->getUserId() !== $this->user->id) {
header("HTTP/1.1 403 Forbidden");
header("Location: /support/view/" . $id);
exit;
}
$ticket->setType(2);
$ticket->save();
$this->flashFail("succ", tr("ticket_changed"), tr("ticket_changed_comment"));
}
} }

View file

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

View file

@ -72,7 +72,7 @@ final class UserPresenter extends OpenVKPresenter
if(!is_null($this->user)) { if(!is_null($this->user)) {
if($this->template->mode !== "friends" && $this->user->id !== $id) { if($this->template->mode !== "friends" && $this->user->id !== $id) {
$name = $user->getFullName(); $name = $user->getFullName();
$this->flash("err", "Ошибка доступа", "Вы не можете просматривать полный список подписок $name."); $this->flash("err", tr("error_access_denied_short"), tr("error_viewing_subs", $name));
$this->redirect($user->getURL()); $this->redirect($user->getURL());
} }
@ -107,11 +107,11 @@ final class UserPresenter extends OpenVKPresenter
$this->notFound(); $this->notFound();
if(!$club->canBeModifiedBy($this->user->identity ?? NULL)) if(!$club->canBeModifiedBy($this->user->identity ?? NULL))
$this->flashFail("err", "Ошибка доступа", "У вас недостаточно прав, чтобы изменять этот ресурс.", NULL, true); $this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"), NULL, true);
$isClubPinned = $this->user->identity->isClubPinned($club); $isClubPinned = $this->user->identity->isClubPinned($club);
if(!$isClubPinned && $this->user->identity->getPinnedClubCount() > 10) if(!$isClubPinned && $this->user->identity->getPinnedClubCount() > 10)
$this->flashFail("err", "Ошибка", "Находится в левом меню могут максимум 10 групп", NULL, true); $this->flashFail("err", tr("error"), tr("error_max_pinned_clubs"), NULL, true);
if($club->getOwner()->getId() === $this->user->identity->getId()) { if($club->getOwner()->getId() === $this->user->identity->getId()) {
$club->setOwner_Club_Pinned(!$isClubPinned); $club->setOwner_Club_Pinned(!$isClubPinned);
@ -237,7 +237,7 @@ final class UserPresenter extends OpenVKPresenter
} elseif($_GET['act'] === "status") { } elseif($_GET['act'] === "status") {
if(mb_strlen($this->postParam("status")) > 255) { if(mb_strlen($this->postParam("status")) > 255) {
$statusLength = (string) mb_strlen($this->postParam("status")); $statusLength = (string) mb_strlen($this->postParam("status"));
$this->flashFail("err", "Ошибка", "Статус слишком длинный ($statusLength символов вместо 255 символов)", NULL, true); $this->flashFail("err", tr("error"), tr("error_status_too_long", $statusLength), NULL, true);
} }
$user->setStatus(empty($this->postParam("status")) ? NULL : $this->postParam("status")); $user->setStatus(empty($this->postParam("status")) ? NULL : $this->postParam("status"));
@ -281,7 +281,7 @@ final class UserPresenter extends OpenVKPresenter
if($_SERVER["REQUEST_METHOD"] === "POST") { if($_SERVER["REQUEST_METHOD"] === "POST") {
if(!$user->verifyNumber($this->postParam("code") ?? 0)) if(!$user->verifyNumber($this->postParam("code") ?? 0))
$this->flashFail("err", "Ошибка", "Не удалось подтвердить номер телефона: неверный код."); $this->flashFail("err", tr("error"), tr("invalid_code"));
$this->flash("succ", tr("changes_saved"), tr("changes_saved_comment")); $this->flash("succ", tr("changes_saved"), tr("changes_saved_comment"));
} }
@ -481,6 +481,7 @@ final class UserPresenter extends OpenVKPresenter
"menu_novajoj" => "news", "menu_novajoj" => "news",
"menu_ligiloj" => "links", "menu_ligiloj" => "links",
"menu_standardo" => "poster", "menu_standardo" => "poster",
"menu_aplikoj" => "apps"
]; ];
foreach($settings as $checkbox => $setting) foreach($settings as $checkbox => $setting)
$user->setLeftMenuItemStatus($setting, $this->checkbox($checkbox)); $user->setLeftMenuItemStatus($setting, $this->checkbox($checkbox));

View file

@ -58,7 +58,7 @@ final class VideosPresenter extends OpenVKPresenter
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
if(OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading']) if(OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading'])
$this->flashFail("err", tr("error"), "Video uploads are disabled by the system administrator."); $this->flashFail("err", tr("error"), tr("video_uploads_disabled"));
if($_SERVER["REQUEST_METHOD"] === "POST") { if($_SERVER["REQUEST_METHOD"] === "POST") {
if(!empty($this->postParam("name"))) { if(!empty($this->postParam("name"))) {
@ -74,18 +74,18 @@ final class VideosPresenter extends OpenVKPresenter
else if(!empty($this->postParam("link"))) else if(!empty($this->postParam("link")))
$video->setLink($this->postParam("link")); $video->setLink($this->postParam("link"));
else else
$this->flashFail("err", "Нету видеозаписи", "Выберите файл или укажите ссылку."); $this->flashFail("err", tr("no_video"), tr("no_video_desc"));
} catch(\DomainException $ex) { } catch(\DomainException $ex) {
$this->flashFail("err", "Произошла ошибка", "Файл повреждён или не содержит видео." ); $this->flashFail("err", tr("error_occured"), tr("error_video_damaged_file"));
} catch(ISE $ex) { } catch(ISE $ex) {
$this->flashFail("err", "Произошла ошибка", "Возможно, ссылка некорректна."); $this->flashFail("err", tr("error_occured"), tr("error_video_incorrect_link"));
} }
$video->save(); $video->save();
$this->redirect("/video" . $video->getPrettyId()); $this->redirect("/video" . $video->getPrettyId());
} else { } else {
$this->flashFail("err", "Произошла ошибка", "Видео не может быть опубликовано без названия."); $this->flashFail("err", tr("error_occured"), tr("error_video_no_title"));
} }
} }
} }
@ -99,14 +99,14 @@ final class VideosPresenter extends OpenVKPresenter
if(!$video) if(!$video)
$this->notFound(); $this->notFound();
if(is_null($this->user) || $this->user->id !== $owner) if(is_null($this->user) || $this->user->id !== $owner)
$this->flashFail("err", "Ошибка доступа", "Вы не имеете права редактировать этот ресурс."); $this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"));
if($_SERVER["REQUEST_METHOD"] === "POST") { if($_SERVER["REQUEST_METHOD"] === "POST") {
$video->setName(empty($this->postParam("name")) ? NULL : $this->postParam("name")); $video->setName(empty($this->postParam("name")) ? NULL : $this->postParam("name"));
$video->setDescription(empty($this->postParam("desc")) ? NULL : $this->postParam("desc")); $video->setDescription(empty($this->postParam("desc")) ? NULL : $this->postParam("desc"));
$video->save(); $video->save();
$this->flash("succ", "Изменения сохранены", "Обновлённое описание появится на странице с видосиком."); $this->flash("succ", tr("changes_saved"), tr("new_data_video"));
$this->redirect("/video" . $video->getPrettyId()); $this->redirect("/video" . $video->getPrettyId());
} }
@ -128,7 +128,7 @@ final class VideosPresenter extends OpenVKPresenter
$video->deleteVideo($owner, $vid); $video->deleteVideo($owner, $vid);
} }
} else { } else {
$this->flashFail("err", "Не удалось удалить пост", "Вы не вошли в аккаунт."); $this->flashFail("err", tr("error_deleting_video"), tr("login_please"));
} }
$this->redirect("/videos" . $owner); $this->redirect("/videos" . $owner);

View file

@ -3,7 +3,7 @@ namespace openvk\Web\Presenters;
use openvk\Web\Models\Exceptions\TooMuchOptionsException; use openvk\Web\Models\Exceptions\TooMuchOptionsException;
use openvk\Web\Models\Entities\{Poll, Post, Photo, Video, Club, User}; use openvk\Web\Models\Entities\{Poll, Post, Photo, Video, Club, User};
use openvk\Web\Models\Entities\Notifications\{MentionNotification, RepostNotification, WallPostNotification}; use openvk\Web\Models\Entities\Notifications\{MentionNotification, RepostNotification, WallPostNotification};
use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums, Notes}; use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums, Notes, Videos, Comments, Photos};
use Chandler\Database\DatabaseConnection; use Chandler\Database\DatabaseConnection;
use Nette\InvalidStateException as ISE; use Nette\InvalidStateException as ISE;
use Bhaktaraz\RSSGenerator\Item; use Bhaktaraz\RSSGenerator\Item;
@ -46,13 +46,13 @@ final class WallPresenter extends OpenVKPresenter
function renderWall(int $user, bool $embedded = false): void function renderWall(int $user, bool $embedded = false): void
{ {
$owner = ($user < 0 ? (new Clubs) : (new Users))->get(abs($user)); $owner = ($user < 0 ? (new Clubs) : (new Users))->get(abs($user));
if ($owner->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
if(is_null($this->user)) { if(is_null($this->user)) {
$canPost = false; $canPost = false;
} else if($user > 0) { } else if($user > 0) {
if(!$owner->isBanned())
$canPost = $owner->getPrivacyPermission("wall.write", $this->user->identity); $canPost = $owner->getPrivacyPermission("wall.write", $this->user->identity);
else
$this->flashFail("err", tr("error"), tr("forbidden"));
} else if($user < 0) { } else if($user < 0) {
if($owner->canBeModifiedBy($this->user->identity)) if($owner->canBeModifiedBy($this->user->identity))
$canPost = true; $canPost = true;
@ -100,6 +100,8 @@ final class WallPresenter extends OpenVKPresenter
} else if($user < 0) { } else if($user < 0) {
if($owner->canBeModifiedBy($this->user->identity)) if($owner->canBeModifiedBy($this->user->identity))
$canPost = true; $canPost = true;
else if ($owner->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
else else
$canPost = $owner->canPost(); $canPost = $owner->canPost();
} else { } else {
@ -212,11 +214,12 @@ final class WallPresenter extends OpenVKPresenter
$wallOwner = ($wall > 0 ? (new Users)->get($wall) : (new Clubs)->get($wall * -1)) $wallOwner = ($wall > 0 ? (new Users)->get($wall) : (new Clubs)->get($wall * -1))
?? $this->flashFail("err", tr("failed_to_publish_post"), tr("error_4")); ?? $this->flashFail("err", tr("failed_to_publish_post"), tr("error_4"));
if ($wallOwner->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
if($wall > 0) { if($wall > 0) {
if(!$wallOwner->isBanned())
$canPost = $wallOwner->getPrivacyPermission("wall.write", $this->user->identity); $canPost = $wallOwner->getPrivacyPermission("wall.write", $this->user->identity);
else
$this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment"));
} else if($wall < 0) { } else if($wall < 0) {
if($wallOwner->canBeModifiedBy($this->user->identity)) if($wallOwner->canBeModifiedBy($this->user->identity))
$canPost = true; $canPost = true;
@ -229,9 +232,6 @@ final class WallPresenter extends OpenVKPresenter
if(!$canPost) if(!$canPost)
$this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment")); $this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment"));
if($_FILES["_vid_attachment"] && OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading'])
$this->flashFail("err", tr("error"), "Video uploads are disabled by the system administrator.");
$anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"]; $anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"];
if($wallOwner instanceof Club && $this->postParam("as_group") === "on" && $this->postParam("force_sign") !== "on" && $anon) { if($wallOwner instanceof Club && $this->postParam("as_group") === "on" && $this->postParam("force_sign") !== "on" && $anon) {
$manager = $wallOwner->getManager($this->user->identity); $manager = $wallOwner->getManager($this->user->identity);
@ -249,23 +249,23 @@ final class WallPresenter extends OpenVKPresenter
if($this->postParam("force_sign") === "on") if($this->postParam("force_sign") === "on")
$flags |= 0b01000000; $flags |= 0b01000000;
try { $photos = [];
$photo = NULL;
$video = NULL;
if($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) {
$album = NULL;
if(!$anon && $wall > 0 && $wall === $this->user->id)
$album = (new Albums)->getUserWallAlbum($wallOwner);
$photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"], $album, $anon); if(!empty($this->postParam("photos"))) {
$un = rtrim($this->postParam("photos"), ",");
$arr = explode(",", $un);
if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$photo = (new Photos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$photo || $photo->isDeleted())
continue;
$photos[] = $photo;
}
} }
if($_FILES["_vid_attachment"]["error"] === UPLOAD_ERR_OK)
$video = Video::fastMake($this->user->id, $_FILES["_vid_attachment"]["name"], $this->postParam("text"), $_FILES["_vid_attachment"], $anon);
} catch(\DomainException $ex) {
$this->flashFail("err", tr("failed_to_publish_post"), tr("media_file_corrupted"));
} catch(ISE $ex) {
$this->flashFail("err", tr("failed_to_publish_post"), tr("media_file_corrupted_or_too_large"));
} }
try { try {
@ -293,7 +293,26 @@ final class WallPresenter extends OpenVKPresenter
} }
} }
if(empty($this->postParam("text")) && !$photo && !$video && !$poll && !$note) $videos = [];
if(!empty($this->postParam("videos"))) {
$un = rtrim($this->postParam("videos"), ",");
$arr = explode(",", $un);
if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$video = (new Videos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$video || $video->isDeleted())
continue;
$videos[] = $video;
}
}
}
if(empty($this->postParam("text")) && sizeof($photos) < 1 && sizeof($videos) < 1 && !$poll && !$note)
$this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_empty_or_too_big")); $this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_empty_or_too_big"));
try { try {
@ -315,11 +334,12 @@ final class WallPresenter extends OpenVKPresenter
$this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_too_big")); $this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_too_big"));
} }
if(!is_null($photo)) foreach($photos as $photo)
$post->attach($photo); $post->attach($photo);
if(!is_null($video)) if(sizeof($videos) > 0)
$post->attach($video); foreach($videos as $vid)
$post->attach($vid);
if(!is_null($poll)) if(!is_null($poll))
$post->attach($poll); $post->attach($poll);
@ -359,6 +379,9 @@ final class WallPresenter extends OpenVKPresenter
} else { } else {
$this->template->wallOwner = (new Clubs)->get(abs($post->getTargetWall())); $this->template->wallOwner = (new Clubs)->get(abs($post->getTargetWall()));
$this->template->isWallOfGroup = true; $this->template->isWallOfGroup = true;
if ($this->template->wallOwner->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
} }
$this->template->cCount = $post->getCommentsCount(); $this->template->cCount = $post->getCommentsCount();
$this->template->cPage = (int) ($_GET["p"] ?? 1); $this->template->cPage = (int) ($_GET["p"] ?? 1);
@ -374,6 +397,9 @@ final class WallPresenter extends OpenVKPresenter
$post = $this->posts->getPostById($wall, $post_id); $post = $this->posts->getPostById($wall, $post_id);
if(!$post || $post->isDeleted()) $this->notFound(); if(!$post || $post->isDeleted()) $this->notFound();
if ($post->getWallOwner()->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
if(!is_null($this->user)) { if(!is_null($this->user)) {
$post->toggleLike($this->user->identity); $post->toggleLike($this->user->identity);
} }
@ -392,6 +418,9 @@ final class WallPresenter extends OpenVKPresenter
if(!$post || $post->isDeleted()) if(!$post || $post->isDeleted())
$this->notFound(); $this->notFound();
if ($post->getWallOwner()->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
$where = $this->postParam("type") ?? "wall"; $where = $this->postParam("type") ?? "wall";
$groupId = NULL; $groupId = NULL;
$flags = 0; $flags = 0;
@ -449,6 +478,9 @@ final class WallPresenter extends OpenVKPresenter
$wallOwner = ($wall > 0 ? (new Users)->get($wall) : (new Clubs)->get($wall * -1)) $wallOwner = ($wall > 0 ? (new Users)->get($wall) : (new Clubs)->get($wall * -1))
?? $this->flashFail("err", tr("failed_to_delete_post"), tr("error_4")); ?? $this->flashFail("err", tr("failed_to_delete_post"), tr("error_4"));
if ($wallOwner->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
if($wall < 0) $canBeDeletedByOtherUser = $wallOwner->canBeModifiedBy($this->user->identity); if($wall < 0) $canBeDeletedByOtherUser = $wallOwner->canBeModifiedBy($this->user->identity);
else $canBeDeletedByOtherUser = false; else $canBeDeletedByOtherUser = false;
@ -473,6 +505,9 @@ final class WallPresenter extends OpenVKPresenter
if(!$post) if(!$post)
$this->notFound(); $this->notFound();
if ($post->getWallOwner()->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
if(!$post->canBePinnedBy($this->user->identity)) if(!$post->canBePinnedBy($this->user->identity))
$this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment")); $this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment"));
@ -485,4 +520,64 @@ final class WallPresenter extends OpenVKPresenter
# TODO localize message based on language and ?act=(un)pin # TODO localize message based on language and ?act=(un)pin
$this->flashFail("succ", tr("information_-1"), tr("changes_saved_comment")); $this->flashFail("succ", tr("information_-1"), tr("changes_saved_comment"));
} }
function renderEdit()
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
if($_SERVER["REQUEST_METHOD"] !== "POST")
$this->redirect("/id0");
if($this->postParam("type") == "post")
$post = $this->posts->get((int)$this->postParam("postid"));
else
$post = (new Comments)->get((int)$this->postParam("postid"));
if(!$post || $post->isDeleted())
$this->returnJson(["error" => "Invalid post"]);
if(!$post->canBeEditedBy($this->user->identity))
$this->returnJson(["error" => "Access denied"]);
$attachmentsCount = sizeof(iterator_to_array($post->getChildren()));
if(empty($this->postParam("newContent")) && $attachmentsCount < 1)
$this->returnJson(["error" => "Empty post"]);
$post->setEdited(time());
try {
$post->setContent($this->postParam("newContent"));
} catch(\LengthException $e) {
$this->returnJson(["error" => $e->getMessage()]);
}
if($this->postParam("type") === "post") {
$post->setNsfw($this->postParam("nsfw") == "true");
$flags = 0;
if($post->getTargetWall() < 0 && $post->getWallOwner()->canBeModifiedBy($this->user->identity)) {
if($this->postParam("fromgroup") == "true") {
$flags |= 0b10000000;
$post->setFlags($flags);
} else
$post->setFlags($flags);
}
}
$post->save(true);
$this->returnJson(["error" => "no",
"new_content" => $post->getText(),
"new_edited" => (string)$post->getEditTime(),
"nsfw" => $this->postParam("type") === "post" ? (int)$post->isExplicit() : 0,
"from_group" => $this->postParam("type") === "post" && $post->getTargetWall() < 0 ?
((int)$post->isPostedOnBehalfOfGroup()) : "false",
"new_text" => $post->getText(false),
"author" => [
"name" => $post->getOwner()->getCanonicalName(),
"avatar" => $post->getOwner()->getAvatarUrl()
]]);
}
} }

View file

@ -10,8 +10,19 @@
<img src="/assets/packages/static/openvk/img/oof.apng" alt="{_banned_alt}" style="width: 20%;" /> <img src="/assets/packages/static/openvk/img/oof.apng" alt="{_banned_alt}" style="width: 20%;" />
</center> </center>
<p> <p>
{var $ban = $thisUser->getBanReason("banned")}
{if is_string($ban)}
{tr("banned_1", htmlentities($thisUser->getCanonicalName()))|noescape}<br/> {tr("banned_1", htmlentities($thisUser->getCanonicalName()))|noescape}<br/>
{tr("banned_2", htmlentities($thisUser->getBanReason()))|noescape} {tr("banned_2", htmlentities($thisUser->getBanReason()))|noescape}
{else}
{tr("banned_1", htmlentities($thisUser->getCanonicalName()))|noescape}
<div>
Эта страница была заморожена {$ban[0]|noescape}
{if $ban[1] !== "app"}
{include "Report/ViewContent.xml", type => $ban[1], object => $ban[2]}
{/if}
</div>
{/if}
{if !$thisUser->getUnbanTime()} {if !$thisUser->getUnbanTime()}
{_banned_perm} {_banned_perm}

View file

@ -38,9 +38,8 @@
<body> <body>
<div id="sudo-banner" n:if="isset($thisUser) && $userTainted"> <div id="sudo-banner" n:if="isset($thisUser) && $userTainted">
<p> <p>
Вы вошли как <b>{$thisUser->getCanonicalName()}</b>. Пожалуйста, уважайте {_you_entered_as} <b>{$thisUser->getCanonicalName()}</b>. {_please_rights}
право на тайну переписки других людей и не злоупотребляйте подменой пользователя. {_click_on} <a href="/setSID/unset?hash={rawurlencode($csrfToken)}">{_there}</a>, {_to_leave}.
Нажмите <a href="/setSID/unset?hash={rawurlencode($csrfToken)}">здесь</a>, чтобы выйти.
</p> </p>
</div> </div>
@ -176,7 +175,7 @@
<a href="{$thisUser->getURL()}" class="link" title="{_my_page} [Alt+Shift+.]" accesskey=".">{_my_page}</a> <a href="{$thisUser->getURL()}" class="link" title="{_my_page} [Alt+Shift+.]" accesskey=".">{_my_page}</a>
<a href="/friends{$thisUser->getId()}" class="link">{_my_friends} <a href="/friends{$thisUser->getId()}" class="link">{_my_friends}
<object type="internal/link" n:if="$thisUser->getFollowersCount() > 0"> <object type="internal/link" n:if="$thisUser->getFollowersCount() > 0">
<a href="/friends{$thisUser->getId()}?act=incoming"> <a href="/friends{$thisUser->getId()}?act=incoming" class="linkunderline">
(<b>{$thisUser->getFollowersCount()}</b>) (<b>{$thisUser->getFollowersCount()}</b>)
</a> </a>
</object> </object>
@ -196,7 +195,7 @@
(<b>{$thisUser->getNotificationsCount()}</b>) (<b>{$thisUser->getNotificationsCount()}</b>)
{/if} {/if}
</a> </a>
<a href="/apps?act=installed" class="link">{_my_apps}</a> <a n:if="$thisUser->getLeftMenuItemStatus('apps')" href="/apps?act=installed" class="link">{_my_apps}</a>
<a href="/settings" class="link">{_my_settings}</a> <a href="/settings" class="link">{_my_settings}</a>
{var $canAccessAdminPanel = $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)} {var $canAccessAdminPanel = $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)}
@ -209,8 +208,23 @@
(<b>{$helpdeskTicketNotAnsweredCount}</b>) (<b>{$helpdeskTicketNotAnsweredCount}</b>)
{/if} {/if}
</a> </a>
<a n:if="$canAccessHelpdesk" href="/scumfeed" class="link">{tr("reports")}
<a n:if="$thisUser->getLeftMenuItemStatus('links')" n:foreach="OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links'] as $menuItem" href="{$menuItem['url']}" target="_blank" class="link">{strpos($menuItem["name"], "@") === 0 ? tr(substr($menuItem["name"], 1)) : $menuItem["name"]}</a> {if $reportNotAnsweredCount > 0}
(<b>{$reportNotAnsweredCount}</b>)
{/if}
</a>
<a n:if="$canAccessHelpdesk" href="/noSpam" class="link">
noSpam
</a>
<a
n:if="$thisUser->getLeftMenuItemStatus('links')"
n:foreach="OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links'] as $menuItem"
href="{$menuItem['url']}"
target="_blank"
class="link">
{strpos($menuItem["name"], "@") === 0 ? tr(substr($menuItem["name"], 1)) : $menuItem["name"]}
</a>
<div id="_groupListPinnedGroups">
<div id="_groupListPinnedGroups"> <div id="_groupListPinnedGroups">
<div n:if="$thisUser->getPinnedClubCount() > 0" class="menu_divider"></div> <div n:if="$thisUser->getPinnedClubCount() > 0" class="menu_divider"></div>
@ -285,6 +299,11 @@
{/ifset} {/ifset}
</div> </div>
</div> </div>
{ifset $thisUser}
{if !$thisUser->isBanned() && !$thisUser->isDeleted()}
</div>
{/if}
{/ifset}
<div class="page_body"> <div class="page_body">
<div id="wrapH"> <div id="wrapH">

View file

@ -12,16 +12,24 @@
{include size, x => $dat} {include size, x => $dat}
{/ifset} {/ifset}
{ifset before_content}
{include before_content, x => $dat}
{/ifset}
{ifset specpage} {ifset specpage}
{include specpage, x => $dat} {include specpage, x => $dat}
{else} {else}
<div class="container_gray"> <div class="container_gray">
{var $data = is_array($iterator) ? $iterator : iterator_to_array($iterator)} {var $data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
{ifset top}
{include top, x => $dat}
{/ifset}
{if sizeof($data) > 0} {if sizeof($data) > 0}
<div class="content" n:foreach="$data as $dat"> <div class="content" n:foreach="$data as $dat">
<table> <table>
<tbody> <tbody n:attr="id => is_null($table_body_id) ? NULL : $table_body_id">
<tr> <tr>
<td valign="top"> <td valign="top">
<a href="{include link, x => $dat}"> <a href="{include link, x => $dat}">

View file

@ -1,12 +1,10 @@
{extends "../@layout.xml"} {extends "../@layout.xml"}
{block title}Ваш браузер устарел{/block} {block title}{_deprecated_browser}{/block}
{block header} {block header}
Устаревший браузер {_deprecated_browser}
{/block} {/block}
{block content} {block content}
Для просмотра этого контента вам понадобится Firefox ESR 52+ или {_deprecated_browser_description}
эквивалентный по функционалу навигатор по всемирной сети интернет.<br/>
Сожалеем об этом.
{/block} {/block}

View file

@ -9,5 +9,5 @@
<div id="faqhead">Для кого этот сайт?</div> <div id="faqhead">Для кого этот сайт?</div>
<div id="faqcontent">Сайт предназначен для поиска друзей и знакомых, а также просмотр данных пользователя. Это как справочник города, с помощью которого люди могут быстро найти актуальную информацию о человеке. Также этот сайт подойдёт для ностальгираторов и тех, кто решил слезть с трубы "ВКонтакте", которого клон и является.<br></div> <div id="faqcontent">Сайт предназначен для поиска друзей и знакомых, а также просмотр данных пользователя. Это как справочник города, с помощью которого люди могут быстро найти актуальную информацию о человеке. Также этот сайт подойдёт для ностальгираторов и тех, кто решил слезть с трубы "ВКонтакте", которого клон и является.<br></div>
Я попозже допишу ок ~~ veselcraft - 12.01.2020 - 22:05 GMT+3 Я попозже допишу ок ~~ veselcraft - 12.01.2020 - 22:05 GMT+3
Давай
{/block} {/block}

View file

@ -2,7 +2,7 @@
{block title}Sandbox{/block} {block title}Sandbox{/block}
{block header} {block header}
Sandbox для разработчиков {_sandbox_for_developers}
{/block} {/block}
{block content} {block content}

View file

@ -124,6 +124,9 @@
<li> <li>
<a href="/admin/settings/tuning">{_admin_settings_tuning}</a> <a href="/admin/settings/tuning">{_admin_settings_tuning}</a>
</li> </li>
<li>
<a href="/admin/logs">Логи</a>
</li>
<li> <li>
<a href="/admin/settings/appearance">{_admin_settings_appearance}</a> <a href="/admin/settings/appearance">{_admin_settings_appearance}</a>
</li> </li>

View file

@ -0,0 +1,86 @@
{extends "./@layout.xml"}
{block title}
{_bans_history}
{/block}
{block heading}
{include title}
{/block}
{block content}
<table class="aui aui-table-list">
<thead>
<tr>
<th>ID</th>
<th>{_bans_history_blocked}</th>
<th>{_bans_history_initiator}</th>
<th>{_bans_history_start}</th>
<th>{_bans_history_end}</th>
<th>{_bans_history_time}</th>
<th>{_bans_history_reason}</th>
<th>{_bans_history_removed}</th>
</tr>
</thead>
<tbody>
<tr n:foreach="$bans as $ban">
<td>{$ban->getId()}</td>
<td>
<span class="aui-avatar aui-avatar-xsmall">
<span class="aui-avatar-inner">
<img src="{$ban->getUser()->getAvatarUrl('miniscule')}"
alt="{$ban->getUser()->getCanonicalName()}" style="object-fit: cover;"
role="presentation"/>
</span>
</span>
<a href="{$ban->getUser()->getURL()}">{$ban->getUser()->getCanonicalName()}</a>
<span n:if="$ban->getUser()->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">
{_admin_banned}
</span>
</td>
<td>
<span class="aui-avatar aui-avatar-xsmall">
<span class="aui-avatar-inner">
<img src="{$ban->getInitiator()->getAvatarUrl('miniscule')}"
alt="{$ban->getInitiator()->getCanonicalName()}" style="object-fit: cover;"
role="presentation"/>
</span>
</span>
<a href="{$ban->getInitiator()->getURL()}">{$ban->getInitiator()->getCanonicalName()}</a>
<span n:if="$ban->getInitiator()->isBanned()"
class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">{_admin_banned}
</span>
</td>
<td>{date('d.m.Y в H:i:s', $ban->getStartTime())}</td>
<td>{date('d.m.Y в H:i:s', $ban->getEndTime())}</td>
<td>{$ban->getTime()}</td>
<td>
{$ban->getReason()}
</td>
<td>
{if $ban->isRemovedManually()}
<span class="aui-avatar aui-avatar-xsmall">
<span class="aui-avatar-inner">
<img src="{$ban->whoRemoved()->getAvatarUrl('miniscule')}"
alt="{$ban->whoRemoved()->getCanonicalName()}" style="object-fit: cover;"
role="presentation"/>
</span>
</span>
<a href="{$ban->whoRemoved()->getURL()}">{$ban->whoRemoved()->getCanonicalName()}</a>
<span n:if="$ban->whoRemoved()->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">
{_admin_banned}
</span>
{else}
<b style="color: red;">{_bans_history_active}</b>
{/if}
</td>
</tr>
</tbody>
</table>
{/block}

View file

@ -0,0 +1,92 @@
{extends "@layout.xml"}
{block title}
{_logs}
{/block}
{block heading}
{_logs}
{/block}
{block content}
{var $amount = sizeof($logs)}
<style>
del, ins { text-decoration: none; color: #000; }
del { background: #fdd; }
ins { background: #dfd; }
</style>
<form class="aui">
<div>
<select class="select medium-field" type="number" id="type" name="type" placeholder="{_logs_change_type}">
<option value="any" n:attr="selected => !$type">{_logs_anything}</option>
<option value="0" n:attr="selected => $type === 0">{_logs_adding}</option>
<option value="1" n:attr="selected => $type === 1">{_logs_editing}</option>
<option value="2" n:attr="selected => $type === 2">{_logs_removing}</option>
<option value="3" n:attr="selected => $type === 3">{_logs_restoring}</option>
</select>
<input class="text medium-field" type="number" id="id" name="id" placeholder="{_logs_id_post}" n:attr="value => $id"/>
<input class="text medium-field" type="text" id="uid" name="uid" placeholder="{_logs_uuid_user}" n:attr="value => $user"/>
</div>
<div style="margin: 8px 0;" />
<div>
<select class="select medium-field" id="obj_type" name="obj_type" placeholder="{_logs_change_object}">
<option value="any" n:attr="selected => !$obj_type">{_logs_anything}</option>
<option n:foreach="$object_types as $type" n:attr="selected => $obj_type === $type">{$type}</option>
</select>
<input class="text medium-field" type="number" id="obj_id" name="obj_id" placeholder="{_logs_id_object}" n:attr="value => $obj_id"/>
<input type="submit" class="aui-button aui-button-primary medium-field" value="Поиск" style="width: 165px;"/>
</div>
</form>
<table class="aui aui-table-list">
<thead>
<tr>
<th>ID</th>
<th>{_logs_user}</th>
<th>{_logs_object}</th>
<th>{_logs_type}</th>
<th>{_logs_changes}</th>
<th>{_logs_time}</th>
</tr>
</thead>
<tbody>
<tr n:foreach="$logs as $log">
<td>{$log->getId()}</td>
<td>
<a href="/admin/chandler/user/{$log->getUser()}" target="_blank">{$log->getUser()}</a>
</td>
<td>
<span n:if="$log->getObjectAvatar()" class="aui-avatar aui-avatar-xsmall">
<span class="aui-avatar-inner">
<img src="{$log->getObjectAvatar()}" alt="{$log->getObjectName()}" style="object-fit: cover;" role="presentation" />
</span>
</span>
<a href="{$log->getObjectURL()}">{$log->getObjectName()}</a>
</td>
<td>{_$log->getTypeNom()}</td>
<td>
{foreach $log->getChanges() as $change}
<div>
<b>{$change["field"]}</b>:
{if array_key_exists('diff', $change)}
{$change["diff"]|noescape}
{else}
<ins>{$change["old_value"]}</ins>
{/if}
</div>
{/foreach}
</td>
<td>
{=new openvk\Web\Util\DateTime($change["ts"])}
</td>
</tr>
</tbody>
</table>
<br/>
<div align="right">
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count}
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">&laquo;</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">&raquo;</a>
</div>
{/block}

View file

@ -1,4 +1,5 @@
{extends "../@layout.xml"} {extends "../@layout.xml"}
{var $canReport = $owner->getId() !== $thisUser->getId()}
{block title} {block title}
{$name} {$name}
@ -6,6 +7,7 @@
{block header} {block header}
{$name} {$name}
<a style="float: right;" onClick="reportApp()" n:if="$canReport ?? false">{_report}</a>
{/block} {/block}
{block content} {block content}
@ -33,5 +35,29 @@
window.appOrigin = {$origin}; window.appOrigin = {$origin};
</script> </script>
<script n:if="$canReport ?? false">
function reportApp() {
uReportMsgTxt = {_going_to_report_app};
uReportMsgTxt += "<br/>"+tr("report_question_text");
uReportMsgTxt += "<br/><br/><b>"+tr("report_reason")+"</b>: <input type='text' id='uReportMsgInput' placeholder='" + tr("reason") + "' />"
MessageBox(tr("report_question"), uReportMsgTxt, [tr("confirm_m"), tr("cancel")], [
(function() {
res = document.querySelector("#uReportMsgInput").value;
xhr = new XMLHttpRequest();
xhr.open("GET", "/report/" + {$id} + "?reason=" + res + "&type=app", true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("reason") === -1)
MessageBox(tr("error"), tr("error_sending_report"), ["OK"], [Function.noop]);
else
MessageBox(tr("action_successfully"), tr("will_be_watched"), ["OK"], [Function.noop]);
});
xhr.send(null);
}),
Function.noop
]);
}
</script>
{script "js/al_games.js"} {script "js/al_games.js"}
{/block} {/block}

View file

@ -1,9 +1,9 @@
{extends "../@layout.xml"} {extends "../@layout.xml"}
{block title}Переход по ссылке заблокирован{/block} {block title}{_transition_is_blocked}{/block}
{block header} {block header}
Предупреждение {_caution}
{/block} {/block}
{block content} {block content}

View file

@ -0,0 +1,22 @@
{extends "../@layout.xml"}
{block title}{$club->getCanonicalName()}{/block}
{block header}{include title}{/block}
{block content}
<center>
<img src="/assets/packages/static/openvk/img/oof.apng" alt="Сообщество заблокировано." style="width: 20%;"/>
<p>
{tr("group_banned", htmlentities($club->getCanonicalName()))|noescape}
<br/>
{_user_banned_comment} <b>{$club->getBanReason()}</b>.
</p>
{if isset($thisUser)}
<p n:if="$thisUser->getChandlerUser()->can('access')->model('admin')->whichBelongsTo(NULL)">
<br />
<a href="/admin/clubs/id{$club->getId()}?act=ban" target="_blank" class="button">{_edit}</a>
</p>
{/if}
</center>
{/block}

View file

@ -55,7 +55,7 @@
<tbody> <tbody>
<tr> <tr>
<td width="120" valign="top"><span class="nobold">{_gender}: </span></td> <td width="120" valign="top"><span class="nobold">{_gender}: </span></td>
<td>{$user->isFemale() ? "женский" : "мужской"}</td> <td>{$user->isFemale() ? tr("female"): tr("male")}</td>
</tr> </tr>
<tr> <tr>
<td width="120" valign="top"><span class="nobold">{_registration_date}: </span></td> <td width="120" valign="top"><span class="nobold">{_registration_date}: </span></td>
@ -82,8 +82,6 @@
</table> </table>
<script n:if="$club->getOwner()->getId() != $user->getId() && $manager && $thisUser->getId() == $club->getOwner()->getId()"> <script n:if="$club->getOwner()->getId() != $user->getId() && $manager && $thisUser->getId() == $club->getOwner()->getId()">
console.log("gayshit");
console.log("сам такой");
function changeOwner(club, newOwner) { function changeOwner(club, newOwner) {
const action = "/groups/" + club + "/setNewOwner/" + newOwner; const action = "/groups/" + club + "/setNewOwner/" + newOwner;

View file

@ -7,12 +7,12 @@
{block content} {block content}
<div> <div>
<h4>Охват</h4> <h4>{_coverage}</h4>
<p>Этот график отображает охват за последние 7 дней.</p> <p>{_coverage_this_week}</p>
<div id="reachChart" style="width: 100%; height: 280px;"></div> <div id="reachChart" style="width: 100%; height: 280px;"></div>
<h4>Просмотры</h4> <h4>{_views}</h4>
<p>Этот график отображает просмотры постов сообщества за последние 7 дней.</p> <p>{_views_this_week}</p>
<div id="viewsChart" style="width: 100%; height: 280px;"></div> <div id="viewsChart" style="width: 100%; height: 280px;"></div>
<style> <style>

View file

@ -9,7 +9,7 @@
<img n:if="$club->isVerified()" <img n:if="$club->isVerified()"
class="name-checkmark" class="name-checkmark"
src="/assets/packages/static/openvk/img/checkmark.png" src="/assets/packages/static/openvk/img/checkmark.png"
alt="Подтверждённая страница" alt="{_verified_page}"
/> />
{/block} {/block}
@ -124,6 +124,7 @@
{/if} {/if}
{if $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)} {if $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)}
<a href="/admin/clubs/id{$club->getId()}" id="profile_link">{_manage_group_action}</a> <a href="/admin/clubs/id{$club->getId()}" id="profile_link">{_manage_group_action}</a>
<a href="/admin/logs?obj_id={$club->getId()}&obj_type=Club" class="profile_link">Последние действия</a>
{/if} {/if}
{if $club->getSubscriptionStatus($thisUser) == false} {if $club->getSubscriptionStatus($thisUser) == false}
<form action="/setSub/club" method="post"> <form action="/setSub/club" method="post">
@ -140,6 +141,34 @@
<input type="submit" id="profile_link" value="{_leave_community}" /> <input type="submit" id="profile_link" value="{_leave_community}" />
</form> </form>
{/if} {/if}
{var $canReport = $thisUser->getId() != $club->getOwner()->getId()}
{if $canReport}
<a class="profile_link" style="display:block;" href="javascript:reportVideo()">{_report}</a>
<script>
function reportVideo() {
uReportMsgTxt = tr("going_to_report_club");
uReportMsgTxt += "<br/>"+tr("report_question_text");
uReportMsgTxt += "<br/><br/><b>"+tr("report_reason")+"</b>: <input type='text' id='uReportMsgInput' placeholder='" + tr("reason") + "' />"
MessageBox(tr("report_question"), uReportMsgTxt, [tr("confirm_m"), tr("cancel")], [
(function() {
res = document.querySelector("#uReportMsgInput").value;
xhr = new XMLHttpRequest();
xhr.open("GET", "/report/" + {$club->getId()} + "?reason=" + res + "&type=group", true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("reason") === -1)
MessageBox(tr("error"), tr("error_sending_report"), ["OK"], [Function.noop]);
else
MessageBox(tr("action_successfully"), tr("will_be_watched"), ["OK"], [Function.noop]);
});
xhr.send(null);
}),
Function.noop
]);
}
</script>
{/if}
</div> </div>
<div> <div>
<div class="content_title_expanded" onclick="hidePanel(this);"> <div class="content_title_expanded" onclick="hidePanel(this);">

View file

@ -0,0 +1,350 @@
{extends "../@layout.xml"}
{block title}noSpam{/block}
{block header}{include title}{/block}
{block content}
<style>
.noSpamIcon {
width: 20px;
height: 20px;
background: url("/assets/packages/static/openvk/img/supp_icons.png");
}
.noSpamIcon-Add {
background-position: 0 0;
}
.noSpamIcon-Delete {
background-position: 0 -21px;
}
</style>
<div class="tabs">{include "Tabs.xml", mode => "form"}</div>
<br/>
<div style="display: flex; border: 1px solid #ECECEC; padding: 8px;">
<div id="noSpam-form" style="width: 50%; border-right: 1px solid #ECECEC;">
<table cellspacing="7" cellpadding="0" width="100%" border="0">
<tbody id="models-list">
<tr id="0-model">
<td width="83px">
<span class="nobold">{_section}:</span>
</td>
<td>
<div style="display: flex; gap: 8px; justify-content: space-between;">
<div id="add-model" class="noSpamIcon noSpamIcon-Add" style="display: none;" />
<select name="model" id="model" class="model initialModel" style="margin-left: -2px;">
<option selected value="none">{_relationship_0}</option>
<option n:foreach="$models as $model" value="{$model}">{$model}</option>
</select>
</div>
</td>
</tr>
</tbody>
</table>
<div style="border-top: 1px solid #ECECEC; margin: 8px 0;"/>
<div id="noSpam-fields" style="display: none;">
<table cellspacing="7" cellpadding="0" width="100%" border="0">
<tbody>
<tr style="width: 129px; border-top: 1px solid #ECECEC;">
<td>
<span class="nobold">{_substring}:</span>
</td>
<td>
<input type="text" name="regex" placeholder="Regex" id="regex">
</td>
</tr>
<tr style="width: 129px; border-top: 1px solid #ECECEC;">
<td>
<span class="nobold">{_n_user}:</span>
</td>
<td>
<input type="text" name="user" placeholder="{_link_to_page}" id="user">
</td>
</tr>
<tr style="width: 129px">
<td>
<span class="nobold">IP:</span>
</td>
<td>
<input type="text" name="ip" id="ip" placeholder="{_or_subnet}">
</td>
</tr>
<tr style="width: 129px">
<td>
<span class="nobold">User-Agent:</span>
</td>
<td>
<input type="text" name="useragent" id="useragent" placeholder="Mozila 1.0 Blablabla/test">
</td>
</tr>
<tr style="width: 129px">
<td>
<span class="nobold">{_time_before}:</span>
</td>
<td>
<input type="datetime-local" name="ts" id="ts">
</td>
</tr>
<tr style="width: 129px">
<td>
<span class="nobold">{_time_after}:</span>
</td>
<td>
<input type="datetime-local" name="te" id="te">
</td>
</tr>
</tbody>
</table>
<textarea style="resize: vertical; width: calc(100% - 6px)" placeholder='city = "Воскресенск" && id = 1'
name="where" id="where"/>
<span style="color: grey; font-size: 8px;">{_where_for_search}</span>
<div style="border-top: 1px solid #ECECEC; margin: 8px 0;"/>
<table cellspacing="7" cellpadding="0" width="100%" border="0">
<tbody>
<tr style="width: 129px; border-top: 1px solid #ECECEC;">
<td>
<span class="nobold">{_block_params}:</span>
</td>
<td>
<select name="ban_type" id="noSpam-ban-type" style="width: 140px;">
<option value="1">{_only_rollback}</option>
<option value="2">{_only_block}</option>
<option value="3">{_rollback_and_block}</option>
</select>
</td>
</tr>
<tr class="banSettings" style="width: 129px; border-top: 1px solid #ECECEC; display: none;">
<td>
<span class="nobold">Причина:</span>
</td>
<td>
<input type="text" name="ban-reason" id="ban-reason" style="width: 140px;" />
</td>
</tr>
<tr class="banSettings" style="width: 129px; border-top: 1px solid #ECECEC; display: none;">
<td>
<span class="nobold">До:</span>
</td>
<td>
<input type="datetime-local" name="unban-time" id="unban-time" style="width: 140px;" />
<br />
<input type="checkbox" name="is_forever" id="is-forever" /> навсегда
</td>
</tr>
</tbody>
</table>
<div style="border-top: 1px solid #ECECEC; margin: 8px 0;"/>
<center>
<div id="noSpam-buttons">
<input id="search" type="submit" value="{_header_search}" class="button"/>
<input id="apply" type="submit" value="{_subm}" class="button" style="display: none;"/>
</div>
<div id="noSpam-loader" style="display: none;">
<img src="/assets/packages/static/openvk/img/loading_mini.gif" style="width: 40px;">
</div>
</center>
</div>
<div id="noSpam-model-not-selected">
<center id="noSpam-model-not-selected-text" style="padding: 71px 25px;">{_select_section_for_start}</center>
<center id="noSpam-model-not-selected-loader" style="display: none;">
<img src="/assets/packages/static/openvk/img/loading_mini.gif" style="width: 40px; margin: 125px 0;">
</center>
</div>
</div>
<div style="width: 50%;">
<center id="noSpam-results-loader" style="display: none;">
<img src="/assets/packages/static/openvk/img/loading_mini.gif" style="width: 40px; margin: 125px 0;">
</center>
<center id="noSpam-results-text" style="margin: 125px 25px;">{_results_will_be_there}</center>
<div id="noSpam-results-block" style="display: none;">
<h4 style="padding: 8px;">{_search_results}
<span style="color: #a2a2a2; font-weight: inherit">
(<span id="noSpam-results-count" style="color: #a2a2a2; font-weight: inherit;"></span> {_cnt}.)
</span>
</h4>
<ul style="padding-inline-start:18px;" id="noSpam-results-list"></ul>
</div>
</div>
</div>
<script>
async function search(ban = false) {
$("#noSpam-results-text").hide();
$("#noSpam-results-block").hide();
$("#apply").hide();
$("#noSpam-buttons").hide();
$("#noSpam-results-loader").show();
$("#noSpam-loader").show();
let models = [];
$(".model").each(function (i) {
let name = $(this).val();
if (!models.includes(name)) {
if (name.length > 0 && name !== "none") {
models.push(name);
}
}
});
models = models.join(",");
let model = $("#model").val();
let regex = $("#regex").val();
let where = $("#where").val();
let ip = $("#ip").val();
let useragent = $("#useragent").val();
let ts = $("#ts").val() ? Math.floor(new Date($("#ts").val()).getTime() / 1000) : null;
let te = $("#te").val() ? Math.floor(new Date($("#te").val()).getTime() / 1000) : null;
let user = $("#user").val();
let ban_reason = $("#ban-reason").val();
let unban_time = $("#unban-time").val() ? Math.floor(new Date($("#unban-time").val()).getTime() / 1000) : null;
let is_forever = $("#is-forever").prop('checked');
console.log(ban_reason, unban_time, is_forever);
await $.ajax({
type: "POST",
url: "/al_abuse/search",
data: {
models: models,
model: model,
q: regex,
where: where,
ban: ban,
ip: ip,
useragent: useragent,
ts: ts,
te: te,
user: user,
ban_reason: ban_reason,
unban_time: unban_time,
is_forever: is_forever,
hash: {=$csrfToken}
},
success: (response) => {
if (response.success) {
console.log(response);
if (response.count > 0) {
$("#noSpam-results-list").empty();
$("#noSpam-results-count").text(response.count);
response.list.forEach((item) => {
const HTML_TAGS_REGEX = /<\/?([^>]+)(>|$)/g;
let fields = "";
Object.entries(item).map(([key, value]) => {
fields += `<b>${ key}</b>: ${ value?.toString()?.replace(HTML_TAGS_REGEX, "[$1]")}<br />`;
});
$("#noSpam-results-list").append(`<li>
<a style="display: block;" onClick="$('#noSpam-result-fields-${ item.__model_name}-${ item.id}').toggle()">
<h4 style="display: inherit; padding: 8px;">${ item.__model_name} #${ item.id}</h4>
</a>
<div style="display: none;" id="noSpam-result-fields-${ item.__model_name}-${ item.id}">${ fields}</div>
</li>`);
});
$("#noSpam-results-block").show();
$("#apply").show();
} else {
$("#noSpam-results-text").text(ban ? tr("operation_successfully") : tr("no_found"));
$("#noSpam-results-text").show();
}
} else {
$("#noSpam-results-text").text(response?.error ?? tr("unknown_error"));
$("#noSpam-results-text").show();
}
},
error: (error) => {
console.error("Error while searching noSpam:", error);
$("#noSpam-results-text").text(tr("error_when_searching"));
$("#noSpam-results-text").show();
}
});
$("#noSpam-buttons").show();
$("#noSpam-loader").hide();
$("#noSpam-results-loader").hide();
}
$("#search").on("click", () => { search(); });
$("input, textarea").keypress((e) => {
if (e.which === 13 && !e.shiftKey) {
e.preventDefault();
search();
}
});
$("#apply").on("click", () => { search(Number($("#noSpam-ban-type").val())); })
async function selectChange(value) {
console.log(value);
if (value !== "none") {
$("#noSpam-fields").hide();
$("#noSpam-model-not-selected").show();
$("#noSpam-model-not-selected-text").hide();
$("#noSpam-model-not-selected-loader").show();
setTimeout(() => {
$("#noSpam-model-not-selected").hide();
$("#noSpam-fields").show();
$("#add-model").show();
$("#noSpam-model-not-selected-loader").hide();
}, 100)
} else {
if ($(".model").not(".initialModel").length === 0) {
$("#noSpam-fields").hide();
$("#noSpam-model-not-selected").show();
$("#noSpam-model-not-selected-loader").show();
setTimeout(() => {
$("#noSpam-model-not-selected-text").show();
$("#noSpam-model-not-selected-loader").hide();
}, 100)
}
}
}
$(".model").change(async (e) => {
selectChange(e.target.value);
})
$("#noSpam-ban-type").change(async (e) => {
if (e.target.value > 1) {
$(".banSettings").show();
} else {
$("#ban-reason").val(null);
$("#unban-time").val(null);
$("#is-forever").prop('checked', false);
$(".banSettings").hide();
}
});
$("#add-model").on("click", () => {
console.log($(".model").length);
$("#models-list").append(`
<tr id="${ $('.model').length}-model">
<td width="83px">
</td>
<td>
<div style="display: flex; gap: 8px; justify-content: space-between;">
<div class="noSpamIcon noSpamIcon-Delete" onClick="deleteModelSelect(${ $('.model').length});"></div>
<select name="model" class="model" style="margin-left: -2px;" onChange="selectChange($(this).val())">
<option selected value="none">{_relationship_0}</option>
{foreach $models as $model}
<option value={$model}>{$model|noescape}</option>
{/foreach}
</select>
</div>
</td>
</tr>`);
});
function deleteModelSelect(id) {
$(`#${ id}-model`).remove();
if ($(".model").length === 0) {
console.log("BLYAT", $(".model"));
$("#noSpam-fields").hide();
$("#noSpam-model-not-selected").show();
$("#noSpam-model-not-selected-loader").show();
setTimeout(() => {
$("#noSpam-model-not-selected-text").show();
$("#noSpam-model-not-selected-loader").hide();
}, 100)
}
}
</script>
{/block}

View file

@ -0,0 +1,9 @@
<div n:attr="id => ($mode === 'form' ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($mode === 'form' ? 'act_tab_a' : 'ki')" href="/noSpam">{_template_ban}</a>
</div>
<div n:attr="id => ($mode === 'templates' ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($mode === 'templates' ? 'act_tab_a' : 'ki')" href="/noSpam?act=templates">{_active_templates}</a>
</div>
<div n:attr="id => ($mode === 'reports' ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($mode === 'reports' ? 'act_tab_a' : 'ki')" href="/scumfeed">{_users_reports}</a>
</div>

View file

@ -0,0 +1,131 @@
{extends "../@layout.xml"}
{block title}{_templates}{/block}
{block header}{include title}{/block}
{block content}
<div class="tabs">{include "Tabs.xml", mode => "templates"}</div>
<style>
table, th, td {
border: 1px solid #ECECEC;
border-collapse: collapse;
border-spacing: 0;
font-family: -apple-system, system-ui, "Helvetica Neue", Roboto, sans-serif;
}
table {
width: 100%;
}
td, th {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 90px;
}
tr:nth-child(odd) {
background-color: #f0f2f5;
}
tr:hover, th:hover {
background-color: #E8EBEF;
}
th {
text-transform: uppercase;
font-size: 0.846em;
color: #626d7a;
}
</style>
<br />
<div>
<table n:if="count($templates) > 0" cellspacing="0" cellpadding="7" width="100%">
<tr>
<th style="text-align: center;">ID</th>
<th>{_n_user}</th>
<th style="text-align: center;">{_section}</th>
<th>{_substring}</th>
<th>Where</th>
<th style="text-align: center;">{_type}</th>
<th style="text-align: center;">{_count}</th>
<th>{_time}</th>
<th style="text-align: center;">{_actions}</th>
</tr>
<tr n:foreach="$templates as $template">
<td id="id-{$template->getId()}" onClick="openTableField('id', {$template->getId()})" style="text-align: center;"><b>{$template->getId()}</b></td>
<td id="user-{$template->getId()}" onClick="openTableField('user', {$template->getId()})">
<a href="{$template->getUser()->getURL()}" target="_blank">{$template->getUser()->getCanonicalName()}</a>
</td>
<td id="model-{$template->getId()}" onClick="openTableField('model', {$template->getId()})" style="text-align: center;">{$template->getModel()}</td>
<td id="regex-{$template->getId()}" onClick="openTableField('regex', {$template->getId()})">
<a>{$template->getRegex() ?? "-"}</a>
</td>
<td id="where-{$template->getId()}" onClick="openTableField('where', {$template->getId()})">
<a>{$template->getRequest() ?? "-"}</a>
</td>
<td id="type-{$template->getId()}" onClick="openTableField('type', {$template->getId()})" style="text-align: center;">{$template->getType()}</td>
<td id="count-{$template->getId()}" onClick="openTableField('count', {$template->getId()})" style="text-align: center;">
{$template->getCount()}
</td>
<td id="time-{$template->getId()}" onClick="openTableField('time', {$template->getId()})">{$template->getTime()}</td>
<td style="text-align: center;">
<div id="noSpam-rollback-{$template->getId()}">
<div id="noSpam-rollback-loader-{$template->getId()}" style="display: none;">
<img src="/assets/packages/static/openvk/img/loading_mini.gif" style="width: 40px;">
</div>
<a n:if="!$template->isRollbacked()" id="noSpam-rollback-template-link-{$template->getId()}" onClick="rollbackTemplate({$template->getId()})">{_roll_back}</a>
<span n:attr="style => $template->isRollbacked() ? '' : 'display: none;'" id="noSpam-rollback-template-rollbacked-{$template->getId()}">{_roll_backed}</span>
</div>
</td>
</tr>
</table>
<div n:if="count($templates) <= 0">
{include "../components/nothing.xml"}
</div>
</div>
<script>
// Full width block
$(".navigation").hide();
$(".page_content").width("100%");
$(".page_body").width("100%").css("margin-right", 0).css("margin-top", "-2px");
$(".tabs").width("100%");
$(".sidebar").css("margin", 0);
$(".page_header").css("position", "initial");
function openTableField(name, id) {
MessageBox(name, $(`#${ name}-${ id}`).text(), ["OK"], [Function.noop]);
}
async function rollbackTemplate(id) {
$(`#noSpam-rollback-template-link-${ id}`).hide();
$(`#noSpam-rollback-template-rollbacked-${ id}`).hide();
$(`#noSpam-rollback-loader-${ id}`).show();
await $.ajax({
type: "POST",
url: "/noSpam?act=rollback",
data: {
id: id,
hash: {=$csrfToken}
},
success: (response) => {
$(`#noSpam-rollback-loader-${ id}`).hide();
if (response.success) {
$(`#noSpam-rollback-template-rollbacked-${ id}`).show();
} else {
NewNotification("Ошибка", (response?.error ?? "Неизвестная ошибка"), "/assets/packages/static/openvk/img/error.png");
$(`#noSpam-rollback-template-link-${ id}`).show();
}
},
error: (error) => {
console.error(error);
NewNotification("Ошибка", "Ошибка при отправке запроса", "/assets/packages/static/openvk/img/error.png");
$(`#noSpam-rollback-loader-${ id}`).hide();
$(`#noSpam-rollback-template-link-${ id}`).show();
}
});
}
</script>
{/block}

View file

@ -13,7 +13,7 @@
<textarea name="html" style="display:none;"></textarea> <textarea name="html" style="display:none;"></textarea>
<div id="editor" style="width:600px;height:300px;border:1px solid grey"></div> <div id="editor" style="width:600px;height:300px;border:1px solid grey"></div>
<p><i><a href="/kb/notes">Кое-что</a> из (X)HTML поддерживается.</i></p> <p><i><a href="/kb/notes">{_something}</a> {_supports_xhtml}</i></p>
<input type="hidden" name="hash" value="{$csrfToken}" /> <input type="hidden" name="hash" value="{$csrfToken}" />
<button class="button">{_save}</button> <button class="button">{_save}</button>

View file

@ -18,7 +18,7 @@
<textarea name="html" style="display:none;"></textarea> <textarea name="html" style="display:none;"></textarea>
<div id="editor" style="width:600px;height:300px;border:1px solid grey"></div> <div id="editor" style="width:600px;height:300px;border:1px solid grey"></div>
<p><i><a href="/kb/notes">Кое-что</a> из (X)HTML поддерживается.</i></p> <p><i><a href="/kb/notes">{_something}</a> {_supports_xhtml}</i></p>
<input type="hidden" name="hash" value="{$csrfToken}" /> <input type="hidden" name="hash" value="{$csrfToken}" />
<button class="button">{_save}</button> <button class="button">{_save}</button>

View file

@ -1,6 +1,6 @@
{extends "../@layout.xml"} {extends "../@layout.xml"}
{block title}Альбом {$album->getName()}{/block} {block title}{_album} {$album->getName()}{/block}
{block header} {block header}
{var $isClub = ($album->getOwner() instanceof openvk\Web\Models\Entities\Club)} {var $isClub = ($album->getOwner() instanceof openvk\Web\Models\Entities\Club)}
@ -18,7 +18,8 @@
{block content} {block content}
<a href="/album{$album->getPrettyId()}"> <a href="/album{$album->getPrettyId()}">
<b>{$album->getPhotosCount()} фотографий</b> {* TODO: Добавить склонения *}
<b>{$album->getPhotosCount()} {_photos}</b>
</a> </a>
{if !is_null($thisUser) && $album->canBeModifiedBy($thisUser) && !$album->isCreatedBySystem()} {if !is_null($thisUser) && $album->canBeModifiedBy($thisUser) && !$album->isCreatedBySystem()}
@ -40,7 +41,7 @@
</a> </a>
<a href="/photo{$photo->getPrettyId()}?from=album{$album->getId()}"> <a href="/photo{$photo->getPrettyId()}?from=album{$album->getId()}">
<img class="album-photo--image" src="{$photo->getURL()}" alt="{$photo->getDescription()}" /> <img class="album-photo--image" src="{$photo->getURLBySizeId('tinier')}" alt="{$photo->getDescription()}" />
</a> </a>
</div> </div>
{/foreach} {/foreach}

View file

@ -58,7 +58,7 @@
{block description} {block description}
<span>{$x->getDescription() ?? $x->getName()}</span><br /> <span>{$x->getDescription() ?? $x->getName()}</span><br />
<span style="color: grey;">{$x->getPhotosCount()} фотографий</span><br /> <span style="color: grey;">{$x->getPhotosCount()} {_photos}</span><br />
<span style="color: grey;">{tr("updated_at", $x->getEditTime() ?? $x->getCreationTime())}</span><br /> <span style="color: grey;">{tr("updated_at", $x->getEditTime() ?? $x->getCreationTime())}</span><br />
<span style="color: grey;">{_created} {$x->getCreationTime()}</span><br /> <span style="color: grey;">{_created} {$x->getCreationTime()}</span><br />
{/block} {/block}

View file

@ -1,5 +1,5 @@
{extends "../@layout.xml"} {extends "../@layout.xml"}
{block title}Изменить альбом{/block} {block title}{_edit_album}{/block}
{block header} {block header}
<a href="{$album->getOwner()->getURL()}">{$album->getOwner()->getCanonicalName()}</a> <a href="{$album->getOwner()->getURL()}">{$album->getOwner()->getCanonicalName()}</a>
@ -14,6 +14,15 @@
{/block} {/block}
{block content} {block content}
<div class="tabs">
<div id="activetabs" class="tab">
<a id="act_tab_a" href="/album{$album->getPrettyId()}/edit">{_edit_album}</a>
</div>
<div class="tab">
<a href="/photos/upload?album={$album->getPrettyId()}">{_add_photos}</a>
</div>
</div>
<form method="post" enctype="multipart/form-data"> <form method="post" enctype="multipart/form-data">
<table cellspacing="6"> <table cellspacing="6">
<tbody> <tbody>

View file

@ -1,5 +1,5 @@
{extends "../@layout.xml"} {extends "../@layout.xml"}
{block title}Изменить фотографию{/block} {block title}{_edit_photo}{/block}
{block header} {block header}
<a href="{$thisUser->getURL()}">{$thisUser->getCanonicalName()}</a> <a href="{$thisUser->getURL()}">{$thisUser->getCanonicalName()}</a>

View file

@ -26,11 +26,11 @@
<hr/> <hr/>
<div style="width: 100%; min-height: 100px;"> <div style="width: 100%; min-height: 100px;" class="ovk-photo-details">
<div style="float: left; min-height: 100px; width: 70%;"> <div style="float: left; min-height: 100px; width: 68%;margin-left: 3px;">
{include "../components/comments.xml", comments => $comments, count => $cCount, page => $cPage, model => "photos", parent => $photo} {include "../components/comments.xml", comments => $comments, count => $cCount, page => $cPage, model => "photos", parent => $photo, custom_id => 999}
</div> </div>
<div style="float: left; min-height: 100px; width: 30%;"> <div style="float:right;min-height: 100px;width: 30%;margin-left: 1px;">
<div> <div>
<h4>{_information}</h4> <h4>{_information}</h4>
<span style="color: grey;">{_info_description}:</span> <span style="color: grey;">{_info_description}:</span>
@ -42,11 +42,38 @@
</div> </div>
<br/> <br/>
<h4>{_actions}</h4> <h4>{_actions}</h4>
{if isset($thisUser) && $thisUser->getId() != $photo->getOwner()->getId()}
{var canReport = true}
{/if}
<div n:if="isset($thisUser) && $thisUser->getId() === $photo->getOwner()->getId()"> <div n:if="isset($thisUser) && $thisUser->getId() === $photo->getOwner()->getId()">
<a href="/photo{$photo->getPrettyId()}/edit" class="profile_link" style="display:block;width:96%;">{_edit}</a> <a href="/photo{$photo->getPrettyId()}/edit" class="profile_link" style="display:block;width:96%;">{_edit}</a>
<a id="_photoDelete" href="/photo{$photo->getPrettyId()}/delete" class="profile_link" style="display:block;width:96%;">{_delete}</a> <a id="_photoDelete" href="/photo{$photo->getPrettyId()}/delete" class="profile_link" style="display:block;width:96%;">{_delete}</a>
</div> </div>
<a href="{$photo->getURL()}" class="profile_link" target="_blank" style="display:block;width:96%;">{_open_original}</a> <a href="{$photo->getURL()}" class="profile_link" target="_blank" style="display:block;width:96%;">{_"open_original"}</a>
<a n:if="$canReport ?? false" class="profile_link" style="display:block;width:96%;" href="javascript:reportPhoto()">{_report}</a>
<script n:if="$canReport ?? false">
function reportPhoto() {
uReportMsgTxt = tr("going_to_report_photo");
uReportMsgTxt += "<br/>"+tr("report_question_text");
uReportMsgTxt += "<br/><br/><b>"+tr("report_reason")+"</b>: <input type='text' id='uReportMsgInput' placeholder='" + tr("reason") + "' />"
MessageBox(tr("report_question"), uReportMsgTxt, [tr("confirm_m"), tr("cancel")], [
(function() {
res = document.querySelector("#uReportMsgInput").value;
xhr = new XMLHttpRequest();
xhr.open("GET", "/report/" + {$photo->getId()} + "?reason=" + res + "&type=photo", true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("reason") === -1)
MessageBox(tr("error"), tr("error_sending_report"), ["OK"], [Function.noop]);
else
MessageBox(tr("action_successfully"), tr("will_be_watched"), ["OK"], [Function.noop]);
});
xhr.send(null);
}),
Function.noop
]);
}
</script>
</div> </div>
</div> </div>
{/block} {/block}

View file

@ -1,20 +1,20 @@
{extends "../@layout.xml"} {extends "../@layout.xml"}
{block title}Удалить фотографию?{/block} {block title}{_delete_photo}{/block}
{block header} {block header}
Удаление фотографии {_delete_photo}
{/block} {/block}
{block content} {block content}
Вы уверены что хотите удалить эту фотографию? {_sure_deleting_photo}
<br/> <br/>
<br/> <br/>
<form method="POST"> <form method="POST">
<input type="hidden" value="{$csrfToken}" name="hash" /> <input type="hidden" value="{$csrfToken}" name="hash" />
<a href="{$_SERVER['HTTP_REFERER']}" class="button">Нет</a> <a href="{$_SERVER['HTTP_REFERER']}" class="button">{_no}</a>
&nbsp; &nbsp;
<button class="button">Да</button> <button class="button">{_yes}</button>
</form> </form>
{/block} {/block}

View file

@ -2,9 +2,13 @@
{block title}{_upload_photo}{/block} {block title}{_upload_photo}{/block}
{block header} {block header}
<a href="{$thisUser->getURL()}">{$thisUser->getCanonicalName()}</a> <a href="{$album->getOwner()->getURL()}">{$album->getOwner()->getCanonicalName()}</a>
» »
<a href="/albums{$thisUser->getId()}">{_albums}</a> {if $album->getOwner() instanceof openvk\Web\Models\Entities\Club}
<a href="/albums{$album->getOwner()->getId() * -1}">{_albums}</a>
{else}
<a href="/albums{$album->getOwner()->getId()}">{_albums}</a>
{/if}
» »
<a href="/album{$album->getPrettyId()}">{$album->getName()}</a> <a href="/album{$album->getPrettyId()}">{$album->getName()}</a>
» »
@ -12,32 +16,53 @@
{/block} {/block}
{block content} {block content}
<form action="/photos/upload?album={$album->getPrettyId()}" method="post" enctype="multipart/form-data"> <div class="tabs">
<table cellspacing="6"> <div class="tab">
<tbody> <a href="/album{$album->getPrettyId()}/edit">{_edit_album}</a>
<tr> </div>
<td width="120" valign="top"><span class="nobold">{_description}:</span></td> <div id="activetabs" class="tab">
<td><textarea style="margin: 0px; height: 50px; width: 159px; resize: none;" name="desc"></textarea></td> <a id="act_tab_a" href="#">{_add_photos}</a>
</tr> </div>
<tr> </div>
<td width="120" valign="top"><span class="nobold">{_photo}:</span></td>
<td>
<label class="button" style="">{_browse}
<input type="file" id="blob" name="blob" style="display: none;" onchange="filename.innerHTML=blob.files[0].name" />
</label>
<div id="filename" style="margin-top: 10px;"></div>
</td>
</tr>
<tr>
<td width="120" valign="top"></td>
<td>
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" class="button" name="submit" value="Загрузить" />
</td>
</tr>
</tbody>
</table>
<input n:ifset="$_GET['album']" type="hidden" name="album" value="{$_GET['album']}" /> <input type="file" accept=".jpg,.png,.gif" name="files[]" multiple class="button" id="uploadButton" style="display:none">
</form>
<div class="container_gray" style="min-height: 344px;">
<div class="insertThere"></div>
<div class="whiteBox" style="display: block;">
<div class="boxContent">
<h4>{_uploading_photos_from_computer}</h4>
<div class="limits" style="margin-top:17px">
<b style="color:#45688E">{_admin_limits}</b>
<ul class="blueList" style="margin-left: -25px;margin-top: 1px;">
<li>{_supported_formats}</li>
<li>{_max_load_photos}</li>
</ul>
<div style="text-align: center;padding-top: 4px;" class="insertAgain">
<input type="button" class="button" id="fakeButton" onclick="uploadButton.click()" value="{_upload_picts}">
</div>
<div class="tipping" style="margin-top: 19px;">
<span style="line-height: 15px"><b>{_tip}</b>: {_tip_ctrl}</span>
</div>
</div>
</div>
</div>
<div class="insertPhotos" id="photos" style="margin-top: 9px;padding-bottom: 12px;"></div>
<input type="button" class="button" style="display:none;margin-left: auto;margin-right: auto;" id="endUploading" value="{_end_uploading}">
</div>
<input n:ifset="$_GET['album']" type="hidden" id="album" value="{$_GET['album']}" />
<script>
uploadButton.value = ''
</script>
{/block}
{block bodyScripts}
{script "js/al_photos.js"}
{/block} {/block}

View file

@ -0,0 +1,60 @@
{extends "../@listView.xml"}
{var iterator = iterator_to_array($reports)}
{var page = $paginatorConf->page}
{var table_body_id = "reports"}
{block tabs}{include "../NoSpam/Tabs.xml", mode => "reports"}{/block}
{block before_content}
{include "./Tabs.xml", mode => $mode}
{/block}
{block title}{_list_of_reports}{/block}
{block header}
{_list_of_reports}
{/block}
{block actions}
{/block}
{block top}
{if !is_null($orig)}
<h4>Дубликаты жалобы №{$orig}</h4>
{/if}
{/block}
{* BEGIN ELEMENTS DESCRIPTION *}
{block link|strip|stripHtml}
/admin/report{$x->getId()}
{/block}
{block preview}
<center><img src="/assets/packages/static/openvk/img/note_icon.png" style="margin-top: 17px;" /></center>
{/block}
{block name}
Жалоба №{$x->getId()}
{/block}
{block description}
<a href="{$x->getReportAuthor()->getURL()}">
{$x->getReportAuthor()->getCanonicalName()}
</a>
пожаловал{!$x->getReportAuthor()->isFemale() ? 'ся' : 'ась'} на
{if $x->getContentType() === "user"}<a href="{$x->getContentObject()->getURL()}">{/if}
{$x->getContentName()}
{if $x->getContentType() === "user"}</a>{/if}
{if $x->hasDuplicates() && !$orig}
<br />
<b>Другие жалобы на этот контент: <a href="/scumfeed?orig={$x->getId()}">{$x->getDuplicatesCount()} шт.</a></b>
{/if}
{/block}
{block bottom}
<center id="reports-loader" style="display: none; padding: 64px;">
<img src="/assets/packages/static/openvk/img/loading_mini.gif" style="width: 40px;">
</center>
{/block}

View file

@ -0,0 +1,145 @@
<style>
.reportsTabs {
display: flex;
flex-wrap: wrap;
justify-content: center;
row-gap: 4px;
gap: 4px;
padding: 8px;
}
.reportsTabs .tab {
display: flex;
flex: 0 0 calc(16.66% - 20px);
justify-content: center;
border-radius: 3px;
padding: 4px;
margin: 0;
cursor: pointer;
}
</style>
<center class="tabs reportsTabs stupid-fix">
<div n:attr="id => ($mode === 'all' ? 'activetabs' : 'ki')" class="tab" mode="all">
<a n:attr="id => ($mode === 'all' ? 'act_tab_a' : 'ki')" mode="all">Все</a>
</div>
<div n:attr="id => ($mode === 'post' ? 'activetabs' : 'ki')" class="tab" mode="post">
<a n:attr="id => ($mode === 'post' ? 'act_tab_a' : 'ki')">Записи</a>
</div>
<div n:attr="id => ($mode === 'photo' ? 'activetabs' : 'ki')" class="tab" mode="photo">
<a n:attr="id => ($mode === 'photo' ? 'act_tab_a' : 'ki')">Фотографии</a>
</div>
<div n:attr="id => ($mode === 'video' ? 'activetabs' : 'ki')" class="tab" mode="video">
<a n:attr="id => ($mode === 'video' ? 'act_tab_a' : 'ki')">Видеозаписи</a>
</div>
<div n:attr="id => ($mode === 'group' ? 'activetabs' : 'ki')" class="tab" mode="group">
<a n:attr="id => ($mode === 'group' ? 'act_tab_a' : 'ki')">Сообщества</a>
</div>
<div n:attr="id => ($mode === 'comment' ? 'activetabs' : 'ki')" class="tab" mode="comment">
<a n:attr="id => ($mode === 'comment' ? 'act_tab_a' : 'ki')">Комментарии</a>
</div>
<div n:attr="id => ($mode === 'note' ? 'activetabs' : 'ki')" class="tab" mode="note">
<a n:attr="id => ($mode === 'note' ? 'act_tab_a' : 'ki')">Заметки</a>
</div>
<div n:attr="id => ($mode === 'app' ? 'activetabs' : 'ki')" class="tab" mode="app">
<a n:attr="id => ($mode === 'app' ? 'act_tab_a' : 'ki')">Приложения</a>
</div>
<div n:attr="id => ($mode === 'user' ? 'activetabs' : 'ki')" class="tab" mode="user">
<a n:attr="id => ($mode === 'user' ? 'act_tab_a' : 'ki')">Пользователи</a>
</div>
</center>
<script>
async function getReports(mode) {
let _content = $(".content").length;
$(".container_gray").empty();
await $.ajax({
type: "POST",
url: `/scumfeed?act=${ mode}`,
data: {
hash: {=$csrfToken}
},
success: (response) => {
if (response?.reports?.length != _content) {
NewNotification("Обратите внимание", "В списке появились новые жалобы. Работа ждёт :)");
}
if (response.reports.length > 0) {
response.reports.forEach((report) => {
$(".container_gray").append(`
<div class="content">
<table>
<tbody>
<tr>
<td valign="top">
<a href="/admin/report${ report.id}">
<center>
<img src="/assets/packages/static/openvk/img/note_icon.png" style="margin-top: 17px;">
</center>
</a>
</td>
<td valign="top" style="width: 100%">
<a href="/admin/report${ report.id}">
<b>
Жалоба №${ report.id}
</b>
</a>
<br>
<a href="${ report.author.url}">
${ report.author.name}
</a>
пожаловал${ report.author.is_female ? "ась" : "ся"} на
${ report.content.type === "user" ? `<a href="${ report.content.url}">` : ''}
${ report.content.name}
${ report.content.type === "user" ? '</a>' : ''}
${ report.duplicates > 0 ? `
<br />
<b>Другие жалобы на этот контент: <a href="/scumfeed?orig=${ report.id}">${ report.duplicates} шт.</a></b>
` : ''}
</td>
<td valign="top" class="action_links" style="width: 150px;">
</td>
</tr>
</tbody>
</table>
</div>
`);
});
} else {
$(".content table").width("100%")
$(".container_gray").html(`
<center style="background: white;border: #DEDEDE solid 1px;">
<span style="color: #707070;margin: 60px 0;display: block;">
{_no_data_description|noescape}
</span>
</center>
`);
}
}
});
}
$(".reportsTabs .tab").on("click", async function () {
let mode = $(this).attr("mode");
$(".reportsTabs #activetabs").attr("id", "ki");
$(".reportsTabs #act_tab_a").attr("id", "ki");
$(`.reportsTabs .tab[mode='${ mode}']`).attr("id", "activetabs");
$(`.reportsTabs .tab[mode='${ mode}'] a`).attr("id", "act_tab_a");
$(".container_gray").hide();
$("#reports-loader").show();
history.pushState(null, null, `/scumfeed?act=${ mode}`);
await getReports(mode);
$(".container_gray").show();
$("#reports-loader").hide();
});
setInterval(async () => {
await getReports($(".reportsTabs #activetabs").attr("mode"));
}, 10000);
</script>

View file

@ -0,0 +1,37 @@
{extends "../@layout.xml"}
{block title}{$report->getReason()}{/block}
{block header}
<a href="/admin/support/reports">{_list_of_reports}</a>
»
{_report_number}{$report->getId()}
{/block}
{block content}
<div class="tabs">{include "../NoSpam/Tabs.xml", mode => "reports"}</div>
<br />
<p>
<b>{$report->getReportAuthor()->getCanonicalName()}</b> пожаловался на <b>{$report->getContentName()}</b>
<br />
<b>{_comment}:</b> {$report->getReason()}
</p>
{include "ViewContent.xml", type => $report->getContentType(), object => $report->getContentObject()}
<center>
<form action="/admin/reportAction{$report->getId()}" method="post">
<center>
<form n:if="$report->getContentType() != 'group'" action="/admin/reportAction{$report->getId()}" method="post">
<input type="hidden" name="hash" value="{$csrfToken}"/>
<input type="submit" name="ban" value="{_ban_user_action}" class="button">
<input n:if="$report->getContentType() !== 'user'" type="submit" name="delete" value="{_delete}" class="button">
<input type="submit" name="ignore" value="{_ignore_report}" class="button">
</form>
<form n:if="$report->getContentType() == 'group'" action="/admin/reportAction{$report->getId()}" method="post">
<input type="hidden" name="hash" value="{$csrfToken}"/>
<input type="submit" name="banClubOwner" value="Заблокировать создателя" class="button">
<input type="submit" name="banClub" value="Заблокировать группу" class="button">
<input type="submit" name="ignore" value="{_ignore_report}" class="button">
</form>
</center>
</form>
{/block}

View file

@ -0,0 +1,30 @@
{block ViewContent}
<div class="container_gray" style="margin-top: 16px; margin-bottom: 16px; max-width: 100%;">
{if $type == "post"}
{include "../components/post/oldpost.xml",
post => $object,
forceNoDeleteLink => true,
forceNoPinLink => true,
forceNoCommentsLink => true,
forceNoShareLink => true,
forceNoLike => true
}
{elseif $type == "photo"}
{include "./content/photo.xml", photo => $object}
{elseif $type == "video"}
{include "./content/video.xml", video => $object}
{elseif $type == "group" || $type == "user"}
{include "../components/group.xml", group => $object, isUser => $type == "user"}
{elseif $type == "comment"}
{include "../components/comment.xml", comment => $object, timeOnly => true, linkWithPost => true}
{elseif $type == "note"}
{include "./content/note.xml", note => $object}
{elseif $type == "app"}
{if $appsSoftDeleting}
{include "./content/app.xml", app => $object}
{/if}
{else}
{include "../components/error.xml", description => tr("version_incompatibility")}
{/if}
</div>
{/block}

View file

@ -0,0 +1,22 @@
{block content}
<div class="content">
<table>
<tbody>
<tr>
<td valign="top">
<a href="/app{$app->getId()}">
<img style="max-width: 75px;" src="{$app->getAvatarUrl()}" />
</a>
</td>
<td valign="top" style="width: 100%">
<a href="/app{$app->getId()}">
<b>{$app->getName()}</b>
</a>
<br/>
{$app->getDescription()}
</td>
</tr>
</tbody>
</table>
</div>
{/block}

View file

@ -0,0 +1,18 @@
{block content}
<article id="userContent" style="margin: 10px 10px 0;">
<div class="note_header">
<div class="note_title">
<div class="note_title">
<a>{$note->getName()}</a>
</div>
</div>
<div class="byline">
<span><a href="{$note->getOwner()->getURL()}">{$note->getOwner()->getCanonicalName()}</a></span> {$note->getPublicationTime()}
<span n:if="$note->getEditTime() > $note->getPublicationTime()">({_edited} {$note->getEditTime()})</span>
</div>
</div>
<div style="margin-left: 6px; width: 535px;">
{$note->getText()|noescape}
</div>
</article>
{/block}

View file

@ -0,0 +1,26 @@
{block content}
<div class="content">
<center style="margin-bottom: 8pt;">
<img src="{$photo->getURLBySizeId('large')}" style="max-width: 80%; max-height: 60vh;" />
</center>
<table>
<tbody>
<tr>
<td valign="top">
</td>
<td valign="top" style="width: 100%">
<div>
<h4>{_information}</h4>
<span style="color: grey;">{_info_description}:</span>
{$photo->getDescription() ?? "(" . tr("none") . ")"}<br/>
<span style="color: grey;">{_info_uploaded_by}:</span>
<a href="{$photo->getOwner()->getURL()}">{$photo->getOwner()->getFullName()}</a><br/>
<span style="color: grey;">{_info_upload_date}:</span>
{$photo->getPublicationTime()}
</div>
</td>
</tr>
</tbody>
</table>
</div>
{/block}

View file

@ -0,0 +1,32 @@
{block content}
<div class="content">
<table>
<tbody>
<tr>
<td valign="top">
<a href="/video{$video->getPrettyId()}">
<div class="video-preview">
<img src="{$video->getThumbnailURL()}"
alt="{$video->getName()}"
style="max-width: 170px; max-height: 127px; margin: auto;" />
</div>
</a>
</td>
<td valign="top" style="width: 100%">
<a href="/video{$video->getPrettyId()}">
<b>
{$video->getName()}
</b>
</a>
<br>
<p>
<span>{$video->getDescription() ?? ""}</span>
</p>
<span style="color: grey;">{_video_uploaded} {$video->getPublicationTime()}</span><br/>
<span style="color: grey;">{_video_updated} {$video->getEditTime() ?? $video->getPublicationTime()}</span>
</td>
</tr>
</tbody>
</table>
</div>
{/block}

View file

@ -55,7 +55,6 @@
<input name="name" type="text" value="{$agent->getCanonicalName()}" placeholder="{_helpdesk_agent} #777" /> <input name="name" type="text" value="{$agent->getCanonicalName()}" placeholder="{_helpdesk_agent} #777" />
<br/><br/> <br/><br/>
<label for="number">{_helpdesk_show_number}?</label> <label for="number">{_helpdesk_show_number}?</label>
{$agent->isShowNumber()}
<select name="number"> <select name="number">
<option value="1" n:attr="selected => $agent->isShowNumber() === 1 ? true : false">{_yes}</option> <option value="1" n:attr="selected => $agent->isShowNumber() === 1 ? true : false">{_yes}</option>
<option value="0" n:attr="selected => $agent->isShowNumber() === 0 ? true : false">{_no}</option> <option value="0" n:attr="selected => $agent->isShowNumber() === 0 ? true : false">{_no}</option>
@ -71,7 +70,7 @@
</div> </div>
</div> </div>
{else} {else}
<h4>Создать</h4> <h4>{_create}</h4>
<br/> <br/>
<form method="post" action="/support/agent{$agent_id}/edit"> <form method="post" action="/support/agent{$agent_id}/edit">
<label for="name">{_helpdesk_showing_name}</label> <label for="name">{_helpdesk_showing_name}</label>

View file

@ -8,7 +8,42 @@
{block content} {block content}
<div class="post-author"> <div class="post-author">
<a href="#" style="font-size: 13px;"><b>{$ticket->getName()}</b></a><br /> <a href="#" style="font-size: 13px;"><b>{$ticket->getName()}</b></a><br />
{_author}: <a href="/id{$ticket->getUser()->getId()}">{$ticket->getUser()->getFullName()}</a> | {$ticket->getUser()->getRegistrationIP()} | {_status}: {$ticket->getStatus()}. {_author}:
<a href="/id{$ticket->getUser()->getId()}">
{$ticket->getUser()->getFullName()}</a>
| {$ticket->getUser()->getRegistrationIP()}
| {_status}: {$ticket->getStatus()}.
| <b n:if="$ticket->getUser()->isBanned()" style="color: red; cursor: pointer;" onclick="$('#ban-reason').toggle();">Блокировка</b>
<div id="ban-reason" style="display: none; padding: 8px;">
<h4 style="padding: 8px;">Причина блокировки</h4>
<div style="padding: 8px;">Так пользователь видит экран с информацией о блокировке:</div>
<div style="padding: 16px; border: 1px solid #C4C4C4; margin: 8px;">
{var $ban = $ticket->getUser()->getBanReason("banned")}
<center>
<img src="/assets/packages/static/openvk/img/oof.apng" alt="{_banned_alt}" style="width: 20%;" />
</center>
<p>
{if is_string($ban)}
{tr("banned_1", htmlentities($ticket->getUser()->getCanonicalName()))|noescape}<br/>
{tr("banned_2", htmlentities($ban))|noescape}
{else}
{tr("banned_1", htmlentities($ticket->getUser()->getCanonicalName()))|noescape}
<div>
Эта страница была заморожена {$ban[0]|noescape}
{if $ban[1] !== "app"}
{include "../Report/ViewContent.xml", type => $ban[1], object => $ban[2]}
{/if}
</div>
{/if}
{if !$ticket->getUser()->getUnbanTime()}
{_banned_perm}
{else}
{tr("banned_until_time", $ticket->getUser()->getUnbanTime())|noescape}
{/if}
</p>
</div>
</div>
</div> </div>
<div class="text" style="padding-top: 10px; border-bottom: #ECECEC solid 1px;"> <div class="text" style="padding-top: 10px; border-bottom: #ECECEC solid 1px;">
{$ticket->getText()|noescape} {$ticket->getText()|noescape}

View file

@ -19,7 +19,7 @@
<a n:attr="id => ($act === 'closed' ? 'act_tab_a' : 'ki')" href="?act=closed">{_support_closed}</a> <a n:attr="id => ($act === 'closed' ? 'act_tab_a' : 'ki')" href="?act=closed">{_support_closed}</a>
</div> </div>
<div class="tab"> <div class="tab">
<a href="/support/agent{$thisUser->getId()}">Мой профиль</a> <a href="/support/agent{$thisUser->getId()}">{_agent_profile}</a>
</div> </div>
{/block} {/block}

View file

@ -27,6 +27,14 @@
function errorHandler(id, mark) { function errorHandler(id, mark) {
document.getElementById("markText-" + id).innerHTML = {_error}; document.getElementById("markText-" + id).innerHTML = {_error};
} }
function closeTicket() {
let url = `/support/ticket${{$ticket->getId()}}/close?hash=${{urlencode($csrfToken)}}`;
$.ajax(url, {
error: () => alert(tr("error")),
success: () => location.reload()
});
}
</script> </script>
{if $ticket->isDeleted() == 0} {if $ticket->isDeleted() == 0}
@ -34,6 +42,11 @@
<a href="#" style="font-size:13px;"><b>{$ticket->getName()}</b></a> <a href="#" style="font-size:13px;"><b>{$ticket->getName()}</b></a>
<br />{_status}: {$ticket->getStatus()} <br />{_status}: {$ticket->getStatus()}
</div> </div>
{if $ticket->getType() === 1}
<div class="post-author" style="border-top: none; padding: 14px; margin-top: 14px;">
{_you_can_close_this_ticket_1} <a onClick="closeTicket()">{_you_can_close_this_ticket_2}</a>.
</div>
{/if}
<div class="text" style="padding-top: 10px; border-bottom: #ECECEC solid 1px;"> <div class="text" style="padding-top: 10px; border-bottom: #ECECEC solid 1px;">
{$ticket->getText()|noescape} {$ticket->getText()|noescape}
<br /></br> <br /></br>

View file

@ -344,9 +344,9 @@
<form method="POST" enctype="multipart/form-data"> <form method="POST" enctype="multipart/form-data">
<div id="backdropEditor"> <div id="backdropEditor">
<div id="backdropFilePicker"> <div id="backdropFilePicker">
<label class="button" style="">Обзор<input type="file" accept="image/*" name="backdrop1" style="display: none;"></label> <label class="button" style="">{_browse}<input type="file" accept="image/*" name="backdrop1" style="display: none;"></label>
<div id="spacer" style="width: 366px;"></div> <div id="spacer" style="width: 366px;"></div>
<label class="button" style="">Обзор<input type="file" accept="image/*" name="backdrop2" style="display: none;"></label> <label class="button" style="">{_browse}<input type="file" accept="image/*" name="backdrop2" style="display: none;"></label>
<div id="spacer" style="width: 366px;"></div> <div id="spacer" style="width: 366px;"></div>
</div> </div>
</div> </div>

View file

@ -190,13 +190,13 @@
<script> <script>
function viewBackupCodes() { function viewBackupCodes() {
MessageBox("Просмотр резервных кодов", ` MessageBox(tr("viewing_backup_codes"), `
<form id="back-codes-view-form" method="post" action="/settings/2fa"> <form id="back-codes-view-form" method="post" action="/settings/2fa">
<label for="password">Пароль</label> <label for="password">Пароль</label>
<input type="password" id="password" name="password" required /> <input type="password" id="password" name="password" required />
<input type="hidden" name="hash" value={$csrfToken} /> <input type="hidden" name="hash" value={$csrfToken} />
</form> </form>
`, ["Просмотреть", "Отменить"], [ `, [tr("viewing"), tr("cancel")], [
() => { () => {
document.querySelector("#back-codes-view-form").submit(); document.querySelector("#back-codes-view-form").submit();
}, Function.noop }, Function.noop
@ -204,13 +204,13 @@
} }
function disableTwoFactorAuth() { function disableTwoFactorAuth() {
MessageBox("Отключить 2FA", ` MessageBox(tr("disable_2fa"), `
<form id="two-factor-auth-disable-form" method="post" action="/settings/2fa/disable"> <form id="two-factor-auth-disable-form" method="post" action="/settings/2fa/disable">
<label for="password">Пароль</label> <label for="password">Пароль</label>
<input type="password" id="password" name="password" required /> <input type="password" id="password" name="password" required />
<input type="hidden" name="hash" value={$csrfToken} /> <input type="hidden" name="hash" value={$csrfToken} />
</form> </form>
`, ["Отключить", "Отменить"], [ `, [tr("disable"), tr("cancel")], [
() => { () => {
document.querySelector("#two-factor-auth-disable-form").submit(); document.querySelector("#two-factor-auth-disable-form").submit();
}, Function.noop }, Function.noop
@ -650,6 +650,16 @@
<td> <td>
<span class="nobold">{_my_feed}</span> <span class="nobold">{_my_feed}</span>
</td> </td>
</tr><tr>
<td width="120" valign="top" align="right" align="right">
<input
n:attr="checked => $user->getLeftMenuItemStatus('apps')"
type="checkbox"
name="menu_aplikoj" />
</td>
<td>
<span class="nobold">{_my_apps}</span>
</td>
</tr><tr n:if="sizeof(OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links']) > 0"> </tr><tr n:if="sizeof(OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links']) > 0">
<td width="120" valign="top" align="right" align="right"> <td width="120" valign="top" align="right" align="right">
<input <input

View file

@ -1,13 +1,13 @@
{extends "../@layout.xml"} {extends "../@layout.xml"}
{block title}Подтвердить номер телефона{/block} {block title}{_verify_phone_number}{/block}
{block header} {block header}
Подтвердить номер телефона {_verify_phone_number}
{/block} {/block}
{block content} {block content}
<center> <center>
<p>Мы отправили SMS с кодом на номер <b>{substr_replace($change->number, "*****", 5, 5)}</b>, введите его сюда:</p> <p>{_we_sended_first} <b>{substr_replace($change->number, "*****", 5, 5)}</b>, {_we_sended_end}:</p>
<form method="POST"> <form method="POST">
<input type="text" name="code" placeholder="34156, например" required /> <input type="text" name="code" placeholder="34156, например" required />

View file

@ -118,6 +118,12 @@
<a href="javascript:warnUser()" class="profile_link" style="width: 194px;"> <a href="javascript:warnUser()" class="profile_link" style="width: 194px;">
{_warn_user_action} {_warn_user_action}
</a> </a>
<a href="/admin/user{$user->getId()}/bans" class="profile_link">
{_blocks}
</a>
<a href="/admin/logs?uid={$user->getId()}" class="profile_link" style="width: 194px;">
{_last_actions}
</a>
{/if} {/if}
{if $thisUser->getChandlerUser()->can('write')->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)} {if $thisUser->getChandlerUser()->can('write')->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
@ -163,6 +169,31 @@
<input type="submit" class="profile_link" value="{_friends_delete}" style="width: 194px;" /> <input type="submit" class="profile_link" value="{_friends_delete}" style="width: 194px;" />
</form> </form>
{/if} {/if}
<a class="profile_link" style="display:block;width:96%;" href="javascript:reportUser()">{_report}</a>
<script>
function reportUser() {
uReportMsgTxt = tr("going_to_report_user");
uReportMsgTxt += "<br/>"+tr("report_question_text");
uReportMsgTxt += "<br/><br/><b>"+tr("report_reason")+"</b>: <input type='text' id='uReportMsgInput' placeholder='" + tr("reason") + "' />"
MessageBox(tr("report_question"), uReportMsgTxt, [tr("confirm_m"), tr("cancel")], [
(function() {
res = document.querySelector("#uReportMsgInput").value;
xhr = new XMLHttpRequest();
xhr.open("GET", "/report/" + {$user->getId()} + "?reason=" + res + "&type=user", true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("reason") === -1)
MessageBox(tr("error"), tr("error_sending_report"), ["OK"], [Function.noop]);
else
MessageBox(tr("action_successfully"), tr("will_be_watched"), ["OK"], [Function.noop]);
});
xhr.send(null);
}),
Function.noop
]);
}
</script>
{/if} {/if}
<a style="width: 194px;" n:if="$user->getFollowersCount() > 0" href="/friends{$user->getId()}?act=incoming" class="profile_link">{tr("followers", $user->getFollowersCount())}</a> <a style="width: 194px;" n:if="$user->getFollowersCount() > 0" href="/friends{$user->getId()}?act=incoming" class="profile_link">{tr("followers", $user->getFollowersCount())}</a>
</div> </div>
@ -601,13 +632,15 @@
uBanMsgTxt += "<br/><b>Предупреждение</b>: Это действие удалит все подписки пользователя и отпишет всех от него."; uBanMsgTxt += "<br/><b>Предупреждение</b>: Это действие удалит все подписки пользователя и отпишет всех от него.";
uBanMsgTxt += "<br/><br/><b>Причина бана</b>: <input type='text' id='uBanMsgInput' placeholder='придумайте что-нибудь крутое' />" uBanMsgTxt += "<br/><br/><b>Причина бана</b>: <input type='text' id='uBanMsgInput' placeholder='придумайте что-нибудь крутое' />"
uBanMsgTxt += "<br/><br/><b>Заблокировать до</b>: <input type='date' id='uBanMsgDate' />"; uBanMsgTxt += "<br/><br/><b>Заблокировать до</b>: <input type='date' id='uBanMsgDate' />";
uBanMsgTxt += "<br/><br/><input id='uBanMsgIncr' type='checkbox' checked='1'/>Автоматически <b>(до " + {date('d.m.Y H\h', time() + $user->getNewBanTime())} + ")</b>";
MessageBox("Забанить " + {$user->getFirstName()}, uBanMsgTxt, ["Подтвердить", "Отмена"], [ MessageBox("Забанить " + {$user->getFirstName()}, uBanMsgTxt, ["Подтвердить", "Отмена"], [
(function() { (function() {
res = document.querySelector("#uBanMsgInput").value; res = document.querySelector("#uBanMsgInput").value;
date = document.querySelector("#uBanMsgDate").value; date = document.querySelector("#uBanMsgDate").value;
incr = document.querySelector("#uBanMsgIncr").checked ? '1' : '0';
xhr = new XMLHttpRequest(); xhr = new XMLHttpRequest();
xhr.open("GET", "/admin/ban/" + {$user->getId()} + "?reason=" + res + "&date=" + date + "&hash=" + {rawurlencode($csrfToken)}, true); xhr.open("GET", "/admin/ban/" + {$user->getId()} + "?reason=" + res + "&incr=" + incr + "&date=" + date + "&hash=" + {rawurlencode($csrfToken)}, true);
xhr.onload = (function() { xhr.onload = (function() {
if(xhr.responseText.indexOf("success") === -1) if(xhr.responseText.indexOf("success") === -1)
MessageBox("Ошибка", "Не удалось забанить пользователя...", ["OK"], [Function.noop]); MessageBox("Ошибка", "Не удалось забанить пользователя...", ["OK"], [Function.noop]);

View file

@ -3,7 +3,9 @@
<p> <p>
{tr("user_banned", htmlentities($user->getFirstName()))|noescape}<br/> {tr("user_banned", htmlentities($user->getFirstName()))|noescape}<br/>
{_user_banned_comment} <b>{$user->getBanReason()}</b>.<br/> {_user_banned_comment} <b>{$user->getBanReason()}</b>.<br/>
Пользователь заблокирован до: <b>{$user->getUnbanTime()}</b> {_user_is_blocked}
<span n:if="$user->getUnbanTime() !== NULL">{_before}: <b>{$user->getUnbanTime()}</b></span>
<span n:if="$user->getUnbanTime() === NULL"><b>{_forever}</b></span>
</p> </p>
{if isset($thisUser)} {if isset($thisUser)}
<p n:if="$thisUser->getChandlerUser()->can('access')->model('admin')->whichBelongsTo(NULL) || $thisUser->getChandlerUser()->can('write')->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)"> <p n:if="$thisUser->getChandlerUser()->can('access')->model('admin')->whichBelongsTo(NULL) || $thisUser->getChandlerUser()->can('write')->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)">

View file

@ -1,5 +1,5 @@
{extends "../@layout.xml"} {extends "../@layout.xml"}
{block title}Изменить видеозапись{/block} {block title}{_change_video}{/block}
{block header} {block header}
<a href="{$thisUser->getURL()}">{$thisUser->getCanonicalName()}</a> <a href="{$thisUser->getURL()}">{$thisUser->getCanonicalName()}</a>
@ -8,12 +8,12 @@
» »
<a href="/video{$video->getPrettyId()}">{_video}</a> <a href="/video{$video->getPrettyId()}">{_video}</a>
» »
Изменить видеозапись {_change_video}
{/block} {/block}
{block content} {block content}
<div class="container_gray"> <div class="container_gray">
<h4>Изменить видеозапись</h4> <h4>{_change_video}</h4>
<form method="post" enctype="multipart/form-data"> <form method="post" enctype="multipart/form-data">
<table cellspacing="7" cellpadding="0" width="60%" border="0" align="center"> <table cellspacing="7" cellpadding="0" width="60%" border="0" align="center">
<tbody> <tbody>

View file

@ -19,7 +19,7 @@
{else} {else}
{var $driver = $video->getVideoDriver()} {var $driver = $video->getVideoDriver()}
{if !$driver} {if !$driver}
Эта видеозапись не поддерживается в вашей версии OpenVK. {_unknown_video}
{else} {else}
{$driver->getEmbed()|noescape} {$driver->getEmbed()|noescape}
{/if} {/if}
@ -59,6 +59,38 @@
{_delete} {_delete}
</a> </a>
</div> </div>
{if isset($thisUser)}
{if $thisUser->getId() != $video->getOwner()->getId()}
{var canReport = true}
{/if}
{/if}
<a n:if="$canReport ?? false" class="profile_link" style="display:block;width:96%;" href="javascript:reportVideo()">{_report}</a>
<script n:if="$canReport ?? false">
function reportVideo() {
uReportMsgTxt = tr("going_to_report_video");
uReportMsgTxt += "<br/>"+tr("report_question_text");
uReportMsgTxt += "<br/><br/><b>"+tr("report_reason")+"</b>: <input type='text' id='uReportMsgInput' placeholder='" + tr("reason") + "' />"
MessageBox(tr("report_question"), uReportMsgTxt, [tr("confirm_m"), tr("cancel")], [
(function() {
res = document.querySelector("#uReportMsgInput").value;
xhr = new XMLHttpRequest();
xhr.open("GET", "/report/" + {$video->getId()} + "?reason=" + res + "&type=video", true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("reason") === -1)
MessageBox(tr("error"), tr("error_sending_report"), ["OK"], [Function.noop]);
else
MessageBox(tr("action_successfully"), tr("will_be_watched"), ["OK"], [Function.noop]);
});
xhr.send(null);
}),
Function.noop
]);
}
</script>
</div> </div>
</div> </div>
{/block} {/block}

View file

@ -6,6 +6,8 @@
{/block} {/block}
{block content} {block content}
{php $GLOBALS["_bigWall"] = 1}
<div class="tabs"> <div class="tabs">
<div n:attr="id => (isset($globalFeed) ? 'ki' : 'activetabs')" class="tab"> <div n:attr="id => (isset($globalFeed) ? 'ki' : 'activetabs')" class="tab">
<a n:attr="id => (isset($globalFeed) ? 'ki' : 'act_tab_a')" href="/feed">{_my_news}</a> <a n:attr="id => (isset($globalFeed) ? 'ki' : 'act_tab_a')" href="/feed">{_my_news}</a>

View file

@ -28,8 +28,43 @@
<h4>{_actions}</h4> <h4>{_actions}</h4>
{if isset($thisUser)} {if isset($thisUser)}
{var $canDelete = $post->canBeDeletedBy($thisUser)} {var $canDelete = $post->canBeDeletedBy($thisUser)}
{if $thisUser->getId() != $post->getOwner()->getId()}
{var $canReport = true}
{/if}
{/if} {/if}
<a n:if="$canDelete ?? false" class="profile_link" style="display:block;width:96%;" href="/wall{$post->getPrettyId()}/delete">{_delete}</a> <a n:if="$canDelete ?? false" class="profile_link" style="display:block;width:96%;" href="/wall{$post->getPrettyId()}/delete">{_delete}</a>
<a
n:if="isset($thisUser) && $thisUser->getChandlerUser()->can('access')->model('admin')->whichBelongsTo(NULL) AND $post->getEditTime()"
style="display:block;width:96%;"
class="profile_link"
href="/admin/logs?type=1&obj_type=Post&obj_id={$post->getId()}"
>
{_changes_history}
</a>
<a n:if="$canReport ?? false" class="profile_link" style="display:block;width:96%;" href="javascript:reportPost()">{_report}</a>
</div> </div>
<script n:if="$canReport ?? false">
function reportPost() {
uReportMsgTxt = tr("going_to_report_post");
uReportMsgTxt += "<br/>"+tr("report_question_text");
uReportMsgTxt += "<br/><br/><b>"+tr("report_reason")+"</b>: <input type='text' id='uReportMsgInput' placeholder='" + tr("reason") + "' />"
MessageBox(tr("report_question"), uReportMsgTxt, [tr("confirm_m"), tr("cancel")], [
(function() {
res = document.querySelector("#uReportMsgInput").value;
xhr = new XMLHttpRequest();
xhr.open("GET", "/report/" + {$post->getId()} + "?reason=" + res + "&type=post", true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("reason") === -1)
MessageBox(tr("error"), tr("error_sending_report"), ["OK"], [Function.noop]);
else
MessageBox(tr("action_successfully"), tr("will_be_watched"), ["OK"], [Function.noop]);
});
xhr.send(null);
}),
Function.noop
]);
}
</script>
{/block} {/block}

View file

@ -1,7 +1,8 @@
{if $attachment instanceof \openvk\Web\Models\Entities\Photo} {if $attachment instanceof \openvk\Web\Models\Entities\Photo}
{if !$attachment->isDeleted()} {if !$attachment->isDeleted()}
<a href="{$attachment->getPageUrl()}"> {var $link = "/photo" . ($attachment->isAnonymous() ? ("s/" . base_convert((string) $attachment->getId(), 10, 32)) : $attachment->getPrettyId())}
<img class="media" src="{$attachment->getURLBySizeId('normal')}" alt="{$attachment->getDescription()}" /> <a href="{$link}" onclick="OpenMiniature(event, {$attachment->getURLBySizeId('normal')}, {$parent->getPrettyId()}, {$attachment->getPrettyId()}, {$parentType})">
<img class="media media_makima" src="{$attachment->getURLBySizeId('normal')}" alt="{$attachment->getDescription()}" />
</a> </a>
{else} {else}
<a href="javascript:alert('{_attach_no_longer_available}');"> <a href="javascript:alert('{_attach_no_longer_available}');">

View file

@ -8,27 +8,57 @@
<tr> <tr>
<td width="30" valign="top"> <td width="30" valign="top">
<a href="{$author->getURL()}"> <a href="{$author->getURL()}">
<img src="{$author->getAvatarURL('miniscule')}" width="30" class="cCompactAvatars" /> <img src="{$author->getAvatarURL('miniscule')}" width="30" class="cCompactAvatars post-avatar" />
</a> </a>
</td> </td>
<td width="100%" valign="top"> <td width="100%" valign="top">
<div class="post-author"> <div class="post-author">
<a href="{$author->getURL()}"><b> <a href="{$author->getURL()}"><b class="post-author-name">
{$author->getCanonicalName()} {$author->getCanonicalName()}
</b></a> </b></a>
<img n:if="$author->isVerified()" class="name-checkmark" src="/assets/packages/static/openvk/img/checkmark.png"><br/> <img n:if="$author->isVerified()" class="name-checkmark" src="/assets/packages/static/openvk/img/checkmark.png"><br/>
</div> </div>
<div class="post-content" id="{$comment->getId()}"> <div class="post-content" id="{$comment->getId()}">
<div class="text" id="text{$comment->getId()}"> <div class="text" id="text{$comment->getId()}">
{$comment->getText()|noescape} <span data-text="{$comment->getText(false)}" class="really_text">{$comment->getText()|noescape}</span>
<div n:ifcontent class="attachments_b"> {var $attachmentsLayout = $comment->getChildrenWithLayout(288)}
<div class="attachment" n:foreach="$comment->getChildren() as $attachment" data-localized-nsfw-text="{_nsfw_warning}"> <div n:ifcontent class="attachments attachments_b" style="height: {$attachmentsLayout->height|noescape}; width: {$attachmentsLayout->width|noescape};">
{include "attachment.xml", attachment => $attachment} <div class="attachment" n:foreach="$attachmentsLayout->tiles as $attachment" style="float: {$attachment[3]|noescape}; width: {$attachment[0]|noescape}; height: {$attachment[1]|noescape};" data-localized-nsfw-text="{_nsfw_warning}">
{include "attachment.xml", attachment => $attachment[2], parent => $comment, parentType => "comment"}
</div>
</div>
<div n:ifcontent class="attachments attachments_m">
<div class="attachment" n:foreach="$attachmentsLayout->extras as $attachment">
{include "attachment.xml", attachment => $attachment, post => $comment}
</div> </div>
</div> </div>
</div> </div>
<div n:if="isset($thisUser) &&! ($compact ?? false)" class="post-menu"> <div n:if="isset($thisUser) &&! ($compact ?? false)" class="post-menu">
<a href="#_comment{$comment->getId()}" class="date">{$comment->getPublicationTime()}
<span n:if="$comment->getEditTime()" class="edited editedMark">({_edited_short})</span>
</a>
{if !$timeOnly}
&nbsp;|
{if $comment->canBeDeletedBy($thisUser)}
<a href="/comment{$comment->getId()}/delete">{_delete}</a>&nbsp;|
{/if}
{if $comment->canBeEditedBy($thisUser)}
<a id="editPost" data-id="{$comment->getId()}">{_edit}</a>&nbsp;|
{/if}
<a class="comment-reply">{_reply}</a>
{if $thisUser->getId() != $comment->getOwner()->getId()}
{var $canReport = true}
| <a href="javascript:reportComment()">{_report}</a>
{/if}
<div style="float: right; font-size: .7rem;">
<a class="post-like-button" href="/comment{$comment->getId()}/like?hash={rawurlencode($csrfToken)}">
<div class="heart" style="{if $comment->hasLikeFrom($thisUser)}opacity: 1;{else}opacity: 0.4;{/if}"></div>
<span class="likeCnt">{if $comment->getLikesCount() > 0}{$comment->getLikesCount()}{/if}</span>
</a>
</div>
{/if}
{var $target = "wall"} {var $target = "wall"}
{if get_class($comment->getTarget()) == "openvk\Web\Models\Entities\Note"} {if get_class($comment->getTarget()) == "openvk\Web\Models\Entities\Note"}
@ -43,21 +73,45 @@
{php $target = "topic"} {php $target = "topic"}
{/if} {/if}
<a {if is_null($linkW)}href="#_comment{$comment->getId()}"{else}href="{$target}{!is_null($comment->getTarget()) ? $comment->getTarget()->getPrettyId() : $comment->getOwner()->getId()}#_comment{$comment->getId()}"{/if} class="date">{$comment->getPublicationTime()}</a>&nbsp;|
{if $comment->canBeDeletedBy($thisUser)} <span n:if="$compact ?? false">
<a href="/comment{$comment->getId()}/delete">{_delete}</a>&nbsp;|
{/if} |&nbsp;<a
{if is_null($linkW)} {if is_null($linkW)}
<a class="comment-reply">{_reply}</a>{/if} href="#_comment{$comment->getId()}"
<div style="float: right; font-size: .7rem;"> {else}
<a class="post-like-button" href="/comment{$comment->getId()}/like?hash={rawurlencode($csrfToken)}"> href="{$target}{!is_null($comment->getTarget()) ? $comment->getTarget()->getPrettyId() : $comment->getOwner()->getId()}#_comment{$comment->getId()}"
<div class="heart" style="{if $comment->hasLikeFrom($thisUser)}opacity: 1;{else}opacity: 0.4;{/if}"></div> {/if}
<span class="likeCnt">{if $comment->getLikesCount() > 0}{$comment->getLikesCount()}{/if}</span> class="date"
</a> >{$comment->getPublicationTime()}</a>
</div>
</span>
</div> </div>
</div> </div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<script n:if="$canReport ?? false">
function reportComment() {
uReportMsgTxt = tr("going_to_report_comment");
uReportMsgTxt += "<br/>"+tr("report_question_text");
uReportMsgTxt += "<br/><br/><b>"+tr("report_reason")+"</b>: <input type='text' id='uReportMsgInput' placeholder='" + tr("reason") + "' />"
MessageBox(tr("report_question"), uReportMsgTxt, [tr("confirm_m"), tr("cancel")], [
(function() {
res = document.querySelector("#uReportMsgInput").value;
xhr = new XMLHttpRequest();
xhr.open("GET", "/report/" + {$comment->getId()} + "?reason=" + res + "&type=comment", true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("reason") === -1)
MessageBox(tr("error"), tr("error_sending_report"), ["OK"], [Function.noop]);
else
MessageBox(tr("action_successfully"), tr("will_be_watched"), ["OK"], [Function.noop]);
});
xhr.send(null);
}),
Function.noop
]);
}
</script>

View file

@ -4,7 +4,7 @@
{var $commentsURL = "/al_comments/create/$model/" . $parent->getId()} {var $commentsURL = "/al_comments/create/$model/" . $parent->getId()}
{var $club = $parent instanceof \openvk\Web\Models\Entities\Post && $parent->getTargetWall() < 0 ? (new openvk\Web\Models\Repositories\Clubs)->get(abs($parent->getTargetWall())) : $club} {var $club = $parent instanceof \openvk\Web\Models\Entities\Post && $parent->getTargetWall() < 0 ? (new openvk\Web\Models\Repositories\Clubs)->get(abs($parent->getTargetWall())) : $club}
{if !$readOnly} {if !$readOnly}
{include "textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), club => $club} {include "textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), club => $club, custom_id => $custom_id}
{/if} {/if}
</div> </div>

View file

@ -0,0 +1,43 @@
{block content}
<div class="content">
<table>
<tbody>
<tr>
<td valign="top">
<a href="{$group->getURL()}">
<img src="{$group->getAvatarURL('normal')}" width="75" alt="Фотография">
</a>
</td>
<td valign="top" style="width: 100%">
<table id="basicInfo" class="ugc-table group_info" cellspacing="0" cellpadding="0" border="0">
<tbody>
<tr>
<td class="label">
<span class="nobold">{_name}:</span>
</td>
<td class="data">
<a href="{$group->getURL()}">{!$isUser ? $group->getName() : $group->getCanonicalName()}</a>
<img n:if="$group->isVerified()"
class="name-checkmark"
src="/assets/packages/static/openvk/img/checkmark.png"
/>
</td>
</tr>
<tr n:if="!$isUser">
<td class="label">
<span class="nobold">{_size}:</span>
</td>
<td class="data">
<a href="/club{$group->getId()}/followers">{tr("participants",
$group->getFollowersCount())}
</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
{/block}

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