/** * This function handles submission of a template file. * It checks the file for syntax errors, and if it passes, it saves it. * * This function is forwarded to, from * ?action=admin;area=theme;sa=edit */ private function _action_edit_submit() { global $context, $settings, $user_info; $selectedTheme = isset($_GET['th']) ? (int) $_GET['th'] : (isset($_GET['id']) ? (int) $_GET['id'] : 0); if (empty($selectedTheme)) { // This should never be happening. Never I say. But... in case it does :P fatal_lang_error('theme_edit_missing'); } $theme_dir = themeDirectory($context['theme_id']); $file = isset($_POST['entire_file']) ? $_POST['entire_file'] : ''; // You did submit *something*, didn't you? if (empty($file)) { // @todo a better error message fatal_lang_error('theme_edit_missing'); } // Checking PHP syntax on css files is not a most constructive use of processing power :P // We need to know what kind of file we have $is_php = substr($_REQUEST['filename'], -4) == '.php'; $is_template = substr($_REQUEST['filename'], -13) == '.template.php'; $is_css = substr($_REQUEST['filename'], -4) == '.css'; // Check you up if (checkSession('post', '', false) == '' && validateToken('admin-te-' . md5($selectedTheme . '-' . $_REQUEST['filename']), 'post', false) == true) { // Consolidate the format in which we received the file contents if (is_array($file)) { $entire_file = implode("\n", $file); } else { $entire_file = $file; } // Convert our tabs back to tabs! $entire_file = rtrim(strtr($entire_file, array("\r" => '', ' ' => "\t"))); // Errors? No errors! $errors = array(); // For PHP files, we check the syntax. if ($is_php) { require_once SUBSDIR . '/DataValidator.class.php'; require_once SUBSDIR . '/Modlog.subs.php'; // Since we are running php code, let's track it, but only once in a while. if (!recentlyLogged('editing_theme', 60)) { logAction('editing_theme', array('member' => $user_info['id']), 'admin'); // But the email only once every 60 minutes should be fine if (!recentlyLogged('editing_theme', 3600)) { require_once SUBSDIR . '/Themes.subs.php'; require_once SUBSDIR . '/Admin.subs.php'; $theme_info = getBasicThemeInfos($context['theme_id']); emailAdmins('editing_theme', array('EDIT_REALNAME' => $user_info['name'], 'FILE_EDITED' => $_REQUEST['filename'], 'THEME_NAME' => $theme_info[$context['theme_id']])); } } $validator = new Data_Validator(); $validator->validation_rules(array('entire_file' => 'php_syntax')); $validator->validate(array('entire_file' => $entire_file)); // Retrieve the errors $errors = $validator->validation_errors(); } // If successful so far, we'll take the plunge and save this piece of art. if (empty($errors)) { // Try to save the new file contents $fp = fopen($theme_dir . '/' . $_REQUEST['filename'], 'w'); fwrite($fp, $entire_file); fclose($fp); if (function_exists('opcache_invalidate')) { opcache_invalidate($theme_dir . '/' . $_REQUEST['filename']); } // We're done here. redirectexit('action=admin;area=theme;th=' . $selectedTheme . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=browse;directory=' . dirname($_REQUEST['filename'])); } else { // Pick the right sub-template for the next try if ($is_template) { $context['sub_template'] = 'edit_template'; } else { $context['sub_template'] = 'edit_file'; } // Fill contextual data for the template, the errors to show foreach ($errors as $error) { $context['parse_error'][] = $error; } // The format of the data depends on template/non-template file. if (!is_array($file)) { $file = array($file); } // Send back the file contents $context['entire_file'] = htmlspecialchars(strtr(implode('', $file), array("\t" => ' ')), ENT_COMPAT, 'UTF-8'); foreach ($file as $i => $file_part) { $context['file_parts'][$i]['lines'] = strlen($file_part); $context['file_parts'][$i]['data'] = $file_part; } // Re-create token for another try createToken('admin-te-' . md5($selectedTheme . '-' . $_REQUEST['filename'])); return; } } else { loadLanguage('Errors'); // Notify the template of trouble $context['session_error'] = true; // Recycle the submitted data. if (is_array($file)) { $context['entire_file'] = htmlspecialchars(implode("\n", $file), ENT_COMPAT, 'UTF-8'); } else { $context['entire_file'] = htmlspecialchars($file, ENT_COMPAT, 'UTF-8'); } $context['edit_filename'] = htmlspecialchars($_POST['filename'], ENT_COMPAT, 'UTF-8'); // Choose sub-template if ($is_template) { $context['sub_template'] = 'edit_template'; } elseif ($is_css) { addJavascriptVar(array('previewData' => '\'\'', 'previewTimeout' => '\'\'', 'refreshPreviewCache' => '\'\'', 'editFilename' => JavaScriptEscape($context['edit_filename']), 'theme_id' => $settings['theme_id'])); $context['sub_template'] = 'edit_style'; } else { $context['sub_template'] = 'edit_file'; } // Re-create the token so that it can be used createToken('admin-te-' . md5($selectedTheme . '-' . $_REQUEST['filename'])); return; } }
/** * Download a language file from the website. * * What it does: * - Requires a valid download ID ("did") in the URL. * - Also handles installing language files. * - Attempts to chmod things as needed. * - Uses a standard list to display information about all the files and where they'll be put. * * @uses ManageLanguages template, download_language sub-template. * @uses Admin template, show_list sub-template. */ public function action_downloadlang() { global $context, $forum_version, $txt, $scripturl, $modSettings; // @todo for the moment there is no facility to download packages, so better kill it here fatal_lang_error('no_access', false); loadLanguage('ManageSettings'); require_once SUBSDIR . '/Package.subs.php'; // Clearly we need to know what to request. if (!isset($_GET['did'])) { fatal_lang_error('no_access', false); } // Some lovely context. $context['download_id'] = $_GET['did']; $context['sub_template'] = 'download_language'; $context['menu_data_' . $context['admin_menu_id']]['current_subsection'] = 'add'; // Can we actually do the installation - and do they want to? if (!empty($_POST['do_install']) && !empty($_POST['copy_file'])) { checkSession('get'); validateToken('admin-dlang'); $chmod_files = array(); $install_files = array(); // Check writable status. foreach ($_POST['copy_file'] as $file) { // Check it's not very bad. if (strpos($file, '..') !== false || strpos($file, 'themes') !== 0 && !preg_match('~agreement\\.[A-Za-z-_0-9]+\\.txt$~', $file)) { fatal_error($txt['languages_download_illegal_paths']); } $chmod_files[] = BOARDDIR . '/' . $file; $install_files[] = $file; } // Call this in case we have work to do. $file_status = create_chmod_control($chmod_files); $files_left = $file_status['files']['notwritable']; // Something not writable? if (!empty($files_left)) { $context['error_message'] = $txt['languages_download_not_chmod']; } elseif (!empty($install_files)) { // @todo retrieve the language pack per naming pattern from our sites read_tgz_file('http://download.elkarte.net/fetch_language.php?version=' . urlencode(strtr($forum_version, array('ElkArte ' => ''))) . ';fetch=' . urlencode($_GET['did']), BOARDDIR, false, true, $install_files); // Make sure the files aren't stuck in the cache. package_flush_cache(); $context['install_complete'] = sprintf($txt['languages_download_complete_desc'], $scripturl . '?action=admin;area=languages'); return; } } // @todo Open up the old china. if (!isset($archive_content)) { $archive_content = read_tgz_file('http://download.elkarte.net/fetch_language.php?version=' . urlencode(strtr($forum_version, array('ElkArte ' => ''))) . ';fetch=' . urlencode($_GET['did']), null); } if (empty($archive_content)) { fatal_error($txt['add_language_error_no_response']); } // Now for each of the files, let's do some *stuff* $context['files'] = array('lang' => array(), 'other' => array()); $context['make_writable'] = array(); foreach ($archive_content as $file) { $dirname = dirname($file['filename']); $filename = basename($file['filename']); $extension = substr($filename, strrpos($filename, '.') + 1); // Don't do anything with files we don't understand. if (!in_array($extension, array('php', 'jpg', 'gif', 'jpeg', 'png', 'txt'))) { continue; } // Basic data. $context_data = array('name' => $filename, 'destination' => BOARDDIR . '/' . $file['filename'], 'generaldest' => $file['filename'], 'size' => $file['size'], 'writable' => false, 'default_copy' => true, 'exists' => false); // Does the file exist, is it different and can we overwrite? if (file_exists(BOARDDIR . '/' . $file['filename'])) { if (is_writable(BOARDDIR . '/' . $file['filename'])) { $context_data['writable'] = true; } // Finally, do we actually think the content has changed? if ($file['size'] == filesize(BOARDDIR . '/' . $file['filename']) && $file['md5'] === md5_file(BOARDDIR . '/' . $file['filename'])) { $context_data['exists'] = 'same'; $context_data['default_copy'] = false; } elseif ($file['md5'] === md5(preg_replace("~[\r]?\n~", "\r\n", file_get_contents(BOARDDIR . '/' . $file['filename'])))) { $context_data['exists'] = 'same'; $context_data['default_copy'] = false; } else { $context_data['exists'] = 'different'; } } else { // Can we at least stick it in the directory... if (is_writable(BOARDDIR . '/' . $dirname)) { $context_data['writable'] = true; } } // I love PHP files, that's why I'm a developer and not an artistic type spending my time drinking absinth and living a life of sin... if ($extension == 'php' && preg_match('~\\w+\\.\\w+(?:-utf8)?\\.php~', $filename)) { $context_data += array('version' => '??', 'cur_version' => false, 'version_compare' => 'newer'); list($name, $language) = explode('.', $filename); // Let's get the new version, I like versions, they tell me that I'm up to date. if (preg_match('~\\s*Version:\\s+(.+?);\\s*' . preg_quote($name, '~') . '~i', $file['preview'], $match) == 1) { $context_data['version'] = $match[1]; } // Now does the old file exist - if so what is it's version? if (file_exists(BOARDDIR . '/' . $file['filename'])) { // OK - what is the current version? $fp = fopen(BOARDDIR . '/' . $file['filename'], 'rb'); $header = fread($fp, 768); fclose($fp); // Find the version. if (preg_match('~(?://|/\\*)\\s*Version:\\s+(.+?);\\s*' . preg_quote($name, '~') . '(?:[\\s]{2}|\\*/)~i', $header, $match) == 1) { $context_data['cur_version'] = $match[1]; // How does this compare? if ($context_data['cur_version'] == $context_data['version']) { $context_data['version_compare'] = 'same'; } elseif ($context_data['cur_version'] > $context_data['version']) { $context_data['version_compare'] = 'older'; } // Don't recommend copying if the version is the same. if ($context_data['version_compare'] != 'newer') { $context_data['default_copy'] = false; } } } // Add the context data to the main set. $context['files']['lang'][] = $context_data; } else { // If we think it's a theme thing, work out what the theme is. if (strpos($dirname, 'themes') === 0 && preg_match('~themes[\\/]([^\\/]+)[\\/]~', $dirname, $match)) { $theme_name = $match[1]; } else { $theme_name = 'misc'; } // Assume it's an image, could be an acceptance note etc but rare. $context['files']['images'][$theme_name][] = $context_data; } // Collect together all non-writable areas. if (!$context_data['writable']) { $context['make_writable'][] = $context_data['destination']; } } // So, I'm a perfectionist - let's get the theme names. $indexes = array(); foreach ($context['files']['images'] as $k => $dummy) { $indexes[] = $k; } $context['theme_names'] = array(); if (!empty($indexes)) { require_once SUBSDIR . '/Themes.subs.php'; $value_data = array('query' => array(), 'params' => array()); foreach ($indexes as $k => $index) { $value_data['query'][] = 'value LIKE {string:value_' . $k . '}'; $value_data['params']['value_' . $k] = '%' . $index; } $themes = validateThemeName($indexes, $value_data); // Now we have the id_theme we can get the pretty description. if (!empty($themes)) { $context['themes'] = getBasicThemeInfos($themes); } } // Before we go to far can we make anything writable, eh, eh? if (!empty($context['make_writable'])) { // What is left to be made writable? $file_status = create_chmod_control($context['make_writable']); $context['still_not_writable'] = $file_status['files']['notwritable']; // Mark those which are now writable as such. foreach ($context['files'] as $type => $data) { if ($type == 'lang') { foreach ($data as $k => $file) { if (!$file['writable'] && !in_array($file['destination'], $context['still_not_writable'])) { $context['files'][$type][$k]['writable'] = true; } } } else { foreach ($data as $theme => $files) { foreach ($files as $k => $file) { if (!$file['writable'] && !in_array($file['destination'], $context['still_not_writable'])) { $context['files'][$type][$theme][$k]['writable'] = true; } } } } } // Are we going to need more language stuff? if (!empty($context['still_not_writable'])) { loadLanguage('Packages'); } } // This is the list for the main files. $listOptions = array('id' => 'lang_main_files_list', 'title' => $txt['languages_download_main_files'], 'get_items' => array('function' => create_function('', ' global $context; return $context[\'files\'][\'lang\']; ')), 'columns' => array('name' => array('header' => array('value' => $txt['languages_download_filename']), 'data' => array('function' => create_function('$rowData', ' global $txt; return \'<strong>\' . $rowData[\'name\'] . \'</strong><br /><span class="smalltext">\' . $txt[\'languages_download_dest\'] . \': \' . $rowData[\'destination\'] . \'</span>\' . ($rowData[\'version_compare\'] == \'older\' ? \'<br />\' . $txt[\'languages_download_older\'] : \'\'); '))), 'writable' => array('header' => array('value' => $txt['languages_download_writable']), 'data' => array('function' => create_function('$rowData', ' global $txt; return \'<span class="\' . ($rowData[\'writable\'] ? \'success\' : \'error\') . \';">\' . ($rowData[\'writable\'] ? $txt[\'yes\'] : $txt[\'no\']) . \'</span>\'; '))), 'version' => array('header' => array('value' => $txt['languages_download_version']), 'data' => array('function' => create_function('$rowData', ' return \'<span class="\' . ($rowData[\'version_compare\'] == \'older\' ? \'error\' : ($rowData[\'version_compare\'] == \'same\' ? \'softalert\' : \'success\')) . \';">\' . $rowData[\'version\'] . \'</span>\'; '))), 'exists' => array('header' => array('value' => $txt['languages_download_exists']), 'data' => array('function' => create_function('$rowData', ' global $txt; return $rowData[\'exists\'] ? ($rowData[\'exists\'] == \'same\' ? $txt[\'languages_download_exists_same\'] : $txt[\'languages_download_exists_different\']) : $txt[\'no\']; '))), 'copy' => array('header' => array('value' => $txt['languages_download_copy'], 'class' => 'centertext'), 'data' => array('function' => create_function('$rowData', ' return \'<input type="checkbox" name="copy_file[]" value="\' . $rowData[\'generaldest\'] . \'" \' . ($rowData[\'default_copy\'] ? \'checked="checked"\' : \'\') . \' class="input_check" />\'; '), 'style' => 'width: 4%;', 'class' => 'centertext')))); // Kill the cache, as it is now invalid.. if (!empty($modSettings['cache_enable'])) { cache_put_data('known_languages', null, !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600); } require_once SUBSDIR . '/GenericList.class.php'; createList($listOptions); createToken('admin-dlang'); }