PageRenderTime 41ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 2ms

/SugarCRM/data/SugarBean.php

https://github.com/guolong/ggxw
PHP | 5551 lines | 3754 code | 516 blank | 1281 comment | 1001 complexity | 592c181708651b1b9faed97f9f41df68 MD5 | raw file
Possible License(s): AGPL-3.0, LGPL-2.1, MPL-2.0-no-copyleft-exception
  1. <?php
  2. if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
  3. /*********************************************************************************
  4. * SugarCRM Community Edition is a customer relationship management program developed by
  5. * SugarCRM, Inc. Copyright (C) 2004-2012 SugarCRM Inc.
  6. *
  7. * This program is free software; you can redistribute it and/or modify it under
  8. * the terms of the GNU Affero General Public License version 3 as published by the
  9. * Free Software Foundation with the addition of the following permission added
  10. * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
  11. * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
  12. * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
  13. *
  14. * This program is distributed in the hope that it will be useful, but WITHOUT
  15. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  16. * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  17. * details.
  18. *
  19. * You should have received a copy of the GNU Affero General Public License along with
  20. * this program; if not, see http://www.gnu.org/licenses or write to the Free
  21. * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  22. * 02110-1301 USA.
  23. *
  24. * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
  25. * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
  26. *
  27. * The interactive user interfaces in modified source and object code versions
  28. * of this program must display Appropriate Legal Notices, as required under
  29. * Section 5 of the GNU Affero General Public License version 3.
  30. *
  31. * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
  32. * these Appropriate Legal Notices must retain the display of the "Powered by
  33. * SugarCRM" logo. If the display of the logo is not reasonably feasible for
  34. * technical reasons, the Appropriate Legal Notices must display the words
  35. * "Powered by SugarCRM".
  36. ********************************************************************************/
  37. /*********************************************************************************
  38. * Description: Defines the base class for all data entities used throughout the
  39. * application. The base class including its methods and variables is designed to
  40. * be overloaded with module-specific methods and variables particular to the
  41. * module's base entity class.
  42. * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
  43. * All Rights Reserved.
  44. *******************************************************************************/
  45. require_once('modules/DynamicFields/DynamicField.php');
  46. require_once("data/Relationships/RelationshipFactory.php");
  47. /**
  48. * SugarBean is the base class for all business objects in Sugar. It implements
  49. * the primary functionality needed for manipulating business objects: create,
  50. * retrieve, update, delete. It allows for searching and retrieving list of records.
  51. * It allows for retrieving related objects (e.g. contacts related to a specific account).
  52. *
  53. * In the current implementation, there can only be one bean per folder.
  54. * Naming convention has the bean name be the same as the module and folder name.
  55. * All bean names should be singular (e.g. Contact). The primary table name for
  56. * a bean should be plural (e.g. contacts).
  57. * @api
  58. */
  59. class SugarBean
  60. {
  61. /**
  62. * A pointer to the database object
  63. *
  64. * @var DBManager
  65. */
  66. var $db;
  67. /**
  68. * When createing a bean, you can specify a value in the id column as
  69. * long as that value is unique. During save, if the system finds an
  70. * id, it assumes it is an update. Setting new_with_id to true will
  71. * make sure the system performs an insert instead of an update.
  72. *
  73. * @var BOOL -- default false
  74. */
  75. var $new_with_id = false;
  76. /**
  77. * How deep logic hooks can go
  78. * @var int
  79. */
  80. protected $max_logic_depth = 10;
  81. /**
  82. * Disble vardefs. This should be set to true only for beans that do not have varders. Tracker is an example
  83. *
  84. * @var BOOL -- default false
  85. */
  86. var $disable_vardefs = false;
  87. /**
  88. * holds the full name of the user that an item is assigned to. Only used if notifications
  89. * are turned on and going to be sent out.
  90. *
  91. * @var String
  92. */
  93. var $new_assigned_user_name;
  94. /**
  95. * An array of booleans. This array is cleared out when data is loaded.
  96. * As date/times are converted, a "1" is placed under the key, the field is converted.
  97. *
  98. * @var Array of booleans
  99. */
  100. var $processed_dates_times = array();
  101. /**
  102. * Whether to process date/time fields for storage in the database in GMT
  103. *
  104. * @var BOOL
  105. */
  106. var $process_save_dates =true;
  107. /**
  108. * This signals to the bean that it is being saved in a mass mode.
  109. * Examples of this kind of save are import and mass update.
  110. * We turn off notificaitons of this is the case to make things more efficient.
  111. *
  112. * @var BOOL
  113. */
  114. var $save_from_post = true;
  115. /**
  116. * When running a query on related items using the method: retrieve_by_string_fields
  117. * this value will be set to true if more than one item matches the search criteria.
  118. *
  119. * @var BOOL
  120. */
  121. var $duplicates_found = false;
  122. /**
  123. * true if this bean has been deleted, false otherwise.
  124. *
  125. * @var BOOL
  126. */
  127. var $deleted = 0;
  128. /**
  129. * Should the date modified column of the bean be updated during save?
  130. * This is used for admin level functionality that should not be updating
  131. * the date modified. This is only used by sync to allow for updates to be
  132. * replicated in a way that will not cause them to be replicated back.
  133. *
  134. * @var BOOL
  135. */
  136. var $update_date_modified = true;
  137. /**
  138. * Should the modified by column of the bean be updated during save?
  139. * This is used for admin level functionality that should not be updating
  140. * the modified by column. This is only used by sync to allow for updates to be
  141. * replicated in a way that will not cause them to be replicated back.
  142. *
  143. * @var BOOL
  144. */
  145. var $update_modified_by = true;
  146. /**
  147. * Setting this to true allows for updates to overwrite the date_entered
  148. *
  149. * @var BOOL
  150. */
  151. var $update_date_entered = false;
  152. /**
  153. * This allows for seed data to be created without using the current uesr to set the id.
  154. * This should be replaced by altering the current user before the call to save.
  155. *
  156. * @var unknown_type
  157. */
  158. //TODO This should be replaced by altering the current user before the call to save.
  159. var $set_created_by = true;
  160. var $team_set_id;
  161. /**
  162. * The database table where records of this Bean are stored.
  163. *
  164. * @var String
  165. */
  166. var $table_name = '';
  167. /**
  168. * This is the singular name of the bean. (i.e. Contact).
  169. *
  170. * @var String
  171. */
  172. var $object_name = '';
  173. /** Set this to true if you query contains a sub-select and bean is converting both select statements
  174. * into count queries.
  175. */
  176. var $ungreedy_count=false;
  177. /**
  178. * The name of the module folder for this type of bean.
  179. *
  180. * @var String
  181. */
  182. var $module_dir = '';
  183. var $module_name = '';
  184. var $field_name_map;
  185. var $field_defs;
  186. var $custom_fields;
  187. var $column_fields = array();
  188. var $list_fields = array();
  189. var $additional_column_fields = array();
  190. var $relationship_fields = array();
  191. var $current_notify_user;
  192. var $fetched_row=false;
  193. var $fetched_rel_row = array();
  194. var $layout_def;
  195. var $force_load_details = false;
  196. var $optimistic_lock = false;
  197. var $disable_custom_fields = false;
  198. var $number_formatting_done = false;
  199. var $process_field_encrypted=false;
  200. /*
  201. * The default ACL type
  202. */
  203. var $acltype = 'module';
  204. var $additional_meta_fields = array();
  205. /**
  206. * Set to true in the child beans if the module supports importing
  207. */
  208. var $importable = false;
  209. /**
  210. * Set to true in the child beans if the module use the special notification template
  211. */
  212. var $special_notification = false;
  213. /**
  214. * Set to true if the bean is being dealt with in a workflow
  215. */
  216. var $in_workflow = false;
  217. /**
  218. *
  219. * By default it will be true but if any module is to be kept non visible
  220. * to tracker, then its value needs to be overriden in that particular module to false.
  221. *
  222. */
  223. var $tracker_visibility = true;
  224. /**
  225. * Used to pass inner join string to ListView Data.
  226. */
  227. var $listview_inner_join = array();
  228. /**
  229. * Set to true in <modules>/Import/views/view.step4.php if a module is being imported
  230. */
  231. var $in_import = false;
  232. /**
  233. * A way to keep track of the loaded relationships so when we clone the object we can unset them.
  234. *
  235. * @var array
  236. */
  237. protected $loaded_relationships = array();
  238. /**
  239. * set to true if dependent fields updated
  240. */
  241. protected $is_updated_dependent_fields = false;
  242. /**
  243. * Constructor for the bean, it performs following tasks:
  244. *
  245. * 1. Initalized a database connections
  246. * 2. Load the vardefs for the module implemeting the class. cache the entries
  247. * if needed
  248. * 3. Setup row-level security preference
  249. * All implementing classes must call this constructor using the parent::SugarBean() class.
  250. *
  251. */
  252. function SugarBean()
  253. {
  254. global $dictionary, $current_user;
  255. static $loaded_defs = array();
  256. $this->db = DBManagerFactory::getInstance();
  257. if (empty($this->module_name))
  258. $this->module_name = $this->module_dir;
  259. if((false == $this->disable_vardefs && empty($loaded_defs[$this->object_name])) || !empty($GLOBALS['reload_vardefs']))
  260. {
  261. VardefManager::loadVardef($this->module_dir, $this->object_name);
  262. // build $this->column_fields from the field_defs if they exist
  263. if (!empty($dictionary[$this->object_name]['fields'])) {
  264. foreach ($dictionary[$this->object_name]['fields'] as $key=>$value_array) {
  265. $column_fields[] = $key;
  266. if(!empty($value_array['required']) && !empty($value_array['name'])) {
  267. $this->required_fields[$value_array['name']] = 1;
  268. }
  269. }
  270. $this->column_fields = $column_fields;
  271. }
  272. //setup custom fields
  273. if(!isset($this->custom_fields) &&
  274. empty($this->disable_custom_fields))
  275. {
  276. $this->setupCustomFields($this->module_dir);
  277. }
  278. //load up field_arrays from CacheHandler;
  279. if(empty($this->list_fields))
  280. $this->list_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'list_fields');
  281. if(empty($this->column_fields))
  282. $this->column_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'column_fields');
  283. if(empty($this->required_fields))
  284. $this->required_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'required_fields');
  285. if(isset($GLOBALS['dictionary'][$this->object_name]) && !$this->disable_vardefs)
  286. {
  287. $this->field_name_map = $dictionary[$this->object_name]['fields'];
  288. $this->field_defs = $dictionary[$this->object_name]['fields'];
  289. if(!empty($dictionary[$this->object_name]['optimistic_locking']))
  290. {
  291. $this->optimistic_lock=true;
  292. }
  293. }
  294. $loaded_defs[$this->object_name]['column_fields'] =& $this->column_fields;
  295. $loaded_defs[$this->object_name]['list_fields'] =& $this->list_fields;
  296. $loaded_defs[$this->object_name]['required_fields'] =& $this->required_fields;
  297. $loaded_defs[$this->object_name]['field_name_map'] =& $this->field_name_map;
  298. $loaded_defs[$this->object_name]['field_defs'] =& $this->field_defs;
  299. }
  300. else
  301. {
  302. $this->column_fields =& $loaded_defs[$this->object_name]['column_fields'] ;
  303. $this->list_fields =& $loaded_defs[$this->object_name]['list_fields'];
  304. $this->required_fields =& $loaded_defs[$this->object_name]['required_fields'];
  305. $this->field_name_map =& $loaded_defs[$this->object_name]['field_name_map'];
  306. $this->field_defs =& $loaded_defs[$this->object_name]['field_defs'];
  307. $this->added_custom_field_defs = true;
  308. if(!isset($this->custom_fields) &&
  309. empty($this->disable_custom_fields))
  310. {
  311. $this->setupCustomFields($this->module_dir, false);
  312. }
  313. if(!empty($dictionary[$this->object_name]['optimistic_locking']))
  314. {
  315. $this->optimistic_lock=true;
  316. }
  317. }
  318. if($this->bean_implements('ACL') && !empty($GLOBALS['current_user'])){
  319. $this->acl_fields = (isset($dictionary[$this->object_name]['acl_fields']) && $dictionary[$this->object_name]['acl_fields'] === false)?false:true;
  320. }
  321. $this->populateDefaultValues();
  322. }
  323. /**
  324. * Returns the object name. If object_name is not set, table_name is returned.
  325. *
  326. * All implementing classes must set a value for the object_name variable.
  327. *
  328. * @param array $arr row of data fetched from the database.
  329. * @return nothing
  330. *
  331. */
  332. function getObjectName()
  333. {
  334. if ($this->object_name)
  335. return $this->object_name;
  336. // This is a quick way out. The generated metadata files have the table name
  337. // as the key. The correct way to do this is to override this function
  338. // in bean and return the object name. That requires changing all the beans
  339. // as well as put the object name in the generator.
  340. return $this->table_name;
  341. }
  342. /**
  343. * Returns a list of fields with their definitions that have the audited property set to true.
  344. * Before calling this function, check whether audit has been enabled for the table/module or not.
  345. * You would set the audit flag in the implemting module's vardef file.
  346. *
  347. * @return an array of
  348. * @see is_AuditEnabled
  349. *
  350. * Internal function, do not override.
  351. */
  352. function getAuditEnabledFieldDefinitions()
  353. {
  354. $aclcheck = $this->bean_implements('ACL');
  355. $is_owner = $this->isOwner($GLOBALS['current_user']->id);
  356. if (!isset($this->audit_enabled_fields))
  357. {
  358. $this->audit_enabled_fields=array();
  359. foreach ($this->field_defs as $field => $properties)
  360. {
  361. if (
  362. (
  363. !empty($properties['Audited']) || !empty($properties['audited']))
  364. )
  365. {
  366. $this->audit_enabled_fields[$field]=$properties;
  367. }
  368. }
  369. }
  370. return $this->audit_enabled_fields;
  371. }
  372. /**
  373. * Return true if auditing is enabled for this object
  374. * You would set the audit flag in the implemting module's vardef file.
  375. *
  376. * @return boolean
  377. *
  378. * Internal function, do not override.
  379. */
  380. function is_AuditEnabled()
  381. {
  382. global $dictionary;
  383. if (isset($dictionary[$this->getObjectName()]['audited']))
  384. {
  385. return $dictionary[$this->getObjectName()]['audited'];
  386. }
  387. else
  388. {
  389. return false;
  390. }
  391. }
  392. /**
  393. * Returns the name of the audit table.
  394. * Audit table's name is based on implementing class' table name.
  395. *
  396. * @return String Audit table name.
  397. *
  398. * Internal function, do not override.
  399. */
  400. function get_audit_table_name()
  401. {
  402. return $this->getTableName().'_audit';
  403. }
  404. /**
  405. * Returns the name of the custom table.
  406. * Custom table's name is based on implementing class' table name.
  407. *
  408. * @return String Custom table name.
  409. *
  410. * Internal function, do not override.
  411. */
  412. public function get_custom_table_name()
  413. {
  414. return $this->getTableName().'_cstm';
  415. }
  416. /**
  417. * If auditing is enabled, create the audit table.
  418. *
  419. * Function is used by the install scripts and a repair utility in the admin panel.
  420. *
  421. * Internal function, do not override.
  422. */
  423. function create_audit_table()
  424. {
  425. global $dictionary;
  426. $table_name=$this->get_audit_table_name();
  427. require('metadata/audit_templateMetaData.php');
  428. // Bug: 52583 Need ability to customize template for audit tables
  429. $custom = 'custom/metadata/audit_templateMetaData_' . $this->getTableName() . '.php';
  430. if (file_exists($custom))
  431. {
  432. require($custom);
  433. }
  434. $fieldDefs = $dictionary['audit']['fields'];
  435. $indices = $dictionary['audit']['indices'];
  436. // Renaming template indexes to fit the particular audit table (removed the brittle hard coding)
  437. foreach($indices as $nr => $properties){
  438. $indices[$nr]['name'] = 'idx_' . strtolower($this->getTableName()) . '_' . $properties['name'];
  439. }
  440. $engine = null;
  441. if(isset($dictionary['audit']['engine'])) {
  442. $engine = $dictionary['audit']['engine'];
  443. } else if(isset($dictionary[$this->getObjectName()]['engine'])) {
  444. $engine = $dictionary[$this->getObjectName()]['engine'];
  445. }
  446. $this->db->createTableParams($table_name, $fieldDefs, $indices, $engine);
  447. }
  448. /**
  449. * Returns the implementing class' table name.
  450. *
  451. * All implementing classes set a value for the table_name variable. This value is returned as the
  452. * table name. If not set, table name is extracted from the implementing module's vardef.
  453. *
  454. * @return String Table name.
  455. *
  456. * Internal function, do not override.
  457. */
  458. public function getTableName()
  459. {
  460. if(isset($this->table_name))
  461. {
  462. return $this->table_name;
  463. }
  464. global $dictionary;
  465. return $dictionary[$this->getObjectName()]['table'];
  466. }
  467. /**
  468. * Returns field definitions for the implementing module.
  469. *
  470. * The definitions were loaded in the constructor.
  471. *
  472. * @return Array Field definitions.
  473. *
  474. * Internal function, do not override.
  475. */
  476. function getFieldDefinitions()
  477. {
  478. return $this->field_defs;
  479. }
  480. /**
  481. * Returns index definitions for the implementing module.
  482. *
  483. * The definitions were loaded in the constructor.
  484. *
  485. * @return Array Index definitions.
  486. *
  487. * Internal function, do not override.
  488. */
  489. function getIndices()
  490. {
  491. global $dictionary;
  492. if(isset($dictionary[$this->getObjectName()]['indices']))
  493. {
  494. return $dictionary[$this->getObjectName()]['indices'];
  495. }
  496. return array();
  497. }
  498. /**
  499. * Returns field definition for the requested field name.
  500. *
  501. * The definitions were loaded in the constructor.
  502. *
  503. * @param string field name,
  504. * @return Array Field properties or boolean false if the field doesn't exist
  505. *
  506. * Internal function, do not override.
  507. */
  508. function getFieldDefinition($name)
  509. {
  510. if ( !isset($this->field_defs[$name]) )
  511. return false;
  512. return $this->field_defs[$name];
  513. }
  514. /**
  515. * Returnss definition for the id field name.
  516. *
  517. * The definitions were loaded in the constructor.
  518. *
  519. * @return Array Field properties.
  520. *
  521. * Internal function, do not override.
  522. */
  523. function getPrimaryFieldDefinition()
  524. {
  525. $def = $this->getFieldDefinition("id");
  526. if(empty($def)) {
  527. $def = $this->getFieldDefinition(0);
  528. }
  529. if (empty($def)) {
  530. $defs = $this->field_defs;
  531. reset($defs);
  532. $def = current($defs);
  533. }
  534. return $def;
  535. }
  536. /**
  537. * Returns the value for the requested field.
  538. *
  539. * When a row of data is fetched using the bean, all fields are created as variables in the context
  540. * of the bean and then fetched values are set in these variables.
  541. *
  542. * @param string field name,
  543. * @return varies Field value.
  544. *
  545. * Internal function, do not override.
  546. */
  547. function getFieldValue($name)
  548. {
  549. if (!isset($this->$name)){
  550. return FALSE;
  551. }
  552. if($this->$name === TRUE){
  553. return 1;
  554. }
  555. if($this->$name === FALSE){
  556. return 0;
  557. }
  558. return $this->$name;
  559. }
  560. /**
  561. * Basically undoes the effects of SugarBean::populateDefaultValues(); this method is best called right after object
  562. * initialization.
  563. */
  564. public function unPopulateDefaultValues()
  565. {
  566. if ( !is_array($this->field_defs) )
  567. return;
  568. foreach ($this->field_defs as $field => $value) {
  569. if( !empty($this->$field)
  570. && ((isset($value['default']) && $this->$field == $value['default']) || (!empty($value['display_default']) && $this->$field == $value['display_default']))
  571. ) {
  572. $this->$field = null;
  573. continue;
  574. }
  575. if(!empty($this->$field) && !empty($value['display_default']) && in_array($value['type'], array('date', 'datetime', 'datetimecombo')) &&
  576. $this->$field == $this->parseDateDefault($value['display_default'], ($value['type'] != 'date'))) {
  577. $this->$field = null;
  578. }
  579. }
  580. }
  581. /**
  582. * Create date string from default value
  583. * like '+1 month'
  584. * @param string $value
  585. * @param bool $time Should be expect time set too?
  586. * @return string
  587. */
  588. protected function parseDateDefault($value, $time = false)
  589. {
  590. global $timedate;
  591. if($time) {
  592. $dtAry = explode('&', $value, 2);
  593. $dateValue = $timedate->getNow(true)->modify($dtAry[0]);
  594. if(!empty($dtAry[1])) {
  595. $timeValue = $timedate->fromString($dtAry[1]);
  596. $dateValue->setTime($timeValue->hour, $timeValue->min, $timeValue->sec);
  597. }
  598. return $timedate->asUser($dateValue);
  599. } else {
  600. return $timedate->asUserDate($timedate->getNow(true)->modify($value));
  601. }
  602. }
  603. function populateDefaultValues($force=false){
  604. if ( !is_array($this->field_defs) )
  605. return;
  606. foreach($this->field_defs as $field=>$value){
  607. if((isset($value['default']) || !empty($value['display_default'])) && ($force || empty($this->$field))){
  608. $type = $value['type'];
  609. switch($type){
  610. case 'date':
  611. if(!empty($value['display_default'])){
  612. $this->$field = $this->parseDateDefault($value['display_default']);
  613. }
  614. break;
  615. case 'datetime':
  616. case 'datetimecombo':
  617. if(!empty($value['display_default'])){
  618. $this->$field = $this->parseDateDefault($value['display_default'], true);
  619. }
  620. break;
  621. case 'multienum':
  622. if(empty($value['default']) && !empty($value['display_default']))
  623. $this->$field = $value['display_default'];
  624. else
  625. $this->$field = $value['default'];
  626. break;
  627. case 'bool':
  628. if(isset($this->$field)){
  629. break;
  630. }
  631. default:
  632. if ( isset($value['default']) && $value['default'] !== '' ) {
  633. $this->$field = htmlentities($value['default'], ENT_QUOTES, 'UTF-8');
  634. } else {
  635. $this->$field = '';
  636. }
  637. } //switch
  638. }
  639. } //foreach
  640. }
  641. /**
  642. * Removes relationship metadata cache.
  643. *
  644. * Every module that has relationships defined with other modules, has this meta data cached. The cache is
  645. * stores in 2 locations: relationships table and file system. This method clears the cache from both locations.
  646. *
  647. * @param string $key module whose meta cache is to be cleared.
  648. * @param string $db database handle.
  649. * @param string $tablename table name
  650. * @param string $dictionary vardef for the module
  651. * @param string $module_dir name of subdirectory where module is installed.
  652. *
  653. * @return Nothing
  654. * @static
  655. *
  656. * Internal function, do not override.
  657. */
  658. function removeRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir)
  659. {
  660. //load the module dictionary if not supplied.
  661. if ((!isset($dictionary) or empty($dictionary)) && !empty($module_dir))
  662. {
  663. $filename='modules/'. $module_dir . '/vardefs.php';
  664. if(file_exists($filename))
  665. {
  666. include($filename);
  667. }
  668. }
  669. if (!is_array($dictionary) or !array_key_exists($key, $dictionary))
  670. {
  671. $GLOBALS['log']->fatal("removeRelationshipMeta: Metadata for table ".$tablename. " does not exist");
  672. display_notice("meta data absent for table ".$tablename." keyed to $key ");
  673. }
  674. else
  675. {
  676. if (isset($dictionary[$key]['relationships']))
  677. {
  678. $RelationshipDefs = $dictionary[$key]['relationships'];
  679. foreach ($RelationshipDefs as $rel_name)
  680. {
  681. Relationship::delete($rel_name,$db);
  682. }
  683. }
  684. }
  685. }
  686. /**
  687. * This method has been deprecated.
  688. *
  689. * @see removeRelationshipMeta()
  690. * @deprecated 4.5.1 - Nov 14, 2006
  691. * @static
  692. */
  693. function remove_relationship_meta($key,$db,$log,$tablename,$dictionary,$module_dir)
  694. {
  695. SugarBean::removeRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir);
  696. }
  697. /**
  698. * Populates the relationship meta for a module.
  699. *
  700. * It is called during setup/install. It is used statically to create relationship meta data for many-to-many tables.
  701. *
  702. * @param string $key name of the object.
  703. * @param object $db database handle.
  704. * @param string $tablename table, meta data is being populated for.
  705. * @param array dictionary vardef dictionary for the object. *
  706. * @param string module_dir name of subdirectory where module is installed.
  707. * @param boolean $iscustom Optional,set to true if module is installed in a custom directory. Default value is false.
  708. * @static
  709. *
  710. * Internal function, do not override.
  711. */
  712. function createRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir,$iscustom=false)
  713. {
  714. //load the module dictionary if not supplied.
  715. if (empty($dictionary) && !empty($module_dir))
  716. {
  717. if($iscustom)
  718. {
  719. $filename='custom/modules/' . $module_dir . '/Ext/Vardefs/vardefs.ext.php';
  720. }
  721. else
  722. {
  723. if ($key == 'User')
  724. {
  725. // a very special case for the Employees module
  726. // this must be done because the Employees/vardefs.php does an include_once on
  727. // Users/vardefs.php
  728. $filename='modules/Users/vardefs.php';
  729. }
  730. else
  731. {
  732. $filename='modules/'. $module_dir . '/vardefs.php';
  733. }
  734. }
  735. if(file_exists($filename))
  736. {
  737. include($filename);
  738. // cn: bug 7679 - dictionary entries defined as $GLOBALS['name'] not found
  739. if(empty($dictionary) || !empty($GLOBALS['dictionary'][$key]))
  740. {
  741. $dictionary = $GLOBALS['dictionary'];
  742. }
  743. }
  744. else
  745. {
  746. $GLOBALS['log']->debug("createRelationshipMeta: no metadata file found" . $filename);
  747. return;
  748. }
  749. }
  750. if (!is_array($dictionary) or !array_key_exists($key, $dictionary))
  751. {
  752. $GLOBALS['log']->fatal("createRelationshipMeta: Metadata for table ".$tablename. " does not exist");
  753. display_notice("meta data absent for table ".$tablename." keyed to $key ");
  754. }
  755. else
  756. {
  757. if (isset($dictionary[$key]['relationships']))
  758. {
  759. $RelationshipDefs = $dictionary[$key]['relationships'];
  760. $delimiter=',';
  761. global $beanList;
  762. $beanList_ucase=array_change_key_case ( $beanList ,CASE_UPPER);
  763. foreach ($RelationshipDefs as $rel_name=>$rel_def)
  764. {
  765. if (isset($rel_def['lhs_module']) and !isset($beanList_ucase[strtoupper($rel_def['lhs_module'])])) {
  766. $GLOBALS['log']->debug('skipping orphaned relationship record ' . $rel_name . ' lhs module is missing ' . $rel_def['lhs_module']);
  767. continue;
  768. }
  769. if (isset($rel_def['rhs_module']) and !isset($beanList_ucase[strtoupper($rel_def['rhs_module'])])) {
  770. $GLOBALS['log']->debug('skipping orphaned relationship record ' . $rel_name . ' rhs module is missing ' . $rel_def['rhs_module']);
  771. continue;
  772. }
  773. //check whether relationship exists or not first.
  774. if (Relationship::exists($rel_name,$db))
  775. {
  776. $GLOBALS['log']->debug('Skipping, reltionship already exists '.$rel_name);
  777. }
  778. else
  779. {
  780. $seed = BeanFactory::getBean("Relationships");
  781. $keys = array_keys($seed->field_defs);
  782. $toInsert = array();
  783. foreach($keys as $key)
  784. {
  785. if ($key == "id")
  786. {
  787. $toInsert[$key] = create_guid();
  788. }
  789. else if ($key == "relationship_name")
  790. {
  791. $toInsert[$key] = $rel_name;
  792. }
  793. else if (isset($rel_def[$key]))
  794. {
  795. $toInsert[$key] = $rel_def[$key];
  796. }
  797. //todo specify defaults if meta not defined.
  798. }
  799. $column_list = implode(",", array_keys($toInsert));
  800. $value_list = "'" . implode("','", array_values($toInsert)) . "'";
  801. //create the record. todo add error check.
  802. $insert_string = "INSERT into relationships (" .$column_list. ") values (".$value_list.")";
  803. $db->query($insert_string, true);
  804. }
  805. }
  806. }
  807. else
  808. {
  809. //todo
  810. //log informational message stating no relationships meta was set for this bean.
  811. }
  812. }
  813. }
  814. /**
  815. * This method has been deprecated.
  816. * @see createRelationshipMeta()
  817. * @deprecated 4.5.1 - Nov 14, 2006
  818. * @static
  819. */
  820. function create_relationship_meta($key,&$db,&$log,$tablename,$dictionary,$module_dir)
  821. {
  822. SugarBean::createRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir);
  823. }
  824. /**
  825. * Handle the following when a SugarBean object is cloned
  826. *
  827. * Currently all this does it unset any relationships that were created prior to cloning the object
  828. *
  829. * @api
  830. */
  831. public function __clone()
  832. {
  833. if(!empty($this->loaded_relationships)) {
  834. foreach($this->loaded_relationships as $rel) {
  835. unset($this->$rel);
  836. }
  837. }
  838. }
  839. /**
  840. * Loads the request relationship. This method should be called before performing any operations on the related data.
  841. *
  842. * This method searches the vardef array for the requested attribute's definition. If the attribute is of the type
  843. * link then it creates a similary named variable and loads the relationship definition.
  844. *
  845. * @param string $rel_name relationship/attribute name.
  846. * @return nothing.
  847. */
  848. function load_relationship($rel_name)
  849. {
  850. $GLOBALS['log']->debug("SugarBean[{$this->object_name}].load_relationships, Loading relationship (".$rel_name.").");
  851. if (empty($rel_name))
  852. {
  853. $GLOBALS['log']->error("SugarBean.load_relationships, Null relationship name passed.");
  854. return false;
  855. }
  856. $fieldDefs = $this->getFieldDefinitions();
  857. //find all definitions of type link.
  858. if (!empty($fieldDefs[$rel_name]))
  859. {
  860. //initialize a variable of type Link
  861. require_once('data/Link2.php');
  862. $class = load_link_class($fieldDefs[$rel_name]);
  863. if (isset($this->$rel_name) && $this->$rel_name instanceof $class) {
  864. return true;
  865. }
  866. //if rel_name is provided, search the fieldef array keys by name.
  867. if (isset($fieldDefs[$rel_name]['type']) && $fieldDefs[$rel_name]['type'] == 'link')
  868. {
  869. if ($class == "Link2")
  870. $this->$rel_name = new $class($rel_name, $this);
  871. else
  872. $this->$rel_name = new $class($fieldDefs[$rel_name]['relationship'], $this, $fieldDefs[$rel_name]);
  873. if (empty($this->$rel_name) ||
  874. (method_exists($this->$rel_name, "loadedSuccesfully") && !$this->$rel_name->loadedSuccesfully()))
  875. {
  876. unset($this->$rel_name);
  877. return false;
  878. }
  879. // keep track of the loaded relationships
  880. $this->loaded_relationships[] = $rel_name;
  881. return true;
  882. }
  883. }
  884. $GLOBALS['log']->debug("SugarBean.load_relationships, Error Loading relationship (".$rel_name.")");
  885. return false;
  886. }
  887. /**
  888. * Loads all attributes of type link.
  889. *
  890. * DO NOT CALL THIS FUNCTION IF YOU CAN AVOID IT. Please use load_relationship directly instead.
  891. *
  892. * Method searches the implmenting module's vardef file for attributes of type link, and for each attribute
  893. * create a similary named variable and load the relationship definition.
  894. *
  895. * @return Nothing
  896. *
  897. * Internal function, do not override.
  898. */
  899. function load_relationships()
  900. {
  901. $GLOBALS['log']->debug("SugarBean.load_relationships, Loading all relationships of type link.");
  902. $linked_fields=$this->get_linked_fields();
  903. foreach($linked_fields as $name=>$properties)
  904. {
  905. $this->load_relationship($name);
  906. }
  907. }
  908. /**
  909. * Returns an array of beans of related data.
  910. *
  911. * For instance, if an account is related to 10 contacts , this function will return an array of contacts beans (10)
  912. * with each bean representing a contact record.
  913. * Method will load the relationship if not done so already.
  914. *
  915. * @param string $field_name relationship to be loaded.
  916. * @param string $bean name class name of the related bean.
  917. * @param array $sort_array optional, unused
  918. * @param int $begin_index Optional, default 0, unused.
  919. * @param int $end_index Optional, default -1
  920. * @param int $deleted Optional, Default 0, 0 adds deleted=0 filter, 1 adds deleted=1 filter.
  921. * @param string $optional_where, Optional, default empty.
  922. *
  923. * Internal function, do not override.
  924. */
  925. function get_linked_beans($field_name,$bean_name, $sort_array = array(), $begin_index = 0, $end_index = -1,
  926. $deleted=0, $optional_where="")
  927. {
  928. //if bean_name is Case then use aCase
  929. if($bean_name=="Case")
  930. $bean_name = "aCase";
  931. if($this->load_relationship($field_name)) {
  932. if ($this->$field_name instanceof Link) {
  933. // some classes are still based on Link, e.g. TeamSetLink
  934. return array_values($this->$field_name->getBeans(new $bean_name(), $sort_array, $begin_index, $end_index, $deleted, $optional_where));
  935. } else {
  936. // Link2 style
  937. if ($end_index != -1 || !empty($deleted) || !empty($optional_where))
  938. return array_values($this->$field_name->getBeans(array(
  939. 'where' => $optional_where,
  940. 'deleted' => $deleted,
  941. 'limit' => ($end_index - $begin_index)
  942. )));
  943. else
  944. return array_values($this->$field_name->getBeans());
  945. }
  946. }
  947. else
  948. return array();
  949. }
  950. /**
  951. * Returns an array of fields that are of type link.
  952. *
  953. * @return array List of fields.
  954. *
  955. * Internal function, do not override.
  956. */
  957. function get_linked_fields()
  958. {
  959. $linked_fields=array();
  960. // require_once('data/Link.php');
  961. $fieldDefs = $this->getFieldDefinitions();
  962. //find all definitions of type link.
  963. if (!empty($fieldDefs))
  964. {
  965. foreach ($fieldDefs as $name=>$properties)
  966. {
  967. if (array_search('link',$properties) === 'type')
  968. {
  969. $linked_fields[$name]=$properties;
  970. }
  971. }
  972. }
  973. return $linked_fields;
  974. }
  975. /**
  976. * Returns an array of fields that are able to be Imported into
  977. * i.e. 'importable' not set to 'false'
  978. *
  979. * @return array List of fields.
  980. *
  981. * Internal function, do not override.
  982. */
  983. function get_importable_fields()
  984. {
  985. $importableFields = array();
  986. $fieldDefs= $this->getFieldDefinitions();
  987. if (!empty($fieldDefs)) {
  988. foreach ($fieldDefs as $key=>$value_array) {
  989. if ( (isset($value_array['importable'])
  990. && (is_string($value_array['importable']) && $value_array['importable'] == 'false'
  991. || is_bool($value_array['importable']) && $value_array['importable'] == false))
  992. || (isset($value_array['type']) && $value_array['type'] == 'link')
  993. || (isset($value_array['auto_increment'])
  994. && ($value_array['type'] == true || $value_array['type'] == 'true')) ) {
  995. // only allow import if we force it
  996. if (isset($value_array['importable'])
  997. && (is_string($value_array['importable']) && $value_array['importable'] == 'true'
  998. || is_bool($value_array['importable']) && $value_array['importable'] == true)) {
  999. $importableFields[$key]=$value_array;
  1000. }
  1001. }
  1002. else {
  1003. $importableFields[$key]=$value_array;
  1004. }
  1005. }
  1006. }
  1007. return $importableFields;
  1008. }
  1009. /**
  1010. * Returns an array of fields that are of type relate.
  1011. *
  1012. * @return array List of fields.
  1013. *
  1014. * Internal function, do not override.
  1015. */
  1016. function get_related_fields()
  1017. {
  1018. $related_fields=array();
  1019. // require_once('data/Link.php');
  1020. $fieldDefs = $this->getFieldDefinitions();
  1021. //find all definitions of type link.
  1022. if (!empty($fieldDefs))
  1023. {
  1024. foreach ($fieldDefs as $name=>$properties)
  1025. {
  1026. if (array_search('relate',$properties) === 'type')
  1027. {
  1028. $related_fields[$name]=$properties;
  1029. }
  1030. }
  1031. }
  1032. return $related_fields;
  1033. }
  1034. /**
  1035. * Returns an array of fields that are required for import
  1036. *
  1037. * @return array
  1038. */
  1039. function get_import_required_fields()
  1040. {
  1041. $importable_fields = $this->get_importable_fields();
  1042. $required_fields = array();
  1043. foreach ( $importable_fields as $name => $properties ) {
  1044. if ( isset($properties['importable']) && is_string($properties['importable']) && $properties['importable'] == 'required' ) {
  1045. $required_fields[$name] = $properties;
  1046. }
  1047. }
  1048. return $required_fields;
  1049. }
  1050. /**
  1051. * Iterates through all the relationships and deletes all records for reach relationship.
  1052. *
  1053. * @param string $id Primary key value of the parent reocrd
  1054. */
  1055. function delete_linked($id)
  1056. {
  1057. $linked_fields=$this->get_linked_fields();
  1058. foreach ($linked_fields as $name => $value)
  1059. {
  1060. if ($this->load_relationship($name))
  1061. {
  1062. $this->$name->delete($id);
  1063. }
  1064. else
  1065. {
  1066. $GLOBALS['log']->fatal("error loading relationship $name");
  1067. }
  1068. }
  1069. }
  1070. /**
  1071. * Creates tables for the module implementing the class.
  1072. * If you override this function make sure that your code can handles table creation.
  1073. *
  1074. */
  1075. function create_tables()
  1076. {
  1077. global $dictionary;
  1078. $key = $this->getObjectName();
  1079. if (!array_key_exists($key, $dictionary))
  1080. {
  1081. $GLOBALS['log']->fatal("create_tables: Metadata for table ".$this->table_name. " does not exist");
  1082. display_notice("meta data absent for table ".$this->table_name." keyed to $key ");
  1083. }
  1084. else
  1085. {
  1086. if(!$this->db->tableExists($this->table_name))
  1087. {
  1088. $this->db->createTable($this);
  1089. if($this->bean_implements('ACL')){
  1090. if(!empty($this->acltype)){
  1091. ACLAction::addActions($this->getACLCategory(), $this->acltype);
  1092. }else{
  1093. ACLAction::addActions($this->getACLCategory());
  1094. }
  1095. }
  1096. }
  1097. else
  1098. {
  1099. echo "Table already exists : $this->table_name<br>";
  1100. }
  1101. if($this->is_AuditEnabled()){
  1102. if (!$this->db->tableExists($this->get_audit_table_name())) {
  1103. $this->create_audit_table();
  1104. }
  1105. }
  1106. }
  1107. }
  1108. /**
  1109. * Delete the primary table for the module implementing the class.
  1110. * If custom fields were added to this table/module, the custom table will be removed too, along with the cache
  1111. * entries that define the custom fields.
  1112. *
  1113. */
  1114. function drop_tables()
  1115. {
  1116. global $dictionary;
  1117. $key = $this->getObjectName();
  1118. if (!array_key_exists($key, $dictionary))
  1119. {
  1120. $GLOBALS['log']->fatal("drop_tables: Metadata for table ".$this->table_name. " does not exist");
  1121. echo "meta data absent for table ".$this->table_name."<br>\n";
  1122. } else {
  1123. if(empty($this->table_name))return;
  1124. if ($this->db->tableExists($this->table_name))
  1125. $this->db->dropTable($this);
  1126. if ($this->db->tableExists($this->table_name. '_cstm'))
  1127. {
  1128. $this->db->dropTableName($this->table_name. '_cstm');
  1129. DynamicField::deleteCache();
  1130. }
  1131. if ($this->db->tableExists($this->get_audit_table_name())) {
  1132. $this->db->dropTableName($this->get_audit_table_name());
  1133. }
  1134. }
  1135. }
  1136. /**
  1137. * Loads the definition of custom fields defined for the module.
  1138. * Local file system cache is created as needed.
  1139. *
  1140. * @param string $module_name setting up custom fields for this module.
  1141. * @param boolean $clean_load Optional, default true, rebuilds the cache if set to true.
  1142. */
  1143. function setupCustomFields($module_name, $clean_load=true)
  1144. {
  1145. $this->custom_fields = new DynamicField($module_name);
  1146. $this->custom_fields->setup($this);
  1147. }
  1148. /**
  1149. * Cleans char, varchar, text, etc. fields of XSS type materials
  1150. */
  1151. function cleanBean() {
  1152. foreach($this->field_defs as $key => $def) {
  1153. if (isset($def['type'])) {
  1154. $type=$def['type'];
  1155. }
  1156. if(isset($def['dbType']))
  1157. $type .= $def['dbType'];
  1158. if($def['type'] == 'html') {
  1159. $this->$key = SugarCleaner::cleanHtml($this->$key, true);
  1160. } elseif((strpos($type, 'char') !== false ||
  1161. strpos($type, 'text') !== false ||
  1162. $type == 'enum') &&
  1163. !empty($this->$key)
  1164. ) {
  1165. $this->$key = SugarCleaner::cleanHtml($this->$key);
  1166. }
  1167. }
  1168. }
  1169. /**
  1170. * Implements a generic insert and update logic for any SugarBean
  1171. * This method only works for subclasses that implement the same variable names.
  1172. * This method uses the presence of an id field that is not null to signify and update.
  1173. * The id field should not be set otherwise.
  1174. *
  1175. * @param boolean $check_notify Optional, default false, if set to true assignee of the record is notified via email.
  1176. * @todo Add support for field type validation and encoding of parameters.
  1177. */
  1178. function save($check_notify = FALSE)
  1179. {
  1180. $this->in_save = true;
  1181. // cn: SECURITY - strip XSS potential vectors
  1182. $this->cleanBean();
  1183. // This is used so custom/3rd-party code can be upgraded with fewer issues, this will be removed in a future release
  1184. $this->fixUpFormatting();
  1185. global $timedate;
  1186. global $current_user, $action;
  1187. $isUpdate = true;
  1188. if(empty($this->id))
  1189. {
  1190. $isUpdate = false;
  1191. }
  1192. if ( $this->new_with_id == true )
  1193. {
  1194. $isUpdate = false;
  1195. }
  1196. if(empty($this->date_modified) || $this->update_date_modified)
  1197. {
  1198. $this->date_modified = $GLOBALS['timedate']->nowDb();
  1199. }
  1200. $this->_checkOptimisticLocking($action, $isUpdate);
  1201. if(!empty($this->modified_by_name)) $this->old_modified_by_name = $this->modified_by_name;
  1202. if($this->update_modified_by)
  1203. {
  1204. $this->modified_user_id = 1;
  1205. if (!empty($current_user))
  1206. {
  1207. $this->modified_user_id = $current_user->id;
  1208. $this->modified_by_name = $current_user->user_name;
  1209. }
  1210. }
  1211. if ($this->deleted != 1)
  1212. $this->deleted = 0;
  1213. if(!$isUpdate)
  1214. {
  1215. if (empty($this->date_entered))
  1216. {
  1217. $this->date_entered = $this->date_modified;
  1218. }
  1219. if($this->set_created_by == true)
  1220. {
  1221. // created by should always be this user
  1222. $this->created_by = (isset($current_user)) ? $current_user->id : "";
  1223. }
  1224. if( $this->new_with_id == false)
  1225. {
  1226. $this->id = create_guid();
  1227. }
  1228. }
  1229. require_once("data/BeanFactory.php");
  1230. BeanFactory::registerBean($this->module_name, $this);
  1231. if (empty($GLOBALS['updating_relationships']) && empty($GLOBALS['saving_relationships']) && empty ($GLOBALS['resavingRelatedBeans']))
  1232. {
  1233. $GLOBALS['saving_relationships'] = true;
  1234. // let subclasses save related field changes
  1235. $this->save_relationship_changes($isUpdate);
  1236. $GLOBALS['saving_relationships'] = false;
  1237. }
  1238. if($isUpdate && !$this->update_date_entered)
  1239. {
  1240. unset($this->date_entered);
  1241. }
  1242. // call the custom business logic
  1243. $custom_logic_arguments['check_notify'] = $check_notify;
  1244. $this->call_custom_logic("before_save", $custom_logic_arguments);
  1245. unset($custom_logic_arguments);
  1246. if(isset($this->custom_fields))
  1247. {
  1248. $this->custom_fields->bean = $this;
  1249. $this->custom_fields->save($isUpdate);
  1250. }
  1251. // use the db independent query generator
  1252. $this->preprocess_fields_on_save();
  1253. //construct the SQL to create the audit record if auditing is enabled.
  1254. $dataChanges=array();
  1255. if ($this->is_AuditEnabled()) {
  1256. if ($isUpdate && !isset($this->fetched_row)) {
  1257. $GLOBALS['log']->debug('Auditing: Retrieve was not called, audit record will not be created.');
  1258. } else {
  1259. $dataChanges=$this->db->getDataChanges($this);
  1260. }
  1261. }
  1262. $this->_sendNotifications($check_notify);
  1263. if ($isUpdate) {
  1264. $this->db->update($this);
  1265. } else {
  1266. $this->db->insert($this);
  1267. }
  1268. if (!empty($dataChanges) && is_array($dataChanges))
  1269. {
  1270. foreach ($dataChanges as $change)
  1271. {
  1272. $this->db->save_audit_records($this,$change);
  1273. }
  1274. }
  1275. if (empty($GLOBALS['resavingRelatedBeans'])){
  1276. SugarRelationship::resaveRelatedBeans();
  1277. }
  1278. //If we aren't in setup mode and we have a current user and module, then we track
  1279. if(isset($GLOBALS['current_user']) && isset($this->module_dir))
  1280. {
  1281. $this->track_view($current_user->id, $this->module_dir, 'save');
  1282. }
  1283. $this->call_custom_logic('after_save', '');
  1284. //Now that the record has been saved, we don't want to insert again on further saves
  1285. $this->new_with_id = false;
  1286. $this->in_save = false;
  1287. return $this->id;
  1288. }
  1289. /**
  1290. * Performs a check if the record has been modified since the specified date
  1291. *
  1292. * @param date $date Datetime for verification
  1293. * @param string $modified_user_id User modified by
  1294. */
  1295. function has_been_modified_since($date, $modified_user_id)
  1296. {
  1297. global $current_user;
  1298. $date = $this->db->convert($this->db->quoted($date), 'datetime');
  1299. if (isset($current_user))
  1300. {
  1301. $query = "SELECT date_modified FROM $this->table_name WHERE id='$this->id' AND modified_user_id != '$current_user->id'
  1302. AND (modified_user_id != '$modified_user_id' OR date_modified > $date)";
  1303. $result = $this->db->query($query);
  1304. if($this->db->fetchByAssoc($result))
  1305. {
  1306. return true;
  1307. }
  1308. }
  1309. return false;
  1310. }
  1311. /**
  1312. * Determines which users receive a notification
  1313. */
  1314. function get_notification_recipients() {
  1315. $notify_user = new User();
  1316. $notify_user->retrieve($this->assigned_user_id);
  1317. $this->new_assigned_user_name = $notify_user->full_name;
  1318. $GLOBALS['log']->info("Notifications: recipient is $this->new_assigned_user_name");
  1319. $user_list = array($notify_user);
  1320. return $user_list;
  1321. /*
  1322. //send notifications to followers, but ensure to not query for the assigned_user.
  1323. return SugarFollowing::getFollowers($this, $notify_user);
  1324. */
  1325. }
  1326. /**
  1327. * Handles sending out email notifications when items are first assigned to users
  1328. *
  1329. * @param string $notify_user user to notify
  1330. * @param string $admin the admin user that sends out the notification
  1331. */
  1332. function send_assignment_notifications($notify_user, $admin)
  1333. {
  1334. global $current_user;
  1335. if(($this->object_name == 'Meeting' || $this->object_name == 'Call') || $notify_user->receive_notifications)
  1336. {
  1337. $sendToEmail = $notify_user->emailAddress->getPrimaryAddress($notify_user);
  1338. $sendEmail = TRUE;
  1339. if(empty($sendToEmail)) {
  1340. $GLOBALS['log']->warn("Notifications: no e-mail address set for user {$notify_user->user_name}, cancelling send");
  1341. $sendEmail = FALSE;
  1342. }
  1343. $notify_mail = $this->create_notification_email($notify_user);
  1344. $notify_mail->setMailerForSystem();
  1345. if(empty($admin->settings['notify_send_from_assigning_user'])) {
  1346. $notify_mail->From = $admin->settings['notify_fromaddress'];
  1347. $notify_mail->FromName = (empty($admin->settings['notify_fromname'])) ? "" : $admin->settings['notify_fromname'];
  1348. } else {
  1349. // Send notifications from the current user's e-mail (if set)
  1350. $fromAddress = $current_user->emailAddress->getReplyToAddress($current_user);
  1351. $fromAddress = !empty($fromAddress) ? $fromAddress : $admin->settings['notify_fromaddress'];
  1352. $notify_mail->From = $fromAddress;
  1353. //Use the users full name is available otherwise default to system name
  1354. $from_name = !empty($admin->settings['notify_fromname']) ? $admin->settings['notify_fromname'] : "";
  1355. $from_name = !empty($current_user->full_name) ? $current_user->full_name : $from_name;
  1356. $notify_mail->FromName = $from_name;
  1357. }
  1358. $oe = new OutboundEmail();
  1359. $oe = $oe->getUserMailerSettings($current_user);
  1360. //only send if smtp server is defined
  1361. if($sendEmail){
  1362. $smtpVerified = false;
  1363. //first check the user settings
  1364. if(!empty($oe->mail_smtpserver)){
  1365. $smtpVerified = true;
  1366. }
  1367. //if still not verified, check against the system settings
  1368. if (!$smtpVerified){
  1369. $oe = $oe->getSystemMailerSettings();
  1370. if(!empty($oe->mail_smtpserver)){
  1371. $smtpVerified = true;
  1372. }
  1373. }
  1374. //if smtp was not verified against user or system, then do not send out email
  1375. if (!$smtpVerified){
  1376. $GLOBALS['log']->fatal("Notifications: error sending e-mail, smtp server was not found ");
  1377. //break out
  1378. return;
  1379. }
  1380. if(!$notify_mail->Send()) {
  1381. $GLOBALS['log']->fatal("Notifications: error sending e-mail (method: {$notify_mail->Mailer}), (error: {$notify_mail->ErrorInfo})");
  1382. }else{
  1383. $GLOBALS['log']->info("Notifications: e-mail successfully sent");
  1384. }
  1385. }
  1386. }
  1387. }
  1388. /**
  1389. * This function handles create the email notifications email.
  1390. * @param string $notify_user the user to send the notification email to
  1391. */
  1392. function create_notification_email($notify_user) {
  1393. global $sugar_version;
  1394. global $sugar_config;
  1395. global $app_list_strings;
  1396. global $current_user;
  1397. global $locale;
  1398. global $beanList;
  1399. $OBCharset = $locale->getPrecedentPreference('default_email_charset');
  1400. require_once("include/SugarPHPMailer.php");
  1401. $notify_address = $notify_user->emailAddress->getPrimaryAddress($notify_user);
  1402. $notify_name = $notify_user->full_name;
  1403. $GLOBALS['log']->debug("Notifications: user has e-mail defined");
  1404. $notify_mail = new SugarPHPMailer();
  1405. $notify_mail->AddAddress($notify_address,$locale->translateCharsetMIME(trim($notify_name), 'UTF-8', $OBCharset));
  1406. if(empty($_SESSION['authenticated_user_language'])) {
  1407. $current_language = $sugar_config['default_language'];
  1408. } else {
  1409. $current_language = $_SESSION['authenticated_user_language'];
  1410. }
  1411. $xtpl = new XTemplate(get_notify_template_file($current_language));
  1412. if($this->module_dir == "Cases") {
  1413. $template_name = "Case"; //we should use Case, you can refer to the en_us.notify_template.html.
  1414. }
  1415. else {
  1416. $template_name = $beanList[$this->module_dir]; //bug 20637, in workflow this->object_name = strange chars.
  1417. }
  1418. $this->current_notify_user = $notify_user;
  1419. if(in_array('set_notification_body', get_class_methods($this))) {
  1420. $xtpl = $this->set_notification_body($xtpl, $this);
  1421. } else {
  1422. $xtpl->assign("OBJECT", translate('LBL_MODULE_NAME'));
  1423. $template_name = "Default";
  1424. }
  1425. if(!empty($_SESSION["special_notification"]) && $_SESSION["special_notification"]) {
  1426. $template_name = $beanList[$this->module_dir].'Special';
  1427. }
  1428. if($this->special_notification) {
  1429. $template_name = $beanList[$this->module_dir].'Special';
  1430. }
  1431. $xtpl->assign("ASSIGNED_USER", $this->new_assigned_user_name);
  1432. $xtpl->assign("ASSIGNER", $current_user->name);
  1433. $port = '';
  1434. if(isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) {
  1435. $port = $_SERVER['SERVER_PORT'];
  1436. }
  1437. if (!isset($_SERVER['HTTP_HOST'])) {
  1438. $_SERVER['HTTP_HOST'] = '';
  1439. }
  1440. $httpHost = $_SERVER['HTTP_HOST'];
  1441. if($colon = strpos($httpHost, ':')) {
  1442. $httpHost = substr($httpHost, 0, $colon);
  1443. }
  1444. $parsedSiteUrl = parse_url($sugar_config['site_url']);
  1445. $host = $parsedSiteUrl['host'];
  1446. if(!isset($parsedSiteUrl['port'])) {
  1447. $parsedSiteUrl['port'] = 80;
  1448. }
  1449. $port = ($parsedSiteUrl['port'] != 80) ? ":".$parsedSiteUrl['port'] : '';
  1450. $path = !empty($parsedSiteUrl['path']) ? $parsedSiteUrl['path'] : "";
  1451. $cleanUrl = "{$parsedSiteUrl['scheme']}://{$host}{$port}{$path}";
  1452. $xtpl->assign("URL", $cleanUrl."/index.php?module={$this->module_dir}&action=DetailView&record={$this->id}");
  1453. $xtpl->assign("SUGAR", "Sugar v{$sugar_version}");
  1454. $xtpl->parse($template_name);
  1455. $xtpl->parse($template_name . "_Subject");
  1456. $notify_mail->Body = from_html(trim($xtpl->text($template_name)));
  1457. $notify_mail->Subject = from_html($xtpl->text($template_name . "_Subject"));
  1458. // cn: bug 8568 encode notify email in User's outbound email encoding
  1459. $notify_mail->prepForOutbound();
  1460. return $notify_mail;
  1461. }
  1462. /**
  1463. * This function is a good location to save changes that have been made to a relationship.
  1464. * This should be overridden in subclasses that have something to save.
  1465. *
  1466. * @param boolean $is_update true if this save is an update.
  1467. * @param array $exclude a way to exclude relationships
  1468. */
  1469. public function save_relationship_changes($is_update, $exclude = array())
  1470. {
  1471. list($new_rel_id, $new_rel_link) = $this->set_relationship_info($exclude);
  1472. $new_rel_id = $this->handle_preset_relationships($new_rel_id, $new_rel_link, $exclude);
  1473. $this->handle_remaining_relate_fields($exclude);
  1474. $this->handle_request_relate($new_rel_id, $new_rel_link);
  1475. }
  1476. /**
  1477. * Look in the bean for the new relationship_id and relationship_name if $this->not_use_rel_in_req is set to true,
  1478. * otherwise check the $_REQUEST param for a relate_id and relate_to field. Once we have that make sure that it's
  1479. * not excluded from the passed in array of relationships to exclude
  1480. *
  1481. * @param array $exclude any relationship's to exclude
  1482. * @return array The relationship_id and relationship_name in an array
  1483. */
  1484. protected function set_relationship_info($exclude = array())
  1485. {
  1486. $new_rel_id = false;
  1487. $new_rel_link = false;
  1488. // check incoming data
  1489. if (isset($this->not_use_rel_in_req) && $this->not_use_rel_in_req == true) {
  1490. // if we should use relation data from properties (for REQUEST-independent calls)
  1491. $rel_id = isset($this->new_rel_id) ? $this->new_rel_id : '';
  1492. $rel_link = isset($this->new_rel_relname) ? $this->new_rel_relname : '';
  1493. }
  1494. else
  1495. {
  1496. // if we should use relation data from REQUEST
  1497. $rel_id = isset($_REQUEST['relate_id']) ? $_REQUEST['relate_id'] : '';
  1498. $rel_link = isset($_REQUEST['relate_to']) ? $_REQUEST['relate_to'] : '';
  1499. }
  1500. // filter relation data
  1501. if ($rel_id && $rel_link && !in_array($rel_link, $exclude) && $rel_id != $this->id) {
  1502. $new_rel_id = $rel_id;
  1503. $new_rel_link = $rel_link;
  1504. // Bug #53223 : wrong relationship from subpanel create button
  1505. // if LHSModule and RHSModule are same module use left link to add new item b/s of:
  1506. // $rel_id and $rel_link are not emty - request is from subpanel
  1507. // $rel_link contains relationship name - checked by call load_relationship
  1508. $this->load_relationship($rel_link);
  1509. if ( !empty($this->$rel_link) && $this->$rel_link->getRelationshipObject() && $this->$rel_link->getRelationshipObject()->getLHSModule() == $this->$rel_link->getRelationshipObject()->getRHSModule() )
  1510. {
  1511. $new_rel_link = $this->$rel_link->getRelationshipObject()->getLHSLink();
  1512. }
  1513. else
  1514. {
  1515. //Try to find the link in this bean based on the relationship
  1516. foreach ($this->field_defs as $key => $def)
  1517. {
  1518. if (isset($def['type']) && $def['type'] == 'link' && isset($def['relationship']) && $def['relationship'] == $rel_link)
  1519. {
  1520. $new_rel_link = $key;
  1521. }
  1522. }
  1523. }
  1524. }
  1525. return array($new_rel_id, $new_rel_link);
  1526. }
  1527. /**
  1528. * Handle the preset fields listed in the fixed relationship_fields array hardcoded into the OOB beans
  1529. *
  1530. * TODO: remove this mechanism and replace with mechanism exclusively based on the vardefs
  1531. *
  1532. * @api
  1533. * @see save_relationship_changes
  1534. * @param string|boolean $new_rel_id String of the ID to add
  1535. * @param string Relationship Name
  1536. * @param array $exclude any relationship's to exclude
  1537. * @return string|boolean Return the new_rel_id if it was not used. False if it was used.
  1538. */
  1539. protected function handle_preset_relationships($new_rel_id, $new_rel_link, $exclude = array())
  1540. {
  1541. if (isset($this->relationship_fields) && is_array($this->relationship_fields)) {
  1542. foreach ($this->relationship_fields as $id => $rel_name)
  1543. {
  1544. if (in_array($id, $exclude)) continue;
  1545. if(!empty($this->$id))
  1546. {
  1547. // Bug #44930 We do not need to update main related field if it is changed from sub-panel.
  1548. if ($rel_name == $new_rel_link && $this->$id != $new_rel_id)
  1549. {
  1550. $new_rel_id = '';
  1551. }
  1552. $GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - adding a relationship record: '.$rel_name . ' = ' . $this->$id);
  1553. //already related the new relationship id so let's set it to false so we don't add it again using the _REQUEST['relate_i'] mechanism in a later block
  1554. $this->load_relationship($rel_name);
  1555. $rel_add = $this->$rel_name->add($this->$id);
  1556. // move this around to only take out the id if it was save successfully
  1557. if ($this->$id == $new_rel_id && $rel_add == true) {
  1558. $new_rel_id = false;
  1559. }
  1560. } else {
  1561. //if before value is not empty then attempt to delete relationship
  1562. if (!empty($this->rel_fields_before_value[$id])) {
  1563. $GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - attempting to remove the relationship record, using relationship attribute' . $rel_name);
  1564. $this->load_relationship($rel_name);
  1565. $this->$rel_name->delete($this->id, $this->rel_fields_before_value[$id]);
  1566. }
  1567. }
  1568. }
  1569. }
  1570. return $new_rel_id;
  1571. }
  1572. /**
  1573. * Next, we'll attempt to update all of the remaining relate fields in the vardefs that have 'save' set in their field_def
  1574. * Only the 'save' fields should be saved as some vardef entries today are not for display only purposes and break the application if saved
  1575. * If the vardef has entries for field <a> of type relate, where a->id_name = <b> and field <b> of type link
  1576. * then we receive a value for b from the MVC in the _REQUEST, and it should be set in the bean as $this->$b
  1577. *
  1578. * @api
  1579. * @see save_relationship_changes
  1580. * @param array $exclude any relationship's to exclude
  1581. * @return array the list of relationships that were added or removed successfully or if they were a failure
  1582. */
  1583. protected function handle_remaining_relate_fields($exclude = array())
  1584. {
  1585. $modified_relationships = array(
  1586. 'add' => array('success' => array(), 'failure' => array()),
  1587. 'remove' => array('success' => array(), 'failure' => array()),
  1588. );
  1589. foreach ($this->field_defs as $def)
  1590. {
  1591. if ($def ['type'] == 'relate' && isset ($def ['id_name']) && isset ($def ['link']) && isset ($def['save'])) {
  1592. if (in_array($def['id_name'], $exclude) || in_array($def['id_name'], $this->relationship_fields))
  1593. continue; // continue to honor the exclude array and exclude any relationships that will be handled by the relationship_fields mechanism
  1594. $linkField = $def ['link'];
  1595. if (isset($this->field_defs[$linkField])) {
  1596. if ($this->load_relationship($linkField)) {
  1597. $idName = $def['id_name'];
  1598. if (!empty($this->rel_fields_before_value[$idName]) && empty($this->$idName)) {
  1599. //if before value is not empty then attempt to delete relationship
  1600. $GLOBALS['log']->debug("save_relationship_changes(): From field_defs - attempting to remove the relationship record: {$def [ 'link' ]} = {$this->rel_fields_before_value[$def [ 'id_name' ]]}");
  1601. $success = $this->$def ['link']->delete($this->id, $this->rel_fields_before_value[$def ['id_name']]);
  1602. // just need to make sure it's true and not an array as it's possible to return an array
  1603. if($success == true) {
  1604. $modified_relationships['remove']['success'][] = $def['link'];
  1605. } else {
  1606. $modified_relationships['remove']['failure'][] = $def['link'];
  1607. }
  1608. $GLOBALS['log']->debug("save_relationship_changes(): From field_defs - attempting to remove the relationship record returned " . var_export($success, true));
  1609. }
  1610. if (!empty($this->$idName) && is_string($this->$idName)) {
  1611. $GLOBALS['log']->debug("save_relationship_changes(): From field_defs - attempting to add a relationship record - {$def [ 'link' ]} = {$this->$def [ 'id_name' ]}");
  1612. $success = $this->$linkField->add($this->$idName);
  1613. // just need to make sure it's true and not an array as it's possible to return an array
  1614. if($success == true) {
  1615. $modified_relationships['add']['success'][] = $linkField;
  1616. } else {
  1617. $modified_relationships['add']['failure'][] = $linkField;
  1618. }
  1619. $GLOBALS['log']->debug("save_relationship_changes(): From field_defs - add a relationship record returned " . var_export($success, true));
  1620. }
  1621. } else {
  1622. $GLOBALS['log']->fatal("Failed to load relationship {$linkField} while saving {$this->module_dir}");
  1623. }
  1624. }
  1625. }
  1626. }
  1627. return $modified_relationships;
  1628. }
  1629. /**
  1630. * Finally, we update a field listed in the _REQUEST['%/relate_id']/_REQUEST['relate_to'] mechanism (if it has not already been updated)
  1631. *
  1632. * @api
  1633. * @see save_relationship_changes
  1634. * @param string|boolean $new_rel_id
  1635. * @param string $new_rel_link
  1636. * @return boolean
  1637. */
  1638. protected function handle_request_relate($new_rel_id, $new_rel_link)
  1639. {
  1640. if (!empty($new_rel_id)) {
  1641. if ($this->load_relationship($new_rel_link)) {
  1642. return $this->$new_rel_link->add($new_rel_id);
  1643. } else {
  1644. $lower_link = strtolower($new_rel_link);
  1645. if ($this->load_relationship($lower_link)) {
  1646. return $this->$lower_link->add($new_rel_id);
  1647. } else {
  1648. require_once('data/Link2.php');
  1649. $rel = Relationship::retrieve_by_modules($new_rel_link, $this->module_dir, $this->db, 'many-to-many');
  1650. if (!empty($rel)) {
  1651. foreach ($this->field_defs as $field => $def) {
  1652. if ($def['type'] == 'link' && !empty($def['relationship']) && $def['relationship'] == $rel) {
  1653. $this->load_relationship($field);
  1654. return $this->$field->add($new_rel_id);
  1655. }
  1656. }
  1657. //ok so we didn't find it in the field defs let's save it anyway if we have the relationshp
  1658. $this->$rel = new Link2($rel, $this, array());
  1659. return $this->$rel->add($new_rel_id);
  1660. }
  1661. }
  1662. }
  1663. }
  1664. // nothing was saved so just return false;
  1665. return false;
  1666. }
  1667. /**
  1668. * This function retrieves a record of the appropriate type from the DB.
  1669. * It fills in all of the fields from the DB into the object it was called on.
  1670. *
  1671. * @param $id - If ID is specified, it overrides the current value of $this->id. If not specified the current value of $this->id will be used.
  1672. * @return this - The object that it was called apon or null if exactly 1 record was not found.
  1673. *
  1674. */
  1675. function check_date_relationships_load()
  1676. {
  1677. global $disable_date_format;
  1678. global $timedate;
  1679. if (empty($timedate))
  1680. $timedate=TimeDate::getInstance();
  1681. if(empty($this->field_defs))
  1682. {
  1683. return;
  1684. }
  1685. foreach($this->field_defs as $fieldDef)
  1686. {
  1687. $field = $fieldDef['name'];
  1688. if(!isset($this->processed_dates_times[$field]))
  1689. {
  1690. $this->processed_dates_times[$field] = '1';
  1691. if(empty($this->$field)) continue;
  1692. if($field == 'date_modified' || $field == 'date_entered')
  1693. {
  1694. $this->$field = $this->db->fromConvert($this->$field, 'datetime');
  1695. if(empty($disable_date_format)) {
  1696. $this->$field = $timedate->to_display_date_time($this->$field);
  1697. }
  1698. }
  1699. elseif(isset($this->field_name_map[$field]['type']))
  1700. {
  1701. $type = $this->field_name_map[$field]['type'];
  1702. if($type == 'relate' && isset($this->field_name_map[$field]['custom_module']))
  1703. {
  1704. $type = $this->field_name_map[$field]['type'];
  1705. }
  1706. if($type == 'date')
  1707. {
  1708. if($this->$field == '0000-00-00')
  1709. {
  1710. $this->$field = '';
  1711. } elseif(!empty($this->field_name_map[$field]['rel_field']))
  1712. {
  1713. $rel_field = $this->field_name_map[$field]['rel_field'];
  1714. if(!empty($this->$rel_field))
  1715. {
  1716. if(empty($disable_date_format)) {
  1717. $mergetime = $timedate->merge_date_time($this->$field,$this->$rel_field);
  1718. $this->$field = $timedate->to_display_date($mergetime);
  1719. $this->$rel_field = $timedate->to_display_time($mergetime);
  1720. }
  1721. }
  1722. }
  1723. else
  1724. {
  1725. if(empty($disable_date_format)) {
  1726. $this->$field = $timedate->to_display_date($this->$field, false);
  1727. }
  1728. }
  1729. } elseif($type == 'datetime' || $type == 'datetimecombo')
  1730. {
  1731. if($this->$field == '0000-00-00 00:00:00')
  1732. {
  1733. $this->$field = '';
  1734. }
  1735. else
  1736. {
  1737. if(empty($disable_date_format)) {
  1738. $this->$field = $timedate->to_display_date_time($this->$field, true, true);
  1739. }
  1740. }
  1741. } elseif($type == 'time')
  1742. {
  1743. if($this->$field == '00:00:00')
  1744. {
  1745. $this->$field = '';
  1746. } else
  1747. {
  1748. //$this->$field = from_db_convert($this->$field, 'time');
  1749. if(empty($this->field_name_map[$field]['rel_field']) && empty($disable_date_format))
  1750. {
  1751. $this->$field = $timedate->to_display_time($this->$field,true, false);
  1752. }
  1753. }
  1754. } elseif($type == 'encrypt' && empty($disable_date_format)){
  1755. $this->$field = $this->decrypt_after_retrieve($this->$field);
  1756. }
  1757. }
  1758. }
  1759. }
  1760. }
  1761. /**
  1762. * This function processes the fields before save.
  1763. * Interal function, do not override.
  1764. */
  1765. function preprocess_fields_on_save()
  1766. {
  1767. $GLOBALS['log']->deprecated('SugarBean.php: preprocess_fields_on_save() is deprecated');
  1768. }
  1769. /**
  1770. * Removes formatting from values posted from the user interface.
  1771. * It only unformats numbers. Function relies on user/system prefernce for format strings.
  1772. *
  1773. * Internal Function, do not override.
  1774. */
  1775. function unformat_all_fields()
  1776. {
  1777. $GLOBALS['log']->deprecated('SugarBean.php: unformat_all_fields() is deprecated');
  1778. }
  1779. /**
  1780. * This functions adds formatting to all number fields before presenting them to user interface.
  1781. *
  1782. * Internal function, do not override.
  1783. */
  1784. function format_all_fields()
  1785. {
  1786. $GLOBALS['log']->deprecated('SugarBean.php: format_all_fields() is deprecated');
  1787. }
  1788. function format_field($fieldDef)
  1789. {
  1790. $GLOBALS['log']->deprecated('SugarBean.php: format_field() is deprecated');
  1791. }
  1792. /**
  1793. * Function corrects any bad formatting done by 3rd party/custom code
  1794. *
  1795. * This function will be removed in a future release, it is only here to assist upgrading existing code that expects formatted data in the bean
  1796. */
  1797. function fixUpFormatting()
  1798. {
  1799. global $timedate;
  1800. static $boolean_false_values = array('off', 'false', '0', 'no');
  1801. foreach($this->field_defs as $field=>$def)
  1802. {
  1803. if ( !isset($this->$field) ) {
  1804. continue;
  1805. }
  1806. if ( (isset($def['source'])&&$def['source']=='non-db') || $field == 'deleted' ) {
  1807. continue;
  1808. }
  1809. if ( isset($this->fetched_row[$field]) && $this->$field == $this->fetched_row[$field] ) {
  1810. // Don't hand out warnings because the field was untouched between retrieval and saving, most database drivers hand pretty much everything back as strings.
  1811. continue;
  1812. }
  1813. $reformatted = false;
  1814. switch($def['type']) {
  1815. case 'datetime':
  1816. case 'datetimecombo':
  1817. if(empty($this->$field)) break;
  1818. if ($this->$field == 'NULL') {
  1819. $this->$field = '';
  1820. break;
  1821. }
  1822. if ( ! preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/',$this->$field) ) {
  1823. // This appears to be formatted in user date/time
  1824. $this->$field = $timedate->to_db($this->$field);
  1825. $reformatted = true;
  1826. }
  1827. break;
  1828. case 'date':
  1829. if(empty($this->$field)) break;
  1830. if ($this->$field == 'NULL') {
  1831. $this->$field = '';
  1832. break;
  1833. }
  1834. if ( ! preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/',$this->$field) ) {
  1835. // This date appears to be formatted in the user's format
  1836. $this->$field = $timedate->to_db_date($this->$field, false);
  1837. $reformatted = true;
  1838. }
  1839. break;
  1840. case 'time':
  1841. if(empty($this->$field)) break;
  1842. if ($this->$field == 'NULL') {
  1843. $this->$field = '';
  1844. break;
  1845. }
  1846. if ( preg_match('/(am|pm)/i',$this->$field) ) {
  1847. // This time appears to be formatted in the user's format
  1848. $this->$field = $timedate->fromUserTime($this->$field)->format(TimeDate::DB_TIME_FORMAT);
  1849. $reformatted = true;
  1850. }
  1851. break;
  1852. case 'double':
  1853. case 'decimal':
  1854. case 'currency':
  1855. case 'float':
  1856. if ( $this->$field === '' || $this->$field == NULL || $this->$field == 'NULL') {
  1857. continue;
  1858. }
  1859. if ( is_string($this->$field) ) {
  1860. $this->$field = (float)unformat_number($this->$field);
  1861. $reformatted = true;
  1862. }
  1863. break;
  1864. case 'uint':
  1865. case 'ulong':
  1866. case 'long':
  1867. case 'short':
  1868. case 'tinyint':
  1869. case 'int':
  1870. if ( $this->$field === '' || $this->$field == NULL || $this->$field == 'NULL') {
  1871. continue;
  1872. }
  1873. if ( is_string($this->$field) ) {
  1874. $this->$field = (int)unformat_number($this->$field);
  1875. $reformatted = true;
  1876. }
  1877. break;
  1878. case 'bool':
  1879. if (empty($this->$field)) {
  1880. $this->$field = false;
  1881. } else if(true === $this->$field || 1 == $this->$field) {
  1882. $this->$field = true;
  1883. } else if(in_array(strval($this->$field), $boolean_false_values)) {
  1884. $this->$field = false;
  1885. $reformatted = true;
  1886. } else {
  1887. $this->$field = true;
  1888. $reformatted = true;
  1889. }
  1890. break;
  1891. case 'encrypt':
  1892. $this->$field = $this->encrpyt_before_save($this->$field);
  1893. break;
  1894. }
  1895. if ( $reformatted ) {
  1896. $GLOBALS['log']->deprecated('Formatting correction: '.$this->module_dir.'->'.$field.' had formatting automatically corrected. This will be removed in the future, please upgrade your external code');
  1897. }
  1898. }
  1899. }
  1900. /**
  1901. * Function fetches a single row of data given the primary key value.
  1902. *
  1903. * The fetched data is then set into the bean. The function also processes the fetched data by formattig
  1904. * date/time and numeric values.
  1905. *
  1906. * @param string $id Optional, default -1, is set to -1 id value from the bean is used, else, passed value is used
  1907. * @param boolean $encode Optional, default true, encodes the values fetched from the database.
  1908. * @param boolean $deleted Optional, default true, if set to false deleted filter will not be added.
  1909. *
  1910. * Internal function, do not override.
  1911. */
  1912. function retrieve($id = -1, $encode=true,$deleted=true)
  1913. {
  1914. $custom_logic_arguments['id'] = $id;
  1915. $this->call_custom_logic('before_retrieve', $custom_logic_arguments);
  1916. if ($id == -1)
  1917. {
  1918. $id = $this->id;
  1919. }
  1920. if(isset($this->custom_fields))
  1921. {
  1922. $custom_join = $this->custom_fields->getJOIN();
  1923. }
  1924. else
  1925. $custom_join = false;
  1926. if($custom_join)
  1927. {
  1928. $query = "SELECT $this->table_name.*". $custom_join['select']. " FROM $this->table_name ";
  1929. }
  1930. else
  1931. {
  1932. $query = "SELECT $this->table_name.* FROM $this->table_name ";
  1933. }
  1934. if($custom_join)
  1935. {
  1936. $query .= ' ' . $custom_join['join'];
  1937. }
  1938. $query .= " WHERE $this->table_name.id = ".$this->db->quoted($id);
  1939. if ($deleted) $query .= " AND $this->table_name.deleted=0";
  1940. $GLOBALS['log']->debug("Retrieve $this->object_name : ".$query);
  1941. $result = $this->db->limitQuery($query,0,1,true, "Retrieving record by id $this->table_name:$id found ");
  1942. if(empty($result))
  1943. {
  1944. return null;
  1945. }
  1946. $row = $this->db->fetchByAssoc($result, $encode);
  1947. if(empty($row))
  1948. {
  1949. return null;
  1950. }
  1951. //make copy of the fetched row for construction of audit record and for business logic/workflow
  1952. $row = $this->convertRow($row);
  1953. $this->fetched_row=$row;
  1954. $this->populateFromRow($row);
  1955. global $module, $action;
  1956. //Just to get optimistic locking working for this release
  1957. if($this->optimistic_lock && $module == $this->module_dir && $action =='EditView' )
  1958. {
  1959. $_SESSION['o_lock_id']= $id;
  1960. $_SESSION['o_lock_dm']= $this->date_modified;
  1961. $_SESSION['o_lock_on'] = $this->object_name;
  1962. }
  1963. $this->processed_dates_times = array();
  1964. $this->check_date_relationships_load();
  1965. if($custom_join)
  1966. {
  1967. $this->custom_fields->fill_relationships();
  1968. }
  1969. $this->is_updated_dependent_fields = false;
  1970. $this->fill_in_additional_detail_fields();
  1971. $this->fill_in_relationship_fields();
  1972. // save related fields values for audit
  1973. foreach ($this->get_related_fields() as $rel_field_name)
  1974. {
  1975. if (! empty($this->$rel_field_name['name']))
  1976. {
  1977. $this->fetched_rel_row[$rel_field_name['name']] = $this->$rel_field_name['name'];
  1978. }
  1979. }
  1980. //make a copy of fields in the relationship_fields array. These field values will be used to
  1981. //clear relationship.
  1982. foreach ( $this->field_defs as $key => $def )
  1983. {
  1984. if ($def [ 'type' ] == 'relate' && isset ( $def [ 'id_name'] ) && isset ( $def [ 'link'] ) && isset ( $def[ 'save' ])) {
  1985. if (isset($this->$key)) {
  1986. $this->rel_fields_before_value[$key]=$this->$key;
  1987. if (isset($this->$def [ 'id_name']))
  1988. $this->rel_fields_before_value[$def [ 'id_name']]=$this->$def [ 'id_name'];
  1989. }
  1990. else
  1991. $this->rel_fields_before_value[$key]=null;
  1992. }
  1993. }
  1994. if (isset($this->relationship_fields) && is_array($this->relationship_fields))
  1995. {
  1996. foreach ($this->relationship_fields as $rel_id=>$rel_name)
  1997. {
  1998. if (isset($this->$rel_id))
  1999. $this->rel_fields_before_value[$rel_id]=$this->$rel_id;
  2000. else
  2001. $this->rel_fields_before_value[$rel_id]=null;
  2002. }
  2003. }
  2004. // call the custom business logic
  2005. $custom_logic_arguments['id'] = $id;
  2006. $custom_logic_arguments['encode'] = $encode;
  2007. $this->call_custom_logic("after_retrieve", $custom_logic_arguments);
  2008. unset($custom_logic_arguments);
  2009. return $this;
  2010. }
  2011. /**
  2012. * Sets value from fetched row into the bean.
  2013. *
  2014. * @param array $row Fetched row
  2015. * @todo loop through vardefs instead
  2016. * @internal runs into an issue when populating from field_defs for users - corrupts user prefs
  2017. *
  2018. * Internal function, do not override.
  2019. */
  2020. function populateFromRow($row)
  2021. {
  2022. $nullvalue='';
  2023. foreach($this->field_defs as $field=>$field_value)
  2024. {
  2025. if($field == 'user_preferences' && $this->module_dir == 'Users')
  2026. continue;
  2027. if(isset($row[$field]))
  2028. {
  2029. $this->$field = $row[$field];
  2030. $owner = $field . '_owner';
  2031. if(!empty($row[$owner])){
  2032. $this->$owner = $row[$owner];
  2033. }
  2034. }
  2035. else
  2036. {
  2037. $this->$field = $nullvalue;
  2038. }
  2039. }
  2040. }
  2041. /**
  2042. * Add any required joins to the list count query. The joins are required if there
  2043. * is a field in the $where clause that needs to be joined.
  2044. *
  2045. * @param string $query
  2046. * @param string $where
  2047. *
  2048. * Internal Function, do Not override.
  2049. */
  2050. function add_list_count_joins(&$query, $where)
  2051. {
  2052. $custom_join = $this->custom_fields->getJOIN();
  2053. if($custom_join)
  2054. {
  2055. $query .= $custom_join['join'];
  2056. }
  2057. }
  2058. /**
  2059. * Changes the select expression of the given query to be 'count(*)' so you
  2060. * can get the number of items the query will return. This is used to
  2061. * populate the upper limit on ListViews.
  2062. *
  2063. * @param string $query Select query string
  2064. * @return string count query
  2065. *
  2066. * Internal function, do not override.
  2067. */
  2068. function create_list_count_query($query)
  2069. {
  2070. // remove the 'order by' clause which is expected to be at the end of the query
  2071. $pattern = '/\sORDER BY.*/is'; // ignores the case
  2072. $replacement = '';
  2073. $query = preg_replace($pattern, $replacement, $query);
  2074. //handle distinct clause
  2075. $star = '*';
  2076. if(substr_count(strtolower($query), 'distinct')){
  2077. if (!empty($this->seed) && !empty($this->seed->table_name ))
  2078. $star = 'DISTINCT ' . $this->seed->table_name . '.id';
  2079. else
  2080. $star = 'DISTINCT ' . $this->table_name . '.id';
  2081. }
  2082. // change the select expression to 'count(*)'
  2083. $pattern = '/SELECT(.*?)(\s){1}FROM(\s){1}/is'; // ignores the case
  2084. $replacement = 'SELECT count(' . $star . ') c FROM ';
  2085. //if the passed query has union clause then replace all instances of the pattern.
  2086. //this is very rare. I have seen this happening only from projects module.
  2087. //in addition to this added a condition that has union clause and uses
  2088. //sub-selects.
  2089. if (strstr($query," UNION ALL ") !== false) {
  2090. //separate out all the queries.
  2091. $union_qs=explode(" UNION ALL ", $query);
  2092. foreach ($union_qs as $key=>$union_query) {
  2093. $star = '*';
  2094. preg_match($pattern, $union_query, $matches);
  2095. if (!empty($matches)) {
  2096. if (stristr($matches[0], "distinct")) {
  2097. if (!empty($this->seed) && !empty($this->seed->table_name ))
  2098. $star = 'DISTINCT ' . $this->seed->table_name . '.id';
  2099. else
  2100. $star = 'DISTINCT ' . $this->table_name . '.id';
  2101. }
  2102. } // if
  2103. $replacement = 'SELECT count(' . $star . ') c FROM ';
  2104. $union_qs[$key] = preg_replace($pattern, $replacement, $union_query,1);
  2105. }
  2106. $modified_select_query=implode(" UNION ALL ",$union_qs);
  2107. } else {
  2108. $modified_select_query = preg_replace($pattern, $replacement, $query,1);
  2109. }
  2110. return $modified_select_query;
  2111. }
  2112. /**
  2113. * This function returns a paged list of the current object type. It is intended to allow for
  2114. * hopping back and forth through pages of data. It only retrieves what is on the current page.
  2115. *
  2116. * @internal This method must be called on a new instance. It trashes the values of all the fields in the current one.
  2117. * @param string $order_by
  2118. * @param string $where Additional where clause
  2119. * @param int $row_offset Optaional,default 0, starting row number
  2120. * @param init $limit Optional, default -1
  2121. * @param int $max Optional, default -1
  2122. * @param boolean $show_deleted Optional, default 0, if set to 1 system will show deleted records.
  2123. * @return array Fetched data.
  2124. *
  2125. * Internal function, do not override.
  2126. *
  2127. */
  2128. function get_list($order_by = "", $where = "", $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0, $singleSelect=false, $select_fields = array())
  2129. {
  2130. $GLOBALS['log']->debug("get_list: order_by = '$order_by' and where = '$where' and limit = '$limit'");
  2131. if(isset($_SESSION['show_deleted']))
  2132. {
  2133. $show_deleted = 1;
  2134. }
  2135. $order_by=$this->process_order_by($order_by);
  2136. if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
  2137. {
  2138. global $current_user;
  2139. $owner_where = $this->getOwnerWhere($current_user->id);
  2140. //rrs - because $this->getOwnerWhere() can return '' we need to be sure to check for it and
  2141. //handle it properly else you could get into a situation where you are create a where stmt like
  2142. //WHERE .. AND ''
  2143. if(!empty($owner_where)){
  2144. if(empty($where)){
  2145. $where = $owner_where;
  2146. }else{
  2147. $where .= ' AND '. $owner_where;
  2148. }
  2149. }
  2150. }
  2151. $query = $this->create_new_list_query($order_by, $where,$select_fields,array(), $show_deleted,'',false,null,$singleSelect);
  2152. return $this->process_list_query($query, $row_offset, $limit, $max, $where);
  2153. }
  2154. /**
  2155. * Prefixes column names with this bean's table name.
  2156. *
  2157. * @param string $order_by Order by clause to be processed
  2158. * @param SugarBean $submodule name of the module this order by clause is for
  2159. * @return string Processed order by clause
  2160. *
  2161. * Internal function, do not override.
  2162. */
  2163. function process_order_by ($order_by, $submodule = null)
  2164. {
  2165. if (empty($order_by))
  2166. return $order_by;
  2167. //submodule is empty,this is for list object in focus
  2168. if (empty($submodule))
  2169. {
  2170. $bean_queried = $this;
  2171. }
  2172. else
  2173. {
  2174. //submodule is set, so this is for subpanel, use submodule
  2175. $bean_queried = $submodule;
  2176. }
  2177. $elements = explode(',',$order_by);
  2178. foreach ($elements as $key=>$value)
  2179. {
  2180. if (strchr($value,'.') === false)
  2181. {
  2182. //value might have ascending and descending decorations
  2183. $list_column = explode(' ',trim($value));
  2184. if (isset($list_column[0]))
  2185. {
  2186. $list_column_name=trim($list_column[0]);
  2187. if (isset($bean_queried->field_defs[$list_column_name]))
  2188. {
  2189. $source=isset($bean_queried->field_defs[$list_column_name]['source']) ? $bean_queried->field_defs[$list_column_name]['source']:'db';
  2190. if (empty($bean_queried->field_defs[$list_column_name]['table']) && $source=='db')
  2191. {
  2192. $list_column[0] = $bean_queried->table_name .".".$list_column[0] ;
  2193. }
  2194. if (empty($bean_queried->field_defs[$list_column_name]['table']) && $source=='custom_fields')
  2195. {
  2196. $list_column[0] = $bean_queried->table_name ."_cstm.".$list_column[0] ;
  2197. }
  2198. // Bug 38803 - Use CONVERT() function when doing an order by on ntext, text, and image fields
  2199. if ($source != 'non-db' && $this->db->isTextType($this->db->getFieldType($bean_queried->field_defs[$list_column_name]))) {
  2200. $list_column[0] = $this->db->convert($list_column[0], "text2char");
  2201. }
  2202. $value = implode(' ',$list_column);
  2203. } else {
  2204. $GLOBALS['log']->debug("process_order_by: ($list_column[0]) does not have a vardef entry.");
  2205. }
  2206. }
  2207. }
  2208. $elements[$key]=$value;
  2209. }
  2210. return implode(',', $elements);
  2211. }
  2212. /**
  2213. * Returns a detail object like retrieving of the current object type.
  2214. *
  2215. * It is intended for use in navigation buttons on the DetailView. It will pass an offset and limit argument to the sql query.
  2216. * @internal This method must be called on a new instance. It overrides the values of all the fields in the current one.
  2217. *
  2218. * @param string $order_by
  2219. * @param string $where Additional where clause
  2220. * @param int $row_offset Optaional,default 0, starting row number
  2221. * @param init $limit Optional, default -1
  2222. * @param int $max Optional, default -1
  2223. * @param boolean $show_deleted Optioanl, default 0, if set to 1 system will show deleted records.
  2224. * @return array Fetched data.
  2225. *
  2226. * Internal function, do not override.
  2227. */
  2228. function get_detail($order_by = "", $where = "", $offset = 0, $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0)
  2229. {
  2230. $GLOBALS['log']->debug("get_detail: order_by = '$order_by' and where = '$where' and limit = '$limit' and offset = '$offset'");
  2231. if(isset($_SESSION['show_deleted']))
  2232. {
  2233. $show_deleted = 1;
  2234. }
  2235. if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
  2236. {
  2237. global $current_user;
  2238. $owner_where = $this->getOwnerWhere($current_user->id);
  2239. if(empty($where))
  2240. {
  2241. $where = $owner_where;
  2242. }
  2243. else
  2244. {
  2245. $where .= ' AND '. $owner_where;
  2246. }
  2247. }
  2248. $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted, $offset);
  2249. //Add Limit and Offset to query
  2250. //$query .= " LIMIT 1 OFFSET $offset";
  2251. return $this->process_detail_query($query, $row_offset, $limit, $max, $where, $offset);
  2252. }
  2253. /**
  2254. * Fetches data from all related tables.
  2255. *
  2256. * @param object $child_seed
  2257. * @param string $related_field_name relation to fetch data for
  2258. * @param string $order_by Optional, default empty
  2259. * @param string $where Optional, additional where clause
  2260. * @return array Fetched data.
  2261. *
  2262. * Internal function, do not override.
  2263. */
  2264. function get_related_list($child_seed,$related_field_name, $order_by = "", $where = "",
  2265. $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0)
  2266. {
  2267. global $layout_edit_mode;
  2268. $query_array = array();
  2269. if(isset($layout_edit_mode) && $layout_edit_mode)
  2270. {
  2271. $response = array();
  2272. $child_seed->assign_display_fields($child_seed->module_dir);
  2273. $response['list'] = array($child_seed);
  2274. $response['row_count'] = 1;
  2275. $response['next_offset'] = 0;
  2276. $response['previous_offset'] = 0;
  2277. return $response;
  2278. }
  2279. $GLOBALS['log']->debug("get_related_list: order_by = '$order_by' and where = '$where' and limit = '$limit'");
  2280. if(isset($_SESSION['show_deleted']))
  2281. {
  2282. $show_deleted = 1;
  2283. }
  2284. $this->load_relationship($related_field_name);
  2285. if ($this->$related_field_name instanceof Link) {
  2286. $query_array = $this->$related_field_name->getQuery(true);
  2287. } else {
  2288. $query_array = $this->$related_field_name->getQuery(array(
  2289. "return_as_array" => true,
  2290. 'where' => '1=1' // hook for 'where' clause in M2MRelationship file
  2291. ));
  2292. }
  2293. $entire_where = $query_array['where'];
  2294. if(!empty($where))
  2295. {
  2296. if(empty($entire_where))
  2297. {
  2298. $entire_where = ' WHERE ' . $where;
  2299. }
  2300. else
  2301. {
  2302. $entire_where .= ' AND ' . $where;
  2303. }
  2304. }
  2305. $query = 'SELECT '.$child_seed->table_name.'.* ' . $query_array['from'] . ' ' . $entire_where;
  2306. if(!empty($order_by))
  2307. {
  2308. $query .= " ORDER BY " . $order_by;
  2309. }
  2310. return $child_seed->process_list_query($query, $row_offset, $limit, $max, $where);
  2311. }
  2312. protected static function build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by)
  2313. {
  2314. global $layout_edit_mode, $beanFiles, $beanList;
  2315. $subqueries = array();
  2316. foreach($subpanel_list as $this_subpanel)
  2317. {
  2318. if(!$this_subpanel->isDatasourceFunction() || ($this_subpanel->isDatasourceFunction()
  2319. && isset($this_subpanel->_instance_properties['generate_select'])
  2320. && $this_subpanel->_instance_properties['generate_select']==true))
  2321. {
  2322. //the custom query function must return an array with
  2323. if ($this_subpanel->isDatasourceFunction()) {
  2324. $shortcut_function_name = $this_subpanel->get_data_source_name();
  2325. $parameters=$this_subpanel->get_function_parameters();
  2326. if (!empty($parameters))
  2327. {
  2328. //if the import file function is set, then import the file to call the custom function from
  2329. if (is_array($parameters) && isset($parameters['import_function_file'])){
  2330. //this call may happen multiple times, so only require if function does not exist
  2331. if(!function_exists($shortcut_function_name)){
  2332. require_once($parameters['import_function_file']);
  2333. }
  2334. //call function from required file
  2335. $query_array = $shortcut_function_name($parameters);
  2336. }else{
  2337. //call function from parent bean
  2338. $query_array = $parentbean->$shortcut_function_name($parameters);
  2339. }
  2340. }
  2341. else
  2342. {
  2343. $query_array = $parentbean->$shortcut_function_name();
  2344. }
  2345. } else {
  2346. $related_field_name = $this_subpanel->get_data_source_name();
  2347. if (!$parentbean->load_relationship($related_field_name)){
  2348. unset ($parentbean->$related_field_name);
  2349. continue;
  2350. }
  2351. $query_array = $parentbean->$related_field_name->getSubpanelQuery(array(), true);
  2352. }
  2353. $table_where = preg_replace('/^\s*WHERE/i', '', $this_subpanel->get_where());
  2354. $where_definition = preg_replace('/^\s*WHERE/i', '', $query_array['where']);
  2355. if(!empty($table_where))
  2356. {
  2357. if(empty($where_definition))
  2358. {
  2359. $where_definition = $table_where;
  2360. }
  2361. else
  2362. {
  2363. $where_definition .= ' AND ' . $table_where;
  2364. }
  2365. }
  2366. $submodulename = $this_subpanel->_instance_properties['module'];
  2367. $submoduleclass = $beanList[$submodulename];
  2368. //require_once($beanFiles[$submoduleclass]);
  2369. $submodule = new $submoduleclass();
  2370. $subwhere = $where_definition;
  2371. $list_fields = $this_subpanel->get_list_fields();
  2372. foreach($list_fields as $list_key=>$list_field)
  2373. {
  2374. if(isset($list_field['usage']) && $list_field['usage'] == 'display_only')
  2375. {
  2376. unset($list_fields[$list_key]);
  2377. }
  2378. }
  2379. if(!$subpanel_def->isCollection() && isset($list_fields[$order_by]) && isset($submodule->field_defs[$order_by])&& (!isset($submodule->field_defs[$order_by]['source']) || $submodule->field_defs[$order_by]['source'] == 'db'))
  2380. {
  2381. $order_by = $submodule->table_name .'.'. $order_by;
  2382. }
  2383. $table_name = $this_subpanel->table_name;
  2384. $panel_name=$this_subpanel->name;
  2385. $params = array();
  2386. $params['distinct'] = $this_subpanel->distinct_query();
  2387. $params['joined_tables'] = $query_array['join_tables'];
  2388. $params['include_custom_fields'] = !$subpanel_def->isCollection();
  2389. $params['collection_list'] = $subpanel_def->get_inst_prop_value('collection_list');
  2390. $subquery = $submodule->create_new_list_query('',$subwhere ,$list_fields,$params, 0,'', true,$parentbean);
  2391. $subquery['select'] = $subquery['select']." , '$panel_name' panel_name ";
  2392. $subquery['from'] = $subquery['from'].$query_array['join'];
  2393. $subquery['query_array'] = $query_array;
  2394. $subquery['params'] = $params;
  2395. $subqueries[] = $subquery;
  2396. }
  2397. }
  2398. return $subqueries;
  2399. }
  2400. /**
  2401. * Constructs a query to fetch data for supanels and list views
  2402. *
  2403. * It constructs union queries for activities subpanel.
  2404. *
  2405. * @param SugarBean $parentbean constructing queries for link attributes in this bean
  2406. * @param string $order_by Optional, order by clause
  2407. * @param string $sort_order Optional, sort order
  2408. * @param string $where Optional, additional where clause
  2409. *
  2410. * Internal Function, do not overide.
  2411. */
  2412. function get_union_related_list($parentbean, $order_by = "", $sort_order='', $where = "",
  2413. $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0, $subpanel_def)
  2414. {
  2415. $secondary_queries = array();
  2416. global $layout_edit_mode, $beanFiles, $beanList;
  2417. if(isset($_SESSION['show_deleted']))
  2418. {
  2419. $show_deleted = 1;
  2420. }
  2421. $final_query = '';
  2422. $final_query_rows = '';
  2423. $subpanel_list=array();
  2424. if ($subpanel_def->isCollection())
  2425. {
  2426. $subpanel_def->load_sub_subpanels();
  2427. $subpanel_list=$subpanel_def->sub_subpanels;
  2428. }
  2429. else
  2430. {
  2431. $subpanel_list[]=$subpanel_def;
  2432. }
  2433. $first = true;
  2434. //Breaking the building process into two loops. The first loop gets a list of all the sub-queries.
  2435. //The second loop merges the queries and forces them to select the same number of columns
  2436. //All columns in a sub-subpanel group must have the same aliases
  2437. //If the subpanel is a datasource function, it can't be a collection so we just poll that function for the and return that
  2438. foreach($subpanel_list as $this_subpanel)
  2439. {
  2440. if($this_subpanel->isDatasourceFunction() && empty($this_subpanel->_instance_properties['generate_select']))
  2441. {
  2442. $shortcut_function_name = $this_subpanel->get_data_source_name();
  2443. $parameters=$this_subpanel->get_function_parameters();
  2444. if (!empty($parameters))
  2445. {
  2446. //if the import file function is set, then import the file to call the custom function from
  2447. if (is_array($parameters) && isset($parameters['import_function_file'])){
  2448. //this call may happen multiple times, so only require if function does not exist
  2449. if(!function_exists($shortcut_function_name)){
  2450. require_once($parameters['import_function_file']);
  2451. }
  2452. //call function from required file
  2453. $tmp_final_query = $shortcut_function_name($parameters);
  2454. }else{
  2455. //call function from parent bean
  2456. $tmp_final_query = $parentbean->$shortcut_function_name($parameters);
  2457. }
  2458. } else {
  2459. $tmp_final_query = $parentbean->$shortcut_function_name();
  2460. }
  2461. if(!$first)
  2462. {
  2463. $final_query_rows .= ' UNION ALL ( '.$parentbean->create_list_count_query($tmp_final_query, $parameters) . ' )';
  2464. $final_query .= ' UNION ALL ( '.$tmp_final_query . ' )';
  2465. } else {
  2466. $final_query_rows = '(' . $parentbean->create_list_count_query($tmp_final_query, $parameters) . ')';
  2467. $final_query = '(' . $tmp_final_query . ')';
  2468. $first = false;
  2469. }
  2470. }
  2471. }
  2472. //If final_query is still empty, its time to build the sub-queries
  2473. if (empty($final_query))
  2474. {
  2475. $subqueries = SugarBean::build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by);
  2476. $all_fields = array();
  2477. foreach($subqueries as $i => $subquery)
  2478. {
  2479. $query_fields = $GLOBALS['db']->getSelectFieldsFromQuery($subquery['select']);
  2480. foreach($query_fields as $field => $select)
  2481. {
  2482. if (!in_array($field, $all_fields))
  2483. $all_fields[] = $field;
  2484. }
  2485. $subqueries[$i]['query_fields'] = $query_fields;
  2486. }
  2487. $first = true;
  2488. //Now ensure the queries have the same set of fields in the same order.
  2489. foreach($subqueries as $subquery)
  2490. {
  2491. $subquery['select'] = "SELECT";
  2492. foreach($all_fields as $field)
  2493. {
  2494. if (!isset($subquery['query_fields'][$field]))
  2495. {
  2496. $subquery['select'] .= " ' ' $field,";
  2497. }
  2498. else
  2499. {
  2500. $subquery['select'] .= " {$subquery['query_fields'][$field]},";
  2501. }
  2502. }
  2503. $subquery['select'] = substr($subquery['select'], 0 , strlen($subquery['select']) - 1);
  2504. //Put the query into the final_query
  2505. $query = $subquery['select'] . " " . $subquery['from'] . " " . $subquery['where'];
  2506. if(!$first)
  2507. {
  2508. $query = ' UNION ALL ( '.$query . ' )';
  2509. $final_query_rows .= " UNION ALL ";
  2510. } else {
  2511. $query = '(' . $query . ')';
  2512. $first = false;
  2513. }
  2514. $query_array = $subquery['query_array'];
  2515. $select_position=strpos($query_array['select'],"SELECT");
  2516. $distinct_position=strpos($query_array['select'],"DISTINCT");
  2517. if (!empty($subquery['params']['distinct']) && !empty($subpanel_def->table_name))
  2518. {
  2519. $query_rows = "( SELECT count(DISTINCT ". $subpanel_def->table_name . ".id)". $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
  2520. }
  2521. elseif ($select_position !== false && $distinct_position!= false)
  2522. {
  2523. $query_rows = "( ".substr_replace($query_array['select'],"SELECT count(",$select_position,6). ")" . $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
  2524. }
  2525. else
  2526. {
  2527. //resort to default behavior.
  2528. $query_rows = "( SELECT count(*)". $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
  2529. }
  2530. if(!empty($subquery['secondary_select']))
  2531. {
  2532. $subquerystring= $subquery['secondary_select'] . $subquery['secondary_from'].$query_array['join']. $subquery['where'];
  2533. if (!empty($subquery['secondary_where']))
  2534. {
  2535. if (empty($subquery['where']))
  2536. {
  2537. $subquerystring.=" WHERE " .$subquery['secondary_where'];
  2538. }
  2539. else
  2540. {
  2541. $subquerystring.=" AND " .$subquery['secondary_where'];
  2542. }
  2543. }
  2544. $secondary_queries[]=$subquerystring;
  2545. }
  2546. $final_query .= $query;
  2547. $final_query_rows .= $query_rows;
  2548. }
  2549. }
  2550. if(!empty($order_by))
  2551. {
  2552. $submodule = false;
  2553. if(!$subpanel_def->isCollection())
  2554. {
  2555. $submodulename = $subpanel_def->_instance_properties['module'];
  2556. $submoduleclass = $beanList[$submodulename];
  2557. $submodule = new $submoduleclass();
  2558. }
  2559. if(!empty($submodule) && !empty($submodule->table_name))
  2560. {
  2561. $final_query .= " ORDER BY " .$parentbean->process_order_by($order_by, $submodule);
  2562. }
  2563. else
  2564. {
  2565. $final_query .= " ORDER BY ". $order_by . ' ';
  2566. }
  2567. if(!empty($sort_order))
  2568. {
  2569. $final_query .= ' ' .$sort_order;
  2570. }
  2571. }
  2572. if(isset($layout_edit_mode) && $layout_edit_mode)
  2573. {
  2574. $response = array();
  2575. if(!empty($submodule))
  2576. {
  2577. $submodule->assign_display_fields($submodule->module_dir);
  2578. $response['list'] = array($submodule);
  2579. }
  2580. else
  2581. {
  2582. $response['list'] = array();
  2583. }
  2584. $response['parent_data'] = array();
  2585. $response['row_count'] = 1;
  2586. $response['next_offset'] = 0;
  2587. $response['previous_offset'] = 0;
  2588. return $response;
  2589. }
  2590. return $parentbean->process_union_list_query($parentbean, $final_query, $row_offset, $limit, $max, '',$subpanel_def, $final_query_rows, $secondary_queries);
  2591. }
  2592. /**
  2593. * Returns a full (ie non-paged) list of the current object type.
  2594. *
  2595. * @param string $order_by the order by SQL parameter. defaults to ""
  2596. * @param string $where where clause. defaults to ""
  2597. * @param boolean $check_dates. defaults to false
  2598. * @param int $show_deleted show deleted records. defaults to 0
  2599. */
  2600. function get_full_list($order_by = "", $where = "", $check_dates=false, $show_deleted = 0)
  2601. {
  2602. $GLOBALS['log']->debug("get_full_list: order_by = '$order_by' and where = '$where'");
  2603. if(isset($_SESSION['show_deleted']))
  2604. {
  2605. $show_deleted = 1;
  2606. }
  2607. $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted);
  2608. return $this->process_full_list_query($query, $check_dates);
  2609. }
  2610. /**
  2611. * Return the list query used by the list views and export button. Next generation of create_new_list_query function.
  2612. *
  2613. * Override this function to return a custom query.
  2614. *
  2615. * @param string $order_by custom order by clause
  2616. * @param string $where custom where clause
  2617. * @param array $filter Optioanal
  2618. * @param array $params Optional *
  2619. * @param int $show_deleted Optional, default 0, show deleted records is set to 1.
  2620. * @param string $join_type
  2621. * @param boolean $return_array Optional, default false, response as array
  2622. * @param object $parentbean creating a subquery for this bean.
  2623. * @param boolean $singleSelect Optional, default false.
  2624. * @return String select query string, optionally an array value will be returned if $return_array= true.
  2625. */
  2626. function create_new_list_query($order_by, $where,$filter=array(),$params=array(), $show_deleted = 0,$join_type='', $return_array = false,$parentbean=null, $singleSelect = false, $ifListForExport = false)
  2627. {
  2628. global $beanFiles, $beanList;
  2629. $selectedFields = array();
  2630. $secondarySelectedFields = array();
  2631. $ret_array = array();
  2632. $distinct = '';
  2633. if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
  2634. {
  2635. global $current_user;
  2636. $owner_where = $this->getOwnerWhere($current_user->id);
  2637. if(empty($where))
  2638. {
  2639. $where = $owner_where;
  2640. }
  2641. else
  2642. {
  2643. $where .= ' AND '. $owner_where;
  2644. }
  2645. }
  2646. if(!empty($params['distinct']))
  2647. {
  2648. $distinct = ' DISTINCT ';
  2649. }
  2650. if(empty($filter))
  2651. {
  2652. $ret_array['select'] = " SELECT $distinct $this->table_name.* ";
  2653. }
  2654. else
  2655. {
  2656. $ret_array['select'] = " SELECT $distinct $this->table_name.id ";
  2657. }
  2658. $ret_array['from'] = " FROM $this->table_name ";
  2659. $ret_array['from_min'] = $ret_array['from'];
  2660. $ret_array['secondary_from'] = $ret_array['from'] ;
  2661. $ret_array['where'] = '';
  2662. $ret_array['order_by'] = '';
  2663. //secondary selects are selects that need to be run after the primary query to retrieve additional info on main
  2664. if($singleSelect)
  2665. {
  2666. $ret_array['secondary_select']=& $ret_array['select'];
  2667. $ret_array['secondary_from'] = & $ret_array['from'];
  2668. }
  2669. else
  2670. {
  2671. $ret_array['secondary_select'] = '';
  2672. }
  2673. $custom_join = false;
  2674. if((!isset($params['include_custom_fields']) || $params['include_custom_fields']) && isset($this->custom_fields))
  2675. {
  2676. $custom_join = $this->custom_fields->getJOIN( empty($filter)? true: $filter );
  2677. if($custom_join)
  2678. {
  2679. $ret_array['select'] .= ' ' .$custom_join['select'];
  2680. }
  2681. }
  2682. if($custom_join)
  2683. {
  2684. $ret_array['from'] .= ' ' . $custom_join['join'];
  2685. // Bug 52490 - Captivea (Sve) - To be able to add custom fields inside where clause in a subpanel
  2686. $ret_array['from_min'] .= ' ' . $custom_join['join'];
  2687. }
  2688. $jtcount = 0;
  2689. //LOOP AROUND FOR FIXIN VARDEF ISSUES
  2690. require('include/VarDefHandler/listvardefoverride.php');
  2691. if (file_exists('custom/include/VarDefHandler/listvardefoverride.php'))
  2692. {
  2693. require('custom/include/VarDefHandler/listvardefoverride.php');
  2694. }
  2695. $joined_tables = array();
  2696. if(!empty($params['joined_tables']))
  2697. {
  2698. foreach($params['joined_tables'] as $table)
  2699. {
  2700. $joined_tables[$table] = 1;
  2701. }
  2702. }
  2703. if(!empty($filter))
  2704. {
  2705. $filterKeys = array_keys($filter);
  2706. if(is_numeric($filterKeys[0]))
  2707. {
  2708. $fields = array();
  2709. foreach($filter as $field)
  2710. {
  2711. $field = strtolower($field);
  2712. //remove out id field so we don't duplicate it
  2713. if ( $field == 'id' && !empty($filter) ) {
  2714. continue;
  2715. }
  2716. if(isset($this->field_defs[$field]))
  2717. {
  2718. $fields[$field]= $this->field_defs[$field];
  2719. }
  2720. else
  2721. {
  2722. $fields[$field] = array('force_exists'=>true);
  2723. }
  2724. }
  2725. }else{
  2726. $fields = $filter;
  2727. }
  2728. }
  2729. else
  2730. {
  2731. $fields = $this->field_defs;
  2732. }
  2733. $used_join_key = array();
  2734. foreach($fields as $field=>$value)
  2735. {
  2736. //alias is used to alias field names
  2737. $alias='';
  2738. if (isset($value['alias']))
  2739. {
  2740. $alias =' as ' . $value['alias'] . ' ';
  2741. }
  2742. if(empty($this->field_defs[$field]) || !empty($value['force_blank']) )
  2743. {
  2744. if(!empty($filter) && isset($filter[$field]['force_exists']) && $filter[$field]['force_exists'])
  2745. {
  2746. if ( isset($filter[$field]['force_default']) )
  2747. $ret_array['select'] .= ", {$filter[$field]['force_default']} $field ";
  2748. else
  2749. //spaces are a fix for length issue problem with unions. The union only returns the maximum number of characters from the first select statement.
  2750. $ret_array['select'] .= ", ' ' $field ";
  2751. }
  2752. continue;
  2753. }
  2754. else
  2755. {
  2756. $data = $this->field_defs[$field];
  2757. }
  2758. //ignore fields that are a part of the collection and a field has been removed as a result of
  2759. //layout customization.. this happens in subpanel customizations, use case, from the contacts subpanel
  2760. //in opportunities module remove the contact_role/opportunity_role field.
  2761. $process_field=true;
  2762. if (isset($data['relationship_fields']) and !empty($data['relationship_fields']))
  2763. {
  2764. foreach ($data['relationship_fields'] as $field_name)
  2765. {
  2766. if (!isset($fields[$field_name]))
  2767. {
  2768. $process_field=false;
  2769. }
  2770. }
  2771. }
  2772. if (!$process_field)
  2773. {
  2774. continue;
  2775. }
  2776. if( (!isset($data['source']) || $data['source'] == 'db') && (!empty($alias) || !empty($filter) ))
  2777. {
  2778. $ret_array['select'] .= ", $this->table_name.$field $alias";
  2779. $selectedFields["$this->table_name.$field"] = true;
  2780. } else if( (!isset($data['source']) || $data['source'] == 'custom_fields') && (!empty($alias) || !empty($filter) )) {
  2781. //add this column only if it has NOT already been added to select statement string
  2782. $colPos = strpos($ret_array['select'],"$this->table_name"."_cstm".".$field");
  2783. if(!$colPos || $colPos<0)
  2784. {
  2785. $ret_array['select'] .= ", $this->table_name"."_cstm".".$field $alias";
  2786. }
  2787. $selectedFields["$this->table_name.$field"] = true;
  2788. }
  2789. if($data['type'] != 'relate' && isset($data['db_concat_fields']))
  2790. {
  2791. $ret_array['select'] .= ", " . $this->db->concat($this->table_name, $data['db_concat_fields']) . " as $field";
  2792. $selectedFields[$this->db->concat($this->table_name, $data['db_concat_fields'])] = true;
  2793. }
  2794. //Custom relate field or relate fields built in module builder which have no link field associated.
  2795. if ($data['type'] == 'relate' && (isset($data['custom_module']) || isset($data['ext2']))) {
  2796. $joinTableAlias = 'jt' . $jtcount;
  2797. $relateJoinInfo = $this->custom_fields->getRelateJoin($data, $joinTableAlias);
  2798. $ret_array['select'] .= $relateJoinInfo['select'];
  2799. $ret_array['from'] .= $relateJoinInfo['from'];
  2800. //Replace any references to the relationship in the where clause with the new alias
  2801. //If the link isn't set, assume that search used the local table for the field
  2802. $searchTable = isset($data['link']) ? $relateJoinInfo['rel_table'] : $this->table_name;
  2803. $field_name = $relateJoinInfo['rel_table'] . '.' . !empty($data['name'])?$data['name']:'name';
  2804. $where = preg_replace('/(^|[\s(])' . $field_name . '/' , '${1}' . $relateJoinInfo['name_field'], $where);
  2805. $jtcount++;
  2806. }
  2807. //Parent Field
  2808. if ($data['type'] == 'parent') {
  2809. //See if we need to join anything by inspecting the where clause
  2810. $match = preg_match('/(^|[\s(])parent_(\w+)_(\w+)\.name/', $where, $matches);
  2811. if ($match) {
  2812. $joinTableAlias = 'jt' . $jtcount;
  2813. $joinModule = $matches[2];
  2814. $joinTable = $matches[3];
  2815. $localTable = $this->table_name;
  2816. if (!empty($data['custom_module'])) {
  2817. $localTable .= '_cstm';
  2818. }
  2819. global $beanFiles, $beanList, $module;
  2820. require_once($beanFiles[$beanList[$joinModule]]);
  2821. $rel_mod = new $beanList[$joinModule]();
  2822. $nameField = "$joinTableAlias.name";
  2823. if (isset($rel_mod->field_defs['name']))
  2824. {
  2825. $name_field_def = $rel_mod->field_defs['name'];
  2826. if(isset($name_field_def['db_concat_fields']))
  2827. {
  2828. $nameField = $this->db->concat($joinTableAlias, $name_field_def['db_concat_fields']);
  2829. }
  2830. }
  2831. $ret_array['select'] .= ", $nameField {$data['name']} ";
  2832. $ret_array['from'] .= " LEFT JOIN $joinTable $joinTableAlias
  2833. ON $localTable.{$data['id_name']} = $joinTableAlias.id";
  2834. //Replace any references to the relationship in the where clause with the new alias
  2835. $where = preg_replace('/(^|[\s(])parent_' . $joinModule . '_' . $joinTable . '\.name/', '${1}' . $nameField, $where);
  2836. $jtcount++;
  2837. }
  2838. }
  2839. if($data['type'] == 'relate' && isset($data['link']))
  2840. {
  2841. $this->load_relationship($data['link']);
  2842. if(!empty($this->$data['link']))
  2843. {
  2844. $params = array();
  2845. if(empty($join_type))
  2846. {
  2847. $params['join_type'] = ' LEFT JOIN ';
  2848. }
  2849. else
  2850. {
  2851. $params['join_type'] = $join_type;
  2852. }
  2853. if(isset($data['join_name']))
  2854. {
  2855. $params['join_table_alias'] = $data['join_name'];
  2856. }
  2857. else
  2858. {
  2859. $params['join_table_alias'] = 'jt' . $jtcount;
  2860. }
  2861. if(isset($data['join_link_name']))
  2862. {
  2863. $params['join_table_link_alias'] = $data['join_link_name'];
  2864. }
  2865. else
  2866. {
  2867. $params['join_table_link_alias'] = 'jtl' . $jtcount;
  2868. }
  2869. $join_primary = !isset($data['join_primary']) || $data['join_primary'];
  2870. $join = $this->$data['link']->getJoin($params, true);
  2871. $used_join_key[] = $join['rel_key'];
  2872. $rel_module = $this->$data['link']->getRelatedModuleName();
  2873. $table_joined = !empty($joined_tables[$params['join_table_alias']]) || (!empty($joined_tables[$params['join_table_link_alias']]) && isset($data['link_type']) && $data['link_type'] == 'relationship_info');
  2874. //if rname is set to 'name', and bean files exist, then check if field should be a concatenated name
  2875. global $beanFiles, $beanList;
  2876. if($data['rname'] && !empty($beanFiles[$beanList[$rel_module]])) {
  2877. //create an instance of the related bean
  2878. require_once($beanFiles[$beanList[$rel_module]]);
  2879. $rel_mod = new $beanList[$rel_module]();
  2880. //if bean has first and last name fields, then name should be concatenated
  2881. if(isset($rel_mod->field_name_map['first_name']) && isset($rel_mod->field_name_map['last_name'])){
  2882. $data['db_concat_fields'] = array(0=>'first_name', 1=>'last_name');
  2883. }
  2884. }
  2885. if($join['type'] == 'many-to-many')
  2886. {
  2887. if(empty($ret_array['secondary_select']))
  2888. {
  2889. $ret_array['secondary_select'] = " SELECT $this->table_name.id ref_id ";
  2890. if(!empty($beanFiles[$beanList[$rel_module]]) && $join_primary)
  2891. {
  2892. require_once($beanFiles[$beanList[$rel_module]]);
  2893. $rel_mod = new $beanList[$rel_module]();
  2894. if(isset($rel_mod->field_defs['assigned_user_id']))
  2895. {
  2896. $ret_array['secondary_select'].= " , ". $params['join_table_alias'] . ".assigned_user_id {$field}_owner, '$rel_module' {$field}_mod";
  2897. }
  2898. else
  2899. {
  2900. if(isset($rel_mod->field_defs['created_by']))
  2901. {
  2902. $ret_array['secondary_select'].= " , ". $params['join_table_alias'] . ".created_by {$field}_owner , '$rel_module' {$field}_mod";
  2903. }
  2904. }
  2905. }
  2906. }
  2907. if(isset($data['db_concat_fields']))
  2908. {
  2909. $ret_array['secondary_select'] .= ' , ' . $this->db->concat($params['join_table_alias'], $data['db_concat_fields']) . ' ' . $field;
  2910. }
  2911. else
  2912. {
  2913. if(!isset($data['relationship_fields']))
  2914. {
  2915. $ret_array['secondary_select'] .= ' , ' . $params['join_table_alias'] . '.' . $data['rname'] . ' ' . $field;
  2916. }
  2917. }
  2918. if(!$singleSelect)
  2919. {
  2920. $ret_array['select'] .= ", ' ' $field ";
  2921. }
  2922. $count_used =0;
  2923. foreach($used_join_key as $used_key) {
  2924. if($used_key == $join['rel_key']) $count_used++;
  2925. }
  2926. if($count_used <= 1) {//27416, the $ret_array['secondary_select'] should always generate, regardless the dbtype
  2927. // add rel_key only if it was not aready added
  2928. if(!$singleSelect)
  2929. {
  2930. $ret_array['select'] .= ", ' ' " . $join['rel_key'] . ' ';
  2931. }
  2932. $ret_array['secondary_select'] .= ', ' . $params['join_table_link_alias'].'.'. $join['rel_key'] .' ' . $join['rel_key'];
  2933. }
  2934. if(isset($data['relationship_fields']))
  2935. {
  2936. foreach($data['relationship_fields'] as $r_name=>$alias_name)
  2937. {
  2938. if(!empty( $secondarySelectedFields[$alias_name]))continue;
  2939. $ret_array['secondary_select'] .= ', ' . $params['join_table_link_alias'].'.'. $r_name .' ' . $alias_name;
  2940. $secondarySelectedFields[$alias_name] = true;
  2941. }
  2942. }
  2943. if(!$table_joined)
  2944. {
  2945. $ret_array['secondary_from'] .= ' ' . $join['join']. ' AND ' . $params['join_table_alias'].'.deleted=0';
  2946. if (isset($data['link_type']) && $data['link_type'] == 'relationship_info' && ($parentbean instanceOf SugarBean))
  2947. {
  2948. $ret_array['secondary_where'] = $params['join_table_link_alias'] . '.' . $join['rel_key']. "='" .$parentbean->id . "'";
  2949. }
  2950. }
  2951. }
  2952. else
  2953. {
  2954. if(isset($data['db_concat_fields']))
  2955. {
  2956. $ret_array['select'] .= ' , ' . $this->db->concat($params['join_table_alias'], $data['db_concat_fields']) . ' ' . $field;
  2957. }
  2958. else
  2959. {
  2960. $ret_array['select'] .= ' , ' . $params['join_table_alias'] . '.' . $data['rname'] . ' ' . $field;
  2961. }
  2962. if(isset($data['additionalFields'])){
  2963. foreach($data['additionalFields'] as $k=>$v){
  2964. $ret_array['select'] .= ' , ' . $params['join_table_alias'] . '.' . $k . ' ' . $v;
  2965. }
  2966. }
  2967. if(!$table_joined)
  2968. {
  2969. $ret_array['from'] .= ' ' . $join['join']. ' AND ' . $params['join_table_alias'].'.deleted=0';
  2970. if(!empty($beanList[$rel_module]) && !empty($beanFiles[$beanList[$rel_module]]))
  2971. {
  2972. require_once($beanFiles[$beanList[$rel_module]]);
  2973. $rel_mod = new $beanList[$rel_module]();
  2974. if(isset($value['target_record_key']) && !empty($filter))
  2975. {
  2976. $selectedFields[$this->table_name.'.'.$value['target_record_key']] = true;
  2977. $ret_array['select'] .= " , $this->table_name.{$value['target_record_key']} ";
  2978. }
  2979. if(isset($rel_mod->field_defs['assigned_user_id']))
  2980. {
  2981. $ret_array['select'] .= ' , ' .$params['join_table_alias'] . '.assigned_user_id ' . $field . '_owner';
  2982. }
  2983. else
  2984. {
  2985. $ret_array['select'] .= ' , ' .$params['join_table_alias'] . '.created_by ' . $field . '_owner';
  2986. }
  2987. $ret_array['select'] .= " , '".$rel_module ."' " . $field . '_mod';
  2988. }
  2989. }
  2990. }
  2991. // To fix SOAP stuff where we are trying to retrieve all the accounts data where accounts.id = ..
  2992. // and this code changes accounts to jt4 as there is a self join with the accounts table.
  2993. //Martin fix #27494
  2994. if(isset($data['db_concat_fields'])){
  2995. $buildWhere = false;
  2996. if(in_array('first_name', $data['db_concat_fields']) && in_array('last_name', $data['db_concat_fields']))
  2997. {
  2998. $exp = '/\(\s*?'.$data['name'].'.*?\%\'\s*?\)/';
  2999. if(preg_match($exp, $where, $matches))
  3000. {
  3001. $search_expression = $matches[0];
  3002. //Create three search conditions - first + last, first, last
  3003. $first_name_search = str_replace($data['name'], $params['join_table_alias'] . '.first_name', $search_expression);
  3004. $last_name_search = str_replace($data['name'], $params['join_table_alias'] . '.last_name', $search_expression);
  3005. $full_name_search = str_replace($data['name'], $this->db->concat($params['join_table_alias'], $data['db_concat_fields']), $search_expression);
  3006. $buildWhere = true;
  3007. $where = str_replace($search_expression, '(' . $full_name_search . ' OR ' . $first_name_search . ' OR ' . $last_name_search . ')', $where);
  3008. }
  3009. }
  3010. if(!$buildWhere)
  3011. {
  3012. $db_field = $this->db->concat($params['join_table_alias'], $data['db_concat_fields']);
  3013. $where = preg_replace('/'.$data['name'].'/', $db_field, $where);
  3014. }
  3015. }else{
  3016. $where = preg_replace('/(^|[\s(])' . $data['name'] . '/', '${1}' . $params['join_table_alias'] . '.'.$data['rname'], $where);
  3017. }
  3018. if(!$table_joined)
  3019. {
  3020. $joined_tables[$params['join_table_alias']]=1;
  3021. $joined_tables[$params['join_table_link_alias']]=1;
  3022. }
  3023. $jtcount++;
  3024. }
  3025. }
  3026. }
  3027. if(!empty($filter))
  3028. {
  3029. if(isset($this->field_defs['assigned_user_id']) && empty($selectedFields[$this->table_name.'.assigned_user_id']))
  3030. {
  3031. $ret_array['select'] .= ", $this->table_name.assigned_user_id ";
  3032. }
  3033. else if(isset($this->field_defs['created_by']) && empty($selectedFields[$this->table_name.'.created_by']))
  3034. {
  3035. $ret_array['select'] .= ", $this->table_name.created_by ";
  3036. }
  3037. if(isset($this->field_defs['system_id']) && empty($selectedFields[$this->table_name.'.system_id']))
  3038. {
  3039. $ret_array['select'] .= ", $this->table_name.system_id ";
  3040. }
  3041. }
  3042. if ($ifListForExport) {
  3043. if(isset($this->field_defs['email1'])) {
  3044. $ret_array['select'].= " ,email_addresses.email_address email1";
  3045. $ret_array['from'].= " LEFT JOIN email_addr_bean_rel on {$this->table_name}.id = email_addr_bean_rel.bean_id and email_addr_bean_rel.bean_module='{$this->module_dir}' and email_addr_bean_rel.deleted=0 and email_addr_bean_rel.primary_address=1 LEFT JOIN email_addresses on email_addresses.id = email_addr_bean_rel.email_address_id ";
  3046. }
  3047. }
  3048. $where_auto = '1=1';
  3049. if($show_deleted == 0)
  3050. {
  3051. $where_auto = "$this->table_name.deleted=0";
  3052. }else if($show_deleted == 1)
  3053. {
  3054. $where_auto = "$this->table_name.deleted=1";
  3055. }
  3056. if($where != "")
  3057. $ret_array['where'] = " where ($where) AND $where_auto";
  3058. else
  3059. $ret_array['where'] = " where $where_auto";
  3060. if(!empty($order_by))
  3061. {
  3062. //make call to process the order by clause
  3063. $ret_array['order_by'] = " ORDER BY ". $this->process_order_by($order_by);
  3064. }
  3065. if($singleSelect)
  3066. {
  3067. unset($ret_array['secondary_where']);
  3068. unset($ret_array['secondary_from']);
  3069. unset($ret_array['secondary_select']);
  3070. }
  3071. if($return_array)
  3072. {
  3073. return $ret_array;
  3074. }
  3075. return $ret_array['select'] . $ret_array['from'] . $ret_array['where']. $ret_array['order_by'];
  3076. }
  3077. /**
  3078. * Returns parent record data for objects that store relationship information
  3079. *
  3080. * @param array $type_info
  3081. *
  3082. * Interal function, do not override.
  3083. */
  3084. function retrieve_parent_fields($type_info)
  3085. {
  3086. $queries = array();
  3087. global $beanList, $beanFiles;
  3088. $templates = array();
  3089. $parent_child_map = array();
  3090. foreach($type_info as $children_info)
  3091. {
  3092. foreach($children_info as $child_info)
  3093. {
  3094. if($child_info['type'] == 'parent')
  3095. {
  3096. if(empty($templates[$child_info['parent_type']]))
  3097. {
  3098. //Test emails will have an invalid parent_type, don't try to load the non-existent parent bean
  3099. if ($child_info['parent_type'] == 'test') {
  3100. continue;
  3101. }
  3102. $class = $beanList[$child_info['parent_type']];
  3103. // Added to avoid error below; just silently fail and write message to log
  3104. if ( empty($beanFiles[$class]) ) {
  3105. $GLOBALS['log']->error($this->object_name.'::retrieve_parent_fields() - cannot load class "'.$class.'", skip loading.');
  3106. continue;
  3107. }
  3108. require_once($beanFiles[$class]);
  3109. $templates[$child_info['parent_type']] = new $class();
  3110. }
  3111. if(empty($queries[$child_info['parent_type']]))
  3112. {
  3113. $queries[$child_info['parent_type']] = "SELECT id ";
  3114. $field_def = $templates[$child_info['parent_type']]->field_defs['name'];
  3115. if(isset($field_def['db_concat_fields']))
  3116. {
  3117. $queries[$child_info['parent_type']] .= ' , ' . $this->db->concat($templates[$child_info['parent_type']]->table_name, $field_def['db_concat_fields']) . ' parent_name';
  3118. }
  3119. else
  3120. {
  3121. $queries[$child_info['parent_type']] .= ' , name parent_name';
  3122. }
  3123. if(isset($templates[$child_info['parent_type']]->field_defs['assigned_user_id']))
  3124. {
  3125. $queries[$child_info['parent_type']] .= ", assigned_user_id parent_name_owner , '{$child_info['parent_type']}' parent_name_mod";;
  3126. }else if(isset($templates[$child_info['parent_type']]->field_defs['created_by']))
  3127. {
  3128. $queries[$child_info['parent_type']] .= ", created_by parent_name_owner, '{$child_info['parent_type']}' parent_name_mod";
  3129. }
  3130. $queries[$child_info['parent_type']] .= " FROM " . $templates[$child_info['parent_type']]->table_name ." WHERE id IN ('{$child_info['parent_id']}'";
  3131. }
  3132. else
  3133. {
  3134. if(empty($parent_child_map[$child_info['parent_id']]))
  3135. $queries[$child_info['parent_type']] .= " ,'{$child_info['parent_id']}'";
  3136. }
  3137. $parent_child_map[$child_info['parent_id']][] = $child_info['child_id'];
  3138. }
  3139. }
  3140. }
  3141. $results = array();
  3142. foreach($queries as $query)
  3143. {
  3144. $result = $this->db->query($query . ')');
  3145. while($row = $this->db->fetchByAssoc($result))
  3146. {
  3147. $results[$row['id']] = $row;
  3148. }
  3149. }
  3150. $child_results = array();
  3151. foreach($parent_child_map as $parent_key=>$parent_child)
  3152. {
  3153. foreach($parent_child as $child)
  3154. {
  3155. if(isset( $results[$parent_key]))
  3156. {
  3157. $child_results[$child] = $results[$parent_key];
  3158. }
  3159. }
  3160. }
  3161. return $child_results;
  3162. }
  3163. /**
  3164. * Processes the list query and return fetched row.
  3165. *
  3166. * Internal function, do not override.
  3167. * @param string $query select query to be processed.
  3168. * @param int $row_offset starting position
  3169. * @param int $limit Optioanl, default -1
  3170. * @param int $max_per_page Optional, default -1
  3171. * @param string $where Optional, additional filter criteria.
  3172. * @return array Fetched data
  3173. */
  3174. function process_list_query($query, $row_offset, $limit= -1, $max_per_page = -1, $where = '')
  3175. {
  3176. global $sugar_config;
  3177. $db = DBManagerFactory::getInstance('listviews');
  3178. /**
  3179. * if the row_offset is set to 'end' go to the end of the list
  3180. */
  3181. $toEnd = strval($row_offset) == 'end';
  3182. $GLOBALS['log']->debug("process_list_query: ".$query);
  3183. if($max_per_page == -1)
  3184. {
  3185. $max_per_page = $sugar_config['list_max_entries_per_page'];
  3186. }
  3187. // Check to see if we have a count query available.
  3188. if(empty($sugar_config['disable_count_query']) || $toEnd)
  3189. {
  3190. $count_query = $this->create_list_count_query($query);
  3191. if(!empty($count_query) && (empty($limit) || $limit == -1))
  3192. {
  3193. // We have a count query. Run it and get the results.
  3194. $result = $db->query($count_query, true, "Error running count query for $this->object_name List: ");
  3195. $assoc = $db->fetchByAssoc($result);
  3196. if(!empty($assoc['c']))
  3197. {
  3198. $rows_found = $assoc['c'];
  3199. $limit = $sugar_config['list_max_entries_per_page'];
  3200. }
  3201. if( $toEnd)
  3202. {
  3203. $row_offset = (floor(($rows_found -1) / $limit)) * $limit;
  3204. }
  3205. }
  3206. }
  3207. else
  3208. {
  3209. if((empty($limit) || $limit == -1))
  3210. {
  3211. $limit = $max_per_page + 1;
  3212. $max_per_page = $limit;
  3213. }
  3214. }
  3215. if(empty($row_offset))
  3216. {
  3217. $row_offset = 0;
  3218. }
  3219. if(!empty($limit) && $limit != -1 && $limit != -99)
  3220. {
  3221. $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $this->object_name list: ");
  3222. }
  3223. else
  3224. {
  3225. $result = $db->query($query,true,"Error retrieving $this->object_name list: ");
  3226. }
  3227. $list = Array();
  3228. $previous_offset = $row_offset - $max_per_page;
  3229. $next_offset = $row_offset + $max_per_page;
  3230. $class = get_class($this);
  3231. //FIXME: Bug? we should remove the magic number -99
  3232. //use -99 to return all
  3233. $index = $row_offset;
  3234. while ($max_per_page == -99 || ($index < $row_offset + $max_per_page))
  3235. {
  3236. $row = $db->fetchByAssoc($result);
  3237. if (empty($row)) break;
  3238. //instantiate a new class each time. This is because php5 passes
  3239. //by reference by default so if we continually update $this, we will
  3240. //at the end have a list of all the same objects
  3241. $temp = new $class();
  3242. foreach($this->field_defs as $field=>$value)
  3243. {
  3244. if (isset($row[$field]))
  3245. {
  3246. $temp->$field = $row[$field];
  3247. $owner_field = $field . '_owner';
  3248. if(isset($row[$owner_field]))
  3249. {
  3250. $temp->$owner_field = $row[$owner_field];
  3251. }
  3252. $GLOBALS['log']->debug("$temp->object_name({$row['id']}): ".$field." = ".$temp->$field);
  3253. }else if (isset($row[$this->table_name .'.'.$field]))
  3254. {
  3255. $temp->$field = $row[$this->table_name .'.'.$field];
  3256. }
  3257. else
  3258. {
  3259. $temp->$field = "";
  3260. }
  3261. }
  3262. $temp->check_date_relationships_load();
  3263. $temp->fill_in_additional_list_fields();
  3264. if($temp->hasCustomFields()) $temp->custom_fields->fill_relationships();
  3265. $temp->call_custom_logic("process_record");
  3266. // fix defect #44206. implement the same logic as sugar_currency_format
  3267. // Smarty modifier does.
  3268. if (property_exists($temp, 'currency_id') && -99 == $temp->currency_id)
  3269. {
  3270. // manually retrieve default currency object as long as it's
  3271. // not stored in database and thus cannot be joined in query
  3272. require_once 'modules/Currencies/Currency.php';
  3273. $currency = new Currency();
  3274. $currency->retrieve($temp->currency_id);
  3275. // walk through all currency-related fields
  3276. foreach ($temp->field_defs as $temp_field)
  3277. {
  3278. if (isset($temp_field['type']) && 'relate' == $temp_field['type']
  3279. && isset($temp_field['module']) && 'Currencies' == $temp_field['module']
  3280. && isset($temp_field['id_name']) && 'currency_id' == $temp_field['id_name'])
  3281. {
  3282. // populate related properties manually
  3283. $temp_property = $temp_field['name'];
  3284. $currency_property = $temp_field['rname'];
  3285. $temp->$temp_property = $currency->$currency_property;
  3286. }
  3287. }
  3288. }
  3289. $list[] = $temp;
  3290. $index++;
  3291. }
  3292. if(!empty($sugar_config['disable_count_query']) && !empty($limit))
  3293. {
  3294. $rows_found = $row_offset + count($list);
  3295. unset($list[$limit - 1]);
  3296. if(!$toEnd)
  3297. {
  3298. $next_offset--;
  3299. $previous_offset++;
  3300. }
  3301. } else if(!isset($rows_found)){
  3302. $rows_found = $row_offset + count($list);
  3303. }
  3304. $response = Array();
  3305. $response['list'] = $list;
  3306. $response['row_count'] = $rows_found;
  3307. $response['next_offset'] = $next_offset;
  3308. $response['previous_offset'] = $previous_offset;
  3309. $response['current_offset'] = $row_offset ;
  3310. return $response;
  3311. }
  3312. /**
  3313. * Returns the number of rows that the given SQL query should produce
  3314. *
  3315. * Internal function, do not override.
  3316. * @param string $query valid select query
  3317. * @param boolean $is_count_query Optional, Default false, set to true if passed query is a count query.
  3318. * @return int count of rows found
  3319. */
  3320. function _get_num_rows_in_query($query, $is_count_query=false)
  3321. {
  3322. $num_rows_in_query = 0;
  3323. if (!$is_count_query)
  3324. {
  3325. $count_query = SugarBean::create_list_count_query($query);
  3326. } else
  3327. $count_query=$query;
  3328. $result = $this->db->query($count_query, true, "Error running count query for $this->object_name List: ");
  3329. $row_num = 0;
  3330. while($row = $this->db->fetchByAssoc($result, true))
  3331. {
  3332. $num_rows_in_query += current($row);
  3333. }
  3334. return $num_rows_in_query;
  3335. }
  3336. /**
  3337. * Applies pagination window to union queries used by list view and subpanels,
  3338. * executes the query and returns fetched data.
  3339. *
  3340. * Internal function, do not override.
  3341. * @param object $parent_bean
  3342. * @param string $query query to be processed.
  3343. * @param int $row_offset
  3344. * @param int $limit optional, default -1
  3345. * @param int $max_per_page Optional, default -1
  3346. * @param string $where Custom where clause.
  3347. * @param array $subpanel_def definition of sub-panel to be processed
  3348. * @param string $query_row_count
  3349. * @param array $seconday_queries
  3350. * @return array Fetched data.
  3351. */
  3352. function process_union_list_query($parent_bean, $query,
  3353. $row_offset, $limit= -1, $max_per_page = -1, $where = '', $subpanel_def, $query_row_count='', $secondary_queries = array())
  3354. {
  3355. $db = DBManagerFactory::getInstance('listviews');
  3356. /**
  3357. * if the row_offset is set to 'end' go to the end of the list
  3358. */
  3359. $toEnd = strval($row_offset) == 'end';
  3360. global $sugar_config;
  3361. $use_count_query=false;
  3362. $processing_collection=$subpanel_def->isCollection();
  3363. $GLOBALS['log']->debug("process_union_list_query: ".$query);
  3364. if($max_per_page == -1)
  3365. {
  3366. $max_per_page = $sugar_config['list_max_entries_per_subpanel'];
  3367. }
  3368. if(empty($query_row_count))
  3369. {
  3370. $query_row_count = $query;
  3371. }
  3372. $distinct_position=strpos($query_row_count,"DISTINCT");
  3373. if ($distinct_position!= false)
  3374. {
  3375. $use_count_query=true;
  3376. }
  3377. $performSecondQuery = true;
  3378. if(empty($sugar_config['disable_count_query']) || $toEnd)
  3379. {
  3380. $rows_found = $this->_get_num_rows_in_query($query_row_count,$use_count_query);
  3381. if($rows_found < 1)
  3382. {
  3383. $performSecondQuery = false;
  3384. }
  3385. if(!empty($rows_found) && (empty($limit) || $limit == -1))
  3386. {
  3387. $limit = $sugar_config['list_max_entries_per_subpanel'];
  3388. }
  3389. if( $toEnd)
  3390. {
  3391. $row_offset = (floor(($rows_found -1) / $limit)) * $limit;
  3392. }
  3393. }
  3394. else
  3395. {
  3396. if((empty($limit) || $limit == -1))
  3397. {
  3398. $limit = $max_per_page + 1;
  3399. $max_per_page = $limit;
  3400. }
  3401. }
  3402. if(empty($row_offset))
  3403. {
  3404. $row_offset = 0;
  3405. }
  3406. $list = array();
  3407. $previous_offset = $row_offset - $max_per_page;
  3408. $next_offset = $row_offset + $max_per_page;
  3409. if($performSecondQuery)
  3410. {
  3411. if(!empty($limit) && $limit != -1 && $limit != -99)
  3412. {
  3413. $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $parent_bean->object_name list: ");
  3414. }
  3415. else
  3416. {
  3417. $result = $db->query($query,true,"Error retrieving $this->object_name list: ");
  3418. }
  3419. //use -99 to return all
  3420. // get the current row
  3421. $index = $row_offset;
  3422. $row = $db->fetchByAssoc($result);
  3423. $post_retrieve = array();
  3424. $isFirstTime = true;
  3425. while($row)
  3426. {
  3427. $function_fields = array();
  3428. if(($index < $row_offset + $max_per_page || $max_per_page == -99))
  3429. {
  3430. if ($processing_collection)
  3431. {
  3432. $current_bean =$subpanel_def->sub_subpanels[$row['panel_name']]->template_instance;
  3433. if(!$isFirstTime)
  3434. {
  3435. $class = get_class($subpanel_def->sub_subpanels[$row['panel_name']]->template_instance);
  3436. $current_bean = new $class();
  3437. }
  3438. } else {
  3439. $current_bean=$subpanel_def->template_instance;
  3440. if(!$isFirstTime)
  3441. {
  3442. $class = get_class($subpanel_def->template_instance);
  3443. $current_bean = new $class();
  3444. }
  3445. }
  3446. $isFirstTime = false;
  3447. //set the panel name in the bean instance.
  3448. if (isset($row['panel_name']))
  3449. {
  3450. $current_bean->panel_name=$row['panel_name'];
  3451. }
  3452. foreach($current_bean->field_defs as $field=>$value)
  3453. {
  3454. if (isset($row[$field]))
  3455. {
  3456. $current_bean->$field = $this->convertField($row[$field], $value);
  3457. unset($row[$field]);
  3458. }
  3459. else if (isset($row[$this->table_name .'.'.$field]))
  3460. {
  3461. $current_bean->$field = $this->convertField($row[$this->table_name .'.'.$field], $value);
  3462. unset($row[$this->table_name .'.'.$field]);
  3463. }
  3464. else
  3465. {
  3466. $current_bean->$field = "";
  3467. unset($row[$field]);
  3468. }
  3469. if(isset($value['source']) && $value['source'] == 'function')
  3470. {
  3471. $function_fields[]=$field;
  3472. }
  3473. }
  3474. foreach($row as $key=>$value)
  3475. {
  3476. $current_bean->$key = $value;
  3477. }
  3478. foreach($function_fields as $function_field)
  3479. {
  3480. $value = $current_bean->field_defs[$function_field];
  3481. $can_execute = true;
  3482. $execute_params = array();
  3483. $execute_function = array();
  3484. if(!empty($value['function_class']))
  3485. {
  3486. $execute_function[] = $value['function_class'];
  3487. $execute_function[] = $value['function_name'];
  3488. }
  3489. else
  3490. {
  3491. $execute_function = $value['function_name'];
  3492. }
  3493. foreach($value['function_params'] as $param )
  3494. {
  3495. if (empty($value['function_params_source']) or $value['function_params_source']=='parent')
  3496. {
  3497. if(empty($this->$param))
  3498. {
  3499. $can_execute = false;
  3500. } else if($param == '$this') {
  3501. $execute_params[] = $this;
  3502. }
  3503. else
  3504. {
  3505. $execute_params[] = $this->$param;
  3506. }
  3507. } else if ($value['function_params_source']=='this')
  3508. {
  3509. if(empty($current_bean->$param))
  3510. {
  3511. $can_execute = false;
  3512. } else if($param == '$this') {
  3513. $execute_params[] = $current_bean;
  3514. }
  3515. else
  3516. {
  3517. $execute_params[] = $current_bean->$param;
  3518. }
  3519. }
  3520. else
  3521. {
  3522. $can_execute = false;
  3523. }
  3524. }
  3525. if($can_execute)
  3526. {
  3527. if(!empty($value['function_require']))
  3528. {
  3529. require_once($value['function_require']);
  3530. }
  3531. $current_bean->$function_field = call_user_func_array($execute_function, $execute_params);
  3532. }
  3533. }
  3534. if(!empty($current_bean->parent_type) && !empty($current_bean->parent_id))
  3535. {
  3536. if(!isset($post_retrieve[$current_bean->parent_type]))
  3537. {
  3538. $post_retrieve[$current_bean->parent_type] = array();
  3539. }
  3540. $post_retrieve[$current_bean->parent_type][] = array('child_id'=>$current_bean->id, 'parent_id'=> $current_bean->parent_id, 'parent_type'=>$current_bean->parent_type, 'type'=>'parent');
  3541. }
  3542. //$current_bean->fill_in_additional_list_fields();
  3543. $list[$current_bean->id] = $current_bean;
  3544. }
  3545. // go to the next row
  3546. $index++;
  3547. $row = $db->fetchByAssoc($result);
  3548. }
  3549. //now handle retrieving many-to-many relationships
  3550. if(!empty($list))
  3551. {
  3552. foreach($secondary_queries as $query2)
  3553. {
  3554. $result2 = $db->query($query2);
  3555. $row2 = $db->fetchByAssoc($result2);
  3556. while($row2)
  3557. {
  3558. $id_ref = $row2['ref_id'];
  3559. if(isset($list[$id_ref]))
  3560. {
  3561. foreach($row2 as $r2key=>$r2value)
  3562. {
  3563. if($r2key != 'ref_id')
  3564. {
  3565. $list[$id_ref]->$r2key = $r2value;
  3566. }
  3567. }
  3568. }
  3569. $row2 = $db->fetchByAssoc($result2);
  3570. }
  3571. }
  3572. }
  3573. if(isset($post_retrieve))
  3574. {
  3575. $parent_fields = $this->retrieve_parent_fields($post_retrieve);
  3576. }
  3577. else
  3578. {
  3579. $parent_fields = array();
  3580. }
  3581. if(!empty($sugar_config['disable_count_query']) && !empty($limit))
  3582. {
  3583. //C.L. Bug 43535 - Use the $index value to set the $rows_found value here
  3584. $rows_found = isset($index) ? $index : $row_offset + count($list);
  3585. if(count($list) >= $limit)
  3586. {
  3587. array_pop($list);
  3588. }
  3589. if(!$toEnd)
  3590. {
  3591. $next_offset--;
  3592. $previous_offset++;
  3593. }
  3594. }
  3595. }
  3596. else
  3597. {
  3598. $row_found = 0;
  3599. $parent_fields = array();
  3600. }
  3601. $response = array();
  3602. $response['list'] = $list;
  3603. $response['parent_data'] = $parent_fields;
  3604. $response['row_count'] = $rows_found;
  3605. $response['next_offset'] = $next_offset;
  3606. $response['previous_offset'] = $previous_offset;
  3607. $response['current_offset'] = $row_offset ;
  3608. $response['query'] = $query;
  3609. return $response;
  3610. }
  3611. /**
  3612. * Applies pagination window to select queries used by detail view,
  3613. * executes the query and returns fetched data.
  3614. *
  3615. * Internal function, do not override.
  3616. * @param string $query query to be processed.
  3617. * @param int $row_offset
  3618. * @param int $limit optional, default -1
  3619. * @param int $max_per_page Optional, default -1
  3620. * @param string $where Custom where clause.
  3621. * @param int $offset Optional, default 0
  3622. * @return array Fetched data.
  3623. *
  3624. */
  3625. function process_detail_query($query, $row_offset, $limit= -1, $max_per_page = -1, $where = '', $offset = 0)
  3626. {
  3627. global $sugar_config;
  3628. $GLOBALS['log']->debug("process_detail_query: ".$query);
  3629. if($max_per_page == -1)
  3630. {
  3631. $max_per_page = $sugar_config['list_max_entries_per_page'];
  3632. }
  3633. // Check to see if we have a count query available.
  3634. $count_query = $this->create_list_count_query($query);
  3635. if(!empty($count_query) && (empty($limit) || $limit == -1))
  3636. {
  3637. // We have a count query. Run it and get the results.
  3638. $result = $this->db->query($count_query, true, "Error running count query for $this->object_name List: ");
  3639. $assoc = $this->db->fetchByAssoc($result);
  3640. if(!empty($assoc['c']))
  3641. {
  3642. $total_rows = $assoc['c'];
  3643. }
  3644. }
  3645. if(empty($row_offset))
  3646. {
  3647. $row_offset = 0;
  3648. }
  3649. $result = $this->db->limitQuery($query, $offset, 1, true,"Error retrieving $this->object_name list: ");
  3650. $previous_offset = $row_offset - $max_per_page;
  3651. $next_offset = $row_offset + $max_per_page;
  3652. $row = $this->db->fetchByAssoc($result);
  3653. $this->retrieve($row['id']);
  3654. $response = Array();
  3655. $response['bean'] = $this;
  3656. if (empty($total_rows))
  3657. $total_rows=0;
  3658. $response['row_count'] = $total_rows;
  3659. $response['next_offset'] = $next_offset;
  3660. $response['previous_offset'] = $previous_offset;
  3661. return $response;
  3662. }
  3663. /**
  3664. * Processes fetched list view data
  3665. *
  3666. * Internal function, do not override.
  3667. * @param string $query query to be processed.
  3668. * @param boolean $check_date Optional, default false. if set to true date time values are processed.
  3669. * @return array Fetched data.
  3670. *
  3671. */
  3672. function process_full_list_query($query, $check_date=false)
  3673. {
  3674. $GLOBALS['log']->debug("process_full_list_query: query is ".$query);
  3675. $result = $this->db->query($query, false);
  3676. $GLOBALS['log']->debug("process_full_list_query: result is ".print_r($result,true));
  3677. $class = get_class($this);
  3678. $isFirstTime = true;
  3679. $bean = new $class();
  3680. // We have some data.
  3681. while (($row = $bean->db->fetchByAssoc($result)) != null)
  3682. {
  3683. $row = $this->convertRow($row);
  3684. if(!$isFirstTime)
  3685. {
  3686. $bean = new $class();
  3687. }
  3688. $isFirstTime = false;
  3689. foreach($bean->field_defs as $field=>$value)
  3690. {
  3691. if (isset($row[$field]))
  3692. {
  3693. $bean->$field = $row[$field];
  3694. $GLOBALS['log']->debug("process_full_list: $bean->object_name({$row['id']}): ".$field." = ".$bean->$field);
  3695. }
  3696. else
  3697. {
  3698. $bean->$field = '';
  3699. }
  3700. }
  3701. if($check_date)
  3702. {
  3703. $bean->processed_dates_times = array();
  3704. $bean->check_date_relationships_load();
  3705. }
  3706. $bean->fill_in_additional_list_fields();
  3707. $bean->call_custom_logic("process_record");
  3708. $bean->fetched_row = $row;
  3709. $list[] = $bean;
  3710. }
  3711. //}
  3712. if (isset($list)) return $list;
  3713. else return null;
  3714. }
  3715. /**
  3716. * Tracks the viewing of a detail record.
  3717. * This leverages get_summary_text() which is object specific.
  3718. *
  3719. * Internal function, do not override.
  3720. * @param string $user_id - String value of the user that is viewing the record.
  3721. * @param string $current_module - String value of the module being processed.
  3722. * @param string $current_view - String value of the current view
  3723. */
  3724. function track_view($user_id, $current_module, $current_view='')
  3725. {
  3726. $trackerManager = TrackerManager::getInstance();
  3727. if($monitor = $trackerManager->getMonitor('tracker')){
  3728. $monitor->setValue('date_modified', $GLOBALS['timedate']->nowDb());
  3729. $monitor->setValue('user_id', $user_id);
  3730. $monitor->setValue('module_name', $current_module);
  3731. $monitor->setValue('action', $current_view);
  3732. $monitor->setValue('item_id', $this->id);
  3733. $monitor->setValue('item_summary', $this->get_summary_text());
  3734. $monitor->setValue('visible', $this->tracker_visibility);
  3735. $trackerManager->saveMonitor($monitor);
  3736. }
  3737. }
  3738. /**
  3739. * Returns the summary text that should show up in the recent history list for this object.
  3740. *
  3741. * @return string
  3742. */
  3743. public function get_summary_text()
  3744. {
  3745. return "Base Implementation. Should be overridden.";
  3746. }
  3747. /**
  3748. * This is designed to be overridden and add specific fields to each record.
  3749. * This allows the generic query to fill in the major fields, and then targeted
  3750. * queries to get related fields and add them to the record. The contact's
  3751. * account for instance. This method is only used for populating extra fields
  3752. * in lists.
  3753. */
  3754. function fill_in_additional_list_fields(){
  3755. if(!empty($this->field_defs['parent_name']) && empty($this->parent_name)){
  3756. $this->fill_in_additional_parent_fields();
  3757. }
  3758. }
  3759. /**
  3760. * This is designed to be overridden and add specific fields to each record.
  3761. * This allows the generic query to fill in the major fields, and then targeted
  3762. * queries to get related fields and add them to the record. The contact's
  3763. * account for instance. This method is only used for populating extra fields
  3764. * in the detail form
  3765. */
  3766. function fill_in_additional_detail_fields()
  3767. {
  3768. if(!empty($this->field_defs['assigned_user_name']) && !empty($this->assigned_user_id)){
  3769. $this->assigned_user_name = get_assigned_user_name($this->assigned_user_id);
  3770. }
  3771. if(!empty($this->field_defs['created_by']) && !empty($this->created_by))
  3772. $this->created_by_name = get_assigned_user_name($this->created_by);
  3773. if(!empty($this->field_defs['modified_user_id']) && !empty($this->modified_user_id))
  3774. $this->modified_by_name = get_assigned_user_name($this->modified_user_id);
  3775. if(!empty($this->field_defs['parent_name'])){
  3776. $this->fill_in_additional_parent_fields();
  3777. }
  3778. }
  3779. /**
  3780. * This is desgined to be overridden or called from extending bean. This method
  3781. * will fill in any parent_name fields.
  3782. */
  3783. function fill_in_additional_parent_fields() {
  3784. if(!empty($this->parent_id) && !empty($this->last_parent_id) && $this->last_parent_id == $this->parent_id){
  3785. return false;
  3786. }else{
  3787. $this->parent_name = '';
  3788. }
  3789. if(!empty($this->parent_type)) {
  3790. $this->last_parent_id = $this->parent_id;
  3791. $this->getRelatedFields($this->parent_type, $this->parent_id, array('name'=>'parent_name', 'document_name' => 'parent_document_name', 'first_name'=>'parent_first_name', 'last_name'=>'parent_last_name'));
  3792. if(!empty($this->parent_first_name) || !empty($this->parent_last_name) ){
  3793. $this->parent_name = $GLOBALS['locale']->getLocaleFormattedName($this->parent_first_name, $this->parent_last_name);
  3794. } else if(!empty($this->parent_document_name)){
  3795. $this->parent_name = $this->parent_document_name;
  3796. }
  3797. }
  3798. }
  3799. /*
  3800. * Fill in a link field
  3801. */
  3802. function fill_in_link_field( $linkFieldName , $def)
  3803. {
  3804. $idField = $linkFieldName;
  3805. //If the id_name provided really was an ID, don't try to load it as a link. Use the normal link
  3806. if (!empty($this->field_defs[$linkFieldName]['type']) && $this->field_defs[$linkFieldName]['type'] == "id" && !empty($def['link']))
  3807. {
  3808. $linkFieldName = $def['link'];
  3809. }
  3810. if ($this->load_relationship($linkFieldName))
  3811. {
  3812. $list=$this->$linkFieldName->get();
  3813. $this->$idField = '' ; // match up with null value in $this->populateFromRow()
  3814. if (!empty($list))
  3815. $this->$idField = $list[0];
  3816. }
  3817. }
  3818. /**
  3819. * Fill in fields where type = relate
  3820. */
  3821. function fill_in_relationship_fields(){
  3822. global $fill_in_rel_depth;
  3823. if(empty($fill_in_rel_depth) || $fill_in_rel_depth < 0)
  3824. $fill_in_rel_depth = 0;
  3825. if($fill_in_rel_depth > 1)
  3826. return;
  3827. $fill_in_rel_depth++;
  3828. foreach($this->field_defs as $field)
  3829. {
  3830. if(0 == strcmp($field['type'],'relate') && !empty($field['module']))
  3831. {
  3832. $name = $field['name'];
  3833. if(empty($this->$name))
  3834. {
  3835. // set the value of this relate field in this bean ($this->$field['name']) to the value of the 'name' field in the related module for the record identified by the value of $this->$field['id_name']
  3836. $related_module = $field['module'];
  3837. $id_name = $field['id_name'];
  3838. if (empty($this->$id_name))
  3839. {
  3840. $this->fill_in_link_field($id_name, $field);
  3841. }
  3842. if(!empty($this->$id_name) && ( $this->object_name != $related_module || ( $this->object_name == $related_module && $this->$id_name != $this->id ))){
  3843. if(isset($GLOBALS['beanList'][ $related_module])){
  3844. $class = $GLOBALS['beanList'][$related_module];
  3845. if(!empty($this->$id_name) && file_exists($GLOBALS['beanFiles'][$class]) && isset($this->$name)){
  3846. require_once($GLOBALS['beanFiles'][$class]);
  3847. $mod = new $class();
  3848. // disable row level security in order to be able
  3849. // to retrieve related bean properties (bug #44928)
  3850. $mod->retrieve($this->$id_name);
  3851. if (!empty($field['rname'])) {
  3852. $this->$name = $mod->$field['rname'];
  3853. } else if (isset($mod->name)) {
  3854. $this->$name = $mod->name;
  3855. }
  3856. }
  3857. }
  3858. }
  3859. if(!empty($this->$id_name) && isset($this->$name))
  3860. {
  3861. if(!isset($field['additionalFields']))
  3862. $field['additionalFields'] = array();
  3863. if(!empty($field['rname']))
  3864. {
  3865. $field['additionalFields'][$field['rname']]= $name;
  3866. }
  3867. else
  3868. {
  3869. $field['additionalFields']['name']= $name;
  3870. }
  3871. $this->getRelatedFields($related_module, $this->$id_name, $field['additionalFields']);
  3872. }
  3873. }
  3874. }
  3875. }
  3876. $fill_in_rel_depth--;
  3877. }
  3878. /**
  3879. * This is a helper function that is used to quickly created indexes when creating tables.
  3880. */
  3881. function create_index($query)
  3882. {
  3883. $GLOBALS['log']->info("create_index: $query");
  3884. $result = $this->db->query($query, true, "Error creating index:");
  3885. }
  3886. /**
  3887. * This function should be overridden in each module. It marks an item as deleted.
  3888. *
  3889. * If it is not overridden, then marking this type of item is not allowed
  3890. */
  3891. function mark_deleted($id)
  3892. {
  3893. global $current_user;
  3894. $date_modified = $GLOBALS['timedate']->nowDb();
  3895. if(isset($_SESSION['show_deleted']))
  3896. {
  3897. $this->mark_undeleted($id);
  3898. }
  3899. else
  3900. {
  3901. // call the custom business logic
  3902. $custom_logic_arguments['id'] = $id;
  3903. $this->call_custom_logic("before_delete", $custom_logic_arguments);
  3904. $this->deleted = 1;
  3905. $this->mark_relationships_deleted($id);
  3906. if ( isset($this->field_defs['modified_user_id']) ) {
  3907. if (!empty($current_user)) {
  3908. $this->modified_user_id = $current_user->id;
  3909. } else {
  3910. $this->modified_user_id = 1;
  3911. }
  3912. $query = "UPDATE $this->table_name set deleted=1 , date_modified = '$date_modified', modified_user_id = '$this->modified_user_id' where id='$id'";
  3913. } else {
  3914. $query = "UPDATE $this->table_name set deleted=1 , date_modified = '$date_modified' where id='$id'";
  3915. }
  3916. $this->db->query($query, true,"Error marking record deleted: ");
  3917. SugarRelationship::resaveRelatedBeans();
  3918. // Take the item off the recently viewed lists
  3919. $tracker = new Tracker();
  3920. $tracker->makeInvisibleForAll($id);
  3921. // call the custom business logic
  3922. $this->call_custom_logic("after_delete", $custom_logic_arguments);
  3923. }
  3924. }
  3925. /**
  3926. * Restores data deleted by call to mark_deleted() function.
  3927. *
  3928. * Internal function, do not override.
  3929. */
  3930. function mark_undeleted($id)
  3931. {
  3932. // call the custom business logic
  3933. $custom_logic_arguments['id'] = $id;
  3934. $this->call_custom_logic("before_restore", $custom_logic_arguments);
  3935. $date_modified = $GLOBALS['timedate']->nowDb();
  3936. $query = "UPDATE $this->table_name set deleted=0 , date_modified = '$date_modified' where id='$id'";
  3937. $this->db->query($query, true,"Error marking record undeleted: ");
  3938. // call the custom business logic
  3939. $this->call_custom_logic("after_restore", $custom_logic_arguments);
  3940. }
  3941. /**
  3942. * This function deletes relationships to this object. It should be overridden
  3943. * to handle the relationships of the specific object.
  3944. * This function is called when the item itself is being deleted.
  3945. *
  3946. * @param int $id id of the relationship to delete
  3947. */
  3948. function mark_relationships_deleted($id)
  3949. {
  3950. $this->delete_linked($id);
  3951. }
  3952. /**
  3953. * This function is used to execute the query and create an array template objects
  3954. * from the resulting ids from the query.
  3955. * It is currently used for building sub-panel arrays.
  3956. *
  3957. * @param string $query - the query that should be executed to build the list
  3958. * @param object $template - The object that should be used to copy the records.
  3959. * @param int $row_offset Optional, default 0
  3960. * @param int $limit Optional, default -1
  3961. * @return array
  3962. */
  3963. function build_related_list($query, &$template, $row_offset = 0, $limit = -1)
  3964. {
  3965. $GLOBALS['log']->debug("Finding linked records $this->object_name: ".$query);
  3966. $db = DBManagerFactory::getInstance('listviews');
  3967. if(!empty($row_offset) && $row_offset != 0 && !empty($limit) && $limit != -1)
  3968. {
  3969. $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $template->object_name list: ");
  3970. }
  3971. else
  3972. {
  3973. $result = $db->query($query, true);
  3974. }
  3975. $list = Array();
  3976. $isFirstTime = true;
  3977. $class = get_class($template);
  3978. while($row = $this->db->fetchByAssoc($result))
  3979. {
  3980. if(!$isFirstTime)
  3981. {
  3982. $template = new $class();
  3983. }
  3984. $isFirstTime = false;
  3985. $record = $template->retrieve($row['id']);
  3986. if($record != null)
  3987. {
  3988. // this copies the object into the array
  3989. $list[] = $template;
  3990. }
  3991. }
  3992. return $list;
  3993. }
  3994. /**
  3995. * This function is used to execute the query and create an array template objects
  3996. * from the resulting ids from the query.
  3997. * It is currently used for building sub-panel arrays. It supports an additional
  3998. * where clause that is executed as a filter on the results
  3999. *
  4000. * @param string $query - the query that should be executed to build the list
  4001. * @param object $template - The object that should be used to copy the records.
  4002. */
  4003. function build_related_list_where($query, &$template, $where='', $in='', $order_by, $limit='', $row_offset = 0)
  4004. {
  4005. $db = DBManagerFactory::getInstance('listviews');
  4006. // No need to do an additional query
  4007. $GLOBALS['log']->debug("Finding linked records $this->object_name: ".$query);
  4008. if(empty($in) && !empty($query))
  4009. {
  4010. $idList = $this->build_related_in($query);
  4011. $in = $idList['in'];
  4012. }
  4013. // MFH - Added Support For Custom Fields in Searches
  4014. $custom_join="";
  4015. if(isset($this->custom_fields)) {
  4016. $custom_join = $this->custom_fields->getJOIN();
  4017. }
  4018. $query = "SELECT id ";
  4019. if (!empty($custom_join)) {
  4020. $query .= $custom_join['select'];
  4021. }
  4022. $query .= " FROM $this->table_name ";
  4023. if (!empty($custom_join) && !empty($custom_join['join'])) {
  4024. $query .= " " . $custom_join['join'];
  4025. }
  4026. $query .= " WHERE deleted=0 AND id IN $in";
  4027. if(!empty($where))
  4028. {
  4029. $query .= " AND $where";
  4030. }
  4031. if(!empty($order_by))
  4032. {
  4033. $query .= "ORDER BY $order_by";
  4034. }
  4035. if (!empty($limit))
  4036. {
  4037. $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $this->object_name list: ");
  4038. }
  4039. else
  4040. {
  4041. $result = $db->query($query, true);
  4042. }
  4043. $list = Array();
  4044. $isFirstTime = true;
  4045. $class = get_class($template);
  4046. $disable_security_flag = ($template->disable_row_level_security) ? true : false;
  4047. while($row = $db->fetchByAssoc($result))
  4048. {
  4049. if(!$isFirstTime)
  4050. {
  4051. $template = new $class();
  4052. $template->disable_row_level_security = $disable_security_flag;
  4053. }
  4054. $isFirstTime = false;
  4055. $record = $template->retrieve($row['id']);
  4056. if($record != null)
  4057. {
  4058. // this copies the object into the array
  4059. $list[] = $template;
  4060. }
  4061. }
  4062. return $list;
  4063. }
  4064. /**
  4065. * Constructs an comma separated list of ids from passed query results.
  4066. *
  4067. * @param string @query query to be executed.
  4068. *
  4069. */
  4070. function build_related_in($query)
  4071. {
  4072. $idList = array();
  4073. $result = $this->db->query($query, true);
  4074. $ids = '';
  4075. while($row = $this->db->fetchByAssoc($result))
  4076. {
  4077. $idList[] = $row['id'];
  4078. if(empty($ids))
  4079. {
  4080. $ids = "('" . $row['id'] . "'";
  4081. }
  4082. else
  4083. {
  4084. $ids .= ",'" . $row['id'] . "'";
  4085. }
  4086. }
  4087. if(empty($ids))
  4088. {
  4089. $ids = "('')";
  4090. }else{
  4091. $ids .= ')';
  4092. }
  4093. return array('list'=>$idList, 'in'=>$ids);
  4094. }
  4095. /**
  4096. * Optionally copies values from fetched row into the bean.
  4097. *
  4098. * Internal function, do not override.
  4099. *
  4100. * @param string $query - the query that should be executed to build the list
  4101. * @param object $template - The object that should be used to copy the records
  4102. * @param array $field_list List of fields.
  4103. * @return array
  4104. */
  4105. function build_related_list2($query, &$template, &$field_list)
  4106. {
  4107. $GLOBALS['log']->debug("Finding linked values $this->object_name: ".$query);
  4108. $result = $this->db->query($query, true);
  4109. $list = Array();
  4110. $isFirstTime = true;
  4111. $class = get_class($template);
  4112. while($row = $this->db->fetchByAssoc($result))
  4113. {
  4114. // Create a blank copy
  4115. $copy = $template;
  4116. if(!$isFirstTime)
  4117. {
  4118. $copy = new $class();
  4119. }
  4120. $isFirstTime = false;
  4121. foreach($field_list as $field)
  4122. {
  4123. // Copy the relevant fields
  4124. $copy->$field = $row[$field];
  4125. }
  4126. // this copies the object into the array
  4127. $list[] = $copy;
  4128. }
  4129. return $list;
  4130. }
  4131. /**
  4132. * Let implementing classes to fill in row specific columns of a list view form
  4133. *
  4134. */
  4135. function list_view_parse_additional_sections(&$list_form)
  4136. {
  4137. }
  4138. /**
  4139. * Assigns all of the values into the template for the list view
  4140. */
  4141. function get_list_view_array()
  4142. {
  4143. static $cache = array();
  4144. // cn: bug 12270 - sensitive fields being passed arbitrarily in listViews
  4145. $sensitiveFields = array('user_hash' => '');
  4146. $return_array = Array();
  4147. global $app_list_strings, $mod_strings;
  4148. foreach($this->field_defs as $field=>$value){
  4149. if(isset($this->$field)){
  4150. // cn: bug 12270 - sensitive fields being passed arbitrarily in listViews
  4151. if(isset($sensitiveFields[$field]))
  4152. continue;
  4153. if(!isset($cache[$field]))
  4154. $cache[$field] = strtoupper($field);
  4155. //Fields hidden by Dependent Fields
  4156. if (isset($value['hidden']) && $value['hidden'] === true) {
  4157. $return_array[$cache[$field]] = "";
  4158. }
  4159. //cn: if $field is a _dom, detect and return VALUE not KEY
  4160. //cl: empty function check for meta-data enum types that have values loaded from a function
  4161. else if (((!empty($value['type']) && ($value['type'] == 'enum' || $value['type'] == 'radioenum') )) && empty($value['function'])){
  4162. if(!empty($value['options']) && !empty($app_list_strings[$value['options']][$this->$field])){
  4163. $return_array[$cache[$field]] = $app_list_strings[$value['options']][$this->$field];
  4164. }
  4165. //nsingh- bug 21672. some modules such as manufacturers, Releases do not have a listing for select fields in the $app_list_strings. Must also check $mod_strings to localize.
  4166. elseif(!empty($value['options']) && !empty($mod_strings[$value['options']][$this->$field]))
  4167. {
  4168. $return_array[$cache[$field]] = $mod_strings[$value['options']][$this->$field];
  4169. }
  4170. else{
  4171. $return_array[$cache[$field]] = $this->$field;
  4172. }
  4173. //end bug 21672
  4174. // tjy: no need to do this str_replace as the changes in 29994 for ListViewGeneric.tpl for translation handle this now
  4175. // }elseif(!empty($value['type']) && $value['type'] == 'multienum'&& empty($value['function'])){
  4176. // $return_array[strtoupper($field)] = str_replace('^,^', ', ', $this->$field );
  4177. }elseif(!empty($value['custom_module']) && $value['type'] != 'currency'){
  4178. // $this->format_field($value);
  4179. $return_array[$cache[$field]] = $this->$field;
  4180. }else{
  4181. $return_array[$cache[$field]] = $this->$field;
  4182. }
  4183. // handle "Assigned User Name"
  4184. if($field == 'assigned_user_name'){
  4185. $return_array['ASSIGNED_USER_NAME'] = get_assigned_user_name($this->assigned_user_id);
  4186. }
  4187. }
  4188. }
  4189. return $return_array;
  4190. }
  4191. /**
  4192. * Override this function to set values in the array used to render list view data.
  4193. *
  4194. */
  4195. function get_list_view_data()
  4196. {
  4197. return $this->get_list_view_array();
  4198. }
  4199. /**
  4200. * Construct where clause from a list of name-value pairs.
  4201. * @param array $fields_array Name/value pairs for column checks
  4202. * @param boolean $deleted Optional, default true, if set to false deleted filter will not be added.
  4203. * @return string The WHERE clause
  4204. */
  4205. function get_where($fields_array, $deleted=true)
  4206. {
  4207. $where_clause = "";
  4208. foreach ($fields_array as $name=>$value)
  4209. {
  4210. if (!empty($where_clause)) {
  4211. $where_clause .= " AND ";
  4212. }
  4213. $name = $this->db->getValidDBName($name);
  4214. $where_clause .= "$name = ".$this->db->quoted($value,false);
  4215. }
  4216. if(!empty($where_clause)) {
  4217. if($deleted) {
  4218. return "WHERE $where_clause AND deleted=0";
  4219. } else {
  4220. return "WHERE $where_clause";
  4221. }
  4222. } else {
  4223. return "";
  4224. }
  4225. }
  4226. /**
  4227. * Constructs a select query and fetch 1 row using this query, and then process the row
  4228. *
  4229. * Internal function, do not override.
  4230. * @param array @fields_array array of name value pairs used to construct query.
  4231. * @param boolean $encode Optional, default true, encode fetched data.
  4232. * @param boolean $deleted Optional, default true, if set to false deleted filter will not be added.
  4233. * @return object Instance of this bean with fetched data.
  4234. */
  4235. function retrieve_by_string_fields($fields_array, $encode=true, $deleted=true)
  4236. {
  4237. $where_clause = $this->get_where($fields_array, $deleted);
  4238. if(isset($this->custom_fields))
  4239. $custom_join = $this->custom_fields->getJOIN();
  4240. else $custom_join = false;
  4241. if($custom_join)
  4242. {
  4243. $query = "SELECT $this->table_name.*". $custom_join['select']. " FROM $this->table_name " . $custom_join['join'];
  4244. }
  4245. else
  4246. {
  4247. $query = "SELECT $this->table_name.* FROM $this->table_name ";
  4248. }
  4249. $query .= " $where_clause";
  4250. $GLOBALS['log']->debug("Retrieve $this->object_name: ".$query);
  4251. //requireSingleResult has been deprecated.
  4252. //$result = $this->db->requireSingleResult($query, true, "Retrieving record $where_clause:");
  4253. $result = $this->db->limitQuery($query,0,1,true, "Retrieving record $where_clause:");
  4254. if( empty($result))
  4255. {
  4256. return null;
  4257. }
  4258. $row = $this->db->fetchByAssoc($result, $encode);
  4259. if(empty($row))
  4260. {
  4261. return null;
  4262. }
  4263. // Removed getRowCount-if-clause earlier and insert duplicates_found here as it seems that we have found something
  4264. // if we didn't return null in the previous clause.
  4265. $this->duplicates_found = true;
  4266. $row = $this->convertRow($row);
  4267. $this->fetched_row = $row;
  4268. $this->fromArray($row);
  4269. $this->is_updated_dependent_fields = false;
  4270. $this->fill_in_additional_detail_fields();
  4271. return $this;
  4272. }
  4273. /**
  4274. * This method is called during an import before inserting a bean
  4275. * Define an associative array called $special_fields
  4276. * the keys are user defined, and don't directly map to the bean's fields
  4277. * the value is the method name within that bean that will do extra
  4278. * processing for that field. example: 'full_name'=>'get_names_from_full_name'
  4279. *
  4280. */
  4281. function process_special_fields()
  4282. {
  4283. foreach ($this->special_functions as $func_name)
  4284. {
  4285. if ( method_exists($this,$func_name) )
  4286. {
  4287. $this->$func_name();
  4288. }
  4289. }
  4290. }
  4291. /**
  4292. * Override this function to build a where clause based on the search criteria set into bean .
  4293. * @abstract
  4294. */
  4295. function build_generic_where_clause($value)
  4296. {
  4297. }
  4298. function getRelatedFields($module, $id, $fields, $return_array = false){
  4299. if(empty($GLOBALS['beanList'][$module]))return '';
  4300. $object = BeanFactory::getObjectName($module);
  4301. VardefManager::loadVardef($module, $object);
  4302. if(empty($GLOBALS['dictionary'][$object]['table']))return '';
  4303. $table = $GLOBALS['dictionary'][$object]['table'];
  4304. $query = 'SELECT id';
  4305. foreach($fields as $field=>$alias){
  4306. if(!empty($GLOBALS['dictionary'][$object]['fields'][$field]['db_concat_fields'])){
  4307. $query .= ' ,' .$this->db->concat($table, $GLOBALS['dictionary'][$object]['fields'][$field]['db_concat_fields']) . ' as ' . $alias ;
  4308. }else if(!empty($GLOBALS['dictionary'][$object]['fields'][$field]) &&
  4309. (empty($GLOBALS['dictionary'][$object]['fields'][$field]['source']) ||
  4310. $GLOBALS['dictionary'][$object]['fields'][$field]['source'] != "non-db"))
  4311. {
  4312. $query .= ' ,' .$table . '.' . $field . ' as ' . $alias;
  4313. }
  4314. if(!$return_array)$this->$alias = '';
  4315. }
  4316. if($query == 'SELECT id' || empty($id)){
  4317. return '';
  4318. }
  4319. if(isset($GLOBALS['dictionary'][$object]['fields']['assigned_user_id']))
  4320. {
  4321. $query .= " , ". $table . ".assigned_user_id owner";
  4322. }
  4323. else if(isset($GLOBALS['dictionary'][$object]['fields']['created_by']))
  4324. {
  4325. $query .= " , ". $table . ".created_by owner";
  4326. }
  4327. $query .= ' FROM ' . $table . ' WHERE deleted=0 AND id=';
  4328. $result = $GLOBALS['db']->query($query . "'$id'" );
  4329. $row = $GLOBALS['db']->fetchByAssoc($result);
  4330. if($return_array){
  4331. return $row;
  4332. }
  4333. $owner = (empty($row['owner']))?'':$row['owner'];
  4334. foreach($fields as $alias){
  4335. $this->$alias = (!empty($row[$alias]))? $row[$alias]: '';
  4336. $alias = $alias .'_owner';
  4337. $this->$alias = $owner;
  4338. $a_mod = $alias .'_mod';
  4339. $this->$a_mod = $module;
  4340. }
  4341. }
  4342. function &parse_additional_headers(&$list_form, $xTemplateSection)
  4343. {
  4344. return $list_form;
  4345. }
  4346. function assign_display_fields($currentModule)
  4347. {
  4348. global $timedate;
  4349. foreach($this->column_fields as $field)
  4350. {
  4351. if(isset($this->field_name_map[$field]) && empty($this->$field))
  4352. {
  4353. if($this->field_name_map[$field]['type'] != 'date' && $this->field_name_map[$field]['type'] != 'enum')
  4354. $this->$field = $field;
  4355. if($this->field_name_map[$field]['type'] == 'date')
  4356. {
  4357. $this->$field = $timedate->to_display_date('1980-07-09');
  4358. }
  4359. if($this->field_name_map[$field]['type'] == 'enum')
  4360. {
  4361. $dom = $this->field_name_map[$field]['options'];
  4362. global $current_language, $app_list_strings;
  4363. $mod_strings = return_module_language($current_language, $currentModule);
  4364. if(isset($mod_strings[$dom]))
  4365. {
  4366. $options = $mod_strings[$dom];
  4367. foreach($options as $key=>$value)
  4368. {
  4369. if(!empty($key) && empty($this->$field ))
  4370. {
  4371. $this->$field = $key;
  4372. }
  4373. }
  4374. }
  4375. if(isset($app_list_strings[$dom]))
  4376. {
  4377. $options = $app_list_strings[$dom];
  4378. foreach($options as $key=>$value)
  4379. {
  4380. if(!empty($key) && empty($this->$field ))
  4381. {
  4382. $this->$field = $key;
  4383. }
  4384. }
  4385. }
  4386. }
  4387. }
  4388. }
  4389. }
  4390. /*
  4391. * RELATIONSHIP HANDLING
  4392. */
  4393. function set_relationship($table, $relate_values, $check_duplicates = true,$do_update=false,$data_values=null)
  4394. {
  4395. $where = '';
  4396. // make sure there is a date modified
  4397. $date_modified = $this->db->convert("'".$GLOBALS['timedate']->nowDb()."'", 'datetime');
  4398. $row=null;
  4399. if($check_duplicates)
  4400. {
  4401. $query = "SELECT * FROM $table ";
  4402. $where = "WHERE deleted = '0' ";
  4403. foreach($relate_values as $name=>$value)
  4404. {
  4405. $where .= " AND $name = '$value' ";
  4406. }
  4407. $query .= $where;
  4408. $result = $this->db->query($query, false, "Looking For Duplicate Relationship:" . $query);
  4409. $row=$this->db->fetchByAssoc($result);
  4410. }
  4411. if(!$check_duplicates || empty($row) )
  4412. {
  4413. unset($relate_values['id']);
  4414. if ( isset($data_values))
  4415. {
  4416. $relate_values = array_merge($relate_values,$data_values);
  4417. }
  4418. $query = "INSERT INTO $table (id, ". implode(',', array_keys($relate_values)) . ", date_modified) VALUES ('" . create_guid() . "', " . "'" . implode("', '", $relate_values) . "', ".$date_modified.")" ;
  4419. $this->db->query($query, false, "Creating Relationship:" . $query);
  4420. }
  4421. else if ($do_update)
  4422. {
  4423. $conds = array();
  4424. foreach($data_values as $key=>$value)
  4425. {
  4426. array_push($conds,$key."='".$this->db->quote($value)."'");
  4427. }
  4428. $query = "UPDATE $table SET ". implode(',', $conds).",date_modified=".$date_modified." ".$where;
  4429. $this->db->query($query, false, "Updating Relationship:" . $query);
  4430. }
  4431. }
  4432. function retrieve_relationships($table, $values, $select_id)
  4433. {
  4434. $query = "SELECT $select_id FROM $table WHERE deleted = 0 ";
  4435. foreach($values as $name=>$value)
  4436. {
  4437. $query .= " AND $name = '$value' ";
  4438. }
  4439. $query .= " ORDER BY $select_id ";
  4440. $result = $this->db->query($query, false, "Retrieving Relationship:" . $query);
  4441. $ids = array();
  4442. while($row = $this->db->fetchByAssoc($result))
  4443. {
  4444. $ids[] = $row;
  4445. }
  4446. return $ids;
  4447. }
  4448. // TODO: this function needs adjustment
  4449. function loadLayoutDefs()
  4450. {
  4451. global $layout_defs;
  4452. if(empty( $this->layout_def) && file_exists('modules/'. $this->module_dir . '/layout_defs.php'))
  4453. {
  4454. include_once('modules/'. $this->module_dir . '/layout_defs.php');
  4455. if(file_exists('custom/modules/'. $this->module_dir . '/Ext/Layoutdefs/layoutdefs.ext.php'))
  4456. {
  4457. include_once('custom/modules/'. $this->module_dir . '/Ext/Layoutdefs/layoutdefs.ext.php');
  4458. }
  4459. if ( empty( $layout_defs[get_class($this)]))
  4460. {
  4461. echo "\$layout_defs[" . get_class($this) . "]; does not exist";
  4462. }
  4463. $this->layout_def = $layout_defs[get_class($this)];
  4464. }
  4465. }
  4466. /**
  4467. * Trigger custom logic for this module that is defined for the provided hook
  4468. * The custom logic file is located under custom/modules/[CURRENT_MODULE]/logic_hooks.php.
  4469. * That file should define the $hook_version that should be used.
  4470. * It should also define the $hook_array. The $hook_array will be a two dimensional array
  4471. * the first dimension is the name of the event, the second dimension is the information needed
  4472. * to fire the hook. Each entry in the top level array should be defined on a single line to make it
  4473. * easier to automatically replace this file. There should be no contents of this file that are not replacable.
  4474. *
  4475. * $hook_array['before_save'][] = Array(1, testtype, 'custom/modules/Leads/test12.php', 'TestClass', 'lead_before_save_1');
  4476. * This sample line creates a before_save hook. The hooks are procesed in the order in which they
  4477. * are added to the array. The second dimension is an array of:
  4478. * processing index (for sorting before exporting the array)
  4479. * A logic type hook
  4480. * label/type
  4481. * php file to include
  4482. * php class the method is in
  4483. * php method to call
  4484. *
  4485. * The method signature for version 1 hooks is:
  4486. * function NAME(&$bean, $event, $arguments)
  4487. * $bean - $this bean passed in by reference.
  4488. * $event - The string for the current event (i.e. before_save)
  4489. * $arguments - An array of arguments that are specific to the event.
  4490. */
  4491. function call_custom_logic($event, $arguments = null)
  4492. {
  4493. if(!isset($this->processed) || $this->processed == false){
  4494. //add some logic to ensure we do not get into an infinite loop
  4495. if(!empty($this->logicHookDepth[$event])) {
  4496. if($this->logicHookDepth[$event] > $this->max_logic_depth)
  4497. return;
  4498. }else
  4499. $this->logicHookDepth[$event] = 0;
  4500. //we have to put the increment operator here
  4501. //otherwise we may never increase the depth for that event in the case
  4502. //where one event will trigger another as in the case of before_save and after_save
  4503. //Also keeping the depth per event allow any number of hooks to be called on the bean
  4504. //and we only will return if one event gets caught in a loop. We do not increment globally
  4505. //for each event called.
  4506. $this->logicHookDepth[$event]++;
  4507. //method defined in 'include/utils/LogicHook.php'
  4508. $logicHook = new LogicHook();
  4509. $logicHook->setBean($this);
  4510. $logicHook->call_custom_logic($this->module_dir, $event, $arguments);
  4511. $this->logicHookDepth[$event]--;
  4512. }
  4513. }
  4514. /* When creating a custom field of type Dropdown, it creates an enum row in the DB.
  4515. A typical get_list_view_array() result will have the *KEY* value from that drop-down.
  4516. Since custom _dom objects are flat-files included in the $app_list_strings variable,
  4517. We need to generate a key-key pair to get the true value like so:
  4518. ([module]_cstm->fields_meta_data->$app_list_strings->*VALUE*)*/
  4519. function getRealKeyFromCustomFieldAssignedKey($name)
  4520. {
  4521. if ($this->custom_fields->avail_fields[$name]['ext1'])
  4522. {
  4523. $realKey = 'ext1';
  4524. }
  4525. elseif ($this->custom_fields->avail_fields[$name]['ext2'])
  4526. {
  4527. $realKey = 'ext2';
  4528. }
  4529. elseif ($this->custom_fields->avail_fields[$name]['ext3'])
  4530. {
  4531. $realKey = 'ext3';
  4532. }
  4533. else
  4534. {
  4535. $GLOBALS['log']->fatal("SUGARBEAN: cannot find Real Key for custom field of type dropdown - cannot return Value.");
  4536. return false;
  4537. }
  4538. if(isset($realKey))
  4539. {
  4540. return $this->custom_fields->avail_fields[$name][$realKey];
  4541. }
  4542. }
  4543. function bean_implements($interface)
  4544. {
  4545. return false;
  4546. }
  4547. /**
  4548. * Check whether the user has access to a particular view for the current bean/module
  4549. * @param $view string required, the view to determine access for i.e. DetailView, ListView...
  4550. * @param $is_owner bool optional, this is part of the ACL check if the current user is an owner they will receive different access
  4551. */
  4552. function ACLAccess($view,$is_owner='not_set')
  4553. {
  4554. global $current_user;
  4555. if($current_user->isAdminForModule($this->getACLCategory())) {
  4556. return true;
  4557. }
  4558. $not_set = false;
  4559. if($is_owner == 'not_set')
  4560. {
  4561. $not_set = true;
  4562. $is_owner = $this->isOwner($current_user->id);
  4563. }
  4564. // If we don't implement ACLs, return true.
  4565. if(!$this->bean_implements('ACL'))
  4566. return true;
  4567. $view = strtolower($view);
  4568. switch ($view)
  4569. {
  4570. case 'list':
  4571. case 'index':
  4572. case 'listview':
  4573. return ACLController::checkAccess($this->module_dir,'list', true);
  4574. case 'edit':
  4575. case 'save':
  4576. if( !$is_owner && $not_set && !empty($this->id)){
  4577. $class = get_class($this);
  4578. $temp = new $class();
  4579. if(!empty($this->fetched_row) && !empty($this->fetched_row['id']) && !empty($this->fetched_row['assigned_user_id']) && !empty($this->fetched_row['created_by'])){
  4580. $temp->populateFromRow($this->fetched_row);
  4581. }else{
  4582. $temp->retrieve($this->id);
  4583. }
  4584. $is_owner = $temp->isOwner($current_user->id);
  4585. }
  4586. case 'popupeditview':
  4587. case 'editview':
  4588. return ACLController::checkAccess($this->module_dir,'edit', $is_owner, $this->acltype);
  4589. case 'view':
  4590. case 'detail':
  4591. case 'detailview':
  4592. return ACLController::checkAccess($this->module_dir,'view', $is_owner, $this->acltype);
  4593. case 'delete':
  4594. return ACLController::checkAccess($this->module_dir,'delete', $is_owner, $this->acltype);
  4595. case 'export':
  4596. return ACLController::checkAccess($this->module_dir,'export', $is_owner, $this->acltype);
  4597. case 'import':
  4598. return ACLController::checkAccess($this->module_dir,'import', true, $this->acltype);
  4599. }
  4600. //if it is not one of the above views then it should be implemented on the page level
  4601. return true;
  4602. }
  4603. /**
  4604. * Get owner field
  4605. *
  4606. * @return STRING
  4607. */
  4608. function getOwnerField($returnFieldName = false)
  4609. {
  4610. if (isset($this->field_defs['assigned_user_id']))
  4611. {
  4612. return $returnFieldName? 'assigned_user_id': $this->assigned_user_id;
  4613. }
  4614. if (isset($this->field_defs['created_by']))
  4615. {
  4616. return $returnFieldName? 'created_by': $this->created_by;
  4617. }
  4618. return '';
  4619. }
  4620. /**
  4621. * Returns true of false if the user_id passed is the owner
  4622. *
  4623. * @param GUID $user_id
  4624. * @return boolean
  4625. */
  4626. function isOwner($user_id)
  4627. {
  4628. //if we don't have an id we must be the owner as we are creating it
  4629. if(!isset($this->id))
  4630. {
  4631. return true;
  4632. }
  4633. //if there is an assigned_user that is the owner
  4634. if(isset($this->assigned_user_id))
  4635. {
  4636. if($this->assigned_user_id == $user_id) return true;
  4637. return false;
  4638. }
  4639. else
  4640. {
  4641. //other wise if there is a created_by that is the owner
  4642. if(isset($this->created_by) && $this->created_by == $user_id)
  4643. {
  4644. return true;
  4645. }
  4646. }
  4647. return false;
  4648. }
  4649. /**
  4650. * Gets there where statement for checking if a user is an owner
  4651. *
  4652. * @param GUID $user_id
  4653. * @return STRING
  4654. */
  4655. function getOwnerWhere($user_id)
  4656. {
  4657. if(isset($this->field_defs['assigned_user_id']))
  4658. {
  4659. return " $this->table_name.assigned_user_id ='$user_id' ";
  4660. }
  4661. if(isset($this->field_defs['created_by']))
  4662. {
  4663. return " $this->table_name.created_by ='$user_id' ";
  4664. }
  4665. return '';
  4666. }
  4667. /**
  4668. *
  4669. * Used in order to manage ListView links and if they should
  4670. * links or not based on the ACL permissions of the user
  4671. *
  4672. * @return ARRAY of STRINGS
  4673. */
  4674. function listviewACLHelper()
  4675. {
  4676. $array_assign = array();
  4677. if($this->ACLAccess('DetailView'))
  4678. {
  4679. $array_assign['MAIN'] = 'a';
  4680. }
  4681. else
  4682. {
  4683. $array_assign['MAIN'] = 'span';
  4684. }
  4685. return $array_assign;
  4686. }
  4687. /**
  4688. * returns this bean as an array
  4689. *
  4690. * @return array of fields with id, name, access and category
  4691. */
  4692. function toArray($dbOnly = false, $stringOnly = false, $upperKeys=false)
  4693. {
  4694. static $cache = array();
  4695. $arr = array();
  4696. foreach($this->field_defs as $field=>$data)
  4697. {
  4698. if( !$dbOnly || !isset($data['source']) || $data['source'] == 'db')
  4699. if(!$stringOnly || is_string($this->$field))
  4700. if($upperKeys)
  4701. {
  4702. if(!isset($cache[$field])){
  4703. $cache[$field] = strtoupper($field);
  4704. }
  4705. $arr[$cache[$field]] = $this->$field;
  4706. }
  4707. else
  4708. {
  4709. if(isset($this->$field)){
  4710. $arr[$field] = $this->$field;
  4711. }else{
  4712. $arr[$field] = '';
  4713. }
  4714. }
  4715. }
  4716. return $arr;
  4717. }
  4718. /**
  4719. * Converts an array into an acl mapping name value pairs into files
  4720. *
  4721. * @param Array $arr
  4722. */
  4723. function fromArray($arr)
  4724. {
  4725. foreach($arr as $name=>$value)
  4726. {
  4727. $this->$name = $value;
  4728. }
  4729. }
  4730. /**
  4731. * Convert row data from DB format to internal format
  4732. * Mostly useful for dates/times
  4733. * @param array $row
  4734. * @return array $row
  4735. */
  4736. public function convertRow($row)
  4737. {
  4738. foreach($this->field_defs as $name => $fieldDef)
  4739. {
  4740. // skip empty fields and non-db fields
  4741. if (isset($name) && !empty($row[$name])) {
  4742. $row[$name] = $this->convertField($row[$name], $fieldDef);
  4743. }
  4744. }
  4745. return $row;
  4746. }
  4747. /**
  4748. * Converts the field value based on the provided fieldDef
  4749. * @param $fieldvalue
  4750. * @param $fieldDef
  4751. * @return string
  4752. */
  4753. public function convertField($fieldvalue, $fieldDef)
  4754. {
  4755. if (!empty($fieldvalue)) {
  4756. if (!(isset($fieldDef['source']) &&
  4757. !in_array($fieldDef['source'], array('db', 'custom_fields', 'relate'))
  4758. && !isset($fieldDef['dbType']))
  4759. ) {
  4760. // fromConvert other fields
  4761. $fieldvalue = $this->db->fromConvert($fieldvalue, $this->db->getFieldType($fieldDef));
  4762. }
  4763. }
  4764. return $fieldvalue;
  4765. }
  4766. /**
  4767. * Loads a row of data into instance of a bean. The data is passed as an array to this function
  4768. *
  4769. * @param array $arr row of data fetched from the database.
  4770. * @return nothing
  4771. *
  4772. * Internal function do not override.
  4773. */
  4774. function loadFromRow($arr)
  4775. {
  4776. $this->populateFromRow($arr);
  4777. $this->processed_dates_times = array();
  4778. $this->check_date_relationships_load();
  4779. $this->fill_in_additional_list_fields();
  4780. if($this->hasCustomFields())$this->custom_fields->fill_relationships();
  4781. $this->call_custom_logic("process_record");
  4782. }
  4783. function hasCustomFields()
  4784. {
  4785. return !empty($GLOBALS['dictionary'][$this->object_name]['custom_fields']);
  4786. }
  4787. /**
  4788. * Ensure that fields within order by clauses are properly qualified with
  4789. * their tablename. This qualification is a requirement for sql server support.
  4790. *
  4791. * @param string $order_by original order by from the query
  4792. * @param string $qualify prefix for columns in the order by list.
  4793. * @return prefixed
  4794. *
  4795. * Internal function do not override.
  4796. */
  4797. function create_qualified_order_by( $order_by, $qualify)
  4798. { // if the column is empty, but the sort order is defined, the value will throw an error, so do not proceed if no order by is given
  4799. if (empty($order_by))
  4800. {
  4801. return $order_by;
  4802. }
  4803. $order_by_clause = " ORDER BY ";
  4804. $tmp = explode(",", $order_by);
  4805. $comma = ' ';
  4806. foreach ( $tmp as $stmp)
  4807. {
  4808. $stmp = (substr_count($stmp, ".") > 0?trim($stmp):"$qualify." . trim($stmp));
  4809. $order_by_clause .= $comma . $stmp;
  4810. $comma = ", ";
  4811. }
  4812. return $order_by_clause;
  4813. }
  4814. /**
  4815. * Combined the contents of street field 2 thru 4 into the main field
  4816. *
  4817. * @param string $street_field
  4818. */
  4819. function add_address_streets(
  4820. $street_field
  4821. )
  4822. {
  4823. $street_field_2 = $street_field.'_2';
  4824. $street_field_3 = $street_field.'_3';
  4825. $street_field_4 = $street_field.'_4';
  4826. if ( isset($this->$street_field_2)) {
  4827. $this->$street_field .= "\n". $this->$street_field_2;
  4828. unset($this->$street_field_2);
  4829. }
  4830. if ( isset($this->$street_field_3)) {
  4831. $this->$street_field .= "\n". $this->$street_field_3;
  4832. unset($this->$street_field_3);
  4833. }
  4834. if ( isset($this->$street_field_4)) {
  4835. $this->$street_field .= "\n". $this->$street_field_4;
  4836. unset($this->$street_field_4);
  4837. }
  4838. if ( isset($this->$street_field)) {
  4839. $this->$street_field = trim($this->$street_field, "\n");
  4840. }
  4841. }
  4842. /**
  4843. * Encrpyt and base64 encode an 'encrypt' field type in the bean using Blowfish. The default system key is stored in cache/Blowfish/{keytype}
  4844. * @param STRING value -plain text value of the bean field.
  4845. * @return string
  4846. */
  4847. function encrpyt_before_save($value)
  4848. {
  4849. require_once("include/utils/encryption_utils.php");
  4850. return blowfishEncode(blowfishGetKey('encrypt_field'),$value);
  4851. }
  4852. /**
  4853. * Decode and decrypt a base 64 encoded string with field type 'encrypt' in this bean using Blowfish.
  4854. * @param STRING value - an encrypted and base 64 encoded string.
  4855. * @return string
  4856. */
  4857. function decrypt_after_retrieve($value)
  4858. {
  4859. require_once("include/utils/encryption_utils.php");
  4860. return blowfishDecode(blowfishGetKey('encrypt_field'), $value);
  4861. }
  4862. /**
  4863. * Moved from save() method, functionality is the same, but this is intended to handle
  4864. * Optimistic locking functionality.
  4865. */
  4866. private function _checkOptimisticLocking($action, $isUpdate){
  4867. if($this->optimistic_lock && !isset($_SESSION['o_lock_fs'])){
  4868. if(isset($_SESSION['o_lock_id']) && $_SESSION['o_lock_id'] == $this->id && $_SESSION['o_lock_on'] == $this->object_name)
  4869. {
  4870. if($action == 'Save' && $isUpdate && isset($this->modified_user_id) && $this->has_been_modified_since($_SESSION['o_lock_dm'], $this->modified_user_id))
  4871. {
  4872. $_SESSION['o_lock_class'] = get_class($this);
  4873. $_SESSION['o_lock_module'] = $this->module_dir;
  4874. $_SESSION['o_lock_object'] = $this->toArray();
  4875. $saveform = "<form name='save' id='save' method='POST'>";
  4876. foreach($_POST as $key=>$arg)
  4877. {
  4878. $saveform .= "<input type='hidden' name='". addslashes($key) ."' value='". addslashes($arg) ."'>";
  4879. }
  4880. $saveform .= "</form><script>document.getElementById('save').submit();</script>";
  4881. $_SESSION['o_lock_save'] = $saveform;
  4882. header('Location: index.php?module=OptimisticLock&action=LockResolve');
  4883. die();
  4884. }
  4885. else
  4886. {
  4887. unset ($_SESSION['o_lock_object']);
  4888. unset ($_SESSION['o_lock_id']);
  4889. unset ($_SESSION['o_lock_dm']);
  4890. }
  4891. }
  4892. }
  4893. else
  4894. {
  4895. if(isset($_SESSION['o_lock_object'])) { unset ($_SESSION['o_lock_object']); }
  4896. if(isset($_SESSION['o_lock_id'])) { unset ($_SESSION['o_lock_id']); }
  4897. if(isset($_SESSION['o_lock_dm'])) { unset ($_SESSION['o_lock_dm']); }
  4898. if(isset($_SESSION['o_lock_fs'])) { unset ($_SESSION['o_lock_fs']); }
  4899. if(isset($_SESSION['o_lock_save'])) { unset ($_SESSION['o_lock_save']); }
  4900. }
  4901. }
  4902. /**
  4903. * Send assignment notifications and invites for meetings and calls
  4904. */
  4905. private function _sendNotifications($check_notify){
  4906. if($check_notify || (isset($this->notify_inworkflow) && $this->notify_inworkflow == true) // cn: bug 5795 - no invites sent to Contacts, and also bug 25995, in workflow, it will set the notify_on_save=true.
  4907. && !$this->isOwner($this->created_by) ) // cn: bug 42727 no need to send email to owner (within workflow)
  4908. {
  4909. $admin = new Administration();
  4910. $admin->retrieveSettings();
  4911. $sendNotifications = false;
  4912. if ($admin->settings['notify_on'])
  4913. {
  4914. $GLOBALS['log']->info("Notifications: user assignment has changed, checking if user receives notifications");
  4915. $sendNotifications = true;
  4916. }
  4917. elseif(isset($_REQUEST['send_invites']) && $_REQUEST['send_invites'] == 1)
  4918. {
  4919. // cn: bug 5795 Send Invites failing for Contacts
  4920. $sendNotifications = true;
  4921. }
  4922. else
  4923. {
  4924. $GLOBALS['log']->info("Notifications: not sending e-mail, notify_on is set to OFF");
  4925. }
  4926. if($sendNotifications == true)
  4927. {
  4928. $notify_list = $this->get_notification_recipients();
  4929. foreach ($notify_list as $notify_user)
  4930. {
  4931. $this->send_assignment_notifications($notify_user, $admin);
  4932. }
  4933. }
  4934. }
  4935. }
  4936. /**
  4937. * Called from ImportFieldSanitize::relate(), when creating a new bean in a related module. Will
  4938. * copies fields over from the current bean into the related. Designed to be overriden in child classes.
  4939. *
  4940. * @param SugarBean $newbean newly created related bean
  4941. */
  4942. public function populateRelatedBean(
  4943. SugarBean $newbean
  4944. )
  4945. {
  4946. }
  4947. /**
  4948. * Called during the import process before a bean save, to handle any needed pre-save logic when
  4949. * importing a record
  4950. */
  4951. public function beforeImportSave()
  4952. {
  4953. }
  4954. /**
  4955. * Called during the import process after a bean save, to handle any needed post-save logic when
  4956. * importing a record
  4957. */
  4958. public function afterImportSave()
  4959. {
  4960. }
  4961. /**
  4962. * This function is designed to cache references to field arrays that were previously stored in the
  4963. * bean files and have since been moved to separate files. Was previously in include/CacheHandler.php
  4964. *
  4965. * @deprecated
  4966. * @param $module_dir string the module directory
  4967. * @param $module string the name of the module
  4968. * @param $key string the type of field array we are referencing, i.e. list_fields, column_fields, required_fields
  4969. **/
  4970. private function _loadCachedArray(
  4971. $module_dir,
  4972. $module,
  4973. $key
  4974. )
  4975. {
  4976. static $moduleDefs = array();
  4977. $fileName = 'field_arrays.php';
  4978. $cache_key = "load_cached_array.$module_dir.$module.$key";
  4979. $result = sugar_cache_retrieve($cache_key);
  4980. if(!empty($result))
  4981. {
  4982. // Use SugarCache::EXTERNAL_CACHE_NULL_VALUE to store null values in the cache.
  4983. if($result == SugarCache::EXTERNAL_CACHE_NULL_VALUE)
  4984. {
  4985. return null;
  4986. }
  4987. return $result;
  4988. }
  4989. if(file_exists('modules/'.$module_dir.'/'.$fileName))
  4990. {
  4991. // If the data was not loaded, try loading again....
  4992. if(!isset($moduleDefs[$module]))
  4993. {
  4994. include('modules/'.$module_dir.'/'.$fileName);
  4995. $moduleDefs[$module] = $fields_array;
  4996. }
  4997. // Now that we have tried loading, make sure it was loaded
  4998. if(empty($moduleDefs[$module]) || empty($moduleDefs[$module][$module][$key]))
  4999. {
  5000. // It was not loaded.... Fail. Cache null to prevent future repeats of this calculation
  5001. sugar_cache_put($cache_key, SugarCache::EXTERNAL_CACHE_NULL_VALUE);
  5002. return null;
  5003. }
  5004. // It has been loaded, cache the result.
  5005. sugar_cache_put($cache_key, $moduleDefs[$module][$module][$key]);
  5006. return $moduleDefs[$module][$module][$key];
  5007. }
  5008. // It was not loaded.... Fail. Cache null to prevent future repeats of this calculation
  5009. sugar_cache_put($cache_key, SugarCache::EXTERNAL_CACHE_NULL_VALUE);
  5010. return null;
  5011. }
  5012. /**
  5013. * Returns the ACL category for this module; defaults to the SugarBean::$acl_category if defined
  5014. * otherwise it is SugarBean::$module_dir
  5015. *
  5016. * @return string
  5017. */
  5018. public function getACLCategory()
  5019. {
  5020. return !empty($this->acl_category)?$this->acl_category:$this->module_dir;
  5021. }
  5022. /**
  5023. * Returns the query used for the export functionality for a module. Override this method if you wish
  5024. * to have a custom query to pull this data together instead
  5025. *
  5026. * @param string $order_by
  5027. * @param string $where
  5028. * @return string SQL query
  5029. */
  5030. public function create_export_query($order_by, $where)
  5031. {
  5032. return $this->create_new_list_query($order_by, $where, array(), array(), 0, '', false, $this, true, true);
  5033. }
  5034. }