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
Comments
Comment by DrewMca
2010-02-09 20:51:10
Has this changed for version 1.2? I'm new to Wikka. I uploaded the file, but the code blocks in parts 2a and 2b look different in the show.php file in version 1.2. I dropped the .footnotesbox css into "templates/light/css". Is that the right place?

Thanks.
Comment by BrianKoontz
2010-02-10 08:55:14
Yes, there are probably code differences between 1.1.6.5 and 1.2. If you do get it to work in 1.2, feel free to post the new diff here (and update the info block at the top of the page). Dropping the css file in the dir you indicated will work as long as you are using the "light" theme.
Valid XHTML :: Valid CSS: :: Powered by WikkaWiki