array('created_by_name'=>'date_entered', 'modified_by_name'=>'date_modified'), 'Cases' => array('created_by_name'=>'date_entered', 'modified_by_name'=>'date_modified'), 'Contracts' => array('created_by_name'=>'date_entered', 'modified_by_name'=>'date_modified'), 'Leads' => array('created_by'=>'date_entered'), 'Meetings' => array('created_by_name'=>'date_entered', 'modified_by_name'=>'date_modified'), 'ProspectLists' => array('created_by_name'=>'date_entered', 'modified_by_name'=>'date_modified'), 'Prospects' => array('created_by_name'=>'date_entered', 'modified_by_name'=>'date_modified'), 'Tasks' => array('created_by_name'=>'date_entered', 'modified_by_name'=>'date_modified'), ); /** * Clears out the values of the arrays so that the same object can be utilized * */ protected function clear(){ unset($this->newData); $this->newData = array(); unset($this->customData); $this->customData = array(); unset($this->originalData); $this->originalData = array(); unset($this->newFields); $this->newFields = array(); unset($this->customFields); $this->customFields = array(); unset($this->originalFields); $this->originalFields = array(); unset($this->mergedFields); $this->mergedFields = array(); unset($this->mergeData); $this->mergeData = array(); $this->defaultPanel = 'default'; } /** * Allows the user to choose to use the best match algorithim or not * * @param BOOLEAN $on */ public function setBestMatch($on=true){ $this->bestMatch = $on; } /** * Allows users to set the name to use as the default panel in the meta data * * @param STRING $name - name of the default panel */ public function setDefaultPanel($name = 'default'){ $this->defaultPanel = $name; } /** * Allows the user to set a filepointer that is already open to log to * * @param FILEPOINTER $fp */ public function setLogFilePointer($fp){ $this->fp = $fp; } /** * opens the file with the 'a' parameter and use it to log messages to * * @param STRING $file - path to file we wish to log to */ public function setLogFile($file){ $this->fp = fopen($file, 'a'); } /** * */ /** * returns true if $val1 and $val2 match otherwise it returns false * * @param MULTI $val1 - a value to compare to val2 * @param MULTI $val2 - a value to compare to val1 * @return BOOLEAN - if $val1 and $val2 match */ protected function areMatchingValues($val1, $val2){ if(!is_array($val1)){ //if val2 is an array and val1 isn't then it isn't a match if(is_array($val2)){ return false; } //otherwise both are not arrays so we can return a comparison between them return $val1 == $val2; }else{ //if val1 is an array and val2 isn't then it isn't a match if(!is_array($val2)){ return false; } } foreach($val1 as $k=>$v){ if(!isset($val2[$k]))return false; if(!$this->areMatchingValues($val1[$k], $val2[$k])){ return false; } unset($val2[$k]); unset($val1[$k]); } //this implies that there are still values left so the two must not match since we unset any matching values if(!empty($val2)){ return false; } return true; } /** * Recursiveley merges two arrays * * @param ARRAY $gimp - if keys match this arrays values are overriden * @param ARRAY $dom - if keys match this arrays values will override the others * @return ARRAY $merged - the merges array */ function arrayMerge($gimp, $dom) { if(is_array($gimp) && is_array($dom)) { foreach($dom as $domKey => $domVal) { if(isset($gimp[$domKey])) { if(is_array($domVal)) { $gimp[$domKey] = $this->arrayMerge($gimp[$domKey], $dom[$domKey]); } else { $gimp[$domKey] = $domVal; } } else { $gimp[$domKey] = $domVal; } } } return $gimp; } /** * Merges the meta data of a single field * * @param ARRAY $orig - the original meta-data for this field * @param ARRAY $new - the new meta-data for this field * @param ARRAY $custom - the custom meta-data for this field * @return ARRAY $merged - the merged meta-data */ protected function mergeField($orig, $new, $custom){ $orig_custom = $this->areMatchingValues($orig, $custom); $new_custom = $this->areMatchingValues($new, $custom); // if both are true then there is nothing to merge since all three fields match if(!($orig_custom && $new_custom)){ $this->log('merging field'); $this->log('original meta-data'); $this->log($orig); $this->log('new meta-data'); $this->log($new); $this->log('custom meta-data'); $this->log($custom); $this->log('merged meta-data'); $log = true; }else{ return $new; } //if orignal and custom match always take the new value or if new and custom match if($orig_custom || $new_custom){ $this->log($new); return $new; } //if original and new match always take the custom if($this->areMatchingValues($orig, $new)){ $this->log($custom); return $custom; } if(is_array($custom)) { //if both new and custom are arrays then at this point new != custom and orig != custom and orig != new so let's merge the custom and the new and return that if(is_array($new)){ $new = $this->arrayMerge($custom, $new); $this->log($new); return $new; }else{ //otherwise we know that new is not an array and custom has been 'customized' so let's keep those customizations. $this->log($custom); return $custom; } } //default to returning the New version of the field $this->log($new); return $new; } /** * Merges the fields together and stores them in $this->mergedFields * */ protected function mergeFields() { foreach($this->customFields as $field=>$data) { //if we have this field in both the new fields and the original fields - it has existed since the last install/upgrade if(isset($this->newFields[$field]) && isset($this->originalFields[$field])){ //if both the custom field and the original match then we take the location of the custom field since it hasn't moved $loc = $this->customFields[$field]['loc']; $loc['source'] = 'custom'; $do_merge = true; //Address fields present a special problem... if(preg_match('/(alt_|primary_|billing_|shipping_)address_street/i', $field, $matches)) { $prefix = $matches[1]; $city = $prefix . 'address_city'; $postal_code = $prefix . 'address_postalcode'; $state = $prefix . 'address_state'; $country = $prefix . 'address_country'; if(isset($this->customFields[$city]) || isset($this->customFields[$postal_code]) || isset($this->customFields[$state]) || isset($this->customFields[$country])) { $do_merge = false; $this->mergedFields[$field] = array( 'data'=>$this->customFields[$field]['data'], 'loc'=>$loc); } } if($do_merge) { //but we still merge the meta data of the three $this->mergedFields[$field] = array( 'data'=>$this->mergeField($this->originalFields[$field]['data'], $this->newFields[$field]['data'], $this->customFields[$field]['data']), 'loc'=>$loc); } //if it's not set in the new fields then it was a custom field or an original field so we take the custom fields data and set the location source to custom } else if(!isset($this->newFields[$field])){ $this->mergedFields[$field] = $data; $this->mergedFields[$field]['loc']['source'] = 'custom'; } else { //otherwise the field is in both new and custom but not in the orignal so we merge the new and custom data together and take the location from the custom $this->mergedFields[$field] = array( 'data'=>$this->mergeField('', $this->newFields[$field]['data'], $this->customFields[$field]['data']), 'loc'=>$this->customFields[$field]['loc']); $this->mergedFields[$field]['loc']['source'] = 'custom'; //echo var_export($this->mergedFields[$field], true); } //then we clear out the field from unset($this->originalFields[$field]); unset($this->customFields[$field]); unset($this->newFields[$field]); } /** * These are fields that were removed by the customer */ foreach($this->originalFields as $field=>$data){ unset($this->originalFields[$field]); unset($this->newFields[$field]); } /** * These are fields that were added by sugar */ $new_field_panel = $this->defaultPanel; foreach($this->customPanelIds as $custom_panel_ids=>$panels) { $new_field_panel = $custom_panel_ids; } foreach($this->newFields as $field=>$data){ $data['loc']['source']= 'new'; $data['loc']['panel'] = $new_field_panel; $this->mergedFields[$field] = array( 'data'=>$data['data'], 'loc'=>$data['loc']); unset($this->newFields[$field]); } } /** * Walks through the merged fields and places them in the appropriate place based on their location parameter as well as the choosen algorithim * * @return ARRAY $panels - the new panels section for the merged file */ protected function buildPanels(){ $panels = array(); $panel_keys = array_keys($this->customPanelIds); $this->defaultPanel = end($panel_keys); foreach($this->mergedFields as $field_id=>$field){ //If this field is in a panel not defined in the custom layout, set it to default panel if(!isset($this->customPanelIds[$field['loc']['panel']])) { $field['loc']['panel'] = $this->defaultPanel; } if($field['loc']['source'] == 'new') { if($this->bestMatch){ //for best match as long as the column is filled let's keep walking down till we can fill it $row = end(array_keys($this->customData[$this->module][$this->viewDefs][$this->panelName][$field['loc']['panel']])); $col = 0; while(!empty($panels[$field['loc']['panel']][$row][$col])){ $col++; if($col == 2) { $row++; $col = 0; } } //row should be at a point that there is no field in this location $panels[$field['loc']['panel']][$row][$col] = $field['data']; }else{ //so for not best match we place it in the default panel at the first available column for the row $row = 0; while(!empty($panels[$this->defaultPanel][$row][$field['loc']['col']])){ $row++; } $panels[$field['loc']['panel']][$row][$field['loc']['col']] = $field['data']; } } else { $panels[$field['loc']['panel']][$field['loc']['row']][$field['loc']['col']] = $field['data']; } } foreach($panels as $k=>$panel){ foreach($panel as $r=>$row){ ksort($panels[$k][$r]); } ksort($panels[$k]); } return $panels; } /** * Merge the templateMeta entry for the view defs. Also assume that any changes made in the custom files should * have precedence since they must be changed manually, even over new files that may be provided in the upgarde * patch. * */ protected function mergeTemplateMeta() { //this is to handle the situation in Calls/Meetings where we updated the templateMeta and will fail if we don't update this. //long term we should not do this and should provide a way for calls/meetings to update themselves. if( isset($this->customData[$this->module][$this->viewDefs][$this->templateMetaName]) && strcmp(strtolower($this->module), 'calls') != 0 && strcmp(strtolower($this->module), 'meetings') != 0 ) { $this->newData[$this->module][$this->viewDefs][$this->templateMetaName] = $this->customData[$this->module][$this->viewDefs][$this->templateMetaName]; } } /** * Sets the panel section for the meta-data after it has been merged * */ protected function setPanels(){ $this->newData[$this->module][$this->viewDefs][$this->panelName] = $this->buildPanels(); /* if(!$this->isMultiPanel) { $this->newData[$this->module][$this->viewDefs][$this->panelName] = $this->newData[$this->module][$this->viewDefs][$this->panelName][$this->defaultPanel]; } */ } /** * Parses out the fields for each files meta data and then calls on mergeFields and setPanels * */ protected function mergeMetaData(){ $this->originalFields = $this->getFields($this->originalData[$this->module][$this->viewDefs][$this->panelName]); $this->originalPanelIds = $this->getPanelIds($this->originalData[$this->module][$this->viewDefs][$this->panelName]); $this->customFields = $this->getFields($this->customData[$this->module][$this->viewDefs][$this->panelName]); $this->customPanelIds = $this->getPanelIds($this->customData[$this->module][$this->viewDefs][$this->panelName]); $this->newFields = $this->getFields($this->newData[$this->module][$this->viewDefs][$this->panelName]); //echo var_export($this->newFields, true); $this->newPanelIds = $this->getPanelIds($this->newData[$this->module][$this->viewDefs][$this->panelName]); $this->mergeFields(); $this->mergeTemplateMeta(); $this->setPanels(); } /** * This takes in a list of panels and returns an associative array of field names to the meta-data of the field as well as the locations of that field * * @param ARRAY $panels - this is the 'panel' section of the meta-data * @return ARRAY $fields - an associate array of fields and their meta-data as well as their location */ protected function getFields(&$panels){ $fields = array(); $blanks = 0; $setDefaultPanel = false; if(count($panels) == 1) { $arrayKeys = array_keys($panels); if(!empty($arrayKeys[0])) { $this->defaultPanel = $arrayKeys[0]; $panels = $panels[$arrayKeys[0]]; } else { $panels = $panels['']; } $setDefaultPanel = true; } if($this->scanForMultiPanel){ require_once('include/SugarFields/Parsers/MetaParser.php'); if($setDefaultPanel || !MetaParser::hasMultiplePanels($panels)) { $panels = array($this->defaultPanel=>$panels); $this->isMultiPanel = false; } } //echo "---------------------------------------------------------\n"; //echo var_export($panels, true); foreach($panels as $panel_id=>$panel){ foreach($panel as $row_id=>$rows){ foreach($rows as $col_id=>$col){ if(empty($col)) { $field_name = 'BLANK_' . $blanks; $blanks++; } else { $field_name = is_array($col) && isset($col['name']) ? $col['name'] : $col; if(is_array($col)){ if(!empty($col['name'])) { $field_name = $col['name']; } }else{ $field_name = $col; } } if(is_string($field_name)) { // We need to replace all instances of the fake uploadfile and filename field that has custom code with the real filename field if(!empty($col['customCode'])) { if($field_name == 'uploadfile') { $replaceField = false; if ( !empty($col['customCode']) ) { $replaceField = true; unset($col['customCode']); } if( !empty($col['displayParams']) && !empty($col['displayParams']['link']) ) { $replaceField = true; } if ( $replaceField ) { $field_name = 'filename'; $col['name'] = 'filename'; } } else if ($field_name == 'filename') { $col = 'filename'; } } $fields[$field_name] = array('data'=>$col, 'loc'=>array('panel'=>"{$panel_id}", 'row'=>"{$row_id}", 'col'=>"{$col_id}")); } } } } //echo "---------------------------------------------------------\n"; //echo var_export($fields, true); return $fields; } /** * getPanelIds * */ protected function getPanelIds($panels){ $panel_ids = array(); $setDefaultPanel = false; if(count($panels) == 1) { $arrayKeys = array_keys($panels); if(!empty($arrayKeys[0])) { $this->defaulPanel = $arrayKeys[0]; $panels = $panels[$arrayKeys[0]]; } else { $panels = $panels['']; } $setDefaultPanel = true; } if($this->scanForMultiPanel){ require_once('include/SugarFields/Parsers/MetaParser.php'); if($setDefaultPanel || !MetaParser::hasMultiplePanels($panels)) { $panels = array($this->defaultPanel=>$panels); $this->isMultiPanel = false; } } foreach($panels as $panel_id=>$panel){ $panel_ids[$panel_id] = $panel_id; } return $panel_ids; } /** * Loads the meta data of the original, new, and custom file into the variables originalData, newData, and customData respectively * * @param STRING $module - name of the module's files that are to be merged * @param STRING $original_file - path to the file that originally shipped with sugar * @param STRING $new_file - path to the new file that is shipping with the patch * @param STRING $custom_file - path to the custom file */ protected function loadData($module, $original_file, $new_file, $custom_file){ $this->module = $module; $varnmame = $this->varName; require($original_file); $this->originalData = $$varnmame; require($new_file); $this->newData = $$varnmame; if(file_exists($custom_file)){ require($custom_file); $this->customData = $$varnmame; }else{ $this->customData = $this->originalData; } } /** * This will save the merged data to a file * * @param STRING $to - path of the file to save it to * @return BOOLEAN - success or failure of the save */ public function save($to){ return write_array_to_file("viewdefs['$this->module']['$this->viewDefs']", $this->newData[$this->module][$this->viewDefs], $to); } /** * This will return the meta data of the merged file * * @return ARRAY - the meta data of the merged file */ public function getData(){ return $this->newData; } /** * public function that will merge meta data from an original sugar file that shipped with the product, a customized file, and a new file shipped with an upgrade * * @param STRING $module - name of the module's files that are to be merged * @param STRING $original_file - path to the file that originally shipped with sugar * @param STRING $new_file - path to the new file that is shipping with the patch * @param STRING $custom_file - path to the custom file * @param BOOLEAN $save - boolean on if it should save the results to the custom file or not * @return BOOLEAN - if the merged file was saved if false is passed in for the save parameter it always returns true */ public function merge($module, $original_file, $new_file, $custom_file=false, $save=true){ $this->clear(); $this->log("\n\n". 'Starting a merge in ' . get_class($this)); $this->log('merging the following files'); $this->log('original file:' . $original_file); $this->log('new file:' . $new_file); $this->log('custom file:' . $custom_file); if(empty($custom_file) && $save){ return true; }else{ $this->loadData($module, $original_file, $new_file, $custom_file); $this->mergeMetaData(); if($save && !empty($this->newData) && !empty($custom_file)){ //backup the file copy($custom_file, $custom_file . '.suback.php'); return $this->save($custom_file); } } if(!$save)return true; return false; } /** * Logs the given message if the message is not a string it will export it first. If $this->fp is NULL then it will try to log to the $GLOBALS['log'] if it is available * * @param MULTI $message */ protected function log($message){ if(!is_string($message)){ $message = var_export($message, true); } if(!empty($this->fp)){ fwrite($this->fp, $message. "\n"); }else{ if(!empty($GLOBALS['log'])){ $GLOBALS['log']->debug($message . "\n"); } } } } ?>