=head1 NAME SuikaWiki::Implementation - SuikaWiki: WikiEngine Core =head1 DESCRIPTION This module implements core part of the SuikaWiki WikiEngine. All implemented features of WikiEngine can be called directly or indirectly from instance of this module (with some exception such as functions provided by WikiPlugin modules). This module is part of SuikaWiki. =head1 SYNOPSIS require SuikaWiki::Implementation; my $WIKI = new SuikaWiki::Implementation; ... $WIKI->exit; C might be a good example for instanciating WikiEngine. =cut package SuikaWiki::Implementation; use strict; our $VERSION = do{my @r=(q$Revision: 1.8 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r}; =head1 METHODS =over 4 =item $wiki = SuikaWiki::Implementation->new () Constructs new instance of wiki implementation =cut sub new ($;%) { my $self = bless { driver_name => 'WikiImplementation', driver_version => '0.0', driver_uri_reference => q, engine_name => 'SuikaWiki', engine_version => '2.9.2', engine_uri_reference => q, }, shift; $self; } =item $wiki->init_variables Initialize per-access variables. This method should be called before other init_* methods are to be called. =cut sub init_variables ($) { my $self = shift; $self->close_input; $self->{var} = {}; $self->__raise_event (name => 'setting_initial_variables'); } =item $wiki->init_plugin Prepares to use wiki plugins =cut sub init_plugin ($) { my $self = shift; require SuikaWiki::Plugin; $self->{plugin} = SuikaWiki::Plugin->new (wiki => $self); $self->__raise_event (name => 'plugin_manager_loaded'); } =item $wiki->init_view Prepares to use wikiview =cut sub init_view ($) { my $self = shift; require SuikaWiki::View::Implementation; $self->{view} = SuikaWiki::View::Implementation->new (wiki => $self); $self->__raise_event (name => 'view_implementation_loaded'); } =item $wiki->init_db Prepares to use wiki database =cut sub init_db ($) { my $self = shift; return if ref $self->{db}; ## Already initialized $self->{config}->{lock} = {-directory => $self->{config}->{path_to}->{db__lock__dir}, -retry => 20, -error_handler => sub { my ($self, %o) = @_; if ($self->{config}->{path_to}->{db__content__error_log}) { open LOG, '>>', $self->{config}->{path_to} ->{db__content__error_log}; print LOG scalar (gmtime), "\@@{[time]} @{[$$]} {$o{level}}: LOCK: ", $o{msg}, "\n"; close LOG; } if ($o{level} eq 'fatal') { die $o{msg}; } }, }; $self->{var}->{db}->{lock_prop} = sub { my $prop = shift; my %lock = %{$self->{config}->{lock}}; $lock{-name} = $prop; $lock{-share} = defined $self->{var}->{db}->{read_only}->{$prop} ? $self->{var}->{db}->{read_only}->{$prop} : $self->{var}->{db}->{read_only}->{'#default'}; \%lock; }; require SuikaWiki::DB::Logical; $self->{db} = new SuikaWiki::DB::Logical; $self->__raise_event (name => 'database_loaded'); } =item $wiki->view_in_mode (%opt) Doing main process in accordance to the mode. Actually, this method only raises an event of 'view_in_mode'. So that "doing main process" code should be registered as an event procedure of 'view_in_mode'. =cut sub view_in_mode ($%) { my ($self, %opt) = @_; $self->__raise_event (name => 'view_in_mode', argv => [\%opt]); } # obsolete sub __raise_event ($%) { my ($self, %o) = @_; for (@{$self->{event}->{$o{name}}||[]}) { &{$_} ($self, @{$o{argv}||[]}); ## TODO: canceling } 1; } sub ___raise_event ($$$;%) { my ($self, $name, $argv, %opt) = @_; my $event = {cancel => 0, name => $name, ($opt{argv_name}||$name) => $argv}; for (@{$self->{event}->{$name}}) { $_->($self, $event); return 0 if $event->{cancel}; } return 1; } =item $uri = $wiki->uri_reference (%option) Returning URI reference that refers the wiki or a WikiPage. Load {input} before calling this method or specify appropriate C option to get proper result. One or two URI reference(s) is returned as C object. See C option. Available options: =over 4 =item anchor_no => positive-integer (default: none) Numeral anchor index. With this option, C option is ignored. =item base => URI reference (default: none) Base URI reference. C is returned when C is specified. Otherwise, C<(absolute, absolute)> is returned. =item fragment => URI reference fragment (default: none) URI refernece fragment. This option value MUST be encoded by URI escape encoding. =item mode => mode-name (default: "default") WikiView mode in which referred. =item page => [WikiName] (default: none) WikiName to that WikiPage URI reference is referring. =item param => {name1 => value1, name2 => value2,...} (default: none) Additional query parameters. Names and values are automatically encoded by URI escape encoding if necessary. =item up_to_date => 1/0 (default: 0) "Up-to-date" URI query parameter for cheating cache. =item wiki_uri => URI reference (default: auto) A base URI reference referring the wiki itself. =item with_lm => 1/0 (default: 0) "Last modified" URI query parameter for chating WWW browser history. =back =cut sub uri_reference ($;%) { my ($self, %opt) = @_; ## Note: $opt{wiki_uri} must be a URI(.pm) if any. my $uri = $opt{wiki_uri} || $self->___get_wiki_uri; ## SuikaWiki 3.0 format my $query_param = qr/[^0-9A-Za-z_.-]/; my @param = map {my $n = $_; $n =~ tr/_/-/; $self->___uri_escape_encode ($n, $query_param).'='. $self->___uri_escape_encode ($opt{param}->{$_}, $query_param)} keys %{$opt{param}}; push @param, 'mode='.$self->___uri_escape_encode ($opt{mode}, $query_param) if $opt{mode}; push @param, 'x-d='.time if $opt{up_to_date}; if ($opt{page}) { if ($opt{with_lm} and ref $self->{db}) { push @param, 'x-lm=' . $self->___uri_escape_encode ($self->{db}->get (lastmodified => $opt{page}), $query_param); } ## TODO: Common WikiName interface my $page = join '//', @{$opt{page}}; if (@param) { ## TODO: Encode by $wiki->{config}->{charset}->{uri_param_encode} unshift @param, 'mypage='.$self->___uri_escape_encode ($page, $query_param); push @param, '_charset_='.$self->{config}->{charset}->{uri_param_encode}; ## TODO: downgrade to & $uri->query (join ';', @param); } else { ## TODO: Encode by $wiki->{config}->{charset}->{uri_query_encode} $uri->query ($self->___uri_escape_encode ($page, $query_param)); } } elsif (@param) { push @param, '_charset_='.$self->{config}->{charset}->{uri_param_encode}; $uri->query (join ';', @param); } if ($opt{anchor_no}) { $uri->fragment ('anchor-'.$opt{anchor_no}); } elsif ($opt{fragment}) { $uri->fragment ($opt{fragment}); } if (defined $opt{base}) { $opt{base} = $self->{input}->request_uri if ref $self->{input} and not ref $opt{base} and $opt{base} eq '1'; return wantarray ? ($uri->rel ($opt{base}), $uri) : $uri->rel ($opt{base}); } else { return ($uri, $uri); } } =item 1/0 = $wiki->uri_is_part_of_wiki ($uri-reference) Check whether given URI reference is "part of" the wiki. =cut sub uri_is_part_of_wiki ($$) { my ($self, $uri) = @_; my $wiki_uri = ''.$self->___get_wiki_uri; $uri = URI->new (substr ($uri, 0, length ($wiki_uri))); $uri eq $wiki_uri ? 1 : 0; } sub ___get_wiki_uri ($) { my ($self) = shift; my $uri; if (ref $self->{___uri}) { $uri = $self->{___uri}->clone; } elsif (ref $self->{input}) { $uri = $self->{input}->request_uri (no_path_info => 1, no_query => 1); $self->{___uri} = $uri->clone; } else { $uri = URI->new; } $uri; } sub ___uri_escape_encode ($$;$) { my ($self, $s, $char) = @_; $char ||= qr([^0-9A-Za-z_.!~*'();/?:\@&=+\$,-]); ## TODO: # require Encode; # $s = Encode::decode ('utf8', $s); $s =~ s/($char)/sprintf '%%%02X', ord $1/ge; $s; } =item $wiki->close_db Closing WikiDB (C<$wiki->{db}>). Although this method is automatically called by C<< $wiki->exit >>, it is good practice to explicitly close something opened explicitly. =cut sub close_db ($) { my $self = shift; $self->{db}->close if ref $self->{db}; delete $self->{db}; } =item $wiki->close_view Closing WikiView manager (C<< $wiki->close_view >>). =cut sub close_view ($) { my $self = shift; $self->{view}->exit if ref $self->{view}; delete $self->{view}; } =item $wiki->close_plugin Closing WikiPlugin manager (C<< $wiki->{plugin} >>). Note that this method does not unload WikiPlugin modules. (They are "merged" to script namespace so that unloading them is almost impossible.) =cut sub close_plugin ($) { my $self = shift; $self->{plugin}->exit if ref $self->{plugin}; delete $self->{plugin}; } =item $wiki->close_input Closing input manager (C<< $wiki->{input} >>). =cut sub close_input ($) { my $self = shift; $self->{input}->exit if ref $self->{input}; delete $self->{input}; } =item 1/0 = $wiki->exit Exitign the wiki. This method closes input manager, WikiDB manager, WikiView manager and WikiPlugin manager after C event is raised. Note that C event handler can "cancel" exiting, it makes this method return C<0>. This method is automatically called before C<$wiki> is destoroyed. =cut sub exit ($) { my $self = shift; return 0 unless $self->___raise_event (name => 'close'); $self->close_input; $self->close_db; $self->close_view; $self->close_plugin; $self->{exited} = 1; 1; } ## TODO: Provides "cancelable" to close event. sub DESTROY ($) { my $self = shift; $self->exit unless $self->{exited}; } =back =head1 PUBLIC PROPERTIES =over 4 =item $wiki->{config} Persistent wiki configureation parameters (that is not changed with the situation when is who accessing in what way) =over 4 =item ->{charset}->{internal} = Character encoding scheme used in wiki implementation =item ->{charset}->{output} = Default character encoding scheme used to output content =item ->{debug}->{$category} = 1/0 (Default 0) Debug mode Categories: =over 4 =item db WikiDatabase related features =item general Generic. =item view WikiView related. =back =item ->{entity}->{expires}->{$rulename} = {delta => $seconds} How long outputed entity will be fresh. =item ->{lock} Default (prototype) properties to give SuikaWiki::DB::Util::Lock =item ->{page}->{ $name } WikiPage which has feature of $name =item ->{path_to}->{ $name } Filesystem path (or path fragment) to $name =back =item $wiki->{db} Wiki main database =item $wiki->{driver_name} Product name of the WikiDriver. For interoperability, only alphanumeric characters and limited symbols (those allowed in RFC 2616 token) should be used as parts of product name. =item $wiki->{driver_version} WikiDriver version in string. For interoperability, only alphanumeric characters and limited symbols (those allowed in RFC 2616 token) should be used as parts of product name. =item $wiki->{engine_name} (Read only) SuikaWiki WikiEngine name =item $wiki->{engine_version} (Read only) SuikaWiki WikiEngine version =item @{$wiki->{event}->{ $event_name }} Event handling procedures Standarized event names: =over 4 =item database_loaded When WikiDatabase manager is loaded. This event handler is typically used to set database property module for SuikaWiki::DB::Logical. =item plugin_manager_loaded When WikiPlugin manager is loaded. Note that plugins themselves are not loaded yet. =item setting_initial_variables On the process to set per-access variables. This event is raised before other core modules such as WikiDatabase or WikiPlugin are loaded. =item view_error Something wrong with or something useful message is available from WikiView manager. =item view_in_mode C method is called. =back =item $wiki->{var} Non-persistent wiki variable options (that might vary with context such as caller's argument values) =over 4 =item ->{client}->{downgrade}->{ $feature } = $parameter Whether downgrade is required. See C plugin module. =item ->{client}->{used_for_negotiation} = [s] HTTP (request) header field names used to select variable content. This value will be used to generate HTTP Vary header field. =item ->{client}->{user_agent_name} = User agent name provided by such ways as User-Agent field (in HTTP) or HTTP_USER_AGENT meta variable (in HTTP-CGI). =item ->{db}->{lock_prop} = sub ($prop) Function returning hash reference of lock options (that will be passed to SuikaWiki::DB::Util::Lock->new). $prop, an argument to the function, is a database property name. =item ->{db}->{read_only}->{ $prop } = 1/0 Whether the database property named as $prop is opened in read only mode or not. Special property name of '#default' is used to set the default value referred when {read_only}->{$prop} is not specified explicily. Note that this value must be set before the instance of database property is loaded. =item ->{error} = [{description => Error 1}, {description => Error 2},...] Trapped errors. =item ->{input} Instance of input parameter interface (such as SuikaWiki::Input::HTTP) =item ->{mode} = mode name Wiki mode name =item ->{page} = [page] WikiPage being referred =back =item $wiki->{view} WikiView implementation (an instance of SuikaWiki::View::Implementation) =cut =head1 LICENSE Copyright 2003-2004 Wakaba . All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut 1; # $Date: 2004/02/01 12:24:05 $