/**
  * 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');
    }