Merge branch 'master' into master
3
.gitmodules
vendored
|
@ -1,3 +0,0 @@
|
|||
[submodule "Web/static/img/oxygen-icons"]
|
||||
path = Web/static/img/oxygen-icons
|
||||
url = https://github.com/KDE/oxygen-icons5.git
|
|
@ -130,6 +130,11 @@ class Club extends RowModel
|
|||
return $this->getRecord()->administrators_list_display;
|
||||
}
|
||||
|
||||
function isEveryoneCanCreateTopics(): bool
|
||||
{
|
||||
return (bool) $this->getRecord()->everyone_can_create_topics;
|
||||
}
|
||||
|
||||
function getType(): int
|
||||
{
|
||||
return $this->getRecord()->type;
|
||||
|
|
|
@ -37,8 +37,19 @@ class Comment extends Post
|
|||
if($honourFlags && $this->isPostedOnBehalfOfGroup()) {
|
||||
if($this->getTarget() instanceof Post)
|
||||
return (new Clubs)->get(abs($this->getTarget()->getTargetWall()));
|
||||
|
||||
if($this->getTarget() instanceof Topic)
|
||||
return $this->getTarget()->getClub();
|
||||
}
|
||||
|
||||
return parent::getOwner($honourFlags, $real);
|
||||
}
|
||||
|
||||
function canBeDeletedBy(User $user): bool
|
||||
{
|
||||
return $this->getOwner()->getId() == $user->getId() ||
|
||||
$this->getTarget()->getOwner()->getId() == $user->getId() ||
|
||||
$this->getTarget() instanceof Post && $this->getTarget()->getTargetWall() < 0 && (new Clubs)->get(abs($this->getTarget()->getTargetWall()))->canBeModifiedBy($user) ||
|
||||
$this->getTarget() instanceof Topic && $this->getTarget()->canBeModifiedBy($user);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
namespace openvk\Web\Models\Entities;
|
||||
use openvk\Web\Util\DateTime;
|
||||
use openvk\Web\Models\RowModel;
|
||||
use openvk\Web\Models\Repositories\{Users, SupportAliases};
|
||||
use openvk\Web\Models\Repositories\{Users, SupportAliases, Tickets};
|
||||
|
||||
class TicketComment extends RowModel
|
||||
{
|
||||
|
@ -30,6 +30,11 @@ class TicketComment extends RowModel
|
|||
return (new Users)->get($this->getRecord()->user_id);
|
||||
}
|
||||
|
||||
function getTicket(): Ticket
|
||||
{
|
||||
return (new Tickets)->get($this->getRecord()->ticket_id);
|
||||
}
|
||||
|
||||
function getAuthorName(): string
|
||||
{
|
||||
if($this->getUType() === 0)
|
||||
|
@ -108,5 +113,19 @@ class TicketComment extends RowModel
|
|||
return false; # Кооостыыыль!!!
|
||||
}
|
||||
|
||||
function getMark(): ?int
|
||||
{
|
||||
return $this->getRecord()->mark;
|
||||
}
|
||||
|
||||
function isLikedByUser(): ?bool
|
||||
{
|
||||
$mark = $this->getMark();
|
||||
if(is_null($mark))
|
||||
return NULL;
|
||||
else
|
||||
return $mark === 1;
|
||||
}
|
||||
|
||||
use Traits\TRichText;
|
||||
}
|
||||
|
|
86
Web/Models/Entities/Topic.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities;
|
||||
use openvk\Web\Models\RowModel;
|
||||
use openvk\Web\Models\Repositories\Clubs;
|
||||
use openvk\Web\Util\DateTime;
|
||||
|
||||
class Topic extends Postable
|
||||
{
|
||||
protected $tableName = "topics";
|
||||
protected $upperNodeReferenceColumnName = "group";
|
||||
|
||||
/**
|
||||
* May return fake owner (group), if flags are [1, (*)]
|
||||
*
|
||||
* @param bool $honourFlags - check flags
|
||||
*/
|
||||
function getOwner(bool $honourFlags = true, bool $real = false): RowModel
|
||||
{
|
||||
if($honourFlags && $this->isPostedOnBehalfOfGroup())
|
||||
return $this->getClub();
|
||||
|
||||
return parent::getOwner($real);
|
||||
}
|
||||
|
||||
function getClub(): Club
|
||||
{
|
||||
return (new Clubs)->get($this->getRecord()->group);
|
||||
}
|
||||
|
||||
function getTitle(): string
|
||||
{
|
||||
return $this->getRecord()->title;
|
||||
}
|
||||
|
||||
function isClosed(): bool
|
||||
{
|
||||
return (bool) $this->getRecord()->closed;
|
||||
}
|
||||
|
||||
function isPinned(): bool
|
||||
{
|
||||
return (bool) $this->getRecord()->pinned;
|
||||
}
|
||||
|
||||
function getPrettyId(): string
|
||||
{
|
||||
return $this->getRecord()->group . "_" . $this->getVirtualId();
|
||||
}
|
||||
|
||||
function isPostedOnBehalfOfGroup(): bool
|
||||
{
|
||||
return ($this->getRecord()->flags & 0b10000000) > 0;
|
||||
}
|
||||
|
||||
function isDeleted(): bool
|
||||
{
|
||||
return (bool) $this->getRecord()->deleted;
|
||||
}
|
||||
|
||||
function canBeModifiedBy(User $user): bool
|
||||
{
|
||||
return $this->getOwner(false)->getId() === $user->getId() || $this->getClub()->canBeModifiedBy($user);
|
||||
}
|
||||
|
||||
function getLastComment(): ?Comment
|
||||
{
|
||||
$array = iterator_to_array($this->getLastComments(1));
|
||||
return isset($array[0]) ? $array[0] : NULL;
|
||||
}
|
||||
|
||||
function getUpdateTime(): DateTime
|
||||
{
|
||||
$lastComment = $this->getLastComment();
|
||||
if(!is_null($lastComment))
|
||||
return $lastComment->getPublicationTime();
|
||||
else
|
||||
return $this->getEditTime() ?? $this->getPublicationTime();
|
||||
}
|
||||
|
||||
function deleteTopic(): void
|
||||
{
|
||||
$this->setDeleted(1);
|
||||
$this->unwire();
|
||||
$this->save();
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities\Traits;
|
||||
use Wkhooy\ObsceneCensorRus;
|
||||
|
||||
trait TRichText
|
||||
{
|
||||
|
@ -58,9 +59,9 @@ trait TRichText
|
|||
if($proc) {
|
||||
$rel = $this->isAd() ? "sponsored" : "ugc";
|
||||
$text = $this->formatLinks($text);
|
||||
$text = preg_replace("%@(id|club)([0-9]++) \(([\p{L} 0-9]+)\)%Xu", "[$1$2|$3]", $text);
|
||||
$text = preg_replace("%@(id|club)([0-9]++)%Xu", "[$1$2|@$1$2]", $text);
|
||||
$text = preg_replace("%\[(id|club)([0-9]++)\|([\p{L} 0-9@]+)\]%Xu", "<a href='/$1$2'>$3</a>", $text);
|
||||
$text = preg_replace("%@([A-Za-z0-9]++) \(([\p{L} 0-9]+)\)%Xu", "[$1|$2]", $text);
|
||||
$text = preg_replace("%@([A-Za-z0-9]++)%Xu", "[$1|@$1]", $text);
|
||||
$text = preg_replace("%\[([A-Za-z0-9]++)\|([\p{L} 0-9@]+)\]%Xu", "<a href='/$1'>$2</a>", $text);
|
||||
$text = preg_replace("%(#([\p{L}_-]++[0-9]*[\p{L}_-]*))%Xu", "<a href='/feed/hashtag/$2'>$1</a>", $text);
|
||||
$text = $this->formatEmojis($text);
|
||||
}
|
||||
|
@ -69,6 +70,9 @@ trait TRichText
|
|||
$text = nl2br($text);
|
||||
}
|
||||
|
||||
if(OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["christian"])
|
||||
ObsceneCensorRus::filterText($text);
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -317,14 +317,14 @@ class User extends RowModel
|
|||
return $this->getRecord()->notification_offset;
|
||||
}
|
||||
|
||||
function getBirthday(): ?int
|
||||
function getBirthday(): ?DateTime
|
||||
{
|
||||
return $this->getRecord()->birthday;
|
||||
return new DateTime($this->getRecord()->birthday);
|
||||
}
|
||||
|
||||
function getAge(): ?int
|
||||
{
|
||||
return (int)floor((time() - $this->getBirthday()) / mktime(0, 0, 0, 1, 1, 1971));
|
||||
return (int)floor((time() - $this->getBirthday()->timestamp()) / mktime(0, 0, 0, 1, 1, 1971));
|
||||
}
|
||||
|
||||
function get2faSecret(): ?string
|
||||
|
|
|
@ -49,5 +49,14 @@ class TicketComments
|
|||
// return $this->toTicket($this->tickets->get($id));
|
||||
// }
|
||||
|
||||
function get(int $id): ?TicketComment
|
||||
{
|
||||
$comment = $this->comments->get($id);;
|
||||
if (!is_null($comment))
|
||||
return new TicketComment($comment);
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
use \Nette\SmartObject;
|
||||
}
|
||||
|
|
73
Web/Models/Repositories/Topics.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Repositories;
|
||||
use openvk\Web\Models\Entities\Topic;
|
||||
use openvk\Web\Models\Entities\Club;
|
||||
use Nette\Database\Table\ActiveRow;
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
|
||||
class Topics
|
||||
{
|
||||
private $context;
|
||||
private $topics;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$this->context = DatabaseConnection::i()->getContext();
|
||||
$this->topics = $this->context->table("topics");
|
||||
}
|
||||
|
||||
private function toTopic(?ActiveRow $ar): ?Topic
|
||||
{
|
||||
return is_null($ar) ? NULL : new Topic($ar);
|
||||
}
|
||||
|
||||
function get(int $id): ?Topic
|
||||
{
|
||||
return $this->toTopic($this->topics->get($id));
|
||||
}
|
||||
|
||||
function getTopicById(int $club, int $topic): ?Topic
|
||||
{
|
||||
return $this->toTopic($this->topics->where(["group" => $club, "virtual_id" => $topic, "deleted" => 0])->fetch());
|
||||
}
|
||||
|
||||
function getClubTopics(Club $club, int $page = 1, ?int $perPage = NULL): \Traversable
|
||||
{
|
||||
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE;
|
||||
|
||||
// Get pinned topics first
|
||||
$query = "SELECT `id` FROM `topics` WHERE `pinned` = 1 AND `group` = ? AND `deleted` = 0 UNION SELECT `id` FROM `topics` WHERE `pinned` = 0 AND `group` = ? AND `deleted` = 0";
|
||||
$query .= " LIMIT " . $perPage . " OFFSET " . ($page - 1) * $perPage;
|
||||
|
||||
foreach(DatabaseConnection::i()->getConnection()->query($query, $club->getId(), $club->getId()) as $topic) {
|
||||
$topic = $this->get($topic->id);
|
||||
if(!$topic) continue;
|
||||
|
||||
yield $topic;
|
||||
}
|
||||
}
|
||||
|
||||
function getClubTopicsCount(Club $club): int
|
||||
{
|
||||
return sizeof($this->topics->where([
|
||||
"group" => $club->getId(),
|
||||
"deleted" => false
|
||||
]));
|
||||
}
|
||||
|
||||
function find(Club $club, string $query): \Traversable
|
||||
{
|
||||
return new Util\EntityStream("Topic", $this->topics->where("title LIKE ? AND group = ? AND deleted = 0", "%$query%", $club->getId()));
|
||||
}
|
||||
|
||||
function getLastTopics(Club $club, ?int $count = NULL): \Traversable
|
||||
{
|
||||
$topics = $this->topics->where([
|
||||
"group" => $club->getId(),
|
||||
"deleted" => false
|
||||
])->page(1, $count ?? OPENVK_DEFAULT_PER_PAGE)->order("created DESC");
|
||||
|
||||
foreach($topics as $topic)
|
||||
yield $this->toTopic($topic);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Presenters;
|
||||
use openvk\Web\Models\Entities\{Comment, Photo, Video, User};
|
||||
use openvk\Web\Models\Entities\{Comment, Photo, Video, User, Topic};
|
||||
use openvk\Web\Models\Entities\Notifications\CommentNotification;
|
||||
use openvk\Web\Models\Repositories\Comments;
|
||||
|
||||
|
@ -11,6 +11,7 @@ final class CommentPresenter extends OpenVKPresenter
|
|||
"photos" => "openvk\\Web\\Models\\Repositories\\Photos",
|
||||
"videos" => "openvk\\Web\\Models\\Repositories\\Videos",
|
||||
"notes" => "openvk\\Web\\Models\\Repositories\\Notes",
|
||||
"topics" => "openvk\\Web\\Models\\Repositories\\Topics",
|
||||
];
|
||||
|
||||
function renderLike(int $id): void
|
||||
|
@ -38,8 +39,16 @@ final class CommentPresenter extends OpenVKPresenter
|
|||
$entity = $repo->get($eId);
|
||||
if(!$entity) $this->notFound();
|
||||
|
||||
if($entity instanceof Topic && $entity->isClosed())
|
||||
$this->notFound();
|
||||
|
||||
if($entity instanceof Post && $entity->getTargetWall() > 0)
|
||||
$club = (new Clubs)->get(abs($entity->getTargetWall()));
|
||||
else if($entity instanceof Topic)
|
||||
$club = $entity->getClub();
|
||||
|
||||
$flags = 0;
|
||||
if($this->postParam("as_group") === "on")
|
||||
if($this->postParam("as_group") === "on" && !is_null($club) && $club->canBeModifiedBy($this->user->identity))
|
||||
$flags |= 0b10000000;
|
||||
|
||||
$photo = NULL;
|
||||
|
@ -106,8 +115,7 @@ final class CommentPresenter extends OpenVKPresenter
|
|||
|
||||
$comment = (new Comments)->get($id);
|
||||
if(!$comment) $this->notFound();
|
||||
if($comment->getOwner()->getId() !== $this->user->id)
|
||||
if($comment->getTarget()->getOwner()->getId() !== $this->user->id)
|
||||
if(!$comment->canBeDeletedBy($this->user->identity))
|
||||
$this->throwError(403, "Forbidden", "У вас недостаточно прав чтобы редактировать этот ресурс.");
|
||||
|
||||
$comment->delete();
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
namespace openvk\Web\Presenters;
|
||||
use openvk\Web\Models\Entities\{Club, Photo};
|
||||
use openvk\Web\Models\Entities\Notifications\ClubModeratorNotification;
|
||||
use openvk\Web\Models\Repositories\{Clubs, Users, Albums, Managers};
|
||||
use openvk\Web\Models\Repositories\{Clubs, Users, Albums, Managers, Topics};
|
||||
use Chandler\Security\Authenticator;
|
||||
|
||||
final class GroupPresenter extends OpenVKPresenter
|
||||
|
@ -29,6 +29,8 @@ final class GroupPresenter extends OpenVKPresenter
|
|||
$this->template->club = $club;
|
||||
$this->template->albums = (new Albums)->getClubAlbums($club, 1, 3);
|
||||
$this->template->albumsCount = (new Albums)->getClubAlbumsCount($club);
|
||||
$this->template->topics = (new Topics)->getLastTopics($club, 3);
|
||||
$this->template->topicsCount = (new Topics)->getClubTopicsCount($club);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,6 +207,7 @@ final class GroupPresenter extends OpenVKPresenter
|
|||
$club->setShortcode(empty($this->postParam("shortcode")) ? NULL : $this->postParam("shortcode"));
|
||||
$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->setEveryone_Can_Create_Topics(empty($this->postParam("everyone_can_create_topics")) ? 0 : 1);
|
||||
|
||||
$website = $this->postParam("website") ?? "";
|
||||
if(empty($website))
|
||||
|
|
|
@ -19,6 +19,8 @@ final class NotesPresenter extends OpenVKPresenter
|
|||
{
|
||||
$user = (new Users)->get($owner);
|
||||
if(!$user) $this->notFound();
|
||||
if(!$user->getPrivacyPermission('notes.read', $this->user->identity ?? NULL))
|
||||
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
|
||||
|
||||
$this->template->notes = $this->notes->getUserNotes($user, (int)($this->queryParam("p") ?? 1));
|
||||
$this->template->count = $this->notes->getUserNotesCount($user);
|
||||
|
@ -36,6 +38,8 @@ final class NotesPresenter extends OpenVKPresenter
|
|||
$note = $this->notes->getNoteById($owner, $note_id);
|
||||
if(!$note || $note->getOwner()->getId() !== $owner || $note->isDeleted())
|
||||
$this->notFound();
|
||||
if(!$note->getOwner()->getPrivacyPermission('notes.read', $this->user->identity ?? NULL))
|
||||
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
|
||||
|
||||
$this->template->cCount = $note->getCommentsCount();
|
||||
$this->template->cPage = (int) ($this->queryParam("p") ?? 1);
|
||||
|
|
|
@ -198,6 +198,7 @@ abstract class OpenVKPresenter extends SimplePresenter
|
|||
header("HTTP/1.1 403 Forbidden");
|
||||
$this->getTemplatingEngine()->render(__DIR__ . "/templates/@banned.xml", [
|
||||
"thisUser" => $this->user->identity,
|
||||
"csrfToken" => $GLOBALS["csrfToken"],
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
@ -221,25 +222,23 @@ abstract class OpenVKPresenter extends SimplePresenter
|
|||
{
|
||||
parent::onBeforeRender();
|
||||
|
||||
if(!is_null($this->user)) {
|
||||
$theme = NULL;
|
||||
if(Session::i()->get("_tempTheme")) {
|
||||
$theme = Themepacks::i()[Session::i()->get("_tempTheme", "ovk")];
|
||||
Session::i()->set("_tempTheme", NULL);
|
||||
} else if($this->requestParam("themePreview")) {
|
||||
$theme = Themepacks::i()[$this->requestParam("themePreview")];
|
||||
} else if($this->user->identity !== null && $this->user->identity->getTheme()) {
|
||||
$theme = $this->user->identity->getTheme();
|
||||
if(!is_null($theme) && $theme->overridesTemplates()) {
|
||||
}
|
||||
|
||||
$this->template->theme = $theme;
|
||||
if(!is_null($theme) && $theme->overridesTemplates())
|
||||
$this->template->_templatePath = $theme->getBaseDir() . "/tpl";
|
||||
}
|
||||
}
|
||||
|
||||
if(!is_null(Session::i()->get("_error"))) {
|
||||
$this->template->flashMessage = json_decode(Session::i()->get("_error"));
|
||||
Session::i()->set("_error", NULL);
|
||||
}
|
||||
|
||||
if(Session::i()->get("_tempTheme"))
|
||||
$this->template->theme = Themepacks::i()[Session::i()->get("_tempTheme", "ovk")];
|
||||
else if($this->requestParam("themePreview"))
|
||||
$this->template->theme = Themepacks::i()[$this->requestParam("themePreview")];
|
||||
else if($this->user->identity !== null && $this->user->identity->getTheme())
|
||||
$this->template->theme = $this->user->identity->getTheme();
|
||||
|
||||
// Знаю, каша ебаная, целестора рефактор всё равно сделает :)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@ final class PhotosPresenter extends OpenVKPresenter
|
|||
if($owner > 0) {
|
||||
$user = $this->users->get($owner);
|
||||
if(!$user) $this->notFound();
|
||||
if (!$user->getPrivacyPermission('photos.read', $this->user->identity ?? NULL))
|
||||
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
|
||||
$this->template->albums = $this->albums->getUserAlbums($user, $this->queryParam("p") ?? 1);
|
||||
$this->template->count = $this->albums->getUserAlbumsCount($user);
|
||||
$this->template->owner = $user;
|
||||
|
@ -129,6 +131,12 @@ final class PhotosPresenter extends OpenVKPresenter
|
|||
if($album->getPrettyId() !== $owner . "_" . $id || $album->isDeleted())
|
||||
$this->notFound();
|
||||
|
||||
if($owner > 0 /* bc we currently don't have perms for clubs */) {
|
||||
$ownerObject = (new Users)->get($owner);
|
||||
if(!$ownerObject->getPrivacyPermission('photos.read', $this->user->identity ?? NULL))
|
||||
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
|
||||
}
|
||||
|
||||
$this->template->album = $album;
|
||||
$this->template->photos = iterator_to_array( $album->getPhotos( (int) ($this->queryParam("p") ?? 1) ) );
|
||||
$this->template->paginatorConf = (object) [
|
||||
|
|
|
@ -31,7 +31,6 @@ final class SupportPresenter extends OpenVKPresenter
|
|||
$tickets = $this->tickets->getTicketsByuId($this->user->id);
|
||||
if($tickets)
|
||||
$this->template->tickets = $tickets;
|
||||
|
||||
if($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
if(!empty($this->postParam("name")) && !empty($this->postParam("text"))) {
|
||||
$this->assertNoCSRF();
|
||||
|
@ -225,4 +224,24 @@ final class SupportPresenter extends OpenVKPresenter
|
|||
$this->template->heading = $heading;
|
||||
$this->template->content = $parser->parse($content);
|
||||
}
|
||||
|
||||
function renderRateAnswer(int $id, int $mark): void
|
||||
{
|
||||
$this->willExecuteWriteAction();
|
||||
$this->assertUserLoggedIn();
|
||||
$this->assertNoCSRF();
|
||||
|
||||
$comment = $this->comments->get($id);
|
||||
|
||||
if($this->user->id !== $comment->getTicket()->getUser()->getId())
|
||||
exit(header("HTTP/1.1 403 Forbidden"));
|
||||
|
||||
if($mark !== 1 && $mark !== 2)
|
||||
exit(header("HTTP/1.1 400 Bad Request"));
|
||||
|
||||
$comment->setMark($mark);
|
||||
$comment->save();
|
||||
|
||||
exit(header("HTTP/1.1 200 OK"));
|
||||
}
|
||||
}
|
194
Web/Presenters/TopicsPresenter.php
Normal file
|
@ -0,0 +1,194 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Presenters;
|
||||
use openvk\Web\Models\Entities\{Topic, Club, Comment, Photo, Video};
|
||||
use openvk\Web\Models\Repositories\{Topics, Clubs};
|
||||
|
||||
final class TopicsPresenter extends OpenVKPresenter
|
||||
{
|
||||
private $topics;
|
||||
private $clubs;
|
||||
|
||||
function __construct(Topics $topics, Clubs $clubs)
|
||||
{
|
||||
$this->topics = $topics;
|
||||
$this->clubs = $clubs;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
function renderBoard(int $id): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
|
||||
$club = $this->clubs->get($id);
|
||||
if(!$club)
|
||||
$this->notFound();
|
||||
|
||||
$this->template->club = $club;
|
||||
$page = (int) ($this->queryParam("p") ?? 1);
|
||||
|
||||
$query = $this->queryParam("query");
|
||||
if($query) {
|
||||
$results = $this->topics->find($club, $query);
|
||||
$this->template->topics = $results->page($page);
|
||||
$this->template->count = $results->size();
|
||||
} else {
|
||||
$this->template->topics = $this->topics->getClubTopics($club, $page);
|
||||
$this->template->count = $this->topics->getClubTopicsCount($club);
|
||||
}
|
||||
|
||||
$this->template->paginatorConf = (object) [
|
||||
"count" => $this->template->count,
|
||||
"page" => $page,
|
||||
"amount" => NULL,
|
||||
"perPage" => OPENVK_DEFAULT_PER_PAGE,
|
||||
];
|
||||
}
|
||||
|
||||
function renderTopic(int $clubId, int $topicId): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
|
||||
$topic = $this->topics->getTopicById($clubId, $topicId);
|
||||
if(!$topic)
|
||||
$this->notFound();
|
||||
|
||||
$this->template->topic = $topic;
|
||||
$this->template->club = $topic->getClub();
|
||||
$this->template->count = $topic->getCommentsCount();
|
||||
$this->template->page = (int) ($this->queryParam("p") ?? 1);
|
||||
$this->template->comments = iterator_to_array($topic->getComments($this->template->page));
|
||||
}
|
||||
|
||||
function renderCreate(int $clubId): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
|
||||
$club = $this->clubs->get($clubId);
|
||||
if(!$club)
|
||||
$this->notFound();
|
||||
|
||||
if(!$club->isEveryoneCanCreateTopics() && !$club->canBeModifiedBy($this->user->identity))
|
||||
$this->notFound();
|
||||
|
||||
|
||||
if($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
$this->willExecuteWriteAction();
|
||||
$title = $this->postParam("title");
|
||||
|
||||
if(!$title)
|
||||
$this->flashFail("err", tr("failed_to_create_topic"), tr("no_title_specified"));
|
||||
|
||||
$flags = 0;
|
||||
if($this->postParam("as_group") === "on" && $club->canBeModifiedBy($this->user->identity))
|
||||
$flags |= 0b10000000;
|
||||
|
||||
$topic = new Topic;
|
||||
$topic->setGroup($club->getId());
|
||||
$topic->setOwner($this->user->id);
|
||||
$topic->setTitle(ovk_proc_strtr($title, 127));
|
||||
$topic->setCreated(time());
|
||||
$topic->setFlags($flags);
|
||||
$topic->save();
|
||||
|
||||
// TODO move to trait
|
||||
try {
|
||||
$photo = NULL;
|
||||
$video = NULL;
|
||||
if($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) {
|
||||
$album = NULL;
|
||||
if($wall > 0 && $wall === $this->user->id)
|
||||
$album = (new Albums)->getUserWallAlbum($wallOwner);
|
||||
|
||||
$photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"], $album);
|
||||
}
|
||||
|
||||
if($_FILES["_vid_attachment"]["error"] === UPLOAD_ERR_OK) {
|
||||
$video = Video::fastMake($this->user->id, $this->postParam("text"), $_FILES["_vid_attachment"]);
|
||||
}
|
||||
} catch(ISE $ex) {
|
||||
$this->flash("err", "Не удалось опубликовать комментарий", "Файл медиаконтента повреждён или слишком велик.");
|
||||
$this->redirect("/topic" . $topic->getPrettyId(), static::REDIRECT_TEMPORARY);
|
||||
}
|
||||
|
||||
if(!empty($this->postParam("text")) || $photo || $video) {
|
||||
try {
|
||||
$comment = new Comment;
|
||||
$comment->setOwner($this->user->id);
|
||||
$comment->setModel(get_class($topic));
|
||||
$comment->setTarget($topic->getId());
|
||||
$comment->setContent($this->postParam("text"));
|
||||
$comment->setCreated(time());
|
||||
$comment->setFlags($flags);
|
||||
$comment->save();
|
||||
} catch (\LengthException $ex) {
|
||||
$this->flash("err", "Не удалось опубликовать комментарий", "Комментарий слишком большой.");
|
||||
$this->redirect("/topic" . $topic->getPrettyId(), static::REDIRECT_TEMPORARY);
|
||||
}
|
||||
|
||||
if(!is_null($photo))
|
||||
$comment->attach($photo);
|
||||
|
||||
if(!is_null($video))
|
||||
$comment->attach($video);
|
||||
}
|
||||
|
||||
$this->redirect("/topic" . $topic->getPrettyId(), static::REDIRECT_TEMPORARY);
|
||||
}
|
||||
|
||||
$this->template->club = $club;
|
||||
$this->template->graffiti = (bool) ovkGetQuirk("comments.allow-graffiti");
|
||||
}
|
||||
|
||||
function renderEdit(int $clubId, int $topicId): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
|
||||
$topic = $this->topics->getTopicById($clubId, $topicId);
|
||||
if(!$topic)
|
||||
$this->notFound();
|
||||
|
||||
if(!$topic->canBeModifiedBy($this->user->identity))
|
||||
$this->notFound();
|
||||
|
||||
if($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
$this->willExecuteWriteAction();
|
||||
$title = $this->postParam("title");
|
||||
|
||||
if(!$title)
|
||||
$this->flashFail("err", tr("failed_to_change_topic"), tr("no_title_specified"));
|
||||
|
||||
$topic->setTitle(ovk_proc_strtr($title, 127));
|
||||
$topic->setClosed(empty($this->postParam("close")) ? 0 : 1);
|
||||
|
||||
if($topic->getClub()->canBeModifiedBy($this->user->identity))
|
||||
$topic->setPinned(empty($this->postParam("pin")) ? 0 : 1);
|
||||
|
||||
$topic->save();
|
||||
|
||||
$this->flash("succ", tr("changes_saved"), tr("topic_changes_saved_comment"));
|
||||
$this->redirect("/topic" . $topic->getPrettyId(), static::REDIRECT_TEMPORARY);
|
||||
}
|
||||
|
||||
$this->template->topic = $topic;
|
||||
$this->template->club = $topic->getClub();
|
||||
}
|
||||
|
||||
function renderDelete(int $clubId, int $topicId): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
$this->assertNoCSRF();
|
||||
|
||||
$topic = $this->topics->getTopicById($clubId, $topicId);
|
||||
if(!$topic)
|
||||
$this->notFound();
|
||||
|
||||
if(!$topic->canBeModifiedBy($this->user->identity))
|
||||
$this->notFound();
|
||||
|
||||
$this->willExecuteWriteAction();
|
||||
$topic->deleteTopic();
|
||||
|
||||
$this->redirect("/board" . $topic->getClub()->getId(), static::REDIRECT_TEMPORARY);
|
||||
}
|
||||
}
|
|
@ -54,6 +54,8 @@ final class UserPresenter extends OpenVKPresenter
|
|||
$page = abs($this->queryParam("p") ?? 1);
|
||||
if(!$user)
|
||||
$this->notFound();
|
||||
elseif (!$user->getPrivacyPermission('friends.read', $this->user->identity ?? NULL))
|
||||
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
|
||||
else
|
||||
$this->template->user = $user;
|
||||
|
||||
|
@ -78,9 +80,11 @@ final class UserPresenter extends OpenVKPresenter
|
|||
$this->assertUserLoggedIn();
|
||||
|
||||
$user = $this->users->get($id);
|
||||
if(!$user) {
|
||||
if(!$user)
|
||||
$this->notFound();
|
||||
} else {
|
||||
elseif (!$user->getPrivacyPermission('groups.read', $this->user->identity ?? NULL))
|
||||
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
|
||||
else {
|
||||
$this->template->user = $user;
|
||||
$this->template->page = $this->queryParam("p") ?? 1;
|
||||
$this->template->admin = $this->queryParam("act") == "managed";
|
||||
|
|
|
@ -22,6 +22,8 @@ final class VideosPresenter extends OpenVKPresenter
|
|||
{
|
||||
$user = $this->users->get($id);
|
||||
if(!$user) $this->notFound();
|
||||
if(!$user->getPrivacyPermission('videos.read', $this->user->identity ?? NULL))
|
||||
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
|
||||
|
||||
$this->template->user = $user;
|
||||
$this->template->videos = $this->videos->getByUser($user, (int) ($this->queryParam("p") ?? 1));
|
||||
|
@ -38,6 +40,8 @@ final class VideosPresenter extends OpenVKPresenter
|
|||
{
|
||||
$user = $this->users->get($owner);
|
||||
if(!$user) $this->notFound();
|
||||
if(!$user->getPrivacyPermission('videos.read', $this->user->identity ?? NULL))
|
||||
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
|
||||
|
||||
if($this->videos->getByOwnerAndVID($owner, $vId)->isDeleted()) $this->notFound();
|
||||
|
||||
|
|
|
@ -197,7 +197,7 @@ final class WallPresenter extends OpenVKPresenter
|
|||
}
|
||||
|
||||
$flags = 0;
|
||||
if($this->postParam("as_group") === "on")
|
||||
if($this->postParam("as_group") === "on" && $wallOwner instanceof Club && $wallOwner->canBeModifiedBy($this->user->identity))
|
||||
$flags |= 0b10000000;
|
||||
if($this->postParam("force_sign") === "on")
|
||||
$flags |= 0b01000000;
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
{extends "@layout.xml"}
|
||||
{block title}Вам бан{/block}
|
||||
{block title}{_"banned_title"}{/block}
|
||||
|
||||
{block header}
|
||||
Вы были верискокнуты
|
||||
{_"banned_header"}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<center>
|
||||
<img src="/assets/packages/static/openvk/img/oof.apng" alt="Пользователь заблокирован." style="width: 20%;" />
|
||||
<img src="/assets/packages/static/openvk/img/oof.apng" alt="{_'banned_alt'}" style="width: 20%;" />
|
||||
</center>
|
||||
<p>
|
||||
Извините, <b>{$thisUser->getCanonicalName()}</b>, но вы были верискокнуты.<br/>
|
||||
А причина этому проста: <b>{$thisUser->getBanReason()}</b>. К сожалению, на этот раз
|
||||
нам пришлось заблокировать вас навсегда.
|
||||
{tr("banned_1", htmlentities($thisUser->getCanonicalName()))|noescape}<br/>
|
||||
{tr("banned_2", htmlentities($thisUser->getBanReason()))|noescape}
|
||||
</p>
|
||||
<hr/>
|
||||
<p>
|
||||
Вы всё ещё можете <a href="/support?act=new">написать в службу поддержки</a>, если считаете что произошла ошибка
|
||||
или <a href="/logout">выйти</a>.
|
||||
{tr("banned_3", urlencode($csrfToken))|noescape}
|
||||
</p>
|
||||
{/block}
|
|
@ -206,8 +206,8 @@
|
|||
style="max-width: 100%; margin-top: 50px;" />
|
||||
</a>
|
||||
{else}
|
||||
<a href="/support" class="link">Поддержка</a>
|
||||
<a href="/logout" class="link">Выйти</a>
|
||||
<a href="/support" class="link">{_"menu_support"}</a>
|
||||
<a href="/logout?hash={urlencode($csrfToken)}" class="link">{_"menu_logout"}</a>
|
||||
{/if}
|
||||
{else}
|
||||
<form id="fastLogin" action="/login" method="POST" enctype="multipart/form-data">
|
||||
|
|
|
@ -77,6 +77,14 @@
|
|||
<input type="checkbox" name="wall" value="1" {if $club->canPost()}checked{/if}/> {_group_allow_post_for_everyone}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top">
|
||||
<span class="nobold">{_discussions}: </span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" name="everyone_can_create_topics" value="1" n:attr="checked => $club->isEveryoneCanCreateTopics()" /> {_everyone_can_create_topics}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top">
|
||||
<span class="nobold">{_group_administrators_list}: </span>
|
||||
|
|
|
@ -197,12 +197,31 @@
|
|||
</div>
|
||||
<div>
|
||||
<b><a href="/album{$album->getPrettyId()}">{$album->getName()}</a></b><br>
|
||||
<span class="nobold">Обновлён {$album->getEditTime() ?? $album->getCreationTime()}</span>
|
||||
<span class="nobold">{tr("updated_at", $album->getEditTime() ?? $album->getCreationTime())}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div n:if="$topicsCount > 0 || $club->isEveryoneCanCreateTopics() || $club->canBeModifiedBy($thisUser)">
|
||||
<div class="content_title_expanded" onclick="hidePanel(this, {$topicsCount});">
|
||||
{_discussions}
|
||||
</div>
|
||||
<div>
|
||||
<div class="content_subtitle">
|
||||
{tr("topics", $topicsCount)}
|
||||
<div style="float: right;">
|
||||
<a href="/board{$club->getId()}">{_"all_title"}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div n:foreach="$topics as $topic" class="topic-list-item">
|
||||
<b><a href="/topic{$topic->getPrettyId()}">{$topic->getTitle()}</a></b><br>
|
||||
<span class="nobold">{tr("updated_at", $topic->getUpdateTime())}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/block}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{extends "../@layout.xml"}
|
||||
{block title}{_my_messages}{/block}
|
||||
|
||||
{block header}{/block}
|
||||
{block header}{_my_messages}{/block}
|
||||
|
||||
{block content}
|
||||
<div class="tabs">
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
{/block}
|
||||
|
||||
{block header}
|
||||
OpenVK »
|
||||
{=OPENVK_ROOT_CONF["openvk"]["appearance"]["name"]} »
|
||||
{if $type === "users"}
|
||||
{tr("search_for_people")}
|
||||
{else}
|
||||
|
@ -17,20 +17,31 @@
|
|||
{/if}
|
||||
{/block}
|
||||
|
||||
{* BEGIN ELEMENTS DESCRIPTION *}
|
||||
|
||||
{block tabs}
|
||||
<form style="margin-left: 12px;">
|
||||
<input name="type" type="hidden" value="{$_GET['type'] ?? 'users'}" />
|
||||
<input name="query" type="text" placeholder="{_"header_search"}" value="{$_GET['query'] ?? ''}" style="width: 90%" />
|
||||
<input type="submit" class="button" value="{_"search_button"}" style="width: 9%" />
|
||||
<div {if $type === "users"}id="activetabs"{/if} class="tab">
|
||||
<a {if $type === "users"}id="act_tab_a"{/if} href="/search?type=users">
|
||||
{_users}
|
||||
</a>
|
||||
</div>
|
||||
<div {if $type === "groups"}id="activetabs"{/if} class="tab">
|
||||
<a {if $type === "groups"}id="act_tab_a"{/if} href="/search?type=groups">
|
||||
{_groups}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<form class="header_search_inputbt">
|
||||
<input name="type" type="hidden" value="{$type ?? 'users'}" />
|
||||
<input name="query" class="header_search_input" placeholder="{_search_placeholder}" value="{$_GET['query'] ?? ''}" />
|
||||
<button class="button_search">{_search_button}</button>
|
||||
</form>
|
||||
|
||||
<p style="margin-left: 15px;">
|
||||
<p style="margin-left: 15px; margin-top: 0;">
|
||||
<b>{tr("results", $count)}</b>
|
||||
</p>
|
||||
{/block}
|
||||
|
||||
{* BEGIN ELEMENTS DESCRIPTION *}
|
||||
|
||||
{block link|strip|stripHtml}
|
||||
{$x->getURL()}
|
||||
{/block}
|
||||
|
|
|
@ -109,6 +109,18 @@
|
|||
<a href="/support/comment/{$comment->getId()}/delete">{_delete}</a>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{if $comment->getUType() === 1}
|
||||
<div class="post-menu">
|
||||
<strong>
|
||||
{if $comment->isLikedByUser()}
|
||||
{_support_good_answer_agent}
|
||||
{else}
|
||||
{_support_bad_answer_agent}
|
||||
{/if}
|
||||
</strong>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -6,6 +6,28 @@
|
|||
{/block}
|
||||
|
||||
{block content}
|
||||
<script>
|
||||
function markAnswer(id, mark) {
|
||||
let url = "/support/comment/" + id + "/rate/" + mark + "?hash=" + {urlencode($csrfToken)};
|
||||
$.ajax(url, {
|
||||
error: errorHandler,
|
||||
success: success(id, mark)
|
||||
});
|
||||
}
|
||||
|
||||
function success(id, mark) {
|
||||
if(mark == 1)
|
||||
document.getElementById("markText-" + id).innerHTML = {_support_good_answer_user};
|
||||
else
|
||||
document.getElementById("markText-" + id).innerHTML = {_support_bad_answer_user};
|
||||
|
||||
document.getElementById("markLinks-" + id).remove();
|
||||
}
|
||||
|
||||
function errorHandler(id, mark) {
|
||||
document.getElementById("markText-" + id).innerHTML = {_error};
|
||||
}
|
||||
</script>
|
||||
{if $ticket->isDeleted() == 0 }
|
||||
<div class="post-author">
|
||||
<a href="#" style="font-size:13px;">
|
||||
|
@ -90,11 +112,34 @@
|
|||
{$comment->getText()|noescape}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{if $comment->getUType() === 0}
|
||||
<div class="post-menu">
|
||||
<a href="/support/comment/{$comment->getId()}/delete">{_delete}</a>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{if $comment->getUType() === 1}
|
||||
<div class="post-menu">
|
||||
{var isLikedByUser = $comment->isLikedByUser()}
|
||||
<strong id="markText-{$comment->getId()}">
|
||||
{if !is_null($isLikedByUser)}
|
||||
{if $comment->isLikedByUser()}
|
||||
{_support_good_answer_user}
|
||||
{else}
|
||||
{_support_bad_answer_user}
|
||||
{/if}
|
||||
{/if}
|
||||
</strong>
|
||||
<div id="markLinks-{$comment->getId()}">
|
||||
{if is_null($isLikedByUser)}
|
||||
<a onClick="markAnswer({$comment->getId()}, 1)">{_support_rate_good_answer}</a>
|
||||
|
|
||||
<a onClick="markAnswer({$comment->getId()}, 2)">{_support_rate_bad_answer}</a>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
61
Web/Presenters/templates/Topics/Board.xml
Normal file
|
@ -0,0 +1,61 @@
|
|||
{extends "../@listView.xml"}
|
||||
{var iterator = iterator_to_array($topics)}
|
||||
{var page = $paginatorConf->page}
|
||||
|
||||
{block title}{_discussions} {$club->getCanonicalName()}{/block}
|
||||
|
||||
{block header}
|
||||
<a href="{$club->getURL()}">{$club->getCanonicalName()}</a> » {_discussions}
|
||||
|
||||
<div n:if="$club->isEveryoneCanCreateTopics() || $club->canBeModifiedBy($thisUser)" style="float: right;">
|
||||
<a href="/board{$club->getId()}/create">{_create_topic}</a>
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
{block tabs}
|
||||
<form style="margin-left: 12px;">
|
||||
<input name="query" class="header_search_input" placeholder="{_"header_search"}" value="{$_GET['query'] ?? ''}" style="width: 90%" />
|
||||
<input type="submit" class="button" value="{_"search_button"}" style="width: 7%" />
|
||||
</form>
|
||||
|
||||
<p style="margin-left: 15px;">
|
||||
<b>{tr("results", $count)}</b>
|
||||
</p>
|
||||
{/block}
|
||||
|
||||
{block actions}
|
||||
|
||||
{/block}
|
||||
|
||||
{* BEGIN ELEMENTS DESCRIPTION *}
|
||||
|
||||
{block link|strip|stripHtml}
|
||||
/topic{$x->getPrettyId()}
|
||||
{/block}
|
||||
|
||||
{block preview}
|
||||
|
||||
{/block}
|
||||
|
||||
{block name}
|
||||
{$x->getTitle()}
|
||||
<div n:if="$x->isPinned()" class="pinned-mark"></div>
|
||||
{/block}
|
||||
|
||||
{block description}
|
||||
<div style="float: left;">
|
||||
{tr("messages", $x->getCommentsCount())}
|
||||
</div>
|
||||
{var lastComment = $x->getLastComment()}
|
||||
<div n:if="$lastComment" class="avatar-list-item" style="float: right;">
|
||||
<div class="avatar">
|
||||
<a href="{$lastComment->getOwner()->getURL()}">
|
||||
<img class="ava" src="{$lastComment->getOwner()->getAvatarUrl()}" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="info">
|
||||
<a href="{$lastComment->getOwner()->getURL()}" class="title">{$lastComment->getOwner()->getCanonicalName()}</a>
|
||||
<div class="subtitle">{_replied} {$lastComment->getPublicationTime()}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
96
Web/Presenters/templates/Topics/Create.xml
Normal file
|
@ -0,0 +1,96 @@
|
|||
{extends "../@layout.xml"}
|
||||
{block title}{_new_topic}{/block}
|
||||
|
||||
{block header}
|
||||
<a href="{$club->getURL()}">{$club->getCanonicalName()}</a>
|
||||
»
|
||||
<a href="/board{$club->getId()}">{_discussions}</a>
|
||||
»
|
||||
{_new_topic}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<table cellspacing="7" cellpadding="0" width="80%" border="0" align="center">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="120" valign="top">
|
||||
<span class="nobold">{_title}</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="title" style="width: 100%;" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top">
|
||||
<span class="nobold">{_text}</span>
|
||||
</td>
|
||||
<td>
|
||||
<textarea id="wall-post-input1" name="text" style="width: 100%; resize: none;"></textarea>
|
||||
<div n:if="$club->canBeModifiedBy($thisUser)" class="post-opts">
|
||||
<label>
|
||||
<input type="checkbox" name="as_group" onchange="onWallAsGroupClick(this)" /> {_post_as_group}
|
||||
</label>
|
||||
</div>
|
||||
<div id="post-buttons1">
|
||||
<div class="post-upload">
|
||||
{_attachment}: <span>(unknown)</span>
|
||||
</div>
|
||||
<input type="file" class="postFileSel" id="postFilePic" name="_pic_attachment" accept="image/*" style="display: none;" />
|
||||
<input type="file" class="postFileSel" id="postFileVid" name="_vid_attachment" accept="video/*" style="display: none;" />
|
||||
<br/>
|
||||
<div style="float: right; display: flex; flex-direction: column;">
|
||||
<a href="javascript:void(u('#post-buttons1 #wallAttachmentMenu').toggleClass('hidden'));">
|
||||
{_attach}
|
||||
</a>
|
||||
|
||||
<div id="wallAttachmentMenu" class="hidden">
|
||||
<a href="javascript:void(document.querySelector('#post-buttons1 input[name=_pic_attachment]').click());">
|
||||
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-x-egon.png" />
|
||||
{_attach_photo}
|
||||
</a>
|
||||
<a href="javascript:void(document.querySelector('#post-buttons1 input[name=_vid_attachment]').click());">
|
||||
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-vnd.rn-realmedia.png" />
|
||||
{_attach_video}
|
||||
</a>
|
||||
<a n:if="$graffiti ?? false" href="javascript:initGraffiti(1);">
|
||||
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/actions/draw-brush.png" />
|
||||
{_draw_graffiti}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input type="submit" value="{_create_topic}" class="button" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
</form>
|
||||
|
||||
<script>
|
||||
$(document).ready(() => {
|
||||
u("#post-buttons1 .postFileSel").on("change", function() {
|
||||
handleUpload.bind(this, 1)();
|
||||
});
|
||||
|
||||
setupWallPostInputHandlers(1);
|
||||
});
|
||||
</script>
|
||||
|
||||
{if $graffiti}
|
||||
{script "js/node_modules/react/dist/react-with-addons.min.js"}
|
||||
{script "js/node_modules/react-dom/dist/react-dom.min.js"}
|
||||
{script "js/vnd_literallycanvas.js"}
|
||||
{css "js/node_modules/literallycanvas/lib/css/literallycanvas.css"}
|
||||
{/if}
|
||||
{/block}
|
55
Web/Presenters/templates/Topics/Edit.xml
Normal file
|
@ -0,0 +1,55 @@
|
|||
{extends "../@layout.xml"}
|
||||
{block title}{_edit_topic} "{$topic->getTitle()}"{/block}
|
||||
|
||||
{block header}
|
||||
<a href="{$club->getURL()}">{$club->getCanonicalName()}</a>
|
||||
»
|
||||
<a href="/board{$club->getId()}">{_discussions}</a>
|
||||
»
|
||||
{_edit_topic}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<div class="container_gray">
|
||||
<b>{$topic->getTitle()}</b>
|
||||
<br />
|
||||
<a href="{$topic->getOwner()->getURL()}">{$topic->getOwner()->getCanonicalName()}</a>
|
||||
</div>
|
||||
|
||||
<form method="POST" enctype="multipart/form-data" style="margin-top: 20px;">
|
||||
<table cellspacing="7" cellpadding="0" width="80%" border="0" align="center">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="120" valign="top">
|
||||
<span class="nobold">{_title}</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="title" style="width: 100%;" value="{$topic->getTitle()}" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top">
|
||||
<span class="nobold">{_topic_settings}</span>
|
||||
</td>
|
||||
<td>
|
||||
{if $topic->getClub()->canBeModifiedBy($thisUser)}
|
||||
<input type="checkbox" name="pin" n:attr="checked => $topic->isPinned()" /> {_pin_topic}<br />
|
||||
{/if}
|
||||
<input type="checkbox" name="close" n:attr="checked => $topic->isClosed()" /> {_close_topic}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a class="button" href="/topic{$topic->getPrettyId()}/delete?hash={urlencode($csrfToken)}">{_delete_topic}</a>
|
||||
</td>
|
||||
<td>
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input type="submit" value="{_save}" class="button" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
</form>
|
||||
{/block}
|
29
Web/Presenters/templates/Topics/Topic.xml
Normal file
|
@ -0,0 +1,29 @@
|
|||
{extends "../@layout.xml"}
|
||||
{block title}{_view_topic} "{$topic->getTitle()}"{/block}
|
||||
|
||||
{block header}
|
||||
<a href="{$club->getURL()}">{$club->getCanonicalName()}</a>
|
||||
»
|
||||
<a href="/board{$club->getId()}">{_discussions}</a>
|
||||
»
|
||||
{_view_topic}
|
||||
|
||||
<div style="float: right;" n:if="$topic->canBeModifiedBy($thisUser)">
|
||||
<a href="/topic{$club->getId()}_{$topic->getVirtualId()}/edit">{_edit_topic_action}</a>
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<div class="container_gray">
|
||||
<b>{$topic->getTitle()}</b>
|
||||
<br />
|
||||
<a href="{$topic->getOwner()->getURL()}">{$topic->getOwner()->getCanonicalName()}</a>
|
||||
<div class="nobold" style="float: right;">
|
||||
{_created} {$topic->getPublicationTime()}
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 20px;">
|
||||
<h4>{tr("topic_messages_count", $count)}</h4>
|
||||
{include "../components/comments.xml", comments => $comments, count => $count, page => $page, model => "topics", club => $club, readOnly => $topic->isClosed(), showTitle => false, parent => $topic}
|
||||
</div>
|
||||
{/block}
|
|
@ -135,7 +135,7 @@
|
|||
<span class="nobold">{_"birth_date"}: </span>
|
||||
</td>
|
||||
<td>
|
||||
<input max={date('Y-m-d')} name="birthday" value={gmdate("Y-m-d", $user->getBirthday())} type="date"/>
|
||||
<input max={date('Y-m-d')} name="birthday" value={$user->getBirthday()->format('%Y-%m-%d')} type="date"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
{block title}{$user->getCanonicalName()}{/block}
|
||||
|
||||
{block headIncludes}
|
||||
{if $user->getPrivacyPermission('page.read', $thisUser ?? NULL)}
|
||||
<!-- openGraph -->
|
||||
<meta property="og:title" content="{$user->getCanonicalName()}" />
|
||||
<meta property="og:url" content="http://{$_SERVER['HTTP_HOST']}{$user->getURL()}" />
|
||||
|
@ -22,6 +23,9 @@
|
|||
"url": {('http://') . $_SERVER['HTTP_HOST'] . $user->getURL()}
|
||||
}
|
||||
</script>
|
||||
{else}
|
||||
<meta name="robots" content="noindex, noarchive">
|
||||
{/if}
|
||||
{/block}
|
||||
|
||||
{block header}
|
||||
|
@ -398,10 +402,10 @@
|
|||
<td class="label"><span class="nobold">{_"politViews"}:</span></td>
|
||||
<td class="data">{var $pviews = $user->getPoliticalViews()}{_"politViews_$pviews"}</td>
|
||||
</tr>
|
||||
{if $user->getBirthday() > 0}
|
||||
{if $user->getBirthday()->timestamp() > 0}
|
||||
<tr>
|
||||
<td class="label"><span class="nobold">{_"birth_date"}:</span></td>
|
||||
<td class="data">{date('d F Y',$user->getBirthday())},
|
||||
<td class="data">{$user->getBirthday()->format('%e %B %Y')},
|
||||
{tr("years", $user->getAge())}</td>
|
||||
</tr>
|
||||
{/if}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<center>
|
||||
<img src="/assets/packages/static/openvk/img/oof.apng" alt="Пользователь заблокирован." style="width: 20%;" />
|
||||
<p>
|
||||
К сожалению, нам пришлось заблокировать страницу пользователя <b>{$user->getFirstName()}</b>.<br/>
|
||||
Комментарий модератора: <b>{$user->getBanReason()}</b>.
|
||||
{tr("user_banned", htmlentities($user->getFirstName()))|noescape}<br/>
|
||||
{_"user_banned_comment"} <b>{$user->getBanReason()}</b>.
|
||||
</p>
|
||||
</center>
|
||||
|
|
|
@ -29,9 +29,7 @@
|
|||
</div>
|
||||
<div n:if="isset($thisUser) &&! ($compact ?? false)" class="post-menu">
|
||||
<a href="#_comment{$comment->getId()}" class="date">{$comment->getPublicationTime()}</a> |
|
||||
{var canDelete = $comment->getOwner()->getId() == $thisUser->getId()}
|
||||
{var canDelete = $canDelete || $comment->getTarget()->getOwner()->getId() == $thisUser->getId()}
|
||||
{if $canDelete}
|
||||
{if $comment->canBeDeletedBy($thisUser)}
|
||||
<a href="/comment{$comment->getId()}/delete">{_"delete"}</a> |
|
||||
{/if}
|
||||
<a class="comment-reply">Ответить</a>
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
<h4>{_"comments"} ({$count})</h4>
|
||||
<h4 n:if="$showTitle ?? true">{_"comments"} ({$count})</h4>
|
||||
|
||||
<div n:ifset="$thisUser">
|
||||
{var commentsURL = "/al_comments.pl/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())) : NULL}
|
||||
{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}
|
||||
{include "textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), club => $club}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{if sizeof($comments) > 0}
|
||||
{foreach $comments as $comment}
|
||||
{include "comment.xml", comment => $comment}
|
||||
{/foreach}
|
||||
<div style="margin-top: 11px;">
|
||||
{include "paginator.xml", conf => (object) ["page" => $page, "count" => $count, "amount" => sizeof($comments), "perPage" => 10]}
|
||||
</div>
|
||||
{else}
|
||||
<!-- {if $model === "photos"}
|
||||
<p>Будьте первым, кто оставит комментарий к этой фотографии</p>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{extends "@default.xml"}
|
||||
{var post = $notification->getModel(0)}
|
||||
|
||||
{block under}
|
||||
{_nt_yours_adjective} <a href="/topic{$post->getPrettyId()}">{_nt_topic_instrumental}</a>
|
||||
{/block}
|
|
@ -8,7 +8,7 @@
|
|||
</div>
|
||||
<div id="post-buttons{$textAreaId}" style="display: none;">
|
||||
<div class="post-upload">
|
||||
Вложение: <span>(unknown)</span>
|
||||
{_attachment}: <span>(unknown)</span>
|
||||
</div>
|
||||
<div n:if="$postOpts ?? true" class="post-opts">
|
||||
{var anonEnabled = OPENVK_ROOT_CONF['openvk']['preferences']['wall']['anonymousPosting']['enable']}
|
||||
|
@ -35,7 +35,7 @@
|
|||
{/if}
|
||||
|
||||
<label n:if="$anonEnabled" id="octoberAnonOpt">
|
||||
<input type="checkbox" name="anon" /> Анонимно
|
||||
<input type="checkbox" name="anon" /> {_"as_anonymous"}
|
||||
</label>
|
||||
|
||||
<label>
|
||||
|
@ -55,7 +55,7 @@
|
|||
<input type="submit" value="{_'write'}" class="button" />
|
||||
<div style="float: right; display: flex; flex-direction: column;">
|
||||
<a href="javascript:void(u('#post-buttons{$textAreaId} #wallAttachmentMenu').toggleClass('hidden'));">
|
||||
Прикрепить
|
||||
{_attach}
|
||||
</a>
|
||||
|
||||
<div id="wallAttachmentMenu" class="hidden">
|
||||
|
@ -65,11 +65,11 @@
|
|||
</a>
|
||||
<a href="javascript:void(document.querySelector('#post-buttons{$textAreaId} input[name=_vid_attachment]').click());">
|
||||
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-vnd.rn-realmedia.png" />
|
||||
Прикрепить видео
|
||||
{_attach_video}
|
||||
</a>
|
||||
<a n:if="$graffiti ?? false" href="javascript:initGraffiti({$textAreaId});">
|
||||
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/actions/draw-brush.png" />
|
||||
Нарисовать граффити
|
||||
{_draw_graffiti}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -19,6 +19,7 @@ services:
|
|||
- openvk\Web\Presenters\AdminPresenter
|
||||
- openvk\Web\Presenters\GiftsPresenter
|
||||
- openvk\Web\Presenters\MessengerPresenter
|
||||
- openvk\Web\Presenters\TopicsPresenter
|
||||
- openvk\Web\Presenters\ThemepacksPresenter
|
||||
- openvk\Web\Presenters\VKAPIPresenter
|
||||
- openvk\Web\Models\Repositories\Users
|
||||
|
@ -36,4 +37,5 @@ services:
|
|||
- openvk\Web\Models\Repositories\IPs
|
||||
- openvk\Web\Models\Repositories\Vouchers
|
||||
- openvk\Web\Models\Repositories\Gifts
|
||||
- openvk\Web\Models\Repositories\Topics
|
||||
- openvk\Web\Models\Repositories\ContentSearchRepository
|
||||
|
|
|
@ -17,6 +17,8 @@ routes:
|
|||
handler: "Support->AnswerTicket"
|
||||
- url: "/support/view/{num}"
|
||||
handler: "Support->view"
|
||||
- url: "/support/comment/{num}/rate/{num}"
|
||||
handler: "Support->rateAnswer"
|
||||
- url: "/al_comments.pl/create/support/{num}"
|
||||
handler: "Support->makeComment"
|
||||
- url: "/al_comments.pl/create/support/reply/{num}"
|
||||
|
@ -165,6 +167,16 @@ routes:
|
|||
handler: "User->pinClub"
|
||||
- url: "/groups_create"
|
||||
handler: "Group->create"
|
||||
- url: "/board{num}"
|
||||
handler: "Topics->board"
|
||||
- url: "/board{num}/create"
|
||||
handler: "Topics->create"
|
||||
- url: "/topic{num}_{num}"
|
||||
handler: "Topics->topic"
|
||||
- url: "/topic{num}_{num}/edit"
|
||||
handler: "Topics->edit"
|
||||
- url: "/topic{num}_{num}/delete"
|
||||
handler: "Topics->delete"
|
||||
- url: "/audios{num}"
|
||||
handler: "Audios->app"
|
||||
- url: "/audios{num}.json"
|
||||
|
|
|
@ -19,7 +19,7 @@ span {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nobold {
|
||||
.nobold, nobold {
|
||||
font-weight: normal;
|
||||
color: gray;
|
||||
}
|
||||
|
@ -1598,3 +1598,92 @@ body.scrolled .toTop:hover {
|
|||
margin: 5px;
|
||||
border: 1px solid #C0CAD5;
|
||||
}
|
||||
|
||||
.header_search {
|
||||
background: #f7f7f7;
|
||||
width: 607px;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #e1e1e1;
|
||||
}
|
||||
|
||||
.header_search_inputbt {
|
||||
padding: 10px;
|
||||
border-top: 1px solid #ebebeb;
|
||||
display: flex;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.header_search_input {
|
||||
border: 1px solid #C0CAD5;
|
||||
padding: 3px;
|
||||
padding-left: 19px;
|
||||
font-size: 11px;
|
||||
font-family: tahoma, verdana, arial, sans-serif;
|
||||
width: 549px;
|
||||
background: #fff url('/assets/packages/static/openvk/img/search_icon.png') no-repeat;
|
||||
background-position-x: 4px;
|
||||
background-position-y: 4px;
|
||||
}
|
||||
|
||||
.button_search {
|
||||
border-radius: 2px;
|
||||
border: #595959;
|
||||
font-size: 11px;
|
||||
outline: none;
|
||||
white-space: nowrap;
|
||||
background: #595959;
|
||||
background-position: 0px -16px;
|
||||
color: #fff;
|
||||
padding: 4px 8px 4px;
|
||||
text-shadow: 0 1px 0 #686868;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
margin-left: 10px;
|
||||
width: 7.5%;
|
||||
}
|
||||
|
||||
.content_search {
|
||||
width: 607px;
|
||||
}
|
||||
|
||||
.content_search_list {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.content_search_list:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.content_search_list_ava img{
|
||||
width: 75px;
|
||||
}
|
||||
|
||||
.content_search_list_ava{
|
||||
width: 85px;
|
||||
}
|
||||
|
||||
.content_search_list_name_h4 {
|
||||
color: #2b587a;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.content_search_list_span {
|
||||
color: #7b7b7b;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.pinned-mark {
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
overflow: auto;
|
||||
background: url("/assets/packages/static/openvk/img/pin.png") no-repeat 0px 0px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.topic-list-item {
|
||||
border-bottom: #e6e6e6 solid 1px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 9d4eb793307e7af10c39c9da3b46646d97ad9dc2
|
|
@ -0,0 +1 @@
|
|||
system-run.png
|
|
@ -0,0 +1 @@
|
|||
run-build.png
|
|
@ -0,0 +1 @@
|
|||
appointment-new.png
|
BIN
Web/static/img/oxygen-icons/16x16/actions/acrobat.png
Normal file
After Width: | Height: | Size: 615 B |
|
@ -0,0 +1 @@
|
|||
folder-new.png
|
|
@ -0,0 +1 @@
|
|||
fork.png
|
1
Web/static/img/oxygen-icons/16x16/actions/actor.png
Normal file
|
@ -0,0 +1 @@
|
|||
im-user.png
|
|
@ -0,0 +1 @@
|
|||
flag-red.png
|
BIN
Web/static/img/oxygen-icons/16x16/actions/address-book-new.png
Normal file
After Width: | Height: | Size: 797 B |
|
@ -0,0 +1 @@
|
|||
address-book-new.png
|
|
@ -0,0 +1 @@
|
|||
address-book-new.png
|
|
@ -0,0 +1 @@
|
|||
go-home.png
|
|
@ -0,0 +1 @@
|
|||
folder-new.png
|
|
@ -0,0 +1 @@
|
|||
document-import.png
|
|
@ -0,0 +1 @@
|
|||
folder-new.png
|
|
@ -0,0 +1 @@
|
|||
configure.png
|
|
@ -0,0 +1 @@
|
|||
edit-delete.png
|
After Width: | Height: | Size: 384 B |
After Width: | Height: | Size: 441 B |
After Width: | Height: | Size: 454 B |
After Width: | Height: | Size: 456 B |
After Width: | Height: | Size: 444 B |
After Width: | Height: | Size: 457 B |
After Width: | Height: | Size: 418 B |
After Width: | Height: | Size: 445 B |
After Width: | Height: | Size: 399 B |
After Width: | Height: | Size: 452 B |
|
@ -0,0 +1 @@
|
|||
align-vertical-bottom-out.png
|
After Width: | Height: | Size: 451 B |
After Width: | Height: | Size: 484 B |
After Width: | Height: | Size: 457 B |
|
@ -0,0 +1 @@
|
|||
align-horizontal-top-out.png
|
BIN
Web/static/img/oxygen-icons/16x16/actions/align-vertical-top.png
Normal file
After Width: | Height: | Size: 413 B |
|
@ -0,0 +1 @@
|
|||
im-invisible-user.png
|
|
@ -0,0 +1 @@
|
|||
appointment-new.png
|
|
@ -0,0 +1 @@
|
|||
view-media-lyrics.png
|
|
@ -0,0 +1 @@
|
|||
view-statistics.png
|
|
@ -0,0 +1 @@
|
|||
view-media-playlist.png
|
|
@ -0,0 +1 @@
|
|||
view-refresh.png
|
|
@ -0,0 +1 @@
|
|||
dialog-ok-apply.png
|
1
Web/static/img/oxygen-icons/16x16/actions/answer.png
Normal file
|
@ -0,0 +1 @@
|
|||
dialog-ok-apply.png
|
BIN
Web/static/img/oxygen-icons/16x16/actions/application-exit.png
Normal file
After Width: | Height: | Size: 842 B |
|
@ -0,0 +1 @@
|
|||
configure.png
|
|
@ -0,0 +1 @@
|
|||
../places/folder-html.png
|
BIN
Web/static/img/oxygen-icons/16x16/actions/appointment-new.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
Web/static/img/oxygen-icons/16x16/actions/archive-extract.png
Normal file
After Width: | Height: | Size: 626 B |
After Width: | Height: | Size: 762 B |
BIN
Web/static/img/oxygen-icons/16x16/actions/archive-insert.png
Normal file
After Width: | Height: | Size: 433 B |
BIN
Web/static/img/oxygen-icons/16x16/actions/archive-remove.png
Normal file
After Width: | Height: | Size: 516 B |
BIN
Web/static/img/oxygen-icons/16x16/actions/arrow-down-double.png
Normal file
After Width: | Height: | Size: 691 B |
BIN
Web/static/img/oxygen-icons/16x16/actions/arrow-down.png
Normal file
After Width: | Height: | Size: 525 B |
BIN
Web/static/img/oxygen-icons/16x16/actions/arrow-left-double.png
Normal file
After Width: | Height: | Size: 653 B |
BIN
Web/static/img/oxygen-icons/16x16/actions/arrow-left.png
Normal file
After Width: | Height: | Size: 512 B |
BIN
Web/static/img/oxygen-icons/16x16/actions/arrow-right-double.png
Normal file
After Width: | Height: | Size: 666 B |
BIN
Web/static/img/oxygen-icons/16x16/actions/arrow-right.png
Normal file
After Width: | Height: | Size: 527 B |
BIN
Web/static/img/oxygen-icons/16x16/actions/arrow-up-double.png
Normal file
After Width: | Height: | Size: 650 B |
BIN
Web/static/img/oxygen-icons/16x16/actions/arrow-up.png
Normal file
After Width: | Height: | Size: 484 B |
1
Web/static/img/oxygen-icons/16x16/actions/atmosphere.png
Normal file
|
@ -0,0 +1 @@
|
|||
../categories/applications-internet.png
|