ParagraphsItemEntity.inc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. <?php
  2. /**
  3. * @file
  4. * Entity implementation for the paragraphs entity.
  5. */
  6. /**
  7. * Class for paragraphs_item entities.
  8. */
  9. class ParagraphsItemEntity extends Entity {
  10. /**
  11. * paragraphs field info.
  12. *
  13. * @var array
  14. */
  15. protected $fieldInfo;
  16. /**
  17. * The host entity object.
  18. *
  19. * @var object
  20. */
  21. protected $hostEntity;
  22. /**
  23. * The host entity ID.
  24. *
  25. * @var integer
  26. */
  27. protected $hostEntityId;
  28. /**
  29. * The host entity revision ID if this is not the default revision.
  30. *
  31. * @var integer
  32. */
  33. protected $hostEntityRevisionId;
  34. /**
  35. * The host entity type.
  36. *
  37. * @var string
  38. */
  39. protected $hostEntityType;
  40. /**
  41. * The host entity bundle.
  42. *
  43. * @var string
  44. */
  45. protected $hostEntityBundle;
  46. /**
  47. * The language under which the paragraphs item is stored.
  48. *
  49. * @var string
  50. */
  51. protected $langcode = LANGUAGE_NONE;
  52. /**
  53. * Entity ID.
  54. *
  55. * @var integer
  56. */
  57. public $item_id;
  58. /**
  59. * paragraphs revision ID.
  60. *
  61. * @var integer
  62. */
  63. public $revision_id;
  64. /**
  65. * The name of the paragraphs field this item is associated with.
  66. *
  67. * @var string
  68. */
  69. public $field_name;
  70. /**
  71. * Whether this revision is the default revision.
  72. *
  73. * @var bool
  74. */
  75. public $default_revision = TRUE;
  76. /**
  77. * Whether the paragraphs item is archived, i.e. not in use.
  78. *
  79. * @see ParagraphsItemEntity::isInUse()
  80. * @var bool
  81. */
  82. public $archived = FALSE;
  83. /**
  84. * The name of the paragraphs field this item is associated with.
  85. *
  86. * @var string
  87. */
  88. private $fetchedHostEntityDetails = NULL;
  89. /**
  90. * Constructs the entity object.
  91. */
  92. public function __construct(array $values = array(), $entityType = NULL) {
  93. parent::__construct($values, 'paragraphs_item');
  94. if (isset($this->field_name)) {
  95. // Ok, we have the field name property, we can proceed and check the field's type
  96. $field_info = $this->fieldInfo();
  97. // Check if we have field info, if not, our field is deleted.
  98. if (!$field_info) {
  99. return FALSE;
  100. }
  101. // We only allow paragraphs type field for this entity.
  102. if ($field_info['type'] != 'paragraphs') {
  103. throw new Exception("Invalid field name given: {$this->field_name} is not a paragraphs field.");
  104. }
  105. }
  106. }
  107. /**
  108. * Provides info about the field on the host entity, which embeds this
  109. * paragraphs item.
  110. */
  111. public function fieldInfo() {
  112. return field_info_field($this->field_name);
  113. }
  114. /**
  115. * Provides info of the field instance containing the reference to this
  116. * paragraphs item.
  117. */
  118. public function instanceInfo() {
  119. return field_info_instance($this->hostEntityType(), $this->field_name, $this->hostEntityBundle());
  120. }
  121. /**
  122. * Returns the field instance label translated to interface language.
  123. */
  124. public function translatedInstanceLabel($langcode = NULL) {
  125. if ($info = $this->instanceInfo()) {
  126. if (module_exists('i18n_field')) {
  127. return i18n_string("field:{$this->field_name}:{$info['bundle']}:label", $info['label'], array('langcode' => $langcode));
  128. }
  129. return $info['label'];
  130. }
  131. }
  132. /**
  133. * Specifies the default label, which is picked up by label() by default.
  134. */
  135. public function defaultLabel() {
  136. if ($this->fetchHostDetails()) {
  137. // Don't show a label, our parent field already shows an label
  138. // If the user decides he wants to show one.
  139. return '';
  140. }
  141. // Should only happen when there is something wrong
  142. return t('Unconnected paragraphs item');
  143. }
  144. /**
  145. * Returns the path used to view the entity.
  146. */
  147. public function path() {
  148. return;
  149. }
  150. /**
  151. * Returns the URI as returned by entity_uri().
  152. */
  153. public function defaultUri() {
  154. return array(
  155. 'path' => $this->path(),
  156. );
  157. }
  158. /**
  159. * Sets the host entity. Only possible during creation of a item.
  160. *
  161. * @param $entity_type
  162. * The entity type of the host.
  163. * @param $entity
  164. * The host entity.
  165. * @param $langcode
  166. * (optional) The field language code we should use for host entity.
  167. * @param $create_link
  168. * (optional) Whether a field-item linking the host entity to the field
  169. * collection item should be created.
  170. * @throws Exception
  171. * When you try to set the host when the item has already been created.
  172. */
  173. public function setHostEntity($entity_type, $entity, $langcode = LANGUAGE_NONE, $create_link = TRUE) {
  174. $this->hostEntityType = $entity_type;
  175. $this->hostEntity = $entity;
  176. $this->langcode = $langcode;
  177. list($this->hostEntityId, $this->hostEntityRevisionId, $this->hostEntityBundle) = entity_extract_ids($this->hostEntityType, $this->hostEntity);
  178. // If the host entity is not saved yet, set the id to FALSE. So
  179. // fetchHostDetails() does not try to load the host entity details.
  180. if (!isset($this->hostEntityId)) {
  181. $this->hostEntityId = FALSE;
  182. }
  183. // We are create a new paragraphs for a non-default entity, thus
  184. // set archived to TRUE.
  185. if (!entity_revision_is_default($entity_type, $entity)) {
  186. $this->hostEntityId = FALSE;
  187. $this->archived = TRUE;
  188. }
  189. if ($create_link) {
  190. $entity->{$this->field_name}[$this->langcode][] = array('entity' => $this);
  191. }
  192. $this->fetchedHostEntityDetails = TRUE;
  193. }
  194. /**
  195. * Function to force change the host entity of this paragraph item.
  196. *
  197. * @param $entity
  198. * The entity to force the host to.
  199. */
  200. public function forceHostEntity($entity) {
  201. $this->hostEntity = $entity;
  202. }
  203. /**
  204. * Function to force change the host entity type of this paragraph item.
  205. *
  206. * @param $entity_type
  207. * The entity type to force the host to.
  208. */
  209. public function forceHostEntityType($entity_type) {
  210. $this->hostEntityType = $entity_type;
  211. }
  212. /**
  213. * Returns the host entity, which embeds this paragraph item.
  214. */
  215. public function hostEntity() {
  216. $this->fetchHostDetails();
  217. return $this->hostEntity;
  218. }
  219. /**
  220. * Returns the entity type of the host entity, which embeds this
  221. * paragraph item.
  222. */
  223. public function hostEntityType() {
  224. return $this->hostEntityType;
  225. }
  226. /**
  227. * Returns the id of the host entity, which embeds this paragraph item.
  228. */
  229. public function hostEntityId() {
  230. return $this->hostEntityId;
  231. }
  232. /**
  233. * Returns the revisions id of the host entity, which embeds this paragraph item.
  234. */
  235. public function hostEntityRevisionId() {
  236. return $this->hostEntityRevisionId;
  237. }
  238. /**
  239. * Returns the bundle of the host entity, which embeds this paragraphs
  240. * item.
  241. */
  242. public function hostEntityBundle() {
  243. return $this->hostEntityBundle;
  244. }
  245. /**
  246. * Fetches details of the host and saves it in the entity.
  247. *
  248. * @return bool
  249. * Whether the fetching was successful.
  250. */
  251. protected function fetchHostDetails() {
  252. if ($this->fetchedHostEntityDetails === NULL) {
  253. if ($this->item_id) {
  254. // For saved paragraphs, query the field data to determine the
  255. // right host entity.
  256. $query = new EntityFieldQuery();
  257. $query->fieldCondition($this->fieldInfo(), 'revision_id', $this->revision_id);
  258. if (!$this->isInUse()) {
  259. $query->age(FIELD_LOAD_REVISION);
  260. }
  261. $result = $query->execute();
  262. list($this->hostEntityType, $data) = each($result);
  263. if ($data) {
  264. $data_values = array_shift($data);
  265. $host_entity_info = entity_get_info($this->hostEntityType);
  266. $host_entity_keys = $host_entity_info['entity keys'];
  267. $this->hostEntityId = (isset($data_values->{$host_entity_keys['id']}) ? $data_values->{$host_entity_keys['id']} : NULL);
  268. $this->hostEntityRevisionId = (isset($data_values->{$host_entity_keys['revision']}) ? $data_values->{$host_entity_keys['revision']} : NULL);
  269. $this->hostEntityBundle = (isset($data_values->{$host_entity_keys['bundle']}) ? $data_values->{$host_entity_keys['bundle']} : NULL);
  270. if ($this->isInUse()) {
  271. $this->hostEntity = entity_load_single($this->hostEntityType, $this->hostEntityId);
  272. }
  273. elseif ($this->hostEntityRevisionId) {
  274. $this->hostEntity = entity_revision_load($this->hostEntityType, $this->hostEntityRevisionId);
  275. }
  276. $this->fetchedHostEntityDetails = TRUE;
  277. }
  278. else {
  279. $this->hostEntityId = FALSE;
  280. $this->hostEntityRevisionId = FALSE;
  281. $this->hostEntityBundle = FALSE;
  282. $this->fetchedHostEntityDetails = FALSE;
  283. }
  284. }
  285. else {
  286. // No host entity available yet.
  287. $this->hostEntityId = FALSE;
  288. $this->hostEntityRevisionId = FALSE;
  289. $this->hostEntityBundle = FALSE;
  290. $this->fetchedHostEntityDetails = FALSE;
  291. }
  292. }
  293. return $this->fetchedHostEntityDetails;
  294. }
  295. /**
  296. * Determines the $delta of the reference pointing to this paragraph
  297. * item.
  298. */
  299. public function delta() {
  300. if (($entity = $this->hostEntity()) && isset($entity->{$this->field_name})) {
  301. foreach ($entity->{$this->field_name} as $langcode => &$data) {
  302. foreach ($data as $delta => $item) {
  303. if (isset($item['value']) && $item['value'] == $this->item_id) {
  304. $this->langcode = $langcode;
  305. return $delta;
  306. }
  307. elseif (isset($item['entity']) && $item['entity'] === $this) {
  308. $this->langcode = $langcode;
  309. return $delta;
  310. }
  311. }
  312. }
  313. }
  314. }
  315. /**
  316. * Determines the language code under which the item is stored.
  317. */
  318. public function langcode() {
  319. if ($this->delta() !== NULL) {
  320. return $this->langcode;
  321. }
  322. }
  323. /**
  324. * Determines whether this paragraphs item revision is in use.
  325. *
  326. * paragraphs items may be contained in from non-default host entity
  327. * revisions. If the paragraphs item does not appear in the default
  328. * host entity revision, the item is actually not used by default and so
  329. * marked as 'archived'.
  330. * If the paragraphs item appears in the default revision of the host
  331. * entity, the default revision of the paragraphs item is in use there
  332. * and the collection is not marked as archived.
  333. */
  334. public function isInUse() {
  335. return $this->default_revision && !$this->archived;
  336. }
  337. /**
  338. * Save the paragraphs item.
  339. *
  340. * By default, always save the host entity, so modules are able to react
  341. * upon changes to the content of the host and any 'last updated' dates of
  342. * entities get updated.
  343. *
  344. * For creating an item a host entity has to be specified via setHostEntity()
  345. * before this function is invoked. For the link between the entities to be
  346. * fully established, the host entity object has to be updated to include a
  347. * reference on this paragraphs item during saving. So do not skip
  348. * saving the host for creating items.
  349. *
  350. * @param $skip_host_save
  351. * (internal) If TRUE is passed, the host entity is not saved automatically
  352. * and therefore no link is created between the host and the item or
  353. * revision updates might be skipped. Use with care.
  354. */
  355. public function save($skip_host_save = FALSE) {
  356. // Only save directly if we are told to skip saving the host entity. Else,
  357. // we always save via the host as saving the host might trigger saving
  358. // paragraphs items anyway (for example, if a new revision is created).
  359. if ($skip_host_save) {
  360. return entity_get_controller($this->entityType)->save($this);
  361. }
  362. else {
  363. $host_entity = $this->hostEntity();
  364. if (!$host_entity) {
  365. throw new Exception("Unable to save a paragraph without a valid reference to a host entity.");
  366. }
  367. // If this is creating a new revision, also do so for the host entity.
  368. if (!empty($this->revision) || !empty($this->is_new_revision)) {
  369. $host_entity->revision = TRUE;
  370. if (!empty($this->default_revision)) {
  371. entity_revision_set_default($this->hostEntityType, $host_entity);
  372. }
  373. }
  374. // Set the host entity reference, so the item will be saved with the host.
  375. // @see paragraphs_field_presave()
  376. $delta = $this->delta();
  377. if (isset($delta)) {
  378. $host_entity->{$this->field_name}[$this->langcode][$delta] = array('entity' => $this);
  379. }
  380. else {
  381. $host_entity->{$this->field_name}[$this->langcode][] = array('entity' => $this);
  382. }
  383. return entity_save($this->hostEntityType, $host_entity);
  384. }
  385. }
  386. /**
  387. * Deletes the host entity's reference of the paragraphs item.
  388. */
  389. protected function deleteHostEntityReference() {
  390. $delta = $this->delta();
  391. if ($this->item_id && isset($delta)) {
  392. unset($this->hostEntity->{$this->field_name}[$this->langcode][$delta]);
  393. entity_save($this->hostEntityType, $this->hostEntity);
  394. }
  395. }
  396. /**
  397. * Intelligently delete a paragraphs item revision.
  398. *
  399. * If a host entity is revisioned with its paragraphs items, deleting
  400. * a paragraphs item on the default revision of the host should not
  401. * delete the collection item from archived revisions too. Instead, we delete
  402. * the current default revision and archive the paragraph.
  403. *
  404. */
  405. public function deleteRevision($skip_host_update = FALSE) {
  406. if (!$this->revision_id) {
  407. return;
  408. }
  409. if (!$skip_host_update) {
  410. // Just remove the item from the host, which cares about deleting the
  411. // item (depending on whether the update creates a new revision).
  412. $this->deleteHostEntityReference();
  413. }
  414. if (!$this->isDefaultRevision()) {
  415. entity_revision_delete('paragraphs_item', $this->revision_id);
  416. }
  417. // If deleting the default revision, take care!
  418. else {
  419. $row = db_select('paragraphs_item_revision', 'r')
  420. ->fields('r')
  421. ->condition('item_id', $this->item_id)
  422. ->condition('revision_id', $this->revision_id, '<>')
  423. ->execute()
  424. ->fetchAssoc();
  425. // If no other revision is left, delete. Else archive the item.
  426. if (!$row) {
  427. $this->delete();
  428. }
  429. else {
  430. // Make the other revision the default revision and archive the item.
  431. db_update('paragraphs_item')
  432. ->fields(array('archived' => 1, 'revision_id' => $row['revision_id']))
  433. ->condition('item_id', $this->item_id)
  434. ->execute();
  435. entity_get_controller('paragraphs_item')->resetCache(array($this->item_id));
  436. entity_revision_delete('paragraphs_item', $this->revision_id);
  437. }
  438. }
  439. }
  440. /**
  441. * Export the paragraphs item.
  442. *
  443. * Since paragraphs entities are not directly exportable (that is, do not
  444. * have 'exportable' set to TRUE in hook_entity_info()) and since Features
  445. * calls this method when exporting the paragraphs as a field attached
  446. * to another entity, we return the export in the format expected by
  447. * Features, rather than in the normal Entity::export() format.
  448. */
  449. public function export($prefix = '') {
  450. // Based on code in EntityDefaultFeaturesController::export_render().
  451. $export = "entity_import('" . $this->entityType() . "', '";
  452. $export .= addcslashes(parent::export(), '\\\'');
  453. $export .= "')";
  454. return $export;
  455. }
  456. /**
  457. * Ensure file fields on the entity have their URIs loaded for previews.
  458. * Copied from #1447338-7, thanks!
  459. */
  460. public function view($view_mode = 'full', $langcode = NULL, $page = NULL) {
  461. if ($langcode == NULL) {
  462. $langcode = LANGUAGE_NONE;
  463. }
  464. // Iterate over fields in the collection to add URIs for image fields.
  465. $field_instances = field_info_instances($this->entityType, $this->bundle);
  466. foreach ($field_instances as $field_name => $field) {
  467. $info = field_info_field($field_name);
  468. if (is_array($info) && $info['type'] == 'image' && $image_field = &$this->{$field_name}) {
  469. // Add the URI to the field on the entity for display.
  470. if (isset($image_field[$langcode])) {
  471. foreach ($image_field[$langcode] as &$field_to_be_updated) {
  472. if (!isset($field_to_be_updated['uri']) && isset($field_to_be_updated['fid'])) {
  473. $image = file_load($field_to_be_updated['fid']);
  474. if ($image) {
  475. $field_to_be_updated['uri'] = $image->uri;
  476. }
  477. }
  478. }
  479. }
  480. }
  481. }
  482. return entity_get_controller($this->entityType)->view(array($this), $view_mode, $langcode, $page);
  483. }
  484. /**
  485. * Magic method to only serialize what's necessary.
  486. */
  487. public function __sleep() {
  488. $vars = get_object_vars($this);
  489. unset($vars['entityInfo'], $vars['idKey'], $vars['nameKey'], $vars['statusKey']);
  490. unset($vars['fieldInfo']);
  491. // Also do not serialize the host entity.
  492. // We add our hostEntity in code.
  493. unset($vars['hostEntity']);
  494. // We unset our host entity, we have to let our object know.
  495. unset($vars['fetchedHostEntityDetails']);
  496. // Also key the returned array with the variable names so the method may
  497. // be easily overridden and customized.
  498. return drupal_map_assoc(array_keys($vars));
  499. }
  500. /**
  501. * Magic method to invoke setUp() on unserialization.
  502. *
  503. * @todo: Remove this once it appears in a released entity API module version.
  504. */
  505. public function __wakeup() {
  506. $this->setUp();
  507. }
  508. }