Browse Source

! package manager updates
+ <credits> tag for inserting mod credits in to the credits page
+ <requires> tag to basic mod dependency
+ <hooks> tag to add / remove hooks
+ First pass at smart emulation in the browse package screen
+ large overhaul of the package list functions complements of emanuele

Spuds 13 years ago
parent
commit
f43508e782

+ 361 - 88
Sources/Packages.php

@@ -49,7 +49,7 @@ function Packages()
 		'install2' => 'PackageInstall',
 		'uninstall' => 'PackageInstallTest',
 		'uninstall2' => 'PackageInstall',
-		'installed' => 'InstalledList',
+		'installed' => 'PackageBrowse',
 		'options' => 'PackageOptions',
 		'perms' => 'PackagePermissions',
 		'flush' => 'FlushInstall',
@@ -293,6 +293,7 @@ function PackageInstallTest()
 	$context['has_failure'] = false;
 	$chmod_files = array();
 
+	// no actions found, return so we can display an error
 	if (empty($actions))
 		return;
 
@@ -313,21 +314,22 @@ function PackageInstallTest()
 			$chmod_files[] = $action['filename'];
 			continue;
 		}
-		elseif ($action['type'] == 'readme')
+		elseif ($action['type'] == 'readme' || $action['type'] == 'license')
 		{
+			$type = 'package_' . $action['type'];
 			if (file_exists($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']))
-				$context['package_readme'] = htmlspecialchars(trim(file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), "\n\r"));
+				$context[$type] = htmlspecialchars(trim(file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), "\n\r"));
 			elseif (file_exists($action['filename']))
-				$context['package_readme'] = htmlspecialchars(trim(file_get_contents($action['filename']), "\n\r"));
+				$context[$type] = htmlspecialchars(trim(file_get_contents($action['filename']), "\n\r"));
 
 			if (!empty($action['parse_bbc']))
 			{
 				require_once($sourcedir . '/Subs-Post.php');
-				preparsecode($context['package_readme']);
-				$context['package_readme'] = parse_bbc($context['package_readme']);
+				preparsecode($context[$type]);
+				$context[$type] = parse_bbc($context[$type]);
 			}
 			else
-				$context['package_readme'] = nl2br($context['package_readme']);
+				$context[$type] = nl2br($context[$type]);
 
 			continue;
 		}
@@ -489,10 +491,12 @@ function PackageInstallTest()
 			$thisAction = array();
 		}
 		elseif ($action['type'] == 'code')
+		{
 			$thisAction = array(
 				'type' => $txt['execute_code'],
 				'action' => $smcFunc['htmlspecialchars']($action['filename']),
 			);
+		}
 		elseif ($action['type'] == 'database')
 		{
 			$thisAction = array(
@@ -501,11 +505,13 @@ function PackageInstallTest()
 			);
 		}
 		elseif (in_array($action['type'], array('create-dir', 'create-file')))
+		{
 			$thisAction = array(
 				'type' => $txt['package_create'] . ' ' . ($action['type'] == 'create-dir' ? $txt['package_tree'] : $txt['package_file']),
 				'action' => $smcFunc['htmlspecialchars'](strtr($action['destination'], array($boarddir => '.')))
 			);
-		elseif (in_array($action['type'], array('hook')))
+		}
+		elseif ($action['type'] == 'hook')
 		{
 			$action['description'] = !isset($action['hook'], $action['function']) ? $txt['package_action_failure'] : $txt['package_action_success'];
 
@@ -517,6 +523,54 @@ function PackageInstallTest()
 				'action' => sprintf($txt['execute_hook_action'],  $smcFunc['htmlspecialchars']($action['hook'])),
 			);
 		}
+		elseif ($action['type'] == 'credits')
+		{
+			$thisAction = array(
+				'type' => $txt['execute_credits_add'],
+				'action' => sprintf($txt['execute_credits_action'],  $smcFunc['htmlspecialchars']($action['title'])),
+			);
+		}
+		elseif ($action['type'] == 'requires')
+		{
+			$installed = false;
+			$version = true;
+
+			// package missing required values?
+			if (!isset($action['id']))
+				$context['has_failure'] = true;
+			else
+			{
+				// See if this dependancy is installed
+				$request = $smcFunc['db_query']('', '
+					SELECT version
+					FROM {db_prefix}log_packages
+					WHERE package_id = {string:current_package}
+						AND install_state != {int:not_installed}
+					ORDER BY time_installed DESC
+					LIMIT 1',
+					array(
+						'not_installed'	=> 0,
+						'current_package' => $action['id'],
+					)
+				);
+				$installed = ($smcFunc['db_num_rows']($request) !== 0);
+				if ($installed)
+					list($version) = $smcFunc['db_fetch_row']($request);
+				$smcFunc['db_free_result']($request);
+
+				// do a version level check (if requested) in the most basic way
+				$version = (isset($action['version']) ? $version == $action['version'] : true);
+			}
+
+			// Set success or failure information
+			$action['description'] = ($installed && $version) ? $txt['package_action_success'] : $txt['package_action_failure'];
+			$context['has_failure'] = !($installed && $version);
+
+			$thisAction = array(
+				'type' => $txt['package_requires'],
+				'action' => $txt['package_check_for'] . ' ' . $action['id'] . (isset($action['version']) ? (' / ' . ($version ? $action['version'] : '<span class="error">' . $action['version'] . '</span>')) : ''),
+			);
+		}
 		elseif (in_array($action['type'], array('require-dir', 'require-file')))
 		{
 			// Do this one...
@@ -564,6 +618,7 @@ function PackageInstallTest()
 				// Is the action already stated?
 				$theme_action = !empty($action['theme_action']) && in_array($action['theme_action'], array('no', 'yes', 'auto')) ? $action['theme_action'] : 'auto';
 				$action['unparsed_destination'] = $action['unparsed_filename'];
+				
 				// If it's not auto do we think we have something we can act upon?
 				if ($theme_action != 'auto' && !in_array($matches[1], array('languagedir', 'languages_dir', 'imagesdir', 'themedir')))
 					$theme_action = '';
@@ -615,6 +670,7 @@ function PackageInstallTest()
 				if (isset($theme_data['theme_dir']) && $id != 1)
 				{
 					$real_path = $theme_data['theme_dir'] . $path;
+					
 					// Confirm that we don't already have this dealt with by another entry.
 					if (!in_array(strtolower(strtr($real_path, array('\\' => '/'))), $themeFinds['other_themes']))
 					{
@@ -626,6 +682,7 @@ function PackageInstallTest()
 								$temp = dirname($temp);
 							$chmod_files[] = $temp;
 						}
+						
 						if ($action_data['type'] == 'require-dir' && !is_writable($real_path) && (file_exists($real_path) || !is_writable(dirname($real_path))))
 							$chmod_files[] = $real_path;
 
@@ -921,7 +978,17 @@ function PackageInstall()
 				// Now include the file and be done with it ;).
 				require($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']);
 			}
-			elseif ($action['type'] == 'hook' && isset($action['hook'], $action['name']))
+			elseif ($action['type'] == 'credits')
+			{
+				// Time to build the billboard
+				$credits_tag = array(
+					'url' => $action['url'],
+					'license' => $action['license'],
+					'copyright' => $action['copyright'],
+					'title' => $action['title'],
+				);
+			}
+			elseif ($action['type'] == 'hook' && isset($action['hook'], $action['function']))
 			{
 				if ($action['reverse'])
 					remove_integration_function($action['hook'], $action['function']);
@@ -1048,19 +1115,21 @@ function PackageInstall()
 			// What failed steps?
 			$failed_step_insert = serialize($failed_steps);
 
+			// Credits tag?
+			$credits_tag = (empty($credits_tag)) ? '' : serialize($credits_tag);
 			$smcFunc['db_insert']('',
 				'{db_prefix}log_packages',
 				array(
 					'filename' => 'string', 'name' => 'string', 'package_id' => 'string', 'version' => 'string',
 					'id_member_installed' => 'int', 'member_installed' => 'string','time_installed' => 'int',
 					'install_state' => 'int', 'failed_steps' => 'string', 'themes_installed' => 'string',
-					'member_removed' => 'int', 'db_changes' => 'string',
+					'member_removed' => 'int', 'db_changes' => 'string', 'credits' => 'string',
 				),
 				array(
 					$packageInfo['filename'], $packageInfo['name'], $packageInfo['id'], $packageInfo['version'],
 					$user_info['id'], $user_info['name'], time(),
 					$is_upgrade ? 2 : 1, $failed_step_insert, $themes_installed,
-					0, $db_changes,
+					0, $db_changes, $credits_tag,
 				),
 				array('id_install')
 			);
@@ -1265,29 +1334,158 @@ function PackageRemove()
  */
 function PackageBrowse()
 {
-	global $txt, $boarddir, $scripturl, $context, $forum_version;
+	global $txt, $boarddir, $scripturl, $context, $forum_version, $sourcedir, $settings;
 
 	$context['page_title'] .= ' - ' . $txt['browse_packages'];
-	$context['sub_template'] = 'browse';
 
 	$context['forum_version'] = $forum_version;
+	$context['modification_types'] = array('modification', 'avatar', 'language', 'unknown');
+
+	$installed = $context['sub_action'] == 'installed' ? true : false;
+
+	require_once($sourcedir . '/Subs-List.php');
+
+	foreach ($context['modification_types'] as $type)
+	{
+		// Use the standard templates for showing this.
+		$listOptions = array(
+			'id' => 'packages_lists_' . $type,
+			'title' => $installed ? $txt['view_and_remove'] : $txt[$type . '_package'],
+			'no_items_label' => $txt['no_packages'],
+			'get_items' => array(
+				'function' => 'list_getPackages',
+				'params' => array('type' => $type, 'installed' => $installed),
+			),
+			'columns' => array(
+				'id' => array(
+					'header' => array(
+						'value' => '',
+						'style' => 'width: 32px;',
+					),
+					'data' => array(
+						'function' => create_function('$packages', '
+							static $packageCounter;
+							if (empty($packageCounter))
+								$packageCounter = 1;
+
+							return $packageCounter++ . \'.\';
+						'),
+					),
+				),
+				'mod_name' => array(
+					'header' => array(
+						'value' => $txt['mod_name'],
+						'style' => 'width: 25%;',
+					),
+					'data' => array(
+						'function' => create_function('$package_md5', '
+							global $context;
 
-	$instmods = loadInstalledPackages();
+							if (isset($context[\'available_' . $type . '\'][$package_md5]))
+								return $context[\'available_' . $type . '\'][$package_md5][\'name\'];'
+						),
+					),
+				),
+				'version' => array(
+					'header' => array(
+						'value' => $txt['mod_version'],
+						'style' => 'width: 25%;',
+					),
+					'data' => array(
+						'function' => create_function('$package_md5', '
+							global $context;
 
-	$installed_mods = array();
-	// Look through the list of installed mods...
-	foreach ($instmods as $installed_mod)
-		$installed_mods[$installed_mod['package_id']] = array(
-			'id' => $installed_mod['id'],
-			'version' => $installed_mod['version'],
+							if (isset($context[\'available_' . $type . '\'][$package_md5]))
+								return $context[\'available_' . $type . '\'][$package_md5][\'version\'];'
+						),
+					),
+				),
+				'operations' => array(
+					'header' => array(
+						'value' => '',
+					),
+					'data' => array(
+						'function' => create_function('$package_md5', '
+							global $context, $scripturl, $txt;
+
+							if (!isset($context[\'available_' . $type . '\'][$package_md5]))
+								return \'\';
+
+							// Rewrite shortcut
+							$package = $context[\'available_' . $type . '\'][$package_md5];
+							$return = \'\';
+
+							if ($package[\'can_uninstall\'])
+								$return = \'
+									<a href="\' . $scripturl . \'?action=admin;area=packages;sa=uninstall;package=\' . $package[\'filename\'] . \';pid=\' . $package[\'installed_id\'] . \'">[ \' . $txt[\'uninstall\'] . \' ]</a>\';
+							elseif ($package[\'can_emulate_uninstall\'])
+								$return = \'
+									<a href="\' . $scripturl . \'?action=admin;area=packages;sa=uninstall;ve=\' . $package[\'can_emulate_uninstall\'] . \';package=\' . $package[\'filename\'] . \';pid=\' . $package[\'installed_id\'] . \'">[ \' . $txt[\'package_emulate_uninstall\'] . \' \' . $package[\'can_emulate_uninstall\'] . \' ]</a>\';
+							elseif ($package[\'can_upgrade\'])
+								$return = \'
+									<a href="\' . $scripturl . \'?action=admin;area=packages;sa=install;package=\' . $package[\'filename\'] . \'">[ \' . $txt[\'package_upgrade\'] . \' ]</a>\';
+							elseif ($package[\'can_install\'])
+								$return = \'
+									<a href="\' . $scripturl . \'?action=admin;area=packages;sa=install;package=\' . $package[\'filename\'] . \'">[ \' . $txt[\'install_mod\'] . \' ]</a>\';
+							elseif ($package[\'can_emulate_install\'])
+								$return = \'
+									<a href="\' . $scripturl . \'?action=admin;area=packages;sa=install;ve=\' . $package[\'can_emulate_install\'] . \';package=\' . $package[\'filename\'] . \'">[ \' . $txt[\'package_emulate_install\'] . \' \' . $package[\'can_emulate_install\'] . \' ]</a>\';
+
+							return $return . \'
+									<a href="\' . $scripturl . \'?action=admin;area=packages;sa=list;package=\' . $package[\'filename\'] . \'">[ \' . $txt[\'list_files\'] . \' ]</a>
+									<a href="\' . $scripturl . \'?action=admin;area=packages;sa=remove;package=\' . $package[\'filename\'] . \';\' . $context[\'session_var\'] . \'=\' . $context[\'session_id\'] . \'"\' . ($package[\'is_installed\'] && $package[\'is_current\'] ? \' onclick="return confirm(\\\'\' . $txt[\'package_delete_bad\'] . \'\\\');"\' : \'\') . \'>[ \' . $txt[\'package_delete\'] . \' ]</a>\';'
+							),
+						'style' => 'text-align: right;',
+					),
+				),
+			),
+			'additional_rows' => array(
+				array(
+					'position' => 'bottom_of_list',
+					'value' => (
+					$context['sub_action'] == 'browse' ? '
+			<div class="padding smalltext floatleft">
+				' . $txt['package_installed_key'] . '
+				<img src="' . $settings['images_url'] . '/icons/package_installed.png" alt="" class="centericon" style="margin-left: 1ex;" /> ' . $txt['package_installed_current'] . '
+				<img src="' . $settings['images_url'] . '/icons/package_old.png" alt="" class="centericon" style="margin-left: 2ex;" /> ' . $txt['package_installed_old'] . '
+			</div>' : '<a class="button_link" href="' . $scripturl . '?action=admin;area=packages;sa=flush;' . $context['session_var'] . '=' . $context['session_id'] . '" onclick="return confirm(\'' . $txt['package_delete_list_warning'] . '\');">' . $txt['delete_list'] . '</a><span style="float: right; margin:.5em;"></span><a class="button_link" href="#" onclick="document.getElementById(\'advanced_box\').style.display = document.getElementById(\'advanced_box\').style.display == \'\' ? \'none\' : \'\'; return false;">' .  $txt['package_advanced_button'] . '</a>'), 
+				),
+			),
 		);
 
+		createList($listOptions);
+	}
+
+	$context['sub_template'] = 'browse';
+	$context['default_list'] = 'packages_lists';
+
+	// Empty lists for now.
+	$context['available_mods'] = array();
+	$context['available_avatars'] = array();
+	$context['available_languages'] = array();
+	$context['available_other'] = array();
+	$context['available_all'] = array();
+}
+
+function list_getPackages($start, $items_per_page, $sort, $params, $installed)
+{
+	global $boarddir, $scripturl, $context, $forum_version;
+	static $instmods, $packages;
+	
+	// Start things up
+	if (!isset($packages[$params]))
+		$packages[$params] = array();
+
+	// We need the packages directory to be writable for this.
+	if (!@is_writable($boarddir . '/Packages'))
+		create_chmod_control(array($boarddir . '/Packages'), array('destination_url' => $scripturl . '?action=admin;area=packages', 'crash_on_error' => true));
+
 	$the_version = strtr($forum_version, array('SMF ' => ''));
 
 	// Here we have a little code to help those who class themselves as something of gods, version emulation ;)
 	if (isset($_GET['version_emulate']))
 	{
-		if ($_GET['version_emulate'] === 0 && isset($_SESSION['version_emulate']))
+		if (($_GET['version_emulate'] === 0 || $_GET['version_emulate'] === $forum_version) && isset($_SESSION['version_emulate']))
 			unset($_SESSION['version_emulate']);
 		elseif ($_GET['version_emulate'] !== 0)
 			$_SESSION['version_emulate'] = strtr($_GET['version_emulate'], array('-' => ' ', '+' => ' ', 'SMF ' => ''));
@@ -1297,20 +1495,44 @@ function PackageBrowse()
 		$context['forum_version'] = 'SMF ' . $_SESSION['version_emulate'];
 		$the_version = $_SESSION['version_emulate'];
 	}
+	if (isset($_SESSION['single_version_emulate']))
+		unset($_SESSION['single_version_emulate']);
 
-	// Get a list of all the ids installed, so the latest packages won't include already installed ones.
-	$context['installed_mods'] = array_keys($installed_mods);
+	if (empty($instmods))
+	{
+		$instmods = loadInstalledPackages();
+		$installed_mods = array();
+		// Look through the list of installed mods...
+		foreach ($instmods as $installed_mod)
+			$installed_mods[$installed_mod['package_id']] = array(
+				'id' => $installed_mod['id'],
+				'version' => $installed_mod['version'],
+			);
 
-	// Empty lists for now.
-	$context['available_mods'] = array();
-	$context['available_avatars'] = array();
-	$context['available_languages'] = array();
-	$context['available_other'] = array();
-	$context['available_all'] = array();
+		// Get a list of all the ids installed, so the latest packages won't include already installed ones.
+		$context['installed_mods'] = array_keys($installed_mods);
+	}
 
-	// We need the packages directory to be writable for this.
-	if (!@is_writable($boarddir . '/Packages'))
-		create_chmod_control(array($boarddir . '/Packages'), array('destination_url' => $scripturl . '?action=admin;area=packages', 'crash_on_error' => true));
+	if ($installed)
+	{
+		foreach ($instmods as $installed_mod)
+		{
+			$packages['modification'][] = $installed_mod['package_id'];
+			$context['available_modification'][$installed_mod['package_id']] = array(
+				'can_uninstall' => true,
+				'name' => $installed_mod['name'],
+				'filename' => $installed_mod['filename'],
+				'installed_id' => $installed_mod['id'],
+				'version' => $installed_mod['version'],
+				'is_installed' => true,
+				'is_current' => true,
+			);
+		}
+		return $packages['modification'];
+	}
+
+	if (empty($packages))
+		$packages = array('modification' => array(), 'avatar' => array(), 'language' => array(), 'unknown' => array());
 
 	if ($dir = @opendir($boarddir . '/Packages'))
 	{
@@ -1320,6 +1542,14 @@ function PackageBrowse()
 			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'))
 				continue;
 
+			$skip = false;
+			foreach ($context['modification_types'] as $type)
+				if (isset($context['available_' . $type][md5($package)]))
+					$skip = true;
+
+			if ($skip)
+				continue;
+
 			// Skip directories or files that are named the same.
 			if (is_dir($boarddir . '/Packages/' . $package))
 			{
@@ -1344,80 +1574,123 @@ function PackageBrowse()
 			if (!is_array($packageInfo))
 				continue;
 
-			$packageInfo['installed_id'] = isset($installed_mods[$packageInfo['id']]) ? $installed_mods[$packageInfo['id']]['id'] : 0;
+			if (!empty($packageInfo))
+			{
+				$packageInfo['installed_id'] = isset($installed_mods[$packageInfo['id']]) ? $installed_mods[$packageInfo['id']]['id'] : 0;
 
-			$packageInfo['is_installed'] = isset($installed_mods[$packageInfo['id']]);
-			$packageInfo['is_current'] = $packageInfo['is_installed'] && ($installed_mods[$packageInfo['id']]['version'] == $packageInfo['version']);
-			$packageInfo['is_newer'] = $packageInfo['is_installed'] && ($installed_mods[$packageInfo['id']]['version'] > $packageInfo['version']);
+				$packageInfo['is_installed'] = isset($installed_mods[$packageInfo['id']]);
+				$packageInfo['is_current'] = $packageInfo['is_installed'] && ($installed_mods[$packageInfo['id']]['version'] == $packageInfo['version']);
+				$packageInfo['is_newer'] = $packageInfo['is_installed'] && ($installed_mods[$packageInfo['id']]['version'] > $packageInfo['version']);
 
-			$packageInfo['can_install'] = false;
-			$packageInfo['can_uninstall'] = false;
-			$packageInfo['can_upgrade'] = false;
+				$packageInfo['can_install'] = false;
+				$packageInfo['can_uninstall'] = false;
+				$packageInfo['can_upgrade'] = false;
+				$packageInfo['can_emulate_install'] = false;
+				$packageInfo['can_emulate_uninstall'] = false;
 
-			// This package is currently NOT installed.  Check if it can be.
-			if (!$packageInfo['is_installed'] && $packageInfo['xml']->exists('install'))
-			{
-				// Check if there's an install for *THIS* version of SMF.
-				$installs = $packageInfo['xml']->set('install');
-				foreach ($installs as $install)
+				// This package is currently NOT installed.  Check if it can be.
+				if (!$packageInfo['is_installed'] && $packageInfo['xml']->exists('install'))
 				{
-					if (!$install->exists('@for') || matchPackageVersion($the_version, $install->fetch('@for')))
+					// Check if there's an install for *THIS* version of SMF.
+					$installs = $packageInfo['xml']->set('install');
+					foreach ($installs as $install)
+					{
+						if (!$install->exists('@for') || matchPackageVersion($the_version, $install->fetch('@for')))
+						{
+							// Okay, this one is good to go.
+							$packageInfo['can_install'] = true;
+							break;
+						}
+					}
+
+					// no install found for this version, lets see if one exists for another
+					if ($packageInfo['can_install'] === false && $install->exists('@for') && empty($_SESSION['version_emulate']))
 					{
-						// Okay, this one is good to go.
-						$packageInfo['can_install'] = true;
-						break;
+						$reset = true;
+						
+						// Get the highest install version that is available from the package
+						foreach ($installs as $install)
+						{
+							$packageInfo['can_emulate_install'] = matchHighestPackageVersion($install->fetch('@for'), $reset, $the_version);
+							$reset = false;
+						}
 					}
 				}
-			}
-			// An already installed, but old, package.  Can we upgrade it?
-			elseif ($packageInfo['is_installed'] && !$packageInfo['is_current'] && $packageInfo['xml']->exists('upgrade'))
-			{
-				$upgrades = $packageInfo['xml']->set('upgrade');
+				// An already installed, but old, package.  Can we upgrade it?
+				elseif ($packageInfo['is_installed'] && !$packageInfo['is_current'] && $packageInfo['xml']->exists('upgrade'))
+				{
+					$upgrades = $packageInfo['xml']->set('upgrade');
 
-				// First go through, and check against the current version of SMF.
-				foreach ($upgrades as $upgrade)
+					// First go through, and check against the current version of SMF.
+					foreach ($upgrades as $upgrade)
+					{
+						// Even if it is for this SMF, is it for the installed version of the mod?
+						if (!$upgrade->exists('@for') || matchPackageVersion($the_version, $upgrade->fetch('@for')))
+							if (!$upgrade->exists('@from') || matchPackageVersion($installed_mods[$packageInfo['id']]['version'], $upgrade->fetch('@from')))
+							{
+								$packageInfo['can_upgrade'] = true;
+								break;
+							}
+					}
+				}
+				// Note that it has to be the current version to be uninstallable.  Shucks.
+				elseif ($packageInfo['is_installed'] && $packageInfo['is_current'] && $packageInfo['xml']->exists('uninstall'))
 				{
-					// Even if it is for this SMF, is it for the installed version of the mod?
-					if (!$upgrade->exists('@for') || matchPackageVersion($the_version, $upgrade->fetch('@for')))
-						if (!$upgrade->exists('@from') || matchPackageVersion($installed_mods[$packageInfo['id']]['version'], $upgrade->fetch('@from')))
+					$uninstalls = $packageInfo['xml']->set('uninstall');
+
+					// Can we find any uninstallation methods that work for this SMF version?
+					foreach ($uninstalls as $uninstall)
+					{
+						if (!$uninstall->exists('@for') || matchPackageVersion($the_version, $uninstall->fetch('@for')))
 						{
-							$packageInfo['can_upgrade'] = true;
+							$packageInfo['can_uninstall'] = true;
 							break;
 						}
-				}
-			}
-			// Note that it has to be the current version to be uninstallable.  Shucks.
-			elseif ($packageInfo['is_installed'] && $packageInfo['is_current'] && $packageInfo['xml']->exists('uninstall'))
-			{
-				$uninstalls = $packageInfo['xml']->set('uninstall');
-
-				// Can we find any uninstallation methods that work for this SMF version?
-				foreach ($uninstalls as $uninstall)
-					if (!$uninstall->exists('@for') || matchPackageVersion($the_version, $uninstall->fetch('@for')))
+					}
+					
+					// no uninstall found for this version, lets see if one exists for another
+					if ($packageInfo['can_uninstall'] === false && $uninstall->exists('@for') && empty($_SESSION['version_emulate']))
 					{
-						$packageInfo['can_uninstall'] = true;
-						break;
+						$reset = true;
+						
+						// Get the highest install version that is available from the package
+						foreach ($uninstalls as $uninstall)
+						{
+							$packageInfo['can_emulate_uninstall'] = matchHighestPackageVersion($uninstall->fetch('@for'), $reset, $the_version);
+							$reset = false;
+						}
 					}
-			}
+				}
 
-			// Store a complete list.
-			$context['available_all'][] = $packageInfo;
-
-			// Modification.
-			if ($packageInfo['type'] == 'modification' || $packageInfo['type'] == 'mod')
-				$context['available_mods'][] = $packageInfo;
-			// Avatar package.
-			elseif ($packageInfo['type'] == 'avatar')
-				$context['available_avatars'][] = $packageInfo;
-			// Language package.
-			elseif ($packageInfo['type'] == 'language')
-				$context['available_languages'][] = $packageInfo;
-			// Other stuff.
-			else
-				$context['available_other'][] = $packageInfo;
+				// Modification.
+				if ($packageInfo['type'] == 'modification' || $packageInfo['type'] == 'mod')
+				{
+					$packages['modification'][] = md5($package);
+					$context['available_modification'][md5($package)] = $packageInfo;
+				}
+				// Avatar package.
+				elseif ($packageInfo['type'] == 'avatar')
+				{
+					$packages['avatar'][] = md5($package);
+					$context['available_avatar'][md5($package)] = $packageInfo;
+				}
+				// Language package.
+				elseif ($packageInfo['type'] == 'language')
+				{
+					$packages['language'][] = md5($package);
+					$context['available_language'][md5($package)] = $packageInfo;
+				}
+				// Other stuff.
+				else
+				{
+					$packages['unknown'][] = md5($package);
+					$context['available_unknown'][md5($package)] = $packageInfo;
+				}
+			}
 		}
 		closedir($dir);
 	}
+	return $packages[$params];
 }
 
 function PackageOptions()

+ 162 - 52
Sources/Subs-Package.php

@@ -50,16 +50,23 @@ function read_tgz_file($gzfilename, $destination, $single_file = false, $overwri
 
 /**
  * Extracts a file or files from the .tar.gz contained in data.
+ *
  * detects if the file is really a .zip file, and if so returns the result of read_zip_data
- * if destination is null, returns a list of files in the archive.
- * if single_file is true, returns the contents of the file specified by destination, if it exists, or false.
- * if single_file is true, destination can start with * and / to signify that the file may come from any directory.
- * destination should not begin with a / if single_file is true.
+ *
+ * if destination is null
+ *	- returns a list of files in the archive.
+ *
+ * if single_file is true
+ * - returns the contents of the file specified by destination, if it exists, or false.
+ * - destination can start with * and / to signify that the file may come from any directory.
+ * - destination should not begin with a / if single_file is true.
+ *
  * overwrites existing files with newer modification times if and only if overwrite is true.
  * creates the destination directory if it doesn't exist, and is is specified.
  * requires zlib support be built into PHP.
  * returns an array of the files extracted.
  * if files_to_extract is not equal to null only extracts file within this array.
+ *
  * @param string data,
  * @param string destination,
  * @param bool single_file = false,
@@ -296,7 +303,7 @@ function read_zip_data($data, $destination, $single_file = false, $overwrite = f
 		// Okay!  We can write this file, looks good from here...
 		if ($write_this && $destination !== null)
 		{
-			if ((strpos($file_info['filename'], '/') !== false && !$single_file) || (!is_dir($file_info['dir'])))
+			if ((strpos($file_info['filename'], '/') !== false && !$single_file) || (!$single_file && !is_dir($file_info['dir'])))
 				mktree($file_info['dir'], 0777);
 
 			// If we're looking for a specific file, and this is it... ka-bam, baby.
@@ -359,8 +366,10 @@ function url_exists($url)
 
 /**
  * Loads and returns an array of installed packages.
- * gets this information from Packages/installed.list.
- * returns the array of data.
+ * - gets this information from Packages/installed.list.
+ * - returns the array of data.
+ * - default sort order is package_installed time
+ * 
  * @return array
  */
 function loadInstalledPackages()
@@ -418,10 +427,11 @@ function loadInstalledPackages()
 
 /**
  * Loads a package's information and returns a representative array.
- * expects the file to be a package in Packages/.
- * returns a error string if the package-info is invalid.
- * returns a basic array of id, version, filename, and similar information.
- * in the array returned, an xmlArray is available in 'xml'.
+ * - expects the file to be a package in Packages/.
+ * - returns a error string if the package-info is invalid.
+ * - otherwise returns a basic array of id, version, filename, and similar information.
+ * - an xmlArray is available in 'xml'.
+ *
  * @param string $filename
  * @return array
  */
@@ -970,12 +980,14 @@ function packageRequireFTP($destination_url, $files = null, $return = false)
 }
 
 /**
- * Parses the actions in package-info.xml files from packages.
- * package should be an xmlArray with package-info as its base.
- * testing_only should be true if the package should not actually be applied.
- * method is upgrade, install, or uninstall.  Its default is install.
- * previous_version should be set to the previous installed version of this package, if any.
- * does not handle failure terribly well; testing first is always better.
+ * Parses the actions in package-info.xml file from packages.
+ *
+ * - package should be an xmlArray with package-info as its base.
+ * - testing_only should be true if the package should not actually be applied.
+ * - method can be upgrade, install, or uninstall.  Its default is install.
+ * - previous_version should be set to the previous installed version of this package, if any.
+ * - does not handle failure terribly well; testing first is always better.
+ *
  * @param xmlArray &$package
  * @param bool $testing_only = true
  * @param string $method = 'install' ('install', 'upgrade', or 'uninstall')
@@ -998,6 +1010,15 @@ function parsePackageInfo(&$packageXML, $testing_only = true, $method = 'install
 	if (!empty($_SESSION['version_emulate']))
 		$the_version = $_SESSION['version_emulate'];
 
+	// Single package emulation
+	if (!empty($_REQUEST['ve']) && !empty($_REQUEST['package']))
+	{
+		$the_version = $_REQUEST['ve'];
+		$_SESSION['single_version_emulate'][$_REQUEST['package']] = $the_version;
+	}
+	if (!empty($_REQUEST['package']) && (!empty($_SESSION['single_version_emulate'][$_REQUEST['package']])))
+		$the_version = $_SESSION['single_version_emulate'][$_REQUEST['package']];
+
 	// Get all the versions of this method and find the right one.
 	$these_methods = $packageXML->set($method);
 	foreach ($these_methods as $this_method)
@@ -1035,53 +1056,55 @@ function parsePackageInfo(&$packageXML, $testing_only = true, $method = 'install
 	$temp_path = $boarddir . '/Packages/temp/' . (isset($context['base_path']) ? $context['base_path'] : '');
 
 	$context['readmes'] = array();
+	$context['licences'] = array();
+
 	// This is the testing phase... nothing shall be done yet.
 	foreach ($actions as $action)
 	{
 		$actionType = $action->name();
 
-		if (in_array($actionType, array('readme', 'code', 'database', 'modification', 'redirect')))
+		if (in_array($actionType, array('readme', 'code', 'database', 'modification', 'redirect', 'license')))
 		{
-			// Allow for translated readme files.
-			if ($actionType == 'readme')
+			// Allow for translated readme and license files.
+			if ($actionType == 'readme' || $actionType == 'license')
 			{
+				$type = $actionType . 's';
 				if ($action->exists('@lang'))
 				{
-					// Auto-select a readme language based on either request variable or current language.
-					if ((isset($_REQUEST['readme']) && $action->fetch('@lang') == $_REQUEST['readme']) || (!isset($_REQUEST['readme']) && $action->fetch('@lang') == $language))
+					// Auto-select the language based on either request variable or current language.
+					if ((isset($_REQUEST['readme']) && $action->fetch('@lang') == $_REQUEST['readme']) || (isset($_REQUEST['license']) && $action->fetch('@lang') == $_REQUEST['license']) || (!isset($_REQUEST['readme']) && $action->fetch('@lang') == $language)	|| (!isset($_REQUEST['license']) && $action->fetch('@lang') == $language))
 					{
-						// In case the user put the readme blocks in the wrong order.
-						if (isset($context['readmes']['selected']) && $context['readmes']['selected'] == 'default')
-							$context['readmes'][] = 'default';
+						// In case the user put the blocks in the wrong order.
+						if (isset($context[$type]['selected']) && $context[$type]['selected'] == 'default')
+							$context[$type][] = 'default';
 
-						$context['readmes']['selected'] = htmlspecialchars($action->fetch('@lang'));
+						$context[$type]['selected'] = htmlspecialchars($action->fetch('@lang'));
 					}
 					else
 					{
-						// We don't want this readme now, but we'll allow the user to select to read it.
-						$context['readmes'][] = htmlspecialchars($action->fetch('@lang'));
+						// We don't want this now, but we'll allow the user to select to read it.
+						$context[$type][] = htmlspecialchars($action->fetch('@lang'));
 						continue;
 					}
 				}
-				// Fallback readme. Without lang parameter.
+				// Fallback when we have no lang parameter.
 				else
 				{
-
-					// Already selected a readme.
-					if (isset($context['readmes']['selected']))
+					// Already selected one for use?
+					if (isset($context[$type]['selected']))
 					{
-						$context['readmes'][] = 'default';
+						$context[$type][] = 'default';
 						continue;
 					}
 					else
-						$context['readmes']['selected'] = 'default';
+						$context[$type]['selected'] = 'default';
 				}
 			}
 
 			// @todo Make sure the file actually exists?  Might not work when testing?
 			if ($action->exists('@type') && $action->fetch('@type') == 'inline')
 			{
-				$filename = $temp_path . '$auto_' . $temp_auto++ . ($actionType == 'readme' || $actionType == 'redirect' ? '.txt' : ($actionType == 'code' || $actionType == 'database' ? '.php' : '.mod'));
+				$filename = $temp_path . '$auto_' . $temp_auto++ . (in_array($actionType, array('readme', 'redirect', 'license')) ? '.txt' : ($actionType == 'code' || $actionType == 'database' ? '.php' : '.mod'));
 				package_put_contents($filename, $action->fetch('.'));
 				$filename = strtr($filename, array($temp_path => ''));
 			}
@@ -1097,7 +1120,7 @@ function parsePackageInfo(&$packageXML, $testing_only = true, $method = 'install
 				'redirect_url' => $action->exists('@url') ? $action->fetch('@url') : '',
 				'redirect_timeout' => $action->exists('@timeout') ? (int) $action->fetch('@timeout') : '',
 				'parse_bbc' => $action->exists('@parsebbc') && $action->fetch('@parsebbc') == 'true',
-				'language' => ($actionType == 'readme' && $action->exists('@lang') && $action->fetch('@lang') == $language) ? $language : '',
+				'language' => (($actionType == 'readme' || $actionType == 'license')  && $action->exists('@lang') && $action->fetch('@lang') == $language) ? $language : '',
 			);
 
 			continue;
@@ -1113,6 +1136,36 @@ function parsePackageInfo(&$packageXML, $testing_only = true, $method = 'install
 			);
 			continue;
 		}
+		elseif ($actionType == 'credits')
+		{
+			// quick check of any supplied url
+			$url = $action->exists('@url') ? $action->fetch('@url') : '';
+			if (strlen(trim($url)) > 0 && substr($url, 0, 7) !== 'http://' && substr($url, 0, 8) !== 'https://')
+			{
+				$url = 'http://' . $url;
+				if (strlen($url) < 8 || (substr($url, 0, 7) !== 'http://' && substr($url, 0, 8) !== 'https://'))
+					$url = '';
+			}
+
+			$return[] = array(
+				'type' => $actionType,
+				'url' => $url,
+				'license' => $action->exists('@license') ? $action->fetch('@license') : '',
+				'copyright' => $action->exists('@copyright') ? $action->fetch('@copyright') : '',
+				'title' => $action->fetch('.'),
+			);
+			continue;
+		}
+		elseif ($actionType == 'requires')
+		{
+			$return[] = array(
+				'type' => $actionType,
+				'id' => $action->exists('@id') ? $action->fetch('@id') : '',
+				'version' => $action->exists('@version') ? $action->fetch('@version') : $action->fetch('.'),
+				'description' => '',
+			);
+			continue;
+		}
 		elseif ($actionType == 'error')
 		{
 			$return[] = array(
@@ -1242,7 +1295,7 @@ function parsePackageInfo(&$packageXML, $testing_only = true, $method = 'install
 		}
 		elseif ($actionType == 'remove-dir')
 		{
-			if (!is_writable($this_action['filename']) && file_exists($this_action['destination']))
+			if (!is_writable($this_action['filename']) && file_exists($this_action['filename']))
 				$return[] = array(
 					'type' => 'chmod',
 					'filename' => $this_action['filename']
@@ -1268,7 +1321,7 @@ function parsePackageInfo(&$packageXML, $testing_only = true, $method = 'install
 	$not_done = array(array('type' => '!'));
 	foreach ($return as $action)
 	{
-		if ($action['type'] == 'modification' || $action['type'] == 'code' || $action['type'] == 'database' || $action['type'] == 'redirect')
+		if (in_array($action['type'], array('modification', 'code', 'database', 'redirect', 'hook', 'credits')))
 			$not_done[] = $action;
 
 		if ($action['type'] == 'create-dir')
@@ -1366,9 +1419,48 @@ function parsePackageInfo(&$packageXML, $testing_only = true, $method = 'install
 
 /**
  * Checks if version matches any of the versions in versions.
- * supports comma separated version numbers, with or without whitespace.
- * supports lower and upper bounds. (1.0-1.2)
- * returns true if the version matched.
+ * - supports comma separated version numbers, with or without whitespace.
+ * - supports lower and upper bounds. (1.0-1.2)
+ * - returns true if the version matched.
+ *
+ * @param string $versions
+ * @return highest install value string or false
+ */
+function matchHighestPackageVersion($versions, $reset = false, $the_version)
+{
+	static $near_version = 0;
+
+	if ($reset)
+		$near_version = 0;
+
+	// Normalize the $versions while we remove our previous Doh!
+	$versions = explode(',', str_replace(array(' ', '2.0rc1-1'), array('', '2.0rc1.1'), strtolower($versions)));
+
+	// Loop through each version, save the highest we can find
+	foreach ($versions as $for)
+	{
+		// Adjust for those wild cards
+		if (strpos($for, '*') !== false)
+			$for = str_replace('*', '0dev0', $for) . '-' . str_replace('*', '999', $for);
+
+		// If we have a range, grab the lower value, done this way so it looks normal-er to the user e.g. 2.0 vs 2.0.99
+		if (strpos($for, '-') !== false)
+			list ($for, $higher) = explode('-', $for);
+
+		// Do the compare, if the for is greater, than what we have but not greater than what we are running .....
+		if (compareVersions($near_version, $for) === -1 && compareVersions($for, $the_version) !== 1)
+			$near_version = $for;
+	}
+
+	return !empty($near_version) ? $near_version : false;
+}
+
+/**
+ * Checks if the forum version matches any of the available versions from the package install xml.
+ * - supports comma separated version numbers, with or without whitespace.
+ * - supports lower and upper bounds. (1.0-1.2)
+ * - returns true if the version matched.
+ *
  * @param string $version
  * @param string $versions
  * @return bool
@@ -1408,10 +1500,14 @@ function matchPackageVersion($version, $versions)
 }
 
 /**
- * Compares two versions.
+ * Compares two versions and determines if one is newer, older or the same, returns
+ * - (-1) if version1 is lower than version2
+ * - (0) if version1 is equal to version2
+ * - (1) if version1 is higher than version2
+ *
  * @param string version1
  * @param string version2
- * @return int (-1 if version1 is lower than version2, 0 if version1 is equal to version2; 1 if version1 is higher than version2)
+ * @return int (-1, 0, 1)
  */
 function compareVersions($version1, $version2)
 {
@@ -2426,7 +2522,7 @@ function package_get_contents($filename)
 
 	if (!isset($package_cache))
 	{
-	
+
 		$mem_check = setMemoryLimit('128M');
 
 		// Windows doesn't seem to care about the memory_limit.
@@ -2460,7 +2556,7 @@ function package_put_contents($filename, $data, $testing = false)
 	{
 		// Try to increase the memory limit - we don't want to run out of ram!
 		$mem_check = setMemoryLimit('128M');
-		
+
 		if (!empty($modSettings['package_disable_cache']) || $mem_check || stripos(PHP_OS, 'win') !== false)
 			$package_cache = array();
 		else
@@ -2523,16 +2619,21 @@ function package_flush_cache($trash = false)
 		elseif (!file_exists($filename))
 			@touch($filename);
 
-		package_chmod($filename);
+		$result = package_chmod($filename);
 
-		$fp = fopen($filename, 'r+');
-		if (!$fp && !$trash)
+		// if we are not doing our test pass, then lets do a full write check
+		if (!$trash)
 		{
-			// We should have package_chmod()'d them before, no?!
-			trigger_error('package_flush_cache(): some files are still not writable', E_USER_WARNING);
-			return;
+			// acid test, can we really open this file for writing?
+			$fp = ($result) ? fopen($filename, 'r+') : $result;
+			if (!$fp)
+			{
+				// We should have package_chmod()'d them before, no?!
+				trigger_error('package_flush_cache(): some files are still not writable', E_USER_WARNING);
+				return;
+			}
+			fclose($fp);
 		}
-		fclose($fp);
 	}
 
 	if ($trash)
@@ -2553,6 +2654,7 @@ function package_flush_cache($trash = false)
 
 /**
  * Try to make a file writable.
+ *
  * @param string $filename
  * @param string $perm_state = 'writable'
  * @param bool $track_change = false
@@ -2686,6 +2788,8 @@ function package_chmod($filename, $perm_state = 'writable', $track_change = fals
 }
 
 /**
+ * Used to crypt the supplied ftp password in this session
+ *
  * @param string $pass
  * @return string The encrypted password
  */
@@ -2838,6 +2942,12 @@ function package_create_backup($id = 'backup')
 
 /**
  * Get the contents of a URL, irrespective of allow_url_fopen.
+ *
+ * - reads the contents of an http or ftp address and retruns the page in a string
+ * - will accept up to 3 page redirections (redirectio_level in the function call is private)
+ * - if post_data is supplied, the value and lenght is posted to the given url as form data
+ * - URL must be supplied in lowercase
+ *
  * @param string $url
  * @param string $post_data = ''
  * @param bool $keep_alive = false

+ 36 - 1
Sources/Who.php

@@ -521,7 +521,7 @@ function determineActions($urls, $preferred_prefix = false)
  */
 function Credits($in_admin = false)
 {
-	global $context, $modSettings, $forum_copyright, $forum_version, $boardurl, $txt, $user_info;
+	global $context, $smcFunc, $modSettings, $forum_copyright, $forum_version, $boardurl, $txt, $user_info;
 
 	// Don't blink. Don't even blink. Blink and you're dead.
 	loadLanguage('Who');
@@ -694,6 +694,40 @@ function Credits($in_admin = false)
 		),
 	);
 
+	// support for mods that use the <credits> tag via the package manager
+	if (($mods = cache_get_data('mods_credits', 86400)) === null)
+	{
+		$mods = array();
+		$request = $smcFunc['db_query']('', '
+			SELECT version, name, credits
+			FROM {db_prefix}log_packages
+			WHERE install_state = {int:installed_mods}
+				AND credits != {string:empty}
+				AND SUBSTRING(filename FROM 1 FOR 9) != {string:patch_name}',
+			array(
+				'installed_mods' => 1,
+				'patch_name' => 'smf_patch',
+				'empty' => '',
+			)
+		);
+		while ($row = $smcFunc['db_fetch_assoc']($request))
+		{
+			$credit_info = unserialize($row['credits']);
+
+			$copyright = empty($credit_info['copyright']) ? '' : $txt['credits_copyright'] . ' &copy; ' . $smcFunc['htmlspecialchars']($credit_info['copyright']);
+			$license = empty($credit_info['license']) ? '' : $txt['credits_license'] . ' ' . $smcFunc['htmlspecialchars']($credit_info['license']);
+			$version = $txt['credits_version'] . $row['version'];
+			$title = (empty($credit_info['title']) ? $row['name'] : $smcFunc['htmlspecialchars']($credit_info['title'])) . ' : ' . $version;
+
+			// build this one out and stash it away
+			$mod_name = empty($credit_info['url']) ? '<strong>' . $title . '</strong>' : '<a href="' . $credit_info['url'] . '">' . '<strong>' . $title . '</strong>' . '</a>';
+			$mods[] =  $mod_name . (!empty($license) ? ' | ' . $license  : '') . (!empty($copyright) ? ' | ' . $copyright  : '');
+		}
+		cache_put_data('mods_credits', $mods, 86400);
+	}
+
+	$context['copyrights']['mods'] += $mods;
+
 	if (!$in_admin)
 	{
 		loadTemplate('Who');
@@ -702,6 +736,7 @@ function Credits($in_admin = false)
 		$context['page_title'] = $txt['credits'];
 	}
 
+	// Support for those that want to use a hook as well
 	call_integration_hook('integrate_credits');
 }
 

+ 80 - 313
Themes/default/Packages.template.php

@@ -58,7 +58,7 @@ function template_view_package()
 				<div class="content">
 					', $context['package_readme'], '
 					<span class="floatright">', $txt['package_available_readme_language'], '
-						<select name="readme_language" id="readme_language" onchange="if (this.options[this.selectedIndex].value) window.location.href = smf_prepareScriptUrl(smf_scripturl + \'', '?action=admin;area=packages;sa=', $context['uninstalling'] ? 'uninstall' : 'install', ';package=', $context['filename'], ';readme=\' + this.options[this.selectedIndex].value);">';
+						<select name="readme_language" id="readme_language" onchange="if (this.options[this.selectedIndex].value) window.location.href = smf_prepareScriptUrl(smf_scripturl + \'', '?action=admin;area=packages;sa=', $context['uninstalling'] ? 'uninstall' : 'install', ';package=', $context['filename'], ';readme=\' + this.options[this.selectedIndex].value + \';license=\' + get_selected(\'license_language\'));">';
 							foreach ($context['readmes'] as $a => $b)
 								echo '<option value="', $b, '"', $a === 'selected' ? ' selected="selected"' : '', '>', $b == 'default' ? $txt['package_readme_default'] : ucfirst($b), '</option>';
 			echo '
@@ -69,7 +69,31 @@ function template_view_package()
 			</div>
 			<br />';
 	}
-
+	
+	// Did they specify a license to display?
+	if (isset($context['package_license']))
+	{
+		echo '
+			<div class="cat_bar">
+				<h3 class="catbg">', $txt['package_install_license'], '</h3>
+			</div>
+			<div class="windowbg2">
+				<span class="topslice"><span></span></span>
+				<div class="content">
+					', $context['package_license'], '
+					<span class="floatright">', $txt['package_available_license_language'], '
+						<select name="license_language" id="license_language" onchange="if (this.options[this.selectedIndex].value) window.location.href = smf_prepareScriptUrl(smf_scripturl + \'', '?action=admin;area=packages;sa=install', ';package=', $context['filename'], ';license=\' + this.options[this.selectedIndex].value + \';readme=\' + get_selected(\'readme_language\'));">';
+							foreach ($context['licenses'] as $a => $b)
+								echo '<option value="', $b, '"', $a === 'selected' ? ' selected="selected"' : '', '>', $b == 'default' ? $txt['package_license_default'] : ucfirst($b), '</option>';
+			echo '
+						</select>
+					</span>
+				</div>
+				<span class="botslice"><span></span></span>
+			</div>
+			<br />';
+	}
+	
 	echo '
 		<form action="', $scripturl, '?action=admin;area=packages;sa=', $context['uninstalling'] ? 'uninstall' : 'install', $context['ftp_needed'] ? '' : '2', ';package=', $context['filename'], ';pid=', $context['install_id'], '" onsubmit="submitonce(this);" method="post" accept-charset="', $context['character_set'], '">
 			<div class="cat_bar">
@@ -347,6 +371,21 @@ function template_view_package()
 
 	echo '
 	// ]]></script>';
+	
+	// Get the currently selected item from a select list
+	echo '
+	<script type="text/javascript"><!-- // --><![CDATA[
+	function get_selected(id)
+	{
+		var aSelected = document.getElementById(id);
+		for (var i = 0; i < aSelected.options.length; i++)
+		{
+			if (aSelected.options[i].selected == true)
+				return aSelected.options[i].value;
+		}
+		return aSelected.options[0];
+	}
+	// ]]></script>';
 
 	// And a bit more for database changes.
 	if (!empty($context['database_changes']))
@@ -495,68 +534,16 @@ function template_examine()
 	<br class="clear" />';
 }
 
-function template_view_installed()
+function template_browse()
 {
-	global $context, $settings, $options, $txt, $scripturl;
+	global $context, $settings, $options, $txt, $scripturl, $modSettings, $forum_version;
 
 	echo '
-	<div id="admincenter">
-		<div class="title_bar">
-			<h3 class="titlebg">' . $txt['view_and_remove'] . '</h3>
-		</div>';
+	<div id="admincenter">';
 
-	if (empty($context['installed_mods']))
-	{
-		echo '
-		<div class="information">
-			', $txt['no_mods_installed'], '
-		</div>';
-	}
-	else
+	if ($context['sub_action'] == 'browse')
 	{
 		echo '
-		<table class="table_grid" width="100%">
-		<thead>
-			<tr class="catbg">
-				<th class="first_th" scope="col" width="32"></th>
-				<th class="lefttext" scope="col" width="25%">', $txt['mod_name'], '</th>
-				<th class="lefttext" scope="col" width="25%">', $txt['mod_version'], '</th>
-				<th class="last_th" scope="col" width="49%"></th>
-			</tr>
-		</thead>
-		<tbody>';
-
-		$alt = false;
-		foreach ($context['installed_mods'] as $i => $file)
-		{
-			echo '
-			<tr class="', $alt ? 'windowbg' : 'windowbg2', '">
-				<td><span class="smalltext">', ++$i, '.</span></td>
-				<td><span class="smalltext">', $file['name'], '</span></td>
-				<td><span class="smalltext">', $file['version'], '</span></td>
-				<td align="right"><span class="smalltext"><a href="', $scripturl, '?action=admin;area=packages;sa=uninstall;package=', $file['filename'], ';pid=', $file['id'], '">[ ', $txt['uninstall'], ' ]</a></span></td>
-			</tr>';
-			$alt = !$alt;
-		}
-
-		echo '
-		</tbody>
-		</table>
-		<br />
-		<a href="', $scripturl, '?action=admin;area=packages;sa=flush;', $context['session_var'], '=', $context['session_id'], '">[ ', $txt['delete_list'], ' ]</a>';
-	}
-
-	echo '
-	</div>
-	<br class="clear" />';
-}
-
-function template_browse()
-{
-	global $context, $settings, $options, $txt, $scripturl, $modSettings, $forum_version;
-
-	echo '
-	<div id="admincenter">
 		<div class="cat_bar">
 			<h3 class="catbg">
 				<span class="ie6_header floatleft"><a href="', $scripturl, '?action=helpadmin;help=latest_packages" onclick="return reqWin(this.href);" class="help"><img class="icon" src="', $settings['images_url'], '/helptopics.png" alt="', $txt['help'], '" align="top" /></a> ', $txt['packages_latest'], '</span>
@@ -575,275 +562,55 @@ function template_browse()
 			window.smfForum_sessionid = "', $context['session_id'], '";
 			window.smfForum_sessionvar = "', $context['session_var'], '";';
 
-	// Make a list of already installed mods so nothing is listed twice ;).
-	echo '
+		// Make a list of already installed mods so nothing is listed twice ;).
+		echo '
 			window.smfInstalledPackages = ["', implode('", "', $context['installed_mods']), '"];
 			window.smfVersion = "', $context['forum_version'], '";
 		// ]]></script>';
 
-	if (empty($modSettings['disable_smf_js']))
-		echo '
+		if (empty($modSettings['disable_smf_js']))
+			echo '
 		<script type="text/javascript" src="', $scripturl, '?action=viewsmfile;filename=latest-packages.js"></script>';
 
-	echo '
-		<script type="text/javascript"><!-- // --><![CDATA[
-			var tempOldOnload;
-		// ]]></script>';
-
 		echo '
 		<script type="text/javascript"><!-- // --><![CDATA[
+			var tempOldOnload;
 			smfSetLatestPackages();
 		// ]]></script>';
 
-	echo '
+		echo '
 		<br />
 		<div class="cat_bar">
 			<h3 class="catbg">', $txt['browse_packages'], '</h3>
 		</div>';
-
-	if (!empty($context['available_mods']))
-	{
-		echo '
-		<br />
-		<div class="title_bar">
-			<h3 class="titlebg">', $txt['modification_package'], '</h3>
-		</div>
-
-		<table class="table_grid" width="100%">
-		<thead>
-			<tr class="catbg">
-				<th class="first_th" width="32"></th>
-				<th class="lefttext" width="25%">', $txt['mod_name'], '</th>
-				<th class="lefttext" width="25%">', $txt['mod_version'], '</th>
-				<th class="last_th" width="49%"></th>
-			</tr>
-		</thead>
-		<tbody>';
-
-		$alt = false;
-		foreach ($context['available_mods'] as $i => $package)
-		{
-			echo '
-			<tr class="', $alt ? 'windowbg2' : 'windowbg', '">
-				<td>', ++$i, '.</td>
-				<td>', $package['name'], '</td>
-				<td>
-					', $package['version'];
-
-			if ($package['is_installed'] && !$package['is_newer'])
-				echo '
-					<img src="', $settings['images_url'], '/icons/package_', $package['is_current'] ? 'installed' : 'old', '.png" alt="" class="centericon" style="margin-left: 2ex;" />';
-
-			echo '
-				</td>
-				<td align="right">';
-
-			if ($package['can_uninstall'])
-				echo '
-					<a href="', $scripturl, '?action=admin;area=packages;sa=uninstall;package=', $package['filename'], ';pid=', $package['installed_id'], '">[ ', $txt['uninstall'], ' ]</a>';
-			elseif ($package['can_upgrade'])
-				echo '
-					<a href="', $scripturl, '?action=admin;area=packages;sa=install;package=', $package['filename'], '">[ ', $txt['package_upgrade'], ' ]</a>';
-			elseif ($package['can_install'])
-				echo '
-					<a href="', $scripturl, '?action=admin;area=packages;sa=install;package=', $package['filename'], '">[ ', $txt['install_mod'], ' ]</a>';
-
-			echo '
-					<a href="', $scripturl, '?action=admin;area=packages;sa=list;package=', $package['filename'], '">[ ', $txt['list_files'], ' ]</a>
-					<a href="', $scripturl, '?action=admin;area=packages;sa=remove;package=', $package['filename'], ';', $context['session_var'], '=', $context['session_id'], '"', $package['is_installed'] && $package['is_current'] ? ' onclick="return confirm(\'' . $txt['package_delete_bad'] . '\');"' : '', '>[ ', $txt['package_delete'], ' ]</a>
-				</td>
-			</tr>';
-			$alt = !$alt;
-		}
-
-		echo '
-		</tbody>
-		</table>';
-	}
-
-	if (!empty($context['available_avatars']))
-	{
-		echo '
-		<br />
-		<div class="title_bar">
-			<h3 class="titlebg">', $txt['avatar_package'], '</h3>
-		</div>
-		<table class="table_grid" width="100%">
-		<thead>
-			<tr class="catbg">
-				<th class="first_th" width="32"></th>
-				<th class="lefttext" width="25%">', $txt['mod_name'], '</th>
-				<th class="lefttext" width="25%">', $txt['mod_version'], '</th>
-				<th lass="last_th" width="49%"></th>
-			</tr>
-		</thead>
-		<tbody>';
-
-		foreach ($context['available_avatars'] as $i => $package)
-		{
-			echo '
-			<tr class="windowbg2">
-				<td>', ++$i, '.</td>
-				<td>', $package['name'], '</td>
-				<td>', $package['version'];
-
-			if ($package['is_installed'] && !$package['is_newer'])
-				echo '
-					<img src="', $settings['images_url'], '/icons/package_', $package['is_current'] ? 'installed' : 'old', '.png" alt="" class="centericon" style="margin-left: 2ex;" />';
-
-			echo '
-				</td>
-				<td align="right">';
-
-		if ($package['can_uninstall'])
-			echo '
-					<a href="', $scripturl, '?action=admin;area=packages;sa=uninstall;package=', $package['filename'], ';pid=', $package['installed_id'], '">[ ', $txt['uninstall'], ' ]</a>';
-		elseif ($package['can_upgrade'])
-			echo '
-					<a href="', $scripturl, '?action=admin;area=packages;sa=install;package=', $package['filename'], '">[ ', $txt['package_upgrade'], ' ]</a>';
-		elseif ($package['can_install'])
-			echo '
-					<a href="', $scripturl, '?action=admin;area=packages;sa=install;package=', $package['filename'], '">[ ', $txt['install_mod'], ' ]</a>';
-
-		echo '
-					<a href="', $scripturl, '?action=admin;area=packages;sa=list;package=', $package['filename'], '">[ ', $txt['list_files'], ' ]</a>
-					<a href="', $scripturl, '?action=admin;area=packages;sa=remove;package=', $package['filename'], ';', $context['session_var'], '=', $context['session_id'], '"', $package['is_installed'] && $package['is_current'] ? ' onclick="return confirm(\'' . $txt['package_delete_bad'] . '\');"' : '', '>[ ', $txt['package_delete'], ' ]</a>
-				</td>
-			</tr>';
-		}
-
-		echo '
-		</tbody>
-		</table>';
-	}
-
-	if (!empty($context['available_languages']))
-	{
-		echo '
-		<br />
-		<div class="title_bar">
-			<h3 class="titlebg">' . $txt['language_package'] . '</h3>
-		</div>
-		<table class="table_grid" width="100%">
-		<thead>
-			<tr class="catbg">
-				<th class="first_th" width="32"></th>
-				<th class="lefttext" width="25%">', $txt['mod_name'], '</th>
-				<th class="lefttext" width="25%">', $txt['mod_version'], '</th>
-				<th class="last_th" width="49%"></th>
-			</tr>
-		</thead>
-		<tbody>';
-
-		foreach ($context['available_languages'] as $i => $package)
-		{
-			echo '
-			<tr class="windowbg">
-				<td>' . ++$i . '.</td>
-				<td>' . $package['name'] . '</td>
-				<td>' . $package['version'];
-
-			if ($package['is_installed'] && !$package['is_newer'])
-				echo '
-					<img src="', $settings['images_url'], '/icons/package_', $package['is_current'] ? 'installed' : 'old', '.png" alt="" class="centericon" style="margin-left: 2ex;" />';
-
-			echo '
-				</td>
-				<td align="right">';
-
-		if ($package['can_uninstall'])
-			echo '
-					<a href="', $scripturl, '?action=admin;area=packages;sa=uninstall;package=', $package['filename'], ';pid=', $package['installed_id'], '">[ ', $txt['uninstall'], ' ]</a>';
-		elseif ($package['can_upgrade'])
-			echo '
-					<a href="', $scripturl, '?action=admin;area=packages;sa=install;package=', $package['filename'], '">[ ', $txt['package_upgrade'], ' ]</a>';
-		elseif ($package['can_install'])
-			echo '
-					<a href="', $scripturl, '?action=admin;area=packages;sa=install;package=', $package['filename'], '">[ ', $txt['install_mod'], ' ]</a>';
-
-		echo '
-					<a href="', $scripturl, '?action=admin;area=packages;sa=list;package=', $package['filename'], '">[ ', $txt['list_files'], ' ]</a>
-					<a href="', $scripturl, '?action=admin;area=packages;sa=remove;package=', $package['filename'], ';', $context['session_var'], '=', $context['session_id'], '"', $package['is_installed'] && $package['is_current'] ? ' onclick="return confirm(\'' . $txt['package_delete_bad'] . '\');"' : '', '>[ ', $txt['package_delete'], ' ]</a>
-				</td>
-			</tr>';
-		}
-
-		echo '
-		</tbody>
-		</table>';
 	}
 
-	if (!empty($context['available_other']))
+	$mods_available = false;
+	foreach ($context['modification_types'] as $type)
 	{
-		echo '
-		<br />
-		<div class="title_bar">
-			<h3 class="titlebg">' . $txt['unknown_package'] . '</h3>
-		</div>
-		<table class="table_grid" width="100%">
-		<thead>
-			<tr class="catbg">
-				<th class="first_th" width="32"></th>
-				<th class="lefttext" width="25%">', $txt['mod_name'], '</th>
-				<th class="lefttext" width="25%">', $txt['mod_version'], '</th>
-				<th class="lasst_th" width="49%"></th>
-			</tr>
-		</thead>
-		<tbody>';
-
-		foreach ($context['available_other'] as $i => $package)
+		if (!empty($context['available_' . $type]))
 		{
 			echo '
-			<tr class="windowbg2">
-				<td>' . ++$i . '.</td>
-				<td>' . $package['name'] . '</td>
-				<td>' . $package['version'];
-
-			if ($package['is_installed'] && !$package['is_newer'])
-				echo '
-					<img src="', $settings['images_url'], '/icons/package_', $package['is_current'] ? 'installed' : 'old', '.png" alt="" class="centericon" style="margin-left: 2ex;" />';
-
-			echo '
-				</td>
-				<td align="right">';
-
-		if ($package['can_uninstall'])
-			echo '
-					<a href="', $scripturl, '?action=admin;area=packages;sa=uninstall;package=', $package['filename'], ';pid=', $package['installed_id'], '">[ ', $txt['uninstall'], ' ]</a>';
-		elseif ($package['can_upgrade'])
-			echo '
-					<a href="', $scripturl, '?action=admin;area=packages;sa=install;package=', $package['filename'], '">[ ', $txt['package_upgrade'], ' ]</a>';
-		elseif ($package['can_install'])
-			echo '
-					<a href="', $scripturl, '?action=admin;area=packages;sa=install;package=', $package['filename'], '">[ ', $txt['install_mod'], ' ]</a>';
-
-		echo '
-					<a href="', $scripturl, '?action=admin;area=packages;sa=list;package=', $package['filename'], '">[ ', $txt['list_files'], ' ]</a>
-					<a href="', $scripturl, '?action=admin;area=packages;sa=remove;package=', $package['filename'], ';', $context['session_var'], '=', $context['session_id'], '"', $package['is_installed'] && $package['is_current'] ? ' onclick="return confirm(\'' . $txt['package_delete_bad'] . '\');"' : '', '>[ ', $txt['package_delete'], ' ]</a>
-				</td>
-			</tr>';
+		<br />';
+		
+			template_show_list('packages_lists_' . $type);
+			$mods_available = true;
 		}
-
-		echo '
-		</tbody>
-		</table>';
 	}
 
-	if (empty($context['available_mods']) && empty($context['available_avatars']) && empty($context['available_languages']) && empty($context['available_other']))
+	if (!$mods_available)
 		echo '
-		<div class="information">', $txt['no_packages'], '</div>';
+		<div class="information">', $context['sub_action'] == 'browse' ? $txt['no_packages'] : $txt['no_mods_installed'], '</div>';
 
+	if ($context['sub_action'] == 'browse')
 	echo '
 		<div class="flow_auto">
-			<div class="padding smalltext floatleft">
-				', $txt['package_installed_key'], '
-				<img src="', $settings['images_url'], '/icons/package_installed.png" alt="" class="centericon" style="margin-left: 1ex;" /> ', $txt['package_installed_current'], '
-				<img src="', $settings['images_url'], '/icons/package_old.png" alt="" class="centericon" style="margin-left: 2ex;" /> ', $txt['package_installed_old'], '
-			</div>
-			<div class="padding smalltext floatright">
+			<div class="padding">
 				<a class="button_link" href="#" onclick="document.getElementById(\'advanced_box\').style.display = document.getElementById(\'advanced_box\').style.display == \'\' ? \'none\' : \'\'; return false;">', $txt['package_advanced_button'], '</a>
 			</div>
-		</div>
+		</div>';
+		
+	echo '
 		<form action="', $scripturl, '?action=admin;area=packages;sa=browse" method="get">
 			<div id="advanced_box" style="display: none;">
 				<div class="cat_bar">
@@ -859,7 +626,7 @@ function template_browse()
 							<dt>
 								<strong>', $txt['package_emulate'], ':</strong><br />
 								<span class="smalltext">
-									<a href="#" onclick="document.getElementById(\'ve\').value = \'', $forum_version, '\'; return false">', $txt['package_emulate_revert'], '</a>
+									<a href="#" onclick="document.getElementById(\'ve\').value = \'', $forum_version, '\';document.getElementsByName(\'version_emulate\')[0].value = \'', $forum_version, '\';return false">', $txt['package_emulate_revert'], '</a>
 								</span>
 							</dt>
 							<dd>
@@ -881,8 +648,8 @@ function template_browse()
 	<br class="clear" />
 	<script type="text/javascript" src="', $settings['default_theme_url'], '/scripts/suggest.js?fin20"></script>
 	<script type="text/javascript"><!-- // --><![CDATA[
-			var oAddMemberSuggest = new smc_AutoSuggest({
-			sSelf: \'oAddMemberSuggest\',
+			var oAddVersionSuggest = new smc_AutoSuggest({
+			sSelf: \'oAddVersionSuggest\',
 			sSessionId: smf_session_id,
 			sSessionVar: smf_session_var,
 			sControlId: \'ve\',
@@ -1039,10 +806,10 @@ function template_servers()
 							<input type="file" name="package" size="38" class="input_file" />
 						</dd>
 					</dl>
-					<div class="righttext">
-						<input type="submit" value="' . $txt['package_upload'] . '" class="button_submit" />
-						<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '" />
-					</div>
+					<hr class="hrcolor" />
+					<input type="submit" value="' . $txt['package_upload'] . '" class="button_submit" />
+					<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '" />
+					<br class="clear_right" />
 				</form>
 			</div>
 			<span class="botslice"><span></span></span>
@@ -1320,10 +1087,10 @@ function template_install_options()
 					</dl>
 					<label for="package_make_backups"><input type="checkbox" name="package_make_backups" id="package_make_backups" value="1" class="input_check"', $context['package_make_backups'] ? ' checked="checked"' : '', ' /> ', $txt['package_install_options_make_backups'], '</label><br /><br />
 					<label for="package_make_full_backups"><input type="checkbox" name="package_make_full_backups" id="package_make_full_backups" value="1" class="input_check"', $context['package_make_full_backups'] ? ' checked="checked"' : '', ' /> ', $txt['package_install_options_make_full_backups'], '</label><br /><br />
-					<div class="righttext">
-						<input type="submit" name="save" value="', $txt['save'], '" class="button_submit" />
-						<input type="hidden" name="', $context['session_var'], '" value="', $context['session_id'], '" />
-					</div>
+					<hr class="hrcolor" />
+					<input type="submit" name="save" value="', $txt['save'], '" class="button_submit" />
+					<input type="hidden" name="', $context['session_var'], '" value="', $context['session_id'], '" />
+					<br class="clear_right" />
 				</form>
 			</div>
 			<span class="botslice"><span></span></span>
@@ -1837,10 +1604,10 @@ function template_file_permissions()
 
 	echo '
 				<span id="test_ftp_placeholder_full"></span>
-				<div class="righttext padding">
-					<input type="hidden" name="action_changes" value="1" />
-					<input type="submit" value="', $txt['package_file_perms_go'], '" name="go" class="button_submit" />
-				</div>
+				<hr class="hrcolor" />
+				<input type="hidden" name="action_changes" value="1" />
+				<input type="submit" value="', $txt['package_file_perms_go'], '" name="go" class="button_submit" />
+				<br class="clear_right" />
 			</div>
 			<span class="botslice"><span></span></span>
 		</div>';

+ 1 - 1
Themes/default/languages/Help.english.php

@@ -580,7 +580,7 @@ $helptxt['chmod_flags'] = 'You can manually set the permissions you wish to set
 $helptxt['postmod'] = 'This section allows members of the moderation team (with sufficient permissions) to approve any posts and topics before they are shown.';
 
 $helptxt['field_show_enclosed'] = 'Encloses the user input between some text or html.  This will allow you to add more instant message providers, images or an embed etc. For example:<br /><br />
-		&lt;a href="http://website.com/{INPUT}"&gt;&lt;img src="{DEFAULT_IMAGES_URL}/icon.gif" alt="{INPUT}" /&gt;&lt;/a&gt;<br /><br />
+		&lt;a href="http://website.com/{INPUT}"&gt;&lt;img src="{DEFAULT_IMAGES_URL}/icon.png" alt="{INPUT}" /&gt;&lt;/a&gt;<br /><br />
 		Note that you can use the following variables:<br />
 		<ul class="normallist">
 			<li>{INPUT} - The input specified by the user.</li>

+ 19 - 8
Themes/default/languages/Packages.english.php

@@ -29,10 +29,11 @@ $txt['no_mods_installed'] = 'No mods currently installed';
 $txt['browse_installed'] = 'Browse installed mods';
 $txt['uninstall'] = 'Uninstall';
 $txt['delete_list'] = 'Delete Mod List';
-$txt['php_safe_mode'] = 'Sorry, you server has PHP set to SAFE MODE.  This feature is not compatible with SAFE MODE.  Sorry.';
+$txt['package_delete_list_warning'] = 'Are you sure you wish to clear the installed modification list?';
+$txt['php_safe_mode'] = 'Sorry, you server has PHP set to SAFE MODE.  This feature is not compatible with SAFE MODE.';
 $txt['lets_try_anyway'] = 'Let me try anyway.';
 
-$txt['package_manager_desc'] = 'From the package manager you can download and install modifications to your forum through an easy to use interface.';
+$txt['package_manager_desc'] = 'From this easy to use interface, you can download and install modifications for use on your forum.';
 $txt['installed_packages_desc'] = 'You can use the interface below to view those packages currently installed on the forum, and remove the ones you no longer require.';
 $txt['download_packages_desc'] = 'From this section you can choose to either download new packages from package servers, or upload a package file directly to the forum.';
 
@@ -50,8 +51,8 @@ $txt['remove'] = 'Remove';
 $txt['package_type'] = 'Package Type';
 $txt['archiving'] = 'Archiving';
 $txt['extracting'] = 'Extracting';
-$txt['avatars_extracted'] = 'The avatars have been extracted, you can now use them.';
-$txt['language_extracted'] = 'The language pack has been extracted, you can now use it (by setting it in your settings).';
+$txt['avatars_extracted'] = 'The avatars have been installed, you should now be able to use them.';
+$txt['language_extracted'] = 'The language pack has been installed, you can now enable its use in the language settings area of your admin control panel.';
 
 $txt['mod_name'] = 'Mod Name';
 $txt['mod_version'] = 'Version';
@@ -79,6 +80,7 @@ $txt['packages_latest_fetch'] = 'Attempting to fetch the most popular and recent
 $txt['package_upgrade'] = 'Upgrade';
 $txt['package_uninstall_readme'] = 'Uninstallation Readme';
 $txt['package_install_readme'] = 'Installation Readme';
+$txt['package_install_license'] = 'License';
 $txt['package_install_type'] = 'Type';
 $txt['package_install_action'] = 'Action';
 $txt['package_install_desc'] = 'Description';
@@ -97,11 +99,16 @@ $txt['execute_database_changes'] = 'Adapt Database';
 $txt['execute_hook_add'] = 'Add Hook';
 $txt['execute_hook_remove'] = 'Remove Hook';
 $txt['execute_hook_action'] = 'Adapting hook %1$s';
+$txt['package_requires'] = 'Requires Modification';
+$txt['package_check_for'] = 'Check for installation:';
+$txt['execute_credits_add'] = 'Add Credits';
+$txt['execute_credits_remove'] = 'Remove Credits';
+$txt['execute_credits_action'] = 'Credits: %1$s';
 
 $txt['package_install_actions'] = 'Installations actions for';
 $txt['package_will_fail_title'] = 'Error in Package Installation';
 $txt['package_will_fail_warning'] = 'At least one error was encountered during a test installation of this package.
-	It is <strong>strongly</strong> recommended that you do not continue with installation unless you know what you are doing, and have made a backup very recently.
+	It is <strong>strongly</strong> recommended that you do not continue with installation unless you know what you are doing, and have made a backup very recently.<br />
 	This error may be caused by a conflict between the package you\'re trying to install and another package you have already installed, an error in the package, a package which requires another package that you don\'t have installed yet, or a package designed for another version of SMF.';
 // Don't use entities in the below string.
 $txt['package_will_fail_popup'] = 'Are you sure you wish to continue installing this modification, even though it will not install successfully?';
@@ -123,7 +130,7 @@ $txt['package_action_success'] = '<strong>Test successful</strong>';
 $txt['package_action_skipping'] = '<strong>Skipping file</strong>';
 
 $txt['package_uninstall_actions'] = 'Uninstall Actions';
-$txt['package_uninstall_done'] = 'The package has been uninstalled, it should no longer take effect.';
+$txt['package_uninstall_done'] = 'The package has been successfully uninstalled.';
 $txt['package_uninstall_cannot'] = 'This package cannot be uninstalled, because there is no uninstaller!<br /><br />Please contact the mod author for more information.';
 
 $txt['package_install_options'] = 'Installation Options';
@@ -138,7 +145,7 @@ $txt['package_install_options_make_full_backups'] = 'Create an entire backup (ex
 $txt['package_ftp_necessary'] = 'FTP Information Required';
 $txt['package_ftp_why'] = 'Some of the files the package manager needs to modify are not writable.  This needs to be changed by logging into FTP and using it to chmod or create the files and folders.  Your FTP information may be temporarily cached for proper operation of the package manager. Note you can also do this manually using an FTP client - to view a list of the affected files please click <a href="#" onclick="%1$s">here</a>.';
 $txt['package_ftp_why_file_list'] = 'The following files need to made writable to continue installation:';
-$txt['package_ftp_why_download'] = 'To download packages, the Packages directory and files in it need to be writable - and they are not currently.  The package manager can use your FTP information to fix this.';
+$txt['package_ftp_why_download'] = 'In order to download packages, the Packages directory, and any files in it, need to be writable.  Currently the system does not have the needed write permissions to this directory.  The package manager can use your FTP information to fix this problem.';
 $txt['package_ftp_server'] = 'FTP Server';
 $txt['package_ftp_port'] = 'Port';
 $txt['package_ftp_username'] = 'Username';
@@ -184,10 +191,12 @@ $txt['package_db_remove_table'] = 'Drop table &quot;%1$s&quot;';
 $txt['package_db_remove_column'] = 'Remove column &quot;%2$s&quot; from &quot;%1$s&quot;';
 $txt['package_db_remove_index'] = 'Remove index &quot;%1$s&quot; from &quot;%2$s&quot;';
 
-$txt['package_advanced_button'] = 'Advanced';
+$txt['package_advanced_button'] = 'Emulation Support';
 $txt['package_advanced_options'] = 'Advanced Options';
 $txt['package_apply'] = 'Apply';
 $txt['package_emulate'] = 'Emulate Version';
+$txt['package_emulate_install'] = 'Install Emulating:';
+$txt['package_emulate_uninstall'] = 'Uninstall Emulating:';
 $txt['package_emulate_revert'] = 'Revert';
 $txt['package_emulate_desc'] = 'Sometimes packages are locked to early versions of SMF but remain compatible with a newer version. Here you can choose to &quot;emulate&quot; a different SMF version within the package manager.';
 
@@ -263,5 +272,7 @@ $txt['package_confirm_go_back'] = 'Go back';
 
 $txt['package_readme_default'] = 'Default';
 $txt['package_available_readme_language'] = 'Available Readme Languages:';
+$txt['package_license_default'] = 'Default';
+$txt['package_available_license_language'] = 'Available License Languages:';
 
 ?>

+ 3 - 0
Themes/default/languages/Who.english.php

@@ -141,6 +141,9 @@ $txt['credits_groups_marketing'] = 'Marketing';
 $txt['credits_groups_internationalizers'] = 'Localizers';
 $txt['credits_groups_servers'] = 'Servers Administrators';
 $txt['credits_groups_site'] = 'Site Administrators';
+$txt['credits_license'] = 'License';
+$txt['credits_copyright'] = 'Copyright';
+$txt['credits_version'] = 'Version';
 // Replace "English" with the name of this language pack in the string below.
 $txt['credits_groups_translation'] = 'English Translation';
 $txt['credits_groups_translators'] = 'Language Translators';