From aa07ddce2bace3c84c2d9dfe63b629b6d0f62770 Mon Sep 17 00:00:00 2001 From: Vadim Belman Date: Tue, 12 Apr 2016 20:37:38 -0400 Subject: [PATCH] Item13897: First HTML page generated. Most of the core still doesn't function properly but it's kinda milestone as the new Foswiki::App finally does something meaningful. - expandStandardEscapes() is now a object method of Foswiki::Macros. Moved from Foswiki.pm - Foswiki::If::OP_*, Foswiki::Render::* modules are adapted to the new model. - A number of macros have been adapted. Only those involved into generation of Main.WebHome. - Attributes attach, renderer, and zones are moved into Foswiki::App from Foswiki.pm. - Methods deepWebList(), inlineAlert(), setCacheControl(), writeCompletePage() have been moved from Foswiki.pm - Attribute urlHost and method getScriptUrl() have been moved into Foswiki::Config. Same is planned for other getXxxUrl() methods. - Foswiki.pm can now export urlEncode, urlDecode, make_params, load_package. It is planned to have all Foswiki.pm API exported for the core and plugins code. --- core/lib/Foswiki.pm | 565 +----------------- core/lib/Foswiki/App.pm | 359 ++++++++++- core/lib/Foswiki/Attach.pm | 28 +- core/lib/Foswiki/Config.pm | 169 +++++- core/lib/Foswiki/Form.pm | 5 +- core/lib/Foswiki/Form/FieldDefinition.pm | 4 +- core/lib/Foswiki/Func.pm | 4 +- core/lib/Foswiki/If/OP_allows.pm | 36 +- core/lib/Foswiki/If/OP_context.pm | 16 +- core/lib/Foswiki/If/OP_defined.pm | 18 +- core/lib/Foswiki/If/OP_dollar.pm | 18 +- core/lib/Foswiki/If/OP_ingroup.pm | 12 +- core/lib/Foswiki/If/OP_isempty.pm | 16 +- core/lib/Foswiki/If/OP_istopic.pm | 18 +- core/lib/Foswiki/If/OP_isweb.pm | 14 +- core/lib/Foswiki/Macros.pm | 96 ++- core/lib/Foswiki/Macros/ADDTOZONE.pm | 2 +- .../lib/Foswiki/Macros/DISPLAYDEPENDENCIES.pm | 2 +- core/lib/Foswiki/Macros/ENCODE.pm | 6 +- core/lib/Foswiki/Macros/EXPAND.pm | 2 +- core/lib/Foswiki/Macros/FORMAT.pm | 5 +- core/lib/Foswiki/Macros/FORMFIELD.pm | 2 +- core/lib/Foswiki/Macros/GROUPINFO.pm | 2 +- core/lib/Foswiki/Macros/ICON.pm | 3 +- core/lib/Foswiki/Macros/ICONURL.pm | 2 +- core/lib/Foswiki/Macros/IF.pm | 4 +- core/lib/Foswiki/Macros/INCLUDE.pm | 63 +- core/lib/Foswiki/Macros/LANGUAGES.pm | 2 +- core/lib/Foswiki/Macros/MAKETEXT.pm | 2 +- core/lib/Foswiki/Macros/META.pm | 14 +- core/lib/Foswiki/Macros/QUERYPARAMS.pm | 4 +- core/lib/Foswiki/Macros/RENDERZONE.pm | 2 +- core/lib/Foswiki/Macros/REVINFO.pm | 12 +- core/lib/Foswiki/Macros/SCRIPTURL.pm | 3 +- core/lib/Foswiki/Macros/SEARCH.pm | 5 +- core/lib/Foswiki/Macros/SPACEOUT.pm | 2 +- core/lib/Foswiki/Macros/TOPICLIST.pm | 2 +- core/lib/Foswiki/Macros/URLPARAM.pm | 2 +- core/lib/Foswiki/Macros/USERINFO.pm | 2 +- core/lib/Foswiki/Macros/USERNAME.pm | 2 +- core/lib/Foswiki/Macros/VAR.pm | 5 +- core/lib/Foswiki/Macros/WEBLIST.pm | 6 +- core/lib/Foswiki/Macros/WIKINAME.pm | 2 +- core/lib/Foswiki/Macros/WIKIUSERNAME.pm | 2 +- core/lib/Foswiki/Render.pm | 93 +-- core/lib/Foswiki/Render/IconImage.pm | 6 +- core/lib/Foswiki/Render/Moved.pm | 20 +- core/lib/Foswiki/Render/Parent.pm | 9 +- core/lib/Foswiki/Render/ToolTip.pm | 10 +- core/lib/Foswiki/Render/Zones.pm | 20 +- core/lib/Foswiki/Response.pm | 10 +- core/lib/Foswiki/Search.pm | 6 +- core/lib/Foswiki/Store.pm | 2 +- core/lib/Foswiki/UI/Edit.pm | 2 +- core/lib/Foswiki/Users.pm | 2 +- core/lib/Foswiki/WebFilter.pm | 10 +- 56 files changed, 869 insertions(+), 861 deletions(-) diff --git a/core/lib/Foswiki.pm b/core/lib/Foswiki.pm index 009b50f9cb..38015b92bb 100644 --- a/core/lib/Foswiki.pm +++ b/core/lib/Foswiki.pm @@ -100,21 +100,11 @@ extends qw( Foswiki::Object ); use Assert; use Exporter qw(import); -our @EXPORT_OK = qw(%regex); +our @EXPORT_OK = qw(%regex urlEncode urlDecode make_params load_package); sub SINGLE_SINGLETONS { 0 } sub SINGLE_SINGLETONS_TRACE { 0 } -has attach => ( - is => 'ro', - lazy => 1, - clearer => 1, - predicate => 1, - default => sub { - require Foswiki::Attach; - new Foswiki::Attach( session => $_[0] ); - }, -); has digester => ( is => 'ro', lazy => 1, @@ -150,16 +140,6 @@ has remoteUser => ( is => 'rw', clearer => 1, ); -has renderer => ( - is => 'ro', - lazy => 1, - clearer => 1, - predicate => 1, - default => sub { - load_package('Foswiki::Render'); - Foswiki::Render->new( session => $_[0] ); - }, -); has requestedWebName => ( is => 'rw', clearer => 1, ); has response => ( is => 'rw', @@ -210,61 +190,10 @@ has topicName => ( clearer => 1, ); -# SMELL Shouldn't urlHost attribute be available from the request object? -has urlHost => ( - is => 'rw', - lazy => 1, - clearer => 1, - default => sub { - my $this = shift; - - #{urlHost} is needed by loadSession.. - my $url = $this->request->url(); - my $urlHost; - if ( $url - && !$Foswiki::cfg{ForceDefaultUrlHost} - && $url =~ m{^([^:]*://[^/]*).*$} ) - { - $urlHost = $1; - - if ( $Foswiki::cfg{RemovePortNumber} ) { - $urlHost =~ s/\:[0-9]+$//; - } - - # If the urlHost in the url is localhost, this is a lot less - # useful than the default url host. This is because new CGI("") - # assigns this host by default - it's a default setting, used - # when there is nothing better available. - if ( $urlHost =~ m/^(https?:\/\/)localhost$/i ) { - my $protocol = $1; - -#only replace localhost _if_ the protocol matches the one specified in the DefaultUrlHost - if ( $Foswiki::cfg{DefaultUrlHost} =~ m/^$protocol/i ) { - $urlHost = $Foswiki::cfg{DefaultUrlHost}; - } - } - } - else { - $urlHost = $Foswiki::cfg{DefaultUrlHost}; - } - ASSERT($urlHost) if DEBUG; - return $urlHost; - }, -); has webName => ( is => 'rw', clearer => 1, ); -has zones => ( - is => 'ro', - lazy => 1, - clearer => 1, - predicate => 1, - default => sub { - load_package('Foswiki::Render::Zones'); - return Foswiki::Render::Zones->new( session => $_[0] ); - }, -); our @_newParameters = qw( user request context ); @@ -720,292 +649,6 @@ sub BUILD { $this->plugins->enable(); } -=begin TML - ----++ ObjectMethod writeCompletePage( $text, $pageType, $contentType ) - -Write a complete HTML page with basic header to the browser. - * =$text= is the text of the page script (<html> to </html> if it's HTML) - * =$pageType= - May be "edit", which will cause headers to be generated that force - caching for 24 hours, to prevent Codev.BackFromPreviewLosesText bug, which caused - data loss with IE5 and IE6. - * =$contentType= - page content type | text/html - -This method removes noautolink and nop tags before outputting the page unless -$contentType is text/plain. - -=cut - -sub writeCompletePage { - my ( $this, $text, $pageType, $contentType ) = @_; - - # true if the body is to be output without encoding to utf8 - # first. This is the case if the body has been gzipped and/or - # rendered from the cache - my $binary_body = 0; - - $contentType ||= 'text/html'; - - my $cgis = $this->users->getCGISession(); - if ( $cgis - && $contentType =~ m!^text/html! - && $Foswiki::cfg{Validation}{Method} ne 'none' ) - { - - # Don't expire the validation key through login, or when - # endpoint is an error. - Foswiki::Validation::expireValidationKeys($cgis) - unless ( $this->request->action() eq 'login' - or ( $ENV{REDIRECT_STATUS} || 0 ) >= 400 ); - - my $usingStrikeOne = $Foswiki::cfg{Validation}{Method} eq 'strikeone'; - if ($usingStrikeOne) { - - # add the validation cookie - my $valCookie = Foswiki::Validation::getCookie($cgis); - $valCookie->secure( $this->request->secure ); - $this->response->cookies( - [ $this->response->cookies, $valCookie ] ); - - # Add the strikeone JS module to the page. - my $src = (DEBUG) ? '.uncompressed' : ''; - $this->zones->addToZone( - 'script', - 'JavascriptFiles/strikeone', - '', - 'JQUERYPLUGIN' - ); - - # Add the onsubmit handler to the form - $text =~ s/(]*method=['"]POST['"][^>]*>)/ - Foswiki::Validation::addOnSubmit($1)/gei; - } - - my $context = - $this->request->url( -full => 1, -path => 1, -query => 1 ) . time(); - - # Inject validation key in HTML forms - $text =~ s/(]*method=['"]POST['"][^>]*>)/ - $1 . Foswiki::Validation::addValidationKey( - $cgis, $context, $usingStrikeOne )/gei; - - #add validation key to HTTP header so we can update it for ajax use - $this->response->pushHeader( - 'X-Foswiki-Validation', - Foswiki::Validation::generateValidationKey( - $cgis, $context, $usingStrikeOne - ) - ) if ($cgis); - } - - if ( $this->zones ) { - - $text = $this->zones()->_renderZones($text); - } - - # Validate format of content-type (defined in rfc2616) - my $tch = qr/[^\[\]()<>@,;:\\"\/?={}\s]/; - if ( $contentType =~ m/($tch+\/$tch+(\s*;\s*$tch+=($tch+|"[^"]*"))*)$/i ) { - $contentType = $1; - } - else { - # SMELL: can't compute; faking content-type for backwards compatibility; - # any other information might become bogus later anyway - $contentType = "text/plain;contenttype=invalid"; - } - my $hdr = "Content-type: " . $1 . "\r\n"; - - # Call final handler - $this->plugins->dispatch( 'completePageHandler', $text, $hdr ); - - # cache final page, but only view and rest - my $cachedPage; - if ( $contentType ne 'text/plain' ) { - - # Remove and tags - $text =~ s/([\t ]?)[ \t]*<\/?(nop|noautolink)\/?>/$1/gis; - if ( $Foswiki::cfg{Cache}{Enabled} - && ( $this->inContext('view') || $this->inContext('rest') ) ) - { - $cachedPage = $this->cache->cachePage( $contentType, $text ); - $this->cache->renderDirtyAreas( \$text ) - if $cachedPage && $cachedPage->{isdirty}; - } - - # remove tags - $text =~ s/<\/?dirtyarea[^>]*>//g; - - # Check that the templates specified clean HTML - if (DEBUG) { - - # When tracing is enabled in Foswiki::Templates, then there will - # always be a after . So we need to disable - # this check. - require Foswiki::Templates; - if ( !Foswiki::Templates->TRACE - && $contentType =~ m#text/html# - && $text =~ m#(.*?\S.*)$#s ) - { - ASSERT( 0, <: $1. Templates may be bogus -- Check for excess blank lines at ends of .tmpl files -- or newlines after %TMPL:INCLUDE -- You can enable TRACE in Foswiki::Templates to help debug -BOGUS - } - } - } - - $this->response->pushHeader( 'X-Foswiki-Monitor-renderTime', - $this->request->getTime() ); - - my $hopts = { 'Content-Type' => $contentType }; - - $this->setCacheControl( $pageType, $hopts ); - - if ($cachedPage) { - $text = '' unless $this->setETags( $cachedPage, $hopts ); - } - - if ( $Foswiki::cfg{HttpCompress} && length($text) ) { - - # Generate a zipped page, if the client accepts them - - # SMELL: $ENV{SPDY} is a non-standard way to detect spdy protocol - if ( my $encoding = _gzipAccepted() ) { - $hopts->{'Content-Encoding'} = $encoding; - $hopts->{'Vary'} = 'Accept-Encoding'; - - # check if we take the version from the cache. NOTE: we don't - # set X-Foswiki-Pagecache because this is *not* coming from - # the cache (well it is, but it was only just put there) - if ( $cachedPage && !$cachedPage->{isdirty} ) { - $text = $cachedPage->{data}; - } - else { - # Not available from the cache, or it has dirty areas - require Compress::Zlib; - $text = Compress::Zlib::memGzip( encode_utf8($text) ); - } - $binary_body = 1; - } - } # Otherwise fall through and generate plain text - - # Generate (and print) HTTP headers. - $this->generateHTTPHeaders($hopts); - - if ($binary_body) { - $this->response->body($text); - } - else { - $this->response->print($text); - } -} - -=begin TML - ----++ ObjectMethod setCacheControl( $pageType, \%hopts ) - -Set the cache control headers in a response - - * =$pageType= - page type - 'view', ;edit' etc - * =\%hopts - ref to partially filled in hash of headers - -=cut - -sub setCacheControl { - my ( $this, $pageType, $hopts ) = @_; - - if ( $pageType && $pageType eq 'edit' ) { - - # Edit pages - future versions will extend to - # of other types of page, with expiry time driven by page type. - - # Get time now in HTTP header format - my $lastModifiedString = - Foswiki::Time::formatTime( time, '$http', 'gmtime' ); - - # Expiry time is set high to avoid any data loss. Each instance of - # Edit page has a unique URL with time-string suffix (fix for - # RefreshEditPage), so this long expiry time simply means that the - # browser Back button always works. The next Edit on this page - # will use another URL and therefore won't use any cached - # version of this Edit page. - my $expireHours = 24; - my $expireSeconds = $expireHours * 60 * 60; - - # and cache control headers, to ensure edit page - # is cached until required expiry time. - $hopts->{'last-modified'} = $lastModifiedString; - $hopts->{expires} = "+${expireHours}h"; - $hopts->{'Cache-Control'} = "max-age=$expireSeconds"; - } - else { - - # we need to force the browser into a check on every - # request; let the server decide on an 304 as below - my $cacheControl = 'max-age=0'; - - # allow the admin to disable us from setting the max-age, as then - # it can't be set by apache - $cacheControl = $Foswiki::cfg{BrowserCacheControl}->{ $this->webName } - if ( $Foswiki::cfg{BrowserCacheControl} - && defined( $Foswiki::cfg{BrowserCacheControl}->{ $this->webName } ) - ); - - # don't remove the 'if'; we need the header to not be there at - # all for the browser to use the cached version - $hopts->{'Cache-Control'} = $cacheControl if ( $cacheControl ne '' ); - } -} - -=begin TML - ----++ ObjectMethod setETags( $cachedPage, \%hopts ) -> $boolean - -Set etags (and modify status) depending on what the cached page specifies. -Return 1 if the page has been modified since it was last retrieved, 0 otherwise. - - * =$cachedPage= - page cache to use - * =\%hopts - ref to partially filled in hash of headers - -=cut - -sub setETags { - my ( $this, $cachedPage, $hopts ) = @_; - - # check etag and last modification time - my $etag = $cachedPage->{etag}; - my $lastModified = $cachedPage->{lastmodified}; - - $hopts->{'ETag'} = $etag if $etag; - $hopts->{'Last-Modified'} = $lastModified if $lastModified; - - # only send a 304 if both criteria are true - return 1 - unless ( - $etag - && $lastModified - - && $ENV{'HTTP_IF_NONE_MATCH'} - && $etag eq $ENV{'HTTP_IF_NONE_MATCH'} - - && $ENV{'HTTP_IF_MODIFIED_SINCE'} - && $lastModified eq $ENV{'HTTP_IF_MODIFIED_SINCE'} - ); - - # finally decide on a 304 reply - $hopts->{'Status'} = '304 Not Modified'; - - #print STDERR "NOT modified\n"; - return 0; -} - # Tests if the $redirect is an external URL, returning false if # AllowRedirectUrl is denied sub _isRedirectSafe { @@ -1358,86 +1001,6 @@ sub isValidEmailAddress { =begin TML ----++ ObjectMethod getScriptUrl( $absolute, $script, $web, $topic, ... ) -> $scriptURL - -Returns the URL to a Foswiki script, providing the web and topic as -"path info" parameters. The result looks something like this: -"http://host/foswiki/bin/$script/$web/$topic". - * =...= - an arbitrary number of name,value parameter pairs that will -be url-encoded and added to the url. The special parameter name '#' is -reserved for specifying an anchor. e.g. -=getScriptUrl('x','y','view','#'=>'XXX',a=>1,b=>2)= will give -=.../view/x/y?a=1&b=2#XXX= - -If $absolute is set, generates an absolute URL. $absolute is advisory only; -Foswiki can decide to generate absolute URLs (for example when run from the -command-line) even when relative URLs have been requested. - -The default script url is taken from {ScriptUrlPath}, unless there is -an exception defined for the given script in {ScriptUrlPaths}. Both -{ScriptUrlPath} and {ScriptUrlPaths} may be absolute or relative URIs. If -they are absolute, then they will always generate absolute URLs. if they -are relative, then they will be converted to absolute when required (e.g. -when running from the command line, or when generating rss). If -$script is not given, absolute URLs will always be generated. - -If either the web or the topic is defined, will generate a full url (including web and topic). Otherwise will generate only up to the script name. An undefined web will default to the main web name. - -As required by RFC3986, the returned URL will only contain the -allowed characters -A-Za-z0-9_.~!*\'();:@&=+$,/?%#[] - -=cut - -sub getScriptUrl { - my ( $this, $absolute, $script, $web, $topic, @params ) = @_; - - $absolute ||= - ( $this->inContext('command_line') || $this->inContext('absolute_urls') ); - - # SMELL: topics and webs that contain spaces? - - my $url; - if ( defined $Foswiki::cfg{ScriptUrlPaths} && $script ) { - $url = $Foswiki::cfg{ScriptUrlPaths}{$script}; - } - unless ( defined($url) ) { - $url = $Foswiki::cfg{ScriptUrlPath}; - if ($script) { - $url .= '/' unless $url =~ m/\/$/; - $url .= $script; - if ( - rindex( $url, $Foswiki::cfg{ScriptSuffix} ) != - ( length($url) - length( $Foswiki::cfg{ScriptSuffix} ) ) ) - { - $url .= $Foswiki::cfg{ScriptSuffix} if $script; - } - } - } - - if ( $absolute && $url !~ /^[a-z]+:/ ) { - - # See http://www.ietf.org/rfc/rfc2396.txt for the definition of - # "absolute URI". Foswiki bastardises this definition by assuming - # that all relative URLs lack the component as well. - $url = $this->urlHost . $url; - } - - if ($topic) { - ( $web, $topic ) = $this->normalizeWebTopicName( $web, $topic ); - - $url .= urlEncode( '/' . $web . '/' . $topic ); - - } - elsif ($web) { - $url .= urlEncode( '/' . $web ); - } - $url .= make_params(@params); - - return $url; -} - -=begin TML - ---++ StaticMethod make_params(...) Generate a URL parameters string from parameters given. A parameter named '#' will generate a fragment identifier. @@ -1513,35 +1076,6 @@ sub getPubURL { =begin TML ----++ ObjectMethod deepWebList($filter, $web) -> @list - -Deep list subwebs of the named web. $filter is a Foswiki::WebFilter -object that is used to filter the list. The listing of subwebs is -dependent on $Foswiki::cfg{EnableHierarchicalWebs} being true. - -Webs are returned as absolute web pathnames. - -=cut - -sub deepWebList { - my ( $this, $filter, $rootWeb ) = @_; - my @list; - my $webObject = new Foswiki::Meta( session => $this, web => $rootWeb ); - my $it = $webObject->eachWeb( $Foswiki::cfg{EnableHierarchicalWebs} ); - return $it->all() unless $filter; - while ( $it->hasNext() ) { - my $w = $rootWeb || ''; - $w .= '/' if $w; - $w .= $it->next(); - if ( $filter->ok( $this, $w ) ) { - push( @list, $w ); - } - } - return @list; -} - -=begin TML - ---++ StaticMethod load_package( $full_package_name ) Will cleanly load the package or fail. This is better than 'eval "require $package"'. @@ -1859,60 +1393,6 @@ sub validatePattern { =begin TML ----++ ObjectMethod inlineAlert($template, $def, ... ) -> $string - -Format an error for inline inclusion in rendered output. The message string -is obtained from the template 'oops'.$template, and the DEF $def is -selected. The parameters (...) are used to populate %PARAM1%..%PARAMn% - -=cut - -sub inlineAlert { - my $this = shift; - my $template = shift; - my $def = shift; - - # web and topic can be anything; they are not used - my $topicObject = Foswiki::Meta->new( - session => $this, - web => $this->webName, - topic => $this->topicName - ); - my $text = $this->templates->readTemplate( 'oops' . $template ); - if ($text) { - my $blah = $this->templates->expandTemplate($def); - $text =~ s/%INSTANTIATE%/$blah/; - - $text = $topicObject->expandMacros($text); - my $n = 1; - while ( defined( my $param = shift ) ) { - $text =~ s/%PARAM$n%/$param/g; - $n++; - } - - # Suppress missing params - $text =~ s/%PARAM\d+%//g; - - # Suppress missing params - $text =~ s/%PARAM\d+%//g; - } - else { - - # Error in the template system. - $text = $topicObject->renderTML(< $encodedText Escape special characters to HTML numeric entities. This is *not* a generic @@ -2259,49 +1739,6 @@ sub readFile { =begin TML ----++ StaticMethod expandStandardEscapes($str) -> $unescapedStr - -Expands standard escapes used in parameter values to block evaluation. See -System.FormatTokens for a full list of supported tokens. - -=cut - -sub expandStandardEscapes { - my $text = shift; - - # expand '$n()' and $n! to new line - $text =~ s/\$n\(\)/\n/gs; - $text =~ s/\$n(?=[^[:alpha:]]|$)/\n/gs; - - # filler, useful for nested search - $text =~ s/\$nop(\(\))?//gs; - - # $quot -> " - $text =~ s/\$quot(\(\))?/\"/gs; - - # $comma -> , - $text =~ s/\$comma(\(\))?/,/gs; - - # $percent -> % - $text =~ s/\$perce?nt(\(\))?/\%/gs; - - # $lt -> < - $text =~ s/\$lt(\(\))?/\ > - $text =~ s/\$gt(\(\))?/\>/gs; - - # $amp -> & - $text =~ s/\$amp(\(\))?/\&/gs; - - # $dollar -> $, done last to avoid creating the above tokens - $text =~ s/\$dollar(\(\))?/\$/gs; - - return $text; -} - -=begin TML - ---++ ObjectMethod webExists( $web ) -> $boolean Test if web exists diff --git a/core/lib/Foswiki/App.pm b/core/lib/Foswiki/App.pm index f326917b49..a686933e13 100644 --- a/core/lib/Foswiki/App.pm +++ b/core/lib/Foswiki/App.pm @@ -19,9 +19,11 @@ use Cwd; use CGI; use Try::Tiny; use Storable qw(dclone); -use Compress::Zlib (); -use Foswiki::Config (); -use Foswiki::Engine (); +use Compress::Zlib (); +use Foswiki::Config (); +use Foswiki::Engine (); +use Foswiki::Templates (); +use Foswiki qw(load_package); use Moo; use namespace::clean; @@ -41,6 +43,13 @@ has access => ( return $this->create($accessClass); }, ); +has attach => ( + is => 'ro', + lazy => 1, + clearer => 1, + predicate => 1, + default => sub { $_[0]->create('Foswiki::Attach'); }, +); has cache => ( is => 'rw', lazy => 1, @@ -127,6 +136,15 @@ has prefs => ( clearer => 1, default => sub { return $_[0]->create('Foswiki::Prefs'); }, ); +has renderer => ( + is => 'ro', + lazy => 1, + clearer => 1, + predicate => 1, + default => sub { + return $_[0]->create('Foswiki::Render'); + }, +); has request => ( is => 'rw', lazy => 1, @@ -201,6 +219,13 @@ has users => ( clearer => 1, default => sub { return $_[0]->create('Foswiki::Users'); }, ); +has zones => ( + is => 'ro', + lazy => 1, + clearer => 1, + predicate => 1, + default => sub { return $_[0]->create('Foswiki::Render::Zones'); }, +); has _dispatcherObject => ( is => 'rw', isa => Foswiki::Object::isaCLASS( @@ -451,6 +476,35 @@ sub create { =begin TML +---++ ObjectMethod deepWebList($filter, $web) -> @list + +Deep list subwebs of the named web. $filter is a Foswiki::WebFilter +object that is used to filter the list. The listing of subwebs is +dependent on $Foswiki::cfg{EnableHierarchicalWebs} being true. + +Webs are returned as absolute web pathnames. + +=cut + +sub deepWebList { + my ( $this, $filter, $rootWeb ) = @_; + my @list; + my $webObject = $this->create( 'Foswiki::Meta', web => $rootWeb ); + my $it = $webObject->eachWeb( $this->cfg->data->{EnableHierarchicalWebs} ); + return $it->all() unless $filter; + while ( $it->hasNext() ) { + my $w = $rootWeb || ''; + $w .= '/' if $w; + $w .= $it->next(); + if ( $filter->ok( $this, $w ) ) { + push( @list, $w ); + } + } + return @list; +} + +=begin TML + ---++ ObjectMethod enterContext( $id, $val ) Add the context id $id into the set of active contexts. The $val @@ -510,6 +564,62 @@ sub inContext { =begin TML +---++ ObjectMethod inlineAlert($template, $def, ... ) -> $string + +Format an error for inline inclusion in rendered output. The message string +is obtained from the template 'oops'.$template, and the DEF $def is +selected. The parameters (...) are used to populate %PARAM1%..%PARAMn% + +=cut + +sub inlineAlert { + my $this = shift; + my $template = shift; + my $def = shift; + + my $req = $this->request; + + # web and topic can be anything; they are not used + my $topicObject = $this->create( + 'Foswiki::Meta', + web => $req->web, + topic => $req->topic, + ); + my $text = $this->templates->readTemplate( 'oops' . $template ); + if ($text) { + my $blah = $this->templates->expandTemplate($def); + $text =~ s/%INSTANTIATE%/$blah/; + + $text = $topicObject->expandMacros($text); + my $n = 1; + while ( defined( my $param = shift ) ) { + $text =~ s/%PARAM$n%/$param/g; + $n++; + } + + # Suppress missing params + $text =~ s/%PARAM\d+%//g; + + # Suppress missing params + $text =~ s/%PARAM\d+%//g; + } + else { + + # Error in the template system. + $text = $topicObject->renderTML(< $boolean Try and satisfy the current request for the given web.topic from the cache, given @@ -611,6 +721,64 @@ sub satisfiedByCache { =begin TML +---++ ObjectMethod setCacheControl( $pageType, \%hopts ) + +Set the cache control headers in a response + + * =$pageType= - page type - 'view', ;edit' etc + * =\%hopts - ref to partially filled in hash of headers + +=cut + +sub setCacheControl { + my ( $this, $pageType, $hopts ) = @_; + + if ( $pageType && $pageType eq 'edit' ) { + + # Edit pages - future versions will extend to + # of other types of page, with expiry time driven by page type. + + # Get time now in HTTP header format + my $lastModifiedString = + Foswiki::Time::formatTime( time, '$http', 'gmtime' ); + + # Expiry time is set high to avoid any data loss. Each instance of + # Edit page has a unique URL with time-string suffix (fix for + # RefreshEditPage), so this long expiry time simply means that the + # browser Back button always works. The next Edit on this page + # will use another URL and therefore won't use any cached + # version of this Edit page. + my $expireHours = 24; + my $expireSeconds = $expireHours * 60 * 60; + + # and cache control headers, to ensure edit page + # is cached until required expiry time. + $hopts->{'last-modified'} = $lastModifiedString; + $hopts->{expires} = "+${expireHours}h"; + $hopts->{'Cache-Control'} = "max-age=$expireSeconds"; + } + else { + + # we need to force the browser into a check on every + # request; let the server decide on an 304 as below + my $cacheControl = 'max-age=0'; + + my $req = $this->request; + + # allow the admin to disable us from setting the max-age, as then + # it can't be set by apache + $cacheControl = $Foswiki::cfg{BrowserCacheControl}->{ $req->web } + if ( $Foswiki::cfg{BrowserCacheControl} + && defined( $Foswiki::cfg{BrowserCacheControl}->{ $req->web } ) ); + + # don't remove the 'if'; we need the header to not be there at + # all for the browser to use the cached version + $hopts->{'Cache-Control'} = $cacheControl if ( $cacheControl ne '' ); + } +} + +=begin TML + ---++ ObjectMethod setETags( $cachedPage, \%hopts ) -> $boolean Set etags (and modify status) depending on what the cached page specifies. @@ -763,6 +931,191 @@ sub systemMessage { push @{ $this->system_messages }, $message; } +=begin TML + +---++ ObjectMethod writeCompletePage( $text, $pageType, $contentType ) + +Write a complete HTML page with basic header to the browser. + * =$text= is the text of the page script (<html> to </html> if it's HTML) + * =$pageType= - May be "edit", which will cause headers to be generated that force + caching for 24 hours, to prevent Codev.BackFromPreviewLosesText bug, which caused + data loss with IE5 and IE6. + * =$contentType= - page content type | text/html + +This method removes noautolink and nop tags before outputting the page unless +$contentType is text/plain. + +=cut + +sub writeCompletePage { + my ( $this, $text, $pageType, $contentType ) = @_; + + # true if the body is to be output without encoding to utf8 + # first. This is the case if the body has been gzipped and/or + # rendered from the cache + my $binary_body = 0; + + $contentType ||= 'text/html'; + + my $cgis = $this->users->getCGISession(); + if ( $cgis + && $contentType =~ m!^text/html! + && $Foswiki::cfg{Validation}{Method} ne 'none' ) + { + + # Don't expire the validation key through login, or when + # endpoint is an error. + Foswiki::Validation::expireValidationKeys($cgis) + unless ( $this->request->action() eq 'login' + or ( $ENV{REDIRECT_STATUS} || 0 ) >= 400 ); + + my $usingStrikeOne = $Foswiki::cfg{Validation}{Method} eq 'strikeone'; + if ($usingStrikeOne) { + + # add the validation cookie + my $valCookie = Foswiki::Validation::getCookie($cgis); + $valCookie->secure( $this->request->secure ); + $this->response->cookies( + [ $this->response->cookies, $valCookie ] ); + + # Add the strikeone JS module to the page. + my $src = (DEBUG) ? '.uncompressed' : ''; + $this->zones->addToZone( + 'script', + 'JavascriptFiles/strikeone', + '', + 'JQUERYPLUGIN' + ); + + # Add the onsubmit handler to the form + $text =~ s/(]*method=['"]POST['"][^>]*>)/ + Foswiki::Validation::addOnSubmit($1)/gei; + } + + my $context = + $this->request->url( -full => 1, -path => 1, -query => 1 ) . time(); + + # Inject validation key in HTML forms + $text =~ s/(]*method=['"]POST['"][^>]*>)/ + $1 . Foswiki::Validation::addValidationKey( + $cgis, $context, $usingStrikeOne )/gei; + + #add validation key to HTTP header so we can update it for ajax use + $this->response->pushHeader( + 'X-Foswiki-Validation', + Foswiki::Validation::generateValidationKey( + $cgis, $context, $usingStrikeOne + ) + ) if ($cgis); + } + + if ( $this->zones ) { + + $text = $this->zones()->_renderZones($text); + } + + # Validate format of content-type (defined in rfc2616) + my $tch = qr/[^\[\]()<>@,;:\\"\/?={}\s]/; + if ( $contentType =~ m/($tch+\/$tch+(\s*;\s*$tch+=($tch+|"[^"]*"))*)$/i ) { + $contentType = $1; + } + else { + # SMELL: can't compute; faking content-type for backwards compatibility; + # any other information might become bogus later anyway + $contentType = "text/plain;contenttype=invalid"; + } + my $hdr = "Content-type: " . $1 . "\r\n"; + + # Call final handler + $this->plugins->dispatch( 'completePageHandler', $text, $hdr ); + + # cache final page, but only view and rest + my $cachedPage; + if ( $contentType ne 'text/plain' ) { + + # Remove and tags + $text =~ s/([\t ]?)[ \t]*<\/?(nop|noautolink)\/?>/$1/gis; + if ( $Foswiki::cfg{Cache}{Enabled} + && ( $this->inContext('view') || $this->inContext('rest') ) ) + { + $cachedPage = $this->cache->cachePage( $contentType, $text ); + $this->cache->renderDirtyAreas( \$text ) + if $cachedPage && $cachedPage->{isdirty}; + } + + # remove tags + $text =~ s/<\/?dirtyarea[^>]*>//g; + + # Check that the templates specified clean HTML + if (DEBUG) { + + # When tracing is enabled in Foswiki::Templates, then there will + # always be a after . So we need to disable + # this check. + if ( !Foswiki::Templates->TRACE + && $contentType =~ m#text/html# + && $text =~ m#(.*?\S.*)$#s ) + { + ASSERT( 0, <: $1. Templates may be bogus +- Check for excess blank lines at ends of .tmpl files +- or newlines after %TMPL:INCLUDE +- You can enable TRACE in Foswiki::Templates to help debug +BOGUS + } + } + } + + $this->response->pushHeader( 'X-Foswiki-Monitor-renderTime', + $this->request->getTime() ); + + my $hopts = { 'Content-Type' => $contentType }; + + $this->setCacheControl( $pageType, $hopts ); + + if ($cachedPage) { + $text = '' unless $this->setETags( $cachedPage, $hopts ); + } + + if ( $Foswiki::cfg{HttpCompress} && length($text) ) { + + # Generate a zipped page, if the client accepts them + + # SMELL: $ENV{SPDY} is a non-standard way to detect spdy protocol + if ( my $encoding = _gzipAccepted() ) { + $hopts->{'Content-Encoding'} = $encoding; + $hopts->{'Vary'} = 'Accept-Encoding'; + + # check if we take the version from the cache. NOTE: we don't + # set X-Foswiki-Pagecache because this is *not* coming from + # the cache (well it is, but it was only just put there) + if ( $cachedPage && !$cachedPage->{isdirty} ) { + $text = $cachedPage->{data}; + } + else { + # Not available from the cache, or it has dirty areas + $text = Compress::Zlib::memGzip( encode_utf8($text) ); + } + $binary_body = 1; + } + } # Otherwise fall through and generate plain text + + # Generate (and print) HTTP headers. + $this->response->generateHTTPHeaders($hopts); + + if ($binary_body) { + $this->response->body($text); + } + else { + $this->response->print($text); + } +} + sub _prepareContext { my $this = shift; $this->context->{SUPPORTS_PARA_INDENT} = 1; diff --git a/core/lib/Foswiki/Attach.pm b/core/lib/Foswiki/Attach.pm index a9a046fd5f..082528b362 100644 --- a/core/lib/Foswiki/Attach.pm +++ b/core/lib/Foswiki/Attach.pm @@ -16,7 +16,7 @@ use Unicode::Normalize; use Moo; use namespace::clean; -extends qw(Foswiki::Object); +extends qw(Foswiki::AppObject); BEGIN { if ( $Foswiki::cfg{UseLocale} ) { @@ -27,17 +27,9 @@ BEGIN { our $MARKER = "\0"; -has session => ( - is => 'rw', - clearer => 1, - required => 1, - weak_ref => 1, - isa => Foswiki::Object::isaCLASS( 'session', 'Foswiki', noUndef => 1 ), -); - =begin TML ----++ ClassMethod new(session => $session) +---++ ClassMethod new(app => $app) Constructor. @@ -93,7 +85,7 @@ sub renderMetaData { my @attachments = $topicObject->find('FILEATTACHMENT'); return '' unless @attachments; - my $templates = $this->session->templates; + my $templates = $this->app->templates; $templates->readTemplate($tmplname); my $rows = ''; @@ -142,7 +134,7 @@ Generate a version history table for a single attachment sub formatVersions { my ( $this, $topicObject, %attrs ) = @_; - my $users = $this->session->users; + my $users = $this->app->users; $attrs{name} = Foswiki::Sandbox::untaint( $attrs{name}, @@ -150,7 +142,7 @@ sub formatVersions { my $revIt = $topicObject->getRevisionHistory( $attrs{name} ); - my $templates = $this->session->templates; + my $templates = $this->app->templates; $templates->readTemplate('attachtables'); my $header = $templates->expandTemplate('ATTACH:versions:header'); @@ -204,7 +196,7 @@ s/%R_(\w+)%/_expandRowAttrs( $this, $1, $topicObject, $info, $attachmentNum, $is sub _expandAttrs { my ( $this, $attr, $topicObject, $info, $attachmentNum ) = @_; my $file = $info->{name} || ''; - my $users = $this->session->users; + my $users = $this->app->users; require Foswiki::Time; @@ -222,7 +214,7 @@ sub _expandAttrs { return $1; } elsif ( $attr eq 'URL' ) { - return $this->session->getScriptUrl( + return $this->app->cfg->getScriptUrl( 0, 'viewfile', $topicObject->web, $topicObject->topic, rev => $info->{version} || undef, filename => $file @@ -302,7 +294,7 @@ sub _expandRowAttrs { sub _cUID { my ( $this, $info ) = @_; - my $users = $this->session->users; + my $users = $this->app->users; my $user = $info->{author} || $info->{user} || 'UnknownUser'; my $cUID; if ($user) { @@ -368,7 +360,7 @@ sub getAttachmentLink { my $fileLink = ''; my $imgSize = ''; - my $prefs = $this->session->prefs; + my $prefs = $this->app->prefs; if ( $attName =~ m/\.(gif|jpg|jpeg|png)$/i ) { @@ -425,7 +417,7 @@ sub getAttachmentLink { require Foswiki::Time; $fileLink = Foswiki::Time::formatTime( $fileTime, $fileLink ); - $fileLink = Foswiki::expandStandardEscapes($fileLink); + $fileLink = $this->app->macros->expandStandardEscapes($fileLink); return $fileLink; } diff --git a/core/lib/Foswiki/Config.pm b/core/lib/Foswiki/Config.pm index c59087860b..4220f79018 100644 --- a/core/lib/Foswiki/Config.pm +++ b/core/lib/Foswiki/Config.pm @@ -1,5 +1,13 @@ # See bottom of file for license and copyright information +=begin TML + +---+!! package Foswiki::Config + +Class representing configuration data. + +=cut + package Foswiki::Config; use v5.14; @@ -11,7 +19,7 @@ use POSIX qw(locale_h); use Unicode::Normalize; use Cwd qw( abs_path ); use Try::Tiny; -use Foswiki (); +use Foswiki qw(urlEncode urlDecode make_params); use Moo; use namespace::clean; @@ -45,6 +53,13 @@ my %remap = ( '{RCS}{WorkAreaDir}' => '{Store}{WorkAreaDir}' ); +=begin TML +---++ Attribute data + +Contains configuration hash. =%Foswiki::cfg= is an alias to this attribute. + +=cut + has data => ( is => 'rw', lazy => 1, @@ -52,13 +67,26 @@ has data => ( default => sub { {} }, ); -# What files we read the config from in the order of reading. +=begin TML +---++ Attribute files + +What files we read the config from in the order of reading. + +=cut + has files => ( is => 'rw', default => sub { [] }, ); -# failed keeps the name of the failed config or spec file. +=begin TML + +---++ Attribute failedConfig + +Keeps the name of the failed config or spec file. + +=cut + has failedConfig => ( is => 'rw', ); has bootstrapMessage => ( is => 'rw', ); has noExpand => ( is => 'rw', default => 0, ); @@ -66,6 +94,55 @@ has noSpec => ( is => 'rw', default => 0, ); has configSpec => ( is => 'rw', default => 0, ); has noLocal => ( is => 'rw', default => 0, ); +# Configuration shortcut attributes. + +=begin TML + +---++ Attribute urlHost + +=cut + +has urlHost => ( + is => 'rw', + lazy => 1, + default => sub { + my $this = shift; + + #{urlHost} is needed by loadSession.. + my $url = $this->app->request->url; + my $cfg = $this->app->cfg; + my $urlHost; + if ( $url + && !$cfg->data->{ForceDefaultUrlHost} + && $url =~ m{^([^:]*://[^/]*).*$} ) + { + $urlHost = $1; + + if ( $cfg->data->{RemovePortNumber} ) { + $urlHost =~ s/\:[0-9]+$//; + } + + # If the urlHost in the url is localhost, this is a lot less + # useful than the default url host. This is because new CGI("") + # assigns this host by default - it's a default setting, used + # when there is nothing better available. + if ( $urlHost =~ m/^(https?:\/\/)localhost$/i ) { + my $protocol = $1; + +#only replace localhost _if_ the protocol matches the one specified in the DefaultUrlHost + if ( $cfg->data->{DefaultUrlHost} =~ m/^$protocol/i ) { + $urlHost = $cfg->data->{DefaultUrlHost}; + } + } + } + else { + $urlHost = $cfg->data->{DefaultUrlHost}; + } + ASSERT($urlHost) if DEBUG; + return $urlHost; + }, +); + =begin TML ---++ ClassMethod new([noExpand => 0/1][, noSpec => 0/1][, configSpec => 0/1][, noLoad => 0/1]) @@ -876,6 +953,88 @@ sub setBootstrap { push( @{ $this->data->{BOOTSTRAP} }, @BOOTSTRAP ); } +=begin TML + +---++ ObjectMethod getScriptUrl( $absolute, $script, $web, $topic, ... ) -> $scriptURL + +Returns the URL to a Foswiki script, providing the web and topic as +"path info" parameters. The result looks something like this: +"http://host/foswiki/bin/$script/$web/$topic". + * =...= - an arbitrary number of name,value parameter pairs that will +be url-encoded and added to the url. The special parameter name '#' is +reserved for specifying an anchor. e.g. +=getScriptUrl('x','y','view','#'=>'XXX',a=>1,b=>2)= will give +=.../view/x/y?a=1&b=2#XXX= + +If $absolute is set, generates an absolute URL. $absolute is advisory only; +Foswiki can decide to generate absolute URLs (for example when run from the +command-line) even when relative URLs have been requested. + +The default script url is taken from {ScriptUrlPath}, unless there is +an exception defined for the given script in {ScriptUrlPaths}. Both +{ScriptUrlPath} and {ScriptUrlPaths} may be absolute or relative URIs. If +they are absolute, then they will always generate absolute URLs. if they +are relative, then they will be converted to absolute when required (e.g. +when running from the command line, or when generating rss). If +$script is not given, absolute URLs will always be generated. + +If either the web or the topic is defined, will generate a full url (including web and topic). Otherwise will generate only up to the script name. An undefined web will default to the main web name. + +As required by RFC3986, the returned URL will only contain the +allowed characters -A-Za-z0-9_.~!*\'();:@&=+$,/?%#[] + +=cut + +sub getScriptUrl { + my ( $this, $absolute, $script, $web, $topic, @params ) = @_; + + my $app = $this->app; + + $absolute ||= + ( $app->inContext('command_line') || $app->inContext('absolute_urls') ); + + # SMELL: topics and webs that contain spaces? + + my $url; + if ( defined $this->data->{ScriptUrlPaths} && $script ) { + $url = $this->data->{ScriptUrlPaths}{$script}; + } + unless ( defined($url) ) { + $url = $this->data->{ScriptUrlPath}; + if ($script) { + $url .= '/' unless $url =~ m/\/$/; + $url .= $script; + if ( + rindex( $url, $this->data->{ScriptSuffix} ) != + ( length($url) - length( $this->data->{ScriptSuffix} ) ) ) + { + $url .= $this->data->{ScriptSuffix} if $script; + } + } + } + + if ( $absolute && $url !~ /^[a-z]+:/ ) { + + # See http://www.ietf.org/rfc/rfc2396.txt for the definition of + # "absolute URI". Foswiki bastardises this definition by assuming + # that all relative URLs lack the component as well. + $url = $this->urlHost . $url; + } + + if ($topic) { + ( $web, $topic ) = $app->request->normalizeWebTopicName( $web, $topic ); + + $url .= urlEncode( '/' . $web . '/' . $topic ); + + } + elsif ($web) { + $url .= urlEncode( '/' . $web ); + } + $url .= make_params(@params); + + return $url; +} + # Preset values that are hard-coded and not coming from external sources. sub _populatePresets { my $this = shift; @@ -1030,10 +1189,10 @@ sub _guessDefaults { # Windows default tmpdir is the C: root use something sane. # Configure does a better job, it should be run. - $Foswiki::cfg{TempfileDir} = $Foswiki::cfg{WorkingDir}; + $this->data->{TempfileDir} = $this->data->{WorkingDir}; } else { - $Foswiki::cfg{TempfileDir} = File::Spec->tmpdir(); + $this->data->{TempfileDir} = File::Spec->tmpdir(); } } diff --git a/core/lib/Foswiki/Form.pm b/core/lib/Foswiki/Form.pm index 6ac579e9c8..cf4f4a6e43 100644 --- a/core/lib/Foswiki/Form.pm +++ b/core/lib/Foswiki/Form.pm @@ -586,8 +586,9 @@ sub renderForEdit { my $dv = $fieldDef->getDefaultValue($value); if ( defined($dv) ) { - $dv = $topicObject->expandMacros($dv); - $value = Foswiki::expandStandardEscapes($dv); # Item2837 + $dv = $topicObject->expandMacros($dv); + $value = + $app->macros->expandStandardEscapes($dv); # Item2837 } } diff --git a/core/lib/Foswiki/Form/FieldDefinition.pm b/core/lib/Foswiki/Form/FieldDefinition.pm index 171e6449c0..25e16f43e6 100644 --- a/core/lib/Foswiki/Form/FieldDefinition.pm +++ b/core/lib/Foswiki/Form/FieldDefinition.pm @@ -297,8 +297,8 @@ sub populateMetaFromQueryData { # Note: we test for 'defined' because value can also be 0 (zero) $value = $query->param( $this->name ); $value = '' unless defined $value; - if ( $this->session->inContext('edit') ) { - $value = Foswiki::expandStandardEscapes($value); + if ( $this->app->inContext('edit') ) { + $value = $this->app->macros->expandStandardEscapes($value); } } } diff --git a/core/lib/Foswiki/Func.pm b/core/lib/Foswiki/Func.pm index 4950991f0b..8b25fca50d 100644 --- a/core/lib/Foswiki/Func.pm +++ b/core/lib/Foswiki/Func.pm @@ -3073,7 +3073,7 @@ sub normalizeWebTopicName { #my( $web, $topic ) = @_; ASSERT($Foswiki::app) if DEBUG; - return $Foswiki::app->normalizeWebTopicName(@_); + return $Foswiki::app->request->normalizeWebTopicName(@_); } =begin TML @@ -3146,7 +3146,7 @@ The set of tokens that is expanded is described in System.FormatTokens. =cut sub decodeFormatTokens { - return Foswiki::expandStandardEscapes(@_); + return $Foswiki::app->macros->expandStandardEscapes(@_); } =begin TML diff --git a/core/lib/Foswiki/If/OP_allows.pm b/core/lib/Foswiki/If/OP_allows.pm index ff6b42b56d..be53cc03fb 100644 --- a/core/lib/Foswiki/If/OP_allows.pm +++ b/core/lib/Foswiki/If/OP_allows.pm @@ -32,47 +32,47 @@ around BUILDARGS => sub { }; sub evaluate { - my $this = shift; - my $node = shift; - my $a = $node->params->[0]; # topic name (string) - my $b = $node->params->[1]; # access mode (string) - my $mode = $b->_evaluate(@_) || 'view'; - my %domain = @_; - my $session = $domain{tom}->session; + my $this = shift; + my $node = shift; + my $a = $node->params->[0]; # topic name (string) + my $b = $node->params->[1]; # access mode (string) + my $mode = $b->_evaluate(@_) || 'view'; + my %domain = @_; + my $app = $domain{tom}->app; Foswiki::Exception->throw( text => 'No context in which to evaluate "' . $a->stringify() . '"' ) - unless $session; + unless $app; + my $req = $app->request; my $str = $a->evaluate(@_); return 0 unless $str; - my ( $web, $topic ) = - $session->normalizeWebTopicName( $session->webName, $str ); + my ( $web, $topic ) = $req->normalizeWebTopicName( $req->web, $str ); my $ok = 0; # Try for an existing topic first. - if ( $session->topicExists( $web, $topic ) ) { + if ( $app->store->topicExists( $web, $topic ) ) { my $topicObject = Foswiki::Meta->new( - session => $session, - web => $web, - topic => $topic + app => $app, + web => $web, + topic => $topic ); $ok = $topicObject->haveAccess($mode); } # Not an existing web.topic name, see if the string on its own # is a web name - elsif ( $session->webExists($str) ) { - my $webObject = Foswiki::Meta->new( session => $session, web => $str ); + elsif ( $app->store->webExists($str) ) { + my $webObject = Foswiki::Meta->new( app => $app, web => $str ); $ok = $webObject->haveAccess($mode); } # Not an existing web.topic or a web on it's own; maybe it's # web.topic for an existing web but non-existing topic - elsif ( $session->webExists($web) ) { - my $webObject = Foswiki::Meta->new( session => $session, web => $web ); + elsif ( $app->store->webExists($web) ) { + my $webObject = $this->create( 'Foswiki::Meta', web => $web ); $ok = $webObject->haveAccess($mode); } else { diff --git a/core/lib/Foswiki/If/OP_context.pm b/core/lib/Foswiki/If/OP_context.pm index 561298b3d2..946892906b 100644 --- a/core/lib/Foswiki/If/OP_context.pm +++ b/core/lib/Foswiki/If/OP_context.pm @@ -33,16 +33,16 @@ around BUILDARGS => sub { }; sub evaluate { - my $this = shift; - my $node = shift; - my $a = $node->params->[0]; - my $text = $a->_evaluate(@_) || ''; - my %domain = @_; - my $session = $domain{tom}->session; + my $this = shift; + my $node = shift; + my $a = $node->params->[0]; + my $text = $a->_evaluate(@_) || ''; + my %domain = @_; + my $app = $domain{tom}->app; Foswiki::Exception->throw( text => 'No context in which to evaluate "' . $a->stringify() . '"' ) - unless $session; - return $session->inContext($text) || 0; + unless $app; + return $app->inContext($text) || 0; } 1; diff --git a/core/lib/Foswiki/If/OP_defined.pm b/core/lib/Foswiki/If/OP_defined.pm index d68687047d..2c93366ebf 100644 --- a/core/lib/Foswiki/If/OP_defined.pm +++ b/core/lib/Foswiki/If/OP_defined.pm @@ -28,24 +28,24 @@ around BUILDARGS => sub { }; sub evaluate { - my $this = shift; - my $node = shift; - my $a = $node->params->[0]; - my %domain = @_; - my $session = $domain{tom}->session; + my $this = shift; + my $node = shift; + my $a = $node->params->[0]; + my %domain = @_; + my $app = $domain{tom}->app; Foswiki::Exception->throw( text => 'No context in which to evaluate "' . $a->stringify() . '"' ) - unless $session; + unless $app; # NOTE: If::Node::_evaluate(), not Query::Node::evaluate my $eval = $a->_evaluate(@_); #print STDERR "Evaluate ".$node->stringify()." -> ".(defined $eval ? $eval : 'undef')."\n"; return 0 unless $eval; - return 1 if ( defined( $session->request->param($eval) ) ); + return 1 if ( defined( $app->request->param($eval) ) ); return 1 if ( defined( $domain{tom}->getPreference($eval) ) ); - return 1 if ( defined( $session->prefs->getPreference($eval) ) ); - return 1 if ( exists( $Foswiki::macros{$eval} ) ); + return 1 if ( defined( $app->prefs->getPreference($eval) ) ); + return 1 if ( $app->macros->exists($eval) ); return 0; } diff --git a/core/lib/Foswiki/If/OP_dollar.pm b/core/lib/Foswiki/If/OP_dollar.pm index 3966b3ec38..d1e116e683 100644 --- a/core/lib/Foswiki/If/OP_dollar.pm +++ b/core/lib/Foswiki/If/OP_dollar.pm @@ -32,21 +32,21 @@ around BUILDARGS => sub { }; sub evaluate { - my $this = shift; - my $node = shift; - my $a = $node->params->[0]; - my %domain = @_; - my $session = $domain{tom}->session; + my $this = shift; + my $node = shift; + my $a = $node->params->[0]; + my %domain = @_; + my $app = $domain{tom}->app; Foswiki::Exception->throw( text => 'No context in which to evaluate "' . $a->stringify() . '"' ) - unless $session; + unless $app; my $text = $a->_evaluate(@_) || ''; - if ( $text && defined( $session->request->param($text) ) ) { - return $session->request->param($text); + if ( $text && defined( $app->request->param($text) ) ) { + return $app->request->param($text); } $text = "%$text%"; - Foswiki::innerExpandMacros( $session, \$text, $domain{tom} ); + $app->macros->innerExpandMacros( \$text, $domain{tom} ); return $text || ''; } diff --git a/core/lib/Foswiki/If/OP_ingroup.pm b/core/lib/Foswiki/If/OP_ingroup.pm index 272407351c..c3499995c9 100644 --- a/core/lib/Foswiki/If/OP_ingroup.pm +++ b/core/lib/Foswiki/If/OP_ingroup.pm @@ -43,17 +43,17 @@ sub evaluate { my $a = $node->params->[0] ; # user cUID/ loginname / WikiName / WebDotWikiName :( (string) - my $b = $node->params->[1]; # group name (string - my %domain = @_; - my $session = $domain{tom}->session; + my $b = $node->params->[1]; # group name (string + my %domain = @_; + my $app = $domain{tom}->app; Foswiki::Exception->throw( text => 'No context in which to evaluate "' . $a->stringify() . '"' ) - unless $session; - my $user = $session->users->getCanonicalUserID( $a->evaluate(@_) ); + unless $app; + my $user = $app->users->getCanonicalUserID( $a->evaluate(@_) ); return 0 unless $user; my $group = $b->_evaluate(@_); return 0 unless $group; - return 1 if ( $session->users->isInGroup( $user, $group ) ); + return 1 if ( $app->users->isInGroup( $user, $group ) ); return 0; } diff --git a/core/lib/Foswiki/If/OP_isempty.pm b/core/lib/Foswiki/If/OP_isempty.pm index bb0b0538a9..ce98236c66 100644 --- a/core/lib/Foswiki/If/OP_isempty.pm +++ b/core/lib/Foswiki/If/OP_isempty.pm @@ -28,18 +28,18 @@ around BUILDARGS => sub { }; sub evaluate { - my $this = shift; - my $node = shift; - my $a = $node->params->[0]; - my %domain = @_; - my $session = $domain{tom}->session; + my $this = shift; + my $node = shift; + my $a = $node->params->[0]; + my %domain = @_; + my $app = $domain{tom}->app; Foswiki::Exception->throw( text => 'No context in which to evaluate "' . $a->stringify() . '"' ) - unless $session; + unless $app; my $eval = $a->_evaluate(@_); return 1 unless $eval; - return 0 if ( $session->request->param($eval) ); - return 0 if ( $session->prefs->getPreference($eval) ); + return 0 if ( $app->request->param($eval) ); + return 0 if ( $app->prefs->getPreference($eval) ); return 1; } diff --git a/core/lib/Foswiki/If/OP_istopic.pm b/core/lib/Foswiki/If/OP_istopic.pm index e7672584f0..86c68e9842 100644 --- a/core/lib/Foswiki/If/OP_istopic.pm +++ b/core/lib/Foswiki/If/OP_istopic.pm @@ -28,15 +28,15 @@ around BUILDARGS => sub { }; sub evaluate { - my $this = shift; - my $node = shift; - my $a = $node->params->[0]; - my %domain = @_; - my $session = $domain{tom}->session; + my $this = shift; + my $node = shift; + my $a = $node->params->[0]; + my %domain = @_; + my $app = $domain{tom}->app; Foswiki::Exception->throw( text => 'No context in which to evaluate "' . $a->stringify() . '"' ) - unless $session; - my ( $web, $topic ) = ( $session->webName, $a->_evaluate(@_) ); + unless $app; + my ( $web, $topic ) = ( $app->request->web, $a->_evaluate(@_) ); return 0 unless ( defined $topic && length($topic) ) @@ -44,9 +44,9 @@ sub evaluate { return 0 if ( $topic eq '0' ); # special case, topic name '0' normalizes to WebHome - ( $web, $topic ) = $session->normalizeWebTopicName( $web, $topic ); + ( $web, $topic ) = $app->request->normalizeWebTopicName( $web, $topic ); - return $session->topicExists( $web, $topic ) ? 1 : 0; + return $app->store->topicExists( $web, $topic ) ? 1 : 0; } 1; diff --git a/core/lib/Foswiki/If/OP_isweb.pm b/core/lib/Foswiki/If/OP_isweb.pm index 406c193a32..e4d14beb43 100644 --- a/core/lib/Foswiki/If/OP_isweb.pm +++ b/core/lib/Foswiki/If/OP_isweb.pm @@ -32,16 +32,16 @@ around BUILDARGS => sub { }; sub evaluate { - my $this = shift; - my $node = shift; - my $a = $node->params->[0]; - my %domain = @_; - my $session = $domain{tom}->session; + my $this = shift; + my $node = shift; + my $a = $node->params->[0]; + my %domain = @_; + my $app = $domain{tom}->app; Foswiki::Exception->throw( text => 'No context in which to evaluate "' . $a->stringify() . '"' ) - unless $session; + unless $app; my $web = $a->_evaluate(@_) || ''; - return $session->webExists($web) ? 1 : 0; + return $app->store->webExists($web) ? 1 : 0; } 1; diff --git a/core/lib/Foswiki/Macros.pm b/core/lib/Foswiki/Macros.pm index 22798d9f2d..21218834e1 100644 --- a/core/lib/Foswiki/Macros.pm +++ b/core/lib/Foswiki/Macros.pm @@ -5,12 +5,13 @@ use v5.14; use Foswiki qw(%regex); use Foswiki::Attrs (); -use Assert; use Moo; use namespace::clean; extends qw(Foswiki::AppObject); +use Assert; + =begin TML ---++!! Class Foswiki::Macros @@ -118,6 +119,7 @@ sub expandMacros { return '' unless defined $text; my $app = $this->app; + my $req = $app->request; # Plugin Hook $app->plugins->dispatch( 'beforeCommonTagsHandler', $text, @@ -134,8 +136,8 @@ sub expandMacros { if $topicObject->isCacheable(); # Require defaults for plugin handlers :-( - my $webContext = $topicObject->web || $this->webName; - my $topicContext = $topicObject->topic || $this->topicName; + my $webContext = $topicObject->web || $req->web; + my $topicContext = $topicObject->topic || $req->topic; my $memW = $app->prefs->getPreference('INCLUDINGWEB'); my $memT = $app->prefs->getPreference('INCLUDINGTOPIC'); @@ -304,6 +306,63 @@ sub expandMacrosOnTopicCreation { =begin TML +---++ StaticMethod expandStandardEscapes($str) -> $unescapedStr + +Expands standard escapes used in parameter values to block evaluation. See +System.FormatTokens for a full list of supported tokens. + +=cut + +sub expandStandardEscapes { + my $this = shift; + my $text = shift; + + # expand '$n()' and $n! to new line + $text =~ s/\$n\(\)/\n/gs; + $text =~ s/\$n(?=[^[:alpha:]]|$)/\n/gs; + + # filler, useful for nested search + $text =~ s/\$nop(\(\))?//gs; + + # $quot -> " + $text =~ s/\$quot(\(\))?/\"/gs; + + # $comma -> , + $text =~ s/\$comma(\(\))?/,/gs; + + # $percent -> % + $text =~ s/\$perce?nt(\(\))?/\%/gs; + + # $lt -> < + $text =~ s/\$lt(\(\))?/\ > + $text =~ s/\$gt(\(\))?/\>/gs; + + # $amp -> & + $text =~ s/\$amp(\(\))?/\&/gs; + + # $dollar -> $, done last to avoid creating the above tokens + $text =~ s/\$dollar(\(\))?/\$/gs; + + return $text; +} + +=begin TML +---++ ObjectMethod exists($macro) -> boolean + +Returns true if =$macro= is a registered macro. + +=cut + +sub exists { + my $this = shift; + my ($macro) = @_; + return defined $this->_macros->{$macro}; +} + +=begin TML + ---++ ObjectMethod innerExpandMacros(\$text, $topicObject) Expands variables by replacing the variables with their values. Some example variables: %TOPIC%, %SCRIPTURL%, @@ -460,6 +519,10 @@ sub _processMacros { $stackTop .= $this->_processMacros( $e, $tagf, $topicObject, $depth - 1 ); + ASSERT( + $stackTop !~ /Foswiki::Macr/, + "Foswiki::Macros for $tag" + ); } else { @@ -546,6 +609,7 @@ round out the spec. =cut sub parseSections { + my $this = shift; my $text = shift; @@ -671,7 +735,7 @@ sub _expandMacroOnTopicRendering { } my $val = $attrs->{$tag}; $val = $tattrs->{default} unless defined $val; - return Foswiki::expandStandardEscapes($val) if defined $val; + return $this->expandStandardEscapes($val) if defined $val; return undef; }, $topicObject, @@ -724,7 +788,7 @@ sub _expandMacroOnTopicRendering { # in the absence of any definition. my $attrs = new Foswiki::Attrs($args); if ( defined $attrs->{default} ) { - $e = Foswiki::expandStandardEscapes( $attrs->{default} ); + $e = $this->expandStandardEscapes( $attrs->{default} ); } } return $e; @@ -767,7 +831,7 @@ sub _registerDefaultMacros { # deprecated, use ADDTOZONE instead ADDTOZONE => undef, - ALLVARIABLES => sub { $_[0]->prefs->stringify() }, + ALLVARIABLES => sub { $_[0]->app->prefs->stringify() }, ATTACHURL => undef, ATTACHURLPATH => undef, CHARSET => sub { 'utf-8' }, @@ -791,7 +855,7 @@ sub _registerDefaultMacros { FORMAT => undef, FORMFIELD => undef, FOSWIKI_BROADCAST => - sub { $_[0]->system_message || $Foswiki::system_message || '' }, + sub { $_[0]->app->system_message || $Foswiki::system_message || '' }, GMTIME => sub { Foswiki::Time::formatTime( time(), $_[1]->{_DEFAULT} || '', 'gmtime' ); @@ -801,7 +865,7 @@ sub _registerDefaultMacros { HTTP_HOST => #deprecated functionality, now implemented using %ENV% - sub { $_[0]->request->header('Host') || '' }, + sub { $_[0]->app->request->header('Host') || '' }, HTTP => undef, HTTPS => undef, ICON => undef, @@ -822,7 +886,7 @@ sub _registerDefaultMacros { } return $lang; }, - LANGUAGE => sub { $_[0]->i18n->language(); }, + LANGUAGE => sub { $_[0]->app->i18n->language(); }, LANGUAGES => undef, MAKETEXT => undef, META => undef, # deprecated @@ -836,14 +900,14 @@ sub _registerDefaultMacros { # topics used as templates for new topics) sub { $_[1]->{_RAW} ? $_[1]->{_RAW} : '' }, PLUGINVERSION => sub { - $_[0]->plugins->getPluginVersion( $_[1]->{_DEFAULT} ); + $_[0]->app->plugins->getPluginVersion( $_[1]->{_DEFAULT} ); }, PUBURL => undef, PUBURLPATH => undef, QUERY => undef, QUERYPARAMS => undef, QUERYSTRING => sub { - my $s = $_[0]->request->queryString(); + my $s = $_[0]->app->request->queryString(); # Aggressively encode QUERYSTRING (even more than the # default) because it might be leveraged for XSS @@ -855,7 +919,7 @@ sub _registerDefaultMacros { # DEPRECATED, now implemented using %ENV% #move to compatibility plugin in Foswiki 2.0 - sub { $_[0]->request->remoteAddress() || ''; }, + sub { $_[0]->app->request->remoteAddress() || ''; }, REMOTE_PORT => # DEPRECATED @@ -867,19 +931,19 @@ sub _registerDefaultMacros { REMOTE_USER => # DEPRECATED - sub { $_[0]->request->remoteUser() || '' }, + sub { $_[0]->app->request->remoteUser() || '' }, RENDERZONE => undef, REVINFO => undef, REVTITLE => undef, REVARG => undef, - SCRIPTNAME => sub { $_[0]->request->action() }, + SCRIPTNAME => sub { $_[0]->app->request->action() }, SCRIPTURL => undef, SCRIPTURLPATH => undef, SEARCH => undef, SEP => # Shortcut to %TMPL:P{"sep"}% - sub { $_[0]->templates->expandTemplate('sep') }, + sub { $_[0]->app->templates->expandTemplate('sep') }, SERVERTIME => sub { Foswiki::Time::formatTime( time(), $_[1]->{_DEFAULT} || '', 'servertime' ); @@ -889,7 +953,7 @@ sub _registerDefaultMacros { SHOWPREFERENCE => undef, SPACEDTOPIC => undef, SPACEOUT => undef, - 'TMPL:P' => sub { $_[0]->templates->tmplP( $_[1] ) }, + 'TMPL:P' => sub { $_[0]->app->templates->tmplP( $_[1] ) }, TOPICLIST => undef, URLENCODE => undef, URLPARAM => undef, diff --git a/core/lib/Foswiki/Macros/ADDTOZONE.pm b/core/lib/Foswiki/Macros/ADDTOZONE.pm index a72be40ff9..8a598626fe 100644 --- a/core/lib/Foswiki/Macros/ADDTOZONE.pm +++ b/core/lib/Foswiki/Macros/ADDTOZONE.pm @@ -40,7 +40,7 @@ sub ADDTOZONE { #print STDERR "WARNING: ADDTOZONE was called for zone 'body' ... rerouting it to zone 'script' ... please fix your templates\n"; $zone = 'script'; } - $this->zones()->addToZone( $zone, $id, $text, $requires ); + $this->app->zones->addToZone( $zone, $id, $text, $requires ); } return (DEBUG) ? "" : ''; diff --git a/core/lib/Foswiki/Macros/DISPLAYDEPENDENCIES.pm b/core/lib/Foswiki/Macros/DISPLAYDEPENDENCIES.pm index 4e103f06b4..a6fe4cebe6 100644 --- a/core/lib/Foswiki/Macros/DISPLAYDEPENDENCIES.pm +++ b/core/lib/Foswiki/Macros/DISPLAYDEPENDENCIES.pm @@ -39,7 +39,7 @@ sub DISPLAYDEPENDENCIES { push @lines, $text; } return '' unless @lines; - return expandStandardEscapes( + return $this->expandStandardEscapes( $header . join( $separator, @lines ) . $footer ); } diff --git a/core/lib/Foswiki/Macros/ENCODE.pm b/core/lib/Foswiki/Macros/ENCODE.pm index 69e583fbb4..a56692bcfe 100644 --- a/core/lib/Foswiki/Macros/ENCODE.pm +++ b/core/lib/Foswiki/Macros/ENCODE.pm @@ -52,12 +52,12 @@ sub ENCODE { return $this->inlineAlert( 'alerts', 'ENCODE_bad_3', $o ); } $toks{$o} = 1; - $o = quotemeta( expandStandardEscapes($o) ); + $o = quotemeta( $this->expandStandardEscapes($o) ); $text =~ s/$o/$e/ge; } for ( my $i = 0 ; $i <= $#new ; $i++ ) { my $e = _s2d($i); - my $n = expandStandardEscapes( $new[$i] ); + my $n = $this->expandStandardEscapes( $new[$i] ); $text =~ s/$e/$n/g; } return $text; @@ -85,7 +85,7 @@ sub ENCODE { # because I can't see any situation in which it might have been # used in anger. # $text =~ s/\r*\n\r*/
/; - return urlEncode($text); + return Foswiki::urlEncode($text); } elsif ( $type =~ m/^(off|none)$/i ) { diff --git a/core/lib/Foswiki/Macros/EXPAND.pm b/core/lib/Foswiki/Macros/EXPAND.pm index 146917df3c..55fc2bfa9c 100644 --- a/core/lib/Foswiki/Macros/EXPAND.pm +++ b/core/lib/Foswiki/Macros/EXPAND.pm @@ -16,7 +16,7 @@ sub EXPAND { my $macro = $params->{_DEFAULT}; return $this->inlineAlert( 'alerts', 'EXPAND_nomacro' ) unless $macro; - $macro = expandStandardEscapes($macro); + $macro = $this->expandStandardEscapes($macro); my $scope = $params->{scope}; my $meta; if ($scope) { diff --git a/core/lib/Foswiki/Macros/FORMAT.pm b/core/lib/Foswiki/Macros/FORMAT.pm index baeeef8ecb..57222db097 100644 --- a/core/lib/Foswiki/Macros/FORMAT.pm +++ b/core/lib/Foswiki/Macros/FORMAT.pm @@ -27,8 +27,7 @@ sub FORMAT { # separator is not defined. FORMAT is a new feature in 1.1 and does # not need the backward compatibility that SEARCH needed. $params->{separator} = '$n' unless ( defined( $params->{separator} ) ); - $params->{separator} = - Foswiki::expandStandardEscapes( $params->{separator} ); + $params->{separator} = $this->expandStandardEscapes( $params->{separator} ); my $type = $params->{type} || 'topic'; $type = 'topic' @@ -73,7 +72,7 @@ sub FORMAT { } my ( $ttopics, $searchResult, $tmplTail ) = $this->search->formatResults( undef, $listIterator, $params ); - $s = Foswiki::expandStandardEscapes($searchResult); + $s = $this->expandStandardEscapes($searchResult); } catch { my $e = $_; diff --git a/core/lib/Foswiki/Macros/FORMFIELD.pm b/core/lib/Foswiki/Macros/FORMFIELD.pm index 11b7860679..05737d6294 100644 --- a/core/lib/Foswiki/Macros/FORMFIELD.pm +++ b/core/lib/Foswiki/Macros/FORMFIELD.pm @@ -127,7 +127,7 @@ sub expand { $text =~ s/\$form(name)?/$fname/g; } - $text = Foswiki::expandStandardEscapes($text); + $text = $app->macros->expandStandardEscapes($text); # render nop exclamation marks before words as $text =~ s/!($Foswiki::regex{wikiWordRegex})/$1/gs; diff --git a/core/lib/Foswiki/Macros/GROUPINFO.pm b/core/lib/Foswiki/Macros/GROUPINFO.pm index 87ac949bea..019217473b 100644 --- a/core/lib/Foswiki/Macros/GROUPINFO.pm +++ b/core/lib/Foswiki/Macros/GROUPINFO.pm @@ -137,7 +137,7 @@ s/\$allowschange\((.*?)\)/$this->users->groupAllowsChange( $cUID , $this->users- else { $result = $header . join( $sep, @rows ) . $footer; } - return expandStandardEscapes($result); + return $this->expandStandardEscapes($result); } 1; diff --git a/core/lib/Foswiki/Macros/ICON.pm b/core/lib/Foswiki/Macros/ICON.pm index 6564a472d7..276fef732e 100644 --- a/core/lib/Foswiki/Macros/ICON.pm +++ b/core/lib/Foswiki/Macros/ICON.pm @@ -22,11 +22,12 @@ has ICONSPACE => ( lazy => 1, isa => Foswiki::Object::isaCLASS( 'ICONSPACE', 'Foswiki::Meta' ), default => sub { + my $this = shift; # SMELL Behaviour change! Before Moo-fication _lookupIcon was trying to # initialize ICONSPACE on each call. But it is likely that this # behaviour was simple waste of CPU. - my $app = $_[0]->app; + my $app = $this->app; my $iconTopic = $app->prefs->getPreference('ICONTOPIC'); if ( defined($iconTopic) ) { $iconTopic =~ s/\s+$//; diff --git a/core/lib/Foswiki/Macros/ICONURL.pm b/core/lib/Foswiki/Macros/ICONURL.pm index da82f2368e..c5c81c5525 100644 --- a/core/lib/Foswiki/Macros/ICONURL.pm +++ b/core/lib/Foswiki/Macros/ICONURL.pm @@ -24,7 +24,7 @@ ICONURL macro implementation sub ICONURL { my ( $this, $params ) = @_; $params->{absolute} = 1; - return Foswiki::Macros::ICON->new( session => $this )->_getIconURL($params); + return $this->create('Foswiki::Macros::ICON')->_getIconURL($params); } 1; diff --git a/core/lib/Foswiki/Macros/IF.pm b/core/lib/Foswiki/Macros/IF.pm index 35d47e973b..5a8784d394 100644 --- a/core/lib/Foswiki/Macros/IF.pm +++ b/core/lib/Foswiki/Macros/IF.pm @@ -50,11 +50,11 @@ sub expand { $expr = $this->ifParser->parse($texpr); if ( $expr->evaluate( tom => $topicObject, data => $topicObject ) ) { $params->{then} = '' unless defined $params->{then}; - $result = Foswiki::expandStandardEscapes( $params->{then} ); + $result = $app->macros->expandStandardEscapes( $params->{then} ); } else { $params->{else} = '' unless defined $params->{else}; - $result = Foswiki::expandStandardEscapes( $params->{else} ); + $result = $app->macros->expandStandardEscapes( $params->{else} ); } } catch { diff --git a/core/lib/Foswiki/Macros/INCLUDE.pm b/core/lib/Foswiki/Macros/INCLUDE.pm index 6549fa603b..324b7b2272 100644 --- a/core/lib/Foswiki/Macros/INCLUDE.pm +++ b/core/lib/Foswiki/Macros/INCLUDE.pm @@ -118,7 +118,7 @@ sub _includeWarning { my $message = shift; if ( $warn eq 'on' ) { - return $this->session->inlineAlert( 'alerts', $message, @_ ); + return $this->app->inlineAlert( 'alerts', $message, @_ ); } elsif ( Foswiki::isTrue($warn) ) { @@ -132,7 +132,7 @@ sub _includeWarning { $argument = shift; } $warn =~ s/\$topic/$argument/g if $argument; - return Foswiki::expandStandardEscapes($warn); + return $this->app->macros->expandStandardEscapes($warn); } # else fail silently return ''; } @@ -147,21 +147,22 @@ sub _includeProtocol { } else { $handler = 'Foswiki::IncludeHandlers::' . $handler; - return $handler->INCLUDE( $this->session, $control, $params ); + return $handler->INCLUDE( $this->app, $control, $params ); } } sub _includeTopic { my ( $this, $includingTopicObject, $control, $params ) = @_; - my $session = $this->session; + my $app = $this->app; + my $req = $app->request; my $includedWeb; my $includedTopic = $control->{_DEFAULT}; $includedTopic =~ s/\.txt$//; # strip optional (undocumented) .txt ( $includedWeb, $includedTopic ) = - $session->normalizeWebTopicName( $includingTopicObject->web, + $app->request->normalizeWebTopicName( $includingTopicObject->web, $includedTopic ); if ( !Foswiki::isValidTopicName( $includedTopic, 1 ) ) { @@ -172,7 +173,7 @@ sub _includeTopic { } # See Codev.FailedIncludeWarning for the history. - unless ( $session->store->topicExists( $includedWeb, $includedTopic ) ) { + unless ( $app->store->topicExists( $includedWeb, $includedTopic ) ) { return _includeWarning( $this, $control->{warn}, 'topic_not_found', $includedWeb, $includedTopic ), 'topic_not_found'; @@ -193,23 +194,23 @@ sub _includeTopic { # Push the topic context to the included topic, so we can create # local (SESSION) macro definitions without polluting the including # topic namespace. - $session->prefs->pushTopicContext( $session->webName, $session->topicName ); + $app->prefs->pushTopicContext( $req->web, $req->topic ); $this->_INCLUDES->{$key} = 1; my $includedTopicObject = - Foswiki::Meta->load( $session, $includedWeb, $includedTopic, + Foswiki::Meta->load( $app, $includedWeb, $includedTopic, $control->{rev} ); unless ( $includedTopicObject->haveAccess('VIEW') ) { if ( Foswiki::isTrue( $control->{warn} ) ) { - return $session->inlineAlert( 'alerts', 'access_denied', + return $app->inlineAlert( 'alerts', 'access_denied', "[[$includedWeb.$includedTopic]]" ), 'access_denied'; } # else fail silently return '', 'access_denied'; } - my $memWeb = $session->prefs->getPreference('INCLUDINGWEB'); - my $memTopic = $session->prefs->getPreference('INCLUDINGTOPIC'); + my $memWeb = $app->prefs->getPreference('INCLUDINGWEB'); + my $memTopic = $app->prefs->getPreference('INCLUDINGTOPIC'); my $text = ''; my $error = ''; @@ -217,13 +218,13 @@ sub _includeTopic { my $dirtyAreas = {}; try { - # Copy params into session level preferences. That way finalisation + # Copy params into app level preferences. That way finalisation # will apply to them. These preferences will be popped when the topic # context is restored after the include. - $session->prefs->setSessionPreferences(%$params); + $app->prefs->setSessionPreferences(%$params); # Set preferences that finalisation does *not* apply to - $session->prefs->setInternalPreferences( + $app->prefs->setInternalPreferences( INCLUDINGWEB => $includingTopicObject->web, INCLUDINGTOPIC => $includingTopicObject->topic ); @@ -247,7 +248,7 @@ sub _includeTopic { if $Foswiki::cfg{Cache}{Enabled}; # handle sections - my ( $ntext, $sections ) = Foswiki::parseSections($text); + my ( $ntext, $sections ) = $app->macros->parseSections($text); my $interesting = ( defined $control->{section} ); if ( $interesting || scalar(@$sections) ) { @@ -283,10 +284,10 @@ sub _includeTopic { || ( $key eq 'stop' ) ); #don't over-ride existing INCLUDE params and settings (so that nested INCLUDEs pass on their values as they used to), and to avoid FINALISE issues - next if ( $session->prefs->getPreference($key) ); + next if ( $app->prefs->getPreference($key) ); $defaults{$key} = $s->{$key}; } - $session->prefs->setSessionPreferences(%defaults); + $app->prefs->setSessionPreferences(%defaults); #we only process the first named section last if ( $control->{section} ); @@ -313,38 +314,38 @@ sub _includeTopic { # preference has been set if ( Foswiki::isTrue( - $session->prefs->getPreference('TOC_HIDE_IF_INCLUDED') + $app->prefs->getPreference('TOC_HIDE_IF_INCLUDED') ) ) { $text =~ s/%TOC(?:{(.*?)})?%//g; } - $session->innerExpandMacros( \$text, $includedTopicObject ); + $app->macros->innerExpandMacros( \$text, $includedTopicObject ); # Item9569: remove verbatim blocks from text passed to # commonTagsHandler $text = Foswiki::takeOutBlocks( $text, 'verbatim', $verbatim ); # 4th parameter tells plugin that its called for an included file - $session->plugins->dispatch( 'commonTagsHandler', $text, + $app->plugins->dispatch( 'commonTagsHandler', $text, $includedTopic, $includedWeb, 1, $includedTopicObject ); Foswiki::putBackBlocks( \$text, $verbatim, 'verbatim' ); # We have to expand tags again, because a plugin may have inserted # additional tags. - $session->innerExpandMacros( \$text, $includedTopicObject ); + $app->macros->innerExpandMacros( \$text, $includedTopicObject ); # If needed, fix all 'TopicNames' to 'Web.TopicNames' to get the # right context so that links continue to work properly if ( $includedWeb ne $includingTopicObject->web ) { - my $noautolink = Foswiki::isTrue( - $session->prefs->getPreference('NOAUTOLINK') ); + my $noautolink = + Foswiki::isTrue( $app->prefs->getPreference('NOAUTOLINK') ); # pre and noautolink parms are used by Foswiki::Render to determine # whether or not to process those blocks. # Pref_NOAUTOLINK passes the preference setting to _fixupIncludedTopic - $text = $session->renderer->forEachLine( + $text = $app->renderer->forEachLine( $text, \&_fixupIncludedTopic, { @@ -356,7 +357,7 @@ sub _includeTopic { ); # handle tags again because of plugin hook - $session->innerExpandMacros( \$text, $includedTopicObject ); + $app->macros->innerExpandMacros( \$text, $includedTopicObject ); } } } @@ -376,7 +377,7 @@ sub _includeTopic { # always restore the context, even in the event of an error delete $this->_INCLUDES->{$key}; - $session->prefs->setInternalPreferences( + $app->prefs->setInternalPreferences( INCLUDINGWEB => $memWeb, INCLUDINGTOPIC => $memTopic ); @@ -385,9 +386,9 @@ sub _includeTopic { Foswiki::putBackBlocks( \$text, $dirtyAreas, 'dirtyarea' ) if $Foswiki::cfg{Cache}{Enabled}; - my @context = $session->prefs->popTopicContext(); - $session->webName( $context[0] ); - $session->topicName( $context[1] ); + my @context = $app->prefs->popTopicContext(); + $req->web( $context[0] ); + $req->topic( $context[1] ); }; @@ -401,7 +402,7 @@ sub _includeTopic { sub expand { my ( $this, $params, $includingTopicObject ) = @_; - my $session = $this->session; + my $app = $this->app; # remember args for the key before mangling the params my $args = $params->stringify(); @@ -413,7 +414,7 @@ sub expand { } $control{_sArgs} = $args; - $control{warn} ||= $session->prefs->getPreference('INCLUDEWARNING'); + $control{warn} ||= $app->prefs->getPreference('INCLUDEWARNING'); my $text; diff --git a/core/lib/Foswiki/Macros/LANGUAGES.pm b/core/lib/Foswiki/Macros/LANGUAGES.pm index 941469dde7..9901ce04b2 100644 --- a/core/lib/Foswiki/Macros/LANGUAGES.pm +++ b/core/lib/Foswiki/Macros/LANGUAGES.pm @@ -39,7 +39,7 @@ sub LANGUAGES { $result .= $item; $i++; } - $result = Foswiki::expandStandardEscapes($result); + $result = $this->expandStandardEscapes($result); return $result; } diff --git a/core/lib/Foswiki/Macros/MAKETEXT.pm b/core/lib/Foswiki/Macros/MAKETEXT.pm index 002cb2a3ec..2cfea01f9a 100644 --- a/core/lib/Foswiki/Macros/MAKETEXT.pm +++ b/core/lib/Foswiki/Macros/MAKETEXT.pm @@ -54,7 +54,7 @@ s/~\[(\*,\_(\d+),[^,]+(,([^,]+))?)~\]/ _validate($1, $2, $max, $min, $param_erro } # do the magic: - my $result = $this->i18n->maketext( $str, @args ); + my $result = $this->app->i18n->maketext( $str, @args ); # replace accesskeys: $result =~ diff --git a/core/lib/Foswiki/Macros/META.pm b/core/lib/Foswiki/Macros/META.pm index c7c65c2ec9..2e42f71d8d 100644 --- a/core/lib/Foswiki/Macros/META.pm +++ b/core/lib/Foswiki/Macros/META.pm @@ -4,8 +4,10 @@ package Foswiki::Macros; use strict; use warnings; -use Foswiki::Meta (); -use Foswiki::Func (); +use Foswiki::Render::Moved (); +use Foswiki::Render::Parent (); +use Foswiki::Meta (); +use Foswiki::Func (); BEGIN { if ( $Foswiki::cfg{UseLocale} ) { @@ -20,6 +22,8 @@ BEGIN { sub META { my ( $this, $params, $topicObject ) = @_; + my $app = $this->app; + my $option = $params->{_DEFAULT} || ''; if ( defined( $params->{topic} ) ) { my ( $nweb, $ntopic ) = @@ -54,17 +58,15 @@ sub META { elsif ( $option eq 'attachments' ) { # renders attachment tables - return $this->attach->renderMetaData( $topicObject, $params ); + return $app->attach->renderMetaData( $topicObject, $params ); } elsif ( $option eq 'moved' ) { - require Foswiki::Render::Moved; return Foswiki::Render::Moved::render( $this, $topicObject, $params ); } elsif ( $option eq 'parent' ) { # Only parent parameter has the format option and should do std escapes - require Foswiki::Render::Parent; - return expandStandardEscapes( + return $this->expandStandardEscapes( Foswiki::Render::Parent::render( $this, $topicObject, $params ) ); } diff --git a/core/lib/Foswiki/Macros/QUERYPARAMS.pm b/core/lib/Foswiki/Macros/QUERYPARAMS.pm index aa703602a4..4976464c18 100644 --- a/core/lib/Foswiki/Macros/QUERYPARAMS.pm +++ b/core/lib/Foswiki/Macros/QUERYPARAMS.pm @@ -29,8 +29,8 @@ sub QUERYPARAMS { # Expand standard escapes early. We must not expand escapes contained # in the param data. - $format = Foswiki::expandStandardEscapes($format); - $separator = Foswiki::expandStandardEscapes($separator); + $format = $this->expandStandardEscapes($format); + $separator = $this->expandStandardEscapes($separator); my @list; foreach my $name ( $this->request->multi_param() ) { diff --git a/core/lib/Foswiki/Macros/RENDERZONE.pm b/core/lib/Foswiki/Macros/RENDERZONE.pm index f4b97ca72f..b25c0bbfbf 100644 --- a/core/lib/Foswiki/Macros/RENDERZONE.pm +++ b/core/lib/Foswiki/Macros/RENDERZONE.pm @@ -14,7 +14,7 @@ BEGIN { sub RENDERZONE { my ( $this, $params, $topicObject ) = @_; - my $zones = $this->zones(); + my $zones = $this->app->zones; # Note, that RENDERZONE is not expanded as soon as this function is called. # Instead, a placeholder is inserted into the page. Rendering the current diff --git a/core/lib/Foswiki/Macros/REVINFO.pm b/core/lib/Foswiki/Macros/REVINFO.pm index 093d069e75..820ff5b61c 100644 --- a/core/lib/Foswiki/Macros/REVINFO.pm +++ b/core/lib/Foswiki/Macros/REVINFO.pm @@ -19,12 +19,13 @@ sub REVINFO { my $format = $params->{_DEFAULT} || $params->{format}; my $web = $params->{web} || $topicObject->web; my $topic = $params->{topic} || $topicObject->topic; - my $cgiQuery = $this->request; + my $app = $this->app; + my $cgiQuery = $app->request; my $cgiRev = ''; $cgiRev = $cgiQuery->param('rev') if ($cgiQuery); my $rev = Foswiki::Store::cleanUpRevID( $params->{rev} || $cgiRev || '' ); - ( $web, $topic ) = $this->normalizeWebTopicName( $web, $topic ); + ( $web, $topic ) = $app->request->normalizeWebTopicName( $web, $topic ); my $loadedRev = $topicObject->getLoadedRev(); if ( $web ne $topicObject->web || $topic ne $topicObject->topic @@ -32,18 +33,17 @@ sub REVINFO { || $loadedRev ne $rev ) { $topicObject = - Foswiki::Meta->new( session => $this, web => $web, topic => $topic ); + Foswiki::Meta->new( app => $app, web => $web, topic => $topic ); # haveAccess will try to load the object on the fly, so make sure # it is loaded if rev is defined $topicObject = $topicObject->load($rev) if ($rev); unless ( $topicObject->haveAccess('VIEW') ) { - return $this->inlineAlert( 'alerts', 'access_denied', $web, - $topic ); + return $app->inlineAlert( 'alerts', 'access_denied', $web, $topic ); } } - return $this->renderer->renderRevisionInfo( $topicObject, $rev, $format ); + return $app->renderer->renderRevisionInfo( $topicObject, $rev, $format ); } 1; diff --git a/core/lib/Foswiki/Macros/SCRIPTURL.pm b/core/lib/Foswiki/Macros/SCRIPTURL.pm index 9d543073e6..1b5de78c30 100644 --- a/core/lib/Foswiki/Macros/SCRIPTURL.pm +++ b/core/lib/Foswiki/Macros/SCRIPTURL.pm @@ -27,7 +27,8 @@ sub SCRIPTURL { $topic = pop(@path) if scalar(@path); $web = join( '/', @path ) if scalar(@path); # web= is ignored } - return $this->getScriptUrl( !$relative, $script, $web, $topic, @p ); + return $this->app->cfg->getScriptUrl( !$relative, $script, $web, $topic, + @p ); } 1; diff --git a/core/lib/Foswiki/Macros/SEARCH.pm b/core/lib/Foswiki/Macros/SEARCH.pm index 7967034a3c..35e7ec0de2 100644 --- a/core/lib/Foswiki/Macros/SEARCH.pm +++ b/core/lib/Foswiki/Macros/SEARCH.pm @@ -27,13 +27,12 @@ sub SEARCH { #TODO: this is a common default that should be extracted into a 'test, default and refine' parameters for all formatResult calls if ( defined( $params->{separator} ) ) { $params->{separator} = - Foswiki::expandStandardEscapes( $params->{separator} ); + $this->expandStandardEscapes( $params->{separator} ); } # newline feature replaces newlines within each search result if ( defined( $params->{newline} ) ) { - $params->{newline} = - Foswiki::expandStandardEscapes( $params->{newline} ); + $params->{newline} = $this->expandStandardEscapes( $params->{newline} ); } my $s; diff --git a/core/lib/Foswiki/Macros/SPACEOUT.pm b/core/lib/Foswiki/Macros/SPACEOUT.pm index 6b72846a69..c7a655aad3 100644 --- a/core/lib/Foswiki/Macros/SPACEOUT.pm +++ b/core/lib/Foswiki/Macros/SPACEOUT.pm @@ -15,7 +15,7 @@ sub SPACEOUT { my ( $this, $params ) = @_; my $spaceOutTopic = $params->{_DEFAULT}; my $sep = $params->{'separator'}; - $spaceOutTopic = spaceOutWikiWord( $spaceOutTopic, $sep ); + $spaceOutTopic = Foswiki::spaceOutWikiWord( $spaceOutTopic, $sep ); return $spaceOutTopic; } diff --git a/core/lib/Foswiki/Macros/TOPICLIST.pm b/core/lib/Foswiki/Macros/TOPICLIST.pm index 47408035f4..1b060d8caf 100644 --- a/core/lib/Foswiki/Macros/TOPICLIST.pm +++ b/core/lib/Foswiki/Macros/TOPICLIST.pm @@ -50,7 +50,7 @@ sub TOPICLIST { $line =~ s/\$qname/"$item"/g; # Undocumented, DO NOT REMOVE my $mark = ( $selection =~ m/ \Q$item\E / ) ? $marker : ''; $line =~ s/\$marker/$mark/g; - $line = expandStandardEscapes($line); + $line = $this->expandStandardEscapes($line); push( @items, $line ); } return join( $separator, @items ); diff --git a/core/lib/Foswiki/Macros/URLPARAM.pm b/core/lib/Foswiki/Macros/URLPARAM.pm index a6e76bdff4..d1b234dad4 100644 --- a/core/lib/Foswiki/Macros/URLPARAM.pm +++ b/core/lib/Foswiki/Macros/URLPARAM.pm @@ -35,7 +35,7 @@ sub URLPARAM { $item = $_; $_ = $multiple; $_ .= $item unless (s/\$item/$item/g); - expandStandardEscapes($_) + $this->expandStandardEscapes($_) } @valueArray; } diff --git a/core/lib/Foswiki/Macros/USERINFO.pm b/core/lib/Foswiki/Macros/USERINFO.pm index 7236ca03b8..673bdbce0e 100644 --- a/core/lib/Foswiki/Macros/USERINFO.pm +++ b/core/lib/Foswiki/Macros/USERINFO.pm @@ -130,7 +130,7 @@ sub USERINFO { return '' unless $user; $info =~ s/\$($USERINFO_tokenregex)/$this->_USERINFO_token($1, $user)/ge; - $info = Foswiki::expandStandardEscapes($info); + $info = $this->expandStandardEscapes($info); return $info; } diff --git a/core/lib/Foswiki/Macros/USERNAME.pm b/core/lib/Foswiki/Macros/USERNAME.pm index 167c9d1fd1..9d049e7854 100644 --- a/core/lib/Foswiki/Macros/USERNAME.pm +++ b/core/lib/Foswiki/Macros/USERNAME.pm @@ -17,7 +17,7 @@ BEGIN { sub USERNAME { my ( $this, $params ) = @_; - $params->{format} = $this->{prefs}->getPreference('USERNAME') + $params->{format} = $this->app->prefs->getPreference('USERNAME') || '$username'; return $this->USERINFO($params); diff --git a/core/lib/Foswiki/Macros/VAR.pm b/core/lib/Foswiki/Macros/VAR.pm index c90a118886..e65bc8f7e4 100644 --- a/core/lib/Foswiki/Macros/VAR.pm +++ b/core/lib/Foswiki/Macros/VAR.pm @@ -19,9 +19,10 @@ sub VAR { my $topic = $topicObject->topic; # handle %USERSWEB%-type cases - ( $web, $topic ) = $this->normalizeWebTopicName( $web, $topic ); + ( $web, $topic ) = + $this->app->request->normalizeWebTopicName( $web, $topic ); - my $webObject = Foswiki::Meta->new( session => $this, web => $web ); + my $webObject = $this->create( 'Foswiki::Meta', web => $web ); # always return a value, even when the key isn't defined return $webObject->getPreference($key) || ''; diff --git a/core/lib/Foswiki/Macros/WEBLIST.pm b/core/lib/Foswiki/Macros/WEBLIST.pm index 26f6af99aa..02d59da4b4 100644 --- a/core/lib/Foswiki/Macros/WEBLIST.pm +++ b/core/lib/Foswiki/Macros/WEBLIST.pm @@ -30,7 +30,7 @@ sub WEBLIST { $format ||= '$name'; my $separator = $params->{separator} || "\n"; - $separator = Foswiki::expandStandardEscapes($separator); + $separator = $this->expandStandardEscapes($separator); my $selection = $params->{selection} || ''; $selection =~ s/\,/ /g; @@ -49,7 +49,7 @@ sub WEBLIST { elsif ( $aweb eq 'webtemplate' ) { $filter = new Foswiki::WebFilter('template,allowed'); } - push( @list, $this->deepWebList( $filter, $rootWeb ) ); + push( @list, $this->app->deepWebList( $filter, $rootWeb ) ); } else { push( @list, $aweb ) if ( $this->webExists($aweb) ); @@ -68,7 +68,7 @@ sub WEBLIST { $line =~ s/\$indentedname/$indenteditem/g; my $mark = ( $selection =~ m/ \Q$item\E / ) ? $marker : ''; $line =~ s/\$marker/$mark/g; - $line = Foswiki::expandStandardEscapes($line); + $line = $this->expandStandardEscapes($line); push( @items, $line ); } return join( $separator, @items ); diff --git a/core/lib/Foswiki/Macros/WIKINAME.pm b/core/lib/Foswiki/Macros/WIKINAME.pm index 0f716e759c..abb3a71cd0 100644 --- a/core/lib/Foswiki/Macros/WIKINAME.pm +++ b/core/lib/Foswiki/Macros/WIKINAME.pm @@ -16,7 +16,7 @@ BEGIN { sub WIKINAME { my ( $this, $params ) = @_; - $params->{format} = $this->prefs->getPreference('WIKINAME') + $params->{format} = $this->app->prefs->getPreference('WIKINAME') || '$wikiname'; return $this->USERINFO($params); diff --git a/core/lib/Foswiki/Macros/WIKIUSERNAME.pm b/core/lib/Foswiki/Macros/WIKIUSERNAME.pm index 14d60e12a4..cf0234b17d 100644 --- a/core/lib/Foswiki/Macros/WIKIUSERNAME.pm +++ b/core/lib/Foswiki/Macros/WIKIUSERNAME.pm @@ -16,7 +16,7 @@ BEGIN { sub WIKIUSERNAME { my ( $this, $params ) = @_; - $params->{format} = $this->{prefs}->getPreference('WIKIUSERNAME') + $params->{format} = $this->app->prefs->getPreference('WIKIUSERNAME') || '$wikiusername'; return $this->USERINFO($params); diff --git a/core/lib/Foswiki/Render.pm b/core/lib/Foswiki/Render.pm index 1630f3c683..3861424bd2 100644 --- a/core/lib/Foswiki/Render.pm +++ b/core/lib/Foswiki/Render.pm @@ -14,14 +14,14 @@ use Assert; use Try::Tiny; use CGI (); -use Foswiki (); +use Foswiki qw(urlEncode); use Foswiki::Time (); use Foswiki::Sandbox (); use Foswiki::Render::Anchors (); use Moo; use namespace::clean; -extends qw(Foswiki::Object); +extends qw(Foswiki::AppObject); BEGIN { if ( $Foswiki::cfg{UseLocale} ) { @@ -83,24 +83,18 @@ my %list_types = ( =begin TML ----++ ClassMethod new ($session) +---++ ClassMethod new (app => $app) Creates a new renderer =cut -has session => ( - is => 'ro', - weak_ref => 1, - isa => Foswiki::Object::isaCLASS( 'session', 'Foswiki', noUndef => 1, ), - required => 1, -); has NEWLINKFORMAT => ( is => 'rw', lazy => 1, clearer => 1, default => sub { - return $_[0]->session->prefs->getPreference('NEWLINKFORMAT') + return $_[0]->app->prefs->getPreference('NEWLINKFORMAT') || DEFAULT_NEWLINKFORMAT; }, ); @@ -111,7 +105,7 @@ has LINKTOOLTIPINFO => ( default => sub { # Add a tooltip, if it's enabled - my $lti = $_[0]->session->prefs->getPreference('LINKTOOLTIPINFO') || ''; + my $lti = $_[0]->app->prefs->getPreference('LINKTOOLTIPINFO') || ''; if ( $lti =~ m/^[Oo][Nn]$/ ) { $lti = '$username - $date - r$rev: $summary'; } @@ -167,6 +161,9 @@ sub internalLink { $hasExplicitLinkLabel, $params ) = @_; + my $req = $this->app->request; + my $cfg = $this->app->cfg; + # Webname/Subweb/ -> Webname/Subweb $web =~ s/\/\Z//; @@ -175,8 +172,8 @@ sub internalLink { } #WebHome links to tother webs render as the WebName - if ( ( $linkText eq $Foswiki::cfg{HomeTopicName} ) - && ( $web ne $this->session->webName ) ) + if ( ( $linkText eq $cfg->data->{HomeTopicName} ) + && ( $web ne $req->web ) ) { $linkText = $web; } @@ -190,7 +187,7 @@ sub internalLink { # should be rendered differently even if the topic author has used a # specific link label. $linkText = - $this->session->plugins->dispatch( 'renderWikiWordHandler', $linkText, + $this->app->plugins->dispatch( 'renderWikiWordHandler', $linkText, $hasExplicitLinkLabel, $web, $topic ) || $linkText; @@ -224,9 +221,9 @@ sub getRenderedVersion { return '' unless defined $text; # nothing to do - my $session = $this->session; - my $plugins = $session->plugins; - my $prefs = $session->prefs; + my $app = $this->app; + my $plugins = $app->plugins; + my $prefs = $app->prefs; $this->clear_LIST; @@ -593,7 +590,7 @@ qr/<[Tt][Ee][Xx][Tt][Aa][Rr][Ee][Aa]\b.*?<\/[Tt][Ee][Xx][Tt][Aa][Rr][Ee][Aa]>/s, $text = _adjustH($text); - $this->session->getLoginManager()->endRenderingHandler($text); + $this->app->users->getLoginManager()->endRenderingHandler($text); $plugins->dispatch( 'postRenderingHandler', $text ); return $text; @@ -688,14 +685,14 @@ sub TML2PlainText { if ( $opts =~ m/expandvar/ ) { $text =~ s/(\%)(SEARCH){/$1$2/g; # prevent recursion - $topicObject = Foswiki::Meta->new( session => $this->session ) + $topicObject = $this->creare('Foswiki::Meta') unless $topicObject; $text = $topicObject->expandMacros($text); } else { $text =~ s/%WEB%/$topicObject->web() || ''/ge; $text =~ s/%TOPIC%/$topicObject->topic() || ''/ge; - my $wtn = $this->session->prefs->getPreference('WIKITOOLNAME') + my $wtn = $this->app->prefs->getPreference('WIKITOOLNAME') || ''; $text =~ s/%WIKITOOLNAME%/$wtn/g; if ( $opts =~ m/showvar/ ) { @@ -784,10 +781,10 @@ sub protectPlainText { # DEPRECATED: retained for compatibility with various hack-job extensions sub makeTopicSummary { my ( $this, $text, $topic, $web, $flags ) = @_; - my $topicObject = Foswiki::Meta->new( - session => $this->session, - web => $web, - web => $topic + my $topicObject = $this->create( + 'Foswiki::Meta', + web => $web, + web => $topic ); return $topicObject->summariseText( '', $text ); } @@ -817,18 +814,18 @@ Obtain and render revision info for a topic. sub renderRevisionInfo { my ( $this, $topicObject, $rrev, $format ) = @_; my $value = $format || 'r$rev - $date - $time - $wikiusername'; - $value = Foswiki::expandStandardEscapes($value); + $value = $this->app->macros->expandStandardEscapes($value); # nop if there are no format tokens return $value unless $value =~ m/\$(?:year|ye|wikiusername|wikiname|week|we|web|wday|username|tz|topic|time|seconds|sec|rev|rcs|month|mo|minutes|min|longdate|isotz|iso|http|hours|hou|epoch|email|dow|day|date)/x; - my $users = $this->session->users; + my $users = $this->app->users; if ($rrev) { my $loadedRev = $topicObject->getLoadedRev() || 0; unless ( $rrev == $loadedRev ) { - $topicObject = Foswiki::Meta->new( session => $topicObject ); + $topicObject = $this->create('Foswiki::Meta'); $topicObject = $topicObject->load($rrev); } } @@ -1296,8 +1293,8 @@ sub _renderWikiWord { my ( $this, $web, $topic, $linkText, $anchor, $linkIfAbsent, $keepWebPrefix, $params ) = @_; - my $session = $this->session; - my $topicExists = $session->topicExists( $web, $topic ); + my $app = $this->app; + my $topicExists = $app->store->topicExists( $web, $topic ); my $singular = ''; unless ($topicExists) { @@ -1306,7 +1303,7 @@ sub _renderWikiWord { require Foswiki::Plurals; $singular = Foswiki::Plurals::singularForm( $web, $topic ); if ($singular) { - $topicExists = $session->topicExists( $web, $singular ); + $topicExists = $app->store->topicExists( $web, $singular ); $topic = $singular if $topicExists; } } @@ -1315,7 +1312,7 @@ sub _renderWikiWord { # add a dependency so that the page gets invalidated as soon as the # topic is deleted - $this->session->cache->addDependency( $web, $topic ) + $this->app->cache->addDependency( $web, $topic ) if $Foswiki::cfg{Cache}{Enabled}; return _renderExistingWikiWord( $this, $web, $topic, $linkText, $anchor, @@ -1331,7 +1328,7 @@ sub _renderWikiWord { # add a dependency so that the page gets invalidated as soon as the # WikiWord comes into existance # Note we *ignore* the params if the target topic does not exist - $this->session->cache->addDependency( $web, $topic ) + $this->app->cache->addDependency( $web, $topic ) if $Foswiki::cfg{Cache}{Enabled}; return _renderNonExistingWikiWord( $this, $web, $topic, $linkText ); @@ -1346,22 +1343,25 @@ sub _renderWikiWord { sub _renderExistingWikiWord { my ( $this, $web, $topic, $text, $anchor, $params ) = @_; + my $req = $this->app->request; + my $cfg = $this->app->cfg; + my @cssClasses; push( @cssClasses, 'foswikiCurrentWebHomeLink' ) - if ( ( $web eq $this->session->webName ) - && ( $topic eq $Foswiki::cfg{HomeTopicName} ) ); + if ( ( $web eq $req->web ) + && ( $topic eq $cfg->data->{HomeTopicName} ) ); my $inCurrentTopic = 0; - if ( ( $web eq $this->session->webName ) - && ( $topic eq $this->session->topicName ) ) + if ( ( $web eq $req->web ) + && ( $topic eq $req->topic ) ) { push( @cssClasses, 'foswikiCurrentTopicLink' ); $inCurrentTopic = 1; } my %attrs; - my $href = $this->session->getScriptUrl( 0, 'view', $web, $topic ); + my $href = $cfg->getScriptUrl( 0, 'view', $web, $topic ); if ($params) { $href .= $params; } @@ -1379,11 +1379,11 @@ sub _renderExistingWikiWord { $attrs{href} = $href; if ( defined $this->LINKTOOLTIPINFO - && $this->session->inContext('view') ) + && $this->app->inContext('view') ) { require Foswiki::Render::ToolTip; my $tooltip = - Foswiki::Render::ToolTip::render( $this->session, $web, $topic, + Foswiki::Render::ToolTip::render( $this->app, $web, $topic, $this->LINKTOOLTIPINFO ); $attrs{title} = $tooltip if $tooltip; } @@ -1406,10 +1406,11 @@ sub _renderNonExistingWikiWord { $ans =~ s/\$web/$web/g; $ans =~ s/\$topic/$topic/g; $ans =~ s/\$text/$text/g; - my $topicObject = Foswiki::Meta->new( - session => $this->session, - web => $this->session->webName, - topic => $this->session->topicName + my $req = $this->app->request; + my $topicObject = $this->create( + 'Foswiki::Meta', + web => $req->web, + topic => $req->topic, ); return $topicObject->expandMacros($ans); } @@ -1451,7 +1452,7 @@ sub _handleWikiWord { # 'Web.TopicName' or 'Web.ABBREV' link: if ( $topic eq $Foswiki::cfg{HomeTopicName} - && $web ne $this->session->webName ) + && $web ne $this->app->request->web ) { $text = $web; } @@ -1464,7 +1465,7 @@ sub _handleWikiWord { # Have to leave "web part" of ABR.ABR.ABR intact if topic not found $keepWeb = ( $topic =~ m/^$Foswiki::regex{abbrevRegex}$/ - && $web ne $this->session->webName ); + && $web ne $this->app->request->web ); # false means suppress link for non-existing pages $linkIfAbsent = ( $topic !~ /^$Foswiki::regex{abbrevRegex}$/ ); @@ -1576,7 +1577,7 @@ sub _handleSquareBracketedLink { # Topic defaults to the current topic my ( $web, $topic ) = - $this->session->normalizeWebTopicName( $topicObject->web, $link ); + $this->app->request->normalizeWebTopicName( $topicObject->web, $link ); return $this->internalLink( $web, $topic, $text, $anchor, 1, undef, $hasExplicitLinkLabel, $params ); diff --git a/core/lib/Foswiki/Render/IconImage.pm b/core/lib/Foswiki/Render/IconImage.pm index a37b001868..e2d7830186 100644 --- a/core/lib/Foswiki/Render/IconImage.pm +++ b/core/lib/Foswiki/Render/IconImage.pm @@ -14,7 +14,7 @@ BEGIN { =begin TML ----++ StaticMethod render($session, $url [, $alt]) -> $html +---++ StaticMethod render($app, $url [, $alt]) -> $html Generate the output for representing an 16x16 icon image. The source of the image is taken from =$url=. The optional =$alt= specifies an alt string. @@ -27,7 +27,7 @@ TODO: Sven's not sure this code belongs here - its only use appears to be the IC =cut sub render { - my ( $session, $url, $alt, $quote ) = @_; + my ( $app, $url, $alt, $quote ) = @_; if ( !defined($alt) ) { @@ -35,7 +35,7 @@ sub render { $alt = $url; } - my $html = $session->templates->expandTemplate("icon:image"); + my $html = $app->templates->expandTemplate("icon:image"); $html =~ s/%URL%/$url/ge; $html =~ s/%WIDTH%/16/g; $html =~ s/%HEIGHT%/16/g; diff --git a/core/lib/Foswiki/Render/Moved.pm b/core/lib/Foswiki/Render/Moved.pm index 506ad8b260..d2275accfe 100644 --- a/core/lib/Foswiki/Render/Moved.pm +++ b/core/lib/Foswiki/Render/Moved.pm @@ -17,14 +17,14 @@ BEGIN { =begin TML ----++ StaticMethod render($session, $topicObject, $params) -> $text +---++ StaticMethod render($app, $topicObject, $params) -> $text Render moved meta-data. Support for %META%. =cut sub render { - my ( $session, $topicObject, $params ) = @_; + my ( $app, $topicObject, $params ) = @_; my $text = ''; my $moved = $topicObject->get('TOPICMOVED'); my $prefix = $params->{prefix} || ''; @@ -32,12 +32,14 @@ sub render { if ($moved) { my ( $fromWeb, $fromTopic ) = - $session->normalizeWebTopicName( $topicObject->web, $moved->{from} ); + $app->request->normalizeWebTopicName( $topicObject->web, + $moved->{from} ); my ( $toWeb, $toTopic ) = - $session->normalizeWebTopicName( $topicObject->web, $moved->{to} ); + $app->request->normalizeWebTopicName( $topicObject->web, + $moved->{to} ); my $by = $moved->{by}; my $u = $by; - my $users = $session->users; + my $users = $app->users; $by = $users->webDotWikiName($u) if $u; my $date = Foswiki::Time::formatTime( $moved->{date}, '', 'gmtime' ); @@ -49,19 +51,19 @@ sub render { . CGI::a( { title => ( - $session->i18n->maketext( + $app->i18n->maketext( 'Click to move topic back to previous location, with option to change references.' ) ), - href => $session->getScriptUrl( + href => $app->cfg->getScriptUrl( 0, 'rename', $topicObject->web, $topicObject->topic ), rel => 'nofollow' }, - $session->i18n->maketext('Put it back...') + $app->i18n->maketext('Put it back...') ); } - $text = $session->i18n->maketext( + $text = $app->i18n->maketext( "[_1] was renamed or moved from [_2] on [_3] by [_4]", "$toWeb.$toTopic", "$fromWeb.$fromTopic", $date, $by diff --git a/core/lib/Foswiki/Render/Parent.pm b/core/lib/Foswiki/Render/Parent.pm index 7146e57d89..878d10ca01 100644 --- a/core/lib/Foswiki/Render/Parent.pm +++ b/core/lib/Foswiki/Render/Parent.pm @@ -16,14 +16,14 @@ BEGIN { =begin TML ----++ StaticMethod render($session, $topicObject, $params) -> $text +---++ StaticMethod render($app, $topicObject, $params) -> $text Render parent meta-data. Support for %META%. =cut sub render { - my ( $session, $topicObject, $ah ) = @_; + my ( $app, $topicObject, $ah ) = @_; my $dontRecurse = $ah->{dontrecurse} || 0; my $depth = $ah->{depth} || 0; my $noWebHome = $ah->{nowebhome} || 0; @@ -52,7 +52,8 @@ sub render { while ($parent) { $currentDepth++; - ( $pWeb, $pTopic ) = $session->normalizeWebTopicName( $pWeb, $parent ); + ( $pWeb, $pTopic ) = + $app->request->normalizeWebTopicName( $pWeb, $parent ); $parent = $pWeb . '.' . $pTopic; last if ( $noWebHome && ( $pTopic eq $Foswiki::cfg{HomeTopicName} ) @@ -70,7 +71,7 @@ sub render { # Compromise; rather than supporting a hack in the store to support # rapid access to parent meta (as in TWiki) accept the hit # of reading the whole topic. - my $topicObject = Foswiki::Meta->load( $session, $pWeb, $pTopic ); + my $topicObject = Foswiki::Meta->load( $app, $pWeb, $pTopic ); my $parentMeta = $topicObject->get('TOPICPARENT'); $parent = $parentMeta->{name} if $parentMeta; } diff --git a/core/lib/Foswiki/Render/ToolTip.pm b/core/lib/Foswiki/Render/ToolTip.pm index 57faff358d..bd9576b33e 100644 --- a/core/lib/Foswiki/Render/ToolTip.pm +++ b/core/lib/Foswiki/Render/ToolTip.pm @@ -18,7 +18,7 @@ BEGIN { =begin TML ----++ StaticMethod render($session, $web, $topic, $template) -> $text +---++ StaticMethod render($app, $web, $topic, $template) -> $text Returns =title= tooltip info for a link to $web,$topic by filling in $template. $template may contain: @@ -35,18 +35,18 @@ in $template. $template may contain: # SMELL: should expand standard escapes sub render { - my ( $session, $web, $topic, $tooltip ) = @_; + my ( $app, $web, $topic, $tooltip ) = @_; # FIXME: This is slow, it can be improved by caching topic rev # info and summary - my $users = $session->users; + my $users = $app->users; # These are safe to untaint blindly because this method is only # called when a regex matches a valid wikiword $web = Foswiki::Sandbox::untaintUnchecked($web); $topic = Foswiki::Sandbox::untaintUnchecked($topic); my $topicObject = - Foswiki::Meta->new( session => $session, web => $web, topic => $topic ); + Foswiki::Meta->new( app => $app, web => $web, topic => $topic ); my $info = $topicObject->getRevisionInfo(); $tooltip =~ s/\$web/$web/g; @@ -67,7 +67,7 @@ sub render { } else { $summary = - $session->inlineAlert( 'alerts', 'access_denied', "$web.$topic" ); + $app->inlineAlert( 'alerts', 'access_denied', "$web.$topic" ); } $summary = $topicObject->summariseText(); $summary =~ diff --git a/core/lib/Foswiki/Render/Zones.pm b/core/lib/Foswiki/Render/Zones.pm index 8bd7198732..5d890157a2 100644 --- a/core/lib/Foswiki/Render/Zones.pm +++ b/core/lib/Foswiki/Render/Zones.pm @@ -18,7 +18,7 @@ use Assert; use Moo; use namespace::clean; -extends qw(Foswiki::Object); +extends qw(Foswiki::AppObject); BEGIN { if ( $Foswiki::cfg{UseLocale} ) { @@ -27,12 +27,6 @@ BEGIN { } } -has session => ( - is => 'rw', - weak_ref => 1, - isa => Foswiki::Object::isaCLASS( 'session', 'Foswiki', noUndef => 1 ), - required => 1, -); has _zones => ( is => 'rw', lazy => 1, default => sub { {} }, ); has _renderZonePlaceholder => ( is => 'rw', lazy => 1, default => sub { {} }, ); @@ -164,7 +158,7 @@ sub _renderZoneById { sub _renderZone { my ( $this, $zone, $params, $topicObject ) = @_; - my $session = $Foswiki::Plugins::SESSION; + my $app = $this->app; # Check the zone is defined and has not already been rendered return '' unless $zone && $this->_zones->{$zone}; @@ -180,10 +174,10 @@ sub _renderZone { #print STDERR "_renderZone called with " . Data::Dumper::Dumper( \$topicObject ); unless ( defined $topicObject ) { - $topicObject = Foswiki::Meta->new( - session => $session, - web => $session->webName, - topic => $session->topicName + $topicObject = $this->create( + 'Foswiki::Meta', + web => $app->request->web, + topic => $app->request->topic, ); } @@ -264,7 +258,7 @@ sub _renderZone { push @result, $line if $line; } my $result = - Foswiki::expandStandardEscapes( $params->{header} + $app->macros->expandStandardEscapes( $params->{header} . join( $params->{separator}, @result ) . $params->{footer} ); diff --git a/core/lib/Foswiki/Response.pm b/core/lib/Foswiki/Response.pm index c234f1f4a6..c781cfbc24 100644 --- a/core/lib/Foswiki/Response.pm +++ b/core/lib/Foswiki/Response.pm @@ -234,13 +234,13 @@ sub generateHTTPHeaders { my ( $this, $hopts ) = @_; my $app = $this->app; + my $req = $app->request; $hopts ||= {}; # DEPRECATED plugins header handler. Plugins should use # modifyHeaderHandler instead. - my $pluginHeaders = - $app->plugins->dispatch( 'writeHeaderHandler', $this->request ) + my $pluginHeaders = $app->plugins->dispatch( 'writeHeaderHandler', $req ) || ''; if ($pluginHeaders) { foreach ( split /\r?\n/, $pluginHeaders ) { @@ -261,13 +261,13 @@ sub generateHTTPHeaders { # use our version of the content type $hopts->{'Content-Type'} = $contentType; - $hopts->{'X-FoswikiAction'} = $this->request->action; - $hopts->{'X-FoswikiURI'} = $this->request->uri; + $hopts->{'X-FoswikiAction'} = $req->action; + $hopts->{'X-FoswikiURI'} = $req->uri; # Turn off XSS protection in DEBUG so it doesn't mask problems $hopts->{'X-XSS-Protection'} = 0 if DEBUG; - $app->plugins->dispatch( 'modifyHeaderHandler', $hopts, $this->request ); + $app->plugins->dispatch( 'modifyHeaderHandler', $hopts, $req ); # The headers method resets all headers to what we pass # what we want is simply ensure our headers are there diff --git a/core/lib/Foswiki/Search.pm b/core/lib/Foswiki/Search.pm index 745f900803..2eb0a44f85 100644 --- a/core/lib/Foswiki/Search.pm +++ b/core/lib/Foswiki/Search.pm @@ -165,7 +165,7 @@ sub _extractPattern { if ($encode) { - # Reverse the action of Foswiki::expandStandardEscapes + # Reverse the action of Foswiki::Macros::expandStandardEscapes $text =~ s/$/\$dollar/g; $text =~ s/&/\$amp()/g; $text =~ s/>/\$gt()/g; @@ -441,7 +441,7 @@ sub searchWeb { #legacy SEARCH counter support $result =~ s/%NTOPICS%/0/g; - $result = Foswiki::expandStandardEscapes($result); + $result = $app->macros->expandStandardEscapes($result); $result =~ s/\n$//s; # remove trailing new line return $result; @@ -472,7 +472,7 @@ sub searchWeb { my $searchResult = join( '', @{ $params{_cbdata} } ); - $searchResult = Foswiki::expandStandardEscapes($searchResult); + $searchResult = $app->macros->expandStandardEscapes($searchResult); # Remove trailing separator or new line if nofinalnewline parameter is set my $noFinalNewline = Foswiki::isTrue( $params{nofinalnewline}, 1 ); diff --git a/core/lib/Foswiki/Store.pm b/core/lib/Foswiki/Store.pm index 28c8fc6267..bee9fc2098 100644 --- a/core/lib/Foswiki/Store.pm +++ b/core/lib/Foswiki/Store.pm @@ -223,7 +223,7 @@ sub getAttachmentURL { # See http://www.ietf.org/rfc/rfc2396.txt for the definition of # "absolute URI". Foswiki bastardises this definition by assuming # that all relative URLs lack the component as well. - $url = $app->request->urlHost . $url; + $url = $app->cfg->urlHost . $url; } return $url; diff --git a/core/lib/Foswiki/UI/Edit.pm b/core/lib/Foswiki/UI/Edit.pm index 3f3ce9b6d7..087fce202f 100644 --- a/core/lib/Foswiki/UI/Edit.pm +++ b/core/lib/Foswiki/UI/Edit.pm @@ -367,7 +367,7 @@ sub init_edit { # Because the form has been expanded from a Template, we # want to expand $percnt-style content right now $topicObject->forEachSelectedValue( qr/FIELD/, qr/value/, - sub { Foswiki::expandStandardEscapes(@_) }, + sub { $this->app->macros->expandStandardEscapes(@_) }, ); } else { diff --git a/core/lib/Foswiki/Users.pm b/core/lib/Foswiki/Users.pm index 0f45f2c432..08293b1397 100644 --- a/core/lib/Foswiki/Users.pm +++ b/core/lib/Foswiki/Users.pm @@ -519,7 +519,7 @@ sub getCanonicalUserID { # TopicUserMappingContrib but may be used by other mappers # that support user topics) my ( $dummy, $nid ) = - $this->app->normalizeWebTopicName( '', $identifier ); + $this->app->request->normalizeWebTopicName( '', $identifier ); $identifier = $nid if ( $dummy eq $Foswiki::cfg{UsersWebName} ); my $found = $this->findUserByWikiName($identifier); diff --git a/core/lib/Foswiki/WebFilter.pm b/core/lib/Foswiki/WebFilter.pm index 42e135dabb..4e395504e5 100755 --- a/core/lib/Foswiki/WebFilter.pm +++ b/core/lib/Foswiki/WebFilter.pm @@ -35,23 +35,23 @@ sub new { } sub ok { - my ( $this, $session, $web ) = @_; + my ( $this, $app, $web ) = @_; return 0 if $this->{template} && $web !~ /(?:^_|\/_)/; - return 1 if ( $web eq $session->webName ); + return 1 if ( $web eq $app->request->web ); return 0 if $this->{user} && $web =~ m/(?:^_|\/_)/; - return 0 if !$session->webExists($web); + return 0 if !$app->store->webExists($web); - my $webObject = Foswiki::Meta->new( session => $session, web => $web ); + my $webObject = Foswiki::Meta->new( app => $app, web => $web ); my $thisWebNoSearchAll = Foswiki::isTrue( $webObject->getPreference('NOSEARCHALL') ); return 0 if $this->{public} - && !$session->users->isAdmin( $session->user ) + && !$app->users->isAdmin( $app->user ) && $thisWebNoSearchAll; return 0 if $this->{allowed} && !$webObject->haveAccess('VIEW');