2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
4 /*********************************************************************************
5 * SugarCRM Community Edition is a customer relationship management program developed by
6 * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
8 * This program is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU Affero General Public License version 3 as published by the
10 * Free Software Foundation with the addition of the following permission added
11 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
12 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
13 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
15 * This program is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
20 * You should have received a copy of the GNU Affero General Public License along with
21 * this program; if not, see http://www.gnu.org/licenses or write to the Free
22 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
26 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
28 * The interactive user interfaces in modified source and object code versions
29 * of this program must display Appropriate Legal Notices, as required under
30 * Section 5 of the GNU Affero General Public License version 3.
32 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
33 * these Appropriate Legal Notices must retain the display of the "Powered by
34 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
35 * technical reasons, the Appropriate Legal Notices must display the words
36 * "Powered by SugarCRM".
37 ********************************************************************************/
39 require_once('include/connectors/sources/SourceFactory.php');
40 require_once('include/connectors/ConnectorFactory.php');
41 require_once('include/MVC/Controller/SugarController.php');
43 class ConnectorsController extends SugarController {
45 var $admin_actions = array('ConnectorSettings', 'DisplayProperties', 'MappingProperties', 'ModifyMapping', 'ModifyDisplay', 'ModifyProperties',
46 'ModifySearch', 'SearchProperties', 'SourceProperties',
47 'SavedModifyDisplay', 'SaveModifyProperties', 'SaveModifySearch');
51 if(!is_admin($GLOBALS['current_user']) && in_array($this->action, $this->admin_actions)) {
52 $this->hasAccess = false;
59 * When the user clicks the Search button, the form is posted back here and this action sets the
60 * search parameters in the session. Once this call returns, the tabs will then call RetrieveSource to load
61 * the data that was saved in the session.
64 function action_SetSearch(){
65 if(empty($_REQUEST)) {
70 require_once('include/connectors/utils/ConnectorUtils.php');
71 $searchdefs = ConnectorUtils::getSearchDefs();
72 $merge_module = $_REQUEST['merge_module'];
73 $record_id = $_REQUEST['record'];
74 $searchDefs = isset($searchdefs) ? $searchdefs : array();
75 unset($_SESSION['searchDefs'][$merge_module][$record_id]);
78 $search_source = $_REQUEST['source_id'];
79 $source_instance = ConnectorFactory::getInstance($search_source);
80 $source_map = $source_instance->getModuleMapping($merge_module);
81 $module_fields = array();
82 foreach($_REQUEST as $search_term => $val){
83 if(!empty($source_map[$search_term])){
84 $module_fields[$source_map[$search_term]] = $val;
88 foreach($module_fields as $search_term => $val){
89 foreach($searchDefs as $source => $modules){
90 if(empty($sMap[$source])){
91 $instance = ConnectorFactory::getInstance($source);
92 $sMap[$source] = array_flip($instance->getModuleMapping($merge_module));
95 if(!empty($sMap[$source][$search_term])){
96 $source_key = $sMap[$source][$search_term];
97 $_SESSION['searchDefs'][$merge_module][$record_id][$source][$source_key] = $val;
104 * This action it meant to handle the hover action on the listview.
107 function action_RetrieveSourceDetails() {
108 $this->view = 'ajax';
109 $source_id = $_REQUEST['source_id'];
110 $record_id = $_REQUEST['record_id'];
112 if(empty($source_id) || empty($record_id)) {
116 $source = ConnectorFactory::getInstance($source_id);
117 $module = $_SESSION['merge_module'];
119 $result = $source->fillBean(array('id' => $record_id), $module);
120 require_once('include/connectors/utils/ConnectorUtils.php');
121 $connector_strings = ConnectorUtils::getConnectorStrings($source_id);
123 $fields = $source->getModuleMapping($module);
124 $fieldDefs = $source->getFieldDefs();
127 foreach($fields as $key=>$field){
130 if(isset($fieldDefs[$key])) {
131 $label = isset($connector_strings[$fieldDefs[$key]['vname']]) ? $connector_strings[$fieldDefs[$key]['vname']] : $label;
134 $val = $result->$field;
136 if(strlen($val) > 50) {
137 $val = substr($val, 0, 47) . '...';
139 $str .= $label . ': ' . $val.'<br/>';
143 $json = getJSONobj();
146 $retArray['body'] = !empty($str) ? str_replace(array("\rn", "\r", "\n"), array('','','<br />'), $str) : $GLOBALS['mod_strings']['ERROR_NO_ADDITIONAL_DETAIL'];
147 $retArray['caption'] = "<div style='float:left'>{$GLOBALS['app_strings']['LBL_ADDITIONAL_DETAILS']}</div>";
148 $retArray['width'] = (empty($results['width']) ? '300' : $results['width']);
149 $retArray['theme'] = $theme;
150 echo 'result = ' . $json->encode($retArray);
154 function action_GetSearchForm(){
155 $this->view = 'ajax';
156 if(!empty($_REQUEST['source_id'])){
157 //get the search fields and return the search form
159 $ss = new Sugar_Smarty();
160 require_once('include/connectors/utils/ConnectorUtils.php');
161 $searchdefs = ConnectorUtils::getSearchDefs();
162 $merge_module = $_REQUEST['merge_module'];
163 $seed = loadBean($merge_module);
164 $_searchDefs = isset($searchdefs) ? $searchdefs : array();
165 $_trueFields = array();
166 $source = $_REQUEST['source_id'];
168 $searchLabels = ConnectorUtils::getConnectorStrings($source);
169 $record = $_REQUEST['record'];
170 $sourceObj = SourceFactory::getSource($source);
171 $field_defs = $sourceObj->getFieldDefs();
173 if(!empty($_searchDefs[$source][$merge_module])) {
174 foreach($_searchDefs[$source][$merge_module] as $key) {
175 if(!empty($_SESSION['searchDefs'][$merge_module][$record][$source][$key])){
176 $_trueFields[$key]['value'] = $_SESSION['searchDefs'][$merge_module][$record][$source][$key];
178 $_trueFields[$key]['value'] = '';
180 if(!empty($field_defs[$key]) && isset($searchLabels[$field_defs[$key]['vname']])){
181 $_trueFields[$key]['label'] = $searchLabels[$field_defs[$key]['vname']];
183 $_trueFields[$key]['label'] = $key;
188 $ss->assign('mod', $GLOBALS['mod_strings']);
189 $ss->assign('search_fields', $_trueFields);
190 $ss->assign('source_id', $source);
191 $ss->assign('fields', $seed->field_defs);
192 $ss->assign('module', $merge_module);
193 $ss->assign('RECORD', $record);
194 $ss->assign('APP', $GLOBALS['app_strings']);
195 $ss->assign('MOD', $GLOBALS['mod_strings']);
196 echo $ss->fetch('modules/Connectors/tpls/search_form.tpl');
201 function pre_save(){}
202 function post_save(){}
205 function action_CallConnectorFunc() {
206 $this->view = 'ajax';
207 $json = getJSONobj();
209 if(!empty($_REQUEST['source_id']))
211 $source_id = $_REQUEST['source_id'];
212 require_once('include/connectors/sources/SourceFactory.php');
213 $source = SourceFactory::getSource($source_id);
215 $method = 'ext_'.$_REQUEST['source_func'];
216 if ( method_exists($source,$method) ) {
217 echo $json->encode($source->$method($_REQUEST));
219 echo $json->encode(array('error'=>true,'errorMessage'=>'Could Not Find Function: '.$method.' in class: '.get_class($source)));
224 echo $json->encode(array('error'=>true,'errorMessage'=>'Source Id is not specified.'));
228 function action_CallRest() {
229 $this->view = 'ajax';
231 if(false === ($result=@file_get_contents($_REQUEST['url']))) {
233 } else if(!empty($_REQUEST['xml'])){
235 $p = xml_parser_create();
236 xml_parse_into_struct($p, $result, $values);
238 $json = getJSONobj();
239 echo $json->encode($values);
245 function action_CallSoap() {
246 $this->view = 'ajax';
247 $source_id = $_REQUEST['source_id'];
248 $module = $_REQUEST['module_id'];
249 $return_params = explode(',', $_REQUEST['fields']);
250 require_once('include/connectors/ConnectorFactory.php');
251 $component = ConnectorFactory::getInstance($source_id);
252 $beans = $component->fillBeans($_REQUEST, $module);
253 if(!empty($beans) && !empty($return_params)) {
256 foreach($beans as $bean) {
257 foreach($return_params as $field) {
258 $results[$count][$field] = $bean->$field;
262 $json = getJSONobj();
263 echo $json->encode($results);
270 function action_DefaultSoapPopup() {
271 $this->view = 'ajax';
272 $source_id = $_REQUEST['source_id'];
273 $module = $_REQUEST['module_id'];
274 $id = $_REQUEST['record_id'];
275 $mapping = $_REQUEST['mapping'];
277 $mapping = explode(',', $mapping);
281 $bean = loadBean($module);
282 $bean->retrieve($id);
284 require_once('include/connectors/ConnectorFactory.php');
285 $component = ConnectorFactory::getInstance($source_id);
288 $field_defs = $bean->getFieldDefinitions();
289 foreach($field_defs as $id=>$field) {
290 if(!empty($bean->$id)) {
291 $args[$id] = $bean->$id;
295 $beans = $component->fillBeans($args, $module);
296 if(!empty($beans) && !empty($mapping)) {
299 foreach($beans as $bean) {
300 foreach($mapping as $field) {
301 $results[$count][$field] = $bean->$field;
305 $json = getJSONobj();
306 echo $json->encode($results);
308 $GLOBALS['log']->error($GLOBALS['app_strings']['ERR_MISSING_MAPPING_ENTRY_FORM_MODULE']);
313 function action_SaveModifyProperties() {
314 require_once('include/connectors/sources/SourceFactory.php');
316 $properties = array();
317 foreach($_REQUEST as $name=>$value) {
318 if(preg_match("/^source[0-9]+$/", $name, $matches)) {
320 $properties = array();
321 foreach($_REQUEST as $arg=>$val) {
322 if(preg_match("/^{$source_id}_(.*?)$/", $arg, $matches2)) {
323 $properties[$matches2[1]] = $val;
326 $source = SourceFactory::getSource($source_id);
327 if(!empty($properties)) {
328 $source->setProperties($properties);
329 $source->saveConfig();
334 require_once('include/connectors/utils/ConnectorUtils.php');
335 ConnectorUtils::updateMetaDataFiles();
337 if(empty($_REQUEST['from_unit_test'])) {
339 header("Location: index.php?action=ConnectorSettings&module=Connectors");
345 function action_SaveModifyDisplay() {
346 if(empty($_REQUEST['display_sources'])) {
350 require_once('include/connectors/utils/ConnectorUtils.php');
351 require_once('include/connectors/sources/SourceFactory.php');
353 $connectors = ConnectorUtils::getConnectors();
354 $connector_keys = array_keys($connectors);
356 $modules_sources = ConnectorUtils::getDisplayConfig();
357 if ( !is_array($modules_sources) ) {
358 $modules_sources = (array) $modules_sources;
363 $new_modules_sources = array();
365 if(!empty($_REQUEST['display_values'])) {
366 $display_values = explode(',', $_REQUEST['display_values']);
367 foreach($display_values as $value) {
368 $entry = explode(':', $value);
369 $mod = get_module_from_singular($entry[1]); // get the internal module name
370 $new_modules_sources[$mod][$entry[0]] = $entry[0];
374 //These are the sources that were modified.
375 //We only update entries for these sources that have been changed
376 $display_sources = explode(',', $_REQUEST['display_sources']);
377 foreach($display_sources as $source) {
378 $sources[$source] = $source;
381 $removedModules = array();
383 //Unset entries that have all sources removed
384 foreach($modules_sources as $module=>$source_entries) {
385 foreach($source_entries as $source_id) {
386 if(!empty($sources[$source_id]) && empty($new_modules_sources[$module][$source_id])) {
387 unset($modules_sources[$module][$source_id]);
388 $removedModules[$module] = true;
392 $removedModules = array_keys($removedModules);
393 foreach($removedModules as $key){
394 if(empty($new_modules_sources[$key])){
395 ConnectorUtils::cleanMetaDataFile($key);
399 //Update based on new_modules_sources
400 foreach($new_modules_sources as $module=>$enabled_sources) {
401 //If the module is not in $modules_sources add it there
402 if(empty($modules_sources[$module])) {
403 $modules_sources[$module] = $enabled_sources;
405 foreach($enabled_sources as $source_id) {
406 if(empty($modules_sources[$module][$source_id])) {
407 $modules_sources[$module][$source_id] = $source_id;
413 //Should we just remove entries where all sources are disabled?
414 $unset_modules = array();
415 foreach($modules_sources as $module=>$mapping) {
416 if(empty($mapping)) {
417 $unset_modules[] = $module;
421 foreach($unset_modules as $mod) {
422 unset($modules_sources[$mod]);
425 if(!write_array_to_file('modules_sources', $modules_sources, CONNECTOR_DISPLAY_CONFIG_FILE)) {
426 //Log error and return empty array
427 $GLOBALS['log']->fatal("Cannot write \$modules_sources to " . CONNECTOR_DISPLAY_CONFIG_FILE);
430 $sources_modules = array();
431 foreach($modules_sources as $module=>$source_entries) {
432 foreach($source_entries as $id) {
433 $sources_modules[$id][$module] = $module;
438 //Now update the searchdefs and field mapping entries accordingly
439 require('modules/Connectors/metadata/searchdefs.php');
440 $originalSearchDefs = $searchdefs;
441 $connectorSearchDefs = ConnectorUtils::getSearchDefs();
443 $searchdefs = array();
444 foreach($sources_modules as $source_id=>$modules) {
445 foreach($modules as $module) {
446 $searchdefs[$source_id][$module] = !empty($connectorSearchDefs[$source_id][$module]) ? $connectorSearchDefs[$source_id][$module] : (!empty($originalSearchDefs[$source_id][$module]) ? $originalSearchDefs[$source_id][$module] : array());
450 //Write the new searchdefs out
451 if(!write_array_to_file('searchdefs', $searchdefs, 'custom/modules/Connectors/metadata/searchdefs.php')) {
452 $GLOBALS['log']->fatal("Cannot write file custom/modules/Connectors/metadata/searchdefs.php");
455 //Unset the $_SESSION['searchDefs'] variable
456 if (isset($_SESSION['searchDefs'])) {
457 unset($_SESSION['searchDefs']);
462 //Clear mapping file if needed (this happens when all modules are removed from a source
463 foreach($sources as $id) {
464 if(empty($sources_modules[$source])) {
465 //Now write the new mapping entry to the custom folder
466 $dir = $connectors[$id]['directory'];
467 if(!preg_match('/^custom\//', $dir)) {
468 $dir = 'custom/' . $dir;
471 if(!file_exists("{$dir}")) {
472 mkdir_recursive("{$dir}");
475 $fakeMapping = array('beans'=>array());
476 if(!write_array_to_file('mapping', $fakeMapping, "{$dir}/mapping.php")) {
477 $GLOBALS['log']->fatal("Cannot write file {$dir}/mapping.php");
479 $s = SourceFactory::getSource($id);
480 $s->saveMappingHook($fakeMapping);
484 //Now update the field mapping entries
485 foreach($sources_modules as $id=>$modules) {
486 $source = SourceFactory::getSource($id);
487 $mapping = $source->getMapping();
488 $mapped_modules = array_keys($mapping['beans']);
490 foreach($mapped_modules as $module) {
491 if(empty($sources_modules[$id][$module])) {
492 unset($mapping['beans'][$module]);
496 //Remove modules from the mapping entries
497 foreach($modules as $module) {
498 if(empty($mapping['beans'][$module])) {
499 $originalMapping = $source->getOriginalMapping();
500 if(empty($originalMapping['beans'][$module])) {
501 $defs = $source->getFieldDefs();
502 $keys = array_keys($defs);
503 $new_mapping_entry = array();
504 foreach($keys as $key) {
505 $new_mapping_entry[$key] = '';
507 $mapping['beans'][$module] = $new_mapping_entry;
509 $mapping['beans'][$module] = $originalMapping['beans'][$module];
515 //Now write the new mapping entry to the custom folder
516 $dir = $connectors[$id]['directory'];
517 if(!preg_match('/^custom\//', $dir)) {
518 $dir = 'custom/' . $dir;
521 if(!file_exists("{$dir}")) {
522 mkdir_recursive("{$dir}");
525 if(!write_array_to_file('mapping', $mapping, "{$dir}/mapping.php")) {
526 $GLOBALS['log']->fatal("Cannot write file {$dir}/mapping.php");
528 $source->saveMappingHook($mapping);
533 foreach($connectors as $connector_name => $data) {
534 if(isset($sources[$connector_name]) && !empty($data["eapm"])) {
535 // if we touched it AND it has EAPM data
536 $connectors[$connector_name]["eapm"]["enabled"] = !empty($_REQUEST[$connector_name."_external"]);
539 ConnectorUtils::saveConnectors($connectors);
541 ConnectorUtils::updateMetaDataFiles();
543 if(empty($_REQUEST['from_unit_test'])) {
545 header("Location: index.php?action=ConnectorSettings&module=Connectors");
555 * action_SaveModifyMapping
557 function action_SaveModifyMapping() {
558 $mapping_sources = !empty($_REQUEST['mapping_sources']) ? explode(',', $_REQUEST['mapping_sources']) : array();
559 $mapping_values = !empty($_REQUEST['mapping_values']) ? explode(',', $_REQUEST['mapping_values']) : array();
561 //Build the source->module->fields mapping
562 $source_modules_fields = array();
563 foreach($mapping_values as $id) {
564 $parts = explode(':', $id);
565 $key_vals = explode('=', $parts[2]);
566 //Note the strtolwer call... we are lowercasing the key values
567 $source_modules_fields[$parts[0]][$parts[1]][strtolower($key_vals[0])] = $key_vals[1];
570 foreach($mapping_sources as $source_id) {
571 if(empty($source_modules_fields[$source_id])) {
572 $source = SourceFactory::getSource($source_id);
573 $mapping = $source->getMapping();
574 foreach($mapping['beans'] as $module=>$entry) {
575 $source_modules_fields[$source_id][$module] = array();
583 require_once('include/connectors/utils/ConnectorUtils.php');
584 $source_entries = ConnectorUtils::getConnectors();
586 require_once('include/connectors/sources/SourceFactory.php');
587 foreach($source_modules_fields as $id=>$mapping_entry) {
588 //Insert the id mapping
589 foreach($mapping_entry as $module=>$entry) {
590 $mapping_entry[$module]['id'] = 'id';
593 $source = SourceFactory::getSource($id);
594 $mapping = $source->getMapping();
595 $mapping['beans'] = $mapping_entry;
597 //Now write the new mapping entry to the custom folder
598 $dir = $source_entries[$id]['directory'];
599 if(!preg_match('/^custom\//', $dir)) {
600 $dir = 'custom/' . $dir;
603 if(!file_exists("{$dir}")) {
604 mkdir_recursive("{$dir}");
607 if(!write_array_to_file('mapping', $mapping, "{$dir}/mapping.php")) {
608 $GLOBALS['log']->fatal("Cannot write file {$dir}/mapping.php");
610 $source->saveMappingHook($mapping);
613 //Rewrite the metadata files
614 ConnectorUtils::updateMetaDataFiles();
617 if(empty($_REQUEST['from_unit_test'])) {
619 header("Location: index.php?action=ConnectorSettings&module=Connectors");
626 function action_RunTest() {
627 $this->view = 'ajax';
628 $source_id = $_REQUEST['source_id'];
629 $source = SourceFactory::getSource($source_id);
630 $properties = array();
631 foreach($_REQUEST as $name=>$value) {
632 if(preg_match("/^{$source_id}_(.*?)$/", $name, $matches)) {
633 $properties[$matches[1]] = $value;
636 $source->setProperties($properties);
637 $source->saveConfig();
639 //Call again and call init
640 $source = SourceFactory::getSource($source_id);
646 if($source->isRequiredConfigFieldsForButtonSet() && $source->test()) {
647 echo $mod_strings['LBL_TEST_SOURCE_SUCCESS'];
649 echo $mod_strings['LBL_TEST_SOURCE_FAILED'];
651 } catch (Exception $ex) {
652 $GLOBALS['log']->fatal($ex->getMessage());
653 echo $ex->getMessage();
659 * action_RetrieveSources
660 * Returns a JSON encoded format of the Connectors that are configured for the system
663 function action_RetrieveSources() {
664 require_once('include/connectors/utils/ConnectorUtils.php');
665 $this->view = 'ajax';
666 $sources = ConnectorUtils:: getConnectors();
668 foreach($sources as $id=>$entry) {
669 $results[$id] = !empty($entry['name']) ? $entry['name'] : $id;
671 $json = getJSONobj();
672 echo $json->encode($results);