mirror of
https://github.com/vichan-devel/vichan.git
synced 2024-11-25 07:50:23 +01:00
Merge with a private vichan branch
Conflicts: inc/bans.php inc/mod/pages.php
This commit is contained in:
commit
643de18d64
108
inc/bans.php
108
inc/bans.php
@ -153,57 +153,86 @@ class Bans {
|
|||||||
|
|
||||||
return $ban_list;
|
return $ban_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function list_all($offset = 0, $limit = 9001, $board = false) {
|
|
||||||
$offset = (int)$offset;
|
|
||||||
$limit = (int)$limit;
|
|
||||||
|
|
||||||
$query = prepare("SELECT ``bans``.*, `username` FROM ``bans``
|
|
||||||
LEFT JOIN ``mods`` ON ``mods``.`id` = `creator`" . ($board ? ' WHERE ``bans``.`board` = :board' : '') . "
|
|
||||||
ORDER BY `created` DESC LIMIT $offset, $limit");
|
|
||||||
if ($board)
|
|
||||||
$query->bindValue(':board', $board);
|
|
||||||
|
|
||||||
$query->execute() or error(db_error());
|
static public function stream_json($out = false, $filter_ips = false, $filter_staff = false, $board_access = false) {
|
||||||
$bans = $query->fetchAll(PDO::FETCH_ASSOC);
|
$query = query("SELECT ``bans``.*, `username` FROM ``bans``
|
||||||
|
LEFT JOIN ``mods`` ON ``mods``.`id` = `creator`
|
||||||
foreach ($bans as &$ban) {
|
ORDER BY `created` DESC") or error(db_error());
|
||||||
$ban['mask'] = self::range_to_string(array($ban['ipstart'], $ban['ipend']));
|
$bans = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($board_access && $board_access[0] == '*') $board_access = false;
|
||||||
|
|
||||||
|
$out ? fputs($out, "[") : print("[");
|
||||||
|
|
||||||
|
$end = end($bans);
|
||||||
|
|
||||||
|
foreach ($bans as &$ban) {
|
||||||
|
$ban['mask'] = self::range_to_string(array($ban['ipstart'], $ban['ipend']));
|
||||||
|
|
||||||
|
if ($ban['post']) {
|
||||||
|
$post = json_decode($ban['post']);
|
||||||
|
$ban['message'] = $post->body;
|
||||||
|
}
|
||||||
|
unset($ban['ipstart'], $ban['ipend'], $ban['post'], $ban['creator']);
|
||||||
|
|
||||||
|
if ($board_access === false || in_array ($ban['board'], $board_access)) {
|
||||||
|
$ban['access'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter_var($ban['mask'], FILTER_VALIDATE_IP) !== false) {
|
||||||
|
$ban['single_addr'] = true;
|
||||||
|
}
|
||||||
|
if ($filter_staff || ($board_access !== false && !in_array($ban['board'], $board_access))) {
|
||||||
|
$ban['username'] = '?';
|
||||||
|
}
|
||||||
|
if ($filter_ips || ($board_access !== false && !in_array($ban['board'], $board_access))) {
|
||||||
|
list($ban['mask'], $subnet) = explode("/", $ban['mask']);
|
||||||
|
$ban['mask'] = preg_split("/[\.:]/", $ban['mask']);
|
||||||
|
$ban['mask'] = array_slice($ban['mask'], 0, 2);
|
||||||
|
$ban['mask'] = implode(".", $ban['mask']);
|
||||||
|
$ban['mask'] .= ".*";
|
||||||
|
if (isset ($subnet)) {
|
||||||
|
$ban['mask'] .= "/$subnet";
|
||||||
|
}
|
||||||
|
$ban['masked'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = json_encode($ban);
|
||||||
|
$out ? fputs($out, $json) : print($json);
|
||||||
|
|
||||||
|
if ($ban['id'] != $end['id']) {
|
||||||
|
$out ? fputs($out, ",") : print(",");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $bans;
|
$out ? fputs($out, "]") : print("]");
|
||||||
}
|
|
||||||
|
|
||||||
static public function count($board = false) {
|
|
||||||
if (!$board) {
|
|
||||||
$query = prepare("SELECT COUNT(*) FROM ``bans``");
|
|
||||||
} else {
|
|
||||||
$query = prepare("SELECT COUNT(*) FROM ``bans`` WHERE `board` = :board");
|
|
||||||
}
|
|
||||||
$query->bindValue(':board', $board);
|
|
||||||
$query->execute() or error(db_error());
|
|
||||||
return (int)$query->fetchColumn();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function seen($ban_id) {
|
static public function seen($ban_id) {
|
||||||
$query = query("UPDATE ``bans`` SET `seen` = 1 WHERE `id` = " . (int)$ban_id) or error(db_error());
|
$query = query("UPDATE ``bans`` SET `seen` = 1 WHERE `id` = " . (int)$ban_id) or error(db_error());
|
||||||
|
rebuildThemes('bans');
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function purge() {
|
static public function purge() {
|
||||||
$query = query("DELETE FROM ``bans`` WHERE `expires` IS NOT NULL AND `expires` < " . time() . " AND `seen` = 1") or error(db_error());
|
$query = query("DELETE FROM ``bans`` WHERE `expires` IS NOT NULL AND `expires` < " . time() . " AND `seen` = 1") or error(db_error());
|
||||||
|
rebuildThemes('bans');
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function delete($ban_id, $modlog = false) {
|
static public function delete($ban_id, $modlog = false, $boards = false, $dont_rebuild = false) {
|
||||||
global $config, $mod;
|
global $config;
|
||||||
|
|
||||||
$query = query("SELECT `ipstart`, `ipend`, `board` FROM ``bans`` WHERE `id` = " . (int)$ban_id) or error(db_error());
|
if ($boards && $boards[0] == '*') $boards = false;
|
||||||
if (!$ban = $query->fetch(PDO::FETCH_ASSOC)) {
|
|
||||||
// Ban doesn't exist
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($mod && $mod['boards'][0] != '*' && !in_array($ban['board'], $mod['boards']))
|
if ($modlog) {
|
||||||
error($config['error']['noaccess']);
|
$query = query("SELECT `ipstart`, `ipend` FROM ``bans`` WHERE `id` = " . (int)$ban_id) or error(db_error());
|
||||||
|
if (!$ban = $query->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
// Ban doesn't exist
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($boards !== false && !in_array($ban['board'], $boards))
|
||||||
|
error($config['error']['noaccess']);
|
||||||
|
|
||||||
if ($modlog) {
|
if ($modlog) {
|
||||||
$mask = self::range_to_string(array($ban['ipstart'], $ban['ipend']));
|
$mask = self::range_to_string(array($ban['ipstart'], $ban['ipend']));
|
||||||
@ -213,6 +242,8 @@ class Bans {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query("DELETE FROM ``bans`` WHERE `id` = " . (int)$ban_id) or error(db_error());
|
query("DELETE FROM ``bans`` WHERE `id` = " . (int)$ban_id) or error(db_error());
|
||||||
|
|
||||||
|
if (!$dont_rebuild) rebuildThemes('bans');
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -282,6 +313,9 @@ class Bans {
|
|||||||
' (<small>#' . $pdo->lastInsertId() . '</small>)' .
|
' (<small>#' . $pdo->lastInsertId() . '</small>)' .
|
||||||
' with ' . ($reason ? 'reason: ' . utf8tohtml($reason) . '' : 'no reason'));
|
' with ' . ($reason ? 'reason: ' . utf8tohtml($reason) . '' : 'no reason'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rebuildThemes('bans');
|
||||||
|
|
||||||
return $pdo->lastInsertId();
|
return $pdo->lastInsertId();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -782,8 +782,8 @@ function mod_page_ip($ip) {
|
|||||||
if (isset($_POST['ban_id'], $_POST['unban'])) {
|
if (isset($_POST['ban_id'], $_POST['unban'])) {
|
||||||
if (!hasPermission($config['mod']['unban']))
|
if (!hasPermission($config['mod']['unban']))
|
||||||
error($config['error']['noaccess']);
|
error($config['error']['noaccess']);
|
||||||
|
|
||||||
Bans::delete($_POST['ban_id'], true);
|
Bans::delete($_POST['ban_id'], true, $mod['boards']);
|
||||||
|
|
||||||
header('Location: ?/IP/' . $ip . '#bans', true, $config['redirect_http']);
|
header('Location: ?/IP/' . $ip . '#bans', true, $config['redirect_http']);
|
||||||
return;
|
return;
|
||||||
@ -879,18 +879,16 @@ function mod_ban() {
|
|||||||
require_once 'inc/mod/ban.php';
|
require_once 'inc/mod/ban.php';
|
||||||
|
|
||||||
Bans::new_ban($_POST['ip'], $_POST['reason'], $_POST['length'], $_POST['board'] == '*' ? false : $_POST['board']);
|
Bans::new_ban($_POST['ip'], $_POST['reason'], $_POST['length'], $_POST['board'] == '*' ? false : $_POST['board']);
|
||||||
|
|
||||||
if (isset($_POST['redirect']))
|
if (isset($_POST['redirect']))
|
||||||
header('Location: ' . $_POST['redirect'], true, $config['redirect_http']);
|
header('Location: ' . $_POST['redirect'], true, $config['redirect_http']);
|
||||||
else
|
else
|
||||||
header('Location: ?/', true, $config['redirect_http']);
|
header('Location: ?/', true, $config['redirect_http']);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mod_bans($page_no = 1) {
|
function mod_bans() {
|
||||||
global $config, $mod;
|
global $config;
|
||||||
|
global $mod;
|
||||||
if ($page_no < 1)
|
|
||||||
error($config['error']['404']);
|
|
||||||
|
|
||||||
if (!hasPermission($config['mod']['view_banlist']))
|
if (!hasPermission($config['mod']['view_banlist']))
|
||||||
error($config['error']['noaccess']);
|
error($config['error']['noaccess']);
|
||||||
@ -908,31 +906,33 @@ function mod_bans($page_no = 1) {
|
|||||||
error(sprintf($config['error']['toomanyunban'], $config['mod']['unban_limit'], count($unban)));
|
error(sprintf($config['error']['toomanyunban'], $config['mod']['unban_limit'], count($unban)));
|
||||||
|
|
||||||
foreach ($unban as $id) {
|
foreach ($unban as $id) {
|
||||||
Bans::delete($id, true);
|
Bans::delete($id, true, $mod['boards'], true);
|
||||||
}
|
}
|
||||||
|
rebuildThemes('bans');
|
||||||
header('Location: ?/bans', true, $config['redirect_http']);
|
header('Location: ?/bans', true, $config['redirect_http']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$board = ($mod['boards'][0] == '*' ? false : $mod['boards'][0]);
|
|
||||||
|
|
||||||
$bans = Bans::list_all(($page_no - 1) * $config['mod']['banlist_page'], $config['mod']['banlist_page'], $board);
|
|
||||||
|
|
||||||
if (empty($bans) && $page_no > 1)
|
|
||||||
error($config['error']['404']);
|
|
||||||
|
|
||||||
foreach ($bans as &$ban) {
|
|
||||||
if (filter_var($ban['mask'], FILTER_VALIDATE_IP) !== false)
|
|
||||||
$ban['single_addr'] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
mod_page(_('Ban list'), 'mod/ban_list.html', array(
|
mod_page(_('Ban list'), 'mod/ban_list.html', array(
|
||||||
'bans' => $bans,
|
'mod' => $mod,
|
||||||
'count' => Bans::count($board),
|
'boards' => json_encode($mod['boards']),
|
||||||
'token' => make_secure_link_token('bans')
|
'token' => make_secure_link_token('bans'),
|
||||||
|
'token_json' => make_secure_link_token('bans.json')
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mod_bans_json() {
|
||||||
|
global $config, $mod;
|
||||||
|
|
||||||
|
if (!hasPermission($config['mod']['ban']))
|
||||||
|
error($config['error']['noaccess']);
|
||||||
|
|
||||||
|
// Compress the json for faster loads
|
||||||
|
if (substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) ob_start("ob_gzhandler");
|
||||||
|
|
||||||
|
Bans::stream_json(false, false, !hasPermission($config['mod']['view_banstaff']), $mod['boards']);
|
||||||
|
}
|
||||||
|
|
||||||
function mod_ban_appeals() {
|
function mod_ban_appeals() {
|
||||||
global $config, $board;
|
global $config, $board;
|
||||||
|
|
||||||
|
@ -25,16 +25,6 @@ onready(function(){
|
|||||||
return [Math.pow(10, count - num.toString().length), num].join('').substr(1);
|
return [Math.pow(10, count - num.toString().length), num].join('').substr(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
var datelocale =
|
|
||||||
{ days: [_('Sunday'), _('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday')]
|
|
||||||
, shortDays: [_("Sun"), _("Mon"), _("Tue"), _("Wed"), _("Thu"), _("Fri"), _("Sat")]
|
|
||||||
, months: [_('January'), _('February'), _('March'), _('April'), _('May'), _('June'), _('July'), _('August'), _('September'), _('October'), _('November'), _('December')]
|
|
||||||
, shortMonths: [_('Jan'), _('Feb'), _('Mar'), _('Apr'), _('May'), _('Jun'), _('Jul'), _('Aug'), _('Sep'), _('Oct'), _('Nov'), _('Dec')]
|
|
||||||
, AM: _('AM')
|
|
||||||
, PM: _('PM')
|
|
||||||
, am: _('am')
|
|
||||||
, pm: _('pm')
|
|
||||||
};
|
|
||||||
var dateformat = (typeof strftime === 'undefined') ? function(t) {
|
var dateformat = (typeof strftime === 'undefined') ? function(t) {
|
||||||
return zeropad(t.getMonth() + 1, 2) + "/" + zeropad(t.getDate(), 2) + "/" + t.getFullYear().toString().substring(2) +
|
return zeropad(t.getMonth() + 1, 2) + "/" + zeropad(t.getDate(), 2) + "/" + t.getFullYear().toString().substring(2) +
|
||||||
" (" + [_("Sun"), _("Mon"), _("Tue"), _("Wed"), _("Thu"), _("Fri"), _("Sat"), _("Sun")][t.getDay()] + ") " +
|
" (" + [_("Sun"), _("Mon"), _("Tue"), _("Wed"), _("Thu"), _("Fri"), _("Sat"), _("Sun")][t.getDay()] + ") " +
|
||||||
|
184
js/longtable/longtable.js
Normal file
184
js/longtable/longtable.js
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
$.fn.longtable = function(fields, options, data) {
|
||||||
|
var elem = $(this).addClass("longtable");
|
||||||
|
|
||||||
|
var orig_data = data;
|
||||||
|
|
||||||
|
options.row_h = options.row_h || 22;
|
||||||
|
options.checkbox = options.checkbox || false;
|
||||||
|
|
||||||
|
var shown_rows = {};
|
||||||
|
|
||||||
|
var sorted_by = undefined;
|
||||||
|
var sorted_reverse = false;
|
||||||
|
|
||||||
|
var filter = function() { return true; };
|
||||||
|
|
||||||
|
var lt = {
|
||||||
|
_gen_field_td: function(field, id) {
|
||||||
|
var el;
|
||||||
|
if (id === undefined) {
|
||||||
|
el = $("<th></th>");
|
||||||
|
el.html(fields[field].name || field);
|
||||||
|
|
||||||
|
if (!fields[field].sort_disable) {
|
||||||
|
el.addClass("sortable").click(function() {
|
||||||
|
lt._sort_by(field);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
el = $("<td></td>");
|
||||||
|
if (fields[field].fmt) { // Special formatting?
|
||||||
|
el.html(fields[field].fmt(data[id]));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
el.html(data[id][field]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
el.css("width", fields[field].width);
|
||||||
|
el.css("height", options.row_h);
|
||||||
|
return el;
|
||||||
|
},
|
||||||
|
_gen_tr: function(id) {
|
||||||
|
var el = $("<tr></tr>");
|
||||||
|
$.each(fields, function(field, f) {
|
||||||
|
lt._gen_field_td(field, id).appendTo(el);
|
||||||
|
});
|
||||||
|
if (id !== undefined) {
|
||||||
|
el.addClass("row").addClass("row_"+id);
|
||||||
|
el.css({position: "absolute", top: options.row_h * (id+1)});
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
},
|
||||||
|
_clean: function() {
|
||||||
|
elem.find('.row').remove();
|
||||||
|
shown_rows = {};
|
||||||
|
},
|
||||||
|
_remove: function(id) {
|
||||||
|
elem.find('.row_'+id).remove();
|
||||||
|
delete shown_rows[id];
|
||||||
|
},
|
||||||
|
_insert: function(id) {
|
||||||
|
var el = lt._gen_tr(id).appendTo(elem);
|
||||||
|
$(elem).trigger("new-row", [data[id], el]);
|
||||||
|
shown_rows[id] = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
_sort_by: function(field) {
|
||||||
|
if (field !== undefined) {
|
||||||
|
if (sorted_by == field) {
|
||||||
|
sorted_reverse = !sorted_reverse;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sorted_reverse = !!fields[field].sort_reverse;
|
||||||
|
sorted_by = field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lt.sort_by(sorted_by, sorted_reverse);
|
||||||
|
},
|
||||||
|
|
||||||
|
_apply_filter: function() {
|
||||||
|
data = data.filter(filter);
|
||||||
|
},
|
||||||
|
_reset_data: function() {
|
||||||
|
data = orig_data;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
set_filter: function(f) {
|
||||||
|
filter = f;
|
||||||
|
lt._reset_data();
|
||||||
|
lt._apply_filter();
|
||||||
|
lt._sort_by();
|
||||||
|
},
|
||||||
|
|
||||||
|
sort_by: function(field, reverse) {
|
||||||
|
if (field !== undefined) {
|
||||||
|
sorted_by = field;
|
||||||
|
sorted_reverse = reverse;
|
||||||
|
|
||||||
|
var ord = fields[field].sort_fun || function(a,b) { return lt.sort_alphanum(a[field], b[field]); };
|
||||||
|
|
||||||
|
data = data.sort(ord);
|
||||||
|
if (reverse) data = data.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
lt.update_data();
|
||||||
|
},
|
||||||
|
|
||||||
|
update_viewport: function() {
|
||||||
|
var first = $(window).scrollTop() - $(elem).offset().top - options.row_h;
|
||||||
|
var last = first + $(window).height();
|
||||||
|
|
||||||
|
first = Math.floor(first / options.row_h);
|
||||||
|
last = Math.ceil (last / options.row_h);
|
||||||
|
|
||||||
|
first = first < 0 ? 0 : first;
|
||||||
|
last = last >= data.length ? data.length - 1 : last;
|
||||||
|
|
||||||
|
$.each(shown_rows, function(id) {
|
||||||
|
if (id < first || id > last) {
|
||||||
|
lt._remove(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (var id = first; id <= last; id++) {
|
||||||
|
if (!shown_rows[id]) lt._insert(id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
update_data: function() {
|
||||||
|
$(elem).height((data.length + 1) * options.row_h);
|
||||||
|
|
||||||
|
lt._clean();
|
||||||
|
lt.update_viewport();
|
||||||
|
},
|
||||||
|
|
||||||
|
get_data: function() {
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
},
|
||||||
|
|
||||||
|
// http://web.archive.org/web/20130826203933/http://my.opera.com/GreyWyvern/blog/show.dml/1671288
|
||||||
|
sort_alphanum: function(a, b) {
|
||||||
|
function chunkify(t) {
|
||||||
|
var tz = [], x = 0, y = -1, n = 0, i, j;
|
||||||
|
|
||||||
|
while (i = (j = t.charAt(x++)).charCodeAt(0)) {
|
||||||
|
var m = (/* dot: i == 46 || */(i >=48 && i <= 57));
|
||||||
|
if (m !== n) {
|
||||||
|
tz[++y] = "";
|
||||||
|
n = m;
|
||||||
|
}
|
||||||
|
tz[y] += j;
|
||||||
|
}
|
||||||
|
return tz;
|
||||||
|
}
|
||||||
|
|
||||||
|
var aa = chunkify((""+a).toLowerCase());
|
||||||
|
var bb = chunkify((""+b).toLowerCase());
|
||||||
|
|
||||||
|
for (x = 0; aa[x] && bb[x]; x++) {
|
||||||
|
if (aa[x] !== bb[x]) {
|
||||||
|
var c = Number(aa[x]), d = Number(bb[x]);
|
||||||
|
if (c == aa[x] && d == bb[x]) {
|
||||||
|
return c - d;
|
||||||
|
} else return (aa[x] > bb[x]) ? 1 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return aa.length - bb.length;
|
||||||
|
}
|
||||||
|
// End of foreign code
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
lt._gen_tr().appendTo(elem);
|
||||||
|
lt.update_data();
|
||||||
|
|
||||||
|
$(window).on("scroll resize", lt.update_viewport);
|
||||||
|
|
||||||
|
return lt;
|
||||||
|
};
|
157
js/mod/ban-list.js
Normal file
157
js/mod/ban-list.js
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
var banlist_init = function(token, my_boards, inMod) {
|
||||||
|
inMod = !inMod;
|
||||||
|
|
||||||
|
var lt;
|
||||||
|
|
||||||
|
var selected = {};
|
||||||
|
|
||||||
|
var time = function() { return Date.now()/1000|0; }
|
||||||
|
|
||||||
|
$.getJSON(inMod ? ("?/bans.json/"+token) : token, function(t) {
|
||||||
|
$("#banlist").on("new-row", function(e, drow, el) {
|
||||||
|
var sel = selected[drow.id];
|
||||||
|
if (sel) {
|
||||||
|
$(el).find('input.unban').prop("checked", true);
|
||||||
|
}
|
||||||
|
$(el).find('input.unban').on("click", function() {
|
||||||
|
selected[drow.id] = $(this).prop("checked");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (drow.expires && drow.expires != 0 && drow.expires < time()) {
|
||||||
|
$(el).find("td").css("text-decoration", "line-through");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var selall = "<input type='checkbox' id='select-all' style='float: left;'>";
|
||||||
|
|
||||||
|
lt = $("#banlist").longtable({
|
||||||
|
mask: {name: selall+_("IP address"), width: "220px", fmt: function(f) {
|
||||||
|
var pre = "";
|
||||||
|
if (inMod && f.access) {
|
||||||
|
pre = "<input type='checkbox' class='unban'>";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inMod && f.single_addr && !f.masked) {
|
||||||
|
return pre+"<a href='?/IP/"+f.mask+"'>"+f.mask+"</a>";
|
||||||
|
}
|
||||||
|
return pre+f.mask;
|
||||||
|
} },
|
||||||
|
reason: {name: _("Reason"), width: "calc(100% - 715px - 6 * 4px)", fmt: function(f) {
|
||||||
|
var add = "", suf = '';
|
||||||
|
if (f.seen == 1) add += "<i class='fa fa-check' title='"+_("Seen")+"'></i>";
|
||||||
|
if (f.message) {
|
||||||
|
add += "<i class='fa fa-comment' title='"+_("Message for which user was banned is included")+"'></i>";
|
||||||
|
suf = "<br /><br /><strong>"+_("Message:")+"</strong><br />"+f.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (add) { add = "<div style='float: right;'>"+add+"</div>"; }
|
||||||
|
|
||||||
|
if (f.reason) return add + f.reason + suf;
|
||||||
|
else return add + "-" + suf;
|
||||||
|
} },
|
||||||
|
board: {name: _("Board"), width: "60px", fmt: function(f) {
|
||||||
|
if (f.board) return "/"+f.board+"/";
|
||||||
|
else return "<em>"+_("all")+"</em>";
|
||||||
|
} },
|
||||||
|
created: {name: _("Set"), width: "100px", fmt: function(f) {
|
||||||
|
return ago(f.created) + _(" ago"); // in AGO form
|
||||||
|
} },
|
||||||
|
// duration?
|
||||||
|
expires: {name: _("Expires"), width: "235px", fmt: function(f) {
|
||||||
|
if (!f.expires || f.expires == 0) return "<em>"+_("never")+"</em>";
|
||||||
|
return strftime(window.post_date, new Date((f.expires|0)*1000), datelocale) +
|
||||||
|
((f.expires < time()) ? "" : " <small>"+_("in ")+until(f.expires|0)+"</small>");
|
||||||
|
} },
|
||||||
|
username: {name: _("Staff"), width: "100px", fmt: function(f) {
|
||||||
|
var pre='',suf='',un=f.username;
|
||||||
|
if (inMod && f.username && f.username != '?') {
|
||||||
|
pre = "<a href='?/new_PM/"+f.username+"'>";
|
||||||
|
suf = "</a>";
|
||||||
|
}
|
||||||
|
if (!f.username) {
|
||||||
|
un = "<em>"+_("system")+"</em>";
|
||||||
|
}
|
||||||
|
return pre + un + suf;
|
||||||
|
} }
|
||||||
|
}, {}, t);
|
||||||
|
|
||||||
|
$("#select-all").click(function(e) {
|
||||||
|
var $this = $(this);
|
||||||
|
$("input.unban").prop("checked", $this.prop("checked"));
|
||||||
|
lt.get_data().forEach(function(v) { v.access && (selected[v.id] = $this.prop("checked")); });
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
var filter = function(e) {
|
||||||
|
if ($("#only_mine").prop("checked") && ($.inArray(e.board, my_boards) === -1)) return false;
|
||||||
|
if ($("#only_not_expired").prop("checked") && e.expires && e.expires != 0 && e.expires < time()) return false;
|
||||||
|
if ($("#search").val()) {
|
||||||
|
var terms = $("#search").val().split(" ");
|
||||||
|
|
||||||
|
var fields = ["mask", "reason", "board", "staff", "message"];
|
||||||
|
|
||||||
|
var ret_false = false;
|
||||||
|
terms.forEach(function(t) {
|
||||||
|
var fs = fields;
|
||||||
|
|
||||||
|
var re = /^(mask|reason|board|staff|message):/, ma;
|
||||||
|
if (ma = t.match(re)) {
|
||||||
|
fs = [ma[1]];
|
||||||
|
t = t.replace(re, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
var found = false
|
||||||
|
fs.forEach(function(f) {
|
||||||
|
if (e[f] && e[f].indexOf(t) !== -1) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!found) ret_false = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ret_false) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
$("#only_mine, #only_not_expired, #search").on("click input", function() {
|
||||||
|
lt.set_filter(filter);
|
||||||
|
});
|
||||||
|
lt.set_filter(filter);
|
||||||
|
|
||||||
|
$(".banform").on("submit", function() { return false; });
|
||||||
|
|
||||||
|
$("#unban").on("click", function() {
|
||||||
|
$(".banform .hiddens").remove();
|
||||||
|
$("<input type='hidden' name='unban' value='unban' class='hiddens'>").appendTo(".banform");
|
||||||
|
|
||||||
|
$.each(selected, function(e) {
|
||||||
|
$("<input type='hidden' name='ban_"+e+"' value='unban' class='hiddens'>").appendTo(".banform");
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".banform").off("submit").submit();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (device_type == 'desktop') {
|
||||||
|
// Stick topbar
|
||||||
|
var stick_on = $(".banlist-opts").offset().top;
|
||||||
|
var state = true;
|
||||||
|
$(window).on("scroll resize", function() {
|
||||||
|
if ($(window).scrollTop() > stick_on && state == true) {
|
||||||
|
$("body").css("margin-top", $(".banlist-opts").height());
|
||||||
|
$(".banlist-opts").addClass("boardlist top").detach().prependTo("body");
|
||||||
|
$("#banlist tr:not(.row)").addClass("tblhead").detach().appendTo(".banlist-opts");
|
||||||
|
state = !state;
|
||||||
|
}
|
||||||
|
else if ($(window).scrollTop() < stick_on && state == false) {
|
||||||
|
$("body").css("margin-top", "auto");
|
||||||
|
$(".banlist-opts").removeClass("boardlist top").detach().prependTo(".banform");
|
||||||
|
$(".tblhead").detach().prependTo("#banlist");
|
||||||
|
state = !state;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
2
mod.php
2
mod.php
@ -63,7 +63,7 @@ $pages = array(
|
|||||||
|
|
||||||
'/ban' => 'secure_POST ban', // new ban
|
'/ban' => 'secure_POST ban', // new ban
|
||||||
'/bans' => 'secure_POST bans', // ban list
|
'/bans' => 'secure_POST bans', // ban list
|
||||||
'/bans/(\d+)' => 'secure_POST bans', // ban list
|
'/bans.json' => 'secure bans_json', // ban list JSON
|
||||||
'/ban-appeals' => 'secure_POST ban_appeals', // view ban appeals
|
'/ban-appeals' => 'secure_POST ban_appeals', // view ban appeals
|
||||||
|
|
||||||
'/recent/(\d+)' => 'recent_posts', // view recent posts
|
'/recent/(\d+)' => 'recent_posts', // view recent posts
|
||||||
|
31
stylesheets/longtable/longtable.css
Normal file
31
stylesheets/longtable/longtable.css
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
.longtable {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.longtable > tbody {
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.longtable > tbody > tr {
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
clear: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.longtable > tbody > tr > td {
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
float: left;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.longtable > tbody > tr > th {
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
float: left;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.longtable > tbody > tr > th.sortable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,53 @@ function fmt(s,a) {
|
|||||||
return s.replace(/\{([0-9]+)\}/g, function(x) { return a[x[1]]; });
|
return s.replace(/\{([0-9]+)\}/g, function(x) { return a[x[1]]; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function until($timestamp) {
|
||||||
|
var $difference = $timestamp - Date.now()/1000|0, $num;
|
||||||
|
switch(true){
|
||||||
|
case ($difference < 60):
|
||||||
|
return "" + $difference + ' ' + _('second(s)');
|
||||||
|
case ($difference < 3600): //60*60 = 3600
|
||||||
|
return "" + ($num = Math.round($difference/(60))) + ' ' + _('minute(s)');
|
||||||
|
case ($difference < 86400): //60*60*24 = 86400
|
||||||
|
return "" + ($num = Math.round($difference/(3600))) + ' ' + _('hour(s)');
|
||||||
|
case ($difference < 604800): //60*60*24*7 = 604800
|
||||||
|
return "" + ($num = Math.round($difference/(86400))) + ' ' + _('day(s)');
|
||||||
|
case ($difference < 31536000): //60*60*24*365 = 31536000
|
||||||
|
return "" + ($num = Math.round($difference/(604800))) + ' ' + _('week(s)');
|
||||||
|
default:
|
||||||
|
return "" + ($num = Math.round($difference/(31536000))) + ' ' + _('year(s)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ago($timestamp) {
|
||||||
|
var $difference = (Date.now()/1000|0) - $timestamp, $num;
|
||||||
|
switch(true){
|
||||||
|
case ($difference < 60) :
|
||||||
|
return "" + $difference + ' ' + _('second(s)');
|
||||||
|
case ($difference < 3600): //60*60 = 3600
|
||||||
|
return "" + ($num = Math.round($difference/(60))) + ' ' + _('minute(s)');
|
||||||
|
case ($difference < 86400): //60*60*24 = 86400
|
||||||
|
return "" + ($num = Math.round($difference/(3600))) + ' ' + _('hour(s)');
|
||||||
|
case ($difference < 604800): //60*60*24*7 = 604800
|
||||||
|
return "" + ($num = Math.round($difference/(86400))) + ' ' + _('day(s)');
|
||||||
|
case ($difference < 31536000): //60*60*24*365 = 31536000
|
||||||
|
return "" + ($num = Math.round($difference/(604800))) + ' ' + _('week(s)');
|
||||||
|
default:
|
||||||
|
return "" + ($num = Math.round($difference/(31536000))) + ' ' + _('year(s)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var datelocale =
|
||||||
|
{ days: [_('Sunday'), _('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday')]
|
||||||
|
, shortDays: [_("Sun"), _("Mon"), _("Tue"), _("Wed"), _("Thu"), _("Fri"), _("Sat")]
|
||||||
|
, months: [_('January'), _('February'), _('March'), _('April'), _('May'), _('June'), _('July'), _('August'), _('September'), _('October'), _('November'), _('December')]
|
||||||
|
, shortMonths: [_('Jan'), _('Feb'), _('Mar'), _('Apr'), _('May'), _('Jun'), _('Jul'), _('Aug'), _('Sep'), _('Oct'), _('Nov'), _('Dec')]
|
||||||
|
, AM: _('AM')
|
||||||
|
, PM: _('PM')
|
||||||
|
, am: _('am')
|
||||||
|
, pm: _('pm')
|
||||||
|
};
|
||||||
|
|
||||||
var saved = {};
|
var saved = {};
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,104 +1,41 @@
|
|||||||
{% if bans|count == 0 %}
|
<script src='main.js'></script>
|
||||||
<p style="text-align:center" class="unimportant">({% trans 'There are no active bans.' %})</p>
|
<script src='js/jquery.min.js'></script>
|
||||||
{% else %}
|
<script src='js/mobile-style.js'></script>
|
||||||
<form action="?/bans" method="post">
|
<script src='js/strftime.min.js'></script>
|
||||||
<input type="hidden" name="token" value="{{ token }}">
|
<script src='js/longtable/longtable.js'></script>
|
||||||
<table class="mod" style="width:100%">
|
<script src='js/mod/ban-list.js'></script>
|
||||||
<tr>
|
<link rel='stylesheet' href='stylesheets/longtable/longtable.css'>
|
||||||
<th>{% trans 'IP address/mask' %}</th>
|
<link rel='stylesheet' href='stylesheets/mod/ban-list.css'>
|
||||||
<th>{% trans 'Reason' %}</th>
|
|
||||||
<th>{% trans 'Board' %}</th>
|
<form action="?/bans" method="post" class="banform">
|
||||||
<th>{% trans 'Set' %}</th>
|
{% if token %}
|
||||||
<th>{% trans 'Duration' %}</th>
|
<input type="hidden" name="token" value="{{ token }}">
|
||||||
<th>{% trans 'Expires' %}</th>
|
{% endif %}
|
||||||
<th>{% trans 'Seen' %}</th>
|
<div class='banlist-opts'>
|
||||||
<th>{% trans 'Staff' %}</th>
|
<div class='checkboxes'>
|
||||||
</tr>
|
{% if mod and mod.boards[0] != '*' %}
|
||||||
{% for ban in bans %}
|
<label><input type="checkbox" id="only_mine"> {% trans %}Show only bans from boards I moderate{% endtrans %}</label>
|
||||||
<tr{% if ban.expires != 0 and ban.expires < time() %} style="text-decoration:line-through"{% endif %}>
|
{% endif %}
|
||||||
<td style="white-space: nowrap">
|
<label><input type="checkbox" id="only_not_expired"> {% trans %}Show only active bans{% endtrans %}</label>
|
||||||
<input type="checkbox" name="ban_{{ ban.id }}">
|
</div>
|
||||||
{% if ban.single_addr %}
|
<div class='buttons'>
|
||||||
<a href="?/IP/{{ ban.mask }}">{{ ban.mask }}</a>
|
<input type="text" id="search" placeholder="{% trans %}Search{% endtrans %}">
|
||||||
{% else %}
|
{% if mod %}
|
||||||
{{ ban.mask }}
|
<input type="submit" name="unban" id="unban" value="{% trans 'Unban selected' %}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</div>
|
||||||
<td>
|
|
||||||
{% if ban.reason %}
|
<br class='clear'>
|
||||||
{{ ban.reason }}
|
</div>
|
||||||
{% else %}
|
|
||||||
-
|
<table class="mod" style="width:100%" id="banlist">
|
||||||
{% 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.created|date(config.post_date) }}">
|
|
||||||
{{ ban.created|ago }} ago
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td style="white-space: nowrap">
|
|
||||||
{% if ban.expires == 0 %}
|
|
||||||
-
|
|
||||||
{% else %}
|
|
||||||
{{ (ban.expires - ban.created + 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.creator == -1 %}
|
|
||||||
<em>system</em>
|
|
||||||
{% else %}
|
|
||||||
<em>{% trans 'deleted?' %}</em>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<p style="text-align:center">
|
|
||||||
<input type="submit" name="unban" value="{% trans 'Unban selected' %}">
|
|
||||||
</p>
|
|
||||||
</form>
|
</form>
|
||||||
|
{% if token_json %}
|
||||||
|
<script>$(function(){ banlist_init("{{ token_json }}", {{ boards }}); });</script>
|
||||||
|
{% else %}
|
||||||
|
<script>$(function(){ banlist_init("{{ uri_json }}", {{ boards }}, true); });</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if count > bans|count %}
|
|
||||||
<p class="unimportant" style="text-align:center;word-wrap:break-word">
|
|
||||||
{% for i in range(0, (count - 1) / config.mod.modlog_page) %}
|
|
||||||
<a href="?/bans/{{ i + 1 }}">[{{ i + 1 }}]</a>
|
|
||||||
{% endfor %}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
33
templates/themes/public_banlist/info.php
Normal file
33
templates/themes/public_banlist/info.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
$theme = Array();
|
||||||
|
|
||||||
|
// Theme name
|
||||||
|
$theme['name'] = 'Public Banlist';
|
||||||
|
// Description (you can use Tinyboard markup here)
|
||||||
|
$theme['description'] =
|
||||||
|
'Shows a public list of bans, that were issued on all boards. Basically, this theme
|
||||||
|
copies the banlist interface from moderation panel.';
|
||||||
|
$theme['version'] = 'v0.1';
|
||||||
|
|
||||||
|
// Theme configuration
|
||||||
|
$theme['config'] = Array();
|
||||||
|
|
||||||
|
$theme['config'][] = Array(
|
||||||
|
'title' => 'JSON feed file',
|
||||||
|
'name' => 'file_json',
|
||||||
|
'type' => 'text',
|
||||||
|
'default' => 'bans.json',
|
||||||
|
'comment' => '(eg. "bans.json")'
|
||||||
|
);
|
||||||
|
|
||||||
|
$theme['config'][] = Array(
|
||||||
|
'title' => 'Main HTML file',
|
||||||
|
'name' => 'file_bans',
|
||||||
|
'type' => 'text',
|
||||||
|
'default' => 'bans.html',
|
||||||
|
'comment' => '(eg. "bans.html")'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Unique function name for building everything
|
||||||
|
$theme['build_function'] = 'pbanlist_build';
|
||||||
|
?>
|
56
templates/themes/public_banlist/theme.php
Normal file
56
templates/themes/public_banlist/theme.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
require 'info.php';
|
||||||
|
|
||||||
|
function pbanlist_build($action, $settings, $board) {
|
||||||
|
// Possible values for $action:
|
||||||
|
// - all (rebuild everything, initialization)
|
||||||
|
// - news (news has been updated)
|
||||||
|
// - boards (board list changed)
|
||||||
|
// - bans (ban list changed)
|
||||||
|
|
||||||
|
PBanlist::build($action, $settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap functions in a class so they don't interfere with normal Tinyboard operations
|
||||||
|
class PBanlist {
|
||||||
|
public static function build($action, $settings) {
|
||||||
|
global $config;
|
||||||
|
|
||||||
|
if ($action == 'all')
|
||||||
|
file_write($config['dir']['home'] . $settings['file_bans'], PBanlist::homepage($settings));
|
||||||
|
|
||||||
|
if ($action == 'all' || $action == 'bans')
|
||||||
|
file_write($config['dir']['home'] . $settings['file_json'], PBanlist::gen_json($settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function gen_json($settings) {
|
||||||
|
ob_start();
|
||||||
|
Bans::stream_json(false, false, !hasPermission($config['mod']['view_banstaff']), $mod['boards']);
|
||||||
|
$out = ob_get_contents();
|
||||||
|
ob_end_clean();
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build homepage
|
||||||
|
public static function homepage($settings) {
|
||||||
|
global $config;
|
||||||
|
|
||||||
|
return Element('page.html', array(
|
||||||
|
'config' => $config,
|
||||||
|
'mod' => false,
|
||||||
|
'hide_dashboard_link' => true,
|
||||||
|
'title' => _("Ban list"),
|
||||||
|
'subtitle' => "",
|
||||||
|
'nojavascript' => true,
|
||||||
|
'body' => Element('mod/ban_list.html', array(
|
||||||
|
'mod' => false,
|
||||||
|
'boards' => "[]",
|
||||||
|
'token' => false,
|
||||||
|
'token_json' => false,
|
||||||
|
'uri_json' => $config['dir']['home'] . $settings['file_json'],
|
||||||
|
))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
?>
|
@ -4,7 +4,11 @@
|
|||||||
function ukko_build($action, $settings) {
|
function ukko_build($action, $settings) {
|
||||||
$ukko = new ukko();
|
$ukko = new ukko();
|
||||||
$ukko->settings = $settings;
|
$ukko->settings = $settings;
|
||||||
|
|
||||||
|
if (! ($action == 'all' || $action == 'post' || $action == 'post-thread' || $action == 'post-delete')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
file_write($settings['uri'] . '/index.html', $ukko->build());
|
file_write($settings['uri'] . '/index.html', $ukko->build());
|
||||||
file_write($settings['uri'] . '/ukko.js', Element('themes/ukko/ukko.js', array()));
|
file_write($settings['uri'] . '/ukko.js', Element('themes/ukko/ukko.js', array()));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user