0
0
mirror of https://github.com/salesagility/SuiteCRM.git synced 2024-11-24 16:46:48 +00:00
salesagility_SuiteCRM/modules/UpgradeWizard/SugarMerge/EditViewMerge.php

813 lines
30 KiB
PHP
Executable File

<?php
if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point');
}
/**
*
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
*
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
* Copyright (C) 2011 - 2018 SalesAgility Ltd.
*
* 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 and "Supercharged by SuiteCRM" logo. If the display of the logos is not
* reasonably feasible for technical reasons, the Appropriate Legal Notices must
* display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM".
*/
/*********************************************************************************
* 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
*
*/
#[\AllowDynamicProperties]
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'),
);
/**
* @var SugarMerge
*/
public $sugarMerge = null;
/**
* 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, 'ab');
}
/**
*
*/
/**
* 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
*/
public 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');
$metaParser = new MetaParser();
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'=> (string)($panel_id), 'row'=> (string)($row_id), 'col'=> (string)($col_id)));
}
}
}
}
//echo "---------------------------------------------------------\n";
//echo var_export($fields, true);
return $fields;
}
/**
* getPanelIds
*
*/
protected function getPanelIds($panels)
{
$panel_ids = array();
$setDefaultPanel = false;
if ((is_countable($panels) ? count($panels) : 0) == 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');
$metaParser = new MetaParser();
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");
}
}
}
}