<?php

class auditReports
{
	private $parent;
	private $module;
	private $processed = array();
	private $tableArray = array();

	/**
	 * auditReports constructor.
	 * @param string $module - the bean name of the parent module
	 */
	public function __construct($module)
	{
		$bean = BeanFactory::newBean($module);
		$this->parent = $bean;
		$this->module = $module;
	}

	/**
	 * Refresh all Audit Report related information
	 * @param int $recordsToProcess
	 */
	public function buildAuditSupport($recordsToProcess = -1)
	{
		$newLangList = array();
		//I arbitrarily check the date on the english language file, you can check any you want.
		$fileTime = filemtime("custom/Extension/application/Ext/Language/en_us.sugar_{$this->module}_field_name_list.php");
		if ($fileTime !== false) {
			$timeSinceUpdate = time() - $fileTime;
		} else {
			$timeSinceUpdate = time();
		}
		//Crete a language file with all the translated names for all the fields contained in this module
		//Just do this once a day, you can adjust as needed
		if (!empty($this->module) && $timeSinceUpdate > 86400) {
			foreach ($this->parent->field_defs as $fieldName => $fieldDefinition) {
				if (isset($this->parent->field_defs[$fieldName]['vname']) && !empty($this->parent->field_defs[$fieldName]['vname'])) {
					$newLangList[$fieldName] = translate($this->parent->field_defs[$fieldName]['vname'],
						$this->module);
				}
			}
			$dropDownList = $this->module . '_field_name_list';
			$this->addItemsToDropdown($dropDownList, $newLangList);
			$GLOBALS['log']->fatal("Audit: Creation of the {$dropDownList} option list is complete.");
		}

		$table = strtolower($this->module);
		//We want to remove any records that hold no data what so ever.
		$deleteSQL = "DELETE FROM {$table}_audit
                   	  WHERE COALESCE(before_value_text,'')='' AND COALESCE(after_value_text,'')='' AND
                            COALESCE(before_value_string,'')='' AND COALESCE(after_value_string,'')=''";
		$result = $GLOBALS['db']->query($deleteSQL, true);
		if ($result) {
			$numOfRows = $GLOBALS['db']->getAffectedRowCount($result);
		} else {
			$numOfRows = 0;
		}
		if ($numOfRows > 0) {
			$GLOBALS['log']->fatal("Audit: Deleted {$numOfRows} empty rows from the audit table {$table}_audit.");
		}
		//Now we want to go to the audit table and collect up all the records that have not been translated as yet.  We
		// do this by getting all the records with a NULL in BOTH before_value_text and after_value_text
		$usedIDs = "SELECT a.id,a.field_name,a.data_type,a.before_value_string,a.after_value_string
					FROM {$table}_audit a
                    WHERE COALESCE(before_value_text,'')='' AND COALESCE(after_value_text,'')=''
                    ORDER BY date_created DESC";
		if ($recordsToProcess > 0) {
			$usedIDs .= " LIMIT {$recordsToProcess}";
		}
		$result = $GLOBALS['db']->query($usedIDs, true);
		if ($result) {
			$numOfRows = $GLOBALS['db']->getRowCount($result);
		} else {
			$numOfRows = 0;
		}
		//We don't cache these for a few reasons.  Mainly because of quoting issues.  I might work this out in
		// future versions.
		$noCache = array('name', 'phone', 'varchar');
		$GLOBALS['log']->fatal("Audit: Processing {$numOfRows} rows from {$table}_audit");
		$processCount = 0;

		while ($hash = $GLOBALS['db']->fetchByAssoc($result)) {
			//Every 500 rows check how many are left
			if ($processCount++ % 1000 == 0) {
				$remainingSQL = "SELECT count(a.id)
								 FROM {$table}_audit a
                    			 WHERE COALESCE(before_value_text,'')='' AND COALESCE(after_value_text,'')=''";
				$count = $GLOBALS['db']->getOne($remainingSQL);
				$GLOBALS['log']->fatal("Audit: Number of unprocessed rows remaining in {$table}_audit: {$count} [{$processCount}/{$numOfRows}]");
			}
			$fieldName = $hash['field_name'];
			$dataType = $hash['data_type'];
			$bvs = $hash['before_value_string'];
			$avs = $hash['after_value_string'];
			$id = $hash['id'];

			//Handle before_value_string
			$isProcessed = (array_key_exists('before' . $bvs . $fieldName, $this->processed) && !in_array($dataType,
					$noCache));
			if (!$isProcessed && !empty($bvs)) {
				if (!in_array($dataType, $noCache)) {
					$this->processed['before' . $bvs . $fieldName] = 1;
				}
				$this->updateAuditTable($dataType, $table, $bvs, $fieldName, $id, 'before');
			}

			//Handle after_value_string
			$isProcessed = (array_key_exists('after' . $avs . $fieldName, $this->processed) && !in_array($dataType,
					$noCache));
			if (!$isProcessed && !empty($avs)) {
				if (!in_array($dataType, $noCache)) {
					$this->processed['after' . $avs . $fieldName] = 1;
				}
				$this->updateAuditTable($dataType, $table, $avs, $fieldName, $id, 'after');
			}
		}
		if($numOfRows==0) {
			$GLOBALS['log']->fatal("Audit: Audit Rebuild for the {$table}_audit is complete.");
		}
	}

	/**
	 * @param $dropDownList
	 * @param $item_list
	 */
	private function addItemsToDropdown($dropDownList, $item_list)
	{
		require_once('modules/ModuleBuilder/MB/ModuleBuilder.php');
		require_once('modules/ModuleBuilder/parsers/parser.dropdown.php');
		$parser = new ParserDropDown();
		$params = array();
		$_REQUEST['view_package'] = 'studio'; //need this in parser.dropdown.php
		$params['view_package'] = 'studio';
		$params['dropdown_name'] = $dropDownList; //replace with the dropdown name
		$params['dropdown_lang'] = 'en_us';//create your list...substitute with db query as needed
		foreach ($item_list as $k => $v) { //merge new and old values
			$drop_list[] = array($k, $v);
		}
		//TODO:Update this to use namespaces
		$json = getJSONobj();
		$params['list_value'] = $json->encode($drop_list);
		$parser->saveDropDown($params);
	}

	/**
	 * This is my version 0.1 of code to figure out what module a 'relate' ID is relating to.  Its not very good but
	 *  it works reliably.  I want to make it a but more fields_defs aware in the next version and remove some of this
	 *  hard coded logic
	 *
	 * @param string $fieldName
	 * @param string $value
	 * @return string
	 */
	private function getRelateValue($fieldName, $value)
	{
		$fieldDefs = $this->parent->field_defs[$fieldName];
		if (isset($fieldDefs['type']) && ($fieldDefs['type'] == 'relate' || $fieldDefs['type'] == 'email')) {
			$module = $fieldDefs['module'];
		} elseif (isset($this->parent->field_defs[$fieldDefs['group']]['module'])) {
			$module = $this->parent->field_defs[$fieldDefs['group']]['module'];
		} elseif (stristr($fieldDefs['name'], 'user') !== false) {
			$module = 'Users';
		} elseif (stristr($fieldDefs['name'], 'team') !== false) {
			$module = 'Teams';
		} elseif ($fieldName == 'parent_id') {
			$fieldDefs = $this->parent->field_defs['parent_name'];
			$module = $fieldDefs['module'];
		} else {
			//if there is a field in the audit table I can't translate I put this in the log to alert me
			$GLOBALS['log']->fatal("Unhandled fieldName in {$this->parent->module} Audit trail: {$fieldName}");
			$module = null;
		}

		if (!array_key_exists($module, $this->tableArray) && $module != null) {
			$relBean = BeanFactory::getBean($module);
			if (isset($relBean->field_defs['first_name'])) {
				$mainName = "CONCAT(first_name,' ',last_name)";
			} elseif (isset($relBean->field_defs['email_address'])) {
				$mainName = 'email_address';
			} else {
				$mainName = 'name';
			}
			$modInfo = $relBean->table_name . '/' . $mainName;
			$this->tableArray[$module] = $modInfo;
		} else {
			$modInfo = $this->tableArray[$module];
		}
		list($table_name, $name) = explode('/', $modInfo, 2);
		$dataSQL = "SELECT {$name} FROM {$table_name} WHERE id='{$value}'";
		$nameOfRecord = $GLOBALS['db']->getOne($dataSQL);
		//if the parent has been deleted from the system use this
		if (empty($nameOfRecord)) {
			$nameOfRecord = '---COULD NOT LOCATE PARENT RECORD---';
		}
		return $nameOfRecord;
	}

	/**
	 * This function does the SQL to update the before_value_text and after_value_text values in the audit table
	 *
	 * @param string $dataType
	 * @param string $table
	 * @param string $currentValue
	 * @param string $fieldName
	 * @param string $id
	 * @param string $auditFieldPrefix
	 */
	private function updateAuditTable($dataType, $table, $currentValue, $fieldName, $id, $auditFieldPrefix)
	{
		global $app_list_strings;
		switch ($dataType) {
			case 'name':
			case 'phone':
			case 'varchar':
				$currentValue = $GLOBALS['db']->quote(htmlspecialchars_decode(str_replace('\\', '', $currentValue),
					ENT_QUOTES));
				if (stristr($currentValue, "'") !== false) {
					$updateSQL = "UPDATE {$table}_audit SET {$auditFieldPrefix}_value_text='{$currentValue}' WHERE id='{$id}'";
				} else {
					$updateSQL = "UPDATE {$table}_audit SET {$auditFieldPrefix}_value_text='{$currentValue}' WHERE {$auditFieldPrefix}_value_string='{$currentValue}'";
					$this->processed[$auditFieldPrefix . $currentValue . $fieldName] = 1;
				}
				$GLOBALS['db']->query($updateSQL, true);
				break;
			case 'int':
			case 'date':
			case 'decimal':
				//These values I update ALL records that have the same text in the string field.  This means I only and
				// to use one SQL update to add translated values to potentially tens or hundreds of records.
				$currentValue = $GLOBALS['db']->quote(htmlspecialchars_decode(str_replace('\\', '', $currentValue),
					ENT_QUOTES));
				$updateSQL = "UPDATE {$table}_audit SET {$auditFieldPrefix}_value_text='{$currentValue}' WHERE {$auditFieldPrefix}_value_string='{$currentValue}'";
				$GLOBALS['db']->query($updateSQL, true);
				break;
			case 'currency':
				//I mass update these fields too but I include a hard coded $ as I couldn't get the SugarCRM currency
				// code to work for some reason.  I will fix this in a future version
				if (empty($currentValue)) {
					$newValue = '0.00';
				} else {
					$newValue = number_format($currentValue, 2, '.', ',');
				}
				//TODO: use SugarCRM currency functions
				$updateSQL = "UPDATE {$table}_audit SET {$auditFieldPrefix}_value_text='\${$newValue}' WHERE {$auditFieldPrefix}_value_string='{$currentValue}'";
				$GLOBALS['db']->query($updateSQL, true);
				break;
			case 'datetimecombo':
			case 'datetime':
				//In this one, we format the date to the Admins date format and timezone.  We cant do it to the report user as we store this
				//perfectly in the DB
				$datetime = new datetime($currentValue, new DateTimeZone('UTC'));
				$timedate = new TimeDate();
				$formattedDate = $timedate->asUser($datetime, BeanFactory::getBean('Users', '1'));
				$updateSQL = "UPDATE {$table}_audit SET {$auditFieldPrefix}_value_text='{$formattedDate} EDT' WHERE {$auditFieldPrefix}_value_string='{$currentValue}'";
				$GLOBALS['db']->query($updateSQL, true);
				break;
			case 'relate':
			case 'id':
			case 'email':
			case 'team_list':
				if ($dataType = 'email' && stristr($currentValue, '@') !== false) {
					$updateSQL = "UPDATE {$table}_audit SET {$auditFieldPrefix}_value_text='{$currentValue}' WHERE {$auditFieldPrefix}_value_string='{$currentValue}'";
				} else {
					//Get the name of the related record
					$nameOfRecord = $this->getRelateValue($fieldName, $currentValue);
					$updateSQL = "UPDATE {$table}_audit SET {$auditFieldPrefix}_value_text='{$nameOfRecord}' WHERE {$auditFieldPrefix}_value_string='{$currentValue}'";
				}
				$GLOBALS['db']->query($updateSQL, true);
				break;
			case 'bool':
				//Checkboxes
				if ($currentValue == 1) {
					$checked = 'Checked';
				} else {
					$checked = 'Unchecked';
				}
				$updateSQL = "UPDATE {$table}_audit SET {$auditFieldPrefix}_value_text='{$checked}' WHERE {$auditFieldPrefix}_value_string='{$currentValue}' AND
																									data_type='{$dataType}'";
				$GLOBALS['db']->query($updateSQL, true);
				break;
			case 'enum':
			case 'multienum':
				//Get a translated Dropdown value
				$optionList = $this->parent->field_defs[$fieldName]['options'];
				$optionArray = $app_list_strings[$optionList];
				if ($dataType == 'enum') {
					$nameOfOption = $optionArray[$currentValue];
					if (empty($nameOfOption)) {
						$nameOfOption = $currentValue . ' [UNDEFINED]';
					}
				} else {
					$listOfNames = array();
					$list = explode('^,^', substr($currentValue, 1, -1));
					foreach ($list as $item) {
						$nameOfOption = $optionArray[$item];
						if (empty($nameOfOption)) {
							$nameOfOption = $currentValue . ' [UNDEFINED]';
						}
						$listOfNames[] = $nameOfOption;
					}
					$nameOfOption = implode(', ', $listOfNames);
				}
				$updateSQL = "UPDATE {$table}_audit SET {$auditFieldPrefix}_value_text='{$nameOfOption}' WHERE {$auditFieldPrefix}_value_string='{$currentValue}' AND
																										 field_name='{$fieldName}'";
				$GLOBALS['db']->query($updateSQL, true);
				break;
			default:
				//If we have a new or unaccounted for dataTyp just log it to the sugarcrm.log file
				$GLOBALS['log']->fatal("Unhandled DataType in {$this->parent->module} Audit trail: {$dataType}");
				break;
		}
	}
}