PageRenderTime 65ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 1ms

/sources/admin/ManageThemes.controller.php

https://github.com/Adrekk/Elkarte
PHP | 1978 lines | 1286 code | 300 blank | 392 comment | 249 complexity | 3c849957b2adf4a7d0fc19e4ba3688f7 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1

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

  1. <?php
  2. /**
  3. * This file concerns itself almost completely with theme administration.
  4. * Its tasks include changing theme settings, installing and removing
  5. * themes, choosing the current theme, and editing themes.
  6. *
  7. * @name ElkArte Forum
  8. * @copyright ElkArte Forum contributors
  9. * @license BSD http://opensource.org/licenses/BSD-3-Clause
  10. *
  11. * This software is a derived product, based on:
  12. *
  13. * Simple Machines Forum (SMF)
  14. * copyright: 2011 Simple Machines (http://www.simplemachines.org)
  15. * license: BSD, See included LICENSE.TXT for terms and conditions.
  16. *
  17. * @version 1.0.2
  18. *
  19. *
  20. * @todo Update this for the new package manager?
  21. *
  22. * Creating and distributing theme packages:
  23. * There isn't that much required to package and distribute your own themes...
  24. * just do the following:
  25. * - create a theme_info.xml file, with the root element theme-info.
  26. * - its name should go in a name element, just like description.
  27. * - your name should go in author. (email in the email attribute.)
  28. * - any support website for the theme should be in website.
  29. * - layers and templates (non-default) should go in those elements ;).
  30. * - if the images dir isn't images, specify in the images element.
  31. * - any extra rows for themes should go in extra, serialized. (as in array(variable => value).)
  32. * - tar and gzip the directory - and you're done!
  33. * - please include any special license in a license.txt file.
  34. */
  35. if (!defined('ELK'))
  36. die('No access...');
  37. /**
  38. * Class to deal with theme administration.
  39. *
  40. * Its tasks include changing theme settings, installing and removing
  41. * themes, choosing the current theme, and editing themes.
  42. *
  43. * @package Themes
  44. */
  45. class ManageThemes_Controller extends Action_Controller
  46. {
  47. /**
  48. * Subaction handler - manages the action and delegates control to the proper
  49. * sub-action.
  50. *
  51. * What it does:
  52. * - It loads both the Themes and Settings language files.
  53. * - Checks the session by GET or POST to verify the sent data.
  54. * - Requires the user to not be a guest.
  55. * - Accessed via ?action=admin;area=theme.
  56. *
  57. * @see Action_Controller::action_index()
  58. */
  59. public function action_index()
  60. {
  61. global $txt, $context;
  62. if (isset($_REQUEST['api']))
  63. return $this->action_index_api();
  64. // Load the important language files...
  65. loadLanguage('ManageThemes');
  66. loadLanguage('Settings');
  67. require_once(SUBSDIR . '/Action.class.php');
  68. // No guests in here.
  69. is_not_guest();
  70. // Theme administration, removal, choice, or installation...
  71. $subActions = array(
  72. 'admin' => array($this, 'action_admin', 'permission' => 'admin_forum'),
  73. 'list' => array($this, 'action_list', 'permission' => 'admin_forum'),
  74. 'reset' => array($this, 'action_options', 'permission' => 'admin_forum'),
  75. 'options' => array($this, 'action_options', 'permission' => 'admin_forum'),
  76. 'install' => array($this, 'action_install', 'permission' => 'admin_forum'),
  77. 'remove' => array($this, 'action_remove', 'permission' => 'admin_forum'),
  78. 'pick' => array($this, 'action_pick'), // @todo ugly having that in this controller
  79. 'edit' => array($this, 'action_edit', 'permission' => 'admin_forum'),
  80. 'copy' => array($this, 'action_copy', 'permission' => 'admin_forum'),
  81. 'themelist' => array($this, 'action_themelist', 'permission' => 'admin_forum'),
  82. 'browse' => array($this, 'action_browse', 'permission' => 'admin_forum'),
  83. );
  84. // Action controller
  85. $action = new Action('manage_themes');
  86. // @todo Layout Settings?
  87. if (!empty($context['admin_menu_name']))
  88. {
  89. $context[$context['admin_menu_name']]['tab_data'] = array(
  90. 'title' => $txt['themeadmin_title'],
  91. 'description' => $txt['themeadmin_description'],
  92. 'tabs' => array(
  93. 'admin' => array(
  94. 'description' => $txt['themeadmin_admin_desc'],
  95. ),
  96. 'list' => array(
  97. 'description' => $txt['themeadmin_list_desc'],
  98. ),
  99. 'reset' => array(
  100. 'description' => $txt['themeadmin_reset_desc'],
  101. ),
  102. 'edit' => array(
  103. 'description' => $txt['themeadmin_edit_desc'],
  104. ),
  105. 'themelist' => array(
  106. 'description' => $txt['themeadmin_edit_desc'],
  107. ),
  108. 'browse' => array(
  109. 'description' => $txt['themeadmin_edit_desc'],
  110. ),
  111. ),
  112. );
  113. }
  114. // Follow the sa or just go to administration, call integrate_sa_manage_themes
  115. $subAction = $action->initialize($subActions, 'admin');
  116. // Default the page title to Theme Administration by default.
  117. $context['page_title'] = $txt['themeadmin_title'];
  118. $context['sub_action'] = $subAction;
  119. // Go to the action, if you have permissions
  120. $action->dispatch($subAction);
  121. }
  122. /**
  123. * Responds to an ajax button request, currently only for remove
  124. *
  125. * @uses generic_xml_buttons sub template
  126. */
  127. public function action_index_api()
  128. {
  129. global $txt, $context, $user_info;
  130. loadTemplate('Xml');
  131. // Remove any template layers that may have been created, this is XML!
  132. Template_Layers::getInstance()->removeAll();
  133. $context['sub_template'] = 'generic_xml_buttons';
  134. // No guests in here.
  135. if ($user_info['is_guest'])
  136. {
  137. loadLanguage('Errors');
  138. $context['xml_data'] = array(
  139. 'error' => 1,
  140. 'text' => $txt['not_guests']
  141. );
  142. return;
  143. }
  144. // Theme administration, removal, choice, or installation...
  145. // Of all the actions we currently know only this
  146. $subActions = array(
  147. // 'admin' => 'action_admin',
  148. // 'list' => 'action_list',
  149. // 'reset' => 'action_options',
  150. // 'options' => 'action_options',
  151. // 'install' => 'action_install',
  152. 'remove' => 'action_remove_api',
  153. // 'pick' => 'action_pick',
  154. // 'edit' => 'action_edit',
  155. // 'copy' => 'action_copy',
  156. // 'themelist' => 'action_themelist',
  157. // 'browse' => 'action_browse',
  158. );
  159. // Follow the sa or just go to administration.
  160. if (isset($_GET['sa']) && !empty($subActions[$_GET['sa']]))
  161. $this->{$subActions[$_GET['sa']]}();
  162. else
  163. {
  164. loadLanguage('Errors');
  165. $context['xml_data'] = array(
  166. 'error' => 1,
  167. 'text' => $txt['error_sa_not_set']
  168. );
  169. return;
  170. }
  171. }
  172. /**
  173. * This function allows administration of themes and their settings,
  174. * as well as global theme settings.
  175. *
  176. * What it does:
  177. * - sets the settings theme_allow, theme_guests, and knownThemes.
  178. * - requires the admin_forum permission.
  179. * - accessed with ?action=admin;area=theme;sa=admin.
  180. *
  181. * @uses Themes template
  182. * @uses Admin language file
  183. */
  184. public function action_admin()
  185. {
  186. global $context, $modSettings;
  187. loadLanguage('Admin');
  188. // Saving?
  189. if (isset($_POST['save']))
  190. {
  191. checkSession();
  192. validateToken('admin-tm');
  193. // What themes are being made as known to the members
  194. if (isset($_POST['options']['known_themes']))
  195. foreach ($_POST['options']['known_themes'] as $key => $id)
  196. $_POST['options']['known_themes'][$key] = (int) $id;
  197. else
  198. fatal_lang_error('themes_none_selectable', false);
  199. if (!in_array($_POST['options']['theme_guests'], $_POST['options']['known_themes']))
  200. fatal_lang_error('themes_default_selectable', false);
  201. // Commit the new settings.
  202. updateSettings(array(
  203. 'theme_allow' => !empty($_POST['options']['theme_allow']),
  204. 'theme_guests' => $_POST['options']['theme_guests'],
  205. 'knownThemes' => implode(',', $_POST['options']['known_themes']),
  206. ));
  207. if ((int) $_POST['theme_reset'] == 0 || in_array($_POST['theme_reset'], $_POST['options']['known_themes']))
  208. updateMemberData(null, array('id_theme' => (int) $_POST['theme_reset']));
  209. redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=admin');
  210. }
  211. // If we aren't submitting - that is, if we are about to...
  212. else
  213. {
  214. loadTemplate('ManageThemes');
  215. $context['sub_template'] = 'manage_themes';
  216. // Make our known themes a little easier to work with.
  217. $knownThemes = !empty($modSettings['knownThemes']) ? explode(',', $modSettings['knownThemes']) : array();
  218. // Load up all the themes.
  219. require_once(SUBSDIR . '/Themes.subs.php');
  220. $context['themes'] = loadThemes($knownThemes);
  221. // Can we create a new theme?
  222. $context['can_create_new'] = is_writable(BOARDDIR . '/themes');
  223. $context['new_theme_dir'] = substr(realpath(BOARDDIR . '/themes/default'), 0, -7);
  224. // Look for a non existent theme directory. (ie theme87.)
  225. $theme_dir = BOARDDIR . '/themes/theme';
  226. $i = 1;
  227. while (file_exists($theme_dir . $i))
  228. $i++;
  229. $context['new_theme_name'] = 'theme' . $i;
  230. createToken('admin-tm');
  231. }
  232. }
  233. /**
  234. * This function lists the available themes and provides an interface
  235. * to reset the paths of all the installed themes.
  236. *
  237. * @uses sub template list_themes, tempalte ManageThemes
  238. */
  239. public function action_list()
  240. {
  241. global $context, $boardurl, $txt;
  242. // Load in the helpers we need
  243. require_once(SUBSDIR . '/Themes.subs.php');
  244. loadLanguage('Admin');
  245. if (isset($_REQUEST['th']))
  246. return $this->action_setthemesettings();
  247. // Saving?
  248. if (isset($_POST['save']))
  249. {
  250. checkSession();
  251. validateToken('admin-tl');
  252. $themes = installedThemes();
  253. $setValues = array();
  254. foreach ($themes as $id => $theme)
  255. {
  256. if (file_exists($_POST['reset_dir'] . '/' . basename($theme['theme_dir'])))
  257. {
  258. $setValues[] = array($id, 0, 'theme_dir', realpath($_POST['reset_dir'] . '/' . basename($theme['theme_dir'])));
  259. $setValues[] = array($id, 0, 'theme_url', $_POST['reset_url'] . '/' . basename($theme['theme_dir']));
  260. $setValues[] = array($id, 0, 'images_url', $_POST['reset_url'] . '/' . basename($theme['theme_dir']) . '/' . basename($theme['images_url']));
  261. }
  262. if (isset($theme['base_theme_dir']) && file_exists($_POST['reset_dir'] . '/' . basename($theme['base_theme_dir'])))
  263. {
  264. $setValues[] = array($id, 0, 'base_theme_dir', realpath($_POST['reset_dir'] . '/' . basename($theme['base_theme_dir'])));
  265. $setValues[] = array($id, 0, 'base_theme_url', $_POST['reset_url'] . '/' . basename($theme['base_theme_dir']));
  266. $setValues[] = array($id, 0, 'base_images_url', $_POST['reset_url'] . '/' . basename($theme['base_theme_dir']) . '/' . basename($theme['base_images_url']));
  267. }
  268. cache_put_data('theme_settings-' . $id, null, 90);
  269. }
  270. if (!empty($setValues))
  271. updateThemeOptions($setValues);
  272. redirectexit('action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id']);
  273. }
  274. loadTemplate('ManageThemes');
  275. $context['themes'] = installedThemes();
  276. // For each theme, make sure the directory exists, and try to fetch the theme version
  277. foreach ($context['themes'] as $i => $theme)
  278. {
  279. $context['themes'][$i]['theme_dir'] = realpath($context['themes'][$i]['theme_dir']);
  280. if (file_exists($context['themes'][$i]['theme_dir'] . '/index.template.php'))
  281. {
  282. // Fetch the header... a good 256 bytes should be more than enough.
  283. $fp = fopen($context['themes'][$i]['theme_dir'] . '/index.template.php', 'rb');
  284. $header = fread($fp, 256);
  285. fclose($fp);
  286. // Can we find a version comment, at all?
  287. if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1)
  288. $context['themes'][$i]['version'] = $match[1];
  289. }
  290. $context['themes'][$i]['valid_path'] = file_exists($context['themes'][$i]['theme_dir']) && is_dir($context['themes'][$i]['theme_dir']);
  291. }
  292. // Off to the template we go
  293. $context['sub_template'] = 'list_themes';
  294. addJavascriptVar(array('txt_theme_remove_confirm' => $txt['theme_remove_confirm']), true);
  295. $context['reset_dir'] = realpath(BOARDDIR . '/themes');
  296. $context['reset_url'] = $boardurl . '/themes';
  297. createToken('admin-tl');
  298. createToken('admin-tr', 'request');
  299. }
  300. /**
  301. * Administrative global settings.
  302. *
  303. * - Accessed by ?action=admin;area=theme;sa=reset;
  304. *
  305. * @uses sub template set_options, template file Settings
  306. * @uses template file ManageThemes
  307. */
  308. public function action_options()
  309. {
  310. global $txt, $context, $settings, $modSettings;
  311. require_once(SUBSDIR . '/Themes.subs.php');
  312. $theme = isset($_GET['th']) ? (int) $_GET['th'] : (isset($_GET['id']) ? (int) $_GET['id'] : 0);
  313. if (empty($theme) && empty($_GET['id']))
  314. {
  315. $context['themes'] = installedThemes();
  316. // How many options do we have setup for guests?
  317. $guestOptions = countConfiguredGuestOptions();
  318. foreach ($guestOptions as $guest_option)
  319. $context['themes'][$guest_option['id_theme']]['num_default_options'] = $guest_option['value'];
  320. // How many options do we have setup for members?
  321. $memberOptions = countConfiguredMemberOptions();
  322. foreach ($memberOptions as $member_option)
  323. $context['themes'][$member_option['id_theme']]['num_members'] = $member_option['value'];
  324. // There has to be a Settings template!
  325. foreach ($context['themes'] as $k => $v)
  326. if (empty($v['theme_dir']) || (!file_exists($v['theme_dir'] . '/Settings.template.php') && empty($v['num_members'])))
  327. unset($context['themes'][$k]);
  328. loadTemplate('ManageThemes');
  329. $context['sub_template'] = 'reset_list';
  330. createToken('admin-stor', 'request');
  331. return;
  332. }
  333. // Submit?
  334. if (isset($_POST['submit']) && empty($_POST['who']))
  335. {
  336. checkSession();
  337. validateToken('admin-sto');
  338. if (empty($_POST['options']))
  339. $_POST['options'] = array();
  340. if (empty($_POST['default_options']))
  341. $_POST['default_options'] = array();
  342. // Set up the query values.
  343. $setValues = array();
  344. foreach ($_POST['options'] as $opt => $val)
  345. $setValues[] = array($theme, -1, $opt, is_array($val) ? implode(',', $val) : $val);
  346. $old_settings = array();
  347. foreach ($_POST['default_options'] as $opt => $val)
  348. {
  349. $old_settings[] = $opt;
  350. $setValues[] = array(1, -1, $opt, is_array($val) ? implode(',', $val) : $val);
  351. }
  352. // If we're actually inserting something..
  353. if (!empty($setValues))
  354. {
  355. // Are there options in non-default themes set that should be cleared?
  356. if (!empty($old_settings))
  357. removeThemeOptions('custom', 'guests', $old_settings);
  358. updateThemeOptions($setValues);
  359. }
  360. // Cache the theme settings
  361. cache_put_data('theme_settings-' . $theme, null, 90);
  362. cache_put_data('theme_settings-1', null, 90);
  363. redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=reset');
  364. }
  365. // Changing the current options for all members using this theme
  366. elseif (isset($_POST['submit']) && $_POST['who'] == 1)
  367. {
  368. checkSession();
  369. validateToken('admin-sto');
  370. $_POST['options'] = empty($_POST['options']) ? array() : $_POST['options'];
  371. $_POST['options_master'] = empty($_POST['options_master']) ? array() : $_POST['options_master'];
  372. $_POST['default_options'] = empty($_POST['default_options']) ? array() : $_POST['default_options'];
  373. $_POST['default_options_master'] = empty($_POST['default_options_master']) ? array() : $_POST['default_options_master'];
  374. $old_settings = array();
  375. foreach ($_POST['default_options'] as $opt => $val)
  376. {
  377. if ($_POST['default_options_master'][$opt] == 0)
  378. continue;
  379. elseif ($_POST['default_options_master'][$opt] == 1)
  380. {
  381. // Delete then insert for ease of database compatibility!
  382. removeThemeOptions('default', 'members', $opt);
  383. addThemeOptions(1, $opt, $val);
  384. $old_settings[] = $opt;
  385. }
  386. elseif ($_POST['default_options_master'][$opt] == 2)
  387. removeThemeOptions('all', 'members', $opt);
  388. }
  389. // Delete options from other themes.
  390. if (!empty($old_settings))
  391. removeThemeOptions('custom', 'members', $old_settings);
  392. foreach ($_POST['options'] as $opt => $val)
  393. {
  394. if ($_POST['options_master'][$opt] == 0)
  395. continue;
  396. elseif ($_POST['options_master'][$opt] == 1)
  397. {
  398. // Delete then insert for ease of database compatibility - again!
  399. removeThemeOptions($theme, 'non_default', $opt);
  400. addThemeOptions($theme, $opt, $val);
  401. }
  402. elseif ($_POST['options_master'][$opt] == 2)
  403. removeThemeOptions($theme, 'all', $opt);
  404. }
  405. redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=reset');
  406. }
  407. // Remove all members options and use the defaults
  408. elseif (!empty($_GET['who']) && $_GET['who'] == 2)
  409. {
  410. checkSession('get');
  411. validateToken('admin-stor', 'request');
  412. removeThemeOptions($theme, 'members');
  413. redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=reset');
  414. }
  415. $old_id = $settings['theme_id'];
  416. $old_settings = $settings;
  417. loadTheme($theme, false);
  418. loadLanguage('Profile');
  419. // @todo Should we just move these options so they are no longer theme dependant?
  420. loadLanguage('PersonalMessage');
  421. // Let the theme take care of the settings.
  422. loadTemplate('Settings');
  423. loadSubTemplate('options');
  424. // Set up for the template
  425. $context['sub_template'] = 'set_options';
  426. $context['page_title'] = $txt['theme_settings'];
  427. $context['options'] = $context['theme_options'];
  428. $context['theme_settings'] = $settings;
  429. // Load the options for these theme
  430. if (empty($_REQUEST['who']))
  431. {
  432. $context['theme_options'] = loadThemeOptionsInto(array(1, $theme), -1, $context['theme_options']);
  433. $context['theme_options_reset'] = false;
  434. }
  435. else
  436. {
  437. $context['theme_options'] = array();
  438. $context['theme_options_reset'] = true;
  439. }
  440. // Prepare the options for the template
  441. foreach ($context['options'] as $i => $setting)
  442. {
  443. // Is this disabled?
  444. if ($setting['id'] == 'calendar_start_day' && empty($modSettings['cal_enabled']))
  445. {
  446. unset($context['options'][$i]);
  447. continue;
  448. }
  449. elseif (($setting['id'] == 'topics_per_page' || $setting['id'] == 'messages_per_page') && !empty($modSettings['disableCustomPerPage']))
  450. {
  451. unset($context['options'][$i]);
  452. continue;
  453. }
  454. // Type of field so we display the right input field
  455. if (!isset($setting['type']) || $setting['type'] == 'bool')
  456. $context['options'][$i]['type'] = 'checkbox';
  457. elseif ($setting['type'] == 'int' || $setting['type'] == 'integer')
  458. $context['options'][$i]['type'] = 'number';
  459. elseif ($setting['type'] == 'string')
  460. $context['options'][$i]['type'] = 'text';
  461. if (isset($setting['options']))
  462. $context['options'][$i]['type'] = 'list';
  463. $context['options'][$i]['value'] = !isset($context['theme_options'][$setting['id']]) ? '' : $context['theme_options'][$setting['id']];
  464. }
  465. // Restore the existing theme.
  466. loadTheme($old_id, false);
  467. $settings = $old_settings;
  468. loadTemplate('ManageThemes');
  469. createToken('admin-sto');
  470. }
  471. /**
  472. * Administrative global settings.
  473. *
  474. * What it does:
  475. * - Saves and requests global theme settings. ($settings)
  476. * - Loads the Admin language file.
  477. * - Calls action_admin() if no theme is specified. (the theme center.)
  478. * - Requires admin_forum permission.
  479. * - Accessed with ?action=admin;area=theme;sa=list&th=xx.
  480. */
  481. public function action_setthemesettings()
  482. {
  483. global $txt, $context, $settings, $modSettings;
  484. require_once(SUBSDIR . '/Themes.subs.php');
  485. // Nothing chosen, back to the start you go
  486. if (empty($_GET['th']) && empty($_GET['id']))
  487. return $this->action_admin();
  488. // The theme's ID is needed
  489. $theme = isset($_GET['th']) ? (int) $_GET['th'] : (int) $_GET['id'];
  490. // Validate inputs/user.
  491. if (empty($theme))
  492. fatal_lang_error('no_theme', false);
  493. // Select the best fitting tab.
  494. $context[$context['admin_menu_name']]['current_subsection'] = 'list';
  495. loadLanguage('Admin');
  496. // Fetch the smiley sets...
  497. $sets = explode(',', 'none,' . $modSettings['smiley_sets_known']);
  498. $set_names = explode("\n", $txt['smileys_none'] . "\n" . $modSettings['smiley_sets_names']);
  499. $context['smiley_sets'] = array(
  500. '' => $txt['smileys_no_default']
  501. );
  502. foreach ($sets as $i => $set)
  503. $context['smiley_sets'][$set] = htmlspecialchars($set_names[$i], ENT_COMPAT, 'UTF-8');
  504. $old_id = $settings['theme_id'];
  505. $old_settings = $settings;
  506. loadTheme($theme, false);
  507. // Also load the actual themes language file - in case of special settings.
  508. loadLanguage('Settings', '', true, true);
  509. // And the custom language strings...
  510. loadLanguage('ThemeStrings', '', false, true);
  511. // Let the theme take care of the settings.
  512. loadTemplate('Settings');
  513. loadSubTemplate('settings');
  514. // Load the variants separately...
  515. $settings['theme_variants'] = array();
  516. if (file_exists($settings['theme_dir'] . '/index.template.php'))
  517. {
  518. $file_contents = implode("\n", file($settings['theme_dir'] . '/index.template.php'));
  519. if (preg_match('~\'theme_variants\'\s*=>(.+?\)),$~sm', $file_contents, $matches))
  520. eval('global $settings; $settings[\'theme_variants\'] = ' . $matches[1] . ';');
  521. call_integration_hook('integrate_init_theme', array($theme, &$settings));
  522. }
  523. // Submitting!
  524. if (isset($_POST['save']))
  525. {
  526. // Allowed?
  527. checkSession();
  528. validateToken('admin-sts');
  529. if (empty($_POST['options']))
  530. $_POST['options'] = array();
  531. if (empty($_POST['default_options']))
  532. $_POST['default_options'] = array();
  533. // Make sure items are cast correctly.
  534. foreach ($context['theme_settings'] as $item)
  535. {
  536. // Unwatch this item if this is just a separator.
  537. if (!is_array($item))
  538. continue;
  539. // Clean them up for the database
  540. foreach (array('options', 'default_options') as $option)
  541. {
  542. if (!isset($_POST[$option][$item['id']]))
  543. continue;
  544. // Checkbox.
  545. elseif (empty($item['type']))
  546. $_POST[$option][$item['id']] = $_POST[$option][$item['id']] ? 1 : 0;
  547. // Number
  548. elseif ($item['type'] == 'number')
  549. $_POST[$option][$item['id']] = (int) $_POST[$option][$item['id']];
  550. }
  551. }
  552. // Set up the sql query.
  553. $inserts = array();
  554. foreach ($_POST['options'] as $opt => $val)
  555. $inserts[] = array($theme, 0, $opt, is_array($val) ? implode(',', $val) : $val);
  556. foreach ($_POST['default_options'] as $opt => $val)
  557. $inserts[] = array(1, 0, $opt, is_array($val) ? implode(',', $val) : $val);
  558. // If we're actually inserting something..
  559. if (!empty($inserts))
  560. updateThemeOptions($inserts);
  561. // Clear and Invalidate the cache.
  562. cache_put_data('theme_settings-' . $theme, null, 90);
  563. cache_put_data('theme_settings-1', null, 90);
  564. updateSettings(array('settings_updated' => time()));
  565. redirectexit('action=admin;area=theme;sa=list;th=' . $theme . ';' . $context['session_var'] . '=' . $context['session_id']);
  566. }
  567. $context['sub_template'] = 'set_settings';
  568. $context['page_title'] = $txt['theme_settings'];
  569. foreach ($settings as $setting => $dummy)
  570. {
  571. if (!in_array($setting, array('theme_url', 'theme_dir', 'images_url', 'template_dirs')))
  572. $settings[$setting] = htmlspecialchars__recursive($settings[$setting]);
  573. }
  574. $context['settings'] = $context['theme_settings'];
  575. $context['theme_settings'] = $settings;
  576. foreach ($context['settings'] as $i => $setting)
  577. {
  578. // Separators are dummies, so leave them alone.
  579. if (!is_array($setting))
  580. continue;
  581. // Create the right input fields for the data
  582. if (!isset($setting['type']) || $setting['type'] == 'bool')
  583. $context['settings'][$i]['type'] = 'checkbox';
  584. elseif ($setting['type'] == 'int' || $setting['type'] == 'integer')
  585. $context['settings'][$i]['type'] = 'number';
  586. elseif ($setting['type'] == 'string')
  587. $context['settings'][$i]['type'] = 'text';
  588. if (isset($setting['options']))
  589. $context['settings'][$i]['type'] = 'list';
  590. $context['settings'][$i]['value'] = !isset($settings[$setting['id']]) ? '' : $settings[$setting['id']];
  591. }
  592. // Do we support variants?
  593. if (!empty($settings['theme_variants']))
  594. {
  595. $context['theme_variants'] = array();
  596. foreach ($settings['theme_variants'] as $variant)
  597. {
  598. // Have any text, old chap?
  599. $context['theme_variants'][$variant] = array(
  600. 'label' => isset($txt['variant_' . $variant]) ? $txt['variant_' . $variant] : $variant,
  601. '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'),
  602. );
  603. }
  604. $context['default_variant'] = !empty($settings['default_variant']) && isset($context['theme_variants'][$settings['default_variant']]) ? $settings['default_variant'] : $settings['theme_variants'][0];
  605. }
  606. // Restore the current theme.
  607. loadTheme($old_id, false);
  608. $settings = $old_settings;
  609. // Reinit just incase.
  610. if (function_exists('template_init'))
  611. $settings += template_init();
  612. loadTemplate('ManageThemes');
  613. // We like Kenny better than Token.
  614. createToken('admin-sts');
  615. }
  616. /**
  617. * Remove a theme from the database.
  618. *
  619. * What it does:
  620. * - Removes an installed theme.
  621. * - Requires an administrator.
  622. * - Accessed with ?action=admin;area=theme;sa=remove.
  623. */
  624. public function action_remove()
  625. {
  626. global $modSettings, $context;
  627. require_once(SUBSDIR . '/Themes.subs.php');
  628. checkSession('get');
  629. validateToken('admin-tr', 'request');
  630. // The theme's ID must be an integer.
  631. $theme = isset($_GET['th']) ? (int) $_GET['th'] : (int) $_GET['id'];
  632. // You can't delete the default theme!
  633. if ($theme == 1)
  634. fatal_lang_error('no_access', false);
  635. // Its no longer known
  636. $known = explode(',', $modSettings['knownThemes']);
  637. for ($i = 0, $n = count($known); $i < $n; $i++)
  638. {
  639. if ($known[$i] == $theme)
  640. unset($known[$i]);
  641. }
  642. $known = strtr(implode(',', $known), array(',,' => ','));
  643. // Remove it as an option everywhere
  644. deleteTheme($theme);
  645. // Fix it if the theme was the overall default theme.
  646. if ($modSettings['theme_guests'] == $theme)
  647. updateSettings(array('theme_guests' => '1', 'knownThemes' => $known));
  648. else
  649. updateSettings(array('knownThemes' => $known));
  650. redirectexit('action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id']);
  651. }
  652. /**
  653. * Remove a theme from the database in response to an ajax api request
  654. *
  655. * What it does:
  656. * - Removes an installed theme.
  657. * - Requires an administrator.
  658. * - Accessed with ?action=admin;area=theme;sa=remove;api
  659. */
  660. public function action_remove_api()
  661. {
  662. global $modSettings, $context, $txt;
  663. require_once(SUBSDIR . '/Themes.subs.php');
  664. // Validate what was sent
  665. if (checkSession('get', '', false))
  666. {
  667. loadLanguage('Errors');
  668. $context['xml_data'] = array(
  669. 'error' => 1,
  670. 'text' => $txt['session_verify_fail'],
  671. );
  672. return;
  673. }
  674. // Not just any John Smith can send in a api request
  675. if (!allowedTo('admin_forum'))
  676. {
  677. loadLanguage('Errors');
  678. $context['xml_data'] = array(
  679. 'error' => 1,
  680. 'text' => $txt['cannot_admin_forum'],
  681. );
  682. return;
  683. }
  684. // Even if you are John Smith, you still neeed a ticket
  685. if (!validateToken('admin-tr', 'request', true, false))
  686. {
  687. loadLanguage('Errors');
  688. $context['xml_data'] = array(
  689. 'error' => 1,
  690. 'text' => $txt['token_verify_fail'],
  691. );
  692. return;
  693. }
  694. // The theme's ID must be an integer.
  695. $theme = isset($_GET['th']) ? (int) $_GET['th'] : (int) $_GET['id'];
  696. // You can't delete the default theme!
  697. if ($theme == 1)
  698. {
  699. loadLanguage('Errors');
  700. $context['xml_data'] = array(
  701. 'error' => 1,
  702. 'text' => $txt['no_access'],
  703. );
  704. return;
  705. }
  706. // It is a theme we know about?
  707. $known = explode(',', $modSettings['knownThemes']);
  708. for ($i = 0, $n = count($known); $i < $n; $i++)
  709. {
  710. if ($known[$i] == $theme)
  711. unset($known[$i]);
  712. }
  713. // Finally, remove it
  714. deleteTheme($theme);
  715. $known = strtr(implode(',', $known), array(',,' => ','));
  716. // Fix it if the theme was the overall default theme.
  717. if ($modSettings['theme_guests'] == $theme)
  718. updateSettings(array('theme_guests' => '1', 'knownThemes' => $known));
  719. else
  720. updateSettings(array('knownThemes' => $known));
  721. // Let them know it worked, all without a page refresh
  722. createToken('admin-tr', 'request');
  723. $context['xml_data'] = array(
  724. 'success' => 1,
  725. 'token_var' => $context['admin-tr_token_var'],
  726. 'token' => $context['admin-tr_token'],
  727. );
  728. }
  729. /**
  730. * Choose a theme from a list.
  731. * Allows a user or administrator to pick a new theme with an interface.
  732. *
  733. * What it does:
  734. * - Can edit everyone's (u = 0), guests' (u = -1), or a specific user's.
  735. * - Uses the Themes template. (pick sub template.)
  736. * - Accessed with ?action=admin;area=theme;sa=pick.
  737. *
  738. * @uses Profile language text
  739. * @uses ManageThemes template
  740. * @todo thought so... Might be better to split this file in ManageThemes and Themes,
  741. * with centralized admin permissions on ManageThemes.
  742. */
  743. public function action_pick()
  744. {
  745. global $txt, $context, $modSettings, $user_info, $scripturl, $settings;
  746. require_once(SUBSDIR . '/Themes.subs.php');
  747. if (!$modSettings['theme_allow'] && $settings['disable_user_variant'] && !allowedTo('admin_forum'))
  748. fatal_lang_error('no_access', false);
  749. loadLanguage('Profile');
  750. loadTemplate('ManageThemes');
  751. // Build the link tree.
  752. $context['linktree'][] = array(
  753. 'url' => $scripturl . '?action=theme;sa=pick;u=' . (!empty($_REQUEST['u']) ? (int) $_REQUEST['u'] : 0),
  754. 'name' => $txt['theme_pick'],
  755. );
  756. $context['default_theme_id'] = $modSettings['theme_default'];
  757. $_SESSION['id_theme'] = 0;
  758. if (isset($_GET['id']))
  759. $_GET['th'] = $_GET['id'];
  760. // Saving a variant cause JS doesn't work - pretend it did ;)
  761. if (isset($_POST['save']))
  762. {
  763. // Which theme?
  764. foreach ($_POST['save'] as $k => $v)
  765. $_GET['th'] = (int) $k;
  766. if (isset($_POST['vrt'][$k]))
  767. $_GET['vrt'] = $_POST['vrt'][$k];
  768. }
  769. // Have we made a decision, or are we just browsing?
  770. if (isset($_GET['th']))
  771. {
  772. checkSession('get');
  773. $_GET['th'] = (int) $_GET['th'];
  774. // Save for this user.
  775. if (!isset($_REQUEST['u']) || !allowedTo('admin_forum'))
  776. {
  777. updateMemberData($user_info['id'], array('id_theme' => (int) $_GET['th']));
  778. // A variants to save for the user?
  779. if (!empty($_GET['vrt']))
  780. {
  781. updateThemeOptions(array($_GET['th'], $user_info['id'], 'theme_variant', $_GET['vrt']));
  782. cache_put_data('theme_settings-' . $_GET['th'] . ':' . $user_info['id'], null, 90);
  783. $_SESSION['id_variant'] = 0;
  784. }
  785. redirectexit('action=profile;area=theme');
  786. }
  787. // If changing members or guests - and there's a variant - assume changing default variant.
  788. if (!empty($_GET['vrt']) && ($_REQUEST['u'] == '0' || $_REQUEST['u'] == '-1'))
  789. {
  790. updateThemeOptions(array($_GET['th'], 0, 'default_variant', $_GET['vrt']));
  791. // Make it obvious that it's changed
  792. cache_put_data('theme_settings-' . $_GET['th'], null, 90);
  793. }
  794. // For everyone.
  795. if ($_REQUEST['u'] == '0')
  796. {
  797. updateMemberData(null, array('id_theme' => (int) $_GET['th']));
  798. // Remove any custom variants.
  799. if (!empty($_GET['vrt']))
  800. deleteVariants((int) $_GET['th']);
  801. redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']);
  802. }
  803. // Change the default/guest theme.
  804. elseif ($_REQUEST['u'] == '-1')
  805. {
  806. updateSettings(array('theme_guests' => (int) $_GET['th']));
  807. redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']);
  808. }
  809. // Change a specific member's theme.
  810. else
  811. {
  812. // The forum's default theme is always 0 and we
  813. if (isset($_GET['th']) && $_GET['th'] == 0)
  814. $_GET['th'] = $modSettings['theme_guests'];
  815. updateMemberData((int) $_REQUEST['u'], array('id_theme' => (int) $_GET['th']));
  816. if (!empty($_GET['vrt']))
  817. {
  818. updateThemeOptions(array($_GET['th'], (int) $_REQUEST['u'], 'theme_variant', $_GET['vrt']));
  819. cache_put_data('theme_settings-' . $_GET['th'] . ':' . (int) $_REQUEST['u'], null, 90);
  820. if ($user_info['id'] == $_REQUEST['u'])
  821. $_SESSION['id_variant'] = 0;
  822. }
  823. redirectexit('action=profile;u=' . (int) $_REQUEST['u'] . ';area=theme');
  824. }
  825. }
  826. // Figure out who the member of the minute is, and what theme they've chosen.
  827. if (!isset($_REQUEST['u']) || !allowedTo('admin_forum'))
  828. {
  829. $context['current_member'] = $user_info['id'];
  830. $current_theme = $user_info['theme'];
  831. }
  832. // Everyone can't chose just one.
  833. elseif ($_REQUEST['u'] == '0')
  834. {
  835. $context['current_member'] = 0;
  836. $current_theme = 0;
  837. }
  838. // Guests and such...
  839. elseif ($_REQUEST['u'] == '-1')
  840. {
  841. $context['current_member'] = -1;
  842. $current_theme = $modSettings['theme_guests'];
  843. }
  844. // Someones else :P.
  845. else
  846. {
  847. $context['current_member'] = (int) $_REQUEST['u'];
  848. require_once(SUBSDIR . '/Members.subs.php');
  849. $member = getBasicMemberData($context['current_member']);
  850. $current_theme = $member['id_theme'];
  851. }
  852. // Get the theme name and descriptions.
  853. list ($context['available_themes'], $guest_theme) = availableThemes($current_theme, $context['current_member']);
  854. // As long as we're not doing the default theme...
  855. if (!isset($_REQUEST['u']) || $_REQUEST['u'] >= 0)
  856. {
  857. if ($guest_theme != 0)
  858. $context['available_themes'][0] = $context['available_themes'][$guest_theme];
  859. $context['available_themes'][0]['id'] = 0;
  860. $context['available_themes'][0]['name'] = $txt['theme_forum_default'];
  861. $context['available_themes'][0]['selected'] = $current_theme == 0;
  862. $context['available_themes'][0]['description'] = $txt['theme_global_description'];
  863. }
  864. ksort($context['available_themes']);
  865. $context['page_title'] = $txt['theme_pick'];
  866. $context['sub_template'] = 'pick';
  867. }
  868. /**
  869. * Installs new themes, either from a gzip or copy of the default.
  870. *
  871. * What it does:
  872. * - Puts themes in $boardurl/themes.
  873. * - Assumes the gzip has a root directory in it. (ie default.)
  874. * - Requires admin_forum.
  875. * - Accessed with ?action=admin;area=theme;sa=install.
  876. *
  877. * @uses ManageThemes template
  878. */
  879. public function action_install()
  880. {
  881. global $boardurl, $txt, $context, $settings, $modSettings;
  882. checkSession('request');
  883. require_once(SUBSDIR . '/Themes.subs.php');
  884. require_once(SUBSDIR . '/Package.subs.php');
  885. loadTemplate('ManageThemes');
  886. // Passed an ID, then the install is complete, lets redirect and show them
  887. if (isset($_GET['theme_id']))
  888. {
  889. $_GET['theme_id'] = (int) $_GET['theme_id'];
  890. $context['sub_template'] = 'installed';
  891. $context['page_title'] = $txt['theme_installed'];
  892. $context['installed_theme'] = array(
  893. 'id' => $_GET['theme_id'],
  894. 'name' => getThemeName($_GET['theme_id']),
  895. );
  896. return;
  897. }
  898. // How are we going to install this theme, from a dir, zip, copy of default?
  899. if ((!empty($_FILES['theme_gz']) && (!isset($_FILES['theme_gz']['error']) || $_FILES['theme_gz']['error'] != 4)) || !empty($_REQUEST['theme_gz']))
  900. $method = 'upload';
  901. elseif (isset($_REQUEST['theme_dir']) && rtrim(realpath($_REQUEST['theme_dir']), '/\\') != realpath(BOARDDIR . '/themes') && file_exists($_REQUEST['theme_dir']))
  902. $method = 'path';
  903. else
  904. $method = 'copy';
  905. // Copy the default theme?
  906. if (!empty($_REQUEST['copy']) && $method == 'copy')
  907. {
  908. // Hopefully the themes directory is writable, or we might have a problem.
  909. if (!is_writable(BOARDDIR . '/themes'))
  910. fatal_lang_error('theme_install_write_error', 'critical');
  911. // Make the new directory, standard characters only
  912. $theme_dir = BOARDDIR . '/themes/' . preg_replace('~[^A-Za-z0-9_\- ]~', '', $_REQUEST['copy']);
  913. umask(0);
  914. mkdir($theme_dir, 0777);
  915. // Get some more time if we can
  916. @set_time_limit(600);
  917. if (function_exists('apache_reset_timeout'))
  918. @apache_reset_timeout();
  919. // Create the subdirectories for css, javascript and font files.
  920. mkdir($theme_dir . '/css', 0777);
  921. mkdir($theme_dir . '/scripts', 0777);
  922. mkdir($theme_dir . '/webfonts', 0777);
  923. // Copy over the default non-theme files.
  924. $to_copy = array('/index.php', '/index.template.php', '/scripts/theme.js');
  925. foreach ($to_copy as $file)
  926. {
  927. copy($settings['default_theme_dir'] . $file, $theme_dir . $file);
  928. @chmod($theme_dir . $file, 0777);
  929. }
  930. // And now the entire css, images and webfonts directories!
  931. copytree($settings['default_theme_dir'] . '/css', $theme_dir . '/css');
  932. copytree($settings['default_theme_dir'] . '/images', $theme_dir . '/images');
  933. copytree($settings['default_theme_dir'] . '/webfonts', $theme_dir . '/webfonts');
  934. package_flush_cache();
  935. $theme_name = $_REQUEST['copy'];
  936. $images_url = $boardurl . '/themes/' . basename($theme_dir) . '/images';
  937. $theme_dir = realpath($theme_dir);
  938. // Lets get some data for the new theme (default theme (1), default settings (0)).
  939. $theme_values = loadThemeOptionsInto(1, 0, array(), array('theme_templates', 'theme_layers'));
  940. // Lets add a theme_info.xml to this theme.
  941. write_theme_info($_REQUEST['copy'], $modSettings['elkVersion'], $theme_dir, $theme_values);
  942. }
  943. // Install from another directory
  944. elseif (isset($_REQUEST['theme_dir']) && $method == 'path')
  945. {
  946. if (!is_dir($_REQUEST['theme_dir']) || !file_exists($_REQUEST['theme_dir'] . '/theme_info.xml'))
  947. fatal_lang_error('theme_install_error', false);
  948. $theme_name = basename($_REQUEST['theme_dir']);
  949. $theme_dir = $_REQUEST['theme_dir'];
  950. }
  951. // Uploaded a zip file to install from
  952. elseif ($method == 'upload')
  953. {
  954. // Hopefully the themes directory is writable, or we might have a problem.
  955. if (!is_writable(BOARDDIR . '/themes'))
  956. fatal_lang_error('theme_install_write_error', 'critical');
  957. // This happens when the admin session is gone and the user has to login again
  958. if (empty($_FILES['theme_gz']) && empty($_REQUEST['theme_gz']))
  959. redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']);
  960. // Set the default settings...
  961. $theme_name = strtok(basename(isset($_FILES['theme_gz']) ? $_FILES['theme_gz']['name'] : $_REQUEST['theme_gz']), '.');
  962. $theme_name = preg_replace(array('/\s/', '/\.[\.]+/', '/[^\w_\.\-]/'), array('_', '.', ''), $theme_name);
  963. $theme_dir = BOARDDIR . '/themes/' . $theme_name;
  964. if (isset($_FILES['theme_gz']) && is_uploaded_file($_FILES['theme_gz']['tmp_name']) && (ini_get('open_basedir') != '' || file_exists($_FILES['theme_gz']['tmp_name'])))
  965. read_tgz_file($_FILES['theme_gz']['tmp_name'], BOARDDIR . '/themes/' . $theme_name, false, true);
  966. elseif (isset($_REQUEST['theme_gz']))
  967. {
  968. if (!isAuthorizedServer($_REQUEST['theme_gz']))
  969. fatal_lang_error('not_valid_server');
  970. read_tgz_file($_REQUEST['theme_gz'], BOARDDIR . '/themes/' . $theme_name, false, true);
  971. }
  972. else
  973. redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']);
  974. }
  975. else
  976. fatal_lang_error('theme_install_general', false);
  977. // Something go wrong?
  978. if ($theme_dir != '' && basename($theme_dir) != 'themes')
  979. {
  980. // Defaults.
  981. $install_info = array(
  982. 'theme_url' => $boardurl . '/themes/' . basename($theme_dir),
  983. 'images_url' => isset($images_url) ? $images_url : $boardurl . '/themes/' . basename($theme_dir) . '/images',
  984. 'theme_dir' => $theme_dir,
  985. 'name' => $theme_name
  986. );
  987. $explicit_images = false;
  988. if (file_exists($theme_dir . '/theme_info.xml'))
  989. {
  990. $theme_info = file_get_contents($theme_dir . '/theme_info.xml');
  991. // Parse theme-info.xml into an Xml_Array.
  992. require_once(SUBSDIR . '/XmlArray.class.php');
  993. $theme_info_xml = new Xml_Array($theme_info);
  994. // @todo Error message of some sort?
  995. if (!$theme_info_xml->exists('theme-info[0]'))
  996. return 'package_get_error_packageinfo_corrupt';
  997. $theme_info_xml = $theme_info_xml->path('theme-info[0]');
  998. $theme_info_xml = $theme_info_xml->to_array();
  999. $xml_elements = array(
  1000. 'name' => 'name',
  1001. 'theme_layers' => 'layers',
  1002. 'theme_templates' => 'templates',
  1003. 'based_on' => 'based-on',
  1004. );
  1005. foreach ($xml_elements as $var => $name)
  1006. {
  1007. if (!empty($theme_info_xml[$name]))
  1008. $install_info[$var] = $theme_info_xml[$name];
  1009. }
  1010. if (!empty($theme_info_xml['images']))
  1011. {
  1012. $install_info['images_url'] = $install_info['theme_url'] . '/' . $theme_info_xml['images'];
  1013. $explicit_images = true;
  1014. }
  1015. if (!empty($theme_info_xml['extra']))
  1016. $install_info += unserialize($theme_info_xml['extra']);
  1017. }
  1018. if (isset($install_info['based_on']))
  1019. {
  1020. if ($install_info['based_on'] == 'default')
  1021. {
  1022. $install_info['theme_url'] = $settings['default_theme_url'];
  1023. $install_info['images_url'] = $settings['default_images_url'];
  1024. }
  1025. elseif ($install_info['based_on'] != '')
  1026. {
  1027. $install_info['based_on'] = preg_replace('~[^A-Za-z0-9\-_ ]~', '', $install_info['based_on']);
  1028. $temp = loadBasedOnTheme($install_info['based_on'], $explicit_images);
  1029. // @todo An error otherwise?
  1030. if (is_array($temp))
  1031. {
  1032. $install_info = $temp + $install_info;
  1033. if (empty($explicit_images) && !empty($install_info['base_theme_url']))
  1034. $install_info['theme_url'] = $install_info['base_theme_url'];
  1035. }
  1036. }
  1037. unset($install_info['based_on']);
  1038. }
  1039. // Find the newest id_theme.
  1040. $id_theme = nextTheme();
  1041. $inserts = array();
  1042. foreach ($install_info as $var => $val)
  1043. $inserts[] = array($id_theme, $var, $val);
  1044. if (!empty($inserts))
  1045. addTheme($inserts);
  1046. updateSettings(array('knownThemes' => strtr($modSettings['knownThemes'] . ',' . $id_theme, array(',,' => ','))));
  1047. }
  1048. redirectexit('action=admin;area=theme;sa=install;theme_id=' . $id_theme . ';' . $context['session_var'] . '=' . $context['session_id']);
  1049. }
  1050. /**
  1051. * Set a theme option via javascript.
  1052. *
  1053. * What it does:
  1054. * - sets a theme option without outputting anything.
  1055. * - can be used with javascript, via a dummy image... (which doesn't require
  1056. * the page to reload.)
  1057. * - requires someone who is logged in.
  1058. * - accessed via ?action=jsoption;var=variable;val=value;session_var=sess_id.
  1059. * - optionally contains &th=theme id
  1060. * - does not log access to the Who's Online log. (in index.php..)
  1061. */
  1062. public function action_jsoption()
  1063. {
  1064. global $settings, $user_info, $options, $modSettings;
  1065. // Check the session id.
  1066. checkSession('get');
  1067. // This good-for-nothing pixel is being used to keep the session alive.
  1068. if (empty($_GET['var']) || !isset($_GET['val']))
  1069. redirectexit($settings['images_url'] . '/blank.png');
  1070. // Sorry, guests can't go any further than this..
  1071. if ($user_info['is_guest'] || $user_info['id'] == 0)
  1072. obExit(false);
  1073. $reservedVars = array(
  1074. 'actual_theme_url',
  1075. 'actual_images_url',
  1076. 'base_theme_dir',
  1077. 'base_theme_url',
  1078. 'default_images_url',
  1079. 'default_theme_dir',
  1080. 'default_theme_url',
  1081. 'default_template',
  1082. 'images_url',
  1083. 'number_recent_posts',
  1084. 'smiley_sets_default',
  1085. 'theme_dir',
  1086. 'theme_id',
  1087. 'theme_layers',
  1088. 'theme_templates',
  1089. 'theme_url',
  1090. 'name',
  1091. );
  1092. // Can't change reserved vars.
  1093. if (in_array(strtolower($_GET['var']), $reservedVars))
  1094. redirectexit($settings['images_url'] . '/blank.png');
  1095. // Use a specific theme?
  1096. if (isset($_GET['th']) || isset($_GET['id']))
  1097. {
  1098. // Invalidate the current themes cache too.
  1099. if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
  1100. cache_put_data('theme_settings-' . $settings['theme_id'] . ':' . $user_info['id'], null, 60);
  1101. $settings['theme_id'] = isset($_GET['th']) ? (int) $_GET['th'] : (int) $_GET['id'];
  1102. }
  1103. // If this is the admin preferences the passed value will just be an element of it.
  1104. if ($_GET['var'] == 'admin_preferences')
  1105. {
  1106. $options['admin_preferences'] = !empty($options['admin_preferences']) ? unserialize($options['admin_preferences']) : array();
  1107. // New thingy...
  1108. if (isset($_GET['admin_key']) && strlen($_GET['admin_key']) < 5)
  1109. $options['admin_preferences'][$_GET['admin_key']] = $_GET['val'];
  1110. // Change the value to be something nice,
  1111. $_GET['val'] = serialize($options['admin_preferences']);
  1112. }
  1113. // If this is the window min/max settings, the passed window name will just be an element of it.
  1114. elseif ($_GET['var'] == 'minmax_preferences')
  1115. {
  1116. $options['minmax_preferences'] = !empty($options['minmax_preferences']) ? unserialize($options['minmax_preferences']) : array();
  1117. // New value for them
  1118. if (isset($_GET['minmax_key']) && strlen($_GET['minmax_key']) < 10)
  1119. $options['minmax_preferences'][$_GET['minmax_key']] = $_GET['val'];
  1120. // Change the value to be something nice,
  1121. $_GET['val'] = serialize($options['minmax_preferences']);
  1122. }
  1123. // Update the option.
  1124. require_once(SUBSDIR . '/Themes.subs.php');
  1125. updateThemeOptions(array($settings['theme_id'], $user_info['id'], $_GET['var'], is_array($_GET['val']) ? implode(',', $_GET['val']) : $_GET['val']));
  1126. if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
  1127. cache_put_data('theme_settings-' . $settings['theme_id'] . ':' . $user_info['id'], null, 60);
  1128. // Don't output anything...
  1129. redirectexit($settings['images_url'] . '/blank.png');
  1130. }
  1131. /**
  1132. * Allows choosing, browsing, and editing a themes files.
  1133. *
  1134. * What it does:
  1135. * - Its subactions handle several features:
  1136. * - edit_template: display and edit a PHP template file
  1137. * - edit_style: display and edit a CSS file
  1138. * - edit_file: display and edit other files in the theme
  1139. * - accessed via ?action=admin;area=theme;sa=edit
  1140. *
  1141. * @uses the ManageThemes template
  1142. */
  1143. public function action_edit()
  1144. {
  1145. global $context;
  1146. loadTemplate('ManageThemes');
  1147. // We'll work hard with them themes!
  1148. require_once(SUBSDIR . '/Themes.subs.php');
  1149. $selectedTheme = isset($_GET['th']) ? (int) $_GET['th'] : (isset($_GET['id']) ? (int) $_GET['id'] : 0);
  1150. // Unfortunately we cannot edit an unkwown theme.. redirect.
  1151. if (empty($selectedTheme))
  1152. redirectexit('action=admin;area=theme;sa=themelist');
  1153. // You're browsing around, aren't you
  1154. elseif (!isset($_REQUEST['filename']))
  1155. redirectexit('action=admin;area=theme;sa=browse;th=' . $selectedTheme);
  1156. // We don't have errors. Yet.
  1157. $context['session_error'] = false;
  1158. // We're editing a theme file.
  1159. // Get the directory of the theme we are editing.
  1160. $context['theme_id'] = $selectedTheme;
  1161. $theme_dir = themeDirectory($context['theme_id']);
  1162. $this->prepareThemeEditContext($theme_dir);
  1163. // Saving?
  1164. if (isset($_POST['save']))
  1165. {
  1166. $this->_action_edit_submit();
  1167. // Now lets get out of here!
  1168. return;
  1169. }
  1170. // We're editing .css, .template.php, .{language}.php or others.
  1171. // Note: we're here sending $theme_dir as parameter to action_()
  1172. // controller functions, which isn't cool. To be refactored.
  1173. if (substr($_REQUEST['filename'], -4) == '.css')
  1174. $this->_action_edit_style($theme_dir);
  1175. elseif (substr($_REQUEST['filename'], -13) == '.template.php')
  1176. $this->_action_edit_template($theme_dir);
  1177. else
  1178. $this->_action_edit_file($theme_dir);
  1179. // Create a special token to allow editing of multiple files.
  1180. createToken('admin-te-' . md5($selectedTheme . '-' . $_REQUEST['filename']));
  1181. }
  1182. /**
  1183. * Displays for edition in admin panel a css file.
  1184. *
  1185. * This function is forwarded to, from
  1186. * ?action=admin;area=theme;sa=edit
  1187. *
  1188. * @param string $theme_dir absolute path of the selected theme directory
  1189. */
  1190. private function _action_edit_style($theme_dir)
  1191. {
  1192. global $context, $settings;
  1193. addJavascriptVar(array(
  1194. 'previewData' => '',
  1195. 'previewTimeout' => '',
  1196. 'refreshPreviewCache' => '',
  1197. 'editFilename' => $context['edit_filename'],
  1198. 'theme_id' => $settings['theme_id'],
  1199. ), true);
  1200. // pick the template and send it the file
  1201. $context['sub_template'] = 'edit_style';
  1202. $context['entire_file'] = htmlspecialchars(strtr(file_get_contents($theme_dir . '/' . $_REQUEST['filename']), array("\t" => ' ')), ENT_COMPAT, 'UTF-8');
  1203. }
  1204. /**
  1205. * Displays for editing in the admin panel a template file.
  1206. *
  1207. * This function is forwarded to, from
  1208. * ?action=admin;area=theme;sa=edit
  1209. *
  1210. * @param string $theme_dir absolute path of the selected theme directory
  1211. */
  1212. private function _action_edit_template($theme_dir)
  1213. {
  1214. global $context;
  1215. // Make sure the sub-template is set
  1216. $context['sub_template'] = 'edit_template';
  1217. // Retrieve the contents of the file
  1218. $file_data = file($theme_dir . '/' . $_REQUEST['filename']);
  1219. // For a PHP template file, we display each function in separate boxes.
  1220. $j = 0;
  1221. $context['file_parts'] = array(array('lines' => 0, 'line' => 1, 'data' => '', 'function' => ''));
  1222. for ($i = 0, $n = count($file_data); $i < $n; $i++)
  1223. {
  1224. // @todo refactor this so the docblocks are in the function content window
  1225. if (substr($file_data[$i], 0, 9) === 'function ')
  1226. {
  1227. // Try to format the functions a little nicer...
  1228. $context['file_parts'][$j]['data'] = trim($context['file_parts'][$j]['data']);
  1229. if (empty($context['file_parts'][$j]['lines']))
  1230. unset($context['file_parts'][$j]);
  1231. // Start a new function block
  1232. $context['file_parts'][++$j] = array('lines' => 0, 'line' => $i, 'data' => '');
  1233. }
  1234. $context['file_parts'][$j]['lines']++;
  1235. $context['file_parts'][$j]['data'] .= htmlspecialchars(strtr($file_data[$i], array("\t" => ' ')), ENT_COMPAT, 'UTF-8');
  1236. }
  1237. $context['entire_file'] = htmlspecialchars(strtr(implode('', $file_data), array("\t" => ' ')), ENT_COMPAT, 'UTF-8');
  1238. }
  1239. /**
  1240. * Handles editing in admin of other types of files from a theme,
  1241. * except templates and css.
  1242. *
  1243. * This function is forwarded to, from
  1244. * ?action=admin;area=theme;sa=edit
  1245. *
  1246. * @param string $theme_dir absolute path of the selected theme directory
  1247. */
  1248. private function _action_edit_file($theme_dir)
  1249. {
  1250. global $context;
  1251. // Simply set the template and the file contents.
  1252. $context['sub_template'] = 'edit_file';
  1253. $context['entire_file'] = htmlspecialchars(strtr(file_get_contents($theme_dir . '/' . $_REQUEST['filename']), array("\t" => ' ')), ENT_COMPAT, 'UTF-8');
  1254. }
  1255. /**
  1256. * This function handles submission of a template file.
  1257. * It checks the file for syntax errors, and if it passes, it saves it.
  1258. *
  1259. * This function is forwarded to, from

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