function mktree($items, $checked = array(), $admins, $setall = FALSE, $adchild = FALSE) { $html = ''; $mod = ''; if (isset($checked[$items['id']]) || $setall) { $mod = ' CHECKED '; } /**** * If the item has children, we need to create the checkboxes for them by calling the mktree function on each of them * If not, just return a single checkbox. */ if (!empty($items['children'])) { $html .= '<li class="' . $items['type'] . '">'; if (trim($items['type']) == 'm' && !$adchild) { if (isset($admins[$items['permission']])) { $adcheck = 'checked'; } $html .= '<input class="checkbox" type=checkbox name="admin[' . $items['permission'] . ']" value="admin' . $items['id'] . '" ' . $adcheck . ' /> '; } $html .= '<input class="checkbox" type=checkbox name="permission[' . $items['id'] . ']" value="' . $items['id'] . '"' . $mod . ' /> ' . prettify($items['permission']) . ": " . $items['description'] . '<ul class="permission">'; foreach ($items['children'] as $child) { $html .= mktree($child, $checked, $admins, $setall, TRUE); } $html .= '</ul></li>'; } else { $html .= '<li class="' . $items['type'] . '">'; if (trim($items['type']) == 'm' && !$adchild) { if (isset($admins[$items['permission']])) { $adcheck = 'checked'; } $html .= '<input class="checkbox" type=checkbox name="admin[' . $items['permission'] . ']" value="admin' . $items['id'] . '" ' . $adcheck . ' /> '; } $html .= '<input class="checkbox" type=checkbox name="permission[' . $items['id'] . ']" value="' . $items['id'] . '"' . $mod . ' /> ' . prettify($items['permission']) . ': ' . $items['description'] . '</li>'; } return $html; }
function package_create_backup($id = 'backup') { global $sourcedir, $boarddir, $smcFunc; $files = array(); $base_files = array('index.php', 'SSI.php', 'agreement.txt', 'ssi_examples.php', 'ssi_examples.shtml'); foreach ($base_files as $file) { if (file_exists($boarddir . '/' . $file)) { $files[realpath($boarddir . '/' . $file)] = array(empty($_REQUEST['use_full_paths']) ? $file : $boarddir . '/' . $file, stat($boarddir . '/' . $file)); } } $dirs = array($sourcedir => empty($_REQUEST['use_full_paths']) ? 'Sources/' : strtr($sourcedir . '/', '\\', '/')); $request = smf_db_query(' SELECT value FROM {db_prefix}themes WHERE id_member = {int:no_member} AND variable = {string:theme_dir}', array('no_member' => 0, 'theme_dir' => 'theme_dir')); while ($row = mysql_fetch_assoc($request)) { $dirs[$row['value']] = empty($_REQUEST['use_full_paths']) ? 'Themes/' . basename($row['value']) . '/' : strtr($row['value'] . '/', '\\', '/'); } mysql_free_result($request); while (!empty($dirs)) { list($dir, $dest) = each($dirs); unset($dirs[$dir]); $listing = @dir($dir); if (!$listing) { continue; } while ($entry = $listing->read()) { if (preg_match('~^(\\.{1,2}|CVS|backup.*|help|images|.*\\~)$~', $entry) != 0) { continue; } $filepath = realpath($dir . '/' . $entry); if (isset($files[$filepath])) { continue; } $stat = stat($dir . '/' . $entry); if ($stat['mode'] & 040000) { $files[$filepath] = array($dest . $entry . '/', $stat); $dirs[$dir . '/' . $entry] = $dest . $entry . '/'; } else { $files[$filepath] = array($dest . $entry, $stat); } } $listing->close(); } if (!file_exists($boarddir . '/Packages/backups')) { mktree($boarddir . '/Packages/backups', 0777); } if (!is_writable($boarddir . '/Packages/backups')) { package_chmod($boarddir . '/Packages/backups'); } $output_file = $boarddir . '/Packages/backups/' . strftime('%Y-%m-%d_') . preg_replace('~[$\\\\/:<>|?*"\']~', '', $id); $output_ext = '.tar' . (function_exists('gzopen') ? '.gz' : ''); if (file_exists($output_file . $output_ext)) { $i = 2; while (file_exists($output_file . '_' . $i . $output_ext)) { $i++; } $output_file = $output_file . '_' . $i . $output_ext; } else { $output_file .= $output_ext; } @set_time_limit(300); if (function_exists('apache_reset_timeout')) { @apache_reset_timeout(); } if (function_exists('gzopen')) { $fwrite = 'gzwrite'; $fclose = 'gzclose'; $output = gzopen($output_file, 'wb'); } else { $fwrite = 'fwrite'; $fclose = 'fclose'; $output = fopen($output_file, 'wb'); } foreach ($files as $real_file => $file) { if (!file_exists($real_file)) { continue; } $stat = $file[1]; if (substr($file[0], -1) == '/') { $stat['size'] = 0; } $current = pack('a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12', $file[0], decoct($stat['mode']), sprintf('%06d', decoct($stat['uid'])), sprintf('%06d', decoct($stat['gid'])), decoct($stat['size']), decoct($stat['mtime']), '', 0, '', '', '', '', '', '', '', '', ''); $checksum = 256; for ($i = 0; $i < 512; $i++) { $checksum += ord($current[$i]); } $fwrite($output, substr($current, 0, 148) . pack('a8', decoct($checksum)) . substr($current, 156, 511)); if ($stat['size'] == 0) { continue; } $fp = fopen($real_file, 'rb'); while (!feof($fp)) { $fwrite($output, fread($fp, 16384)); } fclose($fp); $fwrite($output, pack('a' . (512 - $stat['size'] % 512), '')); } $fwrite($output, pack('a1024', '')); $fclose($output); }
/** * 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); }
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); }
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'); } }
function CleanupMods() { global $db_prefix, $modSettings, $upcontext, $boarddir, $sourcedir, $settings, $smcFunc, $command_line; // Sorry. Not supported for command line users. if ($command_line) { return true; } // Skipping first? if (!empty($_POST['skip'])) { unset($_POST['skip']); return true; } // If we get here withOUT SSI we need to redirect to ensure we get it! if (!isset($_GET['ssi']) || !function_exists('mktree')) { redirectLocation('&ssi=1'); } $upcontext['sub_template'] = 'clean_mods'; $upcontext['page_title'] = 'Cleanup Modifications'; // This can be skipped. $upcontext['skip'] = true; // If we're on the second redirect continue... if (isset($_POST['cleandone2'])) { return true; } // Do we already know about some writable files? if (isset($_POST['writable_files'])) { $writable_files = unserialize(base64_decode($_POST['writable_files'])); if (!makeFilesWritable($writable_files)) { // What have we left? $upcontext['writable_files'] = $writable_files; return false; } } // Load all theme paths.... $request = $smcFunc['db_query']('', ' SELECT id_theme, variable, value FROM {db_prefix}themes WHERE id_member = {int:id_member} AND variable IN ({string:theme_dir}, {string:images_url})', array('id_member' => 0, 'theme_dir' => 'theme_dir', 'images_url' => 'images_url', 'db_error_skip' => true)); $theme_paths = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) { if ($row['id_theme'] == 1) { $settings['default_' . $row['variable']] = $row['value']; } elseif ($row['variable'] == 'theme_dir') { $theme_paths[$row['id_theme']][$row['variable']] = $row['value']; } } $smcFunc['db_free_result']($request); // Are there are mods installed that may need uninstalling? $request = $smcFunc['db_query']('', ' SELECT id_install, filename, name, themes_installed, version FROM {db_prefix}log_packages WHERE install_state = {int:installed} ORDER BY time_installed DESC', array('installed' => 1, 'db_error_skip' => true)); $upcontext['packages'] = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) { // Work out the status. if (!file_exists($boarddir . '/Packages/' . $row['filename'])) { $status = 'Missing'; $status_color = 'red'; $result = 'Removed'; } else { $status = 'Installed'; $status_color = 'green'; $result = 'No Action Needed'; } $upcontext['packages'][$row['id_install']] = array('id' => $row['id_install'], 'themes' => explode(',', $row['themes_installed']), 'name' => $row['name'], 'filename' => $row['filename'], 'missing_file' => file_exists($boarddir . '/Packages/' . $row['filename']) ? 0 : 1, 'files' => array(), 'file_count' => 0, 'status' => $status, 'result' => $result, 'color' => $status_color, 'version' => $row['version'], 'needs_removing' => false); } $smcFunc['db_free_result']($request); // Don't carry on if there are none. if (empty($upcontext['packages'])) { return true; } // Setup some basics. if (!empty($upcontext['user']['version'])) { $_SESSION['version_emulate'] = $upcontext['user']['version']; } // Before we get started, don't report notice errors. $oldErrorReporting = error_reporting(E_ALL ^ E_NOTICE); if (!mktree($boarddir . '/Packages/temp', 0755)) { deltree($boarddir . '/Packages/temp', false); if (!mktree($boarddir . '/Packages/temp', 0777)) { deltree($boarddir . '/Packages/temp', false); //!!! Error here - plus chmod! } } // Anything which reinstalled should not have its entry removed. $reinstall_worked = array(); // We're gonna be doing some removin' $test = isset($_POST['cleandone']) ? false : true; foreach ($upcontext['packages'] as $id => $package) { // Can't do anything about this.... if ($package['missing_file']) { continue; } // Not testing *and* this wasn't checked? if (!$test && (!isset($_POST['remove']) || !isset($_POST['remove'][$id]))) { continue; } // What are the themes this was installed into? $cur_theme_paths = array(); foreach ($theme_paths as $tid => $data) { if ($tid != 1 && in_array($tid, $package['themes'])) { $cur_theme_paths[$tid] = $data; } } // Get the modifications data if applicable. $filename = $package['filename']; $packageInfo = getPackageInfo($filename); if (!is_array($packageInfo)) { continue; } $info = parsePackageInfo($packageInfo['xml'], $test, 'uninstall'); // Also get the reinstall details... if (isset($_POST['remove'])) { $infoInstall = parsePackageInfo($packageInfo['xml'], true); } if (is_file($boarddir . '/Packages/' . $filename)) { read_tgz_file($boarddir . '/Packages/' . $filename, $boarddir . '/Packages/temp'); } else { copytree($boarddir . '/Packages/' . $filename, $boarddir . '/Packages/temp'); } // Work out how we uninstall... $files = array(); foreach ($info as $change) { // Work out two things: // 1) Whether it's installed at the moment - and if so whether its fully installed, and: // 2) Whether it could be installed on the new version. if ($change['type'] == 'modification') { $contents = @file_get_contents($boarddir . '/Packages/temp/' . $upcontext['base_path'] . $change['filename']); if ($change['boardmod']) { $results = parseBoardMod($contents, $test, $change['reverse'], $cur_theme_paths); } else { $results = parseModification($contents, $test, $change['reverse'], $cur_theme_paths); } foreach ($results as $action) { // Something we can remove? Probably means it existed! if (($action['type'] == 'replace' || $action['type'] == 'append' || !empty($action['filename']) && $action['type'] == 'failure') && !in_array($action['filename'], $files)) { $files[] = $action['filename']; } if ($action['type'] == 'failure') { $upcontext['packages'][$id]['needs_removing'] = true; $upcontext['packages'][$id]['status'] = 'Reinstall Required'; $upcontext['packages'][$id]['color'] = '#FD6435'; } } } } // Store this info for the template as appropriate. $upcontext['packages'][$id]['files'] = $files; $upcontext['packages'][$id]['file_count'] = count($files); // If we've done something save the changes! if (!$test) { package_flush_cache(); } // Are we attempting to reinstall this thing? if (isset($_POST['remove']) && !$test && isset($infoInstall)) { // Need to extract again I'm afraid. if (is_file($boarddir . '/Packages/' . $filename)) { read_tgz_file($boarddir . '/Packages/' . $filename, $boarddir . '/Packages/temp'); } else { copytree($boarddir . '/Packages/' . $filename, $boarddir . '/Packages/temp'); } $errors = false; $upcontext['packages'][$id]['result'] = 'Removed'; foreach ($infoInstall as $change) { if ($change['type'] == 'modification') { $contents = @file_get_contents($boarddir . '/Packages/temp/' . $upcontext['base_path'] . $change['filename']); if ($change['boardmod']) { $results = parseBoardMod($contents, true, $change['reverse'], $cur_theme_paths); } else { $results = parseModification($contents, true, $change['reverse'], $cur_theme_paths); } // Are there any errors? foreach ($results as $action) { if ($action['type'] == 'failure') { $errors = true; } } } } if (!$errors) { $reinstall_worked[] = $id; $upcontext['packages'][$id]['result'] = 'Reinstalled'; $upcontext['packages'][$id]['color'] = 'green'; foreach ($infoInstall as $change) { if ($change['type'] == 'modification') { $contents = @file_get_contents($boarddir . '/Packages/temp/' . $upcontext['base_path'] . $change['filename']); if ($change['boardmod']) { $results = parseBoardMod($contents, false, $change['reverse'], $cur_theme_paths); } else { $results = parseModification($contents, false, $change['reverse'], $cur_theme_paths); } } } // Save the changes. package_flush_cache(); } } } // Put errors back on a sec. error_reporting($oldErrorReporting); // Check everything is writable. if ($test && !empty($upcontext['packages'])) { $writable_files = array(); foreach ($upcontext['packages'] as $package) { if (!empty($package['files'])) { foreach ($package['files'] as $file) { $writable_files[] = $file; } } } if (!empty($writable_files)) { $writable_files = array_unique($writable_files); $upcontext['writable_files'] = $writable_files; if (!makeFilesWritable($writable_files)) { return false; } } } if (file_exists($boarddir . '/Packages/temp')) { deltree($boarddir . '/Packages/temp'); } // Removing/Reinstalling any packages? if (isset($_POST['remove'])) { $deletes = array(); foreach ($_POST['remove'] as $id => $dummy) { if (!in_array((int) $id, $reinstall_worked)) { $deletes[] = (int) $id; } } if (!empty($deletes)) { upgrade_query(' UPDATE ' . $db_prefix . 'log_packages SET install_state = 0 WHERE id_install IN (' . implode(',', $deletes) . ')'); } // Ensure we don't lose our changes! package_put_contents($boarddir . '/Packages/installed.list', time()); $upcontext['sub_template'] = 'cleanup_done'; return false; } else { $allgood = true; // Is there actually anything that needs our attention? foreach ($upcontext['packages'] as $package) { if ($package['color'] != 'green') { $allgood = false; } } if ($allgood) { return true; } } $_GET['substep'] = 0; return isset($_POST['cleandone']) ? true : false; }
/** * Creates a site backup before installing a package just in case things don't go * as planned. * * @package Packages * @param string $id */ function package_create_backup($id = 'backup') { $db = database(); $files = array(); // The files that reside outside of sources, in the base, we add manually $base_files = array('index.php', 'SSI.php', 'agreement.txt', 'ssi_examples.php', 'ssi_examples.shtml', 'subscriptions.php', 'email_imap_cron.php', 'emailpost.php', 'emailtopic.php'); foreach ($base_files as $file) { if (file_exists(BOARDDIR . '/' . $file)) { $files[realpath(BOARDDIR . '/' . $file)] = array(empty($_REQUEST['use_full_paths']) ? $file : BOARDDIR . '/' . $file, stat(BOARDDIR . '/' . $file)); } } // Root directory where most of our files reside $dirs = array(SOURCEDIR => empty($_REQUEST['use_full_paths']) ? 'sources/' : strtr(SOURCEDIR . '/', '\\', '/')); // Find all installed theme directories $request = $db->query('', ' SELECT value FROM {db_prefix}themes WHERE id_member = {int:no_member} AND variable = {string:theme_dir}', array('no_member' => 0, 'theme_dir' => 'theme_dir')); while ($row = $db->fetch_assoc($request)) { $dirs[$row['value']] = empty($_REQUEST['use_full_paths']) ? 'themes/' . basename($row['value']) . '/' : strtr($row['value'] . '/', '\\', '/'); } $db->free_result($request); // While we have directorys to check while (!empty($dirs)) { list($dir, $dest) = each($dirs); unset($dirs[$dir]); // Get the file listing for this directory $listing = @dir($dir); if (!$listing) { continue; } while ($entry = $listing->read()) { if (preg_match('~^(\\.{1,2}|CVS|backup.*|help|images|.*\\~)$~', $entry) != 0) { continue; } $filepath = realpath($dir . '/' . $entry); if (isset($files[$filepath])) { continue; } $stat = stat($dir . '/' . $entry); // If this is a directory, add it to the dir stack for processing if ($stat['mode'] & 040000) { $files[$filepath] = array($dest . $entry . '/', $stat); $dirs[$dir . '/' . $entry] = $dest . $entry . '/'; } else { $files[$filepath] = array($dest . $entry, $stat); } } $listing->close(); } // Make sure we have a backup directory and its writable if (!file_exists(BOARDDIR . '/packages/backups')) { mktree(BOARDDIR . '/packages/backups', 0777); } if (!is_writable(BOARDDIR . '/packages/backups')) { package_chmod(BOARDDIR . '/packages/backups'); } // Name the output file, yyyy-mm-dd_before_package_name.tar.gz $output_file = BOARDDIR . '/packages/backups/' . strftime('%Y-%m-%d_') . preg_replace('~[$\\\\/:<>|?*"\']~', '', $id); $output_ext = '.tar' . (function_exists('gzopen') ? '.gz' : ''); if (file_exists($output_file . $output_ext)) { $i = 2; while (file_exists($output_file . '_' . $i . $output_ext)) { $i++; } $output_file = $output_file . '_' . $i . $output_ext; } else { $output_file .= $output_ext; } // Buy some more time so we have enough to create this archive @set_time_limit(300); if (function_exists('apache_reset_timeout')) { @apache_reset_timeout(); } // Set up the file output handle, try gzip first to save space if (function_exists('gzopen')) { $fwrite = 'gzwrite'; $fclose = 'gzclose'; $output = gzopen($output_file, 'wb'); } else { $fwrite = 'fwrite'; $fclose = 'fclose'; $output = fopen($output_file, 'wb'); } // For each file we found in the directory, we add them to a TAR archive foreach ($files as $real_file => $file) { if (!file_exists($real_file)) { continue; } // Check if its a directory $stat = $file[1]; if (substr($file[0], -1) == '/') { $stat['size'] = 0; } // Create a tar file header, pack the details in to the fields $current = pack('a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12', $file[0], decoct($stat['mode']), sprintf('%06d', decoct($stat['uid'])), sprintf('%06d', decoct($stat['gid'])), decoct($stat['size']), decoct($stat['mtime']), '', 0, '', '', '', '', '', '', '', '', ''); // Create the header checksum $checksum = 256; for ($i = 0; $i < 512; $i++) { $checksum += ord($current[$i]); } // Write out the file header (insert the checksum we just computed) $fwrite($output, substr($current, 0, 148) . pack('a8', decoct($checksum)) . substr($current, 156, 511)); // If this is a directory entry all thats needed is the header if ($stat['size'] == 0) { continue; } // Write the actual file contents to the backup file $fp = fopen($real_file, 'rb'); while (!feof($fp)) { $fwrite($output, fread($fp, 16384)); } fclose($fp); // Pad the output so its on 512 boundarys $fwrite($output, pack('a' . (512 - $stat['size'] % 512), '')); } $fwrite($output, pack('a1024', '')); $fclose($output); }
/** * Does the actual writing of the file * * - Writes the extracted file to disk or if we are extracting a single file * - it returns the extracted data */ private function _write_this_file() { $this->_skip = false; $this->_found = false; // A directory may need to be created if (strpos($this->_filename, '/') !== false && !$this->single_file || !$this->single_file && !is_dir($this->_file_info['dir'])) { mktree($this->_file_info['dir'], 0777); } // If we're looking for a **specific file**, and this is it... ka-bam, baby. if ($this->single_file && ($this->destination === $this->_filename || $this->destination === '*/' . basename($this->_filename))) { $this->_found = $this->_file_info['data']; } elseif ($this->single_file) { $this->_skip = true; } elseif ($this->files_to_extract !== null && !in_array($this->_filename, $this->files_to_extract)) { $this->_skip = true; } // Write it out then if ($this->_skip === true) { return; } elseif (!empty($this->_found)) { $this->_check_crc(); } elseif ($this->_skip === false && $this->_found === false && $this->_check_crc()) { package_put_contents($this->destination . '/' . $this->_filename, $this->_file_info['data']); } }
function mktree($ind, $indent = "", $back) { global $ctall, $deep, $type; $kids = array(); foreach ($ctall as $v) { if ($v['parentId'] == $ind) { $kids[] = $v; } } if (count($kids)) { foreach ($kids as $k) { $back .= $indent . "|" . $k['name'] . "|tiki-browse_categories.php?parentId=" . $k['categId'] . "&type=" . urlencode($type) . "&deep={$deep}\n"; $back .= mktree($k['categId'], ".{$indent}", ""); } return $back; } else { return ""; } }
/** * Install a smiley set. */ public function action_install() { global $modSettings, $scripturl, $context, $txt, $user_info; isAllowedTo('manage_smileys'); checkSession('request'); // One of these two may be necessary loadLanguage('Errors'); loadLanguage('Packages'); require_once SUBSDIR . '/Smileys.subs.php'; require_once SUBSDIR . '/Package.subs.php'; // Installing unless proven otherwise $testing = false; $destination = ''; $name = ''; if (isset($_REQUEST['set_gz'])) { $base_name = strtr(basename($_REQUEST['set_gz']), ':/', '-_'); $name = Util::htmlspecialchars(strtok(basename($_REQUEST['set_gz']), '.')); $name_pr = preg_replace(array('/\\s/', '/\\.[\\.]+/', '/[^\\w_\\.\\-]/'), array('_', '.', ''), $name); $context['filename'] = $base_name; // Check that the smiley is from simplemachines.org, for now... maybe add mirroring later. if (!isAuthorizedServer($_REQUEST['set_gz']) == 0) { fatal_lang_error('not_valid_server'); } $destination = BOARDDIR . '/packages/' . $base_name; if (file_exists($destination)) { fatal_lang_error('package_upload_error_exists'); } // Let's copy it to the packages directory file_put_contents($destination, fetch_web_data($_REQUEST['set_gz'])); $testing = true; } elseif (isset($_REQUEST['package'])) { $base_name = basename($_REQUEST['package']); $name = Util::htmlspecialchars(strtok(basename($_REQUEST['package']), '.')); $name_pr = preg_replace(array('/\\s/', '/\\.[\\.]+/', '/[^\\w_\\.\\-]/'), array('_', '.', ''), $name); $context['filename'] = $base_name; $destination = BOARDDIR . '/packages/' . basename($_REQUEST['package']); } if (!file_exists($destination)) { fatal_lang_error('package_no_file', false); } // Make sure temp directory exists and is empty. if (file_exists(BOARDDIR . '/packages/temp')) { deltree(BOARDDIR . '/packages/temp', false); } if (!mktree(BOARDDIR . '/packages/temp', 0755)) { deltree(BOARDDIR . '/packages/temp', false); if (!mktree(BOARDDIR . '/packages/temp', 0777)) { deltree(BOARDDIR . '/packages/temp', false); // @todo not sure about url in destination_url create_chmod_control(array(BOARDDIR . '/packages/temp/delme.tmp'), array('destination_url' => $scripturl . '?action=admin;area=smileys;sa=install;set_gz=' . $_REQUEST['set_gz'], 'crash_on_error' => true)); deltree(BOARDDIR . '/packages/temp', false); if (!mktree(BOARDDIR . '/packages/temp', 0777)) { fatal_lang_error('package_cant_download', false); } } } $extracted = read_tgz_file($destination, BOARDDIR . '/packages/temp'); // @todo needs to change the URL in the next line ;) if (!$extracted) { fatal_lang_error('packageget_unable', false, array('http://custom.elkarte.net/index.php?action=search;type=12;basic_search=' . $name)); } if ($extracted && !file_exists(BOARDDIR . '/packages/temp/package-info.xml')) { foreach ($extracted as $file) { if (basename($file['filename']) == 'package-info.xml') { $base_path = dirname($file['filename']) . '/'; break; } } } if (!isset($base_path)) { $base_path = ''; } if (!file_exists(BOARDDIR . '/packages/temp/' . $base_path . 'package-info.xml')) { fatal_lang_error('package_get_error_missing_xml', false); } $smileyInfo = getPackageInfo($context['filename']); if (!is_array($smileyInfo)) { fatal_lang_error($smileyInfo); } // See if it is installed? if (isSmileySetInstalled($smileyInfo['id'])) { fata_lang_error('package_installed_warning1'); } // Everything is fine, now it's time to do something, first we test $actions = parsePackageInfo($smileyInfo['xml'], true, 'install'); $context['post_url'] = $scripturl . '?action=admin;area=smileys;sa=install;package=' . $base_name; $context['has_failure'] = false; $context['actions'] = array(); $context['ftp_needed'] = false; foreach ($actions as $action) { if ($action['type'] == 'readme' || $action['type'] == 'license') { $type = 'package_' . $action['type']; if (file_exists(BOARDDIR . '/packages/temp/' . $base_path . $action['filename'])) { $context[$type] = htmlspecialchars(trim(file_get_contents(BOARDDIR . '/packages/temp/' . $base_path . $action['filename']), "\n\r"), ENT_COMPAT, 'UTF-8'); } elseif (file_exists($action['filename'])) { $context[$type] = htmlspecialchars(trim(file_get_contents($action['filename']), "\n\r"), ENT_COMPAT, 'UTF-8'); } if (!empty($action['parse_bbc'])) { require_once SUBSDIR . '/Post.subs.php'; preparsecode($context[$type]); $context[$type] = parse_bbc($context[$type]); } else { $context[$type] = nl2br($context[$type]); } continue; } elseif ($action['type'] == 'require-dir') { // Do this one... $thisAction = array('type' => $txt['package_extract'] . ' ' . ($action['type'] == 'require-dir' ? $txt['package_tree'] : $txt['package_file']), 'action' => Util::htmlspecialchars(strtr($action['destination'], array(BOARDDIR => '.')))); $file = BOARDDIR . '/packages/temp/' . $base_path . $action['filename']; if (isset($action['filename']) && (!file_exists($file) || !is_writable(dirname($action['destination'])))) { $context['has_failure'] = true; $thisAction += array('description' => $txt['package_action_error'], 'failed' => true); } // Show a description for the action if one is provided if (empty($thisAction['description'])) { $thisAction['description'] = isset($action['description']) ? $action['description'] : ''; } $context['actions'][] = $thisAction; } elseif ($action['type'] == 'credits') { // Time to build the billboard $credits_tag = array('url' => $action['url'], 'license' => $action['license'], 'copyright' => $action['copyright'], 'title' => $action['title']); } } if ($testing) { $context['sub_template'] = 'view_package'; $context['uninstalling'] = false; $context['is_installed'] = false; $context['package_name'] = $smileyInfo['name']; loadTemplate('Packages'); } else { $actions = parsePackageInfo($smileyInfo['xml'], false, 'install'); foreach ($context['actions'] as $action) { updateSettings(array('smiley_sets_known' => $modSettings['smiley_sets_known'] . ',' . basename($action['action']), 'smiley_sets_names' => $modSettings['smiley_sets_names'] . "\n" . $smileyInfo['name'] . (count($context['actions']) > 1 ? ' ' . (!empty($action['description']) ? Util::htmlspecialchars($action['description']) : basename($action['action'])) : ''))); } package_flush_cache(); // Time to tell pacman we have a new package installed! package_put_contents(BOARDDIR . '/packages/installed.list', time()); // Credits tag? $credits_tag = empty($credits_tag) ? '' : serialize($credits_tag); $installed = array('filename' => $smileyInfo['filename'], 'name' => $smileyInfo['name'], 'package_id' => $smileyInfo['id'], 'version' => $smileyInfo['filename'], 'id_member' => $user_info['id'], 'member_name' => $user_info['name'], 'credits_tag' => $credits_tag); logPackageInstall($installed); logAction('install_package', array('package' => Util::htmlspecialchars($smileyInfo['name']), 'version' => Util::htmlspecialchars($smileyInfo['version'])), 'admin'); cache_put_data('parsing_smileys', null, 480); cache_put_data('posting_smileys', null, 480); } if (file_exists(BOARDDIR . '/packages/temp')) { deltree(BOARDDIR . '/packages/temp'); } if (!$testing) { redirectexit('action=admin;area=smileys'); } }
/** * Does the actual writing of the file * * - Writes the extracted file to disk or if we are extracting a single file * - it returns the extracted data */ private function _write_this_file() { $this->_skip = false; $this->_found = false; // A directory may need to be created if (strpos($this->_current['filename'], '/') !== false && !$this->single_file) { mktree($this->destination . '/' . dirname($this->_current['filename']), 0777); } // Is this the file we're looking for? if ($this->single_file && ($this->destination === $this->_current['filename'] || $this->destination === '*/' . basename($this->_current['filename']))) { $this->_found = $this->_current['data']; } elseif ($this->single_file) { $this->_skip = true; } elseif ($this->files_to_extract !== null && !in_array($this->_current['filename'], $this->files_to_extract)) { $this->_skip = true; } // Write it out then if ($this->_check_header_crc() && $this->_skip === false && $this->_found === false) { package_put_contents($this->destination . '/' . $this->_current['filename'], $this->_current['data']); } }