mirror of
https://github.com/YunoHost-Apps/webtrees_ynh.git
synced 2024-09-03 18:26:37 +02:00
386 lines
16 KiB
PHP
386 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&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;
|
||
|
}
|