diff options
| -rw-r--r-- | src/application/i18n.php | 2 | ||||
| -rw-r--r-- | src/application/messages/de.msg | 20 | ||||
| -rw-r--r-- | src/application/mystic/forum/Database.php | 27 | ||||
| -rw-r--r-- | src/application/views/form_search.php | 28 | ||||
| -rw-r--r-- | src/application/views/nav_guest.php | 1 | ||||
| -rw-r--r-- | src/application/views/nav_logged_in.php | 1 | ||||
| -rw-r--r-- | src/application/views/view_search_results.php | 33 | ||||
| -rw-r--r-- | src/index.php | 59 | 
8 files changed, 170 insertions, 1 deletions
| diff --git a/src/application/i18n.php b/src/application/i18n.php index a1f2f24..a7bdd82 100644 --- a/src/application/i18n.php +++ b/src/application/i18n.php @@ -173,7 +173,7 @@ function i18n_get(string $msgid, array $params = [], ?string $context = null): s      if ($context !== null)          $key = $context . "\x7F" . $msgid; -    $msg = ($__i18n_msg_store[$__i18n_current_locale] ?? [])[$msgid] ?? $key; +    $msg = ($__i18n_msg_store[$__i18n_current_locale] ?? [])[$key] ?? $msgid;      uksort($params, fn(string $a, string $b): int => strlen($b) <=> strlen($a));      return str_replace( diff --git a/src/application/messages/de.msg b/src/application/messages/de.msg index ed8178e..784c257 100644 --- a/src/application/messages/de.msg +++ b/src/application/messages/de.msg @@ -5,6 +5,14 @@ metadata({    }  }) +: "." +? "Number formatting" += "," + +: "," +? "Number formatting" += "." +  : "Log in"  = "Anmelden" @@ -318,3 +326,15 @@ metadata({  : "Language:"  = "Sprache:" + +: "Search" += "Suche" + +: "Search results for “%query%”" += "Suchergebnisse für „%query%“" + +: "No results for this search" += "Keine Ergebnisse für diese Suche" + +: "%result_count% result(s) in %search_duration% second(s)" += "%result_count% Treffer in %search_duration% Sekunde(n)" diff --git a/src/application/mystic/forum/Database.php b/src/application/mystic/forum/Database.php index a1e67ea..8ebc36b 100644 --- a/src/application/mystic/forum/Database.php +++ b/src/application/mystic/forum/Database.php @@ -414,6 +414,33 @@ class Database {          return $items;      } +    public function execCustomQuery(string $query, ?array $customQueryParams = null, ?string $entityClassName = null): array { +        if ($customQueryParams === null) +            $result = \pg_query($this->connection, $query); +        else +            $result = \pg_query_params($this->connection, $query, $customQueryParams); +        if ($result === false) +            throw new \RuntimeException("Query failed: " . \pg_last_error($this->connection)); +        $cols = null; +        if ($entityClassName !== null) { +            $reflClass = new ReflectionClass($entityClassName); +            $cols = self::getColumns($reflClass); +        } +        $rowsOrItems = []; +        while (($row = \pg_fetch_assoc($result)) !== false) { +            if ($entityClassName !== null) { +                $entity = new $entityClassName(); +                foreach ($cols as $colName => $colProps) +                    self::assignValue($entity, $colProps, $row[$colName]); +                $rowsOrItems []= $entity; +            } else { +                $rowsOrItems []= $row; +            } +        } +        \pg_free_result($result); +        return $rowsOrItems; +    } +      public function delete(Entity &$entity): bool {          $entityClassName = get_class($entity);          $tableName = self::getTableName($entityClassName); diff --git a/src/application/views/form_search.php b/src/application/views/form_search.php new file mode 100644 index 0000000..edc68b8 --- /dev/null +++ b/src/application/views/form_search.php @@ -0,0 +1,28 @@ +<?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()) !== null) { +    _view("alert_error", ["message" => $_formError]); +} +?> +<form action="<?= htmlentities($_SERVER["REQUEST_URI"]) ?>" method="post"> +    <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> +            <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 index 1c65a50..88c551b 100644 --- a/src/application/views/nav_guest.php +++ b/src/application/views/nav_guest.php @@ -1,4 +1,5 @@  <ul class="nav navbar-nav navbar-right"> +<li<?= $GLOBALS["action"] === "search" ? ' class="active"' : '' ?>><a href="?_action=search"><span class="glyphicon glyphicon-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($_SERVER["REQUEST_URI"])) ?>"><?= __("Log in") ?></a></li>  <?php if (REGISTRATION_ENABLED): ?>  <li<?= $GLOBALS["action"] === "register" ? ' class="active"' : '' ?>><a href="?_action=register&next=<?= htmlentities(urlencode($_SERVER["REQUEST_URI"])) ?>"><?= __("Register") ?></a></li> diff --git a/src/application/views/nav_logged_in.php b/src/application/views/nav_logged_in.php index 39c65cb..0f77f90 100644 --- a/src/application/views/nav_logged_in.php +++ b/src/application/views/nav_logged_in.php @@ -6,6 +6,7 @@ use mystic\forum\orm\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="glyphicon glyphicon-search" aria-hidden="true"></span><span class="sr-only"><?= __("Search") ?></span></a></li>  <li><a href="?_action=viewuser&user=<?= htmlentities(urlencode($user->id)) ?>"><span class="glyphicon glyphicon-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="glyphicon glyphicon-log-out" aria-hidden="true"></span><span class="sr-only">Log out</span></a></li>  </ul> diff --git a/src/application/views/view_search_results.php b/src/application/views/view_search_results.php new file mode 100644 index 0000000..19a6978 --- /dev/null +++ b/src/application/views/view_search_results.php @@ -0,0 +1,33 @@ +<?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="glyphicon glyphicon-paperclip"></span></span> +                <?php endif; ?> +                <?= htmlentities(StringUtils::truncate(strip_tags(renderPost($post->content)), 100)) ?><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>' . 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="glyphicon glyphicon-info-sign color-info" aria-hidden="true"></span> +        <em><?= __("No results for this search") ?></em> +    </div> +<?php endif; ?> diff --git a/src/index.php b/src/index.php index 1b13e97..f3a59b2 100644 --- a/src/index.php +++ b/src/index.php @@ -1139,6 +1139,65 @@ if ($_action === "auth") {      }      header("Location: ./?_action=viewtopic&topic=" . urlencode($topicId)); +} elseif ($_action === "search") { +    if (RequestUtils::isRequestMethod("POST")) { +        $query = RequestUtils::getRequiredField("query"); +        /** @var Post[] $posts */ + +        $start_time = microtime(true); +        $posts = $db->execCustomQuery(<<<SQL +            SELECT posts.* FROM topics, posts +            WHERE +                NOT posts.deleted +                AND to_tsvector('english', topics.title || ' ' || posts.content) @@ websearch_to_tsquery('english', $1) +            ORDER BY posts.post_date DESC +            ; +        SQL, [ $query ], Post::class); + +        $topicLookup = []; +        $attachmentLookup = []; +        $userLookup = []; +        foreach ($posts as $post) { +            if (!isset($topicLookup[$post->topicId])) { +                $topic = new Topic; +                $topic->id = $post->topicId; +                if ($db->fetch($topic)) +                    $topicLookup[$topic->id] = &$topic; +            } +            if (!isset($attachmentLookup[$post->id])) { +                $attachmentLookup[$post->id] = $db->fetchCustom(Attachment::class, 'WHERE post_id = $1', [ $post->id ]); +            } +            if (!isset($userLookup[$post->authorId])) { +                $user = new User; +                $user->id = $post->authorId; +                if ($db->fetch($user)) +                    $userLookup[$post->authorId] = &$user; +            } +        } +        $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", [ +            "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()]); +    }  } elseif ($_action === "captcha") {      $phrase = generateCaptchaText();      $builder = new CaptchaBuilder($phrase); |