Index: openacs-4/packages/xowiki/tcl/xowiki-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xowiki/tcl/xowiki-procs.tcl,v diff -u -r1.473 -r1.474 --- openacs-4/packages/xowiki/tcl/xowiki-procs.tcl 27 Oct 2014 16:42:05 -0000 1.473 +++ openacs-4/packages/xowiki/tcl/xowiki-procs.tcl 27 Apr 2015 15:28:23 -0000 1.474 @@ -34,7 +34,7 @@ # -spec "richtext" ::xo::Attribute create nls_language \ -spec {select,options=[xowiki::locales]} \ - -default [ad_conn locale] + -default {[ad_conn locale]} #::xo::Attribute create publish_date \ # -spec date ::xo::Attribute create last_modified \ @@ -43,9 +43,9 @@ -spec user_id } \ -extend_slot {title -required false} \ - -extend_slot {description -spec "textarea,cols=80,rows=2"} \ + -extend_slot {description -spec "textarea,cols=80,rows=2,label=#xowiki.Page-description#"} \ -extend_slot {text -spec "richtext"} \ - -extend_slot {publish_date -spec "date"} \ + -extend_slot {publish_date -spec "date,label=#xowiki.Page-publish_date#"} \ -parameter { {render_adp 1} {do_substitutions 1} @@ -142,9 +142,10 @@ -spec "hidden" ::xo::db::CrAttribute create state -default "" } - - # create various extra tables, indices and views + # + # Create various extra tables, indices and views + # ::xo::db::require index -table xowiki_form_page -col assignee ::xo::db::require index -table xowiki_page_instance -col page_template @@ -154,8 +155,9 @@ page {integer references cr_items(item_id) on delete cascade} }] ::xo::db::require index -table xowiki_references -col reference + ::xo::db::require index -table xowiki_references -col page - + ::xo::db::require table xowiki_last_visited { page_id {integer references cr_items(item_id) on delete cascade} package_id integer @@ -167,6 +169,7 @@ ::xo::db::require index -table xowiki_last_visited -col user_id,page_id -unique true ::xo::db::require index -table xowiki_last_visited -col user_id,package_id ::xo::db::require index -table xowiki_last_visited -col time + ::xo::db::require index -table xowiki_last_visited -col page_id # Oracle has a limit of 3118 characters for keys, therefore we @@ -182,10 +185,16 @@ ::xo::db::require index -table xowiki_tags -col tag,package_id ::xo::db::require index -table xowiki_tags -col user_id,package_id ::xo::db::require index -table xowiki_tags -col package_id - + ::xo::db::require index -table xowiki_tags -col user_id + ::xo::db::require index -table xowiki_tags -col item_id + ::xo::db::require index -table xowiki_page -col page_order \ -using [expr {[::xo::dc has_ltree] ? "gist" : ""}] + # + # view: xowiki_page_live_revision + # + set sortkeys [expr {[db_driverkey ""] eq "oracle" ? "" : ", ci.tree_sortkey, ci.max_child_sortkey"}] ::xo::db::require view xowiki_page_live_revision \ "select p.*, cr.*,ci.parent_id, ci.name, ci.locale, ci.live_revision, \ @@ -196,14 +205,209 @@ and p.page_id = cr.revision_id \ and ci.publish_status <> 'production'" + # + # xowiki_form_instance_item_index: + # + # A materialized table of xowiki formpage instances, containing + # just the item information, but combined with other attributes + # frequently used for indexing (like page_id, paren_id, ... hkey). + # + # Rationale: The quality of indices on cr_revisions tend to + # decrease when there are many revisions stored in the database, + # since the number of duplicates increases due to non-live + # revisions. This table can be used for indexing just the live + # revision on the item level. Example query combining package_id + # and page_template: + # + # select count(*) from xowiki_form_instance_item_index + # where package_id = 18255683 + # and page_template = 20260757 + # and publish_status='ready'; + # + # In order to get rid of this helper table (may be to regenerate it + # on the next load) use + # + # drop table xowiki_form_instance_item_index cascade; + # + # + #ns_logctl severity Debug(sql) on + # + # $populate is a dml statement to populate the materialized index + # xowiki_form_instance_item_index, when it does not exist. + # + set popuplate { + insert into xowiki_form_instance_item_index ( + item_id, name, package_id, parent_id, publish_status, + page_template, assignee, state ) + select ci.item_id, ci.name, o.package_id, ci.parent_id, ci.publish_status, + xpi.page_template, xfp.assignee, xfp.state + from cr_items ci + join xowiki_page_instance xpi on (ci.live_revision = xpi.page_instance_id) + join xowiki_form_page xfp on (ci.live_revision = xfp.xowiki_form_page_id) + join acs_objects o on (o.object_id = ci.item_id) + } + + if {[::xo::dc has_hstore]} { + ::xo::db::require table xowiki_form_instance_item_index { + item_id {integer references cr_items(item_id) on delete cascade} + name {character varying(400)} + package_id {integer} + parent_id {integer references cr_items(item_id) on delete cascade} + publish_status {text} + page_template {integer references cr_items(item_id) on delete cascade} + hkey {hstore} + assignee {integer references parties(party_id) on delete cascade} + state {text} + } $popuplate + ::xo::db::require index -table xowiki_form_instance_item_index -col hkey -using gist + set hkey_in_view "xi.hkey," + } else { + ::xo::db::require table xowiki_form_instance_item_index { + item_id {integer references cr_items(item_id) on delete cascade} + name {character varying(400)} + package_id {integer} + parent_id {integer references cr_items(item_id) on delete cascade} + publish_status {text} + page_template {integer references cr_items(item_id) on delete cascade} + assignee {integer references parties(party_id) on delete cascade} + state {text} + } $popuplate + set hkey_in_view "" + } + + ::xo::db::require index -table xowiki_form_instance_item_index -col item_id -unique true + ::xo::db::require index -table xowiki_form_instance_item_index -col parent_id,name -unique true + ::xo::db::require index -table xowiki_form_instance_item_index -col page_template + ::xo::db::require index -table xowiki_form_instance_item_index -col page_template,package_id + ::xo::db::require index -table xowiki_form_instance_item_index -col parent_id + ::xo::db::require index -table xowiki_form_instance_item_index -col parent_id,page_template + + # + # Define helper views in connection with the form_instance_item_index: + # + # - xowiki_form_instance_item_view + # - xowiki_form_instance_children + # - xowiki_form_instance_attributes + + # + # - xowiki_form_instance_item_view: + # + # A view similar to xowiki_form_pagei, but containing already + # often extra-joined attributes like parent_id. This view returns + # only the values of the live revisions, and uses the + # form_instance_item_index for quick lookup. Example query to + # obtain all attributes necessary for object creation based on + # package_id and page_template. + # + # select * from xowiki_form_instance_item_view + # where package_id = 18255683 + # and page_template = 20260757 + # and publish_status='ready'; + # + ::xo::db::require view xowiki_form_instance_item_view [subst { + SELECT + xi.package_id, xi.parent_id, xi.name, + $hkey_in_view xi.publish_status, xi.assignee, xi.state, xi.page_template, xi.item_id, + o.object_id, o.object_type, o.title AS object_title, o.context_id, + o.security_inherit_p, o.creation_user, o.creation_date, o.creation_ip, + o.last_modified, o.modifying_user, o.modifying_ip, + --o.tree_sortkey, o.max_child_sortkey, + cr.revision_id, cr.title, content_revision__get_content(cr.revision_id) AS text, + cr.description, cr.publish_date, cr.mime_type, cr.nls_language, + xowiki_form_page.xowiki_form_page_id, + xowiki_page_instance.page_instance_id, + xowiki_page_instance.instance_attributes, + xowiki_page.page_id, + xowiki_page.page_order, + xowiki_page.creator + FROM cr_text, + xowiki_form_instance_item_index xi + left join cr_items ci on (ci.item_id = xi.item_id) + left join cr_revisions cr on (cr.revision_id = ci.live_revision) + left join acs_objects o on (o.object_id = ci.live_revision) + left join xowiki_page on (o.object_id = xowiki_page.page_id) + left join xowiki_page_instance on (o.object_id = xowiki_page_instance.page_instance_id) + left join xowiki_form_page on (o.object_id = xowiki_form_page.xowiki_form_page_id) + }] + + # xowiki_form_instance_children: + # + # Return the root_item_id and all attributes of the + # form_instance_item_index of all child items under the tree based + # on parent_ids. Use a query like the following to count the + # children of an item having a certain page_template (e.g. + # find all the folders/links/... having the the specified item + # as parent): + # + # select count(*) from xowiki_form_instance_children + # where root_item_id = 18255779 + # and page_template = 20260757 + # and publish_status='ready'; + # + # Note: this query needs an oracle counter-part + + ::xo::db::require view xowiki_form_instance_children { + With RECURSIVE child_items AS ( + select item_id as root_item_id, * from xowiki_form_instance_item_index + UNION ALL + select child_items.root_item_id, xi.* from xowiki_form_instance_item_index xi, child_items + where xi.parent_id = child_items.item_id + ) + select * from child_items + } + + # xowiki_form_instance_attributes + # + # Return for a given item_id the full set of attributes like the + # one returned from xowiki_form_instance_item_view. The idea is to + # make it convenient to obtain from a query all attributes + # necessary for creating instances. The same view can be used to + # complete either values from the xowiki_form_instance_item_index + # + # select * from xowiki_form_instance_item_index xi + # left join xowiki_form_instance_attributes xa on xi.item_id = xa.item_id; + # + # or from xowiki_form_instance_children + # + # select * from xowiki_form_instance_children ch + # left join xowiki_form_instance_attributes xa on ch.item_id = xa.item_id; + # + # + ::xo::db::require view xowiki_form_instance_attributes { + SELECT + ci.item_id, + o.package_id, + o.object_id, o.object_type, o.title AS object_title, o.context_id, + o.security_inherit_p, o.creation_user, o.creation_date, o.creation_ip, + o.last_modified, o.modifying_user, o.modifying_ip, + --o.tree_sortkey, o.max_child_sortkey, + cr.revision_id, cr.title, content_revision__get_content(cr.revision_id) AS data, + cr_text.text_data AS text, cr.description, cr.publish_date, cr.mime_type, cr.nls_language, + xowiki_form_page.xowiki_form_page_id, + xowiki_page_instance.page_instance_id, + xowiki_page_instance.instance_attributes, + xowiki_page.page_id, + xowiki_page.page_order, + xowiki_page.creator + FROM cr_text, cr_items ci + left join cr_revisions cr on (cr.revision_id = ci.live_revision) + left join acs_objects o on (o.object_id = ci.live_revision) + left join xowiki_page on (o.object_id = xowiki_page.page_id) + left join xowiki_page_instance on (o.object_id = xowiki_page_instance.page_instance_id) + left join xowiki_form_page on (o.object_id = xowiki_form_page.xowiki_form_page_id) + } + + #ns_logctl severity Debug(sql) off + + ############################# # # A simple autoname handler # # The autoname handler has the purpose to generate new names based # on a stem and a parent_id. Typically this is used for the - # autonaming of FormPages. The goal is to generate "nice" names, + # auto-naming of FormPages. The goal is to generate "nice" names, # i.e. with rather small numbers. # # Instead of using the table below, another option would be to use @@ -218,6 +422,7 @@ count integer } ::xo::db::require index -table xowiki_autonames -col parent_id,name -unique true + ::xo::db::require index -table xowiki_autonames -col parent_id ::xotcl::Object create autoname autoname proc generate {-parent_id -name} { @@ -228,12 +433,12 @@ if {$already_recorded} { incr count - db_dml [my qn update_autoname_counter] \ + ::xo::dc dml update_autoname_counter \ "update xowiki_autonames set count = count + 1 \ where parent_id = :parent_id and name = :name" } else { set count 1 - db_dml [my qn insert_autoname_counter] \ + ::xo::dc dml insert_autoname_counter \ "insert into xowiki_autonames (parent_id, name, count) \ values (:parent_id, :name, $count)" } @@ -281,7 +486,7 @@ Page set recursion_count 0 Page array set RE { - include {{{([^<]+?)}}([&<\s]|$)} + include {{{([^<]+?)}}([^\}]|$)} anchor {\\\[\\\[([^\]]+?)\\\]\\\]} div {>>([^&<]*?)<<([ \n]*)?} clean {[\\](\{\{|>>|\[\[)} @@ -884,12 +1089,12 @@ -user_id:required tags } { - ::xo::dc dml [my qn delete_tags] \ + ::xo::dc dml delete_tags \ "delete from xowiki_tags where item_id = :item_id and user_id = :user_id" foreach tag [split $tags " ,;"] { if {$tag ne ""} { - ::xo::dc dml [my qn insert_tag] \ + ::xo::dc dml insert_tag \ "insert into xowiki_tags (item_id,package_id, user_id, tag, time) \ values (:item_id, :package_id, :user_id, :tag, now())" } @@ -1060,7 +1265,128 @@ return [my exists_property form_constraints] } + FormPage instproc hstore_attributes {} { + # Per default, we save all instance attributes in hstore, but a + # subclass/object might have different requirements. + return [my instance_attributes] + } + # + # Update helper for xowiki_form_instance_item_index (called from + # cr_procs, whenever a live-revision becomes updated). + # + FormPage ad_instproc update_item_index {} { + } { + my instvar name item_id package_id parent_id publish_status \ + page_template instance_attributes assignee state + + set rows [xo::dc dml update_xowiki_form_instance_item_index { + update xowiki_form_instance_item_index + set name = :name, package_id = :package_id, + parent_id = :parent_id, publish_status = :publish_status, + page_template = :page_template, assignee = :assignee, + state = :state + where item_id = :item_id + }] + if {$rows ne "" && $rows < 1} { + ::xo::dc dml insert_xowiki_form_instance_item_index { + insert into xowiki_form_instance_item_index ( + item_id, name, package_id, parent_id, publish_status, + page_template, assignee, state + ) values ( + :item_id, :name, :package_id, :parent_id, :publish_status, + :page_template, :assignee, :state + ) + } + } + if {[$package_id get_parameter use_hstore 0]} { + set hkey [::xowiki::hstore::dict_as_hkey [my hstore_attributes]] + xo::dc dml update_hstore "update xowiki_form_instance_item_index \ + set hkey = '$hkey' \ + where item_id = :item_id" + } + } + + ad_proc update_item_index { + -item_id:required + -package_id + -parent_id + -publish_status + -page_template + -instance_attributes + -assignee + -state + -hstore_attributes + } { + + Helper function to update single or multiple fields of the + xowiki_form_instance_item_index. Call this function only when + updating fields of the xowiki_form_instance_item_index in cases + where the standard API based on save and save_use canot be used. + + } { + foreach var { + package_id parent_id + publish_status page_template + instance_attributes assignee state + } { + if {[info exists $var]} { + xo::dc dml update_xowiki_form_instance_item_index_$var [subst { + update xowiki_form_instance_item_index + set $var = :$var + where item_id = :item_id + }] + } + } + if {[info exists hstore_attributes]} { + set hkey [::xowiki::hstore::dict_as_hkey $hstore_attributes] + xo::dc dml update_hstore "update xowiki_form_instance_item_index \ + set hkey = '$hkey' \ + where item_id = :item_id" + } + } + + # + # Define a specialized version of CrClass.fetch_object based + # on xowiki_form_instance_item_view + # + FormPage ad_proc fetch_object { + -item_id:required + {-revision_id 0} + -object:required + {-initialize true} + } { + Load a content item into the specified object. If revision_id is + provided, the specified revision is returned, otherwise the live + revision of the item_id. If the object does not exist, we create it. + + @return cr item object + } { + # + # We handle here just loading object instances via item_id, since + # only live_revisions are kept in xowiki_form_instance_item_index. + # The loading via revisions happens as before in CrClass. + # + if {$item_id == 0} { + return [next] + } + + if {![::xotcl::Object isobject $object]} { + # if the object does not yet exist, we have to create it + my create $object + } + + $object set item_id $item_id + $object db_1row [my qn fetch_from_view_item_id] { + select * from xowiki_form_instance_item_view where item_id = :item_id + } + + if {$initialize} {$object initialize_loaded_object} + return $object + } + + + # # helper for nls and lang # @@ -1198,6 +1524,25 @@ return 1 } + Page instproc can_link {item_id} { + # + # This is a stub which can / should be refined in applications, + # which want to disallow links to other pages, in the sense, that + # the links are not shown at all. A sample implementation might + # look like the follwing. + # + # if {$item_id ne 0} { + # set obj [::xo::db::CrClass get_instance_from_db -item_id $item_id] + # return [$obj can_be_linked] + # } + # + return 1 + } + + Page instproc can_be_linked {} { + return 1 + } + Page instproc can_save {} { # # Determine the parent object of the page to be saved. If the @@ -1561,7 +1906,7 @@ return [list name $name lang $lang normalized_name $normalized_name anchor $anchor query $query] } - Page instforward item_ref -verbose {%my package_id} %proc + Page instforward item_ref {%my package_id} %proc Page instproc pretty_link { {-anchor ""} @@ -1665,21 +2010,28 @@ -parent_id $parent_id \ $(link)] } - #my msg "link '$(link)' => [array get {}]" + #my log "link '$(link)' => [array get {}]" if {$label eq $arg} {set label $(link)} set item_name [string trimleft $(prefix):$(stripped_name) :] - + Link create [self]::link \ -page [self] -form $(form) \ -type $(link_type) [list -name $item_name] -lang $(prefix) \ [list -anchor $(anchor)] [list -query $(query)] \ [list -stripped_name $(stripped_name)] [list -label $label] \ -parent_id $(parent_id) -item_id $(item_id) -package_id $package_id - + + # in case, we can't link, flush the href + if {[my can_link $(item_id)] == 0} { + my references refused $(item_id) + [self]::link href "" + } + if {[catch {[self]::link configure {*}$options} errorMsg]} { ns_log error "$errorMsg\n$::errorInfo" - return "
$errorMsg
$errorMsg
$errorMsg