PageRenderTime 65ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/packages/blocks/blocks.php

https://bitbucket.org/navigatecms/navigatecms
PHP | 3299 lines | 2911 code | 358 blank | 30 comment | 174 complexity | f7ce073a2e8adaef57443d06b6789311 MD5 | raw file
Possible License(s): GPL-2.0, MIT, LGPL-2.1, BSD-3-Clause, AGPL-3.0, Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. require_once(NAVIGATE_PATH.'/lib/packages/blocks/block.class.php');
  3. require_once(NAVIGATE_PATH.'/lib/packages/blocks/block_group.class.php');
  4. require_once(NAVIGATE_PATH.'/lib/packages/properties/property.class.php');
  5. require_once(NAVIGATE_PATH.'/lib/packages/properties/property.layout.php');
  6. require_once(NAVIGATE_PATH.'/lib/packages/extensions/extension.class.php');
  7. require_once(NAVIGATE_PATH.'/lib/packages/webusers/webuser_group.class.php');
  8. function run()
  9. {
  10. global $layout;
  11. global $DB;
  12. global $website;
  13. $out = '';
  14. $item = new block();
  15. switch($_REQUEST['act'])
  16. {
  17. case 'json':
  18. case 1: // json data retrieval & operations
  19. switch($_REQUEST['oper'])
  20. {
  21. case 'del': // remove rows
  22. $ids = $_REQUEST['ids'];
  23. foreach($ids as $id)
  24. {
  25. $item->load($id);
  26. $item->delete();
  27. }
  28. echo json_encode(true);
  29. break;
  30. default: // list or search
  31. // translation of request search & order fields
  32. switch($_REQUEST['searchField'])
  33. {
  34. case 'id':
  35. $_REQUEST['searchField'] = 'b.id';
  36. break;
  37. case 'type':
  38. $_REQUEST['searchField'] = 'b.type';
  39. break;
  40. case 'title':
  41. $_REQUEST['searchField'] = 'd.text';
  42. break;
  43. case 'category':
  44. $_REQUEST['searchField'] = 'b.category';
  45. break;
  46. case 'dates':
  47. $_REQUEST['searchField'] = 'b.date_published';
  48. break;
  49. case 'enabled':
  50. $_REQUEST['searchField'] = 'b.enabled';
  51. break;
  52. case 'date_modified':
  53. default:
  54. $_REQUEST['searchField'] = 'b.date_modified';
  55. }
  56. if($_REQUEST['sidx']=='dates')
  57. $_REQUEST['sidx'] = 'b.date_published';
  58. $page = intval($_REQUEST['page']);
  59. $max = intval($_REQUEST['rows']);
  60. $offset = ($page - 1) * $max;
  61. $orderby= $_REQUEST['sidx'].' '.$_REQUEST['sord'];
  62. $where = " 1=1 ";
  63. if($_REQUEST['_search']=='true' || isset($_REQUEST['quicksearch']))
  64. {
  65. if(isset($_REQUEST['quicksearch']))
  66. $where .= $item->quicksearch($_REQUEST['quicksearch']);
  67. else if(isset($_REQUEST['filters']))
  68. {
  69. $where .= navitable::jqgridsearch($_REQUEST['filters']);
  70. // special case
  71. if( strpos($where, 'title LIKE')!== false)
  72. {
  73. $where = substr_replace($where, 'd.text', strpos($where, 'title LIKE'), 5);
  74. }
  75. }
  76. else // single search
  77. $where .= ' AND '.navitable::jqgridcompare($_REQUEST['searchField'], $_REQUEST['searchOper'], $_REQUEST['searchString']);
  78. }
  79. $sql = ' SELECT SQL_CALC_FOUND_ROWS b.*, d.text as title
  80. FROM nv_blocks b
  81. LEFT JOIN nv_webdictionary d
  82. ON b.id = d.node_id
  83. AND d.node_type = "block"
  84. AND d.subtype = "title"
  85. AND d.lang = "'.$website->languages_list[0].'"
  86. AND d.website = '.$website->id.'
  87. WHERE '.$where.'
  88. AND b.website = '.$website->id.'
  89. ORDER BY '.$orderby.'
  90. LIMIT '.$max.'
  91. OFFSET '.$offset;
  92. if(!$DB->query($sql, 'array'))
  93. {
  94. throw new Exception($DB->get_last_error());
  95. }
  96. $dataset = $DB->result();
  97. $total = $DB->foundRows();
  98. $block_types = block::types();
  99. $block_types_list = array();
  100. for($i=0; $i < count($block_types); $i++)
  101. {
  102. if(is_numeric($block_types[$i]['id']))
  103. $block_types_list[$block_types[$i]['code']] = $block_types[$i]['title'];
  104. else
  105. $block_types_list[$block_types[$i]['id']] = $block_types[$i]['title'];
  106. }
  107. $dataset = grid_notes::summary($dataset, 'block', 'id');
  108. // we need to format the values and retrieve the needed strings from the dictionary
  109. $out = array();
  110. for($i=0; $i < count($dataset); $i++)
  111. {
  112. if(empty($dataset[$i])) continue;
  113. $access = array(
  114. 0 => '<img src="img/icons/silk/page_white_go.png" align="absmiddle" title="'.t(254, 'Everybody').'" />',
  115. 1 => '<img src="img/icons/silk/lock.png" align="absmiddle" title="'.t(361, 'Web users only').'" />',
  116. 2 => '<img src="img/icons/silk/user_gray.png" align="absmiddle" title="'.t(363, 'Users who have not yet signed up or signed in').'" />',
  117. 3 => '<img src="img/icons/silk/group_key.png" align="absmiddle" title="'.t(512, "Selected web user groups").'" />'
  118. );
  119. if(empty($dataset[$i]['date_published']))
  120. $dataset[$i]['date_published'] = '&infin;';
  121. else
  122. $dataset[$i]['date_published'] = core_ts2date($dataset[$i]['date_published'], false);
  123. if(empty($dataset[$i]['date_unpublish']))
  124. $dataset[$i]['date_unpublish'] = '&infin;';
  125. else
  126. $dataset[$i]['date_unpublish'] = core_ts2date($dataset[$i]['date_unpublish'], false);
  127. if($dataset[$i]['category'] > 0)
  128. $dataset[$i]['category'] = $DB->query_single(
  129. 'text',
  130. 'nv_webdictionary',
  131. ' node_type = "structure" AND
  132. node_id = "'.$dataset[$i]['category'].'" AND
  133. subtype = "title" AND
  134. lang = "'.$website->languages_list[0].'"
  135. '
  136. );
  137. $out[$i] = array(
  138. 0 => $dataset[$i]['id'],
  139. 1 => $block_types_list[$dataset[$i]['type']],
  140. 2 => '<div class="list-row" data-enabled="'.$dataset[$i]['enabled'].'">'.$dataset[$i]['title'].'</div>',
  141. 3 => $dataset[$i]['date_published'].' - '.$dataset[$i]['date_unpublish'],
  142. 4 => $access[$dataset[$i]['access']],
  143. 5 => (($dataset[$i]['enabled']==1)? '<img src="img/icons/silk/accept.png" />' : '<img src="img/icons/silk/cancel.png" />'),
  144. 6 => $dataset[$i]['_grid_notes_html']
  145. );
  146. }
  147. navitable::jqgridJson($out, $page, $offset, $max, $total);
  148. break;
  149. }
  150. session_write_close();
  151. exit;
  152. break;
  153. case 'load':
  154. case 'edit':
  155. case 2:
  156. if(!empty($_REQUEST['id']))
  157. {
  158. $item->load(intval($_REQUEST['id']));
  159. }
  160. if(isset($_REQUEST['form-sent']))
  161. {
  162. $item->load_from_post();
  163. try
  164. {
  165. $item->save();
  166. property::save_properties_from_post('block', $item->id);
  167. $id = $item->id;
  168. // set block order
  169. if(!empty($item->type) && !empty($_REQUEST['blocks-order']))
  170. block::reorder($item->type, $_REQUEST['blocks-order'], $_REQUEST['blocks-order-fixed']);
  171. unset($item);
  172. $item = new block();
  173. $item->load($id);
  174. $layout->navigate_notification(t(53, "Data saved successfully."), false, false, 'fa fa-check');
  175. }
  176. catch(Exception $e)
  177. {
  178. $layout->navigate_notification($e->getMessage(), true, true);
  179. }
  180. users_log::action($_REQUEST['fid'], $item->id, 'save', $item->dictionary[$website->languages_list[0]]['title'], json_encode($_REQUEST));
  181. }
  182. else
  183. users_log::action($_REQUEST['fid'], $item->id, 'load', $item->dictionary[$website->languages_list[0]]['title']);
  184. $out = blocks_form($item);
  185. break;
  186. case 'delete':
  187. case 4: // remove
  188. if(!empty($_REQUEST['id']))
  189. {
  190. $item->load(intval($_REQUEST['id']));
  191. if($item->delete() > 0)
  192. {
  193. $layout->navigate_notification(t(55, 'Item removed successfully.'), false);
  194. $out = blocks_list();
  195. }
  196. else
  197. {
  198. $layout->navigate_notification(t(56, 'Unexpected error.'), false);
  199. $out = blocks_form($item);
  200. }
  201. users_log::action($_REQUEST['fid'], $item->id, 'remove', $item->dictionary[$website->languages_list[0]]['title']);
  202. }
  203. break;
  204. case 'duplicate':
  205. if(!empty($_REQUEST['id']))
  206. {
  207. $item->load(intval($_REQUEST['id']));
  208. $properties = property::load_properties_associative(
  209. 'block', $item->type,
  210. 'block', $item->id
  211. );
  212. // try to duplicate
  213. $item->id = 0;
  214. $ok = $item->insert();
  215. if($ok)
  216. {
  217. // also duplicate block properties
  218. $ok = property::save_properties_from_array('block', $item->id, $item->type, $properties);
  219. }
  220. if($ok)
  221. {
  222. $layout->navigate_notification(t(478, 'Item duplicated successfully.'), false, false, 'fa fa-check');
  223. $out = blocks_form($item);
  224. }
  225. else
  226. {
  227. $layout->navigate_notification(t(56, 'Unexpected error.'), false);
  228. $item = new block();
  229. $item->load(intval($_REQUEST['id']));
  230. $out = blocks_form($item);
  231. }
  232. users_log::action($_REQUEST['fid'], $item->id, 'duplicate', $item->dictionary[$website->languages_list[0]]['title'], json_encode($_REQUEST));
  233. }
  234. break;
  235. case 'path':
  236. case 5: // search an existing path
  237. $DB->query('SELECT path as id, path as label, path as value
  238. FROM nv_paths
  239. WHERE path LIKE '.protect('%'.$_REQUEST['term'].'%').'
  240. AND website = '.$website->id.'
  241. ORDER BY path ASC
  242. LIMIT 10',
  243. 'array');
  244. echo json_encode($DB->result());
  245. core_terminate();
  246. break;
  247. case 'block_groups_list':
  248. $out = block_groups_list();
  249. break;
  250. case 'block_groups_json': // block groups: json data retrieval
  251. $page = intval($_REQUEST['page']);
  252. $max = intval($_REQUEST['rows']);
  253. $offset = ($page - 1) * $max;
  254. list($rs, $total) = block_group::paginated_list($offset, $max, $_REQUEST['sidx'], $_REQUEST['sord']);
  255. $rs = grid_notes::summary($rs, 'block_group', 'id');
  256. // translate $rs to an array of ordered fields
  257. foreach($rs as $row)
  258. {
  259. if(substr($row['blocks'], 0, 2)=='a:') // nv < 2.1
  260. $row['blocks'] = mb_unserialize($row['blocks']);
  261. else // nv >= 2.1
  262. $row['blocks'] = json_decode($row['blocks'], true);
  263. $dataset[] = array(
  264. 'id' => $row['id'],
  265. 'code' => $row['code'],
  266. 'title' => $row['title'],
  267. 'blocks' => count($row['blocks']),
  268. 'notes' => $row['_grid_notes_html']
  269. );
  270. }
  271. navitable::jqgridJson($dataset, $page, $offset, $max, $total, 'id');
  272. session_write_close();
  273. exit;
  274. break;
  275. case 'block_group_edit':
  276. $item = new block_group();
  277. if(!empty($_REQUEST['id']))
  278. {
  279. $item->load(intval($_REQUEST['id']));
  280. }
  281. if(isset($_REQUEST['form-sent']))
  282. {
  283. $item->load_from_post();
  284. try
  285. {
  286. $item->save();
  287. $layout->navigate_notification(t(53, "Data saved successfully."), false, false, 'fa fa-check');
  288. }
  289. catch(Exception $e)
  290. {
  291. $layout->navigate_notification($e->getMessage(), true, true);
  292. }
  293. users_log::action($_REQUEST['fid'], $item->id, 'save', $item->title, json_encode($_REQUEST));
  294. }
  295. else if(!empty($_REQUEST['id']))
  296. users_log::action($_REQUEST['fid'], $item->id, 'edit', $item->title);
  297. $out = block_group_form($item);
  298. break;
  299. case 'block_group_delete':
  300. $item = new block_group();
  301. if(!empty($_REQUEST['id']))
  302. {
  303. $item->load(intval($_REQUEST['id']));
  304. if($item->delete() > 0)
  305. {
  306. $layout->navigate_notification(t(55, 'Item removed successfully.'), false);
  307. $out = block_groups_list();
  308. }
  309. else
  310. {
  311. $layout->navigate_notification(t(56, 'Unexpected error.'), false);
  312. $out = block_group_form($item);
  313. }
  314. users_log::action($_REQUEST['fid'], $item->id, 'remove', $item->title);
  315. }
  316. break;
  317. case 'block_types_list':
  318. $out = blocks_types_list();
  319. break;
  320. case 'block_types_json': // block types: json data retrieval
  321. $page = intval($_REQUEST['page']);
  322. $max = intval($_REQUEST['rows']);
  323. $offset = ($page - 1) * $max;
  324. $rs = block::types($_REQUEST['sidx'], $_REQUEST['sord']);
  325. $block_modes = block::modes();
  326. // translate $rs to an array of ordered fields
  327. foreach($rs as $row)
  328. {
  329. $dataset[] = array(
  330. 'id' => $row['id'],
  331. 'type' => $block_modes[$row['type']],
  332. 'code' => $row['code'],
  333. 'title' => $row['title'],
  334. 'width' => $row['width'],
  335. 'height' => $row['height']
  336. );
  337. }
  338. $total = count($dataset);
  339. navitable::jqgridJson($dataset, $page, $offset, $max, $total, 'id');
  340. session_write_close();
  341. exit;
  342. break;
  343. case 'block_type_edit':
  344. case 82: // edit/create block type
  345. $item = NULL;
  346. $position = NULL;
  347. $max_id = 0;
  348. $dataset = block::custom_types();
  349. for($i=0; $i < count($dataset); $i++)
  350. {
  351. if($dataset[$i]['id'] > $max_id)
  352. $max_id = $dataset[$i]['id'];
  353. if($dataset[$i]['id'] == $_REQUEST['id'])
  354. {
  355. $item = $dataset[$i];
  356. $position = $i;
  357. }
  358. }
  359. if(empty($item))
  360. {
  361. $layout->navigate_notification(t(599, "Sorry, can't display a theme block type info."));
  362. $out = blocks_types_list();
  363. }
  364. else
  365. {
  366. if(isset($_REQUEST['form-sent']))
  367. {
  368. if(empty($item))
  369. $item = array('id' => $max_id + 1);
  370. $item['type'] = $_REQUEST['type'];
  371. $item['title'] = $_REQUEST['title'];
  372. $item['code'] = $_REQUEST['code'];
  373. $item['width'] = $_REQUEST['width'];
  374. $item['height'] = $_REQUEST['height'];
  375. $item['order'] = $_REQUEST['order'];
  376. $item['maximum'] = $_REQUEST['maximum'];
  377. $item['notes'] = pquotes($_REQUEST['notes']);
  378. if(!is_null($position))
  379. $dataset[$position] = $item;
  380. else
  381. $dataset[] = $item;
  382. try
  383. {
  384. // save
  385. $ok = block::types_update($dataset);
  386. $layout->navigate_notification(t(53, "Data saved successfully."), false, false, 'fa fa-check');
  387. }
  388. catch(Exception $e)
  389. {
  390. $layout->navigate_notification($e->getMessage(), true, true);
  391. }
  392. }
  393. $out = blocks_type_form($item);
  394. }
  395. break;
  396. case 'block_type_delete':
  397. case 84: // remove block type
  398. $dataset = block::custom_types();
  399. $item = NULL;
  400. for($i=0; $i < count($dataset); $i++)
  401. {
  402. if($dataset[$i]['id'] == $_REQUEST['id'])
  403. {
  404. unset($dataset[$i]);
  405. break;
  406. }
  407. }
  408. try
  409. {
  410. block::types_update($dataset);
  411. $layout->navigate_notification(t(55, 'Item removed successfully.'), false);
  412. $out = blocks_types_list();
  413. }
  414. catch(Exception $e)
  415. {
  416. $out = $layout->navigate_message("error", t(23, 'Blocks'), t(56, 'Unexpected error.'));
  417. }
  418. break;
  419. case 'block_property_load':
  420. $property = new property();
  421. if(!empty($_REQUEST['id']))
  422. {
  423. if(is_numeric($_REQUEST['id']))
  424. $property->load(intval($_REQUEST['id']));
  425. else
  426. $property->load_from_theme($_REQUEST['id'], null, 'block', $_REQUEST['block']);
  427. }
  428. header('Content-type: text/json');
  429. $types = property::types();
  430. $property->type_text = $types[$property->type];
  431. echo json_encode($property);
  432. session_write_close();
  433. exit;
  434. break;
  435. case 'block_property_save': // save property details
  436. $property = new property();
  437. if(!empty($_REQUEST['property-id']))
  438. $property->load(intval($_REQUEST['property-id']));
  439. $property->load_from_post();
  440. $property->save();
  441. header('Content-type: text/json');
  442. $types = property::types();
  443. $property->type_text = $types[$property->type];
  444. echo json_encode($property);
  445. session_write_close();
  446. exit;
  447. break;
  448. case 'block_property_remove': // remove property
  449. $property = new property();
  450. if(!empty($_REQUEST['property-id']))
  451. $property->load(intval($_REQUEST['property-id']));
  452. $property->delete();
  453. session_write_close();
  454. exit;
  455. break;
  456. case 'block_group_block_options':
  457. $status = null;
  458. $block_group = $_REQUEST['block_group'];
  459. $block_code = $_REQUEST['code'];
  460. $block_uid = $_REQUEST['block_uid'];
  461. if(isset($_REQUEST['form-sent']))
  462. $status = property::save_properties_from_post('block_group_block', $block_code, $block_group, $block_code, $block_uid);
  463. $out = block_group_block_options($block_group, $block_code, $block_uid, $status);
  464. echo $out;
  465. core_terminate();
  466. break;
  467. case 'block_group_extension_block_options':
  468. $status = null;
  469. $block_group = $_REQUEST['block_group']; // block_group type
  470. $block_id = $_REQUEST['block_id']; // extension block id (type)
  471. $block_uid = $_REQUEST['block_uid']; // extension block unique id
  472. $block_extension = $_REQUEST['block_extension']; // extension name
  473. if(isset($_REQUEST['form-sent']))
  474. $status = property::save_properties_from_post('extension_block', $block_group, $block_id, null, $block_uid);
  475. $out = block_group_extension_block_options($block_group, $block_extension, $block_id, $block_uid, $status);
  476. echo $out;
  477. core_terminate();
  478. break;
  479. case 'list':
  480. case 0:
  481. default:
  482. $out = blocks_list();
  483. break;
  484. }
  485. return $out;
  486. }
  487. function blocks_list()
  488. {
  489. global $events;
  490. global $user;
  491. $navibars = new navibars();
  492. $navitable = new navitable("blocks_list");
  493. $navibars->title(t(23, 'Blocks'));
  494. // retrieve block groups, if more than 10, do not show quickmenu
  495. $group_blocks_links = array();
  496. list($bg_rs, $bg_total) = block_group::paginated_list(0, 10, 'title', 'desc');
  497. if($bg_total > 0 && $bg_total <= 10)
  498. {
  499. foreach($bg_rs as $bg)
  500. $group_blocks_links[] = '<a class="ui-menu-action-bigger" href="?fid=blocks&act=block_group_edit&id='.$bg['id'].'"><i class="fa fa-fw fa-caret-right"></i> '.$bg['title'].'</a>';
  501. $events->add_actions(
  502. 'blocks',
  503. array(
  504. 'item' => null,
  505. 'navibars' => &$navibars
  506. ),
  507. $group_blocks_links,
  508. '<a class="content-actions-submenu-trigger" href="?fid=blocks&act=block_groups_list">
  509. <img height="16" align="absmiddle" width="16" src="img/icons/silk/bricks.png"> '.t(506, 'Groups').' &#9662;
  510. </a>'
  511. );
  512. }
  513. $navibars->add_actions(
  514. array(
  515. (!empty($group_blocks_links)? '' : '<a href="?fid=blocks&act=block_groups_list"><img height="16" align="absmiddle" width="16" src="img/icons/silk/bricks.png"> '.t(506, 'Groups').'</a>'),
  516. '<a href="?fid=blocks&act=block_types_list"><img height="16" align="absmiddle" width="16" src="img/icons/silk/brick_edit.png"> '.t(167, 'Types').'</a>'
  517. )
  518. );
  519. $navibars->add_actions(
  520. array(
  521. '<a href="?fid=blocks&act=edit"><img height="16" align="absmiddle" width="16" src="img/icons/silk/add.png"> '.t(38, 'Create').'</a>',
  522. '<a href="?fid=blocks&act=list"><img height="16" align="absmiddle" width="16" src="img/icons/silk/application_view_list.png"> '.t(39, 'List').'</a>',
  523. 'search_form'
  524. )
  525. );
  526. if(@$_REQUEST['quicksearch']=='true')
  527. $navitable->setInitialURL("?fid=blocks&act=json&_search=true&quicksearch=".$_REQUEST['navigate-quicksearch']);
  528. $navitable->setURL('?fid=blocks&act=json');
  529. $navitable->sortBy('date_modified', 'desc');
  530. $navitable->setDataIndex('id');
  531. $navitable->setEditUrl('id', '?fid=blocks&act=edit&id=');
  532. $navitable->enableSearch();
  533. if($user->permission("blocks.delete") == 'true')
  534. $navitable->enableDelete();
  535. $navitable->setGridNotesObjectName("block");
  536. $navitable->addCol("ID", 'id', "40", "true", "left");
  537. $navitable->addCol(t(160, 'Type'), 'type', "120", "true", "center");
  538. $navitable->addCol(t(67, 'Title'), 'title', "400", "true", "left");
  539. $navitable->addCol(t(85, 'Date published'), 'dates', "100", "true", "center");
  540. $navitable->addCol(t(364, 'Access'), 'access', "40", "true", "center");
  541. $navitable->addCol(t(65, 'Enabled'), 'enabled', "40", "true", "center");
  542. $navitable->addCol(t(168, 'Notes'), 'note', "50", "false", "center");
  543. $navibars->add_content($navitable->generate());
  544. return $navibars->generate();
  545. }
  546. function blocks_form($item)
  547. {
  548. global $user;
  549. global $DB;
  550. global $website;
  551. global $layout;
  552. global $events;
  553. global $theme;
  554. $current_version = $_SESSION['current_version'];
  555. $extra_actions = array();
  556. $navibars = new navibars();
  557. $naviforms = new naviforms();
  558. $layout->navigate_media_browser(); // we can use media browser in this function
  559. if(empty($item->id))
  560. $navibars->title(t(23, 'Blocks').' / '.t(38, 'Create'));
  561. else
  562. $navibars->title(t(23, 'Blocks').' / '.t(170, 'Edit').' ['.$item->id.']');
  563. $navibars->add_actions(
  564. array(
  565. '<a href="#" onclick="javascript: navigate_media_browser();" title="Ctrl+M">
  566. <img height="16" align="absmiddle" width="16" src="img/icons/silk/images.png"> '.t(36, 'Media').'
  567. </a>'
  568. )
  569. );
  570. $layout->add_script("
  571. $(document).on('keydown.ctrl_s', function (evt) { navigate_tabform_submit(1); return false; } );
  572. $(document).on('keydown.ctrl_m', function (evt) { navigate_media_browser(); return false; } );
  573. ");
  574. if(!empty($item->id))
  575. {
  576. $notes = grid_notes::comments('block', $item->id);
  577. $navibars->add_actions(
  578. array(
  579. '<a href="#" onclick="javascript: navigate_display_notes_dialog();"><span class="navigate_grid_notes_span" style=" width: 20px; line-height: 16px; ">'.count($notes).'</span><img src="img/skins/badge.png" width="20px" height="18px" style="margin-top: -2px;" class="grid_note_edit" align="absmiddle" /> '.t(168, 'Notes').'</a>'
  580. )
  581. );
  582. }
  583. if(empty($item->id))
  584. {
  585. $navibars->add_actions(
  586. array(
  587. ($user->permission('blocks.create')=='true'?
  588. '<a href="#" onclick="navigate_tabform_submit(1);" title="Ctrl+S">
  589. <img height="16" align="absmiddle" width="16" src="img/icons/silk/accept.png"> '.t(34, 'Save').'
  590. </a>' : "")
  591. )
  592. );
  593. }
  594. else
  595. {
  596. $navibars->add_actions(
  597. array(
  598. (($user->permission('blocks.edit') == 'true') ?
  599. '<a href="#" onclick="navigate_tabform_submit(1);" title="Ctrl+S">
  600. <img height="16" align="absmiddle" width="16" src="img/icons/silk/accept.png"> '.t(34, 'Save').'
  601. </a>' : ""),
  602. ($user->permission("blocks.delete") == 'true' ?
  603. '<a href="#" onclick="navigate_delete_dialog();">
  604. <img height="16" align="absmiddle" width="16" src="img/icons/silk/cancel.png"> '.t(35, 'Delete').'
  605. </a>' : "")
  606. )
  607. );
  608. $layout->add_script('
  609. function navigate_delete_dialog()
  610. {
  611. navigate_confirmation_dialog(
  612. function() { window.location.href = "?fid=blocks&act=delete&id='.$item->id.'"; },
  613. null, null, "'.t(35, 'Delete').'"
  614. );
  615. }
  616. ');
  617. if($user->permission("blocks.create") == 'true')
  618. $extra_actions[] = '<a href="?fid=blocks&act=duplicate&id='.$item->id.'" onclick="$(this).attr(\'#\');"><img height="16" align="absmiddle" width="16" src="img/icons/silk/page_copy.png"> '.t(477, 'Duplicate').'</a>';
  619. }
  620. array_unshift($extra_actions, '<a href="?fid=blocks&act=block_types_list"><img height="16" align="absmiddle" width="16" src="img/icons/silk/brick_edit.png"> '.t(167, 'Types').'</a>');
  621. $events->add_actions(
  622. 'blocks',
  623. array(
  624. 'item' => null,
  625. 'navibars' => &$navibars
  626. ),
  627. $extra_actions
  628. );
  629. $group_blocks_links = array();
  630. list($bg_rs, $bg_total) = block_group::paginated_list(0, 10, 'title', 'desc');
  631. if($bg_total > 0 && $bg_total <= 10)
  632. {
  633. foreach($bg_rs as $bg)
  634. $group_blocks_links[] = '<a href="?fid=blocks&act=block_group_edit&id='.$bg['id'].'"><i class="fa fa-fw fa-caret-right"></i> '.$bg['title'].'</a>';
  635. $events->add_actions(
  636. 'blocks',
  637. array(
  638. 'item' => null,
  639. 'navibars' => &$navibars
  640. ),
  641. $group_blocks_links,
  642. '<a class="content-actions-submenu-trigger" href="?fid=blocks&act=block_groups_list"><img height="16" align="absmiddle" width="16" src="img/icons/silk/bricks.png"> '.t(506, 'Groups').' &#9662;</a>'
  643. );
  644. }
  645. else
  646. {
  647. $navibars->add_actions(
  648. array(
  649. '<a href="?fid=blocks&act=block_groups_list"><img height="16" align="absmiddle" width="16" src="img/icons/silk/bricks.png"> '.t(506, 'Groups').'</a>'
  650. )
  651. );
  652. }
  653. $navibars->add_actions(
  654. array(
  655. (!empty($item->id)? '<a href="?fid=blocks&act=edit"><img height="16" align="absmiddle" width="16" src="img/icons/silk/add.png"> '.t(38, 'Create').'</a>' : ''),
  656. '<a href="?fid=blocks&act=list"><img height="16" align="absmiddle" width="16" src="img/icons/silk/application_view_list.png"> '.t(39, 'List').'</a>',
  657. 'search_form'
  658. )
  659. );
  660. if(!empty($item->id))
  661. $layout->navigate_notes_dialog('block', $item->id);
  662. $navibars->form(NULL, '?fid=blocks&act=edit');
  663. $navibars->add_content('
  664. <script type="text/javascript" src="lib/packages/blocks/blocks.js?r='.$current_version->revision.'"></script>
  665. ');
  666. $navibars->add_tab(t(43, "Main"));
  667. $navibars->add_tab_content($naviforms->hidden('form-sent', 'true'));
  668. $navibars->add_tab_content($naviforms->hidden('id', $item->id));
  669. $navibars->add_tab_content_row(
  670. array(
  671. '<label>ID</label>',
  672. '<span>'.(!empty($item->id)? $item->id : t(52, '(new)')).'</span>'
  673. )
  674. );
  675. $block_types = block::types();
  676. $block_types_keys = array();
  677. $block_types_info = array();
  678. for($i=0; $i < count($block_types); $i++)
  679. {
  680. if($item->type == $block_types[$i]['code'])
  681. $block_type_width = $block_types[$i]['width'];
  682. $block_size_helper = '';
  683. if(!empty($block_types[$i]['width']) || !empty($block_types[$i]['height']))
  684. {
  685. if(empty($block_types[$i]['width'])) $block_types[$i]['width'] = '***';
  686. if(empty($block_types[$i]['height'])) $block_types[$i]['height'] = '***';
  687. $block_size_helper = ' ('.$block_types[$i]['width'].' x '.$block_types[$i]['height'].' px)';
  688. }
  689. if(is_numeric($block_types[$i]['id']))
  690. $block_types_keys[] = $block_types[$i]['code']; // block type created via navigate interface
  691. else
  692. $block_types_keys[] = $block_types[$i]['id']; // block described in theme definition
  693. $block_types_info[] = $block_types[$i]['title'].$block_size_helper;
  694. }
  695. $navibars->add_tab_content_row(
  696. array(
  697. '<label>'.t(160, 'Type').'</label>',
  698. $naviforms->selectfield('type', $block_types_keys, $block_types_info, $item->type)
  699. )
  700. );
  701. $navibars->add_tab_content_row(
  702. array(
  703. '<label>'.t(85, 'Date published').'</label>',
  704. $naviforms->datefield('date_published', $item->date_published, true),
  705. )
  706. );
  707. $navibars->add_tab_content_row(
  708. array(
  709. '<label>'.t(90, 'Date unpublished').'</label>',
  710. $naviforms->datefield('date_unpublish', $item->date_unpublish, true),
  711. )
  712. );
  713. // Notes field is deprecated, but we keep on showing the existing Notes
  714. if(!empty($item->notes))
  715. {
  716. $navibars->add_tab_content_row(
  717. array(
  718. '<label>'.t(168, 'Notes').'</label>',
  719. $naviforms->textarea('notes', $item->notes)
  720. )
  721. );
  722. }
  723. $navibars->add_tab_content_row(
  724. array(
  725. '<label>'.t(364, 'Access').'</label>',
  726. $naviforms->selectfield(
  727. 'access',
  728. array(
  729. 0 => 0,
  730. 1 => 2,
  731. 2 => 1,
  732. 3 => 3
  733. ),
  734. array(
  735. 0 => t(254, 'Everybody'),
  736. 1 => t(362, 'Not signed in'),
  737. 2 => t(361, 'Web users only'),
  738. 3 => t(512, 'Selected web user groups')
  739. ),
  740. $item->access,
  741. 'navigate_webuser_groups_visibility($(this).val());',
  742. false,
  743. array(
  744. 1 => t(363, 'Users who have not yet signed in')
  745. )
  746. )
  747. )
  748. );
  749. $webuser_groups = webuser_group::all_in_array();
  750. $navibars->add_tab_content_row(
  751. array(
  752. '<label>'.t(506, "Groups").'</label>',
  753. $naviforms->multiselect(
  754. 'groups',
  755. array_keys($webuser_groups),
  756. array_values($webuser_groups),
  757. $item->groups
  758. )
  759. ),
  760. 'webuser-groups-field'
  761. );
  762. $layout->add_script('
  763. function navigate_webuser_groups_visibility(access_value)
  764. {
  765. if(access_value==3)
  766. $("#webuser-groups-field").show();
  767. else
  768. $("#webuser-groups-field").hide();
  769. }
  770. navigate_webuser_groups_visibility('.$item->access.');
  771. ');
  772. if(empty($item->id)) $item->enabled = true;
  773. $navibars->add_tab_content_row(
  774. array(
  775. '<label>'.t(65, 'Enabled').'</label>',
  776. $naviforms->checkbox('enabled', $item->enabled),
  777. )
  778. );
  779. if($item->date_modified > 0)
  780. {
  781. $navibars->add_tab_content_row(
  782. array(
  783. '<label>'.t(227, 'Date modified').'</label>',
  784. core_ts2date($item->date_modified, true)
  785. )
  786. );
  787. }
  788. $navibars->add_tab(t(9, "Content"));
  789. switch($item->class)
  790. {
  791. case 'poll':
  792. $options = array();
  793. foreach($website->languages_list as $lang)
  794. $options[$lang] = language::name_by_code($lang);
  795. $navibars->add_tab_content_row(
  796. array(
  797. '<label>'.t(63, 'Languages').'</label>',
  798. $naviforms->buttonset('language_selector', $options, $website->languages_list[0], "navigate_items_select_language(this);")
  799. )
  800. );
  801. foreach($website->languages_list as $lang)
  802. {
  803. $navibars->add_tab_content('
  804. <div class="language_fields" id="language_fields_'.$lang.'" style=" display: none; ">
  805. ');
  806. $navibars->add_tab_content_row(
  807. array(
  808. '<label>'.t(558, 'Question').'</label>',
  809. $naviforms->textfield('title-'.$lang, @$item->dictionary[$lang]['title']),
  810. ''
  811. )
  812. );
  813. // Poll options
  814. $table = new naviorderedtable("poll_answers_table_".$lang);
  815. $table->setWidth("330px");
  816. $table->setHiddenInput("poll-answers-table-order-".$lang);
  817. $navibars->add_tab_content(
  818. $naviforms->hidden("poll-answers-table-order-".$lang, "")
  819. );
  820. $table->addHeaderColumn(t(67, 'Title'), 200);
  821. //$table->addHeaderColumn(t(237, 'Code'), 120);
  822. $table->addHeaderColumn(t(352, 'Votes'), 80);
  823. $table->addHeaderColumn(t(35, 'Remove'), 50);
  824. if(!empty($item->trigger[$lang]))
  825. {
  826. $poll_answers = $item->trigger[$lang];
  827. foreach($poll_answers as $pa)
  828. {
  829. $uid = uniqid();
  830. $table->addRow(
  831. "poll-answers-table-row-".$uid,
  832. array(
  833. array('content' => '<input type="text" name="poll-answers-table-title-'.$lang.'['.$uid.']" value="'.$pa['title'].'" style="width: 200px;" />', 'align' => 'left'),
  834. //array('content' => '<input type="text" name="poll-answers-table-code-'.$lang.'['.$uid.']" value="'.$pa['code'].'" style="width: 120px;" />', 'align' => 'left'),
  835. array('content' => '<input type="text" name="poll-answers-table-votes-'.$lang.'['.$uid.']" value="'.intval($pa['votes']).'" style="width: 80px;" />', 'align' => 'left'),
  836. array('content' => '<img src="'.NAVIGATE_URL.'/img/icons/silk/cancel.png" style="cursor: pointer;" onclick="navigate_blocks_poll_answers_table_row_remove(this);" />', 'align' => 'center')
  837. )
  838. );
  839. }
  840. }
  841. $uid = uniqid();
  842. $table->addRow(
  843. "poll-answers-table-row-model-".$lang,
  844. array(
  845. array('content' => '<input type="text" name="poll-answers-table-title-'.$lang.'['.$uid.']" value="" style="width: 200px;" />', 'align' => 'left'),
  846. //array('content' => '<input type="text" name="poll-answers-table-code-'.$lang.'['.$uid.']" value="'.$uid.'" style="width: 120px;" />', 'align' => 'left'),
  847. array('content' => '<input type="text" name="poll-answers-table-votes-'.$lang.'['.$uid.']" value="0" style="width: 80px;" />', 'align' => 'left'),
  848. array('content' => '<img src="'.NAVIGATE_URL.'/img/icons/silk/cancel.png" style="cursor: pointer;" onclick="navigate_blocks_poll_answers_table_row_remove(this);" />', 'align' => 'center')
  849. )
  850. );
  851. $navibars->add_tab_content_row(
  852. array(
  853. '<label>'.t(559, "Answers").'</label>',
  854. '<div id="poll-answers-'.$lang.'">'.$table->generate().'</div>',
  855. '<label>&nbsp;</label>',
  856. '<button id="poll-answers-table-add-'.$lang.'" data-lang="'.$lang.'"><img src="img/icons/silk/add.png" align="absmiddle" style="cursor:pointer;" /> '.t(472, 'Add').'</button>'
  857. )
  858. );
  859. $navibars->add_tab_content('
  860. </div>
  861. ');
  862. }
  863. foreach($website->languages_list as $alang)
  864. {
  865. $layout->add_script('
  866. $(window).on("load", function()
  867. {
  868. poll_answers_table_row_models["'.$alang.'"] = $("#poll-answers-table-row-model-'.$alang.'").html();
  869. if($("#poll_answers_table_'.$alang.'").find("tr").not(".nodrag").length > 1)
  870. $("#poll-answers-table-row-model-'.$alang.'").hide();
  871. navigate_naviorderedtable_poll_answers_table_'.$alang.'_reorder();
  872. });
  873. ');
  874. }
  875. $layout->add_script('
  876. var active_languages = ["'.implode('", "', array_keys($options)).'"];
  877. navigate_items_select_language("'.$website->languages_list[0].'");
  878. ');
  879. break;
  880. case 'block':
  881. case 'theme':
  882. default:
  883. $options = array();
  884. foreach($website->languages_list as $lang)
  885. $options[$lang] = language::name_by_code($lang);
  886. $navibars->add_tab_content_row(
  887. array(
  888. '<label>'.t(63, 'Languages').'</label>',
  889. $naviforms->buttonset('language_selector', $options, $website->languages_list[0], "navigate_items_select_language(this);")
  890. )
  891. );
  892. foreach($website->languages_list as $lang)
  893. {
  894. $navibars->add_tab_content('<div class="language_fields" id="language_fields_'.$lang.'" style=" display: none; ">');
  895. $navibars->add_tab_content_row(
  896. array(
  897. '<label>'.t(67, 'Title').'</label>',
  898. $naviforms->textfield('title-'.$lang, @$item->dictionary[$lang]['title']),
  899. ''
  900. )
  901. );
  902. $block_trigger_types = array(
  903. '' => t(181, 'Hidden'),
  904. 'title' => t(67, 'Title'),
  905. 'content' => t(9, 'Content'),
  906. 'image' => t(157, 'Image'),
  907. 'rollover' => t(182, 'Rollover'),
  908. 'video' => t(272, 'Video'),
  909. 'html' => 'HTML',
  910. 'links' => t(549, 'Links'),
  911. 'flash' => 'Flash'
  912. );
  913. // check block trigger restrictions in theme definition
  914. if(is_array($theme->blocks))
  915. {
  916. foreach($theme->blocks as $tb)
  917. {
  918. // navigate 1.x compatibility
  919. if(!isset($tb->id) && isset($tb->code))
  920. $tb->id = $tb->code;
  921. if($tb->id == $item->type && isset($tb->trigger))
  922. {
  923. if(!is_array($tb->trigger))
  924. $tb->trigger = array($tb->trigger);
  925. foreach($block_trigger_types as $btt_key => $btt_val)
  926. {
  927. if(empty($btt_key) || in_array($btt_key, $tb->trigger))
  928. continue;
  929. unset($block_trigger_types[$btt_key]);
  930. }
  931. $block_trigger_types = array_filter($block_trigger_types);
  932. }
  933. }
  934. }
  935. $navibars->add_tab_content_row(array(
  936. '<label>'.t(160, 'Type').'</label>',
  937. $naviforms->selectfield('trigger-type-'.$lang,
  938. array_keys($block_trigger_types),
  939. array_values($block_trigger_types),
  940. $item->trigger['trigger-type'][$lang],
  941. "navigate_blocks_trigger_change('".$lang."', this);"
  942. )
  943. )
  944. );
  945. $navibars->add_tab_content_row(
  946. array(
  947. '<label>'.t(157, 'Image').'</label>',
  948. $naviforms->dropbox('trigger-image-'.$lang, @$item->trigger['trigger-image'][$lang], 'image')
  949. )
  950. );
  951. $navibars->add_tab_content_row(
  952. array(
  953. '<label>'.t(182, 'Rollover').' (off / on)</label>',
  954. $naviforms->dropbox('trigger-rollover-'.$lang, @$item->trigger['trigger-rollover'][$lang], 'image'),
  955. $naviforms->dropbox('trigger-rollover-active-'.$lang, @$item->trigger['trigger-rollover-active'][$lang], 'image'),
  956. ''
  957. )
  958. );
  959. $navibars->add_tab_content_row(
  960. array(
  961. '<label>'.t(272, 'Video').'</label>',
  962. $naviforms->dropbox('trigger-video-'.$lang, @$item->trigger['trigger-video'][$lang], 'video')
  963. )
  964. );
  965. $navibars->add_tab_content_row(
  966. array(
  967. '<label>Flash (SWF)</label>',
  968. $naviforms->dropbox('trigger-flash-'.$lang, @$item->trigger['trigger-flash'][$lang], 'flash'),
  969. ''
  970. )
  971. );
  972. /* links list */
  973. // check if navigate must show an icon selector
  974. $links_icons = '';
  975. if(!empty($theme) && !empty($theme->blocks))
  976. {
  977. foreach($theme->blocks as $tb)
  978. {
  979. if($item->type == $tb->id)
  980. {
  981. $links_icons = @$tb->icons;
  982. break;
  983. }
  984. }
  985. }
  986. $table = new naviorderedtable("trigger_links_table_".$lang);
  987. $table->setWidth("776px");
  988. $table->setHiddenInput("trigger-links-table-order-".$lang);
  989. $navibars->add_tab_content( $naviforms->hidden("trigger-links-table-order-".$lang, "") );
  990. $table->addHeaderColumn(t(242, 'Icon'), 50);
  991. $table->addHeaderColumn(t(67, 'Title'), 180);
  992. $table->addHeaderColumn(t(197, 'Link'), 390);
  993. $table->addHeaderColumn('<i class="fa fa-lg fa-fw fa-external-link" title="'.t(324, 'New window').'"></i>', 16);
  994. $table->addHeaderColumn('<i class="fa fa-lg fa-fw fa-globe" title="'.t(364, 'Access').'"></i>', 20);
  995. $table->addHeaderColumn(t(35, 'Remove'), 50);
  996. if(empty($item->trigger['trigger-links'][$lang]['link']))
  997. {
  998. // create a default entry
  999. $item->trigger['trigger-links'][$lang] = array(
  1000. 'order' => '',
  1001. 'icon' => '',
  1002. 'title' => array('0' => ''),
  1003. 'access' => array('0' => 0),
  1004. 'link' => array('0' => '')
  1005. );
  1006. }
  1007. if(!empty($item->trigger['trigger-links'][$lang]))
  1008. {
  1009. $tlinks = $item->trigger['trigger-links'][$lang];
  1010. foreach($tlinks['link'] as $key => $link)
  1011. {
  1012. $uid = uniqid();
  1013. $table->addRow(
  1014. uniqid('trigger-links-table-row-'),
  1015. array(
  1016. ( empty($links_icons)?
  1017. array('content' => '-', 'align' => 'center') :
  1018. array('content' => '<select name="trigger-links-table-icon-'.$lang.'['.$uid.']" data-select2-value="'.$tlinks['icon'][$key].'" data-role="icon" style="width: 190px;"></select>', 'align' => 'left')
  1019. ),
  1020. array('content' => '<input type="text" name="trigger-links-table-title-'.$lang.'['.$uid.']" value="'.$tlinks['title'][$key].'" data-role="title" style="width: 250px;" />', 'align' => 'left'),
  1021. array('content' => '<input type="text" name="trigger-links-table-link-'.$lang.'['.$uid.']" value="'.$tlinks['link'][$key].'" data-role="link" style="width: 260px;" />'.
  1022. '<a class="uibutton nv_block_nv_link_trigger"><i class="fa fa-sitemap"></i></a>',
  1023. 'align' => 'left',
  1024. 'style' => 'white-space: nowrap;'
  1025. ),
  1026. array('content' => '<input type="checkbox" name="trigger-links-table-new_window-'.$lang.'['.$uid.']" data-role="target" id="trigger-links-table-new_window-'.$lang.'['.$uid.']" value="1" '.($tlinks['new_window'][$key]=='1'? 'checked="checked"' : '').' />
  1027. <label for="trigger-links-table-new_window-'.$lang.'['.$uid.']" />',
  1028. 'align' => 'left'),
  1029. array('content' => '<input type="hidden" name="trigger-links-table-access-'.$lang.'['.$uid.']" data-role="access" id="trigger-links-table-access-'.$lang.'['.$uid.']" value="'.value_or_default($tlinks['access'][$key], 0).'" />
  1030. <i class="fa fa-fw fa-lg fa-eye '.($tlinks['access'][$key]=='1'? 'hidden' : '').'" onclick="navigate_blocks_trigger_links_table_row_access(this);" data-value="0" for="trigger-links-table-access-'.$lang.'['.$uid.']"></i>
  1031. <i class="fa fa-fw fa-lg fa-eye-slash '.($tlinks['access'][$key]=='1'? '' : 'hidden').'" onclick="navigate_blocks_trigger_links_table_row_access(this);" data-value="1" for="trigger-links-table-access-'.$lang.'['.$uid.']"></i>',
  1032. 'align' => 'center'),
  1033. array('content' => '<img src="'.NAVIGATE_URL.'/img/icons/silk/cancel.png" style="cursor: pointer;" onclick="navigate_blocks_trigger_links_table_row_remove(this);" />', 'align' => 'center')
  1034. )
  1035. );
  1036. }
  1037. }
  1038. $uid = uniqid();
  1039. $table->addRow(
  1040. "trigger-links-table-row-model-".$lang,
  1041. array(
  1042. ( empty($links_icons)?
  1043. array('content' => '-', 'align' => 'center') :
  1044. array('content' => '<select name="trigger-links-table-icon-'.$lang.'['.$uid.']" data-select2-value="" data-role="icon" style="width: 190px;"></select>', 'align' => 'left')
  1045. ),
  1046. array('content' => '<input type="text" name="trigger-links-table-title-'.$lang.'['.$uid.']" value="" data-role="title" style="width: 250px;" />', 'align' => 'left'),
  1047. array('content' => '<input type="text" name="trigger-links-table-link-'.$lang.'['.$uid.']" value="" data-role="link" style="width: 260px;" />'.
  1048. '<a class="uibutton nv_block_nv_link_trigger"><i class="fa fa-sitemap"></i></a>',
  1049. 'align' => 'left'
  1050. ),
  1051. array('content' => '<input type="checkbox" name="trigger-links-table-new_window-'.$lang.'['.$uid.']" data-role="target" id="trigger-links-table-new_window-'.$lang.'['.$uid.']" value="1" />
  1052. <label for="trigger-links-table-new_window-'.$lang.'['.$uid.']" />',
  1053. 'align' => 'left'),
  1054. array('content' => '<input type="hidden" name="trigger-links-table-access-'.$lang.'['.$uid.']" data-role="access" id="trigger-links-table-access-'.$lang.'['.$uid.']" value="0" />
  1055. <i class="fa fa-fw fa-lg fa-eye" onclick="navigate_blocks_trigger_links_table_row_visibility(this);" data-value="0" for="trigger-links-table-access-'.$lang.'['.$uid.']"></i>
  1056. <i class="fa fa-fw fa-lg fa-eye-slash hidden" onclick="navigate_blocks_trigger_links_table_row_visibility(this);" data-value="1" for="trigger-links-table-access-'.$lang.'['.$uid.']"></i>',
  1057. 'align' => 'center'),
  1058. array('content' => '<img src="'.NAVIGATE_URL.'/img/icons/silk/cancel.png" style="cursor: pointer;" onclick="navigate_blocks_trigger_links_table_row_remove(this);" />', 'align' => 'center')
  1059. )
  1060. );
  1061. $navibars->add_tab_content_row(
  1062. array(
  1063. '<label>'.t(549, "Links").'</label>',
  1064. '<div id="trigger-links-'.$lang.'">'.$table->generate().'</div>',
  1065. '<label>&nbsp;</label>',
  1066. '<button id="trigger-links-table-add-'.$lang.'" data-lang="'.$lang.'"><img src="img/icons/silk/add.png" align="absmiddle" style="cursor:pointer;" /> '.t(472, 'Add').'</button>'
  1067. )
  1068. );
  1069. $navibars->add_tab_content_row(

Large files files are truncated, but you can click here to view the full file