function ModifyLanguage()
{
    global $settings, $context, $smcFunc, $txt, $modSettings, $boarddir, $sourcedir, $language;
    loadLanguage('ManageSettings');
    // Select the languages tab.
    $context['menu_data_' . $context['admin_menu_id']]['current_subsection'] = 'edit';
    $context['page_title'] = $txt['edit_languages'];
    $context['sub_template'] = 'modify_language_entries';
    $context['lang_id'] = $_GET['lid'];
    list($theme_id, $file_id) = empty($_REQUEST['tfid']) || strpos($_REQUEST['tfid'], '+') === false ? array(1, '') : explode('+', $_REQUEST['tfid']);
    // Clean the ID - just in case.
    preg_match('~([A-Za-z0-9_-]+)~', $context['lang_id'], $matches);
    $context['lang_id'] = $matches[1];
    // Get all the theme data.
    $request = $smcFunc['db_query']('', '
		SELECT id_theme, variable, value
		FROM {db_prefix}themes
		WHERE id_theme != {int:default_theme}
			AND id_member = {int:no_member}
			AND variable IN ({string:name}, {string:theme_dir})', array('default_theme' => 1, 'no_member' => 0, 'name' => 'name', 'theme_dir' => 'theme_dir'));
    $themes = array(1 => array('name' => $txt['dvc_default'], 'theme_dir' => $settings['default_theme_dir']));
    while ($row = $smcFunc['db_fetch_assoc']($request)) {
        $themes[$row['id_theme']][$row['variable']] = $row['value'];
    }
    $smcFunc['db_free_result']($request);
    // This will be where we look
    $lang_dirs = array();
    // Check we have themes with a path and a name - just in case - and add the path.
    foreach ($themes as $id => $data) {
        if (count($data) != 2) {
            unset($themes[$id]);
        } elseif (is_dir($data['theme_dir'] . '/languages')) {
            $lang_dirs[$id] = $data['theme_dir'] . '/languages';
        }
        // How about image directories?
        if (is_dir($data['theme_dir'] . '/images/' . $context['lang_id'])) {
            $images_dirs[$id] = $data['theme_dir'] . '/images/' . $context['lang_id'];
        }
    }
    $current_file = $file_id ? $lang_dirs[$theme_id] . '/' . $file_id . '.' . $context['lang_id'] . '.php' : '';
    // Now for every theme get all the files and stick them in context!
    $context['possible_files'] = array();
    foreach ($lang_dirs as $theme => $theme_dir) {
        // Open it up.
        $dir = dir($theme_dir);
        while ($entry = $dir->read()) {
            // We're only after the files for this language.
            if (preg_match('~^([A-Za-z]+)\\.' . $context['lang_id'] . '\\.php$~', $entry, $matches) == 0) {
                continue;
            }
            //!!! Temp!
            if ($matches[1] == 'EmailTemplates') {
                continue;
            }
            if (!isset($context['possible_files'][$theme])) {
                $context['possible_files'][$theme] = array('id' => $theme, 'name' => $themes[$theme]['name'], 'files' => array());
            }
            $context['possible_files'][$theme]['files'][] = array('id' => $matches[1], 'name' => isset($txt['lang_file_desc_' . $matches[1]]) ? $txt['lang_file_desc_' . $matches[1]] : $matches[1], 'selected' => $theme_id == $theme && $file_id == $matches[1]);
        }
        $dir->close();
    }
    // We no longer wish to speak this language.
    if (!empty($_POST['delete_main']) && $context['lang_id'] != 'english') {
        checkSession();
        // !!! Todo: FTP Controls?
        require_once $sourcedir . '/Subs-Package.php';
        // First, Make a backup?
        if (!empty($modSettings['package_make_backups']) && (!isset($_SESSION['last_backup_for']) || $_SESSION['last_backup_for'] != $context['lang_id'] . '$$$')) {
            $_SESSION['last_backup_for'] = $context['lang_id'] . '$$$';
            package_create_backup('backup_lang_' . $context['lang_id']);
        }
        // Second, loop through the array to remove the files.
        foreach ($lang_dirs as $curPath) {
            foreach ($context['possible_files'][1]['files'] as $lang) {
                if (file_exists($curPath . '/' . $lang['id'] . '.' . $context['lang_id'] . '.php')) {
                    unlink($curPath . '/' . $lang['id'] . '.' . $context['lang_id'] . '.php');
                }
            }
            // Check for the email template.
            if (file_exists($curPath . '/EmailTemplates.' . $context['lang_id'] . '.php')) {
                unlink($curPath . '/EmailTemplates.' . $context['lang_id'] . '.php');
            }
        }
        // Third, the agreement file.
        if (file_exists($boarddir . '/agreement.' . $context['lang_id'] . '.txt')) {
            unlink($boarddir . '/agreement.' . $context['lang_id'] . '.txt');
        }
        // Fourth, a related images folder?
        foreach ($images_dirs as $curPath) {
            if (is_dir($curPath)) {
                deltree($curPath);
            }
        }
        // Members can no longer use this language.
        $smcFunc['db_query']('', '
			UPDATE {db_prefix}members
			SET lngfile = {string:empty_string}
			WHERE lngfile = {string:current_language}', array('empty_string' => '', 'current_language' => $context['lang_id']));
        // Fifth, update getLanguages() cache.
        if (!empty($modSettings['cache_enable'])) {
            cache_put_data('known_languages', null, !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600);
            cache_put_data('known_languages_all', null, !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600);
        }
        // Sixth, if we deleted the default language, set us back to english?
        if ($context['lang_id'] == $language) {
            require_once $sourcedir . '/Subs-Admin.php';
            $language = 'english';
            updateSettingsFile(array('language' => '\'' . $language . '\''));
        }
        // Seventh, get out of here.
        redirectexit('action=admin;area=languages;sa=edit;' . $context['session_var'] . '=' . $context['session_id']);
    }
    // Saving primary settings?
    $madeSave = false;
    if (!empty($_POST['save_main']) && !$current_file) {
        checkSession();
        // Read in the current file.
        $current_data = implode('', file($settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php'));
        // These are the replacements. old => new
        $replace_array = array('~\\$txt\\[\'lang_character_set\'\\]\\s=\\s(\'|")[^\\r\\n]+~' => '$txt[\'lang_character_set\'] = \'' . addslashes($_POST['character_set']) . '\';', '~\\$txt\\[\'lang_locale\'\\]\\s=\\s(\'|")[^\\r\\n]+~' => '$txt[\'lang_locale\'] = \'' . addslashes($_POST['locale']) . '\';', '~\\$txt\\[\'lang_dictionary\'\\]\\s=\\s(\'|")[^\\r\\n]+~' => '$txt[\'lang_dictionary\'] = \'' . addslashes($_POST['dictionary']) . '\';', '~\\$txt\\[\'lang_spelling\'\\]\\s=\\s(\'|")[^\\r\\n]+~' => '$txt[\'lang_spelling\'] = \'' . addslashes($_POST['spelling']) . '\';', '~\\$txt\\[\'lang_rtl\'\\]\\s=\\s[A-Za-z0-9]+;~' => '$txt[\'lang_rtl\'] = ' . (!empty($_POST['rtl']) ? 'true' : 'false') . ';');
        $current_data = preg_replace(array_keys($replace_array), array_values($replace_array), $current_data);
        $fp = fopen($settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php', 'w+');
        fwrite($fp, $current_data);
        fclose($fp);
        $madeSave = true;
    }
    // Quickly load index language entries.
    $old_txt = $txt;
    require $settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php';
    $context['lang_file_not_writable_message'] = is_writable($settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php') ? '' : sprintf($txt['lang_file_not_writable'], $settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php');
    // Setup the primary settings context.
    $context['primary_settings'] = array('name' => $smcFunc['ucwords'](strtr($context['lang_id'], array('_' => ' ', '-utf8' => ''))), 'character_set' => $txt['lang_character_set'], 'locale' => $txt['lang_locale'], 'dictionary' => $txt['lang_dictionary'], 'spelling' => $txt['lang_spelling'], 'rtl' => $txt['lang_rtl']);
    // Restore normal service.
    $txt = $old_txt;
    // Are we saving?
    $save_strings = array();
    if (isset($_POST['save_entries']) && !empty($_POST['entry'])) {
        checkSession();
        // Clean each entry!
        foreach ($_POST['entry'] as $k => $v) {
            // Only try to save if it's changed!
            if ($_POST['entry'][$k] != $_POST['comp'][$k]) {
                $save_strings[$k] = cleanLangString($v, false);
            }
        }
    }
    // If we are editing a file work away at that.
    if ($current_file) {
        $context['entries_not_writable_message'] = is_writable($current_file) ? '' : sprintf($txt['lang_entries_not_writable'], $current_file);
        $entries = array();
        // We can't just require it I'm afraid - otherwise we pass in all kinds of variables!
        $multiline_cache = '';
        foreach (file($current_file) as $line) {
            // Got a new entry?
            if ($line[0] == '$' && !empty($multiline_cache)) {
                preg_match('~\\$(helptxt|txt)\\[\'(.+)\'\\]\\s=\\s(.+);~', strtr($multiline_cache, array("\n" => '', "\t" => '')), $matches);
                if (!empty($matches[3])) {
                    $entries[$matches[2]] = array('type' => $matches[1], 'full' => $matches[0], 'entry' => $matches[3]);
                    $multiline_cache = '';
                }
            }
            $multiline_cache .= $line . "\n";
        }
        // Last entry to add?
        if ($multiline_cache) {
            preg_match('~\\$(helptxt|txt)\\[\'(.+)\'\\]\\s=\\s(.+);~', strtr($multiline_cache, array("\n" => '', "\t" => '')), $matches);
            if (!empty($matches[3])) {
                $entries[$matches[2]] = array('type' => $matches[1], 'full' => $matches[0], 'entry' => $matches[3]);
            }
        }
        // These are the entries we can definitely save.
        $final_saves = array();
        $context['file_entries'] = array();
        foreach ($entries as $entryKey => $entryValue) {
            // Ignore some things we set separately.
            $ignore_files = array('lang_character_set', 'lang_locale', 'lang_dictionary', 'lang_spelling', 'lang_rtl');
            if (in_array($entryKey, $ignore_files)) {
                continue;
            }
            // These are arrays that need breaking out.
            $arrays = array('days', 'days_short', 'months', 'months_titles', 'months_short');
            if (in_array($entryKey, $arrays)) {
                // Get off the first bits.
                $entryValue['entry'] = substr($entryValue['entry'], strpos($entryValue['entry'], '(') + 1, strrpos($entryValue['entry'], ')') - strpos($entryValue['entry'], '('));
                $entryValue['entry'] = explode(',', strtr($entryValue['entry'], array(' ' => '')));
                // Now create an entry for each item.
                $cur_index = 0;
                $save_cache = array('enabled' => false, 'entries' => array());
                foreach ($entryValue['entry'] as $id => $subValue) {
                    // Is this a new index?
                    if (preg_match('~^(\\d+)~', $subValue, $matches)) {
                        $cur_index = $matches[1];
                        $subValue = substr($subValue, strpos($subValue, '\''));
                    }
                    // Clean up some bits.
                    $subValue = strtr($subValue, array('"' => '', '\'' => '', ')' => ''));
                    // Can we save?
                    if (isset($save_strings[$entryKey . '-+- ' . $cur_index])) {
                        $save_cache['entries'][$cur_index] = strtr($save_strings[$entryKey . '-+- ' . $cur_index], array('\'' => ''));
                        $save_cache['enabled'] = true;
                    } else {
                        $save_cache['entries'][$cur_index] = $subValue;
                    }
                    $context['file_entries'][] = array('key' => $entryKey . '-+- ' . $cur_index, 'value' => $subValue, 'rows' => 1);
                    $cur_index++;
                }
                // Do we need to save?
                if ($save_cache['enabled']) {
                    // Format the string, checking the indexes first.
                    $items = array();
                    $cur_index = 0;
                    foreach ($save_cache['entries'] as $k2 => $v2) {
                        // Manually show the custom index.
                        if ($k2 != $cur_index) {
                            $items[] = $k2 . ' => \'' . $v2 . '\'';
                            $cur_index = $k2;
                        } else {
                            $items[] = '\'' . $v2 . '\'';
                        }
                        $cur_index++;
                    }
                    // Now create the string!
                    $final_saves[$entryKey] = array('find' => $entryValue['full'], 'replace' => '$' . $entryValue['type'] . '[\'' . $entryKey . '\'] = array(' . implode(', ', $items) . ');');
                }
            } else {
                // Saving?
                if (isset($save_strings[$entryKey]) && $save_strings[$entryKey] != $entryValue['entry']) {
                    // !!! Fix this properly.
                    if ($save_strings[$entryKey] == '') {
                        $save_strings[$entryKey] = '\'\'';
                    }
                    // Set the new value.
                    $entryValue['entry'] = $save_strings[$entryKey];
                    // And we know what to save now!
                    $final_saves[$entryKey] = array('find' => $entryValue['full'], 'replace' => '$' . $entryValue['type'] . '[\'' . $entryKey . '\'] = ' . $save_strings[$entryKey] . ';');
                }
                $editing_string = cleanLangString($entryValue['entry'], true);
                $context['file_entries'][] = array('key' => $entryKey, 'value' => $editing_string, 'rows' => (int) (strlen($editing_string) / 38) + substr_count($editing_string, "\n") + 1);
            }
        }
        // Any saves to make?
        if (!empty($final_saves)) {
            checkSession();
            $file_contents = implode('', file($current_file));
            foreach ($final_saves as $save) {
                $file_contents = strtr($file_contents, array($save['find'] => $save['replace']));
            }
            // Save the actual changes.
            $fp = fopen($current_file, 'w+');
            fwrite($fp, $file_contents);
            fclose($fp);
            $madeSave = true;
        }
        // Another restore.
        $txt = $old_txt;
    }
    // If we saved, redirect.
    if ($madeSave) {
        redirectexit('action=admin;area=languages;sa=editlang;lid=' . $context['lang_id']);
    }
}
Example #2
0
 /**
  * Apply another type of (avatar, language, etc.) package.
  */
 public function action_install2()
 {
     global $txt, $context, $boardurl, $scripturl, $modSettings;
     // Make sure we don't install this mod twice.
     checkSubmitOnce('check');
     checkSession();
     // If there's no file, what are we installing?
     if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '') {
         redirectexit('action=admin;area=packages');
     }
     $context['filename'] = $_REQUEST['package'];
     // If this is an uninstall, we'll have an id.
     $context['install_id'] = isset($_REQUEST['pid']) ? (int) $_REQUEST['pid'] : 0;
     require_once SUBSDIR . '/Package.subs.php';
     require_once SUBSDIR . '/Themes.subs.php';
     // @todo Perhaps do it in steps, if necessary?
     $context['uninstalling'] = $_REQUEST['sa'] == 'uninstall2';
     // Set up the linktree for other.
     $context['linktree'][count($context['linktree']) - 1] = array('url' => $scripturl . '?action=admin;area=packages;sa=browse', 'name' => $context['uninstalling'] ? $txt['uninstall'] : $txt['extracting']);
     $context['page_title'] .= ' - ' . ($context['uninstalling'] ? $txt['uninstall'] : $txt['extracting']);
     $context['sub_template'] = 'extract_package';
     if (!file_exists(BOARDDIR . '/packages/' . $context['filename'])) {
         fatal_lang_error('package_no_file', false);
     }
     // Load up the package FTP information?
     create_chmod_control(array(), array('destination_url' => $scripturl . '?action=admin;area=packages;sa=' . $_REQUEST['sa'] . ';package=' . $_REQUEST['package']));
     // Make sure temp directory exists and is empty!
     if (file_exists(BOARDDIR . '/packages/temp')) {
         deltree(BOARDDIR . '/packages/temp', false);
     } else {
         mktree(BOARDDIR . '/packages/temp', 0777);
     }
     // Let the unpacker do the work.
     if (is_file(BOARDDIR . '/packages/' . $context['filename'])) {
         $context['extracted_files'] = read_tgz_file(BOARDDIR . '/packages/' . $context['filename'], BOARDDIR . '/packages/temp');
         if (!file_exists(BOARDDIR . '/packages/temp/package-info.xml')) {
             foreach ($context['extracted_files'] as $file) {
                 if (basename($file['filename']) == 'package-info.xml') {
                     $context['base_path'] = dirname($file['filename']) . '/';
                     break;
                 }
             }
         }
         if (!isset($context['base_path'])) {
             $context['base_path'] = '';
         }
     } elseif (is_dir(BOARDDIR . '/packages/' . $context['filename'])) {
         copytree(BOARDDIR . '/packages/' . $context['filename'], BOARDDIR . '/packages/temp');
         $context['extracted_files'] = listtree(BOARDDIR . '/packages/temp');
         $context['base_path'] = '';
     } else {
         fatal_lang_error('no_access', false);
     }
     // Are we installing this into any custom themes?
     $custom_themes = array(1);
     $known_themes = explode(',', $modSettings['knownThemes']);
     if (!empty($_POST['custom_theme'])) {
         foreach ($_POST['custom_theme'] as $tid) {
             if (in_array($tid, $known_themes)) {
                 $custom_themes[] = (int) $tid;
             }
         }
     }
     // Now load up the paths of the themes that we need to know about.
     $theme_paths = getThemesPathbyID($custom_themes);
     $themes_installed = array(1);
     // Are there any theme copying that we want to take place?
     $context['theme_copies'] = array('require-file' => array(), 'require-dir' => array());
     if (!empty($_POST['theme_changes'])) {
         foreach ($_POST['theme_changes'] as $change) {
             if (empty($change)) {
                 continue;
             }
             $theme_data = unserialize(base64_decode($change));
             if (empty($theme_data['type'])) {
                 continue;
             }
             $themes_installed[] = $theme_data['id'];
             $context['theme_copies'][$theme_data['type']][$theme_data['orig']][] = $theme_data['future'];
         }
     }
     // Get the package info...
     $packageInfo = getPackageInfo($context['filename']);
     if (!is_array($packageInfo)) {
         fatal_lang_error($packageInfo);
     }
     $packageInfo['filename'] = $context['filename'];
     // Set the type of extraction...
     $context['extract_type'] = isset($packageInfo['type']) ? $packageInfo['type'] : 'modification';
     // Create a backup file to roll back to! (but if they do this more than once, don't run it a zillion times.)
     if (!empty($modSettings['package_make_full_backups']) && (!isset($_SESSION['last_backup_for']) || $_SESSION['last_backup_for'] != $context['filename'] . ($context['uninstalling'] ? '$$' : '$'))) {
         $_SESSION['last_backup_for'] = $context['filename'] . ($context['uninstalling'] ? '$$' : '$');
         // @todo Internationalize this?
         package_create_backup(($context['uninstalling'] ? 'backup_' : 'before_') . strtok($context['filename'], '.'));
     }
     // The mod isn't installed.... unless proven otherwise.
     $context['is_installed'] = false;
     // Is it actually installed?
     $package_installed = isPackageInstalled($packageInfo['id']);
     // Wait, it's not installed yet!
     // @todo Replace with a better error message!
     if (!isset($package_installed['old_version']) && $context['uninstalling']) {
         deltree(BOARDDIR . '/packages/temp');
         fatal_error('Hacker?', false);
     } elseif ($context['uninstalling']) {
         $install_log = parsePackageInfo($packageInfo['xml'], false, 'uninstall');
         // Gadzooks!  There's no uninstaller at all!?
         if (empty($install_log)) {
             fatal_lang_error('package_uninstall_cannot', false);
         }
         // They can only uninstall from what it was originally installed into.
         foreach ($theme_paths as $id => $data) {
             if ($id != 1 && !in_array($id, $package_installed['old_themes'])) {
                 unset($theme_paths[$id]);
             }
         }
     } elseif (isset($package_installed['old_version']) && $package_installed['old_version'] != $packageInfo['version']) {
         // Look for an upgrade...
         $install_log = parsePackageInfo($packageInfo['xml'], false, 'upgrade', $package_installed['old_version']);
         // There was no upgrade....
         if (empty($install_log)) {
             $context['is_installed'] = true;
         } else {
             // Upgrade previous themes only!
             foreach ($theme_paths as $id => $data) {
                 if ($id != 1 && !in_array($id, $package_installed['old_themes'])) {
                     unset($theme_paths[$id]);
                 }
             }
         }
     } elseif (isset($package_installed['old_version']) && $package_installed['old_version'] == $packageInfo['version']) {
         $context['is_installed'] = true;
     }
     if (!isset($package_installed['old_version']) || $context['is_installed']) {
         $install_log = parsePackageInfo($packageInfo['xml'], false, 'install');
     }
     $context['install_finished'] = false;
     // We're gonna be needing the table db functions! ...Sometimes.
     $table_installer = db_table();
     // @todo Make a log of any errors that occurred and output them?
     if (!empty($install_log)) {
         $failed_steps = array();
         $failed_count = 0;
         foreach ($install_log as $action) {
             $failed_count++;
             if ($action['type'] == 'modification' && !empty($action['filename'])) {
                 if ($action['boardmod']) {
                     $mod_actions = parseBoardMod(file_get_contents(BOARDDIR . '/packages/temp/' . $context['base_path'] . $action['filename']), false, $action['reverse'], $theme_paths);
                 } else {
                     $mod_actions = parseModification(file_get_contents(BOARDDIR . '/packages/temp/' . $context['base_path'] . $action['filename']), false, $action['reverse'], $theme_paths);
                 }
                 // Any errors worth noting?
                 foreach ($mod_actions as $key => $action) {
                     if ($action['type'] == 'failure') {
                         $failed_steps[] = array('file' => $action['filename'], 'large_step' => $failed_count, 'sub_step' => $key, 'theme' => 1);
                     }
                     // Gather the themes we installed into.
                     if (!empty($action['is_custom'])) {
                         $themes_installed[] = $action['is_custom'];
                     }
                 }
             } elseif ($action['type'] == 'code' && !empty($action['filename'])) {
                 // This is just here as reference for what is available.
                 global $txt, $modSettings, $context;
                 // Now include the file and be done with it ;).
                 if (file_exists(BOARDDIR . '/packages/temp/' . $context['base_path'] . $action['filename'])) {
                     require BOARDDIR . '/packages/temp/' . $context['base_path'] . $action['filename'];
                 }
             } elseif ($action['type'] == 'credits') {
                 // Time to build the billboard
                 $credits_tag = array('url' => $action['url'], 'license' => $action['license'], 'copyright' => $action['copyright'], 'title' => $action['title']);
             } elseif ($action['type'] == 'hook' && isset($action['hook'], $action['function'])) {
                 if ($action['reverse']) {
                     remove_integration_function($action['hook'], $action['function'], $action['include_file']);
                 } else {
                     add_integration_function($action['hook'], $action['function'], $action['include_file']);
                 }
             } elseif ($action['type'] == 'database' && !empty($action['filename']) && (!$context['uninstalling'] || !empty($_POST['do_db_changes']))) {
                 // These can also be there for database changes.
                 global $txt, $modSettings, $context;
                 // Let the file work its magic ;)
                 if (file_exists(BOARDDIR . '/packages/temp/' . $context['base_path'] . $action['filename'])) {
                     require BOARDDIR . '/packages/temp/' . $context['base_path'] . $action['filename'];
                 }
             } elseif ($action['type'] == 'redirect' && !empty($action['redirect_url'])) {
                 $context['redirect_url'] = $action['redirect_url'];
                 $context['redirect_text'] = !empty($action['filename']) && file_exists(BOARDDIR . '/packages/temp/' . $context['base_path'] . $action['filename']) ? file_get_contents(BOARDDIR . '/packages/temp/' . $context['base_path'] . $action['filename']) : ($context['uninstalling'] ? $txt['package_uninstall_done'] : $txt['package_installed_done']);
                 $context['redirect_timeout'] = $action['redirect_timeout'];
                 // Parse out a couple of common urls.
                 $urls = array('$boardurl' => $boardurl, '$scripturl' => $scripturl, '$session_var' => $context['session_var'], '$session_id' => $context['session_id']);
                 $context['redirect_url'] = strtr($context['redirect_url'], $urls);
             }
         }
         package_flush_cache();
         // First, ensure this change doesn't get removed by putting a stake in the ground (So to speak).
         package_put_contents(BOARDDIR . '/packages/installed.list', time());
         // See if this is already installed
         $is_upgrade = false;
         $old_db_changes = array();
         $package_check = isPackageInstalled($packageInfo['id']);
         // Change the installed state as required.
         if (!empty($package_check['install_state'])) {
             if ($context['uninstalling']) {
                 setPackageState($package_check['package_id']);
             } else {
                 // not uninstalling so must be an upgrade
                 $is_upgrade = true;
                 $old_db_changes = empty($package_check['db_changes']) ? array() : $package_check['db_changes'];
             }
         }
         // Assuming we're not uninstalling, add the entry.
         if (!$context['uninstalling']) {
             // Any db changes from older version?
             $table_log = $table_installer->package_log();
             if (!empty($old_db_changes)) {
                 $db_package_log = empty($table_log) ? $old_db_changes : array_merge($old_db_changes, $table_log);
             } else {
                 $db_package_log = $table_log;
             }
             // If there are some database changes we might want to remove then filter them out.
             if (!empty($db_package_log)) {
                 // We're really just checking for entries which are create table AND add columns (etc).
                 $tables = array();
                 usort($db_package_log, array($this, '_sort_table_first'));
                 foreach ($db_package_log as $k => $log) {
                     if ($log[0] == 'remove_table') {
                         $tables[] = $log[1];
                     } elseif (in_array($log[1], $tables)) {
                         unset($db_package_log[$k]);
                     }
                 }
                 $package_installed['db_changes'] = serialize($db_package_log);
             } else {
                 $package_installed['db_changes'] = '';
             }
             // What themes did we actually install?
             $themes_installed = array_unique($themes_installed);
             $themes_installed = implode(',', $themes_installed);
             // What failed steps?
             $failed_step_insert = serialize($failed_steps);
             // Credits tag?
             $credits_tag = empty($credits_tag) ? '' : serialize($credits_tag);
             // Add to the log packages
             addPackageLog($packageInfo, $failed_step_insert, $themes_installed, $package_installed['db_changes'], $is_upgrade, $credits_tag);
         }
         $context['install_finished'] = true;
     }
     // If there's database changes - and they want them removed - let's do it last!
     if (!empty($package_installed['db_changes']) && !empty($_POST['do_db_changes'])) {
         foreach ($package_installed['db_changes'] as $change) {
             if ($change[0] == 'remove_table' && isset($change[1])) {
                 $table_installer->db_drop_table($change[1]);
             } elseif ($change[0] == 'remove_column' && isset($change[2])) {
                 $table_installer->db_remove_column($change[1], $change[2]);
             } elseif ($change[0] == 'remove_index' && isset($change[2])) {
                 $table_installer->db_remove_index($change[1], $change[2]);
             }
         }
     }
     // Clean house... get rid of the evidence ;).
     if (file_exists(BOARDDIR . '/packages/temp')) {
         deltree(BOARDDIR . '/packages/temp');
     }
     // Log what we just did.
     logAction($context['uninstalling'] ? 'uninstall_package' : (!empty($is_upgrade) ? 'upgrade_package' : 'install_package'), array('package' => Util::htmlspecialchars($packageInfo['name']), 'version' => Util::htmlspecialchars($packageInfo['version'])), 'admin');
     // Just in case, let's clear the whole cache to avoid anything going up the swanny.
     clean_cache();
     // Restore file permissions?
     create_chmod_control(array(), array(), true);
 }
Example #3
0
function PackageInstall()
{
    global $boarddir, $txt, $context, $boardurl, $scripturl, $sourcedir, $modSettings;
    checkSession('post');
    // If there's no file, what are we installing?
    if (empty($_REQUEST['package']) || preg_match('~[^\\w0-9.\\-_]~', $_REQUEST['package']) === 1 || strpos($_REQUEST['package'], '..') !== false) {
        redirectexit('action=packages');
    }
    $context['filename'] = $_REQUEST['package'];
    require_once $sourcedir . '/Subs-Package.php';
    // !!! TODO: Perhaps do it in steps, if necessary?
    $context['uninstalling'] = $_REQUEST['sa'] == 'uninstall2';
    // Set up the linktree for other.
    $context['linktree'][] = array('url' => $scripturl . '?action=packages;sa=browse', 'name' => $context['uninstalling'] ? $txt['smf198b'] : $txt['package37']);
    $context['page_title'] .= ' - ' . ($context['uninstalling'] ? $txt['smf198b'] : $txt['package37']);
    $context['sub_template'] = 'extract_package';
    if (!file_exists($boarddir . '/Packages/' . $context['filename'])) {
        fatal_lang_error('package_no_file', false);
    }
    // Load up the package FTP information?
    if (isset($_SESSION['pack_ftp'])) {
        packageRequireFTP($scripturl . '?action=packages;sa=' . $_REQUEST['sa'] . ';package=' . $_REQUEST['package']);
    }
    // Make sure temp directory exists and is empty!
    if (file_exists($boarddir . '/Packages/temp')) {
        deltree($boarddir . '/Packages/temp', false);
    } else {
        mktree($boarddir . '/Packages/temp', 0777);
    }
    // Let the unpacker do the work.
    if (is_file($boarddir . '/Packages/' . $context['filename'])) {
        $context['extracted_files'] = read_tgz_file($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp');
        if (!file_exists($boarddir . '/Packages/temp/package-info.xml')) {
            foreach ($context['extracted_files'] as $file) {
                if (basename($file['filename']) == 'package-info.xml') {
                    $context['base_path'] = dirname($file['filename']) . '/';
                    break;
                }
            }
        }
        if (!isset($context['base_path'])) {
            $context['base_path'] = '';
        }
    } elseif (is_dir($boarddir . '/Packages/' . $context['filename'])) {
        copytree($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp');
        $context['extracted_files'] = listtree($boarddir . '/Packages/temp');
        $context['base_path'] = '';
    } else {
        fatal_lang_error(1, false);
    }
    // Get the package info...
    $packageInfo = getPackageInfo($context['filename']);
    $packageInfo['filename'] = $context['filename'];
    // Set the type of extraction...
    $context['extract_type'] = isset($packageInfo['type']) ? $packageInfo['type'] : 'modification';
    // Create a backup file to roll back to! (but if they do this more than once, don't run it a zillion times.)
    if (!empty($modSettings['package_make_backups']) && (!isset($_SESSION['last_backup_for']) || $_SESSION['last_backup_for'] != $context['filename'] . ($context['uninstalling'] ? '$$' : '$'))) {
        $_SESSION['last_backup_for'] = $context['filename'] . ($context['uninstalling'] ? '$$' : '$');
        // !!! Internationalize this?
        package_create_backup(($context['uninstalling'] ? 'backup_' : 'before_') . strtok($context['filename'], '.'));
    }
    $instmods = loadInstalledPackages();
    // The mod isn't installed.... unless proven otherwise.
    $context['is_installed'] = false;
    foreach ($instmods as $installed_mod) {
        if ($installed_mod['id'] == $packageInfo['id']) {
            $old_version = $installed_mod['version'];
        }
    }
    // Wait, it's not installed yet!
    // !!! TODO: Replace with a better error message!
    if (!isset($old_version) && $context['uninstalling']) {
        deltree($boarddir . '/Packages/temp');
        fatal_error('Hacker?', false);
    } elseif ($context['uninstalling']) {
        $install_log = parsePackageInfo($packageInfo['xml'], false, 'uninstall');
        // Gadzooks!  There's no uninstaller at all!?
        if (empty($install_log)) {
            fatal_lang_error('package_uninstall_cannot', false);
        }
    } elseif (isset($old_version) && $old_version != $packageInfo['version']) {
        // Look for an upgrade...
        $install_log = parsePackageInfo($packageInfo['xml'], false, 'upgrade', $old_version);
        // There was no upgrade....
        if (empty($install_log)) {
            $context['is_installed'] = true;
        }
    } elseif (isset($old_version) && $old_version == $packageInfo['version']) {
        $context['is_installed'] = true;
    }
    if (!isset($old_version) || $context['is_installed']) {
        $install_log = parsePackageInfo($packageInfo['xml'], false, 'install');
    }
    $context['install_finished'] = false;
    // !!! TODO: Make a log of any errors that occurred and output them?
    if (!empty($install_log)) {
        foreach ($install_log as $action) {
            if ($action['type'] == 'modification' && !empty($action['filename'])) {
                if ($action['boardmod']) {
                    parseBoardMod(file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), false, $action['reverse']);
                } else {
                    parseModification(file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), false, $action['reverse']);
                }
            } elseif ($action['type'] == 'code' && !empty($action['filename'])) {
                // This is just here as reference for what is available.
                global $txt, $boarddir, $sourcedir, $modSettings, $context, $settings, $db_prefix, $forum_version;
                // Now include the file and be done with it ;).
                require $boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename'];
            } elseif ($action['type'] == 'redirect' && !empty($action['redirect_url'])) {
                $context['redirect_url'] = $action['redirect_url'];
                $context['redirect_text'] = file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']);
                $context['redirect_timeout'] = $action['redirect_timeout'];
                // Parse out a couple of common urls.
                $urls = array('$boardurl' => $boardurl, '$scripturl' => $scripturl, '$session_id' => $context['session_id']);
                $context['redirect_url'] = strtr($context['redirect_url'], $urls);
            }
        }
        package_flush_cache();
        // Check if the mod has been installed.
        $seen = false;
        // Look through the list of installed mods...
        foreach ($instmods as $i => $installed_mod) {
            if ($installed_mod['id'] == $packageInfo['id']) {
                if ($context['uninstalling']) {
                    $instmods[$i] = array();
                } else {
                    $instmods[$i]['version'] = $packageInfo['version'];
                    $seen = true;
                }
                break;
            }
        }
        // Hasn't.... make it show as installed.
        if (!$seen && !$context['uninstalling']) {
            $instmods[] = $packageInfo;
        }
        saveInstalledPackages($instmods);
        $context['install_finished'] = true;
    }
    // Clean house... get rid of the evidence ;).
    if (file_exists($boarddir . '/Packages/temp')) {
        deltree($boarddir . '/Packages/temp');
    }
}
Example #4
0
function PackageInstall()
{
    global $boarddir, $txt, $context, $boardurl, $scripturl, $sourcedir, $modSettings;
    global $user_info, $smcFunc;
    // Make sure we don't install this mod twice.
    checkSubmitOnce('check');
    checkSession();
    // If there's no file, what are we installing?
    if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '') {
        redirectexit('action=admin;area=packages');
    }
    $context['filename'] = $_REQUEST['package'];
    // If this is an uninstall, we'll have an id.
    $context['install_id'] = isset($_REQUEST['pid']) ? (int) $_REQUEST['pid'] : 0;
    require_once $sourcedir . '/lib/Subs-Package.php';
    // !!! TODO: Perhaps do it in steps, if necessary?
    $context['uninstalling'] = $_REQUEST['sa'] == 'uninstall2';
    // Set up the linktree for other.
    $context['linktree'][count($context['linktree']) - 1] = array('url' => $scripturl . '?action=admin;area=packages;sa=browse', 'name' => $context['uninstalling'] ? $txt['uninstall'] : $txt['extracting']);
    $context['page_title'] .= ' - ' . ($context['uninstalling'] ? $txt['uninstall'] : $txt['extracting']);
    $context['sub_template'] = 'extract_package';
    if (!file_exists($boarddir . '/Packages/' . $context['filename'])) {
        fatal_lang_error('package_no_file', false);
    }
    // Load up the package FTP information?
    create_chmod_control(array(), array('destination_url' => $scripturl . '?action=admin;area=packages;sa=' . $_REQUEST['sa'] . ';package=' . $_REQUEST['package']));
    // Make sure temp directory exists and is empty!
    if (file_exists($boarddir . '/Packages/temp')) {
        deltree($boarddir . '/Packages/temp', false);
    } else {
        mktree($boarddir . '/Packages/temp', 0777);
    }
    // Let the unpacker do the work.
    if (is_file($boarddir . '/Packages/' . $context['filename'])) {
        $context['extracted_files'] = read_tgz_file($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp');
        if (!file_exists($boarddir . '/Packages/temp/package-info.xml')) {
            foreach ($context['extracted_files'] as $file) {
                if (basename($file['filename']) == 'package-info.xml') {
                    $context['base_path'] = dirname($file['filename']) . '/';
                    break;
                }
            }
        }
        if (!isset($context['base_path'])) {
            $context['base_path'] = '';
        }
    } elseif (is_dir($boarddir . '/Packages/' . $context['filename'])) {
        copytree($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp');
        $context['extracted_files'] = listtree($boarddir . '/Packages/temp');
        $context['base_path'] = '';
    } else {
        fatal_lang_error('no_access', false);
    }
    // Are we installing this into any custom themes?
    $custom_themes = array(1);
    $known_themes = explode(',', $modSettings['knownThemes']);
    if (!empty($_POST['custom_theme'])) {
        foreach ($_POST['custom_theme'] as $tid) {
            if (in_array($tid, $known_themes)) {
                $custom_themes[] = (int) $tid;
            }
        }
    }
    // Now load up the paths of the themes that we need to know about.
    $request = smf_db_query('
		SELECT id_theme, variable, value
		FROM {db_prefix}themes
		WHERE id_theme IN ({array_int:custom_themes})
			AND variable IN ({string:name}, {string:theme_dir})', array('custom_themes' => $custom_themes, 'name' => 'name', 'theme_dir' => 'theme_dir'));
    $theme_paths = array();
    $themes_installed = array(1);
    while ($row = mysql_fetch_assoc($request)) {
        $theme_paths[$row['id_theme']][$row['variable']] = $row['value'];
    }
    mysql_free_result($request);
    // Are there any theme copying that we want to take place?
    $context['theme_copies'] = array('require-file' => array(), 'require-dir' => array());
    if (!empty($_POST['theme_changes'])) {
        foreach ($_POST['theme_changes'] as $change) {
            if (empty($change)) {
                continue;
            }
            $theme_data = unserialize(base64_decode($change));
            if (empty($theme_data['type'])) {
                continue;
            }
            $themes_installed[] = $theme_data['id'];
            $context['theme_copies'][$theme_data['type']][$theme_data['orig']][] = $theme_data['future'];
        }
    }
    // Get the package info...
    $packageInfo = getPackageInfo($context['filename']);
    if (!is_array($packageInfo)) {
        fatal_lang_error($packageInfo);
    }
    $packageInfo['filename'] = $context['filename'];
    // Set the type of extraction...
    $context['extract_type'] = isset($packageInfo['type']) ? $packageInfo['type'] : 'modification';
    // Create a backup file to roll back to! (but if they do this more than once, don't run it a zillion times.)
    if (!empty($modSettings['package_make_backups']) && (!isset($_SESSION['last_backup_for']) || $_SESSION['last_backup_for'] != $context['filename'] . ($context['uninstalling'] ? '$$' : '$'))) {
        $_SESSION['last_backup_for'] = $context['filename'] . ($context['uninstalling'] ? '$$' : '$');
        // !!! Internationalize this?
        package_create_backup(($context['uninstalling'] ? 'backup_' : 'before_') . strtok($context['filename'], '.'));
    }
    // The mod isn't installed.... unless proven otherwise.
    $context['is_installed'] = false;
    // Is it actually installed?
    $request = smf_db_query('
		SELECT version, themes_installed, db_changes
		FROM {db_prefix}log_packages
		WHERE package_id = {string:current_package}
			AND install_state != {int:not_installed}
		ORDER BY time_installed DESC
		LIMIT 1', array('not_installed' => 0, 'current_package' => $packageInfo['id']));
    while ($row = mysql_fetch_assoc($request)) {
        $old_themes = explode(',', $row['themes_installed']);
        $old_version = $row['version'];
        $db_changes = empty($row['db_changes']) ? array() : unserialize($row['db_changes']);
    }
    mysql_free_result($request);
    // Wait, it's not installed yet!
    // !!! TODO: Replace with a better error message!
    if (!isset($old_version) && $context['uninstalling']) {
        deltree($boarddir . '/Packages/temp');
        fatal_error('Hacker?', false);
    } elseif ($context['uninstalling']) {
        $install_log = parsePackageInfo($packageInfo['xml'], false, 'uninstall');
        // Gadzooks!  There's no uninstaller at all!?
        if (empty($install_log)) {
            fatal_lang_error('package_uninstall_cannot', false);
        }
        // They can only uninstall from what it was originally installed into.
        foreach ($theme_paths as $id => $data) {
            if ($id != 1 && !in_array($id, $old_themes)) {
                unset($theme_paths[$id]);
            }
        }
    } elseif (isset($old_version) && $old_version != $packageInfo['version']) {
        // Look for an upgrade...
        $install_log = parsePackageInfo($packageInfo['xml'], false, 'upgrade', $old_version);
        // There was no upgrade....
        if (empty($install_log)) {
            $context['is_installed'] = true;
        } else {
            // Upgrade previous themes only!
            foreach ($theme_paths as $id => $data) {
                if ($id != 1 && !in_array($id, $old_themes)) {
                    unset($theme_paths[$id]);
                }
            }
        }
    } elseif (isset($old_version) && $old_version == $packageInfo['version']) {
        $context['is_installed'] = true;
    }
    if (!isset($old_version) || $context['is_installed']) {
        $install_log = parsePackageInfo($packageInfo['xml'], false, 'install');
    }
    $context['install_finished'] = false;
    // !!! TODO: Make a log of any errors that occurred and output them?
    if (!empty($install_log)) {
        $failed_steps = array();
        $failed_count = 0;
        foreach ($install_log as $action) {
            $failed_count++;
            if ($action['type'] == 'modification' && !empty($action['filename'])) {
                if ($action['boardmod']) {
                    $mod_actions = parseBoardMod(file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), false, $action['reverse'], $theme_paths);
                } else {
                    $mod_actions = parseModification(file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), false, $action['reverse'], $theme_paths);
                }
                // Any errors worth noting?
                foreach ($mod_actions as $key => $action) {
                    if ($action['type'] == 'failure') {
                        $failed_steps[] = array('file' => $action['filename'], 'large_step' => $failed_count, 'sub_step' => $key, 'theme' => 1);
                    }
                    // Gather the themes we installed into.
                    if (!empty($action['is_custom'])) {
                        $themes_installed[] = $action['is_custom'];
                    }
                }
            } elseif ($action['type'] == 'code' && !empty($action['filename'])) {
                // This is just here as reference for what is available.
                global $txt, $boarddir, $sourcedir, $modSettings, $context, $settings, $forum_version, $smcFunc;
                // Now include the file and be done with it ;).
                require $boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename'];
            } elseif ($action['type'] == 'database' && !empty($action['filename']) && (!$context['uninstalling'] || !empty($_POST['do_db_changes']))) {
                // These can also be there for database changes.
                global $txt, $boarddir, $sourcedir, $modSettings, $context, $settings, $forum_version, $smcFunc;
                global $db_package_log;
                // We'll likely want the package specific database functionality!
                db_extend('packages');
                // Let the file work its magic ;)
                require $boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename'];
            } elseif ($action['type'] == 'redirect' && !empty($action['redirect_url'])) {
                $context['redirect_url'] = $action['redirect_url'];
                $context['redirect_text'] = !empty($action['filename']) && file_exists($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']) ? file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']) : ($context['uninstalling'] ? $txt['package_uninstall_done'] : $txt['package_installed_done']);
                $context['redirect_timeout'] = $action['redirect_timeout'];
                // Parse out a couple of common urls.
                $urls = array('$boardurl' => $boardurl, '$scripturl' => $scripturl, '$session_var' => $context['session_var'], '$session_id' => $context['session_id']);
                $context['redirect_url'] = strtr($context['redirect_url'], $urls);
            }
        }
        package_flush_cache();
        // First, ensure this change doesn't get removed by putting a stake in the ground (So to speak).
        package_put_contents($boarddir . '/Packages/installed.list', time());
        // See if this is already installed, and change it's state as required.
        $request = smf_db_query('
			SELECT package_id, install_state, db_changes
			FROM {db_prefix}log_packages
			WHERE install_state != {int:not_installed}
				AND package_id = {string:current_package}
				' . ($context['install_id'] ? ' AND id_install = {int:install_id} ' : '') . '
			ORDER BY time_installed DESC
			LIMIT 1', array('not_installed' => 0, 'install_id' => $context['install_id'], 'current_package' => $packageInfo['id']));
        $is_upgrade = false;
        while ($row = mysql_fetch_assoc($request)) {
            // Uninstalling?
            if ($context['uninstalling']) {
                smf_db_query('
					UPDATE {db_prefix}log_packages
					SET install_state = {int:not_installed}, member_removed = {string:member_name}, id_member_removed = {int:current_member},
						time_removed = {int:current_time}
					WHERE package_id = {string:package_id}', array('current_member' => $user_info['id'], 'not_installed' => 0, 'current_time' => time(), 'package_id' => $row['package_id'], 'member_name' => $user_info['name']));
            } else {
                $is_upgrade = true;
                $old_db_changes = empty($row['db_changes']) ? array() : unserialize($row['db_changes']);
            }
        }
        // Assuming we're not uninstalling, add the entry.
        if (!$context['uninstalling']) {
            // Any db changes from older version?
            if (!empty($old_db_changes)) {
                $db_package_log = empty($db_package_log) ? $old_db_changes : array_merge($old_db_changes, $db_package_log);
            }
            // If there are some database changes we might want to remove then filter them out.
            if (!empty($db_package_log)) {
                // We're really just checking for entries which are create table AND add columns (etc).
                $tables = array();
                function sort_table_first($a, $b)
                {
                    if ($a[0] == $b[0]) {
                        return 0;
                    }
                    return $a[0] == 'remove_table' ? -1 : 1;
                }
                usort($db_package_log, 'sort_table_first');
                foreach ($db_package_log as $k => $log) {
                    if ($log[0] == 'remove_table') {
                        $tables[] = $log[1];
                    } elseif (in_array($log[1], $tables)) {
                        unset($db_package_log[$k]);
                    }
                }
                $db_changes = serialize($db_package_log);
            } else {
                $db_changes = '';
            }
            // What themes did we actually install?
            $themes_installed = array_unique($themes_installed);
            $themes_installed = implode(',', $themes_installed);
            // What failed steps?
            $failed_step_insert = serialize($failed_steps);
            smf_db_insert('', '{db_prefix}log_packages', array('filename' => 'string', 'name' => 'string', 'package_id' => 'string', 'version' => 'string', 'id_member_installed' => 'int', 'member_installed' => 'string', 'time_installed' => 'int', 'install_state' => 'int', 'failed_steps' => 'string', 'themes_installed' => 'string', 'member_removed' => 'int', 'db_changes' => 'string'), array($packageInfo['filename'], $packageInfo['name'], $packageInfo['id'], $packageInfo['version'], $user_info['id'], $user_info['name'], time(), $is_upgrade ? 2 : 1, $failed_step_insert, $themes_installed, 0, $db_changes), array('id_install'));
        }
        mysql_free_result($request);
        $context['install_finished'] = true;
    }
    // If there's database changes - and they want them removed - let's do it last!
    if (!empty($db_changes) && !empty($_POST['do_db_changes'])) {
        // We're gonna be needing the package db functions!
        db_extend('packages');
        foreach ($db_changes as $change) {
            if ($change[0] == 'remove_table' && isset($change[1])) {
                smf_db_drop_table($change[1]);
            } elseif ($change[0] == 'remove_column' && isset($change[2])) {
                smf_db_remove_column($change[1], $change[2]);
            } elseif ($change[0] == 'remove_index' && isset($change[2])) {
                smf_db_remove_index($change[1], $change[2]);
            }
        }
    }
    // Clean house... get rid of the evidence ;).
    if (file_exists($boarddir . '/Packages/temp')) {
        deltree($boarddir . '/Packages/temp');
    }
    // Log what we just did.
    logAction($context['uninstalling'] ? 'uninstall_package' : (!empty($is_upgrade) ? 'upgrade_package' : 'install_package'), array('package' => commonAPI::htmlspecialchars($packageInfo['name']), 'version' => commonAPI::htmlspecialchars($packageInfo['version'])), 'admin');
    // Just in case, let's clear the whole cache to avoid anything going up the swanny.
    clean_cache();
    // Restore file permissions?
    create_chmod_control(array(), array(), true);
}
 /**
  * Edit a particular set of language entries.
  */
 public function action_editlang()
 {
     global $settings, $context, $txt, $modSettings, $language, $scripturl;
     require_once SUBSDIR . '/Language.subs.php';
     loadLanguage('ManageSettings');
     // Select the languages tab.
     $context['menu_data_' . $context['admin_menu_id']]['current_subsection'] = 'edit';
     $context['page_title'] = $txt['edit_languages'];
     $context['sub_template'] = 'modify_language_entries';
     $context['lang_id'] = $_GET['lid'];
     list($theme_id, $file_id) = empty($_REQUEST['tfid']) || strpos($_REQUEST['tfid'], '+') === false ? array(1, '') : explode('+', $_REQUEST['tfid']);
     // Clean the ID - just in case.
     preg_match('~([A-Za-z0-9_-]+)~', $context['lang_id'], $matches);
     $context['lang_id'] = $matches[1];
     // Get all the theme data.
     require_once SUBSDIR . '/Themes.subs.php';
     $themes = getCustomThemes();
     // This will be where we look
     $lang_dirs = array();
     $images_dirs = array();
     // Check we have themes with a path and a name - just in case - and add the path.
     foreach ($themes as $id => $data) {
         if (count($data) != 2) {
             unset($themes[$id]);
         } elseif (is_dir($data['theme_dir'] . '/languages/' . $context['lang_id'])) {
             $lang_dirs[$id] = $data['theme_dir'] . '/languages/' . $context['lang_id'];
         }
         // How about image directories?
         if (is_dir($data['theme_dir'] . '/images/' . $context['lang_id'])) {
             $images_dirs[$id] = $data['theme_dir'] . '/images/' . $context['lang_id'];
         }
     }
     $current_file = $file_id ? $lang_dirs[$theme_id] . '/' . $file_id . '.' . $context['lang_id'] . '.php' : '';
     // Now for every theme get all the files and stick them in context!
     $context['possible_files'] = array();
     foreach ($lang_dirs as $theme => $theme_dir) {
         // Open it up.
         $dir = dir($theme_dir);
         while ($entry = $dir->read()) {
             // We're only after the files for this language.
             if (preg_match('~^([A-Za-z]+)\\.' . $context['lang_id'] . '\\.php$~', $entry, $matches) == 0) {
                 continue;
             }
             if (!isset($context['possible_files'][$theme])) {
                 $context['possible_files'][$theme] = array('id' => $theme, 'name' => $themes[$theme]['name'], 'files' => array());
             }
             $context['possible_files'][$theme]['files'][] = array('id' => $matches[1], 'name' => isset($txt['lang_file_desc_' . $matches[1]]) ? $txt['lang_file_desc_' . $matches[1]] : $matches[1], 'selected' => $theme_id == $theme && $file_id == $matches[1]);
         }
         $dir->close();
         usort($context['possible_files'][$theme]['files'], create_function('$val1, $val2', 'return strcmp($val1[\'name\'], $val2[\'name\']);'));
     }
     if ($context['lang_id'] != 'english') {
         $possiblePackage = findPossiblePackages($context['lang_id']);
         if ($possiblePackage !== false) {
             $context['langpack_uninstall_link'] = $scripturl . '?action=admin;area=packages;sa=uninstall;package=' . $possiblePackage[1] . ';pid=' . $possiblePackage[0];
         }
     }
     // We no longer wish to speak this language.
     // @todo - languages have been moved to packages
     // this may or may not be used in the future, for now it's not used at all
     // @deprecated since 1.0
     if (!empty($_POST['delete_main']) && $context['lang_id'] != 'english') {
         checkSession();
         validateToken('admin-mlang');
         // @todo FTP Controls?
         require_once SUBSDIR . '/Package.subs.php';
         // First, Make a backup?
         if (!empty($modSettings['package_make_backups']) && (!isset($_SESSION['last_backup_for']) || $_SESSION['last_backup_for'] != $context['lang_id'] . '$$$')) {
             $_SESSION['last_backup_for'] = $context['lang_id'] . '$$$';
             package_create_backup('backup_lang_' . $context['lang_id']);
         }
         // Second, loop through the array to remove the files.
         foreach ($lang_dirs as $curPath) {
             foreach ($context['possible_files'][1]['files'] as $lang) {
                 if (file_exists($curPath . '/' . $lang['id'] . '.' . $context['lang_id'] . '.php')) {
                     unlink($curPath . '/' . $lang['id'] . '.' . $context['lang_id'] . '.php');
                 }
             }
             // Check for the email template.
             if (file_exists($curPath . '/EmailTemplates.' . $context['lang_id'] . '.php')) {
                 unlink($curPath . '/EmailTemplates.' . $context['lang_id'] . '.php');
             }
         }
         // Third, the agreement file.
         if (file_exists(BOARDDIR . '/agreement.' . $context['lang_id'] . '.txt')) {
             unlink(BOARDDIR . '/agreement.' . $context['lang_id'] . '.txt');
         }
         // Fourth, a related images folder?
         if (!empty($images_dirs)) {
             foreach ($images_dirs as $curPath) {
                 if (is_dir($curPath)) {
                     deltree($curPath);
                 }
             }
         }
         // Members can no longer use this language.
         removeLanguageFromMember($context['lang_id']);
         // Fifth, update getLanguages() cache.
         if (!empty($modSettings['cache_enable'])) {
             cache_put_data('known_languages', null, !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600);
         }
         // Sixth, if we deleted the default language, set us back to english?
         if ($context['lang_id'] == $language) {
             require_once SUBSDIR . '/SettingsForm.class.php';
             $language = 'english';
             Settings_Form::save_file(array('language' => '\'' . $language . '\''));
         }
         // Seventh, get out of here.
         redirectexit('action=admin;area=languages;sa=edit;' . $context['session_var'] . '=' . $context['session_id']);
     }
     // Saving primary settings?
     $madeSave = false;
     if (!empty($_POST['save_main']) && !$current_file) {
         checkSession();
         validateToken('admin-mlang');
         // Read in the current file.
         $current_data = implode('', file($settings['default_theme_dir'] . '/languages/' . $context['lang_id'] . '/index.' . $context['lang_id'] . '.php'));
         // These are the replacements. old => new
         $replace_array = array('~\\$txt\\[\'lang_locale\'\\]\\s=\\s(\'|")[^\\r\\n]+~' => '$txt[\'lang_locale\'] = \'' . addslashes($_POST['locale']) . '\';', '~\\$txt\\[\'lang_dictionary\'\\]\\s=\\s(\'|")[^\\r\\n]+~' => '$txt[\'lang_dictionary\'] = \'' . addslashes($_POST['dictionary']) . '\';', '~\\$txt\\[\'lang_spelling\'\\]\\s=\\s(\'|")[^\\r\\n]+~' => '$txt[\'lang_spelling\'] = \'' . addslashes($_POST['spelling']) . '\';', '~\\$txt\\[\'lang_rtl\'\\]\\s=\\s[A-Za-z0-9]+;~' => '$txt[\'lang_rtl\'] = ' . (!empty($_POST['rtl']) ? 'true' : 'false') . ';');
         $current_data = preg_replace(array_keys($replace_array), array_values($replace_array), $current_data);
         $fp = fopen($settings['default_theme_dir'] . '/languages/' . $context['lang_id'] . '/index.' . $context['lang_id'] . '.php', 'w+');
         fwrite($fp, $current_data);
         fclose($fp);
         $madeSave = true;
     }
     // Quickly load index language entries.
     $old_txt = $txt;
     require $settings['default_theme_dir'] . '/languages/' . $context['lang_id'] . '/index.' . $context['lang_id'] . '.php';
     $context['lang_file_not_writable_message'] = is_writable($settings['default_theme_dir'] . '/languages/' . $context['lang_id'] . '/index.' . $context['lang_id'] . '.php') ? '' : sprintf($txt['lang_file_not_writable'], $settings['default_theme_dir'] . '/languages/' . $context['lang_id'] . '/index.' . $context['lang_id'] . '.php');
     // Setup the primary settings context.
     $context['primary_settings'] = array('name' => Util::ucwords(strtr($context['lang_id'], array('_' => ' ', '-utf8' => ''))), 'locale' => $txt['lang_locale'], 'dictionary' => $txt['lang_dictionary'], 'spelling' => $txt['lang_spelling'], 'rtl' => $txt['lang_rtl']);
     // Restore normal service.
     $txt = $old_txt;
     // Are we saving?
     $save_strings = array();
     if (isset($_POST['save_entries']) && !empty($_POST['entry'])) {
         checkSession();
         validateToken('admin-mlang');
         // Clean each entry!
         foreach ($_POST['entry'] as $k => $v) {
             // Only try to save if it's changed!
             if ($_POST['entry'][$k] != $_POST['comp'][$k]) {
                 $save_strings[$k] = cleanLangString($v, false);
             }
         }
     }
     // If we are editing a file work away at that.
     if ($current_file) {
         $context['entries_not_writable_message'] = is_writable($current_file) ? '' : sprintf($txt['lang_entries_not_writable'], $current_file);
         $entries = array();
         // We can't just require it I'm afraid - otherwise we pass in all kinds of variables!
         $multiline_cache = '';
         foreach (file($current_file) as $line) {
             // Got a new entry?
             if ($line[0] == '$' && !empty($multiline_cache)) {
                 preg_match('~\\$(helptxt|txt|editortxt)\\[\'(.+)\'\\]\\s?=\\s?(.+);~ms', strtr($multiline_cache, array("\r" => '')), $matches);
                 if (!empty($matches[3])) {
                     $entries[$matches[2]] = array('type' => $matches[1], 'full' => $matches[0], 'entry' => $matches[3]);
                     $multiline_cache = '';
                 }
             }
             $multiline_cache .= $line;
         }
         // Last entry to add?
         if ($multiline_cache) {
             preg_match('~\\$(helptxt|txt|editortxt)\\[\'(.+)\'\\]\\s?=\\s?(.+);~ms', strtr($multiline_cache, array("\r" => '')), $matches);
             if (!empty($matches[3])) {
                 $entries[$matches[2]] = array('type' => $matches[1], 'full' => $matches[0], 'entry' => $matches[3]);
             }
         }
         // These are the entries we can definitely save.
         $final_saves = array();
         $context['file_entries'] = array();
         foreach ($entries as $entryKey => $entryValue) {
             // Nowadays some entries have fancy keys, so better use something "portable" for the form
             $md5EntryKey = md5($entryKey);
             // Ignore some things we set separately.
             $ignore_files = array('lang_character_set', 'lang_locale', 'lang_dictionary', 'lang_spelling', 'lang_rtl');
             if (in_array($entryKey, $ignore_files)) {
                 continue;
             }
             // These are arrays that need breaking out.
             $arrays = array('days', 'days_short', 'months', 'months_titles', 'months_short', 'happy_birthday_author', 'karlbenson1_author', 'nite0859_author', 'zwaldowski_author', 'geezmo_author', 'karlbenson2_author');
             if (in_array($entryKey, $arrays)) {
                 // Get off the first bits.
                 $entryValue['entry'] = substr($entryValue['entry'], strpos($entryValue['entry'], '(') + 1, strrpos($entryValue['entry'], ')') - strpos($entryValue['entry'], '('));
                 $entryValue['entry'] = explode(',', strtr($entryValue['entry'], array(' ' => '')));
                 // Now create an entry for each item.
                 $cur_index = 0;
                 $save_cache = array('enabled' => false, 'entries' => array());
                 foreach ($entryValue['entry'] as $id => $subValue) {
                     // Is this a new index?
                     if (preg_match('~^(\\d+)~', $subValue, $matches)) {
                         $cur_index = $matches[1];
                         $subValue = substr($subValue, strpos($subValue, '\''));
                     }
                     // Clean up some bits.
                     $subValue = strtr($subValue, array('"' => '', '\'' => '', ')' => ''));
                     // Can we save?
                     if (isset($save_strings[$md5EntryKey . '-+- ' . $cur_index])) {
                         $save_cache['entries'][$cur_index] = strtr($save_strings[$md5EntryKey . '-+- ' . $cur_index], array('\'' => ''));
                         $save_cache['enabled'] = true;
                     } else {
                         $save_cache['entries'][$cur_index] = $subValue;
                     }
                     $context['file_entries'][] = array('key' => $entryKey . '-+- ' . $cur_index, 'display_key' => $entryKey . '-+- ' . $cur_index, 'value' => $subValue, 'rows' => 1);
                     $cur_index++;
                 }
                 // Do we need to save?
                 if ($save_cache['enabled']) {
                     // Format the string, checking the indexes first.
                     $items = array();
                     $cur_index = 0;
                     foreach ($save_cache['entries'] as $k2 => $v2) {
                         // Manually show the custom index.
                         if ($k2 != $cur_index) {
                             $items[] = $k2 . ' => \'' . $v2 . '\'';
                             $cur_index = $k2;
                         } else {
                             $items[] = '\'' . $v2 . '\'';
                         }
                         $cur_index++;
                     }
                     // Now create the string!
                     $final_saves[$entryKey] = array('find' => $entryValue['full'], 'replace' => '$' . $entryValue['type'] . '[\'' . $entryKey . '\'] = array(' . implode(', ', $items) . ');');
                 }
             } else {
                 // Saving?
                 if (isset($save_strings[$md5EntryKey]) && $save_strings[$md5EntryKey] != $entryValue['entry']) {
                     // @todo Fix this properly.
                     if ($save_strings[$md5EntryKey] == '') {
                         $save_strings[$md5EntryKey] = '\'\'';
                     }
                     // Set the new value.
                     $entryValue['entry'] = $save_strings[$md5EntryKey];
                     // And we know what to save now!
                     $final_saves[$entryKey] = array('find' => $entryValue['full'], 'replace' => '$' . $entryValue['type'] . '[\'' . $entryKey . '\'] = ' . $save_strings[$md5EntryKey] . ';');
                 }
                 $editing_string = cleanLangString($entryValue['entry'], true);
                 $context['file_entries'][] = array('key' => $md5EntryKey, 'display_key' => $entryKey, 'value' => $editing_string, 'rows' => (int) (strlen($editing_string) / 38) + substr_count($editing_string, "\n") + 1);
             }
         }
         // Any saves to make?
         if (!empty($final_saves)) {
             checkSession();
             $file_contents = implode('', file($current_file));
             foreach ($final_saves as $save) {
                 $file_contents = strtr($file_contents, array($save['find'] => $save['replace']));
             }
             // Save the actual changes.
             $fp = fopen($current_file, 'w+');
             fwrite($fp, strtr($file_contents, array("\r" => '')));
             fclose($fp);
             $madeSave = true;
         }
         // Another restore.
         $txt = $old_txt;
     }
     // If we saved, redirect.
     if ($madeSave) {
         redirectexit('action=admin;area=languages;sa=editlang;lid=' . $context['lang_id']);
     }
     createToken('admin-mlang');
 }