Exemplo n.º 1
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);
}
Exemplo n.º 2
0
function smf_db_create_table($table_name, $columns, $indexes = array(), $parameters = array(), $if_exists = 'ignore', $error = 'fatal')
{
    global $reservedTables, $db_package_log, $db_prefix;
    // Strip out the table name, we might not need it in some cases
    $real_prefix = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix;
    // With or without the database name, the fullname looks like this.
    $full_table_name = str_replace('{db_prefix}', $real_prefix, $table_name);
    $table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
    // First - no way do we touch SMF tables.
    if (in_array(strtolower($table_name), $reservedTables)) {
        return false;
    }
    // Log that we'll want to remove this on uninstall.
    $db_package_log[] = array('remove_table', $table_name);
    // Slightly easier on MySQL than the others...
    $tables = smf_db_list_tables();
    if (in_array($full_table_name, $tables)) {
        // This is a sad day... drop the table? If not, return false (error) by default.
        if ($if_exists == 'overwrite') {
            smf_db_drop_table($table_name);
        } else {
            return $if_exists == 'ignore';
        }
    }
    // Righty - let's do the damn thing!
    $table_query = 'CREATE TABLE ' . $table_name . "\n" . '(';
    foreach ($columns as $column) {
        // Auto increment is easy here!
        if (!empty($column['auto'])) {
            $default = 'auto_increment';
        } elseif (isset($column['default']) && $column['default'] !== null) {
            $default = 'default \'' . addslashes($column['default']) . '\'';
        } else {
            $default = '';
        }
        // Sort out the size... and stuff...
        $column['size'] = isset($column['size']) && is_numeric($column['size']) ? $column['size'] : null;
        list($type, $size) = smf_db_calculate_type($column['type'], $column['size']);
        // Allow unsigned integers (mysql only)
        $unsigned = in_array($type, array('int', 'tinyint', 'smallint', 'mediumint', 'bigint')) && !empty($column['unsigned']) ? 'unsigned ' : '';
        if ($size !== null) {
            $type = $type . '(' . $size . ')';
        }
        // Now just put it together!
        $table_query .= "\n\t`" . $column['name'] . '` ' . $type . ' ' . (!empty($unsigned) ? $unsigned : '') . (!empty($column['null']) ? '' : 'NOT NULL') . ' ' . $default . ',';
    }
    // Loop through the indexes next...
    foreach ($indexes as $index) {
        $columns = implode(',', $index['columns']);
        // Is it the primary?
        if (isset($index['type']) && $index['type'] == 'primary') {
            $table_query .= "\n\t" . 'PRIMARY KEY (' . implode(',', $index['columns']) . '),';
        } else {
            if (empty($index['name'])) {
                $index['name'] = implode('_', $index['columns']);
            }
            $table_query .= "\n\t" . (isset($index['type']) && $index['type'] == 'unique' ? 'UNIQUE' : 'KEY') . ' ' . $index['name'] . ' (' . $columns . '),';
        }
    }
    // No trailing commas!
    if (substr($table_query, -1) == ',') {
        $table_query = substr($table_query, 0, -1);
    }
    $table_query .= ') ENGINE=MyISAM';
    $table_query .= ' DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci';
    // Create the table!
    smf_db_query($table_query, array('security_override' => true));
}