<?php
/**
 * @package    Joomla.Component.Builder
 *
 * @created    30th April, 2015
 * @author     Llewellyn van der Merwe <http://www.joomlacomponentbuilder.com>
 * @github     Joomla Component Builder <https://github.com/vdm-io/Joomla-Component-Builder>
 * @copyright  Copyright (C) 2015 - 2020 Vast Development Method. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

// No direct access to this file
defined('_JEXEC') or die('Restricted access');

// Use the component builder autoloader
ComponentbuilderHelper::autoLoader();

/**
 * Compiler class
 */
class Compiler extends Infusion
{
	/**
	 * The Temp path
	 *
	 * @var      string
	 */
	public $tempPath;

	/**
	 * The timer
	 *
	 * @var      string
	 */
	private $time_start;
	private $time_end;
	public $secondsCompiled;

	/**
	 * The file path array
	 *
	 * @var      string
	 */
	public $filepath
		= array(
			'component'        => '',
			'component-folder' => '',
			'package'          => '',
			'plugins'          => array(),
			'plugins-folders'  => array(),
			'modules'          => array()
		);

	// fixed pathes
	protected $dynamicIntegration = false;
	protected $backupPath = false;
	protected $repoPath = false;
	protected $addCustomCodeAt = array();

	/**
	 * Constructor
	 */
	public function __construct($config = array())
	{
		// to check the compiler speed
		$this->time_start = microtime(true);
		// first we run the perent constructors
		if (parent::__construct($config))
		{
			// set temp directory
			$comConfig      = JFactory::getConfig();
			$this->tempPath = $comConfig->get('tmp_path');
			// set some folder paths in relation to distribution
			if ($config['backup'])
			{
				$this->backupPath         = $this->params->get(
					'backup_folder_path', $this->tempPath
				);
				$this->dynamicIntegration = true;
			}
			// set local repos switch
			if ($config['repository'])
			{
				$this->repoPath = $this->params->get('git_folder_path', null);
			}
			// remove site folder if not needed (TODO add check if custom script was moved to site folder then we must do a more complex cleanup here)
			if ($this->removeSiteFolder && $this->removeSiteEditFolder)
			{
				// first remove the files and folders
				$this->removeFolder($this->componentPath . '/site');
				// clear form component xml
				$xmlPath        = $this->componentPath . '/'
					. $this->fileContentStatic[$this->hhh . 'component'
					. $this->hhh] . '.xml';
				$componentXML   = ComponentbuilderHelper::getFileContents(
					$xmlPath
				);
				$textToSite     = ComponentbuilderHelper::getBetween(
					$componentXML, '<files folder="site">', '</files>'
				);
				$textToSiteLang = ComponentbuilderHelper::getBetween(
					$componentXML, '<languages folder="site">', '</languages>'
				);
				$componentXML   = str_replace(
					array('<files folder="site">' . $textToSite . "</files>",
					      '<languages folder="site">' . $textToSiteLang
					      . "</languages>"), array('', ''), $componentXML
				);
				$this->writeFile($xmlPath, $componentXML);
			}
			// Trigger Event: jcb_ce_onBeforeUpdateFiles
			$this->triggerEvent(
				'jcb_ce_onBeforeUpdateFiles',
				array(&$this->componentContext, $this)
			);
			// now update the files
			if (!$this->updateFiles())
			{
				return false;
			}
			// Trigger Event: jcb_ce_onBeforeGetCustomCode
			$this->triggerEvent(
				'jcb_ce_onBeforeGetCustomCode',
				array(&$this->componentContext, $this)
			);
			// now insert into the new files
			if ($this->getCustomCode())
			{
				// Trigger Event: jcb_ce_onBeforeAddCustomCode
				$this->triggerEvent(
					'jcb_ce_onBeforeAddCustomCode',
					array(&$this->componentContext, $this)
				);

				$this->addCustomCode();
			}
			// Trigger Event: jcb_ce_onBeforeSetLangFileData
			$this->triggerEvent(
				'jcb_ce_onBeforeSetLangFileData',
				array(&$this->componentContext, $this)
			);
			// set the lang data now
			$this->setLangFileData();
			// set the language notice if it was set
			if (ComponentbuilderHelper::checkArray($this->langNot)
				|| ComponentbuilderHelper::checkArray($this->langSet))
			{
				if (ComponentbuilderHelper::checkArray($this->langNot))
				{
					$this->app->enqueueMessage(
						JText::_('<hr /><h3>Language Warning</h3>'), 'Warning'
					);
					foreach ($this->langNot as $tag => $percentage)
					{
						$this->app->enqueueMessage(
							JText::sprintf(
								'The <b>%s</b> language has %s&#37; translated, you will need to translate %s&#37; of the language strings before it will be added.',
								$tag, $percentage, $this->percentageLanguageAdd
							), 'Warning'
						);
					}
					$this->app->enqueueMessage(
						JText::_('<hr /><h3>Language Notice</h3>'), 'Notice'
					);
					$this->app->enqueueMessage(
						JText::sprintf(
							'<b>You can change this percentage of translated strings required in the global options of JCB.</b><br />Please watch this <a href=%s>tutorial for more help surrounding the JCB translations manager</a>.',
							'"https://youtu.be/zzAcVkn_cWU?list=PLQRGFI8XZ_wtGvPQZWBfDzzlERLQgpMRE" target="_blank" title="JCB Tutorial surrounding Translation Manager"'
						), 'Notice'
					);
				}
				// set why the strings were added
				$whyAddedLang = JText::sprintf(
					'because more then %s&#37; of the strings have been translated.',
					$this->percentageLanguageAdd
				);
				if ($this->debugLinenr)
				{
					$whyAddedLang = JText::_(
						'because the debugging mode is on. (debug line numbers)'
					);
				}
				// show languages that were added
				if (ComponentbuilderHelper::checkArray($this->langSet))
				{
					$this->app->enqueueMessage(
						JText::_('<hr /><h3>Language Notice</h3>'), 'Notice'
					);
					foreach ($this->langSet as $tag => $percentage)
					{
						$this->app->enqueueMessage(
							JText::sprintf(
								'The <b>%s</b> language has %s&#37; translated. Was addeded %s',
								$tag, $percentage, $whyAddedLang
							), 'Notice'
						);
					}
				}
			}
			// move the update server into place
			$this->setUpdateServer();
			// set the global counters
			$this->setCountingStuff();
			// build read me
			$this->buildReadMe();
			// set local repos
			$this->setLocalRepos();
			// zip the component
			if (!$this->zipComponent())
			{
				// done with error
				return false;
			}
			// if there are modules zip them
			$this->zipModules();
			// if there are plugins zip them
			$this->zipPlugins();
			// do lang mismatch check
			if (ComponentbuilderHelper::checkArray($this->langMismatch))
			{
				if (ComponentbuilderHelper::checkArray($this->langMatch))
				{
					$mismatch = array_diff(
						array_unique($this->langMismatch),
						array_unique($this->langMatch)
					);
				}
				else
				{
					$mismatch = array_unique($this->langMismatch);
				}
				// set a notice if we have a mismatch
				if (isset($mismatch)
					&& ComponentbuilderHelper::checkArray(
						$mismatch
					))
				{
					$this->app->enqueueMessage(
						JText::_('<hr /><h3>Language Warning</h3>'), 'Warning'
					);
					if (count((array) $mismatch) > 1)
					{
						$this->app->enqueueMessage(
							JText::_(
								'<h3>Please check the following mismatching Joomla.JText language constants.</h3>'
							), 'Warning'
						);
					}
					else
					{
						$this->app->enqueueMessage(
							JText::_(
								'<h3>Please check the following mismatch Joomla.JText language constant.</h3>'
							), 'Warning'
						);
					}
					// add the mismatching issues
					foreach ($mismatch as $string)
					{
						$constant = $this->langPrefix . '_'
							. ComponentbuilderHelper::safeString($string, 'U');
						$this->app->enqueueMessage(
							JText::sprintf(
								'The <b>Joomla.JText._(&apos;%s&apos;)</b> language constant for <b>%s</b> does not have a corresponding <code>JText::script(&apos;%s&apos;)</code> decalaration, please add it.',
								$constant, $string, $string
							), 'Warning'
						);
					}
				}
			}
			// check if we should add a EXTERNALCODE notice
			if (ComponentbuilderHelper::checkArray($this->externalCodeString))
			{
				// number of external code strings
				$externalCount = count($this->externalCodeString);
				// the correct string
				$externalCodeString = ($externalCount == 1) ? JText::_(
					'code/string'
				) : JText::_('code/strings');
				// the notice
				$this->app->enqueueMessage(
					JText::_('<hr /><h3>External Code Notice</h3>'), 'Notice'
				);
				$this->app->enqueueMessage(
					JText::sprintf(
						'There has been <b>%s - %s</b> added to this component as EXTERNALCODE. To avoid shipping your component with malicious %s always make sure that the correct <b>code/string values</b> were used.',
						$externalCount, $externalCodeString, $externalCodeString
					), 'Notice'
				);
			}
			// end the timer here
			$this->time_end        = microtime(true);
			$this->secondsCompiled = $this->time_end - $this->time_start;

			// completed the compilation
			return true;
		}

		return false;
	}

	/**
	 * Set the line number in comments
	 *
	 * @param   int  $nr  The line number
	 *
	 * @return  void
	 *
	 */
	private function setLine($nr)
	{
		if ($this->debugLinenr)
		{
			return ' [Compiler ' . $nr . ']';
		}

		return '';
	}

	/**
	 * Set the dynamic data to the created fils
	 *
	 * @return  bool true on success
	 *
	 */
	protected function updateFiles()
	{
		if (isset($this->newFiles['static'])
			&& ComponentbuilderHelper::checkArray($this->newFiles['static'])
			&& isset($this->newFiles['dynamic'])
			&& ComponentbuilderHelper::checkArray($this->newFiles['dynamic']))
		{
			// get the bom file
			$bom = ComponentbuilderHelper::getFileContents($this->bomPath);
			// first we do the static files
			foreach ($this->newFiles['static'] as $static)
			{
				if (JFile::exists($static['path']))
				{
					$this->setFileContent(
						$static['name'], $static['path'], $bom
					);
				}
			}
			// now we do the dynamic files
			foreach ($this->newFiles['dynamic'] as $view => $files)
			{
				if (isset($this->fileContentDynamic[$view])
					&& ComponentbuilderHelper::checkArray(
						$this->fileContentDynamic[$view]
					))
				{
					foreach ($files as $file)
					{
						if ($file['view'] == $view)
						{
							if (JFile::exists($file['path']))
							{
								$this->setFileContent(
									$file['name'], $file['path'], $bom,
									$file['view']
								);
							}
						}
					}
				}
				// free up some memory
				unset($this->fileContentDynamic[$view]);
			}
			// free up some memory
			unset($this->newFiles['dynamic']);
			// do modules if found
			if (ComponentbuilderHelper::checkArray($this->joomlaModules))
			{
				foreach ($this->joomlaModules as $module)
				{
					if (ComponentbuilderHelper::checkObject($module)
						&& isset($this->newFiles[$module->key])
						&& ComponentbuilderHelper::checkArray(
							$this->newFiles[$module->key]
						))
					{
						// move field or rule if needed
						if (isset($module->fields_rules_paths)
							&& $module->fields_rules_paths == 2)
						{
							// check the config fields
							if (isset($module->config_fields)
								&& ComponentbuilderHelper::checkArray(
									$module->config_fields
								))
							{
								foreach (
									$module->config_fields as $field_name =>
									$fieldsets
								)
								{
									foreach ($fieldsets as $fieldset => $fields)
									{
										foreach ($fields as $field)
										{
											$this->moveFieldsRules(
												$field, $module->folder_path
											);
										}
									}
								}
							}
							// check the fieldsets
							if (isset($module->form_files)
								&& ComponentbuilderHelper::checkArray(
									$module->form_files
								))
							{
								foreach ($module->form_files as $file => $files)
								{
									foreach (
										$files as $field_name => $fieldsets
									)
									{
										foreach (
											$fieldsets as $fieldset => $fields
										)
										{
											foreach ($fields as $field)
											{
												$this->moveFieldsRules(
													$field, $module->folder_path
												);
											}
										}
									}
								}
							}
						}
						// update the module files
						foreach ($this->newFiles[$module->key] as $module_file)
						{
							if (JFile::exists($module_file['path']))
							{
								$this->setFileContent(
									$module_file['name'], $module_file['path'],
									$bom, $module->key
								);
							}
						}
						// free up some memory
						unset($this->newFiles[$module->key]);
						unset($this->fileContentDynamic[$module->key]);
					}
				}
			}
			// do plugins if found
			if (ComponentbuilderHelper::checkArray($this->joomlaPlugins))
			{
				foreach ($this->joomlaPlugins as $plugin)
				{
					if (ComponentbuilderHelper::checkObject($plugin)
						&& isset($this->newFiles[$plugin->key])
						&& ComponentbuilderHelper::checkArray(
							$this->newFiles[$plugin->key]
						))
					{
						// move field or rule if needed
						if (isset($plugin->fields_rules_paths)
							&& $plugin->fields_rules_paths == 2)
						{
							// check the config fields
							if (isset($plugin->config_fields)
								&& ComponentbuilderHelper::checkArray(
									$plugin->config_fields
								))
							{
								foreach (
									$plugin->config_fields as $field_name =>
									$fieldsets
								)
								{
									foreach ($fieldsets as $fieldset => $fields)
									{
										foreach ($fields as $field)
										{
											$this->moveFieldsRules(
												$field, $plugin->folder_path
											);
										}
									}
								}
							}
							// check the fieldsets
							if (isset($plugin->form_files)
								&& ComponentbuilderHelper::checkArray(
									$plugin->form_files
								))
							{
								foreach ($plugin->form_files as $file => $files)
								{
									foreach (
										$files as $field_name => $fieldsets
									)
									{
										foreach (
											$fieldsets as $fieldset => $fields
										)
										{
											foreach ($fields as $field)
											{
												$this->moveFieldsRules(
													$field, $plugin->folder_path
												);
											}
										}
									}
								}
							}
						}
						// update the plugin files
						foreach ($this->newFiles[$plugin->key] as $plugin_file)
						{
							if (JFile::exists($plugin_file['path']))
							{
								$this->setFileContent(
									$plugin_file['name'], $plugin_file['path'],
									$bom, $plugin->key
								);
							}
						}
						// free up some memory
						unset($this->newFiles[$plugin->key]);
						unset($this->fileContentDynamic[$plugin->key]);
					}
				}
			}

			return true;
		}

		return false;
	}

	/**
	 * set the file content
	 *
	 * @return  void
	 *
	 */
	protected function setFileContent(&$name, &$path, &$bom, $view = null)
	{
		// Trigger Event: jcb_ce_onBeforeSetFileContent
		$this->triggerEvent(
			'jcb_ce_onBeforeSetFileContent',
			array(&$this->componentContext, &$name, &$path, &$bom, &$view)
		);
		// set the file name
		$this->fileContentStatic[$this->hhh . 'FILENAME' . $this->hhh] = $name;
		// check if the file should get PHP opening
		$php = '';
		if (ComponentbuilderHelper::checkFileType($name, 'php'))
		{
			$php = "<?php\n";
		}
		// get content of the file
		$string = ComponentbuilderHelper::getFileContents($path);
		// Trigger Event: jcb_ce_onGetFileContents
		$this->triggerEvent(
			'jcb_ce_onGetFileContents',
			array(&$this->componentContext, &$string, &$name, &$path, &$bom,
			      &$view)
		);
		// see if we should add a BOM
		if (strpos($string, $this->hhh . 'BOM' . $this->hhh) !== false)
		{
			list($wast, $code) = explode(
				$this->hhh . 'BOM' . $this->hhh, $string
			);
			$string = $php . $bom . $code;
		}
		// set the answer
		$answer = $this->setPlaceholders($string, $this->fileContentStatic, 3);
		// set the dynamic answer
		if ($view)
		{
			$answer = $this->setPlaceholders(
				$answer, $this->fileContentDynamic[$view], 3
			);
		}
		// check if this file needs extra care :)
		if (isset($this->updateFileContent[$path]))
		{
			$answer = $this->setDynamicValues($answer);
		}
		// Trigger Event: jcb_ce_onBeforeSetFileContent
		$this->triggerEvent(
			'jcb_ce_onBeforeWriteFileContent',
			array(&$this->componentContext, &$answer, &$name, &$path, &$bom,
			      &$view)
		);
		// add answer back to file
		$this->writeFile($path, $answer);
		// count the file lines
		$this->lineCount = $this->lineCount + substr_count($answer, PHP_EOL);
	}

	/**
	 * move the local update server xml file to a remote ftp server
	 *
	 * @return  void
	 *
	 */
	protected function setUpdateServer()
	{
		// move the component update server to host
		if ($this->componentData->add_update_server == 1
			&& $this->componentData->update_server_target == 1
			&& isset($this->updateServerFileName)
			&& $this->dynamicIntegration)
		{
			$update_server_xml_path = $this->componentPath . '/'
				. $this->updateServerFileName . '.xml';
			// make sure we have the correct file
			if (JFile::exists($update_server_xml_path)
				&& isset($this->componentData->update_server))
			{
				// move to server
				ComponentbuilderHelper::moveToServer(
					$update_server_xml_path,
					$this->updateServerFileName . '.xml',
					(int) $this->componentData->update_server,
					$this->componentData->update_server_protocol
				);
				// remove the local file
				JFile::delete($update_server_xml_path);
			}
		}
		// move the plugins update server to host
		if (ComponentbuilderHelper::checkArray($this->joomlaPlugins))
		{
			foreach ($this->joomlaPlugins as $plugin)
			{
				if (ComponentbuilderHelper::checkObject($plugin)
					&& isset($plugin->add_update_server)
					&& $plugin->add_update_server == 1
					&& isset($plugin->update_server_target)
					&& $plugin->update_server_target == 1
					&& isset($plugin->update_server)
					&& is_numeric($plugin->update_server)
					&& $plugin->update_server > 0
					&& isset($plugin->update_server_xml_path)
					&& JFile::exists($plugin->update_server_xml_path)
					&& isset($plugin->update_server_xml_file_name)
					&& ComponentbuilderHelper::checkString(
						$plugin->update_server_xml_file_name
					))
				{
					// move to server
					ComponentbuilderHelper::moveToServer(
						$plugin->update_server_xml_path,
						$plugin->update_server_xml_file_name,
						(int) $plugin->update_server,
						$plugin->update_server_protocol
					);
					// remove the local file
					JFile::delete($plugin->update_server_xml_path);
				}
			}
		}
	}

	// link canges made to views into the file license
	protected function fixLicenseValues($data)
	{
		// check if these files have its own config data)
		if (isset($data['config'])
			&& ComponentbuilderHelper::checkArray(
				$data['config']
			)
			&& $this->componentData->mvc_versiondate == 1)
		{
			foreach ($data['config'] as $key => $value)
			{
				if ($this->hhh . 'VERSION' . $this->hhh === $key)
				{
					// hmm we sould in some way make it known that this version number
					// is not in relation the the project but to the file only... any ideas?
					// this is the best for now...
					if (1 == $value)
					{
						$value = '@first version of this MVC';
					}
					else
					{
						$value = '@update number ' . $value . ' of this MVC';
					}
				}
				$this->fileContentStatic[$key] = $value;
			}

			return true;
		}
		// else insure to reset to global
		$this->fileContentStatic[$this->hhh . 'CREATIONDATE' . $this->hhh]
			= $this->fileContentStatic[$this->hhh . 'CREATIONDATE' . $this->hhh
		. 'GLOBAL'];
		$this->fileContentStatic[$this->hhh . 'BUILDDATE' . $this->hhh]
			= $this->fileContentStatic[$this->hhh . 'BUILDDATE' . $this->hhh
		. 'GLOBAL'];
		$this->fileContentStatic[$this->hhh . 'VERSION' . $this->hhh]
			= $this->fileContentStatic[$this->hhh . 'VERSION' . $this->hhh
		. 'GLOBAL'];
	}

	// set all global numbers
	protected function setCountingStuff()
	{
		// what is the size in terms of an A4 book
		$this->pageCount = round($this->lineCount / 56);
		// setup the unrealistic numbers
		$this->folderSeconds = $this->folderCount * 5;
		$this->fileSeconds   = $this->fileCount * 5;
		$this->lineSeconds   = $this->lineCount * 10;
		$this->seconds       = $this->folderSeconds + $this->fileSeconds
			+ $this->lineSeconds;
		$this->totalHours    = round($this->seconds / 3600);
		$this->totalDays     = round($this->totalHours / 8);
		// setup the more realistic numbers
		$this->secondsDebugging = $this->seconds / 4;
		$this->secondsPlanning  = $this->seconds / 7;
		$this->secondsMapping   = $this->seconds / 10;
		$this->secondsOffice    = $this->seconds / 6;
		$this->actualSeconds    = $this->folderSeconds + $this->fileSeconds
			+ $this->lineSeconds + $this->secondsDebugging
			+ $this->secondsPlanning + $this->secondsMapping
			+ $this->secondsOffice;
		$this->actualTotalHours = round($this->actualSeconds / 3600);
		$this->actualTotalDays  = round($this->actualTotalHours / 8);
		$this->debuggingHours   = round($this->secondsDebugging / 3600);
		$this->planningHours    = round($this->secondsPlanning / 3600);
		$this->mappingHours     = round($this->secondsMapping / 3600);
		$this->officeHours      = round($this->secondsOffice / 3600);
		// the actual time spent
		$this->actualHoursSpent = $this->actualTotalHours - $this->totalHours;
		$this->actualDaysSpent  = $this->actualTotalDays - $this->totalDays;
		// calculate the projects actual time frame of completion
		$this->projectWeekTime  = round($this->actualTotalDays / 5, 1);
		$this->projectMonthTime = round($this->actualTotalDays / 24, 1);
	}

	private function buildReadMe()
	{
		// do a final run to update the readme file
		$two = 0;
		foreach ($this->newFiles['static'] as $static)
		{
			if (('README.md' === $static['name']
					|| 'README.txt' === $static['name'])
				&& $this->componentData->addreadme
				&& JFile::exists($static['path']))
			{
				$this->setReadMe($static['path']);
				$two++;
			}
			if ($two == 2)
			{
				break;
			}
		}
		unset($this->newFiles['static']);
	}

	private function setReadMe($path)
	{
		// set readme data if not set already
		if (!isset(
				$this->fileContentStatic[$this->hhh . 'LINE_COUNT' . $this->hhh]
			)
			|| $this->fileContentStatic[$this->hhh . 'LINE_COUNT' . $this->hhh]
			!= $this->lineCount)
		{
			$this->buildReadMeData();
		}
		// get the file
		$string = ComponentbuilderHelper::getFileContents($path);
		// update the file
		$answer = $this->setPlaceholders($string, $this->fileContentStatic);
		// add to zip array
		$this->writeFile($path, $answer);
	}

	private function buildReadMeData()
	{
		// set some defaults
		$this->fileContentStatic[$this->hhh . 'LINE_COUNT' . $this->hhh]
			= $this->lineCount;
		$this->fileContentStatic[$this->hhh . 'FIELD_COUNT' . $this->hhh]
			= $this->fieldCount;
		$this->fileContentStatic[$this->hhh . 'FILE_COUNT' . $this->hhh]
			= $this->fileCount;
		$this->fileContentStatic[$this->hhh . 'FOLDER_COUNT' . $this->hhh]
			= $this->folderCount;
		$this->fileContentStatic[$this->hhh . 'PAGE_COUNT' . $this->hhh]
			= $this->pageCount;
		$this->fileContentStatic[$this->hhh . 'folders' . $this->hhh]
			= $this->folderSeconds;
		$this->fileContentStatic[$this->hhh . 'foldersSeconds' . $this->hhh]
			= $this->folderSeconds;
		$this->fileContentStatic[$this->hhh . 'files' . $this->hhh]
			= $this->fileSeconds;
		$this->fileContentStatic[$this->hhh . 'filesSeconds' . $this->hhh]
			= $this->fileSeconds;
		$this->fileContentStatic[$this->hhh . 'lines' . $this->hhh]
			= $this->lineSeconds;
		$this->fileContentStatic[$this->hhh . 'linesSeconds' . $this->hhh]
			= $this->lineSeconds;
		$this->fileContentStatic[$this->hhh . 'seconds' . $this->hhh]
			= $this->actualSeconds;
		$this->fileContentStatic[$this->hhh . 'actualSeconds' . $this->hhh]
			= $this->actualSeconds;
		$this->fileContentStatic[$this->hhh . 'totalHours' . $this->hhh]
			= $this->totalHours;
		$this->fileContentStatic[$this->hhh . 'totalDays' . $this->hhh]
			= $this->totalDays;
		$this->fileContentStatic[$this->hhh . 'debugging' . $this->hhh]
			= $this->secondsDebugging;
		$this->fileContentStatic[$this->hhh . 'secondsDebugging' . $this->hhh]
			= $this->secondsDebugging;
		$this->fileContentStatic[$this->hhh . 'planning' . $this->hhh]
			= $this->secondsPlanning;
		$this->fileContentStatic[$this->hhh . 'secondsPlanning' . $this->hhh]
			= $this->secondsPlanning;
		$this->fileContentStatic[$this->hhh . 'mapping' . $this->hhh]
			= $this->secondsMapping;
		$this->fileContentStatic[$this->hhh . 'secondsMapping' . $this->hhh]
			= $this->secondsMapping;
		$this->fileContentStatic[$this->hhh . 'office' . $this->hhh]
			= $this->secondsOffice;
		$this->fileContentStatic[$this->hhh . 'secondsOffice' . $this->hhh]
			= $this->secondsOffice;
		$this->fileContentStatic[$this->hhh . 'actualTotalHours' . $this->hhh]
			= $this->actualTotalHours;
		$this->fileContentStatic[$this->hhh . 'actualTotalDays' . $this->hhh]
			= $this->actualTotalDays;
		$this->fileContentStatic[$this->hhh . 'debuggingHours' . $this->hhh]
			= $this->debuggingHours;
		$this->fileContentStatic[$this->hhh . 'planningHours' . $this->hhh]
			= $this->planningHours;
		$this->fileContentStatic[$this->hhh . 'mappingHours' . $this->hhh]
			= $this->mappingHours;
		$this->fileContentStatic[$this->hhh . 'officeHours' . $this->hhh]
			= $this->officeHours;
		$this->fileContentStatic[$this->hhh . 'actualHoursSpent' . $this->hhh]
			= $this->actualHoursSpent;
		$this->fileContentStatic[$this->hhh . 'actualDaysSpent' . $this->hhh]
			= $this->actualDaysSpent;
		$this->fileContentStatic[$this->hhh . 'projectWeekTime' . $this->hhh]
			= $this->projectWeekTime;
		$this->fileContentStatic[$this->hhh . 'projectMonthTime' . $this->hhh]
			= $this->projectMonthTime;
	}

	private function setLocalRepos()
	{
		// move it to the repo folder if set
		if (isset($this->repoPath)
			&& ComponentbuilderHelper::checkString(
				$this->repoPath
			))
		{
			// set the repo path
			$repoFullPath = $this->repoPath . '/com_'
				. $this->componentData->sales_name . '__joomla_'
				. $this->joomlaVersion;
			// Trigger Event: jcb_ce_onBeforeUpdateRepo
			$this->triggerEvent(
				'jcb_ce_onBeforeUpdateRepo',
				array(&$this->componentContext, &$this->componentPath,
				      &$repoFullPath, &$this->componentData)
			);
			// remove old data
			$this->removeFolder($repoFullPath, $this->componentData->toignore);
			// set the new data
			JFolder::copy($this->componentPath, $repoFullPath, '', true);
			// Trigger Event: jcb_ce_onAfterUpdateRepo
			$this->triggerEvent(
				'jcb_ce_onAfterUpdateRepo',
				array(&$this->componentContext, &$this->componentPath,
				      &$repoFullPath, &$this->componentData)
			);

			// move the modules to local folder repos
			if (ComponentbuilderHelper::checkArray($this->joomlaModules))
			{
				foreach ($this->joomlaModules as $module)
				{
					if (ComponentbuilderHelper::checkObject($module)
						&& isset($module->file_name))
					{
						$module_context = 'module.' . $module->file_name . '.'
							. $module->id;
						// set the repo path
						$repoFullPath = $this->repoPath . '/'
							. $module->folder_name . '__joomla_'
							. $this->joomlaVersion;
						// Trigger Event: jcb_ce_onBeforeUpdateRepo
						$this->triggerEvent(
							'jcb_ce_onBeforeUpdateRepo',
							array(&$module_context, &$module->folder_path,
							      &$repoFullPath, &$module)
						);
						// remove old data
						$this->removeFolder(
							$repoFullPath, $this->componentData->toignore
						);
						// set the new data
						JFolder::copy(
							$module->folder_path, $repoFullPath, '', true
						);
						// Trigger Event: jcb_ce_onAfterUpdateRepo
						$this->triggerEvent(
							'jcb_ce_onAfterUpdateRepo',
							array(&$module_context, &$module->folder_path,
							      &$repoFullPath, &$module)
						);
					}
				}
			}
			// move the plugins to local folder repos
			if (ComponentbuilderHelper::checkArray($this->joomlaPlugins))
			{
				foreach ($this->joomlaPlugins as $plugin)
				{
					if (ComponentbuilderHelper::checkObject($plugin)
						&& isset($plugin->file_name))
					{
						$plugin_context = 'plugin.' . $plugin->file_name . '.'
							. $plugin->id;
						// set the repo path
						$repoFullPath = $this->repoPath . '/'
							. $plugin->folder_name . '__joomla_'
							. $this->joomlaVersion;
						// Trigger Event: jcb_ce_onBeforeUpdateRepo
						$this->triggerEvent(
							'jcb_ce_onBeforeUpdateRepo',
							array(&$plugin_context, &$plugin->folder_path,
							      &$repoFullPath, &$plugin)
						);
						// remove old data
						$this->removeFolder(
							$repoFullPath, $this->componentData->toignore
						);
						// set the new data
						JFolder::copy(
							$plugin->folder_path, $repoFullPath, '', true
						);
						// Trigger Event: jcb_ce_onAfterUpdateRepo
						$this->triggerEvent(
							'jcb_ce_onAfterUpdateRepo',
							array(&$plugin_context, &$plugin->folder_path,
							      &$repoFullPath, &$plugin)
						);
					}
				}
			}
		}
	}

	private function zipComponent()
	{
		// Component Folder Name
		$this->filepath['component-folder'] = $this->componentFolderName;
		// the name of the zip file to create
		$this->filepath['component'] = $this->tempPath . '/'
			. $this->filepath['component-folder'] . '.zip';
		// Trigger Event: jcb_ce_onBeforeZipComponent
		$this->triggerEvent(
			'jcb_ce_onBeforeZipComponent',
			array(&$this->componentContext, &$this->componentPath,
			      &$this->filepath['component'], &$this->tempPath,
			      &$this->componentFolderName, &$this->componentData)
		);
		//create the zip file
		if (ComponentbuilderHelper::zip(
			$this->componentPath, $this->filepath['component']
		))
		{
			// now move to backup if zip was made and backup is required
			if ($this->backupPath && $this->dynamicIntegration)
			{
				// Trigger Event: jcb_ce_onBeforeBackupZip
				$this->triggerEvent(
					'jcb_ce_onBeforeBackupZip', array(&$this->componentContext,
					                                  &$this->filepath['component'],
					                                  &$this->tempPath,
					                                  &$this->backupPath,
					                                  &$this->componentData)
				);
				// copy the zip to backup path
				JFile::copy(
					$this->filepath['component'],
					$this->backupPath . '/' . $this->componentBackupName
					. '.zip'
				);
			}

			// move to sales server host
			if ($this->componentData->add_sales_server == 1
				&& $this->dynamicIntegration)
			{
				// make sure we have the correct file
				if (isset($this->componentData->sales_server))
				{
					// Trigger Event: jcb_ce_onBeforeMoveToServer
					$this->triggerEvent(
						'jcb_ce_onBeforeMoveToServer',
						array(&$this->componentContext,
						      &$this->filepath['component'], &$this->tempPath,
						      &$this->componentSalesName, &$this->componentData)
					);
					// move to server
					ComponentbuilderHelper::moveToServer(
						$this->filepath['component'],
						$this->componentSalesName . '.zip',
						(int) $this->componentData->sales_server,
						$this->componentData->sales_server_protocol
					);
				}
			}
			// Trigger Event: jcb_ce_onAfterZipComponent
			$this->triggerEvent(
				'jcb_ce_onAfterZipComponent',
				array(&$this->componentContext, &$this->filepath['component'],
				      &$this->tempPath, &$this->componentFolderName,
				      &$this->componentData)
			);
			// remove the component folder since we are done
			if ($this->removeFolder($this->componentPath))
			{
				return true;
			}
		}

		return false;
	}

	private function zipModules()
	{
		if (ComponentbuilderHelper::checkArray($this->joomlaModules))
		{
			foreach ($this->joomlaModules as $module)
			{
				if (ComponentbuilderHelper::checkObject($module)
					&& isset($module->zip_name)
					&& ComponentbuilderHelper::checkString($module->zip_name)
					&& isset($module->folder_path)
					&& ComponentbuilderHelper::checkString(
						$module->folder_path
					))
				{
					// set module context
					$module_context = $module->file_name . '.' . $module->id;
					// Component Folder Name
					$this->filepath['modules-folder'][$module->id]
						= $module->zip_name;
					// the name of the zip file to create
					$this->filepath['modules'][$module->id] = $this->tempPath
						. '/' . $module->zip_name . '.zip';
					// Trigger Event: jcb_ce_onBeforeZipModule
					$this->triggerEvent(
						'jcb_ce_onBeforeZipModule',
						array(&$module_context, &$module->folder_path,
						      &$this->filepath['modules'][$module->id],
						      &$this->tempPath, &$module->zip_name, &$module)
					);
					//create the zip file
					if (ComponentbuilderHelper::zip(
						$module->folder_path,
						$this->filepath['modules'][$module->id]
					))
					{
						// now move to backup if zip was made and backup is required
						if ($this->backupPath)
						{
							$__module_context = 'module.' . $module_context;
							// Trigger Event: jcb_ce_onBeforeBackupZip
							$this->triggerEvent(
								'jcb_ce_onBeforeBackupZip',
								array(&$__module_context,
								      &$this->filepath['modules'][$module->id],
								      &$this->tempPath, &$this->backupPath,
								      &$module)
							);
							// copy the zip to backup path
							JFile::copy(
								$this->filepath['modules'][$module->id],
								$this->backupPath . '/' . $module->zip_name
								. '.zip'
							);
						}

						// move to sales server host
						if ($module->add_sales_server == 1)
						{
							// make sure we have the correct file
							if (isset($module->sales_server))
							{
								// Trigger Event: jcb_ce_onBeforeMoveToServer
								$this->triggerEvent(
									'jcb_ce_onBeforeMoveToServer',
									array(&$__module_context,
									      &$this->filepath['modules'][$module->id],
									      &$this->tempPath, &$module->zip_name,
									      &$module)
								);
								// move to server
								ComponentbuilderHelper::moveToServer(
									$this->filepath['modules'][$module->id],
									$module->zip_name . '.zip',
									(int) $module->sales_server,
									$module->sales_server_protocol
								);
							}
						}
						// Trigger Event: jcb_ce_onAfterZipModule
						$this->triggerEvent(
							'jcb_ce_onAfterZipModule', array(&$module_context,
							                                 &$this->filepath['modules'][$module->id],
							                                 &$this->tempPath,
							                                 &$module->zip_name,
							                                 &$module)
						);
						// remove the module folder since we are done
						$this->removeFolder($module->folder_path);
					}
				}
			}
		}
	}

	private function zipPlugins()
	{
		if (ComponentbuilderHelper::checkArray($this->joomlaPlugins))
		{
			foreach ($this->joomlaPlugins as $plugin)
			{
				if (ComponentbuilderHelper::checkObject($plugin)
					&& isset($plugin->zip_name)
					&& ComponentbuilderHelper::checkString($plugin->zip_name)
					&& isset($plugin->folder_path)
					&& ComponentbuilderHelper::checkString(
						$plugin->folder_path
					))
				{
					// set plugin context
					$plugin_context = $plugin->file_name . '.' . $plugin->id;
					// Component Folder Name
					$this->filepath['plugins-folder'][$plugin->id]
						= $plugin->zip_name;
					// the name of the zip file to create
					$this->filepath['plugins'][$plugin->id] = $this->tempPath
						. '/' . $plugin->zip_name . '.zip';
					// Trigger Event: jcb_ce_onBeforeZipPlugin
					$this->triggerEvent(
						'jcb_ce_onBeforeZipPlugin',
						array(&$plugin_context, &$plugin->folder_path,
						      &$this->filepath['plugins'][$plugin->id],
						      &$this->tempPath, &$plugin->zip_name, &$plugin)
					);
					//create the zip file
					if (ComponentbuilderHelper::zip(
						$plugin->folder_path,
						$this->filepath['plugins'][$plugin->id]
					))
					{
						// now move to backup if zip was made and backup is required
						if ($this->backupPath)
						{
							$__plugin_context = 'plugin.' . $plugin_context;
							// Trigger Event: jcb_ce_onBeforeBackupZip
							$this->triggerEvent(
								'jcb_ce_onBeforeBackupZip',
								array(&$__plugin_context,
								      &$this->filepath['plugins'][$plugin->id],
								      &$this->tempPath, &$this->backupPath,
								      &$plugin)
							);
							// copy the zip to backup path
							JFile::copy(
								$this->filepath['plugins'][$plugin->id],
								$this->backupPath . '/' . $plugin->zip_name
								. '.zip'
							);
						}

						// move to sales server host
						if ($plugin->add_sales_server == 1)
						{
							// make sure we have the correct file
							if (isset($plugin->sales_server))
							{
								// Trigger Event: jcb_ce_onBeforeMoveToServer
								$this->triggerEvent(
									'jcb_ce_onBeforeMoveToServer',
									array(&$__plugin_context,
									      &$this->filepath['plugins'][$plugin->id],
									      &$this->tempPath, &$plugin->zip_name,
									      &$plugin)
								);
								// move to server
								ComponentbuilderHelper::moveToServer(
									$this->filepath['plugins'][$plugin->id],
									$plugin->zip_name . '.zip',
									(int) $plugin->sales_server,
									$plugin->sales_server_protocol
								);
							}
						}
						// Trigger Event: jcb_ce_onAfterZipPlugin
						$this->triggerEvent(
							'jcb_ce_onAfterZipPlugin', array(&$plugin_context,
							                                 &$this->filepath['plugins'][$plugin->id],
							                                 &$this->tempPath,
							                                 &$plugin->zip_name,
							                                 &$plugin)
						);
						// remove the plugin folder since we are done
						$this->removeFolder($plugin->folder_path);
					}
				}
			}
		}
	}

	protected function addCustomCode()
	{
		// reset all these
		$this->clearFromPlaceHolders('view');
		$this->clearFromPlaceHolders('arg');
		foreach ($this->customCode as $nr => $target)
		{
			// reset each time per custom code
			$fingerPrint = array();
			if (isset($target['hashtarget'][0]) && $target['hashtarget'][0] > 3
				&& isset($target['path'])
				&& ComponentbuilderHelper::checkString($target['path'])
				&& isset($target['hashtarget'][1])
				&& ComponentbuilderHelper::checkString(
					$target['hashtarget'][1]
				))
			{
				$file      = $this->componentPath . '/' . $target['path'];
				$size      = (int) $target['hashtarget'][0];
				$hash      = $target['hashtarget'][1];
				$cut       = $size - 1;
				$found     = false;
				$bites     = 0;
				$lineBites = array();
				$replace   = array();
				if ($target['type'] == 1 && isset($target['hashendtarget'][0])
					&& $target['hashendtarget'][0] > 0)
				{
					$foundEnd = false;
					$sizeEnd  = (int) $target['hashendtarget'][0];
					$hashEnd  = $target['hashendtarget'][1];
					$cutEnd   = $sizeEnd - 1;
				}
				else
				{
					// replace to the end of the file
					$foundEnd = true;
				}
				$counter = 0;
				// check if file exist			
				if (JFile::exists($file))
				{
					foreach (
						new SplFileObject($file) as $lineNumber => $lineContent
					)
					{
						// if not found we need to load line bites per line
						$lineBites[$lineNumber] = (int) mb_strlen(
							$lineContent, '8bit'
						);
						if (!$found)
						{
							$bites = (int) ComponentbuilderHelper::bcmath(
								'add', $lineBites[$lineNumber], $bites
							);
						}
						if ($found && !$foundEnd)
						{
							$replace[] = (int) $lineBites[$lineNumber];
							// we musk keep last three lines to dynamic find target entry
							$fingerPrint[$lineNumber] = trim($lineContent);
							// check lines each time if it fits our target
							if (count((array) $fingerPrint) === $sizeEnd
								&& !$foundEnd)
							{
								$fingerTest = md5(implode('', $fingerPrint));
								if ($fingerTest === $hashEnd)
								{
									// we are done here
									$foundEnd = true;
									$replace  = array_slice(
										$replace, 0, count($replace) - $sizeEnd
									);
									break;
								}
								else
								{
									$fingerPrint = array_slice(
										$fingerPrint, -$cutEnd, $cutEnd, true
									);
								}
							}
							continue;
						}
						if ($found && $foundEnd)
						{
							$replace[] = (int) $lineBites[$lineNumber];
						}
						// we musk keep last three lines to dynamic find target entry
						$fingerPrint[$lineNumber] = trim($lineContent);
						// check lines each time if it fits our target
						if (count((array) $fingerPrint) === $size && !$found)
						{
							$fingerTest = md5(implode('', $fingerPrint));
							if ($fingerTest === $hash)
							{
								// we are done here
								$found = true;
								// reset in case
								$fingerPrint = array();
								// break if it is insertion
								if ($target['type'] == 2)
								{
									break;
								}
							}
							else
							{
								$fingerPrint = array_slice(
									$fingerPrint, -$cut, $cut, true
								);
							}
						}
					}
					if ($found)
					{
						$placeholder = $this->getPlaceHolder(
							(int) $target['comment_type'] . $target['type'],
							$target['id']
						);
						$data        = $placeholder['start'] . PHP_EOL
							. $this->setPlaceholders(
								$target['code'], $this->placeholders
							) . $placeholder['end'] . PHP_EOL;
						if ($target['type'] == 2)
						{
							// found it now add code from the next line
							$this->addDataToFile($file, $data, $bites);
						}
						elseif ($target['type'] == 1 && $foundEnd)
						{
							// found it now add code from the next line
							$this->addDataToFile(
								$file, $data, $bites, (int) array_sum($replace)
							);
						}
						else
						{
							// Load escaped code since the target endhash has changed
							$this->loadEscapedCode($file, $target, $lineBites);
							$this->app->enqueueMessage(
								JText::_('<hr /><h3>Custom Code Warning</h3>'),
								'Warning'
							);
							$this->app->enqueueMessage(
								JText::sprintf(
									'Custom code %s could not be added to <b>%s</b> please review the file after install at <b>line %s</b> and reposition the code, remove the comments and recompile to fix the issue. The issue could be due to a change to <b>lines below</b> the custom code.',
									'<a href="index.php?option=com_componentbuilder&view=custom_codes&task=custom_code.edit&id='
									. $target['id'] . '" target="_blank">#'
									. $target['id'] . '</a>', $target['path'],
									$target['from_line']
								), 'Warning'
							);
						}
					}
					else
					{
						// Load escaped code since the target hash has changed
						$this->loadEscapedCode($file, $target, $lineBites);
						$this->app->enqueueMessage(
							JText::_('<hr /><h3>Custom Code Warning</h3>'),
							'Warning'
						);
						$this->app->enqueueMessage(
							JText::sprintf(
								'Custom code %s could not be added to <b>%s</b> please review the file after install at <b>line %s</b> and reposition the code, remove the comments and recompile to fix the issue. The issue could be due to a change to <b>lines above</b> the custom code.',
								'<a href="index.php?option=com_componentbuilder&view=custom_codes&task=custom_code.edit&id='
								. $target['id'] . '" target="_blank">#'
								. $target['id'] . '</a>', $target['path'],
								$target['from_line']
							), 'Warning'
						);
					}
				}
				else
				{
					// Give developer a notice that file is not found.
					$this->app->enqueueMessage(
						JText::_('<hr /><h3>Custom Code Warning</h3>'),
						'Warning'
					);
					$this->app->enqueueMessage(
						JText::sprintf(
							'File <b>%s</b> could not be found, so the custom code for this file could not be addded.',
							$target['path']
						), 'Warning'
					);
				}
			}
		}
	}

	protected function loadEscapedCode($file, $target, $lineBites)
	{
		// get comment type
		if ($target['comment_type'] == 1)
		{
			$commentType  = "// ";
			$_commentType = "";
		}
		else
		{
			$commentType  = "<!--";
			$_commentType = " -->";
		}
		// escape the code
		$code = explode(PHP_EOL, $target['code']);
		$code = PHP_EOL . $commentType . implode(
				$_commentType . PHP_EOL . $commentType, $code
			) . $_commentType . PHP_EOL;
		// get place holders
		$placeholder = $this->getPlaceHolder(
			(int) $target['comment_type'] . $target['type'], $target['id']
		);
		// build the data
		$data = $placeholder['start'] . $code . $placeholder['end'] . PHP_EOL;
		// get the bites before insertion
		$bitBucket = array();
		foreach ($lineBites as $line => $value)
		{
			if ($line < $target['from_line'])
			{
				$bitBucket[] = $value;
			}
		}
		// add to the file
		$this->addDataToFile($file, $data, (int) array_sum($bitBucket));
	}

	// Thanks to http://stackoverflow.com/a/16813550/1429677
	protected function addDataToFile($file, $data, $position, $replace = null)
	{
		// start the process
		$fpFile = fopen($file, "rw+");
		$fpTemp = fopen('php://temp', "rw+");
		// make a copy of the file
		stream_copy_to_stream($fpFile, $fpTemp);
		// move to the position where we should add the data
		fseek($fpFile, $position);
		// Add the data
		fwrite($fpFile, $data);
		// truncate file at the end of the data that was added
		$remove = ComponentbuilderHelper::bcmath(
			'add', $position, mb_strlen($data, '8bit')
		);
		ftruncate($fpFile, $remove);
		// check if this was a replacement of data
		if ($replace)
		{
			$position = ComponentbuilderHelper::bcmath(
				'add', $position, $replace
			);
		}
		// move to the position of the data that should remain below the new data
		fseek($fpTemp, $position);
		// copy that remaining data to the file
		stream_copy_to_stream($fpTemp, $fpFile); // @Jack
		// done close both files
		fclose($fpFile);
		fclose($fpTemp);

		// any help to improve this is welcome...
	}

}
