示例#1
0
	/**
	 * Test that a message can be written to a log file.
	 *
	 * @return \Core\Utilities\Logger\LogFile The log file created.
	 */
	public function testWrite(){
		$type = 'testphpunit';
		$msg  = \BaconIpsumGenerator::Make_a_Sentence();
		$code = '/test/' . Core::RandomHex(6);

		// First, I'll test the functional method.
		\Core\Utilities\Logger\append_to($type, $msg, $code);

		// Now a file should exist called testphpunit.log that contains the line above.
		$this->assertTrue(file_exists(ROOT_PDIR . 'logs/' . $type . '.log'));
		$contents = file_get_contents(ROOT_PDIR . 'logs/' . $type . '.log');
		$this->assertContains($msg, $contents);
		$this->assertContains($code, $contents);

		// And now the class method, (should be identical).
		$type = 'testphpunit';
		$msg  = \BaconIpsumGenerator::Make_a_Sentence();
		$code = '/test/' . Core::RandomHex(6);

		// First, I'll test the functional method.
		$log = new \Core\Utilities\Logger\LogFile($type);
		$log->write($msg, $code);

		// Now a file should exist called testphpunit.log that contains the line above.
		$this->assertTrue($log->exists());
		$contents = $log->getContents();
		$this->assertContains($msg, $contents);
		$this->assertContains($code, $contents);

		return $log;
	}
示例#2
0
 /**
  * Test the creation and validation of a good nonce.
  *
  * This will use the shortcut methods to perform the operations.
  */
 public function testQuickCreateValidNonce()
 {
     $randomdata = Core::RandomHex(90);
     // Make sure core::randomhex gives me the correct number of characters.
     $this->assertEquals(90, strlen($randomdata));
     $key = NonceModel::Generate('10 minutes', $randomdata);
     $this->assertTrue($key != '' && strlen($key) >= 40);
     // Do something blah
     // ..... and check it!
     $this->assertTrue(NonceModel::ValidateAndUse($key, $randomdata));
 }
 public function render()
 {
     if (!$this->get('basedir')) {
         throw new Exception('MultiFileInput cannot be rendered without a basedir attribute!');
     }
     // Make sure it ends with a trailing slash.
     if (substr($this->get('basedir'), -1) != '/') {
         $this->_attributes['basedir'] .= '/';
     }
     //var_dump($_SESSION['multifileinputobjects'], serialize($this->_attributes)); die();
     // This is a slightly different element than the traditional form system, as it must be able to be called without
     // the rest of the form system on submit.
     // This is because this system will do an ajax submit to do the actual upload.
     if (!is_array(\Core\Session::Get('multifileinputobjects'))) {
         \Core\Session::Set('multifileinputobjects', []);
     }
     // I don't need this key to be cryptographically secure, just generally unique.
     $key = md5(serialize($this->_attributes));
     foreach (\Core\Session::Get('multifileinputobjects') as $obj) {
         if (!isset($obj['key'])) {
             continue;
         }
         if ($obj['key'] == $key) {
             $this->set('id', $obj['id']);
         }
     }
     if (!isset($this->_attributes['id'])) {
         // This system requires a valid id.
         $this->set('id', 'multifileinput-' . Core::RandomHex('2'));
     }
     $this->set('key', $key);
     $this->set('uploadkey', $key);
     // Convert the string representation of a filesize to the raw bytes.
     $size = strtoupper(str_replace(' ', '', ini_get('upload_max_filesize')));
     if (strpos($size, 'G') !== false) {
         $size = preg_replace('/[^0-9]/', '', $size);
         $size = $size * (1024 * 1024 * 1024);
     } elseif (strpos($size, 'M') !== false) {
         $size = preg_replace('/[^0-9]/', '', $size);
         $size = $size * (1024 * 1024);
     } elseif (strpos($size, 'K') !== false) {
         $size = preg_replace('/[^0-9]/', '', $size);
         $size = $size * 1024;
     }
     $this->set('maxsize', $size);
     // Now that the session variable has been initialized, the traditional session variable is reliable.
     $_SESSION['multifileinputobjects'][$key] = array('obj' => serialize($this), 'key' => $key, 'id' => $this->_attributes['id']);
     return parent::render();
 }
示例#4
0
 protected function setUp()
 {
     // Setup some variables that will be used throughout this method.
     $title = 'Blog Bug 409 [' . Core::RandomHex(6) . ']';
     $this->blog = new BlogModel();
     $this->blog->set('title', $title);
     // Make sure the page model has been loaded into this model too.
     $page = $this->blog->getLink('Page');
     $page->set('title', $title);
     $this->blog->save();
     $bacon = new BaconIpsumGenerator();
     // Create an article with an invalid title.
     $this->article = new BlogArticleModel();
     $this->article->setFromArray(['blogid' => $this->blog->get('id'), 'title' => 'Sömé "ḮnvÁlid" & \'Bad\' T¹tle!¡', 'body' => $bacon->getParagraphsAsMarkup(), 'status' => 'published']);
     $this->article->save();
 }
示例#5
0
	/**
	 * Write the raw contents of this file
	 *
	 * Essentially file_put_contents()
	 *
	 * @param mixed $data
	 *
	 * @return boolean
	 */
	public function putContents($data) {
		// Resolve it from its default.
		// This is provided from a config define, (probably).
		$mode = (defined('DEFAULT_FILE_PERMS') ? DEFAULT_FILE_PERMS : 0644);

		// FTP requires a filename, not data...
		$tmpfile = Filestore\get_tmp_path() . 'ftpupload-' . \Core::RandomHex(4);
		file_put_contents($tmpfile, $data);

		if (!ftp_put($this->_ftp->getConn(), $this->_filename, $tmpfile, FTP_BINARY)) {
			// Well, delete the temp file anyway...
			unlink($tmpfile);
			return false;
		}

		if (!ftp_chmod($this->_ftp->getConn(), $mode, $this->_filename)) return false;

		// woot... but cleanup the trash first.
		unlink($tmpfile);
		$this->_tmplocal = null;
		return true;
	}
	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';
	}
示例#7
0
	/**
	 * Verify that some given data has a valid signature.
	 *
	 * Calls verifyFileSignature internally!
	 *
	 * @param string $signature
	 * @param string $content
	 *
	 * @throws \Exception
	 *
	 * @return Signature
	 */
	public function verifyDataSignature($signature, $content){
		// First, write a temporary file to contain the signature.
		$sig = \Core\Filestore\Factory::File('tmp/gpg-verify-' . \Core::RandomHex(6) . '.asc');
		$sig->putContents($signature);

		// And the content
		$con = \Core\Filestore\Factory::File('tmp/gpg-verify-' . \Core::RandomHex(6) . '.dat');
		$con->putContents($content);

		try{
			$result = $this->verifyFileSignature($sig, $con);
		}
		catch(\Exception $e){
			// Cleanup.
			$sig->delete();
			$con->delete();

			throw $e;
		}

		$sig->delete();
		$con->delete();
		return $result;
	}
示例#8
0
 /**
  * Use /dev/urandom to generate a pseudo-random key for this nonce.
  */
 private function _generateAndSetKey()
 {
     // This will guarantee that a key is unique.
     // A UUID is based on the current server, (in the group), date, and a small amount of entropy.
     $key = Core::GenerateUUID();
     // But since this is designed to be somewhat secure... I want to be a little more cryptographically secure.
     $fp = fopen('/dev/random', 'rb');
     if ($fp !== FALSE) {
         $bits = fread($fp, 16);
         fclose($fp);
     }
     // Convert that to ASCII
     $bits_b64 = base64_encode($bits);
     // Damn "==" of base64 :/
     $bits_b64 = substr($bits_b64, 0, -2);
     // And append.
     $key .= $bits_b64;
     // And convert spaces and other invalid characters to a random digit.
     $randombit = Core::RandomHex(2);
     $key = str_replace(['+', ' ', '\\', '/'], $randombit, $key);
     // make sure it's all lowercase... URLs don't like capital letters!
     $key = strtolower($key);
     $this->set('key', $key);
 }
示例#9
0
	/**
	 * Display ALL the system configuration options.
	 *
	 * @return int
	 */
	public function config() {
		// Admin-only page.
		if(!\Core\user()->checkAccess('g:admin')){
			return View::ERROR_ACCESSDENIED;
		}

		$view = $this->getView();

		$where = array();
		// If the enterprise component is installed and multisite is enabled, configurations have another layer of complexity.
		if(Core::IsComponentAvailable('multisite') && MultiSiteHelper::GetCurrentSiteID()){
			$where['overrideable'] = '1';
		}

		$configs = ConfigModel::Find($where, null, 'key');

		$groups  = array();
		foreach ($configs as $c) {
			/** @var ConfigModel $c */
			// Export out the group for this config option.
			$el = $c->getAsFormElement();
			$gname = $el->get('group');

			if (!isset($groups[$gname])){
				$groups[$gname] = new FormGroup(
					[
						'title' => $gname,
						'name' => $gname,
						//'class' => 'collapsible collapsed'
						'class' => 'system-config-group'
					]
				);
			}

			$groups[$gname]->addElement($el);
		}


		$form = new Form();
		$form->set('callsmethod', 'AdminController::_ConfigSubmit');
		// This form gives me more trouble with its persistence....
		// @todo Implement a better option than this.
		// This hack is designed to prevent this form from using stale values from a previous page load instead of
		// pulling them from the database.
		$form->set('uniqueid', 'admin-config-' . Core::RandomHex(6));
		foreach ($groups as $g) {
			$form->addElement($g);
		}

		$form->addElement('submit', array('value' => t('STRING_SAVE')));

		$this->setTemplate('/pages/admin/config.tpl');
		$view->assign('form', $form);
		$view->assign('config_count', sizeof($configs));
	}
示例#10
0
	/**
	 * Write a string to a file
	 *
	 * @link http://php.net/manual/en/function.file-put-contents.php
	 *
	 * @param string $filename <p>
	 * Path to the file where to write the data.
	 * </p>
	 * @param mixed  $data <p>
	 * The data to write. Can be either a string, an
	 * array or a stream resource.
	 * </p>
	 * <p>
	 * If data is a stream resource, the
	 * remaining buffer of that stream will be copied to the specified file.
	 * This is similar with using stream_copy_to_stream.
	 * </p>
	 * <p>
	 * You can also specify the data parameter as a single
	 * dimension array. This is equivalent to
	 * file_put_contents($filename, implode('', $array)).
	 * </p>
	 *
	 * @return bool Returns true on success or false on failure.
	 */
	public static function _PutContents($filename, $data) {
		$ftp    = \Core\ftp();
		$tmpdir = TMP_DIR;
		if ($tmpdir{0} != '/') $tmpdir = ROOT_PDIR . $tmpdir; // Needs to be fully resolved

		// Resolve it from its default.
		// This is provided from a config define, (probably).
		$mode = (defined('DEFAULT_FILE_PERMS') ? DEFAULT_FILE_PERMS : 0644);

		if (!$ftp) {
			$ret = file_put_contents($filename, $data);
			if ($ret === false) return $ret;
			chmod($filename, $mode);
			return $ret;
		}
		elseif (strpos($filename, $tmpdir) === 0) {
			// Tmp files should be written directly.
			$ret = file_put_contents($filename, $data);
			if ($ret === false) return $ret;
			chmod($filename, $mode);
			return $ret;
		}
		else {
			// Trim off the ROOT_PDIR since it'll be relative to the ftp root set in the config.
			if (strpos($filename, ROOT_PDIR) === 0) $filename = substr($filename, strlen(ROOT_PDIR));
			//$filename = ConfigHandler::Get('/core/ftp/path') . $filename;
			// FTP requires a filename, not data...
			$tmpfile = $tmpdir . 'ftpupload-' . Core::RandomHex(4);
			file_put_contents($tmpfile, $data);

			if (!ftp_put($ftp, $filename, $tmpfile, FTP_BINARY)) {
				// Well, delete the temp file anyway...
				unlink($tmpfile);
				return false;
			}

			if (!ftp_chmod($ftp, $mode, $filename)) return false;

			// woot... but cleanup the trash first.
			unlink($tmpfile);
			return true;
		}
	}
示例#11
0
 /**
  * Test the creation of a blog article based off the newly created blog
  *
  * @depends testCreateBlog
  */
 public function testCreateBlogArticle()
 {
     // Update the current user so it has admin access.
     \Core\user()->set('admin', true);
     // Setup some variables that will be used throughout this method.
     $title = 'New Test Blog Article';
     $randomsnippet = 'Random-Snippet-' . Core::RandomHex(10);
     $lorem = new BaconIpsumGenerator();
     $body = $lorem->getParagraph(1);
     // Tack on the random snipped I'll be looking for.
     $body .= $lorem->getParagraphsAsMarkup(8, $randomsnippet);
     $blog = new BlogModel(self::$TestBlogID);
     $request = new PageRequest('/blog/article/create/' . self::$TestBlogID);
     $request->execute();
     $view = $request->getView();
     $this->assertEquals(200, $view->error, 'Checking that article creation returns a valid page');
     // The returned data should have a "form" available.  This is the actual creation form.
     /** @var $form Form */
     $form = $view->getVariable('form');
     $this->assertInstanceOf('Form', $form, 'Checking that the form is set from the blog article create controller');
     // Set some variables on the form
     $form->getElement('page[title]')->set('value', $title);
     $form->getElement('page[rewriteurl]')->set('value', $blog->get('rewriteurl') . '/' . \Core\str_to_url($title));
     $form->getElement('model[image]')->set('value', 'public/blog/blog-test-image.png');
     $form->getElement('model[body]')->set('value', $body);
     // Copy in the image
     $src = \Core\Filestore\Factory::File(ROOT_PDIR . 'components/blog/tests/blog-test-image.png');
     /** @var $dest \Core\Filestore\File */
     $dest = \Core\Filestore\Factory::File('public/blog/blog-test-image.png');
     $src->copyTo($dest, true);
     // make sure that it exists
     $this->assertTrue($dest->exists(), 'Checking that files can be copied into the public filestore');
     // And submit this form to the handler.
     // On a successful submission, it should be simply the URL of the blog.
     $formsubmission = call_user_func_array($form->get('callsmethod'), array($form));
     if ($formsubmission === false) {
         throw new Exception(implode("\n", $form->getErrors()));
     }
     // Go to the parent listing page and find this entry.
     $request = new PageRequest($blog->get('rewriteurl'));
     $request->execute();
     $view = $request->getView();
     $this->assertEquals(200, $view->error);
     $html = $view->fetch();
     $this->assertContains($title, $html);
     $this->assertContains('itemtype="http://schema.org/BlogPosting"', $html);
     preg_match_all('#<div[^>]*itemtype="http://schema.org/BlogPosting"[^>]*>.*<a[^>]*href="(.*)"[^>]*>(.*)</a>#msU', $html, $matches);
     // Title should now have three keys, with at least one value each.
     $this->assertNotEmpty($matches[1]);
     $this->assertNotEmpty($matches[2]);
     // This node contains the URL.
     $foundurl = $matches[1][0];
     $foundtitle = trim($matches[2][0]);
     // Make sure the url contains the site url.
     $this->assertStringStartsWith(ROOT_URL, $foundurl);
     // And trim it off.  This is because PageRequest expects that the url is already trimmed.
     $foundurl = '/' . substr($foundurl, strlen(ROOT_URL));
     $this->assertEquals($title, $foundtitle);
     //$this->assertStringStartsWith('/blog/article/view/', $formsubmission, 'Checking that blog article creation was successful');
     // Go to the page and make sure that it loads up!
     $request = new PageRequest($foundurl);
     $request->execute();
     $view = $request->getView();
     $this->assertEquals(200, $view->error, 'Checking that public blog article exists');
     $html = $view->fetch();
     $this->assertContains($title, $html, 'Checking that the public blog article page contains the correct title');
     $this->assertContains($randomsnippet, $html, 'Checking that the public blog article page contains the correct body');
     $this->assertContains('blog-test-image', $html, 'Checking that the public blog article page contains the correct image');
 }
示例#12
0
	/**
	 * Prompt the user a question and return the result.
	 *
	 * @param string       $question The question to prompt to the user.
	 * @param array|string $answers  What answers to provide to the user.
	 *                               array           - Will prompt the user with the value of each pair, returning the key.
	 *                               "boolean"       - Will ask for a yes/no response and return true/false.
	 *                               "text"          - Open-ended text input, user can type in anything and that input is returned.
	 *                               "text-required" - Open-ended text input, user can type in anything (non-blank), and that value is returned.
	 * @param string|bool  $default  string The default answer if the user simply presses "enter". [optional]
	 *
	 * @throws \Exception
	 * @return bool|string
	 */
	public static function PromptUser($question, $answers, $default = false) {
		$isanswered = false;
		while (!$isanswered) {
			echo NL . $question . NL;
			$extras = [];

			if (is_array($answers)) {
				$answerhash = array();
				$x          = 0;
				foreach ($answers as $a => $q) {
					if (($a === 'exit')) { // && ($x+1 == sizeof($answers))){
						// This is a 'special' action, so it gets a special key.
						$answerhash['x'] = $a;
						echo TAB . " x - $q" . NL;
						$extras[] = 'x';
					}
					elseif($a === 'quit'){
						$answerhash['q'] = $a;
						echo TAB . " q - $q" . NL;
						$extras[] = 'q';
					}
					elseif($a === 'menu'){
						$answerhash['m'] = $a;
						echo TAB . " m - $q" . NL;
						$extras[] = 'm';
					}
					elseif ($a === 'save') {
						$answerhash['s'] = $a;
						echo TAB . " s - $q" . NL;
						$extras[] = 's';
					}
					else {
						$x++;
						$answerhash[$x] = $a;
						$indent         = ($x < 10) ? ' ' : '';
						if ($default !== false && $default == $a) {
							echo TAB . $indent . "$x*- $q (default)" . NL;
						}
						else {
							echo TAB . $indent . "$x - $q" . NL;
						}
					}
				}

				// Print the "enter a number 1-10..." text.
				if ($x == 1 && !sizeof($extras)){
					echo NL . '(Enter 1 to continue) ';
				}
				elseif($x > 1 && !sizeof($extras)){
					echo NL . "(Enter a number, 1-$x) ";
				}
				else{
					$extras = array_merge(['1-' . $x], $extras);
					echo NL . '(Enter ';
					$last = null;
					while(($e = array_shift($extras))){
						if($last){
							echo $last . ', ';
						}
						$last = $e;
					}
					echo 'or ' . $last . ') ';
				}

				// Read the response.
				$line = strtolower(trim(fgets(STDIN)));
				echo NL;

				// Maybe there's a default.
				if ($line == '' && $default !== false) {
					return $default;
				}

				if (!isset($answerhash[$line])) {
					echo "Invalid Response!" . NL . NL;
					sleep(1.5);
					continue;
				}

				return $answerhash[$line];
			}
			else {
				switch (strtolower($answers)) {
					case 'boolean':
					case 'bool':
						echo "(enter y for yes, n for no.) ";
						$line = strtolower(trim(fgets(STDIN)));
						echo NL;
						// Maybe there's a default.
						if ($line == '' && $default !== false) {
							return $default;
						}
						elseif ($line == 'y' || $line == 'yes') {
							return true;
						}
						elseif ($line == 'n' || $line == 'no') {
							return false;
						}
						else {
							echo "Invalid Response!" . NL . NL;
							sleep(1.5);
							continue;
						}
						break;
					case 'text':
						if ($default !== false) {
							echo "Press enter for [" . $default . "]" . NL;
						}
						$line = trim(fgets(STDIN));
						echo NL;

						if ($line == '' && $default !== false) {
							return $default;
						}
						else {
							return $line;
						}
						break;
					case 'text-required':
						if ($default !== false) {
							echo "Press enter for [" . $default . "]" . NL;
						}
						$line = trim(fgets(STDIN));
						echo NL;

						if ($line == '' && $default !== false) {
							return $default;
						}
						elseif ($line != '') {
							return $line;
						}
						else {
							echo "Invalid Response!" . NL . NL;
							sleep(1.5);
							continue;
						}
						break;
					case 'textarea':
						// This requires a bit of app-trickery.  
						// I need to pass any "default" data into a text file in /tmp, then edit that and read the file afterwards.
						echo '(Press enter to open with ' . basename($_SERVER['EDITOR']) . '.  Save and close when done.)';
						fgets(STDIN);

						$file = "/tmp/cae2-cli-textarea-" . \Core::RandomHex(4) . '.tmp';
						file_put_contents($file, $default);
						system($_SERVER['EDITOR'] . " " . $file . " > `tty`");
						// And read back in that file.
						$data = file_get_contents($file);
						// Remove the file from the filesystem, no need for clutter.
						unlink($file);
						return $data;
					default:
						throw new \Exception('Unsupported answer choice [' . strtolower($answers) . '], please ensure it is either an array of options, "boolean", "text", "text-required", or "textarea"!');
				}
			}

		}
	}
	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,
		];
	}
示例#14
0
	/**
	 * Generate a new secure API key for this user.
	 *
	 * This is a built-in function that can be used for automated access to
	 * secured resources on the application/site.
	 *
	 * Will only set the config, save() still needs to be called externally.
	 *
	 * @since 2011.08
	 */
	public function generateNewApiKey() {
		$this->set('apikey', Core::RandomHex(64, true));
	}