PageRenderTime 86ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/symphony/content/content.publish.php

https://github.com/nils-werner/symphony-2
PHP | 1408 lines | 871 code | 231 blank | 306 comment | 180 complexity | b55f9c273a75936b30feedeb3140ea2f MD5 | raw file
  1. <?php
  2. /**
  3. * @package content
  4. */
  5. /**
  6. * The Publish page is where the majority of an Authors time will
  7. * be spent in Symphony with adding, editing and removing entries
  8. * from Sections. This Page controls the entries table as well as
  9. * the Entry creation screens.
  10. */
  11. require_once(TOOLKIT . '/class.administrationpage.php');
  12. require_once(TOOLKIT . '/class.entrymanager.php');
  13. require_once(TOOLKIT . '/class.sectionmanager.php');
  14. require_once(CONTENT . '/class.sortable.php');
  15. Class contentPublish extends AdministrationPage{
  16. public $_errors = array();
  17. public function sort(&$sort, &$order, $params) {
  18. $section = $params['current-section'];
  19. // If `?unsort` is appended to the URL, then sorting information are reverted
  20. // to their defaults
  21. if($params['unsort']) {
  22. $section->setSortingField($section->getDefaultSortingField(), false);
  23. $section->setSortingOrder('asc');
  24. redirect(Administration::instance()->getCurrentPageURL());
  25. }
  26. // By default, sorting information are retrieved from
  27. // the filesystem and stored inside the `Configuration` object
  28. if(is_null($sort) && is_null($order)) {
  29. $sort = $section->getSortingField();
  30. $order = $section->getSortingOrder();
  31. // Set the sorting in the `EntryManager` for subsequent use
  32. EntryManager::setFetchSorting($sort, $order);
  33. }
  34. else {
  35. // Ensure that this field is infact sortable, otherwise
  36. // fallback to IDs
  37. if(($field = FieldManager::fetch($sort)) instanceof Field && !$field->isSortable()) {
  38. $sort = $section->getDefaultSortingField();
  39. }
  40. // If the sort order or direction differs from what is saved,
  41. // update the config file and reload the page
  42. if($sort != $section->getSortingField() || $order != $section->getSortingOrder()){
  43. $section->setSortingField($sort, false);
  44. $section->setSortingOrder($order);
  45. redirect(Administration::instance()->getCurrentPageURL() . $params['filters']);
  46. }
  47. }
  48. }
  49. public function action(){
  50. $this->__switchboard('action');
  51. }
  52. public function __switchboard($type='view'){
  53. $function = ($type == 'action' ? '__action' : '__view') . ucfirst($this->_context['page']);
  54. if(!method_exists($this, $function)) {
  55. // If there is no action function, just return without doing anything
  56. if($type == 'action') return;
  57. Administration::instance()->errorPageNotFound();
  58. }
  59. $this->$function();
  60. }
  61. public function view(){
  62. $this->__switchboard();
  63. }
  64. public function __viewIndex(){
  65. if(!$section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle'])) {
  66. Administration::instance()->throwCustomError(
  67. __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
  68. __('Unknown Section'),
  69. Page::HTTP_STATUS_NOT_FOUND
  70. );
  71. }
  72. $section = SectionManager::fetch($section_id);
  73. $this->setPageType('table');
  74. $this->setTitle(__('%1$s &ndash; %2$s', array($section->get('name'), __('Symphony'))));
  75. $filters = array();
  76. $filter_querystring = $prepopulate_querystring = $where = $joins = NULL;
  77. $current_page = (isset($_REQUEST['pg']) && is_numeric($_REQUEST['pg']) ? max(1, intval($_REQUEST['pg'])) : 1);
  78. if(isset($_REQUEST['filter'])) {
  79. // legacy implementation, convert single filter to an array
  80. // split string in the form ?filter=handle:value
  81. if(!is_array($_REQUEST['filter'])) {
  82. list($field_handle, $filter_value) = explode(':', $_REQUEST['filter'], 2);
  83. $filters[$field_handle] = rawurldecode($filter_value);
  84. } else {
  85. $filters = $_REQUEST['filter'];
  86. }
  87. foreach($filters as $handle => $value) {
  88. $field_id = FieldManager::fetchFieldIDFromElementName(
  89. Symphony::Database()->cleanValue($handle),
  90. $section->get('id')
  91. );
  92. $field = FieldManager::fetch($field_id);
  93. if($field instanceof Field) {
  94. // For deprecated reasons, call the old, typo'd function name until the switch to the
  95. // properly named buildDSRetrievalSQL function.
  96. $field->buildDSRetrivalSQL(array($value), $joins, $where, false);
  97. $filter_querystring .= sprintf("filter[%s]=%s&amp;", $handle, rawurlencode($value));
  98. $prepopulate_querystring .= sprintf("prepopulate[%d]=%s&amp;", $field_id, rawurlencode($value));
  99. }
  100. else {
  101. unset($filters[$handle]);
  102. }
  103. }
  104. $filter_querystring = preg_replace("/&amp;$/", '', $filter_querystring);
  105. $prepopulate_querystring = preg_replace("/&amp;$/", '', $prepopulate_querystring);
  106. }
  107. Sortable::initialize($this, $entries, $sort, $order, array(
  108. 'current-section' => $section,
  109. 'filters' => ($filter_querystring ? "&amp;" . $filter_querystring : ''),
  110. 'unsort' => isset($_REQUEST['unsort'])
  111. ));
  112. $this->Form->setAttribute('action', Administration::instance()->getCurrentPageURL(). '?pg=' . $current_page.($filter_querystring ? "&amp;" . $filter_querystring : ''));
  113. $subheading_buttons = array(
  114. Widget::Anchor(__('Create New'), Administration::instance()->getCurrentPageURL().'new/'.($prepopulate_querystring ? '?' . $prepopulate_querystring : ''), __('Create a new entry'), 'create button', NULL, array('accesskey' => 'c'))
  115. );
  116. // Only show the Edit Section button if the Author is a developer. #938 ^BA
  117. if(Administration::instance()->Author->isDeveloper()) {
  118. array_unshift($subheading_buttons, Widget::Anchor(__('Edit Section'), SYMPHONY_URL . '/blueprints/sections/edit/' . $section_id . '/', __('Edit Section Configuration'), 'button'));
  119. }
  120. $this->appendSubheading($section->get('name'), $subheading_buttons);
  121. /**
  122. * Allows adjustments to be made to the SQL where and joins statements
  123. * before they are used to fetch the entries for the page
  124. *
  125. * @delegate AdjustPublishFiltering
  126. * @since Symphony 2.3.3
  127. * @param string $context
  128. * '/publish/'
  129. * @param integer $section_id
  130. * An array of the current columns, passed by reference
  131. * @param string $where
  132. * The current where statement, or null if not set
  133. * @param string $joins
  134. */
  135. Symphony::ExtensionManager()->notifyMembers('AdjustPublishFiltering', '/publish/', array('section-id' => $section_id, 'where' => &$where, 'joins' => &$joins));
  136. // Check that the filtered query fails that the filter is dropped and an
  137. // error is logged. #841 ^BA
  138. try {
  139. $entries = EntryManager::fetchByPage($current_page, $section_id, Symphony::Configuration()->get('pagination_maximum_rows', 'symphony'), $where, $joins);
  140. }
  141. catch(DatabaseException $ex) {
  142. $this->pageAlert(__('An error occurred while retrieving filtered entries. Showing all entries instead.'), Alert::ERROR);
  143. $filter_querystring = null;
  144. Symphony::Log()->pushToLog(sprintf(
  145. '%s - %s%s%s',
  146. $section->get('name') . ' Publish Index',
  147. $ex->getMessage(),
  148. ($ex->getFile() ? " in file " . $ex->getFile() : null),
  149. ($ex->getLine() ? " on line " . $ex->getLine() : null)
  150. ),
  151. E_NOTICE, true
  152. );
  153. $entries = EntryManager::fetchByPage($current_page, $section_id, Symphony::Configuration()->get('pagination_maximum_rows', 'symphony'));
  154. }
  155. $visible_columns = $section->fetchVisibleColumns();
  156. $columns = array();
  157. if(is_array($visible_columns) && !empty($visible_columns)){
  158. foreach($visible_columns as $column){
  159. $columns[] = array(
  160. 'label' => $column->get('label'),
  161. 'sortable' => $column->isSortable(),
  162. 'handle' => $column->get('id'),
  163. 'attrs' => array(
  164. 'id' => 'field-' . $column->get('id'),
  165. 'class' => 'field-' . $column->get('type')
  166. )
  167. );
  168. }
  169. }
  170. else {
  171. $columns[] = array(
  172. 'label' => __('ID'),
  173. 'sortable' => true,
  174. 'handle' => 'id'
  175. );
  176. }
  177. $aTableHead = Sortable::buildTableHeaders(
  178. $columns, $sort, $order,
  179. ($filter_querystring) ? "&amp;" . $filter_querystring : ''
  180. );
  181. $child_sections = array();
  182. $associated_sections = $section->fetchAssociatedSections(true);
  183. if(is_array($associated_sections) && !empty($associated_sections)){
  184. foreach($associated_sections as $key => $as){
  185. $child_sections[$key] = SectionManager::fetch($as['child_section_id']);
  186. $aTableHead[] = array($child_sections[$key]->get('name'), 'col');
  187. }
  188. }
  189. /**
  190. * Allows the creation of custom table columns for each entry. Called
  191. * after all the Section Visible columns have been added as well
  192. * as the Section Associations
  193. *
  194. * @delegate AddCustomPublishColumn
  195. * @since Symphony 2.2
  196. * @param string $context
  197. * '/publish/'
  198. * @param array $tableHead
  199. * An array of the current columns, passed by reference
  200. * @param integer $section_id
  201. * The current Section ID
  202. */
  203. Symphony::ExtensionManager()->notifyMembers('AddCustomPublishColumn', '/publish/', array('tableHead' => &$aTableHead, 'section_id' => $section->get('id')));
  204. // Table Body
  205. $aTableBody = array();
  206. if(!is_array($entries['records']) || empty($entries['records'])){
  207. $aTableBody = array(
  208. Widget::TableRow(array(Widget::TableData(__('None found.'), 'inactive', NULL, count($aTableHead))), 'odd')
  209. );
  210. }
  211. else {
  212. $field_pool = array();
  213. if(is_array($visible_columns) && !empty($visible_columns)){
  214. foreach($visible_columns as $column){
  215. $field_pool[$column->get('id')] = $column;
  216. }
  217. }
  218. $link_column = array_reverse($visible_columns);
  219. $link_column = end($link_column);
  220. reset($visible_columns);
  221. foreach($entries['records'] as $entry) {
  222. $tableData = array();
  223. // Setup each cell
  224. if(!is_array($visible_columns) || empty($visible_columns)) {
  225. $tableData[] = Widget::TableData(Widget::Anchor($entry->get('id'), Administration::instance()->getCurrentPageURL() . 'edit/' . $entry->get('id') . '/'));
  226. }
  227. else {
  228. $link = Widget::Anchor(
  229. __('None'),
  230. Administration::instance()->getCurrentPageURL() . 'edit/' . $entry->get('id') . '/'.($filter_querystring ? '?' . $prepopulate_querystring : ''),
  231. $entry->get('id'),
  232. 'content'
  233. );
  234. foreach ($visible_columns as $position => $column) {
  235. $data = $entry->getData($column->get('id'));
  236. $field = $field_pool[$column->get('id')];
  237. $value = $field->prepareTableValue($data, ($column == $link_column) ? $link : null, $entry->get('id'));
  238. if (!is_object($value) && (strlen(trim($value)) == 0 || $value == __('None'))) {
  239. $value = ($position == 0 ? $link->generate() : __('None'));
  240. }
  241. if ($value == __('None')) {
  242. $tableData[] = Widget::TableData($value, 'inactive field-' . $column->get('type') . ' field-' . $column->get('id'));
  243. }
  244. else {
  245. $tableData[] = Widget::TableData($value, 'field-' . $column->get('type') . ' field-' . $column->get('id'));
  246. }
  247. unset($field);
  248. }
  249. }
  250. if(is_array($child_sections) && !empty($child_sections)){
  251. foreach($child_sections as $key => $as){
  252. $field = FieldManager::fetch((int)$associated_sections[$key]['child_section_field_id']);
  253. $parent_section_field_id = (int)$associated_sections[$key]['parent_section_field_id'];
  254. if(!is_null($parent_section_field_id)){
  255. $search_value = $field->fetchAssociatedEntrySearchValue(
  256. $entry->getData($parent_section_field_id),
  257. $parent_section_field_id,
  258. $entry->get('id')
  259. );
  260. }
  261. else {
  262. $search_value = $entry->get('id');
  263. }
  264. if(!is_array($search_value)) {
  265. $associated_entry_count = $field->fetchAssociatedEntryCount($search_value);
  266. $tableData[] = Widget::TableData(
  267. Widget::Anchor(
  268. sprintf('%d &rarr;', max(0, intval($associated_entry_count))),
  269. sprintf(
  270. '%s/publish/%s/?filter[%s]=%s',
  271. SYMPHONY_URL,
  272. $as->get('handle'),
  273. $field->get('element_name'),
  274. rawurlencode($search_value)
  275. ),
  276. $entry->get('id'),
  277. 'content')
  278. );
  279. }
  280. }
  281. }
  282. /**
  283. * Allows Extensions to inject custom table data for each Entry
  284. * into the Publish Index
  285. *
  286. * @delegate AddCustomPublishColumnData
  287. * @since Symphony 2.2
  288. * @param string $context
  289. * '/publish/'
  290. * @param array $tableData
  291. * An array of `Widget::TableData`, passed by reference
  292. * @param integer $section_id
  293. * The current Section ID
  294. * @param Entry $entry_id
  295. * The entry object, please note that this is by error and this will
  296. * be removed in Symphony 2.4. The entry object is available in
  297. * the 'entry' key as of Symphony 2.3.1.
  298. * @param Entry $entry
  299. * The entry object for this row
  300. */
  301. Symphony::ExtensionManager()->notifyMembers('AddCustomPublishColumnData', '/publish/', array(
  302. 'tableData' => &$tableData,
  303. 'section_id' => $section->get('id'),
  304. 'entry_id' => $entry,
  305. 'entry' => $entry
  306. ));
  307. $tableData[count($tableData) - 1]->appendChild(Widget::Input('items['.$entry->get('id').']', NULL, 'checkbox'));
  308. // Add a row to the body array, assigning each cell to the row
  309. $aTableBody[] = Widget::TableRow($tableData, NULL, 'id-' . $entry->get('id'));
  310. }
  311. }
  312. $table = Widget::Table(
  313. Widget::TableHead($aTableHead),
  314. NULL,
  315. Widget::TableBody($aTableBody),
  316. 'selectable'
  317. );
  318. $this->Form->appendChild($table);
  319. $tableActions = new XMLElement('div');
  320. $tableActions->setAttribute('class', 'actions');
  321. $options = array(
  322. array(NULL, false, __('With Selected...')),
  323. array('delete', false, __('Delete'), 'confirm', null, array(
  324. 'data-message' => __('Are you sure you want to delete the selected entries?')
  325. ))
  326. );
  327. $toggable_fields = $section->fetchToggleableFields();
  328. if (is_array($toggable_fields) && !empty($toggable_fields)) {
  329. $index = 2;
  330. foreach ($toggable_fields as $field) {
  331. $toggle_states = $field->getToggleStates();
  332. if (is_array($toggle_states)) {
  333. $options[$index] = array('label' => __('Set %s', array($field->get('label'))), 'options' => array());
  334. foreach ($toggle_states as $value => $state) {
  335. $options[$index]['options'][] = array('toggle-' . $field->get('id') . '-' . $value, false, $state);
  336. }
  337. }
  338. $index++;
  339. }
  340. }
  341. /**
  342. * Allows an extension to modify the existing options for this page's
  343. * With Selected menu. If the `$options` parameter is an empty array,
  344. * the 'With Selected' menu will not be rendered.
  345. *
  346. * @delegate AddCustomActions
  347. * @since Symphony 2.3.2
  348. * @param string $context
  349. * '/publish/'
  350. * @param array $options
  351. * An array of arrays, where each child array represents an option
  352. * in the With Selected menu. Options should follow the same format
  353. * expected by `Widget::__SelectBuildOption`. Passed by reference.
  354. */
  355. Symphony::ExtensionManager()->notifyMembers('AddCustomActions', '/publish/', array(
  356. 'options' => &$options
  357. ));
  358. if(!empty($options)) {
  359. $tableActions->appendChild(Widget::Apply($options));
  360. $this->Form->appendChild($tableActions);
  361. }
  362. if($entries['total-pages'] > 1){
  363. $ul = new XMLElement('ul');
  364. $ul->setAttribute('class', 'page');
  365. // First
  366. $li = new XMLElement('li');
  367. if($current_page > 1) $li->appendChild(Widget::Anchor(__('First'), Administration::instance()->getCurrentPageURL(). '?pg=1'.($filter_querystring ? "&amp;" . $filter_querystring : '')));
  368. else $li->setValue(__('First'));
  369. $ul->appendChild($li);
  370. // Previous
  371. $li = new XMLElement('li');
  372. if($current_page > 1) $li->appendChild(Widget::Anchor(__('&larr; Previous'), Administration::instance()->getCurrentPageURL(). '?pg=' . ($current_page - 1).($filter_querystring ? "&amp;" . $filter_querystring : '')));
  373. else $li->setValue(__('&larr; Previous'));
  374. $ul->appendChild($li);
  375. // Summary
  376. $li = new XMLElement('li');
  377. $li->setAttribute('title', __('Viewing %1$s - %2$s of %3$s entries', array(
  378. $entries['start'],
  379. ($current_page != $entries['total-pages']) ? $current_page * Symphony::Configuration()->get('pagination_maximum_rows', 'symphony') : $entries['total-entries'],
  380. $entries['total-entries']
  381. )));
  382. $pgform = Widget::Form(Administration::instance()->getCurrentPageURL(),'get','paginationform');
  383. $pgmax = max($current_page, $entries['total-pages']);
  384. $pgform->appendChild(Widget::Input('pg', NULL, 'text', array(
  385. 'data-active' => __('Go to page …'),
  386. 'data-inactive' => __('Page %1$s of %2$s', array((string)$current_page, $pgmax)),
  387. 'data-max' => $pgmax
  388. )));
  389. $li->appendChild($pgform);
  390. $ul->appendChild($li);
  391. // Next
  392. $li = new XMLElement('li');
  393. if($current_page < $entries['total-pages']) $li->appendChild(Widget::Anchor(__('Next &rarr;'), Administration::instance()->getCurrentPageURL(). '?pg=' . ($current_page + 1).($filter_querystring ? "&amp;" . $filter_querystring : '')));
  394. else $li->setValue(__('Next &rarr;'));
  395. $ul->appendChild($li);
  396. // Last
  397. $li = new XMLElement('li');
  398. if($current_page < $entries['total-pages']) $li->appendChild(Widget::Anchor(__('Last'), Administration::instance()->getCurrentPageURL(). '?pg=' . $entries['total-pages'].($filter_querystring ? "&amp;" . $filter_querystring : '')));
  399. else $li->setValue(__('Last'));
  400. $ul->appendChild($li);
  401. $this->Contents->appendChild($ul);
  402. }
  403. }
  404. public function __actionIndex() {
  405. $checked = (is_array($_POST['items'])) ? array_keys($_POST['items']) : null;
  406. if(is_array($checked) && !empty($checked)){
  407. /**
  408. * Extensions can listen for any custom actions that were added
  409. * through `AddCustomPreferenceFieldsets` or `AddCustomActions`
  410. * delegates.
  411. *
  412. * @delegate CustomActions
  413. * @since Symphony 2.3.2
  414. * @param string $context
  415. * '/publish/'
  416. * @param array $checked
  417. * An array of the selected rows. The value is usually the ID of the
  418. * the associated object.
  419. */
  420. Symphony::ExtensionManager()->notifyMembers('CustomActions', '/publish/', array(
  421. 'checked' => $checked
  422. ));
  423. switch($_POST['with-selected']) {
  424. case 'delete':
  425. /**
  426. * Prior to deletion of entries. An array of Entry ID's is provided which
  427. * can be manipulated. This delegate was renamed from `Delete` to `EntryPreDelete`
  428. * in Symphony 2.3.
  429. *
  430. * @delegate EntryPreDelete
  431. * @param string $context
  432. * '/publish/'
  433. * @param array $entry_id
  434. * An array of Entry ID's passed by reference
  435. */
  436. Symphony::ExtensionManager()->notifyMembers('EntryPreDelete', '/publish/', array('entry_id' => &$checked));
  437. EntryManager::delete($checked);
  438. /**
  439. * After the deletion of entries, this delegate provides an array of Entry ID's
  440. * that were deleted.
  441. *
  442. * @since Symphony 2.3
  443. * @delegate EntryPostDelete
  444. * @param string $context
  445. * '/publish/'
  446. * @param array $entry_id
  447. * An array of Entry ID's that were deleted.
  448. */
  449. Symphony::ExtensionManager()->notifyMembers('EntryPostDelete', '/publish/', array('entry_id' => $checked));
  450. redirect($_SERVER['REQUEST_URI']);
  451. default:
  452. list($option, $field_id, $value) = explode('-', $_POST['with-selected'], 3);
  453. if($option == 'toggle'){
  454. $field = FieldManager::fetch($field_id);
  455. $fields = array($field->get('element_name') => $value);
  456. $section = SectionManager::fetch($field->get('parent_section'));
  457. foreach($checked as $entry_id){
  458. $entry = EntryManager::fetch($entry_id);
  459. $existing_data = $entry[0]->getData($field_id);
  460. $entry[0]->setData($field_id, $field->toggleFieldData(is_array($existing_data) ? $existing_data : array(), $value, $entry_id));
  461. /**
  462. * Just prior to editing of an Entry
  463. *
  464. * @delegate EntryPreEdit
  465. * @param string $context
  466. * '/publish/edit/'
  467. * @param Section $section
  468. * @param Entry $entry
  469. * @param array $fields
  470. */
  471. Symphony::ExtensionManager()->notifyMembers('EntryPreEdit', '/publish/edit/', array(
  472. 'section' => $section,
  473. 'entry' => &$entry[0],
  474. 'fields' => $fields
  475. ));
  476. $entry[0]->commit();
  477. /**
  478. * Editing an entry. Entry object is provided.
  479. *
  480. * @delegate EntryPostEdit
  481. * @param string $context
  482. * '/publish/edit/'
  483. * @param Section $section
  484. * @param Entry $entry
  485. * @param array $fields
  486. */
  487. Symphony::ExtensionManager()->notifyMembers('EntryPostEdit', '/publish/edit/', array(
  488. 'section' => $section,
  489. 'entry' => $entry[0],
  490. 'fields' => $fields
  491. ));
  492. }
  493. redirect($_SERVER['REQUEST_URI']);
  494. }
  495. break;
  496. }
  497. }
  498. }
  499. public function __viewNew() {
  500. if(!$section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle'])) {
  501. Administration::instance()->throwCustomError(
  502. __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
  503. __('Unknown Section'),
  504. Page::HTTP_STATUS_NOT_FOUND
  505. );
  506. }
  507. $section = SectionManager::fetch($section_id);
  508. $this->setPageType('form');
  509. $this->Form->setAttribute('enctype', 'multipart/form-data');
  510. $this->Form->setAttribute('class', 'two columns');
  511. $this->setTitle(__('%1$s &ndash; %2$s', array($section->get('name'), __('Symphony'))));
  512. // Only show the Edit Section button if the Author is a developer. #938 ^BA
  513. if(Administration::instance()->Author->isDeveloper()) {
  514. $this->appendSubheading(__('Untitled'),
  515. Widget::Anchor(__('Edit Section'), SYMPHONY_URL . '/blueprints/sections/edit/' . $section_id . '/', __('Edit Section Configuration'), 'button')
  516. );
  517. }
  518. else {
  519. $this->appendSubheading(__('Untitled'));
  520. }
  521. // Build filtered breadcrumb [#1378}
  522. if(isset($_REQUEST['prepopulate'])){
  523. $link = '?';
  524. foreach($_REQUEST['prepopulate'] as $field_id => $value) {
  525. $handle = FieldManager::fetchHandleFromID($field_id);
  526. $link .= "filter[$handle]=$value&amp;";
  527. }
  528. $link = preg_replace("/&amp;$/", '', $link);
  529. }
  530. else {
  531. $link = '';
  532. }
  533. $this->insertBreadcrumbs(array(
  534. Widget::Anchor($section->get('name'), SYMPHONY_URL . '/publish/' . $this->_context['section_handle'] . '/' . $link),
  535. ));
  536. $this->Form->appendChild(Widget::Input('MAX_FILE_SIZE', Symphony::Configuration()->get('max_upload_size', 'admin'), 'hidden'));
  537. // If there is post data floating around, due to errors, create an entry object
  538. if (isset($_POST['fields'])) {
  539. $entry = EntryManager::create();
  540. $entry->set('section_id', $section_id);
  541. $entry->setDataFromPost($_POST['fields'], $error, true);
  542. }
  543. // Brand new entry, so need to create some various objects
  544. else {
  545. $entry = EntryManager::create();
  546. $entry->set('section_id', $section_id);
  547. }
  548. // Check if there is a field to prepopulate
  549. if (isset($_REQUEST['prepopulate'])) {
  550. foreach($_REQUEST['prepopulate'] as $field_id => $value) {
  551. $this->Form->prependChild(Widget::Input(
  552. "prepopulate[{$field_id}]",
  553. rawurlencode($value),
  554. 'hidden'
  555. ));
  556. // The actual pre-populating should only happen if there is not existing fields post data
  557. if(!isset($_POST['fields']) && $field = FieldManager::fetch($field_id)) {
  558. $entry->setData(
  559. $field->get('id'),
  560. $field->processRawFieldData($value, $error, $message, true)
  561. );
  562. }
  563. }
  564. }
  565. $primary = new XMLElement('fieldset');
  566. $primary->setAttribute('class', 'primary column');
  567. $sidebar_fields = $section->fetchFields(NULL, 'sidebar');
  568. $main_fields = $section->fetchFields(NULL, 'main');
  569. if ((!is_array($main_fields) || empty($main_fields)) && (!is_array($sidebar_fields) || empty($sidebar_fields))) {
  570. $message = __('Fields must be added to this section before an entry can be created.');
  571. if(Administration::instance()->Author->isDeveloper()) {
  572. $message .= ' <a href="' . SYMPHONY_URL . '/blueprints/sections/edit/' . $section->get('id') . '/" accesskey="c">'
  573. . __('Add fields')
  574. . '</a>';
  575. }
  576. $this->pageAlert($message, Alert::ERROR);
  577. }
  578. else {
  579. if (is_array($main_fields) && !empty($main_fields)) {
  580. foreach ($main_fields as $field) {
  581. $primary->appendChild($this->__wrapFieldWithDiv($field, $entry));
  582. }
  583. $this->Form->appendChild($primary);
  584. }
  585. if (is_array($sidebar_fields) && !empty($sidebar_fields)) {
  586. $sidebar = new XMLElement('fieldset');
  587. $sidebar->setAttribute('class', 'secondary column');
  588. foreach ($sidebar_fields as $field) {
  589. $sidebar->appendChild($this->__wrapFieldWithDiv($field, $entry));
  590. }
  591. $this->Form->appendChild($sidebar);
  592. }
  593. $div = new XMLElement('div');
  594. $div->setAttribute('class', 'actions');
  595. $div->appendChild(Widget::Input('action[save]', __('Create Entry'), 'submit', array('accesskey' => 's')));
  596. $this->Form->appendChild($div);
  597. // Create a Drawer for Associated Sections
  598. $this->prepareAssociationsDrawer($section);
  599. }
  600. }
  601. public function __actionNew(){
  602. if(array_key_exists('save', $_POST['action']) || array_key_exists("done", $_POST['action'])) {
  603. $section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle']);
  604. if(!$section = SectionManager::fetch($section_id)) {
  605. Administration::instance()->throwCustomError(
  606. __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
  607. __('Unknown Section'),
  608. Page::HTTP_STATUS_NOT_FOUND
  609. );
  610. }
  611. $entry = EntryManager::create();
  612. $entry->set('author_id', Administration::instance()->Author->get('id'));
  613. $entry->set('section_id', $section_id);
  614. $entry->set('creation_date', DateTimeObj::get('c'));
  615. $entry->set('modification_date', DateTimeObj::get('c'));
  616. $fields = $_POST['fields'];
  617. // Combine FILES and POST arrays, indexed by their custom field handles
  618. if(isset($_FILES['fields'])){
  619. $filedata = General::processFilePostData($_FILES['fields']);
  620. foreach($filedata as $handle => $data){
  621. if(!isset($fields[$handle])) $fields[$handle] = $data;
  622. elseif(isset($data['error']) && $data['error'] == UPLOAD_ERR_NO_FILE) $fields[$handle] = NULL;
  623. else{
  624. foreach($data as $ii => $d){
  625. if(isset($d['error']) && $d['error'] == UPLOAD_ERR_NO_FILE) $fields[$handle][$ii] = NULL;
  626. elseif(is_array($d) && !empty($d)){
  627. foreach($d as $key => $val)
  628. $fields[$handle][$ii][$key] = $val;
  629. }
  630. }
  631. }
  632. }
  633. }
  634. // Initial checks to see if the Entry is ok
  635. if(__ENTRY_FIELD_ERROR__ == $entry->checkPostData($fields, $this->_errors)) {
  636. $this->pageAlert(__('Some errors were encountered while attempting to save.'), Alert::ERROR);
  637. }
  638. // Secondary checks, this will actually process the data and attempt to save
  639. else if(__ENTRY_OK__ != $entry->setDataFromPost($fields, $errors)) {
  640. foreach($errors as $field_id => $message) {
  641. $this->pageAlert($message, Alert::ERROR);
  642. }
  643. }
  644. // Everything is awesome. Dance.
  645. else {
  646. /**
  647. * Just prior to creation of an Entry
  648. *
  649. * @delegate EntryPreCreate
  650. * @param string $context
  651. * '/publish/new/'
  652. * @param Section $section
  653. * @param Entry $entry
  654. * @param array $fields
  655. */
  656. Symphony::ExtensionManager()->notifyMembers('EntryPreCreate', '/publish/new/', array('section' => $section, 'entry' => &$entry, 'fields' => &$fields));
  657. // Check to see if the dancing was premature
  658. if(!$entry->commit()){
  659. define_safe('__SYM_DB_INSERT_FAILED__', true);
  660. $this->pageAlert(NULL, Alert::ERROR);
  661. }
  662. else {
  663. /**
  664. * Creation of an Entry. New Entry object is provided.
  665. *
  666. * @delegate EntryPostCreate
  667. * @param string $context
  668. * '/publish/new/'
  669. * @param Section $section
  670. * @param Entry $entry
  671. * @param array $fields
  672. */
  673. Symphony::ExtensionManager()->notifyMembers('EntryPostCreate', '/publish/new/', array('section' => $section, 'entry' => $entry, 'fields' => $fields));
  674. $prepopulate_querystring = '';
  675. if(isset($_POST['prepopulate'])){
  676. foreach($_POST['prepopulate'] as $field_id => $value) {
  677. $prepopulate_querystring .= sprintf("prepopulate[%s]=%s&", $field_id, rawurldecode($value));
  678. }
  679. $prepopulate_querystring = trim($prepopulate_querystring, '&');
  680. }
  681. redirect(sprintf(
  682. '%s/publish/%s/edit/%d/created/%s',
  683. SYMPHONY_URL,
  684. $this->_context['section_handle'],
  685. $entry->get('id'),
  686. (!empty($prepopulate_querystring) ? "?" . $prepopulate_querystring : NULL)
  687. ));
  688. }
  689. }
  690. }
  691. }
  692. public function __viewEdit() {
  693. if(!$section_id = SectionManager::fetchIDFromHandle($this->_context['section_handle'])) {
  694. Administration::instance()->throwCustomError(
  695. __('The Section, %s, could not be found.', array('<code>' . $this->_context['section_handle'] . '</code>')),
  696. __('Unknown Section'),
  697. Page::HTTP_STATUS_NOT_FOUND
  698. );
  699. }
  700. $section = SectionManager::fetch($section_id);
  701. $entry_id = intval($this->_context['entry_id']);
  702. $base = '/publish/'.$this->_context['section_handle'] . '/';
  703. $new_link = $base . 'new/';
  704. $filter_link = $base;
  705. EntryManager::setFetchSorting('id', 'DESC');
  706. if(!$existingEntry = EntryManager::fetch($entry_id)) {
  707. Administration::instance()->throwCustomError(
  708. __('Unknown Entry'),
  709. __('The Entry, %s, could not be found.', array($entry_id)),
  710. Page::HTTP_STATUS_NOT_FOUND
  711. );
  712. }
  713. $existingEntry = $existingEntry[0];
  714. // If there is post data floating around, due to errors, create an entry object
  715. if (isset($_POST['fields'])) {
  716. $fields = $_POST['fields'];
  717. $entry = EntryManager::create();
  718. $entry->set('id', $entry_id);
  719. $entry->set('author_id', $existingEntry->get('author_id'));
  720. $entry->set('section_id', $existingEntry->get('section_id'));
  721. $entry->set('creation_date', $existingEntry->get('creation_date'));
  722. $entry->set('modification_date', $existingEntry->get('modification_date'));
  723. $entry->setDataFromPost($fields, $errors, true);
  724. }
  725. // Editing an entry, so need to create some various objects
  726. else {
  727. $entry = $existingEntry;
  728. $fields = array();
  729. if (!$section) {
  730. $section = SectionManager::fetch($entry->get('section_id'));
  731. }
  732. }
  733. /**
  734. * Just prior to rendering of an Entry edit form.
  735. *
  736. * @delegate EntryPreRender
  737. * @param string $context
  738. * '/publish/edit/'
  739. * @param Section $section
  740. * @param Entry $entry
  741. * @param array $fields
  742. */
  743. Symphony::ExtensionManager()->notifyMembers('EntryPreRender', '/publish/edit/', array(
  744. 'section' => $section,
  745. 'entry' => &$entry,
  746. 'fields' => $fields
  747. ));
  748. // Iterate over the `prepopulate` parameters to build a URL
  749. // to remember this state for Create New, View all Entries and
  750. // Breadcrumb links. If `prepopulate` doesn't exist, this will
  751. // just use the standard pages (ie. no filtering)
  752. if(isset($_REQUEST['prepopulate'])){
  753. $new_link .= '?';
  754. $filter_link .= '?';
  755. foreach($_REQUEST['prepopulate'] as $field_id => $value) {
  756. $new_link .= "prepopulate[$field_id]=$value&amp;";
  757. $field_name = FieldManager::fetchHandleFromID($field_id);
  758. $filter_link .= "filter[$field_name]=$value&amp;";
  759. }
  760. $new_link = preg_replace("/&amp;$/", '', $new_link);
  761. $filter_link = preg_replace("/&amp;$/", '', $filter_link);
  762. }
  763. if(isset($this->_context['flag'])) {
  764. // These flags are only relevant if there are no errors
  765. if(empty($this->_errors)) {
  766. switch($this->_context['flag']) {
  767. case 'saved':
  768. $this->pageAlert(
  769. __('Entry updated at %s.', array(DateTimeObj::getTimeAgo()))
  770. . ' <a href="' . SYMPHONY_URL . $new_link . '" accesskey="c">'
  771. . __('Create another?')
  772. . '</a> <a href="' . SYMPHONY_URL . $filter_link . '" accesskey="a">'
  773. . __('View all Entries')
  774. . '</a>'
  775. , Alert::SUCCESS);
  776. break;
  777. case 'created':
  778. $this->pageAlert(
  779. __('Entry created at %s.', array(DateTimeObj::getTimeAgo()))
  780. . ' <a href="' . SYMPHONY_URL . $new_link . '" accesskey="c">'
  781. . __('Create another?')
  782. . '</a> <a href="' . SYMPHONY_URL . $filter_link . '" accesskey="a">'
  783. . __('View all Entries')
  784. . '</a>'
  785. , Alert::SUCCESS);
  786. break;
  787. }
  788. }
  789. }
  790. // Determine the page title
  791. $field_id = Symphony::Database()->fetchVar('id', 0, "SELECT `id` FROM `tbl_fields` WHERE `parent_section` = '".$section->get('id')."' ORDER BY `sortorder` LIMIT 1");
  792. if(!is_null($field_id)) {
  793. $field = FieldManager::fetch($field_id);
  794. }
  795. if($field) {
  796. $title = trim(strip_tags($field->prepareTableValue($existingEntry->getData($field->get('id')), NULL, $entry_id)));
  797. }
  798. else {
  799. $title = '';
  800. }
  801. if (trim($title) == '') {
  802. $title = __('Untitled');
  803. }
  804. // Check if there is a field to prepopulate
  805. if (isset($_REQUEST['prepopulate'])) {
  806. foreach($_REQUEST['prepopulate'] as $field_id => $value) {
  807. $this->Form->prependChild(Widget::Input(
  808. "prepopulate[{$field_id}]",
  809. rawurlencode($value),
  810. 'hidden'
  811. ));
  812. }
  813. }
  814. $this->setPageType('form');
  815. $this->Form->setAttribute('enctype', 'multipart/form-data');
  816. $this->Form->setAttribute('class', 'two columns');
  817. $this->setTitle(__('%1$s &ndash; %2$s &ndash; %3$s', array($title, $section->get('name'), __('Symphony'))));
  818. // Only show the Edit Section button if the Author is a developer. #938 ^BA
  819. if(Administration::instance()->Author->isDeveloper()) {
  820. $this->appendSubheading($title,
  821. Widget::Anchor(__('Edit Section'), SYMPHONY_URL . '/blueprints/sections/edit/' . $section_id . '/', __('Edit Section Configuration'), 'button')
  822. );
  823. }
  824. else {
  825. $this->appendSubheading($title);
  826. }
  827. $this->insertBreadcrumbs(array(
  828. Widget::Anchor($section->get('name'), SYMPHONY_URL . (isset($filter_link) ? $filter_link : $base)),
  829. ));
  830. $this->Form->appendChild(Widget::Input('MAX_FILE_SIZE', Symphony::Configuration()->get('max_upload_size', 'admin'), 'hidden'));
  831. $primary = new XMLElement('fieldset');
  832. $primary->setAttribute('class', 'primary column');
  833. $sidebar_fields = $section->fetchFields(NULL, 'sidebar');
  834. $main_fields = $section->fetchFields(NULL, 'main');
  835. if((!is_array($main_fields) || empty($main_fields)) && (!is_array($sidebar_fields) || empty($sidebar_fields))){
  836. $message = __('Fields must be added to this section before an entry can be created.');
  837. if(Administration::instance()->Author->isDeveloper()) {
  838. $message .= ' <a href="' . SYMPHONY_URL . '/blueprints/sections/edit/' . $section->get('id') . '/" accesskey="c">'
  839. . __('Add fields')
  840. . '</a>';
  841. }
  842. $this->pageAlert($message, Alert::ERROR);
  843. }
  844. else {
  845. if(is_array($main_fields) && !empty($main_fields)){
  846. foreach($main_fields as $field){
  847. $primary->appendChild($this->__wrapFieldWithDiv($field, $entry));
  848. }
  849. $this->Form->appendChild($primary);
  850. }
  851. if(is_array($sidebar_fields) && !empty($sidebar_fields)){
  852. $sidebar = new XMLElement('fieldset');
  853. $sidebar->setAttribute('class', 'secondary column');
  854. foreach($sidebar_fields as $field){
  855. $sidebar->appendChild($this->__wrapFieldWithDiv($field, $entry));
  856. }
  857. $this->Form->appendChild($sidebar);
  858. }
  859. $div = new XMLElement('div');
  860. $div->setAttribute('class', 'actions');
  861. $div->appendChild(Widget::Input('action[save]', __('Save Changes'), 'submit', array('accesskey' => 's')));
  862. $button = new XMLElement('button', __('Delete'));
  863. $button->setAttributeArray(array('name' => 'action[delete]', 'class' => 'button confirm delete', 'title' => __('Delete this entry'), 'type' => 'submit', 'accesskey' => 'd', 'data-message' => __('Are you sure you want to delete this entry?')));
  864. $div->appendChild($button);
  865. $this->Form->appendChild($div);
  866. // Create a Drawer for Associated Sections
  867. $this->prepareAssociationsDrawer($section);
  868. }
  869. }
  870. public function __actionEdit(){
  871. $entry_id = intval($this->_context['entry_id']);
  872. if(@array_key_exists('save', $_POST['action']) || @array_key_exists("done", $_POST['action'])){
  873. if(!$ret = EntryManager::fetch($entry_id)) {
  874. Administration::instance()->throwCustomError(
  875. __('The Entry, %s, could not be found.', array($entry_id)),
  876. __('Unknown Entry'),
  877. Page::HTTP_STATUS_NOT_FOUND
  878. );
  879. }
  880. $entry = $ret[0];
  881. $section = SectionManager::fetch($entry->get('section_id'));
  882. $post = General::getPostData();
  883. $fields = $post['fields'];
  884. // Initial checks to see if the Entry is ok
  885. if(__ENTRY_FIELD_ERROR__ == $entry->checkPostData($fields, $this->_errors)) {
  886. $this->pageAlert(__('Some errors were encountered while attempting to save.'), Alert::ERROR);
  887. }
  888. // Secondary checks, this will actually process the data and attempt to save
  889. else if(__ENTRY_OK__ != $entry->setDataFromPost($fields, $errors)) {
  890. foreach($errors as $field_id => $message) {
  891. $this->pageAlert($message, Alert::ERROR);
  892. }
  893. }
  894. // Everything is awesome. Dance.
  895. else {
  896. /**
  897. * Just prior to editing of an Entry.
  898. *
  899. * @delegate EntryPreEdit
  900. * @param string $context
  901. * '/publish/edit/'
  902. * @param Section $section
  903. * @param Entry $entry
  904. * @param array $fields
  905. */
  906. Symphony::ExtensionManager()->notifyMembers('EntryPreEdit', '/publish/edit/', array('section' => $section, 'entry' => &$entry, 'fields' => $fields));
  907. // Check to see if the dancing was premature
  908. if(!$entry->commit()){
  909. define_safe('__SYM_DB_INSERT_FAILED__', true);
  910. $this->pageAlert(NULL, Alert::ERROR);
  911. }
  912. else {
  913. /**
  914. * Just after the editing of an Entry
  915. *
  916. * @delegate EntryPostEdit
  917. * @param string $context
  918. * '/publish/edit/'
  919. * @param Section $section
  920. * @param Entry $entry
  921. * @param array $fields
  922. */
  923. Symphony::ExtensionManager()->notifyMembers('EntryPostEdit', '/publish/edit/', array('section' => $section, 'entry' => $entry, 'fields' => $fields));
  924. $prepopulate_querystring = '';
  925. if(isset($_POST['prepopulate'])){
  926. foreach($_POST['prepopulate'] as $field_id => $value) {
  927. $prepopulate_querystring .= sprintf("prepopulate[%s]=%s&", $field_id, $value);
  928. }
  929. $prepopulate_querystring = trim($prepopulate_querystring, '&');
  930. }
  931. redirect(sprintf(
  932. '%s/publish/%s/edit/%d/saved/%s',
  933. SYMPHONY_URL,
  934. $this->_context['section_handle'],
  935. $entry->get('id'),
  936. (!empty($prepopulate_querystring) ? "?" . $prepopulate_querystring : NULL)
  937. ));
  938. }
  939. }
  940. }
  941. else if(@array_key_exists('delete', $_POST['action']) && is_numeric($entry_id)){
  942. /**
  943. * Prior to deletion of entries. An array of Entry ID's is provided which
  944. * can be manipulated. This delegate was renamed from `Delete` to `EntryPreDelete`
  945. * in Symphony 2.3.
  946. *
  947. * @delegate EntryPreDelete
  948. * @param string $context
  949. * '/publish/'
  950. * @param array $entry_id
  951. * An array of Entry ID's passed by reference
  952. */
  953. $checked = array($entry_id);
  954. Symphony::ExtensionManager()->notifyMembers('EntryPreDelete', '/publish/', array('entry_id' => &$checked));
  955. EntryManager::delete($checked);
  956. /**
  957. * After the deletion of entries, this delegate provides an array of Entry ID's
  958. * that were deleted.
  959. *
  960. * @since Symphony 2.3
  961. * @delegate EntryPostDelete
  962. * @param string $context
  963. * '/publish/'
  964. * @param array $entry_id
  965. * An array of Entry ID's that were deleted.
  966. */
  967. Symphony::ExtensionManager()->notifyMembers('EntryPostDelete', '/publish/', array('entry_id' => $checked));
  968. redirect(SYMPHONY_URL . '/publish/'.$this->_context['section_handle'].'/');
  969. }
  970. }
  971. /**
  972. * Given a Field and Entry object, this function will wrap
  973. * the Field's displayPublishPanel result with a div that
  974. * contains some contextual information such as the Field ID,
  975. * the Field handle and whether it is required or not.
  976. *
  977. * @param Field $field
  978. * @param Entry $entry
  979. * @return XMLElement
  980. */
  981. private function __wrapFieldWithDiv(Field $field, Entry $entry){
  982. $is_hidden = $this->isFieldHidden($field);
  983. $div = new XMLElement('div', NULL, array('id' => 'field-' . $field->get('id'), 'class' => 'field field-'.$field->handle().($field->get('required') == 'yes' ? ' required' : '').($is_hidden == true ? ' irrelevant' : '')));
  984. $field->displayPublishPanel(
  985. $div, $entry->getData($field->get('id')),
  986. (isset($this->_errors[$field->get('id')]) ? $this->_errors[$field->get('id')] : NULL),
  987. null, null, (is_numeric($entry->get('id')) ? $entry->get('id') : NULL)
  988. );
  989. return $div;
  990. }
  991. /**
  992. * Check whether a field is a Select Box Link and is hidden
  993. *
  994. * @param Field $field
  995. * @return String
  996. */
  997. public function isFieldHidden(Field $field) {
  998. if($field->get('hide_when_prepopulated') == 'yes') {
  999. if (isset($_REQUEST['prepopulate'])) foreach($_REQUEST['prepopulate'] as $field_id => $value) {
  1000. if($field_id == $field->get('id')) {
  1001. return true;
  1002. }
  1003. }
  1004. }
  1005. return false;
  1006. }
  1007. /**
  1008. * Prepare a Drawer to visualize section associations
  1009. *
  1010. * @param Section $section The current Section object
  1011. */
  1012. private function prepareAssociationsDrawer($section){
  1013. $entry_id = (!is_null($this->_context['entry_id'])) ? $this->_context['entry_id'] : null;
  1014. $show_entries = Symphony::Configuration()->get('association_maximum_rows', 'symphony');
  1015. if(is_null($entry_id) && !isset($_GET['prepopulate']) || is_null($show_entries) || $show_entries == 0) return;
  1016. $parent_associations = SectionManager::fetchParentAssociations($section->get('id'), true);
  1017. $child_associations = SectionManager::fetchChildAssociations($section->get('id'), true);
  1018. $content = null;
  1019. $drawer_position = 'vertical-right';
  1020. /**
  1021. * Prepare Associations Drawer from an Extension
  1022. *
  1023. * @since Symphony 2.3.3
  1024. * @delegate PrepareAssociationsDrawer
  1025. * @param string $context
  1026. * '/publish/'
  1027. * @param integer $entry_id
  1028. * The entry ID or null
  1029. * @param array $parent_associations
  1030. * Array of Sections
  1031. * @param array $child_associations
  1032. * Array of Sections
  1033. * @param string $drawer_position
  1034. * The position of the Drawer, defaults to `vertical-right`. Available
  1035. * values of `vertical-left, `vertical-right` and `horizontal`
  1036. */
  1037. Symphony::ExtensionManager()->notifyMembers('PrepareAssociationsDrawer', '/publish/', array(
  1038. 'entry_id' => $entry_id,
  1039. 'parent_associations' => &$parent_associations,
  1040. 'child_associations' => &$child_associations,
  1041. 'content' => &$content,
  1042. 'drawer-position' => &$drawer_position
  1043. ));
  1044. // If there are no associations, return now.
  1045. if(
  1046. (is_null($parent_associations) || empty($parent_associations))
  1047. &&
  1048. (is_null($child_associations) || empty($child_associations))
  1049. ) {
  1050. return;
  1051. }
  1052. if(!($content instanceof XMLElement)) {
  1053. $content = new XMLElement('div', null, array('class' => 'content'));
  1054. $content->setSelfClosingTag(false);
  1055. // Process Parent Associations
  1056. if(!is_null($parent_associations) && !empty($parent_associations)) foreach($parent_associations as $as){
  1057. if ($field = FieldManager::fetch($as['parent_section_field_id'])) {
  1058. // get associated entries if entry exists,
  1059. // get prepopulated entry otherwise
  1060. $entry_ids = $entry_id ?
  1061. $this->findParentRelatedEntries($as['child_section_field_id'], $entry_id) :
  1062. array(intval(current($_GET['prepopulate'])));
  1063. // Use $schema for perf reasons
  1064. $schema = array($field->get('element_name'));
  1065. $where = (!empty($entry_ids)) ? sprintf(' AND `e`.`id` IN (%s)', implode(', ', $entry_ids)) : null;
  1066. $entries = (!empty($entry_ids) || isset($_GET['prepopulate']))
  1067. ? EntryManager::fetchByPage(1, $as['parent_section_id'], $show_entries, $where, null, false, false, true, $schema)
  1068. : array();
  1069. $has_entries = !empty($entries) && $entries['total-entries'] != 0;
  1070. if($has_entries) {
  1071. $element = new XMLElement('section', null, array('class' => 'association parent'));
  1072. $header = new XMLElement('header');
  1073. $header->appendChild(new XMLElement('p', __('Linked to %s in', array('<a class="association-section" href="' . SYMPHONY_URL . '/publish/' . $as['handle'] . '/">' . $as['name'] . '</a>'))));
  1074. $element->appendChild($header);
  1075. $ul = new XMLElement('ul', null, array(
  1076. 'class' => 'association-links',
  1077. 'data-section-id' => $as['child_section_id'],
  1078. 'data-association-ids' => implode(', ', $entry_ids)
  1079. ));
  1080. foreach($entries['records'] as $e) {
  1081. $value = $field->prepareTableValue($e->getData($field->get('id')), null, $e->get('id'));
  1082. $li = new XMLElement('li');
  1083. $a = new XMLElement('a', strip_tags($value));
  1084. $a->setAttribute('href', SYMPHONY_URL . '/publish/' . $as['handle'] . '/edit/' . $e->get('id') . '/');
  1085. $li->appendChild($a);
  1086. $ul->appendChild($li);
  1087. }
  1088. $element->appendChild($ul);
  1089. $content->appendChild($element);
  1090. }
  1091. }
  1092. }
  1093. // Process Child Associations
  1094. if(!is_null($child_associations) && !empty($child_associations)) foreach($child_associations as $as){
  1095. // Get the related section
  1096. $child_section = SectionManager::fetch($as['child_section_id']);
  1097. if(!($child_section instanceof Section)) continue;
  1098. // Get the visible field instance (using the sorting field, this is more flexible than visibleColumns())
  1099. // Get the link field instance
  1100. $visible_field = current($child_section->fetchVisibleColumns());
  1101. $relation_field = FieldManager::fetch($as['child_section_field_id']);
  1102. // Get entries, using $schema for performance reasons.
  1103. $entry_ids = $this->findRelatedEntries($as['child_section_field_id'], $entry_id);
  1104. $schema = $visible_field ? array($visible_field->get('element_name')) : array();
  1105. $where = sprintf(' AND `e`.`id` IN (%s)', implode(', ', $entry_ids));
  1106. $entries = (!empty($entry_ids))
  1107. ? EntryManager::fetchByPage(1, $as['child_section_id'], $show_entries, $where, null, false, false, true, $schema)
  1108. : array();
  1109. $has_entries = !empty($entries) && $entries['total-entries'] != 0;
  1110. // Build the HTML of the relationship
  1111. $element = new XMLElement('section', null, array('class' => 'association child'));
  1112. $header = new XMLElement('header');
  1113. $filter = '?filter[' . $relation_field->get('element_name') . ']=' . $entry_id;
  1114. $prepopulate = '?prepopulate[' . $as['child_section_field_id'] . ']=' . $entry_id;
  1115. // Create link with filter or prepopulate
  1116. $link = SYMPHONY_URL . '/publish/' . $as['handle'] . '/' . $filter;
  1117. $a = new XMLElement('a', $as['name'], array(
  1118. 'class' => 'association-section',
  1119. 'href' => $link
  1120. ));
  1121. // Create new entries
  1122. $create = new XMLElement('a', __('Create New'), array(
  1123. 'class' => 'button association-new',
  1124. 'href' => SYMPHONY_URL . '/publish/' . $as['handle'] . '/new/' . $prepopulate
  1125. ));
  1126. // Display existing entries
  1127. if($has_entries) {
  1128. $header->appendChild(new XMLElement('p', __('Links in %s', array($a->generate()))));
  1129. $ul = new XMLElement('ul', null, array(
  1130. 'class' => 'association-links',
  1131. 'data-section-id' => $as['child_section_id'],
  1132. 'data-association-ids' => implode(', ', $entry_ids)
  1133. ));
  1134. foreach($entries['records'] as $key => $e) {
  1135. $value = $visible_field ?
  1136. $visible_field->prepareTableValue($e->getData($visible_field->get('id')), null, $e->get('id')) :
  1137. $e->get('id');
  1138. $li = new XMLElement('li');
  1139. $a = new XMLElement('a', strip_tags($value));
  1140. $a->setAttribute('href', SYMPHONY_URL . '/publish/' . $as['handle'] . '/edit/' . $e->get('id') . '/' . $prepopulate);
  1141. $li->appendChild($a);
  1142. $ul->appendChild($li);
  1143. }
  1144. $element->appendChild($ul);
  1145. // If we are only showing 'some' of the entries, then show this on the UI
  1146. if($entries['total-entries'] > $show_entries) {
  1147. $total_entries = new XMLElement('a', __('%d entries', array($entries['total-entries'])), array(
  1148. 'href' => $link,
  1149. ));
  1150. $pagination = new XMLElement('li', null, array(
  1151. 'class' => 'association-more',
  1152. 'data-current-page' => '1',
  1153. 'data-total-pages' => ceil($entries['total-entries'] / $show_entries)
  1154. ));
  1155. $counts = new XMLElement('a', __('Show more entries'), array(
  1156. 'href' => $link
  1157. ));
  1158. $pagination->appendChild($counts);
  1159. $ul->appendChild($pagination);
  1160. }
  1161. }
  1162. // No entries
  1163. else {
  1164. $element->setAttribute('class', 'association child empty');
  1165. $header->appendChild(new XMLElement('p', __('No links in %s', array($a->generate()))));
  1166. }
  1167. $header->appendChild($create);
  1168. $element->prependChild($header);
  1169. $content->appendChild($element);
  1170. }
  1171. }
  1172. $drawer = Widget::Drawer('section-associations', __('Show Associations'), $content);
  1173. $this->insertDrawer($drawer, $drawer_position, 'prepend');
  1174. }
  1175. /**
  1176. * Find related entries from a linking field's data table. Requires the
  1177. * column names to be `entry_id` and `relat