diff options
62 files changed, 1887 insertions, 1592 deletions
| diff --git a/src/application/actions/_default/get.php b/src/application/actions/_default/get.php index cd0c21c..616589f 100644 --- a/src/application/actions/_default/get.php +++ b/src/application/actions/_default/get.php @@ -1,11 +1,7 @@  <?php  use mystic\forum\orm\Topic; -use mystic\forum\utils\RequestUtils; -_view("template_start"); -_view("template_navigation_start"); -_view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); -_view("template_navigation_end"); -_view("view_topics", ["topics" => $db->fetchCustom(Topic::class, "ORDER BY creation_date DESC")]); -_view("template_end", [...getThemeAndLangInfo()]); +render("view_topics.twig", [ +    "topics" => $db->fetchCustom(Topic::class, "ORDER BY creation_date DESC"), +]); diff --git a/src/application/actions/auth/get.php b/src/application/actions/auth/get.php index 2ff38ff..dfeb837 100644 --- a/src/application/actions/auth/get.php +++ b/src/application/actions/auth/get.php @@ -1,10 +1,3 @@  <?php -use mystic\forum\utils\RequestUtils; - -_view("template_start", ["_title" => __("Log in")]); -_view("template_navigation_start"); -_view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); -_view("template_navigation_end"); -_view("form_login"); -_view("template_end", [...getThemeAndLangInfo()]); +render("login.twig"); diff --git a/src/application/actions/deletepost/post.php b/src/application/actions/deletepost/post.php index b711021..4cd9872 100644 --- a/src/application/actions/deletepost/post.php +++ b/src/application/actions/deletepost/post.php @@ -24,11 +24,11 @@ if (!$db->fetch($item) || $item->deleted) {      exit;  } -$topicAuthor = new User(); -$topicAuthor->id = $item->authorId; +$postAuthor = new User(); +$postAuthor->id = $item->authorId; -if (!$db->fetch($topicAuthor)) -    $topicAuthor = null; +if (!$db->fetch($postAuthor)) +    $postAuthor = null;  $topic = new Topic();  $topic->id = $item->topicId; @@ -36,10 +36,10 @@ $topic->id = $item->topicId;  if (!$db->fetch($topic))      $topic = null; -$canEdit = ($currentUser->id === $topicAuthor?->id && $topicAuthor?->hasPermission(UserPermissions::DELETE_OWN_POST)) +$canDelete = ($currentUser->id === $postAuthor?->id && $postAuthor?->hasPermission(UserPermissions::DELETE_OWN_POST))            || ($currentUser->hasPermission(UserPermissions::DELETE_OTHER_POST)); -if (!$canEdit) { +if (!$canDelete) {      http_response_code(403);      msg_error("You don't have permission to delete this post");      exit; @@ -75,16 +75,11 @@ if ($confirm !== null) {      header("Location: ?_action=viewtopic&topic=" . urlencode($item->topicId));  } else { -    _view("template_start", ["_title" => __("Delete post")]); -    _view("template_navigation_start"); -    _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); -    _view("template_navigation_end"); -    _view("form_delete_post_confirm", [ +    render("delete_post.twig", [          "post" => $item, -        "postAuthor" => $topicAuthor, +        "postAuthor" => $postAuthor,          "topicAuthor" => null,          "attachments" => $attachments,          "topic" => $topic,      ]); -    _view("template_end", [...getThemeAndLangInfo()]);  } diff --git a/src/application/actions/deletetopic/post.php b/src/application/actions/deletetopic/post.php index e67cadf..8683c0f 100644 --- a/src/application/actions/deletetopic/post.php +++ b/src/application/actions/deletetopic/post.php @@ -55,13 +55,8 @@ if ($confirm !== null) {      header("Location: .");  } else { -    _view("template_start", ["_title" => "Delete topic"]); -    _view("template_navigation_start"); -    _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); -    _view("template_navigation_end"); -    _view("form_delete_topic_confirm", [ +    render("delete_topic.twig", [          "topic" => $topic,          "topicAuthor" => $topicAuthor,      ]); -    _view("template_end", [...getThemeAndLangInfo()]);  } diff --git a/src/application/actions/newtopic/get.php b/src/application/actions/newtopic/get.php index 366caac..621cd18 100644 --- a/src/application/actions/newtopic/get.php +++ b/src/application/actions/newtopic/get.php @@ -1,10 +1,3 @@  <?php -use mystic\forum\utils\RequestUtils; - -_view("template_start", ["_title" => __("New topic")]); -_view("template_navigation_start"); -_view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); -_view("template_navigation_end"); -_view("form_newtopic"); -_view("template_end", [...getThemeAndLangInfo()]); +render("new_topic.twig"); diff --git a/src/application/actions/pwreset/get.php b/src/application/actions/pwreset/get.php index c66b28d..35b93f0 100644 --- a/src/application/actions/pwreset/get.php +++ b/src/application/actions/pwreset/get.php @@ -11,19 +11,7 @@ if ($token !== null && $signature !== null) {          exit;      } -    _view("template_start", [ "_title" => __("Reset password") ]); -    _view("template_navigation_start"); -    _view("template_navigation_end"); -    _view("form_new_password", [ -        "token" => $token, -        "signature" => $signature, -    ]); -    _view("template_end", [...getThemeAndLangInfo()]); +    render("new_password.twig");  } else { -    _view("template_start", [ "_title" => __("Reset password") ]); -    _view("template_navigation_start"); -    _view("template_navigation", ["user" => null]); -    _view("template_navigation_end"); -    _view("form_password_reset"); -    _view("template_end", [...getThemeAndLangInfo()]); +    render("password_reset.twig");  } diff --git a/src/application/actions/register/get.php b/src/application/actions/register/get.php index 914ea4e..d1df1b6 100644 --- a/src/application/actions/register/get.php +++ b/src/application/actions/register/get.php @@ -1,10 +1,3 @@  <?php -use mystic\forum\utils\RequestUtils; - -_view("template_start", ["_title" => __("Register")]); -_view("template_navigation_start"); -_view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); -_view("template_navigation_end"); -_view("form_register"); -_view("template_end", [...getThemeAndLangInfo()]); +render("register.twig"); diff --git a/src/application/actions/search/get.php b/src/application/actions/search/get.php index d3de970..bc1bdba 100644 --- a/src/application/actions/search/get.php +++ b/src/application/actions/search/get.php @@ -41,25 +41,14 @@ if ($query !== null) {      }      $end_time = microtime(true);      $search_duration = $end_time - $start_time; -     -    _view("template_start", ["_title" => __("Search results for “%query%”", [ "query" => $query ])]); -    _view("template_navigation_start"); -    _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); -    _view("template_navigation_end"); -    _view("form_search", [ "query" => $query ]); -    _view("view_search_results", [ + +    render("search.twig", [          "posts" => &$posts,          "topics" => &$topicLookup,          "users" => &$userLookup,          "attachments" => &$attachmentLookup,          "search_duration" => $search_duration,      ]); -    _view("template_end", [...getThemeAndLangInfo()]);  } else { -    _view("template_start", ["_title" => __("Search")]); -    _view("template_navigation_start"); -    _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); -    _view("template_navigation_end"); -    _view("form_search"); -    _view("template_end", [...getThemeAndLangInfo()]); -}
\ No newline at end of file +    render("search.twig"); +} diff --git a/src/application/actions/viewtopic/get.php b/src/application/actions/viewtopic/get.php index 45dc824..636d791 100644 --- a/src/application/actions/viewtopic/get.php +++ b/src/application/actions/viewtopic/get.php @@ -6,7 +6,6 @@ use mystic\forum\orm\Attachment;  use mystic\forum\orm\Post;  use mystic\forum\orm\TopicLogMessage;  use mystic\forum\orm\User; -use mystic\forum\utils\RequestUtils;  $posts = $db->fetchCustom(Post::class, 'WHERE topic_id = $1 ORDER BY post_date', [ $topicId ]);  /** @var TopicLogMessage[] $logMessages */ @@ -25,13 +24,7 @@ if ($topic->createdBy !== null) {  $allItems = [...$posts, ...$logMessages];  usort($allItems, fn(Post|TopicLogMessage $a, Post|TopicLogMessage $b): int => $a->postDate <=> $b->postDate); -_view("template_start", ["_title" => $topic->title]); -_view("template_navigation_start"); -_view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]); -_view("template_navigation_end"); -_view("view_topic_start", ["topic" => $topic, "topicAuthor" => $topicAuthor]); - -foreach ($allItems as $item) { +$allItems = array_map(function(Post|TopicLogMessage $item) use (&$db, &$topicAuthor, &$topic): array {      /** @var ?User $postAuthor */      $postAuthor = null;      if ($item->authorId !== null && !isset($userCache[$item->authorId])) { @@ -46,31 +39,27 @@ foreach ($allItems as $item) {      if ($item instanceof Post) {          $attachments = $db->fetchCustom(Attachment::class, 'WHERE post_id = $1', [ $item->id ]); -        _view("view_post", [ +        return [ +            "type" => "post",              "post" => $item,              "postAuthor" => $postAuthor,              "topicAuthor" => $topicAuthor,              "attachments" => $attachments,              "topic" => $topic, -        ]); +        ];      } else { -        _view("view_topiclog", [ +        return [ +            "type" => "logMessage",              "logMessage" => $item,              "postAuthor" => $postAuthor,              "topicAuthor" => $topicAuthor,              "topic" => $topic, -        ]); +        ];      } -} - -_view("view_topic_end"); - -if ($topic->isLocked) { -    _view("view_topic_locked"); -} elseif ($currentUser) { -    _view("form_addpost"); -} else { -    _view("view_logintoreply"); -} +}, $allItems); -_view("template_end", [...getThemeAndLangInfo()]); +render("view_topic.twig", [ +    "topic" => $topic, +    "topicAuthor" => $topicAuthor, +    "allItems" => &$allItems, +]); diff --git a/src/application/actions/viewuser/get.php b/src/application/actions/viewuser/get.php index c98df16..e6dac43 100644 --- a/src/application/actions/viewuser/get.php +++ b/src/application/actions/viewuser/get.php @@ -17,18 +17,15 @@ foreach ($posts as $item) {      $attachs = $db->fetchCustom(Attachment::class, 'WHERE post_id = $1', [ $item->id ]);      $attachments[$item->id] = $attachs;  } -_view("template_start", ["_title" => $user->displayName]); -_view("template_navigation_start"); -_view("template_navigation", [ -    "user" => $currentUser, -    "isViewingOwnProfile" => $isOwnProfile, -]); -_view("template_navigation_end"); -_view("view_user", [ + +$dateJoined = DateTime::createFromImmutable($user->created); +$dateJoined->setTime(0, 0, 0, 0); + +render("view_user.twig", [      "user" => $user,      "posts" => $posts,      "topics" => $topics,      "attachments" => $attachments,      "lastNameChangeTooRecent" => $lastNameChangeTooRecent, +    "dateJoined" => $dateJoined,  ]); -_view("template_end", [...getThemeAndLangInfo()]); diff --git a/src/application/messages/de.msg b/src/application/messages/de.msg index d77f2eb..b6fb3d6 100644 --- a/src/application/messages/de.msg +++ b/src/application/messages/de.msg @@ -525,3 +525,8 @@ metadata({  : "Spoiler"  = "Spoiler" +: "View profile" += "Profil ansehen" + +: "Log out" += "Abmelden" diff --git a/src/application/mystic/forum/utils/RequestUtils.php b/src/application/mystic/forum/utils/RequestUtils.php index 5c6a485..11253aa 100644 --- a/src/application/mystic/forum/utils/RequestUtils.php +++ b/src/application/mystic/forum/utils/RequestUtils.php @@ -40,9 +40,10 @@ final class RequestUtils {          return $fieldValue;      } -    public static function storeForm(): void { +    public static function storeForm(string $formId): void {          $_SESSION["lastForm"] = $_POST ?? [];          $_SESSION["lastForm_uri"] = $_SERVER["REQUEST_URI"]; +        $_SESSION["lastForm_id"] = $formId;      }      public static function setFormErrorDestination(?string $dest): ?string { @@ -55,7 +56,7 @@ final class RequestUtils {          $next ??= self::$formErrorDestination ?? $_SERVER["REQUEST_URI"];          $_SESSION["formError/$formId"] = $message;          // store last form submission -        self::storeForm(); +        self::storeForm($formId);          header("Location: $next");          exit;      } @@ -66,14 +67,20 @@ final class RequestUtils {          return $err;      } -    public static function getLastForm(string &$lastFormUri): ?array { +    public static function getLastForm_legacy(string &$lastFormUri): ?array {          $lastFormUri = $_SESSION["lastForm_uri"] ?? "";          return $_SESSION["lastForm"] ?? null;      } +    public static function getLastForm(string &$lastFormId): ?array { +        $lastFormId = $_SESSION["lastForm_id"] ?? ""; +        return $_SESSION["lastForm"] ?? null; +    } +      public static function clearLastForm(): void {          unset($_SESSION["lastForm"]);          unset($_SESSION["lastForm_uri"]); +        unset($_SESSION["lastForm_id"]);      }      public static function getAuthorizedUser(Database &$db): ?User { diff --git a/src/application/templates/bootstrap-3/base.twig b/src/application/templates/bootstrap-3/base.twig new file mode 100644 index 0000000..5e003b9 --- /dev/null +++ b/src/application/templates/bootstrap-3/base.twig @@ -0,0 +1,250 @@ +{%- if title -%} +    {%- set title = title ~ " | " -%} +{%- endif -%} +{%- set title = title ~ (g.env.MYSTIC_FORUM_TITLE|default("Forum")) -%} +{%- set nextParam = "" -%} +{%- if g.globals.action in ["login", "register"] -%} +    {%- set nextParam = g.get.next|default("") -%} +{%- else -%} +    {%- set nextParam = g.server.REQUEST_URI -%} +{%- endif -%} +<!DOCTYPE html> +<!--[if lt IE 7]>      <html lang="de" class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]--> +<!--[if IE 7]>         <html lang="de" class="no-js lt-ie9 lt-ie8"> <![endif]--> +<!--[if IE 8]>         <html lang="de" class="no-js lt-ie9"> <![endif]--> +<!--[if gt IE 8]><!--> <html lang="de" class="no-js"> <!--<![endif]--> +<head> +    <meta charset="utf-8"> +    <meta http-equiv="X-UA-Compatible" content="IE=edge"> +    <meta name="viewport" content="width=device-width,initial-scale=1"> +    <meta name="generator" content="mysticBB {{ constant("MYSTICBB_VERSION") }}"> +    <title>{{ title }}</title> +    <link rel="stylesheet" href="?_action=ctheme"> +    <script src="/ui/jquery-1.12.4.min.js"></script> +    <script src="/ui/dist/js/bootstrap.min.js"></script> +    <script src="/ui/modernizr-2.6.2.min.js"></script> +    <script src="?_action=ji18n"></script> +    <!--[if lt IE 9]> +    <script src="/ui/html5shiv.min.js"></script> +    <script src="/ui/respond.min.js"></script> +    <![endif]--> +    {% block head_after %}{% endblock %} +</head> +<body> + +{% block nav %} +<nav class="navbar navbar-default navbar-static-top"> +    <div class="container"> +        <div class="navbar-header"> +            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#nav-collapse" aria-expanded="false"> +                <span class="sr-only">Toggle navigation</span> +                <span class="icon-bar"></span> +                <span class="icon-bar"></span> +                <span class="icon-bar"></span> +            </button> +            <a class="navbar-brand" href="."> +                <span class="myb-icon mybblogo" aria-hidden="false"></span> +                {{ g.env.MYSTIC_FORUM_TITLE|default("Forum") }} +            </a> +        </div> +        <div class="collapse navbar-collapse" id="nav-collapse"> +            {% block navbar %} +                {% if currentUser %} +                    <ul class="nav navbar-nav navbar-right"> +                        <li><p class="navbar-text">{{ __("Welcome, %user%!", { +                            user: currentUser.id == constant("mystic\\forum\\orm\\User::SUPERUSER_ID") ? ('<strong class="text-danger">' ~ (currentUser.displayName|e("html")) ~ '</strong>')|raw : ('<strong>' ~ (currentUser.displayName|e("html")) ~ '</strong>')|raw +                        }) }} +                        </p></li> +                        <li{{ g.globals.action == "search" ? ' class="active"'|raw : '' }}><a href="?_action=search"><span class="fa fa-search" aria-hidden="true"></span><span class="sr-only">{{ __("Search") }}</span></a></li> +                        <li{{ (g.globals.action == "viewuser" and g.get.user == currentUser.id) ? ' class="active"'|raw : '' }}><a href="?_action=viewuser&user={{ currentUser.id|url_encode }}"><span class="fa fa-user" aria-hidden="true"></span><span class="sr-only">{{ __("View profile") }}</span></a></li> +                        <li><a href="?_action=logout&next={{ g.server.REQUEST_URI|url_encode }}"><span class="fa fa-sign-out" aria-hidden="true"></span><span class="sr-only">{{ __("Log out") }}</span></a></li> +                    </ul> +                {% else %} +                    <ul class="nav navbar-nav navbar-right"> +                        <li{{ g.globals.action == "search" ? ' class="active"'|raw : '' }}><a href="?_action=search"><span class="fa fa-search" aria-hidden="true"></span><span class="sr-only">{{ __("Search") }}</span></a></li> +                        <li{{ g.globals.action == "auth" ? ' class="active"'|raw : '' }}><a href="?_action=auth&next={{ nextParam|url_encode }}">{{ __("Log in") }}</a></li> +                        {% if constant("REGISTRATION_ENABLED") %} +                            <li{{ g.globals.action == "register" ? ' class="active"'|raw : '' }}><a href="?_action=register&next={{ nextParam|url_encode }}">{{ __("Register") }}</a></li> +                        {% endif %} +                    </ul> +                {% endif %} +            {% endblock %} +        </div> +    </div> +</nav> +{% endblock %} + +{% block main %} +<div class="container"> +    {% block content %}{% endblock %} +</div> +{% endblock %} + +{% block footer %} +<footer class="footer"> +<div class="container"> +<div class="panel panel-default"> +<div class="panel-body"> +    <table style="border-collapse: collapse; width: 100%; background: none"> +    <tbody> +    <tr> +        <td style="padding: 0; vertical-align: middle; text-align: left; width: 100%" class="text-normal"> +            © {{ "now"|date("Y") }} {{ g.env.MYSTIC_FORUM_COPYRIGHT|default(g.env.MYSTIC_FORUM_TITLE)|default("Forum") }}. +            Powered by <a href="https://git.jkohl.link/mystic-forum.git/tag/?h=v{{ constant("MYSTICBB_VERSION")|url_encode }}">mysticBB v{{ constant("MYSTICBB_VERSION") }}</a>. +        </td> +        <td style="padding: 0; vertical-align: middle; text-align: right; white-space: nowrap" class="text-normal"> +            <form action="?_action=settheme" class="form-inline seamless-inline" method="post"> +                <input type="hidden" name="next" value="{{ g.server.REQUEST_URI }}"> +                <div class="form-group"> +                    <label for="theme-select">{{ __("Theme:") }}</label> +                    <select class="form-control input-sm auto-submit" id="theme-select" name="theme"> +                        {% for themeKey, themeInfo in availableThemes %} +                            <option value="{{ themeKey }}"{{ themeKey == currentTheme ? " selected" : "" }}>{{ themeInfo.name }}</option> +                        {% endfor %} +                    </select> +                </div> +            </form> +                +            <form action="?_action=setlang" class="form-inline seamless-inline" method="post"> +                <input type="hidden" name="next" value="{{ g.server.REQUEST_URI }}"> +                <div class="form-group"> +                    <label for="lang-select">{{ __("Language:") }}</label> +                    <select class="form-control input-sm auto-submit" id="lang-select" name="lang"> +                        {% for langKey, langName in availableLangs %} +                            <option value="{{ langKey }}"{{ langKey == currentLang ? " selected" : "" }}>{{ langName }}</option> +                        {% endfor %} +                    </select> +                </div> +            </form> +        </td> +    </tr> +    </tbody> +    </table> +</div> +</div> +</div> +</footer> +{% endblock %} + +{% block scripts %} +<script type="text/javascript"> +    $(function() { +        function insertAroundSelection(textarea, before, after) { +            var start = textarea.selectionStart; +            var end = textarea.selectionEnd; +            var text = textarea.value; +            var pre = text.substring(0, start); +            var inner = text.substring(start, end); +            var post = text.substring(end); +            start += before.length; +            end += before.length; +            text = pre + before + inner + after + post; +            textarea.value = text; +            textarea.focus(); +            textarea.selectionStart = start; +            textarea.selectionEnd = end; +        } + +        function getTextarea(btn) { +            return $($(btn).attr("data-area"))[0]; +        } + +        var commands = { +            bold: function(textarea) { +                insertAroundSelection(textarea, "[b]", "[/b]"); +            }, +            italic: function(textarea) { +                insertAroundSelection(textarea, "[i]", "[/i]"); +            }, +            underline: function(textarea) { +                insertAroundSelection(textarea, "[u]", "[/u]"); +            }, +            strikethrough: function(textarea) { +                insertAroundSelection(textarea, "[s]", "[/s]"); +            }, +            sup: function(textarea) { +                insertAroundSelection(textarea, "[^]", "[/^]"); +            }, +            sub: function(textarea) { +                insertAroundSelection(textarea, "[_]", "[/_]"); +            }, +            quote: function(textarea) { +                insertAroundSelection(textarea, "> ", ""); +            }, +            spoiler: function(textarea) { +                insertAroundSelection(textarea, "[spoiler]", "[/spoiler]"); +            } +        } + +        $("button[data-editor-command]").attr("data-toggle", "tooltip").attr("data-placement", "bottom").click(function() { +            var command = $(this).attr("data-editor-command"); +            var textarea = getTextarea(this); +            commands[command](textarea); +        }).tooltip(); +    }); +</script> + +<script> +$(function() { +    var _messages = {{ ({ +        selectFiles: [ +            ___("Select file", "Select files", 1), +            ___("Select file", "Select files", 2), +        ], +        filesSelected: [ +            ___("%n% file selected", "No files selected", 0), +            ___("%n% file selected", "%n% files selected", 1), +            ___("%n% file selected", "%n% files selected", 2), +        ], +    })|json_encode|raw }} + +    $(".auto-submit").on("change", function() { +        $(this)[0].form.submit(); +    }); + +    $("._time").each(function(i, e) { +        var date = new Date($(e).text()); +        $(e).text(date.toLocaleString()); +    }); +    $("._date").each(function(i, e) { +        var date = new Date($(e).text()); +        $(e).text(date.toLocaleDateString()); +    }); +    $("._time-only").each(function(i, e) { +        var date = new Date($(e).text()); +        $(e).text(date.toLocaleTimeString()); +    }); + +    $("input[type=file]").each(function(i, e) { +        var isMultiple = !!$(e).prop("multiple"); +        var isSmall = !!$(e).hasClass("small"); +        var $input = $('<input type="text" readonly class="form-control" />').attr("placeholder", _messages.filesSelected[0]).css("text-overflow", "ellipsis"); +        var $btn = $('<button class="btn btn-default" type="button"></button>'); +        if (isSmall) { +            $input.addClass("input-sm"); +            $btn.addClass("btn-sm"); +        } +        $(e).after($('<div class="input-group file-input-group"></div>').append( +            $input, +            $('<span class="input-group-btn"></span>').append( +                $btn.text(_messages.selectFiles[isMultiple ? 1 : 0]).click(function() { +                    $(e).click(); +                }) +            ) +        )).addClass("sr-only"); +        $(e).on("change", function() { +            var files = $(e)[0].files; +            if (files.length < 1) +                $input.val(""); +            else if (files.length === 1) +                $input.val(files[0].name); +            else +                $input.val(_messages.filesSelected[2].replace("%n%", files.length)); +        }); +    }) +}); +</script> +{% endblock %} + +</body> +</html> diff --git a/src/application/templates/bootstrap-3/components/alert_error.twig b/src/application/templates/bootstrap-3/components/alert_error.twig new file mode 100644 index 0000000..f3f759b --- /dev/null +++ b/src/application/templates/bootstrap-3/components/alert_error.twig @@ -0,0 +1,8 @@ +<div class="alert alert-danger" role="alert"> +    <span class="fa fa-exclamation-circle" aria-hidden="true"></span> +    {% if message starts with "?!HTML::" %} +        {{ message|slice(8)|raw }} +    {% else %} +        {{ message }} +    {% endif %} +</div> diff --git a/src/application/templates/bootstrap-3/components/alert_info.twig b/src/application/templates/bootstrap-3/components/alert_info.twig new file mode 100644 index 0000000..099d091 --- /dev/null +++ b/src/application/templates/bootstrap-3/components/alert_info.twig @@ -0,0 +1,8 @@ +<div class="alert alert-info" role="alert"> +    <span class="fa fa-info-circle" aria-hidden="true"></span> +    {% if message starts with "?!HTML::" %} +        {{ message|slice(8)|raw }} +    {% else %} +        {{ message }} +    {% endif %} +</div> diff --git a/src/application/templates/bootstrap-3/components/alert_success.twig b/src/application/templates/bootstrap-3/components/alert_success.twig new file mode 100644 index 0000000..1c8f403 --- /dev/null +++ b/src/application/templates/bootstrap-3/components/alert_success.twig @@ -0,0 +1,8 @@ +<div class="alert alert-success" role="alert"> +    <span class="fa fa-check-circle" aria-hidden="true"></span> +    {% if message starts with "?!HTML::" %} +        {{ message|slice(8)|raw }} +    {% else %} +        {{ message }} +    {% endif %} +</div> diff --git a/src/application/templates/bootstrap-3/components/post.twig b/src/application/templates/bootstrap-3/components/post.twig new file mode 100644 index 0000000..ac969c0 --- /dev/null +++ b/src/application/templates/bootstrap-3/components/post.twig @@ -0,0 +1,168 @@ +{%- set fileAttachments = attachments|filter(a => not (a.mimeType starts with "image/" or a.mimeType starts with "video/")) -%} +{%- set imageAttachments = attachments|filter(a => a.mimeType starts with "image/" or a.mimeType starts with "video/") -%} + +{%- set canReply = +    not post.deleted +    and not topic.isLocked +    and currentUser is not null +    and currentUser.hasPermission(permission("CREATE_OWN_POST")) +-%} + +{%- set canEdit = +    not post.deleted +    and not topic.isLocked +    and currentUser is not null +    and ( +        ( +            postAuthor is not null +            and postAuthor.hasPermission(permission("EDIT_OWN_POST")) +        ) +        or currentUser.hasPermission(permission("EDIT_OTHER_POST")) +    ) -%} + +{%- set canDelete = +    not post.deleted +    and currentUser is not null +    and ( +        ( +            postAuthor is not null +            and postAuthor.id == currentUser.id +            and postAuthor.hasPermission(permission("DELETE_OWN_POST")) +        ) +        or currentUser.hasPermission(permission("DELETE_OTHER_POST")) +    ) -%} + +{%- set canViewAttachments = currentUser is not null -%} + +{%- set your_are_the_author = +    currentUser is not null +    and postAuthor is not null +    and currentUser.id == postAuthor.id +-%} + +{%- set is_op = +    topicAuthor is not null +    and postAuthor is not null +    and postAuthor.id == topicAuthor.id +-%} + +{% if post.deleted %} +    <div class="media" id="post-{{ post.id }}"> +        <div class="media-left hidden-sm hidden-xs"> +            <div class="media-object" style="width:64px"></div> +        </div> +        <div class="media-body"> +            <div class="well icon-well text-warning"> +                <span class="fa fa-exclamation-triangle text-warning" aria-hidden="true"></span> +                <em>{{ __("This post has been deleted") }}</em> +            </div> +        </div> +    </div> +{% else %} +    <div class="media" id="post-{{ post.id }}" data-text="{{ post.content }}" style="overflow: visible;"> +        {% if not hide_pfp %} +            <div class="media-left hidden-sm hidden-xs"> +                {% if postAuthor %} +                    {% if hide_actions %} +                        <img class="media-object" alt="{{ __("Profile picture") }}" src="?_action=profilepicture&user={{ postAuthor.id|url_encode }}" width="64" height="64"> +                    {% else %} +                        <a href="?_action=viewuser&user={{ postAuthor.id|url_encode }}"> +                            <img class="media-object" alt="{{ __("Profile picture") }}" src="?_action=profilepicture&user={{ postAuthor.id|url_encode }}" width="64" height="64"> +                        </a> +                    {% endif %} +                {% else %} +                    <div class="media-object" style="width:64px;height:64px"></div> +                {% endif %} +            </div> +        {% endif %} +        <div class="media-body" style="overflow: visible;"> +            <div class="panel panel-default"> +                <div class="panel-heading"> +                    <div class="panel-title h3"> +                        {% if not hide_actions %} +                            <div class="pull-right"> +                                <a href="#post-{{ post.id }}" class="btn btn-default"><span class="fa fa-link" aria-hidden="true"></span><span class="sr-only">{{ __("Permalink") }}</span></a> +                                {% if canReply %} +                                    <button data-post-id="{{ post.id }}" class="btn btn-default js-only _reply-post"><span class="fa fa-comment" aria-hidden="true"></span><span class="sr-only">{{ __("Reply to post") }}</span></button> +                                {% endif %} +                                {% if canEdit %} +                                    <button data-post-id="{{ post.id }}" class="btn btn-default js-only _edit-post"><span class="fa fa-pencil" aria-hidden="true"></span><span class="sr-only">{{ __("Edit post") }}</span></button> +                                {% endif %} +                                {% if canDelete %} +                                    <form action="?_action=deletepost" method="post" class="seamless-inline"> +                                        <input type="hidden" name="post" value="{{ post.id }}"> +                                        <button type="submit" class="btn btn-danger"><span class="fa fa-trash" aria-hidden="true"></span><span class="sr-only">{{ __("Delete post") }}</span></button> +                                    </form> +                                {% endif %} +                            </div> +                        {% endif %} +                        {% if postAuthor %} +                            {% if hide_actions %} +                                {{ postAuthor.displayName }} +                            {% else %} +                                <a href="?_action=viewuser&user={{ postAuthor.id|url_encode }}">{{ postAuthor.displayName }}</a> +                                {% if is_op %} +                                    <span title="{{ __("Created this topic") }}" class="text-info fa fa-user-circle"></span> +                                {% endif %} +                            {% endif %} +                            {% if your_are_the_author %} +                                <span class="text-normal label label-primary">{{ __("You") }}</span> +                            {% endif %} +                        {% else %} +                            <em class="text-muted">{{ __("(deleted)") }}</em> +                        {% endif %} +                    </div> +                    <span class="_time">{{ post.postDate.format("c") }}</span> +                    {% if post.edited %} +                        <em class="text-muted">{{ __("(edited)") }}</em> +                    {% endif %} +                </div> +                <div class="panel-body"> +                    <div class="post-content">{{ renderPost(post.content) }}</div> +                    {% if imageAttachments|length > 0 %} +                        <div class="post-images clearfix"> +                            {% for attachment in imageAttachments %} +                                {% if hide_actions %} +                                    <span class="image-attachment" title="{{ attachment.name }}"> +                                        <img class="image-attachment-image" src="?_action=thumb&attachment={{ attachment.id|url_encode }}" alt="" width="100"> +                                    </span> +                                {% else %} +                                    <a class=" +                                        image-attachment +                                        attachment +                                        {{ attachment.mimeType starts with "video/" ? "video-attachment" }} +                                    " href="?_action=attachment&attachment={{ attachment.id|url_encode }}" title="{{ attachment.name }}" data-attachment-id="{{ attachment.id }}"> +                                        <img class="image-attachment-image" src="?_action=thumb&attachment={{ attachment.id|url_encode }}" alt="" width="100"> +                                        {% if not canViewAttachments %} +                                            <span class="attachment-lock fa fa-ban" aria-hidden="true"></span> +                                        {% elseif attachment.mimeType starts with "video/" %} +                                            <span class="video-player-icon fa fa-play-circle" aria-hidden="true"></span> +                                        {% endif %} +                                    </a> +                                {% endif %} +                            {% endfor %} +                        </div> +                    {% endif %} +                </div> +                {% if fileAttachments|length > 0 %} +                    <div class="panel-footer"> +                        <div class="btn-group"> +                            {% for attachment in fileAttachments %} +                                {% if hide_actions %} +                                    <button class="btn btn-default">{{ attachment.name }}</button> +                                {% else %} +                                    <a class="btn btn-default attachment" href="?_action=attachment&attachment={{ attachment.id|url_encode }}"> +                                        {% if not canViewAttachments %} +                                            <span class="fa fa-ban" aria-hidden="true"></span> +                                        {% endif %} +                                        {{ attachment.name }} +                                    </a> +                                {% endif %} +                            {% endfor %} +                        </div> +                    </div> +                {% endif %} +            </div> +        </div> +    </div> +{% endif %} diff --git a/src/application/templates/bootstrap-3/components/topic_log.twig b/src/application/templates/bootstrap-3/components/topic_log.twig new file mode 100644 index 0000000..9364cd1 --- /dev/null +++ b/src/application/templates/bootstrap-3/components/topic_log.twig @@ -0,0 +1,62 @@ +{%- set user = "" -%} +{%- if postAuthor is null -%} +    {%- set user = __("deleted")|e("html") -%} +{%- else -%} +    {%- set user = +        '<a href="?_action=viewuser&user=' +        ~ postAuthor.id|url_encode|e("html") +        ~ '">' +        ~ postAuthor.displayName|e("html") +        ~ '</a>' +    -%} +{%- endif -%} + +<div class="media" id="post-{{ logMessage.id }}"> +    <div class="media-left hidden-sm hidden-xs"> +        {% if postAuthor %} +            {% if hideActions %} +                <img class="media-object" alt="{{ __("Profile picture") }}" src="?_action=profilepicture&user={{ postAuthor.id|url_encode }}" width="64" height="64"> +            {% else %} +                <a href="?_action=viewuser&user={{ postAuthor.id|url_encode }}"> +                    <img class="media-object" alt="{{ __("Profile picture") }}" src="?_action=profilepicture&user={{ postAuthor.id|url_encode }}" width="64" height="64"> +                </a> +            {% endif %} +        {% else %} +            <div class="media-object" style="width:64px;height:64px"></div> +        {% endif %} +    </div> +    <div class="media-body"> +        {% if logMessage.type == constant("mystic\\forum\\orm\\TopicLogMessage::LOCKED") %} +            <div class="well icon-well text-info"> +                <span class="fa fa-lock text-info" aria-hidden="true"></span> +                <em>{{ __("%user% locked this topic", { +                    "user": user, +                }) }}</em> +                <br> +                <small class="_time">{{ logMessage.postDate.format("c") }}</small> +            </div> +        {% elseif logMessage.type == constant("mystic\\forum\\orm\\TopicLogMessage::UNLOCKED") %} +            <div class="well icon-well text-success"> +                <span class="fa fa-unlock text-success" aria-hidden="true"></span> +                <em>{{ __("%user% unlocked this topic", { +                    "user": user, +                }) }}</em> +                <br> +                <small class="_time">{{ logMessage.postDate.format("c") }}</small> +            </div> +        {% elseif logMessage.type == constant("mystic\\forum\\orm\\TopicLogMessage::TITLE_CHANGED") %} +            <div class="well icon-well text-info"> +                <span class="fa fa-pencil text-info" aria-hidden="true"></span> +                <em>{{ __("%user% changed the title of this topic from %old_title% to %new_title%", { +                    "user": user, +                    "old_title": '<strong>' ~ logMessage.params.old_value|default(__("unknown"))|e("html") ~ '</strong>', +                    "new_title": '<strong>' ~ logMessage.params.new_value|default(__("unknown"))|e("html") ~ '</strong>', +                }) }}</em> +                <br> +                <small class="_time">{{ logMessage.postDate.format("c") }}</small> +            </div> +        {% else %} +            {{ __("unknown") }} +        {% endif %} +    </div> +</div> diff --git a/src/application/templates/bootstrap-3/delete_post.twig b/src/application/templates/bootstrap-3/delete_post.twig new file mode 100644 index 0000000..90eaf09 --- /dev/null +++ b/src/application/templates/bootstrap-3/delete_post.twig @@ -0,0 +1,36 @@ +{% set title = __("Delete post") %} + +{% extends "base.twig" %} + +{% block content %} + +<div class="panel panel-danger"> +    <div class="panel-heading"> +        <h3 class="panel-title">{{ __("Do you want to delete this post?") }}</h3> +    </div> +    <div class="panel-body"> +        {{ __("Are you sure you want to delete the following post:") }}<br> +        {% include "components/post.twig" with { +            post: ctx.post, +            postAuthor: ctx.postAuthor, +            attachments: ctx.attachments, +            hide_actions: true, +        } %} +    </div> +    <div class="panel-footer"> +        <div class="text-right"> +            <form action=".#post-{{ ctx.post.id }}" method="get" class="seamless-inline"> +                <input type="hidden" name="_action" value="viewtopic"> +                <input type="hidden" name="topic" value="{{ ctx.post.topicId }}"> +                <button class="btn btn-default">{{ __("Keep post") }}</button> +            </form> +            <form action="?_action=deletepost" method="post" class="seamless-inline"> +                <input type="hidden" name="post" value="{{ ctx.post.id }}"> +                <input type="hidden" name="confirm" value="{{ ("confirm" ~ ctx.post.id)|hash("sha256", true)|base64_encode }}"> +                <button class="btn btn-danger">{{ __("Delete post") }}</button> +            </form> +        </div> +    </div> +</div> + +{% endblock %} diff --git a/src/application/templates/bootstrap-3/delete_topic.twig b/src/application/templates/bootstrap-3/delete_topic.twig new file mode 100644 index 0000000..3107c89 --- /dev/null +++ b/src/application/templates/bootstrap-3/delete_topic.twig @@ -0,0 +1,32 @@ +{% set title = __("Delete topic") %} + +{% extends "base.twig" %} + +{% block content %} + +<div class="panel panel-danger"> +    <div class="panel-heading"> +        <h3 class="panel-title">{{ __("Do you want to delete this topic?") }}</h3> +    </div> +    <div class="panel-body"> +        {{ __("Are you sure you want to delete the topic <em>%topic%</em> including <strong>all posts and attachments</strong>?", { +            "topic": ctx.topic.title|e("html"), +        }) }} +    </div> +    <div class="panel-footer"> +        <div class="text-right"> +            <form action="." method="get" class="seamless-inline"> +                <input type="hidden" name="_action" value="viewtopic"> +                <input type="hidden" name="topic" value="{{ ctx.topic.id }}"> +                <button class="btn btn-default">{{ __("Keep topic") }}</button> +            </form> +            <form action="?_action=deletetopic" method="post" class="seamless-inline"> +                <input type="hidden" name="topic" value="{{ ctx.topic.id }}"> +                <input type="hidden" name="confirm" value="{{ ("confirm" ~ ctx.topic.id)|hash("sha256", true)|base64_encode }}"> +                <button class="btn btn-danger">{{ __("Delete topic & posts") }}</button> +            </form> +        </div> +    </div> +</div> + +{% endblock %} diff --git a/src/application/templates/bootstrap-3/error_page.twig b/src/application/templates/bootstrap-3/error_page.twig new file mode 100644 index 0000000..dde4057 --- /dev/null +++ b/src/application/templates/bootstrap-3/error_page.twig @@ -0,0 +1,12 @@ +{% set title = __("Error") %} +{% extends "base.twig" %} + +{% block navbar %} +    {% if not ctx.skipLoginCheck %} +        {{ parent() }} +    {% endif %} +{% endblock %} + +{% block content %} +    {% include "components/alert_error.twig" with { message: ctx.message } %} +{% endblock %} diff --git a/src/application/templates/bootstrap-3/info_page.twig b/src/application/templates/bootstrap-3/info_page.twig new file mode 100644 index 0000000..e243b36 --- /dev/null +++ b/src/application/templates/bootstrap-3/info_page.twig @@ -0,0 +1,12 @@ +{% set title = __("Information") %} +{% extends "base.twig" %} + +{% block navbar %} +    {% if not ctx.skipLoginCheck %} +        {{ parent() }} +    {% endif %} +{% endblock %} + +{% block content %} +    {% include "components/alert_info.twig" with { message: ctx.message } %} +{% endblock %} diff --git a/src/application/templates/bootstrap-3/login.twig b/src/application/templates/bootstrap-3/login.twig new file mode 100644 index 0000000..63b9867 --- /dev/null +++ b/src/application/templates/bootstrap-3/login.twig @@ -0,0 +1,50 @@ +{% set title = __("Log in") %} +{% set formId = "login" %} +{% set formError = getAndClearFormError(formId) %} + +{% extends "base.twig" %} + +{% block content %} + +<div class="page-header margin-top-0"> +    <h1>{{ __("Log in") }}</h1> +</div> + +<div class="col-md-4"></div> + +<div class="well col-md-4"> +    {% if formError %} +        {% include "components/alert_error.twig" with { message: formError } %} +    {% endif %} +    <form action="{{ g.server.REQUEST_URI }}" method="post"> +        <input type="hidden" name="form_id" value="{{ formId }}"> + +        <div class="form-group"> +            <label for="i_username">{{ __("Username:") }}</label> +            <input class="form-control" type="text" id="i_username" name="username" value="{{ lastFormField(formId, "username") }}" required autofocus> +        </div> + +        <div class="form-group"> +            <label for="i_password">{{ __("Password:") }}</label> +            <input class="form-control" type="password" id="i_password" name="password" required> +        </div> + +        <div class="form-group"> +            <button class="btn btn-primary" type="submit">{{ __("Log in") }}</button> +            <a class="btn btn-link" href="?_action=pwreset">{{ __("I forgot my password") }}</a> +        </div> + +        {% if constant("REGISTRATION_ENABLED") %} +            <div class="form-group"> +                {{ __("Don't have an account? %link%Register now%/link%", { +                    "link": '<a href="?_action=register">', +                    "/link": '</a>', +                }) }} +            </div> +        {% endif %} +    </form> +</div> + +<div class="col-md-4"></div> + +{% endblock %} diff --git a/src/application/templates/bootstrap-3/new_password.twig b/src/application/templates/bootstrap-3/new_password.twig new file mode 100644 index 0000000..55a4b79 --- /dev/null +++ b/src/application/templates/bootstrap-3/new_password.twig @@ -0,0 +1,42 @@ +{% set title = __("Reset password") %} +{% set formId = "pwnew" %} +{% set formError = getAndClearFormError(formId) %} + +{% extends "base.twig" %} + +{% block content %} + +<div class="page-header margin-top-0"> +    <h1>{{ __("Reset password") }}</h1> +</div> + +<div class="col-md-4"></div> + +<div class="well col-md-4"> +    {% if formError %} +        {% include "components/alert_error.twig" with { message: formError } %} +    {% endif %} +    <form action="{{ g.server.REQUEST_URI }}" method="post"> +        <input type="hidden" name="form_id" value="{{ formId }}"> +        <input type="hidden" name="token" value="{{ g.get.token }}"> +        <input type="hidden" name="sig" value="{{ g.get.sig }}"> +         +        <div class="form-group"> +            <label for="i_new_password">{{ __("New password:") }}</label> +            <input class="form-control" type="password" id="i_new_password" name="new_password" required autofocus> +        </div> + +        <div class="form-group"> +            <label for="i_retype_password">{{ __("Retype password:") }}</label> +            <input class="form-control" type="password" id="i_retype_password" name="retype_password" required> +        </div> + +        <div class="form-group"> +            <button class="btn btn-primary" type="submit">{{ __("Set new password") }}</button> +        </div> +    </form> +</div> + +<div class="col-md-4"></div> + +{% endblock %} diff --git a/src/application/templates/bootstrap-3/new_topic.twig b/src/application/templates/bootstrap-3/new_topic.twig new file mode 100644 index 0000000..74be156 --- /dev/null +++ b/src/application/templates/bootstrap-3/new_topic.twig @@ -0,0 +1,58 @@ +{% set title = __("New topic") %} +{% set formId = "newtopic" %} +{% set formError = getAndClearFormError(formId) %} + +{% extends "base.twig" %} + +{% block content %} + +<div class="page-header margin-top-0"> +    <h1>{{ __("New topic") }}</h1> +</div> + +{% if formError %} +    {% include "components/alert_error.twig" with { message: formError } %} +{% endif %} +<form action="{{ g.server.REQUEST_URI }}" method="post" enctype="multipart/form-data"> +    <input type="hidden" name="form_id" value="{{ formId }}"> +    <div class="form-group"> +        <label for="i_message">{{ __("Topic title:") }}</label> +        <input type="text" class="form-control" id="i_title" name="title" value="{{ lastFormField(formId, "title") }}" required autofocus> +    </div> +    <div class="form-group"> +        <label for="i_message">{{ __("Message:") }}</label> +        <div class="panel panel-default"> +            <div class="panel-heading" style="padding:4px"> +                <div class="btn-toolbar" role="toolbar"> +                    <div class="btn-group" role="group"> +                        <button data-area="#i_message" title="{{ __("Bold") }}" data-editor-command="bold" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-bold"></i></button> +                        <button data-area="#i_message" title="{{ __("Italic") }}" data-editor-command="italic" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-italic"></i></button> +                        <button data-area="#i_message" title="{{ __("Underlined") }}" data-editor-command="underline" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-underline"></i></button> +                        <button data-area="#i_message" title="{{ __("Strikethrough") }}" data-editor-command="strikethrough" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-strikethrough"></i></button> +                    </div> +                    <div class="btn-group" role="group"> +                        <button data-area="#i_message" title="{{ __("Superscript") }}" data-editor-command="sup" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-superscript"></i></button> +                        <button data-area="#i_message" title="{{ __("Subscript") }}" data-editor-command="sub" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-subscript"></i></button> +                    </div> +                    <div class="btn-group" role="group"> +                        <button data-area="#i_message" title="{{ __("Quote") }}" data-editor-command="quote" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-quote-left"></i></button> +                        <button data-area="#i_message" title="{{ __("Spoiler") }}" data-editor-command="spoiler" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-eye"></i></button> +                    </div> +                </div> +            </div> +            <div class="panel-body" style="padding:0"> +                <textarea class="form-control" id="i_message" name="message" required rows="12" cols="60" style="resize:vertical;max-height:499px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;margin:-1px;width:calc(100% + 2px)">{{ lastFormField(formId, "message") }}</textarea> +            </div> +        </div> +    </div> +    <div class="form-group"> +        <label for="i_files">{{ __("Attachments: <small>(max. %max_attachment_count% files, max. %max_attachment_size% MiB each)</small>", { +            "max_attachment_count": constant("MAX_ATTACHMENT_COUNT"), +            "max_attachment_size": constant("MAX_ATTACHMENT_SIZE") // (2**20), +        }) }}</label> +        <input type="file" name="files[]" id="i_files" multiple accept="*/*"> +    </div> +    <button type="submit" class="btn btn-success">{{ __("Create topic") }} <span class="fa fa-send" aria-hidden="true"></span></button> +</form> + +{% endblock %} diff --git a/src/application/templates/bootstrap-3/password_reset.twig b/src/application/templates/bootstrap-3/password_reset.twig new file mode 100644 index 0000000..f633106 --- /dev/null +++ b/src/application/templates/bootstrap-3/password_reset.twig @@ -0,0 +1,42 @@ +{% set title = __("Reset password") %} +{% set formId = "pwreset" %} +{% set formError = getAndClearFormError(formId) %} + +{% extends "base.twig" %} + +{% block content %} + +<div class="page-header margin-top-0"> +    <h1>{{ __("Reset password") }}</h1> +</div> + +<div class="col-md-4"></div> + +<div class="well col-md-4"> +    {% if formError %} +        {% include "components/alert_error.twig" with { message: formError } %} +    {% endif %} +    <form action="{{ g.server.REQUEST_URI }}" method="post"> +        <input type="hidden" name="form_id" value="{{ formId }}"> + +        <div class="form-group"> +            <label for="i_username">{{ __("Email address:") }}</label> +            <input class="form-control" type="email" id="i_email" name="email" value="{{ lastFormField(formId, "email") }}" required autofocus> +        </div> + +        <div class="form-group"> +            <button class="btn btn-primary" type="submit">{{ __("Reset password") }}</button> +        </div> + +        <div class="form-group"> +            {{ __("I know my password and I want to %link%log in%/link%!", { +                "link": '<a href="?_action=auth">', +                "/link": '</a>', +            }) }} +        </div> +    </form> +</div> + +<div class="col-md-4"></div> + +{% endblock %} diff --git a/src/application/templates/bootstrap-3/register.twig b/src/application/templates/bootstrap-3/register.twig new file mode 100644 index 0000000..1e4cd92 --- /dev/null +++ b/src/application/templates/bootstrap-3/register.twig @@ -0,0 +1,89 @@ +{% set title = __("Register") %} +{% set formId = "register" %} +{% set formError = getAndClearFormError(formId) %} + +{% extends "base.twig" %} + +{% block content %} + +<div class="page-header margin-top-0"> +    <h1>{{ __("Register") }}</h1> +</div> + +<div class="col-md-4"></div> + +<div class="well col-md-4"> +    {% if formError %} +        {% include "components/alert_error.twig" with { message: formError } %} +    {% endif %} +    <form action="{{ g.server.REQUEST_URI }}" method="post"> +        <input type="hidden" name="form_id" value="{{ formId }}"> + +        <div class="form-group" id="group0"> +            <label for="i_username">{{ __("Username:") }}</label> +            <input class="form-control" id="i_username" type="text" name="username" value="" required> +        </div> + +        <div class="form-group" id="group1"> +            <label for="i_username">{{ __("Username:") }}</label> +            <input class="form-control" id="i_username" type="text" name="df82a9bc21" value="{{ lastFormField(formId, "df82a9bc21") }}" required autofocus> +        </div> + +        <div class="form-group" id="group2"> +            <label for="i_display_name">{{ __("Display name:") }}</label> +            <input class="form-control" id="i_display_name" type="text" name="display_name" value="{{ lastFormField(formId, "display_name") }}" required> +        </div> + +        <div class="form-group" id="group3"> +            <label for="i_password">{{ __("Choose password:") }}</label> +            <input class="form-control" id="i_password" type="password" name="password" required> +        </div> + +        <div class="form-group" id="group4"> +            <label for="i_password_retype">{{ __("Repeat password:") }}</label> +            <input class="form-control" id="i_password_retype" type="password" name="password_retype" required> +        </div> + +        <div class="form-group" id="group5"> +            <label for="i_email">{{ __("Email address:") }}</label> +            <input class="form-control" id="i_email" type="email" name="email" value="{{ lastFormField(formId, "email") }}" required> +        </div> + +        <div class="form-group" id="group6"> +            <label for="i_email">{{ __("CAPTCHA:") }}</label> +            <div class="text-center margin-bottom"> +                <img src="?_action=captcha&t={{ "now"|date("Uv") }}" alt="CAPTCHA" width="192" height="48" id="captcha-img"> +            </div> +            <div class="input-group"> +                <input type="text" name="captcha" id="i_captcha" class="form-control" required> +                <div class="input-group-btn"> +                    <button class="btn btn-default" type="button" id="btn-refresh-captcha"><span class="fa fa-refresh" aria-hidden="true"></span><span class="sr-only">{{ __("New CAPTCHA") }}</span></button> +                </div> +            </div> +        </div> + +        <div class="form-group"> +            <button class="btn btn-primary" type="submit">{{ __("Register now") }}</button> +        </div> + +        <div class="form-group"> +            {{ __("Already have an account? %link%Sign in now%/link%", { +                "link": '<a href="?_action=auth">', +                "/link": '</a>', +            }) }} +        </div> +    </form> +</div> + +<div class="col-md-4"></div> + +<script> +$(function() { +    $("#btn-refresh-captcha").click(function() { +        $("#captcha-img").attr("src", "?_action=captcha&t=" + new Date().getTime().toString()); +    }); +    $("#i_username").prop("disabled", true).prop("required", false); +}); +</script> + +{% endblock %} diff --git a/src/application/templates/bootstrap-3/search.twig b/src/application/templates/bootstrap-3/search.twig new file mode 100644 index 0000000..c8f6315 --- /dev/null +++ b/src/application/templates/bootstrap-3/search.twig @@ -0,0 +1,63 @@ +{% set title = __("Search") %} +{% set formId = "search" %} +{% set formError = getAndClearFormError(formId) %} + +{% extends "base.twig" %} + +{% block content %} + +<div class="page-header margin-top-0"> +    <h1>{{ __("Search") }}</h1> +</div> + +{% if formError %} +    {% include "components/alert_error.twig" with { message: formError } %} +{% endif %} + +<form action="." method="get"> +    <input type="hidden" name="form_id" value="{{ formId }}"> +    <input type="hidden" name="_action" value="search"> +    <div class="form-group"> +        <div class="input-group"> +            <input class="form-control" type="search" id="i_query" name="query" value="{{ lastFormField(formId, "query")|default(g.get.query)|default("") }}" required autofocus placeholder="{{ __("Enter your search query...") }}"> +            <div class="input-group-btn"> +                <button class="btn btn-primary" type="submit">{{ __("Search") }}</button> +            </div> +        </div> +    </div> +</form> + +{% if g.get.query is defined and g.get.query is not null and g.get.query != "" %} +    {% if ctx.posts|length > 0 %} +        <p>{{ __("%result_count% result(s) in %search_duration% second(s)", { +            "result_count": ctx.posts|length, +            "search_duration": ctx.search_duration|number_format(2, __(".", context: "Number formatting"), __(",", context: "Number formatting")), +        }) }}</p> +        <div class="list-group margin-top"> +            {% for post in ctx.posts|filter(p => not p.deleted) %} +                {% set hasAttachments = ctx.attachments[post.id]|length > 0 %} +                {% set postAuthor = ctx.users[post.authorId] %} +                <a href="?_action=viewtopic&topic={{ post.topicId|url_encode }}#post-{{ post.id|url_encode }}" class="list-group-item"> +                    {% if hasAttachments %} +                        <span class="badge"><span class="fa fa-paperclip"></span></span> +                    {% endif %} +                    {{ renderPostSummary(post.content) }}<br> +                    <span class="text-muted">{{ __("posted by %author% on %post_date% in %topic%", { +                        "author": '<em>' ~ (postAuthor ? postAuthor.displayName : __("unknown"))|e("html") ~ '</em>', +                        "post_date": '<span class="_time">' ~ post.postDate.format("c")|e("html") ~ '</span>', +                        "topic": '<em>' +                            ~ (topics[post.topicId].isLocked ? '<span class="fa fa-lock text-muted" aria-hidden="true"></span> ' : '') +                            ~ (topics[post.topicId] ? topics[post.topicId].title : null)|default("unknown")|e("html") ~ '</em>', +                    }) }}</span> +                </a> +            {% endfor %} +        </div> +    {% else %} +        <div class="well icon-well text-info margin-top margin-bottom"> +            <span class="fa fa-info-circle text-info" aria-hidden="true"></span> +            <em>{{ __("No results for this search") }}</em> +        </div> +    {% endif %} +{% endif %} + +{% endblock %} diff --git a/src/application/templates/bootstrap-3/view_topic.twig b/src/application/templates/bootstrap-3/view_topic.twig new file mode 100644 index 0000000..008c336 --- /dev/null +++ b/src/application/templates/bootstrap-3/view_topic.twig @@ -0,0 +1,353 @@ +{% set canReply = +    not ctx.topic.isLocked +    and currentUser is not null +    and currentUser.hasPermission(permission("CREATE_OWN_POST")) %} + +{% set canEdit = +    currentUser is not null and ( +        ( +            ctx.topicAuthor is not null +            and currentUser.id == ctx.topicAuthor.id +            and ctx.topicAuthor.hasPermission(permission("EDIT_OWN_TOPIC")) +        ) +        or currentUser.hasPermission(permission("EDIT_OTHER_TOPIC")) +    ) %} + +{% set canDelete = +    currentUser is not null and ( +        ( +            ctx.topicAuthor is not null +            and currentUser.id == ctx.topicAuthor.id +            and ctx.topicAuthor.hasPermission(permission("DELETE_OWN_TOPIC")) +        ) +        or currentUser.hasPermission(permission("DELETE_OTHER_TOPIC")) +    ) %} + +{% set title = ctx.topic.title %} +{% extends "base.twig" %} + +{% block content %} + +{% if canEdit %} +    <div class="modal fade" tabindex="-1" role="dialog" id="diag-edit-post"> +        <form class="modal-dialog" role="document" action="?_action=updatepost" method="post"> +            <input type="hidden" id="i_edit_post" name="post"> +            <div class="modal-content"> +                <div class="modal-header"> +                    <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> +                    <h4 class="modal-title">{{ __("Edit post") }}</h4> +                </div> +                <div class="modal-body"> +                    <label class="sr-only" for="i_edit_message">{{ __("Message:") }}</label> +                    <div class="panel panel-default"> +                        <div class="panel-heading" style="padding:4px"> +                            <div class="btn-toolbar" role="toolbar"> +                                <div class="btn-group" role="group"> +                                    <button data-area="#i_edit_message" title="{{ __("Bold") }}" data-editor-command="bold" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-bold"></i></button> +                                    <button data-area="#i_edit_message" title="{{ __("Italic") }}" data-editor-command="italic" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-italic"></i></button> +                                    <button data-area="#i_edit_message" title="{{ __("Underlined") }}" data-editor-command="underline" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-underline"></i></button> +                                    <button data-area="#i_edit_message" title="{{ __("Strikethrough") }}" data-editor-command="strikethrough" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-strikethrough"></i></button> +                                </div> +                                <div class="btn-group" role="group"> +                                    <button data-area="#i_edit_message" title="{{ __("Superscript") }}" data-editor-command="sup" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-superscript"></i></button> +                                    <button data-area="#i_edit_message" title="{{ __("Subscript") }}" data-editor-command="sub" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-subscript"></i></button> +                                </div> +                                <div class="btn-group" role="group"> +                                    <button data-area="#i_edit_message" title="{{ __("Quote") }}" data-editor-command="quote" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-quote-left"></i></button> +                                    <button data-area="#i_edit_message" title="{{ __("Spoiler") }}" data-editor-command="spoiler" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-eye"></i></button> +                                </div> +                            </div> +                        </div> +                        <div class="panel-body" style="padding:0"> +                            <textarea class="form-control" id="i_edit_message" name="message" required rows="12" cols="60" style="resize:vertical;max-height:500px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;margin:-1px;width:calc(100% + 2px)"></textarea> +                        </div> +                    </div> +                </div> +                <div class="modal-footer"> +                    <button type="button" class="btn btn-default" data-dismiss="modal"><span class="fa fa-remove" aria-hidden="true"></span> {{ __("Cancel") }}</button> +                    <button type="submit" class="btn btn-success"><span class="fa fa-save" aria-hidden="true"></span> {{ __("Save changes") }}</button> +                </div> +            </div> +        </form> +    </div> +{% endif %} +{% if currentUser is null %} +    <div class="modal fade" tabindex="-1" role="dialog" id="diag-cant-view-attachment"> +        <div class="modal-dialog modal-danger" role="document"> +            <div class="modal-content panel-danger"> +                <div class="modal-header panel-heading"> +                    <h4 class="modal-title"><span class="fa fa-exclamation-circle" aria-hidden="true"></span> {{ __("Permission denied") }}</h4> +                </div> +                <div class="modal-body"> +                    {{ __("You must be logged in to view attachments") }} +                </div> +                <div class="modal-footer"> +                    <button type="button" class="btn btn-default" data-dismiss="modal"><span class="fa fa-close" aria-hidden="true"></span> {{ __("Close") }}</button> +                    <a href="?_action=auth&next={{ g.server.REQUEST_URI|url_encode }}" class="btn btn-success"><span class="fa fa-user" aria-hidden="true"></span> {{ __("Log in") }}</a> +                </div> +            </div> +        </div> +    </div> +    <script> +        $(function() { +            $(".attachment").click(function(e) { +                e.preventDefault(); +                $("#diag-cant-view-attachment").modal(); +            }); +        }); +    </script> +{% else %} +    <div class="modal fade" tabindex="-1" role="dialog" id="diag-image-attachment"> +        <div class="modal-dialog" role="document"> +            <div class="modal-content"> +                <div class="modal-header"> +                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> +                    <h4 class="modal-title">{{ __("Attachment") }}</h4> +                </div> +                <div class="modal-body"> +                    <img class="image-attachment-view attachment-view" id="image-attachment-view" alt=""> +                </div> +                <div class="modal-footer"> +                    <a href="" download id="image-attachment-dl-btn" class="btn btn-default"><span class="fa fa-download" aria-hidden="true"></span> {{ __("Download") }}</a> +                </div> +            </div> +        </div> +    </div> +    <div class="modal fade" tabindex="-1" role="dialog" id="diag-video-attachment"> +        <div class="modal-dialog" role="document"> +            <div class="modal-content"> +                <div class="modal-header"> +                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> +                    <h4 class="modal-title">{{ __("Attachment") }}</h4> +                </div> +                <div class="modal-body"> +                    <video class="video-attachment-view attachment-view" id="video-attachment-view" controls></video> +                </div> +                <div class="modal-footer"> +                    <a href="" download id="video-attachment-dl-btn" class="btn btn-default"><span class="fa fa-download" aria-hidden="true"></span> {{ __("Download") }}</a> +                </div> +            </div> +        </div> +    </div> +    <script> +        $(function() { +            $(".image-attachment:not(.video-attachment)").click(function(e) { +                e.preventDefault(); +                var attUrl = "?_action=attachment&attachment=" + encodeURIComponent($(this).attr("data-attachment-id")); +                $("#image-attachment-view").attr("src", attUrl); +                $("#image-attachment-dl-btn").attr("href", attUrl); +                $("#diag-image-attachment").modal(); +            }); +            $(".image-attachment.video-attachment").click(function(e) { +                e.preventDefault(); +                var attUrl = "?_action=attachment&attachment=" + encodeURIComponent($(this).attr("data-attachment-id")); +                $("#video-attachment-view").attr("src", attUrl); +                $("#video-attachment-dl-btn").attr("href", attUrl); +                $("#diag-video-attachment").modal(); +            }); +            $("#diag-video-attachment").on("hide.bs.modal", function() { +                $("#video-attachment-view")[0].pause(); +            }); +        }); +    </script> +{% endif %} + +{% set formError = getAndClearFormError("updateTopic") %} +{% if formError %} +    {% include "components/alert_error.twig" with { message: formError } %} +{% endif %} +{% set formError = getAndClearFormError("lockTopic") %} +{% if formError %} +    {% include "components/alert_error.twig" with { message: formError } %} +{% endif %} +{% set formError = null %} + +<div class="page-header margin-top-0 clearfix"> +    <div id="displayHeading"> +        <div role="heading" class="h1 seamless-inline"> +            {% if ctx.topic.isLocked %} +                <span class="fa fa-lock text-muted" aria-hidden="true"></span> +            {% endif %} +            {{ ctx.topic.title }} +            <div class="pull-right text-normal"> +                {% if canEdit and not ctx.topic.isLocked %} +                    <button id="btn-edit-title" class="btn btn-default js-only"><span class="fa fa-pencil" aria-hidden="true"></span> {{ __("Edit title") }}</button> +                {% endif %} +                {% if canReply %} +                    <button id="btn-reply" class="btn btn-default js-only"><span class="fa fa-comment" aria-hidden="true"></span> {{ __("Reply") }}</button> +                {% endif %} +                {% if canEdit %} +                    {% if ctx.topic.isLocked %} +                        <form action="?_action=locktopic" method="post" class="seamless-inline"> +                            <input type="hidden" name="topic" value="{{ ctx.topic.id }}"> +                            <input type="hidden" name="locked" value="false"> +                            <button type="submit" class="btn btn-success"><span class="fa fa-unlock" aria-hidden="true"></span> {{ __("Unlock topic") }}</button> +                        </form> +                    {% else %} +                        <form action="?_action=locktopic" method="post" class="seamless-inline"> +                            <input type="hidden" name="topic" value="{{ ctx.topic.id }}"> +                            <input type="hidden" name="locked" value="true"> +                            <button type="submit" class="btn btn-warning"><span class="fa fa-lock" aria-hidden="true"></span> {{ __("Lock topic") }}</button> +                        </form> +                    {% endif %} +                {% endif %} +                {% if canDelete %} +                    <form action="?_action=deletetopic" method="post" class="seamless-inline"> +                        <input type="hidden" name="topic" value="{{ ctx.topic.id }}"> +                        <button type="submit" class="btn btn-danger"><span class="fa fa-trash" aria-hidden="true"></span> {{ __("Delete topic") }}</button> +                    </form> +                {% endif %} +            </div> +        </div><br> +        {{ __("Started by %user% on %date%", { +            "user": (ctx.topicAuthor is not null) ? ('<a href="?_action=viewuser&user=' ~ ctx.topicAuthor.id|url_encode|e("html") ~ '">' ~ ctx.topicAuthor.displayName|e("html") ~ '</a>') : __("(deleted)"), +            "date": '<span class="_time">' ~ ctx.topic.creationDate.format("c")|e("html") ~ '</span>', +        }) }} +    </div> +    {% if canEdit %} +        <form action="?_action=updatetopic" method="post" id="editHeading" style="display: none;" class="form-inline seamless-inline"> +            <input type="hidden" name="topic" value="{{ ctx.topic.id }}"> +            <div class="row"> +                <div class="col-md-8"> +                    <input type="text" class="form-control" name="title" id="i_edit_title" data-original-value="{{ ctx.topic.title }}" value="{{ ctx.topic.title }}" style="box-sizing: border-box; width: 100%; font-size: 36px; height: 56px"> +                </div> +                <div class="col-md-4 text-right"> +                    <button type="button" id="topicTitleEditCancel" class="btn btn-default"><span class="fa fa-close" aria-hidden="true"></span> {{ __("Cancel") }}</button> +                    <button type="submit" class="btn btn-success"><span class="fa fa-save" aria-hidden="true"></span> {{ __("Save changes") }}</button> +                </div> +            </div> +        </form> +    {% endif %} +</div> +<script> +{% if canEdit %} +$(function() { +    $("#btn-edit-title").click(function() { +        $("#displayHeading").hide(); +        $("#editHeading").show(); +        $("#i_edit_title").val($("#i_edit_title").attr("data-original-value")).focus(); +    }); +    $("#topicTitleEditCancel").click(function() { +        $("#displayHeading").show(); +        $("#editHeading").hide(); +    }); +    $("._edit-post").click(function() { +        var $post = $("#post-" + $(this).attr("data-post-id")); +        var $postContent = $post.find(".post-content"); +        $("#i_edit_message").css("height", "").val($post.attr("data-text")); +        $("#i_edit_post").val($(this).attr("data-post-id")); +        $("#diag-edit-post").modal(); +    }); +    $("#diag-edit-post").on("shown.bs.modal", function() { +        $("#i_edit_message").focus(); +    }); +}); +{% endif %} +{% if canReply %} +$(function() { +    function focusReplyBox() { +        var msgInput = $("#i_message"); +        msgInput[0].scrollIntoView(); +        msgInput.focus(); +    } +    $("#btn-reply").click(function() { +        focusReplyBox(); +    }); +    $("._reply-post").click(function() { +        var text = $("#post-" + $(this).attr("data-post-id")).attr("data-text"); +        var val = $("#i_message").val(); +        var lines = text.split("\n"); +        for (var i = 0; i < lines.length; ++i) +            val += "\n> " + lines[i]; +        val += "\n\n"; +        $("#i_message").val(val.replace(/^\n+/, "")); +        focusReplyBox(); +    }); +}); +{% endif %} +</script> + +{% for item in ctx.allItems %} +    {% if item.type == "post" %} +        {% include "components/post.twig" with { +            post: item.post, +            postAuthor: item.postAuthor, +            topic: item.topic, +            attachments: item.attachments, +            hide_actions: false, +            hide_pfp: false, +        } %} +    {% elseif item.type == "logMessage" %} +        {% include "components/topic_log.twig" with { +            type: item.type, +            logMessage: item.logMessage, +            postAuthor: item.postAuthor, +            topicAuthor: item.topicAuthor, +            topic: item.topic, +            hide_actions: false, +            hide_pfp: false, +        } %} +    {% endif %} +{% endfor %} + +{% if ctx.topic.isLocked %} +    <div class="well icon-well text-warning margin-top-4x"> +        <span class="fa fa-lock text-warning" aria-hidden="true"></span> +        <em>{{ __("This topic has been locked") }}</em> +    </div> +{% elseif currentUser is not null %} +    {% set formId = "addpost" %} +    <h3 id="form">{{ __("Reply to this topic") }}</h3> +    {% set formError = getAndClearFormError(formId) %} +    {% if formError %} +        {% include "components/alert_error.twig" with { message: formError } %} +    {% endif %} +    <form action="{{ g.server.REQUEST_URI }}#form" method="post" enctype="multipart/form-data"> +        <input type="hidden" name="form_id" value="{{ formId }}"> +        <div class="form-group"> +            <label for="i_message">{{ __("Message:") }}</label> +            <div class="panel panel-default"> +                <div class="panel-heading" style="padding:4px"> +                    <div class="btn-toolbar" role="toolbar"> +                        <div class="btn-group" role="group"> +                            <button data-area="#i_message" title="{{ __("Bold") }}" data-editor-command="bold" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-bold"></i></button> +                            <button data-area="#i_message" title="{{ __("Italic") }}" data-editor-command="italic" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-italic"></i></button> +                            <button data-area="#i_message" title="{{ __("Underlined") }}" data-editor-command="underline" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-underline"></i></button> +                            <button data-area="#i_message" title="{{ __("Strikethrough") }}" data-editor-command="strikethrough" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-strikethrough"></i></button> +                        </div> +                        <div class="btn-group" role="group"> +                            <button data-area="#i_message" title="{{ __("Superscript") }}" data-editor-command="sup" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-superscript"></i></button> +                            <button data-area="#i_message" title="{{ __("Subscript") }}" data-editor-command="sub" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-subscript"></i></button> +                        </div> +                        <div class="btn-group" role="group"> +                            <button data-area="#i_message" title="{{ __("Quote") }}" data-editor-command="quote" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-quote-left"></i></button> +                            <button data-area="#i_message" title="{{ __("Spoiler") }}" data-editor-command="spoiler" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-eye"></i></button> +                        </div> +                    </div> +                </div> +                <div class="panel-body" style="padding:0"> +                    <textarea class="form-control" id="i_message" name="message" required rows="12" cols="60" style="resize:vertical;max-height:499px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;margin:-1px;width:calc(100% + 2px)"></textarea> +                </div> +            </div> +        </div> +        <div class="form-group"> +            <label for="i_files">{{ __("Attachments: <small>(max. %max_attachment_count% files, max. %max_attachment_size% MiB each)</small>", { +                "max_attachment_count": constant("MAX_ATTACHMENT_COUNT"), +                "max_attachment_size": constant("MAX_ATTACHMENT_SIZE") // (2**20), +            }) }}</label> +            <input type="file" name="files[]" id="i_files" multiple accept="*/*"> +        </div> +        <button type="submit" class="btn btn-success">{{ __("Post reply") }} <span class="fa fa-send" aria-hidden="true"></span></button> +    </form> +{% else %} +    <div class="well margin-top-4x text-center"> +        <div class="h3 margin-top-0">{{ __("Log in to reply to this topic") }}</div> +        <a href="?_action=auth&next={{ g.server.REQUEST_URI|url_encode }}" class="btn btn-success"> +            <span class="fa fa-user" aria-hidden="true"></span> +            {{ __("Log in") }} +        </a> +    </div> +{% endif %} + + +{% endblock %} diff --git a/src/application/templates/bootstrap-3/view_topics.twig b/src/application/templates/bootstrap-3/view_topics.twig new file mode 100644 index 0000000..899ebe7 --- /dev/null +++ b/src/application/templates/bootstrap-3/view_topics.twig @@ -0,0 +1,25 @@ +{% extends "base.twig" %} + +{% block content %} + +{% if currentUser.hasPermission(permission("CREATE_OWN_TOPIC")) %} +    <p class="text-right"> +        <a href="?_action=newtopic" class="btn btn-success"><span class="fa fa-plus" aria-hidden="true"></span> {{ __("New topic") }}</a> +    </p> +{% endif %} + +<div class="list-group"> +    {% for topic in ctx.topics %} +        <a class="list-group-item" href="?_action=viewtopic&topic={{ topic.id|url_encode }}"> +            <h4 class="list-group-item-heading"> +                {% if topic.isLocked %} +                    <span class="fa fa-lock text-muted" aria-hidden="true"></span> +                {% endif %} +                {{ topic.title }} +            </h4> +            <p class="list-group-item-text _time">{{ topic.creationDate.format("c") }}</p> +        </a> +    {% endfor %} +</div> + +{% endblock %} diff --git a/src/application/templates/bootstrap-3/view_user.twig b/src/application/templates/bootstrap-3/view_user.twig new file mode 100644 index 0000000..b4c936c --- /dev/null +++ b/src/application/templates/bootstrap-3/view_user.twig @@ -0,0 +1,201 @@ +{% set canEdit = +    currentUser is not null +    and ( +        ( +            ctx.user.id == currentUser.id +            and currentUser.hasPermission(permission("EDIT_OWN_USER")) +        ) +        or currentUser.hasPermission(permission("EDIT_OTHER_USER")) +    ) %} + +{% set isOwnProfile = +    currentUser is not null +    and currentUser.id == ctx.user.id %} + +{% set sUserPossessive = isOwnProfile ? "Your Posts" : "%display_name%'s posts" %} + +{% set emailPending = isOwnProfile and ctx.user.pendingEmail is not null %} + +{% set title = ctx.user.displayName %} + +{% extends "base.twig" %} + +{% block content %} + +<div class="clearfix page-header margin-top-0"> +    <img class="pull-left margin-right" src="?_action=profilepicture&user={{ ctx.user.id|url_encode }}" alt="{{ __("Profile picture") }}" width="64" height="64"> +    <span class="h1">{{ ctx.user.displayName }}</span> +    {% if isOwnProfile %} +        <span class="label label-primary">{{ __("You") }}</span> +    {% endif %}<br> +    @{{ ctx.user.name }} • <span class="text-muted">{{ __("Member since %join_date%", { +        "join_date": '<span class="_date">' ~ (ctx.dateJoined.format("c")|e("html")) ~ '</span>', +    }) }}</span> +</div> + +{% if canEdit %} +<div class="row"> +<div class="col-md-9"> +{% endif %} + +<h3>{{ __(sUserPossessive, { +    "display_name": ctx.user.displayName|e("html"), +}) }}</h3> + +{% if ctx.posts|length > 0 %} + +    <div class="post-container"> +        <div class="post-container-posts"> +            <div class="list-group margin-top"> +                {% for post in ctx.posts %} +                    <a href="?_action=viewtopic&topic={{ post.topicId|url_encode }}#post-{{ post.id|url_encode }}" class="list-group-item"> +                        {% if hasAttachments %} +                            <span class="badge"><span class="fa fa-paperclip"></span></span> +                        {% endif %} +                        {{ renderPostSummary(post.content) }}<br> +                        <span class="text-muted">{{ __("posted on %post_date% in %topic%", { +                            "post_date": '<span class="_time">' ~ post.postDate.format("c")|e("html") ~ '</span>', +                            "topic": '<em>' ~ +                                (ctx.topics[post.topicId] is not null and ctx.topics[post.topicId].isLocked ? '<span class="fa fa-lock text-muted" aria-hidden="true"></span> ' : '') ~ +                                (ctx.topics[post.topicId] is not null ? ctx.topics[post.topicId].title : __("unknown"))|e("html") ~ '</em>', +                        }) }}</span> +                    </a> +                {% endfor %} +            </div> +        </div> +        <div class="post-container-controls"> +            <button class="btn btn-default">{{ __("Show all posts") }}</button> +        </div> +    </div> +{% else %} +    <div class="well icon-well text-info margin-top margin-bottom"> +        <span class="fa fa-info-circle text-info" aria-hidden="true"></span> +        <em>{{ __("This user has not posted anything yet") }}</em> +    </div> +{% endif %} + +{% if canEdit %} +</div> <!-- .col-md-9 --> + +<div class="col-md-3"> +    <h3>{{ __("Edit profile") }}</h3> +    {% set formId = "update_profile" %} +    {% set formError = getAndClearFormError(formId) %} +    {% if formError %} +        {% include "components/alert_error.twig" with { message: formError } %} +    {% endif %} +    <form action="{{ g.server.REQUEST_URI }}" method="post" enctype="multipart/form-data"> +        <input type="hidden" name="form_id" value="{{ formId }}"> +        <div class="form-group"> +            <label for="i_display_name">{{ __("Display name:") }}</label> +            <input required class="form-control" type="text" name="display_name" id="i_display_name" value="{{ ctx.user.displayName }}"> +        </div> +        <div class="form-group"> +            <label for="i_name">{{ __("Username:") }}</label> +            {% if ctx.lastNameChangeTooRecent %} +                <input class="form-control" type="text" id="i_name" value="{{ ctx.user.name }}" disabled> +                <small class="text-danger"><strong>{{ __("You can only change your username every 30 days!") }}</strong></small> +            {% else %} +                <input required class="form-control" type="text" name="name" id="i_name" value="{{ ctx.user.name }}"> +            {% endif %} +        </div> +        <div class="form-group"> +            <label for="i_email">{{ __("Email address:") }}</label> +            {% if emailPending %} +                <input class="form-control" type="email" id="i_email" value="{{ ctx.user.email }}" disabled> +            {% else %} +                <input required class="form-control" type="email" id="i_email" name="email" value="{{ ctx.user.email }}"> +            {% endif %} +        </div> +        <div class="form-group"> +            <label>{{ __("Profile picture:") }}</label> +            <div class="radio margin-top-0 {{ ctx.user.profilePicture is empty ? " disabled text-muted" }}"> +                <label> +                    <input type="radio" name="pfp_action" id="pfp_action_1" value="keep"{{ ctx.user.profilePicture is not empty ? ' checked' : ' disabled' }}> +                    {{ __("Keep current profile picture") }} +                </label> +            </div> +            <div class="radio"> +                <label> +                    <input type="radio" name="pfp_action" id="pfp_action_2" value="remove"{{ ctx.user.profilePicture is empty ? ' checked' : '' }}> +                    {% if ctx.user.profilePicture is empty %} +                        {{ __("No profile picture") }} +                    {% else %} +                        {{ __("Remove profile picture") }} +                    {% endif %} +                </label> +            </div> +            <div class="radio"> +                <label> +                    <input type="radio" name="pfp_action" value="replace" id="pfp_action_3"> +                    {{ __("Upload new profile picture") }} +                </label> +            </div> +            <input type="file" name="pfp" id="i_pfp" accept="image/png,image/jpeg" class="margin-left-3x small"> +        </div> +        <div class="form-group"> +            <button type="submit" class="btn btn-success">{{ __("Save changes") }}</button> +        </div> +    </form> +    {% if isOwnProfile %} +        <h3>{{ __("Change password") }}</h3> +        {% set formId = "update_password" %} +        {% set formError = getAndClearFormError(formId) %} +        {% if formError %} +            {% include "components/alert_error.twig" with { message: formError } %} +        {% endif %} +        <form action="{{ g.server.REQUEST_URI }}" method="post"> +            <input type="hidden" name="form_id" value="{{ formId }}"> +            <div class="form-group"> +                <label for="i_current_password">{{ __("Current password:") }}</label> +                <input autocomplete="current-password" required class="form-control" type="password" name="current_password" id="i_current_password" required> +            </div> +            <div class="form-group"> +                <label for="i_new_password">{{ __("New password:") }}</label> +                <input autocomplete="new-password" required class="form-control" type="password" name="new_password" id="i_new_password" required> +            </div> +            <div class="form-group"> +                <label for="i_retype_password">{{ __("Retype password:") }}</label> +                <input autocomplete="new-password" required class="form-control" type="password" name="retype_password" id="i_retype_password" required> +            </div> +            <div class="form-group"> +                <button type="submit" class="btn btn-success">{{ __("Change password") }}</button> +            </div> +        </form> +    {% endif %} +</div> <!-- .col-md-3 --> +</div> <!-- .row --> +{% endif %} + +<script> +$(function() { +    $(".post-container").each(function(i, e) { +        if ($(e).height() > 900) { // more than 800 so it doesn't collapse just a few pixels +            $(e).addClass("collapsed"); +        } +        $(e).find(".post-container-controls button").click(function() { +            $(e).removeClass("collapsed"); +        }); +    }); +}); +{% if canEdit %} +$(function() { +    function _hide() { +        $("#i_pfp").hide().prop("disabled", true).prop("required", false); +        $("#i_pfp + .file-input-group").hide().find("button").prop("disabled", true); +    } +    _hide(); +    setTimeout(_hide, 10); +    $("[name='pfp_action']").on("change input check click", function() { +        if ($("#pfp_action_3").is(":checked")) { +            $("#i_pfp").show().prop("disabled", false).prop("required", true); +            $("#i_pfp + .file-input-group").show().find("button").prop("disabled", false); +        } else { +            _hide(); +        } +    }) +}); +{% endif %} +</script> + +{% endblock %} diff --git a/src/application/views/alert_error.php b/src/application/views/alert_error.php deleted file mode 100644 index e3b48ba..0000000 --- a/src/application/views/alert_error.php +++ /dev/null @@ -1,10 +0,0 @@ -<?php -if (str_starts_with($message, "?!HTML::")) -    $message = substr($message, 8); -else -    $message = htmlentities($message); -?> -<div class="alert alert-danger" role="alert"> -<span class="fa fa-exclamation-circle" aria-hidden="true"></span> -<?= $message; ?> -</div> diff --git a/src/application/views/alert_info.php b/src/application/views/alert_info.php deleted file mode 100644 index 7bf2e7b..0000000 --- a/src/application/views/alert_info.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php -if (str_starts_with($message, "?!HTML::")) -    $message = substr($message, 8); -else -    $message = htmlentities($message); -?> -<div class="alert alert-info" role="alert"> -<?= $message; ?> -</div> diff --git a/src/application/views/alert_success.php b/src/application/views/alert_success.php deleted file mode 100644 index de3e023..0000000 --- a/src/application/views/alert_success.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php -if (str_starts_with($message, "?!HTML::")) -    $message = substr($message, 8); -else -    $message = htmlentities($message); -?> -<div class="alert alert-success" role="alert"> -<?= $message; ?> -</div> diff --git a/src/application/views/form_addpost.php b/src/application/views/form_addpost.php deleted file mode 100644 index b2fb2f2..0000000 --- a/src/application/views/form_addpost.php +++ /dev/null @@ -1,53 +0,0 @@ -<?php - -use mystic\forum\utils\RequestUtils; - -$lastFormUri = ""; -$lastForm = RequestUtils::getLastForm($lastFormUri) ?? []; -if ($lastFormUri !== $_SERVER["REQUEST_URI"]) $lastForm = []; -RequestUtils::clearLastForm(); - -?> -<h3 id="form"><?= __("Reply to this topic") ?></h3> -<?php -if (($_formError = RequestUtils::getAndClearFormError("addpost")) !== null) { -    _view("alert_error", ["message" => $_formError]); -} -?> -<form action="<?= htmlentities($_SERVER["REQUEST_URI"]) ?>#form" method="post" enctype="multipart/form-data"> -<input type="hidden" name="form_id" value="addpost"> -<div class="form-group"> -    <label for="i_message"><?= __("Message:") ?></label> -    <div class="panel panel-default"> -        <div class="panel-heading" style="padding:4px"> -            <div class="btn-toolbar" role="toolbar"> -                <div class="btn-group" role="group"> -                    <button data-area="#i_message" title="<?= __("Bold") ?>" data-editor-command="bold" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-bold"></i></button> -                    <button data-area="#i_message" title="<?= __("Italic") ?>" data-editor-command="italic" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-italic"></i></button> -                    <button data-area="#i_message" title="<?= __("Underlined") ?>" data-editor-command="underline" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-underline"></i></button> -                    <button data-area="#i_message" title="<?= __("Strikethrough") ?>" data-editor-command="strikethrough" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-strikethrough"></i></button> -                </div> -                <div class="btn-group" role="group"> -                    <button data-area="#i_message" title="<?= __("Superscript") ?>" data-editor-command="sup" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-superscript"></i></button> -                    <button data-area="#i_message" title="<?= __("Subscript") ?>" data-editor-command="sub" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-subscript"></i></button> -                </div> -                <div class="btn-group" role="group"> -                    <button data-area="#i_message" title="<?= __("Quote") ?>" data-editor-command="quote" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-quote-left"></i></button> -                    <button data-area="#i_message" title="<?= __("Spoiler") ?>" data-editor-command="spoiler" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-eye"></i></button> -                </div> -            </div> -        </div> -        <div class="panel-body" style="padding:0"> -            <textarea class="form-control" id="i_message" name="message" required rows="12" cols="60" style="resize:vertical;max-height:499px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;margin:-1px;width:calc(100% + 2px)"></textarea> -        </div> -    </div> -</div> -<div class="form-group"> -    <label for="i_files"><?= __("Attachments: <small>(max. %max_attachment_count% files, max. %max_attachment_size% MiB each)</small>", [ -        "max_attachment_count" => strval(MAX_ATTACHMENT_COUNT), -        "max_attachment_size" => strval(MAX_ATTACHMENT_SIZE >> 20) -    ]) ?></label> -    <input type="file" name="files[]" id="i_files" multiple accept="*/*"> -</div> -<button type="submit" class="btn btn-success"><?= __("Post reply") ?> <span class="fa fa-send" aria-hidden="true"></span></button> -</form> diff --git a/src/application/views/form_delete_post_confirm.php b/src/application/views/form_delete_post_confirm.php deleted file mode 100644 index e127235..0000000 --- a/src/application/views/form_delete_post_confirm.php +++ /dev/null @@ -1,26 +0,0 @@ -<div class="panel panel-danger"> -    <div class="panel-heading"> -        <h3 class="panel-title"><?= __("Do you want to delete this post?") ?></h3> -    </div> -    <div class="panel-body"> -        <?= __("Are you sure you want to delete the following post:") ?><br> -        <?php _view("view_post", [ -                ...$___PARAMS, -                "hide_actions" => true -            ]) ?> -    </div> -    <div class="panel-footer"> -        <div class="text-right"> -            <form action=".#post-<?= htmlentities(urlencode($post->id)) ?>" method="get" class="seamless-inline"> -                <input type="hidden" name="_action" value="viewtopic"> -                <input type="hidden" name="topic" value="<?= htmlentities($post->topicId) ?>"> -                <button class="btn btn-default"><?= __("Keep post") ?></button> -            </form> -            <form action="?_action=deletepost" method="post" class="seamless-inline"> -                <input type="hidden" name="post" value="<?= htmlentities($post->id) ?>"> -                <input type="hidden" name="confirm" value="<?= htmlentities(base64_encode(hash("sha256", "confirm" . $post->id, true))); ?>"> -                <button class="btn btn-danger"><?= __("Delete post") ?></button> -            </form> -        </div> -    </div> -</div> diff --git a/src/application/views/form_delete_topic_confirm.php b/src/application/views/form_delete_topic_confirm.php deleted file mode 100644 index 4f9deec..0000000 --- a/src/application/views/form_delete_topic_confirm.php +++ /dev/null @@ -1,24 +0,0 @@ -<div class="panel panel-danger"> -    <div class="panel-heading"> -        <h3 class="panel-title"><?= __("Do you want to delete this topic?") ?></h3> -    </div> -    <div class="panel-body"> -        <?= __("Are you sure you want to delete the topic <em>%topic%</em> including <strong>all posts and attachments</strong>?", [ -            "topic" => htmlentities($topic->title), -        ]) ?> -    </div> -    <div class="panel-footer"> -        <div class="text-right"> -            <form action="." method="get" class="seamless-inline"> -                <input type="hidden" name="_action" value="viewtopic"> -                <input type="hidden" name="topic" value="<?= htmlentities($topic->id) ?>"> -                <button class="btn btn-default"><?= __("Keep topic") ?></button> -            </form> -            <form action="?_action=deletetopic" method="post" class="seamless-inline"> -                <input type="hidden" name="topic" value="<?= htmlentities($topic->id) ?>"> -                <input type="hidden" name="confirm" value="<?= htmlentities(base64_encode(hash("sha256", "confirm" . $topic->id, true))); ?>"> -                <button class="btn btn-danger"><?= __("Delete topic & posts") ?></button> -            </form> -        </div> -    </div> -</div> diff --git a/src/application/views/form_login.php b/src/application/views/form_login.php deleted file mode 100644 index 5fa79ad..0000000 --- a/src/application/views/form_login.php +++ /dev/null @@ -1,51 +0,0 @@ -<?php - -use mystic\forum\Messaging; -use mystic\forum\utils\RequestUtils; - -$lastFormUri = ""; -$lastForm = RequestUtils::getLastForm($lastFormUri) ?? []; -if ($lastFormUri !== $_SERVER["REQUEST_URI"]) $lastForm = []; -RequestUtils::clearLastForm(); - -?> -<div class="page-header margin-top-0"> -    <h1><?= __("Log in") ?></h1> -</div> -<div class="col-md-4"></div> -<div class="well col-md-4"> -<?php -if (($_formError = RequestUtils::getAndClearFormError("login")) !== null) { -    _view("alert_error", ["message" => $_formError]); -} -?> -<form action="<?= htmlentities($_SERVER["REQUEST_URI"]) ?>" method="post"> -<input type="hidden" name="form_id" value="login"> -<input type="hidden" name="token" value="<?= htmlentities($token) ?>"> -<input type="hidden" name="sig" value="<?= htmlentities($signature) ?>"> -<div class="form-group"> -    <label for="i_username"><?= __("Username:") ?></label> -    <input class="form-control" type="text" id="i_username" name="username" value="<?= htmlentities($lastForm["username"] ?? "") ?>" required autofocus> -</div> - -<div class="form-group"> -    <label for="i_password"><?= __("Password:") ?></label> -    <input class="form-control" type="password" id="i_password" name="password" required> -</div> - -<div class="form-group"> -    <button class="btn btn-primary" type="submit"><?= __("Log in") ?></button> -    <a class="btn btn-link" href="?_action=pwreset"><?= __("I forgot my password") ?></a> -</div> - -<?php if (REGISTRATION_ENABLED): ?> -<div class="form-group"> -    <?= __("Don't have an account? %link%Register now%/link%", [ -        "link" => '<a href="?_action=register">', -        "/link" => '</a>', -    ]) ?> -</div> -<?php endif; ?> -</form> -</div> -<div class="col-md-4"></div> diff --git a/src/application/views/form_new_password.php b/src/application/views/form_new_password.php deleted file mode 100644 index 7431bd5..0000000 --- a/src/application/views/form_new_password.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php - -use mystic\forum\utils\RequestUtils; - -$lastFormUri = ""; -$lastForm = RequestUtils::getLastForm($lastFormUri) ?? []; -if ($lastFormUri !== $_SERVER["REQUEST_URI"]) $lastForm = []; -RequestUtils::clearLastForm(); - -?> -<div class="page-header margin-top-0"> -    <h1><?= __("Reset password") ?></h1> -</div> -<div class="col-md-4"></div> -<div class="well col-md-4"> -<?php -if (($_formError = RequestUtils::getAndClearFormError("pwnew")) !== null) { -    _view("alert_error", ["message" => $_formError]); -} -?> -<form action="<?= htmlentities($_SERVER["REQUEST_URI"]) ?>" method="post"> -<input type="hidden" name="form_id" value="pwnew"> -<div class="form-group"> -    <label for="i_new_password"><?= __("New password:") ?></label> -    <input class="form-control" type="password" id="i_new_password" name="new_password" required autofocus> -</div> - -<div class="form-group"> -    <label for="i_retype_password"><?= __("Retype password:") ?></label> -    <input class="form-control" type="password" id="i_retype_password" name="retype_password" required> -</div> - -<div class="form-group"> -    <button class="btn btn-primary" type="submit"><?= __("Set new password") ?></button> -</div> -</form> -</div> -<div class="col-md-4"></div> diff --git a/src/application/views/form_newtopic.php b/src/application/views/form_newtopic.php deleted file mode 100644 index e7050c7..0000000 --- a/src/application/views/form_newtopic.php +++ /dev/null @@ -1,60 +0,0 @@ -<?php - -use mystic\forum\Messaging; -use mystic\forum\utils\RequestUtils; - -$lastFormUri = ""; -$lastForm = RequestUtils::getLastForm($lastFormUri) ?? []; -if ($lastFormUri !== $_SERVER["REQUEST_URI"]) $lastForm = []; -RequestUtils::clearLastForm(); - -?> -<div class="page-header margin-top-0"> -    <h1><?= __("New topic") ?></h1> -</div> -<?php -if (($_formError = RequestUtils::getAndClearFormError("newtopic")) !== null) { -    _view("alert_error", ["message" => $_formError]); -} -?> -<form action="<?= htmlentities($_SERVER["REQUEST_URI"]) ?>#form" method="post" enctype="multipart/form-data"> -<input type="hidden" name="form_id" value="newtopic"> -<div class="form-group"> -    <label for="i_message"><?= __("Topic title:") ?></label> -    <input type="text" class="form-control" id="i_title" name="title" value="<?= htmlentities($lastForm["title"] ?? "") ?>" required autofocus> -</div> -<div class="form-group"> -    <label for="i_message"><?= __("Message:") ?></label> -    <div class="panel panel-default"> -        <div class="panel-heading" style="padding:4px"> -            <div class="btn-toolbar" role="toolbar"> -                <div class="btn-group" role="group"> -                    <button data-area="#i_message" title="<?= __("Bold") ?>" data-editor-command="bold" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-bold"></i></button> -                    <button data-area="#i_message" title="<?= __("Italic") ?>" data-editor-command="italic" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-italic"></i></button> -                    <button data-area="#i_message" title="<?= __("Underlined") ?>" data-editor-command="underline" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-underline"></i></button> -                    <button data-area="#i_message" title="<?= __("Strikethrough") ?>" data-editor-command="strikethrough" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-strikethrough"></i></button> -                </div> -                <div class="btn-group" role="group"> -                    <button data-area="#i_message" title="<?= __("Superscript") ?>" data-editor-command="sup" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-superscript"></i></button> -                    <button data-area="#i_message" title="<?= __("Subscript") ?>" data-editor-command="sub" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-subscript"></i></button> -                </div> -                <div class="btn-group" role="group"> -                    <button data-area="#i_message" title="<?= __("Quote") ?>" data-editor-command="quote" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-quote-left"></i></button> -                    <button data-area="#i_message" title="<?= __("Spoiler") ?>" data-editor-command="spoiler" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-eye"></i></button> -                </div> -            </div> -        </div> -        <div class="panel-body" style="padding:0"> -            <textarea class="form-control" id="i_message" name="message" required rows="12" cols="60" style="resize:vertical;max-height:499px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;margin:-1px;width:calc(100% + 2px)"><?= htmlentities($lastForm["message"] ?? "") ?></textarea> -        </div> -    </div> -</div> -<div class="form-group"> -    <label for="i_files"><?= __("Attachments: <small>(max. %max_attachment_count% files, max. %max_attachment_size% MiB each)</small>", [ -        "max_attachment_count" => strval(MAX_ATTACHMENT_COUNT), -        "max_attachment_size" => strval(MAX_ATTACHMENT_SIZE >> 20) -    ]) ?></label> -    <input type="file" name="files[]" id="i_files" multiple accept="*/*"> -</div> -<button type="submit" class="btn btn-success"><?= __("Create topic") ?> <span class="fa fa-send" aria-hidden="true"></span></button> -</form> diff --git a/src/application/views/form_password_reset.php b/src/application/views/form_password_reset.php deleted file mode 100644 index 57d8ed2..0000000 --- a/src/application/views/form_password_reset.php +++ /dev/null @@ -1,41 +0,0 @@ -<?php - -use mystic\forum\Messaging; -use mystic\forum\utils\RequestUtils; - -$lastFormUri = ""; -$lastForm = RequestUtils::getLastForm($lastFormUri) ?? []; -if ($lastFormUri !== $_SERVER["REQUEST_URI"]) $lastForm = []; -RequestUtils::clearLastForm(); - -?> -<div class="page-header margin-top-0"> -    <h1><?= __("Reset password") ?></h1> -</div> -<div class="col-md-4"></div> -<div class="well col-md-4"> -<?php -if (($_formError = RequestUtils::getAndClearFormError("pwreset")) !== null) { -    _view("alert_error", ["message" => $_formError]); -} -?> -<form action="<?= htmlentities($_SERVER["REQUEST_URI"]) ?>" method="post"> -<input type="hidden" name="form_id" value="pwreset"> -<div class="form-group"> -    <label for="i_username"><?= __("Email address:") ?></label> -    <input class="form-control" type="email" id="i_email" name="email" value="<?= htmlentities($lastForm["email"] ?? "") ?>" required autofocus> -</div> - -<div class="form-group"> -    <button class="btn btn-primary" type="submit"><?= __("Reset password") ?></button> -</div> - -<div class="form-group"> -    <?= __("I know my password and I want to %link%log in%/link%!", [ -        "link" => '<a href="?_action=auth">', -        "/link" => '</a>', -    ]) ?> -</div> -</form> -</div> -<div class="col-md-4"></div> diff --git a/src/application/views/form_register.php b/src/application/views/form_register.php deleted file mode 100644 index 54da2c4..0000000 --- a/src/application/views/form_register.php +++ /dev/null @@ -1,88 +0,0 @@ -<?php - -use mystic\forum\Messaging; -use mystic\forum\utils\RequestUtils; - -$lastFormUri = ""; -$lastForm = RequestUtils::getLastForm($lastFormUri) ?? []; -if ($lastFormUri !== $_SERVER["REQUEST_URI"]) $lastForm = []; -RequestUtils::clearLastForm(); - -?> -<div class="page-header margin-top-0"> -    <h1><?= __("Register") ?></h1> -</div> -<div class="col-md-4"></div> -<div class="well col-md-4"> -<?php -if (($_formError = RequestUtils::getAndClearFormError("register")) !== null) { -    _view("alert_error", ["message" => $_formError]); -} -?> -<form action="<?= htmlentities($_SERVER["REQUEST_URI"]) ?>" method="post"> -<input type="hidden" name="form_id" value="register"> -<div class="form-group" id="group0"> -    <label for="i_username"><?= __("Username:") ?></label> -    <input class="form-control" id="i_username" type="text" name="username" value="" required> -</div> - -<div class="form-group" id="group1"> -    <label for="i_username"><?= __("Username:") ?></label> -    <input class="form-control" id="i_username" type="text" name="df82a9bc21" value="<?= htmlentities($lastForm["df82a9bc21"] ?? "") ?>" required autofocus> -</div> - -<div class="form-group" id="group2"> -    <label for="i_display_name"><?= __("Display name:") ?></label> -    <input class="form-control" id="i_display_name" type="text" name="display_name" value="<?= htmlentities($lastForm["display_name"] ?? "") ?>" required> -</div> - -<div class="form-group" id="group3"> -    <label for="i_password"><?= __("Choose password:") ?></label> -    <input class="form-control" id="i_password" type="password" name="password" required> -</div> - -<div class="form-group" id="group4"> -    <label for="i_password_retype"><?= __("Repeat password:") ?></label> -    <input class="form-control" id="i_password_retype" type="password" name="password_retype" required> -</div> - -<div class="form-group" id="group5"> -    <label for="i_email"><?= __("Email address:") ?></label> -    <input class="form-control" id="i_email" type="email" name="email" value="<?= htmlentities($lastForm["email"] ?? "") ?>" required> -</div> - -<div class="form-group" id="group6"> -    <label for="i_email"><?= __("CAPTCHA:") ?></label> -    <div class="text-center margin-bottom"> -        <img src="?_action=captcha&t=<?= htmlentities(strval(microtime(true) * 1000)) ?>" alt="CAPTCHA" width="192" height="48" id="captcha-img"> -    </div> -    <div class="input-group"> -        <input type="text" name="captcha" id="i_captcha" class="form-control" required> -        <div class="input-group-btn"> -            <button class="btn btn-default" type="button" id="btn-refresh-captcha"><span class="fa fa-refresh" aria-hidden="true"></span><span class="sr-only"><?= __("New CAPTCHA") ?></span></button> -        </div> -    </div> -</div> - -<div class="form-group"> -    <button class="btn btn-primary" type="submit"><?= __("Register now") ?></button> -</div> - -<div class="form-group"> -    <?= __("Already have an account? %link%Sign in now%/link%", [ -        "link" => '<a href="?_action=auth">', -        "/link" => '</a>', -    ]) ?> -</div> -</form> -</div> -<div class="col-md-4"></div> - -<script> -$(function() { -    $("#btn-refresh-captcha").click(function() { -        $("#captcha-img").attr("src", "?_action=captcha&t=" + new Date().getTime().toString()); -    }); -    $("#i_username").prop("disabled", true).prop("required", false); -}); -</script> diff --git a/src/application/views/form_search.php b/src/application/views/form_search.php deleted file mode 100644 index 394a0e7..0000000 --- a/src/application/views/form_search.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php - -use mystic\forum\utils\RequestUtils; - -$lastFormUri = ""; -$lastForm = RequestUtils::getLastForm($lastFormUri) ?? []; -if ($lastFormUri !== $_SERVER["REQUEST_URI"]) $lastForm = []; -RequestUtils::clearLastForm(); - -?> -<div class="page-header margin-top-0"> -    <h1><?= __("Search") ?></h1> -</div> -<?php -if (($_formError = RequestUtils::getAndClearFormError("search")) !== null) { -    _view("alert_error", ["message" => $_formError]); -} -?> -<form action="." method="get"> -    <input type="hidden" name="form_id" value="search"> -    <input type="hidden" name="_action" value="search"> -    <div class="form-group"> -        <div class="input-group"> -            <input class="form-control" type="search" id="i_query" name="query" value="<?= htmlentities($lastForm["query"] ?? $query ?? "") ?>" required autofocus placeholder="<?= htmlentities(__("Enter your search query...")) ?>"> -            <div class="input-group-btn"> -                <button class="btn btn-primary" type="submit"><?= __("Search") ?></button> -            </div> -        </div> -    </div> -</form> diff --git a/src/application/views/nav_guest.php b/src/application/views/nav_guest.php deleted file mode 100644 index 95c236e..0000000 --- a/src/application/views/nav_guest.php +++ /dev/null @@ -1,23 +0,0 @@ -<?php - -$nextParam = ""; - -switch ($GLOBALS["action"]) { -    case "auth": -    case "register": -        $nextParam = $_GET["next"] ?? ""; -        break; -    default: -        $nextParam = $_SERVER["REQUEST_URI"]; -} - -if ($GLOBALS["action"] === "login") - -?> -<ul class="nav navbar-nav navbar-right"> -<li<?= $GLOBALS["action"] === "search" ? ' class="active"' : '' ?>><a href="?_action=search"><span class="fa fa-search" aria-hidden="true"></span><span class="sr-only"><?= __("Search") ?></span></a></li> -<li<?= $GLOBALS["action"] === "auth" ? ' class="active"' : '' ?>><a href="?_action=auth&next=<?= htmlentities(urlencode($nextParam)) ?>"><?= __("Log in") ?></a></li> -<?php if (REGISTRATION_ENABLED): ?> -<li<?= $GLOBALS["action"] === "register" ? ' class="active"' : '' ?>><a href="?_action=register&next=<?= htmlentities(urlencode($nextParam)) ?>"><?= __("Register") ?></a></li> -<?php endif; ?> -</ul>
\ No newline at end of file diff --git a/src/application/views/nav_logged_in.php b/src/application/views/nav_logged_in.php deleted file mode 100644 index 8dbb325..0000000 --- a/src/application/views/nav_logged_in.php +++ /dev/null @@ -1,12 +0,0 @@ -<?php -use mystic\forum\orm\User; -?> -<ul class="nav navbar-nav navbar-right"> -<li><p class="navbar-text"><?= __("Welcome, %user%!", [ -    "user" => ($user->id === User::SUPERUSER_ID) ? ('<strong class="text-danger">' . htmlentities($user->displayName) . '</strong>') : ('<strong>' . htmlentities($user->displayName) . '</strong>') -]) ?> -</p></li> -<li<?= $GLOBALS["action"] === "search" ? ' class="active"' : '' ?>><a href="?_action=search"><span class="fa fa-search" aria-hidden="true"></span><span class="sr-only"><?= __("Search") ?></span></a></li> -<li<?= ($isViewingOwnProfile ?? false) ? ' class="active"' : '' ?>><a href="?_action=viewuser&user=<?= htmlentities(urlencode($user->id)) ?>"><span class="fa fa-user" aria-hidden="true"></span><span class="sr-only">View profile</span></a></li> -<li><a href="?_action=logout&next=<?= htmlentities(urlencode($_SERVER["REQUEST_URI"])) ?>"><span class="fa fa-sign-out" aria-hidden="true"></span><span class="sr-only">Log out</span></a></li> -</ul> diff --git a/src/application/views/template_end.php b/src/application/views/template_end.php deleted file mode 100644 index b162def..0000000 --- a/src/application/views/template_end.php +++ /dev/null @@ -1,165 +0,0 @@ -</div> -<footer class="footer"> -<div class="container"> -<div class="panel panel-default"> -<div class="panel-body"> -    <table style="border-collapse: collapse; width: 100%; background: none"> -    <tbody> -    <tr> -        <td style="padding: 0; vertical-align: middle; text-align: left; width: 100%" class="text-normal"> -            © <?= date("Y") ?> <?= htmlentities(env("MYSTIC_FORUM_COPYRIGHT") ?? env("MYSTIC_FORUM_TITLE") ?? "Forum") ?>. -            Powered by <a href="https://git.jkohl.link/mystic-forum.git/tag/?h=v<?= htmlentities(urlencode(MYSTICBB_VERSION)) ?>">mysticBB v<?= htmlentities(MYSTICBB_VERSION) ?></a>. -        </td> -        <td style="padding: 0; vertical-align: middle; text-align: right; white-space: nowrap" class="text-normal"> -            <form action="?_action=settheme" class="form-inline seamless-inline" method="post"> -                <input type="hidden" name="next" value="<?= htmlentities($_SERVER["REQUEST_URI"]) ?>"> -                <div class="form-group"> -                    <label for="theme-select"><?= __("Theme:") ?></label> -                    <select class="form-control input-sm auto-submit" id="theme-select" name="theme"> -                        <?php foreach ($availableThemes as $themeKey => $themeInfo): ?> -                            <option value="<?= htmlentities($themeKey) ?>" <?= $themeKey === $currentTheme ? " selected" : "" ?>><?= htmlentities($themeInfo->name) ?></option> -                        <?php endforeach; ?> -                    </select> -                </div> -            </form> -                -            <form action="?_action=setlang" class="form-inline seamless-inline" method="post"> -                <input type="hidden" name="next" value="<?= htmlentities($_SERVER["REQUEST_URI"]) ?>"> -                <div class="form-group"> -                    <label for="lang-select"><?= __("Language:") ?></label> -                    <select class="form-control input-sm auto-submit" id="lang-select" name="lang"> -                        <?php foreach ($availableLangs as $langKey => $langName): ?> -                            <option value="<?= htmlentities($langKey) ?>" <?= $langKey === $currentLang ? " selected" : "" ?>><?= htmlentities($langName) ?></option> -                        <?php endforeach; ?> -                    </select> -                </div> -            </form> -        </td> -    </tr> -    </tbody> -    </table> -</div> -</div> -</div> -</footer> - -<script type="text/javascript"> -    $(function() { -        function insertAroundSelection(textarea, before, after) { -            var start = textarea.selectionStart; -            var end = textarea.selectionEnd; -            var text = textarea.value; -            var pre = text.substring(0, start); -            var inner = text.substring(start, end); -            var post = text.substring(end); -            start += before.length; -            end += before.length; -            text = pre + before + inner + after + post; -            textarea.value = text; -            textarea.focus(); -            textarea.selectionStart = start; -            textarea.selectionEnd = end; -        } - -        function getTextarea(btn) { -            return $($(btn).attr("data-area"))[0]; -        } - -        var commands = { -            bold: function(textarea) { -                insertAroundSelection(textarea, "[b]", "[/b]"); -            }, -            italic: function(textarea) { -                insertAroundSelection(textarea, "[i]", "[/i]"); -            }, -            underline: function(textarea) { -                insertAroundSelection(textarea, "[u]", "[/u]"); -            }, -            strikethrough: function(textarea) { -                insertAroundSelection(textarea, "[s]", "[/s]"); -            }, -            sup: function(textarea) { -                insertAroundSelection(textarea, "[^]", "[/^]"); -            }, -            sub: function(textarea) { -                insertAroundSelection(textarea, "[_]", "[/_]"); -            }, -            quote: function(textarea) { -                insertAroundSelection(textarea, "> ", ""); -            }, -            spoiler: function(textarea) { -                insertAroundSelection(textarea, "[spoiler]", "[/spoiler]"); -            } -        } - -        $("button[data-editor-command]").attr("data-toggle", "tooltip").attr("data-placement", "bottom").click(function() { -            var command = $(this).attr("data-editor-command"); -            var textarea = getTextarea(this); -            commands[command](textarea); -        }).tooltip(); -    }); -</script> - -<script> -$(function() { -    var _messages = <?= json_encode([ -        "selectFiles" => [ -            ___("Select file", "Select files", 1), -            ___("Select file", "Select files", 2), -        ], -        "filesSelected" => [ -            ___("%n% file selected", "No files selected", 0), -            ___("%n% file selected", "%n% files selected", 1), -            ___("%n% file selected", "%n% files selected", 2), -        ], -    ]) ?> - -    $(".auto-submit").on("change", function() { -        $(this)[0].form.submit(); -    }); - -    $("._time").each(function(i, e) { -        var date = new Date($(e).text()); -        $(e).text(date.toLocaleString()); -    }); -    $("._date").each(function(i, e) { -        var date = new Date($(e).text()); -        $(e).text(date.toLocaleDateString()); -    }); -    $("._time-only").each(function(i, e) { -        var date = new Date($(e).text()); -        $(e).text(date.toLocaleTimeString()); -    }); - -    $("input[type=file]").each(function(i, e) { -        var isMultiple = !!$(e).prop("multiple"); -        var isSmall = !!$(e).hasClass("small"); -        var $input = $('<input type="text" readonly class="form-control" />').attr("placeholder", _messages.filesSelected[0]).css("text-overflow", "ellipsis"); -        var $btn = $('<button class="btn btn-default" type="button"></button>'); -        if (isSmall) { -            $input.addClass("input-sm"); -            $btn.addClass("btn-sm"); -        } -        $(e).after($('<div class="input-group file-input-group"></div>').append( -            $input, -            $('<span class="input-group-btn"></span>').append( -                $btn.text(_messages.selectFiles[isMultiple ? 1 : 0]).click(function() { -                    $(e).click(); -                }) -            ) -        )).addClass("sr-only"); -        $(e).on("change", function() { -            var files = $(e)[0].files; -            if (files.length < 1) -                $input.val(""); -            else if (files.length === 1) -                $input.val(files[0].name); -            else -                $input.val(_messages.filesSelected[2].replace("%n%", files.length)); -        }); -    }) -}); -</script> - -</body> -</html> diff --git a/src/application/views/template_navigation.php b/src/application/views/template_navigation.php deleted file mode 100644 index ff0752b..0000000 --- a/src/application/views/template_navigation.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php -if ($user) { -    _view("nav_logged_in", [ -        "user" => $user, -        "isViewingOwnProfile" => $isViewingOwnProfile ?? false, -    ]); -} else { -    _view("nav_guest"); -} diff --git a/src/application/views/template_navigation_end.php b/src/application/views/template_navigation_end.php deleted file mode 100644 index a43919c..0000000 --- a/src/application/views/template_navigation_end.php +++ /dev/null @@ -1,4 +0,0 @@ -</div> -</div> -</nav> -<div class="container"> diff --git a/src/application/views/template_navigation_start.php b/src/application/views/template_navigation_start.php deleted file mode 100644 index bd2b3a5..0000000 --- a/src/application/views/template_navigation_start.php +++ /dev/null @@ -1,15 +0,0 @@ -<nav class="navbar navbar-default navbar-static-top"> -    <div class="container"> -        <div class="navbar-header"> -            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#nav-collapse" aria-expanded="false"> -                <span class="sr-only">Toggle navigation</span> -                <span class="icon-bar"></span> -                <span class="icon-bar"></span> -                <span class="icon-bar"></span> -            </button> -            <a class="navbar-brand" href="."> -                <span class="myb-icon mybblogo" aria-hidden="false"></span> -                <?= htmlentities(env("MYSTIC_FORUM_TITLE") ?? "Forum") ?> -            </a> -        </div> -        <div class="collapse navbar-collapse" id="nav-collapse"> diff --git a/src/application/views/template_start.php b/src/application/views/template_start.php deleted file mode 100644 index cf3daf6..0000000 --- a/src/application/views/template_start.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php - -$pageTitle = ""; -if (isset($_title) && $_title !== null) -    $pageTitle = "$_title | "; -$pageTitle .= env("MYSTIC_FORUM_TITLE") ?? "Forum"; - -?> -<!DOCTYPE html> -<!--[if lt IE 7]>      <html lang="de" class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]--> -<!--[if IE 7]>         <html lang="de" class="no-js lt-ie9 lt-ie8"> <![endif]--> -<!--[if IE 8]>         <html lang="de" class="no-js lt-ie9"> <![endif]--> -<!--[if gt IE 8]><!--> <html lang="de" class="no-js"> <!--<![endif]--> -<head> -    <meta charset="utf-8"> -    <meta http-equiv="X-UA-Compatible" content="IE=edge"> -    <meta name="viewport" content="width=device-width,initial-scale=1"> -    <meta name="generator" content="mysticBB <?= htmlentities(MYSTICBB_VERSION) ?>"> -    <title><?= htmlentities($pageTitle) ?></title> -    <link rel="stylesheet" href="?_action=ctheme"> -    <script src="/ui/jquery-1.12.4.min.js"></script> -    <script src="/ui/dist/js/bootstrap.min.js"></script> -    <script src="/ui/modernizr-2.6.2.min.js"></script> -    <script src="?_action=ji18n"></script> -    <!--[if lt IE 9]> -    <script src="/ui/html5shiv.min.js"></script> -    <script src="/ui/respond.min.js"></script> -    <![endif]--> -</head> -<body> diff --git a/src/application/views/view_logintoreply.php b/src/application/views/view_logintoreply.php deleted file mode 100644 index 1b5983a..0000000 --- a/src/application/views/view_logintoreply.php +++ /dev/null @@ -1,7 +0,0 @@ -<div class="well margin-top-4x text-center"> -    <div class="h3 margin-top-0"><?= __("Log in to reply to this topic") ?></div> -    <a href="?_action=auth&next=<?= htmlentities(urlencode($_SERVER["REQUEST_URI"])) ?>" class="btn btn-success"> -        <span class="fa fa-user" aria-hidden="true"></span> -        <?= __("Log in") ?> -    </a> -</div>
\ No newline at end of file diff --git a/src/application/views/view_post.php b/src/application/views/view_post.php deleted file mode 100644 index e2fcc36..0000000 --- a/src/application/views/view_post.php +++ /dev/null @@ -1,150 +0,0 @@ -<?php - -use mystic\forum\orm\UserPermissions; -use mystic\forum\orm\Attachment; - -/** @var mystic\forum\orm\Post $post */ -/** @var mystic\forum\orm\User $postAuthor */ -/** @var mystic\forum\orm\Topic $topic */ - -$isImage = fn(string $m) => str_starts_with($m, "image/") || str_starts_with($m, "video/"); - -$fileAttachments = array_filter($attachments, fn(Attachment $a) => !$isImage($a->mimeType)); -$imageAttachments = array_filter($attachments, fn(Attachment $a) => $isImage($a->mimeType)); - -$canReply = !$topic->isLocked && ($GLOBALS["currentUser"]?->hasPermission(UserPermissions::CREATE_OWN_POST) ?? false); - -$canEdit = ($GLOBALS["currentUser"]?->id === $postAuthor?->id && $postAuthor?->hasPermission(UserPermissions::EDIT_OWN_POST)) -        || ($GLOBALS["currentUser"]?->hasPermission(UserPermissions::EDIT_OTHER_POST)); - -$canEdit = $canEdit && !$topic->isLocked; - -$canDelete = ($GLOBALS["currentUser"]?->id === $postAuthor?->id && $postAuthor?->hasPermission(UserPermissions::DELETE_OWN_POST)) -          || ($GLOBALS["currentUser"]?->hasPermission(UserPermissions::DELETE_OTHER_POST)); - -$canViewAttachments = $GLOBALS["currentUser"] !== null; - -$hide_actions ??= false; -$hide_pfp ??= false; -$your_are_the_author = $GLOBALS["currentUser"]?->id === $postAuthor?->id; -$is_op = $postAuthor?->id === $topicAuthor?->id && $postAuthor?->id !== null; - -?> - -<?php if ($post->deleted): ?> -<div class="media" id="post-<?= htmlentities($post->id) ?>"> -<div class="media-left hidden-sm hidden-xs"> -    <div class="media-object" style="width:64px"></div> -</div> -<div class="media-body"> -    <div class="well icon-well text-warning"> -        <span class="fa fa-exclamation-triangle text-warning" aria-hidden="true"></span> -        <em><?= __("This post has been deleted") ?></em> -    </div> -</div> -</div> -<?php else: ?> -<div class="media" id="post-<?= htmlentities($post->id) ?>" data-text="<?= htmlentities($post->content) ?>" style="overflow: visible;"> -<?php if (!$hide_pfp): ?> -    <div class="media-left hidden-sm hidden-xs"> -        <?php if ($postAuthor): ?> -            <?php if ($hide_actions): ?> -                <img class="media-object" alt="<?= __("Profile picture") ?>" src="?_action=profilepicture&user=<?= htmlentities(urlencode($postAuthor->id)) ?>" width="64" height="64"> -            <?php else: ?> -                <a href="?_action=viewuser&user=<?= htmlentities(urlencode($postAuthor->id)) ?>"> -                    <img class="media-object" alt="<?= __("Profile picture") ?>" src="?_action=profilepicture&user=<?= htmlentities(urlencode($postAuthor->id)) ?>" width="64" height="64"> -                </a> -            <?php endif; ?> -        <?php else: ?> -            <div class="media-object" style="width:64px;height:64px"></div> -        <?php endif; ?> -    </div> -<?php endif; ?> -    <div class="media-body" style="overflow: visible;"> -        <div class="panel panel-default"> -            <div class="panel-heading"> -                <div class="panel-title h3"> -                    <?php if (!$hide_actions): ?> -                    <div class="pull-right"> -                        <a href="#post-<?= htmlentities(urlencode($post->id)) ?>" class="btn btn-default"><span class="fa fa-link" aria-hidden="true"></span><span class="sr-only"><?= __("Permalink") ?></span></a> -                        <?php if ($canReply): ?> -                            <button data-post-id="<?= htmlentities($post->id) ?>" class="btn btn-default js-only _reply-post"><span class="fa fa-comment" aria-hidden="true"></span><span class="sr-only"><?= __("Reply to post") ?></span></button> -                        <?php endif; ?> -                        <?php if ($canEdit): ?> -                            <button data-post-id="<?= htmlentities($post->id) ?>" class="btn btn-default js-only _edit-post"><span class="fa fa-pencil" aria-hidden="true"></span><span class="sr-only"><?= __("Edit post") ?></span></button> -                        <?php endif; ?> -                        <?php if ($canDelete): ?> -                            <form action="?_action=deletepost" method="post" class="seamless-inline"> -                                <input type="hidden" name="post" value="<?= htmlentities($post->id) ?>"> -                                <button type="submit" class="btn btn-danger"><span class="fa fa-trash" aria-hidden="true"></span><span class="sr-only"><?= __("Delete post") ?></span></button> -                            </form> -                        <?php endif; ?> -                    </div> -                    <?php endif; ?> -                    <?php if ($postAuthor): ?> -                        <?php if ($hide_actions): ?> -                            <?= htmlentities($postAuthor->displayName) ?> -                        <?php else: ?> -                            <a href="?_action=viewuser&user=<?= htmlentities(urlencode($postAuthor->id)) ?>"><?= htmlentities($postAuthor->displayName) ?></a> -                            <?php if ($is_op): ?> -                                <span title="<?= __("Created this topic") ?>" class="text-info fa fa-user-circle"></span> -                            <?php endif; ?> -                        <?php endif; ?> -                        <?php if ($your_are_the_author): ?> -                            <span class="text-normal label label-primary"><?= __("You") ?></span> -                        <?php endif; ?> -                    <?php else: ?> -                        <em class="text-muted"><?= __("(deleted)") ?></em> -                    <?php endif; ?> -                </div> -                <span class="_time"><?= $post->postDate->format("c"); ?></span> -                <?php if ($post->edited): ?> -                    <em class="text-muted"><?= __("(edited)") ?></em> -                <?php endif; ?> -            </div> -            <div class="panel-body"> -                <div class="post-content"><?= renderPost($post->content) ?></div> -                <?php if (count($imageAttachments) > 0): ?> -                <div class="post-images clearfix"> -                    <?php /** @var Attachment $attachment */ foreach ($imageAttachments as $attachment): ?> -                        <?php if ($hide_actions): ?> -                            <span class="image-attachment" title="<?= htmlentities($attachment->name) ?>"> -                                <img class="image-attachment-image" src="?_action=thumb&attachment=<?= htmlentities(urlencode($attachment->id)) ?>" alt="" width="100"> -                            </span> -                        <?php else: ?> -                            <a class="image-attachment attachment<?php if (str_starts_with($attachment->mimeType, "video/")): ?> video-attachment<?php endif; ?>" href="?_action=attachment&attachment=<?= htmlentities(urlencode($attachment->id)) ?>" title="<?= htmlentities($attachment->name) ?>" data-attachment-id="<?= htmlentities($attachment->id) ?>"> -                                <img class="image-attachment-image" src="?_action=thumb&attachment=<?= htmlentities(urlencode($attachment->id)) ?>" alt="" width="100"> -                                <?php if (!$canViewAttachments): ?> -                                    <span class="attachment-lock fa fa-ban" aria-hidden="true"></span> -                                <?php elseif (str_starts_with($attachment->mimeType, "video/")): ?> -                                    <span class="video-player-icon fa fa-play-circle" aria-hidden="true"></span> -                                <?php endif; ?> -                            </a> -                        <?php endif; ?> -                    <?php endforeach; ?> -                </div> -            <?php endif; ?> -            </div> -            <?php if (count($fileAttachments) > 0): ?> -                <div class="panel-footer"> -                    <div class="btn-group"> -                        <?php /** @var Attachment $attachment */ foreach ($fileAttachments as $attachment): ?> -                            <?php if ($hide_actions): ?> -                                <button class="btn btn-default"><?= htmlentities($attachment->name) ?></button> -                            <?php else: ?> -                                <a class="btn btn-default attachment" href="?_action=attachment&attachment=<?= htmlentities(urlencode($attachment->id)) ?>"> -                                    <?php if (!$canViewAttachments): ?> -                                        <span class="fa fa-ban" aria-hidden="true"></span> -                                    <?php endif; ?> -                                    <?= htmlentities($attachment->name) ?> -                                </a> -                            <?php endif; ?> -                        <?php endforeach; ?> -                    </div> -                </div> -            <?php endif; ?> -        </div> -    </div> -</div> - -<?php endif; ?> diff --git a/src/application/views/view_search_results.php b/src/application/views/view_search_results.php deleted file mode 100644 index 660284d..0000000 --- a/src/application/views/view_search_results.php +++ /dev/null @@ -1,35 +0,0 @@ -<?php -use mystic\forum\utils\StringUtils; -?> - -<?php if (count($posts) > 0): ?> -    <p><?= __("%result_count% result(s) in %search_duration% second(s)", [ -        "result_count" => count($posts), -        "search_duration" => number_format($search_duration, 2, __(".", context: "Number formatting"), __(",", context: "Number formatting")), -    ]) ?></p> -    <div class="list-group margin-top"> -        <?php foreach ($posts as $post): -            if ($post->deleted) continue; -            $hasAttachments = count($attachments[$post->id]) > 0; -        ?> -            <a href="?_action=viewtopic&topic=<?= htmlentities(urlencode($post->topicId)) ?>#post-<?= htmlentities(urlencode($post->id)) ?>" class="list-group-item"> -                <?php if ($hasAttachments): ?> -                    <span class="badge"><span class="fa fa-paperclip"></span></span> -                <?php endif; ?> -                <?= renderPostSummary($post->content) ?><br> -                <span class="text-muted"><?= __("posted by %author% on %post_date% in %topic%", [ -                    "author" => '<em>' . htmlentities($users[$post->authorId]?->displayName ?? __("unknown")) . '</em>', -                    "post_date" => '<span class="_time">' . htmlentities($post->postDate->format("c")) . '</span>', -                    "topic" => '<em>' -                        . ($topics[$post->topicId]?->isLocked ? '<span class="fa fa-lock text-muted" aria-hidden="true"></span> ' : '') -                        . htmlentities($topics[$post->topicId]?->title ?? "unknown") . '</em>', -                ]) ?></span> -            </a> -        <?php endforeach; ?> -    </div> -<?php else: ?> -    <div class="well icon-well text-info margin-top margin-bottom"> -        <span class="fa fa-info-circle text-info" aria-hidden="true"></span> -        <em><?= __("No results for this search") ?></em> -    </div> -<?php endif; ?> diff --git a/src/application/views/view_topic_end.php b/src/application/views/view_topic_end.php deleted file mode 100644 index e69de29..0000000 --- a/src/application/views/view_topic_end.php +++ /dev/null diff --git a/src/application/views/view_topic_locked.php b/src/application/views/view_topic_locked.php deleted file mode 100644 index b3eb99e..0000000 --- a/src/application/views/view_topic_locked.php +++ /dev/null @@ -1,4 +0,0 @@ -<div class="well icon-well text-warning margin-top-4x"> -    <span class="fa fa-lock text-warning" aria-hidden="true"></span> -    <em><?= __("This topic has been locked") ?></em> -</div> diff --git a/src/application/views/view_topic_start.php b/src/application/views/view_topic_start.php deleted file mode 100644 index 7f62986..0000000 --- a/src/application/views/view_topic_start.php +++ /dev/null @@ -1,254 +0,0 @@ -<?php -use mystic\forum\orm\UserPermissions; -use mystic\forum\utils\RequestUtils; - -$canReply = !$topic->isLocked && ($GLOBALS["currentUser"]?->hasPermission(UserPermissions::CREATE_OWN_POST) ?? false); - -$canEdit = ($GLOBALS["currentUser"]?->id === $topicAuthor->id && $topicAuthor->hasPermission(UserPermissions::EDIT_OWN_TOPIC)) -          || ($GLOBALS["currentUser"]?->hasPermission(UserPermissions::EDIT_OTHER_TOPIC)); - -$canDelete = ($GLOBALS["currentUser"]?->id === $topicAuthor->id && $topicAuthor->hasPermission(UserPermissions::DELETE_OWN_TOPIC)) -          || ($GLOBALS["currentUser"]?->hasPermission(UserPermissions::DELETE_OTHER_TOPIC)); -?> -<?php if ($canEdit): ?> -    <div class="modal fade" tabindex="-1" role="dialog" id="diag-edit-post"> -        <form class="modal-dialog" role="document" action="?_action=updatepost" method="post"> -            <input type="hidden" id="i_edit_post" name="post"> -            <div class="modal-content"> -                <div class="modal-header"> -                    <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> -                    <h4 class="modal-title"><?= __("Edit post") ?></h4> -                </div> -                <div class="modal-body"> -                    <label class="sr-only" for="i_edit_message"><?= __("Message:") ?></label> -                    <div class="panel panel-default"> -                        <div class="panel-heading" style="padding:4px"> -                            <div class="btn-toolbar" role="toolbar"> -                                <div class="btn-group" role="group"> -                                    <button data-area="#i_edit_message" title="<?= __("Bold") ?>" data-editor-command="bold" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-bold"></i></button> -                                    <button data-area="#i_edit_message" title="<?= __("Italic") ?>" data-editor-command="italic" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-italic"></i></button> -                                    <button data-area="#i_edit_message" title="<?= __("Underlined") ?>" data-editor-command="underline" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-underline"></i></button> -                                    <button data-area="#i_edit_message" title="<?= __("Strikethrough") ?>" data-editor-command="strikethrough" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-strikethrough"></i></button> -                                </div> -                                <div class="btn-group" role="group"> -                                    <button data-area="#i_edit_message" title="<?= __("Superscript") ?>" data-editor-command="sup" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-superscript"></i></button> -                                    <button data-area="#i_edit_message" title="<?= __("Subscript") ?>" data-editor-command="sub" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-subscript"></i></button> -                                </div> -                                <div class="btn-group" role="group"> -                                    <button data-area="#i_edit_message" title="<?= __("Quote") ?>" data-editor-command="quote" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-quote-left"></i></button> -                                    <button data-area="#i_edit_message" title="<?= __("Spoiler") ?>" data-editor-command="spoiler" type="button" class="btn btn-default btn-xs"><i class="fa fa-fw fa-eye"></i></button> -                                </div> -                            </div> -                        </div> -                        <div class="panel-body" style="padding:0"> -                            <textarea class="form-control" id="i_edit_message" name="message" required rows="12" cols="60" style="resize:vertical;max-height:500px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;margin:-1px;width:calc(100% + 2px)"></textarea> -                        </div> -                    </div> -                </div> -                <div class="modal-footer"> -                    <button type="button" class="btn btn-default" data-dismiss="modal"><span class="fa fa-remove" aria-hidden="true"></span> <?= __("Cancel") ?></button> -                    <button type="submit" class="btn btn-success"><span class="fa fa-save" aria-hidden="true"></span> <?= __("Save changes") ?></button> -                </div> -            </div> -        </form> -    </div> -<?php endif; ?> -<?php if ($GLOBALS["currentUser"] === null): ?> -    <div class="modal fade" tabindex="-1" role="dialog" id="diag-cant-view-attachment"> -        <div class="modal-dialog modal-danger" role="document"> -            <div class="modal-content panel-danger"> -                <div class="modal-header panel-heading"> -                    <h4 class="modal-title"><span class="fa fa-exclamation-circle" aria-hidden="true"></span> <?= __("Permission denied") ?></h4> -                </div> -                <div class="modal-body"> -                    <?= __("You must be logged in to view attachments") ?> -                </div> -                <div class="modal-footer"> -                    <button type="button" class="btn btn-default" data-dismiss="modal"><span class="fa fa-close" aria-hidden="true"></span> <?= __("Close") ?></button> -                    <a href="?_action=auth&next=<?= htmlentities(urlencode($_SERVER["REQUEST_URI"])) ?>" class="btn btn-success"><span class="fa fa-user" aria-hidden="true"></span> <?= __("Log in") ?></a> -                </div> -            </div> -        </div> -    </div> -    <script> -        $(function() { -            $(".attachment").click(function(e) { -                e.preventDefault(); -                $("#diag-cant-view-attachment").modal(); -            }); -        }); -    </script> -<?php else: ?> -    <div class="modal fade" tabindex="-1" role="dialog" id="diag-image-attachment"> -        <div class="modal-dialog" role="document"> -            <div class="modal-content"> -                <div class="modal-header"> -                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> -                    <h4 class="modal-title"><?= __("Attachment") ?></h4> -                </div> -                <div class="modal-body"> -                    <img class="image-attachment-view attachment-view" id="image-attachment-view" alt=""> -                </div> -                <div class="modal-footer"> -                    <a href="" download id="image-attachment-dl-btn" class="btn btn-default"><span class="fa fa-download" aria-hidden="true"></span> <?= __("Download") ?></a> -                </div> -            </div> -        </div> -    </div> -    <div class="modal fade" tabindex="-1" role="dialog" id="diag-video-attachment"> -        <div class="modal-dialog" role="document"> -            <div class="modal-content"> -                <div class="modal-header"> -                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> -                    <h4 class="modal-title"><?= __("Attachment") ?></h4> -                </div> -                <div class="modal-body"> -                    <video class="video-attachment-view attachment-view" id="video-attachment-view" controls></video> -                </div> -                <div class="modal-footer"> -                    <a href="" download id="video-attachment-dl-btn" class="btn btn-default"><span class="fa fa-download" aria-hidden="true"></span> <?= __("Download") ?></a> -                </div> -            </div> -        </div> -    </div> -    <script> -        $(function() { -            $(".image-attachment:not(.video-attachment)").click(function(e) { -                e.preventDefault(); -                var attUrl = "?_action=attachment&attachment=" + encodeURIComponent($(this).attr("data-attachment-id")); -                $("#image-attachment-view").attr("src", attUrl); -                $("#image-attachment-dl-btn").attr("href", attUrl); -                $("#diag-image-attachment").modal(); -            }); -            $(".image-attachment.video-attachment").click(function(e) { -                e.preventDefault(); -                var attUrl = "?_action=attachment&attachment=" + encodeURIComponent($(this).attr("data-attachment-id")); -                $("#video-attachment-view").attr("src", attUrl); -                $("#video-attachment-dl-btn").attr("href", attUrl); -                $("#diag-video-attachment").modal(); -            }); -            $("#diag-video-attachment").on("hide.bs.modal", function() { -                $("#video-attachment-view")[0].pause(); -            }); -        }); -    </script> -<?php endif; ?> - -<?php -if (($_formError = RequestUtils::getAndClearFormError("updatetopic")) !== null) { -    _view("alert_error", ["message" => $_formError]); -} - -if (($_formError = RequestUtils::getAndClearFormError("locktopic")) !== null) { -    _view("alert_error", ["message" => $_formError]); -} -?> - -<div class="page-header margin-top-0 clearfix"> -    <div id="displayHeading"> -        <div role="heading" class="h1 seamless-inline"> -            <?php if ($topic->isLocked): ?> -                <span class="fa fa-lock text-muted" aria-hidden="true"></span> -            <?php endif; ?> -            <?= htmlentities($topic->title) ?> -            <div class="pull-right text-normal"> -                <?php if ($canEdit && !$topic->isLocked): ?> -                    <button id="btn-edit-title" class="btn btn-default js-only"><span class="fa fa-pencil" aria-hidden="true"></span> <?= __("Edit title") ?></button> -                <?php endif; ?> -                <?php if ($canReply): ?> -                    <button id="btn-reply" class="btn btn-default js-only"><span class="fa fa-comment" aria-hidden="true"></span> <?= __("Reply") ?></button> -                <?php endif; ?> -                <?php if ($canEdit): ?> -                    <?php if ($topic->isLocked): ?> -                        <form action="?_action=locktopic" method="post" class="seamless-inline"> -                            <input type="hidden" name="topic" value="<?= htmlentities($topic->id) ?>"> -                            <input type="hidden" name="locked" value="false"> -                            <button type="submit" class="btn btn-success"><span class="fa fa-unlock" aria-hidden="true"></span> <?= __("Unlock topic") ?></button> -                        </form> -                    <?php else: ?> -                        <form action="?_action=locktopic" method="post" class="seamless-inline"> -                            <input type="hidden" name="topic" value="<?= htmlentities($topic->id) ?>"> -                            <input type="hidden" name="locked" value="true"> -                            <button type="submit" class="btn btn-warning"><span class="fa fa-lock" aria-hidden="true"></span> <?= __("Lock topic") ?></button> -                        </form> -                    <?php endif; ?> -                <?php endif; ?> -                <?php if ($canDelete): ?> -                    <form action="?_action=deletetopic" method="post" class="seamless-inline"> -                        <input type="hidden" name="topic" value="<?= htmlentities($topic->id) ?>"> -                        <button type="submit" class="btn btn-danger"><span class="fa fa-trash" aria-hidden="true"></span> <?= __("Delete topic") ?></button> -                    </form> -                <?php endif; ?> -            </div> -        </div><br> -        <?= __("Started by %user% on %date%", [ -            "user" => ($topicAuthor !== null) ? ('<a href="?_action=viewuser&user=' . htmlentities(urlencode($topicAuthor->id)) . '">' . htmlentities($topicAuthor->displayName) . '</a>') : __("(deleted)"), -            "date" => '<span class="_time">' . htmlentities($topic->creationDate->format("c")) . '</span>', -        ]); ?> -    </div> -    <?php if ($canEdit): ?> -        <form action="?_action=updatetopic" method="post" id="editHeading" style="display: none;" class="form-inline seamless-inline"> -            <input type="hidden" name="topic" value="<?= htmlentities(urlencode($topic->id)) ?>"> -            <div class="row"> -                <div class="col-md-8"> -                    <input type="text" class="form-control" name="title" id="i_edit_title" data-original-value="<?= htmlentities($topic->title) ?>" value="<?= htmlentities($topic->title) ?>" style="box-sizing: border-box; width: 100%; font-size: 36px; height: 56px"> -                </div> -                <div class="col-md-4 text-right"> -                    <button type="button" id="topicTitleEditCancel" class="btn btn-default"><span class="fa fa-close" aria-hidden="true"></span> <?= __("Cancel") ?></button> -                    <button type="submit" class="btn btn-success"><span class="fa fa-save" aria-hidden="true"></span> <?= __("Save changes") ?></button> -                </div> -            </div> -        </form> -    <?php endif; ?> -</div> -<script> -<?php if ($canEdit): ?> -$(function() { -    $("#btn-edit-title").click(function() { -        $("#displayHeading").hide(); -        $("#editHeading").show(); -        $("#i_edit_title").val($("#i_edit_title").attr("data-original-value")).focus(); -    }); -    $("#topicTitleEditCancel").click(function() { -        $("#displayHeading").show(); -        $("#editHeading").hide(); -    }); -}); -<?php endif; ?> -<?php if ($canReply): ?> -$(function() { -    function focusReplyBox() { -        var msgInput = $("#i_message"); -        msgInput[0].scrollIntoView(); -        msgInput.focus(); -    } -    $("#btn-reply").click(function() { -        focusReplyBox(); -    }); -    $("._reply-post").click(function() { -        var text = $("#post-" + $(this).attr("data-post-id")).attr("data-text"); -        var val = $("#i_message").val(); -        var lines = text.split("\n"); -        for (var i = 0; i < lines.length; ++i) -            val += "\n> " + lines[i]; -        val += "\n\n"; -        $("#i_message").val(val.replace(/^\n+/, "")); -        focusReplyBox(); -    }); -}); -<?php endif; ?> -<?php if ($canEdit): ?> -$(function() { -    $("._edit-post").click(function() { -        var $post = $("#post-" + $(this).attr("data-post-id")); -        var $postContent = $post.find(".post-content"); -        $("#i_edit_message").css("height", "").val($post.attr("data-text")); -        $("#i_edit_post").val($(this).attr("data-post-id")); -        $("#diag-edit-post").modal(); -    }); -    $("#diag-edit-post").on("shown.bs.modal", function() { -        $("#i_edit_message").focus(); -    }); -}); -<?php endif; ?> -</script> diff --git a/src/application/views/view_topiclog.php b/src/application/views/view_topiclog.php deleted file mode 100644 index 291dada..0000000 --- a/src/application/views/view_topiclog.php +++ /dev/null @@ -1,67 +0,0 @@ -<?php - -/** @var \mystic\forum\orm\TopicLogMessage $logMessage */ -/** @var ?\mystic\forum\orm\User $postAuthor */ - -use mystic\forum\orm\TopicLogMessage; - -$hide_actions ??= false; -$hide_pfp ??= false; - -$user = ""; -if ($postAuthor === null) { -    $user = __("(deleted)"); -} else { -    $user = '<a href="?_action=viewuser&user=' . htmlentities(urlencode($postAuthor->id)) . '">' . htmlentities($postAuthor->displayName) . '</a>'; -} - -?> -<div class="media" id="post-<?= htmlentities($logMessage->id) ?>"> -<div class="media-left hidden-sm hidden-xs"> -    <?php if ($postAuthor): ?> -        <?php if ($hide_actions): ?> -            <img class="media-object" alt="<?= __("Profile picture") ?>" src="?_action=profilepicture&user=<?= htmlentities(urlencode($postAuthor->id)) ?>" width="64" height="64"> -        <?php else: ?> -            <a href="?_action=viewuser&user=<?= htmlentities(urlencode($postAuthor->id)) ?>"> -                <img class="media-object" alt="<?= __("Profile picture") ?>" src="?_action=profilepicture&user=<?= htmlentities(urlencode($postAuthor->id)) ?>" width="64" height="64"> -            </a> -        <?php endif; ?> -    <?php else: ?> -        <div class="media-object" style="width:64px;height:64px"></div> -    <?php endif; ?> -</div> -<div class="media-body"> -    <?php if ($logMessage->type === TopicLogMessage::LOCKED): ?> -        <div class="well icon-well text-info"> -            <span class="fa fa-lock text-info" aria-hidden="true"></span> -            <em><?= __("%user% locked this topic", [ -                "user" => $user, -            ]) ?></em> -            <br> -            <small class="_time"><?= $logMessage->postDate->format("c") ?></small> -        </div> -    <?php elseif ($logMessage->type === TopicLogMessage::UNLOCKED): ?> -        <div class="well icon-well text-success"> -            <span class="fa fa-unlock text-success" aria-hidden="true"></span> -            <em><?= __("%user% unlocked this topic", [ -                "user" => $user, -            ]) ?></em> -            <br> -            <small class="_time"><?= $logMessage->postDate->format("c") ?></small> -        </div> -    <?php elseif ($logMessage->type === TopicLogMessage::TITLE_CHANGED): ?> -        <div class="well icon-well text-info"> -            <span class="fa fa-pencil text-info" aria-hidden="true"></span> -            <em><?= __("%user% changed the title of this topic from %old_title% to %new_title%", [ -                "user" => $user, -                "old_title" => '<strong>' . htmlentities($logMessage->params["old_value"] ?? __("unknown")) . '</strong>', -                "new_title" => '<strong>' . htmlentities($logMessage->params["new_value"] ?? __("unknown")) . '</strong>', -            ]) ?></em> -            <br> -            <small class="_time"><?= $logMessage->postDate->format("c") ?></small> -        </div> -    <?php else: ?> -        <?= __("unknown") ?> -    <?php endif; ?> -</div> -</div> diff --git a/src/application/views/view_topics.php b/src/application/views/view_topics.php deleted file mode 100644 index 8a94775..0000000 --- a/src/application/views/view_topics.php +++ /dev/null @@ -1,24 +0,0 @@ -<?php - -use mystic\forum\orm\User; -use mystic\forum\orm\UserPermissions; - -if ($GLOBALS["currentUser"]?->hasPermission(UserPermissions::CREATE_OWN_TOPIC)): -?> -<p class="text-right"> -<a href="?_action=newtopic" class="btn btn-success"><span class="fa fa-plus" aria-hidden="true"></span> <?= __("New topic") ?></a> -</p> -<?php endif; ?> -<div class="list-group"> -<?php /** @var Topic $topic */ foreach ($topics as $topic): ?> -<a class="list-group-item" href="?_action=viewtopic&topic=<?= htmlentities(urlencode($topic->id)) ?>"> -    <h4 class="list-group-item-heading"> -        <?php if ($topic->isLocked): ?> -            <span class="fa fa-lock text-muted" aria-hidden="true"></span> -        <?php endif; ?> -        <?= htmlentities($topic->title) ?> -    </h4> -    <p class="list-group-item-text _time"><?= htmlentities($topic->creationDate->format("c")) ?></p> -</a> -<?php endforeach; ?> -</div> diff --git a/src/application/views/view_user.php b/src/application/views/view_user.php deleted file mode 100644 index e7c9753..0000000 --- a/src/application/views/view_user.php +++ /dev/null @@ -1,209 +0,0 @@ -<?php - -use mystic\forum\orm\UserPermissions; -use mystic\forum\utils\RequestUtils; -use mystic\forum\utils\StringUtils; - -/** @var mystic\forum\orm\User $user */ -/** @var bool $lastNameChangeTooRecent */ - -$canEdit = ($user->id === $GLOBALS["currentUser"]?->id && $user->hasPermission(UserPermissions::EDIT_OWN_USER)) -        || $GLOBALS["currentUser"]?->hasPermission(UserPermissions::EDIT_OTHER_USER); - -$isOwnProfile = $user->id === $GLOBALS["currentUser"]?->id; - -$sUserPossessive = ""; -if ($isOwnProfile) -    $sUserPossessive = "Your posts"; -else -    $sUserPossessive = "%display_name%'s posts"; - -$dateJoined = DateTime::createFromImmutable($user->created); -$dateJoined->setTime(0, 0, 0, 0); - -$emailPending = $isOwnProfile && $user->pendingEmail !== null; -?> - -<div class="clearfix page-header margin-top-0"> -    <img class="pull-left margin-right" src="?_action=profilepicture&user=<?= htmlentities(urlencode($user->id)) ?>" alt="<?= __("Profile picture") ?>" width="64" height="64"> -    <span class="h1"><?= htmlentities($user->displayName) ?></span> -    <?php if ($isOwnProfile): ?> -        <span class="label label-primary"><?= __("You") ?></span> -    <?php endif; ?><br> -    @<?= htmlentities($user->name) ?> • <span class="text-muted"><?= __("Member since %join_date%", [ -        "join_date" => '<span class="_date">' . htmlentities($dateJoined->format("c")) . '</span>', -    ]) ?></span> -</div> - -<?php if ($canEdit): ?> -<div class="row"> -<div class="col-md-9"> -<?php endif; ?> - -<h3><?= __($sUserPossessive, [ -    "display_name" => $user->displayName, -]) ?></h3> - -<?php if (count($posts) > 0): ?> -    <div class="post-container"> -        <div class="post-container-posts"> -            <div class="list-group margin-top"> -                <?php foreach ($posts as $post): -                    if ($post->deleted) continue; -                    $hasAttachments = count($attachments[$post->id]) > 0; -                ?> -                    <a href="?_action=viewtopic&topic=<?= htmlentities(urlencode($post->topicId)) ?>#post-<?= htmlentities(urlencode($post->id)) ?>" class="list-group-item"> -                        <?php if ($hasAttachments): ?> -                            <span class="badge"><span class="fa fa-paperclip"></span></span> -                        <?php endif; ?> -                        <?= renderPostSummary($post->content) ?><br> -                        <span class="text-muted"><?= __("posted on %post_date% in %topic%", [ -                            "post_date" => '<span class="_time">' . htmlentities($post->postDate->format("c")) . '</span>', -                            "topic" => '<em>' . -                                ($topics[$post->topicId]?->isLocked ? '<span class="fa fa-lock text-muted" aria-hidden="true"></span> ' : '') . -                                htmlentities($topics[$post->topicId]?->title ?? "unknown") . '</em>', -                        ]) ?></span> -                    </a> -                <?php endforeach; ?> -            </div> -        </div> -        <div class="post-container-controls"> -            <button class="btn btn-default"><?= __("Show all posts") ?></button> -        </div> -    </div> -<?php else: ?> -    <div class="well icon-well text-info margin-top margin-bottom"> -        <span class="fa fa-info-circle text-info" aria-hidden="true"></span> -        <em><?= __("This user has not posted anything yet") ?></em> -    </div> -<?php endif; ?> - -<?php if ($canEdit): ?> -</div> - -<div class="col-md-3"> -<h3><?= __("Edit profile") ?></h3> -<?php -if (($_formError = RequestUtils::getAndClearFormError("update_profile")) !== null) { -    _view("alert_error", ["message" => $_formError]); -} -?> -<form action="<?= htmlentities($_SERVER["REQUEST_URI"]) ?>" method="post" enctype="multipart/form-data"> -    <input type="hidden" name="form_id" value="update_profile"> -    <div class="form-group"> -        <label for="i_display_name"><?= __("Display name:") ?></label> -        <input required class="form-control" type="text" name="display_name" id="i_display_name" value="<?= htmlentities($user->displayName) ?>"> -    </div> -    <div class="form-group"> -        <label for="i_name"><?= __("Username:") ?></label> -        <?php if ($lastNameChangeTooRecent): ?> -            <input class="form-control" type="text" id="i_name" value="<?= htmlentities($user->name) ?>" disabled> -            <small class="text-danger"><strong><?= __("You can only change your username every 30 days!") ?></strong></small> -        <?php else: ?> -            <input required class="form-control" type="text" name="name" id="i_name" value="<?= htmlentities($user->name) ?>"> -        <?php endif; ?> -    </div> -    <div class="form-group"> -        <label for="i_email"><?= __("Email address:") ?></label> -        <?php if ($emailPending): ?> -            <input class="form-control" type="email" id="i_email" value="<?= htmlentities($user->email) ?>" disabled> -        <?php else: ?> -            <input required class="form-control" type="email" id="i_email" name="email" value="<?= htmlentities($user->email) ?>"> -        <?php endif; ?> -    </div> -    <div class="form-group"> -        <label><?= __("Profile picture:") ?></label> -<?php -$_checkbox_disabled = empty($user->profilePicture); -$_checkbox_disabled_class = $_checkbox_disabled ? " disabled text-muted" : ""; -?> -        <div class="radio margin-top-0 <?= $_checkbox_disabled_class ?>"> -            <label> -                <input type="radio" name="pfp_action" id="pfp_action_1" value="keep"<?= !empty($user->profilePicture) ? ' checked' : ' disabled' ?>> -                <?= __("Keep current profile picture") ?> -            </label> -        </div> -        <div class="radio"> -            <label> -                <input type="radio" name="pfp_action" id="pfp_action_2" value="remove"<?= empty($user->profilePicture) ? ' checked' : '' ?>> -                <?php if (empty($user->profilePicture)): ?> -                    <?= __("No profile picture") ?> -                <?php else: ?> -                    <?= __("Remove profile picture") ?> -                <?php endif; ?> -            </label> -        </div> -        <div class="radio"> -            <label> -                <input type="radio" name="pfp_action" value="replace" id="pfp_action_3"> -                <?= __("Upload new profile picture") ?> -            </label> -        </div> -        <input type="file" name="pfp" id="i_pfp" accept="image/png,image/jpeg" class="margin-left-3x small"> -    </div> -    <div class="form-group"> -        <button type="submit" class="btn btn-success"><?= __("Save changes") ?></button> -    </div> -</form> -<?php if ($isOwnProfile): ?> -<h3><?= __("Change password") ?></h3> -<?php -if (($_formError = RequestUtils::getAndClearFormError("update_password")) !== null) { -    _view("alert_error", ["message" => $_formError]); -} -?> -<form action="<?= htmlentities($_SERVER["REQUEST_URI"]) ?>" method="post"> -    <input type="hidden" name="form_id" value="update_password"> -    <div class="form-group"> -        <label for="i_current_password"><?= __("Current password:") ?></label> -        <input autocomplete="current-password" required class="form-control" type="password" name="current_password" id="i_current_password" required> -    </div> -    <div class="form-group"> -        <label for="i_new_password"><?= __("New password:") ?></label> -        <input autocomplete="new-password" required class="form-control" type="password" name="new_password" id="i_new_password" required> -    </div> -    <div class="form-group"> -        <label for="i_retype_password"><?= __("Retype password:") ?></label> -        <input autocomplete="new-password" required class="form-control" type="password" name="retype_password" id="i_retype_password" required> -    </div> -    <div class="form-group"> -        <button type="submit" class="btn btn-success"><?= __("Change password") ?></button> -    </div> -</form> -<?php endif; ?> - -</div> - -</div> -<?php endif; ?> - -<script> -$(function() { -    $(".post-container").each(function(i, e) { -        if ($(e).height() > 900) { // more than 800 so it doesn't collapse just a few pixels -            $(e).addClass("collapsed"); -        } -        $(e).find(".post-container-controls button").click(function() { -            $(e).removeClass("collapsed"); -        }); -    }); -}); -<?php if ($canEdit): ?> -$(function() { -    function _hide() { -        $("#i_pfp").hide().prop("disabled", true).prop("required", false); -        $("#i_pfp + .file-input-group").hide().find("button").prop("disabled", true); -    } -    _hide(); -    setTimeout(_hide, 10); -    $("[name='pfp_action']").on("change input check click", function() { -        if ($("#pfp_action_3").is(":checked")) { -            $("#i_pfp").show().prop("disabled", false).prop("required", true); -            $("#i_pfp + .file-input-group").show().find("button").prop("disabled", false); -        } else { -            _hide(); -        } -    }) -}); -<?php endif; ?> -</script> diff --git a/src/composer.json b/src/composer.json index 630c8ac..d532fc8 100644 --- a/src/composer.json +++ b/src/composer.json @@ -8,6 +8,7 @@          "symfony/mailer": "^7.1",          "gregwar/captcha": "^1.2",          "andig/php-shunting-yard": "dev-master", -        "php-ffmpeg/php-ffmpeg": "^1.2" +        "php-ffmpeg/php-ffmpeg": "^1.2", +        "twig/twig": "^3.14"      }  } diff --git a/src/composer.lock b/src/composer.lock index c1405cf..ca42d3b 100644 --- a/src/composer.lock +++ b/src/composer.lock @@ -4,7 +4,7 @@          "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",          "This file is @generated automatically"      ], -    "content-hash": "51563e81645c915860c5db7676154cdc", +    "content-hash": "050fa6cca991c3f783a7a4ff541d1690",      "packages": [          {              "name": "andig/php-shunting-yard", @@ -1275,6 +1275,85 @@              "time": "2024-08-13T14:28:19+00:00"          },          { +            "name": "symfony/polyfill-ctype", +            "version": "v1.31.0", +            "source": { +                "type": "git", +                "url": "https://github.com/symfony/polyfill-ctype.git", +                "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" +            }, +            "dist": { +                "type": "zip", +                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", +                "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", +                "shasum": "" +            }, +            "require": { +                "php": ">=7.2" +            }, +            "provide": { +                "ext-ctype": "*" +            }, +            "suggest": { +                "ext-ctype": "For best performance" +            }, +            "type": "library", +            "extra": { +                "thanks": { +                    "name": "symfony/polyfill", +                    "url": "https://github.com/symfony/polyfill" +                } +            }, +            "autoload": { +                "files": [ +                    "bootstrap.php" +                ], +                "psr-4": { +                    "Symfony\\Polyfill\\Ctype\\": "" +                } +            }, +            "notification-url": "https://packagist.org/downloads/", +            "license": [ +                "MIT" +            ], +            "authors": [ +                { +                    "name": "Gert de Pagter", +                    "email": "BackEndTea@gmail.com" +                }, +                { +                    "name": "Symfony Community", +                    "homepage": "https://symfony.com/contributors" +                } +            ], +            "description": "Symfony polyfill for ctype functions", +            "homepage": "https://symfony.com", +            "keywords": [ +                "compatibility", +                "ctype", +                "polyfill", +                "portable" +            ], +            "support": { +                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" +            }, +            "funding": [ +                { +                    "url": "https://symfony.com/sponsor", +                    "type": "custom" +                }, +                { +                    "url": "https://github.com/fabpot", +                    "type": "github" +                }, +                { +                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", +                    "type": "tidelift" +                } +            ], +            "time": "2024-09-09T11:45:10+00:00" +        }, +        {              "name": "symfony/polyfill-intl-idn",              "version": "v1.31.0",              "source": { @@ -1519,6 +1598,82 @@              "time": "2024-09-09T11:45:10+00:00"          },          { +            "name": "symfony/polyfill-php81", +            "version": "v1.31.0", +            "source": { +                "type": "git", +                "url": "https://github.com/symfony/polyfill-php81.git", +                "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" +            }, +            "dist": { +                "type": "zip", +                "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", +                "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", +                "shasum": "" +            }, +            "require": { +                "php": ">=7.2" +            }, +            "type": "library", +            "extra": { +                "thanks": { +                    "name": "symfony/polyfill", +                    "url": "https://github.com/symfony/polyfill" +                } +            }, +            "autoload": { +                "files": [ +                    "bootstrap.php" +                ], +                "psr-4": { +                    "Symfony\\Polyfill\\Php81\\": "" +                }, +                "classmap": [ +                    "Resources/stubs" +                ] +            }, +            "notification-url": "https://packagist.org/downloads/", +            "license": [ +                "MIT" +            ], +            "authors": [ +                { +                    "name": "Nicolas Grekas", +                    "email": "p@tchwork.com" +                }, +                { +                    "name": "Symfony Community", +                    "homepage": "https://symfony.com/contributors" +                } +            ], +            "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", +            "homepage": "https://symfony.com", +            "keywords": [ +                "compatibility", +                "polyfill", +                "portable", +                "shim" +            ], +            "support": { +                "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" +            }, +            "funding": [ +                { +                    "url": "https://symfony.com/sponsor", +                    "type": "custom" +                }, +                { +                    "url": "https://github.com/fabpot", +                    "type": "github" +                }, +                { +                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", +                    "type": "tidelift" +                } +            ], +            "time": "2024-09-09T11:45:10+00:00" +        }, +        {              "name": "symfony/process",              "version": "v7.1.3",              "source": { @@ -1737,6 +1892,85 @@                  }              ],              "time": "2024-06-28T08:00:31+00:00" +        }, +        { +            "name": "twig/twig", +            "version": "v3.14.0", +            "source": { +                "type": "git", +                "url": "https://github.com/twigphp/Twig.git", +                "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72" +            }, +            "dist": { +                "type": "zip", +                "url": "https://api.github.com/repos/twigphp/Twig/zipball/126b2c97818dbff0cdf3fbfc881aedb3d40aae72", +                "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72", +                "shasum": "" +            }, +            "require": { +                "php": ">=8.0.2", +                "symfony/deprecation-contracts": "^2.5|^3", +                "symfony/polyfill-ctype": "^1.8", +                "symfony/polyfill-mbstring": "^1.3", +                "symfony/polyfill-php81": "^1.29" +            }, +            "require-dev": { +                "psr/container": "^1.0|^2.0", +                "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" +            }, +            "type": "library", +            "autoload": { +                "files": [ +                    "src/Resources/core.php", +                    "src/Resources/debug.php", +                    "src/Resources/escaper.php", +                    "src/Resources/string_loader.php" +                ], +                "psr-4": { +                    "Twig\\": "src/" +                } +            }, +            "notification-url": "https://packagist.org/downloads/", +            "license": [ +                "BSD-3-Clause" +            ], +            "authors": [ +                { +                    "name": "Fabien Potencier", +                    "email": "fabien@symfony.com", +                    "homepage": "http://fabien.potencier.org", +                    "role": "Lead Developer" +                }, +                { +                    "name": "Twig Team", +                    "role": "Contributors" +                }, +                { +                    "name": "Armin Ronacher", +                    "email": "armin.ronacher@active-4.com", +                    "role": "Project Founder" +                } +            ], +            "description": "Twig, the flexible, fast, and secure template language for PHP", +            "homepage": "https://twig.symfony.com", +            "keywords": [ +                "templating" +            ], +            "support": { +                "issues": "https://github.com/twigphp/Twig/issues", +                "source": "https://github.com/twigphp/Twig/tree/v3.14.0" +            }, +            "funding": [ +                { +                    "url": "https://github.com/fabpot", +                    "type": "github" +                }, +                { +                    "url": "https://tidelift.com/funding/github/packagist/twig/twig", +                    "type": "tidelift" +                } +            ], +            "time": "2024-09-09T17:55:12+00:00"          }      ],      "packages-dev": [], diff --git a/src/index.php b/src/index.php index 0c5cc3b..2403a9b 100644 --- a/src/index.php +++ b/src/index.php @@ -8,8 +8,13 @@ use mystic\forum\orm\Post;  use mystic\forum\orm\Topic;  use mystic\forum\orm\TopicLogMessage;  use mystic\forum\orm\User; +use mystic\forum\orm\UserPermissions;  use mystic\forum\utils\RequestUtils;  use mystic\forum\utils\StringUtils; +use Twig\Environment; +use Twig\Loader\FilesystemLoader; +use Twig\TwigFilter; +use Twig\TwigFunction;  header_remove("X-Powered-By"); @@ -39,23 +44,17 @@ $_action = $_GET["_action"] ?? null;  $GLOBALS["action"] = $_action;  function msg_error(string $err, bool $skipLoginCheck = false): void { -    _view("template_start", ["_title" => __("Error")]); -    _view("template_navigation_start"); -    if (!$skipLoginCheck) -        _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($GLOBALS["db"])]); -    _view("template_navigation_end"); -    _view("alert_error", ["message" => $err]); -    _view("template_end", [...getThemeAndLangInfo()]); +    render("error_page.twig", [ +        "message" => $err, +        "skipLoginCheck" => $skipLoginCheck, +    ]);  }  function msg_info(string $msg, bool $skipLoginCheck = false): void { -    _view("template_start", ["_title" => __("Information")]); -    _view("template_navigation_start"); -    if (!$skipLoginCheck) -        _view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($GLOBALS["db"])]); -    _view("template_navigation_end"); -    _view("alert_info", ["message" => $msg]); -    _view("template_end", [...getThemeAndLangInfo()]); +    render("info_page.twig", [ +        "message" => $msg, +        "skipLoginCheck" => $skipLoginCheck, +    ]);  }  function generateCaptchaText(): string { @@ -129,20 +128,6 @@ function getThemeAndLangInfo(): array {      ];  } -function _view(string $___NAME, array $___PARAMS = []): void { -    $___PATH = __DIR__ . "/application/views/" . $___NAME . ".php"; -    if (!is_file($___PATH)) { -        echo "<!--!ERROR Failed to include {" . htmlentities($___NAME) . "}-->\n"; -        echo "<br><p><strong style=\"color:red !important\">Failed to include <em>" . htmlentities($___NAME) . "</em></strong></p><br>\n"; -        echo "<!--/ERROR-->\n"; -    } else { -        extract($___PARAMS); -        echo "<!--{" . htmlentities($___NAME) . "}-->\n"; -        include $___PATH; -        echo "<!--{/" . htmlentities($___NAME) . "}-->\n"; -    } -} -  function isTrue(string $str): bool {      $str = strtolower($str);      return in_array($str, ["yes","true","y","t","on","enabled","1","?1"]); @@ -164,6 +149,69 @@ function reArrayFiles(&$file_post) {      return $file_ary;  } +function render(string $page, array $context = []): void { +    $defaultTemplate = "bootstrap-3"; // bootstrap-3 is the default for backwards compatibility +    $templateDir = __ROOT__ . "/application/templates"; + +    [ +        "availableThemes" => $availableThemes, +        "currentTheme" => $currentTheme, +    ] = getThemeAndLangInfo(); + +    $currentThemeInfo = $availableThemes[$currentTheme] ?? (object)[]; +    $currentTemplate = $currentThemeInfo->template ?? $defaultTemplate; + +    $availableTemplates = array_values(array_filter(scandir($templateDir), function($theme) use ($templateDir) { +        return $theme[0] !== "." && is_dir($templateDir . "/" . $theme); +    })); + +    if (!in_array($currentTemplate, $availableTemplates)) +        $currentTemplate = $defaultTemplate; + +    $loader = new FilesystemLoader([ +        $templateDir . "/" . $currentTemplate, +    ]); +    $twig = new Environment($loader, [ +        // TODO Enable caching +    ]); + +    $twig->addFunction(new TwigFunction("__", __(...), [ "is_safe" => ["html"] ])); +    $twig->addFunction(new TwigFunction("___", ___(...), [ "is_safe" => ["html"] ])); +    $twig->addFunction(new TwigFunction("renderPost", renderPost(...), [ "is_safe" => ["html"] ])); +    $twig->addFunction(new TwigFunction("renderPostSummary", renderPostSummary(...), [ "is_safe" => ["html"] ])); +    $twig->addFunction(new TwigFunction("permission", fn(string $name): int => constant(UserPermissions::class . "::" . $name))); +    $twig->addFunction(new TwigFunction("getAndClearFormError", RequestUtils::getAndClearFormError(...))); +    $twig->addFunction(new TwigFunction("lastFormField", function(string $formId, string $field): ?string { +        $lastFormId = ""; +        $lastForm = RequestUtils::getLastForm($lastFormId) ?? null; +        if ($lastForm === null || $lastFormId !== $formId) +            return null; +        return $lastForm[$field] ?? null; +    })); + +    $twig->addFilter(new TwigFilter("hash", fn(string $data, string $algo, bool $binary = false, array $options = []) => hash($algo, $data, $binary, $options))); +    $twig->addFilter(new TwigFilter("base64_encode", base64_encode(...))); +    $twig->addFilter(new TwigFilter("base64_decode", base64_decode(...))); + +    echo $twig->render($page, [ +        "g" => [ +            "get" => &$_GET, +            "post" => &$_POST, +            "request" => &$_REQUEST, +            "session" => &$_SESSION, +            "cookie" => &$_COOKIE, +            "env" => &$_ENV, +            "server" => &$_SERVER, +            "globals" => $GLOBALS, +        ], +        "ctx" => &$context, + +        "currentUser" => &$GLOBALS["currentUser"], +        ...getThemeAndLangInfo(), +    ]); +    RequestUtils::clearLastForm(); +} +  const TAGS_REGEX = '/\[(b|i|u|s|\\^|_|spoiler)\](.+?)\[\/\1\]/s';  function expandTags(string $contents): string { |