Example #1
0
	public function getSmarty(){
		if($this->_smarty === null){
			$this->_smarty = new \Smarty();

			/**
			 * @var $caching int
			 *
			 * Leave this off!  Smarty doesn't seem to cache the output of our {css} blocks when this is enabled as of 3.1.27
			 */
			$this->_smarty->caching = \Smarty::CACHING_OFF;
			//$this->_smarty->caching = DEVELOPMENT_MODE ? \Smarty::CACHING_OFF : \Smarty::CACHING_LIFETIME_SAVED;

			$this->_smarty->setCompileDir(TMP_DIR . 'smarty_templates_c');
			$this->_smarty->setCacheDir(TMP_DIR . 'smarty_cache');

			/**
			 * @var $force_compile boolean
			 *
			 * This forces Smarty to (re)compile templates on every invocation.
			 * This setting overrides $compile_check. By default this is FALSE.
			 * This is handy for development and debugging.
			 * It should never be used in a production environment.
			 * If $caching is enabled, the cache file(s) will be regenerated every time.
			 */
			$this->_smarty->force_compile = DEVELOPMENT_MODE ? true : false;

			/**
			 * @var $compile_check boolean
			 *
			 * Upon each invocation of the PHP application,
			 * Smarty tests to see if the current template has changed (different timestamp) since the last time it was compiled.
			 * If it has changed, it recompiles that template.
			 * If the template has yet not been compiled at all, it will compile regardless of this setting.
			 * By default this variable is set to TRUE.
			 */
			$this->_smarty->compile_check = DEVELOPMENT_MODE ? true : false;

			$this->_smarty->assign('__core_template', $this);
		}
		return $this->_smarty;
	}
	public static function _UploadHandler(Form $form) {
		$localfile = \Core\Filestore\Factory::File($form->getElement('upload')->get('value'));
		$localobj = $localfile->getContentsObject();
		if(!$localobj instanceof Core\Filestore\Contents\ContentTGZ){
			$localfile->delete();
			\Core\set_message('Invalid file uploaded', 'error');
			return false;
		}
		
		$tmpdir = $localobj->extract('tmp/installer-' . Core::RandomHex(4));
		
		// There should now be a package.xml metafile inside that temporary directory.
		// Parse it to get the necessary information for this package.
		$metafile = \Core\Filestore\Factory::File($tmpdir->getPath() . 'package.xml');
		if(!$metafile->exists()){
			$localfile->delete();
			$tmpdir->delete();
			\Core\set_message('Invalid package, package does not contain a "package.xml" file.');
			return false;
		}
		
		$pkg     = new PackageXML($metafile->getFilename());
		$key     = str_replace(' ', '-', strtolower($pkg->getName()));
		$name    = $pkg->getName();
		$type    = $pkg->getType();
		$version = $pkg->getVersion();
		
		// Validate the contents of the package.
		if(!(
			$type == 'component' ||
			$type == 'theme' ||
			$type == 'core'
		)){
			$localfile->delete();
			$tmpdir->delete();
			\Core\set_message('Invalid package, package does not appear to be a valid Core package.');
			return false;
		}

		// Now that the data is extracted in a temporary directory, extract every file in the destination.
		/** @var $datadir \Core\Filestore\Directory */
		$datadir = $tmpdir->get('data/');
		if(!$datadir){
			\Core\set_message('Invalid package, package does not contain a "data" directory.');
			return false;
		}
		
		if($type == 'component'){
			$destdir = ROOT_PDIR . 'components/' . $key . '/';
		}
		elseif($type == 'theme'){
			$destdir = ROOT_PDIR . 'themes/' . $key . '/';
		}
		else{
			$destdir = ROOT_PDIR . '/';
		}

		try{
			// Will give me an array of Files in the data directory.
			$files = $datadir->ls(null, true);
			// Used to get the relative path for each contained file.
			$datalen = strlen($datadir->getPath());
			foreach($files as $file){
				if(!$file instanceof \Core\Filestore\Backends\FileLocal) continue;

				// It's a file, copy it over.
				// To do so, resolve the directory path inside the temp data dir.
				$dest = \Core\Filestore\Factory::File($destdir . substr($file->getFilename(), $datalen));
				/** @var $dest \Core\Filestore\Backends\FileLocal */
				$dest->copyFrom($file, true);
			}
		}
		catch(Exception $e){
			// OH NOES!
			$localfile->delete();
			$tmpdir->delete();
			\Core\set_message($e->getMessage(), 'error');
			return false;
		}
		
		
		// Cleanup everything
		$localfile->delete();
		$tmpdir->delete();

		// Clear the cache so the next pageload will pick up on the new components and goodies.
		\Core\Cache::Flush();
		\Core\Templates\Backends\Smarty::FlushCache();
		
		// Print a nice message to the user that it completed.
		\Core\set_message('Successfully installed ' . $name . ' ' . $version, 'success');
		return '/updater';
	}
Example #3
0
			// All Models MUST reside in the global namespace in order to be valid.
			continue;
		}

		$ref = new ReflectionClass($class);
		if(!$ref->getProperty('HasSearch')->getValue()){
			// This model doesn't have the searchable flag, skip it.
			continue;
		}

		CLI::PrintActionStart("Syncing searchable model $class");
		$fac = new ModelFactory($class);
		foreach($fac->get() as $m){
			/** @var Model $m */
			$m->set('search_index_pri', '!');
			$m->save();
		}
		CLI::PrintActionStatus('ok');
		$changes[] = "Synced searchable model " . $class;
	}
}

// Flush the system cache, just in case
\Core\Cache::Flush();
\Core\Templates\Backends\Smarty::FlushCache();


CLI::PrintHeader('DONE!');
foreach($changes as $line){
	CLI::PrintLine($line);
}
	/**
	 * Run through and reinstall all components and themes.
	 *
	 * @return int
	 */
	public function reinstallAll() {
		// Admin-only page.
		if(!\Core\user()->checkAccess('g:admin')){
			return View::ERROR_ACCESSDENIED;
		}

		// Just run through every component currently installed and reinstall it.
		// This will just ensure that the component is up to date and correct as per the component.xml metafile.
		$view    = $this->getView();
		$request = $this->getPageRequest();


		if($request->isPost()){
			$view->mode = View::MODE_NOOUTPUT;
			$view->contenttype = View::CTYPE_HTML;
			$view->record = false;
			$view->templatename = null;
			$view->render();

			// Try to perform the reinstall.
			$changes  = array();
			$errors   = array();
			$allpages = [];

			$t = ThemeHandler::GetTheme();

			CLI::PrintHeader('Reinstalling Theme ' . $t->getName());
			if (($change = $t->reinstall(1)) !== false) {

				SystemLogModel::LogInfoEvent('/updater/theme/reinstall', 'Theme ' . $t->getName() . ' reinstalled successfully', implode("\n", $change));

				$changes[] = '<b>Changes to theme [' . $t->getName() . ']:</b><br/>' . "\n" . implode("<br/>\n", $change) . "<br/>\n<br/>\n";
			}

			foreach (Core::GetComponents() as $c) {
				/** @var $c Component_2_1 */

				try{
					if(!$c->isInstalled()) continue;
					if(!$c->isEnabled()) continue;

					CLI::PrintHeader('Reinstalling Component ' . $c->getName());
					// Request the reinstallation
					$change = $c->reinstall(1);

					// 1.0 version components don't support verbose changes :(
					if ($change === true) {
						$changes[] = '<b>Changes to component [' . $c->getName() . ']:</b><br/>' . "\n(list of changes not supported with this component!)<br/>\n<br/>\n";
					}
					// 2.1 components support an array of changes, yay!
					elseif ($change !== false) {
						$changes[] = '<b>Changes to component [' . $c->getName() . ']:</b><br/>' . "\n" . implode("<br/>\n", $change) . "<br/>\n<br/>\n";
					}
					// I don't care about "else", nothing changed if it was false.

					// Get the pages, (for the cleanup operation)
					$allpages = array_merge($allpages, $c->getPagesDefined());
				}
				catch(DMI_Query_Exception $e){
					$changes[] = 'Attempted database changes to component [' . $c->getName() . '], but failed!<br/>';
					//var_dump($e); die();
					$errors[] = array(
						'type' => 'component',
						'name' => $c->getName(),
						'message' => $e->getMessage() . '<br/>' . $e->query,
					);
				}
				catch(Exception $e){
					$changes[] = 'Attempted changes to component [' . $c->getName() . '], but failed!<br/>';
					//var_dump($e); die();
					$errors[] = array(
						'type' => 'component',
						'name' => $c->getName(),
						'message' => $e->getMessage(),
					);
				}
			}

			// Flush any non-existent admin page.
			// These can be created from developers changing their page URLs after the page is already registered.
			// Purging admin-only pages is relatively safe because these are defined in component metafiles anyway.
			CLI::PrintHeader('Cleaning up non-existent pages');
			$pageremovecount = 0;
			foreach(
				\Core\Datamodel\Dataset::Init()
					->select('baseurl')
					->table('page')
					->where('admin = 1')
					->execute() as $row
			){
				$baseurl = $row['baseurl'];

				// This page existed already, no need to do anything :)
				if(isset($allpages[$baseurl])) continue;

				++$pageremovecount;

				// Otherwise, this page was deleted or for some reason doesn't exist in the component list.....
				// BUH BAI
				\Core\Datamodel\Dataset::Init()->delete()->table('page')->where('baseurl = ' . $baseurl)->execute();
				\Core\Datamodel\Dataset::Init()->delete()->table('page_meta')->where('baseurl = ' . $baseurl)->execute();
				CLI::PrintLine("Flushed non-existent admin page: " . $baseurl);
				$changes[] = "<b>Flushed non-existent admin page:</b> " . $baseurl;
			}
			if($pageremovecount == 0){
				CLI::PrintLine('No pages flushed');
			}



			if(sizeof($errors) > 0){
				CLI::PrintHeader('Done, but with errors');
				foreach($errors as $e){
					CLI::PrintError('Error while processing ' . $e['type'] . ' ' . $e['name'] . ': ' . $e['message']);
				}
			}
			else{
				CLI::PrintHeader('DONE!');
			}

			foreach($changes as $str){
				echo $str;
			}

			// Flush the system cache, just in case
			\Core\Cache::Flush();
			\Core\Templates\Backends\Smarty::FlushCache();

			// Increment the version counter.
			$version = ConfigHandler::Get('/core/filestore/assetversion');
			ConfigHandler::Set('/core/filestore/assetversion', ++$version);
		} // End if is post.

		//$page->title = 'Reinstall All Components';
		$this->setTemplate('/pages/admin/reinstallall.tpl');
	}
	public static function PerformInstall($type, $name, $version, $dryrun = false, $verbose = false){

		if($verbose){
			// These are needed to force the output to be sent immediately.
			while ( @ob_end_flush() ); // even if there is no nested output buffer
			if(function_exists('apache_setenv')){
				// This function doesn't exist in CGI mode :/
				apache_setenv('no-gzip', '1');
			}
			ini_set('output_buffering','on');
			ini_set('zlib.output_compression', 0);
			ob_implicit_flush();

			// Give some basic styles for this output.
			echo '<html>
	<head>
	<!-- Yes, the following is 1024 spaces.  This is because some browsers have a 1Kb buffer before they start rendering text -->
	' . str_repeat(" ", 1024) . '
		<style>
			body {
				background: none repeat scroll 0 0 black;
				color: #22EE33;
				font-family: monospace;
			}
		</style>
	</head>
	<body>';
		}

		$timer = microtime(true);
		// Give this script a few more seconds to run.
		set_time_limit(max(90, ini_get('max_execution_time')));

		// This will get a list of all available updates and their sources :)
		if($verbose) self::_PrintHeader('Retrieving Updates');
		$updates = UpdaterHelper::GetUpdates();
		if($verbose){
			self::_PrintInfo('Found ' . $updates['sitecount'] . ' repository site(s)!', $timer);
			self::_PrintInfo('Found ' . $updates['pkgcount'] . ' packages!', $timer);
		}

		// A list of changes that are to be applied, (mainly for the dry run).
		$changes = array();

		// Target in on the specific object we're installing.  Useful for a shortcut.
		switch($type){
			case 'core':
				$initialtarget = &$updates['core'];
				break;
			case 'components':
				$initialtarget = &$updates['components'][$name];
				break;
			case 'themes':
				$initialtarget = &$updates['themes'][$name];
				break;
			default:
				return [
					'status' => 0,
					'message' => '[' . $type . '] is not a valid installation type!',
				];
		}

		// This is a special case for testing the installer UI.
		$test = ($type == 'core' && $version == '99.1337~(test)');

		if($test && $verbose){
			self::_PrintHeader('Performing a test installation!');
		}

		if($test){
			if($verbose){
				self::_PrintInfo('Sleeping for a few seconds... because servers are always slow when you don\'t want them to be!', $timer);
			}
			sleep(4);

			// Also overwrite some of the target's information.
			$repo = UpdateSiteModel::Find(null, 1);
			$initialtarget['source'] = 'repo-' . $repo->get('id');
			$initialtarget['location'] = 'http://corepl.us/api/2_4/tests/updater-test.tgz.asc';
			$initialtarget['destdir'] = ROOT_PDIR;
			$initialtarget['key'] = 'B2BEDCCB';
			$initialtarget['status'] = 'update';

			//if($verbose){
			//	echo '[DEBUG]' . $nl;
			//	var_dump($initialtarget);
			//}
		}

		// Make sure the name and version exist in the updates list.
		// In theory, the latest version of core is the only one displayed.
		if(!$test && $initialtarget['version'] != $version){
			return [
				'status' => 0,
				'message' => $initialtarget['typetitle'] . ' does not have the requested version available.',
				'debug' => [
					'versionrequested' => $version,
					'versionfound' => $initialtarget['version'],
				],
			];
		}

		// A queue of components to check.
		$pendingqueue = array($initialtarget);
		// A queue of components that will be installed that have satisfied dependencies.
		$checkedqueue = array();

		// This will assemble the list of required installs in the correct order.
		// If a given dependency can't be met, the installation will be aborted.
		if($verbose){
			self::_PrintHeader('CHECKING DEPENDENCIES');
		}
		do{
			$lastsizeofqueue = sizeof($pendingqueue);

			foreach($pendingqueue as $k => $c){
				$good = true;

				if(isset($c['requires'])){
					if($verbose){
						self::_PrintInfo('Checking dependencies for ' . $c['typetitle'], $timer);
					}

					foreach($c['requires'] as $r){

						// Sometimes there will be blank requirements in the metafile.
						if(!$r['name']) continue;

						$result = UpdaterHelper::CheckRequirement($r, $checkedqueue, $updates);

						if($result === false){
							// Dependency not met
							return [
								'status' => 0,
								'message' => $c['typetitle'] . ' requires ' . $r['name'] . ' ' . $r['version']
							];
						}
						elseif($result === true){
							// Dependency met via either installed components or new components
							// yay
							if($verbose){
								self::_PrintInfo('Dependency [' . $r['name'] . ' ' . $r['version'] . '] met with already-installed packages.', $timer);
							}
						}
						else{
							if($verbose){
								self::_PrintInfo('Additional package [' . $result['typetitle'] . '] required to meet dependency [' . $r['name'] . ' ' . $r['version'] . '], adding to queue and retrying!', $timer);
							}
							// It's an array of requirements that are needed to satisfy this installation.
							$pendingqueue = array_merge(array($result), $pendingqueue);
							$good = false;
						}
					}
				}
				else{
					if($verbose){
						self::_PrintInfo('Skipping dependency check for ' . $c['typetitle'] . ', no requirements present', $timer);
					}

					// The require key isn't present... OK!
					// This happens with themes, as they do not have any dependency logic.
				}

				if($good === true){
					$checkedqueue[] = $c;
					$changes[] = (($c['status'] == 'update') ? 'Update' : 'Install') .
						' ' . $c['typetitle'] . ' ' . $c['version'];
					unset($pendingqueue[$k]);
				}
			}
		}
		while(sizeof($pendingqueue) && sizeof($pendingqueue) != $lastsizeofqueue);

		// Do validation checks on all these changes.  I need to make sure I have the GPG key for each one.
		// This is done here to save having to download the files from the remote server first.
		foreach($checkedqueue as $target){
			// It'll be validated prior to installation anyway.
			if(!$target['key']) continue;

			$output = array();
			exec('gpg --homedir "' . GPG_HOMEDIR . '" --list-public-keys "' . $target['key'] . '"', $output, $result);
			if($result > 0){
				// Key validation failed!
				if($verbose){
					echo implode("<br/>\n", $output);
				}
				return [
					'status' => 0,
					'message' => $c['typetitle'] . ' failed GPG verification! Is the key ' . $target['key'] . ' installed?'
				];
			}
		}


		// Check that the queued packages have not been locally modified if installed.
		if($verbose){
			self::_PrintHeader('Checking for local modifications');
		}
		foreach($checkedqueue as $target){
			if($target['status'] == 'update'){
				switch($target['type']){
					case 'core':
						$c = Core::GetComponent('core');
						break;
					case 'components':
						$c = Core::GetComponent($target['name']);
						break;
					case 'themes':
						$c = null;
						break;
				}

				if($c){
					// Are there changes?
					if(sizeof($c->getChangedAssets())){
						foreach($c->getChangedAssets() as $change){
							$changes[] = 'Overwrite locally-modified asset ' . $change;
						}
					}
					if(sizeof($c->getChangedFiles())){
						foreach($c->getChangedFiles() as $change){
							$changes[] = 'Overwrite locally-modified file ' . $change;
						}
					}
					if(sizeof($c->getChangedTemplates())){
						foreach($c->getChangedTemplates() as $change){
							$changes[] = 'Overwrite locally-modified template ' . $change;
						}
					}
				}
			}
		}


		// If dry run is enabled, stop here.
		// After this stage, dragons be let loose from thar cages.
		if($dryrun){
			return [
				'status' => 1,
				'message' => 'All dependencies are met, ok to install',
				'changes' => $changes,
			];
		}


		// Reset changes, in this case it'll be what was installed.
		$changes = array();

		// By now, $checkedqueue will contain all the pending changes, theoretically with
		// the initially requested package at the end of the list.
		foreach($checkedqueue as $target){

			if($verbose){
				self::_PrintHeader('PERFORMING INSTALL (' . strtoupper($target['typetitle']) . ')');
			}

			// This package is already installed and up to date.
			if($target['source'] == 'installed'){
				return [
					'status' => 0,
					'message' => $target['typetitle'] . ' is already installed and at the newest version.',
				];
			}
			// If this package is coming from a repo, install it from that repo.
			elseif(strpos($target['source'], 'repo-') !== false){
				/** @var $repo UpdateSiteModel */
				$repo = new UpdateSiteModel(substr($target['source'], 5));
				if($verbose){
					self::_PrintInfo('Using repository ' . $repo->get('url') . ' for installation source', $timer);
				}

				// Setup the remote file that will be used to download from.
				$file = new \Core\Filestore\Backends\FileRemote($target['location']);
				$file->username = $repo->get('username');
				$file->password = $repo->get('password');

				// The initial HEAD request pulls the metadata for the file, and sees if it exists.
				if($verbose){
					self::_PrintInfo('Performing HEAD lookup on ' . $file->getFilename(), $timer);
				}
				if(!$file->exists()){
					return [
						'status' => 0,
						'message' => $target['location'] . ' does not seem to exist!'
					];
				}
				if($verbose){
					self::_PrintInfo('Found a(n) ' . $file->getMimetype() . ' file that returned a ' . $file->getStatus() . ' status.', $timer);
				}

				// Get file contents will download the file.
				if($verbose){
					self::_PrintInfo('Downloading ' . $file->getFilename(), $timer);
				}
				$downloadtimer = microtime(true);
				$obj = $file->getContentsObject();
				// Getting the object simply sets it up, it doesn't download the contents yet.
				$obj->getContents();
				// Now it has :p
				// How long did it take?
				if($verbose){
					self::_PrintInfo('Downloaded ' . $file->getFilesize(true) . ' in ' . (round(microtime(true) - $downloadtimer, 2) . ' seconds'), $timer);
				}

				if(!($obj instanceof \Core\Filestore\Contents\ContentASC)){
					return [
						'status' => 0,
						'message' => $target['location'] . ' does not appear to be a valid GPG signed archive'
					];
				}

				if(!$obj->verify()){
					// Maybe it can at least get the key....
					if($key = $obj->getKey()){
						return [
							'status' => 0,
							'message' => 'Unable to locate public key for ' . $key . '.  Is it installed?'
						];
					}
					return [
						'status' => 0,
						'message' => 'Invalid GPG signature for ' . $target['typetitle'],
					];
				}

				// The object's key must also match what's in the repo.
				if($obj->getKey() != $target['key']){
					return [
						'status' => 0,
						'message' => '!!!WARNING!!!, Key for ' . $target['typetitle'] . ' is valid, but does not match what was expected form the repository data!  This could be a major risk!',
						'debug' => [
							'detectedkey' => $obj->getKey(),
							'expectedkey' => $target['key'],
						],
					];
				}
				if($verbose){
					self::_PrintInfo('Found key ' . $target['key'] . ' for package maintainer, appears to be valid.', $timer);
					exec('gpg --homedir "' . GPG_HOMEDIR . '" --list-public-keys "' . $target['key'] . '"', $output, $result);
					foreach($output as $line){
						if(trim($line)) self::_PrintInfo(htmlentities($line), $timer);
					}
				}

				if($verbose) self::_PrintInfo('Checking write permissions', $timer);
				$dir = \Core\directory($target['destdir']);
				if(!$dir->isWritable()){
					return [
						'status' => 0,
						'message' => $target['destdir'] . ' is not writable!'
					];
				}
				if($verbose) self::_PrintInfo('OK!', $timer);


				// Decrypt the signed file.
				if($verbose) self::_PrintInfo('Decrypting signed file', $timer);

				if(version_compare(Core::GetComponent('core')->getVersionInstalled(), '4.1.1', '<=') && $file->getBaseFilename() == 'download'){
					// HACK < 4.1.2
					// Retrieve the filename from the last part of the URL.
					// This is required because the URL may be /download?file=component/blah.tgz.asc
					$f = substr($file->getFilename(), strrpos($file->getFilename(), '/'), -4);
					/** @var $localfile \Core\Filestore\File */
					$localfile = $obj->decrypt('tmp/updater/' . $f);
				}
				else{
					/** @var $localfile \Core\Filestore\File */
					$localfile = $obj->decrypt('tmp/updater/');
				}

				/** @var $localobj \Core\Filestore\Contents\ContentTGZ */
				$localobj = $localfile->getContentsObject();
				if($verbose) self::_PrintInfo('OK!', $timer);

				// This tarball will be extracted to a temporary directory, then copied from there.
				if($verbose){
					self::_PrintInfo('Extracting tarball ' . $localfile->getFilename(), $timer);
				}
				$tmpdir = $localobj->extract('tmp/installer-' . Core::RandomHex(4));

				// Now that the data is extracted in a temporary directory, extract every file in the destination.
				/** @var $datadir \Core\Filestore\Directory */
				$datadir = $tmpdir->get('data/');
				if(!$datadir){
					return [
						'status' => 0,
						'message' => 'Invalid package, ' . $target['typetitle'] . ', does not contain a "data" directory.'
					];
				}
				if($verbose) self::_PrintInfo('OK!', $timer);


				if($verbose){
					self::_PrintInfo('Installing files into ' . $target['destdir'], $timer);
				}

				// Will give me an array of Files in the data directory.
				$files = $datadir->ls(null, true);
				// Used to get the relative path for each contained file.
				$datalen = strlen($datadir->getPath());
				foreach($files as $file){
					if(!$file instanceof \Core\Filestore\Backends\FileLocal) continue;

					// It's a file, copy it over.
					// To do so, resolve the directory path inside the temp data dir.
					$dest = \Core\Filestore\Factory::File($target['destdir'] . substr($file->getFilename(), $datalen));
					/** @var $dest \Core\Filestore\Backends\FileLocal */
					if($verbose){
						self::_PrintInfo('...' . substr($dest->getFilename(''), 0, 67), $timer);
					}
					$dest->copyFrom($file, true);
				}
				if($verbose) self::_PrintInfo('OK!', $timer);


				// Cleanup the temp directory
				if($verbose){
					self::_PrintInfo('Cleaning up temporary directory', $timer);
				}
				$tmpdir->remove();
				if($verbose) self::_PrintInfo('OK!', $timer);

				$changes[] = 'Installed ' . $target['typetitle'] . ' ' . $target['version'];
			}
		}

		// Clear the cache so the next pageload will pick up on the new components and goodies.
		\Core\Cache::Flush();
		\Core\Templates\Backends\Smarty::FlushCache();

		// Yup, that's it.
		// Just extract the files and Core will autoinstall/autoupgrade everything on the next page view.


		// yay...
		return [
			'status' => 1,
			'message' => 'Performed all operations successfully!',
			'changes' => $changes,
		];
	}