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];
 }