- Publicity: Public Only All
import-procs.tcl
XoWiki - importer
- Location:
- packages/xowiki/tcl/import-procs.tcl
- Created:
- 2008-04-25
- Author:
- Gustaf Neumann
- CVS Identification:
$Id: import-procs.tcl,v 1.45 2024/09/11 06:15:56 gustafn Exp $
Procedures in this file
Detailed information
[ hide source ] | [ make this the default ]Content File Source
::xo::library doc { XoWiki - importer @creation-date 2008-04-25 @author Gustaf Neumann @cvs-id $Id: import-procs.tcl,v 1.45 2024/09/11 06:15:56 gustafn Exp $ } namespace eval ::xowiki { Class create Importer -parameter { {added 0} {replaced 0} {updated 0} {inherited 0} {package_id} {parent_id} {user_id} } Importer instproc init {} { set :log "" :destroy_on_cleanup } Importer instproc report_lines {} { util_user_message -message "[_ xowiki.Import_successful]" return "<table><caption>Details</caption>${:log}</table>" } Importer instproc report_line {obj operation} { set href [$obj pretty_link] set name [[$obj package_id] external_name -parent_id [$obj parent_id] [$obj name]] switch -- $operation { "added" { set operation [_ xowiki.added] } "replaced" { set operation [_ xowiki.replaced] } "updated" { set operation [_ xowiki.updated] } "inherited" { set operation [_ xowiki.inherited] } } append :log "<tr><td>$operation</td><td><a href='[ns_quotehtml $href]'>$name</a></td></tr>\n" } Importer instproc report {} { return "<b>${:added}</b> #xowiki.objects_newly_inserted#,\ <b>${:updated}</b> #xowiki.objects_updated#, <b>${:replaced}</b> #xowiki.objects_replaced#, <b>${:inherited}</b> #xowiki.inherited_update_ignored#<p>\ [:report_lines]" } Importer instproc import {-object:object,required -replace:boolean -create_user_ids} { # # Import a single object. In essence, this method demarshalls a # single object and inserts it (or updates it) in the database. It # takes as well care about categories. # $object demarshall -parent_id [$object parent_id] -package_id ${:package_id} \ -creation_user ${:user_id} -create_user_ids $create_user_ids set item_id [::xo::db::CrClass lookup -name [$object name] -parent_id [$object parent_id]] #:msg "lookup of [$object name] parent [$object parent_id] => $item_id" if {$item_id != 0} { if {$replace} { ;# we delete the original ::xo::db::CrClass delete -item_id $item_id set item_id 0 :report_line $object replaced incr :replaced } else { #:msg "$item_id update: [$object name]" ::xo::db::CrClass get_instance_from_db -item_id $item_id set item ::$item_id $item copy_content_vars -from_object $object $item save -use_given_publish_date [$item exists publish_date] \ -modifying_user [$object set modifying_user] #:log "$item_id saved" $object set item_id [$item item_id] #:msg "$item_id updated: [$object name]" :report_line $item_id updated incr :updated } } if {$item_id == 0} { $object save_new \ -use_given_publish_date [$object exists publish_date] \ -creation_user [$object set modifying_user] set item $object #:msg "$object added: [$object name]" :report_line $object added incr :added } # # The method demarshall might set the mapped __category_ids in $object. # Insert these into the category object map # if {[$object exists __category_ids]} { #:msg "$item_id map_categories [object set __category_ids] // [$item item_id]" $item map_categories [$object set __category_ids] } ${:package_id} flush_references -item_id [$object item_id] -name [$object name] } Importer instproc import_all {-replace -objects:required {-create_user_ids 0} {-keep_inherited 1}} { # # Import a series of objects. This method takes care especially # about dependencies of objects, which is reflected by the order # of object-imports. # # # Extract information from objects to be imported, that might be # changed later in the objects. # foreach o $objects { # # Remember old item_ids and old_names for pages with # item_ids. Only these can have parents (page_templates) or # child_objects # if {[$o exists item_id]} { set item_ids([$o item_id]) $o set old_names([$o item_id]) [$o name] } { $o item_id "" } # Remember old parent_ids for name-mapping, names are # significant per parent_id. if {[$o exists parent_id]} { set parent_ids([$o item_id]) [$o parent_id] } { $o parent_id "" } set todo($o) 1 # # Handle import of categories in the first pass # if {[$o exists __map_command]} { $o package_id ${:package_id} $o eval [$o set __map_command] } # FIXME remove? #if {[$o exists __category_map]} { # array set ::__category_map [$o set __category_map] #} } #:msg "item_ids=[array names item_ids], parent_ids=[array names parent_ids]" # # Make a fix-point iteration during import. Do only import, when # all pre-requirement pages are already loaded. # while {[array size todo] > 0} { set new 0 foreach o [array names todo] { #:msg "work on $o [$o info class] [$o name]" set old_name [$o name] set old_item_id [$o item_id] set old_parent_id [$o parent_id] # page instances have references to page templates, add the templates first if {[$o istype ::xowiki::PageInstance]} { set old_template_id [$o page_template] if {![info exists old_names($old_template_id)]} { set new 0 :msg "need name for $old_template_id. Maybe item_ids for PageTemplate missing?" break } set template_name_key $parent_ids($old_template_id)-$old_names($old_template_id) if {![info exists name_map($template_name_key)]} { #:msg "... delay import of $o (no object with name $template_name_key) imported" continue } #:msg "we found entry for name_map($template_name_key) = $name_map($template_name_key)" } if {[info exists item_ids($old_parent_id)]} { # we have a child object if {![info exists id_map($old_parent_id)]} { #:msg "... delay import of $o (map of parent_id $old_parent_id missing)" continue } } set need_to_import 1 # # If the page was implicitly added (due to being a # page_template of an exported page), and a page (e.g. a form # or a workflow) with the same name can be found in the # target, don't materialize the inherited page. # if {$keep_inherited && [$o exists __export_reason] && [$o set __export_reason] eq "implicit_page_template"} { $o unset __export_reason set page [::${:package_id} get_page_from_item_ref \ -allow_cross_package_item_refs false \ -use_package_path true \ -use_site_wide_pages true \ -use_prototype_pages false \ [$o name] \ ] # If we would like to restrict to just inherited pages in # the target, we could extend the test below with a test like # the following: # set inherited [expr {[$page physical_parent_id] ne [$page parent_id]}] if {$page ne ""} { #:msg "page [$o name] can ne found in folder ${:parent_id}" incr :inherited unset todo($o) set o $page set need_to_import 0 } } if {$need_to_import} { # Now, all requirements are met, parent-object and # child-object conditions are fulfilled. We have to map # page_template for PageInstances and parent_ids for child # objects to new IDs. # if {[$o istype ::xowiki::PageInstance]} { #:msg "importing [$o name] page_instance, map $template_name_key to $name_map($template_name_key)" $o page_template $name_map($template_name_key) #:msg "exists template? [nsf::is object [$o page_template]]" if {![nsf::is object [$o page_template]]} { ::xo::db::CrClass get_instance_from_db -item_id [$o page_template] #:msg "[nsf::is object [$o page_template]] loaded" } } if {[info exists item_ids($old_parent_id)]} { $o set parent_id $id_map($old_parent_id) } else { $o set parent_id ${:parent_id} } # Everything is mapped, we can now do the import. #:msg "start import for $o, name=[$o name]" :import \ -object $o \ -replace $replace \ -create_user_ids $create_user_ids #:msg "import for $o done, name=[$o name]" unset todo($o) } # # Maintain the maps and iterate # if {$old_item_id ne ""} { set id_map($old_item_id) [$o item_id] } set name_map($old_parent_id-$old_name) [$o item_id] #:msg "setting name_map($old_parent_id-$old_name)=$name_map($old_parent_id-$old_name), o=$o, old_item_id=$old_item_id" set new 1 } if {$new == 0} { :msg "could not import [array names todo]" break } } #:msg "final name_map=[array get name_map], id_map=[array get id_map]" # # final cleanup # foreach o $objects {if {[nsf::is object $o]} {$o destroy}} ${:package_id} flush_page_fragment_cache } # # A small helper for exporting objects # Object create exporter exporter proc include_needed_objects {item_ids} { # # Load the objects # foreach item_id $item_ids { if {[::xo::db::CrClass get_instance_from_db -item_id $item_id] eq ""} { :log "Warning: cannot fetch item $item_id for exporting" } else { set items($item_id) 1 } } # # In a second step, include the objects which should be exported implicitly # while {1} { set new 0 if {[array size items] > 0} { ns_log notice "--export works on [array size items] items: [array names items]" } foreach item_id [array names items] { # # We flag the reason, why the implicitly included elements were # included. If the target can resolve already such items # (e.g. forms), we might not have to materialize these finally. # # For PageInstances (or its subtypes), include the parent-objects as well # if {[::$item_id istype ::xowiki::PageInstance]} { set template_id [::$item_id page_template] if {![info exists items($template_id)]} { ns_log notice "--export including template-object $template_id of item $item_id has name? [::$template_id exists name]" ns_log notice "--export including template-object $template_id [::$template_id name]" set items($template_id) 1 ::xo::db::CrClass get_instance_from_db -item_id $template_id set new 1 ::$template_id set __export_reason implicit_page_template continue } } # # check for child objects of the item # set sql [::xowiki::Page instance_select_query -folder_id $item_id -with_subtypes true] ::xo::dc foreach export_child_obj $sql { if {![info exists items($item_id)]} { ns_log notice "--export child $item_id not included, try to fetch" ::xo::db::CrClass get_instance_from_db -item_id $item_id ns_log notice "--export including child $item_id [::$item_id name]" set items($item_id) 1 set new 1 ::$item_id set __export_reason implicit_child_page } } } if {!$new} { break } } return [array names items] } exporter proc marshall_all {{-mode export} item_ids} { set content "" foreach item_id $item_ids { ad_try { set obj [::$item_id marshall -mode $mode] } on error {errorMsg} { ns_log error "Error while exporting $item_id [::$item_id name]\n$errorMsg\n$::errorInfo" error $errorMsg } append content $obj\n } return $content } exporter proc marshall_all_to_file { {-mode export} {-cleanup:boolean false} -filename:required item_ids } { # # This method is similar to "marshall_all", but exports the objects # directly to a file. This can save memory when exporting a large # collection of objects, since the plain "marshall_all" appends to # a string, which can get very large, especially due to Tcl's # "double the size when space is needed" policy during "append" # operations. # set output_file [open $filename w] foreach item_id $item_ids { ad_try { puts $output_file [::$item_id marshall -mode $mode] if {$cleanup && [info exists ::xo::cleanup(::$item_id)]} { {*}$::xo::cleanup(::$item_id) } } on error {errorMsg} { ns_log error "Error while exporting $item_id [::$item_id name]\n$errorMsg\n$::errorInfo" error $errorMsg } finally { close $output_file } } } exporter proc export {item_ids} { # # include implicitly needed objects, instantiate the objects. # set item_ids [:include_needed_objects $item_ids] # # stream the objects via ns_write # ns_set put [ns_conn outputheaders] "Content-Type" "text/plain" ns_set put [ns_conn outputheaders] "Content-Disposition" "attachment;filename=export.xotcl" ad_return_top_of_page "" foreach item_id $item_ids { ns_log notice "--exporting $item_id [::$item_id name]" set pretty_link [expr {[::$item_id package_id] ne "" ? [::$item_id pretty_link] : "(not visible)"}] ns_write "# exporting $item_id [::$item_id name] $pretty_link\n" ad_try { set obj [::$item_id marshall] } on error {errorMsg} { ns_log error "Error while exporting $item_id [::$item_id name]\n$errorMsg\n$::errorInfo" } finally { ns_write "$obj\n" } } } # # Simple archive file manager # # The Archive manages supports importing .zip files and .tar.gz # files as ::xowiki::File into xowiki folders. # ::xotcl::Class create ArchiveFile -parameter { file name parent_id {use_photo_form false} } ArchiveFile instproc init {} { :destroy_on_cleanup ::xo::db::CrClass get_instance_from_db -item_id ${:parent_id} set :tmpdir [ad_mktmpdir import] } ArchiveFile instproc delete {} { file delete -force -- ${:tmpdir} next } ArchiveFile instproc unpack {} { set success 0 switch [::xowiki::guesstype ${:name}] { application/zip - application/x-zip - application/x-zip-compressed { set success [util::file_content_check -type zip -file ${:file}] if {!$success} { util_user_message -message "The uploaded file is apparently not a zip file." } else { ::util::unzip -source ${:file} -destination ${:tmpdir} :import -dir ${:tmpdir} -parent_id ${:parent_id} } } application/x-compressed { if {[string match "*tar.gz" ${:name}]} { set success [util::file_content_check -type gzip -file ${:file}] if {!$success} { util_user_message -message "The uploaded file is apparently not a gzip file." } else { set cmd [::util::which tar] exec $cmd -xzf ${:file} -C ${:tmpdir} :import -dir ${:tmpdir} -parent_id ${:parent_id} } } else { util_user_message -message "Unknown compressed file type ${:name}." } } default { util_user_message -message "Type '[::xowiki::guesstype ${:name}]' is not an supported archive format." } } #:msg success=$success return $success } ArchiveFile instproc import {-dir -parent_id} { set package_id [::$parent_id package_id] foreach tmpfile [glob -nocomplain -directory $dir *] { #:msg "work on $tmpfile [::file isdirectory $tmpfile]" set file_name [::file tail $tmpfile] if {[::file isdirectory $tmpfile]} { # ignore mac os x resource fork directories if {[string match "*__MACOSX" $tmpfile]} continue set folder_object [::$package_id get_page_from_name -assume_folder true \ -name $file_name -parent_id $parent_id] if {$folder_object ne ""} { # if the folder exists already, we have nothing to do } else { # we create a new folder ... set folder_form_id [::$package_id instantiate_forms -forms en:folder.form] set folder_object [FormPage new \ -title $file_name \ -name $file_name \ -package_id $package_id \ -parent_id $parent_id \ -nls_language en_US \ -instance_attributes {} \ -page_template $folder_form_id \ -destroy_on_cleanup ] $folder_object save_new # ..... and refetch it under its canonical name ::xo::db::CrClass get_instance_from_db -item_id [$folder_object item_id] } :import -dir $tmpfile -parent_id [$folder_object item_id] } else { set mime_type [::xowiki::guesstype $file_name] if {[string match "image/*" $mime_type] && [:use_photo_form]} { set photo_object [::$package_id get_page_from_name -name en:$file_name -parent_id $parent_id] if {$photo_object ne ""} { # photo entry exists already, create a new revision :log "Photo $file_name exists already" $photo_object set title $file_name set f [::xowiki::formfield::file new -object $photo_object -name "image" -destroy_on_cleanup] $f set value $file_name $f content-type $mime_type $f set tmpfile $tmpfile $f convert_to_internal $photo_object save } else { # create a new photo entry :log "new Photo $file_name" set photoFormObj [::$package_id instantiate_forms \ -parent_id $parent_id -forms en:photo.form] set photo_object [$photoFormObj create_form_page_instance \ -name en:$file_name \ -nls_language en_US \ -creation_user [::xo::cc user_id] \ -parent_id $parent_id \ -package_id $package_id \ -instance_attributes [list image [list name $file_name]]] $photo_object title $file_name $photo_object publish_status "ready" $photo_object save_new ;# to obtain item_id needed by the form-field set f [::xowiki::formfield::file new -object $photo_object -name "image" -destroy_on_cleanup] $f set value $file_name $f content-type $mime_type $f set tmpfile $tmpfile $f convert_to_internal #:log "after convert to internal $file_name" } } else { set file_object [::$package_id get_page_from_name -name file:$file_name -parent_id $parent_id] if {$file_object ne ""} { :msg "file $file_name exists already" # file entry exists already, create a new revision $file_object set import_file $tmpfile $file_object set mime_type $mime_type $file_object set title $file_name $file_object save } else { :msg "file $file_name created new" set file_object [::xowiki::File new \ -title $file_name \ -name file:$file_name \ -parent_id $parent_id \ -mime_type $mime_type \ -package_id $package_id \ -creation_user [::xo::cc user_id] \ -destroy_on_cleanup ] $file_object set import_file $tmpfile $file_object save_new } } } } } } ::xo::library source_dependent # # Local variables: # mode: tcl # tcl-indent-level: 2 # indent-tabs-mode: nil # End: