<?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".
 */


require_once 'modules/ModuleBuilder/parsers/relationships/AbstractRelationships.php' ;
require_once 'modules/ModuleBuilder/parsers/relationships/RelationshipsInterface.php' ;
require_once 'modules/ModuleBuilder/parsers/relationships/RelationshipFactory.php' ;


class DeployedRelationships extends AbstractRelationships implements RelationshipsInterface
{
    public function __construct($moduleName)
    {
        $this->moduleName = $moduleName ;
        $this->load() ;
    }

    public static function findRelatableModules($includeActivitiesSubmodules = true)
    {
        return parent::findRelatableModules(true) ;
    }

    /*
     * Load the set of relationships for this module - the set is the combination of that held in the working file plus all of the relevant deployed relationships for the module
     * Note that deployed relationships are readonly and cannot be modified - getDeployedRelationships() takes care of marking them as such
     * Assumes that only called for modules which exist in $beansList - otherwise get_module_info will break
     * This means that load() cannot be called for Activities, only Tasks, Notes, etc
     *
     * Note that we may need to adjust the cardinality for any custom relationships that we do not have entries for in the working directory
     * These relationships might have been loaded from an installation package by ModuleInstaller, or the custom/working directory might have been cleared at some point
     * The cardinality in the installed relationship is not necessarily correct for custom relationships, which currently are all built as many-to-many relationships
     * Instead we must obtain the true cardinality from a property we added to the relationship metadata when we created the relationship
     * This relationship metadata is accessed through the Table Dictionary
     */
    public function load()
    {
        $relationships = $this->getDeployedRelationships() ;
        
        if (! empty($relationships)) {
            // load the relationship definitions for all installed custom relationships into $dictionary
            $dictionary = array( ) ;
            if (file_exists('custom/application/Ext/TableDictionary/tabledictionary.ext.php')) {
                include('custom/application/Ext/TableDictionary/tabledictionary.ext.php') ;
            }
            
            $invalidModules = array();
            $validModules = array_keys(self::findRelatableModules()) ;
            
            // now convert the relationships array into an array of AbstractRelationship objects
            foreach ($relationships as $name => $definition) {
                if (($definition [ 'lhs_module' ] == $this->moduleName) || ($definition [ 'rhs_module' ] == $this->moduleName)) {
                    if (in_array($definition [ 'lhs_module' ], $validModules) && in_array($definition [ 'rhs_module' ], $validModules)
                        && ! in_array($definition [ 'lhs_module' ], $invalidModules) && ! in_array($definition [ 'rhs_module' ], $invalidModules)) {
                        // identify the subpanels for this relationship - TODO: optimize this - currently does m x n scans through the subpanel list...
                        $definition [ 'rhs_subpanel' ] = self::identifySubpanel($definition [ 'lhs_module' ], $definition [ 'rhs_module' ]) ;
                        $definition [ 'lhs_subpanel' ] = self::identifySubpanel($definition [ 'rhs_module' ], $definition [ 'lhs_module' ]) ;
                        
                        // now adjust the cardinality with the true cardinality found in the relationships metadata (see method comment above)
                        

                        if (! empty($dictionary) && ! empty($dictionary [ $name ])) {
                            if (! empty($dictionary [ $name ] [ 'true_relationship_type' ])) {
                                $definition [ 'relationship_type' ] = $dictionary [ $name ] [ 'true_relationship_type' ] ;
                            }
                            if (! empty($dictionary [ $name ] [ 'from_studio' ])) {
                                $definition [ 'from_studio' ] = $dictionary [ $name ] [ 'from_studio' ] ;
                            }
                            $definition [ 'is_custom' ] = true;
                        }
                            
                        
                        $this->relationships [ $name ] = RelationshipFactory::newRelationship($definition) ;
                    }
                }
            }
        }
        
        /*        // Now override with any definitions from the working directory
            // must do this to capture one-to-ones that we have created as these don't show up in the relationship table that is the source for getDeployedRelationships()
            $overrides = parent::_load ( "custom/working/modules/{$this->moduleName}" ) ;
            foreach ( $overrides as $name => $relationship )
            {
                $this->relationships [ $name ] = $relationship ;
            }*/
    }

    /*
     * Save this modules relationship definitions out to a working file
     */
    public function save()
    {
        parent::_save($this->relationships, "custom/working/modules/{$this->moduleName}") ;
    }

    /*
     * Update pre-5.1 relationships to the 5.1 relationship definition
     * There is nothing to do for Deployed relationships as these were only introduced in 5.1
     * @param array definition  The 5.0 relationship definition
     * @return array            The definition updated to 5.1 format
     */
    protected function _updateRelationshipDefinition($definition)
    {
        return $definition ;
    }

    /*
     * Use the module Loader to delete the relationship from the instance.
     */
    public function delete($rel_name)
    {
        //Remove any fields from layouts
        $rel = $this->get($rel_name);
        if (!empty($rel)) {
            $this->removeFieldsFromDeployedLayout($rel);
        }
        require_once("ModuleInstall/ModuleInstaller.php");
        require_once('modules/Administration/QuickRepairAndRebuild.php') ;
        $mi = new ModuleInstaller();
        $mi->silent = true;
        $mi->id_name = 'custom' . $rel_name; // provide the moduleinstaller with a unique name for this relationship - normally this value is set to the package key...
        $mi->uninstall_relationship("custom/metadata/{$rel_name}MetaData.php");
        $mi->uninstallLabels('custom/Extension/modules/relationships/language/', $rel->buildLabels());
        $mi->uninstallExtLabels($rel->buildLabels());
        
        // now clear all caches so that our changes are visible
        Relationship::delete_cache();
        $mi->rebuild_tabledictionary();
        
        $MBmodStrings = $GLOBALS [ 'mod_strings' ];
        $GLOBALS [ 'mod_strings' ] = return_module_language('', 'Administration') ;
        $rac = new RepairAndClear() ;
        $rac->repairAndClearAll(array( 'clearAll', 'rebuildExtensions',  ), array( $GLOBALS [ 'mod_strings' ] [ 'LBL_ALL_MODULES' ] ), true, false) ;
        $GLOBALS [ 'mod_strings' ] = $MBmodStrings;

        //Bug 41070, supercedes the previous 40941 fix in this section
        if (isset($this->relationships[$rel_name])) {
            unset($this->relationships[$rel_name]);
        }
    }

    /*
     * Return the set of all known relevant relationships for a deployed module
     * The set is made up of the relationships held in this class, plus all those already deployed in the application
     * @return array Set of all relevant relationships
     */
    protected function getAllRelationships()
    {
        return array_merge($this->relationships, parent::getDeployedRelationships()) ;
    }

    /*
     * Return the name of the first (currently only) subpanel displayed in the DetailView of $thisModuleName provided by $sourceModuleName
     * We can assume that both sides of the relationship are deployed modules as this is only called within the context of DeployedRelationships
     * @param string $thisModuleName    Name of the related module
     * @param string $sourceModuleName  Name of the primary module
     * @return string Name of the subpanel if found; null otherwise
     */
    private static function identifySubpanel($thisModuleName, $sourceModuleName)
    {
        $module = get_module_info($thisModuleName) ;
        require_once('include/SubPanel/SubPanelDefinitions.php') ;
        $spd = new SubPanelDefinitions($module) ;
        $subpanelNames = $spd->get_available_tabs() ; // actually these are the displayed subpanels
        
        foreach ($subpanelNames as $key => $name) {
            $GLOBALS [ 'log' ]->debug($thisModuleName . " " . $name) ;
            
            $subPanel = $spd->load_subpanel($name) ;
            if ($subPanel && ! isset($subPanel->_instance_properties [ 'collection_list' ])) {
                if ($sourceModuleName == $subPanel->_instance_properties [ 'module' ]) {
                    return $subPanel->_instance_properties [ 'subpanel_name' ] ;
                }
            }
        }
        
        return null ;
    }

    /*
     * Return the name of the first (currently only) relate field of $thisModuleName sourced from by $sourceModuleName
     * We can assume that both sides of the relationship are deployed modules as this is only called within the context of DeployedRelationships
     * @param string $thisModuleName    Name of the related module
     * @param string $sourceModuleName  Name of the primary module
     * @return string Name of the relate field, if found; null otherwise
     */
    
    private static function identifyRelateField($thisModuleName, $sourceModuleName)
    {
        $module = get_module_info($thisModuleName) ;
        
        foreach ($module->field_defs as $field) {
            if ($field [ 'type' ] == 'relate' && isset($field [ 'module' ]) && $field [ 'module' ] == $sourceModuleName) {
                return $field [ 'name' ] ;
            }
        }
        return null ;
    }

    /*
     * As of SugarCRM 5.1 the subpanel code and the widgets have difficulty handling multiple subpanels or relate fields from the same module
     * Until this is fixed, we new relationships which will trigger this problem must be flagged as "relationship_only" and built without a UI component
     * This function is called from the view when constructing a new relationship
     * We can assume that both sides of the relationship are deployed modules as this is only called within the context of DeployedRelationships
     * @param AbstractRelationship $relationship The relationship to be enforced
     */
    public function enforceRelationshipOnly($relationship)
    {
        $lhs = $relationship->lhs_module ;
        $rhs = $relationship->rhs_module ;
        // if the lhs_module already has a subpanel or relate field sourced from the rhs_module,
    // or the rhs_module already has a subpanel or relate field sourced from the lhs_module,
    // then set "relationship_only" in the relationship
    

    //        if (($relationship->getType() != MB_ONETOONE && ! is_null ( self::identifySubpanel ( $lhs, $rhs ) )) || ($relationship->getType() == MB_MANYTOMANY && ! is_null ( self::identifySubpanel ( $rhs, $lhs ) )) || ($relationship->getType() == MB_ONETOONE && ! is_null ( self::identifyRelateField ( $rhs, $lhs ) )) || ($relationship->getType() != MB_MANYTOMANY && ! is_null ( self::identifyRelateField ( $lhs, $rhs ) )))
    //            $relationship->setRelationship_only () ;
    }

    /*
     * BUILD FUNCTIONS
     */
    
    /*
     * Implement all of the Relationships in this set of relationships
     * This is more general than it needs to be given that deployed relationships are built immediately - there should only be one relationship to build here...
     * We use the Extension mechanism to do this for DeployedRelationships
     * All metadata is placed in the modules Ext directory, and then Rebuild is called to activate them
     */
    public function build($basepath = null, $installDefPrefix = null, $relationships = null)
    {
        $basepath = "custom/Extension/modules" ;
        
        $this->activitiesToAdd = false ;
        
        // and mark all as built so that the next time we come through we'll know and won't build again
        foreach ($this->relationships as $name => $relationship) {
            $definition = $relationship->getDefinition() ;
            // activities will always appear on the rhs only - lhs will be always be this module in MB
            if (strtolower($definition [ 'rhs_module' ]) == 'activities') {
                $this->activitiesToAdd = true ;
                $relationshipName = $definition [ 'relationship_name' ] ;
                foreach (self::$activities as $activitiesSubModuleLower => $activitiesSubModuleName) {
                    $definition [ 'rhs_module' ] = $activitiesSubModuleName ;
                    $definition [ 'for_activities' ] = true ;
                    $definition [ 'relationship_name' ] = $relationshipName . '_' . $activitiesSubModuleLower ;
                    $this->relationships [ $definition [ 'relationship_name' ] ] = RelationshipFactory::newRelationship($definition) ;
                }
                unset($this->relationships [ $name ]) ;
            }
        }
        
        $GLOBALS [ 'log' ]->info(get_class($this) . "->build(): installing relationships") ;

        $MBModStrings = $GLOBALS [ 'mod_strings' ] ;
        $adminModStrings = return_module_language('', 'Administration') ; // required by ModuleInstaller
            
        foreach ($this->relationships as $name => $relationship) {
            $relationship->setFromStudio();
            $GLOBALS [ 'mod_strings' ] = $MBModStrings ;
            $installDefs = parent::build($basepath, "<basepath>", array($name => $relationship )) ;

            // and mark as built so that the next time we come through we'll know and won't build again
            $relationship->setReadonly() ;
            $this->relationships [ $name ] = $relationship ;

            // now install the relationship - ModuleInstaller normally only does this as part of a package load where it installs the relationships defined in the manifest. However, we don't have a manifest or a package, so...
            
            // If we were to chose to just use the Extension mechanism, without using the ModuleInstaller install_...() methods, we must :
            // 1)   place the information for each side of the relationship in the appropriate Ext directory for the module, which means specific $this->save...() methods for DeployedRelationships, and
            // 2)   we must also manually add the relationship into the custom/application/Ext/TableDictionary/tabledictionary.ext.php file as install_relationship doesn't handle that (install_relationships which requires the manifest however does)
            //      Relationships must be in tabledictionary.ext.php for the Admin command Rebuild Relationships to reliably work:
            //      Rebuild Relationships looks for relationships in the modules vardefs.php, in custom/modules/<modulename>/Ext/vardefs/vardefs.ext.php, and in modules/TableDictionary.php and custom/application/Ext/TableDictionary/tabledictionary.ext.php
            //      if the relationship is not defined in one of those four places it could be deleted during a rebuilt, or during a module installation (when RebuildRelationships.php deletes all entries in the Relationships table)
            // So instead of doing this, we use common save...() methods between DeployedRelationships and UndeployedRelationships that will produce installDefs,
            // and rather than building a full manifest file to carry them, we manually add these installDefs to the ModuleInstaller, and then
            // individually call the appropriate ModuleInstaller->install_...() methods to take our relationship out of our staging area and expand it out to the individual module Ext areas

            $GLOBALS [ 'mod_strings' ] = $adminModStrings ;
            require_once 'ModuleInstall/ModuleInstaller.php' ;
            $mi = new ModuleInstaller() ;

            $mi->id_name = 'custom' . $name ; // provide the moduleinstaller with a unique name for this relationship - normally this value is set to the package key...
            $mi->installdefs = $installDefs ;
            $mi->base_dir = $basepath ;
            $mi->silent = true ;

            
            VardefManager::clearVardef() ;

            $mi->install_relationships() ;
            $mi->install_languages() ;
            $mi->install_vardefs() ;
            $mi->install_layoutdefs() ;
            $mi->install_extensions();
        }
        
        // Run through the module installer to rebuild the relationships once after everything is done.
        require_once 'ModuleInstall/ModuleInstaller.php' ;
        $mi = new ModuleInstaller() ;
        $mi->silent = true;
        $mi->rebuild_relationships();

        // now clear all caches so that our changes are visible
        require_once('modules/Administration/QuickRepairAndRebuild.php') ;
        $rac = new RepairAndClear() ;
        $rac->repairAndClearAll(array( 'clearAll' ), array( $GLOBALS [ 'mod_strings' ] [ 'LBL_ALL_MODULES' ] ), true, false) ;

        $GLOBALS [ 'mod_strings' ] = $MBModStrings ; // finally, restore the ModuleBuilder mod_strings

        // save out the updated definitions so that we keep track of the change in built status
        $this->save() ;
        
        $GLOBALS [ 'log' ]->info(get_class($this) . "->build(): finished relationship installation") ;
    }

    /*
     * Add any fields to the DetailView and EditView of the appropriate modules
     * @param string $basepath              Basepath location for this module (not used)
     * @param string $relationshipName      Name of this relationship (for uniqueness)
     * @param array $layoutAdditions  An array of module => fieldname
     * return null
     */
    protected function saveFieldsToLayouts($basepath, $dummy, $relationshipName, $layoutAdditions)
    {
        require_once 'modules/ModuleBuilder/parsers/views/GridLayoutMetaDataParser.php' ;
        
        // these modules either lack editviews/detailviews or use custom mechanisms for the editview/detailview. In either case, we don't want to attempt to add a relate field to them
        // would be better if GridLayoutMetaDataParser could handle this gracefully, so we don't have to maintain this list here
        $invalidModules = array( 'emails' , 'kbdocuments' ) ;
        
        foreach ($layoutAdditions as $deployedModuleName => $fieldName) {
            if (! in_array(strtolower($deployedModuleName), $invalidModules)) {
                foreach (array( MB_EDITVIEW , MB_DETAILVIEW ) as $view) {
                    $GLOBALS [ 'log' ]->info(get_class($this) . ": adding $fieldName to $view layout for module $deployedModuleName") ;
                    $parser = new GridLayoutMetaDataParser($view, $deployedModuleName) ;
                    $parser->addField(array( 'name' => $fieldName )) ;
                    $parser->handleSave(false) ;
                }
            }
        }
    }
    
    /**
     * Added for bug #40941
     * Deletes the field from DetailView and editView of the appropriate module
     * after the relatioship is deleted in delete() function above.
     * @param $relationship    The relationship that is getting deleted
     * return null
     */
    private function removeFieldsFromDeployedLayout($relationship)
    {
        
        // many-to-many relationships don't have fields so if we have a many-to-many we can just skip this...
        if ($relationship->getType() == MB_MANYTOMANY) {
            return false ;
        }
        
        $successful = true ;
        $layoutAdditions = $relationship->buildFieldsToLayouts() ;
        
        require_once 'modules/ModuleBuilder/parsers/views/GridLayoutMetaDataParser.php' ;
        foreach ($layoutAdditions as $deployedModuleName => $fieldName) {
            foreach (array( MB_EDITVIEW , MB_DETAILVIEW ) as $view) {
                $parser = new GridLayoutMetaDataParser($view, $deployedModuleName) ;
                $parser->removeField($fieldName);
                $parser->handleSave(false) ;
            }
        }
        
        return $successful ;
    }
}
