PageRenderTime 72ms CodeModel.GetById 9ms app.highlight 50ms RepoModel.GetById 1ms app.codeStats 0ms

/Sources/Themes.php

https://github.com/ArsenArsen/SMF2.1
PHP | 1973 lines | 1388 code | 311 blank | 274 comment | 265 complexity | 032ff75de159b53c74dc82d55f73e125 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2
   3/**
   4 * This file concerns itself almost completely with theme administration.
   5 * Its tasks include changing theme settings, installing and removing
   6 * themes, choosing the current theme, and editing themes.
   7 *
   8 * @todo Update this for the new package manager?
   9 *
  10 * Creating and distributing theme packages:
  11 * 	There isn't that much required to package and distribute your own themes...
  12 * just do the following:
  13 * - create a theme_info.xml file, with the root element theme-info.
  14 * - its name should go in a name element, just like description.
  15 * - your name should go in author. (email in the email attribute.)
  16 * - any support website for the theme should be in website.
  17 * - layers and templates (non-default) should go in those elements ;).
  18 * - if the images dir isn't images, specify in the images element.
  19 * - any extra rows for themes should go in extra, serialized. (as in array(variable => value).)
  20 * - tar and gzip the directory - and you're done!
  21 * - please include any special license in a license.txt file.
  22 *
  23 * Simple Machines Forum (SMF)
  24 *
  25 * @package SMF
  26 * @author Simple Machines http://www.simplemachines.org
  27 * @copyright 2014 Simple Machines and individual contributors
  28 * @license http://www.simplemachines.org/about/smf/license.php BSD
  29 *
  30 * @version 2.1 Alpha 1
  31 */
  32
  33if (!defined('SMF'))
  34	die('No direct access...');
  35
  36/**
  37 * Subaction handler - manages the action and delegates control to the proper
  38 * sub-action.
  39 * It loads both the Themes and Settings language files.
  40 * Checks the session by GET or POST to verify the sent data.
  41 * Requires the user not be a guest. (@todo what?)
  42 * Accessed via ?action=admin;area=theme.
  43 */
  44function ThemesMain()
  45{
  46	global $txt, $context, $sourcedir;
  47
  48	// Load the important language files...
  49	loadLanguage('Themes');
  50	loadLanguage('Settings');
  51	loadLanguage('Drafts');
  52
  53	// No funny business - guests only.
  54	is_not_guest();
  55
  56	require_once($sourcedir . '/Subs-Themes.php');
  57
  58	// Default the page title to Theme Administration by default.
  59	$context['page_title'] = $txt['themeadmin_title'];
  60
  61	// Theme administration, removal, choice, or installation...
  62	$subActions = array(
  63		'admin' => 'ThemeAdmin',
  64		'list' => 'ThemeList',
  65		'reset' => 'SetThemeOptions',
  66		'options' => 'SetThemeOptions',
  67		'install' => 'ThemeInstall',
  68		'remove' => 'RemoveTheme',
  69		'pick' => 'PickTheme',
  70		'edit' => 'EditTheme',
  71		'enable' => 'EnableTheme',
  72		'copy' => 'CopyTemplate',
  73	);
  74
  75	// @todo Layout Settings?  huh?
  76	if (!empty($context['admin_menu_name']))
  77	{
  78		$context[$context['admin_menu_name']]['tab_data'] = array(
  79			'title' => $txt['themeadmin_title'],
  80			'help' => 'themes',
  81			'description' => $txt['themeadmin_description'],
  82			'tabs' => array(
  83				'admin' => array(
  84					'description' => $txt['themeadmin_admin_desc'],
  85				),
  86				'list' => array(
  87					'description' => $txt['themeadmin_list_desc'],
  88				),
  89				'reset' => array(
  90					'description' => $txt['themeadmin_reset_desc'],
  91				),
  92				'edit' => array(
  93					'description' => $txt['themeadmin_edit_desc'],
  94				),
  95			),
  96		);
  97	}
  98
  99	// CRUD $subActions as needed.
 100	call_integration_hook('integrate_manage_themes', array(&$subActions));
 101
 102	// Follow the sa or just go to administration.
 103	if (isset($_GET['sa']) && !empty($subActions[$_GET['sa']]))
 104		call_helper($subActions[$_GET['sa']]);
 105
 106	else
 107		call_helper($subActions['admin']);
 108}
 109
 110/**
 111 * This function allows administration of themes and their settings,
 112 * as well as global theme settings.
 113 *  - sets the settings theme_allow, theme_guests, and knownThemes.
 114 *  - requires the admin_forum permission.
 115 *  - accessed with ?action=admin;area=theme;sa=admin.
 116 *
 117 *  @uses Themes template
 118 *  @uses Admin language file
 119 */
 120function ThemeAdmin()
 121{
 122	global $context, $boarddir;
 123
 124	// Are handling any settings?
 125	if (isset($_POST['save']))
 126	{
 127		checkSession();
 128		validateToken('admin-tm');
 129
 130		if (isset($_POST['options']['known_themes']))
 131			foreach ($_POST['options']['known_themes'] as $key => $id)
 132				$_POST['options']['known_themes'][$key] = (int) $id;
 133
 134		else
 135			fatal_lang_error('themes_none_selectable', false);
 136
 137		if (!in_array($_POST['options']['theme_guests'], $_POST['options']['known_themes']))
 138			fatal_lang_error('themes_default_selectable', false);
 139
 140		// Commit the new settings.
 141		updateSettings(array(
 142			'theme_allow' => $_POST['options']['theme_allow'],
 143			'theme_guests' => $_POST['options']['theme_guests'],
 144			'knownThemes' => implode(',', $_POST['options']['known_themes']),
 145		));
 146		if ((int) $_POST['theme_reset'] == 0 || in_array($_POST['theme_reset'], $_POST['options']['known_themes']))
 147			updateMemberData(null, array('id_theme' => (int) $_POST['theme_reset']));
 148
 149		redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=admin');
 150	}
 151
 152	loadLanguage('Admin');
 153	isAllowedTo('admin_forum');
 154	loadTemplate('Themes');
 155
 156	// List all installed and enabled themes.
 157	get_all_themes(true);
 158
 159	// Can we create a new theme?
 160	$context['can_create_new'] = is_writable($boarddir . '/Themes');
 161	$context['new_theme_dir'] = substr(realpath($boarddir . '/Themes/default'), 0, -7);
 162
 163	// Look for a non existent theme directory. (ie theme87.)
 164	$theme_dir = $boarddir . '/Themes/theme';
 165	$i = 1;
 166	while (file_exists($theme_dir . $i))
 167		$i++;
 168
 169	$context['new_theme_name'] = 'theme' . $i;
 170
 171	// A bunch of tokens for a bunch of forms.
 172	createToken('admin-tm');
 173	createToken('admin-t-file');
 174	createToken('admin-t-copy');
 175	createToken('admin-t-dir');
 176}
 177
 178/**
 179 * This function lists the available themes and provides an interface to reset
 180 * the paths of all the installed themes.
 181 */
 182function ThemeList()
 183{
 184	global $context, $boarddir, $boardurl, $smcFunc;
 185
 186	loadLanguage('Admin');
 187	isAllowedTo('admin_forum');
 188
 189	if (isset($_REQUEST['th']))
 190		return SetThemeSettings();
 191
 192	if (isset($_POST['save']))
 193	{
 194		checkSession();
 195		validateToken('admin-tl');
 196
 197		// Calling the almighty power of global vars!
 198		get_all_themes(false);
 199
 200		$setValues = array();
 201		foreach ($context['themes'] as $id => $theme)
 202		{
 203			if (file_exists($_POST['reset_dir'] . '/' . basename($theme['theme_dir'])))
 204			{
 205				$setValues[] = array($id, 0, 'theme_dir', realpath($_POST['reset_dir'] . '/' . basename($theme['theme_dir'])));
 206				$setValues[] = array($id, 0, 'theme_url', $_POST['reset_url'] . '/' . basename($theme['theme_dir']));
 207				$setValues[] = array($id, 0, 'images_url', $_POST['reset_url'] . '/' . basename($theme['theme_dir']) . '/' . basename($theme['images_url']));
 208			}
 209
 210			if (isset($theme['base_theme_dir']) && file_exists($_POST['reset_dir'] . '/' . basename($theme['base_theme_dir'])))
 211			{
 212				$setValues[] = array($id, 0, 'base_theme_dir', realpath($_POST['reset_dir'] . '/' . basename($theme['base_theme_dir'])));
 213				$setValues[] = array($id, 0, 'base_theme_url', $_POST['reset_url'] . '/' . basename($theme['base_theme_dir']));
 214				$setValues[] = array($id, 0, 'base_images_url', $_POST['reset_url'] . '/' . basename($theme['base_theme_dir']) . '/' . basename($theme['base_images_url']));
 215			}
 216
 217			cache_put_data('theme_settings-' . $id, null, 90);
 218		}
 219
 220		if (!empty($setValues))
 221		{
 222			$smcFunc['db_insert']('replace',
 223				'{db_prefix}themes',
 224				array('id_theme' => 'int', 'id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
 225				$setValues,
 226				array('id_theme', 'variable', 'id_member')
 227			);
 228		}
 229
 230		redirectexit('action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id']);
 231	}
 232
 233	loadTemplate('Themes');
 234
 235	// Get all installed themes.
 236	get_all_themes(false);
 237
 238	$context['reset_dir'] = realpath($boarddir . '/Themes');
 239	$context['reset_url'] = $boardurl . '/Themes';
 240
 241	$context['sub_template'] = 'list_themes';
 242	createToken('admin-tl');
 243	createToken('admin-tr', 'request');
 244	createToken('admin-tre', 'request');
 245}
 246
 247/**
 248 * Administrative global settings.
 249 */
 250function SetThemeOptions()
 251{
 252	global $txt, $context, $settings, $modSettings, $smcFunc;
 253
 254	$_GET['th'] = isset($_GET['th']) ? (int) $_GET['th'] : (isset($_GET['id']) ? (int) $_GET['id'] : 0);
 255
 256	isAllowedTo('admin_forum');
 257
 258	if (empty($_GET['th']) && empty($_GET['id']))
 259	{
 260		$request = $smcFunc['db_query']('', '
 261			SELECT id_theme, variable, value
 262			FROM {db_prefix}themes
 263			WHERE variable IN ({string:name}, {string:theme_dir})
 264				AND id_member = {int:no_member}',
 265			array(
 266				'no_member' => 0,
 267				'name' => 'name',
 268				'theme_dir' => 'theme_dir',
 269			)
 270		);
 271		$context['themes'] = array();
 272		while ($row = $smcFunc['db_fetch_assoc']($request))
 273		{
 274			if (!isset($context['themes'][$row['id_theme']]))
 275				$context['themes'][$row['id_theme']] = array(
 276					'id' => $row['id_theme'],
 277					'num_default_options' => 0,
 278					'num_members' => 0,
 279				);
 280			$context['themes'][$row['id_theme']][$row['variable']] = $row['value'];
 281		}
 282		$smcFunc['db_free_result']($request);
 283
 284		$request = $smcFunc['db_query']('', '
 285			SELECT id_theme, COUNT(*) AS value
 286			FROM {db_prefix}themes
 287			WHERE id_member = {int:guest_member}
 288			GROUP BY id_theme',
 289			array(
 290				'guest_member' => -1,
 291			)
 292		);
 293		while ($row = $smcFunc['db_fetch_assoc']($request))
 294			$context['themes'][$row['id_theme']]['num_default_options'] = $row['value'];
 295		$smcFunc['db_free_result']($request);
 296
 297		// Need to make sure we don't do custom fields.
 298		$request = $smcFunc['db_query']('', '
 299			SELECT col_name
 300			FROM {db_prefix}custom_fields',
 301			array(
 302			)
 303		);
 304		$customFields = array();
 305		while ($row = $smcFunc['db_fetch_assoc']($request))
 306			$customFields[] = $row['col_name'];
 307		$smcFunc['db_free_result']($request);
 308		$customFieldsQuery = empty($customFields) ? '' : ('AND variable NOT IN ({array_string:custom_fields})');
 309
 310		$request = $smcFunc['db_query']('themes_count', '
 311			SELECT COUNT(DISTINCT id_member) AS value, id_theme
 312			FROM {db_prefix}themes
 313			WHERE id_member > {int:no_member}
 314				' . $customFieldsQuery . '
 315			GROUP BY id_theme',
 316			array(
 317				'no_member' => 0,
 318				'custom_fields' => empty($customFields) ? array() : $customFields,
 319			)
 320		);
 321		while ($row = $smcFunc['db_fetch_assoc']($request))
 322			$context['themes'][$row['id_theme']]['num_members'] = $row['value'];
 323		$smcFunc['db_free_result']($request);
 324
 325		// There has to be a Settings template!
 326		foreach ($context['themes'] as $k => $v)
 327			if (empty($v['theme_dir']) || (!file_exists($v['theme_dir'] . '/Settings.template.php') && empty($v['num_members'])))
 328				unset($context['themes'][$k]);
 329
 330		loadTemplate('Themes');
 331		$context['sub_template'] = 'reset_list';
 332
 333		createToken('admin-stor', 'request');
 334		return;
 335	}
 336
 337	// Submit?
 338	if (isset($_POST['submit']) && empty($_POST['who']))
 339	{
 340		checkSession();
 341		validateToken('admin-sto');
 342
 343		if (empty($_POST['options']))
 344			$_POST['options'] = array();
 345		if (empty($_POST['default_options']))
 346			$_POST['default_options'] = array();
 347
 348		// Set up the sql query.
 349		$setValues = array();
 350
 351		foreach ($_POST['options'] as $opt => $val)
 352			$setValues[] = array(-1, $_GET['th'], $opt, is_array($val) ? implode(',', $val) : $val);
 353
 354		$old_settings = array();
 355		foreach ($_POST['default_options'] as $opt => $val)
 356		{
 357			$old_settings[] = $opt;
 358
 359			$setValues[] = array(-1, 1, $opt, is_array($val) ? implode(',', $val) : $val);
 360		}
 361
 362		// If we're actually inserting something..
 363		if (!empty($setValues))
 364		{
 365			// Are there options in non-default themes set that should be cleared?
 366			if (!empty($old_settings))
 367				$smcFunc['db_query']('', '
 368					DELETE FROM {db_prefix}themes
 369					WHERE id_theme != {int:default_theme}
 370						AND id_member = {int:guest_member}
 371						AND variable IN ({array_string:old_settings})',
 372					array(
 373						'default_theme' => 1,
 374						'guest_member' => -1,
 375						'old_settings' => $old_settings,
 376					)
 377				);
 378
 379			$smcFunc['db_insert']('replace',
 380				'{db_prefix}themes',
 381				array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
 382				$setValues,
 383				array('id_theme', 'variable', 'id_member')
 384			);
 385		}
 386
 387		cache_put_data('theme_settings-' . $_GET['th'], null, 90);
 388		cache_put_data('theme_settings-1', null, 90);
 389
 390		redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=reset');
 391	}
 392	elseif (isset($_POST['submit']) && $_POST['who'] == 1)
 393	{
 394		checkSession();
 395		validateToken('admin-sto');
 396
 397		$_POST['options'] = empty($_POST['options']) ? array() : $_POST['options'];
 398		$_POST['options_master'] = empty($_POST['options_master']) ? array() : $_POST['options_master'];
 399		$_POST['default_options'] = empty($_POST['default_options']) ? array() : $_POST['default_options'];
 400		$_POST['default_options_master'] = empty($_POST['default_options_master']) ? array() : $_POST['default_options_master'];
 401
 402		$old_settings = array();
 403		foreach ($_POST['default_options'] as $opt => $val)
 404		{
 405			if ($_POST['default_options_master'][$opt] == 0)
 406				continue;
 407			elseif ($_POST['default_options_master'][$opt] == 1)
 408			{
 409				// Delete then insert for ease of database compatibility!
 410				$smcFunc['db_query']('substring', '
 411					DELETE FROM {db_prefix}themes
 412					WHERE id_theme = {int:default_theme}
 413						AND id_member != {int:no_member}
 414						AND variable = SUBSTRING({string:option}, 1, 255)',
 415					array(
 416						'default_theme' => 1,
 417						'no_member' => 0,
 418						'option' => $opt,
 419					)
 420				);
 421				$smcFunc['db_query']('substring', '
 422					INSERT INTO {db_prefix}themes
 423						(id_member, id_theme, variable, value)
 424					SELECT id_member, 1, SUBSTRING({string:option}, 1, 255), SUBSTRING({string:value}, 1, 65534)
 425					FROM {db_prefix}members',
 426					array(
 427						'option' => $opt,
 428						'value' => (is_array($val) ? implode(',', $val) : $val),
 429					)
 430				);
 431
 432				$old_settings[] = $opt;
 433			}
 434			elseif ($_POST['default_options_master'][$opt] == 2)
 435			{
 436				$smcFunc['db_query']('', '
 437					DELETE FROM {db_prefix}themes
 438					WHERE variable = {string:option_name}
 439						AND id_member > {int:no_member}',
 440					array(
 441						'no_member' => 0,
 442						'option_name' => $opt,
 443					)
 444				);
 445			}
 446		}
 447
 448		// Delete options from other themes.
 449		if (!empty($old_settings))
 450			$smcFunc['db_query']('', '
 451				DELETE FROM {db_prefix}themes
 452				WHERE id_theme != {int:default_theme}
 453					AND id_member > {int:no_member}
 454					AND variable IN ({array_string:old_settings})',
 455				array(
 456					'default_theme' => 1,
 457					'no_member' => 0,
 458					'old_settings' => $old_settings,
 459				)
 460			);
 461
 462		foreach ($_POST['options'] as $opt => $val)
 463		{
 464			if ($_POST['options_master'][$opt] == 0)
 465				continue;
 466			elseif ($_POST['options_master'][$opt] == 1)
 467			{
 468				// Delete then insert for ease of database compatibility - again!
 469				$smcFunc['db_query']('substring', '
 470					DELETE FROM {db_prefix}themes
 471					WHERE id_theme = {int:current_theme}
 472						AND id_member != {int:no_member}
 473						AND variable = SUBSTRING({string:option}, 1, 255)',
 474					array(
 475						'current_theme' => $_GET['th'],
 476						'no_member' => 0,
 477						'option' => $opt,
 478					)
 479				);
 480				$smcFunc['db_query']('substring', '
 481					INSERT INTO {db_prefix}themes
 482						(id_member, id_theme, variable, value)
 483					SELECT id_member, {int:current_theme}, SUBSTRING({string:option}, 1, 255), SUBSTRING({string:value}, 1, 65534)
 484					FROM {db_prefix}members',
 485					array(
 486						'current_theme' => $_GET['th'],
 487						'option' => $opt,
 488						'value' => (is_array($val) ? implode(',', $val) : $val),
 489					)
 490				);
 491			}
 492			elseif ($_POST['options_master'][$opt] == 2)
 493			{
 494				$smcFunc['db_query']('', '
 495					DELETE FROM {db_prefix}themes
 496					WHERE variable = {string:option}
 497						AND id_member > {int:no_member}
 498						AND id_theme = {int:current_theme}',
 499					array(
 500						'no_member' => 0,
 501						'current_theme' => $_GET['th'],
 502						'option' => $opt,
 503					)
 504				);
 505			}
 506		}
 507
 508		redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=reset');
 509	}
 510	elseif (!empty($_GET['who']) && $_GET['who'] == 2)
 511	{
 512		checkSession('get');
 513		validateToken('admin-stor', 'request');
 514
 515		// Don't delete custom fields!!
 516		if ($_GET['th'] == 1)
 517		{
 518			$request = $smcFunc['db_query']('', '
 519				SELECT col_name
 520				FROM {db_prefix}custom_fields',
 521				array(
 522				)
 523			);
 524			$customFields = array();
 525			while ($row = $smcFunc['db_fetch_assoc']($request))
 526				$customFields[] = $row['col_name'];
 527			$smcFunc['db_free_result']($request);
 528		}
 529		$customFieldsQuery = empty($customFields) ? '' : ('AND variable NOT IN ({array_string:custom_fields})');
 530
 531		$smcFunc['db_query']('', '
 532			DELETE FROM {db_prefix}themes
 533			WHERE id_member > {int:no_member}
 534				AND id_theme = {int:current_theme}
 535				' . $customFieldsQuery,
 536			array(
 537				'no_member' => 0,
 538				'current_theme' => $_GET['th'],
 539				'custom_fields' => empty($customFields) ? array() : $customFields,
 540			)
 541		);
 542
 543		redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=reset');
 544	}
 545
 546	$old_id = $settings['theme_id'];
 547	$old_settings = $settings;
 548
 549	loadTheme($_GET['th'], false);
 550
 551	loadLanguage('Profile');
 552	// @todo Should we just move these options so they are no longer theme dependant?
 553	loadLanguage('PersonalMessage');
 554
 555	// Let the theme take care of the settings.
 556	loadTemplate('Settings');
 557	loadSubTemplate('options');
 558
 559	$context['sub_template'] = 'set_options';
 560	$context['page_title'] = $txt['theme_settings'];
 561
 562	$context['options'] = $context['theme_options'];
 563	$context['theme_settings'] = $settings;
 564
 565	if (empty($_REQUEST['who']))
 566	{
 567		$request = $smcFunc['db_query']('', '
 568			SELECT variable, value
 569			FROM {db_prefix}themes
 570			WHERE id_theme IN (1, {int:current_theme})
 571				AND id_member = {int:guest_member}',
 572			array(
 573				'current_theme' => $_GET['th'],
 574				'guest_member' => -1,
 575			)
 576		);
 577		$context['theme_options'] = array();
 578		while ($row = $smcFunc['db_fetch_assoc']($request))
 579			$context['theme_options'][$row['variable']] = $row['value'];
 580		$smcFunc['db_free_result']($request);
 581
 582		$context['theme_options_reset'] = false;
 583	}
 584	else
 585	{
 586		$context['theme_options'] = array();
 587		$context['theme_options_reset'] = true;
 588	}
 589
 590	foreach ($context['options'] as $i => $setting)
 591	{
 592		// Is this disabled?
 593		if ($setting['id'] == 'calendar_start_day' && empty($modSettings['cal_enabled']))
 594		{
 595			unset($context['options'][$i]);
 596			continue;
 597		}
 598		elseif (($setting['id'] == 'topics_per_page' || $setting['id'] == 'messages_per_page') && !empty($modSettings['disableCustomPerPage']))
 599		{
 600			unset($context['options'][$i]);
 601			continue;
 602		}
 603
 604		if (!isset($setting['type']) || $setting['type'] == 'bool')
 605			$context['options'][$i]['type'] = 'checkbox';
 606		elseif ($setting['type'] == 'int' || $setting['type'] == 'integer')
 607			$context['options'][$i]['type'] = 'number';
 608		elseif ($setting['type'] == 'string')
 609			$context['options'][$i]['type'] = 'text';
 610
 611		if (isset($setting['options']))
 612			$context['options'][$i]['type'] = 'list';
 613
 614		$context['options'][$i]['value'] = !isset($context['theme_options'][$setting['id']]) ? '' : $context['theme_options'][$setting['id']];
 615	}
 616
 617	// Restore the existing theme.
 618	loadTheme($old_id, false);
 619	$settings = $old_settings;
 620
 621	loadTemplate('Themes');
 622	createToken('admin-sto');
 623}
 624
 625/**
 626 * Administrative global settings.
 627 * - saves and requests global theme settings. ($settings)
 628 * - loads the Admin language file.
 629 * - calls ThemeAdmin() if no theme is specified. (the theme center.)
 630 * - requires admin_forum permission.
 631 * - accessed with ?action=admin;area=theme;sa=list&th=xx.
 632 */
 633function SetThemeSettings()
 634{
 635	global $txt, $context, $settings, $modSettings, $smcFunc;
 636
 637	if (empty($_GET['th']) && empty($_GET['id']))
 638		return ThemeAdmin();
 639
 640	$_GET['th'] = isset($_GET['th']) ? (int) $_GET['th'] : (int) $_GET['id'];
 641
 642	// Select the best fitting tab.
 643	$context[$context['admin_menu_name']]['current_subsection'] = 'list';
 644
 645	loadLanguage('Admin');
 646	isAllowedTo('admin_forum');
 647
 648	// Validate inputs/user.
 649	if (empty($_GET['th']))
 650		fatal_lang_error('no_theme', false);
 651
 652	// Fetch the smiley sets...
 653	$sets = explode(',', 'none,' . $modSettings['smiley_sets_known']);
 654	$set_names = explode("\n", $txt['smileys_none'] . "\n" . $modSettings['smiley_sets_names']);
 655	$context['smiley_sets'] = array(
 656		'' => $txt['smileys_no_default']
 657	);
 658	foreach ($sets as $i => $set)
 659		$context['smiley_sets'][$set] = $smcFunc['htmlspecialchars']($set_names[$i]);
 660
 661	$old_id = $settings['theme_id'];
 662	$old_settings = $settings;
 663
 664	loadTheme($_GET['th'], false);
 665
 666	// Sadly we really do need to init the template.
 667	loadSubTemplate('init', 'ignore');
 668
 669	// Also load the actual themes language file - in case of special settings.
 670	loadLanguage('Settings', '', true, true);
 671
 672	// And the custom language strings...
 673	loadLanguage('ThemeStrings', '', false, true);
 674
 675	// Let the theme take care of the settings.
 676	loadTemplate('Settings');
 677	loadSubTemplate('settings');
 678
 679	// Load the variants separately...
 680	$settings['theme_variants'] = array();
 681	if (file_exists($settings['theme_dir'] . '/index.template.php'))
 682	{
 683		$file_contents = implode('', file($settings['theme_dir'] . '/index.template.php'));
 684		if (preg_match('~\$settings\[\'theme_variants\'\]\s*=(.+?);~', $file_contents, $matches))
 685				eval('global $settings;' . $matches[0]);
 686	}
 687
 688	// Submitting!
 689	if (isset($_POST['save']))
 690	{
 691		checkSession();
 692		validateToken('admin-sts');
 693
 694		if (empty($_POST['options']))
 695			$_POST['options'] = array();
 696		if (empty($_POST['default_options']))
 697			$_POST['default_options'] = array();
 698
 699		// Make sure items are cast correctly.
 700		foreach ($context['theme_settings'] as $item)
 701		{
 702			// Disregard this item if this is just a separator.
 703			if (!is_array($item))
 704				continue;
 705
 706			foreach (array('options', 'default_options') as $option)
 707			{
 708				if (!isset($_POST[$option][$item['id']]))
 709					continue;
 710				// Checkbox.
 711				elseif (empty($item['type']))
 712					$_POST[$option][$item['id']] = $_POST[$option][$item['id']] ? 1 : 0;
 713				// Number
 714				elseif ($item['type'] == 'number')
 715					$_POST[$option][$item['id']] = (int) $_POST[$option][$item['id']];
 716			}
 717		}
 718
 719		// Set up the sql query.
 720		$inserts = array();
 721		foreach ($_POST['options'] as $opt => $val)
 722			$inserts[] = array(0, $_GET['th'], $opt, is_array($val) ? implode(',', $val) : $val);
 723		foreach ($_POST['default_options'] as $opt => $val)
 724			$inserts[] = array(0, 1, $opt, is_array($val) ? implode(',', $val) : $val);
 725		// If we're actually inserting something..
 726		if (!empty($inserts))
 727		{
 728			$smcFunc['db_insert']('replace',
 729				'{db_prefix}themes',
 730				array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
 731				$inserts,
 732				array('id_member', 'id_theme', 'variable')
 733			);
 734		}
 735
 736		cache_put_data('theme_settings-' . $_GET['th'], null, 90);
 737		cache_put_data('theme_settings-1', null, 90);
 738
 739		// Invalidate the cache.
 740		updateSettings(array('settings_updated' => time()));
 741
 742		redirectexit('action=admin;area=theme;sa=list;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id']);
 743	}
 744
 745	$context['sub_template'] = 'set_settings';
 746	$context['page_title'] = $txt['theme_settings'];
 747
 748	foreach ($settings as $setting => $dummy)
 749	{
 750		if (!in_array($setting, array('theme_url', 'theme_dir', 'images_url', 'template_dirs')))
 751			$settings[$setting] = htmlspecialchars__recursive($settings[$setting]);
 752	}
 753
 754	$context['settings'] = $context['theme_settings'];
 755	$context['theme_settings'] = $settings;
 756
 757	foreach ($context['settings'] as $i => $setting)
 758	{
 759		// Separators are dummies, so leave them alone.
 760		if (!is_array($setting))
 761			continue;
 762
 763		if (!isset($setting['type']) || $setting['type'] == 'bool')
 764			$context['settings'][$i]['type'] = 'checkbox';
 765		elseif ($setting['type'] == 'int' || $setting['type'] == 'integer')
 766			$context['settings'][$i]['type'] = 'number';
 767		elseif ($setting['type'] == 'string')
 768			$context['settings'][$i]['type'] = 'text';
 769
 770		if (isset($setting['options']))
 771			$context['settings'][$i]['type'] = 'list';
 772
 773		$context['settings'][$i]['value'] = !isset($settings[$setting['id']]) ? '' : $settings[$setting['id']];
 774	}
 775
 776	// Do we support variants?
 777	if (!empty($settings['theme_variants']))
 778	{
 779		$context['theme_variants'] = array();
 780		foreach ($settings['theme_variants'] as $variant)
 781		{
 782			// Have any text, old chap?
 783			$context['theme_variants'][$variant] = array(
 784				'label' => isset($txt['variant_' . $variant]) ? $txt['variant_' . $variant] : $variant,
 785				'thumbnail' => !file_exists($settings['theme_dir'] . '/images/thumbnail.png') || file_exists($settings['theme_dir'] . '/images/thumbnail_' . $variant . '.png') ? $settings['images_url'] . '/thumbnail_' . $variant . '.png' : ($settings['images_url'] . '/thumbnail.png'),
 786			);
 787		}
 788		$context['default_variant'] = !empty($settings['default_variant']) && isset($context['theme_variants'][$settings['default_variant']]) ? $settings['default_variant'] : $settings['theme_variants'][0];
 789	}
 790
 791	// Restore the current theme.
 792	loadTheme($old_id, false);
 793
 794	// Reinit just incase.
 795	loadSubTemplate('init', 'ignore');
 796
 797	$settings = $old_settings;
 798
 799	loadTemplate('Themes');
 800
 801	// We like Kenny better than Token.
 802	createToken('admin-sts');
 803}
 804
 805/**
 806 * Remove a theme from the database.
 807 * - removes an installed theme.
 808 * - requires an administrator.
 809 * - accessed with ?action=admin;area=theme;sa=remove.
 810 */
 811function RemoveTheme()
 812{
 813	global $context;
 814
 815	checkSession('get');
 816
 817	isAllowedTo('admin_forum');
 818	validateToken('admin-tr', 'request');
 819
 820	// The theme's ID must be an integer.
 821	$themeID = isset($_GET['th']) ? (int) $_GET['th'] : (int) $_GET['id'];
 822
 823	// You can't delete the default theme!
 824	if ($themeID == 1)
 825		fatal_lang_error('no_access', false);
 826
 827	$theme_info = get_single_theme($themeID);
 828
 829	// Remove it from the DB.
 830	remove_theme($themeID);
 831
 832	// And remove all its files and folders too.
 833	if (!empty($theme_info) && !empty($theme_info['theme_dir']))
 834		remove_dir($theme_info['theme_dir']);
 835
 836	// Go back to the list page.
 837	redirectexit('action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id'] .';done=removing');
 838}
 839
 840function EnableTheme()
 841{
 842	global $modSettings, $context;
 843
 844	checkSession('get');
 845
 846	isAllowedTo('admin_forum');
 847	validateToken('admin-tre', 'request');
 848
 849	// The theme's ID must be an string.
 850	$themeID = isset($_GET['th']) ? (string) trim($_GET['th']) : (string) trim($_GET['id']);
 851
 852	// Get the current list.
 853	$enableThemes = explode(',', $modSettings['enableThemes']);
 854
 855	// Are we disabling it?
 856	if (isset($_GET['disabled']))
 857		$enableThemes = array_diff($enableThemes, array($themeID));
 858
 859	// Nope? then enable it!
 860	else
 861		$enableThemes[] = (string) $themeID;
 862
 863	// Update the setting.
 864	$enableThemes = strtr(implode(',', $enableThemes), array(',,' => ','));
 865	updateSettings(array('enableThemes' => $enableThemes));
 866
 867	// Done!
 868	redirectexit('action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id'] .';done='. (isset($_GET['disabled']) ? 'disabling' : 'enabling'));
 869}
 870
 871/**
 872 * Choose a theme from a list.
 873 * allows an user or administrator to pick a new theme with an interface.
 874 * - can edit everyone's (u = 0), guests' (u = -1), or a specific user's.
 875 * - uses the Themes template. (pick sub template.)
 876 * - accessed with ?action=admin;area=theme;sa=pick.
 877 * @todo thought so... Might be better to split this file in ManageThemes and Themes,
 878 * with centralized admin permissions on ManageThemes.
 879 */
 880function PickTheme()
 881{
 882	global $txt, $context, $modSettings, $user_info, $language, $smcFunc, $settings, $scripturl;
 883
 884	loadLanguage('Profile');
 885	loadTemplate('Themes');
 886
 887	// Build the link tree.
 888	$context['linktree'][] = array(
 889		'url' => $scripturl . '?action=theme;sa=pick;u=' . (!empty($_REQUEST['u']) ? (int) $_REQUEST['u'] : 0),
 890		'name' => $txt['theme_pick'],
 891	);
 892	$context['default_theme_id'] = $modSettings['theme_default'];
 893
 894	$_SESSION['id_theme'] = 0;
 895
 896	if (isset($_GET['id']))
 897		$_GET['th'] = $_GET['id'];
 898
 899	// Saving a variant cause JS doesn't work - pretend it did ;)
 900	if (isset($_POST['save']))
 901	{
 902		// Which theme?
 903		foreach ($_POST['save'] as $k => $v)
 904			$_GET['th'] = (int) $k;
 905
 906		if (isset($_POST['vrt'][$k]))
 907			$_GET['vrt'] = $_POST['vrt'][$k];
 908	}
 909
 910	// Have we made a decision, or are we just browsing?
 911	if (isset($_GET['th']))
 912	{
 913		checkSession('get');
 914
 915		$_GET['th'] = (int) $_GET['th'];
 916
 917		// Save for this user.
 918		if (!isset($_REQUEST['u']) || !allowedTo('admin_forum'))
 919		{
 920			updateMemberData($user_info['id'], array('id_theme' => (int) $_GET['th']));
 921
 922			// A variants to save for the user?
 923			if (!empty($_GET['vrt']))
 924			{
 925				$smcFunc['db_insert']('replace',
 926					'{db_prefix}themes',
 927					array('id_theme' => 'int', 'id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
 928					array($_GET['th'], $user_info['id'], 'theme_variant', $_GET['vrt']),
 929					array('id_theme', 'id_member', 'variable')
 930				);
 931				cache_put_data('theme_settings-' . $_GET['th'] . ':' . $user_info['id'], null, 90);
 932
 933				$_SESSION['id_variant'] = 0;
 934			}
 935
 936			redirectexit('action=profile;area=theme');
 937		}
 938
 939		// If changing members or guests - and there's a variant - assume changing default variant.
 940		if (!empty($_GET['vrt']) && ($_REQUEST['u'] == '0' || $_REQUEST['u'] == '-1'))
 941		{
 942			$smcFunc['db_insert']('replace',
 943				'{db_prefix}themes',
 944				array('id_theme' => 'int', 'id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
 945				array($_GET['th'], 0, 'default_variant', $_GET['vrt']),
 946				array('id_theme', 'id_member', 'variable')
 947			);
 948
 949			// Make it obvious that it's changed
 950			cache_put_data('theme_settings-' . $_GET['th'], null, 90);
 951		}
 952
 953		// For everyone.
 954		if ($_REQUEST['u'] == '0')
 955		{
 956			updateMemberData(null, array('id_theme' => (int) $_GET['th']));
 957
 958			// Remove any custom variants.
 959			if (!empty($_GET['vrt']))
 960			{
 961				$smcFunc['db_query']('', '
 962					DELETE FROM {db_prefix}themes
 963					WHERE id_theme = {int:current_theme}
 964						AND variable = {string:theme_variant}',
 965					array(
 966						'current_theme' => (int) $_GET['th'],
 967						'theme_variant' => 'theme_variant',
 968					)
 969				);
 970			}
 971
 972			redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']);
 973		}
 974		// Change the default/guest theme.
 975		elseif ($_REQUEST['u'] == '-1')
 976		{
 977			updateSettings(array('theme_guests' => (int) $_GET['th']));
 978
 979			redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']);
 980		}
 981		// Change a specific member's theme.
 982		else
 983		{
 984			// The forum's default theme is always 0 and we
 985			if (isset($_GET['th']) && $_GET['th'] == 0)
 986					$_GET['th'] = $modSettings['theme_guests'];
 987
 988			updateMemberData((int) $_REQUEST['u'], array('id_theme' => (int) $_GET['th']));
 989
 990			if (!empty($_GET['vrt']))
 991			{
 992				$smcFunc['db_insert']('replace',
 993					'{db_prefix}themes',
 994					array('id_theme' => 'int', 'id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
 995					array($_GET['th'], (int) $_REQUEST['u'], 'theme_variant', $_GET['vrt']),
 996					array('id_theme', 'id_member', 'variable')
 997				);
 998				cache_put_data('theme_settings-' . $_GET['th'] . ':' . (int) $_REQUEST['u'], null, 90);
 999
1000				if ($user_info['id'] == $_REQUEST['u'])
1001					$_SESSION['id_variant'] = 0;
1002			}
1003
1004			redirectexit('action=profile;u=' . (int) $_REQUEST['u'] . ';area=theme');
1005		}
1006	}
1007
1008	// Figure out who the member of the minute is, and what theme they've chosen.
1009	if (!isset($_REQUEST['u']) || !allowedTo('admin_forum'))
1010	{
1011		$context['current_member'] = $user_info['id'];
1012		$context['current_theme'] = $user_info['theme'];
1013	}
1014	// Everyone can't chose just one.
1015	elseif ($_REQUEST['u'] == '0')
1016	{
1017		$context['current_member'] = 0;
1018		$context['current_theme'] = 0;
1019	}
1020	// Guests and such...
1021	elseif ($_REQUEST['u'] == '-1')
1022	{
1023		$context['current_member'] = -1;
1024		$context['current_theme'] = $modSettings['theme_guests'];
1025	}
1026	// Someones else :P.
1027	else
1028	{
1029		$context['current_member'] = (int) $_REQUEST['u'];
1030
1031		$request = $smcFunc['db_query']('', '
1032			SELECT id_theme
1033			FROM {db_prefix}members
1034			WHERE id_member = {int:current_member}
1035			LIMIT 1',
1036			array(
1037				'current_member' => $context['current_member'],
1038			)
1039		);
1040		list ($context['current_theme']) = $smcFunc['db_fetch_row']($request);
1041		$smcFunc['db_free_result']($request);
1042	}
1043
1044	// Get the theme name and descriptions.
1045	$context['available_themes'] = array();
1046	if (!empty($modSettings['knownThemes']))
1047	{
1048		$request = $smcFunc['db_query']('', '
1049			SELECT id_theme, variable, value
1050			FROM {db_prefix}themes
1051			WHERE variable IN ({string:name}, {string:theme_url}, {string:theme_dir}, {string:images_url}, {string:disable_user_variant})' . (!allowedTo('admin_forum') ? '
1052				AND id_theme IN ({array_string:known_themes})' : '') . '
1053				AND id_theme != {int:default_theme}
1054				AND id_member = {int:no_member}
1055				AND id_theme IN ({array_string:enable_themes})',
1056			array(
1057				'default_theme' => 0,
1058				'name' => 'name',
1059				'no_member' => 0,
1060				'theme_url' => 'theme_url',
1061				'theme_dir' => 'theme_dir',
1062				'images_url' => 'images_url',
1063				'disable_user_variant' => 'disable_user_variant',
1064				'known_themes' => explode(',', $modSettings['knownThemes']),
1065				'enable_themes' => explode(',', $modSettings['enableThemes']),
1066			)
1067		);
1068		while ($row = $smcFunc['db_fetch_assoc']($request))
1069		{
1070			if (!isset($context['available_themes'][$row['id_theme']]))
1071				$context['available_themes'][$row['id_theme']] = array(
1072					'id' => $row['id_theme'],
1073					'selected' => $context['current_theme'] == $row['id_theme'],
1074					'num_users' => 0
1075				);
1076			$context['available_themes'][$row['id_theme']][$row['variable']] = $row['value'];
1077		}
1078		$smcFunc['db_free_result']($request);
1079	}
1080
1081	// Okay, this is a complicated problem: the default theme is 1, but they aren't allowed to access 1!
1082	if (!isset($context['available_themes'][$modSettings['theme_guests']]))
1083	{
1084		$context['available_themes'][0] = array(
1085			'num_users' => 0
1086		);
1087		$guest_theme = 0;
1088	}
1089	else
1090		$guest_theme = $modSettings['theme_guests'];
1091
1092	$request = $smcFunc['db_query']('', '
1093		SELECT id_theme, COUNT(*) AS the_count
1094		FROM {db_prefix}members
1095		GROUP BY id_theme
1096		ORDER BY id_theme DESC',
1097		array(
1098		)
1099	);
1100	while ($row = $smcFunc['db_fetch_assoc']($request))
1101	{
1102		// Figure out which theme it is they are REALLY using.
1103		if (!empty($modSettings['knownThemes']) && !in_array($row['id_theme'], explode(',',$modSettings['knownThemes'])))
1104			$row['id_theme'] = $guest_theme;
1105		elseif (empty($modSettings['theme_allow']))
1106			$row['id_theme'] = $guest_theme;
1107
1108		if (isset($context['available_themes'][$row['id_theme']]))
1109			$context['available_themes'][$row['id_theme']]['num_users'] += $row['the_count'];
1110		else
1111			$context['available_themes'][$guest_theme]['num_users'] += $row['the_count'];
1112	}
1113	$smcFunc['db_free_result']($request);
1114
1115	// Get any member variant preferences.
1116	$variant_preferences = array();
1117	if ($context['current_member'] > 0)
1118	{
1119		$request = $smcFunc['db_query']('', '
1120			SELECT id_theme, value
1121			FROM {db_prefix}themes
1122			WHERE variable = {string:theme_variant}
1123				AND id_member IN ({array_int:id_member})
1124			ORDER BY id_member ASC',
1125			array(
1126				'theme_variant' => 'theme_variant',
1127				'id_member' => isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'pick' ? array(-1, $context['current_member']) : array(-1),
1128			)
1129		);
1130		while ($row = $smcFunc['db_fetch_assoc']($request))
1131			$variant_preferences[$row['id_theme']] = $row['value'];
1132		$smcFunc['db_free_result']($request);
1133	}
1134
1135	// Save the setting first.
1136	$current_images_url = $settings['images_url'];
1137	$current_theme_variants = !empty($settings['theme_variants']) ? $settings['theme_variants'] : array();
1138
1139	foreach ($context['available_themes'] as $id_theme => $theme_data)
1140	{
1141		// Don't try to load the forum or board default theme's data... it doesn't have any!
1142		if ($id_theme == 0)
1143			continue;
1144
1145		// The thumbnail needs the correct path.
1146		$settings['images_url'] = &$theme_data['images_url'];
1147
1148		if (file_exists($theme_data['theme_dir'] . '/languages/Settings.' . $user_info['language'] . '.php'))
1149			include($theme_data['theme_dir'] . '/languages/Settings.' . $user_info['language'] . '.php');
1150		elseif (file_exists($theme_data['theme_dir'] . '/languages/Settings.' . $language . '.php'))
1151			include($theme_data['theme_dir'] . '/languages/Settings.' . $language . '.php');
1152		else
1153		{
1154			$txt['theme_thumbnail_href'] = $theme_data['images_url'] . '/thumbnail.png';
1155			$txt['theme_description'] = '';
1156		}
1157
1158		$context['available_themes'][$id_theme]['thumbnail_href'] = $txt['theme_thumbnail_href'];
1159		$context['available_themes'][$id_theme]['description'] = $txt['theme_description'];
1160
1161		// Are there any variants?
1162		if (file_exists($theme_data['theme_dir'] . '/index.template.php') && (empty($theme_data['disable_user_variant']) || allowedTo('admin_forum')))
1163		{
1164			$file_contents = implode('', file($theme_data['theme_dir'] . '/index.template.php'));
1165			if (preg_match('~\$settings\[\'theme_variants\'\]\s*=(.+?);~', $file_contents, $matches))
1166			{
1167				$settings['theme_variants'] = array();
1168
1169				// Fill settings up.
1170				eval('global $settings;' . $matches[0]);
1171
1172				if (!empty($settings['theme_variants']))
1173				{
1174					loadLanguage('Settings');
1175
1176					$context['available_themes'][$id_theme]['variants'] = array();
1177					foreach ($settings['theme_variants'] as $variant)
1178						$context['available_themes'][$id_theme]['variants'][$variant] = array(
1179							'label' => isset($txt['variant_' . $variant]) ? $txt['variant_' . $variant] : $variant,
1180							'thumbnail' => !file_exists($theme_data['theme_dir'] . '/images/thumbnail.png') || file_exists($theme_data['theme_dir'] . '/images/thumbnail_' . $variant . '.png') ? $theme_data['images_url'] . '/thumbnail_' . $variant . '.png' : ($theme_data['images_url'] . '/thumbnail.png'),
1181						);
1182
1183					$context['available_themes'][$id_theme]['selected_variant'] = isset($_GET['vrt']) ? $_GET['vrt'] : (!empty($variant_preferences[$id_theme]) ? $variant_preferences[$id_theme] : (!empty($settings['default_variant']) ? $settings['default_variant'] : $settings['theme_variants'][0]));
1184					if (!isset($context['available_themes'][$id_theme]['variants'][$context['available_themes'][$id_theme]['selected_variant']]['thumbnail']))
1185						$context['available_themes'][$id_theme]['selected_variant'] = $settings['theme_variants'][0];
1186
1187					$context['available_themes'][$id_theme]['thumbnail_href'] = $context['available_themes'][$id_theme]['variants'][$context['available_themes'][$id_theme]['selected_variant']]['thumbnail'];
1188					// Allow themes to override the text.
1189					$context['available_themes'][$id_theme]['pick_label'] = isset($txt['variant_pick']) ? $txt['variant_pick'] : $txt['theme_pick_variant'];
1190				}
1191			}
1192		}
1193	}
1194	// Then return it.
1195	$settings['images_url'] = $current_images_url;
1196	$settings['theme_variants'] = $current_theme_variants;
1197
1198	// As long as we're not doing the default theme...
1199	if (!isset($_REQUEST['u']) || $_REQUEST['u'] >= 0)
1200	{
1201		if ($guest_theme != 0)
1202			$context['available_themes'][0] = $context['available_themes'][$guest_theme];
1203
1204		$context['available_themes'][0]['id'] = 0;
1205		$context['available_themes'][0]['name'] = $txt['theme_forum_default'];
1206		$context['available_themes'][0]['selected'] = $context['current_theme'] == 0;
1207		$context['available_themes'][0]['description'] = $txt['theme_global_description'];
1208	}
1209
1210	ksort($context['available_themes']);
1211
1212	$context['page_title'] = $txt['theme_pick'];
1213	$context['sub_template'] = 'pick';
1214}
1215
1216/**
1217 * Installs new themes, calls the respective function according to the install type.
1218 * - puts themes in $boardurl/Themes.
1219 * - assumes the gzip has a root directory in it. (ie default.)
1220 * Requires admin_forum.
1221 * Accessed with ?action=admin;area=theme;sa=install.
1222 */
1223function ThemeInstall()
1224{
1225	global $sourcedir, $txt, $context, $boarddir, $boardurl;
1226	global $themedir, $themeurl, $smcFunc;
1227
1228	checkSession('request');
1229	isAllowedTo('admin_forum');
1230
1231	require_once($sourcedir . '/Subs-Package.php');
1232
1233	// Make it easier to change the path and url.
1234	$themedir = $boarddir . '/Themes';
1235	$themeurl = $boardurl . '/Themes';
1236
1237	loadTemplate('Themes');
1238
1239	$subActions = array(
1240		'file' => 'InstallFile',
1241		'copy' => 'InstallCopy',
1242		'dir' => 'InstallDir',
1243	);
1244
1245	// Is there a function to call?
1246	if (isset($_GET['do']) && !empty($_GET['do']) && isset($subActions[$_GET['do']]))
1247	{
1248		$action = $smcFunc['htmlspecialchars'](trim($_GET['do']));
1249
1250		// Got any info from the specific form?
1251		if (!isset($_POST['save_'. $action]))
1252			fatal_lang_error('theme_install_no_action', false);
1253
1254		validateToken('admin-t-'. $action);
1255
1256		// Hopefully the themes directory is writable, or we might have a problem.
1257		if (!is_writable($themedir))
1258			fatal_lang_error('theme_install_write_error', 'critical');
1259
1260		// Call the function and handle the result.
1261		$result = $subActions[$action]();
1262
1263		// Everything went better than expected!
1264		if (!empty($result))
1265		{
1266			$context['sub_template'] = 'installed';
1267			$context['page_title'] = $txt['theme_installed'];
1268			$context['installed_theme'] = $result;
1269		}
1270	}
1271
1272	// Nope, show a nice error.
1273	else
1274		fatal_lang_error('theme_install_no_action', false);
1275}
1276
1277/**
1278 * Installs a theme from a theme package.
1279 *
1280 * Stores the theme files on a temp dir, on success it renames the dir to the new theme's name. Ends execution with fatal_lang_error() on any error.
1281 * @return array The newly created theme's info.
1282 */
1283function InstallFile()
1284{
1285	global $themedir, $themeurl, $context;
1286
1287	// Set a temp dir for dumping all required files on it.
1288	$dirtemp = $themedir .'/temp';
1289
1290	// Create the temp dir.
1291	mkdir($dirtemp, 0777);
1292
1293	// Hopefully the temp directory is writable, or we might have a problem.
1294	if (!is_writable($dirtemp))
1295	{
1296		// Lets give it a try.
1297		@chmod($dirtmp, '0755');
1298
1299		// How about now?
1300		if (!is_writable($dirtemp))
1301			fatal_lang_error('theme_install_write_error', 'critical');
1302	}
1303
1304	// This happens when the admin session is gone and the user has to login again.
1305	if (!isset($_FILES) || !isset($_FILES['theme_gz']) || empty($_FILES['theme_gz']))
1306		redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']);
1307
1308	// Another error check layer, something went wrong with the upload.
1309	if (isset($_FILES['theme_gz']['error']) && $_FILES['theme_gz']['error'] != 0)
1310		fatal_lang_error('theme_install_error_file_'. $_FILES['theme_gz']['error'], false);
1311
1312	// Get the theme's name.
1313	$name = strtok(basename($_FILES['theme_gz']['name']));
1314	$name = preg_replace(array('/\s/', '/\.[\.]+/', '/[^\w_\.\-]/'), array('_', '.', ''), $name);
1315
1316	// Start setting some vars.
1317	$context['to_install'] = array(
1318		'theme_dir' => $themedir . '/' . $name,
1319		'theme_url' => $themeurl . '/' . $name,
1320		'images_url' => $themeurl . '/' . $name . '/images',
1321		'name' => $name,
1322	);
1323
1324	// Extract the file on the proper themes dir.
1325	$extracted = read_tgz_file($_FILES['theme_gz']['tmp_name'], $dirtemp, false, true);
1326
1327	if ($extracted)
1328	{
1329		// Read its info form the XML file.
1330		$theme_info = get_theme_info($dirtemp);
1331		$context['to_install'] += $theme_info;
1332
1333		// Install the theme. theme_install() will return the new installed ID.
1334		$context['to_install']['id'] = theme_install($context['to_install']);
1335
1336		// Rename the temp dir to the actual theme name.
1337		rename($dirtemp, $context['to_install']['theme_dir']);
1338
1339		// return all the info.
1340		return $context['to_install'];
1341	}
1342
1343	else
1344		fatal_lang_error('theme_install_error_title', false);
1345}
1346
1347/**
1348 * Makes a copy from the default theme, assigns a name for it and installs it.
1349 *
1350 * Creates a new .xml file containing all the theme's info.
1351 * @return array The newly created theme's info.
1352 */
1353function InstallCopy()
1354{
1355	global $themedir, $themeurl, $settings, $smcFunc, $context;
1356	global $forum_version;
1357
1358	// There's gotta be something to work with.
1359	if (!isset($_REQUEST['copy']) || empty($_REQUEST['copy']))
1360		fatal_lang_error('theme_install_error_title', false);
1361
1362	// Get a cleaner version.
1363	$name = preg_replace('~[^A-Za-z0-9_\- ]~', '', $_REQUEST['copy']);
1364
1365	// Is there a theme already named like this?
1366	if (file_exists($themedir .'/'. $name))
1367		fatal_lang_error('theme_install_already_dir', false);
1368
1369	// This is a brand new theme so set all possible values.
1370	$context['to_install'] = array(
1371		'theme_dir' => $themedir . '/' . $name,
1372		'theme_url' => $themeurl . '/' . $name,
1373		'name' => $name,
1374		'images_url' => $themeurl . '/' . $name . '/images',
1375		'version' => '1.0',
1376		'install_for' => '2.1 - 2.1.99, '. strtr($forum_version, array('SMF ' => '')),
1377		'based_on' => '',
1378		'based_on_dir' => $themedir . '/default',
1379	);
1380
1381	// Create the specific dir.
1382	umask(0);
1383	mkdir($context['to_install']['theme_dir'], 0777);
1384
1385	// Buy some time.
1386	@set_time_limit(600);
1387	if (function_exists('apache_reset_timeout'))
1388		@apache_reset_timeout();
1389
1390	// Create subdirectories for css and javascript files.
1391	mkdir($context['to_install']['theme_dir'] . '/css', 0777);
1392	mkdir($context['to_install']['theme_dir'] . '/scripts', 0777);
1393
1394	// Copy over the default non-theme files.
1395	$to_copy = array('/index.php', '/index.template.php', '/css/index.css', '/css/index.responsive.css', '/css/rtl.css', '/css/calendar.css', '/css/calendar.rtl.css', '/css/admin.css', '/scripts/theme.js');
1396
1397	foreach ($to_copy as $file)
1398	{
1399		copy($settings['default_theme_dir'] . $file, $context['to_install']['theme_dir'] . $file);
1400		@chmod($context['to_install']['theme_dir'] . $file, 0777);
1401	}
1402
1403	// And now the entire images directory!
1404	copytree($settings['default_theme_dir'] . '/images', $context['to_install']['theme_dir'] . '/images');
1405	package_flush_cache();
1406
1407	// Lets get some data for the new theme.
1408	$request = $smcFunc['db_query']('', '
1409		SELECT variable, value
1410		FROM {db_prefix}themes
1411		WHERE variable IN ({string:theme_templates}, {string:theme_layers})
1412			AND id_member = {int:no_member}
1413			AND id_theme = {int:default_theme}',
1414		array(
1415			'no_member' => 0,
1416			'default_theme' => 1,
1417			'theme_templates' => 'theme_templates',
1418			'theme_layers' => 'theme_layers',
1419		)
1420	);
1421
1422	while ($row = $smcFunc['db_fetch_assoc']($request))
1423	{
1424		if ($row['variable'] == 'theme_templates')
1425			$theme_templates = $row['value'];
1426		elseif ($row['variable'] == 'theme_layers')
1427			$theme_layers = $row['value'];
1428		else
1429			continue;
1430	}
1431
1432	$smcFunc['db_free_result']($request);
1433
1434	$context['to_install'] += array(
1435		'theme_layers' => empty($theme_layers) ? 'html,body' : $theme_layers,
1436		'theme_templates' => empty($theme_templates) ? 'index' : $theme_templates,
1437	);
1438
1439	// Lets add a theme_info.xml to this theme.
1440	$xml_info = '<' . '?xml version="1.0"?' . '>
1441<theme-info xmlns="http://www.simplemachines.org/xml/theme-info" xmlns:smf="http://www.simplemachines.org/">
1442<!-- For the id, always use something unique - put your name, a colon, and then the package name. -->
1443<id>smf:' . $smcFunc['strtolower']($context['to_install']['name']) . '</id>
1444<!-- The theme\'s version, please try to use semantic versioning. -->
1445<version>1.0</version>
1446<!-- Install for, the SMF versions this theme was designed for. Uses the same wildcards used in the packager manager. This field is mandatory. -->
1447<install for="'. $context['to_install']['install_for'] .'" />
1448<!-- Theme name, used purely for aesthetics. -->
1449<name>' . $context['to_install']['name'] . '</name>
1450<!-- Author: your email address or contact information. The name attribute is optional. -->
1451<author name="Simple Machines">info@simplemachines.org</author>
1452<!-- Website... where to get updates and more information. -->
1453<website>http://www.simplemachines.org/</website>
1454<!-- Template layers to use, defaults to "html,body". -->
1455<layers>' . $context['to_install']['theme_layers'] . '</layers>
1456<!-- Templates to load on startup. Default is "index". -->
1457<templates>' . $context['to_install']['theme_templates'] . '</templates>
1458<!-- Base this theme off another? Default is blank, or no. It could be "default". -->
1459<based-on></based-on>
1460</theme-info>';
1461
1462	// Now write it.
1463	$fp = @fopen($context['to_install']['theme_dir'] . '/theme_info.xml', 'w+');
1464	if ($fp)
1465	{
1466		fwrite($fp, $xml_info);
1467		fclose($fp);
1468	}
1469
1470	// Install the theme. theme_install() will take care of possible errors.
1471	$context['to_install']['id'] = theme_install($context['to_install']);
1472
1473	// return the info.
1474	return $context['to_install'];
1475}
1476
1477/**
1478 * Install a theme from a specific dir
1479 *
1480 * Assumes the dir is located on the main Themes dir. Ends execution with fatal_lang_error() on any error.
1481 * @return array The newly created theme's info.
1482 */
1483function InstallDir()
1484{
1485	global $themedir, $themeurl, $context;
1486
1487	// Cannot use the theme dir as a theme dir.
1488	if (!isset($_REQUEST['theme_dir']) || empty($_REQUEST['theme_dir']) || rtrim(realpath($_REQUEST['theme_dir']), '/\\') == realpath($themedir))
1489		fatal_lang_error('theme_install_invalid_dir', false);
1490
1491	// Check is there is "something" on the dir.
1492	elseif (!is_dir($_REQUEST['theme_dir']) || !file_exists($_REQUEST['theme_dir'] . '/theme_info.xml'))
1493		fatal_lang_error('theme_install_error', false);
1494
1495	$name = basename($_REQUEST['theme_dir']);
1496	$name = preg_replace(array('/\s/', '/\.[\.]+/', '/[^\w_\.\-]/'), array('_', '.', ''), $name);
1497
1498	// All good! set some needed vars.
1499	$context['to_install'] = array(
1500		'theme_dir' => $_REQUEST['theme_dir'],
1501		'theme_url' => $themeurl . '/' . $name,
1502		'name' => $name,
1503		'images_url' => $themeurl . '/' . $name . '/images',
1504	);
1505
1506	// Read its info form the XML file.
1507	$theme_info = get_theme_info($context['to_install']['theme_dir']);
1508	$context['to_install'] += $theme_info;
1509
1510	// Install the theme. theme_install() will take care of possible errors.
1511	$context['to_install']['id'] = theme_install($context['to_install']);
1512
1513	// return the info.
1514	return $context['to_install'];
1515}
1516
1517/**
1518 * Possibly the simplest and best example of how to use the template system.
1519 *  - allows the theme to take care of actions.
1520 *  - happens if $settings['catch_action'] is set and action isn't found
1521 *   in the action array.
1522 *  - can use a template, layers, sub_template, filename, and/or function.
1523 */
1524function WrapAction()
1525{
1526	global $context, $settings;
1527
1528	// Load any necessary template(s)?
1529	if (isset($settings['catch_action']['template']))
1530	{
1531		// Load both the template and language file. (but don't fret if t…

Large files files are truncated, but you can click here to view the full file