Elgg  Version master
RelationshipsTable.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Database;
4 
5 use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
6 use Elgg\Database;
13 use Elgg\Traits\TimeUsing;
14 
22 
23  use TimeUsing;
24 
28  public const RELATIONSHIP_COLUMN_LENGTH = 255;
29 
30  public const TABLE_NAME = 'entity_relationships';
31 
32  public const DEFAULT_JOIN_ALIAS = 'r';
33 
42  public function __construct(
43  protected Database $db,
44  protected EntityTable $entities,
45  protected MetadataTable $metadata,
46  protected EventsService $events
47  ) {
48  }
49 
57  public function get(int $id): ?\ElggRelationship {
58  $select = Select::fromTable(self::TABLE_NAME);
59  $select->select('*')
60  ->where($select->compare('id', '=', $id, ELGG_VALUE_ID));
61 
62  return $this->db->getDataRow($select, [$this, 'rowToElggRelationship']) ?: null;
63  }
64 
72  public function delete(int $id): bool {
73  $relationship = $this->get($id);
74  if (!$relationship instanceof \ElggRelationship) {
75  return false;
76  }
77 
78  return $this->events->triggerSequence('delete', 'relationship', $relationship, function() use ($id) {
79  $delete = Delete::fromTable(self::TABLE_NAME);
80  $delete->where($delete->compare('id', '=', $id, ELGG_VALUE_ID));
81 
82  return (bool) $this->db->deleteData($delete);
83  });
84  }
85 
98  public function add(\ElggRelationship $relationship, bool $return_id = false): bool|int {
99  if (strlen($relationship->relationship) > self::RELATIONSHIP_COLUMN_LENGTH) {
100  throw new LengthException('Relationship name cannot be longer than ' . self::RELATIONSHIP_COLUMN_LENGTH);
101  }
102 
103  // Check for duplicates
104  // note: escape $relationship after this call, we don't want to double-escape
105  if ($this->check($relationship->guid_one, $relationship->relationship, $relationship->guid_two)) {
106  return false;
107  }
108 
109  // Check if the related entities exist
110  if (!$this->entities->exists($relationship->guid_one) || !$this->entities->exists($relationship->guid_two)) {
111  // one or both of the guids doesn't exist
112  return false;
113  }
114 
115  $id = 0;
116 
117  $result = $this->events->triggerSequence('create', 'relationship', $relationship, function (\ElggRelationship $relationship) use (&$id) {
118  $insert = Insert::intoTable(self::TABLE_NAME);
119  $insert->values([
120  'guid_one' => $insert->param($relationship->guid_one, ELGG_VALUE_GUID),
121  'relationship' => $insert->param($relationship->relationship, ELGG_VALUE_STRING),
122  'guid_two' => $insert->param($relationship->guid_two, ELGG_VALUE_GUID),
123  'time_created' => $insert->param($this->getCurrentTime()->getTimestamp(), ELGG_VALUE_TIMESTAMP),
124  ]);
125 
126  try {
127  $id = $this->db->insertData($insert);
128  if (!$id) {
129  return false;
130  }
131  } catch (DatabaseException $e) {
132  $prev = $e->getPrevious();
133  if ($prev instanceof UniqueConstraintViolationException) {
134  // duplicate key error see https://github.com/Elgg/Elgg/issues/9179
135  return false;
136  }
137 
138  throw $e;
139  }
140 
141  return true;
142  });
143 
144  if (!$result) {
145  return false;
146  }
147 
148  return $return_id ? $id : true;
149  }
150 
162  public function check(int $guid_one, string $relationship, int $guid_two) {
163  $select = Select::fromTable(self::TABLE_NAME);
164  $select->select('*')
165  ->where($select->compare('guid_one', '=', $guid_one, ELGG_VALUE_GUID))
166  ->andWhere($select->compare('relationship', '=', $relationship, ELGG_VALUE_STRING))
167  ->andWhere($select->compare('guid_two', '=', $guid_two, ELGG_VALUE_GUID))
168  ->setMaxResults(1);
169 
170  $row = $this->db->getDataRow($select, [$this, 'rowToElggRelationship']);
171  return $row instanceof \ElggRelationship ? $row : false;
172  }
173 
185  public function remove(int $guid_one, string $relationship, int $guid_two): bool {
186  $obj = $this->check($guid_one, $relationship, $guid_two);
187  if (!$obj instanceof \ElggRelationship) {
188  return false;
189  }
190 
191  return $this->delete($obj->id);
192  }
193 
206  public function removeAll(int $guid, string $relationship = '', bool $inverse_relationship = false, string $type = '', bool $trigger_events = true): bool {
207  if ($trigger_events) {
208  return $this->removeAllWithEvents($guid, $relationship, $inverse_relationship, $type);
209  }
210 
211  return $this->removeAllWithoutEvents($guid, $relationship, $inverse_relationship, $type);
212  }
213 
227  protected function removeAllWithoutEvents(int $guid, string $relationship = '', bool $inverse_relationship = false, string $type = ''): bool {
228  $delete = Delete::fromTable(self::TABLE_NAME);
229 
230  if ($inverse_relationship) {
231  $delete->where($delete->compare('guid_two', '=', $guid, ELGG_VALUE_GUID));
232  } else {
233  $delete->where($delete->compare('guid_one', '=', $guid, ELGG_VALUE_GUID));
234  }
235 
236  if (!empty($relationship)) {
237  $delete->andWhere($delete->compare('relationship', '=', $relationship, ELGG_VALUE_STRING));
238  }
239 
240  if (!empty($type)) {
241  $entity_sub = $delete->subquery(EntityTable::TABLE_NAME);
242  $entity_sub->select('guid')
243  ->where($delete->compare('type', '=', $type, ELGG_VALUE_STRING));
244 
245  if (!$inverse_relationship) {
246  $delete->andWhere($delete->compare('guid_two', 'in', $entity_sub->getSQL()));
247  } else {
248  $delete->andWhere($delete->compare('guid_one', 'in', $entity_sub->getSQL()));
249  }
250  }
251 
252  $this->db->deleteData($delete);
253 
254  return true;
255  }
256 
270  protected function removeAllWithEvents(int $guid, string $relationship = '', bool $inverse_relationship = false, string $type = ''): bool {
271  $select = Select::fromTable(self::TABLE_NAME);
272  $select->select('*');
273 
274  if ($inverse_relationship) {
275  $select->where($select->compare('guid_two', '=', $guid, ELGG_VALUE_GUID));
276  } else {
277  $select->where($select->compare('guid_one', '=', $guid, ELGG_VALUE_GUID));
278  }
279 
280  if (!empty($relationship)) {
281  $select->andWhere($select->compare('relationship', '=', $relationship, ELGG_VALUE_STRING));
282  }
283 
284  if (!empty($type)) {
285  $entity_sub = $select->subquery(EntityTable::TABLE_NAME);
286  $entity_sub->select('guid')
287  ->where($select->compare('type', '=', $type, ELGG_VALUE_STRING));
288 
289  if (!$inverse_relationship) {
290  $select->andWhere($select->compare('guid_two', 'in', $entity_sub->getSQL()));
291  } else {
292  $select->andWhere($select->compare('guid_one', 'in', $entity_sub->getSQL()));
293  }
294  }
295 
296  $remove_ids = [];
297 
298  $relationships = $this->db->getData($select, [$this, 'rowToElggRelationship']);
299 
300  /* @var $rel \ElggRelationship */
301  foreach ($relationships as $rel) {
302  if (!$this->events->triggerBefore('delete', 'relationship', $rel)) {
303  continue;
304  }
305 
306  if (!$this->events->trigger('delete', 'relationship', $rel)) {
307  continue;
308  }
309 
310  $remove_ids[] = $rel->id;
311  }
312 
313  // to prevent MySQL query length issues
314  $chunks = array_chunk($remove_ids, 250);
315  foreach ($chunks as $chunk) {
316  if (empty($chunk)) {
317  continue;
318  }
319 
320  $delete = Delete::fromTable(self::TABLE_NAME);
321  $delete->where($delete->compare('id', 'in', $chunk));
322 
323  $this->db->deleteData($delete);
324  }
325 
326  /* @var $rel \ElggRelationship */
327  foreach ($relationships as $rel) {
328  if (!in_array($rel->id, $remove_ids)) {
329  continue;
330  }
331 
332  $this->events->triggerAfter('delete', 'relationship', $rel);
333  }
334 
335  return true;
336  }
337 
347  public function getEntitiesFromCount(array $options = []) {
348  $options['selects'][] = new SelectClause('COUNT(' . EntityTable::DEFAULT_JOIN_ALIAS . '.guid) AS total');
349  $options['group_by'][] = new GroupByClause(self::DEFAULT_JOIN_ALIAS . '.guid_two');
350  $options['order_by'][] = new OrderByClause('total', 'desc');
351 
352  return Entities::find($options);
353  }
354 
362  public function rowToElggRelationship(\stdClass $row): \ElggRelationship {
363  return new \ElggRelationship($row);
364  }
365 }
$guid
Reset an ElggUpgrade.
Definition: reset.php:6
$id
Generic annotation delete action.
Definition: delete.php:6
$type
Definition: delete.php:21
return[ 'admin/delete_admin_notices'=>['access'=> 'admin'], 'admin/menu/save'=>['access'=> 'admin'], 'admin/plugins/activate'=>['access'=> 'admin'], 'admin/plugins/activate_all'=>['access'=> 'admin'], 'admin/plugins/deactivate'=>['access'=> 'admin'], 'admin/plugins/deactivate_all'=>['access'=> 'admin'], 'admin/plugins/set_priority'=>['access'=> 'admin'], 'admin/security/security_txt'=>['access'=> 'admin'], 'admin/security/settings'=>['access'=> 'admin'], 'admin/security/regenerate_site_secret'=>['access'=> 'admin'], 'admin/site/cache/invalidate'=>['access'=> 'admin'], 'admin/site/flush_cache'=>['access'=> 'admin'], 'admin/site/icons'=>['access'=> 'admin'], 'admin/site/set_maintenance_mode'=>['access'=> 'admin'], 'admin/site/set_robots'=>['access'=> 'admin'], 'admin/site/theme'=>['access'=> 'admin'], 'admin/site/unlock_upgrade'=>['access'=> 'admin'], 'admin/site/settings'=>['access'=> 'admin'], 'admin/upgrade'=>['access'=> 'admin'], 'admin/upgrade/reset'=>['access'=> 'admin'], 'admin/user/ban'=>['access'=> 'admin'], 'admin/user/bulk/ban'=>['access'=> 'admin'], 'admin/user/bulk/delete'=>['access'=> 'admin'], 'admin/user/bulk/unban'=>['access'=> 'admin'], 'admin/user/bulk/validate'=>['access'=> 'admin'], 'admin/user/change_email'=>['access'=> 'admin'], 'admin/user/delete'=>['access'=> 'admin'], 'admin/user/login_as'=>['access'=> 'admin'], 'admin/user/logout_as'=>[], 'admin/user/makeadmin'=>['access'=> 'admin'], 'admin/user/resetpassword'=>['access'=> 'admin'], 'admin/user/removeadmin'=>['access'=> 'admin'], 'admin/user/unban'=>['access'=> 'admin'], 'admin/user/validate'=>['access'=> 'admin'], 'annotation/delete'=>[], 'avatar/upload'=>[], 'comment/save'=>[], 'diagnostics/download'=>['access'=> 'admin'], 'entity/chooserestoredestination'=>[], 'entity/delete'=>[], 'entity/mute'=>[], 'entity/restore'=>[], 'entity/subscribe'=>[], 'entity/trash'=>[], 'entity/unmute'=>[], 'entity/unsubscribe'=>[], 'login'=>['access'=> 'logged_out'], 'logout'=>[], 'notifications/mute'=>['access'=> 'public'], 'plugins/settings/remove'=>['access'=> 'admin'], 'plugins/settings/save'=>['access'=> 'admin'], 'plugins/usersettings/save'=>[], 'register'=>['access'=> 'logged_out', 'middleware'=>[\Elgg\Router\Middleware\RegistrationAllowedGatekeeper::class,],], 'river/delete'=>[], 'settings/notifications'=>[], 'settings/notifications/subscriptions'=>[], 'user/changepassword'=>['access'=> 'public'], 'user/requestnewpassword'=>['access'=> 'public'], 'useradd'=>['access'=> 'admin'], 'usersettings/save'=>[], 'widgets/add'=>[], 'widgets/delete'=>[], 'widgets/move'=>[], 'widgets/save'=>[],]
Definition: actions.php:73
$delete
Extends QueryBuilder with GROUP BY statements.
Extends QueryBuilder with ORDER BY clauses.
Extends QueryBuilder with SELECT clauses.
Query builder for updating data in the database.
Definition: Delete.php:8
static fromTable(string $table)
Returns a QueryBuilder for deleting data from a given table.
Definition: Delete.php:17
Entity table database service.
Definition: EntityTable.php:24
static intoTable(string $table)
Returns a QueryBuilder for inserting data in a given table.
Definition: Insert.php:17
This class interfaces with the database to perform CRUD operations on metadata.
subquery(string $table, ?string $alias=null)
Creates a new SelectQueryBuilder for join/where sub queries using the DB connection of the primary Qu...
Relationships table database service.
removeAllWithoutEvents(int $guid, string $relationship='', bool $inverse_relationship=false, string $type='')
Removes all relationships originating from a particular entity.
removeAllWithEvents(int $guid, string $relationship='', bool $inverse_relationship=false, string $type='')
Removes all relationships originating from a particular entity.
__construct(protected Database $db, protected EntityTable $entities, protected MetadataTable $metadata, protected EventsService $events)
Constructor.
rowToElggRelationship(\stdClass $row)
Convert a database row to a new \ElggRelationship.
removeAll(int $guid, string $relationship='', bool $inverse_relationship=false, string $type='', bool $trigger_events=true)
Removes all relationships originating from a particular entity.
getEntitiesFromCount(array $options=[])
Gets the number of entities by a the number of entities related to them in a particular way.
add(\ElggRelationship $relationship, bool $return_id=false)
Create a relationship between two entities.
check(int $guid_one, string $relationship, int $guid_two)
Check if a relationship exists between two entities.
Query builder for fetching data from the database.
Definition: Select.php:8
static fromTable(string $table, ?string $alias=null)
Returns a QueryBuilder for selecting data from a given table.
Definition: Select.php:18
The Elgg database.
Definition: Database.php:26
Events service.
A generic parent class for database exceptions.
Exception thrown if a length is invalid.
const ELGG_VALUE_STRING
Definition: constants.php:112
const ELGG_VALUE_ID
Definition: constants.php:114
const ELGG_VALUE_GUID
Definition: constants.php:113
const ELGG_VALUE_TIMESTAMP
Definition: constants.php:115
if($who_can_change_language==='nobody') elseif($who_can_change_language==='admin_only' &&!elgg_is_admin_logged_in()) $options
Definition: language.php:20
foreach($recommendedExtensions as $extension) if(empty(ini_get('session.gc_probability'))||empty(ini_get('session.gc_divisor'))) $db
$relationship
Elgg default relationship view.
Definition: default.php:10
if(parse_url(elgg_get_site_url(), PHP_URL_PATH) !=='/') if(file_exists(elgg_get_root_path() . 'robots.txt'))
Set robots.txt.
Definition: robots.php:10
$metadata
Output annotation metadata.
Definition: metadata.php:9