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