mirror of
https://github.com/openvk/openvk
synced 2024-12-22 16:42:32 +03:00
Add early implementation of wiki pages
This commit is contained in:
parent
bb055f90aa
commit
d1e55fd53d
17 changed files with 599 additions and 37 deletions
|
@ -2,8 +2,8 @@
|
||||||
namespace openvk\Web\Models\Entities;
|
namespace openvk\Web\Models\Entities;
|
||||||
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\{User, Manager};
|
use openvk\Web\Models\Entities\{User, Manager, WikiPage};
|
||||||
use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Managers};
|
use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Managers, WikiPages};
|
||||||
use Nette\Database\Table\{ActiveRow, GroupedSelection};
|
use Nette\Database\Table\{ActiveRow, GroupedSelection};
|
||||||
use Chandler\Database\DatabaseConnection as DB;
|
use Chandler\Database\DatabaseConnection as DB;
|
||||||
use Chandler\Security\User as ChandlerUser;
|
use Chandler\Security\User as ChandlerUser;
|
||||||
|
@ -328,6 +328,31 @@ class Club extends RowModel
|
||||||
])->delete();
|
])->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function containsWiki(): bool
|
||||||
|
{
|
||||||
|
return (bool) $this->getRecord()->pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWikiHomePage(): ?WikiPage
|
||||||
|
{
|
||||||
|
return (new WikiPages)->getByOwnerAndVID($this->getId() * -1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWikiEnabled(bool $enable = true): void
|
||||||
|
{
|
||||||
|
if($enable) {
|
||||||
|
if(is_null((new WikiPages)->getByOwnerAndVID($this->getId() * -1, 1))) {
|
||||||
|
$page = new WikiPage;
|
||||||
|
$page->setOwner($this->getId() * -1);
|
||||||
|
$page->setTitle("Fresh News");
|
||||||
|
$page->setSource("");
|
||||||
|
$page->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->stateChanges("pages", (int) $enable);
|
||||||
|
}
|
||||||
|
|
||||||
function canBeModifiedBy(User $user): bool
|
function canBeModifiedBy(User $user): bool
|
||||||
{
|
{
|
||||||
$id = $user->getId();
|
$id = $user->getId();
|
||||||
|
|
6
Web/Models/Entities/ILinkable.php
Normal file
6
Web/Models/Entities/ILinkable.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
namespace openvk\Web\Models\Entities;
|
||||||
|
|
||||||
|
interface ILinkable {
|
||||||
|
public function getOVKLink(): string;
|
||||||
|
}
|
3
Web/Models/Entities/WikiPage.php
Normal file
3
Web/Models/Entities/WikiPage.php
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
class_alias('openvk\Web\Models\Entities\WikiPage\WikiPage', 'openvk\Web\Models\Entities\WikiPage');
|
213
Web/Models/Entities/WikiPage/Parser.php
Normal file
213
Web/Models/Entities/WikiPage/Parser.php
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
namespace openvk\Web\Models\Entities\WikiPage;
|
||||||
|
use openvk\Web\Models\Repositories\WikiPages;
|
||||||
|
use Netcarver\Textile;
|
||||||
|
use HTMLPurifier_Config;
|
||||||
|
use HTMLPurifier;
|
||||||
|
|
||||||
|
class Parser
|
||||||
|
{
|
||||||
|
private $depth;
|
||||||
|
private $page;
|
||||||
|
private $repo;
|
||||||
|
private $vars;
|
||||||
|
private $ctx;
|
||||||
|
|
||||||
|
private $entityNames = [
|
||||||
|
"id" => [0, "User", "get", "getAvatarURL"],
|
||||||
|
"photo" => [1, "Photo", "getByOwnerAndVID", "getURL"],
|
||||||
|
"video" => [1, "Video", "getByOwnerAndVID", "getThumbnailURL"],
|
||||||
|
"note" => [1, "Note", "getNoteById", NULL],
|
||||||
|
"club" => [0, "Group", "get", "getAvatarURL"],
|
||||||
|
"wall" => [1, "Post", "getPostById", NULL],
|
||||||
|
];
|
||||||
|
|
||||||
|
const REFERENCE_SINGULAR = 0;
|
||||||
|
const REFERENCE_DUAL = 1;
|
||||||
|
|
||||||
|
function __construct(WikiPage $page, WikiPages $repo, int &$counter, array $vars = [], array $ctx = []) {
|
||||||
|
$this->depth = $counter;
|
||||||
|
$this->page = $page;
|
||||||
|
$this->repo = $repo;
|
||||||
|
$this->vars = $vars;
|
||||||
|
$this->ctx = $ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPurifier(): HTMLPurifier
|
||||||
|
{
|
||||||
|
$config = HTMLPurifier_Config::createDefault();
|
||||||
|
$config->set("Attr.AllowedClasses", ["unbordered", "inline", "nonexistent"]);
|
||||||
|
$config->set("Attr.DefaultInvalidImageAlt", "Unknown image");
|
||||||
|
$config->set("AutoFormat.AutoParagraph", true);
|
||||||
|
$config->set("AutoFormat.Linkify", true);
|
||||||
|
$config->set("URI.Base", "//$_SERVER[SERVER_NAME]/");
|
||||||
|
$config->set("URI.Munge", "/away.php?xinf=%n.%m:%r&css=%p&to=%s");
|
||||||
|
$config->set("URI.MakeAbsolute", true);
|
||||||
|
$config->set("HTML.Doctype", "XHTML 1.1");
|
||||||
|
$config->set("HTML.TidyLevel", "heavy");
|
||||||
|
$config->set("HTML.AllowedElements", [
|
||||||
|
"div",
|
||||||
|
"h3",
|
||||||
|
"h4",
|
||||||
|
"h5",
|
||||||
|
"h6",
|
||||||
|
"p",
|
||||||
|
"i",
|
||||||
|
"b",
|
||||||
|
"a",
|
||||||
|
"del",
|
||||||
|
"ins",
|
||||||
|
"sup",
|
||||||
|
"sub",
|
||||||
|
"table",
|
||||||
|
"thead",
|
||||||
|
"tbody",
|
||||||
|
"tr",
|
||||||
|
"td",
|
||||||
|
"th",
|
||||||
|
"img",
|
||||||
|
"ul",
|
||||||
|
"ol",
|
||||||
|
"li",
|
||||||
|
"hr",
|
||||||
|
"br",
|
||||||
|
"acronym",
|
||||||
|
"blockquote",
|
||||||
|
"cite",
|
||||||
|
]);
|
||||||
|
$config->set("HTML.AllowedAttributes", [
|
||||||
|
"table.summary",
|
||||||
|
"td.abbr",
|
||||||
|
"th.abbr",
|
||||||
|
"a.href",
|
||||||
|
"img.src",
|
||||||
|
"img.alt",
|
||||||
|
"img.style",
|
||||||
|
"div.style",
|
||||||
|
"div.title",
|
||||||
|
]);
|
||||||
|
$config->set("CSS.AllowedProperties", [
|
||||||
|
"float",
|
||||||
|
"height",
|
||||||
|
"width",
|
||||||
|
"max-height",
|
||||||
|
"max-width",
|
||||||
|
"font-weight",
|
||||||
|
]);
|
||||||
|
|
||||||
|
return new HTMLPurifier($config);;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveEntityURL(array $matches, bool $dual = false, bool $needImage = false): ?string
|
||||||
|
{
|
||||||
|
$descriptor = $this->entityNames[$matches[1]] ?? NULL;
|
||||||
|
if($descriptor && $descriptor[0] === ((int) $dual)) {
|
||||||
|
$repoClass = 'openvk\Web\Models\Repositories\\' . $descriptor[1] . 's';
|
||||||
|
$repoInst = new $repoClass;
|
||||||
|
$entity = $repoInst->{$descriptor[2]}(...array_map(function($x): int {
|
||||||
|
return (int) $x;
|
||||||
|
}, array_slice($matches, 2)));
|
||||||
|
|
||||||
|
if($entity) {
|
||||||
|
if($needImage) {
|
||||||
|
$serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"];
|
||||||
|
$thumbnailMethod = $descriptor[3];
|
||||||
|
if(!$thumbnailMethod)
|
||||||
|
return "$serverUrl/assets/packages/static/openvk/img/entity_nopic.png";
|
||||||
|
else
|
||||||
|
return $entity->{$thumbnailMethod}();
|
||||||
|
} else {
|
||||||
|
if(in_array('openvk\Web\Models\Entities\ILinkable', class_implements($entity)))
|
||||||
|
return $entity->getOVKLink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolvePageLinks(): string
|
||||||
|
{
|
||||||
|
return preg_replace_callback('%\[\[((?:\p{L}\p{M}?|[ 0-9\-\'_\/#])+)(\|(\p{L}\p{M}?|[ 0-9\-\'_\/]))?\]\]%u', function(array $matches): string {
|
||||||
|
$gid = $this->page->getOwner()->getId() * -1;
|
||||||
|
$page = $this->repo->getByOwnerAndTitle($gid, $matches[1]);
|
||||||
|
$title = $matches[3] ?? $matches[1];
|
||||||
|
$nTitle = htmlentities($title);
|
||||||
|
if(!$page)
|
||||||
|
return "\"(nonexistent)$nTitle\":/pages?elid=0&gid=" . ($gid * -1) . "&title=" . rawurlencode($title);
|
||||||
|
else
|
||||||
|
return "\"$nTitle\":/page" . $page->getPrettyId();
|
||||||
|
}, $this->page->getSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseVariables(): string
|
||||||
|
{
|
||||||
|
$html = $this->resolvePageLinks();
|
||||||
|
return preg_replace_callback('%(?<!\\\\)\?(\$|#)([A-z_]|[A-z_][A-z_0-9]|(?:[A-z_][A-z_\-\\\'0-9]+[A-z_0-9]))\?%', function(array $matches): string {
|
||||||
|
return (string) (($matches[1] === "$" ? $this->ctx : $this->vars)[$matches[2]] ?? "<b>Notice: Unknown variable $matches[2]</b><br/>");
|
||||||
|
}, $html);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseOvkTemplates(): string
|
||||||
|
{
|
||||||
|
$html = $this->parseVariables();
|
||||||
|
|
||||||
|
if($this->counter < 5) {
|
||||||
|
return preg_replace_callback('%{{Template:\-([0-9]++)_([0-9]++)\|?([^{}]++)}}%', function(array $matches): string {
|
||||||
|
$params = [];
|
||||||
|
[, $public, $page, $paramStr] = $matches;
|
||||||
|
|
||||||
|
$tplPage = $this->repo->getByOwnerAndVID(-1 * $public, (int) $page);
|
||||||
|
if(!$tplPage)
|
||||||
|
return "<b>Notice: No template at public$public/$page</b><br/>";
|
||||||
|
|
||||||
|
foreach(explode("|", $paramStr) as $kvPair) {
|
||||||
|
$kvPair = explode("=", $kvPair);
|
||||||
|
if(sizeof($kvPair) != 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$params[$kvPair[0]] = $kvPair[1];
|
||||||
|
}
|
||||||
|
bdump($params);
|
||||||
|
$parser = new Parser($tplPage, $this->repo, $this->depth, $params, $this->ctx);
|
||||||
|
return $parser->asHTML();
|
||||||
|
}, $html);
|
||||||
|
} else {
|
||||||
|
return "<b>Notice: Refusing to include template due to high indirection level (6)</b><br/>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseTextile(): string
|
||||||
|
{
|
||||||
|
return (new Textile\Parser)->parse($this->parseOvkTemplates());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseOvkIncludes(): string
|
||||||
|
{
|
||||||
|
$html = new \DOMDocument();
|
||||||
|
$html->loadHTML("<?xml encoding=\"UTF-8\">" . $this->parseTextile());
|
||||||
|
foreach($html->getElementsByTagName("a") as $link) {
|
||||||
|
$href = $link->getAttribute("href");
|
||||||
|
if(preg_match('%^#([a-z]++)(\-?[0-9]++)_([0-9]++)#$%', $href, $matches))
|
||||||
|
$link->setAttribute("href", $this->resolveEntityURL($matches, true, false) ?? "unknown");
|
||||||
|
else if(preg_match('%^#([a-z]++)(\-?[0-9]++)#$%', $href, $matches))
|
||||||
|
$link->setAttribute("href", $this->resolveEntityURL($matches, false, false) ?? "unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($html->getElementsByTagName("img") as $pic) {
|
||||||
|
$src = $pic->getAttribute("src");
|
||||||
|
if(preg_match('%^#([a-z]++)(\-?[0-9]++)_([0-9]++)#$%', $src, $matches))
|
||||||
|
$pic->setAttribute("src", $this->resolveEntityURL($matches, true, true) ?? "unknown");
|
||||||
|
else if(preg_match('%^#([a-z]++)(\-?[0-9]++)#$%', $src, $matches))
|
||||||
|
$pic->setAttribute("src", $this->resolveEntityURL($matches, false, true) ?? "unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $html->saveHTML();
|
||||||
|
}
|
||||||
|
|
||||||
|
function asHTML(): string
|
||||||
|
{
|
||||||
|
$purifier = $this->getPurifier();
|
||||||
|
return $purifier->purify($this->parseOvkIncludes());
|
||||||
|
}
|
||||||
|
}
|
39
Web/Models/Entities/WikiPage/WikiPage.php
Normal file
39
Web/Models/Entities/WikiPage/WikiPage.php
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
namespace openvk\Web\Models\Entities\WikiPage;
|
||||||
|
use openvk\Web\Models\Repositories\WikiPages;
|
||||||
|
use openvk\Web\Models\Entities\Postable;
|
||||||
|
|
||||||
|
class WikiPage extends Postable {
|
||||||
|
protected $tableName = "wikipages";
|
||||||
|
|
||||||
|
function getTitle(): string
|
||||||
|
{
|
||||||
|
return $this->getRecord()->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSource(): string
|
||||||
|
{
|
||||||
|
return $this->getRecord()->source;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHitCounter(): int
|
||||||
|
{
|
||||||
|
return $this->getRecord()->hits;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getText(array $ctx = []): string
|
||||||
|
{
|
||||||
|
$counter = 0;
|
||||||
|
$parser = new Parser($this, new WikiPages, $counter, ["firstInclusion" => "yes"], array_merge([
|
||||||
|
"time" => time(),
|
||||||
|
], $ctx));
|
||||||
|
|
||||||
|
return $parser->asHTML();
|
||||||
|
}
|
||||||
|
|
||||||
|
function view(): void
|
||||||
|
{
|
||||||
|
$this->stateChanges("hits", $this->getRecord()->hits + 1);
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
}
|
39
Web/Models/Repositories/WikiPages.php
Normal file
39
Web/Models/Repositories/WikiPages.php
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
namespace openvk\Web\Models\Repositories;
|
||||||
|
use openvk\Web\Models\Entities\WikiPage;
|
||||||
|
use Nette\Database\Table\ActiveRow;
|
||||||
|
use Chandler\Database\DatabaseConnection;
|
||||||
|
|
||||||
|
class WikiPages
|
||||||
|
{
|
||||||
|
private $context;
|
||||||
|
private $wp;
|
||||||
|
|
||||||
|
function __construct()
|
||||||
|
{
|
||||||
|
$this->context = DatabaseConnection::i()->getContext();
|
||||||
|
$this->wp = $this->context->table("wikipages");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function toWikiPage(?ActiveRow $ar): ?WikiPage
|
||||||
|
{
|
||||||
|
return is_null($ar) ? NULL : new WikiPage($ar);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get(int $id): ?WikiPage
|
||||||
|
{
|
||||||
|
return $this->toWikiPage($this->wp->get($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getByOwnerAndVID(int $owner, int $note): ?WikiPage
|
||||||
|
{
|
||||||
|
$wp = (clone $this->wp)->where(['owner' => $owner, 'virtual_id' => $note])->fetch();
|
||||||
|
return $this->toWikiPage($wp);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getByOwnerAndTitle(int $owner, string $title): ?WikiPage
|
||||||
|
{
|
||||||
|
$wp = (clone $this->wp)->where(['owner' => $owner, 'title' => $title])->fetch();
|
||||||
|
return $this->toWikiPage($wp);
|
||||||
|
}
|
||||||
|
}
|
|
@ -203,6 +203,7 @@ final class GroupPresenter extends OpenVKPresenter
|
||||||
$club->setAbout(empty($this->postParam("about")) ? NULL : $this->postParam("about"));
|
$club->setAbout(empty($this->postParam("about")) ? NULL : $this->postParam("about"));
|
||||||
$club->setShortcode(empty($this->postParam("shortcode")) ? NULL : $this->postParam("shortcode"));
|
$club->setShortcode(empty($this->postParam("shortcode")) ? NULL : $this->postParam("shortcode"));
|
||||||
$club->setWall(empty($this->postParam("wall")) ? 0 : 1);
|
$club->setWall(empty($this->postParam("wall")) ? 0 : 1);
|
||||||
|
$club->setWikiEnabled(empty($this->postParam("wiki")) ? false : true);
|
||||||
$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"));
|
||||||
|
|
||||||
$website = $this->postParam("website") ?? "";
|
$website = $this->postParam("website") ?? "";
|
||||||
|
|
112
Web/Presenters/WikiPresenter.php
Normal file
112
Web/Presenters/WikiPresenter.php
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
namespace openvk\Web\Presenters;
|
||||||
|
use openvk\Web\Models\Repositories\{Clubs, WikiPages};
|
||||||
|
use openvk\Web\Models\Entities\WikiPage;
|
||||||
|
|
||||||
|
final class WikiPresenter extends OpenVKPresenter
|
||||||
|
{
|
||||||
|
private $groups;
|
||||||
|
private $pages;
|
||||||
|
|
||||||
|
function __construct(Clubs $groups, WikiPages $pages)
|
||||||
|
{
|
||||||
|
$this->groups = $groups;
|
||||||
|
$this->pages = $pages;
|
||||||
|
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderView(int $owner, int $page)
|
||||||
|
{
|
||||||
|
$page = $this->pages->getByOwnerAndVID((int) $owner, (int) $page);
|
||||||
|
if(!$page || !$page->getOwner()->containsWiki())
|
||||||
|
$this->notFound();
|
||||||
|
|
||||||
|
$this->template->oURL = $page->getOwner()->getURL();
|
||||||
|
$this->template->oName = $page->getOwner()->getCanonicalName();
|
||||||
|
$this->template->title = $page->getTitle();
|
||||||
|
$this->template->html = $page->getText([
|
||||||
|
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSource(int $owner, int $page)
|
||||||
|
{
|
||||||
|
$page = $this->pages->getByOwnerAndVID((int) $owner, (int) $page);
|
||||||
|
if(!$page)
|
||||||
|
$this->flashFail("err", tr("error"), tr("page_id_invalid"));
|
||||||
|
|
||||||
|
$src = $page->getSource();
|
||||||
|
header("Content-Type: text/plain");
|
||||||
|
header("Content-Length: " . strlen($src));
|
||||||
|
header("Pragma: no-cache");
|
||||||
|
exit($src);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderEdit()
|
||||||
|
{
|
||||||
|
$this->assertUserLoggedIn();
|
||||||
|
|
||||||
|
if(is_null($groupId = $this->queryParam("gid")))
|
||||||
|
$this->flashFail("err", tr("error"), tr("group_id_invalid"));
|
||||||
|
|
||||||
|
$group = $this->groups->get((int) $groupId);
|
||||||
|
if(!$group)
|
||||||
|
$this->flashFail("err", tr("error"), tr("group_id_invalid"));
|
||||||
|
else if(!$group->canBeModifiedBy($this->user->identity))
|
||||||
|
$this->flashFail("err", tr("error"), tr("access_error"));
|
||||||
|
|
||||||
|
$page;
|
||||||
|
$pageId = $this->queryParam("elid");
|
||||||
|
$title = $this->requestParam("title");
|
||||||
|
if(!is_null($pageId) && $pageId > 0) {
|
||||||
|
$page = $this->pages->getByOwnerAndVID($groupId * -1, (int) $pageId);
|
||||||
|
if(!$page)
|
||||||
|
$this->flashFail("err", tr("error"), tr("page_id_invalid"));
|
||||||
|
} else if(!is_null($title)) {
|
||||||
|
$page = $this->pages->getByOwnerAndTitle($groupId * -1, $title);
|
||||||
|
} else {
|
||||||
|
$this->flashFail("err", tr("error"), tr("page_id_invalid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||||
|
$this->willExecuteWriteAction();
|
||||||
|
|
||||||
|
if($this->postParam("elid") == 0) {
|
||||||
|
$page = new WikiPage;
|
||||||
|
$page->setOwner($groupId * -1);
|
||||||
|
$page->setTitle($title);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->postParam("elid") != 0 && $title !== $page->getTitle()) {
|
||||||
|
if(!is_null($this->pages->getByOwnerAndTitle($groupId * -1, $title)))
|
||||||
|
$this->flashFail("err", tr("error"), tr("article_already_exists"));
|
||||||
|
|
||||||
|
$page->setTitle($title);
|
||||||
|
}
|
||||||
|
|
||||||
|
$page->setSource($this->postParam("source"));
|
||||||
|
$page->save();
|
||||||
|
|
||||||
|
$this->flash("succ", tr("succ"), tr("article_saved"));
|
||||||
|
$this->redirect("/pages?gid=$groupId&title=" . rawurlencode($title));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$page) {
|
||||||
|
$this->template->form = (object) [
|
||||||
|
"pId" => 0,
|
||||||
|
"gId" => $groupId,
|
||||||
|
"title" => $title ?? "",
|
||||||
|
"source" => "",
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$this->template->form = (object) [
|
||||||
|
"pId" => $page->getVirtualId(),
|
||||||
|
"gId" => $groupId,
|
||||||
|
"title" => $page->getTitle(),
|
||||||
|
"source" => $page->getSource(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -69,7 +69,7 @@
|
||||||
<input type="file" name="ava" accept="image/*" />
|
<input type="file" name="ava" accept="image/*" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td width="120" valign="top">
|
<td width="120" valign="top">
|
||||||
<span class="nobold">{_wall}: </span>
|
<span class="nobold">{_wall}: </span>
|
||||||
</td>
|
</td>
|
||||||
|
@ -77,6 +77,14 @@
|
||||||
<input type="checkbox" name="wall" value="1" {if $club->canPost()}checked{/if}/> {_group_allow_post_for_everyone}
|
<input type="checkbox" name="wall" value="1" {if $club->canPost()}checked{/if}/> {_group_allow_post_for_everyone}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="120" valign="top">
|
||||||
|
<span class="nobold">{_wiki}: </span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="wiki" n:attr="checked => $club->containsWiki()" /> {_enable_wiki}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td width="120" valign="top">
|
<td width="120" valign="top">
|
||||||
<span class="nobold">{_group_administrators_list}: </span>
|
<span class="nobold">{_group_administrators_list}: </span>
|
||||||
|
|
|
@ -41,42 +41,64 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div n:if="$club->getFollowersCount() > 0">
|
<div n:if="$club->getFollowersCount() > 0">
|
||||||
{var followersCount = $club->getFollowersCount()}
|
{var followersCount = $club->getFollowersCount()}
|
||||||
|
|
||||||
<div class="content_title_expanded" onclick="hidePanel(this, {$followersCount});">
|
<div class="content_title_expanded" onclick="hidePanel(this, {$followersCount});">
|
||||||
{_participants}
|
{_participants}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="content_subtitle">
|
||||||
|
{tr("participants", $followersCount)}
|
||||||
|
<div style="float:right;">
|
||||||
|
<a href="/club{$club->getId()}/followers">{_all_title}</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div style="padding-left: 5px;">
|
||||||
<div class="content_subtitle">
|
<table
|
||||||
{tr("participants", $followersCount)}
|
n:foreach="$club->getFollowers(1) as $follower"
|
||||||
<div style="float:right;">
|
n:class="User"
|
||||||
<a href="/club{$club->getId()}/followers">{_all_title}</a>
|
style="text-align:center;display:inline-block;width:62px"
|
||||||
</div>
|
cellspacing='4'>
|
||||||
</div>
|
<tbody>
|
||||||
<div style="padding-left: 5px;">
|
<tr>
|
||||||
<table
|
<td>
|
||||||
n:foreach="$club->getFollowers(1) as $follower"
|
<a href="{$follower->getURL()}">
|
||||||
n:class="User"
|
<img src="{$follower->getAvatarUrl()}" width="50" />
|
||||||
style="text-align:center;display:inline-block;width:62px"
|
</a>
|
||||||
cellspacing=4>
|
</td>
|
||||||
<tbody>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{$follower->getURL()}">
|
<a href="{$follower->getURL()}">{$follower->getFirstName()}</a>
|
||||||
<img src="{$follower->getAvatarUrl()}" width="50" />
|
</td>
|
||||||
</a>
|
</tr>
|
||||||
</td>
|
</tbody>
|
||||||
</tr>
|
</table>
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="{$follower->getURL()}">{$follower->getFirstName()}</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div n:if="$club->containsWiki()">
|
||||||
|
{var page = $club->getWikiHomePage()}
|
||||||
|
|
||||||
|
<div class="content_title_expanded" onclick="hidePanel(this);">
|
||||||
|
{$page->getTitle()}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="content_subtitle">
|
||||||
|
{_wiki_page}
|
||||||
|
<div n:if="$club->canBeModifiedBy($thisUser)" style="float:right;">
|
||||||
|
<a href="/pages?gid={$club->getId()}&elid=1" target="_blank">{_edit}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="padding-left: 5px;">
|
||||||
|
<article id="userContent" style="overflow-y: scroll; height: 200px;">
|
||||||
|
{$page->getText()|noescape}
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
{presenter "openvk!Wall->wallEmbedded", -$club->getId()}
|
{presenter "openvk!Wall->wallEmbedded", -$club->getId()}
|
||||||
|
|
||||||
|
|
42
Web/Presenters/templates/Wiki/Edit.xml
Normal file
42
Web/Presenters/templates/Wiki/Edit.xml
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
{extends "../@layout.xml"}
|
||||||
|
|
||||||
|
{block title}{_create_note}{/block}
|
||||||
|
|
||||||
|
{block header}
|
||||||
|
{_create_note}
|
||||||
|
{/block}
|
||||||
|
|
||||||
|
{block content}
|
||||||
|
<form id="wikiFactory" method="POST">
|
||||||
|
<input type="text" name="title" value="{$form->title}" placeholder="{_name_note}" style="width:603px;" />
|
||||||
|
<br/><br/>
|
||||||
|
<textarea name="source" style="display:none;"></textarea>
|
||||||
|
<div id="editor" style="width:600px;height:300px;border:1px solid grey"></div>
|
||||||
|
|
||||||
|
<input type="hidden" name="elid" value="{$form->pId}" />
|
||||||
|
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||||
|
<button class="button">{_save}</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{script "js/node_modules/monaco-editor/min/vs/loader.js"}
|
||||||
|
{script "js/node_modules/requirejs/bin/r.js"}
|
||||||
|
<script>
|
||||||
|
require.config({
|
||||||
|
paths: {
|
||||||
|
'vs': '/assets/packages/static/openvk/js/node_modules/monaco-editor/min/vs'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
require(['vs/editor/editor.main'], function() {
|
||||||
|
window._editor = monaco.editor.create(document.getElementById('editor'), {
|
||||||
|
value: {$form->source},
|
||||||
|
lineNumbers: "on",
|
||||||
|
wordWrap: "on",
|
||||||
|
language: "html"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector("#wikiFactory").addEventListener("submit", function() {
|
||||||
|
document.querySelector("textarea").value = window._editor.getValue();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{/block}
|
15
Web/Presenters/templates/Wiki/View.xml
Normal file
15
Web/Presenters/templates/Wiki/View.xml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{extends "../@layout.xml"}
|
||||||
|
|
||||||
|
{block title}{$title} - {$oName}{/block}
|
||||||
|
|
||||||
|
{block header}
|
||||||
|
<a href="{$oURL}">{$oName}</a>
|
||||||
|
»
|
||||||
|
{$title}
|
||||||
|
{/block}
|
||||||
|
|
||||||
|
{block content}
|
||||||
|
<article id="userContent" style="min-height: 300pt;">
|
||||||
|
{$html|noescape}
|
||||||
|
</article>
|
||||||
|
{/block}
|
|
@ -18,6 +18,7 @@ services:
|
||||||
- openvk\Web\Presenters\SupportPresenter
|
- openvk\Web\Presenters\SupportPresenter
|
||||||
- openvk\Web\Presenters\AdminPresenter
|
- openvk\Web\Presenters\AdminPresenter
|
||||||
- openvk\Web\Presenters\GiftsPresenter
|
- openvk\Web\Presenters\GiftsPresenter
|
||||||
|
- openvk\Web\Presenters\WikiPresenter
|
||||||
- openvk\Web\Presenters\MessengerPresenter
|
- openvk\Web\Presenters\MessengerPresenter
|
||||||
- openvk\Web\Presenters\ThemepacksPresenter
|
- openvk\Web\Presenters\ThemepacksPresenter
|
||||||
- openvk\Web\Presenters\VKAPIPresenter
|
- openvk\Web\Presenters\VKAPIPresenter
|
||||||
|
@ -36,4 +37,5 @@ services:
|
||||||
- openvk\Web\Models\Repositories\IPs
|
- openvk\Web\Models\Repositories\IPs
|
||||||
- openvk\Web\Models\Repositories\Vouchers
|
- openvk\Web\Models\Repositories\Vouchers
|
||||||
- openvk\Web\Models\Repositories\Gifts
|
- openvk\Web\Models\Repositories\Gifts
|
||||||
|
- openvk\Web\Models\Repositories\WikiPages
|
||||||
- openvk\Web\Models\Repositories\ContentSearchRepository
|
- openvk\Web\Models\Repositories\ContentSearchRepository
|
||||||
|
|
|
@ -215,6 +215,12 @@ routes:
|
||||||
handler: "Gifts->userGifts"
|
handler: "Gifts->userGifts"
|
||||||
- url: "/gifts"
|
- url: "/gifts"
|
||||||
handler: "Gifts->stub"
|
handler: "Gifts->stub"
|
||||||
|
- url: "/pages"
|
||||||
|
handler: "Wiki->edit"
|
||||||
|
- url: "/page{num}_{num}"
|
||||||
|
handler: "Wiki->view"
|
||||||
|
- url: "/page{num}_{num}.textile"
|
||||||
|
handler: "Wiki->source"
|
||||||
- url: "/admin"
|
- url: "/admin"
|
||||||
handler: "Admin->index"
|
handler: "Admin->index"
|
||||||
- url: "/admin/users"
|
- url: "/admin/users"
|
||||||
|
|
|
@ -1598,3 +1598,15 @@ body.scrolled .toTop:hover {
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
border: 1px solid #C0CAD5;
|
border: 1px solid #C0CAD5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
article#userContent {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
article#userContent a {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
article#userContent a.nonexistent {
|
||||||
|
color: #d90000;
|
||||||
|
}
|
|
@ -234,7 +234,7 @@ return (function() {
|
||||||
|
|
||||||
define("nullptr", NULL);
|
define("nullptr", NULL);
|
||||||
define("OPENVK_DEFAULT_INSTANCE_NAME", "OpenVK", false);
|
define("OPENVK_DEFAULT_INSTANCE_NAME", "OpenVK", false);
|
||||||
define("OPENVK_VERSION", "Altair Preview ($ver)", false);
|
define("OPENVK_VERSION", "Altair Preview ($ver-wiki)", false);
|
||||||
define("OPENVK_DEFAULT_PER_PAGE", 10, false);
|
define("OPENVK_DEFAULT_PER_PAGE", 10, false);
|
||||||
define("__OPENVK_ERROR_CLOCK_IN_FUTURE", "Server clock error: FK1200-DTF", false);
|
define("__OPENVK_ERROR_CLOCK_IN_FUTURE", "Server clock error: FK1200-DTF", false);
|
||||||
});
|
});
|
||||||
|
|
17
install/sqls/00014-wikipages.sql
Normal file
17
install/sqls/00014-wikipages.sql
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS `wikipages` (
|
||||||
|
`id` bigint(20) unsigned PRIMARY KEY NOT NULL AUTO_INCREMENT,
|
||||||
|
`owner` bigint(20) NOT NULL,
|
||||||
|
`virtual_id` bigint(20) NOT NULL,
|
||||||
|
`created` bigint(20) NOT NULL,
|
||||||
|
`edited` bigint(20) DEFAULT NULL,
|
||||||
|
`title` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`source` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`hits` bigint(20) NOT NULL DEFAULT 0,
|
||||||
|
`anonymous` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
|
`deleted` tinyint(4) NOT NULL DEFAULT '0'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
ALTER TABLE `wikipages` ADD INDEX( `owner`, `virtual_id`);
|
||||||
|
ALTER TABLE `wikipages` ADD UNIQUE( `owner`, `title`);
|
||||||
|
|
||||||
|
ALTER TABLE `groups` ADD `pages` BOOLEAN NOT NULL DEFAULT FALSE AFTER `wall`;
|
Loading…
Reference in a new issue