Moved to merjis/tools/wiki.
authorrich <rich>
Tue, 7 Sep 2004 10:14:07 +0000 (10:14 +0000)
committerrich <rich>
Tue, 7 Sep 2004 10:14:07 +0000 (10:14 +0000)
119 files changed:
.cvsignore [new file with mode: 0644]
COPYING [new file with mode: 0644]
MANIFEST [new file with mode: 0644]
Makefile [new file with mode: 0644]
Makefile.config [new file with mode: 0644]
README [new file with mode: 0644]
cocanwiki.sql [new file with mode: 0644]
conf/cocanwiki.conf [new file with mode: 0644]
debian/.cvsignore [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/docs [new file with mode: 0644]
debian/rules [new file with mode: 0755]
html/_bin/.cvsignore [new file with mode: 0644]
html/_css/admin.css [new file with mode: 0644]
html/_css/create.css [new file with mode: 0644]
html/_css/editor.css [new file with mode: 0644]
html/_css/files.css [new file with mode: 0644]
html/_css/images.css [new file with mode: 0644]
html/_css/markup.css [new file with mode: 0644]
html/_css/standard.css [new file with mode: 0644]
html/_graphics/.cvsignore [new file with mode: 0644]
html/_graphics/arrow-down.png [new file with mode: 0644]
html/_graphics/arrow-up.png [new file with mode: 0644]
html/_graphics/cross.png [new file with mode: 0644]
html/_graphics/error.png [new file with mode: 0644]
html/_graphics/external.png [new file with mode: 0644]
html/_graphics/file.png [new file with mode: 0644]
html/_graphics/mailto.png [new file with mode: 0644]
html/_graphics/markup-preview.png [new file with mode: 0644]
html/_graphics/newpage.png [new file with mode: 0644]
html/_graphics/ok.png [new file with mode: 0644]
html/_js/editor.js [new file with mode: 0644]
html/_static/markup.html [new file with mode: 0644]
html/robots.txt [new file with mode: 0644]
scripts/.cvsignore [new file with mode: 0644]
scripts/.depend [new file with mode: 0644]
scripts/Makefile [new file with mode: 0644]
scripts/admin/.cvsignore [new file with mode: 0644]
scripts/admin/admin.ml [new file with mode: 0644]
scripts/admin/create_host.ml [new file with mode: 0644]
scripts/admin/create_host_form.ml [new file with mode: 0644]
scripts/admin/edit_emails.ml [new file with mode: 0644]
scripts/admin/edit_emails_form.ml [new file with mode: 0644]
scripts/admin/edit_host_css.ml [new file with mode: 0644]
scripts/admin/edit_host_css_form.ml [new file with mode: 0644]
scripts/admin/edit_hostnames.ml [new file with mode: 0644]
scripts/admin/edit_hostnames_form.ml [new file with mode: 0644]
scripts/admin/host.ml [new file with mode: 0644]
scripts/cgi_expires.ml [new file with mode: 0644]
scripts/cocanwiki.ml [new file with mode: 0644]
scripts/cocanwiki_diff.ml [new file with mode: 0644]
scripts/cocanwiki_emailnotify.ml [new file with mode: 0644]
scripts/cocanwiki_ok.ml [new file with mode: 0644]
scripts/cocanwiki_version.ml.in [new file with mode: 0644]
scripts/create.ml [new file with mode: 0644]
scripts/create_form.ml [new file with mode: 0644]
scripts/delete_file.ml [new file with mode: 0644]
scripts/delete_file_form.ml [new file with mode: 0644]
scripts/delete_image.ml [new file with mode: 0644]
scripts/delete_image_form.ml [new file with mode: 0644]
scripts/diff.ml [new file with mode: 0644]
scripts/edit.ml [new file with mode: 0644]
scripts/edit_page_css.ml [new file with mode: 0644]
scripts/edit_page_css_form.ml [new file with mode: 0644]
scripts/file.ml [new file with mode: 0644]
scripts/files.ml [new file with mode: 0644]
scripts/history.ml [new file with mode: 0644]
scripts/hoststyle.ml [new file with mode: 0644]
scripts/image.ml [new file with mode: 0644]
scripts/images.ml [new file with mode: 0644]
scripts/merjisforwiki.ml [new file with mode: 0644]
scripts/merjisforwiki.mli [new file with mode: 0644]
scripts/page.ml [new file with mode: 0644]
scripts/pagestyle.ml [new file with mode: 0644]
scripts/preview.ml [new file with mode: 0644]
scripts/recent.ml [new file with mode: 0644]
scripts/restore.ml [new file with mode: 0644]
scripts/restore_form.ml [new file with mode: 0644]
scripts/search.ml [new file with mode: 0644]
scripts/sitemap.ml [new file with mode: 0644]
scripts/undelete_file.ml [new file with mode: 0644]
scripts/undelete_file_form.ml [new file with mode: 0644]
scripts/undelete_image.ml [new file with mode: 0644]
scripts/undelete_image_form.ml [new file with mode: 0644]
scripts/upload_file.ml [new file with mode: 0644]
scripts/upload_file_form.ml [new file with mode: 0644]
scripts/upload_image.ml [new file with mode: 0644]
scripts/upload_image_form.ml [new file with mode: 0644]
scripts/wikilib.ml [new file with mode: 0644]
scripts/wikilib.mli [new file with mode: 0644]
templates/admin/admin.html [new file with mode: 0644]
templates/admin/create_host_form.html [new file with mode: 0644]
templates/admin/edit_emails_form.html [new file with mode: 0644]
templates/admin/edit_host_css_form.html [new file with mode: 0644]
templates/admin/edit_hostnames_form.html [new file with mode: 0644]
templates/admin/host.html [new file with mode: 0644]
templates/create_form.html [new file with mode: 0644]
templates/delete_file_form.html [new file with mode: 0644]
templates/delete_image_form.html [new file with mode: 0644]
templates/diff.html [new file with mode: 0644]
templates/edit.html [new file with mode: 0644]
templates/edit_conflict.html [new file with mode: 0644]
templates/edit_page_css_form.html [new file with mode: 0644]
templates/files.html [new file with mode: 0644]
templates/history.html [new file with mode: 0644]
templates/images.html [new file with mode: 0644]
templates/ok_error.html [new file with mode: 0644]
templates/page.html [new file with mode: 0644]
templates/page_404.html [new file with mode: 0644]
templates/recent.html [new file with mode: 0644]
templates/restore_form.html [new file with mode: 0644]
templates/sitemap.html [new file with mode: 0644]
templates/undelete_file_form.html [new file with mode: 0644]
templates/undelete_image_form.html [new file with mode: 0644]
templates/upload_file_form.html [new file with mode: 0644]
templates/upload_image_form.html [new file with mode: 0644]

diff --git a/.cvsignore b/.cvsignore
new file mode 100644 (file)
index 0000000..20cd39b
--- /dev/null
@@ -0,0 +1 @@
+cocanwiki-*.tar.gz
\ No newline at end of file
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..1942c43
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,341 @@
+                   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.
+\f
+                   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.)
+\f
+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.
+\f
+  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.
+\f
+  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
+\f
+           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) 19yy  <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; see the file COPYING.  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) 19yy 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.
diff --git a/MANIFEST b/MANIFEST
new file mode 100644 (file)
index 0000000..dc0f32a
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,112 @@
+.cvsignore
+COPYING
+MANIFEST
+Makefile
+Makefile.config
+README
+cocanwiki.sql
+conf/cocanwiki.conf
+html/_bin/.cvsignore
+html/_css/admin.css
+html/_css/create.css
+html/_css/editor.css
+html/_css/files.css
+html/_css/images.css
+html/_css/markup.css
+html/_css/standard.css
+html/_graphics/.cvsignore
+html/_graphics/arrow-down.png
+html/_graphics/arrow-up.png
+html/_graphics/cross.png
+html/_graphics/error.png
+html/_graphics/external.png
+html/_graphics/file.png
+html/_graphics/mailto.png
+html/_graphics/markup-preview.png
+html/_graphics/newpage.png
+html/_graphics/ok.png
+html/_js/editor.js
+html/_static/markup.html
+html/robots.txt
+scripts/.cvsignore
+scripts/.depend
+scripts/Makefile
+scripts/admin/.cvsignore
+scripts/admin/admin.ml
+scripts/admin/create_host.ml
+scripts/admin/create_host_form.ml
+scripts/admin/edit_emails.ml
+scripts/admin/edit_emails_form.ml
+scripts/admin/edit_host_css.ml
+scripts/admin/edit_host_css_form.ml
+scripts/admin/edit_hostnames.ml
+scripts/admin/edit_hostnames_form.ml
+scripts/admin/host.ml
+scripts/cgi_expires.ml
+scripts/cocanwiki.ml
+scripts/cocanwiki_diff.ml
+scripts/cocanwiki_emailnotify.ml
+scripts/cocanwiki_ok.ml
+scripts/cocanwiki_version.ml.in
+scripts/create.ml
+scripts/create_form.ml
+scripts/delete_file.ml
+scripts/delete_file_form.ml
+scripts/delete_image.ml
+scripts/delete_image_form.ml
+scripts/diff.ml
+scripts/edit.ml
+scripts/edit_page_css.ml
+scripts/edit_page_css_form.ml
+scripts/file.ml
+scripts/files.ml
+scripts/history.ml
+scripts/hoststyle.ml
+scripts/image.ml
+scripts/images.ml
+scripts/merjisforwiki.ml
+scripts/merjisforwiki.mli
+scripts/page.ml
+scripts/pagestyle.ml
+scripts/preview.ml
+scripts/recent.ml
+scripts/restore.ml
+scripts/restore_form.ml
+scripts/search.ml
+scripts/sitemap.ml
+scripts/undelete_file.ml
+scripts/undelete_file_form.ml
+scripts/undelete_image.ml
+scripts/undelete_image_form.ml
+scripts/upload_file.ml
+scripts/upload_file_form.ml
+scripts/upload_image.ml
+scripts/upload_image_form.ml
+scripts/wikilib.ml
+scripts/wikilib.mli
+templates/admin/admin.html
+templates/admin/create_host_form.html
+templates/admin/edit_emails_form.html
+templates/admin/edit_host_css_form.html
+templates/admin/edit_hostnames_form.html
+templates/admin/host.html
+templates/create_form.html
+templates/delete_file_form.html
+templates/delete_image_form.html
+templates/diff.html
+templates/edit.html
+templates/edit_conflict.html
+templates/edit_page_css_form.html
+templates/files.html
+templates/history.html
+templates/images.html
+templates/ok_error.html
+templates/page.html
+templates/page_404.html
+templates/recent.html
+templates/restore_form.html
+templates/sitemap.html
+templates/undelete_file_form.html
+templates/undelete_image_form.html
+templates/upload_file_form.html
+templates/upload_image_form.html
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..f3fe0e3
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,90 @@
+# $Id: Makefile,v 1.1 2004/09/07 10:14:07 rich Exp $
+
+include Makefile.config
+
+all:
+       $(MAKE) -C scripts all
+
+install:
+       $(MAKE) -C scripts install
+
+# This installs the package centrally (you need to be root).  I only
+# use this for packaging up .debs.
+pkg-install:
+       $(MAKE) -C scripts install
+
+       install -d -m 0755 $(DESTDIR)$(APACHECONFDIR)
+       install -m 0644 conf/cocanwiki.conf $(DESTDIR)$(APACHECONFDIR)/cocanwiki.conf
+
+       install -d -m 0755 $(DESTDIR)$(WIKIINSTALLDIR)
+       install -d -m 0755 $(DESTDIR)$(WIKIINSTALLDIR)/conf
+       install -d -m 0755 $(DESTDIR)$(WIKIINSTALLDIR)/html
+       install -d -m 0755 $(DESTDIR)$(WIKIINSTALLDIR)/html/_bin
+       install -d -m 0755 $(DESTDIR)$(WIKIINSTALLDIR)/html/_bin/admin
+       install -d -m 0755 $(DESTDIR)$(WIKIINSTALLDIR)/html/_css
+       install -d -m 0755 $(DESTDIR)$(WIKIINSTALLDIR)/html/_graphics
+       install -d -m 0755 $(DESTDIR)$(WIKIINSTALLDIR)/html/_js
+       install -d -m 0755 $(DESTDIR)$(WIKIINSTALLDIR)/html/_static
+       install -d -m 0755 $(DESTDIR)$(WIKIINSTALLDIR)/templates
+
+       install -m 0644 cocanwiki.sql $(DESTDIR)$(WIKIINSTALLDIR)
+       install -m 0644 conf/cocanwiki.conf $(DESTDIR)$(WIKIINSTALLDIR)/conf
+       install -m 0644 html/robots.txt $(DESTDIR)$(WIKIINSTALLDIR)/html
+       install -m 0644 html/_bin/*.cma $(DESTDIR)$(WIKIINSTALLDIR)/html/_bin
+       install -m 0644 html/_bin/*.cmo $(DESTDIR)$(WIKIINSTALLDIR)/html/_bin
+       install -m 0644 html/_bin/admin/*.cmo $(DESTDIR)$(WIKIINSTALLDIR)/html/_bin/admin
+       install -m 0644 html/_css/*.css $(DESTDIR)$(WIKIINSTALLDIR)/html/_css
+       install -m 0644 html/_graphics/*.png $(DESTDIR)$(WIKIINSTALLDIR)/html/_graphics
+       install -m 0644 html/_js/*.js $(DESTDIR)$(WIKIINSTALLDIR)/html/_js
+       install -m 0644 html/_static/*.html $(DESTDIR)$(WIKIINSTALLDIR)/html/_static
+       install -m 0644 templates/*.html $(DESTDIR)$(WIKIINSTALLDIR)/templates
+
+clean:
+       rm -f *~ core
+       $(MAKE) -C scripts clean
+
+cocanwiki.sql:
+       pg_dump -i -s cocanwiki > $@
+
+dist:
+       $(MAKE) check-manifest
+       rm -rf $(PACKAGE)-$(VERSION)
+       mkdir $(PACKAGE)-$(VERSION)
+       tar -cf - -T MANIFEST | tar -C $(PACKAGE)-$(VERSION) -xf -
+       tar zcf $(PACKAGE)-$(VERSION).tar.gz $(PACKAGE)-$(VERSION)
+       rm -rf $(PACKAGE)-$(VERSION)
+       ls -l $(PACKAGE)-$(VERSION).tar.gz
+
+check-manifest:
+       @for d in `find -type d -name CVS | grep -v '^\./debian/'`; \
+       do \
+       b=`dirname $$d`/; \
+       awk -F/ '$$1 != "D" {print $$2}' $$d/Entries | \
+       sed -e "s|^|$$b|" -e "s|^\./||"; \
+       done | sort > .check-manifest; \
+       sort MANIFEST > .orig-manifest; \
+       diff -u .orig-manifest .check-manifest; rv=$$?; \
+       rm -f .orig-manifest .check-manifest; \
+       exit $$rv
+
+# Build Debian package.
+dpkg:
+       @if [ 0 != `cvs -q update | wc -l` ]; then \
+       echo Please commit all changes to CVS first.; \
+       exit 1; \
+       fi
+       $(MAKE) dist
+       rm -rf /tmp/dbuild
+       mkdir /tmp/dbuild
+       cp $(PACKAGE)-$(VERSION).tar.gz \
+         /tmp/dbuild/cocanwiki_$(VERSION).orig.tar.gz
+       export CVSROOT=`cat CVS/Root`; \
+         cd /tmp/dbuild && \
+         cvs export \
+         -d cocanwiki-$(VERSION) \
+         -D now merjis/test/wiki
+       cd /tmp/dbuild/cocanwiki-$(VERSION) && dpkg-buildpackage -rfakeroot
+       rm -rf /tmp/dbuild/cocanwiki-$(VERSION)
+       ls -l /tmp/dbuild
+
+.PHONY:        depend pkg-install dist check-manifest dpkg
diff --git a/Makefile.config b/Makefile.config
new file mode 100644 (file)
index 0000000..16524af
--- /dev/null
@@ -0,0 +1,13 @@
+# $Id: Makefile.config,v 1.1 2004/09/07 10:14:07 rich Exp $
+
+PACKAGE := cocanwiki
+VERSION := 0.9.7
+
+# Normally ignored.  However, if you are installing centrally (using
+# 'make pkg-install'), then the components are installed in the
+# following directories.  This really only applies for packagers
+# building the Debian package.
+
+APACHECONFDIR := /etc/apache
+
+WIKIINSTALLDIR := /usr/share/cocanwiki
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..f71bd17
--- /dev/null
+++ b/README
@@ -0,0 +1,115 @@
+README for COCANWIKI
+--------------------
+
+This is a Wiki, written in Objective CAML by Richard W.M. Jones
+(rich@merjis.com).
+
+It is licensed to you under the GNU General Public License (GNU GPL).
+Please see the file COPYING in this directory for full details.
+
+Requirements
+------------
+
+* Apache (probably Apache version 1.3, unless you want to build your
+  own version of Apache 2.0 which doesn't include PCRE).
+
+* Objective CAML 3.07 or above.
+
+* PCRE library for OCaml.
+
+* ExtLib for OCaml.
+  http://ocaml-lib.sourceforge.net/
+
+* mod_caml and DBI libraries for OCaml.
+  http://www.merjis.com/developers/mod_caml/
+
+* PostgreSQL database 7.3 or above.
+
+* Postgres bindings for OCaml.
+  http://www.eleves.ens.fr/home/frisch/soft#postgres
+  (Markus Mottl's later version, renamed PostgreSQL, will probably work, but
+  you may need to modify a little bit of the code in scripts/cocanwiki.ml so
+  it connects to the right database using the right driver).
+
+Installation
+------------
+
+First install all of the above requirements, and check that they're
+working.
+
+Type 'make all install' (you don't need to be root).
+
+Then create a UNICODE database called 'cocanwiki' and load the schema
+from 'cocanwiki.sql'.
+
+Create a Wiki site by hand in the database:
+
+  begin work;
+  set constraints "hosts_hostname_cn" deferred;
+  insert into hosts (canonical_hostname) values ('test.cocan.org');
+  -- HOSTID is the value of hosts.id, normally 1
+  insert into hostnames (hostid, name) values (HOSTID, 'test.cocan.org');
+  commit work;
+
+Create an index page by hand in the database:
+
+  insert into pages (hostid, url, title, description) values (HOSTID, 'index',
+    'Index Page', 'Index Page');
+
+Modify your Apache configuration, adding:
+
+  CamlLoad ...path/to/html/_bin/cocanwiki.cma
+
+  <VirtualHost test.cocan.org>
+        ServerAdmin you@example.com
+        DocumentRoot ...path/to/html
+        ServerName test.cocan.org
+        Include ....path/to/conf/cocanwiki.conf
+  </VirtualHost>
+
+(Change 'test.cocan.org' to a suitable local server name, and set the
+paths as appropriate).
+
+Set any necessary environment variables and start up the server:
+
+  export COCANWIKI_TEMPLATES=...path/to/templates
+  # The following is not required if the database is running locally:
+  export PGHOST=hostname.of.database
+  # Replace this with however you would normally start your webserver:
+  /usr/bin/apache
+
+Try connecting to the local server using your browser.  If it doesn't
+work, look in the Apache error_log file.
+
+More notes on setting environment variables
+-------------------------------------------
+
+You CANNOT use SetEnv to set COCANWIKI_TEMPLATES and/or PGHOST.  This
+is because SetEnv runs too late in the Apache handler cycle for it to
+be picked up by the scripts.
+
+On Debian you must modify /etc/init.d/apache in order to set up
+environment variables.  On the line which reads:
+
+ENV="env -i ...."
+
+remove the -i option from env (Debian bug #252627, marked as WONTFIX).
+
+Directory layout
+----------------
+
+  html/
+
+    Everything visible under the webserver root.
+
+  conf/
+
+    Configuration fragment for Apache.
+
+  scripts/
+
+    CGI scripts.
+
+  templates/
+
+    Templates used by the CGI scripts.
diff --git a/cocanwiki.sql b/cocanwiki.sql
new file mode 100644 (file)
index 0000000..b3f8646
--- /dev/null
@@ -0,0 +1,419 @@
+--
+-- PostgreSQL database dump
+--
+
+SET client_encoding = 'UNICODE';
+SET check_function_bodies = false;
+
+SET SESSION AUTHORIZATION 'postgres';
+
+--
+-- TOC entry 4 (OID 2200)
+-- Name: public; Type: ACL; Schema: -; Owner: postgres
+--
+
+REVOKE ALL ON SCHEMA public FROM PUBLIC;
+REVOKE ALL ON SCHEMA public FROM postgres;
+GRANT ALL ON SCHEMA public TO PUBLIC;
+
+
+SET SESSION AUTHORIZATION 'rich';
+
+SET search_path = public, pg_catalog;
+
+--
+-- TOC entry 5 (OID 536004)
+-- Name: pages; Type: TABLE; Schema: public; Owner: rich
+--
+
+CREATE TABLE pages (
+    id serial NOT NULL,
+    url text,
+    url_deleted text,
+    title text NOT NULL,
+    description text NOT NULL,
+    creation_date timestamp without time zone DEFAULT ('now'::text)::timestamp(6) with time zone NOT NULL,
+    last_modified_date timestamp without time zone DEFAULT ('now'::text)::timestamp(6) with time zone NOT NULL,
+    hostid integer NOT NULL,
+    logged_ip text,
+    redirect text,
+    css text
+);
+
+
+--
+-- TOC entry 6 (OID 536004)
+-- Name: pages; Type: ACL; Schema: public; Owner: rich
+--
+
+REVOKE ALL ON TABLE pages FROM PUBLIC;
+GRANT ALL ON TABLE pages TO "www-data";
+
+
+--
+-- TOC entry 19 (OID 536004)
+-- Name: pages_id_seq; Type: ACL; Schema: public; Owner: rich
+--
+
+REVOKE ALL ON TABLE pages_id_seq FROM PUBLIC;
+GRANT ALL ON TABLE pages_id_seq TO "www-data";
+
+
+--
+-- TOC entry 7 (OID 536021)
+-- Name: contents; Type: TABLE; Schema: public; Owner: rich
+--
+
+CREATE TABLE contents (
+    id serial NOT NULL,
+    pageid integer NOT NULL,
+    ordering integer NOT NULL,
+    sectionname text NOT NULL,
+    content text NOT NULL,
+    divname text
+);
+
+
+--
+-- TOC entry 8 (OID 536021)
+-- Name: contents; Type: ACL; Schema: public; Owner: rich
+--
+
+REVOKE ALL ON TABLE contents FROM PUBLIC;
+GRANT ALL ON TABLE contents TO "www-data";
+
+
+--
+-- TOC entry 20 (OID 536021)
+-- Name: contents_id_seq; Type: ACL; Schema: public; Owner: rich
+--
+
+REVOKE ALL ON TABLE contents_id_seq FROM PUBLIC;
+GRANT ALL ON TABLE contents_id_seq TO "www-data";
+
+
+--
+-- TOC entry 9 (OID 536371)
+-- Name: hosts; Type: TABLE; Schema: public; Owner: rich
+--
+
+CREATE TABLE hosts (
+    id serial NOT NULL,
+    canonical_hostname text NOT NULL,
+    css text
+);
+
+
+--
+-- TOC entry 10 (OID 536371)
+-- Name: hosts; Type: ACL; Schema: public; Owner: rich
+--
+
+REVOKE ALL ON TABLE hosts FROM PUBLIC;
+GRANT ALL ON TABLE hosts TO "www-data";
+
+
+--
+-- TOC entry 21 (OID 536371)
+-- Name: hosts_id_seq; Type: ACL; Schema: public; Owner: rich
+--
+
+REVOKE ALL ON TABLE hosts_id_seq FROM PUBLIC;
+GRANT ALL ON TABLE hosts_id_seq TO "www-data";
+
+
+--
+-- TOC entry 11 (OID 536379)
+-- Name: hostnames; Type: TABLE; Schema: public; Owner: rich
+--
+
+CREATE TABLE hostnames (
+    hostid integer NOT NULL,
+    name text NOT NULL
+);
+
+
+--
+-- TOC entry 12 (OID 536379)
+-- Name: hostnames; Type: ACL; Schema: public; Owner: rich
+--
+
+REVOKE ALL ON TABLE hostnames FROM PUBLIC;
+GRANT ALL ON TABLE hostnames TO "www-data";
+
+
+--
+-- TOC entry 13 (OID 536915)
+-- Name: email_notify; Type: TABLE; Schema: public; Owner: rich
+--
+
+CREATE TABLE email_notify (
+    hostid integer NOT NULL,
+    email text NOT NULL,
+    name text
+);
+
+
+--
+-- TOC entry 14 (OID 536915)
+-- Name: email_notify; Type: ACL; Schema: public; Owner: rich
+--
+
+REVOKE ALL ON TABLE email_notify FROM PUBLIC;
+GRANT ALL ON TABLE email_notify TO "www-data";
+
+
+--
+-- TOC entry 15 (OID 537151)
+-- Name: images; Type: TABLE; Schema: public; Owner: rich
+--
+
+CREATE TABLE images (
+    id serial NOT NULL,
+    hostid integer NOT NULL,
+    name text,
+    name_deleted text,
+    image bytea NOT NULL,
+    width integer NOT NULL,
+    height integer NOT NULL,
+    alt text NOT NULL,
+    title text,
+    longdesc text,
+    "class" text,
+    mime_type text NOT NULL,
+    thumbnail bytea,
+    tn_width integer,
+    tn_height integer,
+    tn_mime_type text,
+    upload_date timestamp without time zone DEFAULT ('now'::text)::timestamp(6) with time zone NOT NULL
+);
+
+
+--
+-- TOC entry 16 (OID 537151)
+-- Name: images; Type: ACL; Schema: public; Owner: rich
+--
+
+REVOKE ALL ON TABLE images FROM PUBLIC;
+GRANT ALL ON TABLE images TO "www-data";
+
+
+--
+-- TOC entry 22 (OID 537151)
+-- Name: images_id_seq; Type: ACL; Schema: public; Owner: rich
+--
+
+REVOKE ALL ON TABLE images_id_seq FROM PUBLIC;
+GRANT ALL ON TABLE images_id_seq TO "www-data";
+
+
+--
+-- TOC entry 17 (OID 537166)
+-- Name: files; Type: TABLE; Schema: public; Owner: rich
+--
+
+CREATE TABLE files (
+    id serial NOT NULL,
+    hostid integer NOT NULL,
+    name text,
+    name_deleted text,
+    content bytea NOT NULL,
+    title text,
+    mime_type text NOT NULL,
+    upload_date timestamp without time zone DEFAULT ('now'::text)::timestamp(6) with time zone NOT NULL
+);
+
+
+--
+-- TOC entry 18 (OID 537166)
+-- Name: files; Type: ACL; Schema: public; Owner: rich
+--
+
+REVOKE ALL ON TABLE files FROM PUBLIC;
+GRANT ALL ON TABLE files TO "www-data";
+
+
+--
+-- TOC entry 23 (OID 537166)
+-- Name: files_id_seq; Type: ACL; Schema: public; Owner: rich
+--
+
+REVOKE ALL ON TABLE files_id_seq FROM PUBLIC;
+GRANT ALL ON TABLE files_id_seq TO "www-data";
+
+
+--
+-- TOC entry 28 (OID 536388)
+-- Name: hostnames_hostid_name_uq; Type: INDEX; Schema: public; Owner: rich
+--
+
+CREATE UNIQUE INDEX hostnames_hostid_name_uq ON hostnames USING btree (hostid, name);
+
+
+--
+-- TOC entry 29 (OID 536389)
+-- Name: hostnams_name_uq; Type: INDEX; Schema: public; Owner: rich
+--
+
+CREATE UNIQUE INDEX hostnams_name_uq ON hostnames USING btree (name);
+
+
+--
+-- TOC entry 25 (OID 536419)
+-- Name: pages_url_uq; Type: INDEX; Schema: public; Owner: rich
+--
+
+CREATE UNIQUE INDEX pages_url_uq ON pages USING btree (hostid, url);
+
+
+--
+-- TOC entry 30 (OID 536924)
+-- Name: email_notify_email_uq; Type: INDEX; Schema: public; Owner: rich
+--
+
+CREATE UNIQUE INDEX email_notify_email_uq ON email_notify USING btree (hostid, email);
+
+
+--
+-- TOC entry 31 (OID 540251)
+-- Name: images_name_uq; Type: INDEX; Schema: public; Owner: rich
+--
+
+CREATE UNIQUE INDEX images_name_uq ON images USING btree (hostid, name);
+
+
+--
+-- TOC entry 33 (OID 540252)
+-- Name: files_name_uq; Type: INDEX; Schema: public; Owner: rich
+--
+
+CREATE UNIQUE INDEX files_name_uq ON files USING btree (hostid, name);
+
+
+--
+-- TOC entry 24 (OID 536012)
+-- Name: pages_pkey; Type: CONSTRAINT; Schema: public; Owner: rich
+--
+
+ALTER TABLE ONLY pages
+    ADD CONSTRAINT pages_pkey PRIMARY KEY (id);
+
+
+--
+-- TOC entry 26 (OID 536027)
+-- Name: contents_pkey; Type: CONSTRAINT; Schema: public; Owner: rich
+--
+
+ALTER TABLE ONLY contents
+    ADD CONSTRAINT contents_pkey PRIMARY KEY (id);
+
+
+--
+-- TOC entry 27 (OID 536377)
+-- Name: hosts_pkey; Type: CONSTRAINT; Schema: public; Owner: rich
+--
+
+ALTER TABLE ONLY hosts
+    ADD CONSTRAINT hosts_pkey PRIMARY KEY (id);
+
+
+--
+-- TOC entry 32 (OID 537158)
+-- Name: images_pkey; Type: CONSTRAINT; Schema: public; Owner: rich
+--
+
+ALTER TABLE ONLY images
+    ADD CONSTRAINT images_pkey PRIMARY KEY (id);
+
+
+--
+-- TOC entry 34 (OID 537173)
+-- Name: files_pkey; Type: CONSTRAINT; Schema: public; Owner: rich
+--
+
+ALTER TABLE ONLY files
+    ADD CONSTRAINT files_pkey PRIMARY KEY (id);
+
+
+--
+-- TOC entry 37 (OID 536029)
+-- Name: $1; Type: FK CONSTRAINT; Schema: public; Owner: rich
+--
+
+ALTER TABLE ONLY contents
+    ADD CONSTRAINT "$1" FOREIGN KEY (pageid) REFERENCES pages(id);
+
+
+--
+-- TOC entry 39 (OID 536384)
+-- Name: $1; Type: FK CONSTRAINT; Schema: public; Owner: rich
+--
+
+ALTER TABLE ONLY hostnames
+    ADD CONSTRAINT "$1" FOREIGN KEY (hostid) REFERENCES hosts(id);
+
+
+--
+-- TOC entry 38 (OID 536394)
+-- Name: hosts_hostname_cn; Type: FK CONSTRAINT; Schema: public; Owner: rich
+--
+
+ALTER TABLE ONLY hosts
+    ADD CONSTRAINT hosts_hostname_cn FOREIGN KEY (id, canonical_hostname) REFERENCES hostnames(hostid, name) DEFERRABLE;
+
+
+--
+-- TOC entry 35 (OID 536404)
+-- Name: $1; Type: FK CONSTRAINT; Schema: public; Owner: rich
+--
+
+ALTER TABLE ONLY pages
+    ADD CONSTRAINT "$1" FOREIGN KEY (hostid) REFERENCES hosts(id);
+
+
+--
+-- TOC entry 40 (OID 536920)
+-- Name: $1; Type: FK CONSTRAINT; Schema: public; Owner: rich
+--
+
+ALTER TABLE ONLY email_notify
+    ADD CONSTRAINT "$1" FOREIGN KEY (hostid) REFERENCES hosts(id);
+
+
+--
+-- TOC entry 41 (OID 537160)
+-- Name: $1; Type: FK CONSTRAINT; Schema: public; Owner: rich
+--
+
+ALTER TABLE ONLY images
+    ADD CONSTRAINT "$1" FOREIGN KEY (hostid) REFERENCES hosts(id);
+
+
+--
+-- TOC entry 42 (OID 537175)
+-- Name: $1; Type: FK CONSTRAINT; Schema: public; Owner: rich
+--
+
+ALTER TABLE ONLY files
+    ADD CONSTRAINT "$1" FOREIGN KEY (hostid) REFERENCES hosts(id);
+
+
+--
+-- TOC entry 36 (OID 539155)
+-- Name: pages_redirect_cn; Type: FK CONSTRAINT; Schema: public; Owner: rich
+--
+
+ALTER TABLE ONLY pages
+    ADD CONSTRAINT pages_redirect_cn FOREIGN KEY (hostid, redirect) REFERENCES pages(hostid, url) DEFERRABLE;
+
+
+SET SESSION AUTHORIZATION 'postgres';
+
+--
+-- TOC entry 3 (OID 2200)
+-- Name: SCHEMA public; Type: COMMENT; Schema: -; Owner: postgres
+--
+
+COMMENT ON SCHEMA public IS 'Standard public namespace';
+
+
diff --git a/conf/cocanwiki.conf b/conf/cocanwiki.conf
new file mode 100644 (file)
index 0000000..88053b7
--- /dev/null
@@ -0,0 +1,75 @@
+# Apache configuration for COCANWIKI.
+# $Id: cocanwiki.conf,v 1.1 2004/09/07 10:14:07 rich Exp $
+
+# Uncomment the following lines if necessary.  You will probably need
+# to adjust the paths to reflect where cocanwiki is really installed.
+
+# DocumentRoot /usr/share/cocanwiki/html
+# CamlLoad /usr/share/cocanwiki/html/_bin/cocanwiki.cma
+
+<Location /_bin>
+  SetHandler ocaml-bytecode
+  CamlHandler Registry.handler
+  Options ExecCGI
+  Allow from all
+</Location>
+
+<Location /_bin/admin>
+  # The admin subdirectory contains sensitive scripts, and should
+  # be protected by ACLs and/or passwords.  This is just an example:
+  Deny from all
+  Allow from 10.0.0.249
+</Location>
+
+<Location /_graphics>
+  ExpiresActive On
+  ExpiresDefault "now plus 1 hour"
+</Location>
+
+<Location /_js>
+  ExpiresActive On
+  ExpiresDefault "now plus 1 hour"
+</Location>
+
+<Location /_css>
+  ExpiresActive On
+  ExpiresDefault "now plus 1 hour"
+</Location>
+
+<Location /_static>
+  ExpiresActive On
+  ExpiresDefault "now plus 1 hour"
+</Location>
+
+RewriteEngine on
+
+# The robots.txt file needs special treatment.
+RewriteRule ^/robots.txt /robots.txt [PT,L]
+
+# Global scripts.
+RewriteRule ^/_admin$ /_bin/admin/admin.cmo [PT,L,QSA]
+RewriteRule ^/_files$ /_bin/files.cmo [PT,L,QSA]
+RewriteRule ^/_global.css$ /_bin/hoststyle.cmo [PT,L,QSA]
+RewriteRule ^/_images$ /_bin/images.cmo [PT,L,QSA]
+RewriteRule ^/_recent$ /_bin/recent.cmo [PT,L,QSA]
+RewriteRule ^/_sitemap$ /_bin/sitemap.cmo [PT,L,QSA]
+
+# Image and file downloads.
+RewriteRule ^/_file/(.*)$ /_bin/file.cmo?name=$1 [PT,L,QSA]
+RewriteRule ^/_image/(.*)$ /_bin/image.cmo?image=$1 [PT,L,QSA]
+
+# Old _dist subdirectory no longer exists.
+RewriteRule ^/_dist/ / [R]
+
+# Page-related scripts.
+RewriteRule ^/([^_].*)/diff$ /_bin/diff.cmo?page=$1 [PT,L,QSA]
+RewriteRule ^/([^_].*)/edit$ /_bin/edit.cmo?page=$1 [PT,L,QSA]
+RewriteRule ^/([^_].*)/editcss$ /_bin/edit_page_css_form.cmo?page=$1 [PT,L,QSA]
+RewriteRule ^/([^_].*)/history$ /_bin/history.cmo?page=$1 [PT,L,QSA]
+RewriteRule ^/([^_].*)/styles.css$ /_bin/pagestyle.cmo?page=$1 [PT,L,QSA]
+
+# Serve pages.
+RewriteRule ^/$ /_bin/page.cmo?page=index [PT,L,QSA]
+#non-greedy matches don't parse - why?
+#RewriteRule ^/([^_].*?)/?$ /_bin/page.cmo?page=$1 [PT,L,QSA]
+RewriteRule ^/([^_].*[^/])/?$ /_bin/page.cmo?page=$1 [PT,L,QSA]
diff --git a/debian/.cvsignore b/debian/.cvsignore
new file mode 100644 (file)
index 0000000..8bb990a
--- /dev/null
@@ -0,0 +1,4 @@
+tmp
+files
+cocanwiki
+*.substvars
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..4d236ca
--- /dev/null
@@ -0,0 +1,6 @@
+cocanwiki (0.9.7-1) unstable; urgency=low
+
+  * Initial Release.
+
+ -- Richard W.M. Jones <rich@annexia.org>  Sat,  1 Nov 2003 12:41:34 +0000
+
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..b8626c4
--- /dev/null
@@ -0,0 +1 @@
+4
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..0429cfb
--- /dev/null
@@ -0,0 +1,17 @@
+Source: cocanwiki
+Priority: optional
+Maintainer: Richard W.M. Jones <rich@annexia.org>
+Build-Depends: debhelper (>= 4.0.0), libpcre-ocaml-dev, libpgsql-ocaml-dev,
+ libextlib-ocaml-dev, ocaml-findlib, ocaml-nox
+Standards-Version: 3.6.0
+
+Package: cocanwiki
+Section: web
+Architecture: any
+Depends: libpgsql-ocaml, libdbi-ocaml (>= 0.9.9), libpcre-ocaml,
+  ocaml-nox (>= 3.08), imagemagick
+Suggests: apache
+Description: A Wiki written in Objective CAML (OCaml)
+ This is a Wiki written entirely in Objective CAML (OCaml).
+ .
+ More information is available at http://sandbox.merjis.com/
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..87a1a25
--- /dev/null
@@ -0,0 +1,21 @@
+This package was debianized by Richard W.M. Jones <rich@annexia.org> on
+Sat,  1 Nov 2003 12:41:34 +0000.
+
+Upstream Author: Richard W.M. Jones <rich@annexia.org>
+
+Copyright: Copyright (C) 2004 Merjis Ltd.
+
+    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; see the file COPYING.  If not, write to
+    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+    Boston, MA 02111-1307, USA.
diff --git a/debian/docs b/debian/docs
new file mode 100644 (file)
index 0000000..2e3abae
--- /dev/null
@@ -0,0 +1,2 @@
+COPYING
+README
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..f7da905
--- /dev/null
@@ -0,0 +1,78 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# GNU copyright 1997 to 1999 by Joey Hess.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+configure: configure-stamp
+configure-stamp:
+       dh_testdir
+       touch configure-stamp
+
+build: build-stamp
+build-stamp: configure-stamp 
+       dh_testdir
+
+       $(MAKE)
+       touch build-stamp
+
+clean:
+       dh_testdir
+       dh_testroot
+       rm -f build-stamp configure-stamp
+
+       -$(MAKE) clean
+       dh_clean 
+
+install: build
+       dh_testdir
+       dh_testroot
+       dh_clean -k 
+       dh_installdirs
+
+       if [ -x /usr/bin/ocamlopt ]; then \
+               BUILD_OPT=1; \
+       else \
+               BUILD_OPT=0; \
+       fi; \
+       $(MAKE) pkg-install DESTDIR=$(CURDIR)/debian/cocanwiki
+
+# Build architecture-independent files here.
+binary-indep: build install
+# We have nothing to do by default.
+
+# Build architecture-dependent files here.
+binary-arch: build install
+       dh_testdir
+       dh_testroot
+       dh_installchangelogs 
+       dh_installdocs
+       dh_installexamples
+#      dh_install
+#      dh_installmenu
+#      dh_installdebconf       
+#      dh_installlogrotate
+#      dh_installemacsen
+#      dh_installpam
+#      dh_installmime
+#      dh_installinit
+#      dh_installcron
+#      dh_installinfo
+       dh_installman
+       dh_link
+#      dh_strip                # Nothing to strip, hopefully.
+       dh_compress
+       dh_fixperms
+#      dh_perl
+#      dh_python
+#      dh_makeshlibs
+       dh_installdeb
+       dh_shlibdeps
+       dh_gencontrol
+       dh_md5sums
+       dh_builddeb
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
diff --git a/html/_bin/.cvsignore b/html/_bin/.cvsignore
new file mode 100644 (file)
index 0000000..72e8ffc
--- /dev/null
@@ -0,0 +1 @@
+*
diff --git a/html/_css/admin.css b/html/_css/admin.css
new file mode 100644 (file)
index 0000000..dfd1a6b
--- /dev/null
@@ -0,0 +1,33 @@
+/* Stylesheet for COCANWIKI, derived from EWM.
+ * $Id: admin.css,v 1.1 2004/09/07 10:14:08 rich Exp $
+ */
+
+table#hosts {
+  width: 100%;
+}
+
+table#host {
+  border-collapse: collapse;
+}
+
+table#host th {
+  padding: 6px;
+/*  border: 1px solid #000; */
+  text-align: right;
+  vertical-align: top;
+}
+
+table#host td {
+  padding: 6px;
+/*  border: 1px solid #000; */
+  vertical-align: top;
+}
+
+a.alias {
+  font-size: 70%;
+}
+
+span.explanation {
+  font-size: 70%;
+  font-style: italic;
+}
\ No newline at end of file
diff --git a/html/_css/create.css b/html/_css/create.css
new file mode 100644 (file)
index 0000000..6e9d1ec
--- /dev/null
@@ -0,0 +1,22 @@
+/* Stylesheet for COCANWIKI, derived from EWM.
+ * $Id: create.css,v 1.1 2004/09/07 10:14:08 rich Exp $
+ */
+
+table#create {
+  empty-cells: show;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+table#create th {
+  vertical-align: top;
+  text-align: right;
+}
+
+table#create td {
+  vertical-align: top;
+}
+
+table#create td.explanation {
+  font-size: 70%;
+}
\ No newline at end of file
diff --git a/html/_css/editor.css b/html/_css/editor.css
new file mode 100644 (file)
index 0000000..af67ca0
--- /dev/null
@@ -0,0 +1,95 @@
+/* Stylesheet for COCANWIKI editor, derived from EWM.
+ * $Id: editor.css,v 1.1 2004/09/07 10:14:08 rich Exp $
+ */
+
+body {
+  margin-top: 4em;
+}
+
+p.insert {
+  text-align: center;
+}
+
+p.insert input {
+  font-size: 70%;
+}
+
+div.action {
+  float: right;
+}
+
+div.action button.action {
+  text-align: left;
+  width: 6em;
+}
+
+div.action input.action {
+  width: 6em;
+}
+
+input.heading {
+  color: #003;
+  background-color: #ffc;
+  padding: 6px 3px 3px 3px;
+  font-size: 120%;
+  font-weight: bold;
+}
+
+div.preview {
+  border: 1px dashed #ddd;
+  background-color: #f8f8f8;
+  padding: 3px;
+}
+
+abbr.css_id {
+  font-size: 70%;
+}
+
+input.css_id {
+  font-size: 70%;
+}
+
+div#errors {
+  border: solid 2px #f00;
+  color: #c00;
+  padding: 4px;
+  width: 80%;
+  margin-left: 10%;
+}
+
+div#errors p {
+  margin-top: 0px;
+  margin-bottom: 0px;
+}
+
+div#errors ul {
+  list-style: none;
+  margin-left: 0px;
+  padding-left: 0px;
+}
+
+a.help_link {
+  font-size: 0.7em;
+}
+
+table#edit_conflict {
+}
+
+table#edit_conflict pre {
+  background-color: inherit;
+  border: none;
+}
+
+table#edit_conflict th.other {
+  background-color: #fcc;
+}
+table#edit_conflict td.other {
+  background-color: #fcc;
+}
+
+table#edit_conflict th.our {
+  background-color: #cfc;
+}
+table#edit_conflict td.our {
+  background-color: #cfc;
+}
diff --git a/html/_css/files.css b/html/_css/files.css
new file mode 100644 (file)
index 0000000..16c8482
--- /dev/null
@@ -0,0 +1,8 @@
+/* Stylesheet for COCANWIKI, derived from EWM.
+ * $Id: files.css,v 1.1 2004/09/07 10:14:08 rich Exp $
+ */
+
+table#files td.actions {
+  text-align: right;
+  vertical-align: bottom;
+}
diff --git a/html/_css/images.css b/html/_css/images.css
new file mode 100644 (file)
index 0000000..34f1ade
--- /dev/null
@@ -0,0 +1,12 @@
+/* Stylesheet for COCANWIKI, derived from EWM.
+ * $Id: images.css,v 1.1 2004/09/07 10:14:08 rich Exp $
+ */
+
+table#images td.thumbnail {
+  text-align: center;
+}
+
+table#images td.actions {
+  text-align: right;
+  vertical-align: bottom;
+}
diff --git a/html/_css/markup.css b/html/_css/markup.css
new file mode 100644 (file)
index 0000000..18eea4d
--- /dev/null
@@ -0,0 +1,32 @@
+/* Stylesheet for COCANWIKI, derived from EWM.
+ * $Id: markup.css,v 1.1 2004/09/07 10:14:08 rich Exp $
+ */
+
+table#markup {
+  border-collapse: collapse;
+  empty-cells: show;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+table#markup th {
+  padding-top: 6px;
+  padding-bottom: 6px;
+  border: 1px solid #000;
+  vertical-align: top;
+  text-align: center;
+}
+
+table#markup td {
+  border: 1px solid #000;
+  vertical-align: top;
+}
+
+table#markup td.notes {
+  font-style: italic;
+}
+
+pre {
+  border: none;
+  background: #fff;
+}
\ No newline at end of file
diff --git a/html/_css/standard.css b/html/_css/standard.css
new file mode 100644 (file)
index 0000000..b729af7
--- /dev/null
@@ -0,0 +1,189 @@
+/* Stylesheet for COCANWIKI, derived from EWM.
+ * $Id: standard.css,v 1.1 2004/09/07 10:14:08 rich Exp $
+ */
+
+body {
+  background: #fff;
+  color: #000;
+  font-family: arial, helvetica, sans-serif;
+  margin-top: 6em;
+}
+
+/* Headers. */
+h1 {
+  background-color: #fff;
+  border-bottom: 1px solid #000;
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  font-size: 140%;
+  width: 100%;
+  padding-left: 2em;
+}
+
+h2 {
+  color: #003;
+  background-color: #ffc;
+  padding: 6px 3px 3px 3px;
+  border-top: 1px solid #eeb;
+  font-size: 120%;
+}
+
+/* Ordinary text. */
+p {
+  margin-left: 1em;
+}
+
+/* Links. */
+a.external {
+  background: url(/_graphics/external.png) center right no-repeat;
+  padding-right: 13px;
+}
+
+a.newpage {
+  background: url(/_graphics/newpage.png) center right no-repeat;
+  padding-right: 13px;
+  color: #ba0000;
+}
+
+a.mailto {
+  background: url(/_graphics/mailto.png) center right no-repeat;
+  padding-right: 13px;
+}
+
+a.image_not_found {
+  background: url(/_graphics/newpage.png) center right no-repeat;
+  padding-right: 13px;
+  color: #ba0000;
+}
+
+a.file_not_found {
+  background: url(/_graphics/newpage.png) center right no-repeat;
+  padding-right: 13px;
+  color: #ba0000;
+}
+
+/* Preformatted text. */
+pre {
+  margin-left: 1em;
+  background-color: #eee;
+  padding: 3px;
+  border: dashed 1px #ddd;
+}
+
+/* Images.  (Try setting 'class' on an image). */
+img.border {
+  border: 1px solid #000;
+  margin: 0.3em;
+}
+
+img.right_float {
+  float: right;
+  margin: 0.3em;
+}
+
+img.right_float_border {
+  border: 1px solid #000;
+  float: right;
+  margin: 0.3em;
+}
+
+img.left_float {
+  float: left;
+  margin: 0.3em;
+}
+
+img.left_float_border {
+  border: 1px solid #000;
+  float: left;
+  margin: 0.3em;
+}
+
+/* Edit links. */
+p.edit_link {
+  margin: 6px 0px 0px 0px;
+  padding-right: 12px;
+  float: right;
+}
+
+/* Menus. */
+ul.menu {
+  padding: 0px;
+  margin-left: 1em;
+  list-style: none;
+}
+
+ul.menu li {
+  display: inline;
+}
+
+ul#topmenu {
+  position: absolute;
+  top: 3em;
+  left: 0.8em;
+}
+
+ul#footer {
+  text-align: center;
+  font-size: 70%;
+}
+
+/* Sitemap page. */
+ul#sitemap {
+  list-style: none;
+  margin-left: 0px;
+  padding-left: 0px;
+}
+
+ul#sitemap p.content {
+  margin-top: 0px;
+  margin-bottom: 0px;
+  font-size: 0.7em;
+}
+
+ul#sitemap p.info {
+  margin-top: 0px;
+  margin-bottom: 0px;
+  font-size: 0.7em;
+}
+
+ul#sitemap p.info a {
+  color: green;
+  text-decoration: none;
+}
+
+/* Recent changes list. */
+ul#recent_changes {
+  list-style: none;
+  margin-left: 0px;
+  padding-left: 0px;
+}
+
+ul#recent_changes span.date {
+  display: block;
+  float: left;
+  width: 8.5em;
+}
+
+/* History list. */
+ul#history {
+  list-style: none;
+  margin-left: 0px;
+  padding-left: 0px;
+}
+
+ul#history span.date {
+  display: block;
+  float: left;
+  width: 8.5em;
+}
+
+/* Versions. */
+div#old_version {
+  border: solid 2px #f00;
+  color: #c00;
+  padding: 4px;
+  width: 80%;
+  margin-left: 10%;
+  clear: both;
+}
diff --git a/html/_graphics/.cvsignore b/html/_graphics/.cvsignore
new file mode 100644 (file)
index 0000000..5c165d9
--- /dev/null
@@ -0,0 +1 @@
+.xvpics
diff --git a/html/_graphics/arrow-down.png b/html/_graphics/arrow-down.png
new file mode 100644 (file)
index 0000000..1fce1f3
Binary files /dev/null and b/html/_graphics/arrow-down.png differ
diff --git a/html/_graphics/arrow-up.png b/html/_graphics/arrow-up.png
new file mode 100644 (file)
index 0000000..610ef3c
Binary files /dev/null and b/html/_graphics/arrow-up.png differ
diff --git a/html/_graphics/cross.png b/html/_graphics/cross.png
new file mode 100644 (file)
index 0000000..4878cf0
Binary files /dev/null and b/html/_graphics/cross.png differ
diff --git a/html/_graphics/error.png b/html/_graphics/error.png
new file mode 100644 (file)
index 0000000..caf0179
Binary files /dev/null and b/html/_graphics/error.png differ
diff --git a/html/_graphics/external.png b/html/_graphics/external.png
new file mode 100644 (file)
index 0000000..8ae475d
Binary files /dev/null and b/html/_graphics/external.png differ
diff --git a/html/_graphics/file.png b/html/_graphics/file.png
new file mode 100644 (file)
index 0000000..3201e40
Binary files /dev/null and b/html/_graphics/file.png differ
diff --git a/html/_graphics/mailto.png b/html/_graphics/mailto.png
new file mode 100644 (file)
index 0000000..26ea946
Binary files /dev/null and b/html/_graphics/mailto.png differ
diff --git a/html/_graphics/markup-preview.png b/html/_graphics/markup-preview.png
new file mode 100644 (file)
index 0000000..ad30013
Binary files /dev/null and b/html/_graphics/markup-preview.png differ
diff --git a/html/_graphics/newpage.png b/html/_graphics/newpage.png
new file mode 100644 (file)
index 0000000..192f6b0
Binary files /dev/null and b/html/_graphics/newpage.png differ
diff --git a/html/_graphics/ok.png b/html/_graphics/ok.png
new file mode 100644 (file)
index 0000000..b7e10c4
Binary files /dev/null and b/html/_graphics/ok.png differ
diff --git a/html/_js/editor.js b/html/_js/editor.js
new file mode 100644 (file)
index 0000000..ad33c6e
--- /dev/null
@@ -0,0 +1,45 @@
+/* Javascript for OCAMLWIKI.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: editor.js,v 1.1 2004/09/07 10:14:09 rich Exp $
+ */
+
+var delay = 1000               // Delay in milliseconds before updating.
+
+function update_preview (content_id, preview_id)
+{
+  // Updating is quite expensive, so only update after a period of apparent
+  // inactivity.
+  var preview = document.getElementById (preview_id);
+  if (preview.timer) clearTimeout (preview.timer);
+  preview.timer =
+    setTimeout ("update_preview_now ('" + content_id + "', '" +
+               preview_id + "')", delay);
+}
+
+function update_preview_now (content_id, preview_id)
+{
+  // Remove the timer.
+  var preview = document.getElementById (preview_id);
+  if (preview.timer) preview.timer = null;
+
+  // Get the Wiki-markup content from the content textarea.
+  var content = document.getElementById (content_id).value;
+
+  // Send the Wiki-markup to the server to be turned into XHTML.
+  var http = document.all ?
+    new ActiveXObject ('Microsoft.XMLHTTP') : new XMLHttpRequest ();
+  if (http)
+    {
+      http.open ('POST', '/_bin/preview.cmo', false);
+      http.setRequestHeader ('Content-Type',
+                            'application/x-www-form-urlencoded');
+      http.send ('content=' + encodeURIComponent (content));
+
+      var xhtml = http.responseText;
+
+      // Next line fails with my copy of IE if the text contains a
+      // link (ie. <a href...>).  It is unclear why.  The error is:
+      // "Unknown runtime error"
+      preview.innerHTML = xhtml;
+    }
+}
diff --git a/html/_static/markup.html b/html/_static/markup.html
new file mode 100644 (file)
index 0000000..7b6ebc5
--- /dev/null
@@ -0,0 +1,134 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Writing in Wiki</title>
+<meta name="description" content="Description of the Wiki markup language supported by this Wiki" />
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+<link rel="stylesheet" href="/_css/markup.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Writing in Wiki</h1>
+
+<table id="markup">
+<tr><th colspan="3">Links</th></tr>
+
+<tr>
+<td><code>See our [[Scary monsters]]</code></td>
+<td>See our <a href="/scary_monsters" title="Scary monsters" class="internal">Scary monsters</a></td>
+<td rowspan="4" class="notes">
+You can also link to email addresses using <code>[[mailto:address]]</code>.
+</td>
+</tr>
+
+<tr>
+<td><code>See our [[Scary monsters|big cats]]</code></td>
+<td>See our <a href="/scary_monsters" title="Scary monsters" class="internal">big cats</a></td>
+</tr>
+
+<tr>
+<td><code>[[http://news.bbc.co.uk/]]</code></td>
+<td><a href="http://news.bbc.co.uk/" title="http://news.bbc.co.uk/" class="external">http://news.bbc.co.uk/</a></td>
+</tr>
+
+<tr>
+<td><code>[[http://news.bbc.co.uk/|BBC News]]</code></td>
+<td><a href="http://news.bbc.co.uk/" title="http://news.bbc.co.uk/" class="external">BBC News</a></td>
+</tr>
+
+<tr><th colspan="3">Bullets, subheadings, preformatted</th></tr>
+
+<tr>
+<td>
+<pre>
+Minutes of last week:
+* Introductions
+* Cup of tea
+</pre>
+</td>
+<td>
+Minutes of last week:
+<ul><li> Introductions</li>
+<li> Cup of tea</li>
+</ul>
+</td>
+<td class="notes">
+Use <code>#</code> for numbered bullets.
+</td>
+</tr>
+
+<tr>
+<td>
+<pre>
+= Subheading
+== Subsubheading
+</pre>
+</td>
+<td>
+<h3>Subheading</h3>
+<h4>Subsubheading</h4>
+</td>
+<td></td>
+</tr>
+
+<tr>
+<td>
+<pre>
+ A space before each line
+ lets you format the
+ text     however    you
+         want.
+</pre>
+</td>
+<td>
+<pre>
+A space before each line
+lets you format the
+text     however    you
+        want.
+</pre>
+</td>
+<td></td>
+</tr>
+
+<tr><th colspan="3">Text formatting</th></tr>
+
+<tr>
+<td><code>Did you <strong>&lt;em&gt;</strong>really<strong>&lt;/em&gt;</strong> mean you drink
+only H<strong>&lt;sub&gt;</strong>2<strong>&lt;/sub&gt;</strong>O?</code></td>
+<td>Did you <em>really</em> mean you drink
+only H<sub>2</sub>O?</td>
+<td class="notes">
+<code>&lt;strong&gt; &lt;em&gt; &lt;code&gt; &lt;sup&gt; &lt;sub&gt;</code>
+</td>
+</tr>
+
+<tr>
+<td><code><strong>&lt;nowiki&gt;</strong>Turns &lt;em&gt;off all wiki<strong>&lt;/nowiki&gt;</strong></td>
+<td>Turns &lt;em&gt;off all wiki</td>
+<td></td>
+</tr>
+
+</table>
+
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/html/robots.txt b/html/robots.txt
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/scripts/.cvsignore b/scripts/.cvsignore
new file mode 100644 (file)
index 0000000..9ef5875
--- /dev/null
@@ -0,0 +1,4 @@
+*.cmo
+*.cmi
+*.cma
+cocanwiki_version.ml
diff --git a/scripts/.depend b/scripts/.depend
new file mode 100644 (file)
index 0000000..dfe6e05
--- /dev/null
@@ -0,0 +1,110 @@
+cgi_expires.cmo: merjisforwiki.cmi 
+cgi_expires.cmx: merjisforwiki.cmx 
+cocanwiki.cmo: merjisforwiki.cmi 
+cocanwiki.cmx: merjisforwiki.cmx 
+cocanwiki_diff.cmo: merjisforwiki.cmi 
+cocanwiki_diff.cmx: merjisforwiki.cmx 
+cocanwiki_emailnotify.cmo: merjisforwiki.cmi 
+cocanwiki_emailnotify.cmx: merjisforwiki.cmx 
+cocanwiki_ok.cmo: cocanwiki.cmo merjisforwiki.cmi 
+cocanwiki_ok.cmx: cocanwiki.cmx merjisforwiki.cmx 
+create.cmo: cocanwiki.cmo cocanwiki_emailnotify.cmo cocanwiki_ok.cmo \
+    wikilib.cmi 
+create.cmx: cocanwiki.cmx cocanwiki_emailnotify.cmx cocanwiki_ok.cmx \
+    wikilib.cmx 
+create_form.cmo: cocanwiki.cmo cocanwiki_ok.cmo wikilib.cmi 
+create_form.cmx: cocanwiki.cmx cocanwiki_ok.cmx wikilib.cmx 
+delete_file.cmo: cocanwiki.cmo cocanwiki_emailnotify.cmo cocanwiki_ok.cmo \
+    merjisforwiki.cmi 
+delete_file.cmx: cocanwiki.cmx cocanwiki_emailnotify.cmx cocanwiki_ok.cmx \
+    merjisforwiki.cmx 
+delete_file_form.cmo: cocanwiki.cmo merjisforwiki.cmi 
+delete_file_form.cmx: cocanwiki.cmx merjisforwiki.cmx 
+delete_image.cmo: cocanwiki.cmo cocanwiki_emailnotify.cmo cocanwiki_ok.cmo \
+    merjisforwiki.cmi 
+delete_image.cmx: cocanwiki.cmx cocanwiki_emailnotify.cmx cocanwiki_ok.cmx \
+    merjisforwiki.cmx 
+delete_image_form.cmo: cocanwiki.cmo merjisforwiki.cmi 
+delete_image_form.cmx: cocanwiki.cmx merjisforwiki.cmx 
+diff.cmo: cocanwiki.cmo cocanwiki_diff.cmo merjisforwiki.cmi 
+diff.cmx: cocanwiki.cmx cocanwiki_diff.cmx merjisforwiki.cmx 
+edit.cmo: cocanwiki.cmo cocanwiki_diff.cmo cocanwiki_emailnotify.cmo \
+    cocanwiki_ok.cmo merjisforwiki.cmi 
+edit.cmx: cocanwiki.cmx cocanwiki_diff.cmx cocanwiki_emailnotify.cmx \
+    cocanwiki_ok.cmx merjisforwiki.cmx 
+edit_page_css.cmo: cocanwiki.cmo cocanwiki_diff.cmo cocanwiki_emailnotify.cmo \
+    cocanwiki_ok.cmo merjisforwiki.cmi 
+edit_page_css.cmx: cocanwiki.cmx cocanwiki_diff.cmx cocanwiki_emailnotify.cmx \
+    cocanwiki_ok.cmx merjisforwiki.cmx 
+edit_page_css_form.cmo: cocanwiki.cmo 
+edit_page_css_form.cmx: cocanwiki.cmx 
+file.cmo: cgi_expires.cmo cocanwiki.cmo merjisforwiki.cmi 
+file.cmx: cgi_expires.cmx cocanwiki.cmx merjisforwiki.cmx 
+files.cmo: cocanwiki.cmo merjisforwiki.cmi 
+files.cmx: cocanwiki.cmx merjisforwiki.cmx 
+history.cmo: cocanwiki.cmo merjisforwiki.cmi 
+history.cmx: cocanwiki.cmx merjisforwiki.cmx 
+hoststyle.cmo: cgi_expires.cmo cocanwiki.cmo 
+hoststyle.cmx: cgi_expires.cmx cocanwiki.cmx 
+image.cmo: cgi_expires.cmo cocanwiki.cmo merjisforwiki.cmi 
+image.cmx: cgi_expires.cmx cocanwiki.cmx merjisforwiki.cmx 
+images.cmo: cocanwiki.cmo merjisforwiki.cmi 
+images.cmx: cocanwiki.cmx merjisforwiki.cmx 
+merjisforwiki.cmo: merjisforwiki.cmi 
+merjisforwiki.cmx: merjisforwiki.cmi 
+page.cmo: cocanwiki.cmo cocanwiki_ok.cmo cocanwiki_version.cmo \
+    merjisforwiki.cmi wikilib.cmi 
+page.cmx: cocanwiki.cmx cocanwiki_ok.cmx cocanwiki_version.cmx \
+    merjisforwiki.cmx wikilib.cmx 
+pagestyle.cmo: cgi_expires.cmo cocanwiki.cmo 
+pagestyle.cmx: cgi_expires.cmx cocanwiki.cmx 
+preview.cmo: cocanwiki.cmo wikilib.cmi 
+preview.cmx: cocanwiki.cmx wikilib.cmx 
+recent.cmo: cocanwiki.cmo 
+recent.cmx: cocanwiki.cmx 
+search.cmo: cocanwiki.cmo 
+search.cmx: cocanwiki.cmx 
+sitemap.cmo: cocanwiki.cmo merjisforwiki.cmi wikilib.cmi 
+sitemap.cmx: cocanwiki.cmx merjisforwiki.cmx wikilib.cmx 
+undelete_file.cmo: cocanwiki.cmo cocanwiki_ok.cmo merjisforwiki.cmi 
+undelete_file.cmx: cocanwiki.cmx cocanwiki_ok.cmx merjisforwiki.cmx 
+undelete_file_form.cmo: cocanwiki.cmo merjisforwiki.cmi 
+undelete_file_form.cmx: cocanwiki.cmx merjisforwiki.cmx 
+undelete_image.cmo: cocanwiki.cmo cocanwiki_ok.cmo merjisforwiki.cmi 
+undelete_image.cmx: cocanwiki.cmx cocanwiki_ok.cmx merjisforwiki.cmx 
+undelete_image_form.cmo: cocanwiki.cmo merjisforwiki.cmi 
+undelete_image_form.cmx: cocanwiki.cmx merjisforwiki.cmx 
+upload_file.cmo: cocanwiki.cmo cocanwiki_emailnotify.cmo cocanwiki_ok.cmo \
+    merjisforwiki.cmi 
+upload_file.cmx: cocanwiki.cmx cocanwiki_emailnotify.cmx cocanwiki_ok.cmx \
+    merjisforwiki.cmx 
+upload_file_form.cmo: cocanwiki.cmo merjisforwiki.cmi 
+upload_file_form.cmx: cocanwiki.cmx merjisforwiki.cmx 
+upload_image.cmo: cocanwiki.cmo cocanwiki_emailnotify.cmo cocanwiki_ok.cmo \
+    merjisforwiki.cmi 
+upload_image.cmx: cocanwiki.cmx cocanwiki_emailnotify.cmx cocanwiki_ok.cmx \
+    merjisforwiki.cmx 
+upload_image_form.cmo: cocanwiki.cmo merjisforwiki.cmi 
+upload_image_form.cmx: cocanwiki.cmx merjisforwiki.cmx 
+wikilib.cmo: merjisforwiki.cmi wikilib.cmi 
+wikilib.cmx: merjisforwiki.cmx wikilib.cmi 
+admin/admin.cmo: cocanwiki.cmo merjisforwiki.cmi 
+admin/admin.cmx: cocanwiki.cmx merjisforwiki.cmx 
+admin/create_host.cmo: cocanwiki.cmo cocanwiki_ok.cmo merjisforwiki.cmi 
+admin/create_host.cmx: cocanwiki.cmx cocanwiki_ok.cmx merjisforwiki.cmx 
+admin/create_host_form.cmo: cocanwiki.cmo 
+admin/create_host_form.cmx: cocanwiki.cmx 
+admin/edit_emails.cmo: cocanwiki.cmo cocanwiki_ok.cmo merjisforwiki.cmi 
+admin/edit_emails.cmx: cocanwiki.cmx cocanwiki_ok.cmx merjisforwiki.cmx 
+admin/edit_emails_form.cmo: cocanwiki.cmo 
+admin/edit_emails_form.cmx: cocanwiki.cmx 
+admin/edit_host_css.cmo: cocanwiki.cmo cocanwiki_ok.cmo merjisforwiki.cmi 
+admin/edit_host_css.cmx: cocanwiki.cmx cocanwiki_ok.cmx merjisforwiki.cmx 
+admin/edit_host_css_form.cmo: cocanwiki.cmo 
+admin/edit_host_css_form.cmx: cocanwiki.cmx 
+admin/edit_hostnames.cmo: cocanwiki.cmo cocanwiki_ok.cmo merjisforwiki.cmi 
+admin/edit_hostnames.cmx: cocanwiki.cmx cocanwiki_ok.cmx merjisforwiki.cmx 
+admin/edit_hostnames_form.cmo: cocanwiki.cmo 
+admin/edit_hostnames_form.cmx: cocanwiki.cmx 
+admin/host.cmo: cocanwiki.cmo merjisforwiki.cmi 
+admin/host.cmx: cocanwiki.cmx merjisforwiki.cmx 
diff --git a/scripts/Makefile b/scripts/Makefile
new file mode 100644 (file)
index 0000000..be3200c
--- /dev/null
@@ -0,0 +1,103 @@
+# Makefile for COCANWIKI.
+# $Id: Makefile,v 1.1 2004/09/07 10:14:09 rich Exp $
+
+include ../Makefile.config
+
+OCAMLC := ocamlc
+OCAMLCFLAGS := -w s -I +apache -I +pcre -I +dbi -I +extlib
+CPP := cpp
+
+LIB_OBJS := \
+       merjisforwiki.cmo \
+       cocanwiki.cmo \
+       cocanwiki_version.cmo \
+       cocanwiki_ok.cmo \
+       cocanwiki_diff.cmo \
+       cocanwiki_emailnotify.cmo \
+       wikilib.cmo \
+       cgi_expires.cmo
+
+OBJS := create.cmo \
+       create_form.cmo \
+       delete_file.cmo \
+       delete_file_form.cmo \
+       delete_image.cmo \
+       delete_image_form.cmo \
+       diff.cmo \
+       edit.cmo \
+       edit_page_css.cmo \
+       edit_page_css_form.cmo \
+       file.cmo \
+       files.cmo \
+       history.cmo \
+       hoststyle.cmo \
+       image.cmo \
+       images.cmo \
+       page.cmo \
+       pagestyle.cmo \
+       preview.cmo \
+       recent.cmo \
+       restore.cmo \
+       restore_form.cmo \
+       search.cmo \
+       sitemap.cmo \
+       undelete_file.cmo \
+       undelete_file_form.cmo \
+       undelete_image.cmo \
+       undelete_image_form.cmo \
+       upload_file.cmo \
+       upload_file_form.cmo \
+       upload_image.cmo \
+       upload_image_form.cmo
+
+ADMIN_OBJS := \
+       admin/admin.cmo \
+       admin/create_host.cmo \
+       admin/create_host_form.cmo \
+       admin/edit_emails.cmo \
+       admin/edit_emails_form.cmo \
+       admin/edit_host_css.cmo \
+       admin/edit_host_css_form.cmo \
+       admin/edit_hostnames.cmo \
+       admin/edit_hostnames_form.cmo \
+       admin/host.cmo
+
+INSTDIR := ../html/_bin
+
+all: cocanwiki.cma $(OBJS) $(ADMIN_OBJS)
+
+cocanwiki.cma: $(LIB_OBJS)
+       $(OCAMLC) $(OCAMLCFLAGS) -a -o $@ $^
+
+cocanwiki_version.ml: cocanwiki_version.ml.in ../Makefile.config
+       $(CPP) -P -DPACKAGE="$(PACKAGE)" -DVERSION="$(VERSION)" $< > $@
+
+clean:
+       rm -f *~ *.bak core *.cmi *.cmo *.cma cocanwiki_version.ml
+       rm -f admin/*~ admin/*.bak admin/core admin/*.cmi admin/*.cmo
+
+install:
+       install -d -m 0755 $(INSTDIR)
+       install -c -m 0555 cocanwiki.cma $(OBJS) $(INSTDIR)
+
+       install -d -m 0755 $(INSTDIR)/admin
+       install -c -m 0555 $(ADMIN_OBJS) $(INSTDIR)/admin
+
+%.cmi: %.mli
+       $(OCAMLC) $(OCAMLCFLAGS) -c $<
+
+%.cmo: %.ml
+       $(OCAMLC) $(OCAMLCFLAGS) -c $<
+
+dep:    .depend
+depend: .depend
+
+.depend:
+       rm -f .depend
+       ocamldep *.mli *.ml admin/*.ml > $@
+
+ifeq ($(wildcard .depend),.depend)
+include .depend
+endif
+
+.SUFFIXES: .ml .mli .cmi .cmo
diff --git a/scripts/admin/.cvsignore b/scripts/admin/.cvsignore
new file mode 100644 (file)
index 0000000..57506e5
--- /dev/null
@@ -0,0 +1,2 @@
+*.cmi
+*.cmo
\ No newline at end of file
diff --git a/scripts/admin/admin.ml b/scripts/admin/admin.ml
new file mode 100644 (file)
index 0000000..109cf8d
--- /dev/null
@@ -0,0 +1,74 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: admin.ml,v 1.1 2004/09/07 10:14:10 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Merjisforwiki
+
+open Cocanwiki
+
+let template = get_template "admin/admin.html"
+
+let run r (q : cgi) (dbh : Dbi.connection) _ _ =
+  (* Select out the alternative hostnames. *)
+  let sth = dbh#prepare_cached
+             "select hs.hostid, hs.name from hostnames hs
+                where not exists (select 1 from hosts
+                                   where id = hs.hostid
+                                     and canonical_hostname = hs.name)" in
+  sth#execute [];
+
+  let hostnames = sth#map (function [`Int hostid; `String name] ->
+                            hostid, name
+                            | _ -> assert false) in
+
+  (* Pull out the details of all the wikis on the server. *)
+  let sth = dbh#prepare_cached
+             "select h.id, h.canonical_hostname,
+                      (select count(*) from pages
+                        where hostid = h.id and url is not null),
+                      (select max(last_modified_date) from pages
+                        where hostid = h.id and url is not null)
+                 from hosts h
+                order by 2" in
+  sth#execute [];
+
+  let table =
+    sth#map
+      (function [`Int id; `String canonical_hostname;
+                (`Null | `Int _) as page_count;
+                (`Null | `Timestamp _) as last_modified_date] ->
+        let page_count = match page_count with
+            `Null -> 0
+          | `Int n -> n in
+        let last_modified_date = match last_modified_date with
+            `Null -> "-"
+          | `Timestamp date -> printable_date date in
+
+        let hostnames =
+          List.filter (fun (i, _) -> i = id) hostnames in
+        let hostnames =
+          List.map (fun (_, hostname) ->
+                      [ "hostname", Template.VarString hostname ])
+            hostnames in
+
+        [ "id", Template.VarString (string_of_int id);
+          "canonical_hostname", Template.VarString canonical_hostname;
+          "page_count", Template.VarString (string_of_int page_count);
+          "last_modified_date", Template.VarString last_modified_date;
+          "hostnames", Template.VarTable hostnames ]
+
+        | _ -> assert false) in
+
+  template#table "hosts" table;
+
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/admin/create_host.ml b/scripts/admin/create_host.ml
new file mode 100644 (file)
index 0000000..62c55af
--- /dev/null
@@ -0,0 +1,86 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: create_host.ml,v 1.1 2004/09/07 10:14:10 rich Exp $
+ *
+ * NB. Because there might not be any hosts existing when this Wiki
+ * is created, this is not a normal Cocanwiki.register_script script.
+ * Instead, we're using the standard mod_caml Registry.
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Merjisforwiki
+
+let split_re = Pcre.regexp "[\\s,;]+"
+
+let run r =
+  let q = new cgi r in
+  let dbh = Cocanwiki._get_dbh r in
+
+  let canonical_hostname = q#param "canonical_hostname" in
+  let hostnames = try q#param "hostnames" with Not_found -> "" in
+  let title = q#param "title" in
+
+  (* Check the title is reasonable. *)
+  let title = trim title in
+  if title = "" then (
+    Cocanwiki_ok.error ~back_button:true ~title:"Bad title"
+      q "You must give a title for this Wiki.";
+  ) else (
+    (* In theory we could verify characters in hostnames.  However
+     * it's probably best to assume the sysadmin knows what they're up to
+     * here.  If this script is allowed to be accessed by untrusted
+     * users, then this has security implications.
+     *)
+    let check_hostname h =
+      let h = trim h in                        (* Trim whitespace. *)
+      let h = String.lowercase h in    (* Lowercase. *)
+      h
+    in
+
+    let canonical_hostname = check_hostname canonical_hostname in
+    let hostnames = Pcre.split ~rex:split_re hostnames in
+    let hostnames = List.map check_hostname hostnames in
+    let hostnames = List.filter ((<>) "") hostnames in
+
+    (* Update the database. *)
+    let sth = dbh#prepare_cached
+               "set constraints \"hosts_hostname_cn\" deferred" in
+    sth#execute [];
+    let sth = dbh#prepare_cached "insert into hosts (canonical_hostname)
+                                  values (?)" in
+    sth#execute [`String canonical_hostname];
+
+    let hostid = sth#serial "hosts_id_seq" in
+
+    let sth = dbh#prepare_cached "insert into hostnames (hostid, name)
+                                  values (?, ?)" in
+    sth#execute [`Int hostid; `String canonical_hostname];
+    List.iter (fun name ->
+                sth#execute [`Int hostid; `String name]) hostnames;
+
+    let sth = dbh#prepare_cached "insert into pages (hostid, url, title,
+                                    description) values (?, 'index', ?, ?)" in
+    sth#execute [`Int hostid; `String title; `String title];
+
+    (* Commit to the database. *)
+    dbh#commit ();
+
+    (* Print confirmation page. *)
+    let buttons = [
+      { StdPages.label = "OK";
+       StdPages.link = "/_bin/admin/host.cmo";
+       StdPages.method_ = None;
+       StdPages.params = [ "hostid", string_of_int hostid ] }
+    ] in
+
+    Cocanwiki_ok.ok ~title:"Wiki created" ~buttons
+      q "A new Wiki was created."
+  )
+
+let () =
+  register_script run
diff --git a/scripts/admin/create_host_form.ml b/scripts/admin/create_host_form.ml
new file mode 100644 (file)
index 0000000..824536f
--- /dev/null
@@ -0,0 +1,24 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: create_host_form.ml,v 1.1 2004/09/07 10:14:10 rich Exp $
+ *
+ * NB. Because there might not be any hosts existing when this Wiki
+ * is created, this is not a normal Cocanwiki.register_script script.
+ * Instead, we're using the standard mod_caml Registry.
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+let template = Cocanwiki.get_template "admin/create_host_form.html"
+
+let run r =
+  let q = new cgi r in
+  (* let dbh = Cocanwiki._get_dbh r in *)
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/admin/edit_emails.ml b/scripts/admin/edit_emails.ml
new file mode 100644 (file)
index 0000000..732dee0
--- /dev/null
@@ -0,0 +1,80 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: edit_emails.ml,v 1.1 2004/09/07 10:14:10 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Merjisforwiki
+
+open Cocanwiki
+open Cocanwiki_ok
+
+let split_re = Pcre.regexp "[\\r\\n,;]+"
+let email_re = Pcre.regexp "(.*)<(.*)>"
+
+let run r (q : cgi) (dbh : Dbi.connection) hostid_hostname _ =
+  let hostid = int_of_string (q#param "hostid") in
+
+  if q#param_true "cancel" then (
+    let _, hostname = hostid_hostname in
+    q#redirect ("http://" ^ hostname ^ "/_bin/admin/host.cmo?hostid=" ^
+               string_of_int hostid);
+    raise CgiExit
+  );
+
+  let emails = try q#param "emails" with Not_found -> "" in
+
+  (* It's very hard to verify email addresses.  Thus this script
+   * should not be exposed to untrusted users.
+   *)
+  let check_email str =
+    let name, email =
+      try
+       let subs = Pcre.exec ~rex:email_re str in
+       Pcre.get_substring subs 1, Pcre.get_substring subs 2
+      with
+         Not_found ->
+           "", str in
+
+    (* Trim whitespace. *)
+    trim name, trim email
+  in
+
+  let emails = Pcre.split ~rex:split_re emails in
+  let emails = List.map check_email emails in
+  let emails = List.filter ((<>) ("","")) emails in
+
+  (* Update the database. *)
+  let sth = dbh#prepare_cached
+             "delete from email_notify where hostid = ?" in
+  sth#execute [`Int hostid];
+  let sth = dbh#prepare_cached "insert into email_notify (hostid, email, name)
+                                values (?, ?, ?)" in
+  List.iter (fun (name, email) ->
+              if name = "" then
+                sth#execute [`Int hostid; `String email; `Null]
+              else
+                sth#execute [`Int hostid; `String email; `String name])
+    emails;
+
+  (* Commit to the database. *)
+  dbh#commit ();
+
+  (* Print confirmation page. *)
+  let buttons = [
+    { StdPages.label = "OK";
+      StdPages.link = "/_bin/admin/host.cmo";
+      StdPages.method_ = None;
+      StdPages.params = [ "hostid", string_of_int hostid ] }
+  ] in
+
+  ok ~title:"Saved" ~buttons
+    q "Email notifications updated."
+
+let () =
+  register_script run
diff --git a/scripts/admin/edit_emails_form.ml b/scripts/admin/edit_emails_form.ml
new file mode 100644 (file)
index 0000000..8c32335
--- /dev/null
@@ -0,0 +1,44 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: edit_emails_form.ml,v 1.1 2004/09/07 10:14:10 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+
+let template = get_template "admin/edit_emails_form.html"
+
+let run r (q : cgi) (dbh : Dbi.connection) _ _ =
+  let hostid = int_of_string (q#param "hostid") in
+
+  template#set "id" (string_of_int hostid);
+
+  let sth = dbh#prepare_cached
+             "select canonical_hostname from hosts where id = ?" in
+  sth#execute [`Int hostid];
+
+  let canonical_hostname = sth#fetch1string () in
+  template#set "canonical_hostname" canonical_hostname;
+
+  let sth = dbh#prepare_cached
+             "select email, name from email_notify where hostid = ?" in
+  sth#execute [`Int hostid];
+
+  let emails = sth#map (function
+                           [`String email; `Null] ->
+                             email
+                         | [`String email; `String name] ->
+                             sprintf "%s <%s>" name email
+                         | _ -> assert false) in
+
+  template#set "emails" (String.concat "\n" emails);
+
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/admin/edit_host_css.ml b/scripts/admin/edit_host_css.ml
new file mode 100644 (file)
index 0000000..04a2ad9
--- /dev/null
@@ -0,0 +1,48 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: edit_host_css.ml,v 1.1 2004/09/07 10:14:10 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Merjisforwiki
+
+open Cocanwiki
+open Cocanwiki_ok
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, _) _ =
+  let hostid = int_of_string (q#param "hostid") in
+
+  let css = q#param "css" in
+
+  let css = if string_is_whitespace css then `Null else `String css in
+
+  (* XXX We should version the global stylesheet.  However this requires
+   * some fairly non-trivial coding.
+   *)
+  let sth = dbh#prepare_cached "update hosts set css = ? where id = ?" in
+  sth#execute [css; `Int hostid];
+
+  dbh#commit ();
+
+  let buttons = [
+    { StdPages.label = "OK";
+      StdPages.link = "/_bin/admin/host.cmo";
+      StdPages.method_ = None;
+      StdPages.params = [ "hostid", string_of_int hostid ] };
+    { StdPages.label = "Edit stylesheet again";
+      StdPages.link = "/_bin/admin/edit_host_css_form.cmo";
+      StdPages.method_ = None;
+      StdPages.params = [ "hostid", string_of_int hostid ] }
+  ] in
+
+  ok ~title:"Stylesheet changed" ~buttons
+    q ("The stylesheet was changed successfully.  " ^
+       "Note: You must RELOAD the page to see changes to stylesheets.")
+
+let () =
+  register_script run
diff --git a/scripts/admin/edit_host_css_form.ml b/scripts/admin/edit_host_css_form.ml
new file mode 100644 (file)
index 0000000..815fe9c
--- /dev/null
@@ -0,0 +1,34 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: edit_host_css_form.ml,v 1.1 2004/09/07 10:14:10 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+
+let template = get_template "admin/edit_host_css_form.html"
+
+let run r (q : cgi) (dbh : Dbi.connection) _ _ =
+  let hostid = int_of_string (q#param "hostid") in
+  template#set "id" (string_of_int hostid);
+
+  let sth = dbh#prepare_cached "select css from hosts where id = ?" in
+  sth#execute [`Int hostid];
+
+  let css =
+    match sth#fetch1 () with
+      | [ `Null ] -> ""
+      | [ `String css ] -> css
+      | _ -> assert false in
+
+  template#set "css" css;
+
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/admin/edit_hostnames.ml b/scripts/admin/edit_hostnames.ml
new file mode 100644 (file)
index 0000000..3a37aa7
--- /dev/null
@@ -0,0 +1,79 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: edit_hostnames.ml,v 1.1 2004/09/07 10:14:10 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Merjisforwiki
+
+open Cocanwiki
+open Cocanwiki_ok
+
+let split_re = Pcre.regexp "[\\s,;]+"
+
+let run r (q : cgi) (dbh : Dbi.connection) hostid_hostname _ =
+  let hostid = int_of_string (q#param "hostid") in
+
+  if q#param_true "cancel" then (
+    let _, hostname = hostid_hostname in
+    q#redirect ("http://" ^ hostname ^ "/_bin/admin/host.cmo?hostid=" ^
+               string_of_int hostid);
+    raise CgiExit
+  );
+
+  let canonical_hostname = q#param "canonical_hostname" in
+  let hostnames = try q#param "hostnames" with Not_found -> "" in
+
+  (* In theory we could verify characters in hostnames, and call
+   * error / raise CgiExit if the format is incorrect.  However
+   * it's probably best to assume the sysadmin knows what they're up to
+   * here.  If this script is allowed to be accessed by untrusted users,
+   * then this has security implications.
+   *)
+  let check_hostname h =
+    let h = trim h in                  (* Trim whitespace. *)
+    let h = String.lowercase h in      (* Lowercase. *)
+    h
+  in
+
+  let canonical_hostname = check_hostname canonical_hostname in
+  let hostnames = Pcre.split ~rex:split_re hostnames in
+  let hostnames = List.map check_hostname hostnames in
+  let hostnames = List.filter ((<>) "") hostnames in
+
+  (* Update the database. *)
+  let sth = dbh#prepare_cached
+             "set constraints \"hosts_hostname_cn\" deferred" in
+  sth#execute [];
+  let sth = dbh#prepare_cached "update hosts set canonical_hostname = ?
+                                 where id = ?" in
+  sth#execute [`String canonical_hostname; `Int hostid];
+  let sth = dbh#prepare_cached "delete from hostnames where hostid = ?" in
+  sth#execute [`Int hostid];
+  let sth = dbh#prepare_cached "insert into hostnames (hostid, name)
+                                values (?, ?)" in
+  sth#execute [`Int hostid; `String canonical_hostname];
+  List.iter (fun name ->
+              sth#execute [`Int hostid; `String name]) hostnames;
+
+  (* Commit to the database. *)
+  dbh#commit ();
+
+  (* Print confirmation page. *)
+  let buttons = [
+    { StdPages.label = "OK";
+      StdPages.link = "/_bin/admin/host.cmo";
+      StdPages.method_ = None;
+      StdPages.params = [ "hostid", string_of_int hostid ] }
+  ] in
+
+  ok ~title:"Saved" ~buttons
+    q "Hostnames updated."
+
+let () =
+  register_script run
diff --git a/scripts/admin/edit_hostnames_form.ml b/scripts/admin/edit_hostnames_form.ml
new file mode 100644 (file)
index 0000000..30dd87d
--- /dev/null
@@ -0,0 +1,40 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: edit_hostnames_form.ml,v 1.1 2004/09/07 10:14:10 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+
+let template = get_template "admin/edit_hostnames_form.html"
+
+let run r (q : cgi) (dbh : Dbi.connection) _ _ =
+  let hostid = int_of_string (q#param "hostid") in
+
+  template#set "id" (string_of_int hostid);
+
+  let sth = dbh#prepare_cached
+             "select canonical_hostname from hosts where id = ?" in
+  sth#execute [`Int hostid];
+
+  let canonical_hostname = sth#fetch1string () in
+  template#set "canonical_hostname" canonical_hostname;
+
+  let sth = dbh#prepare_cached "select name from hostnames
+                                 where hostid = ?
+                                   and name <> ?" in
+  sth#execute [`Int hostid; `String canonical_hostname];
+
+  let hostnames = sth#map (function [`String hostname] -> hostname
+                            | _ -> assert false) in
+  template#set "hostnames" (String.concat "\n" hostnames);
+
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/admin/host.ml b/scripts/admin/host.ml
new file mode 100644 (file)
index 0000000..96d6a04
--- /dev/null
@@ -0,0 +1,98 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: host.ml,v 1.1 2004/09/07 10:14:10 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Merjisforwiki
+
+open Cocanwiki
+
+let template = get_template "admin/host.html"
+
+let run r (q : cgi) (dbh : Dbi.connection) _ _ =
+  let hostid = int_of_string (q#param "hostid") in
+
+  template#set "id" (string_of_int hostid);
+
+  (* Pull out some overall details for this host. *)
+  let sth = dbh#prepare_cached
+             "select h.canonical_hostname, h.css is not null,
+                      (select count(*) from pages
+                        where hostid = h.id and url is not null),
+                      (select count(*) from pages
+                        where hostid = h.id),
+                      (select max(last_modified_date) from pages
+                        where hostid = h.id and url is not null),
+                      (select min(last_modified_date) from pages
+                        where hostid = h.id and url is not null)
+                 from hosts h
+                where h.id = ?" in
+  sth#execute [`Int hostid];
+
+  let canonical_hostname, has_css, page_count, total_count,
+    last_modified_date, creation_date =
+    match sth#fetch1 () with
+       [ `String canonical_hostname;
+         `Bool has_css;
+         (`Null | `Int _) as page_count; (`Null | `Int _) as total_count;
+         (`Null | `Timestamp _) as last_modified_date;
+         (`Null | `Timestamp _) as creation_date ] ->
+         let page_count = match page_count with
+             `Null -> 0
+           | `Int n -> n in
+         let total_count = match total_count with
+             `Null -> 0
+           | `Int n -> n in
+         let last_modified_date = match last_modified_date with
+             `Null -> ""
+           | `Timestamp t -> printable_date t in
+         let creation_date = match creation_date with
+             `Null -> ""
+           | `Timestamp t -> printable_date t in
+         canonical_hostname, has_css, page_count, total_count,
+         last_modified_date, creation_date
+      | _ -> assert false in
+
+  template#set "canonical_hostname" canonical_hostname;
+  template#conditional "has_css" has_css;
+  template#set "page_count" (string_of_int page_count);
+  template#set "total_count" (string_of_int total_count);
+  template#set "last_modified_date" last_modified_date;
+  template#set "creation_date" creation_date;
+
+  (* Pull out any aliases. *)
+  let sth = dbh#prepare_cached "select name from hostnames
+                                 where hostid = ?
+                                   and name <> ?" in
+  sth#execute [`Int hostid; `String canonical_hostname];
+
+  let table = sth#map (function [`String hostname] ->
+                        [ "hostname", Template.VarString hostname ]
+                        | _ -> assert false) in
+  template#table "hostnames" table;
+
+  (* Pull out any email notifications. *)
+  let sth = dbh#prepare_cached "select email, name from email_notify
+                                 where hostid = ?" in
+  sth#execute [`Int hostid];
+
+  let table = sth#map (function
+                          [`String email; `Null] ->
+                            [ "email", Template.VarString email;
+                              "name", Template.VarString "" ]
+                        | [ `String email; `String name] ->
+                            [ "email", Template.VarString email;
+                              "name", Template.VarString name ]
+                        | _ -> assert false) in
+  template#table "emails" table;
+
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/cgi_expires.ml b/scripts/cgi_expires.ml
new file mode 100644 (file)
index 0000000..48d6b8e
--- /dev/null
@@ -0,0 +1,54 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: cgi_expires.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+
+open Printf
+
+open Merjisforwiki
+
+(* This library should eventually be integrated with mod_caml. XXX *)
+
+(* Expires headers.
+ *
+ * All content on wiki sites is dynamic, but we try to make it look static
+ * for users and search engines.  We have 4 types of 'Expires' headers that
+ * we can send:
+ *
+ * expires_past ()
+ *   Send an expiry header in the past (theoretically removing content from
+ *   caches).
+ * expires_short ()
+ *   Send a short expiry header (now + 5 minutes).  This should be used for
+ *   all news pages.
+ * expires_medium ()
+ *   Send a medium-term expiry header (now + 24 hours).  This should be used
+ *   for all "static" content.
+ * expires_long ()
+ *   Send a very long expiry header (now + 2 years).  This should be used for
+ *   content which really never will change.
+ *)
+let expires_past, expires_short, expires_medium, expires_long =
+  let make offset =
+    let t = Unix.time () in
+    let tm = Unix.gmtime (t +. float offset) in
+    sprintf "%s, %02d %s %04d %02d:%02d:%02d GMT"
+      (short_weekday tm.Unix.tm_wday)
+      tm.Unix.tm_mday
+      (short_month (tm.Unix.tm_mon + 1))
+      (tm.Unix.tm_year + 1900)
+      tm.Unix.tm_hour
+      tm.Unix.tm_min
+      tm.Unix.tm_sec
+  in
+  let mins m = m * 60 in
+  let days d = d * 86400 in
+  (fun () -> make (mins (-5))),
+  (fun () -> make (mins 5)),
+  (fun () -> make (days 1)),
+  (fun () -> make (days (365*2)))
diff --git a/scripts/cocanwiki.ml b/scripts/cocanwiki.ml
new file mode 100644 (file)
index 0000000..6852dc4
--- /dev/null
@@ -0,0 +1,89 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: cocanwiki.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Merjisforwiki
+
+module Pool = DbiPool (Dbi_postgres)
+
+(* Wrapper around [Cgi.Template.template] function which loads the
+ * template from a pre-defined path and sets up some default variables.
+ *)
+let get_template =
+  let path =
+    try Sys.getenv "COCANWIKI_TEMPLATES"
+    with Not_found -> "/usr/share/cocanwiki/templates" in
+  let is_dir path =
+    try (Unix.stat path).Unix.st_kind = Unix.S_DIR
+    with Unix.Unix_error _ -> false in
+  if not (is_dir path) then
+    failwith ("environment variable $COCANWIKI_TEMPLATES " ^
+             "must be set to point to my 'templates' directory " ^
+             "(see README file for more details)");
+  fun filename ->
+    Template.template (path // filename)
+
+(* Generate a printable datestamp for pages. *)
+let printable_date (date, _) =
+  sprintf "%d %s %04d" date.Dbi.day (short_month date.Dbi.month) date.Dbi.year
+
+let printable_date_time (date, time) =
+  sprintf "%d %s %04d %02d:%02d" date.Dbi.day (short_month date.Dbi.month)
+    date.Dbi.year time.Dbi.hour time.Dbi.min
+
+(* This function is used to grab a database handle.  It's used in a couple
+ * of very special places, and is not for general consumption.
+ *)
+let _get_dbh r = Pool.get r "cocanwiki"
+
+(* The [CgiExit] exception should be folded back into the base
+ * mod_caml code at some point.  It just causes the 'run' function to
+ * return at that point safely.  (XXX)
+ *)
+exception CgiExit
+
+(* Our wrapper around the standard [register_script] function. *)
+let register_script run =
+  (* Actually register the script with the real [Registry] module. *)
+  register_script
+    (fun r ->
+       let q = new cgi r in
+       let dbh = _get_dbh r in
+
+       (* Get the host ID, by comparing the Host: header with the hostnames
+       * table in the database.
+       *)
+       let hostid, hostname =
+        let hostname = try Request.hostname r
+        with Not_found -> failwith "No ``Host:'' header in request" in
+        let hostname = String.lowercase hostname in
+
+        let sth = dbh#prepare_cached "select h.id, h.canonical_hostname
+                                         from hostnames hn, hosts h
+                                        where hn.name = ?
+                                          and hn.hostid = h.id" in
+        sth#execute [`String hostname];
+
+        try
+          (match sth#fetch1 () with
+               [ `Int id; `String hostname ] -> id, hostname
+             | _ -> assert false)
+        with
+            Not_found ->
+              failwith ("Hostname ``" ^ hostname ^ "'' not found in " ^
+                        "the hosts/hostnames tables in the database.") in
+
+       (* Call the actual CGI script.  Note the fourth (unit) argument
+       * is reserved for later usage (for authentication information).
+       *)
+       try
+        run r q dbh (hostid, hostname) ()
+       with
+          CgiExit -> ())
diff --git a/scripts/cocanwiki_diff.ml b/scripts/cocanwiki_diff.ml
new file mode 100644 (file)
index 0000000..0da7f42
--- /dev/null
@@ -0,0 +1,82 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: cocanwiki_diff.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Merjisforwiki
+
+(* Convenience code for generating diffs between versions.  See diff.ml
+ * and edit.ml which both use this code.
+ *)
+let page_for_diff css sections =
+  (String.concat ""
+     (List.map (fun (sectionname, content) ->
+                 "HEADING: " ^ sectionname ^ "\n\n" ^
+                 content ^ "\n\n") sections)) ^
+  "CSS:\n\n" ^ css
+
+let diff_cmd old_page new_page =
+  let new_filename = output_tempfile new_page in
+  let old_filename = output_tempfile old_page in
+
+  let cmd = sprintf "diff -u %s %s ||:" old_filename new_filename in
+  let diff = pget cmd in
+
+  let diff =
+    match diff with
+       _ :: _ :: diff -> diff
+      | diff -> diff in
+
+  unlink new_filename; unlink old_filename;
+  String.concat "\n" diff
+
+let get_version_for_diff (dbh : Dbi.connection) version =
+  if version = 0 then "" else (
+    let sth = dbh#prepare_cached "select coalesce (css, '') as css
+                                    from pages where id = ?" in
+    sth#execute [`Int version];
+
+    let css = sth#fetch1string () in
+
+    let sth = dbh#prepare_cached "select sectionname, content
+                                    from contents where pageid = ?
+                                   order by ordering" in
+    sth#execute [`Int version];
+
+    let sections =
+      sth#map (function
+                  [`String sectionname; `String content] ->
+                    sectionname, content
+                | _ -> assert false) in
+    let page = page_for_diff css sections in
+
+    page
+  )
+
+let get_diff (dbh : Dbi.connection) hostid page ?old_version ~version () =
+  let old_version =
+    match old_version with
+      | Some version -> version
+      | None ->
+         let sth = dbh#prepare_cached "select id from pages
+                                         where hostid = ?
+                                           and url_deleted = ? and id < ?
+                                         order by 1 desc limit 1" in
+         sth#execute [`Int hostid; `String page; `Int version];
+
+         try sth#fetch1int ()
+         with Not_found -> 0 in
+
+  (* Get the two versions. *)
+  let new_page = get_version_for_diff dbh version in
+  let old_page = get_version_for_diff dbh old_version in
+
+  (* Compute the diff of the two versions. *)
+  let diff = diff_cmd old_page new_page in
+  diff, old_version
diff --git a/scripts/cocanwiki_emailnotify.ml b/scripts/cocanwiki_emailnotify.ml
new file mode 100644 (file)
index 0000000..79ca5e7
--- /dev/null
@@ -0,0 +1,41 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: cocanwiki_emailnotify.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Merjisforwiki
+
+(* This is where we coordinate email notification from various
+ * scripts which create or update the wiki.
+ *)
+let email_notify ~subject ~body (dbh : Dbi.connection) hostid =
+  (* Is anyone listed for email notification at this host? *)
+  let sth = dbh#prepare_cached "select email, name from email_notify
+                                where hostid = ?" in
+  sth#execute [`Int hostid];
+
+  let to_addr = sth#map (function
+                          | [`String email; `String name] ->
+                              name ^ " <" ^ email ^ ">"
+                          | [`String email; `Null] ->
+                              email
+                          | _ -> assert false) in
+
+  if to_addr <> [] then (
+    (* Prepare the body of the message.  The assumption is that
+     * this takes time and database access, so we defer the creation
+     * of the body until we know that someone needs to be notified.
+     *)
+    let body = body () in
+
+    let subject = "Wiki notice: " ^ subject in
+
+    (* Send the email. *)
+    Sendmail.send_mail ~subject ~to_addr ~body ()
+  )
diff --git a/scripts/cocanwiki_ok.ml b/scripts/cocanwiki_ok.ml
new file mode 100644 (file)
index 0000000..02b6f2f
--- /dev/null
@@ -0,0 +1,34 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: cocanwiki_ok.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Merjisforwiki
+
+open Cocanwiki
+
+(* Override StdPages.ok and StdPages.error with out our versions.
+ * Also have some standard buttons around.
+ *)
+let ok_error_template = get_template "ok_error.html"
+
+let error ?cookie ?cookies ?title ?(icon = "/_graphics/error.png")
+    ?icon_alt ?back_button ?close_button q message =
+  StdPages.error ?cookie ?cookies ~template:ok_error_template
+    ?title ~icon ?icon_alt ?back_button ?close_button q message
+
+let ok ?cookie ?cookies ?title ?(icon = "/_graphics/ok.png")
+    ?icon_alt ?back_button ?close_button ?buttons q message =
+  StdPages.ok ?cookie ?cookies ~template:ok_error_template
+    ?title ~icon ?icon_alt ?back_button ?close_button ?buttons q message
+
+let ok_button url = { StdPages.label = "     OK     ";
+                      StdPages.link = url;
+                      StdPages.method_ = None;
+                      StdPages.params = [] }
diff --git a/scripts/cocanwiki_version.ml.in b/scripts/cocanwiki_version.ml.in
new file mode 100644 (file)
index 0000000..c0197ea
--- /dev/null
@@ -0,0 +1,4 @@
+#define xstr(s) str(s)
+#define str(s) #s
+let package = xstr(PACKAGE)
+let version = xstr(VERSION)
diff --git a/scripts/create.ml b/scripts/create.ml
new file mode 100644 (file)
index 0000000..420d1ef
--- /dev/null
@@ -0,0 +1,78 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: create.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open ExtString
+
+open Cocanwiki
+open Cocanwiki_emailnotify
+open Cocanwiki_ok
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, hostname) _ =
+  (* Get the page title. *)
+  let title = q#param "title" in
+
+  let url =
+    match Wikilib.generate_url_of_title dbh hostid title with
+       Wikilib.GenURL_OK url -> url
+      | Wikilib.GenURL_TooShort | Wikilib.GenURL_BadURL ->
+         error ~back_button:true ~title:"Bad page name"
+           q "The page name supplied is too short or invalid.";
+         raise CgiExit
+      | Wikilib.GenURL_Duplicate url ->
+         q#redirect ("http://" ^ hostname ^ "/" ^ url);
+         raise CgiExit in
+
+  (* Description field must contain something. *)
+  let description = q#param "description" in
+  if description = "" then (
+    error ~back_button:true ~title:"Description field missing"
+      q "You must write a brief description for search engines and
+         directories.";
+    raise CgiExit
+  );
+
+  (* Get the IP address of the user, if available. *)
+  let logged_ip =
+    try `String (Connection.remote_ip (Request.connection r))
+    with Not_found -> `Null in
+
+  (* Create the page. *)
+  let sth = dbh#prepare_cached "insert into pages (hostid, url, title,
+                                  description, logged_ip)
+                                values (?, ?, ?, ?, ?)" in
+  sth#execute [`Int hostid; `String url; `String title; `String description;
+              logged_ip];
+
+  let pageid = sth#serial "pages_id_seq" in
+
+  (* Create a single section. *)
+  let sectionname = "Section title - change this" in
+  let content = "Write some content here." in
+
+  let sth = dbh#prepare_cached "insert into contents (pageid, ordering,
+                                  sectionname, content) values (?, 1, ?, ?)" in
+  sth#execute [`Int pageid; `String sectionname; `String content];
+
+  (* Commit. *)
+  dbh#commit ();
+
+  (* Email notification, if anyone is listed for this host. *)
+  let subject = "Page " ^ url ^ " has been created" in
+  let body = fun () ->
+    "Page: http://" ^ hostname ^ "/" ^ url ^ "\n" in
+
+  email_notify ~subject ~body dbh hostid;
+
+  (* Redirect to the editing page. *)
+  q#redirect ("http://" ^ hostname ^ "/" ^ url ^ "/edit")
+
+let () =
+  register_script run
diff --git a/scripts/create_form.ml b/scripts/create_form.ml
new file mode 100644 (file)
index 0000000..6336021
--- /dev/null
@@ -0,0 +1,40 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: create_form.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open ExtString
+
+open Cocanwiki
+open Cocanwiki_ok
+
+let template = get_template "create_form.html"
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, hostname) _ =
+  (* Get the page title. *)
+  let title = q#param "title" in
+
+  let url =
+    match Wikilib.generate_url_of_title dbh hostid title with
+       Wikilib.GenURL_OK url -> url
+      | Wikilib.GenURL_TooShort | Wikilib.GenURL_BadURL ->
+         error ~back_button:true ~title:"Bad page name"
+           q "The page name supplied is too short or invalid.";
+         raise CgiExit
+      | Wikilib.GenURL_Duplicate url ->
+         q#redirect ("http://" ^ hostname ^ "/" ^ url);
+         raise CgiExit in
+
+  (* Show the form. *)
+  template#set "title" title;
+
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/delete_file.ml b/scripts/delete_file.ml
new file mode 100644 (file)
index 0000000..e2b5e2f
--- /dev/null
@@ -0,0 +1,46 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: delete_file.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+open Cocanwiki_ok
+open Cocanwiki_emailnotify
+
+open Merjisforwiki
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, hostname) _ =
+  let id = int_of_string (q#param "id") in
+
+  if q#param_true "yes" then (
+    (* Delete the file. *)
+    let sth = dbh#prepare_cached "update files
+                                     set name_deleted = name, name = null
+                                   where hostid = ? and id = ?
+                                     and name is not null" in
+    sth#execute [`Int hostid; `Int id];
+
+    dbh#commit ();
+
+    (* Email notify. *)
+    let subject = "File #" ^ string_of_int id ^ " has been deleted." in
+    let body = fun () ->
+      "Page: http://" ^ hostname ^ "/_files?deleted=1" in
+
+    email_notify ~body ~subject dbh hostid;
+
+    (* Done. *)
+    let buttons = [ ok_button "/_files" ] in
+    ok ~title:"File deleted" ~buttons
+      q "File was deleted successfully."
+  ) else
+    q#redirect ("http://" ^ hostname ^ "/_files")
+
+let () =
+  register_script run
diff --git a/scripts/delete_file_form.ml b/scripts/delete_file_form.ml
new file mode 100644 (file)
index 0000000..1b71ec2
--- /dev/null
@@ -0,0 +1,33 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: delete_file_form.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+
+open Merjisforwiki
+
+let template = get_template "delete_file_form.html"
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, _) _ =
+  let id = int_of_string (q#param "id") in
+
+  let sth = dbh#prepare_cached "select name from files
+                                 where hostid = ? and id = ?" in
+  sth#execute [`Int hostid; `Int id];
+
+  let name = sth#fetch1string () in
+
+  template#set "id" (string_of_int id);
+  template#set "name" name;
+
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/delete_image.ml b/scripts/delete_image.ml
new file mode 100644 (file)
index 0000000..63b03d1
--- /dev/null
@@ -0,0 +1,45 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: delete_image.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+open Cocanwiki_ok
+open Cocanwiki_emailnotify
+
+open Merjisforwiki
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, hostname) _ =
+  let id = int_of_string (q#param "id") in
+
+  if q#param_true "yes" then (
+    (* Delete the image. *)
+    let sth = dbh#prepare_cached "update images
+                                     set name_deleted = name, name = null
+                                   where hostid = ? and id = ?
+                                     and name is not null" in
+    sth#execute [`Int hostid; `Int id];
+
+    dbh#commit ();
+
+    (* Email notify. *)
+    let subject = "Image #" ^ string_of_int id ^ " has been deleted." in
+    let body = fun () ->
+      "Page: http://" ^ hostname ^ "/_images?deleted=1" in
+
+    email_notify ~body ~subject dbh hostid;
+    (* Done. *)
+    let buttons = [ ok_button "/_images" ] in
+    ok ~title:"Image deleted" ~buttons
+      q "Image was deleted successfully."
+  ) else
+    q#redirect ("http://" ^ hostname ^ "/_images")
+
+let () =
+  register_script run
diff --git a/scripts/delete_image_form.ml b/scripts/delete_image_form.ml
new file mode 100644 (file)
index 0000000..6d17059
--- /dev/null
@@ -0,0 +1,41 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: delete_image_form.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+
+open Merjisforwiki
+
+let template = get_template "delete_image_form.html"
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, _) _ =
+  let id = int_of_string (q#param "id") in
+
+  let sth = dbh#prepare_cached "select name, width, height, alt
+                                  from images
+                                 where hostid = ? and id = ?" in
+  sth#execute [`Int hostid; `Int id];
+
+  let name, width, height, alt =
+    match sth#fetch1 () with
+       [ `String name; `Int width; `Int height; `String alt] ->
+         name, width, height, alt
+      | _ -> assert false in
+
+  template#set "id" (string_of_int id);
+  template#set "name" name;
+  template#set "width" (string_of_int width);
+  template#set "height" (string_of_int height);
+  template#set "alt" alt;
+
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/diff.ml b/scripts/diff.ml
new file mode 100644 (file)
index 0000000..1d0ad76
--- /dev/null
@@ -0,0 +1,51 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: diff.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+open Cocanwiki_diff
+
+open Merjisforwiki
+
+let template = get_template "diff.html"
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, _) _ =
+  let page = q#param "page" in
+  let page = if page = "" then "index" else page in
+
+  (* "version" parameter, required, is the newest version.  If the
+   * "old_version" parameter is given then we compare against that.
+   * Otherwise we look in the database to find the version of this
+   * page previous to "version", and use that as "old_version".  If
+   * there is no older version in the database, then we compare against
+   * version '0' which means a blank page.
+   *
+   * NB. In case you hadn't worked it out yet, version numbers are
+   * page.ids.
+   *)
+  let version = int_of_string (q#param "version") in
+
+  let diff, old_version =
+    try
+      let old_version = int_of_string (q#param "old_version") in
+      get_diff dbh hostid page ~old_version ~version ()
+    with
+       Not_found ->
+         get_diff dbh hostid page ~version () in
+
+  template#set "version" (string_of_int version);
+  template#set "old_version" (string_of_int old_version);
+  template#set "page" page;
+  template#set "diff" diff;
+
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/edit.ml b/scripts/edit.ml
new file mode 100644 (file)
index 0000000..d86815c
--- /dev/null
@@ -0,0 +1,482 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: edit.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open ExtString
+
+open Merjisforwiki
+
+open Cocanwiki
+open Cocanwiki_ok
+open Cocanwiki_emailnotify
+open Cocanwiki_diff
+
+let template = get_template "edit.html"
+let template_conflict = get_template "edit_conflict.html"
+
+(* We keep an "internal model" of the page - see build_internal_model ()
+ * below.
+ *)
+type model_t = {
+  id : int;                            (* Original page ID. *)
+  description : string;                        (* Description. *)
+  redirect : string;                   (* Redirect to ("" = none). *)
+  contents : (string * string * string) list;
+                                       (* (sectionname, divname, content)
+                                        * for each section. *)
+}
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, hostname) _ =
+  (* Workaround bugs in IE, specifically lack of support for <button>
+   * elements.
+   *)
+  let msie =
+    try
+      let ua = Table.get (Request.headers_in r) "User-Agent" in
+      ignore (String.find ua "MSIE"); (* Throws Invalid_string if not found. *)
+      true
+    with
+       Not_found | String.Invalid_string -> false in
+  template#conditional "msie" msie;
+
+  (* Build the internal model from the parameters passed to the script. *)
+  let build_internal_model () =
+    let id = int_of_string (q#param "id") in
+    let description = q#param "description" in
+    let redirect = q#param "redirect" in
+
+    let contents = ref [] in
+    let i = ref 1 in
+    while q#param_exists ("content_" ^ string_of_int !i) do
+      let sectionname = q#param ("sectionname_" ^ string_of_int !i) in
+      let content = q#param ("content_" ^ string_of_int !i) in
+      let divname = q#param ("divname_" ^ string_of_int !i) in
+      contents := (sectionname, divname, content) :: !contents;
+      incr i
+    done;
+    let contents = List.rev !contents in
+
+    { id = id;
+      description = description;
+      redirect = redirect;
+      contents = contents; }
+  in
+
+  (* Check for errors in the model. *)
+  let check_for_errors model =
+    let errors = ref [] in
+    let add_error msg = errors := msg :: !errors in
+    let get_errors () = List.rev !errors in
+
+    if model.redirect = "" then (
+      (* Empty page? *)
+      if model.contents = [] then
+       add_error ("This page is empty.  Use 'Insert new section here' " ^
+                  "to write something!");
+
+      (* Description field? *)
+      if model.description = "" then
+       add_error ("The description field is very important!  This field is " ^
+                   "used by search engines and directories to describe " ^
+                   "what's on this page.");
+    )
+    else (* it's a redirect *) (
+      (* Redirect points to a real page? *)
+      let sth = dbh#prepare_cached "select 1 from pages
+                                     where hostid = ?
+                                       and url is not null
+                                       and url = ?
+                                       and id <> ?
+                                       and redirect is null" in
+      sth#execute [`Int hostid; `String model.redirect; `Int model.id];
+
+      let ok = try sth#fetch1 () = [`Int 1] with Not_found -> false in
+      if not ok then
+       add_error ("Redirect must point to an ordinary page " ^
+                  "(ie. not to a page which is itself a redirect).")
+    );
+
+    (* All sections have sectionnames? *)
+    List.iter (function (sectionnames, _, _)
+                  when string_is_whitespace sectionnames ->
+                    add_error ("Every section must have a title.");
+                | _ -> ())
+      model.contents;
+
+    get_errors ()
+  in
+
+  (* Various "actions" that can be performed on the model. *)
+  let action_insert model posn item =
+    (* posn = 0 means insert before the first element of the current list. *)
+    let rec loop =
+      function
+          0, xs -> item :: xs
+        | _, [] -> [ item ]
+        | n, x :: xs -> x :: (loop (n-1, xs))
+    in
+    let contents = loop (posn, model.contents) in
+    { model with contents = contents }
+  in
+  let action_moveup model posn =
+    (* posn = 1 means move up the first element, ie. do nothing
+     * posn = 2 means move up the second element to the first position
+     * etc.
+     *)
+    let rec loop =
+      function
+          0, xs
+        | 1, xs -> xs
+        | _, [] -> []
+        | 2, x :: y :: xs -> y :: x :: xs
+        | n, x :: xs -> x :: (loop (n-1, xs))
+    in
+    let contents = loop (posn, model.contents) in
+    { model with contents = contents }
+  in
+  let action_movedn model posn =
+    (* posn = 1 means move down the first element to the second position
+     * etc.
+     *)
+    let rec loop =
+      function
+          0, xs -> xs
+        | _, [] -> []
+        | 1, x :: y :: xs -> y :: x :: xs
+        | n, x :: xs -> x :: (loop (n-1, xs))
+    in
+    let contents = loop (posn, model.contents) in
+    { model with contents = contents }
+  in
+  let action_delete model posn =
+    (* posn = 1 means delete the first element *)
+    let rec loop =
+      function
+          0, xs -> xs
+        | _, [] -> []
+        | 1, x :: xs -> xs
+        | n, x :: xs -> x :: (loop (n-1, xs))
+    in
+    let contents = loop (posn, model.contents) in
+    { model with contents = contents }
+  in
+
+  (* Convert model to template. *)
+  let model_to_template model template =
+    template#set "id" (string_of_int model.id);
+    template#set "description" model.description;
+
+    (* Redirects table. *)
+    let sth = dbh#prepare_cached "select url, title from pages
+                                   where url is not null
+                                     and redirect is null
+                                     and hostid = ? and id <> ?
+                                   order by 2" in
+    sth#execute [`Int hostid; `Int model.id];
+    let table = sth#map (function [`String url; `String title] ->
+                          let selected = model.redirect = url in
+                          [ "url", Template.VarString url;
+                            "title", Template.VarString title;
+                            "selected", Template.VarConditional selected ]
+                          | _ -> assert false) in
+    template#table "redirects" table;
+
+    (* Need to go to the database to get the title of the page ... *)
+    let sth = dbh#prepare_cached "select title from pages
+                                   where hostid = ? and id = ?" in
+    sth#execute [`Int hostid; `Int model.id];
+    let title = sth#fetch1string () in
+    template#set "title" title;
+
+    let ordering = ref 0 in
+    let table =
+      List.map
+       (fun (sectionname, divname, content) ->
+          incr ordering; let ordering = !ordering in
+          [ "ordering", Template.VarString (string_of_int ordering);
+            "sectionname", Template.VarString sectionname;
+            "divname", Template.VarString divname;
+            "content", Template.VarString content ]) model.contents in
+    template#table "contents" table;
+
+    (* Check for errors and put those into the template. *)
+    let errors = check_for_errors model in
+    let errors = List.map (fun msg ->
+                            [ "error", Template.VarString msg ]) errors in
+    template#table "errors" errors;
+    template#conditional "has_errors" (errors <> [])
+  in
+
+  (* Begin editing a page, pulling the page out of the database and building
+   * a model from it.
+   *)
+  let begin_editing page =
+    (* Pull out the page itself from the database. *)
+    let sth = dbh#prepare_cached "select id, title, description,
+                                         coalesce (redirect, '')
+                                    from pages
+                                   where hostid = ? and url = ?" in
+    sth#execute [`Int hostid; `String page];
+
+    let pageid, title, description, redirect =
+      match sth#fetch1 () with
+         [`Int pageid; `String title; `String description; `String redirect]->
+           pageid, title, description, redirect
+       | _ -> assert false in
+
+    (* Get the sections. *)
+    let sth = dbh#prepare_cached "select sectionname, content,
+                                         coalesce (divname, '')
+                                    from contents
+                                   where pageid = ?
+                                   order by ordering" in
+    sth#execute [`Int pageid];
+
+    let contents =
+      sth#map (function
+                | [`String sectionname; `String content; `String divname] ->
+                    sectionname, divname, content
+                | _ -> assert false) in
+
+    let model = { id = pageid;
+                 description = description;
+                 redirect = redirect;
+                 contents = contents; } in
+
+    model_to_template model template
+  in
+
+  let continue_editing () =
+    let model = ref (build_internal_model ()) in
+
+    (* An "action" parameter? *)
+    let is_action, get_action =
+      let actions = q#params in
+      (* Don't actually care about the value fields ... *)
+      let actions = List.map (fun (str, _) -> str) actions in
+      (* Some of our actions are imagemaps, so parameters like name.x, name.y
+       * need to be changed to name and have resulting duplicates removed.
+       *)
+      let actions =
+       List.filter (fun str ->
+                      String.length str > 7 &&
+                      String.sub str 0 7 = "action_" &&
+                      not (String.ends_with str ".y")) actions in
+      let actions =
+       List.map (fun str ->
+                   if String.ends_with str ".x" then (
+                     let str = String.sub str 0 (String.length str - 2) in
+                     str
+                   )
+                   else str) actions in
+      let actions =
+       List.map (fun str ->
+                   let action_type = String.sub str 7 6 in
+                   let action_value =
+                     String.sub str 14 (String.length str - 14) in
+                   let action_value = int_of_string action_value in
+                   action_type, action_value) actions in
+
+      let is_action typ = List.mem_assoc typ actions in
+      let get_value typ = List.assoc typ actions in
+
+      is_action, get_value
+    in
+
+    if is_action "insert" then (
+      let posn = get_action "insert" in
+      let item = "New section - change this", "", "Write some content here." in
+      model := action_insert !model posn item
+    ) else if is_action "moveup" then (
+      let posn = get_action "moveup" in
+      model := action_moveup !model posn
+    ) else if is_action "movedn" then (
+      let posn = get_action "movedn" in
+      model := action_movedn !model posn
+    ) else if is_action "delete" then (
+      let posn = get_action "delete" in
+      model := action_delete !model posn
+    );
+
+    model_to_template !model template
+  in
+
+  (* Try to save the page.  Returns a boolean indicating if the
+   * page was saved successfully.
+   *)
+  let try_save () =
+    let model = build_internal_model () in
+    let no_errors = [] = check_for_errors model in
+    if no_errors then (
+      (* No errors, so we can save the page ... *)
+
+      (* Pull out fields from the database. *)
+      let sth = dbh#prepare_cached "select creation_date,
+                                           coalesce (url, url_deleted),
+                                           title, css
+                                      from pages
+                                     where hostid = ? and id = ?" in
+      sth#execute [`Int hostid; `Int model.id];
+
+      let creation_date, url, title, css =
+       match sth#fetch1 () with
+           [ creation_date; `String url; `String title; css ] ->
+             creation_date, url, title, css
+         | _ -> assert false in
+
+      (* Has someone else edited this page in the meantime? *)
+      let sth = dbh#prepare_cached "select max(id) from pages
+                                     where hostid = ? and url = ?" in
+      sth#execute [`Int hostid; `String url];
+
+      let max_id = sth#fetch1int () in
+      let edited = max_id <> model.id in
+
+      if edited then (
+       (* Edited by someone else ...  Get the other's changes. *)
+       let other_diff, _ =
+         get_diff dbh hostid url ~old_version:model.id ~version:max_id () in
+
+       (* Synthesize our own changes. *)
+       let old_page = get_version_for_diff dbh model.id in
+       let new_page =
+         let css = match css with
+             `Null -> "" | `String css -> css
+           | _ -> assert false in
+         page_for_diff css (List.map (fun (sectionname, _, content) ->
+                              sectionname, content) model.contents) in
+       let our_diff = diff_cmd old_page new_page in
+
+       (* Fill out the conflict template. *)
+       template_conflict#set "other_diff" other_diff;
+       template_conflict#set "our_diff" our_diff;
+       template_conflict#set "old_version" (string_of_int model.id);
+       template_conflict#set "new_version" (string_of_int max_id);
+       template_conflict#set "url" url;
+
+       q#template template_conflict;
+       raise CgiExit
+      );
+
+      (* Defer the pages_redirect_cn constraint because that would
+       * temporarily fail on the next UPDATE.
+       *)
+      let sth =
+       dbh#prepare_cached "set constraints pages_redirect_cn deferred" in
+      sth#execute [];
+
+      (* Mark the old page as deleted.  NB. There is a small race
+       * condition here because PostgreSQL doesn't do isolation
+       * properly.  If a user tries to visit this page between the
+       * delete and the creation of the new page, then they'll get
+       * a page not found error. (XXX)
+       *)
+      let sth = dbh#prepare_cached "update pages set url_deleted = url,
+                                                     url = null
+                                     where hostid = ? and id = ?" in
+      sth#execute [`Int hostid; `Int model.id];
+
+      (* Get the IP address of the user, if available. *)
+      let logged_ip =
+       try `String (Connection.remote_ip (Request.connection r))
+       with Not_found -> `Null in
+
+      (* Get redirect. *)
+      let redirect = if model.redirect = "" then `Null
+                     else `String model.redirect in
+
+      (* Create the new page. *)
+      let sth = dbh#prepare_cached "insert into pages (hostid, url, title,
+                                      description, creation_date, logged_ip,
+                                      redirect, css)
+                                    values (?, ?, ?, ?, ?, ?, ?, ?)" in
+      sth#execute [`Int hostid; `String url; `String title;
+                  `String model.description; creation_date; logged_ip;
+                  redirect; css];
+
+      (* New page ID <> old page ID model.id. *)
+      let pageid = sth#serial "pages_id_seq" in
+
+      (* Create the page contents. *)
+      let sth = dbh#prepare_cached "insert into contents (pageid,
+                                      ordering, sectionname, divname, content)
+                                    values (?, ?, ?, ?, ?)" in
+      let ordering = ref 0 in          (* Creating new ordering. *)
+      List.iter (fun (sectionname, divname, content) ->
+                  let divname =
+                    if string_is_whitespace divname then `Null
+                    else `String divname in
+                  incr ordering; let ordering = !ordering in
+                  sth#execute [`Int pageid; `Int ordering;
+                               `String sectionname; divname;
+                               `String content])
+       model.contents;
+
+      (* Commit changes to the database. *)
+      dbh#commit ();
+
+      (* Email notification, if anyone is listed for this host. *)
+      let subject = "Page " ^ url ^ " has been edited" in
+
+      let body = fun () ->
+       (* Prepare the diff between this version and the previous version. *)
+       let diff, _ = get_diff dbh hostid url ~version:pageid () in
+       "Page: http://" ^ hostname ^ "/" ^ url ^ "\n\n" ^
+       diff in
+
+      email_notify ~body ~subject dbh hostid;
+
+      let buttons = [ ok_button ("/" ^ url) ] in
+      ok ~title:"Saved" ~buttons
+        q "The page was saved."
+    );
+
+    no_errors
+  in
+
+  let cancel id =
+    let sth = dbh#prepare_cached "select url from pages
+                                   where hostid = ? and id = ?" in
+    sth#execute [`Int hostid; `Int id];
+    let url = sth#fetch1string () in
+
+    q#redirect ("http://" ^ hostname ^ "/" ^ url)
+  in
+
+  (* This codes decides where we are in the current editing cycle.
+   *
+   * Inputs:
+   *   id - if set, then we are in the midst of editing a page.
+   *   save - if set, then we want to save the page.
+   *   cancel - if set, abandon changes and go back to viewing the page.
+   *   action_* - one of the action buttons was set, eg. move up/down.
+   *   page - the page URL opened newly for editing.
+   *)
+  (try
+     let id = int_of_string (q#param "id") in
+     if q#param_true "cancel" then (
+       cancel id;
+       raise CgiExit
+     );
+     if q#param_true "save" then (
+       let ok = try_save () in
+       if ok then raise CgiExit                (* ... else fall through *)
+     );
+     continue_editing ()               (* Processes the action, if any. *)
+   with
+       Not_found ->
+        let page = q#param "page" in
+        let page = if page = "" then "index" else page in
+        begin_editing page);
+
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/edit_page_css.ml b/scripts/edit_page_css.ml
new file mode 100644 (file)
index 0000000..d348ce8
--- /dev/null
@@ -0,0 +1,92 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: edit_page_css.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Merjisforwiki
+
+open Cocanwiki
+open Cocanwiki_ok
+open Cocanwiki_diff
+open Cocanwiki_emailnotify
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, hostname) _ =
+  let page = q#param "page" in
+  let css = q#param "css" in
+
+  let css = if string_is_whitespace css then `Null else `String css in
+
+  (* Get the IP address of the user, if available. *)
+  let logged_ip =
+    try `String (Connection.remote_ip (Request.connection r))
+    with Not_found -> `Null in
+
+  (* Changing the CSS creates a new version of the page.  This enables
+   * us to revert changes to the CSS easily.
+   *)
+  let sth = dbh#prepare_cached "select id, title, description, creation_date,
+                                       redirect
+                                  from pages
+                                 where hostid = ? and url = ?" in
+  sth#execute [`Int hostid; `String page];
+
+  let oldpageid, title, description, creation_date, redirect =
+    match sth#fetch1 () with
+       [ `Int id; title; description; creation_date; redirect ] ->
+         id, title, description, creation_date, redirect
+      | _ -> assert false in
+
+  let sth = dbh#prepare_cached "set constraints pages_redirect_cn deferred" in
+  sth#execute [];
+
+  let sth = dbh#prepare_cached "update pages set url_deleted = url,
+                                                 url = null
+                                 where hostid = ? and id = ?" in
+  sth#execute [`Int hostid; `Int oldpageid];
+
+  let sth = dbh#prepare_cached "insert into pages (hostid, url, title,
+                                   description, creation_date, logged_ip,
+                                   redirect, css)
+                                values (?, ?, ?, ?, ?, ?, ?, ?)" in
+  sth#execute [`Int hostid; `String page; title; description;
+              creation_date; logged_ip; redirect; css ];
+
+  let pageid = sth#serial "pages_id_seq" in
+
+  let sth = dbh#prepare_cached "insert into contents (pageid, ordering,
+                                       sectionname, content, divname)
+                                select ? as pageid, ordering, sectionname,
+                                            content, divname
+                                  from contents
+                                 where pageid = ?" in
+  sth#execute [`Int pageid; `Int oldpageid];
+
+  dbh#commit ();
+
+  (* Email notification. *)
+  let subject = "CSS for page " ^ page ^ " has been modified" in
+  let body = fun () ->
+    let diff, _ =
+      get_diff dbh hostid page ~version:pageid ~old_version:oldpageid () in
+    "Page: http://" ^ hostname ^ "/" ^ page ^ "\n\n" ^
+    diff in
+
+  email_notify ~subject ~body dbh hostid;
+
+  let buttons = [ ok_button ("/" ^ page);
+                 { StdPages.label = "Edit stylesheet again";
+                   StdPages.link = "/_bin/edit_page_css_form.cmo";
+                   StdPages.method_ = None;
+                   StdPages.params = [ "page", page ] } ] in
+  ok ~title:"Stylesheet changed" ~buttons
+    q ("The stylesheet was changed successfully.  " ^
+       "Note: You must RELOAD the page to see changes to stylesheets.")
+
+let () =
+  register_script run
diff --git a/scripts/edit_page_css_form.ml b/scripts/edit_page_css_form.ml
new file mode 100644 (file)
index 0000000..91d226e
--- /dev/null
@@ -0,0 +1,35 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: edit_page_css_form.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+
+let template = get_template "edit_page_css_form.html"
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, _) _ =
+  let page = q#param "page" in
+
+  let sth = dbh#prepare_cached "select css from pages
+                                 where hostid = ? and url = ?" in
+  sth#execute [`Int hostid; `String page];
+
+  let css =
+    match sth#fetch1 () with
+      | [ `Null ] -> ""
+      | [ `String css ] -> css
+      | _ -> assert false in
+
+  template#set "page" page;
+  template#set "css" css;
+
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/file.ml b/scripts/file.ml
new file mode 100644 (file)
index 0000000..6a5deb1
--- /dev/null
@@ -0,0 +1,48 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: file.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Merjisforwiki
+
+open Cocanwiki
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, _) _ =
+  let name = q#param "name" in
+  let version =
+    try Some (int_of_string (q#param "version")) with Not_found -> None in
+
+  (* Get the file and its MIME type. *)
+  let where, args =
+    match version with
+       None -> "hostid = ? and name = ?", [`Int hostid; `String name]
+      | Some version ->
+         "hostid = ? and (name = ? or name_deleted = ?) and id = ?",
+         [`Int hostid; `String name; `String name; `Int version] in
+
+  let sth =
+    dbh#prepare_cached ("select content, mime_type from files
+                          where " ^ where) in
+  sth#execute args;
+
+  let data, mime_type =
+    match sth#fetch1 () with
+       [ `Binary data; `String mime_type ] ->
+         data, mime_type
+      | _ -> assert false in
+
+  if version <> None then
+    (* Set a medium-length expiry time on this resource. *)
+    Table.set (Request.headers_out r) "Expires" (Cgi_expires.expires_medium());
+
+  q#header ~content_type:mime_type ();
+  print_string r data
+
+let () =
+  register_script run
diff --git a/scripts/files.ml b/scripts/files.ml
new file mode 100644 (file)
index 0000000..f9b7dee
--- /dev/null
@@ -0,0 +1,52 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: files.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+
+open Merjisforwiki
+
+let template = get_template "files.html"
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, _) _ =
+  let deleted = q#param_true "deleted" in
+  template#conditional "deleted" deleted;
+
+  let sql =
+    "select id, name, name_deleted, octet_length (content)
+       from files
+      where hostid = ? and " ^
+    (if not deleted then "name is not null"
+     else "name_deleted is not null") ^
+    " order by 2, 3" in
+  let sth = dbh#prepare_cached sql in
+  sth#execute [`Int hostid];
+
+  let table =
+    sth#map
+      (fun row ->
+        let id, name, size, is_deleted =
+          match row with
+            | [`Int id; `String name; `Null; `Int size] ->
+                id, name, size, false
+            | [`Int id; `Null; `String name; `Int size] ->
+                id, name, size, true
+            | _ -> assert false in
+        [ "id", Template.VarString (string_of_int id);
+          "name", Template.VarString name;
+          "ksize", Template.VarString (string_of_int (size / 1024));
+          "is_deleted", Template.VarConditional is_deleted ]) in
+
+  template#table "files" table;
+
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/history.ml b/scripts/history.ml
new file mode 100644 (file)
index 0000000..a5fdf5b
--- /dev/null
@@ -0,0 +1,70 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: history.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+
+open Merjisforwiki
+
+let template = get_template "history.html"
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, _) _ =
+  let page = q#param "page" in
+  let page = if page = "" then "index" else page in
+
+  let sth =
+    dbh#prepare_cached
+      "select id, url, url_deleted, title, last_modified_date, logged_ip
+         from pages
+        where hostid = ? and (url = ? or url_deleted = ?)
+        order by last_modified_date desc" in
+  sth#execute [`Int hostid; `String page; `String page];
+
+  let table =
+    sth#map
+      (function
+        | [`Int version; `String url; _; `String title;
+           `Timestamp last_modified_date; logged_ip ] ->
+            let date = printable_date_time last_modified_date in
+            let has_logged_ip, logged_ip =
+              match logged_ip with
+                  `Null -> false, ""
+                | `String ip -> true, ip
+                | _ -> assert false in
+            [ "version", Template.VarString (string_of_int version);
+              "url", Template.VarString url;
+              "title", Template.VarString title;
+              "last_modified_date", Template.VarString date;
+              "has_logged_ip", Template.VarConditional has_logged_ip;
+              "logged_ip", Template.VarString logged_ip;
+              "is_live", Template.VarConditional true ]
+        | [`Int version; `Null; `String url; `String title;
+           `Timestamp last_modified_date; logged_ip ] ->
+            let date = printable_date_time last_modified_date in
+            let has_logged_ip, logged_ip =
+              match logged_ip with
+                  `Null -> false, ""
+                | `String ip -> true, ip
+                | _ -> assert false in
+            [ "version", Template.VarString (string_of_int version);
+              "url", Template.VarString url;
+              "title", Template.VarString title;
+              "last_modified_date", Template.VarString date;
+              "has_logged_ip", Template.VarConditional has_logged_ip;
+              "logged_ip", Template.VarString logged_ip;
+              "is_live", Template.VarConditional false ]
+        | _ -> assert false) in
+
+  template#table "history" table;
+
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/hoststyle.ml b/scripts/hoststyle.ml
new file mode 100644 (file)
index 0000000..1253076
--- /dev/null
@@ -0,0 +1,36 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: hoststyle.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, _) _ =
+  (* Get the CSS. *)
+  let sth = dbh#prepare_cached "select css from hosts where id = ?" in
+  sth#execute [`Int hostid];
+
+  let css =
+    match sth#fetch1 () with
+       [ `Null ] -> ""
+      | [ `String css ] -> css
+      | _ -> assert false in
+
+  (* It's crucial, for speed of page delivery and rendering, to have
+   * an expires header for CSS.  Even though this means that occasionally
+   * people will need to hit [Shift]+Reload, I'm going to set a
+   * medium-length expiry time on this resource.
+   *)
+  Table.set (Request.headers_out r) "Expires" (Cgi_expires.expires_medium ());
+
+  q#header ~content_type:"text/css" ();
+  print_string r css
+
+let () =
+  register_script run
diff --git a/scripts/image.ml b/scripts/image.ml
new file mode 100644 (file)
index 0000000..958292b
--- /dev/null
@@ -0,0 +1,51 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: image.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Merjisforwiki
+
+open Cocanwiki
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, _) _ =
+  let image = q#param "image" in
+  let is_thumbnail = q#param_true "thumbnail" in
+  let version =
+    try Some (int_of_string (q#param "version")) with Not_found -> None in
+
+  (* Get the image and its MIME type. *)
+  let what =
+    if not is_thumbnail then "image, mime_type"
+    else "thumbnail, tn_mime_type" in
+  let where, args =
+    match version with
+       None -> "hostid = ? and name = ?", [`Int hostid; `String image]
+      | Some version ->
+         "hostid = ? and (name = ? or name_deleted = ?) and id = ?",
+         [`Int hostid; `String image; `String image; `Int version] in
+
+  let sth = dbh#prepare_cached
+             ("select " ^ what ^ " from images where " ^ where) in
+  sth#execute args;
+
+  let data, mime_type =
+    match sth#fetch1 () with
+       [ `Binary data; `String mime_type ] ->
+         data, mime_type
+      | _ -> assert false in
+
+  if version <> None then
+    (* Set a medium-length expiry time on this resource. *)
+    Table.set (Request.headers_out r) "Expires" (Cgi_expires.expires_medium());
+
+  q#header ~content_type:mime_type ();
+  print_string r data
+
+let () =
+  register_script run
diff --git a/scripts/images.ml b/scripts/images.ml
new file mode 100644 (file)
index 0000000..3b5e1e9
--- /dev/null
@@ -0,0 +1,72 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: images.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+
+open Merjisforwiki
+
+let template = get_template "images.html"
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, _) _ =
+  let deleted = q#param_true "deleted" in
+  template#conditional "deleted" deleted;
+
+  let sql =
+    "select id, name, name_deleted, width, height, alt, octet_length (image),
+            tn_width, tn_height
+       from images
+      where hostid = ? and " ^
+    (if not deleted then "name is not null"
+     else "name_deleted is not null") ^
+    " order by 2, 3" in
+  let sth = dbh#prepare_cached sql in
+  sth#execute [`Int hostid];
+
+  let table =
+    sth#map
+      (fun row ->
+        let id, name, width, height, alt, size, tn_width, tn_height,
+          is_deleted, has_thumbnail =
+          match row with
+            | [`Int id; `String name; `Null; `Int width; `Int height;
+               `String alt; `Int size; `Int tn_width; `Int tn_height] ->
+                id, name, width, height, alt, size, tn_width, tn_height,
+                false, true
+            | [`Int id; `Null; `String name; `Int width; `Int height;
+               `String alt; `Int size; `Int tn_width; `Int tn_height] ->
+                id, name, width, height, alt, size, tn_width, tn_height,
+                true, true
+            | [`Int id; `String name; `Null; `Int width; `Int height;
+               `String alt; `Int size; `Null; `Null] ->
+                id, name, width, height, alt, size, 0, 0,
+                false, false
+            | [`Int id; `Null; `String name; `Int width; `Int height;
+               `String alt; `Int size; `Null; `Null] ->
+                id, name, width, height, alt, size, 0, 0,
+                true, false
+            | _ -> assert false in
+        [ "id", Template.VarString (string_of_int id);
+          "name", Template.VarString name;
+          "width", Template.VarString (string_of_int width);
+          "height", Template.VarString (string_of_int height);
+          "alt", Template.VarString alt;
+          "ksize", Template.VarString (string_of_int (size / 1024));
+          "tn_width", Template.VarString (string_of_int tn_width);
+          "tn_height", Template.VarString (string_of_int tn_height);
+          "is_deleted", Template.VarConditional is_deleted;
+          "has_thumbnail", Template.VarConditional has_thumbnail ]) in
+
+  template#table "images" table;
+
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/merjisforwiki.ml b/scripts/merjisforwiki.ml
new file mode 100644 (file)
index 0000000..6182ab9
--- /dev/null
@@ -0,0 +1,334 @@
+(* Basic Merjis functions.
+ * Copyright (C) 2004 Merjis Ltd.
+ * Written By Richard W.M. Jones (rich@merjis.com)
+ * $Id: merjisforwiki.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+(* This is a copy of an internal library which we use at Merjis. *)
+
+open Printf
+open ExtString
+open ExtList
+
+(*----- Basic stuff. -----*)
+
+let identity x = x
+
+let unique =
+  let n = ref 0 in
+  fun () -> incr n; !n
+
+let rec range a b =
+  if a <= b then
+    a :: range (a+1) b
+  else
+    []
+
+(*----- String functions. -----*)
+
+let string_contains substr str =
+  try String.find str substr; true
+  with String.Invalid_string -> false
+
+let string_of_char = String.make 1
+
+let truncate n str =
+  if String.length str < n then str else String.sub str 0 (n-1)
+
+(* These versions only work in the C locale for 7-bit characters. *)
+let isspace c =
+  c = ' '
+  (* || c = '\f' *) || c = '\n' || c = '\r' || c = '\t' (* || c = '\v' *)
+
+let isalpha c =
+  c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'
+
+let isdigit c =
+  c >= '0' && c <= '9'
+
+let isalnum c =
+  c >= '0' && c <= '9' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'
+
+let islower c =
+  c >= 'a' && c <= 'z'
+
+let isupper c =
+  c >= 'A' && c <= 'Z'
+
+let isxdigit c =
+  c >= '0' && c <= '9' || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F'
+
+let triml ?(test = isspace) str =
+  let i = ref 0 in
+  let n = ref (String.length str) in
+  while !n > 0 && test str.[!i]; do
+    decr n;
+    incr i
+  done;
+  if !i = 0 then str
+  else String.sub str !i !n
+
+let trimr ?(test = isspace) str =
+  let n = ref (String.length str) in
+  while !n > 0 && test str.[!n-1]; do
+    decr n
+  done;
+  if !n = String.length str then str
+  else String.sub str 0 !n
+
+let trim ?(test = isspace) str =
+  trimr (triml str)
+
+let string_for_all f str =
+  let len = String.length str in
+  let rec loop i =
+    if i = len then true
+    else (
+      let c = str.[i] in
+      if not (f c) then false
+      else loop (i+1)
+    )
+  in
+  loop 0
+
+let string_exists f str =
+  let len = String.length str in
+  let rec loop i =
+    if i = len then false
+    else (
+      let c = str.[i] in
+      if f c then true
+      else loop (i+1)
+    )
+  in
+  loop 0
+
+let string_is_whitespace = string_for_all isspace
+
+(*----- List functions. -----*)
+
+let first n xs =
+  fst (List.split_nth n xs)
+
+let rec uniq ?(cmp = Pervasives.compare) = function
+    [] -> []
+  | [x] -> [x]
+  | x :: y :: xs when compare x y = 0 ->
+      uniq (x :: xs)
+  | x :: y :: xs ->
+      x :: uniq (y :: xs)
+
+let sort_uniq ?cmp xs =
+  let xs = List.sort ?cmp xs in
+  let xs = uniq ?cmp xs in
+  xs
+
+let frequency ?(cmp = Pervasives.compare) xs =
+  let xs = List.sort ~cmp xs in
+  let rec loop = function
+      [] -> []
+    | [x] -> [1, x]
+    | x :: y :: xs when cmp x y = 0 ->
+        let rest = loop (y :: xs) in
+        let (count, _), rest = List.hd rest, List.tl rest in
+        (count+1, y) :: rest
+    | x :: xs ->
+        (1, x) :: loop xs
+  in
+  let xs = loop xs in
+  List.rev (List.sort xs)
+
+(* This version by Isaac Trotts. *)
+let group_by ?(cmp = Pervasives.compare) ls =
+  let ls' =
+    List.fold_left
+      (fun acc (day1, x1) ->
+         match acc with
+             [] -> [day1, [x1]]
+           | (day2, ls2) :: acctl ->
+               if cmp day1 day2 = 0
+               then (day1, x1 :: ls2) :: acctl
+               else (day1, [x1]) :: acc)
+      []
+      ls
+  in
+  let ls' = List.rev ls' in
+  List.map (fun (x, xs) -> x, List.rev xs) ls'
+
+(*----- File functions. -----*)
+
+let (//) = Filename.concat
+
+let rec input_all_lines chan =
+  try
+    let line = input_line chan in
+    line :: input_all_lines chan
+  with
+      End_of_file -> []
+
+let input_all chan =
+  let buf = Buffer.create 16384 in
+  let tmpsize = 16384 in
+  let tmp = String.create tmpsize in
+  let n = ref 0 in
+  while n := input chan tmp 0 tmpsize; !n > 0 do
+    Buffer.add_substring buf tmp 0 !n;
+  done;
+  Buffer.contents buf
+
+let input_file filename =
+  let chan = open_in_bin filename in
+  let data = input_all chan in
+  close_in chan;
+  data
+
+let output_file filename data =
+  let chan = open_out_bin filename in
+  output_string chan data;
+  close_out chan
+
+let output_tempfile data =
+  let filename, chan = Filename.open_temp_file "tmp" ".tmp" in
+  output_string chan data;
+  close_out chan;
+  filename
+
+(*----- Command functions. -----*)
+
+let cmd cmd =
+  let code = Sys.command cmd in
+  if code <> 0 then failwith (cmd ^ ": error code " ^ string_of_int code)
+
+let copy infile outfile =
+  cmd (sprintf "cp %s %s" infile outfile)
+
+let pget cmd =
+  let chan = Unix.open_process_in cmd in
+  let lines = input_all_lines chan in
+  let stat = Unix.close_process_in chan in
+  (match stat with
+       Unix.WEXITED 0 -> ()
+     | Unix.WEXITED i ->
+        failwith ("command failed with code " ^ string_of_int i)
+     | Unix.WSIGNALED i ->
+        failwith ("command killed by signal " ^ string_of_int i)
+     | Unix.WSTOPPED i ->
+        failwith ("command stopped by signal " ^ string_of_int i));
+  lines
+
+let unlink file =
+  try Unix.unlink file with Unix.Unix_error _ -> ()
+
+(*----- Meta-functions. -----*)
+let notf f =
+  fun v -> not (f v)
+
+(*----- Time and date. -----*)
+let short_weekday = function
+  | 0 -> "Sun" | 1 -> "Mon" | 2 -> "Tue" | 3 -> "Wed"
+  | 4 -> "Thu" | 5 -> "Fri" | 6 -> "Sat" | 7 -> "Sun"
+  | _ -> invalid_arg "short_weekday"
+
+let short_month = function
+  | 1 -> "Jan" | 2 -> "Feb" | 3 -> "Mar" | 4 -> "Apr"
+  | 5 -> "May" | 6 -> "Jun" | 7 -> "Jul" | 8 -> "Aug"
+  | 9 -> "Sep" | 10 -> "Oct" | 11 -> "Nov" | 12 -> "Dec"
+  | _ -> invalid_arg "short_month"
+
+(*----- Images. -----*)
+
+(* Find the format of an image.  Uses the external 'identify' program,
+ * part of ImageMagick.  Returns (mime_type, width, height).  Throws
+ * Invalid_argument "image_identify" if the data is not an image.
+ *)
+let image_identify_re = Pcre.regexp "^\\S+ ([A-Z]+) (\\d+)x(\\d+)"
+
+let image_identify data =
+  let filename = output_tempfile data in
+  let in_chan = Unix.open_process_in ("identify " ^ filename) in
+  let line = input_line in_chan in
+  let status = Unix.close_process_in in_chan in
+  unlink filename;
+  (match status with
+       Unix.WEXITED 0 ->               (* Identify was OK with it ... *)
+        ()
+     | Unix.WEXITED _ ->               (* Couldn't identify the file type. *)
+        raise (Invalid_argument "image_identify")
+     | Unix.WSIGNALED n ->
+        failwith ("image_identify: 'identify' killed by signal " ^
+                  string_of_int n)
+     | Unix.WSTOPPED n ->
+        failwith ("image_identify: 'identify' stopped by signal " ^
+                  string_of_int n));
+  try
+    let subs = Pcre.exec ~rex:image_identify_re line in
+    let type_string = Pcre.get_substring subs 1 in
+    let width = int_of_string (Pcre.get_substring subs 2) in
+    let height = int_of_string (Pcre.get_substring subs 3) in
+    let typ =
+      match type_string with
+         "JPEG" -> "image/jpeg"
+       | "GIF" -> "image/gif"
+       | "PNG" -> "image/png"
+       | _ -> raise (Invalid_argument "image_identify") in
+    typ, width, height
+  with
+      Not_found ->
+        raise (Invalid_argument "image_identify")
+
+(* Make a thumbnail of an image.  This uses the ImageMagick program 'convert'.
+ *)
+let image_thumbnail data max_width max_height =
+  let filename = output_tempfile data in
+  let cmd = sprintf "convert -size %dx%d %s -resize %dx%d -"
+             max_width max_height filename max_width max_height in
+  let in_chan = Unix.open_process_in cmd in
+  let thumbnail = input_all in_chan in
+  let status = Unix.close_process_in in_chan in
+  unlink filename;
+  (match status with
+       Unix.WEXITED 0 ->               (* Convert was OK with it ... *)
+        ()
+     | Unix.WEXITED n ->               (* Convert failed. *)
+        failwith ("convert: fail with error code " ^ string_of_int n)
+     | Unix.WSIGNALED n ->
+        failwith ("convert: killed by signal " ^ string_of_int n)
+     | Unix.WSTOPPED n ->
+        failwith ("convert: stopped by signal " ^ string_of_int n));
+  let mime_type, width, height = image_identify thumbnail in
+  thumbnail, mime_type, width, height
+
+(*----- Files and MIME types. -----*)
+
+let ws_re = Pcre.regexp "\\s+"
+let ext_re = Pcre.regexp "\\.([a-z0-9]+)$"
+
+let mime_types =
+  try
+    let chan = open_in "/etc/mime.types" in
+    let lines = input_all_lines chan in
+    close_in chan;
+    let lines = List.filter (fun line ->
+                              not (string_is_whitespace line) &&
+                              String.length line > 0 &&
+                              line.[0] <> '#') lines in
+    let res = ref [] in
+    List.iter (fun line ->
+                let fields = Pcre.split ~rex:ws_re line in
+                match fields with
+                    [] -> assert false
+                  | typ :: exts ->
+                      List.iter (fun ext ->
+                                   res := (ext, typ) :: !res) exts) lines;
+    !res
+  with
+      Sys_error _ -> []
+
+let mime_type_of_filename name =
+  try
+    let subs = Pcre.exec ~rex:ext_re name in
+    let ext = Pcre.get_substring subs 1 in
+    let ext = String.lowercase ext in
+    List.assoc ext mime_types
+  with
+      Not_found -> "application/octet-stream"
diff --git a/scripts/merjisforwiki.mli b/scripts/merjisforwiki.mli
new file mode 100644 (file)
index 0000000..ca7b2fd
--- /dev/null
@@ -0,0 +1,56 @@
+(* Basic Merjis functions.
+ * Copyright (C) 2004 Merjis Ltd.
+ * Written By Richard W.M. Jones (rich@merjis.com)
+ * $Id: merjisforwiki.mli,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+(* This is a copy of an internal library which we use at Merjis. *)
+
+val identity : 'a -> 'a
+val unique : unit -> int
+val range : int -> int -> int list
+
+val string_contains : string -> string -> bool
+val string_of_char : char -> string
+val truncate : int -> string -> string
+val isspace : char -> bool
+val isalpha : char -> bool
+val isdigit : char -> bool
+val isalnum : char -> bool
+val islower : char -> bool
+val isupper : char -> bool
+val isxdigit : char -> bool
+val triml : ?test:(char -> bool) -> string -> string
+val trimr : ?test:(char -> bool) -> string -> string
+val trim : ?test:(char -> bool) -> string -> string
+val string_for_all : (char -> bool) -> string -> bool
+val string_exists : (char -> bool) -> string -> bool
+val string_is_whitespace : string -> bool
+
+val first : int -> 'a list -> 'a list
+val uniq : ?cmp:('a -> 'a -> int) -> 'b list -> 'b list
+val sort_uniq : ?cmp:('a -> 'a -> int) -> 'a list -> 'a list
+val frequency : ?cmp:('a -> 'a -> int) -> 'a list -> (int * 'a) list
+val group_by : ?cmp:('a -> 'a -> int) -> ('a * 'b) list -> ('a * 'b list) list
+
+val (//) : string -> string -> string
+val input_all_lines : in_channel -> string list
+val input_all : in_channel -> string
+val input_file : string -> string
+val output_file : string -> string -> unit
+val output_tempfile : string -> string
+
+val cmd : string -> unit
+val copy : string -> string -> unit
+val pget : string -> string list
+val unlink : string -> unit
+
+val notf : ('a -> bool) -> 'a -> bool
+
+val short_weekday : int -> string
+val short_month : int -> string
+
+val image_identify : string -> string * int * int
+val image_thumbnail : string -> int -> int -> string * string * int * int
+
+val mime_type_of_filename : string -> string
diff --git a/scripts/page.ml b/scripts/page.ml
new file mode 100644 (file)
index 0000000..0f380a6
--- /dev/null
@@ -0,0 +1,212 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: page.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open ExtString
+
+open Merjisforwiki
+
+open Cocanwiki
+open Cocanwiki_ok
+
+let template_page = get_template "page.html"
+let template_404  = get_template "page_404.html"
+
+(* Maximum level of redirection. *)
+let max_redirect = 4
+
+type fp_status = FPOK of int * string * string * Dbi.datetime * bool
+              | FPRedirect of string
+              | FPNotFound
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, _) _ =
+  let page = q#param "page" in
+  let page = if page = "" then "index" else page in
+
+  (* Host-specific CSS? *)
+  let sth = dbh#prepare_cached "select css is not null from hosts
+                                 where id = ?" in
+  sth#execute [`Int hostid];
+  let has_host_css =
+    match sth#fetch1 () with
+      | [ `Bool has_host_css ] -> has_host_css
+      | _ -> assert false in
+
+  (* This code generates ordinary pages. *)
+  let make_page title description pageid last_modified_date has_page_css
+      version page page' =
+    let t = template_page in
+    t#set "title" title;
+    t#set "description" description;
+    t#set "pageid" (string_of_int pageid);
+    t#set "last_modified_date" (printable_date last_modified_date);
+    t#set "cocanwiki_package" Cocanwiki_version.package;
+    t#set "cocanwiki_version" Cocanwiki_version.version;
+
+    if page <> page' then (* redirection *) (
+      t#set "page" page';
+      t#set "original_page" page; (* XXX title - get it from database *)
+      t#conditional "redirected" true
+    ) else (
+      t#set "page" page;
+      t#conditional "redirected" false
+    );
+
+    t#conditional "has_host_css" has_host_css;
+    t#conditional "has_page_css" has_page_css;
+
+    (* Pull out the sections in this page. *)
+    let sth = dbh#prepare_cached
+               "select ordering, sectionname, content, divname
+                   from contents
+                  where pageid = ?
+                  order by ordering" in
+    sth#execute [`Int pageid];
+
+    let sections =
+      sth#map
+       (function [`Int ordering; `String sectionname; `String content;
+                  (`Null | `String _) as divname] ->
+          let divname, has_divname =
+            match divname with
+                `Null -> "", false
+              | `String divname -> divname, true in
+          [ "ordering", Template.VarString (string_of_int ordering);
+            "sectionname", Template.VarString sectionname;
+            "content",
+              Template.VarString
+                (Wikilib.xhtml_of_content dbh hostid content);
+            "has_divname", Template.VarConditional has_divname;
+            "divname", Template.VarString divname ]
+          | _ -> assert false) in
+
+    t#table "sections" sections;
+
+    (* Are we showing an old version of the page?  If so, warn. *)
+    (match version with
+        None ->
+          t#conditional "is_old_version" false
+       | Some pageid ->
+          t#conditional "is_old_version" true;
+          t#set "old_version" (string_of_int pageid));
+
+    q#template t
+  in
+
+  (* This code generates 404 pages. *)
+  let make_404 () =
+    Request.set_status r 404;          (* Return a 404 error code. *)
+
+    let t = template_404 in
+    t#set "page" page;
+
+    let search_terms =
+      String.map
+        (function
+             ('a'..'z' | 'A'..'Z' | '0'..'9') as c -> c
+           | _ -> ' ') page in
+
+    template_404#set "search_terms" search_terms;
+
+    q#template t
+  in
+
+  (* Fetch a page by name.  This function can give three answers:
+   * (1) Page fetched OK (fetches some details of the page).
+   * (2) Page is a redirect (fetches the name of the redirect page).
+   * (3) Page not found in database, ie. 404 error.
+   *)
+  (* XXX Should do a case-insensitive matching of URLs, and if the URL differs
+   * in case only should redirect to the lowercase version.
+   *)
+  let fetch_page page version allow_redirect =
+    match version with
+      | None ->
+         if allow_redirect then (
+           let sth =
+             dbh#prepare_cached
+               "select redirect, id, title, description, last_modified_date,
+                        css is not null
+                   from pages where hostid = ? and url = ?" in
+           sth#execute [`Int hostid; `String page];
+           (try
+              (match sth#fetch1 () with
+                 | [ `Null; `Int id; `String title; `String description;
+                     `Timestamp last_modified_date; `Bool has_page_css ] ->
+                     FPOK (id, title, description, last_modified_date,
+                           has_page_css)
+                 | `String redirect :: _ ->
+                     FPRedirect redirect
+                 | _ -> assert false)
+            with
+                Not_found -> FPNotFound)
+         ) else (* redirects not allowed ... *) (
+           let sth =
+             dbh#prepare_cached
+               "select id, title, description, last_modified_date,
+                        css is not null
+                   from pages where hostid = ? and url = ?" in
+           sth#execute [`Int hostid; `String page];
+           (try
+              (match sth#fetch1 () with
+                 | [ `Int id; `String title; `String description;
+                     `Timestamp last_modified_date; `Bool has_page_css ] ->
+                     FPOK (id, title, description, last_modified_date,
+                           has_page_css)
+                 | _ -> assert false)
+            with
+                Not_found -> FPNotFound)
+         )
+      | Some version ->
+           let sth =
+             dbh#prepare_cached
+               "select id, title, description, last_modified_date,
+                        css is not null
+                   from pages
+                  where hostid = ? and id = ? and
+                        (url = ? or url_deleted = ?)" in
+           sth#execute [`Int hostid; `Int version;
+                        `String page; `String page];
+           (try
+              (match sth#fetch1 () with
+                 | [ `Int id; `String title; `String description;
+                     `Timestamp last_modified_date; `Bool has_page_css ] ->
+                     FPOK (id, title, description, last_modified_date,
+                           has_page_css)
+                 | _ -> assert false)
+            with
+                Not_found -> FPNotFound)
+  in
+
+  (* Here we deal with the complex business of redirects and versions. *)
+  let allow_redirect = not (q#param_true "no_redirect") in
+  let version =
+    try Some (int_of_string (q#param "version")) with Not_found -> None in
+
+  let rec loop page' i =
+    if i > max_redirect then (
+      error ~title:"Too many redirections" ~back_button:true
+        q ("Too many redirects between pages.  This may happen because " ^
+          "of a cycle of redirections.");
+      raise CgiExit
+    ) else
+      match fetch_page page' version allow_redirect with
+       | FPOK (pageid, title, description, last_modified_date, has_page_css)->
+           make_page title description pageid last_modified_date has_page_css
+             version page page'
+       | FPRedirect page' ->
+           loop page' (i+1)
+       | FPNotFound ->
+           make_404 ()
+  in
+  loop page 0
+
+let () =
+  register_script run
diff --git a/scripts/pagestyle.ml b/scripts/pagestyle.ml
new file mode 100644 (file)
index 0000000..94a5d81
--- /dev/null
@@ -0,0 +1,51 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: pagestyle.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, _) _ =
+  let page = q#param "page" in
+  let version =
+    try Some (int_of_string (q#param "version")) with Not_found -> None in
+
+  (* Get the CSS. *)
+  let sth =
+    match version with
+       None ->
+         let sth = dbh#prepare_cached
+                     "select css from pages where hostid = ? and url = ?" in
+         sth#execute [`Int hostid; `String page];
+         sth
+      | Some version ->
+         let sth = dbh#prepare_cached
+                     "select css from pages
+                        where hostid = ? and id = ? and
+                              (url = ? or url_deleted = ?)" in
+         sth#execute [`Int hostid; `Int version; `String page; `String page];
+         sth in
+  let css =
+    match sth#fetch1 () with
+       [ `Null ] -> ""
+      | [ `String css ] -> css
+      | _ -> assert false in
+
+  (* It's crucial, for speed of page delivery and rendering, to have
+   * an expires header for CSS.  Even though this means that occasionally
+   * people will need to hit [Shift]+Reload, I'm going to set a
+   * medium-length expiry time on this resource.
+   *)
+  Table.set (Request.headers_out r) "Expires" (Cgi_expires.expires_medium ());
+
+  q#header ~content_type:"text/css" ();
+  print_string r css
+
+let () =
+  register_script run
diff --git a/scripts/preview.ml b/scripts/preview.ml
new file mode 100644 (file)
index 0000000..68d1e56
--- /dev/null
@@ -0,0 +1,25 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: preview.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *
+ * In case you were wondering, this script is called from the Javascript
+ * to update the preview <div>.  See /_js/editor.js for details.
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, _) _ =
+  let content = q#param "content" in
+  let xhtml = Wikilib.xhtml_of_content dbh hostid content in
+
+  q#header ~content_type:"text/html; charset=utf-8" ();
+  print_string r xhtml
+
+let () =
+  register_script run
diff --git a/scripts/recent.ml b/scripts/recent.ml
new file mode 100644 (file)
index 0000000..39526ca
--- /dev/null
@@ -0,0 +1,68 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: recent.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+
+let template = get_template "recent.html"
+
+let max_age = "3 months"
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, _) _ =
+  let sth =
+    dbh#prepare_cached
+      "select id, url, url_deleted, title, last_modified_date, logged_ip
+         from pages
+        where hostid = ?
+          and last_modified_date >= current_timestamp - interval ?
+        order by last_modified_date desc" in
+  sth#execute [`Int hostid; `String max_age];
+
+  let table =
+    sth#map
+      (function
+        | [`Int version; `String url; _; `String title;
+           `Timestamp last_modified_date; logged_ip] ->
+            let date = printable_date_time last_modified_date in
+            let has_logged_ip, logged_ip =
+              match logged_ip with
+                  `Null -> false, ""
+                | `String ip -> true, ip
+                | _ -> assert false in
+            [ "version", Template.VarString (string_of_int version);
+              "url", Template.VarString url;
+              "title", Template.VarString title;
+              "last_modified_date", Template.VarString date;
+              "has_logged_ip", Template.VarConditional has_logged_ip;
+              "logged_ip", Template.VarString logged_ip;
+              "is_live", Template.VarConditional true ]
+        | [`Int version; `Null; `String url; `String title;
+           `Timestamp last_modified_date; logged_ip] ->
+            let date = printable_date_time last_modified_date in
+            let has_logged_ip, logged_ip =
+              match logged_ip with
+                  `Null -> false, ""
+                | `String ip -> true, ip
+                | _ -> assert false in
+            [ "version", Template.VarString (string_of_int version);
+              "url", Template.VarString url;
+              "title", Template.VarString title;
+              "last_modified_date", Template.VarString date;
+              "has_logged_ip", Template.VarConditional has_logged_ip;
+              "logged_ip", Template.VarString logged_ip;
+              "is_live", Template.VarConditional false ]
+        | _ -> assert false) in
+
+  template#table "recent_changes" table;
+
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/restore.ml b/scripts/restore.ml
new file mode 100644 (file)
index 0000000..a77cff1
--- /dev/null
@@ -0,0 +1,89 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: restore.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+open Cocanwiki_ok
+open Cocanwiki_diff
+open Cocanwiki_emailnotify
+
+open Merjisforwiki
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, hostname) _ =
+  let version = int_of_string (q#param "version") in
+  let page = q#param "page" in
+
+  if q#param_true "yes" then (
+    (* Get the IP address of the user, if available. *)
+    let logged_ip =
+      try `String (Connection.remote_ip (Request.connection r))
+      with Not_found -> `Null in
+
+    (* Copy the old version of the page to be live. *)
+    let sth = dbh#prepare_cached "select title, description, creation_date,
+                                         redirect, css
+                                    from pages
+                                   where hostid = ?
+                                     and url_deleted = ? and id = ?" in
+    sth#execute [`Int hostid; `String page; `Int version];
+
+    let title, description, creation_date, redirect, css =
+      match sth#fetch1 () with
+         [ title; description; creation_date; redirect; css ] ->
+           title, description, creation_date, redirect, css
+       | _ -> assert false in
+
+    let sth =
+      dbh#prepare_cached "set constraints pages_redirect_cn deferred" in
+    sth#execute [];
+
+    let sth = dbh#prepare_cached "update pages set url_deleted = url,
+                                                   url = null
+                                   where hostid = ? and url = ?" in
+    sth#execute [`Int hostid; `String page];
+
+    let sth = dbh#prepare_cached "insert into pages (hostid, url, title,
+                                     description, creation_date, logged_ip,
+                                     redirect, css)
+                                  values (?, ?, ?, ?, ?, ?, ?, ?)" in
+    sth#execute [`Int hostid; `String page; title; description;
+                creation_date; logged_ip; redirect; css ];
+
+    let pageid = sth#serial "pages_id_seq" in
+
+    let sth = dbh#prepare_cached "insert into contents (pageid, ordering,
+                                         sectionname, content, divname)
+                                  select ? as pageid, ordering, sectionname,
+                                              content, divname
+                                    from contents
+                                   where pageid = ?" in
+    sth#execute [`Int pageid; `Int version];
+
+    dbh#commit ();
+
+    (* Email notify. *)
+    let subject = "Page " ^ page ^ " has been restored." in
+    let body = fun () ->
+      (* Prepare the diff between this version and the previous version. *)
+      let diff, _ = get_diff dbh hostid page ~version:pageid () in
+      "Page: http://" ^ hostname ^ "/" ^ page ^ "\n\n" ^
+      diff in
+
+    email_notify ~body ~subject dbh hostid;
+
+    (* Done. *)
+    let buttons = [ ok_button ("/" ^ page) ] in
+    ok ~title:"Restored" ~buttons
+      q "The old page was restored successfully."
+  ) else
+    q#redirect ("http://" ^ hostname ^ "/" ^ page)
+
+let () =
+  register_script run
diff --git a/scripts/restore_form.ml b/scripts/restore_form.ml
new file mode 100644 (file)
index 0000000..050f6e8
--- /dev/null
@@ -0,0 +1,51 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: restore_form.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Merjisforwiki
+
+open Cocanwiki
+open Cocanwiki_ok
+open Cocanwiki_diff
+
+let template = get_template "restore_form.html"
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, _) _ =
+  (* Parameters. *)
+  let page = q#param "page" in
+  let old_version = int_of_string (q#param "version") in
+
+  template#set "page" page;
+  template#set "version" (string_of_int old_version);
+
+  (* Compute the diff between the latest version of this page and the
+   * page we're wanting to restore.
+   *)
+  let sth = dbh#prepare_cached "select id from pages
+                                 where hostid = ? and url = ?" in
+  sth#execute [`Int hostid; `String page];
+
+  let version = sth#fetch1int () in
+
+  if version = old_version then (
+    error ~back_button:true ~title:"Restoring live version"
+      q "You seem to be trying to restore the live version.";
+    raise CgiExit
+  );
+
+  let diff, _ = get_diff dbh hostid page ~old_version ~version () in
+
+  (* Display the diff. *)
+  template#set "diff" diff;
+
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/search.ml b/scripts/search.ml
new file mode 100644 (file)
index 0000000..369afcf
--- /dev/null
@@ -0,0 +1,26 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: search.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Cgi_escape
+open Printf
+
+open Cocanwiki
+
+let search : ('a -> 'b -> 'c, unit, string, string) format4 =
+  "http://www.google.com/search?q=site:%s+%s"
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, hostname) _ =
+  let query = q#param "q" in
+
+  let query = sprintf search (escape_url hostname) (escape_url query) in
+
+  q#redirect query
+
+let () =
+  register_script run
diff --git a/scripts/sitemap.ml b/scripts/sitemap.ml
new file mode 100644 (file)
index 0000000..154f2f0
--- /dev/null
@@ -0,0 +1,59 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: sitemap.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Merjisforwiki
+
+open Cocanwiki
+
+let template = get_template "sitemap.html"
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, hostname) _ =
+  (* Pull out all the current pages, and a bit of content from each. *)
+  let sth = dbh#prepare_cached "select p.url, p.url = 'index',
+                                       p.title, p.description,
+                                       p.last_modified_date,
+                                       (select content from contents
+                                         where pageid = p.id
+                                         order by ordering limit 1) as content
+                                  from pages p
+                                 where p.hostid = ? and p.url is not null
+                                   and p.redirect is null
+                                 order by 2 desc, 3, 1" in
+  sth#execute [`Int hostid];
+
+  let table =
+    sth#map
+      (function [`String url; _; `String title; `String description;
+                `Timestamp last_modified_date;
+                (`Null | `String _) as content] ->
+        let url = if url = "index" then "" else url in
+        let date = printable_date last_modified_date in
+        [ "url", Template.VarString url;
+          "title", Template.VarString title;
+          "description", Template.VarString description;
+          "last_modified_date", Template.VarString date;
+          "has_content", Template.VarConditional (content <> `Null);
+          "content", Template.VarString
+            (match content with
+                 `Null -> ""
+               | `String c ->
+                   truncate 160
+                     (Wikilib.text_of_xhtml
+                        (Wikilib.xhtml_of_content dbh hostid c))) ]
+        | _ -> assert false) in
+
+  template#set "hostname" hostname;
+  template#table "sitemap" table;
+
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/undelete_file.ml b/scripts/undelete_file.ml
new file mode 100644 (file)
index 0000000..c4ec21e
--- /dev/null
@@ -0,0 +1,56 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: undelete_file.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+open Cocanwiki_ok
+
+open Merjisforwiki
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, hostname) _ =
+  let id = int_of_string (q#param "id") in
+
+  if q#param_true "yes" then (
+    (* Get the name of the file. *)
+    let sth = dbh#prepare_cached "select name_deleted from files
+                                   where hostid = ? and id = ?" in
+    sth#execute [`Int hostid; `Int id];
+
+    let name = sth#fetch1string () in
+
+    (* First delete any more recent versions of this file. *)
+    let sth = dbh#prepare_cached "update files
+                                     set name_deleted = name, name = null
+                                   where hostid = ? and name = ?" in
+    sth#execute [`Int hostid; `String name];
+
+    (* Now copy the old row, changing name_deleted back to name so the file
+     * becomes live.
+     *)
+    let sth = dbh#prepare_cached "insert into files
+                                  (hostid, name, content, title, mime_type,
+                                   upload_date)
+                                  select hostid, name_deleted, content,
+                                         title, mime_type, upload_date
+                                    from files
+                                   where hostid = ? and id = ?" in
+    sth#execute [`Int hostid; `Int id];
+
+    dbh#commit ();
+
+    (* Done. *)
+    let buttons = [ ok_button "/_files" ] in
+    ok ~title:"File restored" ~buttons
+      q "File was restored successfully."
+  ) else
+    q#redirect ("http://" ^ hostname ^ "/_files")
+
+let () =
+  register_script run
diff --git a/scripts/undelete_file_form.ml b/scripts/undelete_file_form.ml
new file mode 100644 (file)
index 0000000..2f47901
--- /dev/null
@@ -0,0 +1,38 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: undelete_file_form.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+
+open Merjisforwiki
+
+let template = get_template "undelete_file_form.html"
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, _) _ =
+  let id = int_of_string (q#param "id") in
+
+  let sth = dbh#prepare_cached "select name, name_deleted
+                                  from files
+                                 where hostid = ? and id = ?" in
+  sth#execute [`Int hostid; `Int id];
+
+  let name =
+    match sth#fetch1 () with
+       [ `String name; `Null]
+      | [ `Null; `String name] -> name
+      | _ -> assert false in
+
+  template#set "id" (string_of_int id);
+  template#set "name" name;
+
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/undelete_image.ml b/scripts/undelete_image.ml
new file mode 100644 (file)
index 0000000..67c2a9f
--- /dev/null
@@ -0,0 +1,61 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: undelete_image.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+open Cocanwiki_ok
+
+open Merjisforwiki
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, hostname) _ =
+  let id = int_of_string (q#param "id") in
+
+  if q#param_true "yes" then (
+    (* Get the name of the image. *)
+    let sth = dbh#prepare_cached "select name_deleted from images
+                                   where hostid = ? and id = ?" in
+    sth#execute [`Int hostid; `Int id];
+
+    let name = sth#fetch1string () in
+
+    (* First delete any more recent versions of this image. *)
+    let sth = dbh#prepare_cached "update images
+                                     set name_deleted = name, name = null
+                                   where hostid = ? and name = ?" in
+    sth#execute [`Int hostid; `String name];
+
+    (* Now copy the old row, changing name_deleted back to name so the image
+     * becomes live.
+     *)
+    let sth = dbh#prepare_cached "insert into images
+                                  (hostid, name, image, width, height,
+                                   alt, title, longdesc, class,
+                                   mime_type, thumbnail, tn_width,
+                                   tn_height, tn_mime_type, upload_date)
+                                  select hostid, name_deleted, image,
+                                         width, height, alt, title, longdesc,
+                                         class, mime_type, thumbnail,
+                                         tn_width, tn_height, tn_mime_type,
+                                         upload_date
+                                    from images
+                                   where hostid = ? and id = ?" in
+    sth#execute [`Int hostid; `Int id];
+
+    dbh#commit ();
+
+    (* Done. *)
+    let buttons = [ ok_button "/_images" ] in
+    ok ~title:"Image restored" ~buttons
+      q "Image was restored successfully."
+  ) else
+    q#redirect ("http://" ^ hostname ^ "/_images")
+
+let () =
+  register_script run
diff --git a/scripts/undelete_image_form.ml b/scripts/undelete_image_form.ml
new file mode 100644 (file)
index 0000000..ebf0c73
--- /dev/null
@@ -0,0 +1,42 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: undelete_image_form.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+
+open Merjisforwiki
+
+let template = get_template "undelete_image_form.html"
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, _) _ =
+  let id = int_of_string (q#param "id") in
+
+  let sth = dbh#prepare_cached "select name, name_deleted, width, height, alt
+                                  from images
+                                 where hostid = ? and id = ?" in
+  sth#execute [`Int hostid; `Int id];
+
+  let name, width, height, alt =
+    match sth#fetch1 () with
+       [ `String name; `Null; `Int width; `Int height; `String alt]
+      | [ `Null; `String name; `Int width; `Int height; `String alt] ->
+         name, width, height, alt
+      | _ -> assert false in
+
+  template#set "id" (string_of_int id);
+  template#set "name" name;
+  template#set "width" (string_of_int width);
+  template#set "height" (string_of_int height);
+  template#set "alt" alt;
+
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/upload_file.ml b/scripts/upload_file.ml
new file mode 100644 (file)
index 0000000..5c0f2c9
--- /dev/null
@@ -0,0 +1,76 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: upload_file.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open ExtString
+
+open Merjisforwiki
+
+open Cocanwiki
+open Cocanwiki_ok
+open Cocanwiki_emailnotify
+
+let is_ws_re = Pcre.regexp "^\\s*$"
+let is_whitespace str = Pcre.pmatch ~rex:is_ws_re str
+
+(* Valid file names. *)
+let file_ok_re = Pcre.regexp "^[a-z0-9][-._a-z0-9]*$"
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, hostname) _ =
+  let name = q#param "name" in
+  let title = q#param "title" in
+
+  (* See if there was an upload. *)
+  let file =
+    try
+      let upload = q#upload "file" in
+      upload.upload_value
+    with
+       Not_found ->
+         error ~title:"No file" ~back_button:true
+           q "No file was uploaded.";
+         raise CgiExit in
+
+  (* Check the name is valid. *)
+  if not (Pcre.pmatch ~rex:file_ok_re name) then (
+    error ~title:"Bad File Name" ~back_button:true
+      q ("The File Name must contain only lowercase English letters, " ^
+        "numbers, dots, dashes and underscore.");
+    raise CgiExit
+  );
+
+  (* Identify the MIME type from the extension. *)
+  let mime_type = mime_type_of_filename name in
+
+  let title = if is_whitespace title then `Null else `String title in
+
+  (* Put the file into the database. *)
+  let sth =
+    dbh#prepare_cached
+      "insert into files (hostid, name, content, title, mime_type)
+       values (?, ?, ?, ?, ?)" in
+  sth#execute [`Int hostid; `String name; `Binary file; title;
+              `String mime_type];
+
+  dbh#commit ();
+
+  (* Email notify. *)
+  let subject = "File " ^ name ^ " has been uploaded." in
+  let body = fun () ->
+    "Page: http://" ^ hostname ^ "/_files" in
+
+  email_notify ~body ~subject dbh hostid;
+
+  let buttons = [ ok_button "/_files" ] in
+  ok ~title:"File uploaded" ~buttons
+    q "File was uploaded successfully."
+
+let () =
+  register_script run
diff --git a/scripts/upload_file_form.ml b/scripts/upload_file_form.ml
new file mode 100644 (file)
index 0000000..480a74e
--- /dev/null
@@ -0,0 +1,22 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: upload_file_form.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+
+open Merjisforwiki
+
+let template = get_template "upload_file_form.html"
+
+let run r (q : cgi) (dbh : Dbi.connection) _ _ =
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/upload_image.ml b/scripts/upload_image.ml
new file mode 100644 (file)
index 0000000..58bc7aa
--- /dev/null
@@ -0,0 +1,126 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: upload_image.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open ExtString
+
+open Merjisforwiki
+
+open Cocanwiki
+open Cocanwiki_ok
+open Cocanwiki_emailnotify
+
+let is_ws_re = Pcre.regexp "^\\s*$"
+let is_whitespace str = Pcre.pmatch ~rex:is_ws_re str
+
+(* Valid image names. *)
+let image_ok_re = Pcre.regexp "^[a-z0-9][_a-z0-9]*\\.(jpg|jpeg|gif|ico|png)$"
+
+let run r (q : cgi) (dbh : Dbi.connection) (hostid, hostname) _ =
+  let name = q#param "name" in
+  let alt = q#param "alt" in
+  let title = q#param "title" in
+  let longdesc = q#param "longdesc" in
+  let clazz = q#param "class" in
+
+  (* See if there was an upload. *)
+  let image =
+    try
+      let upload = q#upload "image" in
+      upload.upload_value
+    with
+       Not_found ->
+         error ~title:"No image" ~back_button:true
+           q "No image was uploaded.";
+         raise CgiExit in
+
+  (* Check the name is valid. *)
+  if not (Pcre.pmatch ~rex:image_ok_re name) then (
+    error ~title:"Bad Image Name" ~back_button:true
+      q ("The Image Name must contain only lowercase English letters, " ^
+        "numbers and underscore.  It must end with .jpg, .gif or .png " ^
+        "depending on the image format.");
+    raise CgiExit
+  );
+
+  (* Check the image is an image, and get the size. *)
+  let mime_type, width, height =
+    try image_identify image
+    with
+       Invalid_argument _ ->
+         error ~title:"Bad image" ~back_button:true
+           q ("Unknown image type.  Is the file you uploaded really an " ^
+              "image?");
+         raise CgiExit in
+
+  (* Check the image filename extension matches the MIME type. *)
+  let ext_ok =
+    match mime_type with
+       "image/jpeg" ->
+         String.ends_with name ".jpg" ||
+         String.ends_with name ".jpeg"
+      | "image/gif" ->
+         String.ends_with name ".gif"
+      | "image/png" ->
+         String.ends_with name ".png"
+      | _ -> assert false in
+  if not ext_ok then (
+    error ~title:"Bad Image Name" ~back_button:true
+      q ("The Image Name extension has to match the image format.  " ^
+        "For example if the image is in JPEG format, the name must " ^
+        "be 'something.jpg'.  I detected the following image type " ^
+        "in the file you uploaded: " ^ mime_type);
+    raise CgiExit
+  );
+
+  (* Check some ALT text was supplied. *)
+  if is_whitespace alt then (
+    error ~title:"Missing Alt text" ~back_button:true
+      q ("You must supply Alt text describing the image.  This is required " ^
+        "by accessibility laws and to allow search engines to discover the " ^
+        "content of images.");
+    raise CgiExit
+  );
+
+  let title = if is_whitespace title then `Null else `String title in
+  let longdesc = if is_whitespace longdesc then `Null else `String longdesc in
+  let clazz = if is_whitespace clazz then `Null else `String clazz in
+
+  (* Make a thumbnail of this image. *)
+  let thumbnail, tn_mime_type, tn_width, tn_height =
+    image_thumbnail image 120 120 in
+
+  (* Put the image into the database. *)
+  let sth =
+    dbh#prepare_cached
+      "insert into images (hostid, name, image, width, height, alt,
+                           title, longdesc, class, thumbnail, tn_width,
+                           tn_height, mime_type, tn_mime_type)
+       values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" in
+  sth#execute [`Int hostid; `String name; `Binary image; `Int width;
+              `Int height; `String alt; title; longdesc; clazz;
+              `Binary thumbnail; `Int tn_width; `Int tn_height;
+              `String mime_type; `String tn_mime_type];
+
+  dbh#commit ();
+
+  (* Email notify. *)
+  let subject = "Image " ^ name ^ " has been uploaded." in
+  let body = fun () ->
+    "Page: http://" ^ hostname ^ "/_images" in
+
+  email_notify ~body ~subject dbh hostid;
+
+  let buttons = [ ok_button "/_images" ] in
+  ok ~title:"Image uploaded" ~buttons
+    q "Image was uploaded successfully."
+
+let () =
+  register_script run
diff --git a/scripts/upload_image_form.ml b/scripts/upload_image_form.ml
new file mode 100644 (file)
index 0000000..d40a39c
--- /dev/null
@@ -0,0 +1,22 @@
+(* COCANWIKI scripts.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: upload_image_form.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Printf
+
+open Cocanwiki
+
+open Merjisforwiki
+
+let template = get_template "upload_image_form.html"
+
+let run r (q : cgi) (dbh : Dbi.connection) _ _ =
+  q#template template
+
+let () =
+  register_script run
diff --git a/scripts/wikilib.ml b/scripts/wikilib.ml
new file mode 100644 (file)
index 0000000..2599f0e
--- /dev/null
@@ -0,0 +1,469 @@
+(* Library of functions useful for people implementing a Wiki.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: wikilib.ml,v 1.1 2004/09/07 10:14:09 rich Exp $
+ *)
+
+open Apache
+open Registry
+open Cgi
+open Cgi_escape
+open Printf
+
+open ExtString
+
+open Merjisforwiki
+
+(* Generate a URL for a new page with the given title.  This code checks
+ * if the URL already exists in the database and can return one of several
+ * errors.
+ *)
+type genurl_error_t = GenURL_OK of string
+                   | GenURL_TooShort
+                   | GenURL_BadURL
+                   | GenURL_Duplicate of string
+
+let nontrivial_re = Pcre.regexp ~flags:[`CASELESS] "[a-z0-9]"
+
+let generate_url_of_title (dbh : Dbi.connection) hostid title =
+  (* Create a suitable URL from this title. *)
+  let url = String.map (function '\000' .. ' ' | '<' | '>' | '&' | '"' -> '_'
+                         | c -> Char.lowercase c) title in
+
+  (* Check URL is not too trivial. *)
+  if not (Pcre.pmatch ~rex:nontrivial_re url) then
+    GenURL_TooShort
+  (* URL cannot begin with '_'. *)
+  else if url.[0] = '_' then
+    GenURL_BadURL
+  else (
+    (* Check that the URL doesn't already exist in the database.  If it does
+     * then it probably means that another page exists with similar enough
+     * content, so we should redirect to there instead.
+     *)
+    let sth = dbh#prepare_cached "select 1 from pages
+                                   where hostid = ? and url = ?" in
+    sth#execute [`Int hostid; `String url];
+
+    try
+      sth#fetch1int ();
+      GenURL_Duplicate url
+    with
+       Not_found ->
+         GenURL_OK url
+  )
+
+(* Obscure a mailto: URL against spammers. *)
+let obscure_mailto url =
+  if String.length url > 8 then (
+    let c7 = Char.code url.[7] in
+    let c8 = Char.code url.[8] in
+    let start = String.sub url 0 7 in
+    let rest = escape_html_tag (String.sub url 9 (String.length url - 9)) in
+    sprintf "%s&#x%02x;&#x%02x;%s" start c7 c8 rest
+  )
+  else
+    url
+
+(* Convert Wiki markup to XHTML 1.0.
+ *
+ * Shortcomings:
+ * Doesn't support multi-level bullet points. (XXX)
+ * Intra-page links. (XXX)
+ *)
+
+(* This matches any markup. *)
+let markup_re =
+  let link = "\\[\\[\\s*(?:.+?)\\s*(?:\\|.+?\\s*)?\\]\\]" in
+  let tag = "</?(?:b|i|strong|em|code|sup|sub|nowiki)>" in
+  Pcre.regexp ("(.*?)((?:" ^ link ^ ")|(?:" ^ tag ^ "))(.*)")
+
+(* This matches links only, and should be compatible with the link contained
+ * in the above regexp.
+ *)
+let link_re = Pcre.regexp "\\[\\[\\s*(.+?)\\s*(?:\\|(.+?)\\s*)?\\]\\]"
+
+let image_re =
+  Pcre.regexp "^(image|thumb(?:nail)?):\\s*([a-z0-9][_a-z0-9]*\\.(?:jpg|jpeg|gif|ico|png))$"
+let file_re =
+  Pcre.regexp "^file:\\s*([a-z0-9][-._a-z0-9]*)$"
+
+let url_re = Pcre.regexp "^[a-z]+://"
+let mailto_re = Pcre.regexp "^mailto:"
+
+(* Links. *)
+let markup_link dbh hostid link =
+  let subs = Pcre.exec ~rex:link_re link in
+  let url = Pcre.get_substring subs 1 in
+
+  let tag name = function
+      `Null -> ""
+    | `String v -> " " ^ name ^ "=\"" ^ escape_html_tag v ^ "\""
+  in
+
+  if Pcre.pmatch ~rex:image_re url then (
+    (* It may be an image. *)
+    let subs = Pcre.exec ~rex:image_re url in
+    let is_thumb = (Pcre.get_substring subs 1).[0] = 't' in
+    let name = Pcre.get_substring subs 2 in
+
+    let sql = "select id, " ^
+             (if is_thumb then "tn_width, tn_height"
+              else "width, height") ^
+             ", alt, title, longdesc, class
+               from images
+              where hostid = ? and name = ?" in
+    let sth = dbh#prepare_cached sql in
+    sth#execute [`Int hostid; `String name];
+
+    try
+      let imageid, width, height, alt, title, longdesc, clasz =
+       match sth#fetch1 () with
+           [`Int imageid; `Int width; `Int height; `String alt;
+            (`Null | `String _) as title;
+            (`Null | `String _) as longdesc;
+            (`Null | `String _) as clasz] ->
+             imageid, width, height, alt, title, longdesc, clasz
+         | _ -> assert false in
+
+      let link = "/_image/" ^ escape_url name in
+
+      (if is_thumb then "<a href=\"" ^ link ^ "\">" else "") ^
+      "<img src=\"" ^ link ^ "?version=" ^ string_of_int imageid ^
+      (if is_thumb then "&thumbnail=1" else "") ^
+      "\" width=\"" ^
+      string_of_int width ^
+      "\" height=\"" ^
+      string_of_int height ^
+      "\" alt=\"" ^
+      escape_html_tag alt ^
+      "\"" ^
+      tag "title" title ^
+      tag "longdesc" longdesc ^
+      tag "class" clasz ^
+      "/>" ^
+      (if is_thumb then "</a>" else "")
+    with
+       Not_found ->
+         (* Image not found. *)
+         "<a class=\"image_not_found\" " ^
+         "href=\"/_bin/upload_image_form.cmo?name=" ^
+         escape_url name ^
+         "\">" ^
+         escape_html name ^
+         "</a>"
+  ) else if Pcre.pmatch ~rex:file_re url then (
+    (* It may be a file. *)
+    let subs = Pcre.exec ~rex:file_re url in
+    let name = Pcre.get_substring subs 1 in
+
+    let sth = dbh#prepare_cached "select title
+                                    from files
+                                   where hostid = ? and name = ?" in
+    sth#execute [`Int hostid; `String name];
+
+    try
+      let title =
+       match sth#fetch1 () with
+           [(`Null | `String _) as title] -> title
+         | _ -> assert false in
+
+      "<a href=\"/_file/" ^
+      escape_url name ^
+      "\"" ^
+      tag "title" title ^
+      ">" ^
+      escape_html name ^
+      "</a>"
+    with
+       Not_found ->
+         (* File not found. *)
+         "<a class=\"file_not_found\" " ^
+         "href=\"/_bin/upload_file_form.cmo?name=" ^
+         escape_url name ^
+         "\">" ^
+         escape_html name ^
+         "</a>"
+  ) else (
+    (* Pcre changed behaviour between versions.  Previously a non-capture
+     * would return "".  Now it throws 'Not_found'.
+     *)
+    let text =
+      try Pcre.get_substring subs 2
+      with Not_found -> "" in
+    let text = if text = "" then url else text in
+
+    (* XXX Escaping here is very hairy indeed.  (See also the obscure_mailto
+     * function which performs some escaping ...)
+     *)
+
+    let url, clasz, title =
+      if Pcre.pmatch ~rex:url_re url then
+       escape_html_tag url, "external", url (* http://.... *)
+      else if Pcre.pmatch ~rex:mailto_re url then (
+       obscure_mailto url, "mailto", url
+      ) else (
+       let title = url in
+       (* Look up the 'URL' against the titles in the database and
+        * obtain the real URL.  If none is found then it's a link to
+        * create a new page.
+        *)
+       let sth = dbh#prepare_cached "select url from pages
+                                       where hostid = ? and url is not null
+                                         and lower (title) = lower (?)" in
+       sth#execute [`Int hostid; `String url];
+
+       try
+         let url = sth#fetch1string () in
+         "/" ^ url, "internal", title
+       with
+           Not_found ->
+             "/_bin/create_form.cmo?title=" ^ escape_url url, "newpage", title
+      ) in
+
+    "<a href=\"" ^ url ^
+    "\" class=\"" ^ clasz ^
+    "\" title=\"" ^ escape_html_tag title ^ "\">" ^
+    escape_html text ^ "</a>"
+  )
+
+type find_t = FoundNothing
+           | FoundOpen of string * string * string
+            | FoundClose of string * string * string * string
+           | FoundLink of string * string * string
+
+let _markup_paragraph dbh hostid text =
+  let find_earliest_markup text =
+    let convert_b_and_i elem =
+      if elem = "b" then "strong"
+      else if elem = "i" then "em"
+      else elem
+    in
+
+    try
+      let subs = Pcre.exec ~rex:markup_re text in
+      let first = Pcre.get_substring subs 1 in
+      let markup = Pcre.get_substring subs 2 in
+      let rest = Pcre.get_substring subs 3 in
+      if String.length markup > 2 &&
+       markup.[0] = '[' && markup.[1] = '[' then (
+         let link = markup_link dbh hostid markup in
+         FoundLink (first, link, rest)
+       )
+      else if String.length markup > 2 &&
+       markup.[0] = '<' && markup.[1] = '/' then (
+         let elem = String.sub markup 2 (String.length markup - 3) in
+         let elem = convert_b_and_i elem in
+         FoundClose (first, elem, rest, markup ^ rest)
+       )
+      else if String.length markup > 1 && markup.[0] = '<' then (
+       let elem = String.sub markup 1 (String.length markup - 2) in
+       let elem = convert_b_and_i elem in
+       FoundOpen (first, elem, rest)
+      )
+      else
+       failwith ("bad regexp: markup is '" ^ markup ^ "'");
+    with
+       Not_found -> FoundNothing
+  in
+
+  (* This code performs markup for a "paragraph" unit.  The strategy
+   * is to look for the next matching markup or link, process that, and
+   * then continue recursively with the remainder of the string.  We also
+   * maintain a stack which is our current level of nesting of <b>-like
+   * operators.
+   *)
+  let rec loop = function
+    | "", [] -> [""]                   (* base case *)
+
+    | text, ("nowiki" :: stack) ->
+       (*prerr_endline ("nowiki case: text = " ^ text);*)
+
+       (* If the top of the stack is <nowiki> then we're just looking for
+        * the closing </nowiki>, and nothing else matters. *)
+       (match Pcre.split ~pat:"</nowiki>" ~max:2 text with
+          | [] -> loop ("", stack)
+          | [x] -> escape_html x :: loop ("", stack)
+          | [x;y] -> escape_html x :: loop (y, stack)
+          | _ -> assert false)
+
+    | "", (x :: xs) ->                 (* base case, popping the stack *)
+       "</" :: x :: ">" :: loop ("", xs)
+
+    | text, [] ->
+       (*prerr_endline ("text = " ^ text ^ ", stack empty");*)
+
+       (* Look for the earliest possible matching markup.  Because the
+        * stack is empty, we're not looking for closing tags.
+        *)
+       (match find_earliest_markup text with
+          | FoundNothing -> escape_html text :: []
+          | FoundClose (first, elem, rest, _) ->
+              (* close tags ignored *)
+              escape_html first :: "&lt;/" :: escape_html elem :: "&gt;" ::
+                loop (rest, [])
+          | FoundOpen (first, elem, rest) when elem = "nowiki" ->
+              (* handle <nowiki> specially ... *)
+              escape_html first :: loop (rest, elem :: [])
+          | FoundOpen (first, elem, rest) ->
+              (* open tag - push it onto the stack *)
+              escape_html first :: "<" :: elem :: ">" :: loop (rest, [elem])
+          | FoundLink (first, link, rest) ->
+              escape_html first :: link :: loop (rest, [])
+       )
+
+    | text, ((x :: xs) as stack) ->
+       (*prerr_endline ("text = " ^ text ^ ", top of stack = " ^ x ^
+         ", stack size = " ^ string_of_int (List.length stack));*)
+
+       (* Look for the earliest possible matching markup. *)
+       (match find_earliest_markup text with
+          | FoundNothing -> escape_html text :: loop ("", stack)
+          | FoundClose (first, elem, rest, _) when x = elem ->
+              (* matching close tag *)
+              escape_html first :: "</" :: elem :: ">" :: loop (rest, xs)
+          | FoundClose (first, elem, rest, elem_rest) ->
+              (* non-matching close tag *)
+              escape_html first :: "</" :: x :: ">" :: loop (elem_rest, xs)
+          | FoundOpen (first, elem, rest) when elem = "nowiki" ->
+              (* handle <nowiki> specially ... *)
+              escape_html first :: loop (rest, elem :: stack)
+          | FoundOpen (first, elem, rest) ->
+              (* open tag - push it onto the stack *)
+              escape_html first :: "<" :: elem :: ">" ::
+                loop (rest, elem :: stack)
+          | FoundLink (first, link, rest) ->
+              (* pop everything off the stack first *)
+              escape_html first :: loop ("", stack) @ link :: loop (rest, [])
+       )
+  in
+
+  (*prerr_endline ("original markup = " ^ text);*)
+  let text = loop (text, []) in
+  let text = String.concat "" text in
+  (*prerr_endline ("after loop = " ^ text);*)
+  text
+
+let markup_paragraph dbh hostid text =
+  "<p>" ^ _markup_paragraph dbh hostid text ^ "</p>"
+
+let markup_heading dbh hostid level text =
+  let text = _markup_paragraph dbh hostid text in
+  sprintf "<h%d>%s</h%d>" level text level
+
+let markup_ul dbh hostid lines =
+  "<ul><li>" ^
+  String.concat "</li>\n<li>"
+    (List.map (fun t -> _markup_paragraph dbh hostid t) lines) ^
+  "</li></ul>"
+
+let markup_ol dbh hostid lines =
+  "<ol><li>" ^
+  String.concat "</li>\n<li>"
+    (List.map (fun t -> _markup_paragraph dbh hostid t) lines) ^
+  "</li></ol>"
+
+let markup_pre lines =
+  "<pre>\n" ^
+  String.concat "\n" (List.map Cgi_escape.escape_html lines) ^
+  "\n</pre>\n"
+
+
+type line_t = STBlank
+           | STHeading of int * string (* <h3>, <h4>, ... *)
+           | STUnnumbered of string list (* <ul> *)
+           | STNumbered of string list (* <ol> *)
+           | STPreformatted of string list (* <pre> *)
+           | STParagraph of string     (* Ordinary <p> *)
+
+let split_lines_re = Pcre.regexp "\\r?\\n"
+let blank_re = Pcre.regexp "^\\s*$"
+let heading_re = Pcre.regexp "^(=+)\\s+(.*)"
+let unnumbered_re = Pcre.regexp "^(\\*)\\s+(.*)"
+let numbered_re = Pcre.regexp "^(\\#)\\s+(.*)"
+let preformatted_re = Pcre.regexp "^ (.*)"
+
+let xhtml_of_content (dbh : Dbi.connection) hostid text =
+  (* Split the text into lines. *)
+  let lines = Pcre.split ~rex:split_lines_re text in
+  (* Iterate over the lines to isolate headers and paragraphs. *)
+  let lines =
+    List.map
+      (fun line ->
+        if Pcre.pmatch ~rex:preformatted_re line then (
+          let subs = Pcre.exec ~rex:preformatted_re line in
+          let line = Pcre.get_substring subs 1 in
+          STPreformatted [line]
+        )
+         else if Pcre.pmatch ~rex:blank_re line then
+           STBlank
+         else if Pcre.pmatch ~rex:heading_re line then (
+           let subs = Pcre.exec ~rex:heading_re line in
+           let count = String.length (Pcre.get_substring subs 1) + 2 in
+           let line = Pcre.get_substring subs 2 in
+           STHeading (count, line)
+         )
+         else if Pcre.pmatch ~rex:unnumbered_re line then (
+           let subs = Pcre.exec ~rex:unnumbered_re line in
+           let line = Pcre.get_substring subs 2 in
+           STUnnumbered [line]
+         )
+         else if Pcre.pmatch ~rex:numbered_re line then (
+           let subs = Pcre.exec ~rex:numbered_re line in
+           let line = Pcre.get_substring subs 2 in
+           STNumbered [line]
+         ) else
+           STParagraph line) lines in
+
+  (* Aggregate paragraphs and lists. *)
+  let rec loop = function
+      [] -> []
+    | STHeading (_, _) as h :: xs ->
+        h :: loop xs
+    | STUnnumbered lines1 :: STUnnumbered lines2 :: xs ->
+        loop (STUnnumbered (lines1 @ lines2) :: xs)
+    | STUnnumbered lines :: xs ->
+        STUnnumbered lines :: loop xs
+    | STNumbered lines1 :: STNumbered lines2 :: xs ->
+        loop (STNumbered (lines1 @ lines2) :: xs)
+    | STNumbered lines :: xs ->
+        STNumbered lines :: loop xs
+    | STPreformatted lines1 :: STPreformatted lines2 :: xs ->
+        loop (STPreformatted (lines1 @ lines2) :: xs)
+    | STPreformatted lines :: xs ->
+        STPreformatted lines :: loop xs
+    | STParagraph line1 :: STParagraph line2 :: xs ->
+        loop (STParagraph (line1 ^ " " ^ line2) :: xs)
+    | STParagraph line :: xs ->
+        STParagraph line :: loop xs
+    | STBlank :: xs ->
+        loop xs
+  in
+  let lines = loop lines in
+
+  (* Convert lines to XHTML. *)
+  let lines =
+    List.map
+      (function
+           STBlank -> assert false    (* Should never happen. *)
+         | STParagraph para ->
+             markup_paragraph dbh hostid para
+         | STHeading (level, text) ->
+             markup_heading dbh hostid level text
+         | STUnnumbered lines ->
+             markup_ul dbh hostid lines
+         | STNumbered lines ->
+             markup_ol dbh hostid lines
+        | STPreformatted lines ->
+            markup_pre lines
+      ) lines in
+
+  (* Return the lines. *)
+  String.concat "\n" lines
+
+(* Convert valid XHTML to plain text. *)
+let text_re = Pcre.regexp "<[^>]+>"
+let text_itempl = Pcre.subst " "
+
+let text_of_xhtml xhtml =
+  Pcre.replace ~rex:text_re ~itempl:text_itempl xhtml
diff --git a/scripts/wikilib.mli b/scripts/wikilib.mli
new file mode 100644 (file)
index 0000000..43f4cc4
--- /dev/null
@@ -0,0 +1,16 @@
+(* Library of functions useful for people implementing a Wiki.
+ * Written by Richard W.M. Jones <rich@merjis.com>.
+ * Copyright (C) 2004 Merjis Ltd.
+ * $Id: wikilib.mli,v 1.1 2004/09/07 10:14:10 rich Exp $
+ *)
+
+type genurl_error_t = GenURL_OK of string
+                   | GenURL_TooShort
+                   | GenURL_BadURL
+                   | GenURL_Duplicate of string
+
+val generate_url_of_title : Dbi.connection -> int -> string -> genurl_error_t
+
+val xhtml_of_content : Dbi.connection -> int -> string -> string
+
+val text_of_xhtml : string -> string
diff --git a/templates/admin/admin.html b/templates/admin/admin.html
new file mode 100644 (file)
index 0000000..0067a00
--- /dev/null
@@ -0,0 +1,53 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Wiki administration</title>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+<link rel="stylesheet" href="/_css/admin.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Wiki administration</h1>
+
+<h2>Wikis on this server</h2>
+
+<table id="hosts">
+::table(hosts)::
+<tr> <td> ID: ::id:: </td> <td>
+<a title="Administration page for this wiki" href="/_bin/admin/host.cmo?hostid=::id::">::canonical_hostname_html::</a>
+<a title="Edit server name, aliases for this wiki" href="/_bin/admin/edit_hostnames_form.cmo?hostid=::id::" class="alias">(alias
+::table(hostnames):: ::hostname_html:: ::end::
+)</a>
+</td>
+<td>
+::page_count:: page(s),
+<a title="Recent changes" href="http://::canonical_hostname_html_tag::/_recent">last modified ::last_modified_date_html::</a>
+</td>
+</tr>
+::end::
+</table>
+
+<p>
+<a href="/_bin/admin/create_host_form.cmo">Create a wiki ...</a>
+</p>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a></li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/admin/create_host_form.html b/templates/admin/create_host_form.html
new file mode 100644 (file)
index 0000000..6946007
--- /dev/null
@@ -0,0 +1,56 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Create new Wiki</title>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+<link rel="stylesheet" href="/_css/admin.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Create new Wiki</h1>
+
+<form action="create_host.cmo" method="post">
+<table id="host">
+<tr>
+<th> Server name: </th>
+<td> <input name="canonical_hostname" value="" size="40" /> </td>
+</tr>
+<tr>
+<th> Aliases: </th>
+<td> <textarea name="hostnames" rows="5" cols="40"></textarea> </td>
+</tr>
+<tr>
+<tr>
+<th> Title of Wiki: </th>
+<td> <input name="title" value="" size="60"/> </td>
+</tr>
+<tr>
+<td></td>
+<td>
+<input type="submit" name="save" value="Create Wiki"/>
+<input type="submit" name="cancel" value="Cancel"/>
+</td>
+</tr>
+</table>
+</form>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a></li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/admin/edit_emails_form.html b/templates/admin/edit_emails_form.html
new file mode 100644 (file)
index 0000000..4f58aae
--- /dev/null
@@ -0,0 +1,48 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Edit email change notification for ::canonical_hostname_html::</title>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+<link rel="stylesheet" href="/_css/admin.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Edit email change notification for ::canonical_hostname_html::</h1>
+
+<form action="edit_emails.cmo" method="post">
+<input type="hidden" name="hostid" value="::id::"/>
+<table id="host">
+<tr>
+<th> Email addresses: </th>
+<td> <textarea name="emails" rows="10" cols="40">::emails_html_textarea::</textarea> </td>
+</tr>
+<tr>
+<td></td>
+<td>
+<input type="submit" name="save" value="Save"/>
+<input type="submit" name="cancel" value="Cancel"/>
+</td>
+</tr>
+</table>
+</form>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a></li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/admin/edit_host_css_form.html b/templates/admin/edit_host_css_form.html
new file mode 100644 (file)
index 0000000..b719d5d
--- /dev/null
@@ -0,0 +1,45 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Edit global stylesheet for this wiki</title>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Edit global stylesheet for this wiki</h1>
+
+<form method="post" action="edit_host_css.cmo">
+<input type="hidden" name="hostid" value="::id::"/>
+<p>
+Editing the global stylesheet for this wiki:
+</p>
+
+<p>
+<textarea name="css" cols="80" rows="30">::css_html_textarea::</textarea>
+</p>
+
+<p>
+<input type="submit" value="Save changes"/>
+</p>
+</form>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/admin/edit_hostnames_form.html b/templates/admin/edit_hostnames_form.html
new file mode 100644 (file)
index 0000000..e5d20c4
--- /dev/null
@@ -0,0 +1,52 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Edit server name and aliases for ::canonical_hostname_html::</title>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+<link rel="stylesheet" href="/_css/admin.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Edit server name and aliases for ::canonical_hostname_html::</h1>
+
+<form action="edit_hostnames.cmo" method="post">
+<input type="hidden" name="hostid" value="::id::"/>
+<table id="host">
+<tr>
+<th> Server name: </th>
+<td> <input name="canonical_hostname" value="::canonical_hostname_html_tag::" size="40" /> </td>
+</tr>
+<tr>
+<th> Aliases: </th>
+<td> <textarea name="hostnames" rows="5" cols="40">::hostnames_html_textarea::</textarea> </td>
+</tr>
+<tr>
+<td></td>
+<td>
+<input type="submit" name="save" value="Save"/>
+<input type="submit" name="cancel" value="Cancel"/>
+</td>
+</tr>
+</table>
+</form>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a></li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/admin/host.html b/templates/admin/host.html
new file mode 100644 (file)
index 0000000..534c779
--- /dev/null
@@ -0,0 +1,87 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Wiki: ::canonical_hostname_html::</title>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+<link rel="stylesheet" href="/_css/admin.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Wiki: ::canonical_hostname_html::</h1>
+
+<table id="host">
+<tr>
+  <th> ID: </th>
+  <td> ::id:: </td>
+</tr>
+<tr>
+  <th> Server&nbsp;name: </th>
+  <td> ::canonical_hostname_html:: </td>
+</tr>
+<tr>
+  <th> Aliases: </th>
+  <td>
+    ::table(hostnames):: ::hostname_html:: <br/> ::end::
+    <a href="/_bin/admin/edit_hostnames_form.cmo?hostid=::id::">Edit
+      server name and aliases ...</a>
+  </td>
+</tr>
+<tr>
+  <th> Index page: </th>
+  <td> <a href="http://::canonical_hostname_html_tag::/">http://::canonical_hostname_html::/</a> </td>
+</tr>
+<tr>
+  <th> Page count: </th>
+  <td> ::page_count_html:: </td>
+</tr>
+<tr>
+  <th> Last modified: </th>
+  <td> ::last_modified_date_html:: </td>
+</tr>
+<tr>
+  <th> Wiki created: </th>
+  <td> ::creation_date_html:: </td>
+</tr>
+<tr>
+  <th> Pages:<br/>
+       <span class="explanation">(including edits and deleted pages)</span> </th>
+  <td> ::total_count_html:: </td>
+</tr>
+<tr>
+  <th> Email notification: </th>
+  <td>
+    ::table(emails):: ::name_html:: &lt;::email_html::&gt; <br/> ::end::
+    <a href="/_bin/admin/edit_emails_form.cmo?hostid=::id::">Edit
+      email notification ...</a>
+  </td>
+</tr>
+<tr>
+  <th> Global stylesheet: </th>
+  <td>
+    ::if(has_css)::This wiki has a global stylesheet.::end::
+    <a href="/_bin/admin/edit_host_css_form.cmo?hostid=::id::">Edit
+      global stylesheet ...</a>
+  </td>
+</tr>
+</table>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a></li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/create_form.html b/templates/create_form.html
new file mode 100644 (file)
index 0000000..51aa086
--- /dev/null
@@ -0,0 +1,56 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>This page does not exist yet</title>
+<meta name="robots" content="noindex,nofollow"/>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+<link rel="stylesheet" href="/_css/create.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>This page does not exist yet</h1>
+
+<p>
+Here you are being asked to create a new page and create some initial
+content for that page.
+</p>
+
+<form><input type="button" value="&lt;&lt; Go Back"
+             onclick="history.go (-1)"></form>
+
+<hr/>
+
+<form method="post" action="/_bin/create.cmo">
+<input type="hidden" name="title" value="::title_html_tag::"/>
+
+<table id="create">
+<tr>
+<th> Page title: </th>
+<td> ::title_html:: </td>
+</tr>
+<tr>
+<th> Description: </th>
+<td> <input name="description" value="::title_html_tag::" size="60"/> </td>
+</tr>
+<tr>
+<td></td>
+<td> <p class="explanation">
+     (Enter a short description of the page here for search engines
+      and directories to use).</p> </td>
+</tr>
+<tr>
+<td></td>
+<td> <input type="submit" value="Create new page - only do this if you have something to put on this page" /> </td>
+</tr>
+</table>
+
+</form>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/delete_file_form.html b/templates/delete_file_form.html
new file mode 100644 (file)
index 0000000..09c22d9
--- /dev/null
@@ -0,0 +1,44 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Delete file</title>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Delete file</h1>
+
+<form method="post" action="/_bin/delete_file.cmo">
+<input type="hidden" name="id" value="::id::"/>
+<p>
+Are you sure you want to delete this file?
+<input type="submit" name="yes" value="Delete it"/>
+<input type="submit" name="no" value="Cancel"/>
+</p>
+</form>
+
+<p>
+<img src="/_graphics/file.png" border="0" width="20" height="22" alt="File icon"/>
+Name: <strong>::name_html::</strong>
+</p>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/delete_image_form.html b/templates/delete_image_form.html
new file mode 100644 (file)
index 0000000..b3e7916
--- /dev/null
@@ -0,0 +1,42 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Delete image</title>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Delete image</h1>
+
+<form method="post" action="/_bin/delete_image.cmo">
+<input type="hidden" name="id" value="::id::"/>
+<p>
+Are you sure you want to delete this image?
+<input type="submit" name="yes" value="Delete it"/>
+<input type="submit" name="no" value="Cancel"/>
+</p>
+</form>
+
+<img src="/_image/::name_html_tag::"
+     width="::width::" height="::height::" alt="::alt_html_tag::"/>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/diff.html b/templates/diff.html
new file mode 100644 (file)
index 0000000..42776c1
--- /dev/null
@@ -0,0 +1,40 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Diff</title>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Diff</h1>
+
+<p>
+Differences between
+<a href="/::page_html_tag::?version=::version::">version ::version::</a> and
+<a href="/::page_html_tag::?version=::old_version::">version ::old_version::</a>.
+</p>
+
+<pre>
+::diff_html::
+</pre>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/edit.html b/templates/edit.html
new file mode 100644 (file)
index 0000000..511319d
--- /dev/null
@@ -0,0 +1,84 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>::title_html:: [edit]</title>
+<meta name="robots" content="noindex,nofollow"/>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+<link rel="stylesheet" href="/_css/editor.css" type="text/css" title="Standard"/>
+<script src="/_js/editor.js" type="text/javascript"></script>
+</head><body>
+
+<h1>::title_html:: [edit]</h1>
+
+<form method="post" action="edit">
+<input type="hidden" name="id" value="::id::"/>
+
+<p>
+Description:
+<input name="description" value="::description_html_tag::" size="60"/>
+</p>
+
+<p>
+Redirect to (if given, page contents are ignored):
+<select name="redirect">
+<option value="">None (ordinary page)</option>
+::table(redirects)::<option value="::url_html_tag::" ::if(selected)::selected="selected"::end::>::title_html:: (<code>/::url_html::</code>)</option>::end::
+</select>
+</p>
+
+::if(has_errors)::
+<div id="errors">
+<p><strong>Errors:</strong></p>
+<ul>
+::table(errors)::<li>::error_html::</li>
+::end::</ul>
+</div>
+::end::
+
+<p class="insert">
+<input class="insert" type="submit" name="action_insert_0" value="Insert new section here"/>
+</p>
+
+::table(contents)::
+<a name="::ordering::"/>
+<div class="action">
+::if(msie)::
+<input type="submit" class="action" name="action_moveup_::ordering::" value="Up" title="Move this section up"/><br/>
+<input type="submit" class="action" name="action_movedn_::ordering::" value="Down" title="Move this section down"/><br/><br/>
+<input type="submit" class="action" name="action_delete_::ordering::" value="Delete" title="Delete this section"/><br/><br/>
+<input type="submit" class="action" name="save" value="Save" title="Save whole page"/><br/><br/>
+<input type="submit" class="action" name="cancel" value="Cancel" title="Cancel all edits"/><br/>
+<a href="/_static/markup.html" target="_blank" class="help_link">(Editing&nbsp;help)</a>
+::else::
+<button type="submit" class="action" name="action_moveup_::ordering::" value="1" title="Move this section up"><img src="/_graphics/arrow-up.png" width="10" height="10" alt=""/> Up</button><br/>
+<button type="submit" class="action" name="action_movedn_::ordering::" value="1" title="Move this section down"><img src="/_graphics/arrow-down.png" width="10" height="10" alt=""/> Down</button><br/><br/>
+<button type="submit" class="action" name="action_delete_::ordering::" value="1" title="Delete this section"><img src="/_graphics/cross.png" width="10" height="10" alt=""/> Delete</button><br/><br/>
+<button type="submit" class="action" name="save" value="1" title="Save whole page"><strong>Save</strong></button><br/><br/>
+<button type="submit" class="action" name="cancel" value="1" title="Cancel all edits">Cancel</button><br/>
+<a href="/_static/markup.html" target="_blank" class="help_link">(Editing&nbsp;help)</a>
+::end::
+</div>
+<input class="heading" name="sectionname_::ordering::" value="::sectionname_html_tag::" size="40"/>
+<textarea id="content_::ordering::" name="content_::ordering::" rows="12" cols="80" onkeypress="update_preview ('content_::ordering::', 'preview_::ordering::')">::content_html_textarea::</textarea>
+<div class="preview" id="preview_::ordering::"><noscript>(If you had Javascript, you would see a preview of your edits here)</noscript></div>
+<script type="text/javascript"><!--
+update_preview_now ('content_::ordering::', 'preview_::ordering::');
+//--></script>
+<abbr class="css_id" title="Assign a stylesheet ID to this block of text to enable further styling">CSS id</abbr>: <input class="css_id" name="divname_::ordering::" value="::divname_html_tag::" size="8"/>
+
+<p class="insert">
+<input class="insert" type="submit" name="action_insert_::ordering::" value="Insert new section here"/>
+</p>
+::end::
+
+</form>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/edit_conflict.html b/templates/edit_conflict.html
new file mode 100644 (file)
index 0000000..234c347
--- /dev/null
@@ -0,0 +1,51 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Edit conflict</title>
+<meta name="robots" content="noindex,nofollow"/>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+<link rel="stylesheet" href="/_css/editor.css" type="text/css" title="Standard"/>
+<script src="/_js/editor.js" type="text/javascript"></script>
+</head><body>
+
+<h1>Edit conflict</h1>
+
+<p>
+Someone else edited that page while you were editing it.  This leads
+to an <strong>edit conflict</strong> which you have to fix by hand.
+</p>
+
+<ul>
+<li> <a target="_blank" href="/::url_html_tag::/edit">Edit the page again</a>
+  (opens in a new window so you can refer back to this page) </li>
+<li> <a href="/::url_html_tag::">Abandon your changes</a> </li>
+</ul>
+
+<table id="edit_conflict" width="100%">
+<tr>
+<th class="other"> Other person's edits </th>
+<th class="our"> Your edits </th>
+</tr>
+<tr>
+<td class="other" width="50%">
+<pre>
+::other_diff_html::
+</pre>
+</td>
+<td class="our" width="50%">
+<pre>
+::our_diff_html::
+</pre>
+</td>
+</tr>
+</table>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/edit_page_css_form.html b/templates/edit_page_css_form.html
new file mode 100644 (file)
index 0000000..820592e
--- /dev/null
@@ -0,0 +1,45 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Edit stylesheet for this page</title>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Edit stylesheet for this page</h1>
+
+<form method="post" action="/_bin/edit_page_css.cmo">
+<input type="hidden" name="page" value="::page_html_tag::"/>
+<p>
+Editing the stylesheet for <a href="/::page_html_tag::">::page_html::</a>:
+</p>
+
+<p>
+<textarea name="css" cols="80" rows="30">::css_html_textarea::</textarea>
+</p>
+
+<p>
+<input type="submit" value="Save changes"/>
+</p>
+</form>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/files.html b/templates/files.html
new file mode 100644 (file)
index 0000000..14c0ec3
--- /dev/null
@@ -0,0 +1,73 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Files</title>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+<link rel="stylesheet" href="/_css/files.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Files</h1>
+
+<ul class="menu">
+<li><a href="/_bin/upload_file_form.cmo">Upload a new file</a> |</li>
+::if(deleted)::
+<li><a href="/_files">Show live files</a> |</li>
+<li>Show deleted files</li>
+::else::
+<li>Show live files |</li>
+<li><a href="/_files?deleted=1">Show deleted files</a></li>
+::end::
+</ul>
+
+<table id="files">
+::table(files)::
+<tr>
+<td rowspan="2"> <a href="/_file/::name_html_tag::?version=::id::"><img src="/_graphics/file.png" border="0" width="20" height="22" alt="File icon" title="Download file"/></a> </td>
+<td>
+<p>
+Name: <strong>::name_html::</strong> (size: ::ksize_html:: K) <br/>
+::if(is_deleted)::
+(This file has been deleted or replaced)
+::else::
+<code>[[file&#x3a;::name_html::]]</code>
+::end::
+</p>
+</td>
+</tr>
+<tr>
+<td class="actions">
+<ul class="menu">
+::if(is_deleted)::
+<li><a href="/_bin/undelete_file_form.cmo?id=::id_url::">Restore</a></li>
+::else::
+<li><a href="/_bin/edit_file_form.cmo?id=::id_url::">Edit</a> |</li>
+<li><a href="/_bin/upload_file_form.cmo?id=::id_url::">Replace</a> |</li>
+<li><a href="/_bin/delete_file_form.cmo?id=::id_url::">Delete</a></li>
+::end::
+</ul>
+</td>
+</tr>
+::end::
+</table>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/history.html b/templates/history.html
new file mode 100644 (file)
index 0000000..dde437c
--- /dev/null
@@ -0,0 +1,42 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Versions of this page</title>
+<meta name="robots" content="noindex,nofollow"/>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Versions of this page</h1>
+
+<ul id="history">
+::table(history)::
+<li>
+<span class="date">::last_modified_date_html::</span>
+(<a href="/::url_html_tag::/diff?version=::version::">diff</a>)
+::if(is_live)::<a href="/::url_html_tag::">::title_html::</a> (live)::else::<a href="/::url_html_tag::?version=::version::">::title_html::</a>::end::
+::if(has_logged_ip)::(from ::logged_ip_html::)::end::
+</li>
+::end::
+</ul>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/images.html b/templates/images.html
new file mode 100644 (file)
index 0000000..48e0fec
--- /dev/null
@@ -0,0 +1,75 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Images</title>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+<link rel="stylesheet" href="/_css/images.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Images</h1>
+
+<ul class="menu">
+<li><a href="/_bin/upload_image_form.cmo">Upload a new image</a> |</li>
+::if(deleted)::
+<li><a href="/_images">Show live images</a> |</li>
+<li>Show deleted images</li>
+::else::
+<li>Show live images |</li>
+<li><a href="/_images?deleted=1">Show deleted images</a></li>
+::end::
+</ul>
+
+<table id="images">
+::table(images)::
+<tr>
+<td rowspan="2" class="thumbnail"> ::if(has_thumbnail):: <a href="/_image/::name_html_tag::?version=::id::"><img src="/_image/::name_html_tag::?thumbnail=1&version=::id::" width="::tn_width::" height="::tn_height::" alt="::alt_html_tag::" title="Thumbnail of ::name_html_tag::.  Click image to show full-size version."/></a> ::else:: No thumbnail.  <a href="/_image/::name_html_tag::?version=::id::">Show full-size version.</a> ::end:: </td>
+<td>
+<p>
+Name: <strong>::name_html::</strong> (size: ::ksize_html:: K) <br/>
+::width_html::x::height_html:: <br/>
+ALT text: ::alt_html:: <br/>
+::if(is_deleted)::
+(This image has been deleted or replaced)
+::else::
+<code>[[image&#x3a;::name_html::]]</code>
+::end::
+</p>
+</td>
+</tr>
+<tr>
+<td class="actions">
+<ul class="menu">
+::if(is_deleted)::
+<li><a href="/_bin/undelete_image_form.cmo?id=::id_url::">Restore</a></li>
+::else::
+<li><a href="/_bin/edit_image_form.cmo?id=::id_url::">Edit</a> |</li>
+<li><a href="/_bin/upload_image_form.cmo?id=::id_url::">Replace</a> |</li>
+<li><a href="/_bin/delete_image_form.cmo?id=::id_url::">Delete</a></li>
+::end::
+</ul>
+</td>
+</tr>
+::end::
+</table>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/ok_error.html b/templates/ok_error.html
new file mode 100644 (file)
index 0000000..8fa7653
--- /dev/null
@@ -0,0 +1,45 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" >
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+::if(has_title)::<title>::title_html::</title>::end::
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+</head>
+<body>
+
+::if(has_title)::
+<h1>::title_html::</h1>
+::end::
+
+<div id="mid">
+<div id="cnt">
+
+::if(has_icon)::
+<img style="float: left; clear:both; margin:10px;"
+     alt="::icon_alt_html_tag::"
+     src="::icon_html_tag::">
+::end::
+<p>::message_html::</p>
+<hr>
+<table><tr>
+::if(has_back_button)::
+<td><form><input type="button" value="&lt;&lt; Go Back"
+                 onclick="history.go (-1)"></form></td>
+::end::
+::if(has_close_button)::
+<td><form><input type="button" value="Close Window"
+                 onclick="top.close ()"></form></td>
+::end::
+::table(buttons)::
+<td><form method="::method_html_tag::" action="::action_html_tag::">
+::table(params)::
+<input type="hidden" name="::name_html_tag::" value="::value_html_tag::">
+::end::
+<input type="submit" value="::name_html_tag::"></form></td>
+::end::
+
+</div>
+</div>
+
+</body>
+</html>
diff --git a/templates/page.html b/templates/page.html
new file mode 100644 (file)
index 0000000..0a94033
--- /dev/null
@@ -0,0 +1,65 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>::title_html::</title>
+::if(is_old_version)::<meta name="robots" content="noindex,nofollow"/>::end::
+<meta name="description" content="::description_html_tag::" />
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+::if(has_host_css)::<link rel="stylesheet" href="/_global.css" type="text/css" title="Standard"/>::end::
+::if(has_page_css)::<link rel="stylesheet" href="/::page_html_tag::/styles.css::if(is_old_version)::?version=::old_version::::end::" type="text/css" title="Standard"/>::end::
+</head><body>
+
+<h1>::title_html::</h1>
+
+::if(redirected)::
+<p id="redirected"><em>(Redirected from <a href="/::original_page_html_tag::?no_redirect=1">::original_page_html::</a>)</em></p>
+::end::
+
+::if(is_old_version)::
+<div id="old_version">
+<p>You are seeing an old version of this page.</p>
+<ul>
+<li> Go to the <a href="/::page_html_tag::">live version</a> of this page.</li>
+<li> See other <a href="/::page_html_tag::/history">old versions</a> of this page.</li>
+<li> See the <a href="/::page_html_tag::/diff?version=::old_version::">differences</a> between this version and the previous version of this page.</li>
+<li> <a href="/_bin/restore_form.cmo?page=::page_url::&version=::old_version::">Restore</a> this version live.</li>
+</ul>
+</div>
+::end::
+
+::table(sections)::
+::if(has_divname)::<div id="::divname_html_tag::">::end::<p class="edit_link">[<a href="/::page_html_tag::/edit#::ordering::" title="Edit this section">edit</a>]</p><a name="::sectionname_html_tag::"></a><h2>::sectionname_html::</h2>
+::content::
+::if(has_divname)::</div>::end::::end::
+
+<ul id="topmenu" class="menu">
+<li> <a href="/::page_html_tag::/edit"><strong>Edit&nbsp;this&nbsp;page</strong></a> | </li>
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> | </li>
+<li> <a href="/::page_html_tag::/history">Versions&nbsp;of&nbsp;this&nbsp;page</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/::page_html_tag::/edit"><strong>Edit&nbsp;this&nbsp;page</strong></a> | </li>
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> | </li>
+<li> <a href="/::page_html_tag::/history">Versions&nbsp;of&nbsp;this&nbsp;page</a> | </li>
+<li> <a href="/_images">Images</a> | </li>
+<li> <a href="/_files">Files</a> | </li>
+<li> <a href="/::page_html_tag::/editcss">Edit&nbsp;stylesheet&nbsp;for&nbsp;this&nbsp;page</a> | </li>
+<li> <a href="/_bin/admin/admin.cmo">Wiki&nbsp;administration</a> </li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> Last change: ::last_modified_date_html:: | </li>
+<li> <a href="/copyright">Copyright &copy; 2004</a> | </li>
+<li> Powered by <a href="http://sandbox.merjis.com/">::cocanwiki_package_html:: ::cocanwiki_version_html::</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/page_404.html b/templates/page_404.html
new file mode 100644 (file)
index 0000000..57b3348
--- /dev/null
@@ -0,0 +1,37 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Page not found</title>
+<meta name="robots" content="noindex,nofollow"/>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+::if(has_host_css)::<link rel="stylesheet" href="/_global.css" type="text/css" title="Standard"/>::end::
+</head><body>
+
+<h1>Page not found</h1>
+
+<p>
+Search our site for this page:
+</p>
+
+<form method="post" action="/_bin/search.cmo">
+<p>
+<input name="q" value="::search_terms_html_tag::" size="50"/>
+<input type="submit" value="Search"/>
+</p>
+</form>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/recent.html b/templates/recent.html
new file mode 100644 (file)
index 0000000..83c30f1
--- /dev/null
@@ -0,0 +1,43 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Recent changes</title>
+<meta name="robots" content="noindex,nofollow"/>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Recent changes</h1>
+
+<ul id="recent_changes">
+::table(recent_changes)::
+<li>
+<span class="date">::last_modified_date_html::</span>
+(<a href="/::url_html_tag::/diff?version=::version::">diff</a>)
+(<a href="/::url_html_tag::/history">history</a>)
+::if(is_live)::<a href="/::url_html_tag::">::title_html::</a> (live)::else::<a href="/::url_html_tag::?version=::version::">::title_html::</a>::end::
+::if(has_logged_ip)::(from ::logged_ip_html::)::end::
+</li>
+::end::
+</ul>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/restore_form.html b/templates/restore_form.html
new file mode 100644 (file)
index 0000000..7312c2b
--- /dev/null
@@ -0,0 +1,46 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Restore page</title>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Restore page</h1>
+
+<form method="post" action="/_bin/restore.cmo">
+<input type="hidden" name="version" value="::version::"/>
+<input type="hidden" name="page" value="::page_html_tag::"/>
+<p>
+Are you sure you want to restore this old version?  Please
+check the differences below to see the changes which you will
+be deleting.
+<input type="submit" name="yes" value="Restore"/>
+<input type="submit" name="no" value="Cancel"/>
+</p>
+</form>
+
+<pre>
+::diff_html::
+</pre>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/sitemap.html b/templates/sitemap.html
new file mode 100644 (file)
index 0000000..523a4bf
--- /dev/null
@@ -0,0 +1,36 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Sitemap</title>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Sitemap</h1>
+
+<ul id="sitemap">
+::table(sitemap)::<li><a href="/::url_html_tag::">::title_html::</a>
+<p class="content">::if(has_content)::::content::::else::::description_html::::end::</p>
+<p class="info"><a href="/::url_html_tag::">::hostname::/::url_html::</a> - ::last_modified_date_html::</p></li>
+::end::</ul>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a></li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/undelete_file_form.html b/templates/undelete_file_form.html
new file mode 100644 (file)
index 0000000..981f895
--- /dev/null
@@ -0,0 +1,44 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Restore file</title>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Restore file</h1>
+
+<form method="post" action="/_bin/undelete_file.cmo">
+<input type="hidden" name="id" value="::id::"/>
+<p>
+Are you sure you want to restore this file?
+<input type="submit" name="yes" value="Restore it"/>
+<input type="submit" name="no" value="Cancel"/>
+</p>
+</form>
+
+<p>
+<img src="/_graphics/file.png" border="0" width="20" height="22" alt="File icon"/>
+Name: <strong>::name_html::</strong>
+</p>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/undelete_image_form.html b/templates/undelete_image_form.html
new file mode 100644 (file)
index 0000000..b2e475a
--- /dev/null
@@ -0,0 +1,42 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Restore image</title>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Restore image</h1>
+
+<form method="post" action="/_bin/undelete_image.cmo">
+<input type="hidden" name="id" value="::id::"/>
+<p>
+Are you sure you want to restore this image?
+<input type="submit" name="yes" value="Restore it"/>
+<input type="submit" name="no" value="Cancel"/>
+</p>
+</form>
+
+<img src="/_image/::name_html_tag::?version=::id::"
+     width="::width::" height="::height::" alt="::alt_html_tag::"/>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/upload_file_form.html b/templates/upload_file_form.html
new file mode 100644 (file)
index 0000000..b4dc980
--- /dev/null
@@ -0,0 +1,74 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Upload file</title>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+<link rel="stylesheet" href="/_css/create.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Upload file</h1>
+
+<form method="post" action="/_bin/upload_file.cmo" enctype="multipart/form-data" name="f">
+<table id="create">
+<tr>
+<td></td>
+<td class="explanation">
+You need to provide a short name for this file.  Use only lowercase letters, numbers, dots ('.'), dashed ('-') and underscores ('_').
+</td>
+</tr>
+<tr>
+<th>File Name:</th>
+<td> <input name="name" value="new_file.jpg" size="40" maxlength="40" /> </td>
+</tr>
+
+<tr>
+<td></td>
+<td class="explanation">
+Upload the file itself.
+</td>
+</tr>
+<tr>
+<th>File:</th>
+<td> <input type="file" name="file" value="" /> </td>
+</tr>
+
+<tr>
+<td></td>
+<td class="explanation">
+The title appears when users hover over a file link with their mouse.  It is not required.
+</td>
+</tr>
+<tr>
+<th>Title:</th>
+<td> <input name="title" value="" size="70" /> </td>
+</tr>
+
+<tr>
+<td></td>
+<td><input type="submit" value="Upload" /></td>
+</tr>
+
+</table>
+</form>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/upload_image_form.html b/templates/upload_image_form.html
new file mode 100644 (file)
index 0000000..0d131f5
--- /dev/null
@@ -0,0 +1,107 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>Upload image</title>
+<meta name="author" content="http://www.merjis.com/" />
+<link rel="stylesheet" href="/_css/standard.css" type="text/css" title="Standard"/>
+<link rel="stylesheet" href="/_css/create.css" type="text/css" title="Standard"/>
+</head><body>
+
+<h1>Upload image</h1>
+
+<form method="post" action="/_bin/upload_image.cmo" enctype="multipart/form-data" name="f">
+<table id="create">
+<tr>
+<td></td>
+<td class="explanation">
+You need to provide a short name for this image.  Use only lowercase letters, numbers and underscores ('_').  JPEG images should end in <code>.jpg</code> and GIF images should end in <code>.gif</code>.
+</td>
+</tr>
+<tr>
+<th>Image Name:</th>
+<td> <input name="name" value="new_image.jpg" size="40" maxlength="40" /> </td>
+</tr>
+
+<tr>
+<td></td>
+<td class="explanation">
+Upload the image itself.
+</td>
+</tr>
+<tr>
+<th>Image:</th>
+<td> <input type="file" name="image" value="" /> </td>
+</tr>
+
+<tr>
+<td></td>
+<td class="explanation">
+The 'Alt'(-ernate) text is what blind users and search engines read.  It should explain what the image is.  You must provide this text to meet accessibility laws and allow search engines to discover the content of images.
+</td>
+</tr>
+<tr>
+<th>Alt text:</th>
+<td> <input name="alt" value="" size="70" /> </td>
+</tr>
+
+<tr>
+<td></td>
+<td class="explanation">
+The title appears when users hover over an image with their mouse.  It is not required.
+</td>
+</tr>
+<tr>
+<th>Title:</th>
+<td> <input name="title" value="" size="70" /> </td>
+</tr>
+
+<tr>
+<td></td>
+<td class="explanation">
+The 'Longdesc'(-ription) gives a longer, complete description of the content of the image, for blind users.  If your Alt text adequately describes the image, then it is not required.
+</td>
+</tr>
+<tr>
+<th>Longdesc:</th>
+<td> <textarea name="longdesc" rows="5" cols="80"></textarea> </td>
+</tr>
+
+<tr>
+<td></td>
+<td class="explanation">
+Class is used with stylesheets.  If in doubt, leave it blank.
+</td>
+</tr>
+<tr>
+<th>Class:</th>
+<td> <input name="class" value="" size="20" /> </td>
+</tr>
+
+<tr>
+<td></td>
+<td><input type="submit" value="Upload" /></td>
+</tr>
+
+</table>
+</form>
+
+<ul id="topmenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<ul id="bottommenu" class="menu">
+<li> <a href="/">Home&nbsp;page</a> | </li>
+<li> <a href="/_sitemap">Sitemap</a> | </li>
+<li> <a href="/_recent">Recent&nbsp;changes</a> </li>
+</ul>
+
+<hr/>
+
+<ul id="footer" class="menu">
+<li> <a href="/copyright">Copyright &copy; 2004</a> </li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file