diff options
| author | Michael Foster <savetheinternet@tinyboard.org> | 2013-07-20 07:50:33 -0400 |
|---|---|---|
| committer | Michael Foster <savetheinternet@tinyboard.org> | 2013-07-20 07:50:33 -0400 |
| commit | e62b83e79c8ed3b9ddf6e7e4b8bdeedc0d319ff4 (patch) | |
| tree | 2cefd703a26f7de18782f3b0f7ba90230ff4cc30 | |
| parent | 158a7c0e79a7d0e8000e8797a277c3fdbf463e0d (diff) | |
Bring back search (searching posts not implemented yet)
| -rw-r--r-- | inc/config.php | 2 | ||||
| -rw-r--r-- | inc/mod/pages.php | 106 | ||||
| -rw-r--r-- | mod.php | 3 | ||||
| -rw-r--r-- | templates/mod/dashboard.html | 11 | ||||
| -rw-r--r-- | templates/mod/search_form.html | 15 | ||||
| -rw-r--r-- | templates/mod/search_results.html | 129 |
6 files changed, 257 insertions, 9 deletions
diff --git a/inc/config.php b/inc/config.php index b7f6d220..9706178c 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1015,7 +1015,7 @@ $config['mod']['master_pm'] = ADMIN; // Rebuild everything $config['mod']['rebuild'] = ADMIN; - // Search through posts + // Search through posts, IP address notes and bans $config['mod']['search'] = JANITOR; // Read the moderator noticeboard $config['mod']['noticeboard'] = JANITOR; diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 3f823d3d..fc5577bc 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -162,6 +162,112 @@ function mod_dashboard() { mod_page(_('Dashboard'), 'mod/dashboard.html', $args); } +function mod_search_redirect() { + global $config; + + if (!hasPermission($config['mod']['search'])) + error($config['error']['noaccess']); + + if (isset($_POST['query'], $_POST['type']) && in_array($_POST['type'], array('posts', 'IP_notes', 'bans'))) { + $query = $_POST['query']; + $query = urlencode($query); + $query = str_replace('_', '%5F', $query); + $query = str_replace('+', '_', $query); + + header('Location: ?/search/' . $_POST['type'] . '/' . $query, true, $config['redirect_http']); + } else { + header('Location: ?/', true, $config['redirect_http']); + } +} + +function mod_search($type, $query) { + global $pdo, $config; + + if (!hasPermission($config['mod']['search'])) + error($config['error']['noaccess']); + + // Unescape query + $query = str_replace('_', ' ', $query); + $query = urldecode($query); + $search_query = $query; + + // Form a series of LIKE clauses for the query. + // This gets a little complicated. + + // Escape "escape" character + $query = str_replace('!', '!!', $query); + + // Escape SQL wildcard + $query = str_replace('%', '!%', $query); + + // Use asterisk as wildcard instead + $query = str_replace('*', '%', $query); + + // Array of phrases to match + $match = array(); + + // Exact phrases ("like this") + if (preg_match_all('/"(.+?)"/', $query, $matches)) { + foreach ($matches[1] as $phrase) { + $query = str_replace("\"{$phrase}\"", '', $query); + $match[] = $pdo->quote($phrase); + } + } + + // Non-exact phrases (ie. plain keywords) + $keywords = explode(' ', $query); + foreach ($keywords as $word) { + if (empty($word)) + continue; + $match[] = $pdo->quote($word); + } + + // Which `field` to search? + if ($type == 'posts') + $sql_field = 'body'; + if ($type == 'IP_notes') + $sql_field = 'body'; + if ($type == 'bans') + $sql_field = 'reason'; + + // Build the "LIKE 'this' AND LIKE 'that'" etc. part of the SQL query + $sql_like = ''; + foreach ($match as $phrase) { + if (!empty($sql_like)) + $sql_like .= ' AND '; + $phrase = preg_replace('/^\'(.+)\'$/', '\'%$1%\'', $phrase); + $sql_like .= '`' . $sql_field . '` LIKE ' . $phrase . ' ESCAPE \'!\''; + } + + if ($type == 'posts') { + error('Searching posts is under development. Sorry.'); + } + + if ($type == 'IP_notes') { + $query = query('SELECT * FROM `ip_notes` LEFT JOIN `mods` ON `mod` = `mods`.`id` WHERE ' . $sql_like . ' ORDER BY `time` DESC') or error(db_error()); + $results = $query->fetchAll(PDO::FETCH_ASSOC); + } + + if ($type == 'bans') { + $query = query('SELECT `bans`.*, `username` FROM `bans` LEFT JOIN `mods` ON `mod` = `mods`.`id` WHERE ' . $sql_like . ' ORDER BY (`expires` IS NOT NULL AND `expires` < UNIX_TIMESTAMP()), `set`') or error(db_error()); + $results = $query->fetchAll(PDO::FETCH_ASSOC); + + foreach ($results as &$ban) { + if (filter_var($ban['ip'], FILTER_VALIDATE_IP) !== false) + $ban['real_ip'] = true; + } + } + + // $results now contains the search results + + mod_page(_('Search results'), 'mod/search_results.html', array( + 'search_type' => $type, + 'search_query' => $search_query, + 'result_count' => count($results), + 'results' => $results + )); +} + function mod_edit_board($boardName) { global $board, $config; @@ -56,6 +56,9 @@ $pages = array( '/IP/([\w.:]+)/remove_note/(\d+)' => 'ip_remove_note', // remove note from ip address '/bans' => 'bans', // ban list '/bans/(\d+)' => 'bans', // ban list + + '/search' => 'search_redirect', // search + '/search/(posts|IP_notes|bans)/(.+)' => 'search', // search // CSRF-protected moderator actions '/ban' => 'secure_POST ban', // new ban diff --git a/templates/mod/dashboard.html b/templates/mod/dashboard.html index e54b3cd3..619663f5 100644 --- a/templates/mod/dashboard.html +++ b/templates/mod/dashboard.html @@ -101,22 +101,17 @@ </ul> </fieldset> -{# +{% if mod|hasPermission(config.mod.search) %} <fieldset> <legend>{% trans 'Search' %}</legend> <ul> <li> - <form style="display:inline" action="?/search" method="post"> - <label style="display:inline" for="search">{% trans 'Phrase:' %}</label> - <input id="search" name="search" type="text" size="35"> - <input type="submit" value="{% trans 'Search' %}"> - </form> - <p class="unimportant">{% trans '(Search is case-insensitive, and based on keywords. To match exact phrases, use "quotes". Use an asterisk (*) for wildcard.)' %}</p> + {% include 'mod/search_form.html' %} </li> </ul> </fieldset> -#} +{% endif %} {% if config.debug %} <fieldset> diff --git a/templates/mod/search_form.html b/templates/mod/search_form.html new file mode 100644 index 00000000..c74121fd --- /dev/null +++ b/templates/mod/search_form.html @@ -0,0 +1,15 @@ +<form style="display:inline" action="?/search" method="post"> + <label style="display:inline" for="search">{% trans 'Phrase:' %}</label> + <input id="search" name="query" type="text" size="60" value="{{ search_query|e }}"> + <select name="type"> + <option value="posts"{% if search_type == 'posts'%} selected{% endif %}>Posts</option> + {% if mod|hasPermission(config.mod.view_notes) and mod|hasPermission(config.mod.show_ip) %} + <option value="IP_notes"{% if search_type == 'IP_notes'%} selected{% endif %}>IP address notes</option> + {% endif %} + {% if mod|hasPermission(config.mod.view_banlist) %} + <option value="bans"{% if search_type == 'bans'%} selected{% endif %}>Bans</option> + {% endif %} + </select> + <input type="submit" value="{% trans 'Search' %}"> +</form> +<p class="unimportant">{% trans '(Search is case-insensitive and based on keywords. To match exact phrases, use "quotes". Use an asterisk (*) for wildcard.)' %}</p>
\ No newline at end of file diff --git a/templates/mod/search_results.html b/templates/mod/search_results.html new file mode 100644 index 00000000..84de8969 --- /dev/null +++ b/templates/mod/search_results.html @@ -0,0 +1,129 @@ +<fieldset style="margin-bottom:20px"> + <legend>{% trans 'Search' %}</legend> + + <ul> + <li> + {% include 'mod/search_form.html' %} + </li> + </ul> +</fieldset> + +<p style="text-align:center">Showing {{ result_count }} result{% if result_count != 1 %}s{% endif %}.</p> + +{% if search_type == 'IP_notes' %} + <table class="modlog"> + <tr> + <th>{% trans 'IP address' %}</th> + <th>{% trans 'Staff' %}</th> + <th>{% trans 'Note' %}</th> + <th>{% trans 'Date' %}</th> + </tr> + {% for note in results %} + <tr> + <td class="minimal"> + <a href="?/IP/{{ note.ip }}#notes">{{ note.ip }}</a> + </td> + <td class="minimal"> + {% if note.username %} + <a href="?/new_PM/{{ note.username|e }}">{{ note.username|e }}</a> + {% else %} + <em>{% trans 'deleted?' %}</em> + {% endif %} + </td> + <td> + {{ note.body }} + </td> + <td class="minimal"> + {{ note.time|date(config.post_date) }} + </td> + </tr> + {% endfor %} + </table> +{% endif %} + + +{% if search_type == 'bans' %} + <table class="modlog" style="width:100%"> + <tr> + <th>{% trans 'IP address/mask' %}</th> + <th>{% trans 'Reason' %}</th> + <th>{% trans 'Board' %}</th> + <th>{% trans 'Set' %}</th> + <th>{% trans 'Duration' %}</th> + <th>{% trans 'Expires' %}</th> + <th>{% trans 'Seen' %}</th> + <th>{% trans 'Staff' %}</th> + </tr> + {% for ban in results %} + <tr{% if ban.expires != 0 and ban.expires < time() %} style="text-decoration:line-through"{% endif %}> + <td style="white-space: nowrap"> + {% if ban.real_ip %} + <a href="?/IP/{{ ban.ip }}#bans">{{ ban.ip }}</a> + {% else %} + {{ ban.ip|e }} + {% endif %} + </td> + <td> + {% if ban.reason %} + {{ ban.reason }} + {% else %} + - + {% endif %} + </td> + <td style="white-space: nowrap"> + {% if ban.board %} + {{ config.board_abbreviation|sprintf(ban.board) }} + {% else %} + <em>{% trans 'all boards' %}</em> + {% endif %} + </td> + <td style="white-space: nowrap"> + <span title="{{ ban.set|date(config.post_date) }}"> + {{ ban.set|ago }} ago + </span> + </td> + <td style="white-space: nowrap"> + {% if ban.expires == 0 %} + - + {% else %} + {{ (ban.expires - ban.set + time()) | until }} + {% endif %} + </td> + <td style="white-space: nowrap"> + {% if ban.expires == 0 %} + <em>{% trans 'never' %}</em> + {% else %} + {{ ban.expires|date(config.post_date) }} + {% if ban.expires > time() %} + <small>(in {{ ban.expires|until }})</small> + {% endif %} + {% endif %} + </td> + <td> + {% if ban.seen %} + {% trans 'Yes' %} + {% else %} + {% trans 'No' %} + {% endif %} + </td> + <td> + {% if ban.username %} + {% if mod|hasPermission(config.mod.view_banstaff) %} + <a href="?/new_PM/{{ ban.username|e }}">{{ ban.username|e }}</a> + {% else %} + {% if mod|hasPermission(config.mod.view_banquestionmark) %} + <em>?</em> + {% else %} + + {% endif %} + {% endif %} + {% elseif ban.mod == -1 %} + <em>system</em> + {% else %} + <em>{% trans 'deleted?' %}</em> + {% endif %} + </td> + </tr> + {% endfor %} + </table> +{% endif %}
\ No newline at end of file |
