1<?php
2/**
3 * @package     Joomla.Administrator
4 * @subpackage  com_joomlaupdate
5 *
6 * @copyright   Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
7 * @license     GNU General Public License version 2 or later; see LICENSE.txt
8 */
9
10defined('_JEXEC') or die;
11
12jimport('joomla.filesystem.folder');
13jimport('joomla.filesystem.file');
14
15/**
16 * Joomla! update overview Model
17 *
18 * @since  2.5.4
19 */
20class JoomlaupdateModelDefault extends JModelLegacy
21{
22	/**
23	 * Detects if the Joomla! update site currently in use matches the one
24	 * configured in this component. If they don't match, it changes it.
25	 *
26	 * @return  void
27	 *
28	 * @since    2.5.4
29	 */
30	public function applyUpdateSite()
31	{
32		// Determine the intended update URL.
33		$params = JComponentHelper::getParams('com_joomlaupdate');
34
35		switch ($params->get('updatesource', 'nochange'))
36		{
37			// "Minor & Patch Release for Current version AND Next Major Release".
38			case 'sts':
39			case 'next':
40				$updateURL = 'https://update.joomla.org/core/sts/list_sts.xml';
41				break;
42
43			// "Testing"
44			case 'testing':
45				$updateURL = 'https://update.joomla.org/core/test/list_test.xml';
46				break;
47
48			// "Custom"
49			// TODO: check if the customurl is valid and not just "not empty".
50			case 'custom':
51				if (trim($params->get('customurl', '')) != '')
52				{
53					$updateURL = trim($params->get('customurl', ''));
54				}
55				else
56				{
57					return JError::raiseWarning(403, JText::_('COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_CUSTOM_ERROR'));
58				}
59				break;
60
61			/**
62			 * "Minor & Patch Release for Current version (recommended and default)".
63			 * The commented "case" below are for documenting where 'default' and legacy options falls
64			 * case 'default':
65			 * case 'lts':
66			 * case 'nochange':
67			 */
68			default:
69				$updateURL = 'https://update.joomla.org/core/list.xml';
70		}
71
72		$db = $this->getDbo();
73		$query = $db->getQuery(true)
74			->select($db->quoteName('us') . '.*')
75			->from($db->quoteName('#__update_sites_extensions') . ' AS ' . $db->quoteName('map'))
76			->join(
77				'INNER', $db->quoteName('#__update_sites') . ' AS ' . $db->quoteName('us')
78				. ' ON (' . 'us.update_site_id = map.update_site_id)'
79			)
80			->where('map.extension_id = ' . $db->quote(700));
81		$db->setQuery($query);
82		$update_site = $db->loadObject();
83
84		if ($update_site->location != $updateURL)
85		{
86			// Modify the database record.
87			$update_site->last_check_timestamp = 0;
88			$update_site->location = $updateURL;
89			$db->updateObject('#__update_sites', $update_site, 'update_site_id');
90
91			// Remove cached updates.
92			$query->clear()
93				->delete($db->quoteName('#__updates'))
94				->where($db->quoteName('extension_id') . ' = ' . $db->quote('700'));
95			$db->setQuery($query);
96			$db->execute();
97		}
98	}
99
100	/**
101	 * Makes sure that the Joomla! update cache is up-to-date.
102	 *
103	 * @param   boolean  $force  Force reload, ignoring the cache timeout.
104	 *
105	 * @return  void
106	 *
107	 * @since    2.5.4
108	 */
109	public function refreshUpdates($force = false)
110	{
111		if ($force)
112		{
113			$cache_timeout = 0;
114		}
115		else
116		{
117			$cache_timeout = 3600 * JComponentHelper::getParams('com_installer')->get('cachetimeout', 6, 'int');
118		}
119
120		$updater               = JUpdater::getInstance();
121		$minimumStability      = JUpdater::STABILITY_STABLE;
122		$comJoomlaupdateParams = JComponentHelper::getParams('com_joomlaupdate');
123
124		if (in_array($comJoomlaupdateParams->get('updatesource', 'nochange'), array('testing', 'custom')))
125		{
126			$minimumStability = $comJoomlaupdateParams->get('minimum_stability', JUpdater::STABILITY_STABLE);
127		}
128
129		$reflection = new ReflectionObject($updater);
130		$reflectionMethod = $reflection->getMethod('findUpdates');
131		$methodParameters = $reflectionMethod->getParameters();
132
133		if (count($methodParameters) >= 4)
134		{
135			// Reinstall support is available in JUpdater
136			$updater->findUpdates(700, $cache_timeout, $minimumStability, true);
137		}
138		else
139		{
140			$updater->findUpdates(700, $cache_timeout, $minimumStability);
141		}
142	}
143
144	/**
145	 * Returns an array with the Joomla! update information.
146	 *
147	 * @return  array
148	 *
149	 * @since   2.5.4
150	 */
151	public function getUpdateInformation()
152	{
153		// Initialise the return array.
154		$ret = array(
155			'installed' => JVERSION,
156			'latest'    => null,
157			'object'    => null,
158			'hasUpdate' => false,
159		);
160
161		// Fetch the update information from the database.
162		$db = $this->getDbo();
163		$query = $db->getQuery(true)
164			->select('*')
165			->from($db->quoteName('#__updates'))
166			->where($db->quoteName('extension_id') . ' = ' . $db->quote(700));
167		$db->setQuery($query);
168		$updateObject = $db->loadObject();
169
170		if (is_null($updateObject))
171		{
172			// We have not found any update in the database we seem to run the latest version
173			$ret['latest'] = JVERSION;
174
175			return $ret;
176		}
177
178		// Check whether this is a valid update or not
179		if (version_compare($updateObject->version, JVERSION, '<'))
180		{
181			// This update points to an outdated version we should not offer to update to this
182			$ret['latest'] = JVERSION;
183
184			return $ret;
185		}
186
187		$ret['latest'] = $updateObject->version;
188
189		// Check whether this is an update or not.
190		if (version_compare($updateObject->version, JVERSION, '>'))
191		{
192			$ret['hasUpdate'] = true;
193		}
194
195		$minimumStability      = JUpdater::STABILITY_STABLE;
196		$comJoomlaupdateParams = JComponentHelper::getParams('com_joomlaupdate');
197
198		if (in_array($comJoomlaupdateParams->get('updatesource', 'nochange'), array('testing', 'custom')))
199		{
200			$minimumStability = $comJoomlaupdateParams->get('minimum_stability', JUpdater::STABILITY_STABLE);
201		}
202
203		// Fetch the full update details from the update details URL.
204		jimport('joomla.updater.update');
205		$update = new JUpdate;
206		$update->loadFromXML($updateObject->detailsurl, $minimumStability);
207
208		$ret['object'] = $update;
209
210		return $ret;
211	}
212
213	/**
214	 * Returns an array with the configured FTP options.
215	 *
216	 * @return  array
217	 *
218	 * @since   2.5.4
219	 */
220	public function getFTPOptions()
221	{
222		$config = JFactory::getConfig();
223
224		return array(
225			'host'      => $config->get('ftp_host'),
226			'port'      => $config->get('ftp_port'),
227			'username'  => $config->get('ftp_user'),
228			'password'  => $config->get('ftp_pass'),
229			'directory' => $config->get('ftp_root'),
230			'enabled'   => $config->get('ftp_enable'),
231		);
232	}
233
234	/**
235	 * Removes all of the updates from the table and enable all update streams.
236	 *
237	 * @return  boolean  Result of operation.
238	 *
239	 * @since   3.0
240	 */
241	public function purge()
242	{
243		$db = $this->getDbo();
244
245		// Reset the last update check timestamp
246		$query = $db->getQuery(true)
247			->update($db->quoteName('#__update_sites'))
248			->set($db->quoteName('last_check_timestamp') . ' = 0');
249		$db->setQuery($query);
250		$db->execute();
251
252		// We should delete all core updates here
253		$query = $db->getQuery(true)
254			->delete($db->quoteName('#__updates'))
255			->where($db->quoteName('element') . ' = ' . $db->quote('joomla'))
256			->where($db->quoteName('type') . ' = ' . $db->quote('file'));
257		$db->setQuery($query);
258
259		if ($db->execute())
260		{
261			$this->_message = JText::_('COM_JOOMLAUPDATE_CHECKED_UPDATES');
262
263			return true;
264		}
265		else
266		{
267			$this->_message = JText::_('COM_JOOMLAUPDATE_FAILED_TO_CHECK_UPDATES');
268
269			return false;
270		}
271	}
272
273	/**
274	 * Downloads the update package to the site.
275	 *
276	 * @return  boolean|string  False on failure, basename of the file in any other case.
277	 *
278	 * @since   2.5.4
279	 */
280	public function download()
281	{
282		$updateInfo = $this->getUpdateInformation();
283		$packageURL = trim($updateInfo['object']->downloadurl->_data);
284		$sources    = $updateInfo['object']->get('downloadSources', array());
285		$headers    = get_headers($packageURL, 1);
286
287		// Follow the Location headers until the actual download URL is known
288		while (isset($headers['Location']))
289		{
290			$packageURL = $headers['Location'];
291			$headers    = get_headers($packageURL, 1);
292		}
293
294		// Remove protocol, path and query string from URL
295		$basename = basename($packageURL);
296
297		if (strpos($basename, '?') !== false)
298		{
299			$basename = substr($basename, 0, strpos($basename, '?'));
300		}
301
302		// Find the path to the temp directory and the local package.
303		$config   = JFactory::getConfig();
304		$tempdir  = $config->get('tmp_path');
305		$target   = $tempdir . '/' . $basename;
306		$response = array();
307
308		// Do we have a cached file?
309		$exists = JFile::exists($target);
310
311		if (!$exists)
312		{
313			// Not there, let's fetch it.
314			$mirror = 0;
315
316			while (!($download = $this->downloadPackage($packageURL, $target)) && isset($sources[$mirror]))
317			{
318				$name       = $sources[$mirror];
319				$packageURL = trim($name->url);
320				$mirror++;
321			}
322
323			$response['basename'] = $download;
324		}
325		else
326		{
327			// Is it a 0-byte file? If so, re-download please.
328			$filesize = @filesize($target);
329
330			if (empty($filesize))
331			{
332				$mirror = 0;
333
334				while (!($download = $this->downloadPackage($packageURL, $target)) && isset($sources[$mirror]))
335				{
336					$name       = $sources[$mirror];
337					$packageURL = trim($name->url);
338					$mirror++;
339				}
340
341				$response['basename'] = $download;
342			}
343
344			// Yes, it's there, skip downloading.
345			$response['basename'] = $basename;
346		}
347
348		$response['check'] = $this->isChecksumValid($target, $updateInfo['object']);
349
350		return $response;
351	}
352
353	/**
354	 * Return the result of the checksum of a package with the SHA256/SHA384/SHA512 tags in the update server manifest
355	 *
356	 * @param   string   $packagefile   Location of the package to be installed
357	 * @param   JUpdate  $updateObject  The Update Object
358	 *
359	 * @return  boolean  False in case the validation did not work; true in any other case.
360	 *
361	 * @note    This method has been forked from (JInstallerHelper::isChecksumValid) so it
362	 *          does not depend on an up-to-date InstallerHelper at the update time
363	 *
364	 * @since   3.9.0
365	 */
366	private function isChecksumValid($packagefile, $updateObject)
367	{
368		$hashes = array('sha256', 'sha384', 'sha512');
369
370		foreach ($hashes as $hash)
371		{
372			if ($updateObject->get($hash, false))
373			{
374				$hashPackage = hash_file($hash, $packagefile);
375				$hashRemote  = $updateObject->$hash->_data;
376
377				if ($hashPackage !== $hashRemote)
378				{
379					// Return false in case the hash did not match
380					return false;
381				}
382			}
383		}
384
385		// Well nothing was provided or all worked
386		return true;
387	}
388
389	/**
390	 * Downloads a package file to a specific directory
391	 *
392	 * @param   string  $url     The URL to download from
393	 * @param   string  $target  The directory to store the file
394	 *
395	 * @return  boolean True on success
396	 *
397	 * @since   2.5.4
398	 */
399	protected function downloadPackage($url, $target)
400	{
401		JLoader::import('helpers.download', JPATH_COMPONENT_ADMINISTRATOR);
402
403		try
404		{
405			JLog::add(JText::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_URL', $url), JLog::INFO, 'Update');
406		}
407		catch (RuntimeException $exception)
408		{
409			// Informational log only
410		}
411
412		// Get the handler to download the package
413		try
414		{
415			$http = JHttpFactory::getHttp(null, array('curl', 'stream'));
416		}
417		catch (RuntimeException $e)
418		{
419			return false;
420		}
421
422		jimport('joomla.filesystem.file');
423
424		// Make sure the target does not exist.
425		JFile::delete($target);
426
427		// Download the package
428		try
429		{
430			$result = $http->get($url);
431		}
432		catch (RuntimeException $e)
433		{
434			return false;
435		}
436
437		if (!$result || ($result->code != 200 && $result->code != 310))
438		{
439			return false;
440		}
441
442		// Write the file to disk
443		JFile::write($target, $result->body);
444
445		return basename($target);
446	}
447
448	/**
449	 * Create restoration file.
450	 *
451	 * @param   string  $basename  Optional base path to the file.
452	 *
453	 * @return  boolean True if successful; false otherwise.
454	 *
455	 * @since  2.5.4
456	 */
457	public function createRestorationFile($basename = null)
458	{
459		// Get a password
460		$password = JUserHelper::genRandomPassword(32);
461		$app = JFactory::getApplication();
462		$app->setUserState('com_joomlaupdate.password', $password);
463
464		// Do we have to use FTP?
465		$method = JFactory::getApplication()->getUserStateFromRequest('com_joomlaupdate.method', 'method', 'direct', 'cmd');
466
467		// Get the absolute path to site's root.
468		$siteroot = JPATH_SITE;
469
470		// If the package name is not specified, get it from the update info.
471		if (empty($basename))
472		{
473			$updateInfo = $this->getUpdateInformation();
474			$packageURL = $updateInfo['object']->downloadurl->_data;
475			$basename = basename($packageURL);
476		}
477
478		// Get the package name.
479		$config  = JFactory::getConfig();
480		$tempdir = $config->get('tmp_path');
481		$file    = $tempdir . '/' . $basename;
482
483		$filesize = @filesize($file);
484		$app->setUserState('com_joomlaupdate.password', $password);
485		$app->setUserState('com_joomlaupdate.filesize', $filesize);
486
487		$data = "<?php\ndefined('_AKEEBA_RESTORATION') or die('Restricted access');\n";
488		$data .= '$restoration_setup = array(' . "\n";
489		$data .= <<<ENDDATA
490	'kickstart.security.password' => '$password',
491	'kickstart.tuning.max_exec_time' => '5',
492	'kickstart.tuning.run_time_bias' => '75',
493	'kickstart.tuning.min_exec_time' => '0',
494	'kickstart.procengine' => '$method',
495	'kickstart.setup.sourcefile' => '$file',
496	'kickstart.setup.destdir' => '$siteroot',
497	'kickstart.setup.restoreperms' => '0',
498	'kickstart.setup.filetype' => 'zip',
499	'kickstart.setup.dryrun' => '0',
500	'kickstart.setup.renamefiles' => array(),
501	'kickstart.setup.postrenamefiles' => false
502ENDDATA;
503
504		if ($method != 'direct')
505		{
506			/*
507			 * Fetch the FTP parameters from the request. Note: The password should be
508			 * allowed as raw mode, otherwise something like !@<sdf34>43H% would be
509			 * sanitised to !@43H% which is just plain wrong.
510			 */
511			$ftp_host = $app->input->get('ftp_host', '');
512			$ftp_port = $app->input->get('ftp_port', '21');
513			$ftp_user = $app->input->get('ftp_user', '');
514			$ftp_pass = addcslashes($app->input->get('ftp_pass', '', 'raw'), "'\\");
515			$ftp_root = $app->input->get('ftp_root', '');
516
517			// Is the tempdir really writable?
518			$writable = @is_writeable($tempdir);
519
520			if ($writable)
521			{
522				// Let's be REALLY sure.
523				$fp = @fopen($tempdir . '/test.txt', 'w');
524
525				if ($fp === false)
526				{
527					$writable = false;
528				}
529				else
530				{
531					fclose($fp);
532					unlink($tempdir . '/test.txt');
533				}
534			}
535
536			// If the tempdir is not writable, create a new writable subdirectory.
537			if (!$writable)
538			{
539				$FTPOptions = JClientHelper::getCredentials('ftp');
540				$ftp = JClientFtp::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
541				$dest = JPath::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $tempdir . '/admintools'), '/');
542
543				if (!@mkdir($tempdir . '/admintools'))
544				{
545					$ftp->mkdir($dest);
546				}
547
548				if (!@chmod($tempdir . '/admintools', 511))
549				{
550					$ftp->chmod($dest, 511);
551				}
552
553				$tempdir .= '/admintools';
554			}
555
556			// Just in case the temp-directory was off-root, try using the default tmp directory.
557			$writable = @is_writeable($tempdir);
558
559			if (!$writable)
560			{
561				$tempdir = JPATH_ROOT . '/tmp';
562
563				// Does the JPATH_ROOT/tmp directory exist?
564				if (!is_dir($tempdir))
565				{
566					JFolder::create($tempdir, 511);
567					$htaccessContents = "order deny,allow\ndeny from all\nallow from none\n";
568					JFile::write($tempdir . '/.htaccess', $htaccessContents);
569				}
570
571				// If it exists and it is unwritable, try creating a writable admintools subdirectory.
572				if (!is_writable($tempdir))
573				{
574					$FTPOptions = JClientHelper::getCredentials('ftp');
575					$ftp = JClientFtp::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
576					$dest = JPath::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $tempdir . '/admintools'), '/');
577
578					if (!@mkdir($tempdir . '/admintools'))
579					{
580						$ftp->mkdir($dest);
581					}
582
583					if (!@chmod($tempdir . '/admintools', 511))
584					{
585						$ftp->chmod($dest, 511);
586					}
587
588					$tempdir .= '/admintools';
589				}
590			}
591
592			// If we still have no writable directory, we'll try /tmp and the system's temp-directory.
593			$writable = @is_writeable($tempdir);
594
595			if (!$writable)
596			{
597				if (@is_dir('/tmp') && @is_writable('/tmp'))
598				{
599					$tempdir = '/tmp';
600				}
601				else
602				{
603					// Try to find the system temp path.
604					$tmpfile = @tempnam('dummy', '');
605					$systemp = @dirname($tmpfile);
606					@unlink($tmpfile);
607
608					if (!empty($systemp))
609					{
610						if (@is_dir($systemp) && @is_writable($systemp))
611						{
612							$tempdir = $systemp;
613						}
614					}
615				}
616			}
617
618			$data .= <<<ENDDATA
619	,
620	'kickstart.ftp.ssl' => '0',
621	'kickstart.ftp.passive' => '1',
622	'kickstart.ftp.host' => '$ftp_host',
623	'kickstart.ftp.port' => '$ftp_port',
624	'kickstart.ftp.user' => '$ftp_user',
625	'kickstart.ftp.pass' => '$ftp_pass',
626	'kickstart.ftp.dir' => '$ftp_root',
627	'kickstart.ftp.tempdir' => '$tempdir'
628ENDDATA;
629		}
630
631		$data .= ');';
632
633		// Remove the old file, if it's there...
634		$configpath = JPATH_COMPONENT_ADMINISTRATOR . '/restoration.php';
635
636		if (JFile::exists($configpath))
637		{
638			JFile::delete($configpath);
639		}
640
641		// Write new file. First try with JFile.
642		$result = JFile::write($configpath, $data);
643
644		// In case JFile used FTP but direct access could help.
645		if (!$result)
646		{
647			if (function_exists('file_put_contents'))
648			{
649				$result = @file_put_contents($configpath, $data);
650
651				if ($result !== false)
652				{
653					$result = true;
654				}
655			}
656			else
657			{
658				$fp = @fopen($configpath, 'wt');
659
660				if ($fp !== false)
661				{
662					$result = @fwrite($fp, $data);
663
664					if ($result !== false)
665					{
666						$result = true;
667					}
668
669					@fclose($fp);
670				}
671			}
672		}
673
674		return $result;
675	}
676
677	/**
678	 * Runs the schema update SQL files, the PHP update script and updates the
679	 * manifest cache and #__extensions entry. Essentially, it is identical to
680	 * JInstallerFile::install() without the file copy.
681	 *
682	 * @return  boolean True on success.
683	 *
684	 * @since   2.5.4
685	 */
686	public function finaliseUpgrade()
687	{
688		$installer = JInstaller::getInstance();
689
690		$manifest = $installer->isManifest(JPATH_MANIFESTS . '/files/joomla.xml');
691
692		if ($manifest === false)
693		{
694			$installer->abort(JText::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST'));
695
696			return false;
697		}
698
699		$installer->manifest = $manifest;
700
701		$installer->setUpgrade(true);
702		$installer->setOverwrite(true);
703
704		$installer->extension = JTable::getInstance('extension');
705		$installer->extension->load(700);
706
707		$installer->setAdapter($installer->extension->type);
708
709		$installer->setPath('manifest', JPATH_MANIFESTS . '/files/joomla.xml');
710		$installer->setPath('source', JPATH_MANIFESTS . '/files');
711		$installer->setPath('extension_root', JPATH_ROOT);
712
713		// Run the script file.
714		JLoader::register('JoomlaInstallerScript', JPATH_ADMINISTRATOR . '/components/com_admin/script.php');
715
716		$manifestClass = new JoomlaInstallerScript;
717
718		ob_start();
719		ob_implicit_flush(false);
720
721		if ($manifestClass && method_exists($manifestClass, 'preflight'))
722		{
723			if ($manifestClass->preflight('update', $installer) === false)
724			{
725				$installer->abort(JText::_('JLIB_INSTALLER_ABORT_FILE_INSTALL_CUSTOM_INSTALL_FAILURE'));
726
727				return false;
728			}
729		}
730
731		// Create msg object; first use here.
732		$msg = ob_get_contents();
733		ob_end_clean();
734
735		// Get a database connector object.
736		$db = $this->getDbo();
737
738		/*
739		 * Check to see if a file extension by the same name is already installed.
740		 * If it is, then update the table because if the files aren't there
741		 * we can assume that it was (badly) uninstalled.
742		 * If it isn't, add an entry to extensions.
743		 */
744		$query = $db->getQuery(true)
745			->select($db->quoteName('extension_id'))
746			->from($db->quoteName('#__extensions'))
747			->where($db->quoteName('type') . ' = ' . $db->quote('file'))
748			->where($db->quoteName('element') . ' = ' . $db->quote('joomla'));
749		$db->setQuery($query);
750
751		try
752		{
753			$db->execute();
754		}
755		catch (RuntimeException $e)
756		{
757			// Install failed, roll back changes.
758			$installer->abort(
759				JText::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK', JText::_('JLIB_INSTALLER_UPDATE'), $e->getMessage())
760			);
761
762			return false;
763		}
764
765		$id = $db->loadResult();
766		$row = JTable::getInstance('extension');
767
768		if ($id)
769		{
770			// Load the entry and update the manifest_cache.
771			$row->load($id);
772
773			// Update name.
774			$row->set('name', 'files_joomla');
775
776			// Update manifest.
777			$row->manifest_cache = $installer->generateManifestCache();
778
779			if (!$row->store())
780			{
781				// Install failed, roll back changes.
782				$installer->abort(
783					JText::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK', JText::_('JLIB_INSTALLER_UPDATE'), $row->getError())
784				);
785
786				return false;
787			}
788		}
789		else
790		{
791			// Add an entry to the extension table with a whole heap of defaults.
792			$row->set('name', 'files_joomla');
793			$row->set('type', 'file');
794			$row->set('element', 'joomla');
795
796			// There is no folder for files so leave it blank.
797			$row->set('folder', '');
798			$row->set('enabled', 1);
799			$row->set('protected', 0);
800			$row->set('access', 0);
801			$row->set('client_id', 0);
802			$row->set('params', '');
803			$row->set('system_data', '');
804			$row->set('manifest_cache', $installer->generateManifestCache());
805
806			if (!$row->store())
807			{
808				// Install failed, roll back changes.
809				$installer->abort(JText::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_ROLLBACK', $row->getError()));
810
811				return false;
812			}
813
814			// Set the insert id.
815			$row->set('extension_id', $db->insertid());
816
817			// Since we have created a module item, we add it to the installation step stack
818			// so that if we have to rollback the changes we can undo it.
819			$installer->pushStep(array('type' => 'extension', 'extension_id' => $row->extension_id));
820		}
821
822		$result = $installer->parseSchemaUpdates($manifest->update->schemas, $row->extension_id);
823
824		if ($result === false)
825		{
826			// Install failed, rollback changes.
827			$installer->abort(JText::sprintf('JLIB_INSTALLER_ABORT_FILE_UPDATE_SQL_ERROR', $db->stderr(true)));
828
829			return false;
830		}
831
832		// Start Joomla! 1.6.
833		ob_start();
834		ob_implicit_flush(false);
835
836		if ($manifestClass && method_exists($manifestClass, 'update'))
837		{
838			if ($manifestClass->update($installer) === false)
839			{
840				// Install failed, rollback changes.
841				$installer->abort(JText::_('JLIB_INSTALLER_ABORT_FILE_INSTALL_CUSTOM_INSTALL_FAILURE'));
842
843				return false;
844			}
845		}
846
847		// Append messages.
848		$msg .= ob_get_contents();
849		ob_end_clean();
850
851		// Clobber any possible pending updates.
852		$update = JTable::getInstance('update');
853		$uid = $update->find(
854			array('element' => 'joomla', 'type' => 'file', 'client_id' => '0', 'folder' => '')
855		);
856
857		if ($uid)
858		{
859			$update->delete($uid);
860		}
861
862		// And now we run the postflight.
863		ob_start();
864		ob_implicit_flush(false);
865
866		if ($manifestClass && method_exists($manifestClass, 'postflight'))
867		{
868			$manifestClass->postflight('update', $installer);
869		}
870
871		// Append messages.
872		$msg .= ob_get_contents();
873		ob_end_clean();
874
875		if ($msg != '')
876		{
877			$installer->set('extension_message', $msg);
878		}
879
880		// Refresh versionable assets cache.
881		JFactory::getApplication()->flushAssets();
882
883		return true;
884	}
885
886	/**
887	 * Removes the extracted package file.
888	 *
889	 * @return  void
890	 *
891	 * @since   2.5.4
892	 */
893	public function cleanUp()
894	{
895		// Remove the update package.
896		$config = JFactory::getConfig();
897		$tempdir = $config->get('tmp_path');
898
899		$file = JFactory::getApplication()->getUserState('com_joomlaupdate.file', null);
900		$target = $tempdir . '/' . $file;
901
902		if (!@unlink($target))
903		{
904			JFile::delete($target);
905		}
906
907		// Remove the restoration.php file.
908		$target = JPATH_COMPONENT_ADMINISTRATOR . '/restoration.php';
909
910		if (!@unlink($target))
911		{
912			JFile::delete($target);
913		}
914
915		// Remove joomla.xml from the site's root.
916		$target = JPATH_ROOT . '/joomla.xml';
917
918		if (!@unlink($target))
919		{
920			JFile::delete($target);
921		}
922
923		// Unset the update filename from the session.
924		JFactory::getApplication()->setUserState('com_joomlaupdate.file', null);
925		$oldVersion = JFactory::getApplication()->getUserState('com_joomlaupdate.oldversion');
926
927		// Trigger event after joomla update.
928		JFactory::getApplication()->triggerEvent('onJoomlaAfterUpdate', array($oldVersion));
929		JFactory::getApplication()->setUserState('com_joomlaupdate.oldversion', null);
930	}
931
932	/**
933	 * Uploads what is presumably an update ZIP file under a mangled name in the temporary directory.
934	 *
935	 * @return  void
936	 *
937	 * @since   3.6.0
938	 */
939	public function upload()
940	{
941		// Get the uploaded file information.
942		$input = JFactory::getApplication()->input;
943
944		// Do not change the filter type 'raw'. We need this to let files containing PHP code to upload. See JInputFiles::get.
945		$userfile = $input->files->get('install_package', null, 'raw');
946
947		// Make sure that file uploads are enabled in php.
948		if (!(bool) ini_get('file_uploads'))
949		{
950			throw new RuntimeException(JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLFILE'), 500);
951		}
952
953		// Make sure that zlib is loaded so that the package can be unpacked.
954		if (!extension_loaded('zlib'))
955		{
956			throw new RuntimeException('COM_INSTALLER_MSG_INSTALL_WARNINSTALLZLIB', 500);
957		}
958
959		// If there is no uploaded file, we have a problem...
960		if (!is_array($userfile))
961		{
962			throw new RuntimeException(JText::_('COM_INSTALLER_MSG_INSTALL_NO_FILE_SELECTED'), 500);
963		}
964
965		// Is the PHP tmp directory missing?
966		if ($userfile['error'] && ($userfile['error'] == UPLOAD_ERR_NO_TMP_DIR))
967		{
968			throw new RuntimeException(
969				JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR') . '<br />' .
970				JText::_('COM_INSTALLER_MSG_WARNINGS_PHPUPLOADNOTSET'),
971				500
972			);
973		}
974
975		// Is the max upload size too small in php.ini?
976		if ($userfile['error'] && ($userfile['error'] == UPLOAD_ERR_INI_SIZE))
977		{
978			throw new RuntimeException(
979				JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR') . '<br />' . JText::_('COM_INSTALLER_MSG_WARNINGS_SMALLUPLOADSIZE'),
980				500
981			);
982		}
983
984		// Check if there was a different problem uploading the file.
985		if ($userfile['error'] || $userfile['size'] < 1)
986		{
987			throw new RuntimeException(JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR'), 500);
988		}
989
990		// Build the appropriate paths.
991		$config   = JFactory::getConfig();
992		$tmp_dest = tempnam($config->get('tmp_path'), 'ju');
993		$tmp_src  = $userfile['tmp_name'];
994
995		// Move uploaded file.
996		jimport('joomla.filesystem.file');
997
998		if (version_compare(JVERSION, '3.4.0', 'ge'))
999		{
1000			$result = JFile::upload($tmp_src, $tmp_dest, false, true);
1001		}
1002		else
1003		{
1004			// Old Joomla! versions didn't have UploadShield and don't need the fourth parameter to accept uploads
1005			$result = JFile::upload($tmp_src, $tmp_dest);
1006		}
1007
1008		if (!$result)
1009		{
1010			throw new RuntimeException(JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR'), 500);
1011		}
1012
1013		JFactory::getApplication()->setUserState('com_joomlaupdate.temp_file', $tmp_dest);
1014	}
1015
1016	/**
1017	 * Checks the super admin credentials are valid for the currently logged in users
1018	 *
1019	 * @param   array  $credentials  The credentials to authenticate the user with
1020	 *
1021	 * @return  boolean
1022	 *
1023	 * @since   3.6.0
1024	 */
1025	public function captiveLogin($credentials)
1026	{
1027		// Make sure the username matches
1028		$username = isset($credentials['username']) ? $credentials['username'] : null;
1029		$user     = JFactory::getUser();
1030
1031		if (strtolower($user->username) != strtolower($username))
1032		{
1033			return false;
1034		}
1035
1036		// Make sure the user is authorised
1037		if (!$user->authorise('core.admin'))
1038		{
1039			return false;
1040		}
1041
1042		// Get the global JAuthentication object.
1043		$authenticate = JAuthentication::getInstance();
1044		$response     = $authenticate->authenticate($credentials);
1045
1046		if ($response->status !== JAuthentication::STATUS_SUCCESS)
1047		{
1048			return false;
1049		}
1050
1051		return true;
1052	}
1053
1054	/**
1055	 * Does the captive (temporary) file we uploaded before still exist?
1056	 *
1057	 * @return  boolean
1058	 *
1059	 * @since   3.6.0
1060	 */
1061	public function captiveFileExists()
1062	{
1063		$file = JFactory::getApplication()->getUserState('com_joomlaupdate.temp_file', null);
1064
1065		JLoader::import('joomla.filesystem.file');
1066
1067		if (empty($file) || !JFile::exists($file))
1068		{
1069			return false;
1070		}
1071
1072		return true;
1073	}
1074
1075	/**
1076	 * Remove the captive (temporary) file we uploaded before and the .
1077	 *
1078	 * @return  void
1079	 *
1080	 * @since   3.6.0
1081	 */
1082	public function removePackageFiles()
1083	{
1084		$files = array(
1085			JFactory::getApplication()->getUserState('com_joomlaupdate.temp_file', null),
1086			JFactory::getApplication()->getUserState('com_joomlaupdate.file', null),
1087		);
1088
1089		JLoader::import('joomla.filesystem.file');
1090
1091		foreach ($files as $file)
1092		{
1093			if (JFile::exists($file))
1094			{
1095				if (!@unlink($file))
1096				{
1097					JFile::delete($file);
1098				}
1099			}
1100		}
1101	}
1102}
1103