WikiSh Examples and Applications

This page gives examples or mini-applications of WikiSh in action...

Note that examples surrounded by {( ... )} are meant to be placed in the body of a page (even if they are formatted here as multiple lines MXes must always be put on a single line). Examples without these brackets are meant to be run from the control panel. The control panel allows you to format your commands on multiple lines and is thus much easier to work with.

There is a live test site available to try out WikiSh. Many of the examples below already contain links, but you can also go to the control panel for more generic testing at http://pmwiki.qdk.org/pmwiki.php/WikiSh/ControlPanel. Feel free to use other pages on that site to test out other functionality as well.

List files in a given directory with optional comments

  • You can look here for a sandbox example of this script
  • Place the following source in a page (Test.CommentedLs in my case):
    • Change the TEXTFILE--uploads/* to a different directory/wildcard set if desired
{(wikish source {$FullName}#ListCommented)}

(:if false:)
[[#ListCommented]]
##
## Comments must be listed on a page named with THIS pagename
## but with a -Comments suffix (MyPage-Comments).  Comments
## must be in the form of a simple table with the first column
## being the filename and the second column being the comment:
##   ||filename.txt||This is a comment for filename.txt ||
##   ||spreadsheet.xls||This is a comment for spreadsheet.xls ||
##
## CHANGE LOCATION OF COMMENT PAGE ON NEXT LINE IF NEEDED
set -s commentpage = "{$FullName}-Comments"
## CHANGE FILESPEC ON NEXT LINE TO REFLECT LOCATION OF FILES
ls TEXTFILE--uploads/* | while read filename
do
  set -s filename = "`basename 'TEXTFILE--${filename}'`"
  set -s dataline = "`grep '^\|\|${filename}\|\|' ${commentpage}`"
  if test -n "${dataline}"
  then
    echo "${dataline}"
  else
    echo "||${filename}|| ||"
  fi
done
[[#ListCommentedEnd]]
(:ifend:)
  • Place the following source in a page (Test.CommentedLs-Comments in my case):
    • Put your comments here -- these are just examples based on files in my uploads directory
    • Make sure the text in the first column exactly matches the filename
||foo||this is a comment for foo||
||2010-05-18-1.txt||this is a comment for May 18||
||Test||and a comment for TEST||
  • Make sure wikish has text file read privilege on that directory (config.php):
    • Be aware of the security implications
include_once("$FarmD/cookbook/WikiSh.php");
include_once("$FarmD/cookbook/SecLayer.php");
$EnableWikiShTextRead = true;
slAddAuth($wshAuthText, "uploads/*", "read");
slAddAuth($wshAuthPage, "Test.CommentedLs*", "read");
  • If you intend to put comments for a large number of files it may be convenient to list your files in the appropriate form via EditMX. Once that recipe (and wikish) is installed, simply place this line of source in your comments page and when you save it it will be replaced with the appropriately formatted list of files:
{ROE(wikish ls TEXTFILE--uploads/* | sed -e 's/^uploads\//||/' -e 's/$/|| ||/')}

Allow people to sign up for "slots" in a table

Say you wanted teachers to sign up for study hall coverage or your book club wants people to sign up for various parts of the meal for the next 3 meetings. It would be nice to have a signup form that allows them to just click on a slot and have their name filled in automatically.

  • In the way it's coded below your row and column labels must be single-word (no spaces, etc.)
  • Obviously you would need to change the row/column labels to fit your given task, but you can put however many in there and it will dynamically recreate the table accordingly
  • Right now there's no way for an administrator to over-ride and unsign-up someone (other than going in and manually deleting the PTV definition). It would be fairly trivial to allow admins to have full privileges...
  • Currently coded assuming authuser and having read attr on the page be at least id:* so that people have to be logged in so there is some value for $AuthId (See alternate version, below, if you don't want to use authuser or require login)
  • This solution requires read/edit privileges on the current page (both from pmwiki as well as from seclayer for wikish)
  • If you want to see this in action take a look at http://pmwiki.qdk.org/pmwiki.php?n=Signup.Form (you can log in using ab/ab)
  • An alternate version (source code is different than below) that does not require logging in can be found at http://pmwiki.qdk.org/pmwiki.php?n=Forms.SignupList (you will need to sign in to access that site, but the code itself does not require you to sign in - it just uses a freeform text field to determine your name).

Put this source in a page somewhere:

{(wikish source {$FullName}#MkForm)}

(:if false:)
[[#MkForm]]
wikish_form QUICKFORM PROCESS
# Process any signups...
for i in ${!cell_*}
do
   if test -n ${i}
   then
      if test ${${i}} == 'X'
      then
         if test {$:${i}} == {$AuthId}
         then
            writeptv {$FullName} ${i}=
            wikish_form REDIRECT target={$FullName}
         else
            echo "Unable to UNsign for someone besides yourself."
         fi
      else
         writeptv {$FullName} ${i}={$AuthId}
         wikish_form REDIRECT target={$FullName}
      fi
   fi
done
# Now draw the table
echo "(:table border=1:)"
for r in '' 8am 9am 10am 11am 12pm
do
   for c in '' Mon Tues Wed Thur Fri
   do
      if test -z ${r}
      then
         echo "(:cell align=center:)${c}"
      else
         if test -z ${c}
         then
            echo "(:cellnr valign=center align=center:)${r}"
         else
            echo "(:cell valign=center align=center:)"
            if test -n {$:cell_${c}_${r}}
            then
               echo "{$:cell_${c}_${r}} (:input submit cell_${c}_${r} 'X':)"
            else
               echo "(:input submit cell_${c}_${r} 'Sign Up':)"
            fi
         fi
      fi
   done
done
echo "(:tableend:)\n(:input end:)"
[[#MkFormEnd]]
(:ifend:)

Create a page from a form interface, choosing the appropriate template from a list

Sometimes a single template is not sufficient -- users need the capability of choosing from different templates. This little form allows users to enter a group and page and choose a template to create a new page. The list of templates is created dynamically based on the contents of the group Templates.*.

The form might look like this (your templates will be different):

Group:
Title / Pagename:
Template: This is Template A (Template.A)
Template B (Template.B)
For Special Pages (Template.C)

  • Create a page called Test.MkNewPage with this source:
(:linebreaks:)
{(wikish MkNewPage)}
(:table:)
(:cellnr align=right:)Group:
(:cell align=left:)(:input text name=group:)
(:cellnr align=right:)Title / Pagename:
(:cell align=left:)(:input text name=pn:)
(:cellnr align=right valign=middle:)Template:
(:cell align=left:){(wikish RadioTemplates)}
(:tableend:)
(:input submit name=submit value=Create:)
(:input end:)
  • Create a page called WikiSh.Test-MkNewPage with this source (note that if you change the name of the page above you will need to change the name of this page to correspond):
function RadioTemplates
{
   set -s LIST = normal
   for i in Templates.*
   do
      if test -n "{${i}$:MenuText}"
      then
         echo '(:input radio name=template value="${i}":){${i}$:MenuText} (${i})'
      else
         echo '(:input radio name=template value="${i}":)${i}'
      fi
   done
}
function MkNewPage
{
   wikish_form quickform process
   if test -z "${submit}"
   then
      exit
   fi
   if test -z "${group}"
   then
      echo "%red%ERROR: You must enter a group name%%"
      exit
   fi
   if test -z "${pn}"
   then
      echo "%red%ERROR: You must enter a title / pagename%%"
      exit
   fi
   set -s newpn = `sed 's/[^a-zA-Z0-9_.-]//g' - "${pn}"`
   if test -f "${group}.${newpn}"
   then
      echo "%red%ERROR: Page [[${group}.${newpn}]] already exists%%"
      exit
   fi
   echo "("":title ${pn}:"")" >${group}.${newpn}
   if test -n ${template}
   then
      grep -v MenuText ${template} >>${group}.${newpn}
   fi
   #echo "[[${group}.${newpn}|+]]" >>Test.NewPageList
   wikish_form redirect target=${group}.${newpn}?action=edit
}
  • Create a set of templates in the "Templates" group. For each one include some text for the menu using this syntax:
(:MenuText:This Text will appear verbatim in the menu of templates:)
  • Any page in the Templates group will automatically be included in the menu
  • Make sure that WikiSh security allows read and create privilege for any groups/pages that may be created. If you are using slParsePage() then this would look like this in SiteAdmin.WikiShAuth to allow read and create on any group or page (obviously if you have protected a group via pmwiki authorizations that protection will be respected):
*.*:read,create

View the differences between a page at a given point in time compared to the current version (form-based)

If you view page history normally it shows you a diff output between each revision. Sometimes you want to be able to do a comparison with a revision several back and the current without seeing the in-between diffs. This form allows provides this functionality.

  • Installation - place this code on a page:
{(wikish_form quickform process)}
page: (:input text pn:)
when: (:input text when:)
(:input submit View View:)

{(wikish source {$FullName}#ViewDiff)}

(:if false:)
[[#ViewDiff]]
if test -n ${View}
then
   if test -z "${pn}" || ! test -f "${pn}"
   then
      echo "ERROR: Must enter a valid page first"
      exit
   fi
   if test -z '`ftime "%s" "${when}"`'
   then
      echo "ERROR: Must enter a date/time in the 'when' field"
      exit
   fi
   #echo "DEBUG: 
   echo "\n----\n"
   diff ${pn}@`ftime "%s" "${when}"` ${pn}
   echo "\n----\n"
fi
[[#ViewDiff]]
(:ifend:)
  • If you want a pretty highlighted type of diff you can install the PEAR Text_Diff and then put this in your config.php:
if ($pagename == 'Test.Diff' && $action=='browse') {
    $DiffFunction = 'mydiff';
    include_once("Text/Diff.php");
    include_once("Text/Diff/Renderer.php");
    include_once("Text/Diff/Renderer/inline.php");
    function mydiff($text1, $text2)
    {
        $diff = &new Text_Diff(explode("\n", $text1), explode("\n", $text2));
        $renderer = &new Text_Diff_Renderer_inline();
        return($renderer->render($diff));
    }
}

View a historical revision of a page as of a certain time - accessed via a form

Sometimes it is convenient to be able to view a given revision of a page (a 'snapshot' at a certain time) from a simple form-based interface.

  • Installation - place this code on a page:
{(wikish_form quickform process)}
page: (:input text pn:)
when: (:input text when:)
(:input submit View View:)

{(wikish source {$FullName}#ViewHistory)}

(:if false:)
[[#ViewHistory]]
if test -n ${View}
then
   if test -z "${pn}" || ! test -f "${pn}"
   then
      echo "ERROR: Must enter a valid page first"
      exit
   fi
   if test -z '`ftime "%s" "${when}"`'
   then
      echo "ERROR: Must enter a date/time in the 'when' field"
      exit
   fi
   #echo "DEBUG: 
   echo "\n----\n"
   cat ${pn}@`ftime "%s" "${when}"`
   echo "\n----\n"
fi
[[#ViewHistoryEnd]]
(:ifend:)
  • Your page will look something like this (note that this form is inactive on this page)

page: when:

  • Simply enter the pagename and some date/time ("1 hour ago", "yesterday", "1/1/2009 01:00am", etc. - whatever is acceptable in {(ftime ...)}) and click on the "View" button - the revision of the page you are requesting will appear below the form between horizontal lines.
  • WikiSh needs to be activated for this page and needs to have read privilege on any page that you will want to view, but it need not be activated on any other page. (Note that pmwiki page authorizations will still be honored - nobody will be able to read a page they are not authorized to view.) Below is the minimal configuration in config.php you would need to activate this form (assuming the form will exist in the page Test.ViewHistory):
if ($pagename == 'Test.ViewHistory') {
   include_once("$FarmD/cookbook/WikiSh.php");
   include_once("$FarmD/cookbook/SecLayer.php");
   slAddAuth($wshAuthPage, "*.*", "read");
}

Compare and use historical revisions of pages

Pmwiki stores old revisions of pages whenever an edit is made. By looking at the "History" link you can see each of those revisions with the relative changes. From that view you can easily restore any of the old versions of the page. But sometimes you want to compare 2 older versions or a much older version directly with the current version without having to understand each of the intervening revisions. Or perhaps you want the older version of the file but in a different page rather than the current. Obviously this can be accomplished via multiple copy/paste operations, but WikiSh makes it much easier:
(All examples here are from within the control panel, as is appropriate for administrative tasks such as these)

Comparing the version of the page from 3 days ago with the version 1 day ago in the control panel

diff Page@`ftime "%s" "3 days ago"` Page@`ftime "%s" "yesterday"`

Copying the version of a page from a certain date to a new page without altering the most up-to-date version of that page

cat Mygroup.MyPage@`ftime "%s" "January 1, 2009 9:39am"` >GroupB.NewPage

Restoring several pages from the version they had at a certain time (for instance, if a spammer struck several pages at once)

for i in Page1 Page2 Page3
do
   cat ${i}@`ftime "%s" "01/01/2009 12:57pm"` >${i}
done

Create a group (form-based) and protect it immediately

  • User X fills in the name of a group and presses "create". Pages from specified locations are copied into that group and the group is immediately protected with a read password which only allows user X to use it.
  • In SiteAdmin.WikiShAuth set the following permissions:
(:if name HomeGroups.Create:)
*:create,read
*.GroupAttributes:attr,forceedit
(:ifend:)
  • Create the page HomeGroups.Create with the following source:
{(wikish_form QUICKFORM PROCESS)}
New Group: 
(:input text name=GroupName:)\\
(:input submit name=submit value=Create:)
(:input end:)

{(wikish source {$FullName}#validate)}

(:if false:)
[[#validate]]
if test -n "${submit}"
then
   if ! test -n "${GroupName}"
   then
      echo "ERROR: Must specify the new group name."
      exit
   fi
   if test -f ${GroupName}.Page1 || test -f ${GroupName}.Page2
   then
      echo "ERROR: ${GroupName} already exists."
      exit
   fi
   if test -z "$AuthId"
   then
      echo "ERROR: You must be [[{$FullName}?action=login|logged in]] as a valid user before creating a group."
      exit
   fi
   echo "Creating New Group...\\\n\\\n"
   cp HomeGroup.Page1 ${GroupName}
   echo "" >${GroupName}.GroupAttributes
   chmod --read:id:$AuthId ${GroupName}.GroupAttributes
   echo "\\\n\\\n...DONE!  Click [[${GroupName}.Page1|here]] to go to your new group - only you have permission to read/edit."
fi
[[#validateend]]
(:if:)
  • Now (VERY IMPORTANT) put an edit password on that page
  • Your page will look like this:

New Group:

Write to a PTV by URL

  • Requires MiscMX and Toolbox for writeptv functionality
  • Requires no write privileges whatsoever on the part of WikiSh since writeptv() is not part of WikiSh. WritePTV will respect pmwiki permissions (i.e., you can't write a PTV to a page you don't have edit permission on)
  • Wikish and MiscMX must be release 2008-09-20 or later.
  • Put this MX on a page somewhere (let's call it Test.Write2PTV):
{(wikish --expandvars writeptv ${!page} ${!var}=${!value})}
  • Now simply access that page specifying the page, the variable name, and the value something like this:
http://www.example.com/pmwiki/pmwiki.php?n=Test.Write2PTV?page=Test.X?var=myvar?value=myvalue
  • Now if you look at Test.X you will see a hidden PTV created named "myvar" with the value "myvalue" like this:
(:myvar:myvalue:)
  • Specifying a PTV format other than hidden PTV is possible - see MiscMX for further details
  • Setting additional variables, etc is simply a question of specifying them in the MX and in the URL (note that writeptv is most efficient when writing many PTVs to one page in a single call rather than being called multiple times)
  • Alternate solutions:
    • Fox does an excellent job of handling PTVs and could probably do something similar via a form
    • Could also be accomplished via httpvariables rather than wikish (still using MiscMX and Toolbox) -- just a different way of getting at the $_REQUEST variables. However, either you'll need to define a LATE rule for MX processing or you'll need to redefine the httpvariables rule to run BEFORE the MX.
      • Here's how to define the "late" rule for markup expressions (in config.php):
Markup('{latemx(', '>if',
  '/\\{latemx(\\(\\w+\\b.*?\\))\\}/e',
  "MarkupExpression(\$pagename, PSS('$1'))");
  • Here's what the MX would look like using http variables and the late MX rule above:
{latemx(writeptv {$!page} {$!var}={$!value})}

Self-Register for a site using AuthUser

  • Requires MiscMX for the crypt functionality
  • Requires read, forceread, append, and forcewrite privilege (SecLayer) on SiteAdmin.AuthUser
    • NOTE that it is SO important to only give this on the page where the legitimate form resides and be absolutely sure that that page is protected from editing so nobody else can change the script to do something else.
    • Here is the text in SiteAdmin.WikiShAuth if you are using slParsePage() to read in your authorizations (you'll need to change the pagename from Test.Signup to whatever page your form sits in):
(:if name Test.Signup:)
SiteAdmin.AuthUser:read,append,forceread,forceedit
(:ifend:)
  • At the risk of being overly cautious, please check again that only trusted admins have edit access to the page which contains the script (i.e., the one that has forceedit
  • On the page on which the form will reside place this source:
{(wikish source {$FullName}#signup)}
Sign up for access to this wiki:\\
Username: (:input text username:)\\
Password: (:input password password:)\\
(:input submit submit Submit:)
(:input end:)

(:if false:)
[[#signup]]
wikish_form quickform process
if test -z "${submit}"
then
   exit
fi
if grep -q "^${username}:" SiteAdmin.AuthUser
then
   echo "%red%That user already exists.  Contact your administrator to change your password.%%"
   exit
fi
if test "${username}" ~= "/[^a-z0-9_]/i"
then
   echo "%red%Only alphabetic characters, numeric digits, and underscore are allowed in the username.%%"
   exit
fi
if test "${password}" ~= "/ /"
then
   echo "%red%No spaces in the password.%%"
   exit
fi
# OK, I think we're legitimate at this point - go ahead and add it to the AuthUser table
echo "${username}:`crypt ${password}`" >>SiteAdmin.AuthUser
echo "%red%Success%%"
[[#signupend]]
(:ifend:)
  • That will result in a page that looks like this:

Sign up for access to this wiki:
Username:
Password:

  • Obviously you can change the look of the form - it's just basic, plain-vanilla pmwiki source - and the wording on the error messages is changed just by editing them (including putting CSS styles or whatever within the quotes if you like)
  • For a live example go to http://pmwiki.qdk.org/pmwiki.php/WikiSh/Signup

Produce multiple pagelists with the same selection criteria

The request it to produce 3 pagelists with the same selection criteria, but each list to be displayed in a different order. See here for the initial request. See here for PM's response giving the $PagelistCacheDir solution that is probably much faster than this.

  • Requires no read nor edit authorization
  • Requires PowerTools
{(wikish set -s pagelist = "`pagelist wrap=inline $:obsolete=-yes`")}
(:table:)
(:cellnr:)Sorted by creation time (most recent first)
(:cell:)Sorted by modification time (most recent first)
(:cell:)Sorted alphabetically
(:cellnr:)(:pagelist name=${pagelist}  fmt=#bygroup order=-ctime :)
(:cell:)(:pagelist   name=${pagelist}  fmt=#bygroup order=-time :)
(:cell:)(:pagelist   name=${pagelist}  fmt=#bygroup  :)
(:tableend:)
  • Explanation
    • Do the pagelist with the selection criteria and save it off (fmt is "name1,name2,name3" by default)
    • Use that variable in the other pagelist invocations below using name=${pagelist} as the selection criteria
    • The pagelist will be executed 4 times rather than 3 times, but the last 3 times there will be no need to parse for PTVs so it should be significantly faster.

Automatically reply to email messages in a given format

For a beta test I was running of wikibox I got tired of having to load up a different user's email, open the new email, and reply every time I wanted to moderate a message. About 90% of the time I wanted the moderator to approve the message but I wanted it to happen automatically. The script below enabled me to automatically reply to messages, altering the subject and body as necessary, simply by loading a single page. (I could then load this page periodically via cron if I wanted it to go perpetually or else just run the wget by hand to quickly load the page.)

  • Read-only
  • Mailx must be enabled and an appropriate profile set up for both POP3 and SMTP
  • WikiMail is required whenever you are doing anything with mailx or fetchmail
  • This source was placed in the page WikiBox.Moderate:
(:linebreaks:)
{(wikish Moderate)}
  • This source was placed on the page WikiSh.WikiBox-Moderate:
function Moderate
{
echo "Checking..."
# Loop through, getting a message, using the profile "moderator" as long as that mailbox has any messages
while fetchmail --profile:moderator --body:body --from:from --subject:subject
do
   echo "====\n'''from=`sed -e 's/</&lt;/g' -e 's/>/&gt;/g' - '${from}'`, subject=${subject}'''"
   # Make sure the subject has the word "confirm" in it before replying - indicates it's a wikibox message
   if grep -qi CONFIRM - "${subject}"
   then
      # Set the moderator's oh-so-secret password to "xyz" in the body of the message
      set -s newbody = "`sed 's/wikibox-password:/wikibox-password:xyz/' - '${body}'`"
      # Update the log of all messages we've sent from this script
      echo "TO: ${from}, SUBJECT: ${subject}" >>WikiBox.SentLog
      # Send the message, giving appropriate messages upon success or failure
      if mailx -f wikibox_mod@qdk.org -t "${from}" -c "username@example.com" -s "${subject}" - "${newbody}"
      then
         echo "Mail Sent."
      else
         echo "ERROR: Mail may not have been sent."
      fi
   else
      # Message was not from wikibox - error.  Put the error in an error log page.
      echo "ERRORS.  See [[WikiBox.Moderate-Errors]]
      echo "====\n'''FROM:''' ${from}\n'''TO:''' ${to}\n'''SUBJECT:''' ${subject}\n${body}" >>WikiBox.Moderate-Errors
   fi
done
echo "Done Checking..."
# Close the POP3 handle so messages actually get deleted
fetchmail --profile:moderator --close
}

Do a pagelist which includes the selection criteria of multiple pagelists.

For instance, only a single link=page can be included in a given pagelist specification. Using this method multiple pagelists can be combined into a single one.

  • This MX requires no write privileges (It will write only to Temp.X pages which are defined to be in-memory pages.)
  • This solution requires PowerTools
  • The example below gives us a pagelist of all pages with a link to either Category.A or Category.B
    • Note that the MX must be formatted on your page in a SINGLE line - additional linebreaks below are merely for appearance sake and must be deleted in your page.
{(wikish pagelist sep=\n link=Category.A wrap=inline | cat >Temp.A; 
pagelist sep=\n link=Category.B wrap=inline | cat >>Temp.A; 
pagelist wrap=inline fmt=#simple name=`sort --uniq --newline:, Temp.A`)}
  • Additional intermediate pagelists can be added using the pattern of the 2nd pagelist (make sure you include the | cat >>Temp.A and do not use | cat >Temp.A).
  • Additional pagelist specifications (order, format, selection criteria, etc.) can be supplied to the 3rd pagelist
  • Solving the same problem using a function you can make a much more author-friendly source:
    • This source should go either in WikiSh.Profile if you want the function to be available in many pages or in WikiSh.Group-Page if you want it available only on that page
function CategoryByWildcards
{
   if test -f Temp.A
   then
      rm Temp.A
   fi
   for i in
   do
      pagelist sep=\n link= wrap=inline | cat >>Temp.A
   done
   sort -u —newline:, Temp.A
}
  • Then on the page where you want the pagelist to appear you would have this source:
{(wikish pagelist wrap=inline fmt=#simple name=`CategoryByWildcards Category.[AB]`)}

or

{(wikish pagelist wrap=inline fmt=#simple name=`CategoryByWildcards Category.A Category.B`)}
  • Below is the same example, but separated out into intermediate steps so you can see what each individual step will give:
(:linebreaks:)
{(wikish pagelist sep=\n link=Category.A wrap=inline | cat >Temp.A)}
After A
{(cat Temp.A)}
{(wikish pagelist sep=\n link=Category.B wrap=inline | cat >>Temp.A)}
After B
{(cat Temp.A)}
Sorted
{(sort -u Temp.A)}
Sep by ,
{(sort --newline:, -u Temp.A)}
Pagelist
{(wikish pagelist wrap=inline fmt=#simple name=`sort --uniq --newline:, Temp.A`)}
  • Below is a more generalized example, allowing any type of selection criteria:
function PagelistUnion
{
if test -f Temp.A
then
   rm Temp.A
fi
for i in ${*}
do
   pagelist sep=\n ${i} wrap=inline | cat >>Temp.A
done
sort -u --newline:, Temp.A
}
  • And in the call (where you want the actual pagelist to appear):
    • This particular example will give the UNION (i.e., connect the conditions by OR instead of by AND) of 3 conditions:
      • link=Category.A
      • link=Category.B
      • name=Test.A*
{(wikish pagelist wrap=inline fmt=#simple name=`PagelistUnion link=Category.A link=Category.B name=Test.A*`)}

Pagelist with redirect for single page returns

  • I would like to run a pagelist and then if there is only a single page result from that pagelist I would like to be redirected to that page. Problem initially requested in http://pmichaud.com/pipermail/pmwiki-users/2008-August/051844.html.
  • This requires no read/write access.
  • This requires PowerTools as a separate recipe
  • Note that the following must be in the page as a single line (newlines have been inserted for formatting purposes here but they will cause problems if you leave them in).
{(wikish set -s pglst = '`pagelist wrap=inline sep=\n name=Test.NewPage`'; 
if test -n "${pglst}" && test `wc -lq - "${pglst}"` -eq 1; then; wikish_form redirect target=${pglst}; 
else; echo "${pglst}"; fi)}
  • Note that the absolutely simple pagelist is necessary because otherwise it's too hard to get the pagename. (One alternative would be to run 2 pagelists, but that's a pretty tough performance hit.)
  • I believe the request actually had more to do with search and less to do with pagelist, but this is what's available...

Look for a page with the word "password" on it in the Site group

  • Requires read privilege on the Site group
  • This solution could obviously be accomplished via a pagelist or the search box. I was trying to find the page which formatted the login form; I new it was in the Site group, but I couldn't remember the name. Since I had the control panel up it was faster to type this than to search somewhere else and scroll through the results
grep --line_prefix:"'''PAGENAME:''' " Password Site.*

OK, so it's a stretch as an application - think of it more as an example... :-)

Provide a form in which a title can be entered and a page will be automatically created

This allows accents, better specification of upper/lower case, etc and the pagename will be related but somewhat abstracted. See http://pmichaud.com/pipermail/pmwiki-users/2008-July/051789.html for the description of the problem.

  • Requires write/create privilege on the group where the pages will be created (Test in this example) and write/append privilege to the page where the list of created links is maintained
  • Requires version 2008-08-02 or later of WikiSh (with updated version [below] which does the redirect)
  • Create the form in a page called Test.MkNewPage
(:linebreaks:)
{(wikish MkNewPage)}
Make new page: (:input text name=pn:)
(:input submit name=submit value=Create:)
(:input end:)

List of pages created:\\
(:include Test.NewPageList:)
  • Place the validation code (function definition for the "MkNewPage" function) in the page named WikiSh.Test-MkNewPage
function MkNewPage
{
   wikish_form quickform process
   if test -z "${submit}"
   then
      exit
   fi
   if test -z "${pn}"
   then
      echo "ERROR: You must enter a title"
      exit
   fi
   set -s newpn = `sed 's/[^a-zA-Z0-9_.-]/_/g' - "${pn}"`
   if test -f "${newpn}"
   then
      echo "ERROR: ${newpn} already exists"
      exit
   fi
   echo "("":title ${pn}:"")" >${newpn}
   echo "[[${newpn}|+]]" >>Test.NewPageList
   wikish_form redirect target=${newpn}?action=edit
}
  • Note the name of the page -- it must be WikiSh.GROUP-PAGE (with the appropriate GROUP and PAGE with a dash in between) in order for the function definition to be found for this particular page.
  • If this were a function you wanted to always be available you could put it in WikiSh.Profile - this page is always searched for function definitions
  • You can use (:include ...:) and (:if ...:) and (:comment ...:) directives on these pages. This greatly empowers these startup pages.

Allow redirects from a specific group to elsewhere in the wiki (ala TinyURL)

Create a list of pages within one group which will be aliases for other pages elsewhere. See RedirectMap for a non-wikish solution.

  • Does not require any write privilege
  • Create the list of aliases and replacements in a page called Tiny.List:
Tiny.Short::MyVeryLongGroupNameThatIAlwaysWriteWrong.ALongPageThatShouldHaveAShorterName
Tiny.ApproveUrl::Test.ApproveUrl
Tiny.CL::WikiSh.ControlPanel
  • Place this code in Tiny.GroupFooter (all one line):
{(wikish set -s target = "`grep '{*$FullName}:' Tiny.List | sed
's/^.*:://'`"; if test -n "${target}" && test `wc -l - ${target}` -eq
1; then; wikish_form redirect target=${target}; else; echo "Could not
redirect from @@{*$FullName}@@ to @@${target:-Nonexistent Target}@@"; fi)}
  • If that code was placed in a multi-line format (for use in the control panel or via wikish source) it would look like this:
set -s target = "`grep '{*$FullName}:' Tiny.List | sed 's/^.*:://'`"
if test -n "${target}" && test `wc -l - ${target}` -eq 1
then
   wikish_form redirect target=${target}
else
   echo "Could not redirect from @@{*$FullName}@@ to @@${target:-Nonexistent Target}@@@"
fi
  • EXPLANATION:
    • The first line looks up the alias in Tiny.List via grep, massages the text into just the target via sed, and then assigns that value to the variable target
      • Grep simply searches a page (Tiny.List in this case) for a pattern ({*$FullName} followed by a colon in this case)
      • Sed allows search & replace (among other things). In this case it searches for any text on the line up to and including a double-colon and replaces it with nothing.
    • The if condition checks to make sure (a) we found something and (b) we found exactly 1 one something
    • If everything is good then go ahead and redirect to that target
    • Otherwise print a message indicating what you tried to look up and what you found in the list
      • The ${var:-alternate} variable format indicates that if there is nothing in the variable then use the alternate text. This allows us to easily print a readable value if we find nothing in the list of aliases.

Create numbered sub-pages

Whatever page this wikish script is placed on will now have a link to create a new subpage named by suffixing "-01", "-02", etc. It can be used in many instances, but my specific need was to have something in my ViewTemplate for my wikiforms application so I could have additional pages with other information besides that held in the fields in the 'database'.
After putting this together Hans pointed out SerialPageNames which provides this functionality.

  • Does not require any write privilege
  • Requires pagelist from PowerTools
  • Requires WikiSh version 2008-07-10 or later
  • This particular implementation allows up to 99 sub-pages (page-01 to page-99) but you could increase that easily simply by adding more digits as needed
  • This script should be placed wherever you want the link to create the sub-page (be sure to edit out the (NO NEWLINE) and put it all on a single line):
{(wikish set -s lastpage = "`pagelist sep=\n name={*$FullName}-[0-9][0-9] | tail -n 1`"; (NO NEWLINE)
if test -z "${lastpage}"; then; set -s newpage = "{*$FullName}-01"; else; (NO NEWLINE)
set newnum = ${lastpage#*-} + 1; set -s newstr = 00${newnum}; set -s newpage = "{*$FullName}-${newstr,-2}"; (NO NEWLINE)
fi; echo "[[${newpage}?action=edit|Create Sub-Page]]")}
  • Elsewhere on the main page (or somewhere else) you are going to want links to these pages. The easiest way is thru pagelist:
(:pagelist wrap=inline name={*$FullName}-[0-9][0-9] fmt=#title2:)
  • If you wanted it to be a little more readable you could place it in its own section on the page and then call it via source:
    • You could also put it on another page (such as WikiSh.Scriptes#SubPageLink) if you wanted to use it in multiple areas within your wiki.
{(wikish source {*$FullName}#SubPageLink)}

...

(:if false:)
[[#SubPageLink]] 
set -s lastpage = "`pagelist sep=\n name={*$FullName}-[0-9][0-9] | tail -n 1`"
if test -z "${lastpage}"
then
   set -s newpage = "{*$FullName}-01"
else
   set newnum = ${lastpage#*-} + 1
   set -s newstr = 00${newnum}
   set -s newpage = "{*$FullName}-${newstr,-2}"
fi
echo "[[${newpage}?action=edit|Create Sub-Page]]"
  • EXPLANATION
    • first get a pagelist of all sub-pages and snag the last one via tail - this will give us the highest-numbered sub-page that currently exists
    • if there is no last page then simply set up the ${newpage} value
    • if there is a last page then get strip off everything up to and including the dash in the pagename and add 1 to that final number
      • then prepend some 0's to that number as a string
      • then use the substr form of the variable modifier to get only the last 2 characters and to set up a ${newpage} variable
    • Finally output the actual link using the ${newpage} values set above

Validate form data and collect it on a separate page in a table

This simply demonstrates a very simple example of validating data from a form (server-side) and then placing the data in a table on another page to collect the data.

  • Requires write privilege for the page where the data will be collected (Test.FormData in this example)
  • This example allows people to vote for their favorite cartoon characters.
    • First and last name are validated to be sure the first character is a capital letter
    • Age is validated to be sure it is 18 or above
    • If all validations are successful then the data is appended to the Test.FormData and a message printed on this page.
{(wikish_form quickform process)}

|| width=50%
|| FIRST NAME:||(:input text name=fname:) ||
||  LAST NAME:||(:input text name=lname:) ||
||        AGE:||(:input text name=age:)   ||
||   FAVORITE:||(:input radio cartoon Fred:) Fred Flinstone\\
(:input radio cartoon Barney:) Barney Rubble||
(:input submit name=Submit value=Submit:)
(:input end:)
{(wikish source {*$FullName}#Validate)}

(:if false:)
[[#Validate]]
#echo "Validating..."
if test -z ${Submit}
then
   exit
fi
set err = 0
set -s out = Test.FormData
# Validate that the names have a capital letter as a 1st character
if test ${fname:0:1} < 'A' || test ${fname:0:1} > 'Z'
then
   echo "ERROR: You must capitalize your first name.  Form not processed."
   set err = 1
fi
if test ${lname:0:1} < 'A' || test ${lname:0:1} > 'Z'
then
   echo "ERROR: You must capitalize your last name.  Form not processed."
   set err = 1
fi
# Validate that the person is 18 or older
if test ${age} -lt 18
then
   echo "ERROR: You must be 18 years of age to vote.  Form not processed."
   set err = 1
fi
if test -z ${cartoon}
then
   echo "ERROR: You must select either Fred or Barney.  Form not processed."
   set err = 1
fi
# If no errors then write out this line of data to the data output page
if test ${err} -eq 0
then
   echo "||${fname} ${lname} || ${age}||${cartoon}||" >>${out}
   echo "One vote processed for ${cartoon} -- see [[${out}]]"
fi
[[#EndValidate]]
(:ifend:)

Assist administrators with approving URLs

ApproveUrls allows an administrator to limit the external URLs placed on his site. However, it requires careful follow-up by the administrator to make sure valid URLs are approved. This script facilitates doing a global check of what URLs have not been approved so they can either be deleted or approved by the administrator.

  • Requires PowerTools.
  • This script will enable an administrator to see all URLs potentially affected by approveurl (both those approved and those not approved). This allows the administrator to determine where approvals need to be made. Global approval is not possible with this solution because ?action=approveurls does not process markup expressions, but see below.
  • Be sure to join the lines wherever you see (NO NEWLINE)
(:linebreaks:)
{(wikish grep --debug:1 --line_prefix:"[[PAGENAME]]::" http (NO NEWLINE)
`pagelist group=-PmWiki,-Site,-SiteAdmin list=normal http sep=\n` (NO NEWLINE)
| sed "s/:.*(http[^]\\|\\s,]*)(?:.*?$)/:$1/")}
  • This script (below) is a little more involved, but it results in a nicer output and the potential for a global approval from the output page.
    • Again, you must have PowerTools installed
    • This time write privilege is required for Test.ToApprove (or whatever you use for your output file)
    • You must also have read privilege for SiteAdmin.ApprovedUrls (normally admin privilege) - make sure you log in with this before starting
    • You will want to edit your config.php and allow enough links for the output page to be created -- for instance:

$UnapprovedLinkCountMax = 500;

The page source itself will look like this:

{(wikish source {$FullName}#funky)}

(:if false:)
[[#funky]]
set -s out = Test.ToApprove   # Change this pagename to put a static list of all links on that page
rm Virtual.X
grep --line_prefix:"PAGENAME:" 'https?:\/\/' `pagelist group=-PmWiki,-Site,-SiteAdmin list=normal http sep=\n` (NO NEWLINE)
| while read --IFS:: pn line
do
   sed 's/http/\n${pn}:http/g' - "${line}" | grep -F '${pn}:http' >>Virtual.X
done
sed 's/^(\S+:https?:\/\/[^]\|?\/\&\s]+).*$/$1/' Virtual.X | while read --IFS:: pn line
do
   if ! grep -qF "  ${line}" SiteAdmin.ApprovedUrls
   then
      echo "||[[${pn}]] ||x - ${line} ||" >>Virtual.Out
   else
      echo "||[[${pn}]] ||${line} ||" >>Virtual.Out
   fi
done
cp Virtual.Out ${out}
echo 'The list of links has been placed in the page [[${out}]].  Please go there and approve links.'
#wikish_form redirect target=${out}
rm Virtual.X
[[#endfunky]]
(:ifend:)
  • EXPLANATION
    • Use pagelist from PowerTools to get a quick list of all pages with the characters http on them. Then search for an actual link format (via grep).
    • This is then read (via read) and each link is replaced by a newline, the pagename where it was found, a colon, and the link. Non-link lines are dropped (via grep). The results are put in the temporary page Virtual.X
    • This temporary page is then read and any characters after the "core" link are deleted
    • The results of the above are read (via read) again and a search is done for each link in the SiteAdmin.ApprovedUrls page and the output is appropriately formatted. (You could comment out the "else ... echo ..." lines if you aren't interested in seeing lines that are already approved.)
    • The use of Virtual.X and Virtual.Out are simple ways to make WikiSh more efficient through the use of in-memory files. But you will note that Virtual.Out must be copied to Test.ToApprove (${out}) before it can be used by anyone who is not WikiSh.

Redirect to a given page via a button (see ButonLink)

  • Going to the pmwiki homepage
{(wikish_form quickform process)}
(:input submit name=gohome value="Go To Pmwiki Homepage":)
(:input end:)
{(wikish if test -n ${gohome}; then; wikish_form redirect target=pmwiki/pmwiki; fi)}
  • Redirecting to a given action on the current page via a button (see 2nd example at ButonLink)
    • Note that the final set of 3 {(wikish ...)} MXes could have been put in a single section with (wikish source) and then formatted in multiple lines for readability. Or they could have been put all in a single MX on a single line {(wikish if ... fi; if ... fi; if ... fi)}
{(wikish_form quickform process)}
(:input submit button "Projects" value="Projects":)
(:input submit button "Products" value="Products":)
(:input submit button "PO" value="PO":)
(:input end:)
{(wikish if test ${button} == "Projects"; then; wikish_form redirect target={$FullName}?action=proj; fi)}
{(wikish if test ${button} == "Products"; then; wikish_form redirect target={$FullName}?action=products; fi)}
{(wikish if test ${button} == "PO"; then; wikish_form redirect target={$FullName}?action=PO; fi)}
(:if equal {$action} Projects:)
handle projects here
(:if equal {$action} Products:)
handle products here
(:if equal {$action} PO:)
handle PO here
(:ifend:)
  • I have followed the 2nd example from ButonLink fairly slavishly above. Going a little "bigger picture" it might be better not to use the redirection and at all but rather use the button name directly without the redirect (i.e., get rid of the final 3 {(wikish ...)} lines completely). If you did this the code would look like this:
{(wikish_form quickform process)}
(:input submit button "Projects" value="Projects":)
(:input submit button "Products" value="Products":)
(:input submit button "PO" value="PO":)
(:input end:)
(:if equal ${button} Projects:)
handle projects here
(:if equal ${button} Products:)
handle products here
(:if equal ${button} PO:)
handle PO here
(:ifend:)
  • EXPLANATION
    • The {(wikish_form ... process)} loads all $_GET variables into WikiSh variables which are then available via the ${var} syntax. (A similar approach could be taken using the httpvariables recipe, I believe, and ProcessForm does the same sort of thing with Page Vars.) This is really the only contribution of WikiSh to this particular solution -- everything else is core stuff...
    • The {(wikish_form quickform ...)} is simply a shorthand for the setting up of the form establishment of the hidden fields for pagename, etc. In other words, pure, unadulterated laziness... :-)

Allow a given user to log in and automatically be taken to the appropriate home page

  • This script does NOT need any write permissions.
  • Assumptions:
    • All pages in Client.* group are homepages with an appropriate and unique read password set for each page
    • Creation of a new client homepage is simply a question of creating the new page in the Client group and setting a unique read password
  • The following text will be placed in a separate page in a separate group (Login.Login or Welcome.Login or something) which will be the main login page for anyone wanting to go to their homepage (this is the link you would give to your client, for instance):
{(wikish set -s LIST = normal; for i in Client.*; do; if test -r ${i}; then; wikish_form redirect target=${i}; (JOIN THIS LINE)
exit; fi; done; wikish_form redirect target={$FullName}?action=login)}
  • Note that the text (JOIN THIS LINE) should be deleted along with the newline - the entire MX should be on a single line
  • Note that it should all be on a single line as that is a current restriction on markup expressions
  • The above is all you need, but if you want to see this in a more readable form, it could be run putting the following functionally identical source on the page (basically it puts the above script in a multi-line "pretty" format and then "calls" it via {(wikish source ...)} ):
{(wikish source {$FullName}#GotoClient)}
(:if false:)
[[#GotoClient]]
set -s LIST = normal
for i in Client.*
do
   if test -r ${i}
   then
      wikish_form redirect target=${i}
      exit
   fi
done
wikish_form redirect target={$FullName}?action=login
[[#EndGotoClient]]
(:ifend:)
  • EXPLANATION (see comments in the code below)
set -s LIST = normal   # Make sure the Client.* doesn't include RecentChanges, other system pages
for i in Client.*      # Loop through every page in the Client group
do
   if test -r ${i}     # If the current Client.X page is readable...
   then
      wikish_form redirect target=${i}  # Redirect to it
      exit                              # and stop processing this script
   fi
done
# If I got here then there are NO pages which are readable -- present a login form to allow the 
# user to enter another password
wikish_form redirect target={$FullName}?action=login
  • Caveats
    • There may be better "big-picture" solutions using authuser and the user ID, but the requested functionality used the password-only security model. As far as I can see this solution would also work with the authuser security model.

Sum up many PTVs named var1, var2, var3, ..., var30

  • This script does NOT need any write permissions.
  • It is assumed that the variable are already assigned on the current page in this form: (:var1:23:)
  • The current page is assumed to be named Sum -- if you name it something else then you will need to change the "wikish source Sum#totalit" to use your pagename.
  • The #totalit section could be placed on a different page - for simplicity I've placed it all on the same page. Just copy/paste this code into your page with your PTVs and you should get a total.
{earlymx(wikish source Sum#totalit)}

And here is the official total: {$total}

(:if false:)
[[#totalit]]
set i = 1
set --pv total = 0
while test ${i} -le 30
do
   set --pv total += {$:var${i}}
   echo "running total ${i} = $total\\\n"
   set i ++
done
echo total = $total
[[#totalitend]]
(:if:)
  • EXPLANATION
    • The {earlymx(...)} form is necessary because you are setting a PV ({$total}) and want to be able to use it. Normally MXes are processed *after* variables are processed. The earlymx form does the same MX processing, but it places the MX markup rule earlier in the process so PVs created during the WikiSh script will be available in the text of your page.
    • The --pv option to set indicates that a PV should be created rather than a normal WikiSh variable.
    • Note the use of a dynamically-named variable with the {$:var${i}} syntax. Since characters like a dollar sign and curly braces are illegal in a PTV name you are guaranteed that the ${i} will be substituted first (the order of the rules of markup and the way WikiSh does WikiSh variables ensures this as well). This leaves an unseen intermediate form that looks like {$:var1} (or another number) which then gets substituted in turn.
    • In the "echo total = $total" you see that $total is used instead of $total. This is because within WikiSh code you have to treat PVs as if you were inside a (:pagelist ...:) -- otherwise they will be prematurely substituted.

Create test data for the Summing Variables example above.

  • This script requires write privilege on the current page (assuming WikiSh.Sum)
  • This script is identical to that above except it is what was used for testing. To that end the test data can be created by pushing the "Make List" button - it will create 30 PTV assignments on the current page immediately below the (:comment BELOWTHIS:). Then if you want to sum the values then you press the "Sum Them" button.
  • This example is given as one option of how testing could occur during development of a WikiSh recipe.
{earlymx(wikish_button A "Sum Them")(wikish source Sum#totalit)}

{earlymx(wikish_button B "Make List")(wikish source Sum#makevars)}

(:comment BELOWTHIS:)

And here is the official total: {$total}

(:if false:)
[[#makevars]]
set RANDOM_MAX = 10
set RANDOM_MIN = 0
set i = 1
while test ${i} -le 30
do
   echo "(:var${i}:${RANDOM}:)" >WikiSh.Sum>BELOWTHIS
   set i ++
done
[[#makevarsend]]

[[#totalit]]
set i = 1
set --pv total = 0
while test ${i} -le 30
do
   set --pv total += {$:var${i}}
   echo "running total ${i} = $total\\\n"
   set i ++
done
echo total = $total
[[#totalitend]]
(:if:)

====
DEBUG

(:messages:)
  • EXPLANATION
    • Observe the setting of RANDOM_MAX and RANDOM_MIN and then the use of ${RANDOM} to get random integers.
    • Note the redirection (outputspec) in this form:
      • >Sum>BELOWTHIS
      • This sends the output of the "echo" command onto the page named Sum (in the current group) and places it on the line immediately after the first line matching the pattern BELOWTHIS.
      • Since each iteration will put the data immediately below that line and we start with var1 that means that the data will end up being in reverse order. You could solve this "problem" either by using >Sum<ABOVETHIS to put the data immediately above the line matching that pattern (obviously you would need to change the comment to match) or, alternatively, you could start with i as 30 and decrement instead of increment down to 1. Since they are invisible it didn't make any difference in this case and so the example leaves them in reverse order.
    • For other explanation see the previous example.

Make a VERY rough shopping cart (can be beautified fairly trivially)

  • Requires write permission to the Order and Form pages within the group "Shop" (as I've got it here)
  • Here is what gets placed in Shop.Items (item#:description:price):
    • Presumably this very simple data definition would need to be expanded with more fields, etc.
001:Chair:17.89
002:Table:79.23
003:Rug:50.01
004:Door:129.01
  • Here is what gets placed in Shop.Code:
    • Making tables for the form in Shop.Code#genform and the order page in Shop.Code#ValidateForm is pretty trivial and would make it look a lot nicer...
{(wikish_button a "Generate Form")(wikish source {$FullName}#genform)}

(:if false:)

[[#ValidateForm]]
if test ${Checkout} != "Checkout"
then
   exit
fi
set nogood = 0
set totalprice = 0
set -s orderpage = Shop.Order
echo "(:linebreaks:)" >${orderpage}
while read --IFS:: ItemNo ItemName ItemCost <Items
do
   if ! test ${Item${ItemNo}} ~= "/^\d*$/"
   then
      echo "ERROR: Item Number ${ItemNo} had a non-numeric quantity." >>${orderpage}
      set nogood = 1
   else
      if test -z ${Item${ItemNo}}
      then
         set Item${ItemNo} = 0
      fi
      if test ${Item${ItemNo}} -gt 0
      then
         set extcost = ${Item${ItemNo}} * ${ItemCost}
         set totalprice += ${extcost}
         echo "${Item${ItemNo}} items of ${ItemName} (item number ${ItemNo}): ${extcost}" >>${orderpage}
      fi
   fi
done
if test ${nogood} -eq 1
then
   echo "Error invalid.  Please fix errors."
else
   echo "Total Price for entire order: ${totalprice}" >>${orderpage}
   wikish_form target=${orderpage} REDIRECT
fi
[[#endValidateForm]]

[[#genform]]
set -s formpage = Form
set -s openparen = '('
set -s closeparen = ')'
echo "{(wikish_form QUICKFORM PROCESS)}" >${formpage}
while read --IFS:: ItemNo ItemName ItemCost <Items
do
   echo "${ItemName}: (:input text name=Item${ItemNo}:)\\" >>${formpage}
done
echo "${openparen}:input submit Checkout Checkout:${closeparen}" >>${formpage}
echo "${openparen}:input end:${closeparen}" >>${formpage}
# (the following 2 lines should be combined into 1
echo "${openparen}:if false:${closeparen}\n{${openparen}wikish source 
   Code#ValidateForm${closeparen}}\n${openparen}:if:${closeparen}" >>${formpage}
[[#genformend]]

(:if:)
  • FUNCTIONALITY: When you browse the Shop.Code page you will have a button labeled "Generate Form". Push that and it will create the form in Shop.Form. Navigate to that page and you will be able to enter quantities for each item and press "Checkout" which will create a very rough "Order" page (with line items, extended prices, total, etc.) and redirect you to that page.

Make a "static" pagelist, doing selection by title by regular expression

  • Requires write permission on the "Foo" file (or whatever you choose for output)
  • Requires multiple invocations due to PHP timeouts
  • A straight (:pagelist ...:) is superior in every way to this solution with these exceptions:
    • If you have more pages than can be processed in a single 30-second time period then you have no other options with a normal (:pagelist ...:)
    • The static pagelist is going to be much quicker while browsing (after it is set up) as compared to a normal pagelist that must be recreated from scratch each time.
  • This script will create a STATIC pagelist. That means you will need to regenerate it if a title is changed or if a page is added or deleted. It has its uses (esp speed) but it is not the same as a pagelist.
  • Technical Notes:
    • Note usage of REFPAGE (set to a pagename) which causes all PVs following ({$Title} in our example) to be evaluated from that page's context
    • Note that ${titlevar} had to be set in 2 steps. If the text "{$Title}" appeared verbatim in the text of the program then PmWiki would have evaluated it before we ever used it. Then ${titlevar} would have held the value of the title of the current page ("Control Panel") rather than the name of the variable ("{$Title}"). Doing it in 2 separate steps means that the text "{$Title}" never occurs in the text of the script so it will not be evaluated prematurely.
  • The pages with a title which matches the pattern (in this case those that start with A, B, or C) will be written in plain text to the page Foo. There are at least 2 ways to format your list more attractively:
    • Examples of "pretty formatting" while writing to Foo
      • echo ${fn} >>Foo # plain text
      • echo "||${fn} ||${title} ||" >>Foo # table with pagename and title
      • echo "[[${fn}|+]]" >>Foo # links with dynamically generated title
      • echo "[[${fn}|${title}]]" >>Foo # links with static title (fast)
    • Or you could take the text in Foo and put it back in a (:pagelist ...:) to do the formatting for you, but only looking at the pages that match your title criteria:
      • (:pagelist name={(cat --newline:, Foo)}:)
      • Explanation: Setting newline to a comma causes all the lines to be written out comma-separated instead of newline-separated and so since each line contains exactly one pagename it becomes a nice input to a pagelist.
      • Even though you are using (:pagelist ...:) your page selection is still being done statically (is that a word?) and so any adds/deletes/title-changes will NOT be reflected in this pagelist until you regenerate the static list again.
read --restoreset # this will not cause an error if there is nothing to restore
if test -z `read --status`
then
   echo "Generating list of files to work off of..."
   ls --list:normal >Tmp
fi
set -s output = "Foo"       # CHANGE THIS VALUE IF YOU WANT YOUR OUTPUT TO GO TO A DIFFERENT PAGE
set -s titlevar = "{$"      # Must build the variable in 2 steps or else other markup rules
set -s titlevar .= "Title}" # will substitute it before we can use it
set -s LIST = normal        # don't include pages like RecentChanges, AllRecentChanges, PmWiki.*, etc.
while read fn <Tmp
do
   #echo "Processing page ${fn}"
   set -s REFPAGE = ${fn}
   set -s title = ${titlevar}
   if test ${title} ~= '/^[ABC]/'
   then
      echo "FOUND: ${fn} (${title})"
      echo ${fn} >>${output}            # CHANGE THIS LINE IF YOU WANT THE OUTPUT IN A DIFFERENT FORMAT
   else
      echo "NOT FOUND: ${fn} (${title})"
   fi
   set -s REFPAGE = ""
   if test ${SECONDSLEFT} -lt 7   # allow 7 seconds for initial compile time plus time to process next file
   then
      read --saveset
      echo "You will need to reinvoke this script to complete.  You have finished" `read --status` "files out of" `read --linecount`
      exit
   fi
done 
echo "COMPLETED!  Please find your output in ${output}."
read --clearset  # Make sure it's all cleared out for the end

Make a "static" list of pages with titles. This list can then be used for a faster pagelist.

  • Requires write permission on the "PageList" file (or whatever you choose for output)
    • If you put the "PageList" page in the WikiSh group (WikiSh.PageList) then the default $WikiShWriteList is sufficient.
    • If you want to put "PageList" in another group, say Site.PageList, then you would need to have a line like this in your config.php after WikiSh.php is included:
      • $WikiShWriteList[] = 'Site.PageList';
  • Requires multiple invocations due to PHP timeouts
    • Normally a script like this would be copy/pasted into the WikiSh.ControlPanel command field and then the execute button pressed multiple times.
    • You could also copy/paste the script into it's own page (for instance, WikiSh.MkPageList) and then on another page (for instance, WikiSh.RunMkPageList) you would have only this markup:
      • {(wikish_button A "Make Pagelist")(wikish source WikiSh.MkPageList)}
      • Then instead of pushing the "execute command" button multiple times you would press the "Make Pagelist" button multiple times until the output indicated that the job was done. (Obviously you will have to wait about 30 seconds between each invocation.)
      • TIP: When the output page (PageList) is small this script processes a lot of pages each iteration (70-100 on my system). When the output page gets larger things REALLY slow down. (By around 1300 files my system was only able to process about 11 files per 30-second-timeout.) However, the problem is easily solved manually by periodically (as often as every 2 iterations) cut/pasting the output into a new page somewhere (or an editor or whatever). Then save the PageList page as a blank page and things will move along quickly for 2-3 more invocations. Then simply do it again, saving the new content of PageList after the old content you've already saved. At the end of the whole process copy/paste the whole list back into the PageList page. (If this doesn't make sense it is not strictly necessary, but it might save you an hour or so if you are working on a site with a lot of pages.)
        • The script could be modified relatively easily to write the output in "chunks" of 50 or 100 files.
  • This script will create a STATIC pagelist. That means you will need to regenerate it if a title is changed or if a page is added or deleted. It has its uses (esp speed) but it is not the same as a pagelist.
  • This solution is designed to be used when obtaining a list of all pages by title.
    • You could take the text in PageList and put it back in a (:pagelist ...:) to format the output. (:pagelist ...:) will only have to look at the pages that match your title criteria so speed will be dramatically improved:
Pagelist of titles starting with A
(:pagelist fmt=#title name={(wikish grep -i "^\s*A" WikiSh.PageList | cut --newline:, -d# -f2)}:)

Pagelist of titles starting with B
(:pagelist fmt=#title name={(wikish grep -i "^\s*B" WikiSh.PageList | cut --newline:, -d# -f2)}:)
  • Explanation: The "grep ..." command selects only lines which begin with an A. Those lines are passed (or piped) to the "cut ..." command which "cuts" each line into fields, cutting wherever the hash symbol (#) is. It then gives back the 2nd field (the pagename). Setting newline to a comma causes all the lines to be written out comma-separated instead of newline-separated and so since each line contains exactly one pagename it becomes a nice input to a pagelist.
  • This script depends on the assumption that you will never have a hash symbol (#) in a title. If you do then you'll need to make some changes here...
  • Even though you are using (:pagelist ...:) your page selection is still being done statically (is that a word?) and so any adds/deletes/title-changes will NOT be reflected in this pagelist until you regenerate the static list again.
  • Performance gains are as expected. On a system with around 1500 pages which could barely have searched through all titles within 30 seconds there is an almost instantaneous load of the page listed above listing those starting with A and those starting with B.
  • Here is the script that would be placed in WikiSh.MkPageList:
set -s output = "WikiSh.PageList"  # CHANGE THIS VALUE IF YOU WANT YOUR OUTPUT TO GO TO A DIFFERENT PAGE
read --restoreset # this will not cause an error if there is nothing to restore
if test -z `read --status`
then
   echo "Generating list of files to work off of..."
   ls --list:normal >Tmp
   echo "--> Static pagelist last updated:" `ftime` >${output}
fi
set -s titlevar = "{$"             # Must build the variable in 2 steps or else other markup rules
set -s titlevar .= "Title}"        # will substitute it before we can use it
set -s PAGEVAR = 'pre'             # In processing ${titlevar} WikiSh variables must be processed BEFORE PVs
while read fn <Tmp
do
   #echo "Processing page ${fn}"
   set -s REFPAGE = ${fn}
   echo "${titlevar}#${fn}" >>${output}
   if test ${SECONDSLEFT} -lt 7   # allow 7 seconds for initial compile time plus time to process next file
   then
      read --saveset
      echo "You will need to reinvoke this script to complete.  You have finished" `read --status` "files out of" `read --linecount`
      exit
   fi
done 
echo "COMPLETED!  Please find your output in ${output}."
read --clearset  # Make sure it's all cleared out for the end

Compute a portion of a pagename (A "base" name)

  • Suppose you have a set of pages in the form Group.Name-Issue-#. The Group.Name is the name of a "base page" you need to access. Here is how you could "compute" the "base name" (Group.Name) from the pagename (Group.Name-Issue-#)
  • (There is a built-in solution to this using the $BaseNamePatterns[] array -- the example below merely shows WikiSh capability. The built-in solution is superior.)
  • Note that NO write permissions at all are necessary for WikiSh to carry out these functions - thus safe for non-admins to use

A simple solution, suitable for just displaying the name:

   {(sed 's/-Issue-.*$//' - {$Name})}

If you wanted to set a PV to that value you could do it like this:

   {earlymx(set -s --pv BASENAME = (sed 's/-Issue-.*$//' - {$Name}))}
   the basename is {$BASENAME}

(By using the {earlymx(...)} form the MX gets executed early in the markup processing queue and the result is that you can then use the PV set -- otherwise the PV gets set but you can't use it because variables have already been interpolated by the time it is set.)

Do a global search and replace on a large number of files

  • Search & Replace (or any other operation which processes a LOT of files) is normally a very challenging operation within a PHP environment because of the default timeouts. WikiSh has implemented some features within the {(read ...)} MX to help administrators get around this problem.
  • Broad write permissions are required prior to running this script. Thus it should be run on pages accessible only by admins.
  • Before you run the main script below you should run this short script to make sure you don't have anything hanging around in your session that will cause problems for you...

read --clearset

  • Once you have cleared the session (above) then you can put this script (below) into your control panel, change the sed ... line to reflect your search/replace values, and start pressing execute. (If you want it to output a line for each file processed, whether updated or not, then uncomment the echo command immediately above the sed command -- however, this deemphasizes the output from sed -v if you are looking for that specifically.)
read --restoreset # this will not cause an error if there is nothing to restore
if test -z `read --status`
then
   echo "Generating list of files to work off of..."
   ls | grep -v RecentChanges >Tmp
fi
while read fn <Tmp
do
   if test ${SECONDSLEFT} -lt 7   # allow 7 seconds for initial compile time plus time to process next file
   then
      read --prev --saveset       # prev because main looping read has already gone to next line - must retrace steps
      echo "You will need to reinvoke this script to complete.  You have finished" `read --status` "files out of" `read --linecount`
      exit
   fi
   #echo "Processing page ${fn}"
   sed -vi 's/Pete /Peter /g' ${fn}
done 
echo "COMPLETED!"
read --clearset
  • You will need to re-invoke this script many times, depending on the size of your site and the speed of your server. Simply press "Execute Command" each time to re-invoke it.

Make a group into a WikiTrail

  • NOTE: Write permissions would be needed on MyGroup.TrailIndexPage as well as every page that will become part of WikiTrail
ls MyGroup.* | grep -v 'RecentChanges|AnotherPageToSkip' | while read fn
do
   echo "${fn}"
   echo "<<|[[TrailIndexPage]]|>>" >${fn}<_TOP
   echo "* [[${fn}]]" >>MyGroup.TrailIndexPage
done

Create a list of changes made by a particular author:

  • Note that NO write permissions at all are necessary for WikiSh to carry out these functions - thus safe for non-admins to use

If you are searching for "John Smith" specifically

{(grep -F "[[~John Smith]]" *.RecentChanges)}

If you want to put it generically in a Profiles.GroupFooter (this allows for an optional space before each capital letter):

{(wikish grep `sed 's/(.)([A-Z])/$1 ?$2/g' - {$Name}` *.RecentChanges)}

If you want to make the output look nicer and in a different order and etc (courtesy of Hans):

||
||'''recent edits by Hans''' || ||
{(wikish grep -F "[[~Hans]]" Site.AllRecentChanges | sed 's/\* (.*?)\. \. \.\s*(.*?)\s*by(.*?)$/||$1 ||$2 ||/U' | sort -ur)}

Get a list of groups

  • Note that NO write permissions at all are necessary for WikiSh to carry out these functions - thus safe for non-admins to use

Option 1 (quick - may miss a group every once in a while if it doesn't have a RecentChanges page)

{(wikish ls *.RecentChanges | sed 's/\.RecentChanges//' | sort -u)}

Option 2 (takes longer but is complete)

{(wikish ls *.* | sed 's/\..*$//' | sort -u)}

Option 3 (with a later version of WikiSh)

{(ls -G)}

Print off a different message if a pagelist returns no pages:

  • Note that NO write permissions at all are necessary for WikiSh to carry out these functions - thus safe for non-admins to use
  • The wrap=inline option is necessary due to the way pagelist interacts with WikiSh. Other options can be changed as you please.
  • The MX pagelist is found in the PowerTools recipe and is necessary for this particular solution.
  • The command below must be placed on a single line even though it's formatted here on multiple lines
{(wikish set -s list = (pagelist group=XYZ wrap=inline fmt=#bygroup); if test -z "${list}"; then; echo "No pages found!"; 
else; echo "${list}"; fi)}

Rename a page fixing all links to it

  • Note that broad write permissions are necessary for this example. Normally only admins should have this level of permission.
  • put the following MX on a page, make sure permissions allow writing to anybody that might need writing to, and then successively re-load the page watching the progress indicators move by. The reason for the multiple reloads is due to the timeouts PHP has for execution time. If you've got a fast server then you can increase the "FilesPerChunk setting right at the beginning.
  • List (ls) all files into LsList, search through those one chunk at a time using grep to find which files link the old group/page and put the results in GrepList, go through GrepList one chunk at a time using sed and search/replace for the old group/page with the new group/page.
  • You will need PowerTools version 2008/06/22 or later for this to work
  • As long as you have a reasonable number of links to the page being renamed (so you don't have to worry about timeouts) this works fine:
set -s OldGroup = TestA
set -s OldPage = A
set -s NewGroup = TestB
set -s NewPage = B
sed -i 's|${OldGroup}([./]\)?)${OldPage}|${NewGroup}$1${NewPage}|gi' `pagelist link=${OldGroup}.${OldPage} sep=\n`
mv ${OldGroup}.${OldPage} ${NewGroup}.${NewPage}
  • If you have a page with a large number of links to it (more than 20 perhaps?) or if your server is particularly slow then you may need to limit the pagelist at the end of the 5th line in some way and process it in multiple steps. For instance, you could limit it by saying name=[A-F]* the first time, name=[G-M]* the second time and etc.
  • Explanation
    • The first 4 lines simply define variables to hold the old group & page name and the new group & page name. It could have been done just as easily hardcoding those values in the last 2 lines, but having those values in variables allows the script to "isolate" the regular expression from users who may want to remain distant from that type of complication.
    • The 5th line beginning "sed" does the substitution fixing all links. It uses the "s|find|replace|ig" command to do the actual replacement. It uses the pagelist command (in backquotes) to determine which pages need to be altered.
    • The 6th line simply renames the page to the new name.

Rename an entire group fixing all links to each page

  • If you are renaming an entire group then the following script should work for you (assuming that each individual page within the group has a reasonable number of links to it)
  • You will need PowerTools version 2008/06/22 or later for this to work
  • Before you run the main script below you should run this short script to make sure you don't have anything hanging around in your session that will cause problems for you...
read --clearset
  • Once you have cleared the session (above) then you can put this script (below) into your control panel, change the set -s OldGroup = TestA and set -s NewGroup = TestB lines to reflect the names of your old/new groups, and start pressing execute.
    • You may want to play with the timeout of 15 - give it less if you are able to process an entire page in less than that or increase it if you need more time
    • You may also want to put a "null --timeout:120" at the very top of the script -- in some servers this will allow you to process for 120 seconds instead of only 30. Be aware, however, that there may be other timeouts which may come into play if you lengthen this PHP execution timeout.
#null --timeout:120
read --restoreset # this will not cause an error if there is nothing to restore
set -s OldGroup = TestA
set -s NewGroup = TestB
if test -z `read --status`
then
   echo "Generating list of files to work off of..."
   ls ${OldGroup}.* >Tmp
fi
while read fn <Tmp
do
   if test ${SECONDSLEFT} -lt 15  # allow 15 seconds for initial compile time plus time to process next page
   then
      read --prev --saveset       # prev because main looping read has already gone to next line - must retrace steps
      echo "You will need to reinvoke this script to complete.  You have finished" `read --status` "files out of" `read --linecount`
      exit
   fi
   set -s OldPage = ${fn#*.}      # calculate just the page part, losing the group
   set -s NewPage = ${OldPage}
   echo "Updating links ${OldGroup}.${OldPage} to ${NewGroup}.${NewPage} ..."
   sed -vi 's|${OldGroup}([./]\)?)${OldPage}|${NewGroup}$1${NewPage}|gi' `pagelist link=${OldGroup}.${OldPage} sep=\n`
   mv ${OldGroup}.${OldPage} ${NewGroup}.${NewPage}
   echo "Moved page ${OldGroup}.${OldPage} to ${NewGroup}.${NewPage} ... DONE"
done 
read --clearset
rm Tmp
echo "Entire Process COMPLETED!"
  • You will potentially need to re-invoke this script many times, depending on the size of the group and the speed of your server and the number of links to each page. Simply press "Execute Command" each time to re-invoke it.

Populate a new group (created via a form) with a set of template pages

  • Note that you will need write permission on the pages which will be created in the new group - normally permissions like this would belong only to the admin. ALTHOUGH you could give create-only permissions to non-admins by setting $EnableWikiShCreatePage but NOT setting $EnableWikiShOverwritePages. Then the worst that could happen from a security perspective is that a bunch of "junk" files could be created by a malicious user.

Create your templates in some (presumably protected) group. Let's say WikiMaster.Page1 and WikiMaster.Page2. Then on WikiMaster.Admin you would put this markup:

===(snip WikiMaster.Admin)===

{(wikish_form QUICKFORM PROCESS)}
New Group: 
(:input text name=GroupName:)\\
(:input submit name=submit value=Create:)
(:input end:)

{(wikish source {$FullName}#validate)}

(:if false:)
[[#validate]]
if test -n "${submit}"
then
   if ! test -n "${GroupName}"
   then
      echo "ERROR: Must specify the new group name."
   else
      if test -f ${GroupName}.Page1 || test -f ${GroupName}.Page2
      then
         echo "ERROR: ${GroupName} already exists."
      else
         echo "Creating New Team Page...\\\n\\\n"
         cp WikiMaster.Page1 WikiMaster.Page2 ${GroupName}
         echo "\\\n\\\n...DONE!"
      fi
   fi
fi
[[#validateend]]
(:if:)

===(snip)===

If you are going to let other people create the groups then you might want to do some other validations on the group name (no disallowed characters, etc), but this will get you the basic operation and you can fine-tune it from there.

Splitting up (:pagelist:) output into separate pages, and creating a pagelist trail

This is useful if you are using a (:pagelist:) that is spitting out one huge list of items; instead, you can spit out shorter lists, spread out over multiple pages.

This recipe requires the following elements:

	$FmtPV['$urlVarPageNum'] = $_REQUEST['p']; // for requesting and using pagenumbers
  • Ed: Note that use of the ${!p} syntax obviates the need for these additional recipes. See suggested recoding below

The following is an example of what is output:

http://mySite.com/Group/Name displays:
pages in PmWiki:

    * AccessKeys
    * Audiences
    * AuthUser
    * AvailableActions
    * BackupAndRestore
    * BasicEditing
    * BasicVariables
    * Blocklist
    * Categories
    * ChangeLog
    * ConditionalMarkup
    * Contributors
    * CreatingNewPages
    * CustomInterMap
    * CustomMarkup 

displaying 1-15 of 93
1 | 2 | 3 | 4 | 5 | 6 | 7 | next >> (where these are all active links)

. . .

http://mySite.com/Group/Name?p=2 then displays:
pages in PmWiki:

    * CustomWikiStyles
    * DebugVariables
    * DeletingPages
    * DesignNotes
    * DocumentationIndex
    * EditVariables
    * FAQ
    * FilePermissions
    * FmtPageName
    * Functions
    * Glossary
    * GroupHeaders
    * I18nVariables
    * Images
    * IncludeOtherPages 

displaying 16-30 of 93
<< prev | 1 | 2 | 3 | 4 | 5 | 6 | 7 | next >> (where these are all active links)

here's the WikiSh code:

  • (sorry - i haven't totally figured out the Control Panel method, so i've written it all out as one might paste directly onto a PmWiki page...)
  • (be sure to remove any instances of <<NOBREAK>> -- they're simply added here for formatting this cookbook page)
    • Ed: See suggested recoding below using functions for a nicer presentation in source
	/* CHECK FOR HTTPVAR - P=1 IF NONE PROVIDED */
	{(wikish if test -n {$urlVarPageNum}; then; set pagenum = {$urlVarPageNum}; else; set pagenum = 1; fi; )}
	/* SET GROUP, INTERVAL (LISTINGS PER PAGE) */
	{(set -s groupname = "PmWiki")}
	{(wikish set -s group = "group=${groupname}"; set -s name = "")}
	{(set listingsperpage = 15)}
	/* DETERMINE BEGINNING AND END VALUES OF PAGELIST'S COUNT=N..NN */
	{(set pagenumOffset = ${pagenum}-1 )}
	{(set nextPagenum = ${pagenum}+1 )}
	{(set prevPagenum = ${pagenum}-1 )}
	{(set start = ${pagenumOffset}*${listingsperpage}+1 )}
	{(set end = ${start}+${listingsperpage}-1 )}
	{(wikish set pagecount = `pagelist ${group} ${name} list=normal fmt=count` )}
	/* CALL PAGELIST & BEGIN OUTPUT */
	pages in {(echo ${groupname})}:
	(:pagelist {(echo ${group})} {(echo ${name})} fmt=#simplename list=normal order=name count={(echo "${start}..${end}")} :)
	/* COUNT HOW MANY PAGES YOU'LL NEED TO DISPLAY ALL ENTRIES */
	{(wikish set numdisplaypages = 1; set testVal = ${numdisplaypages} * ${listingsperpage}; <<NOBREAK>>
	while test ${testVal} -lt ${pagecount}; do; set numdisplaypages ++; <<NOBREAK>>
	set testVal = ${numdisplaypages} * ${listingsperpage}; done; )}
	/* DETERMINE LINKS */
	/* NO SPACER IF ONLY 1 PAGE */
	{(wikish set -s space = "|"; if test ${numdisplaypages} -eq 1; then; set -s space = ""; fi; )}
	/* SET 'END' FOR "displaying nn-start to nn-end" */
	{(wikish if test ${pagenum} -lt ${numdisplaypages}; then; set -s endN = ${end}; else; <<NOBREAK>>
	set -s endN = ${pagecount}; fi; if test ${pagenum} -eq 1; then; if test ${numdisplaypages} -eq 1; <<NOBREAK>>
	then set -s endN = ${pagecount}; fi; fi; )}
	{(set -s tally = "displaying ${start}-${endN} of ${pagecount}")}
	/* PREV & NEXT LINKS */
	{(wikish if test ${pagenum} -eq 1; then; set -s prevLink = ""; else; set -s prevLink = <<NOBREAK>>
	"[[{$PageUrl}?p=${prevPagenum} | << prev]] ${space} "; fi; )}
	{(wikish if test ${pagenum} -eq ${numdisplaypages}; then; set -s nextLink = ""; else; <<NOBREAK>>
	set -s nextLink = " ${space} [[{$PageUrl}?p=${nextPagenum} | next >>]]"; fi; )}
	/* GENERATE PAGELIST TRAIL */
	{(wikish set i = 1; set -s output = ""; set -s outputLink = ""; while test ${i} -le ${numdisplaypages}; do; <<NOBREAK>>
	set -s outputLink = "[[{$PageUrl}?p=${i} | ${i}]]"; if test ${i} -eq ${pagenum}; then; <<NOBREAK>>
	set -s outputLink = "'''${outputLink}'''"; fi; if test ${i} -eq 1; then; set -s output = "${outputLink}"; else; <<NOBREAK>>
	set -s output = "${output} ${space} ${outputLink}"; fi; set i ++; done; <<NOBREAK>>
	set -s output = "${prevLink}${output}${nextLink}"; )}

	/* OUTPUT LINKS TO NEXT SET OF PAGES -- DON'T DISPLAY IF THERE'S ONLY ONE PAGE */
	{(echo "${tally}")}
	{(wikish if test ${numdisplaypages} -ne 1; then; echo "${output}"; fi; )}

overtones99 July 12, 2008, at 06:35 AM

  • Here's a quick shot at re-coding the above example in a simpler form using functions...
# This function may be called with the following positional parameters:
#   ${1} = a description of the pagelist ("pages in X")
#   ${2} = a specified group (group=X)
#   ${3} = a specified pagename (wildcard pattern) (name=X)
function PagelistPaginated
{
	# SET GROUP, INTERVAL (LISTINGS PER PAGE)
	set -s pagelistdesc = ${1} #  positional parameter ("pages in X" label)
	set -s groupname = ${2} #  positional parameter (group=X)
	set -s namename = ${3}  #  positional parameter (name=X)
	if test -n ${groupname}
	then
		set -s group = "group=${groupname}"
	else
		set -s group = ""
	fi
	if test -n ${namename}
	then
		set -s name = "name=${namename}"
	else
		set -s name = ""
	fi
	set listingsperpage = 15
	if test -n ${!p}
	then
		set pagenum = ${!p}
	else
		set pagenum = 1
	fi
	# DETERMINE BEGINNING AND END VALUES OF PAGELIST'S COUNT=N..NN
	set pagenumOffset = ${pagenum}-1
	set nextPagenum = ${pagenum}+1
	set prevPagenum = ${pagenum}-1
	set start = ${pagenumOffset}*${listingsperpage}+1
	set end = ${start}+${listingsperpage}-1
	set pagecount = `pagelist ${group} ${name} list=normal fmt=count`
	# CALL PAGELIST & BEGIN OUTPUT
	echo "pages in ${pagelistdesc}:"
	pagelist wrap=inline ${group} ${name} fmt=#simplename list=normal order=name count=${start}..${end}
	# COUNT HOW MANY PAGES YOU'LL NEED TO DISPLAY ALL ENTRIES
	set numdisplaypages = 1
	set testVal = ${numdisplaypages} * ${listingsperpage}
	while test ${testVal} -lt ${pagecount}
	do
		set numdisplaypages ++
		set testVal = ${numdisplaypages} * ${listingsperpage}
	done
	# DETERMINE LINKS
	# NO SPACER IF ONLY 1 PAGE
	set -s space = "|"
	if test ${numdisplaypages} -eq 1
	then
		set -s space = ""
	fi
	# SET 'END' FOR "displaying nn-start to nn-end"
	if test ${pagenum} -lt ${numdisplaypages}
	then
		set -s endN = ${end}
	else
		set -s endN = ${pagecount}
	fi
	if test ${pagenum} -eq 1 && test ${numdisplaypages} -eq 1
	then 
		set -s endN = ${pagecount}
	fi
	set -s tally = "displaying ${start}-${endN} of ${pagecount}"
	# PREV & NEXT LINKS
	if test ${pagenum} -eq 1
	then
		set -s prevLink = ""
	else
		set -s prevLink = "[[{$FullName}?p=${prevPagenum} | << prev]] ${space} "
	fi
	if test ${pagenum} -eq ${numdisplaypages}
	then
		set -s nextLink = ""
	else
		set -s nextLink = " ${space} [[{$FullName}?p=${nextPagenum} | next >>]]"
	fi
	# GENERATE PAGELIST TRAIL
	set i = 1
	set -s output = ""
	set -s outputLink = ""
	while test ${i} -le ${numdisplaypages}
	do
		set -s outputLink = "[[{$FullName}?p=${i} | ${i}]]"
		if test ${i} -eq ${pagenum}
		then
			set -s outputLink = "'''${outputLink}'''"
		fi
		if test ${i} -eq 1
		then
			set -s output = "${outputLink}"
		else
			set -s output = "${output} ${space} ${outputLink}"
		fi
		set i ++
	done
	set -s output = "${prevLink}${output}${nextLink}"
	# OUTPUT LINKS TO NEXT SET OF PAGES -- DON'T DISPLAY IF THERE'S ONLY ONE PAGE
	echo "\n${tally}"
	if test ${numdisplaypages} -ne 1
	then
		echo "${output}"
	fi
}
  • If the above source were placed in WikiSh.Profile then you could call this function like this on any page:
{(wikish PagelistPaginated "Description for top of pagelist" GroupX GroupY.PageWildcard*)}
  • Description positional parameter (#1) will be placed in a "Pages in X" label at the top
  • Group positional parameter (#2) can be blank by using double-double-quotes ("")
  • Name positional parameter (#3) can be left out or added in as you wish
  • As stated earlier, the httpvars recipe is no longer a prerequisite due to the presence of the ${!p} syntax to access $_REQUEST variables.

. . .

Shorten titles of very long pages, and add ellipses

Long pagetitles on occasion mess up the formatting of tables you've created and make for harder reading/browsing, so this is a nice way to shorten the title if it's too long, and add an "..." to the end of it. I've found this especially useful on Category pages.

Add the following pagelist template to your Site/LocalTemplates

	----
	/* USING WikiSh TO ADD ELLIPSES TO LONG TITLES */
	!!!fmt=#shortentitles
	[@
	[[#shortentitles]]
	(:template first:)
	|| width=30%
	(:template each:)
	{(set -s pageName = "{=$Group}.{=$Name}")}
	{(set -s titleName = "{=$Namespaced}")}
	{(set -s elipsis = "...")}
	{(set maxStringSize = 30)}
	/* shorten title and add "..." if it's too long */
	{(wikish if test ${#titleName} -gt ${maxStringSize}; then; set -s titleNamePost = <<NOBREAK>>
	"${titleName:0:${maxStringSize}}${elipsis}"; else; set -s titleNamePost = "${titleName}"; fi; )}
	||[[{=$Group}/{=$Name} | {(echo "${titleNamePost}")}]] ||[-([[{=$Group}(/Index)]], added {=$PageCreationDate})-] ||
	[[#shortentitlesend]]
	@ ]

then, (:pagelist group=PmWiki fmt=#shortentitles list=normal count=10:) results in:

	Access Key...	(PmWiki, added 11 Jan 2006)
	Audiences	(PmWiki, added 31 Dec 1969)
	Auth User	(PmWiki, added 31 Dec 1969)
	Available ...	(PmWiki, added 31 Dec 1969)
	Backup And...	(PmWiki, added 15 Dec 2005)
	Basic Edit...	(PmWiki, added 31 Dec 1969)
	Basic Vari...	(PmWiki, added 31 Dec 1969)
	Blocklist	(PmWiki, added 29 Sep 2006)
	Categories	(PmWiki, added 31 Dec 1969)
	Change Log	(PmWiki, added 31 Dec 1969)

overtones99 July 12, 2008, at 08:29 AM

. . .