PageRenderTime 1345ms CodeModel.GetById 18ms RepoModel.GetById 14ms app.codeStats 2ms

/data/SugarBean.php

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