1<?php 2 3/** 4 * Bootstrap phpMyFAQ. 5 * 6 * This Source Code Form is subject to the terms of the Mozilla Public License, 7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can 8 * obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * @package phpMyFAQ 11 * @author Thorsten Rinne <thorsten@phpmyfaq.de> 12 * @copyright 2012-2020 phpMyFAQ Team 13 * @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0 14 * @link https://www.phpmyfaq.de 15 * @since 2012-03-07 16 */ 17 18use Composer\Autoload\ClassLoader; 19use Elasticsearch\ClientBuilder; 20use phpMyFAQ\Configuration; 21use phpMyFAQ\Database; 22use phpMyFAQ\Exception; 23use phpMyFAQ\Init; 24 25// 26// Debug mode: 27// - false debug mode disabled 28// - true debug mode enabled 29// 30define('DEBUG', false); 31if (DEBUG) { 32 ini_set('display_errors', 1); 33 ini_set('display_startup_errors', 1); 34 error_reporting(E_ALL | E_STRICT); 35} else { 36 error_reporting(0); 37} 38 39// 40// Fix the PHP include path if PMF is running under a "strange" PHP configuration 41// 42$foundCurrPath = false; 43$includePaths = explode(PATH_SEPARATOR, ini_get('include_path')); 44$i = 0; 45while ((!$foundCurrPath) && ($i < count($includePaths))) { 46 if ('.' == $includePaths[$i]) { 47 $foundCurrPath = true; 48 } 49 ++$i; 50} 51if (!$foundCurrPath) { 52 ini_set('include_path', '.' . PATH_SEPARATOR . ini_get('include_path')); 53} 54 55// 56// Tweak some PHP configuration values 57// Warning: be sure the server has enough memory and stack for PHP 58// 59ini_set('pcre.backtrack_limit', 100000000); 60ini_set('pcre.recursion_limit', 100000000); 61 62// 63// The root directory 64// 65if (!defined('PMF_ROOT_DIR')) { 66 define('PMF_ROOT_DIR', dirname(__DIR__)); 67} 68 69// 70// Check if multisite/multisite.php exist for Multisite support 71// 72if (file_exists(PMF_ROOT_DIR . '/multisite/multisite.php') && 'cli' !== PHP_SAPI) { 73 require PMF_ROOT_DIR . '/multisite/multisite.php'; 74} 75 76// 77// Read configuration and constants 78// 79if (!defined('PMF_MULTI_INSTANCE_CONFIG_DIR')) { 80 define('PMF_CONFIG_DIR', PMF_ROOT_DIR . '/config'); // Single instance configuration 81} else { 82 define('PMF_CONFIG_DIR', PMF_MULTI_INSTANCE_CONFIG_DIR); // Multi instance configuration 83} 84 85// 86// Check if config/database.php exist -> if not, redirect to installer 87// 88if (!file_exists(PMF_CONFIG_DIR . '/database.php')) { 89 header('Location: ./setup/index.php'); 90 exit(); 91} 92 93require PMF_CONFIG_DIR . '/database.php'; 94require PMF_CONFIG_DIR . '/constants.php'; 95 96/* 97 * The /src directory 98 */ 99define('PMF_SRC_DIR', __DIR__); 100 101/* 102 * The directory where the translations reside 103 */ 104define('PMF_LANGUAGE_DIR', dirname(__DIR__) . '/lang'); 105 106// 107// Setting up autoloader 108// 109require PMF_SRC_DIR . '/libs/autoload.php'; 110 111$loader = new ClassLoader(); 112$loader->add('phpMyFAQ', PMF_SRC_DIR); 113$loader->addPsr4('Abraham\\TwitterOAuth\\', PMF_SRC_DIR . '/libs/abraham/twitteroauth/src'); 114$loader->register(); 115 116require PMF_SRC_DIR . '/libs/parsedown/Parsedown.php'; 117require PMF_SRC_DIR . '/libs/parsedown/ParsedownExtra.php'; 118 119// 120// Set the error handler to our phpMyFAQErrorHandler() function 121// 122set_error_handler('phpMyFAQErrorHandler'); 123 124// 125// Create a database connection 126// 127try { 128 Database::setTablePrefix($DB['prefix']); 129 $db = Database::factory($DB['type']); 130 $db->connect($DB['server'], $DB['user'], $DB['password'], $DB['db'], isset($DB['port']) ? $DB['port'] : null); 131} catch (Exception $e) { 132 Database::errorPage($e->getMessage()); 133 exit(-1); 134} 135 136// 137// Fetch the configuration and add the database connection 138// 139$faqConfig = new Configuration($db); 140$faqConfig->getAll(); 141 142// 143// We always need a valid session! 144// 145ini_set('session.use_only_cookies', 1); // Avoid any PHP version to move sessions on URLs 146ini_set('session.auto_start', 0); // Prevent error to use session_start() if it's active in php.ini 147ini_set('session.use_trans_sid', 0); 148ini_set('session.cookie_samesite', 'Strict'); 149ini_set('session.cookie_httponly', true); 150ini_set('url_rewriter.tags', ''); 151 152// 153// Start the PHP session 154// 155Init::cleanRequest(); 156if (defined('PMF_SESSION_SAVE_PATH') && !empty(PMF_SESSION_SAVE_PATH)) { 157 session_save_path(PMF_SESSION_SAVE_PATH); 158} 159session_start(); 160 161// 162// Connect to LDAP server, when LDAP support is enabled 163// 164if ($faqConfig->get('ldap.ldapSupport') && file_exists(PMF_CONFIG_DIR . '/ldap.php') && extension_loaded('ldap')) { 165 require PMF_CONFIG_DIR . '/ldap.php'; 166 $faqConfig->setLdapConfig($PMF_LDAP); 167} else { 168 $ldap = null; 169} 170// 171// Connect to Elasticsearch if enabled 172// 173if ($faqConfig->get('search.enableElasticsearch') && file_exists(PMF_CONFIG_DIR . '/elasticsearch.php')) { 174 175 require PMF_CONFIG_DIR . '/elasticsearch.php'; 176 require PMF_CONFIG_DIR . '/constants_elasticsearch.php'; 177 178 $psr4Loader = new ClassLoader(); 179 $psr4Loader->addPsr4('Elasticsearch\\', PMF_SRC_DIR . '/libs/elasticsearch/src/Elasticsearch'); 180 $psr4Loader->addPsr4('GuzzleHttp\\Ring\\', PMF_SRC_DIR . '/libs/guzzlehttp/ringphp/src'); 181 $psr4Loader->addPsr4('Monolog\\', PMF_SRC_DIR . '/libs/monolog/src/Monolog'); 182 $psr4Loader->addPsr4('Psr\\', PMF_SRC_DIR . '/libs/psr/log/Psr'); 183 $psr4Loader->addPsr4('React\\Promise\\', PMF_SRC_DIR . '/libs/react/promise/src'); 184 $psr4Loader->register(); 185 186 $esClient = ClientBuilder::create() 187 ->setHosts($PMF_ES['hosts']) 188 ->build(); 189 190 $faqConfig->setElasticsearch($esClient); 191 $faqConfig->setElasticsearchConfig($PMF_ES); 192} 193 194// 195// Build attachments path 196// 197$confAttachmentsPath = trim($faqConfig->get('records.attachmentsPath')); 198if ('/' == $confAttachmentsPath[0] || preg_match('%^[a-z]:(\\\\|/)%i', $confAttachmentsPath)) { 199 // If we're here, some windows or unix style absolute path was detected. 200 define('PMF_ATTACHMENTS_DIR', $confAttachmentsPath); 201} else { 202 // otherwise build the absolute path 203 $tmp = dirname(__DIR__) . DIRECTORY_SEPARATOR . $confAttachmentsPath; 204 205 // Check that nobody is traversing 206 if (0 === strpos((string)$tmp, dirname(__DIR__))) { 207 define('PMF_ATTACHMENTS_DIR', $tmp); 208 } else { 209 define('PMF_ATTACHMENTS_DIR', false); 210 } 211} 212 213// 214// Fix if phpMyFAQ is running behind a proxy server 215// 216if (!isset($_SERVER['HTTP_HOST'])) { 217 if (isset($_SERVER['HTTP_X_FORWARDED_SERVER'])) { 218 $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_SERVER']; 219 } else { 220 $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_HOST']; 221 } 222} 223 224// 225// Fix undefined server variables in Windows IIS & CGI mode 226// 227if (!isset($_SERVER['SCRIPT_NAME'])) { 228 if (isset($_SERVER['SCRIPT_FILENAME'])) { 229 $_SERVER['SCRIPT_NAME'] = $_SERVER['SCRIPT_FILENAME']; 230 } elseif (isset($_SERVER['PATH_TRANSLATED'])) { 231 $_SERVER['SCRIPT_NAME'] = $_SERVER['PATH_TRANSLATED']; 232 } elseif (isset($_SERVER['PATH_INFO'])) { 233 $_SERVER['SCRIPT_NAME'] = $_SERVER['PATH_INFO']; 234 } elseif (isset($_SERVER['SCRIPT_URL'])) { 235 $_SERVER['SCRIPT_NAME'] = $_SERVER['SCRIPT_URL']; 236 } 237} 238 239// 240// phpMyFAQ exception log 241// 242$pmfExceptions = []; 243 244/** 245 * phpMyFAQ custom error handler function, also to prevent the disclosure of 246 * potential sensitive data. 247 * 248 * @param int $level The level of the error raised. 249 * @param string $message The error message. 250 * @param string $filename The filename that the error was raised in. 251 * @param int $line The line number the error was raised at. 252 * @return bool|null 253 */ 254function phpMyFAQErrorHandler($level, $message, $filename, $line) 255{ 256 // Sanity check 257 // Note: when DEBUG mode is true we want to track any error! 258 if ( 259 // 1. the @ operator sets the PHP's error_reporting() value to 0 260 (!DEBUG && (0 == error_reporting())) 261 // 2. Honor the value of PHP's error_reporting() function 262 || (!DEBUG && (0 == ($level & error_reporting()))) 263 ) { 264 // Do nothing 265 return true; 266 } 267 268 // Cleanup potential sensitive data 269 $filename = (DEBUG ? $filename : basename($filename)); 270 271 $errorTypes = [ 272 E_ERROR => 'error', 273 E_WARNING => 'warning', 274 E_PARSE => 'parse error', 275 E_NOTICE => 'notice', 276 E_CORE_ERROR => 'code error', 277 E_CORE_WARNING => 'core warning', 278 E_COMPILE_ERROR => 'compile error', 279 E_COMPILE_WARNING => 'compile warning', 280 E_USER_ERROR => 'user error', 281 E_USER_WARNING => 'user warning', 282 E_USER_NOTICE => 'user notice', 283 E_STRICT => 'strict warning', 284 E_RECOVERABLE_ERROR => 'recoverable error', 285 E_DEPRECATED => 'deprecated warning', 286 E_USER_DEPRECATED => 'user deprecated warning', 287 ]; 288 $errorType = 'unknown error'; 289 if (isset($errorTypes[$level])) { 290 $errorType = $errorTypes[$level]; 291 } 292 293 // Custom error message 294 $errorMessage = sprintf( 295 '<br><strong>phpMyFAQ %s</strong> [%s]: %s in <strong>%s</strong> on line <strong>%d</strong><br>', 296 $errorType, 297 $level, 298 $message, 299 $filename, 300 $line 301 ); 302 303 if (ini_get('display_errors')) { 304 echo $errorMessage; 305 } 306 if (ini_get('log_errors')) { 307 error_log(sprintf('phpMyFAQ %s: %s in %s on line %d', 308 $errorType, 309 $message, 310 $filename, 311 $line) 312 ); 313 } 314 315 switch ($level) { 316 // Blocking errors 317 case E_ERROR: 318 case E_PARSE: 319 case E_CORE_ERROR: 320 case E_COMPILE_ERROR: 321 case E_USER_ERROR: 322 // Prevent processing any more PHP scripts 323 exit(); 324 break; 325 // Not blocking errors 326 default: 327 break; 328 } 329 330 return true; 331} 332