Add themepack support

This commit is contained in:
Jill Stingray 2020-06-11 23:21:49 +03:00
parent 3eb5b6b4fc
commit d509096d46
14 changed files with 330 additions and 171 deletions

3
.gitignore vendored
View file

@ -6,5 +6,8 @@ Web/static/js/node_modules
tmp/* tmp/*
!tmp/.gitkeep !tmp/.gitkeep
!tmp/themepack_artifacts/.gitkeep
themepacks/*
!themepacks/.gitkeep
storage/* storage/*
!storage/.gitkeep !storage/.gitkeep

View file

@ -1,5 +1,6 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\Web\Models\Entities; namespace openvk\Web\Models\Entities;
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}; use openvk\Web\Models\Entities\{Photo, Message, Correspondence};
@ -53,11 +54,16 @@ class User extends RowModel
return $this->getRecord()->id; return $this->getRecord()->id;
} }
function getStyle(): int function getStyle(): string
{ {
return $this->getRecord()->style; return $this->getRecord()->style;
} }
function getTheme(): ?Themepack
{
return Themepacks::i()[$this->getStyle()] ?? NULL;
}
function getStyleAvatar(): int function getStyleAvatar(): int
{ {
return $this->getRecord()->style_avatar; return $this->getRecord()->style_avatar;

View file

@ -0,0 +1,35 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use openvk\Web\Themes\Themepacks;
final class ThemepacksPresenter extends OpenVKPresenter
{
protected $banTolerant = true;
function renderResource(string $themepack, string $version, string $resClass, string $resource): void
{
if(!isset(Themepacks::i()[$themepack]))
$this->notFound();
else
$theme = Themepacks::i()[$themepack];
if($resClass === "resource") {
$data = $theme->fetchStaticResource($resource);
} else if($resClass === "stylesheet") {
if($resource !== "styles.css")
$this->notFound();
else
$data = $theme->fetchStyleSheet();
} else {
$this->notFound();
}
if(!$data)
$this->notFound();
header("Content-Type: " . system_extension_mime_type($resource));
header("Content-Size: " . strlen($data));
header("Cache-Control: public, no-transform, max-age=31536000");
exit($data);
}
}

View file

@ -1,6 +1,7 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\Web\Presenters; namespace openvk\Web\Presenters;
use openvk\Web\Util\Sms; use openvk\Web\Util\Sms;
use openvk\Web\Themes\Themepacks;
use openvk\Web\Models\Entities\Photo; use openvk\Web\Models\Entities\Photo;
use openvk\Web\Models\Repositories\Users; use openvk\Web\Models\Repositories\Users;
use openvk\Web\Models\Repositories\Albums; use openvk\Web\Models\Repositories\Albums;
@ -91,7 +92,7 @@ final class UserPresenter extends OpenVKPresenter
if(!$id) if(!$id)
$this->notFound(); $this->notFound();
else
$user = $this->users->get($id); $user = $this->users->get($id);
if($_SERVER["REQUEST_METHOD"] === "POST") { if($_SERVER["REQUEST_METHOD"] === "POST") {
if($_GET['act'] === "main" || $_GET['act'] == NULL) { if($_GET['act'] === "main" || $_GET['act'] == NULL) {
@ -215,78 +216,79 @@ final class UserPresenter extends OpenVKPresenter
if(!$id) if(!$id)
$this->notFound(); $this->notFound();
else
$user = $this->users->get($id); $user = $this->users->get($id);
if($_SERVER["REQUEST_METHOD"] === "POST") { if($_SERVER["REQUEST_METHOD"] === "POST") {
if($_GET['act'] === "main" || $_GET['act'] == NULL) { if($_GET['act'] === "main" || $_GET['act'] == NULL) {
if($this->postParam("old_pass") && $this->postParam("new_pass") && $this->postParam("repeat_pass")) { if($this->postParam("old_pass") && $this->postParam("new_pass") && $this->postParam("repeat_pass")) {
if($this->postParam("new_pass") === $this->postParam("repeat_pass")) { if($this->postParam("new_pass") === $this->postParam("repeat_pass")) {
if(!$this->user->identity->getChandlerUser()->updatePassword($this->postParam("new_pass"), $this->postParam("old_pass"))) if(!$this->user->identity->getChandlerUser()->updatePassword($this->postParam("new_pass"), $this->postParam("old_pass")))
$this->flashFail("err", "Ошибка", "Старый пароль не совпадает."); $this->flashFail("err", "Ошибка", "Старый пароль не совпадает.");
} else { } else {
$this->flashFail("err", "Ошибка", "Новые пароли не совпадают."); $this->flashFail("err", "Ошибка", "Новые пароли не совпадают.");
}
} }
if(!$user->setShortCode(empty($this->postParam("sc")) ? NULL : $this->postParam("sc")))
$this->flashFail("err", "Ошибка", "Короткий адрес имеет некорректный формат.");
}elseif($_GET['act'] === "privacy") {
$settings = [
"page.read",
"page.info.read",
"groups.read",
"photos.read",
"videos.read",
"notes.read",
"friends.read",
"friends.add",
"wall.write",
];
foreach($settings as $setting) {
$input = $this->postParam(str_replace(".", "_", $setting));
$user->setPrivacySetting($setting, min(3, abs($input ?? $user->getPrivacySetting($setting))));
}
}elseif($_GET['act'] === "interface") {
if ($this->postParam("style") <= 20 && $this->postParam("style") >= 0)
$user->setStyle((int)$this->postParam("style"));
if ($this->postParam("style_avatar") <= 2 && $this->postParam("style_avatar") >= 0)
$user->setStyle_Avatar((int)$this->postParam("style_avatar"));
if (in_array($this->postParam("rating"), [0, 1]))
$user->setShow_Rating((int) $this->postParam("rating"));
}elseif($_GET['act'] === "lMenu") {
$settings = [
"menu_bildoj" => "photos",
"menu_filmetoj" => "videos",
"menu_mesagoj" => "messages",
"menu_notatoj" => "notes",
"menu_grupoj" => "groups",
"menu_novajoj" => "news",
];
foreach($settings as $checkbox => $setting)
$user->setLeftMenuItemStatus($setting, $this->checkbox($checkbox));
} }
try { if(!$user->setShortCode(empty($this->postParam("sc")) ? NULL : $this->postParam("sc")))
$user->save(); $this->flashFail("err", "Ошибка", "Короткий адрес имеет некорректный формат.");
} catch(\PDOException $ex) { }elseif($_GET['act'] === "privacy") {
if($ex->getCode() == 23000) $settings = [
$this->flashFail("err", "Ошибка", "Данный короткий адрес уже занят."); "page.read",
else "page.info.read",
throw $ex; "groups.read",
"photos.read",
"videos.read",
"notes.read",
"friends.read",
"friends.add",
"wall.write",
];
foreach($settings as $setting) {
$input = $this->postParam(str_replace(".", "_", $setting));
$user->setPrivacySetting($setting, min(3, abs($input ?? $user->getPrivacySetting($setting))));
} }
}elseif($_GET['act'] === "interface") {
if (isset(Themepacks::i()[$this->postParam("style")]) || $this->postParam("style") === Themepacks::DEFAULT_THEME_ID)
$user->setStyle($this->postParam("style"));
$this->flash( if ($this->postParam("style_avatar") <= 2 && $this->postParam("style_avatar") >= 0)
"succ", $user->setStyle_Avatar((int)$this->postParam("style_avatar"));
"Изменения сохранены",
"Новые данные появятся на вашей странице.<br/>Если вы изменили стиль, перезагрузите страницу." if (in_array($this->postParam("rating"), [0, 1]))
); $user->setShow_Rating((int) $this->postParam("rating"));
}elseif($_GET['act'] === "lMenu") {
$settings = [
"menu_bildoj" => "photos",
"menu_filmetoj" => "videos",
"menu_mesagoj" => "messages",
"menu_notatoj" => "notes",
"menu_grupoj" => "groups",
"menu_novajoj" => "news",
];
foreach($settings as $checkbox => $setting)
$user->setLeftMenuItemStatus($setting, $this->checkbox($checkbox));
} }
$this->template->mode = in_array($this->queryParam("act"), [
"main", "privacy", "finance", "interface" try {
]) ? $this->queryParam("act") $user->save();
: "main"; } catch(\PDOException $ex) {
$this->template->user = $user; if($ex->getCode() == 23000)
$this->flashFail("err", "Ошибка", "Данный короткий адрес уже занят.");
else
throw $ex;
}
$this->flash(
"succ",
"Изменения сохранены",
"Новые данные появятся на вашей странице.<br/>Если вы изменили стиль, перезагрузите страницу."
);
}
$this->template->mode = in_array($this->queryParam("act"), [
"main", "privacy", "finance", "interface"
]) ? $this->queryParam("act")
: "main";
$this->template->user = $user;
$this->template->themes = Themepacks::i()->getThemeList();
} }
} }

View file

@ -12,74 +12,9 @@
{script "js/node_modules/umbrellajs/umbrella.min.js"} {script "js/node_modules/umbrellajs/umbrella.min.js"}
{script "js/openvk.cls.js"} {script "js/openvk.cls.js"}
{ifset $thisUser} {ifset $thisUser}
{var style = (int) ($_GET['__ovkStyleOverride'] ?? $thisUser->getStyle())} {if !is_null($thisUser->getTheme())}
{var theme = $thisUser->getTheme()}
{if $style == 1} <link rel="stylesheet" href="/themepack/{$theme->getId()}/{$theme->getVersion()}/stylesheet/styles.css" />
{css "css/vkontakte.css"}
{/if}
{if $style == 2}
{css "css/ovkdan.5.css"}
{/if}
{if $style == 3}
{css "css/vkontakte.css"}
{css "css/vkontakte2006.css"}
{/if}
{if $style == 4}
{css "css/ovkg.css"}
{/if}
{if $style == 5}
{css "css/black.css"}
{/if}
{if $style == 6}
{css "css/ash_oss_themes/vk2015.2.css"}
{/if}
{if $style == 7}
{css "css/spacepink.css"}
{/if}
{if $style == 9}
{css "css/vriska.css"}
{/if}
{if $style == 8}
{css "css/ash_oss_themes/vk2015.2.css"}
{/if}
{if $style == 10}
{css "css/ash_oss_themes/ВСоюзе.css"}
{/if}
{if $style == 11}
{css "css/ash_oss_themes/fb2005.css"}
{/if}
{if $style == 12}
{css "css/ooer.css"}
{/if}
{if $style == 13}
{css "css/ash_oss_themes/Twitter2007.css"}
{/if}
{if $style == 14}
{css "css/kos.css"}
{/if}
{if $style == 15}
{css "css/ash_oss_themes/pager.css"}
{/if}
{if $style == 16}
{css "css/ash_oss_themes/vkdark.css"}
{/if}
{if $style == 17}
{css "css/ash_oss_themes/vtlenu.css"}
{/if}
{if $style == 18}
{css "css/ash_oss_themes/Fb2006.css"}
{/if}
{if $style == 19}
{css "css/ash_oss_themes/vkdefenders08.css"}
{/if}
{if $style == 20}
{css "css/ash_oss_themes/tlenta.css"}
{/if}
{if $thisUser->getStyleAvatar() == 1}
{css "css/avatar.1.css"}
{/if}
{if $thisUser->getStyleAvatar() == 2}
{css "css/avatar.2.css"}
{/if} {/if}
{/ifset} {/ifset}
@ -105,11 +40,7 @@
</div> </div>
<div class="layout"> <div class="layout">
{ifset $thisUser} <div id="xhead" class="dm"></div>
{if in_array($thisUser->getStyle(), [4, 5, 6, 20])}
<div id="xhead" class="dm"></div>
{/if}
{/ifset}
<div class="page_header"> <div class="page_header">
<a href="/" class="home_button" title="OpenVK">openvk</a> <a href="/" class="home_button" title="OpenVK">openvk</a>
<div n:if="isset($thisUser) ? !$thisUser->isBanned() : true" class="header_navigation"> <div n:if="isset($thisUser) ? !$thisUser->isBanned() : true" class="header_navigation">

View file

@ -305,33 +305,12 @@ Block chain PRIZM был запущен 17 февраля 2017-го года. П
</td> </td>
<td> <td>
<select name="style"> <select name="style">
<optgroup label="OpenVK"> <option value="ovk" {if $user->getStyle() == 'ovk'}selected{/if}>OpenVK ({_"default"})</option>
<option value="0" {if $user->getStyle() == 0}selected{/if}>OpenVK ({_"default"})</option> <option n:foreach="$themes as $id => $theme"
<option value="2" {if $user->getStyle() == 2}selected{/if}>OpenVK [by Daniel Myslivets]</option> n:attr="selected => $user->getStyle() === $id"
<option value="16" {if $user->getStyle() == 16}selected{/if}>OpenVK Night [by Ash Defenders]</option> value="{$id}">
<option value="5" {if $user->getStyle() == 5}selected{/if}>OpenVK Black [by Daniel Myslivets]</option> {$theme}
<option value="7" {if $user->getStyle() == 7}selected{/if}>OpenVK SpacePink [by Ash Defenders]</option> </option>
</optgroup>
<optgroup label="VKontakte">
<option value="3" {if $user->getStyle() == 3}selected{/if}>ВКонтакте (2006)</option>
<option value="1" {if $user->getStyle() == 1}selected{/if}>ВКонтакте (2007)</option>
<option value="8" {if $user->getStyle() == 8}selected{/if}>ВКонтакте (2015) [by Ash Defenders]</option>
<option value="19" {if $user->getStyle() == 19}selected{/if}>VK.COM [by Ash Defenders]</option>
<option value="6" {if $user->getStyle() == 6}selected{/if} disabled>VK.com (не работает)</option>
<option value="10" {if $user->getStyle() == 10}selected{/if}>ВСоюзе</option>
<option value="17" {if $user->getStyle() == 17}selected{/if}>ВТлену [by Ash Defenders]</option>
</optgroup>
<optgroup label="Сайты">
<option value="11" {if $user->getStyle() == 11}selected{/if}>TheFacebook (2005) [by Ash Defenders]</option>
<option value="18" {if $user->getStyle() == 18}selected{/if}>Facebook (2006) [by Ash Defenders]</option>
<option value="13" {if $user->getStyle() == 13}selected{/if}>Twitter (2007) [by Ash Defenders]</option>
<option value="4" {if $user->getStyle() == 4}selected{/if}>IVinete.ru [by Daniel Myslivets]</option>
<option value="15" {if $user->getStyle() == 15}selected{/if}>Пейджер [by Ash Defenders]</option>
<option value="20" {if $user->getStyle() == 20}selected{/if}>TLENTA.RU [by Ash Defenders]</option>
<option value="14" {if $user->getStyle() == 14}selected{/if}>kosSpace</option>
<option value="9" {if $user->getStyle() == 9}selected{/if}>vriska.ru</option>
<option value="12" {if $user->getStyle() == 12}selected{/if}>r/Ooer</option>
</optgroup>
</select> </select>
</td> </td>
</tr> </tr>

View file

@ -0,0 +1,5 @@
<?php declare(strict_types=1);
namespace openvk\Web\Themes\Exceptions;
final class IncompatibleThemeException extends \Exception
{}

View file

@ -0,0 +1,5 @@
<?php declare(strict_types=1);
namespace openvk\Web\Themes\Exceptions;
final class MalformedManifestException extends \Exception
{}

View file

@ -0,0 +1,5 @@
<?php declare(strict_types=1);
namespace openvk\Web\Themes\Exceptions;
final class NotThemeDirectoryException extends \Exception
{}

76
Web/Themes/Themepack.php Normal file
View file

@ -0,0 +1,76 @@
<?php declare(strict_types=1);
namespace openvk\Web\Themes;
class Themepack
{
private $id;
private $ver;
private $meta;
private $home;
function __construct(string $id, string $ver, object $meta)
{
$this->id = $id;
$this->ver = $ver;
$this->meta = $meta;
$this->home = OPENVK_ROOT . "/themepacks/$id";
}
function getId(): string
{
return $this->id;
}
function getName(?string $lang = NULL): string
{
if(!$this->meta->name)
return $this->getId() . " theme";
else if(is_array($this->meta->name))
return $this->meta->name[$lang ?? "_"] ?? $this->getId() . " theme";
else
return $this->meta->name;
}
function getVersion(): string
{
return $this->ver;
}
function getDescription(): string
{
return $this->meta->description ?? "A theme with name \"" . $this->getName() . "\"";
}
function getAuthor(): string
{
return $this->meta->author ?? $this->getName() . " authors";
}
function fetchStyleSheet(): ?string
{
$file = "$this->home/stylesheet.css";
return file_exists($file) ? file_get_contents($file) : NULL;
}
function fetchStaticResource(string $name): ?string
{
$file = "$this->home/res/$name";
return file_exists($file) ? file_get_contents($file) : NULL;
}
static function themepackFromDir(string $dirname): Themepack
{
$manifestFile = "$dirname/theme.yml";
if(!file_exists($manifestFile))
throw new Exceptions\NotThemeDirectoryException("Could not locate manifest at $dirname");
$manifest = (object) chandler_parse_yaml($manifestFile);
if(!isset($manifest->id) || !isset($manifest->version) || !isset($manifest->openvk_version) || !isset($manifest->metadata))
throw new Exceptions\MalformedManifestException("Manifest is missing required information");
if($manifest->openvk_version > Themepacks::THEMPACK_ENGINE_VERSION)
throw new Exceptions\IncompatibleThemeException("Theme is built for newer OVK (themeEngine" . $manifest->openvk_version . ")");
return new static($manifest->id, $manifest->version, (object) $manifest->metadata);
}
}

105
Web/Themes/Themepacks.php Normal file
View file

@ -0,0 +1,105 @@
<?php declare(strict_types=1);
namespace openvk\Web\Themes;
use Nette\InvalidStateException as ISE;
use Chandler\Session\Session;
use Chandler\Patterns\TSimpleSingleton;
class Themepacks implements \ArrayAccess
{
const THEMPACK_ENGINE_VERSION = 1;
const DEFAULT_THEME_ID = "ovk"; # блин было бы смешно если было бы Fore, потому что Лунка, а Luna это название дефолт темы винхп
private $loadedThemepacks = [];
function __construct()
{
foreach(glob(OPENVK_ROOT . "/themepacks/*", GLOB_ONLYDIR) as $themeDir) {
try {
$theme = Themepack::themepackFromDir($themeDir);
$tid = $theme->getId();
if(isset($this->loadedThemepacks[$tid]))
trigger_error("Duplicate theme $tid found at $themeDir, skipping...", E_USER_WARNING);
else
$this->loadedThemepacks[$tid] = $theme;
} catch(\Exception $e) {
trigger_error("Could not load theme at $themeDir, skipping...", E_USER_WARNING);
}
}
}
private function installUnpacked(string $path): bool
{
try {
$theme = Themepack::themepackFromDir($path);
$tid = $theme->getId();
if(isset($this->loadedThemepacks[$tid]))
return false;
rename($path, OPENVK_ROOT . "/themepacks/$tid");
$this->loadedThemepacks[$tid] = $theme;
return true;
} catch(\Exception $e) {
return false;
}
}
function getThemeList(): \Traversable
{
foreach($this->loadedThemepacks as $id => $theme)
yield $id => ($theme->getName(Session::i()->get("lang", "ru")));
}
/* ArrayAccess */
function offsetExists($offset): bool
{
return $offset === Themepacks::DEFAULT_THEME_ID ? false : isset($this->loadedThemepacks[$offset]);
}
function offsetGet($offset)
{
return $this->loadedThemepacks[$offset];
}
function offsetSet($offset, $value): void
{
throw new ISE("Theme substitution in runtime is prohbited");
}
function offsetUnset($offset): void
{
$this->uninstall($offset);
}
/* /ArrayAccess */
function install(string $archivePath): bool
{
if(!file_exists($archivePath))
return false;
$tmpDir = mkdir(tempnam(OPENVK_ROOT . "/tmp/themepack_artifacts/", "themex_"));
try {
$archive = new \CabArchive($archivePath);
$archive->extract($tmpDir);
return $this->installUnpacked($tmpDir);
} catch (\Exception $e) {
return false;
} finally {
rmdir($tmpDir);
}
}
function uninstall(string $id): bool
{
if(!isset($loadedThemepacks[$id]))
return false;
rmdir(OPENVK_ROOT . "/themepacks/$id");
unset($loadedThemepacks[$id]);
return true;
}
use TSimpleSingleton;
}

View file

@ -19,6 +19,7 @@ services:
- openvk\Web\Presenters\SupportPresenter - openvk\Web\Presenters\SupportPresenter
- openvk\Web\Presenters\AdminPresenter - openvk\Web\Presenters\AdminPresenter
- openvk\Web\Presenters\MessengerPresenter - openvk\Web\Presenters\MessengerPresenter
- openvk\Web\Presenters\ThemepacksPresenter
- openvk\Web\Models\Repositories\Users - openvk\Web\Models\Repositories\Users
- openvk\Web\Models\Repositories\Posts - openvk\Web\Models\Repositories\Posts
- openvk\Web\Models\Repositories\Photos - openvk\Web\Models\Repositories\Photos
@ -32,4 +33,4 @@ services:
- openvk\Web\Models\Repositories\Restores - openvk\Web\Models\Repositories\Restores
- openvk\Web\Models\Repositories\Notifications - openvk\Web\Models\Repositories\Notifications
- openvk\Web\Models\Repositories\TicketComments - openvk\Web\Models\Repositories\TicketComments
- openvk\Web\Models\Repositories\ContentSearchRepository - openvk\Web\Models\Repositories\ContentSearchRepository

View file

@ -91,6 +91,12 @@ routes:
handler: "Wall->delete" handler: "Wall->delete"
- url: "/blob_{text}/{text}.{text}" - url: "/blob_{text}/{text}.{text}"
handler: "Blob->file" handler: "Blob->file"
- url: "/themepack/{text}/{?version}/{?resClass}/{?any}"
handler: "Themepacks->resource"
placeholders:
version: "(?:[0-9]+\\.?)+"
resClass: "stylesheet|resource"
any: ".+"
- url: "/albums{num}" - url: "/albums{num}"
handler: "Photos->albumList" handler: "Photos->albumList"
- url: "/albums/create" - url: "/albums/create"
@ -186,4 +192,4 @@ routes:
- url: "/{?shortCode}" - url: "/{?shortCode}"
handler: "UnknownTextRouteStrategy->delegate" handler: "UnknownTextRouteStrategy->delegate"
placeholders: placeholders:
shortCode: "[a-z][a-z0-9\\@\\.\\_]{0,30}[a-z0-9]" shortCode: "[a-z][a-z0-9\\@\\.\\_]{0,30}[a-z0-9]"

0
themepacks/.gitkeep Normal file
View file