# Module of TWiki Enterprise Collaboration Platform, http://TWiki.org/ # # Copyright (C) 1999-2007 Peter Thoeny, peter@thoeny.org # and TWiki Contributors. All Rights Reserved. TWiki Contributors # are listed in the AUTHORS file in the root of this distribution. # NOTE: Please extend that file, not this notice. # # Additional copyrights apply to some or all of the code in this # file as follows: # Based on parts of Ward Cunninghams original Wiki and JosWiki. # Copyright (C) 1998 Markus Peter - SPiN GmbH (warpi@spin.de) # Some changes by Dave Harris (drh@bhresearch.co.uk) incorporated # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. For # more details read LICENSE in the root of this distribution. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # # As per the GPL, removal of this notice is prohibited. =pod ---+ package TWiki::UI::View UI delegate for view function =cut package TWiki::UI::View; use strict; use integer; use TWiki; use TWiki::User; use TWiki::UI; use TWiki::Time; =pod ---++ StaticMethod view( $session ) =view= command handler. This method is designed to be invoked via the =TWiki::UI::run= method. Generate a complete HTML page that represents the viewed topics. The view is controlled by CGI parameters as follows: | =rev= | topic revision to view | | =section= | restrict view to a named section | | =raw= | no format body text if set | | =skin= | comma-separated list of skin(s) to use | | =contenttype= | Allows you to specify an alternate content type | =cut sub view { my $session = shift; my $query = $session->{cgiQuery}; my $webName = $session->{webName}; my $topicName = $session->{topicName}; my $raw = $query->param( 'raw' ) || ''; my $contentType = $query->param( 'contenttype' ); my $showRev = 1; my $logEntry = ''; my $revdate = ''; my $revuser = ''; my $store = $session->{store}; # is this view indexable by search engines? Default yes. my $indexableView = 1; TWiki::UI::checkWebExists( $session, $webName, $topicName, 'view' ); my $skin = $session->getSkin(); my $rev = $store->cleanUpRevID( $query->param( 'rev' )); my $topicExists = $store->topicExists( $webName, $topicName ); # text and meta of the _latest_ rev of the topic my( $currText, $currMeta ); # text and meta of the chosen rev of the topic my( $meta, $text ); if( $topicExists ) { ( $currMeta, $currText ) = $store->readTopic ( $session->{user}, $webName, $topicName, undef ); TWiki::UI::checkAccess( $session, $webName, $topicName, 'view', $session->{user}, $currText ); ( $revdate, $revuser, $showRev ) = $currMeta->getRevisionInfo(); $revdate = TWiki::Time::formatTime( $revdate ); if ( !$rev || $rev > $showRev ) { $rev = $showRev; } if( $rev < $showRev ) { ( $meta, $text ) = $store->readTopic ( $session->{user}, $webName, $topicName, $rev ); ( $revdate, $revuser ) = $meta->getRevisionInfo(); $revdate = TWiki::Time::formatTime( $revdate ); $logEntry .= 'r'.$rev; } else { # viewing the most recent rev ( $text, $meta ) = ( $currText, $currMeta ); } # So we're reading an existing topic here. It is about time # to apply the 'section' selection (and maybe others in the # future as well). $text is cleared unless a named section # matching the 'section' URL parameter is found. if (my $section = $query->param('section')) { my ( $ntext, $sections ) = TWiki::parseSections( $text ); $text = ''; # in the beginning, there was ... NO section FINDSECTION: for my $s (@$sections) { if ($s->{type} eq 'section' && $s->{name} eq $section) { $text = substr( $ntext, $s->{start}, $s->{end}-$s->{start} ); last FINDSECTION; } } } } else { # Topic does not exist yet $indexableView = 0; $session->enterContext( 'new_topic' ); $rev = 1; if( TWiki::isValidTopicName( $topicName )) { ( $currMeta, $currText ) = TWiki::UI::readTemplateTopic( $session, 'WebTopicViewTemplate' ); } else { ( $currMeta, $currText ) = TWiki::UI::readTemplateTopic( $session, 'WebTopicNonWikiTemplate' ); } ( $text, $meta ) = ( $currText, $currMeta ); $logEntry .= ' (not exist)'; } if( $raw ) { $indexableView = 0; $logEntry .= ' raw='.$raw; if( $raw eq 'debug' || $raw eq 'all' ) { $text = $store->getDebugText( $meta, $text ); } } if( $TWiki::cfg{Log}{view} ) { $session->writeLog( 'view', $webName.'.'.$topicName, $logEntry ); } my( $mirrorSiteName, $mirrorViewURL, $mirrorLink, $mirrorNote ) = $session->readOnlyMirrorWeb( $webName ); # Note; must enter all contexts before the template is read, as # TMPL:P is expanded on the fly in the template reader. :-( my( $revTitle, $revArg ) = ( '', '' ); if( $mirrorSiteName ) { $session->enterContext( 'inactive' ); unless( $topicExists ) { $text = ''; } } elsif( $rev < $showRev ) { $session->enterContext( 'inactive' ); # disable edit of previous revisions $revTitle = '(r'.$rev.')'; $revArg = '&rev='.$rev; } my $template = $query->param( 'template' ) || $session->{prefs}->getPreferencesValue( 'VIEW_TEMPLATE' ) || 'view'; my $tmpl = $session->{templates}->readTemplate( $template, $skin ); if( !$tmpl && $template ne 'view' ) { $tmpl = $session->{templates}->readTemplate( 'view', $skin ); } if( !$tmpl ) { throw TWiki::OopsException( 'attention', def => 'no_such_template', web => $webName, topic => $topicName, params => [ $template, 'VIEW_TEMPLATE' ] ); } $tmpl =~ s/%REVINFO%/%REVINFO%$mirrorNote/go; $tmpl =~ s/%REVTITLE%/$revTitle/g; $tmpl =~ s/%REVARG%/$revArg/g; if( $indexableView && $TWiki::cfg{AntiSpam}{RobotsAreWelcome} && !$query->param() ) { # it's an indexable view type, there are no parameters # on the url, and robots are welcome. Remove the NOINDEX meta tag $tmpl =~ s/]*>//goi; } # Show revisions around the one being displayed # we start at $showRev then possibly jump near $rev if too distant my $revsToShow = $TWiki::cfg{NumberOfRevisions} + 1; $revsToShow = $showRev if $showRev < $revsToShow; my $doingRev = $showRev; my $revs = ''; while( $revsToShow > 0 ) { $revsToShow--; if( $doingRev == $rev) { $revs .= 'r'.$rev; } else { $revs .= CGI::a({ href=>$session->getScriptUrl( 0, 'view', $webName, $topicName, rev => $doingRev ), rel => 'nofollow' }, "r$doingRev" ); } if ( $doingRev - $rev >= $TWiki::cfg{NumberOfRevisions} ) { # we started too far away, need to jump closer to $rev use integer; $doingRev = $rev + $revsToShow / 2; $doingRev = $revsToShow if $revsToShow > $doingRev; $revs .= ' | '; next; } if( $revsToShow ) { $revs .= ' ' . CGI::a ( { href=>$session->getScriptUrl( 0, 'rdiff', $webName, $topicName, rev1 => $doingRev, rev2 => $doingRev-1 ), rel => 'nofollow' }, '<' ) . ' '; } $doingRev--; } my $ri = $session->{renderer}->renderRevisionInfo( $webName, $topicName, $meta ); $tmpl =~ s/%REVINFO%/$ri/go; $tmpl =~ s/%REVISIONS%/$revs/go; ## SMELL: This is also used in TWiki::_TOC. Could insert a tag in ## TOC and remove all those here, finding the parameters only once my @qparams = (); foreach my $name ( $query->param ) { next if ($name eq 'keywords'); next if ($name eq 'topic'); push @qparams, $name => $query->param($name); } $tmpl =~ s/%QUERYPARAMSTRING%/TWiki::_make_params(1,@qparams)/geo; # extract header and footer from the template, if there is a # %TEXT% tag marking the split point. The topic text is inserted # in place of the %TEXT% tag. The text before this tag is inserted # as header, the text after is inserted as footer. If there is a # %STARTTEXT% tag present, the header text between %STARTTEXT% and # %TEXT is rendered together, as is the footer text between %TEXT% # and %ENDTEXT%, if present. This allows correct handling of TWiki # markup in header or footer if those do require examination of the # topic text to work correctly (e.g., %TOC%). # Note: This feature is experimental and may be replaced by an # alternative solution not requiring additional tags. my( $start, $end ); if( $tmpl =~ m/^(.*)%TEXT%(.*)$/s ) { my @starts = split( /%STARTTEXT%/, $1 ); if ( $#starts > 0 ) { # we know that there is something before %STARTTEXT% $start = $starts[0]; $text = $starts[1] . $text; } else { $start = $1; } my @ends = split( /%ENDTEXT%/, $2 ); if ( $#ends > 0 ) { # we know that there is something after %ENDTEXT% $text .= $ends[0]; $end = $ends[1]; } else { $end = $2; } } else { my @starts = split( /%STARTTEXT%/, $tmpl ); if ( $#starts > 0 ) { # we know that there is something before %STARTTEXT% $start = $starts[0]; $text = $starts[1]; } else { $start = $tmpl; $text = ''; } $end = ''; } # If minimalist is set, images and anchors will be stripped from text my $minimalist = 0; if( $contentType ) { $minimalist = ( $skin =~ /\brss/ ); } elsif( $skin =~ /\brss/ ) { $contentType = 'text/xml'; $minimalist = 1; } elsif( $skin =~ /\bxml/ ) { $contentType = 'text/xml'; $minimalist = 1; } elsif( $raw eq 'text' || $raw eq 'all' ) { $contentType = 'text/plain'; } else { $contentType = 'text/html' } $session->{SESSION_TAGS}{MAXREV} = $showRev; $session->{SESSION_TAGS}{CURRREV} = $rev; # Set page generation mode to RSS if using an RSS skin $session->enterContext( 'rss' ) if $skin =~ /\brss/; # Set the meta-object that contains the rendering info # SMELL: hack to get around not having a proper topic object model $session->enterContext( 'can_render_meta', $meta ); my $page; # Legacy: If the _only_ skin is 'text' it is used like this: # http://.../view/Codev/MyTopic?skin=text&contenttype=text/plain&raw=on # which shows the topic as plain text; useful for those who want # to download plain text for the topic. So when the skin is 'text' # we do _not_ want to create a textarea. # raw=on&skin=text is deprecated; use raw=text instead. if( $raw eq 'text' || $raw eq 'all' || ( $raw && $skin eq 'text' )) { # use raw text $page = $text; } else { my @args = ( $session, $webName, $topicName, $meta, $minimalist ); $session->enterContext( 'header_text' ); $page = _prepare($start, @args); $session->leaveContext( 'header_text' ); if( $raw ) { if ($text) { my $p = $session->{prefs}; $page .= CGI::textarea( -readonly => 'readonly', -rows => $p->getPreferencesValue('EDITBOXHEIGHT'), -cols => $p->getPreferencesValue('EDITBOXWIDTH'), -style => $p->getPreferencesValue('EDITBOXSTYLE'), -default => $text ); } } else { $session->enterContext( 'body_text' ); $page .= _prepare($text, @args); $session->leaveContext( 'body_text' ); } $session->enterContext( 'footer_text' ); $page .= _prepare($end, @args); $session->leaveContext( 'footer_text' ); } # Output has to be done in one go, because if we generate the header and # then redirect because of some later constraint, some browsers fall over $session->writeCompletePage( $page, 'view', $contentType ) } sub _prepare { my( $text, $session, $webName, $topicName, $meta, $minimalist) = @_; $text = $session->handleCommonTags( $text, $webName, $topicName ); $text = $session->{renderer}->getRenderedVersion( $text, $webName, $topicName ); $text =~ s/( ?) *<\/?(nop|noautolink)\/?>\n?/$1/gois; if( $minimalist ) { $text =~ s/]*>//gi; # remove image tags $text =~ s/]*>//gi; # remove anchor tags $text =~ s/<\/a>//gi; # remove anchor tags } return $text; } =pod ---++ StaticMethod viewfile( $session, $web, $topic, $query ) =viewfile= command handler. This method is designed to be invoked via the =TWiki::UI::run= method. Command handler for viewfile. View a file in the browser. Some parameters are passed in CGI query: | =filename= | Attachment to view | | =rev= | Revision to view | =cut sub viewfile { my $session = shift; my $query = $session->{cgiQuery}; my $webName = $session->{webName}; my $topic = $session->{topicName}; my $fileName = $query->param( 'filename' ); $fileName = TWiki::Sandbox::sanitizeAttachmentName( $fileName ); my $rev = $session->{store}->cleanUpRevID( $query->param( 'rev' ) ); unless( $fileName && $session->{store}->attachmentExists( $webName, $topic, $fileName )) { throw TWiki::OopsException( 'attention', def => 'no_such_attachment', web => $webName, topic => $topic, params => [ 'viewfile', $fileName||'?' ] ); } my $fileContent = $session->{store}->readAttachment( $session->{user}, $webName, $topic, $fileName, $rev ); my $type = _suffixToMimeType( $session, $fileName ); my $length = length( $fileContent ); my $dispo = 'inline;filename='.$fileName; print <