1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/webtrees_ynh.git synced 2024-09-03 18:26:37 +02:00
webtrees_ynh/sources/admin_site_access.php

385 lines
16 KiB
PHP

<?php
/**
* webtrees: online genealogy
* Copyright (C) 2016 webtrees development team
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Fisharebest\Webtrees;
use Fisharebest\Webtrees\Controller\PageController;
use Fisharebest\Webtrees\Functions\FunctionsEdit;
use PDO;
define('WT_SCRIPT_NAME', 'admin_site_access.php');
require './includes/session.php';
$rules_display = array(
'unknown' => I18N::translate('unknown'),
'allow' => /* I18N: An access rule - allow access to the site */ I18N::translate('allow'),
'deny' => /* I18N: An access rule - deny access to the site */ I18N::translate('deny'),
'robot' => /* I18N: http://en.wikipedia.org/wiki/Web_crawler */ I18N::translate('robot'),
);
$rules_edit = array(
'unknown' => I18N::translate('unknown'),
'allow' => /* I18N: An access rule - allow access to the site */ I18N::translate('allow'),
'deny' => /* I18N: An access rule - deny access to the site */ I18N::translate('deny'),
'robot' => /* I18N: http://en.wikipedia.org/wiki/Web_crawler */ I18N::translate('robot'),
);
// Form actions
switch (Filter::post('action')) {
case 'save':
if (Filter::checkCsrf()) {
$site_access_rule_id = Filter::postInteger('site_access_rule_id');
$ip_address_start = Filter::post('ip_address_start', WT_REGEX_IPV4);
$ip_address_end = Filter::post('ip_address_end', WT_REGEX_IPV4);
$user_agent_pattern = Filter::post('user_agent_pattern');
$rule = Filter::post('rule', 'allow|deny|robot');
$comment = Filter::post('comment');
$user_agent_string = Filter::server('HTTP_USER_AGENT');
$ip_address = WT_CLIENT_IP;
if ($ip_address_start !== null && $ip_address_end !== null && $user_agent_pattern !== null && $rule !== null) {
// This doesn't work with named placeholders. The :user_agent_string parameter is not recognised.
$oops = $rule !== 'allow' && Database::prepare(
"SELECT INET_ATON(:ip_address) BETWEEN INET_ATON(:ip_address_start) AND INET_ATON(:ip_address_end)" .
" AND :user_agent_string LIKE :user_agent_pattern"
)->execute(array(
'ip_address' => $ip_address,
'ip_address_start' => $ip_address_start,
'ip_address_end' => $ip_address_end,
'user_agent_string' => $user_agent_string,
'user_agent_pattern' => $user_agent_pattern,
))->fetchOne();
if ($oops) {
FlashMessages::addMessage(I18N::translate('You cannot create a rule which would prevent yourself from accessing the website.'), 'danger');
} elseif ($site_access_rule_id === null) {
Database::prepare(
"INSERT INTO `##site_access_rule` (ip_address_start, ip_address_end, user_agent_pattern, rule, comment) VALUES (INET_ATON(:ip_address_start), INET_ATON(:ip_address_end), :user_agent_pattern, :rule, :comment)"
)->execute(array(
'ip_address_start' => $ip_address_start,
'ip_address_end' => $ip_address_end,
'user_agent_pattern' => $user_agent_pattern,
'rule' => $rule,
'comment' => $comment,
));
FlashMessages::addMessage(I18N::translate('The website access rule has been created.'), 'success');
} else {
Database::prepare(
"UPDATE `##site_access_rule` SET ip_address_start = INET_ATON(:ip_address_start), ip_address_end = INET_ATON(:ip_address_end), user_agent_pattern = :user_agent_pattern, rule = :rule, comment = :comment WHERE site_access_rule_id = :site_access_rule_id"
)->execute(array(
'ip_address_start' => $ip_address_start,
'ip_address_end' => $ip_address_end,
'user_agent_pattern' => $user_agent_pattern,
'rule' => $rule,
'comment' => $comment,
'site_access_rule_id' => $site_access_rule_id,
));
FlashMessages::addMessage(I18N::translate('The website access rule has been updated.'), 'success');
}
}
}
header('Location: ' . WT_BASE_URL . WT_SCRIPT_NAME);
return;
case 'delete':
if (Filter::checkCsrf()) {
$site_access_rule_id = Filter::postInteger('site_access_rule_id');
Database::prepare(
"DELETE FROM `##site_access_rule` WHERE site_access_rule_id = :site_access_rule_id"
)->execute(array(
'site_access_rule_id' => $site_access_rule_id,
));
FlashMessages::addMessage(I18N::translate('The website access rule has been deleted.'), 'success');
}
header('Location: ' . WT_BASE_URL . WT_SCRIPT_NAME);
return;
}
// Delete any "unknown" visitors that are now "known".
// This could happen every time we create/update a rule.
Database::exec(
"DELETE unknown" .
" FROM `##site_access_rule` AS unknown" .
" JOIN `##site_access_rule` AS known ON (unknown.user_agent_pattern LIKE known.user_agent_pattern)" .
" WHERE unknown.rule='unknown' AND known.rule<>'unknown'" .
" AND unknown.ip_address_start BETWEEN known.ip_address_start AND known.ip_address_end"
);
$controller = new PageController;
$controller
->restrictAccess(Auth::isAdmin())
->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)
->addExternalJavascript(WT_DATATABLES_BOOTSTRAP_JS_URL)
->setPageTitle(I18N::translate('Website access rules'));
$action = Filter::get('action');
switch ($action) {
case 'load':
// AJAX callback for datatables
$search = Filter::get('search');
$search = $search['value'];
$start = Filter::getInteger('start');
$length = Filter::getInteger('length');
$sql =
"SELECT SQL_CACHE SQL_CALC_FOUND_ROWS" .
" '', INET_NTOA(ip_address_start), ip_address_start, INET_NTOA(ip_address_end), ip_address_end, user_agent_pattern, rule, comment, site_access_rule_id" .
" FROM `##site_access_rule`";
$args = array();
if ($search) {
$sql .=
" WHERE (INET_ATON(:search_1) BETWEEN ip_address_start AND ip_address_end" .
" OR INET_NTOA(ip_address_start) LIKE CONCAT('%', :search_2, '%')" .
" OR INET_NTOA(ip_address_end) LIKE CONCAT('%', :search_3, '%')" .
" OR user_agent_pattern LIKE CONCAT('%', :search_4, '%')" .
" OR comment LIKE CONCAT('%', :search_5, '%'))";
$args['search_1'] = Filter::escapeLike($search);
$args['search_2'] = Filter::escapeLike($search);
$args['search_3'] = Filter::escapeLike($search);
$args['search_4'] = Filter::escapeLike($search);
$args['search_5'] = Filter::escapeLike($search);
}
$order = Filter::getArray('order');
$sql .= ' ORDER BY';
if ($order) {
foreach ($order as $key => $value) {
if ($key > 0) {
$sql .= ',';
}
// Datatables numbers columns 0, 1, 2
// MySQL numbers columns 1, 2, 3
switch ($value['dir']) {
case 'asc':
$sql .= " :col_" . $key . " ASC";
break;
case 'desc':
$sql .= " :col_" . $key . " DESC";
break;
}
$args['col_' . $key] = 1 + $value['column'];
}
} else {
$sql .= ' 1 ASC';
}
if ($length > 0) {
$sql .= " LIMIT :length OFFSET :start";
$args['length'] = $length;
$args['start'] = $start;
}
// This becomes a JSON list, not a JSON array, so we need numeric keys.
$data = Database::prepare($sql)->execute($args)->fetchAll(PDO::FETCH_NUM);
// Reformat the data for display
foreach ($data as &$datum) {
$site_access_rule_id = $datum[8];
$datum[0] = '<div class="btn-group"><button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><i class="fa fa-pencil"></i> <span class="caret"></span></button><ul class="dropdown-menu" role="menu"><li><a href="?action=edit&amp;site_access_rule_id=' . $site_access_rule_id . '"><i class="fa fa-fw fa-pencil"></i> ' . I18N::translate('Edit') . '</a></li><li class="divider"><li><a href="#" onclick="if (confirm(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs($datum[5])) . '\')) delete_site_access_rule(' . $site_access_rule_id . '); return false;"><i class="fa fa-fw fa-trash-o"></i> ' . I18N::translate('Delete') . '</a></li></ul></div>';
$datum[5] = '<span dir="ltr">' . $datum[5] . '</span>';
$datum[6] = $rules_display[$datum[6]];
$datum[7] = '<span dir="auto">' . $datum[7] . '</span>';
}
// Total filtered/unfiltered rows
$recordsFiltered = Database::prepare("SELECT FOUND_ROWS()")->fetchOne();
$recordsTotal = Database::prepare("SELECT COUNT(*) FROM `##site_access_rule`")->fetchOne();
header('Content-type: application/json');
// See http://www.datatables.net/usage/server-side
echo json_encode(array(
'draw' => Filter::getInteger('draw'),
'recordsTotal' => $recordsTotal,
'recordsFiltered' => $recordsFiltered,
'data' => $data,
));
break;
case 'edit':
case 'create':
if (Filter::get('action') === 'edit') {
$controller->setPageTitle(I18N::translate('Edit a website access rule'));
} else {
$controller->setPageTitle(I18N::translate('Create a website access rule'));
}
$controller->pageHeader();
$site_access_rule = Database::prepare(
"SELECT site_access_rule_id, INET_NTOA(ip_address_start) AS ip_address_start, INET_NTOA(ip_address_end) AS ip_address_end, user_agent_pattern, rule, comment" .
" FROM `##site_access_rule` WHERE site_access_rule_id = :site_access_rule_id"
)->execute(array(
'site_access_rule_id' => Filter::getInteger('site_access_rule_id'),
))->fetchOneRow();
$site_access_rule_id = $site_access_rule ? $site_access_rule->site_access_rule_id : null;
$ip_address_start = $site_access_rule ? $site_access_rule->ip_address_start : '0.0.0.0';
$ip_address_end = $site_access_rule ? $site_access_rule->ip_address_end : '255.255.255.255';
$user_agent_pattern = $site_access_rule ? $site_access_rule->user_agent_pattern : '%';
$rule = $site_access_rule ? $site_access_rule->rule : 'allow';
$comment = $site_access_rule ? $site_access_rule->comment : '';
?>
<ol class="breadcrumb small">
<li><a href="admin.php"><?php echo I18N::translate('Control panel'); ?></a></li>
<li><a href="admin_site_access.php"><?php echo I18N::translate('Website access rules'); ?></a></li>
<li class="active"><?php echo $controller->getPageTitle(); ?></li>
</ol>
<h1><?php echo $controller->getPageTitle(); ?></h1>
<form method="post" class="form form-horizontal">
<input type="hidden" name="action" value="save">
<input type="hidden" name="site_access_rule_id" value="<?php echo $site_access_rule_id; ?>">
<?php echo Filter::getCsrf(); ?>
<!-- IP_ADDRESS_START -->
<div class="form-group">
<label class="control-label col-sm-3" for="ip_address_start">
<?php echo I18N::translate('Start IP address'); ?>
</label>
<div class="col-sm-9">
<input class="form-control" type="text" id="ip_address_start" name="ip_address_start" required pattern="<?php echo WT_REGEX_IPV4; ?>" value="<?php echo Filter::escapeHtml($ip_address_start); ?>">
</div>
</div>
<!-- IP_ADDRESS_END -->
<div class="form-group">
<label class="control-label col-sm-3" for="ip_address_end">
<?php echo I18N::translate('End IP address'); ?>
</label>
<div class="col-sm-9">
<input class="form-control" type="text" id="ip_address_end" name="ip_address_end" required pattern="<?php echo WT_REGEX_IPV4; ?>" value="<?php echo Filter::escapeHtml($ip_address_end); ?>">
</div>
</div>
<!-- USER_AGENT_PATTERN -->
<div class="form-group">
<label class="control-label col-sm-3" for="user_agent_pattern">
<?php echo I18N::translate('User-agent string'); ?>
</label>
<div class="col-sm-9">
<input class="form-control" type="text" id="user_agent_pattern" name="user_agent_pattern" required value="<?php echo Filter::escapeHtml($user_agent_pattern); ?>" maxlength="255" dir="ltr">
<p class="small text-muted">
<?php echo I18N::translate('The “%” character is a wildcard, and will match zero or more other characters.'); ?>
</p>
</div>
</div>
<!-- RULE -->
<div class="form-group">
<label class="control-label col-sm-3" for="rule">
<?php echo /* I18N: A configuration setting */ I18N::translate('Rule'); ?>
</label>
<div class="col-sm-9">
<?php echo FunctionsEdit::selectEditControl('rule', $rules_edit, null, $rule, 'class="form-control"'); ?>
</div>
</div>
<!-- COMMENT -->
<div class="form-group">
<label class="control-label col-sm-3" for="comment">
<?php echo I18N::translate('Comment'); ?>
</label>
<div class="col-sm-9">
<input class="form-control" type="text" id="comment" name="comment" value="<?php echo Filter::escapeHtml($comment); ?>" maxlength="255" dir="auto">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<button type="submit" class="btn btn-primary">
<?php echo I18N::translate('save'); ?>
</button>
</div>
</div>
</form>
<?php
break;
default:
$controller
->pageHeader()
->addInlineJavascript('
jQuery.fn.dataTableExt.oSort["unicode-asc" ]=function(a,b) {return a.replace(/<[^<]*>/, "").localeCompare(b.replace(/<[^<]*>/, ""))};
jQuery.fn.dataTableExt.oSort["unicode-desc"]=function(a,b) {return b.replace(/<[^<]*>/, "").localeCompare(a.replace(/<[^<]*>/, ""))};
jQuery(".table-site-access-rules").dataTable({
ajax: "' . WT_BASE_URL . WT_SCRIPT_NAME . '?action=load",
serverSide: true,
' . I18N::datatablesI18N() . ',
processing: true,
stateSave: true,
stateDuration: 180,
sorting: [[1, "asc"]],
columns: [
/* 0 <edit> */ { sortable: false },
/* 1 ip_address_start */ { dataSort: 2, class: "ip_address" },
/* 2 ip_address_start (sort) */ { type: "num", visible: false },
/* 3 ip_address_end */ { dataSort: 4, class: "ip_address" },
/* 4 ip_address_end (sort) */ { type: "num", visible: false },
/* 5 user_agent_pattern */ { class: "ua_string" },
/* 6 comment */ { },
/* 7 rule */ { }
]
});
');
?>
<ol class="breadcrumb small">
<li><a href="admin.php"><?php echo I18N::translate('Control panel'); ?></a></li>
<li class="active"><?php echo $controller->getPageTitle(); ?></li>
</ol>
<h1><?php echo $controller->getPageTitle(); ?></h1>
<p><?php echo /* I18N: http://en.wikipedia.org/wiki/User_agent */ I18N::translate('Restrict access to the website, using IP addresses and user-agent strings.'); ?></p>
<table class="table table-hover table-condensed table-bordered table-site-access-rules">
<caption>
<?php echo I18N::translate('The following rules are used to decide whether a visitor is a human being (allow full access), a search-engine robot (allow restricted access) or an unwanted crawler (deny all access).'); ?>
</caption>
<thead>
<tr>
<th><?php echo I18N::translate('Edit'); ?></th>
<th><?php echo /* I18N …of a range of addresses */ I18N::translate('Start IP address'); ?></th>
<th>-</th>
<th><?php echo /* I18N …of a range of addresses */ I18N::translate('End IP address'); ?></th>
<th>-</th>
<th><?php echo /* I18N: http://en.wikipedia.org/wiki/User_agent_string */ I18N::translate('User-agent string'); ?></th>
<th><?php echo /* I18N: noun */ I18N::translate('Rule'); ?></th>
<th><?php echo I18N::translate('Comment'); ?></th>
</tr>
</thead>
</table>
<!-- Implement the delete action -->
<form class="hide" method="post" id="delete-form">
<?php echo Filter::getCsrf(); ?>
<input type="hidden" name="site_access_rule_id" id="site-access-rule-id" value="">
<input type="hidden" name="action" value="delete">
</form>
<script>
function delete_site_access_rule(site_access_rule_id) {
document.getElementById("site-access-rule-id").value = site_access_rule_id;
document.getElementById("delete-form").submit();
}
</script>
<?php
break;
}