CerFieldHandler.inc 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. <?php
  2. /**
  3. * @file
  4. * Contains CerFieldHandler.
  5. */
  6. /**
  7. * @class
  8. * Handles low-level operations for a single field on a single entity. Exposes
  9. * methods to add, delete and check for references. This will also iterate over
  10. * the references, returning each one as an EntityDrupalWrapper object.
  11. */
  12. class CerFieldHandler implements Countable, SeekableIterator {
  13. /**
  14. * @var CerField
  15. */
  16. protected $field;
  17. /**
  18. * @var EntityDrupalWrapper
  19. */
  20. protected $entity;
  21. /**
  22. * @var EntityMetadataWrapper
  23. */
  24. protected $value;
  25. /**
  26. * @var integer
  27. */
  28. protected $delta = 0;
  29. /**
  30. * @var boolean
  31. */
  32. protected $isMultiValue;
  33. public function __construct(CerField $field, EntityDrupalWrapper $entity) {
  34. $this->field = $field;
  35. $this->entity = $entity;
  36. $this->value = $entity->{ $field->name };
  37. $this->isMultiValue = ($this->value instanceof EntityListWrapper);
  38. $this->rewind();
  39. }
  40. /**
  41. * Adds a reference to $entity, validating it first.
  42. *
  43. * @param EntityDrupalWrapper $entity
  44. * The wrapped entity to reference.
  45. */
  46. public function add(EntityDrupalWrapper $entity) {
  47. if ($this->validate($entity)) {
  48. $this->write();
  49. }
  50. }
  51. /**
  52. * Deletes all references to $entity.
  53. *
  54. * @param EntityDrupalWrapper $entity
  55. * The wrapped entity to dereference.
  56. */
  57. public function delete(EntityDrupalWrapper $entity) {
  58. $entityID = $entity->getIdentifier();
  59. if ($this->isMultiValue) {
  60. foreach ($this->value as $delta => $ref) {
  61. if ($entityID == $ref->getIdentifier()) {
  62. $this->value[$delta]->set(NULL);
  63. }
  64. }
  65. }
  66. elseif ($entityID == $this->value->getIdentifier()) {
  67. $this->value->set(NULL);
  68. }
  69. $this->write();
  70. }
  71. /**
  72. * Validates a potential reference. After doing a cardinality check, the
  73. * reference is validated through the Field Attach API, allowing the module
  74. * which owns the field to do its normal validation logic. If validation
  75. * fails, the error(s) are logged.
  76. *
  77. * @param EntityDrupalWrapper $entity
  78. * The wrapped entity to validate.
  79. *
  80. * @return boolean
  81. */
  82. protected function validate(EntityDrupalWrapper $entity) {
  83. // Before we do anything else, check that the field has enough space to add the
  84. // reference. If there isn't, bail out so we don't blindly overwrite existing
  85. // field data.
  86. if ($this->checkCardinality()) {
  87. // Keep the previous value so we can restore it if validation fails.
  88. $prev_value = $this->value->value();
  89. if ($this->isMultiValue) {
  90. $value = $this->value->value();
  91. $value[] = $entity->value();
  92. $this->value->set($value);
  93. }
  94. else {
  95. $this->value->set( $entity->value() );
  96. }
  97. // Leverage the Field Attach API to validate the reference. If errors occur,
  98. // field_attach_validate() throws FieldValidationException, containing an array
  99. // of every validation error.
  100. try {
  101. // Only validate this field.
  102. field_attach_validate($this->entity->type(), $this->entity->value(), array('field_name' => $this->field->name));
  103. return TRUE;
  104. }
  105. catch (FieldValidationException $e) {
  106. foreach ($e->errors as $field) {
  107. foreach ($field as $language) {
  108. foreach ($language as $errors) {
  109. foreach ($errors as $error) {
  110. $this->logError($error['message'], $entity);
  111. }
  112. }
  113. }
  114. }
  115. $this->value->set($prev_value);
  116. }
  117. }
  118. else {
  119. $this->logError('Cannot add reference to !that_link from !field_label on !this_link because there are no more slots available.', $entity);
  120. }
  121. return FALSE;
  122. }
  123. /**
  124. * Checks that there are enough slots in the field to add a reference.
  125. *
  126. * @return boolean
  127. */
  128. protected function checkCardinality() {
  129. return ($this->field->cardinality == FIELD_CARDINALITY_UNLIMITED ? TRUE : ($this->field->cardinality > $this->count()));
  130. }
  131. /**
  132. * Saves changes to the entity and resets the iterator.
  133. */
  134. protected function write() {
  135. $entity_type = $this->entity->type();
  136. $entityID = $this->entity->getIdentifier();
  137. $entity = $this->entity->value();
  138. $entity->cer_processed = TRUE;
  139. entity_save($entity_type, $entity);
  140. // Reload the entity we just saved and cleared from the static cache.
  141. $entities = entity_load($entity_type, (array) $entityID);
  142. $this->entity->set($entities[$entityID]);
  143. $this->__construct($this->field, $this->entity);
  144. }
  145. /**
  146. * Logs an error, optionally against a specific entity. If the cer_debug
  147. * variable is set, the error will also be set as a message.
  148. *
  149. * @param string $message
  150. * The untranslated message to log.
  151. *
  152. * @param EntityDrupalWrapper $entity
  153. * The entity that has caused the error, if any.
  154. */
  155. protected function logError($message, EntityDrupalWrapper $entity = NULL) {
  156. $variables = array(
  157. '!field_name' => $this->field->name,
  158. '!field_type' => $this->field->fieldTypeLabel,
  159. '!field_label' => $this->field->label,
  160. );
  161. $variables['!this_type'] = $this->entity->type();
  162. $variables['!this_label'] = $this->entity->label();
  163. // If the entity has a URI, provide a link to it. Otherwise, its "link"
  164. // will just be an unlinked label. Entity API doesn't reliably expose a url
  165. // property on entities, and there doesn't appear to be a way to check for
  166. // it without risking an EntityMetadataWrapperException. So I need to use
  167. // this clunky BS instead...ugh.
  168. $this_uri = entity_uri($this->entity->type(), $this->entity->value());
  169. if (isset($this_uri)) {
  170. $variables['!this_url'] = url($this_uri['path'], $this_uri['options']);
  171. $variables['!this_link'] = l($this->entity->label(), $this_uri['path'], $this_uri['options']);
  172. }
  173. else {
  174. $variables['!this_link'] = $this->entity->label();
  175. }
  176. if ($entity) {
  177. $variables['!that_type'] = $entity->type();
  178. $variables['!that_label'] = $entity->label();
  179. // If the entity has a URI, link to it.
  180. $that_uri = entity_uri($entity->type(), $entity->value());
  181. if (isset($that_uri)) {
  182. $variables['!that_url'] = url($that_uri['path'], $that_uri['options']);
  183. $variables['!that_link'] = l($entity->label(), $that_uri['path'], $that_uri['options']);
  184. }
  185. else {
  186. $variables['!that_link'] = $entity->label();
  187. }
  188. }
  189. watchdog('cer', $message, $variables, WATCHDOG_ERROR);
  190. if (variable_get('cer_debug', FALSE)) {
  191. drupal_set_message(t($message, $variables), 'error');
  192. }
  193. }
  194. public function getIDs() {
  195. $IDs = array();
  196. if ($this->isMultiValue) {
  197. foreach ($this->value as $ref) {
  198. $IDs[] = $ref->raw();
  199. }
  200. }
  201. else {
  202. $IDs[] = $this->value->raw();
  203. }
  204. return array_unique(array_filter($IDs));
  205. }
  206. /**
  207. * Implements Countable::count().
  208. */
  209. public function count() {
  210. if ($this->isMultiValue) {
  211. return sizeof($this->value);
  212. }
  213. else {
  214. return ($this->value->value() ? 1 : 0);
  215. }
  216. }
  217. /**
  218. * Implements SeekableIterator::seek().
  219. */
  220. public function seek($position) {
  221. $length = $this->count();
  222. if ($position < 0) {
  223. $position += $length;
  224. }
  225. if ($position >= 0 && $position < $length) {
  226. $this->delta = $position;
  227. }
  228. else {
  229. throw new OutOfBoundsException(t('Cannot seek to invalid position.'));
  230. }
  231. }
  232. /**
  233. * Implements Iterator::current().
  234. */
  235. public function current() {
  236. return ($this->isMultiValue ? $this->value[$this->delta] : $this->value);
  237. }
  238. /**
  239. * Implements Iterator::key().
  240. */
  241. public function key() {
  242. return $this->current()->getIdentifier();
  243. }
  244. /**
  245. * Implements Iterator::next().
  246. */
  247. public function next() {
  248. $this->delta++;
  249. }
  250. /**
  251. * Implements Iterator::rewind().
  252. */
  253. public function rewind() {
  254. $this->delta = 0;
  255. }
  256. /**
  257. * Implements Iterator::valid().
  258. */
  259. public function valid() {
  260. return ($this->delta < $this->count());
  261. }
  262. }