line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Daizu; |
2
|
12
|
|
|
12
|
|
1164616
|
use warnings; |
|
12
|
|
|
|
|
33
|
|
|
12
|
|
|
|
|
1296
|
|
3
|
12
|
|
|
12
|
|
70
|
use strict; |
|
12
|
|
|
|
|
26
|
|
|
12
|
|
|
|
|
470
|
|
4
|
|
|
|
|
|
|
|
5
|
12
|
|
|
12
|
|
33427
|
use XML::LibXML; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
use DBI; |
7
|
|
|
|
|
|
|
use SVN::Ra; |
8
|
|
|
|
|
|
|
use Path::Class qw( dir ); |
9
|
|
|
|
|
|
|
use Carp qw( croak ); |
10
|
|
|
|
|
|
|
use Carp::Assert qw( assert DEBUG ); |
11
|
|
|
|
|
|
|
use Daizu::Revision; |
12
|
|
|
|
|
|
|
use Daizu::Wc; |
13
|
|
|
|
|
|
|
use Daizu::Util qw( |
14
|
|
|
|
|
|
|
trim trim_with_empty_null |
15
|
|
|
|
|
|
|
validate_number validate_uri validate_mime_type |
16
|
|
|
|
|
|
|
validate_date db_datetime |
17
|
|
|
|
|
|
|
db_row_exists db_row_id db_select db_insert db_update db_delete |
18
|
|
|
|
|
|
|
wc_file_data |
19
|
|
|
|
|
|
|
guid_first_last_times |
20
|
|
|
|
|
|
|
load_class |
21
|
|
|
|
|
|
|
xml_attr xml_croak |
22
|
|
|
|
|
|
|
daizu_data_dir |
23
|
|
|
|
|
|
|
); |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
=head1 NAME |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
Daizu - class for accessing Daizu CMS from Perl |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
=head1 INTRODUCTION |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
Daizu CMS is an experimental content management system. It uses content |
32
|
|
|
|
|
|
|
stored in a Subversion repository, and keeps track of it in a PostgreSQL |
33
|
|
|
|
|
|
|
database. It is an attempt to solve some of the underlying problems of |
34
|
|
|
|
|
|
|
content management once and for all. As such the development so far has |
35
|
|
|
|
|
|
|
focused on the 'back end' parts of the system, and it doesn't really have |
36
|
|
|
|
|
|
|
a user interface to speak of. It's certainly not ready for less technical |
37
|
|
|
|
|
|
|
users yet. More information is available on the Daizu website: |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
L |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
=head1 DESCRIPTION |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
Most access to Daizu functionality requires a Daizu object. It provides |
44
|
|
|
|
|
|
|
a database handle for access to the 'live' content data, and a L |
45
|
|
|
|
|
|
|
object for access to the Subversion repository. |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
Some other classes are documented as requiring a C<$cms> value as the |
48
|
|
|
|
|
|
|
first argument to their constructors or methods. This should always be |
49
|
|
|
|
|
|
|
a Daizu object. |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
=head2 CONSTANTS |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
=over |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
=item $Daizu::VERSION |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
The version number of Daizu CMS (as a whole, not just this module). |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
=item $Daizu::DEFAULT_CONFIG_FILENAME |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
The full path and filename of the config file which will be read by |
62
|
|
|
|
|
|
|
default, if none is specified in the constructor call or the environment. |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
Value: I |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
=item $Daizu::CONFIG_NS |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
The URI used as an XML namespace for the elements in the config file. |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
Value: L |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
=item $Daizu::HTML_EXTENSION_NS |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
The URI used as an XML namespace for special elements in XHTML content. |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
Value: L |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
=item $Daizu::HIDING_FILENAMES |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
A list of file and directory names which prevent any publication of |
81
|
|
|
|
|
|
|
files with one of the names, or anything inside a directory so named. |
82
|
|
|
|
|
|
|
Separated by '|' so that the whole string can be included in Perl |
83
|
|
|
|
|
|
|
and PostgreSQL regular expressions. |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
Value: C<_template|_hide> |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
=cut |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
our $VERSION = '0.3'; |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
our $DEFAULT_CONFIG_FILENAME = '/etc/daizu/config.xml'; |
92
|
|
|
|
|
|
|
our $CONFIG_NS = 'http://www.daizucms.org/ns/config/'; |
93
|
|
|
|
|
|
|
our $HTML_EXTENSION_NS = 'http://www.daizucms.org/ns/html-extension/'; |
94
|
|
|
|
|
|
|
our $HIDING_FILENAMES = '_template|_hide|_lib'; |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
=item %OVERRIDABLE_PROPERTY |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
A hash describing which pieces of metadata can be overridden by article |
99
|
|
|
|
|
|
|
loader plugins. The keys are the names of Subversion properties, and |
100
|
|
|
|
|
|
|
the values are the names of columns in the C table. |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
=cut |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
our %OVERRIDABLE_PROPERTY = ( |
105
|
|
|
|
|
|
|
'dc:title' => 'title', |
106
|
|
|
|
|
|
|
'dc:description' => 'description', |
107
|
|
|
|
|
|
|
'daizu:short-title' => 'short_title', |
108
|
|
|
|
|
|
|
); |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
=back |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
=head2 METHODS |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=over |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
=item Daizu-Enew($config_filename) |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
Return a Daizu object based on the information in the given configuration |
119
|
|
|
|
|
|
|
file. If C<$config_filename> is not supplied, it will fall back on any |
120
|
|
|
|
|
|
|
file specified by the C environment variable, and then |
121
|
|
|
|
|
|
|
by the default config file (see C<$DEFAULT_CONFIG_FILENAME> above). |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
The value returned will be called C<$cms> in the documentation. |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
For information about the format of the configuration file, see |
126
|
|
|
|
|
|
|
the documentation on the website: |
127
|
|
|
|
|
|
|
L |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=cut |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
# This ensures that @INC is only fiddled with once for each Daizu installation. |
132
|
|
|
|
|
|
|
# The keys are the URIs of content repositories. If an entry exists for a |
133
|
|
|
|
|
|
|
# particular repository, then its _lib directory has already been added. |
134
|
|
|
|
|
|
|
my %added_lib_path; |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
sub new |
137
|
|
|
|
|
|
|
{ |
138
|
|
|
|
|
|
|
my ($class, $filename) = @_; |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
if (!defined $filename) { |
141
|
|
|
|
|
|
|
if (defined $ENV{DAIZU_CONFIG}) { |
142
|
|
|
|
|
|
|
$filename = $ENV{DAIZU_CONFIG}; |
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
elsif (-r $DEFAULT_CONFIG_FILENAME) { |
145
|
|
|
|
|
|
|
$filename = $DEFAULT_CONFIG_FILENAME; |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
else { |
148
|
|
|
|
|
|
|
croak "cannot find Daizu configuration file" . |
149
|
|
|
|
|
|
|
" (set DAIZU_CONFIG environment variable)"; |
150
|
|
|
|
|
|
|
} |
151
|
|
|
|
|
|
|
} |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
croak "Bad config file '$filename', not a normal file\n" |
154
|
|
|
|
|
|
|
unless -f $filename; |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
my $self = bless { config_filename => $filename }, $class; |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
my $parser = XML::LibXML->new; |
159
|
|
|
|
|
|
|
my $doc = $parser->parse_file($filename); |
160
|
|
|
|
|
|
|
my $root = $doc->documentElement; |
161
|
|
|
|
|
|
|
xml_croak($filename, $root, "root element must be ") |
162
|
|
|
|
|
|
|
unless $root->localname eq 'config'; |
163
|
|
|
|
|
|
|
xml_croak($filename, $root, "root element in wrong namespace") |
164
|
|
|
|
|
|
|
unless defined $root->namespaceURI && $root->namespaceURI eq $CONFIG_NS; |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
# Open database connection. |
167
|
|
|
|
|
|
|
{ |
168
|
|
|
|
|
|
|
my $elem = _singleton_conf_elem($filename, $root, 'database'); |
169
|
|
|
|
|
|
|
my $dsn = xml_attr($filename, $elem, 'dsn'); |
170
|
|
|
|
|
|
|
my $user = $elem->getAttribute('user'); |
171
|
|
|
|
|
|
|
die "$filename: should have 'user' attribute, not 'username'" |
172
|
|
|
|
|
|
|
if !defined $user && $elem->hasAttribute('username'); |
173
|
|
|
|
|
|
|
my $password = $elem->getAttribute('password'); |
174
|
|
|
|
|
|
|
$self->{db} = DBI->connect($dsn, $user, $password, { |
175
|
|
|
|
|
|
|
AutoCommit => 1, |
176
|
|
|
|
|
|
|
RaiseError => 1, |
177
|
|
|
|
|
|
|
PrintError => 0, |
178
|
|
|
|
|
|
|
}); |
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
# Open Subversion remote-access connection. |
182
|
|
|
|
|
|
|
my $svn_url; |
183
|
|
|
|
|
|
|
{ |
184
|
|
|
|
|
|
|
my $elem = _singleton_conf_elem($filename, $root, 'repository'); |
185
|
|
|
|
|
|
|
$svn_url = xml_attr($filename, $elem, 'url'); |
186
|
|
|
|
|
|
|
my $svn_username = xml_attr($filename, $elem, 'username', ''); |
187
|
|
|
|
|
|
|
my $svn_password = xml_attr($filename, $elem, 'password', ''); |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
my $auth_callback = sub { |
190
|
|
|
|
|
|
|
my ($creds, $realm, $default_username, $may_save, $pool) = @_; |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
$creds->username($svn_username); |
193
|
|
|
|
|
|
|
$creds->password($svn_password); |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
# There's no real reason to cache this stuff since we can always |
196
|
|
|
|
|
|
|
# get it from the config files, so we don't cache to avoid |
197
|
|
|
|
|
|
|
# confusion, and in case we're running as a special user with |
198
|
|
|
|
|
|
|
# a home directory we can't write to. |
199
|
|
|
|
|
|
|
$creds->may_save(0); |
200
|
|
|
|
|
|
|
}; |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
$self->{ra} = SVN::Ra->new( |
203
|
|
|
|
|
|
|
url => $svn_url, |
204
|
|
|
|
|
|
|
($svn_username eq '' && $svn_password eq '' ? () : (auth => [ |
205
|
|
|
|
|
|
|
SVN::Client::get_simple_prompt_provider($auth_callback, 0), |
206
|
|
|
|
|
|
|
])), |
207
|
|
|
|
|
|
|
); |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
# Get live working copy ID. |
211
|
|
|
|
|
|
|
{ |
212
|
|
|
|
|
|
|
my $elem = _singleton_conf_elem($filename, $root, 'live-working-copy'); |
213
|
|
|
|
|
|
|
my $wc_id = xml_attr($filename, $elem, 'id'); |
214
|
|
|
|
|
|
|
$self->{live_wc_id} = validate_number($wc_id); |
215
|
|
|
|
|
|
|
xml_croak($filename, $elem, "bad WC ID in ") |
216
|
|
|
|
|
|
|
unless defined $self->{live_wc_id}; |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
# Path to directory containing the default templates distributed with |
220
|
|
|
|
|
|
|
# Daizu, and possibly also to a directory where templates should be |
221
|
|
|
|
|
|
|
# loaded during testing instead of from the database. |
222
|
|
|
|
|
|
|
{ |
223
|
|
|
|
|
|
|
$self->{template_default_path} = daizu_data_dir('template'); |
224
|
|
|
|
|
|
|
my ($elem) = $root->getChildrenByTagNameNS($CONFIG_NS, 'template-test'); |
225
|
|
|
|
|
|
|
$self->{template_test_path} = xml_attr($filename, $elem, 'path') |
226
|
|
|
|
|
|
|
if defined $elem; |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
# Add to @INC the '_lib' directory from the content repository, either |
230
|
|
|
|
|
|
|
# by loading files from the live working copy, or from the 'template-test' |
231
|
|
|
|
|
|
|
# path. |
232
|
|
|
|
|
|
|
unless (exists $added_lib_path{$svn_url}) { |
233
|
|
|
|
|
|
|
if (defined $self->{template_test_path}) { |
234
|
|
|
|
|
|
|
push @INC, dir($self->{template_test_path})->subdir('_lib') |
235
|
|
|
|
|
|
|
->stringify; |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
else { |
238
|
|
|
|
|
|
|
push @INC, sub { |
239
|
|
|
|
|
|
|
my (undef, $filename) = @_; |
240
|
|
|
|
|
|
|
my $file_id = db_row_id($self->{db}, 'wc_file', |
241
|
|
|
|
|
|
|
wc_id => $self->{live_wc_id}, |
242
|
|
|
|
|
|
|
path => "_lib/$filename", |
243
|
|
|
|
|
|
|
); |
244
|
|
|
|
|
|
|
return undef unless defined $file_id; |
245
|
|
|
|
|
|
|
my $data = wc_file_data($self->{db}, $file_id); |
246
|
|
|
|
|
|
|
open my $fh, '<', $data |
247
|
|
|
|
|
|
|
or die "error opening memory file for '_lib/$filename': $!"; |
248
|
|
|
|
|
|
|
return $fh; |
249
|
|
|
|
|
|
|
}; |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
$added_lib_path{$svn_url} = undef; |
253
|
|
|
|
|
|
|
} |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
# How output should be published. |
256
|
|
|
|
|
|
|
for my $elem ($root->getChildrenByTagNameNS($CONFIG_NS, 'output')) { |
257
|
|
|
|
|
|
|
my $url = trim(xml_attr($filename, $elem, 'url')); |
258
|
|
|
|
|
|
|
my $path = trim(xml_attr($filename, $elem, 'path')); |
259
|
|
|
|
|
|
|
my $url_ob = validate_uri($url); |
260
|
|
|
|
|
|
|
xml_croak($filename, $elem, " |
261
|
|
|
|
|
|
|
unless defined $url_ob; |
262
|
|
|
|
|
|
|
xml_croak($filename, $elem, " |
263
|
|
|
|
|
|
|
unless defined $url_ob->scheme && $url_ob->scheme =~ /^https?/i; |
264
|
|
|
|
|
|
|
$url = $url_ob->canonical; |
265
|
|
|
|
|
|
|
xml_croak($filename, $elem, "more than one |
266
|
|
|
|
|
|
|
if exists $self->{output}{$url}; |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
my $redirect_map = trim(xml_attr($filename, $elem, 'redirect-map', '')); |
269
|
|
|
|
|
|
|
my $gone_map = trim(xml_attr($filename, $elem, 'gone-map', '')); |
270
|
|
|
|
|
|
|
for ($redirect_map, $gone_map) { |
271
|
|
|
|
|
|
|
$_ = undef if $_ eq ''; |
272
|
|
|
|
|
|
|
next unless defined; |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
# Check for duplicate filenames. |
275
|
|
|
|
|
|
|
while (my ($other_url, $config) = each %{$self->{output}}) { |
276
|
|
|
|
|
|
|
for my $map (qw( redirect gone )) { |
277
|
|
|
|
|
|
|
xml_croak($filename, $elem, "filename '$_' duplicates" . |
278
|
|
|
|
|
|
|
" '$map-map' for '$other_url' config") |
279
|
|
|
|
|
|
|
if defined $config->{"${map}_map"} && |
280
|
|
|
|
|
|
|
$config->{"${map}_map"} eq $_; |
281
|
|
|
|
|
|
|
} |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
my $index_filename = trim(xml_attr($filename, $elem, 'index-filename', |
286
|
|
|
|
|
|
|
'index.html')); |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
$self->{output}{$url} = { |
289
|
|
|
|
|
|
|
url => $url_ob, |
290
|
|
|
|
|
|
|
path => $path, |
291
|
|
|
|
|
|
|
redirect_map => $redirect_map, |
292
|
|
|
|
|
|
|
gone_map => $gone_map, |
293
|
|
|
|
|
|
|
index_filename => $index_filename, |
294
|
|
|
|
|
|
|
}; |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
# Initialize hooks for plugins. |
298
|
|
|
|
|
|
|
$self->{property_loaders}{'*'} = [ [ $self => '_std_property_loader' ] ]; |
299
|
|
|
|
|
|
|
$self->{html_dom_filters} = {}; |
300
|
|
|
|
|
|
|
$self->{article_loaders} = {}; |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
# Read global configuration for things which can be overridden for |
303
|
|
|
|
|
|
|
# specific paths. |
304
|
|
|
|
|
|
|
$self->_read_config_for_path($filename, $root, ''); |
305
|
|
|
|
|
|
|
xml_croak($filename, $root, "no default element") |
306
|
|
|
|
|
|
|
unless defined $self->{default_entity}; |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
# Read path-specific configuration in each inner element. |
309
|
|
|
|
|
|
|
for my $elem ($root->getChildrenByTagNameNS($CONFIG_NS, 'config')) { |
310
|
|
|
|
|
|
|
xml_croak($filename, $elem, "inner elements must have path") |
311
|
|
|
|
|
|
|
unless $elem->hasAttribute('path'); |
312
|
|
|
|
|
|
|
my $path = $elem->getAttribute('path'); |
313
|
|
|
|
|
|
|
xml_croak($filename, $elem, "inner element's path is empty") |
314
|
|
|
|
|
|
|
if $path eq ''; |
315
|
|
|
|
|
|
|
$self->_read_config_for_path($filename, $elem, $path); |
316
|
|
|
|
|
|
|
} |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
return $self; |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
sub _read_config_for_path |
322
|
|
|
|
|
|
|
{ |
323
|
|
|
|
|
|
|
my ($self, $filename, $config, $path) = @_; |
324
|
|
|
|
|
|
|
xml_croak($filename, $config, " element has bad path '$path'") |
325
|
|
|
|
|
|
|
if $path =~ /^\// || $path =~ /\/$/; |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
# Load information for minting GUID URLs. |
328
|
|
|
|
|
|
|
for my $elem ($config->getChildrenByTagNameNS($CONFIG_NS, 'guid-entity')) { |
329
|
|
|
|
|
|
|
my $entity = trim(xml_attr($filename, $elem, 'entity')); |
330
|
|
|
|
|
|
|
xml_croak($filename, $elem, " has empty entity") |
331
|
|
|
|
|
|
|
if $entity eq ''; |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
if ($path eq '') { |
334
|
|
|
|
|
|
|
xml_croak($filename, $elem, |
335
|
|
|
|
|
|
|
"more than one default (pathless) element") |
336
|
|
|
|
|
|
|
if defined $self->{default_entity}; |
337
|
|
|
|
|
|
|
$self->{default_entity} = $entity; |
338
|
|
|
|
|
|
|
} |
339
|
|
|
|
|
|
|
else { |
340
|
|
|
|
|
|
|
xml_croak($filename, $elem, |
341
|
|
|
|
|
|
|
"more than one for path '$path'") |
342
|
|
|
|
|
|
|
if exists $self->{path_entity}{$path}; |
343
|
|
|
|
|
|
|
$self->{path_entity}{$path} = $entity; |
344
|
|
|
|
|
|
|
} |
345
|
|
|
|
|
|
|
} |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
# Load and register plugins. |
348
|
|
|
|
|
|
|
for my $elem ($config->getChildrenByTagNameNS($CONFIG_NS, 'plugin')) { |
349
|
|
|
|
|
|
|
my $class = trim(xml_attr($filename, $elem, 'class')); |
350
|
|
|
|
|
|
|
load_class($class); |
351
|
|
|
|
|
|
|
$class->register($self, $config, $elem, $path); |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
# Configuration for generator classes |
355
|
|
|
|
|
|
|
for my $elem ($config->getChildrenByTagNameNS($CONFIG_NS, 'generator')) { |
356
|
|
|
|
|
|
|
my $class = trim(xml_attr($filename, $elem, 'class')); |
357
|
|
|
|
|
|
|
xml_croak($filename, $elem, |
358
|
|
|
|
|
|
|
"only one generator config allowed for '$class' at '$path'") |
359
|
|
|
|
|
|
|
if exists $self->{generator_config}{$class}{$path}; |
360
|
|
|
|
|
|
|
$self->{generator_config}{$class}{$path} = $elem; |
361
|
|
|
|
|
|
|
} |
362
|
|
|
|
|
|
|
} |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
# Return a named element which must be a child of the specified $root element, |
365
|
|
|
|
|
|
|
# and check that there is exactly one of them. |
366
|
|
|
|
|
|
|
sub _singleton_conf_elem |
367
|
|
|
|
|
|
|
{ |
368
|
|
|
|
|
|
|
my ($filename, $root, $name) = @_; |
369
|
|
|
|
|
|
|
my ($elem, $extra) = $root->getChildrenByTagNameNS($CONFIG_NS, $name); |
370
|
|
|
|
|
|
|
xml_croak($filename, $root, "missing <$name> element") |
371
|
|
|
|
|
|
|
unless defined $elem; |
372
|
|
|
|
|
|
|
xml_croak($filename, $extra, "only one <$name> element is allowed") |
373
|
|
|
|
|
|
|
if defined $extra; |
374
|
|
|
|
|
|
|
return $elem; |
375
|
|
|
|
|
|
|
} |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
=item $cms-Era |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
Return the Subversion remote access (L) object for accessing the |
380
|
|
|
|
|
|
|
repository. |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
=cut |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
sub ra { $_[0]->{ra} } |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
=item $cms-Edb |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
Return the L database handle for accessing the Daizu database. |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
=cut |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
sub db { $_[0]->{db} } |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
=item $cms-Econfig_filename |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
Returns a string containing the filename from which the configuration |
397
|
|
|
|
|
|
|
was loaded. The filename may be a full (absolute) path, or may be |
398
|
|
|
|
|
|
|
relative to the current directory at the time the Daizu object was |
399
|
|
|
|
|
|
|
created. |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
=cut |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
sub config_filename { $_[0]->{config_filename} } |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
=item $cms-Elive_wc |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
Return a L object representing the live working copy. |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
=cut |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
sub live_wc |
412
|
|
|
|
|
|
|
{ |
413
|
|
|
|
|
|
|
my ($self) = @_; |
414
|
|
|
|
|
|
|
return Daizu::Wc->new($self); |
415
|
|
|
|
|
|
|
} |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
=item $cms-Eload_revision($update_to_rev) |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
Load information about revisions and file paths for any new revisions, |
420
|
|
|
|
|
|
|
upto C<$update_to_rev>, from the repository into the database. If no |
421
|
|
|
|
|
|
|
revision number is supplied, updates to the latest revision. |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
This is called automatically before any working copy updates, to ensure |
424
|
|
|
|
|
|
|
that the database knows about revisions before any working copies are |
425
|
|
|
|
|
|
|
updated to them. It is idempotent. |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
This is a simple wrapper round the code in L. |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
=cut |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
sub load_revision |
432
|
|
|
|
|
|
|
{ |
433
|
|
|
|
|
|
|
my ($self, $update_to_rev) = @_; |
434
|
|
|
|
|
|
|
return Daizu::Revision::load_revision($self, $update_to_rev); |
435
|
|
|
|
|
|
|
} |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
=item $cms-Eadd_property_loader($pattern, $object, $method) |
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
Plugins can use this to register themselves as a 'property loader', |
440
|
|
|
|
|
|
|
which will be called when a property whose name matches C<$pattern> |
441
|
|
|
|
|
|
|
is updated in a working copy. |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
Currently it isn't possible to localize property loader plugins to |
444
|
|
|
|
|
|
|
have different configuration for different paths in the repository |
445
|
|
|
|
|
|
|
using the normal path configuration system. |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
The pattern can be either the exact property name, a wildcard match on |
448
|
|
|
|
|
|
|
some prefix of the name ending in a colon, such as C, or just |
449
|
|
|
|
|
|
|
a C<*> which will match all property names. There isn't any generic |
450
|
|
|
|
|
|
|
wildcard or regular expression matching capability. |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
C<$object> should be an object (probably of the plugin's class) on which |
453
|
|
|
|
|
|
|
C<$method> can be called. Since it is called as a method, the first |
454
|
|
|
|
|
|
|
value passed in will be C<$object>, followed by these: |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
=over |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
=item $cms |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
A C object. |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
=item $id |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
The ID number of the file in the C database table for which the |
465
|
|
|
|
|
|
|
new property values apply. |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
=item $props |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
A reference to a hash of the new property values. |
470
|
|
|
|
|
|
|
Only properties which have been |
471
|
|
|
|
|
|
|
changed during a working copy update will have entries, so the file |
472
|
|
|
|
|
|
|
may have other properties which haven't been changed. |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
Properties which have been deleted during the update will have an |
475
|
|
|
|
|
|
|
entry in this hash with a value of C. |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
=back |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
An example of a property loader method is C<_std_property_loader> in |
480
|
|
|
|
|
|
|
this module. It is always registered automatically. |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
=cut |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
sub add_property_loader |
485
|
|
|
|
|
|
|
{ |
486
|
|
|
|
|
|
|
my ($self, $pattern, $object, $method) = @_; |
487
|
|
|
|
|
|
|
push @{$self->{property_loaders}{$pattern}}, [ $object => $method ]; |
488
|
|
|
|
|
|
|
} |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
=item $cms-Eadd_article_loader($mime_type, $path, $object, $method) |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
Plugins can use this to register a method which will be called whenever |
493
|
|
|
|
|
|
|
an article of type C<$mime_type> needs to be loaded. The MIME type can be |
494
|
|
|
|
|
|
|
fully specified, or be something like C (to match any image format), |
495
|
|
|
|
|
|
|
or just be C<*> to match any type. These aren't generic glob or regex |
496
|
|
|
|
|
|
|
patterns, so only those three levels of specificity are allowed. The |
497
|
|
|
|
|
|
|
most specific plugin available will be tried first. Plugins of the same |
498
|
|
|
|
|
|
|
specificity will be tried in the order they are registered. The plugin |
499
|
|
|
|
|
|
|
methods can return false if they can't handle a particular file for |
500
|
|
|
|
|
|
|
some reason, in which case Daizu will continue to look for another suitable |
501
|
|
|
|
|
|
|
plugin. |
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
The plugin registered will only be called on for files with paths which |
504
|
|
|
|
|
|
|
are the same as, or are under the directory specified by, C<$path>. |
505
|
|
|
|
|
|
|
Plugins should usually just pass the C<$path> value from their C |
506
|
|
|
|
|
|
|
method through to this method as-is. |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
C<$method> (a method name) will be called on C<$object>, and will be |
509
|
|
|
|
|
|
|
passed C<$cms> and a |
510
|
|
|
|
|
|
|
L object representing the input file. The method should |
511
|
|
|
|
|
|
|
return a hash of values describing the article. Alternatively it can |
512
|
|
|
|
|
|
|
return false to indicate that it can't handle the file. |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
The hash returned can contain the following values: |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
=over |
517
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
=item content |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
Required. All the other values are optional. |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
This should be an XHTML DOM of the article's content, as it will be published. |
523
|
|
|
|
|
|
|
It should be an L object, with a root element called |
524
|
|
|
|
|
|
|
C in the XHTML namespace. It can contain extension elements to be |
525
|
|
|
|
|
|
|
processed by article filter plugins. It can contain XInclude elements, |
526
|
|
|
|
|
|
|
which will be processed by the |
527
|
|
|
|
|
|
|
L. |
528
|
|
|
|
|
|
|
Entity references should not be present. |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
=item title |
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
The title to use for the article. If this is present and not undef then |
533
|
|
|
|
|
|
|
it will override the value of the C property. |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
=item short_title |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
The 'short title' to use for the article. If this is present and not |
538
|
|
|
|
|
|
|
undef then it will override the value of the C property. |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
=item description |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
The description to use for the article. If this is present and not undef then |
543
|
|
|
|
|
|
|
it will override the value of the C property. |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
=item pages_url |
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
The URL to use for the first page of the article, and which will also be |
548
|
|
|
|
|
|
|
used to generate URLs for subsequent pages (if any). This can be absolute, |
549
|
|
|
|
|
|
|
or relative to the file's base URL. |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
=item extra_urls |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
A reference to an array of URL info hashes describing extra URLs generated |
554
|
|
|
|
|
|
|
by the file in addition to the actual pages of the article. These are |
555
|
|
|
|
|
|
|
stored in the C table. |
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
=item extra_templates |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
A reference to an array of filenames of extra templates to be included in |
560
|
|
|
|
|
|
|
the article's 'extras' column. These are stored in the |
561
|
|
|
|
|
|
|
C table. |
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
=back |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
See L or L for |
566
|
|
|
|
|
|
|
examples of registering and writing article loader plugins. |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
=cut |
569
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
sub add_article_loader |
571
|
|
|
|
|
|
|
{ |
572
|
|
|
|
|
|
|
my ($self, $mime_type, $path, $object, $method) = @_; |
573
|
|
|
|
|
|
|
push @{$self->{article_loaders}{$mime_type}{$path}}, [ $object => $method ]; |
574
|
|
|
|
|
|
|
} |
575
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
=item $cms-Eadd_html_dom_filter($path, $object, $method) |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
Plugins can use this to register a method which will be called whenever |
579
|
|
|
|
|
|
|
an XHTML file is being published. C<$method> (a method name) will be |
580
|
|
|
|
|
|
|
called on C<$object>, and will be passed C<$cms>, a L object |
581
|
|
|
|
|
|
|
for the file being filtered, and an XML DOM object |
582
|
|
|
|
|
|
|
of the source, as a L object. The plugin method |
583
|
|
|
|
|
|
|
should return a reference to a hash containing a C value which |
584
|
|
|
|
|
|
|
is the filtered content, either a completely new copy of the DOM |
585
|
|
|
|
|
|
|
or the same value it was passed (which it might have modified in place). |
586
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
The returned hash can also contain an C array, in the same |
588
|
|
|
|
|
|
|
way as an article loader, if the filter adds additional URLs for the file. |
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
The plugin registered will only be called on for files with paths which |
591
|
|
|
|
|
|
|
are the same as, or are under the directory specified by, C<$path>. |
592
|
|
|
|
|
|
|
Plugins should usually just pass the C<$path> value from their C |
593
|
|
|
|
|
|
|
method through to this method as-is. |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
See L for an example of registering and |
596
|
|
|
|
|
|
|
implementing a DOM filter method. |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
=cut |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
sub add_html_dom_filter |
601
|
|
|
|
|
|
|
{ |
602
|
|
|
|
|
|
|
my ($self, $path, $object, $method) = @_; |
603
|
|
|
|
|
|
|
my $filter_name = ref($object) . "->$method"; # just for a hash key |
604
|
|
|
|
|
|
|
croak "HTML DOM filter already defined for '$filter_name' at '$path'" |
605
|
|
|
|
|
|
|
if exists $self->{html_dom_filters}{$filter_name}{$path}; |
606
|
|
|
|
|
|
|
$self->{html_dom_filters}{$filter_name}{$path} = [ $object => $method ]; |
607
|
|
|
|
|
|
|
} |
608
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
sub _std_property_loader |
610
|
|
|
|
|
|
|
{ |
611
|
|
|
|
|
|
|
my ($self, undef, $id, $props) = @_; |
612
|
|
|
|
|
|
|
my $db = $self->{db}; |
613
|
|
|
|
|
|
|
my %update; |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
$update{content_type} = validate_mime_type($props->{'svn:mime-type'}) |
616
|
|
|
|
|
|
|
if exists $props->{'svn:mime-type'}; |
617
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
if (exists $props->{'dcterms:issued'}) { |
619
|
|
|
|
|
|
|
my $time = validate_date($props->{'dcterms:issued'}); |
620
|
|
|
|
|
|
|
warn "file $id has invalid 'dcterms:issued' datetime, ignoring\n" |
621
|
|
|
|
|
|
|
if !defined $time && defined $props->{'dcterms:issued'}; |
622
|
|
|
|
|
|
|
# If the custom publication datetime is removed, or isn't valid, then |
623
|
|
|
|
|
|
|
# reset it back to the default, which is the time of the file's |
624
|
|
|
|
|
|
|
# first commit. |
625
|
|
|
|
|
|
|
if (!defined $time) { |
626
|
|
|
|
|
|
|
my $guid_id = db_select($db, wc_file => $id, 'guid_id'); |
627
|
|
|
|
|
|
|
($time, undef) = guid_first_last_times($db, $guid_id); |
628
|
|
|
|
|
|
|
assert(defined $time) if DEBUG; |
629
|
|
|
|
|
|
|
} |
630
|
|
|
|
|
|
|
$update{issued_at} = db_datetime($time); |
631
|
|
|
|
|
|
|
} |
632
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
if (exists $props->{'dcterms:modified'}) { |
634
|
|
|
|
|
|
|
my $time = validate_date($props->{'dcterms:modified'}); |
635
|
|
|
|
|
|
|
warn "file $id has invalid 'dcterms:modified' datetime, ignoring\n" |
636
|
|
|
|
|
|
|
if !defined $time && defined $props->{'dcterms:modified'}; |
637
|
|
|
|
|
|
|
# If the custom update datetime is removed, or isn't valid, then |
638
|
|
|
|
|
|
|
# reset it back to the default, which is the time of the file's |
639
|
|
|
|
|
|
|
# most recent commit. |
640
|
|
|
|
|
|
|
if (!defined $time) { |
641
|
|
|
|
|
|
|
my $guid_id = db_select($db, wc_file => $id, 'guid_id'); |
642
|
|
|
|
|
|
|
(undef, $time) = guid_first_last_times($db, $guid_id); |
643
|
|
|
|
|
|
|
assert(defined $time) if DEBUG; |
644
|
|
|
|
|
|
|
} |
645
|
|
|
|
|
|
|
$update{modified_at} = db_datetime($time); |
646
|
|
|
|
|
|
|
} |
647
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
while (my ($property, $column) = each %Daizu::OVERRIDABLE_PROPERTY) { |
649
|
|
|
|
|
|
|
$update{$column} = trim_with_empty_null($props->{$property}) |
650
|
|
|
|
|
|
|
if exists $props->{$property}; |
651
|
|
|
|
|
|
|
} |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
if (exists $props->{'daizu:flags'}) { |
654
|
|
|
|
|
|
|
my @stat = split ' ', $props->{'daizu:flags'}; |
655
|
|
|
|
|
|
|
$update{retired} = $update{no_index} = 0; |
656
|
|
|
|
|
|
|
for (@stat) { |
657
|
|
|
|
|
|
|
if ($_ eq 'retired') { |
658
|
|
|
|
|
|
|
$update{retired} = 1; |
659
|
|
|
|
|
|
|
} |
660
|
|
|
|
|
|
|
elsif ($_ eq 'no-index') { |
661
|
|
|
|
|
|
|
$update{no_index} = 1; |
662
|
|
|
|
|
|
|
} |
663
|
|
|
|
|
|
|
else { |
664
|
|
|
|
|
|
|
warn "file contains unrecognized value '$_' in 'daizu:flags'"; |
665
|
|
|
|
|
|
|
} |
666
|
|
|
|
|
|
|
} |
667
|
|
|
|
|
|
|
} |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
$update{custom_url} = validate_uri($props->{'daizu:url'}) |
670
|
|
|
|
|
|
|
if exists $props->{'daizu:url'}; |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
db_update $db, wc_file => $id, %update; |
673
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
if (exists $props->{'daizu:tags'}) { |
675
|
|
|
|
|
|
|
db_delete($db, 'wc_file_tag', file_id => $id); |
676
|
|
|
|
|
|
|
if (defined $props->{'daizu:tags'}) { |
677
|
|
|
|
|
|
|
for (split /\s*[\x0A\x0D]\s*/, trim($props->{'daizu:tags'})) { |
678
|
|
|
|
|
|
|
my $original = $_; |
679
|
|
|
|
|
|
|
# There is no standard for how tags should be written and |
680
|
|
|
|
|
|
|
# what characters are allowed. I fold them to lowercase, and |
681
|
|
|
|
|
|
|
# collapse sequences of whitespace to a single space. |
682
|
|
|
|
|
|
|
$_ = lc $_; |
683
|
|
|
|
|
|
|
s/\s+/ /g; |
684
|
|
|
|
|
|
|
db_insert($db, 'tag', tag => $_) |
685
|
|
|
|
|
|
|
unless db_row_exists($db, 'tag', tag => $_); |
686
|
|
|
|
|
|
|
db_insert($db, 'wc_file_tag', |
687
|
|
|
|
|
|
|
file_id => $id, |
688
|
|
|
|
|
|
|
tag => $_, |
689
|
|
|
|
|
|
|
original_spelling => $original, |
690
|
|
|
|
|
|
|
); |
691
|
|
|
|
|
|
|
} |
692
|
|
|
|
|
|
|
} |
693
|
|
|
|
|
|
|
} |
694
|
|
|
|
|
|
|
} |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
=item $cms-Ecall_property_loaders($id, $props) |
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
Calls the plugin methods which wish to be informed of property changes on |
699
|
|
|
|
|
|
|
a file, where C<$id> is a file ID for a record in the C table, |
700
|
|
|
|
|
|
|
and C<$props> is a reference to a hash of the format described for the |
701
|
|
|
|
|
|
|
Ladd_property_loader($pattern, $object, $method)> |
702
|
|
|
|
|
|
|
method. |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
=cut |
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
sub call_property_loaders |
707
|
|
|
|
|
|
|
{ |
708
|
|
|
|
|
|
|
my ($self, $id, $props) = @_; |
709
|
|
|
|
|
|
|
my $loaders = $self->{property_loaders}; |
710
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
my %seen_loader; |
712
|
|
|
|
|
|
|
my %seen_prefix; |
713
|
|
|
|
|
|
|
for my $name (keys %$props) { |
714
|
|
|
|
|
|
|
if (exists $loaders->{$name}) { |
715
|
|
|
|
|
|
|
for my $loader (@{$loaders->{$name}}) { |
716
|
|
|
|
|
|
|
next if exists $seen_loader{"$loader"}; |
717
|
|
|
|
|
|
|
my ($object, $method) = @$loader; |
718
|
|
|
|
|
|
|
$object->$method($self, $id, $props); |
719
|
|
|
|
|
|
|
undef $seen_loader{"$loader"}; |
720
|
|
|
|
|
|
|
} |
721
|
|
|
|
|
|
|
} |
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
if ($name =~ /^([^:]+):/ && !$seen_prefix{$1} && |
724
|
|
|
|
|
|
|
exists $loaders->{"$1:*"}) |
725
|
|
|
|
|
|
|
{ |
726
|
|
|
|
|
|
|
undef $seen_prefix{$1}; |
727
|
|
|
|
|
|
|
for my $loader (@{$loaders->{"$1:*"}}) { |
728
|
|
|
|
|
|
|
next if exists $seen_loader{"$loader"}; |
729
|
|
|
|
|
|
|
my ($object, $method) = @$loader; |
730
|
|
|
|
|
|
|
$object->$method($self, $id, $props); |
731
|
|
|
|
|
|
|
undef $seen_loader{"$loader"}; |
732
|
|
|
|
|
|
|
} |
733
|
|
|
|
|
|
|
} |
734
|
|
|
|
|
|
|
} |
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
if (exists $loaders->{'*'}) { |
737
|
|
|
|
|
|
|
for my $loader (@{$loaders->{'*'}}) { |
738
|
|
|
|
|
|
|
next if exists $seen_loader{"$loader"}; |
739
|
|
|
|
|
|
|
my ($object, $method) = @$loader; |
740
|
|
|
|
|
|
|
$object->$method($self, $id, $props); |
741
|
|
|
|
|
|
|
undef $seen_loader{"$loader"}; |
742
|
|
|
|
|
|
|
} |
743
|
|
|
|
|
|
|
} |
744
|
|
|
|
|
|
|
} |
745
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
=item $cms-Eguid_entity |
747
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
Return the entity to be used for minting GUID URLs for the file at |
749
|
|
|
|
|
|
|
C<$path>. This finds the best match from the C elements |
750
|
|
|
|
|
|
|
in the configuration file and returns the corresponding C value. |
751
|
|
|
|
|
|
|
|
752
|
|
|
|
|
|
|
=cut |
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
sub guid_entity |
755
|
|
|
|
|
|
|
{ |
756
|
|
|
|
|
|
|
my ($self, $path) = @_; |
757
|
|
|
|
|
|
|
my $best_entity = $self->{default_entity}; |
758
|
|
|
|
|
|
|
my $matched_path = ''; |
759
|
|
|
|
|
|
|
|
760
|
|
|
|
|
|
|
while (my ($want_path, $entity) = each %{$self->{path_entity}}) { |
761
|
|
|
|
|
|
|
next if length($matched_path) > length($want_path); |
762
|
|
|
|
|
|
|
next unless $path eq $want_path || |
763
|
|
|
|
|
|
|
substr($path, 0, length($want_path) + 1) eq "$want_path/"; |
764
|
|
|
|
|
|
|
$best_entity = $entity; |
765
|
|
|
|
|
|
|
$matched_path = $want_path; |
766
|
|
|
|
|
|
|
} |
767
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
return $best_entity; |
769
|
|
|
|
|
|
|
} |
770
|
|
|
|
|
|
|
|
771
|
|
|
|
|
|
|
=item $cms-Eoutput_config($url) |
772
|
|
|
|
|
|
|
|
773
|
|
|
|
|
|
|
Return information about where the published output for C<$url> (a |
774
|
|
|
|
|
|
|
string or L object) should be written to. If there is a suitable |
775
|
|
|
|
|
|
|
C |
776
|
|
|
|
|
|
|
containing information from that element, followed by a list |
777
|
|
|
|
|
|
|
of three strings, which will all be defined. If you join these strings |
778
|
|
|
|
|
|
|
together (by passing them to the C function from L for |
779
|
|
|
|
|
|
|
example) to form a complete path then it will be the path to the file |
780
|
|
|
|
|
|
|
(never directory) which the output should be written to. |
781
|
|
|
|
|
|
|
|
782
|
|
|
|
|
|
|
The first value returned will be a reference to a hash containing the |
783
|
|
|
|
|
|
|
following keys: |
784
|
|
|
|
|
|
|
|
785
|
|
|
|
|
|
|
=over |
786
|
|
|
|
|
|
|
|
787
|
|
|
|
|
|
|
=item url |
788
|
|
|
|
|
|
|
|
789
|
|
|
|
|
|
|
The value from the C attribute in the configuration file, as |
790
|
|
|
|
|
|
|
a L object. |
791
|
|
|
|
|
|
|
|
792
|
|
|
|
|
|
|
=item path |
793
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
The value from the C attribute. |
795
|
|
|
|
|
|
|
|
796
|
|
|
|
|
|
|
=item index_filename |
797
|
|
|
|
|
|
|
|
798
|
|
|
|
|
|
|
The value from the C attribute, or the default |
799
|
|
|
|
|
|
|
value I if one isn't set. |
800
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
=item redirect_map |
802
|
|
|
|
|
|
|
|
803
|
|
|
|
|
|
|
The value from the C attribute, or undef if there isn't one. |
804
|
|
|
|
|
|
|
|
805
|
|
|
|
|
|
|
=item gone_map |
806
|
|
|
|
|
|
|
|
807
|
|
|
|
|
|
|
The value from the C attribute, or undef if there isn't one. |
808
|
|
|
|
|
|
|
|
809
|
|
|
|
|
|
|
=back |
810
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
The other three values are: |
812
|
|
|
|
|
|
|
|
813
|
|
|
|
|
|
|
=over |
814
|
|
|
|
|
|
|
|
815
|
|
|
|
|
|
|
=item * |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
The absolute path to the document root directory, which will be the value |
818
|
|
|
|
|
|
|
of the C attribute in the appropriate C |
819
|
|
|
|
|
|
|
configuration file. This is the same as the C value in the hash. |
820
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
=item * |
822
|
|
|
|
|
|
|
|
823
|
|
|
|
|
|
|
The relative path from there to the directory in which the output file |
824
|
|
|
|
|
|
|
should be written. This is given separately so that you can create that |
825
|
|
|
|
|
|
|
directory if it doesn't exist. This will be the empty string if the |
826
|
|
|
|
|
|
|
output file is to be stored directly in the document root directory, but |
827
|
|
|
|
|
|
|
the C function mentioned above will correctly elide it for you in |
828
|
|
|
|
|
|
|
that case. |
829
|
|
|
|
|
|
|
|
830
|
|
|
|
|
|
|
=item * |
831
|
|
|
|
|
|
|
|
832
|
|
|
|
|
|
|
The filename of the output file. This is a single name, not a path. |
833
|
|
|
|
|
|
|
|
834
|
|
|
|
|
|
|
=back |
835
|
|
|
|
|
|
|
|
836
|
|
|
|
|
|
|
If the configuration doesn't say where C<$url> should be published to then |
837
|
|
|
|
|
|
|
this will return nothing. |
838
|
|
|
|
|
|
|
|
839
|
|
|
|
|
|
|
TODO - this doesn't use C itself, so the results aren't portable |
840
|
|
|
|
|
|
|
across different platforms. |
841
|
|
|
|
|
|
|
|
842
|
|
|
|
|
|
|
=cut |
843
|
|
|
|
|
|
|
|
844
|
|
|
|
|
|
|
sub output_config |
845
|
|
|
|
|
|
|
{ |
846
|
|
|
|
|
|
|
my ($self, $out_url) = @_; |
847
|
|
|
|
|
|
|
$out_url = URI->new($out_url) unless ref $out_url; |
848
|
|
|
|
|
|
|
|
849
|
|
|
|
|
|
|
# Search through all the configured output URLs in reverse order to |
850
|
|
|
|
|
|
|
# find the most specific (longest) one which is a prefix of $out_url. |
851
|
|
|
|
|
|
|
# We do that by checking to see if $out_url can be expressed relative to |
852
|
|
|
|
|
|
|
# the output's base URL without going backwards with '../' at the start. |
853
|
|
|
|
|
|
|
my ($config, $path); |
854
|
|
|
|
|
|
|
for my $url (sort { length $b <=> length $a } keys %{$self->{output}}) { |
855
|
|
|
|
|
|
|
my $rel_url = $out_url->rel($url); |
856
|
|
|
|
|
|
|
next if $rel_url eq $out_url; |
857
|
|
|
|
|
|
|
$rel_url = '' if $rel_url eq './'; |
858
|
|
|
|
|
|
|
next if $rel_url =~ m!^\.\.?(?:/|$)!; |
859
|
|
|
|
|
|
|
$config = $self->{output}{$url}; |
860
|
|
|
|
|
|
|
$path = $rel_url; |
861
|
|
|
|
|
|
|
last; |
862
|
|
|
|
|
|
|
} |
863
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
return unless defined $config; |
865
|
|
|
|
|
|
|
|
866
|
|
|
|
|
|
|
my $filename = $config->{index_filename}; |
867
|
|
|
|
|
|
|
$filename = $1 |
868
|
|
|
|
|
|
|
if $path =~ m!(?:^|/)([^/]+)\z!; |
869
|
|
|
|
|
|
|
$path =~ s!(?:^|/)[^/]*\z!!; |
870
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
return ($config, $config->{path}, $path, $filename); |
872
|
|
|
|
|
|
|
} |
873
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
=back |
875
|
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
=head1 COPYRIGHT |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
This software is copyright 2006 Geoff Richards Egeoff@laxan.comE. |
879
|
|
|
|
|
|
|
For licensing information see this page: |
880
|
|
|
|
|
|
|
|
881
|
|
|
|
|
|
|
L |
882
|
|
|
|
|
|
|
|
883
|
|
|
|
|
|
|
=cut |
884
|
|
|
|
|
|
|
|
885
|
|
|
|
|
|
|
1; |
886
|
|
|
|
|
|
|
# vi:ts=4 sw=4 expandtab |