1<?php 2/** 3 * webtrees: online genealogy 4 * Copyright (C) 2021 webtrees development team 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15 */ 16namespace Fisharebest\Webtrees; 17 18use Fisharebest\Webtrees\Controller\PageController; 19use Fisharebest\Webtrees\Theme\AdministrationTheme; 20use PDOException; 21use Symfony\Component\HttpFoundation\Request; 22 23/** 24 * This is the bootstrap script, that is run on every request. 25 */ 26 27if (version_compare(PHP_VERSION, '7.4.0') >= 0) { 28 echo '<!DOCTYPE html><html lang="en"><body>'; 29 echo 'PHP ' . PHP_VERSION . ' detected.<br>'; 30 echo 'webtrees 1.7 requires PHP 5.3 - 7.3.<br>'; 31 echo 'Visit https://webtrees.net for more information.<br>'; 32 echo '</body></html>'; 33 exit; 34} 35 36// WT_SCRIPT_NAME is defined in each script that the user is permitted to load. 37if (!defined('WT_SCRIPT_NAME')) { 38 http_response_code(403); 39 40 return; 41} 42 43/** 44 * We set the following globals 45 * 46 * @global boolean $SEARCH_SPIDER 47 * @global Tree $WT_TREE 48 */ 49global $WT_TREE, $SEARCH_SPIDER; 50 51// Identify ourself 52define('WT_WEBTREES', 'webtrees'); 53define('WT_VERSION', '1.7.18'); 54 55// External URLs 56define('WT_WEBTREES_URL', 'https://www.webtrees.net/'); 57define('WT_WEBTREES_WIKI', 'https://wiki.webtrees.net/'); 58 59// Resources have version numbers in the URL, so that they can be cached indefinitely. 60define('WT_STATIC_URL', getenv('STATIC_URL')); // We could set this to load our own static resources from a cookie-free domain. 61 62if (getenv('USE_CDN')) { 63 // Caution, using a CDN will break support for responsive features in IE8, as respond.js 64 // needs to be on the same domain as all the CSS files. 65 define('WT_BOOTSTRAP_CSS_URL', '//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css'); 66 define('WT_BOOTSTRAP_DATETIMEPICKER_CSS_URL', '//cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.37/css/bootstrap-datetimepicker.min.css'); 67 define('WT_BOOTSTRAP_DATETIMEPICKER_JS_URL', '//cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.37/js/bootstrap-datetimepicker.min.js'); 68 define('WT_BOOTSTRAP_JS_URL', '//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/js/bootstrap.min.js'); 69 define('WT_BOOTSTRAP_RTL_CSS_URL', '//cdnjs.cloudflare.com/ajax/libs/bootstrap-rtl/3.2.0-rc2/css/bootstrap-rtl.min.css'); // Cloudflare is out of date 70 //define('WT_DATATABLES_BOOTSTRAP_CSS_URL', '//cdn.datatables.net/plug-ins/1.10.7/integration/bootstrap/3/dataTables.bootstrap.css'); 71 define('WT_DATATABLES_BOOTSTRAP_JS_URL', '//cdn.datatables.net/plug-ins/1.10.7/integration/bootstrap/3/dataTables.bootstrap.js'); 72 define('WT_FONT_AWESOME_CSS_URL', '//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.4.0/css/font-awesome.min.css'); 73 define('WT_JQUERYUI_JS_URL', '//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js'); 74 define('WT_JQUERYUI_TOUCH_PUNCH_URL', '//cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.3/jquery.ui.touch-punch.min.js'); 75 define('WT_JQUERY_DATATABLES_JS_URL', '//cdnjs.cloudflare.com/ajax/libs/datatables/1.10.7/js/jquery.dataTables.min.js'); 76 define('WT_JQUERY_JS_URL', '//cdnjs.cloudflare.com/ajax/libs/jquery/1.12.1/jquery.min.js'); 77 define('WT_JQUERY2_JS_URL', '//cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js'); 78 define('WT_MODERNIZR_JS_URL', '//cdnjs.cloudflare.com/ajax/libs/modernizr/2.8.3/modernizr.min.js'); 79 define('WT_MOMENT_JS_URL', '//cdnjs.cloudflare.com/ajax/libs/moment.js/2.11.2/moment-with-locales.min.js'); 80 define('WT_RESPOND_JS_URL', '//cdnjs.cloudflare.com/ajax/libs/respond.js/1.4.2/respond.min.js'); 81} else { 82 define('WT_BOOTSTRAP_CSS_URL', WT_STATIC_URL . 'packages/bootstrap-3.3.6/css/bootstrap.min.css'); 83 define('WT_BOOTSTRAP_DATETIMEPICKER_CSS_URL', WT_STATIC_URL . 'packages/bootstrap-datetimepicker-4.17.37/css/bootstrap-datetimepicker.min.css'); 84 define('WT_BOOTSTRAP_DATETIMEPICKER_JS_URL', WT_STATIC_URL . 'packages/bootstrap-datetimepicker-4.17.37/js/bootstrap-datetimepicker.min.js'); 85 define('WT_BOOTSTRAP_JS_URL', WT_STATIC_URL . 'packages/bootstrap-3.3.6/js/bootstrap.min.js'); 86 define('WT_BOOTSTRAP_RTL_CSS_URL', WT_STATIC_URL . 'packages/bootstrap-rtl-3.3.4/css/bootstrap-rtl.min.css'); 87 //define('WT_DATATABLES_BOOTSTRAP_CSS_URL', WT_STATIC_URL . 'packages/datatables-1.10.7/plugins/dataTables.bootstrap.css'); 88 define('WT_DATATABLES_BOOTSTRAP_JS_URL', WT_STATIC_URL . 'packages/datatables-1.10.7/plugins/dataTables.bootstrap.js'); 89 define('WT_FONT_AWESOME_CSS_URL', WT_STATIC_URL . 'packages/font-awesome-4.4.0/css/font-awesome.min.css'); 90 define('WT_JQUERYUI_JS_URL', WT_STATIC_URL . 'packages/jquery-ui-1.11.4/js/jquery-ui.min.js'); 91 define('WT_JQUERYUI_TOUCH_PUNCH_URL', WT_STATIC_URL . 'packages/jqueryui-touch-punch-0.2.3/jquery.ui.touch-punch.min.js'); 92 define('WT_JQUERY_DATATABLES_JS_URL', WT_STATIC_URL . 'packages/datatables-1.10.7/js/jquery.dataTables.min.js'); 93 define('WT_JQUERY_JS_URL', WT_STATIC_URL . 'packages/jquery-1.12.1/jquery.min.js'); 94 define('WT_JQUERY2_JS_URL', WT_STATIC_URL . 'packages/jquery-2.2.1/jquery.min.js'); 95 define('WT_MODERNIZR_JS_URL', WT_STATIC_URL . 'packages/modernizr-2.8.3/modernizr.min.js'); 96 define('WT_MOMENT_JS_URL', WT_STATIC_URL . 'packages/moment-2.11.2/moment-with-locales.min.js'); 97 define('WT_RESPOND_JS_URL', WT_STATIC_URL . 'packages/respond-1.4.2/respond.min.js'); 98} 99 100// We can't load these from a CDN, as these have been patched. 101define('WT_JQUERY_COLORBOX_URL', WT_STATIC_URL . 'assets/js-1.7.9/jquery.colorbox-1.5.14.js'); 102define('WT_JQUERY_WHEELZOOM_URL', WT_STATIC_URL . 'assets/js-1.7.9/jquery.wheelzoom-2.0.0.js'); 103define('WT_CKEDITOR_BASE_URL', WT_STATIC_URL . 'packages/ckeditor-4.5.2-custom/'); 104// See https://github.com/DataTables/Plugins/pull/178 105define('WT_DATATABLES_BOOTSTRAP_CSS_URL', WT_STATIC_URL . 'packages/datatables-1.10.7/plugins/dataTables.bootstrap-rtl.css'); 106 107// Location of our own scripts 108define('WT_ADMIN_JS_URL', WT_STATIC_URL . 'assets/js-1.7.9/admin.js'); 109define('WT_AUTOCOMPLETE_JS_URL', WT_STATIC_URL . 'assets/js-1.7.9/autocomplete.js'); 110define('WT_WEBTREES_JS_URL', WT_STATIC_URL . 'assets/js-1.7.9/webtrees.js'); 111define('WT_FONT_AWESOME_RTL_CSS_URL', WT_STATIC_URL . 'assets/js-1.7.9/font-awesome-rtl.css'); 112 113// Location of our modules and themes. These are used as URLs and folder paths. 114define('WT_MODULES_DIR', 'modules_v3/'); // Update setup.php and build/Makefile when this changes 115define('WT_THEMES_DIR', 'themes/'); 116 117// Enable debugging output on development builds 118define('WT_DEBUG', strpos(WT_VERSION, 'dev') !== false); 119define('WT_DEBUG_SQL', false); 120 121// Required version of database tables/columns/indexes/etc. 122define('WT_SCHEMA_VERSION', 37); 123 124// Regular expressions for validating user input, etc. 125define('WT_MINIMUM_PASSWORD_LENGTH', 6); 126define('WT_REGEX_XREF', '[A-Za-z0-9:_-]+'); 127define('WT_REGEX_TAG', '[_A-Z][_A-Z0-9]*'); 128define('WT_REGEX_INTEGER', '-?\d+'); 129define('WT_REGEX_BYTES', '[0-9]+[bBkKmMgG]?'); 130define('WT_REGEX_IPV4', '\d{1,3}(\.\d{1,3}){3}'); 131define('WT_REGEX_PASSWORD', '.{' . WT_MINIMUM_PASSWORD_LENGTH . ',}'); 132 133// UTF8 representation of various characters 134define('WT_UTF8_BOM', "\xEF\xBB\xBF"); // U+FEFF (Byte order mark) 135define('WT_UTF8_LRM', "\xE2\x80\x8E"); // U+200E (Left to Right mark: zero-width character with LTR directionality) 136define('WT_UTF8_RLM', "\xE2\x80\x8F"); // U+200F (Right to Left mark: zero-width character with RTL directionality) 137define('WT_UTF8_LRO', "\xE2\x80\xAD"); // U+202D (Left to Right override: force everything following to LTR mode) 138define('WT_UTF8_RLO', "\xE2\x80\xAE"); // U+202E (Right to Left override: force everything following to RTL mode) 139define('WT_UTF8_LRE', "\xE2\x80\xAA"); // U+202A (Left to Right embedding: treat everything following as LTR text) 140define('WT_UTF8_RLE', "\xE2\x80\xAB"); // U+202B (Right to Left embedding: treat everything following as RTL text) 141define('WT_UTF8_PDF', "\xE2\x80\xAC"); // U+202C (Pop directional formatting: restore state prior to last LRO, RLO, LRE, RLE) 142 143// Alternatives to BMD events for lists, charts, etc. 144define('WT_EVENTS_BIRT', 'BIRT|CHR|BAPM|_BRTM|ADOP'); 145define('WT_EVENTS_DEAT', 'DEAT|BURI|CREM'); 146define('WT_EVENTS_MARR', 'MARR|_NMR'); 147define('WT_EVENTS_DIV', 'DIV|ANUL|_SEPR'); 148 149// Use these line endings when writing files on the server 150define('WT_EOL', "\r\n"); 151 152// Gedcom specification/definitions 153define('WT_GEDCOM_LINE_LENGTH', 255 - strlen(WT_EOL)); // Characters, not bytes 154 155// Used in Google charts 156define('WT_GOOGLE_CHART_ENCODING', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.'); 157 158// For performance, it is quicker to refer to files using absolute paths 159define('WT_ROOT', realpath(dirname(__DIR__)) . DIRECTORY_SEPARATOR); 160 161// Keep track of time statistics, for the summary in the footer 162define('WT_START_TIME', microtime(true)); 163 164// We want to know about all PHP errors during development, and fewer in production. 165if (WT_DEBUG) { 166 error_reporting(E_ALL | E_STRICT | E_NOTICE | E_DEPRECATED); 167} else { 168 error_reporting(E_ALL); 169} 170 171require WT_ROOT . 'vendor/autoload.php'; 172 173// PHP requires a time zone to be set. We'll set a better one later on. 174date_default_timezone_set('UTC'); 175 176// Calculate the base URL, so we can generate absolute URLs. 177$request = Request::createFromGlobals(); 178$request_uri = $request->getSchemeAndHttpHost() . $request->getRequestUri(); 179 180// Remove any PHP script name and parameters. 181$base_uri = preg_replace('/[^\/]+\.php(\?.*)?$/', '', $request_uri); 182define('WT_BASE_URL', $base_uri); 183 184// Convert PHP warnings/notices into exceptions 185set_error_handler(function ($errno, $errstr, $errfile, $errline) { 186 // Ignore errors that are silenced with '@' 187 if (error_reporting() & $errno) { 188 throw new \ErrorException($errstr, 0, $errno, $errfile, $errline); 189 } 190}); 191 192set_exception_handler(function ($ex) { 193 $message = $ex->getFile() . ':' . $ex->getLine() . ' ' . $ex->getMessage() . PHP_EOL; 194 195 foreach ($ex->getTrace() as $level => $frame) { 196 $frame += array('args' => array(), 'file' => 'unknown', 'line' => 'unknown'); 197 array_walk($frame['args'], function (&$arg) { 198 switch (gettype($arg)) { 199 case 'boolean': 200 case 'integer': 201 case 'double': 202 case 'null': 203 $arg = var_export($arg, true); 204 break; 205 case 'string': 206 if (mb_strlen($arg) > 30) { 207 $arg = substr($arg, 0, 30) . '…'; 208 } 209 $arg = var_export($arg, true); 210 break; 211 case 'object': 212 $reflection = new \ReflectionClass($arg); 213 if (is_object($arg) && method_exists($arg, '__toString')) { 214 $arg = '[' . $reflection->getShortName() . ' ' . (string) $arg . ']'; 215 } else { 216 $arg = '[' . $reflection->getShortName() . ']'; 217 } 218 break; 219 default: 220 $arg = '[' . gettype($arg) . ']'; 221 break; 222 } 223 }); 224 $frame['file'] = str_replace(dirname(__DIR__), '', $frame['file']); 225 $message .= '#' . $level . ' ' . $frame['file'] . ':' . $frame['line'] . ' '; 226 if ($level) { 227 $message .= $frame['function'] . '(' . implode(', ', $frame['args']) . ')' . PHP_EOL; 228 } else { 229 $message .= get_class($ex) . '("' . $ex->getMessage() . '")' . PHP_EOL; 230 } 231 } 232 233 if ($ex instanceof PDOException || error_reporting() & $ex->getCode()) { 234 echo $message; 235 } 236 237 Log::addErrorLog($message); 238}); 239 240// Load our configuration file, so we can connect to the database 241if (file_exists(WT_ROOT . 'data/config.ini.php')) { 242 $dbconfig = parse_ini_file(WT_ROOT . 'data/config.ini.php'); 243 // Invalid/unreadable config file? 244 if (!is_array($dbconfig)) { 245 header('Location: ' . WT_BASE_URL . 'site-unavailable.php'); 246 exit; 247 } 248 // Down for maintenance? 249 if (file_exists(WT_ROOT . 'data/offline.txt')) { 250 header('Location: ' . WT_BASE_URL . 'site-offline.php'); 251 exit; 252 } 253} else { 254 // No config file. Set one up. 255 header('Location: ' . WT_BASE_URL . 'setup.php'); 256 exit; 257} 258 259// What is the remote client's IP address 260if (Filter::server('HTTP_CLIENT_IP') !== null) { 261 define('WT_CLIENT_IP', Filter::server('HTTP_CLIENT_IP')); 262} elseif (Filter::server('HTTP_X_FORWARDED_FOR') !== null) { 263 define('WT_CLIENT_IP', Filter::server('HTTP_X_FORWARDED_FOR')); 264} else { 265 define('WT_CLIENT_IP', Filter::server('REMOTE_ADDR', WT_REGEX_IPV4, '127.0.0.1')); 266} 267 268// Connect to the database 269try { 270 Database::createInstance($dbconfig['dbhost'], $dbconfig['dbport'], $dbconfig['dbname'], $dbconfig['dbuser'], $dbconfig['dbpass']); 271 define('WT_TBLPREFIX', $dbconfig['tblpfx']); 272 unset($dbconfig); 273 // Some of the FAMILY JOIN HUSBAND JOIN WIFE queries can excede the MAX_JOIN_SIZE setting 274 Database::exec("SET NAMES 'utf8' COLLATE 'utf8_unicode_ci', SQL_BIG_SELECTS=1"); 275 // Update the database schema 276 $updated = Database::updateSchema('\Fisharebest\Webtrees\Schema', 'WT_SCHEMA_VERSION', WT_SCHEMA_VERSION); 277 if ($updated) { 278 // updateSchema() might load custom modules - which we cannot load again. 279 header('Location: ' . WT_BASE_URL . WT_SCRIPT_NAME . (isset($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '')); 280 exit; 281 } 282} catch (PDOException $ex) { 283 header('Location: ' . WT_BASE_URL . 'site-unavailable.php?message=' . rawurlencode($ex->getMessage())); 284 exit; 285} 286 287// The config.ini.php file must always be in a fixed location. 288// Other user files can be stored elsewhere... 289define('WT_DATA_DIR', realpath(Site::getPreference('INDEX_DIRECTORY') ? Site::getPreference('INDEX_DIRECTORY') : 'data') . DIRECTORY_SEPARATOR); 290 291// If we have a preferred URL (e.g. www.example.com instead of www.isp.com/~example), then redirect to it. 292$SERVER_URL = Site::getPreference('SERVER_URL'); 293if ($SERVER_URL && $SERVER_URL != WT_BASE_URL) { 294 header('Location: ' . $SERVER_URL . WT_SCRIPT_NAME . (isset($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''), true, 301); 295 exit; 296} 297 298// Request more resources - if we can/want to 299if (!ini_get('safe_mode')) { 300 $memory_limit = Site::getPreference('MEMORY_LIMIT'); 301 if ($memory_limit && strpos(ini_get('disable_functions'), 'ini_set') === false) { 302 ini_set('memory_limit', $memory_limit); 303 } 304 $max_execution_time = Site::getPreference('MAX_EXECUTION_TIME'); 305 if ($max_execution_time && strpos(ini_get('disable_functions'), 'set_time_limit') === false) { 306 set_time_limit($max_execution_time); 307 } 308} 309 310$rule = Database::prepare( 311 "SELECT rule FROM `##site_access_rule`" . 312 " WHERE IFNULL(INET_ATON(?), 0) BETWEEN ip_address_start AND ip_address_end" . 313 " AND ? LIKE user_agent_pattern" . 314 " ORDER BY ip_address_end LIMIT 1" 315)->execute(array(WT_CLIENT_IP, Filter::server('HTTP_USER_AGENT', null, '')))->fetchOne(); 316 317switch ($rule) { 318 case 'allow': 319 $SEARCH_SPIDER = false; 320 break; 321 case 'deny': 322 http_response_code(403); 323 exit; 324 case 'robot': 325 case 'unknown': 326 // Search engines don’t send cookies, and so create a new session with every visit. 327 // Make sure they always use the same one 328 Session::setId('search-engine-' . str_replace('.', '-', WT_CLIENT_IP)); 329 $SEARCH_SPIDER = true; 330 break; 331 case '': 332 Database::prepare( 333 "INSERT INTO `##site_access_rule` (ip_address_start, ip_address_end, user_agent_pattern, comment) VALUES (IFNULL(INET_ATON(?), 0), IFNULL(INET_ATON(?), 4294967295), ?, '')" 334 )->execute(array(WT_CLIENT_IP, WT_CLIENT_IP, Filter::server('HTTP_USER_AGENT', null, ''))); 335 $SEARCH_SPIDER = true; 336 break; 337} 338 339// Store our session data in the database. 340session_set_save_handler( 341 // open 342 function () { 343 return true; 344 }, 345 // close 346 function () { 347 return true; 348 }, 349 // read 350 function ($id) { 351 return (string) Database::prepare("SELECT session_data FROM `##session` WHERE session_id=?")->execute(array($id))->fetchOne(); 352 }, 353 // write 354 function ($id, $data) { 355 // Only update the session table once per minute, unless the session data has actually changed. 356 Database::prepare( 357 "INSERT INTO `##session` (session_id, user_id, ip_address, session_data, session_time)" . 358 " VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP - SECOND(CURRENT_TIMESTAMP))" . 359 " ON DUPLICATE KEY UPDATE" . 360 " user_id = VALUES(user_id)," . 361 " ip_address = VALUES(ip_address)," . 362 " session_data = VALUES(session_data)," . 363 " session_time = CURRENT_TIMESTAMP - SECOND(CURRENT_TIMESTAMP)" 364 )->execute(array($id, (int) Auth::id(), WT_CLIENT_IP, $data)); 365 366 return true; 367 }, 368 // destroy 369 function ($id) { 370 Database::prepare("DELETE FROM `##session` WHERE session_id=?")->execute(array($id)); 371 372 return true; 373 }, 374 // gc 375 function ($maxlifetime) { 376 Database::prepare("DELETE FROM `##session` WHERE session_time < DATE_SUB(NOW(), INTERVAL ? SECOND)")->execute(array($maxlifetime)); 377 378 return true; 379 } 380); 381 382Session::start(array( 383 'gc_maxlifetime' => Site::getPreference('SESSION_TIME'), 384 'cookie_path' => implode('/', array_map('rawurlencode', explode('/', parse_url(WT_BASE_URL, PHP_URL_PATH)))), 385)); 386 387if (!Auth::isSearchEngine() && !Session::get('initiated')) { 388 // A new session, so prevent session fixation attacks by choosing a new PHPSESSID. 389 Session::regenerate(true); 390 Session::put('initiated', true); 391} else { 392 // An existing session 393} 394 395// Set the tree for the page; (1) the request, (2) the session, (3) the site default, (4) any tree 396foreach (array(Filter::post('ged'), Filter::get('ged'), Session::get('GEDCOM'), Site::getPreference('DEFAULT_GEDCOM')) as $tree_name) { 397 $WT_TREE = Tree::findByName($tree_name); 398 if ($WT_TREE) { 399 Session::put('GEDCOM', $tree_name); 400 break; 401 } 402} 403// No chosen tree? Use any one. 404if (!$WT_TREE) { 405 foreach (Tree::getAll() as $WT_TREE) { 406 break; 407 } 408} 409 410// With no parameters, init() looks to the environment to choose a language 411define('WT_LOCALE', I18N::init()); 412Session::put('locale', WT_LOCALE); 413 414// Note that the database/webservers may not be synchronised, so use DB time throughout. 415define('WT_TIMESTAMP', (int) Database::prepare("SELECT UNIX_TIMESTAMP()")->fetchOne()); 416 417// Users get their own time-zone. Visitors get the site time-zone. 418if (Auth::check()) { 419 date_default_timezone_set(Auth::user()->getPreference('TIMEZONE', 'UTC')); 420} else { 421 date_default_timezone_set(Site::getPreference('TIMEZONE') ?: 'UTC'); 422} 423define('WT_TIMESTAMP_OFFSET', date_offset_get(new \DateTime('now'))); 424 425define('WT_CLIENT_JD', 2440588 + (int) ((WT_TIMESTAMP + WT_TIMESTAMP_OFFSET) / 86400)); 426 427// The login URL must be an absolute URL, and can be user-defined 428if (Site::getPreference('LOGIN_URL')) { 429 define('WT_LOGIN_URL', Site::getPreference('LOGIN_URL')); 430} else { 431 define('WT_LOGIN_URL', WT_BASE_URL . 'login.php'); 432} 433 434// If there is no current tree and we need one, then redirect somewhere 435if (WT_SCRIPT_NAME != 'admin_trees_manage.php' && WT_SCRIPT_NAME != 'admin_pgv_to_wt.php' && WT_SCRIPT_NAME != 'login.php' && WT_SCRIPT_NAME != 'logout.php' && WT_SCRIPT_NAME != 'import.php' && WT_SCRIPT_NAME != 'help_text.php' && WT_SCRIPT_NAME != 'message.php' && WT_SCRIPT_NAME != 'action.php') { 436 if (!$WT_TREE || !$WT_TREE->getPreference('imported')) { 437 if (Auth::isAdmin()) { 438 header('Location: ' . WT_BASE_URL . 'admin_trees_manage.php'); 439 } else { 440 // We're not an administrator, so we can only log in if there is a tree. 441 if (Auth::id()) { 442 Auth::logout(); 443 FlashMessages::addMessage( 444 I18N::translate('This user account does not have access to any tree.') 445 ); 446 } 447 header('Location: ' . WT_LOGIN_URL . '?url=' . rawurlencode(WT_SCRIPT_NAME . (isset($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '')), true, 301); 448 449 } 450 exit; 451 } 452} 453 454// Update the last-login time no more than once a minute 455if (WT_TIMESTAMP - Session::get('activity_time') >= 60) { 456 if (Session::get('masquerade') === null) { 457 Auth::user()->setPreference('sessiontime', WT_TIMESTAMP); 458 } 459 Session::put('activity_time', WT_TIMESTAMP); 460} 461 462// Set the theme 463if (substr(WT_SCRIPT_NAME, 0, 5) === 'admin' || WT_SCRIPT_NAME === 'module.php' && substr(Filter::get('mod_action'), 0, 5) === 'admin') { 464 // Administration scripts begin with “admin” and use a special administration theme 465 Theme::theme(new AdministrationTheme)->init($WT_TREE); 466} else { 467 // Last theme used? 468 $theme_id = Session::get('theme_id'); 469 // Default for tree 470 if (!array_key_exists($theme_id, Theme::themeNames()) && $WT_TREE) { 471 $theme_id = $WT_TREE->getPreference('THEME_DIR'); 472 } 473 // Default for site 474 if (!array_key_exists($theme_id, Theme::themeNames())) { 475 $theme_id = Site::getPreference('THEME_DIR'); 476 } 477 // Default 478 if (!array_key_exists($theme_id, Theme::themeNames())) { 479 $theme_id = 'webtrees'; 480 } 481 foreach (Theme::installedThemes() as $theme) { 482 if ($theme->themeId() === $theme_id) { 483 Theme::theme($theme)->init($WT_TREE); 484 // Remember this setting 485 if (Site::getPreference('ALLOW_USER_THEMES')) { 486 Session::put('theme_id', $theme_id); 487 } 488 break; 489 } 490 } 491} 492 493// Search engines are only allowed to see certain pages. 494if (Auth::isSearchEngine() && !in_array(WT_SCRIPT_NAME, array( 495 'index.php', 'indilist.php', 'module.php', 'mediafirewall.php', 496 'individual.php', 'family.php', 'mediaviewer.php', 'note.php', 'repo.php', 'source.php', 497))) { 498 http_response_code(403); 499 $controller = new PageController; 500 $controller->setPageTitle(I18N::translate('Search engine')); 501 $controller->pageHeader(); 502 echo '<p class="ui-state-error">', I18N::translate('You do not have permission to view this page.'), '</p>'; 503 exit; 504} 505