123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- #!/usr/bin/env perl
- use strict;
- use v5.10;
- # ====================[ crossbar.pl ]====================
- =head1 NAME
- crossbar - An Oddmuse module for adding a site-wide footer, header, or other
- summary markup to all Oddmuse Wiki pages.
- =head1 SYNOPSIS
- crossbar is a drop-in substitute for the Sidebar module, which, as it is not
- entirely "backwards compatible" with the Sidebar module, is provided as a
- separate module and not revision of that module.
- crossbar provides additional functionality, including:
- =over
- =item Support for the Table of Contents and Footnotes modules. (The Sidebar
- module does not support these modules.)
- =item Support for displaying the crossbar anywhere in a page. (The Sidebar
- module does not permit the sidebar to be displayed anywhere except
- immediately after the header div and before the content div.)
- =back
- And so on.
- =head1 INSTALLATION
- crossbar is easily installable; move this file into the B<wiki/modules/>
- directory for your Oddmuse Wiki.
- =cut
- AddModuleDescription('crossbar.pl', 'Crossbar Extension');
- our ($q, $bol, $OpenPageName, @HtmlStack, @MyInitVariables, @MyRules, %AdminPages, $DeletedPage, $SidebarName, $TocIsApplyingAutomaticRules);
- # ....................{ CONFIGURATION }....................
- our ($CrossbarPageName,
- $CrossbarDivIsOutsideContentDiv,
- $CrossbarSubstitutionPattern);
- =head1 CONFIGURATION
- crossbar is easily configurable; set these variables in the B<wiki/config.pl>
- file for your Oddmuse Wiki.
- =cut
- =head2 $CrossbarPageName
- The name of the page having crossbar markup. This markup will be added,
- automatically, to every Wiki page at the position matched by the
- C<$CrossbarSubstitutionPattern>, below.
- =cut
- $CrossbarPageName = 'Crossbar';
- =head2 $CrossbarDivIsOutsideContentDiv
- A boolean that, if true, places the <div class="crossbar">...</div> block
- "outside" the <div class="content browse">...</div> block; otherwise, this
- places it inside the <div class="content browse">...</div> block. Generally,
- placing the crossbar div outside the content div gives a cleaner, sensibler
- aesthetic. (Your mileage may vary!)
- By default, this boolean is true.
- =cut
- $CrossbarDivIsOutsideContentDiv = 1;
- =head2 $CrossbarSubstitutionPattern
- The regular expression matching the position in each page to place the crossbar
- for that page. While, theoretically, this can be any pattern, it tends to be one
- the following two:
- =over
- =item '^'. This places the sidebar for each page immediately after that page's
- header and before that page's content.
- =item '$'. This places the sidebar for each page immediately after that page's
- content and before that page's footer.
- =back
- This module uses the first regular expression, by default.
- =cut
- $CrossbarSubstitutionPattern = '^';
- # ....................{ INITIALIZATION }....................
- push(@MyInitVariables, \&CrossbarInit);
- # A boolean that, if true, indicates that a crossbar has already been applied to
- # this page. This prevents application of a crossbar onto pages included by this
- # current page -- and, in general, protects against "reentrant recursion."
- my $CrossbarIsApplied;
- sub CrossbarInit {
- $CrossbarIsApplied = '';
- $CrossbarPageName = FreeToNormal($CrossbarPageName); # spaces to underscores
- # Add a link to the crossbar page to the "Administration" page.
- $AdminPages{$CrossbarPageName} = 1;
- # If pulling the crossbar div outside the content div, we redefine the
- # default PrintPageContent() function to do this.
- if ($CrossbarDivIsOutsideContentDiv) {
- *PrintPageContentCrossbarOld = \&PrintPageContent;
- *PrintPageContent = \&PrintPageContentCrossbar;
- }
- # If this user is an authenticated administrator, forcefully clear the page
- # cache whenever saving the crossbar page.
- if (UserIsAdmin()) {
- *SaveCrossbarOld = \&Save;
- *Save = \&SaveCrossbar;
- }
- # If the Table of Contents module is also installed, we must prevent handling
- # of any Table of Contents-specific code when in the
- # '<div class="crossbar">...</div>' block. Why? Because: Table of Contents-
- # specific code adds unique identifiers to HTML headers, Crossbar pages may
- # contain HTML headers, and those HTML headers should not have unique
- # identifiers added to them, since adding unique identifiers to Crossbar page
- # headers would add those headers to the Table of Contents for //every// page.
- # (Trust us on this one...)
- if (defined &RunMyRulesToc) {
- *RunMyRulesCrossbarOld = \&RunMyRules;
- *RunMyRules = \&RunMyRulesCrossbar;
- }
- }
- # ....................{ MARKUP =before }....................
- *OldCrossbarApplyRules = \&ApplyRules;
- *ApplyRules = \&NewCrossbarApplyRules;
- sub NewCrossbarApplyRules {
- my $text = shift;
- if (not $CrossbarIsApplied
- and not TextIsFile($text)
- and (not $SidebarName or $OpenPageName ne $SidebarName)) {
- my $crossbar_markup = GetPageContent($CrossbarPageName);
- if ($crossbar_markup and $crossbar_markup !~ m~^(\s*$|$DeletedPage)~) {
- $CrossbarIsApplied = 1;
- $text =~ s~$CrossbarSubstitutionPattern~
- "\n\n<crossbar>\n\n".QuoteHtml($crossbar_markup).
- "\n\n</crossbar>\n\n"~e;
- }
- }
- return OldCrossbarApplyRules($text, @_);
- }
- # ....................{ MARKUP }....................
- push(@MyRules, \&CrossbarRule);
- SetHtmlEnvironmentContainer('div', '^class="crossbar"$');
- sub CrossbarRule {
- if ($bol) {
- if ( m~\G\<crossbar\>~cg) {
- return ($HtmlStack[0] eq 'p' ? CloseHtmlEnvironment() : '')
- .AddHtmlEnvironment ('div', 'class="crossbar"');
- }
- elsif (m~\G\</crossbar\>~cg) {
- return
- CloseHtmlEnvironment('div', '^class="crossbar"$')
- # If pulling the crossbar div outside the content div, we mark the point
- # immediately after the close of the crossbar div with an HTML comment;
- # this allows us to match the contents of the div with a clean regular
- # expression. (A bit complicated, that one...)
- .($CrossbarDivIsOutsideContentDiv ? '<!-- crossbar/-->' : '');
- }
- }
- return;
- }
- =head2 RunMyRulesCrossbar
- Redefines the Table of Contents module's C<RunMyRulesToc> function, when that
- module is installed, so as to apply a hacky, Crossbar-specific fix.
- =cut
- sub RunMyRulesCrossbar {
- my $TocIsApplyingAutomaticRulesOld = $TocIsApplyingAutomaticRules;
- $TocIsApplyingAutomaticRules = '' if InElement('div', '^class="crossbar"$');
- my $html = RunMyRulesCrossbarOld(@_);
- $TocIsApplyingAutomaticRules = $TocIsApplyingAutomaticRulesOld;
- return $html;
- }
- # ....................{ BROWSING }....................
- =head2 PrintPageContentCrossbar
- Redefines the default C<PrintPageContent> function so as to extract the
- crossbar "<div...>" outside the content "<div...>", when so desired.
- =cut
- sub PrintPageContentCrossbar {
- my $html = '';
- my $crossbar_pattern = '(<div class="crossbar">.*?</div>)<!-- crossbar/-->';
- { local *STDOUT;
- open( STDOUT, '>', \$html) or die "Can't open memory file: $!";
- PrintPageContentCrossbarOld(@_);
- close STDOUT; }
- # If the crossbar div is placed immediately after the content div, place it
- # immediately before the content div.
- if (not ($html =~ s~(<div class="content browse" lang="[a-z]*">)$crossbar_pattern~$2$1~)) {
- # Otherwise, if the crossbar div is placed immediately before the end of the
- # content div, place it immediately after the end of the content div.
- $html =~
- s~$crossbar_pattern(.*?<div class="wrapper close"></div></div>)~$2$1~;
- }
- print $html;
- }
- # ....................{ EDITING }....................
- *GetEditFormCrossbarOld = \&GetEditForm;
- *GetEditForm = \&GetEditFormCrossbar;
- sub GetEditFormCrossbar {
- my ($page_name) = @_;
- return
- ($page_name eq $CrossbarPageName ?
- $q->p({-class=> 'crossbar_edit_message'},
- T(UserIsAdmin()
- ? '<strong>You are currently logged in as an administrator.</strong> '
- .'Saving this page propagates your crossbar changes to '
- .'<em>all</em> other pages by forcefully clearing this Wiki\'s page cache.'
- : '<strong>You are not currently logged in as an administrator.</strong> '
- .'Saving this page only propagates your crossbar changes to <em>newly '
- .'created </em> or <em>edited</em> pages — but don\'t let that deter you!')) : '')
- .GetEditFormCrossbarOld(@_);
- }
- # ....................{ SAVING }....................
- =head2 SaveCrossbar
- Clears the page cache whenever a user saves the crossbar page. Why?
- Because the the contents of the crossbar page is injected into every
- other page. Consequently, when the crossbar page changes, the contents
- of other pages are also changed; and must have their caches forcefully
- cleared, to ensure they are changed.
- =cut
- sub SaveCrossbar {
- my ($page_name) = @_;
- SaveCrossbarOld(@_);
- if ($page_name eq $CrossbarPageName) {
- # Prevent the RequestLockOrError() and ReleaseLock() functions from doing
- # anything while in the DoClearCache() method, since the default Save()
- # function already obtains the lock. (We can't obtain it twice!)
- *RequestLockOrErrorCrossbarOld = \&RequestLockOrError;
- *RequestLockOrError = \&RequestLockOrErrorCrossbarNoop;
- *ReleaseLockCrossbarOld = \&ReleaseLock;
- *ReleaseLock = \&ReleaseLockCrossbarNoop;
- # Clear the page cache, now. Go! (Note: this prints a heap of HTML.)
- DoClearCache();
- # Restore locking functionality.
- *RequestLockOrError = \&RequestLockOrErrorCrossbarOld;
- *ReleaseLock = \&ReleaseLockCrossbarOld;
- }
- }
- sub RequestLockOrErrorCrossbarNoop { }
- sub ReleaseLockCrossbarNoop { }
- =head1 COPYRIGHT AND LICENSE
- The information below applies to everything in this distribution,
- except where noted.
- Copyleft 2008 by B.w.Curry <http://www.raiazome.com>.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see L<http://www.gnu.org/licenses/>.
- =cut
|