## Categories

Object Names and IdHandler Service Contract

### Functionality overview

Categories are organized in separate category trees.
When a package admin clicks on an Administer Categories link, they are presented with a page that shows the following items:

- list of trees currently mapped to the object (this "object" will be usually a package instance)
- list of trees that can be mapped to the object , those trees are just the trees that the admin has the 'category_read' permission on
- link to create and map a new category tree

Creating a new tree involves entering tree name and description. The name must be unique among all the trees.
Upon creation of a tree, the admin is granted the 'category_read' and 'category_write' permisssions.
Normally, the category_write permission should not be shared with anybody else, in the rare cases when granting this permission to another party is needed, site-wide admin intervention will be required.

In addition to mapping an entire tree to an object, admins have the option of mapping only a subtree of an existing tree. To do that, they have to click on a "Map subtree" link, after which they will see a list of tree nodes.
The mapped subtree will consist of all subcategories of the category the user selected - the category itself will not be included. Note that the mapped subtree will not be a new tree. Therefore this option should be used only if an admin plans to use the subtree 'as is' and has no intention of making changes to it.

An alternative solution is available for admins who want to create a tree by copying one of the existing trees and subsequently playing around with it (moving/adding/deleting categories). To accomplish that, they would have to create a new tree, go to the admin page for this tree and click on a "Copy existing tree" link. They will see a list of available trees to copy. Clicking on the "Copy this one" link will result in creating copies of the categories from the source trees and placing them in the new tree.
This operation can be performed several times, each time the copied categories will be placed as toplevel categories of the tree.

As far as unmapping is concerned, this operation doesn't delete the mapping between categories and objects.

Permissions

The creator of the category tree is granted the category_tree_read, category_tree_write and category_tree_grant_permissions privileges.

The operations one can perform on categories are:

- (a) changing of a parent
- (b) adding childen
- (c) deleting
- (d) editing
- (e) phasing in/out
- (f) changing sort key

ad (d) You cannot delete a category that has children. Also, you cannot delete a category that has objects mapped to it (do we want it or not?)
ad (e) The effect of phasing out a category is that users no longer will be able to associate objects with it, but existing associations will still be visible
Deletions and phasing it/out can be performed as bulk operations.
ad (f) sort key is used to order children of the same parent category, that is the elements of the tree are sorted first by parent, then by the sort key.

Datamodel

This table actually stores the information whether the tree is side-wide or not.

    create table category_trees (
           tree_id			integer primary key
                                    constraint cat_trees_tree_id_fk
                                    references acs_objects on delete cascade,
           site_wide_p		char(1) default 't'
                                    constraint cat_trees_site_wide_p_ck
                                    check (site_wide_p in ('t','f'))
    );

Here the tree's name and description is stored in different translations.

    create table category_tree_translations (
           tree_id			integer
                                    constraint cat_tree_trans_tree_id_fk
                                    references category_trees on delete cascade,
           locale		        varchar2(5) not null
                                    constraint cat_tree_trans_locale_fk
                                    references ad_locales,
           name			varchar2(50) not null,
           description		varchar2(1000),
           primary key (tree_id, locale)
    );

This table stores the tree hierarchy by holding the information about the parent category. The tree is ordered by a nested index (left_ind, right_ind). Sorting is thus accomplished by means of a nested set. You can read a description of how nested sets work. This also describes how to write queries that sort correctly when using categories.

    create table categories (
           category_id		    integer primary key
                                        constraint cat_category_id_fk
                                        references acs_objects on delete cascade,
           tree_id			    integer
                                        constraint cat_tree_id_fk
                                        references category_trees on delete cascade,
           parent_id		    integer
                                        constraint cat_parent_id_fk
                                        references categories,
           deprecated_p		    char(1) default 'f'
                                        constraint cat_deprecated_p_ck
                                        check (deprecated_p in ('t','f')),
           left_ind			    integer,
           right_ind		    integer
    );

Here the actual categories are stored together with different translations.

    create table category_translations (
           category_id	    integer
                                constraint cat_trans_category_id_fk
                                references categories on delete cascade,
           locale		    varchar2(5) not null
                                constraint cat_trans_locale_fk
                                references ad_locales,
           name		    varchar2(200),
           description	    varchar2(4000),
           primary key (category_id, locale)
    );

This table contains mapping between categories and objects

    create table category_object_map (
           category_id		     integer
                                         constraint cat_object_map_category_id_fk
                                         references categories on delete cascade,
           object_id		     integer
                                         constraint cat_object_map_object_id_fk
                                         references acs_objects on delete cascade,
           primary key (object_id, category_id)
    ) organization index;

This is the table for the relation of trees and objects. subtree_category_id comes to play in situations when you map a subtree of an existing tree to an object.

    create table category_tree_map (
    	tree_id			integer
                                    constraint cat_tree_map_tree_id_fk
                                    references category_trees on delete cascade,
    	object_id		integer
                                    constraint cat_tree_map_object_id_fk
    	                        references acs_objects on delete cascade,
    	subtree_category_id	integer default null
                                    constraint cat_tree_map_subtree_id_fk
                                    references categories,
    	primary key (object_id, tree_id)
    ) organization index;

Known Limitations

- The tree order is the same for all translations.
- You can map a tree only once to a package (or other object).
- The number of objects mapped to a category is not shown yet. These results should be cached.
- When browsing categories all mapped categories should be shown for each object.
- There should be browsing widget easily used by other packages to let the user browse through all categorized objects.

Integration with other packages

Here are the changes needed to be made to integrate with other packages.

index.adp
Provide an admin-link to /categories/cadmin/one-object?object_id=@package_id@ to let admins map trees to the package instance.

form-page.tcl
Use this in ad_form to display all mapped category trees and selected categories (if editing an object):

        {category_ids:integer(category),multiple,optional {label "Categories"}
           {html {size 4}} {value {$object_id $package_id}}}

Alternatively, you can include the following in your adp:

      <include src="/packages/categories/www/include/widget" object_id=@object_id@ package_id=@package_id@>

In the processing part of ad_form use:

    category::map_object -remove_old -object_id $object_id $category_ids