Chris@76: 'PackageBrowse', Chris@76: 'remove' => 'PackageRemove', Chris@76: 'list' => 'PackageList', Chris@76: 'ftptest' => 'PackageFTPTest', Chris@76: 'install' => 'PackageInstallTest', Chris@76: 'install2' => 'PackageInstall', Chris@76: 'uninstall' => 'PackageInstallTest', Chris@76: 'uninstall2' => 'PackageInstall', Chris@76: 'installed' => 'InstalledList', Chris@76: 'options' => 'PackageOptions', Chris@76: 'perms' => 'PackagePermissions', Chris@76: 'flush' => 'FlushInstall', Chris@76: 'examine' => 'ExamineFile', Chris@76: 'showoperations' => 'ViewOperations', Chris@76: ); Chris@76: Chris@76: // Work out exactly who it is we are calling. Chris@76: if (isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']])) Chris@76: $context['sub_action'] = $_REQUEST['sa']; Chris@76: else Chris@76: $context['sub_action'] = 'browse'; Chris@76: Chris@76: // Set up some tabs... Chris@76: $context[$context['admin_menu_name']]['tab_data'] = array( Chris@76: 'title' => $txt['package_manager'], Chris@76: // !!! 'help' => 'registrations', Chris@76: 'description' => $txt['package_manager_desc'], Chris@76: 'tabs' => array( Chris@76: 'browse' => array( Chris@76: ), Chris@76: 'packageget' => array( Chris@76: 'description' => $txt['download_packages_desc'], Chris@76: ), Chris@76: 'installed' => array( Chris@76: 'description' => $txt['installed_packages_desc'], Chris@76: ), Chris@76: 'perms' => array( Chris@76: 'description' => $txt['package_file_perms_desc'], Chris@76: ), Chris@76: 'options' => array( Chris@76: 'description' => $txt['package_install_options_ftp_why'], Chris@76: ), Chris@76: ), Chris@76: ); Chris@76: Chris@76: // Call the function we're handing control to. Chris@76: $subActions[$context['sub_action']](); Chris@76: } Chris@76: Chris@76: // Test install a package. Chris@76: function PackageInstallTest() Chris@76: { Chris@76: global $boarddir, $txt, $context, $scripturl, $sourcedir, $modSettings, $smcFunc, $settings; Chris@76: Chris@76: // You have to specify a file!! Chris@76: if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '') Chris@76: redirectexit('action=admin;area=packages'); Chris@76: $context['filename'] = preg_replace('~[\.]+~', '.', $_REQUEST['package']); Chris@76: Chris@76: // Do we have an existing id, for uninstalls and the like. Chris@76: $context['install_id'] = isset($_REQUEST['pid']) ? (int) $_REQUEST['pid'] : 0; Chris@76: Chris@76: require_once($sourcedir . '/Subs-Package.php'); Chris@76: Chris@76: // Load up the package FTP information? Chris@76: create_chmod_control(); Chris@76: Chris@76: // Make sure temp directory exists and is empty. Chris@76: if (file_exists($boarddir . '/Packages/temp')) Chris@76: deltree($boarddir . '/Packages/temp', false); Chris@76: Chris@76: if (!mktree($boarddir . '/Packages/temp', 0755)) Chris@76: { Chris@76: deltree($boarddir . '/Packages/temp', false); Chris@76: if (!mktree($boarddir . '/Packages/temp', 0777)) Chris@76: { Chris@76: deltree($boarddir . '/Packages/temp', false); Chris@76: create_chmod_control(array($boarddir . '/Packages/temp/delme.tmp'), array('destination_url' => $scripturl . '?action=admin;area=packages;sa=' . $_REQUEST['sa'] . ';package=' . $_REQUEST['package'], 'crash_on_error' => true)); Chris@76: Chris@76: deltree($boarddir . '/Packages/temp', false); Chris@76: if (!mktree($boarddir . '/Packages/temp', 0777)) Chris@76: fatal_lang_error('package_cant_download', false); Chris@76: } Chris@76: } Chris@76: Chris@76: $context['uninstalling'] = $_REQUEST['sa'] == 'uninstall'; Chris@76: Chris@76: // Change our last link tree item for more information on this Packages area. Chris@76: $context['linktree'][count($context['linktree']) - 1] = array( Chris@76: 'url' => $scripturl . '?action=admin;area=packages;sa=browse', Chris@76: 'name' => $context['uninstalling'] ? $txt['package_uninstall_actions'] : $txt['install_actions'] Chris@76: ); Chris@76: $context['page_title'] .= ' - ' . ($context['uninstalling'] ? $txt['package_uninstall_actions'] : $txt['install_actions']); Chris@76: Chris@76: $context['sub_template'] = 'view_package'; Chris@76: Chris@76: if (!file_exists($boarddir . '/Packages/' . $context['filename'])) Chris@76: { Chris@76: deltree($boarddir . '/Packages/temp'); Chris@76: fatal_lang_error('package_no_file', false); Chris@76: } Chris@76: Chris@76: // Extract the files so we can get things like the readme, etc. Chris@76: if (is_file($boarddir . '/Packages/' . $context['filename'])) Chris@76: { Chris@76: $context['extracted_files'] = read_tgz_file($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp'); Chris@76: Chris@76: if ($context['extracted_files'] && !file_exists($boarddir . '/Packages/temp/package-info.xml')) Chris@76: foreach ($context['extracted_files'] as $file) Chris@76: if (basename($file['filename']) == 'package-info.xml') Chris@76: { Chris@76: $context['base_path'] = dirname($file['filename']) . '/'; Chris@76: break; Chris@76: } Chris@76: Chris@76: if (!isset($context['base_path'])) Chris@76: $context['base_path'] = ''; Chris@76: } Chris@76: elseif (is_dir($boarddir . '/Packages/' . $context['filename'])) Chris@76: { Chris@76: copytree($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp'); Chris@76: $context['extracted_files'] = listtree($boarddir . '/Packages/temp'); Chris@76: $context['base_path'] = ''; Chris@76: } Chris@76: else Chris@76: fatal_lang_error('no_access', false); Chris@76: Chris@76: // Load up any custom themes we may want to install into... Chris@76: $request = $smcFunc['db_query']('', ' Chris@76: SELECT id_theme, variable, value Chris@76: FROM {db_prefix}themes Chris@76: WHERE (id_theme = {int:default_theme} OR id_theme IN ({array_int:known_theme_list})) Chris@76: AND variable IN ({string:name}, {string:theme_dir})', Chris@76: array( Chris@76: 'known_theme_list' => explode(',', $modSettings['knownThemes']), Chris@76: 'default_theme' => 1, Chris@76: 'name' => 'name', Chris@76: 'theme_dir' => 'theme_dir', Chris@76: ) Chris@76: ); Chris@76: $theme_paths = array(); Chris@76: while ($row = $smcFunc['db_fetch_assoc']($request)) Chris@76: $theme_paths[$row['id_theme']][$row['variable']] = $row['value']; Chris@76: $smcFunc['db_free_result']($request); Chris@76: Chris@76: // Get the package info... Chris@76: $packageInfo = getPackageInfo($context['filename']); Chris@76: Chris@76: if (!is_array($packageInfo)) Chris@76: fatal_lang_error($packageInfo); Chris@76: Chris@76: $packageInfo['filename'] = $context['filename']; Chris@76: $context['package_name'] = isset($packageInfo['name']) ? $packageInfo['name'] : $context['filename']; Chris@76: Chris@76: // Set the type of extraction... Chris@76: $context['extract_type'] = isset($packageInfo['type']) ? $packageInfo['type'] : 'modification'; Chris@76: Chris@76: // The mod isn't installed.... unless proven otherwise. Chris@76: $context['is_installed'] = false; Chris@76: Chris@76: // See if it is installed? Chris@76: $request = $smcFunc['db_query']('', ' Chris@76: SELECT version, themes_installed, db_changes Chris@76: FROM {db_prefix}log_packages Chris@76: WHERE package_id = {string:current_package} Chris@76: AND install_state != {int:not_installed} Chris@76: ORDER BY time_installed DESC Chris@76: LIMIT 1', Chris@76: array( Chris@76: 'not_installed' => 0, Chris@76: 'current_package' => $packageInfo['id'], Chris@76: ) Chris@76: ); Chris@76: Chris@76: while ($row = $smcFunc['db_fetch_assoc']($request)) Chris@76: { Chris@76: $old_themes = explode(',', $row['themes_installed']); Chris@76: $old_version = $row['version']; Chris@76: $db_changes = empty($row['db_changes']) ? array() : unserialize($row['db_changes']); Chris@76: } Chris@76: $smcFunc['db_free_result']($request); Chris@76: Chris@76: $context['database_changes'] = array(); Chris@76: if (!empty($db_changes)) Chris@76: { Chris@76: foreach ($db_changes as $change) Chris@76: { Chris@76: if (isset($change[2]) && isset($txt['package_db_' . $change[0]])) Chris@76: $context['database_changes'][] = sprintf($txt['package_db_' . $change[0]], $change[1], $change[2]); Chris@76: elseif (isset($txt['package_db_' . $change[0]])) Chris@76: $context['database_changes'][] = sprintf($txt['package_db_' . $change[0]], $change[1]); Chris@76: else Chris@76: $context['database_changes'][] = $change[0] . '-' . $change[1] . (isset($change[2]) ? '-' . $change[2] : ''); Chris@76: } Chris@76: } Chris@76: Chris@76: // Uninstalling? Chris@76: if ($context['uninstalling']) Chris@76: { Chris@76: // Wait, it's not installed yet! Chris@76: if (!isset($old_version) && $context['uninstalling']) Chris@76: { Chris@76: deltree($boarddir . '/Packages/temp'); Chris@76: fatal_lang_error('package_cant_uninstall', false); Chris@76: } Chris@76: Chris@76: $actions = parsePackageInfo($packageInfo['xml'], true, 'uninstall'); Chris@76: Chris@76: // Gadzooks! There's no uninstaller at all!? Chris@76: if (empty($actions)) Chris@76: { Chris@76: deltree($boarddir . '/Packages/temp'); Chris@76: fatal_lang_error('package_uninstall_cannot', false); Chris@76: } Chris@76: Chris@76: // Can't edit the custom themes it's edited if you're unisntalling, they must be removed. Chris@76: $context['themes_locked'] = true; Chris@76: Chris@76: // Only let them uninstall themes it was installed into. Chris@76: foreach ($theme_paths as $id => $data) Chris@76: if ($id != 1 && !in_array($id, $old_themes)) Chris@76: unset($theme_paths[$id]); Chris@76: } Chris@76: elseif (isset($old_version) && $old_version != $packageInfo['version']) Chris@76: { Chris@76: // Look for an upgrade... Chris@76: $actions = parsePackageInfo($packageInfo['xml'], true, 'upgrade', $old_version); Chris@76: Chris@76: // There was no upgrade.... Chris@76: if (empty($actions)) Chris@76: $context['is_installed'] = true; Chris@76: else Chris@76: { Chris@76: // Otherwise they can only upgrade themes from the first time around. Chris@76: foreach ($theme_paths as $id => $data) Chris@76: if ($id != 1 && !in_array($id, $old_themes)) Chris@76: unset($theme_paths[$id]); Chris@76: } Chris@76: } Chris@76: elseif (isset($old_version) && $old_version == $packageInfo['version']) Chris@76: $context['is_installed'] = true; Chris@76: Chris@76: if (!isset($old_version) || $context['is_installed']) Chris@76: $actions = parsePackageInfo($packageInfo['xml'], true, 'install'); Chris@76: Chris@76: $context['actions'] = array(); Chris@76: $context['ftp_needed'] = false; Chris@76: $context['has_failure'] = false; Chris@76: $chmod_files = array(); Chris@76: Chris@76: if (empty($actions)) Chris@76: return; Chris@76: Chris@76: // This will hold data about anything that can be installed in other themes. Chris@76: $themeFinds = array( Chris@76: 'candidates' => array(), Chris@76: 'other_themes' => array(), Chris@76: ); Chris@76: Chris@76: // Now prepare things for the template. Chris@76: foreach ($actions as $action) Chris@76: { Chris@76: // Not failed until proven otherwise. Chris@76: $failed = false; Chris@76: Chris@76: if ($action['type'] == 'chmod') Chris@76: { Chris@76: $chmod_files[] = $action['filename']; Chris@76: continue; Chris@76: } Chris@76: elseif ($action['type'] == 'readme') Chris@76: { Chris@76: if (file_exists($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename'])) Chris@76: $context['package_readme'] = htmlspecialchars(trim(file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), "\n\r")); Chris@76: elseif (file_exists($action['filename'])) Chris@76: $context['package_readme'] = htmlspecialchars(trim(file_get_contents($action['filename']), "\n\r")); Chris@76: Chris@76: if (!empty($action['parse_bbc'])) Chris@76: { Chris@76: require_once($sourcedir . '/Subs-Post.php'); Chris@76: preparsecode($context['package_readme']); Chris@76: $context['package_readme'] = parse_bbc($context['package_readme']); Chris@76: } Chris@76: else Chris@76: $context['package_readme'] = nl2br($context['package_readme']); Chris@76: Chris@76: continue; Chris@76: } Chris@76: // Don't show redirects. Chris@76: elseif ($action['type'] == 'redirect') Chris@76: continue; Chris@76: elseif ($action['type'] == 'error') Chris@76: $context['has_failure'] = true; Chris@76: elseif ($action['type'] == 'modification') Chris@76: { Chris@76: if (!file_exists($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename'])) Chris@76: { Chris@76: $context['has_failure'] = true; Chris@76: Chris@76: $context['actions'][] = array( Chris@76: 'type' => $txt['execute_modification'], Chris@76: 'action' => $smcFunc['htmlspecialchars'](strtr($action['filename'], array($boarddir => '.'))), Chris@76: 'description' => $txt['package_action_error'], Chris@76: 'failed' => true, Chris@76: ); Chris@76: } Chris@76: Chris@76: if ($action['boardmod']) Chris@76: $mod_actions = parseBoardMod(@file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), true, $action['reverse'], $theme_paths); Chris@76: else Chris@76: $mod_actions = parseModification(@file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), true, $action['reverse'], $theme_paths); Chris@76: Chris@76: if (count($mod_actions) == 1 && isset($mod_actions[0]) && $mod_actions[0]['type'] == 'error' && $mod_actions[0]['filename'] == '-') Chris@76: $mod_actions[0]['filename'] = $action['filename']; Chris@76: Chris@76: foreach ($mod_actions as $key => $mod_action) Chris@76: { Chris@76: // Lets get the last section of the file name. Chris@76: if (isset($mod_action['filename']) && substr($mod_action['filename'], -13) != '.template.php') Chris@76: $actual_filename = strtolower(substr(strrchr($mod_action['filename'], '/'), 1) . '||' . $action['filename']); Chris@76: elseif (isset($mod_action['filename']) && preg_match('~([\w]*)/([\w]*)\.template\.php$~', $mod_action['filename'], $matches)) Chris@76: $actual_filename = strtolower($matches[1] . '/' . $matches[2] . '.template.php' . '||' . $action['filename']); Chris@76: else Chris@76: $actual_filename = $key; Chris@76: Chris@76: if ($mod_action['type'] == 'opened') Chris@76: $failed = false; Chris@76: elseif ($mod_action['type'] == 'failure') Chris@76: { Chris@76: if (empty($mod_action['is_custom'])) Chris@76: $context['has_failure'] = true; Chris@76: $failed = true; Chris@76: } Chris@76: elseif ($mod_action['type'] == 'chmod') Chris@76: { Chris@76: $chmod_files[] = $mod_action['filename']; Chris@76: } Chris@76: elseif ($mod_action['type'] == 'saved') Chris@76: { Chris@76: if (!empty($mod_action['is_custom'])) Chris@76: { Chris@76: if (!isset($context['theme_actions'][$mod_action['is_custom']])) Chris@76: $context['theme_actions'][$mod_action['is_custom']] = array( Chris@76: 'name' => $theme_paths[$mod_action['is_custom']]['name'], Chris@76: 'actions' => array(), Chris@76: 'has_failure' => $failed, Chris@76: ); Chris@76: else Chris@76: $context['theme_actions'][$mod_action['is_custom']]['has_failure'] |= $failed; Chris@76: Chris@76: $context['theme_actions'][$mod_action['is_custom']]['actions'][$actual_filename] = array( Chris@76: 'type' => $txt['execute_modification'], Chris@76: 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), Chris@76: 'description' => $failed ? $txt['package_action_failure'] : $txt['package_action_success'], Chris@76: 'failed' => $failed, Chris@76: ); Chris@76: } Chris@76: elseif (!isset($context['actions'][$actual_filename])) Chris@76: { Chris@76: $context['actions'][$actual_filename] = array( Chris@76: 'type' => $txt['execute_modification'], Chris@76: 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), Chris@76: 'description' => $failed ? $txt['package_action_failure'] : $txt['package_action_success'], Chris@76: 'failed' => $failed, Chris@76: ); Chris@76: } Chris@76: else Chris@76: { Chris@76: $context['actions'][$actual_filename]['failed'] |= $failed; Chris@76: $context['actions'][$actual_filename]['description'] = $context['actions'][$actual_filename]['failed'] ? $txt['package_action_failure'] : $txt['package_action_success']; Chris@76: } Chris@76: } Chris@76: elseif ($mod_action['type'] == 'skipping') Chris@76: { Chris@76: $context['actions'][$actual_filename] = array( Chris@76: 'type' => $txt['execute_modification'], Chris@76: 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), Chris@76: 'description' => $txt['package_action_skipping'] Chris@76: ); Chris@76: } Chris@76: elseif ($mod_action['type'] == 'missing' && empty($mod_action['is_custom'])) Chris@76: { Chris@76: $context['has_failure'] = true; Chris@76: $context['actions'][$actual_filename] = array( Chris@76: 'type' => $txt['execute_modification'], Chris@76: 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), Chris@76: 'description' => $txt['package_action_missing'], Chris@76: 'failed' => true, Chris@76: ); Chris@76: } Chris@76: elseif ($mod_action['type'] == 'error') Chris@76: $context['actions'][$actual_filename] = array( Chris@76: 'type' => $txt['execute_modification'], Chris@76: 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), Chris@76: 'description' => $txt['package_action_error'], Chris@76: 'failed' => true, Chris@76: ); Chris@76: } Chris@76: Chris@76: // We need to loop again just to get the operations down correctly. Chris@76: foreach ($mod_actions as $operation_key => $mod_action) Chris@76: { Chris@76: // Lets get the last section of the file name. Chris@76: if (isset($mod_action['filename']) && substr($mod_action['filename'], -13) != '.template.php') Chris@76: $actual_filename = strtolower(substr(strrchr($mod_action['filename'], '/'), 1) . '||' . $action['filename']); Chris@76: elseif (isset($mod_action['filename']) && preg_match('~([\w]*)/([\w]*)\.template\.php$~', $mod_action['filename'], $matches)) Chris@76: $actual_filename = strtolower($matches[1] . '/' . $matches[2] . '.template.php' . '||' . $action['filename']); Chris@76: else Chris@76: $actual_filename = $key; Chris@76: Chris@76: // We just need it for actual parse changes. Chris@76: if (!in_array($mod_action['type'], array('error', 'result', 'opened', 'saved', 'end', 'missing', 'skipping', 'chmod'))) Chris@76: { Chris@76: if (empty($mod_action['is_custom'])) Chris@76: $context['actions'][$actual_filename]['operations'][] = array( Chris@76: 'type' => $txt['execute_modification'], Chris@76: 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), Chris@76: 'description' => $mod_action['failed'] ? $txt['package_action_failure'] : $txt['package_action_success'], Chris@76: 'position' => $mod_action['position'], Chris@76: 'operation_key' => $operation_key, Chris@76: 'filename' => $action['filename'], Chris@76: 'is_boardmod' => $action['boardmod'], Chris@76: 'failed' => $mod_action['failed'], Chris@76: 'ignore_failure' => !empty($mod_action['ignore_failure']), Chris@76: ); Chris@76: Chris@76: // Themes are under the saved type. Chris@76: if (isset($mod_action['is_custom']) && isset($context['theme_actions'][$mod_action['is_custom']])) Chris@76: $context['theme_actions'][$mod_action['is_custom']]['actions'][$actual_filename]['operations'][] = array( Chris@76: 'type' => $txt['execute_modification'], Chris@76: 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), Chris@76: 'description' => $mod_action['failed'] ? $txt['package_action_failure'] : $txt['package_action_success'], Chris@76: 'position' => $mod_action['position'], Chris@76: 'operation_key' => $operation_key, Chris@76: 'filename' => $action['filename'], Chris@76: 'is_boardmod' => $action['boardmod'], Chris@76: 'failed' => $mod_action['failed'], Chris@76: 'ignore_failure' => !empty($mod_action['ignore_failure']), Chris@76: ); Chris@76: } Chris@76: } Chris@76: Chris@76: // Don't add anything else. Chris@76: $thisAction = array(); Chris@76: } Chris@76: elseif ($action['type'] == 'code') Chris@76: $thisAction = array( Chris@76: 'type' => $txt['execute_code'], Chris@76: 'action' => $smcFunc['htmlspecialchars']($action['filename']), Chris@76: ); Chris@76: elseif ($action['type'] == 'database') Chris@76: { Chris@76: $thisAction = array( Chris@76: 'type' => $txt['execute_database_changes'], Chris@76: 'action' => $smcFunc['htmlspecialchars']($action['filename']), Chris@76: ); Chris@76: } Chris@76: elseif (in_array($action['type'], array('create-dir', 'create-file'))) Chris@76: $thisAction = array( Chris@76: 'type' => $txt['package_create'] . ' ' . ($action['type'] == 'create-dir' ? $txt['package_tree'] : $txt['package_file']), Chris@76: 'action' => $smcFunc['htmlspecialchars'](strtr($action['destination'], array($boarddir => '.'))) Chris@76: ); Chris@76: elseif (in_array($action['type'], array('require-dir', 'require-file'))) Chris@76: { Chris@76: // Do this one... Chris@76: $thisAction = array( Chris@76: 'type' => $txt['package_extract'] . ' ' . ($action['type'] == 'require-dir' ? $txt['package_tree'] : $txt['package_file']), Chris@76: 'action' => $smcFunc['htmlspecialchars'](strtr($action['destination'], array($boarddir => '.'))) Chris@76: ); Chris@76: Chris@76: // Could this be theme related? Chris@76: if (!empty($action['unparsed_destination']) && preg_match('~^\$(languagedir|languages_dir|imagesdir|themedir|themes_dir)~i', $action['unparsed_destination'], $matches)) Chris@76: { Chris@76: // Is the action already stated? Chris@76: $theme_action = !empty($action['theme_action']) && in_array($action['theme_action'], array('no', 'yes', 'auto')) ? $action['theme_action'] : 'auto'; Chris@76: // If it's not auto do we think we have something we can act upon? Chris@76: if ($theme_action != 'auto' && !in_array($matches[1], array('languagedir', 'languages_dir', 'imagesdir', 'themedir'))) Chris@76: $theme_action = ''; Chris@76: // ... or if it's auto do we even want to do anything? Chris@76: elseif ($theme_action == 'auto' && $matches[1] != 'imagesdir') Chris@76: $theme_action = ''; Chris@76: Chris@76: // So, we still want to do something? Chris@76: if ($theme_action != '') Chris@76: $themeFinds['candidates'][] = $action; Chris@76: // Otherwise is this is going into another theme record it. Chris@76: elseif ($matches[1] == 'themes_dir') Chris@76: $themeFinds['other_themes'][] = strtolower(strtr(parse_path($action['unparsed_destination']), array('\\' => '/')) . '/' . basename($action['filename'])); Chris@76: } Chris@76: } Chris@76: elseif (in_array($action['type'], array('move-dir', 'move-file'))) Chris@76: $thisAction = array( Chris@76: 'type' => $txt['package_move'] . ' ' . ($action['type'] == 'move-dir' ? $txt['package_tree'] : $txt['package_file']), Chris@76: 'action' => $smcFunc['htmlspecialchars'](strtr($action['source'], array($boarddir => '.'))) . ' => ' . $smcFunc['htmlspecialchars'](strtr($action['destination'], array($boarddir => '.'))) Chris@76: ); Chris@76: elseif (in_array($action['type'], array('remove-dir', 'remove-file'))) Chris@76: { Chris@76: $thisAction = array( Chris@76: 'type' => $txt['package_delete'] . ' ' . ($action['type'] == 'remove-dir' ? $txt['package_tree'] : $txt['package_file']), Chris@76: 'action' => $smcFunc['htmlspecialchars'](strtr($action['filename'], array($boarddir => '.'))) Chris@76: ); Chris@76: Chris@76: // Could this be theme related? Chris@76: if (!empty($action['unparsed_filename']) && preg_match('~^\$(languagedir|languages_dir|imagesdir|themedir|themes_dir)~i', $action['unparsed_filename'], $matches)) Chris@76: { Chris@76: Chris@76: // Is the action already stated? Chris@76: $theme_action = !empty($action['theme_action']) && in_array($action['theme_action'], array('no', 'yes', 'auto')) ? $action['theme_action'] : 'auto'; Chris@76: $action['unparsed_destination'] = $action['unparsed_filename']; Chris@76: // If it's not auto do we think we have something we can act upon? Chris@76: if ($theme_action != 'auto' && !in_array($matches[1], array('languagedir', 'languages_dir', 'imagesdir', 'themedir'))) Chris@76: $theme_action = ''; Chris@76: // ... or if it's auto do we even want to do anything? Chris@76: elseif ($theme_action == 'auto' && $matches[1] != 'imagesdir') Chris@76: $theme_action = ''; Chris@76: Chris@76: // So, we still want to do something? Chris@76: if ($theme_action != '') Chris@76: $themeFinds['candidates'][] = $action; Chris@76: // Otherwise is this is going into another theme record it. Chris@76: elseif ($matches[1] == 'themes_dir') Chris@76: $themeFinds['other_themes'][] = strtolower(strtr(parse_path($action['unparsed_filename']), array('\\' => '/')) . '/' . basename($action['filename'])); Chris@76: } Chris@76: } Chris@76: Chris@76: if (empty($thisAction)) Chris@76: continue; Chris@76: Chris@76: // !!! None given? Chris@76: $thisAction['description'] = isset($action['description']) ? $action['description'] : ''; Chris@76: $context['actions'][] = $thisAction; Chris@76: } Chris@76: Chris@76: // Have we got some things which we might want to do "multi-theme"? Chris@76: if (!empty($themeFinds['candidates'])) Chris@76: { Chris@76: foreach ($themeFinds['candidates'] as $action_data) Chris@76: { Chris@76: // Get the part of the file we'll be dealing with. Chris@76: preg_match('~^\$(languagedir|languages_dir|imagesdir|themedir)(\\|/)*(.+)*~i', $action_data['unparsed_destination'], $matches); Chris@76: Chris@76: if ($matches[1] == 'imagesdir') Chris@76: $path = '/' . basename($settings['default_images_url']); Chris@76: elseif ($matches[1] == 'languagedir' || $matches[1] == 'languages_dir') Chris@76: $path = '/languages'; Chris@76: else Chris@76: $path = ''; Chris@76: Chris@76: if (!empty($matches[3])) Chris@76: $path .= $matches[3]; Chris@76: Chris@76: if (!$context['uninstalling']) Chris@76: $path .= '/' . basename($action_data['filename']); Chris@76: Chris@76: // Loop through each custom theme to note it's candidacy! Chris@76: foreach ($theme_paths as $id => $theme_data) Chris@76: { Chris@76: if (isset($theme_data['theme_dir']) && $id != 1) Chris@76: { Chris@76: $real_path = $theme_data['theme_dir'] . $path; Chris@76: // Confirm that we don't already have this dealt with by another entry. Chris@76: if (!in_array(strtolower(strtr($real_path, array('\\' => '/'))), $themeFinds['other_themes'])) Chris@76: { Chris@76: // Check if we will need to chmod this. Chris@76: if (!mktree(dirname($real_path), false)) Chris@76: { Chris@76: $temp = dirname($real_path); Chris@76: while (!file_exists($temp) && strlen($temp) > 1) Chris@76: $temp = dirname($temp); Chris@76: $chmod_files[] = $temp; Chris@76: } Chris@76: if ($action_data['type'] == 'require-dir' && !is_writable($real_path) && (file_exists($real_path) || !is_writable(dirname($real_path)))) Chris@76: $chmod_files[] = $real_path; Chris@76: Chris@76: if (!isset($context['theme_actions'][$id])) Chris@76: $context['theme_actions'][$id] = array( Chris@76: 'name' => $theme_data['name'], Chris@76: 'actions' => array(), Chris@76: ); Chris@76: Chris@76: if ($context['uninstalling']) Chris@76: $context['theme_actions'][$id]['actions'][] = array( Chris@76: 'type' => $txt['package_delete'] . ' ' . ($action_data['type'] == 'require-dir' ? $txt['package_tree'] : $txt['package_file']), Chris@76: 'action' => strtr($real_path, array('\\' => '/', $boarddir => '.')), Chris@76: 'description' => '', Chris@76: 'value' => base64_encode(serialize(array('type' => $action_data['type'], 'orig' => $action_data['filename'], 'future' => $real_path, 'id' => $id))), Chris@76: 'not_mod' => true, Chris@76: ); Chris@76: else Chris@76: $context['theme_actions'][$id]['actions'][] = array( Chris@76: 'type' => $txt['package_extract'] . ' ' . ($action_data['type'] == 'require-dir' ? $txt['package_tree'] : $txt['package_file']), Chris@76: 'action' => strtr($real_path, array('\\' => '/', $boarddir => '.')), Chris@76: 'description' => '', Chris@76: 'value' => base64_encode(serialize(array('type' => $action_data['type'], 'orig' => $action_data['destination'], 'future' => $real_path, 'id' => $id))), Chris@76: 'not_mod' => true, Chris@76: ); Chris@76: } Chris@76: } Chris@76: } Chris@76: } Chris@76: } Chris@76: Chris@76: // Trash the cache... which will also check permissions for us! Chris@76: package_flush_cache(true); Chris@76: Chris@76: if (file_exists($boarddir . '/Packages/temp')) Chris@76: deltree($boarddir . '/Packages/temp'); Chris@76: Chris@76: if (!empty($chmod_files)) Chris@76: { Chris@76: $ftp_status = create_chmod_control($chmod_files); Chris@76: $context['ftp_needed'] = !empty($ftp_status['files']['notwritable']) && !empty($context['package_ftp']); Chris@76: } Chris@76: Chris@76: checkSubmitOnce('register'); Chris@76: } Chris@76: Chris@76: // Apply another type of (avatar, language, etc.) package. Chris@76: function PackageInstall() Chris@76: { Chris@76: global $boarddir, $txt, $context, $boardurl, $scripturl, $sourcedir, $modSettings; Chris@76: global $user_info, $smcFunc; Chris@76: Chris@76: // Make sure we don't install this mod twice. Chris@76: checkSubmitOnce('check'); Chris@76: checkSession(); Chris@76: Chris@76: // If there's no file, what are we installing? Chris@76: if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '') Chris@76: redirectexit('action=admin;area=packages'); Chris@76: $context['filename'] = $_REQUEST['package']; Chris@76: Chris@76: // If this is an uninstall, we'll have an id. Chris@76: $context['install_id'] = isset($_REQUEST['pid']) ? (int) $_REQUEST['pid'] : 0; Chris@76: Chris@76: require_once($sourcedir . '/Subs-Package.php'); Chris@76: Chris@76: // !!! TODO: Perhaps do it in steps, if necessary? Chris@76: Chris@76: $context['uninstalling'] = $_REQUEST['sa'] == 'uninstall2'; Chris@76: Chris@76: // Set up the linktree for other. Chris@76: $context['linktree'][count($context['linktree']) - 1] = array( Chris@76: 'url' => $scripturl . '?action=admin;area=packages;sa=browse', Chris@76: 'name' => $context['uninstalling'] ? $txt['uninstall'] : $txt['extracting'] Chris@76: ); Chris@76: $context['page_title'] .= ' - ' . ($context['uninstalling'] ? $txt['uninstall'] : $txt['extracting']); Chris@76: Chris@76: $context['sub_template'] = 'extract_package'; Chris@76: Chris@76: if (!file_exists($boarddir . '/Packages/' . $context['filename'])) Chris@76: fatal_lang_error('package_no_file', false); Chris@76: Chris@76: // Load up the package FTP information? Chris@76: create_chmod_control(array(), array('destination_url' => $scripturl . '?action=admin;area=packages;sa=' . $_REQUEST['sa'] . ';package=' . $_REQUEST['package'])); Chris@76: Chris@76: // Make sure temp directory exists and is empty! Chris@76: if (file_exists($boarddir . '/Packages/temp')) Chris@76: deltree($boarddir . '/Packages/temp', false); Chris@76: else Chris@76: mktree($boarddir . '/Packages/temp', 0777); Chris@76: Chris@76: // Let the unpacker do the work. Chris@76: if (is_file($boarddir . '/Packages/' . $context['filename'])) Chris@76: { Chris@76: $context['extracted_files'] = read_tgz_file($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp'); Chris@76: Chris@76: if (!file_exists($boarddir . '/Packages/temp/package-info.xml')) Chris@76: foreach ($context['extracted_files'] as $file) Chris@76: if (basename($file['filename']) == 'package-info.xml') Chris@76: { Chris@76: $context['base_path'] = dirname($file['filename']) . '/'; Chris@76: break; Chris@76: } Chris@76: Chris@76: if (!isset($context['base_path'])) Chris@76: $context['base_path'] = ''; Chris@76: } Chris@76: elseif (is_dir($boarddir . '/Packages/' . $context['filename'])) Chris@76: { Chris@76: copytree($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp'); Chris@76: $context['extracted_files'] = listtree($boarddir . '/Packages/temp'); Chris@76: $context['base_path'] = ''; Chris@76: } Chris@76: else Chris@76: fatal_lang_error('no_access', false); Chris@76: Chris@76: // Are we installing this into any custom themes? Chris@76: $custom_themes = array(1); Chris@76: $known_themes = explode(',', $modSettings['knownThemes']); Chris@76: if (!empty($_POST['custom_theme'])) Chris@76: { Chris@76: foreach ($_POST['custom_theme'] as $tid) Chris@76: if (in_array($tid, $known_themes)) Chris@76: $custom_themes[] = (int) $tid; Chris@76: } Chris@76: Chris@76: // Now load up the paths of the themes that we need to know about. Chris@76: $request = $smcFunc['db_query']('', ' Chris@76: SELECT id_theme, variable, value Chris@76: FROM {db_prefix}themes Chris@76: WHERE id_theme IN ({array_int:custom_themes}) Chris@76: AND variable IN ({string:name}, {string:theme_dir})', Chris@76: array( Chris@76: 'custom_themes' => $custom_themes, Chris@76: 'name' => 'name', Chris@76: 'theme_dir' => 'theme_dir', Chris@76: ) Chris@76: ); Chris@76: $theme_paths = array(); Chris@76: $themes_installed = array(1); Chris@76: while ($row = $smcFunc['db_fetch_assoc']($request)) Chris@76: $theme_paths[$row['id_theme']][$row['variable']] = $row['value']; Chris@76: $smcFunc['db_free_result']($request); Chris@76: Chris@76: // Are there any theme copying that we want to take place? Chris@76: $context['theme_copies'] = array( Chris@76: 'require-file' => array(), Chris@76: 'require-dir' => array(), Chris@76: ); Chris@76: if (!empty($_POST['theme_changes'])) Chris@76: { Chris@76: foreach ($_POST['theme_changes'] as $change) Chris@76: { Chris@76: if (empty($change)) Chris@76: continue; Chris@76: $theme_data = unserialize(base64_decode($change)); Chris@76: if (empty($theme_data['type'])) Chris@76: continue; Chris@76: Chris@76: $themes_installed[] = $theme_data['id']; Chris@76: $context['theme_copies'][$theme_data['type']][$theme_data['orig']][] = $theme_data['future']; Chris@76: } Chris@76: } Chris@76: Chris@76: // Get the package info... Chris@76: $packageInfo = getPackageInfo($context['filename']); Chris@76: if (!is_array($packageInfo)) Chris@76: fatal_lang_error($packageInfo); Chris@76: Chris@76: $packageInfo['filename'] = $context['filename']; Chris@76: Chris@76: // Set the type of extraction... Chris@76: $context['extract_type'] = isset($packageInfo['type']) ? $packageInfo['type'] : 'modification'; Chris@76: Chris@76: // Create a backup file to roll back to! (but if they do this more than once, don't run it a zillion times.) Chris@76: if (!empty($modSettings['package_make_backups']) && (!isset($_SESSION['last_backup_for']) || $_SESSION['last_backup_for'] != $context['filename'] . ($context['uninstalling'] ? '$$' : '$'))) Chris@76: { Chris@76: $_SESSION['last_backup_for'] = $context['filename'] . ($context['uninstalling'] ? '$$' : '$'); Chris@76: // !!! Internationalize this? Chris@76: package_create_backup(($context['uninstalling'] ? 'backup_' : 'before_') . strtok($context['filename'], '.')); Chris@76: } Chris@76: Chris@76: // The mod isn't installed.... unless proven otherwise. Chris@76: $context['is_installed'] = false; Chris@76: Chris@76: // Is it actually installed? Chris@76: $request = $smcFunc['db_query']('', ' Chris@76: SELECT version, themes_installed, db_changes Chris@76: FROM {db_prefix}log_packages Chris@76: WHERE package_id = {string:current_package} Chris@76: AND install_state != {int:not_installed} Chris@76: ORDER BY time_installed DESC Chris@76: LIMIT 1', Chris@76: array( Chris@76: 'not_installed' => 0, Chris@76: 'current_package' => $packageInfo['id'], Chris@76: ) Chris@76: ); Chris@76: while ($row = $smcFunc['db_fetch_assoc']($request)) Chris@76: { Chris@76: $old_themes = explode(',', $row['themes_installed']); Chris@76: $old_version = $row['version']; Chris@76: $db_changes = empty($row['db_changes']) ? array() : unserialize($row['db_changes']); Chris@76: } Chris@76: $smcFunc['db_free_result']($request); Chris@76: Chris@76: // Wait, it's not installed yet! Chris@76: // !!! TODO: Replace with a better error message! Chris@76: if (!isset($old_version) && $context['uninstalling']) Chris@76: { Chris@76: deltree($boarddir . '/Packages/temp'); Chris@76: fatal_error('Hacker?', false); Chris@76: } Chris@76: // Uninstalling? Chris@76: elseif ($context['uninstalling']) Chris@76: { Chris@76: $install_log = parsePackageInfo($packageInfo['xml'], false, 'uninstall'); Chris@76: Chris@76: // Gadzooks! There's no uninstaller at all!? Chris@76: if (empty($install_log)) Chris@76: fatal_lang_error('package_uninstall_cannot', false); Chris@76: Chris@76: // They can only uninstall from what it was originally installed into. Chris@76: foreach ($theme_paths as $id => $data) Chris@76: if ($id != 1 && !in_array($id, $old_themes)) Chris@76: unset($theme_paths[$id]); Chris@76: } Chris@76: elseif (isset($old_version) && $old_version != $packageInfo['version']) Chris@76: { Chris@76: // Look for an upgrade... Chris@76: $install_log = parsePackageInfo($packageInfo['xml'], false, 'upgrade', $old_version); Chris@76: Chris@76: // There was no upgrade.... Chris@76: if (empty($install_log)) Chris@76: $context['is_installed'] = true; Chris@76: else Chris@76: { Chris@76: // Upgrade previous themes only! Chris@76: foreach ($theme_paths as $id => $data) Chris@76: if ($id != 1 && !in_array($id, $old_themes)) Chris@76: unset($theme_paths[$id]); Chris@76: } Chris@76: } Chris@76: elseif (isset($old_version) && $old_version == $packageInfo['version']) Chris@76: $context['is_installed'] = true; Chris@76: Chris@76: if (!isset($old_version) || $context['is_installed']) Chris@76: $install_log = parsePackageInfo($packageInfo['xml'], false, 'install'); Chris@76: Chris@76: $context['install_finished'] = false; Chris@76: Chris@76: // !!! TODO: Make a log of any errors that occurred and output them? Chris@76: Chris@76: if (!empty($install_log)) Chris@76: { Chris@76: $failed_steps = array(); Chris@76: $failed_count = 0; Chris@76: Chris@76: foreach ($install_log as $action) Chris@76: { Chris@76: $failed_count++; Chris@76: Chris@76: if ($action['type'] == 'modification' && !empty($action['filename'])) Chris@76: { Chris@76: if ($action['boardmod']) Chris@76: $mod_actions = parseBoardMod(file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), false, $action['reverse'], $theme_paths); Chris@76: else Chris@76: $mod_actions = parseModification(file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), false, $action['reverse'], $theme_paths); Chris@76: Chris@76: // Any errors worth noting? Chris@76: foreach ($mod_actions as $key => $action) Chris@76: { Chris@76: if ($action['type'] == 'failure') Chris@76: $failed_steps[] = array( Chris@76: 'file' => $action['filename'], Chris@76: 'large_step' => $failed_count, Chris@76: 'sub_step' => $key, Chris@76: 'theme' => 1, Chris@76: ); Chris@76: Chris@76: // Gather the themes we installed into. Chris@76: if (!empty($action['is_custom'])) Chris@76: $themes_installed[] = $action['is_custom']; Chris@76: } Chris@76: } Chris@76: elseif ($action['type'] == 'code' && !empty($action['filename'])) Chris@76: { Chris@76: // This is just here as reference for what is available. Chris@76: global $txt, $boarddir, $sourcedir, $modSettings, $context, $settings, $forum_version, $smcFunc; Chris@76: Chris@76: // Now include the file and be done with it ;). Chris@76: require($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']); Chris@76: } Chris@76: // Only do the database changes on uninstall if requested. Chris@76: elseif ($action['type'] == 'database' && !empty($action['filename']) && (!$context['uninstalling'] || !empty($_POST['do_db_changes']))) Chris@76: { Chris@76: // These can also be there for database changes. Chris@76: global $txt, $boarddir, $sourcedir, $modSettings, $context, $settings, $forum_version, $smcFunc; Chris@76: global $db_package_log; Chris@76: Chris@76: // We'll likely want the package specific database functionality! Chris@76: db_extend('packages'); Chris@76: Chris@76: // Let the file work its magic ;) Chris@76: require($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']); Chris@76: } Chris@76: // Handle a redirect... Chris@76: elseif ($action['type'] == 'redirect' && !empty($action['redirect_url'])) Chris@76: { Chris@76: $context['redirect_url'] = $action['redirect_url']; Chris@76: $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']); Chris@76: $context['redirect_timeout'] = $action['redirect_timeout']; Chris@76: Chris@76: // Parse out a couple of common urls. Chris@76: $urls = array( Chris@76: '$boardurl' => $boardurl, Chris@76: '$scripturl' => $scripturl, Chris@76: '$session_var' => $context['session_var'], Chris@76: '$session_id' => $context['session_id'], Chris@76: ); Chris@76: Chris@76: $context['redirect_url'] = strtr($context['redirect_url'], $urls); Chris@76: } Chris@76: } Chris@76: Chris@76: package_flush_cache(); Chris@76: Chris@76: // First, ensure this change doesn't get removed by putting a stake in the ground (So to speak). Chris@76: package_put_contents($boarddir . '/Packages/installed.list', time()); Chris@76: Chris@76: // See if this is already installed, and change it's state as required. Chris@76: $request = $smcFunc['db_query']('', ' Chris@76: SELECT package_id, install_state, db_changes Chris@76: FROM {db_prefix}log_packages Chris@76: WHERE install_state != {int:not_installed} Chris@76: AND package_id = {string:current_package} Chris@76: ' . ($context['install_id'] ? ' AND id_install = {int:install_id} ' : '') . ' Chris@76: ORDER BY time_installed DESC Chris@76: LIMIT 1', Chris@76: array( Chris@76: 'not_installed' => 0, Chris@76: 'install_id' => $context['install_id'], Chris@76: 'current_package' => $packageInfo['id'], Chris@76: ) Chris@76: ); Chris@76: $is_upgrade = false; Chris@76: while ($row = $smcFunc['db_fetch_assoc']($request)) Chris@76: { Chris@76: // Uninstalling? Chris@76: if ($context['uninstalling']) Chris@76: { Chris@76: $smcFunc['db_query']('', ' Chris@76: UPDATE {db_prefix}log_packages Chris@76: SET install_state = {int:not_installed}, member_removed = {string:member_name}, id_member_removed = {int:current_member}, Chris@76: time_removed = {int:current_time} Chris@76: WHERE package_id = {string:package_id}', Chris@76: array( Chris@76: 'current_member' => $user_info['id'], Chris@76: 'not_installed' => 0, Chris@76: 'current_time' => time(), Chris@76: 'package_id' => $row['package_id'], Chris@76: 'member_name' => $user_info['name'], Chris@76: ) Chris@76: ); Chris@76: } Chris@76: // Otherwise must be an upgrade. Chris@76: else Chris@76: { Chris@76: $is_upgrade = true; Chris@76: $old_db_changes = empty($row['db_changes']) ? array() : unserialize($row['db_changes']); Chris@76: } Chris@76: } Chris@76: Chris@76: // Assuming we're not uninstalling, add the entry. Chris@76: if (!$context['uninstalling']) Chris@76: { Chris@76: // Any db changes from older version? Chris@76: if (!empty($old_db_changes)) Chris@76: $db_package_log = empty($db_package_log) ? $old_db_changes : array_merge($old_db_changes, $db_package_log); Chris@76: Chris@76: // If there are some database changes we might want to remove then filter them out. Chris@76: if (!empty($db_package_log)) Chris@76: { Chris@76: // We're really just checking for entries which are create table AND add columns (etc). Chris@76: $tables = array(); Chris@76: function sort_table_first($a, $b) Chris@76: { Chris@76: if ($a[0] == $b[0]) Chris@76: return 0; Chris@76: return $a[0] == 'remove_table' ? -1 : 1; Chris@76: } Chris@76: usort($db_package_log, 'sort_table_first'); Chris@76: foreach ($db_package_log as $k => $log) Chris@76: { Chris@76: if ($log[0] == 'remove_table') Chris@76: $tables[] = $log[1]; Chris@76: elseif (in_array($log[1], $tables)) Chris@76: unset($db_package_log[$k]); Chris@76: } Chris@76: $db_changes = serialize($db_package_log); Chris@76: } Chris@76: else Chris@76: $db_changes = ''; Chris@76: Chris@76: // What themes did we actually install? Chris@76: $themes_installed = array_unique($themes_installed); Chris@76: $themes_installed = implode(',', $themes_installed); Chris@76: Chris@76: // What failed steps? Chris@76: $failed_step_insert = serialize($failed_steps); Chris@76: Chris@76: $smcFunc['db_insert']('', Chris@76: '{db_prefix}log_packages', Chris@76: array( Chris@76: 'filename' => 'string', 'name' => 'string', 'package_id' => 'string', 'version' => 'string', Chris@76: 'id_member_installed' => 'int', 'member_installed' => 'string','time_installed' => 'int', Chris@76: 'install_state' => 'int', 'failed_steps' => 'string', 'themes_installed' => 'string', Chris@76: 'member_removed' => 'int', 'db_changes' => 'string', Chris@76: ), Chris@76: array( Chris@76: $packageInfo['filename'], $packageInfo['name'], $packageInfo['id'], $packageInfo['version'], Chris@76: $user_info['id'], $user_info['name'], time(), Chris@76: $is_upgrade ? 2 : 1, $failed_step_insert, $themes_installed, Chris@76: 0, $db_changes, Chris@76: ), Chris@76: array('id_install') Chris@76: ); Chris@76: } Chris@76: $smcFunc['db_free_result']($request); Chris@76: Chris@76: $context['install_finished'] = true; Chris@76: } Chris@76: Chris@76: // If there's database changes - and they want them removed - let's do it last! Chris@76: if (!empty($db_changes) && !empty($_POST['do_db_changes'])) Chris@76: { Chris@76: // We're gonna be needing the package db functions! Chris@76: db_extend('packages'); Chris@76: Chris@76: foreach ($db_changes as $change) Chris@76: { Chris@76: if ($change[0] == 'remove_table' && isset($change[1])) Chris@76: $smcFunc['db_drop_table']($change[1]); Chris@76: elseif ($change[0] == 'remove_column' && isset($change[2])) Chris@76: $smcFunc['db_remove_column']($change[1], $change[2]); Chris@76: elseif ($change[0] == 'remove_index' && isset($change[2])) Chris@76: $smcFunc['db_remove_index']($change[1], $change[2]); Chris@76: } Chris@76: } Chris@76: Chris@76: // Clean house... get rid of the evidence ;). Chris@76: if (file_exists($boarddir . '/Packages/temp')) Chris@76: deltree($boarddir . '/Packages/temp'); Chris@76: Chris@76: // Log what we just did. Chris@76: logAction($context['uninstalling'] ? 'uninstall_package' : (!empty($is_upgrade) ? 'upgrade_package' : 'install_package'), array('package' => $smcFunc['htmlspecialchars']($packageInfo['name']), 'version' => $smcFunc['htmlspecialchars']($packageInfo['version'])), 'admin'); Chris@76: Chris@76: // Just in case, let's clear the whole cache to avoid anything going up the swanny. Chris@76: clean_cache(); Chris@76: Chris@76: // Restore file permissions? Chris@76: create_chmod_control(array(), array(), true); Chris@76: } Chris@76: Chris@76: // List the files in a package. Chris@76: function PackageList() Chris@76: { Chris@76: global $txt, $scripturl, $boarddir, $context, $sourcedir; Chris@76: Chris@76: require_once($sourcedir . '/Subs-Package.php'); Chris@76: Chris@76: // No package? Show him or her the door. Chris@76: if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '') Chris@76: redirectexit('action=admin;area=packages'); Chris@76: Chris@76: $context['linktree'][] = array( Chris@76: 'url' => $scripturl . '?action=admin;area=packages;sa=list;package=' . $_REQUEST['package'], Chris@76: 'name' => $txt['list_file'] Chris@76: ); Chris@76: $context['page_title'] .= ' - ' . $txt['list_file']; Chris@76: $context['sub_template'] = 'list'; Chris@76: Chris@76: // The filename... Chris@76: $context['filename'] = $_REQUEST['package']; Chris@76: Chris@76: // Let the unpacker do the work. Chris@76: if (is_file($boarddir . '/Packages/' . $context['filename'])) Chris@76: $context['files'] = read_tgz_file($boarddir . '/Packages/' . $context['filename'], null); Chris@76: elseif (is_dir($boarddir . '/Packages/' . $context['filename'])) Chris@76: $context['files'] = listtree($boarddir . '/Packages/' . $context['filename']); Chris@76: } Chris@76: Chris@76: // List the files in a package. Chris@76: function ExamineFile() Chris@76: { Chris@76: global $txt, $scripturl, $boarddir, $context, $sourcedir; Chris@76: Chris@76: require_once($sourcedir . '/Subs-Package.php'); Chris@76: Chris@76: // No package? Show him or her the door. Chris@76: if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '') Chris@76: redirectexit('action=admin;area=packages'); Chris@76: Chris@76: // No file? Show him or her the door. Chris@76: if (!isset($_REQUEST['file']) || $_REQUEST['file'] == '') Chris@76: redirectexit('action=admin;area=packages'); Chris@76: Chris@76: $_REQUEST['package'] = preg_replace('~[\.]+~', '.', strtr($_REQUEST['package'], array('/' => '_', '\\' => '_'))); Chris@76: $_REQUEST['file'] = preg_replace('~[\.]+~', '.', $_REQUEST['file']); Chris@76: Chris@76: if (isset($_REQUEST['raw'])) Chris@76: { Chris@76: if (is_file($boarddir . '/Packages/' . $_REQUEST['package'])) Chris@76: echo read_tgz_file($boarddir . '/Packages/' . $_REQUEST['package'], $_REQUEST['file'], true); Chris@76: elseif (is_dir($boarddir . '/Packages/' . $_REQUEST['package'])) Chris@76: echo file_get_contents($boarddir . '/Packages/' . $_REQUEST['package'] . '/' . $_REQUEST['file']); Chris@76: Chris@76: obExit(false); Chris@76: } Chris@76: Chris@76: $context['linktree'][count($context['linktree']) - 1] = array( Chris@76: 'url' => $scripturl . '?action=admin;area=packages;sa=list;package=' . $_REQUEST['package'], Chris@76: 'name' => $txt['package_examine_file'] Chris@76: ); Chris@76: $context['page_title'] .= ' - ' . $txt['package_examine_file']; Chris@76: $context['sub_template'] = 'examine'; Chris@76: Chris@76: // The filename... Chris@76: $context['package'] = $_REQUEST['package']; Chris@76: $context['filename'] = $_REQUEST['file']; Chris@76: Chris@76: // Let the unpacker do the work.... but make sure we handle images properly. Chris@76: if (in_array(strtolower(strrchr($_REQUEST['file'], '.')), array('.bmp', '.gif', '.jpeg', '.jpg', '.png'))) Chris@76: $context['filedata'] = '' . $_REQUEST['file'] . ''; Chris@76: else Chris@76: { Chris@76: if (is_file($boarddir . '/Packages/' . $_REQUEST['package'])) Chris@76: $context['filedata'] = htmlspecialchars(read_tgz_file($boarddir . '/Packages/' . $_REQUEST['package'], $_REQUEST['file'], true)); Chris@76: elseif (is_dir($boarddir . '/Packages/' . $_REQUEST['package'])) Chris@76: $context['filedata'] = htmlspecialchars(file_get_contents($boarddir . '/Packages/' . $_REQUEST['package'] . '/' . $_REQUEST['file'])); Chris@76: Chris@76: if (strtolower(strrchr($_REQUEST['file'], '.')) == '.php') Chris@76: $context['filedata'] = highlight_php_code($context['filedata']); Chris@76: } Chris@76: } Chris@76: Chris@76: // List the installed packages. Chris@76: function InstalledList() Chris@76: { Chris@76: global $txt, $scripturl, $context; Chris@76: Chris@76: $context['page_title'] .= ' - ' . $txt['installed_packages']; Chris@76: $context['sub_template'] = 'view_installed'; Chris@76: Chris@76: // Load the installed mods and send them to the template. Chris@76: $context['installed_mods'] = loadInstalledPackages(); Chris@76: } Chris@76: Chris@76: // Empty out the installed list. Chris@76: function FlushInstall() Chris@76: { Chris@76: global $boarddir, $sourcedir, $smcFunc; Chris@76: Chris@76: // Always check the session. Chris@76: checkSession('get'); Chris@76: Chris@76: include_once($sourcedir . '/Subs-Package.php'); Chris@76: Chris@76: // Record when we last did this. Chris@76: package_put_contents($boarddir . '/Packages/installed.list', time()); Chris@76: Chris@76: // Set everything as uninstalled. Chris@76: $smcFunc['db_query']('', ' Chris@76: UPDATE {db_prefix}log_packages Chris@76: SET install_state = {int:not_installed}', Chris@76: array( Chris@76: 'not_installed' => 0, Chris@76: ) Chris@76: ); Chris@76: Chris@76: redirectexit('action=admin;area=packages;sa=installed'); Chris@76: } Chris@76: Chris@76: // Delete a package. Chris@76: function PackageRemove() Chris@76: { Chris@76: global $scripturl, $boarddir; Chris@76: Chris@76: // Check it. Chris@76: checkSession('get'); Chris@76: Chris@76: // Ack, don't allow deletion of arbitrary files here, could become a security hole somehow! Chris@76: if (!isset($_GET['package']) || $_GET['package'] == 'index.php' || $_GET['package'] == 'installed.list' || $_GET['package'] == 'backups') Chris@76: redirectexit('action=admin;area=packages;sa=browse'); Chris@76: $_GET['package'] = preg_replace('~[\.]+~', '.', strtr($_GET['package'], array('/' => '_', '\\' => '_'))); Chris@76: Chris@76: // Can't delete what's not there. Chris@76: if (file_exists($boarddir . '/Packages/' . $_GET['package']) && (substr($_GET['package'], -4) == '.zip' || substr($_GET['package'], -4) == '.tgz' || substr($_GET['package'], -7) == '.tar.gz' || is_dir($boarddir . '/Packages/' . $_GET['package'])) && $_GET['package'] != 'backups' && substr($_GET['package'], 0, 1) != '.') Chris@76: { Chris@76: create_chmod_control(array($boarddir . '/Packages/' . $_GET['package']), array('destination_url' => $scripturl . '?action=admin;area=packages;sa=remove;package=' . $_GET['package'], 'crash_on_error' => true)); Chris@76: Chris@76: if (is_dir($boarddir . '/Packages/' . $_GET['package'])) Chris@76: deltree($boarddir . '/Packages/' . $_GET['package']); Chris@76: else Chris@76: { Chris@76: @chmod($boarddir . '/Packages/' . $_GET['package'], 0777); Chris@76: unlink($boarddir . '/Packages/' . $_GET['package']); Chris@76: } Chris@76: } Chris@76: Chris@76: redirectexit('action=admin;area=packages;sa=browse'); Chris@76: } Chris@76: Chris@76: // Browse a list of installed packages. Chris@76: function PackageBrowse() Chris@76: { Chris@76: global $txt, $boarddir, $scripturl, $context, $forum_version; Chris@76: Chris@76: $context['page_title'] .= ' - ' . $txt['browse_packages']; Chris@76: $context['sub_template'] = 'browse'; Chris@76: Chris@76: $context['forum_version'] = $forum_version; Chris@76: Chris@76: $instmods = loadInstalledPackages(); Chris@76: Chris@76: $installed_mods = array(); Chris@76: // Look through the list of installed mods... Chris@76: foreach ($instmods as $installed_mod) Chris@76: $installed_mods[$installed_mod['package_id']] = array( Chris@76: 'id' => $installed_mod['id'], Chris@76: 'version' => $installed_mod['version'], Chris@76: ); Chris@76: Chris@76: $the_version = strtr($forum_version, array('SMF ' => '')); Chris@76: Chris@76: // Here we have a little code to help those who class themselves as something of gods, version emulation ;) Chris@76: if (isset($_GET['version_emulate'])) Chris@76: { Chris@76: if ($_GET['version_emulate'] === 0 && isset($_SESSION['version_emulate'])) Chris@76: unset($_SESSION['version_emulate']); Chris@76: elseif ($_GET['version_emulate'] !== 0) Chris@76: $_SESSION['version_emulate'] = strtr($_GET['version_emulate'], array('-' => ' ', '+' => ' ', 'SMF ' => '')); Chris@76: } Chris@76: if (!empty($_SESSION['version_emulate'])) Chris@76: { Chris@76: $context['forum_version'] = 'SMF ' . $_SESSION['version_emulate']; Chris@76: $the_version = $_SESSION['version_emulate']; Chris@76: } Chris@76: Chris@76: // Get a list of all the ids installed, so the latest packages won't include already installed ones. Chris@76: $context['installed_mods'] = array_keys($installed_mods); Chris@76: Chris@76: // Empty lists for now. Chris@76: $context['available_mods'] = array(); Chris@76: $context['available_avatars'] = array(); Chris@76: $context['available_languages'] = array(); Chris@76: $context['available_other'] = array(); Chris@76: $context['available_all'] = array(); Chris@76: Chris@76: // We need the packages directory to be writable for this. Chris@76: if (!@is_writable($boarddir . '/Packages')) Chris@76: create_chmod_control(array($boarddir . '/Packages'), array('destination_url' => $scripturl . '?action=admin;area=packages', 'crash_on_error' => true)); Chris@76: Chris@76: if ($dir = @opendir($boarddir . '/Packages')) Chris@76: { Chris@76: $dirs = array(); Chris@76: while ($package = readdir($dir)) Chris@76: { Chris@76: if ($package == '.' || $package == '..' || $package == 'temp' || (!(is_dir($boarddir . '/Packages/' . $package) && file_exists($boarddir . '/Packages/' . $package . '/package-info.xml')) && substr(strtolower($package), -7) != '.tar.gz' && substr(strtolower($package), -4) != '.tgz' && substr(strtolower($package), -4) != '.zip')) Chris@76: continue; Chris@76: Chris@76: // Skip directories or files that are named the same. Chris@76: if (is_dir($boarddir . '/Packages/' . $package)) Chris@76: { Chris@76: if (in_array($package, $dirs)) Chris@76: continue; Chris@76: $dirs[] = $package; Chris@76: } Chris@76: elseif (substr(strtolower($package), -7) == '.tar.gz') Chris@76: { Chris@76: if (in_array(substr($package, 0, -7), $dirs)) Chris@76: continue; Chris@76: $dirs[] = substr($package, 0, -7); Chris@76: } Chris@76: elseif (substr(strtolower($package), -4) == '.zip' || substr(strtolower($package), -4) == '.tgz') Chris@76: { Chris@76: if (in_array(substr($package, 0, -4), $dirs)) Chris@76: continue; Chris@76: $dirs[] = substr($package, 0, -4); Chris@76: } Chris@76: Chris@76: $packageInfo = getPackageInfo($package); Chris@76: if (!is_array($packageInfo)) Chris@76: continue; Chris@76: Chris@76: $packageInfo['installed_id'] = isset($installed_mods[$packageInfo['id']]) ? $installed_mods[$packageInfo['id']]['id'] : 0; Chris@76: Chris@76: $packageInfo['is_installed'] = isset($installed_mods[$packageInfo['id']]); Chris@76: $packageInfo['is_current'] = $packageInfo['is_installed'] && ($installed_mods[$packageInfo['id']]['version'] == $packageInfo['version']); Chris@76: $packageInfo['is_newer'] = $packageInfo['is_installed'] && ($installed_mods[$packageInfo['id']]['version'] > $packageInfo['version']); Chris@76: Chris@76: $packageInfo['can_install'] = false; Chris@76: $packageInfo['can_uninstall'] = false; Chris@76: $packageInfo['can_upgrade'] = false; Chris@76: Chris@76: // This package is currently NOT installed. Check if it can be. Chris@76: if (!$packageInfo['is_installed'] && $packageInfo['xml']->exists('install')) Chris@76: { Chris@76: // Check if there's an install for *THIS* version of SMF. Chris@76: $installs = $packageInfo['xml']->set('install'); Chris@76: foreach ($installs as $install) Chris@76: { Chris@76: if (!$install->exists('@for') || matchPackageVersion($the_version, $install->fetch('@for'))) Chris@76: { Chris@76: // Okay, this one is good to go. Chris@76: $packageInfo['can_install'] = true; Chris@76: break; Chris@76: } Chris@76: } Chris@76: } Chris@76: // An already installed, but old, package. Can we upgrade it? Chris@76: elseif ($packageInfo['is_installed'] && !$packageInfo['is_current'] && $packageInfo['xml']->exists('upgrade')) Chris@76: { Chris@76: $upgrades = $packageInfo['xml']->set('upgrade'); Chris@76: Chris@76: // First go through, and check against the current version of SMF. Chris@76: foreach ($upgrades as $upgrade) Chris@76: { Chris@76: // Even if it is for this SMF, is it for the installed version of the mod? Chris@76: if (!$upgrade->exists('@for') || matchPackageVersion($the_version, $upgrade->fetch('@for'))) Chris@76: if (!$upgrade->exists('@from') || matchPackageVersion($installed_mods[$packageInfo['id']]['version'], $upgrade->fetch('@from'))) Chris@76: { Chris@76: $packageInfo['can_upgrade'] = true; Chris@76: break; Chris@76: } Chris@76: } Chris@76: } Chris@76: // Note that it has to be the current version to be uninstallable. Shucks. Chris@76: elseif ($packageInfo['is_installed'] && $packageInfo['is_current'] && $packageInfo['xml']->exists('uninstall')) Chris@76: { Chris@76: $uninstalls = $packageInfo['xml']->set('uninstall'); Chris@76: Chris@76: // Can we find any uninstallation methods that work for this SMF version? Chris@76: foreach ($uninstalls as $uninstall) Chris@76: if (!$uninstall->exists('@for') || matchPackageVersion($the_version, $uninstall->fetch('@for'))) Chris@76: { Chris@76: $packageInfo['can_uninstall'] = true; Chris@76: break; Chris@76: } Chris@76: } Chris@76: Chris@76: // Store a complete list. Chris@76: $context['available_all'][] = $packageInfo; Chris@76: Chris@76: // Modification. Chris@76: if ($packageInfo['type'] == 'modification' || $packageInfo['type'] == 'mod') Chris@76: $context['available_mods'][] = $packageInfo; Chris@76: // Avatar package. Chris@76: elseif ($packageInfo['type'] == 'avatar') Chris@76: $context['available_avatars'][] = $packageInfo; Chris@76: // Language package. Chris@76: elseif ($packageInfo['type'] == 'language') Chris@76: $context['available_languages'][] = $packageInfo; Chris@76: // Other stuff. Chris@76: else Chris@76: $context['available_other'][] = $packageInfo; Chris@76: } Chris@76: closedir($dir); Chris@76: } Chris@76: } Chris@76: Chris@76: function PackageOptions() Chris@76: { Chris@76: global $txt, $scripturl, $context, $sourcedir, $modSettings, $smcFunc; Chris@76: Chris@76: if (isset($_POST['submit'])) Chris@76: { Chris@76: checkSession('post'); Chris@76: Chris@76: updateSettings(array( Chris@76: 'package_server' => trim($smcFunc['htmlspecialchars']($_POST['pack_server'])), Chris@76: 'package_port' => trim($smcFunc['htmlspecialchars']($_POST['pack_port'])), Chris@76: 'package_username' => trim($smcFunc['htmlspecialchars']($_POST['pack_user'])), Chris@76: 'package_make_backups' => !empty($_POST['package_make_backups']) Chris@76: )); Chris@76: Chris@76: redirectexit('action=admin;area=packages;sa=options'); Chris@76: } Chris@76: Chris@76: if (preg_match('~^/home/([^/]+?)/public_html~', $_SERVER['DOCUMENT_ROOT'], $match)) Chris@76: $default_username = $match[1]; Chris@76: else Chris@76: $default_username = ''; Chris@76: Chris@76: $context['page_title'] = $txt['package_settings']; Chris@76: $context['sub_template'] = 'install_options'; Chris@76: Chris@76: $context['package_ftp_server'] = isset($modSettings['package_server']) ? $modSettings['package_server'] : 'localhost'; Chris@76: $context['package_ftp_port'] = isset($modSettings['package_port']) ? $modSettings['package_port'] : '21'; Chris@76: $context['package_ftp_username'] = isset($modSettings['package_username']) ? $modSettings['package_username'] : $default_username; Chris@76: $context['package_make_backups'] = !empty($modSettings['package_make_backups']); Chris@76: } Chris@76: Chris@76: function ViewOperations() Chris@76: { Chris@76: global $context, $txt, $boarddir, $sourcedir, $smcFunc, $modSettings; Chris@76: Chris@76: // Can't be in here buddy. Chris@76: isAllowedTo('admin_forum'); Chris@76: Chris@76: // We need to know the operation key for the search and replace, mod file looking at, is it a board mod? Chris@76: if (!isset($_REQUEST['operation_key'], $_REQUEST['filename']) && !is_numeric($_REQUEST['operation_key'])) Chris@76: fatal_lang_error('operation_invalid', 'general'); Chris@76: Chris@76: // Load the required file. Chris@76: require_once($sourcedir . '/Subs-Package.php'); Chris@76: Chris@76: // Uninstalling the mod? Chris@76: $reverse = isset($_REQUEST['reverse']) ? true : false; Chris@76: Chris@76: // Get the base name. Chris@76: $context['filename'] = preg_replace('~[\.]+~', '.', $_REQUEST['package']); Chris@76: Chris@76: // We need to extract this again. Chris@76: if (is_file($boarddir . '/Packages/' . $context['filename'])) Chris@76: { Chris@76: $context['extracted_files'] = read_tgz_file($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp'); Chris@76: Chris@76: if ($context['extracted_files'] && !file_exists($boarddir . '/Packages/temp/package-info.xml')) Chris@76: foreach ($context['extracted_files'] as $file) Chris@76: if (basename($file['filename']) == 'package-info.xml') Chris@76: { Chris@76: $context['base_path'] = dirname($file['filename']) . '/'; Chris@76: break; Chris@76: } Chris@76: Chris@76: if (!isset($context['base_path'])) Chris@76: $context['base_path'] = ''; Chris@76: } Chris@76: elseif (is_dir($boarddir . '/Packages/' . $context['filename'])) Chris@76: { Chris@76: copytree($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp'); Chris@76: $context['extracted_files'] = listtree($boarddir . '/Packages/temp'); Chris@76: $context['base_path'] = ''; Chris@76: } Chris@76: Chris@76: // Load up any custom themes we may want to install into... Chris@76: $request = $smcFunc['db_query']('', ' Chris@76: SELECT id_theme, variable, value Chris@76: FROM {db_prefix}themes Chris@76: WHERE (id_theme = {int:default_theme} OR id_theme IN ({array_int:known_theme_list})) Chris@76: AND variable IN ({string:name}, {string:theme_dir})', Chris@76: array( Chris@76: 'known_theme_list' => explode(',', $modSettings['knownThemes']), Chris@76: 'default_theme' => 1, Chris@76: 'name' => 'name', Chris@76: 'theme_dir' => 'theme_dir', Chris@76: ) Chris@76: ); Chris@76: $theme_paths = array(); Chris@76: while ($row = $smcFunc['db_fetch_assoc']($request)) Chris@76: $theme_paths[$row['id_theme']][$row['variable']] = $row['value']; Chris@76: $smcFunc['db_free_result']($request); Chris@76: Chris@76: // Boardmod? Chris@76: if (isset($_REQUEST['boardmod'])) Chris@76: $mod_actions = parseBoardMod(@file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $_REQUEST['filename']), true, $reverse, $theme_paths); Chris@76: else Chris@76: $mod_actions = parseModification(@file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $_REQUEST['filename']), true, $reverse, $theme_paths); Chris@76: Chris@76: // Ok lets get the content of the file. Chris@76: $context['operations'] = array( Chris@76: 'search' => strtr(htmlspecialchars($mod_actions[$_REQUEST['operation_key']]['search_original']), array('[' => '[', ']' => ']')), Chris@76: 'replace' => strtr(htmlspecialchars($mod_actions[$_REQUEST['operation_key']]['replace_original']), array('[' => '[', ']' => ']')), Chris@76: 'position' => $mod_actions[$_REQUEST['operation_key']]['position'], Chris@76: ); Chris@76: Chris@76: // Let's do some formatting... Chris@76: $operation_text = $context['operations']['position'] == 'replace' ? 'operation_replace' : ($context['operations']['position'] == 'before' ? 'operation_after' : 'operation_before'); Chris@76: $context['operations']['search'] = parse_bbc('[code=' . $txt['operation_find'] . ']' . ($context['operations']['position'] == 'end' ? '?>' : $context['operations']['search']) . '[/code]'); Chris@76: $context['operations']['replace'] = parse_bbc('[code=' . $txt[$operation_text] . ']' . $context['operations']['replace'] . '[/code]'); Chris@76: Chris@76: // No layers Chris@76: $context['template_layers'] = array(); Chris@76: $context['sub_template'] = 'view_operations'; Chris@76: } Chris@76: Chris@76: // Allow the admin to reset permissions on files. Chris@76: function PackagePermissions() Chris@76: { Chris@76: global $context, $txt, $modSettings, $boarddir, $sourcedir, $cachedir, $smcFunc, $package_ftp; Chris@76: Chris@76: // Let's try and be good, yes? Chris@76: checkSession('get'); Chris@76: Chris@76: // If we're restoring permissions this is just a pass through really. Chris@76: if (isset($_GET['restore'])) Chris@76: { Chris@76: create_chmod_control(array(), array(), true); Chris@76: fatal_lang_error('no_access', false); Chris@76: } Chris@76: Chris@76: // This is a memory eat. Chris@76: @ini_set('memory_limit', '128M'); Chris@76: @set_time_limit(600); Chris@76: Chris@76: // Load up some FTP stuff. Chris@76: create_chmod_control(); Chris@76: Chris@76: if (empty($package_ftp) && !isset($_POST['skip_ftp'])) Chris@76: { Chris@76: loadClassFile('Class-Package.php'); Chris@76: $ftp = new ftp_connection(null); Chris@76: list ($username, $detect_path, $found_path) = $ftp->detect_path($boarddir); Chris@76: Chris@76: $context['package_ftp'] = array( Chris@76: 'server' => isset($modSettings['package_server']) ? $modSettings['package_server'] : 'localhost', Chris@76: 'port' => isset($modSettings['package_port']) ? $modSettings['package_port'] : '21', Chris@76: 'username' => empty($username) ? (isset($modSettings['package_username']) ? $modSettings['package_username'] : '') : $username, Chris@76: 'path' => $detect_path, Chris@76: 'form_elements_only' => true, Chris@76: ); Chris@76: } Chris@76: else Chris@76: $context['ftp_connected'] = true; Chris@76: Chris@76: // Define the template. Chris@76: $context['page_title'] = $txt['package_file_perms']; Chris@76: $context['sub_template'] = 'file_permissions'; Chris@76: Chris@76: // Define what files we're interested in, as a tree. Chris@76: $context['file_tree'] = array( Chris@76: strtr($boarddir, array('\\' => '/')) => array( Chris@76: 'type' => 'dir', Chris@76: 'contents' => array( Chris@76: 'agreement.txt' => array( Chris@76: 'type' => 'file', Chris@76: 'writable_on' => 'standard', Chris@76: ), Chris@76: 'Settings.php' => array( Chris@76: 'type' => 'file', Chris@76: 'writable_on' => 'restrictive', Chris@76: ), Chris@76: 'Settings_bak.php' => array( Chris@76: 'type' => 'file', Chris@76: 'writable_on' => 'restrictive', Chris@76: ), Chris@76: 'attachments' => array( Chris@76: 'type' => 'dir', Chris@76: 'writable_on' => 'restrictive', Chris@76: ), Chris@76: 'avatars' => array( Chris@76: 'type' => 'dir', Chris@76: 'writable_on' => 'standard', Chris@76: ), Chris@76: 'cache' => array( Chris@76: 'type' => 'dir', Chris@76: 'writable_on' => 'restrictive', Chris@76: ), Chris@76: 'custom_avatar_dir' => array( Chris@76: 'type' => 'dir', Chris@76: 'writable_on' => 'restrictive', Chris@76: ), Chris@76: 'Smileys' => array( Chris@76: 'type' => 'dir_recursive', Chris@76: 'writable_on' => 'standard', Chris@76: ), Chris@76: 'Sources' => array( Chris@76: 'type' => 'dir', Chris@76: 'list_contents' => true, Chris@76: 'writable_on' => 'standard', Chris@76: ), Chris@76: 'Themes' => array( Chris@76: 'type' => 'dir_recursive', Chris@76: 'writable_on' => 'standard', Chris@76: 'contents' => array( Chris@76: 'default' => array( Chris@76: 'type' => 'dir_recursive', Chris@76: 'list_contents' => true, Chris@76: 'contents' => array( Chris@76: 'languages' => array( Chris@76: 'type' => 'dir', Chris@76: 'list_contents' => true, Chris@76: ), Chris@76: ), Chris@76: ), Chris@76: ), Chris@76: ), Chris@76: 'Packages' => array( Chris@76: 'type' => 'dir', Chris@76: 'writable_on' => 'standard', Chris@76: 'contents' => array( Chris@76: 'temp' => array( Chris@76: 'type' => 'dir', Chris@76: ), Chris@76: 'backup' => array( Chris@76: 'type' => 'dir', Chris@76: ), Chris@76: 'installed.list' => array( Chris@76: 'type' => 'file', Chris@76: 'writable_on' => 'standard', Chris@76: ), Chris@76: ), Chris@76: ), Chris@76: ), Chris@76: ), Chris@76: ); Chris@76: Chris@76: // Directories that can move. Chris@76: if (substr($sourcedir, 0, strlen($boarddir)) != $boarddir) Chris@76: { Chris@76: unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['Sources']); Chris@76: $context['file_tree'][strtr($sourcedir, array('\\' => '/'))] = array( Chris@76: 'type' => 'dir', Chris@76: 'list_contents' => true, Chris@76: 'writable_on' => 'standard', Chris@76: ); Chris@76: } Chris@76: Chris@76: // Moved the cache? Chris@76: if (substr($cachedir, 0, strlen($boarddir)) != $boarddir) Chris@76: { Chris@76: unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['cache']); Chris@76: $context['file_tree'][strtr($cachedir, array('\\' => '/'))] = array( Chris@76: 'type' => 'dir', Chris@76: 'list_contents' => false, Chris@76: 'writable_on' => 'restrictive', Chris@76: ); Chris@76: } Chris@76: Chris@76: // Are we using multiple attachment directories? Chris@76: if (!empty($modSettings['currentAttachmentUploadDir'])) Chris@76: { Chris@76: unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['attachments']); Chris@76: Chris@76: if (!is_array($modSettings['attachmentUploadDir'])) Chris@76: $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']); Chris@76: Chris@76: // !!! Should we suggest non-current directories be read only? Chris@76: foreach ($modSettings['attachmentUploadDir'] as $dir) Chris@76: $context['file_tree'][strtr($dir, array('\\' => '/'))] = array( Chris@76: 'type' => 'dir', Chris@76: 'writable_on' => 'restrictive', Chris@76: ); Chris@76: Chris@76: } Chris@76: elseif (substr($modSettings['attachmentUploadDir'], 0, strlen($boarddir)) != $boarddir) Chris@76: { Chris@76: unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['attachments']); Chris@76: $context['file_tree'][strtr($modSettings['attachmentUploadDir'], array('\\' => '/'))] = array( Chris@76: 'type' => 'dir', Chris@76: 'writable_on' => 'restrictive', Chris@76: ); Chris@76: } Chris@76: Chris@76: if (substr($modSettings['smileys_dir'], 0, strlen($boarddir)) != $boarddir) Chris@76: { Chris@76: unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['Smileys']); Chris@76: $context['file_tree'][strtr($modSettings['smileys_dir'], array('\\' => '/'))] = array( Chris@76: 'type' => 'dir_recursive', Chris@76: 'writable_on' => 'standard', Chris@76: ); Chris@76: } Chris@76: if (substr($modSettings['avatar_directory'], 0, strlen($boarddir)) != $boarddir) Chris@76: { Chris@76: unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['avatars']); Chris@76: $context['file_tree'][strtr($modSettings['avatar_directory'], array('\\' => '/'))] = array( Chris@76: 'type' => 'dir', Chris@76: 'writable_on' => 'standard', Chris@76: ); Chris@76: } Chris@76: if (isset($modSettings['custom_avatar_dir']) && substr($modSettings['custom_avatar_dir'], 0, strlen($boarddir)) != $boarddir) Chris@76: { Chris@76: unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['custom_avatar_dir']); Chris@76: $context['file_tree'][strtr($modSettings['custom_avatar_dir'], array('\\' => '/'))] = array( Chris@76: 'type' => 'dir', Chris@76: 'writable_on' => 'restrictive', Chris@76: ); Chris@76: } Chris@76: Chris@76: // Load up any custom themes. Chris@76: $request = $smcFunc['db_query']('', ' Chris@76: SELECT value Chris@76: FROM {db_prefix}themes Chris@76: WHERE id_theme > {int:default_theme_id} Chris@76: AND id_member = {int:guest_id} Chris@76: AND variable = {string:theme_dir} Chris@76: ORDER BY value ASC', Chris@76: array( Chris@76: 'default_theme_id' => 1, Chris@76: 'guest_id' => 0, Chris@76: 'theme_dir' => 'theme_dir', Chris@76: ) Chris@76: ); Chris@76: while ($row = $smcFunc['db_fetch_assoc']($request)) Chris@76: { Chris@76: if (substr(strtolower(strtr($row['value'], array('\\' => '/'))), 0, strlen($boarddir) + 7) == strtolower(strtr($boarddir, array('\\' => '/')) . '/Themes')) Chris@76: $context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['Themes']['contents'][substr($row['value'], strlen($boarddir) + 8)] = array( Chris@76: 'type' => 'dir_recursive', Chris@76: 'list_contents' => true, Chris@76: 'contents' => array( Chris@76: 'languages' => array( Chris@76: 'type' => 'dir', Chris@76: 'list_contents' => true, Chris@76: ), Chris@76: ), Chris@76: ); Chris@76: else Chris@76: { Chris@76: $context['file_tree'][strtr($row['value'], array('\\' => '/'))] = array( Chris@76: 'type' => 'dir_recursive', Chris@76: 'list_contents' => true, Chris@76: 'contents' => array( Chris@76: 'languages' => array( Chris@76: 'type' => 'dir', Chris@76: 'list_contents' => true, Chris@76: ), Chris@76: ), Chris@76: ); Chris@76: } Chris@76: } Chris@76: $smcFunc['db_free_result']($request); Chris@76: Chris@76: // If we're submitting then let's move on to another function to keep things cleaner.. Chris@76: if (isset($_POST['action_changes'])) Chris@76: return PackagePermissionsAction(); Chris@76: Chris@76: $context['look_for'] = array(); Chris@76: // Are we looking for a particular tree - normally an expansion? Chris@76: if (!empty($_REQUEST['find'])) Chris@76: $context['look_for'][] = base64_decode($_REQUEST['find']); Chris@76: // Only that tree? Chris@76: $context['only_find'] = isset($_GET['xml']) && !empty($_REQUEST['onlyfind']) ? $_REQUEST['onlyfind'] : ''; Chris@76: if ($context['only_find']) Chris@76: $context['look_for'][] = $context['only_find']; Chris@76: Chris@76: // Have we got a load of back-catalogue trees to expand from a submit etc? Chris@76: if (!empty($_GET['back_look'])) Chris@76: { Chris@76: $potententialTrees = unserialize(base64_decode($_GET['back_look'])); Chris@76: foreach ($potententialTrees as $tree) Chris@76: $context['look_for'][] = $tree; Chris@76: } Chris@76: // ... maybe posted? Chris@76: if (!empty($_POST['back_look'])) Chris@76: $context['only_find'] = array_merge($context['only_find'], $_POST['back_look']); Chris@76: Chris@76: $context['back_look_data'] = base64_encode(serialize(array_slice($context['look_for'], 0, 15))); Chris@76: Chris@76: // Are we finding more files than first thought? Chris@76: $context['file_offset'] = !empty($_REQUEST['fileoffset']) ? (int) $_REQUEST['fileoffset'] : 0; Chris@76: // Don't list more than this many files in a directory. Chris@76: $context['file_limit'] = 150; Chris@76: Chris@76: // How many levels shall we show? Chris@76: $context['default_level'] = empty($context['only_find']) ? 2 : 25; Chris@76: Chris@76: // This will be used if we end up catching XML data. Chris@76: $context['xml_data'] = array( Chris@76: 'roots' => array( Chris@76: 'identifier' => 'root', Chris@76: 'children' => array( Chris@76: array( Chris@76: 'value' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find']), Chris@76: ), Chris@76: ), Chris@76: ), Chris@76: 'folders' => array( Chris@76: 'identifier' => 'folder', Chris@76: 'children' => array(), Chris@76: ), Chris@76: ); Chris@76: Chris@76: foreach ($context['file_tree'] as $path => $data) Chris@76: { Chris@76: // Run this directory. Chris@76: if (file_exists($path) && (empty($context['only_find']) || substr($context['only_find'], 0, strlen($path)) == $path)) Chris@76: { Chris@76: // Get the first level down only. Chris@76: fetchPerms__recursive($path, $context['file_tree'][$path], 1); Chris@76: $context['file_tree'][$path]['perms'] = array( Chris@76: 'chmod' => @is_writable($path), Chris@76: 'perms' => @fileperms($path), Chris@76: ); Chris@76: } Chris@76: else Chris@76: unset($context['file_tree'][$path]); Chris@76: } Chris@76: Chris@76: // Is this actually xml? Chris@76: if (isset($_GET['xml'])) Chris@76: { Chris@76: loadTemplate('Xml'); Chris@76: $context['sub_template'] = 'generic_xml'; Chris@76: $context['template_layers'] = array(); Chris@76: } Chris@76: } Chris@76: Chris@76: function fetchPerms__recursive($path, &$data, $level) Chris@76: { Chris@76: global $context; Chris@76: Chris@76: $isLikelyPath = false; Chris@76: foreach ($context['look_for'] as $possiblePath) Chris@76: if (substr($possiblePath, 0, strlen($path)) == $path) Chris@76: $isLikelyPath = true; Chris@76: Chris@76: // Is this where we stop? Chris@76: if (isset($_GET['xml']) && !empty($context['look_for']) && !$isLikelyPath) Chris@76: return; Chris@76: elseif ($level > $context['default_level'] && !$isLikelyPath) Chris@76: return; Chris@76: Chris@76: // Are we actually interested in saving this data? Chris@76: $save_data = empty($context['only_find']) || $context['only_find'] == $path; Chris@76: Chris@76: //!!! Shouldn't happen - but better error message? Chris@76: if (!is_dir($path)) Chris@76: fatal_lang_error('no_access', false); Chris@76: Chris@76: // This is where we put stuff we've found for sorting. Chris@76: $foundData = array( Chris@76: 'files' => array(), Chris@76: 'folders' => array(), Chris@76: ); Chris@76: Chris@76: $dh = opendir($path); Chris@76: while ($entry = readdir($dh)) Chris@76: { Chris@76: // Some kind of file? Chris@76: if (!is_dir($path . '/' . $entry)) Chris@76: { Chris@76: // Are we listing PHP files in this directory? Chris@76: if ($save_data && !empty($data['list_contents']) && substr($entry, -4) == '.php') Chris@76: $foundData['files'][$entry] = true; Chris@76: // A file we were looking for. Chris@76: elseif ($save_data && isset($data['contents'][$entry])) Chris@76: $foundData['files'][$entry] = true; Chris@76: } Chris@76: // It's a directory - we're interested one way or another, probably... Chris@76: elseif ($entry != '.' && $entry != '..') Chris@76: { Chris@76: // Going further? Chris@76: if ((!empty($data['type']) && $data['type'] == 'dir_recursive') || (isset($data['contents'][$entry]) && (!empty($data['contents'][$entry]['list_contents']) || (!empty($data['contents'][$entry]['type']) && $data['contents'][$entry]['type'] == 'dir_recursive')))) Chris@76: { Chris@76: if (!isset($data['contents'][$entry])) Chris@76: $foundData['folders'][$entry] = 'dir_recursive'; Chris@76: else Chris@76: $foundData['folders'][$entry] = true; Chris@76: Chris@76: // If this wasn't expected inherit the recusiveness... Chris@76: if (!isset($data['contents'][$entry])) Chris@76: // We need to do this as we will be going all recursive. Chris@76: $data['contents'][$entry] = array( Chris@76: 'type' => 'dir_recursive', Chris@76: ); Chris@76: Chris@76: // Actually do the recursive stuff... Chris@76: fetchPerms__recursive($path . '/' . $entry, $data['contents'][$entry], $level + 1); Chris@76: } Chris@76: // Maybe it is a folder we are not descending into. Chris@76: elseif (isset($data['contents'][$entry])) Chris@76: $foundData['folders'][$entry] = true; Chris@76: // Otherwise we stop here. Chris@76: } Chris@76: } Chris@76: closedir($dh); Chris@76: Chris@76: // Nothing to see here? Chris@76: if (!$save_data) Chris@76: return; Chris@76: Chris@76: // Now actually add the data, starting with the folders. Chris@76: ksort($foundData['folders']); Chris@76: foreach ($foundData['folders'] as $folder => $type) Chris@76: { Chris@76: $additional_data = array( Chris@76: 'perms' => array( Chris@76: 'chmod' => @is_writable($path . '/' . $folder), Chris@76: 'perms' => @fileperms($path . '/' . $folder), Chris@76: ), Chris@76: ); Chris@76: if ($type !== true) Chris@76: $additional_data['type'] = $type; Chris@76: Chris@76: // If there's an offset ignore any folders in XML mode. Chris@76: if (isset($_GET['xml']) && $context['file_offset'] == 0) Chris@76: { Chris@76: $context['xml_data']['folders']['children'][] = array( Chris@76: 'attributes' => array( Chris@76: 'writable' => $additional_data['perms']['chmod'] ? 1 : 0, Chris@76: 'permissions' => substr(sprintf('%o', $additional_data['perms']['perms']), -4), Chris@76: 'folder' => 1, Chris@76: 'path' => $context['only_find'], Chris@76: 'level' => $level, Chris@76: 'more' => 0, Chris@76: 'offset' => $context['file_offset'], Chris@76: 'my_ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find'] . '/' . $folder), Chris@76: 'ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find']), Chris@76: ), Chris@76: 'value' => $folder, Chris@76: ); Chris@76: } Chris@76: elseif (!isset($_GET['xml'])) Chris@76: { Chris@76: if (isset($data['contents'][$folder])) Chris@76: $data['contents'][$folder] = array_merge($data['contents'][$folder], $additional_data); Chris@76: else Chris@76: $data['contents'][$folder] = $additional_data; Chris@76: } Chris@76: } Chris@76: Chris@76: // Now we want to do a similar thing with files. Chris@76: ksort($foundData['files']); Chris@76: $counter = -1; Chris@76: foreach ($foundData['files'] as $file => $dummy) Chris@76: { Chris@76: $counter++; Chris@76: Chris@76: // Have we reached our offset? Chris@76: if ($context['file_offset'] > $counter) Chris@76: continue; Chris@76: // Gone too far? Chris@76: if ($counter > ($context['file_offset'] + $context['file_limit'])) Chris@76: continue; Chris@76: Chris@76: $additional_data = array( Chris@76: 'perms' => array( Chris@76: 'chmod' => @is_writable($path . '/' . $file), Chris@76: 'perms' => @fileperms($path . '/' . $file), Chris@76: ), Chris@76: ); Chris@76: Chris@76: // XML? Chris@76: if (isset($_GET['xml'])) Chris@76: { Chris@76: $context['xml_data']['folders']['children'][] = array( Chris@76: 'attributes' => array( Chris@76: 'writable' => $additional_data['perms']['chmod'] ? 1 : 0, Chris@76: 'permissions' => substr(sprintf('%o', $additional_data['perms']['perms']), -4), Chris@76: 'folder' => 0, Chris@76: 'path' => $context['only_find'], Chris@76: 'level' => $level, Chris@76: 'more' => $counter == ($context['file_offset'] + $context['file_limit']) ? 1 : 0, Chris@76: 'offset' => $context['file_offset'], Chris@76: 'my_ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find'] . '/' . $file), Chris@76: 'ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find']), Chris@76: ), Chris@76: 'value' => $file, Chris@76: ); Chris@76: } Chris@76: elseif ($counter != ($context['file_offset'] + $context['file_limit'])) Chris@76: { Chris@76: if (isset($data['contents'][$file])) Chris@76: $data['contents'][$file] = array_merge($data['contents'][$file], $additional_data); Chris@76: else Chris@76: $data['contents'][$file] = $additional_data; Chris@76: } Chris@76: } Chris@76: } Chris@76: Chris@76: // Actually action the permission changes they want. Chris@76: function PackagePermissionsAction() Chris@76: { Chris@76: global $context, $txt, $time_start, $package_ftp; Chris@76: Chris@76: umask(0); Chris@76: Chris@76: $timeout_limit = 5; Chris@76: Chris@76: $context['method'] = $_POST['method'] == 'individual' ? 'individual' : 'predefined'; Chris@76: $context['sub_template'] = 'action_permissions'; Chris@76: $context['page_title'] = $txt['package_file_perms_applying']; Chris@76: $context['back_look_data'] = isset($_POST['back_look']) ? $_POST['back_look'] : array(); Chris@76: Chris@76: // Skipping use of FTP? Chris@76: if (empty($package_ftp)) Chris@76: $context['skip_ftp'] = true; Chris@76: Chris@76: // We'll start off in a good place, security. Make sure that if we're dealing with individual files that they seem in the right place. Chris@76: if ($context['method'] == 'individual') Chris@76: { Chris@76: // Only these path roots are legal. Chris@76: $legal_roots = array_keys($context['file_tree']); Chris@76: $context['custom_value'] = (int) $_POST['custom_value']; Chris@76: Chris@76: // Continuing? Chris@76: if (isset($_POST['toProcess'])) Chris@76: $_POST['permStatus'] = unserialize(base64_decode($_POST['toProcess'])); Chris@76: Chris@76: if (isset($_POST['permStatus'])) Chris@76: { Chris@76: $context['to_process'] = array(); Chris@76: $validate_custom = false; Chris@76: foreach ($_POST['permStatus'] as $path => $status) Chris@76: { Chris@76: // Nothing to see here? Chris@76: if ($status == 'no_change') Chris@76: continue; Chris@76: $legal = false; Chris@76: foreach ($legal_roots as $root) Chris@76: if (substr($path, 0, strlen($root)) == $root) Chris@76: $legal = true; Chris@76: Chris@76: if (!$legal) Chris@76: continue; Chris@76: Chris@76: // Check it exists. Chris@76: if (!file_exists($path)) Chris@76: continue; Chris@76: Chris@76: if ($status == 'custom') Chris@76: $validate_custom = true; Chris@76: Chris@76: // Now add it. Chris@76: $context['to_process'][$path] = $status; Chris@76: } Chris@76: $context['total_items'] = isset($_POST['totalItems']) ? (int) $_POST['totalItems'] : count($context['to_process']); Chris@76: Chris@76: // Make sure the chmod status is valid? Chris@76: if ($validate_custom) Chris@76: { Chris@76: if (preg_match('~^[4567][4567][4567]$~', $context['custom_value']) == false) Chris@76: fatal_error($txt['chmod_value_invalid']); Chris@76: } Chris@76: Chris@76: // Nothing to do? Chris@76: if (empty($context['to_process'])) Chris@76: redirectexit('action=admin;area=packages;sa=perms' . (!empty($context['back_look_data']) ? ';back_look=' . base64_encode(serialize($context['back_look_data'])) : '') . ';' . $context['session_var'] . '=' . $context['session_id']); Chris@76: } Chris@76: // Should never get here, Chris@76: else Chris@76: fatal_lang_error('no_access', false); Chris@76: Chris@76: // Setup the custom value. Chris@76: $custom_value = octdec('0' . $context['custom_value']); Chris@76: Chris@76: // Start processing items. Chris@76: foreach ($context['to_process'] as $path => $status) Chris@76: { Chris@76: if (in_array($status, array('execute', 'writable', 'read'))) Chris@76: package_chmod($path, $status); Chris@76: elseif ($status == 'custom' && !empty($custom_value)) Chris@76: { Chris@76: // Use FTP if we have it. Chris@76: if (!empty($package_ftp) && !empty($_SESSION['pack_ftp'])) Chris@76: { Chris@76: $ftp_file = strtr($path, array($_SESSION['pack_ftp']['root'] => '')); Chris@76: $package_ftp->chmod($ftp_file, $custom_value); Chris@76: } Chris@76: else Chris@76: @chmod($path, $custom_value); Chris@76: } Chris@76: Chris@76: // This fish is fried... Chris@76: unset($context['to_process'][$path]); Chris@76: Chris@76: // See if we're out of time? Chris@76: if (time() - array_sum(explode(' ', $time_start)) > $timeout_limit) Chris@76: return false; Chris@76: } Chris@76: } Chris@76: // If predefined this is a little different. Chris@76: else Chris@76: { Chris@76: $context['predefined_type'] = isset($_POST['predefined']) ? $_POST['predefined'] : 'restricted'; Chris@76: Chris@76: $context['total_items'] = isset($_POST['totalItems']) ? (int) $_POST['totalItems'] : 0; Chris@76: $context['directory_list'] = isset($_POST['dirList']) ? unserialize(base64_decode($_POST['dirList'])) : array(); Chris@76: Chris@76: $context['file_offset'] = isset($_POST['fileOffset']) ? (int) $_POST['fileOffset'] : 0; Chris@76: Chris@76: // Haven't counted the items yet? Chris@76: if (empty($context['total_items'])) Chris@76: { Chris@76: function count_directories__recursive($dir) Chris@76: { Chris@76: global $context; Chris@76: Chris@76: $count = 0; Chris@76: $dh = @opendir($dir); Chris@76: while ($entry = readdir($dh)) Chris@76: { Chris@76: if ($entry != '.' && $entry != '..' && is_dir($dir . '/' . $entry)) Chris@76: { Chris@76: $context['directory_list'][$dir . '/' . $entry] = 1; Chris@76: $count++; Chris@76: $count += count_directories__recursive($dir . '/' . $entry); Chris@76: } Chris@76: } Chris@76: closedir($dh); Chris@76: Chris@76: return $count; Chris@76: } Chris@76: Chris@76: foreach ($context['file_tree'] as $path => $data) Chris@76: { Chris@76: if (is_dir($path)) Chris@76: { Chris@76: $context['directory_list'][$path] = 1; Chris@76: $context['total_items'] += count_directories__recursive($path); Chris@76: $context['total_items']++; Chris@76: } Chris@76: } Chris@76: } Chris@76: Chris@76: // Have we built up our list of special files? Chris@76: if (!isset($_POST['specialFiles']) && $context['predefined_type'] != 'free') Chris@76: { Chris@76: $context['special_files'] = array(); Chris@76: function build_special_files__recursive($path, &$data) Chris@76: { Chris@76: global $context; Chris@76: Chris@76: if (!empty($data['writable_on'])) Chris@76: if ($context['predefined_type'] == 'standard' || $data['writable_on'] == 'restrictive') Chris@76: $context['special_files'][$path] = 1; Chris@76: Chris@76: if (!empty($data['contents'])) Chris@76: foreach ($data['contents'] as $name => $contents) Chris@76: build_special_files__recursive($path . '/' . $name, $contents); Chris@76: } Chris@76: Chris@76: foreach ($context['file_tree'] as $path => $data) Chris@76: build_special_files__recursive($path, $data); Chris@76: } Chris@76: // Free doesn't need special files. Chris@76: elseif ($context['predefined_type'] == 'free') Chris@76: $context['special_files'] = array(); Chris@76: else Chris@76: $context['special_files'] = unserialize(base64_decode($_POST['specialFiles'])); Chris@76: Chris@76: // Now we definitely know where we are, we need to go through again doing the chmod! Chris@76: foreach ($context['directory_list'] as $path => $dummy) Chris@76: { Chris@76: // Do the contents of the directory first. Chris@76: $dh = @opendir($path); Chris@76: $file_count = 0; Chris@76: $dont_chmod = false; Chris@76: while ($entry = readdir($dh)) Chris@76: { Chris@76: $file_count++; Chris@76: // Actually process this file? Chris@76: if (!$dont_chmod && !is_dir($path . '/' . $entry) && (empty($context['file_offset']) || $context['file_offset'] < $file_count)) Chris@76: { Chris@76: $status = $context['predefined_type'] == 'free' || isset($context['special_files'][$path . '/' . $entry]) ? 'writable' : 'execute'; Chris@76: package_chmod($path . '/' . $entry, $status); Chris@76: } Chris@76: Chris@76: // See if we're out of time? Chris@76: if (!$dont_chmod && time() - array_sum(explode(' ', $time_start)) > $timeout_limit) Chris@76: { Chris@76: $dont_chmod = true; Chris@76: // Don't do this again. Chris@76: $context['file_offset'] = $file_count; Chris@76: } Chris@76: } Chris@76: closedir($dh); Chris@76: Chris@76: // If this is set it means we timed out half way through. Chris@76: if ($dont_chmod) Chris@76: { Chris@76: $context['total_files'] = $file_count; Chris@76: return false; Chris@76: } Chris@76: Chris@76: // Do the actual directory. Chris@76: $status = $context['predefined_type'] == 'free' || isset($context['special_files'][$path]) ? 'writable' : 'execute'; Chris@76: package_chmod($path, $status); Chris@76: Chris@76: // We've finished the directory so no file offset, and no record. Chris@76: $context['file_offset'] = 0; Chris@76: unset($context['directory_list'][$path]); Chris@76: Chris@76: // See if we're out of time? Chris@76: if (time() - array_sum(explode(' ', $time_start)) > $timeout_limit) Chris@76: return false; Chris@76: } Chris@76: } Chris@76: Chris@76: // If we're here we are done! Chris@76: redirectexit('action=admin;area=packages;sa=perms' . (!empty($context['back_look_data']) ? ';back_look=' . base64_encode(serialize($context['back_look_data'])) : '') . ';' . $context['session_var'] . '=' . $context['session_id']); Chris@76: } Chris@76: Chris@76: // Test an FTP connection. Chris@76: function PackageFTPTest() Chris@76: { Chris@76: global $context, $txt, $package_ftp; Chris@76: Chris@76: checkSession('get'); Chris@76: Chris@76: // Try to make the FTP connection. Chris@76: create_chmod_control(array(), array('force_find_error' => true)); Chris@76: Chris@76: // Deal with the template stuff. Chris@76: loadTemplate('Xml'); Chris@76: $context['sub_template'] = 'generic_xml'; Chris@76: $context['template_layers'] = array(); Chris@76: Chris@76: // Define the return data, this is simple. Chris@76: $context['xml_data'] = array( Chris@76: 'results' => array( Chris@76: 'identifier' => 'result', Chris@76: 'children' => array( Chris@76: array( Chris@76: 'attributes' => array( Chris@76: 'success' => !empty($package_ftp) ? 1 : 0, Chris@76: ), Chris@76: 'value' => !empty($package_ftp) ? $txt['package_ftp_test_success'] : (isset($context['package_ftp'], $context['package_ftp']['error']) ? $context['package_ftp']['error'] : $txt['package_ftp_test_failed']), Chris@76: ), Chris@76: ), Chris@76: ), Chris@76: ); Chris@76: } Chris@76: Chris@76: ?>