diff --git a/wiki.pl b/wiki.pl index 09ff7306..3d32439d 100755 --- a/wiki.pl +++ b/wiki.pl @@ -56,19 +56,19 @@ $SurgeProtectionTime $SurgeProtectionViews $DeletedPage %Languages $InterMap $ValidatorLink @LockOnCreation $PermanentAnchors $RssStyleSheet $PermanentAnchorsFile @MyRules %CookieParameters @UserGotoBarPages $NewComment $StyleSheetPage $ConfigPage $ScriptName -@MyMacros $CommentsPrefix @UploadTypes $DefaultStyleSheet -$AllNetworkFiles $UsePathInfo $UploadAllowed $LastUpdate $PageCluster -$HtmlHeaders $RssInterwikiTranslate $UseCache $ModuleDir $DebugInfo -$FullUrlPattern %InvisibleCookieParameters $FreeInterLinkPattern -@AdminPages @MyAdminCode @MyInitVariables @MyMaintenance); +@MyMacros $CommentsPrefix @UploadTypes $AllNetworkFiles $UsePathInfo +$UploadAllowed $LastUpdate $PageCluster $HtmlHeaders +$RssInterwikiTranslate $UseCache $ModuleDir $DebugInfo $FullUrlPattern +%InvisibleCookieParameters $FreeInterLinkPattern @AdminPages +@MyAdminCode @MyInitVariables @MyMaintenance); # Other global variables: use vars qw(%Page %InterSite %IndexHash %Translate %OldCookie -%NewCookie $InterSiteInit $FootnoteNumber $OpenPageName @IndexList +%NewCookie $InterInit $FootnoteNumber $OpenPageName @IndexList $IndexInit $Message $q $Now %RecentVisitors @HtmlStack $Monolithic $ReplaceForm %PermanentAnchors %PagePermanentAnchors %MyInc $CollectingJournal $WikiDescription $PrintedHeader %Locks $Fragment -@Blocks @Flags %NearSite %NearSource %NearLinksUsed $NearSiteInit +@Blocks @Flags %NearSite %NearSource %NearLinksUsed $NearInit $NearDir $NearMap $SisterSiteLogoUrl %NearSearch @KnownLocks $PermanentAnchorsInit $ModulesDescription %RuleOrder %Action $bol %RssInterwikiTranslate $RssInterwikiTranslateInit); @@ -98,7 +98,7 @@ $SiteBase = ''; # Full URL for header $MaxPost = 1024 * 210; # Maximum 210K posts (about 200K for pages) $HttpCharset = 'UTF-8'; # You are on your own if you change this! $StyleSheet = ''; # URL for CSS stylesheet (like '/wiki.css') -$StyleSheetPage = ''; # Page for CSS sheet +$StyleSheetPage = 'css'; # Page for CSS sheet $LogoUrl = ''; # URL for site logo ('' for no logo) $NotFoundPg = ''; # Page for not-found links ('' for blank pg) $NewText = "Describe the new page here.\n"; # New page text @@ -159,49 +159,6 @@ $HtmlHeaders = ''; # Additional stuff to put in the HTML section $IndentLimit = 20; # Maximum depth of nested lists $LanguageLimit = 3; # Number of matches req. for each language $SisterSiteLogoUrl = 'file:///tmp/oddmuse/%s.png'; # URL format string for logos -$DefaultStyleSheet = q{ -body { background-color:#FFF; color:#000; } -textarea { width:100%; } -a:link { color:#00F; } -a:visited { color:#A0A; } -a:active { color:#F00; } -a.definition:before { content:"[::"; } -a.definition:after { content:"]"; } -a.alias { text-decoration:none; border-bottom: thin dashed; } -a.near:link { color:#093; } -a.near:visited { color:#550; } -a.upload:before { content:"<"; } -a.upload:after { content:">"; } -a.outside:before { content:"["; } -a.outside:after { content:"]"; } -img.logo { float: right; clear: right; border-style:none; } -div.diff { padding-left:5%; padding-right:5%; } -div.old { background-color:#FFFFAF; } -div.new { background-color:#CFFFCF; } -div.message { background-color:#FEE; } -div.journal h1 { font-size:large; } -table.history { border-style:none; } -td.history { border-style:none; } -span.result { font-size:larger; } -span.info { font-size:smaller; font-style:italic; } -div.rss { background-color:#EEF; } -div.search { background-color:#F1F5FF } -div.sister { float:left; margin-right:1ex; background-color:#FFF; } -div.sister p { margin-top:0; } -div.sister img { border:none; } -div.near { background-color:#EFE; } -div.near p { margin-top:0; } -@media print { - body { font:12pt sans-serif; } - a, a:link, a:visited { color:#000; text-decoration:none; font-style:oblique; } - h1 a, h2 a, h3 a, h4 a { font-style:normal; } - a.edit, div.footer, form, span.gotobar, a.number span { display:none; } - a[class="url number"]:after, a[class="inter number"]:after { content:"[" attr(href) "]"; } - a[class="local number"]:after { content:"[" attr(title) "]"; } - img[smiley] { line-height: inherit; } -} -}; # the is added at the end - # Display short comments below the GotoBar for special days # Example: %SpecialDays = ('1-1' => 'New Year', '1-2' => 'Next Day'); %SpecialDays = (); @@ -223,7 +180,7 @@ div.near p { margin-top:0; } download => \&DoDownload, rss => \&DoRss, unlock => \&DoUnlock, password => \&DoPassword, index => \&DoIndex, admin => \&DoAdminPage, - all => \&DoPrintAllPages, ); + all => \&DoPrintAllPages, css => \&DoCss, ); # The 'main' program, called at the end of this script file (aka. as handler) sub DoWikiRequest { @@ -236,9 +193,9 @@ sub DoWikiRequest { } sub ReportError { # fatal! - my ($errmsg, $status, $log) = @_; + my ($errmsg, $status, $log, @html) = @_; print GetHttpHeader('text/html', 'nocache', $status); - print $q->start_html, $q->h2(QuoteHtml($errmsg)), $q->end_html; + print $q->start_html, $q->h2(QuoteHtml($errmsg)), @html, $q->end_html; map { ReleaseLockDir($_); } keys %Locks; WriteStringToFile("$TempDir/error", $q->start_html . $q->h1("$status $errmsg") . $q->Dump . $q->end_html) if $log; @@ -302,25 +259,18 @@ sub InitVariables { # Init global session variables for mod_perl! $FullUrl = $ScriptName unless $FullUrl; # URL used in forms $Now = time; # Reset in case script is persistent $LastUpdate = (stat($IndexFile))[9]; - $InterSiteInit = 0; - %InterSite = (); - $NearSiteInit = 0; - %NearSite = (); - %NearSearch = (); - %NearSource = (); - %NearLinksUsed = (); - $RssInterwikiTranslateInit = 0; - %RssInterwikiTranslate = (); %Locks = (); - $IndexInit =0; @Blocks = (); @Flags = (); $Fragment = ''; %RecentVisitors = (); - %PagePermanentAnchors = (); + CreateDir($DataDir); # Create directory if it doesn't exist + AllPagesList(); # Ordinary pages, read from $IndexFile (saving it requires $DataDir) + NearInit(); # reads $NearMap and includes InterInit (reads $InterMap) + PermanentAnchorsInit() if $PermanentAnchors; # reads $PermanentAnchorsFile + %NearLinksUsed = (); # List of links used during this request $OpenPageName = ''; # Currently open page $PrintedHeader = 0; # Error messages don't print headers unless necessary - CreateDir($DataDir); # Create directory if it doesn't exist ReportError(Ts('Could not create %s', $DataDir) . ": $!", '500 INTERNAL SERVER ERROR') unless -d $DataDir; my $add_space = $CommentsPrefix =~ /[ \t_]$/; @@ -336,7 +286,7 @@ sub InitVariables { # Init global session variables for mod_perl! unshift(@MyRules, \&MyRules) if defined(&MyRules) && (not @MyRules or $MyRules[0] != \&MyRules); @MyRules = sort {$RuleOrder{$a} <=> $RuleOrder{$b}} @MyRules; # default is 0 $WikiDescription = $q->p($q->a({-href=>'http://www.oddmuse.org/'}, 'Oddmuse')) - . $q->p(q{$Id: wiki.pl,v 1.603 2005/09/28 19:50:18 as Exp $}); + . $q->p(q{$Id: wiki.pl,v 1.604 2005/10/05 20:38:12 as Exp $}); $WikiDescription .= $ModulesDescription if $ModulesDescription; foreach my $sub (@MyInitVariables) { my $result = &$sub; @@ -426,7 +376,6 @@ sub Dirty { # arg 1 is the raw text; the real output must be printed instead sub ApplyRules { # locallinks: apply rules that create links depending on local config (incl. interlink!) my ($text, $locallinks, $withanchors, $revision, @tags) = @_; # $revision is used for images - NearInit() unless $NearSiteInit; $text =~ s/\r\n/\n/g; # DOS to Unix $text =~ s/\n+$//g; # No trailing paragraphs return unless $text; @@ -434,7 +383,9 @@ sub ApplyRules { local @Blocks=(); # the list of cached HTML blocks local @Flags=(); # a list for each block, 1 = dirty, 0 = clean Clean(join('', map { AddHtmlEnvironment($_) } @tags)); - if (my ($type) = TextIsFile($text)) { + if ($OpenPageName eq $StyleSheetPage or $OpenPageName eq $ConfigPage) { + Clean($q->pre($text)); + } elsif (my ($type) = TextIsFile($text)) { Clean($q->p(T('This page contains an uploaded file:')) . $q->p(GetDownloadLink($OpenPageName, (substr($type, 0, 6) eq 'image/'), $revision))); } else { @@ -840,7 +791,7 @@ sub RSS { } else { my ($counter, $interwiki); if (@uris > 1) { - RssInterwikiTranslateInit() unless $RssInterwikiTranslateInit; + RssInterwikiTranslateInit(); # not needed anywhere else, therefore not in InitVariables $interwiki = $rss->{channel}->{$wikins}->{interwiki}; $interwiki =~ s/^\s+//; # when RDF is used, sometimes whitespace remains, $interwiki =~ s/\s+$//; # which breaks the test for an existing $interwiki below @@ -974,7 +925,9 @@ sub GetRssFile { } sub RssInterwikiTranslateInit { - $RssInterwikiTranslateInit = 1; + return if $RssInterwikiTranslateInit; + $RssInterwikiTranslateInit = 1; # set to 0 when $RssInterwikiTranslate is saved + %RssInterwikiTranslate = (); foreach (split(/\n/, GetPageContent($RssInterwikiTranslate))) { if (/^ ([^ ]+)[ \t]+([^ ]+)$/) { $RssInterwikiTranslate{$1} = $2; @@ -983,8 +936,12 @@ sub RssInterwikiTranslateInit { } sub NearInit { - InterInit() unless $InterSiteInit; - $NearSiteInit = 1; + InterInit(); + return if $NearInit; + $NearInit = 1; # set to 0 when $NearMap is saved + %NearSite = (); + %NearSearch = (); + %NearSource = (); foreach (split(/\n/, GetPageContent($NearMap))) { if (/^ ($InterSitePattern)[ \t]+([^ ]+)(?:[ \t]+([^ ]+))?$/) { my ($site, $url, $search) = ($1, $2, $3); @@ -1037,7 +994,9 @@ sub GetInterLink { } sub InterInit { - $InterSiteInit = 1; + return if $InterInit; + %InterSite = (); + $InterInit = 1; # set to 0 when $InterMap is saved foreach (split(/\n/, GetPageContent($InterMap))) { if (/^ ($InterSitePattern)[ \t]+([^ ]+)$/) { $InterSite{$1} = $2; @@ -1147,7 +1106,6 @@ sub GetDownloadLink { my ($name, $image, $revision, $alt) = @_; $alt = $name unless $alt; my $id = FreeToNormal($name); - AllPagesList(); # if the page does not exist return '[' . ($image ? T('image') : T('download')) . ':' . $name . ']' . GetEditLink($id, '?', 1) unless $IndexHash{$id}; @@ -1304,16 +1262,13 @@ sub ValidIdOrDie { sub ResolveId { # return css class, resolved id, title (eg. for popups), exist-or-not my $id = shift; - AllPagesList(); my $exists = $IndexHash{$id}; # if the page exists physically if (GetParam('anchor', $PermanentAnchors)) { # anchors are preferred - ReadPermanentAnchors() unless $PermanentAnchorsInit; my $page = $PermanentAnchors{$id}; return ('alias', $page . '#' . $id, $page, $exists) # $page used as link title if $page and $page ne $id; } return ('local', $id, '', $exists) if $exists; - NearInit() unless $NearSiteInit; if ($NearSource{$id}) { $NearLinksUsed{$id} = 1; my $site = $NearSource{$id}[0]; @@ -1958,6 +1913,7 @@ sub DoAdminPage { } else { push(@menu, ScriptLink('action=editlock;set=1', T('Lock site'))); } + push(@menu, ScriptLink('action=css', T('Install CSS'))) unless $StyleSheet; if ($id) { my $title = $id; $title =~ s/_/ /g; @@ -2067,7 +2023,7 @@ sub GetHeader { } $result .= $q->start_div({-class=>'header'}); if ((!$embed) && ($LogoUrl ne '')) { - $result .= ScriptLink($HomePage, $q->img({-src=>$LogoUrl, -alt=>$altText, -class=>'logo'})); + $result .= ScriptLink($HomePage, $q->img({-src=>$LogoUrl, -alt=>$altText, -class=>'logo'}), 'logo'); } if (GetParam('toplinkbar', $TopLinkBar)) { $result .= GetGotoBar($id); @@ -2178,10 +2134,11 @@ sub GetCss { } } elsif ($StyleSheet) { return qq(); - } elsif ($StyleSheetPage) { - return $q->style({-type=>'text/css'}, GetPageContent($StyleSheetPage)); + } elsif ($IndexHash{$StyleSheetPage}) { + $css = "$ScriptName?action=browse;id=" . UrlEncode($StyleSheetPage); + return qq(); } else { - return $q->style({-type=>'text/css'}, ""); + return qq(); } } @@ -2209,7 +2166,6 @@ sub PrintFooter { sub GetSisterSites { my $id = shift; - NearInit() unless $NearSiteInit; if ($id and $NearSource{$id}) { my $sistersites = T('The same page on other sites:') . $q->br(); foreach my $site (@{$NearSource{$id}}) { @@ -2498,7 +2454,6 @@ sub OpenPage { # Sets global variables if ($OpenPageName eq $id) { return; } - AllPagesList(); # set IndexHash if ($IndexHash{$id}) { %Page = ParseData(ReadFileOrDie(GetPageFile($id))); } else { @@ -2547,7 +2502,6 @@ sub GetTextRevision { sub GetPageContent { my $id = shift; - AllPagesList(); # set IndexHash if ($IndexHash{$id}) { my %data = ParseData(ReadFileOrDie(GetPageFile($id))); return $data{text}; @@ -2837,18 +2791,18 @@ sub DoEdit { ValidIdOrDie($id); my $upload = GetParam('upload', undef); if (!UserCanEdit($id, 1)) { - print GetHeader('', T('Editing Denied'), undef, undef, '403 FORBIDDEN'); my $rule = UserIsBanned(); if ($rule) { - print $q->p(T('Editing not allowed: user, ip, or network is blocked.')); - print $q->p(T('Contact the wiki administrator for more information.')); - print $q->p(Ts('The rule %s matched for you.', $rule) . ' ' - . Ts('See %s for more information.', GetPageLink($BannedHosts))); + ReportError(T('Edit Denied'), '403 FORBIDDEN', undef, + $q->p(T('Editing not allowed: user, ip, or network is blocked.')), + $q->p(T('Contact the wiki administrator for more information.')), + $q->p(Ts('The rule %s matched for you.', $rule) . ' ' + . Ts('See %s for more information.', GetPageLink($BannedHosts)))); } else { - print $q->p(Ts('Editing not allowed: %s is read-only.', $SiteName)); + $id =~ s/_/ /g; + ReportError(T('Edit Denied'), '403 FORBIDDEN', undef, + $q->p(Ts('Editing not allowed: %s is read-only.', $id))); } - PrintFooter(); - return; } elsif ($upload and not $UploadAllowed and not UserIsAdmin()) { ReportError(T('Only administrators can upload files.'), '403 FORBIDDEN'); } @@ -3058,8 +3012,6 @@ sub DoIndex { my $anchors = GetParam('permanentanchors', 1); my $near = GetParam('near', 0); my $match = GetParam('match', ''); - NearInit() if not $NearSiteInit; # init always to get the menu right - ReadPermanentAnchors() if $PermanentAnchors and not $PermanentAnchorsInit; push(@pages, AllPagesList()) if $pages; push(@pages, keys %PermanentAnchors) if $anchors; push(@pages, keys %NearSource) if $near; @@ -3140,7 +3092,7 @@ sub AllPagesList { if ($status) { %IndexHash = split(/\s+/, $rawIndex); @IndexList = sort(keys %IndexHash); - $IndexInit = 1; + $IndexInit = 1; # set to 0 when a new page is saved return @IndexList; } # If open fails just refresh the index @@ -3153,7 +3105,7 @@ sub AllPagesList { push(@IndexList, $id); $IndexHash{$id} = 1; } - $IndexInit = 1; # Initialized for this run of the script + $IndexInit = 1; # set to 0 when a new page is saved # Try to write out the list for future runs. If file exists and cannot be changed, error! RequestLockDir('index', undef, undef, -f $IndexFile) or return @IndexList; WriteStringToFile($IndexFile, join(' ', %IndexHash)); @@ -3185,7 +3137,6 @@ sub DoSearch { print GetHeader('', QuoteHtml(Ts('Search for: %s', $string))), $q->start_div({-class=>'content search'}); $ReplaceForm = UserIsAdmin(); - NearInit(); my @elements = (ScriptLink('action=rc;rcfilteronly=' . UrlEncode($string), T('View changes for these pages'))); push(@elements, ScriptLink('near=2;search=' . UrlEncode($string), @@ -3251,7 +3202,6 @@ sub SearchNearPages { my %found; foreach (@_) { $found{$_} = 1; }; # to avoid using grep on the list my $regex = HighlightRegex($string); - NearInit(); if (%NearSearch and GetParam('near', 1) > 1 and GetParam('context',1)) { foreach my $site (keys %NearSearch) { my $url = $NearSearch{$site}; @@ -3480,14 +3430,11 @@ sub DoPost { # Banned Content if (not UserIsEditor()) { my $rule = BannedContent($string); - if ($rule) { - print GetHeader('', T('Edit Denied'), undef, undef, '403 FORBIDDEN'); - print $q->p(T('The page contains banned text.')); - print $q->p(T('Contact the wiki administrator for more information.')); - print $q->p($rule . ' ' . Ts('See %s for more information.', GetPageLink($BannedContent))); - ReleaseLock(); - return; - } + ReportError(T('Edit Denied'), '403 FORBIDDEN', undef, + $q->p(T('The page contains banned text.')), + $q->p(T('Contact the wiki administrator for more information.')), + $q->p($rule . ' ' . Ts('See %s for more information.', GetPageLink($BannedContent)))) + if $rule; } my $summary = GetSummary(); # rebrowse if no changes @@ -3589,9 +3536,12 @@ sub Save { # call within lock, with opened page . ' ' . T('Please check the directory permissions.') . ' ' . T('Your changes were not saved.')); return; - } else { - utime time, time, $IndexFile; # touch index file } + $IndexInit = 0 if $revision == 1; + $NearInit = 0 if $id eq $NearMap; + $InterInit = 0 if $id eq $InterMap; + $RssInterwikiTranslateInit = 0 if $id eq $RssInterwikiTranslate; + utime time, time, $IndexFile; # touch index file SaveKeepFile(); # deletes blocks, flags, diff-major, and diff-minor, and sets keep-ts ExpireKeepFiles(); $Page{ts} = $Now; @@ -3612,7 +3562,7 @@ sub Save { # call within lock, with opened page $Page{languages} = $languages; SavePage(); if ($revision == 1 and grep(/^$id$/, @LockOnCreation)) { - WriteStringToFile(GetLockedPageFile($id), 'editing locked.'); + WriteStringToFile(GetLockedPageFile($id), '@LockOnCreation'); } WriteRcLog($id, $summary, $minor, $revision, $user, $host, $languages, GetCluster($new)); $LastUpdate = $Now; # for mod_perl @@ -3739,7 +3689,6 @@ sub DoMaintain { WriteStringToFile($RcFile . '.old', $data); WriteStringToFile($RcFile, join("\n",@rc) . "\n"); } - NearInit() unless $NearSiteInit; if (%NearSite) { CreateDir($NearDir); foreach my $site (keys %NearSite) { @@ -3849,7 +3798,6 @@ sub DoShowVersion { print $q->p(ScriptLink('action=version;dependencies=1', T('Show dependencies'))); } if (GetParam('links', 0)) { - NearInit() unless $NearSiteInit; print $q->h2(T('Inter links:')), $q->p(join(', ', sort keys %InterSite)); print $q->h2(T('Near links:')), $q->p(join($q->br(), map { $_ . ': ' . join(', ', @{$NearSource{$_}})} @@ -3932,8 +3880,10 @@ sub WriteRecentVisitors { # == Permanent Anchors == -sub ReadPermanentAnchors { - $PermanentAnchorsInit = 1; +sub PermanentAnchorsInit { + return if $PermanentAnchorsInit; + %PagePermanentAnchors = (); + $PermanentAnchorsInit = 1; # set to 0 when $PermanentAnchorsFile is saved my ($status, $data) = ReadFile($PermanentAnchorsFile); return unless $status; # not fatal %PermanentAnchors = split(/\n| |$FS/,$data); # FIXME: $FS was used in 1.417 and earlier @@ -3970,7 +3920,6 @@ sub GetPermanentAnchor { } sub DeletePermanentAnchors { - ReadPermanentAnchors() unless $PermanentAnchorsInit; foreach (keys %PermanentAnchors) { if ($PermanentAnchors{$_} eq $OpenPageName and !$PagePermanentAnchors{$_}) { delete($PermanentAnchors{$_}) ; @@ -3983,6 +3932,22 @@ sub DeletePermanentAnchors { sub TextIsFile { $_[0] =~ /^#FILE (\S+)\n/ } +sub DoCss { + my $css = GetParam('install', ''); + if ($css) { + SetParam('text', GetRaw($css)); + DoPost($StyleSheetPage); + } else { + print GetHeader('', T('Install CSS')), $q->start_div({-class=>'content css'}); + my @css = qw(http://www.emacswiki.org/css/beige-red.css + http://www.emacswiki.org/css/green.css); + print $q->p(Ts('Copy one of the following stylesheets to %s:', GetPageLink($StyleSheetPage))); + print $q->ul(map {$q->li(ScriptLink("action=css;install=" . UrlEncode($_), $_))} @css); + print $q->end_div(); + PrintFooter(); + } +} + sub handler { my $r = shift; Apache->request($r);