Files
crm.twinpol.com/modules/UpgradeWizard/SugarMerge/EditViewMerge.php
2025-05-12 15:44:39 +00:00

751 lines
24 KiB
PHP
Executable File

<?php
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
/*********************************************************************************
* SugarCRM is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2010 SugarCRM Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo. If the display of the logo is not reasonably feasible for
* technical reasons, the Appropriate Legal Notices must display the words
* "Powered by SugarCRM".
********************************************************************************/
/*********************************************************************************
* Description: Defines the English language pack for the base application.
* Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
* All Rights Reserved.
* Contributor(s): ______________________________________..
********************************************************************************/
/**
* This is the base class that all other SugarMerge objects extend
*
*/
class EditViewMerge{
/**
* The variable name that is used with the file for example in editviewdefs and detailviewdefs it is $viewdefs
*
* @var STRING
*/
protected $varName = 'viewdefs';
/**
* Enter the name of the parameter used in the $varName for example in editviewdefs and detailviewdefs it is 'EditView' and 'DetailView' respectively - $viewdefs['EditView']
*
* @var STRING
*/
protected $viewDefs = 'EditView';
/**
* this will store the meta data for the original file
*
* @var ARRAY
*/
protected $originalData = array();
/**
* this will store the meta data for the new file
*
* @var ARRAY
*/
protected $newData = array();
/**
* this will store the meta data for the custom file
*
* @var ARRAY
*/
protected $customData = array();
/**
* this will store an associative array contianing all the fields that are used in the original meta data file
*
* @var ARRAY
*/
protected $originalFields = array();
/**
* this will store an associative array contianing all the fields that are used in the new meta data file
*
* @var ARRAY
*/
protected $newFields = array();
/**
* this will store an associative array contianing all the fields that are used in the custom meta data file
*
* @var ARRAY
*/
protected $customFields = array();
/**
* this will store an associative array contianing all the merged fields
*
* @var ARRAY
*/
protected $mergedFields = array();
/**
* the name of the module to be merged
*
* @var STRING
*/
protected $module = 'module';
/**
* the max number of columns for this view
*
* @var INT
*/
protected $maxCols = 2;
/**
* If we should use the best match algorithim
*
* @var BOOLEAN
*/
protected $bestMatch = true;
/**
* The default panel we place the fields in if we aren't using the best match algorithim
*
* @var STRING
*/
protected $defaultPanel = 'default';
/**
* The name of the panels section in the meta data
*
* @var STRING
*/
protected $panelName = 'panels';
/**
* The name of the templateMeta data secion in the meta data
*/
protected $templateMetaName = 'templateMeta';
/**
* The file pointer to log to if set to NULL it will use the GLOBALS['log'] if available and log to debug
*
* @var FILEPOINTER
*/
protected $fp = NULL;
/**
* Determines if getFields should analyze panels to determine if it is a MultiPanel
*
* @var unknown_type
*/
protected $scanForMultiPanel = true;
/**
* If true then it works as though it's a multipanel
*
* @var BOOLEAN
*/
protected $isMultiPanel = true;
/**
* The ids of the panels found in custom metadata fuke
*
*/
protected $customPanelIds = array();
/**
* The ids of the panels found in original metadata fuke
*
*/
protected $originalPanelIds = array();
/**
* The ids of the panels found in original metadata fuke
*
*/
protected $newPanelIds = array();
/**
* Special case conversion
*
*/
protected $fieldConversionMapping = array(
'Campaigns' => 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()
{
if( isset($this->customData[$this->module][$this->viewDefs][$this->templateMetaName]) )
$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)) {
$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");
}
}
}
}
?>