line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Blog::Blosxom; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
29635
|
use warnings; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
40
|
|
4
|
1
|
|
|
1
|
|
7
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
39
|
|
5
|
|
|
|
|
|
|
|
6
|
1
|
|
|
1
|
|
1186
|
use FindBin; |
|
1
|
|
|
|
|
1522
|
|
|
1
|
|
|
|
|
48
|
|
7
|
1
|
|
|
1
|
|
1232
|
use FileHandle; |
|
1
|
|
|
|
|
14500
|
|
|
1
|
|
|
|
|
7
|
|
8
|
1
|
|
|
1
|
|
448
|
use File::Find; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
63
|
|
9
|
1
|
|
|
1
|
|
6
|
use File::Spec; |
|
1
|
|
|
|
|
5
|
|
|
1
|
|
|
|
|
20
|
|
10
|
1
|
|
|
1
|
|
1061
|
use File::stat; |
|
1
|
|
|
|
|
15206
|
|
|
1
|
|
|
|
|
10
|
|
11
|
1
|
|
|
1
|
|
1436
|
use Time::localtime; |
|
1
|
|
|
|
|
3053
|
|
|
1
|
|
|
|
|
72
|
|
12
|
|
|
|
|
|
|
|
13
|
1
|
|
|
1
|
|
9
|
use List::Util qw(min); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1370
|
|
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head1 NAME |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
Blog::Blosxom - A module version of the apparently inactive blosxom.cgi |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 VERSION |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
Version 0.01 |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=cut |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
our $VERSION = '0.10'; |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=head1 SYNOPSIS |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
use Blog::Blosxom; |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
... |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
my $blog = Blog::Blosxom->new(%params); |
34
|
|
|
|
|
|
|
$blog->run($path, $flavour); |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
A path comes in from somewhere - usually an HTTP request. This is applied to the |
37
|
|
|
|
|
|
|
blog directory. A configurable file extension is added to the path and if it matches |
38
|
|
|
|
|
|
|
a file that file is served as the matched entry. If the path matches a directory, |
39
|
|
|
|
|
|
|
all entries in that directory and in subdirectories are served. If the path looks |
40
|
|
|
|
|
|
|
like a date, all posts that match that date are served. A string is returned, which |
41
|
|
|
|
|
|
|
is usually printed back to CGI. The string is the matched entries, served in the |
42
|
|
|
|
|
|
|
specified output format, or flavour. The flavour is determined by the file extension |
43
|
|
|
|
|
|
|
on the incoming path, or a GET parameter, or a configured default. |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
=head1 DESCRIPTION |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
Blosxom is a blog engine. It is a rewrite of a CGI script found at www.blosxom.com. |
48
|
|
|
|
|
|
|
Blosxom uses the filesystem as the database for blog entries. Blosxom's run() |
49
|
|
|
|
|
|
|
method takes two parameters: the path and the flavour. |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
The CGI script that ships with the module is an example of how to use this module |
52
|
|
|
|
|
|
|
to reproduce the behaviour of the original Blosxom script, but the idea here is |
53
|
|
|
|
|
|
|
that it is up to you how to get this data. |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
Every file that ends in a configurable file extension and is placed somewhere within |
56
|
|
|
|
|
|
|
the blog root directory is, in the default situation, served up on a big page, in |
57
|
|
|
|
|
|
|
date order, having been processed and turned into blog posts. The set of files chosen |
58
|
|
|
|
|
|
|
for display is pared down by specifying a path to limit the set to only entries under |
59
|
|
|
|
|
|
|
that path, the narrowest possible filter of course being when the path actually |
60
|
|
|
|
|
|
|
matches a single blog entry. |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
Alternatively, the path may be a date in the format YYYY[/MM[/DD]], with the brackets |
63
|
|
|
|
|
|
|
denoting an optional section. This will be used to filter the posts by date instead |
64
|
|
|
|
|
|
|
of by location. All posts under the blog root are candidates for being returned. A |
65
|
|
|
|
|
|
|
TODO is to concatenate a date-style filter and a directory-style filter. |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
The module is designed to be extensible. That is, there are several methods in it |
68
|
|
|
|
|
|
|
that are designed to be overridden by subclasses to extend the functionality of |
69
|
|
|
|
|
|
|
Blog::Blosxom. You can see the L section below for details on these, and |
70
|
|
|
|
|
|
|
examples. |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
=head1 TERMS |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=head2 entry |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
Entry is used to mean both the individual article (its title, content and any |
77
|
|
|
|
|
|
|
metadata) and the file that contains that data. The entry is a filename with a |
78
|
|
|
|
|
|
|
customisable file extension. The file name and file extension have two differnent |
79
|
|
|
|
|
|
|
purposes. |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
The file extension is used to decide what is a blog post and what isn't. Files that |
82
|
|
|
|
|
|
|
have the defined file extension will be found by the blog engine and displayed, so |
83
|
|
|
|
|
|
|
long as they are within the filter. |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
The entry's filename is used to find a single entry. The path you provide to |
86
|
|
|
|
|
|
|
C is given the file extension defined for blog entries and then applied to the |
87
|
|
|
|
|
|
|
root directory. If this is a file, that is served. If not, it is tested without the |
88
|
|
|
|
|
|
|
file extension to be a directory. If it is a directory, all files ending with this |
89
|
|
|
|
|
|
|
extension and within that directory are served. |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=head2 story |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
The story is the formatted version of an entry. The file story.$flavour is used to |
94
|
|
|
|
|
|
|
insert the various parts of the blog entry into a template, which is then concatenated |
95
|
|
|
|
|
|
|
to a string which is itself returned from the C method. See below for what I |
96
|
|
|
|
|
|
|
mean by $flavour. |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
=head2 flavour |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
The flavour of the blog is simply the format in which the blog entry is served as a |
101
|
|
|
|
|
|
|
story. The flavour is determined by you. The CGI script takes the file extension |
102
|
|
|
|
|
|
|
from the request URI, or the C GET parameter. |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
If neither is provided, the default flavour is used. This is passed as a parameter |
105
|
|
|
|
|
|
|
to C and defaults to C. |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
=head2 component |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
A component is one of the five (currently) sections of the page: head, foot, story, |
110
|
|
|
|
|
|
|
date and content-type. The story and date components appear zero-to-many times on |
111
|
|
|
|
|
|
|
each page and the other three appear exactly once. |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=head2 template |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
A template is a flavoured component. It is defined as a file in the blog root whose |
116
|
|
|
|
|
|
|
filename is the component and whose extension is the flavour. E.g. C is |
117
|
|
|
|
|
|
|
the HTML-flavoured head component's template. |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
=head1 METHODS |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
=head2 new(%params) |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
Create a new blog. Parameters are provided in a hash and are: |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
=over |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
=item blog_title |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
This will be available in your templates as $blog_title, and by default appears in the |
130
|
|
|
|
|
|
|
page title and at the top of the page body. |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
This parameter is required. |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=item blog_description |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
This will be available in your templates as $blog_description. This does not appear in |
137
|
|
|
|
|
|
|
the default templates. |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=item blog_language |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
This is used for the RSS feed, as well as any other flavours you specify that have a |
142
|
|
|
|
|
|
|
language parameter. |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=item datadir |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
This is where blosxom will look for the blog's entries. A relative path will be relative |
147
|
|
|
|
|
|
|
to the script that is using this module. |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
This parameter is required. |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
=item url |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
This will override the base URL for the blog, which is automatic if you do not provide. |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
=item depth |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
This is how far down subdirectories of datadir to look for more blog entries. 0 is the |
158
|
|
|
|
|
|
|
default, which means to look down indefinitely. 1, therefore, means to look only in the |
159
|
|
|
|
|
|
|
datadir itself, up to n, which will look n-1 subdirectories down. |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=item num_entries |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
This is the maximum number of stories to display when multiple are found in the filter. |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
=item file_extension |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
By default, Blosxom will treat .txt files as blog entries. Change this to use a |
168
|
|
|
|
|
|
|
different file extension. Do not provide the dot that separates the filename and the |
169
|
|
|
|
|
|
|
file extension. |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
=item default_flavour |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
The flavour simply determines which set of templates to use to draw the blog. This |
174
|
|
|
|
|
|
|
defines which flavour to use by default. Vanilla blosxom has HTML and RSS flavours, |
175
|
|
|
|
|
|
|
of which RSS sucks really hard so really only HTML is available by default. |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
=item show_future_entries |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
This is a bit of a strange one, since by default, having a future date on an entry |
180
|
|
|
|
|
|
|
is a filesystem error, but if you want to override how the date of a template is |
181
|
|
|
|
|
|
|
defined, this will be helpful to you. |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
=item plugin_dir |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
Tells blosxom where to look for plugins. This is empty by default, which means it won't |
186
|
|
|
|
|
|
|
look for plugins. Relative paths will be taken relative to the script that uses this |
187
|
|
|
|
|
|
|
module. |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=item plugin_state_dir |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
Some plugins wish to store state. This is where the state data will be stored. It will |
192
|
|
|
|
|
|
|
need to be writable. Defaults to plugin_dir/state if you specify a plugin_dir. |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=item static_dir |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
Blosxom can publish your files statically, which means you run the script and it creates |
197
|
|
|
|
|
|
|
HTML files (for example) for each of your entries, instead of loading them dynamically. |
198
|
|
|
|
|
|
|
This defines where those files should go. I haven't actually implemented this because I |
199
|
|
|
|
|
|
|
don't really want to. |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
=item static_password |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
You have to provide a password if you want to use static rendering, as a security |
204
|
|
|
|
|
|
|
measure or something. |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
=item static_flavours |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
An arrayref of the flavours that Blosxom should generate statically. By default this is |
209
|
|
|
|
|
|
|
html and rss. |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
=item static_entries |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
Set this to a true value to turn on static generation of individual entries. Generally |
214
|
|
|
|
|
|
|
there is no point because your entries are static files already, but you may be using a |
215
|
|
|
|
|
|
|
plugin to alter them before rendering. |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
=back |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
=cut |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
sub new { |
222
|
0
|
|
|
0
|
1
|
|
my ($class, %params) = @_; |
223
|
|
|
|
|
|
|
|
224
|
0
|
|
|
|
|
|
for (qw(blog_title datadir)) { |
225
|
0
|
0
|
0
|
|
|
|
die "Required parameter $_ not provided to Blog::Blosxom->new" |
226
|
|
|
|
|
|
|
unless exists $params{$_} && $params{$_}; |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
|
229
|
0
|
0
|
|
|
|
|
die $params{datadir} . " does not exist!" unless -d $params{datadir}; |
230
|
|
|
|
|
|
|
|
231
|
0
|
|
|
|
|
|
my %defaults = ( |
232
|
|
|
|
|
|
|
blog_description => "", |
233
|
|
|
|
|
|
|
blog_language => "en", |
234
|
|
|
|
|
|
|
url => "", |
235
|
|
|
|
|
|
|
depth => 0, |
236
|
|
|
|
|
|
|
num_entries => 40, |
237
|
|
|
|
|
|
|
file_extension => "txt", |
238
|
|
|
|
|
|
|
default_flavour => "html", |
239
|
|
|
|
|
|
|
show_future_entries => 0, |
240
|
|
|
|
|
|
|
plugin_dir => "", |
241
|
|
|
|
|
|
|
plugin_state_dir => "", |
242
|
|
|
|
|
|
|
static_dir => "", |
243
|
|
|
|
|
|
|
static_password => "", |
244
|
|
|
|
|
|
|
static_flavours => [qw(html rss)], |
245
|
|
|
|
|
|
|
static_entries => 0, |
246
|
|
|
|
|
|
|
require_namespace => 0, |
247
|
|
|
|
|
|
|
); |
248
|
|
|
|
|
|
|
|
249
|
0
|
|
|
|
|
|
%params = (%defaults, %params); |
250
|
|
|
|
|
|
|
|
251
|
0
|
0
|
0
|
|
|
|
$params{plugin_state_dir} ||= $params{plugin_dir} if $params{plugin_dir}; |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
# Absolutify relative paths |
254
|
0
|
|
|
|
|
|
for my $key (qw(plugin_dir datadir static_dir plugin_state_dir)) { |
255
|
0
|
|
|
|
|
|
my $dir = $params{$key}; |
256
|
|
|
|
|
|
|
|
257
|
0
|
0
|
|
|
|
|
unless (File::Spec->file_name_is_absolute( $dir )) { |
258
|
0
|
|
|
|
|
|
$dir = File::Spec->catdir($FindBin::Bin, $dir); |
259
|
|
|
|
|
|
|
} |
260
|
|
|
|
|
|
|
|
261
|
0
|
|
|
|
|
|
$params{$key} = $dir; |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
0
|
|
|
|
|
|
my $self = bless \%params, $class; |
265
|
0
|
|
|
|
|
|
$self->_load_plugins; |
266
|
0
|
|
|
|
|
|
$self->_load_templates; |
267
|
|
|
|
|
|
|
|
268
|
0
|
|
|
|
|
|
return $self; |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
=head2 run ($path, $flavour) |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
It is now the responsibility of the user to provide the correct path and |
274
|
|
|
|
|
|
|
flavour. That is because there are several ways that you can gain this |
275
|
|
|
|
|
|
|
information, and it is not up to this engine to decide what they are. That is, |
276
|
|
|
|
|
|
|
this information comes from the request URL and, possibly, the parameter string, |
277
|
|
|
|
|
|
|
POST, cookies, what-have-you. |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
Therefore: |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
=over |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
=item |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
The path is the entire path up to the filename. The filename shall not include |
286
|
|
|
|
|
|
|
a file extension. The filename is optional, and if omitted, the directory given |
287
|
|
|
|
|
|
|
will be searched for all entries and an index page generated. |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
=item |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
The flavour can be gathered in any manner you desire. The original Blosxom |
292
|
|
|
|
|
|
|
script would use either a parameter string, C, or simply by using |
293
|
|
|
|
|
|
|
the flavour as the file extension for the requested path. |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
=back |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
No flavour provided will result in the default being used, obviously. No path |
298
|
|
|
|
|
|
|
being provided will result in the root path being used, since these are |
299
|
|
|
|
|
|
|
equivalent. |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
=cut |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
sub run { |
304
|
0
|
|
|
0
|
1
|
|
my ($self, $path, $flavour) = @_; |
305
|
|
|
|
|
|
|
|
306
|
0
|
|
0
|
|
|
|
$path ||= ""; |
307
|
0
|
|
0
|
|
|
|
$flavour ||= $self->{default_flavour}; |
308
|
|
|
|
|
|
|
|
309
|
0
|
|
|
|
|
|
$path =~ s|^/||; |
310
|
|
|
|
|
|
|
|
311
|
0
|
|
|
|
|
|
my @entries; |
312
|
|
|
|
|
|
|
|
313
|
0
|
|
|
|
|
|
$self->{path_info} = $path; |
314
|
0
|
|
|
|
|
|
$self->{flavour} = $flavour; |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
# Build an index page for the path |
317
|
0
|
|
|
|
|
|
@entries = $self->entries_for_path($path); |
318
|
0
|
|
|
|
|
|
@entries = $self->filter(@entries); |
319
|
0
|
|
|
|
|
|
@entries = $self->sort(@entries); |
320
|
|
|
|
|
|
|
|
321
|
0
|
|
|
|
|
|
@entries = @entries[0 .. min($#entries, $self->{num_entries}-1) ]; |
322
|
|
|
|
|
|
|
|
323
|
0
|
|
|
|
|
|
$self->{entries} = []; |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
# A special template. The user is going to need to know this, but not print it. |
326
|
0
|
|
|
|
|
|
$self->{content_type} = $self->template($path, "content_type", $flavour); |
327
|
|
|
|
|
|
|
|
328
|
0
|
|
|
|
|
|
my @templates; |
329
|
|
|
|
|
|
|
|
330
|
0
|
|
|
|
|
|
my $date = ""; |
331
|
0
|
|
|
|
|
|
for my $entry (@entries) { |
332
|
|
|
|
|
|
|
# TODO: Here is an opportunity to style the entries in the style |
333
|
|
|
|
|
|
|
# of the subdir they came from. |
334
|
0
|
|
|
|
|
|
my $entry_data = $self->entry_data($entry); |
335
|
0
|
|
|
|
|
|
push @{$self->{entries}}, $entry_data; |
|
0
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
|
337
|
0
|
|
|
|
|
|
my $entry_date = join " ", @{$entry_data}{qw(da mo yr)}; # To create a date entry when it changes. |
|
0
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
|
339
|
0
|
0
|
|
|
|
|
if ($date ne $entry_date) { |
340
|
0
|
|
|
|
|
|
$date = $entry_date; |
341
|
0
|
|
|
|
|
|
my $date_data = { map { $_ => $entry_data->{$_} } qw( yr mo mo_num dw da hr min ) }; |
|
0
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
|
343
|
0
|
|
|
|
|
|
push @templates, $self->interpolate($self->template($path, "date", $flavour), $date_data); |
344
|
|
|
|
|
|
|
} |
345
|
|
|
|
|
|
|
|
346
|
0
|
|
|
|
|
|
push @templates, $self->interpolate($self->template($path, "story", $flavour), $entry_data); |
347
|
|
|
|
|
|
|
} |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
# If we do head and foot last, we let plugins use data about the contents in them. |
350
|
0
|
|
|
|
|
|
unshift @templates, $self->interpolate($self->template($path, "head", $flavour), $self->head_data()); |
351
|
0
|
|
|
|
|
|
push @templates, $self->interpolate($self->template($path, "foot", $flavour), $self->foot_data()); |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
# A skip plugin will stop processing just before anything is output. |
354
|
|
|
|
|
|
|
# Not sure why. |
355
|
0
|
0
|
|
|
|
|
return if $self->_check_plugins('skip'); |
356
|
|
|
|
|
|
|
|
357
|
0
|
|
|
|
|
|
return join "\n", @templates; |
358
|
|
|
|
|
|
|
} |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
=head2 template($path, $component, $flavour) |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
Returns a chunk of markup for the requested component in the requested flavour |
363
|
|
|
|
|
|
|
for the requested path. The path will be the one given to C. |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
By default the template file chosen is the file C<$component.$flavour> within |
366
|
|
|
|
|
|
|
the C<$path> provided, and if not found, upwards from there to the blog root. |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
The templates used are I, I, I, I and I, |
369
|
|
|
|
|
|
|
so the HTML template for the head would be C. |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
=cut |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
sub template { |
374
|
0
|
|
|
0
|
1
|
|
my ($self, $path, $comp, $flavour) = @_; |
375
|
|
|
|
|
|
|
|
376
|
0
|
|
|
|
|
|
my $template; |
377
|
|
|
|
|
|
|
|
378
|
0
|
0
|
|
|
|
|
unless ($template = $self->_check_plugins('template', @_)) { |
379
|
0
|
|
|
|
|
|
my $fn = File::Spec->catfile($self->{datadir}, $path, "$comp.$flavour"); |
380
|
|
|
|
|
|
|
|
381
|
0
|
|
|
|
|
|
while (1) { |
382
|
|
|
|
|
|
|
# Return the contents of this template if the file exists. If it is empty, |
383
|
|
|
|
|
|
|
# we have defaults set up. |
384
|
0
|
0
|
|
|
|
|
if (-e $fn) { |
385
|
0
|
|
|
|
|
|
open my $fh, "<", $fn; |
386
|
0
|
|
|
|
|
|
$template = join '', <$fh>; |
387
|
|
|
|
|
|
|
} |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
# Stop looking when there is no $path to go between datadir and the |
390
|
|
|
|
|
|
|
# template file. For portability, we can't check whether it is a "/" |
391
|
0
|
0
|
0
|
|
|
|
last if !$path or $path eq File::Spec->rootdir; |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
# Look one dir higher and go again. |
394
|
0
|
|
|
|
|
|
my @dir = File::Spec->splitdir($path); |
395
|
0
|
|
|
|
|
|
pop @dir; |
396
|
0
|
|
|
|
|
|
$path = File::Spec->catdir(@dir); |
397
|
0
|
|
|
|
|
|
$fn = File::Spec->catfile($self->{datadir}, $path, "$comp.$flavour"); |
398
|
|
|
|
|
|
|
} |
399
|
|
|
|
|
|
|
} |
400
|
|
|
|
|
|
|
|
401
|
0
|
|
0
|
|
|
|
$template ||= $self->{template}{$flavour}{$comp} || $self->{template}{error}{$comp}; |
|
|
|
0
|
|
|
|
|
402
|
|
|
|
|
|
|
|
403
|
0
|
|
|
|
|
|
return $template; |
404
|
|
|
|
|
|
|
} |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
=head2 entries_for_path |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
Given a path, find the entries that should be returned. This may be overridden |
409
|
|
|
|
|
|
|
by a plugin defining the function "entries", or this "entries_for_path" function. |
410
|
|
|
|
|
|
|
They are synonymous. See L for information on overriding this method. |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
The path will not include C. |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
It implements two behaviours. If the path requested is a real path then it is |
415
|
|
|
|
|
|
|
searched for all blog entries, honouring the depth parameter that limits how far |
416
|
|
|
|
|
|
|
below the C we should look for blog entries. |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
If it is not then it is expected to be a date, being in 1, 2 or 3 parts, in one |
419
|
|
|
|
|
|
|
true date ISO format. This version will return all entries filtered by this date |
420
|
|
|
|
|
|
|
specification. See also L, which determines the date on which the |
421
|
|
|
|
|
|
|
post was made and can be overridden in plugins. |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
=cut |
424
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
sub entries_for_path { |
426
|
0
|
|
|
0
|
1
|
|
my ($self, $path) = @_; |
427
|
|
|
|
|
|
|
|
428
|
0
|
|
|
|
|
|
my @entries; |
429
|
|
|
|
|
|
|
|
430
|
0
|
0
|
|
|
|
|
return @entries if @entries = $self->_check_plugins('entries', @_); |
431
|
|
|
|
|
|
|
|
432
|
0
|
|
|
|
|
|
my $abs_path = File::Spec->catdir( $self->{datadir}, $path ); |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
# If this is an entry, return it. |
435
|
0
|
0
|
|
|
|
|
if (-f $abs_path . "." . $self->{file_extension}) { |
436
|
0
|
|
|
|
|
|
my $date = $self->date_of_post($abs_path . "." . $self->{file_extension}); |
437
|
0
|
|
|
|
|
|
return [ $path . "." . $self->{file_extension}, { date => $date } ]; |
438
|
|
|
|
|
|
|
} |
439
|
|
|
|
|
|
|
|
440
|
0
|
0
|
|
|
|
|
if (-d $abs_path) { |
441
|
|
|
|
|
|
|
# We use File::Find on a real path |
442
|
|
|
|
|
|
|
my $find = sub { |
443
|
0
|
0
|
|
0
|
|
|
return unless -f; |
444
|
|
|
|
|
|
|
|
445
|
0
|
|
|
|
|
|
my $rel_path = File::Spec->abs2rel( $File::Find::dir, $self->{datadir} ); |
446
|
0
|
|
|
|
|
|
my $curdepth = File::Spec->splitdir($rel_path); |
447
|
|
|
|
|
|
|
|
448
|
0
|
|
|
|
|
|
my $fex = $self->{file_extension}; |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
# not specifying a file extension is a bit silly. |
451
|
0
|
0
|
0
|
|
|
|
if (!$fex || /\.$fex$/) { |
452
|
1
|
|
|
1
|
|
8
|
no warnings "once"; # File::Find::name causes a warning. |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
889
|
|
453
|
|
|
|
|
|
|
|
454
|
0
|
|
|
|
|
|
my $rel_file = File::Spec->catfile( $rel_path, $_ ); |
455
|
0
|
|
|
|
|
|
$rel_file = File::Spec->canonpath($rel_file); # This removes any artefacts like ./ |
456
|
0
|
|
|
|
|
|
my $date = $self->date_of_post($File::Find::name); |
457
|
0
|
|
|
|
|
|
my $file_info = { date => $date }; |
458
|
|
|
|
|
|
|
|
459
|
0
|
|
|
|
|
|
push @entries, [$rel_file, $file_info ]; |
460
|
|
|
|
|
|
|
} |
461
|
|
|
|
|
|
|
|
462
|
0
|
|
0
|
|
|
|
$File::Find::prune = ($self->{depth} && $curdepth > $self->{depth}); |
463
|
0
|
|
|
|
|
|
}; |
464
|
|
|
|
|
|
|
|
465
|
0
|
|
|
|
|
|
File::Find::find( $find, $abs_path ); |
466
|
|
|
|
|
|
|
} |
467
|
|
|
|
|
|
|
else { |
468
|
|
|
|
|
|
|
# We use date stuff on a fake path. |
469
|
|
|
|
|
|
|
# TODO: see whether we can split the path into a date section and a real section. |
470
|
0
|
|
|
|
|
|
my @ymd = File::Spec->splitdir( $path ); |
471
|
0
|
|
|
|
|
|
my @all_entries = $self->entries_for_path( "" ); |
472
|
|
|
|
|
|
|
|
473
|
0
|
|
|
|
|
|
my @entries = grep { |
474
|
0
|
|
|
|
|
|
my $post_date = localtime( $_->[1]{date} ); |
475
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
# requested year |
477
|
0
|
0
|
0
|
|
|
|
($ymd[0] == ($post_date->year+1900)) |
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
478
|
|
|
|
|
|
|
and |
479
|
|
|
|
|
|
|
# matches month, or moth not requested |
480
|
|
|
|
|
|
|
(!$ymd[1] || $ymd[1] == ($post_date->mon+1)) |
481
|
|
|
|
|
|
|
and |
482
|
|
|
|
|
|
|
# matches day, or day not rquested |
483
|
|
|
|
|
|
|
(!$ymd[2] || $ymd[2] == $post_date->mday) |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
? $_ : () |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
} @all_entries; |
488
|
|
|
|
|
|
|
} |
489
|
|
|
|
|
|
|
|
490
|
0
|
|
|
|
|
|
return @entries; |
491
|
|
|
|
|
|
|
} |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
=head2 date_of_post ($fn) |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
Return a unix timestamp defining the date of the post. The filename provided to |
496
|
|
|
|
|
|
|
the method is an absolute filename. |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
=cut |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
sub date_of_post { |
501
|
0
|
|
|
0
|
1
|
|
my ($self, $fn) = @_; |
502
|
|
|
|
|
|
|
|
503
|
0
|
|
|
|
|
|
my $dop; |
504
|
0
|
0
|
|
|
|
|
return $dop if $dop = $self->_check_plugins("date_of_post", @_); |
505
|
|
|
|
|
|
|
|
506
|
0
|
|
|
|
|
|
return stat($fn)->mtime; |
507
|
|
|
|
|
|
|
} |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
=head2 filter (@entries) |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
This function returns only the desired entries from the array passed in. By |
512
|
|
|
|
|
|
|
default it just returns the array back, so is just a place to check for plugins. |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
This can be overridden by plugins in order to alter the way the module filters |
515
|
|
|
|
|
|
|
the files. See L for more details. |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
=cut |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
sub filter { |
520
|
0
|
|
|
0
|
1
|
|
my ($self, @entries) = @_; |
521
|
|
|
|
|
|
|
|
522
|
0
|
|
|
|
|
|
my @remaining_files = $self->_check_plugins("filter", @_); |
523
|
|
|
|
|
|
|
|
524
|
0
|
|
0
|
|
|
|
return @remaining_files || @entries; |
525
|
|
|
|
|
|
|
} |
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
=head2 sort (@entries) |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
Sort @entries and return the new list. |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
Default behaviour is to sort by date. |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
=cut |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
sub sort { |
536
|
0
|
|
|
0
|
1
|
|
my ($self, @entries) = @_; |
537
|
|
|
|
|
|
|
|
538
|
0
|
|
|
|
|
|
my @sorted_entries; |
539
|
0
|
0
|
|
|
|
|
return @sorted_entries if @sorted_entries = $self->_check_plugins("sort", @_); |
540
|
|
|
|
|
|
|
|
541
|
0
|
|
|
|
|
|
@sorted_entries = sort { $a->[1]->{date} <=> $b->[1]->{date} } @entries; |
|
0
|
|
|
|
|
|
|
542
|
0
|
|
|
|
|
|
return @sorted_entries; |
543
|
|
|
|
|
|
|
} |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
=head2 static_mode($password, $on) |
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
Sets static mode. Pass in the password to turn it on. Turns it off if it is already on. |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
=cut |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
sub static_mode { |
552
|
0
|
|
|
0
|
1
|
|
my ($self, $password, $on) = @_; |
553
|
|
|
|
|
|
|
|
554
|
0
|
0
|
|
|
|
|
die "No static dir defined" unless $self->{static_dir}; |
555
|
0
|
0
|
|
|
|
|
die "No static publishing password defined" unless $self->{static_password}; |
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
# Set it to toggle if we don't specify. |
558
|
0
|
0
|
|
|
|
|
$on = !$self->{static_mode} if !defined $on; |
559
|
|
|
|
|
|
|
|
560
|
0
|
0
|
0
|
|
|
|
if ($self->{static_mode} && !$on) { |
561
|
0
|
|
|
|
|
|
$self->{static_mode} = 0; |
562
|
0
|
|
|
|
|
|
$blosxom::static_or_dynamic = 'dynamic'; |
563
|
0
|
|
|
|
|
|
return; |
564
|
|
|
|
|
|
|
} |
565
|
|
|
|
|
|
|
|
566
|
0
|
0
|
0
|
|
|
|
if ($on && $password eq $self->{static_password}) { |
567
|
0
|
|
|
|
|
|
$self->{static_mode} = 1; |
568
|
0
|
|
|
|
|
|
$blosxom::static_or_dynamic = 'static'; |
569
|
|
|
|
|
|
|
} |
570
|
|
|
|
|
|
|
} |
571
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
=head2 interpolate($template, $extra_data) |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
Each template is interpolated, which means that variables are swapped out if |
575
|
|
|
|
|
|
|
they exist. Each template may have template-specific variables; e.g. the story |
576
|
|
|
|
|
|
|
template has a title and a body. Those are provided in the extra data, which is |
577
|
|
|
|
|
|
|
a hashref with the variable name to be replaced (without the $) as the key, and |
578
|
|
|
|
|
|
|
the corresponding value as the value. |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
By default, a different set of variables are available to each template: |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
=head3 All templates |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
These are defined by you when you provide them to new() or run() |
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
=over |
587
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
=item blog_title |
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
=item blog_description |
591
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
=item blog_language |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
=item url |
595
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
=item path_info |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
=item flavour |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
=back |
601
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
=head3 Story (entry) template |
603
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
These are defined by the entry. |
605
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
=over |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
=item title |
609
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
Post title |
611
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
=item body |
613
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
The body of the post |
615
|
|
|
|
|
|
|
|
616
|
|
|
|
|
|
|
=item yr |
617
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
=item mo |
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
=item mo_num |
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
=item da |
623
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
=item dw |
625
|
|
|
|
|
|
|
|
626
|
|
|
|
|
|
|
=item hr |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
=item min |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
Timestamp of entry. mo = month name; dw = day name |
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
=item path |
633
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
The folder in which the post lives, relative to the blog's base URL. |
635
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
=item fn |
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
The filename of the post, sans extension. |
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
=back |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
=head3 Head template |
643
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
=over |
645
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
=item title |
647
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
This method can be overridden by a plugin. |
649
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
=cut |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
sub interpolate { |
653
|
0
|
|
|
0
|
1
|
|
my ($self, $template, $extra_data) = @_; |
654
|
|
|
|
|
|
|
|
655
|
0
|
|
|
|
|
|
my $done; |
656
|
0
|
0
|
|
|
|
|
return $done if $done = $self->_check_plugins("interpolate", @_); |
657
|
|
|
|
|
|
|
|
658
|
0
|
|
|
|
|
|
for my $var (keys %$extra_data){ |
659
|
0
|
0
|
|
|
|
|
if($self->{require_namespace}) { |
660
|
0
|
|
|
|
|
|
$template =~ s/\$blosxom::$var\b/$extra_data->{$var}/g; |
661
|
|
|
|
|
|
|
} |
662
|
|
|
|
|
|
|
else { |
663
|
0
|
|
|
|
|
|
$template =~ s/\$(?:blosxom::)?$var\b/$extra_data->{$var}/g; |
664
|
|
|
|
|
|
|
} |
665
|
|
|
|
|
|
|
} |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
# The blosxom docs say these are global vars, so let's mimic that. |
668
|
0
|
|
|
|
|
|
for my $var (qw(blog_title blog_description blog_language url path_info flavour)) { |
669
|
|
|
|
|
|
|
# You can set this option so that only $blosxom::foo variables are interpolated |
670
|
0
|
0
|
|
|
|
|
if($self->{require_namespace}) { |
671
|
0
|
|
|
|
|
|
$template =~ s/\$blosxom::$var\b/$self->{$var}/g; |
672
|
|
|
|
|
|
|
} |
673
|
|
|
|
|
|
|
else { |
674
|
0
|
|
|
|
|
|
$template =~ s/\$(?:blosxom::)?$var\b/$self->{$var}/g; |
675
|
|
|
|
|
|
|
} |
676
|
|
|
|
|
|
|
} |
677
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
{ |
679
|
1
|
|
|
1
|
|
8
|
no strict 'vars'; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
1306
|
|
|
0
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
# Non-blosxom:: variables must be namespaced. I can't be bothered |
681
|
|
|
|
|
|
|
# making it work with more than one :: in it just yet, sorry. |
682
|
0
|
|
|
|
|
|
$template =~ s/\$(\w+::\w+)/"defined \$$1 ? \$$1 : '\$$1'"/gee; |
|
0
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
} |
684
|
|
|
|
|
|
|
|
685
|
0
|
|
|
|
|
|
return $template; |
686
|
|
|
|
|
|
|
} |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
=head2 entry_data ($entry) |
689
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
Provided with the entry data, which is an arrayref with the entry filename, |
691
|
|
|
|
|
|
|
relative to datadir, in the first slot and a hashref in the second. The hashref |
692
|
|
|
|
|
|
|
will have at least a date entry, being a UNIX timestamp for the entry. See |
693
|
|
|
|
|
|
|
the section on plugin entries. |
694
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
Returns a hashref containing the following keys: |
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
=over |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
=item title |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
Post title |
702
|
|
|
|
|
|
|
|
703
|
|
|
|
|
|
|
=item body |
704
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
The body of the post |
706
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
=item yr |
708
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
=item mo |
710
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
=item mo_num |
712
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
=item da |
714
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
=item dw |
716
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
=item hr |
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
=item min |
720
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
Timestamp of entry. mo = month name; dw = day name |
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
=item path |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
The folder in which the post lives, relative to the blog's base URL. |
726
|
|
|
|
|
|
|
|
727
|
|
|
|
|
|
|
=item fn |
728
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
The filename of the post, sans extension. |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
=back |
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
These should be returned such that it is true that |
734
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
$path . "/" . $fn . "." . $flavour eq $request_url |
736
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
i.e. these components together are what was originally asked for. (Note that |
738
|
|
|
|
|
|
|
flavour is a variable available to templates but not returned by this method.) |
739
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
=cut |
741
|
|
|
|
|
|
|
|
742
|
|
|
|
|
|
|
sub entry_data { |
743
|
0
|
|
|
0
|
1
|
|
my ($self, $entry) = @_; |
744
|
|
|
|
|
|
|
|
745
|
0
|
|
|
|
|
|
my $entry_data = {}; |
746
|
|
|
|
|
|
|
|
747
|
0
|
|
|
|
|
|
my $fn = $entry->[0]; |
748
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
{ |
750
|
0
|
|
|
|
|
|
open my $fh, "<", File::Spec->catfile($self->{datadir}, $fn); |
|
0
|
|
|
|
|
|
|
751
|
0
|
|
|
|
|
|
my $title = <$fh>; chomp $title; |
|
0
|
|
|
|
|
|
|
752
|
0
|
|
|
|
|
|
$entry_data->{title} = $title; |
753
|
|
|
|
|
|
|
|
754
|
0
|
|
|
|
|
|
$entry_data->{body} = join "", <$fh>; |
755
|
0
|
|
|
|
|
|
close $fh; |
756
|
|
|
|
|
|
|
} |
757
|
|
|
|
|
|
|
|
758
|
|
|
|
|
|
|
{ |
759
|
0
|
|
|
|
|
|
my @path = (File::Spec->splitpath($fn)); |
|
0
|
|
|
|
|
|
|
760
|
0
|
|
|
|
|
|
$fn = pop @path; |
761
|
0
|
|
|
|
|
|
$fn =~ s/\.$self->{file_extension}$//; |
762
|
0
|
|
|
|
|
|
$entry_data->{fn} = $fn; |
763
|
0
|
|
|
|
|
|
$entry_data->{path} = File::Spec->catpath(@path); |
764
|
|
|
|
|
|
|
} |
765
|
|
|
|
|
|
|
|
766
|
|
|
|
|
|
|
{ |
767
|
0
|
|
|
|
|
|
my $i = 1; |
|
0
|
|
|
|
|
|
|
768
|
0
|
|
|
|
|
|
my %month2num = map {$_, $i++} qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); |
|
0
|
|
|
|
|
|
|
769
|
0
|
|
|
|
|
|
my $c_time = ctime($entry->[1]->{date}); |
770
|
|
|
|
|
|
|
|
771
|
0
|
|
|
|
|
|
my($dw,$mo,$da,$hr,$min,$yr) = ( $c_time =~ /(\w{3}) +(\w{3}) +(\d{1,2}) +(\d{2}):(\d{2}):\d{2} +(\d{4})$/ ); |
772
|
|
|
|
|
|
|
|
773
|
0
|
|
|
|
|
|
$da = sprintf("%02d", $da); |
774
|
0
|
|
|
|
|
|
my $mo_num = $month2num{$mo}; |
775
|
0
|
|
|
|
|
|
$mo_num = sprintf("%02d", $mo_num); |
776
|
|
|
|
|
|
|
|
777
|
0
|
|
|
|
|
|
@{$entry_data}{qw(dw mo da yr mo_num hr min)} = ($dw, $mo, $da, $yr, $mo_num, $hr, $min); |
|
0
|
|
|
|
|
|
|
778
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
# Keep track of the latest date we find. |
780
|
0
|
0
|
|
|
|
|
$self->{latest_entry_date} = 0 if !exists $self->{latest_entry_date}; |
781
|
0
|
0
|
|
|
|
|
$self->{latest_entry_date} = $entry->[1]->{date} |
782
|
|
|
|
|
|
|
if $entry->[1]->{date} > $self->{latest_entry_date}; |
783
|
|
|
|
|
|
|
} |
784
|
|
|
|
|
|
|
|
785
|
0
|
|
|
|
|
|
return $entry_data; |
786
|
|
|
|
|
|
|
} |
787
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
=head2 head_data () |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
Return the data you want to be available to the head template. The head is |
791
|
|
|
|
|
|
|
attached to the top of the output I the entries have been run through, |
792
|
|
|
|
|
|
|
so you have the data for all the entry data available to you in the arrayref |
793
|
|
|
|
|
|
|
$self->{entries}. |
794
|
|
|
|
|
|
|
|
795
|
|
|
|
|
|
|
Example: |
796
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
my $self = shift; |
798
|
|
|
|
|
|
|
my $data = {}; |
799
|
|
|
|
|
|
|
if(@{$self->{entries}} == 1) { |
800
|
|
|
|
|
|
|
$data->{title} = $self->{entries}->[0]->{title} |
801
|
|
|
|
|
|
|
} |
802
|
|
|
|
|
|
|
return $data; |
803
|
|
|
|
|
|
|
|
804
|
|
|
|
|
|
|
=cut |
805
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
sub head_data { |
807
|
0
|
|
|
0
|
1
|
|
+{}; |
808
|
|
|
|
|
|
|
} |
809
|
|
|
|
|
|
|
|
810
|
|
|
|
|
|
|
=head2 foot_data () |
811
|
|
|
|
|
|
|
|
812
|
|
|
|
|
|
|
Return the data you want to be available in your foot template. This is attached |
813
|
|
|
|
|
|
|
to the output after everything else, as you'd expect. |
814
|
|
|
|
|
|
|
|
815
|
|
|
|
|
|
|
=cut |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
sub foot_data { |
818
|
0
|
|
|
0
|
1
|
|
+{}; |
819
|
|
|
|
|
|
|
} |
820
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
## PRIVATE FUNCTIONS |
822
|
|
|
|
|
|
|
# Purposely not in the POD |
823
|
|
|
|
|
|
|
|
824
|
|
|
|
|
|
|
## _load_plugins |
825
|
|
|
|
|
|
|
# Trawl the plugins directory and look for plugins. Put them in the object hash. |
826
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
sub _load_plugins { |
828
|
0
|
|
|
0
|
|
|
my $self = shift; |
829
|
|
|
|
|
|
|
|
830
|
0
|
|
|
|
|
|
my $dir = $self->{plugins_dir}; |
831
|
0
|
0
|
|
|
|
|
return unless $dir; |
832
|
|
|
|
|
|
|
|
833
|
0
|
|
|
|
|
|
opendir my($plugins), $dir; |
834
|
|
|
|
|
|
|
|
835
|
|
|
|
|
|
|
# blosxom docs say modules ending in _ will not be loaded. |
836
|
0
|
0
|
0
|
|
|
|
for my $plugin (grep { /^\w+$/ && !/_$/ && -f File::Spec->join($dir, $_) } |
|
0
|
|
|
|
|
|
|
837
|
|
|
|
|
|
|
sort readdir $plugins) { |
838
|
|
|
|
|
|
|
# blosxom docs say you can order modules by prefixing numbers. |
839
|
0
|
|
|
|
|
|
$plugin =~ s/^\d+//; |
840
|
|
|
|
|
|
|
|
841
|
|
|
|
|
|
|
# This will blow up if your package name is not the same as your file name. |
842
|
0
|
|
|
|
|
|
require "$dir/$plugin"; |
843
|
0
|
0
|
|
|
|
|
if ($plugin->start()) { |
844
|
0
|
|
|
|
|
|
$self->{active_plugins}->{$plugin} = 1; |
845
|
|
|
|
|
|
|
|
846
|
0
|
|
0
|
|
|
|
$self->{plugins_ordered} ||= []; |
847
|
0
|
|
|
|
|
|
push @{$self->{plugins_ordered}}, $plugin; |
|
0
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
} |
849
|
|
|
|
|
|
|
} |
850
|
|
|
|
|
|
|
|
851
|
0
|
|
|
|
|
|
closedir $plugins; |
852
|
|
|
|
|
|
|
} |
853
|
|
|
|
|
|
|
|
854
|
|
|
|
|
|
|
## _load_templates |
855
|
|
|
|
|
|
|
# Read the default templates from DATA. Later the plugins get an opportunity to |
856
|
|
|
|
|
|
|
# override what happens when the real templates are read in, so we don't do that here. |
857
|
|
|
|
|
|
|
|
858
|
|
|
|
|
|
|
sub _load_templates { |
859
|
0
|
|
|
0
|
|
|
my $self = shift; |
860
|
|
|
|
|
|
|
|
861
|
0
|
|
|
|
|
|
while () { |
862
|
0
|
0
|
|
|
|
|
last if /^(__END__)?$/; |
863
|
0
|
|
|
|
|
|
my($flavour, $comp, $txt) = /^(\S+)\s(\S+)\s(.*)$/; |
864
|
0
|
|
|
|
|
|
$txt =~ s/\\n/\n/mg; |
865
|
0
|
|
|
|
|
|
$self->{template}{$flavour}{$comp} = $txt; |
866
|
|
|
|
|
|
|
} |
867
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
} |
869
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
## _check_plugins |
871
|
|
|
|
|
|
|
# Look for plugins that can do the first arg, and pass them the rest of the args. |
872
|
|
|
|
|
|
|
# Return the first value returned by a plugin. |
873
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
sub _check_plugins { |
875
|
0
|
|
|
0
|
|
|
my ($self, $method, @underscore) = @_; |
876
|
|
|
|
|
|
|
|
877
|
0
|
0
|
|
|
|
|
return unless $self->{plugins_ordered}; |
878
|
0
|
0
|
|
|
|
|
return if $self->{no_plugins}; |
879
|
|
|
|
|
|
|
|
880
|
0
|
|
|
|
|
|
for my $plugin (@{$self->{plugins_ordered}}) { |
|
0
|
|
|
|
|
|
|
881
|
0
|
|
|
|
|
|
local $self->{no_plugins} = 1; |
882
|
0
|
|
|
|
|
|
my @return; |
883
|
0
|
0
|
|
|
|
|
@return = $plugin->$method($self, @underscore) if $plugin->can($method); |
884
|
|
|
|
|
|
|
|
885
|
0
|
0
|
|
|
|
|
return @return if @return; |
886
|
|
|
|
|
|
|
} |
887
|
|
|
|
|
|
|
} |
888
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
=head1 USAGE |
890
|
|
|
|
|
|
|
|
891
|
|
|
|
|
|
|
=head2 Quick start |
892
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
To quick start, first you need to create a Blosxom object. You have to provide |
894
|
|
|
|
|
|
|
three parameters to the C method: |
895
|
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
datadir |
897
|
|
|
|
|
|
|
blog_title |
898
|
|
|
|
|
|
|
blog_description |
899
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
The latter is likely to be dropped as a requirement soon. |
901
|
|
|
|
|
|
|
|
902
|
|
|
|
|
|
|
Then you need to find some way of collecting a path and a flavour from the user. |
903
|
|
|
|
|
|
|
The original script used the URL provided by the web server. You provide these |
904
|
|
|
|
|
|
|
to the C method. |
905
|
|
|
|
|
|
|
|
906
|
|
|
|
|
|
|
use Blog::Blosxom; |
907
|
|
|
|
|
|
|
use CGI qw(standard); |
908
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
my $blog = Blog::Blosxom->new( |
910
|
|
|
|
|
|
|
datadir => '/var/www/blosxom/entries', |
911
|
|
|
|
|
|
|
blog_title => 'Descriptive blog title.', |
912
|
|
|
|
|
|
|
blog_description => 'Descriptive blog description.', |
913
|
|
|
|
|
|
|
); |
914
|
|
|
|
|
|
|
|
915
|
|
|
|
|
|
|
my $path = path_info() || param('path'); |
916
|
|
|
|
|
|
|
my ($flavour) = $path =~ s/(\.\w+)$// || param('flav'); |
917
|
|
|
|
|
|
|
|
918
|
|
|
|
|
|
|
print header, |
919
|
|
|
|
|
|
|
$blog->run($path, $flavour); |
920
|
|
|
|
|
|
|
|
921
|
|
|
|
|
|
|
The above is a complete CGI script that will run your blog. Note that C |
922
|
|
|
|
|
|
|
is a CGI function. Don't print that if you're not using CGI! |
923
|
|
|
|
|
|
|
|
924
|
|
|
|
|
|
|
Now that we know how to run Blosxom we can look at how to make entries. |
925
|
|
|
|
|
|
|
|
926
|
|
|
|
|
|
|
=head2 Entries |
927
|
|
|
|
|
|
|
|
928
|
|
|
|
|
|
|
To create an entry, create a plaintext file in your C with the .txt |
929
|
|
|
|
|
|
|
extension. The first line of this file is the title of the post and the rest |
930
|
|
|
|
|
|
|
is the body. |
931
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
This post will be displayed if it is somewhere under the C<$path> you provided |
933
|
|
|
|
|
|
|
to C, unless it is the 41st such file, because by default only 40 are |
934
|
|
|
|
|
|
|
displayed at once. |
935
|
|
|
|
|
|
|
|
936
|
|
|
|
|
|
|
The C part of all this is configurable in C. |
937
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
=head2 Flavour |
939
|
|
|
|
|
|
|
|
940
|
|
|
|
|
|
|
The flavour that you provide determines which set of templates are used to |
941
|
|
|
|
|
|
|
compose the output blog. |
942
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
You may be wondering about the fact that the blog entry ends with .txt, but in |
944
|
|
|
|
|
|
|
the CGI script we have used the extension to determine the flavour. The file |
945
|
|
|
|
|
|
|
extension is ignored when mapping the path to the file system, so your path |
946
|
|
|
|
|
|
|
could feasibly match a single entry, which will of course be served on its own. |
947
|
|
|
|
|
|
|
|
948
|
|
|
|
|
|
|
The template to be chosen is a file whose file extension is the current flavour |
949
|
|
|
|
|
|
|
and whose file name is the template in question. Have a look at the docs for the |
950
|
|
|
|
|
|
|
C function. |
951
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
Writing these templates is the primary way you make your blog entries show up |
953
|
|
|
|
|
|
|
as decent stories. The other way is when you override the C method |
954
|
|
|
|
|
|
|
and have the content of your entries moulded into some slightly better markup. |
955
|
|
|
|
|
|
|
|
956
|
|
|
|
|
|
|
=head2 More information |
957
|
|
|
|
|
|
|
|
958
|
|
|
|
|
|
|
The best source of information on this is the documentation for the methods |
959
|
|
|
|
|
|
|
themselves. Therefore, we provide the execution order so you can see exactly |
960
|
|
|
|
|
|
|
what is going on and figure stuff out that way. |
961
|
|
|
|
|
|
|
|
962
|
|
|
|
|
|
|
=over |
963
|
|
|
|
|
|
|
|
964
|
|
|
|
|
|
|
=item new |
965
|
|
|
|
|
|
|
|
966
|
|
|
|
|
|
|
Blosxom is an object-oriented thing now. This is so that you can subclass it to |
967
|
|
|
|
|
|
|
override any or all of the default functionality, which is kind of the point |
968
|
|
|
|
|
|
|
of Blosxom in the first place. |
969
|
|
|
|
|
|
|
|
970
|
|
|
|
|
|
|
=item run |
971
|
|
|
|
|
|
|
|
972
|
|
|
|
|
|
|
The original script found the path and flavour for you but this one lets you |
973
|
|
|
|
|
|
|
decide where they should come from, so you can integrate them into other |
974
|
|
|
|
|
|
|
applications if you wish. |
975
|
|
|
|
|
|
|
|
976
|
|
|
|
|
|
|
=item entries_for_path |
977
|
|
|
|
|
|
|
|
978
|
|
|
|
|
|
|
The next thing that happens is the path is searched for all entries, and this |
979
|
|
|
|
|
|
|
function simply returns them all. This function returns an array of each entry's |
980
|
|
|
|
|
|
|
filename and a bit of extra data alongside. |
981
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
=item date_of_post |
983
|
|
|
|
|
|
|
|
984
|
|
|
|
|
|
|
C calls C to get that little bit of extra data. |
985
|
|
|
|
|
|
|
|
986
|
|
|
|
|
|
|
=item filter |
987
|
|
|
|
|
|
|
|
988
|
|
|
|
|
|
|
Then the entries list is filtered. This function is empty by default, intended |
989
|
|
|
|
|
|
|
to be overridden by plugins or subclasses. |
990
|
|
|
|
|
|
|
|
991
|
|
|
|
|
|
|
=item sort |
992
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
The remaining list is sorted. This is done by date by default, the date being |
994
|
|
|
|
|
|
|
ascertained during C. |
995
|
|
|
|
|
|
|
|
996
|
|
|
|
|
|
|
=item entry_data |
997
|
|
|
|
|
|
|
|
998
|
|
|
|
|
|
|
This is one of the more powerful functions to reimplement. It returns as much |
999
|
|
|
|
|
|
|
data about the provided entry as is necessary for your use of Blosxom. The |
1000
|
|
|
|
|
|
|
required return data are defined in the docs for this function; see also the |
1001
|
|
|
|
|
|
|
PLUGINS section. |
1002
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
This is called for each entry that remains after C is done and C |
1004
|
|
|
|
|
|
|
has ordered them. |
1005
|
|
|
|
|
|
|
|
1006
|
|
|
|
|
|
|
=item template |
1007
|
|
|
|
|
|
|
|
1008
|
|
|
|
|
|
|
This is called to get the data to give to C. It simply returns the |
1009
|
|
|
|
|
|
|
contents of the template file. |
1010
|
|
|
|
|
|
|
|
1011
|
|
|
|
|
|
|
=item interpolate |
1012
|
|
|
|
|
|
|
|
1013
|
|
|
|
|
|
|
This is the second of the more powerful functions. In this function, the raw |
1014
|
|
|
|
|
|
|
templates have their variables replaced with their values. How that works is |
1015
|
|
|
|
|
|
|
documented in the method's own documentation! |
1016
|
|
|
|
|
|
|
|
1017
|
|
|
|
|
|
|
Each entry's data is given to its template through this function; occasionally |
1018
|
|
|
|
|
|
|
while this is happening the date template is also given its data too. |
1019
|
|
|
|
|
|
|
|
1020
|
|
|
|
|
|
|
=item head_data |
1021
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
This gets data to give to C for the C template. |
1023
|
|
|
|
|
|
|
|
1024
|
|
|
|
|
|
|
=item foot_data |
1025
|
|
|
|
|
|
|
|
1026
|
|
|
|
|
|
|
This gets data to give to C for the C template. |
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
=back |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
The interpolated templates are aggregated into an array, onto which the head |
1031
|
|
|
|
|
|
|
and foot templates are added at the end, allowing info about the whole page to |
1032
|
|
|
|
|
|
|
be available to both the top and bottom of the page. |
1033
|
|
|
|
|
|
|
|
1034
|
|
|
|
|
|
|
=head1 PLUGINS |
1035
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
Writing plugins for this new version of Blosxom is easy. If you know exactly |
1037
|
|
|
|
|
|
|
what you want from your plugin, you can simply subclass this one and override |
1038
|
|
|
|
|
|
|
the methods below. Alternatively, you can create files in some directory, and |
1039
|
|
|
|
|
|
|
then configure your Blosxom object to use that directory as your plugins |
1040
|
|
|
|
|
|
|
directory. |
1041
|
|
|
|
|
|
|
|
1042
|
|
|
|
|
|
|
In order to use a plugin directory, the package name in the plugin file must |
1043
|
|
|
|
|
|
|
be identical to the filename itself. That is because the blosxom engine uses |
1044
|
|
|
|
|
|
|
the filename to know which package to give to C. The only thing that |
1045
|
|
|
|
|
|
|
deviates from this rule is that you can prepend the filename with any number of |
1046
|
|
|
|
|
|
|
digits, and these will be used to load the plugins in order. The order is that |
1047
|
|
|
|
|
|
|
returned by the sort function, so it is recommended all your numbers have the |
1048
|
|
|
|
|
|
|
same number of digits. |
1049
|
|
|
|
|
|
|
|
1050
|
|
|
|
|
|
|
In order to disable a plugin, simply alter its C subroutine to return a |
1051
|
|
|
|
|
|
|
false value instead of a true one. |
1052
|
|
|
|
|
|
|
|
1053
|
|
|
|
|
|
|
=head2 Starting a plugin |
1054
|
|
|
|
|
|
|
|
1055
|
|
|
|
|
|
|
As mentioned, it is necessary that your plugin's filename is the same as the |
1056
|
|
|
|
|
|
|
package defined in the plugin. Please also include a bit of a comment about |
1057
|
|
|
|
|
|
|
what your plugin does, plus author information. The community would appreciate |
1058
|
|
|
|
|
|
|
it if you would use an open licence for your copyrighting, since the community |
1059
|
|
|
|
|
|
|
is built on this attitude. However, the licence you use is, of course, up to |
1060
|
|
|
|
|
|
|
you. |
1061
|
|
|
|
|
|
|
|
1062
|
|
|
|
|
|
|
The smallest possible plugin (comments aside) is shown below, and its filename |
1063
|
|
|
|
|
|
|
would be C. |
1064
|
|
|
|
|
|
|
|
1065
|
|
|
|
|
|
|
## Blosxom plugin myplugin |
1066
|
|
|
|
|
|
|
## Makes blosxom not suck |
1067
|
|
|
|
|
|
|
## Author: Altreus |
1068
|
|
|
|
|
|
|
## Licence: X11/MIT |
1069
|
|
|
|
|
|
|
|
1070
|
|
|
|
|
|
|
package myplugin; |
1071
|
|
|
|
|
|
|
|
1072
|
|
|
|
|
|
|
sub start{1} |
1073
|
|
|
|
|
|
|
|
1074
|
|
|
|
|
|
|
1; |
1075
|
|
|
|
|
|
|
|
1076
|
|
|
|
|
|
|
=head2 Hooks |
1077
|
|
|
|
|
|
|
|
1078
|
|
|
|
|
|
|
Every single hook in the plugin will be passed the I object as the |
1079
|
|
|
|
|
|
|
first argument, effectively running the function as a method on the object |
1080
|
|
|
|
|
|
|
itself. This is shown as the C<$self> argument. |
1081
|
|
|
|
|
|
|
|
1082
|
|
|
|
|
|
|
In all cases, the first plugin that defines a hook is the one that gets to do |
1083
|
|
|
|
|
|
|
it. For this reason you may find that you want to use the method above to |
1084
|
|
|
|
|
|
|
decide the order in which the plugins are loaded. |
1085
|
|
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
Also in all cases, except where a true/false value is expected, simply not |
1087
|
|
|
|
|
|
|
returning anything is the way to go about deciding you don't want to alter the |
1088
|
|
|
|
|
|
|
default behaviour. For example, if you wanted to take the date of a post from |
1089
|
|
|
|
|
|
|
the filename, then in the cases where the filename does not define a date, you |
1090
|
|
|
|
|
|
|
can simply C and processing will continue as if it had not defined |
1091
|
|
|
|
|
|
|
this functionality in the first place. |
1092
|
|
|
|
|
|
|
|
1093
|
|
|
|
|
|
|
Also also in all cases, you can get the return value of the default method by |
1094
|
|
|
|
|
|
|
simply calling $self->method. This is helpful if you want to slightly but not |
1095
|
|
|
|
|
|
|
wildly alter the output, such as adding extra information to the same set of |
1096
|
|
|
|
|
|
|
data. Obviously this is not true of C and C, since these are not |
1097
|
|
|
|
|
|
|
methods on the class in the first place. |
1098
|
|
|
|
|
|
|
|
1099
|
|
|
|
|
|
|
=head3 start ($self) |
1100
|
|
|
|
|
|
|
|
1101
|
|
|
|
|
|
|
The C subroutine is required in your module and will return either a |
1102
|
|
|
|
|
|
|
true or a false value to decide whether the plugin should be used. |
1103
|
|
|
|
|
|
|
|
1104
|
|
|
|
|
|
|
You can use the values on the Blosxom object if you need to make a decision. |
1105
|
|
|
|
|
|
|
|
1106
|
|
|
|
|
|
|
sub start { |
1107
|
|
|
|
|
|
|
return shift->{static_mode}; # Only active in static mode |
1108
|
|
|
|
|
|
|
} |
1109
|
|
|
|
|
|
|
|
1110
|
|
|
|
|
|
|
=head3 template ($self, $path, $comp, $flavour) |
1111
|
|
|
|
|
|
|
|
1112
|
|
|
|
|
|
|
$path: path of request, filename removed |
1113
|
|
|
|
|
|
|
$comp: component e.g. head, story |
1114
|
|
|
|
|
|
|
$flavour: quite obviously the flavour of the request |
1115
|
|
|
|
|
|
|
|
1116
|
|
|
|
|
|
|
This returns the template to use in the given path for the given component for |
1117
|
|
|
|
|
|
|
the given flavour. The requested filename will not be part of the path, if the |
1118
|
|
|
|
|
|
|
requested path matched an individual entry. |
1119
|
|
|
|
|
|
|
|
1120
|
|
|
|
|
|
|
The default template procedure is to check the given path and all parent |
1121
|
|
|
|
|
|
|
directories of that path, up to the blog root, for the file $comp.$flavour, |
1122
|
|
|
|
|
|
|
and to use the first one found. |
1123
|
|
|
|
|
|
|
|
1124
|
|
|
|
|
|
|
Since it is pretty easy to find this file based on just the filename, you'd |
1125
|
|
|
|
|
|
|
think this method has something a bit more difficult to do. In fact this |
1126
|
|
|
|
|
|
|
function returns the I of that file, ready for interpolation. |
1127
|
|
|
|
|
|
|
|
1128
|
|
|
|
|
|
|
This function implements the functionality of both the C hook in the |
1129
|
|
|
|
|
|
|
original blosxom script, as well as the hooks for the individual templates |
1130
|
|
|
|
|
|
|
themselves. That means that if your original plugin defined a new template for, |
1131
|
|
|
|
|
|
|
e.g., the date.html in certain situations, this is where you should now return |
1132
|
|
|
|
|
|
|
that magic HTML. |
1133
|
|
|
|
|
|
|
|
1134
|
|
|
|
|
|
|
=head3 entries_for_path ($self, $path) |
1135
|
|
|
|
|
|
|
|
1136
|
|
|
|
|
|
|
$path: The path as provided to run() |
1137
|
|
|
|
|
|
|
|
1138
|
|
|
|
|
|
|
This returns an array of items. Each item is itself an arrayref, whose first |
1139
|
|
|
|
|
|
|
entry is the filename and whose second entry is a hashref. The hashref is |
1140
|
|
|
|
|
|
|
required to contain the 'date' key, which specifies the date of the file. |
1141
|
|
|
|
|
|
|
|
1142
|
|
|
|
|
|
|
That's pretty complicated. Here's some more info. When Blosxom is converting |
1143
|
|
|
|
|
|
|
entries into stories it needs to know what entries exist under a given path, |
1144
|
|
|
|
|
|
|
and the date of each entry. It therefore needs an array of arrayrefs, each |
1145
|
|
|
|
|
|
|
arrayref representing one of the entries found under the given path, in the |
1146
|
|
|
|
|
|
|
format C<[$filename, $date]>. |
1147
|
|
|
|
|
|
|
|
1148
|
|
|
|
|
|
|
However, since it is envisioned you might want to add more metadata about the |
1149
|
|
|
|
|
|
|
entry, the C part is a hashref, so you can add more stuff to it if you |
1150
|
|
|
|
|
|
|
want to. So now the format is C<[$filename, { date => $date }]>. |
1151
|
|
|
|
|
|
|
|
1152
|
|
|
|
|
|
|
The filename returned is the I of the entry file, I
|
1153
|
|
|
|
|
|
|
the datadir>. The input $path is not necessarily relative to anything, because |
1154
|
|
|
|
|
|
|
it will be the path the user requested. Thus, please note, it may contain the |
1155
|
|
|
|
|
|
|
year, month and day of the requested post(s) and not a path to any real file or |
1156
|
|
|
|
|
|
|
directory at all. |
1157
|
|
|
|
|
|
|
|
1158
|
|
|
|
|
|
|
It is worth noting that you can override how Blosxom decides the date of the |
1159
|
|
|
|
|
|
|
file by implementing the C method instead of this one. |
1160
|
|
|
|
|
|
|
|
1161
|
|
|
|
|
|
|
=head3 date_of_post ($self, $post) |
1162
|
|
|
|
|
|
|
|
1163
|
|
|
|
|
|
|
$post: path and filename relative to blog root |
1164
|
|
|
|
|
|
|
|
1165
|
|
|
|
|
|
|
You should return an arrayref where [0] is the 4-digit year, [1] is the 2-digit |
1166
|
|
|
|
|
|
|
month, and [2] is the 2-digit day. This is not checked for validity but will |
1167
|
|
|
|
|
|
|
probably cause something to blow up somewhere if it is not a real date. |
1168
|
|
|
|
|
|
|
|
1169
|
|
|
|
|
|
|
=head3 filter ($self, @entries) |
1170
|
|
|
|
|
|
|
|
1171
|
|
|
|
|
|
|
@entries: an array of all entries, exactly as returned by entries_for_path |
1172
|
|
|
|
|
|
|
|
1173
|
|
|
|
|
|
|
This function does nothing by default and is a hook by which you can |
1174
|
|
|
|
|
|
|
scrupulously filter out posts one way or another. You are given the output of |
1175
|
|
|
|
|
|
|
the C method above, and you should return an array in exactly |
1176
|
|
|
|
|
|
|
the same format, except having removed any entries you do not want to show up on |
1177
|
|
|
|
|
|
|
the generated page. |
1178
|
|
|
|
|
|
|
|
1179
|
|
|
|
|
|
|
=head3 sort ($self, @entries) |
1180
|
|
|
|
|
|
|
|
1181
|
|
|
|
|
|
|
@entries: an array of all entries, exactly as returned by entries_for_path |
1182
|
|
|
|
|
|
|
|
1183
|
|
|
|
|
|
|
You can override the default sorting mechanism, which is by date by default, |
1184
|
|
|
|
|
|
|
by implementing this method. It is advisable to empty your date template if |
1185
|
|
|
|
|
|
|
you do this, because the date template is inserted every time the date changes |
1186
|
|
|
|
|
|
|
as processing goes down the list. |
1187
|
|
|
|
|
|
|
|
1188
|
|
|
|
|
|
|
A future release may see the date template replaced by a divider template, which |
1189
|
|
|
|
|
|
|
would be configurable and merely default to the date. |
1190
|
|
|
|
|
|
|
|
1191
|
|
|
|
|
|
|
=head3 skip |
1192
|
|
|
|
|
|
|
|
1193
|
|
|
|
|
|
|
The skip function is a feature from the original blosxom. Setting it to return |
1194
|
|
|
|
|
|
|
a true value will cause the Blosxom object to stop just before anything is |
1195
|
|
|
|
|
|
|
actually output. That is, it will find all the entries and pull in the templates |
1196
|
|
|
|
|
|
|
but not do anything with them if any active plugin makes this function return |
1197
|
|
|
|
|
|
|
true. This is useful if, e.g., your plugin issues a redirect header. |
1198
|
|
|
|
|
|
|
|
1199
|
|
|
|
|
|
|
=head3 interpolate ($self, $template, $extra_data) |
1200
|
|
|
|
|
|
|
|
1201
|
|
|
|
|
|
|
This is where you can override the default way in which the variables are |
1202
|
|
|
|
|
|
|
interpolated into the template. |
1203
|
|
|
|
|
|
|
|
1204
|
|
|
|
|
|
|
The extra data will be a hashref of var => val, var being the variable name to |
1205
|
|
|
|
|
|
|
interpolate, without its $. |
1206
|
|
|
|
|
|
|
|
1207
|
|
|
|
|
|
|
If you don't call the parent function for this, be aware that there is an |
1208
|
|
|
|
|
|
|
option called $self->{require_namespace}, which means that only fully-qualified |
1209
|
|
|
|
|
|
|
variables will be interpolated. You should honour this if you intend anyone |
1210
|
|
|
|
|
|
|
else to use your plugin. |
1211
|
|
|
|
|
|
|
|
1212
|
|
|
|
|
|
|
The three functions C, C and C return simple |
1213
|
|
|
|
|
|
|
hashrefs that are interpolated in this function. |
1214
|
|
|
|
|
|
|
|
1215
|
|
|
|
|
|
|
You should also be aware that there are several "global" variables, available |
1216
|
|
|
|
|
|
|
to all templates, that are not returned by any of those functions. They are |
1217
|
|
|
|
|
|
|
hard-coded in the default implementation of C, so it is probably |
1218
|
|
|
|
|
|
|
for the best if you defer to this method. |
1219
|
|
|
|
|
|
|
|
1220
|
|
|
|
|
|
|
The section on usage will probably help here. |
1221
|
|
|
|
|
|
|
|
1222
|
|
|
|
|
|
|
=head3 entry_data ($entry) |
1223
|
|
|
|
|
|
|
|
1224
|
|
|
|
|
|
|
This is provided with an arrayref as returned by entries_for_path, and should |
1225
|
|
|
|
|
|
|
return a hashref with the keys as described above, in the method's own |
1226
|
|
|
|
|
|
|
documentation. Briefly, they are |
1227
|
|
|
|
|
|
|
|
1228
|
|
|
|
|
|
|
title body yr mo mo_name da dw hr min path fn |
1229
|
|
|
|
|
|
|
|
1230
|
|
|
|
|
|
|
You may also provide any extra keys that your own templates may want. It is |
1231
|
|
|
|
|
|
|
recommended that you use next::method to get the default hashref, and add more |
1232
|
|
|
|
|
|
|
things to it. |
1233
|
|
|
|
|
|
|
|
1234
|
|
|
|
|
|
|
See the section on usage for how all this works and thus to get a better idea |
1235
|
|
|
|
|
|
|
of what you should or should not be overriding. |
1236
|
|
|
|
|
|
|
|
1237
|
|
|
|
|
|
|
=head3 head_data |
1238
|
|
|
|
|
|
|
|
1239
|
|
|
|
|
|
|
This function is called to get the data required for the head template. By |
1240
|
|
|
|
|
|
|
default, only the global variables are available in the head template. |
1241
|
|
|
|
|
|
|
|
1242
|
|
|
|
|
|
|
You may override any global variable by returning it as a key in this hashref, |
1243
|
|
|
|
|
|
|
or you can add to the set of available variables instead. See the section on |
1244
|
|
|
|
|
|
|
usage for how all this works. |
1245
|
|
|
|
|
|
|
|
1246
|
|
|
|
|
|
|
=head3 foot_data |
1247
|
|
|
|
|
|
|
|
1248
|
|
|
|
|
|
|
This function is called to provide data to the foot template. By default it |
1249
|
|
|
|
|
|
|
returns an empty hashref. |
1250
|
|
|
|
|
|
|
|
1251
|
|
|
|
|
|
|
You may override any global variable by returning it as a key in this hashref, |
1252
|
|
|
|
|
|
|
or you can add to the set of available variables instead. |
1253
|
|
|
|
|
|
|
|
1254
|
|
|
|
|
|
|
See the section on usage for how all this works. |
1255
|
|
|
|
|
|
|
|
1256
|
|
|
|
|
|
|
=head1 AUTHOR |
1257
|
|
|
|
|
|
|
|
1258
|
|
|
|
|
|
|
Altreus, C<< >> |
1259
|
|
|
|
|
|
|
|
1260
|
|
|
|
|
|
|
=head1 TODO |
1261
|
|
|
|
|
|
|
|
1262
|
|
|
|
|
|
|
Most existing plugins won't work because I've changed the way it works to the |
1263
|
|
|
|
|
|
|
extent that the same functions don't necessarily exist. However, existing |
1264
|
|
|
|
|
|
|
plugins should be fairly easy to tailor to the new plugin system. I didn't |
1265
|
|
|
|
|
|
|
think this was too important a feature because a good number of the plugins at |
1266
|
|
|
|
|
|
|
the blosxom plugin repository are 404s anyway. |
1267
|
|
|
|
|
|
|
|
1268
|
|
|
|
|
|
|
The plugin system is like the original blosxom's, where the first plugin to |
1269
|
|
|
|
|
|
|
define a function is the boss of that function. Some functions may benefit |
1270
|
|
|
|
|
|
|
from the combined effort of all plugins, such as filtering. That is something |
1271
|
|
|
|
|
|
|
to think about in the future. |
1272
|
|
|
|
|
|
|
|
1273
|
|
|
|
|
|
|
Static rendering is not yet implemented. Frankly I don't think I can be |
1274
|
|
|
|
|
|
|
bothered. |
1275
|
|
|
|
|
|
|
|
1276
|
|
|
|
|
|
|
A comment system is common on many blogs and I think I will write a separate |
1277
|
|
|
|
|
|
|
plugin to make this easy. |
1278
|
|
|
|
|
|
|
|
1279
|
|
|
|
|
|
|
=head1 BUGS |
1280
|
|
|
|
|
|
|
|
1281
|
|
|
|
|
|
|
Bug reports on github, please! http://github.com/Altreus/Blog-Blosxom/issues |
1282
|
|
|
|
|
|
|
|
1283
|
|
|
|
|
|
|
You can also get the latest version from here. |
1284
|
|
|
|
|
|
|
|
1285
|
|
|
|
|
|
|
=head1 SUPPORT |
1286
|
|
|
|
|
|
|
|
1287
|
|
|
|
|
|
|
You are reading the only documentation for this module. |
1288
|
|
|
|
|
|
|
|
1289
|
|
|
|
|
|
|
You should check out the examples folder. If you don't know where that is, |
1290
|
|
|
|
|
|
|
either check $HOME/.cpan/build or browse/clone the Git repository |
1291
|
|
|
|
|
|
|
http://github.com/Altreus/Blog-Blosxom |
1292
|
|
|
|
|
|
|
|
1293
|
|
|
|
|
|
|
If you're brave you can see if I'm around in #perl on irc.freenode.com. Your |
1294
|
|
|
|
|
|
|
use case will help me refine the module, since in its initial state it was |
1295
|
|
|
|
|
|
|
merely a rewrite of the original script for my own sanity. |
1296
|
|
|
|
|
|
|
|
1297
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS |
1298
|
|
|
|
|
|
|
|
1299
|
|
|
|
|
|
|
Thanks to the original author of blosxom.cgi for writing it and giving me code |
1300
|
|
|
|
|
|
|
to do much of the stuff it did. |
1301
|
|
|
|
|
|
|
|
1302
|
|
|
|
|
|
|
http://blosxom.com |
1303
|
|
|
|
|
|
|
|
1304
|
|
|
|
|
|
|
Thanks to f00li5h on that irc.freenode.com (and many others!) for being the |
1305
|
|
|
|
|
|
|
first person I mean cat other than me to use it and therefore have lots of |
1306
|
|
|
|
|
|
|
things to say about it. |
1307
|
|
|
|
|
|
|
|
1308
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
1309
|
|
|
|
|
|
|
|
1310
|
|
|
|
|
|
|
This module is released under the X11/MIT licence, which is the one where you |
1311
|
|
|
|
|
|
|
use it as you wish and don't blame me for it. I hope the author of the original |
1312
|
|
|
|
|
|
|
script does not take this badly; if the author sees this and wishes me to |
1313
|
|
|
|
|
|
|
change the licence I am happy to do so. |
1314
|
|
|
|
|
|
|
|
1315
|
|
|
|
|
|
|
=cut |
1316
|
|
|
|
|
|
|
|
1317
|
|
|
|
|
|
|
1; |
1318
|
|
|
|
|
|
|
|
1319
|
|
|
|
|
|
|
__DATA__ |