ElggCollection Proposal

An ElggCollection entity would store an ordered set of integers optimized for filtering and/or ordering SQL queries of entities, annotations, or other tables with integer primary keys.

Use Cases

  1. Filtering with ordering: setting visibility and order of group widgets; photo albums; user’s favorite entities
  2. Ordering without filtering: sticky items/comments; setting top pages to display in widgets
  3. Filtering without ordering: list of users from whom a user does not want to see activity; list of users who can access a resource (possible replacement of separate ACL tables)

The logic of how a collection filters/orders a result set could be stored in the collection to specify a default behavior, but this logic could be overridden to allow, e.g., ordering in reverse order or excluding items in the collection.

Data Model

ElggCollection would extend ElggEntity, storing the following attributes in a new table {prefix}collections_entity:

  • key_id (int): metastring id of the key (case-insensitive) used to locate the collection within its container. A container may have only one collection under a given key.
  • items_type (tinyint, default 0): 0 = entity, 1 = annotation
  • order_direction (tinyint, default 1): 0 = no order, 1 = ASC, 2 = DESC
  • filter_type (tinyint, default 0) 0 = do not filter items, 1 = fetch only collection items, 2 = fetch only non-collection items
  • items_first (tinyint, default 1) 0 = non-collection items appear first, 1 = collection items appear first
  • title (varchar 255, optional)
  • description (text, optional)

Note: The collection key eliminates the need for a separate metadata/relationship entity to tie a collection to an entity, and gives a predictable method of finding a collection. It’s likely that keys will be identical among many entities so the metastrings are a good use here.

The table {prefix}collection_items would provide storage for the collection:

  • id (AUTOINC): primary key
  • guid (int): the collection’s GUID
  • item (int): an integer in the collection (e.g. an entity GUID, an annotation id)
  • priority (int, optional): the ranking of the stored item.

Usage and Permissions

To access a collection directly, user code would access its container entity, then access the associated collection (which may have a different ACL) via its key:

$stickyItems = elgg_get_collection($group, 'stickyItems');

To use the collection, code would pass the ElggCollection object (or an array of them) to any of the elgg_get_entities() family of functions via the options key “collections”.

$content = elgg_list_entities(array(
    'collections' => elgg_get_collection($group, 'stickyItems'),
    /* other options */
));

During query generation, the order_direction, filter_type, and items_first attributes of the collections would be used to specify how the collection_items table is joined and how the query is ordered.

Creation and Editing

Only users who canEdit() the container entity are permitted to create or alter associated collections.

$faves = elgg_create_collection($user, 'faves', ElggCollection::ENTITIES);
$faves->order_direction = ElggCollection::ORDER_DESC;
$faves->save();
$faves->appendItem($entity_or_guid); // ...alteration methods TBD

Integration Without Rewriting Code

By naming their query and calling a plugin hook like “collections:apply”, an author could allow other authors to add collections to be applied. The following is an example of enabling this in the pages widget query:

$collections = elgg_trigger_plugin_hook(
    'collections:apply',
    'entity',
    array(
        'container' => $vars['entity'],
        'query_name' => 'pages_widget_content'),
    array()
);
$options = array(
    /* ...existing pages widget content options... */
    'collections' => $collections,
);
$content = elgg_list_entities($options);

A less verbose API might allow passing the query_name and container directly into elgg_get_entities(), which could trigger the plugin hook:

$options = array(
    /* ...existing pages widget content options... */
    'collections_query_name' => 'pages_widget_content',
    'collections_container' => $vars['entity'],
);
$content = elgg_list_entities($options);

Notes

  • Tying collections (and their editing permissions) to a container (and its canEdit() method) I think makes sense. Only group owners can alter collections tied to their groups. Users can still make their own collections about anything, but cannot hijack a group’s “official” collection by simply knowing its key.
  • How do we apply multiple collections?
  • If the applied collections just provide order, which collection’s ORDER BY clause comes first?
  • A plugin hook could remove/reorder collections. Should they be able to?
  • What happens if a collection cannot be accessed? Some queries will not make sense without the collection. Considering this, a collection that’s meant to filter out all non-collection items is a special case where the query should not run at all. What makes the problem harder is that, if the collection can’t be accessed, we also can’t know that it is this type of collection!
  • The above point makes me suspect that it’s folly to store the join/ordering attributes. A better idea might be to require the user to specify join/order attributes whenever the collection is fetched. That way, depending on need, an empty, non-savable collection could be substituted.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.