EPESI BIM • Business Information Manager • Cloud CRM/ERP http://epe.si/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3762 lines
172 KiB

  1. <?php
  2. /**
  3. * RecordBrowserCommon class.
  4. *
  5. * @author Arkadiusz Bisaga <abisaga@telaxus.com>
  6. * @copyright Copyright &copy; 2008, Telaxus LLC
  7. * @license MIT
  8. * @version 1.0
  9. * @package epesi-utils
  10. * @subpackage RecordBrowser
  11. */
  12. defined("_VALID_ACCESS") || die('Direct access forbidden');
  13. class Utils_RecordBrowserCommon extends ModuleCommon {
  14. private static $del_or_a = '';
  15. public static $admin_filter = '';
  16. public static $table_rows = array();
  17. public static $hash = array();
  18. public static $admin_access = false;
  19. public static $cols_order = array();
  20. public static $options_limit = 50;
  21. public static $display_callback_table = array();
  22. private static $clear_get_val_cache = false;
  23. public static function display_callback_cache($tab) {
  24. if (self::$clear_get_val_cache) {
  25. self::$clear_get_val_cache = false;
  26. self::$display_callback_table = array();
  27. }
  28. if($tab=='__RECORDSETS__' || preg_match('/,/',$tab)) return;
  29. if (!isset(self::$display_callback_table[$tab])) {
  30. $ret = DB::Execute('SELECT * FROM '.$tab.'_callback WHERE freezed=1');
  31. while ($row = $ret->FetchRow())
  32. self::$display_callback_table[$tab][$row['field']] = $row['callback'];
  33. }
  34. }
  35. public static function callback_check_function($callback, $only_check_syntax = false)
  36. {
  37. if (is_array($callback)) $callback = implode('::', $callback);
  38. $func = null;
  39. if (preg_match('/^([\\\\a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*)::([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$/', $callback, $match)) {
  40. $func = array($match[1], $match[2]);
  41. } elseif (preg_match('/^([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$/', $callback, $match)) {
  42. $func = $match[1];
  43. }
  44. if (is_callable($func, $only_check_syntax)) {
  45. return $func;
  46. }
  47. return false;
  48. }
  49. public static function call_display_callback($callback, $record, $links_not_recommended, $field, $tab)
  50. {
  51. $callback_func = self::callback_check_function($callback, true);
  52. if ($callback_func) {
  53. if (is_callable($callback_func)) {
  54. $ret = call_user_func($callback_func, $record, $links_not_recommended, $field, $tab);
  55. } else {
  56. $callback_str = (is_array($callback_func) ? implode('::', $callback_func) : $callback_func);
  57. trigger_error("Callback $callback_str for field: '$field[id]', recordset: '$tab' not found", E_USER_NOTICE);
  58. $ret = $record[$field['id']];
  59. }
  60. } else {
  61. ob_start();
  62. $ret = eval($callback);
  63. if($ret===false) trigger_error($callback,E_USER_ERROR);
  64. else print($ret);
  65. $ret = ob_get_contents();
  66. ob_end_clean();
  67. }
  68. return $ret;
  69. }
  70. public static function call_QFfield_callback($callback, &$form, $field, $label, $mode, $default, $desc, $rb_obj, $display_callback_table = null)
  71. {
  72. if ($display_callback_table === null) {
  73. $display_callback_table = self::display_callback_cache($rb_obj->tab);
  74. }
  75. $callback_func = self::callback_check_function($callback, true);
  76. if ($callback_func) {
  77. if (is_callable($callback_func)) {
  78. call_user_func_array($callback_func, array(&$form, $field, $label, $mode, $default, $desc, $rb_obj, $display_callback_table));
  79. } else {
  80. $callback_str = (is_array($callback_func) ? implode('::', $callback_func) : $callback_func);
  81. trigger_error("Callback $callback_str for field: '$field', recordset: '{$rb_obj->tab}' not found", E_USER_NOTICE);
  82. }
  83. } else {
  84. eval($callback);
  85. }
  86. }
  87. public static function get_val($tab, $field, $record, $links_not_recommended = false, $desc = null) {
  88. static $recurrence_call_stack = array();
  89. self::init($tab);
  90. if (!isset(self::$table_rows[$field])) {
  91. if (!isset(self::$hash[$field])) trigger_error('Unknown field "'.$field.'" for recordset "'.$tab.'"',E_USER_ERROR);
  92. $field = self::$hash[$field];
  93. }
  94. if ($desc===null) $desc = self::$table_rows[$field];
  95. if(!array_key_exists('id',$record)) $record['id'] = null;
  96. if (!array_key_exists($desc['id'],$record)) trigger_error($desc['id'].' - unknown field for record '.serialize($record), E_USER_ERROR);
  97. $val = $record[$desc['id']];
  98. $function_call_id = implode('|', array($tab, $field, serialize($val)));
  99. if (isset($recurrence_call_stack[$function_call_id])) {
  100. return '!! ' . __('recurrence issue') . ' !!';
  101. } else {
  102. $recurrence_call_stack[$function_call_id] = true;
  103. }
  104. self::display_callback_cache($tab);
  105. if (isset(self::$display_callback_table[$tab][$field])) {
  106. $display_callback = self::$display_callback_table[$tab][$field];
  107. } else {
  108. $display_callback = self::get_default_display_callback($desc['type']);
  109. }
  110. if ($display_callback) {
  111. $ret = self::call_display_callback($display_callback, $record, $links_not_recommended, $desc, $tab);
  112. } else {
  113. $ret = $val;
  114. }
  115. unset($recurrence_call_stack[$function_call_id]);
  116. return $ret;
  117. }
  118. ////////////////////////////
  119. // default display callbacks
  120. public static function get_default_display_callback($type) {
  121. $types = array('select', 'multiselect', 'commondata', 'autonumber', 'currency', 'checkbox',
  122. 'date', 'timestamp', 'time', 'long text', 'file');
  123. if (array_search($type, $types) !== false) {
  124. return __CLASS__. '::display_' . self::get_field_id($type);
  125. }
  126. return null;
  127. }
  128. public static function display_select($record, $nolink=false, $desc=null, $tab=null) {
  129. $ret = '---';
  130. if (isset($desc['id']) && isset($record[$desc['id']]) && $record[$desc['id']]!=='') {
  131. $val = $record[$desc['id']];
  132. $commondata_sep = '/';
  133. if ((is_array($val) && empty($val))) return $ret;
  134. $param = self::decode_select_param($desc['param']);
  135. if(!$param['array_id'] && $param['single_tab'] == '__COMMON__') return;
  136. if (!is_array($val)) $val = array($val);
  137. $ret = '';
  138. foreach ($val as $v) {
  139. $ret .= ($ret!=='')? '<br>': '';
  140. if ($param['single_tab'] == '__COMMON__') {
  141. $array_id = $param['array_id'];
  142. $path = explode('/', $v);
  143. $tooltip = '';
  144. $res = '';
  145. if (count($path) > 1) {
  146. $res .= Utils_CommonDataCommon::get_value($array_id . '/' . $path[0], true);
  147. if (count($path) > 2) {
  148. $res .= $commondata_sep . '...';
  149. $tooltip = '';
  150. $full_path = $array_id;
  151. foreach ($path as $w) {
  152. $full_path .= '/' . $w;
  153. $tooltip .= ($tooltip? $commondata_sep: '').Utils_CommonDataCommon::get_value($full_path, true);
  154. }
  155. }
  156. $res .= $commondata_sep;
  157. }
  158. $label = Utils_CommonDataCommon::get_value($array_id . '/' . $v, true);
  159. if (!$label) continue;
  160. $res .= $label;
  161. $res = self::no_wrap($res);
  162. if ($tooltip) $res = '<span '.Utils_TooltipCommon::open_tag_attrs($tooltip, false) . '>' . $res . '</span>';
  163. } else {
  164. $tab_id = self::decode_record_token($v, $param['single_tab']);
  165. if (!$tab_id) continue;
  166. list($select_tab, $id) = $tab_id;
  167. if ($param['cols']) {
  168. $res = self::create_linked_label($select_tab, $param['cols'], $id, $nolink);
  169. } else {
  170. $res = self::create_default_linked_label($select_tab, $id, $nolink);
  171. }
  172. }
  173. $ret .= $res;
  174. }
  175. }
  176. return $ret;
  177. }
  178. public static function display_multiselect($record, $nolink=false, $desc=null, $tab=null) {
  179. return self::display_select($record, $nolink, $desc, $tab);
  180. }
  181. public static function display_commondata($record, $nolink=false, $desc=null, $tab=null) {
  182. $ret = '';
  183. if (isset($desc['id']) && isset($record[$desc['id']]) && $record[$desc['id']]!=='') {
  184. $arr = explode('::', $desc['param']['array_id']);
  185. $path = array_shift($arr);
  186. foreach($arr as $v) $path .= '/' . $record[self::get_field_id($v)];
  187. $path .= '/' . $record[$desc['id']];
  188. $ret = Utils_CommonDataCommon::get_value($path, true);
  189. }
  190. return $ret;
  191. }
  192. public static function display_autonumber($record, $nolink=false, $desc=null, $tab=null) {
  193. $ret = '';
  194. if (isset($desc['id']) && isset($record[$desc['id']]) && $record[$desc['id']]!=='') {
  195. $ret = $record[$desc['id']];
  196. if (!$nolink && isset($record['id']) && $record['id'])
  197. $ret = self::record_link_open_tag_r($tab, $record) . $ret . self::record_link_close_tag();
  198. }
  199. return $ret;
  200. }
  201. public static function display_currency($record, $nolink=false, $desc=null, $tab=null) {
  202. $ret = '';
  203. if (isset($desc['id']) && isset($record[$desc['id']]) && $record[$desc['id']]!=='') {
  204. $val = Utils_CurrencyFieldCommon::get_values($record[$desc['id']]);
  205. $ret = Utils_CurrencyFieldCommon::format($val[0], $val[1]);
  206. }
  207. return $ret;
  208. }
  209. public static function display_checkbox($record, $nolink=false, $desc=null, $tab=null) {
  210. $ret = '';
  211. if (isset($desc['id']) && array_key_exists($desc['id'], $record)) {
  212. $ret = $record[$desc['id']]? __('Yes'): __('No');
  213. }
  214. return $ret;
  215. }
  216. public static function display_checkbox_icon($record, $nolink, $desc=null) {
  217. $ret = '';
  218. if (isset($desc['id']) && isset($record[$desc['id']]) && $record[$desc['id']]!=='') {
  219. $ret = '<img src="'.Base_ThemeCommon::get_template_file('images', ($record[$desc['id']]? 'checkbox_on': 'checkbox_off') . '.png') .'">';;
  220. }
  221. return $ret;
  222. }
  223. public static function display_date($record, $nolink, $desc=null) {
  224. $ret = '';
  225. if (isset($desc['id']) && isset($record[$desc['id']]) && $record[$desc['id']]!=='') {
  226. $ret = Base_RegionalSettingsCommon::time2reg($record[$desc['id']], false, true, false);
  227. }
  228. return $ret;
  229. }
  230. public static function display_timestamp($record, $nolink, $desc=null) {
  231. $ret = '';
  232. if (isset($desc['id']) && isset($record[$desc['id']]) && $record[$desc['id']]!=='') {
  233. $ret = Base_RegionalSettingsCommon::time2reg($record[$desc['id']], 'without_seconds');
  234. }
  235. return $ret;
  236. }
  237. public static function display_time($record, $nolink, $desc=null) {
  238. $ret = '';
  239. if (isset($desc['id']) && isset($record[$desc['id']])) {
  240. $ret = $record[$desc['id']] !== '' && $record[$desc['id']] !== false
  241. ? Base_RegionalSettingsCommon::time2reg($record[$desc['id']], 'without_seconds', false)
  242. : '---';
  243. }
  244. return $ret;
  245. }
  246. public static function display_long_text($record, $nolink, $desc=null) {
  247. $ret = '';
  248. if (isset($desc['id']) && isset($record[$desc['id']]) && $record[$desc['id']]!=='') {
  249. $ret = self::format_long_text($record[$desc['id']]);
  250. }
  251. return $ret;
  252. }
  253. public static function multiselect_from_common($arrid) {
  254. return '__COMMON__::'.$arrid;
  255. }
  256. public static function format_long_text($text){
  257. $ret = htmlspecialchars($text);
  258. $ret = str_replace("\n",'<br>',$ret);
  259. $ret = Utils_BBCodeCommon::parse($ret);
  260. return $ret;
  261. }
  262. public static function encode_multi($v) {
  263. if ($v==='') return '';
  264. if (is_array($v)) {
  265. if (empty($v)) return '';
  266. } else $v = array($v);
  267. return '__'.implode('__',$v).'__';
  268. }
  269. public static function decode_multi($v) {
  270. if ($v===null) return array();
  271. if(is_array($v)) return $v;
  272. $v = explode('__',$v);
  273. array_shift($v);
  274. array_pop($v);
  275. $ret = array();
  276. foreach ($v as $w)
  277. $ret[$w] = $w;
  278. return $ret;
  279. }
  280. public static function decode_commondata_param($param) {
  281. $param = explode('__',$param);
  282. if (isset($param[1])) {
  283. $order = Utils_CommonDataCommon::validate_order($param[0]);
  284. $array_id = $param[1];
  285. } else {
  286. $order = 'value';
  287. $array_id = $param[0];
  288. }
  289. return array('order'=>$order, 'order_by_key'=>$order, 'array_id'=>$array_id);
  290. }
  291. public static function encode_commondata_param($param) {
  292. if (!is_array($param))
  293. $param = array($param);
  294. $order = 'value';
  295. if (isset($param['order']) || isset($param['order_by_key'])) {
  296. $order = Utils_CommonDataCommon::validate_order(isset($param['order'])? $param['order']: $param['order_by_key']);
  297. unset($param['order']);
  298. unset($param['order_by_key']);
  299. }
  300. $array_id = implode('::', $param);
  301. return implode('__', array($order, $array_id));
  302. }
  303. public static function decode_autonumber_param($param, &$prefix, &$pad_length, &$pad_mask) {
  304. $parsed = explode('__', $param, 4);
  305. if (!is_array($parsed) || count($parsed) != 3)
  306. trigger_error("Not well formed autonumber parameter: $param", E_USER_ERROR);
  307. list($prefix, $pad_length, $pad_mask) = $parsed;
  308. }
  309. public static function encode_autonumber_param($prefix, $pad_length, $pad_mask) {
  310. return implode('__', array($prefix, $pad_length, $pad_mask));
  311. }
  312. public static function format_autonumber_str($param, $id) {
  313. self::decode_autonumber_param($param, $prefix, $pad_length, $pad_mask);
  314. if ($id === null)
  315. $pad_mask = '?';
  316. return $prefix . str_pad($id, $pad_length, $pad_mask, STR_PAD_LEFT);
  317. }
  318. public static function format_autonumber_str_all_records($tab, $field, $param) {
  319. self::decode_autonumber_param($param, $prefix, $pad_length, $pad_mask);
  320. $ids = DB::GetCol('SELECT id FROM '.$tab.'_data_1');
  321. DB::StartTrans();
  322. foreach ($ids as $id) {
  323. $str = $prefix . str_pad($id, $pad_length, $pad_mask, STR_PAD_LEFT);
  324. DB::Execute('UPDATE ' . $tab . '_data_1 SET indexed = 0, f_' . $field . '=%s WHERE id=%d', array($str, $id));
  325. }
  326. DB::CompleteTrans();
  327. }
  328. public static function decode_select_param($param) {
  329. if (is_array($param)) return $param;
  330. $param = explode(';', $param);
  331. $reference = explode('::', $param[0]);
  332. $crits_callback = isset($param[1]) && $param[1] != '::' ? explode('::', $param[1]): null;
  333. $adv_params_callback = isset($param[2]) && $param[2] != '::' ? explode('::', $param[2]): null;
  334. //in case RB records select
  335. $select_tab = $reference[0];
  336. $cols = isset($reference[1]) ? array_filter(explode('|', $reference[1])) : null;
  337. //in case __COMMON__
  338. $array_id = isset($reference[1])? $reference[1]: null;
  339. $order = isset($reference[2])? $reference[2]: 'value';
  340. if ($select_tab == '__RECORDSETS__') {
  341. $select_tabs = DB::GetCol('SELECT tab FROM recordbrowser_table_properties');
  342. $single_tab = false;
  343. }
  344. else {
  345. $select_tabs = array_filter(explode(',',$select_tab));
  346. $single_tab = count($select_tabs)==1? $select_tab: false;
  347. }
  348. return array(
  349. 'single_tab'=>$single_tab? $select_tab: false, //returns single tab name, __COMMON__ or false
  350. 'select_tabs'=>$select_tabs, //returns array of tab names
  351. 'cols'=>$cols, // returns array of columns for formatting the display value (used in case RB records select)
  352. 'array_id'=>$array_id, //returns array_id (used in case __COMMON__)
  353. 'order'=>$order, //returns order code (used in case __COMMON__)
  354. 'crits_callback'=>$crits_callback, //returns crits callback (used in case RB records select)
  355. 'adv_params_callback'=>$adv_params_callback //returns adv_params_callback (used in case RB records select)
  356. );
  357. }
  358. public static function decode_record_token($token, $single_tab=false) {
  359. //#merge, in bootstrap limit of explode = 2
  360. $kk = explode('/',$token);
  361. if(count($kk)==1) {
  362. if ($single_tab && is_numeric($kk[0])) {
  363. $tab = $single_tab;
  364. $record_id = $kk[0];
  365. } else return false;
  366. } else {
  367. $tab = $kk[0];
  368. $record_id = $kk[1];
  369. if (!self::check_table_name($tab) || !is_numeric($record_id)) return false;
  370. }
  371. return array('tab'=>$tab, 'id'=>$record_id, $tab, $record_id);
  372. }
  373. public static function call_select_adv_params_callback($callback, $record=null) {
  374. $ret = array(
  375. 'order'=>array(),
  376. 'cols'=>array(),
  377. 'format_callback'=>array('Utils_RecordBrowserCommon', 'autoselect_label')
  378. );
  379. $adv_params = array();
  380. if (is_callable($callback))
  381. $adv_params = call_user_func($callback, $record);
  382. if (!is_array($adv_params))
  383. $adv_params = array();
  384. return array_merge($ret, $adv_params);
  385. }
  386. public static function get_select_tab_crits($param, $record=null) {
  387. $param = self::decode_select_param($param);
  388. $ret = array();
  389. if (is_callable($param['crits_callback']))
  390. $ret = call_user_func($param['crits_callback'], false, $record);
  391. $tabs = $param['select_tabs'];
  392. $ret = !empty($ret)? $ret: array();
  393. if ($param['single_tab'] && (!is_array($ret) || !isset($ret[$param['single_tab']]))) {
  394. $ret = array($param['single_tab'] => $ret);
  395. }
  396. elseif(is_array($ret) && !array_intersect($tabs, array_keys($ret))) {
  397. $tab_crits = array();
  398. foreach($tabs as $tab)
  399. $tab_crits[$tab] = $ret;
  400. $ret = $tab_crits;
  401. }
  402. foreach ($ret as $tab=>$crits) {
  403. if (!$tab || !self::check_table_name($tab, false, false)) {
  404. unset($ret[$tab]);
  405. continue;
  406. }
  407. $access_crits = self::get_access_crits($tab, 'selection');
  408. if ($access_crits===false) unset($ret[$tab]);
  409. if ($access_crits===true) continue;
  410. if (is_array($access_crits) || $access_crits instanceof Utils_RecordBrowser_CritsInterface) {
  411. if((is_array($crits) && $crits) || $crits instanceof Utils_RecordBrowser_CritsInterface)
  412. $ret[$tab] = self::merge_crits($crits, $access_crits);
  413. else
  414. $ret[$tab] = $access_crits;
  415. }
  416. }
  417. return $ret;
  418. }
  419. public static function call_select_item_format_callback($callback, $tab_id, $args) {
  420. // $args = array($tab, $tab_crits, $format_callback, $params);
  421. $param = self::decode_select_param($args[3]);
  422. $val = self::decode_record_token($tab_id, $param['single_tab']);
  423. if (!$val) return '';
  424. list($tab, $record_id) = $val;
  425. $tab_caption = '';
  426. if (!$param['single_tab']) {
  427. $tab_caption = self::get_caption($tab);
  428. $tab_caption = '[' . ((!$tab_caption || $tab_caption == '---')? $tab: $tab_caption) . '] ';
  429. }
  430. $callback = is_callable($callback)? $callback: array('Utils_RecordBrowserCommon', 'autoselect_label');
  431. return $tab_caption . call_user_func($callback, $tab_id, $args);
  432. }
  433. public static function user_settings(){
  434. $ret = DB::Execute('SELECT tab, caption, icon, recent, favorites, full_history FROM recordbrowser_table_properties');
  435. $settings = array(0=>array(), 1=>array(), 2=>array(), 3=>array());
  436. while ($row = $ret->FetchRow()) {
  437. $caption = _V($row['caption']); // ****** RecordBrowser - recordset caption
  438. if (!self::get_access($row['tab'],'browse')) continue;
  439. if ($row['favorites'] || $row['recent']) {
  440. $options = array('all'=>__('All'));
  441. if ($row['favorites']) $options['favorites'] = __('Favorites');
  442. if ($row['recent']) $options['recent'] = __('Recent');
  443. if (Utils_WatchdogCommon::category_exists($row['tab'])) $options['watchdog'] = __('Watched');
  444. $settings[0][] = array('name'=>$row['tab'].'_default_view','label'=>$caption,'type'=>'select','values'=>$options,'default'=>'all');
  445. }
  446. if ($row['favorites'])
  447. $settings[1][] = array('name'=>$row['tab'].'_auto_fav','label'=>$caption,'type'=>'select','values'=>array(__('Disabled'), __('Enabled')),'default'=>0);
  448. if (Utils_WatchdogCommon::category_exists($row['tab'])) {
  449. $settings[2][] = array('name'=>$row['tab'].'_auto_subs','label'=>$caption,'type'=>'select','values'=>array(__('Disabled'), __('Enabled')),'default'=>0);
  450. }
  451. $settings[0][] = array('name'=>$row['tab'].'_show_filters','label'=>'','type'=>'hidden','default'=>0);
  452. }
  453. $final_settings = array();
  454. $subscribe_settings = array();
  455. $final_settings[] = array('name'=>'add_in_table_shown','label'=>__('Quick new record - show by default'),'type'=>'checkbox','default'=>0);
  456. $final_settings[] = array('name'=>'hide_empty','label'=>__('Hide empty fields'),'type'=>'checkbox','default'=>0);
  457. $final_settings[] = array('name'=>'enable_autocomplete','label'=>__('Enable autocomplete in select/multiselect at'),'type'=>'select','default'=>50, 'values'=>array(0=>__('Always'), 20=>__('%s records', array(20)), 50=>__('%s records', array(50)), 100=>__('%s records', array(100))));
  458. $final_settings[] = array('name'=>'grid','label'=>__('Grid edit (experimental)'),'type'=>'checkbox','default'=>0);
  459. $final_settings[] = array('name'=>'confirm_leave','label'=>__('Confirm before leave edit page'),'type'=>'checkbox','default'=>1);
  460. $final_settings[] = array('name'=>'header_default_view','label'=>__('Default data view'),'type'=>'header');
  461. $final_settings = array_merge($final_settings,$settings[0]);
  462. $final_settings[] = array('name'=>'header_auto_fav','label'=>__('Automatically add to favorites records created by me'),'type'=>'header');
  463. $final_settings = array_merge($final_settings,$settings[1]);
  464. $final_settings[] = array('name'=>'header_auto_subscriptions','label'=>__('Automatically watch records created by me'),'type'=>'header');
  465. $final_settings = array_merge($final_settings,$settings[2]);
  466. return array(__('Browsing records')=>$final_settings);
  467. }
  468. public static function check_table_name($tab, $flush=false, $failure_on_missing=true){
  469. static $tables = null;
  470. if($tab=='__RECORDSETS__' || preg_match('/,/',$tab)) return true;
  471. if ($tables===null || $flush) {
  472. $r = DB::GetAll('SELECT tab FROM recordbrowser_table_properties');
  473. $tables = array();
  474. foreach($r as $v)
  475. $tables[$v['tab']] = true;
  476. }
  477. if (!isset($tables[$tab]) && !$flush && $failure_on_missing) trigger_error('RecordBrowser critical failure, terminating. (Requested '.serialize($tab).', available '.print_r($tables, true).')', E_USER_ERROR);
  478. return isset($tables[$tab]);
  479. }
  480. public static function get_value($tab, $id, $field) {
  481. self::init($tab);
  482. if (isset(self::$table_rows[$field])) $field = self::$table_rows[$field]['id'];
  483. elseif (!isset(self::$hash[$field])) trigger_error('get_value(): Unknown column: '.$field, E_USER_ERROR);
  484. $ret = DB::GetOne('SELECT f_'.$field.' FROM '.$tab.'_data_1 WHERE id=%d', array($id));
  485. if ($ret===false || $ret===null) return null;
  486. return $ret;
  487. }
  488. public static function count_possible_values($tab, $field) { //it ignores empty values!
  489. self::init($tab);
  490. if (isset(self::$table_rows[$field])) $field = self::$table_rows[$field]['id'];
  491. elseif (!isset(self::$hash[$field])) trigger_error('count_possible_values(): Unknown column: '.$field, E_USER_ERROR);
  492. $par = self::build_query($tab, array('!'.$field=>''));
  493. return DB::GetOne('SELECT COUNT(DISTINCT(f_'.$field.')) FROM '.$par['sql'], $par['vals']);
  494. }
  495. public static function get_possible_values($tab, $field) {
  496. self::init($tab);
  497. if (isset(self::$table_rows[$field])) $field = self::$table_rows[$field]['id'];
  498. elseif (!isset(self::$hash[$field])) trigger_error('get_possible_values(): Unknown column: '.$field, E_USER_ERROR);
  499. $par = self::build_query($tab, array('!'.$field=>''));
  500. return DB::GetAssoc('SELECT MIN(id), MIN(f_'.$field.') FROM'.$par['sql'].' GROUP BY f_'.$field, $par['vals']);
  501. }
  502. public static function get_id($tab, $field, $value) {
  503. self::init($tab);
  504. $where = '';
  505. if (!is_array($field)) $field=array($field);
  506. if (!is_array($value)) $value=array($value);
  507. foreach ($field as $k=>$v) {
  508. if (!$v) continue;
  509. if (isset(self::$table_rows[$v])) $v = $field[$k] = self::$table_rows[$v]['id'];
  510. elseif (!isset(self::$hash[$v])) trigger_error('get_id(): Unknown column: '.$v, E_USER_ERROR);
  511. $f_id = self::$hash[$v];
  512. if (self::$table_rows[$f_id]['type']=='multiselect') {
  513. $where .= ' AND f_'.$v.' '.DB::like().' '.DB::Concat(DB::qstr('%\_\_'),'%s',DB::qstr('\_\_%'));
  514. } else $where .= ' AND f_'.$v.'=%s';
  515. }
  516. $ret = DB::GetOne('SELECT id FROM '.$tab.'_data_1 WHERE active=1'.$where, $value);
  517. if ($ret===false || $ret===null) return null;
  518. return $ret;
  519. }
  520. public static function is_active($tab, $id) {
  521. self::init($tab);
  522. return DB::GetOne('SELECT active FROM '.$tab.'_data_1 WHERE id=%d', array($id));
  523. }
  524. public static function admin_caption() {
  525. return array('label'=>__('Record Browser'), 'section'=>__('Data'));
  526. }
  527. public static function admin_access() {
  528. return DEMO_MODE?false:true;
  529. }
  530. public static function admin_access_levels() {
  531. return array(
  532. 'records'=>array('label'=>__('Manage Records'), 'values'=>array(0=>__('No access'), 1=>__('View'), 2=>__('Full')), 'default'=>1),
  533. 'fields'=>array('label'=>__('Manage Fields'), 'values'=>array(0=>__('No access'), 1=>__('View'), 2=>__('Full')), 'default'=>1),
  534. 'settings'=>array('label'=>__('Manage Settings'), 'values'=>array(0=>__('No access'), 1=>__('View'), 2=>__('Full')), 'default'=>1),
  535. 'addons'=>array('label'=>__('Manage Addons'), 'values'=>array(0=>__('No access'), 1=>__('View'), 2=>__('Full')), 'default'=>2),
  536. 'permissions'=>array('label'=>__('Permissions'), 'values'=>array(0=>__('No access'), 1=>__('View'), 2=>__('Full')), 'default'=>1),
  537. 'pattern'=>array('label'=>__('Clipboard Pattern'), 'values'=>array(0=>__('No access'), 1=>__('View'), 2=>__('Full')), 'default'=>2)
  538. );
  539. }
  540. public static function get_field_id($f) {
  541. return preg_replace('/[^|a-z0-9]/','_',strtolower($f));
  542. }
  543. public static function init($tab, $admin=false, $force=false) {
  544. static $cache = array();
  545. if (!isset(self::$cols_order[$tab])) self::$cols_order[$tab] = array();
  546. $cache_str = $tab.'__'.$admin.'__'.md5(serialize(self::$cols_order[$tab]));
  547. if (!$force && isset($cache[$cache_str])) {
  548. self::$hash = $cache[$cache_str]['hash'];
  549. return self::$table_rows = $cache[$cache_str]['rows'];
  550. }
  551. self::$table_rows = array();
  552. if($tab=='__RECORDSETS__' || preg_match('/,/',$tab)) return;
  553. self::check_table_name($tab);
  554. self::display_callback_cache($tab);
  555. $ret = DB::Execute('SELECT * FROM '.$tab.'_field'.($admin?'':' WHERE active=1 AND type!=\'page_split\'').' ORDER BY position');
  556. self::$hash = array();
  557. while($row = $ret->FetchRow()) {
  558. if ($row['field']=='id') continue;
  559. $commondata = false;
  560. if ($row['type']=='commondata') {
  561. $row['param'] = self::decode_commondata_param($row['param']);
  562. $commondata = true;
  563. }
  564. if ($row['type'] == 'file') {
  565. $row['param'] = json_decode($row['param'], true);
  566. if (!is_array($row['param'])) $row['param'] = [];
  567. if (!isset($row['param']['max_files'])) $row['param']['max_files'] = false;
  568. }
  569. $next_field =
  570. array( 'name'=>str_replace('%','%%',$row['caption']?$row['caption']:$row['field']),
  571. 'id'=>self::get_field_id($row['field']),
  572. 'pkey'=>$row['id'],
  573. 'type'=>$row['type'],
  574. 'visible'=>$row['visible'],
  575. 'required'=>($row['type']=='calculated'?false:$row['required']),
  576. 'extra'=>$row['extra'],
  577. 'active'=>$row['active'],
  578. 'export'=>$row['export'],
  579. 'tooltip'=>$row['tooltip'],
  580. 'position'=>$row['position'],
  581. 'processing_order' => $row['processing_order'],
  582. 'filter'=>$row['filter'],
  583. 'style'=>$row['style'],
  584. 'param'=>$row['param'],
  585. 'help' =>$row['help'],
  586. 'template'=>$row['template']
  587. );
  588. if (isset(self::$display_callback_table[$tab][$row['field']]))
  589. $next_field['display_callback'] = self::$display_callback_table[$tab][$row['field']];
  590. if (($row['type']=='select' || $row['type']=='multiselect') && $row['param']) {
  591. $pos = strpos($row['param'], ':');
  592. $next_field['ref_table'] = substr($row['param'], 0, $pos);
  593. if ($next_field['ref_table']=='__COMMON__') {
  594. $next_field['ref_field'] = '__COMMON__';
  595. $exploded = explode('::', $row['param']);
  596. $next_field['commondata_array'] = $next_field['ref_table'] = $exploded[1];
  597. $next_field['commondata_order'] = isset($exploded[2]) ? $exploded[2] : 'value';
  598. $commondata = true;
  599. } else {
  600. $end = strpos($row['param'], ';', $pos+2);
  601. if ($end==0) $end = strlen($row['param']);
  602. $next_field['ref_field'] = substr($row['param'], $pos+2, $end-$pos-2);
  603. }
  604. }
  605. $next_field['commondata'] = $commondata;
  606. if ($commondata) {
  607. if (!isset($next_field['commondata_order'])) {
  608. if (isset($next_field['param']['order'])) {
  609. $next_field['commondata_order'] = $next_field['param']['order'];
  610. } else {
  611. $next_field['commondata_order'] = 'value';
  612. }
  613. }
  614. if (!isset($next_field['commondata_array'])) {
  615. $next_field['commondata_array'] = $next_field['param']['array_id'];
  616. }
  617. }
  618. self::$table_rows[$row['field']] = $next_field;
  619. self::$hash[$next_field['id']] = $row['field'];
  620. }
  621. if (!empty(self::$cols_order[$tab])) {
  622. $rows = array();
  623. foreach (self::$cols_order[$tab] as $v) {
  624. $rows[self::$hash[$v]] = self::$table_rows[self::$hash[$v]];
  625. unset(self::$table_rows[self::$hash[$v]]);
  626. }
  627. foreach(self::$table_rows as $k=>$v)
  628. $rows[$k] = $v;
  629. self::$table_rows = $rows;
  630. }
  631. $cache[$tab.'__'.$admin.'__'.md5(serialize(self::$cols_order[$tab]))] = array('rows'=>self::$table_rows,'hash'=>self::$hash);
  632. return self::$table_rows;
  633. }
  634. public static function install_new_recordset($tab, $fields=array()) {
  635. if (!preg_match('/^[a-zA-Z_0-9]+$/',$tab)) trigger_error('Invalid table name ('.$tab.') given to install_new_recordset.',E_USER_ERROR);
  636. if (strlen($tab)>39) trigger_error('Invalid table name ('.$tab.') given to install_new_recordset, max length is 39 characters.',E_USER_ERROR);
  637. if (!DB::GetOne('SELECT 1 FROM recordbrowser_table_properties WHERE tab=%s', array($tab))) {
  638. DB::Execute('INSERT INTO recordbrowser_table_properties (tab) VALUES (%s)', array($tab));
  639. }
  640. @DB::DropTable($tab.'_callback');
  641. @DB::DropTable($tab.'_recent');
  642. @DB::DropTable($tab.'_favorite');
  643. @DB::DropTable($tab.'_edit_history_data');
  644. @DB::DropTable($tab.'_edit_history');
  645. @DB::DropTable($tab.'_field');
  646. @DB::DropTable($tab.'_data_1');
  647. @DB::DropTable($tab.'_access_clearance');
  648. @DB::DropTable($tab.'_access_fields');
  649. @DB::DropTable($tab.'_access');
  650. self::check_table_name(null, true);
  651. DB::CreateTable($tab.'_field',
  652. 'id I2 AUTO KEY NOTNULL,'.
  653. 'field C(32) UNIQUE NOTNULL,'.
  654. 'caption C(255),'.
  655. 'type C(32),'.
  656. 'extra I1 DEFAULT 1,'.
  657. 'visible I1 DEFAULT 1,'.
  658. 'tooltip I1 DEFAULT 1,'.
  659. 'required I1 DEFAULT 1,'.
  660. 'export I1 DEFAULT 1,'.
  661. 'active I1 DEFAULT 1,'.
  662. 'position I2,'.
  663. 'processing_order I2 NOTNULL,'.
  664. 'filter I1 DEFAULT 0,'.
  665. 'param C(255),'.
  666. 'style C(64),'.
  667. 'template C(255),'.
  668. 'help X',
  669. array('constraints'=>''));
  670. DB::CreateTable($tab.'_callback',
  671. 'field C(32),'.
  672. 'callback C(255),'.
  673. 'freezed I1',
  674. array('constraints'=>''));
  675. DB::Execute('INSERT INTO '.$tab.'_field(field, type, extra, visible, position, processing_order) VALUES(\'id\', \'foreign index\', 0, 0, 1, 1)');
  676. DB::Execute('INSERT INTO '.$tab.'_field(field, type, extra, position, processing_order) VALUES(\'General\', \'page_split\', 0, 2, 2)');
  677. $fields_sql = '';
  678. foreach ($fields as $v)
  679. $fields_sql .= Utils_RecordBrowserCommon::new_record_field($tab, $v, false, false);
  680. DB::CreateTable($tab.'_data_1',
  681. 'id I AUTO KEY,'.
  682. 'created_on T NOT NULL,'.
  683. 'created_by I NOT NULL,'.
  684. 'indexed I1 NOT NULL DEFAULT 0,'.
  685. 'active I1 NOT NULL DEFAULT 1'.
  686. $fields_sql,
  687. array('constraints'=>''));
  688. DB::CreateIndex($tab.'_idxed',$tab.'_data_1','indexed,active');
  689. DB::CreateIndex($tab.'_act',$tab.'_data_1','active');
  690. DB::CreateTable($tab.'_edit_history',
  691. 'id I AUTO KEY,'.
  692. $tab.'_id I NOT NULL,'.
  693. 'edited_on T NOT NULL,'.
  694. 'edited_by I NOT NULL',
  695. array('constraints'=>', FOREIGN KEY (edited_by) REFERENCES user_login(id), FOREIGN KEY ('.$tab.'_id) REFERENCES '.$tab.'_data_1(id)'));
  696. DB::CreateTable($tab.'_edit_history_data',
  697. 'edit_id I,'.
  698. 'field C(32),'.
  699. 'old_value X',
  700. array('constraints'=>', FOREIGN KEY (edit_id) REFERENCES '.$tab.'_edit_history(id)'));
  701. DB::CreateTable($tab.'_favorite',
  702. 'fav_id I AUTO KEY,'.
  703. $tab.'_id I,'.
  704. 'user_id I',
  705. array('constraints'=>', FOREIGN KEY (user_id) REFERENCES user_login(id), FOREIGN KEY ('.$tab.'_id) REFERENCES '.$tab.'_data_1(id)'));
  706. DB::CreateTable($tab.'_recent',
  707. 'recent_id I AUTO KEY,'.
  708. $tab.'_id I,'.
  709. 'user_id I,'.
  710. 'visited_on T',
  711. array('constraints'=>', FOREIGN KEY (user_id) REFERENCES user_login(id), FOREIGN KEY ('.$tab.'_id) REFERENCES '.$tab.'_data_1(id)'));
  712. DB::CreateTable($tab.'_access',
  713. 'id I AUTO KEY,'.
  714. 'action C(16),'.
  715. 'crits X',
  716. array('constraints'=>''));
  717. DB::CreateTable($tab.'_access_fields',
  718. 'rule_id I,'.
  719. 'block_field C(32)',
  720. array('constraints'=>', FOREIGN KEY (rule_id) REFERENCES '.$tab.'_access(id)'));
  721. DB::CreateTable($tab.'_access_clearance',
  722. 'rule_id I,'.
  723. 'clearance C(32)',
  724. array('constraints'=>', FOREIGN KEY (rule_id) REFERENCES '.$tab.'_access(id)'));
  725. self::check_table_name($tab, true);
  726. self::add_access($tab, 'print', 'SUPERADMIN');
  727. self::add_access($tab, 'export', 'SUPERADMIN');
  728. return true;
  729. }
  730. public static function enable_watchdog($tab,$watchdog_callback) {
  731. self::check_table_name($tab);
  732. Utils_WatchdogCommon::register_category($tab, $watchdog_callback);
  733. }
  734. public static function set_display_callback($tab, $field, $callback) {
  735. self::check_table_name($tab);
  736. self::$clear_get_val_cache = true;
  737. if (is_array($callback)) $callback = implode('::',$callback);
  738. DB::Execute('DELETE FROM '.$tab.'_callback WHERE field=%s AND freezed=1', array($field));
  739. DB::Execute('INSERT INTO '.$tab.'_callback (field, callback, freezed) VALUES(%s, %s, 1)', array($field, $callback));
  740. }
  741. public static function set_QFfield_callback($tab, $field, $callback) {
  742. self::check_table_name($tab);
  743. self::$clear_get_val_cache = true;
  744. if (is_array($callback)) $callback = implode('::',$callback);
  745. DB::Execute('DELETE FROM '.$tab.'_callback WHERE field=%s AND freezed=0', array($field));
  746. DB::Execute('INSERT INTO '.$tab.'_callback (field, callback, freezed) VALUES(%s, %s, 0)', array($field, $callback));
  747. }
  748. public static function unset_display_callback($tab, $field) {
  749. self::check_table_name($tab);
  750. self::$clear_get_val_cache = true;
  751. DB::Execute('DELETE FROM '.$tab.'_callback WHERE field=%s AND freezed=1', array($field));
  752. }
  753. public static function unset_QFfield_callback($tab, $field) {
  754. self::check_table_name($tab);
  755. self::$clear_get_val_cache = true;
  756. DB::Execute('DELETE FROM '.$tab.'_callback WHERE field=%s AND freezed=0', array($field));
  757. }
  758. public static function uninstall_recordset($tab) {
  759. if (!self::check_table_name($tab,true)) return;
  760. self::$clear_get_val_cache = true;
  761. Utils_WatchdogCommon::unregister_category($tab);
  762. DB::DropTable($tab.'_callback');
  763. DB::DropTable($tab.'_recent');
  764. DB::DropTable($tab.'_favorite');
  765. DB::DropTable($tab.'_edit_history_data');
  766. DB::DropTable($tab.'_edit_history');
  767. DB::DropTable($tab.'_field');
  768. DB::DropTable($tab.'_data_1');
  769. DB::DropTable($tab.'_access_clearance');
  770. DB::DropTable($tab.'_access_fields');
  771. DB::DropTable($tab.'_access');
  772. DB::Execute('DELETE FROM recordbrowser_table_properties WHERE tab=%s', array($tab));
  773. DB::Execute('DELETE FROM recordbrowser_processing_methods WHERE tab=%s', array($tab));
  774. DB::Execute('DELETE FROM recordbrowser_browse_mode_definitions WHERE tab=%s', array($tab));
  775. DB::Execute('DELETE FROM recordbrowser_clipboard_pattern WHERE tab=%s', array($tab));
  776. DB::Execute('DELETE FROM recordbrowser_addon WHERE tab=%s', array($tab));
  777. DB::Execute('DELETE FROM recordbrowser_access_methods WHERE tab=%s', array($tab));
  778. return true;
  779. }
  780. public static function delete_record_field($tab, $field){
  781. self::init($tab);
  782. self::$clear_get_val_cache = true;
  783. $exists = DB::GetOne('SELECT 1 FROM '.$tab.'_field WHERE field=%s', array($field));
  784. if(!$exists) return;
  785. // move to the end
  786. self::change_field_position($tab, $field, 16000);
  787. DB::Execute('DELETE FROM '.$tab.'_field WHERE field=%s', array($field));
  788. DB::Execute('DELETE FROM '.$tab.'_callback WHERE field=%s', array($field));
  789. if (isset(self::$table_rows[$field]['id'])) {
  790. $f_id = self::$table_rows[$field]['id'];
  791. @DB::Execute('ALTER TABLE '.$tab.'_data_1 DROP COLUMN f_'.$f_id);
  792. @DB::Execute('DELETE FROM '.$tab.'_access_fields WHERE block_field=%s', array($f_id));
  793. self::init($tab, false, true);
  794. @DB::Execute('UPDATE '.$tab.'_data_1 SET indexed=0');
  795. }
  796. }
  797. private static $datatypes = null;
  798. public static function new_record_field($tab, $definition, $alter=true, $set_empty_position_before_first_page_split=true){
  799. if (self::$datatypes===null) {
  800. self::$datatypes = array();
  801. $ret = DB::Execute('SELECT * FROM recordbrowser_datatype');
  802. while ($row = $ret->FetchRow())
  803. self::$datatypes[$row['type']] = array($row['module'], $row['func']);
  804. }
  805. if (!is_array($definition)) {
  806. // Backward compatibility - got to get rid of this one someday
  807. $args = func_get_args();
  808. array_shift($args);
  809. $definition = array();
  810. foreach (array( 0=>'name',
  811. 1=>'type',
  812. 2=>'visible',
  813. 3=>'required',
  814. 4=>'param',
  815. 5=>'style',
  816. 6=>'extra',
  817. 7=>'filter',
  818. 8=>'position',
  819. 9=>'processing_order',
  820. 10=>'caption') as $k=>$w)
  821. if (isset($args[$k])) $definition[$w] = $args[$k];
  822. }
  823. if (!isset($definition['type'])) trigger_error(print_r($definition,true));
  824. if (!isset($definition['param'])) $definition['param'] = '';
  825. if (!isset($definition['caption'])) $definition['caption'] = '';
  826. if (!isset($definition['style'])) {
  827. if (in_array($definition['type'], array('time','timestamp','currency')))
  828. $definition['style'] = $definition['type'];
  829. else {
  830. if (in_array($definition['type'], array('float','integer', 'autonumber')))
  831. $definition['style'] = 'number';
  832. else
  833. $definition['style'] = '';
  834. }
  835. }
  836. if (!isset($definition['extra'])) $definition['extra'] = true;
  837. if (!isset($definition['export'])) $definition['export'] = true;
  838. if (!isset($definition['visible'])) $definition['visible'] = false;
  839. if (!isset($definition['tooltip'])) $definition['tooltip'] = $definition['visible'];
  840. if (!isset($definition['required'])) $definition['required'] = false;
  841. if (!isset($definition['filter'])) $definition['filter'] = false;
  842. if (!isset($definition['position'])) $definition['position'] = null;
  843. if (!isset($definition['template'])) $definition['template'] = '';
  844. if (!isset($definition['help'])) $definition['help'] = '';
  845. $definition['template'] = is_array($definition['template'])? implode('::', $definition['template']): $definition['template'];
  846. if (isset(self::$datatypes[$definition['type']])) $definition = call_user_func(self::$datatypes[$definition['type']], $definition);
  847. if (isset($definition['display_callback'])) self::set_display_callback($tab, $definition['name'], $definition['display_callback']);
  848. if (isset($definition['QFfield_callback'])) self::set_QFfield_callback($tab, $definition['name'], $definition['QFfield_callback']);
  849. // $field, $type, $visible, $required, $param='', $style='', $extra = true, $filter = false, $pos = null
  850. if (strpos($definition['name'],'|')!==false) trigger_error('Invalid field name (character | is not allowed):'.$definition['name'], E_USER_ERROR);
  851. self::check_table_name($tab);
  852. self::$clear_get_val_cache = true;
  853. if ($alter) {
  854. $exists = DB::GetOne('SELECT field FROM '.$tab.'_field WHERE field=%s', array($definition['name']));
  855. if ($exists) return;
  856. }
  857. DB::StartTrans();
  858. if (is_string($definition['position'])) $definition['position'] = DB::GetOne('SELECT position FROM '.$tab.'_field WHERE field=%s', array($definition['position']))+1;
  859. if ($definition['position']===null || $definition['position']===false) {
  860. $first_page_split = $set_empty_position_before_first_page_split?DB::GetOne('SELECT MIN(position) FROM '.$tab.'_field WHERE type=%s AND field!=%s', array('page_split', 'General')):0;
  861. $definition['position'] = $first_page_split?$first_page_split:DB::GetOne('SELECT MAX(position) FROM '.$tab.'_field')+1;
  862. }
  863. DB::Execute('UPDATE '.$tab.'_field SET position = position+1 WHERE position>=%d', array($definition['position']));
  864. DB::CompleteTrans();
  865. if (!isset($definition['processing_order'])) $definition['processing_order'] = DB::GetOne('SELECT MAX(processing_order) FROM '.$tab.'_field') + 1;
  866. $param = $definition['param'];
  867. if (is_array($param)) {
  868. if ($definition['type']=='commondata') {
  869. $param = self::encode_commondata_param($param);
  870. } elseif($definition['type'] == 'file') {
  871. $param = json_encode($param);
  872. } else {
  873. $tmp = array();
  874. foreach ($param as $k=>$v) $tmp[] = $k.'::'.$v;
  875. $param = implode(';',$tmp);
  876. }
  877. }
  878. $f = self::actual_db_type($definition['type'], $param);
  879. DB::Execute('INSERT INTO '.$tab.'_field(field, caption, type, visible, param, style, position, processing_order, extra, required, filter, export, tooltip, template, help) VALUES(%s, %s, %s, %d, %s, %s, %d, %d, %d, %d, %d, %d, %d, %s, %s)', array($definition['name'], $definition['caption'], $definition['type'], $definition['visible']?1:0, $param, $definition['style'], $definition['position'], $definition['processing_order'], $definition['extra']?1:0, $definition['required']?1:0, $definition['filter']?1:0, $definition['export']?1:0, $definition['tooltip']?1:0, $definition['template'], $definition['help']));
  880. $column = 'f_'.self::get_field_id($definition['name']);
  881. if ($alter) {
  882. self::init($tab, false, true);
  883. if ($f!=='') {
  884. @DB::Execute('ALTER TABLE '.$tab.'_data_1 ADD COLUMN '.$column.' '.$f);
  885. if ($definition['type'] === 'autonumber') {
  886. self::format_autonumber_str_all_records($tab, self::get_field_id($definition['name']), $param);
  887. }
  888. }
  889. } else {
  890. if ($f!=='') return ','.$column.' '.$f;
  891. else return '';
  892. }
  893. @DB::Execute('UPDATE '.$tab.'_data_1 SET indexed=0');
  894. }
  895. public static function change_field_position($tab, $field, $new_pos){
  896. if ($new_pos <= 2) return; // make sure that no field is before "General" tab split
  897. DB::StartTrans();
  898. $pos = DB::GetOne('SELECT position FROM ' . $tab . '_field WHERE field=%s', array($field));
  899. if ($pos) {
  900. // move all following fields back
  901. DB::Execute('UPDATE '.$tab.'_field SET position=position-1 WHERE position>%d',array($pos));
  902. // make place for moved field
  903. DB::Execute('UPDATE '.$tab.'_field SET position=position+1 WHERE position>=%d',array($new_pos));
  904. // set new field position
  905. DB::Execute('UPDATE '.$tab.'_field SET position=%d WHERE field=%s',array($new_pos, $field));
  906. }
  907. DB::CompleteTrans();
  908. }
  909. /**
  910. * @param string $tab Recordset identifier. e.g. contact, company
  911. * @param string $old_name Current field name. Not field id. e.g. "First Name"
  912. * @param string $new_name New field name.
  913. */
  914. public static function rename_field($tab, $old_name, $new_name)
  915. {
  916. $id = self::get_field_id($old_name);
  917. $new_id = self::get_field_id($new_name);
  918. self::check_table_name($tab);
  919. $type = DB::GetOne('SELECT type FROM ' . $tab . '_field WHERE field=%s', array($old_name));
  920. $old_param = DB::GetOne('SELECT param FROM ' . $tab . '_field WHERE field=%s', array($old_name));
  921. $db_field_exists = !($type === 'calculated' && !$old_param);
  922. DB::StartTrans();
  923. if ($db_field_exists) {
  924. if (DB::is_postgresql()) {
  925. DB::Execute('ALTER TABLE ' . $tab . '_data_1 RENAME COLUMN f_' . $id . ' TO f_' . $new_id);
  926. } else {
  927. DB::RenameColumn($tab . '_data_1', 'f_' . $id, 'f_' . $new_id, self::actual_db_type($type, $old_param));
  928. }
  929. DB::Execute('UPDATE ' . $tab . '_edit_history_data SET field=%s WHERE field=%s', array($new_id, $id));
  930. }
  931. DB::Execute('UPDATE ' . $tab . '_field SET field=%s WHERE field=%s', array($new_name, $old_name));
  932. DB::Execute('UPDATE ' . $tab . '_access_fields SET block_field=%s WHERE block_field=%s', array($new_id, $id));
  933. DB::Execute('UPDATE ' . $tab . '_callback SET field=%s WHERE field=%s', array($new_name, $old_name));
  934. $result = DB::Execute('SELECT * FROM ' . $tab . '_access');
  935. while ($row = $result->FetchRow()) {
  936. $crits = self::unserialize_crits($row['crits']);
  937. if (!$crits) continue;
  938. if (!is_object($crits))
  939. $crits = Utils_RecordBrowser_Crits::from_array($crits);
  940. foreach ($crits->find($id) as $c) $c->set_field($new_id);
  941. DB::Execute('UPDATE ' . $tab . '_access SET crits=%s WHERE id=%d', array(self::serialize_crits($crits), $row['id']));
  942. }
  943. DB::CompleteTrans();
  944. }
  945. public static function set_field_template($tab, $fields, $template)
  946. {
  947. Utils_RecordBrowserCommon::check_table_name($tab);
  948. $template = is_array($template)? implode('::', $template): $template;
  949. $fields = is_array($fields)? $fields: array($fields);
  950. $s = array_fill(0, count($fields), '%s');
  951. DB::Execute('UPDATE ' . $tab . '_field SET template=%s WHERE field IN ('. implode(',', $s) .')', array_merge(array($template), $fields));
  952. }
  953. /**
  954. * List all installed recordsets.
  955. * @param string $format Simple formatting of the values
  956. *
  957. * You can use three keys that will be replaced with values: <br>
  958. * - %tab - table identifier <br>
  959. * - %orig_caption - original table caption <br>
  960. * - %caption - translated table caption <br>
  961. * Default is '%caption'. If no caption is specified then
  962. * table identifier is used as caption. <br>
  963. * Other common usage: '%caption (%tab)'
  964. * @return array Keys are tab identifiers, values according to $format param
  965. */
  966. public static function list_installed_recordsets($format = '%caption')
  967. {
  968. $tabs = DB::GetAssoc('SELECT tab, caption FROM recordbrowser_table_properties');
  969. $ret = array();
  970. foreach ($tabs as $tab_id => $caption) {
  971. if (!$caption) {
  972. $translated_caption = $caption = $tab_id;
  973. } else {
  974. $translated_caption = _V($caption);
  975. }
  976. $ret[$tab_id] = str_replace(
  977. array('%tab', '%orig_caption', '%caption'),
  978. array($tab_id, $caption, $translated_caption),
  979. $format
  980. );
  981. }
  982. return $ret;
  983. }
  984. public static function actual_db_type($type, $param=null) {
  985. $f = '';
  986. switch ($type) {
  987. case 'page_split': $f = ''; break;
  988. case 'text': $f = DB::dict()->ActualType('C').'('.$param.')'; break;
  989. case 'select':
  990. $param = self::decode_select_param($param);
  991. if($param['single_tab']) $f = DB::dict()->ActualType('I4');
  992. else $f = DB::dict()->ActualType('X');
  993. break;
  994. case 'multiselect': $f = DB::dict()->ActualType('X'); break;
  995. case 'commondata': $f = DB::dict()->ActualType('C').'(128)'; break;
  996. case 'integer': $f = DB::dict()->ActualType('I4'); break;
  997. case 'float': $f = DB::dict()->ActualType('F'); break;
  998. case 'date': $f = DB::dict()->ActualType('D'); break;
  999. case 'timestamp': $f = DB::dict()->ActualType('T'); break;
  1000. case 'time': $f = DB::dict()->ActualType('T'); break;
  1001. case 'long text': $f = DB::dict()->ActualType('X'); break;
  1002. case 'hidden': $f = (isset($param)?$param:''); break;
  1003. case 'calculated': $f = (isset($param)?$param:''); break;
  1004. case 'checkbox': $f = DB::dict()->ActualType('I1'); break;
  1005. case 'currency': $f = DB::dict()->ActualType('C').'(128)'; break;
  1006. case 'autonumber': $len = strlen(self::format_autonumber_str($param, null));
  1007. $f = DB::dict()->ActualType('C') . "($len)"; break;
  1008. case 'file': $f = DB::dict()->ActualType('X'); break;
  1009. }
  1010. return $f;
  1011. }
  1012. public static function new_browse_mode_details_callback($tab, $mod, $func) {
  1013. self::check_table_name($tab);
  1014. if(!DB::GetOne('SELECT 1 FROM recordbrowser_browse_mode_definitions WHERE tab=%s AND module=%s AND func=%s', array($tab, $mod, $func)))
  1015. DB::Execute('INSERT INTO recordbrowser_browse_mode_definitions (tab, module, func) VALUES (%s, %s, %s)', array($tab, $mod, $func));
  1016. }
  1017. public static function delete_browse_mode_details_callback($tab, $mod, $func) {
  1018. self::check_table_name($tab);
  1019. DB::Execute('DELETE FROM recordbrowser_browse_mode_definitions WHERE tab=%s AND module=%s AND func=%s', array($tab, $mod, $func));
  1020. }
  1021. public static function new_addon($tab, $module, $func, $label) {
  1022. if (is_array($label)) $label= implode('::',$label);
  1023. $module = str_replace('/','_',$module);
  1024. self::delete_addon($tab, $module, $func);
  1025. $pos = DB::GetOne('SELECT MAX(pos) FROM recordbrowser_addon WHERE tab=%s', array($tab));
  1026. if (!$pos) $pos=0;
  1027. DB::Execute('INSERT INTO recordbrowser_addon (tab, module, func, label, pos, enabled) VALUES (%s, %s, %s, %s, %d, 1)', array($tab, $module, $func, $label, $pos+1));
  1028. }
  1029. public static function delete_addon($tab, $module, $func) {
  1030. $module = str_replace('/','_',$module);
  1031. $pos = DB::GetOne('SELECT pos FROM recordbrowser_addon WHERE tab=%s AND module=%s AND func=%s', array($tab, $module, $func));
  1032. if ($pos===false || $pos===null) return false;
  1033. DB::Execute('DELETE FROM recordbrowser_addon WHERE tab=%s AND module=%s AND func=%s', array($tab, $module, $func));
  1034. while (DB::GetOne('SELECT pos FROM recordbrowser_addon WHERE tab=%s AND pos=%d', array($tab, $pos+1))) {
  1035. DB::Execute('UPDATE recordbrowser_addon SET pos=pos-1 WHERE tab=%s AND pos=%d', array($tab, $pos+1));
  1036. $pos++;
  1037. }
  1038. return true;
  1039. }
  1040. public static function set_addon_pos($tab, $module, $func, $pos) {
  1041. $module = str_replace('/','_',$module);
  1042. $old_pos = DB::GetOne('SELECT pos FROM recordbrowser_addon WHERE tab=%s AND module=%s AND func=%s', array($tab, $module, $func));
  1043. if ($old_pos>$pos)
  1044. DB::Execute('UPDATE recordbrowser_addon SET pos=pos+1 WHERE tab=%s AND pos>=%d AND pos<%d', array($tab, $pos, $old_pos));
  1045. else
  1046. DB::Execute('UPDATE recordbrowser_addon SET pos=pos-1 WHERE tab=%s AND pos<=%d AND pos>%d', array($tab, $pos, $old_pos));
  1047. DB::Execute('UPDATE recordbrowser_addon SET pos=%d WHERE tab=%s AND module=%s AND func=%s', array($pos, $tab, $module, $func));
  1048. }
  1049. public static function register_datatype($type, $module, $func) {
  1050. if(self::$datatypes!==null) self::$datatypes[$type] = array($module,$func);
  1051. DB::Execute('INSERT INTO recordbrowser_datatype (type, module, func) VALUES (%s, %s, %s)', array($type, $module, $func));
  1052. }
  1053. public static function unregister_datatype($type) {
  1054. if(self::$datatypes!==null) unset(self::$datatypes[$type]);
  1055. DB::Execute('DELETE FROM recordbrowser_datatype WHERE type=%s', array($type));
  1056. }
  1057. public static function new_filter($tab, $col_name) {
  1058. self::check_table_name($tab);
  1059. DB::Execute('UPDATE '.$tab.'_field SET filter=1 WHERE field=%s', array($col_name));
  1060. }
  1061. public static function delete_filter($tab, $col_name) {
  1062. self::check_table_name($tab);
  1063. DB::Execute('UPDATE '.$tab.'_field SET filter=0 WHERE field=%s', array($col_name));
  1064. }
  1065. public static function register_processing_callback($tab, $callback) {
  1066. if (is_array($callback)) $callback = implode('::',$callback);
  1067. if(!DB::GetOne('SELECT 1 FROM recordbrowser_processing_methods WHERE tab=%s AND func=%s', array($tab, $callback)))
  1068. DB::Execute('INSERT INTO recordbrowser_processing_methods (tab, func) VALUES (%s, %s)', array($tab, $callback));
  1069. }
  1070. public static function unregister_processing_callback($tab, $callback) {
  1071. if (is_array($callback)) $callback = implode('::',$callback);
  1072. DB::Execute('DELETE FROM recordbrowser_processing_methods WHERE tab=%s AND func=%s', array($tab, $callback));
  1073. }
  1074. public static function set_quickjump($tab, $col_name) {
  1075. DB::Execute('UPDATE recordbrowser_table_properties SET quickjump=%s WHERE tab=%s', array($col_name, $tab));
  1076. }
  1077. public static function set_tpl($tab, $filename) {
  1078. DB::Execute('UPDATE recordbrowser_table_properties SET tpl=%s WHERE tab=%s', array($filename, $tab));
  1079. }
  1080. public static function set_favorites($tab, $value) {
  1081. DB::Execute('UPDATE recordbrowser_table_properties SET favorites=%d WHERE tab=%s', array($value?1:0, $tab));
  1082. }
  1083. public static function set_recent($tab, $value) {
  1084. DB::Execute('UPDATE recordbrowser_table_properties SET recent=%d WHERE tab=%s', array($value, $tab));
  1085. }
  1086. public static function set_full_history($tab, $value) {
  1087. DB::Execute('UPDATE recordbrowser_table_properties SET full_history=%d WHERE tab=%s', array($value?1:0, $tab));
  1088. }
  1089. public static function set_caption($tab, $value) {
  1090. DB::Execute('UPDATE recordbrowser_table_properties SET caption=%s WHERE tab=%s', array($value, $tab));
  1091. }
  1092. public static function set_icon($tab, $value) {
  1093. DB::Execute('UPDATE recordbrowser_table_properties SET icon=%s WHERE tab=%s', array($value, $tab));
  1094. }
  1095. /**
  1096. * Enable search
  1097. * @param string $tab recordset identifier
  1098. * @param int $mode 0 - search disabled, 1 - enabled by default, 2 - optional
  1099. * @param int $priority Possible values: -2, -1, 0, 1, 2
  1100. */
  1101. public static function set_search($tab, $mode,$priority=0) {
  1102. DB::Execute('UPDATE recordbrowser_table_properties SET search_include=%d,search_priority=%d WHERE tab=%s', array($mode, $priority, $tab));
  1103. }
  1104. public static function set_description_callback($tab, $callback){
  1105. if (is_array($callback)) $callback = implode('::',$callback);
  1106. DB::Execute('UPDATE recordbrowser_table_properties SET description_callback=%s WHERE tab=%s', array($callback, $tab));
  1107. }
  1108. /**
  1109. * Set description fields to be used as default linked label
  1110. *
  1111. * You can use double quotes to put any text between field values
  1112. * e.g. 'Last Name, ", ", First Name,'
  1113. *
  1114. * @param string $tab recordset name
  1115. * @param string|array $fields comma separated list of fields or array of fields
  1116. */
  1117. public static function set_description_fields($tab, $fields)
  1118. {
  1119. if (is_array($fields)) $fields = implode(',', $fields);
  1120. DB::Execute('UPDATE recordbrowser_table_properties SET description_fields=%s WHERE tab=%s', array($fields, $tab));
  1121. }
  1122. public static function set_printer($tab,$class) {
  1123. Base_PrintCommon::register_printer(new $class());
  1124. DB::Execute('UPDATE recordbrowser_table_properties SET printer=%s WHERE tab=%s', array($class, $tab));
  1125. }
  1126. public static function unset_printer($tab)
  1127. {
  1128. $printer_class = DB::GetOne('SELECT printer FROM recordbrowser_table_properties WHERE tab=%s',$tab);
  1129. if ($printer_class) {
  1130. Base_PrintCommon::unregister_printer($printer_class);
  1131. DB::Execute('UPDATE recordbrowser_table_properties SET printer=%s WHERE tab=%s', array('', $tab));
  1132. return $printer_class;
  1133. }
  1134. return false;
  1135. }
  1136. /**
  1137. * Enable or disable jump to id. By default it is enabled.
  1138. *
  1139. * @param string $tab Recordset identifier
  1140. * @param bool $enabled True to enable, false to disable
  1141. */
  1142. public static function set_jump_to_id($tab, $enabled = true)
  1143. {
  1144. $sql = 'UPDATE recordbrowser_table_properties SET jump_to_id=%d WHERE tab=%s';
  1145. DB::Execute($sql, array($enabled ? 1 : 0, $tab));
  1146. }
  1147. public static function get_caption($tab) {
  1148. static $cache = null;
  1149. if ($cache===null) $cache = DB::GetAssoc('SELECT tab, caption FROM recordbrowser_table_properties');
  1150. if (is_string($tab) && isset($cache[$tab])) return _V($cache[$tab]);
  1151. return '---';
  1152. }
  1153. public static function get_description_callback($tab) {
  1154. static $cache = null;
  1155. if ($cache===null) $cache = DB::GetAssoc('SELECT tab, description_callback FROM recordbrowser_table_properties');
  1156. if (is_string($tab) && isset($cache[$tab])) {
  1157. if(is_string($cache[$tab]) && preg_match('/::/',$cache[$tab])) {
  1158. $cache[$tab] = explode('::',$cache[$tab]);
  1159. }
  1160. if(!is_callable($cache[$tab]))
  1161. $cache[$tab] = false;
  1162. return $cache[$tab];
  1163. }
  1164. return false;
  1165. }
  1166. public static function get_description_fields($tab) {
  1167. static $cache = null;
  1168. if ($cache===null) {
  1169. $db_ret = DB::GetAssoc('SELECT tab, description_fields FROM recordbrowser_table_properties');
  1170. foreach ($db_ret as $t => $fields) {
  1171. if ($fields) {
  1172. $fields = str_replace('"', '\'"', $fields);
  1173. $cache[$t] = array_filter(array_map('trim', str_getcsv($fields, ',', "'")));
  1174. }
  1175. }
  1176. }
  1177. if (is_string($tab) && isset($cache[$tab])) {
  1178. return $cache[$tab];
  1179. }
  1180. return false;
  1181. }
  1182. public static function get_sql_type($type) {
  1183. switch ($type) {
  1184. case 'checkbox': return '%d';
  1185. case 'select': return '%s';
  1186. case 'float': return '%f';
  1187. case 'integer': return '%d';
  1188. case 'date': return '%D';
  1189. case 'timestamp': return '%T';
  1190. }
  1191. return '%s';
  1192. }
  1193. public static function set_record_properties( $tab, $id, $info = array()) {
  1194. self::check_table_name($tab);
  1195. foreach ($info as $k=>$v)
  1196. switch ($k) {
  1197. case 'created_on': DB::Execute('UPDATE '.$tab.'_data_1 SET created_on=%T WHERE id=%d', array($v, $id));
  1198. break;
  1199. case 'created_by': DB::Execute('UPDATE '.$tab.'_data_1 SET created_by=%d WHERE id=%d', array($v, $id));
  1200. break;
  1201. }
  1202. }
  1203. public static function record_processing($tab, $base, $mode, $clone=null) {
  1204. self::check_table_name($tab);
  1205. static $cache = array();
  1206. if (!isset($cache[$tab])) {
  1207. $ret = DB::Execute('SELECT * FROM recordbrowser_processing_methods WHERE tab=%s', array($tab));
  1208. $cache[$tab] = array();
  1209. while ($row = $ret->FetchRow()) {
  1210. $callback = explode('::',$row['func']);
  1211. if (is_callable($callback))
  1212. $cache[$tab][] = $callback;
  1213. }
  1214. }
  1215. $current = $base;
  1216. if ($mode=='display') $result = array();
  1217. else $result = $base;
  1218. if ($mode=='cloned') $current = array('original'=>$clone, 'clone'=>$current);
  1219. foreach ($cache[$tab] as $callback) {
  1220. $return = call_user_func($callback, $current, $mode, $tab);
  1221. if ($return===false) return false;
  1222. if ($return) {
  1223. if ($mode!='display') $current = $return;
  1224. else $result = array_merge_recursive($result, $return);
  1225. }
  1226. }
  1227. if ($mode!='display') $result = $current;
  1228. return $result;
  1229. }
  1230. public static function new_record( $tab, $values = array()) {
  1231. self::init($tab);
  1232. $user = Acl::get_user();
  1233. $for_processing = $values;
  1234. foreach(self::$table_rows as $field=>$desc)
  1235. if ($desc['type']==='multiselect') {
  1236. if (!isset($for_processing[$desc['id']]) || !$for_processing[$desc['id']])
  1237. $for_processing[$desc['id']] = array();
  1238. } elseif (!isset($for_processing[$desc['id']])) $for_processing[$desc['id']] = '';
  1239. $values = self::record_processing($tab, $for_processing, 'add');
  1240. if ($values===false) return;
  1241. self::init($tab);
  1242. $fields = 'created_on,created_by,active';
  1243. $fields_types = '%T,%d,%d';
  1244. $vals = array(date('Y-m-d H:i:s'), $user, 1);
  1245. $filestorageIds = [];
  1246. foreach(self::$table_rows as $field => $desc) {
  1247. if ($desc['type'] == 'file') {
  1248. $files = $values[$desc['id']];
  1249. if (is_string($files)) $files = self::decode_multi($files);
  1250. if ($desc['param']['max_files'] && count($files) > $desc['param']['max_files']) {
  1251. throw new Exception('Too many files in field ' . $desc['id']);
  1252. }
  1253. $filestorageIds[$field] = Utils_FileStorageCommon::add_files($files);
  1254. $values[$desc['id']] = self::encode_multi($filestorageIds[$field]);
  1255. }
  1256. if ($desc['type']=='calculated' && preg_match('/^[a-z]+(\([0-9]+\))?$/i',$desc['param'])===0) continue; // FIXME move DB definiton to *_field table
  1257. if (!isset($values[$desc['id']]) || $values[$desc['id']]==='') continue;
  1258. if (!is_array($values[$desc['id']])) $values[$desc['id']] = trim($values[$desc['id']]);
  1259. if ($desc['type']=='long text')
  1260. $values[$desc['id']] = Utils_BBCodeCommon::optimize($values[$desc['id']]);
  1261. if ($desc['type']=='multiselect' && empty($values[$desc['id']])) continue;
  1262. if ($desc['type']=='multiselect')
  1263. $values[$desc['id']] = self::encode_multi($values[$desc['id']]);
  1264. if ($desc['type']=='multiselect' || $desc['type']=='select') $values[$desc['id']] = str_replace(array('P:', 'C:'), array('contact/', 'company/'), $values[$desc['id']]);
  1265. $fields_types .= ','.self::get_sql_type($desc['type']);
  1266. $fields .= ',f_'.$desc['id'];
  1267. if (is_bool($values[$desc['id']])) $values[$desc['id']] = $values[$desc['id']]?1:0;
  1268. $vals[] = $values[$desc['id']];
  1269. }
  1270. DB::Execute('INSERT INTO '.$tab.'_data_1 ('.$fields.') VALUES ('.$fields_types.')',$vals);
  1271. $id = DB::Insert_ID($tab.'_data_1', 'id');
  1272. if ($user) self::add_recent_entry($tab, $user, $id);
  1273. if (Base_User_SettingsCommon::get('Utils_RecordBrowser',$tab.'_auto_fav'))
  1274. DB::Execute('INSERT INTO '.$tab.'_favorite (user_id, '.$tab.'_id) VALUES (%d, %d)', array($user, $id));
  1275. self::init($tab);
  1276. foreach(self::$table_rows as $field=>$desc) {
  1277. if ($desc['type']==='multiselect') {
  1278. if (!isset($values[$desc['id']])) $values[$desc['id']] = array();
  1279. elseif (!is_array($values[$desc['id']]))
  1280. $values[$desc['id']] = self::decode_multi($values[$desc['id']]);
  1281. }
  1282. if ($desc['type'] === 'autonumber') {
  1283. $autonumber_value = self::format_autonumber_str($desc['param'], $id);
  1284. self::update_record($tab, $id, array($desc['id'] => $autonumber_value), false, null, true);
  1285. $values[$desc['id']] = $autonumber_value;
  1286. }
  1287. if ($desc['type'] === 'file') {
  1288. // update backref
  1289. Utils_FileStorageCommon::add_files($filestorageIds[$field], "rb:$tab/$id/$desc[pkey]");
  1290. $values[$desc['id']] = $filestorageIds[$field];
  1291. }
  1292. }
  1293. $values['id'] = $id;
  1294. self::record_processing($tab, $values, 'added');
  1295. if (Base_User_SettingsCommon::get('Utils_RecordBrowser',$tab.'_auto_subs')==1)
  1296. Utils_WatchdogCommon::subscribe($tab,$id);
  1297. Utils_WatchdogCommon::new_event($tab,$id,'C');
  1298. return $id;
  1299. }
  1300. public static function new_record_history($tab,$id,$old_value) {
  1301. DB::Execute('INSERT INTO ' . $tab . '_edit_history(edited_on, edited_by, ' . $tab . '_id) VALUES (%T,%d,%d)', array(date('Y-m-d G:i:s'), Acl::get_user(), $id));
  1302. $edit_id = DB::Insert_ID($tab . '_edit_history', 'id');
  1303. DB::Execute('INSERT INTO ' . $tab . '_edit_history_data(edit_id, field, old_value) VALUES (%d,%s,%s)', array($edit_id, 'id', $old_value));
  1304. return $edit_id;
  1305. }
  1306. public static function update_record($tab,$id,$values,$all_fields = false, $date = null, $dont_notify = false) {
  1307. DB::StartTrans();
  1308. self::init($tab);
  1309. $record = self::get_record($tab, $id, false);
  1310. if (!is_array($record)) {
  1311. DB::CompleteTrans();
  1312. return false;
  1313. }
  1314. $process_method_args = $values;
  1315. $process_method_args['id'] = $id;
  1316. foreach ($record as $k=>$v)
  1317. if (!isset($process_method_args[$k])) $process_method_args[$k] = $v;
  1318. $values = self::record_processing($tab, $process_method_args, 'edit');
  1319. $diff = array();
  1320. self::init($tab);
  1321. foreach(self::$table_rows as $field => $desc){
  1322. if ($desc['type']=='calculated' && preg_match('/^[a-z]+(\([0-9]+\))?$/i',$desc['param'])===0) continue; // FIXME move DB definiton to *_field table
  1323. if ($desc['id']=='id') continue;
  1324. if (!isset($values[$desc['id']])) {
  1325. if ($all_fields) $values[$desc['id']] = '';
  1326. else continue;
  1327. }
  1328. if ($desc['type']=='checkbox') {
  1329. if ($values[$desc['id']]) $values[$desc['id']] = 1;
  1330. else $values[$desc['id']] = 0;
  1331. if ($record[$desc['id']]) $record[$desc['id']] = 1;
  1332. else $record[$desc['id']] = 0;
  1333. }
  1334. if ($desc['type']=='long text')
  1335. $values[$desc['id']] = Utils_BBCodeCommon::optimize($values[$desc['id']]);
  1336. if ($desc['type']=='multiselect') {
  1337. if (!is_array($values[$desc['id']])) $values[$desc['id']] = array($values[$desc['id']]);
  1338. $array_diff = array_diff($record[$desc['id']], $values[$desc['id']]);
  1339. if (empty($array_diff)) {
  1340. $array_diff = array_diff($values[$desc['id']], $record[$desc['id']]);
  1341. if (empty($array_diff)) continue;
  1342. }
  1343. $v = self::encode_multi($values[$desc['id']]);
  1344. $old = self::encode_multi($record[$desc['id']]);
  1345. } elseif ($desc['type'] == 'file') {
  1346. $files = $values[$desc['id']];
  1347. if (is_string($files)) {
  1348. $files = self::decode_multi($files);
  1349. }
  1350. if ($desc['param']['max_files'] && count($files) > $desc['param']['max_files']) {
  1351. throw new Exception('Too many files in field ' . $desc['id']);
  1352. }
  1353. $filestorageIds = Utils_FileStorageCommon::add_files($files, "rb:$tab/$id/$desc[pkey]");
  1354. $values[$desc['id']] = $filestorageIds;
  1355. // Delete files not present in the field right now
  1356. $old = $record[$desc['id']];
  1357. sort($old);
  1358. if ($old == $filestorageIds) continue;
  1359. foreach ($record[$desc['id']] as $file) {
  1360. if (!in_array($file, $filestorageIds)) {
  1361. // delete file
  1362. Utils_FileStorageCommon::delete($file);
  1363. }
  1364. }
  1365. $v = self::encode_multi($filestorageIds);
  1366. $old = self::encode_multi($old);
  1367. } else {
  1368. if ($record[$desc['id']]===$values[$desc['id']]) continue;
  1369. $v = $values[$desc['id']];
  1370. $old = $record[$desc['id']];
  1371. }
  1372. if ($v!=='') DB::Execute('UPDATE '.$tab.'_data_1 SET f_'.$desc['id'].'='.self::get_sql_type($desc['type']).' WHERE id=%d',array($v, $id));
  1373. else DB::Execute('UPDATE '.$tab.'_data_1 SET f_'.$desc['id'].'=NULL WHERE id=%d',array($id));
  1374. $diff[$desc['id']] = $old;
  1375. }
  1376. if(!empty($diff)) {
  1377. @DB::Execute('UPDATE '.$tab.'_data_1 SET indexed=0 WHERE id=%d',array($id));
  1378. self::record_processing($tab, $values, 'edited');
  1379. }
  1380. if (!$dont_notify && !empty($diff)) {
  1381. $diff = self::record_processing($tab, $diff, 'edit_changes');
  1382. DB::Execute('INSERT INTO '.$tab.'_edit_history(edited_on, edited_by, '.$tab.'_id) VALUES (%T,%d,%d)', array((($date==null)?date('Y-m-d G:i:s'):$date), Acl::get_user(), $id));
  1383. $edit_id = DB::Insert_ID(''.$tab.'_edit_history','id');
  1384. foreach($diff as $k=>$v) {
  1385. if (!is_array($v)) $v = array($v);
  1386. foreach($v as $c)
  1387. DB::Execute('INSERT INTO '.$tab.'_edit_history_data(edit_id, field, old_value) VALUES (%d,%s,%s)', array($edit_id, $k, $c));
  1388. }
  1389. Utils_WatchdogCommon::new_event($tab,$id,'E_'.$edit_id);
  1390. }
  1391. return DB::CompleteTrans();
  1392. }
  1393. public static function add_recent_entry($tab, $user_id ,$id){
  1394. self::check_table_name($tab);
  1395. static $rec_size = array();
  1396. if (!isset($rec_size[$tab])) $rec_size[$tab] = DB::GetOne('SELECT recent FROM recordbrowser_table_properties WHERE tab=%s', array($tab));
  1397. $ids = array();
  1398. if ($rec_size[$tab]) {
  1399. $ret = DB::SelectLimit('SELECT '.$tab.'_id FROM '.$tab.'_recent WHERE user_id = %d ORDER BY visited_on DESC',
  1400. $rec_size[$tab],
  1401. -1,
  1402. array($user_id));
  1403. while($row = $ret->FetchRow()) {
  1404. if ($row[$tab.'_id']==$id || !$row[$tab.'_id']) continue;
  1405. if (count($ids)>=$rec_size[$tab]-1) continue;
  1406. $ids[] = $row[$tab.'_id'];
  1407. }
  1408. }
  1409. if (empty($ids)) $where = '';
  1410. else $where = ' AND '.$tab.'_id NOT IN ('.implode(',',$ids).')';
  1411. DB::Execute('DELETE FROM '.$tab.'_recent WHERE user_id = %d'.$where,
  1412. array($user_id));
  1413. if ($rec_size[$tab])
  1414. DB::Execute('INSERT INTO '.$tab.'_recent ('.$tab.'_id, user_id, visited_on) VALUES (%d, %d, %T)',
  1415. array($id,
  1416. $user_id,
  1417. date('Y-m-d H:i:s')));
  1418. }
  1419. public static function merge_crits($a = array(), $b = array(), $or=false) {
  1420. if (is_array($a)) {
  1421. $a = Utils_RecordBrowser_Crits::from_array($a);
  1422. }
  1423. if (!($a instanceof Utils_RecordBrowser_Crits)) {
  1424. $a = new Utils_RecordBrowser_Crits($a);
  1425. }
  1426. if (is_array($b)) {
  1427. $b = Utils_RecordBrowser_Crits::from_array($b);
  1428. }
  1429. if (!($b instanceof Utils_RecordBrowser_Crits)) {
  1430. $b = new Utils_RecordBrowser_Crits($b);
  1431. }
  1432. if ($a->is_empty()) {
  1433. return clone $b;
  1434. }
  1435. if ($b->is_empty()) {
  1436. return clone $a;
  1437. }
  1438. $a = clone $a;
  1439. $b = clone $b;
  1440. $ret = $or ? $a->_or($b) : $a->_and($b);
  1441. return $ret;
  1442. }
  1443. public static function build_query($tab, $crits = null, $admin = false, $order = array(), $tab_alias = 'r') {
  1444. static $stack = array();
  1445. static $cache;
  1446. if (!is_object($crits)) {
  1447. $crits = Utils_RecordBrowser_Crits::from_array($crits);
  1448. }
  1449. $cache_key = $tab . '__' . $tab_alias . '__' . md5(serialize($crits)) . '__' . $admin . '__' . md5(serialize($order)) . '__' . Base_AclCommon::get_user();
  1450. if (isset($cache[$cache_key])) {
  1451. return $cache[$cache_key];
  1452. }
  1453. $access_crits = ($admin || in_array($tab, $stack)) ? true : self::get_access_crits($tab, 'browse');
  1454. if ($access_crits == false) return array();
  1455. elseif ($access_crits !== true) {
  1456. $crits = self::merge_crits($crits, $access_crits);
  1457. }
  1458. if ($admin) {
  1459. $admin_filter = str_replace('<tab>', $tab_alias, self::$admin_filter);
  1460. } else {
  1461. $admin_filter = $tab_alias . '.active=1 AND ';
  1462. }
  1463. array_push($stack, $tab);
  1464. $query_builder = new Utils_RecordBrowser_QueryBuilder($tab, $tab_alias, $admin);
  1465. $ret = $query_builder->build_query($crits, $order, $admin_filter);
  1466. $cache[$cache_key] = $ret;
  1467. array_pop($stack);
  1468. return $ret;
  1469. }
  1470. /**
  1471. * Get records count
  1472. *
  1473. * @param string $tab
  1474. * @param array|Utils_RecordBrowser_Crits $crits
  1475. * @param bool $admin
  1476. * @param array $order Just for SQL cache optimization. Same query will be used to fetch records
  1477. *
  1478. * @return int records count
  1479. */
  1480. public static function get_records_count( $tab, $crits = null, $admin = false, $order = array()) {
  1481. $par = self::build_query($tab, $crits, $admin, $order);
  1482. if (empty($par) || !$par) return 0;
  1483. return DB::GetOne('SELECT COUNT(*) FROM'.$par['sql'], $par['vals']);
  1484. }
  1485. public static function get_next_and_prev_record( $tab, $crits, $order, $id, $last = null) {
  1486. $par = self::build_query($tab, $crits, false, $order);
  1487. if (empty($par) || !$par) return null;
  1488. if ($last===null || is_array($last)) {
  1489. /* Just failsafe - should not happen */
  1490. $ret = DB::GetCol('SELECT id FROM'.$par['sql'].$par['order'], $par['vals']);
  1491. if ($ret===false || $ret===null) return null;
  1492. $k = array_search($id,$ret);
  1493. return array( 'next'=>isset($ret[$k+1])?$ret[$k+1]:null,
  1494. 'curr'=>$k,
  1495. 'prev'=>isset($ret[$k-1])?$ret[$k-1]:null);
  1496. } else {
  1497. $r = DB::SelectLimit('SELECT id FROM'.$par['sql'].$par['order'],3,($last!=0?$last-1:$last), $par['vals']);
  1498. $ret = array();
  1499. while ($row=$r->FetchRow()) {
  1500. $ret[] = $row['id'];
  1501. }
  1502. if ($ret===false || $ret===null) return null;
  1503. if ($last===0) $ret = array(0=>null, 2=>isset($ret[1])?$ret[1]:null);
  1504. return array( 'next'=>isset($ret[2])?$ret[2]:null,
  1505. 'curr'=>$last,
  1506. 'prev'=>isset($ret[0])?$ret[0]:null);
  1507. }
  1508. }
  1509. /**
  1510. * @param string $tab Recordset identifier
  1511. * @param array|Utils_RecordBrowser_Crits $crits
  1512. * @param array $cols not used anymore
  1513. * @param array $order
  1514. * @param int|array $limit nr of rows or array('offset'=>X, 'numrows'=>Y);
  1515. * @param bool $admin
  1516. *
  1517. * @return array|bool
  1518. */
  1519. public static function get_records( $tab, $crits = array(), $cols = array(), $order = array(), $limit = array(), $admin = false) {
  1520. if (!$tab) return false;
  1521. if (is_numeric($limit)) {
  1522. $limit = array('numrows'=>$limit,'offset'=>0);
  1523. } else {
  1524. if (!isset($limit['offset'])) $limit['offset'] = 0;
  1525. if (!isset($limit['numrows'])) $limit['numrows'] = -1;
  1526. }
  1527. if (!$order) $order = array();
  1528. $tab_alias = 'r';
  1529. $fields = "$tab_alias.*";
  1530. self::init($tab);
  1531. $par = self::build_query($tab, $crits, $admin, $order);
  1532. if (empty($par)) return array();
  1533. $ret = DB::SelectLimit('SELECT '.$fields.' FROM'.$par['sql'].$par['order'], $limit['numrows'], $limit['offset'], $par['vals']);
  1534. $records = array();
  1535. self::init($tab);
  1536. $fields = self::$table_rows;
  1537. while ($row = $ret->FetchRow()) {
  1538. if (isset($records[$row['id']])) {
  1539. continue;
  1540. }
  1541. $r = array( 'id'=>$row['id'],
  1542. ':active'=>$row['active'],
  1543. 'created_by'=>$row['created_by'],
  1544. 'created_on'=>$row['created_on']);
  1545. foreach($fields as $desc){
  1546. if (isset($row['f_'.$desc['id']])) {
  1547. if ($desc['type'] == 'multiselect' || $desc['type'] == 'file') {
  1548. $r[$desc['id']] = self::decode_multi($row['f_' . $desc['id']]);
  1549. } elseif ($desc['type']=='text' || $desc['type']=='long text') {
  1550. $r[$desc['id']] = $row['f_' . $desc['id']];
  1551. } else {
  1552. $r[$desc['id']] = $row['f_' . $desc['id']];
  1553. }
  1554. } else {
  1555. if ($desc['type']=='multiselect') $r[$desc['id']] = array();
  1556. else $r[$desc['id']] = '';
  1557. }
  1558. }
  1559. if($admin || self::get_access($tab,'view',$r)) $records[$row['id']] = $r;
  1560. }
  1561. return $records;
  1562. }
  1563. public static function check_record_against_crits($tab, $id, $crits, & $problems = array()) {
  1564. if (is_numeric($id)) $r = self::get_record($tab, $id);
  1565. else $r = $id;
  1566. if (!is_object($crits)) {
  1567. $crits = Utils_RecordBrowser_Crits::from_array($crits);
  1568. }
  1569. $crits_validator = new Utils_RecordBrowser_CritsValidator($tab);
  1570. $crits->normalize();
  1571. list($success, $issues) = $crits_validator->validate($crits, $r);
  1572. if (!$success) {
  1573. $problems = $issues;
  1574. }
  1575. return $success;
  1576. }
  1577. public static function crits_special_values()
  1578. {
  1579. $ret = array();
  1580. $ret[] = new Utils_RecordBrowser_ReplaceValue('USER_ID', __('User Login'), Base_AclCommon::get_user());
  1581. foreach (array('VIEW', 'VIEW_ALL', 'EDIT', 'EDIT_ALL', 'PRINT', 'PRINT_ALL', 'DELETE', 'DELETE_ALL') as $a) {
  1582. $description = 'Allow ' . str_replace('_', ' ', strtolower($a)) . ' record(s)';
  1583. $ret[] = new Utils_RecordBrowser_ReplaceValue("ACCESS_$a", _V($description), 'Utils_RecordBrowserCommon::get_recursive_'.strtolower($a).'_access');
  1584. }
  1585. return $ret;
  1586. }
  1587. public static function get_recursive_access($otab,&$r,$field,$action,$any) {
  1588. self::init($otab);
  1589. $desc = self::$table_rows[self::$hash[$field]];
  1590. $param = self::decode_select_param($desc['param']);
  1591. if($param['single_tab']=='__COMMON__') return $r[$field];
  1592. $ret = true;
  1593. $field_is_empty = true;
  1594. if (!isset($r[$field])) $values = array();
  1595. elseif(!is_array($r[$field])) $values = array($r[$field]);
  1596. else $values = $r[$field];
  1597. foreach($values as $rid) {
  1598. if (!$rid) continue;
  1599. $val = self::decode_record_token($rid, $param['single_tab']);
  1600. if(!$val) continue;
  1601. list($tab, $rid) = $val;
  1602. $rr = self::get_record($tab, $rid);
  1603. $access = self::get_access($tab, $action, $rr);
  1604. $field_is_empty = false;
  1605. if($any && $access) return $r[$field];
  1606. $ret &= $access;
  1607. }
  1608. return $field_is_empty ? true : ($ret ? true : false);
  1609. }
  1610. public static function get_recursive_view_access($tab,&$r,$field) {
  1611. return self::get_recursive_access($tab,$r,$field,'view',true);
  1612. }
  1613. public static function get_recursive_view_all_access($tab,&$r,$field) {
  1614. return self::get_recursive_access($tab,$r,$field,'view',false);
  1615. }
  1616. public static function get_recursive_edit_access($tab,&$r,$field) {
  1617. return self::get_recursive_access($tab,$r,$field,'edit',true);
  1618. }
  1619. public static function get_recursive_edit_all_access($tab,&$r,$field) {
  1620. return self::get_recursive_access($tab,$r,$field,'edit',false);
  1621. }
  1622. public static function get_recursive_print_access($tab,&$r,$field) {
  1623. return self::get_recursive_access($tab,$r,$field,'print',true);
  1624. }
  1625. public static function get_recursive_print_all_access($tab,&$r,$field) {
  1626. return self::get_recursive_access($tab,$r,$field,'print',false);
  1627. }
  1628. public static function get_recursive_delete_access($tab,&$r,$field) {
  1629. return self::get_recursive_access($tab,$r,$field,'delete',true);
  1630. }
  1631. public static function get_recursive_delete_all_access($tab,&$r,$field) {
  1632. return self::get_recursive_access($tab,$r,$field,'delete',false);
  1633. }
  1634. public static function serialize_crits($crits)
  1635. {
  1636. $serialized = serialize($crits);
  1637. if (DB::is_postgresql()) {
  1638. $serialized = bin2hex($serialized);
  1639. }
  1640. return $serialized;
  1641. }
  1642. public static function unserialize_crits($str)
  1643. {
  1644. $ret = @unserialize($str);
  1645. if ($ret === false && DB::is_postgresql()) {
  1646. $ret = unserialize(hex2bin($str));
  1647. }
  1648. return $ret;
  1649. }
  1650. public static function parse_access_crits($str, $human_readable = false) {
  1651. $ret = self::unserialize_crits($str);
  1652. if (!is_object($ret)) {
  1653. $ret = Utils_RecordBrowser_Crits::from_array($ret);
  1654. }
  1655. return $ret->replace_special_values($human_readable);
  1656. }
  1657. public static function add_default_access($tab) {
  1658. Utils_RecordBrowserCommon::add_access($tab, 'view', 'ACCESS:employee');
  1659. Utils_RecordBrowserCommon::add_access($tab, 'add', 'ACCESS:employee');
  1660. Utils_RecordBrowserCommon::add_access($tab, 'edit', 'ACCESS:employee');
  1661. Utils_RecordBrowserCommon::add_access($tab, 'delete', 'ACCESS:employee');
  1662. }
  1663. public static function field_deny_access($tab, $fields, $action='', $clearance=null) {
  1664. if (!self::check_table_name($tab, false, false)) return;
  1665. if (!is_array($fields)) $fields = array($fields);
  1666. $sql = '';
  1667. $vals = array();
  1668. if ($clearance!=null) {
  1669. $sql .= ' WHERE NOT EXISTS (SELECT * FROM '.$tab.'_access_clearance WHERE rule_id=acs.id AND '.implode(' AND ',array_fill(0, count($clearance), 'clearance!=%s')).')';
  1670. $vals = array_values($clearance);
  1671. }
  1672. if ($action!='') {
  1673. if ($sql) $sql .= ' AND ';
  1674. else $sql .= ' WHERE ';
  1675. $sql .= 'action=%s';
  1676. $vals[] = $action;
  1677. }
  1678. $sql = 'SELECT id, id FROM '.$tab.'_access AS acs'.$sql;
  1679. $ids = DB::GetAssoc($sql, $vals);
  1680. foreach ($fields as $f) {
  1681. $f = self::get_field_id($f);
  1682. foreach ($ids as $id)
  1683. DB::Execute('INSERT INTO '.$tab.'_access_fields (rule_id, block_field) VALUES (%d, %s)', array($id, $f));
  1684. }
  1685. }
  1686. public static function wipe_access($tab) {
  1687. if (!self::check_table_name($tab, false, false)) return;
  1688. DB::Execute('DELETE FROM '.$tab.'_access_clearance');
  1689. DB::Execute('DELETE FROM '.$tab.'_access_fields');
  1690. DB::Execute('DELETE FROM '.$tab.'_access');
  1691. }
  1692. public static function delete_access($tab, $id) {
  1693. if (!self::check_table_name($tab, false, false)) return;
  1694. DB::Execute('DELETE FROM '.$tab.'_access_clearance WHERE rule_id=%d', array($id));
  1695. DB::Execute('DELETE FROM '.$tab.'_access_fields WHERE rule_id=%d', array($id));
  1696. DB::Execute('DELETE FROM '.$tab.'_access WHERE id=%d', array($id));
  1697. }
  1698. public static function delete_access_rules($tab, $action, $clearance, $crits = array())
  1699. {
  1700. if (!self::check_table_name($tab, false, false)) return;
  1701. if (!is_array($clearance)) $clearance = array($clearance);
  1702. $clearance_c = count($clearance);
  1703. $serialized = self::serialize_crits($crits);
  1704. $ids = DB::GetCol('SELECT id FROM ' . $tab . '_access WHERE crits=%s AND action=%s', array($serialized, $action));
  1705. $ret = 0;
  1706. foreach ($ids as $rule_id) {
  1707. $existing_clearance = DB::GetCol('SELECT clearance FROM ' . $tab . '_access_clearance WHERE rule_id=%d', array($rule_id));
  1708. if ($clearance_c == count($existing_clearance) &&
  1709. $clearance_c == count(array_intersect($existing_clearance, $clearance))) {
  1710. self::delete_access($tab, $rule_id);
  1711. $ret += 1;
  1712. }
  1713. }
  1714. return $ret;
  1715. }
  1716. public static function add_access($tab, $action, $clearance, $crits=array(), $blocked_fields=array()) {
  1717. if (!self::check_table_name($tab, false, false)) return;
  1718. $serialized = self::serialize_crits($crits);
  1719. DB::Execute('INSERT INTO '.$tab.'_access (crits, action) VALUES (%s, %s)', array($serialized, $action));
  1720. $rule_id = DB::Insert_ID($tab.'_access','id');
  1721. if (!is_array($clearance)) $clearance = array($clearance);
  1722. foreach ($clearance as $c)
  1723. DB::Execute('INSERT INTO '.$tab.'_access_clearance (rule_id, clearance) VALUES (%d, %s)', array($rule_id, $c));
  1724. foreach ($blocked_fields as $f)
  1725. DB::Execute('INSERT INTO '.$tab.'_access_fields (rule_id, block_field) VALUES (%d, %s)', array($rule_id, $f));
  1726. }
  1727. public static function update_access($tab, $id, $action, $clearance, $crits=array(), $blocked_fields=array()) {
  1728. if(is_string($id) && in_array($id,array('grant','restrict'))) return;
  1729. elseif(!is_numeric($id)) throw new Exception('Utils_RecordBrowserCommon::update_access - id have to be a number');
  1730. $serialized = self::serialize_crits($crits);
  1731. DB::Execute('UPDATE ' . $tab . '_access SET crits=%s, action=%s WHERE id=%d', array($serialized, $action, $id));
  1732. if (!is_array($clearance)) $clearance = array($clearance);
  1733. DB::Execute('DELETE FROM '.$tab.'_access_clearance WHERE rule_id=%d', array($id));
  1734. DB::Execute('DELETE FROM '.$tab.'_access_fields WHERE rule_id=%d', array($id));
  1735. foreach ($clearance as $c)
  1736. DB::Execute('INSERT INTO '.$tab.'_access_clearance (rule_id, clearance) VALUES (%d, %s)', array($id, $c));
  1737. foreach ($blocked_fields as $f)
  1738. DB::Execute('INSERT INTO '.$tab.'_access_fields (rule_id, block_field) VALUES (%d, %s)', array($id, $f));
  1739. }
  1740. public static function register_custom_access_callback($tab, $callback, $priority = 10)
  1741. {
  1742. if (!is_callable($callback)) {
  1743. return false;
  1744. }
  1745. if (is_array($callback)) {
  1746. $callback = implode('::', $callback);
  1747. }
  1748. $existing = self::get_custom_access_callbacks($tab);
  1749. if (in_array($callback, $existing)) {
  1750. return false;
  1751. }
  1752. DB::Execute('INSERT INTO recordbrowser_access_methods (tab, func, priority) VALUES (%s, %s, %d)', array($tab, $callback, $priority));
  1753. self::get_custom_access_callbacks(null, true);
  1754. return true;
  1755. }
  1756. public static function unregister_custom_access_callback($tab, $callback)
  1757. {
  1758. if (is_array($callback)) {
  1759. $callback = implode('::', $callback);
  1760. }
  1761. DB::Execute('DELETE FROM recordbrowser_access_methods WHERE tab=%s AND func=%s', array($tab, $callback));
  1762. }
  1763. public static function get_custom_access_callbacks($tab = null, $force_reload = false)
  1764. {
  1765. static $custom_access_callbacks;
  1766. if ($force_reload || $custom_access_callbacks === null) {
  1767. $custom_access_callbacks = array();
  1768. $db = DB::GetAll('SELECT * FROM recordbrowser_access_methods ORDER BY priority DESC');
  1769. foreach ($db as $row) {
  1770. if (!isset($custom_access_callbacks[$row['tab']])) {
  1771. $custom_access_callbacks[$row['tab']] = array();
  1772. }
  1773. $custom_access_callbacks[$row['tab']][] = $row['func'];
  1774. }
  1775. }
  1776. if ($tab === null) {
  1777. return $custom_access_callbacks;
  1778. }
  1779. return isset($custom_access_callbacks[$tab]) ? $custom_access_callbacks[$tab] : array();
  1780. }
  1781. public static function call_custom_access_callbacks($tab, $action, $record = null)
  1782. {
  1783. $callbacks = self::get_custom_access_callbacks($tab);
  1784. $ret = array('grant'=>null, 'restrict'=>null);
  1785. foreach ($callbacks as $callback) {
  1786. $callback_crits = call_user_func($callback, $action, $record, $tab);
  1787. if (is_bool($callback_crits)) {
  1788. $ret[$callback_crits? 'grant': 'restrict'] = true;
  1789. break;
  1790. }
  1791. if ($callback_crits === null) continue;
  1792. // if callback return is crits or crits array use it by default in restrict mode for backward compatibility
  1793. $crits = array(
  1794. 'grant' => null,
  1795. 'restrict' => $callback_crits
  1796. );
  1797. if (is_array($callback_crits) && (isset($callback_crits['grant']) || isset($callback_crits['restrict']))) {
  1798. // if restrict rules are not set make sure the restrict crits are clean
  1799. if (! isset($callback_crits['restrict'])) $callback_crits['restrict'] = null;
  1800. $crits = array_merge($crits, $callback_crits);
  1801. }
  1802. if (!$crits['grant'])
  1803. $crits['grant'] = null;
  1804. foreach ($crits as $mode => $c) {
  1805. $c = is_array($c) ? Utils_RecordBrowser_Crits::from_array($c): $c;
  1806. if ($c instanceof Utils_RecordBrowser_Crits)
  1807. $ret[$mode] = ($ret[$mode] !== null) ? self::merge_crits($ret[$mode], $c, $mode === 'grant'): $c;
  1808. elseif (is_bool($c))
  1809. $ret[$mode] = $c;
  1810. }
  1811. }
  1812. return $ret;
  1813. }
  1814. /**
  1815. *
  1816. * Check if user has access to recordset, record or recordset fields based on action performed
  1817. *
  1818. * @param string $tab
  1819. * @param string $action
  1820. * @param array $record
  1821. * @param boolean $return_crits - deprecated, use method Utils_RecordBrowserCommon::get_access_crits instead
  1822. * @param string $return_in_array - deprecated, use method Utils_RecordBrowserCommon::get_access_rule_crits instead
  1823. * @return false - deny access | array - fields access array
  1824. */
  1825. public static function get_access($tab, $action, $record=null, $return_crits=false, $return_in_array=false){
  1826. $access = Utils_RecordBrowser_Access::create($tab, $action, $record);
  1827. //start deprecated code - used for backward compatibility
  1828. if ($return_crits) {
  1829. if ($return_in_array)
  1830. return $access->getRuleCrits();
  1831. return $access->getCrits();;
  1832. }
  1833. //end deprecated code
  1834. return $access->getUserAccess(self::$admin_access);
  1835. }
  1836. /**
  1837. * @param string $tab
  1838. * @param string $action
  1839. * @param array $record
  1840. * @return null|boolean|Utils_RecordBrowser_Crits
  1841. */
  1842. public static function get_access_crits($tab, $action, $record=null) {
  1843. $access = Utils_RecordBrowser_Access::create($tab, $action, $record);
  1844. return $access->getCrits();
  1845. }
  1846. /**
  1847. * @param string $tab
  1848. * @param string $action
  1849. * @param array $record
  1850. * @return array - rule_id => rule
  1851. */
  1852. public static function get_access_rule_crits($tab, $action, $record=null) {
  1853. $access = Utils_RecordBrowser_Access::create($tab, $action, $record);
  1854. return $access->getRuleCrits();
  1855. }
  1856. public static function is_record_active($record) {
  1857. if (isset($record[':active']) && !$record[':active'])
  1858. return false;
  1859. return true;
  1860. }
  1861. public static function get_record_info($tab, $id) {
  1862. self::check_table_name($tab);
  1863. $created = DB::GetRow('SELECT created_on, created_by FROM '.$tab.'_data_1 WHERE id=%d', array($id));
  1864. $edited = DB::GetRow('SELECT edited_on, edited_by FROM '.$tab.'_edit_history WHERE '.$tab.'_id=%d ORDER BY edited_on DESC', array($id));
  1865. if (!isset($edited['edited_on'])) $edited['edited_on'] = null;
  1866. if (!isset($edited['edited_by'])) $edited['edited_by'] = null;
  1867. if (!isset($created['created_on'])) trigger_error('There is no such record as '.$id.' in table '.$tab, E_USER_ERROR);
  1868. return array( 'created_on'=>$created['created_on'],'created_by'=>$created['created_by'],
  1869. 'edited_on'=>$edited['edited_on'],'edited_by'=>$edited['edited_by'],
  1870. 'id'=>$id);
  1871. }
  1872. public static function get_fav_button($tab, $id, $isfav = null) {
  1873. $tag_id = 'rb_fav_button_'.$tab.'_'.$id;
  1874. return '<span id="'.$tag_id.'">'.self::get_fav_button_tags($tab, $id, $isfav).'</span>';
  1875. }
  1876. public static function get_fav_button_tags($tab, $id, $isfav = null) {
  1877. self::check_table_name($tab);
  1878. $star_on = Base_ThemeCommon::get_template_file('Utils_RecordBrowser','star_fav.png');
  1879. $star_off = Base_ThemeCommon::get_template_file('Utils_RecordBrowser','star_nofav.png');
  1880. load_js('modules/Utils/RecordBrowser/favorites.js');
  1881. if ($isfav===null) $isfav = DB::GetOne('SELECT '.$tab.'_id FROM '.$tab.'_favorite WHERE user_id=%d AND '.$tab.'_id=%d', array(Acl::get_user(), $id));
  1882. $tag_id = 'rb_fav_button_'.$tab.'_'.$id;
  1883. return '<a '.Utils_TooltipCommon::open_tag_attrs(($isfav?__('This item is on your favorites list').'<br>'.__('Click to remove it from your favorites'):__('Click to add this item to favorites'))).' onclick="utils_recordbrowser_set_favorite('.($isfav?0:1).',\''.$tab.'\','.$id.',\''.$tag_id.'\')" href="javascript:void(0);"><i class="fa fa-lg fa-fw '.($isfav==false?"fa-star-o text-muted":"fa-star text-warning").'"></i></a>';
  1884. }
  1885. public static function set_favs($tab, $id, $state) {
  1886. self::check_table_name($tab);
  1887. if ($state) {
  1888. if (DB::GetOne('SELECT * FROM '.$tab.'_favorite WHERE user_id=%d AND '.$tab.'_id=%d', array(Acl::get_user(), $id))) return;
  1889. DB::Execute('INSERT INTO '.$tab.'_favorite (user_id, '.$tab.'_id) VALUES (%d, %d)', array(Acl::get_user(), $id));
  1890. } else {
  1891. DB::Execute('DELETE FROM '.$tab.'_favorite WHERE user_id=%d AND '.$tab.'_id=%d', array(Acl::get_user(), $id));
  1892. }
  1893. }
  1894. public static function get_html_record_info($tab, $id){
  1895. if (is_string($id)) {
  1896. // to separate id in recurrent event
  1897. $tmp = explode('_', $id);
  1898. $id = $tmp[0];
  1899. }
  1900. if (is_numeric($id)) $info = Utils_RecordBrowserCommon::get_record_info($tab, $id);
  1901. elseif (is_array($id)) $info = $id;
  1902. else trigger_error('Cannot decode record id: ' . print_r($id, true), E_USER_ERROR);
  1903. if (isset($info['id'])) $id = $info['id'];
  1904. // If CRM Module is not installed get user login only
  1905. $created_by = Base_UserCommon::get_user_label($info['created_by']);
  1906. $htmlinfo=array(
  1907. __('Record ID').':'=>$id,
  1908. __('Created by').':'=>$created_by,
  1909. __('Created on').':'=>Base_RegionalSettingsCommon::time2reg($info['created_on'])
  1910. );
  1911. if ($info['edited_on']!==null) {
  1912. $htmlinfo=$htmlinfo+array(
  1913. __('Edited by').':'=>$info['edited_by']!==null?Base_UserCommon::get_user_label($info['edited_by']):'',
  1914. __('Edited on').':'=>Base_RegionalSettingsCommon::time2reg($info['edited_on'])
  1915. );
  1916. }
  1917. return Utils_TooltipCommon::format_info_tooltip($htmlinfo);
  1918. }
  1919. public static function get_record($tab, $id, $htmlspecialchars=true) {
  1920. if (!is_numeric($id)) return null;
  1921. if (isset($id)) {
  1922. if(!self::check_table_name($tab,false,false)) return null;
  1923. self::init($tab);
  1924. $row = DB::GetRow('SELECT * FROM '.$tab.'_data_1 WHERE id=%d', array($id));
  1925. $record = array('id'=>$id);
  1926. if (!isset($row['active'])) return null;
  1927. foreach(array('created_by','created_on') as $v)
  1928. $record[$v] = $row[$v];
  1929. $record[':active'] = $row['active'];
  1930. foreach(self::$table_rows as $field=>$desc) {
  1931. if ($desc['type']==='multiselect' || $desc['type'] === 'file') {
  1932. if (!isset($row['f_'.$desc['id']])) $r = array();
  1933. else $r = self::decode_multi($row['f_'.$desc['id']]);
  1934. $record[$desc['id']] = $r;
  1935. } else {
  1936. $record[$desc['id']] = (isset($row['f_'.$desc['id']])?$row['f_'.$desc['id']]:'');
  1937. if ($htmlspecialchars && $desc['type'] == 'text') $record[$desc['id']] = htmlspecialchars($record[$desc['id']]);
  1938. }
  1939. }
  1940. return $record;
  1941. } else {
  1942. return null;
  1943. }
  1944. }
  1945. public static function get_record_respecting_access($tab, $id, $access_mode = 'view', $htmlspecialchars = true)
  1946. {
  1947. $record = self::get_record($tab, $id, $htmlspecialchars);
  1948. return self::filter_record_by_access($tab, $record, $access_mode);
  1949. }
  1950. public static function filter_record_by_access($tab, $record, $access_mode = 'view')
  1951. {
  1952. if (!$record) {
  1953. return $record;
  1954. }
  1955. $access = self::get_access($tab, $access_mode, $record);
  1956. if (is_array($access)) {
  1957. foreach ($access as $field => $has_access) {
  1958. if (!$has_access) {
  1959. $record[$field] = null;
  1960. }
  1961. }
  1962. } else if (!$access) {
  1963. $record = false;
  1964. }
  1965. return $record;
  1966. }
  1967. /**
  1968. * Change record state: active / inactive. Soft delete.
  1969. *
  1970. * @param $tab Recordset identifier
  1971. * @param $id Record ID
  1972. * @param $state Active / Inactive state
  1973. *
  1974. * @return bool True when status has been changed, false otherwise
  1975. */
  1976. public static function set_active($tab, $id, $state)
  1977. {
  1978. self::check_table_name($tab);
  1979. $current = DB::GetOne('SELECT active FROM ' . $tab . '_data_1 WHERE id=%d', array($id));
  1980. if ($current == ($state ? 1 : 0)) {
  1981. return false;
  1982. }
  1983. $record = self::get_record($tab, $id);
  1984. if (!$record) {
  1985. return false;
  1986. }
  1987. $values = self::record_processing($tab, $record, $state ? 'restore' : 'delete');
  1988. if ($values === false) {
  1989. return false;
  1990. }
  1991. @DB::Execute('UPDATE ' . $tab . '_data_1 SET active=%d,indexed=0 WHERE id=%d', array($state ? 1 : 0, $id));
  1992. $tab_prop = DB::GetRow('SELECT id,search_include FROM recordbrowser_table_properties WHERE tab=%s',array($tab));
  1993. if ($tab_prop['search_include'] > 0) {
  1994. DB::Execute('DELETE FROM recordbrowser_search_index WHERE tab_id=%d AND record_id=%d',array($tab_prop['id'],$id));
  1995. }
  1996. $edit_id = self::new_record_history($tab,$id,$state ? 'RESTORED' : 'DELETED');
  1997. Utils_WatchdogCommon::new_event($tab, $id, ($state ? 'R' : 'D').'_'.$edit_id);
  1998. self::record_processing($tab, $record, $state ? 'restored' : 'deleted');
  1999. return true;
  2000. }
  2001. /**
  2002. * Delete record.
  2003. *
  2004. * @param string $tab Recordset identifier
  2005. * @param int $id Record ID
  2006. * @param bool $perma Delete permanently with all edit history
  2007. *
  2008. * @return bool True when record has been deleted, false otherwise
  2009. */
  2010. public static function delete_record($tab, $id, $perma = false)
  2011. {
  2012. $ret = false;
  2013. if (!$perma) {
  2014. $ret = self::set_active($tab, $id, false);
  2015. } elseif (self::check_table_name($tab)) {
  2016. $record = self::get_record($tab, $id);
  2017. $values = self::record_processing($tab, $record, 'delete');
  2018. if ($values === false) {
  2019. $ret = false;
  2020. } else {
  2021. self::delete_record_history($tab, $id);
  2022. self::delete_from_favorite($tab, $id);
  2023. self::delete_from_recent($tab, $id);
  2024. DB::Execute('DELETE FROM ' . $tab . '_data_1 WHERE id=%d', array($id));
  2025. $ret = DB::Affected_Rows() > 0;
  2026. if ($ret) {
  2027. self::record_processing($tab, $record, 'deleted');
  2028. }
  2029. }
  2030. }
  2031. return $ret;
  2032. }
  2033. /**
  2034. * Delete all history entries for specified record.
  2035. *
  2036. * @param $tab Recordset identifier
  2037. * @param $id Record ID
  2038. *
  2039. * @return int Number of affected edits deleted
  2040. */
  2041. public static function delete_record_history($tab, $id)
  2042. {
  2043. $sql = 'DELETE FROM ' . $tab . '_edit_history_data WHERE edit_id IN' .
  2044. ' (SELECT id FROM ' . $tab . '_edit_history WHERE ' . $tab . '_id = %d)';
  2045. DB::Execute($sql, array($id));
  2046. $sql = 'DELETE FROM ' . $tab . '_edit_history WHERE ' . $tab . '_id = %d';
  2047. DB::Execute($sql, array($id));
  2048. return DB::Affected_Rows();
  2049. }
  2050. /**
  2051. * Delete favorites entries for specified record.
  2052. *
  2053. * @param $tab Recordset identifier
  2054. * @param $id Record ID
  2055. *
  2056. * @return int Number of favorites entries deleted
  2057. */
  2058. public static function delete_from_favorite($tab, $id)
  2059. {
  2060. $sql = 'DELETE FROM ' . $tab . '_favorite WHERE ' . $tab . '_id = %d';
  2061. DB::Execute($sql, array($id));
  2062. return DB::Affected_Rows();
  2063. }
  2064. /**
  2065. * Delete recent entries for specified record.
  2066. *
  2067. * @param $tab Recordset identifier
  2068. * @param $id Record ID
  2069. *
  2070. * @return int Number of recent entries deleted
  2071. */
  2072. public static function delete_from_recent($tab, $id)
  2073. {
  2074. $sql = 'DELETE FROM ' . $tab . '_recent WHERE ' . $tab . '_id = %d';
  2075. DB::Execute($sql, array($id));
  2076. return DB::Affected_Rows();
  2077. }
  2078. /**
  2079. * Restore record.
  2080. *
  2081. * @param $tab Recordset identifier
  2082. * @param $id Record ID
  2083. *
  2084. * @return bool True when record has been restored, false otherwise
  2085. */
  2086. public static function restore_record($tab, $id) {
  2087. return self::set_active($tab, $id, true);
  2088. }
  2089. public static function no_wrap($s) {
  2090. $content_no_wrap = $s;
  2091. preg_match_all('/>([^\<\>]*)</', $s, $match);
  2092. if (empty($match[1])) return str_replace(' ','&nbsp;', $s); // if no matches[1] then that's not html
  2093. // below handle html
  2094. foreach ($match[1] as $v) {
  2095. if ($v === ' ') continue; // do not replace single space in html
  2096. $content_no_wrap = str_replace($v, str_replace(' ', '&nbsp;', $v), $content_no_wrap);
  2097. }
  2098. return $content_no_wrap;
  2099. }
  2100. public static function get_new_record_href($tab, $def, $id='none', $check_defaults=true){
  2101. self::check_table_name($tab);
  2102. if (class_exists('Utils_RecordBrowser') && Utils_RecordBrowser::$clone_result!==null) {
  2103. if (is_numeric(Utils_RecordBrowser::$clone_result)) Base_BoxCommon::push_module(Utils_RecordBrowser::module_name(),'view_entry',array('view', Utils_RecordBrowser::$clone_result), array(Utils_RecordBrowser::$clone_tab));
  2104. Utils_RecordBrowser::$clone_result = null;
  2105. }
  2106. $def_key = $def;
  2107. if (is_array($check_defaults)) foreach ($check_defaults as $c) unset($def_key[$c]);
  2108. $def_md5 = md5(serialize($def_key));
  2109. // print_r($_REQUEST);
  2110. // print('<br>'.$tab.' - '.$def_md5.' - '.$id.' - '.$check_defaults);
  2111. // print('<hr>');
  2112. if (isset($_REQUEST['__add_record_to_RB_table']) &&
  2113. isset($_REQUEST['__add_record_id']) &&
  2114. isset($_REQUEST['__add_record_def']) &&
  2115. ($tab==$_REQUEST['__add_record_to_RB_table']) &&
  2116. (!$check_defaults || $def_md5==$_REQUEST['__add_record_def']) &&
  2117. ($id==$_REQUEST['__add_record_id'])) {
  2118. unset($_REQUEST['__add_record_to_RB_table']);
  2119. unset($_REQUEST['__add_record_id']);
  2120. unset($_REQUEST['__add_record_def']);
  2121. Base_BoxCommon::push_module(Utils_RecordBrowser::module_name(),'view_entry',array('add', null, $def), array($tab));
  2122. return array();
  2123. }
  2124. return array('__add_record_to_RB_table'=>$tab, '__add_record_id'=>$id, '__add_record_def'=>$def_md5);
  2125. }
  2126. public static function create_new_record_href($tab, $def, $id='none', $check_defaults=true, $multiple_defaults=false){
  2127. if($multiple_defaults) {
  2128. static $done = false;
  2129. if($done) return Libs_LeightboxCommon::get_open_href('actionbar_rb_new_record');
  2130. eval_js_once('actionbar_rb_new_record_deactivate = function(){leightbox_deactivate(\'actionbar_rb_new_record\');}');
  2131. $th = Base_ThemeCommon::init_smarty();
  2132. $cds = array();
  2133. foreach ($def as $k=>$v) {
  2134. $cds[] = array( 'label'=>_V($k),
  2135. 'open'=>'<a OnClick="actionbar_rb_new_record_deactivate();'.Module::create_href_js(self::get_new_record_href($tab,$v['defaults'], $id, $check_defaults)).'">',
  2136. 'icon'=>$v['icon'],
  2137. 'close'=>'</a>'
  2138. );
  2139. }
  2140. $th->assign('custom_defaults', $cds);
  2141. ob_start();
  2142. Base_ThemeCommon::display_smarty($th,'Utils_RecordBrowser','new_record_leightbox');
  2143. $panel = ob_get_clean();
  2144. Libs_LeightboxCommon::display('actionbar_rb_new_record',$panel,__('New record'));
  2145. $done = true;
  2146. return Libs_LeightboxCommon::get_open_href('actionbar_rb_new_record');
  2147. } else
  2148. return Module::create_href(self::get_new_record_href($tab,$def, $id, $check_defaults));
  2149. }
  2150. public static function get_record_href_array($tab, $id, $action='view'){
  2151. self::check_table_name($tab);
  2152. if (isset($_REQUEST['__jump_to_RB_table']) &&
  2153. ($tab==$_REQUEST['__jump_to_RB_table']) &&
  2154. ($id==$_REQUEST['__jump_to_RB_record']) &&
  2155. ($action==$_REQUEST['__jump_to_RB_action'])) {
  2156. unset($_REQUEST['__jump_to_RB_record']);
  2157. unset($_REQUEST['__jump_to_RB_table']);
  2158. unset($_REQUEST['__jump_to_RB_action']);
  2159. Base_BoxCommon::push_module(Utils_RecordBrowser::module_name(),'view_entry_with_REQUEST',array($action, $id, array(), true, $_REQUEST),array($tab));
  2160. return array();
  2161. }
  2162. return array('__jump_to_RB_table'=>$tab, '__jump_to_RB_record'=>$id, '__jump_to_RB_action'=>$action);
  2163. }
  2164. public static function create_record_href($tab, $id, $action='view',$more=array()){
  2165. return Module::create_href(self::get_record_href_array($tab,$id,$action)+$more);
  2166. }
  2167. public static function record_link_open_tag_r($tab, $record, $nolink=false, $action='view', $more=array())
  2168. {
  2169. self::check_table_name($tab);
  2170. $ret = '';
  2171. if (!isset($record['id']) || !is_numeric($record['id'])) {
  2172. return self::$del_or_a = '';
  2173. }
  2174. if (class_exists('Utils_RecordBrowser') &&
  2175. isset(Utils_RecordBrowser::$access_override) &&
  2176. Utils_RecordBrowser::$access_override['tab']==$tab &&
  2177. Utils_RecordBrowser::$access_override['id']==$record['id']) {
  2178. self::$del_or_a = '</a>';
  2179. if (!$nolink) $ret = '<a '.self::create_record_href($tab, $record['id'], $action, $more).'>';
  2180. else self::$del_or_a = '';
  2181. } else {
  2182. $ret = '';
  2183. $tip = '';
  2184. self::$del_or_a = '';
  2185. $has_access = self::get_access($tab, 'view', $record);
  2186. if (!self::is_record_active($record)) {
  2187. $tip = __('This record was deleted from the system, please edit current record or contact system administrator');
  2188. $ret = '<del>';
  2189. self::$del_or_a = '</del>';
  2190. }
  2191. if (!$has_access) {
  2192. $tip .= ($tip?'<br>':'').__('You don\'t have permission to view this record.');
  2193. }
  2194. $tip = $tip ? Utils_TooltipCommon::open_tag_attrs($tip) : '';
  2195. if (!$nolink) {
  2196. if($has_access) {
  2197. $href = self::create_record_href($tab, $record['id'], $action, $more);
  2198. $ret = '<a '.$tip.' '.$href.'>'.$ret;
  2199. self::$del_or_a .= '</a>';
  2200. } else {
  2201. $ret = '<span '.$tip.'>'.$ret;
  2202. self::$del_or_a .= '</span>';
  2203. }
  2204. }
  2205. }
  2206. return $ret;
  2207. }
  2208. public static function record_link_open_tag($tab, $id, $nolink=false, $action='view', $more=array()){
  2209. self::check_table_name($tab);
  2210. $ret = '';
  2211. if (!is_numeric($id)) {
  2212. return self::$del_or_a = '';
  2213. }
  2214. if (class_exists('Utils_RecordBrowser') &&
  2215. isset(Utils_RecordBrowser::$access_override) &&
  2216. Utils_RecordBrowser::$access_override['tab']==$tab &&
  2217. Utils_RecordBrowser::$access_override['id']==$id) {
  2218. self::$del_or_a = '</a>';
  2219. if (!$nolink) $ret = '<a '.self::create_record_href($tab, $id, $action, $more).'>';
  2220. else self::$del_or_a = '';
  2221. } else {
  2222. $record = self::get_record($tab, $id);
  2223. $ret = self::record_link_open_tag_r($tab, $record, $nolink, $action, $more);
  2224. }
  2225. return $ret;
  2226. }
  2227. public static function record_link_close_tag(){
  2228. return self::$del_or_a;
  2229. }
  2230. public static function create_linked_label($tab, $cols, $id, $nolink=false, $tooltip=false, $more=array()){
  2231. if (!is_numeric($id)) return '';
  2232. if (!is_array($cols))
  2233. $cols = explode('|', $cols);
  2234. $record = self::get_record($tab, $id);
  2235. $fields = array_map(array('Utils_RecordBrowserCommon', 'get_field_id'), $cols);
  2236. $record_vals = self::get_record_vals($tab, $record, true, $fields, false);
  2237. if (empty($record_vals)) return '';
  2238. $vals = array();
  2239. foreach ($fields as $field) {
  2240. if (empty($record_vals[$field])) continue;
  2241. $vals[] = $record_vals[$field];
  2242. }
  2243. $record_label = implode(' ', $vals);
  2244. if (!$record_label) $record_label = self::get_caption($tab) . ": " . sprintf("#%06d", $id);
  2245. $text = self::create_record_tooltip($record_label, $tab, $id, $nolink, $tooltip);
  2246. return self::record_link_open_tag_r($tab, $record, $nolink, 'view', $more) .
  2247. $text . self::record_link_close_tag();
  2248. }
  2249. public static function create_linked_text($text, $tab, $id, $nolink=false, $tooltip=true, $more=array()){
  2250. if ($nolink) return $text;
  2251. if (!is_numeric($id)) return '';
  2252. $text = self::create_record_tooltip($text, $tab, $id, $nolink, $tooltip);
  2253. return self::record_link_open_tag($tab, $id, $nolink, 'view', $more) .
  2254. $text . self::record_link_close_tag();
  2255. }
  2256. public static function create_record_tooltip($text, $tab, $id, $nolink=false, $tooltip=true){
  2257. if (!$tooltip || $nolink || Utils_TooltipCommon::is_tooltip_code_in_str($text))
  2258. return $text;
  2259. if (!is_array($tooltip))
  2260. return self::create_default_record_tooltip_ajax($text, $tab, $id);
  2261. //args name => expected index (in case of numeric indexed array)
  2262. $tooltip_create_args = array('tip'=>0, 'args'=>1, 'help'=>1, 'max_width'=>2);
  2263. foreach ($tooltip_create_args as $name=>&$key) {
  2264. switch (true) {
  2265. case isset($tooltip[$name]):
  2266. $key = $tooltip[$name];
  2267. break;
  2268. case isset($tooltip[$key]):
  2269. $key = $tooltip[$key];
  2270. break;
  2271. default:
  2272. $key = null;
  2273. break;
  2274. }
  2275. }
  2276. if (is_callable($tooltip_create_args['tip'])) {
  2277. unset($tooltip_create_args['help']);
  2278. if (!is_array($tooltip_create_args['args']))
  2279. $tooltip_create_args['args'] = array($tooltip_create_args['args']);
  2280. $tooltip_create_callback = array('Utils_TooltipCommon', 'ajax_create');
  2281. }
  2282. else {
  2283. unset($tooltip_create_args['args']);
  2284. $tooltip_create_callback = array('Utils_TooltipCommon', 'create');
  2285. }
  2286. array_unshift($tooltip_create_args, $text);
  2287. //remove null values from end of the create_tooltip_args to ensure default argument values are set in the callback
  2288. while (is_null(end($tooltip_create_args)))
  2289. array_pop($tooltip_create_args);
  2290. return call_user_func_array($tooltip_create_callback, $tooltip_create_args);
  2291. }
  2292. public static function get_record_vals($tab, $record, $nolink=false, $fields = array(), $silent = true){
  2293. if (is_numeric($record)) $record = self::get_record($tab, $record);
  2294. if (!is_array($record)) return array();
  2295. self::init($tab);
  2296. if (empty($fields)) {
  2297. $fields = array_keys(self::$hash);
  2298. }
  2299. else {
  2300. $available_fields = array_intersect(array_keys(self::$hash), $fields);
  2301. if (!$silent && count($available_fields) != count($fields)) {
  2302. trigger_error('Unknown field names: ' . implode(', ', array_diff($fields, $available_fields)), E_USER_ERROR);
  2303. }
  2304. $fields = $available_fields;
  2305. }
  2306. $ret = array();
  2307. foreach ($fields as $field) {
  2308. if (!isset($record[$field])) continue;
  2309. $ret[$field] = self::get_val($tab, $field, $record, $nolink);
  2310. }
  2311. return $ret;
  2312. }
  2313. public static function create_default_linked_label($tab, $id, $nolink=false, $table_name=true, $detailed_tooltip = true){
  2314. if (!is_numeric($id)) return '';
  2315. $record = self::get_record($tab,$id);
  2316. if(!$record) return '';
  2317. $description_callback = self::get_description_callback($tab);
  2318. $description_fields = self::get_description_fields($tab);
  2319. $access = self::get_access($tab, 'view', $record);
  2320. $tab_caption = self::get_caption($tab);
  2321. if(!$tab_caption || $tab_caption == '---') $tab_caption = $tab;
  2322. $label = '';
  2323. if ($access) {
  2324. if ($description_fields) {
  2325. $put_space_before = false;
  2326. foreach ($description_fields as $field) {
  2327. if ($field[0] === '"') {
  2328. $label .= trim($field, '"');
  2329. $put_space_before = false;
  2330. } else {
  2331. $field_id = self::get_field_id($field);
  2332. if ($access === true || (array_key_exists($field_id, $access) && $access[$field_id])) {
  2333. $field_val = self::get_val($tab, $field, $record, true);
  2334. if ($field_val) {
  2335. if ($put_space_before) $label .= ' ';
  2336. $label .= $field_val;
  2337. $put_space_before = true;
  2338. }
  2339. }
  2340. }
  2341. }
  2342. } elseif ($description_callback) {
  2343. $label = call_user_func($description_callback, $record, $nolink);
  2344. } else {
  2345. $field = DB::GetOne('SELECT field FROM ' . $tab . '_field WHERE (type=\'autonumber\' OR ((type=\'text\' OR type=\'commondata\' OR type=\'integer\' OR type=\'date\') AND required=1)) AND visible=1 AND active=1 ORDER BY position');
  2346. if ($field) {
  2347. $label = self::get_val($tab, $field, $record, $nolink);
  2348. }
  2349. }
  2350. }
  2351. if (!$label) {
  2352. $label = sprintf("%s: #%06d", $tab_caption, $id);
  2353. } else {
  2354. $label = ($table_name? $tab_caption . ': ': '') . $label;
  2355. }
  2356. $ret = self::record_link_open_tag_r($tab, $record, $nolink) . $label . self::record_link_close_tag();
  2357. if ($nolink == false && $detailed_tooltip) {
  2358. $ret = self::create_default_record_tooltip_ajax($ret, $tab, $id);
  2359. }
  2360. return $ret;
  2361. }
  2362. public static function create_default_record_tooltip_ajax($string, $tab, $id, $force = false)
  2363. {
  2364. if ($force == false && Utils_TooltipCommon::is_tooltip_code_in_str($string)) {
  2365. return $string;
  2366. }
  2367. $string = Utils_TooltipCommon::ajax_create($string, array(__CLASS__, 'default_record_tooltip'), array($tab, $id));
  2368. return $string;
  2369. }
  2370. public static function get_record_tooltip_data($tab, $record_id)
  2371. {
  2372. $record = self::get_record($tab, $record_id);
  2373. if (!$record[':active']) {
  2374. return array();
  2375. }
  2376. $cols = self::init($tab);
  2377. $access = self::get_access($tab, 'view', $record);
  2378. $data = array();
  2379. foreach ($cols as $desc) {
  2380. if ($desc['tooltip'] && $access[$desc['id']]) {
  2381. $data[_V($desc['name'])] = self::get_val($tab, $desc['id'], $record, true);
  2382. }
  2383. }
  2384. return $data;
  2385. }
  2386. public static function default_record_tooltip($tab, $record_id)
  2387. {
  2388. $data = self::get_record_tooltip_data($tab, $record_id);
  2389. return Utils_TooltipCommon::format_info_tooltip($data);
  2390. }
  2391. public static function display_linked_field_label($record, $nolink=false, $desc=null, $tab = ''){
  2392. return Utils_RecordBrowserCommon::create_linked_label_r($tab, $desc['id'], $record, $nolink);
  2393. }
  2394. public static function create_linked_label_r($tab, $cols, $r, $nolink=false, $tooltip=false){
  2395. if (!is_array($cols))
  2396. $cols = array($cols);
  2397. $open_tag = self::record_link_open_tag_r($tab, $r, $nolink);
  2398. $close_tag = self::record_link_close_tag();
  2399. self::init($tab);
  2400. $vals = array();
  2401. foreach ($cols as $col) {
  2402. if (isset(self::$table_rows[$col]))
  2403. $col = self::$table_rows[$col]['id'];
  2404. elseif (!isset(self::$hash[$col]))
  2405. trigger_error('Unknown column name: ' . $col, E_USER_ERROR);
  2406. if ($r[$col])
  2407. $vals[] = $r[$col];
  2408. }
  2409. $text = self::create_record_tooltip(implode(' ', $vals), $tab, $r['id'], $nolink, $tooltip);
  2410. return $open_tag . $text . $close_tag;
  2411. }
  2412. public static function record_bbcode($tab, $fields, $text, $record_id, $opt, $tag = null) {
  2413. if (!is_numeric($record_id)) {
  2414. $parts = explode(' ', $text);
  2415. $crits = array();
  2416. foreach ($parts as $k=>$v) {
  2417. $v = "%$v%";
  2418. $chr = '(';
  2419. foreach ($fields as $f) {
  2420. $crits[$chr.str_repeat('_', $k).$f] = $v;
  2421. $chr='|';
  2422. }
  2423. }
  2424. $rec = Utils_RecordBrowserCommon::get_records($tab, $crits, array(), array(), 1);
  2425. if (is_array($rec) && !empty($rec)) $rec = array_shift($rec);
  2426. else {
  2427. $crits = array();
  2428. foreach ($parts as $k=>$v) {
  2429. $v = "%$v%";
  2430. $chr = '(';
  2431. foreach ($fields as $f) {
  2432. $crits[$chr.str_repeat('_', $k).'~'.$f] = $v;
  2433. $chr='|';
  2434. }
  2435. }
  2436. $rec = Utils_RecordBrowserCommon::get_records($tab, $crits, array(), array(), 1);
  2437. if (is_array($rec)) $rec = array_shift($rec);
  2438. else $rec = null;
  2439. }
  2440. } else {
  2441. $rec = Utils_RecordBrowserCommon::get_record($tab, $record_id);
  2442. }
  2443. if ($opt) {
  2444. if (!$rec) return null;
  2445. $tag_param = $rec['id'];
  2446. if ($tag == 'rb') $tag_param = "$tab/$tag_param";
  2447. return Utils_BBCodeCommon::create_bbcode(null, $tag_param, $text);
  2448. }
  2449. if ($rec) {
  2450. $access = self::get_access($tab, 'view', $rec);
  2451. if (!$access) {
  2452. $text = "[" . __('Link to record') . ']';
  2453. }
  2454. if (!$text) {
  2455. if ($fields) {
  2456. return self::create_linked_label_r($tab, $fields, $rec);
  2457. }
  2458. return self::create_default_linked_label($tab, $rec['id']);
  2459. }
  2460. return Utils_RecordBrowserCommon::record_link_open_tag_r($tab, $rec).$text.Utils_RecordBrowserCommon::record_link_close_tag();
  2461. }
  2462. $msg = __('Record not found');
  2463. if ($tag == 'rb') {
  2464. if (!self::check_table_name($tab, false, false)) {
  2465. $msg = __('Recordset not found');
  2466. }
  2467. return Utils_BBCodeCommon::create_bbcode($tag, "$tab/$record_id", $text, $msg);
  2468. }
  2469. return Utils_BBCodeCommon::create_bbcode($tag, $record_id, $text, $msg);
  2470. }
  2471. public static function applet_settings($some_more = array()) {
  2472. $some_more = array_merge($some_more,array(
  2473. array('label'=>__('Actions'),'name'=>'actions_header','type'=>'header'),
  2474. array('label'=>__('Info'),'name'=>'actions_info','type'=>'checkbox','default'=>true),
  2475. array('label'=>__('View'),'name'=>'actions_view','type'=>'checkbox','default'=>false),
  2476. array('label'=>__('Edit'),'name'=>'actions_edit','type'=>'checkbox','default'=>true),
  2477. array('label'=>__('Delete'),'name'=>'actions_delete','type'=>'checkbox','default'=>false),
  2478. array('label'=>__('View edit history'),'name'=>'actions_history','type'=>'checkbox','default'=>false),
  2479. ));
  2480. return $some_more;
  2481. }
  2482. /**
  2483. * Returns older version of te record.
  2484. *
  2485. * @param RecordSet - name of the recordset
  2486. * @param Record ID - ID of the record
  2487. * @param Revision ID - RB will backtrace all edits on that record down-to and including edit with this ID
  2488. */
  2489. public static function get_record_revision($tab, $id, $rev_id) {
  2490. self::init($tab);
  2491. $r = self::get_record($tab, $id);
  2492. $ret = DB::Execute('SELECT id, edited_on, edited_by FROM '.$tab.'_edit_history WHERE '.$tab.'_id=%d AND id>=%d ORDER BY edited_on DESC, id DESC',array($id, $rev_id));
  2493. while ($row = $ret->FetchRow()) {
  2494. $changed = array();
  2495. $ret2 = DB::Execute('SELECT * FROM '.$tab.'_edit_history_data WHERE edit_id=%d',array($row['id']));
  2496. while($row2 = $ret2->FetchRow()) {
  2497. $k = $row2['field'];
  2498. $v = $row2['old_value'];
  2499. if ($k=='id') $r['active'] = ($v!='DELETED');
  2500. else {
  2501. if (!isset(self::$hash[$k])) continue;
  2502. $r[$k] = $v;
  2503. }
  2504. }
  2505. }
  2506. return $r;
  2507. }
  2508. public static function get_edit_details($tab, $rid, $edit_id,$details=true) {
  2509. return self::get_edit_details_modify_record($tab, $rid, $edit_id,$details);
  2510. }
  2511. public static function get_edit_details_modify_record($tab, $rid, $edit_id, $details=true) {
  2512. self::init($tab);
  2513. if (is_numeric($rid)) {
  2514. $prev_rev = DB::GetOne('SELECT MIN(id) FROM '.$tab.'_edit_history WHERE '.$tab.'_id=%d AND id>%d', array($rid, $edit_id));
  2515. $r = self::get_record_revision($tab, $rid, $prev_rev);
  2516. } else $r = $rid;
  2517. $edit_info = DB::GetRow('SELECT * FROM '.$tab.'_edit_history WHERE id=%d',array($edit_id));
  2518. $event_display = array('what'=>'Error, Invalid event: '.$edit_id);
  2519. if (!$edit_info) return $event_display;
  2520. $event_display = array(
  2521. 'who'=>Base_UserCommon::get_user_label($edit_info['edited_by'], true),
  2522. 'when'=>Base_RegionalSettingsCommon::time2reg($edit_info['edited_on']),
  2523. 'what'=>array()
  2524. );
  2525. $edit_details = DB::GetAssoc('SELECT field, old_value FROM '.$tab.'_edit_history_data WHERE edit_id=%d',array($edit_id));
  2526. self::init($tab); // because get_user_label messes up
  2527. foreach ($r as $k=>$v) {
  2528. if (isset(self::$hash[$k]) && self::$table_rows[self::$hash[$k]]['type']=='multiselect')
  2529. $r[$k] = self::decode_multi($r[$k]); // We have to decode all fields, because access and some display relay on it, regardless which field changed
  2530. }
  2531. $r2 = $r;
  2532. foreach ($edit_details as $k=>$v) {
  2533. $k = self::get_field_id($k); // failsafe
  2534. if (!isset(self::$hash[$k])) continue;
  2535. if (self::$table_rows[self::$hash[$k]]['type']=='multiselect') {
  2536. $v = $edit_details[$k] = self::decode_multi($v);
  2537. }
  2538. $r2[$k] = $v;
  2539. }
  2540. $access = self::get_access($tab,'view',$r);
  2541. $modifications_to_show = 0;
  2542. foreach ($edit_details as $k=>$v) {
  2543. if($k=='id') {
  2544. $modifications_to_show += 1;
  2545. if (!$details) continue; // do not generate content when we dont want them
  2546. $event_display['what'] = _V($v);
  2547. continue;
  2548. }
  2549. $k = self::get_field_id($k); // failsafe
  2550. if (!isset(self::$hash[$k])) continue;
  2551. if (!$access[$k]) continue;
  2552. $modifications_to_show += 1;
  2553. if (!$details) continue; // do not generate content when we dont want them
  2554. self::init($tab);
  2555. $field = self::$hash[$k];
  2556. $desc = self::$table_rows[$field];
  2557. $event_display['what'][] = array(
  2558. _V($desc['name']),
  2559. self::get_val($tab, $field, $r2, true, $desc),
  2560. self::get_val($tab, $field, $r, true, $desc)
  2561. );
  2562. }
  2563. if ($modifications_to_show)
  2564. return $event_display;
  2565. return null;
  2566. }
  2567. public static function get_edit_details_label($tab, $rid, $edit_id,$details = true) {
  2568. $ret = self::watchdog_label($tab, '', $rid, array('E_'.$edit_id), '', $details);
  2569. return $ret['events'];
  2570. }
  2571. public static function watchdog_label($tab, $cat, $rid, $events = array(), $label = null, $details = true) {
  2572. $ret = array('category'=>$cat);
  2573. if ($rid!==null) {
  2574. $r = self::get_record($tab, $rid);
  2575. if ($r===null) return null;
  2576. if (!self::get_access($tab, 'view', $r)) return null;
  2577. if (is_array($label) && is_callable($label)) {
  2578. $label = self::create_linked_text(call_user_func($label, $r, true), $tab, $rid);
  2579. } elseif ($label) {
  2580. $label = self::create_linked_label_r($tab, $label, $r, false, true);
  2581. } else {
  2582. $label = self::create_default_linked_label($tab, $rid, false, false);
  2583. }
  2584. $ret['title'] = $label;
  2585. $ret['view_href'] = Utils_RecordBrowserCommon::create_record_href($tab, $rid);
  2586. $events_display = array();
  2587. $events = array_reverse($events);
  2588. $other_events = array();
  2589. $header = false;
  2590. foreach ($events as $v) {
  2591. if (count($events_display)>20) {
  2592. $other_events[__('And more...')] = 1;
  2593. break;
  2594. }
  2595. $param = explode('_', $v);
  2596. switch ($param[0]) {
  2597. case 'C': $what = 'Created';
  2598. $event_display = array(
  2599. 'who'=> Base_UserCommon::get_user_label($r['created_by'], true),
  2600. 'when'=>Base_RegionalSettingsCommon::time2reg($r['created_on']),
  2601. 'what'=>_V($what)
  2602. );
  2603. break;
  2604. case 'D': if (!isset($what)) $what = 'Deleted';
  2605. case 'R': if (!isset($what)) $what = 'Restored';
  2606. if(!isset($param[1])) {
  2607. $event_display = array(
  2608. 'who' => '',
  2609. 'when' => '',
  2610. 'what' => _V($what)
  2611. );
  2612. break;
  2613. }
  2614. case 'E': $event_display = self::get_edit_details_modify_record($tab, $r['id'], $param[1] ,$details);
  2615. if (isset($event_display['what']) && !empty($event_display['what'])) $header = true;
  2616. break;
  2617. case 'N': $event_display = false;
  2618. switch($param[1]) {
  2619. case '+':
  2620. $action = __('Note linked');
  2621. break;
  2622. case '-':
  2623. $action = __('Note unlinked');
  2624. break;
  2625. default:
  2626. if (!isset($other_events[$param[1]])) $other_events[$param[1]] = 0;
  2627. $other_events[$param[1]]++;
  2628. $event_display = null;
  2629. break;
  2630. }
  2631. if($event_display===false) {
  2632. $date = isset($param[3]) ? Base_RegionalSettingsCommon::time2reg($param[3]) : '';
  2633. $who = isset($param[4]) ? Base_UserCommon::get_user_label($param[4], true) : '';
  2634. $action .= ' - ' . self::create_default_linked_label('utils_attachment', $param[2]);
  2635. $event_display = array('what'=>$action,
  2636. 'who' => $who,
  2637. 'when' => $date);
  2638. }
  2639. break;
  2640. default: $event_display = array('what'=>_V($v));
  2641. }
  2642. if ($event_display) $events_display[] = $event_display;
  2643. }
  2644. foreach ($other_events as $k=>$v)
  2645. $events_display[] = array('what'=>_V($k).($v>1?' ['.$v.']':''));
  2646. if ($events_display) {
  2647. $theme = Base_ThemeCommon::init_smarty();
  2648. if ($header) {
  2649. $theme->assign('header', array(__('Field'), __('Old value'), __('New value')));
  2650. }
  2651. $theme->assign('events', $events_display);
  2652. $tpl = 'changes_list';
  2653. if (Utils_WatchdogCommon::email_mode()) {
  2654. $record_data = self::get_record_tooltip_data($tab, $rid);
  2655. $theme->assign('record', $record_data);
  2656. $tpl = 'changes_list_email';
  2657. }
  2658. ob_start();
  2659. Base_ThemeCommon::display_smarty($theme,'Utils_RecordBrowser', $tpl);
  2660. $output = ob_get_clean();
  2661. $ret['events'] = $output;
  2662. } else {
  2663. // if we've generated empty events for certain record, then
  2664. // it's possible that some of the fields, that have changed,
  2665. // are hidden so we have to check if there are any other events
  2666. // If all events are the same and output is empty we can safely
  2667. // mark all as notified.
  2668. $all_events = Utils_WatchdogCommon::check_if_notified($tab, $rid);
  2669. if (count($all_events) == count($events)) {
  2670. Utils_WatchdogCommon::notified($tab, $rid);
  2671. }
  2672. $ret = null;
  2673. }
  2674. }
  2675. return $ret;
  2676. }
  2677. public static function get_tables($tab){
  2678. return array( $tab.'_callback',
  2679. $tab.'_recent',
  2680. $tab.'_favorite',
  2681. $tab.'_edit_history_data',
  2682. $tab.'_edit_history',
  2683. $tab.'_field',
  2684. $tab.'_data_1');
  2685. }
  2686. public static function applet_new_record_button($tab, $defaults = array()) {
  2687. if (!self::get_access($tab, 'add')) return '';
  2688. return '<a '.Utils_TooltipCommon::open_tag_attrs(__('New record')).' '.Utils_RecordBrowserCommon::create_new_record_href($tab,$defaults).'><span class="card-options-collapse"><i class="fa fa-plus"></span></i></a>';
  2689. }
  2690. public static function get_calculated_id($tab, $field, $id) {
  2691. return $tab.'__'.$field.'___'.$id;
  2692. }
  2693. public static function check_for_jump() {
  2694. if (isset($_REQUEST['__jump_to_RB_table']) &&
  2695. isset($_REQUEST['__jump_to_RB_record'])) {
  2696. $tab = $_REQUEST['__jump_to_RB_table'];
  2697. $id = $_REQUEST['__jump_to_RB_record'];
  2698. $action = $_REQUEST['__jump_to_RB_action'];
  2699. if (!is_numeric($id)) return false;
  2700. Utils_RecordBrowserCommon::check_table_name($tab);
  2701. if (!self::get_access($tab,'browse')) return false;
  2702. if (!DB::GetOne('SELECT id FROM '.$tab.'_data_1 WHERE id=%d', $id)) return false;
  2703. unset($_REQUEST['__jump_to_RB_record']);
  2704. unset($_REQUEST['__jump_to_RB_table']);
  2705. unset($_REQUEST['__jump_to_RB_action']);
  2706. Base_BoxCommon::push_module(Utils_RecordBrowser::module_name(),'view_entry_with_REQUEST',array($action, $id, array(), true, $_REQUEST),array($tab));
  2707. return true;
  2708. }
  2709. return false;
  2710. }
  2711. public static function cut_string($str, $len, $tooltip=true, &$cut=null) {
  2712. return $str;
  2713. if ($len==-1) return $str;
  2714. $ret = '';
  2715. $strings = explode('<br>',$str);
  2716. foreach ($strings as $str) {
  2717. if ($ret) $ret .= '<br>';
  2718. $label = '';
  2719. $i = 0;
  2720. $curr_len = 0;
  2721. $tags = array();
  2722. $inside = 0;
  2723. preg_match_all('/./u', $str, $a);
  2724. $a = $a[0];
  2725. $strlen = count($a);
  2726. while ($curr_len<=$len && $i<$strlen) {
  2727. if ($a[$i] == '&' && !$inside) {
  2728. $e = -1;
  2729. if (isset($a[$i+3]) && $a[$i+3]==';') $e = 3;
  2730. elseif (isset($a[$i+4]) && $a[$i+4]==';') $e = 4;
  2731. elseif (isset($a[$i+5]) && $a[$i+5]==';') $e = 5;
  2732. if ($e!=-1) {
  2733. $hsc = implode("", array_slice($a, $i, $e+1));
  2734. if ($hsc=='&nbsp;' || strlen(htmlspecialchars_decode($hsc))==1) {
  2735. $label .= implode("", array_slice($a, $i, $e));
  2736. $i += $e;
  2737. $curr_len++;
  2738. }
  2739. }
  2740. } elseif ($a[$i] == '<' && !$inside) {
  2741. $inside = 1;
  2742. if (isset($a[$i+1]) && $a[$i+1] == '/') {
  2743. if (!empty($tags)) array_pop($tags);
  2744. } else {
  2745. $j = 1;
  2746. $next_tag = '';
  2747. while ($i+$j<=$strlen && $a[$i+$j]!=' ' && $a[$i+$j]!='>' && $a[$i+$j]!='/') {
  2748. $next_tag .= $a[$i+$j];
  2749. $j++;
  2750. }
  2751. $tags[] = $next_tag;
  2752. }
  2753. } elseif ($a[$i] == '"' && $inside==1) {
  2754. $inside = 2;
  2755. } elseif ($a[$i] == '"' && $inside==2) {
  2756. $inside = 1;
  2757. } elseif ($a[$i] == '>' && $inside==1) {
  2758. if ($i>0 && $a[$i-1] == '/') array_pop($tags);
  2759. $inside = 0;
  2760. } elseif (!$inside) {
  2761. $curr_len++;
  2762. }
  2763. $label .= $a[$i];
  2764. $i++;
  2765. }
  2766. if ($i<$strlen) {
  2767. $cut = true;
  2768. $label .= '...';
  2769. if ($tooltip) {
  2770. if (!strpos($str, 'Utils_Toltip__showTip(')) $label = '<span '.Utils_TooltipCommon::open_tag_attrs(strip_tags($str)).'>'.$label.'</span>';
  2771. else $label = preg_replace('/Utils_Toltip__showTip\(\'(.*?)\'/', 'Utils_Toltip__showTip(\''.escapeJS(htmlspecialchars($str)).'<hr>$1\'', $label);
  2772. }
  2773. }
  2774. while (!empty($tags)) $label .= '</'.array_pop($tags).'>';
  2775. $ret .= $label;
  2776. }
  2777. return $ret;
  2778. }
  2779. public static function build_cols_array($tab, $arg) {
  2780. self::init($tab);
  2781. $arg = array_flip($arg);
  2782. $ret = array();
  2783. foreach (self::$table_rows as $desc) {
  2784. if ($desc['visible'] && !isset($arg[$desc['id']])) $ret[$desc['id']] = false;
  2785. elseif (!$desc['visible'] && isset($arg[$desc['id']])) $ret[$desc['id']] = true;
  2786. }
  2787. return $ret;
  2788. }
  2789. public static function autoselect_label($tab_id, $args) {
  2790. // $args = array($tab, $tab_crits, $format_callback, $params);
  2791. $param = self::decode_select_param($args[3]);
  2792. $val = self::decode_record_token($tab_id, $param['single_tab']);
  2793. if (!$val) return '';
  2794. list($tab, $record_id) = $val;
  2795. if ($param['cols'])
  2796. return self::create_linked_label($tab, $param['cols'], $record_id, true);
  2797. else
  2798. return self::create_default_linked_label($tab, $record_id, true, false);
  2799. }
  2800. private static $automulti_order_tabs;
  2801. public static function automulti_order_by($a,$b) {
  2802. $aa = _V($a);
  2803. $bb = _V($b);
  2804. $aam = preg_match(self::$automulti_order_tabs,$aa) || preg_match(self::$automulti_order_tabs,$a);
  2805. $bbm = preg_match(self::$automulti_order_tabs,$bb) || preg_match(self::$automulti_order_tabs,$b);
  2806. if($aam && !$bbm)
  2807. return -1;
  2808. if($bbm && !$aam)
  2809. return 1;
  2810. return strcasecmp($aa,$bb);
  2811. }
  2812. public static function automulti_suggestbox($str, $tab, $tab_crits, $f_callback, $param) {
  2813. $param = self::decode_select_param($param);
  2814. $words = array_filter(explode(' ',$str));
  2815. $words_db = $words;
  2816. self::$automulti_order_tabs = array();
  2817. foreach($words_db as & $w) {
  2818. if(mb_strlen($w)>=3) self::$automulti_order_tabs[] = preg_quote($w,'/');
  2819. $w = "%$w%";
  2820. }
  2821. self::$automulti_order_tabs = '/('.implode('|',self::$automulti_order_tabs).')/i';
  2822. $tabs = $param['select_tabs'];
  2823. foreach($tabs as & $t) $t = DB::qstr($t);
  2824. $tabs = DB::GetAssoc('SELECT tab,caption FROM recordbrowser_table_properties WHERE tab IN ('.implode(',',$tabs).')');
  2825. $single_tab = $param['single_tab'];
  2826. uasort($tabs,array('Utils_RecordBrowserCommon','automulti_order_by'));
  2827. $ret = array();
  2828. //backward compatibility
  2829. if ($single_tab) {
  2830. if (is_array($tab_crits) && !isset($tab_crits[$single_tab])) $tab_crits = array($single_tab=>$tab_crits);
  2831. }
  2832. foreach($tabs as $t=>$caption) {
  2833. if(!empty($tab_crits) && !isset($tab_crits[$t])) continue;
  2834. $access_crits = self::get_access_crits($t, 'selection');
  2835. if ($access_crits===false) continue;
  2836. if ($access_crits!==true && (is_array($access_crits) || $access_crits instanceof Utils_RecordBrowser_CritsInterface)) {
  2837. if((is_array($tab_crits[$t]) && $tab_crits[$t]) || $tab_crits[$t] instanceof Utils_RecordBrowser_CritsInterface)
  2838. $tab_crits[$t] = self::merge_crits($tab_crits[$t], $access_crits);
  2839. else
  2840. $tab_crits[$t] = $access_crits;
  2841. }
  2842. $fields = $param['cols'];
  2843. if(!$fields) $fields = DB::GetCol("SELECT field FROM {$t}_field WHERE active=1 AND visible=1 AND (type NOT IN ('calculated','page_split','hidden') OR (type='calculated' AND param is not null AND param!=''))");
  2844. $words_db_tmp = $words_db;
  2845. $words_tmp = $words;
  2846. if (!$single_tab) {
  2847. foreach ($words_tmp as $pos => $word) {
  2848. $expr = '/' . preg_quote($word, '/') . '/i';
  2849. if (preg_match($expr, $caption) || preg_match($expr, _V($caption))) {
  2850. unset($words_db_tmp[$pos]);
  2851. unset($words_tmp[$pos]);
  2852. }
  2853. }
  2854. }
  2855. $str_db = '%' . implode(' ', $words_tmp) . '%';
  2856. $crits2A = array();
  2857. $crits2B = array();
  2858. $order = array();
  2859. foreach ($fields as $f) {
  2860. $field_id = self::get_field_id($f);
  2861. $crits2A = self::merge_crits($crits2A, array('~' . $field_id => $str_db), true);
  2862. $crits2B = self::merge_crits($crits2B, array('~' . $field_id => $words_db_tmp), true);
  2863. $order[$field_id] = 'ASC';
  2864. }
  2865. $crits3A = self::merge_crits(isset($tab_crits[$t])?$tab_crits[$t]:array(),$crits2A);
  2866. $crits3B = self::merge_crits(isset($tab_crits[$t])?$tab_crits[$t]:array(),$crits2B);
  2867. $records = self::get_records($t, $crits3A, array(), $order, 10);
  2868. foreach ($records as $r) {
  2869. if(!self::get_access($t,'view',$r)) continue;
  2870. $ret[($single_tab?'':$t.'/').$r['id']] = self::call_select_item_format_callback($f_callback, $t.'/'.$r['id'], array($tab, $crits3A, $f_callback, $param));
  2871. }
  2872. $records = self::get_records($t, $crits3B, array(), $order, 10);
  2873. foreach ($records as $r) {
  2874. if(isset($ret[($single_tab?'':$t.'/').$r['id']]) ||
  2875. !self::get_access($t,'view',$r)) continue;
  2876. $ret[($single_tab?'':$t.'/').$r['id']] = self::call_select_item_format_callback($f_callback, $t.'/'.$r['id'], array($tab, $crits3B, $f_callback, $param));
  2877. }
  2878. if(count($ret)>=10) break;
  2879. }
  2880. return $ret;
  2881. }
  2882. /**
  2883. * Function to manipulate clipboard pattern
  2884. * @param string $tab recordbrowser table name
  2885. * @param string|null $pattern pattern, or when it's null the pattern stays the same, only enable state changes
  2886. * @param bool $enabled new enabled state of clipboard pattern
  2887. * @param bool $force make it true to allow any changes or overwrite when clipboard pattern exist
  2888. * @return bool true if any changes were made, false otherwise
  2889. */
  2890. public static function set_clipboard_pattern($tab, $pattern, $enabled = true, $force = false) {
  2891. $ret = null;
  2892. $enabled = $enabled ? 1 : 0;
  2893. $r = self::get_clipboard_pattern($tab, true);
  2894. /* when pattern exists and i can overwrite it... */
  2895. if($r && $force) {
  2896. /* just change enabled state, when pattern is null */
  2897. if($pattern === null) {
  2898. $ret = DB::Execute('UPDATE recordbrowser_clipboard_pattern SET enabled=%d WHERE tab=%s',array($enabled,$tab));
  2899. } else {
  2900. /* delete if it's not necessary to hold any value */
  2901. if($enabled == 0 && strlen($pattern) == 0) $ret = DB::Execute('DELETE FROM recordbrowser_clipboard_pattern WHERE tab = %s', array($tab));
  2902. /* or update values */
  2903. else $ret = DB::Execute('UPDATE recordbrowser_clipboard_pattern SET pattern=%s,enabled=%d WHERE tab=%s',array($pattern,$enabled,$tab));
  2904. }
  2905. }
  2906. /* there is no such pattern in database so create it*/
  2907. if(!$r) {
  2908. $ret = DB::Execute('INSERT INTO recordbrowser_clipboard_pattern values (%s,%s,%d)',array($tab, $pattern, $enabled));
  2909. }
  2910. if($ret) return true;
  2911. return false;
  2912. }
  2913. /**
  2914. * Returns clipboard pattern string only if it is enabled. If 'with_state' is true return value is associative array with pattern and enabled keys.
  2915. * @param string $tab name of RecordBrowser table
  2916. * @param bool $with_state return also state of pattern
  2917. * @return string|array string by default, array when with_state=true
  2918. */
  2919. public static function get_clipboard_pattern($tab, $with_state = false) {
  2920. if($with_state) {
  2921. $ret = DB::GetArray('SELECT pattern,enabled FROM recordbrowser_clipboard_pattern WHERE tab=%s', array($tab));
  2922. if(sizeof($ret)) return $ret[0];
  2923. }
  2924. return DB::GetOne('SELECT pattern FROM recordbrowser_clipboard_pattern WHERE tab=%s AND enabled=1', array($tab));
  2925. }
  2926. public static function get_field_tooltip($label) {
  2927. if(strpos($label,'Utils_Tooltip')!==false) return $label;
  2928. $args = func_get_args();
  2929. array_shift($args);
  2930. return Utils_TooltipCommon::ajax_create($label, array('Utils_RecordBrowserCommon', 'ajax_get_field_tooltip'), $args);
  2931. }
  2932. public static function ajax_get_field_tooltip() {
  2933. $args = func_get_args();
  2934. $type = array_shift($args);
  2935. switch ($type) {
  2936. case 'autonumber':
  2937. case 'calculated': return __('This field is not editable');
  2938. case 'integer':
  2939. case 'float': return __('Enter a numeric value in the text field');
  2940. case 'checkbox': return __('Click to switch between checked/unchecked state');
  2941. case 'currency': return __('Enter the amount in text field and select currency');
  2942. case 'text': $ret = __('Enter the text in the text field');
  2943. if (isset($args[0]) && is_numeric($args[0])) $ret .= '<br />'.__('Maximum allowed length is %s characters', array('<b>'.$args[0].'</b>'));
  2944. return $ret;
  2945. case 'long text': $example_text = __('Example text');
  2946. return __('Enter the text in the text area').'<br />'.__('Maximum allowed length is %s characters', array('<b>400</b>')).'<br/>'.'<br/>'.
  2947. __('BBCodes are supported:').'<br/>'.
  2948. '[b]'.$example_text.'[/b] - <b>'.$example_text.'</b>'.'<br/>'.
  2949. '[u]'.$example_text.'[/u] - <u>'.$example_text.'</u>'.'<br/>'.
  2950. '[i]'.$example_text.'[/i] - <i>'.$example_text.'</i>';
  2951. case 'date': return __('Enter the date in your selected format').'<br />'.__('Click on the text field to bring up a popup Calendar that allows you to pick the date').'<br />'.__('Click again on the text field to close popup Calendar');
  2952. case 'timestamp': return __('Enter the date in your selected format and the time using select elements').'<br />'.__('Click on the text field to bring up a popup Calendar that allows you to pick the date').'<br />'.__('Click again on the text field to close popup Calendar').'<br />'.__('You can change 12/24-hour format in Control Panel, Regional Settings');
  2953. case 'time': return __('Enter the time using select elements').'<br />'.__('You can change 12/24-hour format in Control Panel, Regional Settings');
  2954. case 'commondata': $ret = __('Select value');
  2955. if (isset($args[0])) $ret .= ' '.__('from %s table', array('<b>'.str_replace('_', '/', $args[0]).'</b>'));
  2956. return $ret;
  2957. case 'select': $ret = __('Select one');
  2958. if (isset($args[0])) {
  2959. if (is_array($args[0])) {
  2960. $cap = array();
  2961. foreach ($args[0] as $t) $cap[] = '<b>'.self::get_caption($t).'</b>';
  2962. $cap = implode(' '.__('or').' ',$cap);
  2963. } else $cap = '<b>'.self::get_caption($args[0]).'</b>';
  2964. $ret .= ' '.__('of').' '.$cap;
  2965. }
  2966. if (isset($args[1])) {
  2967. $val = self::crits_to_words($args[0], $args[1]);
  2968. if ($val) $ret .= ' '.__('for which').'<br />&nbsp;&nbsp;&nbsp;'.$val;
  2969. }
  2970. return $ret;
  2971. case 'multiselect': $ret = __('Select multiple');
  2972. if (isset($args[0])) {
  2973. if (is_array($args[0])) {
  2974. $cap = array();
  2975. foreach ($args[0] as $t) $cap[] = '<b>'.self::get_caption($t).'</b>';
  2976. $cap = implode(' '.__('or').' ',$cap);
  2977. } else $cap = '<b>'.self::get_caption($args[0]).'</b>';
  2978. $ret .= ' '.$cap;
  2979. }
  2980. if (isset($args[1])) {
  2981. $val = self::crits_to_words($args[0], $args[1]);
  2982. if ($val) $ret .= ' '.__('for which').'<br />&nbsp;&nbsp;&nbsp;'.$val;
  2983. }
  2984. return $ret;
  2985. }
  2986. return __('No additional information');
  2987. }
  2988. public static $date_values = array('-1 year'=>'1 year back','-6 months'=>'6 months back','-3 months'=>'3 months back','-2 months'=>'2 months back','-1 month'=>'1 month back','-2 weeks'=>'2 weeks back','-1 week'=>'1 week back','-6 days'=>'6 days back','-5 days'=>'5 days back','-4 days'=>'4 days back','-3 days'=>'3 days back','-2 days'=>'2 days back','-1 days'=>'1 days back','today'=>'current day','+1 days'=>'1 days forward','+2 days'=>'2 days forward','+3 days'=>'3 days forward','+4 days'=>'4 days forward','+5 days'=>'5 days forward','+6 days'=>'6 days forward','+1 week'=>'1 week forward','+2 weeks'=>'2 weeks forward','+1 month'=>'1 month forward','+2 months'=>'2 months forward','+3 months'=>'3 months forward','+6 months'=>'6 months forward','+1 year'=>'1 year forward');
  2989. public static function crits_to_words($tab, $crits, $html_decoration=true) {
  2990. if (!is_object($crits)) {
  2991. $crits = Utils_RecordBrowser_Crits::from_array($crits);
  2992. }
  2993. $crits = $crits->replace_special_values(true);
  2994. $c2w = new Utils_RecordBrowser_CritsToWords($tab);
  2995. $c2w->enable_html_decoration($html_decoration);
  2996. return $c2w->to_words($crits);
  2997. }
  2998. public static function get_printer($tab)
  2999. {
  3000. $class = DB::GetOne('SELECT printer FROM recordbrowser_table_properties WHERE tab=%s',$tab);
  3001. if($class && class_exists($class))
  3002. return new $class();
  3003. return new Utils_RecordBrowser_RecordPrinter();
  3004. }
  3005. ////////////////////////////
  3006. // default QFfield callbacks
  3007. public static function get_default_QFfield_callback($type) {
  3008. $types = array('hidden', 'checkbox', 'calculated', 'integer', 'float',
  3009. 'currency', 'text', 'long text', 'date', 'timestamp', 'time',
  3010. 'commondata', 'select', 'multiselect', 'autonumber', 'file');
  3011. if (array_search($type, $types) !== false) {
  3012. return __CLASS__. '::QFfield_' . self::get_field_id($type);
  3013. }
  3014. return null;
  3015. }
  3016. public static function QFfield_static_display(&$form, $field, $label, $mode, $default, $desc, $rb_obj) {
  3017. if ($mode !== 'add' && $mode !== 'edit') {
  3018. if ($desc['type'] != 'checkbox' || isset($rb_obj->display_callback_table[$field])) {
  3019. $def = self::get_val($rb_obj->tab, $field, $rb_obj->record, false, $desc);
  3020. $form->addElement('static', $field, $label, $def, array('id' => $field));
  3021. return true;
  3022. }
  3023. }
  3024. return false;
  3025. }
  3026. public static function QFfield_hidden(&$form, $field, $label, $mode, $default, $desc, $rb_obj) {
  3027. $form->addElement('hidden', $field);
  3028. $form->setDefaults(array($field => $default));
  3029. }
  3030. public static function QFfield_checkbox(&$form, $field, $label, $mode, $default, $desc, $rb_obj) {
  3031. $label = Utils_RecordBrowserCommon::get_field_tooltip($label, $desc['type']);
  3032. $el = $form->addElement('advcheckbox', $field, $label, '', array('id' => $field));
  3033. $el->setValues(array('0','1'));
  3034. if ($mode !== 'add')
  3035. $form->setDefaults(array($field => $default));
  3036. }
  3037. public static function QFfield_calculated(&$form, $field, $label, $mode, $default, $desc, $rb_obj) {
  3038. if (self::QFfield_static_display($form, $field, $label, $mode, $default, $desc, $rb_obj))
  3039. return;
  3040. $label = Utils_RecordBrowserCommon::get_field_tooltip($label, $desc['type']);
  3041. $form->addElement('static', $field, $label);
  3042. if (!is_array($rb_obj->record))
  3043. $values = $rb_obj->custom_defaults;
  3044. else {
  3045. $values = $rb_obj->record;
  3046. if (is_array($rb_obj->custom_defaults))
  3047. $values = $values + $rb_obj->custom_defaults;
  3048. }
  3049. $val = isset($values[$desc['id']]) ?
  3050. self::get_val($rb_obj->tab, $field, $values, true, $desc)
  3051. : '';
  3052. if (!$val)
  3053. $val = '[' . __('formula') . ']';
  3054. $record_id = isset($rb_obj->record['id']) ? $rb_obj->record['id'] : null;
  3055. $form->setDefaults(array($field => '<div class="static_field" id="' . Utils_RecordBrowserCommon::get_calculated_id($rb_obj->tab, $field, $record_id) . '">' . $val . '</div>'));
  3056. }
  3057. public static function QFfield_integer(&$form, $field, $label, $mode, $default, $desc, $rb_obj) {
  3058. if (self::QFfield_static_display($form, $field, $label, $mode, $default, $desc, $rb_obj))
  3059. return;
  3060. $label = Utils_RecordBrowserCommon::get_field_tooltip($label, $desc['type']);
  3061. $form->addElement('text', $field, $label, array('id' => $field));
  3062. $form->addRule($field, __('Only integer numbers are allowed.'), 'regex', '/^\-?[0-9]*$/');
  3063. if ($mode !== 'add')
  3064. $form->setDefaults(array($field => $default));
  3065. }
  3066. public static function QFfield_float(&$form, $field, $label, $mode, $default, $desc, $rb_obj) {
  3067. if (self::QFfield_static_display($form, $field, $label, $mode, $default, $desc, $rb_obj))
  3068. return;
  3069. $label = Utils_RecordBrowserCommon::get_field_tooltip($label, $desc['type']);
  3070. $form->addElement('text', $field, $label, array('id' => $field));
  3071. $form->addRule($field, __('Only numbers are allowed.'), 'numeric');
  3072. if ($mode !== 'add')
  3073. $form->setDefaults(array($field => $default));
  3074. }
  3075. public static function QFfield_currency(&$form, $field, $label, $mode, $default, $desc, $rb_obj) {
  3076. if (self::QFfield_static_display($form, $field, $label, $mode, $default, $desc, $rb_obj))
  3077. return;
  3078. $label = Utils_RecordBrowserCommon::get_field_tooltip($label, $desc['type']);
  3079. $form->addElement('currency', $field, $label, (isset($desc['param']) && is_array($desc['param']))?$desc['param']:array(), array('id' => $field));
  3080. if ($mode !== 'add')
  3081. $form->setDefaults(array($field => $default));
  3082. // set element value to persist currency over soft submit
  3083. if ($form->isSubmitted() && $form->exportValue('submited') == false) {
  3084. $default = $form->exportValue($field);
  3085. $form->getElement($field)->setValue($default);
  3086. }
  3087. }
  3088. public static function QFfield_text(&$form, $field, $label, $mode, $default, $desc, $rb_obj) {
  3089. if (self::QFfield_static_display($form, $field, $label, $mode, $default, $desc, $rb_obj))
  3090. return;
  3091. $label = Utils_RecordBrowserCommon::get_field_tooltip($label, $desc['type'], $desc['param']);
  3092. $form->addElement('text', $field, $label, array('id' => $field, 'maxlength' => $desc['param']));
  3093. $form->addRule($field, __('Maximum length for this field is %s characters.', array($desc['param'])), 'maxlength', $desc['param']);
  3094. if ($mode !== 'add')
  3095. $form->setDefaults(array($field => $default));
  3096. }
  3097. public static function QFfield_long_text(&$form, $field, $label, $mode, $default, $desc, $rb_obj) {
  3098. if (self::QFfield_static_display($form, $field, $label, $mode, $default, $desc, $rb_obj))
  3099. return;
  3100. $label = Utils_RecordBrowserCommon::get_field_tooltip($label, $desc['type']);
  3101. $form->addElement('textarea', $field, $label, array('id' => $field));
  3102. if ($mode !== 'add')
  3103. $form->setDefaults(array($field => $default));
  3104. }
  3105. public static function QFfield_date(&$form, $field, $label, $mode, $default, $desc, $rb_obj) {
  3106. if (self::QFfield_static_display($form, $field, $label, $mode, $default, $desc, $rb_obj))
  3107. return;
  3108. $label = Utils_RecordBrowserCommon::get_field_tooltip($label, $desc['type']);
  3109. $form->addElement('datepicker', $field, $label, array('id' => $field));
  3110. if ($mode !== 'add')
  3111. $form->setDefaults(array($field => $default));
  3112. }
  3113. public static function timestamp_required($v) {
  3114. return $v !== '' && Base_RegionalSettingsCommon::reg2time($v, false) !== false;
  3115. }
  3116. public static function QFfield_timestamp(&$form, $field, $label, $mode, $default, $desc, $rb_obj) {
  3117. if (self::QFfield_static_display($form, $field, $label, $mode, $default, $desc, $rb_obj))
  3118. return;
  3119. $f_param = array('id' => $field);
  3120. if ($desc['param'])
  3121. $f_param['optionIncrement'] = array('i' => $desc['param']);
  3122. $label = Utils_RecordBrowserCommon::get_field_tooltip($label, $desc['type']);
  3123. $form->addElement('timestamp', $field, $label, $f_param);
  3124. static $rule_defined = false;
  3125. if (!$rule_defined) {
  3126. $form->registerRule('timestamp_required', 'callback', 'timestamp_required', __CLASS__);
  3127. $rule_defined = true;
  3128. }
  3129. if (isset($desc['required']) && $desc['required'])
  3130. $form->addRule($field, __('Field required'), 'timestamp_required');
  3131. if ($mode !== 'add' && $default)
  3132. $form->setDefaults(array($field => $default));
  3133. }
  3134. public static function QFfield_time(&$form, $field, $label, $mode, $default, $desc, $rb_obj) {
  3135. if (self::QFfield_static_display($form, $field, $label, $mode, $default, $desc, $rb_obj))
  3136. return;
  3137. $time_format = Base_RegionalSettingsCommon::time_12h() ? 'h:i a' : 'H:i';
  3138. $lang_code = Base_LangCommon::get_lang_code();
  3139. $label = Utils_RecordBrowserCommon::get_field_tooltip($label, $desc['type']);
  3140. $minute_increment = 5;
  3141. if ($desc['param']) {
  3142. $minute_increment = $desc['param'];
  3143. }
  3144. $form->addElement('timestamp', $field, $label, array('date' => false, 'format' => $time_format, 'optionIncrement' => array('i' => $minute_increment), 'language' => $lang_code, 'id' => $field));
  3145. if ($mode !== 'add' && $default)
  3146. $form->setDefaults(array($field => $default));
  3147. }
  3148. public static function QFfield_commondata(&$form, $field, $label, $mode, $default, $desc, $rb_obj) {
  3149. if (self::QFfield_static_display($form, $field, $label, $mode, $default, $desc, $rb_obj))
  3150. return;
  3151. $param = explode('::', $desc['param']['array_id']);
  3152. foreach ($param as $k => $v)
  3153. if ($k != 0)
  3154. $param[$k] = self::get_field_id($v);
  3155. $label = Utils_RecordBrowserCommon::get_field_tooltip($label, $desc['type'], $desc['param']['array_id']);
  3156. $form->addElement($desc['type'], $field, $label, $param, array('empty_option' => true, 'order' => $desc['param']['order']), array('id' => $field));
  3157. if ($mode !== 'add')
  3158. $form->setDefaults(array($field => $default));
  3159. }
  3160. public static function QFfield_select(&$form, $field, $label, $mode, $default, $desc, $rb_obj) {
  3161. if (self::QFfield_static_display($form, $field, $label, $mode, $default, $desc, $rb_obj))
  3162. return;
  3163. $record = $rb_obj->record;
  3164. $comp = array();
  3165. $param = self::decode_select_param($desc['param']);
  3166. $multi_adv_params = self::call_select_adv_params_callback($param['adv_params_callback'], $record);
  3167. $format_callback = $multi_adv_params['format_callback'];
  3168. $rec_count = 0;
  3169. if ($param['single_tab'] == '__COMMON__') {
  3170. if (empty($param['array_id']))
  3171. trigger_error("Commondata array id not set for field: $field", E_USER_ERROR);
  3172. $data = Utils_CommonDataCommon::get_translated_tree($param['array_id'], $param['order']);
  3173. if (!is_array($data))
  3174. $data = array();
  3175. $comp = $comp + $data;
  3176. $label = Utils_RecordBrowserCommon::get_field_tooltip($label, 'commondata', $param['array_id']);
  3177. } else {
  3178. $tab_crits = self::get_select_tab_crits($param, $record);
  3179. $tabs = array_keys($tab_crits);
  3180. foreach($tabs as $t) {
  3181. $rec_count += Utils_RecordBrowserCommon::get_records_count($t, $tab_crits[$t]);
  3182. if ($rec_count > Utils_RecordBrowserCommon::$options_limit) break;
  3183. }
  3184. if ($rec_count <= Utils_RecordBrowserCommon::$options_limit) {
  3185. foreach($tabs as $t) {
  3186. $records = Utils_RecordBrowserCommon::get_records($t, $tab_crits[$t], array(), $multi_adv_params['order']);
  3187. foreach($records as $key=>$rec) {
  3188. if(!self::get_access($t,'view',$rec)) continue;
  3189. $tab_id = ($param['single_tab']?'':$t.'/').$key;
  3190. $comp[$tab_id] = self::call_select_item_format_callback($multi_adv_params['format_callback'], $tab_id, array($rb_obj->tab, $tab_crits[$t], $multi_adv_params['format_callback'], $param));
  3191. }
  3192. }
  3193. }
  3194. if (isset($record[$field])) {
  3195. if (!is_array($record[$field])) {
  3196. if ($record[$field] != '')
  3197. $record[$field] = array($record[$field] => $record[$field]);
  3198. else
  3199. $record[$field] = array();
  3200. }
  3201. }
  3202. if ($default) {
  3203. if (!is_array($default))
  3204. $record[$field][$default] = $default;
  3205. else {
  3206. foreach ($default as $v)
  3207. $record[$field][$v] = $v;
  3208. }
  3209. }
  3210. if (isset($record[$field])) {
  3211. foreach ($record[$field] as $tab_id) {
  3212. if (isset($comp[$tab_id])) continue;
  3213. $vals = self::decode_record_token($tab_id, $param['single_tab']);
  3214. if (!$vals) continue;
  3215. list($t,$rid) = $vals;
  3216. if (!isset($tab_crits[$t])) continue;
  3217. $comp[$tab_id] = self::call_select_item_format_callback($multi_adv_params['format_callback'], $tab_id, array($rb_obj->tab, $tab_crits[$t], $multi_adv_params['format_callback'], $param));
  3218. }
  3219. }
  3220. if (empty($multi_adv_params['order']))
  3221. natcasesort($comp);
  3222. if($param['single_tab'])
  3223. $label = self::get_field_tooltip($label, $desc['type'], $param['single_tab'], $tab_crits[$param['single_tab']]);
  3224. }
  3225. if ($rec_count > Utils_RecordBrowserCommon::$options_limit) {
  3226. if ($desc['type'] == 'multiselect') {
  3227. $el = $form->addElement('automulti', $field, $label, array('Utils_RecordBrowserCommon', 'automulti_suggestbox'), array($rb_obj->tab, $tab_crits, $format_callback, $desc['param']), $format_callback);
  3228. if (method_exists($rb_obj, 'init_module')) { // fixes mobile edit issue - to be removed when mobile.php will be removed
  3229. ${'rp_' . $field} = $rb_obj->init_module(Utils_RecordBrowser_RecordPicker::module_name(), array());
  3230. $filters_defaults = isset($multi_adv_params['filters_defaults']) ? $multi_adv_params['filters_defaults'] : array();
  3231. $rb_obj->display_module(${'rp_' . $field}, array($tabs, $field, $format_callback, $param['crits_callback']?:$tab_crits, array(), array(), array(), $filters_defaults));
  3232. $el->set_search_button('<a ' . ${'rp_' . $field}->create_open_href() . ' ' . Utils_TooltipCommon::open_tag_attrs(__('Advanced Selection')) . ' href="javascript:void(0);"><img border="0" src="' . Base_ThemeCommon::get_template_file('Utils_RecordBrowser', 'icon_zoom.png') . '"></a>');
  3233. }
  3234. } else
  3235. $el = $form->addElement('autoselect', $field, $label, $comp, array(array('Utils_RecordBrowserCommon', 'automulti_suggestbox'), array($rb_obj->tab, $tab_crits, $format_callback, $desc['param'])), $format_callback);
  3236. } else {
  3237. if ($desc['type'] === 'select')
  3238. $comp = array('' => '---') + $comp;
  3239. $form->addElement($desc['type'], $field, $label, $comp, array('id' => $field));
  3240. }
  3241. if ($mode !== 'add')
  3242. $form->setDefaults(array($field => $default));
  3243. }
  3244. public static function QFfield_multiselect(&$form, $field, $label, $mode, $default, $desc, $rb_obj) {
  3245. self::QFfield_select($form, $field, $label, $mode, $default, $desc, $rb_obj);
  3246. }
  3247. public static function QFfield_autonumber(&$form, $field, $label, $mode, $default, $desc, $rb_obj) {
  3248. if (self::QFfield_static_display($form, $field, $label, $mode, $default, $desc, $rb_obj))
  3249. return;
  3250. $label = Utils_RecordBrowserCommon::get_field_tooltip($label, $desc['type']);
  3251. $value = $default ? $default : self::format_autonumber_str($desc['param'], null);
  3252. $form->addElement('static', $field, $label);
  3253. $record_id = isset($rb_obj->record['id']) ? $rb_obj->record['id'] : null;
  3254. $field_id = Utils_RecordBrowserCommon::get_calculated_id($rb_obj->tab, $field, $record_id);
  3255. $val = '<div class="static_field" id="' . $field_id . '">' . $value . '</div>';
  3256. $form->setDefaults(array($field => $val));
  3257. }
  3258. //region File
  3259. public static function display_file($r, $nolink=false, $desc=null, $tab=null)
  3260. {
  3261. $labels = [];
  3262. $inline_nodes = [];
  3263. $fileStorageIds = self::decode_multi($r[$desc['id']]);
  3264. $fileHandler = new Utils_RecordBrowser_FileActionHandler();
  3265. foreach($fileStorageIds as $fileStorageId) {
  3266. if(!empty($fileStorageId)) {
  3267. $actions = $fileHandler->getActionUrlsRB($fileStorageId, $tab, $r['id'], $desc['id']);
  3268. $labels[]= Utils_FileStorageCommon::get_file_label($fileStorageId, $nolink, true, $actions);
  3269. $inline_nodes[]= Utils_FileStorageCommon::get_file_inline_node($fileStorageId, $actions);
  3270. }
  3271. }
  3272. $inline_nodes = array_filter($inline_nodes);
  3273. return implode('<br>', $labels) . ($inline_nodes? '<hr>': '') . implode('<hr>', $inline_nodes);
  3274. }
  3275. public static function QFfield_file(&$form, $field, $label, $mode, $default, $desc, $rb_obj)
  3276. {
  3277. if (self::QFfield_static_display($form, $field, $label, $mode, $default, $desc, $rb_obj))
  3278. return;
  3279. $record_id = isset($rb_obj->record['id']) ? $rb_obj->record['id'] : 'new';
  3280. $module_id = md5($rb_obj->tab . '/' . $record_id . '/' . $field);
  3281. /** @var Utils_FileUpload_Dropzone $dropzoneField */
  3282. $dropzoneField = Utils_RecordBrowser::$rb_obj->init_module('Utils_FileUpload#Dropzone', null, $module_id);
  3283. $default = self::decode_multi($default);
  3284. if ($default) {
  3285. $files = [];
  3286. foreach ($default as $filestorageId) {
  3287. $meta = Utils_FileStorageCommon::meta($filestorageId);
  3288. $arr = [
  3289. 'filename' => $meta['filename'],
  3290. 'type' => $meta['type'],
  3291. 'size' => $meta['size'],
  3292. ];
  3293. $backref = substr($meta['backref'], 0, 3) == 'rb:' ? explode('/', substr($meta['backref'], 3)) : [];
  3294. if (count($backref) === 3) {
  3295. list ($br_tab, $br_record, $br_field) = $backref;
  3296. $file_handler = new Utils_RecordBrowser_FileActionHandler();
  3297. $actions = $file_handler->getActionUrlsRB($filestorageId, $br_tab, $br_record, $br_field);
  3298. if (isset($actions['preview'])) {
  3299. $arr['file'] = $actions['preview'];
  3300. }
  3301. }
  3302. $files[$filestorageId] = $arr;
  3303. }
  3304. $dropzoneField->set_defaults($files);
  3305. }
  3306. if (isset($desc['param']['max_files']) && $desc['param']['max_files'] !== false) {
  3307. $dropzoneField->set_max_files($desc['param']['max_files']);
  3308. }
  3309. if (isset($desc['param']['accepted_files']) && $desc['param']['accepted_files'] !== false) {
  3310. $dropzoneField->set_accepted_files($desc['param']['accepted_files']);
  3311. }
  3312. $dropzoneField->add_to_form($form, $field, $label);
  3313. }
  3314. //endregion
  3315. public static function cron() {
  3316. return array('indexer' => 10);
  3317. }
  3318. public static function index_record($tab,$record,$table_rows=null,$tab_id=null) {
  3319. if($tab_id===null) $tab_id = DB::GetOne('SELECT id FROM recordbrowser_table_properties WHERE tab=%s',array($tab));
  3320. if($table_rows===null) $table_rows = self::init($tab);
  3321. $record = self::record_processing($tab, $record, 'index');
  3322. DB::StartTrans();
  3323. if($record) {
  3324. DB::Execute('DELETE FROM recordbrowser_search_index WHERE tab_id=%d AND record_id=%d',array($tab_id,$record['id']));
  3325. $cleanup_str = function($value) {
  3326. $decoded = html_entity_decode($value);
  3327. $added_spaces = str_replace('<', ' <', $decoded);
  3328. $stripped = strip_tags($added_spaces);
  3329. $removed_spaces = preg_replace('/[ ]+/', ' ', $stripped);
  3330. return mb_strtolower(trim($removed_spaces));
  3331. };
  3332. $insert_vals = array();
  3333. foreach($table_rows as $field_info) {
  3334. $field = $field_info['id'];
  3335. if(!isset($record[$field])) continue;
  3336. ob_start();
  3337. $text = self::get_val($tab,$field,$record, true);
  3338. ob_end_clean();
  3339. $text = $cleanup_str($text);
  3340. if ($text) {
  3341. $insert_vals[] = $tab_id;
  3342. $insert_vals[] = $record['id'];
  3343. $insert_vals[] = $field_info['pkey'];
  3344. $insert_vals[] = $text;
  3345. }
  3346. }
  3347. $insert_query = implode(',', array_fill(0, count($insert_vals) / 4, '(%d, %d, %d, %s)'));
  3348. DB::Execute('INSERT INTO recordbrowser_search_index VALUES ' . $insert_query, $insert_vals);
  3349. }
  3350. DB::Execute('UPDATE '.$tab.'_data_1 SET indexed=1 WHERE id=%d',array($record['id']));
  3351. DB::CompleteTrans();
  3352. }
  3353. public static function clear_search_index($tab)
  3354. {
  3355. $tab_id = DB::GetOne('SELECT id FROM recordbrowser_table_properties WHERE tab=%s',array($tab));
  3356. if ($tab_id) {
  3357. DB::Execute('DELETE FROM recordbrowser_search_index WHERE tab_id=%d',array($tab_id));
  3358. DB::Execute('UPDATE ' . $tab . '_data_1 SET indexed=0');
  3359. return true;
  3360. }
  3361. return false;
  3362. }
  3363. public static function indexer($limit=null,&$total=0) {
  3364. $limit_sum = 0;
  3365. $limit_file = DATA_DIR.'/Utils_RecordBrowser/limit';
  3366. if(defined('RB_INDEXER_LIMIT_QUERIES')) { //limit queries per hour
  3367. $time = time();
  3368. $limit_time = 0;
  3369. if(file_exists($limit_file)) {
  3370. $tmp = array_filter(explode("\n",file_get_contents($limit_file)));
  3371. $limit_time = array_shift($tmp);
  3372. if($limit_time>$time-3600) {
  3373. $limit_sum = array_sum($tmp);
  3374. if($limit_sum>RB_INDEXER_LIMIT_QUERIES) return;
  3375. }
  3376. }
  3377. if($limit_sum==0)
  3378. file_put_contents($limit_file,$time."\n", LOCK_EX);
  3379. }
  3380. if(!$limit) $limit = defined('RB_INDEXER_LIMIT_RECORDS') ? RB_INDEXER_LIMIT_RECORDS : 300;
  3381. $tabs = DB::GetAssoc('SELECT id,tab FROM recordbrowser_table_properties WHERE search_include>0');
  3382. foreach($tabs as $tab_id=>$tab) {
  3383. $lock = DATA_DIR.'/Utils_RecordBrowser/'.$tab_id.'.lock';
  3384. if(file_exists($lock) && filemtime($lock)>time()-1200) continue;
  3385. $table_rows = self::init($tab);
  3386. self::$admin_filter = ' <tab>.indexed=0 AND <tab>.active=1 AND ';
  3387. $ret = self::get_records($tab,array(),array(),array(),$limit,true);
  3388. self::$admin_filter = '';
  3389. if(!$ret) continue;
  3390. register_shutdown_function(create_function('','@unlink("'.$lock.'");'));
  3391. if(file_exists($lock) && filemtime($lock)>time()-1200) continue;
  3392. file_put_contents($lock,'');
  3393. foreach($ret as $row) {
  3394. self::index_record($tab,$row,$table_rows,$tab_id);
  3395. $total++;
  3396. if($total>=$limit) break;
  3397. if(defined('RB_INDEXER_LIMIT_QUERIES') && RB_INDEXER_LIMIT_QUERIES<$limit_sum+DB::GetQueriesQty()) break;
  3398. }
  3399. @unlink($lock);
  3400. if($total>=$limit) break;
  3401. if(defined('RB_INDEXER_LIMIT_QUERIES') && RB_INDEXER_LIMIT_QUERIES<$limit_sum+DB::GetQueriesQty()) break;
  3402. }
  3403. if(defined('RB_INDEXER_LIMIT_QUERIES')) {
  3404. file_put_contents($limit_file,DB::GetQueriesQty()."\n",FILE_APPEND | LOCK_EX);
  3405. }
  3406. }
  3407. public static function search($search, $categories)
  3408. {
  3409. $x = new Utils_RecordBrowser_Search($categories);
  3410. $ret = $x->search_results($search);
  3411. return $ret;
  3412. }
  3413. public static function search_categories() {
  3414. $tabs = DB::GetAssoc('SELECT t.id,t.tab,t.search_include FROM recordbrowser_table_properties t WHERE t.search_include>0 AND t.id IN (SELECT DISTINCT m.tab_id FROM recordbrowser_search_index m)');
  3415. $ret = array();
  3416. foreach($tabs as $tab_id=>$tab) {
  3417. $caption = self::get_caption($tab['tab']);
  3418. if(!$caption) continue;
  3419. $ret[$tab_id] = array('caption'=>$caption,'checked'=>$tab['search_include']==1);
  3420. }
  3421. uasort($ret,create_function('$a,$b','return strnatcasecmp($a["caption"],$b["caption"]);'));
  3422. return $ret;
  3423. }
  3424. }
  3425. function rb_or($crits, $_ = null)
  3426. {
  3427. $args = func_get_args();
  3428. if (count($args) > 1) {
  3429. foreach ($args as $k => $v) {
  3430. if (is_array($v)) {
  3431. $args[$k] = new Utils_RecordBrowser_Crits($v, true);
  3432. }
  3433. }
  3434. $crits = $args;
  3435. } else {
  3436. $crits = $args[0];
  3437. }
  3438. $ret = new Utils_RecordBrowser_Crits($crits, true);
  3439. return $ret;
  3440. }
  3441. function rb_and($crits, $_ = null)
  3442. {
  3443. $args = func_get_args();
  3444. if (count($args) > 1) {
  3445. foreach ($args as $k => $v) {
  3446. if (is_array($v)) {
  3447. $args[$k] = new Utils_RecordBrowser_Crits($v);
  3448. }
  3449. }
  3450. $crits = $args;
  3451. } else {
  3452. $crits = $args[0];
  3453. }
  3454. $ret = new Utils_RecordBrowser_Crits($crits);
  3455. return $ret;
  3456. }
  3457. require_once 'modules/Utils/RecordBrowser/object_wrapper/include.php';
  3458. Utils_RecordBrowser_Crits::register_special_value_callback(array('Utils_RecordBrowserCommon', 'crits_special_values'));
  3459. if(!READ_ONLY_SESSION) {
  3460. if(!isset($_SESSION['rb_indexer_token']))
  3461. $_SESSION['rb_indexer_token'] = md5(microtime(true));
  3462. load_js('modules/Utils/RecordBrowser/indexer.js');
  3463. eval_js_once('rb_indexer("'.$_SESSION['rb_indexer_token'].'")');
  3464. }
  3465. ?>