/**
  * Get the repository XML as a string that can be returned to the browser or cached for future use.
  *
  * @param string|null $serverid      The server ID making the request, or null for anonymous.
  * @param string|null $limitPackager Limit the packager returned to at least version X.Y.
  * 
  * @return RepoXML
  */
 public static function GetAsRepoXML($serverid, $limitPackager)
 {
     $repo = new RepoXML();
     $repo->setDescription(ConfigHandler::Get('/package_repository/description'));
     $gpg = new Core\GPG\GPG();
     $keysfound = [];
     $where = [];
     if ($limitPackager) {
         $where[] = 'packager LIKE ' . $limitPackager . '%';
     }
     $packages = PackageRepositoryPackageModel::Find($where, null, 'type DESC, key ASC, version');
     foreach ($packages as $pkg) {
         /** @var PackageRepositoryPackageModel $pkg */
         $package = new PackageXML(null);
         $package->setType($pkg->get('type'));
         $package->setName($pkg->get('name'));
         $package->setVersion($pkg->get('version'));
         $package->setPackager($pkg->get('packager'));
         $package->setDescription($pkg->get('description'));
         $package->setKey($pkg->get('gpg_key'));
         if (!in_array($pkg->get('gpg_key'), $keysfound)) {
             $keysfound[] = $pkg->get('gpg_key');
         }
         $package->setFileLocation(\Core\resolve_link('/packagerepository/download?file=' . $pkg->get('file')));
         $upgrades = $pkg->get('requires');
         foreach ($upgrades as $dat) {
             $package->setRequire($dat['type'], $dat['name'], $dat['version'], $dat['operation']);
         }
         $upgrades = $pkg->get('provides');
         foreach ($upgrades as $dat) {
             $package->setProvide($dat['type'], $dat['name'], $dat['version']);
         }
         $upgrades = $pkg->get('upgrades');
         foreach ($upgrades as $dat) {
             $package->setUpgrade($dat['from'], $dat['to']);
         }
         $screens = $pkg->get('screenshots');
         foreach ($screens as $dat) {
             $f = \Core\Filestore\Factory::File($dat);
             $package->setScreenshot($f->getURL());
         }
         $package->setChangelog($pkg->get('changelog'));
         $repo->addPackage($package);
     }
     return $repo;
 }
	/**
	 * Add a repository to the site.
	 * This will also handle the embedded keys, (as of 2.4.5).
	 *
	 * This contains the first step and second steps.
	 */
	public function repos_add() {
		$request = $this->getPageRequest();
		$view    = $this->getView();

		$site = new UpdateSiteModel();

		$form = Form::BuildFromModel($site);
		$form->set('action', \Core\resolve_link('/updater/repos/add'));
		$form->addElement('submit', array('value' => 'Next'));

		$view->title = 'Add Repo';
		// Needed because dynamic pages do not record navigation.
		$view->addBreadcrumb('Repositories', 'updater/repos');

		$view->assign('form', $form);

		if(!is_dir(GPG_HOMEDIR)){
			// Try to create it?
			if(is_writable(dirname(GPG_HOMEDIR))){
				// w00t
				mkdir(GPG_HOMEDIR);
			}
			else{
				\Core\set_message(GPG_HOMEDIR . ' does not exist and could not be created!  Please fix this before proceeding!', 'error');
				$form = null;
			}
		}
		elseif(!is_writable(GPG_HOMEDIR)){
			\Core\set_message(GPG_HOMEDIR . ' is not writable!  Please fix this before proceeding!', 'error');
			$form = null;
		}


		// This is the logic for step 2 (confirmation).
		// This is after all the template logic from step 1 because it will fallback to that form if necessary.
		if($request->isPost()){
			$url      = $request->getPost('model[url]');
			$username = $request->getPost('model[username]');
			$password = $request->getPost('model[password]');

			// Validate and standardize this repo url.
			// This is because most people will simply type repo.corepl.us.
			if(strpos($url, '://') === false){
				$url = 'http://' . $url;
			}

			// Lookup that URL first!
			if(UpdateSiteModel::Count(array('url' => $url)) > 0){
				\Core\set_message($url . ' is already used!', 'error');
				return;
			}

			// Load up a new Model, that's the easiest way to pull the repo data.
			$model = new UpdateSiteModel();
			$model->setFromArray([
				'url' => $url,
				'username' => $username,
				'password' => $password,
			]);

			// From here on out, populate the previous form with this new model.
			$form = Form::BuildFromModel($model);
			$form->set('action', \Core\resolve_link('/updater/repos/add'));
			$form->addElement('submit', array('value' => 'Next'));
			$view->assign('form', $form);

			/** @var \Core\Filestore\Backends\FileRemote $remote */
			$remote = $model->getFile();

			if($remote->requiresAuthentication()){
				if(!$username){
					\Core\set_message($url . ' requires authentication!', 'error');
					return;
				}
				else{
					\Core\set_message('Invalid credentials for ' . $url, 'error');
					return;
				}
			}

			if(!$model->isValid()){
				\Core\set_message($url . ' does not appear to be a valid repository!', 'error');
				return;
			}

			$repo = new RepoXML();
			$repo->loadFromFile($remote);

			// Make sure the keys are good
			if(!$repo->validateKeys()){
				\Core\set_message('There were invalid/unpublished keys in the repo!  Refusing to import.', 'error');
				return;
			}

			// The very final bit of this logic is to look and see if there's a "confirm" present.
			// If there is, the user clicked accept on the second page and I need to go ahead and import the data.
			if($request->getPost('confirm')){
				$model->set('description', $repo->getDescription());
				$model->save();
				$keysimported = 0;
				$keycount     = sizeof($repo->getKeys());
				$gpg          = new \Core\GPG\GPG();

				foreach($repo->getKeys() as $keyData){
					try{
						$gpg->importKey($keyData['key']);
						++$keysimported;
					}
					catch(Exception $e){
						\Core\set_message('Unable to import key [' . $keyData['key'] . '] from keyserver!', 'error');
					}
				}

				if(!$keycount){
					\Core\set_message('Added repository site successfully!', 'success');
				}
				elseif($keycount != $keysimported){
					\Core\set_message('Added repository site, but unable to import ' . ($keycount-$keysimported) . ' key(s).', 'info');
				}
				else{
					\Core\set_message('Added repository site and imported ' . $keysimported . ' key(s) successfully!', 'success');
				}

				\core\redirect('/updater/repos');
			}

			$view->templatename = 'pages/updater/repos_add2.tpl';
			$view->assign('description', $repo->getDescription());
			$view->assign('keys', $repo->getKeys());
			$view->assign('url', $url);
			$view->assign('username', $username);
			$view->assign('password', $password);
		}
	}
 /**
  * Get the repository XML as a string that can be returned to the browser or cached for future use.
  *
  * @return string
  */
 private function _getRepoXML()
 {
     $repo = new RepoXML();
     $repo->setDescription(ConfigHandler::Get('/package_repository/description'));
     $dir = Factory::Directory(\ConfigHandler::Get('/package_repository/base_directory'));
     $coredir = $dir->getPath() . 'core/';
     $componentdir = $dir->getPath() . 'components/';
     $themedir = $dir->getPath() . 'themes/';
     $tmpdir = Factory::Directory('tmp/exports/');
     $gpg = new Core\GPG\GPG();
     $keysfound = [];
     $private = ConfigHandler::Get('/package_repository/is_private') || strpos($dir->getPath(), ROOT_PDIR) !== 0;
     $addedpackages = 0;
     $failedpackages = 0;
     $iterator = new \Core\Filestore\DirectoryIterator($dir);
     // Only find signed packages.
     $iterator->findExtensions = ['asc'];
     // Recurse into sub directories
     $iterator->recursive = true;
     // No directories
     $iterator->findDirectories = false;
     // Just files
     $iterator->findFiles = true;
     // And sort them by their filename to make things easy.
     $iterator->sortBy('filename');
     // Ensure that the necessary temp directory exists.
     $tmpdir->mkdir();
     foreach ($iterator as $file) {
         /** @var \Core\Filestore\File $file */
         $fullpath = $file->getFilename();
         // Used in the XML file.
         if ($private) {
             $relpath = \Core\resolve_link('/packagerepository/download?file=' . substr($file->getFilename(), strlen($dir->getPath())));
         } else {
             $relpath = $file->getFilename(ROOT_PDIR);
         }
         // Drop the .asc extension.
         $basename = $file->getBasename(true);
         // Tarball of the temporary package
         $tgz = Factory::File($tmpdir->getPath() . $basename);
         $output = [];
         // I need to 1) retrieve and 2) verify the key for this package.
         try {
             $signature = $gpg->verifyFileSignature($fullpath);
             if (!in_array($signature->keyID, $keysfound)) {
                 $repo->addKey($signature->keyID, null, null);
                 $keysfound[] = $signature->keyID;
             }
         } catch (\Exception $e) {
             trigger_error($fullpath . ' was not able to be verified as authentic, (probably because the GPG public key was not available)');
             $failedpackages++;
             continue;
         }
         // decode and untar it in a temp directory to get the package.xml file.
         exec('gpg --homedir "' . GPG_HOMEDIR . '" -q -d "' . $fullpath . '" > "' . $tgz->getFilename() . '" 2>/dev/null', $output, $ret);
         if ($ret) {
             trigger_error('Decryption of file ' . $fullpath . ' failed!');
             $failedpackages++;
             continue;
         }
         exec('tar -xzf "' . $tgz->getFilename() . '" -C "' . $tmpdir->getPath() . '" ./package.xml', $output, $ret);
         if ($ret) {
             trigger_error('Unable to extract package.xml from' . $tgz->getFilename());
             unlink($tmpdir->getPath() . $basename);
             $failedpackages++;
             continue;
         }
         // Read in that package file and append it to the repo xml.
         $package = new PackageXML($tmpdir->getPath() . 'package.xml');
         $package->getRootDOM()->setAttribute('key', $signature->keyID);
         $package->setFileLocation($relpath);
         $repo->addPackage($package);
         $addedpackages++;
         // But I can still cleanup!
         unlink($tmpdir->getPath() . 'package.xml');
         $tgz->delete();
     }
     return $repo->asPrettyXML();
 }
Example #4
0
if(!sizeof($updatesites)){
	CLI::PrintActionStart('No repositories installed');
	CLI::PrintActionStatus('skip');
}
foreach($updatesites as $site){
	CLI::PrintActionStart('Scanning repository ' . $site->get('url'));
	/** @var UpdateSiteModel $site */
	if(!$site->isValid()){
		CLI::PrintActionStatus('failed');
		continue;
	}

	++$sitecount;
	$file = $site->getFile();

	$repoxml = new RepoXML();
	$repoxml->loadFromFile($file);
	$rootpath = dirname($file->getFilename()) . '/';

	CLI::PrintActionStatus('ok');

	foreach($repoxml->getPackages() as $pkg){
		/** @var PackageXML $pkg */

		$n = str_replace(' ', '-', strtolower($pkg->getName()));
		$type = $pkg->getType();
		$location = $pkg->getFileLocation();
		if(strpos($location, '://') === false){
			// The remote location may or may not be fully resolved.
			$location = $rootpath . $location;
		}
Example #5
0
$destdir = BASE_DIR . 'exports/';
$tmpdir = BASE_DIR . 'exports/_tmp/';
// Ensure the export directory exists.
if(!is_dir($destdir)) exec('mkdir -p "' . $destdir . '"');
if(!is_dir($tmpdir)) exec('mkdir -p "' . $tmpdir . '"');


if(file_exists($destdir . 'repo.xml')){
	// w00t, load it up and import the info!
	$repo = new RepoXML($destdir . 'repo.xml');
	// Don't forget to remove any previous components!
	$repo->clearPackages();
}
else{
	// Just a new one works...
	$repo = new RepoXML();
}


// Prompt the user if there's not information already set on the necessary ones.
if(!$repo->getDescription()){
	$desc = \Core\CLI\CLI::PromptUser('Please enter a short description for this repo.', 'textarea');
	$repo->setDescription($desc);
}

// Load in the keys if not set.
if(!sizeof($repo->getKeys())){
	// Find and use the package maintainer's key.
	$out = array();
	exec('gpg --homedir "' . GPG_HOMEDIR . '" --no-permission-warning --list-secret-keys', $out);
	$key = null;
	/**
	 * Perform a lookup on any repository sites installed and get a list of provided pacakges.
	 *
	 * @return array
	 */
	public static function GetUpdates(){
		// Allow this to be cached for x amount of time.  This will save the number of remote requests.
		//if(false && isset($_SESSION['updaterhelper_getupdates']) && $_SESSION['updaterhelper_getupdates']['expire'] <= time()){
		//	return $_SESSION['updaterhelper_getupdates']['data'];
		//}

		// Build a list of components currently installed, this will act as a base.
		$components = array();
		$core       = array();
		$themes     = array();
		$sitecount  = 0;
		$pkgcount   = 0;
		$current    = Core::GetComponents();
		$froze      = \ConfigHandler::Get('/core/updater/versionfreeze');

		// If Core isn't installed yet, GetComponents will yield null.
		if($current === null) $current = array();

		/** @var string $backportVersion Add support for "~bpoXYZ" version strings for backported packages. */
		$coreVersion      = Core::GetVersion();
		$coreVersionParts = Core::VersionSplit($coreVersion);
		$backportVersion  = '~bpo' . $coreVersionParts['major'] . $coreVersionParts['minor'] . $coreVersionParts['point'];

		foreach($current as $c){
			/** @var $c Component_2_1 */
			$n = $c->getKeyName();

			$parts = Core::VersionSplit($c->getVersion());

			if($n == 'core'){
				$core = array(
					'name' => $n,
					'title' => $c->getName(),
					'version' => $c->getVersion(),
					'feature' => $parts['major'] . '.' . $parts['minor'],
					'source' => 'installed',
					'description' => $c->getDescription(),
					'provides' => $c->getProvides(),
					'requires' => $c->getRequires(),
					'location' => null,
					'status' => 'installed',
					'type' => 'core',
					'typetitle' => 'Core',
					'key' => null,
					'destdir' => $c->getBaseDir(),
				);
			}
			else{
				$components[$n] = array(
					'name' => $n,
					'title' => $c->getName(),
					'version' => $c->getVersion(),
					'feature' => $parts['major'] . '.' . $parts['minor'],
					'source' => 'installed',
					'description' => $c->getDescription(),
					'provides' => $c->getProvides(),
					'requires' => $c->getRequires(),
					'location' => null,
					'status' => 'installed',
					'type' => 'components',
					'typetitle' => 'Component ' . $c->getName(),
					'key' => null,
					'destdir' => $c->getBaseDir(),
				);
			}
		}

		foreach(Core::GetDisabledComponents() as $c){
			/** @var $c Component_2_1 */
			$n = $c->getKeyName();

			$parts = Core::VersionSplit($c->getVersion());

			$components[$n] = array(
				'name' => $n,
				'title' => $c->getName(),
				'version' => $c->getVersion(),
				'feature' => $parts['major'] . '.' . $parts['minor'],
				'source' => 'installed',
				'description' => $c->getDescription(),
				'provides' => $c->getProvides(),
				'requires' => $c->getRequires(),
				'location' => null,
				'status' => 'disabled',
				'type' => 'components',
				'typetitle' => 'Component ' . $c->getName(),
				'key' => null,
				'destdir' => $c->getBaseDir(),
			);
		}

		// And repeat for the themes.
		// I need to do a check if they exist because if called from the installer, it may not.
		if(class_exists('ThemeHandler')){
			$currentthemes = ThemeHandler::GetAllThemes();
			if($currentthemes === null) $currentthemes = array();
		}
		else{
			$currentthemes = array();
		}

		foreach($currentthemes as $t){
			/** @var $t Theme */
			$n = $t->getKeyName();

			$parts = Core::VersionSplit($t->getVersion());

			$themes[$n] = array(
				'name' => $n,
				'title' => $t->getName(),
				'version' => $t->getVersion(),
				'feature' => $parts['major'] . '.' . $parts['minor'],
				'source' => 'installed',
				'description' => $t->getDescription(),
				'location' => null,
				'status' => 'installed',
				'type' => 'themes',
				'typetitle' => 'Theme ' . $t->getName(),
				'key' => null,
				'destdir' => $t->getBaseDir(),
			);
		}

		// Now, look up components from all the updates sites.
		// If the system isn't installed yet, then this will not be found.  Just use a blank array.
		if(class_exists('UpdateSiteModel')){
			$updatesites = UpdateSiteModel::Find();
		}
		else{
			$updatesites = array();
		}


		foreach($updatesites as $site){

			if(!$site->isValid()) continue;

			++$sitecount;
			$file = $site->getFile();

			$repoxml = new RepoXML();
			$repoxml->loadFromFile($file);
			$rootpath = dirname($file->getFilename()) . '/';

			foreach($repoxml->getPackages() as $pkg){
				/** @var $pkg PackageXML */
				// Already installed and is up to date, don't do anything.
				//if($pkg->isCurrent()) continue;

				$n = str_replace(' ', '-', strtolower($pkg->getName()));
				$type = $pkg->getType();
				if($n == 'core') $type = 'core'; // Override the core, even though it is a component...
				++$pkgcount;

				switch($type){
					case 'core':
						$vers = $pkg->getVersion();

						// Only display the newest version available.
						if(!Core::VersionCompare($vers, $core['version'], 'gt')) continue;

						// Only display new feature versions if it's not frozen.
						$parts = Core::VersionSplit($pkg->getVersion());
						if($froze && $core['feature'] != $parts['major'] . '.' . $parts['minor']) continue;

						$core = array(
							'name' => $n,
							'title' => $pkg->getName(),
							'version' => $vers,
							'feature' => $parts['major'] . '.' . $parts['minor'],
							'source' => 'repo-' . $site->get('id'),
							'sourceurl' => $site->get('url'),
							'description' => $pkg->getDescription(),
							'provides' => $pkg->getProvides(),
							'requires' => $pkg->getRequires(),
							'location' => $pkg->getFileLocation(),
							'status' =>'update',
							'type' => 'core',
							'typetitle' => 'Core ',
							'key' => $pkg->getKey(),
							'destdir' => ROOT_PDIR,
						);
						break;
					case 'component':
						$vers  = $pkg->getVersion();
						$parts = Core::VersionSplit($pkg->getVersion());
						$packagedWithVersion = $pkg->getRootDOM()->getAttribute('packager');

						if($packagedWithVersion && Core::VersionCompare($packagedWithVersion, $coreVersion, '>')){
							// Skip any package created with a newer version of Core than what is currently installed!
							continue;
						}

						// Is it already loaded in the list?
						if(isset($components[$n])){

							if(strpos($vers, '~bpo') !== false && strpos($vers, $backportVersion) === false){
								// Skip back ported versions not specifically for this version of Core.
								// This will check and see if the string ~bpo is present,
								// and when it is, enforce that it matches exactly this version of Core.
								continue;
							}

							// I only want the newest version.
							if(!Core::VersionCompare($vers, $components[$n]['version'], 'gt')){
								continue;
							}

							// Only display new feature versions if it's not frozen.
							if(
								$froze &&
								$components[$n]['status'] == 'installed' &&
								$components[$n]['feature'] != $parts['major'] . '.' . $parts['minor']
							){
								continue;
							}
						}

						// If it's available in the core, it's an update... otherwise it's new.
						$status = Core::GetComponent($n) ? 'update' : 'new';

						$components[$n] = array(
							'name' => $n,
							'title' => $pkg->getName(),
							'version' => $vers,
							'feature' => $parts['major'] . '.' . $parts['minor'],
							'source' => 'repo-' . $site->get('id'),
							'sourceurl' => $site->get('url'),
							'description' => $pkg->getDescription(),
							'provides' => $pkg->getProvides(),
							'requires' => $pkg->getRequires(),
							'location' => $pkg->getFileLocation(),
							'status' => $status,
							'type' => 'components',
							'typetitle' => 'Component ' . $pkg->getName(),
							'key' => $pkg->getKey(),
							'destdir' => ROOT_PDIR . 'components/' . $n . '/',
						);
						break;
					case 'theme':
						$vers = $pkg->getVersion();
						$parts = Core::VersionSplit($pkg->getVersion());

						// Is it already loaded in the list?
						if(isset($themes[$n])){
							// I only want the newest version.
							if(!Core::VersionCompare($vers, $themes[$n]['version'], 'gt')) continue;

							// Only display new feature versions if it's not frozen.
							if(
								$froze &&
								$themes[$n]['status'] == 'installed' &&
								$themes[$n]['feature'] != $parts['major'] . '.' . $parts['minor']
							){
								continue;
							}
						}

						$status = ThemeHandler::GetTheme($n) ? 'update' : 'new';

						$themes[$n] = array(
							'name' => $n,
							'title' => $pkg->getName(),
							'version' => $vers,
							'feature' => $parts['major'] . '.' . $parts['minor'],
							'source' => 'repo-' . $site->get('id'),
							'sourceurl' => $site->get('url'),
							'description' => $pkg->getDescription(),
							'location' => $pkg->getFileLocation(),
							'status' => $status,
							'type' => 'themes',
							'typetitle' => 'Theme ' . $pkg->getName(),
							'key' => $pkg->getKey(),
							'destdir' => ROOT_PDIR . 'themes/' . $n . '/',
						);
				}

				//var_dump($pkg->asPrettyXML()); die();
			}
		}

		// Give me the components in alphabetical order.
		ksort($components);
		ksort($themes);

		// Cache this for next pass.
		//$_SESSION['updaterhelper_getupdates'] = array();
		//$_SESSION['updaterhelper_getupdates']['data'] = array('core' => $core, 'components' => $components, 'themes' => $themes);
		//$_SESSION['updaterhelper_getupdates']['expire'] = time() + 60;

		return [
			'core'       => $core,
			'components' => $components,
			'themes'     => $themes,
			'sitecount'  => $sitecount,
			'pkgcount'   => $pkgcount,
		];
	}