Commit 31b361ef authored by Frank Bergmann's avatar Frank Bergmann

Initial Import

parents
<?xml version="1.0"?>
<!-- Generated by the OpenACS Package Manager -->
<package key="categories" url="http://openacs.org/repository/apm/packages/categories" type="apm_service">
<package-name>Categories</package-name>
<pretty-plural>Categories</pretty-plural>
<initial-install-p>f</initial-install-p>
<singleton-p>f</singleton-p>
<auto-mount>categories</auto-mount>
<version name="1.0d7" url="http://openacs.org/repository/apm/packages/categories-1.0d7">
<owner>timo@timohentschel.de</owner>
<summary>Manage categories in category trees and let users map objects to categories.</summary>
<release-date>2003-04-16</release-date>
<description format="text/html">Datamodel for category trees with supporting API and management pages. Provides a widget for
general categorization of arbitrary objects and tracks which package instances use which category trees. Also supports localization.</description>
<provides url="categories" version="1.0d7"/>
<callbacks>
<callback type="after-install" proc="category::after_install"/>
<callback type="before-uninstall" proc="category::before_uninstall"/>
</callbacks>
<parameters>
<!-- No version parameters -->
</parameters>
</version>
</package>
<formtemplate id="tree_form"></formtemplate>
set package_id [ad_conn package_id]
set languages [lang::system::get_locale_options]
ad_form -name tree_form -mode [ad_decode [ad_form_new_p -key tree_id] 1 edit display] -action tree-form -export { locale object_id } -form {
{tree_id:key}
{tree_name:text {label "Name"} {html {size 50 maxlength 50}}}
{language:text(select) {label "Language"} {options $languages}}
{description:text(textarea),optional {label "Description"} {html {rows 5 cols 80}}}
} -new_request {
permission::require_permission -object_id $package_id -privilege category_admin
set language $locale
} -edit_request {
permission::require_permission -object_id $tree_id -privilege category_tree_write
set action Edit
util_unlist [category_tree::get_translation $tree_id $locale] tree_name description
set language $locale
} -on_submit {
set description [util_close_html_tags $description 4000]
} -new_data {
db_transaction {
category_tree::add -tree_id $tree_id -name $tree_name -description $description -locale $language -context_id $package_id
if { [info exists object_id] } {
category_tree::map -tree_id $tree_id -object_id $object_id
set return_url [export_vars -base one-object { locale object_id }]
} else {
set return_url [export_vars -base tree-view { tree_id locale }]
}
}
} -edit_data {
category_tree::update -tree_id $tree_id -name $tree_name -description $description -locale $language
set return_url [export_vars -base tree-view { tree_id locale object_id }]
} -after_submit {
ad_returnredirect $return_url
ad_script_abort
}
This diff is collapsed.
--
-- The Categories Package
--
-- @author Timo Hentschel (timo@timohentschel.de)
-- @creation-date 2003-04-16
--
drop table category_search_results;
drop table category_search_index;
drop table category_search;
drop table category_synonym_index;
drop table category_synonyms;
drop sequence category_search_id_seq;
drop sequence category_synonyms_id_seq;
drop trigger ins_synonym_on_ins_transl_trg;
drop trigger upd_synonym_on_upd_transl_trg;
drop table category_links;
drop sequence category_links_id_seq;
drop table category_temp;
drop table category_object_map;
drop table category_tree_map;
drop table category_translations;
drop table categories;
drop table category_tree_translations;
drop table category_trees;
delete from acs_permissions where object_id in
(select object_id from acs_objects where object_type = 'category_tree');
delete from acs_objects where object_type='category';
delete from acs_objects where object_type='category_tree';
begin
acs_object_type.drop_type('category', 't');
acs_object_type.drop_type('category_tree', 't');
end;
/
show errors
delete from acs_permissions
where privilege in
('category_tree_write','category_tree_read',
'category_tree_grant_permissions','category_admin');
delete from acs_privilege_hierarchy
where privilege in
('category_tree_write','category_tree_read',
'category_tree_grant_permissions','category_admin');
delete from acs_privilege_hierarchy
where child_privilege in
('category_tree_write','category_tree_read',
'category_tree_grant_permissions','category_admin');
delete from acs_privileges
where privilege in
('category_tree_write','category_tree_read',
'category_tree_grant_permissions','category_admin');
/
drop package category_synonym;
drop package category_link;
drop package category_tree;
drop package category;
--
-- The Categories Package
--
-- @author Timo Hentschel (timo@timohentschel.de)
-- @creation-date 2003-04-16
--
-- This should eventually be added to the acs-service-contract installation files
declare
v_id integer;
begin
v_id := acs_sc_contract.new(
contract_name => 'AcsObject',
contract_desc => 'Acs Object Id Handler'
);
v_id := acs_sc_msg_type.new(
msg_type_name => 'AcsObject.PageUrl.InputType',
msg_type_spec => 'object_id:integer'
);
v_id := acs_sc_msg_type.new(
msg_type_name => 'AcsObject.PageUrl.OutputType',
msg_type_spec => 'page_url:string'
);
v_id := acs_sc_operation.new(
contract_name => 'AcsObject',
operation_name => 'PageUrl',
operation_desc => 'Returns the package specific url to a page that displays an object',
operation_iscachable_p => 'f',
operation_nargs => 1,
operation_inputtype => 'AcsObject.PageUrl.InputType',
operation_outputtype => 'AcsObject.PageUrl.OutputType'
);
end;
/
show errors
-- there should be an implementation of this contract
-- for apm_package, user, group and other object types
-- this should eventually be added to acs-kernel
create table acs_named_objects (
object_id integer not null
constraint acs_named_objs_object_id_fk
references acs_objects(object_id) on delete cascade,
locale varchar2(5)
constraint acs_named_objs_locale_fk
references ad_locales,
object_name varchar2(200),
creation_date date default sysdate not null,
package_id integer
constraint acs_named_objs_package_id_fk
references apm_packages(package_id) on delete cascade,
constraint acs_named_objs_pk
primary key (object_id, locale)
);
create index acs_named_objs_name_ix on acs_named_objects (substr(upper(object_name),1,1));
create index acs_named_objs_package_ix on acs_named_objects(package_id);
begin
acs_object_type.create_type (
supertype => 'acs_object',
object_type => 'acs_named_object',
pretty_name => 'Named Object',
pretty_plural => 'Named Objects',
table_name => 'acs_named_objects',
id_column => 'object_id'
);
end;
/
show errors
--
-- The Categories Package
--
-- @author Timo Hentschel (timo@timohentschel.de)
-- @creation-date 2003-04-16
--
begin
-- create the privileges
acs_privilege.create_privilege('category_tree_write');
acs_privilege.create_privilege('category_tree_read');
acs_privilege.create_privilege('category_tree_grant_permissions');
acs_privilege.create_privilege('category_admin', 'Categories Administrator');
acs_privilege.add_child('admin','category_admin');
acs_privilege.add_child('category_admin','category_tree_read');
acs_privilege.add_child('category_admin','category_tree_write');
acs_privilege.add_child('category_admin','category_tree_grant_permissions');
end;
/
show errors;
commit;
--
-- The Categories Package
-- Extension for linking categories
--
-- @author Timo Hentschel (timo@timohentschel.de)
-- @creation-date 2004-02-04
--
CREATE or REPLACE PACKAGE category_link AS
FUNCTION new (
from_category_id in categories.category_id%TYPE,
to_category_id in categories.category_id%TYPE
) RETURN integer;
PROCEDURE del (
link_id in category_links.link_id%TYPE
);
END;
/
show errors
CREATE OR REPLACE PACKAGE BODY category_link AS
FUNCTION new (
from_category_id in categories.category_id%TYPE,
to_category_id in categories.category_id%TYPE
) RETURN integer
IS
v_link_id integer;
BEGIN
select category_links_id_seq.nextval() into v_link_id from dual;
insert into category_links (link_id, from_category_id, to_category_id)
values (v_link_id, new.from_category_id, new.to_category_id);
return v_link_id;
END new;
PROCEDURE del (
link_id in category_links.link_id%TYPE
) IS
BEGIN
delete from category_links
where link_id = del.link_id;
END del;
END category_link;
/
show errors
This diff is collapsed.
--
-- The Categories Package
-- Extension for category synonyms
--
-- @author Bernd Schmeil (bernd@thebernd.de)
-- @author Timo Hentschel (timo@timohentschel.de)
-- @creation-date 2004-01-08
--
CREATE or REPLACE PACKAGE category_synonym AS
FUNCTION new (
name in category_synonyms.name%TYPE,
locale in category_synonyms.locale%TYPE,
category_id in categories.category_id%TYPE,
synonym_id in category_synonyms.synonym_id%TYPE default null
) RETURN integer;
PROCEDURE del (
synonym_id in category_synonyms.synonym_id%TYPE
);
FUNCTION edit (
synonym_id in category_synonyms.synonym_id%TYPE,
name in category_synonyms.name%TYPE,
locale in category_synonyms.locale%TYPE
) RETURN integer;
FUNCTION search (
search_text in category_search.search_text%TYPE,
locale in category_search.locale%TYPE
) RETURN integer;
FUNCTION get_similarity (
len1 integer,
len2 integer,
matches integer
) RETURN integer;
END;
/
show errors
CREATE OR REPLACE PACKAGE BODY category_synonym AS
FUNCTION convert_string (
string in category_search.search_text%TYPE
) RETURN varchar;
PROCEDURE reindex (
synonym_id in category_synonyms.synonym_id%TYPE,
name in category_synonyms.name%TYPE,
locale in category_synonyms.locale%TYPE
);
FUNCTION new (
name in category_synonyms.name%TYPE,
locale in category_synonyms.locale%TYPE,
category_id in categories.category_id%TYPE,
synonym_id in category_synonyms.synonym_id%TYPE default null
) RETURN integer
IS
v_synonym_id integer;
BEGIN
-- get new synonym_id
if (new.synonym_id is null) then
select category_synonyms_id_seq.nextval() into v_synonym_id from dual;
else
v_synonym_id := new.synonym_id;
end if;
-- insert synonym data
insert into category_synonyms (synonym_id, category_id, locale, name, synonym_p)
values (v_synonym_id, new.category_id, new.locale, new.name, 't');
-- insert in synonym index and search results
category_synonym.reindex (v_synonym_id, new.name, new.locale);
return v_synonym_id;
END new;
PROCEDURE del (
synonym_id in category_synonyms.synonym_id%TYPE
) IS
BEGIN
-- delete search results
delete from category_search_results
where synonym_id = del.synonym_id;
-- delete synonym index
delete from category_synonym_index
where synonym_id = del.synonym_id;
-- delete synonym
delete from category_synonyms
where synonym_id = del.synonym_id;
END del;
FUNCTION edit (
synonym_id in category_synonyms.synonym_id%TYPE,
name in category_synonyms.name%TYPE,
locale in category_synonyms.locale%TYPE
) RETURN integer IS
BEGIN
-- update synonym data
update category_synonyms
set name = edit.name,
locale = edit.locale
where synonym_id = edit.synonym_id;
-- update synonym index and search results
category_synonym.reindex (edit.synonym_id, edit.name, edit.locale);
return edit.synonym_id;
END edit;
FUNCTION search (
search_text in category_search.search_text%TYPE,
locale in category_search.locale%TYPE
) RETURN integer
IS
v_search_text varchar(200);
v_query_id integer;
v_len integer;
v_i integer;
BEGIN
-- check if search text already exists
select query_id into v_query_id
from category_search
where search_text = search.search_text
and locale = search.locale;
-- simply update old search data if already exists
if (v_query_id is not null) then
update category_search
set queried_count = queried_count + 1,
last_queried = sysdate
where query_id = v_query_id;
return v_query_id;
end if;
-- get new search query id
select category_search_id_seq.nextval() into v_query_id from dual;
-- convert string to uppercase and substitute special chars
v_search_text := category_synonym.convert_string (search.search_text);
-- insert search data
insert into category_search (query_id, search_text, locale, queried_count, last_queried)
values (v_query_id, search.search_text, search.locale, 1, sysdate);
-- build search index
v_len := length (v_search_text) - 2;
v_i := 1;
while (v_i <= v_len) loop
insert into category_search_index
values (v_query_id, substr (v_search_text, v_i , 3));
v_i := v_i + 1;
end loop;
-- build search result
insert into category_search_results
select v_query_id, s.synonym_id,
category_synonym.get_similarity (v_len, length (s.name) - 2, count(*))
from category_search_index si,
category_synonym_index i,
category_synonyms s
where si.query_id = v_query_id
and si.trigram = i.trigram
and s.synonym_id = i.synonym_id
and s.locale = search.locale
group by s.synonym_id, s.name;
return v_query_id;
END search;
FUNCTION get_similarity (
len1 integer,
len2 integer,
matches integer
) RETURN integer IS
BEGIN
return (matches * 200 / (len1 + len2));
END get_similarity;
-----
-- helper procs and functions
-----
FUNCTION convert_string (
string in category_search.search_text%TYPE
) RETURN varchar
IS
v_index_string varchar(200);
BEGIN
-- convert string to uppercase and substitute special chars
-- TODO: complete
v_index_string := upper (
replace (
replace (
replace (
replace (
replace (
replace (
replace (convert_string.string, 'ä', 'AE'),
'Ä', 'AE'),
'ö', 'OE'),
'Ö', 'OE'),
'ü', 'UE'),
'Ü', 'UE'),
'ß', 'SS'));
return (' ' || v_index_string || ' ');
END convert_string;
PROCEDURE reindex (
synonym_id in category_synonyms.synonym_id%TYPE,
name in category_synonyms.name%TYPE,
locale in category_synonyms.locale%TYPE
) IS
v_name varchar(200);
v_len integer;
v_i integer;
BEGIN
-- delete old search results for this synonym
delete from category_search_results
where synonym_id = reindex.synonym_id;
-- delete old synonym index for this synonym
delete from category_synonym_index
where synonym_id = reindex.synonym_id;
-- convert string to uppercase and substitute special chars
v_name := category_synonym.convert_string (reindex.name);
-- rebuild synonym index
v_len := length (v_name) - 2;
v_i := 1;
while (v_i <= v_len) loop
insert into category_synonym_index
values (reindex.synonym_id, substr (v_name, v_i , 3));
v_i := v_i + 1;
end loop;
-- rebuild search results
insert into category_search_results
select s.query_id, reindex.synonym_id,
category_synonym.get_similarity (v_len, length (s.search_text) - 2, count(*))
from category_search_index si,
category_synonym_index i,
category_search s
where i.synonym_id = reindex.synonym_id
and si.trigram = i.trigram
and si.query_id = s.query_id
and s.locale = reindex.locale
group by s.query_id, s.search_text;
END reindex;
END category_synonym;
/
show errors
-----
-- triggers for category synonyms
-----
create or replace trigger ins_synonym_on_ins_transl_trg
after insert on category_translations for each row
declare
v_synonym_id integer;
begin
-- create synonym
v_synonym_id := category_synonym.new (:new.name, :new.locale, :new.category_id, null);
-- mark synonym as not editable for users
update category_synonyms
set synonym_p = 'f'
where synonym_id = v_synonym_id;
end;
/
show errors
create or replace trigger upd_synonym_on_upd_transl_trg
before update on category_translations for each row
declare
v_synonym_id integer;
begin
-- get synonym_id of updated category translation
select synonym_id into v_synonym_id
from category_synonyms
where category_id = :old.category_id
and name = :old.name
and locale = :old.locale
and synonym_p = 'f';
-- update synonym
v_synonym_id := category_synonym.edit (v_synonym_id, :new.name, :new.locale);
end;
/
show errors
This diff is collapsed.
--
-- Adding view useful when getting categories for a specific tree
--
create or replace view category_object_map_tree as
select c.category_id,
c.tree_id,
m.object_id
from category_object_map m,
categories c
where c.category_id = m.category_id;
alter table category_tree_map add (
assign_single_p char(1) default 'f' constraint cat_tree_map_single_p_ck check (assign_single_p in ('t','f')),
require_category_p char(1) default 'f' constraint cat_tree_map_categ_p_ck check (require_category_p in ('t','f'))
);
comment on column category_tree_map.assign_single_p is '
Are the users allowed to assign multiple or only a single category
to objects?
';
@@../category-tree-package.sql
-----
-- category links
-----
create table category_links (
link_id integer not null
constraint category_links_pk primary key,
from_category_id integer not null
constraint category_links_from_fk
references categories on delete cascade,
to_category_id integer not null
constraint category_links_to_fk
references categories on delete cascade,
constraint category_links_un
unique (from_category_id, to_category_id)
);
create unique index category_links_rev_ix on category_links (to_category_id, from_category_id);
create sequence category_links_id_seq;
comment on table category_links is '
Stores directed graph of linked categories. If category A
and category B are linked, then any categorization on A
will result in an additional categorization in B.
';
comment on column category_links.link_id is '
Primary key.
';
comment on column category_links.from_category_id is '
Category the link is coming from. Any categorization in this
category will trigger a categorization in the other category.
';
comment on column category_links.to_category_id is '
Category the link is coming to. Any categorization in the other
category will trigger a categorization in this category.
';
@@../category-link-package.sql
-----
-- Synonyms
-----
create table category_synonyms (
synonym_id integer not null
constraint category_synonyms_pk primary key,
category_id integer not null
constraint category_synonyms_cat_fk
references categories on delete cascade,
locale varchar(5) not null
constraint category_synonyms_locale_fk
references ad_locales on delete cascade,
name varchar(100) not null,
synonym_p char(1) default 't'
constraint category_synonyms_synonym_p_ck
check (synonym_p in ('t','f'))
);
-- to get all synonyms in given locale
create index category_synonyms_locale_ix on category_synonyms(category_id, locale);
-- to sort synonyms by name
create index category_synonyms_name_ix on category_synonyms(category_id, name);
create sequence category_synonyms_id_seq;
comment on table category_synonyms is '
Stores multilingual synonyms of the categories.
';
comment on column category_synonyms.synonym_id is '
Primary key.
';
comment on column category_synonyms.category_id is '
Category the synonyms are refering to.
';
comment on column category_synonyms.locale is '
Language of the synonym.
';
comment on column category_synonyms.name is '
Actual synonym of the category in given language.
';
comment on column category_synonyms.synonym_p is '
Marks if the entry is a synonym to be edited by user or is a copy
of a category translation and cannot be edited directly.
';
create table category_synonym_index (
-- category synonyms split up in 3-grams to be used by fuzzy search
synonym_id integer not null
constraint category_synonym_index_fk
references category_synonyms on delete cascade,
trigram char(3) not null
);
-- to get all synonyms of given 3-gram
create index category_syn_index_trigram_ix on category_synonym_index(trigram);
-- to delete all 3-grams of given synonym
create index category_syn_index_synonym_ix on category_synonym_index(synonym_id);
comment on table category_synonym_index is '
Stores the synonym cut down in portions of 3 characters
to be used in search.
';
comment on column category_synonym_index.synonym_id is '
Id of the synonym refered to.
';
comment on column category_synonym_index.trigram is '
3 character part of the synonym.
';
create table category_search (
query_id integer not null
constraint category_search_id_pk primary key,
search_text varchar(200) not null,
locale varchar(5) not null
constraint category_search_locale_fk
references ad_locales on delete cascade,
queried_count integer default 1 not null,
last_queried date default sysdate not null,
constraint category_search_query_un
unique (search_text, locale)
);
-- to delete old queries
create index category_search_date_ix on category_search(last_queried);
create sequence category_search_id_seq;
comment on table category_search is '
Stores users multilingual search texts for category synonyms.
';
comment on column category_search.query_id is '
Primary key.
';
comment on column category_search.locale is '
Language of the search text.
';
comment on column category_search.search_text is '
Actual search text in given language.
';
comment on column category_search.queried_count is '
Counts how often this search text has been used by users.
';
comment on column category_search.last_queried is '
Date of last usage of this search text.
A sweeper will delete search texts not used for a while.
';
create table category_search_index (
query_id integer not null
constraint category_search_index_fk
references category_search on delete cascade,
trigram char(3) not null
);
-- to get all search texts of given 3-gram
create index category_search_ind_trigram_ix on category_search_index(trigram);
-- to delete all 3-grams of given search text
create index category_search_ind_query_ix on category_search_index(query_id);
comment on table category_search_index is '
Stores the search text cut down in portions of 3 characters
to be used in search.
';
comment on column category_search_index.query_id is '
Id of the search text refered to.
';
comment on column category_search_index.trigram is '
3 character part of the search text.
';
create table category_search_results (
query_id integer not null
constraint category_results_query_fk
references category_search on delete cascade,
synonym_id integer not null
constraint category_results_synonym_fk
references category_synonyms on delete cascade,
similarity integer not null,
constraint category_search_results_pk
primary key (query_id, synonym_id)
);
-- to sort all matches found by similarity
create index category_results_similarity_ix on category_search_results (query_id, similarity);
comment on table category_search_results is '
Stores the result of a users search in synonyms,
stores matching synonyms and their degree of similarity
to the search text.
';
comment on column category_search_results.query_id is '
Id of the search text.
';
comment on column category_search_results.synonym_id is '
Id of the synonym found.
';
comment on column category_search_results.similarity is '
Percent of similarity between search text and found synonym.
';
@@ ../category-synonym-package.sql
-- insert existing category translations as synonyms
-- and build synonym index
declare
v_synonym_id integer;
v_translation category_translations%ROWTYPE;
begin
for v_translation in (select * from category_translations)
loop
v_synonym_id := category_synonym.new (v_translation.name, v_translation.locale, v_translation.category_id, null);
update category_synonyms set synonym_p = 'f' where synonym_id = v_synonym_id;
end loop;
end;
/
show errors
This diff is collapsed.
--
-- The Categories Package
--
-- @author Timo Hentschel (timo@timohentschel.de)
-- @creation-date 2003-04-16
--
drop table category_search_results;
drop table category_search_index;
drop table category_search;
drop table category_synonym_index;
drop table category_synonyms;
drop sequence category_search_id_seq;
drop sequence category_synonyms_id_seq;
drop table category_links;
drop sequence category_links_id_seq;
drop table category_temp;
drop table category_object_map;
drop table category_tree_map;
drop index cat_tree_map_ix;
drop index cat_object_map_ix;
drop table category_translations;
drop table categories;
drop index categories_left_ix;
drop index categories_parent_ix;
drop table category_tree_translations;
drop table category_trees;
delete from acs_permissions where object_id in
(select object_id from acs_objects where object_type = 'category_tree');
delete from acs_objects where object_type='category';
delete from acs_objects where object_type='category_tree';
begin;
select acs_object_type__drop_type('category', 't');
select acs_object_type__drop_type('category_tree', 't');
end;
drop function category_synonym__convert_string (varchar);
drop function category_synonym__get_similarity (integer, integer, bigint);
drop function category_synonym__search (varchar, varchar);
drop function category_synonym__reindex (integer, varchar, varchar);
drop function category_synonym__new (varchar, varchar, integer, integer);
drop function category_synonym__del (integer);
drop function category_synonym__edit (integer, varchar, varchar);
drop function category_synonym__edit_cat_trans_trg () cascade;
drop function category_synonym__new_cat_trans_trg () cascade;
drop function category_link__new (integer,integer);
drop function category_link__del (integer);
drop function category_tree__new (integer,varchar,varchar,varchar,
char,timestamp with time zone,integer,varchar,integer);
drop function category_tree__new_translation (integer,varchar,varchar,
varchar,timestamp with time zone,integer,varchar);
drop function category_tree__del (integer);
drop function category_tree__edit (integer,varchar,varchar,varchar,
char,timestamp with time zone,integer,varchar);
drop function category_tree__copy (integer,integer,integer,varchar);
drop function category_tree__map (integer,integer,integer,char);
drop function category_tree__unmap (integer,integer);
drop function category_tree__check_nested_ind (integer);
-- drop function category_tree__index_children (integer,integer);
drop function category__new (integer,integer,varchar,varchar,varchar,
integer,char,timestamp with time zone,integer,varchar);
drop function category__new_translation (integer,varchar,varchar,varchar,
timestamp with time zone,integer,varchar);
drop function category__phase_out (integer);
drop function category__phase_in (integer);
drop function category__del (integer);
drop function category__edit (integer,varchar,varchar,varchar,
timestamp with time zone,integer,varchar);
drop function category__change_parent (integer,integer,integer);
drop function category__name (integer);
-- delete privileges;
-- this shouldn't be necessary
begin;
delete from acs_privilege_descendant_map where privilege like 'category%';
end;
select acs_privilege__remove_child('category_admin','category_tree_read');
select acs_privilege__remove_child('category_admin','category_tree_write');
select acs_privilege__remove_child('category_admin','category_tree_grant_permissions');
select acs_privilege__remove_child('admin','category_admin');
select acs_privilege__drop_privilege('category_admin');
select acs_privilege__drop_privilege('category_tree_write');
select acs_privilege__drop_privilege('category_tree_read');
select acs_privilege__drop_privilege('category_tree_grant_permissions');
-- from categories-init
drop table acs_named_objects;
select acs_object_type__drop_type('acs_named_object', 't');
select acs_sc_contract__delete(acs_sc_contract__get_id('AcsObject'));
select acs_sc_msg_type__delete(acs_sc_msg_type__get_id('AcsObject.PageUrl.InputType'));
select acs_sc_msg_type__delete(acs_sc_msg_type__get_id('AcsObject.PageUrl.OutputType'));
select acs_sc_operation__delete(acs_sc_operation__get_id('AcsObject','PageUrl'));
-- this should be being handled at the tcl callback level but isn't?
select acs_sc_impl__delete('AcsObject','category_idhandler');
select acs_sc_impl__delete('AcsObject','category_tree_idhandler');
--
-- The Categories Package
--
-- @author Timo Hentschel (timo@timohentschel.de)
-- @creation-date 2003-04-16
--
-- This should eventually be added to the acs-service-contract installation files
begin;
select acs_sc_contract__new(
'AcsObject', -- contract_name
'Acs Object Id Handler' -- contract_desc
);
select acs_sc_msg_type__new(
'AcsObject.PageUrl.InputType', -- msg_type_name
'object_id:integer' -- msg_type_spec
);
select acs_sc_msg_type__new(
'AcsObject.PageUrl.OutputType', -- msg_type_name
'page_url:string' -- msg_type_spec
);
select acs_sc_operation__new(
'AcsObject', -- contract_name
'PageUrl', -- operation_name
'Returns the package specific url to a page that displays an object', -- operation_desc
'f', -- operation_iscachable_p
1, -- operation_nargs
'AcsObject.PageUrl.InputType', -- operation_inputtype
'AcsObject.PageUrl.OutputType' -- operation_outputtype
);
end;
-- there should be an implementation of this contract
-- for apm_package, user, group and other object types
-- this should eventually be added to acs-kernel
create table acs_named_objects (
object_id integer not null
constraint acs_named_objs_pk primary key
constraint acs_named_objs_object_id_fk
references acs_objects(object_id) on delete cascade,
object_name varchar(200),
package_id integer
constraint acs_named_objs_package_id_fk
references apm_packages(package_id) on delete cascade
);
create index acs_named_objs_name_ix on acs_named_objects(object_name);
create index acs_named_objs_package_ix on acs_named_objects(package_id);
create function inline_0 ()
returns integer as '
begin
PERFORM acs_object_type__create_type (
''acs_named_object'', -- object_type
''Named Object'', -- pretty_name
''Named Objects'', -- pretty_plural
''acs_object'', -- supertype
''acs_named_objects'', -- table_name
''object_id'', -- id_column
null, -- name_method
''f'',
null,
null
);
return 0;
end;' language 'plpgsql';
select inline_0 ();
drop function inline_0 ();
--
-- The Categories Package
--
-- @author Timo Hentschel (timo@timohentschel.de)
-- @creation-date 2003-04-16
--
begin;
-- create the privileges
select acs_privilege__create_privilege('category_tree_write', null, null);
select acs_privilege__create_privilege('category_tree_read', null, null);
select acs_privilege__create_privilege('category_tree_grant_permissions', null, null);
select acs_privilege__create_privilege('category_admin','Categories Administrator','Categories Administrators');
select acs_privilege__add_child('admin','category_admin');
select acs_privilege__add_child('category_admin','category_tree_read');
select acs_privilege__add_child('category_admin','category_tree_write');
select acs_privilege__add_child('category_admin','category_tree_grant_permissions');
end;
--
-- The Categories Package
-- Extension for linking categories
--
-- @author Timo Hentschel (timo@timohentschel.de)
-- @creation-date 2004-02-04
--
create or replace function category_link__new (
integer, -- from_category_id
integer -- to_category_id
) returns integer as '
-- function for adding category links
declare
p_from_category_id alias for $1;
p_to_category_id alias for $2;
v_link_id integer;
begin
v_link_id := nextval (''category_links_id_seq'');
insert into category_links (link_id, from_category_id, to_category_id)
values (v_link_id, p_from_category_id, p_to_category_id);
return v_link_id;
end;' language 'plpgsql';
create or replace function category_link__del (
integer -- link_id
) returns integer as '
-- function for deleting category links
declare
p_link_id alias for $1;
begin
delete from category_links
where link_id = p_link_id;
return p_link_id;
end;' language 'plpgsql';
--
-- The Categories Package
--
-- @author Timo Hentschel (timo@timohentschel.de)
-- @creation-date 2003-04-16
--
create or replace function category__new (
integer, -- category_id
integer, -- tree_id
varchar, -- locale
varchar, -- name
varchar, -- description
integer, -- parent_id
char, -- deprecated_p
timestamp with time zone, -- creation_date
integer, -- creation_user
varchar -- creation_ip
)
returns integer as '
declare
p_category_id alias for $1;
p_tree_id alias for $2;
p_locale alias for $3;
p_name alias for $4;
p_description alias for $5;
p_parent_id alias for $6;
p_deprecated_p alias for $7;
p_creation_date alias for $8;
p_creation_user alias for $9;
p_creation_ip alias for $10;
v_category_id integer;
v_left_ind integer;
v_right_ind integer;
begin
v_category_id := acs_object__new (
p_category_id, -- object_id
''category'', -- object_type
p_creation_date, -- creation_date
p_creation_user, -- creation_user
p_creation_ip, -- creation_ip
p_tree_id, -- context_id
''t'' -- security_inherit_p
);
if (p_parent_id is null) then
select 1, coalesce(max(right_ind)+1,1) into v_left_ind, v_right_ind
from categories
where tree_id = p_tree_id;
else
select left_ind, right_ind into v_left_ind, v_right_ind
from categories
where category_id = p_parent_id;
end if;
insert into categories
(category_id, tree_id, deprecated_p, parent_id, left_ind, right_ind)
values
(v_category_id, p_tree_id, p_deprecated_p, p_parent_id, -1, -2);
-- move right subtrees to make room for new category
update categories
set left_ind = left_ind + 2,
right_ind = right_ind + 2
where tree_id = p_tree_id
and left_ind > v_right_ind;
-- expand upper nodes to make room for new category
update categories
set right_ind = right_ind + 2
where tree_id = p_tree_id
and left_ind <= v_left_ind
and right_ind >= v_right_ind;
-- insert new category
update categories
set left_ind = v_right_ind,
right_ind = v_right_ind + 1
where category_id = v_category_id;
insert into category_translations
(category_id, locale, name, description)
values
(v_category_id, p_locale, p_name, p_description);
return v_category_id;
end;
' language 'plpgsql';
create or replace function category__new_translation (
integer, -- category_id
varchar, -- locale
varchar, -- name
varchar, -- description
timestamp with time zone, -- modifying_date
integer, -- modifying_user
varchar -- modifying_ip
)
returns integer as '
declare
p_category_id alias for $1;
p_locale alias for $2;
p_name alias for $3;
p_description alias for $4;
p_modifying_date alias for $5;
p_modifying_user alias for $6;
p_modifying_ip alias for $7;
begin
insert into category_translations
(category_id, locale, name, description)
values
(p_category_id, p_locale, p_name, p_description);
update acs_objects
set last_modified = p_modifying_date,
modifying_user = p_modifying_user,
modifying_ip = p_modifying_ip
where object_id = p_category_id;
return 0;
end;
' language 'plpgsql';
create or replace function category__phase_out (
integer -- category_id
)
returns integer as '
declare
p_category_id alias for $1;
begin
update categories
set deprecated_p = ''t''
where category_id = p_category_id;
return 0;
end;
' language 'plpgsql';
create or replace function category__phase_in (
integer -- category_id
)
returns integer as '
declare
p_category_id alias for $1;
begin
update categories
set deprecated_p = ''f''
where category_id = p_category_id;
return 0;
end;
' language 'plpgsql';
create or replace function category__del (
integer -- category_id
)
returns integer as '
declare
p_category_id alias for $1;
v_tree_id integer;
v_left_ind integer;
v_right_ind integer;
node record;
begin
select tree_id, left_ind, right_ind
into v_tree_id, v_left_ind, v_right_ind
from categories where category_id = p_category_id;
for node in
select category_id
from categories
where tree_id = v_tree_id
and left_ind >= v_left_ind
and right_ind <= v_right_ind
loop
delete from category_object_map where category_id = node.category_id;
delete from category_translations where category_id = node.category_id;
delete from categories where category_id = node.category_id;
perform acs_object__delete(node.category_id);
end loop;
update categories
set right_ind = (right_ind - (1 + v_right_ind - v_left_ind))
where left_ind <= v_left_ind
and right_ind > v_left_ind
and tree_id = v_tree_id;
update categories
set right_ind = (right_ind - (1 + v_right_ind - v_left_ind)),
left_ind = (left_ind - (1 + v_right_ind - v_left_ind))
where left_ind > v_left_ind
and tree_id = v_tree_id;
-- for debugging reasons
perform category_tree__check_nested_ind(v_tree_id);
return 0;
end;
' language 'plpgsql';
create or replace function category__edit (
integer, -- category_id
varchar, -- locale
varchar, -- name
varchar, -- description
timestamp with time zone, -- modifying_date
integer, -- modifying_user
varchar -- modifying_ip
)
returns integer as '
declare
p_category_id alias for $1;
p_locale alias for $2;
p_name alias for $3;
p_description alias for $4;
p_modifying_date alias for $5;
p_modifying_user alias for $6;
p_modifying_ip alias for $7;
begin
-- change category name
update category_translations
set name = p_name,
description = p_description
where category_id = p_category_id
and locale = p_locale;
update acs_objects
set last_modified = p_modifying_date,
modifying_user = p_modifying_user,
modifying_ip = p_modifying_ip
where object_id = p_category_id;
return 0;
end;
' language 'plpgsql';
create or replace function category__change_parent (
integer, -- category_id
integer, -- tree_id
integer -- parent_id
)
returns integer as '
declare
p_category_id alias for $1;
p_tree_id alias for $2;
p_parent_id alias for $3;
v_old_left_ind integer;
v_old_right_ind integer;
v_new_left_ind integer;
v_new_right_ind integer;
v_width integer;
begin
update categories
set parent_id = p_parent_id
where category_id = p_category_id;
-- first save the subtree, then compact tree, then expand tree to make room
-- for subtree, then insert it
select left_ind, right_ind into v_old_left_ind, v_old_right_ind
from categories
where category_id = p_category_id;
v_width := v_old_right_ind - v_old_left_ind + 1;
-- cut out old subtree
update categories
set left_ind = -left_ind, right_ind = -right_ind
where tree_id = p_tree_id
and left_ind >= v_old_left_ind
and right_ind <= v_old_right_ind;
-- compact parent trees
update categories
set right_ind = right_ind - v_width
where tree_id = p_tree_id
and left_ind < v_old_left_ind
and right_ind > v_old_right_ind;
-- compact right tree portion
update categories
set left_ind = left_ind - v_width,
right_ind = right_ind - v_width
where tree_id = p_tree_id
and left_ind > v_old_left_ind;
if (p_parent_id is null) then
select 1, max(right_ind)+1 into v_new_left_ind, v_new_right_ind
from categories
where tree_id = p_tree_id;
else
select left_ind, right_ind into v_new_left_ind, v_new_right_ind
from categories
where category_id = p_parent_id;
end if;
-- move parent trees to make room
update categories
set right_ind = right_ind + v_width
where tree_id = p_tree_id
and left_ind <= v_new_left_ind
and right_ind >= v_new_right_ind;
-- move right tree portion to make room
update categories
set left_ind = left_ind + v_width,
right_ind = right_ind + v_width
where tree_id = p_tree_id
and left_ind > v_new_right_ind;
-- insert subtree at correct place
update categories
set left_ind = -left_ind + (v_new_right_ind - v_old_left_ind),
right_ind = -right_ind + (v_new_right_ind - v_old_left_ind)
where tree_id = p_tree_id
and left_ind < 0;
-- for debugging reasons
perform category_tree__check_nested_ind(p_tree_id);
return 0;
end;
' language 'plpgsql';
create or replace function category__name (
integer -- category_id
)
returns integer as '
declare
v_name varchar;
begin
select name into v_name
from category_translations
where category_id = p_category_id
and locale = ''en_US'';
return 0;
end;
' language 'plpgsql';
--
-- The Categories Package
-- Extension for category synonyms
--
-- @author Bernd Schmeil (bernd@thebernd.de)
-- @author Timo Hentschel (timo@timohentschel.de)
-- @creation-date 2004-01-08
--
create or replace function category_synonym__convert_string (varchar(100))
returns varchar(200) as '
-- return string to build search index
declare
p_name alias for $1;
v_index_string varchar(200);
begin
-- convert string to uppercase and substitute special chars
-- TODO: complete
v_index_string := upper (
replace (
replace (
replace (
replace (
replace (
replace (
replace (p_name, ''ä'', ''AE''),
''Ä'', ''AE''),
''ö'', ''OE''),
''Ö'', ''OE''),
''ü'', ''UE''),
''Ü'', ''UE''),
''ß'', ''SS''));
return ('' '' || v_index_string || '' '');
end;' language 'plpgsql';
create or replace function category_synonym__get_similarity (integer, integer, bigint)
returns integer as '
-- calculates similarity of two strings
declare
p_len1 alias for $1;
p_len2 alias for $2;
p_matches alias for $3;
begin
return (p_matches * 200 / (p_len1 + p_len2));
end;' language 'plpgsql';
create or replace function category_synonym__search (varchar(100), varchar(5))
returns integer as '
-- return id for search string
declare
p_search_text alias for $1;
p_locale alias for $2;
v_search_text varchar(200);
v_query_id integer;
v_len integer;
v_i integer;
begin
-- check if search text already exists
select query_id into v_query_id
from category_search
where search_text = p_search_text
and locale = p_locale;
-- simply update old search data if already exists
if (v_query_id is not null) then
update category_search
set queried_count = queried_count + 1,
last_queried = date(''now'')
where query_id = v_query_id;
return (v_query_id);
end if;
-- get new search query id
v_query_id := nextval (''category_search_id_seq'');
-- convert string to uppercase and substitute special chars
v_search_text := category_synonym__convert_string (p_search_text);
-- insert search data
insert into category_search (query_id, search_text, locale, queried_count, last_queried)
values (v_query_id, p_search_text, p_locale, 1, date(''now''));
-- build search index
v_len := length (v_search_text) - 2;
v_i := 1;
while (v_i <= v_len) loop
insert into category_search_index
values (v_query_id, substring (v_search_text, v_i , 3));
v_i := v_i + 1;
end loop;
-- build search result
insert into category_search_results
select v_query_id, s.synonym_id,
category_synonym__get_similarity (v_len, length (s.name) - 2, count(*))
from category_search_index si,
category_synonym_index i,
category_synonyms s
where si.query_id = v_query_id
and si.trigram = i.trigram
and s.synonym_id = i.synonym_id
and s.locale = p_locale
group by s.synonym_id, s.name;
return (v_query_id);
end;' language 'plpgsql';
create or replace function category_synonym__reindex (integer, varchar(100), varchar(5))
returns integer as '
-- build search index for synonym
declare
p_synonym_id alias for $1;
p_name alias for $2;
p_locale alias for $3;
v_name varchar(200);
v_len integer;
v_i integer;
begin
-- delete old search results for this synonym
delete from category_search_results
where synonym_id = p_synonym_id;
-- delete old synonym index for this synonym
delete from category_synonym_index
where synonym_id = p_synonym_id;
-- convert string to uppercase and substitute special chars
v_name := category_synonym__convert_string (p_name);
-- rebuild synonym index
v_len := length (v_name) - 2;
v_i := 1;
while (v_i <= v_len) loop
insert into category_synonym_index
values (p_synonym_id, substring (v_name, v_i , 3));
v_i := v_i + 1;
end loop;
-- rebuild search results
insert into category_search_results
select s.query_id, p_synonym_id,
category_synonym__get_similarity (v_len, length (s.search_text) - 2, count(*))
from category_search_index si,
category_synonym_index i,
category_search s
where i.synonym_id = p_synonym_id
and si.trigram = i.trigram
and si.query_id = s.query_id
and s.locale = p_locale
group by s.query_id, s.search_text;
return (1);
end;' language 'plpgsql';
create or replace function category_synonym__new (varchar(100), varchar(5), integer, integer)
returns integer as '
declare
p_name alias for $1;
p_locale alias for $2;
p_category_id alias for $3;
p_synonym_id alias for $4;
v_synonym_id integer;
begin
-- get new synonym_id
if (p_synonym_id is null) then
v_synonym_id := nextval (''category_synonyms_id_seq'');
else
v_synonym_id := p_synonym_id;
end if;
-- insert synonym data
insert into category_synonyms (synonym_id, category_id, locale, name, synonym_p)
values (v_synonym_id, p_category_id, p_locale, p_name, ''t'');
-- insert in synonym index and search results
PERFORM category_synonym__reindex (v_synonym_id, p_name, p_locale);
return (v_synonym_id);
end;' language 'plpgsql';
create or replace function category_synonym__del (integer)
returns integer as '
-- delete synonym
declare
p_synonym_id alias for $1;
begin
-- delete search results
delete from category_search_results
where synonym_id = p_synonym_id;
-- delete synonym index
delete from category_synonym_index
where synonym_id = p_synonym_id;
-- delete synonym
delete from category_synonyms
where synonym_id = p_synonym_id;
return (1);
end;' language 'plpgsql';
create or replace function category_synonym__edit (integer, varchar(100), varchar(5))
returns integer as '
declare
p_synonym_id alias for $1;
p_new_name alias for $2;
p_locale alias for $3;
begin
-- update synonym data
update category_synonyms
set name = p_new_name,
locale = p_locale
where synonym_id = p_synonym_id;
-- update synonym index and search results
PERFORM category_synonym__reindex (p_synonym_id, p_new_name, p_locale);
return (p_synonym_id);
end;' language 'plpgsql';
-----
-- triggers for category synonyms
-----
create or replace function category_synonym__new_cat_trans_trg ()
returns trigger as '
-- trigger function for inserting category translation
declare
v_synonym_id integer;
begin
-- create synonym
v_synonym_id := category_synonym__new (NEW.name, NEW.locale, NEW.category_id, null);
-- mark synonym as not editable for users
update category_synonyms
set synonym_p = ''f''
where synonym_id = v_synonym_id;
return new;
end;' language 'plpgsql';
create or replace function category_synonym__edit_cat_trans_trg ()
returns trigger as '
-- trigger function for updating a category translation
declare
v_synonym_id integer;
begin
-- get synonym_id of updated category translation
select synonym_id into v_synonym_id
from category_synonyms
where category_id = OLD.category_id
and name = OLD.name
and locale = OLD.locale
and synonym_p = ''f'';
-- update synonym
PERFORM category_synonym__edit (v_synonym_id, NEW.name, NEW.locale);
return new;
end;' language 'plpgsql';
create trigger category_synonym__insert_cat_trans_trg
after insert
on category_translations for each row
execute procedure category_synonym__new_cat_trans_trg();
create trigger category_synonym__update_cat_trans_trg
before update
on category_translations for each row
execute procedure category_synonym__edit_cat_trans_trg();
--
-- The Categories Package
--
-- @author Timo Hentschel (timo@timohentschel.de)
-- @author Michael Steigman (michael@steigman.net)
-- @creation-date 2003-04-16
--
create or replace function category_tree__new (
integer, -- tree_id
varchar, -- locale
varchar, -- tree_name
varchar, -- description
char, -- site_wide_p
timestamp with time zone, -- creation_date
integer, -- creation_user
varchar, -- creation_ip
integer -- context_id
)
returns integer as '
declare
p_tree_id alias for $1;
p_locale alias for $2;
p_tree_name alias for $3;
p_description alias for $4;
p_site_wide_p alias for $5;
p_creation_date alias for $6;
p_creation_user alias for $7;
p_creation_ip alias for $8;
p_context_id alias for $9;
v_tree_id integer;
begin
v_tree_id := acs_object__new (
p_tree_id, -- object_id
''category_tree'', -- object_type
p_creation_date, -- creation_date
p_creation_user, -- creation_user
p_creation_ip, -- creation_ip
p_context_id -- context_id
);
insert into category_trees
(tree_id, site_wide_p)
values
(v_tree_id, p_site_wide_p);
perform acs_permission__grant_permission (
v_tree_id, -- object_id
p_creation_user, -- grantee_id
''category_tree_read'' -- privilege
);
perform acs_permission__grant_permission (
v_tree_id, -- object_id
p_creation_user, -- grantee_id
''category_tree_write'' -- privilege
);
perform acs_permission__grant_permission (
v_tree_id, -- object_id
p_creation_user, -- grantee_id
''category_tree_grant_permissions'' -- privilege
);
insert into category_tree_translations
(tree_id, locale, name, description)
values
(v_tree_id, p_locale, p_tree_name, p_description);
return v_tree_id;
end;
' language 'plpgsql';
create or replace function category_tree__new_translation (
integer, -- tree_id
varchar, -- locale
varchar, -- tree_name
varchar, -- description
timestamp with time zone, -- modifying_date
integer, -- modifying_user
varchar -- modifying_ip
)
returns integer as '
declare
p_tree_id alias for $1;
p_locale alias for $2;
p_tree_name alias for $3;
p_description alias for $4;
p_modifying_date alias for $5;
p_modifying_user alias for $6;
p_modifying_ip alias for $7;
begin
insert into category_tree_translations
(tree_id, locale, name, description)
values
(p_tree_id, p_locale, p_tree_name, p_description);
update acs_objects
set last_modified = p_modifying_date,
modifying_user = p_modifying_user,
modifying_ip = p_modifying_ip
where object_id = p_tree_id;
return 0;
end;
' language 'plpgsql';
create or replace function category_tree__del (
integer -- tree_id
)
returns integer as '
declare
p_tree_id alias for $1;
begin
delete from category_tree_map where tree_id = p_tree_id;
delete from category_object_map where category_id in (select category_id from categories where tree_id = p_tree_id);
delete from category_translations where category_id in (select category_id from categories where tree_id = p_tree_id);
delete from categories where tree_id = p_tree_id;
delete from acs_objects where context_id = p_tree_id;
delete from acs_permissions where object_id = p_tree_id;
delete from category_tree_translations where tree_id = p_tree_id;
delete from category_trees where tree_id = p_tree_id;
perform acs_object__delete(p_tree_id);
return 0;
end;
' language 'plpgsql';
create or replace function category_tree__edit (
integer, -- tree_id
varchar, -- locale
varchar, -- tree_name
varchar, -- description
char, -- site_wide_p
timestamp with time zone, -- modifying_date
integer, -- modifying_user
varchar -- modifying_ip
)
returns integer as '
declare
p_tree_id alias for $1;
p_locale alias for $2;
p_tree_name alias for $3;
p_description alias for $4;
p_site_wide_p alias for $5;
p_modifying_date alias for $6;
p_modifying_user alias for $7;
p_modifying_ip alias for $8;
begin
update category_trees
set site_wide_p = p_site_wide_p
where tree_id = p_tree_id;
update category_tree_translations
set name = p_tree_name,
description = p_description
where tree_id = p_tree_id
and locale = p_locale;
update acs_objects
set last_modified = p_modifying_date,
modifying_user = p_modifying_user,
modifying_ip = p_modifying_ip
where object_id = p_tree_id;
return 0;
end;
' language 'plpgsql';
create or replace function category_tree__copy (
integer, -- source_tree
integer, -- dest_tree
integer, -- creation_user
varchar -- creation_ip
)
returns integer as '
declare
p_source_tree alias for $1;
p_dest_tree alias for $2;
p_creation_user alias for $3;
p_creation_ip alias for $4;
v_new_left_ind integer;
v_category_id integer;
begin
select coalesce(max(right_ind),0) into v_new_left_ind
from categories
where tree_id = p_dest_tree;
for source in (select category_id, parent_id, left_ind, right_ind from categories where tree_id = p_source_tree) loop
v_category_id := acs_object__new (
''category'', -- object_type
now(), -- creation_date
p_creation_user, -- creation_user
p_creation_ip, -- creation_ip
p_dest_tree -- context_id
);
insert into categories
(category_id, tree_id, parent_id, left_ind, right_ind)
values
(v_category_id, p_dest_tree, source.parent_id, source.left_ind + v_new_left_ind, source.right_ind + v_new_left_ind);
end loop;
-- correct parent_ids
update categories c
set parent_id = (select t.category_id
from categories s, categories t
where s.category_id = c.parent_id
and t.tree_id = copy.dest_tree
and s.left_ind + v_new_left_ind = t.left_ind)
where tree_id = p_dest_tree;
-- copy all translations
insert into category_translations
(category_id, locale, name, description)
(select ct.category_id, t.locale, t.name, t.description
from category_translations t, categories cs, categories ct
where ct.tree_id = p_dest_tree
and cs.tree_id = p_source_tree
and cs.left_ind + v_new_left_ind = ct.left_ind
and t.category_id = cs.category_id);
-- for debugging reasons
perform category_tree__check_nested_ind(p_dest_tree);
return 0;
end;
' language 'plpgsql';
create or replace function category_tree__map (
integer, -- object_id
integer, -- tree_id
integer, -- subtree_category_id
char, -- assign_single_p
char -- require_category_p
)
returns integer as '
declare
p_object_id alias for $1;
p_tree_id alias for $2;
p_subtree_category_id alias for $3;
p_assign_single_p alias for $4;
p_require_category_p alias for $5;
v_map_count integer;
begin
select count(*)
into v_map_count
from category_tree_map
where object_id = p_object_id
and tree_id = p_tree_id;
if v_map_count = 0 then
insert into category_tree_map
(tree_id, subtree_category_id, object_id,
assign_single_p, require_category_p)
values (p_tree_id, p_subtree_category_id, p_object_id,
p_assign_single_p, p_require_category_p);
end if;
return 0;
end;
' language 'plpgsql';
create or replace function category_tree__unmap (
integer, -- object_id
integer -- tree_id
)
returns integer as '
declare
p_object_id alias for $1;
p_tree_id alias for $2;
begin
delete from category_tree_map
where object_id = p_object_id
and tree_id = p_tree_id;
return 0;
end;
' language 'plpgsql';
create or replace function category_tree__name (
integer -- tree_id
)
returns varchar as '
declare
p_tree_id alias for $1;
v_name varchar;
begin
select name into v_name
from category_tree_translations
where tree_id = p_tree_id
and locale = ''en_US'';
return v_name;
end;
' language 'plpgsql';
create or replace function category_tree__check_nested_ind (
integer -- tree_id
)
returns integer as '
declare
p_tree_id alias for $1;
v_negative numeric;
v_order numeric;
v_parent numeric;
begin
select count(*) into v_negative from categories
where tree_id = p_tree_id and (left_ind < 1 or right_ind < 1);
if v_negative > 0 then
raise EXCEPTION ''-20001: negative index not allowed!'';
end if;
select count(*) into v_order from categories
where tree_id = p_tree_id
and left_ind >= right_ind;
if v_order > 0 then
raise EXCEPTION ''-20002: right index must be greater than left index!'';
end if;
select count(*) into v_parent
from categories parent, categories child
where parent.tree_id = p_tree_id
and child.tree_id = parent.tree_id
and (parent.left_ind >= child.left_ind or parent.right_ind <= child.right_ind)
and child.parent_id = parent.category_id;
if v_parent > 0 then
raise EXCEPTION ''-20003: child index must be between parent index!'';
end if;
return 0;
end;
' language 'plpgsql';
--
-- Adding view useful when getting categories for a specific tree
--
create or replace view category_object_map_tree as
select c.category_id,
c.tree_id,
m.object_id
from category_object_map m,
categories c
where c.category_id = m.category_id;
alter table category_tree_map add column
assign_single_p char(1) constraint cat_tree_map_single_p_ck check (assign_single_p in ('t','f'))
;
alter table category_tree_map alter column assign_single_p set default 'f';
alter table category_tree_map add column
require_category_p char(1) constraint cat_tree_map_categ_p_ck check (require_category_p in ('t','f'))
;
alter table category_tree_map alter column require_category_p set default 'f';
update category_tree_map set assign_single_p = 'f', require_category_p = 'f';
comment on column category_tree_map.assign_single_p is '
Are the users allowed to assign multiple or only a single category
to objects?
';
comment on column category_tree_map.require_category_p is '
Do the users have to assign at least one category to objects?
';
drop function category_tree__map (integer,integer,integer);
create or replace function category_tree__map (
integer, -- object_id
integer, -- tree_id
integer, -- subtree_category_id
char, -- assign_single_p
char -- require_category_p
)
returns integer as '
declare
p_object_id alias for $1;
p_tree_id alias for $2;
p_subtree_category_id alias for $3;
p_assign_single_p alias for $4;
p_require_category_p alias for $5;
v_map_count integer;
begin
select count(*)
into v_map_count
from category_tree_map
where object_id = p_object_id
and tree_id = p_tree_id;
if v_map_count = 0 then
insert into category_tree_map
(tree_id, subtree_category_id, object_id,
assign_single_p, require_category_p)
values (p_tree_id, p_subtree_category_id, p_object_id,
p_assign_single_p, p_require_category_p);
end if;
return 0;
end;
' language 'plpgsql';
-----
-- category links
-----
create table category_links (
link_id integer not null
constraint category_links_pk primary key,
from_category_id integer not null
constraint category_links_from_fk
references categories on delete cascade,
to_category_id integer not null
constraint category_links_to_fk
references categories on delete cascade,
constraint category_links_un
unique (from_category_id, to_category_id)
);
create unique index category_links_rev_ix on category_links (to_category_id, from_category_id);
create sequence category_links_id_seq;
comment on table category_links is '
Stores directed graph of linked categories. If category A
and category B are linked, then any categorization on A
will result in an additional categorization in B.
';
comment on column category_links.link_id is '
Primary key.
';
comment on column category_links.from_category_id is '
Category the link is coming from. Any categorization in this
category will trigger a categorization in the other category.
';
comment on column category_links.to_category_id is '
Category the link is coming to. Any categorization in the other
category will trigger a categorization in this category.
';
\i ../category-link-package.sql
drop function category_synonym__convert_string (varchar);
drop function category_synonym__get_similarity (integer, integer, bigint);
drop function category_synonym__search (varchar, varchar);
drop function category_synonym__reindex (integer, varchar, varchar);
drop function category_synonym__new (varchar, varchar, integer, integer);
drop function category_synonym__del (integer);
drop function category_synonym__edit (integer, varchar, varchar);
drop function category_synonym__edit_cat_trans_trg () cascade;
drop function category_synonym__new_cat_trans_trg () cascade;
drop table category_search_results;
drop table category_search_index;
drop table category_search;
drop table category_synonym_index;
drop table category_synonyms;
drop sequence category_search_id_seq;
drop sequence category_synonyms_id_seq;
-----
-- Synonyms
-----
create table category_synonyms (
synonym_id integer not null
constraint category_synonyms_pk primary key,
category_id integer not null
constraint category_synonyms_cat_fk
references categories on delete cascade,
locale varchar(5) not null
constraint category_synonyms_locale_fk
references ad_locales on delete cascade,
name varchar(100) not null,
synonym_p char(1) default 't'
constraint category_synonyms_synonym_p_ck
check (synonym_p in ('t','f'))
);
-- to get all synonyms in given locale
create index category_synonyms_locale_ix on category_synonyms(category_id, locale);
-- to sort synonyms by name
create index category_synonyms_name_ix on category_synonyms(category_id, name);
create sequence category_synonyms_id_seq;
comment on table category_synonyms is '
Stores multilingual synonyms of the categories.
';
comment on column category_synonyms.synonym_id is '
Primary key.
';
comment on column category_synonyms.category_id is '
Category the synonyms are refering to.
';
comment on column category_synonyms.locale is '
Language of the synonym.
';
comment on column category_synonyms.name is '
Actual synonym of the category in given language.
';
comment on column category_synonyms.synonym_p is '
Marks if the entry is a synonym to be edited by user or is a copy
of a category translation and cannot be edited directly.
';
create table category_synonym_index (
-- category synonyms split up in 3-grams to be used by fuzzy search
synonym_id integer not null
constraint category_synonym_index_fk
references category_synonyms on delete cascade,
trigram char(3) not null
);
-- to get all synonyms of given 3-gram
create index category_syn_index_trigram_ix on category_synonym_index(trigram);
-- to delete all 3-grams of given synonym
create index category_syn_index_synonym_ix on category_synonym_index(synonym_id);
comment on table category_synonym_index is '
Stores the synonym cut down in portions of 3 characters
to be used in search.
';
comment on column category_synonym_index.synonym_id is '
Id of the synonym refered to.
';
comment on column category_synonym_index.trigram is '
3 character part of the synonym.
';
create table category_search (
query_id integer not null
constraint category_search_id_pk primary key,
search_text varchar(200) not null,
locale varchar(5) not null
constraint category_search_locale_fk
references ad_locales on delete cascade,
queried_count integer default 1 not null,
last_queried timestamptz default current_timestamp not null,
constraint category_search_query_un
unique (search_text, locale)
);
-- to delete old queries
create index category_search_date_ix on category_search(last_queried);
create sequence category_search_id_seq;
comment on table category_search is '
Stores users multilingual search texts for category synonyms.
';
comment on column category_search.query_id is '
Primary key.
';
comment on column category_search.locale is '
Language of the search text.
';
comment on column category_search.search_text is '
Actual search text in given language.
';
comment on column category_search.queried_count is '
Counts how often this search text has been used by users.
';
comment on column category_search.last_queried is '
Date of last usage of this search text.
A sweeper will delete search texts not used for a while.
';
create table category_search_index (
query_id integer not null
constraint category_search_index_fk
references category_search on delete cascade,
trigram char(3) not null
);
-- to get all search texts of given 3-gram
create index category_search_ind_trigram_ix on category_search_index(trigram);
-- to delete all 3-grams of given search text
create index category_search_ind_query_ix on category_search_index(query_id);
comment on table category_search_index is '
Stores the search text cut down in portions of 3 characters
to be used in search.
';
comment on column category_search_index.query_id is '
Id of the search text refered to.
';
comment on column category_search_index.trigram is '
3 character part of the search text.
';
create table category_search_results (
query_id integer not null
constraint category_results_query_fk
references category_search on delete cascade,
synonym_id integer not null
constraint category_results_synonym_fk
references category_synonyms on delete cascade,
similarity integer not null,
constraint category_search_results_pk
primary key (query_id, synonym_id)
);
-- to sort all matches found by similarity
create index category_results_similarity_ix on category_search_results (query_id, similarity);
comment on table category_search_results is '
Stores the result of a users search in synonyms,
stores matching synonyms and their degree of similarity
to the search text.
';
comment on column category_search_results.query_id is '
Id of the search text.
';
comment on column category_search_results.synonym_id is '
Id of the synonym found.
';
comment on column category_search_results.similarity is '
Percent of similarity between search text and found synonym.
';
\i ../category-synonym-package.sql
-- insert existing category translations as synonyms
-- and build synonym index
create function inline_0 ()
returns integer as '
declare
rec_translations record;
v_synonym_id integer;
begin
for rec_translations in
select category_id, name, locale
from category_translations
loop
v_synonym_id := category_synonym__new (rec_translations.name, rec_translations.locale, rec_translations.category_id, null);
update category_synonyms set synonym_p = ''f'' where synonym_id = v_synonym_id;
end loop;
return 0;
end;' language 'plpgsql';
select inline_0 ();
drop function inline_0 ();
--
-- packages/categories/sql/postgresql/upgrade/upgrade-1.0d6-1.0d7.sql
--
-- @author Deds Castillo (deds@i-manila.com.ph)
-- @creation-date 2005-01-13
-- @arch-tag: a966a122-5391-45e3-8176-dc0956fc9450
-- @cvs-id $Id$
--
-----
--
-- drop trigger as we force update the synonyms and we do not want to end
-- up with cyclic problems
--
-----
drop trigger category_synonym__insert_cat_trans_trg on category_translations;
drop trigger category_synonym__update_cat_trans_trg on category_translations;
-----
--
-- fix entries destroyed by old procs
--
----
create function inline_0 ()
returns integer as '
declare
v_name category_translations.name%TYPE;
v_synonym_cursor RECORD;
begin
FOR v_synonym_cursor IN
select category_id,
locale
from category_synonyms
where synonym_p = ''f''
LOOP
select name into v_name
from category_translations
where category_id = v_synonym_cursor.category_id
and locale = v_synonym_cursor.locale;
update category_synonyms
set name = v_name
where category_id = v_synonym_cursor.category_id
and locale = v_synonym_cursor.locale;
END LOOP;
return 0;
end;
' language 'plpgsql';
select inline_0 ();
drop function inline_0 ();
-----
--
-- recreate functions that return the proper record
--
-----
create or replace function category_synonym__new_cat_trans_trg ()
returns trigger as '
-- trigger function for inserting category translation
declare
v_synonym_id integer;
begin
-- create synonym
v_synonym_id := category_synonym__new (NEW.name, NEW.locale, NEW.category_id, null);
-- mark synonym as not editable for users
update category_synonyms
set synonym_p = ''f''
where synonym_id = v_synonym_id;
return new;
end;' language 'plpgsql';
create or replace function category_synonym__edit_cat_trans_trg ()
returns trigger as '
-- trigger function for updating a category translation
declare
v_synonym_id integer;
begin
-- get synonym_id of updated category translation
select synonym_id into v_synonym_id
from category_synonyms
where category_id = OLD.category_id
and name = OLD.name
and locale = OLD.locale
and synonym_p = ''f'';
-- update synonym
PERFORM category_synonym__edit (v_synonym_id, NEW.name, NEW.locale);
return new;
end;' language 'plpgsql';
-----
--
-- recreate triggers
--
-----
create trigger category_synonym__insert_cat_trans_trg
after insert
on category_translations for each row
execute procedure category_synonym__new_cat_trans_trg();
create trigger category_synonym__update_cat_trans_trg
before update
on category_translations for each row
execute procedure category_synonym__edit_cat_trans_trg();
-----
--
-- these function have embedded tabs which make pg or is is the driver(?) barf
-- fix them to have spaces
--
-----
create or replace function category__edit (
integer, -- category_id
varchar, -- locale
varchar, -- name
varchar, -- description
timestamp with time zone, -- modifying_date
integer, -- modifying_user
varchar -- modifying_ip
)
returns integer as '
declare
p_category_id alias for $1;
p_locale alias for $2;
p_name alias for $3;
p_description alias for $4;
p_modifying_date alias for $5;
p_modifying_user alias for $6;
p_modifying_ip alias for $7;
begin
-- change category name
update category_translations
set name = p_name,
description = p_description
where category_id = p_category_id
and locale = p_locale;
update acs_objects
set last_modified = p_modifying_date,
modifying_user = p_modifying_user,
modifying_ip = p_modifying_ip
where object_id = p_category_id;
return 0;
end;
' language 'plpgsql';
ad_library {
Procs for the site-wide categorization package.
@author Timo Hentschel (timo@timohentschel.de)
@creation-date 16 April 2003
@cvs-id $Id:
}
category::reset_translation_cache
category_tree::reset_translation_cache
category_tree::reset_cache
ad_schedule_proc -thread t -schedule_proc ns_schedule_daily [list 0 16] category_synonym::search_sweeper
<?xml version="1.0"?>
<queryset>
<rdbms><type>oracle</type><version>8.1.6</version></rdbms>
<fullquery name="category::add.insert_category">
<querytext>
begin
:1 := category.new (
category_id => :category_id,
locale => :locale,
name => :name,
description => :description,
tree_id => :tree_id,
parent_id => :parent_id,
creation_user => :user_id,
creation_ip => :creation_ip
);
end;
</querytext>
</fullquery>
<fullquery name="category::add.insert_default_category">
<querytext>
begin
category.new_translation (
category_id => :category_id,
locale => :default_locale,
name => :name,
description => :description,
modifying_user => :user_id,
modifying_ip => :creation_ip
);
end;
</querytext>
</fullquery>
<fullquery name="category::update.insert_category_translation">
<querytext>
begin
category.new_translation (
category_id => :category_id,
locale => :locale,
name => :name,
description => :description,
modifying_user => :user_id,
modifying_ip => :modifying_ip
);
end;
</querytext>
</fullquery>
<fullquery name="category::update.update_category_translation">
<querytext>
begin
category.edit (
category_id => :category_id,
locale => :locale,
name => :name,
description => :description,
modifying_user => :user_id,
modifying_ip => :modifying_ip
);
end;
</querytext>
</fullquery>
<fullquery name="category::delete.delete_category">
<querytext>
begin
category.del ( :category_id );
end;
</querytext>
</fullquery>
<fullquery name="category::change_parent.change_parent_category">
<querytext>
begin
category.change_parent (
category_id => :category_id,
tree_id => :tree_id,
parent_id => :parent_id
);
end;
</querytext>
</fullquery>
<fullquery name="category::phase_in.phase_in">
<querytext>
begin
category.phase_in(:category_id);
end;
</querytext>
</fullquery>
<fullquery name="category::phase_out.phase_out">
<querytext>
begin
category.phase_out(:category_id);
end;
</querytext>
</fullquery>
<fullquery name="category::get_object_context.object_name">
<querytext>
select acs_object.name(:object_id) from dual
</querytext>
</fullquery>
</queryset>
<?xml version="1.0"?>
<queryset>
<rdbms><type>postgresql</type><version>7.1</version></rdbms>
<fullquery name="category::add.insert_category">
<querytext>
select category__new (
:category_id,
:tree_id,
:locale,
:name,
:description,
:parent_id,
:deprecated_p,
current_timestamp,
:user_id,
:creation_ip
)
</querytext>
</fullquery>
<fullquery name="category::add.insert_default_category">
<querytext>
select category__new_translation (
:category_id,
:default_locale,
:name,
:description,
current_timestamp,
:user_id,
:creation_ip
)
</querytext>
</fullquery>
<fullquery name="category::update.insert_category_translation">
<querytext>
select category__new_translation (
:category_id,
:locale,
:name,
:description,
current_timestamp,
:user_id,
:modifying_ip
)
</querytext>
</fullquery>
<fullquery name="category::update.update_category_translation">
<querytext>
select category__edit (
:category_id,
:locale,
:name,
:description,
current_timestamp,
:user_id,
:modifying_ip
)
</querytext>
</fullquery>
<fullquery name="category::delete.delete_category">
<querytext>
select category__del ( :category_id )
</querytext>
</fullquery>
<fullquery name="category::change_parent.change_parent_category">
<querytext>
select category__change_parent (
:category_id,
:tree_id,
:parent_id
)
</querytext>
</fullquery>
<fullquery name="category::phase_in.phase_in">
<querytext>
select category__phase_in(:category_id)
</querytext>
</fullquery>
<fullquery name="category::phase_out.phase_out">
<querytext>
select category__phase_out(:category_id)
</querytext>
</fullquery>
<fullquery name="category::get_object_context.object_name">
<querytext>
select acs_object__name(:object_id)
</querytext>
</fullquery>
</queryset>
This diff is collapsed.
<?xml version="1.0"?>
<queryset>
<fullquery name="category::update.check_category_existence">
<querytext>
select 1
from category_translations
where category_id = :category_id
and locale = :locale
</querytext>
</fullquery>
<fullquery name="category::map_object.remove_mapped_categories">
<querytext>
delete from category_object_map
where object_id = :object_id
</querytext>
</fullquery>
<fullquery name="category::map_object.insert_mapped_categories">
<querytext>
insert into category_object_map (category_id, object_id)
values (:category_id, :object_id)
</querytext>
</fullquery>
<fullquery name="category::map_object.insert_linked_categories">
<querytext>
insert into category_object_map (category_id, object_id)
(select l.to_category_id as category_id, m.object_id
from category_links l, category_object_map m
where l.from_category_id = m.category_id
and m.object_id = :object_id
and not exists (select 1
from category_object_map m2
where m2.object_id = :object_id
and m2.category_id = l.to_category_id))
</querytext>
</fullquery>
<fullquery name="category::get_mapped_categories.get_mapped_categories">
<querytext>
select category_id
from category_object_map
where object_id = :object_id
</querytext>
</fullquery>
<fullquery name="category::reset_translation_cache.reset_translation_cache">
<querytext>
select t.category_id, c.tree_id, t.locale, t.name
from category_translations t, categories c
where t.category_id = c.category_id
order by t.category_id, t.locale
</querytext>
</fullquery>
<fullquery name="category::flush_translation_cache.flush_translation_cache">
<querytext>
select t.locale, t.name, c.tree_id
from category_translations t, categories c
where t.category_id = :category_id
and t.category_id = c.category_id
order by t.locale
</querytext>
</fullquery>
<fullquery name="category::pageurl.get_tree_id_for_pageurl">
<querytext>
select tree_id
from categories
where category_id = :object_id
</querytext>
</fullquery>
</queryset>
ad_library {
Procs for the integration in ad_form of the site-wide categorization package.
@author Branimir Dolicki (bdolicki@branimir.com)
@creation-date 06 February 2004
@cvs-id $Id:
}
namespace eval category::ad_form {}
ad_proc -public category::ad_form::add_widgets {
{-container_object_id:required}
{-categorized_object_id}
{-form_name:required}
{-element_name "category_id"}
} {
For each category tree associated with this container_object_id (usually
package_id) put a category widget into the ad_form. On form submission the
procedure category::ad_form::get_categories should be called to collect
the categories in which this object belongs.
@author Branimir Dolicki (bdolicki@branimir.com)
} {
set category_trees [category_tree::get_mapped_trees $container_object_id]
foreach tree $category_trees {
util_unlist $tree tree_id name subtree_id assign_single_p require_category_p
set options ""
if {$assign_single_p == "f"} {
set options ",multiple"
}
if {$require_category_p == "f"} {
append options ",optional"
}
ad_form -extend -name $form_name -form \
[list [list __category__ad_form__$element_name\_${tree_id}:category$options \
{label $name} \
{category_tree_id $tree_id} \
{category_subtree_id $subtree_id} \
{category_object_id {[value_if_exists categorized_object_id]}} \
{category_assign_single_p $assign_single_p} \
{category_require_category_p $require_category_p}]]
}
}
ad_proc -public category::ad_form::get_categories {
{-container_object_id:required}
{-element_name "category_id"}
} {
Collects categories from the category widget in the format compatible with
category::add_ad_form_elements. To be used in the -on_submit clause of
ad_form.
@author Branimir Dolicki (bdolicki@branimir.com)
} {
set category_trees [category_tree::get_mapped_trees $container_object_id]
set category_ids [list]
foreach tree $category_trees {
util_unlist $tree tree_id name subtree_id assign_single_p require_category_p
upvar #[template::adp_level] \
__category__ad_form__$element_name\_${tree_id} my_category_ids
eval lappend category_ids $my_category_ids
}
return $category_ids
}
<?xml version="1.0"?>
<queryset>
<rdbms><type>oracle</type><version>8.1.6</version></rdbms>
<fullquery name="category_link::add.insert_category_link">
<querytext>
begin
:1 := category_link.new (
from_category_id => :from_category_id,
to_category_id => :to_category_id
);
end;
</querytext>
</fullquery>
<fullquery name="category_link::delete.delete_category_link">
<querytext>
begin
category_link.del ( :link_id );
end;
</querytext>
</fullquery>
</queryset>
<?xml version="1.0"?>
<queryset>
<rdbms><type>postgresql</type><version>7.1</version></rdbms>
<fullquery name="category_link::add.insert_category_link">
<querytext>
select category_link__new (
:from_category_id,
:to_category_id
)
</querytext>
</fullquery>
<fullquery name="category_link::delete.delete_category_link">
<querytext>
select category_link__del ( :link_id )
</querytext>
</fullquery>
</queryset>
ad_library {
category-links procs for the site-wide categorization package.
@author Timo Hentschel (timo@timohentschel.de)
@creation-date 04 February 2004
@cvs-id $Id:
}
namespace eval category_link {}
ad_proc -public category_link::add {
{-from_category_id:required}
{-to_category_id:required}
} {
Insert a new category link.
@option from_category_id category_id the links comes from.
@option to_category_id category_id the link goes to.
@return link_id
@author Timo Hentschel (timo@timohentschel.de)
} {
db_transaction {
set link_id [db_exec_plsql insert_category_link ""]
}
return $link_id
}
ad_proc -public category_link::delete { link_id } {
Deletes a category link.
@param link_id category link to be deleted.
@author Timo Hentschel (timo@timohentschel.de)
} {
db_exec_plsql delete_category_link ""
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
ad_page_contract {
Deletes a category from a category tree
@author Timo Hentschel (timo@timohentschel.de)
@cvs-id $Id:
} {
tree_id:integer
category_id:integer,multiple
{locale ""}
object_id:integer,optional
}
set user_id [ad_maybe_redirect_for_registration]
permission::require_permission -object_id $tree_id -privilege category_tree_write
db_transaction {
foreach category_id [db_list order_categories_for_delete ""] {
category::delete $category_id
}
category_tree::flush_cache $tree_id
} on_error {
ad_return_complaint 1 {{Error deleting category. A category probably still contains subcategories. If you really want to delete those subcategories, please delete them first. Thank you.}}
return
}
ad_returnredirect [export_vars -no_empty -base tree-view {tree_id locale object_id}]
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<master src="master">
<property name="page_title">@page_title;noquote@</property>
<property name="context_bar">@context_bar;noquote@</property>
<property name="change_locale">f</property>
<property name="focus">category_form.name</property>
<blockquote>
<formtemplate id="category_form"></formtemplate>
</blockquote>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment