Footnote Action

See also:
works with:
  • Wikka 1.1.6.2 to 1.2
NOT included in any Wikka version
Last edited by DrewMca:
updated for version 1.2
Thu, 11 Feb 2010 20:10 UTC [diff]

This is the development page for the Footnote action.

Installation


Code


1. New actions/footnote.php file

actions/footnote.php


<?php                                        
#
# Manages and displays footnotes on the page
#
# @package      Actions
# @name         footnote
#
# @authors      DomBonj
#
# @version      0.98
#
# @input        Parameters = note='text of the note'
#
# @uses         Wakka::Action()
# @uses         Wakka::FormClose()
# @uses         Wakka::FormOpen()
# @uses         Wakka::GeSHi_Highlight()
# @uses         Wakka::GetConfigValue()
# @uses         Wakka::Href()
# @uses         Wakka::Link()
# @uses         Wakka::ReturnSafeHTML()
# @uses         Wakka::htmlspecialchars_ent()
#

// i18n strings
if (!defined('FN_NOTES')) define('FN_NOTES', 'Notes');
if (!defined('FN_GO_BACK_MSG')) define('FN_GO_BACK_MSG', 'Jump back to page');
if (!defined('FN_ERROR_REQUEST_FORMAT')) define ('FN_ERROR_REQUEST_FORMAT', 'Incorrect parameter; usage: %s');
if (!defined('FN_ERROR_USAGE')) define ('FN_ERROR_USAGE', "footnote note='note_text'");

if (!function_exists('FNprint'))
{
    function FNerror ($msg) {
        return ("<em class='error'>$msg</em><br />");
    }
 
    function FNwakka2callback($things)
    // the only change made to this function (from formatters/wakka.php in version 1.1.6.3) is in the processing of $things='closetags':
    // instead of directly echoing the HTML closing tags, they are returned as a string
    {
        $thing = $things[1];
        $result='';

        static $oldIndentLevel = 0;
        static $oldIndentLength= 0;
        static $indentClosers = array();
        static $newIndentSpace= array();
        static $br = 1;
        static $trigger_bold = 0;
        static $trigger_italic = 0;
        static $trigger_underline = 0;
        static $trigger_monospace = 0;
        static $trigger_notes = 0;
        static $trigger_strike = 0;
        static $trigger_inserted = 0;
        static $trigger_deleted = 0;
        static $trigger_floatl = 0;
        static $trigger_keys = 0;
        static $trigger_strike = 0;
        static $trigger_inserted = 0;
        static $trigger_center = 0;
        static $trigger_l = array(-1, 0, 0, 0, 0, 0);
        static $output = '';
        static $valid_filename = '';
        static $invalid = '';

        global $wakka;

        if ((!is_array($things)) && ($things == 'closetags'))
        {
        // start of changes to standard wakka2callback
            $ret = "";
            if ($trigger_strike % 2) $ret .= ('</span>');
            if ($trigger_notes % 2) $ret .= ('</span>');
            if ($trigger_inserted % 2) $ret .= ('</span>');
            if ($trigger_underline % 2) $ret .= ('</span>');
            if ($trigger_floatl % 2) $ret .= ('</div>');
            if ($trigger_center % 2) $ret .= ('</div>');
            if ($trigger_italic % 2) $ret .= ('</em>');
            if ($trigger_monospace % 2) $ret .= ('</tt>');
            if ($trigger_bold % 2) $ret .= ('</strong>');
            for ($i = 1; $i<=5; $i ++)
                if ($trigger_l[$i] % 2) $ret .= ("</h$i>");
            $trigger_bold = $trigger_center = $trigger_floatl = $trigger_inserted = $trigger_deleted = $trigger_italic = $trigger_keys = 0;
            $trigger_l = array(-1, 0, 0, 0, 0, 0);
            $trigger_monospace = $trigger_notes = $trigger_strike = $trigger_underline = 0;
            return $ret;
        // end of changes to standard wakka2callback
        }
        // convert HTML thingies
        if ($thing == "<")
            return "&lt;";
        else if ($thing == ">")
            return "&gt;";
        // float box left
        else if ($thing == "<<")
        {
            return (++$trigger_floatl % 2 ? "<div class=\"floatl\">\n" : "\n</div>\n");
        }
        // float box right
        else if ($thing == ">>")
        {
            return (++$trigger_floatl % 2 ? "<div class=\"floatr\">\n" : "\n</div>\n");
        }
        // clear floated box
        else if ($thing == "::c::")
        {
            return ("<div class=\"clear\">&nbsp;</div>\n");
        }
        // keyboard
        else if ($thing == "#%")
        {
            return (++$trigger_keys % 2 ? "<kbd class=\"keys\">" : "</kbd>");
        }
        // bold
        else if ($thing == "**")
        {
            return (++$trigger_bold % 2 ? "<strong>" : "</strong>");
        }
        // italic
        else if ($thing == "//")
        {
            return (++$trigger_italic % 2 ? "<em>" : "</em>");
        }
        // underlinue
        else if ($thing == "__")
        {
            return (++$trigger_underline % 2 ? "<span class=\"underline\">" : "</span>");
        }
        // monospace
        else if ($thing == "##")
        {
            return (++$trigger_monospace % 2 ? "<tt>" : "</tt>");
        }
        // notes
        else if ($thing == "''")
        {
            return (++$trigger_notes % 2 ? "<span class=\"notes\">" : "</span>");
        }
        // strikethrough
        else if ($thing == "++")
        {
            return (++$trigger_strike % 2 ? "<span class=\"strikethrough\">" : "</span>");
        }
        // additions
        else if ($thing == "&pound;&pound;")
        {
            return (++$trigger_inserted % 2 ? "<span class=\"additions\">" : "</span>");
        }
        // deletions
        else if ($thing == "&yen;&yen;")
        {
            return (++$trigger_deleted % 2 ? "<span class=\"deletions\">" : "</span>");
        }
        // center
        else if ($thing == "@@")
        {
            return (++$trigger_center % 2 ? "<div class=\"center\">\n" : "\n</div>\n");
        }
        // urls
        else if (preg_match("/^([a-z]+:\/\/\S+?)([^[:alnum:]^\/])?$/", $thing, $matches))
        {
            $url = $matches[1];
            /* Inline images are disabled for security reason, use {{image action}} #142
            But if you still need this functionality, update this file like below
            if (preg_match("/\.(gif|jpg|png|svg)$/si", $url)) {
                return '<img src="'.$wakka->Link($url).'" alt="image" />'.$wakka->htmlspecialchars_ent($matches[2]);
            } else */

            // Mind Mapping Mod
            if (preg_match("/\.(mm)$/si", $url)) { #145
                return $wakka->Action("mindmap ".$url);
            } else
                return $wakka->Link($url).$matches[2];
        }
        // header level 5
        else if ($thing == "==")
        {
                $br = 0;
                return (++$trigger_l[5] % 2 ? "<h5>" : "</h5>\n");
        }
        // header level 4
        else if ($thing == "===")
        {
                $br = 0;
                return (++$trigger_l[4] % 2 ? "<h4>" : "</h4>\n");
        }
        // header level 3
        else if ($thing == "====")
        {
                $br = 0;
                return (++$trigger_l[3] % 2 ? "<h3>" : "</h3>\n");
        }
        // header level 2
        else if ($thing == "=====")
        {
                $br = 0;
                return (++$trigger_l[2] % 2 ? "<h2>" : "</h2>\n");
        }
        // header level 1
        else if ($thing == "======")
        {
                $br = 0;
                return (++$trigger_l[1] % 2 ? "<h1>" : "</h1>\n");
        }
        // forced line breaks
        else if ($thing == "---")
        {
            return "<br />";
        }
        // escaped text
        else if (preg_match("/^\"\"(.*)\"\"$/s", $thing, $matches))
        {
            $allowed_double_doublequote_html = $wakka->GetConfigValue("double_doublequote_html");
            if ($allowed_double_doublequote_html == 'safe')
            {
                $filtered_output = $wakka->ReturnSafeHTML($matches[1]);
                return $filtered_output;
            }
            elseif ($allowed_double_doublequote_html == 'raw')
            {
                return $matches[1];
            }
            else
            {
                return $wakka->htmlspecialchars_ent($matches[1]);
            }
        }
        // code text
        else if (preg_match("/^\%\%(.*?)\%\%$/s", $thing, $matches))
        {
            $output = ''; //reinitialize variable
            $code = $matches[1];
            // if configuration path isn't set, make sure we'll get an invalid path so we
            // don't match anything in the home directory
            $geshi_hi_path = isset($wakka->config['geshi_languages_path']) ? $wakka->config['geshi_languages_path'] : '/:/';
            $wikka_hi_path = isset($wakka->config['wikka_highlighters_path']) ? $wakka->config['wikka_highlighters_path'] : '/:/';
            // check if a language (and an optional starting line or filename) has been specified
            if (preg_match('/^'.PATTERN_OPEN_BRACKET.PATTERN_FORMATTER.PATTERN_LINE_NUMBER.PATTERN_FILENAME.PATTERN_CLOSE_BRACKET.PATTERN_CODE.'$/s', $code, $matches))
            {
                list(, $language, , $start, , $filename, $invalid, $code) = $matches;
            }
            // get rid of newlines at start and end (and preceding/following whitespace)
            // Note: unlike trim(), this preserves any tabs at the start of the first "real" line
            $code = preg_replace('/^\s*\n+|\n+\s*$/','',$code);
           
            // check if GeSHi path is set and we have a GeSHi highlighter for this language
            if (isset($language) && isset($wakka->config['geshi_path']) && file_exists($geshi_hi_path.'/'.$language.'.php'))
            {
                // check if specified filename is valid and generate code block header
                if (isset($filename) && strlen($filename) > 0 && strlen($invalid) == 0) # TODO: use central regex library for filename validation
                {
                    $valid_filename = $filename;
                    // create code block header
                    $output .= '<div class="code_header">';
                    // display filename and start line, if specified
                    $output .= $filename;
                    if (strlen($start)>0)
                    {
                        $output .= ' (line '.$start.')';
                    }
                    $output .= '</div>'."\n";
                }
                // use GeSHi for highlighting
                $output .= $wakka->GeSHi_Highlight($code, $language, $start);
            }
            // check Wikka highlighter path is set and if we have an internal Wikka highlighter
            elseif (isset($language) && isset($wakka->config['wikka_formatter_path']) && file_exists($wikka_hi_path.'/'.$language.'.php') && 'wakka' != $language)
            {
                // use internal Wikka highlighter
                $output = '<div class="code">'."\n";
                $output .= $wakka->Format($code, $language);
                $output .= "</div>\n";
            }
            // no language defined or no formatter found: make default code block;
            // IncludeBuffered() will complain if 'code' formatter doesn't exist
            else
            {
                $output = '<div class="code">'."\n";
                $output .= $wakka->Format($code, 'code');
                $output .= "</div>\n";
            }

            // display grab button if option is set in the config file
            if ($wakka->config['grabcode_button'] == '1')
            {
                $output .= $wakka->FormOpen("grabcode");
                // build form
                $output .= '<input type="submit" class="grabcode" name="save" value="'.GRABCODE_BUTTON_VALUE.'" title="'.rtrim(sprintf(GRABCODE_BUTTON_TITLE, $valid_filename)).'" />';
                $output .= '<input type="hidden" name="filename" value="'.urlencode($valid_filename).'" />';
                $output .= '<input type="hidden" name="code" value="'.urlencode($code).'" />';
                $output .= $wakka->FormClose();
            }
            // output
            return $output;
        }
        // forced links
        // \S : any character that is not a whitespace character
        // \s : any whitespace character
        else if (preg_match("/^\[\[(\S*)(\s+(.+))?\]\]$/s", $thing, $matches))      # recognize forced links across lines
        {
            list (, $url, , $text) = $matches;
            if ($url)
            {
                //if ($url!=($url=(preg_replace("/@@|&pound;&pound;||\[\[/","",$url))))$result="</span>";
                if (!$text) $text = $url;
                //$text=preg_replace("/@@|&pound;&pound;|\[\[/","",$text);
                return $result.$wakka->Link($url, "", $text);
            }
            else
            {
                return "";
            }
        }
        // indented text
        elseif (preg_match("/\n([\t~]+)(-|&|([0-9a-zA-ZÄÖÜßäöü]+)\))?(\n|$)/s", $thing, $matches))
        {
            // new line
            $result .= ($br ? "<br />\n" : "\n");

            // we definitely want no line break in this one.
            $br = 0;

            // find out which indent type we want
            $newIndentType = $matches[2];
            if (!$newIndentType) { $opener = "<div class=\"indent\">"; $closer = "</div>"; $br = 1; }
            elseif ($newIndentType == "-") { $opener = "<ul><li>"; $closer = "</li></ul>"; $li = 1; }
            elseif ($newIndentType == "&") { $opener = "<ul class=\"thread\"><li>"; $closer = "</li></ul>"; $li = 1; } #inline comments
            else { $opener = "<ol type=\"". substr($newIndentType, 0, 1)."\"><li>"; $closer = "</li></ol>"; $li = 1; }

            // get new indent level
            $newIndentLevel = strlen($matches[1]);
            if ($newIndentLevel > $oldIndentLevel)
            {
                for ($i = 0; $i < $newIndentLevel - $oldIndentLevel; $i++)
                {
                    $result .= $opener;
                    array_push($indentClosers, $closer);
                }
            }
            else if ($newIndentLevel < $oldIndentLevel)
            {
                for ($i = 0; $i < $oldIndentLevel - $newIndentLevel; $i++)
                {
                    $result .= array_pop($indentClosers);
                }
            }

            $oldIndentLevel = $newIndentLevel;

            if (isset($li) && !preg_match("/".str_replace(")", "\)", $opener)."$/", $result))
            {
                $result .= "</li><li>";
            }

            return $result;
        }
        // new lines
        else if ($thing == "\n")
        {
            // if we got here, there was no tab in the next line; this means that we can close all open indents.
            $c = count($indentClosers);
            for ($i = 0; $i < $c; $i++)
            {
                $result .= array_pop($indentClosers);
                $br = 0;
            }
            $oldIndentLevel = 0;
            $oldIndentLength= 0;
            $newIndentSpace=array();

            $result .= ($br ? "<br />\n" : "\n");
            $br = 1;
            return $result;
        }
        // Actions
        else if (preg_match("/^\{\{(.*?)\}\}$/s", $thing, $matches))
        {
            if ($matches[1])
                return $wakka->Action($matches[1]);
            else
                return "{{}}";
        }
        // interwiki links!
        else if (preg_match("/^[A-ZÄÖÜ][A-Za-zÄÖÜßäöü]+[:]\S*$/s", $thing))
        {
            return $wakka->Link($thing);
        }
        // wiki links!
        else if (preg_match("/^[A-ZÄÖÜ]+[a-zßäöü]+[A-Z0-9ÄÖÜ][A-Za-z0-9ÄÖÜßäöü]*$/s", $thing))
        {
            return $wakka->Link($thing);
        }
        // separators
        else if (preg_match("/-{4,}/", $thing, $matches))
        {
            // TODO: This could probably be improved for situations where someone puts text on the same line as a separator.
            //       Which is a stupid thing to do anyway! HAW HAW! Ahem.
            $br = 0;
            return "<hr />\n";
        }
        // mind map xml
        else if (preg_match("/^<map.*<\/map>$/s", $thing))
        {
            return $wakka->Action("mindmap ".$wakka->Href()."/mindmap.mm");
        }
        // if we reach this point, it must have been an accident.
        return $thing;
    }

    function FNprint (&$thisone, $method, $note_txt='', $base_url='')
    {
        if (!isset($footnotes))
        {
            static $footnotes = array();
            static $footnotesindex;
        }
        $out = '';
        if ('addnote' == $method)
        { // display a single footnote and add it to the page's list of footnotes
            $footnotesindex = ($footnotesindex) ? $footnotesindex+1 : 1;
            // no markup for the 'title' attribute
            $title_text = $thisone->ReturnSafeHTML(preg_replace("/(\*\*|\'\'|\#\#|\#\%|\+\+|__|\/\/|\[\[|\]\])/ms", "", $note_txt));
            $out = "<a href='". $thisone->Href(). '#fn' . $footnotesindex . "' title='". $title_text ."' ><sup id='fnback". $footnotesindex. "'>" . $footnotesindex . "</sup></a>";
            $note_txt = preg_replace("(&#39;|&#039;)", "'", $note_txt);
            $note_txt_raw = $thisone->htmlspecialchars_ent($note_txt);
            $footnotes[$footnotesindex] = $note_txt_raw;   
        }
        else if ('list' == $method)
        { // display the list of all the page's footnotes
            $i = 1;
            if (isset($footnotes) && !empty($footnotes))
            {
                $out = "<fieldset class='footnotesbox'><legend>&nbsp;<strong>". FN_NOTES .'&nbsp;</strong></legend>';
                foreach ($footnotes as $note_txt_raw)
                {
                    $out .= "<a id='fn". $i. "' href='". $base_url ."#fnback". $i ."' class='underline' title='". FN_GO_BACK_MSG ."'><span class='underline'>". $i. "</span></a>: ";
                    // format the footnote's text
                    $note_txt = preg_replace_callback(
                        "/(".
                        "\b[a-z]+:\/\/\S+|".                # URL
                        "\[\[[^\[]*?\]\]|".                 # forced link
                        "\*\*|\'\'|\#\#|\#\%|\+\+|__|\/\/". # Wiki markup
                        ")/ms", "FNwakka2callback", $thisone->htmlspecialchars_ent($note_txt_raw) );
                    $out .= ($note_txt . FNwakka2callback('closetags') . '<br />');
                    $i++;
                }
                $out .= '</fieldset><br />';
                $footnotes = array();
                $footnotesindex = 0;
            }
        }
        else if ('purge' == $method)
        { // empty the footnotes array, so they are not displayed at the bottom of the page
            $footnotes = array();
            $footnotesindex = 0;           
        }
        // do nothing silently if unknown $method
        return $out;
    }
} // if !function_exists()

$output = '';
if (!isset($vars['note']))
{
    $output .= FNerror(sprintf(FN_ERROR_REQUEST_FORMAT, FN_ERROR_USAGE));
}
else
{
    $output .= FNprint($this, 'addnote', $vars['note']);
}
echo $output;
?>

2a. [version 1.1.6.4 only] In handlers/page/show.php, go to line 80 and replace the following code block:

        // display page
        echo $this->Format($this->page['body'], 'wakka');
        echo '<div style="clear: both"></div>'."\n";

        echo '</div>'."\n";
        echo '<!--closing page content-->'."\n";

with the following code block:

        // display page
        echo $this->Format($this->page['body'], 'wakka');
        echo '<div style="clear: both"></div>'."\n";
       
        // Footnote action
        if (function_exists('FNprint'))
        {
            echo (FNprint($this, 'list', '', $this->Href()));
        }
       
        echo '</div>'."\n";
        echo '<!--closing page content-->'."\n";

2b. [versions 1.1.6.2 & 1.1.6.3 only] In handlers/page/show.php, go to line 21 and replace the following code block:

        // display page
        echo $this->Format($this->page['body'], 'wakka');

        // if this is an old revision, display some buttons
        if ($this->page['latest'] == 'N' && $this->HasAccess('write'))

with the following code block:

        // display page
        echo $this->Format($this->page['body'], 'wakka');
       
        // Footnote action
        if (function_exists('FNprint')) {
            echo (FNprint($this, 'list', "", $this->Href()));
        }

        // if this is an old revision, display some buttons
        if ($this->page['latest'] == 'N' && $this->HasAccess('write'))


2c. [version 1.2 only] In handlers/page/show.php, replace the following code block:

<?php
                }
            }
            echo '<div class="clear"></div></div>'."\n";
        }
        // display page
        if ($raw == 1)
        {
            echo '<div class="wikisource">'.nl2br($this->htmlspecialchars_ent($this->page["body"], ENT_QUOTES)).'</div>';
        }
        else
        {
            echo $this->Format($this->page['body'], 'wakka', 'page');
        }

?>


with the following code block

<?php
                }
            }
            echo '<div class="clear"></div></div>'."\n";
        }
        // display page
        if ($raw == 1)
        {
            echo '<div class="wikisource">'.nl2br($this->htmlspecialchars_ent($this->page["body"], ENT_QUOTES)).'</div>';
        }
        else
        {
            echo $this->Format($this->page['body'], 'wakka', 'page');
        }
// Footnote action
        if (function_exists('FNprint'))
        {
            echo (FNprint($this, 'list', '', $this->Href()));
        }

?>



3. Add the following line to css/wikka.css:

.footnotesbox { background-color: #eeeeee; padding: 10px; margin-top: 15px; border: #666666 1px solid; font-size: 0.9em; }


CategoryUsercontributions
There are 2 comments on this page. [Show comments]
Valid XHTML :: Valid CSS: :: Powered by WikkaWiki