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.
 
 
 
 
 
 

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