From acb38afcd4780191569ee809f3e8bdb550a634bc Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Wed, 5 Sep 2012 18:34:48 +0000 Subject: blogsum --- Blogsum/Config.pm.dist | 38 +++ admin.cgi | 252 +++++++++++++++++ docs/LICENSE | 29 ++ docs/LICENSE.images | 14 + docs/README | 6 + examples/create_sqlite.sql | 36 +++ examples/httpd-blogsum.conf | 50 ++++ examples/httpd2-blogsum.conf | 49 ++++ examples/wp2blogsum.pl | 75 +++++ index.cgi | 460 +++++++++++++++++++++++++++++++ startup.pl | 42 +++ themes/default/admin.tmpl | 107 +++++++ themes/default/images/asterisk-green.gif | Bin 0 -> 4255 bytes themes/default/images/asterisk-red.gif | Bin 0 -> 4310 bytes themes/default/images/check.gif | Bin 0 -> 349 bytes themes/default/images/delete.gif | Bin 0 -> 394 bytes themes/default/images/draft-disabled.gif | Bin 0 -> 4074 bytes themes/default/images/draft.gif | Bin 0 -> 350 bytes themes/default/images/play-disabled.gif | Bin 0 -> 4062 bytes themes/default/images/play.gif | Bin 0 -> 340 bytes themes/default/images/plus.gif | Bin 0 -> 233 bytes themes/default/images/xml.gif | Bin 0 -> 585 bytes themes/default/index.tmpl | 179 ++++++++++++ themes/default/style.css | 87 ++++++ 24 files changed, 1424 insertions(+) create mode 100644 Blogsum/Config.pm.dist create mode 100755 admin.cgi create mode 100644 docs/LICENSE create mode 100644 docs/LICENSE.images create mode 100644 docs/README create mode 100644 examples/create_sqlite.sql create mode 100644 examples/httpd-blogsum.conf create mode 100644 examples/httpd2-blogsum.conf create mode 100755 examples/wp2blogsum.pl create mode 100755 index.cgi create mode 100644 startup.pl create mode 100644 themes/default/admin.tmpl create mode 100644 themes/default/images/asterisk-green.gif create mode 100644 themes/default/images/asterisk-red.gif create mode 100644 themes/default/images/check.gif create mode 100644 themes/default/images/delete.gif create mode 100644 themes/default/images/draft-disabled.gif create mode 100644 themes/default/images/draft.gif create mode 100644 themes/default/images/play-disabled.gif create mode 100644 themes/default/images/play.gif create mode 100644 themes/default/images/plus.gif create mode 100644 themes/default/images/xml.gif create mode 100644 themes/default/index.tmpl create mode 100644 themes/default/style.css diff --git a/Blogsum/Config.pm.dist b/Blogsum/Config.pm.dist new file mode 100644 index 0000000..fb4cf6e --- /dev/null +++ b/Blogsum/Config.pm.dist @@ -0,0 +1,38 @@ + +# Blogsum +# Copyright (c) 2009 Jason Dixon +# All rights reserved. + + +########################### +# pragmas # +########################### +package Blogsum::Config; +use strict; + + +########################### +# user options # +########################### +our $database = 'data/site.db'; +our $blog_theme = 'default'; +our $blog_title = 'example.com'; +our $blog_subtitle = 'My New Blog'; +our $blog_url = 'http://www.example.com/'; +our $blog_owner = 'user@example.com'; +our $blog_rights = 'Copyright 2009, Example User'; +our $feed_updates = 'hourly'; +our $captcha_pubkey = ''; +our $captcha_seckey = ''; +our $comment_max_length = '1000'; +our $comments_allowed = 0; +our $smtp_server = 'localhost:25'; +our $smtp_sender = 'blogsum@example.com'; +our $articles_per_page = '10'; +our $google_analytics_id = ''; +our $google_webmaster_id = ''; +our $max_tags_in_cloud = 20; +our $page_not_found_error = ''; + +1; + diff --git a/admin.cgi b/admin.cgi new file mode 100755 index 0000000..21de7c0 --- /dev/null +++ b/admin.cgi @@ -0,0 +1,252 @@ + +# Blogsum +# +# Copyright (c) 2010 DixonGroup Consulting +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# - Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# - Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +########################### +# pragmas and vars # +########################### +use strict; +use Blogsum::Config; +my $database = $Blogsum::Config::database; +my $blog_theme = $Blogsum::Config::blog_theme; +my $blog_title = $Blogsum::Config::blog_title; + + +########################### +# main execution # +########################### +my $cgi = CGI->new; +my $dbh = DBI->connect("DBI:SQLite:dbname=$database", '', '', { RaiseError => 1 }) || die $DBI::errstr; +my $template = HTML::Template->new(filename => "themes/${blog_theme}/admin.tmpl", die_on_bad_params => 0); +$template->param( theme => $blog_theme ); +my $view; + +if ($cgi->param('view')) { + if ($cgi->param('view') eq 'moderate') { + $view = 'moderate'; + manage_comments(); + } elsif ($cgi->param('view') eq 'edit') { + $view = 'create'; + edit_article(); + } else { + $view = 'administrate'; + manage_articles(); + } +} else { + $view = 'administrate'; + manage_articles(); +} + +$dbh->disconnect; + + +########################### +# subfunctions # +########################### + +sub manage_articles { + + my $article_id; + my $status=2; + + if ($cgi->param('delete') =~ /\d+/) { + $article_id = $cgi->param('delete'); + $status=-1; + } + if ($cgi->param('draft') =~ /\d+/) { + $article_id = $cgi->param('draft'); + $status=0; + } + if ($cgi->param('publish') =~ /\d+/) { + $article_id = $cgi->param('publish'); + $status=1; + } + if ($status < 2) { + my $stmt = "UPDATE articles SET enabled=? WHERE id=?"; + my $sth = $dbh->prepare($stmt); + $sth->execute($status, $article_id) || die $dbh->errstr; + } + if ($status == 1) { + my $stmt = "UPDATE articles SET date = datetime('now', 'localtime') WHERE id=?"; + my $sth = $dbh->prepare($stmt); + $sth->execute($article_id) || die $dbh->errstr; + } + + if (@{get_comments()} > 0) { + $template->param( comments_to_moderate => 1); + } + $template->param( view => $view, blog_title => $blog_title, articles => get_articles() ); + print $cgi->header(), $template->output; +} + +sub manage_comments { + + my $comment_id; + my $status=2; + + if ($cgi->param('delete') =~ /\d+/) { + $comment_id = $cgi->param('delete'); + $status=-1; + } + if ($cgi->param('publish') =~ /\d+/) { + $comment_id = $cgi->param('publish'); + $status=1; + } + if ($status < 2) { + my $stmt = "UPDATE comments SET enabled=? WHERE id=?"; + my $sth = $dbh->prepare($stmt); + $sth->execute($status, $comment_id) || die $dbh->errstr; + } + + $template->param( view => $view, blog_title => $blog_title, comments => get_comments() ); + print $cgi->header(), $template->output; +} + +sub edit_article { + + # preview, pass through all input + if ($cgi->param('preview')) { + my $uri = $cgi->param('uri') || $cgi->param('title') || undef; + $uri =~ s/\ /\-/g if ($uri); + $template->param( view => $view, blog_title => $blog_title, preview => 1, edit => 1 ); + $template->param( id => $cgi->param('id') ) if ($cgi->param('id')); + $template->param( title => $cgi->param('title') ) if ($cgi->param('title')); + $template->param( uri => $uri ) if ($uri); + $template->param( preview => $cgi->param('body') ) if ($cgi->param('body')); + $template->param( body => HTML::Entities::encode($cgi->param('body')) ) if ($cgi->param('body')); + $template->param( tags => $cgi->param('tags') ) if ($cgi->param('tags')); + print $cgi->header(), $template->output; + + # save edits, with id (update) + } elsif ($cgi->param('save') && $cgi->param('id')) { + if ($cgi->param('title') && $cgi->param('uri') && $cgi->param('body')) { + my $uri = $cgi->param('uri'); + $uri =~ s/\ /\-/g; + my $stmt = "UPDATE articles SET title=?, uri=?, body=?, tags=? WHERE id=?"; + my $sth = $dbh->prepare($stmt); + $sth->execute($cgi->param('title'), $uri, $cgi->param('body'), $cgi->param('tags'), $cgi->param('id')) || die $dbh->errstr; + manage_articles(); + # if missing data, push back to preview + } else { + $template->param( error => 'required fields: title, uri, body' ); + $template->param( view => $view, blog_title => $blog_title, edit => 1 ); + $template->param( id => $cgi->param('id') ) if ($cgi->param('id')); + $template->param( title => $cgi->param('title') ) if ($cgi->param('title')); + $template->param( uri => $cgi->param('uri') ) if ($cgi->param('uri')); + $template->param( preview => $cgi->param('body') ) if ($cgi->param('body')); + $template->param( body => HTML::Entities::encode($cgi->param('body')) ) if ($cgi->param('body')); + $template->param( tags => $cgi->param('tags') ) if ($cgi->param('tags')); + print $cgi->header(), $template->output; + } + + # save new, no id (insert) + } elsif ($cgi->param('save')) { + if ($cgi->param('title') && $cgi->param('body')) { + my $uri = $cgi->param('uri') || $cgi->param('title'); + $uri =~ s/\ /\-/g; + my $author = $ENV{'REMOTE_USER'} || 'author'; + my $stmt = "INSERT INTO articles VALUES (NULL, datetime('now', 'localtime'), ?, ?, ?, ?, 0, ?)"; + my $sth = $dbh->prepare($stmt); + $sth->execute($cgi->param('title'), $uri, $cgi->param('body'), $cgi->param('tags'), $author) || die $dbh->errstr; + manage_articles(); + # if missing data, push back to preview + } else { + $template->param( error => 'required fields: title, body' ); + $template->param( view => $view, blog_title => $blog_title, edit => 1 ); + $template->param( id => $cgi->param('id') ) if ($cgi->param('id')); + $template->param( title => $cgi->param('title') ) if ($cgi->param('title')); + $template->param( uri => $cgi->param('uri') ) if ($cgi->param('uri')); + $template->param( preview => $cgi->param('body') ) if ($cgi->param('body')); + $template->param( body => HTML::Entities::encode($cgi->param('body')) ) if ($cgi->param('body')); + $template->param( tags => $cgi->param('tags') ) if ($cgi->param('tags')); + print $cgi->header(), $template->output; + } + + # edit an existing + } elsif ($cgi->param('id')) { + my $query = "SELECT * FROM articles WHERE id=?"; + my $sth = $dbh->prepare($query); + $sth->execute($cgi->param('id')) || die $dbh->errstr; + my $result = $sth->fetchrow_hashref; + if ($result) { + $template->param( view => $view, blog_title => $blog_title, edit => 1 ); + $template->param( preview => $result->{'body'} ); + $result->{'body'} = HTML::Entities::encode($result->{'body'}); + $template->param( $result ); + print $cgi->header(), $template->output; + } else { + $template->param( error => 'no results found' ); + manage_articles(); + } + + # brand new, show form + } else { + $template->param( view => $view, blog_title => $blog_title, edit => 1 ); + print $cgi->header(), $template->output; + } +} + +sub get_articles { + + my $query = 'SELECT * FROM articles WHERE enabled !=-1 ORDER BY date DESC'; + my $sth = $dbh->prepare($query); + $sth->execute() || die $dbh->errstr; + + my @articles; + while (my $result = $sth->fetchrow_hashref) { + $result->{'date'} =~ /(\d{4})\-(\d{2})\-\d{2} \d{2}\:\d{2}\:\d{2}/; + ($result->{'year'}, $result->{'month'}) = ($1, $2); + $result->{'date'} =~ s/(\d{4}\-\d{2}\-\d{2}) \d{2}\:\d{2}\:\d{2}/$1/; + delete $result->{'enabled'} if ($result->{'enabled'} == 0); + $result->{'theme'} = $blog_theme; + push(@articles, $result); + } + + return \@articles; +} + +sub get_comments { + + my $query = 'SELECT a.title AS article_title, a.uri AS article_uri, a.date AS article_date, c.* FROM articles a, comments c WHERE a.id=c.article_id AND c.enabled=0 ORDER BY c.date DESC'; + my $sth = $dbh->prepare($query); + $sth->execute() || die $dbh->errstr; + + my @comments; + while (my $result = $sth->fetchrow_hashref) { + $result->{'article_date'} =~ /(\d{4})\-(\d{2})\-\d{2} \d{2}\:\d{2}\:\d{2}/; + ($result->{'article_year'}, $result->{'article_month'}) = ($1, $2); + $result->{'theme'} = $blog_theme; + push(@comments, $result); + } + + return \@comments; +} + + diff --git a/docs/LICENSE b/docs/LICENSE new file mode 100644 index 0000000..55d5bab --- /dev/null +++ b/docs/LICENSE @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010 DixonGroup Consulting + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ diff --git a/docs/LICENSE.images b/docs/LICENSE.images new file mode 100644 index 0000000..51b415a --- /dev/null +++ b/docs/LICENSE.images @@ -0,0 +1,14 @@ +The images included in portions of Blogsum were provided from the Proxal +Icon Set v2 under an "Open Source" license by user 'valkyre' at +deviantart.com. + +From his original announcement: + +"This package is open source. Do what you want with it, use it how you +want. I don't care if you take the icons and use them on your website, or +in another application. The only restriction is you can't sell them, or +sell a product that includes them, and blah blah blah. Enough of the +legalities." + +http://valkyre.deviantart.com/art/Proxal-Icon-Set-v2-17102198 + diff --git a/docs/README b/docs/README new file mode 100644 index 0000000..d840a41 --- /dev/null +++ b/docs/README @@ -0,0 +1,6 @@ +# Blogsum README + +Please refer to the online documentation +for manual installation instructions. + +http://blogsum.obfuscurity.com/ diff --git a/examples/create_sqlite.sql b/examples/create_sqlite.sql new file mode 100644 index 0000000..8bc8850 --- /dev/null +++ b/examples/create_sqlite.sql @@ -0,0 +1,36 @@ +BEGIN TRANSACTION; +CREATE TABLE articles (id integer primary key, date date, title text, uri text, body text, tags text, enabled boolean, author text); +INSERT INTO "articles" VALUES(1,'2009-11-16 18:07:10','Welcome to Blogsum','Welcome-to-Blogsum','

Introduction

+ +

Blogsum is a very basic blogging application. It was written from scratch with a focus on simplicity and security, favoring practicality over feature bloat.

+ +

Writing Articles

+ +

The Administration interface is straightforward and bereft of unnecessary features. Three views allow you to create posts, moderate comments and administrate (manage posts). Blogsum uses HTML markup for article formatting. New articles are saved as a draft .

+ +

There''s one other useful thing to remember about editing your articles. If you have a lengthy post you might want to split it up with the <!--readmore--> tag. Anything that appears after this HTML comment will appear in the full article view, but will be hidden from your blog''s front page. Here comes one now...

+ + + +

Managing Articles

+ +

As mentioned above, new articles are saved as a draft. Click the publish button to see your post go live. You can make edits to a live article, but the timestamp won''t be updated unless you re-draft and re-publish the story. If you decide that you really want to remove an article from the administrate view, you can delete it.

+ +

Comments and Moderation

+ +

Article comments are moderated and must be accompanied with a successful Captcha challenge. All user input is encoded to avoid XSS issues. Click on the moderate view to approve or deny a comment submission.

+ +

Using Tags

+ +

Tags are used liberally throughout Blogsum. Besides the tags defined in an article, Blogsum also uses the /Tags/ path to search for authors. It also favors the use of tags as a conventional replacement to categories. Anyone can use the /Tags/ path to search for articles in Blogsum (example).

+ +

Themes

+ +

Blogsum includes a default theme (what you''re viewing now). If you wish to modify it according to your tastes, you should create your own theme. To create a theme, copy the themes/default directory to your own directory (e.g. themes/foobar) and modify accordingly. Changes can be made to any of the files in a theme, but the path and filenames should not change. When you are finished, edit the $blog_theme setting in Blogsum/Config.pm and restart your webserver.

+ +

Go Forth and Blog!

+ +

As you can see, Blogsum is a very simple application designed for less maintenance, more writing. We hope you enjoy publishing your works within Blogsum. If you create your own themes, please consider donating those back to the project. Enjoy!

+','Blogsum,Welcome',1,'jdixon'); +CREATE TABLE comments (id integer primary key, article_id integer, date date, name text, email text, url text, comment text, enabled boolean); +COMMIT; diff --git a/examples/httpd-blogsum.conf b/examples/httpd-blogsum.conf new file mode 100644 index 0000000..f7f669c --- /dev/null +++ b/examples/httpd-blogsum.conf @@ -0,0 +1,50 @@ + + ServerName www.example.com + DocumentRoot /var/www/blogsum + DirectoryIndex index.cgi + + Options +FollowSymlinks + RewriteEngine On + RewriteRule ^/rss.xml$ /index.cgi?rss=1 [PT,QSA] + RewriteRule ^/rss2.xml$ /index.cgi?rss=2 [PT,QSA] + RewriteRule ^/Page/([^/]+)$ /index.cgi?page=$1 [PT,QSA] + RewriteRule ^/Tags/([^/]+)$ /index.cgi?search=$1 [PT,QSA] + RewriteRule ^/([0-9]{4})/([0-9]{2})/([^/]+)$ /index.cgi?view=article&year=$1&month=$2&uri=$3 [PT,QSA] + RewriteRule ^/([0-9]{4})/([0-9]{2})/?$ /index.cgi?view=article&year=$1&month=$2 [PT,QSA] + RewriteRule ^/([0-9]{4})/?$ /index.cgi?view=article&year=$1 [PT,QSA] + + PerlModule Apache::PerlRun + + SetHandler perl-script + PerlHandler Apache::PerlRun + PerlRequire /var/www/blogsum/startup.pl + Options ExecCGI + Order deny,allow + Allow from all + + + SetHandler perl-script + PerlHandler Apache::PerlRun + PerlRequire /var/www/blogsum/startup.pl + Options ExecCGI + Order deny,allow + Allow from all + AuthUserFile /var/www/conf/blogsum.htpasswd + AuthName "Blogsum Admin - example.com" + AuthType Basic + + require valid-user + + + + SetHandler perl-script + PerlHandler Apache::PerlRun + Options -ExecCGI + Order deny,allow + Allow from all + + + Order deny,allow + Deny from all + + diff --git a/examples/httpd2-blogsum.conf b/examples/httpd2-blogsum.conf new file mode 100644 index 0000000..a887d0d --- /dev/null +++ b/examples/httpd2-blogsum.conf @@ -0,0 +1,49 @@ + + ServerName www.example.com + DocumentRoot /var/www/blogsum + DirectoryIndex index.cgi + PerlRequire /www/blogsum/startup.pl + + Options +FollowSymlinks + RewriteEngine On + RewriteRule ^/rss.xml$ /index.cgi?rss=1 [PT,QSA] + RewriteRule ^/rss2.xml$ /index.cgi?rss=2 [PT,QSA] + RewriteRule ^/Page/([^/]+)$ /index.cgi?page=$1 [PT,QSA] + RewriteRule ^/Tags/([^/]+)$ /index.cgi?search=$1 [PT,QSA] + RewriteRule ^/([0-9]{4})/([0-9]{2})/([^/]+)$ /index.cgi?view=article&year=$1&month=$2&uri=$3 [PT,QSA] + RewriteRule ^/([0-9]{4})/([0-9]{2})/?$ /index.cgi?view=article&year=$1&month=$2 [PT,QSA] + RewriteRule ^/([0-9]{4})/?$ /index.cgi?view=article&year=$1 [PT,QSA] + + PerlModule ModPerl::PerlRun + + SetHandler perl-script + PerlResponseHandler ModPerl::PerlRunPrefork + Options ExecCGI + Order deny,allow + Allow from all + + + SetHandler perl-script + PerlResponseHandler ModPerl::PerlRunPrefork + Options ExecCGI + Order deny,allow + Allow from all + AuthUserFile /var/www/conf/blogsum.htpasswd + AuthName "Blogsum Admin - example.com" + AuthType Basic + + require valid-user + + + + SetHandler perl-script + PerlResponseHandler ModPerl::PerlRunPrefork + Options -ExecCGI + Order deny,allow + Allow from all + + + Order deny,allow + Deny from all + + diff --git a/examples/wp2blogsum.pl b/examples/wp2blogsum.pl new file mode 100755 index 0000000..e11f1eb --- /dev/null +++ b/examples/wp2blogsum.pl @@ -0,0 +1,75 @@ +#!/usr/bin/perl + +# Blogsum +# Copyright (c) 2009 Jason Dixon +# All rights reserved. + +use strict; +use DBI; +use XML::Simple; + +die "Usage: wp2blogsum.pl \n\n" unless (@ARGV == 2); + +my $wpxml = $ARGV[0]; +my $database = $ARGV[1]; +my $xs = XML::Simple->new(); +my $ref = $xs->XMLin($wpxml); +my $dbh = DBI->connect("DBI:SQLite:dbname=$database",'','', { RaiseError => 1 }) || die $DBI::errstr; +my $stmt = "INSERT INTO articles VALUES (NULL, ?, ?, ?, ?, ?, ?, ?)"; +my $sth = $dbh->prepare($stmt); +my $stmt2 = "INSERT INTO comments VALUES (NULL, ?, ?, ?, ?, ?, ?, ?)"; +my $sth2 = $dbh->prepare($stmt2); + +foreach my $item ( @{$ref->{'channel'}->{'item'}} ) { + next unless ($item->{'wp:post_type'} eq 'post'); + my $title = $item->{'title'}; + my $date = $item->{'wp:post_date'}; + my $uri = $item->{'wp:post_name'}; + my $author = $item->{'dc:creator'}; + my $enabled = ($item->{'wp:status'} eq 'publish') ? 1 : 0; + my $content = $item->{'content:encoded'}; + $content =~ s/ //g; # remove + unless (($content =~ /
/) || ($content =~ 
    ) || ($content =~
      )) { + $content =~ s/<\!\-\-more\-\->/<\!\-\-readmore\-\->/mg; # convert more to readmore + $content =~ s/^/

      /mg; # add

      to beginning of line + $content =~ s/\r\n/<\/p>\r\n/mg; # add

      to end of line + $content =~ s/$/<\/p>/mg; # add

      to end of story (no \r\n) + $content =~ s/^

      <\/p>$//mg; # remove

      (empty lines) + $content =~ s/^

      (<\!\-\-\w+\-\->)<\/p>/$1/mg; # remove

      (comment lines) + $content =~ s/^<\/p>$//mg; # remove extra

      from end of story + $content =~ s/

        /
          /mg; # remove

          before

            + $content =~ s/
              <\/p>/
                /mg; # remove

                after
                  + $content =~ s/

                  <\/ul>/<\/ul>/mg; # remove

                  before

                + $content =~ s/<\/ul><\/p>/<\/ul>/mg; # remove

                after
              + $content =~ s/

            • /
            • /mg; # remove

              before

            • + $content =~ s/
            • <\/p>/
            • /mg; # remove

              after
            • + $content =~ s/

              <\/li>/<\/li>/mg; # remove

              before

            • + $content =~ s/<\/li><\/p>/<\/li>/mg; # remove

              after + } + my @tags; + if ($item->{'category'}) { + for my $category (@{$item->{'category'}}) { + if (ref($category) eq 'HASH') { + if ($category->{'nicename'}) { + push(@tags, $category->{'content'}); + } + } + } + } + $sth->execute($date, $title, $uri, $content, join(',', @tags), $enabled, $author) || die $dbh->errstr; + my $article_id = $dbh->func('last_insert_rowid'); + if ($item->{'wp:comment'}) { + if (ref($item->{'wp:comment'}) eq 'ARRAY') { + for my $comment (@{$item->{'wp:comment'}}) { + $sth2->execute($article_id, $comment->{'wp:comment_date'}, $comment->{'wp:comment_author'}, $comment->{'wp:comment_author_email'}, $comment->{'wp:comment_author_url'}, $comment->{'wp:comment_content'}, $comment->{'wp:comment_approved'}) || die $dbh->errstr; + } + } else { + my $comment = $item->{'wp:comment'}; + $sth2->execute($article_id, $comment->{'wp:comment_date'}, $comment->{'wp:comment_author'}, $comment->{'wp:comment_author_email'}, $comment->{'wp:comment_author_url'}, $comment->{'wp:comment_content'}, $comment->{'wp:comment_approved'}) || die $dbh->errstr; + } + } +} + +$dbh->disconnect; + + diff --git a/index.cgi b/index.cgi new file mode 100755 index 0000000..9c2321c --- /dev/null +++ b/index.cgi @@ -0,0 +1,460 @@ + +# Blogsum +# +# Copyright (c) 2010 DixonGroup Consulting +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# - Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# - Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +########################### +# pragmas and vars # +########################### +use strict; +use Blogsum::Config; +my $database = $Blogsum::Config::database; +my $blog_theme = $Blogsum::Config::blog_theme; +my $blog_title = $Blogsum::Config::blog_title; +my $blog_subtitle = $Blogsum::Config::blog_subtitle; +my $blog_url = $Blogsum::Config::blog_url; +$blog_url .= '/' unless ($blog_url =~ /^.*\/$/); +my $blog_owner = $Blogsum::Config::blog_owner; +my $blog_rights = $Blogsum::Config::blog_rights; +my $feed_updates = $Blogsum::Config::feed_updates; +my $captcha_pubkey = $Blogsum::Config::captcha_pubkey; +my $captcha_seckey = $Blogsum::Config::captcha_seckey; +my $comment_max_length = $Blogsum::Config::comment_max_length; +my $comments_allowed = $Blogsum::Config::comments_allowed; +my $smtp_server = $Blogsum::Config::smtp_server; +my $smtp_sender = $Blogsum::Config::smtp_sender; +my $articles_per_page = $Blogsum::Config::articles_per_page; +my $google_analytics_id = $Blogsum::Config::google_analytics_id; +my $google_webmaster_id = $Blogsum::Config::google_webmaster_id; +my $max_tags_in_cloud = $Blogsum::Config::max_tags_in_cloud; +my $page_not_found_error = $Blogsum::Config::page_not_found_error; +$page_not_found_error ||= '404 page not found'; + + +########################### +# main execution # +########################### +my $cgi = CGI->new; +my $dbh = DBI->connect("DBI:SQLite:dbname=$database", '', '', { RaiseError => 1 }) || die $DBI::errstr; +my $template = HTML::Template->new(filename => "themes/${blog_theme}/index.tmpl", die_on_bad_params => 0); +if ($cgi->param('rss')) { + output_rss(); +} else { + read_comment() if $comments_allowed; + my $articles = get_articles(); + my $archives = get_archives(); + my $tagcloud = get_tag_cloud(); + $template->param( archives => $archives ); + $template->param( tagcloud => $tagcloud ); + $template->param( theme => $blog_theme ); + $template->param( blog_url => $blog_url ); + $template->param( title => $blog_title ); + $template->param( subtitle => $blog_subtitle ); + $template->param( copyright => $blog_rights ); + $template->param( google_analytics_id => $google_analytics_id ); + $template->param( google_webmaster_id => $google_webmaster_id ); + if (@{$articles}) { + $template->param( articles => $articles ); + if ($cgi->param('uri') && $comments_allowed) { + $template->param( comment_form => 1 ); + $template->param( comment_max_length => $comment_max_length ); + $template->param( id => $articles->[0]->{'id'} ); + } + } else { + $template->param( error => $page_not_found_error ); + } + print $cgi->header(), $template->output; +} +$dbh->disconnect; + + +########################### +# subfunctions # +########################### + +sub output_rss { + + my $version = ($cgi->param('rss') == 2) ? '2.0' : '1.0'; + my $rss = XML::RSS->new( version => $version ); + + $rss->channel ( + title => $blog_title, + link => $blog_url, + description => $blog_subtitle, + dc => { + subject => $blog_title, + creator => $blog_owner, + publisher => $blog_owner, + rights => $blog_rights, + language => 'en-us', + }, + syn => { + updatePeriod => $feed_updates, + updateFrequency => 1, + updateBase => '1901-01-01T00:00+00:00', + } + ); + + my $articles = get_articles(); + for my $item (@{$articles}) { + $item->{'date'} =~ /(\d{4})\-(\d{2})\-\d{2} \d{2}\:\d{2}\:\d{2}/; + ($item->{'year'}, $item->{'month'}) = ($1, $2); + my $link = sprintf("%s%s/%s/%s", $blog_url, $item->{'year'}, $item->{'month'}, $item->{'uri'}); + + if ($version eq '2.0') { + $rss->add_item ( + title => $item->{'title'}, + link => $link, + description => $item->{'body'}, + author => $item->{'author'}, + comments => "${link}#comments", + pubDate => POSIX::strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime($item->{'epoch'})), + category => @{[split(/, */, $item->{'tags'})]}, + ); + } else { + $rss->add_item ( + title => $item->{'title'}, + link => $link, + description => $item->{'body'}, + dc => { + subject => $blog_title, + creator => $item->{'author'}, + date => POSIX::strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime($item->{'epoch'})), + }, + ); + } + } + print $cgi->header('application/rss+xml'), $rss->as_string; +} + +sub get_articles { + + my $page; + my $offset; + my $limit_clause; + my $where_clause; + my $j = 0; + my $show_comments = 0; + + $articles_per_page = ($articles_per_page > 0) ? $articles_per_page : -1; + if ($cgi->param('page') && POSIX::isdigit($cgi->param('page'))) { + $page = $cgi->param('page'); + $offset = ($page - 1) * $articles_per_page; + } else { + $page = 1; + $offset = 0; + } + + if (($cgi->param('year') =~ /\d{4}/)&& (1900 < $cgi->param('year')) && ($cgi->param('year') < 2036)) { + $where_clause .= 'WHERE date LIKE \'%' . $cgi->param('year'); + $j++; + if (($cgi->param('month') =~ /\d{2}/) && (0 < $cgi->param('month')) && ($cgi->param('month') <= 12)) { + $where_clause .= '-' . $cgi->param('month') . '%\' AND enabled=1 '; + $j++; + if ($cgi->param('uri') =~ /\w+/) { + $where_clause .= 'AND uri=? AND enabled=1 '; + $j++; + $show_comments=1; + } + } else { + $where_clause .= "\%' AND enabled=1 "; + } + } elsif ($cgi->param('search')) { + $where_clause .= "WHERE (tags LIKE ? OR author LIKE ?) AND enabled=1 "; + $j++; + + } elsif ($cgi->param('id')) { + $where_clause .= 'WHERE id=? AND enabled=1 '; + $show_comments=1; + + } else { + $where_clause .= 'WHERE enabled=1 '; + $limit_clause = " LIMIT $articles_per_page OFFSET $offset"; + } + + my $query = 'SELECT *, strftime("%s", date) AS epoch FROM articles ' . $where_clause . 'ORDER BY date DESC' . $limit_clause; + my $sth = $dbh->prepare($query); + + if ($j == 3) { + $sth->execute($cgi->param('uri')) || die $dbh->errstr; + } elsif ($cgi->param('search')) { + my $search_tag = sprintf("%%%s%%", $cgi->param('search')); + $sth->execute($search_tag, $search_tag) || die $dbh->errstr; + } elsif ($cgi->param('id')) { + $sth->execute($cgi->param('id')) || die $dbh->errstr; + } else { + $sth->execute() || die $dbh->errstr; + } + + my @articles; + while (my $result = $sth->fetchrow_hashref) { + $result->{'date'} =~ /(\d{4})\-(\d{2})\-\d{2} \d{2}\:\d{2}\:\d{2}/; + ($result->{'year'}, $result->{'month'}) = ($1, $2); + # cut off readmore if we're on the front page + if (($result->{'body'} =~ //) && ($j < 3) && !($cgi->param('rss'))) { + $result->{'body'} =~ /(.*)\<\!\-\-readmore\-\-\>/s; + $result->{'body'} = $1; + $result->{'readmore'}++; + } + $result->{'tag_loop'} = format_tags($result->{'tags'}) if ($result->{'tags'}); + my $comments = get_comments(article_id => $result->{'id'}, enabled => 1); + $result->{'comments_count'} = scalar(@{$comments}); + if ($show_comments) { + $result->{'comments'} = $comments; + } + push(@articles, $result); + } + + my $query2 = 'SELECT count(*) as total FROM articles WHERE enabled=1'; + my $sth2 = $dbh->prepare($query2); + $sth2->execute || die $dbh->errstr; + my $article_count = $sth2->fetchrow_hashref->{'total'}; + $template->param( page_prev => ($page - 1) ) if (($j == 0) && ($article_count > ($offset - $articles_per_page))); + $template->param( page_next => ($page + 1) ) if (($j == 0) && ($article_count > ($offset + $articles_per_page))); + + return (\@articles); +} + +sub get_archives { + + my %history; + my @archives; + my @archives_compressed; + my $current_month = $cgi->param('month') || sprintf("%0.2d", ((localtime)[4] + 1)); + my $current_year = $cgi->param('year') || ((localtime)[5] + 1900); + my %months = ( + '01' => 'January', + '02' => 'February', + '03' => 'March', + '04' => 'April', + '05' => 'May', + '06' => 'June', + '07' => 'July', + '08' => 'August', + '09' => 'September', + '10' => 'October', + '11' => 'November', + '12' => 'December', + ); + + my $query = 'SELECT * FROM articles WHERE enabled=1 ORDER BY date DESC'; + my $sth = $dbh->prepare($query); + $sth->execute || die $dbh->errstr; + while (my $result = $sth->fetchrow_hashref) { + $result->{'date'} =~ /(\d{4})\-(\d{2})\-\d{2} \d{2}\:\d{2}\:\d{2}/; + ($result->{'year'}, $result->{'month'}) = ($1, $2); + my $title = my $full_title = $result->{'title'}; + if (length($title) > 28) { + $title = substr($title, 0, 25) . '...'; + } + + if (($result->{'year'} eq $current_year) && ($result->{'month'} eq $current_month) && $result->{'uri'}) { + push(@{$history{$result->{'year'}}{$result->{'month'}}->{'uri_loop'}}, + { + year => $result->{'year'}, + month => $result->{'month'}, + month_name => $months{$result->{'month'}}, + title => $title, + full_title => $full_title, + uri => $result->{'uri'}, + } + ); + } else { + $history{$result->{'year'}}->{$result->{'month'}}->{'count'}++; + } + $history{$result->{'year'}}->{'count'}++; + + } + + for my $year (sort {$b <=> $a} keys %history) { + no strict "refs"; + for my $month (sort {$b <=> $a} keys %{$history{$year}}) { + my $m = { + 'year' => $year, + 'month' => $month, + 'month_name' => $months{$month}, + 'count' => $history{$year}->{$month}->{'count'}, + }; + # check to see if uri_loop exists first + if ($history{$year}->{$month}->{'uri_loop'}) { + $m->{'uri_loop'} = $history{$year}->{$month}->{'uri_loop'}; + } + push(@{$history{$year}->{'month_loop'}}, $m) unless ($month eq 'count'); + } + my $y = { + 'year' => $year, + 'count' => $history{$year}->{'count'}, + }; + # check to see if we're showing this year, and that month_loop exists + if (($year eq $current_year) && $history{$year}->{'month_loop'}) { + $y->{'month_loop'} = $history{$year}->{'month_loop'}; + } + push(@{$history{'year_loop'}}, $y) unless ($year eq 'count'); + } + + return \@{$history{'year_loop'}}; +} + +sub format_tags { + + my $tags = shift; + my @tags; + + foreach (split(/, */, $tags)) { + push(@tags, { 'tag' => $_ }); + } + + return \@tags; +} + +sub read_comment { + + if ($cgi->param('recaptcha_challenge_field') && $cgi->param('recaptcha_response_field') && $cgi->param('comment') && $cgi->param('id')) { + + # test our captcha + my $result = verify_captcha( $captcha_seckey, $ENV{'REMOTE_ADDR'}, $cgi->param('recaptcha_challenge_field'), $cgi->param('recaptcha_response_field') ); + + if ($result->{'success'}) { + + # save comment + my $comment = HTML::Entities::encode($cgi->param('comment')); + my $stmt = "INSERT INTO comments VALUES (NULL, ?, datetime('now', 'localtime'), ?, ?, ?, ?, 0)"; + my $sth = $dbh->prepare($stmt); + my $comment_name = $cgi->param('name') ? substr($cgi->param('name'), 0, 100) : 'anonymous'; + my $comment_email = $cgi->param('email') ? substr($cgi->param('email'), 0, 100) : undef; + my $comment_url = $cgi->param('url') ? substr($cgi->param('url'), 0, 100) : undef; + my $comment_body = substr(HTML::Entities::encode($cgi->param('comment')), 0, $comment_max_length); + $sth->execute($cgi->param('id'), $comment_name, $comment_email, $comment_url, $comment_body) || die $dbh->errstr; + $template->param( message => 'comment awaiting moderation, thank you' ); + + # send email notification + my $smtp = Net::SMTP->new($smtp_server); + $smtp->mail($ENV{USER}); + $smtp->to("$blog_owner\n"); + $smtp->data(); + $smtp->datasend("From: $smtp_sender\n"); + $smtp->datasend("To: $blog_owner\n"); + $smtp->datasend("Subject: $blog_title comment submission\n\n"); + $smtp->datasend("You have received a new comment submission.\n\n"); + $smtp->datasend(sprintf("From: %s\n", $comment_name)); + $smtp->datasend(sprintf("Date: %s\n", scalar(localtime))); + $smtp->datasend(sprintf("Comment:\n\"%s\"\n\n", $comment_body)); + $smtp->datasend("Moderate comments at ${blog_url}admin.cgi?view=moderate\n"); + $smtp->dataend(); + $smtp->quit; + } else { + my $error; + if ($result->{'error'} eq 'incorrect-captcha-sol') { + $error = 'failed challenge, please try again'; + } else { + $error = 'Error: ' . $result->{'error'} . ', please report to site admin'; + } + $template->param( error => $error ); + $template->param( name => $cgi->param('name') ); + $template->param( email => $cgi->param('email') ); + $template->param( url => $cgi->param('url') ); + $template->param( comment => $cgi->param('comment') ); + $template->param( id => $cgi->param('id') ); + $template->param( comment_form => 1 ); + } + } + + # present the challenge + $template->param( captcha_api_server => 'http://api.recaptcha.net', captcha_pubkey => $captcha_pubkey ); +} + +sub verify_captcha { + + my ( $privkey, $remoteip, $challenge, $response ) = @_; + + my $http = HTTP::Lite->new(); + $http->prepare_post( + { + privatekey => $privkey, + remoteip => $remoteip, + challenge => $challenge, + response => $response + } + ); + $http->request( 'http://api-verify.recaptcha.net/verify' ); + + if ( $http->status eq '200' ) { + my ( $answer, $message ) = split( /\n/, $http->body, 2 ); + return { success => 1 } if ( $answer =~ /true/ ); + return { success => 0, error => $message }; + } else { + return { success => 0, error => 'recaptcha-not-reachable' }; + } +} + +sub get_comments { + + my %args = @_; + + my $query = 'SELECT * FROM comments WHERE article_id=? AND enabled=? ORDER BY date ASC'; + my $sth = $dbh->prepare($query); + $sth->execute($args{'article_id'}, $args{'enabled'}) || die $dbh->errstr; + my @comments; + while (my $result = $sth->fetchrow_hashref) { + push(@comments, $result); + } + return \@comments; +} + +sub get_tag_cloud { + + my $query = 'SELECT tags FROM articles WHERE enabled=1'; + my $sth = $dbh->prepare($query); + $sth->execute || die $dbh->errstr; + + # create a frequency table keyed by tag + my %freq; + while (my $result = $sth->fetchrow_hashref) { + map { $freq{$_}++ } split(/, */, $result->{'tags'}); + } + + # calculate the scaling denominator + my @tags = sort { $freq{$b} <=> $freq{$a} } keys %freq; + @tags = splice @tags, 0, $max_tags_in_cloud; + my $denominator = $freq{ $tags[0] } == $freq{ $tags[-1] } + ? ( 1 / 5 ) + : ( $freq{ $tags[0] } - $freq{ $tags[-1] } ) / 5; + + # build an HTML::Template friendly data structure + my @tag_cloud_data = (); + for my $tag (sort { lc $a cmp lc $b } @tags) { + my %row; + $row{'tag'} = $tag; + $row{'scale'} = int( ( $freq{$tag} - $freq{ $tags[-1] } ) / $denominator ); + push(@tag_cloud_data, \%row); + } + + return( \@tag_cloud_data ); + +} diff --git a/startup.pl b/startup.pl new file mode 100644 index 0000000..03083ad --- /dev/null +++ b/startup.pl @@ -0,0 +1,42 @@ + +# Blogsum +# +# Copyright (c) 2010 DixonGroup Consulting +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# - Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# - Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +use POSIX; +use CGI; +use DBI; +use DBD::SQLite; +use HTML::Template; +use XML::RSS; +use Net::SMTP; +use HTML::Entities; +use HTTP::Lite; + +1; diff --git a/themes/default/admin.tmpl b/themes/default/admin.tmpl new file mode 100644 index 0000000..0ecd1d7 --- /dev/null +++ b/themes/default/admin.tmpl @@ -0,0 +1,107 @@ + + + + <TMPL_VAR name="blog_title"> + /style.css" title="Default"> + + + +
              + + + +

              + /images/asterisk-red.gif" style="height: 20px; padding-right: 5px;"> +

              +
              + + + +
              +

              +

              +
              +
              +
              + + + +
              +

              ">   title

              +

              ">   uri

              +

              ">   tags (e.g. foo,bar,baz)

              +

              +

                

              + + + "> + +
              +
              + + + + +

              + + ">/images/play.gif" alt="Publish"> + + /images/play-disabled.gif" alt="Publish"> + + ">/images/plus.gif" alt="Edit"> + + ">/images/draft.gif" alt="Draft"> + + /images/draft-disabled.gif" alt="Draft"> + + ">/images/delete.gif" alt="Delete">     + + //" target="_new"> + + + + + + + + + +   submitted by + on +

              +
              +
              + + + + +
              +

              + ">/images/check.gif" alt="Publish"> + ">/images/delete.gif" alt="Delete"> +   comment in article   + //" target="_new"> +
              + submitted by + at +

              +

              + "" +

              +
              +
              +
              + +
              + + + diff --git a/themes/default/images/asterisk-green.gif b/themes/default/images/asterisk-green.gif new file mode 100644 index 0000000..cf57939 Binary files /dev/null and b/themes/default/images/asterisk-green.gif differ diff --git a/themes/default/images/asterisk-red.gif b/themes/default/images/asterisk-red.gif new file mode 100644 index 0000000..01c2341 Binary files /dev/null and b/themes/default/images/asterisk-red.gif differ diff --git a/themes/default/images/check.gif b/themes/default/images/check.gif new file mode 100644 index 0000000..06750c7 Binary files /dev/null and b/themes/default/images/check.gif differ diff --git a/themes/default/images/delete.gif b/themes/default/images/delete.gif new file mode 100644 index 0000000..b725248 Binary files /dev/null and b/themes/default/images/delete.gif differ diff --git a/themes/default/images/draft-disabled.gif b/themes/default/images/draft-disabled.gif new file mode 100644 index 0000000..1aa73ad Binary files /dev/null and b/themes/default/images/draft-disabled.gif differ diff --git a/themes/default/images/draft.gif b/themes/default/images/draft.gif new file mode 100644 index 0000000..0eee31b Binary files /dev/null and b/themes/default/images/draft.gif differ diff --git a/themes/default/images/play-disabled.gif b/themes/default/images/play-disabled.gif new file mode 100644 index 0000000..7849e9d Binary files /dev/null and b/themes/default/images/play-disabled.gif differ diff --git a/themes/default/images/play.gif b/themes/default/images/play.gif new file mode 100644 index 0000000..506a7a1 Binary files /dev/null and b/themes/default/images/play.gif differ diff --git a/themes/default/images/plus.gif b/themes/default/images/plus.gif new file mode 100644 index 0000000..adc2e7f Binary files /dev/null and b/themes/default/images/plus.gif differ diff --git a/themes/default/images/xml.gif b/themes/default/images/xml.gif new file mode 100644 index 0000000..8f7eb6a Binary files /dev/null and b/themes/default/images/xml.gif differ diff --git a/themes/default/index.tmpl b/themes/default/index.tmpl new file mode 100644 index 0000000..dbca045 --- /dev/null +++ b/themes/default/index.tmpl @@ -0,0 +1,179 @@ + + + + + <TMPL_VAR name="title"> + + /style.css" title="Default"> + + + + "> + + + +
              + +
              + +
              +

              + //"> + +

              +

              + by "> +

              + + + +
              + +

              Comments

              + +
              at , + + "> + + + + wrote in to say... +
              +

              + +

              +
              +
              +
              +
              +
              + + + +

              + /images/asterisk-red.gif" style="height: 20px; padding-right: 5px;"> +

              +
              + +

              + /images/asterisk-green.gif" style="height: 20px; padding-right: 5px;"> +

              +
              + + + +
              +

              Add a comment:

              +
              +

              ">   name

              +

              ">   email

              +

              ">   url

              +

              max length chars
              + +

              + + +

              + "> +
              +
              +
              +
              + + +
              + +
              + + + + + + + + diff --git a/themes/default/style.css b/themes/default/style.css new file mode 100644 index 0000000..7e2f549 --- /dev/null +++ b/themes/default/style.css @@ -0,0 +1,87 @@ +/* global styles */ +body { font-family: Georgia, Palatino, Palatino Linotype, Times, Times New Roman, serif; text-align: left; } +div, h1, h2, h3, h4, h5 { padding: 0; margin: 0; } +h1 { font-size: 1.8em; } +h2 { font-size: 1.3em; } +h3 { font-size: 0.9em; } +img { border: 0; } +p { font-size: 0.8em; } +pre { font-size: 0.7em; background-color: #ccc; padding: 15px 25px; } +pre, tt { font-family: Courier; } + +/* page header */ +#header { padding: .5em 0 1.5em 0; } +#header a { text-decoration: none; color: #aaa; } +#header h1 span { font-size: 0.5em; color: #ccc; } +#header h3 a:hover { color: #777; } + +/* page content wrapper */ +#wrapper { margin-left: 15%; margin-right: 10%; } + +/* article wrapper */ +#main { width: 65%; float: left; position: relative; } + +/* admin functions */ +#header #view { text-decoration: none; color: #777; } +#mod_story, #mod_story_disabled { font-size: 1.3em; font-weight: bold; } +#mod_user, #mod_date { font-size: 1.1em; font-weight: bold; } +#mod_story_disabled { color: #999; } +#preview { padding: 3% 5%; margin: 0 30% 10% 0; background-color: #fff; border: 1px dotted black; } +#preview h2 { color: #a14732; } +#preview div a { color: #a14732; text-decoration: none; border-bottom: 1px dashed; } +#preview ul { padding: 0; list-style: none; } + +/* article display */ +.article { width: 100%; padding: 4% 0; } +.article h2 { border-bottom: 1px solid #ccc; } +.article h2 a { text-decoration: none; } +.article h2 a, a#mod_story { color: #a14732; } +.article h3 { margin: 18px 0 10px 0; } +.article h3 span { font-size: 0.9em; color: #999; } +.article h3 span a { text-decoration: none; font-weight: normal; color: #09c; border-bottom: 1px dashed; } +.article div a { color: #a14732; text-decoration: none; border-bottom: 1px dashed; } +.article ul { padding: 0; list-style: none; } +.article ul li.comments_count { width: 20%; float: left; } +.article ul li.tags { width: 80%; float: right; text-align: right; } +.article ul li span, .tags span { font-size: 0.7em; } +.article ul li a, .tags span a { color: #09c; } +.article .comments h4 { padding-top: 30px; } +.article .comments h5 { font-size: 0.8em; font-weight: normal; } +.article .comments h5 span { font-size: 1.0em; font-weight: bold; } +.article .comments p { padding: 0 0 18px 10px; } +.article .tags span a { text-decoration: none; border-bottom: 1px dashed; } + +/* sidebar archive/rss styles */ +#sidebar { float: left; clear: none; position: relative; width: 30%; padding: 2% 0 0 5%; font-size: 0.8em; } +#sidebar h3 { font-size: 1.4em; color: #777; padding: 4px 0 17px 0; } +#sidebar h3 { padding-bottom: 5px; margin-bottom: 10px; border-bottom: 1px solid #ccc; } +#sidebar ul { list-style: none; padding: 0; margin: 0; } +#sidebar li span { color: #777; padding-left: 5px; } +#sidebar a { color: #c66; text-decoration: none; font-weight: bold; } +#sidebar ul li ul { padding-left: 20px; } +#sidebar #tagcloud h3, #feeds h3 { padding-top: 30px; } +#sidebar #feeds img { padding-right: 7px; } + +/* tag cloud */ +#tagcloud li { display: inline; } +#tagcloud .tagcloud_0 a { font-size: 1.0em; color: #8ea0d2; } +#tagcloud .tagcloud_1 a { font-size: 1.2em; color: #7c91cb; } +#tagcloud .tagcloud_2 a { font-size: 1.5em; color: #6981c3; } +#tagcloud .tagcloud_3 a { font-size: 1.8em; color: #5772bc; } +#tagcloud .tagcloud_4 a { font-size: 2.1em; color: #4764b3; } +#tagcloud .tagcloud_5 a { font-size: 2.4em; color: #405aa0; } + +/* footer styles */ +#footer { padding-top: 5%; clear: both; text-align: center; } +#footer ul { list-style: none; padding: 0; margin: 0; } +#footer li { padding: 15px 0; font-size: 0.9em; color: #aaa; border-top: 1px solid; } +#footer ul li.lastpage { float: left; border: none; } +#footer ul li.nextpage { float: right; text-align: right; border: none; } +#footer a { font-size: 0.9em; text-decoration: none; color: #777; } +#footer a:hover { text-decoration: underline; color: #09c; } + +/* messages and debugging */ +h3.error { padding-top: 2%; font-size: 1.1em; color: #c00; } +h3.message { padding-top: 2%; font-size: 0.9em; color: #060; } +pre#dump { background-color: #fff; } + -- cgit v1.2.3