<?php
/**
* Community Builder (TM)
* @version $Id: $
* @package CommunityBuilder
* @copyright (C) 2004-2021 www.joomlapolis.com / Lightning MultiCom SA - and its licensors, all rights reserved
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU/GPL version 2
*/

use CB\Database\Table\PluginTable;
use CBLib\Core\CBLib;
use Joomla\CMS\HTML\HTMLHelper;

if ( ! ( defined( '_VALID_CB' ) || defined( '_JEXEC' ) || defined( '_VALID_MOS' ) ) ) { die( 'Direct Access to this location is not allowed.' ); }

/**
 * CB installer
 *
 * @return bool
 */
function plug_cbpackageinstaller_install()
{
	$installer		=	new packageinstallerInstaller( 'cb' );

	return $installer->install();
}

/**
 * Joomla installer
 *
 * Class com_packageinstallerInstallerScript
 */
class com_packageinstallerInstallerScript
{

	/**
	 * @return bool
	 */
	public function preflight( $type, $parent )
	{
		// Delete the extensions folder if for some reason package installer is already installed
		if ( $type == 'update' ) {
			jimport( 'joomla.filesystem.folder' );

			$extensions	=	JPATH_ADMINISTRATOR . '/components/com_packageinstaller/extensions';

			if ( JFolder::exists( $extensions ) ) {
				JFolder::delete( $extensions );
			}
		}

		return true;
	}

	/**
	 * @return bool
	 */
	public function install()
	{
		$installer		=	new packageinstallerInstaller();

		return $installer->install();
	}

	/**
	 * @return bool
	 */
	public function discover_install()
	{
		return $this->install();
	}

	/**
	 * @return bool
	 */
	public function update()
	{
		return $this->install();
	}
}

class packageinstallerInstaller
{
	/** @var PluginTable  */
	private $plugin		=	null;

	/**
	 * com_packageinstallerInstallerScript constructor.
	 *
	 * @param string $location
	 */
	public function __construct( $location = 'joomla' )
	{
		if ( $location === 'cb' ) {
			if ( ( ! file_exists( JPATH_SITE . '/libraries/CBLib/CBLib/Core/CBLib.php' ) ) || ( ! file_exists( JPATH_ADMINISTRATOR . '/components/com_comprofiler/plugin.foundation.php' ) ) ) {
				return;
			}

			include_once( JPATH_ADMINISTRATOR . '/components/com_comprofiler/plugin.foundation.php' );

			cbimport( 'cb.html' );

			static $plugin		=	null;

			if ( $plugin == null ) {
				$plugin			=	new PluginTable();

				$plugin->load( array( 'element' => 'cbpackageinstaller' ) );
			}

			$this->plugin		=	$plugin;
		}
	}

	/**
	 * @return bool
	 */
	public function install()
	{
		global $_CB_framework, $_PLUGINS;

		$packages					=	array(	'packages'		=>	array(),
												'libraries'		=>	array(),
												'components'	=>	array(),
												'plugins'		=>	array(),
												'modules'		=>	array(),
												'languages'		=>	array(),
												'templates'		=>	array(),
												'cb_plugins'	=>	array(),
												'queries'		=>	array(),
												'scripts'		=>	array(),
												'overrides'		=>	array(),
												'custom'		=>	array()
											);

		foreach ( array_keys( $packages ) as $type ) {
			$this->getPackages( $type, $packages );
		}

		if ( $this->plugin ) {
			$uninstallUrl			=	$_CB_framework->backendViewUrl( 'editPlugin', false, array( 'action' => 'uninstallself', 'cid' => (int) $this->plugin->get( 'id' ) ), 'raw' );
			$cbVersion				=	CBLib::version();
		} else {
			$uninstallUrl			=	JRoute::_( 'index.php?option=com_packageinstaller&view=uninstallself&format=raw', false );
			$cbVersion				=	null;

			if ( class_exists( '\CBLib\Core\CBLib' ) ) {
				$cbVersion			=	CBLib::version();
			}
		}

		$html						=	array();
		$js							=	array();
		$step						=	1;

		foreach ( $packages as $pkgs ) {
			if ( ! $pkgs ) {
				continue;
			}

			foreach ( $pkgs as $pkg ) {
				if ( $this->plugin ) {
					$url				=	$_CB_framework->backendViewUrl( 'editPlugin', false, array( 'action' => 'installpkg', 'cid' => (int) $this->plugin->get( 'id' ), 'fld' => $pkg['type'], 'pkg' => $pkg['path'] ), 'raw' );
				} else {
					$url				=	JRoute::_( 'index.php?option=com_packageinstaller&view=installpkg&fld=' . urlencode( $pkg['type'] ) . '&pkg=' . urlencode( $pkg['path'] ) . '&format=raw', false );
				}

				$html[$step]			=	'<div class="p-3 col-12 col-md-6 col-lg-4 cbPkgInstallRow cbPkgInstallRow' . $step . ' hidden">'
										.		'<div class="card h-100 border-info">'
										.			'<div class="card-header p-2 text-large text-wrap text-center cbPkgInstallName">'
										.				'<strong>' . $pkg['details']['name'] . '</strong>'
										.			'</div>'
										.			'<div class="d-flex flex-column justify-content-end card-body p-0 cbPkgInstallDetails">';

					if ( $pkg['details']['description'] ) {
						$html[$step]	.=				'<div class="flex-grow-1 p-2 text-wrap cbPkgInstallDetailDescription">'
										.					'<div class="cbPkgInstallDescription">'
										.						$pkg['details']['description']
										.					'</div>'
										.				'</div>';
					}

					if ( $pkg['type'] ) {
						$html[$step]	.=				'<div class="row no-gutters cbPkgInstallDetailType">'
										.					'<div class="p-2 col-3 border-right">Type</div>'
										.					'<div class="p-2 col text-wrap">' . $this->getPackageType( $pkg['type'] ) . '</div>'
										.				'</div>';
					}

					if ( $pkg['details']['version'] ) {
						$html[$step]	.=				'<div class="row no-gutters cbPkgInstallDetailVersion">'
										.					'<div class="p-2 col-3 border-right">Version</div>'
										.					'<div class="p-2 col text-wrap">' . $pkg['details']['version'] . '</div>'
										.				'</div>';
					}

					if ( $pkg['details']['date'] ) {
						$html[$step]	.=				'<div class="row no-gutters cbPkgInstallDetailDate">'
										.					'<div class="p-2 col-3 border-right">Date</div>'
										.					'<div class="p-2 col text-wrap">' . $pkg['details']['date'] . '</div>'
										.				'</div>';
					}

					$html[$step]		.=				'<div class="row no-gutters cbPkgInstallDetailFile">'
										.					'<div class="p-2 col-3 border-right">File</div>'
										.					'<div class="p-2 col text-wrap">' . $pkg['file'] . '</div>'
										.				'</div>'
										.				'<div class="row no-gutters cbPkgInstallDetailCopy">'
										.					'<div class="p-2 col-3 border-right"></div>'
										.					'<div class="p-2 col text-wrap">'
										.						'<button type="button" class="btn btn-sm btn-primary cbPkgInstallDetailCopyBtn">Click to Copy Installation Details</button>'
										.						'<textarea class="cbPkgInstallDetailCopyText">'
										.							'System Information'
										.							"\n" . 'Joomla: ' . $this->jVersion( 'version' )
										.							( $cbVersion ? "\n" . 'Community Builder: ' . $cbVersion : '' )
										.							"\n" . 'PHP: ' . PHP_VERSION
										.							"\n" . 'Database: ' . JFactory::getDbo()->getServerType() . ' ' . JFactory::getDbo()->getVersion()
										.							"\n"
										.							"\n" . 'Package'
										.							"\n" . 'File: ' . $pkg['file']
										.							"\n" . 'Log:'
										.						'</textarea>'
										.						' <span class="text-small cbPkgInstallDetailCopyNote hidden">Copied to Clipboard!</span>'
										.					'</div>'
										.				'</div>'
										.			'</div>'
										.			'<div class="card-body p-2 border-top cbPkgInstallLog hidden">'
										.				'<div class="mb-2 text-center"><strong>Install Messages</strong></div>'
										.			'</div>'
										.			'<div class="card-footer p-0">'
										.				'<div class="m-0 w-100 p-1 bg-info text-white text-center cbPkgInstallState cbPkgInstallStateInstalling">'
										.					'<div class="d-flex align-items-center">'
										.						'<span class="spinner-border spinner-border-sm mr-auto" role="status"></span>'
										.						'<span class="d-inline-block mr-auto">Installing...</span>'
										.					'</div>'
										.				'</div>'
										.				'<div class="m-0 w-100 p-1 bg-success text-white text-center cbPkgInstallState cbPkgInstallStateInstalled hidden">'
										.					'<span class="cbPkgInstallStateText">Installed</span>'
										.					'<span class="cbPkgInstallStateInfo">Click for Installation Details</span>'
										.				'</div>'
										.				'<div class="m-0 w-100 p-1 bg-danger text-white text-center cbPkgInstallState cbPkgInstallStateFailed hidden">'
										.					'<span class="cbPkgInstallStateText">Failed</span>'
										.					'<span class="cbPkgInstallStateInfo">Click for Installation Details</span>'
										.				'</div>'
										.			'</div>'
										.		'</div>'
										.	'</div>';

				$js[]					=	array(	'url'	=>	$url,
													'step'	=>	$step
												);

				++$step;
			}
		}

		$jsHtml						=	null;

		if ( $this->plugin ) {
			$_CB_framework->document->addHeadStyleSheet( $_PLUGINS->getPluginLivePath( $this->plugin ) . '/cbpackageinstaller.css' );
			$_CB_framework->document->addHeadScriptUrl( $_PLUGINS->getPluginLivePath( $this->plugin ) . '/packageinstaller.js', false, "let packages = " . json_encode( $js ) . "; let cleanup = " . json_encode( $uninstallUrl, JSON_HEX_TAG ) . ";" );

			$uninstallAlert			=		'The CB Package Installer failed to remove it self. Please navigate to <a href="index.php?option=com_comprofiler&view=showPlugins">Plugin Management</a> and uninstall <strong>CB Package Installer</strong> manually.';
		} else {
			$jsHtml					=		'<script type="text/javascript">let packages = ' . json_encode( $js ) . '; let cleanup = ' . json_encode( $uninstallUrl, JSON_HEX_TAG ) . ';</script>'
									.		'<link type="text/css" href="' . JURI::base() . 'components/com_packageinstaller/packageinstaller.css?v=' . substr( md5( filemtime( JPATH_ADMINISTRATOR . '/components/com_packageinstaller/packageinstaller.css' ) ), 0, 16 ) . '" rel="stylesheet" />'
									.		'<script type="text/javascript" src="' . JURI::base() . 'components/com_packageinstaller/packageinstaller.js?v=' . substr( md5( filemtime( JPATH_ADMINISTRATOR . '/components/com_packageinstaller/packageinstaller.js' ) ), 0, 16 ) . '" defer></script>';

			$uninstallAlert			=		'The Package Installer failed to remove it self. Please navigate to <a href="index.php?option=com_installer&view=manage">Extension Management</a> and uninstall <strong>Package Installer</strong> manually.';
		}

		echo	'<div class="cb_template">'
			.		'<div class="mb-4 cbPkgInstaller cb_packageinstaller">'
			.			'<div class="cbPkgInstall">'
			.				'<div class="mb-4">'
			.					'<div class="m-0 progress cbPkgInstallProgress">'
			.						'<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">'
			.							'<span class="sr-only">0% Complete</span>'
			.						'</div>'
			.					'</div>'
			.					'<div class="mt-4 mb-4 alert alert-warning cbPkgUninstallAlert hidden">'
			.						$uninstallAlert
			.					'</div>'
			.				'</div>'
			.				'<div class="mb-4">'
			.					'<div class="m-n3 row no-gutters cbPkgInstallRows">'
			.						implode( '', array_reverse( $html ) )
			.					'</div>'
			.				'</div>'
			.			'</div>'
			.		'</div>'
			.		$jsHtml
			.	'</div>';

		return true;
	}

	/**
	 * Parses for package type files with Joomla version dependency check
	 *
	 * @param string $type
	 * @param array  $packages
	 * @param null   $subDir
	 */
	private function getPackages( $type, &$packages, $subDir = null )
	{
		global $_PLUGINS;

		if ( $this->plugin ) {
			$extensionDir								=	$_PLUGINS->getPluginPath( $this->plugin ) . '/extensions/' . $type . '/' . $subDir;
		} else {
			$extensionDir								=	JPATH_ADMINISTRATOR . '/components/com_packageinstaller/extensions/' . $type . '/' . $subDir;
		}

		if ( is_dir( $extensionDir ) ) {
			$files										=	scandir( $extensionDir );

			if ( $files ) {
				foreach ( $files as $file ) {
					if ( ( $file !== '.' ) && ( $file !== '..' ) && ( $file !== 'index.html' ) ) {
						if ( is_dir( $extensionDir . $file ) ) {
							$this->getPackages( $type, $packages, $subDir . $file . '/' );
						} else {
							$package					=	null;

							switch ( $type ) {
								case 'scripts':
									if ( preg_match( '/^([\w+.-]+)\.php$/', $file ) ) {
										$package		=	$file;
									}
									break;
								case 'queries':
									if ( preg_match( '/^([\w+.-]+)\.(sql|txt)$/', $file ) ) {
										$package		=	$file;
									}
									break;
								case 'custom': // We'll filter to some safe types here, but technically custom can be anything:
									if ( preg_match( '/^([\w+.-]+)\.(zip|rar|doc|pdf|txt|xls|jpg|jpeg|gif|png)$/', $file ) ) {
										$package		=	$file;
									}
									break;
								default:
									if ( preg_match( '/^([\w+.-]+)\.zip$/', $file ) ) {
										$package		=	$file;
									}
									break;
							}

							if ( $package ) {
								$include				=	true;
								$jVersion				=	$this->jVersion();

								if ( $subDir ) {
									$folderCMS			=	preg_match( '%^j((\d)\.?(\d))/%', $subDir, $matches );

									if ( $folderCMS ) {
										$folderVersion	=	$this->jVersion( $matches[2] . '.' . $matches[3] );

										if ( $jVersion < $folderVersion ) {
											$include	=	false;
										}
									}
								}

								if ( $include ) {
									$packageCMS			=	preg_match( '/j((\d)\.?(\d))/', $package, $matches );

									if ( $packageCMS ) {
										$packageVersion	=	$this->jVersion( $matches[2] . '.' . $matches[3] );

										if ( $jVersion < $packageVersion ) {
											$include	=	false;
										}
									}
								}

								if ( $include ) {
									$packages[$type][]	=	array(	'path'		=>	$subDir . $package,
																	'type'		=>	$type,
																	'file'		=>	$package,
																	'details'	=>	$this->getPackageDetails( $extensionDir . $file, $package )
																);
								}
							}
						}
					}
				}
			}
		}
	}

	/**
	 * Converts folder to package type
	 *
	 * @param string $folder
	 * @return string
	 */
	private function getPackageType( $folder )
	{
		switch ( $folder ) {
			case 'cb_plugins':
				return 'Community Builder Plugin';
			case 'components':
				return 'Joomla Component';
			case 'custom':
				return 'Custom';
			case 'languages':
				return 'Joomla Language';
			case 'libraries':
				return 'Joomla Library';
			case 'modules':
				return 'Joomla Module';
			case 'overrides':
				return 'Override';
			case 'packages':
				return 'Joomla Package';
			case 'plugins':
				return 'Joomla Plugin';
			case 'queries':
				return 'Query';
			case 'scripts':
				return 'Script';
			case 'templates':
				return 'Joomla Template';
			default:
				return $folder;
		}
	}

	/**
	 * @param string $file
	 * @param string $name
	 * @return array
	 */
	private function getPackageDetails( $file, $name )
	{
		$details	=	array(	'name'			=>	$name,
								'description'	=>	null,
								'version'		=>	null,
								'date'			=>	null
							);

		// Try to extract the details from generic filename structure to establish defaults:
		if ( preg_match( '/^(?:\d+(?:_|-|\.))?([a-zA-Z-_]+)(?:(?:_|-|\.)([.\d]+))(?:\+(?:build(?:_|-|\.))?(\d{4}(?:_|-|\.)\d{2}(?:_|-|\.)\d{2}))/', $name, $pkgDetails ) ) {
			if ( isset( $pkgDetails[1] ) && $pkgDetails[1] ) {
				$details['name']	=	$pkgDetails[1];
			}

			if ( isset( $pkgDetails[2] ) && $pkgDetails[2] ) {
				$details['version']	=	$pkgDetails[2];
			}

			if ( isset( $pkgDetails[3] ) && $pkgDetails[3] ) {
				$details['date']	=	$pkgDetails[3];
			}
		}

		if ( strpos( $name, '.zip' ) === false ) {
			return $details;
		}

		// Now lets see if we can find the XML file and pull the details from it so we can have a more informed display:
		try {
			$zip					=	new ZipArchive();

			if ( $zip->open( $file ) === true ) {
				for ( $i = 0; $i < $zip->numFiles; $i++ ) {
					$zipFile						=	$zip->getNameIndex( $i );

					// Use the first xml file found:
					if ( strpos( $zipFile, '.xml' ) !== false ) {
						$xml						=	new SimpleXMLElement( $zip->getFromIndex( $i ) );
						$name						=	(string) $xml->name;

						if ( $name ) {
							$details['name']		=	$name;
						}

						$description				=	(string) $xml->description;

						if ( $description ) {
							$details['description']	=	$description;
						}

						$version					=	(string) $xml->version;

						if ( $version ) {
							$details['version']		=	$version;
						}

						$release					=	(string) $xml->release;

						if ( $release ) {
							$details['version']		=	$release;
						}

						$date						=	(string) $xml->creationDate;

						if ( $date ) {
							$details['date']		=	$date;
						}

						break;
					}
				}
			}

			$zip->close();
		} catch ( Exception $e ) {}

		return $details;
	}

	/**
	 * Returns Joomla version
	 *
	 * @param string $v
	 * @return int
	 */
	private function jVersion( $v = null )
	{
		static $cache			=	array();

		if ( ! $v ) {
			$v					=	'joomla';
		}

		if ( ! isset( $cache[$v] ) ) {
			$version			=	'';
			$release			=	'';

			if ( ! in_array( $v, array( 'joomla', 'version', 'release' ) ) ) {
				$release		=	substr( $v, 0, 3 );
			} elseif ( class_exists( '\Joomla\CMS\Version' ) ) {
				$jVersion		=	new \Joomla\CMS\Version();
				$version		=	$jVersion->getShortVersion();
				$release		=	substr( $jVersion->getShortVersion(), 0, 3 );
			} elseif ( class_exists( 'JVersion' ) ) {
				$jVersion		=	new JVersion();
				$version		=	$jVersion->RELEASE;
				$release		=	substr( $jVersion->RELEASE, 0, 3 );
			}

			if ( $v === 'version' ) {
				$cache[$v]		=	$version;
			} elseif ( $v === 'release' ) {
				$cache[$v]		=	$release;
			} elseif ( strcasecmp( $release, '4.0' ) >= 0 ) {
				$cache[$v]		=	6;
			} elseif ( strcasecmp( $release, '3.0' ) >= 0 ) {
				$cache[$v]		=	5;
			} elseif ( strcasecmp( $release, '2.5' ) >= 0 ) {
				$cache[$v]		=	4;
			} elseif ( strcasecmp( $release, '1.7' ) >= 0 ) {
				$cache[$v]		=	3;
			} elseif ( strcasecmp( $release, '1.6' ) >= 0 ) {
				$cache[$v]		=	2;
			} else {
				$cache[$v]		=	1;
			}
		}

		return $cache[$v];
	}
}