1<?php
2/**
3 * Procedural code for creating, loading, and modifying \ElggEntity objects.
4 */
5
6/**
7 * Return the class name registered as a constructor for an entity of a given type and subtype
8 *
9 * @see elgg_set_entity_type()
10 *
11 * @param string $type    The type
12 * @param string $subtype The subtype
13 *
14 * @return string
15 */
16function elgg_get_entity_class($type, $subtype) {
17	return _elgg_services()->entityTable->getEntityClass($type, $subtype);
18}
19
20/**
21 * Sets class constructor name for entities with given type and subtype
22 *
23 * By default entities are loaded as one of the 4 parent objects:
24 *  - site: ElggSite
25 *  - user: ElggUser
26 *  - object: ElggObject
27 *  - group: ElggGroup
28 *
29 * Entity classes for subtypes should extend the base class for entity type,
30 * e.g. ElggBlog must extend ElggObject
31 *
32 * @param string $type    Entity type
33 * @param string $subtype Entity subtype
34 * @param string $class   Class name for the object
35 *                        Can be empty to reset previously declared class name
36 *
37 * @return void
38 */
39function elgg_set_entity_class($type, $subtype, $class = "") {
40	_elgg_services()->entityTable->setEntityClass($type, $subtype, $class);
41}
42
43
44/**
45 * Returns a database row from the entities table.
46 *
47 * @tip Use get_entity() to return the fully loaded entity.
48 *
49 * @warning This will only return results if a) it exists, b) you have access to it.
50 * see {@link _elgg_get_access_where_sql()}.
51 *
52 * @param int $guid The GUID of the object to extract
53 *
54 * @return \stdClass|false
55 * @see entity_row_to_elggstar()
56 * @internal
57 */
58function get_entity_as_row($guid) {
59	return _elgg_services()->entityTable->getRow($guid);
60}
61
62/**
63 * Create an Elgg* object from a given entity row.
64 *
65 * Handles loading all tables into the correct class.
66 *
67 * @param \stdClass $row The row of the entry in the entities table.
68 *
69 * @return \ElggEntity|false
70 * @see get_entity_as_row()
71 * @see get_entity()
72 * @internal
73 *
74 * @throws ClassException|InstallationException
75 */
76function entity_row_to_elggstar($row) {
77	return _elgg_services()->entityTable->rowToElggStar($row);
78}
79
80/**
81 * Loads and returns an entity object from a guid.
82 *
83 * @param int $guid The GUID of the entity
84 *
85 * @return \ElggEntity|false The correct Elgg or custom object based upon entity type and subtype
86 */
87function get_entity($guid) {
88	if ($guid == 1) {
89		return _elgg_config()->site;
90	}
91	return _elgg_services()->entityTable->get($guid);
92}
93
94/**
95 * Does an entity exist?
96 *
97 * This function checks for the existence of an entity independent of access
98 * permissions. It is useful for situations when a user cannot access an entity
99 * and it must be determined whether entity has been deleted or the access level
100 * has changed.
101 *
102 * @param int $guid The GUID of the entity
103 *
104 * @return bool
105 * @since 1.8.0
106 */
107function elgg_entity_exists($guid) {
108	return _elgg_services()->entityTable->exists($guid);
109}
110
111/**
112 * Get the current site entity
113 *
114 * @return \ElggSite
115 * @since 1.8.0
116 */
117function elgg_get_site_entity() {
118	return _elgg_config()->site;
119}
120
121/**
122 * Fetches/counts entities or performs a calculation on their properties
123 *
124 * Note that you can use singulars for most options, e.g. $options['type'] will be normalized to $options['types']
125 *
126 * ------------------------
127 * TYPE SUBTYPE CONSTRAINTS
128 * ------------------------
129 *
130 * Filter entities by their type and subtype
131 *
132 * @option string[] $types
133 * @option string[] $subtypes
134 * @option string[] $type_subtype_pairs
135 *
136 * <code>
137 * $options['types'] = ['object'];
138 * $options['subtypes'] = ['blog', 'file'];
139 * $options['type_subtype_pairs'] = [
140 *     'object' => ['blog', 'file'],
141 *     'group' => [], // all group subtypes
142 *     'user' => null, // all user subtypes
143 * ];
144 * </code>
145 *
146 * ----------------
147 * GUID CONSTRAINTS
148 * ----------------
149 *
150 * Filter entities by their guid, owner or container
151 *
152 * @option int[]|ElggEntity[] $guids
153 * @option int[]|ElggEntity[] $owner_guids
154 * @option int[]|ElggEntity[] $container_guids
155 *
156 * ----------------
157 * TIME CONSTRAINTS
158 * ----------------
159 *
160 * Filter entities that were created, updated or last acted on within certain bounds
161 *
162 * @option DateTime|string|int $created_after
163 * @option DateTime|string|int $created_before
164 * @option DateTime|string|int $updated_after
165 * @option DateTime|string|int $updated_before
166 * @option DateTime|string|int $last_action_after
167 * @option DateTime|string|int $last_action_before
168 *
169 * <code>
170 * $options['created_after'] = '-1 year';
171 * $options['created_before'] = 'now';
172 * </code>
173 *
174 * ------------------
175 * ACCESS CONSTRAINTS
176 * ------------------
177 *
178 * Filter entities by their access_id attribute. Note that this filter apply to entities that the user has access to.
179 * You can ignore access system using {@link elgg_call()}
180 *
181 * @option int[] $access_id
182 *
183 * ----------------
184 * LIMIT AND OFFSET
185 * ----------------
186 *
187 * This options are used for paginating lists of entities
188 *
189 * @option int $limit
190 * @option int $offset
191 *
192 * --------------------
193 * METADATA CONSTRAINTS
194 * --------------------
195 *
196 * Filter entities by their metadata and attributes
197 *
198 * The following options will be merged and applied as a metadata pair to @options['metadata_name_value_pairs']
199 * Note metadata names can contain attributes names and will be resolved automatically during query building.
200 * @option int[]                $metadata_ids
201 * @option string[]             $metadata_names
202 * @option mixed                $metadata_values
203 * @option DateTime|string|int  $metadata_created_after
204 * @option DateTime|string|int  $metadata_created_before
205 * @option bool                 $metadata_case_sensitive
206 *
207 * Metadata name value pairs will be joined by the boolean specified in $metadata_name_value_pairs_operator
208 * @option array                $metadata_name_value_pairs
209 * @option string               $metadata_name_value_pairs_operator
210 *
211 * In addition to metadata name value pairs, you can specify search pair, which will be merged using OR boolean
212 * and will filter entities regardless of metadata name value pairs and their operator
213 * @warning During normalization, search name value pairs will ignore properties under metadata_ namespace, that is
214 *          you can not use metadata_ids, metadata_created_before, metadata_created_after, metadata_case_sensitive
215 *          to constrain search pairs. You will need to pass these properties for each individual search pair,
216 *          as seen in the example below
217 *
218 * @option array                $search_name_value_pairs
219 *
220 * <code>
221 * // Search for entities with:
222 * // status of draft or unsaved_draft
223 * // AND index greater than 5
224 * // AND (title/description containing the word hello OR tags containing the word world)
225 * $options['metadata_name_value_pairs'] = [
226 *    [
227 *       'name' => 'status',
228 *       'value' => ['draft', 'unsaved_draft'],
229 *       'operand' => 'IN',
230 *       'created_after' => '-1 day',
231 *    ],
232 *    [
233 *        'name' => 'index',
234 *        'value' => 5,
235 *        'operand' => '>=',
236 *        'type' => ELGG_VALUE_INTEGER,
237 *    ]
238 * ];
239 * $options['search_name_value_pairs'] = [
240 *    [
241 *       'name' => ['title', 'description'],
242 *       'value' => '%hello%',
243 *       'operand' => 'LIKE',
244 *       'case_sensitive' => false,
245 *    ],
246 *    [
247 *       // 'ids' => [55, 56, 57, 58, 59, 60], // only search these 5 metadata rows
248 *       'name' => 'tags',
249 *       'value' => '%world%',
250 *       'operand' => 'LIKE',
251 *       'case_sensitive' => false,
252 *       'created_after' => '-1 day',
253 *       'created_before' => 'now',
254 *    ],
255 * ];
256 * </code>
257 *
258 * ----------------------
259 * ANNOTATION CONSTRAINTS
260 * ----------------------
261 *
262 * Filter entities by their annotations
263 *
264 * The following options will be merged and applied as an annotation pair to @options['annotation_name_value_pairs']
265 * @option int[]                $annotation_ids
266 * @option string[]             $annotation_names
267 * @option mixed                $annotation_values
268 * @option bool                 $annotation_case_sensitive
269 * @option DateTime|string|int  $annotation_created_after
270 * @option DateTime|string|int  $annotation_created_before
271 * @option int[]|ElggEntity[]   $annotation_owner_guids
272 * @option int[]|ElggEntity[]   $annotation_access_ids
273 *
274 * Annotation name value pairs will be joined by the boolean specified in $annotation_name_value_pairs_operator
275 * @option array                $annotation_name_value_pairs
276 * @option string               $annotation_name_value_pairs_operator
277 **
278 * <code>
279 * $options['annotation_name_value_pairs'] = [
280 *    [
281 *       'name' => 'likes',
282 *       'created_after' => '-1 day',
283 *    ],
284 *    [
285 *        'name' => 'rating',
286 *        'value' => 5,
287 *        'operand' => '>=',
288 *        'type' => ELGG_VALUE_INTEGER,
289 *    ],
290 *    [
291 *        'name' => 'review',
292 *        'value' => '%awesome%',
293 *        'operand' => 'LIKE',
294 *        'type' => ELGG_VALUE_STRING,
295 *    ]
296 * ];
297 * </code>
298 *
299 * ------------------------
300 * RELATIONSHIP CONSTRAINTS
301 * ------------------------
302 *
303 * Filter entities by their relationships
304 *
305 * The following options will be merged and applied as a relationship pair to $options['relationship_name_value_pairs']
306 * @option int[]                $relationship_ids
307 * @option string[]             $relationship
308 * @option int[]|ElggEntity[]   $relationship_guid
309 * @option bool                 $inverse_relationship
310 * @option DateTime|string|int  $relationship_created_after
311 * @option DateTime|string|int  $relationship_created_before
312 * @option string               $relationship_join_on Column name in the name main table
313 *
314 * @option array                $relationship_pairs
315 *
316 * <code>
317 * // Get all entities that user with guid 25 has friended or been friended by
318 * $options['relationship_pairs'] = [
319 *    [
320 *       'relationship' => 'friend',
321 *       'relationship_guid' => 25,
322 *       'inverse_relationship' => true,
323 *    ],
324 *    [
325 *       'relationship' => 'friend',
326 *       'relationship_guid' => 25,
327 *       'inverse_relationship' => false,
328 *    ],
329 * ];
330 * </code>
331 *
332 * ----------------------------
333 * PRIVATE SETTINGS CONSTRAINTS
334 * ----------------------------
335 *
336 * Filter entities by their private settings
337 *
338 * The following options will be merged and applied as a private_setting pair to
339 * $options['private_setting_name_value_pairs']
340 * @option int[]                $private_setting_ids
341 * @option string[]             $private_setting_names
342 * @option mixed                $private_setting_values
343 * @option bool                 $private_setting_case_sensitive
344 *
345 * Private name value pairs will be joined by the boolean specified in $private_setting_name_value_pairs_operator
346 * @option array                $private_setting_name_value_pairs
347 * @option string               $private_setting_name_value_pairs_operator
348 *
349 * Setting names in all pairs can be namespaced using the prefix
350 * @option string               $private_setting_name_prefix
351 *
352 * <code>
353 * $options['private_setting_name_value_pairs'] = [
354 *    [
355 *       'name' => 'handler',
356 *       'value' => ['admin', 'dashboard'],
357 *       'operand' => 'IN',
358 *    ],
359 * ];
360 * </code>
361 *
362 * -------
363 * SORTING
364 * -------
365 *
366 * You can specify sorting options using ONE of the following options
367 *
368 * NOTE: Some order by options only work when fetching entities and not from
369 * derived function (eg elgg_get_annotations, elgg_get_relationships)
370 *
371 * Order by a calculation performed on annotation name value pairs
372 * $option array annotation_sort_by_calculation e.g. avg, max, min, sum
373 *
374 * Order by value of a specific annotation
375 * @option array $order_by_annotation
376 *
377 * Order by value of a specific metadata/attribute
378 * @option array $order_by_metadata
379 *
380 * Order by arbitrary clauses
381 * @option array $order_by
382 *
383 * <code>
384 * $options['order_by_metadata'] = [
385 *     'name' => 'priority',
386 *     'direction' => 'DESC',
387 *     'as' => 'integer',
388 * ];
389 * $options['order_by_annotation'] = [
390 *     'name' => 'priority',
391 *     'direction' => 'DESC',
392 *     'as' => 'integer',
393 * ];
394 *
395 * $sort_by = new \Elgg\Database\Clauses\EntitySortByClause();
396 * $sort_by->property = 'private';
397 * $sort_by->property_type = 'private_setting';
398 * $sort_by->join_type = 'left';
399 *
400 * $fallback = new \Elgg\Database\Clauses\OrderByClause('e.time_created', 'desc');
401 *
402 * $options['order_by'] = [
403 *     $sort_by,
404 *     $fallback,
405 * ];
406 * </code>
407 *
408 * -----------------
409 * COUNT/CALCULATION
410 * -----------------
411 *
412 * Performs a calculation on a set of entities that match all of the criteria
413 * If any of these are specific, the return of this function will be int or float
414 *
415 * Return total number of entities
416 * @option bool $count
417 *
418 * Perform a calculation on a set of entity's annotations using a numeric sql function
419 * If specified, the number of annotation name value pairs can not be more than 1, or they must be merged using OR
420 * operator
421 * @option string $annotation_calculation e.g. avg, max, min, sum
422 *
423 * Perform a calculation on a set of entity's metadat using a numeric sql function
424 * If specified, the number of metadata name value pairs can not be more than 1, or they must be merged using OR
425 * operator
426 * @option string $metadata_calculation e.g. avg, max, min, sum
427 *
428 * ----------
429 * SQL SELECT
430 * ----------
431 *
432 * @option array $selects
433 * <code>
434 * $options['selects'] = [
435 *    'e.last_action AS last_action',
436 *    function(QueryBulder $qb, $main_alias) {
437 *        $joined_alias = $qb->joinMetadataTable($main_alias, 'guid', 'status');
438 *        return "$joined_alias.value AS status";
439 *    }
440 * ];
441 * </code>
442 *
443 * --------
444 * SQL JOIN
445 * --------
446 *
447 * @option array $joins
448 * <code>
449 * $on = function(QueryBuilder $qb, $joined_alias, $main_alias) {
450 *     return $qb->compare("$joined_alias.user_guid", '=', "$main_alias.guid");
451 * };
452 * $options['joins'] = [
453 *     new JoinClause('access_collections_membership', 'acm', $on);
454 * ];
455 * </code>
456 *
457 * ----------
458 * SQL GROUPS
459 * ----------
460 *
461 * @option array $group_by
462 * @option array $having
463 *
464 * <code>
465 * $options['group_by'] = [
466 *      function(QueryBuilder $qb, $main_alias) {
467 *          return "$main_alias.guid";
468 *      }
469 * ];
470 * $options['having'] = [
471 *      function(QueryBuilder $qb, $main_alias) {
472 *          return $qb->compare("$main_alias.guid", '>=', 50, ELGG_VALUE_INTEGER);
473 *      }
474 * ];
475 * </code>
476 *
477 * ---------
478 * SQL WHERE
479 * ---------
480 *
481 * @option array $where
482 * <code>
483 * $options['wheres'] = [
484 *      function(QueryBuilder $qb, $main_alias) {
485 *          return $qb->merge([
486 *              $qb->compare("$main_alias.guid", '>=', 50, ELGG_VALUE_INTEGER),
487 *              $qb->compare("$main_alias.guid", '<=', 250, ELGG_VALUE_INTEGER),
488 *          ], 'OR');
489 *      }
490 * ];
491 * </code>
492 *
493 * --------------
494 * RESULT OPTIONS
495 * --------------
496 *
497 * @option bool $distinct                 If set to false, Elgg will drop the DISTINCT clause from
498 *                                        the MySQL query, which will improve performance in some situations.
499 *                                        Avoid setting this option without a full understanding of the underlying
500 *                                        SQL query Elgg creates.
501 *                                        Default: true
502 * @option callable|false $callback       A callback function to pass each row through
503 *                                        Default: entity_row_to_elggstar
504 * @option bool $preload_owners           If set to true, this function will preload
505 *                                        all the owners of the returned entities resulting in better
506 *                                        performance when displaying entities owned by several users
507 *                                        Default: false
508 * @option bool $preload_containers       If set to true, this function will preload
509 *                                        all the containers of the returned entities resulting in better
510 *                                        performance when displaying entities contained by several users/groups
511 *                                        Default: false
512 * @option bool $preload_private_settings If set to true, this function will preload
513 *                                        all the private settings of the returned entities resulting in better
514 *                                        performance when displaying entities where private settings are often used, such as widgets
515 *                                        Default: false
516 * @option bool $batch                    If set to true, an Elgg\BatchResult object will be returned instead of an array.
517 *                                        Default: false
518 * @option bool $batch_inc_offset         If "batch" is used, this tells the batch to increment the offset
519 *                                        on each fetch. This must be set to false if you delete the batched results.
520 *                                        Default: true
521 * @option int  $batch_size               If "batch" is used, this is the number of entities/rows to pull in before
522 *                                        requesting more.
523 *                                        Default: 25
524 *
525 *
526 * @see    elgg_list_entities()
527 * @see    \Elgg\Database\LegacyQueryOptionsAdapter
528 *
529 * @param array $options Options
530 *
531 * @return \ElggEntity[]|int|mixed If count, int. Otherwise an array or an Elgg\BatchResult. false on errors.
532 *
533 * @since 1.7.0
534 */
535function elgg_get_entities(array $options = []) {
536	return \Elgg\Database\Entities::find($options);
537}
538
539/**
540 * Returns a count of entities.
541 *
542 * @param array $options the same options as elgg_get_entities() but forces 'count' to true
543 *
544 * @return int
545 */
546function elgg_count_entities(array $options = []) {
547	$options['count'] = true;
548
549	return (int) elgg_get_entities($options);
550}
551
552/**
553 * Returns a string of rendered entities.
554 *
555 * Displays list of entities with formatting specified by the entity view.
556 *
557 * @tip Pagination is handled automatically.
558 *
559 * @note Internal: This also provides the views for elgg_view_annotation().
560 *
561 * @note Internal: If the initial COUNT query returns 0, the $getter will not be called again.
562 *
563 * @param array    $options Any options from $getter options plus:
564 *                   item_view => STR Optional. Alternative view used to render list items
565 *                   full_view => BOOL Display full view of entities (default: false)
566 *                   list_type => STR 'list', 'gallery', or 'table'
567 *                   columns => ARR instances of Elgg\Views\TableColumn if list_type is "table"
568 *                   list_type_toggle => BOOL Display gallery / list switch
569 *                   pagination => BOOL Display pagination links
570 *                   no_results => STR|true for default notfound text|Closure Message to display when there are no entities
571 *
572 * @param callable $getter  The entity getter function to use to fetch the entities.
573 * @param callable $viewer  The function to use to view the entity list.
574 *
575 * @return string
576 * @since 1.7
577 * @see elgg_get_entities()
578 * @see elgg_view_entity_list()
579 */
580function elgg_list_entities(array $options = [], $getter = 'elgg_get_entities', $viewer = 'elgg_view_entity_list') {
581
582	$offset_key = isset($options['offset_key']) ? $options['offset_key'] : 'offset';
583
584	$defaults = [
585		'offset' => (int) max(get_input($offset_key, 0), 0),
586		'limit' => (int) max(get_input('limit', _elgg_config()->default_limit), 0),
587		'full_view' => false,
588		'list_type_toggle' => false,
589		'pagination' => true,
590		'no_results' => '',
591		'preload_owners' => true,
592		'preload_containers' => true,
593	];
594
595	$options = array_merge($defaults, $options);
596
597	$options['register_rss_link'] = elgg_extract('register_rss_link', $options, elgg_extract('pagination', $options));
598	if ($options['register_rss_link']) {
599		elgg_register_rss_link();
600	}
601
602	if ($options['no_results'] === true) {
603		$options['no_results'] = elgg_echo('notfound');
604	}
605
606	$options['count'] = false;
607	$entities = call_user_func($getter, $options);
608	$options['count'] = is_array($entities) ? count($entities) : 0;
609
610	if (!empty($entities) || !empty($options['offset'])) {
611		$count_needed = true;
612		if (!$options['pagination']) {
613			$count_needed = false;
614		} elseif (!$options['offset'] && !$options['limit']) {
615			$count_needed = false;
616		} elseif (($options['count'] < (int) $options['limit']) && !$options['offset']) {
617			$count_needed = false;
618		}
619
620		if ($count_needed) {
621			$options['count'] = true;
622
623			$options['count'] = (int) call_user_func($getter, $options);
624		}
625	}
626
627	return call_user_func($viewer, $entities, $options);
628}
629
630/**
631 * Returns a list of months in which entities were updated or created.
632 *
633 * @tip     Use this to generate a list of archives by month for when entities were added or updated.
634 *
635 * @warning Months are returned in the form YYYYMM.
636 *
637 * @param array $options all entity options supported by {@see elgg_get_entities()}
638 *
639 * @return array|false Either an array months as YYYYMM, or false on failure
640 * @since 3.0
641 */
642function elgg_get_entity_dates(array $options = []) {
643	return \Elgg\Database\Entities::with($options)->getDates();
644}
645
646/**
647 * Registers an entity type and subtype as a public-facing entity that should
648 * be shown in search and by {@link elgg_list_registered_entities()}.
649 *
650 * @warning Entities that aren't registered here will not show up in search.
651 *
652 * @tip Add a language string item:type:subtype and collection:type:subtype to make sure the items are display properly.
653 *
654 * @param string $type    The type of entity (object, site, user, group)
655 * @param string $subtype The subtype to register (may be blank)
656 *
657 * @return bool Depending on success
658 * @see get_registered_entity_types()
659 */
660function elgg_register_entity_type($type, $subtype = null) {
661	$type = strtolower($type);
662	if (!in_array($type, \Elgg\Config::getEntityTypes())) {
663		return false;
664	}
665
666	$entities = _elgg_config()->registered_entities;
667	if (empty($entities)) {
668		$entities = [];
669	}
670
671	if (!isset($entities[$type])) {
672		$entities[$type] = [];
673	}
674
675	if ($subtype) {
676		if (in_array($subtype, $entities[$type])) {
677			// subtype already registered
678			return true;
679		}
680
681		$entities[$type][] = $subtype;
682	}
683
684	_elgg_config()->registered_entities = $entities;
685
686	return true;
687}
688
689/**
690 * Unregisters an entity type and subtype as a public-facing type.
691 *
692 * @warning With a blank subtype, it unregisters that entity type including
693 * all subtypes. This must be called after all subtypes have been registered.
694 *
695 * @param string $type    The type of entity (object, site, user, group)
696 * @param string $subtype The subtype to register (may be blank)
697 *
698 * @return bool Depending on success
699 * @see elgg_register_entity_type()
700 */
701function elgg_unregister_entity_type($type, $subtype = null) {
702	$type = strtolower($type);
703	if (!in_array($type, \Elgg\Config::getEntityTypes())) {
704		return false;
705	}
706
707	$entities = _elgg_config()->registered_entities;
708	if (empty($entities)) {
709		return false;
710	}
711
712	if (!isset($entities[$type])) {
713		return false;
714	}
715
716	if ($subtype) {
717		if (in_array($subtype, $entities[$type])) {
718			$key = array_search($subtype, $entities[$type]);
719			unset($entities[$type][$key]);
720		} else {
721			return false;
722		}
723	} else {
724		unset($entities[$type]);
725	}
726
727	_elgg_config()->registered_entities = $entities;
728	return true;
729}
730
731/**
732 * Returns registered entity types and subtypes
733 *
734 * @param string $type The type of entity (object, site, user, group) or blank for all
735 *
736 * @return array|false Depending on whether entities have been registered
737 * @see elgg_register_entity_type()
738 */
739function get_registered_entity_types($type = null) {
740	$registered_entities = _elgg_config()->registered_entities;
741	if (empty($registered_entities)) {
742		return false;
743	}
744
745	if ($type) {
746		$type = strtolower($type);
747	}
748
749	if (!empty($type) && !isset($registered_entities[$type])) {
750		return false;
751	}
752
753	if (empty($type)) {
754		return $registered_entities;
755	}
756
757	return $registered_entities[$type];
758}
759
760/**
761 * Returns if the entity type and subtype have been registered with {@link elgg_register_entity_type()}.
762 *
763 * @param string $type    The type of entity (object, site, user, group)
764 * @param string $subtype The subtype (may be blank)
765 *
766 * @return bool Depending on whether or not the type has been registered
767 */
768function is_registered_entity_type($type, $subtype = null) {
769	$registered_entities = _elgg_config()->registered_entities;
770	if (empty($registered_entities)) {
771		return false;
772	}
773
774	$type = strtolower($type);
775
776	// @todo registering a subtype implicitly registers the type.
777	// see #2684
778	if (!isset($registered_entities[$type])) {
779		return false;
780	}
781
782	if ($subtype && !in_array($subtype, $registered_entities[$type])) {
783		return false;
784	}
785	return true;
786}
787
788/**
789 * Checks options for the existing of site_guid or site_guids contents and reports a warning if found
790 *
791 * @param array $options array of options to check
792 *
793 * @return void
794 */
795function _elgg_check_unsupported_site_guid(array $options = []) {
796	$site_guid = elgg_extract('site_guid', $options, elgg_extract('site_guids', $options));
797	if ($site_guid === null) {
798		return;
799	}
800
801	$backtrace = debug_backtrace();
802	// never show this call.
803	array_shift($backtrace);
804
805	if (!empty($backtrace[0]['class'])) {
806		$warning = "Passing site_guid or site_guids to the method {$backtrace[0]['class']}::{$backtrace[0]['file']} is not supported.";
807		$warning .= "Please update your usage of the method.";
808	} else {
809		$warning = "Passing site_guid or site_guids to the function {$backtrace[0]['function']} in {$backtrace[0]['file']} is not supported.";
810		$warning .= "Please update your usage of the function.";
811	}
812
813	_elgg_services()->logger->warning($warning);
814}
815