public function index() { $request = $this->getPageRequest(); $view = $this->getView(); $isAdmin = \Core\user()->checkAccess('g:admin'); $serverid = isset($_SERVER['HTTP_X_CORE_SERVER_ID']) ? $_SERVER['HTTP_X_CORE_SERVER_ID'] : null; // If the server ID is set, it should be a 32-digit character. // Anything else and omit. if (strlen($serverid) != 32) { $serverid = null; } elseif (!preg_match('/^[A-Z0-9]*$/', $serverid)) { // Invalid string. $serverid = null; } $ua = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; if (strpos($ua, '(http://corepl.us)') !== false) { /** @var string $uav ex: "Core Plus 1.2.3" */ $uav = str_replace(' (http://corepl.us)', '', $ua); /** @var string $version Just the version, ex: "1.2.3" */ $version = str_replace('Core Plus ', '', $uav); // The set of logic to compare the current version of Core against the version connecting. // This is used primarily to set a class name onto the graphs so that they can be coloured specifically. $v = Core::VersionSplit($version); // These two values are used in the historical map, (as revision may be a bit useless at this scale). $briefVersion = $v['major'] . '.' . $v['minor']; } elseif ($request->getParameter('packager')) { $briefVersion = $request->getParameter('packager'); } else { $briefVersion = null; } // Record this key as connected. if ($serverid) { $licmod = PackageRepositoryLicenseModel::Construct($serverid); $licmod->set('datetime_last_checkin', Core\Date\DateTime::NowGMT()); $licmod->set('ip_last_checkin', REMOTE_IP); $licmod->set('referrer_last_checkin', isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : ''); $licmod->set('useragent_last_checkin', isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''); $licmod->save(); } if ($request->ctype == 'application/xml') { // This is a repo.xml request, usually for debugging purposes, (as the app requests gz compressed versions). $xml = PackageRepositoryPackageModel::GetAsRepoXML($serverid, $briefVersion); $view->mode = View::MODE_NOOUTPUT; $view->contenttype = 'application/xml'; $view->render(); echo $xml->asMinifiedXML(); return; } elseif ($request->ext == 'xml.gz') { // This is a normal from-client request; a compressed repo.xml structure. $xml = PackageRepositoryPackageModel::GetAsRepoXML($serverid, $briefVersion); $view->mode = View::MODE_NOOUTPUT; $view->contenttype = 'application/gzip'; $view->render(); $c = $xml->asMinifiedXML(); echo gzencode($c); return; } else { // This is a browse request. $packages = []; $where = []; if ($briefVersion) { $where[] = 'packager LIKE ' . $briefVersion . '%'; } $allPackages = PackageRepositoryPackageModel::Find($where); foreach ($allPackages as $pkg) { /** @var PackageRepositoryPackageModel $pkg */ $pkgKey = $pkg->get('type') . '-' . $pkg->get('key'); $pkgPackager = $pkg->get('packager'); $pkgVersion = $pkg->get('version'); if (!isset($packages[$pkgKey])) { $packages[$pkgKey] = ['package' => $pkg, 'check' => $pkgVersion, 'min' => $pkgPackager, 'max' => $pkgPackager]; } if (\Core\version_compare($pkgPackager, $packages[$pkgKey]['min'], 'lt')) { // Record the lowest supported Core version for this package $packages[$pkgKey]['min'] = $pkgPackager; } if (\Core\version_compare($pkgPackager, $packages[$pkgKey]['max'], 'gt')) { // Record the higest supported Core version for this package $packages[$pkgKey]['max'] = $pkgPackager; } if (\Core\version_compare($pkgVersion, $packages[$pkgKey]['check'], 'gt')) { // Save the newest component as the reference package for all the display data. $packages[$pkgKey]['check'] = $pkgVersion; $packages[$pkgKey]['package'] = $pkg; } } // Build a list of all supported versions of Core in the database. $allCoreVersions = []; $packagerVersions = \Core\Datamodel\Dataset::Init()->unique(true)->select('packager')->table('package_repository_package')->order('packager')->executeAndGet(); foreach ($packagerVersions as $pkg) { $pkgObject = new \Core\VersionString($pkg); $pkgBase = $pkgObject->major . '.' . $pkgObject->minor; if (!isset($allCoreVersions[$pkgBase])) { $allCoreVersions[$pkgBase] = 'Core Plus ' . $pkgBase; } } krsort($allCoreVersions, SORT_NATURAL); $allCoreVersions = array_merge(['' => '-- All Versions --'], $allCoreVersions); $versionSelector = new Form(); $versionSelector->set('method', 'get'); $versionSelector->addElement('select', ['name' => 'packager', 'options' => $allCoreVersions, 'value' => $briefVersion]); $versionSelector->addElement('submit', ['value' => 'Filter']); $view->assign('version_selector', $versionSelector); $view->assign('packages', $packages); $view->assign('version_selected', $briefVersion); } if ($isAdmin) { $view->addControl(['title' => t('STRING_PACKAGE_REPOSITORY_REBUILD'), 'link' => '/packagerepository/rebuild']); } $view->title = 'Package Repository'; $view->templatename = 'pages/packagerepository/index.tpl'; $view->assign('is_admin', $isAdmin); }
public static function RebuildPackages() { $dir = \Core\Filestore\Factory::Directory(\ConfigHandler::Get('/package_repository/base_directory')); $coredir = $dir->getPath() . 'core/'; $componentdir = $dir->getPath() . 'components/'; $themedir = $dir->getPath() . 'themes/'; $tmpdir = \Core\Filestore\Factory::Directory('tmp/exports/'); $gpg = new Core\GPG\GPG(); $keysfound = []; $addedpackages = 0; $failedpackages = 0; $skippedpackages = 0; $ls = $dir->ls('asc', true); \Core\CLI\CLI::PrintProgressBar(0); $totalPackages = sizeof($ls); $percentEach = 100 / $totalPackages; $currentPercent = 0; // Ensure that the necessary temp directory exists. $tmpdir->mkdir(); foreach ($ls as $file) { /** @var \Core\Filestore\File $file */ $fullpath = $file->getFilename(); $relpath = substr($file->getFilename(), strlen($dir->getPath())); $tmpdirpath = $tmpdir->getPath(); // Drop the .asc extension. $basename = $file->getBasename(true); // Tarball of the temporary package $tgz = \Core\Filestore\Factory::File($tmpdirpath . $basename); $output = []; // I need to 1) retrieve and 2) verify the key for this package. try { $signature = $gpg->verifyFileSignature($fullpath); } 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; } // Extract the package.xml metafile, this is critical! exec('tar -xzf "' . $tgz->getFilename() . '" -C "' . $tmpdirpath . '" ./package.xml', $output, $ret); if ($ret) { trigger_error('Unable to extract package.xml from' . $tgz->getFilename()); unlink($tmpdirpath . $basename); $failedpackages++; continue; } // Read in that package file and append it to the repo xml. $package = new PackageXML($tmpdirpath . 'package.xml'); $package->getRootDOM()->setAttribute('key', $signature->keyID); $package->setFileLocation($relpath); // Core has a few differences than most components. if ($package->getKeyName() == 'core') { $pkgName = 'Core Plus'; $chngName = 'Core Plus'; $type = 'core'; $chngDepth = 3; $chngFile = './data/core/CHANGELOG'; $xmlFile = './data/core/component.xml'; } else { $pkgName = $package->getName(); $chngName = ($package->getType() == 'theme' ? 'Theme/' : '') . $package->getName(); $type = $package->getType(); $chngDepth = 2; $chngFile = './data/CHANGELOG'; $xmlFile = './data/' . ($package->getType() == 'theme' ? 'theme.xml' : 'component.xml'); } // Lookup this package in the database or create if it doesn't exist. $model = PackageRepositoryPackageModel::Find(['type = ' . $package->getType(), 'key = ' . $package->getKeyName(), 'version = ' . $package->getVersion()], 1); if (!$model) { $model = new PackageRepositoryPackageModel(); $model->set('type', $type); $model->set('key', $package->getKeyName()); $model->set('version', $package->getVersion()); } // Set the data provided by the package.xml file. $model->set('name', $pkgName); $model->set('gpg_key', $package->getKey()); $model->set('packager', $package->getPackager()); $model->set('file', $relpath); $model->set('description', $package->getDescription()); $model->set('requires', $package->getRequires()); $model->set('provides', $package->getProvides()); $model->set('upgrades', $package->getUpgrades()); unlink($tmpdirpath . 'package.xml'); // Extract out the CHANGELOG file, this is not so critical. // I need strip-components=2 to drop off the "." and "data" prefixes. exec('tar -xzf "' . $tgz->getFilename() . '" -C "' . $tmpdirpath . '" --strip-components=' . $chngDepth . ' ' . $chngFile, $output, $ret); // If there is a CHANGELOG, parse that too! if (file_exists($tmpdirpath . 'CHANGELOG')) { try { $ch = new Core\Utilities\Changelog\Parser($chngName, $tmpdirpath . 'CHANGELOG'); $ch->parse(); // Get the version for this iteration. $chsec = $ch->getSection($model->get('version')); $model->set('packager_name', $chsec->getPackagerName()); $model->set('packager_email', $chsec->getPackagerEmail()); $model->set('datetime_released', $chsec->getReleasedDateUTC()); $model->set('changelog', $chsec->fetchAsHTML(null)); } catch (Exception $e) { // meh, we just won't have a changelog. } finally { if (file_exists($tmpdirpath . 'CHANGELOG')) { // Cleanup unlink($tmpdirpath . 'CHANGELOG'); } } } // Retrieve out the screenshots from this component. exec('tar -xzf "' . $tgz->getFilename() . '" -O ' . $xmlFile . ' > "' . $tmpdirpath . 'comp.xml"', $output, $ret); if (file_exists($tmpdirpath . 'comp.xml')) { try { $images = []; $c = new Component_2_1($tmpdirpath . 'comp.xml'); $screens = $c->getScreenshots(); if (sizeof($screens)) { foreach ($screens as $s) { // Extract out this screen and save it to the filesystem. $archivedFile = dirname($xmlFile) . '/' . $s; $localFile = \Core\Filestore\Factory::File('public/packagerepo-screens/' . $model->get('type') . '-' . $model->get('key') . '-' . $model->get('version') . '/' . basename($s)); // Write something into the file so that it exists on the filesystem. $localFile->putContents(''); // And now tar can extract directly to that destination! exec('tar -xzf "' . $tgz->getFilename() . '" -O ' . $archivedFile . ' > "' . $localFile->getFilename() . '"', $output, $ret); if (!$ret) { // Return code should be 0 on a successful write. $images[] = $localFile->getFilename(false); } } } $model->set('screenshots', $images); } catch (Exception $e) { // meh, we just won't have images.. } finally { if (file_exists($tmpdirpath . 'comp.xml')) { // Cleanup unlink($tmpdirpath . 'comp.xml'); } } } if ($model->changed()) { $model->save(true); $addedpackages++; } else { $skippedpackages++; } // But I can still cleanup! $tgz->delete(); $currentPercent += $percentEach; \Core\CLI\CLI::PrintProgressBar($currentPercent); } // Commit everything! PackageRepositoryPackageModel::CommitSaves(); return ['updated' => $addedpackages, 'skipped' => $skippedpackages, 'failed' => $failedpackages]; }