JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
generic editable site
authorJason Woofenden <jason@jasonwoof.com>
Mon, 7 Jun 2010 06:42:12 +0000 (02:42 -0400)
committerJason Woofenden <jason@jasonwoof.com>
Thu, 4 Nov 2010 10:08:48 +0000 (06:08 -0400)
22 files changed:
.gitignore [new file with mode: 0644]
.gitmodules
.htaccess [new file with mode: 0644]
.sync [new file with mode: 0644]
admin.html [new file with mode: 0644]
admin.php [new file with mode: 0644]
admin_images.html [new file with mode: 0644]
admin_images.php [new file with mode: 0644]
admin_images.sql [new file with mode: 0644]
admin_login.html [new file with mode: 0644]
admin_login.php [new file with mode: 0644]
admin_pages.html [new file with mode: 0644]
admin_pages.php [new file with mode: 0644]
admin_pages.sql [new file with mode: 0644]
code/ckeditor [new submodule]
code/cms.php [new file with mode: 0644]
code/config.php [new file with mode: 0644]
code/wfpl
logout.php [new file with mode: 0644]
run.php [new symlink]
style.css [new file with mode: 0644]
template.html [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..442b74a
--- /dev/null
@@ -0,0 +1,3 @@
+*.tar.gz
+*.tgz
+cms_images
index a7b64d8..8e7aaba 100644 (file)
@@ -1,3 +1,6 @@
 [submodule "code/wfpl"]
        path = code/wfpl
        url = git://gitorious.org/wfpl/wfpl.git
+[submodule "code/ckeditor"]
+       path = code/ckeditor
+       url = git://gitorious.org/jasonwoof/ckeditor.git
diff --git a/.htaccess b/.htaccess
new file mode 100644 (file)
index 0000000..c2a8568
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,15 @@
+# php_value post_max_size 205M
+# php_value upload_max_filesize 200M
+php_flag register_globals off
+php_flag magic_quotes_gpc off
+Options -MultiViews
+DirectorySlash Off
+AddDefaultCharset UTF-8
+RewriteEngine  on
+RewriteRule    ^[^/.]*$  /run.php [L]
+RewriteRule    ^style_[0-9]*.css$  /style.css [L]
+
+<FilesMatch "\.(css|jpg|png)$">
+       ExpiresActive On
+       ExpiresDefault A31536000
+</FilesMatch>
diff --git a/.sync b/.sync
new file mode 100644 (file)
index 0000000..90638d8
--- /dev/null
+++ b/.sync
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+verbose git push
diff --git a/admin.html b/admin.html
new file mode 100644 (file)
index 0000000..2cf3368
--- /dev/null
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+
+<html lang="en">
+<head>
+       <title><!--~main_title show {~-->~this_host~ Administration<!--~}~--></title>
+       <link rel="stylesheet" href="style.css" type="text/css" />
+</head>
+
+<body>
+       <!--~main_body show {~-->
+               <h2>~this_host~ Administration</h2>
+
+               <p><a href="admin_images">Upload/edit images</a></p>
+
+               <p><a href="admin_pages">Add/edit pages</a></p>
+
+               <p><a href="logout">Log out</a></p>
+       <!--~}~-->
+</body>
+</html>
diff --git a/admin.php b/admin.php
new file mode 100644 (file)
index 0000000..8c115ce
--- /dev/null
+++ b/admin.php
@@ -0,0 +1,11 @@
+<?php
+
+require_once('code/wfpl/session.php');
+
+function admin_main() {
+       if(!logged_in_as_admin()) {
+               $_REQUEST['url'] = this_url();
+               return 'admin_login';
+       }
+       tem_set('this_host', this_host());
+}
diff --git a/admin_images.html b/admin_images.html
new file mode 100644 (file)
index 0000000..3ba2adb
--- /dev/null
@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+       <title><!--~main_title show {~-->Images<!--~}~--></title>
+       <link rel="stylesheet" href="style.css" type="text/css">
+</head>
+
+<body>
+<!--~main_body show {~-->
+       <!--~display {~-->
+               <h2>Image details</h2>
+
+               <p><a href="admin_images?admin_images_new=1">Add another image</a></p>
+
+               <p><a href="admin_images">Back to images</a></p>
+
+               <p><a href="admin_images?admin_images_edit_id=~id attr~">Edit</a></p>
+
+               <!--~references {~--><p><strong>Note:</strong> This image appears on the following page~count s~: <!--~data {~--><a href="~filename attr~">~title html~</a><!--~ sep {~-->, <!--~}~--><!--~}~--></p><!--~}~-->
+
+               <h3>How to put one of these images on a page</h3>
+               <ol>
+                       <li>Choose which size you want. If you want a size that is not shown below, <a href="admin_images?admin_images_edit_id=~id attr~">edit this image</a> and add a new display size.</li>
+                       <li>Above the image (at your chosen size) you will see HTML code with a light gray background. Drag (or triple click) to select the HTML code for your desired alignment, and copy it to the clipboard.</li>
+                       <li>Open the page editor in another tab (so you can refer to these instructions while you use it.)</li>
+                       <li>In the page editor, type "HERE HERE HERE" where you'd like to insert the image, then click the "Source" button at the top/left of the editor.</li>
+                       <li>Now you will see the HTML code for the page. Find where it says "HERE HERE HERE", select those three words, then paste over them. You can paste with Ctrl-V (on Mac that's Command-V) or with the Edit menu, but the right-click paste may not work.</li>
+                       <li>Click the "Source" button again to get back to the normal view. Your image should now be visible in the editor (though you may have to scroll down to see it.)</li>
+               </ol>
+               
+               <!--~smaller {~-->
+                       <div><strong>On the right: </strong>(with the page text to the left of it)</div><div><code class="html">&lt;span class="float_right"&gt;&lt;img src="~src~" width="~width~" height="~height~" alt="" /&gt;~caption html~&lt;/span&gt;</code></div>
+                       <div><strong>On the left: </strong>(with the page text to the right of it)</div><div><code class="html">&lt;span class="float_left"&gt;&lt;img src="~src~" width="~width~" height="~height~" alt="" /&gt;~caption html~&lt;/span&gt;</code></div>
+                       <div><strong>Centered: </strong>(with nothing to either side)</div><div> <code class="html">&lt;div class="mid_pic" style="width: ~width~px"&gt;&lt;img src="~src~" width="~width~" height="~height~" alt="" /&gt;~caption html~&lt;/div&gt;</code></div>
+                       <div><img src="~src~" alt="" /></div>
+               <!--~}~-->
+
+               <div style="margin-top: 30px"><strong>Full Size (centered): </div><div></strong> <code class="html">&lt;div class="mid_pic"&gt;&lt;img src="~image image_src~" width="~image image_width~" height="~image image_height~" alt="" /&gt;~caption html~&lt;/div&gt;</code></div>
+                       <div><img src="~image image_src~" width="~image image_width~" height="~image image_height~" alt="" /></div>
+
+               <!--~no_sizes {~-->
+                       <p>To display this image smaller, <a href="admin_images?admin_images_edit_id=~id attr~">edit this image</a> and enter display size(s).</p>
+               <!--~}~-->
+
+
+
+               <p><a href="admin_images">Back to images</a></p>
+
+               <p><a href="admin_images?admin_images_edit_id=~id attr~">Edit</a></p>
+       <!--~}~-->
+
+       <!--~form {~-->
+               <h2><!--~new_msg {~-->Add a new image<!--~}~--><!--~edit_msg {~-->Edit image "~name html~"<!--~}~--></h2>
+
+               <form action="admin_images" method="post" enctype="multipart/form-data"><!--~editing {~--><div style="display: none"><input type="hidden" name="admin_images_edit_id" value="~admin_images_edit_id attr~"></div><!--~}~--><input type="hidden" name="MAX_FILE_SIZE" value="~upload_max_filesize~">
+                       <div style="display: none"><input type="hidden" name="" value="~ attr~"></div>
+
+                       <div class="caption">Image</div>
+                       <div class="field"><input type="file" name="image"><input type="hidden" name="old_image" value="~image attr~"></div>
+
+                       <div class="caption">Name (optional)</div>
+                       <div class="field_notes">This name is only displayed on the image administration page.</div>
+                       <div class="field"><input type="text" name="name" value="~name attr~"></div>
+
+                       <div class="caption">Caption (optional)</div>
+                       <div class="field_notes">Here's some symbols you might want to paste in: &copy; &nbsp; &mdash; &nbsp; &ndash;</div>
+                       <div class="field"><input type="text" name="caption" value="~caption attr~"></div>
+
+                       <div class="caption">Sizes</div>
+                       <div class="field_notes">(Enter the width and height (in pixels) at which you'd like to use this image. Put an x between them with no spaces, so it looks like this: 100x200. The image will be scaled so it maintains it's aspect ratio (shape) and just fits inside those dimentions. You can use this image at multiple different sizes, by entering a different set of dimensions (WIDTHxHEIGHT) on each line)</div>
+                       <div class="field"><textarea rows="9" cols="22" name="sizes">~sizes html~</textarea></div>
+
+                       <div class="caption">&nbsp;</div>
+                       <div class="field"><input type="submit" name="save" value="Save"></div>
+
+               </form>
+
+               <div class="caption">&nbsp;</div>
+               <div class="field"><!--~jam_edit_id {~--><a href="admin_images?admin_images_id=~admin_images_edit_id~"><!--~}~--><!--~jam_edit_id unset {~--><a href="admin_images"><!--~}~-->Cancel</a></div>
+       <!--~}~-->
+
+       <!--~listings {~-->
+               <h2>Images Listing</h2>
+
+               <!--~populated_listing {~-->
+                       <p><a href="admin_images?admin_images_new=1">[Add a new image]</a></p>
+
+                       <table cellspacing="0" cellpadding="4" border="1" summary="">
+                               <th>Image</th><th>Name</th><th>Caption</th><th>&nbsp;</th><!--~rows {~-->
+                               <tr>
+                                       <td class="listing"><a href="admin_images?admin_images_id=~id~"><!--~image nonempty {~--><img src="~image thumb_src~" width="~image thumb_width~" height="~image thumb_height~" alt=""><!--~}~--></a></td>
+                                       <td class="listing"><a href="admin_images?admin_images_id=~id~">~name html~</a></td>
+                                       <td class="listing"><a href="admin_images?admin_images_id=~id~">~caption html~</a></td>
+                                       <td><a href="admin_images?admin_images_delete_id=~id~" onclick="return confirm('Permanently delete?')">[delete this image]</a></td>
+                               </tr><!--~}~-->
+
+                       </table>
+               <!--~}~-->
+               <!--~empty_listing {~-->
+                       <p>No images in database.</p>
+               <!--~}~-->
+
+               <p><a href="admin_images?admin_images_new=1">[Add a new image]</a></p>
+       <!--~}~-->
+
+<!--~}~-->
+</body>
+</html>
diff --git a/admin_images.php b/admin_images.php
new file mode 100644 (file)
index 0000000..a8c8a9b
--- /dev/null
@@ -0,0 +1,242 @@
+<?php
+
+# This form requires wfpl. See: http://jasonwoof.org/wfpl
+
+# This form was initially auto-generated. If you would like to alter the
+# parameters and generate a new one try this URL:
+#
+# http://jasonwoof.com/metaform/?file_name=admin_images&table_name=cms_images&singular=image&plural=images&opt_email=No&opt_db=Yes&opt_listing=Yes&opt_display=Yes&opt_pass=Yes&fields=image+thumb%0D%0Aname+textbox%0D%0Acaption+textbox%0D%0Asizes+textarea&edit=yes
+
+
+# SETUP
+
+# To save results to a database, you'll need to create the cms_images table
+# (the file admin_images.sql should help with this), and create the file
+# 'code/db_connect.php' which calls db_connect() see:
+# code/wfpl/examples/db_connect.php
+#
+# if you rename any of the database fields, you'll need to update this:
+
+define('ADMIN_IMAGES_DB_FIELDS', 'image,name,caption,sizes');
+
+# Set this to the path to your uploads directory. It can be relative to the
+# location of this script. IT MUST END WITH A SLASH
+$GLOBALS['upload_directory'] = 'cms_images/';
+
+$GLOBALS['image_max_width'] = '596';
+$GLOBALS['image_max_height'] = '1600';
+$GLOBALS['image_thumb_max_width'] = '70';
+$GLOBALS['image_thumb_max_height'] = '70';
+$GLOBALS['image_file_name'] = uniqid() . getmypid() . '.jpg'; # comment this out to use uploader's filename
+
+
+require_once('code/wfpl/format.php');
+require_once('code/wfpl/email.php');
+require_once('code/wfpl/upload.php');
+
+# example: 200x300
+function format_width_height($str) {
+       $fields = explode('x', $str);
+       if(count($fields) != 2) {
+               return '';
+       }
+
+       list($width, $height) = $fields;
+       $width = format_int_0($width);
+       $height = format_int_0($height);
+
+       return "${width}x$height";
+}
+
+function admin_images_get_fields() {
+       $data = array();
+
+       $data['name'] = format_oneline($_REQUEST['name']);
+       $data['caption'] = format_oneline($_REQUEST['caption']);
+       $data['sizes'] = format_unix($_REQUEST['sizes']);
+       if($_FILES['image'] && $_FILES['image']['error'] == 0) {
+               $data['image'] = convert_uploaded_image('image', $GLOBALS['upload_directory'] . $GLOBALS['image_file_name'], $GLOBALS['image_max_width'], $GLOBALS['image_max_height'], $GLOBALS['image_thumb_max_width'], $GLOBALS['image_thumb_max_height']);
+       } else {
+               if($_REQUEST['delete_image'] == 'Yes') {
+                       $data['image'] = '';
+               } else {
+                       $data['image'] = format_image_w_h_thumb_w_h($_REQUEST['old_image']);
+               }
+       }
+
+       return $data;
+}
+
+
+# You may pass a "where clause" for the db query.
+function admin_images_display_listing($where = 'order by name, caption') {
+       $rows = db_get_assocs('cms_images', 'id,image,name,caption', $where);
+       if($rows == false || count($rows) == 0) {
+               tem_set('listings', array('empty_listing' => true));
+               return;
+       }
+
+       # make sure there's something clickable
+       foreach($rows as &$row) {
+               if($row['name'] == '') {
+                       $row['name'] = '--';
+               }
+       }
+       tem_set('listings', array(
+               'populated_listing' => true,
+               'rows' => $rows));
+       return true;
+}
+
+function admin_images_main() {
+       if(!logged_in_as_admin()) {
+               $_REQUEST['url'] = this_url();
+               return 'admin_login';
+       }
+
+       if(isset($_REQUEST['admin_images_id'])) {
+               return admin_images_display_main();
+       } else {
+               return admin_images_edit_main();
+       }
+}
+
+function admin_images_display_main() {
+       $id = format_int($_REQUEST['admin_images_id']);
+       unset($_REQUEST['admin_images_id']);
+       if(!$id) {
+               message('Error: Broken link');
+               return './admin_images';
+       }
+       $data = db_get_assoc('cms_images', 'id,'.ADMIN_IMAGES_DB_FIELDS, 'where id=%i', $id);
+       if(!$data) {
+               message('Error: Image not found');
+               return './admin_images';
+       }
+
+       # Find pages that have this image on it
+       if($data['image']) {
+               $references = db_get_assocs('cms_pages', 'title,filename', 'where content like "%%%s%%" order by concat(nav_title,title)', substr(enc_image_src($data['image']), 0, -4)); # FIXME test that this works for smaller images
+               if($references) {
+                       $data['references'] = array(
+                               'data' => $references,
+                               'count' => count($references));
+               }
+       }
+
+       # display smaller versions with instructions and example code
+       $smaller == array();
+       if($data['image'] && $data['sizes']) {
+               $big_src = enc_image_src($data['image']);
+               $row = explode("\n", $data['sizes']);
+               foreach($row as $max_hw) {
+                       $max_hw = format_width_height($max_hw);
+                       if($max_hw == '') {
+                               continue;
+                       }
+                       list($max_width, $max_height) = explode('x', $max_hw);
+                       $src = str_replace('.', "-$max_hw.", $big_src);
+                       $dimensions = image_dimensions($src);
+                       if($dimensions) {
+                               list($width, $height) = explode('x', $dimensions);
+                       } else {
+                               $width = $max_width;
+                               $height = $max_height;
+                       }
+
+                       $smaller[] = array(
+                               'src' => $src,
+                               'max_width' => $max_width,
+                               'max_height' => $max_height,
+                               'width' => $width,
+                               'height' => $height);
+               }
+       }
+       if($smaller) {
+               $data['smaller'] = $smaller;
+       } else {
+               tem_set('no_sizes');
+       }
+
+       tem_set('display', $data);
+}
+
+function admin_images_edit_main() {
+       $edit_id = format_int($_REQUEST['admin_images_edit_id']);
+       unset($_REQUEST['admin_images_edit_id']);
+       if($edit_id) {
+               # add hidden field for database id of row we're editing
+               tem_set('admin_images_edit_id', $edit_id);
+               tem_set('editing', 'show');
+               tem_set('edit_msg', 'show');
+       }
+
+       $delete_id = format_int($_REQUEST['admin_images_delete_id']);
+       unset($_REQUEST['admin_images_delete_id']);
+       if($delete_id) {
+               db_delete('cms_images', 'where id=%i', $delete_id);
+               message('Image deleted.');
+
+               return './admin_images';
+       }
+
+       if(!$edit_id) {
+               if(!isset($_REQUEST['admin_images_new']) && !isset($_REQUEST['name'])) {
+                       admin_images_display_listing();
+                       return;
+               }
+
+               tem_set('new_msg', 'show');
+       }
+
+       if(isset($_POST['name'])) {
+               $data = admin_images_get_fields();
+
+               # save anything
+               # Note: If you change this to re-display the form in some cases, be sure to handle image uploads well (don't make them upload it again.
+
+               # resize image as needed
+               if($data['image'] && $data['sizes']) {
+                       $big_src = enc_image_src($data['image']);
+                       $row = explode("\n", $data['sizes']);
+                       foreach($row as $max_hw) {
+                               $max_hw = format_width_height($max_hw);
+                               if($max_hw == '') {
+                                       continue;
+                               }
+                               list($max_width, $max_height) = explode('x', $max_hw);
+                               $src = str_replace('.', "-$max_hw.", $big_src);
+                               if(($_FILES['image'] && $_FILES['image']['error'] == 0) || !file_exists($src)) {
+                                       imagemagick_convert($big_src, $src, "-geometry $max_hw", 'Resizing image');
+                               }
+                       }
+               }
+
+               # save to database
+               if($edit_id) {
+                       db_update_assoc('cms_images', $data, 'where id=%i', $edit_id);
+                       message('Image updated.');
+                       $saved_id = $edit_id;
+               } else {
+                       db_insert_assoc('cms_images', $data);
+                       message('Image saved.');
+                       $saved_id = db_auto_id();
+               }
+
+               # return user to display page where they can see instructions, etc
+               return "./admin_images?admin_images_id=$saved_id";
+
+       } elseif($edit_id) {
+               # we've recieved an edit id, but no data. So we grab the values to be edited from the database
+               $data = db_get_assoc('cms_images', ADMIN_IMAGES_DB_FIELDS, 'where id=%i', $edit_id);
+       } else {
+               # form not submitted, set default values:
+               $data = array('sizes' => '275x500');
+       }
+
+       tem_set('upload_max_filesize', upload_max_filesize());
+
+       tem_set('form', $data);
+}
+
+?>
diff --git a/admin_images.sql b/admin_images.sql
new file mode 100644 (file)
index 0000000..815969e
--- /dev/null
@@ -0,0 +1,8 @@
+drop table if exists cms_images;
+create table cms_images (
+    id int unique auto_increment,
+    image varchar(240) not null default "",
+    name varchar(200) not null default "",
+    caption varchar(200) not null default "",
+    sizes text not null default ""
+);
diff --git a/admin_login.html b/admin_login.html
new file mode 100644 (file)
index 0000000..9da243f
--- /dev/null
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+       <title><!--~main_title show {~-->~this_host~ Admin Login<!--~}~--></title>
+</head>
+
+<body>
+       <!--~main_body show {~-->
+               <!--~form {~-->
+                       <h2>~this_host~ Admin Login</h2>
+
+                       <form action="admin_login" method="post">
+                               <div class="caption">Username</div>
+                               <div class="field"><input type="text" name="username" value="~username attr~" autofocus></div>
+
+                               <div class="caption">Password</div>
+                               <div class="field"><input type="password" name="password" value=""></div>
+
+                               <div class="caption">&nbsp;</div>
+                               <div class="field"><input type="hidden" name="url" value="~url attr~"><input type="submit" name="save" value="Save"></div>
+                       </form>
+               <!--~}~-->
+       <!--~}~-->
+</body>
+</html>
diff --git a/admin_login.php b/admin_login.php
new file mode 100644 (file)
index 0000000..a3d47ce
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+# This form requires wfpl. See: http://jasonwoof.org/wfpl
+
+function admin_login_get_fields() {
+       $data = array();
+
+       $data['url'] = format_oneline($_REQUEST['url']);
+       $data['username'] = format_oneline($_REQUEST['username']);
+       $data['password'] = format_oneline($_REQUEST['password']);
+
+       return $data;
+}
+
+
+function admin_login_main() {
+       # Always accept "url" parameter, so might as well just:
+       $data = admin_login_get_fields();
+
+       if(strlen($data['username'])) {
+               if($data['username'] == CMS_ADMIN_USER &&
+                  sha1($data['password']) == CMS_ADMIN_PASS) {
+                       session_new();
+                       session_set('auth_username', $username);
+                       session_set('auth_admin', 'yes');
+                       require_once('code/wfpl/http.php');
+                       if(!$data['url']) {
+                               $data['url'] = './admin';
+                       } elseif(strpos(':', $data['url']) !== false) {
+                               $data['url'] = "./$data[url]";
+                       }
+
+                       # redirect to the page they were trying to access:
+                       return $data['url'];
+               } else {
+                       message('Incorrect username and/or password.');
+               }
+       }
+
+       # Don't put (even failed) password back into the form
+       $data['password'] = '';
+
+       # include domain name in title (especially for bookmarks) and header
+       tem_set('this_host', this_host());
+
+       # display the form [again]
+       tem_set('form', $data);
+}
diff --git a/admin_pages.html b/admin_pages.html
new file mode 100644 (file)
index 0000000..2900215
--- /dev/null
@@ -0,0 +1,103 @@
+<!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">
+<head>
+  <title><!--~main_title show {~-->~this_host~ Admin: <!--~listings {~-->Pages Listing<!--~}~--><!--~form {~--><!--~new_msg {~-->Add a new page<!--~}~--><!--~edit_msg {~-->Edit page "~title html~"<!--~}~--><!--~}~--><!--~}~--></title>
+  <!--~extra_headers {~-->
+    <script type="text/javascript" src="code/ckeditor/ckeditor.js"></script>
+    <script type="text/javascript">
+      function make_wysiwyg(name) {
+        CKEDITOR.replace(name, {
+               'uiColor': '#ccccff',
+               'removePlugins': 'forms,templates,smiley,pagebreak,save,newpage,preview,print',
+               'height': '300px',
+                       'toolbar': [
+                               ['Source'],
+                               ['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'SpellChecker', 'Scayt'],
+                               ['Undo', 'Redo', '-', 'Find', 'Replace', '-', 'SelectAll', 'RemoveFormat'],
+                               '/',
+                               ['Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript'],
+                               ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', 'Blockquote'],
+                               ['JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'],
+                               ['Link', 'Unlink', 'Anchor'],
+                               ['Image', 'Table', 'HorizontalRule', 'SpecialChar', 'PageBreak'],
+                               '/',
+                               ['Styles', 'Format', 'Font', 'FontSize'],
+                               ['TextColor', 'BGColor'],
+                               ['Maximize', 'ShowBlocks']
+                       ]
+        });
+      }
+    </script>
+  <!--~}~-->
+
+</head>
+
+<body>
+  <!--~main_body show {~-->
+
+<!--~form {~-->
+  <form action="admin_pages" method="post"><!--~editing {~--><div style="display: none"><input type="hidden" name="and_then" value="~and_then attr~" /><input type="hidden" name="admin_pages_edit_id" value="~admin_pages_edit_id attr~" /></div><!--~}~-->
+
+
+    <div class="caption">Title</div>
+    <div class="field_notes">(This appears at the top of the page, in the window title-bar (by the close button) and as the headline/link of search engine results.)</div>
+    <div class="field"><input type="text" name="title" value="~title attr~" /></div>
+
+    <div class="caption">Filename</div>
+    <div class="field_notes">(<!--~editing {~-->Careful: if you change this, be sure to update all links to this page<!--~}~--><!--~editing unset {~-->Please use only a-z, 0-9 and _ (underscore) in your filename. Please, no capitals, punctuation or spaces.<!--~}~-->)</div>
+    <div class="field"><input type="text" name="filename" value="~filename attr~" /></div>
+
+    <div class="caption">Show in navigation links</div>
+    <div class="field_notes">(On the left of every page.)</div>
+    <div class="field"><select name="navbar"><!--~navbar options~--></select></div>
+
+    <div class="caption">Navigation Link Text</div>
+    <div class="field_notes">(If you'd like this page to appear in the navigation with a shorter title.)</div>
+    <div class="field"><input type="text" name="nav_title" value="~nav_title attr~" /></div>
+
+    <div class="caption">Content</div>
+    <div class="field_notes">
+      <ul>
+        <li>If you don't see an editor below (with buttons in it) then please try this page in <a href="http://getfirefox.com">Mozilla FireFox</a> or <a href="http://www.google.com/chrome/">Google Chrome</a>.</li>
+        <li>If you're pasting from Microsoft Word, please use the "paste from word" button (4 right of the scissors).</li>
+        <li>In the link dialog, you can make a link to another page on this site by entering that page's "filename" in the URL field.</li>
+      </ul></div>
+    <div class="field"><textarea class="html_editor" rows="20" cols="50" id="content" name="content">~content html~</textarea><script type="text/javascript">make_wysiwyg('content');</script></div>
+
+    <div class="caption">Description</div>
+    <div class="field_notes">(Hidden description of this page, primarily for search engines.)</div>
+    <div class="field"><textarea rows="9" cols="22" name="description">~description html~</textarea></div>
+
+    <div class="caption">Keywords</div>
+    <div class="field_notes">(Hidden words (up to 30) with commas between them for search engines)</div>
+    <div class="field"><textarea rows="9" cols="22" name="keywords">~keywords html~</textarea></div>
+
+    <div class="field"><input type="submit" name="save" value="Save" /></div>
+
+  </form>
+<!--~}~-->
+<!--~listings {~-->
+  <!--~populated_listing {~-->
+  <p><a href="admin_pages?admin_pages_new=1">[Add a new page]</a></p>
+
+  <table cellspacing="0" cellpadding="4" border="1" summary="">
+    <th>Title</th><th>Filename</th><th>&nbsp;</th><!--~rows {~-->
+    <tr>
+      <td class="listing"><a href="admin_pages?admin_pages_edit_id=~id~">~title html~</a></td>
+      <td class="listing"><a href="admin_pages?admin_pages_edit_id=~id~">~filename html~</a></td>
+      <td><a href="admin_pages?admin_pages_delete_id=~id~" onclick="return confirm('Permanently delete?')">[delete this page]</a></td>
+    </tr><!--~}~-->
+
+  </table>
+  <!--~}~-->
+  <!--~empty_listing {~-->
+    <p>No pages in database.</p>
+  <!--~}~-->
+
+  <p><a href="admin_pages?admin_pages_new=1">[Add a new page]</a></p>
+<!--~}~-->
+
+<!--~}~-->
+</body>
+</html>
diff --git a/admin_pages.php b/admin_pages.php
new file mode 100644 (file)
index 0000000..cb2ecbe
--- /dev/null
@@ -0,0 +1,161 @@
+<?php
+
+# This form requires wfpl. See: http://jasonwoof.org/wfpl
+
+define('ADMIN_PAGES_DB_FIELDS', 'title,filename,navbar,nav_title,content,description,keywords');
+
+
+require_once('code/wfpl/format.php');
+require_once('code/wfpl/email.php');
+
+function format_cms_filename($str) {
+       $str = format_filename($str);
+       $str = str_replace('.', '_', $str);
+       return $str;
+}
+
+
+function admin_pages_get_fields() {
+       $data = array();
+       $data['title'] = format_oneline($_REQUEST['title']);
+       $data['filename'] = format_cms_filename($_REQUEST['filename']);
+       $data['navbar'] = format_oneline($_REQUEST['navbar'], 'navbar');
+       $data['nav_title'] = format_oneline($_REQUEST['nav_title']);
+       $data['content'] = format_unix($_REQUEST['content']);
+       $data['description'] = format_unix($_REQUEST['description']);
+       $data['keywords'] = format_unix($_REQUEST['keywords']);
+
+       return $data;
+}
+
+
+# You may pass a "where clause" for the db query.
+function admin_pages_display_listing($where = 'order by concat(nav_title,title)') {
+       $rows = db_get_assocs('cms_pages', 'id,filename,coalesce(nullif(nav_title,\'\'), title) as title', $where);
+       if($rows == false || count($rows) == 0) {
+               tem_set('listings', array('empty_listing' => true));
+               return;
+       }
+
+       # make sure there's something clickable
+       foreach($rows as &$row) {
+               if($row['filename'] == '') {
+                       $row['filename'] = '-- offline --';
+               }
+               if($row['title'] == '') {
+                       $row['title'] = '-- untitled --';
+               }
+       }
+       tem_set('listings', array(
+               'populated_listing' => true,
+               'rows' => $rows));
+       return true;
+}
+
+function admin_pages_main() {
+       if(!logged_in_as_admin()) {
+               $_REQUEST['url'] = this_url();
+               return 'admin_login';
+       }
+
+       tem_set('this_host', this_host());
+
+       if(isset($_REQUEST['admin_pages_id'])) {
+               return admin_pages_display_main();
+       } else {
+               return admin_pages_edit_main();
+       }
+}
+
+# admin-only access to view pages with no filename
+function admin_pages_display_main() {
+       $id = format_int($_REQUEST['admin_pages_id']);
+       unset($_REQUEST['admin_pages_id']);
+       if(!$id) {
+               message('Error: Broken link');
+               return './admin_pages';
+       }
+       cms_display_content($GLOBALS['wfpl_main_template'], 'where id=%i', $id);
+}
+
+function admin_pages_edit_main() {
+       $edit_id = format_int($_REQUEST['admin_pages_edit_id']);
+       unset($_REQUEST['admin_pages_edit_id']);
+       if($edit_id) {
+               # add hidden field for database id of row we're editing
+               tem_set('admin_pages_edit_id', $edit_id);
+               tem_set('editing', 'show');
+               tem_set('edit_msg', 'show');
+       }
+
+       $delete_id = format_int($_REQUEST['admin_pages_delete_id']);
+       unset($_REQUEST['admin_pages_delete_id']);
+       if($delete_id) {
+               db_delete('cms_pages', 'where id=%i', $delete_id);
+               message('Page deleted.');
+
+               return './admin_pages';
+       }
+
+       if(!$edit_id) {
+               if(!isset($_REQUEST['admin_pages_new']) && !isset($_REQUEST['title'])) {
+                       admin_pages_display_listing();
+                       return;
+               }
+
+               tem_set('new_msg', 'show');
+       }
+
+       $navbar_options = array(array('ignored', 'Not at all'), array('0', 'First'));
+       $rows = db_get_rows('cms_pages', 'id,coalesce(nullif(nav_title,\'\'), title) as title,navbar', 'where navbar != 0 order by navbar');
+       if($rows) for($i = 0; $i < count($rows); ++$i) {
+               list($other_id, $other_title, $other_ord) = $rows[$i];
+               if($other_id != $edit_id) { # don't display ourselves
+                       $navbar_options[] = array($i + 1, "After \"$other_title\"");
+               }
+       }
+       pulldown('navbar', $navbar_options, PULLDOWN_2D);
+
+       if(isset($_POST['title'])) {
+               $data = admin_pages_get_fields();
+
+               # We'll save anything (no required fields)
+
+               $data['navbar'] = db_reposition('cms_pages', $edit_id, $data['navbar'], 'navbar', 'page');
+
+               if($data['navbar'] && $data['filename'] == '') {
+                       message('This page was removed from the navigation column because it does not have a filename. (Pages without filenames are visible only to admins.)');
+                       $data['navbar'] = 0;
+               }
+
+               if($edit_id) {
+                       db_update_assoc('cms_pages', $data, 'where id=%i', $edit_id);
+                       $id = $edit_id;
+                       message('Page updated.');
+               } else {
+                       db_insert_assoc('cms_pages', $data);
+                       $id = db_auto_id();
+                       message('Page saved.');
+               }
+               if($data['filename']) {
+                       return "./$data[filename]";
+               } else {
+                       return "./admin_pages?admin_pages_id=$id";
+               }
+       } elseif($edit_id) {
+               # we've recieved an edit id, but no data. So we grab the values to be edited from the database
+               $data = db_get_assoc('cms_pages', ADMIN_PAGES_DB_FIELDS, 'where id=%i', $edit_id);
+               if($data['navbar']) {
+                       $data['navbar'] = db_count('cms_pages', 'where navbar!=0 && navbar<%i', $data['navbar']);
+               } else {
+                       $data['navbar'] = 'ignored';
+               }
+       } else {
+               # form not submitted, you can set default values like so:
+               #$data = array('title' => 'Yes');
+               $data = array('filename' => format_cms_filename($_REQUEST['new_filename']));
+       }
+
+       tem_set('form', $data);
+       tem_set('extra_headers', 'show');
+}
diff --git a/admin_pages.sql b/admin_pages.sql
new file mode 100644 (file)
index 0000000..e82ed35
--- /dev/null
@@ -0,0 +1,11 @@
+drop table if exists pages;
+create table cms_pages (
+    id int unique auto_increment,
+    filename varchar(200) not null default "",
+    title varchar(200) not null default "",
+    nav_title varchar(200) not null default "",
+    navbar int not null default 0,
+    content text not null default "",
+    description text not null default "",
+    keywords text not null default ""
+);
diff --git a/code/ckeditor b/code/ckeditor
new file mode 160000 (submodule)
index 0000000..c9fdde6
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit c9fdde67e6384bd5a66adc2b3bba5c4ce9db56c7
diff --git a/code/cms.php b/code/cms.php
new file mode 100644 (file)
index 0000000..60e53b9
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+
+function cms_display($basename, &$tem) {
+       $tem->set('basename', $basename);
+
+       $nav_items = db_get_assocs('cms_pages', "coalesce(nullif(nav_title,''), title) as title,filename", 'where navbar!=0 order by navbar');
+       if($nav_items) {
+               foreach($nav_items as &$nav_item) {
+                       if($nav_item['filename'] == 'index') {
+                               $nav_item['filename'] = './';
+                       }
+                       if($nav_item['title'] == '') {
+                               $nav_item['title'] = '-- blank --';
+                       }
+               }
+               $tem->set('navbar_items', $nav_items);
+       }
+
+       $have_content = cms_display_content($tem, 'where filename=%"', $basename);
+
+       if(logged_in_as_admin()) {
+               $tem->set('admin_links', 1);
+       }
+       return $have_content;
+}
+
+function cms_display_content(&$tem /*, 'where clause %", %i', string, int */) {
+       $args = array_slice(func_get_args(), 1);
+       $args = array_merge(array('cms_pages', 'id as admin_edit_page_id,title as cms_title,content as cms_body,keywords as meta_keywords,description as meta_description'), $args);
+       $row = call_user_func_array('db_get_assoc', $args);
+       if($row) {
+               # dump it into the global scope:
+               $tem->sets($row);
+               return true;
+       }
+       return false;
+}
diff --git a/code/config.php b/code/config.php
new file mode 100644 (file)
index 0000000..39900ee
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+
+define('WFPL_DB', 'fixme');
+define('WFPL_DB_USER', 'fixme');
+define('WFPL_DB_PASS', 'fixme');
+define('CMS_ADMIN_USER', 'fixme');
+define('CMS_ADMIN_PASS', '98fd71615b073b75810f4ed40d4538198c6450cc');
+# To change the cms admin password to "secret" run this command:
+#    echo '<?php print(sha1("secret"));' | php
+# And replace the hash above with its output
+
+
+
+require_once('code/wfpl/format.php');
+require_once('code/wfpl/db.php');
+require_once('code/wfpl/session_messages.php');
+require_once('code/cms.php');
+
+db_connect(WFPL_DB, WFPL_DB_USER, WFPL_DB_PASS);
index 0055352..7b104a1 160000 (submodule)
--- a/code/wfpl
+++ b/code/wfpl
@@ -1 +1 @@
-Subproject commit 005535291b8acf5b0f9843cc7df22db941f8fd57
+Subproject commit 7b104a1fadfd6033dc7d08c0cf9fb494ec10266d
diff --git a/logout.php b/logout.php
new file mode 100644 (file)
index 0000000..9600c09
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+
+function logout_main() {
+       kill_session();
+       message('logged out successfully');
+       return './';
+}
diff --git a/run.php b/run.php
new file mode 120000 (symlink)
index 0000000..1d3ff92
--- /dev/null
+++ b/run.php
@@ -0,0 +1 @@
+code/wfpl/run.php
\ No newline at end of file
diff --git a/style.css b/style.css
new file mode 100644 (file)
index 0000000..d705bb7
--- /dev/null
+++ b/style.css
@@ -0,0 +1,145 @@
+body {
+       margin: 0;
+       padding: 0;
+       color: black;
+       background: white;
+       font: 14px verdana, arial, free-sans, sans-serif;
+}
+
+nav {
+       width: 154px;
+       float: left;
+       overflow: none;
+       background: #ffe;
+}
+
+nav h3 {
+       padding-left: 10px;
+}
+
+footer {
+       float: left;
+       clear: both;
+       width: 596px;
+       margin-top: 35px;
+       text-align: center;
+       font-size: 9px;
+}
+
+#body {
+       width: 596px;
+       padding: 25px 25px 15px 25px;
+       float: left;
+}
+
+#centerer {
+       margin: 10px auto 20px;
+       width: 800px;
+}
+
+#admin_links {
+       background: #ddf;
+       margin-bottom: 20px;
+}
+
+
+/************ FLOATING IMAGES **************/
+span.float_left {
+       display: block;
+       float: left;
+       font-size: 10px;
+       text-align: right;
+       padding: 4px 15px 7px 0;
+}
+span.float_right {
+       display: block;
+       float: right;
+       font-size: 10px;
+       text-align: right;
+       padding: 4px 0 7px 15px;
+}
+span.float_left img, span.float_right img {
+       display: block;
+       padding-bottom: 1px;
+}
+span.float_center img {
+       display: inline;
+}
+div.mid_pic {
+       margin: 15px auto;
+       text-align: right;
+}
+code.html {
+       background: #ddd;
+}
+
+
+/************ GENERAL **************/
+/* HTML5 tags */
+header, hgroup, section, footer, aside, nav, article, figure {
+       display: block;
+}
+
+img {
+       display: block;
+}
+
+a img {
+       border: 0;
+}
+
+.first {
+       padding-top: 0px;
+       margin-top: 0px;
+}
+.last, div.last {
+       padding-bottom: 0px;
+       margin-bottom: 0px;
+}
+
+
+/************ FORMS **************/
+div.caption {
+       font-weight: bold;
+}
+div.field {
+       margin-bottom: 20px;
+}
+div.field_notes {
+       font-size: 12px;
+       line-height: 16px;
+}
+div.field_notes ul {
+       padding-top: 0;
+       padding-bottom: 0;
+       margin-top: 0;
+       margin-bottom: 0;
+}
+div.error {
+       border: 2px solid red;
+       padding: 13px;
+       margin: 20px;
+       background: #fdd;
+}
+
+.form_section {
+       border: 1px dotted black;
+       padding: 23px 15px 15px 15px;
+       margin: 25px 0;
+       position: relative;
+}
+.form_section_header {
+       position: absolute;
+       left: 42px;
+       top: -9px;
+       background: white;
+       padding: 0 6px;
+}
+
+#cke_content {
+       /* wysiwyg area seems to be 10px narrower than cke_content */
+       /* Then we leave another 20px for the scrollbar */
+       /* And another 20px for padding */
+       width: 670px;
+       margin-left: -11px;
+}
diff --git a/template.html b/template.html
new file mode 100644 (file)
index 0000000..5c191d6
--- /dev/null
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+       <title>~main_title html~~cms_title html~</title>
+       <meta charset="utf-8" />
+       <link rel="stylesheet" href="style_04.css" />
+       <!--~meta_description nonempty {~--><meta name="description" content="~meta_description attr~" /><!--~}~-->
+       <!--~meta_description nonempty {~--><meta name="keywords" content="~meta_keywords attr~" /><!--~}~-->
+       <!--[if IE]>
+               <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+       <![endif]-->
+       <!--~extra_headers~-->
+</head>
+
+<body>
+       <div id="centerer">
+               <header>
+                       header image here
+               </header>
+               <nav><!--~navbar_items {~-->
+                       <h3><a href="~filename~">~title html~</a></h3><!--~}~-->
+               </nav>
+
+               <div id="body">
+                       <!--~admin_links {~-->
+                               <div id="admin_links">You are logged in as an administrator. &nbsp; <a href="admin_pages?admin_pages_edit_id=~admin_edit_page_id~&amp;admin_pages_new=1&amp;new_filename=~basename~">Edit this page</a> &mdash; <a href="admin">Control panel</a> &mdash; <a href="logout">Log out</a></div>
+                       <!--~}~-->
+                       <!--~wfpl_messages {~-->
+                               <!--~ first {~--><div style="border: 2px solid red; background: #fbb; padding: 5px; margin: 20px 0px"><!--~}~-->
+                               <p style="font-size: 120%; padding: 5px; margin: 0px">~data html~</p>
+                               <!--~ sep {~--><hr><!--~}~-->
+                               <!--~ last {~--></div><!--~}~-->
+                       <!--~}~-->
+
+                       <article>
+                               ~cms_body~
+
+                               ~main_body~
+                       </article>
+                       <footer>
+                               footer text here
+                       </footer>
+               </div>
+       </div>
+</body>
+</html>