1<?php
2use Symfony\Component\DependencyInjection\ContainerBuilder;
3use Symfony\Component\Config\FileLocator;
4use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
5use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
6use Symfony\Component\Yaml\Yaml;
7use Tiki\Package\ExtensionManager as PackageExtensionManager;
8use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
9
10/**
11 * Tiki initialization functions and classes
12 *
13 * @package TikiWiki
14 * @subpackage lib\init
15 * @copyright (c) Copyright by authors of the Tiki Wiki CMS Groupware Project. All Rights Reserved. See copyright.txt for details and a complete list of authors.
16 * @licence Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
17 */
18// $Id$
19
20//this script may only be included - so its better to die if called directly.
21if (strpos($_SERVER['SCRIPT_NAME'], basename(__FILE__)) !== false) {
22	header('location: index.php');
23	exit;
24}
25
26if (! file_exists(__DIR__ . '/../../vendor_bundled/vendor/autoload.php')) {
27	$error = "Your Tiki is not completely installed because Composer has not been run to fetch package dependencies.\n" .
28		"You need to run 'sh setup.sh' from the command line.\n" .
29		"See https://doc.tiki.org/Composer for details.\n";
30
31	if (http_response_code() === false) { // if running in cli
32		$error = "\033[31m" . $error . "\e[0m\n";
33	}
34	die($error);
35}
36
37require_once __DIR__ . '/../../vendor_bundled/vendor/autoload.php'; // vendor libs bundled into tiki
38
39// vendor libs managed by the user using composer (if any)
40if (file_exists(__DIR__ . '/../../vendor/autoload.php')) {
41	// In some cases, the vendor folder may contain the files from the old vendor folder before migrating to
42	// vendor_bundled. In these cases eg. when unzipping a Tiki => 17.x on top of an existing Tiki <= 16.x instance,
43	// loading the autoload from the vendor folder will cause issues.
44	// We check for some core libraries (ZendFramework, Smarty and Adodb), if they are all present in the
45	// vendor folder we will consider that there is a old vendor folder, and skip loading the autoload.php unless
46	// there is a file called do_not_clean.txt inside the vendor folder (we will only check the file exists)
47	if (file_exists(__DIR__ . '/../../vendor/do_not_clean.txt')
48		|| ! ( // check the existence of critical files denoting a legacy vendor folder
49			(file_exists(__DIR__ . '/../../vendor/zendframework/zend-config/src/Config.php') //ZF2
50				|| file_exists(__DIR__ . '/../../vendor/bombayworks/zendframework1/library/Zend/Config.php')) //ZF1
51			&& (file_exists(__DIR__ . '/../../vendor/smarty/smarty/libs/Smarty.class.php') //Smarty
52				|| file_exists(__DIR__ . '/../../vendor/smarty/smarty/distribution/libs/Smarty.class.php')) //Smarty
53			&& file_exists(__DIR__ . '/../../vendor/adodb/adodb/adodb.inc.php') //Adodb
54		)) {
55		$autoloader = require_once __DIR__ . '/../../vendor/autoload.php';
56		// Autoload extension packages libs
57		foreach (\Tiki\Package\ExtensionManager::getEnabledPackageExtensions(false) as $package) {
58			if (is_dir($package['path'] . '/lib/') && strpos($package['path'], 'vendor_custom') === false) {
59				$autoloader->addPsr4(str_replace('/', '\\', $package['name']) . '\\', $package['path'] . '/lib/');
60			}
61		}
62	}
63}
64
65// vendor libraries managed by the user, packaged (if any)
66if (is_dir(__DIR__ . '/../../vendor_custom')) {
67	foreach (new DirectoryIterator(__DIR__ . '/../../vendor_custom') as $fileInfo) {
68		if (! $fileInfo->isDir() || $fileInfo->isDot()) {
69			continue;
70		}
71		if (file_exists($fileInfo->getPathname() . '/autoload.php')) {
72			require_once $fileInfo->getPathname() . '/autoload.php';
73			 // Autoload extension packages libs
74			$packagePath = $fileInfo->getPathname();
75			if (is_dir($packagePath . '/lib/') && $composerJson = json_decode(file_get_contents($packagePath . '/composer.json'), true)) {
76				$packageName = $composerJson['name'] ?? '';
77				if ($packageName && \Tiki\Package\ExtensionManager::isExtension($packageName, $packagePath) && \Tiki\Package\ExtensionManager::isEnabled($packageName)) {
78					$autoloader->addPsr4(str_replace('/', '\\', $packageName) . '\\', $packagePath . '/lib/');
79				}
80			}
81		}
82	}
83}
84
85spl_autoload_register('Tiki_Autoload::autoload');
86
87/**
88 * performs some checks on the underlying system, before initializing Tiki.
89 * @package TikiWiki\lib\init
90 */
91class TikiInit
92{
93	/**
94	 * dummy constructor
95	 */
96	function __construct()
97	{
98	}
99
100	static function getContainer()
101	{
102		/** @var ContainerBuilder $container */
103		static $container;
104
105		if ($container) {
106			return $container;
107		}
108
109		require_once 'lib/setup/twversion.class.php';
110		$TWV = new TWVersion();
111		$version = $TWV->getVersion();
112
113		$cache = TIKI_PATH . '/temp/cache/container.php';
114		if (is_readable($cache)) {
115			require_once $cache;
116
117			if (! class_exists('TikiCachedContainer')) {
118				// mangled or otherwise invalid container
119				unlink($cache);
120			} else {
121				$container = new TikiCachedContainer;
122
123				/* If the server moved or was upgraded, the container must be recreated */
124				if (TIKI_PATH == $container->getParameter('kernel.root_dir') &&
125						$container->hasParameter('tiki.version') &&					// no version before 15.0
126						$container->getParameter('tiki.version') === $version) {
127					if (TikiDb::get()) {
128						$container->set('tiki.lib.db', TikiDb::get());
129					}
130					return $container;
131				} else {
132					/* This server moved or was upgraded, container must be recreated */
133					unlink($cache);
134				}
135			}
136		}
137
138		$path = TIKI_PATH . '/db/config';
139		$container = new ContainerBuilder;
140		$container->addCompilerPass(new \Tiki\MailIn\Provider\CompilerPass);
141		$container->addCompilerPass(new \Tiki\Recommendation\Engine\CompilerPass);
142		$container->addCompilerPass(new \Tiki\Wiki\SlugManager\CompilerPass);
143		$container->addCompilerPass(new \Search\Federated\CompilerPass);
144		$container->addCompilerPass(new \Tracker\CompilerPass);
145
146		$container->setParameter('kernel.root_dir', TIKI_PATH);
147		$container->setParameter('tiki.version', $version);
148
149		$loader = new XmlFileLoader($container, new FileLocator($path));
150
151		$loader->load('tiki.xml');
152		$loader->load('controllers.xml');
153		$loader->load('mailin.xml');
154
155		try {
156			$loader->load('custom.xml');
157		} catch (InvalidArgumentException $e) {
158			// Do nothing, absence of custom.xml file is expected
159		}
160
161		$extensionPackagesDefinition = PackageExtensionManager::getEnabledPackageExtensions(false);
162		$container->setParameter('tiki.packages.extensions', $extensionPackagesDefinition);
163		foreach ($extensionPackagesDefinition as $packageDefinition) {
164			try {
165				$path = sprintf('%s/%s/config/services.xml', TIKI_PATH, $packageDefinition['path']);
166				$loader->load($path);
167			} catch (InvalidArgumentException $e) {
168				// Do nothing, absence of services.xml file is expected
169			}
170		}
171
172		if (TikiDb::get()) {
173			$container->set('tiki.lib.db', TikiDb::get());
174		}
175
176		$container->compile();
177
178		$dumper = new PhpDumper($container);
179		file_put_contents($cache, $dumper->dump([
180			'class' => 'TikiCachedContainer',
181		]));
182
183		return $container;
184	}
185
186/** Return 'windows' if windows, otherwise 'unix'
187 * \static
188 */
189	function os()
190	{
191		static $os;
192		if (! isset($os)) {
193			if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
194				$os = 'windows';
195			} else {
196				$os = 'unix';
197			}
198		}
199		return $os;
200	}
201
202
203/** Return true if windows, otherwise false
204  * @static
205  */
206	static function isWindows()
207	{
208		static $windows;
209		if (! isset($windows)) {
210			$windows = strtoupper(substr(PHP_OS, 0, 3)) == 'WIN';
211		}
212		return $windows;
213	}
214
215	/**
216	 * Copes with Windows permissions
217	 *
218	 * @param string $path directory to test
219	 *
220	 * @return bool
221	 */
222	static function is_writeable($path)
223	{
224		if (self::isWindows()) {
225			return self::is__writable($path);
226		} else {
227			return is_writeable($path);
228		}
229	}
230
231	/**
232	 * From the php is_writable manual (thanks legolas558 d0t users dot sf dot net)
233	 * Note the two underscores and no "e".
234	 *
235	 * will work in despite of Windows ACLs bug
236	 * NOTE: use a trailing slash for folders!!!
237	 * {@see http://bugs.php.net/bug.php?id=27609}
238	 * {@see http://bugs.php.net/bug.php?id=30931}
239	 *
240	 * @param string $path	directory to test	NOTE: use a trailing slash for folders!!!
241	 * @return bool
242	 */
243	static function is__writable($path)
244	{
245		if ($path{strlen($path) - 1} == '/') { // recursively return a temporary file path
246			return self::is__writable($path . uniqid(mt_rand()) . '.tmp');
247		} elseif (is_dir($path)) {
248			return self::is__writable($path . '/' . uniqid(mt_rand()) . '.tmp');
249		}
250		// check tmp file for read/write capabilities
251		$rm = file_exists($path);
252		$f = @fopen($path, 'a');
253		if ($f === false) {
254			return false;
255		}
256		fclose($f);
257		if (! $rm) {
258			unlink($path);
259		}
260		return true;
261	}
262
263
264	/** Prepend $path to the include path
265	 * @static
266	 * @param string $path the path to prepend
267	 * @return string
268	 */
269	static function prependIncludePath($path)
270	{
271		$include_path = ini_get('include_path');
272		$paths = explode(PATH_SEPARATOR, $include_path);
273
274		if ($include_path && ! in_array($path, $paths)) {
275			$include_path = $path . PATH_SEPARATOR . $include_path;
276		} elseif (! $include_path) {
277			$include_path = $path;
278		}
279
280		return set_include_path($include_path);
281	}
282
283
284	/** Append $path to the include path
285	 * @static
286	 * @param mixed $path
287	 */
288	static function appendIncludePath($path)
289	{
290		$include_path = ini_get('include_path');
291		$paths = explode(PATH_SEPARATOR, $include_path);
292
293		if ($include_path && ! in_array($path, $paths)) {
294			$include_path .= PATH_SEPARATOR . $path;
295		} elseif (! $include_path) {
296			$include_path = $path;
297		}
298
299		return set_include_path($include_path);
300	}
301
302
303	/** Return system defined temporary directory.
304	 * In Unix, this is usually /tmp
305	 * In Windows, this is usually c:\windows\temp or c:\winnt\temp
306	 * @static
307	 * @deprecated by sys_get_temp_dir()
308	 */
309	static function tempdir()
310	{
311		return sys_get_temp_dir();
312	}
313
314	/**
315	 * Convert a string to UTF-8. Fixes a bug in PHP decode
316	 * From http://w3.org/International/questions/qa-forms-utf-8.html
317	 * @static
318	 * @param string String to be converted
319	 * @return UTF-8 representation of the string
320	 */
321	static function to_utf8($string)
322	{
323		if (preg_match(
324			'%^(?:
325	  		   [\x09\x0A\x0D\x20-\x7E]            # ASCII
326   		 | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
327		    | \xE0[\xA0-\xBF][\x80-\xBF]         # excluding overlongs
328   		 | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
329		    | \xED[\x80-\x9F][\x80-\xBF]         # excluding surrogates
330			 | \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
331			 | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
332		    | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
333			)*$%xs',
334			$string
335		)
336		) {
337			return $string;
338		} else {
339			return iconv('CP1252', 'UTF-8', $string);
340		}
341	}
342
343	/**
344	 * Determine if the web server is an IIS server
345	 * @return true if IIS server, else false
346	   * @static
347	 */
348	static function isIIS()
349	{
350		static $IIS;
351
352		// Sample value Microsoft-IIS/7.5
353		if (! isset($IIS) && isset($_SERVER['SERVER_SOFTWARE'])) {
354			$IIS = substr($_SERVER['SERVER_SOFTWARE'], 0, 13) == 'Microsoft-IIS';
355		}
356
357		return $IIS;
358	}
359
360	/**
361	 * Determine if the web server is an IIS server
362	 * @return true if IIS server, else false
363	   * \static
364	 */
365	static function hasIIS_UrlRewriteModule()
366	{
367		return isset($_SERVER['IIS_UrlRewriteModule']) == true;
368	}
369
370	static function getCredentialsFile()
371	{
372		global $default_api_tiki, $api_tiki, $db_tiki, $dbversion_tiki, $host_tiki, $user_tiki, $pass_tiki, $dbs_tiki, $tikidomain, $tikidomainslash, $dbfail_url;
373		// Please use the local.php file instead containing these variables
374		// If you set sessions to store in the database, you will need a local.php file
375		// Otherwise you will be ok.
376		//$api_tiki		= 'pear';
377		//$api_tiki			= 'pdo';
378		$api_tiki			= 'pdo';
379		$db_tiki			= 'mysql';
380		$dbversion_tiki = '2.0';
381		$host_tiki		= 'localhost';
382		$user_tiki		= 'root';
383		$pass_tiki		= '';
384		$dbs_tiki			= 'tiki';
385		$tikidomain		= '';
386		$dbfail_url		= '';
387
388		/*
389		SVN Developers: Do not change any of the above.
390		Instead, create a file, called db/local.php, containing any of
391		the variables listed above that are different for your
392		development environment.  This will protect you from
393		accidentally committing your username/password to SVN!
394
395		example of db/local.php
396		<?php
397		$host_tiki   = 'myhost';
398		$user_tiki   = 'myuser';
399		$pass_tiki   = 'mypass';
400		$dbs_tiki    = 'mytiki';
401		$api_tiki    = 'adodb';
402
403		** Multi-tiki
404		**************************************
405		see http://tikiwiki.org/MultiTiki19
406
407		Setup of virtual tikis is done using setup.sh script
408		-----------------------------------------------------------
409		-> Multi-tiki trick for virtualhosting
410
411		$tikidomain variable is set to :
412		or TIKI_VIRTUAL
413			That is set in apache virtual conf : SetEnv TIKI_VIRTUAL myvirtual
414		or SERVER_NAME
415			From apache directive ServerName set for that virtualhost block
416		or HTTP_HOST
417			From the real domain name called in the browser
418			(can be ServerAlias from apache conf)
419
420		*/
421
422		if (! isset($local_php) or ! is_file($local_php)) {
423			$local_php = 'db/local.php';
424		} else {
425			$local_php = preg_replace(['/\.\./', '/^db\//'], ['',''], $local_php);
426		}
427		$tikidomain = '';
428		if (is_file('db/virtuals.inc')) {
429			if (isset($_SERVER['TIKI_VIRTUAL']) and is_file('db/' . $_SERVER['TIKI_VIRTUAL'] . '/local.php')) {
430				$tikidomain = $_SERVER['TIKI_VIRTUAL'];
431			} elseif (isset($_SERVER['SERVER_NAME']) and is_file('db/' . $_SERVER['SERVER_NAME'] . '/local.php')) {
432				$tikidomain = $_SERVER['SERVER_NAME'];
433			} elseif (isset($_REQUEST['multi']) && is_file('db/' . $_REQUEST['multi'] . '/local.php')) {
434				$tikidomain = $_REQUEST['multi'];
435			} elseif (isset($_SERVER['HTTP_HOST'])) {
436				if (is_file('db/' . $_SERVER['HTTP_HOST'] . '/local.php')) {
437					$tikidomain = $_SERVER['HTTP_HOST'];
438				} elseif (is_file('db/' . preg_replace('/^www\./', '', $_SERVER['HTTP_HOST']) . '/local.php')) {
439					$tikidomain = preg_replace('/^www\./', '', $_SERVER['HTTP_HOST']);
440				}
441			}
442			if (! empty($tikidomain)) {
443				$local_php = "db/$tikidomain/local.php";
444			}
445		}
446		$tikidomainslash = (! empty($tikidomain) ? $tikidomain . '/' : '');
447
448		$default_api_tiki = $api_tiki;
449		$api_tiki = '';
450
451		return $local_php;
452	}
453
454	static function getEnvironmentCredentials()
455	{
456		// Load connection strings from environment variables, as used by Azure and possibly other hosts
457		$connectionString = null;
458		foreach (['MYSQLCONNSTR_Tiki', 'MYSQLCONNSTR_DefaultConnection'] as $envVar) {
459			if (isset($_SERVER[$envVar])) {
460				$connectionString = $_SERVER[$envVar];
461				continue;
462			}
463		}
464
465		if ($connectionString && preg_match('/^Database=(?P<dbs>.+);Data Source=(?P<host>.+);User Id=(?P<user>.+);Password=(?P<pass>.+)$/', $connectionString, $parts)) {
466			$parts['charset'] = 'utf8';
467			$parts['socket'] = null;
468			return $parts;
469		}
470		return null;
471	}
472}
473
474/**
475 * set how Tiki will report Errors
476 * @param $errno
477 * @param $errstr
478 * @param $errfile
479 * @param $errline
480 */
481function tiki_error_handling($errno, $errstr, $errfile, $errline)
482{
483	global $prefs, $phpErrors;
484
485	if (0 === error_reporting()) {
486		// This error was triggered when evaluating an expression prepended by the at sign (@) error control operator, but since we are in a custom error handler, we have to ignore it manually.
487		// See http://ca3.php.net/manual/en/language.operators.errorcontrol.php#98895 and http://php.net/set_error_handler
488		return;
489	}
490
491	// FIXME: Optionally return false so errors are still logged
492	$err[E_ERROR]           = 'E_ERROR';
493	$err[E_CORE_ERROR]      = 'E_CORE_ERROR';
494	$err[E_USER_ERROR]      = 'E_USER_ERROR';
495	$err[E_COMPILE_ERROR]   = 'E_COMPILE_ERROR';
496	$err[E_WARNING]         = 'E_WARNING';
497	$err[E_CORE_WARNING]    = 'E_CORE_WARNING';
498	$err[E_USER_WARNING]    = 'E_USER_WARNING';
499	$err[E_COMPILE_WARNING] = 'E_COMPILE_WARNING';
500	$err[E_PARSE]           = 'E_PARSE';
501	$err[E_STRICT]          = 'E_STRICT';
502	$err[E_NOTICE]          = 'E_NOTICE';
503	$err[E_USER_NOTICE]     = 'E_USER_NOTICE';
504	$err[E_DEPRECATED]      = 'E_DEPRECATED';
505	$err[E_USER_DEPRECATED] = 'E_USER_DEPRECATED';
506
507	global $tikipath;
508	$errfile = str_replace($tikipath, '', $errfile);
509	switch ($errno) {
510		case E_ERROR:
511		case E_CORE_ERROR:
512		case E_USER_ERROR:
513		case E_COMPILE_ERROR:
514		case E_WARNING:
515		case E_CORE_WARNING:
516		case E_USER_WARNING:
517		case E_COMPILE_WARNING:
518		case E_PARSE:
519		case E_RECOVERABLE_ERROR:
520			$type = 'ERROR';
521			break;
522		case E_STRICT:
523		case E_NOTICE:
524		case E_USER_NOTICE:
525		case E_DEPRECATED:
526		case E_USER_DEPRECATED:
527			if (! defined('THIRD_PARTY_LIBS_PATTERN') ||  ! preg_match(THIRD_PARTY_LIBS_PATTERN, $errfile)) {
528				if (! empty($prefs['smarty_notice_reporting']) && $prefs['smarty_notice_reporting'] != 'y' && strstr($errfile, '.tpl.php')) {
529					return;
530				}
531			}
532			$type = 'NOTICE';
533			break;
534		default:
535			return;
536	}
537
538	$back = "<div class='rbox-data p-3 mb-3' style='font-size: 12px; border: 1px solid'>";
539	$back .= $type . " ($err[$errno]): <b>" . $errstr . "</b><br />";
540	$back .= "At line $errline in $errfile"; // $errfile comes after $errline to ease selection for copy-pasting.
541	$back .= "</div>";
542
543	$phpErrors[] = $back;
544}
545
546// Patch missing $_SERVER['REQUEST_URI'] on IIS6
547if (empty($_SERVER['REQUEST_URI'])) {
548	if (TikiInit::isIIS()) {
549		$_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'];
550	}
551}
552