entity.ui.inc 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834
  1. <?php
  2. /**
  3. * @file
  4. * Provides a controller for building an entity overview form.
  5. */
  6. /**
  7. * Default UI controller providing admin UI.
  8. *
  9. * This controller suites best for managing configuration entities.
  10. * For a controller suiting content entities, see EntityContentUIController.
  11. */
  12. class EntityDefaultUIController {
  13. protected $entityType;
  14. protected $entityInfo, $path;
  15. protected $id_count;
  16. /**
  17. * Defines the number of entries to show per page in overview table.
  18. */
  19. public $overviewPagerLimit = 25;
  20. public function __construct($entity_type, $entity_info) {
  21. $this->entityType = $entity_type;
  22. $this->entityInfo = $entity_info;
  23. $this->path = $this->entityInfo['admin ui']['path'];
  24. $this->statusKey = empty($this->entityInfo['entity keys']['status']) ? 'status' : $this->entityInfo['entity keys']['status'];
  25. }
  26. /**
  27. * Provides definitions for implementing hook_menu().
  28. */
  29. public function hook_menu() {
  30. $items = array();
  31. // Set this on the object so classes that extend hook_menu() can use it.
  32. $this->id_count = count(explode('/', $this->path));
  33. $wildcard = isset($this->entityInfo['admin ui']['menu wildcard']) ? $this->entityInfo['admin ui']['menu wildcard'] : '%entity_object';
  34. $plural_label = isset($this->entityInfo['plural label']) ? $this->entityInfo['plural label'] : $this->entityInfo['label'] . 's';
  35. $items[$this->path] = array(
  36. 'title' => $plural_label,
  37. 'page callback' => 'drupal_get_form',
  38. 'page arguments' => array($this->entityType . '_overview_form', $this->entityType),
  39. 'description' => 'Manage ' . $plural_label . '.',
  40. 'access callback' => 'entity_access',
  41. 'access arguments' => array('view', $this->entityType),
  42. 'file' => 'includes/entity.ui.inc',
  43. );
  44. $items[$this->path . '/list'] = array(
  45. 'title' => 'List',
  46. 'type' => MENU_DEFAULT_LOCAL_TASK,
  47. 'weight' => -10,
  48. );
  49. $items[$this->path . '/add'] = array(
  50. 'title callback' => 'entity_ui_get_action_title',
  51. 'title arguments' => array('add', $this->entityType),
  52. 'page callback' => 'entity_ui_get_form',
  53. 'page arguments' => array($this->entityType, NULL, 'add'),
  54. 'access callback' => 'entity_access',
  55. 'access arguments' => array('create', $this->entityType),
  56. 'type' => MENU_LOCAL_ACTION,
  57. );
  58. $items[$this->path . '/manage/' . $wildcard] = array(
  59. 'title' => 'Edit',
  60. 'title callback' => 'entity_label',
  61. 'title arguments' => array($this->entityType, $this->id_count + 1),
  62. 'page callback' => 'entity_ui_get_form',
  63. 'page arguments' => array($this->entityType, $this->id_count + 1),
  64. 'load arguments' => array($this->entityType),
  65. 'access callback' => 'entity_access',
  66. 'access arguments' => array('update', $this->entityType, $this->id_count + 1),
  67. );
  68. $items[$this->path . '/manage/' . $wildcard . '/edit'] = array(
  69. 'title' => 'Edit',
  70. 'load arguments' => array($this->entityType),
  71. 'type' => MENU_DEFAULT_LOCAL_TASK,
  72. );
  73. // Clone form, a special case for the edit form.
  74. $items[$this->path . '/manage/' . $wildcard . '/clone'] = array(
  75. 'title' => 'Clone',
  76. 'page callback' => 'entity_ui_get_form',
  77. 'page arguments' => array($this->entityType, $this->id_count + 1, 'clone'),
  78. 'load arguments' => array($this->entityType),
  79. 'access callback' => 'entity_access',
  80. 'access arguments' => array('create', $this->entityType),
  81. );
  82. // Menu item for operations like revert and delete.
  83. $items[$this->path . '/manage/' . $wildcard . '/%'] = array(
  84. 'page callback' => 'drupal_get_form',
  85. 'page arguments' => array($this->entityType . '_operation_form', $this->entityType, $this->id_count + 1, $this->id_count + 2),
  86. 'load arguments' => array($this->entityType),
  87. 'access callback' => 'entity_access',
  88. 'access arguments' => array('delete', $this->entityType, $this->id_count + 1),
  89. 'file' => 'includes/entity.ui.inc',
  90. );
  91. if (!empty($this->entityInfo['exportable'])) {
  92. // Menu item for importing an entity.
  93. $items[$this->path . '/import'] = array(
  94. 'title callback' => 'entity_ui_get_action_title',
  95. 'title arguments' => array('import', $this->entityType),
  96. 'page callback' => 'drupal_get_form',
  97. 'page arguments' => array($this->entityType . '_operation_form', $this->entityType, NULL, 'import'),
  98. 'access callback' => 'entity_access',
  99. 'access arguments' => array('create', $this->entityType),
  100. 'file' => 'includes/entity.ui.inc',
  101. 'type' => MENU_LOCAL_ACTION,
  102. );
  103. }
  104. if (!empty($this->entityInfo['admin ui']['file'])) {
  105. // Add in the include file for the entity form.
  106. foreach (array("/manage/$wildcard", "/manage/$wildcard/clone", '/add') as $path_end) {
  107. $items[$this->path . $path_end]['file'] = $this->entityInfo['admin ui']['file'];
  108. $items[$this->path . $path_end]['file path'] = isset($this->entityInfo['admin ui']['file path']) ? $this->entityInfo['admin ui']['file path'] : drupal_get_path('module', $this->entityInfo['module']);
  109. }
  110. }
  111. return $items;
  112. }
  113. /**
  114. * Provides definitions for implementing hook_forms().
  115. *
  116. * Use per bundle form ids if possible, such that easy per bundle alterations
  117. * are supported too.
  118. *
  119. * Note that for performance reasons, this method is only invoked for forms,
  120. * which receive the entity_type as first argument. Thus any forms added, must
  121. * follow that pattern.
  122. *
  123. * @see entity_forms()
  124. */
  125. public function hook_forms() {
  126. // The overview and the operation form are implemented by the controller,
  127. // the callback and validation + submit handlers just invoke the controller.
  128. $forms[$this->entityType . '_overview_form'] = array(
  129. 'callback' => 'entity_ui_overview_form',
  130. 'wrapper_callback' => 'entity_ui_form_defaults',
  131. );
  132. $forms[$this->entityType . '_operation_form'] = array(
  133. 'callback' => 'entity_ui_operation_form',
  134. 'wrapper_callback' => 'entity_ui_form_defaults',
  135. );
  136. // The entity form (ENTITY_TYPE_form) handles editing, adding and cloning.
  137. // For that form, the wrapper callback entity_ui_main_form_defaults() gets
  138. // directly invoked via entity_ui_get_form().
  139. // If there are bundles though, we use form ids that include the bundle name
  140. // (ENTITY_TYPE_edit_BUNDLE_NAME_form) to enable per bundle alterations
  141. // as well as alterations based upon the base form id (ENTITY_TYPE_form).
  142. if (!(count($this->entityInfo['bundles']) == 1 && isset($this->entityInfo['bundles'][$this->entityType]))) {
  143. foreach ($this->entityInfo['bundles'] as $bundle => $bundle_info) {
  144. $forms[$this->entityType . '_edit_' . $bundle . '_form']['callback'] = $this->entityType . '_form';
  145. // Again the wrapper callback is invoked by entity_ui_get_form() anyway.
  146. }
  147. }
  148. return $forms;
  149. }
  150. /**
  151. * Builds the entity overview form.
  152. */
  153. public function overviewForm($form, &$form_state) {
  154. // By default just show a simple overview for all entities.
  155. $form['table'] = $this->overviewTable();
  156. $form['pager'] = array('#theme' => 'pager');
  157. return $form;
  158. }
  159. /**
  160. * Overview form validation callback.
  161. *
  162. * @param $form
  163. * The form array of the overview form.
  164. * @param $form_state
  165. * The overview form state which will be used for validating.
  166. */
  167. public function overviewFormValidate($form, &$form_state) {}
  168. /**
  169. * Overview form submit callback.
  170. *
  171. * @param $form
  172. * The form array of the overview form.
  173. * @param $form_state
  174. * The overview form state which will be used for submitting.
  175. */
  176. public function overviewFormSubmit($form, &$form_state) {}
  177. /**
  178. * Generates the render array for a overview table for arbitrary entities
  179. * matching the given conditions.
  180. *
  181. * @param $conditions
  182. * An array of conditions as needed by entity_load().
  183. * @return Array
  184. * A renderable array.
  185. */
  186. public function overviewTable($conditions = array()) {
  187. $query = new EntityFieldQuery();
  188. $query->entityCondition('entity_type', $this->entityType);
  189. // Add all conditions to query.
  190. foreach ($conditions as $key => $value) {
  191. $query->propertyCondition($key, $value);
  192. }
  193. if ($this->overviewPagerLimit) {
  194. $query->pager($this->overviewPagerLimit);
  195. }
  196. $results = $query->execute();
  197. $ids = isset($results[$this->entityType]) ? array_keys($results[$this->entityType]) : array();
  198. $entities = $ids ? entity_load($this->entityType, $ids) : array();
  199. ksort($entities);
  200. $rows = array();
  201. foreach ($entities as $entity) {
  202. $rows[] = $this->overviewTableRow($conditions, entity_id($this->entityType, $entity), $entity);
  203. }
  204. $render = array(
  205. '#theme' => 'table',
  206. '#header' => $this->overviewTableHeaders($conditions, $rows),
  207. '#rows' => $rows,
  208. '#empty' => t('None.'),
  209. );
  210. return $render;
  211. }
  212. /**
  213. * Generates the table headers for the overview table.
  214. */
  215. protected function overviewTableHeaders($conditions, $rows, $additional_header = array()) {
  216. $header = $additional_header;
  217. array_unshift($header, t('Label'));
  218. if (!empty($this->entityInfo['exportable'])) {
  219. $header[] = t('Status');
  220. }
  221. // Add operations with the right colspan.
  222. $header[] = array('data' => t('Operations'), 'colspan' => $this->operationCount());
  223. return $header;
  224. }
  225. /**
  226. * Returns the operation count for calculating colspans.
  227. */
  228. protected function operationCount() {
  229. $count = 3;
  230. $count += !empty($this->entityInfo['bundle of']) && entity_type_is_fieldable($this->entityInfo['bundle of']) && module_exists('field_ui') ? 2 : 0;
  231. $count += !empty($this->entityInfo['exportable']) ? 1 : 0;
  232. $count += !empty($this->entityInfo['i18n controller class']) ? 1 : 0;
  233. return $count;
  234. }
  235. /**
  236. * Generates the row for the passed entity and may be overridden in order to
  237. * customize the rows.
  238. *
  239. * @param $additional_cols
  240. * Additional columns to be added after the entity label column.
  241. */
  242. protected function overviewTableRow($conditions, $id, $entity, $additional_cols = array()) {
  243. $entity_uri = entity_uri($this->entityType, $entity);
  244. $row[] = array('data' => array(
  245. '#theme' => 'entity_ui_overview_item',
  246. '#label' => entity_label($this->entityType, $entity),
  247. '#name' => !empty($this->entityInfo['exportable']) ? entity_id($this->entityType, $entity) : FALSE,
  248. '#url' => $entity_uri ? $entity_uri : FALSE,
  249. '#entity_type' => $this->entityType),
  250. );
  251. // Add in any passed additional cols.
  252. foreach ($additional_cols as $col) {
  253. $row[] = $col;
  254. }
  255. // Add a row for the exportable status.
  256. if (!empty($this->entityInfo['exportable'])) {
  257. $row[] = array('data' => array(
  258. '#theme' => 'entity_status',
  259. '#status' => $entity->{$this->statusKey},
  260. ));
  261. }
  262. // In case this is a bundle, we add links to the field ui tabs.
  263. $field_ui = !empty($this->entityInfo['bundle of']) && entity_type_is_fieldable($this->entityInfo['bundle of']) && module_exists('field_ui');
  264. // For exportable entities we add an export link.
  265. $exportable = !empty($this->entityInfo['exportable']);
  266. // If i18n integration is enabled, add a link to the translate tab.
  267. $i18n = !empty($this->entityInfo['i18n controller class']);
  268. // Add operations depending on the status.
  269. if (entity_has_status($this->entityType, $entity, ENTITY_FIXED)) {
  270. $row[] = array('data' => l(t('clone'), $this->path . '/manage/' . $id . '/clone'), 'colspan' => $this->operationCount());
  271. }
  272. else {
  273. $row[] = l(t('edit'), $this->path . '/manage/' . $id);
  274. if ($field_ui) {
  275. $row[] = l(t('manage fields'), $this->path . '/manage/' . $id . '/fields');
  276. $row[] = l(t('manage display'), $this->path . '/manage/' . $id . '/display');
  277. }
  278. if ($i18n) {
  279. $row[] = l(t('translate'), $this->path . '/manage/' . $id . '/translate');
  280. }
  281. if ($exportable) {
  282. $row[] = l(t('clone'), $this->path . '/manage/' . $id . '/clone');
  283. }
  284. if (empty($this->entityInfo['exportable']) || !entity_has_status($this->entityType, $entity, ENTITY_IN_CODE)) {
  285. $row[] = l(t('delete'), $this->path . '/manage/' . $id . '/delete', array('query' => drupal_get_destination()));
  286. }
  287. elseif (entity_has_status($this->entityType, $entity, ENTITY_OVERRIDDEN)) {
  288. $row[] = l(t('revert'), $this->path . '/manage/' . $id . '/revert', array('query' => drupal_get_destination()));
  289. }
  290. else {
  291. $row[] = '';
  292. }
  293. }
  294. if ($exportable) {
  295. $row[] = l(t('export'), $this->path . '/manage/' . $id . '/export');
  296. }
  297. return $row;
  298. }
  299. /**
  300. * Builds the operation form.
  301. *
  302. * For the export operation a serialized string of the entity is directly
  303. * shown in the form (no submit function needed).
  304. */
  305. public function operationForm($form, &$form_state, $entity, $op) {
  306. switch ($op) {
  307. case 'revert':
  308. $label = entity_label($this->entityType, $entity);
  309. $confirm_question = t('Are you sure you want to revert the %entity %label?', array('%entity' => $this->entityInfo['label'], '%label' => $label));
  310. return confirm_form($form, $confirm_question, $this->path);
  311. case 'delete':
  312. $label = entity_label($this->entityType, $entity);
  313. $confirm_question = t('Are you sure you want to delete the %entity %label?', array('%entity' => $this->entityInfo['label'], '%label' => $label));
  314. return confirm_form($form, $confirm_question, $this->path);
  315. case 'export':
  316. if (!empty($this->entityInfo['exportable'])) {
  317. $export = entity_export($this->entityType, $entity);
  318. $form['export'] = array(
  319. '#type' => 'textarea',
  320. '#title' => t('Export'),
  321. '#description' => t('For importing copy the content of the text area and paste it into the import page.'),
  322. '#rows' => 25,
  323. '#default_value' => $export,
  324. );
  325. return $form;
  326. }
  327. case 'import':
  328. $form['import'] = array(
  329. '#type' => 'textarea',
  330. '#title' => t('Import'),
  331. '#description' => t('Paste an exported %entity_type here.', array('%entity_type' => $this->entityInfo['label'])),
  332. '#rows' => 20,
  333. );
  334. $form['overwrite'] = array(
  335. '#title' => t('Overwrite'),
  336. '#type' => 'checkbox',
  337. '#description' => t('If checked, any existing %entity with the same identifier will be replaced by the import.', array('%entity' => $this->entityInfo['label'])),
  338. '#default_value' => FALSE,
  339. );
  340. $form['submit'] = array(
  341. '#type' => 'submit',
  342. '#value' => t('Import'),
  343. );
  344. return $form;
  345. }
  346. drupal_not_found();
  347. exit;
  348. }
  349. /**
  350. * Operation form validation callback.
  351. */
  352. public function operationFormValidate($form, &$form_state) {
  353. if ($form_state['op'] == 'import') {
  354. if ($entity = entity_import($this->entityType, $form_state['values']['import'])) {
  355. // Store the successfully imported entity in $form_state.
  356. $form_state[$this->entityType] = $entity;
  357. if (!$form_state['values']['overwrite']) {
  358. // Check for existing entities with the same identifier.
  359. $id = entity_id($this->entityType, $entity);
  360. $entities = entity_load($this->entityType, array($id));
  361. if (!empty($entities)) {
  362. $label = entity_label($this->entityType, $entity);
  363. $vars = array('%entity' => $this->entityInfo['label'], '%label' => $label);
  364. form_set_error('import', t('Import of %entity %label failed, a %entity with the same machine name already exists. Check the overwrite option to replace it.', $vars));
  365. }
  366. }
  367. }
  368. else {
  369. form_set_error('import', t('Import failed.'));
  370. }
  371. }
  372. }
  373. /**
  374. * Operation form submit callback.
  375. */
  376. public function operationFormSubmit($form, &$form_state) {
  377. $msg = $this->applyOperation($form_state['op'], $form_state[$this->entityType]);
  378. drupal_set_message($msg);
  379. $form_state['redirect'] = $this->path;
  380. }
  381. /**
  382. * Applies an operation to the given entity.
  383. *
  384. * Note: the export operation is directly carried out by the operationForm()
  385. * method.
  386. *
  387. * @param string $op
  388. * The operation (revert, delete or import).
  389. * @param $entity
  390. * The entity to manipulate.
  391. *
  392. * @return
  393. * The status message of what has been applied.
  394. */
  395. public function applyOperation($op, $entity) {
  396. $label = entity_label($this->entityType, $entity);
  397. $vars = array('%entity' => $this->entityInfo['label'], '%label' => $label);
  398. $id = entity_id($this->entityType, $entity);
  399. $edit_link = l(t('edit'), $this->path . '/manage/' . $id . '/edit');
  400. switch ($op) {
  401. case 'revert':
  402. entity_delete($this->entityType, $id);
  403. watchdog($this->entityType, 'Reverted %entity %label to the defaults.', $vars, WATCHDOG_NOTICE, $edit_link);
  404. return t('Reverted %entity %label to the defaults.', $vars);
  405. case 'delete':
  406. entity_delete($this->entityType, $id);
  407. watchdog($this->entityType, 'Deleted %entity %label.', $vars);
  408. return t('Deleted %entity %label.', $vars);
  409. case 'import':
  410. // First check if there is any existing entity with the same ID.
  411. $id = entity_id($this->entityType, $entity);
  412. $entities = entity_load($this->entityType, array($id));
  413. if ($existing_entity = reset($entities)) {
  414. // Copy DB id and remove the new indicator to overwrite the DB record.
  415. $idkey = $this->entityInfo['entity keys']['id'];
  416. $entity->{$idkey} = $existing_entity->{$idkey};
  417. unset($entity->is_new);
  418. }
  419. entity_save($this->entityType, $entity);
  420. watchdog($this->entityType, 'Imported %entity %label.', $vars);
  421. return t('Imported %entity %label.', $vars);
  422. default:
  423. return FALSE;
  424. }
  425. }
  426. /**
  427. * Entity submit builder invoked via entity_ui_form_submit_build_entity().
  428. *
  429. * Extracts the form values and updates the entity.
  430. *
  431. * The provided implementation makes use of the helper function
  432. * entity_form_submit_build_entity() provided by core, which already invokes
  433. * the field API attacher for fieldable entities.
  434. *
  435. * @return
  436. * The updated entity.
  437. *
  438. * @see entity_ui_form_submit_build_entity()
  439. */
  440. public function entityFormSubmitBuildEntity($form, &$form_state) {
  441. // Add the bundle property to the entity if the entity type supports bundles
  442. // and the form provides a value for the bundle key. Especially new entities
  443. // need to have their bundle property pre-populated before we invoke
  444. // entity_form_submit_build_entity().
  445. if (!empty($this->entityInfo['entity keys']['bundle']) && isset($form_state['values'][$this->entityInfo['entity keys']['bundle']])) {
  446. $form_state[$this->entityType]->{$this->entityInfo['entity keys']['bundle']} = $form_state['values'][$this->entityInfo['entity keys']['bundle']];
  447. }
  448. entity_form_submit_build_entity($this->entityType, $form_state[$this->entityType], $form, $form_state);
  449. return $form_state[$this->entityType];
  450. }
  451. }
  452. /**
  453. * UI controller providing UI for content entities.
  454. *
  455. * For a controller providing UI for bundleable content entities, see
  456. * EntityBundleableUIController.
  457. * For a controller providing admin UI for configuration entities, see
  458. * EntityDefaultUIController.
  459. */
  460. class EntityContentUIController extends EntityDefaultUIController {
  461. /**
  462. * Provides definitions for implementing hook_menu().
  463. */
  464. public function hook_menu() {
  465. $items = parent::hook_menu();
  466. $wildcard = isset($this->entityInfo['admin ui']['menu wildcard']) ? $this->entityInfo['admin ui']['menu wildcard'] : '%entity_object';
  467. // Unset the manage entity path, as the provided UI is for admin entities.
  468. unset($items[$this->path]);
  469. $defaults = array(
  470. 'file' => $this->entityInfo['admin ui']['file'],
  471. 'file path' => isset($this->entityInfo['admin ui']['file path']) ? $this->entityInfo['admin ui']['file path'] : drupal_get_path('module', $this->entityInfo['module']),
  472. );
  473. // Add view, edit and delete menu items for content entities.
  474. $items[$this->path . '/' . $wildcard] = array(
  475. 'title callback' => 'entity_ui_get_page_title',
  476. 'title arguments' => array('view', $this->entityType, $this->id_count),
  477. 'page callback' => 'entity_ui_entity_page_view',
  478. 'page arguments' => array($this->id_count),
  479. 'load arguments' => array($this->entityType),
  480. 'access callback' => 'entity_access',
  481. 'access arguments' => array('view', $this->entityType, $this->id_count),
  482. ) + $defaults;
  483. $items[$this->path . '/' . $wildcard . '/view'] = array(
  484. 'title' => 'View',
  485. 'type' => MENU_DEFAULT_LOCAL_TASK,
  486. 'load arguments' => array($this->entityType),
  487. 'weight' => -10,
  488. ) + $defaults;
  489. $items[$this->path . '/' . $wildcard . '/edit'] = array(
  490. 'page callback' => 'entity_ui_get_form',
  491. 'page arguments' => array($this->entityType, $this->id_count),
  492. 'load arguments' => array($this->entityType),
  493. 'access callback' => 'entity_access',
  494. 'access arguments' => array('edit', $this->entityType, $this->id_count),
  495. 'title' => 'Edit',
  496. 'type' => MENU_LOCAL_TASK,
  497. 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
  498. ) + $defaults;
  499. $items[$this->path . '/' . $wildcard . '/delete'] = array(
  500. 'page callback' => 'drupal_get_form',
  501. 'page arguments' => array($this->entityType . '_operation_form', $this->entityType, $this->id_count, 'delete'),
  502. 'load arguments' => array($this->entityType),
  503. 'access callback' => 'entity_access',
  504. 'access arguments' => array('delete', $this->entityType, $this->id_count),
  505. 'title' => 'Delete',
  506. 'type' => MENU_LOCAL_TASK,
  507. 'context' => MENU_CONTEXT_INLINE,
  508. 'file' => $this->entityInfo['admin ui']['file'],
  509. 'file path' => isset($this->entityInfo['admin ui']['file path']) ? $this->entityInfo['admin ui']['file path'] : drupal_get_path('module', $this->entityInfo['module']),
  510. ) + $defaults;
  511. return $items;
  512. }
  513. /**
  514. * Operation form submit callback.
  515. */
  516. public function operationFormSubmit($form, &$form_state) {
  517. parent::operationFormSubmit($form, $form_state);
  518. // The manage entity path is unset for the content entity UI.
  519. $form_state['redirect'] = '<front>';
  520. }
  521. }
  522. /**
  523. * UI controller providing UI for bundleable content entities.
  524. *
  525. * Adds a bundle selection page to the entity/add path, analogously to the
  526. * node/add path.
  527. */
  528. class EntityBundleableUIController extends EntityContentUIController {
  529. /**
  530. * Provides definitions for implementing hook_menu().
  531. */
  532. public function hook_menu() {
  533. $items = parent::hook_menu();
  534. // Extend the 'add' path.
  535. $items[$this->path . '/add'] = array(
  536. 'title callback' => 'entity_ui_get_action_title',
  537. 'title arguments' => array('add', $this->entityType),
  538. 'page callback' => 'entity_ui_bundle_add_page',
  539. 'page arguments' => array($this->entityType),
  540. 'access callback' => 'entity_access',
  541. 'access arguments' => array('create', $this->entityType),
  542. 'type' => MENU_LOCAL_ACTION,
  543. );
  544. $items[$this->path . '/add/%'] = array(
  545. 'title callback' => 'entity_ui_get_action_title',
  546. 'title arguments' => array('add', $this->entityType, $this->id_count + 1),
  547. 'page callback' => 'entity_ui_get_bundle_add_form',
  548. 'page arguments' => array($this->entityType, $this->id_count + 1),
  549. 'access callback' => 'entity_access',
  550. 'access arguments' => array('create', $this->entityType),
  551. );
  552. if (!empty($this->entityInfo['admin ui']['file'])) {
  553. // Add in the include file for the entity form.
  554. foreach (array('/add', '/add/%') as $path_end) {
  555. $items[$this->path . $path_end]['file'] = $this->entityInfo['admin ui']['file'];
  556. $items[$this->path . $path_end]['file path'] = isset($this->entityInfo['admin ui']['file path']) ? $this->entityInfo['admin ui']['file path'] : drupal_get_path('module', $this->entityInfo['module']);
  557. }
  558. }
  559. return $items;
  560. }
  561. }
  562. /**
  563. * Form builder function for the overview form.
  564. *
  565. * @see EntityDefaultUIController::overviewForm()
  566. */
  567. function entity_ui_overview_form($form, &$form_state, $entity_type) {
  568. return entity_ui_controller($entity_type)->overviewForm($form, $form_state);
  569. }
  570. /**
  571. * Form builder for the entity operation form.
  572. *
  573. * @see EntityDefaultUIController::operationForm()
  574. */
  575. function entity_ui_operation_form($form, &$form_state, $entity_type, $entity, $op) {
  576. $form_state['op'] = $op;
  577. return entity_ui_controller($entity_type)->operationForm($form, $form_state, $entity, $op);
  578. }
  579. /**
  580. * Form wrapper the main entity form.
  581. *
  582. * @see entity_ui_form_defaults()
  583. */
  584. function entity_ui_main_form_defaults($form, &$form_state, $entity = NULL, $op = NULL) {
  585. // Now equals entity_ui_form_defaults() but is still here to keep backward
  586. // compatability.
  587. return entity_ui_form_defaults($form, $form_state, $form_state['entity_type'], $entity, $op);
  588. }
  589. /**
  590. * Clones the entity object and makes sure it will get saved as new entity.
  591. *
  592. * @return
  593. * The cloned entity object.
  594. */
  595. function entity_ui_clone_entity($entity_type, $entity) {
  596. // Clone the entity and make sure it will get saved as a new entity.
  597. $entity = clone $entity;
  598. $entity_info = entity_get_info($entity_type);
  599. $entity->{$entity_info['entity keys']['id']} = FALSE;
  600. if (!empty($entity_info['entity keys']['name'])) {
  601. $entity->{$entity_info['entity keys']['name']} = FALSE;
  602. }
  603. $entity->is_new = TRUE;
  604. // Make sure the status of a cloned exportable is custom.
  605. if (!empty($entity_info['exportable'])) {
  606. $status_key = isset($entity_info['entity keys']['status']) ? $entity_info['entity keys']['status'] : 'status';
  607. $entity->$status_key = ENTITY_CUSTOM;
  608. }
  609. return $entity;
  610. }
  611. /**
  612. * Form wrapper callback for all entity ui forms.
  613. *
  614. * This callback makes sure the form state is properly initialized and sets
  615. * some useful default titles.
  616. *
  617. * @see EntityDefaultUIController::hook_forms()
  618. */
  619. function entity_ui_form_defaults($form, &$form_state, $entity_type, $entity = NULL, $op = NULL) {
  620. $defaults = array(
  621. 'entity_type' => $entity_type,
  622. );
  623. if (isset($entity)) {
  624. $defaults[$entity_type] = $entity;
  625. }
  626. if (isset($op)) {
  627. $defaults['op'] = $op;
  628. }
  629. $form_state += $defaults;
  630. if (isset($op)) {
  631. drupal_set_title(entity_ui_get_page_title($op, $entity_type, $entity), PASS_THROUGH);
  632. }
  633. // Add in handlers pointing to the controller for the forms implemented by it.
  634. if (isset($form_state['build_info']['base_form_id']) && $form_state['build_info']['base_form_id'] != $entity_type . '_form') {
  635. $form['#validate'][] = 'entity_ui_controller_form_validate';
  636. $form['#submit'][] = 'entity_ui_controller_form_submit';
  637. }
  638. return $form;
  639. }
  640. /**
  641. * Validation callback for forms implemented by the UI controller.
  642. */
  643. function entity_ui_controller_form_validate($form, &$form_state) {
  644. // Remove 'entity_ui_' prefix and the '_form' suffix.
  645. $base = substr($form_state['build_info']['base_form_id'], 10, -5);
  646. $method = $base . 'FormValidate';
  647. entity_ui_controller($form_state['entity_type'])->$method($form, $form_state);
  648. }
  649. /**
  650. * Submit callback for forms implemented by the UI controller.
  651. */
  652. function entity_ui_controller_form_submit($form, &$form_state) {
  653. // Remove 'entity_ui_' prefix and the '_form' suffix.
  654. $base = substr($form_state['build_info']['base_form_id'], 10, -5);
  655. $method = $base . 'FormSubmit';
  656. entity_ui_controller($form_state['entity_type'])->$method($form, $form_state);
  657. }
  658. /**
  659. * Gets the page title for the passed operation.
  660. */
  661. function entity_ui_get_page_title($op, $entity_type, $entity = NULL) {
  662. $label = entity_label($entity_type, $entity);
  663. switch ($op) {
  664. case 'view':
  665. return $label;
  666. case 'edit':
  667. return t('Edit @label', array('@label' => $label));
  668. case 'clone':
  669. return t('Clone @label', array('@label' => $label));
  670. case 'revert':
  671. return t('Revert @label', array('@label' => $label));
  672. case 'delete':
  673. return t('Delete @label', array('@label' => $label));
  674. case 'export':
  675. return t('Export @label', array('@label' => $label));
  676. }
  677. if (isset($entity)) {
  678. list(, , $bundle) = entity_extract_ids($entity_type, $entity);
  679. }
  680. return entity_ui_get_action_title($op, $entity_type, $bundle);
  681. }
  682. /**
  683. * Gets the page/menu title for local action operations.
  684. *
  685. * @param $op
  686. * The current operation. One of 'add' or 'import'.
  687. * @param $entity_type
  688. * The entity type.
  689. * @param $bundle_name
  690. * (Optional) The name of the bundle. May be NULL if the bundle name is not
  691. * relevant to the current page. If the entity type has only one bundle, or no
  692. * bundles, this will be the same as the entity type.
  693. */
  694. function entity_ui_get_action_title($op, $entity_type, $bundle_name = NULL) {
  695. $info = entity_get_info($entity_type);
  696. switch ($op) {
  697. case 'add':
  698. if (isset($bundle_name) && $bundle_name != $entity_type) {
  699. return t('Add @bundle_name @entity_type', array(
  700. '@bundle_name' => drupal_strtolower($info['bundles'][$bundle_name]['label']),
  701. '@entity_type' => drupal_strtolower($info['label']),
  702. ));
  703. }
  704. else {
  705. return t('Add @entity_type', array('@entity_type' => drupal_strtolower($info['label'])));
  706. }
  707. case 'import':
  708. return t('Import @entity_type', array('@entity_type' => drupal_strtolower($info['label'])));
  709. }
  710. }
  711. /**
  712. * Submit builder for the main entity form, which extracts the form values and updates the entity.
  713. *
  714. * This is a helper function for entities making use of the entity UI
  715. * controller.
  716. *
  717. * @return
  718. * The updated entity.
  719. *
  720. * @see EntityDefaultUIController::hook_forms()
  721. * @see EntityDefaultUIController::entityFormSubmitBuildEntity()
  722. */
  723. function entity_ui_form_submit_build_entity($form, &$form_state) {
  724. return entity_ui_controller($form_state['entity_type'])->entityFormSubmitBuildEntity($form, $form_state);
  725. }
  726. /**
  727. * Validation callback for machine names of exportables.
  728. *
  729. * We don't allow numeric machine names, as entity_load() treats them as the
  730. * numeric identifier and they are easily confused with ids in general.
  731. */
  732. function entity_ui_validate_machine_name($element, &$form_state) {
  733. if (is_numeric($element['#value'])) {
  734. form_error($element, t('Machine-readable names must not consist of numbers only.'));
  735. }
  736. }
  737. /**
  738. * Returns HTML for an entity on the entity overview listing.
  739. *
  740. * @ingroup themeable
  741. */
  742. function theme_entity_ui_overview_item($variables) {
  743. $output = $variables['url'] ? l($variables['label'], $variables['url']['path'], $variables['url']['options']) : check_plain($variables['label']);
  744. if ($variables['name']) {
  745. $output .= ' <small> (' . t('Machine name') . ': ' . check_plain($variables['name']) . ')</small>';
  746. }
  747. return $output;
  748. }
  749. /**
  750. * Page callback for viewing an entity.
  751. *
  752. * @param Entity $entity
  753. * The entity to be rendered.
  754. *
  755. * @return array
  756. * A renderable array of the entity in full view mode.
  757. */
  758. function entity_ui_entity_page_view($entity) {
  759. return $entity->view('full', NULL, TRUE);
  760. }