Passed
Push — master ( c0a3a7...3b84a4 )
by Jeroen
58:51
created

classes/Elgg/Database/RelationshipsTable.php (1 issue)

1
<?php
2
namespace Elgg\Database;
3
4
use Elgg\Database;
5
use Elgg\Database\Clauses\GroupByClause;
6
use Elgg\Database\Clauses\OrderByClause;
7
use Elgg\Database\Clauses\SelectClause;
8
use Elgg\EventsService;
9
10
/**
11
 * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
12
 *
13
 * @access private
14
 *
15
 * @package    Elgg.Core
16
 * @subpackage Database
17
 * @since      1.10.0
18
 */
19
class RelationshipsTable {
20
21
	use \Elgg\TimeUsing;
22
	
23
	/**
24
	 * @var Database
25
	 */
26
	protected $db;
27
28
	/**
29
	 * @var EntityTable
30
	 */
31
	protected $entities;
32
33
	/**
34
	 * @var MetadataTable
35
	 */
36
	protected $metadata;
37
38
	/**
39
	 * @var EventsService
40
	 */
41
	protected $events;
42
43
	/**
44
	 * Constructor
45
	 *
46
	 * @param Database      $db       Elgg Database
47
	 * @param EntityTable   $entities Entity table
48
	 * @param MetadataTable $metadata Metadata table
49
	 * @param EventsService $events   Events service
50
	 */
51 4417
	public function __construct(Database $db, EntityTable $entities, MetadataTable $metadata, EventsService $events) {
52 4417
		$this->db = $db;
53 4417
		$this->entities = $entities;
54 4417
		$this->metadata = $metadata;
55 4417
		$this->events = $events;
56 4417
	}
57
58
	/**
59
	 * Get a relationship by its ID
60
	 *
61
	 * @param int $id The relationship ID
62
	 *
63
	 * @return \ElggRelationship|false False if not found
64
	 */
65 116
	public function get($id) {
66 116
		$row = $this->getRow($id);
67 116
		if (!$row) {
68
			return false;
69
		}
70
71 116
		return new \ElggRelationship($row);
72
	}
73
74
	/**
75
	 * Get a database row from the relationship table
76
	 *
77
	 * @param int $id The relationship ID
78
	 *
79
	 * @return \stdClass|false False if no row found
80
	 * @access private
81
	 */
82 116
	public function getRow($id) {
83 116
		$sql = "SELECT * FROM {$this->db->prefix}entity_relationships WHERE id = :id";
84
		$params = [
85 116
			':id' => (int) $id,
86
		];
87 116
		return $this->db->getDataRow($sql, null, $params);
88
	}
89
90
	/**
91
	 * Delete a relationship by its ID
92
	 *
93
	 * @param int  $id         Relationship ID
94
	 * @param bool $call_event Call the delete event before deleting
95
	 *
96
	 * @return bool
97
	 */
98 12
	public function delete($id, $call_event = true) {
99 12
		$id = (int) $id;
100
101 12
		$relationship = $this->get($id);
102
103 12
		if ($call_event && !$this->events->trigger('delete', 'relationship', $relationship)) {
104 1
			return false;
105
		}
106
107 11
		$sql = "DELETE FROM {$this->db->prefix}entity_relationships WHERE id = :id";
108
		$params = [
109 11
			':id' => $id,
110
		];
111 11
		return (bool) $this->db->deleteData($sql, $params);
112
	}
113
114
	/**
115
	 * Create a relationship between two entities. E.g. friendship, group membership, site membership.
116
	 *
117
	 * This function lets you make the statement "$guid_one is a $relationship of $guid_two". In the statement,
118
	 * $guid_one is the subject of the relationship, $guid_two is the target, and $relationship is the type.
119
	 *
120
	 * @param int    $guid_one     GUID of the subject entity of the relationship
121
	 * @param string $relationship Type of the relationship
122
	 * @param int    $guid_two     GUID of the target entity of the relationship
123
	 * @param bool   $return_id    Return the ID instead of bool?
124
	 *
125
	 * @return bool|int
126
	 * @throws \InvalidArgumentException
127
	 */
128 115
	public function add($guid_one, $relationship, $guid_two, $return_id = false) {
129 115
		if (strlen($relationship) > \ElggRelationship::RELATIONSHIP_LIMIT) {
130
			$msg = "relationship name cannot be longer than " . \ElggRelationship::RELATIONSHIP_LIMIT;
131
			throw new \InvalidArgumentException($msg);
132
		}
133
134
		// Check for duplicates
135
		// note: escape $relationship after this call, we don't want to double-escape
136 115
		if ($this->check($guid_one, $relationship, $guid_two)) {
137
			return false;
138
		}
139
		
140
		$sql = "
141 115
			INSERT INTO {$this->db->prefix}entity_relationships
142
			       (guid_one, relationship, guid_two, time_created)
143
			VALUES (:guid1, :relationship, :guid2, :time)
144
				ON DUPLICATE KEY UPDATE time_created = :time
145
		";
146
		$params = [
147 115
			':guid1' => (int) $guid_one,
148 115
			':guid2' => (int) $guid_two,
149 115
			':relationship' => $relationship,
150 115
			':time' => $this->getCurrentTime()->getTimestamp(),
151
		];
152
153 115
		$id = $this->db->insertData($sql, $params);
154 115
		if (!$id) {
155
			return false;
156
		}
157
158 115
		$obj = $this->get($id);
159
160 115
		$result = $this->events->trigger('create', 'relationship', $obj);
161 115
		if (!$result) {
162 1
			$this->delete($id, false);
163 1
			return false;
164
		}
165
166 114
		return $return_id ? $obj->id : true;
167
	}
168
169
	/**
170
	 * Check if a relationship exists between two entities. If so, the relationship object is returned.
171
	 *
172
	 * This function lets you ask "Is $guid_one a $relationship of $guid_two?"
173
	 *
174
	 * @param int    $guid_one     GUID of the subject entity of the relationship
175
	 * @param string $relationship Type of the relationship
176
	 * @param int    $guid_two     GUID of the target entity of the relationship
177
	 *
178
	 * @return \ElggRelationship|false Depending on success
179
	 */
180 185
	public function check($guid_one, $relationship, $guid_two) {
181
		$query = "
182 185
			SELECT * FROM {$this->db->prefix}entity_relationships
183
			WHERE guid_one = :guid1
184
			  AND relationship = :relationship
185
			  AND guid_two = :guid2
186
			LIMIT 1
187
		";
188
		$params = [
189 185
			':guid1' => (int) $guid_one,
190 185
			':guid2' => (int) $guid_two,
191 185
			':relationship' => $relationship,
192
		];
193 185
		$row = $this->rowToElggRelationship($this->db->getDataRow($query, null, $params));
194 185
		if ($row) {
195 82
			return $row;
196
		}
197
198 138
		return false;
199
	}
200
201
	/**
202
	 * Delete a relationship between two entities.
203
	 *
204
	 * This function lets you say "$guid_one is no longer a $relationship of $guid_two."
205
	 *
206
	 * @param int    $guid_one     GUID of the subject entity of the relationship
207
	 * @param string $relationship Type of the relationship
208
	 * @param int    $guid_two     GUID of the target entity of the relationship
209
	 *
210
	 * @return bool
211
	 */
212 14
	public function remove($guid_one, $relationship, $guid_two) {
213 14
		$obj = $this->check($guid_one, $relationship, $guid_two);
214 14
		if (!$obj) {
215 8
			return false;
216
		}
217
218 8
		return $this->delete($obj->id);
219
	}
220
221
	/**
222
	 * Removes all relationships originating from a particular entity
223
	 *
224
	 * @param int    $guid                 GUID of the subject or target entity (see $inverse)
225
	 * @param string $relationship         Type of the relationship (optional, default is all relationships)
226
	 * @param bool   $inverse_relationship Is $guid the target of the deleted relationships? By default, $guid is the
227
	 *                                     subject of the relationships.
228
	 * @param string $type                 The type of entity related to $guid (defaults to all)
229
	 *
230
	 * @return true
231
	 */
232 214
	public function removeAll($guid, $relationship = "", $inverse_relationship = false, $type = '') {
233 214
		$guid = (int) $guid;
234 214
		$params = [];
235
236 214
		if (!empty($relationship)) {
237 1
			$where = "AND er.relationship = :relationship";
238 1
			$params[':relationship'] = $relationship;
239
		} else {
240 214
			$where = "";
241
		}
242
243 214
		if (!empty($type)) {
244
			if (!$inverse_relationship) {
245
				$join = "JOIN {$this->db->prefix}entities e ON e.guid = er.guid_two";
246
			} else {
247
				$join = "JOIN {$this->db->prefix}entities e ON e.guid = er.guid_one";
248
				$where .= " AND ";
249
			}
250
			$where .= " AND e.type = :type";
251
			$params[':type'] = $type;
252
		} else {
253 214
			$join = "";
254
		}
255
256 214
		$guid_col = $inverse_relationship ? "guid_two" : "guid_one";
257
258 214
		$this->db->deleteData("
259 214
			DELETE er FROM {$this->db->prefix}entity_relationships AS er
260 214
			$join
261 214
			WHERE $guid_col = $guid
262 214
			$where
263 214
		", $params);
264
265 214
		return true;
266
	}
267
268
	/**
269
	 * Get all the relationships for a given GUID.
270
	 *
271
	 * @param int  $guid                 GUID of the subject or target entity (see $inverse)
272
	 * @param bool $inverse_relationship Is $guid the target of the deleted relationships? By default $guid is
273
	 *                                   the subject of the relationships.
274
	 *
275
	 * @return \ElggRelationship[]
276
	 */
277
	public function getAll($guid, $inverse_relationship = false) {
278
		$params[':guid'] = (int) $guid;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$params was never initialized. Although not strictly required by PHP, it is generally a good practice to add $params = array(); before regardless.
Loading history...
279
280
		$where = ($inverse_relationship ? "guid_two = :guid" : "guid_one = :guid");
281
282
		$query = "SELECT * from {$this->db->prefix}entity_relationships WHERE {$where}";
283
284
		return $this->db->getData($query, [$this, 'rowToElggRelationship'], $params);
285
	}
286
287
	/**
288
	 * Gets the number of entities by a the number of entities related to them in a particular way.
289
	 * This is a good way to get out the users with the most friends, or the groups with the
290
	 * most members.
291
	 *
292
	 * @param array $options An options array compatible with elgg_get_entities()
293
	 *
294
	 * @return \ElggEntity[]|int|boolean If count, int. If not count, array. false on errors.
295
	 */
296 1
	public function getEntitiesFromCount(array $options = []) {
297 1
		$options['selects'][] = new SelectClause("COUNT(e.guid) AS total");
298 1
		$options['group_by'][] = new GroupByClause('r.guid_two');
299 1
		$options['order_by'][] = new OrderByClause('total', 'desc');
300
301 1
		return Entities::find($options);
302
	}
303
304
	/**
305
	 * Convert a database row to a new \ElggRelationship
306
	 *
307
	 * @param \stdClass $row Database row from the relationship table
308
	 *
309
	 * @return \ElggRelationship|false
310
	 * @access private
311
	 */
312 185
	public function rowToElggRelationship($row) {
313 185
		if ($row instanceof \stdClass) {
314 82
			return new \ElggRelationship($row);
315
		}
316
317 138
		return false;
318
	}
319
}
320