Elgg  Version master
ElggEntity.php
Go to the documentation of this file.
1 <?php
2 
10 
48 abstract class ElggEntity extends \ElggData {
49 
50  use Icons;
51  use Subscriptions;
52 
53  public const PRIMARY_ATTR_NAMES = [
54  'guid',
55  'type',
56  'subtype',
57  'owner_guid',
58  'container_guid',
59  'access_id',
60  'time_created',
61  'time_updated',
62  'last_action',
63  'enabled',
64  'deleted',
65  'time_deleted',
66  ];
67 
71  protected const INTEGER_ATTR_NAMES = [
72  'guid',
73  'owner_guid',
74  'container_guid',
75  'access_id',
76  'time_created',
77  'time_updated',
78  'last_action',
79  'time_deleted',
80  ];
81 
87  protected $temp_metadata = [];
88 
94  protected $temp_annotations = [];
95 
101  protected $volatile = [];
102 
107  protected $orig_attributes = [];
108 
112  protected $_is_cacheable = true;
113 
124  protected $_cached_metadata;
125 
140  public function __construct(stdClass $row = null) {
141  $this->initializeAttributes();
142 
143  if (!empty($row) && !$this->load($row)) {
144  throw new IOException('Failed to load new ' . get_class() . " for GUID: {$row->guid}");
145  }
146  }
147 
155  protected function initializeAttributes() {
156  parent::initializeAttributes();
157 
158  $this->attributes['guid'] = null;
159  $this->attributes['type'] = $this->getType();
160  $this->attributes['subtype'] = null;
161 
162  $this->attributes['owner_guid'] = _elgg_services()->session_manager->getLoggedInUserGuid();
163  $this->attributes['container_guid'] = _elgg_services()->session_manager->getLoggedInUserGuid();
164 
165  $this->attributes['access_id'] = ACCESS_PRIVATE;
166  $this->attributes['time_updated'] = null;
167  $this->attributes['last_action'] = null;
168  $this->attributes['enabled'] = 'yes';
169  $this->attributes['deleted'] = 'no';
170  $this->attributes['time_deleted'] = null;
171  }
172 
183  public function __clone() {
184  $orig_entity = get_entity($this->guid);
185  if (!$orig_entity) {
186  _elgg_services()->logger->error("Failed to clone entity with GUID $this->guid");
187  return;
188  }
189 
190  $metadata_array = elgg_get_metadata([
191  'guid' => $this->guid,
192  'limit' => false,
193  ]);
194 
195  $this->attributes['guid'] = null;
196  $this->attributes['time_created'] = null;
197  $this->attributes['time_updated'] = null;
198  $this->attributes['last_action'] = null;
199 
200  $this->attributes['subtype'] = $orig_entity->getSubtype();
201 
202  // copy metadata over to new entity - slightly convoluted due to
203  // handling of metadata arrays
204  if (is_array($metadata_array)) {
205  // create list of metadata names
206  $metadata_names = [];
207  foreach ($metadata_array as $metadata) {
208  $metadata_names[] = $metadata->name;
209  }
210 
211  // arrays are stored with multiple entries per name
212  $metadata_names = array_unique($metadata_names);
213 
214  // move the metadata over
215  foreach ($metadata_names as $name) {
216  $this->__set($name, $orig_entity->$name);
217  }
218  }
219  }
220 
236  public function __set($name, $value) {
237  if ($this->$name === $value) {
238  // quick return if value is not changing
239  return;
240  }
241 
242  if (array_key_exists($name, $this->attributes)) {
243  // if an attribute is 1 (integer) and it's set to "1" (string), don't consider that a change.
244  if (is_int($this->attributes[$name])
245  && is_string($value)
246  && ((string) $this->attributes[$name] === $value)) {
247  return;
248  }
249 
250  // keep original values
251  if ($this->guid && !array_key_exists($name, $this->orig_attributes)) {
252  $this->orig_attributes[$name] = $this->attributes[$name];
253  }
254 
255  // Certain properties should not be manually changed!
256  switch ($name) {
257  case 'guid':
258  case 'last_action':
259  case 'time_deleted':
260  case 'time_updated':
261  case 'type':
262  return;
263  case 'subtype':
264  throw new ElggInvalidArgumentException(elgg_echo('ElggEntity:Error:SetSubtype', ['setSubtype()']));
265  case 'enabled':
266  throw new ElggInvalidArgumentException(elgg_echo('ElggEntity:Error:SetEnabled', ['enable() / disable()']));
267  case 'deleted':
268  throw new ElggInvalidArgumentException(elgg_echo('ElggEntity:Error:SetDeleted', ['delete() / restore()']));
269  case 'access_id':
270  case 'owner_guid':
271  case 'container_guid':
272  if ($value !== null) {
273  $this->attributes[$name] = (int) $value;
274  } else {
275  $this->attributes[$name] = null;
276  }
277  break;
278  default:
279  $this->attributes[$name] = $value;
280  break;
281  }
282 
283  return;
284  }
285 
286  $this->setMetadata($name, $value);
287  }
288 
294  public function getOriginalAttributes(): array {
295  return $this->orig_attributes;
296  }
297 
311  public function __get($name) {
312  if (array_key_exists($name, $this->attributes)) {
313  return $this->attributes[$name];
314  }
315 
316  return $this->getMetadata($name);
317  }
318 
324  public function getDisplayName(): string {
325  return (string) $this->name;
326  }
327 
335  public function setDisplayName(string $display_name): void {
336  $this->name = $display_name;
337  }
338 
346  public function getMetadata(string $name) {
347  $metadata = $this->getAllMetadata();
348  return elgg_extract($name, $metadata);
349  }
350 
356  public function getAllMetadata(): array {
357  if (!$this->guid) {
358  return array_map(function($values) {
359  return count($values) > 1 ? $values : $values[0];
361  }
362 
363  $this->_cached_metadata = _elgg_services()->metadataCache->getAll($this->guid);
364 
366  }
367 
383  public function setMetadata(string $name, $value, string $value_type = '', bool $multiple = false): bool {
384 
385  if ($value === null || $value === '') {
386  return $this->deleteMetadata($name);
387  }
388 
389  // normalize value to an array that we will loop over
390  // remove indexes if value already an array.
391  if (is_array($value)) {
392  $value = array_values($value);
393  } else {
394  $value = [$value];
395  }
396 
397  // strip null values from array
398  $value = array_filter($value, function($var) {
399  return !is_null($var);
400  });
401 
402  if (empty($this->guid)) {
403  // unsaved entity. store in temp array
404  return $this->setTempMetadata($name, $value, $multiple);
405  }
406 
407  // saved entity. persist md to db.
408  if (!$multiple) {
409  $current_metadata = $this->getMetadata($name);
410 
411  if ((is_array($current_metadata) || count($value) > 1 || $value === []) && isset($current_metadata)) {
412  // remove current metadata if needed
413  // need to remove access restrictions right now to delete
414  // because this is the expected behavior
415  $delete_result = elgg_call(ELGG_IGNORE_ACCESS, function() use ($name) {
416  return elgg_delete_metadata([
417  'guid' => $this->guid,
418  'metadata_name' => $name,
419  'limit' => false,
420  ]);
421  });
422 
423  if ($delete_result === false) {
424  return false;
425  }
426  }
427 
428  if (count($value) > 1) {
429  // new value is a multiple valued metadata
430  $multiple = true;
431  }
432  }
433 
434  // create new metadata
435  foreach ($value as $value_tmp) {
436  $metadata = new ElggMetadata();
437  $metadata->entity_guid = $this->guid;
438  $metadata->name = $name;
439  $metadata->value = $value_tmp;
440 
441  if (!empty($value_type)) {
442  $metadata->value_type = $value_type;
443  }
444 
445  $md_id = _elgg_services()->metadataTable->create($metadata, $multiple);
446  if ($md_id === false) {
447  return false;
448  }
449  }
450 
451  return true;
452  }
453 
464  protected function setTempMetadata(string $name, $value, bool $multiple = false): bool {
465  // if overwrite, delete first
466  if (!$multiple) {
467  unset($this->temp_metadata[$name]);
468  if (count($value)) {
469  // only save if value array contains data
470  $this->temp_metadata[$name] = $value;
471  }
472 
473  return true;
474  }
475 
476  if (!isset($this->temp_metadata[$name])) {
477  $this->temp_metadata[$name] = [];
478  }
479 
480  $this->temp_metadata[$name] = array_merge($this->temp_metadata[$name], $value);
481 
482  return true;
483  }
484 
496  public function deleteMetadata(string $name = null): bool {
497 
498  if (!$this->guid) {
499  // remove from temp_metadata
500  if (isset($name)) {
501  if (isset($this->temp_metadata[$name])) {
502  unset($this->temp_metadata[$name]);
503  }
504  } else {
505  $this->temp_metadata = [];
506  }
507 
508  return true;
509  }
510 
511  return elgg_delete_metadata([
512  'guid' => $this->guid,
513  'limit' => false,
514  'metadata_name' => $name,
515  ]);
516  }
517 
525  public function getVolatileData(string $name) {
526  return array_key_exists($name, $this->volatile) ? $this->volatile[$name] : null;
527  }
528 
537  public function setVolatileData(string $name, $value): void {
538  $this->volatile[$name] = $value;
539  }
540 
552  public function addRelationship(int $guid_two, string $relationship): bool {
553  return _elgg_services()->relationshipsTable->add($this->guid, (string) $relationship, (int) $guid_two);
554  }
555 
567  public function hasRelationship(int $guid_two, string $relationship): bool {
568  return (bool) _elgg_services()->relationshipsTable->check($this->guid, $relationship, $guid_two);
569  }
570 
580  public function getRelationship(int $guid_two, string $relationship): ?\ElggRelationship {
581  return _elgg_services()->relationshipsTable->check($this->guid, $relationship, $guid_two) ?: null;
582  }
583 
594  public function getEntitiesFromRelationship(array $options = []) {
595  $options['relationship_guid'] = $this->guid;
596  return elgg_get_entities($options);
597  }
598 
607  public function countEntitiesFromRelationship(string $relationship, bool $inverse_relationship = false): int {
608  return elgg_count_entities([
609  'relationship' => $relationship,
610  'relationship_guid' => $this->guid,
611  'inverse_relationship' => $inverse_relationship,
612  ]);
613  }
614 
623  public function removeRelationship(int $guid_two, string $relationship): bool {
624  return _elgg_services()->relationshipsTable->remove($this->guid, (string) $relationship, (int) $guid_two);
625  }
626 
640  public function removeAllRelationships(string $relationship = '', bool $inverse_relationship = false): bool {
641  return _elgg_services()->relationshipsTable->removeAll($this->guid, $relationship, $inverse_relationship);
642  }
643 
649  public function removeAllRelatedRiverItems(): void {
650  elgg_delete_river(['subject_guid' => $this->guid, 'limit' => false]);
651  elgg_delete_river(['object_guid' => $this->guid, 'limit' => false]);
652  elgg_delete_river(['target_guid' => $this->guid, 'limit' => false]);
653  }
654 
666  public function deleteAnnotations(string $name = null): bool {
667  if ($this->guid) {
668  return elgg_delete_annotations([
669  'guid' => $this->guid,
670  'limit' => false,
671  'annotation_name' => $name,
672  ]);
673  }
674 
675  if ($name) {
676  unset($this->temp_annotations[$name]);
677  } else {
678  $this->temp_annotations = [];
679  }
680 
681  return true;
682  }
683 
693  public function deleteOwnedAnnotations(string $name = null): bool {
694  // access is turned off for this because they might
695  // no longer have access to an entity they created annotations on
696  return elgg_call(ELGG_IGNORE_ACCESS, function() use ($name) {
697  return elgg_delete_annotations([
698  'annotation_owner_guid' => $this->guid,
699  'limit' => false,
700  'annotation_name' => $name,
701  ]);
702  });
703  }
704 
713  private function getAnnotationCalculation($name, $calculation) {
714  $options = [
715  'guid' => $this->getGUID(),
716  'distinct' => false,
717  'annotation_name' => $name,
718  'annotation_calculation' => $calculation
719  ];
720 
722  }
723 
743  public function annotate($name, $value, $access_id = ACCESS_PRIVATE, $owner_guid = 0, $value_type = '') {
744  if (!$this->guid) {
745  $this->temp_annotations[$name] = $value;
746  return true;
747  }
748 
749  if (!$owner_guid) {
750  $owner_guid = _elgg_services()->session_manager->getLoggedInUserGuid();
751  }
752 
753  $annotation = new ElggAnnotation();
754  $annotation->entity_guid = $this->guid;
755  $annotation->name = $name;
756  $annotation->value = $value;
757  $annotation->owner_guid = $owner_guid;
758  $annotation->access_id = $access_id;
759 
760  if (!empty($value_type)) {
761  $annotation->value_type = $value_type;
762  }
763 
764  if ($annotation->save()) {
765  return $annotation->id;
766  }
767 
768  return false;
769  }
770 
782  public function getAnnotations(array $options = []) {
783  if ($this->guid) {
784  $options['guid'] = $this->guid;
785 
787  } else {
788  $name = elgg_extract('annotation_name', $options, '');
789 
790  if (isset($this->temp_annotations[$name])) {
791  return [$this->temp_annotations[$name]];
792  }
793  }
794 
795  return [];
796  }
797 
805  public function countAnnotations(string $name = ''): int {
806  return $this->getAnnotationCalculation($name, 'count');
807  }
808 
816  public function getAnnotationsAvg(string $name) {
817  return $this->getAnnotationCalculation($name, 'avg');
818  }
819 
827  public function getAnnotationsSum(string $name) {
828  return $this->getAnnotationCalculation($name, 'sum');
829  }
830 
838  public function getAnnotationsMin(string $name) {
839  return $this->getAnnotationCalculation($name, 'min');
840  }
841 
849  public function getAnnotationsMax(string $name) {
850  return $this->getAnnotationCalculation($name, 'max');
851  }
852 
859  public function countComments(): int {
860  if (!$this->hasCapability('commentable')) {
861  return 0;
862  }
863 
864  $params = ['entity' => $this];
865  $num = _elgg_services()->events->triggerResults('comments:count', $this->getType(), $params);
866 
867  if (is_int($num)) {
868  return $num;
869  }
870 
871  return \Elgg\Comments\DataService::instance()->getCommentsCount($this);
872  }
873 
884  public function getOwnedAccessCollections(array $options = []): array {
885  $options['owner_guid'] = $this->guid;
886  return _elgg_services()->accessCollections->getEntityCollections($options);
887  }
888 
900  if ($subtype === '') {
901  throw new ElggInvalidArgumentException(__METHOD__ . ' requires $subtype to be non empty');
902  }
903 
904  $acls = $this->getOwnedAccessCollections([
905  'subtype' => $subtype,
906  ]);
907 
908  return elgg_extract(0, $acls);
909  }
910 
919  public function hasAccess(int $user_guid = 0): bool {
920  return _elgg_services()->accessCollections->hasAccessToEntity($this, $user_guid);
921  }
922 
932  public function canEdit(int $user_guid = 0): bool {
933  return _elgg_services()->userCapabilities->canEdit($this, $user_guid);
934  }
935 
946  public function canDelete(int $user_guid = 0): bool {
947  return _elgg_services()->userCapabilities->canDelete($this, $user_guid);
948  }
949 
960  public function canWriteToContainer(int $user_guid = 0, string $type = '', string $subtype = ''): bool {
961  if (empty($type) || empty($subtype)) {
962  throw new ElggInvalidArgumentException(__METHOD__ . ' requires $type and $subtype to be set');
963  }
964 
965  return _elgg_services()->userCapabilities->canWriteToContainer($this, $type, $subtype, $user_guid);
966  }
967 
977  public function canComment(int $user_guid = 0): bool {
978  return _elgg_services()->userCapabilities->canComment($this, $user_guid);
979  }
980 
995  public function canAnnotate(int $user_guid = 0, string $annotation_name = ''): bool {
996  return _elgg_services()->userCapabilities->canAnnotate($this, $user_guid, $annotation_name);
997  }
998 
1004  public function getGUID(): ?int {
1005  return $this->guid;
1006  }
1007 
1013  public function getType(): string {
1014  // this is just for the PHPUnit mocking framework
1015  return (string) $this->type;
1016  }
1017 
1026  public function setSubtype(string $subtype): void {
1027  // keep original values
1028  if ($this->guid && !array_key_exists('subtype', $this->orig_attributes)) {
1029  $this->orig_attributes['subtype'] = $this->attributes['subtype'];
1030  }
1031 
1032  $this->attributes['subtype'] = $subtype;
1033  }
1034 
1040  public function getSubtype(): string {
1041  return (string) $this->attributes['subtype'];
1042  }
1043 
1049  public function getOwnerGUID(): int {
1050  return (int) $this->owner_guid;
1051  }
1052 
1058  public function getOwnerEntity(): ?\ElggEntity {
1059  return $this->owner_guid ? get_entity($this->owner_guid) : null;
1060  }
1061 
1069  public function setContainerGUID(int $container_guid): void {
1071  }
1072 
1078  public function getContainerGUID(): int {
1079  return (int) $this->container_guid;
1080  }
1081 
1088  public function getContainerEntity(): ?\ElggEntity {
1089  return $this->container_guid ? get_entity($this->getContainerGUID()) : null;
1090  }
1091 
1097  public function getTimeUpdated(): int {
1098  return (int) $this->time_updated;
1099  }
1100 
1109  public function getURL(): string {
1110  $url = elgg_generate_entity_url($this, 'view');
1111 
1112  $url = _elgg_services()->events->triggerResults('entity:url', $this->getType(), ['entity' => $this], $url);
1113 
1114  if (empty($url)) {
1115  return '';
1116  }
1117 
1118  return elgg_normalize_url($url);
1119  }
1120 
1124  public function save(): bool {
1125  if ($this->guid > 0) {
1126  $result = $this->update();
1127  } else {
1128  $result = $this->create() !== false;
1129  }
1130 
1131  if ($result) {
1132  $this->cache();
1133  }
1134 
1135  return $result;
1136  }
1137 
1150  protected function create() {
1151 
1152  $type = $this->attributes['type'];
1153  if (!in_array($type, \Elgg\Config::ENTITY_TYPES)) {
1154  throw new ElggDomainException('Entity type must be one of the allowed types: ' . implode(', ', \Elgg\Config::ENTITY_TYPES));
1155  }
1156 
1157  $subtype = $this->attributes['subtype'];
1158  if (!$subtype) {
1159  throw new ElggInvalidArgumentException('All entities must have a subtype');
1160  }
1161 
1162  $owner_guid = (int) $this->attributes['owner_guid'];
1163  $access_id = (int) $this->attributes['access_id'];
1164  $now = $this->getCurrentTime()->getTimestamp();
1165  $time_created = isset($this->attributes['time_created']) ? (int) $this->attributes['time_created'] : $now;
1166  $deleted = $this->attributes['deleted'];
1167  $time_deleted = (int) $this->attributes['time_deleted'];
1168 
1169  $container_guid = $this->attributes['container_guid'];
1170  if ($container_guid == 0) {
1172  $this->attributes['container_guid'] = $container_guid;
1173  }
1174 
1176 
1177  if ($access_id === ACCESS_DEFAULT) {
1178  throw new ElggInvalidArgumentException('ACCESS_DEFAULT is not a valid access level. See its documentation in constants.php');
1179  }
1180 
1181  if ($access_id === ACCESS_FRIENDS) {
1182  throw new ElggInvalidArgumentException('ACCESS_FRIENDS is not a valid access level. See its documentation in constants.php');
1183  }
1184 
1185  $user_guid = _elgg_services()->session_manager->getLoggedInUserGuid();
1186 
1187  // If given an owner, verify it can be loaded
1188  if (!empty($owner_guid)) {
1189  $owner = $this->getOwnerEntity();
1190  if (!$owner instanceof \ElggEntity) {
1191  $error = "User {$user_guid} tried to create a ({$type}, {$subtype}),";
1192  $error .= " but the given owner {$owner_guid} could not be loaded.";
1194  }
1195 
1196  // If different owner than logged in, verify can write to container.
1197  if ($user_guid !== $owner_guid && !$owner->canEdit() && !$owner->canWriteToContainer($user_guid, $type, $subtype)) {
1198  $error = "User {$user_guid} tried to create a ({$type}, {$subtype}) with owner {$owner_guid},";
1199  $error .= " but the user wasn't permitted to write to the owner's container.";
1201  }
1202  }
1203 
1204  // If given a container, verify it can be loaded and that the current user can write to it
1205  if (!empty($container_guid)) {
1206  $container = $this->getContainerEntity();
1207  if (!$container instanceof \ElggEntity) {
1208  $error = "User {$user_guid} tried to create a ({$type}, {$subtype}),";
1209  $error .= " but the given container {$container_guid} could not be loaded.";
1211  }
1212 
1213  if (!$container->canWriteToContainer($user_guid, $type, $subtype)) {
1214  $error = "User {$user_guid} tried to create a ({$type}, {$subtype}),";
1215  $error .= " but was not permitted to write to container {$container_guid}.";
1217  }
1218  }
1219 
1220  if (!_elgg_services()->events->triggerBefore('create', $this->type, $this)) {
1221  return false;
1222  }
1223 
1224  // Create primary table row
1225  $guid = _elgg_services()->entityTable->insertRow((object) [
1226  'type' => $type,
1227  'subtype' => $subtype,
1228  'owner_guid' => $owner_guid,
1229  'container_guid' => $container_guid,
1230  'access_id' => $access_id,
1231  'time_created' => $time_created,
1232  'time_updated' => $now,
1233  'last_action' => $now,
1234  'deleted' => $deleted,
1235  'time_deleted' => $time_deleted
1236  ], $this->attributes);
1237 
1238  if (!$guid) {
1239  throw new IOException("Unable to save new object's base entity information!");
1240  }
1241 
1242  $this->attributes['subtype'] = $subtype;
1243  $this->attributes['guid'] = (int) $guid;
1244  $this->attributes['time_created'] = (int) $time_created;
1245  $this->attributes['time_updated'] = (int) $now;
1246  $this->attributes['last_action'] = (int) $now;
1247  $this->attributes['container_guid'] = (int) $container_guid;
1248  $this->attributes['deleted'] = $deleted;
1249  $this->attributes['time_deleted'] = (int) $time_deleted;
1250 
1251  // We are writing this new entity to cache to make sure subsequent calls
1252  // to get_entity() load the entity from cache and not from the DB. This
1253  // MUST come before the metadata and annotation writes below!
1254  $this->cache();
1255 
1256  // Save any unsaved metadata
1257  if (count($this->temp_metadata) > 0) {
1258  foreach ($this->temp_metadata as $name => $value) {
1259  // temp metadata is always an array, but if there is only one value return just the value
1260  $this->setMetadata($name, $value, '', count($value) > 1);
1261  }
1262 
1263  $this->temp_metadata = [];
1264  }
1265 
1266  // Save any unsaved annotations.
1267  if (count($this->temp_annotations) > 0) {
1268  foreach ($this->temp_annotations as $name => $value) {
1269  $this->annotate($name, $value);
1270  }
1271 
1272  $this->temp_annotations = [];
1273  }
1274 
1275  if (isset($container) && !$container instanceof \ElggUser) {
1276  // users have their own logic for setting last action
1277  $container->updateLastAction();
1278  }
1279 
1280  // for BC reasons this event is still needed (for example for notifications)
1281  _elgg_services()->events->trigger('create', $this->type, $this);
1282 
1283  _elgg_services()->events->triggerAfter('create', $this->type, $this);
1284 
1285  return $guid;
1286  }
1287 
1295  protected function update(): bool {
1296 
1297  if (!$this->canEdit()) {
1298  return false;
1299  }
1300 
1301  // give old update event a chance to stop the update
1302  if (!_elgg_services()->events->trigger('update', $this->type, $this)) {
1303  return false;
1304  }
1305 
1306  $this->invalidateCache();
1307 
1308  // See #6225. We copy these after the update event in case a handler changed one of them.
1309  $guid = (int) $this->guid;
1310  $owner_guid = (int) $this->owner_guid;
1311  $access_id = (int) $this->access_id;
1312  $container_guid = (int) $this->container_guid;
1313  $time_created = (int) $this->time_created;
1314  $time = $this->getCurrentTime()->getTimestamp();
1316  $time_deleted = (int) $this->time_deleted;
1317 
1318  if ($access_id == ACCESS_DEFAULT) {
1319  throw new ElggInvalidArgumentException('ACCESS_DEFAULT is not a valid access level. See its documentation in constants.php');
1320  }
1321 
1322  if ($access_id == ACCESS_FRIENDS) {
1323  throw new ElggInvalidArgumentException('ACCESS_FRIENDS is not a valid access level. See its documentation in constants.php');
1324  }
1325 
1326  // Update primary table
1327  $ret = _elgg_services()->entityTable->updateRow($guid, (object) [
1328  'owner_guid' => $owner_guid,
1329  'container_guid' => $container_guid,
1330  'access_id' => $access_id,
1331  'time_created' => $time_created,
1332  'time_updated' => $time,
1333  'guid' => $guid,
1334  'deleted' => $deleted,
1335  'time_deleted' => $time_deleted
1336  ]);
1337  if ($ret === false) {
1338  return false;
1339  }
1340 
1341  $this->attributes['time_updated'] = $time;
1342 
1343  _elgg_services()->events->triggerAfter('update', $this->type, $this);
1344 
1345  $this->orig_attributes = [];
1346 
1347  $this->cache();
1348 
1349  // Handle cases where there was no error BUT no rows were updated!
1350  return true;
1351  }
1352 
1360  protected function load(stdClass $row): bool {
1361  $attributes = array_merge($this->attributes, (array) $row);
1362 
1363  if (array_diff(self::PRIMARY_ATTR_NAMES, array_keys($attributes)) !== []) {
1364  // Some primary attributes are missing
1365  return false;
1366  }
1367 
1368  foreach ($attributes as $name => $value) {
1369  if (!in_array($name, self::PRIMARY_ATTR_NAMES)) {
1370  $this->setVolatileData("select:{$name}", $value);
1371  unset($attributes[$name]);
1372  continue;
1373  }
1374 
1375  if (in_array($name, static::INTEGER_ATTR_NAMES)) {
1376  $attributes[$name] = (int) $value;
1377  }
1378  }
1379 
1380  $this->attributes = $attributes;
1381 
1382  $this->cache();
1383 
1384  return true;
1385  }
1386 
1404  public function disable(string $reason = '', bool $recursive = true): bool {
1405  if (!$this->guid) {
1406  return false;
1407  }
1408 
1409  if (!_elgg_services()->events->trigger('disable', $this->type, $this)) {
1410  return false;
1411  }
1412 
1413  if (!$this->canEdit()) {
1414  return false;
1415  }
1416 
1417  if ($this instanceof ElggUser && !$this->isBanned()) {
1418  // temporarily ban to prevent using the site during disable
1419  // not using ban function to bypass events
1420  $this->setMetadata('banned', 'yes');
1421  $unban_after = true;
1422  } else {
1423  $unban_after = false;
1424  }
1425 
1426  if (!empty($reason)) {
1427  $this->disable_reason = $reason;
1428  }
1429 
1430  $guid = (int) $this->guid;
1431 
1432  if ($recursive) {
1434  $base_options = [
1435  'wheres' => [
1436  function(QueryBuilder $qb, $main_alias) use ($guid) {
1437  return $qb->compare("{$main_alias}.guid", '!=', $guid, ELGG_VALUE_GUID);
1438  },
1439  ],
1440  'limit' => false,
1441  'batch' => true,
1442  'batch_inc_offset' => false,
1443  ];
1444 
1445  foreach (['owner_guid', 'container_guid'] as $db_column) {
1446  $options = $base_options;
1447  $options[$db_column] = $guid;
1448 
1449  $subentities = elgg_get_entities($options);
1450  /* @var $subentity \ElggEntity */
1451  foreach ($subentities as $subentity) {
1452  if (!$subentity->isEnabled()) {
1453  continue;
1454  }
1455 
1456  $subentity->addRelationship($guid, 'disabled_with');
1457  $subentity->disable($reason, true);
1458  }
1459  }
1460  });
1461  }
1462 
1463  $disabled = _elgg_services()->entityTable->disable($this);
1464 
1465  if ($unban_after) {
1466  $this->setMetadata('banned', 'no');
1467  }
1468 
1469  if ($disabled) {
1470  $this->invalidateCache();
1471 
1472  $this->attributes['enabled'] = 'no';
1473  _elgg_services()->events->triggerAfter('disable', $this->type, $this);
1474  }
1475 
1476  return $disabled;
1477  }
1478 
1486  public function enable(bool $recursive = true): bool {
1487  if (empty($this->guid)) {
1488  return false;
1489  }
1490 
1491  if (!_elgg_services()->events->trigger('enable', $this->type, $this)) {
1492  return false;
1493  }
1494 
1495  if (!$this->canEdit()) {
1496  return false;
1497  }
1498 
1500  $result = _elgg_services()->entityTable->enable($this);
1501 
1502  $this->deleteMetadata('disable_reason');
1503 
1504  if ($recursive) {
1505  $disabled_with_it = elgg_get_entities([
1506  'relationship' => 'disabled_with',
1507  'relationship_guid' => $this->guid,
1508  'inverse_relationship' => true,
1509  'limit' => false,
1510  'batch' => true,
1511  'batch_inc_offset' => false,
1512  ]);
1513 
1514  foreach ($disabled_with_it as $e) {
1515  $e->enable($recursive);
1516  $e->removeRelationship($this->guid, 'disabled_with');
1517  }
1518  }
1519 
1520  return $result;
1521  });
1522 
1523  if ($result) {
1524  $this->attributes['enabled'] = 'yes';
1525  _elgg_services()->events->triggerAfter('enable', $this->type, $this);
1526  }
1527 
1528  return $result;
1529  }
1530 
1536  public function isEnabled(): bool {
1537  return $this->enabled == 'yes';
1538  }
1539 
1557  public function delete(bool $recursive = true, bool $persistent = null): bool {
1558  if (!$this->canDelete()) {
1559  return false;
1560  }
1561 
1562  if (!elgg_get_config('trash_enabled')) {
1563  $persistent = true;
1564  }
1565 
1566  if (!isset($persistent)) {
1567  $persistent = !$this->hasCapability('restorable');
1568  }
1569 
1570  try {
1571  if (empty($this->guid) || $persistent) {
1572  return $this->persistentDelete($recursive);
1573  } else {
1574  return $this->trash($recursive);
1575  }
1576  } catch (DatabaseException $ex) {
1577  elgg_log($ex, 'ERROR');
1578  return false;
1579  }
1580  }
1581 
1590  protected function persistentDelete(bool $recursive = true): bool {
1591  return _elgg_services()->entityTable->delete($this, $recursive);
1592  }
1593 
1602  protected function trash(bool $recursive = true): bool {
1603  $result = _elgg_services()->entityTable->trash($this, $recursive);
1604  if ($result) {
1605  $this->attributes['deleted'] = 'yes';
1606  }
1607 
1608  return $result;
1609  }
1610 
1619  public function restore(bool $recursive = true): bool {
1620  if (!$this->isDeleted()) {
1621  return true;
1622  }
1623 
1624  if (empty($this->guid) || !$this->canEdit()) {
1625  return false;
1626  }
1627 
1628  return _elgg_services()->events->triggerSequence('restore', $this->type, $this, function () use ($recursive) {
1630  $result = _elgg_services()->entityTable->restore($this);
1631 
1632  if ($recursive) {
1633  set_time_limit(0);
1634 
1635  /* @var $deleted_with_it \ElggBatch */
1636  $deleted_with_it = elgg_get_entities([
1637  'relationship' => 'deleted_with',
1638  'relationship_guid' => $this->guid,
1639  'inverse_relationship' => true,
1640  'limit' => false,
1641  'batch' => true,
1642  'batch_inc_offset' => false,
1643  ]);
1644 
1645  /* @var $e \ElggEntity */
1646  foreach ($deleted_with_it as $e) {
1647  if (!$e->restore($recursive)) {
1648  $deleted_with_it->reportFailure();
1649  continue;
1650  }
1651 
1652  $e->removeRelationship($this->guid, 'deleted_with');
1653  }
1654  }
1655 
1656  $this->removeAllRelationships('deleted_by', true);
1657 
1658  if ($result) {
1659  $this->attributes['deleted'] = 'no';
1660  $this->attributes['time_deleted'] = 0;
1661  }
1662 
1663  return $result;
1664  });
1665  });
1666  }
1667 
1673  public function isDeleted(): bool {
1674  return $this->deleted === 'yes';
1675  }
1676 
1684  public function toObject(array $params = []) {
1685  $object = $this->prepareObject(new \Elgg\Export\Entity());
1686 
1687  $params['entity'] = $this;
1688 
1689  return _elgg_services()->events->triggerResults('to:object', 'entity', $params, $object);
1690  }
1691 
1699  protected function prepareObject(\Elgg\Export\Entity $object) {
1700  $object->guid = $this->guid;
1701  $object->type = $this->getType();
1702  $object->subtype = $this->getSubtype();
1703  $object->owner_guid = $this->getOwnerGUID();
1704  $object->container_guid = $this->getContainerGUID();
1705  $object->time_created = date('c', $this->getTimeCreated());
1706  $object->time_updated = date('c', $this->getTimeUpdated());
1707  $object->url = $this->getURL();
1708  $object->read_access = (int) $this->access_id;
1709  return $object;
1710  }
1711 
1720  public function setLatLong(float $lat, float $long): void {
1721  $this->{'geo:lat'} = $lat;
1722  $this->{'geo:long'} = $long;
1723  }
1724 
1730  public function getLatitude(): float {
1731  return (float) $this->{'geo:lat'};
1732  }
1733 
1739  public function getLongitude(): float {
1740  return (float) $this->{'geo:long'};
1741  }
1742 
1743  /*
1744  * SYSTEM LOG INTERFACE
1745  */
1746 
1750  public function getSystemLogID(): int {
1751  return (int) $this->getGUID();
1752  }
1753 
1762  public function getObjectFromID(int $id): ?\ElggEntity {
1763  return get_entity($id);
1764  }
1765 
1773 
1774  if (!$this->guid) {
1775  return false;
1776  }
1777 
1778  if ($this->type !== 'user') {
1779  return true;
1780  }
1781 
1782  $ac = _elgg_services()->accessCollections;
1783 
1784  $collections = $ac->getCollectionsByMember($this->guid);
1785  if (empty($collections)) {
1786  return true;
1787  }
1788 
1789  $result = true;
1790  foreach ($collections as $collection) {
1791  $result &= $ac->removeUser($this->guid, $collection->id);
1792  }
1793 
1794  return $result;
1795  }
1796 
1803  public function deleteOwnedAccessCollections() {
1804 
1805  if (!$this->guid) {
1806  return false;
1807  }
1808 
1809  $collections = $this->getOwnedAccessCollections();
1810  if (empty($collections)) {
1811  return true;
1812  }
1813 
1814  $result = true;
1815  foreach ($collections as $collection) {
1816  $result = $result & $collection->delete();
1817  }
1818 
1819  return $result;
1820  }
1821 
1833  public function updateLastAction(int $posted = null): int {
1834  $posted = _elgg_services()->entityTable->updateLastAction($this, $posted);
1835 
1836  $this->attributes['last_action'] = $posted;
1837  $this->cache();
1838 
1839  return $posted;
1840  }
1841 
1850  public function updateTimeDeleted(int $deleted = null): int {
1851  $deleted = _elgg_services()->entityTable->updateTimeDeleted($this, $deleted);
1852 
1853  $this->attributes['time_deleted'] = $deleted;
1854  $this->cache();
1855 
1856  return $deleted;
1857  }
1858 
1865  public function disableCaching(): void {
1866  $this->_is_cacheable = false;
1867  if ($this->guid) {
1868  _elgg_services()->entityCache->delete($this->guid);
1869  }
1870  }
1871 
1878  public function enableCaching(): void {
1879  $this->_is_cacheable = true;
1880  }
1881 
1888  public function isCacheable(): bool {
1889  if (!$this->guid) {
1890  return false;
1891  }
1892 
1893  if (_elgg_services()->session_manager->getIgnoreAccess()) {
1894  return false;
1895  }
1896 
1897  return $this->_is_cacheable;
1898  }
1899 
1906  public function cache(): void {
1907  if (!$this->isCacheable()) {
1908  return;
1909  }
1910 
1911  _elgg_services()->entityCache->save($this);
1912  }
1913 
1920  public function invalidateCache(): void {
1921  if (!$this->guid) {
1922  return;
1923  }
1924 
1925  _elgg_services()->entityCache->delete($this->guid);
1926  _elgg_services()->dataCache->get('metadata')->delete($this->guid);
1927  }
1928 
1937  public function hasCapability(string $capability): bool {
1938  return _elgg_services()->entity_capabilities->hasCapability($this->getType(), $this->getSubtype(), $capability);
1939  }
1940 }
elgg_call(int $flags, Closure $closure)
Calls a callable autowiring the arguments using public DI services and applying logic based on flags...
Definition: elgglib.php:304
deleteOwnedAccessCollections()
Remove all access collections owned by this entity.
getSubtype()
Get the entity subtype.
setMetadata(string $name, $value, string $value_type= '', bool $multiple=false)
Set metadata on this entity.
Definition: ElggEntity.php:383
$user_guid
Definition: login_as.php:10
$deleted
Definition: delete.php:25
getOwnerGUID()
Get the guid of the entity&#39;s owner.
getTimeCreated()
Returns the UNIX epoch time that this entity was created.
Definition: ElggData.php:99
__clone()
Clone an entity.
Definition: ElggEntity.php:183
$params
Saves global plugin settings.
Definition: save.php:13
setVolatileData(string $name, $value)
Set a piece of volatile (non-persisted) data on this entity.
Definition: ElggEntity.php:537
__get($name)
Get an attribute or metadata value.
Definition: ElggEntity.php:311
elgg_get_config(string $name, $default=null)
Get an Elgg configuration value.
getOwnerEntity()
Gets the that owns this entity.
persistentDelete(bool $recursive=true)
Permanently delete the entity from the database.
$owner
Definition: upload.php:7
const ACCESS_DEFAULT
Controls access levels on entities, metadata, and annotations.
Definition: constants.php:9
if($id< 1) $annotation
Definition: delete.php:11
An IO Exception, throw when an IO Exception occurs.
Definition: IOException.php:12
canWriteToContainer(int $user_guid=0, string $type= '', string $subtype= '')
Can a user add an entity to this container.
Definition: ElggEntity.php:960
const PRIMARY_ATTR_NAMES
Definition: ElggEntity.php:53
elgg_delete_annotations(array $options)
Deletes annotations based on $options.
Definition: annotations.php:85
if(!$user||!$user->canDelete()) $name
Definition: delete.php:22
canDelete(int $user_guid=0)
Can a user delete this entity?
Definition: ElggEntity.php:946
const INTEGER_ATTR_NAMES
Definition: ElggEntity.php:71
invalidateCache()
Invalidate cache for entity.
canComment(int $user_guid=0)
Can a user comment on an entity?
Definition: ElggEntity.php:977
initializeAttributes()
Initialize the attributes array.
Definition: ElggEntity.php:155
const ACCESS_FRIENDS
Definition: constants.php:13
if(!$user instanceof\ElggUser) $time_created
Definition: online.php:13
deleteOwnedAnnotations(string $name=null)
Deletes all annotations owned by this object (annotations.owner_guid = $this->guid).
Definition: ElggEntity.php:693
elgg_get_annotations(array $options=[])
Fetch annotations or perform a calculation on them.
Definition: annotations.php:50
countComments()
Count the number of comments attached to this entity.
Definition: ElggEntity.php:859
elgg_delete_river(array $options=[])
Delete river items based on $options.
Definition: river.php:135
getVolatileData(string $name)
Get a piece of volatile (non-persisted) data on this entity.
Definition: ElggEntity.php:525
c Accompany it with the information you received as to the offer to distribute corresponding source complete source code means all the source code for all modules it plus any associated interface definition plus the scripts used to control compilation and installation of the executable as a special the source code distributed need not include anything that is normally and so on of the operating system on which the executable unless that component itself accompanies the executable If distribution of executable or object code is made by offering access to copy from a designated then offering equivalent access to copy the source code from the same place counts as distribution of the source even though third parties are not compelled to copy the source along with the object code You may not or distribute the Program except as expressly provided under this License Any attempt otherwise to sublicense or distribute the Program is void
Definition: LICENSE.txt:215
countEntitiesFromRelationship(string $relationship, bool $inverse_relationship=false)
Gets the number of entities from a specific relationship type.
Definition: ElggEntity.php:607
elgg_echo(string $message_key, array $args=[], string $language= '')
Elgg language module Functions to manage language and translations.
Definition: languages.php:17
enable(bool $recursive=true)
Enable the entity.
$relationship
Elgg default relationship view.
Definition: default.php:10
if(!$annotation instanceof ElggAnnotation) $time
Definition: time.php:20
Entity Annotation.
const ELGG_VALUE_GUID
Definition: constants.php:113
prepareObject(\Elgg\Export\Entity $object)
Prepare an object copy for toObject()
Database abstraction query builder.
getGUID()
Returns the guid.
$time_updated
Definition: time.php:21
getContainerGUID()
Gets the container GUID for this entity.
$type
Definition: delete.php:21
getTimeUpdated()
Returns the UNIX epoch time that this entity was last updated.
enableCaching()
Enable runtime caching for entity.
deleteAnnotations(string $name=null)
Deletes all annotations on this object (annotations.entity_guid = $this->guid).
Definition: ElggEntity.php:666
addRelationship(int $guid_two, string $relationship)
Add a relationship between this an another entity.
Definition: ElggEntity.php:552
canAnnotate(int $user_guid=0, string $annotation_name= '')
Can a user annotate an entity?
Definition: ElggEntity.php:995
cache()
Cache the entity in a session cache.
canEdit(int $user_guid=0)
Can a user edit this entity?
Definition: ElggEntity.php:932
getEntitiesFromRelationship(array $options=[])
Gets an array of entities with a relationship to this entity.
Definition: ElggEntity.php:594
setTempMetadata(string $name, $value, bool $multiple=false)
Set temp metadata on this entity.
Definition: ElggEntity.php:464
$value
Definition: generic.php:51
const ELGG_HIDE_DISABLED_ENTITIES
Definition: constants.php:133
elgg_extract($key, $array, $default=null, bool $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:254
getAnnotationsSum(string $name)
Get the sum of integer type annotations of a given name.
Definition: ElggEntity.php:827
getObjectFromID(int $id)
For a given ID, return the object associated with it.
countAnnotations(string $name= '')
Count annotations.
Definition: ElggEntity.php:805
getCurrentTime($modifier= '')
Get the (cloned) time.
Definition: TimeUsing.php:25
if($who_can_change_language=== 'nobody') elseif($who_can_change_language=== 'admin_only'&&!elgg_is_admin_logged_in()) $options
Definition: language.php:20
const ELGG_IGNORE_ACCESS
elgg_call() flags
Definition: constants.php:130
$owner_guid
$error
Bad request error.
Definition: 400.php:6
getRelationship(int $guid_two, string $relationship)
Return the relationship if this entity has a relationship with another entity.
Definition: ElggEntity.php:580
disableCaching()
Disable runtime caching for entity.
hasRelationship(int $guid_two, string $relationship)
Check if this entity has a relationship with another entity.
Definition: ElggEntity.php:567
setSubtype(string $subtype)
Set the subtype of the entity.
getAnnotationsMin(string $name)
Get the minimum of integer type annotations of given name.
Definition: ElggEntity.php:838
const ACCESS_PRIVATE
Definition: constants.php:10
const ELGG_SHOW_DISABLED_ENTITIES
Definition: constants.php:132
removeAllRelatedRiverItems()
Removes all river items related to this entity.
Definition: ElggEntity.php:649
get_entity(int $guid)
Loads and returns an entity object from a guid.
Definition: entities.php:70
elgg_get_entities(array $options=[])
Fetches/counts entities or performs a calculation on their properties.
Definition: entities.php:507
getAnnotationsAvg(string $name)
Get the average of an integer type annotation.
Definition: ElggEntity.php:816
hasAccess(int $user_guid=0)
Check if the given user has access to this entity.
Definition: ElggEntity.php:919
load(stdClass $row)
Loads attributes from the entities table into the object.
elgg_log($message, $level=\Psr\Log\LogLevel::NOTICE)
Log a message.
Definition: elgglib.php:86
compare(string $x, string $comparison, $y=null, string $type=null, bool $case_sensitive=null)
Build value comparison clause.
elgg_generate_entity_url(ElggEntity $entity, string $resource= 'view', string $subresource=null, array $parameters=[])
Generate entity URL from a named route.
elgg_count_entities(array $options=[])
Returns a count of entities.
Definition: entities.php:518
A generic parent class for database exceptions.
__set($name, $value)
Set an attribute or metadata value for this entity.
Definition: ElggEntity.php:236
deleteMetadata(string $name=null)
Deletes all metadata on this object (metadata.entity_guid = $this->guid).
Definition: ElggEntity.php:496
$container
Definition: delete.php:23
toObject(array $params=[])
Export an entity.
ElggMetadata.
getAnnotations(array $options=[])
Gets an array of annotations.
Definition: ElggEntity.php:782
isDeleted()
Is the entity marked as deleted.
setLatLong(float $lat, float $long)
Set latitude and longitude metadata tags for a given entity.
hasCapability(string $capability)
Checks a specific capability is enabled for the entity type/subtype.
A generic class that contains shared code among , , and .
Definition: ElggData.php:10
deleteAccessCollectionMemberships()
Remove the membership of all access collections for this entity (if the entity is a user) ...
isEnabled()
Is this entity enabled?
const ELGG_SHOW_DELETED_ENTITIES
Definition: constants.php:136
update()
Update the entity in the database.
annotate($name, $value, $access_id=ACCESS_PRIVATE, $owner_guid=0, $value_type= '')
Adds an annotation to an entity.
Definition: ElggEntity.php:743
elgg_get_metadata(array $options=[])
Fetch metadata or perform a calculation on them.
Definition: metadata.php:32
if($entity instanceof\ElggComment) $comment container_guid
Definition: save.php:54
$posted
Definition: comment.php:77
$comment access_id
Definition: save.php:55
if($email instanceof\Elgg\Email) $object
Definition: body.php:24
elgg_delete_metadata(array $options)
Deletes metadata based on $options.
Definition: metadata.php:49
trash(bool $recursive=true)
Move the entity to the trash.
$metadata
Output annotation metadata.
Definition: metadata.php:9
removeAllRelationships(string $relationship= '', bool $inverse_relationship=false)
Remove all relationships to or from this entity.
Definition: ElggEntity.php:640
getType()
Returns the entity type.
$subtype
Definition: delete.php:22
getOwnedAccessCollection(string $subtype)
Returns the first ACL owned by the entity with a given subtype.
Definition: ElggEntity.php:899
updateTimeDeleted(int $deleted=null)
Update the time_deleted column in the entities table.
foreach($plugin_guids as $guid) if(empty($deactivated_plugins)) $url
Definition: deactivate.php:39
removeRelationship(int $guid_two, string $relationship)
Remove a relationship.
Definition: ElggEntity.php:623
$access_id
Definition: access.php:10
create()
Create a new entry in the entities table.
setContainerGUID(int $container_guid)
Set the container for this object.
getAllMetadata()
Get all entity metadata.
Definition: ElggEntity.php:356
_elgg_services()
Get the global service provider.
Definition: elgglib.php:351
$persistent
Definition: login_as.php:21
getOriginalAttributes()
Get the original values of attribute(s) that have been modified since the entity was persisted...
Definition: ElggEntity.php:294
restore(bool $recursive=true)
Restore the entity.
isCacheable()
Is entity cacheable in the runtime cache.
getLatitude()
Return the entity&#39;s latitude.
if(!$new_container instanceof\ElggEntity) if(!$new_container->canWriteToContainer(0, $entity->type, $entity->subtype)) $display_name
__construct(stdClass $row=null)
Create a new entity.
Definition: ElggEntity.php:140
$container_guid
getContainerEntity()
Get the container entity for this object.
elgg_normalize_url(string $url)
Definition: output.php:163
disable(string $reason= '', bool $recursive=true)
Disable this entity.
$site name
Definition: settings.php:15
updateLastAction(int $posted=null)
Update the last_action column in the entities table.
getSystemLogID()
{}
$id
Generic annotation delete action.
Definition: delete.php:6
$qb
Definition: queue.php:12
getAnnotationsMax(string $name)
Get the maximum of integer type annotations of a given name.
Definition: ElggEntity.php:849
getOwnedAccessCollections(array $options=[])
Returns the ACLs owned by the entity.
Definition: ElggEntity.php:884
getURL()
Gets the URL for this entity.
setDisplayName(string $display_name)
Sets the title or name of this entity.
Definition: ElggEntity.php:335
getMetadata(string $name)
Return the value of a piece of metadata.
Definition: ElggEntity.php:346
getDisplayName()
Get the entity&#39;s display name.
Definition: ElggEntity.php:324
$guid
Reset an ElggUpgrade.
Definition: reset.php:6
getLongitude()
Return the entity&#39;s longitude.