Commit Diff
Diff:
/dev/null
5127fd5866635a199a895214e8aa76ce8494deb2
5127fd5866635a199a895214e8aa76ce8494deb2
Commit:
5127fd5866635a199a895214e8aa76ce8494deb2
Tree:
730fc4da46e652d88547118dd5884cfdf210458b
Committer:
jrmu <jrmu@ircnow.org>
Date:
Fri Dec 17 16:26:51 2021
UTC
Message:
Import sources
--- /dev/null
+++ README.txt
@@ -0,0 +1,85 @@
+This is the README.txt file for PmWiki, a wiki-based system for
+
+collaborative creation and maintenance of websites.
+
+
+PmWiki is distributed with the following directories:
+
+
+ docs/ Brief documentation, sample configuration scripts
+
+ local/ Configuration scripts
+
+ cookbook/ Recipes (add-ons) from the PmWiki Cookbook
+
+ pub/skins/ Layout templates ("skins" for custom look and feel)
+
+ pub/css/ Extra CSS stylesheet files
+
+ pub/guiedit/ Files for the Edit Form's GUIEdit module
+
+ scripts/ Scripts that are part of PmWiki
+
+ wikilib.d/ Bundled wiki pages, including
+
+ * a default Home Page
+
+ * PmWiki documentation pages
+
+ * some Site-oriented pages
+
+
+After PmWiki is installed the following directories may also exist:
+
+
+ wiki.d/ Wiki pages
+
+ uploads/ Uploaded files (page attachments)
+
+
+For quick installation advice, see docs/INSTALL.txt.
+
+
+For more extensive information about installing PmWiki, visit
+
+ http://pmwiki.org/wiki/PmWiki/Installation
+
+
+For information about running PmWiki in standalone mode without
+
+requiring a webserver, visit
+
+ http://pmwiki.org/wiki/Cookbook/Standalone
+
+
+PmWiki is Copyright 2001-2006 Patrick R. Michaud
+
+pmichaud@pobox.com
+
+http://www.pmichaud.com/
+
+
+This program is free software; you can redistribute it and/or modify
+
+it under the terms of the GNU General Public License as published by
+
+the Free Software Foundation; either version 2 of the License, or
+
+(at your option) any later version.
+
+
+This program is distributed in the hope that it will be useful,
+
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+
+GNU General Public License for more details.
+
+
+The GNU General Public License is distributed with this program
+
+(see docs/COPYING.txt) and it is also available online at
+
+http://www.fsf.org/licensing/licenses/gpl.txt .
+
--- /dev/null
+++ cookbook/.htaccess
@@ -0,0 +1,19 @@
+# This file is cookbook/.htaccess -- the default distribution contains this
+# file to prevent cookbook/ scripts from being accessed directly by browsers
+# (this is a potential, albeit very unlikely, security hole).
+#
+# If you alter or replace this file, it will likely be overwritten when
+# you upgrade from one version of PmWiki to another. Be sure to save
+# a copy of your alterations in another location so you can restore them,
+# and you might try changing this file to be read-only to prevent a PmWiki
+# upgrade from overwriting your altered version.
+
+<IfModule !mod_authz_host.c>
+ Order Deny,Allow
+ Deny from all
+</IfModule>
+
+<IfModule mod_authz_host.c>
+ Require all denied
+</IfModule>
+
--- /dev/null
+++ docs/.htaccess
@@ -0,0 +1 @@
+AddType text/plain .php
--- /dev/null
+++ docs/COPYING.txt
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
--- /dev/null
+++ docs/DOCUMENTATION.txt
@@ -0,0 +1,17 @@
+Where is the documentation?
+
+
+PmWiki maintains its documentation as wiki pages.
+
+If you already have PmWiki installed, then a local copy of
+
+the documentation is available through PmWiki itself --
+
+see the "PmWiki.DocumentationIndex" page on your site.
+
+
+The documentation is also available online at
+
+http://www.pmwiki.org/wiki/PmWiki/DocumentationIndex .
+
+
--- /dev/null
+++ docs/INSTALL.txt
@@ -0,0 +1,67 @@
+This is the INSTALL.txt file for PmWiki. This document provides
+
+convenient steps so an administrator can have a PmWiki site up and
+
+running quickly. More extensive information about installing PmWiki
+
+is available at http://www.pmwiki.org/wiki/PmWiki/Installation .
+
+
+Once your site is up and running you will be able to read the bundled
+
+documentation pages.
+
+
+Here are some quick steps to start you on your path toward a complete,
+
+customized installation:
+
+
+1a) Put the software in a location accessible by your webserver.
+
+
+1b) PmWiki can also be run if no webserver is installed. See
+
+ http://pmwiki.org/wiki/Cookbook/Standalone
+
+
+2) Point your browser to pmwiki.php.
+
+
+3) You may see an error message saying that PmWiki needs to have
+
+ a writable wiki.d/ directory. If so, follow the directions to
+
+ establish one. This directory will hold your wiki page files.
+
+
+4) If you want a directory index file, create a file called index.php
+
+ in the main directory that contains the following single line of
+
+ text, purposefully without a closing "?>":
+
+
+<?php include('pmwiki.php');
+
+
+5) Sitewide configuration settings will go in a "local configuration
+
+ file" named local/config.php. Copy the well-commented sample
+
+ configuration file from docs/sample-config.php to the local/
+
+ subdirectory, then rename the copy to config.php. Edit your
+
+ new local/config.php file to suit your preferences.
+
+
+That's it. Next you'll probably want to browse your new site and
+
+read the bundled documentation. A good place to start is the
+
+PmWiki.InitialSetupTasks page.
+
+
+Enjoy!
+
--- /dev/null
+++ docs/UPGRADE.txt
@@ -0,0 +1,82 @@
+This UPGRADE.txt file is a command-line syntax reminder for
+
+experienced PmWiki administrators. For full documentation on
+
+upgrading Pmwiki, see the bundled PmWiki.Upgrades page or visit
+
+
+ http://www.pmwiki.org/wiki/PmWiki/Upgrades
+
+
+See also these related pages:
+
+
+ http://www.pmwiki.org/wiki/PmWiki/BackupAndRestore
+
+ http://www.pmwiki.org/wiki/PmWiki/Subversion
+
+
+The examples assume your PmWiki site is in a ./pmwiki/
+
+directory (a directory named "pmwiki" immediately below the
+
+working directory).
+
+
+Backing up (always a good idea!):
+
+
+ tar -zcvf ~/pmwiki-backup.tar.gz pmwiki
+
+ zip -9r ~/pmwiki-backup.zip pmwiki
+
+
+Or, to keep backups organized by date:
+
+
+ tar -zcvf ~/pmwiki-site-`date +%Y%m%d%M`.tar.gz pmwiki
+
+ zip -9r ~/pmwiki-site-`date +%Y%m%d%M`.zip pmwiki
+
+
+The latest release is available here:
+
+
+ http://www.pmwiki.org/pub/pmwiki/pmwiki-latest.tgz
+
+ http://www.pmwiki.org/pub/pmwiki/pmwiki-latest.zip
+
+
+Example download commands:
+
+
+ wget http://www.pmwiki.org/pub/pmwiki/pmwiki-latest.tgz
+
+ lftpget http://www.pmwiki.org/pub/pmwiki/pmwiki-latest.tgz
+
+ links http://www.pmwiki.org/pub/pmwiki/pmwiki-latest.tgz
+
+ lynx http://www.pmwiki.org/pub/pmwiki/pmwiki-latest.tgz
+
+
+Expanding the archive:
+
+
+ tar -zxvf pmwiki-latest.tgz # for the gzipped tarball
+
+ unzip pmwiki-latest.zip # for the .zip archive
+
+
+Copying the files (two ways to do it):
+
+
+ cp -av pmwiki-2.1.x/. pmwiki
+
+ cp -Rpv pmwiki-2.1.x/. pmwiki
+
+
+Subversion upgrade:
+
+
+ svn export svn://pmwiki.org/pmwiki/tags/latest pmwiki --force
+
--- /dev/null
+++ docs/sample-config.php
@@ -0,0 +1,155 @@
+<?php if (!defined('PmWiki')) exit();
+## This is a sample config.php file. To use this file, copy it to
+## local/config.php, then edit it for whatever customizations you want.
+## Also, be sure to take a look at https://www.pmwiki.org/wiki/Cookbook
+## for more details on the customizations that can be added to PmWiki.
+
+## $WikiTitle is the name that appears in the browser's title bar.
+$WikiTitle = 'PmWiki';
+
+## $ScriptUrl is the URL for accessing wiki pages with a browser.
+## $PubDirUrl is the URL for the pub directory.
+# $ScriptUrl = 'https://www.mydomain.com/path/to/pmwiki.php';
+# $PubDirUrl = 'https://www.mydomain.com/path/to/pub';
+
+## If you want to use URLs of the form .../pmwiki.php/Group/PageName
+## instead of .../pmwiki.php?p=Group.PageName, try setting
+## $EnablePathInfo below. Note that this doesn't work in all environments,
+## it depends on your webserver and PHP configuration. You might also
+## want to check https://www.pmwiki.org/wiki/Cookbook/CleanUrls more
+## details about this setting and other ways to create nicer-looking urls.
+# $EnablePathInfo = 1;
+
+## $PageLogoUrl is the URL for a logo image -- you can change this
+## to your own logo if you wish.
+# $PageLogoUrl = "$PubDirUrl/skins/pmwiki/pmwiki-32.gif";
+
+## If you want to have a custom skin, then set $Skin to the name
+## of the directory (in pub/skins/) that contains your skin files.
+## See PmWiki.Skins and Cookbook.Skins.
+# $Skin = 'pmwiki-responsive';
+
+## You'll probably want to set an administrative password that you
+## can use to get into password-protected pages. Also, by default
+## the "attr" passwords for the PmWiki and Main groups are locked, so
+## an admin password is a good way to unlock those. See PmWiki.Passwords
+## and PmWiki.PasswordsAdmin.
+# $DefaultPasswords['admin'] = pmcrypt('secret');
+
+## Unicode (UTF-8) allows the display of all languages and all alphabets.
+## Highly recommended for new wikis.
+include_once("scripts/xlpage-utf-8.php");
+
+## If you're running a publicly available site and allow anyone to
+## edit without requiring a password, you probably want to put some
+## blocklists in place to avoid wikispam. See PmWiki.Blocklist.
+# $EnableBlocklist = 1; # enable manual blocklists
+# $EnableBlocklist = 10; # enable automatic blocklists
+
+## PmWiki comes with graphical user interface buttons for editing;
+## to enable these buttons, set $EnableGUIButtons to 1.
+# $EnableGUIButtons = 1;
+
+## To enable markup syntax from the Creole common wiki markup language
+## (http://www.wikicreole.org/), include it here:
+# include_once("scripts/creole.php");
+
+## Some sites may want leading spaces on markup lines to indicate
+## "preformatted text blocks", set $EnableWSPre=1 if you want to do
+## this. Setting it to a higher number increases the number of
+## space characters required on a line to count as "preformatted text".
+# $EnableWSPre = 1; # lines beginning with space are preformatted (default)
+# $EnableWSPre = 4; # lines with 4 or more spaces are preformatted
+# $EnableWSPre = 0; # disabled
+
+## If you want uploads enabled on your system, set $EnableUpload=1.
+## You'll also need to set a default upload password, or else set
+## passwords on individual groups and pages. For more information
+## see PmWiki.UploadsAdmin.
+# $EnableUpload = 1;
+# $DefaultPasswords['upload'] = pmcrypt('secret');
+$UploadPermAdd = 0; # Recommended for most new installations
+
+## Setting $EnableDiag turns on the ?action=diag and ?action=phpinfo
+## actions, which often helps others to remotely troubleshoot
+## various configuration and execution problems.
+# $EnableDiag = 1; # enable remote diagnostics
+
+## By default, PmWiki doesn't allow browsers to cache pages. Setting
+## $EnableIMSCaching=1; will re-enable browser caches in a somewhat
+## smart manner. Note that you may want to have caching disabled while
+## adjusting configuration files or layout templates.
+# $EnableIMSCaching = 1; # allow browser caching
+
+## Set $SpaceWikiWords if you want WikiWords to automatically
+## have spaces before each sequence of capital letters.
+# $SpaceWikiWords = 1; # turn on WikiWord spacing
+
+## Set $EnableWikiWords if you want to allow WikiWord links.
+## For more options with WikiWords, see scripts/wikiwords.php .
+# $EnableWikiWords = 1; # enable WikiWord links
+
+## $DiffKeepDays specifies the minimum number of days to keep a page's
+## revision history. The default is 3650 (approximately 10 years).
+# $DiffKeepDays=30; # keep page history at least 30 days
+
+## By default, viewers are prevented from seeing the existence
+## of read-protected pages in search results and page listings,
+## but this can be slow as PmWiki has to check the permissions
+## of each page. Setting $EnablePageListProtect to zero will
+## speed things up considerably, but it will also mean that
+## viewers may learn of the existence of read-protected pages.
+## (It does not enable them to access the contents of the pages.)
+# $EnablePageListProtect = 0;
+
+## The refcount.php script enables ?action=refcount, which helps to
+## find missing and orphaned pages. See PmWiki.RefCount.
+# if ($action == 'refcount') include_once("scripts/refcount.php");
+
+## The feeds.php script enables ?action=rss, ?action=atom, ?action=rdf,
+## and ?action=dc, for generation of syndication feeds in various formats.
+# if ($action == 'rss') include_once("scripts/feeds.php"); # RSS 2.0
+# if ($action == 'atom') include_once("scripts/feeds.php"); # Atom 1.0
+# if ($action == 'dc') include_once("scripts/feeds.php"); # Dublin Core
+# if ($action == 'rdf') include_once("scripts/feeds.php"); # RSS 1.0
+
+## By default, pages in the Category group are manually created.
+## Uncomment the following line to have blank category pages
+## automatically created whenever a link to a non-existent
+## category page is saved. (The page is created only if
+## the author has edit permissions to the Category group.)
+# $AutoCreate['/^Category\\./'] = array('ctime' => $Now);
+
+## PmWiki allows a great deal of flexibility for creating custom markup.
+## To add support for '*bold*' and '~italic~' markup (the single quotes
+## are part of the markup), uncomment the following lines.
+## (See PmWiki.CustomMarkup and the Cookbook for details and examples.)
+# Markup("'~", "<'''''", "/'~(.*?)~'/", "<i>$1</i>"); # '~italic~'
+# Markup("'*", "<'''''", "/'\\*(.*?)\\*'/", "<b>$1</b>"); # '*bold*'
+
+## If you want to have to approve links to external sites before they
+## are turned into links, uncomment the line below. See PmWiki.UrlApprovals.
+## Also, setting $UnapprovedLinkCountMax limits the number of unapproved
+## links that are allowed in a page (useful to control wikispam).
+# $UnapprovedLinkCountMax = 10;
+# include_once("scripts/urlapprove.php");
+
+## The following lines make additional editing buttons appear in the
+## edit page for subheadings, lists, tables, etc.
+# $GUIButtons['h2'] = array(400, '\\n!! ', '\\n', '$[Heading]',
+# '$GUIButtonDirUrlFmt/h2.gif"$[Heading]"');
+# $GUIButtons['h3'] = array(402, '\\n!!! ', '\\n', '$[Subheading]',
+# '$GUIButtonDirUrlFmt/h3.gif"$[Subheading]"');
+# $GUIButtons['indent'] = array(500, '\\n->', '\\n', '$[Indented text]',
+# '$GUIButtonDirUrlFmt/indent.gif"$[Indented text]"');
+# $GUIButtons['outdent'] = array(510, '\\n-<', '\\n', '$[Hanging indent]',
+# '$GUIButtonDirUrlFmt/outdent.gif"$[Hanging indent]"');
+# $GUIButtons['ol'] = array(520, '\\n# ', '\\n', '$[Ordered list]',
+# '$GUIButtonDirUrlFmt/ol.gif"$[Ordered (numbered) list]"');
+# $GUIButtons['ul'] = array(530, '\\n* ', '\\n', '$[Unordered list]',
+# '$GUIButtonDirUrlFmt/ul.gif"$[Unordered (bullet) list]"');
+# $GUIButtons['hr'] = array(540, '\\n----\\n', '', '',
+# '$GUIButtonDirUrlFmt/hr.gif"$[Horizontal rule]"');
+# $GUIButtons['table'] = array(600,
+# '||border=1 width=80%\\n||!Hdr ||!Hdr ||!Hdr ||\\n|| || || ||\\n|| || || ||\\n', '', '',
+# '$GUIButtonDirUrlFmt/table.gif"$[Table]"');
--- /dev/null
+++ index.php
@@ -0,0 +1 @@
+<?php include('pmwiki.php');
--- /dev/null
+++ local/.htaccess
@@ -0,0 +1,19 @@
+# This file is local/.htaccess -- the default distribution contains this
+# file to prevent local/ scripts from being accessed directly by browsers
+# (this is a potential, albeit very unlikely, security hole).
+#
+# If you alter or replace this file, it will likely be overwritten when
+# you upgrade from one version of PmWiki to another. Be sure to save
+# a copy of your alterations in another location so you can restore them,
+# and you might try changing this file to be read-only to prevent a PmWiki
+# upgrade from overwriting your altered version.
+
+<IfModule !mod_authz_host.c>
+ Order Deny,Allow
+ Deny from all
+</IfModule>
+
+<IfModule mod_authz_host.c>
+ Require all denied
+</IfModule>
+
--- /dev/null
+++ local/localmap.txt
@@ -0,0 +1,6 @@
+irc: irc:
+ircs: ircs:
+nntp: nntp:
+xmpp: xmpp:
+telnet: telnet:
+ssh: ssh:
--- /dev/null
+++ pmwiki.php
@@ -0,0 +1,2501 @@
+<?php
+/*
+ PmWiki
+ Copyright 2001-2020 Patrick R. Michaud
+ pmichaud@pobox.com
+ http://www.pmichaud.com/
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ ----
+ Note from Pm: Trying to understand the PmWiki code? Wish it had
+ more comments? If you want help with any of the code here,
+ write me at <pmichaud@pobox.com> with your question(s) and I'll
+ provide explanations (and add comments) that answer them.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+ $Id: pmwiki.php 3643 2020-05-21 14:25:47Z petko $
+*/
+error_reporting(E_ALL ^ E_NOTICE);
+StopWatch('PmWiki');
+@ini_set('magic_quotes_runtime', 0);
+@ini_set('magic_quotes_sybase', 0);
+if (@ini_get('pcre.backtrack_limit') < 1000000)
+ @ini_set('pcre.backtrack_limit', 1000000);
+if (ini_get('register_globals'))
+ foreach($_REQUEST as $k=>$v) {
+ if (preg_match('/^(GLOBALS|_SERVER|_GET|_POST|_COOKIE|_FILES|_ENV|_REQUEST|_SESSION|FarmD|WikiDir)$/i', $k)) exit();
+ ${$k}=''; unset(${$k});
+ }
+$UnsafeGlobals = array_keys($GLOBALS); $GCount=0; $FmtV=array();
+SDV($FarmD,dirname(__FILE__));
+SDV($WorkDir,'wiki.d');
+define('PmWiki',1);
+if (preg_match('/\\w\\w:/', $FarmD)) exit();
+@include_once("$FarmD/scripts/version.php");
+$GroupPattern = '[[:upper:]][\\w]*(?:-\\w+)*';
+$NamePattern = '[[:upper:]\\d][\\w]*(?:-\\w+)*';
+$BlockPattern = 'form|div|table|t[rdh]|p|[uo]l|d[ltd]|h[1-6r]|pre|blockquote';
+$WikiWordPattern = '[[:upper:]][[:alnum:]]*(?:[[:upper:]][[:lower:]0-9]|[[:lower:]0-9][[:upper:]])[[:alnum:]]*';
+$WikiDir = new PageStore('wiki.d/{$FullName}');
+$WikiLibDirs = array(&$WikiDir,new PageStore('$FarmD/wikilib.d/{$FullName}'));
+$PageFileEncodeFunction = 'PUE'; # only used if $WikiDir->encodefilenames is set
+$PageFileDecodeFunction = 'urldecode';
+$LocalDir = 'local';
+$InterMapFiles = array("$FarmD/scripts/intermap.txt",
+ "$FarmD/local/farmmap.txt", '$SiteGroup.InterMap', 'local/localmap.txt');
+$Newline = "\263"; # deprecated, 2.0.0
+$KeepToken = "\034\034";
+$Now=time();
+define('READPAGE_CURRENT', $Now+604800);
+$TimeFmt = '%B %d, %Y, at %I:%M %p';
+$TimeISOFmt = '%Y-%m-%dT%H:%M:%S';
+$TimeISOZFmt = '%Y-%m-%dT%H:%M:%SZ';
+$MessagesFmt = array();
+$BlockMessageFmt = "<h3 class='wikimessage'>$[This post has been blocked by the administrator]</h3>";
+$EditFields = array('text');
+$EditFunctions = array('EditTemplate', 'RestorePage', 'ReplaceOnSave',
+ 'SaveAttributes', 'PostPage', 'PostRecentChanges', 'AutoCreateTargets',
+ 'PreviewPage');
+$EnablePost = 1;
+$ChangeSummary = substr(preg_replace('/[\\x00-\\x1f]|=\\]/', '',
+ stripmagic(@$_REQUEST['csum'])), 0, 100);
+$AsSpacedFunction = 'AsSpaced';
+$SpaceWikiWords = 0;
+$RCDelimPattern = ' ';
+$RecentChangesFmt = array(
+ '$SiteGroup.AllRecentChanges' =>
+ '* [[{$Group}.{$Name}]] . . . $CurrentTime $[by] $AuthorLink: [=$ChangeSummary=]',
+ '$Group.RecentChanges' =>
+ '* [[{$Group}/{$Name}]] . . . $CurrentTime $[by] $AuthorLink: [=$ChangeSummary=]');
+$UrlScheme = (@$_SERVER['HTTPS']=='on' || @$_SERVER['SERVER_PORT']==443)
+ ? 'https' : 'http';
+$ScriptUrl = $UrlScheme.'://'.$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'];
+$PubDirUrl = preg_replace('#/[^/]*$#', '/pub', $ScriptUrl, 1);
+$HTMLVSpace = "<vspace>";
+$HTMLPNewline = '';
+$MarkupFrame = array();
+$MarkupFrameBase = array('cs' => array(), 'vs' => '', 'ref' => 0,
+ 'closeall' => array(), 'is' => array(),
+ 'escape' => 1);
+$WikiWordCountMax = 1000000;
+$WikiWordCount['PmWiki'] = 1;
+$TableRowIndexMax = 1;
+$UrlExcludeChars = '<>"{}|\\\\^`()[\\]\'';
+$QueryFragPattern = "[?#][^\\s$UrlExcludeChars]*";
+$SuffixPattern = '(?:-?[[:alnum:]]+)*';
+$LinkPageSelfFmt = "<a class='selflink' href='\$LinkUrl' title='\$LinkAlt'>\$LinkText</a>";
+$LinkPageExistsFmt = "<a class='wikilink' href='\$LinkUrl' title='\$LinkAlt'>\$LinkText</a>";
+$LinkPageCreateFmt =
+ "<a class='createlinktext' rel='nofollow' title='\$LinkAlt'
+ href='{\$PageUrl}?action=edit'>\$LinkText</a><a rel='nofollow'
+ class='createlink' href='{\$PageUrl}?action=edit'>?</a>";
+$UrlLinkFmt =
+ "<a class='urllink' href='\$LinkUrl' title='\$LinkAlt' rel='nofollow'>\$LinkText</a>";
+umask(002);
+$CookiePrefix = '';
+$SiteGroup = 'Site';
+$SiteAdminGroup = 'SiteAdmin';
+$DefaultGroup = 'Main';
+$DefaultName = 'HomePage';
+$GroupHeaderFmt = '(:include {$Group}.GroupHeader self=0 basepage={*$FullName}:)(:nl:)';
+$GroupFooterFmt = '(:nl:)(:include {$Group}.GroupFooter self=0 basepage={*$FullName}:)';
+$PagePathFmt = array('{$Group}.$1','$1.$1','$1.{$DefaultName}');
+$PageAttributes = array(
+ 'passwdread' => '$[Set new read password:]',
+ 'passwdedit' => '$[Set new edit password:]',
+ 'passwdattr' => '$[Set new attribute password:]');
+$XLLangs = array('en');
+if (preg_match('/^C$|\.UTF-?8/i',setlocale(LC_ALL,0)))
+ setlocale(LC_ALL,'en_US');
+$FmtP = array();
+$FmtPV = array(
+ # '$ScriptUrl' => 'PUE($ScriptUrl)', ## $ScriptUrl is special
+ '$PageUrl' =>
+ 'PUE(($EnablePathInfo)
+ ? "$ScriptUrl/$group/$name"
+ : "$ScriptUrl?n=$group.$name")',
+ '$FullName' => '"$group.$name"',
+ '$Groupspaced' => '$AsSpacedFunction($group)',
+ '$Namespaced' => '$AsSpacedFunction($name)',
+ '$Group' => '$group',
+ '$Name' => '$name',
+ '$Titlespaced' => 'FmtPageTitle(@$page["title"], $name, 1)',
+ '$Title' => 'FmtPageTitle(@$page["title"], $name, 0)',
+ '$LastModifiedBy' => '@$page["author"]',
+ '$LastModifiedHost' => '@$page["host"]',
+ '$LastModified' => 'strftime($GLOBALS["TimeFmt"], $page["time"])',
+ '$LastModifiedSummary' => '@$page["csum"]',
+ '$LastModifiedTime' => '$page["time"]',
+ '$Description' => '@$page["description"]',
+ '$SiteGroup' => '$GLOBALS["SiteGroup"]',
+ '$SiteAdminGroup' => '$GLOBALS["SiteAdminGroup"]',
+ '$VersionNum' => '$GLOBALS["VersionNum"]',
+ '$Version' => '$GLOBALS["Version"]',
+ '$WikiTitle' => '$GLOBALS["WikiTitle"]',
+ '$Author' => 'NoCache($GLOBALS["Author"])',
+ '$AuthId' => 'NoCache($GLOBALS["AuthId"])',
+ '$DefaultGroup' => '$GLOBALS["DefaultGroup"]',
+ '$DefaultName' => '$GLOBALS["DefaultName"]',
+ '$BaseName' => 'MakeBaseName($pn)',
+ '$Action' => '$GLOBALS["action"]',
+ '$PasswdRead' => 'PasswdVar($pn, "read")',
+ '$PasswdEdit' => 'PasswdVar($pn, "edit")',
+ '$PasswdAttr' => 'PasswdVar($pn, "attr")',
+ );
+$SaveProperties = array('title', 'description', 'keywords');
+$PageTextVarPatterns = array(
+ 'var:' => '/^(:*[ \\t]*(\\w[-\\w]*)[ \\t]*:[ \\t]?)(.*)($)/m',
+ '(:var:...:)' => '/(\\(: *(\\w[-\\w]*) *:(?!\\))\\s?)(.*?)(:\\))/s'
+ );
+
+
+$WikiTitle = 'PmWiki';
+$Charset = 'ISO-8859-1';
+$HTTPHeaders = array(
+ "Expires: Tue, 01 Jan 2002 00:00:00 GMT",
+ "Cache-Control: no-store, no-cache, must-revalidate",
+ "Content-type: text/html; charset=ISO-8859-1;");
+$CacheActions = array('browse','diff','print');
+$EnableHTMLCache = 0;
+$NoHTMLCache = 0;
+$HTMLTagAttr = '';
+$HTMLDoctypeFmt =
+ "<!DOCTYPE html
+ PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"
+ \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">
+ <html xmlns='http://www.w3.org/1999/xhtml' \$HTMLTagAttr><head>\n";
+$HTMLStylesFmt['pmwiki'] = "
+ ul, ol, pre, dl, p { margin-top:0px; margin-bottom:0px; }
+ code.escaped { white-space: pre; }
+ .vspace { margin-top:1.33em; }
+ .indent { margin-left:40px; }
+ .outdent { margin-left:40px; text-indent:-40px; }
+ a.createlinktext { text-decoration:none; border-bottom:1px dotted gray; }
+ a.createlink { text-decoration:none; position:relative; top:-0.5em;
+ font-weight:bold; font-size:smaller; border-bottom:none; }
+ img { border:0px; }
+ ";
+$HTMLHeaderFmt['styles'] = array(
+ "<style type='text/css'><!--",&$HTMLStylesFmt,"\n--></style>");
+$HTMLBodyFmt = "</head>\n<body>";
+$HTMLStartFmt = array('headers:',&$HTMLDoctypeFmt,&$HTMLHeaderFmt,
+ &$HTMLBodyFmt);
+$HTMLEndFmt = "\n</body>\n</html>";
+$PageStartFmt = array(&$HTMLStartFmt,"\n<div id='contents'>\n");
+$PageEndFmt = array('</div>',&$HTMLEndFmt);
+
+$HandleActions = array(
+ 'browse' => 'HandleBrowse', 'print' => 'HandleBrowse',
+ 'edit' => 'HandleEdit', 'source' => 'HandleSource',
+ 'attr' => 'HandleAttr', 'postattr' => 'HandlePostAttr',
+ 'logout' => 'HandleLogoutA', 'login' => 'HandleLoginA');
+$HandleAuth = array(
+ 'browse' => 'read', 'source' => 'read', 'print' => 'read',
+ 'edit' => 'edit', 'attr' => 'attr', 'postattr' => 'attr',
+ 'logout' => 'read', 'login' => 'login');
+$ActionTitleFmt = array(
+ 'edit' => '| $[Edit]',
+ 'attr' => '| $[Attributes]',
+ 'login' => '| $[Login]');
+$DefaultPasswords = array('admin'=>'@lock','read'=>'','edit'=>'','attr'=>'');
+$AuthCascade = array('edit'=>'read', 'attr'=>'edit');
+$AuthList = array('' => 1, 'nopass:' => 1, '@nopass' => 1);
+$SessionEncode = 'base64_encode';
+$SessionDecode = 'base64_decode';
+
+$CallbackFnTemplates = array(
+ 'default' => '%s',
+ 'return' => 'return %s;',
+ 'markup_e' => 'extract($GLOBALS["MarkupToHTML"]); return %s;',
+ 'qualify' => 'extract($GLOBALS["tmp_qualify"]); return %s;',
+);
+
+$Conditions['enabled'] = '(boolean)@$GLOBALS[$condparm]';
+$Conditions['false'] = 'false';
+$Conditions['true'] = 'true';
+$Conditions['group'] =
+ "(boolean)MatchPageNames(\$pagename, FixGlob(\$condparm, '$1$2.*'))";
+$Conditions['name'] =
+ "(boolean)MatchPageNames(\$pagename, FixGlob(\$condparm, '$1*.$2'))";
+$Conditions['match'] = 'preg_match("!$condparm!",$pagename)';
+$Conditions['authid'] = 'NoCache(@$GLOBALS["AuthId"] > "")';
+$Conditions['exists'] = "(boolean)ListPages(FixGlob(
+ str_replace(array('[[',']]'), array('', ''), \$condparm) , '$1*.$2'))";
+$Conditions['equal'] = 'CompareArgs($condparm) == 0';
+function CompareArgs($arg)
+ { $arg = ParseArgs($arg); return strcmp(@$arg[''][0], @$arg[''][1]); }
+
+$Conditions['auth'] = 'NoCache(CondAuth($pagename, $condparm))';
+function CondAuth($pagename, $condparm) {
+ global $HandleAuth;
+ @list($level, $pn) = explode(' ', $condparm, 2);
+ $pn = ($pn > '') ? MakePageName($pagename, $pn) : $pagename;
+ if (@$HandleAuth[$level]>'') $level = $HandleAuth[$level];
+ return (boolean)RetrieveAuthPage($pn, $level, false, READPAGE_CURRENT);
+}
+
+## CondExpr handles complex conditions (expressions)
+## Portions Copyright 2005 by D. Faure (dfaure@cpan.org)
+function CondExpr($pagename, $condname, $condparm) {
+ global $CondExprOps;
+ SDV($CondExprOps, 'and|x?or|&&|\\|\\||[!()]');
+ if ($condname == '(' || $condname == '[')
+ $condparm = preg_replace('/[\\]\\)]\\s*$/', '', $condparm);
+ $condparm = str_replace('&&', '&&', $condparm);
+ $terms = preg_split("/(?<!\\S)($CondExprOps)(?!\\S)/i", $condparm, -1,
+ PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+ foreach($terms as $i => $t) {
+ $t = trim($t);
+ if (preg_match("/^($CondExprOps)$/i", $t)) continue;
+ if ($t) $terms[$i] = CondText($pagename, "if $t", 'TRUE') ? '1' : '0';
+ }
+ return @eval('return(' . implode(' ', $terms) . ');');
+}
+$Conditions['expr'] = 'CondExpr($pagename, $condname, $condparm)';
+$Conditions['('] = 'CondExpr($pagename, $condname, $condparm)';
+$Conditions['['] = 'CondExpr($pagename, $condname, $condparm)';
+
+$MarkupTable['_begin']['seq'] = 'B';
+$MarkupTable['_end']['seq'] = 'E';
+Markup('fulltext','>_begin');
+Markup('split','>fulltext',"\n",
+ '$RedoMarkupLine=1; return explode("\n",$x);');
+Markup('directives','>split');
+Markup('inline','>directives');
+Markup('links','>inline');
+Markup('block','>links');
+Markup('style','>block');
+Markup('closeall', '_begin',
+ '/^\\(:closeall:\\)$/', "MarkupMarkupClose");
+function MarkupMarkupClose() { return '<:block>' . MarkupClose(); }
+
+$ImgExtPattern="\\.(?:gif|jpg|jpeg|png|svgz?|GIF|JPG|JPEG|PNG|SVGZ?)";
+$ImgTagFmt="<img src='\$LinkUrl' alt='\$LinkAlt' title='\$LinkAlt' />";
+
+$BlockMarkups = array(
+ 'block' => array('','','',0),
+ 'ul' => array('<ul><li>','</li><li>','</li></ul>',1),
+ 'dl' => array('<dl>','</dd>','</dd></dl>',1),
+ 'ol' => array('<ol><li>','</li><li>','</li></ol>',1),
+ 'p' => array('<p>','','</p>',0),
+ 'indent' =>
+ array("<div class='indent'>","</div><div class='indent'>",'</div>',1),
+ 'outdent' =>
+ array("<div class='outdent'>","</div><div class='outdent'>",'</div>',1),
+ 'pre' => array('<pre>','','</pre>',0),
+ 'table' => array("<table width='100%'>",'','</table>',0));
+
+foreach(array('http:','https:','mailto:','ftp:','news:','gopher:','nap:',
+ 'file:', 'tel:', 'geo:') as $m)
+ { $LinkFunctions[$m] = 'LinkIMap'; $IMap[$m]="$m$1"; }
+$LinkFunctions['<:page>'] = 'LinkPage';
+
+$q = preg_replace('/(\\?|%3f)([-\\w]+=)/i', '&$2', @$_SERVER['QUERY_STRING']);
+if ($q != @$_SERVER['QUERY_STRING']) {
+ unset($_GET);
+ parse_str($q, $_GET);
+ $_REQUEST = array_merge($_REQUEST, $_GET, $_POST);
+}
+
+if (isset($_GET['action'])) $action = $_GET['action'];
+elseif (isset($_POST['action'])) $action = $_POST['action'];
+else $action = 'browse';
+
+$pagename = @$_REQUEST['n'];
+if (!$pagename) $pagename = @$_REQUEST['pagename'];
+if (!$pagename &&
+ preg_match('!^'.preg_quote($_SERVER['SCRIPT_NAME'],'!').'/?([^?]*)!',
+ $_SERVER['REQUEST_URI'],$match))
+ $pagename = urldecode($match[1]);
+if (preg_match('/[\\x80-\\xbf]/',$pagename))
+ $pagename=utf8_decode($pagename);
+$pagename = preg_replace('![^[:alnum:]\\x80-\\xff]+$!','',$pagename);
+$pagename_unfiltered = $pagename;
+$pagename = preg_replace('![${}\'"\\\\]+!', '', $pagename);
+$FmtPV['$RequestedPage'] = 'PHSC($GLOBALS["pagename_unfiltered"], ENT_QUOTES)';
+$Cursor['*'] = &$pagename;
+if (function_exists("date_default_timezone_get") ) { # fix PHP5.3 warnings
+ @date_default_timezone_set(@date_default_timezone_get());
+}
+
+$DenyHtaccessContent = <<<EOF
+<IfModule !mod_authz_host.c>
+ Order Deny,Allow
+ Deny from all
+</IfModule>
+
+<IfModule mod_authz_host.c>
+ Require all denied
+</IfModule>
+
+EOF;
+
+if (file_exists("$FarmD/local/farmconfig.php"))
+ include_once("$FarmD/local/farmconfig.php");
+if (IsEnabled($EnableLocalConfig,1)) {
+ if (file_exists("$LocalDir/config.php"))
+ include_once("$LocalDir/config.php");
+ elseif (file_exists('config.php'))
+ include_once('config.php');
+}
+
+SDV($CurrentTime, strftime($TimeFmt, $Now));
+SDV($CurrentTimeISO, strftime($TimeISOFmt, $Now));
+
+if (IsEnabled($EnableStdConfig,1))
+ include_once("$FarmD/scripts/stdconfig.php");
+
+if (isset($PostConfig) && is_array($PostConfig)) {
+ asort($PostConfig, SORT_NUMERIC);
+ foreach ($PostConfig as $k=>$v) {
+ if (!$k || !$v || $v<50) continue;
+ if (function_exists($k)) $k($pagename);
+ elseif (file_exists($k)) include_once($k);
+ }
+}
+
+function pmsetcookie($name, $val="", $exp=0, $path="", $dom="", $secure=null, $httponly=null) {
+ global $EnableCookieSecure, $EnableCookieHTTPOnly, $SetCookieFunction;
+ if(IsEnabled($SetCookieFunction))
+ return $SetCookieFunction($name, $val, $exp, $path, $dom, $secure, $httponly);
+ if (is_null($secure)) $secure = IsEnabled($EnableCookieSecure, false);
+ if (is_null($httponly)) $httponly = IsEnabled($EnableCookieHTTPOnly, false);
+ setcookie($name, $val, $exp, $path, $dom, $secure, $httponly);
+}
+if (IsEnabled($EnableCookieSecure, false))
+ @ini_set('session.cookie_secure', $EnableCookieSecure);
+if (IsEnabled($EnableCookieHTTPOnly, false))
+ @ini_set('session.cookie_httponly', $EnableCookieHTTPOnly);
+
+foreach((array)$InterMapFiles as $f) {
+ $f = FmtPageName($f, $pagename);
+ if (($v = @file($f)))
+ $v = preg_replace('/^\\s*(?>\\w[-\\w]*)(?!:)/m', '$0:', implode('', $v));
+ else if (@PageExists($f)) {
+ $p = ReadPage($f, READPAGE_CURRENT);
+ $v = @$p['text'];
+ } else continue;
+ if (!preg_match_all("/^\\s*(\\w[-\\w]*:)[^\\S\n]+(\\S*)/m", $v,
+ $match, PREG_SET_ORDER)) continue;
+ foreach($match as $m) {
+ if (strpos($m[2], '$1') === false) $m[2] .= '$1';
+ $LinkFunctions[$m[1]] = 'LinkIMap';
+ $IMap[$m[1]] = FmtPageName($m[2], $pagename);
+ }
+}
+
+$keys = array_keys($AuthCascade);
+while ($keys) {
+ $k = array_shift($keys); $t = $AuthCascade[$k];
+ if (in_array($t, $keys))
+ { unset($AuthCascade[$k]); $AuthCascade[$k] = $t; array_push($keys, $k); }
+}
+
+$LinkPattern = implode('|',array_keys($LinkFunctions)); # after InterMaps
+SDV($LinkPageCreateSpaceFmt,$LinkPageCreateFmt);
+$ActionTitle = FmtPageName(@$ActionTitleFmt[$action], $pagename);
+
+
+if (!@$HandleActions[$action] || !function_exists($HandleActions[$action]))
+ $action='browse';
+if (IsEnabled($EnableActions, 1)) HandleDispatch($pagename, $action);
+Lock(0);
+return;
+
+## HandleDispatch() is used to dispatch control to the appropriate
+## action handler with the appropriate permissions.
+## If a message is supplied, it is added to $MessagesFmt.
+function HandleDispatch($pagename, $action, $msg=NULL) {
+ global $MessagesFmt, $HandleActions, $HandleAuth;
+ if ($msg) $MessagesFmt[] = "<div class='wikimessage'>$msg</div>";
+ $fn = $HandleActions[$action];
+ $auth = @$HandleAuth[$action];
+ if (!$auth) $auth = 'read';
+ return $fn($pagename, $auth);
+}
+
+## helper functions
+function stripmagic($x) {
+ $fn = 'get_magic_quotes_gpc';
+ if (!function_exists($fn)) return $x;
+ if (is_array($x)) {
+ foreach($x as $k=>$v) $x[$k] = stripmagic($v);
+ return $x;
+ }
+ return @$fn() ? stripslashes($x) : $x;
+}
+function pre_r(&$x)
+ { return '<pre>'.PHSC(print_r($x, true)).'</pre>'; }
+function PSS($x)
+ { return str_replace('\\"','"',$x); }
+function PVS($x)
+ { return preg_replace("/\n[^\\S\n]*(?=\n)/", "\n<:vspace>", $x); }
+function PVSE($x) { return PVS(PHSC($x, ENT_NOQUOTES)); }
+function PZZ($x,$y='') { return ''; }
+function PRR($x=NULL)
+ { if ($x || is_null($x)) $GLOBALS['RedoMarkupLine']++; return $x; }
+function PUE($x)
+ { return preg_replace_callback('/[\\x80-\\xff \'"<>]/', "cb_pue", $x); }
+function cb_pue($m) { return '%'.dechex(ord($m[0])); }
+function PQA($x) {
+ $out = '';
+ if (preg_match_all('/([a-zA-Z][-\\w]*)\\s*=\\s*("[^"]*"|\'[^\']*\'|\\S*)/',
+ $x, $attr, PREG_SET_ORDER)) {
+ foreach($attr as $a) {
+ if (preg_match('/^on/i', $a[1])) continue;
+ $val = preg_replace('/^([\'"]?)(.*)\\1$/', '$2', $a[2]);
+ $val = str_replace("'", ''', $val);
+ $out .= "{$a[1]}='$val' ";
+ }
+ }
+ return $out;
+}
+function SDV(&$v,$x) { if (!isset($v)) $v=$x; }
+function SDVA(&$var,$val)
+ { foreach($val as $k=>$v) if (!isset($var[$k])) $var[$k]=$v; }
+function IsEnabled(&$var,$f=0)
+ { return (isset($var)) ? $var : $f; }
+function SetTmplDisplay($var, $val)
+ { NoCache(); $GLOBALS['TmplDisplay'][$var] = $val; }
+function NoCache($x = '') { $GLOBALS['NoHTMLCache'] |= 1; return $x; }
+function ParseArgs($x, $optpat = '(?>(\\w+)[:=])') {
+ $z = array();
+ preg_match_all("/($optpat|[-+])?(\"[^\"]*\"|'[^']*'|\\S+)/",
+ $x, $terms, PREG_SET_ORDER);
+ foreach($terms as $t) {
+ $v = preg_replace('/^([\'"])?(.*)\\1$/', '$2', $t[3]);
+ if ($t[2]) { $z['#'][] = $t[2]; $z[$t[2]] = $v; }
+ else { $z['#'][] = $t[1]; $z[$t[1]][] = $v; }
+ $z['#'][] = $v;
+ }
+ return $z;
+}
+function PHSC($x, $flags=ENT_COMPAT, $enc=null, $dbl_enc=true) { # for PHP 5.4
+ if (is_null($enc)) $enc = "ISO-8859-1"; # single-byte charset
+ if (! is_array($x)) return @htmlspecialchars($x, $flags, $enc, $dbl_enc);
+ foreach($x as $k=>$v) $x[$k] = PHSC($v, $flags, $enc, $dbl_enc);
+ return $x;
+}
+function PCCF($code, $template = 'default', $args = '$m') {
+ global $CallbackFnTemplates, $CallbackFunctions, $PCCFOverrideFunction;
+ if ($PCCFOverrideFunction && is_callable($PCCFOverrideFunction))
+ return $PCCFOverrideFunction($code, $template, $args);
+
+ if (!isset($CallbackFnTemplates[$template]))
+ Abort("No \$CallbackFnTemplates[$template]).");
+ $code = sprintf($CallbackFnTemplates[$template], $code);
+ if (!isset($CallbackFunctions[$code])) {
+ $fn = create_function($args, $code); # called by old addon|skin|recipe needing update, see pmwiki.org/Troubleshooting
+ if ($fn) $CallbackFunctions[$code] = $fn;
+ else StopWatch("Failed to create callback function: ".PHSC($code));
+ }
+ return $CallbackFunctions[$code];
+}
+function PPRE($pat, $rep, $x) {
+ $lambda = PCCF("return $rep;");
+ return preg_replace_callback($pat, $lambda, $x);
+}
+function PPRA($array, $x) {
+ foreach((array)$array as $pat => $rep) {
+ $fmt = $x; # for $FmtP
+ if (is_callable($rep) && $rep != '_') $x = preg_replace_callback($pat,$rep,$x);
+ else $x = preg_replace($pat,$rep,$x);# simple text OR called by old addon|skin|recipe needing update, see pmwiki.org/Troubleshooting
+ }
+ return $x;
+}
+## callback functions
+class PPRC { # PmWiki preg replace callbacks + pass local vars
+ var $vars;
+ function __construct($vars = false) {
+ if ($vars && !is_null($vars)) $this->vars = $vars;
+ }
+ function pagevar($m) { # called from FmtPageName
+ $pagename = $this->vars;
+ return PageVar($pagename, $m[1]);
+ }
+}
+
+# restores kept/protected strings
+function cb_expandkpv($m) { return $GLOBALS['KPV'][$m[1]]; }
+
+# make a string upper or lower case in various patterns
+function cb_toupper($m) { return strtoupper($m[1]); }
+function cb_tolower($m) { return strtolower($m[1]); }
+
+function pmcrypt($str, $salt=null) {
+ if ($salt && preg_match('/^(-?@|\\*$)/', $salt)) return false;
+ if (!is_null($salt)) return crypt($str, $salt);
+
+ if (function_exists('password_hash'))
+ return password_hash($str, PASSWORD_DEFAULT);
+ return crypt($str);
+}
+
+function StopWatch($x) {
+ global $StopWatch, $EnableStopWatch;
+ if (!$EnableStopWatch) return;
+ static $wstart = 0, $ustart = 0;
+ list($usec,$sec) = explode(' ',microtime());
+ $wtime = ($sec+$usec);
+ if (!$wstart) $wstart = $wtime;
+ if ($EnableStopWatch != 2)
+ { $StopWatch[] = sprintf("%05.2f %s", $wtime-$wstart, $x); return; }
+ $dat = getrusage();
+ $utime = ($dat['ru_utime.tv_sec']+$dat['ru_utime.tv_usec']/1000000);
+ if (!$ustart) $ustart=$utime;
+ $StopWatch[] =
+ sprintf("%05.2f %05.2f %s", $wtime-$wstart, $utime-$ustart, $x);
+}
+
+
+## DRange converts a variety of string formats into date (ranges).
+## It returns the start and end timestamps (+1 second) of the specified date.
+function DRange($when) {
+ global $Now;
+ ## unix/posix @timestamp dates
+ if (preg_match('/^\\s*@(\\d+)\\s*(.*)$/', $when, $m)) {
+ $t0 = $m[2] ? strtotime($m[2], $m[1]) : $m[1];
+ return array($t0, $t0+1);
+ }
+ ## ISO-8601 dates
+ $dpat = '/
+ (?<!\\d) # non-digit
+ (19\\d\\d|20[0-3]\\d) # year ($1)
+ ([-.\\/]?) # date separator ($2)
+ (0\\d|1[0-2]) # month ($3)
+ (?: \\2 # repeat date separator
+ ([0-2]\\d|3[0-1]) # day ($4)
+ (?: T # time marker
+ ([01]\\d|2[0-4]) # hour ($5)
+ ([.:]?) # time separator ($6)
+ ([0-5]\\d) # minute ($7)
+ (?: \\6 # repeat time separator
+ ([0-5]\\d|60) # seconds ($8)
+ )? # optional :ss
+ )? # optional Thh:mm:ss
+ )? # optional -ddThh:mm:ss
+ (?!\d) # non-digit
+ /x';
+ if (preg_match($dpat, $when, $m) &&
+ !preg_match('/[+-]\\s*\\d+\\s*(sec(ond)?|min(ute)?|forth?night|day|week(day)?|month|year)s?/i', $when)) {
+ $n = $m;
+ ## if no time given, assume range of 1 day (except when full month)
+ if (@$m[4]>'' && @$m[5] == '') { @$n[4]++; }
+ ## if no day given, assume 1st of month and full month range
+ if (@$m[4] == '') { $m[4] = 1; $n[4] = 1; $n[3]++; }
+ ## if no seconds given, assume range of 1 minute (except when full day)
+ if (@$m[7]>'' && @$m[8] == '') { @$n[7]++; }
+ $t0 = @mktime($m[5], $m[7], $m[8], $m[3], $m[4], $m[1]);
+ $t1 = @mktime($n[5], $n[7], $n[8], $n[3], $n[4], $n[1]);
+ return array($t0, $t1);
+ }
+ ## now, today, tomorrow, yesterday
+ NoCache();
+ if ($when == 'now') return array($Now, $Now+1);
+ $m = localtime(time());
+ if ($when == 'tomorrow') { $m[3]++; $when = 'today'; }
+ if ($when == 'yesterday') { $m[3]--; $when = 'today'; }
+ if ($when == 'today')
+ return array(mktime(0, 0, 0, $m[4]+1, $m[3] , $m[5]+1900),
+ mktime(0, 0, 0, $m[4]+1, $m[3]+1, $m[5]+1900));
+ if (preg_match('/^\\s*$/', $when)) return array(-1, -1);
+ $t0 = strtotime($when);
+ $t1 = strtotime("+1 day", $t0);
+ return array($t0, $t1);
+}
+
+## DiffTimeCompact subtracts 2 timestamps and outputs a compact
+## human-readable delay in hours, days, weeks, months or years
+function DiffTimeCompact($time, $time2=null, $precision=1) {
+ if(is_null($time2)) $time2 = $GLOBALS['Now'];
+ $suffix = explode(',', XL('h,d,w,m,y'));
+ $x = $hours = abs($time2 - $time)/3600;
+ if($x<24) return round($x,$precision).$suffix[0];
+ $x /= 24; if($x<14) return round($x,$precision).$suffix[1];
+ $x /= 7; if($x< 9) return round($x,$precision).$suffix[2];
+ $x = $hours/2/365.2425; if($x<24) return round($x,$precision).$suffix[3];
+ return round($hours/24/365.2425,$precision).$suffix[4];
+}
+
+## FileSizeCompact outputs a human readable file size
+## with an appropriate suffix.
+## Note: unreliable filemtime()/stat() over 2GB @ 32bit
+function FileSizeCompact($n, $precision=1) {
+ if(!(float)$n) return 0;
+ $units = 'bkMGTPEZY';
+ $b = log((float)$n, 1024);
+ $fb = floor($b);
+ return round(pow(1024,$b-$fb),$precision).@$units[$fb];
+}
+
+## AsSpaced converts a string with WikiWords into a spaced version
+## of that string. (It can be overridden via $AsSpacedFunction.)
+function AsSpaced($text) {
+ $text = preg_replace("/([[:lower:]\\d])([[:upper:]])/", '$1 $2', $text);
+ $text = preg_replace('/([^-\\d])(\\d[-\\d]*( |$))/','$1 $2',$text);
+ return preg_replace("/([[:upper:]])([[:upper:]][[:lower:]\\d])/",
+ '$1 $2', $text);
+}
+
+## Lock is used to make sure only one instance of PmWiki is running when
+## files are being written. It does not "lock pages" for editing.
+function Lock($op) {
+ global $WorkDir, $LockFile, $EnableReadOnly;
+ if ($op > 0 && IsEnabled($EnableReadOnly, 0))
+ Abort('Cannot modify site -- $EnableReadOnly is set', 'readonly');
+ SDV($LockFile, "$WorkDir/.flock");
+ mkdirp(dirname($LockFile));
+ static $lockfp,$curop;
+ if (!$lockfp) $lockfp = @fopen($LockFile, "w");
+ if (!$lockfp) {
+ if ($op <= 0) return;
+ @unlink($LockFile);
+ $lockfp = fopen($LockFile,"w") or
+ Abort('Cannot acquire lockfile', 'flock');
+ fixperms($LockFile);
+ }
+ if ($op<0) { flock($lockfp,LOCK_UN); fclose($lockfp); $lockfp=0; $curop=0; }
+ elseif ($op==0) { flock($lockfp,LOCK_UN); $curop=0; }
+ elseif ($op==1 && $curop<1)
+ { session_write_close(); flock($lockfp,LOCK_SH); $curop=1; }
+ elseif ($op==2 && $curop<2)
+ { session_write_close(); flock($lockfp,LOCK_EX); $curop=2; }
+}
+
+## mkdirp creates a directory and its parents as needed, and sets
+## permissions accordingly.
+function mkdirp($dir) {
+ global $ScriptUrl;
+ if (file_exists($dir)) return;
+ if (!file_exists(dirname($dir))) mkdirp(dirname($dir));
+ if (mkdir($dir, 0777)) {
+ fixperms($dir);
+ if (@touch("$dir/xxx")) { unlink("$dir/xxx"); return; }
+ rmdir($dir);
+ }
+ $parent = realpath(dirname($dir));
+ $bdir = basename($dir);
+ $perms = decoct(fileperms($parent) & 03777);
+ $msg = "PmWiki needs to have a writable <tt>$dir/</tt> directory
+ before it can continue. You can create the directory manually
+ by executing the following commands on your server:
+ <pre> mkdir $parent/$bdir\n chmod 777 $parent/$bdir</pre>
+ Then, <a href='{$ScriptUrl}'>reload this page</a>.";
+ $safemode = ini_get('safe_mode');
+ if (!$safemode) $msg .= "<br /><br />Or, for a slightly more
+ secure installation, try executing <pre> chmod 2777 $parent</pre>
+ on your server and following <a target='_blank' href='$ScriptUrl'>
+ this link</a>. Afterwards you can restore the permissions to
+ their current setting by executing <pre> chmod $perms $parent</pre>.";
+ Abort($msg);
+}
+
+## fixperms attempts to correct permissions on a file or directory
+## so that both PmWiki and the account (current dir) owner can manipulate it
+function fixperms($fname, $add = 0, $set = 0) {
+ clearstatcache();
+ if (!file_exists($fname)) Abort('?no such file');
+ if ($set) { # advanced admins, $UploadPermSet
+ if (fileperms($fname) != $set) @chmod($fname,$set);
+ }
+ else {
+ $bp = 0;
+ if (fileowner($fname)!=@fileowner('.') && @fileowner('.')!==0)
+ $bp = (is_dir($fname)) ? 007 : 006;
+ if (filegroup($fname)==@filegroup('.')) $bp <<= 3;
+ $bp |= $add;
+ if ($bp && (fileperms($fname) & $bp) != $bp)
+ @chmod($fname,fileperms($fname)|$bp);
+ }
+}
+
+## GlobToPCRE converts wildcard patterns into pcre patterns for
+## inclusion and exclusion. Wildcards beginning with '-' or '!'
+## are treated as things to be excluded.
+function GlobToPCRE($pat) {
+ $pat = preg_quote($pat, '/');
+ $pat = str_replace(array('\\*', '\\?', '\\[', '\\]', '\\^', '\\-', '\\!'),
+ array('.*', '.', '[', ']', '^', '-', '!'), $pat);
+ $excl = array(); $incl = array();
+ foreach(preg_split('/,+\s?/', $pat, -1, PREG_SPLIT_NO_EMPTY) as $p) {
+ if ($p[0] == '-' || $p[0] == '!') $excl[] = '^'.substr($p, 1).'$';
+ else $incl[] = "^$p$";
+ }
+ return array(implode('|', $incl), implode('|', $excl));
+}
+
+## FixGlob changes wildcard patterns without '.' to things like
+## '*.foo' (name matches) or 'foo.*' (group matches).
+function FixGlob($x, $rep = '$1*.$2') {
+ return preg_replace('/([\\s,][-!]?)([^\\/.\\s,]+)(?=[\\s,])/', $rep, ",$x,");
+}
+
+## MatchPageNames reduces $pagelist to those pages with names
+## matching the pattern(s) in $pat. Patterns can be either
+## regexes to include ('/'), regexes to exclude ('!'), or
+## wildcard patterns (all others).
+function MatchPageNames($pagelist, $pat) {
+ # Note: MatchNames() is the generic function matching patterns,
+ # works for attachments and other arrays. We can commit to
+ # keep it generic, even if we someday change MatchPageNames().
+ return MatchNames($pagelist, $pat);
+}
+function MatchNames($list, $pat) {
+ global $Charset, $EnableRangeMatchUTF8;
+ # allow range matches in utf8; doesn't work on pmwiki.org and possibly elsewhere
+ $pcre8 = (IsEnabled($EnableRangeMatchUTF8,0) && $Charset=='UTF-8')? 'u' : '';
+ $list = (array)$list;
+ foreach((array)$pat as $p) {
+ if (count($list) < 1) break;
+ if (!$p) continue;
+ switch ($p[0]) {
+ case '/':
+ $list = preg_grep($p, $list);
+ break;
+ case '!':
+ $list = array_diff($list, preg_grep($p, $list));
+ break;
+ default:
+ list($inclp, $exclp) = GlobToPCRE(str_replace('/', '.', $p));
+ if ($exclp)
+ $list = array_diff($list, preg_grep("/$exclp/i$pcre8", $list));
+ if ($inclp)
+ $list = preg_grep("/$inclp/i$pcre8", $list);
+ }
+ }
+ return $list;
+}
+
+## ResolvePageName "normalizes" a pagename based on the current
+## settings of $DefaultPage and $PagePathFmt. It's normally used
+## during initialization to fix up any missing or partial pagenames.
+function ResolvePageName($pagename) {
+ global $DefaultPage, $DefaultGroup, $DefaultName,
+ $GroupPattern, $NamePattern, $EnableFixedUrlRedirect;
+ SDV($DefaultPage, "$DefaultGroup.$DefaultName");
+ $pagename = preg_replace('!([./][^./]+)\\.html?$!', '$1', $pagename);
+ if ($pagename == '') return $DefaultPage;
+ $p = MakePageName($DefaultPage, $pagename);
+ if (!preg_match("/^($GroupPattern)[.\\/]($NamePattern)$/i", $p)) {
+ header('HTTP/1.1 404 Not Found');
+ Abort("\$[?invalid page name] \"$p\"");
+ }
+ if (preg_match("/^($GroupPattern)[.\\/]($NamePattern)$/i", $pagename))
+ return $p;
+ if (IsEnabled($EnableFixedUrlRedirect, 1)
+ && $p && (PageExists($p) || preg_match('/[\\/.]/', $pagename)))
+ { Redirect($p); exit(); }
+ return MakePageName($DefaultPage, "$pagename.$pagename");
+}
+
+## MakePageName is used to convert a string $str into a fully-qualified
+## pagename. If $str doesn't contain a group qualifier, then
+## MakePageName uses $basepage and $PagePathFmt to determine the
+## group of the returned pagename.
+function MakePageName($basepage, $str) {
+ global $MakePageNameFunction, $PageNameChars, $PagePathFmt,
+ $MakePageNamePatterns, $MakePageNameSplitPattern;
+ if (@$MakePageNameFunction) return $MakePageNameFunction($basepage, $str);
+ SDV($PageNameChars,'-[:alnum:]');
+ SDV($MakePageNamePatterns, array(
+ "/'/" => '', # strip single-quotes
+ "/[^$PageNameChars]+/" => ' ', # convert everything else to space
+ '/((^|[^-\\w])\\w)/' => 'cb_toupper', # CamelCase
+ '/ /' => ''));
+ SDV($MakePageNameSplitPattern, '/[.\\/]/');
+ $str = preg_replace('/[#?].*$/', '', $str);
+ $m = preg_split($MakePageNameSplitPattern, $str);
+ if (count($m)<1 || count($m)>2 || $m[0]=='') return '';
+ ## handle "Group.Name" conversions
+ if (@$m[1] > '') {
+ $group = PPRA($MakePageNamePatterns, $m[0]);
+ $name = PPRA($MakePageNamePatterns, $m[1]);
+ return "$group.$name";
+ }
+ $name = PPRA($MakePageNamePatterns, $m[0]);
+ $isgrouphome = count($m) > 1;
+ foreach((array)$PagePathFmt as $pg) {
+ if ($isgrouphome && strncmp($pg, '$1.', 3) !== 0) continue;
+ $pn = FmtPageName(str_replace('$1', $name, $pg), $basepage);
+ if (PageExists($pn)) return $pn;
+ }
+ if ($isgrouphome) {
+ foreach((array)$PagePathFmt as $pg)
+ if (strncmp($pg, '$1.', 3) == 0)
+ return FmtPageName(str_replace('$1', $name, $pg), $basepage);
+ return "$name.$name";
+ }
+ return preg_replace('/[^\\/.]+$/', $name, $basepage);
+}
+
+
+## MakeBaseName uses $BaseNamePatterns to return the "base" form
+## of a given pagename -- i.e., stripping any recipe-defined
+## prefixes or suffixes from the page.
+function MakeBaseName($pagename, $patlist = NULL) {
+ global $BaseNamePatterns;
+ if (is_null($patlist)) $patlist = (array)@$BaseNamePatterns;
+ foreach($patlist as $pat => $rep)
+ $pagename = preg_replace($pat, $rep, $pagename); # TODO
+ return $pagename;
+}
+
+
+## PCache caches basic information about a page and its attributes--
+## usually everything except page text and page history. This makes
+## for quicker access to certain values in PageVar below.
+function PCache($pagename, $page) {
+ global $PCache;
+ foreach($page as $k=>$v)
+ if ($k!='text' && strpos($k,':')===false) $PCache[$pagename][$k]=$v;
+}
+
+## SetProperty saves a page property into $PCache. For convenience
+## it returns the $value of the property just set. If $sep is supplied,
+## then $value is appended to the current property (with $sep as
+## as separator) instead of replacing it. If $keep is suplied and the
+## property already exists, then $value will be ignored.
+function SetProperty($pagename, $prop, $value, $sep=NULL, $keep=NULL) {
+ global $PCache, $KeepToken;
+ NoCache();
+ $prop = "=p_$prop";
+ $value = preg_replace_callback("/$KeepToken(\\d.*?)$KeepToken/",
+ "cb_expandkpv", $value);
+ if (!is_null($sep) && isset($PCache[$pagename][$prop]))
+ $value = $PCache[$pagename][$prop] . $sep . $value;
+ if (is_null($keep) || !isset($PCache[$pagename][$prop]))
+ $PCache[$pagename][$prop] = $value;
+ return $PCache[$pagename][$prop];
+}
+
+
+## PageTextVar loads a page's text variables (defined by
+## $PageTextVarPatterns) into a page's $PCache entry, and returns
+## the property associated with $var.
+function PageTextVar($pagename, $var) {
+ global $PCache, $PageTextVarPatterns, $MaxPageTextVars, $DefaultUnsetPageTextVars, $DefaultEmptyPageTextVars;
+ SDV($MaxPageTextVars, 500);
+ static $status;
+ if (@$status["$pagename:$var"]++ > $MaxPageTextVars) return '';
+ if (!@$PCache[$pagename]['=pagetextvars']) {
+ $pc = &$PCache[$pagename];
+ $pc['=pagetextvars'] = 1;
+ $page = RetrieveAuthPage($pagename, 'read', false, READPAGE_CURRENT);
+ if ($page) {
+ foreach((array)$PageTextVarPatterns as $pat)
+ if (preg_match_all($pat, IsEnabled($pc['=preview'],@$page['text']),
+ $match, PREG_SET_ORDER))
+ foreach($match as $m) {
+ $t = preg_replace("/\\{\\$:{$m[2]}\\}/", '', $m[3]);
+ $pc["=p_{$m[2]}"] = Qualify($pagename, $t);
+ }
+ }
+ }
+ if (! isset($PCache[$pagename]["=p_$var"]) && is_array($DefaultUnsetPageTextVars)) {
+ foreach($DefaultUnsetPageTextVars as $k=>$v) {
+ if (count(MatchNames($var, $k))) {
+ $PCache[$pagename]["=p_$var"] = $v;
+ break;
+ }
+ }
+ SDV($PCache[$pagename]["=p_$var"], ''); # to avoid re-loop
+ }
+ elseif (@$PCache[$pagename]["=p_$var"] == '' && is_array($DefaultEmptyPageTextVars)) {
+ foreach($DefaultEmptyPageTextVars as $k=>$v) {
+ if (count(MatchNames($var, $k))) {
+ $PCache[$pagename]["=p_$var"] = $v;
+ break;
+ }
+ }
+ SDV($PCache[$pagename]["=p_$var"], ''); # to avoid re-loop
+ }
+ return @$PCache[$pagename]["=p_$var"];
+}
+
+
+function PageVar($pagename, $var, $pn = '') {
+ global $Cursor, $PCache, $FmtPV, $AsSpacedFunction, $ScriptUrl,
+ $EnablePathInfo;
+ if ($var == '$ScriptUrl') return PUE($ScriptUrl);
+ if ($pn) {
+ $pn = isset($Cursor[$pn]) ? $Cursor[$pn] : MakePageName($pagename, $pn);
+ } else $pn = $pagename;
+ if ($pn) {
+ if (preg_match('/^(.+)[.\\/]([^.\\/]+)$/', $pn, $match)
+ && !isset($PCache[$pn]['time'])
+ && (!@$FmtPV[$var] || strpos($FmtPV[$var], '$page') !== false)) {
+ $page = ReadPage($pn, READPAGE_CURRENT);
+ PCache($pn, $page);
+ }
+ @list($d, $group, $name) = $match;
+ $page = &$PCache[$pn];
+ if (strpos(@$FmtPV[$var], '$authpage') !== false) {
+ if (!isset($page['=auth']['read'])) {
+ $x = RetrieveAuthPage($pn, 'read', false, READPAGE_CURRENT);
+ if ($x) PCache($pn, $x);
+ }
+ if (@$page['=auth']['read']) $authpage = &$page;
+ }
+ } else { $group = ''; $name = ''; }
+ if (@$FmtPV[$var]) return eval("return ({$FmtPV[$var]});");
+ if (strncmp($var, '$:', 2)==0) return PageTextVar($pn, substr($var, 2));
+ return '';
+}
+
+
+## FmtPageName handles $[internationalization] and $Variable
+## substitutions in strings based on the $pagename argument.
+function FmtPageName($fmt, $pagename) {
+ # Perform $-substitutions on $fmt relative to page given by $pagename
+ global $GroupPattern, $NamePattern, $EnablePathInfo, $ScriptUrl,
+ $GCount, $UnsafeGlobals, $FmtV, $FmtP, $FmtPV, $PCache, $AsSpacedFunction;
+ if (strpos($fmt,'$')===false) return $fmt;
+ $fmt = preg_replace_callback('/\\$([A-Z]\\w*Fmt)\\b/','cb_expandglobal',$fmt);
+ $fmt = preg_replace_callback('/\\$\\[(?>([^\\]]+))\\]/',"cb_expandxlang",$fmt);
+ $fmt = str_replace('{$ScriptUrl}', '$ScriptUrl', $fmt);
+ $pprc = new PPRC($pagename);
+ $fmt = preg_replace_callback('/\\{\\*?(\\$[A-Z]\\w+)\\}/',
+ array($pprc, 'pagevar'), $fmt);
+ if (strpos($fmt,'$')===false) return $fmt;
+ if ($FmtP) $fmt = PPRA($FmtP, $fmt); # FIXME
+ static $pv, $pvpat;
+ if ($pv != count($FmtPV)) {
+ $pvpat = str_replace('$', '\\$', implode('|', array_keys($FmtPV)));
+ $pv = count($FmtPV);
+ }
+ $fmt = preg_replace_callback("/($pvpat)\\b/", array($pprc, 'pagevar'), $fmt);
+ $fmt = preg_replace_callback('!\\$ScriptUrl/([^?#\'"\\s<>]+)!',
+ 'cb_expandscripturl', $fmt);
+ if (strpos($fmt,'$')===false) return $fmt;
+ static $g;
+ if ($GCount != count($GLOBALS)+count($FmtV)) {
+ $g = array();
+ foreach($GLOBALS as $n=>$v) {
+ if (is_array($v) || is_object($v) ||
+ isset($FmtV["\$$n"]) || in_array($n,$UnsafeGlobals)) continue;
+ $g["\$$n"] = $v;
+ }
+ $GCount = count($GLOBALS)+count($FmtV);
+ krsort($g); reset($g);
+ }
+ $fmt = str_replace(array_keys($g),array_values($g),$fmt);
+ $fmt = preg_replace_callback('/(?>(\\$[[:alpha:]]\\w+))/',
+ "cb_expandfmtv", $fmt);
+ return $fmt;
+}
+function cb_expandglobal($m){ return @$GLOBALS[$m[1]]; }
+function cb_expandxlang ($m){ return NoCache(XL($m[1])); }
+function cb_expandfmtv ($m){
+ return isset($GLOBALS['FmtV'][$m[1]]) ? $GLOBALS['FmtV'][$m[1]] : $m[1];
+}
+function cb_expandscripturl($m) {
+ global $EnablePathInfo, $ScriptUrl;
+ return (@$EnablePathInfo) ? "$ScriptUrl/" . PUE($m[1])
+ : "$ScriptUrl?n=".str_replace('/','.',PUE($m[1]));
+}
+
+
+## FmtPageTitle returns the page title, or the page name.
+## It localizes standard technical pages (RecentChanges...)
+function FmtPageTitle($title, $name, $spaced=0) {
+ if ($title>'') return str_replace("$", "$", $title);
+ global $SpaceWikiWords, $AsSpacedFunction;
+ if (preg_match("/^(Site(Admin)?
+ |(All)?(Site|Group)(Header|Footer|Attributes)
+ |(Side|Left|Right)Bar
+ |(Wiki)?Sand[Bb]ox
+ |(All)?Recent(Changes|Uploads)|(Auth|Edit)Form
+ |InterMap|PageActions|\\w+QuickReference|\\w+Templates
+ |NotifyList|AuthUser|ApprovedUrls|(Block|Auth)List
+ )$/x", $name) && $name != XL($name))
+ return XL($name);
+ return ($spaced || $SpaceWikiWords) ? $AsSpacedFunction($name) : $name;
+}
+
+## FmtTemplateVars uses $vars to replace all occurrences of
+## {$$key} in $text with $vars['key'].
+function FmtTemplateVars($text, $vars, $pagename = NULL) {
+ global $FmtPV, $EnableUndefinedTemplateVars;
+ if ($pagename) {
+ $pat = implode('|', array_map('preg_quote', array_keys($FmtPV)));
+ $pprc = new PPRC($pagename);
+ $text = preg_replace_callback("/\\{\\$($pat)\\}/",
+ array($pprc, 'pagevar'), $text);
+ }
+ foreach(preg_grep('/^[\\w$]/', array_keys($vars)) as $k)
+ if (!is_array($vars[$k]))
+ $text = str_replace("{\$\$$k}", @$vars[$k], $text);
+ if (! IsEnabled($EnableUndefinedTemplateVars, 0))
+ $text = preg_replace("/\\{\\$\\$\\w+\\}/", '', $text);
+ return $text;
+}
+
+## The XL functions provide translation tables for $[i18n] strings
+## in FmtPageName().
+function XL($key) {
+ global $XL,$XLLangs;
+ foreach($XLLangs as $l) if (isset($XL[$l][$key])) return $XL[$l][$key];
+ return $key;
+}
+function XLSDV($lang,$a) {
+ global $XL;
+ foreach($a as $k=>$v) {
+ if (!isset($XL[$lang][$k])) {
+ if (preg_match('/^e_(rows|cols)$/', $k)) $v = intval($v);
+ elseif (preg_match('/^ak_/', $k)) $v = @$v[0];
+ $XL[$lang][$k]=$v;
+ }
+ }
+}
+function XLPage($lang,$p,$nohtml=false) {
+ global $TimeFmt,$XLLangs,$FarmD, $EnableXLPageScriptLoad;
+ $page = ReadPage($p, READPAGE_CURRENT);
+ if (!$page) return;
+ $text = preg_replace("/=>\\s*\n/",'=> ',@$page['text']);
+ foreach(explode("\n",$text) as $l)
+ if (preg_match('/^\\s*[\'"](.+?)[\'"]\\s*=>\\s*[\'"](.+)[\'"]/',$l,$m))
+ $xl[stripslashes($m[1])] = stripslashes($nohtml? PHSC($m[2]): $m[2]);
+ if (isset($xl)) {
+ if (IsEnabled($EnableXLPageScriptLoad, 0) && @$xl['xlpage-i18n']) {
+ $i18n = preg_replace('/[^-\\w]/','',$xl['xlpage-i18n']);
+ include_once("$FarmD/scripts/xlpage-$i18n.php");
+ }
+ if (@$xl['Locale']) setlocale(LC_ALL,$xl['Locale']);
+ if (@$xl['TimeFmt']) $TimeFmt=$xl['TimeFmt'];
+ if (!in_array($lang, $XLLangs)) array_unshift($XLLangs, $lang);
+ XLSDV($lang,$xl);
+ }
+}
+
+## CmpPageAttr is used with uksort to order a page's elements with
+## the latest items first. This can make some operations more efficient.
+function CmpPageAttr($a, $b) {
+ @list($x, $agmt) = explode(':', $a);
+ @list($x, $bgmt) = explode(':', $b);
+ if ($agmt != $bgmt)
+ return ($agmt==0 || $bgmt==0) ? $agmt - $bgmt : $bgmt - $agmt;
+ return strcmp($a, $b);
+}
+
+## class PageStore holds objects that store pages via the native
+## filesystem.
+class PageStore {
+ var $dirfmt;
+ var $iswrite;
+ var $encodefilenames;
+ var $attr;
+ function __construct($d='$WorkDir/$FullName', $w=0, $a=NULL) {
+ $this->dirfmt = $d; $this->iswrite = $w; $this->attr = (array)$a;
+ $GLOBALS['PageExistsCache'] = array();
+ }
+ function recodefn($s,$from,$to) {
+ static $able;
+ if(is_null($able)) {
+ # can we rely on iconv() or on mb_convert_encoding() ?
+ if (function_exists('iconv') && @iconv("UTF-8", "WINDOWS-1252//IGNORE", "te\xd0\xafst")=='test' )
+ $able = 'iconv';
+ elseif (function_exists('mb_convert_encoding') && @mb_convert_encoding("te\xd0\xafst", "WINDOWS-1252", "UTF-8")=="te?st")
+ $able = 'mb';
+ }
+ switch ($able) {
+ case "iconv":
+ return @iconv($from,"$to//IGNORE",$s);
+ case "mb":
+ return @mb_convert_encoding($s,$to,$from);
+ }
+ if ($to=='UTF-8' && $from=='WINDOWS-1252') return utf8_decode($s);
+ if ($from=='UTF-8' && $to=='WINDOWS-1252') return utf8_encode($s);
+ return $s;
+ }
+ function pagefile($pagename) {
+ global $FarmD;
+ $dfmt = $this->dirfmt;
+ if ($pagename > '') {
+ $pagename = str_replace('/', '.', $pagename);
+ if ($dfmt == 'wiki.d/{$FullName}') # optimizations for
+ return $this->PFE("wiki.d/$pagename"); # standard locations
+ if ($dfmt == '$FarmD/wikilib.d/{$FullName}') #
+ return $this->PFE("$FarmD/wikilib.d/$pagename");
+ if ($dfmt == 'wiki.d/{$Group}/{$FullName}')
+ return $this->PFE(preg_replace('/([^.]+).*/', 'wiki.d/$1/$0', $pagename));
+ }
+ return $this->PFE(FmtPageName($dfmt, $pagename));
+ }
+ function PFE($f) { # pagefile_encode
+ if (!$this->encodefilenames) return $f;
+ global $PageFileEncodeFunction;
+ return $PageFileEncodeFunction($f);
+ }
+ function PFD($f) { # pagefile_decode
+ if (!$this->encodefilenames) return $f;
+ global $PageFileDecodeFunction;
+ return $PageFileDecodeFunction($f);
+ }
+ function read($pagename, $since=0) {
+ $newline = '';
+ $urlencoded = false;
+ $pagefile = $this->pagefile($pagename);
+ if ($pagefile && ($fp=@fopen($pagefile, "r"))) {
+ $page = $this->attr;
+ while (!feof($fp)) {
+ $line = @fgets($fp, 4096);
+ while (substr($line, -1, 1) != "\n" && !feof($fp))
+ { $line .= fgets($fp, 4096); }
+ $line = rtrim($line);
+ if ($urlencoded) $line = urldecode(str_replace('+', '%2b', $line));
+ @list($k,$v) = explode('=', $line, 2);
+ if (!$k) continue;
+ if ($k == 'version') {
+ $ordered = (strpos($v, 'ordered=1') !== false);
+ $urlencoded = (strpos($v, 'urlencoded=1') !== false);
+ if (strpos($v, 'pmwiki-0.')!==false) $newline="\262";
+ }
+ if ($k == 'newline') { $newline = $v; continue; }
+ if ($since > 0 && preg_match('/:(\\d+)/', $k, $m) && $m[1] < $since) {
+ if ($ordered) break;
+ continue;
+ }
+ if ($newline) $v = str_replace($newline, "\n", $v);
+ $page[$k] = $v;
+ }
+ fclose($fp);
+ }
+ return $this->recode($pagename, @$page);
+ }
+ function write($pagename,$page) {
+ global $Now, $Version, $Charset, $EnableRevUserAgent, $PageExistsCache, $DenyHtaccessContent;
+ $page['charset'] = $Charset;
+ $page['name'] = $pagename;
+ $page['time'] = $Now;
+ $page['host'] = $_SERVER['REMOTE_ADDR'];
+ $page['agent'] = @$_SERVER['HTTP_USER_AGENT'];
+ if(IsEnabled($EnableRevUserAgent, 0)) $page["agent:$Now"] = $page['agent'];
+ $page['rev'] = @$page['rev']+1;
+ unset($page['version']); unset($page['newline']);
+ uksort($page, 'CmpPageAttr');
+ $s = false;
+ $pagefile = $this->pagefile($pagename);
+ $dir = dirname($pagefile); mkdirp($dir);
+ if (!file_exists("$dir/.htaccess") && $fp = @fopen("$dir/.htaccess", "w"))
+ { fwrite($fp, $DenyHtaccessContent); fclose($fp); }
+ if ($pagefile && ($fp=fopen("$pagefile,new","w"))) {
+ $r0 = array('%', "\n", '<');
+ $r1 = array('%25', '%0a', '%3c');
+ $x = "version=$Version ordered=1 urlencoded=1\n";
+ $s = true && fputs($fp, $x); $sz = strlen($x);
+ foreach($page as $k=>$v)
+ if ($k > '' && $k[0] != '=') {
+ $x = str_replace($r0, $r1, "$k=$v") . "\n";
+ $s = $s && fputs($fp, $x); $sz += strlen($x);
+ }
+ $s = fclose($fp) && $s;
+ $s = $s && (filesize("$pagefile,new") > $sz * 0.95);
+ if (file_exists($pagefile)) $s = $s && unlink($pagefile);
+ $s = $s && rename("$pagefile,new", $pagefile);
+ }
+ $s && fixperms($pagefile);
+ if (!$s)
+ Abort("Cannot write page to $pagename ($pagefile)...changes not saved");
+ PCache($pagename, $page);
+ unset($PageExistsCache[$pagename]); # PITS:01401
+ }
+ function exists($pagename) {
+ if (!$pagename) return false;
+ $pagefile = $this->pagefile($pagename);
+ return ($pagefile && file_exists($pagefile));
+ }
+ function delete($pagename) {
+ global $Now, $PageExistsCache;
+ $pagefile = $this->pagefile($pagename);
+ @rename($pagefile,"$pagefile,del-$Now");
+ unset($PageExistsCache[$pagename]); # PITS:01401
+ }
+ function ls($pats=NULL) {
+ global $GroupPattern, $NamePattern;
+ StopWatch("PageStore::ls begin {$this->dirfmt}");
+ $pats=(array)$pats;
+ array_push($pats, "/^$GroupPattern\.$NamePattern$/");
+ $dir = $this->pagefile('$Group.$Name');
+ $maxslash = substr_count($dir, '/');
+ $dirlist = array(preg_replace('!/*[^/]*\\$.*$!','',$dir));
+ $out = array();
+ while (count($dirlist)>0) {
+ $dir = array_shift($dirlist);
+ $dfp = @opendir($dir); if (!$dfp) { continue; }
+ $dirslash = substr_count($dir, '/') + 1;
+ $o = array();
+ while ( ($pagefile = readdir($dfp)) !== false) {
+ if ($pagefile[0] == '.') continue;
+ if ($dirslash < $maxslash && is_dir("$dir/$pagefile"))
+ { array_push($dirlist,"$dir/$pagefile"); continue; }
+ if ($dirslash == $maxslash) $o[] = $this->PFD($pagefile);
+ }
+ closedir($dfp);
+ StopWatch("PageStore::ls merge {$this->dirfmt}");
+ $out = array_merge($out, MatchPageNames($o, $pats));
+ }
+ StopWatch("PageStore::ls end {$this->dirfmt}");
+ return $out;
+ }
+ function recode($pagename, $a) {
+ if (!$a) return false;
+ global $Charset, $PageRecodeFunction, $DefaultPageCharset, $EnableOldCharset;
+ if (function_exists($PageRecodeFunction)) return $PageRecodeFunction($a);
+ if (IsEnabled($EnableOldCharset)) $a['=oldcharset'] = @$a['charset'];
+ SDVA($DefaultPageCharset, array(''=>@$Charset)); # pre-2.2.31 RecentChanges
+ if (@$DefaultPageCharset[$a['charset']]>'') # wrong pre-2.2.30 encs. *-2, *-9, *-13
+ $a['charset'] = $DefaultPageCharset[@$a['charset']];
+ if (!$a['charset'] || $Charset==$a['charset']) return $a;
+ $from = ($a['charset']=='ISO-8859-1') ? 'WINDOWS-1252' : $a['charset'];
+ $to = ($Charset=='ISO-8859-1') ? 'WINDOWS-1252' : $Charset;
+ if($from != $to) {
+ foreach($a as $k=>$v) $a[$k] = $this->recodefn($v,$from,$to);
+ }
+ $a['charset'] = $Charset;
+ return $a;
+ }
+}
+
+function ReadPage($pagename, $since=0) {
+ # read a page from the appropriate directories given by $WikiReadDirsFmt.
+ global $WikiLibDirs,$Now;
+ foreach ($WikiLibDirs as $dir) {
+ $page = $dir->read($pagename, $since);
+ if ($page) break;
+ }
+ if (@!$page) $page['ctime'] = $Now;
+ if (@!$page['time']) $page['time'] = $Now;
+ return $page;
+}
+
+function WritePage($pagename,$page) {
+ global $WikiLibDirs,$WikiDir,$LastModFile;
+ $WikiDir->iswrite = 1;
+ for($i=0; $i<count($WikiLibDirs); $i++) {
+ $wd = &$WikiLibDirs[$i];
+ if ($wd->iswrite && $wd->exists($pagename)) break;
+ }
+ if ($i >= count($WikiLibDirs)) $wd = &$WikiDir;
+ $wd->write($pagename,$page);
+ if ($LastModFile && !@touch($LastModFile))
+ { unlink($LastModFile); touch($LastModFile); fixperms($LastModFile); }
+}
+
+function PageExists($pagename) {
+ ## note: $PageExistsCache might change or disappear someday
+ global $WikiLibDirs, $PageExistsCache;
+ if (!isset($PageExistsCache[$pagename])) {
+ foreach((array)$WikiLibDirs as $dir)
+ if ($PageExistsCache[$pagename] = $dir->exists($pagename)) break;
+ }
+ return $PageExistsCache[$pagename];
+}
+
+function ListPages($pat=NULL) {
+ global $WikiLibDirs;
+ foreach((array)$WikiLibDirs as $dir)
+ $out = array_unique(array_merge($dir->ls($pat),(array)@$out));
+ return $out;
+}
+
+function RetrieveAuthPage($pagename, $level, $authprompt=true, $since=0) {
+ global $AuthFunction;
+ SDV($AuthFunction,'PmWikiAuth');
+ if (!function_exists($AuthFunction)) return ReadPage($pagename, $since);
+ return $AuthFunction($pagename, $level, $authprompt, $since);
+}
+
+function Abort($msg, $info='') {
+ # exit pmwiki with an abort message
+ global $ScriptUrl, $Charset, $AbortFunction;
+ if (@$AbortFunction) return $AbortFunction($msg, $info);
+ if ($info)
+ $info = "<p class='vspace'><a target='_blank' rel='nofollow' href='http://www.pmwiki.org/pmwiki/info/$info'>$[More information]</a></p>";
+ $msg = "<h3>$[PmWiki can't process your request]</h3>
+ <p class='vspace'>$msg</p>
+ <p class='vspace'>$[We are sorry for any inconvenience].</p>
+ $info
+ <p class='vspace'><a href='$ScriptUrl'>$[Return to] $ScriptUrl</a></p>";
+ @header("Content-type: text/html; charset=$Charset");
+ echo preg_replace_callback('/\\$\\[([^\\]]+)\\]/', "cb_expandxlang", $msg);
+ exit;
+}
+
+function Redirect($pagename, $urlfmt='$PageUrl', $redirecturl=null) {
+ # redirect the browser to $pagename
+ global $EnableRedirect, $RedirectDelay, $EnableStopWatch;
+ SDV($RedirectDelay, 0);
+ clearstatcache();
+ if (is_null($redirecturl)) $redirecturl = FmtPageName($urlfmt,$pagename);
+ if (IsEnabled($EnableRedirect,1) &&
+ (!isset($_REQUEST['redirect']) || $_REQUEST['redirect'])) {
+ header("Location: $redirecturl");
+ header("Content-type: text/html");
+ echo "<html><head>
+ <meta http-equiv='Refresh' Content='$RedirectDelay; URL=$redirecturl' />
+ <title>Redirect</title></head><body></body></html>";
+ exit;
+ }
+ echo "<a href='$redirecturl'>Redirect to $redirecturl</a>";
+ if (@$EnableStopWatch && function_exists('StopWatchHTML'))
+ StopWatchHTML($pagename, 1);
+ exit;
+}
+
+function PrintFmt($pagename,$fmt) {
+ global $HTTPHeaders,$FmtV;
+ if (is_array($fmt))
+ { foreach($fmt as $f) PrintFmt($pagename,$f); return; }
+ if ($fmt == 'headers:') {
+ foreach($HTTPHeaders as $h) (@$sent++) ? @header($h) : header($h);
+ return;
+ }
+ $x = FmtPageName($fmt,$pagename);
+ if (strncmp($fmt, 'function:', 9) == 0 &&
+ preg_match('/^function:(\S+)\s*(.*)$/s', $x, $match) &&
+ function_exists($match[1]))
+ { $match[1]($pagename,$match[2]); return; }
+ if (strncmp($fmt, 'file:', 5) == 0 && preg_match("/^file:(.+)/s",$x,$match)) {
+ $filelist = preg_split('/[\\s]+/',$match[1],-1,PREG_SPLIT_NO_EMPTY);
+ foreach($filelist as $f) {
+ if (file_exists($f)) { include($f); return; }
+ }
+ return;
+ }
+ if (substr($x, 0, 7) == 'markup:')
+ { print MarkupToHTML($pagename, substr($x, 7)); return; }
+ if (substr($x, 0, 5) == 'wiki:')
+ { PrintWikiPage($pagename, substr($x, 5), 'read'); return; }
+ if (substr($x, 0, 5) == 'page:')
+ { PrintWikiPage($pagename, substr($x, 5), ''); return; }
+ echo $x;
+}
+
+function PrintWikiPage($pagename, $wikilist=NULL, $auth='read') {
+ if (is_null($wikilist)) $wikilist=$pagename;
+ $pagelist = preg_split('/\s+/',$wikilist,-1,PREG_SPLIT_NO_EMPTY);
+ foreach($pagelist as $p) {
+ if (PageExists($p)) {
+ $page = ($auth) ? RetrieveAuthPage($p, $auth, false, READPAGE_CURRENT)
+ : ReadPage($p, READPAGE_CURRENT);
+ if ($page['text'])
+ echo MarkupToHTML($pagename,Qualify($p, $page['text']));
+ return;
+ }
+ }
+}
+
+function Keep($x, $pool=NULL) {
+ if(is_array($x)) $x = $x[0]; # used in many callbacks
+ # Keep preserves a string from being processed by wiki markups
+ global $BlockPattern, $KeepToken, $KPV, $KPCount;
+ $x = preg_replace_callback("/$KeepToken(\\d.*?)$KeepToken/", 'cb_expandkpv', $x);
+ if (is_null($pool) && preg_match("!</?($BlockPattern)\\b!", $x)) $pool = 'B';
+ $KPCount++; $KPV[$KPCount.$pool]=$x;
+ return $KeepToken.$KPCount.$pool.$KeepToken;
+}
+
+
+## MarkupEscape examines markup source and escapes any [@...@]
+## and [=...=] sequences using Keep(). MarkupRestore undoes the
+## effect of any MarkupEscape().
+function MarkupEscape($text) {
+ global $EscapePattern;
+ SDV($EscapePattern, '\\[([=@]).*?\\1\\]');
+ return preg_replace_callback("/$EscapePattern/s", "Keep", $text);
+}
+function MarkupRestore($text) {
+ global $KeepToken, $KPV;
+ return preg_replace_callback("/$KeepToken(\\d.*?)$KeepToken/", 'cb_expandkpv', $text);
+}
+
+
+## Qualify() applies $QualifyPatterns to convert relative links
+## and references into absolute equivalents.
+function Qualify($pagename, $text) {
+ global $QualifyPatterns, $KeepToken, $KPV, $tmp_qualify;
+ if (!@$QualifyPatterns) return $text;
+ $text = MarkupEscape($text);
+ $group = $tmp_qualify['group'] = PageVar($pagename, '$Group');
+ $name = $tmp_qualify['name'] = PageVar($pagename, '$Name');
+ $tmp_qualify['pagename'] = $pagename;
+ $text = PPRA((array)$QualifyPatterns, $text);
+ return MarkupRestore($text);
+}
+
+
+function CondText($pagename,$condspec,$condtext) {
+ global $Conditions;
+ if (!preg_match("/^(\\S+)\\s*(!?)\\s*(\\S+)?\\s*(.*?)\\s*$/",
+ $condspec,$match)) return '';
+ @list($condstr,$condtype,$not,$condname,$condparm) = $match;
+ if (isset($Conditions[$condname])) {
+ $tf = @eval("return (".$Conditions[$condname].");");
+ if (!$tf xor $not) $condtext='';
+ }
+ return $condtext;
+}
+
+
+## TextSection extracts a section of text delimited by page anchors.
+## The $sections parameter can have the form
+## #abc - [[#abc]] to next anchor
+## #abc#def - [[#abc]] up to [[#def]]
+## #abc#, #abc.. - [[#abc]] to end of text
+## ##abc, #..#abc - beginning of text to [[#abc]]
+## Returns the text unchanged if no sections are requested,
+## or false if a requested beginning anchor isn't in the text.
+function TextSection($text, $sections, $args = NULL) {
+ $args = (array)$args;
+ $npat = '[[:alpha:]][-\\w.]*';
+ if (!preg_match("/#($npat)?(\\.\\.)?(#($npat)?)?/", $sections, $match))
+ return $text;
+ @list($x, $aa, $dots, $b, $bb) = $match;
+ if (!$dots && !$b) $bb = $npat;
+ if ($aa) {
+ $aa = preg_replace('/\\.\\.$/', '', $aa);
+ $pos = strpos($text, "[[#$aa]]"); if ($pos === false) return false;
+ if (@$args['anchors'])
+ while ($pos > 0 && $text[$pos-1] != "\n") $pos--;
+ else $pos += strlen("[[#$aa]]");
+ $text = substr($text, $pos);
+ }
+ if ($bb)
+ $text = preg_replace("/(\n)[^\n]*\\[\\[#$bb\\]\\].*$/s", '$1', $text, 1);
+ return $text;
+}
+
+
+## RetrieveAuthSection extracts a section of text from a page.
+## If $pagesection starts with anything other than '#', it identifies
+## the page to extract text from. Otherwise RetrieveAuthSection looks
+## in the pages given by $list, or in $pagename if $list is not specified.
+## The selected page is placed in the global $RASPageName variable.
+## The caller is responsible for calling Qualify() as needed.
+function RetrieveAuthSection($pagename, $pagesection, $list=NULL, $auth='read') {
+ global $RASPageName, $PCache;
+ if ($pagesection[0] != '#')
+ $list = array(MakePageName($pagename, $pagesection));
+ else if (is_null($list)) $list = array($pagename);
+ foreach((array)$list as $t) {
+ $t = FmtPageName($t, $pagename);
+ if (!PageExists($t)) continue;
+ $tpage = RetrieveAuthPage($t, $auth, false, READPAGE_CURRENT);
+ if (!$tpage) continue;
+ $text = TextSection(IsEnabled($PCache[$t]['=preview'],$tpage['text']),$pagesection);
+ if ($text !== false) { $RASPageName = $t; return $text; }
+ }
+ $RASPageName = '';
+ return false;
+}
+
+function IncludeText($pagename, $inclspec) {
+ global $MaxIncludes, $IncludeOpt, $InclCount, $PCache;
+ SDV($MaxIncludes,50);
+ SDVA($IncludeOpt, array('self'=>1));
+ if (@$InclCount[$pagename]++>=$MaxIncludes) return Keep($inclspec);
+ $args = array_merge($IncludeOpt, ParseArgs($inclspec));
+ while (count($args['#'])>0) {
+ $k = array_shift($args['#']); $v = array_shift($args['#']);
+ if ($k=='') {
+ if ($v[0] != '#') {
+ if (isset($itext)) continue;
+ $iname = MakePageName($pagename, $v);
+ if (!$args['self'] && $iname == $pagename) continue;
+ $ipage = RetrieveAuthPage($iname, 'read', false, READPAGE_CURRENT);
+ $itext = IsEnabled($PCache[$iname]['=preview'], @$ipage['text']);
+ }
+ $itext = TextSection(@$itext, $v, array('anchors' => 1));
+ continue;
+ }
+ if (preg_match('/^(?:line|para)s?$/', $k)) {
+ preg_match('/^(\\d*)(\\.\\.(\\d*))?$/', $v, $match);
+ @list($x, $a, $dots, $b) = $match;
+ $upat = ($k[0] == 'p') ? ".*?(\n\\s*\n|$)" : "[^\n]*(?:\n|$)";
+ if (!$dots) { $b=$a; $a=0; }
+ if ($a>0) $a--;
+ $itext=preg_replace("/^(($upat){0,$b}).*$/s",'$1',$itext,1);
+ $itext=preg_replace("/^($upat){0,$a}/s",'',$itext,1);
+ continue;
+ }
+ }
+ $basepage = isset($args['basepage'])
+ ? MakePageName($pagename, $args['basepage'])
+ : @$iname;
+ if ($basepage) $itext = Qualify(@$basepage, @$itext);
+ return FmtTemplateVars(PVSE(@$itext), $args);
+}
+
+
+function RedirectMarkup($pagename, $opt) {
+ $k = Keep("(:redirect $opt:)");
+ global $MarkupFrame, $EnableRedirectQuiet;
+ if (!@$MarkupFrame[0]['redirect']) return $k;
+ $opt = ParseArgs($opt);
+ $to = @$opt['to']; if (!$to) $to = @$opt[''][0];
+ if (!$to) return $k;
+ if (preg_match('/^([^#]+)(#[A-Za-z][-\\w]*)$/', $to, $match))
+ { $to = $match[1]; $anchor = @$match[2]; }
+ $to = MakePageName($pagename, $to);
+ if (!PageExists($to)) return $k;
+ if ($to == $pagename) return '';
+ if (@$opt['from']
+ && !MatchPageNames($pagename, FixGlob($opt['from'], '$1*.$2')))
+ return '';
+ if (preg_match('/^30[1237]$/', @$opt['status']))
+ header("HTTP/1.1 {$opt['status']}");
+ Redirect($to, "{\$PageUrl}"
+ . (IsEnabled($EnableRedirectQuiet, 0) && IsEnabled($opt['quiet'], 0)
+ ? '' : "?from=$pagename")
+ . $anchor);
+ exit();
+}
+
+
+function Block($b) {
+ global $BlockMarkups,$HTMLVSpace,$HTMLPNewline,$MarkupFrame;
+ $mf = &$MarkupFrame[0]; $cs = &$mf['cs']; $vspaces = &$mf['vs'];
+ $out = '';
+ if ($b == 'vspace') {
+ $vspaces .= "\n";
+ while (count($cs)>0 && @end($cs)!='pre' && @$BlockMarkups[@end($cs)][3]==0)
+ { $c = array_pop($cs); $out .= $BlockMarkups[$c][2]; }
+ return $out;
+ }
+ @list($code, $depth, $icol) = explode(',', $b);
+ if (!$code) $depth = 1;
+ if ($depth == 0) $depth = strlen($depth);
+ if ($icol == 0) $icol = strlen($icol);
+ if ($depth > 0) $depth += @$mf['idep'];
+ if ($icol > 0) $mf['is'][$depth] = $icol + @$mf['icol'];
+ @$mf['idep'] = @$mf['icol'] = 0;
+ while (count($cs)>$depth)
+ { $c = array_pop($cs); $out .= $BlockMarkups[$c][2]; }
+ if (!$code) {
+ if (@end($cs) == 'p') { $out .= $HTMLPNewline; $code = 'p'; }
+ else if ($depth < 2) { $code = 'p'; $mf['is'][$depth] = 0; }
+ else { $out .= $HTMLPNewline; $code = 'block'; }
+ }
+ if ($depth>0 && $depth==count($cs) && $cs[$depth-1]!=$code)
+ { $c = array_pop($cs); $out .= $BlockMarkups[$c][2]; }
+ while (count($cs)>0 && @end($cs)!=$code &&
+ @$BlockMarkups[@end($cs)][3]==0)
+ { $c = array_pop($cs); $out .= $BlockMarkups[$c][2]; }
+ if ($vspaces) {
+ $out .= (@end($cs) == 'pre') ? $vspaces : $HTMLVSpace;
+ $vspaces='';
+ }
+ if ($depth==0) { return $out; }
+ if ($depth==count($cs)) { return $out.$BlockMarkups[$code][1]; }
+ while (count($cs)<$depth-1) {
+ array_push($cs, 'dl'); $mf['is'][count($cs)] = 0;
+ $out .= $BlockMarkups['dl'][0].'<dd>';
+ }
+ if (count($cs)<$depth) {
+ array_push($cs,$code);
+ $out .= $BlockMarkups[$code][0];
+ }
+ return $out;
+}
+
+
+function MarkupClose($key = '') {
+ global $MarkupFrame;
+ $cf = & $MarkupFrame[0]['closeall'];
+ $out = '';
+ if ($key == '' || isset($cf[$key])) {
+ $k = array_keys((array)$cf);
+ while ($k) {
+ $x = array_pop($k); $out .= $cf[$x]; unset($cf[$x]);
+ if ($x == $key) break;
+ }
+ }
+ return $out;
+}
+
+
+function FormatTableRow($x, $sep = '\\|\\|') {
+ global $TableCellAttrFmt, $TableCellAlignFmt, $TableRowAttrFmt,
+ $TableRowIndexMax, $MarkupFrame, $FmtV, $EnableSimpleTableRowspan;
+ static $rowcount;
+ SDV($TableCellAlignFmt, " align='%s'");
+
+ if (IsEnabled($EnableSimpleTableRowspan, 0)) {
+ $x = preg_replace("/\\|\\|__+(?=\\|\\|)/", '||', $x);
+ $x = preg_replace("/\\|\\|\\^\\^+(?=\\|\\|)/", '', $x);
+ }
+ $x = preg_replace("/$sep\\s*$/",'',$x);
+ $td = preg_split("/$sep/", $x); $y = '';
+ for($i=0;$i<count($td);$i++) {
+ if ($td[$i]=='') continue;
+ $FmtV['$TableCellCount'] = $i;
+ $attr = FmtPageName(@$TableCellAttrFmt, '');
+ if (IsEnabled($EnableSimpleTableRowspan, 0)) {
+ if (preg_match('/(\\+\\++)\\s*$/', $td[$i], $rspn)) {
+ $td[$i] = preg_replace('/\\+\\++(\\s*)$/', '$1', $td[$i]);
+ $attr .= " rowspan='".strlen($rspn[1])."'";
+ }
+ }
+ $td[$i] = preg_replace('/^(!?)\\s+$/', '$1 ', $td[$i]);
+ if (preg_match('/^!(.*?)!$/',$td[$i],$match))
+ { $td[$i]=$match[1]; $t='caption'; $attr=''; }
+ elseif (preg_match('/^!(.*)$/',$td[$i],$match))
+ { $td[$i]=$match[1]; $t='th'; }
+ else $t='td';
+ if (preg_match('/^\\s.*\\s$/',$td[$i])) {
+ if ($t!='caption') $attr .= sprintf($TableCellAlignFmt, 'center');
+ }
+ elseif (preg_match('/^\\s/',$td[$i])) { $attr .= sprintf($TableCellAlignFmt, 'right'); }
+ elseif (preg_match('/\\s$/',$td[$i])) { $attr .= sprintf($TableCellAlignFmt, 'left'); }
+ for ($colspan=1;$i+$colspan<count($td);$colspan++)
+ if ($td[$colspan+$i]!='') break;
+ if ($colspan>1) { $attr .= " colspan='$colspan'"; }
+ $y .= "<$t $attr>".trim($td[$i])."</$t>";
+ }
+ if ($t=='caption') return "<:table,1>$y";
+ if (@$MarkupFrame[0]['cs'][0] != 'table') $rowcount = 0; else $rowcount++;
+ $FmtV['$TableRowCount'] = $rowcount + 1;
+ $FmtV['$TableRowIndex'] = ($rowcount % $TableRowIndexMax) + 1;
+ $trattr = FmtPageName(@$TableRowAttrFmt, '');
+ return "<:table,1><tr $trattr>$y</tr>";
+}
+
+function LinkIMap($pagename,$imap,$path,$alt,$txt,$fmt=NULL) {
+ global $FmtV, $IMap, $IMapLinkFmt, $UrlLinkFmt, $IMapLocalPath, $ScriptUrl, $AddLinkCSS;
+ SDVA($IMapLocalPath, array('Path:'=>1));
+ if (@$IMapLocalPath[$imap]) {
+ $path = preg_replace('/^(\\w+):/', "$1%3a", $path); # PITS:01260
+ }
+ $FmtV['$LinkUrl'] = PUE(str_replace('$1',$path,$IMap[$imap]));
+ $FmtV['$LinkText'] = $txt;
+ $FmtV['$LinkAlt'] = str_replace(array('"',"'"),array('"','''),$alt);
+ if (!$fmt)
+ $fmt = (isset($IMapLinkFmt[$imap])) ? $IMapLinkFmt[$imap] : $UrlLinkFmt;
+ if(IsEnabled($AddLinkCSS['samedomain'])) {
+ $parsed_url = parse_url($FmtV['$LinkUrl']);
+ $parsed_wiki = parse_url($ScriptUrl);
+ if(! @$parsed_url['host'] || $parsed_url['host'] == $parsed_wiki['host']) {
+ $fmt = preg_replace('/(<a[^>]*class=["\'])/', "$1{$AddLinkCSS['samedomain']} ", $fmt);
+ }
+ }
+ return str_replace(array_keys($FmtV),array_values($FmtV),$fmt);
+}
+
+## These 2 functions hide e-mail addresses from spam bot harvesters
+## recover them for most users with a javascript utility,
+## while keeping them readable for users with JS disabled.
+## Based on Cookbook:DeObMail by Petko Yotov
+## To enable, set $LinkFunctions['mailto:'] = 'ObfuscateLinkIMap';
+function ObfuscateLinkIMap($pagename,$imap,$path,$title,$txt,$fmt=NULL) {
+ global $FmtV, $IMap, $IMapLinkFmt;
+ SDVA($IMapLinkFmt, array('obfuscate-mailto:' =>
+ "<span class='_pmXmail' title=\"\$LinkAlt\"><span class='_t'>\$LinkText</span><span class='_m'>\$LinkUrl</span></span>"));
+ $FmtV['$LinkUrl'] = cb_obfuscate_mail(str_replace('$1',$path,$IMap[$imap]));
+ $FmtV['$LinkText'] = cb_obfuscate_mail(preg_replace('/^mailto:/i', '', $txt));
+ if($FmtV['$LinkText'] == preg_replace('/^mailto:/i', '', $FmtV['$LinkUrl'])) $FmtV['$LinkUrl'] = '';
+ else $FmtV['$LinkUrl'] = " -> ".$FmtV['$LinkUrl'];
+ $FmtV['$LinkAlt'] = str_replace(array('"',"'"),array('"','''),cb_obfuscate_mail($title, 0));
+ return str_replace(array_keys($FmtV),array_values($FmtV), $IMapLinkFmt['obfuscate-mailto:']);
+}
+
+function cb_obfuscate_mail($x, $wrap=1) {
+ $classes = array('.' => '_d', '@' => '_a');
+ $texts = array( '.' => XL(' [period] '), '@' => XL(' [snail] '));
+ foreach($classes as $k=>$v)
+ $x = preg_replace("/(\\w)".preg_quote($k)."(\\w)/",
+ ($wrap?
+ "$1<span class='$v'>{$texts[$k]}</span>$2"
+ : "$1{$texts[$k]}$2")
+ , $x);
+ return $x;
+}
+
+function LinkPage($pagename,$imap,$path,$alt,$txt,$fmt=NULL) {
+ global $QueryFragPattern, $LinkPageExistsFmt, $LinkPageSelfFmt,
+ $LinkPageCreateSpaceFmt, $LinkPageCreateFmt, $LinkTargets,
+ $EnableLinkPageRelative, $EnableLinkPlusTitlespaced, $AddLinkCSS;
+ $alt = str_replace(array('"',"'"),array('"','''),$alt);
+ $path = preg_replace('/(#[-.:\\w]*)#.*$/', '$1', $path); # PITS:01388
+ if (is_array($txt)) { # PITS:01392
+ $suffix = $txt[1];
+ $txt = $txt[0];
+ }
+ if (!$fmt && @$path[0] == '#') {
+ $path = preg_replace("/[^-.:\\w]/", '', $path);
+ if (trim($txt) == '+') $txt = PageVar($pagename, '$Title') . @$suffix;
+ if ($alt) $alt = " title='$alt'";
+ return ($path) ? "<a href='#$path'$alt>".str_replace("$", "$", $txt)."</a>" : '';
+ }
+ if (!preg_match("/^\\s*([^#?]+)($QueryFragPattern)?$/",$path,$match))
+ return '';
+ $tgtname = MakePageName($pagename, $match[1]);
+ if (!$tgtname) return '';
+ $qf = @$match[2];
+ @$LinkTargets[$tgtname]++;
+ if (!$fmt) {
+ if (!PageExists($tgtname) && !preg_match('/[&?]action=/', $qf))
+ $fmt = preg_match('/\\s/', $txt)
+ ? $LinkPageCreateSpaceFmt : $LinkPageCreateFmt;
+ else
+ $fmt = ($tgtname == $pagename && $qf == '')
+ ? $LinkPageSelfFmt : $LinkPageExistsFmt;
+ }
+ $url = PageVar($tgtname, '$PageUrl');
+ if (trim($txt) == '+') $txt = PageVar($tgtname,
+ IsEnabled($EnableLinkPlusTitlespaced, 0) ? '$Titlespaced' : '$Title') . @$suffix;
+ $txt = str_replace("$", "$", $txt);
+ if (@$EnableLinkPageRelative)
+ $url = preg_replace('!^[a-z]+://[^/]*!i', '', $url);
+ $fmt = str_replace(array('$LinkUrl', '$LinkText', '$LinkAlt'),
+ array($url.PUE($qf), $txt, $alt), $fmt);
+ if(IsEnabled($AddLinkCSS['othergroup'])) {
+ list($cgroup, ) = explode('.', $pagename);
+ list($tgroup, ) = explode('.', $tgtname);
+ if($cgroup != $tgroup)
+ $fmt = preg_replace('/(<a[^>]*class=["\'])/', "$1{$AddLinkCSS['othergroup']} ", $fmt);
+ }
+ return FmtPageName($fmt,$tgtname);
+}
+
+function MakeLink($pagename,$tgt,$txt=NULL,$suffix=NULL,$fmt=NULL) {
+ global $LinkPattern,$LinkFunctions,$UrlExcludeChars,$ImgExtPattern,$ImgTagFmt,
+ $LinkTitleFunction;
+ if(preg_match("/^(.*)(?:\"(.*)\")\\s*$/",$tgt,$x)) list(,$tgt,$title) = $x;
+ $t = preg_replace('/[()]/','',trim($tgt));
+ $t = preg_replace('/<[^>]*>/','',$t);
+ $t = trim(MarkupRestore($t));
+ $txtr = trim(MarkupRestore($txt));
+
+ preg_match("/^($LinkPattern)?(.+)$/",$t,$m);
+ if (!@$m[1]) $m[1]='<:page>';
+ if (preg_match("/(($LinkPattern)([^$UrlExcludeChars]+$ImgExtPattern))(\"(.*)\")?$/",$txtr,$tm))
+ $txt = $LinkFunctions[$tm[2]]($pagename,$tm[2],$tm[3],@$tm[5],
+ $tm[1],$ImgTagFmt);
+ else {
+ if (is_null($txt)) {
+ $txt = preg_replace('/\\([^)]*\\)/','',$tgt);
+ if ($m[1]=='<:page>') {
+ $txt = preg_replace('!/\\s*$!', '', $txt);
+ $txt = preg_replace('!^.*[^<]/!', '', $txt);
+ }
+ }
+ if ($m[1]=='<:page>' && trim($txt) == '+' && $suffix>'') { # PITS:01392
+ $txt = array(trim($txt), $suffix);
+ }
+ else $txt .= $suffix;
+ }
+ if (@$LinkTitleFunction) $title = $LinkTitleFunction($pagename,$m,$txt);
+ else $title = PHSC(MarkupRestore(@$title), ENT_QUOTES);
+ $out = $LinkFunctions[$m[1]]($pagename,$m[1],@$m[2],@$title,$txt,$fmt);
+ return preg_replace('/(<[^>]+)\\stitle=(""|\'\')/', '$1', $out);
+}
+
+function Markup($id, $when, $pat=NULL, $rep=NULL, $tracelev=0) {
+ global $MarkupTable, $EnableMarkupDiag;
+ unset($GLOBALS['MarkupRules']);
+ if (preg_match('/^([<>])?(.+)$/', $when, $m)) {
+ $MarkupTable[$id]['cmd'] = $when;
+ $MarkupTable[$m[2]]['dep'][$id] = $m[1];
+ if (!$m[1]) $m[1] = '=';
+ if (@$MarkupTable[$m[2]]['seq']) {
+ $MarkupTable[$id]['seq'] = $MarkupTable[$m[2]]['seq'].$m[1];
+ foreach((array)@$MarkupTable[$id]['dep'] as $i=>$m)
+ Markup($i,"$m$id");
+ unset($GLOBALS['MarkupTable'][$id]['dep']);
+ }
+ }
+ if ($pat && !isset($MarkupTable[$id]['pat'])) {
+ $MarkupTable[$id]['pat'] = $pat;
+ $MarkupTable[$id]['rep'] = $rep;
+
+ $oldpat = preg_match('!/[^/]*e[^/]*$!', $pat);
+ if (IsEnabled($EnableMarkupDiag, 0) || $oldpat) {
+ $exmark = $oldpat ? '!' : ' ';
+ if (function_exists('debug_backtrace')) {
+ $dbg = debug_backtrace();
+ $dbginfo = $dbg[$tracelev];
+ $MarkupTable[$id]['dbg'] = "$exmark file: {$dbginfo['file']}, "
+ . "line: {$dbginfo['line']}, pat: {$dbginfo['args'][2]}";
+ }
+ else
+ $MarkupTable[$id]['dbg'] = "$exmark id: '$id', pat: '$pat'";
+ }
+ }
+}
+
+function Markup_e($id, $when, $pat, $rep, $template = 'markup_e') {
+ if (!is_callable($rep)) $rep = PCCF($rep, $template);
+ Markup($id, $when, $pat, $rep, 1);
+}
+
+function DisableMarkup() {
+ global $MarkupTable;
+ $idlist = func_get_args();
+ unset($GLOBALS['MarkupRules']);
+ while (count($idlist)>0) {
+ $id = array_shift($idlist);
+ if (is_array($id)) { $idlist = array_merge($idlist, $id); continue; }
+ $MarkupTable[$id] = array('cmd' => 'none', 'pat'=>'');
+ }
+}
+
+function mpcmp($a,$b) { return @strcmp($a['seq'].'=',$b['seq'].'='); }
+function BuildMarkupRules() {
+ global $MarkupTable,$MarkupRules,$LinkPattern;
+ if (!$MarkupRules) {
+ uasort($MarkupTable,'mpcmp');
+ foreach($MarkupTable as $id=>$m)
+ if (@$m['pat'] && @$m['seq']) {
+ $MarkupRules[str_replace('\\L',$LinkPattern,$m['pat'])]
+ = array($m['rep'], $id);
+ }
+ }
+ return $MarkupRules;
+}
+
+
+function MarkupToHTML($pagename, $text, $opt = NULL) {
+ # convert wiki markup text to HTML output
+ global $MarkupRules, $MarkupFrame, $MarkupFrameBase, $WikiWordCount,
+ $K0, $K1, $RedoMarkupLine, $MarkupToHTML;
+ $MarkupToHTML['pagename'] = $pagename;
+
+ StopWatch('MarkupToHTML begin');
+ array_unshift($MarkupFrame, array_merge($MarkupFrameBase, (array)$opt));
+ $MarkupFrame[0]['wwcount'] = $WikiWordCount;
+ foreach((array)$text as $l)
+ $lines[] = $MarkupFrame[0]['escape'] ? PVSE($l) : $l;
+ $lines[] = '(:closeall:)';
+ $out = '';
+ while (count($lines)>0) {
+ $x = array_shift($lines);
+ $RedoMarkupLine=0;
+ $markrules = BuildMarkupRules();
+ foreach($markrules as $p=>$r) {
+ list($r, $id) = (array)$r;
+ $MarkupToHTML['markupid'] = $id;
+ if ($p[0] == '/') {
+ if (is_callable($r)) $x = preg_replace_callback($p,$r,$x);
+ else $x=preg_replace($p,$r,$x); # simple text OR called by old addon|skin|recipe needing update, see pmwiki.org/Troubleshooting
+ }
+ elseif (strstr($x,$p)!==false) $x=eval($r);
+ if (isset($php_errormsg))
+ { echo "ERROR: pat=$p $php_errormsg"; unset($php_errormsg); }
+ if ($RedoMarkupLine) { $lines=array_merge((array)$x,$lines); continue 2; }
+ }
+ if ($x>'') $out .= "$x\n";
+ }
+ foreach((array)(@$MarkupFrame[0]['posteval']) as $v) eval($v);
+ array_shift($MarkupFrame);
+ StopWatch('MarkupToHTML end');
+ return $out;
+}
+
+function HandleBrowse($pagename, $auth = 'read') {
+ # handle display of a page
+ global $DefaultPageTextFmt, $PageNotFoundHeaderFmt, $HTTPHeaders,
+ $EnableHTMLCache, $NoHTMLCache, $PageCacheFile, $LastModTime, $IsHTMLCached,
+ $FmtV, $HandleBrowseFmt, $PageStartFmt, $PageEndFmt, $PageRedirectFmt;
+ $page = RetrieveAuthPage($pagename, $auth, true, READPAGE_CURRENT);
+ if (!$page) Abort("?cannot read $pagename");
+ PCache($pagename,$page);
+ if (PageExists($pagename)) $text = @$page['text'];
+ else {
+ SDV($DefaultPageTextFmt,'(:include $[{$SiteGroup}.PageNotFound]:)');
+ $text = FmtPageName($DefaultPageTextFmt, $pagename);
+ SDV($PageNotFoundHeaderFmt, 'HTTP/1.1 404 Not Found');
+ SDV($HTTPHeaders['status'], $PageNotFoundHeaderFmt);
+ }
+ $opt = array();
+ SDV($PageRedirectFmt,"<p><i>($[redirected from] <a rel='nofollow'
+ href='{\$PageUrl}?action=edit'>{\$FullName}</a>)</i></p>\n");
+ if (@!$_GET['from']) { $opt['redirect'] = 1; $PageRedirectFmt = ''; }
+ else {
+ $frompage = MakePageName($pagename, $_GET['from']);
+ $PageRedirectFmt = (!$frompage) ? ''
+ : FmtPageName($PageRedirectFmt, $frompage);
+ }
+ if (@$EnableHTMLCache && !$NoHTMLCache && $PageCacheFile &&
+ @filemtime($PageCacheFile) > $LastModTime) {
+ list($ctext) = unserialize(file_get_contents($PageCacheFile));
+ $FmtV['$PageText'] = "<!--cached-->$ctext";
+ $IsHTMLCached = 1;
+ StopWatch("HandleBrowse: using cached copy");
+ } else {
+ $IsHTMLCached = 0;
+ $text = '(:groupheader:)'.@$text.'(:groupfooter:)';
+ $t1 = time();
+ $FmtV['$PageText'] = MarkupToHTML($pagename, $text, $opt);
+ if (@$EnableHTMLCache > 0 && !$NoHTMLCache && $PageCacheFile
+ && (time() - $t1 + 1) >= $EnableHTMLCache) {
+ $fp = @fopen("$PageCacheFile,new", "x");
+ if ($fp) {
+ StopWatch("HandleBrowse: caching page");
+ fwrite($fp, serialize(array($FmtV['$PageText']))); fclose($fp);
+ rename("$PageCacheFile,new", $PageCacheFile);
+ }
+ }
+ }
+ SDV($HandleBrowseFmt,array(&$PageStartFmt, &$PageRedirectFmt, '$PageText',
+ &$PageEndFmt));
+ PrintFmt($pagename,$HandleBrowseFmt);
+}
+
+
+## UpdatePage goes through all of the steps needed to update a page,
+## preserving page history, computing link targets, page titles,
+## and other page attributes. It does this by calling each entry
+## in $EditFunctions. $pagename is the name of the page to be updated,
+## $page is the old version of the page (used for page history),
+## $new is the new version of the page to be saved, and $fnlist is
+## an optional list of functions to use instead of $EditFunctions.
+function UpdatePage(&$pagename, &$page, &$new, $fnlist = NULL) {
+ global $EditFunctions, $IsPagePosted;
+ StopWatch("UpdatePage: begin $pagename");
+ if (is_null($fnlist)) $fnlist = $EditFunctions;
+ $IsPagePosted = false;
+ foreach((array)$fnlist as $fn) {
+ StopWatch("UpdatePage: $fn ($pagename)");
+ $fn($pagename, $page, $new);
+ }
+ StopWatch("UpdatePage: end $pagename");
+ return $IsPagePosted;
+}
+
+
+# EditTemplate allows a site administrator to pre-populate new pages
+# with the contents of another page.
+function EditTemplate($pagename, &$page, &$new) {
+ global $EditTemplatesFmt;
+ if (@$new['text'] > '') return;
+ if (@$_REQUEST['template'] && PageExists($_REQUEST['template'])) {
+ $p = RetrieveAuthPage($_REQUEST['template'], 'read', false,
+ READPAGE_CURRENT);
+ if ($p['text'] > '') $new['text'] = $p['text'];
+ return;
+ }
+ foreach((array)$EditTemplatesFmt as $t) {
+ $p = RetrieveAuthPage(FmtPageName($t,$pagename), 'read', false,
+ READPAGE_CURRENT);
+ if (@$p['text'] > '') { $new['text'] = $p['text']; return; }
+ }
+}
+
+# RestorePage handles returning to the version of text as of
+# the version given by $restore or $_REQUEST['restore'].
+function RestorePage($pagename,&$page,&$new,$restore=NULL) {
+ if (is_null($restore)) $restore=@$_REQUEST['restore'];
+ if (!$restore) return;
+ $t = $page['text'];
+ $nl = (substr($t,-1)=="\n");
+ $t = explode("\n",$t);
+ if ($nl) array_pop($t);
+ krsort($page); reset($page);
+ foreach($page as $k=>$v) {
+ if ($k<$restore) break;
+ if (strncmp($k, 'diff:', 5) != 0) continue;
+ foreach(explode("\n",$v) as $x) {
+ if (preg_match('/^(\\d+)(,(\\d+))?([adc])(\\d+)/',$x,$match)) {
+ $a1 = $a2 = $match[1];
+ if ($match[3]) $a2=$match[3];
+ $b1 = $match[5];
+ if ($match[4]=='d') array_splice($t,$b1,$a2-$a1+1);
+ if ($match[4]=='c') array_splice($t,$b1-1,$a2-$a1+1);
+ continue;
+ }
+ if (strncmp($x,'< ',2) == 0) { $nlflag=true; continue; }
+ if (preg_match('/^> (.*)$/',$x,$match)) {
+ $nlflag=false;
+ array_splice($t,$b1-1,0,$match[1]); $b1++;
+ }
+ if ($x=='\\ No newline at end of file') $nl=$nlflag;
+ }
+ }
+ if ($nl) $t[]='';
+ $new['text']=implode("\n",$t);
+ $new['=preview'] = $new['text'];
+ PCache($pagename, $new);
+ return $new['text'];
+}
+
+## ReplaceOnSave performs text replacements on the text being posted.
+## Patterns held in $ROEPatterns are replaced on every edit request,
+## patterns held in $ROSPatterns are replaced only when the page
+## is being posted (as signaled by $EnablePost).
+function ReplaceOnSave($pagename,&$page,&$new) {
+ global $EnablePost, $ROSPatterns, $ROEPatterns;
+ $new['text'] = ProcessROESPatterns(@$new['text'], $ROEPatterns);
+ if ($EnablePost) {
+ $new['text'] = ProcessROESPatterns($new['text'], $ROSPatterns);
+ }
+ $new['=preview'] = $new['text'];
+ PCache($pagename, $new);
+}
+function ProcessROESPatterns($text, $patterns) {
+ global $EnableROSEscape;
+ if (IsEnabled($EnableROSEscape, 0)) $text = MarkupEscape($text);
+ $text = PPRA((array)@$patterns, $text);
+ if (IsEnabled($EnableROSEscape, 0)) $text = MarkupRestore($text);
+ return $text;
+}
+
+function SaveAttributes($pagename,&$page,&$new) {
+ global $EnablePost, $LinkTargets, $SaveAttrPatterns, $PCache,
+ $SaveProperties;
+ if (!$EnablePost) return;
+ $text = PPRA($SaveAttrPatterns, $new['text']);
+ $LinkTargets = array();
+ $new['=html'] = MarkupToHTML($pagename,$text);
+ $new['targets'] = implode(',',array_keys((array)$LinkTargets));
+ $p = & $PCache[$pagename];
+ foreach((array)$SaveProperties as $k) {
+ if (@$p["=p_$k"]) $new[$k] = $p["=p_$k"];
+ else unset($new[$k]);
+ }
+ unset($new['excerpt']);
+}
+
+function PostPage($pagename, &$page, &$new) {
+ global $DiffKeepDays, $DiffFunction, $DeleteKeyPattern, $EnablePost,
+ $Now, $Charset, $Author, $WikiDir, $IsPagePosted, $DiffKeepNum;
+ SDV($DiffKeepDays,3650);
+ SDV($DiffKeepNum,20);
+ SDV($DeleteKeyPattern,"^\\s*delete\\s*$");
+ $IsPagePosted = false;
+ if ($EnablePost) {
+ $new['charset'] = $Charset; # kept for now, may be needed if custom PageStore
+ $new['author'] = @$Author;
+ $new["author:$Now"] = @$Author;
+ $new["host:$Now"] = $_SERVER['REMOTE_ADDR'];
+ $diffclass = preg_replace('/\\W/','',@$_POST['diffclass']);
+ if ($page['time']>0 && function_exists(@$DiffFunction))
+ $new["diff:$Now:{$page['time']}:$diffclass"] =
+ $DiffFunction($new['text'],@$page['text']);
+ $keepgmt = $Now-$DiffKeepDays * 86400;
+ $keepnum = array();
+ $keys = array_keys($new);
+ foreach($keys as $k)
+ if (preg_match("/^\\w+:(\\d+)/",$k,$match)) {
+ $keepnum[$match[1]] = 1;
+ if (count($keepnum)>$DiffKeepNum && $match[1]<$keepgmt)
+ unset($new[$k]);
+ }
+ if (preg_match("/$DeleteKeyPattern/",$new['text'])){
+ if (@$new['passwdattr']>'' && !CondAuth($pagename, 'attr'))
+ Abort('$[The page has an "attr" attribute and cannot be deleted.]');
+ else $WikiDir->delete($pagename);
+ }
+ else WritePage($pagename,$new);
+ $IsPagePosted = true;
+ }
+}
+
+function PostRecentChanges($pagename,$page,$new,$Fmt=null) {
+ global $IsPagePosted, $RecentChangesFmt, $RCDelimPattern, $RCLinesMax,
+ $EnableRCDiffBytes;
+ if (!$IsPagePosted && $Fmt==null) return;
+ if ($Fmt==null) $Fmt = $RecentChangesFmt;
+ foreach($Fmt as $rcfmt=>$pgfmt) {
+ $rcname = FmtPageName($rcfmt,$pagename); if (!$rcname) continue;
+ $pgtext = FmtPageName($pgfmt,$pagename); if (!$pgtext) continue;
+ if (@$seen[$rcname]++) continue;
+
+ if (IsEnabled($EnableRCDiffBytes, 0)) {
+ $pgtext = PPRA(array(
+ '/\\(([+-])(\\d+)\\)(\\s*=\\]\\s*)$/'=>'$3%diffmarkup%{$1($1$2)$1}%%',
+ '/\\(\\+(0\\)\\+\\}%%)$/'=>'(±$1'), $pgtext);
+ }
+ $rcpage = ReadPage($rcname);
+ $rcelim = preg_quote(preg_replace("/$RCDelimPattern.*$/",' ',$pgtext),'/');
+ $rcpage['text'] = preg_replace("/^.*$rcelim.*\n/m", '', @$rcpage['text']);
+ if (!preg_match("/$RCDelimPattern/",$rcpage['text']))
+ $rcpage['text'] .= "$pgtext\n";
+ else
+ $rcpage['text'] = preg_replace("/([^\n]*$RCDelimPattern.*\n)/",
+ str_replace("$", "\\$", $pgtext) . "\n$1", $rcpage['text'], 1);
+ if (@$RCLinesMax > 0)
+ $rcpage['text'] = implode("\n", array_slice(
+ explode("\n", $rcpage['text'], $RCLinesMax + 1), 0, $RCLinesMax));
+ WritePage($rcname, $rcpage);
+ }
+}
+
+function AutoCreateTargets($pagename, &$page, &$new) {
+ global $IsPagePosted, $AutoCreate, $LinkTargets;
+ if (!$IsPagePosted) return;
+ foreach((array)@$AutoCreate as $pat => $init) {
+ if (is_null($init)) continue;
+ foreach(preg_grep($pat, array_keys((array)@$LinkTargets)) as $aname) {
+ if (PageExists($aname)) continue;
+ $x = RetrieveAuthPage($aname, 'edit', false, READPAGE_CURRENT);
+ if (!$x) continue;
+ WritePage($aname, $init);
+ }
+ }
+}
+
+function PreviewPage($pagename,&$page,&$new) {
+ global $IsPageSaved, $FmtV, $ROSPatterns;
+ if (@$_REQUEST['preview']) {
+ $text = ProcessROESPatterns($new['text'], $ROSPatterns);
+ $text = '(:groupheader:)'.$text.'(:groupfooter:)';
+ $FmtV['$PreviewText'] = MarkupToHTML($pagename,$text);
+ }
+}
+
+function HandleEdit($pagename, $auth = 'edit') {
+ global $IsPagePosted, $EditFields, $ChangeSummary, $EditFunctions,
+ $EnablePost, $FmtV, $Now, $EditRedirectFmt, $EnableRCDiffBytes,
+ $PageEditForm, $HandleEditFmt, $PageStartFmt, $PageEditFmt, $PageEndFmt;
+ SDV($EditRedirectFmt, '$FullName');
+ if (@$_POST['cancel'])
+ { Redirect(FmtPageName($EditRedirectFmt, $pagename)); return; }
+ Lock(2);
+ $page = RetrieveAuthPage($pagename, $auth, true);
+ if (!$page) Abort("?cannot edit $pagename");
+ $new = $page;
+ foreach((array)$EditFields as $k)
+ if (isset($_POST[$k])) $new[$k]=str_replace("\r",'',stripmagic($_POST[$k]));
+
+ if (IsEnabled($EnableRCDiffBytes, 0) && isset($new['text'])) {
+ $bytes = strlen($new['text']) - strlen(@$page['text']);
+ if ($bytes>=0) $bytes = "+$bytes";
+ $ChangeSummary = rtrim($ChangeSummary) . " ($bytes)";
+ }
+ $new['csum'] = $ChangeSummary;
+ if ($ChangeSummary) $new["csum:$Now"] = $ChangeSummary;
+ $EnablePost &= preg_grep('/^post/', array_keys(@$_POST));
+ $new['=preview'] = @$new['text'];
+ PCache($pagename, $new);
+ UpdatePage($pagename, $page, $new);
+ Lock(0);
+ if ($IsPagePosted && !@$_POST['postedit'])
+ { Redirect(FmtPageName($EditRedirectFmt, $pagename)); return; }
+ $FmtV['$DiffClassMinor'] =
+ (@$_POST['diffclass']=='minor') ? "checked='checked'" : '';
+ $FmtV['$EditText'] =
+ str_replace('$','$',PHSC(@$new['text'],ENT_NOQUOTES));
+ $FmtV['$EditBaseTime'] = $Now;
+ if (@$PageEditForm) {
+ $efpage = FmtPageName($PageEditForm, $pagename);
+ $form = RetrieveAuthPage($efpage, 'read', false, READPAGE_CURRENT);
+ if (!$form || !@$form['text'])
+ Abort("?unable to retrieve edit form $efpage", 'editform');
+ $FmtV['$EditForm'] = MarkupToHTML($pagename, $form['text']);
+ }
+ SDV($PageEditFmt, "<div id='wikiedit'>
+ <h2 class='wikiaction'>$[Editing {\$FullName}]</h2>
+ <form method='post' rel='nofollow' action='\$PageUrl?action=edit'>
+ <input type='hidden' name='action' value='edit' />
+ <input type='hidden' name='n' value='\$FullName' />
+ <input type='hidden' name='basetime' value='\$EditBaseTime' />
+ \$EditMessageFmt
+ <textarea id='text' name='text' rows='25' cols='60'
+ onkeydown='if (event.keyCode==27) event.returnValue=false;'
+ >\$EditText</textarea><br />
+ <input type='submit' name='post' value=' $[Save] ' />");
+ SDV($HandleEditFmt, array(&$PageStartFmt, &$PageEditFmt, &$PageEndFmt));
+ PrintFmt($pagename, $HandleEditFmt);
+}
+
+function HandleSource($pagename, $auth = 'read') {
+ global $HTTPHeaders;
+ $page = RetrieveAuthPage($pagename, $auth, true, READPAGE_CURRENT);
+ if (!$page) Abort("?cannot source $pagename");
+ foreach ($HTTPHeaders as $h) {
+ $h = preg_replace('!^Content-type:\\s+text/html!i',
+ 'Content-type: text/plain', $h);
+ header($h);
+ }
+ echo @$page['text'];
+}
+
+## PmWikiAuth provides password-protection of pages using PHP sessions.
+## It is normally called from RetrieveAuthPage. Since RetrieveAuthPage
+## can be called a lot within a single page execution (i.e., for every
+## page accessed), we cache the results of site passwords and
+## GroupAttribute pages to be able to speed up subsequent calls.
+function PmWikiAuth($pagename, $level, $authprompt=true, $since=0) {
+ global $DefaultPasswords, $GroupAttributesFmt, $AllowPassword,
+ $AuthCascade, $FmtV, $AuthPromptFmt, $PageStartFmt, $PageEndFmt,
+ $AuthId, $AuthList, $NoHTMLCache;
+ static $acache;
+ SDV($GroupAttributesFmt,'$Group/GroupAttributes');
+ SDV($AllowPassword,'nopass');
+ $page = ReadPage($pagename, $since);
+ if (!$page) { return false; }
+ if (!isset($acache))
+ SessionAuth($pagename, (@$_POST['authpw'])
+ ? array('authpw' => array($_POST['authpw'] => 1))
+ : '');
+ if (@$AuthId) {
+ $AuthList["id:$AuthId"] = 1;
+ $AuthList["id:-$AuthId"] = -1;
+ $AuthList["id:*"] = 1;
+ }
+ ## To allow @_site_edit in GroupAttributes, we cache it first
+ if (!isset($acache['@site'])) {
+ foreach($DefaultPasswords as $k => $v) {
+ $x = array(2, array(), '');
+ $acache['@site'][$k] = IsAuthorized($v, 'site', $x);
+ $AuthList["@_site_$k"] = $acache['@site'][$k][0] ? 1 : 0;
+ }
+ }
+ $gn = FmtPageName($GroupAttributesFmt, $pagename);
+ if (!isset($acache[$gn])) {
+ $gp = ReadPage($gn, READPAGE_CURRENT);
+ foreach($DefaultPasswords as $k => $v) {
+ $acache[$gn][$k] = IsAuthorized(@$gp["passwd$k"], 'group',
+ $acache['@site'][$k]);
+ }
+ }
+ foreach($DefaultPasswords as $k => $v)
+ list($page['=auth'][$k], $page['=passwd'][$k], $page['=pwsource'][$k]) =
+ IsAuthorized(@$page["passwd$k"], 'page', $acache[$gn][$k]);
+ foreach($AuthCascade as $k => $t) {
+ if ($page['=auth'][$k]+0 == 2) {
+ $page['=auth'][$k] = $page['=auth'][$t];
+ if ($page['=passwd'][$k] = $page['=passwd'][$t]) # assign
+ $page['=pwsource'][$k] = "cascade:$t";
+ }
+ }
+ if (@$page['=auth']['admin'])
+ foreach($page['=auth'] as $lv=>$a) @$page['=auth'][$lv] = 3;
+ if (@$page['=passwd']['read']) $NoHTMLCache |= 2;
+ if ($level=='ALWAYS' || @$page['=auth'][$level]) return $page;
+ if (!$authprompt) return false;
+ $GLOBALS['AuthNeeded'] = (@$_POST['authpw'])
+ ? $page['=pwsource'][$level] . ' ' . $level : '';
+ PCache($pagename, $page);
+ $postvars = '';
+ foreach($_POST as $k=>$v) {
+ if ($k == 'authpw' || $k == 'authid') continue;
+ $k = PHSC(stripmagic($k), ENT_QUOTES);
+ if (is_array($v)) {
+ foreach($v as $vk=>$vv) {
+ $vk = PHSC(stripmagic($vk), ENT_QUOTES);
+ $vv = str_replace('$', '$',
+ PHSC(stripmagic($vv), ENT_COMPAT));
+ $postvars .= "<input type='hidden' name='{$k}[{$vk}]' value=\"$vv\" />\n";
+ }
+ }
+ else {
+ $v = str_replace('$', '$',
+ PHSC(stripmagic($v), ENT_COMPAT));
+ $postvars .= "<input type='hidden' name='$k' value=\"$v\" />\n";
+ }
+ }
+ $FmtV['$PostVars'] = $postvars;
+ $r = str_replace("'", '%37', stripmagic($_SERVER['REQUEST_URI']));
+ SDV($AuthPromptFmt,array(&$PageStartFmt,
+ "<p><b>$[Password required]</b></p>
+ <form name='authform' action='$r' method='post'>
+ $[Password]: <input tabindex='1' type='password' name='authpw'
+ value='' />
+ <input type='submit' value='$[OK]' />\$PostVars</form>
+ <script language='javascript' type='text/javascript'><!--
+ document.authform.authpw.focus() //--></script>", &$PageEndFmt));
+ PrintFmt($pagename,$AuthPromptFmt);
+ exit;
+}
+
+function IsAuthorized($chal, $source, &$from) {
+ global $AuthList, $AuthPw, $AllowPassword;
+ if (!$chal) return $from;
+ $auth = 0;
+ $passwd = array();
+ foreach((array)$chal as $c) {
+ $x = '';
+ $pwchal = preg_split('/([, ]|\\w+:)/', $c, -1, PREG_SPLIT_DELIM_CAPTURE);
+ foreach($pwchal as $pw) {
+ if ($pw == ',' || $pw == '') continue;
+ else if ($pw == ' ') { $x = ''; continue; }
+ else if (substr($pw, -1, 1) == ':') { $x = $pw; continue; }
+ else if ($pw[0] != '@' && $x > '') $pw = $x . $pw;
+ if (!$pw) continue;
+ $passwd[] = $pw;
+ if ($auth < 0) continue;
+ if ($x || $pw[0] == '@') {
+ if (@$AuthList[$pw]) $auth = $AuthList[$pw];
+ continue;
+ }
+ if ($AllowPassword && pmcrypt($AllowPassword, $pw) == $pw) # nopass
+ { $auth=1; continue; }
+ foreach((array)$AuthPw as $pwresp) # password
+ if (pmcrypt($pwresp, $pw) == $pw) { $auth=1; continue; }
+ }
+ }
+ if (!$passwd) return $from;
+ if ($auth < 0) $auth = 0;
+ return array($auth, $passwd, $source);
+}
+
+
+## SessionAuth works with PmWikiAuth to manage authorizations
+## as stored in sessions. First, it can be used to set session
+## variables by calling it with an $auth argument. It then
+## uses the authid, authpw, and authlist session variables
+## to set the corresponding values of $AuthId, $AuthPw, and $AuthList
+## as needed.
+function SessionAuth($pagename, $auth = NULL) {
+ global $AuthId, $AuthList, $AuthPw, $SessionEncode, $SessionDecode,
+ $EnableSessionPasswords;
+ static $called;
+
+ @$called++;
+ $sn = session_name(); # in PHP5.3, $_REQUEST doesn't contain $_COOKIE
+ if (!$auth && ($called > 1 || (!@$_REQUEST[$sn] && !@$_COOKIE[$sn]))) return;
+
+ $sid = session_id();
+ @session_start();
+ foreach((array)$auth as $k => $v) {
+ if ($k == 'authpw') {
+ foreach((array)$v as $pw => $pv) {
+ if ($SessionEncode) $pw = $SessionEncode($pw);
+ $_SESSION[$k][$pw] = $pv;
+ }
+ }
+ else if ($k) $_SESSION[$k] = (array)$v + (array)@$_SESSION[$k];
+ }
+
+ if (!isset($AuthId)) $AuthId = @end($_SESSION['authid']);
+ $AuthPw = array_map($SessionDecode, array_keys((array)@$_SESSION['authpw']));
+ if (!IsEnabled($EnableSessionPasswords, 1)) $_SESSION['authpw'] = array();
+ $AuthList = array_merge($AuthList, (array)@$_SESSION['authlist']);
+
+ if (!$sid) @session_write_close();
+}
+
+
+function PasswdVar($pagename, $level) {
+ global $PCache, $PasswdVarAuth, $FmtV;
+ $page = $PCache[$pagename];
+ if (!isset($page['=passwd'][$level])) {
+ $page = RetrieveAuthPage($pagename, 'ALWAYS', false, READPAGE_CURRENT);
+ if ($page) PCache($pagename, $page);
+ }
+ SDV($PasswdVarAuth, 'attr');
+ if ($PasswdVarAuth && !@$page['=auth'][$PasswdVarAuth]) return XL('(protected)');
+ $pwsource = $page['=pwsource'][$level];
+ if (strncmp($pwsource, 'cascade:', 8) == 0) {
+ $FmtV['$PWCascade'] = substr($pwsource, 8);
+ return FmtPageName('$[(using $PWCascade password)]', $pagename);
+ }
+ $setting = PHSC(implode(' ', preg_replace('/^(?!@|\\w+:).+$/', '****',
+ (array)$page['=passwd'][$level])));
+ if ($pwsource == 'group' || $pwsource == 'site') {
+ $FmtV['$PWSource'] = $pwsource;
+ $setting = FmtPageName('$[(set by $PWSource)] ', $pagename)
+ . PHSC($setting);
+ }
+ return $setting;
+}
+
+
+function PrintAttrForm($pagename) {
+ global $PageAttributes, $PCache, $FmtV;
+ echo FmtPageName("<form action='\$PageUrl' method='post'>
+ <input type='hidden' name='action' value='postattr' />
+ <input type='hidden' name='n' value='\$FullName' />
+ <table>",$pagename);
+ $page = $PCache[$pagename];
+ foreach($PageAttributes as $attr=>$p) {
+ if (!$p) continue;
+ if (strncmp($attr, 'passwd', 6) == 0) {
+ $setting = PageVar($pagename, '$Passwd'.ucfirst(substr($attr, 6)));
+ $value = '';
+ } else { $setting = $value = PHSC(@$page[$attr]); }
+ $prompt = FmtPageName($p,$pagename);
+ echo "<tr><td>$prompt</td>
+ <td><input type='text' name='$attr' value='$value' /></td>
+ <td>$setting</td></tr>";
+ }
+ echo FmtPageName("</table><input type='submit' value='$[Save]' /></form>",
+ $pagename);
+}
+
+function HandleAttr($pagename, $auth = 'attr') {
+ global $HandleAttrFmt,$PageAttrFmt,$PageStartFmt,$PageEndFmt;
+ $page = RetrieveAuthPage($pagename, $auth, true, READPAGE_CURRENT);
+ if (!$page) { Abort("?unable to read $pagename"); }
+ PCache($pagename,$page);
+ XLSDV('en', array('EnterAttributes' =>
+ "Enter new attributes for this page below. Leaving a field blank
+ will leave the attribute unchanged. To clear an attribute, enter
+ 'clear'."));
+ SDV($PageAttrFmt,"<div class='wikiattr'>
+ <h2 class='wikiaction'>$[{\$FullName} Attributes]</h2>
+ <p>$[EnterAttributes]</p></div>");
+ SDV($HandleAttrFmt,array(&$PageStartFmt,&$PageAttrFmt,
+ 'function:PrintAttrForm',&$PageEndFmt));
+ PrintFmt($pagename,$HandleAttrFmt);
+}
+
+function HandlePostAttr($pagename, $auth = 'attr') {
+ global $PageAttributes, $EnablePostAttrClearSession;
+ Lock(2);
+ $page = RetrieveAuthPage($pagename, $auth, true);
+ if (!$page) { Abort("?unable to read $pagename"); }
+ foreach($PageAttributes as $attr=>$p) {
+ $v = stripmagic(@$_POST[$attr]);
+ if ($v == '') continue;
+ if ($v=='clear') unset($page[$attr]);
+ else if (strncmp($attr, 'passwd', 6) != 0) $page[$attr] = $v;
+ else {
+ $a = array();
+ preg_match_all('/"[^"]*"|\'[^\']*\'|\\S+/', $v, $match);
+ foreach($match[0] as $pw)
+ $a[] = preg_match('/^(@|\\w+:)/', $pw) ? $pw
+ : pmcrypt(preg_replace('/^([\'"])(.*)\\1$/', '$2', $pw));
+ if ($a) $page[$attr] = implode(' ',$a);
+ }
+ }
+ WritePage($pagename,$page);
+ Lock(0);
+ if (IsEnabled($EnablePostAttrClearSession, 1)) {
+ @session_start();
+ unset($_SESSION['authid']);
+ unset($_SESSION['authlist']);
+ $_SESSION['authpw'] = array();
+ }
+ Redirect($pagename);
+ exit;
+}
+
+
+function HandleLogoutA($pagename, $auth = 'read') {
+ global $LogoutRedirectFmt, $LogoutCookies;
+ SDV($LogoutRedirectFmt, '$FullName');
+ SDV($LogoutCookies, array());
+ @session_start();
+ $_SESSION = array();
+ if ( session_id() != '' || isset($_COOKIE[session_name()]) )
+ pmsetcookie(session_name(), '', time()-43200, '/');
+ foreach ($LogoutCookies as $c)
+ if (isset($_COOKIE[$c])) pmsetcookie($c, '', time()-43200, '/');
+ session_destroy();
+ Redirect(FmtPageName($LogoutRedirectFmt, $pagename));
+}
+
+
+function HandleLoginA($pagename, $auth = 'login') {
+ global $AuthId, $DefaultPasswords;
+ unset($DefaultPasswords['admin']);
+ $prompt = @(!$_POST['authpw'] || ($AuthId != $_POST['authid']));
+ $page = RetrieveAuthPage($pagename, $auth, $prompt, READPAGE_CURRENT);
+ Redirect($pagename);
+}
+
--- /dev/null
+++ pub/guiedit/README
@@ -0,0 +1,6 @@
+The images in this directory are part of the GUIEdit module
+for PmWiki, Copyright 2005-2006 Patrick R. Michaud (pmichaud@pobox.com)
+These images are part of PmWiki; you can redistribute it and/or modify
+them under the terms of the GNU General Public License as published
+by the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version. See pmwiki.php for full details.
Binary files /dev/null and pub/guiedit/attach.gif differ
Binary files /dev/null and pub/guiedit/big.gif differ
Binary files /dev/null and pub/guiedit/blank.gif differ
Binary files /dev/null and pub/guiedit/center.gif differ
Binary files /dev/null and pub/guiedit/em.gif differ
Binary files /dev/null and pub/guiedit/extlink.gif differ
Binary files /dev/null and pub/guiedit/fixurl.png differ
--- /dev/null
+++ pub/guiedit/guiedit.js
@@ -0,0 +1,181 @@
+/* Copyright 2004-2019 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This file provides Javascript functions to support WYSIWYG-style
+ editing. The concepts are borrowed from the editor used in Wikipedia,
+ but the code has been rewritten from scratch to integrate better with
+ PHP and PmWiki's codebase.
+
+ Script maintained by Petko Yotov www.pmwiki.org/petko
+*/
+
+function insButton(mopen, mclose, mtext, mlabel, mkey) {
+ if (mkey > '') { mkey = 'accesskey="' + mkey + '" ' }
+ document.write("<a tabindex='-1' " + mkey + "onclick=\"insMarkup('"
+ + mopen + "','"
+ + mclose + "','"
+ + mtext + "');\">"
+ + mlabel + "</a>");
+}
+
+function insMarkup() {
+ var func = false, tid='text', mopen = '', mclose = '', mtext = '';
+ if (arguments[0] == 'FixSelectedURL') {
+ func = FixSelectedURL;
+ }
+ else if (typeof arguments[0] == 'function') {
+ var func = arguments[0];
+ if(arguments.length > 1) tid = arguments[1];
+ mtext = func('');
+ }
+ else if (arguments.length >= 3) {
+ var mopen = arguments[0], mclose = arguments[1], mtext = arguments[2];
+ if(arguments.length > 3) tid = arguments[3];
+ }
+
+ var tarea = document.getElementById(tid);
+ if (tarea.setSelectionRange > '') {
+ var p0 = tarea.selectionStart;
+ var p1 = tarea.selectionEnd;
+ var top = tarea.scrollTop;
+ var str = mtext;
+ var cur0 = p0 + mopen.length;
+ var cur1 = p0 + mopen.length + str.length;
+ while (p1 > p0 && tarea.value.substring(p1-1, p1) == ' ') p1--;
+ if (p1 > p0) {
+ str = tarea.value.substring(p0, p1);
+ if(func) str = func(str);
+ cur0 = p0 + mopen.length + str.length + mclose.length;
+ cur1 = cur0;
+ }
+ tarea.value = tarea.value.substring(0,p0)
+ + mopen + str + mclose
+ + tarea.value.substring(p1);
+ tarea.focus();
+ tarea.selectionStart = cur0;
+ tarea.selectionEnd = cur1;
+ tarea.scrollTop = top;
+ } else if (document.selection) {
+ var str = document.selection.createRange().text;
+ tarea.focus();
+ range = document.selection.createRange();
+ if (str == '') {
+ range.text = mopen + mtext + mclose;
+ range.moveStart('character', -mclose.length - mtext.length );
+ range.moveEnd('character', -mclose.length );
+ } else {
+ if (str.charAt(str.length - 1) == " ") {
+ mclose = mclose + " ";
+ str = str.substr(0, str.length - 1);
+ if(func) str = func(str);
+ }
+ range.text = mopen + str + mclose;
+ }
+ range.select();
+ } else { tarea.value += mopen + mtext + mclose; }
+ return;
+}
+
+// Helper functions below by Petko Yotov, www.pmwiki.org/petko
+function aE(el, ev, fn) {
+ if(typeof el == 'string') el = dqsa(el);
+ for(var i=0; i<el.length; i++) el[i].addEventListener(ev, fn);
+}
+function dqs(str) { return document.querySelector(str); }
+function dqsa(str) { return document.querySelectorAll(str); }
+function tap(q, fn) { aE(q, 'click', fn); };
+function adata(el, x) { return el.getAttribute("data-"+x); }
+function FixSelectedURL(str) {
+ var rx = new RegExp("[ <>\"{}|\\\\^`()\\[\\]']", 'g');
+ str = str.replace(rx, function(a){
+ return '%'+a.charCodeAt(0).toString(16); });
+ return str;
+}
+
+window.addEventListener('DOMContentLoaded', function(){
+ var NsForm = false;
+
+ var sTop = dqs("#textScrollTop");
+ var tarea = dqs('#text');
+ if(sTop && tarea) {
+ if(sTop.value) tarea.scrollTop = sTop.value;
+ sTop.form.addEventListener('submit', function(){
+ sTop.value = tarea.scrollTop;
+ });
+ }
+
+ var ensw = dqs('#EnableNotSavedWarning');
+ if(ensw) {
+ var NsMessage = ensw.value;
+ NsForm = ensw.form;
+ if(NsForm) {
+ NsForm.addEventListener('submit', function(e){
+ NsMessage="";
+ });
+ window.onbeforeunload = function(ev) {
+ if(NsMessage=="") {return;}
+ if (typeof ev == "undefined") {ev = window.event;}
+ if (tarea && tarea.codemirror) {tarea.codemirror.save();}
+
+ var tx = NsForm.querySelectorAll('textarea, input[type="text"]');
+ for(var i=0; i<tx.length; i++) {
+ var el = tx[i];
+ if(ensw.className.match(/\bpreview\b/) || el.value != el.defaultValue) {
+ if (ev) {ev.returnValue = NsMessage;}
+ return NsMessage;
+ }
+ }
+ }
+ }
+ }
+ if(dqs('#EnableEditAutoText')) EditAutoText();
+});
+
+/*
+ * Edit helper for PmWiki
+ * (c) 2016 Petko Yotov www.pmwiki.org/petko
+ */
+function EditAutoText(){
+ var t = dqs('#text');
+ if(!t) return;
+
+
+ t.addEventListener('keydown', function(e){
+ if (e.keyCode != 13) return;
+ //else [Enter/Return]
+ var caret = this.selectionStart;
+ if(!caret) return true; // old MSIE, sorry
+ var content = this.value;
+ var before = content.substring(0, caret).split(/\n/g);
+ var after = content.substring(this.selectionEnd);
+ var currline = before[before.length-1];
+
+ if(currline.match(/[^\\]\\$/)) return true; // line ending with a single \ backslash
+ var insert = "\n";
+ if(e.ctrlKey && e.shiftKey) {
+ insert = "~~~~\n";
+ }
+ else if(e.ctrlKey) {
+ insert = "[[<<]]\n";
+ }
+ else if(e.shiftKey) {
+ insert = "\\\\\n";
+ }
+ else {
+ var m = currline.match(/^((?: *\*+| *\#+|-+[<>]|:+|\|\|| ) *)/);
+ if(!m) return true;
+ var insert = "\n"+m[1];
+ }
+ e.preventDefault();
+
+ content = before.join("\n") + insert + after;
+ this.value = content;
+ this.selectionStart = caret + insert.length;
+ this.selectionEnd = caret + insert.length;
+ return false;
+ });
+};
+
Binary files /dev/null and pub/guiedit/h.gif differ
Binary files /dev/null and pub/guiedit/h1.gif differ
Binary files /dev/null and pub/guiedit/h2.gif differ
Binary files /dev/null and pub/guiedit/h3.gif differ
Binary files /dev/null and pub/guiedit/hr.gif differ
Binary files /dev/null and pub/guiedit/indent.gif differ
Binary files /dev/null and pub/guiedit/left.gif differ
Binary files /dev/null and pub/guiedit/math.gif differ
Binary files /dev/null and pub/guiedit/ol.gif differ
Binary files /dev/null and pub/guiedit/outdent.gif differ
Binary files /dev/null and pub/guiedit/pagelink.gif differ
Binary files /dev/null and pub/guiedit/preview.gif differ
Binary files /dev/null and pub/guiedit/right.gif differ
Binary files /dev/null and pub/guiedit/save.gif differ
Binary files /dev/null and pub/guiedit/small.gif differ
Binary files /dev/null and pub/guiedit/spellcheck.gif differ
Binary files /dev/null and pub/guiedit/strong.gif differ
Binary files /dev/null and pub/guiedit/sub.gif differ
Binary files /dev/null and pub/guiedit/sup.gif differ
Binary files /dev/null and pub/guiedit/table.gif differ
Binary files /dev/null and pub/guiedit/ul.gif differ
Binary files /dev/null and pub/guiedit/underline.gif differ
--- /dev/null
+++ pub/pmwiki-utils.js
@@ -0,0 +1,407 @@
+/*
+ JavaScript utilities for PmWiki
+ (c) 2009-2020 Petko Yotov www.pmwiki.org/petko
+ based on PmWiki addons DeObMail, AutoTOC and Ape
+ licensed GNU GPLv2 or any more recent version.
+
+ libsortable() "Sortable tables" adapted for PmWiki from
+ a Public Domain event listener by github.com/tofsjonas
+*/
+
+(function(){
+ function aE(el, ev, fn) {
+ if(typeof el == 'string') el = dqsa(el);
+ for(var i=0; i<el.length; i++) el[i].addEventListener(ev, fn);
+ }
+ function dqs(str) { return document.querySelector(str); }
+ function dqsa(str) { return document.querySelectorAll(str); }
+ function tap(q, fn) { aE(q, 'click', fn); };
+ function adata(el, x) { return el.getAttribute("data-"+x); }
+ function sdata(el, x, val) { el.setAttribute("data-"+x, val); }
+ function pf(x) {return parseFloat(x);}
+
+ var __script__ = dqs('script[src*="pmwiki-utils.js"]');
+ var wikitext = document.getElementById('wikitext');
+
+ function PmXMail() {
+ var els = document.querySelectorAll('span._pmXmail');
+ var LinkFmt = '<a href="%u" class="mail">%t</a>';
+
+ for(var i=0; i<els.length; i++) {
+ var x = els[i].querySelector('span._t');
+ var txt = cb_mail(x.innerHTML);
+ var y = els[i].querySelector('span._m');
+ var url = cb_mail(y.innerHTML.replace(/^ *-> */, ''));
+
+ if(!url) url = 'mailto:'+txt.replace(/^mailto:/, '');
+
+ url = url.replace(/"/g, '%22').replace(/'/g, '%27');
+ var html = LinkFmt.replace(/%u/g, url).replace(/%t/g, txt);
+ els[i].innerHTML = html;
+ }
+ }
+ function cb_mail(x){
+ return x.replace( /<span class=(['"]?)_d\1>[^<]+<\/span>/ig, '.')
+ .replace( /<span class=(['"]?)_a\1>[^<]+<\/span>/ig, '@');
+ }
+
+ function is_toc_heading(el) {
+ if(el.offsetParent === null) {return false;} // hidden
+ if(el.className.match(/\bnotoc\b/)) {return false;} // %notoc%
+ var p = el.parentNode;
+ while(p && p !== wikitext) { // >>notoc<<, (:markup:)
+ if(p.className.match(/\b(notoc|markup2)\b/)) {return false;}
+ if(p.parentNode) p = p.parentNode;
+ }
+ return true;
+ }
+ function posy(el) {
+ var top = 0;
+ if (el.offsetParent) {
+ do {
+ top += el.offsetTop;
+ } while (el = el.offsetParent);
+ }
+ return top;
+ }
+
+ function any_id(h) {
+ if(h.id) {return h.id;} // %id=anchor%
+ var a = h.querySelector('a[id]'); // inline [[#anchor]]
+ if(a && a.id) {return a.id;}
+ var prev = h.previousElementSibling;
+ if(prev) { // [[#anchor]] before !!heading
+ var a = prev.querySelectorAll('a[id]');
+ if(a.length) {
+ last = a[a.length-1];
+ if(last.id && ! last.nextElementSibling) {
+ var atop = posy(last) + last.offsetHeight;
+ var htop = posy(h);
+ if( Math.abs(htop-atop)<20 ) {
+ h.appendChild(last);
+ return last.id;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ function repeat(x, times) {
+ var y = '';
+ for(var i=0; i<times; i++) y += '' + x;
+ return y;
+ }
+ function inittoggle() {
+ var tnext = adata(__script__, 'toggle');
+ if(! tnext) { return; }
+ var x = dqsa(tnext);
+ if(! x.length) return;
+ for(var i=0; i<x.length; i++) togglenext(x[i]);
+ tap(tnext, togglenext);
+ tap('.pmtoggleall', toggleall);
+ }
+ function togglenext(z) {
+ var el = z.type == 'click' ? this : z;
+ var attr = adata(el, 'pmtoggle')=='closed' ? 'open' : 'closed';
+ sdata(el, 'pmtoggle', attr);
+ }
+ function toggleall(){
+ var curr = adata(this, 'pmtoggleall');
+ if(!curr) curr = 'closed';
+ var toggles = dqsa('*[data-pmtoggle="'+curr+'"]');
+ var next = curr=='closed' ? 'open' : 'closed';
+ for(var i=0; i<toggles.length; i++) {
+ sdata(toggles[i], 'pmtoggle', next);
+ }
+ var all = dqsa('.pmtoggleall');
+ for(var i=0; i<all.length; i++) {
+ sdata(all[i], 'pmtoggleall', next);
+ }
+ }
+
+ function autotoc() {
+ if(dqs('.noPmTOC')) { return; } // (:notoc:) in page
+ var dtoc = adata(__script__, 'pmtoc');
+ try {dtoc = JSON.parse(dtoc);} catch(e) {dtoc = false;}
+ if(! dtoc) { return; } // error
+
+ if(! dtoc.Enable || !dtoc.MaxLevel) { return; } // disabled
+
+ if(dtoc.NumberedHeadings) {
+ var specs = dtoc.NumberedHeadings.toString().split(/\./g);
+ for(var i=0; i<specs.length; i++) {
+ if(specs[i].match(/^[1AI]$/i)) numheadspec[i] = specs[i];
+ }
+ }
+
+ var query = [];
+ for(var i=1; i<=dtoc.MaxLevel; i++) {
+ query.push('h'+i);
+ }
+ if(dtoc.EnableQMarkup) query.push('p.question');
+ var pageheadings = wikitext.querySelectorAll(query.join(','));
+ if(!pageheadings.length) { return; }
+
+ var toc_headings = [ ];
+ var minlevel = 1000, hcache = [ ];
+ for(var i=0; i<pageheadings.length; i++) {
+ var h = pageheadings[i];
+ if(! is_toc_heading(h)) {continue;}
+ toc_headings.push(h);
+ }
+ if(! toc_headings.length) return;
+
+ var tocdiv = dqs('.PmTOCdiv');
+ var shouldmaketoc = ( tocdiv || (toc_headings.length >= dtoc.MinNumber && dtoc.MinNumber != -1)) ? 1:0;
+ if(!dtoc.NumberedHeadings && !shouldmaketoc) return;
+
+ for(var i=0; i<toc_headings.length; i++) {
+ var h = toc_headings[i];
+ var level = pf(h.tagName.substring(1));
+ if(! level) level = 6;
+ minlevel = Math.min(minlevel, level);
+ var id = any_id(h);
+ hcache.push([h, level, id]);
+ }
+
+ prevlevel = 0;
+ var html = '';
+ var sectionedit = hcache[0][0] ? hcache[0][0].querySelector('.sectionedit') : false;
+ var selength = sectionedit? sectionedit.textContent.length : false;
+ for(var i=0; i<hcache.length; i++) {
+ var hc = hcache[i];
+ var actual_level = hc[1] - minlevel;
+// if(actual_level>prevlevel+1) actual_level = prevlevel+1;
+// prevlevel = actual_level;
+
+ var currnb = numberheadings(actual_level);
+ if(! hc[2]) {
+ hc[2] = 'toc-'+currnb.replace(/\.+$/g, '');
+ hc[0].id = hc[2];
+ }
+ if(dtoc.NumberedHeadings && currnb.length) hc[0].insertAdjacentHTML('afterbegin', currnb+' ');
+
+ if(! shouldmaketoc) { continue; }
+ var txt = hc[0].textContent.replace(/^\s+|\s+$/g, '').replace(/</g, '<');
+ if(selength) txt = txt.slice(0, -selength);
+
+ html += repeat(' ', 3*actual_level)
+ + '<a href="#'+hc[2]+'">' + txt + '</a><br>\n';
+ if(dtoc.EnableBacklinks) hc[0].insertAdjacentHTML('beforeend', ' <a class="back-arrow" href="#_toc">↑</a>');
+
+ }
+
+ if(! shouldmaketoc) return;
+
+ html = "<b>"+dtoc.contents+"</b> "
+ +"[<input type='checkbox' id='PmTOCchk'><label for='PmTOCchk'>"
+ +"<span class='pmtoc-show'>"+dtoc.show+"</span>"
+ +"<span class='pmtoc-hide'>"+dtoc.hide+"</span></label>]"
+ +"<div class='PmTOCtable'>" + html + "</div>";
+
+ if(!tocdiv) {
+ var wrap = "<div class='PmTOCdiv'></div>";
+ if(dtoc.ParentElement && dqs(dtoc.ParentElement)) {
+ dqs(dtoc.ParentElement).insertAdjacentHTML('afterbegin', wrap);
+ }
+ else {
+ hcache[0][0].insertAdjacentHTML('beforebegin', wrap);
+ }
+ tocdiv = dqs('.PmTOCdiv');
+
+ }
+ if(!tocdiv) return; // error?
+ tocdiv.className += " frame";
+ tocdiv.id = '_toc';
+
+ tocdiv.innerHTML = html;
+
+ if(window.localStorage.getItem('closeTOC')) { dqs('#PmTOCchk').checked = true; }
+ aE('#PmTOCchk', 'change', function(e){
+ window.localStorage.setItem('closeTOC', this.checked ? "close" : '');
+ });
+
+ var hh = location.hash;
+ if(hh.length>1) {
+ var cc = document.getElementById(hh.substring(1));
+ if(cc) cc.scrollIntoView();
+ }
+ }
+
+ var numhead = [0, 0, 0, 0, 0, 0, 0];
+ var numheadspec = '1 1 1 1 1 1 1'.split(/ /g);
+ function numhead_alpha(n, upper) {
+ if(!n) return '_';
+ var alpha = '', mod, start = upper=='A' ? 65 : 97;
+ while (n>0) {
+ mod = (n-1)%26;
+ alpha = String.fromCharCode(start + mod) + '' + alpha;
+ n = (n-mod)/26 | 0;
+ }
+ return alpha;
+ }
+ function numhead_roman(n, upper) {
+ if(!n) return '_';
+ // partially based on http://blog.stevenlevithan.com/?p=65#comment-16107
+ var lst = [ [1000,'M'], [900,'CM'], [500,'D'], [400,'CD'], [100,'C'], [90,'XC'],
+ [50,'L'], [40,'XL'], [10,'X'], [9,'IX'], [5,'V'], [4,'IV'], [1,'I'] ];
+ var roman = '';
+ for(var i=0; i<lst.length; i++) {
+ while(n>=lst[i][0]) {
+ roman += lst[i][1];
+ n -= lst[i][0];
+ }
+ }
+ return (upper == 'I') ? roman : roman.toLowerCase();
+ }
+
+ function numberheadings(n) {
+ if(n<numhead[6]) for(var j=numhead[6]; j>n; j--) numhead[j]=0;
+ numhead[6]=n;
+ numhead[n]++;
+ var qq = '';
+ for (var j=0; j<=n; j++) {
+ var curr = numhead[j];
+ var currspec = numheadspec[j];
+ if(currspec.match(/a/i)) { curr = numhead_alpha(curr, currspec); }
+ else if(currspec.match(/i/i)) { curr = numhead_roman(curr, currspec); }
+
+ qq+=curr+".";
+ }
+ return qq;
+ }
+
+ function makesortable() {
+ if(! pf(adata(__script__, 'sortable'))) return;
+ var tables = dqsa('table.sortable,table.sortable-footer');
+ for(var i=0; i<tables.length; i++) {
+ // non-pmwiki-core table, already ready
+ if(tables[i].querySelector('thead')) continue;
+
+ tables[i].classList.add('sortable'); // for .sortable-footer
+
+ var thead = document.createElement('thead');
+ tables[i].insertBefore(thead, tables[i].firstChild);
+
+ var rows = tables[i].querySelectorAll('tr');
+ thead.appendChild(rows[0]);
+ var tbody = tables[i].querySelector('tbody');
+ if(! tbody) {
+ tbody = tables[i].appendChild(document.createElement('tbody'));
+ for(var r=1; r<rows.length; r++) tbody.appendChild(rows[r]);
+ }
+ if(tables[i].className.match(/sortable-footer/)) {
+ var tfoot = tables[i].appendChild(document.createElement('tfoot'));
+ tfoot.appendChild(rows[rows.length-1]);
+ }
+ mkdatasort(rows);
+ }
+ libsortable();
+ }
+ function mkdatasort(rows) {
+ var hcells = rows[0].querySelectorAll('th,td');
+ var specialsort = [], span;
+ for(var i=0; i<hcells.length; i++) {
+ sortspan = hcells[i].querySelector('.sort-number,.sort-number-us,.sort-date');
+ if(sortspan) specialsort[i] = sortspan.className;
+ }
+ if(! specialsort.length) return;
+ for(var i=1; i<rows.length; i++) {
+ var cells = rows[i].querySelectorAll('td,th');
+ var k = 0;
+ for(var j=0; j<cells.length && j<specialsort.length; j++) {
+ if(! specialsort[j]) continue;
+ var t = cells[j].innerText, ds = '';
+ if(specialsort[j] == 'sort-number-us') {ds = t.replace(/[^-.\d]+/g, ''); }
+ else if(specialsort[j] == 'sort-number') {ds = t.replace(/[^-,\d]+/g, '').replace(/,/g, '.'); }
+ else if(specialsort[j] == 'sort-date') {ds = new Date(t).getTime(); }
+ if(ds) cells[j].setAttribute('data-sort', ds);
+ }
+ }
+ }
+ function libsortable(){
+ //adapted from Public Domain code by github.com/tofsjonas
+ document.addEventListener('click', function(e) {
+ var down_class = ' dir-d ';
+ var up_class = ' dir-u ';
+ var regex_dir = / dir-(u|d) /;
+ var regex_table = /\bsortable\b/;
+ var element = e.target;
+
+ function getValue(obj) {
+ obj = obj.cells[column_index];
+ return obj.getAttribute('data-sort') || obj.innerText;
+ }
+
+ function reclassify(element, dir) {
+ element.className = element.className.replace(regex_dir, '') + dir;
+ }
+ if (element.nodeName == 'TH') {
+ var table = element.offsetParent;
+ if (regex_table.test(table.className)) {
+ var column_index;
+ var tr = element.parentNode;
+ var nodes = tr.cells;
+ for (var i = 0; i < nodes.length; i++) {
+ if (nodes[i] === element) {
+ column_index = i;
+ } else {
+ reclassify(nodes[i], '');
+ }
+ }
+ var dir = down_class;
+ if (element.className.indexOf(down_class) !== -1) {
+ dir = up_class;
+ }
+ reclassify(element, dir);
+ var org_tbody = table.tBodies[0];
+ var rows = [].slice.call(org_tbody.cloneNode(true).rows, 0);
+ var reverse = (dir == up_class);
+ rows.sort(function(a, b) {
+ a = getValue(a);
+ b = getValue(b);
+ if (reverse) {
+ var c = a;
+ a = b;
+ b = c;
+ }
+ return isNaN(a - b) ? a.localeCompare(b) : a - b;
+ });
+ var clone_tbody = org_tbody.cloneNode();
+ for (i = 0; i < rows.length; i++) {
+ clone_tbody.appendChild(rows[i]);
+ }
+ table.replaceChild(clone_tbody, org_tbody);
+ }
+ }
+ });
+ }
+
+ function highlight_pre() {
+ if (! pf(adata(__script__, 'highlight'))) return;
+ if (typeof hljs == 'undefined') return;
+ var x = dqsa('.highlight,.hlt');
+
+ for(var i=0; i<x.length; i++) {
+ var pre = Array.prototype.slice.call(x[i].querySelectorAll('pre,code'));
+ var n = x[i].nextElementSibling;
+ if (n && n.tagName == 'PRE') pre.push(n);
+ for(var j=0; j<pre.length; j++) {
+ pre[j].className += ' ' + x[i].className;
+ hljs.highlightBlock(pre[j]);
+ }
+ }
+
+ }
+
+ function ready(){
+ PmXMail();
+ inittoggle();
+ autotoc();
+ makesortable();
+ highlight_pre();
+ }
+ if( document.readyState !== 'loading' ) ready();
+ else window.addEventListener('DOMContentLoaded', ready);
+})();
--- /dev/null
+++ pub/skins/ircnow/README
@@ -0,0 +1,28 @@
+This directory contains the files to display pages in PmWiki according
+to the default "responsive" layout, adaptive for large and small screens.
+
+Note that currently this layout is "beta". It requires relatively recent
+mobile and desktop browsers (post-2010).
+
+==>Don't edit these files directly, as you may lose your edits the
+next time you upgrade PmWiki!
+
+Instead, copy the files to another directory in pub/skins/ and edit
+them there. You can then configure PmWiki to use your modified layout
+files by setting the $Skin variable in your local/config.php. For
+example, if you copy your custom skin to pub/skins/custom, then you
+would set
+ $Skin = 'custom';
+in local/config.php.
+
+The files in this directory:
+ skin.tmpl -- the default template for page layouts
+ skin.css -- PmWiki's default css
+ skin.php -- some template functions
+ skin.js -- some template functions (browser)
+ *.svg -- icons
+
+Note, the default PmWiki logo is in the "pmwiki" template directory.
+If you just want to change the logo, you can do it by setting $PageLogoUrl
+to the url location of your logo.
+
--- /dev/null
+++ pub/skins/ircnow/skin.css
@@ -0,0 +1,557 @@
+/***********************************************************************
+** skin.css
+** Copyright 2016-2020 Petko Yotov www.pmwiki.org/petko
+**
+** Partially based on pmwiki.css:
+** Copyright 2004-2006 Patrick R. Michaud pmichaud@pobox.com
+** Copyright 2006 Hagan Fox
+**
+** This file is part of PmWiki; you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published
+** by the Free Software Foundation; either version 2 of the License, or
+** (at your option) any later version. See pmwiki.php for full details.
+**
+** This skin was implemented with a mobile-first approach.
+** It should work reasonably well with browsers released since 2009.
+** This CSS uses relative "Reference pixel" dimensions.
+***********************************************************************/
+
+html, body, #bodywrap {
+ padding: 0;
+ margin: 0;
+ font-family: 'Arial', 'Helvetica', Sans-serif;
+ font-size:15px;
+ line-height: 25px;
+ background-color: #f7f7f7;
+ color: black;
+ min-height: 100%;
+ position: relative;
+}
+
+#wikibody {
+ position: relative;
+ background-color: white;
+ padding: 3px;
+ margin: 0;
+}
+
+#wikileft, #wikihead-searchform, #wikicmds {
+ background-color: #fff;
+ opacity: 0.1;
+ display: none;
+ position: absolute;
+ border: 1px solid #ccc;
+ max-width: 90%;
+ max-width: 90vw;
+ height: auto;
+ overflow: auto;
+ top: 8px;
+ -webkit-box-shadow: 2px 2px 8px 0px rgba(0,0,0,0.75);
+ -moz-box-shadow: 2px 2px 8px 0px rgba(0,0,0,0.75);
+ box-shadow: 2px 2px 8px 0px rgba(0,0,0,0.75);
+}
+
+#wikihead {
+ border-bottom:1px solid #ccc;
+ padding: 0;
+ margin: 0;
+ line-height: 13px;
+ min-height: 33px;
+}
+
+#wikihead-searchform {
+ text-align: center;
+ padding: 10px;
+ z-index: 5;
+ right: 30px;
+ width: 16em;
+ max-width: 80%;
+ line-height: 167%;
+}
+#wikihead-searchquery {
+ max-width: 12em;
+}
+#wikimid {
+ margin: 0;
+ padding: 0;
+ max-width: 100%;
+}
+
+#wikileft {
+ left: 30px;
+ padding: 6px;
+ z-index: 4;
+}
+
+#wikicmds {
+ padding:0px;
+ z-index: 6;
+ white-space:nowrap;
+ right: 30px;
+}
+
+#wikitext {
+ margin-top: 12px;
+}
+
+#wikifoot {
+ border-top: 1px solid #ccc;
+ padding: 3px;
+ font-size: 13.5px;
+}
+
+#wikifoot.nosidebar {
+ padding-left:3px;
+}
+
+#wikihead-search-toggle, #wikileft-toggle, #wikicmds-toggle {
+ display: none;
+}
+
+#wikihead-search-toggle-label { background-image: url(xsearch.svg); }
+#wikileft-toggle-label { background-image: url(xmenu.svg); }
+#wikicmds-toggle-label { background-image: url(xwrench.svg); }
+
+#wikihead-search-toggle-label, #wikileft-toggle-label, #wikicmds-toggle-label {
+ position: relative;
+ display: block;
+ width: 22px;
+ height: 22px;
+ background-size: cover;
+ background-repeat: no-repeat;
+ float:right;
+ margin: 6px 6px 0 6px;
+ z-index: 3;
+}
+
+#wikileft-toggle-label {
+ float: left;
+}
+#wikicmds-toggle-label {
+ margin: 3px;
+}
+
+#wikihead-search-toggle:checked + label,
+#wikileft-toggle:checked + label,
+#wikicmds-toggle:checked ~ #wikimid #wikicmds-toggle-label {
+ background-image: url(xclose.svg);
+}
+
+#wikileft-toggle:checked ~ * #wikileft,
+#wikihead-search-toggle:checked ~ * #wikihead-searchform,
+#wikicmds-toggle:checked ~ * #wikicmds {
+ display: block;
+ opacity: 1;
+
+-webkit-animation: fadein 1s;
+ -moz-animation: fadein 1s;
+ -ms-animation: fadein 1s;
+ -o-animation: fadein 1s;
+ animation: fadein 1s;
+}
+
+#wikioverlay {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ background-color: rgba(0, 0, 0, .2);
+ z-index: 2;
+ display: none;
+}
+#wikileft-toggle:checked ~ #wikioverlay,
+#wikihead-search-toggle:checked ~ #wikioverlay,
+#wikicmds-toggle:checked ~ #wikioverlay {
+ display: block;
+}
+
+
+
+/* These are for the left-sidebar. */
+#wikileft .vspace { margin-top:20px; }
+#wikileft ul { list-style:none; padding:0px; margin:0px; }
+#wikileft li { margin:8px 0px; padding-left: 6px; }
+.sidehead {
+ margin:0px; padding:4px 2px 2px 2px;
+ font-weight:bold; font-style:normal;
+}
+.sidehead a
+ { color:#505050; font-weight:bold; font-style:normal; }
+#wikileft a { text-decoration:none; color:black; padding: 8px 0; }
+#wikileft a:hover { text-decoration:underline; color:blue; }
+
+#wikicmds ul { list-style:none; margin:0px; padding:0px; }
+#wikicmds li { padding: 8px; border-top:1px solid #ccc;}
+#wikicmds li:first-child {border-top:none;}
+#wikicmds li a { text-decoration:none; color:black; border: none; }
+#wikicmds li a.createlink { display:none; }
+#wikicmds li a:hover { text-decoration:underline; color:blue; }
+
+
+/* These primarily adjust the size and spacing of heading elements,
+** most browsers have atrocious defaults for these. */
+h1, h2, h3, h4, h5, h6 { margin-top:15px; margin-bottom:9px; }
+h1, h2, h3, h6 { font-weight:normal; }
+h4, h5 { font-weight:bold; }
+h1 code, h2 code, h3 code, h4 code { font-size:15px; }
+h1 { font-size:27px; }
+h2 { font-size:22px; }
+h3 { font-size:18px; }
+h4 { font-size:16px; }
+h5 { font-size:15px; }
+h6 { font-size:15px; }
+
+.pagegroup { line-height:30px; }
+.pagetitle { line-height:24px; margin:0px; font-size:24px; font-weight:normal; }
+.wikiaction { margin-top:4px; margin-bottom:4px; }
+
+/* These control the fixed-width text elements of the page */
+pre, code { font-size:14px; }
+pre, code, .diffmarkup { font-family:'Lucida Console','Andale Mono','Courier New',Courier,monospace; }
+pre { line-height: 18px; }
+pre code, code code, pre pre { font-size:100%; }
+pre, code.escaped { max-width: 100%; overflow: auto; }
+
+/* Large tables can scroll */
+div.scrollable { max-width: 100%; overflow: auto; border: 1px dotted red;}
+
+#wikiedit form { margin:0px; width:100%; max-width:100%; }
+#wikiedit textarea { width:99.5%; max-width:99.5%; max-height: 60vh; }
+#wikiedit input { max-width:99.5%; }
+.wikimessage { margin-top:4px; margin-bottom:4px; font-style:italic; }
+
+input, img, iframe {
+ max-width: 100%;
+}
+dd {
+ margin-left: 15px;
+}
+ul, ol {
+ margin: 0;
+ padding: 0 0 0 20px;
+}
+
+select, textarea, input {
+ font-size: 16px; /*disable zoom-in on some phones*/
+}
+
+/* xlpage-utf-8.php */
+.rtl, .rtl * {direction:rtl; unicode-bidi:bidi-override;}
+.ltr, .ltr * {direction:ltr; unicode-bidi:bidi-override;}
+.rtl .indent, .rtl.indent, .rtl .outdent, .rtl.outdent {
+ margin-left:0; margin-right: 15px;
+}
+
+/* pmwiki.php */
+ul, ol, pre, dl, p { margin-top:0px; margin-bottom:0px; }
+code.escaped {
+ white-space: pre;
+ display: inline-block;
+ vertical-align: bottom;
+ text-indent: 0;
+}
+.vspace { margin-top:25px; }
+.indent { margin-left:15px; }
+.outdent { margin-left:15px; text-indent:-15px; }
+a.createlinktext { text-decoration:none; border-bottom:1px dotted gray; }
+a.createlink { text-decoration:none; position:relative; top:-7px;
+ font-weight:bold; font-size:smaller; border-bottom:none; }
+img { border:0px; }
+
+/* Prevent white space below vertically stacked images */
+div.imgonly img, div.imgcaption img:first-child { vertical-align: bottom; }
+
+/* wikistyles.php */
+.frame { border:1px solid #cccccc; padding:4px; background-color:#f9f9f9; }
+.lfloat { float:left; margin-right:7px; }
+.rfloat { float:right; margin-left:7px; }
+
+/* stdmarkup.php */
+table.markup { border:2px dotted #ccf; width:100%; }
+/* td.markup1, td.markup2 { padding-left:0px; padding-right:0px; } */
+table.horiz td.markup1, table.vert td.markup1 {
+ border-bottom:1px solid #ccf; border-right: none; width: auto; }
+table.horiz td.markup1, table.horiz td.markup2 {
+ /* horizontal markup tables to vertical */
+ display: block; }
+table.markup caption { text-align:left; }
+div.faq p, div.faq pre { margin-left:15px; }
+div.faq p.question { margin: 0; font-weight:bold; }
+div.faqtoc div.faq * { display:none; }
+div.faqtoc div.faq p.question
+ { display:block; font-weight:normal; margin:7px 0 7px 15px; line-height:normal; }
+div.faqtoc div.faq p.question * { display:inline; }
+
+/* simuledit.php */
+.editconflict { color:green;
+ font-style:italic; margin-top:20px; margin-bottom:20px; }
+
+/* pagerev.php */
+.diffbox { border-left:1px #999 solid; margin-top:20px; font-size:12px; }
+.diffauthor { font-weight:bold; }
+.diffchangesum { font-weight:bold; }
+.difftime { background-color:#ddd; }
+.difftype { font-weight:bold; }
+.diffadd { border-left:5px #9f9 solid; padding-left:5px; }
+.diffdel { border-left:5px #ff9 solid; padding-left:5px; }
+.diffrestore { margin:20px 0px; }
+.diffmarkup { font-size:14px; }
+.diffmarkup del { background:#ff9; text-decoration: none; }
+.diffmarkup ins { background:#9f9; text-decoration: none; }
+
+/* urlapprove.php */
+.apprlink { font-size:smaller; }
+
+/* vardoc.php */
+a.varlink { text-decoration:none; }
+
+#wikiedit-minoredit {
+ white-space: nowrap;
+}
+
+/* In HTML5 only styles are valid for alignment */
+td.left, th.left { text-align: left;}
+td.center, th.center { text-align: center;}
+td.right, th.right { text-align: right;}
+td.top, th.top { vertical-align: top;}
+td.bottom, th.bottom { vertical-align: bottom;}
+td.middle, th.middle { vertical-align: middle;}
+
+.noPmTOC {display:none;}
+.PmTOCdiv { display: inline-block; font-size: 13px; overflow: auto; max-height: 500px;}
+.PmTOCdiv a { text-decoration: none;}
+.back-arrow {font-size: .9em; text-decoration: none;}
+#PmTOCchk + label {cursor: pointer;}
+#PmTOCchk {display: none;}
+#PmTOCchk:not(:checked) + label > .pmtoc-show {display: none;}
+#PmTOCchk:checked + label > .pmtoc-hide {display: none;}
+#PmTOCchk:checked + label + div {display: none;}
+
+table.simpletable {
+ border-collapse: collapse;
+}
+table.simpletable tr:nth-child(odd) {
+ background-color: #eee;
+}
+table.simpletable th {
+ background-color: #ccc;
+}
+table.simpletable th, table.simpletable td {
+ border: 1px solid #888;
+}
+
+table.sortable th {
+ cursor: pointer;
+}
+
+table.sortable th:hover::after {
+ color: inherit;
+ content: "\00A0\025B8";
+}
+
+table.sortable th::after {
+ color: transparent;
+ content: "\00A0\025B8";
+}
+
+table.sortable th.dir-u::after {
+ color: inherit;
+ content: "\00A0\025BE";
+}
+
+table.sortable th.dir-d::after {
+ color: inherit;
+ content: "\00A0\025B4";
+}
+
+*[data-pmtoggle], .pmtoggleall {
+ cursor: pointer;
+ font-weight: bold;
+}
+*[data-pmtoggle]::before {
+ content: "\025BE\00A0";
+ float: left;
+}
+*[data-pmtoggle="closed"]::before {
+ content: "\025B8\00A0";
+}
+
+*[data-pmtoggle] + * {
+ margin-left: .8em;
+}
+*[data-pmtoggle="closed"] + * {
+ display: none;
+}
+
+@media screen and (min-width:50em) {
+ html, body, #bodywrap {
+ line-height: 20px;
+ width: 100%;
+ margin: 0 auto;
+ background-color: #f7f7f7;
+ }
+
+ #wikileft, #wikihead-searchform, #wikicmds {
+ background-color: #f7f7f7;
+ opacity: 1;
+ display: block;
+ position: relative;
+ border: none;
+ max-width: none;
+ height: auto;
+ max-height: none;
+ overflow: auto;
+ top: 0px;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+ }
+ #wikicmds-toggle-label, #wikileft-toggle-label, #wikihead-search-toggle-label {
+ display: none;
+ }
+ #wikihead-searchform {
+ background-color: transparent;
+ float: right;
+ padding: 4px;
+ right: 6px;
+ top: 6px;
+ width: auto;
+ max-width: none;
+ line-height: inherit;
+ overflow: hidden;
+ }
+ #wikilogo {
+ display: inline-block;
+ margin-top: 4px;
+ padding: 6px;
+ }
+ #wikihead, #wikihead input {
+ font-size: 85%;
+ }
+
+ #wikihead-recent-changes-link, #wikihead-searchlink{
+ display: inline;
+ }
+
+ #wikimid {
+ display: table;
+ table-layout:fixed;
+ width: 100%;
+ }
+ #wikisidebar, #wikibody {
+ display: table-cell;
+ }
+
+ #wikisidebar {
+ position: relative;
+ border-right:1px solid #ccc;
+ width:167px;
+ vertical-align: top;
+ padding: 0; margin: 0;
+ overflow: auto;
+ }
+ #wikileft {
+ padding:6px;
+ left: 0;
+ margin: 0 1px 0 0;
+ background: transparent;
+ font-size: 13px;
+ line-height: 17px;
+ }
+ #wikileft a { padding: 0px; }
+ #wikileft li {margin:0px; }
+
+ #wikibody {
+ padding-left: 10px;
+ vertical-align: top;
+ }
+
+ #wikicmds {
+ right: 0px;
+ background-color: transparent;
+ float:right;
+ white-space:nowrap;
+ font-size:13px;
+ }
+ #wikicmds li { display:inline; margin:0px 5px; padding: 0; border: none; }
+
+ #wikifoot {
+ padding-left:178px;
+ }
+
+ #wikifoot.nosidebar {
+ padding-left:10px;
+ }
+ .vspace { margin-top: 20px; }
+ .indent { margin-left:40px; }
+ .outdent { margin-left:40px; text-indent:-40px; }
+
+ .rtl .indent, .rtl.indent, .rtl .outdent, .rtl.outdent {
+ margin-left:0; margin-right: 40px;
+ }
+
+ ul, ol { padding: 0 0 0 40px; }
+ dd { margin-left: 40px; }
+
+ table.horiz td.markup1, table.horiz td.markup2 {
+ display: table-cell; }
+
+ td.markup1, td.markup2 { padding-left:10px; padding-right:10px; }
+ table.vert td.markup1 { border-bottom:1px solid #ccf; }
+ table.horiz td.markup1 { width:23em; border-right:1px solid #ccf; border-bottom:none; }
+ td.markup1 pre { white-space: pre-wrap; }
+
+ div.faq p, div.faq pre { margin-left:40px; }
+ div.faq p.question { margin:15px 0 12px 0; }
+ div.faqtoc div.faq p.question
+ { margin:8px 0 8px 20px; }
+
+
+ select, textarea, input {
+ font-size: 14px;
+ }
+
+ #wikileft-toggle:checked ~ #wikioverlay,
+ #wikihead-search-toggle:checked ~ #wikioverlay,
+ #wikicmds-toggle:checked ~ #wikioverlay {
+ display: none;
+ }
+
+}
+
+/* These affect the printed appearance of the web view (not the separate
+** print view) of pages. The sidebar and action links aren't printed. */
+@media print {
+ body { width:auto; margin:0px; padding:8px; }
+ #wikihead, #wikileft, #wikicmds, .footnav { display:none; }
+ #wikifoot { padding:2px; }
+ *[data-pmtoggle="closed"] + * { display: inherit; }
+ *[data-pmtoggle="closed"]::before { content: "\025BE\00A0"; }
+}
+
+/* Fade-in animation */
+@keyframes fadein {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+@-moz-keyframes fadein {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+@-webkit-keyframes fadein {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+@-ms-keyframes fadein {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+@-o-keyframes fadein {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
--- /dev/null
+++ pub/skins/ircnow/skin.js
@@ -0,0 +1,78 @@
+/***********************************************************************
+** skin.js
+** Copyright 2016-2017 Petko Yotov www.pmwiki.org/petko
+**
+** This file is part of PmWiki; you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published
+** by the Free Software Foundation; either version 2 of the License, or
+** (at your option) any later version. See pmwiki.php for full details.
+**
+** This script fixes the styles of some elements when some directives
+** like (:noleft:) are used in a page.
+***********************************************************************/
+(function(){
+ var W = window, D = document;
+ function $(x) { // returns element from id
+ return D.getElementById(x);
+ }
+ function hide(id) { // hides element
+ var el = $(id);
+ if(el) el.style.display = 'none';
+ }
+ function cname(id, c) { // set element className
+ var el = $(id);
+ if(el) el.className = c;
+ }
+ var wsb = $('wikisidebar');
+ if(! wsb) { // (:noleft:)
+ hide('wikileft-toggle-label')
+ cname('wikifoot', 'nosidebar');
+ }
+ else {
+ var sbcontent = wsb.textContent || wsb.innerText;
+ if(! sbcontent.replace(/\s+/, '').length) // empty sidebar, eg. protected
+ hide('wikileft-toggle-label');
+ }
+ var wcmd = $('wikicmds');
+ if(wcmd) { // page actions
+ var pacontent = wcmd.textContent || wcmd.innerText;
+ if(! pacontent.replace(/\s+/, '').length) // empty, eg. protected
+ hide('wikicmds-toggle-label');
+ }
+ if(! $('wikihead-searchform')) // no search form, eg. custom header
+ hide('wikihead-search-toggle-label');
+ var overlay = $('wikioverlay');
+ if(overlay) {
+ overlay.addEventListener('click', function(){
+ $('wikicmds-toggle').checked = false;
+ $('wikihead-search-toggle').checked = false;
+ $('wikileft-toggle').checked = false;
+ });
+ }
+ var scrolltables = function() {
+ // This function "wraps" large tables in a scrollable div
+ // and "unwraps" narrow tables from the scrollable div
+ // allowing table alignement
+ var tables = D.getElementsByTagName('table');
+ for(var i=0; i<tables.length; i++) {
+ var t = tables[i];
+ var pn = t.parentNode;
+ if(pn.className == 'scrollable') {
+ var gp = pn.parentNode;
+ if(t.offsetWidth < gp.offsetWidth) {
+ gp.insertBefore(t, pn);
+ gp.removeChild(pn);
+ }
+ }
+ else {
+ if(t.offsetWidth > pn.offsetWidth) {
+ var nn = D.createElement('div');
+ pn.insertBefore(nn, t).className = 'scrollable';
+ nn.appendChild(t);
+ }
+ }
+ }
+ }
+ W.addEventListener('resize', scrolltables, false);
+ D.addEventListener('DOMContentLoaded', scrolltables, false);
+})();
--- /dev/null
+++ pub/skins/ircnow/skin.php
@@ -0,0 +1,89 @@
+<?php if (!defined('PmWiki')) exit();
+/***********************************************************************
+** skin.php
+** Copyright 2016-2019 Petko Yotov www.pmwiki.org/petko
+**
+** This file is part of PmWiki; you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published
+** by the Free Software Foundation; either version 2 of the License, or
+** (at your option) any later version. See pmwiki.php for full details.
+***********************************************************************/
+
+
+global $HTMLStylesFmt, $SkinElementsPages, $DefaultSkinElements, $TableCellAlignFmt,
+ $SearchBoxInputType, $WrapSkinSections, $HideTemplateSections, $EnableTableAttrToStyles;
+
+# Disable inline styles injected by the PmWiki core (we provide these styles in skin.css)
+$styles = explode(' ', 'pmwiki rtl-ltr wikistyles markup simuledit diff urlapprove vardoc PmSortable PmTOC');
+foreach($styles as $style) $HTMLStylesFmt[$style] = '';
+
+# CSS alignment for table cells (valid HTML5)
+SDV($TableCellAlignFmt, " class='%s'");
+
+# For (:searchbox:), valid semantic HTML5
+$SearchBoxInputType = "search";
+
+# remove deprecated "name=" parameter from anchor tags
+if($GLOBALS['VersionNum'] < 2002056) {
+ # we want the skin to also work with older PmWiki versions
+ Markup('[[#','<[[','/(?>\\[\\[#([A-Za-z][-.:\\w]*))\\]\\]/e',
+ "Keep(TrackAnchors('$1') ? '' : \"<a id='$1'></a>\", 'L')");
+}
+else {
+ Markup('[[#','<[[','/(?>\\[\\[#([A-Za-z][-.:\\w]*))\\]\\]/',
+ "MarkupKeepTrackAnchors");
+}
+function MarkupKeepTrackAnchors($m) {
+ return Keep(TrackAnchors($m[1]) ? '' : "<a id='{$m[1]}'></a>", 'L');
+}
+
+# in HTML5 "clear" is a style not an attribute
+Markup('[[<<]]','inline','/\\[\\[<<\\]\\]/',"<br style='clear:both;' />");
+
+# Allow skin header and footer to be written
+# in a wiki page, and use defaults otherwise
+SDVA($WrapSkinSections, array(
+ '#skinheader' => '<header id="wikihead">
+ <div id="wikihead-content">
+ %s
+ </div>
+ </header>',
+ '#skinfooter' => '<footer id="wikifoot">
+ %s
+ </footer>',
+));
+SDVA($HideTemplateSections, array(
+ '#skinheader' => 'PageHeaderFmt',
+ '#skinfooter' => 'PageFooterFmt',
+));
+
+# This function prints a skin element which is written
+# inside a [[#header]]...[[#headerend]] section in Site.SkinElements
+# overriding the existing section from the template file
+
+function SkinFmt($pagename, $args) {
+ global $WrapSkinSections, $HideTemplateSections, $TmplDisplay;
+
+ $args = preg_split('!\\s+!', $args, null, PREG_SPLIT_NO_EMPTY);
+
+ $section = array_shift($args);
+ $hidesection = $HideTemplateSections[$section];
+
+ if(isset($TmplDisplay[$hidesection]) && $TmplDisplay[$hidesection] == 0) {
+ return; # Section was disabled by (:noheader:) or (:nofooter:)
+ }
+
+ foreach($args as $p) {
+ $pn = FmtPageName($p, $pagename);
+ $elm = RetrieveAuthSection($pn, "$section{$section}end");
+ if(!$elm) continue;
+
+ $html = MarkupToHTML($pagename, Qualify($pn, $elm));
+ echo sprintf($WrapSkinSections[$section], $html);
+ SetTmplDisplay($hidesection,0);
+ return;
+ }
+ if(@$DefaultSkinElements[$section])
+ echo FmtPageName($DefaultSkinElements[$section], $pagename);
+}
+
--- /dev/null
+++ pub/skins/ircnow/skin.tmpl
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html $HTMLTagAttr>
+<head>
+ <title>$WikiTitle | {$Group} / {$Title} $ActionTitle</title>
+ <!-- Skin Copyright 2017 Petko Yotov www.pmwiki.org/petko ; Skin License GNU GPLv2+ -->
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
+ <link rel='stylesheet' href='$SkinDirUrl/skin.css' type='text/css' />
+ <!--HTMLHeader-->
+</head>
+<body>
+ <div id="bodywrap">
+ <input id="wikileft-toggle" type="checkbox"/>
+ <label for="wikileft-toggle" id="wikileft-toggle-label"></label>
+
+ <input id="wikihead-search-toggle" type="checkbox"/>
+ <label for="wikihead-search-toggle" id="wikihead-search-toggle-label"></label>
+ <input id="wikicmds-toggle" type="checkbox"/>
+
+<!--function:SkinFmt #skinheader {$Group}.SkinElements {$SiteGroup}.SkinElements-->
+<!--PageHeaderFmt-->
+ <header id="wikihead">
+ <div id="wikihead-content">
+ <span id="wikilogo"><a href="{$ScriptUrl}"><img src="$PageLogoUrl" alt="$WikiTitle"/></a></span>
+ <form id="wikihead-searchform" action="{$ScriptUrl}" method="get">
+ <span class="headnav" id="wikihead-recent-changes-link">
+ <a href="{$ScriptUrl}/$[{$Group}/RecentChanges]" accesskey="$[ak_recentchanges]">$[Recent Changes]</a> -
+ </span>
+ <span id="wikihead-searchlink"><a href="{$ScriptUrl}/$[{$SiteGroup}/Search]">$[Search]</a>:</span>
+ <input type="hidden" name="n" value="{$FullName}" />
+ <input type="hidden" name="action" value="search" />
+ <input id="wikihead-searchquery" type="search" name="q" value="" class="inputbox searchbox" placeholder="$[Search]" />
+ <input id="wikihead-searchsubmitbtn" type="submit" class="inputbutton searchbutton" value="$[Go]" />
+ </form>
+ </div><!--wikiheadcontent-->
+ </header><!--wikihead-->
+<!--/PageHeaderFmt-->
+
+ <div id="wikimid">
+<!--PageLeftFmt-->
+ <nav id="wikisidebar">
+ <div id="wikileft">
+ <!--wiki:{$Group}.SideBar {$SiteGroup}.SideBar-->
+ </div>
+ </nav>
+<!--/PageLeftFmt-->
+
+ <div id="wikibody">
+ <main>
+<!--PageActionFmt-->
+ <label for="wikicmds-toggle" id="wikicmds-toggle-label"></label>
+ <div id='wikicmds'><!--wiki:{$Group}.PageActions {$SiteGroup}.PageActions--></div>
+<!--/PageActionFmt-->
+<!--PageTitleFmt-->
+ <div id='wikititle'>
+ <div class='pagegroup'><a href='{$ScriptUrl}/{$Group}'>{$Group}</a> /</div>
+ <h1 class='pagetitle'>{$Title}</h1>
+ </div>
+<!--/PageTitleFmt-->
+
+<!--PageText-->
+ </main>
+
+ </div><!--wikibody-->
+
+ </div><!--wikimid-->
+
+<!--function:SkinFmt #skinfooter {$Group}.SkinElements {$SiteGroup}.SkinElements-->
+<!--PageFooterFmt-->
+ <footer id='wikifoot'>
+ <div id="wikifoot-links" class="footnav">
+ <a rel="nofollow" href="{$PageUrl}?action=edit">$[Edit]</a> -
+ <a rel="nofollow" href="{$PageUrl}?action=diff">$[History]</a> -
+ <a rel="nofollow" href="{$PageUrl}?action=print" target="_blank">$[Print]</a> -
+ <a href="{$ScriptUrl}/$[{$Group}/RecentChanges]">$[Recent Changes]</a> -
+ <a href="{$ScriptUrl}/$[{$SiteGroup}/Search]">$[Search]</a>
+ </div>
+ <div id="wikifoot-lastmod" class="lastmod">All content is under the <a href="index.php?n=License.Ircnow">IRCNow License</a>. $[Page last modified on {$LastModified}]</div>
+ </footer>
+<!--/PageFooterFmt-->
+ <div id="wikioverlay"></div>
+ </div><!--bodywrap-->
+
+ <script type='text/javascript' src='$SkinDirUrl/skin.js' async></script>
+
+<!--HTMLFooter-->
+
+</body>
+</html>
+
--- /dev/null
+++ pub/skins/ircnow/xclose.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" stroke-width="8" stroke="#bf0000" viewBox="0 0 60 60"><!-- Copyright 2016 Petko Yotov www.pmwiki.org/petko ; License GNU GPLv2+ --><path d="m56 56l-52-52m0 52l52-52"/></svg>
\ No newline at end of file
--- /dev/null
+++ pub/skins/ircnow/xmenu.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" stroke-width="8" fill="none" stroke="#7f7f7f" viewBox="0 0 60 60"><!-- Copyright 2016 Petko Yotov www.pmwiki.org/petko ; License GNU GPLv2+ --><path d="m0 6h60m-60 21h60m-60 21h60"/></svg>
\ No newline at end of file
--- /dev/null
+++ pub/skins/ircnow/xsearch.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" stroke="#7c7c7c" fill="none" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="8" viewBox="0 0 60 60"><!-- Copyright 2016 Petko Yotov www.pmwiki.org/petko ; License GNU GPLv2+ --><circle r="20" cy="25" cx="25"/><path d="m56 56l-16-16"/></svg>
\ No newline at end of file
--- /dev/null
+++ pub/skins/ircnow/xwrench.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" stroke-linecap="null" stroke-linejoin="null" stroke-width="0" stroke="#7f7f7f" fill="#7f7f7f" viewBox="0 0 60 60"><!-- Copyright 2016 Petko Yotov www.pmwiki.org/petko ; License GNU GPLv2+ --><path d="m51.28 27.324c3.917-4.503 4.704-10.65 2.573-15.818l-7.863 9.17-7.751-1.508-2.569-7.467 7.844-9.149c-5.396-1.359-11.34.28-15.24 4.766-4.113 4.73-4.786 11.278-2.241 16.592l-20.498 23.571c-2.18 2.506-1.915 6.306.591 8.487 2.506 2.178 6.306 1.915 8.487-.591l20.474-23.547c5.636 1.816 12.06.244 16.193-4.508z" transform="rotate(-90 30 30)"/></svg>
\ No newline at end of file
--- /dev/null
+++ pub/skins/pmwiki/README
@@ -0,0 +1,22 @@
+This directory contains the files to display pages in PmWiki according
+to the default layout.
+
+==>Don't edit these files directly, as you may lose your edits the
+next time you upgrade PmWiki!
+
+Instead, copy the files to another directory in pub/skins/ and edit
+them there. You can then configure PmWiki to use your modified layout
+files by setting the $Skin variable in your local/config.php. For
+example, if you copy your custom skin to pub/skins/custom, then you
+would set
+ $Skin = 'custom';
+in local/config.php.
+
+The files in this directory:
+ pmwiki.tmpl -- the default template for page layouts
+ pmwiki.css -- PmWiki's default css
+ pmwiki-32.gif -- the PmWiki logo
+
+If you just want to change the logo, you can do it by setting $PageLogoUrl
+to the url location of your logo.
+
Binary files /dev/null and pub/skins/pmwiki/pmwiki-32.gif differ
--- /dev/null
+++ pub/skins/pmwiki/pmwiki.css
@@ -0,0 +1,118 @@
+/***********************************************************************
+** pmwiki.css
+** Copyright 2004-2017 Patrick R. Michaud (pmichaud@pobox.com)
+** Copyright 2006 Hagan Fox
+** This file is part of PmWiki; you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published
+** by the Free Software Foundation; either version 2 of the License, or
+** (at your option) any later version. See pmwiki.php for full details.
+**
+** File maintained by Petko Yotov www.pmwiki.org/petko
+***********************************************************************/
+
+/* This sets the overall frame for the site */
+body {
+ margin:0px; background-color:#f7f7f7; color: black;
+ font-family:Arial,Helvetica,sans-serif; font-size:11pt;
+}
+
+/* These control the fixed-width text elements of the page */
+textarea, pre, code { font-size:0.9em; }
+pre, code { font-family:'Lucida Console','Andale Mono','Courier New',Courier,monospace; }
+pre { line-height:1.2em; }
+pre code, code code, pre pre { font-size:100%; }
+
+/* These primarily adjust the size and spacing of heading elements,
+** most browsers have atrocious defaults for these. */
+h1, h2, h3, h4, h5, h6 { margin-top:1.0em; margin-bottom:0.6em; }
+h1, h2, h3, h6 { font-weight:normal; }
+h4, h5 { font-weight:bold; }
+h1 code, h2 code, h3 code, h4 code { font-size:1em; }
+h1 { font-size:1.8em; }
+h2 { font-size:1.44em; }
+h3 { font-size:1.22em; }
+h4 { font-size:1.07em; }
+h5 { font-size:1.0em; }
+h6 { font-size:1.0em; }
+
+/* The #wikilogo element is the logo from $PageLogoFmt */
+#wikilogo { margin-top:4px; padding:6px; border-bottom:1px #cccccc solid; }
+
+/* This controls the rest of the heading (primarily the search box) */
+#wikihead {
+ position:absolute; right:10px; top:10px;
+ font-family:Verdana,sans-serif; font-size:85%;
+}
+#wikihead input { font-size:85%; }
+
+/* These are for the left-sidebar. */
+#wikileft {
+ width:155px;
+ padding:6px; border-right:1px #cccccc solid;
+ line-height:1.33em;
+ font-size:9.4pt; font-family:Verdana,sans-serif;
+}
+#wikileft .vspace { margin-top:1.125em; }
+#wikileft a { text-decoration:none; color:black; }
+#wikileft a:hover { text-decoration:underline; color:blue; }
+#wikileft ul { list-style:none; padding:0px; margin:0px; }
+#wikileft li { margin:0px; padding-left: 6px; }
+.sidehead {
+ margin:0px; padding:4px 2px 2px 2px;
+ font-size:11pt; font-weight:bold; font-style:normal;
+}
+.sidehead a
+ { color:#505050; font-weight:bold; font-style:normal; }
+
+/* These affect the main content area. */
+#wikibody {
+ padding:0px 10px 10px 10px; background-color:white;
+ font-size:11pt;
+}
+#wikicmds {
+ float:right; white-space:nowrap;
+ font-family:Verdana,sans-serif; font-size:80%;
+}
+#wikicmds ul { list-style:none; margin:0px; padding:0px; }
+#wikicmds li { display:inline; margin:0px 5px; }
+#wikicmds li a { text-decoration:none; color:black; border:none; }
+#wikicmds li a.createlink { display:none; }
+#wikicmds li a:hover { text-decoration:underline; color:blue; }
+.pagegroup { margin-top:8px; margin-bottom:2px; }
+.pagetitle { line-height:1em; margin:0px; font-size:1.6em; font-weight:normal; }
+.wikiaction { margin-top:4px; margin-bottom:4px; }
+#wikitext { margin-top:12px; line-height:1.33em; }
+#wikitext table { font-size:100%; line-height:1.33em; } /* For MSIE 5.5 */
+
+/* These are for the edit form. */
+#wikiedit form { margin:0px; width:100%; }
+#wikiedit textarea { width:100%; }
+.wikimessage { margin-top:4px; margin-bottom:4px; font-style:italic; }
+
+/* These affect the lines at the very bottom. */
+#wikifoot {
+ padding-left:178px; padding-bottom:4px; border-top:1px #cccccc solid;
+ font-family:Verdana,sans-serif; font-size:80%;
+}
+
+/* table class=simpletable from pmwiki-responsive.css */
+table.simpletable {
+ border-collapse: collapse;
+}
+table.simpletable tr:nth-child(odd) {
+ background-color: #eee;
+}
+table.simpletable th {
+ background-color: #ccc;
+}
+table.simpletable th, table.simpletable td {
+ border: 1px solid #888;
+}
+
+/* These affect the printed appearance of the web view (not the separate
+** print view) of pages. The sidebar and action links aren't printed. */
+@media print {
+ body { width:auto; margin:0px; padding:0.5em; }
+ #wikihead, #wikileft, #wikicmds, .footnav { display:none; }
+ #wikifoot { padding:2px; }
+}
--- /dev/null
+++ pub/skins/pmwiki/pmwiki.tmpl
@@ -0,0 +1,52 @@
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" $HTMLTagAttr>
+<head>
+ <title>$WikiTitle | {$Group} / {$Title} $ActionTitle</title>
+ <meta http-equiv='Content-Style-Type' content='text/css' />
+ <link rel='stylesheet' href='$SkinDirUrl/pmwiki.css' type='text/css' />
+ <!--HTMLHeader-->
+</head>
+<body>
+<!--PageHeaderFmt-->
+ <div id='wikilogo'><a href='{$ScriptUrl}'><img src='$PageLogoUrl'
+ alt='$WikiTitle' border='0' /></a></div>
+ <div id='wikihead'>
+ <form action='{$ScriptUrl}'>
+ <span class='headnav'><a href='{$ScriptUrl}/$[{$Group}/RecentChanges]'
+ accesskey='$[ak_recentchanges]'>$[Recent Changes]</a> -</span>
+ <input type='hidden' name='n' value='{$FullName}' />
+ <input type='hidden' name='action' value='search' />
+ <a href='{$ScriptUrl}/$[{$SiteGroup}/Search]'>$[Search]</a>:
+ <input type='text' name='q' value='' class='inputbox searchbox' />
+ <input type='submit' class='inputbutton searchbutton'
+ value='$[Go]' /></form></div>
+<!--/PageHeaderFmt-->
+ <table id='wikimid' width='100%' cellspacing='0' cellpadding='0'><tr>
+<!--PageLeftFmt-->
+ <td id='wikileft' valign='top'>
+ <!--wiki:{$Group}.SideBar {$SiteGroup}.SideBar--></td>
+<!--/PageLeftFmt-->
+ <td id='wikibody' valign='top'>
+<!--PageActionFmt-->
+ <div id='wikicmds'><!--wiki:{$Group}.PageActions {$SiteGroup}.PageActions--></div>
+<!--PageTitleFmt-->
+ <div id='wikititle'>
+ <div class='pagegroup'><a href='{$ScriptUrl}/{$Group}'>{$Group}</a> /</div>
+ <h1 class='pagetitle'>{$Title}</h1></div>
+<!--PageText-->
+ </td>
+ </tr></table>
+<!--PageFooterFmt-->
+ <div id='wikifoot'>
+ <div class='footnav'>
+ <a rel="nofollow" href='{$PageUrl}?action=edit'>$[Edit]</a> -
+ <a rel="nofollow" href='{$PageUrl}?action=diff'>$[History]</a> -
+ <a rel="nofollow" href='{$PageUrl}?action=print' target='_blank'>$[Print]</a> -
+ <a href='{$ScriptUrl}/$[{$Group}/RecentChanges]'>$[Recent Changes]</a> -
+ <a href='{$ScriptUrl}/$[{$SiteGroup}/Search]'>$[Search]</a></div>
+ <div class='lastmod'>All content is under the <a href="index.php?n=License.Ircnow">IRCNow License</a>. $[Page last modified on {$LastModified}]</div></div>
+<!--HTMLFooter-->
+</body>
+</html>
--- /dev/null
+++ pub/skins/pmwiki-responsive/README
@@ -0,0 +1,28 @@
+This directory contains the files to display pages in PmWiki according
+to the default "responsive" layout, adaptive for large and small screens.
+
+Note that currently this layout is "beta". It requires relatively recent
+mobile and desktop browsers (post-2010).
+
+==>Don't edit these files directly, as you may lose your edits the
+next time you upgrade PmWiki!
+
+Instead, copy the files to another directory in pub/skins/ and edit
+them there. You can then configure PmWiki to use your modified layout
+files by setting the $Skin variable in your local/config.php. For
+example, if you copy your custom skin to pub/skins/custom, then you
+would set
+ $Skin = 'custom';
+in local/config.php.
+
+The files in this directory:
+ skin.tmpl -- the default template for page layouts
+ skin.css -- PmWiki's default css
+ skin.php -- some template functions
+ skin.js -- some template functions (browser)
+ *.svg -- icons
+
+Note, the default PmWiki logo is in the "pmwiki" template directory.
+If you just want to change the logo, you can do it by setting $PageLogoUrl
+to the url location of your logo.
+
--- /dev/null
+++ pub/skins/pmwiki-responsive/skin.css
@@ -0,0 +1,557 @@
+/***********************************************************************
+** skin.css
+** Copyright 2016-2020 Petko Yotov www.pmwiki.org/petko
+**
+** Partially based on pmwiki.css:
+** Copyright 2004-2006 Patrick R. Michaud pmichaud@pobox.com
+** Copyright 2006 Hagan Fox
+**
+** This file is part of PmWiki; you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published
+** by the Free Software Foundation; either version 2 of the License, or
+** (at your option) any later version. See pmwiki.php for full details.
+**
+** This skin was implemented with a mobile-first approach.
+** It should work reasonably well with browsers released since 2009.
+** This CSS uses relative "Reference pixel" dimensions.
+***********************************************************************/
+
+html, body, #bodywrap {
+ padding: 0;
+ margin: 0;
+ font-family: 'Arial', 'Helvetica', Sans-serif;
+ font-size:15px;
+ line-height: 25px;
+ background-color: #f7f7f7;
+ color: black;
+ min-height: 100%;
+ position: relative;
+}
+
+#wikibody {
+ position: relative;
+ background-color: white;
+ padding: 3px;
+ margin: 0;
+}
+
+#wikileft, #wikihead-searchform, #wikicmds {
+ background-color: #fff;
+ opacity: 0.1;
+ display: none;
+ position: absolute;
+ border: 1px solid #ccc;
+ max-width: 90%;
+ max-width: 90vw;
+ height: auto;
+ overflow: auto;
+ top: 8px;
+ -webkit-box-shadow: 2px 2px 8px 0px rgba(0,0,0,0.75);
+ -moz-box-shadow: 2px 2px 8px 0px rgba(0,0,0,0.75);
+ box-shadow: 2px 2px 8px 0px rgba(0,0,0,0.75);
+}
+
+#wikihead {
+ border-bottom:1px solid #ccc;
+ padding: 0;
+ margin: 0;
+ line-height: 13px;
+ min-height: 33px;
+}
+
+#wikihead-searchform {
+ text-align: center;
+ padding: 10px;
+ z-index: 5;
+ right: 30px;
+ width: 16em;
+ max-width: 80%;
+ line-height: 167%;
+}
+#wikihead-searchquery {
+ max-width: 12em;
+}
+#wikimid {
+ margin: 0;
+ padding: 0;
+ max-width: 100%;
+}
+
+#wikileft {
+ left: 30px;
+ padding: 6px;
+ z-index: 4;
+}
+
+#wikicmds {
+ padding:0px;
+ z-index: 6;
+ white-space:nowrap;
+ right: 30px;
+}
+
+#wikitext {
+ margin-top: 12px;
+}
+
+#wikifoot {
+ border-top: 1px solid #ccc;
+ padding: 3px;
+ font-size: 13.5px;
+}
+
+#wikifoot.nosidebar {
+ padding-left:3px;
+}
+
+#wikihead-search-toggle, #wikileft-toggle, #wikicmds-toggle {
+ display: none;
+}
+
+#wikihead-search-toggle-label { background-image: url(xsearch.svg); }
+#wikileft-toggle-label { background-image: url(xmenu.svg); }
+#wikicmds-toggle-label { background-image: url(xwrench.svg); }
+
+#wikihead-search-toggle-label, #wikileft-toggle-label, #wikicmds-toggle-label {
+ position: relative;
+ display: block;
+ width: 22px;
+ height: 22px;
+ background-size: cover;
+ background-repeat: no-repeat;
+ float:right;
+ margin: 6px 6px 0 6px;
+ z-index: 3;
+}
+
+#wikileft-toggle-label {
+ float: left;
+}
+#wikicmds-toggle-label {
+ margin: 3px;
+}
+
+#wikihead-search-toggle:checked + label,
+#wikileft-toggle:checked + label,
+#wikicmds-toggle:checked ~ #wikimid #wikicmds-toggle-label {
+ background-image: url(xclose.svg);
+}
+
+#wikileft-toggle:checked ~ * #wikileft,
+#wikihead-search-toggle:checked ~ * #wikihead-searchform,
+#wikicmds-toggle:checked ~ * #wikicmds {
+ display: block;
+ opacity: 1;
+
+-webkit-animation: fadein 1s;
+ -moz-animation: fadein 1s;
+ -ms-animation: fadein 1s;
+ -o-animation: fadein 1s;
+ animation: fadein 1s;
+}
+
+#wikioverlay {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ background-color: rgba(0, 0, 0, .2);
+ z-index: 2;
+ display: none;
+}
+#wikileft-toggle:checked ~ #wikioverlay,
+#wikihead-search-toggle:checked ~ #wikioverlay,
+#wikicmds-toggle:checked ~ #wikioverlay {
+ display: block;
+}
+
+
+
+/* These are for the left-sidebar. */
+#wikileft .vspace { margin-top:20px; }
+#wikileft ul { list-style:none; padding:0px; margin:0px; }
+#wikileft li { margin:8px 0px; padding-left: 6px; }
+.sidehead {
+ margin:0px; padding:4px 2px 2px 2px;
+ font-weight:bold; font-style:normal;
+}
+.sidehead a
+ { color:#505050; font-weight:bold; font-style:normal; }
+#wikileft a { text-decoration:none; color:black; padding: 8px 0; }
+#wikileft a:hover { text-decoration:underline; color:blue; }
+
+#wikicmds ul { list-style:none; margin:0px; padding:0px; }
+#wikicmds li { padding: 8px; border-top:1px solid #ccc;}
+#wikicmds li:first-child {border-top:none;}
+#wikicmds li a { text-decoration:none; color:black; border: none; }
+#wikicmds li a.createlink { display:none; }
+#wikicmds li a:hover { text-decoration:underline; color:blue; }
+
+
+/* These primarily adjust the size and spacing of heading elements,
+** most browsers have atrocious defaults for these. */
+h1, h2, h3, h4, h5, h6 { margin-top:15px; margin-bottom:9px; }
+h1, h2, h3, h6 { font-weight:normal; }
+h4, h5 { font-weight:bold; }
+h1 code, h2 code, h3 code, h4 code { font-size:15px; }
+h1 { font-size:27px; }
+h2 { font-size:22px; }
+h3 { font-size:18px; }
+h4 { font-size:16px; }
+h5 { font-size:15px; }
+h6 { font-size:15px; }
+
+.pagegroup { line-height:30px; }
+.pagetitle { line-height:24px; margin:0px; font-size:24px; font-weight:normal; }
+.wikiaction { margin-top:4px; margin-bottom:4px; }
+
+/* These control the fixed-width text elements of the page */
+pre, code { font-size:14px; }
+pre, code, .diffmarkup { font-family:'Lucida Console','Andale Mono','Courier New',Courier,monospace; }
+pre { line-height: 18px; }
+pre code, code code, pre pre { font-size:100%; }
+pre, code.escaped { max-width: 100%; overflow: auto; }
+
+/* Large tables can scroll */
+div.scrollable { max-width: 100%; overflow: auto; border: 1px dotted red;}
+
+#wikiedit form { margin:0px; width:100%; max-width:100%; }
+#wikiedit textarea { width:99.5%; max-width:99.5%; max-height: 60vh; }
+#wikiedit input { max-width:99.5%; }
+.wikimessage { margin-top:4px; margin-bottom:4px; font-style:italic; }
+
+input, img, iframe {
+ max-width: 100%;
+}
+dd {
+ margin-left: 15px;
+}
+ul, ol {
+ margin: 0;
+ padding: 0 0 0 20px;
+}
+
+select, textarea, input {
+ font-size: 16px; /*disable zoom-in on some phones*/
+}
+
+/* xlpage-utf-8.php */
+.rtl, .rtl * {direction:rtl; unicode-bidi:bidi-override;}
+.ltr, .ltr * {direction:ltr; unicode-bidi:bidi-override;}
+.rtl .indent, .rtl.indent, .rtl .outdent, .rtl.outdent {
+ margin-left:0; margin-right: 15px;
+}
+
+/* pmwiki.php */
+ul, ol, pre, dl, p { margin-top:0px; margin-bottom:0px; }
+code.escaped {
+ white-space: pre;
+ display: inline-block;
+ vertical-align: bottom;
+ text-indent: 0;
+}
+.vspace { margin-top:25px; }
+.indent { margin-left:15px; }
+.outdent { margin-left:15px; text-indent:-15px; }
+a.createlinktext { text-decoration:none; border-bottom:1px dotted gray; }
+a.createlink { text-decoration:none; position:relative; top:-7px;
+ font-weight:bold; font-size:smaller; border-bottom:none; }
+img { border:0px; }
+
+/* Prevent white space below vertically stacked images */
+div.imgonly img, div.imgcaption img:first-child { vertical-align: bottom; }
+
+/* wikistyles.php */
+.frame { border:1px solid #cccccc; padding:4px; background-color:#f9f9f9; }
+.lfloat { float:left; margin-right:7px; }
+.rfloat { float:right; margin-left:7px; }
+
+/* stdmarkup.php */
+table.markup { border:2px dotted #ccf; width:100%; }
+/* td.markup1, td.markup2 { padding-left:0px; padding-right:0px; } */
+table.horiz td.markup1, table.vert td.markup1 {
+ border-bottom:1px solid #ccf; border-right: none; width: auto; }
+table.horiz td.markup1, table.horiz td.markup2 {
+ /* horizontal markup tables to vertical */
+ display: block; }
+table.markup caption { text-align:left; }
+div.faq p, div.faq pre { margin-left:15px; }
+div.faq p.question { margin: 0; font-weight:bold; }
+div.faqtoc div.faq * { display:none; }
+div.faqtoc div.faq p.question
+ { display:block; font-weight:normal; margin:7px 0 7px 15px; line-height:normal; }
+div.faqtoc div.faq p.question * { display:inline; }
+
+/* simuledit.php */
+.editconflict { color:green;
+ font-style:italic; margin-top:20px; margin-bottom:20px; }
+
+/* pagerev.php */
+.diffbox { border-left:1px #999 solid; margin-top:20px; font-size:12px; }
+.diffauthor { font-weight:bold; }
+.diffchangesum { font-weight:bold; }
+.difftime { background-color:#ddd; }
+.difftype { font-weight:bold; }
+.diffadd { border-left:5px #9f9 solid; padding-left:5px; }
+.diffdel { border-left:5px #ff9 solid; padding-left:5px; }
+.diffrestore { margin:20px 0px; }
+.diffmarkup { font-size:14px; }
+.diffmarkup del { background:#ff9; text-decoration: none; }
+.diffmarkup ins { background:#9f9; text-decoration: none; }
+
+/* urlapprove.php */
+.apprlink { font-size:smaller; }
+
+/* vardoc.php */
+a.varlink { text-decoration:none; }
+
+#wikiedit-minoredit {
+ white-space: nowrap;
+}
+
+/* In HTML5 only styles are valid for alignment */
+td.left, th.left { text-align: left;}
+td.center, th.center { text-align: center;}
+td.right, th.right { text-align: right;}
+td.top, th.top { vertical-align: top;}
+td.bottom, th.bottom { vertical-align: bottom;}
+td.middle, th.middle { vertical-align: middle;}
+
+.noPmTOC {display:none;}
+.PmTOCdiv { display: inline-block; font-size: 13px; overflow: auto; max-height: 500px;}
+.PmTOCdiv a { text-decoration: none;}
+.back-arrow {font-size: .9em; text-decoration: none;}
+#PmTOCchk + label {cursor: pointer;}
+#PmTOCchk {display: none;}
+#PmTOCchk:not(:checked) + label > .pmtoc-show {display: none;}
+#PmTOCchk:checked + label > .pmtoc-hide {display: none;}
+#PmTOCchk:checked + label + div {display: none;}
+
+table.simpletable {
+ border-collapse: collapse;
+}
+table.simpletable tr:nth-child(odd) {
+ background-color: #eee;
+}
+table.simpletable th {
+ background-color: #ccc;
+}
+table.simpletable th, table.simpletable td {
+ border: 1px solid #888;
+}
+
+table.sortable th {
+ cursor: pointer;
+}
+
+table.sortable th:hover::after {
+ color: inherit;
+ content: "\00A0\025B8";
+}
+
+table.sortable th::after {
+ color: transparent;
+ content: "\00A0\025B8";
+}
+
+table.sortable th.dir-u::after {
+ color: inherit;
+ content: "\00A0\025BE";
+}
+
+table.sortable th.dir-d::after {
+ color: inherit;
+ content: "\00A0\025B4";
+}
+
+*[data-pmtoggle], .pmtoggleall {
+ cursor: pointer;
+ font-weight: bold;
+}
+*[data-pmtoggle]::before {
+ content: "\025BE\00A0";
+ float: left;
+}
+*[data-pmtoggle="closed"]::before {
+ content: "\025B8\00A0";
+}
+
+*[data-pmtoggle] + * {
+ margin-left: .8em;
+}
+*[data-pmtoggle="closed"] + * {
+ display: none;
+}
+
+@media screen and (min-width:50em) {
+ html, body, #bodywrap {
+ line-height: 20px;
+ width: 100%;
+ margin: 0 auto;
+ background-color: #f7f7f7;
+ }
+
+ #wikileft, #wikihead-searchform, #wikicmds {
+ background-color: #f7f7f7;
+ opacity: 1;
+ display: block;
+ position: relative;
+ border: none;
+ max-width: none;
+ height: auto;
+ max-height: none;
+ overflow: auto;
+ top: 0px;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+ }
+ #wikicmds-toggle-label, #wikileft-toggle-label, #wikihead-search-toggle-label {
+ display: none;
+ }
+ #wikihead-searchform {
+ background-color: transparent;
+ float: right;
+ padding: 4px;
+ right: 6px;
+ top: 6px;
+ width: auto;
+ max-width: none;
+ line-height: inherit;
+ overflow: hidden;
+ }
+ #wikilogo {
+ display: inline-block;
+ margin-top: 4px;
+ padding: 6px;
+ }
+ #wikihead, #wikihead input {
+ font-size: 85%;
+ }
+
+ #wikihead-recent-changes-link, #wikihead-searchlink{
+ display: inline;
+ }
+
+ #wikimid {
+ display: table;
+ table-layout:fixed;
+ width: 100%;
+ }
+ #wikisidebar, #wikibody {
+ display: table-cell;
+ }
+
+ #wikisidebar {
+ position: relative;
+ border-right:1px solid #ccc;
+ width:167px;
+ vertical-align: top;
+ padding: 0; margin: 0;
+ overflow: auto;
+ }
+ #wikileft {
+ padding:6px;
+ left: 0;
+ margin: 0 1px 0 0;
+ background: transparent;
+ font-size: 13px;
+ line-height: 17px;
+ }
+ #wikileft a { padding: 0px; }
+ #wikileft li {margin:0px; }
+
+ #wikibody {
+ padding-left: 10px;
+ vertical-align: top;
+ }
+
+ #wikicmds {
+ right: 0px;
+ background-color: transparent;
+ float:right;
+ white-space:nowrap;
+ font-size:13px;
+ }
+ #wikicmds li { display:inline; margin:0px 5px; padding: 0; border: none; }
+
+ #wikifoot {
+ padding-left:178px;
+ }
+
+ #wikifoot.nosidebar {
+ padding-left:10px;
+ }
+ .vspace { margin-top: 20px; }
+ .indent { margin-left:40px; }
+ .outdent { margin-left:40px; text-indent:-40px; }
+
+ .rtl .indent, .rtl.indent, .rtl .outdent, .rtl.outdent {
+ margin-left:0; margin-right: 40px;
+ }
+
+ ul, ol { padding: 0 0 0 40px; }
+ dd { margin-left: 40px; }
+
+ table.horiz td.markup1, table.horiz td.markup2 {
+ display: table-cell; }
+
+ td.markup1, td.markup2 { padding-left:10px; padding-right:10px; }
+ table.vert td.markup1 { border-bottom:1px solid #ccf; }
+ table.horiz td.markup1 { width:23em; border-right:1px solid #ccf; border-bottom:none; }
+ td.markup1 pre { white-space: pre-wrap; }
+
+ div.faq p, div.faq pre { margin-left:40px; }
+ div.faq p.question { margin:15px 0 12px 0; }
+ div.faqtoc div.faq p.question
+ { margin:8px 0 8px 20px; }
+
+
+ select, textarea, input {
+ font-size: 14px;
+ }
+
+ #wikileft-toggle:checked ~ #wikioverlay,
+ #wikihead-search-toggle:checked ~ #wikioverlay,
+ #wikicmds-toggle:checked ~ #wikioverlay {
+ display: none;
+ }
+
+}
+
+/* These affect the printed appearance of the web view (not the separate
+** print view) of pages. The sidebar and action links aren't printed. */
+@media print {
+ body { width:auto; margin:0px; padding:8px; }
+ #wikihead, #wikileft, #wikicmds, .footnav { display:none; }
+ #wikifoot { padding:2px; }
+ *[data-pmtoggle="closed"] + * { display: inherit; }
+ *[data-pmtoggle="closed"]::before { content: "\025BE\00A0"; }
+}
+
+/* Fade-in animation */
+@keyframes fadein {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+@-moz-keyframes fadein {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+@-webkit-keyframes fadein {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+@-ms-keyframes fadein {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+@-o-keyframes fadein {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
--- /dev/null
+++ pub/skins/pmwiki-responsive/skin.js
@@ -0,0 +1,78 @@
+/***********************************************************************
+** skin.js
+** Copyright 2016-2017 Petko Yotov www.pmwiki.org/petko
+**
+** This file is part of PmWiki; you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published
+** by the Free Software Foundation; either version 2 of the License, or
+** (at your option) any later version. See pmwiki.php for full details.
+**
+** This script fixes the styles of some elements when some directives
+** like (:noleft:) are used in a page.
+***********************************************************************/
+(function(){
+ var W = window, D = document;
+ function $(x) { // returns element from id
+ return D.getElementById(x);
+ }
+ function hide(id) { // hides element
+ var el = $(id);
+ if(el) el.style.display = 'none';
+ }
+ function cname(id, c) { // set element className
+ var el = $(id);
+ if(el) el.className = c;
+ }
+ var wsb = $('wikisidebar');
+ if(! wsb) { // (:noleft:)
+ hide('wikileft-toggle-label')
+ cname('wikifoot', 'nosidebar');
+ }
+ else {
+ var sbcontent = wsb.textContent || wsb.innerText;
+ if(! sbcontent.replace(/\s+/, '').length) // empty sidebar, eg. protected
+ hide('wikileft-toggle-label');
+ }
+ var wcmd = $('wikicmds');
+ if(wcmd) { // page actions
+ var pacontent = wcmd.textContent || wcmd.innerText;
+ if(! pacontent.replace(/\s+/, '').length) // empty, eg. protected
+ hide('wikicmds-toggle-label');
+ }
+ if(! $('wikihead-searchform')) // no search form, eg. custom header
+ hide('wikihead-search-toggle-label');
+ var overlay = $('wikioverlay');
+ if(overlay) {
+ overlay.addEventListener('click', function(){
+ $('wikicmds-toggle').checked = false;
+ $('wikihead-search-toggle').checked = false;
+ $('wikileft-toggle').checked = false;
+ });
+ }
+ var scrolltables = function() {
+ // This function "wraps" large tables in a scrollable div
+ // and "unwraps" narrow tables from the scrollable div
+ // allowing table alignement
+ var tables = D.getElementsByTagName('table');
+ for(var i=0; i<tables.length; i++) {
+ var t = tables[i];
+ var pn = t.parentNode;
+ if(pn.className == 'scrollable') {
+ var gp = pn.parentNode;
+ if(t.offsetWidth < gp.offsetWidth) {
+ gp.insertBefore(t, pn);
+ gp.removeChild(pn);
+ }
+ }
+ else {
+ if(t.offsetWidth > pn.offsetWidth) {
+ var nn = D.createElement('div');
+ pn.insertBefore(nn, t).className = 'scrollable';
+ nn.appendChild(t);
+ }
+ }
+ }
+ }
+ W.addEventListener('resize', scrolltables, false);
+ D.addEventListener('DOMContentLoaded', scrolltables, false);
+})();
--- /dev/null
+++ pub/skins/pmwiki-responsive/skin.php
@@ -0,0 +1,89 @@
+<?php if (!defined('PmWiki')) exit();
+/***********************************************************************
+** skin.php
+** Copyright 2016-2019 Petko Yotov www.pmwiki.org/petko
+**
+** This file is part of PmWiki; you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published
+** by the Free Software Foundation; either version 2 of the License, or
+** (at your option) any later version. See pmwiki.php for full details.
+***********************************************************************/
+
+
+global $HTMLStylesFmt, $SkinElementsPages, $DefaultSkinElements, $TableCellAlignFmt,
+ $SearchBoxInputType, $WrapSkinSections, $HideTemplateSections, $EnableTableAttrToStyles;
+
+# Disable inline styles injected by the PmWiki core (we provide these styles in skin.css)
+$styles = explode(' ', 'pmwiki rtl-ltr wikistyles markup simuledit diff urlapprove vardoc PmSortable PmTOC');
+foreach($styles as $style) $HTMLStylesFmt[$style] = '';
+
+# CSS alignment for table cells (valid HTML5)
+SDV($TableCellAlignFmt, " class='%s'");
+
+# For (:searchbox:), valid semantic HTML5
+$SearchBoxInputType = "search";
+
+# remove deprecated "name=" parameter from anchor tags
+if($GLOBALS['VersionNum'] < 2002056) {
+ # we want the skin to also work with older PmWiki versions
+ Markup('[[#','<[[','/(?>\\[\\[#([A-Za-z][-.:\\w]*))\\]\\]/e',
+ "Keep(TrackAnchors('$1') ? '' : \"<a id='$1'></a>\", 'L')");
+}
+else {
+ Markup('[[#','<[[','/(?>\\[\\[#([A-Za-z][-.:\\w]*))\\]\\]/',
+ "MarkupKeepTrackAnchors");
+}
+function MarkupKeepTrackAnchors($m) {
+ return Keep(TrackAnchors($m[1]) ? '' : "<a id='{$m[1]}'></a>", 'L');
+}
+
+# in HTML5 "clear" is a style not an attribute
+Markup('[[<<]]','inline','/\\[\\[<<\\]\\]/',"<br style='clear:both;' />");
+
+# Allow skin header and footer to be written
+# in a wiki page, and use defaults otherwise
+SDVA($WrapSkinSections, array(
+ '#skinheader' => '<header id="wikihead">
+ <div id="wikihead-content">
+ %s
+ </div>
+ </header>',
+ '#skinfooter' => '<footer id="wikifoot">
+ %s
+ </footer>',
+));
+SDVA($HideTemplateSections, array(
+ '#skinheader' => 'PageHeaderFmt',
+ '#skinfooter' => 'PageFooterFmt',
+));
+
+# This function prints a skin element which is written
+# inside a [[#header]]...[[#headerend]] section in Site.SkinElements
+# overriding the existing section from the template file
+
+function SkinFmt($pagename, $args) {
+ global $WrapSkinSections, $HideTemplateSections, $TmplDisplay;
+
+ $args = preg_split('!\\s+!', $args, null, PREG_SPLIT_NO_EMPTY);
+
+ $section = array_shift($args);
+ $hidesection = $HideTemplateSections[$section];
+
+ if(isset($TmplDisplay[$hidesection]) && $TmplDisplay[$hidesection] == 0) {
+ return; # Section was disabled by (:noheader:) or (:nofooter:)
+ }
+
+ foreach($args as $p) {
+ $pn = FmtPageName($p, $pagename);
+ $elm = RetrieveAuthSection($pn, "$section{$section}end");
+ if(!$elm) continue;
+
+ $html = MarkupToHTML($pagename, Qualify($pn, $elm));
+ echo sprintf($WrapSkinSections[$section], $html);
+ SetTmplDisplay($hidesection,0);
+ return;
+ }
+ if(@$DefaultSkinElements[$section])
+ echo FmtPageName($DefaultSkinElements[$section], $pagename);
+}
+
--- /dev/null
+++ pub/skins/pmwiki-responsive/skin.tmpl
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html $HTMLTagAttr>
+<head>
+ <title>$WikiTitle | {$Group} / {$Title} $ActionTitle</title>
+ <!-- Skin Copyright 2017 Petko Yotov www.pmwiki.org/petko ; Skin License GNU GPLv2+ -->
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
+ <link rel='stylesheet' href='$SkinDirUrl/skin.css' type='text/css' />
+ <!--HTMLHeader-->
+</head>
+<body>
+ <div id="bodywrap">
+ <input id="wikileft-toggle" type="checkbox"/>
+ <label for="wikileft-toggle" id="wikileft-toggle-label"></label>
+
+ <input id="wikihead-search-toggle" type="checkbox"/>
+ <label for="wikihead-search-toggle" id="wikihead-search-toggle-label"></label>
+ <input id="wikicmds-toggle" type="checkbox"/>
+
+<!--function:SkinFmt #skinheader {$Group}.SkinElements {$SiteGroup}.SkinElements-->
+<!--PageHeaderFmt-->
+ <header id="wikihead">
+ <div id="wikihead-content">
+ <span id="wikilogo"><a href="{$ScriptUrl}"><img src="$PageLogoUrl" alt="$WikiTitle"/></a></span>
+ <form id="wikihead-searchform" action="{$ScriptUrl}" method="get">
+ <span class="headnav" id="wikihead-recent-changes-link">
+ <a href="{$ScriptUrl}/$[{$Group}/RecentChanges]" accesskey="$[ak_recentchanges]">$[Recent Changes]</a> -
+ </span>
+ <span id="wikihead-searchlink"><a href="{$ScriptUrl}/$[{$SiteGroup}/Search]">$[Search]</a>:</span>
+ <input type="hidden" name="n" value="{$FullName}" />
+ <input type="hidden" name="action" value="search" />
+ <input id="wikihead-searchquery" type="search" name="q" value="" class="inputbox searchbox" placeholder="$[Search]" />
+ <input id="wikihead-searchsubmitbtn" type="submit" class="inputbutton searchbutton" value="$[Go]" />
+ </form>
+ </div><!--wikiheadcontent-->
+ </header><!--wikihead-->
+<!--/PageHeaderFmt-->
+
+ <div id="wikimid">
+<!--PageLeftFmt-->
+ <nav id="wikisidebar">
+ <div id="wikileft">
+ <!--wiki:{$Group}.SideBar {$SiteGroup}.SideBar-->
+ </div>
+ </nav>
+<!--/PageLeftFmt-->
+
+ <div id="wikibody">
+ <main>
+<!--PageActionFmt-->
+ <label for="wikicmds-toggle" id="wikicmds-toggle-label"></label>
+ <div id='wikicmds'><!--wiki:{$Group}.PageActions {$SiteGroup}.PageActions--></div>
+<!--/PageActionFmt-->
+<!--PageTitleFmt-->
+ <div id='wikititle'>
+ <div class='pagegroup'><a href='{$ScriptUrl}/{$Group}'>{$Group}</a> /</div>
+ <h1 class='pagetitle'>{$Title}</h1>
+ </div>
+<!--/PageTitleFmt-->
+
+<!--PageText-->
+ </main>
+
+ </div><!--wikibody-->
+
+ </div><!--wikimid-->
+
+<!--function:SkinFmt #skinfooter {$Group}.SkinElements {$SiteGroup}.SkinElements-->
+<!--PageFooterFmt-->
+ <footer id='wikifoot'>
+ <div id="wikifoot-links" class="footnav">
+ <a rel="nofollow" href="{$PageUrl}?action=edit">$[Edit]</a> -
+ <a rel="nofollow" href="{$PageUrl}?action=diff">$[History]</a> -
+ <a rel="nofollow" href="{$PageUrl}?action=print" target="_blank">$[Print]</a> -
+ <a href="{$ScriptUrl}/$[{$Group}/RecentChanges]">$[Recent Changes]</a> -
+ <a href="{$ScriptUrl}/$[{$SiteGroup}/Search]">$[Search]</a>
+ </div>
+ <div id="wikifoot-lastmod" class="lastmod">All content is under the <a href="index.php?n=License.Ircnow">IRCNow License</a>. $[Page last modified on {$LastModified}]</div>
+ </footer>
+<!--/PageFooterFmt-->
+ <div id="wikioverlay"></div>
+ </div><!--bodywrap-->
+
+ <script type='text/javascript' src='$SkinDirUrl/skin.js' async></script>
+
+<!--HTMLFooter-->
+
+</body>
+</html>
+
--- /dev/null
+++ pub/skins/pmwiki-responsive/xclose.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" stroke-width="8" stroke="#bf0000" viewBox="0 0 60 60"><!-- Copyright 2016 Petko Yotov www.pmwiki.org/petko ; License GNU GPLv2+ --><path d="m56 56l-52-52m0 52l52-52"/></svg>
\ No newline at end of file
--- /dev/null
+++ pub/skins/pmwiki-responsive/xmenu.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" stroke-width="8" fill="none" stroke="#7f7f7f" viewBox="0 0 60 60"><!-- Copyright 2016 Petko Yotov www.pmwiki.org/petko ; License GNU GPLv2+ --><path d="m0 6h60m-60 21h60m-60 21h60"/></svg>
\ No newline at end of file
--- /dev/null
+++ pub/skins/pmwiki-responsive/xsearch.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" stroke="#7c7c7c" fill="none" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="8" viewBox="0 0 60 60"><!-- Copyright 2016 Petko Yotov www.pmwiki.org/petko ; License GNU GPLv2+ --><circle r="20" cy="25" cx="25"/><path d="m56 56l-16-16"/></svg>
\ No newline at end of file
--- /dev/null
+++ pub/skins/pmwiki-responsive/xwrench.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" stroke-linecap="null" stroke-linejoin="null" stroke-width="0" stroke="#7f7f7f" fill="#7f7f7f" viewBox="0 0 60 60"><!-- Copyright 2016 Petko Yotov www.pmwiki.org/petko ; License GNU GPLv2+ --><path d="m51.28 27.324c3.917-4.503 4.704-10.65 2.573-15.818l-7.863 9.17-7.751-1.508-2.569-7.467 7.844-9.149c-5.396-1.359-11.34.28-15.24 4.766-4.113 4.73-4.786 11.278-2.241 16.592l-20.498 23.571c-2.18 2.506-1.915 6.306.591 8.487 2.506 2.178 6.306 1.915 8.487-.591l20.474-23.547c5.636 1.816 12.06.244 16.193-4.508z" transform="rotate(-90 30 30)"/></svg>
\ No newline at end of file
--- /dev/null
+++ pub/skins/print/README
@@ -0,0 +1,20 @@
+This directory contains the files to print pages in PmWiki for ?action=print.
+This is a template just like any other layout skin, except that for
+?action=print PmWiki looks for print.tmpl instead of screen.tmpl.
+
+==>Don't edit these files directly, as you may lose your edits the
+next time you upgrade PmWiki!
+
+Instead, copy the files to another directory in pub/skins/ and edit
+them there. You can then configure PmWiki to use your modified layout
+files by setting $ActionSkin['print'] to the name of your new skin.
+For example, if you copy your custom print skin to pub/skins/custom,
+then you would set
+ $ActionSkin['print'] = 'custom';
+in local/config.php.
+
+The files in this directory:
+ print.tmpl -- the default template for ?action=print
+ print.css -- the print template's css
+ print.php -- loaded when the skin is loaded, it redefines the link
+ formats to a form better suited for printing
--- /dev/null
+++ pub/skins/print/print.css
@@ -0,0 +1,50 @@
+/***********************************************************************
+** print.css
+** Copyright 2004 Patrick R. Michaud (pmichaud@pobox.com)
+** This file is part of PmWiki; you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published
+** by the Free Software Foundation; either version 2 of the License, or
+** (at your option) any later version. See pmwiki.php for full details.
+***********************************************************************/
+
+/***********************************************************************
+** These settings are part of the ?action=print skin. If you want
+** to change these settings, create a new print template and set
+** $PrintTemplateFmt in the config.php file to point to your new
+** printing skin.
+***********************************************************************/
+
+body {
+ width:auto;
+ background-color:white;
+ color:black;
+ font-family:serif;
+}
+
+#printhead {
+ font-family:sans-serif;
+ border-top:3px solid #a0a0a0;
+ border-bottom:5px solid #a0a0a0;
+ margin-bottom:1em;
+}
+#printhead h3 { margin-top:0px; }
+#printhead h1 { margin-bottom:0px; }
+
+#printtitle {
+}
+
+#printfoot {
+ clear:both;
+ margin-top:1em;
+ border-top:5px solid #a0a0a0;
+ font-size:smaller;
+}
+
+
+a:link { color:#444444; font-weight:bold; text-decoration:none; }
+a:visited { color:#444444; font-weight:bold; text-decoration:none; }
+a.wikilink:hover { color: #444444; text-decoration:underline; }
+a.createlink { color:#444444; }
+a.createlink:visited { color:#444444; }
+a.createlink:hover { color:#ff2222; }
+
--- /dev/null
+++ pub/skins/print/print.php
@@ -0,0 +1,29 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2004 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This script defines additional settings needed when the 'print'
+ skin is loaded (usually in response to ?action=print, as controlled
+ by the $ActionSkin['print'] setting. See scripts/skins.php for
+ more details.
+
+ The changes made are:
+ - Redefines the standard layout to a format suitable for printing
+ - Redefines internal links to keep ?action=print
+ - Changes the display of URL and mailto: links
+ - Uses GroupPrintHeader and GroupPrintFooter pages instead
+ of GroupHeader and GroupFooter
+*/
+
+global $LinkPageExistsFmt, $GroupPrintHeaderFmt,
+ $GroupPrintFooterFmt, $GroupHeaderFmt, $GroupFooterFmt;
+
+$LinkPageExistsFmt = "<a class='wikilink' href='\$PageUrl?action=print'>\$LinkText</a>";
+SDV($GroupPrintHeaderFmt,'(:include $Group.GroupPrintHeader basepage={*$FullName}:)(:nl:)');
+SDV($GroupPrintFooterFmt,'(:nl:)(:include $Group.GroupPrintFooter basepage={*$FullName}:)');
+$GroupHeaderFmt = $GroupPrintHeaderFmt;
+$GroupFooterFmt = $GroupPrintFooterFmt;
+
--- /dev/null
+++ pub/skins/print/print.tmpl
@@ -0,0 +1,20 @@
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"><head>
+ <title>$WikiTitle | {$Group} / {$Title}</title>
+ <link rel='stylesheet' href='$SkinDirUrl/print.css' type='text/css' />
+ <!--HTMLHeader-->
+</head>
+<body>
+ <div id='printhead'>
+ <h3>$[From $WikiTitle]</h3>
+ <h1 class='pagename'><a href='$ScriptUrl/{$Group}'>{$Group}: {$Title}</a></h1>
+ </div>
+<!--PageText-->
+ <div id='printfoot'>
+ <div class='from'>$[Retrieved from {$PageUrl}]</div>
+ <div class='lastmod'>All content is under the <a href="index.php?n=License.Ircnow">IRCNow License</a>. $[Page last modified on {$LastModified}]</div></div>
+<!--HTMLFooter-->
+</body>
+</html>
--- /dev/null
+++ scripts/.htaccess
@@ -0,0 +1,19 @@
+# This file is script/.htaccess -- the default distribution contains this
+# file to prevent script/ files from being accessed directly by browsers
+# (this is a potential, albeit very unlikely, security hole).
+#
+# If you alter or replace this file, it will likely be overwritten when
+# you upgrade from one version of PmWiki to another. Be sure to save
+# a copy of your alterations in another location so you can restore them,
+# and you might try changing this file to be read-only to prevent a PmWiki
+# upgrade from overwriting your altered version.
+
+<IfModule !mod_authz_host.c>
+ Order Deny,Allow
+ Deny from all
+</IfModule>
+
+<IfModule mod_authz_host.c>
+ Require all denied
+</IfModule>
+
--- /dev/null
+++ scripts/author.php
@@ -0,0 +1,54 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2004-2017 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This script handles author tracking.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+SDV($AuthorNameChars, "- '\\w\\x80-\\xff");
+SDV($AuthorCookie, $CookiePrefix.'author');
+SDV($AuthorCookieExpires,$Now+60*60*24*30);
+SDV($AuthorCookieDir,'/');
+SDV($AuthorGroup,'Profiles');
+SDV($AuthorRequiredFmt,
+ "<h3 class='wikimessage'>$[An author name is required.]</h3>");
+Markup('[[~','<links','/\\[\\[~(.*?)\\]\\]/',"[[$AuthorGroup/$1]]");
+
+$LogoutCookies[] = $AuthorCookie;
+
+if (!isset($Author)) {
+ if (isset($_POST['author'])) {
+ $x = stripmagic($_POST['author']);
+ pmsetcookie($AuthorCookie, $x, $AuthorCookieExpires, $AuthorCookieDir);
+ } elseif (@$_COOKIE[$AuthorCookie]) {
+ $x = stripmagic(@$_COOKIE[$AuthorCookie]);
+ } else $x = @$AuthId;
+ $Author = PHSC(preg_replace("/[^$AuthorNameChars]/", '', $x),
+ ENT_COMPAT);
+}
+if (!isset($AuthorPage)) $AuthorPage =
+ FmtPageName('$AuthorGroup/$Name', MakePageName("$AuthorGroup.$AuthorGroup", $Author));
+SDV($AuthorLink,($Author) ? "[[~$Author]]" : '?');
+
+if (IsEnabled($EnableAuthorSignature,1)) {
+ SDVA($ROSPatterns, array(
+ '/(?<!~)~~~~(?!~)/' => "[[~$Author]] $CurrentTime",
+ '/(?<!~)~~~(?!~)/' => "[[~$Author]]",
+ ));
+}
+if (IsEnabled($EnablePostAuthorRequired,0))
+ array_unshift($EditFunctions,'RequireAuthor');
+
+## RequireAuthor forces an author to enter a name before posting.
+function RequireAuthor($pagename, &$page, &$new) {
+ global $Author, $MessagesFmt, $AuthorRequiredFmt, $EnablePost;
+ if (!$Author) {
+ $MessagesFmt[] = $AuthorRequiredFmt;
+ $EnablePost = 0;
+ }
+}
--- /dev/null
+++ scripts/authuser.php
@@ -0,0 +1,219 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2005-2019 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ The APR compatible MD5 encryption algorithm in _crypt() below is
+ based on code Copyright 2005 by D. Faure and the File::Passwd
+ PEAR library module by Mike Wallner <mike@php.net>.
+
+ This script enables simple authentication based on username and
+ password combinations. At present this script can authenticate
+ from passwords held in arrays or in .htpasswd-formatted files,
+ but eventually it will support authentication via sources such
+ as LDAP and Active Directory.
+
+ To configure a .htpasswd-formatted file for authentication, do
+ $AuthUser['htpasswd'] = '/path/to/.htpasswd';
+ prior to including this script.
+
+ Individual username/password combinations can also be placed
+ directly in the $AuthUser array, such as:
+ $AuthUser['pmichaud'] = pmcrypt('secret');
+
+ To authenticate against an LDAP server, put the url for
+ the server in $AuthUser['ldap'], as in:
+ $AuthUser['ldap'] = 'ldap://ldap.example.com/ou=People,o=example?uid';
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+# let Site.AuthForm know that we're doing user-based authorization
+$EnableAuthUser = 1;
+
+if (@$_POST['authid'])
+ AuthUserId($pagename, stripmagic(@$_POST['authid']),
+ stripmagic(@$_POST['authpw']));
+else SessionAuth($pagename);
+
+function AuthUserId($pagename, $id, $pw=NULL) {
+ global $AuthUser, $AuthUserPageFmt, $AuthUserFunctions,
+ $AuthId, $MessagesFmt, $AuthUserPat;
+
+ $auth = array();
+ foreach((array)$AuthUser as $k=>$v) $auth[$k] = (array)$v;
+ $authid = '';
+
+ # load information from SiteAdmin.AuthUser (or page in $AuthUserPageFmt)
+ SDV($AuthUserPageFmt, '$SiteAdminGroup.AuthUser');
+ SDVA($AuthUserFunctions, array(
+ 'htpasswd' => 'AuthUserHtPasswd',
+ 'ldap' => 'AuthUserLDAP',
+# 'mysql' => 'AuthUserMySQL',
+ $id => 'AuthUserConfig'));
+
+ SDV($AuthUserPat, "/^\\s*([@\\w][^\\s:]*):(.*)/m");
+ foreach ( (array)$AuthUserPageFmt as $aupn) {
+ $pn = FmtPageName($aupn, $pagename);
+ $apage = ReadPage($pn, READPAGE_CURRENT);
+ if ($apage && preg_match_all($AuthUserPat,
+ $apage['text'], $matches, PREG_SET_ORDER)) {
+ foreach($matches as $m) {
+ if (!preg_match_all('/\\bldaps?:\\S+|[^\\s,]+/', $m[2], $v))
+ continue;
+ if ($m[1][0] == '@')
+ foreach($v[0] as $g) $auth[$g][] = $m[1];
+ else $auth[$m[1]] = array_merge((array)@$auth[$m[1]], $v[0]);
+ }
+ }
+ }
+
+ if (func_num_args()==2) $authid = $id;
+ else
+ foreach($AuthUserFunctions as $k => $fn)
+ if (@$auth[$k] && $fn($pagename, $id, $pw, $auth[$k], $authlist))
+ { $authid = $id; break; }
+
+ if (!$authid) { $GLOBALS['InvalidLogin'] = 1; return; }
+ if (!isset($AuthId)) $AuthId = $authid;
+ $authlist["id:$authid"] = 1;
+ $authlist["id:-$authid"] = -1;
+ foreach(preg_grep('/^@/', (array)@$auth[$authid]) as $g)
+ $authlist[$g] = 1;
+ foreach(preg_grep('/^@/', (array)@$auth['*']) as $g)
+ $authlist[$g] = 1;
+ foreach(preg_grep('/^@/', array_keys($auth)) as $g) # useless? PITS:01201
+ if (in_array($authid, $auth[$g])) $authlist[$g] = 1;
+ if ($auth['htgroup']) {
+ foreach(AuthUserHtGroup($pagename, $id, $pw, $auth['htgroup']) as $g)
+ $authlist["@$g"] = 1;
+ }
+ foreach(preg_grep('/^@/', (array)@$auth["-$authid"]) as $g)
+ unset($authlist[$g]);
+ SessionAuth($pagename, array('authid' => $authid, 'authlist' => $authlist));
+}
+
+
+function AuthUserConfig($pagename, $id, $pw, $pwlist) {
+ foreach ((array)$pwlist as $chal)
+ if (_crypt($pw, $chal) == $chal) return true;
+ return false;
+}
+
+
+function AuthUserHtPasswd($pagename, $id, $pw, $pwlist) {
+ foreach ((array)$pwlist as $f) {
+ $fp = fopen($f, "r"); if (!$fp) continue;
+ while ($x = fgets($fp, 1024)) {
+ $x = rtrim($x);
+ @list($i, $c, $r) = explode(':', $x, 3);
+ if ($i == $id && _crypt($pw, $c) == $c) { fclose($fp); return true; }
+ }
+ fclose($fp);
+ }
+ return false;
+}
+
+
+function AuthUserHtGroup($pagename, $id, $pw, $pwlist) {
+ $groups = array();
+ foreach ((array)$pwlist as $f) {
+ $fp = fopen($f, 'r'); if (!$fp) continue;
+ while ($x = fgets($fp, 4096)) {
+ if (preg_match('/^(\\w[^\\s:]+)\\s*:(.*)$/', trim($x), $match)) {
+ $glist = preg_split('/[\\s,]+/', $match[2], -1, PREG_SPLIT_NO_EMPTY);
+ if (in_array($id, $glist)) $groups[$match[1]] = 1;
+ }
+ }
+ fclose($fp);
+ }
+ return array_keys($groups);
+}
+
+
+function AuthUserLDAP($pagename, $id, $pw, $pwlist) {
+ global $AuthLDAPBindDN, $AuthLDAPBindPassword, $AuthLDAPReferrals;
+ if (!$pw) return false;
+ if (!function_exists('ldap_connect'))
+ Abort('authuser: LDAP authentication requires PHP ldap functions','ldapfn');
+ foreach ((array)$pwlist as $ldap) {
+ if (!preg_match('!(ldaps?://[^/]+)/(.*)$!', $ldap, $match))
+ continue;
+ ## connect to the LDAP server
+ list($z, $url, $path) = $match;
+ $ds = ldap_connect($url);
+ ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
+ if(isset($AuthLDAPReferrals)) # *NOT* IsEnabled
+ ldap_set_option($ds, LDAP_OPT_REFERRALS, $AuthLDAPReferrals);
+ ## For Active Directory, don't specify a path and we simply
+ ## attempt to bind with the username and password directly
+ if (!$path && @ldap_bind($ds, $id, $pw)) { ldap_close($ds); return true; }
+ ## Otherwise, we use Apache-style urls for LDAP authentication
+ ## Split the path into its search components
+ list($basedn, $attr, $sub, $filter) = explode('?', $path);
+ if (!$attr) $attr = 'uid';
+ if (!$sub) $sub = 'one';
+ if (!$filter) $filter = '(objectClass=*)';
+ $binddn = @$AuthLDAPBindDN;
+ $bindpw = @$AuthLDAPBindPassword;
+ if (ldap_bind($ds, $binddn, $bindpw)) {
+ ## Search for the appropriate uid
+ $fn = ($sub == 'sub') ? 'ldap_search' : 'ldap_list';
+ $sr = $fn($ds, $basedn, "(&$filter($attr=$id))", array($attr));
+ $x = ldap_get_entries($ds, $sr);
+ ## If we find a unique id, bind to it for success
+ if ($x['count'] == 1) {
+ $dn = $x[0]['dn'];
+ if (@ldap_bind($ds, $dn, $pw)) { ldap_close($ds); return true; }
+ }
+ }
+ ldap_close($ds);
+ }
+ return false;
+}
+
+
+# The _crypt function provides support for SHA1 encrypted passwords
+# (keyed by '{SHA}') and Apache MD5 encrypted passwords (keyed by
+# '$apr1$'); otherwise it just calls PHP's crypt() for the rest.
+# The APR MD5 encryption code was contributed by D. Faure.
+
+function _crypt($plain, $salt=null) {
+ if (strncmp($salt, '{SHA}', 5) == 0)
+ return '{SHA}'.base64_encode(pack('H*', sha1($plain)));
+ if (strncmp($salt, '$apr1$', 6) == 0) {
+ preg_match('/^\\$apr1\\$([^$]+)/', $salt, $match);
+ $salt = $match[1];
+ $length = strlen($plain);
+ $context = $plain . '$apr1$' . $salt;
+ $binary = pack('H32', md5($plain . $salt . $plain));
+ for($i = $length; $i > 0; $i -= 16)
+ $context .= substr($binary, 0, min(16, $i));
+ for($i = $length; $i > 0; $i >>= 1)
+ $context .= ($i & 1) ? chr(0) : $plain[0];
+ $binary = pack('H32', md5($context));
+ for($i = 0; $i < 1000; $i++) {
+ $new = ($i & 1) ? $plain : $binary;
+ if ($i % 3) $new .= $salt;
+ if ($i % 7) $new .= $plain;
+ $new .= ($i & 1) ? $binary : $plain;
+ $binary = pack('H32', md5($new));
+ }
+ $q = '';
+ for ($i = 0; $i < 5; $i++) {
+ $k = $i + 6;
+ $j = $i + 12;
+ if ($j == 16) $j = 5;
+ $q = $binary[$i].$binary[$k].$binary[$j] . $q;
+ }
+ $q = chr(0).chr(0).$binary[11] . $q;
+ $q = strtr(strrev(substr(base64_encode($q), 2)),
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
+ './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz');
+ return "\$apr1\$$salt\$$q";
+ }
+ if (md5($plain) == $salt) return $salt;
+ return pmcrypt($plain, $salt);
+}
--- /dev/null
+++ scripts/blocklist.php
@@ -0,0 +1,242 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2006-2019 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This script adds blocklisting capabilities to PmWiki, and can
+ be enabled by simply setting the following in local/config.php:
+
+ $EnableBlocklist = 1;
+
+ With $EnableBlocklist set to 1, this module will search through
+ the SiteAdmin.Blocklist page, as well as any other pages given by
+ the $Blocklist pages variable, looking for lines of the
+ form "block:some phrase" or "block:/regex/", with "some phrase"
+ and "/regex/" indicating things to be excluded from any
+ posting to the site.
+
+ In addition, if a page contains IP addresses of the form
+ "a.b.c.d" or "a.b.c.*", then any posts coming from hosts
+ matching the address will be blocked.
+
+ There is also an "unblock:..." form, which removes an entry
+ from the blocklist. This is useful for removing specific
+ block items in wikifarms and with automatically downloaded
+ blocklists (below).
+
+ The script also has the capability of automatically downloading
+ blocklists from other sources, such as chongqed.org and
+ and the MoinMaster blocklist. These are configured using
+ the $BlocklistDownload array. An $EnableBlocklist value
+ of at least 10 configures PmWiki to automatically download
+ these external blocklists and refresh them daily.
+
+ More information about blocklists is available in the
+ PmWiki.Blocklist page.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+
+## Some recipes do page updates outside of the built-in posting
+## cycle, so $EnableBlocklistImmediate is used to determine if
+## we need to catch these. Currently this defaults to enabled,
+## but at some point we may change the default to disabled.
+if (IsEnabled($EnableBlocklistImmediate, 1)) {
+ SDVA($BlocklistActions, array('comment' => 1));
+ $ptext = implode(' ', @$_POST);
+ if ($ptext && @$BlocklistActions[$action]) {
+ Blocklist($pagename, $ptext);
+ if (!$EnablePost) {
+ unset($_POST['post']);
+ unset($_POST['postattr']);
+ unset($_POST['postedit']);
+ }
+ }
+}
+
+
+## If $EnableBlocklist is set to 10 or higher, then arrange to
+## periodically download the "chongqed" and "moinmaster" blacklists.
+if ($EnableBlocklist >= 10) {
+# SDVA($BlocklistDownload['SiteAdmin.Blocklist-Chongqed'], array(
+# 'url' => 'http://blacklist.chongqed.org/',
+# 'format' => 'regex'));
+ SDVA($BlocklistDownload['SiteAdmin.Blocklist-MoinMaster'], array(
+ 'url' => 'http://moinmo.in/BadContent?action=raw',
+ 'format' => 'regex'));
+}
+
+
+## CheckBlocklist is inserted into $EditFunctions, to automatically
+## check for blocks on anything being posted through the normal
+## "update a page cycle"
+array_unshift($EditFunctions, 'CheckBlocklist');
+function CheckBlocklist($pagename, &$page, &$new) {
+ StopWatch("CheckBlocklist: begin $pagename");
+ $ptext = implode(' ', @$_POST);
+ if (@$ptext) Blocklist($pagename, $ptext);
+ StopWatch("CheckBlocklist: end $pagename");
+}
+
+
+## Blocklist is the function that does all of the work of
+## checking for reasons to block a posting. It reads
+## the available blocklist pages ($BlocklistPages) and
+## builds an array of strings and regular expressiongs to
+## be checked against the page; if any are found, then
+## posting is blocked (via $EnablePost=0). The function
+## also checks the REMOTE_ADDR against any blocked IP addresses.
+function Blocklist($pagename, $text) {
+ global $BlocklistPages, $BlockedMessagesFmt, $BlocklistDownload,
+ $BlocklistDownloadRefresh, $Now, $EnablePost, $WhyBlockedFmt,
+ $MessagesFmt, $BlocklistMessageFmt, $EnableWhyBlocked, $IsBlocked;
+
+ StopWatch("Blocklist: begin $pagename");
+
+ $BlocklistDownload = (array)@$BlocklistDownload;
+ SDV($BlocklistPages,
+ array_merge(array('$SiteAdminGroup.Blocklist',
+ '$SiteAdminGroup.Blocklist-Farm'),
+ array_keys($BlocklistDownload)));
+ SDV($BlocklistMessageFmt, "<h3 class='wikimessage'>$[This post has been blocked by the administrator]</h3>");
+ SDVA($BlockedMessagesFmt, array(
+ 'ip' => '$[Address blocked from posting]: ',
+ 'text' => '$[Text blocked from posting]: '));
+ SDV($BlocklistDownloadRefresh, 86400);
+
+ ## Loop over all blocklist pages
+ foreach((array)$BlocklistPages as $b) {
+
+ ## load the current blocklist page
+ $pn = FmtPageName($b, $pagename);
+ $page = ReadPage($pn, READPAGE_CURRENT);
+ if (!$page) continue;
+
+ ## if the page being checked is a blocklist page, stop blocking
+ if ($pagename == $pn) return;
+
+ ## If the blocklist page is managed by automatic download,
+ ## schedule any new downloads here
+ if (@$BlocklistDownload[$pn]) {
+ $bd = &$BlocklistDownload[$pn];
+ SDVA($bd, array(
+ 'refresh' => $BlocklistDownloadRefresh,
+ 'url' => "http://www.pmwiki.org/blocklists/$pn" ));
+ if (!@$page['text'] || $page['time'] < $Now - $bd['refresh'])
+ register_shutdown_function('BlocklistDownload', $pn, getcwd());
+ }
+
+ ## If the blocklist is simply a list of regexes to be matched, load
+ ## them into $terms['block'] and continue to the next blocklist page.
+ ## Some regexes from remote sites aren't well-formed, so we have
+ ## to escape any slashes that aren't already escaped.
+ if (strpos(@$page['text'], 'blocklist-format: regex') !==false) {
+ if (preg_match_all('/^([^\\s#].+)/m', $page['text'], $match))
+ foreach($match[0] as $m) {
+ $m = preg_replace('#(?<!\\\\)/#', '\\/', trim($m));
+ $terms['block'][] = "/$m/";
+ }
+ continue;
+ }
+
+ ## Treat the page as a pmwiki-format blocklist page, with
+ ## IP addresses and "block:"-style declarations. First, see
+ ## if we need to block the author based on a.b.c.d or a.b.c.*
+ ## IP addresses.
+ $ip = preg_quote($_SERVER['REMOTE_ADDR']);
+ $ip = preg_replace('/\\d+$/', '($0\\b|\\*)', $ip);
+ if (preg_match("/\\b$ip/", @$page['text'], $match)) {
+ $EnablePost = 0;
+ $IsBlocked = 1;
+ $WhyBlockedFmt[] = $BlockedMessagesFmt['ip'] . $match[0];
+ }
+
+ ## Now we'll load any "block:" or "unblock:" specifications
+ ## from the page text.
+ if (preg_match_all('/(un)?(?:block|regex):(.*)/', @$page['text'],
+ $match, PREG_SET_ORDER))
+ foreach($match as $m) $terms[$m[1].'block'][] = trim($m[2]);
+ }
+
+ ## okay, we've loaded all of the terms, now subtract any 'unblock'
+ ## terms from the block set.
+ StopWatch("Blocklist: diff unblock");
+ $blockterms = array_diff((array)@$terms['block'], (array)@$terms['unblock']);
+
+ ## go through each of the remaining blockterms and see if it matches the
+ ## text -- if so, disable posting and add a message to $WhyBlockedFmt.
+ StopWatch('Blocklist: blockterms (count='.count($blockterms).')');
+ $itext = strtolower($text);
+ foreach($blockterms as $b) {
+ if ($b[0] == '/') {
+ if (!preg_match($b, $text)) continue;
+ } else if (strpos($itext, strtolower($b)) === false) continue;
+ $EnablePost = 0;
+ $IsBlocked = 1;
+ $WhyBlockedFmt[] = $BlockedMessagesFmt['text'] . $b;
+ }
+ StopWatch('Blocklist: blockterms done');
+
+ ## If we came across any reasons to block, let's provide a message
+ ## to the author that it was blocked. If $EnableWhyBlocked is set,
+ ## we'll even tell the author why. :-)
+ if (@$WhyBlockedFmt) {
+ $MessagesFmt[] = $BlocklistMessageFmt;
+ if (IsEnabled($EnableWhyBlocked, 0))
+ foreach((array)$WhyBlockedFmt as $why)
+ $MessagesFmt[] = "<pre class='blocklistmessage'>$why</pre>\n";
+ }
+ StopWatch("Blocklist: end $pagename");
+}
+
+
+## BlocklistDownload() handles retrieving blocklists from
+## external sources into PmWiki pages. If it's able to
+## download an updated list, it uses that; otherwise it leaves
+## any existing list alone.
+function BlocklistDownload($pagename, $dir = '') {
+ global $BlocklistDownloadFmt, $BlocklistDownload, $FmtV;
+
+ if ($dir) { flush(); chdir($dir); }
+ SDV($BlocklistDownloadFmt, "
+ [@
+## blocklist-note: NOTE: This page is automatically generated by blocklist.php
+## blocklist-note: NOTE: Any edits to this page may be lost!
+## blocklist-url: \$BlocklistDownloadUrl
+## blocklist-when: \$CurrentTimeISO
+# blocklist-format: \$BlocklistFormat
+\$BlocklistData
+ @]
+");
+
+ ## get the existing blocklist page
+ $bd = &$BlocklistDownload[$pagename];
+ $page = ReadPage($pagename, READPAGE_CURRENT);
+
+ ## try to retrieve the remote data
+ $blocklistdata = @file($bd['url']);
+
+ ## if we didn't get it, and we don't already have text, save a
+ ## note in the page so we know what happened
+ if (!$blocklistdata && !@$page['text']) {
+ $auf = ini_get('allow_url_fopen');
+ $blocklistdata = "#### Unable to download blocklist (allow_url_fopen=$auf)";
+ }
+
+ ## if we have some new text to save, let's format it and save it
+ if ($blocklistdata) {
+ $blocklistdata = implode('', (array)$blocklistdata);
+ $blocklistdata = preg_replace('/^##blocklist.*/m', '', $blocklistdata);
+ $FmtV['$BlocklistData'] = $blocklistdata;
+ $FmtV['$BlocklistDownloadUrl'] = $bd['url'];
+ $FmtV['$BlocklistFormat'] = $bd['format'];
+ $page['text'] = FmtPageName($BlocklistDownloadFmt, $pagename);
+ SDV($page['passwdread'], '@lock');
+ }
+
+ ## save our updated(?) blocklist page
+ WritePage($pagename, $page);
+}
--- /dev/null
+++ scripts/caches.php
@@ -0,0 +1,67 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2006-2020 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+## Browser cache-control. If this is a cacheable action (e.g., browse,
+## diff), then set the Last-Modified header to the time the site was
+## last modified. If the browser has provided us with a matching
+## If-Modified-Since request header, we can return 304 Not Modified.
+SDV($LastModFile,"$WorkDir/.lastmod");
+if (!$LastModFile) return;
+
+$LastModTime = @filemtime($LastModFile);
+foreach(get_included_files() as $f)
+ { $v = @filemtime($f); if ($v > $LastModTime) $LastModTime = $v; }
+
+if (@$EnableIMSCaching) {
+ SDV($IMSCookie, $CookiePrefix.'imstime');
+ SDV($IMSCookieExpires, $Now + 60*60*24*30);
+ SDV($IMSInvalidators, array('authpw', 'author'));
+ $LogoutCookies[] = $IMSCookie;
+
+ if ($IMSCookie) {
+ $IMSTime = @$_COOKIE[$IMSCookie];
+ if ($IMSTime < $LastModTime
+ || array_intersect($IMSInvalidators, array_keys($_POST))) {
+ $IMSTime = $Now;
+ pmsetcookie($IMSCookie, $IMSTime, $IMSCookieExpires, '/');
+ }
+ } else $IMSTime = $LastModTime;
+
+ if (in_array($action, (array)$CacheActions)) {
+ $HTTPLastMod = gmdate('D, d M Y H:i:s \G\M\T',$IMSTime);
+ $HTTPHeaders[] = "Cache-Control: no-cache";
+ $HTTPHeaders[] = "Last-Modified: $HTTPLastMod";
+ if (@$_SERVER['HTTP_IF_MODIFIED_SINCE']==$HTTPLastMod) {
+ header("HTTP/1.0 304 Not Modified");
+ header("Cache-Control: no-cache");
+ header("Expires: ");
+ header("Last-Modified: $HTTPLastMod");
+ exit();
+ }
+ }
+}
+
+if ($NoHTMLCache
+ || !@$PageCacheDir
+ || count($_POST) > 0
+ || count($_GET) > 1
+ || (count($_GET) == 1 && !@$_GET['n'])) { $NoHTMLCache |= 1; return; }
+
+mkdirp($PageCacheDir);
+if (!file_exists("$PageCacheDir/.htaccess")
+ && $fp = @fopen("$PageCacheDir/.htaccess", "w"))
+ { fwrite($fp, $DenyHtaccessContent); fclose($fp); }
+
+SDV($PageCacheFileFmt, "%s/%s,cache");
+SDV($PageCacheFile, sprintf($PageCacheFileFmt, $PageCacheDir, $pagename));
+
+if (file_exists($PageCacheFile) && @filemtime($PageCacheFile) < $LastModTime)
+ @unlink($PageCacheFile);
+
--- /dev/null
+++ scripts/creole.php
@@ -0,0 +1,82 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2007-2017 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This script adds Creole v0.4 markup (http://www.wikicreole.org/)
+ to PmWiki. To activate this script, simply add the following into
+ a local customization file:
+
+ include_once('scripts/creole.php');
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+## **strong**
+Markup('**', 'inline',
+ '/^\\*\\*(?>(.+?)\\*\\*)(?!\\S)|(?<!^)\\*\\*(.+?)\\*\\*/',
+ '<strong>$1$2</strong>');
+
+## //emphasized//
+Markup('//', 'inline',
+ '/(?<!http:|https:|ftp:)\\/\\/(.*?)\\/\\//',
+ '<em>$1</em>');
+
+## == Headings ==
+Markup('^=', 'block',
+ '/^(={1,6})\\s?(.*?)(\\s*=*\\s*)$/',
+ "MarkupCreole");
+
+## Line breaks
+Markup('\\\\', 'inline', '/\\\\\\\\/', '<br />');
+
+## Preformatted
+Markup('^{{{', '[=',
+ "/^\\{\\{\\{\n(.*?\n)\\}\\}\\}[^\\S\n]*\n/sm",
+ "MarkupCreole");
+Markup('{{{', '>{{{',
+ '/\\{\\{\\{(.*?)\\}\\}\\}/s',
+ "MarkupCreole");
+
+## Tables
+Markup('|-table', '>^||',
+ '/^\\|(.*)$/',
+ "MarkupCreole");
+
+## Images
+Markup('{{', 'inline',
+ '/\\{\\{(?>(\\L))([^|\\]]*)(?:\\|\\s*(.*?)\\s*)?\\}\\}/',
+ "MarkupCreole");
+
+function MarkupCreole($m) {
+ extract($GLOBALS["MarkupToHTML"]); # get $pagename, $markupid
+ switch ($markupid) {
+ case '^=':
+ return '<:block,1><h'.strlen($m[1]).'>'.$m[2].'</h'.strlen($m[1]).'>';
+ case '^{{{':
+ return Keep('<pre class="escaped">'.$m[1].'</pre>');
+ case '{{{':
+ return Keep('<code class="escaped">'.$m[1].'</code>');
+ case '|-table':
+ return FormatTableRow($m[0], '\\|');
+ case '{{':
+ return Keep($GLOBALS['LinkFunctions'][$m[1]]($pagename, $m[1], $m[2], $m[3],
+ $m[1].$m[2], $GLOBALS['ImgTagFmt']),'L');
+ }
+}
+
+## GUIButtons
+SDVA($GUIButtons, array(
+ 'em' => array(100, "//", "//", '$[Emphasized]',
+ '$GUIButtonDirUrlFmt/em.gif"$[Emphasized (italic)]"',
+ '$[ak_em]'),
+ 'strong' => array(110, "**", "**", '$[Strong]',
+ '$GUIButtonDirUrlFmt/strong.gif"$[Strong (bold)]"',
+ '$[ak_strong]'),
+ 'h2' => array(400, '\\n== ', ' ==\\n', '$[Heading]',
+ '$GUIButtonDirUrlFmt/h.gif"$[Heading]"'),
+
+ ));
+
--- /dev/null
+++ scripts/crypt.php
@@ -0,0 +1,43 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2002-2015 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This script defines ?action=crypt, providing help for WikiAdministrators
+ to set up site-wide passwords in the installation.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+SDV($HandleActions['crypt'],'HandleCrypt');
+SDV($ActionTitleFmt['crypt'],'| $[Password encryption]');
+
+function HandleCrypt($pagename, $auth='read') {
+ global $ScriptUrl,$HTMLStartFmt,$HTMLEndFmt;
+ PrintFmt($pagename,$HTMLStartFmt);
+ $passwd = stripmagic(@$_POST["passwd"]);
+ echo FmtPageName(
+ "<form action='{\$ScriptUrl}' method='POST'><p>
+ Enter password to encrypt:
+ <input type='text' name='passwd' value='"
+ . PHSC($passwd, ENT_QUOTES) ."' />
+ <input type='submit' />
+ <input type='hidden' name='n' value='{\$FullName}' />
+ <input type='hidden' name='action' value='crypt' /></p></form>",
+ $pagename);
+ if ($passwd) {
+ $crypt = pmcrypt($passwd);
+ echo "<p class='vspace'>Encrypted password = $crypt</p>";
+ echo "<p class='vspace'>To set a site-wide password, insert the line below
+ in your <i>config.php</i> file, <br />replacing <tt>'type'</tt> with
+ one of <tt>'admin'</tt>, <tt>'read'</tt>, <tt>'edit'</tt>,
+ or <tt>'attr'</tt>. <br />See <a
+ href='$ScriptUrl?n=PmWiki.PasswordsAdmin'>PasswordsAdmin</a> for more
+ details.</p>
+ <pre class='vspace'> \$DefaultPasswords['type']='$crypt';</pre>";
+ }
+ PrintFmt($pagename,$HTMLEndFmt);
+}
+
--- /dev/null
+++ scripts/diag.php
@@ -0,0 +1,172 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2003-2015 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This file adds "?action=diag" and "?action=phpinfo" actions to PmWiki.
+ This produces lots of diagnostic output that may be helpful to the
+ software authors when debugging PmWiki or other scripts.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+
+if ($action=='diag') {
+ @ini_set('track_errors','1');
+ @ini_set('display_errors', '1');
+ @session_start();
+ header('Content-type: text/plain');
+ print_r($GLOBALS);
+ exit();
+}
+
+if ($action=='phpinfo') { phpinfo(); exit(); }
+
+function Ruleset() {
+ global $MarkupTable;
+ $out = '';
+ $dbg = 0;
+ BuildMarkupRules();
+ foreach($MarkupTable as $id=>$m) {
+ $out .= sprintf("%-16s %-16s %-16s %s\n",$id,@$m['cmd'],@$m['seq'], @$m['dbg']);
+ if (@$m['dbg']) $dbg++;
+ }
+ if ($dbg) $out .= "
+[!] Markup rules possibly incompatible with PHP 5.5 or newer.
+ Please contact the recipe maintainer for update
+ or see www.pmwiki.org/wiki/PmWiki/CustomMarkup";
+ return $out;
+}
+
+$HandleActions['ruleset'] = 'HandleRuleset';
+
+function HandleRuleset($pagename) {
+ header("Content-type: text/plain");
+ print Ruleset();
+}
+
+function StopWatchHTML($pagename, $print = 0) {
+ global $StopWatch;
+ StopWatch('now');
+ $l = strlen(count($StopWatch));
+ $out = '<pre>';
+ foreach((array)$StopWatch as $i => $x)
+ $out .= sprintf("%{$l}d: %s\n", $i, $x);
+ $out .= '</pre>';
+ if (is_array($StopWatch)) array_pop($StopWatch);
+ if ($print) print $out;
+ return $out;
+}
+
+### From Cookbook:RecipeCheck
+/* Copyright 2007-2019 Patrick R. Michaud (pmichaud@pobox.com)
+
+ This recipe adds ?action=recipecheck to a site. When activated,
+ ?action=recipecheck fetches the current list of Cookbook recipes
+ and their versions from pmwiki.org. It then compares this list
+ with the versions of any installed recipes on the local site
+ and reports any differences.
+
+ By default the recipe restricts ?action=recipecheck to admins.
+
+ Note that not all recipes currently follow PmWiki's
+ recipecheck standard (feel free to report these to the pmwiki-users
+ mailing list).
+
+ * 2007-04-17: Added suggestions by Feral
+ - explicit black text
+ - skip non-php files and directories
+ * 2019-11-28: Added to scripts/diag.php by Petko
+*/
+SDV($HandleActions['recipecheck'], 'HandleRecipeCheckCore');
+SDV($HandleAuth['recipecheck'], 'admin');
+SDV($ActionTitleFmt['recipecheck'], '| $[Recipe Check]');
+
+SDV($WikiStyleApply['tr'], 'tr');
+SDV($HTMLStylesFmt['recipecheck'], '
+ table.recipecheck tr.ok { color:black; background-color:#ccffcc; }
+ table.recipecheck tr.check { color:black; background-color:#ffffcc; }
+ table.recipecheck { border:1px solid #cccccc; padding:4px; }
+');
+
+SDV($RecipeListUrl, 'http://www.pmwiki.org/pmwiki/recipelist');
+
+function HandleRecipeCheckCore($pagename, $auth = 'admin') {
+ global $RecipeListUrl, $Version, $RecipeInfo,
+ $RecipeCheckFmt, $PageStartFmt, $PageEndFmt;
+ $page = RetrieveAuthPage($pagename, $auth, true, READPAGE_CURRENT);
+ if (!$page) Abort('?admin access required');
+ $cvinfo = GetRecipeListCore($RecipeListUrl);
+ if (!$cvinfo) {
+ $msg = "Unable to retrieve cookbook data from $RecipeListUrl\n";
+ $allow_url_fopen = ini_get('allow_url_fopen');
+ if (!$allow_url_fopen) $msg .= "
+ <br /><br />It appears that your PHP environment isn't allowing
+ the recipelist to be downloaded from pmwiki.org
+ (allow_url_fopen = $allow_url_fopen).";
+ Abort($msg);
+ }
+ $rinfo['PmWiki:Upgrades'] = $Version;
+ ScanRecipeInfoCore('cookbook', $cvinfo);
+ foreach((array)$RecipeInfo as $r => $v) {
+ if (!@$v['Version']) continue;
+ $r = preg_replace('/^(?!PmWiki:)(Cookbook[.:])?/', 'Cookbook:', $r);
+ $rinfo[$r] = $v['Version'];
+ }
+ $markup = "!!Recipe status for {\$PageUrl}\n".RecipeTableCore($rinfo, $cvinfo);
+ $html = MarkupToHTML($pagename, $markup);
+ SDV($RecipeCheckFmt, array(&$PageStartFmt, $html, &$PageEndFmt));
+ PrintFmt($pagename, $RecipeCheckFmt);
+}
+
+
+function GetRecipeListCore($list) {
+ $cvinfo = array();
+ $fp = fopen($list, 'r');
+ while ($fp && !feof($fp)) {
+ $line = fgets($fp, 1024);
+ if ($line[0] == '#') continue;
+ if (preg_match('/(\\S+) +(.*)/', $line, $match))
+ $cvinfo[$match[1]] = trim($match[2]);
+ }
+ fclose($fp);
+ return $cvinfo;
+}
+
+
+function ScanRecipeInfoCore($dlistfmt, $cvinfo = NULL) {
+ global $RecipeInfo;
+ foreach((array)$dlistfmt as $dir) {
+ $dfp = @opendir($dir); if (!$dfp) continue;
+ while ( ($name = readdir($dfp)) !== false) {
+ if ($name[0] == '.') continue;
+ if (!preg_match('/\\.php/i', $name)) continue;
+ $text = implode('', @file("$dir/$name"));
+ if (preg_match("/^\\s*\\\$RecipeInfo\\['(.*?)'\\]\\['Version'\\]\\s*=\\s*'(.*?)'\\s*;/m", $text, $match))
+ SDV($RecipeInfo[$match[1]]['Version'], $match[2]);
+ if (preg_match("/^\\s*SDV\\(\\s*\\\$RecipeInfo\\['(.*?)'\\]\\['Version'\\]\\s*,\\s*'(.*?)'\\s*\\)\\s*\\;/m", $text, $match))
+ SDV($RecipeInfo[$match[1]]['Version'], $match[2]);
+ if (@$cvinfo[$name]) {
+ $r = preg_replace('/^.*:/', '', $cvinfo[$name]);
+ SDV($RecipeInfo[$r]['Version'], "unknown ($name)");
+ }
+ }
+ closedir($dfp);
+ }
+}
+
+
+function RecipeTableCore($rinfo, $cvinfo) {
+ $fmt = "||%-40s ||%-20s ||%-20s ||\n";
+ $out = "||class=recipecheck cellpadding=0 cellspacing=0 width=600\n";
+ $out .= sprintf($fmt, '!Recipe', '!local', '!pmwiki.org');
+ foreach($rinfo as $r => $lv) {
+ $cv = @$cvinfo[$r];
+ $style = ($lv == $cv) ? 'ok' : 'check';
+ $out .= sprintf($fmt, "%apply=tr $style%[[$r]]", $lv, $cv);
+ }
+ return $out;
+}
+
--- /dev/null
+++ scripts/draft.php
@@ -0,0 +1,75 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2006-2015 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+SDV($DraftSuffix, '-Draft');
+if ($DraftSuffix)
+ SDV($SearchPatterns['normal']['draft'], "!$DraftSuffix\$!");
+
+## set up a 'publish' authorization level, defaulting to 'edit' authorization
+SDV($DefaultPasswords['publish'], '');
+SDV($AuthCascade['publish'], 'edit');
+SDV($FmtPV['$PasswdPublish'], 'PasswdVar($pn, "publish")');
+if ($AuthCascade['attr'] == 'edit') $AuthCascade['attr'] = 'publish';
+
+## Add a 'publish' page attribute if desired
+if (IsEnabled($EnablePublishAttr, 0))
+ SDV($PageAttributes['passwdpublish'], '$[Set new publish password:]');
+
+
+$basename = preg_replace("/$DraftSuffix\$/", '', $pagename);
+## if no -Draft page, switch to $basename
+if (!PageExists($pagename) && PageExists($basename)) $pagename = $basename;
+
+## The section below handles specialized EditForm pages and handler.
+## We don't bother to load it if we're not editing.
+SDV($DraftActionsPattern, 'edit');
+if (! preg_match("/($DraftActionsPattern)/", $action)) return;
+
+## set edit form button labels to reflect draft prompts
+SDVA($InputTags['e_savebutton'], array('value' => ' '.XL('Publish').' '));
+SDVA($InputTags['e_saveeditbutton'], array('value' => ' '.XL('Save draft and edit').' '));
+SDVA($InputTags['e_savedraftbutton'], array(
+ ':html' => "<input type='submit' \$InputFormArgs />",
+ 'name' => 'postdraft', 'value' => ' '.XL('Save draft').' ',
+ 'accesskey' => XL('ak_savedraft')));
+
+## with drafts enabled, the 'post' operation requires 'publish' permissions
+if ($_POST['post'] && $HandleAuth['edit'] == 'edit')
+ $HandleAuth['edit'] = 'publish';
+
+## disable the 'publish' button if not authorized to publish
+if (!CondAuth($basename, 'publish'))
+ SDVA($InputTags['e_savebutton'], array('disabled' => 'disabled'));
+
+## add the draft handler into $EditFunctions
+array_unshift($EditFunctions, 'EditDraft');
+function EditDraft(&$pagename, &$page, &$new) {
+ global $WikiDir, $DraftSuffix, $DeleteKeyPattern, $EnableDraftAtomicDiff,
+ $DraftRecentChangesFmt, $RecentChangesFmt, $Now;
+ SDV($DeleteKeyPattern, "^\\s*delete\\s*$");
+ $basename = preg_replace("/$DraftSuffix\$/", '', $pagename);
+ $draftname = $basename . $DraftSuffix;
+ if ($_POST['postdraft'] || $_POST['postedit']) $pagename = $draftname;
+ else if ($_POST['post'] && !preg_match("/$DeleteKeyPattern/", $new['text'])) {
+ $pagename = $basename;
+ if (IsEnabled($EnableDraftAtomicDiff, 0)) {
+ $page = ReadPage($basename);
+ foreach($new as $k=>$v) # delete draft history
+ if (preg_match('/:\\d+(:\\d+:)?$/', $k) && ! preg_match("/:$Now(:\\d+:)?$/", $k)) unset($new[$k]);
+ unset($new['rev']);
+ SDVA($new, $page);
+ }
+ $WikiDir->delete($draftname);
+ }
+ else if (PageExists($draftname) && $pagename != $draftname)
+ { Redirect($draftname, '$PageUrl?action=edit'); exit(); }
+ if ($pagename == $draftname && isset($DraftRecentChangesFmt))
+ $RecentChangesFmt = $DraftRecentChangesFmt;
+}
--- /dev/null
+++ scripts/feeds.php
@@ -0,0 +1,546 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2005-2019 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This script provides a number of syndication feed and xml-based
+ metadata options to PmWiki, including Atom, RSS 2.0, RSS 1.0 (RDF),
+ and the Dublin Core Metadata extensions. This module is typically
+ activated from a local configuration file via a line such as
+
+ if ($action == 'atom') include_once("$FarmD/scripts/feeds.php");
+ if ($action == 'dc') include_once("$FarmD/scripts/feeds.php");
+
+ When enabled, ?action=atom, ?action=rss, and ?action=rdf produce
+ syndication feeds based on any wikitrail contained in the page,
+ or, for Category pages, on the pages in the category. The feeds
+ are generated using pagelist, thus one can include parameters such
+ as count=, list=, order=, etc. in the url to adjust the feed output.
+
+ ?action=dc will normally generate Dublin Core Metadata for the
+ current page only, but placing a group=, trail=, or link= argument
+ in the url causes it to generate metadata for all pages in the
+ associated group, trail, or backlink.
+
+ There are a large number of customizations available, most of which
+ are controlled by the $FeedFmt array. Elements $FeedFmt look like
+
+ $FeedFmt['atom']['feed']['rights'] = 'All Rights Reserved';
+
+ where the first index corresponds to the action (?action=atom),
+ the second index indicates a per-feed or per-item element, and
+ the third index is the name of the element being generated.
+ The above setting would therefore generate a
+ "<rights>All Rights Reserved</rights>" in the feed for
+ ?action=atom. If the value of an entry begins with a '<',
+ then feeds.php doesn't automatically add the tag around it.
+ Elements can also be callable functions which are called to
+ generate the appropriate output.
+
+ For example, to set the RSS 2.0 <author> element to the
+ value of the last author to modify a page, one can set
+ (in local/config.php):
+
+ $FeedFmt['rss']['item']['author'] = '$LastModifiedBy';
+
+ To use the RSS 2.0 <description> element to contain the
+ change summary of the most recent edit, set
+
+ $FeedFmt['rss']['item']['description'] = '$LastModifiedSummary';
+
+ Feeds.php can also be combined with attachments to support
+ podcasting via ?action=rss. Any page such as "PageName"
+ that has an mp3 attachment with the same name as the page
+ ("PageName.mp3") will have an appropriate <enclosure> element
+ in the feed output. The set of allowed attachments can be
+ extended using the $RSSEnclosureFmt array:
+
+ $RSSEnclosureFmt = array('{$Name}.mp3', '{$Name}.mp4');
+
+ References:
+ http://www.atomenabled.org/developers/syndication/
+ http://dublincore.org/documents/dcmes-xml/
+ http://en.wikipedia.org/wiki/Podcasting
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+## Settings for ?action=atom
+SDVA($FeedFmt['atom']['feed'], array(
+ '_header' => 'Content-type: text/xml; charset="$Charset"',
+ '_start' => '<?xml version="1.0" encoding="$Charset"?'.'>
+<feed xmlns="http://www.w3.org/2005/Atom">'."\n",
+ '_end' => "</feed>\n",
+ 'title' => '$WikiTitle',
+ 'link' => '<link rel="self" href="{$PageUrl}?action=atom" />',
+ 'id' => '{$PageUrl}?action=atom',
+ 'updated' => '$FeedISOTime',
+ 'author' => "<author><name>$WikiTitle</name></author>\n",
+ 'generator' => '$Version',
+ 'logo' => '$PageLogoUrl'));
+SDVA($FeedFmt['atom']['item'], array(
+ '_start' => "<entry>\n",
+ 'id' => '{$PageUrl}',
+ 'title' => '{$Title}',
+ 'updated' => '$ItemISOTime',
+ 'link' => "<link rel=\"alternate\" href=\"{\$PageUrl}\" />\n",
+ 'author' => "<author><name>{\$LastModifiedBy}</name></author>\n",
+ 'summary' => '{$Description}',
+ 'category' => "<category term=\"\$Category\" />\n",
+ '_end' => "</entry>\n"));
+
+## Settings for ?action=dc
+SDVA($FeedFmt['dc']['feed'], array(
+ '_header' => 'Content-type: text/xml; charset="$Charset"',
+ '_start' => '<?xml version="1.0" encoding="$Charset"?'.'>
+<!DOCTYPE rdf:RDF PUBLIC "-//DUBLIN CORE//DCMES DTD 2002/07/31//EN"
+ "http://dublincore.org/documents/2002/07/31/dcmes-xml/dcmes-xml-dtd.dtd">
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n",
+ '_end' => "</rdf:RDF>\n"));
+SDVA($FeedFmt['dc']['item'], array(
+ '_start' => "<rdf:Description rdf:about=\"{\$PageUrl}\">\n",
+ 'dc:title' => '{$Title}',
+ 'dc:identifier' => '{$PageUrl}',
+ 'dc:date' => '$ItemISOTime',
+ 'dc:type' => 'Text',
+ 'dc:format' => 'text/html',
+ 'dc:description' => '{$Description}',
+ 'dc:subject' => "<dc:subject>\$Category</dc:subject>\n",
+ 'dc:publisher' => '$WikiTitle',
+ 'dc:author' => '{$LastModifiedBy}',
+ '_end' => "</rdf:Description>\n"));
+
+## RSS 2.0 settings for ?action=rss
+SDVA($FeedFmt['rss']['feed'], array(
+ '_header' => 'Content-type: text/xml; charset="$Charset"',
+ '_start' => '<?xml version="1.0" encoding="$Charset"?'.'>
+<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
+<channel>'."\n",
+ '_end' => "</channel>\n</rss>\n",
+ 'title' => '$WikiTitle | {$Group} / {$Title}',
+ 'link' => '{$PageUrl}?action=rss',
+ 'description' => '{$Group}.{$Title}',
+ 'lastBuildDate' => '$FeedRSSTime'));
+SDVA($FeedFmt['rss']['item'], array(
+ '_start' => "<item>\n",
+ '_end' => "</item>\n",
+ 'title' => '{$Group} / {$Title}',
+ 'link' => '{$PageUrl}',
+ 'description' => '{$Description}',
+ 'dc:contributor' => '{$LastModifiedBy}',
+ 'dc:date' => '$ItemISOTime',
+ 'pubDate' => '$ItemRSSTime',
+ 'enclosure' => 'RSSEnclosure'));
+
+## RDF 1.0, for ?action=rdf
+SDVA($FeedFmt['rdf']['feed'], array(
+ '_header' => 'Content-type: text/xml; charset="$Charset"',
+ '_start' => '<?xml version="1.0" encoding="$Charset"?'.'>
+<rdf:RDF xmlns="http://purl.org/rss/1.0/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <channel rdf:about="{$PageUrl}?action=rdf">'."\n",
+ 'title' => '$WikiTitle | {$Group} / {$Title}',
+ 'link' => '{$PageUrl}?action=rdf',
+ 'description' => '{$Group}.{$Title}',
+ 'dc:date' => '$FeedISOTime',
+ 'items' => "<items>\n<rdf:Seq>\n\$FeedRDFSeq</rdf:Seq>\n</items>\n",
+ '_items' => "</channel>\n",
+ '_end' => "</rdf:RDF>\n"));
+SDVA($FeedFmt['rdf']['item'], array(
+ '_start' => "<item rdf:about=\"{\$PageUrl}\">\n",
+ '_end' => "</item>\n",
+ 'title' => '$WikiTitle | {$Group} / {$Title}',
+ 'link' => '{$PageUrl}',
+ 'description' => '{$Description}',
+ 'dc:date' => '$ItemISOTime'));
+
+foreach(array_keys($FeedFmt) as $k) {
+ SDV($HandleActions[$k], 'HandleFeed');
+ SDV($HandleAuth[$k], 'read');
+}
+
+function HandleFeed($pagename, $auth = 'read') {
+ global $FeedFmt, $action, $PCache, $FmtV, $TimeISOZFmt, $RSSTimeFmt,
+ $FeedPageListOpt, $FeedCategoryOpt, $FeedTrailOpt,
+ $FeedDescPatterns, $CategoryGroup, $EntitiesTable;
+ SDV($RSSTimeFmt, 'D, d M Y H:i:s \G\M\T');
+ SDV($FeedDescPatterns,
+ array('/<[^>]*$/' => ' ', '/\\w+$/' => '', '/<[^>]+>/' => ''));
+ $FeedPageListOpt = (array)@$FeedPageListOpt;
+ SDVA($FeedCategoryOpt, array('link' => $pagename));
+ SDVA($FeedTrailOpt, array('trail' => $pagename, 'count' => 10));
+
+ $f = $FeedFmt[$action];
+ $page = RetrieveAuthPage($pagename, $auth, true, READPAGE_CURRENT);
+ if (!$page) Abort("?cannot generate feed");
+ $feedtime = $page['time'];
+
+ # determine list of pages to display
+ if (@($_REQUEST['trail'] || $_REQUEST['group'] || $_REQUEST['link']
+ || $_REQUEST['name']))
+ $opt = $FeedPageListOpt;
+ else if (preg_match("/^$CategoryGroup\\./", $pagename))
+ $opt = $FeedCategoryOpt;
+ else if ($action != 'dc') $opt = $FeedTrailOpt;
+ else {
+ PCache($pagename, $page);
+ $pagelist = array($pagename);
+ }
+ if (!@$pagelist) {
+ $opt = array_merge($opt, @$_REQUEST);
+ $pagelist = MakePageList($pagename, $opt, 0);
+ }
+
+ # process list of pages in feed
+ $rdfseq = '';
+ $pl = array();
+ foreach($pagelist as $pn) {
+ if (!PageExists($pn)) continue;
+ if (!isset($PCache[$pn]['time']))
+ { $page = ReadPage($pn, READPAGE_CURRENT); PCache($pn, $page); }
+ $pc = & $PCache[$pn];
+ $pl[] = $pn;
+ $rdfseq .= FmtPageName("<rdf:li resource=\"{\$PageUrl}\" />\n", $pn);
+ if ($pc['time'] > $feedtime) $feedtime = $pc['time'];
+ if (@$opt['count'] && count($pl) >= $opt['count']) break;
+ }
+ $pagelist = $pl;
+
+ $FmtV['$FeedRDFSeq'] = $rdfseq;
+ $FmtV['$FeedISOTime'] = gmstrftime($TimeISOZFmt, $feedtime);
+ $FmtV['$FeedRSSTime'] = gmdate($RSSTimeFmt, $feedtime);
+ # format start of feed
+ $out = FmtPageName($f['feed']['_start'], $pagename);
+
+ # format feed elements
+ foreach($f['feed'] as $k => $v) {
+ if ($k[0] == '_' || !$v) continue;
+ $x = FmtPageName($v, $pagename);
+ if (!$x) continue;
+ $out .= ($v[0] == '<') ? $x : "<$k>$x</$k>\n";
+ }
+
+ # format items in feed
+ if (@$f['feed']['_items'])
+ $out .= FmtPageName($f['feed']['_items'], $pagename);
+ foreach($pagelist as $pn) {
+ $page = &$PCache[$pn];
+ $FmtV['$ItemDesc'] = @$page['description'];
+ $FmtV['$ItemISOTime'] = gmstrftime($TimeISOZFmt, $page['time']);
+ $FmtV['$ItemRSSTime'] = gmdate($RSSTimeFmt, $page['time']);
+
+ $out .= FmtPageName($f['item']['_start'], $pn);
+ foreach((array)@$f['item'] as $k => $v) {
+ if ($k[0] == '_' || !$v) continue;
+ if (is_callable($v)) { $out .= $v($pn, $page, $k); continue; }
+ if (strpos($v, '$LastModifiedBy') !== false && !@$page['author'])
+ continue;
+ if (strpos($v, '$Category') !== false) {
+ if (preg_match_all("/(?<=^|,)$CategoryGroup\\.([^,]+)/",
+ @$page['targets'], $match)) {
+ foreach($match[1] as $c) {
+ $FmtV['$Category'] = $c;
+ $out .= FmtPageName($v, $pn);
+ }
+ }
+ continue;
+ }
+ $x = FmtPageName($v, $pn);
+ if (!$x) continue;
+ $out .= ($v[0] == '<') ? $x : "<$k>$x</$k>\n";
+ }
+ $out .= FmtPageName($f['item']['_end'], $pn);
+ }
+ $out .= FmtPageName($f['feed']['_end'], $pagename);
+ foreach((array)@$f['feed']['_header'] as $fmt)
+ header(FmtPageName($fmt, $pagename));
+ print str_replace(array_keys($EntitiesTable),
+ array_values($EntitiesTable), $out);
+}
+
+## RSSEnclosure is called in ?action=rss to generate <enclosure>
+## tags for any pages that have an attached "PageName.mp3" file.
+## The set of attachments to enclose is given by $RSSEnclosureFmt.
+function RSSEnclosure($pagename, &$page, $k) {
+ global $RSSEnclosureFmt, $UploadFileFmt, $UploadExts;
+ if (!function_exists('MakeUploadName')) return '';
+ SDV($RSSEnclosureFmt, array('{$Name}.mp3'));
+ $encl = '';
+ foreach((array)$RSSEnclosureFmt as $fmt) {
+ $path = FmtPageName($fmt, $pagename);
+ $upname = MakeUploadName($pagename, $path);
+ $filepath = FmtPageName("$UploadFileFmt/$upname", $pagename);
+ if (file_exists($filepath)) {
+ $length = filesize($filepath);
+ $type = @$UploadExts[preg_replace('/.*\\./', '', $filepath)];
+ $url = LinkUpload($pagename, 'Attach:', $path, '', '', '$LinkUrl');
+ $encl .= "<$k url='$url' length='$length' type='$type' />";
+ }
+ }
+ return $encl;
+}
+
+## Since most feeds don't understand html character entities, we
+## convert the common ones to their numeric form here.
+SDVA($EntitiesTable, array(
+ # entities defined in "http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent"
+ ' ' => ' ',
+ '¡' => '¡',
+ '¢' => '¢',
+ '£' => '£',
+ '¤' => '¤',
+ '¥' => '¥',
+ '¦' => '¦',
+ '§' => '§',
+ '¨' => '¨',
+ '©' => '©',
+ 'ª' => 'ª',
+ '«' => '«',
+ '¬' => '¬',
+ '­' => '­',
+ '®' => '®',
+ '¯' => '¯',
+ '°' => '°',
+ '±' => '±',
+ '²' => '²',
+ '³' => '³',
+ '´' => '´',
+ 'µ' => 'µ',
+ '¶' => '¶',
+ '·' => '·',
+ '¸' => '¸',
+ '¹' => '¹',
+ 'º' => 'º',
+ '»' => '»',
+ '¼' => '¼',
+ '½' => '½',
+ '¾' => '¾',
+ '¿' => '¿',
+ 'À' => 'À',
+ 'Á' => 'Á',
+ 'Â' => 'Â',
+ 'Ã' => 'Ã',
+ 'Ä' => 'Ä',
+ 'Å' => 'Å',
+ 'Æ' => 'Æ',
+ 'Ç' => 'Ç',
+ 'È' => 'È',
+ 'É' => 'É',
+ 'Ê' => 'Ê',
+ 'Ë' => 'Ë',
+ 'Ì' => 'Ì',
+ 'Í' => 'Í',
+ 'Î' => 'Î',
+ 'Ï' => 'Ï',
+ 'Ð' => 'Ð',
+ 'Ñ' => 'Ñ',
+ 'Ò' => 'Ò',
+ 'Ó' => 'Ó',
+ 'Ô' => 'Ô',
+ 'Õ' => 'Õ',
+ 'Ö' => 'Ö',
+ '×' => '×',
+ 'Ø' => 'Ø',
+ 'Ù' => 'Ù',
+ 'Ú' => 'Ú',
+ 'Û' => 'Û',
+ 'Ü' => 'Ü',
+ 'Ý' => 'Ý',
+ 'Þ' => 'Þ',
+ 'ß' => 'ß',
+ 'à' => 'à',
+ 'á' => 'á',
+ 'â' => 'â',
+ 'ã' => 'ã',
+ 'ä' => 'ä',
+ 'å' => 'å',
+ 'æ' => 'æ',
+ 'ç' => 'ç',
+ 'è' => 'è',
+ 'é' => 'é',
+ 'ê' => 'ê',
+ 'ë' => 'ë',
+ 'ì' => 'ì',
+ 'í' => 'í',
+ 'î' => 'î',
+ 'ï' => 'ï',
+ 'ð' => 'ð',
+ 'ñ' => 'ñ',
+ 'ò' => 'ò',
+ 'ó' => 'ó',
+ 'ô' => 'ô',
+ 'õ' => 'õ',
+ 'ö' => 'ö',
+ '÷' => '÷',
+ 'ø' => 'ø',
+ 'ù' => 'ù',
+ 'ú' => 'ú',
+ 'û' => 'û',
+ 'ü' => 'ü',
+ 'ý' => 'ý',
+ 'þ' => 'þ',
+ 'ÿ' => 'ÿ',
+ # entities defined in "http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent"
+ '"' => '"',
+ #'&' => '&#38;',
+ #'<' => '&#60;',
+ #'>' => '>',
+ ''' => ''',
+ 'Œ' => 'Œ',
+ 'œ' => 'œ',
+ 'Š' => 'Š',
+ 'š' => 'š',
+ 'Ÿ' => 'Ÿ',
+ 'ˆ' => 'ˆ',
+ '˜' => '˜',
+ ' ' => ' ',
+ ' ' => ' ',
+ ' ' => ' ',
+ '‌' => '‌',
+ '‍' => '‍',
+ '‎' => '‎',
+ '‏' => '‏',
+ '–' => '–',
+ '—' => '—',
+ '‘' => '‘',
+ '’' => '’',
+ '‚' => '‚',
+ '“' => '“',
+ '”' => '”',
+ '„' => '„',
+ '†' => '†',
+ '‡' => '‡',
+ '‰' => '‰',
+ '‹' => '‹',
+ '›' => '›',
+ '€' => '€',
+ # entities defined in "http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent"
+ 'ƒ' => 'ƒ',
+ 'Α' => 'Α',
+ 'Β' => 'Β',
+ 'Γ' => 'Γ',
+ 'Δ' => 'Δ',
+ 'Ε' => 'Ε',
+ 'Ζ' => 'Ζ',
+ 'Η' => 'Η',
+ 'Θ' => 'Θ',
+ 'Ι' => 'Ι',
+ 'Κ' => 'Κ',
+ 'Λ' => 'Λ',
+ 'Μ' => 'Μ',
+ 'Ν' => 'Ν',
+ 'Ξ' => 'Ξ',
+ 'Ο' => 'Ο',
+ 'Π' => 'Π',
+ 'Ρ' => 'Ρ',
+ 'Σ' => 'Σ',
+ 'Τ' => 'Τ',
+ 'Υ' => 'Υ',
+ 'Φ' => 'Φ',
+ 'Χ' => 'Χ',
+ 'Ψ' => 'Ψ',
+ 'Ω' => 'Ω',
+ 'α' => 'α',
+ 'β' => 'β',
+ 'γ' => 'γ',
+ 'δ' => 'δ',
+ 'ε' => 'ε',
+ 'ζ' => 'ζ',
+ 'η' => 'η',
+ 'θ' => 'θ',
+ 'ι' => 'ι',
+ 'κ' => 'κ',
+ 'λ' => 'λ',
+ 'μ' => 'μ',
+ 'ν' => 'ν',
+ 'ξ' => 'ξ',
+ 'ο' => 'ο',
+ 'π' => 'π',
+ 'ρ' => 'ρ',
+ 'ς' => 'ς',
+ 'σ' => 'σ',
+ 'τ' => 'τ',
+ 'υ' => 'υ',
+ 'φ' => 'φ',
+ 'χ' => 'χ',
+ 'ψ' => 'ψ',
+ 'ω' => 'ω',
+ 'ϑ' => 'ϑ',
+ 'ϒ' => 'ϒ',
+ 'ϖ' => 'ϖ',
+ '•' => '•',
+ '…' => '…',
+ '′' => '′',
+ '″' => '″',
+ '‾' => '‾',
+ '⁄' => '⁄',
+ '℘' => '℘',
+ 'ℑ' => 'ℑ',
+ 'ℜ' => 'ℜ',
+ '™' => '™',
+ 'ℵ' => 'ℵ',
+ '←' => '←',
+ '↑' => '↑',
+ '→' => '→',
+ '↓' => '↓',
+ '↔' => '↔',
+ '↵' => '↵',
+ '⇐' => '⇐',
+ '⇑' => '⇑',
+ '⇒' => '⇒',
+ '⇓' => '⇓',
+ '⇔' => '⇔',
+ '∀' => '∀',
+ '∂' => '∂',
+ '∃' => '∃',
+ '∅' => '∅',
+ '∇' => '∇',
+ '∈' => '∈',
+ '∉' => '∉',
+ '∋' => '∋',
+ '∏' => '∏',
+ '∑' => '∑',
+ '−' => '−',
+ '∗' => '∗',
+ '√' => '√',
+ '∝' => '∝',
+ '∞' => '∞',
+ '∠' => '∠',
+ '∧' => '∧',
+ '∨' => '∨',
+ '∩' => '∩',
+ '∪' => '∪',
+ '∫' => '∫',
+ '∴' => '∴',
+ '∼' => '∼',
+ '≅' => '≅',
+ '≈' => '≈',
+ '≠' => '≠',
+ '≡' => '≡',
+ '≤' => '≤',
+ '≥' => '≥',
+ '⊂' => '⊂',
+ '⊃' => '⊃',
+ '⊄' => '⊄',
+ '⊆' => '⊆',
+ '⊇' => '⊇',
+ '⊕' => '⊕',
+ '⊗' => '⊗',
+ '⊥' => '⊥',
+ '⋅' => '⋅',
+ '⌈' => '⌈',
+ '⌉' => '⌉',
+ '⌊' => '⌊',
+ '⌋' => '⌋',
+ '⟨' => '〈',
+ '⟩' => '〉',
+ '◊' => '◊',
+ '♠' => '♠',
+ '♣' => '♣',
+ '♥' => '♥',
+ '♦' => '♦'));
+
--- /dev/null
+++ scripts/forms.php
@@ -0,0 +1,423 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2005-2019 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+# $InputAttrs are the attributes we allow in output tags
+SDV($InputAttrs, array('name', 'value', 'id', 'class', 'rows', 'cols',
+ 'size', 'maxlength', 'action', 'method', 'accesskey', 'tabindex', 'multiple',
+ 'checked', 'disabled', 'readonly', 'enctype', 'src', 'alt', 'title', 'list',
+ 'required', 'placeholder', 'autocomplete', 'min', 'max', 'step', 'pattern',
+ 'role', 'aria-label', 'aria-labelledby', 'aria-describedby',
+ 'aria-expanded', 'aria-pressed', 'aria-current', 'aria-hidden',
+ 'formnovalidate'
+ ));
+
+# Set up formatting for text, submit, hidden, radio, etc. types
+foreach(array('text', 'submit', 'hidden', 'password', 'reset', 'file',
+ 'image', 'email', 'url', 'tel', 'number', 'search', 'date', 'button') as $t)
+ SDV($InputTags[$t][':html'], "<input type='$t' \$InputFormArgs />");
+
+foreach(array('text', 'email', 'url', 'tel', 'number', 'search', 'date') as $t)
+ SDV($InputTags[$t]['class'], "inputbox");
+
+foreach(array('submit', 'button', 'reset') as $t)
+ SDV($InputTags[$t]['class'], "inputbutton");
+
+foreach(array('radio', 'checkbox') as $t)
+ SDVA($InputTags[$t], array(
+ ':html' => "<input type='$t' \$InputFormArgs />\$InputFormLabel",
+ ':args' => array('name', 'value', 'label'),
+ ':checked' => 'checked'));
+
+# (:input form:)
+SDVA($InputTags['form'], array(
+ ':args' => array('action', 'method'),
+ ':html' => "<form \$InputFormArgs>",
+ 'method' => 'post'));
+
+# (:input end:)
+SDV($InputTags['end'][':html'], '</form>');
+
+# (:input textarea:)
+SDVA($InputTags['textarea'], array(
+ ':content' => array('value'),
+ ':attr' => array_diff($InputAttrs, array('value')),
+ ':html' => "<textarea \$InputFormArgs>\$InputFormContent</textarea>"));
+
+# (:input image:)
+SDV($InputTags['image'][':args'], array('name', 'src', 'alt'));
+
+# (:input select:)
+SDVA($InputTags['select-option'], array(
+ ':args' => array('name', 'value', 'label'),
+ ':content' => array('label', 'value', 'name'),
+ ':attr' => array('value', 'selected'),
+ ':checked' => 'selected',
+ ':html' => "<option \$InputFormArgs>\$InputFormContent</option>"));
+SDVA($InputTags['select'], array(
+ 'class' => 'inputbox',
+ ':html' => "<select \$InputSelectArgs>\$InputSelectOptions</select>"));
+
+# (:input datalist:)
+SDVA($InputTags['datalist-option'], array(
+ ':args' => array('id', 'value'),
+ ':attr' => array('value'),
+ ':html' => "<option \$InputFormArgs>"));
+SDVA($InputTags['datalist'], array(
+ ':html' => "<datalist \$InputSelectArgs>\$InputSelectOptions</datalist>"));
+
+# (:input defaults?:)
+SDVA($InputTags['default'], array(':fn' => 'InputDefault'));
+SDVA($InputTags['defaults'], array(':fn' => 'InputDefault'));
+
+## (:input ...:) directives
+Markup('input', 'directives',
+ '/\\(:input\\s+(\\w+)(.*?):\\)/i',
+ "MarkupInputForms");
+
+## (:input select:) has its own markup processing
+Markup('input-select', '<input',
+ '/\\(:input\\s+select\\s.*?:\\)(?:\\s*\\(:input\\s+select\\s.*?:\\))*/i',
+ "MarkupInputForms");
+
+## (:input datalist:) has its own markup processing
+Markup('input-datalist', '<input',
+ '/\\(:input\\s+datalist\\s.*?:\\)(?:\\s*\\(:input\\s+datalist\\s.*?:\\))*/i',
+ "MarkupInputForms");
+
+function MarkupInputForms($m) {
+ extract($GLOBALS["MarkupToHTML"]); # get $pagename, $markupid
+ switch ($markupid) {
+ case 'input':
+ return InputMarkup($pagename, $m[1], $m[2]);
+ case 'input-select':
+ return InputSelect($pagename, 'select', $m[0]);
+ case 'input-datalist':
+ return InputSelect($pagename, 'datalist', $m[0]);
+ case 'e_preview':
+ return isset($GLOBALS['FmtV']['$PreviewText'])
+ ? Keep($GLOBALS['FmtV']['$PreviewText']): '';
+ }
+}
+
+## The 'input+sp' rule combines multiple (:input select ... :)
+## into a single markup line (to avoid split line effects)
+Markup('input+sp', '<split',
+ '/(\\(:input\\s+(select|datalist)\\s(?>.*?:\\)))\\s+(?=\\(:input\\s)/', '$1');
+
+SDV($InputFocusFmt,
+ "<script language='javascript' type='text/javascript'><!--
+ document.getElementById('\$InputFocusId').focus();//--></script>");
+
+## InputToHTML performs standard processing on (:input ...:) arguments,
+## and returns the formatted HTML string.
+function InputToHTML($pagename, $type, $args, &$opt) {
+ global $InputTags, $InputAttrs, $InputValues, $FmtV, $KeepToken,
+ $InputFocusLevel, $InputFocusId, $InputFocusFmt, $HTMLFooterFmt,
+ $EnableInputDataAttr;
+ if (!@$InputTags[$type]) return "(:input $type $args:)";
+ ## get input arguments
+ if (!is_array($args)) $args = ParseArgs($args, '(?>([\\w-]+)[:=])');
+ ## convert any positional arguments to named arguments
+ $posnames = @$InputTags[$type][':args'];
+ if (!$posnames) $posnames = array('name', 'value');
+ while (count($posnames) > 0 && @count(@$args['']) > 0) {
+ $n = array_shift($posnames);
+ if (!isset($args[$n])) $args[$n] = array_shift($args['']);
+ }
+ ## merge defaults for input type with arguments
+ $opt = array_merge($InputTags[$type], $args);
+ ## www.w3.org/TR/html4/types
+ if (isset($opt['id'])) $opt['id'] = preg_replace('/[^-A-Za-z0-9:_.]+/', '_', $opt['id']);
+ ## convert any remaining positional args to flags
+ foreach ((array)@$opt[''] as $a)
+ { $a = strtolower($a); if (!isset($opt[$a])) $opt[$a] = $a; }
+ if (isset($opt['name'])) {
+ $opt['name'] = preg_replace('/^\\$:/', 'ptv_', @$opt['name']);
+ $opt['name'] = preg_replace('/[^-A-Za-z0-9:_.\\[\\]]+/', '_', $opt['name']);
+ $name = $opt['name'];
+ ## set control values from $InputValues array
+ ## radio, checkbox, select, etc. require a flag of some sort,
+ ## others just set 'value'
+ if (isset($InputValues[$name])) {
+ $checked = @$opt[':checked'];
+ if ($checked) {
+ $opt[$checked] = in_array(@$opt['value'], (array)$InputValues[$name])
+ ? $checked : false;
+ } else if (!isset($opt['value'])) $opt['value'] = $InputValues[$name];
+ }
+ }
+ ## build $InputFormContent
+ $FmtV['$InputFormContent'] = '';
+ foreach((array)@$opt[':content'] as $a)
+ if (isset($opt[$a])) {
+ $FmtV['$InputFormContent'] = is_array($opt[$a]) ? $opt[$a][0]: $opt[$a];
+ break;
+ }
+ ## hash and store any "secure" values
+ if (@$opt['secure'] == '#') $opt['secure'] = rand();
+ if (@$opt['secure'] > '') {
+ $md5 = md5($opt['secure'] . $opt['value']);
+ @session_start();
+ $_SESSION['forms'][$md5] = $opt['value'];
+ $opt['value'] = $md5;
+ }
+ ## labels for checkbox and radio
+ $FmtV['$InputFormLabel'] = '';
+ if (isset($opt['label']) && strpos($InputTags[$type][':html'], '$InputFormLabel')!==false) {
+ static $labelcnt = 0;
+ if (!isset($opt['id'])) $opt['id'] = "lbl_". (++$labelcnt);
+ $lbtitle = isset($opt['title']) ? " title='".str_replace("'", ''', $opt['title'])."'" : '';
+ $FmtV['$InputFormLabel'] = " <label for=\"{$opt['id']}\"$lbtitle>{$opt['label']}</label> ";
+ }
+ ## handle focus=# option
+ $focus = @$opt['focus'];
+ if (isset($focus)
+ && (!isset($InputFocusLevel) || $focus < $InputFocusLevel)) {
+ if (!isset($opt['id'])) $opt['id'] = "wikifocus$focus";
+ $InputFocusLevel = $focus;
+ $InputFocusId = $opt['id'];
+ $HTMLFooterFmt['inputfocus'] = $InputFocusFmt;
+ }
+ ## build $InputFormArgs from $opt
+ $attrlist = (isset($opt[':attr'])) ? $opt[':attr'] : $InputAttrs;
+ if (IsEnabled($EnableInputDataAttr, 1)) {
+ $dataattr = preg_grep('/^data-[-a-z]+$/', array_keys($opt));
+ $attrlist = array_merge($attrlist, $dataattr);
+ }
+ $attr = array();
+ foreach ($attrlist as $a) {
+ if (!isset($opt[$a]) || $opt[$a]===false) continue;
+ if (is_array($opt[$a])) $opt[$a] = $opt[$a][0];
+ if (strpos($opt[$a], $KeepToken)!== false) # multiline textarea/hidden fields
+ $opt[$a] = Keep(str_replace("'", ''', MarkupRestore($opt[$a]) ));
+ $attr[] = "$a='".str_replace("'", ''', $opt[$a])."'";
+ }
+ $FmtV['$InputFormArgs'] = implode(' ', $attr);
+ return FmtPageName($opt[':html'], $pagename);
+}
+
+
+## InputMarkup handles the (:input ...:) directive. It either
+## calls any function given by the :fn element of the corresponding
+## tag, or else just returns the result of InputToHTML().
+function InputMarkup($pagename, $type, $args) {
+ global $InputTags;
+ $fn = @$InputTags[$type][':fn'];
+ if ($fn) return $fn($pagename, $type, $args);
+ return Keep(InputToHTML($pagename, $type, $args, $opt));
+}
+
+
+## (:input default:) directive.
+function InputDefault($pagename, $type, $args) {
+ global $InputValues, $PageTextVarPatterns, $PCache;
+ $args = ParseArgs($args);
+ $args[''] = (array)@$args[''];
+ $name = (isset($args['name'])) ? $args['name'] : array_shift($args['']);
+ $name = preg_replace('/^\\$:/', 'ptv_', $name);
+ $value = (isset($args['value'])) ? $args['value'] : $args[''];
+ if (!isset($InputValues[$name])) $InputValues[$name] = $value;
+ if (@$args['request']) {
+ $req = RequestArgs();
+ foreach($req as $k => $v) {
+ if (is_array($v)) {
+ foreach($v as $vk=>$vv) {
+ if (is_numeric($vk)) $InputValues["{$k}[]"][] = PHSC($vv, ENT_NOQUOTES);
+ else $InputValues["{$k}[{$vk}]"] = PHSC($vv, ENT_NOQUOTES);
+ }
+ }
+ else {
+ if (!isset($InputValues[$k]))
+ $InputValues[$k] = PHSC($v, ENT_NOQUOTES);
+ }
+ }
+ }
+ $sources = @$args['source'];
+ if ($sources) {
+ foreach(explode(',', $sources) as $source) {
+ $source = MakePageName($pagename, $source);
+ if (!PageExists($source)) continue;
+ $page = RetrieveAuthPage($source, 'read', false, READPAGE_CURRENT);
+ if (! $page || ! isset($page['text'])) continue;
+ foreach((array)$PageTextVarPatterns as $pat)
+ if (preg_match_all($pat, IsEnabled($PCache[$source]['=preview'], $page['text']),
+ $match, PREG_SET_ORDER))
+ foreach($match as $m)
+# if (!isset($InputValues['ptv_'.$m[2]])) PITS:01337
+ $InputValues['ptv_'.$m[2]] =
+ PHSC(Qualify($source, $m[3]), ENT_NOQUOTES);
+ break;
+ }
+ }
+ return '';
+}
+
+
+## (:input select ...:) is special, because we need to process a bunch of
+## them as a single unit.
+function InputSelect($pagename, $type, $markup) {
+ global $InputTags, $InputAttrs, $FmtV;
+ preg_match_all('/\\(:input\\s+\\S+\\s+(.*?):\\)/', $markup, $match);
+ $selectopt = (array)$InputTags[$type];
+ $opt = $selectopt;
+ $optionshtml = '';
+ $optiontype = isset($InputTags["$type-option"])
+ ? "$type-option" : "select-option";
+ foreach($match[1] as $args) {
+ $optionshtml .= InputToHTML($pagename, $optiontype, $args, $oo);
+ $opt = array_merge($opt, $oo);
+ }
+ $attrlist = array_diff($InputAttrs, array('value'));
+ $attr = array();
+ foreach($attrlist as $a) {
+ if (!isset($opt[$a]) || $opt[$a]===false) continue;
+ $attr[] = "$a='".str_replace("'", ''', $opt[$a])."'";
+ }
+ $FmtV['$InputSelectArgs'] = implode(' ', $attr);
+ $FmtV['$InputSelectOptions'] = $optionshtml;
+ return Keep(FmtPageName($selectopt[':html'], $pagename));
+}
+
+
+function InputActionForm($pagename, $type, $args) {
+ global $InputAttrs;
+ $args = ParseArgs($args);
+ if (@$args['pagename']) $pagename = $args['pagename'];
+ $opt = NULL;
+ $html = InputToHTML($pagename, $type, $args, $opt);
+ foreach(preg_grep('/^[\\w$]/', array_keys($args)) as $k) {
+ if (is_array($args[$k]) || in_array($k, $InputAttrs)) continue;
+ if ($k == 'n' || $k == 'pagename') continue;
+ $html .= "<input type='hidden' name='$k' value='{$args[$k]}' />";
+ }
+ return Keep($html);
+}
+
+
+## RequestArgs is used to extract values from controls (typically
+## in $_GET and $_POST).
+function RequestArgs($req = NULL) {
+ if (is_null($req)) $req = array_merge($_GET, $_POST);
+ foreach ($req as $k => $v) {
+ if (is_array($v)) $req[$k] = RequestArgs($v);
+ else $req[$k] = stripmagic($req[$k]);
+ }
+ return $req;
+}
+
+
+## Form-based authorization prompts (for use with PmWikiAuth)
+SDVA($InputTags['auth_form'], array(
+ ':html' => "<form \$InputFormArgs>\$PostVars",
+ 'action' => str_replace("'", '%37', stripmagic($_SERVER['REQUEST_URI'])),
+ 'method' => 'post',
+ 'name' => 'authform'));
+SDV($AuthPromptFmt, array(&$PageStartFmt, 'page:$SiteGroup.AuthForm',
+ "<script language='javascript' type='text/javascript'><!--
+ try { document.authform.authid.focus(); }
+ catch(e) { document.authform.authpw.focus(); } //--></script>",
+ &$PageEndFmt));
+
+## PITS:01188, these should exist in "browse" mode
+## NOTE: also defined in prefs.php
+XLSDV('en', array(
+ 'ak_save' => 's',
+ 'ak_saveedit' => 'u',
+ 'ak_preview' => 'p',
+ 'ak_textedit' => ',',
+ 'e_rows' => '23',
+ 'e_cols' => '60'));
+
+## The section below handles specialized EditForm pages.
+## We don't bother to load it if we're not editing.
+
+if ($action != 'edit') return;
+
+SDV($PageEditForm, '$SiteGroup.EditForm');
+SDV($PageEditFmt, '$EditForm');
+if (@$_REQUEST['editform']) {
+ $PageEditForm=$_REQUEST['editform'];
+ $PageEditFmt='$EditForm';
+}
+$Conditions['e_preview'] = '(boolean)$_REQUEST["preview"]';
+
+# (:e_preview:) displays the preview of formatted text.
+Markup('e_preview', 'directives',
+ '/^\\(:e_preview:\\)/', "MarkupInputForms");
+
+# If we didn't load guiedit.php, then set (:e_guibuttons:) to
+# simply be empty.
+Markup('e_guibuttons', 'directives', '/\\(:e_guibuttons:\\)/', '');
+
+# Prevent (:e_preview:) and (:e_guibuttons:) from
+# participating in text rendering step.
+SDV($SaveAttrPatterns['/\\(:e_(preview|guibuttons):\\)/'], ' ');
+
+$TextScrollTop = intval(@$_REQUEST['textScrollTop']);
+SDVA($InputTags['e_form'], array(
+ ':html' => "<form action='{\$PageUrl}?action=edit' method='post'
+ \$InputFormArgs><input type='hidden' name='action' value='edit'
+ /><input type='hidden' name='n' value='{\$FullName}'
+ /><input type='hidden' name='basetime' value='\$EditBaseTime'
+ /><input type='hidden' name='textScrollTop' id='textScrollTop' value='$TextScrollTop'
+ />"));
+SDVA($InputTags['e_textarea'], array(
+ ':html' => "<textarea \$InputFormArgs
+ onkeydown='if (event.keyCode==27) event.returnValue=false;'
+ >\$EditText</textarea>",
+ 'name' => 'text', 'id' => 'text', 'accesskey' => XL('ak_textedit'),
+ 'rows' => XL('e_rows'), 'cols' => XL('e_cols')));
+SDVA($InputTags['e_author'], array(
+ ':html' => "<input type='text' \$InputFormArgs />",
+ 'name' => 'author', 'value' => $Author));
+SDVA($InputTags['e_changesummary'], array(
+ ':html' => "<input type='text' \$InputFormArgs />",
+ 'name' => 'csum', 'size' => '60', 'maxlength' => '100',
+ 'value' => PHSC(stripmagic(@$_POST['csum']), ENT_QUOTES)));
+SDVA($InputTags['e_minorcheckbox'], array(
+ ':html' => "<input type='checkbox' \$InputFormArgs />",
+ 'name' => 'diffclass', 'value' => 'minor'));
+if (@$_POST['diffclass']=='minor')
+ SDV($InputTags['e_minorcheckbox']['checked'], 'checked');
+SDVA($InputTags['e_savebutton'], array(
+ ':html' => "<input type='submit' \$InputFormArgs />",
+ 'name' => 'post', 'value' => ' '.XL('Save').' ',
+ 'accesskey' => XL('ak_save')));
+SDVA($InputTags['e_saveeditbutton'], array(
+ ':html' => "<input type='submit' \$InputFormArgs />",
+ 'name' => 'postedit', 'value' => ' '.XL('Save and edit').' ',
+ 'accesskey' => XL('ak_saveedit')));
+SDVA($InputTags['e_savedraftbutton'], array(':html' => ''));
+SDVA($InputTags['e_previewbutton'], array(
+ ':html' => "<input type='submit' \$InputFormArgs />",
+ 'name' => 'preview', 'value' => ' '.XL('Preview').' ',
+ 'accesskey' => XL('ak_preview')));
+SDVA($InputTags['e_cancelbutton'], array(
+ ':html' => "<input type='submit' \$InputFormArgs />",
+ 'name' => 'cancel', 'value' => ' '.XL('Cancel').' ',
+ 'formnovalidate' => 'formnovalidate'));
+SDVA($InputTags['e_resetbutton'], array(
+ ':html' => "<input type='reset' \$InputFormArgs />",
+ 'value' => ' '.XL('Reset').' '));
+
+if(IsEnabled($EnablePostAuthorRequired))
+ $InputTags['e_author']['required'] = 'required';
+
+if(IsEnabled($EnableNotSavedWarning)) {
+ $is_preview = @$_REQUEST['preview'] ? 'class="preview"' : '';
+ $InputTags['e_form'][':html'] .=
+ "<input type='hidden' id='EnableNotSavedWarning'
+ value=\"$[Content was modified, but not saved!]\" $is_preview />";
+}
+
+if(IsEnabled($EnableEditAutoText)) {
+ $InputTags['e_form'][':html'] .=
+ "<input type='hidden' id='EnableEditAutoText' />";
+}
--- /dev/null
+++ scripts/guiedit.php
@@ -0,0 +1,95 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2004-2019 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This script adds a graphical button bar to the edit page form.
+ The buttons are placed in the $GUIButtons array; each button
+ is specified by an array of five values:
+ - the position of the button relative to others (a number)
+ - the opening markup sequence
+ - the closing markup sequence
+ - the default text if none was highlighted
+ - the text of the button, either (a) HTML markup or (b) the
+ url of a gif/jpg/png image to be used for the button
+ (along with optional "title" text in quotes).
+
+ The buttons specified in this file are the default buttons
+ for the standard markups. Some buttons (e.g., the attach/upload
+ button) are specified in their respective cookbook module.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+SDVA($HTMLHeaderFmt, array('guiedit' => "<script type='text/javascript'
+ src='\$FarmPubDirUrl/guiedit/guiedit.js'></script>\n"));
+
+SDV($GUIButtonDirUrlFmt,'$FarmPubDirUrl/guiedit');
+
+if(IsEnabled($EnableGUIButtons,0)) {
+ SDVA($GUIButtons, array(
+ 'em' => array(100, "''", "''", '$[Emphasized]',
+ '$GUIButtonDirUrlFmt/em.gif"$[Emphasized (italic)]"',
+ '$[ak_em]'),
+ 'strong' => array(110, "'''", "'''", '$[Strong]',
+ '$GUIButtonDirUrlFmt/strong.gif"$[Strong (bold)]"',
+ '$[ak_strong]'),
+ 'pagelink' => array(200, '[[', ']]', '$[Page link]',
+ '$GUIButtonDirUrlFmt/pagelink.gif"$[Link to internal page]"'),
+ 'extlink' => array(210, '[[', ']]', 'http:// | $[link text]',
+ '$GUIButtonDirUrlFmt/extlink.gif"$[Link to external page]"'),
+ 'big' => array(300, "'+", "+'", '$[Big text]',
+ '$GUIButtonDirUrlFmt/big.gif"$[Big text]"'),
+ 'small' => array(310, "'-", "-'", '$[Small text]',
+ '$GUIButtonDirUrlFmt/small.gif"$[Small text]"'),
+ 'sup' => array(320, "'^", "^'", '$[Superscript]',
+ '$GUIButtonDirUrlFmt/sup.gif"$[Superscript]"'),
+ 'sub' => array(330, "'_", "_'", '$[Subscript]',
+ '$GUIButtonDirUrlFmt/sub.gif"$[Subscript]"'),
+ 'h2' => array(400, '\\n!! ', '\\n', '$[Heading]',
+ '$GUIButtonDirUrlFmt/h.gif"$[Heading]"'),
+ 'center' => array(410, '%center%', '', '',
+ '$GUIButtonDirUrlFmt/center.gif"$[Center]"')));
+
+ if(IsEnabled($EnableGuiEditFixUrl)) {
+ $GUIButtons['fixurl'] = array($EnableGuiEditFixUrl, 'FixSelectedURL', '', '',
+ '$GUIButtonDirUrlFmt/fixurl.png"$[Encode special characters in URL link addresses]"');
+ }
+
+ Markup('e_guibuttons', 'directives',
+ '/\\(:e_guibuttons:\\)/', 'GUIButtonCode');
+}
+
+function cb_gbcompare($a, $b) {return $a[0]-$b[0];}
+function GUIButtonCode() {
+ global $GUIButtons;
+ extract($GLOBALS["MarkupToHTML"]); # get $pagename
+
+ usort($GUIButtons, 'cb_gbcompare');
+
+ $out = "<script type='text/javascript'><!--\n";
+ foreach ($GUIButtons as $k => $g) {
+ if (!$g) continue;
+ @list($when, $mopen, $mclose, $mtext, $tag, $mkey) = $g;
+ if (@$ta[0] == '<') {
+ $out .= "document.write(\"$tag\");\n";
+ continue;
+ }
+ if (preg_match('/^(.*\\.(gif|jpg|png))("([^"]+)")?$/', $tag, $m)) {
+ $title = (@$m[4] > '') ? "title='{$m[4]}'" : '';
+ $tag = "<img src='{$m[1]}' $title style='border:0px;' />";
+ }
+ $mopen = str_replace(array('\\', "'"), array('\\\\', "\\\\'"), $mopen);
+ $mclose = str_replace(array('\\', "'"), array('\\\\', "\\\\'"), $mclose);
+ $mtext = str_replace(array('\\', "'"), array('\\\\', "\\\\'"), $mtext);
+ $out .=
+ "insButton(\"$mopen\", \"$mclose\", '$mtext', \"$tag\", \"$mkey\");\n";
+ }
+ $out .= '//--></script>';
+ return Keep(FmtPageName($out, $pagename));
+}
+
+
+
--- /dev/null
+++ scripts/httpauth.php
@@ -0,0 +1,46 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2004-2005 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This file defines an alternate authentication scheme based on the
+ HTTP Basic authentication protocol (i.e., the scheme used by default
+ in PmWiki 1).
+*/
+
+## If the webserver has already authenticated someone, then use
+## that identifier for our authorization id. We also disable
+## the use of the browser's Basic Auth form later, since it tends
+## to confuse webservers.
+if (IsEnabled($EnableRemoteUserAuth, 1) && @$_SERVER['REMOTE_USER']) {
+ SDV($EnableHTTPBasicAuth, 0);
+ SDV($AuthId, $_SERVER['REMOTE_USER']);
+}
+
+## If the browser supplied a password, add that password to the
+## list of passwords used for authentication
+if (@$_SERVER['PHP_AUTH_PW'])
+ SessionAuth($pagename, array('authpw'=>array($_SERVER['PHP_AUTH_PW'] => 1)));
+
+
+## $EnableHTTPBasicAuth tells PmWikiAuth to use the browser's
+## HTTP Basic protocol prompt instead of a form-based prompt.
+if (IsEnabled($EnableHTTPBasicAuth, 1))
+ SDV($AuthPromptFmt, 'function:HTTPBasicAuthPrompt');
+
+## HTTPBasicAuthPrompt replaces PmWikiAuth's form-based password
+## prompt with the browser-based HTTP Basic prompt.
+function HTTPBasicAuthPrompt($pagename) {
+ global $AuthRealmFmt, $AuthDeniedFmt;
+ SDV($AuthRealmFmt,$GLOBALS['WikiTitle']);
+ SDV($AuthDeniedFmt,'A valid password is required to access this feature.');
+ $realm=FmtPageName($AuthRealmFmt,$pagename);
+ header("WWW-Authenticate: Basic realm=\"$realm\"");
+ header("Status: 401 Unauthorized");
+ header("HTTP-Status: 401 Unauthorized");
+ PrintFmt($pagename,$AuthDeniedFmt);
+ exit;
+}
+
--- /dev/null
+++ scripts/intermap.txt
@@ -0,0 +1,10 @@
+PmWiki: https://www.pmwiki.org/wiki/PmWiki/
+Cookbook: https://www.pmwiki.org/wiki/Cookbook/
+Skins: https://www.pmwiki.org/wiki/Skins/
+Wiki: http://www.c2.com/cgi/wiki?
+UseMod: http://www.usemod.com/cgi-bin/wiki.pl?
+Meatball: http://www.usemod.com/cgi-bin/mb.pl?
+Wikipedia: https://en.wikipedia.org/wiki/
+PITS: https://www.pmwiki.org/wiki/PITS/
+PmL10n: https://www.pmwiki.org/wiki/Localization/
+Path:
--- /dev/null
+++ scripts/markupexpr.php
@@ -0,0 +1,143 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2007-2019 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This script implements "markup expressions" -- a method to
+ do simple computations and manipulations from markup. The
+ generic form of a markup expression is "{(func arg1 arg2)}",
+ where the named function (held in the $MarkupExpr array)
+ is called with arg1 and arg2 as arguments.
+
+ Markup expressions can be nested. For example, to strip
+ off the first five characters and convert the remainder to
+ lowercase, an author can write:
+
+ {(tolower (substr "HELLOWORLD" 5))} # produces "world"
+
+ Some "built-in" expressions defined by this recipe include:
+ substr - extract a portion of a string
+ ftime - date/time formatting
+ strlen - length of a string
+ rand - generate a random number
+ pagename - build a pagename from a string
+ toupper - convert string to uppercase
+ tolower - convert string to lowercase
+ ucfirst - convert first character to uppercase
+ ucwords - convert first character of each word to uppercase
+ asspaced - spaceformatting of wikiwords
+
+ Custom expressions may be added by other recipes by adding
+ entries into the $MarkupExpr array. Each entry's key is
+ the name of the function, the value is the code to be evaluated
+ for that function (similar to the way $FmtPV works). By default,
+ any arguments for the expression are placed into the $args array:
+
+ ## expressions like {(myfunc foo bar)}
+ $MarkupExpr['myfunc'] = 'myfunc($args[0], $args[1])';
+
+ The expression arguments are parsed using ParseArgs(), and the
+ result of this parsing is available through the $argp array:
+
+ ## expressions like {(myfunc fmt=foo output=bar)}
+ $MarkupExpr['myfunc'] = 'myfunc($argp["fmt"], $argp["output"])';
+
+ Finally, if the code in $MarkupExpr contains '$params', then
+ it is executed directly without any preprocessing into arguments,
+ and $params contains the entire argument string. Note that $params
+ may contain escaped values representing quoted arguments and
+ results of other expressions; these values may be un-escaped
+ by using "preg_replace_callback($rpat, 'cb_expandkpv', $params)".
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+Markup('{(', '>{$var}',
+ '/\\{(\\(\\w+\\b.*?\\))\\}/',
+ "MarkupMarkupExpression");
+
+function MarkupMarkupExpression($m) {
+ extract($GLOBALS["MarkupToHTML"]); # get $pagename
+ return MarkupExpression($pagename, $m[1]);
+}
+
+SDVA($MarkupExpr, array(
+ 'substr' => 'call_user_func_array("substr", $args)',
+ 'strlen' => 'strlen($args[0])',
+ 'ftime' => 'ME_ftime(@$args[0], @$args[1], $argp)',
+ 'rand' => '($args) ? rand($args[0], $args[1]) : rand()',
+ 'ucfirst' => 'ucfirst($args[0])',
+ 'ucwords' => 'ucwords($args[0])',
+ 'tolower' => 'strtolower($args[0])',
+ 'toupper' => 'strtoupper($args[0])',
+ 'mod' => '0 + (intval($args[0]) % intval($args[1]))',
+ 'asspaced' => '$GLOBALS["AsSpacedFunction"]($args[0])',
+ 'pagename' => 'MakePageName($pagename, preg_replace_callback($rpat, "cb_expandkpv", $params))',
+));
+
+function cb_keep_m0_p($m) { return Keep($m[0],'P'); }
+function cb_keep_m2_p($m) { return Keep($m[2],'P'); }
+
+function MarkupExpression($pagename, $expr) {
+ global $KeepToken, $KPV, $MarkupExpr;
+ $rpat = "/$KeepToken(\\d+P)$KeepToken/";
+
+ $expr = preg_replace_callback('/([\'"])(.*?)\\1/','cb_keep_m2_p', $expr);
+ $expr = preg_replace_callback('/\\(\\W/', 'cb_keep_m0_p', $expr);
+ while (preg_match('/\\((\\w+)(\\s[^()]*)?\\)/', $expr, $match)) {
+ list($repl, $func, $params) = $match;
+ $code = @$MarkupExpr[$func];
+ ## if not a valid function, save this string as-is and exit
+ if (!$code) break;
+ ## if the code uses '$params', we just evaluate directly
+ if (strpos($code, '$params') !== false) {
+ $out = eval("return ({$code});");
+ if ($expr == $repl) { $expr = $out; break; }
+ $expr = str_replace($repl, $out, $expr);
+ continue;
+ }
+ ## otherwise, we parse arguments into $args before evaluating
+ $argp = ParseArgs($params);
+ $x = $argp['#']; $args = array();
+ while ($x) {
+ list($k, $v) = array_splice($x, 0, 2);
+ if ($k == '' || $k == '+' || $k == '-')
+ $args[] = $k.preg_replace_callback($rpat, 'cb_expandkpv', $v);
+ }
+ ## fix any quoted arguments
+ foreach ($argp as $k => $v)
+ if (!is_array($v)) $argp[$k] = preg_replace_callback($rpat, 'cb_expandkpv', $v);
+ $out = eval("return ({$code});");
+ if ($expr == $repl) { $expr = $out; break; }
+ $expr = str_replace($repl, Keep($out, 'P'), $expr);
+ }
+ return preg_replace_callback($rpat, 'cb_expandkpv', $expr);
+}
+
+## ME_ftime handles {(ftime ...)} expressions.
+##
+function ME_ftime($arg0 = '', $arg1 = '', $argp = NULL) {
+ global $TimeFmt, $Now, $FTimeFmt;
+ if (@$argp['fmt']) $fmt = $argp['fmt'];
+ else if (strpos($arg0, '%') !== false) { $fmt = $arg0; $arg0 = $arg1; }
+ else if (strpos($arg1, '%') !== false) $fmt = $arg1;
+ ## determine the timestamp
+ if (isset($argp['when'])) list($time, $x) = DRange($argp['when']);
+ else if ($arg0 > '') list($time, $x) = DRange($arg0);
+ else $time = $Now;
+ $dtz = function_exists('date_default_timezone_get') # tz=Europe/Paris
+ ? date_default_timezone_get() : false;
+ if (@$argp['tz'] && $dtz) @date_default_timezone_set($argp['tz']);
+ $dloc = setlocale(LC_TIME, 0);
+ if(@$argp['locale']) # locale=fr_FR.utf8,bg_BG,C
+ setlocale(LC_TIME, preg_split('/[, ]+/', $argp['locale'], null, PREG_SPLIT_NO_EMPTY));
+ if (@$fmt == '') { SDV($FTimeFmt, $TimeFmt); $fmt = $FTimeFmt; }
+ ## make sure we have %F available for ISO dates
+ $fmt = str_replace(array('%F', '%s'), array('%Y-%m-%d', $time), $fmt);
+ $ret = strftime($fmt, $time);
+ if (@$argp['tz'] && $dtz) date_default_timezone_set($dtz);
+ if(@$argp['locale']) setlocale(LC_TIME, $dloc);
+ return $ret;
+}
+
--- /dev/null
+++ scripts/notify.php
@@ -0,0 +1,200 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2006-2018 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This script enables email notifications to be sent when posts
+ are made. It is included by default from the stdconfig.php
+ script if $EnableNotify is set to non-zero.
+
+ Once enabled, the addresses to receive messages are configured
+ via the Site.NotifyList page. A simple line in that page
+ such as
+
+ notify=somebody@example.com
+
+ will cause messages to be periodically sent to "somebody@example.com"
+ listing the pages that have changed on the site since the previous
+ message was sent. Multiple notify lines can be placed in the page,
+ and there are options to restrict the types of notifications
+ desired. For more details, see the PmWiki.Notify page in
+ the documentation.
+
+ Several variables set defaults for this script:
+
+ $NotifyFrom - return email address to use in message.
+ $NotifyDelay - number of seconds to wait before sending mail
+ after the first post.
+ $NotifySquelch - minimum number of seconds between sending email
+ messages to each address. Individual "notify=" lines in
+ Site.NotifyList can override this value via a custom "squelch="
+ parameter.
+ $NotifyFile - scratchpad file used to keep track of pending emails.
+ $NotifyListPageFmt - name of the NotifyList configuration page.
+ $NotifySubjectFmt - subject line for sent messages.
+ $NotifyBodyFmt - body of message to be sent. The string '$NotifyItems'
+ is replaced with the list of posts in the email.
+ $NotifyItemFmt - the format for each post to be included in a notification.
+ $NotifyTimeFmt - the format for dates and times ($PostTime)
+ in notification messages.
+ $NotifyHeaders - any additional message headers to be sent.
+ $NotifyParameters - any additional parameters to be passed to PHP's
+ mail() function.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+SDV($NotifyDelay, 0);
+SDV($NotifySquelch, 10800);
+SDV($NotifyFile, "$WorkDir/.notifylist");
+SDV($NotifyListPageFmt, '$SiteAdminGroup.NotifyList');
+SDV($NotifySubjectFmt, '[$WikiTitle] recent notify posts');
+SDV($NotifyBodyFmt,
+ "Recent \$WikiTitle posts:\n"
+ . " \$ScriptUrl/$[{\$SiteGroup}/AllRecentChanges]\n\n\$NotifyItems\n");
+SDV($NotifyTimeFmt, $TimeFmt);
+SDV($NotifyItemFmt,
+ ' * {$FullName} . . . $PostTime by {$Author}');
+SDV($NotifyHeaders, '');
+SDV($NotifyParameters, '');
+
+if (@$NotifyFrom)
+ $NotifyHeaders = "From: $NotifyFrom\r\n$NotifyHeaders";
+
+$EditFunctions[] = 'PostNotify';
+
+## check if we need to do notifications
+if ($action != 'edit' && $action != 'postupload') NotifyCheck($pagename);
+
+
+function NotifyCheck($pagename) {
+ global $NotifyFile, $Now, $LastModTime;
+ $nfp = @fopen($NotifyFile, 'r');
+ if (!$nfp) return;
+ $nextevent = fgets($nfp);
+ fclose($nfp);
+ if ($Now < $nextevent && $LastModTime < filemtime($NotifyFile)) return;
+ register_shutdown_function('NotifyUpdate', $pagename, getcwd());
+}
+
+
+function PostNotify($pagename, &$page, &$new) {
+ global $IsPagePosted;
+ if ($IsPagePosted)
+ register_shutdown_function('NotifyUpdate', $pagename, getcwd());
+}
+
+
+function NotifyUpdate($pagename, $dir='') {
+ global $NotifyList, $NotifyListPageFmt, $NotifyFile, $IsPagePosted, $IsUploadPosted,
+ $FmtV, $NotifyTimeFmt, $NotifyItemFmt, $SearchPatterns, $MailFunction,
+ $NotifySquelch, $NotifyDelay, $Now, $Charset, $EnableNotifySubjectEncode,
+ $NotifySubjectFmt, $NotifyBodyFmt, $NotifyHeaders, $NotifyParameters;
+
+ $abort = ignore_user_abort(true);
+ if ($dir) { flush(); chdir($dir); }
+
+ $GLOBALS['EnableRedirect'] = 0;
+
+ ## Read in the current notify configuration
+ $pn = FmtPageName($NotifyListPageFmt, $pagename);
+ $npage = ReadPage($pn, READPAGE_CURRENT);
+ preg_match_all('/^[\s*:#->]*(notify[:=].*)/m', $npage['text'], $nlist);
+ $nlist = array_merge((array)@$NotifyList, (array)@$nlist[1]);
+ if (!$nlist) return;
+
+ ## make sure other processes are locked out
+ Lock(2);
+
+ ## let's load the current .notifylist table
+ $nfile = FmtPageName($NotifyFile, $pagename);
+ $nfp = @fopen($nfile, 'r');
+ if ($nfp) {
+ ## get our current squelch and delay timestamps
+ clearstatcache();
+ $sz = filesize($nfile);
+ list($nextevent, $firstpost) = explode(' ', rtrim(fgets($nfp, $sz)));
+ ## restore our notify array
+ $notify = unserialize(fgets($nfp, $sz));
+ fclose($nfp);
+ }
+ if (!is_array($notify)) $notify = array();
+
+ ## if this is for a newly posted page, get its information
+ if ($IsPagePosted || $IsUploadPosted) {
+ $page = ReadPage($pagename, READPAGE_CURRENT);
+ $FmtV['$PostTime'] = strftime($NotifyTimeFmt, $Now);
+ $item = urlencode(FmtPageName($NotifyItemFmt, $pagename));
+ if ($firstpost < 1) $firstpost = $Now;
+ }
+
+ foreach($nlist as $n) {
+ $opt = ParseArgs($n);
+ $mailto = preg_split('/[\s,]+/', $opt['notify']);
+ if (!$mailto) continue;
+ if ($opt['squelch'])
+ foreach($mailto as $m) $squelch[$m] = $opt['squelch'];
+ if (!$IsPagePosted) continue;
+ if ($opt['link']) {
+ $link = MakePageName($pagename, $opt['link']);
+ if (!preg_match("/(^|,)$link(,|$)/i", $page['targets'])) continue;
+ }
+ $pats = @(array)$SearchPatterns[$opt['list']];
+ if ($opt['group']) $pats[] = FixGlob($opt['group'], '$1$2.*');
+ if ($opt['name']) $pats[] = FixGlob($opt['name'], '$1*.$2');
+ if ($pats && !MatchPageNames($pagename, $pats)) continue;
+ if ($opt['trail']) {
+ $trail = ReadTrail($pagename, $opt['trail']);
+ for ($i=0; $i<count($trail); $i++)
+ if ($trail[$i]['pagename'] == $pagename) break;
+ if ($i >= count($trail)) continue;
+ }
+ foreach($mailto as $m) { $notify[$m][] = $item; }
+ }
+
+ $nnow = time();
+ if ($nnow < $firstpost + $NotifyDelay)
+ $nextevent = $firstpost + $NotifyDelay;
+ else {
+ $firstpost = 0;
+ $nextevent = $nnow + 86400;
+ $mailto = array_keys($notify);
+ $subject = FmtPageName($NotifySubjectFmt, $pagename);
+ if(IsEnabled($EnableNotifySubjectEncode, 0)
+ && preg_match("/[^\x20-\x7E]/", $subject))
+ $subject = strtoupper("=?$Charset?B?"). base64_encode($subject)."?=";
+ $body = FmtPageName($NotifyBodyFmt, $pagename);
+ foreach ($mailto as $m) {
+ $msquelch = @$notify[$m]['lastmail'] +
+ ((@$squelch[$m]) ? $squelch[$m] : $NotifySquelch);
+ if ($nnow < $msquelch) {
+ if ($msquelch < $nextevent && count($notify[$m])>1)
+ $nextevent = $msquelch;
+ continue;
+ }
+ unset($notify[$m]['lastmail']);
+ if (!$notify[$m]) { unset($notify[$m]); continue; }
+ $mbody = str_replace('$NotifyItems',
+ urldecode(implode("\n", $notify[$m])), $body);
+ SDV($MailFunction, 'mail');
+ if ($NotifyParameters && !@ini_get('safe_mode'))
+ $MailFunction($m, $subject, $mbody, $NotifyHeaders, $NotifyParameters);
+ else
+ $MailFunction($m, $subject, $mbody, $NotifyHeaders);
+ $notify[$m] = array('lastmail' => $nnow);
+ }
+ }
+
+ ## save the updated notify status
+ $nfp = @fopen($nfile, "w");
+ if ($nfp) {
+ fputs($nfp, "$nextevent $firstpost\n");
+ fputs($nfp, serialize($notify) . "\n");
+ fclose($nfp);
+ }
+ Lock(0);
+ return true;
+}
+
--- /dev/null
+++ scripts/pagelist.php
@@ -0,0 +1,924 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2004-2020 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This script implements (:pagelist:) and friends -- it's one
+ of the nastiest scripts you'll ever encounter. Part of the reason
+ for this is that page listings are so powerful and flexible, so
+ that adds complexity. They're also expensive, so we have to
+ optimize them wherever we can.
+
+ The core function is FmtPageList(), which will generate a
+ listing according to a wide variety of options. FmtPageList takes
+ care of initial option processing, and then calls a "FPL"
+ (format page list) function to obtain the formatted output.
+ The FPL function is chosen by the 'fmt=' option to (:pagelist:).
+
+ Each FPL function calls MakePageList() to obtain the list
+ of pages, formats the list somehow, and returns the results
+ to FmtPageList. FmtPageList then returns the output to
+ the caller, and calls Keep() (preserves HTML) or PRR() (re-evaluate
+ as markup) as appropriate for the output being returned.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+## $PageIndexFile is the index file for term searches and link= option
+if (IsEnabled($EnablePageIndex, 1)) {
+ SDV($PageIndexFile, "$WorkDir/.pageindex");
+ $EditFunctions[] = 'PostPageIndex';
+}
+
+SDV($StrFoldFunction, 'strtolower');
+SDV($PageListSortCmpFunction, 'strcasecmp');
+
+## $SearchPatterns holds patterns for list= option
+SDV($SearchPatterns['all'], array());
+SDVA($SearchPatterns['normal'], array(
+ 'recent' => '!\.(All)?Recent(Changes|Uploads)$!',
+ 'group' => '!\.Group(Print)?(Header|Footer|Attributes)$!',
+ 'self' => str_replace('.', '\\.', "!^$pagename$!")));
+
+# The list=grouphomes search pattern requires to scan
+# all PageStore directories to get the pagenames.
+# This takes (a tiny amoint of) time, so we only do it when needed.
+function EnablePageListGroupHomes() {
+ global $SearchPatterns;
+ if(isset($SearchPatterns['grouphomes'])) return;
+
+ $groups = $homes = array();
+ foreach(ListPages() as $pn) {
+ list($g, $n) = explode(".", $pn);
+ @$groups[$g]++;
+ }
+ foreach($groups as $g => $cnt) {
+ $homes[] = MakePageName("$g.$g", "$g.");
+ }
+ $SearchPatterns['grouphomes'] = array('/^('.implode('|', $homes).')$/');
+}
+
+## $FPLFormatOpt is a list of options associated with fmt=
+## values. 'default' is used for any undefined values of fmt=.
+SDVA($FPLFormatOpt, array(
+ 'default' => array('fn' => 'FPLTemplate', 'fmt' => '#default'),
+ 'bygroup' => array('fn' => 'FPLTemplate', 'template' => '#bygroup',
+ 'class' => 'fplbygroup'),
+ 'simple' => array('fn' => 'FPLTemplate', 'template' => '#simple',
+ 'class' => 'fplsimple'),
+ 'group' => array('fn' => 'FPLTemplate', 'template' => '#group',
+ 'class' => 'fplgroup'),
+ 'title' => array('fn' => 'FPLTemplate', 'template' => '#title',
+ 'class' => 'fpltitle', 'order' => 'title'),
+ 'count' => array('fn' => 'FPLCountA'),
+ ));
+
+SDV($SearchResultsFmt, "<div class='wikisearch'>\$[SearchFor]
+ <div class='vspace'></div>\$MatchList
+ <div class='vspace'></div>\$[SearchFound]</div>");
+SDV($SearchQuery, str_replace('$', '$',
+ PHSC(stripmagic(@$_REQUEST['q']), ENT_NOQUOTES)));
+XLSDV('en', array(
+ 'SearchFor' => 'Results of search for <em>$Needle</em>:',
+ 'SearchFound' =>
+ '$MatchCount pages found out of $MatchSearched pages searched.'));
+
+SDV($PageListArgPattern, '((?:\\$:?)?\\w[-\\w]*)[:=]');
+
+Markup('pagelist', 'directives',
+ '/\\(:pagelist(\\s+.*?)?:\\)/i', "MarkupPageList");
+Markup('searchbox', 'directives',
+ '/\\(:searchbox(\\s.*?)?:\\)/', "MarkupPageList");
+Markup('searchresults', 'directives',
+ '/\\(:searchresults(\\s+.*?)?:\\)/i', "MarkupPageList");
+
+function MarkupPageList($m) {
+ extract($GLOBALS["MarkupToHTML"]); # get $pagename, $markupid
+ switch ($markupid) {
+ case 'pagelist':
+ return FmtPageList('$MatchList', $pagename, array('o' => $m[1].' '));
+ case 'searchbox':
+ return SearchBox($pagename,
+ ParseArgs(@$m[1], $GLOBALS['PageListArgPattern']));
+ case 'searchresults':
+ return FmtPageList($GLOBALS['SearchResultsFmt'],
+ $pagename, array('req' => 1, 'request'=>1, 'o' => @$m[1]));
+ }
+}
+
+# called from PageListIf and FPLExpandItemVars
+class cb_pl_expandvars extends PPRC {
+ function pl_expandvars($m) {
+ $pn = $this->vars;
+ return PVSE(PageVar($pn, $m[2], $m[1]));
+ }
+}
+
+SDV($SaveAttrPatterns['/\\(:(searchresults|pagelist)(\\s+.*?)?:\\)/i'], ' ');
+
+SDV($HandleActions['search'], 'HandleSearchA');
+SDV($HandleAuth['search'], 'read');
+SDV($ActionTitleFmt['search'], '| $[Search Results]');
+
+SDVA($PageListFilters, array(
+ 'PageListCache' => 80,
+ 'PageListProtect' => 90,
+ 'PageListSources' => 100,
+ 'PageListPasswords' => 120,
+ 'PageListIf' => 140,
+ 'PageListTermsTargets' => 160,
+ 'PageListVariables' => 180,
+ 'PageListSort' => 900,
+));
+
+function CorePageListSorts($x, $y, $o) {
+ global $PCache;
+ if($o == 'title')
+ return @strcasecmp($PCache[$x]['=title'],$PCache[$y]['=title']);
+ return @($PCache[$x][$o]-$PCache[$y][$o]);
+}
+foreach(array('random', 'size', 'time', 'ctime', 'title') as $o)
+ SDV($PageListSortCmp[$o], 'CorePageListSorts');
+
+define('PAGELIST_PRE' , 1);
+define('PAGELIST_ITEM', 2);
+define('PAGELIST_POST', 4);
+
+## SearchBox generates the output of the (:searchbox:) markup.
+## If $SearchBoxFmt is defined, that is used, otherwise a searchbox
+## is generated. Options include group=, size=, label=.
+function SearchBox($pagename, $opt) {
+ global $SearchBoxFmt, $SearchBoxInputType, $SearchBoxOpt, $SearchQuery, $EnablePathInfo;
+ if (isset($SearchBoxFmt)) return Keep(FmtPageName($SearchBoxFmt, $pagename));
+ SDVA($SearchBoxOpt, array('size' => '40',
+ 'label' => FmtPageName('$[Search]', $pagename),
+ 'value' => str_replace("'", "'", $SearchQuery)));
+ $opt = array_merge((array)$SearchBoxOpt, @$_GET, (array)$opt);
+ $opt['action'] = 'search';
+ $target = (@$opt['target'])
+ ? MakePageName($pagename, $opt['target']) : $pagename;
+ $opt['n'] = IsEnabled($EnablePathInfo, 0) ? '' : $target;
+ $out = FmtPageName(" class='wikisearch' action='\$PageUrl' method='get'>",
+ $target);
+ foreach($opt as $k => $v) {
+ if ($v == '' || is_array($v)) continue;
+ $v = str_replace("'", "'", $v);
+ $opt[$k] = $v;
+ if(preg_match('/^(q|label|value|size|placeholder|aria-\\w+)$/', $k)) continue;
+ $k = str_replace("'", "'", $k);
+ $out .= "<input type='hidden' name='$k' value='$v' />";
+ }
+ SDV($SearchBoxInputType, 'text');
+ $out .= "<input type='$SearchBoxInputType' name='q' value='{$opt['value']}' ";
+ $attrs = preg_grep('/^(placeholder|aria-\\w+)/', array_keys($opt));
+ foreach ($attrs as $k) $out .= " $k='{$opt[$k]}' ";
+ $out .= " class='inputbox searchbox' size='{$opt['size']}' /><input type='submit'
+ class='inputbutton searchbutton' value='{$opt['label']}' />";
+ return '<form '.Keep($out).'</form>';
+}
+
+
+## FmtPageList combines options from markup, request form, and url,
+## calls the appropriate formatting function, and returns the string.
+function FmtPageList($outfmt, $pagename, $opt) {
+ global $GroupPattern, $FmtV, $PageListArgPattern,
+ $FPLFormatOpt, $FPLFunctions;
+ # get any form or url-submitted request
+ $rq = PHSC(stripmagic(@$_REQUEST['q']), ENT_NOQUOTES);
+ # build the search string
+ $FmtV['$Needle'] = $opt['o'] . ' ' . $rq;
+ # Handle "group/" at the beginning of the form-submitted request
+ if (preg_match("!^($GroupPattern(\\|$GroupPattern)*)?/!i", $rq, $match)) {
+ $opt['group'] = @$match[1];
+ $rq = substr($rq, strlen(@$match[1])+1);
+ }
+ $opt = array_merge($opt, ParseArgs($opt['o'], $PageListArgPattern));
+ # merge markup options with form and url
+ if (@$opt['request'] && @$_REQUEST) {
+ $rkeys = preg_grep('/^=/', array_keys($_REQUEST), PREG_GREP_INVERT);
+ if ($opt['request'] != '1') {
+ list($incl, $excl) = GlobToPCRE($opt['request']);
+ if ($excl) $rkeys = array_diff($rkeys, preg_grep("/$excl/", $rkeys));
+ if ($incl) $rkeys = preg_grep("/$incl/", $rkeys);
+ }
+ $cleanrequest = array();
+ foreach($rkeys as $k) {
+ $cleanrequest[$k] = stripmagic($_REQUEST[$k]);
+ if(substr($k, 0, 4)=='ptv_') # defined separately in forms
+ $cleanrequest['$:'.substr($k, 4)] = stripmagic($_REQUEST[$k]);
+ }
+ $opt = array_merge($opt, ParseArgs($rq, $PageListArgPattern), $cleanrequest);
+ }
+
+ # non-posted blank search requests return nothing
+ if (@($opt['req'] && !$opt['-'] && !$opt[''] && !$opt['+'] && !$opt['q']))
+ return '';
+ # terms and group to be included and excluded
+ $GLOBALS['SearchIncl'] = array_merge((array)@$opt[''], (array)@$opt['+']);
+ $GLOBALS['SearchExcl'] = (array)@$opt['-'];
+ $GLOBALS['SearchGroup'] = @$opt['group'];
+ $fmt = @$opt['fmt']; if (!$fmt) $fmt = 'default';
+ $fmtopt = @$FPLFormatOpt[$fmt];
+ if (!is_array($fmtopt)) {
+ if ($fmtopt) $fmtopt = array('fn' => $fmtopt);
+ elseif (@$FPLFunctions[$fmt])
+ $fmtopt = array('fn' => $FPLFunctions[$fmt]);
+ else $fmtopt = $FPLFormatOpt['default'];
+ }
+ $fmtfn = @$fmtopt['fn'];
+ if (!is_callable($fmtfn)) $fmtfn = $FPLFormatOpt['default']['fn'];
+ $matches = array();
+ $opt = array_merge($fmtopt, $opt);
+ $out = $fmtfn($pagename, $matches, $opt);
+ $FmtV['$MatchCount'] = count($matches);
+ if ($outfmt != '$MatchList')
+ { $FmtV['$MatchList'] = $out; $out = FmtPageName($outfmt, $pagename); }
+ if ($out[0] == '<') $out = Keep($out);
+ return PRR($out);
+}
+
+
+## MakePageList generates a list of pages using the specifications given
+## by $opt.
+function MakePageList($pagename, $opt, $retpages = 1) {
+ global $MakePageListOpt, $PageListFilters, $PCache;
+
+ StopWatch('MakePageList pre');
+ SDVA($MakePageListOpt, array('list' => 'default'));
+ $opt = array_merge((array)$MakePageListOpt, (array)$opt);
+ if (!@$opt['order'] && !@$opt['trail']) $opt['order'] = 'name';
+ $opt['order'] = preg_replace('/[^-\\w:$]+/', ',', @$opt['order']);
+
+ ksort($opt); $opt['=key'] = md5(serialize($opt));
+
+ $itemfilters = array(); $postfilters = array();
+ asort($PageListFilters);
+ $opt['=phase'] = PAGELIST_PRE; $list=array(); $pn=NULL; $page=NULL;
+ foreach($PageListFilters as $fn => $v) {
+ if ($v<0) continue;
+ $ret = $fn($list, $opt, $pagename, $page);
+ if ($ret & PAGELIST_ITEM) $itemfilters[] = $fn;
+ if ($ret & PAGELIST_POST) $postfilters[] = $fn;
+ }
+
+ StopWatch("MakePageList items count=".count($list).", filters=".implode(',',$itemfilters));
+ $opt['=phase'] = PAGELIST_ITEM;
+ $matches = array(); $opt['=readc'] = 0;
+ foreach((array)$list as $pn) {
+ $page = array();
+ foreach((array)$itemfilters as $fn)
+ if (!$fn($list, $opt, $pn, $page)) continue 2;
+ $page['pagename'] = $page['name'] = $pn;
+ PCache($pn, $page);
+ $matches[] = $pn;
+ }
+ $list = $matches;
+ StopWatch("MakePageList post count=".count($list).", readc={$opt['=readc']}");
+
+ $opt['=phase'] = PAGELIST_POST; $pn=NULL; $page=NULL;
+ foreach((array)$postfilters as $fn)
+ $fn($list, $opt, $pagename, $page);
+
+ if ($retpages)
+ for($i=0; $i<count($list); $i++)
+ $list[$i] = &$PCache[$list[$i]];
+ StopWatch('MakePageList end');
+ return $list;
+}
+
+
+function PageListProtect(&$list, &$opt, $pn, &$page) {
+ global $EnablePageListProtect;
+
+ switch ($opt['=phase']) {
+ case PAGELIST_PRE:
+ if (!IsEnabled($EnablePageListProtect, 1) && @$opt['readf'] < 1000)
+ return 0;
+ StopWatch("PageListProtect enabled");
+ $opt['=protectexclude'] = array();
+ $opt['=protectsafe'] = (array)@$opt['=protectsafe'];
+ return PAGELIST_ITEM|PAGELIST_POST;
+
+ case PAGELIST_ITEM:
+ if (@$opt['=protectsafe'][$pn]) return 1;
+ $page = RetrieveAuthPage($pn, 'ALWAYS', false, READPAGE_CURRENT);
+ $opt['=readc']++;
+ if (!$page['=auth']['read']) $opt['=protectexclude'][$pn] = 1;
+ if (!$page['=passwd']['read']) $opt['=protectsafe'][$pn] = 1;
+ else NoCache();
+ return 1;
+
+ case PAGELIST_POST:
+ $excl = array_keys($opt['=protectexclude']);
+ $safe = array_keys($opt['=protectsafe']);
+ StopWatch("PageListProtect excluded=" .count($excl)
+ . ", safe=" . count($safe));
+ $list = array_diff($list, $excl);
+ return 1;
+ }
+}
+
+
+function PageListSources(&$list, &$opt, $pn, &$page) {
+ global $SearchPatterns;
+
+ StopWatch('PageListSources begin');
+ if ($opt['list'] == 'grouphomes') EnablePageListGroupHomes();
+ ## add the list= option to our list of pagename filter patterns
+ $opt['=pnfilter'] = array_merge((array)@$opt['=pnfilter'],
+ (array)@$SearchPatterns[$opt['list']]);
+
+ if (@$opt['group']) $opt['=pnfilter'][] = FixGlob($opt['group'], '$1$2.*');
+ if (@$opt['name']) $opt['=pnfilter'][] = FixGlob($opt['name'], '$1*.$2');
+
+ if (@$opt['trail']) {
+ $trail = ReadTrail($pn, $opt['trail']);
+ $tlist = array();
+ foreach($trail as $tstop) {
+ $n = $tstop['pagename'];
+ $tlist[] = $n;
+ $tstop['parentnames'] = array();
+ PCache($n, $tstop);
+ }
+ foreach($trail as $tstop)
+ $PCache[$tstop['pagename']]['parentnames'][] =
+ @$trail[$tstop['parent']]['pagename'];
+ if (!@$opt['=cached']) $list = MatchPageNames($tlist, $opt['=pnfilter']);
+ } else if (!@$opt['=cached']) $list = ListPages($opt['=pnfilter']);
+ StopWatch("PageListSources end count=".count($list));
+ return 0;
+}
+
+
+function PageListPasswords(&$list, &$opt, $pn, &$page) {
+ if ($opt['=phase'] == PAGELIST_PRE)
+ return (@$opt['passwd'] > '' && !@$opt['=cached']) ? PAGELIST_ITEM : 0;
+
+ if (!$page) { $page = ReadPage($pn, READPAGE_CURRENT); $opt['=readc']++; }
+ if (!$page) return 0;
+ return (boolean)preg_grep('/^passwd/', array_keys($page));
+}
+
+
+function PageListIf(&$list, &$opt, $pn, &$page) {
+ global $Conditions, $Cursor;
+
+ ## See if we have any "if" processing to perform
+ if ($opt['=phase'] == PAGELIST_PRE)
+ return (@$opt['if'] > '') ? PAGELIST_ITEM : 0;
+
+ $condspec = $opt['if'];
+ $Cursor['='] = $pn;
+ $varpat = '\\{([=*]|!?[-\\w.\\/\\x80-\\xff]*)(\\$:?\\w+)\\}';
+ while (preg_match("/$varpat/", $condspec, $match)) {
+ $cb = new cb_pl_expandvars($pn);
+ $condspec = preg_replace_callback("/$varpat/",
+ array($cb, 'pl_expandvars'), $condspec);
+ }
+ if (!preg_match("/^\\s*(!?)\\s*(\\S*)\\s*(.*?)\\s*$/", $condspec, $match))
+ return 0;
+ list($x, $not, $condname, $condparm) = $match;
+ if (!isset($Conditions[$condname])) return 1;
+ $tf = (int)@eval("return ({$Conditions[$condname]});");
+ return (boolean)($tf xor $not);
+}
+
+function PageListTermsTargets(&$list, &$opt, $pn, &$page) {
+ global $FmtV;
+ static $reindex = array();
+ $fold = $GLOBALS['StrFoldFunction'];
+
+ switch ($opt['=phase']) {
+ case PAGELIST_PRE:
+ $FmtV['$MatchSearched'] = count($list);
+ $incl = array(); $excl = array();
+ foreach((array)@$opt[''] as $i) { $incl[] = $fold($i); }
+ foreach((array)@$opt['+'] as $i) { $incl[] = $fold($i); }
+ foreach((array)@$opt['-'] as $i) { $excl[] = $fold($i); }
+
+ $indexterms = PageIndexTerms($incl);
+ foreach($incl as $i) {
+ $delim = (!preg_match('/[^\\w\\x80-\\xff]/', $i)) ? '$' : '/';
+ $opt['=inclp'][] = $delim . preg_quote($i,$delim) . $delim . 'i';
+ }
+ if ($excl)
+ $opt['=exclp'][] = '$'.implode('|', array_map('preg_quote',$excl)).'$i';
+
+ if (@$opt['link']) {
+ $link = MakePageName($pn, $opt['link']);
+ $opt['=linkp'] = "/(^|,)$link(,|$)/i";
+ $indexterms[] = " $link ";
+ }
+
+ if (@$opt['=cached']) return 0;
+ if ($indexterms) {
+ StopWatch("PageListTermsTargets begin count=".count($list));
+ $xlist = PageIndexGrep($indexterms, true);
+ $list = array_diff($list, $xlist);
+ StopWatch("PageListTermsTargets end count=".count($list));
+ }
+
+ if (@$opt['=inclp'] || @$opt['=exclp'] || @$opt['=linkp'])
+ return PAGELIST_ITEM|PAGELIST_POST;
+ return 0;
+
+ case PAGELIST_ITEM:
+ if (!$page) { $page = ReadPage($pn, READPAGE_CURRENT); $opt['=readc']++; }
+ if (!$page) return 0;
+ if (@$opt['=linkp'] && !preg_match($opt['=linkp'], @$page['targets']))
+ { $reindex[] = $pn; return 0; }
+ if (@$opt['=inclp'] || @$opt['=exclp']) {
+ $text = $fold($pn."\n".@$page['targets']."\n".@$page['text']);
+ foreach((array)@$opt['=exclp'] as $i)
+ if (preg_match($i, $text)) return 0;
+ foreach((array)@$opt['=inclp'] as $i)
+ if (!preg_match($i, $text)) {
+ if ($i[0] == '$') $reindex[] = $pn;
+ return 0;
+ }
+ }
+ return 1;
+
+ case PAGELIST_POST:
+ if ($reindex) PageIndexQueueUpdate($reindex);
+ $reindex = array();
+ return 0;
+ }
+}
+
+
+function PageListVariables(&$list, &$opt, $pn, &$page) {
+ global $PageListVarFoldFn, $StrFoldFunction;
+ $fold = empty($PageListVarFoldFn)
+ ? $StrFoldFunction : $PageListVarFoldFn;
+
+ switch ($opt['=phase']) {
+ case PAGELIST_PRE:
+ $varlist = preg_grep('/^\\$/', array_keys($opt));
+ if (!$varlist) return 0;
+ foreach($varlist as $v) {
+ list($inclp, $exclp) = GlobToPCRE($opt[$v]);
+ if ($inclp) $opt['=varinclp'][$v] = $fold("/$inclp/i");
+ if ($exclp) $opt['=varexclp'][$v] = $fold("/$exclp/i");
+ }
+ return PAGELIST_ITEM;
+
+ case PAGELIST_ITEM:
+ if (@$opt['=varinclp'])
+ foreach($opt['=varinclp'] as $v => $pat)
+ if (!preg_match($pat, $fold(PageVar($pn, $v)))) return 0;
+ if (@$opt['=varexclp'])
+ foreach($opt['=varexclp'] as $v => $pat)
+ if (preg_match($pat, $fold(PageVar($pn, $v)))) return 0;
+ return 1;
+ }
+}
+
+
+function PageListSort(&$list, &$opt, $pn, &$page) {
+ global $PageListSortCmp, $PCache, $PageListSortRead;
+ SDVA($PageListSortRead, array('name' => 0, 'group' => 0, 'random' => 0,
+ 'title' => 0));
+
+ switch ($opt['=phase']) {
+ case PAGELIST_PRE:
+ $ret = 0;
+ foreach(preg_split('/[^-\\w:$]+/', @$opt['order'], -1, PREG_SPLIT_NO_EMPTY)
+ as $o) {
+ $ret |= PAGELIST_POST;
+ $r = '+';
+ if ($o[0] == '-') { $r = '-'; $o = substr($o, 1); }
+ $opt['=order'][$o] = $r;
+ if ($o[0] != '$' &&
+ (!isset($PageListSortRead[$o]) || $PageListSortRead[$o]))
+ $ret |= PAGELIST_ITEM;
+ }
+ StopWatch(@"PageListSort pre ret=$ret order={$opt['order']}");
+ return $ret;
+
+ case PAGELIST_ITEM:
+ if (!$page) { $page = ReadPage($pn, READPAGE_CURRENT); $opt['=readc']++; }
+ return 1;
+ }
+
+ ## case PAGELIST_POST
+ StopWatch('PageListSort begin');
+ $order = $opt['=order'];
+ if (@$order['title'])
+ foreach($list as $pn) $PCache[$pn]['=title'] = PageVar($pn, '$Title');
+ if (@$order['group'])
+ foreach($list as $pn) $PCache[$pn]['group'] = PageVar($pn, '$Group');
+ if (@$order['random'])
+ { NoCache(); foreach($list as $pn) $PCache[$pn]['random'] = rand(); }
+ foreach(preg_grep('/^\\$/', array_keys($order)) as $o)
+ foreach($list as $pn)
+ $PCache[$pn][$o] = PageVar($pn, $o);
+ foreach($PageListSortCmp as $o=>$f)
+ if(! is_callable($f)) # DEPRECATED
+ $PageListSortCmp[$o] = create_function( # called by old addon needing update, see pmwiki.org/CustomPagelistSortOrder
+ '$x,$y', "global \$PCache; return {$f};");
+
+ StopWatch('PageListSort sort');
+ if (count($opt['=order'])) {
+ $PCache['=pagelistoptorder'] = $opt['=order'];
+ uasort($list, 'PageListUASort');
+ }
+ StopWatch('PageListSort end');
+}
+function PageListUASort($x,$y) {
+ global $PCache, $PageListSortCmp, $PageListSortCmpFunction;
+ foreach($PCache['=pagelistoptorder'] as $o => $r) {
+ $sign = ($r == '-') ? -1 : 1;
+ if (@$PageListSortCmp[$o] && is_callable($PageListSortCmp[$o]))
+ $c = $PageListSortCmp[$o]($x, $y, $o);
+ else
+ $c = @$PageListSortCmpFunction($PCache[$x][$o],$PCache[$y][$o]);
+ if ($c) return $sign*$c;
+ }
+ return 0;
+}
+
+function PageListCache(&$list, &$opt, $pn, &$page) {
+ global $PageListCacheDir, $LastModTime, $PageIndexFile;
+
+ if (@!$PageListCacheDir) return 0;
+ if (isset($opt['cache']) && !$opt['cache']) return 0;
+
+ $key = $opt['=key'];
+ $cache = "$PageListCacheDir/$key,cache";
+ switch ($opt['=phase']) {
+ case PAGELIST_PRE:
+ if (!file_exists($cache) || filemtime($cache) <= $LastModTime)
+ return PAGELIST_POST;
+ StopWatch("PageListCache begin load key=$key");
+ list($list, $opt['=protectsafe']) =
+ unserialize(file_get_contents($cache));
+ $opt['=cached'] = 1;
+ StopWatch("PageListCache end load");
+ return 0;
+
+ case PAGELIST_POST:
+ StopWatch("PageListCache begin save key=$key");
+ $fp = @fopen($cache, "w");
+ if ($fp) {
+ fputs($fp, serialize(array($list, $opt['=protectsafe'])));
+ fclose($fp);
+ }
+ StopWatch("PageListCache end save");
+ return 0;
+ }
+ return 0;
+}
+
+
+## HandleSearchA performs ?action=search. It's basically the same
+## as ?action=browse, except it takes its contents from Site.Search.
+function HandleSearchA($pagename, $level = 'read') {
+ global $PageSearchForm, $FmtV, $HandleSearchFmt,
+ $PageStartFmt, $PageEndFmt;
+ SDV($HandleSearchFmt,array(&$PageStartFmt, '$PageText', &$PageEndFmt));
+ SDV($PageSearchForm, '$[{$SiteGroup}/Search]');
+ $form = RetrieveAuthPage($pagename, $level, true, READPAGE_CURRENT);
+ if (!$form) Abort("?unable to read $pagename");
+ PCache($pagename, $form);
+ $text = preg_replace('/\\[([=@])(.*?)\\1\\]/s', ' ', @$form['text']);
+ if (!preg_match('/\\(:searchresults(\\s.*?)?:\\)/', $text))
+ foreach((array)$PageSearchForm as $formfmt) {
+ $form = ReadPage(FmtPageName($formfmt, $pagename), READPAGE_CURRENT);
+ if ($form['text']) break;
+ }
+ $text = @$form['text'];
+ if (!$text) $text = '(:searchresults:)';
+ $FmtV['$PageText'] = MarkupToHTML($pagename,$text);
+ PrintFmt($pagename, $HandleSearchFmt);
+}
+
+
+########################################################################
+## The functions below provide different formatting options for
+## the output list, controlled by the fmt= parameter and the
+## $FPLFormatOpt hash.
+########################################################################
+
+## This helper function handles the count= parameter for extracting
+## a range of pagelist in the list.
+function CalcRange($range, $n) {
+ if ($n < 1) return array(0, 0);
+ if (strpos($range, '..') === false) {
+ if ($range > 0) return array(1, min($range, $n));
+ if ($range < 0) return array(max($n + $range + 1, 1), $n);
+ return array(1, $n);
+ }
+ list($r0, $r1) = explode('..', $range);
+ if ($r0 < 0) $r0 += $n + 1;
+ if ($r1 < 0) $r1 += $n + 1;
+ else if ($r1 == 0) $r1 = $n;
+ if ($r0 < 1 && $r1 < 1) return array($n+1, $n+1);
+ return array(max($r0, 1), max($r1, 1));
+}
+
+
+## FPLCountA handles fmt=count
+function FPLCountA($pagename, &$matches, $opt) {
+ $matches = array_values(MakePageList($pagename, $opt, 0));
+ return count($matches);
+}
+
+SDVA($FPLTemplateFunctions, array(
+ 'FPLTemplateLoad' => 100,
+ 'FPLTemplateDefaults' => 200,
+ 'FPLTemplatePageList' => 300,
+ 'FPLTemplateSliceList' => 400,
+ 'FPLTemplateFormat' => 500
+ ));
+
+function FPLTemplate($pagename, &$matches, $opt) {
+ global $FPLTemplateFunctions;
+ StopWatch("FPLTemplate: Chain begin");
+ asort($FPLTemplateFunctions, SORT_NUMERIC);
+ $fnlist = $FPLTemplateFunctions;
+ $output = '';
+ foreach($FPLTemplateFunctions as $fn=>$i) {
+ if ($i<0) continue;
+ StopWatch("FPLTemplate: $fn");
+ $fn($pagename, $matches, $opt, $tparts, $output);
+ }
+ StopWatch("FPLTemplate: Chain end");
+ return $output;
+}
+
+## Loads a template section
+function FPLTemplateLoad($pagename, $matches, $opt, &$tparts){
+ global $Cursor, $FPLTemplatePageFmt, $RASPageName, $PageListArgPattern;
+ SDV($FPLTemplatePageFmt, array('{$FullName}',
+ '{$SiteGroup}.LocalTemplates', '{$SiteGroup}.PageListTemplates'));
+
+ $template = @$opt['template'];
+ if (!$template) $template = @$opt['fmt'];
+ $ttext = RetrieveAuthSection($pagename, $template, $FPLTemplatePageFmt);
+ $ttext = PVSE(Qualify($RASPageName, $ttext));
+
+ ## save any escapes
+ $ttext = MarkupEscape($ttext);
+ ## remove any anchor markups to avoid duplications
+ $ttext = preg_replace('/\\[\\[#[A-Za-z][-.:\\w]*\\]\\]/', '', $ttext);
+
+ ## extract portions of template
+ $tparts = preg_split('/\\(:(template)\\s+([-!]?)\\s*(\\w+)\\s*(.*?):\\)/i',
+ $ttext, -1, PREG_SPLIT_DELIM_CAPTURE);
+}
+
+## Merge parameters from (:template default :) with those in the (:pagelist:)
+function FPLTemplateDefaults($pagename, $matches, &$opt, &$tparts){
+ global $PageListArgPattern;
+ $i = 0;
+ while ($i < count($tparts)) {
+ if ($tparts[$i] != 'template') { $i++; continue; }
+ if ($tparts[$i+2] != 'defaults' && $tparts[$i+2] != 'default') { $i+=5; continue; }
+ $pvars = $GLOBALS['MarkupTable']['{$var}']; # expand {$PVars}
+ $ttext = preg_replace_callback($pvars['pat'], $pvars['rep'], $tparts[$i+3]);
+ $opt = array_merge(ParseArgs($ttext, $PageListArgPattern), $opt);
+ array_splice($tparts, $i, 4);
+ }
+ SDVA($opt, array('class' => 'fpltemplate', 'wrap' => 'div'));
+}
+
+## get the list of pages
+function FPLTemplatePageList($pagename, &$matches, &$opt){
+ $matches = array_unique(array_merge((array)$matches, MakePageList($pagename, $opt, 0)));
+ ## count matches before any slicing and save value as template var {$$PageListCount}
+ $opt['PageListCount'] = count($matches);
+}
+
+## extract page subset according to 'count=' parameter
+function FPLTemplateSliceList($pagename, &$matches, $opt){
+ if (@$opt['count']) {
+ list($r0, $r1) = CalcRange($opt['count'], count($matches));
+ if ($r1 < $r0)
+ $matches = array_reverse(array_slice($matches, $r1-1, $r0-$r1+1));
+ else
+ $matches = array_slice($matches, $r0-1, $r1-$r0+1);
+ }
+}
+
+
+function FPLTemplateFormat($pagename, $matches, $opt, $tparts, &$output){
+ global $Cursor, $FPLTemplateMarkupFunction, $PCache;
+ SDV($FPLTemplateMarkupFunction, 'MarkupToHTML');
+ $savecursor = $Cursor;
+ $pagecount = $groupcount = $grouppagecount = $traildepth = $eachcount = 0;
+ $pseudovars = array('{$$PageCount}' => &$pagecount,
+ '{$$EachCount}' => &$eachcount,
+ '{$$GroupCount}' => &$groupcount,
+ '{$$GroupPageCount}' => &$grouppagecount,
+ '{$$PageTrailDepth}' => &$traildepth);
+
+ foreach(preg_grep('/^[\\w$]/', array_keys($opt)) as $k)
+ if (!is_array($opt[$k]))
+ $pseudovars["{\$\$$k}"] = PHSC($opt[$k], ENT_NOQUOTES);
+
+ $vk = array_keys($pseudovars);
+ $vv = array_values($pseudovars);
+
+ $lgroup = $lcontrol = ''; $out = '';
+ if (count($matches)==0) {
+ $t = 0;
+ while($t < count($tparts)) {
+ if ($tparts[$t]=='template' && $tparts[$t+2]=='none') {
+ $out .= MarkupRestore(FPLExpandItemVars($tparts[$t+4], $matches, 0, $pseudovars));
+ $t+=4;
+ }
+ $t++;
+ }
+ } # else:
+ foreach($matches as $i => $pn) {
+ $traildepth = intval(@$PCache[$pn]['depth']);
+ $group = PageVar($pn, '$Group');
+ if ($group != $lgroup) { $groupcount++; $grouppagecount = 0; $lgroup = $group; }
+ $grouppagecount++; $pagecount++; $eachcount++;
+
+ $t = 0;
+ while ($t < count($tparts)) {
+ if ($tparts[$t] != 'template') { $item = $tparts[$t]; $t++; }
+ else {
+ list($neg, $when, $control, $item) = array_slice($tparts, $t+1, 4); $t+=5;
+ if ($when=='none') continue;
+ if (!$control) {
+ if ($when == 'first' && ($neg xor ($i != 0))) continue;
+ if ($when == 'last' && ($neg xor ($i != count($matches) - 1))) continue;
+ } else {
+ $currcontrol = FPLExpandItemVars($control, $matches, $i, $pseudovars);
+ if($currcontrol != $lcontrol) { $eachcount=1; $lcontrol = $currcontrol; }
+ if ($when == 'first' || !isset($last[$t])) {
+ $curr = FPLExpandItemVars($control, $matches, $i, $pseudovars);
+
+ if ($when == 'first' && ($neg xor (($i != 0) && ($last[$t] == $curr))))
+ { $last[$t] = $curr; continue; }
+ $last[$t] = $curr;
+ }
+ if ($when == 'last') {
+ $next = FPLExpandItemVars($control, $matches, $i+1, $pseudovars);
+ if ($neg xor ($next == $last[$t] && $i != count($matches) - 1)) continue;
+ $last[$t] = $next;
+ }
+ }
+ }
+ $item = FPLExpandItemVars($item, $matches, $i, $pseudovars);
+ $out .= MarkupRestore($item);
+ }
+ }
+
+ $class = preg_replace('/[^-a-zA-Z0-9\\x80-\\xff]/', ' ', @$opt['class']);
+ if ($class) $class = " class='$class'";
+ $wrap = @$opt['wrap'];
+ if ($wrap != 'inline') {
+ $out = $FPLTemplateMarkupFunction($pagename, $out, array('escape' => 0, 'redirect'=>1));
+ if ($wrap != 'none') $out = "<div$class>$out</div>";
+ }
+ $Cursor = $savecursor;
+ $output .= $out;
+}
+## This function moves repeated code blocks out of FPLTemplateFormat()
+function FPLExpandItemVars($item, $matches, $idx, $psvars) {
+ global $Cursor, $EnableUndefinedTemplateVars;
+ $Cursor['<'] = $Cursor['<'] = (string)@$matches[$idx-1];
+ $Cursor['='] = $pn = (string)@$matches[$idx];
+ $Cursor['>'] = $Cursor['>'] = (string)@$matches[$idx+1];
+ $item = str_replace(array_keys($psvars), array_values($psvars), $item);
+ $cb = new cb_pl_expandvars($pn);
+ $item = preg_replace_callback('/\\{(=|&[lg]t;)(\\$:?\\w[-\\w]*)\\}/',
+ array($cb, 'pl_expandvars'), $item);
+ if (! IsEnabled($EnableUndefinedTemplateVars, 0))
+ $item = preg_replace("/\\{\\$\\$\\w+\\}/", '', $item);
+ return $item;
+}
+
+########################################################################
+## The functions below optimize searches by maintaining a file of
+## words and link cross references (the "page index").
+########################################################################
+
+## PageIndexTerms($terms) takes an array of strings and returns a
+## normalized list of associated search terms. This reduces the
+## size of the index and speeds up searches.
+function PageIndexTerms($terms) {
+ global $StrFoldFunction;
+ $w = array();
+ foreach((array)$terms as $t) {
+ $w = array_merge($w, preg_split('/[^\\w\\x80-\\xff]+/',
+ $StrFoldFunction($t), -1, PREG_SPLIT_NO_EMPTY));
+ }
+ return $w;
+}
+
+## The PageIndexUpdate($pagelist) function updates the page index
+## file with terms and target links for the pages in $pagelist.
+## The optional $dir parameter allows this function to be called
+## via register_shutdown_function (which sometimes changes directories
+## on us).
+function PageIndexUpdate($pagelist = NULL, $dir = '') {
+ global $EnableReadOnly, $PageIndexUpdateList, $PageIndexFile,
+ $PageIndexTime, $Now;
+ if (IsEnabled($EnableReadOnly, 0)) return;
+ $abort = ignore_user_abort(true);
+ if ($dir) { flush(); chdir($dir); }
+ if (is_null($pagelist))
+ { $pagelist = (array)$PageIndexUpdateList; $PageIndexUpdateList = array(); }
+ if (!$pagelist || !$PageIndexFile) return;
+ SDV($PageIndexTime, 10);
+ $c = count($pagelist); $updatecount = 0;
+ StopWatch("PageIndexUpdate begin ($c pages to update)");
+ $pagelist = (array)$pagelist;
+ $timeout = time() + $PageIndexTime;
+ $cmpfn = 'PageIndexUpdateSort';
+ Lock(2);
+ $ofp = fopen("$PageIndexFile,new", 'w');
+ foreach($pagelist as $pn) {
+ if (@$updated[$pn]) continue;
+ @$updated[$pn]++;
+ if (time() > $timeout) continue;
+ $page = ReadPage($pn, READPAGE_CURRENT);
+ if ($page) {
+ $targets = str_replace(',', ' ', @$page['targets']);
+ $terms = PageIndexTerms(array(@$page['text'], $targets, $pn));
+ usort($terms, $cmpfn);
+ $x = '';
+ foreach($terms as $t) { if (strpos($x, $t) === false) $x .= " $t"; }
+ fputs($ofp, "$pn:$Now: $targets :$x\n");
+ }
+ $updatecount++;
+ }
+ $ifp = @fopen($PageIndexFile, 'r');
+ if ($ifp) {
+ while (!feof($ifp)) {
+ $line = fgets($ifp, 4096);
+ while (substr($line, -1, 1) != "\n" && !feof($ifp))
+ $line .= fgets($ifp, 4096);
+ $i = strpos($line, ':');
+ if ($i === false) continue;
+ $n = substr($line, 0, $i);
+ if (@$updated[$n]) continue;
+ fputs($ofp, $line);
+ }
+ fclose($ifp);
+ }
+ fclose($ofp);
+ if (file_exists($PageIndexFile)) unlink($PageIndexFile);
+ rename("$PageIndexFile,new", $PageIndexFile);
+ fixperms($PageIndexFile);
+ StopWatch("PageIndexUpdate end ($updatecount updated)");
+ ignore_user_abort($abort);
+}
+function PageIndexUpdateSort($a,$b) {return strlen($b)-strlen($a);}
+
+## PageIndexQueueUpdate specifies pages to be updated in
+## the index upon shutdown (via register_shutdown function).
+function PageIndexQueueUpdate($pagelist) {
+ global $PageIndexUpdateList;
+ if (!@$PageIndexUpdateList)
+ register_shutdown_function('PageIndexUpdate', NULL, getcwd());
+ $PageIndexUpdateList = array_merge((array)@$PageIndexUpdateList,
+ (array)$pagelist);
+ $c1 = @count($pagelist); $c2 = count($PageIndexUpdateList);
+ StopWatch("PageIndexQueueUpdate: queued $c1 pages ($c2 total)");
+}
+
+## PageIndexGrep returns a list of pages that match the strings
+## provided. Note that some search terms may need to be normalized
+## in order to get the desired results (see PageIndexTerms above).
+## Also note that this just works for the index; if the index is
+## incomplete, then so are the results returned by this list.
+## (MakePageList above already knows how to deal with this.)
+function PageIndexGrep($terms, $invert = false) {
+ global $PageIndexFile;
+ if (!$PageIndexFile) return array();
+ StopWatch('PageIndexGrep begin');
+ $pagelist = array();
+ $fp = @fopen($PageIndexFile, 'r');
+ if ($fp) {
+ $terms = (array)$terms;
+ while (!feof($fp)) {
+ $line = fgets($fp, 4096);
+ while (substr($line, -1, 1) != "\n" && !feof($fp))
+ $line .= fgets($fp, 4096);
+ $i = strpos($line, ':');
+ if (!$i) continue;
+ $add = true;
+ foreach($terms as $t)
+ if (strpos($line, $t) === false) { $add = false; break; }
+ if ($add xor $invert) $pagelist[] = substr($line, 0, $i);
+ }
+ fclose($fp);
+ }
+ StopWatch('PageIndexGrep end');
+ return $pagelist;
+}
+
+## PostPageIndex is inserted into $EditFunctions to update
+## the linkindex whenever a page is saved.
+function PostPageIndex($pagename, &$page, &$new) {
+ global $IsPagePosted;
+ if ($IsPagePosted) PageIndexQueueUpdate($pagename);
+}
--- /dev/null
+++ scripts/pagerev.php
@@ -0,0 +1,242 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2004-2019 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This script defines routines for displaying page revisions. It
+ is included by default from the stdconfig.php script.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+function LinkSuppress($pagename,$imap,$path,$title,$txt,$fmt=NULL)
+ { return $txt; }
+
+SDV($DiffShow['minor'],(@$_REQUEST['minor']!='n')?'y':'n');
+SDV($DiffShow['source'],(@$_REQUEST['source']!='n')?'y':'n');
+SDV($DiffMinorFmt, ($DiffShow['minor']=='y') ?
+ "<a href='{\$PageUrl}?action=diff&source=".$DiffShow['source']."&minor=n'>$[Hide minor edits]</a>" :
+ "<a href='{\$PageUrl}?action=diff&source=".$DiffShow['source']."&minor=y'>$[Show minor edits]</a>" );
+SDV($DiffSourceFmt, ($DiffShow['source']=='y') ?
+ "<a href='{\$PageUrl}?action=diff&source=n&minor=".$DiffShow['minor']."'>$[Show changes to output]</a>" :
+ "<a href='{\$PageUrl}?action=diff&source=y&minor=".$DiffShow['minor']."'>$[Show changes to markup]</a>");
+SDV($PageDiffFmt,"<h2 class='wikiaction'>$[{\$FullName} History]</h2>
+ <p>$DiffMinorFmt - $DiffSourceFmt</p>
+ ");
+SDV($DiffStartFmt,"
+ <div class='diffbox'><div class='difftime'><a name='diff\$DiffGMT' href='#diff\$DiffGMT'>\$DiffTime</a>
+ \$[by] <span class='diffauthor' title='\$DiffHost'>\$DiffAuthor</span> - \$DiffChangeSum</div>");
+SDV($DiffDelFmt['a'],"
+ <div class='difftype'>\$[Deleted line \$DiffLines:]</div>
+ <div class='diffdel'>");
+SDV($DiffDelFmt['c'],"
+ <div class='difftype'>\$[Changed line \$DiffLines from:]</div>
+ <div class='diffdel'>");
+SDV($DiffAddFmt['d'],"
+ <div class='difftype'>\$[Added line \$DiffLines:]</div>
+ <div class='diffadd'>");
+SDV($DiffAddFmt['c'],"</div>
+ <div class='difftype'>$[to:]</div>
+ <div class='diffadd'>");
+SDV($DiffEndDelAddFmt,"</div>");
+SDV($DiffEndFmt,"</div>");
+SDV($DiffRestoreFmt,"
+ <div class='diffrestore'><a href='{\$PageUrl}?action=edit&restore=\$DiffId&preview=y'>$[Restore]</a></div>");
+
+SDV($HandleActions['diff'], 'HandleDiff');
+SDV($HandleAuth['diff'], 'read');
+SDV($ActionTitleFmt['diff'], '| $[History]');
+SDV($HTMLStylesFmt['diff'], "
+ .diffbox { width:570px; border-left:1px #999999 solid; margin-top:1.33em; }
+ .diffauthor { font-weight:bold; }
+ .diffchangesum { font-weight:bold; }
+ .difftime { font-family:verdana,sans-serif; font-size:66%;
+ background-color:#dddddd; }
+ .difftype { clear:both; font-family:verdana,sans-serif;
+ font-size:66%; font-weight:bold; }
+ .diffadd { border-left:5px #99ff99 solid; padding-left:5px; }
+ .diffdel { border-left:5px #ffff99 solid; padding-left:5px; }
+ .diffrestore { clear:both; font-family:verdana,sans-serif;
+ font-size:66%; margin:1.5em 0px; }
+ .diffmarkup { font-family:monospace; }
+ .diffmarkup del { background:#ffff99; text-decoration: none; }
+ .diffmarkup ins { background:#99ff99; text-decoration: none; }");
+
+function PrintDiff($pagename) {
+ global $DiffHTMLFunction,$DiffShow,$DiffStartFmt,$TimeFmt,
+ $DiffEndFmt,$DiffRestoreFmt,$FmtV, $LinkFunctions;
+ $page = ReadPage($pagename);
+ if (!$page) return;
+ krsort($page); reset($page);
+ $lf = $LinkFunctions;
+ $LinkFunctions['http:'] = 'LinkSuppress';
+ $LinkFunctions['https:'] = 'LinkSuppress';
+ SDV($DiffHTMLFunction, 'DiffHTML');
+ foreach($page as $k=>$v) {
+ if (!preg_match("/^diff:(\d+):(\d+):?([^:]*)/",$k,$match)) continue;
+ $diffclass = $match[3];
+ if ($diffclass=='minor' && $DiffShow['minor']!='y') continue;
+ $diffgmt = $FmtV['$DiffGMT'] = $match[1];
+ $FmtV['$DiffTime'] = strftime($TimeFmt,$diffgmt);
+ $diffauthor = @$page["author:$diffgmt"];
+ if (!$diffauthor) @$diffauthor=$page["host:$diffgmt"];
+ if (!$diffauthor) $diffauthor="unknown";
+ $FmtV['$DiffChangeSum'] = PHSC(@$page["csum:$diffgmt"]);
+ $FmtV['$DiffHost'] = @$page["host:$diffgmt"];
+ $FmtV['$DiffUserAgent'] = PHSC(@$page["agent:$diffgmt"], ENT_QUOTES);
+ $FmtV['$DiffAuthor'] = $diffauthor;
+ $FmtV['$DiffId'] = $k;
+ $html = $DiffHTMLFunction($pagename, $v);
+ if ($html===false) continue;
+ echo FmtPageName($DiffStartFmt,$pagename);
+ echo $html;
+ echo FmtPageName($DiffEndFmt,$pagename);
+ echo FmtPageName($DiffRestoreFmt,$pagename);
+ }
+ $LinkFunctions = $lf;
+}
+
+# This function converts a single diff entry from the wikipage file
+# into HTML, ready for display.
+function DiffHTML($pagename, $diff) {
+ if (@$_REQUEST['nodiff']>'') return '';
+ global $FmtV, $DiffShow, $DiffAddFmt, $DiffDelFmt, $DiffEndDelAddFmt,
+ $DiffRenderSourceFunction;
+ SDV($DiffRenderSourceFunction, 'DiffRenderSource');
+ $difflines = explode("\n",$diff."\n");
+ $in=array(); $out=array(); $dtype=''; $html = '';
+ foreach($difflines as $d) {
+ if ($d>'') {
+ if ($d[0]=='-' || $d[0]=='\\') continue;
+ if ($d[0]=='<') { $out[]=substr($d,2); continue; }
+ if ($d[0]=='>') { $in[]=substr($d,2); continue; }
+ }
+ if (preg_match("/^(\\d+)(,(\\d+))?([adc])(\\d+)(,(\\d+))?/",
+ $dtype,$match)) {
+ if (@$match[7]>'') {
+ $lines='lines';
+ $count=$match[1].'-'.($match[1]+$match[7]-$match[5]);
+ } elseif ($match[3]>'') {
+ $lines='lines'; $count=$match[1].'-'.$match[3];
+ } else { $lines='line'; $count=$match[1]; }
+ if ($match[4]=='a' || $match[4]=='c') {
+ $txt = str_replace('line',$lines,$DiffDelFmt[$match[4]]);
+ $FmtV['$DiffLines'] = $count;
+ $html .= FmtPageName($txt,$pagename);
+ if ($DiffShow['source']=='y')
+ $html .= "<div class='diffmarkup'>"
+ .$DiffRenderSourceFunction($in, $out, 0)
+ ."</div>";
+ else $html .= MarkupToHTML($pagename,
+ preg_replace_callback('/\\(:.*?:\\)/',"cb_diffhtml", join("\n",$in)));
+ }
+ if ($match[4]=='d' || $match[4]=='c') {
+ $txt = str_replace('line',$lines,$DiffAddFmt[$match[4]]);
+ $FmtV['$DiffLines'] = $count;
+ $html .= FmtPageName($txt,$pagename);
+ if ($DiffShow['source']=='y')
+ $html .= "<div class='diffmarkup'>"
+ .$DiffRenderSourceFunction($in, $out, 1)
+ ."</div>";
+ else $html .= MarkupToHTML($pagename,
+ preg_replace_callback('/\\(:.*?:\\)/',"cb_diffhtml",join("\n",$out)));
+ }
+ $html .= FmtPageName($DiffEndDelAddFmt,$pagename);
+ }
+ $in=array(); $out=array(); $dtype=$d;
+ }
+ return $html;
+}
+function cb_diffhtml($m) { return Keep(PHSC($m[0])); }
+
+function HandleDiff($pagename, $auth='read') {
+ global $HandleDiffFmt, $PageStartFmt, $PageDiffFmt, $PageEndFmt;
+ $page = RetrieveAuthPage($pagename, $auth, true, READPAGE_CURRENT);
+ if (!$page) { Abort("?cannot diff $pagename"); }
+ PCache($pagename, $page);
+ SDV($HandleDiffFmt,array(&$PageStartFmt,
+ &$PageDiffFmt,"<div id='wikidiff'>", 'function:PrintDiff', '</div>',
+ &$PageEndFmt));
+ PrintFmt($pagename,$HandleDiffFmt);
+}
+## Functions for simple word-diff (written by Petko Yotov)
+function DiffRenderSource($in, $out, $which) {
+ global $WordDiffFunction, $EnableDiffInline;
+ if (!IsEnabled($EnableDiffInline, 1)) {
+ $a = $which? $out : $in;
+ return str_replace("\n","<br />",PHSC(join("\n",$a)));
+ }
+ $countdifflines = abs(count($in)-count($out));
+ $lines = $cnt = $x2 = $y2 = array();
+ foreach($in as $line) {
+ $tmp = $countdifflines>20 ? array($line) : DiffPrepareInline($line);
+ if (!$which) $cnt[] = array(count($x2), count($tmp));
+ $x2 = array_merge($x2, $tmp);
+ }
+ foreach($out as $line) {
+ $tmp = $countdifflines>20 ? array($line) : DiffPrepareInline($line);
+ if ($which) $cnt[] = array(count($y2), count($tmp));
+ $y2 = array_merge($y2, $tmp);
+ }
+ $z = $WordDiffFunction(implode("\n", $x2), implode("\n", $y2));
+
+ $z2 = array_map('PHSC', ($which? $y2 : $x2));
+ array_unshift($z2, '');
+ foreach (explode("\n", $z) as $zz) {
+ if (preg_match('/^(\\d+)(,(\\d+))?([adc])(\\d+)(,(\\d+))?/',$zz,$m)) {
+ $a1 = $a2 = $m[1];
+ if (@$m[3]) $a2=$m[3];
+ $b1 = $b2 = $m[5];
+ if (@$m[7]) $b2=$m[7];
+
+ if (!$which && ($m[4]=='c'||$m[4]=='d')) {
+ $z2[$a1] = '<del>'. $z2[$a1];
+ $z2[$a2] .= '</del>';
+ }
+ if ($which && ($m[4]=='c'||$m[4]=='a')) {
+ $z2[$b1] = '<ins>'.$z2[$b1];
+ $z2[$b2] .= '</ins>';
+ }
+ }
+ }
+ $line = array_shift($z2);
+ $z2[0] = $line.$z2[0];
+ foreach ($cnt as $a) $lines[] = implode('', array_slice($z2, $a[0], $a[1]));
+ $ret = implode("\n", $lines);
+ $ret = str_replace(array('</del> <del>', '</ins> <ins>'), ' ', $ret);
+ $ret = preg_replace('/(<(ins|del)>|^) /', '$1 ', $ret);
+ return str_replace(array(" ", "\n ", "\n"),array(" ", "<br /> ", "<br />"),$ret);
+}
+## Split a line into pieces before passing it through `diff`
+function DiffPrepareInline($x) {
+ global $DiffSplitInlineDelims;
+ SDV($DiffSplitInlineDelims, "-@!?#$%^&*()=+[]{}.'\"\\:|,<>_/;~");
+ return preg_split("/([".preg_quote($DiffSplitInlineDelims, '/')."\\s])/",
+ $x, -1, PREG_SPLIT_DELIM_CAPTURE);
+}
+
+SDV($WordDiffFunction, 'PHPDiff'); # faster than sysdiff for many calls
+if (IsEnabled($EnableDiffInline, 1) && $DiffShow['source'] == 'y'
+ && $WordDiffFunction == 'PHPDiff' && !function_exists('PHPDiff'))
+ include_once("$FarmD/scripts/phpdiff.php");
+
+## Show diff before the preview Cookbook:PreviewChanges
+function PreviewDiff($pagename,&$page,&$new) {
+ global $FmtV, $DiffFunction, $DiffHTMLFunction, $EnableDiffInline, $DiffShow;
+ if (@$_REQUEST['preview']>'' && @$page['text']>'' && $page['text']!=$new['text']) {
+ $d = IsEnabled($DiffShow['source'], 'y');
+ $e = IsEnabled($EnableDiffInline, 1);
+ $DiffShow['source'] = 'y';
+ $EnableDiffInline = 1;
+ SDV($DiffHTMLFunction, 'DiffHTML');
+ $diff = $DiffFunction($new['text'], $page['text']);# reverse the diff
+ $FmtV['$PreviewText'] = $DiffHTMLFunction($pagename, $diff).'<hr/>'.@$FmtV['$PreviewText'];
+ $DiffShow['source'] = $d;
+ $EnableDiffInline = $e;
+ }
+}
+if (IsEnabled($EnablePreviewChanges, 0) && @$_REQUEST['preview']>'') {
+ $EditFunctions[] = 'PreviewDiff';
+}
--- /dev/null
+++ scripts/pgcust.php
@@ -0,0 +1,39 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2002-2005 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This script enables per-page and per-group customizations in the
+ local/ subdirectory (or whatever directory is given by $LocalDir).
+ For example, to create customizations for the 'Demo' group, place
+ them in a file called local/Demo.php. To customize a single page,
+ use the full page name (e.g., local/Demo.MyPage.php).
+ Per-page/per-group customizations can be handled at any time by adding
+ include_once("scripts/pgcust.php");
+ to config.php. It is automatically included by scripts/stdconfig.php
+ unless $EnablePGCust is set to zero in config.php.
+
+ A page's customization is loaded first, followed by any group
+ customization. If no page or group customizations are loaded,
+ then 'local/default.php' is loaded.
+
+ A per-page configuration file can prevent its group's config from
+ loading by setting $EnablePGCust=0;. A per-page configuration file
+ can force group customizations to be loaded first by using include_once
+ on the group customization file.
+
+*/
+
+$f = 1;
+for($p=$pagename;$p;$p=preg_replace('/\\.*[^.]*$/','',$p)) {
+ if (!IsEnabled($EnablePGCust,1)) return;
+ if (file_exists("$LocalDir/$p.php"))
+ { include_once("$LocalDir/$p.php"); $f=0; }
+}
+
+if ($f && IsEnabled($EnablePGCust,1) && file_exists("$LocalDir/default.php"))
+ include_once("$LocalDir/default.php");
+
+
--- /dev/null
+++ scripts/phpdiff.php
@@ -0,0 +1,118 @@
+<?php if (!defined('PmWiki')) exit();
+/*
+ Copyright 2003,2004 Nils Knappmeier (nk@knappi.org)
+ Copyright 2004-2015 Patrick R. Michaud (pmichaud@pobox.com)
+
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This file implements a diff function in native PHP. It is based
+ upon the PHPDiffEngine code written by Nils Knappmeier, who in turn
+ had used Daniel Unterberger's diff
+ (http://www.holomind.de/phpnet/diff.php) as the basis for his code.
+
+ Pm's revision of Nils' code simply attempts to streamline it
+ for speed (eliminate function calls and unnecessary string ops)
+ and place everything into a single file.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+## PHPDiff returns the differences between $old and $new, formatted
+## in the standard diff(1) output format.
+function PHPDiff($old, $new)
+{
+ StopWatch("PHPDiff: begin");
+ # split the source text into arrays of lines
+ $t1 = explode("\n", $old);
+ $x = array_pop($t1);
+ if ($x > '') $t1[] = "$x\n\\ No newline at end of file";
+ $t2 = explode("\n", $new);
+ $x = array_pop($t2);
+ if ($x > '') $t2[] = "$x\n\\ No newline at end of file";
+
+ $t1_start = 0; $t1_end = count($t1);
+ $t2_start = 0; $t2_end = count($t2);
+
+ # stop with a common ending
+ while ($t1_start < $t1_end && $t2_start < $t2_end
+ && $t1[$t1_end-1] == $t2[$t2_end-1]) { $t1_end--; $t2_end--; }
+
+ # skip over any common beginning
+ while ($t1_start < $t1_end && $t2_start < $t2_end
+ && $t1[$t1_start] == $t2[$t2_start]) { $t1_start++; $t2_start++; }
+
+ # build a reverse-index array using the line as key and line number as value
+ # don't store blank lines, so they won't be targets of the shortest distance
+ # search
+ for($i = $t1_start; $i < $t1_end; $i++) if ($t1[$i]>'') $r1[$t1[$i]][] = $i;
+ for($i = $t2_start; $i < $t2_end; $i++) if ($t2[$i]>'') $r2[$t2[$i]][] = $i;
+
+ $a1 = $t1_start; $a2 = $t2_start; # start at beginning of each list
+ $actions = array();
+
+ # walk this loop until we reach the end of one of the lists
+ while ($a1 < $t1_end && $a2 < $t2_end) {
+ # if we have a common element, save it and go to the next
+ if ($t1[$a1] == $t2[$a2]) { $actions[] = 4; $a1++; $a2++; continue; }
+
+ # otherwise, find the shortest move (Manhattan-distance) from the
+ # current location
+ $best1 = $t1_end; $best2 = $t2_end;
+ $s1 = $a1; $s2 = $a2;
+ while(($s1 + $s2 - $a1 - $a2) < ($best1 + $best2 - $a1 - $a2)) {
+ $d = -1;
+ foreach((array)@$r1[$t2[$s2]] as $n)
+ if ($n >= $s1) { $d = $n; break; }
+ if ($d >= $s1 && ($d + $s2 - $a1 - $a2) < ($best1 + $best2 - $a1 - $a2))
+ { $best1 = $d; $best2 = $s2; }
+ $d = -1;
+ foreach((array)@$r2[$t1[$s1]] as $n)
+ if ($n >= $s2) { $d = $n; break; }
+ if ($d >= $s2 && ($s1 + $d - $a1 - $a2) < ($best1 + $best2 - $a1 - $a2))
+ { $best1 = $s1; $best2 = $d; }
+ $s1++; $s2++;
+ }
+ while ($a1 < $best1) { $actions[] = 1; $a1++; } # deleted elements
+ while ($a2 < $best2) { $actions[] = 2; $a2++; } # added elements
+ }
+
+ # we've reached the end of one list, now walk to the end of the other
+ while($a1 < $t1_end) { $actions[] = 1; $a1++; } # deleted elements
+ while($a2 < $t2_end) { $actions[] = 2; $a2++; } # added elements
+
+ # and this marks our ending point
+ $actions[] = 8;
+
+ # now, let's follow the path we just took and report the added/deleted
+ # elements into $out.
+ $op = 0;
+ $x0 = $x1 = $t1_start; $y0 = $y1 = $t2_start;
+ $out = array();
+ foreach($actions as $act) {
+ if ($act == 1) { $op |= $act; $x1++; continue; }
+ if ($act == 2) { $op |= $act; $y1++; continue; }
+ if ($op > 0) {
+ $xstr = ($x1 == ($x0+1)) ? $x1 : ($x0+1) . ",$x1";
+ $ystr = ($y1 == ($y0+1)) ? $y1 : ($y0+1) . ",$y1";
+ if ($op == 1) $out[] = "{$xstr}d{$y1}";
+ elseif ($op == 3) $out[] = "{$xstr}c{$ystr}";
+ while ($x0 < $x1) { $out[] = '< ' . $t1[$x0]; $x0++; } # deleted elems
+ if ($op == 2) $out[] = "{$x1}a{$ystr}";
+ elseif ($op == 3) $out[] = '---';
+ while ($y0 < $y1) { $out[] = '> '.$t2[$y0]; $y0++; } # added elems
+ }
+ $x1++; $x0 = $x1;
+ $y1++; $y0 = $y1;
+ $op = 0;
+ }
+ $out[] = '';
+ StopWatch("PHPDiff: end");
+ return join("\n",$out);
+}
+
+if (!function_exists(@$DiffFunction))
+ $DiffFunction = 'PHPDiff';
+
--- /dev/null
+++ scripts/prefs.php
@@ -0,0 +1,59 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2005-2019 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This script handles per-browser preferences. Preference settings
+ are stored in wiki pages as XLPage translations, and a cookie on
+ the browser tells PmWiki where to find the browser's preferred
+ settings.
+
+ This script looks for a ?setprefs= request parameter (e.g., in
+ a url); when it finds one it sets a 'setprefs' cookie on the browser
+ identifying the page to be used to load browser preferences,
+ and loads the associated preferences.
+
+ If there is no ?setprefs= request, then the script uses the
+ 'setprefs' cookie from the browser to load the preference settings.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+SDV($PrefsCookie, $CookiePrefix.'setprefs');
+SDV($PrefsCookieExpires, $Now + 60 * 60 * 24 * 365);
+$LogoutCookies[] = $PrefsCookie;
+
+$sp = '';
+if (@$_COOKIE[$PrefsCookie]) $sp = $_COOKIE[$PrefsCookie];
+if (isset($_GET['setprefs'])) {
+ $sp = MakePageName($pagename, $_GET['setprefs']);
+ pmsetcookie($PrefsCookie, $sp, $PrefsCookieExpires, '/');
+}
+if ($sp && PageExists($sp)) XLPage('prefs', $sp, true);
+
+if(@is_array($XL['prefs'])) {
+ foreach($XL['prefs'] as $k=>$v) {
+ if(! preg_match('/^(e_rows|e_cols|TimeFmt|Locale|Site\\.EditForm)$|^ak_/', $k))
+ unset($XL['prefs'][$k]);
+ }
+}
+
+XLSDV('en', array(
+ 'ak_view' => '',
+ 'ak_edit' => 'e',
+ 'ak_history' => 'h',
+ 'ak_attach' => '',
+ 'ak_backlinks' => '',
+ 'ak_logout' => '',
+ 'ak_print' => '',
+ 'ak_recentchanges' => 'c',
+ 'ak_save' => 's',
+ 'ak_saveedit' => 'u',
+ 'ak_savedraft' => 'd',
+ 'ak_preview' => 'p',
+ 'ak_em' => '',
+ 'ak_strong' => '',
+ ));
+
--- /dev/null
+++ scripts/refcount.php
@@ -0,0 +1,123 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2004-2018 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This file does simple reference counting on pages in a PmWiki site.
+ Simply activate this script using
+ include_once('scripts/refcount.php');
+ in the config.php file and then use ?action=refcount to bring up
+ the reference count form. The output is a table where each row
+ of the table contains a page name or link reference, the number
+ of (non-RecentChanges) pages that contain links to the page,
+ the number of RecentChanges pages with links to the page, and the
+ total number of references in all pages.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+SDV($PageRefCountFmt,"<h2 class='wikiaction'>Reference Count Results</h2>");
+SDV($RefCountTimeFmt," <small>%Y-%b-%d %H:%M</small>");
+SDV($HandleActions['refcount'], 'HandleRefCount');
+
+function PrintRefCount($pagename) {
+ global $GroupPattern,$NamePattern,$PageRefCountFmt,$RefCountTimeFmt, $ScriptUrl;
+ $pagelist = ListPages();
+ $grouplist = array();
+ foreach($pagelist as $pname) {
+ if (!preg_match("/^($GroupPattern)[\\/.]($NamePattern)$/",$pname,$m))
+ continue;
+ $grouplist[$m[1]]=$m[1];
+ }
+ asort($grouplist);
+ $grouplist = array_merge(array('all' => 'all groups'),$grouplist);
+
+ $wlist = array('all','missing','existing','orphaned');
+ $tlist = isset($_REQUEST['tlist']) ? $_REQUEST['tlist'] : array('all');
+ $flist = isset($_REQUEST['flist']) ? $_REQUEST['flist'] : array('all');
+ $whichrefs = @$_REQUEST['whichrefs'];
+ $showrefs = (@$_REQUEST['showrefs']=='checked')? "checked='checked'" : '';
+ $submit = @$_REQUEST['submit'];
+
+ echo FmtPageName($PageRefCountFmt,$pagename);
+ echo FmtPageName("<form method='post' action='{\$PageUrl}'><input type='hidden' name='action' value='refcount'/>
+ <table cellspacing='10'><tr><td valign='top'>Show
+ <br/><select name='whichrefs'>",$pagename);
+ foreach($wlist as $w)
+ echo "<option ",($whichrefs==$w) ? 'selected="selected"' : ''," value='$w'>$w</option>\n";
+ echo "</select></td><td valign='top'> page names in group<br/>
+ <select name='tlist[]' multiple='multiple' size='4'>";
+ foreach($grouplist as $g=>$t)
+ echo "<option ",in_array($g,$tlist) ? 'selected="selected"' : ''," value='$g'>$t</option>\n";
+ echo "</select></td><td valign='top'> referenced from pages in<br/>
+ <select name='flist[]' multiple='multiple' size='4'>";
+ foreach($grouplist as $g=>$t)
+ echo "<option ",in_array($g,$flist) ? 'selected="selected"' : ''," value='$g'>$t</option>\n";
+ echo "</select></td></tr></table>
+ <p><input type='checkbox' name='showrefs' id='chk_showrefs' value='checked' $showrefs/>
+ <label for='chk_showrefs'>Display referencing pages</label>
+ </p><p><input type='submit' name='submit' value='Search'/></p></form><hr/>";
+
+ if ($submit) {
+ foreach($pagelist as $pname) {
+ $ref = array();
+ $page = ReadPage($pname, READPAGE_CURRENT);
+ if (!$page) continue;
+ $tref[$pname]['time'] = $page['time'];
+ if (!in_array('all',$flist) &&
+ !in_array(FmtPageName('$Group',$pname),$flist)) continue;
+ $rc = preg_match('/RecentChanges$/',$pname);
+ foreach(explode(',',@$page['targets']) as $r) {
+ if ($r=='') continue;
+ if ($rc) @$tref[$r]['rc']++;
+ else { @$tref[$r]['page']++; @$pref[$r][$pname]++; }
+ }
+ }
+ uasort($tref,'RefCountCmp');
+ echo "<table class='refcount'>
+ <tr><th></th><th colspan='2'>Referring pages</th></tr>
+ <tr><th>Name / Time</th><th>All</th><th>R.C.</th></tr>";
+ reset($tref);
+ foreach($tref as $p=>$c) {
+ if (!in_array('all',$tlist) &&
+ !in_array(FmtPageName('$Group',$p),$tlist)) continue;
+ if ($whichrefs=='missing' && PageExists($p)) continue;
+ elseif ($whichrefs=='existing' && !PageExists($p)) continue;
+ elseif ($whichrefs=='orphaned' &&
+ (@$tref[$p]['page']>0 || !PageExists($p))) continue;
+ echo "<tr><td valign='top'>",LinkPage($pagename, '', $p, '', $p);
+ if (@$tref[$p]['time']) echo strftime($RefCountTimeFmt,$tref[$p]['time']);
+ if ($showrefs && is_array(@$pref[$p])) {
+ foreach($pref[$p] as $pr=>$pc)
+ echo "<dd>", LinkPage($pagename, '', $pr, '', $pr);
+ }
+ echo "</td>";
+ echo "<td align='center' valign='top'>",@$tref[$p]['page']+0,"</td>";
+ echo "<td align='center' valign='top'>",@$tref[$p]['rc']+0,"</td>";
+ echo "</tr>";
+ }
+ echo "</table>";
+ }
+}
+
+
+function RefCountCmp($ua,$ub) {
+ if (@($ua['page']!=$ub['page'])) return @($ub['page']-$ua['page']);
+ if (@($ua['rc']!=$ub['rc'])) return @($ub['rc']-$ua['rc']);
+ return @($ub['time']-$ua['time']);
+}
+
+
+
+function HandleRefCount($pagename, $auth='read') {
+ global $HandleRefCountFmt,$PageStartFmt,$PageEndFmt;
+ $page = RetrieveAuthPage($pagename, $auth, true, READPAGE_CURRENT);
+ if (!$page) Abort('?unauthorized');
+ PCache($pagename, $page);
+ SDV($HandleRefCountFmt,array(&$PageStartFmt,
+ 'function:PrintRefCount',&$PageEndFmt));
+ PrintFmt($pagename,$HandleRefCountFmt);
+}
+
--- /dev/null
+++ scripts/robots.php
@@ -0,0 +1,81 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2005-2017 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This file provides various features to allow PmWiki to control
+ what web crawlers (robots) see when they visit the site. Of course
+ it's still possible to control robots at the webserver level
+ and via robots.txt, but this page provides some finer level
+ of control.
+
+ The $MetaRobots variable controls generation of the
+ <meta name='robots' ... /> tag in the head of the HTML document.
+ By default $MetaRobots is set so that robots do not index pages in
+ the Site, SiteAdmin, and PmWiki groups.
+
+ The $RobotPattern variable is used to determine if the user agent
+ accessing the site is a robot, and $IsRobotAgent is set accordingly.
+ By default this pattern identifies Googlebot, Yahoo! Slurp, msnbot,
+ BecomeBot, and HTTrack as robots.
+
+ If the agent is deemed a robot, then the $RobotActions array is
+ checked to see if robots are allowed to perform the given action,
+ and if not the robot is immediately sent an HTTP 403 Forbidden
+ response.
+
+ If $EnableRobotCloakActions is set, then a pattern is added to
+ $FmtP to hide any "?action=" url parameters in page urls
+ generated by PmWiki for actions that robots aren't allowed to
+ access. This can greatly reduce the load on the server by
+ not providing the robot with links to pages that it will be
+ forbidden to index anyway.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+## $MetaRobots provides the value for the <meta name='robots' ...> tag.
+SDV($MetaRobots,
+ ($action!='browse' || !PageExists($pagename)
+ || preg_match('#^PmWiki[./](?!PmWiki$)|^Site(Admin)?[./]#', $pagename))
+ ? 'noindex,nofollow' : 'index,follow');
+if ($MetaRobots)
+ $HTMLHeaderFmt['robots'] =
+ " <meta name='robots' content='\$MetaRobots' />\n";
+
+## $RobotPattern is used to identify robots.
+SDV($RobotPattern,'\\w+[-_ ]?(bot|spider|crawler)'
+ .'|Slurp|Teoma|ia_archiver|HTTrack|XML Sitemaps|Jabse|Yandex|PageAnalyzer|Yeti|Riddler|Aboundex|ADmantX|WikiDo'
+ .'|Pinterest|Qwantify|worldwebheritage|coccoc|HostWallker|Add Catalog|idmarch|MegaIndex|heritrix|SEOdiver');
+SDV($IsRobotAgent,
+ $RobotPattern && preg_match("!$RobotPattern!i", @$_SERVER['HTTP_USER_AGENT']));
+if (!$IsRobotAgent) return;
+
+## $RobotActions indicates which actions a robot is allowed to perform.
+SDVA($RobotActions, array('browse' => 1, 'rss' => 1, 'dc' => 1));
+if (!@$RobotActions[$action]) {
+ $pagename = ResolvePageName($pagename);
+ if (!PageExists($pagename)) {
+ header("HTTP/1.1 404 Not Found");
+ print("<h1>Not Found</h1>");
+ exit();
+ }
+ header("HTTP/1.1 403 Forbidden");
+ print("<h1>Forbidden</h1>");
+ exit();
+}
+
+## The following removes any ?action= parameters that robots aren't
+## allowed to access.
+function cb_bool($a) { return (boolean)$a; }
+if (IsEnabled($EnableRobotCloakActions, 0)) {
+ $p = join('|', array_keys(array_filter($RobotActions, 'cb_bool')));
+ $FmtPV['$PageUrl'] =
+ 'PUE(($EnablePathInfo)
+ ? "\\$ScriptUrl/$group/$name"
+ : "\\$ScriptUrl?n=$group.$name")';
+ $FmtP["/(\\\$ScriptUrl[^#\"'\\s<>]+)\\?action=(?!$p)\\w+/"] = '$1';
+}
+
--- /dev/null
+++ scripts/simuledit.php
@@ -0,0 +1,76 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2004-2015 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This file enables merging of concurrent edits, using the "diff3"
+ program available on most Unix systems to merge the edits. If
+ diff3 is not available or you'd like to use a different command,
+ then set $SysMergeCmd accordingly.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+array_unshift($EditFunctions,'MergeSimulEdits');
+$HTMLStylesFmt['simuledit'] = ".editconflict { color:green;
+ font-style:italic; margin-top:1.33em; margin-bottom:1.33em; }\n";
+
+function Merge($newtext,$oldtext,$pagetext) {
+ global $WorkDir,$SysMergeCmd, $SysMergePassthru;
+ SDV($SysMergeCmd,"/usr/bin/diff3 -L '' -L '' -L '' -m -E");
+ if (substr($newtext,-1,1)!="\n") $newtext.="\n";
+ if (substr($oldtext,-1,1)!="\n") $oldtext.="\n";
+ if (substr($pagetext,-1,1)!="\n") $pagetext.="\n";
+ $tempnew = tempnam($WorkDir,"new");
+ $tempold = tempnam($WorkDir,"old");
+ $temppag = tempnam($WorkDir,"page");
+ if ($newfp=fopen($tempnew,'w')) { fputs($newfp,$newtext); fclose($newfp); }
+ if ($oldfp=fopen($tempold,'w')) { fputs($oldfp,$oldtext); fclose($oldfp); }
+ if ($pagfp=fopen($temppag,'w')) { fputs($pagfp,$pagetext); fclose($pagfp); }
+ $mergetext = '';
+ if (IsEnabled($SysMergePassthru, 0)) {
+ ob_start();
+ passthru("$SysMergeCmd $tempnew $tempold $temppag");
+ $mergetext = ob_get_clean();
+ }
+ else {
+ $merge_handle = popen("$SysMergeCmd $tempnew $tempold $temppag",'r');
+ if ($merge_handle) {
+ while (!feof($merge_handle)) $mergetext .= fread($merge_handle,4096);
+ pclose($merge_handle);
+ }
+ }
+ @unlink($tempnew); @unlink($tempold); @unlink($temppag);
+ return $mergetext;
+}
+
+function MergeSimulEdits($pagename,&$page,&$new) {
+ global $Now, $EnablePost, $MessagesFmt, $WorkDir;
+ if (@!$_POST['basetime'] || !PageExists($pagename)
+ || $page['time'] >= $Now
+ || $_POST['basetime']>=$page['time']
+ || $page['text'] == $new['text']) return;
+ $EnablePost = 0;
+ $old = array();
+ RestorePage($pagename,$page,$old,"diff:{$_POST['basetime']}");
+ $text = Merge($new['text'],$old['text'],$page['text']);
+ if ($text > '') { $new['text'] = $text; $ec = '$[EditConflict]'; }
+ else $ec = '$[EditWarning]';
+ XLSDV('en', array(
+ 'EditConflict' => "The page you are
+ editing has been modified since you started editing it.
+ The modifications have been merged into the text below,
+ you may want to verify the results of the merge before
+ pressing save. Conflicts the system couldn't resolve are
+ bracketed by <<<<<<< and
+ >>>>>>>.",
+ 'EditWarning' => "The page you are editing has been modified
+ since you started editing it. If you continue, your
+ changes will overwrite any changes that others have made."));
+ $MessagesFmt[] = "<p class='editconflict'>$ec
+ (<a target='_blank' href='\$PageUrl?action=diff'>$[View changes]</a>)
+ </p>\n";
+}
+
--- /dev/null
+++ scripts/skins.php
@@ -0,0 +1,234 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2004-2020 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This file implements the skin selection code for PmWiki. Skin
+ selection is controlled by the $Skin variable, which can also
+ be an array (in which case the first skin found is loaded).
+
+ In addition, $ActionSkin[$action] specifies other skins to be
+ searched based on the current action.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+SDV($Skin, 'pmwiki');
+SDV($ActionSkin['print'], 'print');
+SDV($FarmPubDirUrl, $PubDirUrl);
+SDV($PageLogoUrl, "$FarmPubDirUrl/skins/pmwiki/pmwiki-32.gif");
+SDVA($TmplDisplay, array('PageEditFmt' => 0));
+
+## from skinchange.php
+if (IsEnabled($EnableAutoSkinList, 0) || isset($PageSkinList)) {
+ SDV($SkinCookie, $CookiePrefix.'setskin');
+ SDV($SkinCookieExpires, $Now+60*60*24*365);
+
+ if (isset($_COOKIE[$SkinCookie])) $sk = $_COOKIE[$SkinCookie];
+ if (isset($_GET['setskin'])) {
+ $sk = $_GET['setskin'];
+ pmsetcookie($SkinCookie, $sk, $SkinCookieExpires, '/');
+ if(@$EnableIMSCaching) {
+ SDV($IMSCookie, $CookiePrefix.'imstime');
+ pmsetcookie($IMSCookie, '', $Now -3600, '/');
+ $EnableIMSCaching = 0;
+ }
+ }
+ if (isset($_GET['skin'])) $sk = $_GET['skin'];
+
+ ## If $EnableAutoSkinList is set, then we accept any skin that
+ ## exists in pub/skins/ or $FarmD/pub/skins/ .
+ if (IsEnabled($EnableAutoSkinList, 0)
+ && @$sk && preg_match('/^[-\\w]+$/', $sk)
+ && (is_dir("pub/skins/$sk") || is_dir("$FarmD/pub/skins/$sk")))
+ $Skin = $sk;
+
+ ## If there's a specific mapping in $PageSkinList, we use it no
+ ## matter what.
+ if (@$PageSkinList[$sk]) $Skin = $PageSkinList[$sk];
+}
+
+# $PageTemplateFmt is deprecated
+if (isset($PageTemplateFmt)) LoadPageTemplate($pagename,$PageTemplateFmt);
+else {
+ $x = array_merge((array)@$ActionSkin[$action], (array)$Skin);
+ SetSkin($pagename, $x);
+}
+
+SDV($PageCSSListFmt,array(
+ 'pub/css/local.css' => '$PubDirUrl/css/local.css',
+ 'pub/css/{$Group}.css' => '$PubDirUrl/css/{$Group}.css',
+ 'pub/css/{$FullName}.css' => '$PubDirUrl/css/{$FullName}.css'));
+
+foreach((array)$PageCSSListFmt as $k=>$v)
+ if (file_exists(FmtPageName($k,$pagename)))
+ $HTMLHeaderFmt[] = "<link rel='stylesheet' type='text/css' href='$v' />\n";
+
+if(IsEnabled($WikiPageCSSFmt, false))
+ InsertWikiPageCSS($pagename, $WikiPageCSSFmt);
+
+# SetSkin changes the current skin to the first available skin from
+# the $skin array.
+function SetSkin($pagename, $skin) {
+ global $Skin, $SkinLibDirs, $SkinDir, $SkinDirUrl,
+ $IsTemplateLoaded, $PubDirUrl, $FarmPubDirUrl, $FarmD, $GCount;
+ SDV($SkinLibDirs, array(
+ "./pub/skins/\$Skin" => "$PubDirUrl/skins/\$Skin",
+ "$FarmD/pub/skins/\$Skin" => "$FarmPubDirUrl/skins/\$Skin"));
+ foreach((array)$skin as $sfmt) {
+ $Skin = FmtPageName($sfmt, $pagename); $GCount = 0;
+ foreach($SkinLibDirs as $dirfmt => $urlfmt) {
+ $SkinDir = FmtPageName($dirfmt, $pagename);
+ if (is_dir($SkinDir))
+ { $SkinDirUrl = FmtPageName($urlfmt, $pagename); break 2; }
+ }
+ }
+ if (!is_dir($SkinDir)) {
+ unset($Skin);
+ Abort("?unable to find skin from list ".implode(' ',(array)$skin));
+ }
+ $IsTemplateLoaded = 0;
+ if (file_exists("$SkinDir/$Skin.php"))
+ include_once("$SkinDir/$Skin.php");
+ else if (file_exists("$SkinDir/skin.php"))
+ include_once("$SkinDir/skin.php");
+ if ($IsTemplateLoaded) return;
+ if (file_exists("$SkinDir/$Skin.tmpl"))
+ LoadPageTemplate($pagename, "$SkinDir/$Skin.tmpl");
+ else if (file_exists("$SkinDir/skin.tmpl"))
+ LoadPageTemplate($pagename, "$SkinDir/skin.tmpl");
+ else if (($dh = opendir($SkinDir))) {
+ while (($fname = readdir($dh)) !== false) {
+ if ($fname[0] == '.') continue;
+ if (substr($fname, -5) != '.tmpl') continue;
+ if ($IsTemplateLoaded)
+ Abort("?unable to find unique template in $SkinDir");
+ LoadPageTemplate($pagename, "$SkinDir/$fname");
+ }
+ closedir($dh);
+ }
+ if (!$IsTemplateLoaded) Abort("Unable to load $Skin template", 'skin');
+}
+
+function cb_includeskintemplate($m) {
+ global $SkinDir, $pagename;
+ $x = preg_split('/\\s+/', $m[1], -1, PREG_SPLIT_NO_EMPTY);
+ for ($i=0; $i<count($x); $i++) {
+ $f = FmtPageName("$SkinDir/{$x[$i]}", $pagename);
+ if (strpos($f, '..')!==false || preg_match('/%2e/i', $f)) continue;
+ if (file_exists($f)) return implode('', file($f));
+ }
+}
+
+# LoadPageTemplate loads a template into $TmplFmt
+function LoadPageTemplate($pagename,$tfilefmt) {
+ global $PageStartFmt, $PageEndFmt, $SkinTemplateIncludeLevel,
+ $EnableSkinDiag, $HTMLHeaderFmt, $HTMLFooterFmt,
+ $IsTemplateLoaded, $TmplFmt, $TmplDisplay,
+ $PageTextStartFmt, $PageTextEndFmt, $SkinDirectivesPattern;
+
+ SDV($PageTextStartFmt, "\n<div id='wikitext'>\n");
+ SDV($PageTextEndFmt, "</div>\n");
+ SDV($SkinDirectivesPattern,
+ "[[<]!--((?:wiki|file|function|markup):.*?)--[]>]");
+
+ $sddef = array('PageEditFmt' => 0);
+ $k = implode('', file(FmtPageName($tfilefmt, $pagename)));
+
+ for ($i=0; $i<IsEnabled($SkinTemplateIncludeLevel, 0) && $i<10; $i++) {
+ if (stripos($k, '<!--IncludeTemplate')===false) break;
+ $k = preg_replace_callback('/[<]!--IncludeTemplate: *(\\S.*?) *--[>]/i',
+ 'cb_includeskintemplate', $k);
+ }
+
+ if (IsEnabled($EnableSkinDiag, 0)) {
+ if (!preg_match('/<!--((No)?(HT|X)MLHeader|HeaderText)-->/i', $k))
+ Abort("Skin template missing <!--HTMLHeader-->", 'htmlheader');
+ if (!preg_match('/<!--(No)?(HT|X)MLFooter-->/i', $k))
+ Abort("Skin template missing <!--HTMLFooter-->", 'htmlheader');
+ }
+
+ $sect = preg_split(
+ '#[[<]!--(/?(?:Page[A-Za-z]+Fmt|(?:HT|X)ML(?:Head|Foot)er|HeaderText|PageText).*?)--[]>]#',
+ $k, 0, PREG_SPLIT_DELIM_CAPTURE);
+ $TmplFmt['Start'] = array_merge(array('headers:'),
+ preg_split("/$SkinDirectivesPattern/s",
+ array_shift($sect),0,PREG_SPLIT_DELIM_CAPTURE));
+ $TmplFmt['End'] = array($PageTextEndFmt);
+ $ps = 'Start';
+ while (count($sect)>0) {
+ $k = array_shift($sect);
+ $v = preg_split("/$SkinDirectivesPattern/s",
+ array_shift($sect),0,PREG_SPLIT_DELIM_CAPTURE);
+ $TmplFmt[$ps][] = "<!--$k-->";
+ if ($k[0] == '/')
+ { $TmplFmt[$ps][] = (count($v) > 1) ? $v : $v[0]; continue; }
+ @list($var, $sd) = explode(' ', $k, 2);
+ $GLOBALS[$var] = (count($v) > 1) ? $v : $v[0];
+ if ($sd > '') $sddef[$var] = $sd;
+ if ($var == 'PageText') { $ps = 'End'; }
+ if ($var == 'HTMLHeader' || $var == 'XMLHeader')
+ $TmplFmt[$ps][] = &$HTMLHeaderFmt;
+ if ($var == 'HTMLFooter' || $var == 'XMLFooter')
+ $TmplFmt[$ps][] = &$HTMLFooterFmt;
+ ## <!--HeaderText--> deprecated, 2.1.16
+ if ($var == 'HeaderText') { $TmplFmt[$ps][] = &$HTMLHeaderFmt; }
+ $TmplFmt[$ps][$var] =& $GLOBALS[$var];
+ }
+ array_push($TmplFmt['Start'], $PageTextStartFmt);
+ $PageStartFmt = 'function:PrintSkin Start';
+ $PageEndFmt = 'function:PrintSkin End';
+ $IsTemplateLoaded = 1;
+ SDVA($TmplDisplay, $sddef);
+}
+
+# This function is called to print a portion of the skin template
+# according to the settings in $TmplDisplay.
+function PrintSkin($pagename, $arg) {
+ global $TmplFmt, $TmplDisplay;
+ foreach ($TmplFmt[$arg] as $k => $v)
+ if (!isset($TmplDisplay[$k]) || $TmplDisplay[$k])
+ PrintFmt($pagename, $v);
+}
+
+# This function parses a wiki page like Site.LocalCSS
+# and inserts CSS rules specific to the current page.
+# Based on Cookbook:LocalCSS by Petko Yotov
+function InsertWikiPageCSS($pagename, $fmt) {
+ global $HTMLStylesFmt, $EnableSelfWikiPageCSS, $WikiPageCSSVars;
+ SDV($WikiPageCSSVars,array('FarmPubDirUrl','PubDirUrl','Skin','action','SkinDirUrl'));
+
+ $stylepagename = FmtPageName($fmt, $pagename);
+ if ($stylepagename == $pagename &&
+ !IsEnabled($EnableSelfWikiPageCSS, 0)) return;
+
+ if ($stylepagename == $pagename && @$_POST['text'])
+ $text = stripmagic($_POST['text']);
+ else {
+ $p = ReadPage($stylepagename, READPAGE_CURRENT);
+ $text = @$p['text'];
+ }
+ if (!$text) return;
+
+ $text = str_replace(array("\r",'$','<','$='),array('','$','<','$='), $text);
+ $varray = array();
+
+ # global PHP variables as @variables
+ foreach($WikiPageCSSVars as $var) $varray["@$var"] = $GLOBALS[$var];
+
+ # get @variables from page
+ if (preg_match_all("/^\\s*(@\\w+):\\s*(.*?)\\s*$/m", $text, $vars) )
+ foreach($vars[1] as $k=>$varname) $varray[$varname] = trim($vars[2][$k]);
+
+ # expand nested @variables
+ for ($i=0; $i<10; $i++) $text = strtr($text, $varray);
+
+ # process snippets
+ if (preg_match_all("/\\[@\\s*([^\\/!\\s]+)\n(.*?)\\s*@\\]/s", $text, $matches, PREG_SET_ORDER) )
+ foreach($matches as $a)
+ if (count(MatchPageNames($pagename, trim($a[1]))))
+ @$HTMLStylesFmt['WikiPageCSS'] .= trim($a[2]);
+}
+
--- /dev/null
+++ scripts/stdconfig.php
@@ -0,0 +1,118 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2002-2019 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This file allows features to be easily enabled/disabled in config.php.
+ Simply set variables for the features to be enabled/disabled in config.php
+ before including this file. For example:
+ $EnableQAMarkup=0; #disable Q: and A: tags
+ $EnableWikiStyles=1; #include default wikistyles
+ Each feature has a default setting, if the corresponding $Enable
+ variable is not set then you get the default.
+
+ To avoid processing any of the features of this file, set
+ $EnableStdConfig = 0;
+ in config.php.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+$pagename = ResolvePageName($pagename);
+
+if (!IsEnabled($EnableStdConfig,1)) return;
+
+
+if (!function_exists('session_start') && IsEnabled($EnableRequireSession, 1))
+ Abort('PHP is lacking session support', 'session');
+
+if (IsEnabled($EnablePGCust,1))
+ include_once("$FarmD/scripts/pgcust.php");
+
+if (isset($PostConfig) && is_array($PostConfig)) {
+ asort($PostConfig, SORT_NUMERIC);
+ foreach ($PostConfig as $k=>$v) {
+ if (!$k || !$v || $v<0 || $v>=50) continue;
+ if (function_exists($k)) $k($pagename);
+ elseif (file_exists($k)) include_once($k);
+ }
+}
+
+if (IsEnabled($EnableRobotControl,1))
+ include_once("$FarmD/scripts/robots.php");
+
+if (IsEnabled($EnableCaches, 1))
+ include_once("$FarmD/scripts/caches.php");
+
+## Scripts that are part of a standard PmWiki distribution.
+if (IsEnabled($EnableAuthorTracking,1))
+ include_once("$FarmD/scripts/author.php");
+if (IsEnabled($EnablePrefs, 1))
+ include_once("$FarmD/scripts/prefs.php");
+if (IsEnabled($EnableSimulEdit, 1))
+ include_once("$FarmD/scripts/simuledit.php");
+if (IsEnabled($EnableDrafts, 0))
+ include_once("$FarmD/scripts/draft.php"); # after simuledit + prefs
+if (IsEnabled($EnableSkinLayout,1))
+ include_once("$FarmD/scripts/skins.php"); # must come after prefs
+if (@$Transition || IsEnabled($EnableTransitions, 0))
+ include_once("$FarmD/scripts/transition.php"); # must come after skins
+if (@$LinkWikiWords || IsEnabled($EnableWikiWords, 0))
+ include_once("$FarmD/scripts/wikiwords.php"); # must come before stdmarkup
+if (IsEnabled($EnableStdMarkup,1))
+ include_once("$FarmD/scripts/stdmarkup.php"); # must come after transition
+if (($action=='diff' && @!$HandleActions['diff'])
+ || (IsEnabled($EnablePreviewChanges, 0) && @$_REQUEST['preview']>''))
+ include_once("$FarmD/scripts/pagerev.php");
+if (IsEnabled($EnableWikiTrails,1))
+ include_once("$FarmD/scripts/trails.php");
+if (IsEnabled($EnableWikiStyles,1))
+ include_once("$FarmD/scripts/wikistyles.php");
+if (IsEnabled($EnableMarkupExpressions, 1)
+ && !function_exists('MarkupExpression'))
+ include_once("$FarmD/scripts/markupexpr.php");
+if (IsEnabled($EnablePageList,1))
+ include_once("$FarmD/scripts/pagelist.php");
+if (IsEnabled($EnableVarMarkup,1))
+ include_once("$FarmD/scripts/vardoc.php");
+if (!function_exists(@$DiffFunction))
+ include_once("$FarmD/scripts/phpdiff.php");
+if ($action=='crypt')
+ include_once("$FarmD/scripts/crypt.php");
+if ($action=='edit')
+ include_once("$FarmD/scripts/guiedit.php");
+if (IsEnabled($EnableForms,1))
+ include_once("$FarmD/scripts/forms.php"); # must come after prefs
+if (IsEnabled($EnableUpload,0))
+ include_once("$FarmD/scripts/upload.php"); # must come after forms
+if (IsEnabled($EnableBlocklist, 0))
+ include_once("$FarmD/scripts/blocklist.php");
+if (IsEnabled($EnableNotify,0))
+ include_once("$FarmD/scripts/notify.php");
+if (IsEnabled($EnableDiag,0) || $action == 'recipecheck')
+ include_once("$FarmD/scripts/diag.php");
+
+if (IsEnabled($PmTOC['Enable'],0) || IsEnabled($PmEmbed,0) || IsEnabled($EnableSortable,0)
+ || $LinkFunctions['mailto:'] == 'ObfuscateLinkIMap' || IsEnabled($EnableHighlight, 0)
+ || IsEnabled($ToggleNextSelector, 0)
+ ) {
+ $utils = "$FarmD/pub/pmwiki-utils.js";
+ if(file_exists($utils)) {
+ $mtime = filemtime($utils);
+ $HTMLFooterFmt['pmwiki-utils'] =
+ "<script type='text/javascript' src='\$FarmPubDirUrl/pmwiki-utils.js?st=$mtime'
+ data-sortable='".@$EnableSortable."' data-highlight='".@$EnableHighlight."'
+ data-pmtoc='".PHSC(json_encode(@$PmTOC), ENT_QUOTES)."'
+ data-toggle='".PHSC(@$ToggleNextSelector, ENT_QUOTES)."'
+ data-pmembed='".PHSC(json_encode(@$PmEmbed), ENT_QUOTES)."' async></script>";
+ }
+}
+
+if (IsEnabled($EnableUpgradeCheck,1)) {
+ SDV($StatusPageName, "$SiteAdminGroup.Status");
+ $page = ReadPage($StatusPageName, READPAGE_CURRENT);
+ if (@$page['updatedto'] != $VersionNum)
+ { $action = 'upgrade'; include_once("$FarmD/scripts/upgrades.php"); }
+}
--- /dev/null
+++ scripts/stdmarkup.php
@@ -0,0 +1,681 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2004-2020 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This script defines PmWiki's standard markup. It is automatically
+ included from stdconfig.php unless $EnableStdMarkup==0.
+
+ Each call to Markup() below adds a new rule to PmWiki's translation
+ engine (unless a rule with the same name has already been defined).
+ The form of the call is Markup($id,$where,$pat,$rep);
+ $id is a unique name for the rule, $where is the position of the rule
+ relative to another rule, $pat is the pattern to look for, and
+ $rep is the string to replace it with.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+## first we preserve text in [=...=] and [@...@]
+function PreserveText($sigil, $text, $lead) {
+ if ($sigil=='=') return $lead.Keep($text);
+ if (strpos($text, "\n")===false)
+ return "$lead<code class='escaped'>".Keep($text)."</code>";
+ $text = preg_replace("/\n[^\\S\n]+$/", "\n", $text);
+ if ($lead == "" || $lead == "\n")
+ return "$lead<pre class='escaped'>".Keep($text)."</pre>";
+ return "$lead<:pre,1>".Keep($text);
+}
+
+Markup('[=','_begin',"/(\n[^\\S\n]*)?\\[([=@])(.*?)\\2\\]/s",
+ "MarkupPreserveText");
+function MarkupPreserveText($m) {return PreserveText($m[2], $m[3], $m[1]);}
+
+Markup('restore','<_end',"/$KeepToken(\\d.*?)$KeepToken/", 'cb_expandkpv');
+Markup('<:', '>restore', '/<:[^>]*>/', '');
+Markup('<vspace>', '<restore',
+ '/<vspace>/',
+ "<div class='vspace'></div>");
+Markup('<vspace><p>', '<<vspace>',
+ "/<vspace><p\\b(([^>]*)(\\s)class=(['\"])([^>]*?)\\4)?/",
+ "<p$2 class='vspace$3$5'");
+
+## remove carriage returns before preserving text
+Markup('\\r','<[=','/\\r/','');
+
+# $[phrase] substitutions
+Markup('$[phrase]', '>[=',
+ '/\\$\\[(?>([^\\]]+))\\]/', "cb_expandxlang");
+
+# {$var} substitutions
+Markup('{$var}', '>$[phrase]',
+ '/\\{(\\*|!?[-\\w.\\/\\x80-\\xff]*)(\\$:?\\w[-\\w]*)\\}/',
+ "MarkupPageVar");
+function MarkupPageVar($m){
+ extract($GLOBALS["MarkupToHTML"]);
+ return PRR(PVSE(PageVar($pagename, $m[2], $m[1])));
+}
+
+# invisible (:textvar:...:) definition
+Markup('textvar:', '<split',
+ '/\\(: *\\w[-\\w]* *:(?!\\)).*?:\\)/s', '');
+
+## handle relative text vars in includes
+if (IsEnabled($EnableRelativePageVars, 1))
+ SDV($QualifyPatterns["/\\{([-\\w\\x80-\\xfe]*)(\\$:?\\w+\\})/"],
+ 'cb_qualifypat');
+
+function cb_qualifypat($m) {
+ extract($GLOBALS["tmp_qualify"]);
+ return '{' . ($m[1] ? MakePageName($pagename, $m[1]) : $pagename) . $m[2];
+}
+
+## character entities
+Markup('&','<if','/&(?>([A-Za-z0-9]+|#\\d+|#[xX][A-Fa-f0-9]+));/',
+ '&$1;');
+Markup('&amp;', '<&', '/&amp;/', Keep('&'));
+
+
+## (:if:)/(:elseif:)/(:else:)
+SDV($CondTextPattern,
+ "/ \\(:if (\d*) (?:end)? \\b[^\n]*?:\\)
+ .*?
+ (?: \\(: (?:if\\1|if\\1end) \\s* :\\)
+ | (?=\\(:(?:if\\1|if\\1end)\\b[^\n]*?:\\) | $)
+ )
+ /six");
+// SDV($CondTextReplacement, "CondText2(\$pagename, \$m[0], \$m[1])");
+SDV($CondTextReplacement, "MarkupCondText2");
+Markup('if', 'fulltext', $CondTextPattern, $CondTextReplacement);
+
+function MarkupCondText2($m) {
+ extract($GLOBALS["MarkupToHTML"]);
+ return CondText2($pagename, $m[0], $m[1]);
+}
+function CondText2($pagename, $text, $code = '') {
+ global $Conditions, $CondTextPattern, $CondTextReplacement;
+ $if = "if$code";
+ $repl = str_replace('$pagename', "'$pagename'", $CondTextReplacement);
+
+ $parts = preg_split("/\\(:(?:{$if}end|$if|else *$if|else$code)\\b\\s*(.*?)\\s*:\\)/",
+ $text, -1, PREG_SPLIT_DELIM_CAPTURE);
+ $x = array_shift($parts);
+ while ($parts) {
+ list($condspec, $condtext) = array_splice($parts, 0, 2);
+ if (!preg_match("/^\\s*(!?)\\s*(\\S*)\\s*(.*?)\\s*$/", $condspec, $match)) continue;
+ list($x, $not, $condname, $condparm) = $match;
+
+ if (!isset($Conditions[$condname]))
+ return preg_replace_callback($CondTextPattern, $repl, $condtext);
+ $tf = @eval("return ({$Conditions[$condname]});");
+ if ($tf xor $not)
+ return preg_replace_callback($CondTextPattern, $repl, $condtext);
+ }
+ return '';
+}
+
+
+## (:include:)
+Markup('include', '>if',
+ '/\\(:include\\s+(\\S.*?):\\)/i',
+ "MarkupRedirectInclude");
+
+## (:redirect:)
+Markup('redirect', '<include',
+ '/\\(:redirect\\s+(\\S.*?):\\)/i',
+ "MarkupRedirectInclude");
+
+function MarkupRedirectInclude($m) {
+ extract($GLOBALS["MarkupToHTML"]);
+ switch ($markupid) {
+ case 'include': return PRR(IncludeText($pagename, $m[1]));
+ case 'redirect': return RedirectMarkup($pagename, $m[1]);
+ }
+}
+$SaveAttrPatterns['/\\(:(if\\d*|include|redirect)(\\s.*?)?:\\)/i'] = ' ';
+
+## GroupHeader/GroupFooter handling
+Markup('nogroupheader', '>include',
+ '/\\(:nogroupheader:\\)/i',
+ "MarkupGroupHeaderFooter");
+Markup('nogroupfooter', '>include',
+ '/\\(:nogroupfooter:\\)/i',
+ "MarkupGroupHeaderFooter");
+Markup('groupheader', '>nogroupheader',
+ '/\\(:groupheader:\\)/i',
+ "MarkupGroupHeaderFooter");
+Markup('groupfooter','>nogroupfooter',
+ '/\\(:groupfooter:\\)/i',
+ "MarkupGroupHeaderFooter");
+
+function MarkupGroupHeaderFooter($m) {
+ extract($GLOBALS["MarkupToHTML"]);
+ global $GroupHeaderFmt, $GroupFooterFmt;
+ switch ($markupid) {
+ case 'nogroupheader': return PZZ($GroupHeaderFmt='');
+ case 'nogroupfooter': return PZZ($GroupFooterFmt='');
+ case 'groupheader': return PRR(FmtPageName($GroupHeaderFmt,$pagename));
+ case 'groupfooter': return PRR(FmtPageName($GroupFooterFmt,$pagename));
+ }
+}
+## (:nl:)
+Markup('nl0','<split',"/([^\n])(?>(?:\\(:nl:\\))+)([^\n])/i","$1\n$2");
+Markup('nl1','>nl0',"/\\(:nl:\\)/i",'');
+
+## \\$ (end of line joins)
+Markup('\\$','>nl1',"/\\\\(?>(\\\\*))\n/", "MarkupEndLineJoin");
+function MarkupEndLineJoin($m) { return str_repeat('<br />',strlen($m[1])); }
+
+## Remove one <:vspace> after !headings
+Markup('!vspace', '>\\$', "/^(!(?>[^\n]+)\n)<:vspace>/m", '$1');
+
+## (:noheader:),(:nofooter:),(:notitle:)...
+Markup('noheader', 'directives', '/\\(:noheader:\\)/i', "MarkupTmplDisplay");
+Markup('nofooter', 'directives', '/\\(:nofooter:\\)/i', "MarkupTmplDisplay");
+Markup('notitle', 'directives', '/\\(:notitle:\\)/i', "MarkupTmplDisplay");
+Markup('noleft', 'directives', '/\\(:noleft:\\)/i', "MarkupTmplDisplay");
+Markup('noright', 'directives', '/\\(:noright:\\)/i', "MarkupTmplDisplay");
+Markup('noaction', 'directives', '/\\(:noaction:\\)/i', "MarkupTmplDisplay");
+
+function MarkupTmplDisplay($m) {
+ extract($GLOBALS["MarkupToHTML"]);
+ switch ($markupid) {
+ case 'noheader': return SetTmplDisplay('PageHeaderFmt',0);
+ case 'nofooter': return SetTmplDisplay('PageFooterFmt',0);
+ case 'notitle': return SetTmplDisplay('PageTitleFmt',0);
+ case 'noleft': return SetTmplDisplay('PageLeftFmt',0);
+ case 'noright': return SetTmplDisplay('PageRightFmt',0);
+ case 'noaction': return SetTmplDisplay('PageActionFmt',0);
+ }
+}
+
+## (:spacewikiwords:)
+Markup('spacewikiwords', 'directives',
+ '/\\(:(no)?spacewikiwords:\\)/i',
+ "MarkupDirectives");
+
+## (:linkwikiwords:)
+Markup('linkwikiwords', 'directives',
+ '/\\(:(no)?linkwikiwords:\\)/i',
+ "MarkupDirectives");
+
+## (:linebreaks:)
+Markup('linebreaks', 'directives',
+ '/\\(:(no)?linebreaks:\\)/i',
+ "MarkupDirectives");
+
+## (:messages:)
+Markup('messages', 'directives',
+ '/^\\(:messages:\\)/i',
+ "MarkupDirectives");
+
+function MarkupDirectives($m) {
+ extract($GLOBALS["MarkupToHTML"]);
+ switch ($markupid) {
+ case 'linkwikiwords': return PZZ($GLOBALS['LinkWikiWords']=($m[1]!='no'));
+ case 'spacewikiwords': return PZZ($GLOBALS['SpaceWikiWords']=($m[1]!='no'));
+ case 'linebreaks':
+ return PZZ($GLOBALS['HTMLPNewline'] = (@$m[1]!='no') ? '<br />' : '');
+ case 'messages':
+ return '<:block>'.Keep(FmtPageName(
+ implode('',(array)$GLOBALS['MessagesFmt']), $pagename));
+ }
+}
+
+
+## (:comment:)
+Markup('comment', 'directives', '/\\(:comment .*?:\\)/i', '');
+
+## (:title:) +fix for PITS:00266, 00779
+$tmpwhen = IsEnabled($EnablePageTitlePriority, 0) ? '<include' : 'directives';
+Markup('title', $tmpwhen,
+ '/\\(:title\\s(.*?):\\)/i',
+ "MarkupSetProperty");
+unset($tmpwhen);
+
+function MarkupSetProperty($m){ # title, description, keywords
+ extract($GLOBALS["MarkupToHTML"]);
+ switch ($markupid) {
+ case 'title':
+ global $EnablePageTitlePriority;
+ return PZZ(PCache($pagename, $zz=array('title' =>
+ SetProperty($pagename, 'title', $m[1], NULL, $EnablePageTitlePriority))));
+ case 'keywords':
+ return PZZ(SetProperty($pagename, 'keywords', $m[1], ', '));
+ case 'description':
+ return PZZ(SetProperty($pagename, 'description', $m[1], '\n'));
+ }
+}
+
+## (:keywords:), (:description:)
+Markup('keywords', 'directives', "/\\(:keywords?\\s+(.+?):\\)/i", "MarkupSetProperty");
+Markup('description', 'directives', "/\\(:description\\s+(.+?):\\)/i", "MarkupSetProperty");
+$HTMLHeaderFmt['meta'] = 'function:PrintMetaTags';
+function PrintMetaTags($pagename, $args) {
+ global $PCache;
+ foreach(array('keywords', 'description') as $n) {
+ foreach((array)@$PCache[$pagename]["=p_$n"] as $v) {
+ $v = str_replace("'", ''', $v);
+ print "<meta name='$n' content='$v' />\n";
+ }
+ }
+}
+
+#### inline markups ####
+## ''emphasis''
+Markup("''",'inline',"/''(.*?)''/",'<em>$1</em>');
+
+## '''strong'''
+Markup("'''","<''","/'''(.*?)'''/",'<strong>$1</strong>');
+
+## '''''strong emphasis'''''
+Markup("'''''","<'''","/'''''(.*?)'''''/",'<strong><em>$1</em></strong>');
+
+## @@code@@
+Markup('@@','inline','/@@(.*?)@@/','<code>$1</code>');
+
+## '+big+', '-small-'
+Markup("'+","<'''''","/'\\+(.*?)\\+'/",'<big>$1</big>');
+Markup("'-","<'''''","/'\\-(.*?)\\-'/",'<small>$1</small>');
+
+## '^superscript^', '_subscript_'
+Markup("'^","<'''''","/'\\^(.*?)\\^'/",'<sup>$1</sup>');
+Markup("'_","<'''''","/'_(.*?)_'/",'<sub>$1</sub>');
+
+## [+big+], [-small-]
+Markup('[+','inline','/\\[(([-+])+)(.*?)\\1\\]/',
+ "MarkupBigSmall");
+
+function MarkupBigSmall($m) {
+ return '<span style=\'font-size:'
+ .(round(pow(6/5,($m[2]=='-'? -1:1)*strlen($m[1]))*100,0))
+ .'%\'>'. $m[3].'</span>';
+}
+
+## {+ins+}, {-del-}
+Markup('{+','inline','/\\{\\+(.*?)\\+\\}/','<ins>$1</ins>');
+Markup('{-','inline','/\\{-(.*?)-\\}/','<del>$1</del>');
+
+## [[<<]] (break)
+Markup('[[<<]]','inline','/\\[\\[<<\\]\\]/',"<br clear='all' />");
+
+###### Links ######
+function MarkupLinks($m){
+ extract($GLOBALS["MarkupToHTML"]);
+ switch ($markupid) {
+ case '[[':
+ return Keep(MakeLink($pagename,$m[1],NULL,$m[2]),'L');
+ case '[[!':
+ global $CategoryGroup, $LinkCategoryFmt;
+ return Keep(MakeLink($pagename,"$CategoryGroup/{$m[1]}",NULL,'',
+ $LinkCategoryFmt),'L');
+ case '[[|':
+ return Keep(MakeLink($pagename,$m[1],$m[2],$m[3]),'L');
+ case '[[->':
+ return Keep(MakeLink($pagename,$m[2],$m[1],$m[3]),'L');
+ case '[[|#':
+ return Keep(MakeLink($pagename,$m[1],
+ '['.++$GLOBALS['MarkupFrame'][0]['ref'].']'),'L');
+ case '[[#':
+ return Keep(TrackAnchors($m[1]) ? '' : "<a name='{$m[1]}' id='{$m[1]}'></a>", 'L');
+ case 'urllink':
+ return Keep(MakeLink($pagename,$m[0],$m[0]),'L');
+ case 'mailto':
+ return Keep(MakeLink($pagename,$m[0],$m[1]),'L');
+ case 'img':
+ global $LinkFunctions, $ImgTagFmt;
+ return Keep($LinkFunctions[$m[1]]($pagename,$m[1],$m[2],@$m[4],$m[1].$m[2],
+ $ImgTagFmt),'L');
+ }
+}
+
+## [[free links]]
+Markup('[[','links',"/(?>\\[\\[\\s*(.*?)\\]\\])($SuffixPattern)/", "MarkupLinks");
+
+## [[!Category]]
+SDV($CategoryGroup,'Category');
+SDV($LinkCategoryFmt,"<a class='categorylink' href='\$LinkUrl'>\$LinkText</a>");
+Markup('[[!','<[[','/\\[\\[!(.*?)\\]\\]/', "MarkupLinks");
+# This is a temporary workaround for blank category pages.
+# It may be removed in a future release (Pm, 2006-01-24)
+if (preg_match("/^$CategoryGroup\\./", $pagename)) {
+ SDV($DefaultPageTextFmt, '');
+ SDV($PageNotFoundHeaderFmt, 'HTTP/1.1 200 Ok');
+}
+
+## [[target | text]]
+Markup('[[|','<[[',
+ "/(?>\\[\\[([^|\\]]*)\\|\\s*)(.*?)\\s*\\]\\]($SuffixPattern)/",
+ "MarkupLinks");
+
+## [[text -> target ]]
+Markup('[[->','>[[|',
+ "/(?>\\[\\[([^\\]]+?)\\s*-+>\\s*)(.*?)\\]\\]($SuffixPattern)/",
+ "MarkupLinks");
+
+## [[#anchor]]
+Markup('[[#','<[[','/(?>\\[\\[#([A-Za-z][-.:\\w]*))\\]\\]/', "MarkupLinks");
+function TrackAnchors($x) { global $SeenAnchor; return @$SeenAnchor[$x]++; }
+
+## [[target |#]] reference links
+Markup('[[|#', '<[[|',
+ "/(?>\\[\\[([^|\\]]+))\\|\\s*#\\s*\\]\\]/",
+ "MarkupLinks");
+
+## [[target |+]] title links moved inside LinkPage()
+
+## bare urllinks
+Markup('urllink','>[[',
+ "/\\b(?>(\\L))[^\\s$UrlExcludeChars]*[^\\s.,?!$UrlExcludeChars]/",
+ "MarkupLinks");
+
+## mailto: links
+Markup('mailto','<urllink',
+ "/\\bmailto:([^\\s$UrlExcludeChars]*[^\\s.,?!$UrlExcludeChars])/",
+ "MarkupLinks");
+
+## inline images
+Markup('img','<urllink',
+ "/\\b(?>(\\L))([^\\s$UrlExcludeChars]+$ImgExtPattern)(\"([^\"]*)\")?/",
+ "MarkupLinks");
+
+if (IsEnabled($EnableRelativePageLinks, 1))
+ SDV($QualifyPatterns['/(\\[\\[(?>[^\\]]+?->)?\\s*)([-\\w\\x80-\\xfe\\s\'()]+([|#?].*?)?\\]\\])/'],
+ 'cb_qualifylinks');
+
+function cb_qualifylinks($m) {
+ extract($GLOBALS['tmp_qualify']);
+ return "{$m[1]}$group/{$m[2]}";
+}
+
+## bare wikilinks
+## v2.2: markup rule moved to scripts/wikiwords.php)
+Markup('wikilink', '>urllink');
+
+## escaped `WikiWords
+## v2.2: rule kept here for markup compatibility with 2.1 and earlier
+Markup('`wikiword', '<wikilink',
+ "/`(($GroupPattern([\\/.]))?($WikiWordPattern))/",
+ "MarkupNoWikiWord");
+function MarkupNoWikiWord($m) { return Keep($m[1]); }
+
+#### Block markups ####
+## Completely blank lines don't do anything.
+Markup('blank', '<block', '/^\\s+$/', '');
+
+## process any <:...> markup (after all other block markups)
+Markup('^<:','>block','/^(?=\\s*\\S)(<:([^>]+)>)?/',"MarkupBlock");
+function MarkupBlock($m) {return Block(@$m[2]);}
+
+## unblocked lines w/block markup become anonymous <:block>
+Markup('^!<:', '<^<:',
+ "/^(?!<:)(?=.*(<\\/?($BlockPattern)\\b)|$KeepToken\\d+B$KeepToken)/",
+ '<:block>');
+
+## Lines that begin with displayed images receive their own block. A
+## pipe following the image indicates a "caption" (generates a linebreak).
+Markup('^img', 'block',
+ "/^((?>(\\s+|%%|%[A-Za-z][-,=:#\\w\\s'\".]*%)*)$KeepToken(\\d+L)$KeepToken)(\\s*\\|\\s?)?(.*)$/",
+ "ImgCaptionDiv");
+function ImgCaptionDiv($m) {
+ global $KPV;
+ if (strpos($KPV[$m[3]], '<img')===false) return $m[0];
+ $dclass = 'img';
+ $ret = $m[1];
+ if ($m[4]) {
+ $dclass .= " imgcaption";
+ $ret .= "<br /><span class='caption'>$m[5]</span>";
+ }
+ elseif (! $m[5]) $dclass .= " imgonly";
+ else $ret .= $m[5];
+ return "<:block,1><div class='$dclass'>$ret</div>";
+}
+
+## Whitespace at the beginning of lines can be used to maintain the
+## indent level of a previous list item, or a preformatted text block.
+Markup('^ws', '<^img', '/^\\s+ #1/x', "WSIndent");
+function WSIndent($i) {
+ if(is_array($i)) $i = $i[0];
+ global $MarkupFrame;
+ $icol = strlen($i);
+ for($depth = count(@$MarkupFrame[0]['cs']); $depth > 0; $depth--)
+ if (@$MarkupFrame[0]['is'][$depth] == $icol) {
+ $MarkupFrame[0]['idep'] = $depth;
+ $MarkupFrame[0]['icol'] = $icol;
+ return '';
+ }
+ return $i;
+}
+
+## The $EnableWSPre setting uses leading spaces on markup lines to indicate
+## blocks of preformatted text.
+SDV($EnableWSPre, 1);
+Markup('^ ', 'block',
+ '/^\\s+ #2/x',
+ "MarkupWSPre");
+function MarkupWSPre($m) {
+ global $EnableWSPre;
+ return ($EnableWSPre > 0 && strlen($m[0]) >= $EnableWSPre)
+ ? '<:pre,1>'.$m[0] : $m[0];
+}
+## bullet lists
+Markup('^*','block','/^(\\*+)\\s?(\\s*)/','<:ul,$1,$0>$2');
+
+## numbered lists
+Markup('^#','block','/^(#+)\\s?(\\s*)/','<:ol,$1,$0>$2');
+
+## indented (->) /hanging indent (-<) text
+Markup('^->','block','/^(?>(-+))>\\s?(\\s*)/','<:indent,$1,$1 $2>$2');
+Markup('^-<','block','/^(?>(-+))<\\s?(\\s*)/','<:outdent,$1,$1 $2>$2');
+
+## definition lists
+Markup('^::','block','/^(:+)(\s*)([^:]+):/','<:dl,$1,$1$2><dt>$2$3</dt><dd>');
+
+## Q: and A:
+Markup('^Q:', 'block', '/^Q:(.*)$/', "<:block,1><p class='question'>$1</p>");
+Markup('^A:', 'block', '/^A:/', Keep(''));
+
+## tables
+function MarkupTables($m) {
+ extract($GLOBALS["MarkupToHTML"]);
+ switch ($markupid) {
+ case 'table': return Cells(@$m[1],@$m[2]);
+ case '^||||': return FormatTableRow($m[0]);
+ case '^||':
+ $GLOBALS['BlockMarkups']['table'][0] = '<table '.SimpleTableAttr($m[1]).'>';
+ return '<:block,1>';
+ }
+}
+
+## ||cell||, ||!header cell||, ||!caption!||
+Markup('^||||', 'block',
+ '/^\\|\\|.*\\|\\|.*$/',
+ "MarkupTables");
+## ||table attributes
+Markup('^||','>^||||','/^\\|\\|(.*)$/',
+ "MarkupTables");
+
+#### (:table:) markup (AdvancedTables)
+Markup('table', '<block',
+ '/^\\(:(table|cell|cellnr|head|headnr|tableend|(?:div\\d*|section\\d*|details\\d*|article\\d*|header|footer|nav|address|aside)(?:end)?)(\\s.*?)?:\\)/i',
+ "MarkupTables");
+Markup('^>>', '<table',
+ '/^>>(.+?)<<(.*)$/',
+ '(:div:)%div $1 apply=div%$2 ');
+Markup('^>><<', '<^>>',
+ '/^>><</',
+ '(:divend:)');
+
+function SimpleTableAttr($attr) {
+ global $SimpleTableDefaultClassName;
+ $qattr = PQA($attr);
+ if(IsEnabled($SimpleTableDefaultClassName) && !preg_match("/(^| )class='.*?' /", $qattr))
+ $qattr .= "class='$SimpleTableDefaultClassName'";
+ return $qattr;
+}
+
+#### (:table:) markup (AdvancedTables)
+function Cells($name,$attr) {
+ global $MarkupFrame, $EnableTableAutoValignTop;
+ $attr = PQA($attr);
+ $tattr = @$MarkupFrame[0]['tattr'];
+ $name = strtolower($name);
+ $key = preg_replace('/end$/', '', $name);
+ if (preg_match("/^(?:head|cell)(nr)?$/", $name)) $key = 'cell';
+ $out = '<:block>'.MarkupClose($key);
+ if (substr($name, -3) == 'end') return $out;
+ $cf = & $MarkupFrame[0]['closeall'];
+ if ($name == 'table') $MarkupFrame[0]['tattr'] = $attr;
+ else if ($key == 'cell') {
+ if (IsEnabled($EnableTableAutoValignTop, 1) && strpos($attr, "valign=")===false)
+ $attr .= " valign='top'";
+ $t = (strpos($name, 'head')===0 ) ? 'th' : 'td';
+ if (!@$cf['table']) {
+ $tattr = @$MarkupFrame[0]['tattr'];
+ $out .= "<table $tattr><tr><$t $attr>";
+ $cf['table'] = '</tr></table>';
+ } else if ( preg_match("/nr$/", $name)) $out .= "</tr><tr><$t $attr>";
+ else $out .= "<$t $attr>";
+ $cf['cell'] = "</$t>";
+ } else {
+ $tag = preg_replace('/\\d+$/', '', $key);
+ $tmp = "<$tag $attr>";
+ if ($tag == 'details') {
+ $tmp = preg_replace("#(<details.*) summary='(.*?)'(.*)$#", '$1$3<summary>$2</summary>', $tmp);
+ }
+ $out .= $tmp;
+ $cf[$key] = "</$tag>";
+ }
+ return $out;
+}
+
+
+## headings
+Markup('^!', 'block', '/^(!{1,6})\\s?(.*)$/', "MarkupHeadings");
+function MarkupHeadings($m) {
+ $len = strlen($m[1]);
+ return "<:block,1><h$len>$m[2]</h$len>";
+}
+
+## horiz rule
+Markup('^----','>^->','/^----+/','<:block,1><hr />');
+
+#### special stuff ####
+## (:markup:) for displaying markup examples
+function MarkupMarkup($pagename, $text, $opt = '') {
+ global $MarkupWordwrapFunction, $MarkupWrapTag;
+ SDV($MarkupWordwrapFunction, 'IsEnabled');
+ SDV($MarkupWrapTag, 'pre');
+ $MarkupMarkupOpt = array('class' => 'vert');
+ $opt = array_merge($MarkupMarkupOpt, ParseArgs($opt));
+ $html = MarkupToHTML($pagename, $text, array('escape' => 0));
+ if (@$opt['caption'])
+ $caption = str_replace("'", ''',
+ "<caption>{$opt['caption']}</caption>");
+ $class = preg_replace('/[^-\\s\\w]+/', ' ', @$opt['class']);
+ if (strpos($class, 'horiz') !== false)
+ { $sep = ''; $pretext = $MarkupWordwrapFunction($text, 40); }
+ else
+ { $sep = '</tr><tr>'; $pretext = $MarkupWordwrapFunction($text, 75); }
+ return Keep(@"<table class='markup $class' align='center'>$caption
+ <tr><td class='markup1' valign='top'><$MarkupWrapTag>$pretext</$MarkupWrapTag></td>$sep<td
+ class='markup2' valign='top'>$html</td></tr></table>");
+}
+
+Markup('markup', '<[=',
+ "/\\(:markup(\\s+([^\n]*?))?:\\)[^\\S\n]*\\[([=@])(.*?)\\3\\]/si",
+ "MarkupMarkupMarkup");
+Markup('markupend', '>markup', # $1 only shifts the other matches
+ "/\\(:(markup)(\\s+([^\n]*?))?:\\)[^\\S\n]*\n(.*?)\\(:markupend:\\)/si",
+ "MarkupMarkupMarkup");
+function MarkupMarkupMarkup($m) { # cannot be joined, $markupid resets
+ extract($GLOBALS["MarkupToHTML"]); global $MarkupMarkupLevel;
+ @$MarkupMarkupLevel++;
+ $x = MarkupMarkup($pagename, $m[4], $m[2]);
+ $MarkupMarkupLevel--;
+ return $x;
+}
+
+SDV($HTMLStylesFmt['markup'], "
+ table.markup { border:2px dotted #ccf; width:90%; }
+ td.markup1, td.markup2 { padding-left:10px; padding-right:10px; }
+ table.vert td.markup1 { border-bottom:1px solid #ccf; }
+ table.horiz td.markup1 { width:23em; border-right:1px solid #ccf; }
+ table.markup caption { text-align:left; }
+ div.faq p, div.faq pre { margin-left:2em; }
+ div.faq p.question { margin:1em 0 0.75em 0; font-weight:bold; }
+ div.faqtoc div.faq * { display:none; }
+ div.faqtoc div.faq p.question
+ { display:block; font-weight:normal; margin:0.5em 0 0.5em 20px; line-height:normal; }
+ div.faqtoc div.faq p.question * { display:inline; }
+ td.markup1 pre { white-space: pre-wrap; }
+ ");
+
+#### Special conditions ####
+## The code below adds (:if date:) conditions to the markup.
+$Conditions['date'] = "CondDate(\$condparm)";
+
+function CondDate($condparm) {
+ global $Now;
+ if (!preg_match('/^(\\S*?)(\\.\\.(\\S*))?(\\s+\\S.*)?$/',
+ trim($condparm), $match))
+ return false;
+ if ($match[4] == '') { $x0 = $Now; NoCache(); }
+ else { list($x0, $x1) = DRange($match[4]); }
+ if ($match[1] > '') {
+ list($t0, $t1) = DRange($match[1]);
+ if ($x0 < $t0) return false;
+ if ($match[2] == '' && $x0 >= $t1) return false;
+ }
+ if ($match[3] > '') {
+ list($t0, $t1) = DRange($match[3]);
+ if ($x0 >= $t1) return false;
+ }
+ return true;
+}
+
+# This pattern enables the (:encrypt <phrase>:) markup/replace-on-save
+# pattern.
+SDV($ROSPatterns['/\\(:encrypt\\s+([^\\s:=]+).*?:\\)/'], 'cb_encrypt');
+function cb_encrypt($m) { return pmcrypt($m[1]);}
+
+# Table of contents, based on Cookbook:AutoTOC by Petko Yotov
+SDVA($PmTOC, array(
+ 'Enable' => 0,
+ 'MaxLevel' => 6,
+ 'MinNumber' => 3,
+ 'ParentElement'=>'',
+ 'NumberedHeadings'=>'',
+ 'EnableBacklinks'=>0,
+ 'EnableQMarkup' => 0,
+ 'contents' => XL('Contents'),
+ 'hide' => XL('hide'),
+ 'show' => XL('show'),
+));
+
+if ($action!='browse') $PmTOC['Enable'] = 0;
+
+Markup("PmTOC", 'directives', '/^\\(:[#*]?(?:toc|tdm).*?:\\)\\s*$/im', 'FmtPmTOC');
+Markup("noPmTOC", 'directives', '/\\(:(no)(?:toc|tdm).*?:\\)/im', 'FmtPmTOC');
+function FmtPmTOC($m) {
+ if (@$m[1]) return Keep('<span class="noPmTOC"></span>');
+ return "<:block,1>".Keep("<div class='PmTOCdiv'></div>");
+}
+SDV($HTMLStylesFmt['PmTOC'], '.noPmTOC, .PmTOCdiv:empty {display:none;}
+.PmTOCdiv { display: inline-block; font-size: 13px; overflow: auto; max-height: 500px;}
+.PmTOCdiv a { text-decoration: none;}
+.back-arrow {font-size: .9em; text-decoration: none;}
+#PmTOCchk + label {cursor: pointer;}
+#PmTOCchk {display: none;}
+#PmTOCchk:not(:checked) + label > .pmtoc-show {display: none;}
+#PmTOCchk:checked + label > .pmtoc-hide {display: none;}
+#PmTOCchk:checked + label + div {display: none;}');
+
+SDV($HTMLStylesFmt['PmSortable'], 'table.sortable th { cursor: pointer; }
+table.sortable th::after { color: transparent; content: "\00A0\025B8"; }
+table.sortable th:hover::after { color: inherit; content: "\00A0\025B8"; }
+table.sortable th.dir-u::after { color: inherit; content: "\00A0\025BE"; }
+table.sortable th.dir-d::after { color: inherit; content: "\00A0\025B4"; }');
+
--- /dev/null
+++ scripts/trails.php
@@ -0,0 +1,140 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2002-2017 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This script enables markup of the form <<|TrailPage|>> to be
+ used to build "trails" through wiki documents.
+
+ This feature is automatically included from stdconfig.php unless
+ disabled by $EnableWikiTrails = 0; . To explicitly include this feature,
+ execute
+ include_once("scripts/trails.php");
+ from config.php somewhere.
+
+ Once enabled, the <<|TrailPage|>> markup is replaced with
+ << PrevPage | TrailPage | NextPage >> on output. TrailPage should
+ contain either a bullet or number list defining the sequence of pages
+ in the "trail".
+
+ The ^|TrailPage|^ markup uses the depth of the bullets to display
+ the ancestry of the TrailPage to the current one. The <|TrailPage|>
+ markup is like <<|TrailPage|>> except that "< PrevPage |" and
+ "| NextPage >" are omitted if at the beginning or end of the
+ trail respectively. Thanks to John Rankin for contributing these
+ markups and the original suggestion for WikiTrails.
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+Markup('<<|','<links','/<<\\|([^|]+|\\[\\[(.+?)\\]\\])\\|>>/',
+ "MarkupMakeTrail");
+Markup('<|','><<|','/<\\|([^|]+|\\[\\[(.+?)\\]\\])\\|>/',
+ "MarkupMakeTrail");
+Markup('^|','<links','/\\^\\|([^|]+|\\[\\[(.+?)\\]\\])\\|\\^/',
+ "MarkupMakeTrail");
+
+function MarkupMakeTrail($m) {
+ extract($GLOBALS["MarkupToHTML"]); # get $pagename, $markupid
+ switch ($markupid) {
+ case '<<|':
+ return PRR(MakeTrailStop($pagename,$m[1]));
+ case '<|':
+ return PRR(MakeTrailStopB($pagename,$m[1]));
+ case '^|':
+ return PRR(MakeTrailPath($pagename,$m[1]));
+ }
+}
+
+SDVA($SaveAttrPatterns, array(
+ '/<<\\|([^|]+|\\[\\[(.+?)\\]\\])\\|>>/' => '$1',
+ '/<\\|([^|]+|\\[\\[(.+?)\\]\\])\\|>/' => '$1',
+ '/\\^\\|([^|]+|\\[\\[(.+?)\\]\\])\\|\\^/' => '$1'));
+
+$Conditions['ontrail'] = 'CondOnTrail($pagename, $condparm)';
+
+function CondOnTrail($pagename, $condparm) {
+ @list($trailname, $pn) = preg_split('/\\s+/', $condparm, 2);
+ $trail = ReadTrail($pagename, $trailname);
+ if (!$trail) return false;
+ $pn = ($pn > '') ? MakePageName($pagename, $pn) : $pagename;
+ foreach($trail as $t)
+ if ($t['pagename'] == $pn) return true;
+ return false;
+}
+
+function ReadTrail($pagename, $trailname) {
+ global $RASPageName, $SuffixPattern, $GroupPattern, $WikiWordPattern,
+ $LinkWikiWords;
+ if (preg_match('/^\\[\\[(.+?)(->|\\|)(.+?)\\]\\]$/', $trailname, $m))
+ $trailname = ($m[2] == '|') ? $m[1] : $m[3];
+ $trailtext = RetrieveAuthSection($pagename, $trailname);
+ $trailname = $RASPageName;
+ $trailtext = Qualify($trailname, $trailtext);
+ $t = array();
+ $n = 0;
+ foreach(explode("\n", PHSC(@$trailtext, ENT_NOQUOTES))
+ as $x) {
+ $x = preg_replace("/\\[\\[([^\\]]*)->([^\\]]*)\\]\\]/",'[[$2|$1]]',$x);
+ if (!preg_match("/^([#*:]+) \\s*
+ (\\[\\[([^:#!|][^|:]*?)(?:\".*?\")?(\\|.*?)?\\]\\]($SuffixPattern)
+ | (($GroupPattern([\\/.]))?$WikiWordPattern)) (.*)/x",$x,$match))
+ continue;
+ if (@$match[6]) {
+ if (!$LinkWikiWords) continue;
+ $tgt = MakePageName($trailname, $match[6]);
+ } else $tgt = MakePageName($trailname, $match[3]);
+ $t[$n]['depth'] = $depth = strlen($match[1]);
+ $t[$n]['pagename'] = $tgt;
+ $t[$n]['markup'] = $match[2];
+ $t[$n]['detail'] = $match[9];
+ for($i=$depth;$i<10;$i++) $d[$i]=$n;
+ if ($depth>1) $t[$n]['parent']=@$d[$depth-1];
+ $n++;
+ }
+ return $t;
+}
+
+function MakeTrailStop($pagename,$trailname) {
+ $t = ReadTrail($pagename,$trailname);
+ $prev=''; $next='';
+ for($i=0;$i<count($t);$i++) {
+ if ($t[$i]['pagename']==$pagename) {
+ if ($i>0) $prev = $t[$i-1]['markup'];
+ if ($i+1<count($t)) $next = $t[$i+1]['markup'];
+ }
+ }
+ return "<span class='wikitrail'><< $prev | $trailname | $next >></span>";
+}
+
+function MakeTrailStopB($pagename,$trailname) {
+ $t = ReadTrail($pagename,$trailname);
+ $prev = ''; $next = '';
+ for($i=0;$i<count($t);$i++) {
+ if ($t[$i]['pagename']==$pagename) {
+ if ($i>0) $prev = '< '.$t[$i-1]['markup'].' | ';
+ if ($i+1<count($t)) $next = ' | '.$t[$i+1]['markup'].' >';
+ }
+ }
+ return "<span class='wikitrail'>$prev$trailname$next</span>";
+}
+
+function MakeTrailPath($pagename,$trailname) {
+ global $TrailPathSep;
+ SDV($TrailPathSep,' | ');
+ $t = ReadTrail($pagename,$trailname);
+ $crumbs = '';
+ for($i=0;$i<count($t);$i++) {
+ if ($t[$i]['pagename']==$pagename) {
+ while (@$t[$i]['depth']>0) {
+ $crumbs = $TrailPathSep.$t[$i]['markup'].$crumbs;
+ $i = @$t[$i]['parent'];
+ }
+ return "<span class='wikitrail'>$trailname$crumbs</span>";
+ }
+ }
+ return "<span class='wikitrail'>$trailname</span>";
+}
+
--- /dev/null
+++ scripts/transition.php
@@ -0,0 +1,279 @@
+<?php if (!defined('PmWiki')) exit();
+/* Copyright 2005-2017 Patrick R. Michaud (pmichaud@pobox.com)
+ This file is part of PmWiki; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version. See pmwiki.php for full details.
+
+ This script handles various "fixup transitions" that might need to
+ occur to help existing sites smoothly upgrade to newer releases of
+ PmWiki. Rather than put the workarounds in the main code files, we
+ try to centralize them here so we can see what's deprecated and a
+ simple switch (?trans=0 in the url) can tell the admin if his site
+ is relying on an outdated feature or way of doing things.
+
+ Transitions defined in this script:
+
+ $Transition['nosessionencode'] - turn off session encoding
+
+ $Transition['version'] < 2001967 - all transitions listed above
+
+ $Transition['wspre'] - leading spaces are pre text
+
+ $Transition['version'] < 2001941 - all transitions listed above
+
+ $Transition['wikiwords'] - 2.1-style WikiWord processing
+
+ $Transition['version'] < 2001924 - all transitions listed above
+
+ $Transition['abslinks'] - absolute links/page vars
+
+ $Transition['version'] < 2001901 - all transitions listed above
+
+ $Transition['vspace'] - restore <p class='vspace'></p>
+
+ $Transition['version'] < 2001006 - all transitions listed above
+
+ $Transition['fplbygroup'] - restore FPLByGroup function
+
+ $Transition['version'] < 2000915 - all transitions listed above
+
+ $Transition['mainrc'] - keep using Main.AllRecentChanges
+ $Transition['mainapprovedurls'] - keep using Main.ApprovedUrls
+ $Transition['pageeditfmt'] - default $PageEditFmt value
+ $Transition['mainpages'] - other default pages in Main
+
+ $Transition['version'] < 1999944 - all transitions listed above
+
+ To get all of the transitions for compatibility with a previous
+ version of PmWiki, simply set $Transition['version'] in a local
+ configuration file to the version number you want compatibility
+ with. All of the transitions associated with that version will
+ then be enabled. Example:
+
+ # Keep compatibility with PmWiki version 2.0.13
+ $Transition['version'] = 2000013;
+
+ To explicitly enable or disable specific transitions, set
+ the corresponding $Transition[] element to 1 or 0. This will
+ override the $Transition['version'] item listed above. For
+ example, to enable just the 'pageeditfmt' transition, use
+
+ $Transition['pageeditfmt'] = 1;
+
+ Script maintained by Petko YOTOV www.pmwiki.org/petko
+*/
+
+## if ?trans=0 is specified, then we don't do any fixups.
+if (@$_REQUEST['trans']==='0') return;
+
+## set a default Transition version if we don't have one
+SDV($Transition['version'], $VersionNum);
+
+## Transitions from 2.2.0-beta67
+if (@$Transition['version'] < 2001967)
+