summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--inc/config.php2
-rw-r--r--inc/mod/pages.php106
-rw-r--r--mod.php3
-rw-r--r--templates/mod/dashboard.html11
-rw-r--r--templates/mod/search_form.html15
-rw-r--r--templates/mod/search_results.html129
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;
diff --git a/mod.php b/mod.php
index 42196ef2..2523658c 100644
--- a/mod.php
+++ b/mod.php
@@ -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