PageRenderTime 69ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/DataTool.php

https://github.com/sugarcrm/Tidbit
PHP | 940 lines | 577 code | 108 blank | 255 comment | 92 complexity | 8e01057ddeb9504727aac61a0daf023a MD5 | raw file
Possible License(s): AGPL-1.0
  1. <?php
  2. /*********************************************************************************
  3. * Tidbit is a data generation tool for the SugarCRM application developed by
  4. * SugarCRM, Inc. Copyright (C) 2004-2010 SugarCRM Inc.
  5. *
  6. * This program is free software; you can redistribute it and/or modify it under
  7. * the terms of the GNU Affero General Public License version 3 as published by the
  8. * Free Software Foundation with the addition of the following permission added
  9. * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
  10. * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
  11. * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
  12. *
  13. * This program is distributed in the hope that it will be useful, but WITHOUT
  14. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  15. * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  16. * details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License along with
  19. * this program; if not, see http://www.gnu.org/licenses or write to the Free
  20. * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  21. * 02110-1301 USA.
  22. *
  23. * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
  24. * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
  25. *
  26. * The interactive user interfaces in modified source and object code versions
  27. * of this program must display Appropriate Legal Notices, as required under
  28. * Section 5 of the GNU Affero General Public License version 3.
  29. *
  30. * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
  31. * these Appropriate Legal Notices must retain the display of the "Powered by
  32. * SugarCRM" logo. If the display of the logo is not reasonably feasible for
  33. * technical reasons, the Appropriate Legal Notices must display the words
  34. * "Powered by SugarCRM".
  35. ********************************************************************************/
  36. require_once('include/utils/db_utils.php');
  37. /**
  38. * DataTool randomly generates data to be inserted into the Sugar application
  39. * A DataTool object corresponds to a Sugar module.
  40. * install_cli.php creates a DataTool object for each Sugar module and
  41. * initializes its fields based on values from that Sugar module.
  42. *
  43. */
  44. class DataTool{
  45. var $installData = array();
  46. var $fields = array();
  47. var $table_name = '';
  48. var $module = '';
  49. var $count = 0;
  50. static $team_sets_array = array();
  51. static $relmodule_index = 0;
  52. // Cache for generateSeed function
  53. static $seedModules = array();
  54. static $seedFields = array();
  55. // based on xhprof, db_convert for datetime and time generation, consume a lot of time.
  56. // getConvertDatetime() function re-generate time only when this index reach max number
  57. // so we will re-generate time only for self::$datetimeIndexMax record
  58. static $datetimeCacheIndex = 0;
  59. static $datetimeIndexMax = 1000;
  60. // Skip db_convert for those types for optimization
  61. // TODO: move this to config
  62. static $notConvertedTypes = array(
  63. 'int',
  64. 'uint',
  65. 'double',
  66. 'float',
  67. 'decimal',
  68. 'decimal2',
  69. 'short',
  70. 'varchar',
  71. 'text',
  72. 'enum',
  73. 'bool',
  74. 'phone',
  75. 'email',
  76. 'created_by'
  77. );
  78. /**
  79. * Generate data and store it in the installData array.
  80. * This function calls generateSeed and passes the return
  81. * value as an argument to getData. This is done for each
  82. * field.
  83. */
  84. function generateData(){
  85. /* For each of the fields in this record, we want to generate
  86. * one element of seed data for it.*/
  87. foreach($this->fields as $field => $data){
  88. if(!empty($data['source']))continue;
  89. $type = (!empty($data['dbType']))?$data['dbType']:$data['type'];
  90. $GLOBALS['fieldData'] = $data;
  91. /* There are 3 unique parts to the seed: the Module name,
  92. * the count of the record, and the name of the field.
  93. * Using these 3 things should keep our seed unique enough.
  94. */
  95. $seed = $this->generateSeed($this->module, $field, $this->count);
  96. $value = $this->getData($field, $type, $data['type'], $seed);
  97. if(!empty($value) || $value == '0'){
  98. $this->installData[$field] = $value;
  99. }
  100. }
  101. /* These fields are filled in once per record. */
  102. if (!empty($this->fields['deleted'])) {
  103. $this->installData['deleted'] = 0;
  104. }
  105. if (!empty($this->fields['date_modified'])) {
  106. $this->installData['date_modified'] = $this->getConvertDatetime();
  107. }
  108. if(!empty($this->fields['date_entered'])) {
  109. $this->installData['date_entered'] = $this->installData['date_modified'];
  110. }
  111. if(!empty($this->fields['assign_user_id'])){
  112. $this->installData['assigned_user_id'] = 1;
  113. }
  114. if (!empty($this->fields['modified_user_id'])) {
  115. $this->installData['modified_user_id'] = 1;
  116. }
  117. if(!empty($this->fields['team_id'])){
  118. $teams = self::$team_sets_array[$this->installData['team_set_id']];
  119. $index = count($teams) == 1 ? 0 : rand(0, count($teams)-1);
  120. if(empty($this->fields['team_id']['source'])){
  121. $this->installData['team_id'] = "'".$teams[$index]."'";
  122. }
  123. //check if the assigned user is part of the team set, if not then add their default team.
  124. if(isset($this->installData['assigned_user_id'])){
  125. $this->installData['team_set_id'] = add_team_to_team_set($this->installData['team_set_id'], $this->installData['assigned_user_id']);
  126. }
  127. $this->installData['team_set_id'] = "'".$this->installData['team_set_id']."'";
  128. }
  129. }
  130. /**
  131. * Generate a unique ID based on the module name, system time, and count (defined
  132. * in install_config.php for each module), and save the ID in the installData array.
  133. */
  134. function generateId() {
  135. $currentModule = $this->getAlias($this->module);
  136. $this->installData['id'] = $this->assembleId($currentModule, $this->count);
  137. if (strlen($this->installData['id']) > 36) {
  138. $moduleLength = strlen($currentModule);
  139. $this->installData['id'] = '\'seed-' . $currentModule . substr(md5($this->installData['id']), 0, -($moduleLength + 1)). "'";
  140. }
  141. }
  142. function clean(){
  143. $this->installData = array();
  144. $this->count = 0;
  145. }
  146. /**
  147. * Dispatch to the handleType function based on what values are present in the
  148. * global $dataTool array. This array is populated by the .php files in the
  149. * TidBit/Data directory.
  150. * @param $fieldName - name of the field for which data is being generated
  151. * @param $fieldType - The DB type of the field, if it differs from the Sugar type
  152. * @param $sugarType - Always the Sugar type of the field
  153. * @param $seed - Seed from generateSeed(), used to generate a random reasonable value
  154. */
  155. function getData($fieldName, $fieldType, $sugarType, $seed){
  156. //echo "GD: $fieldName, $fieldType, $sugarType, $seed\n";
  157. // Check if the fieldName is defined
  158. if(!empty($GLOBALS['dataTool'][$this->module][$fieldName])){
  159. return $this->handleType($GLOBALS['dataTool'][$this->module][$fieldName], $fieldType, $fieldName, $seed);
  160. }
  161. // Check if the Sugar type is defined
  162. if(!empty($GLOBALS['dataTool'][$this->module][$sugarType])){
  163. return $this->handleType($GLOBALS['dataTool'][$this->module][$sugarType], $fieldType, $fieldName, $seed);
  164. }
  165. // If the fieldName is undefined for this module, see if a default value is defined
  166. if(!empty($GLOBALS['dataTool']['default'][$fieldName])){
  167. return $this->handleType($GLOBALS['dataTool']['default'][$fieldName], $fieldType, $fieldName, $seed);
  168. }
  169. // If the sugarType is undefined for this module, see if a default value is defined
  170. if(!empty($GLOBALS['dataTool']['default'][$sugarType])){
  171. return $this->handleType($GLOBALS['dataTool']['default'][$sugarType], $fieldType, $fieldName, $seed);
  172. }
  173. // Check if fieldType is defined
  174. if(!empty($GLOBALS['dataTool'][$this->module][$fieldType])){
  175. return $this->handleType($GLOBALS['dataTool'][$this->module][$fieldType], $fieldType, $fieldName, $seed);
  176. }
  177. // If the fieldType is undefined for this module, see if a default value is defined
  178. if(!empty($GLOBALS['dataTool']['default'][$fieldType])){
  179. return $this->handleType($GLOBALS['dataTool']['default'][$fieldType],$fieldType, $fieldName, $seed);
  180. }
  181. return '';
  182. }
  183. /**
  184. * Returns a randomly generated piece of data for the current module and field.
  185. * @param $typeData - An array from a .php file in the Tidbit/Data directory
  186. * @param $type - The type of the current field
  187. * @param $field - The name of the current field
  188. * @param $seed - Number to be used as the seed for mt_srand()
  189. */
  190. function handleType($typeData, $type, $field, $seed){
  191. /* We want all data to be predictable. $seed should be charactaristic of
  192. * this entity or the remote entity we want to simulate
  193. */
  194. mt_srand($seed);
  195. // echo "HT: $typeData, $type, $field, $seed\n";
  196. if(!empty($typeData['skip']))return '';
  197. if(!empty($typeData['teamset'])) {
  198. $index = rand(0, count(self::$team_sets_array)-1);
  199. $keys = array_keys(self::$team_sets_array);
  200. return $this->installData['team_set_id'] = $keys[$index];
  201. }
  202. if(!empty($typeData['value']) || (isset($typeData['value']) && $typeData['value']=="0")){
  203. return $typeData['value'];
  204. }
  205. if(!empty($typeData['increment'])){
  206. static $inc = -1;
  207. $inc ++;
  208. if($typeData['increment']['max']){
  209. return $typeData['increment']['min'] + ($inc % ($typeData['increment']['max']-$typeData['increment']['min']));
  210. }else{
  211. return $typeData['increment']['min'] + $inc;
  212. }
  213. }
  214. /* This gets used for usernames, which need to be
  215. * user1, user2 etc.
  216. */
  217. if(!empty($typeData['incname'])){
  218. static $ninc = 0;
  219. $ninc ++;
  220. return "'".@trim($typeData['incname'].$ninc)."'";
  221. }
  222. if(!empty($typeData['autoincrement'])){
  223. if($GLOBALS['sugar_config']['dbconfig']['db_type'] == 'oci8'
  224. || $GLOBALS['sugar_config']['dbconfig']['db_type'] == 'ibm_db2'){
  225. return strtoupper($this->table_name. '_' . $field . '_seq') . '.nextval';
  226. }else{
  227. return '';
  228. }
  229. }
  230. if(!empty($typeData['autoincrement'])){
  231. if($GLOBALS['sugar_config']['dbconfig']['db_type'] != 'oci8'){
  232. return '';
  233. }else{
  234. return strtoupper($this->table_name. '_' . $field . '_seq') . '.nextval';
  235. }
  236. }
  237. /* This type alternates between two specified options */
  238. if(!empty($typeData['binary_enum'])){
  239. static $inc = -1;
  240. $inc ++;
  241. return $typeData['binary_enum'][$inc % 2];
  242. }
  243. if(!empty($typeData['sum'])){
  244. $sum = 0;
  245. foreach($typeData['sum'] as $piece){
  246. /* If it is a string, access the
  247. * value of that field. Otherwise
  248. * just treat it as a number.
  249. */
  250. if(is_string($piece)){
  251. $value = $this->accessLocalField($piece);
  252. if(is_numeric($value)){
  253. $sum += $value;
  254. }
  255. }else{
  256. $sum += $piece;
  257. }
  258. }
  259. return $sum;
  260. }
  261. if(!empty($typeData['sum_ref'])){
  262. $sum = 0;
  263. foreach($typeData['sum_ref'] as $piece){
  264. $sum += $this->accessRemoteField($piece['module'], $piece['field']);
  265. }
  266. return $sum;
  267. }
  268. if(!empty($typeData['same'])){
  269. if(is_string($typeData['same']) && !empty($this->fields[$typeData['same']])){
  270. //return $this->accessLocalField($typeData['same']);
  271. $rtn = $this->accessLocalField($typeData['same']);
  272. }else{
  273. //return $typeData['same'];
  274. $rtn = $typeData['same'];
  275. }
  276. if (!empty($typeData['toUpper'])) {
  277. $rtn = strtoupper($rtn);
  278. }
  279. if (!empty($typeData['toLower'])) {
  280. $rtn = strtolower($rtn);
  281. }
  282. return @trim($rtn);
  283. }
  284. if(!empty($typeData['same_ref'])){
  285. /* We aren't going to consider literal values,
  286. * because you can just use 'same' for that.
  287. */
  288. //echo "SR: ";
  289. return @trim($this->accessRemoteField($typeData['same_ref']['module'], $typeData['same_ref']['field']));
  290. }
  291. if(!empty($typeData['same_hash'])){
  292. if(is_string($typeData['same_hash']) && !empty($this->fields[$typeData['same_hash']])){
  293. $value = $this->accessLocalField($typeData['same_hash']);
  294. if(is_string($value)){
  295. $value = substr($value, 1, strlen($value)-2);
  296. }
  297. return "'".md5($value)."'";
  298. }else{
  299. return "'".md5($typeData['same_hash'])."'";
  300. }
  301. }
  302. if(!empty($typeData['related'])){
  303. if(!empty($typeData['related']['ratio'])){
  304. $thisToRelatedRatio = $typeData['related']['ratio'];
  305. }else{
  306. $thisToRelatedRatio = 0;
  307. }
  308. $relModule = $this->getAlias($typeData['related']['module']);
  309. $relUpID = $this->getRelatedUpId($typeData['related']['module'], $thisToRelatedRatio);
  310. $relatedId = $this->assembleId($relModule, $relUpID);
  311. return $relatedId;
  312. }
  313. if (!empty($typeData['gibberish'])) {
  314. $baseValue = @trim($this->generateGibberish($typeData['gibberish']));
  315. // Check field length and truncate data depends on vardefs length
  316. if (!empty($GLOBALS['fieldData']['len']) && $GLOBALS['fieldData']['len'] < strlen($baseValue)){
  317. $baseValue = $this->truncateDataByLength($baseValue, (string) $GLOBALS['fieldData']['len']);
  318. }
  319. return "'" . $baseValue . "'";
  320. }
  321. if(!empty($typeData['meeting_probability'])){
  322. /* If this is for meetings, and it's in the past,
  323. * we need to adjust the probability.
  324. * Note that this will break if date_start comes after
  325. * status in the vardefs for Meetings :-/.
  326. */
  327. if(!empty($GLOBALS['fieldData']['options']) && !empty($GLOBALS['app_list_strings'][$GLOBALS['fieldData']['options']])){
  328. $options = $GLOBALS['app_list_strings'][$GLOBALS['fieldData']['options']];
  329. $keys = array_keys($options);
  330. /* accessLocalField loads the value of that field or
  331. * computes it if it has not been computed.
  332. */
  333. $startDate = $this->accessLocalField('date_start');
  334. $stamp = strtotime(substr($startDate, 1, strlen($startDate)-2));
  335. if($stamp >= mktime()){
  336. $rn = mt_rand(0, 9);
  337. /* 10% chance of being NOT HELD - aka CLOSED */
  338. if($rn > 8){
  339. $selected = 2;
  340. }
  341. else{
  342. $selected = 0;
  343. }
  344. }
  345. else{
  346. $rn = mt_rand(0, 49);
  347. /* 2% chance of being HELD - aka OPEN */
  348. if($rn > 48){
  349. $selected = 0;
  350. }
  351. else{
  352. $selected = 2;
  353. }
  354. }
  355. }
  356. return "'" . @trim($keys[$selected]) . "'";
  357. }
  358. $isQuote = true;
  359. //we have a range then it should either be a number or a date
  360. $baseValue = '';
  361. if(!empty($typeData['range'])){
  362. $baseValue = mt_rand($typeData['range']['min'], $typeData['range']['max']);
  363. if(!empty($typeData['multiply'])){
  364. $baseValue *= $typeData['multiply'];
  365. }
  366. //everything but numbers must have a type so we are just a range
  367. if(!empty($typeData['type'])){
  368. $isQuote = true;
  369. $basetime = (!empty($typeData['basetime']))?$typeData['basetime']: time();
  370. if(!empty($baseValue)){
  371. $basetime += $baseValue * 3600 * 24;
  372. }
  373. switch($typeData['type']){
  374. case 'date': $baseValue = date('Y-m-d', $basetime); break;
  375. case 'datetime': $baseValue = date('Y-m-d H:i:s', $basetime); break;
  376. case 'time': $baseValue = date('H:i:s', $basetime); break;
  377. }
  378. }else{
  379. $isQuote = false;
  380. }
  381. }else if(!empty($typeData['list']) && !empty($GLOBALS[$typeData['list']])){
  382. $selected = mt_rand(0, count($GLOBALS[$typeData['list']]) - 1);
  383. $baseValue = $GLOBALS[$typeData['list']][$selected];
  384. }
  385. if(!empty($typeData['suffixlist'])){
  386. foreach($typeData['suffixlist'] as $suffixlist){
  387. if(!empty($GLOBALS[$suffixlist])){
  388. $selected = mt_rand(0, count($GLOBALS[$suffixlist]) - 1);
  389. $baseValue .= ' ' .$GLOBALS[$suffixlist][$selected];
  390. }
  391. }
  392. }else if($type == 'enum'){
  393. if(!empty($GLOBALS['fieldData']['options']) && !empty($GLOBALS['app_list_strings'][$GLOBALS['fieldData']['options']])){
  394. $options = $GLOBALS['app_list_strings'][$GLOBALS['fieldData']['options']];
  395. $keys = array_keys($options);
  396. $selected = mt_rand(0, count($keys) - 1);
  397. return "'" . @trim($keys[$selected]). "'";
  398. }
  399. }
  400. // This is used to associate email addresses with rows in
  401. // Contacts or Leads. See Relationships/email_addr_bean_rel.php
  402. if (!empty($typeData['getmodule'])) {
  403. $rtn = "'" . $this->module . "'";
  404. return $rtn;
  405. }
  406. if(!empty($typeData['prefixlist'])){
  407. foreach($typeData['prefixlist'] as $prefixlist){
  408. if(!empty($GLOBALS[$prefixlist])){
  409. $selected = mt_rand(0, count($GLOBALS[$prefixlist]) - 1);
  410. $baseValue = $GLOBALS[$prefixlist][$selected] . ' ' . $baseValue;
  411. }
  412. }
  413. }
  414. if(!empty($typeData['suffix'])){
  415. $baseValue .= $typeData['suffix'];
  416. }
  417. if(!empty($typeData['prefix'])){
  418. $baseValue = $typeData['prefix'] . $baseValue;
  419. }
  420. if (!empty($GLOBALS['fieldData']['len']) && $GLOBALS['fieldData']['len'] < strlen($baseValue)) {
  421. $baseValue = $this->truncateDataByLength($baseValue, (string) $GLOBALS['fieldData']['len']);
  422. }
  423. if($isQuote || !empty($typeData['isQuoted']) ){
  424. $baseValue = "'".@trim($baseValue) . "'";
  425. }
  426. // Run db convert only for specific types. see DBManager::convert()
  427. if (!in_array($type, self::$notConvertedTypes)) {
  428. $baseValue = $GLOBALS['db']->convert($baseValue, $type);
  429. }
  430. return $baseValue;
  431. }
  432. /**
  433. * Truncate data value by VarDefs length
  434. *
  435. * @param $value - data base value
  436. * @param $length - could be "integer" or float length value, f.e. "5,2"
  437. * @return string
  438. */
  439. protected function truncateDataByLength($value, $length)
  440. {
  441. $arr = explode(",", $length, 2);
  442. $baseLength = $arr[0];
  443. return substr($value, 0, $baseLength);
  444. }
  445. /**
  446. * Returns the value of this module's field called $fieldname.
  447. * If a value has already been generated, it uses that one, otherwise
  448. * it calls getData() to generate the value.
  449. * @param $fieldName - Name of the local field you want to retrieve
  450. */
  451. function accessLocalField($fieldName){
  452. /* TODO - OPTIMIZATION - if we have to render the data,
  453. * then save it to installData so we only do it once.
  454. * Make sure that generateData checks for it.
  455. * Note that this is safe even when used as a foreign
  456. * object, becuase accessRemoteField calls clean each time.
  457. */
  458. /* We will only deal with fields defined in the
  459. * vardefs.
  460. */
  461. if(!empty($this->fields[$fieldName])){
  462. /* If this data has already been generated,
  463. * then just use it.
  464. */
  465. if(!empty($this->installData[$fieldName])){
  466. //echo "AAA: {$this->installData[$fieldName]}\n";
  467. return $this->installData[$fieldName];
  468. /* Otherwise, we have to pre-render it. */
  469. }else{
  470. $recSeed = $this->generateSeed($this->module, $fieldName, $this->count);
  471. $recData = $this->fields[$fieldName];
  472. $recType = (!empty($recData['dbType']))?$recData['dbType']:$recData['type'];
  473. //echo "BBB: $fieldName, $recType, {$recData['type']}, $recSeed\n";
  474. return $this->getData($fieldName, $recType, $recData['type'], $recSeed);
  475. }
  476. }else{
  477. return $fieldName;
  478. }
  479. }
  480. /**
  481. * Returns the value of $module's field called $fieldName.
  482. * Calls accessLocalField($fieldName) on a separate DataTool object
  483. * for the remote module.
  484. * @param $module - Name of remote module to access.
  485. * @param $fieldName - Name of field in remote module to retrieve.
  486. */
  487. function accessRemoteField($module, $fieldName){
  488. /* Form is 'Module' => field */
  489. /* I need to call $this->getData. */
  490. /* I need to load the Data/Module.php file,
  491. * to make a proper call to getData. */
  492. /* I need the var_defs for the Module, so
  493. * I can access its type or dbType. (only if it's an enum...)*/
  494. /* I also need the remote count of the 'parent' or 'related'
  495. * module. This count would be the one that I get when
  496. * I generate relationships or fill 'related' fields.
  497. */
  498. /* But getData looks at $this->module... ick */
  499. /* Well, I'm loading the vardefs for this class, so I might
  500. * as well load a new dataTool for it.
  501. */
  502. /* 1. Load Data/Module definitions
  503. * 2. Load class[Module]
  504. * 3. Identify related count - ?
  505. * 4. call getData on the one field we want.
  506. */
  507. /* Should one accidentally refer to itsself, just call local */
  508. if($module == $this->module) return $this->accessLocalField($fieldName);
  509. /* Check if a cached dataTool object exists. */
  510. if(!empty($GLOBALS['foreignDataTools']) && !empty($GLOBALS['foreignDataTools'][$module])){
  511. $rbfd = $GLOBALS['foreignDataTools'][$module];
  512. }else{
  513. include('include/modules.php');
  514. $class = $beanList[$module];
  515. require_once($beanFiles[$class]);
  516. $bean = new $class();
  517. if(file_exists('Tidbit/Data/' . $bean->module_dir . '.php')){
  518. require_once('Tidbit/Data/' . $bean->module_dir . '.php');
  519. }
  520. $rbfd = new DataTool();
  521. $rbfd->fields = $bean->field_defs;
  522. $rbfd->table_name = $bean->table_name;
  523. $rbfd->module = $module;
  524. /* Cache the dataTool object. */
  525. $GLOBALS['foreignDataTools'][$module] = $rbfd;
  526. }
  527. $rbfd->clean();
  528. $rbfd->count = $this->getRelatedUpId($module);
  529. return $rbfd->accessLocalField($fieldName);
  530. }
  531. /**
  532. * Generate a 'related' id for use
  533. * by handleType:'related' and 'generateRelationships'
  534. */
  535. function getRelatedId($relModule, $baseModule, $thisToRelatedRatio = 0){
  536. if(empty($GLOBALS['counters'][$this->module.$relModule])){
  537. $GLOBALS['counters'][$this->module.$relModule] = 0;
  538. }
  539. $c = $GLOBALS['counters'][$this->module.$relModule];
  540. /* All ratios can be determined simply by looking
  541. * at the relative counts in $GLOBALS['modules']
  542. */
  543. /* Such a module must exist. */
  544. if(!empty($GLOBALS['modules'][$relModule])){
  545. $baseToRelatedRatio = $GLOBALS['modules'][$relModule] / $GLOBALS['modules'][$baseModule];
  546. $baseToThisRatio = $GLOBALS['modules'][$this->module] / $GLOBALS['modules'][$baseModule];
  547. /* floor($this->count/$acctToThisRatio) determines what 'group'
  548. * this record is a part of. Multiplying by $acctToRelatedRatio
  549. * gives us the starting record for the group of the related type.
  550. */
  551. $n = floor(floor($this->count/$baseToThisRatio)*$baseToRelatedRatio);
  552. $GLOBALS['counters'][$this->module.$relModule]++;
  553. /* There are $acctToRelatedRatio of the related types
  554. * in the group, so we can just pick one of them.
  555. */
  556. return $n + ($c%ceil($baseToRelatedRatio));
  557. }
  558. }
  559. /**
  560. * Generate a 'parent' id for use
  561. * by handleType:'parent'
  562. */
  563. function getRelatedUpId($relModule, $thisToRelatedRatio = 0){
  564. /* The relModule that we point up to should be the base */
  565. return $this->getRelatedId($relModule,$relModule,$thisToRelatedRatio);
  566. }
  567. /**
  568. * Generate a 'parent' id for use
  569. * by handleType:'parent'
  570. */
  571. function getRelatedLinkId($relModule, $thisToRelatedRatio = 0){
  572. /* The baseModule needs to be Accounts normally
  573. * but we need to keep Quotes inclusive
  574. * and Teams and Users, which are above Accounts,
  575. * need to have themselves as the base.
  576. */
  577. if($relModule == 'Teams'){
  578. $baseModule = 'Teams';
  579. }elseif($this->module == 'Users'){
  580. $baseModule = 'Users';
  581. }elseif($this->module == 'ProductBundles'){
  582. $baseModule = 'Quotes';
  583. }else{
  584. $baseModule = 'Accounts';
  585. }
  586. return $this->getRelatedId($relModule,$baseModule,$thisToRelatedRatio);
  587. }
  588. /**
  589. * Creates the query head and query bodies, and saves them in global arrays
  590. * $queryHead and $queries, respectively.
  591. */
  592. function createInserts(){
  593. if(empty($GLOBALS['queryHead'])){
  594. $GLOBALS['queryHead'] = $this->createInsertHead($this->table_name);
  595. }
  596. $GLOBALS['queries'][] = $this->createInsertBody();
  597. $_SESSION['allProcessedRecords']++;
  598. }
  599. function createInsertHead($table){
  600. return 'INSERT INTO ' . $table . ' ( ' .implode(', ',array_keys($this->installData)) . ') VALUES ';
  601. }
  602. function createInsertBody(){
  603. return ' ( ' .implode(', ',array_values($this->installData)). ' )';
  604. }
  605. /**
  606. * Generates and saves queries to create relationships in the Sugar app, based
  607. * on the contents of the global array $tidbit_relationships.
  608. */
  609. function generateRelationships(){
  610. global $relQueryCount;
  611. $baseId = $this->installData['id'];
  612. if(empty($GLOBALS['tidbit_relationships'][$this->module]))return;
  613. foreach($GLOBALS['tidbit_relationships'][$this->module] as $relModule=>$relationship){
  614. // TODO: remove this check or replace with something else
  615. if (!is_dir('modules/' . $relModule)) {
  616. continue;
  617. }
  618. if(!empty($GLOBALS['modules'][$relModule])){
  619. if(!empty($relationship['ratio'])){
  620. $thisToRelatedRatio = $relationship['ratio'];
  621. }else{
  622. $thisToRelatedRatio = $GLOBALS['modules'][$relModule] / $GLOBALS['modules'][$this->module];
  623. }
  624. /* Load any custom feilds for this relationship */
  625. if(file_exists('Tidbit/Relationships/' . $relationship['table'] . '.php')){
  626. //echo "\n". 'loading custom fields from ' . 'Tidbit/Relationships/' . $relationship['table'] . '.php' . "\n";
  627. require_once('Tidbit/Relationships/' . $relationship['table'] . '.php');
  628. }
  629. /* According to $relationship['ratio'],
  630. * we attach that many of the related object to the current object
  631. * through $relationship['table']
  632. */
  633. for($j = 0; $j < $thisToRelatedRatio; $j++){
  634. $currentRelModule = $this->getAlias($relModule);
  635. $relId = $this->assembleId($currentRelModule, $this->getRelatedLinkId($relModule), false);
  636. $relOverridesStore = array();
  637. /* If a repeat factor is specified, then we will process the body multiple times. */
  638. if(!empty($GLOBALS['dataTool'][$relationship['table']]) && !empty($GLOBALS['dataTool'][$relationship['table']]['repeat'])){
  639. $multiply = $GLOBALS['dataTool'][$relationship['table']]['repeat']['factor'];
  640. /* We don't want 'repeat' to get into the DB, but we'll put it back into
  641. * the globals later.
  642. */
  643. $relOverridesStore = $GLOBALS['dataTool'][$relationship['table']];
  644. unset($GLOBALS['dataTool'][$relationship['table']]['repeat']);
  645. }else{
  646. $multiply = 1;
  647. }
  648. /* Normally $multiply == 1 */
  649. while($multiply--){
  650. $GLOBALS['relatedQueries'][$relationship['table']][] = $this->generateRelationshipBody($relationship, $baseId, $relId);
  651. }
  652. $_SESSION['allProcessedRecords']++;
  653. if(empty($GLOBALS['relatedQueries'][$relationship['table']]['head'])){
  654. $GLOBALS['relatedQueries'][$relationship['table']]['head'] = $this->generateRelationshipHead($relationship);
  655. }
  656. /* Restore the relationship settings */
  657. if($relOverridesStore){
  658. $GLOBALS['dataTool'][$relationship['table']] = $relOverridesStore;
  659. }
  660. $relQueryCount++;
  661. }
  662. }
  663. }
  664. }
  665. /**
  666. * Returns the common head shared by all the current relationship queries.
  667. * @param $relationship - Array defining the relationship, from global $tidbit_relationships[$module]
  668. */
  669. function generateRelationshipHead($relationship){
  670. /* Include custom fields in our header */
  671. $customFields = '';
  672. if(!empty($GLOBALS['dataTool'][$relationship['table']])){
  673. foreach($GLOBALS['dataTool'][$relationship['table']] as $field => $typeData){
  674. $customFields .= ', ' . $field;
  675. }
  676. }
  677. return 'INSERT INTO ' . $relationship['table'] . '(id, '
  678. . $relationship['self'] . ', ' . $relationship['you']
  679. . ',' . 'deleted, date_modified' . $customFields
  680. .') VALUES ';
  681. }
  682. /**
  683. * Returns the body for the current relationship query.
  684. *
  685. * @param $relationship - Array defining the relationship, from global $tidbit_relationships[$module]
  686. * @param $baseId - Current module id
  687. * @param $relId - Id for the related module
  688. * @return string
  689. */
  690. function generateRelationshipBody($relationship, $baseId, $relId)
  691. {
  692. static $relCounter = 0;
  693. if ($relCounter == 0) {
  694. $relCounter = !empty($_GET['offset']) ? $_GET['offset'] : 0;
  695. }
  696. $relCounter++;
  697. $date = $this->getConvertDatetime();
  698. $customData = '';
  699. if (!empty($GLOBALS['dataTool'][$relationship['table']])) {
  700. foreach($GLOBALS['dataTool'][$relationship['table']] as $field => $typeData){
  701. $seed = $this->generateSeed($this->module, $field, $this->count);
  702. $customData .= ', ' . $this->handleType($typeData, '', $field, $seed);
  703. }
  704. }
  705. return ' (' ."'seed-rel" . time() . $relCounter . "',$baseId , '$relId' , 0 , $date".$customData." )";
  706. }
  707. /**
  708. * Cache datetime generation and convert to db format
  709. * Based on xhprof data, this operation in time consuming, so we need to cache that
  710. *
  711. * @return mixed
  712. */
  713. function getConvertDatetime()
  714. {
  715. static $datetime = '';
  716. self::$datetimeCacheIndex++;
  717. if ((self::$datetimeCacheIndex > self::$datetimeIndexMax) || empty($datetime)) {
  718. $datetime = $GLOBALS['db']->convert("'".date('Y-m-d H:i:s') ."'", 'datetime') ;
  719. self::$datetimeCacheIndex = 0;
  720. }
  721. return $datetime;
  722. }
  723. /**
  724. * Returns a gibberish string based on a reordering of the base text
  725. * (Lorem ipsum dolor sit amet, ....)
  726. *
  727. * @param $wordCount
  728. * @return string
  729. */
  730. function generateGibberish($wordCount = 1)
  731. {
  732. static $words = array();
  733. if (empty($words)) {
  734. $baseText = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc pulvinar tellus et arcu. Integer venenatis nonummy risus. Sed turpis lorem, cursus sit amet, eleifend at, dapibus vitae, lacus. Nunc in leo ac justo volutpat accumsan. In venenatis consectetuer ante. Proin tempus sapien et sapien. Nunc ante turpis, bibendum sed, pharetra a, eleifend porta, augue. Curabitur et nulla. Proin tristique erat. In non turpis. In lorem mauris, iaculis ac, feugiat sed, bibendum eu, enim. Donec pede. Phasellus sem risus, fermentum in, imperdiet vel, mattis nec, justo. Nullam vitae risus. Fusce neque. Mauris malesuada euismod magna. Sed nibh pede, consectetuer quis, condimentum sit amet, pretium ut, eros. Quisque nec arcu. Sed ac neque. Maecenas volutpat erat ac est. Nam mauris. Sed condimentum cursus purus. Integer facilisis. Duis libero ante, cursus nec, congue nec, imperdiet et, ligula. Pellentesque porttitor suscipit nulla. Integer diam magna, luctus rutrum, luctus sit amet, euismod a, diam. Nunc vel eros faucibus velit lobortis faucibus. Phasellus ultrices, nisl id pulvinar fringilla, justo augue elementum enim, eget tincidunt dolor pede et tortor. Vestibulum at justo vitae sem auctor tincidunt. Maecenas facilisis volutpat dui. Pellentesque non justo. Quisque eleifend, tellus quis venenatis volutpat, ipsum purus cursus dolor, in aliquam magna sem.";
  735. $words = explode(' ', $baseText);
  736. }
  737. shuffle($words);
  738. if ($wordCount > 0) {
  739. $words = array_slice($words, 0, $wordCount);
  740. }
  741. return implode(' ', $words);
  742. }
  743. /**
  744. * Assemble Bean id string by module and related/count IDs
  745. *
  746. * @param string $module
  747. * @param int $id
  748. * @param bool $quotes
  749. * @return string
  750. */
  751. function assembleId($module, $id, $quotes = true)
  752. {
  753. static $assembleIdCache = array();
  754. if (empty($assembleIdCache[$module])) {
  755. $assembleIdCache[$module] = (($module == 'Users') || ($module == 'Teams'))
  756. ? 'seed-' . $module
  757. : 'seed-' . $module . $_SESSION['baseTime'];
  758. }
  759. $seedId = $assembleIdCache[$module] . $id;
  760. // should return id be quoted or not
  761. if ($quotes) {
  762. $seedId = "'" . $seedId . "'";
  763. }
  764. return $seedId;
  765. }
  766. /*
  767. * TODO - OPTIMIZATION - cache sums of $fieldName and $this->module
  768. * somewhere, sessions maybe.
  769. * DONE: cache in static properties
  770. */
  771. /**
  772. * Returns a seed to be used with the RNG.
  773. *
  774. * @param string $module - The current module
  775. * @param string $field - The current field
  776. * @param int $count - The current record number
  777. * @return int
  778. */
  779. function generateSeed($module, $field, $count)
  780. {
  781. // Cache module
  782. if (!isset(self::$seedModules[$module])) {
  783. self::$seedModules[$module] = $this->str_sum($this->module);
  784. }
  785. // Cache fields
  786. if (!isset(self::$seedFields[$field])) {
  787. self::$seedFields[$field] = $this->str_sum($field);
  788. }
  789. /*
  790. * We multiply by two because mt_srand
  791. * doesn't work well when you give it
  792. * consecutive integers.
  793. */
  794. return 2 * (self::$seedModules[$module] + self::$seedFields[$field] + $count + $_SESSION['baseTime']);
  795. }
  796. /**
  797. * Returns an alias to be used for id generation.
  798. * @param $name - The current module
  799. * @return string
  800. */
  801. function getAlias($name) {
  802. global $aliases;
  803. return (isset ($aliases[$name]))
  804. ? $aliases[$name]
  805. : $name;
  806. }
  807. /**
  808. * Calculate string check sum
  809. *
  810. * @param $str
  811. * @return int
  812. */
  813. function str_sum($str)
  814. {
  815. $sum = 0;
  816. for ($i = strlen($str); $i--;) {
  817. $sum += ord($str[$i]);
  818. }
  819. return $sum;
  820. }
  821. }