line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Wiki::Toolkit::Store::Database; |
2
|
|
|
|
|
|
|
|
3
|
8
|
|
|
8
|
|
2636
|
use strict; |
|
8
|
|
|
|
|
8
|
|
|
8
|
|
|
|
|
214
|
|
4
|
|
|
|
|
|
|
|
5
|
8
|
|
|
8
|
|
26
|
use vars qw( $VERSION $timestamp_fmt ); |
|
8
|
|
|
|
|
7
|
|
|
8
|
|
|
|
|
397
|
|
6
|
|
|
|
|
|
|
$timestamp_fmt = "%Y-%m-%d %H:%M:%S"; |
7
|
|
|
|
|
|
|
|
8
|
8
|
|
|
8
|
|
6721
|
use DBI; |
|
8
|
|
|
|
|
63796
|
|
|
8
|
|
|
|
|
360
|
|
9
|
8
|
|
|
8
|
|
5698
|
use Time::Piece; |
|
8
|
|
|
|
|
75885
|
|
|
8
|
|
|
|
|
31
|
|
10
|
8
|
|
|
8
|
|
473
|
use Time::Seconds; |
|
8
|
|
|
|
|
9
|
|
|
8
|
|
|
|
|
521
|
|
11
|
8
|
|
|
8
|
|
32
|
use Carp qw( carp croak ); |
|
8
|
|
|
|
|
11
|
|
|
8
|
|
|
|
|
349
|
|
12
|
8
|
|
|
8
|
|
31
|
use Digest::MD5 qw( md5_hex ); |
|
8
|
|
|
|
|
8
|
|
|
8
|
|
|
|
|
492
|
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
$VERSION = '0.31'; |
15
|
|
|
|
|
|
|
my $SCHEMA_VER = 10; |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
# first, detect if Encode is available - it's not under 5.6. If we _are_ |
18
|
|
|
|
|
|
|
# under 5.6, give up - we'll just have to hope that nothing explodes. This |
19
|
|
|
|
|
|
|
# is the current 0.54 behaviour, so that's ok. |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
my $CAN_USE_ENCODE; |
22
|
|
|
|
|
|
|
BEGIN { |
23
|
8
|
|
|
8
|
|
431
|
eval " use Encode "; |
|
8
|
|
|
8
|
|
1948
|
|
|
8
|
|
|
|
|
27870
|
|
|
8
|
|
|
|
|
436
|
|
24
|
8
|
50
|
|
|
|
50970
|
$CAN_USE_ENCODE = $@ ? 0 : 1; |
25
|
|
|
|
|
|
|
} |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=head1 NAME |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
Wiki::Toolkit::Store::Database - parent class for database storage backends |
30
|
|
|
|
|
|
|
for Wiki::Toolkit |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
=head1 SYNOPSIS |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
This is probably only useful for Wiki::Toolkit developers. |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
# See below for parameter details. |
37
|
|
|
|
|
|
|
my $store = Wiki::Toolkit::Store::MySQL->new( %config ); |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
=head1 METHODS |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
=over 4 |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
=item B |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
my $store = Wiki::Toolkit::Store::MySQL->new( dbname => "wiki", |
46
|
|
|
|
|
|
|
dbuser => "wiki", |
47
|
|
|
|
|
|
|
dbpass => "wiki", |
48
|
|
|
|
|
|
|
dbhost => "db.example.com", |
49
|
|
|
|
|
|
|
dbport => 1234, |
50
|
|
|
|
|
|
|
charset => "iso-8859-1" ); |
51
|
|
|
|
|
|
|
or |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
my $store = Wiki::Toolkit::Store::MySQL->new( dbh => $dbh ); |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
C is optional, defaults to C, and does nothing |
56
|
|
|
|
|
|
|
unless you're using perl 5.8 or newer. |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
If you do not provide an active database handle in C, then |
59
|
|
|
|
|
|
|
C is mandatory. C, C, C and C |
60
|
|
|
|
|
|
|
are optional, but you'll want to supply them unless your database's |
61
|
|
|
|
|
|
|
connection method doesn't require them. |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
If you do provide C then it must have the following |
64
|
|
|
|
|
|
|
parameters set; otherwise you should just provide the connection |
65
|
|
|
|
|
|
|
information and let us create our own handle: |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
=over 4 |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
=item * |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
C = 1 |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=item * |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
C = 0 |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=item * |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
C = 1 |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
=back |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=cut |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
sub new { |
86
|
2
|
|
|
2
|
1
|
34
|
my ($class, @args) = @_; |
87
|
2
|
|
|
|
|
5
|
my $self = {}; |
88
|
2
|
|
|
|
|
3
|
bless $self, $class; |
89
|
2
|
|
|
|
|
11
|
return $self->_init(@args); |
90
|
|
|
|
|
|
|
} |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
sub _init { |
93
|
3
|
|
|
3
|
|
7
|
my ($self, %args) = @_; |
94
|
|
|
|
|
|
|
|
95
|
3
|
50
|
|
|
|
9
|
if ( $args{dbh} ) { |
96
|
0
|
|
|
|
|
0
|
$self->{_dbh} = $args{dbh}; |
97
|
0
|
|
|
|
|
0
|
$self->{_external_dbh} = 1; # don't disconnect at DESTROY time |
98
|
0
|
|
0
|
|
|
0
|
$self->{_charset} = $args{charset} || "iso-8859-1"; |
99
|
|
|
|
|
|
|
} else { |
100
|
3
|
50
|
|
|
|
37
|
die "Must supply a dbname" unless defined $args{dbname}; |
101
|
0
|
|
|
|
|
0
|
$self->{_dbname} = $args{dbname}; |
102
|
0
|
|
0
|
|
|
0
|
$self->{_dbuser} = $args{dbuser} || ""; |
103
|
0
|
|
0
|
|
|
0
|
$self->{_dbpass} = $args{dbpass} || ""; |
104
|
0
|
|
0
|
|
|
0
|
$self->{_dbhost} = $args{dbhost} || ""; |
105
|
0
|
|
0
|
|
|
0
|
$self->{_dbport} = $args{dbport} || ""; |
106
|
0
|
|
0
|
|
|
0
|
$self->{_charset} = $args{charset} || "iso-8859-1"; |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
# Connect to database and store the database handle. |
109
|
|
|
|
|
|
|
my ($dbname, $dbuser, $dbpass, $dbhost, $dbport) = |
110
|
0
|
|
|
|
|
0
|
@$self{qw(_dbname _dbuser _dbpass _dbhost _dbport)}; |
111
|
0
|
0
|
|
|
|
0
|
my $dsn = $self->_dsn($dbname, $dbhost, $dbport) |
112
|
|
|
|
|
|
|
or croak "No data source string provided by class"; |
113
|
0
|
0
|
|
|
|
0
|
$self->{_dbh} = DBI->connect( $dsn, $dbuser, $dbpass, |
114
|
|
|
|
|
|
|
$self->_get_dbh_connect_attr ) |
115
|
|
|
|
|
|
|
or croak "Can't connect to database $dbname using $dsn: " |
116
|
|
|
|
|
|
|
. DBI->errstr; |
117
|
|
|
|
|
|
|
} |
118
|
|
|
|
|
|
|
|
119
|
0
|
|
|
|
|
0
|
my ($cur_ver, $db_ver) = $self->schema_current; |
120
|
0
|
0
|
|
|
|
0
|
if ($db_ver < $cur_ver) { |
|
|
0
|
|
|
|
|
|
121
|
0
|
|
|
|
|
0
|
croak "Database schema version $db_ver is too old (need $cur_ver)"; |
122
|
|
|
|
|
|
|
} elsif ($db_ver > $cur_ver) { |
123
|
0
|
|
|
|
|
0
|
croak "Database schema version $db_ver is too new (need $cur_ver)"; |
124
|
|
|
|
|
|
|
} |
125
|
|
|
|
|
|
|
|
126
|
0
|
|
|
|
|
0
|
return $self; |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
# Internal method to get attributes for passing to DBI->connect(). |
130
|
|
|
|
|
|
|
# Override in subclasses to add database-dependent attributes. |
131
|
|
|
|
|
|
|
sub _get_dbh_connect_attr { |
132
|
|
|
|
|
|
|
return { |
133
|
0
|
|
|
0
|
|
0
|
PrintError => 0, |
134
|
|
|
|
|
|
|
RaiseError => 1, |
135
|
|
|
|
|
|
|
AutoCommit => 1, |
136
|
|
|
|
|
|
|
}; |
137
|
|
|
|
|
|
|
} |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
# Internal method, used to handle the logic of how to add up return |
140
|
|
|
|
|
|
|
# values from pre_ plugins |
141
|
|
|
|
|
|
|
sub handle_pre_plugin_ret { |
142
|
0
|
|
|
0
|
0
|
0
|
my ($running_total_ref,$result) = @_; |
143
|
|
|
|
|
|
|
|
144
|
0
|
0
|
0
|
|
|
0
|
if(($result && $result == 0) || !$result) { |
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
145
|
|
|
|
|
|
|
# No opinion, no need to change things |
146
|
|
|
|
|
|
|
} elsif($result == -1 || $result == 1) { |
147
|
|
|
|
|
|
|
# Increase or decrease as requested |
148
|
0
|
|
|
|
|
0
|
$$running_total_ref += $result; |
149
|
|
|
|
|
|
|
} else { |
150
|
|
|
|
|
|
|
# Invalid return code |
151
|
0
|
|
|
|
|
0
|
warn("Pre_ plugin returned invalid accept/deny value of '$result'"); |
152
|
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
=item B |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
my $content = $store->retrieve_node($node); |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
# Or get additional meta-data too. |
160
|
|
|
|
|
|
|
my %node = $store->retrieve_node("HomePage"); |
161
|
|
|
|
|
|
|
print "Current Version: " . $node{version}; |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
# Maybe we stored some metadata too. |
164
|
|
|
|
|
|
|
my $categories = $node{metadata}{category}; |
165
|
|
|
|
|
|
|
print "Categories: " . join(", ", @$categories); |
166
|
|
|
|
|
|
|
print "Postcode: $node{metadata}{postcode}[0]"; |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
# Or get an earlier version: |
169
|
|
|
|
|
|
|
my %node = $store->retrieve_node(name => "HomePage", |
170
|
|
|
|
|
|
|
version => 2 ); |
171
|
|
|
|
|
|
|
print $node{content}; |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
In scalar context, returns the current (raw Wiki language) contents of |
175
|
|
|
|
|
|
|
the specified node. In list context, returns a hash containing the |
176
|
|
|
|
|
|
|
contents of the node plus additional data: |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
=over 4 |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=item B |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
=item B |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=item B |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
=item B - a reference to a hash containing any caller-supplied |
187
|
|
|
|
|
|
|
metadata sent along the last time the node was written |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=back |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
The node parameter is mandatory. The version parameter is optional and |
192
|
|
|
|
|
|
|
defaults to the newest version. If the node hasn't been created yet, |
193
|
|
|
|
|
|
|
it is considered to exist but be empty (this behaviour might change). |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
B on metadata - each hash value is returned as an array ref, |
196
|
|
|
|
|
|
|
even if that type of metadata only has one value. |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
=cut |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
sub retrieve_node { |
201
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
202
|
0
|
0
|
|
|
|
0
|
my %args = scalar @_ == 1 ? ( name => $_[0] ) : @_; |
203
|
0
|
0
|
|
|
|
0
|
unless($args{'version'}) { $args{'version'} = undef; } |
|
0
|
|
|
|
|
0
|
|
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
# Call pre_retrieve on any plugins, in case they want to tweak anything |
206
|
0
|
0
|
|
|
|
0
|
my @plugins = @{ $args{plugins} || [ ] }; |
|
0
|
|
|
|
|
0
|
|
207
|
0
|
|
|
|
|
0
|
foreach my $plugin (@plugins) { |
208
|
0
|
0
|
|
|
|
0
|
if ( $plugin->can( "pre_retrieve" ) ) { |
209
|
|
|
|
|
|
|
$plugin->pre_retrieve( |
210
|
|
|
|
|
|
|
node => \$args{'name'}, |
211
|
0
|
|
|
|
|
0
|
version => \$args{'version'} |
212
|
|
|
|
|
|
|
); |
213
|
|
|
|
|
|
|
} |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
# Note _retrieve_node_data is sensitive to calling context. |
217
|
0
|
0
|
|
|
|
0
|
unless(wantarray) { |
218
|
|
|
|
|
|
|
# Scalar context, will return just the content |
219
|
0
|
|
|
|
|
0
|
return $self->_retrieve_node_data( %args ); |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
|
222
|
0
|
|
|
|
|
0
|
my %data = $self->_retrieve_node_data( %args ); |
223
|
0
|
|
|
|
|
0
|
$data{'checksum'} = $self->_checksum(%data); |
224
|
0
|
|
|
|
|
0
|
return %data; |
225
|
|
|
|
|
|
|
} |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
# Returns hash or scalar depending on calling context. |
228
|
|
|
|
|
|
|
sub _retrieve_node_data { |
229
|
0
|
|
|
0
|
|
0
|
my ($self, %args) = @_; |
230
|
0
|
|
|
|
|
0
|
my %data = $self->_retrieve_node_content( %args ); |
231
|
0
|
0
|
|
|
|
0
|
unless(wantarray) { |
232
|
|
|
|
|
|
|
# Scalar context, return just the content |
233
|
0
|
|
|
|
|
0
|
return $data{content}; |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
# If we want additional data then get it. Note that $data{version} |
237
|
|
|
|
|
|
|
# will already have been set by C<_retrieve_node_content>, if it wasn't |
238
|
|
|
|
|
|
|
# specified in the call. |
239
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
240
|
0
|
|
|
|
|
0
|
my $sql = "SELECT metadata_type, metadata_value " |
241
|
|
|
|
|
|
|
. "FROM node " |
242
|
|
|
|
|
|
|
. "INNER JOIN metadata ON (node_id = id) " |
243
|
|
|
|
|
|
|
. "WHERE name=? " |
244
|
|
|
|
|
|
|
. "AND metadata.version=?"; |
245
|
0
|
|
|
|
|
0
|
my $sth = $dbh->prepare($sql); |
246
|
0
|
0
|
|
|
|
0
|
$sth->execute($args{name},$data{version}) or croak $dbh->errstr; |
247
|
|
|
|
|
|
|
|
248
|
0
|
|
|
|
|
0
|
my %metadata; |
249
|
0
|
|
|
|
|
0
|
while ( my ($type, $val) = $self->charset_decode( $sth->fetchrow_array ) ) { |
250
|
0
|
0
|
|
|
|
0
|
if ( defined $metadata{$type} ) { |
251
|
0
|
|
|
|
|
0
|
push @{$metadata{$type}}, $val; |
|
0
|
|
|
|
|
0
|
|
252
|
|
|
|
|
|
|
} else { |
253
|
0
|
|
|
|
|
0
|
$metadata{$type} = [ $val ]; |
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
} |
256
|
0
|
|
|
|
|
0
|
$data{metadata} = \%metadata; |
257
|
0
|
|
|
|
|
0
|
return %data; |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
# $store->_retrieve_node_content( name => $node_name, |
261
|
|
|
|
|
|
|
# version => $node_version ); |
262
|
|
|
|
|
|
|
# Params: 'name' is compulsory, 'version' is optional and defaults to latest. |
263
|
|
|
|
|
|
|
# Returns a hash of data for C - content, version, last modified |
264
|
|
|
|
|
|
|
sub _retrieve_node_content { |
265
|
0
|
|
|
0
|
|
0
|
my ($self, %args) = @_; |
266
|
0
|
0
|
|
|
|
0
|
croak "No valid node name supplied" unless $args{name}; |
267
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
268
|
0
|
|
|
|
|
0
|
my $sql; |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
my $version_sql_val; |
271
|
0
|
|
|
|
|
0
|
my $text_source; |
272
|
0
|
0
|
|
|
|
0
|
if ( $args{version} ) { |
273
|
|
|
|
|
|
|
# Version given - get that version, and the content for that version |
274
|
0
|
|
|
|
|
0
|
$version_sql_val = $dbh->quote($self->charset_encode($args{version})); |
275
|
0
|
|
|
|
|
0
|
$text_source = "content"; |
276
|
|
|
|
|
|
|
} else { |
277
|
|
|
|
|
|
|
# No version given, grab latest version (and content for that) |
278
|
0
|
|
|
|
|
0
|
$version_sql_val = "node.version"; |
279
|
0
|
|
|
|
|
0
|
$text_source = "node"; |
280
|
|
|
|
|
|
|
} |
281
|
|
|
|
|
|
|
$sql = "SELECT " |
282
|
|
|
|
|
|
|
. " $text_source.text, content.version, " |
283
|
|
|
|
|
|
|
. " content.modified, content.moderated, " |
284
|
|
|
|
|
|
|
. " node.moderate " |
285
|
|
|
|
|
|
|
. "FROM node " |
286
|
|
|
|
|
|
|
. "INNER JOIN content ON (id = node_id) " |
287
|
0
|
|
|
|
|
0
|
. "WHERE name=" . $dbh->quote($self->charset_encode($args{name})) |
288
|
|
|
|
|
|
|
. " AND content.version=" . $version_sql_val; |
289
|
0
|
|
|
|
|
0
|
my @results = $self->charset_decode( $dbh->selectrow_array($sql) ); |
290
|
0
|
0
|
|
|
|
0
|
@results = ("", 0, "") unless scalar @results; |
291
|
0
|
|
|
|
|
0
|
my %data; |
292
|
0
|
|
|
|
|
0
|
@data{ qw( content version last_modified moderated node_requires_moderation ) } = @results; |
293
|
0
|
|
|
|
|
0
|
return %data; |
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
# Expects a hash as returned by ->retrieve_node - it's actually slightly lax |
297
|
|
|
|
|
|
|
# in this, in that while ->retrieve_node always wraps up the metadata values in |
298
|
|
|
|
|
|
|
# (refs to) arrays, this method will accept scalar metadata values too. |
299
|
|
|
|
|
|
|
sub _checksum { |
300
|
0
|
|
|
0
|
|
0
|
my ($self, %node_data) = @_; |
301
|
0
|
|
|
|
|
0
|
my $string = $node_data{content}; |
302
|
0
|
0
|
|
|
|
0
|
my %metadata = %{ $node_data{metadata} || {} }; |
|
0
|
|
|
|
|
0
|
|
303
|
0
|
|
|
|
|
0
|
foreach my $key ( sort keys %metadata ) { |
304
|
0
|
|
|
|
|
0
|
$string .= "\0\0\0" . $key . "\0\0"; |
305
|
0
|
|
|
|
|
0
|
my $val = $metadata{$key}; |
306
|
0
|
0
|
|
|
|
0
|
if ( ref $val eq "ARRAY" ) { |
307
|
0
|
|
|
|
|
0
|
$string .= join("\0", sort @$val ); |
308
|
|
|
|
|
|
|
} else { |
309
|
0
|
|
|
|
|
0
|
$string .= $val; |
310
|
|
|
|
|
|
|
} |
311
|
|
|
|
|
|
|
} |
312
|
0
|
|
|
|
|
0
|
return md5_hex($self->charset_encode($string)); |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
# Expects an array of hashes whose keys and values are scalars. |
316
|
|
|
|
|
|
|
sub _checksum_hashes { |
317
|
0
|
|
|
0
|
|
0
|
my ($self, @hashes) = @_; |
318
|
0
|
|
|
|
|
0
|
my @strings = ""; |
319
|
0
|
|
|
|
|
0
|
foreach my $hashref ( @hashes ) { |
320
|
0
|
|
|
|
|
0
|
my %hash = %$hashref; |
321
|
0
|
|
|
|
|
0
|
my $substring = ""; |
322
|
0
|
|
|
|
|
0
|
foreach my $key ( sort keys %hash ) { |
323
|
0
|
|
|
|
|
0
|
$substring .= "\0\0" . $key . "\0" . $hash{$key}; |
324
|
|
|
|
|
|
|
} |
325
|
0
|
|
|
|
|
0
|
push @strings, $substring; |
326
|
|
|
|
|
|
|
} |
327
|
0
|
|
|
|
|
0
|
my $string = join("\0\0\0", sort @strings); |
328
|
0
|
|
|
|
|
0
|
return md5_hex($string); |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
=item B |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
my $ok = $store->node_exists( "Wombat Defenestration" ); |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
# or ignore case - optional but recommended |
336
|
|
|
|
|
|
|
my $ok = $store->node_exists( |
337
|
|
|
|
|
|
|
name => "monkey brains", |
338
|
|
|
|
|
|
|
ignore_case => 1, |
339
|
|
|
|
|
|
|
); |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
Returns true if the node has ever been created (even if it is |
342
|
|
|
|
|
|
|
currently empty), and false otherwise. |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
By default, the case-sensitivity of C depends on your |
345
|
|
|
|
|
|
|
database. If you supply a true value to the C parameter, |
346
|
|
|
|
|
|
|
then you can be sure of its being case-insensitive. This is |
347
|
|
|
|
|
|
|
recommended. |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
=cut |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
sub node_exists { |
352
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
353
|
0
|
0
|
|
|
|
0
|
if ( scalar @_ == 1 ) { |
354
|
0
|
|
|
|
|
0
|
my $node = shift; |
355
|
0
|
|
|
|
|
0
|
return $self->_do_old_node_exists( $node ); |
356
|
|
|
|
|
|
|
} else { |
357
|
0
|
|
|
|
|
0
|
my %args = @_; |
358
|
|
|
|
|
|
|
return $self->_do_old_node_exists( $args{name} ) |
359
|
0
|
0
|
|
|
|
0
|
unless $args{ignore_case}; |
360
|
0
|
|
|
|
|
0
|
my $sql = $self->_get_node_exists_ignore_case_sql; |
361
|
0
|
|
|
|
|
0
|
my $sth = $self->dbh->prepare( $sql ); |
362
|
0
|
|
|
|
|
0
|
$sth->execute( $args{name} ); |
363
|
0
|
|
0
|
|
|
0
|
my $found_name = $sth->fetchrow_array || ""; |
364
|
0
|
|
|
|
|
0
|
$sth->finish; |
365
|
0
|
0
|
|
|
|
0
|
return lc($found_name) eq lc($args{name}) ? 1 : 0; |
366
|
|
|
|
|
|
|
} |
367
|
|
|
|
|
|
|
} |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
sub _do_old_node_exists { |
370
|
0
|
|
|
0
|
|
0
|
my ($self, $node) = @_; |
371
|
0
|
0
|
|
|
|
0
|
my %data = $self->retrieve_node($node) or return (); |
372
|
0
|
|
|
|
|
0
|
return $data{version}; # will be 0 if node doesn't exist, >=1 otherwise |
373
|
|
|
|
|
|
|
} |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
=item B |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
my $ok = $store->verify_checksum($node, $checksum); |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
Sees whether your checksum is current for the given node. Returns true |
380
|
|
|
|
|
|
|
if so, false if not. |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
B Be aware that when called directly and without locking, this |
383
|
|
|
|
|
|
|
might not be accurate, since there is a small window between the |
384
|
|
|
|
|
|
|
checking and the returning where the node might be changed, so |
385
|
|
|
|
|
|
|
B rely on it for safe commits; use C for that. It |
386
|
|
|
|
|
|
|
can however be useful when previewing edits, for example. |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
=cut |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
sub verify_checksum { |
391
|
0
|
|
|
0
|
1
|
0
|
my ($self, $node, $checksum) = @_; |
392
|
|
|
|
|
|
|
#warn $self; |
393
|
0
|
|
|
|
|
0
|
my %node_data = $self->_retrieve_node_data( name => $node ); |
394
|
0
|
|
|
|
|
0
|
return ( $checksum eq $self->_checksum( %node_data ) ); |
395
|
|
|
|
|
|
|
} |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
=item B |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
# List all nodes that link to the Home Page. |
400
|
|
|
|
|
|
|
my @links = $store->list_backlinks( node => "Home Page" ); |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
=cut |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
sub list_backlinks { |
405
|
0
|
|
|
0
|
1
|
0
|
my ( $self, %args ) = @_; |
406
|
0
|
|
|
|
|
0
|
my $node = $args{node}; |
407
|
0
|
0
|
|
|
|
0
|
croak "Must supply a node name" unless $node; |
408
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
409
|
|
|
|
|
|
|
# XXX see comment in list_dangling_links |
410
|
0
|
|
|
|
|
0
|
my $sql = "SELECT link_from FROM internal_links INNER JOIN |
411
|
|
|
|
|
|
|
node AS node_from ON node_from.name=internal_links.link_from |
412
|
|
|
|
|
|
|
WHERE link_to=" |
413
|
|
|
|
|
|
|
. $dbh->quote($node); |
414
|
0
|
|
|
|
|
0
|
my $sth = $dbh->prepare($sql); |
415
|
0
|
0
|
|
|
|
0
|
$sth->execute or croak $dbh->errstr; |
416
|
0
|
|
|
|
|
0
|
my @backlinks; |
417
|
0
|
|
|
|
|
0
|
while ( my ($backlink) = $self->charset_decode( $sth->fetchrow_array ) ) { |
418
|
0
|
|
|
|
|
0
|
push @backlinks, $backlink; |
419
|
|
|
|
|
|
|
} |
420
|
0
|
|
|
|
|
0
|
return @backlinks; |
421
|
|
|
|
|
|
|
} |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
=item B |
424
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
# List all nodes that have been linked to from other nodes but don't |
426
|
|
|
|
|
|
|
# yet exist. |
427
|
|
|
|
|
|
|
my @links = $store->list_dangling_links; |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
Each node is returned once only, regardless of how many other nodes |
430
|
|
|
|
|
|
|
link to it. |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
=cut |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
sub list_dangling_links { |
435
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
436
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
437
|
|
|
|
|
|
|
# XXX this is really hiding an inconsistency in the database; |
438
|
|
|
|
|
|
|
# should really fix the constraints so that this inconsistency |
439
|
|
|
|
|
|
|
# cannot be introduced; also rework this table completely so |
440
|
|
|
|
|
|
|
# that it uses IDs, not node names (will simplify rename_node too) |
441
|
0
|
|
|
|
|
0
|
my $sql = "SELECT DISTINCT internal_links.link_to |
442
|
|
|
|
|
|
|
FROM internal_links INNER JOIN node AS node_from ON |
443
|
|
|
|
|
|
|
node_from.name=internal_links.link_from LEFT JOIN node |
444
|
|
|
|
|
|
|
AS node_to ON node_to.name=internal_links.link_to |
445
|
|
|
|
|
|
|
WHERE node_to.version IS NULL"; |
446
|
0
|
|
|
|
|
0
|
my $sth = $dbh->prepare($sql); |
447
|
0
|
0
|
|
|
|
0
|
$sth->execute or croak $dbh->errstr; |
448
|
0
|
|
|
|
|
0
|
my @links; |
449
|
0
|
|
|
|
|
0
|
while ( my ($link) = $self->charset_decode( $sth->fetchrow_array ) ) { |
450
|
0
|
|
|
|
|
0
|
push @links, $link; |
451
|
|
|
|
|
|
|
} |
452
|
0
|
|
|
|
|
0
|
return @links; |
453
|
|
|
|
|
|
|
} |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
=item B |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
$store->write_node_post_locking( node => $node, |
458
|
|
|
|
|
|
|
content => $content, |
459
|
|
|
|
|
|
|
links_to => \@links_to, |
460
|
|
|
|
|
|
|
metadata => \%metadata, |
461
|
|
|
|
|
|
|
requires_moderation => $requires_moderation, |
462
|
|
|
|
|
|
|
plugins => \@plugins ) |
463
|
|
|
|
|
|
|
or handle_error(); |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
Writes the specified content into the specified node, then calls |
466
|
|
|
|
|
|
|
C on all supplied plugins, with arguments C, |
467
|
|
|
|
|
|
|
C, C, C. |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
Making sure that locking/unlocking/transactions happen is left up to |
470
|
|
|
|
|
|
|
you (or your chosen subclass). This method shouldn't really be used |
471
|
|
|
|
|
|
|
directly as it might overwrite someone else's changes. Croaks on error |
472
|
|
|
|
|
|
|
but otherwise returns the version number of the update just made. A |
473
|
|
|
|
|
|
|
return value of -1 indicates that the change was not applied. This |
474
|
|
|
|
|
|
|
may be because the plugins voted against the change, or because the |
475
|
|
|
|
|
|
|
content and metadata in the proposed new version were identical to the |
476
|
|
|
|
|
|
|
current version (a "null" change). |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
Supplying a ref to an array of nodes that this ones links to is |
479
|
|
|
|
|
|
|
optional, but if you do supply it then this node will be returned when |
480
|
|
|
|
|
|
|
calling C on the nodes in C<@links_to>. B that |
481
|
|
|
|
|
|
|
if you don't supply the ref then the store will assume that this node |
482
|
|
|
|
|
|
|
doesn't link to any others, and update itself accordingly. |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
The metadata hashref is also optional, as is requires_moderation. |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
B on the metadata hashref: Any data in here that you wish to |
487
|
|
|
|
|
|
|
access directly later must be a key-value pair in which the value is |
488
|
|
|
|
|
|
|
either a scalar or a reference to an array of scalars. For example: |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
$wiki->write_node( "Calthorpe Arms", "nice pub", $checksum, |
491
|
|
|
|
|
|
|
{ category => [ "Pubs", "Bloomsbury" ], |
492
|
|
|
|
|
|
|
postcode => "WC1X 8JR" } ); |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
# and later |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
my @nodes = $wiki->list_nodes_by_metadata( |
497
|
|
|
|
|
|
|
metadata_type => "category", |
498
|
|
|
|
|
|
|
metadata_value => "Pubs" ); |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
For more advanced usage (passing data through to registered plugins) |
501
|
|
|
|
|
|
|
you may if you wish pass key-value pairs in which the value is a |
502
|
|
|
|
|
|
|
hashref or an array of hashrefs. The data in the hashrefs will not be |
503
|
|
|
|
|
|
|
stored as metadata; it will be checksummed and the checksum will be |
504
|
|
|
|
|
|
|
stored instead (as C<__metadatatypename__checksum>). Such data can |
505
|
|
|
|
|
|
|
I be accessed via plugins. |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
=cut |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
sub write_node_post_locking { |
510
|
0
|
|
|
0
|
1
|
0
|
my ($self, %args) = @_; |
511
|
|
|
|
|
|
|
my ($node, $content, $links_to_ref, $metadata_ref, $requires_moderation) = |
512
|
0
|
|
|
|
|
0
|
@args{ qw( node content links_to metadata requires_moderation) }; |
513
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
514
|
|
|
|
|
|
|
|
515
|
0
|
|
|
|
|
0
|
my $timestamp = $self->_get_timestamp(); |
516
|
0
|
0
|
|
|
|
0
|
my @links_to = @{ $links_to_ref || [] }; # default to empty array |
|
0
|
|
|
|
|
0
|
|
517
|
0
|
|
|
|
|
0
|
my $version; |
518
|
0
|
0
|
|
|
|
0
|
unless($requires_moderation) { $requires_moderation = 0; } |
|
0
|
|
|
|
|
0
|
|
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
# Call pre_write on any plugins, in case they want to tweak anything |
521
|
0
|
0
|
|
|
|
0
|
my @preplugins = @{ $args{plugins} || [ ] }; |
|
0
|
|
|
|
|
0
|
|
522
|
0
|
|
|
|
|
0
|
my $write_allowed = 1; |
523
|
0
|
|
|
|
|
0
|
foreach my $plugin (@preplugins) { |
524
|
0
|
0
|
|
|
|
0
|
if ( $plugin->can( "pre_write" ) ) { |
525
|
0
|
|
|
|
|
0
|
handle_pre_plugin_ret( |
526
|
|
|
|
|
|
|
\$write_allowed, |
527
|
|
|
|
|
|
|
$plugin->pre_write( |
528
|
|
|
|
|
|
|
node => \$node, |
529
|
|
|
|
|
|
|
content => \$content, |
530
|
|
|
|
|
|
|
metadata => \$metadata_ref ) |
531
|
|
|
|
|
|
|
); |
532
|
|
|
|
|
|
|
} |
533
|
|
|
|
|
|
|
} |
534
|
0
|
0
|
|
|
|
0
|
if($write_allowed < 1) { |
535
|
|
|
|
|
|
|
# The plugins didn't want to allow this action |
536
|
0
|
|
|
|
|
0
|
return -1; |
537
|
|
|
|
|
|
|
} |
538
|
|
|
|
|
|
|
|
539
|
0
|
0
|
|
|
|
0
|
if ( $self->_checksum( %args ) eq $args{checksum} ) { |
540
|
|
|
|
|
|
|
# Refuse to commit as nothing has changed |
541
|
0
|
|
|
|
|
0
|
return -1; |
542
|
|
|
|
|
|
|
} |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
# Either inserting a new page or updating an old one. |
545
|
0
|
|
|
|
|
0
|
my $sql = "SELECT count(*) FROM node WHERE name=" . $dbh->quote($node); |
546
|
0
|
|
0
|
|
|
0
|
my $exists = @{ $dbh->selectcol_arrayref($sql) }[0] || 0; |
547
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
# If it doesn't exist, add it right now |
550
|
0
|
0
|
|
|
|
0
|
if(! $exists) { |
551
|
|
|
|
|
|
|
# Add in a new version |
552
|
0
|
|
|
|
|
0
|
$version = 1; |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
# Handle initial moderation |
555
|
0
|
|
|
|
|
0
|
my $node_content = $content; |
556
|
0
|
0
|
|
|
|
0
|
if($requires_moderation) { |
557
|
0
|
|
|
|
|
0
|
$node_content = "=== This page has yet to be moderated. ==="; |
558
|
|
|
|
|
|
|
} |
559
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
# Add the node and content |
561
|
0
|
|
|
|
|
0
|
my $add_sql = |
562
|
|
|
|
|
|
|
"INSERT INTO node " |
563
|
|
|
|
|
|
|
." (name, version, text, modified, moderate) " |
564
|
|
|
|
|
|
|
."VALUES (?, ?, ?, ?, ?)"; |
565
|
0
|
|
|
|
|
0
|
my $add_sth = $dbh->prepare($add_sql); |
566
|
|
|
|
|
|
|
$add_sth->execute( |
567
|
0
|
0
|
|
|
|
0
|
map{ $self->charset_encode($_) } |
|
0
|
|
|
|
|
0
|
|
568
|
|
|
|
|
|
|
($node, $version, $node_content, $timestamp, $requires_moderation) |
569
|
|
|
|
|
|
|
) or croak "Error updating database: " . DBI->errstr; |
570
|
|
|
|
|
|
|
} |
571
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
# Get the ID of the node we've added / we're about to update |
573
|
|
|
|
|
|
|
# Also get the moderation status for it |
574
|
0
|
|
|
|
|
0
|
$sql = "SELECT id, moderate FROM node WHERE name=" . $dbh->quote($node); |
575
|
0
|
|
|
|
|
0
|
my ($node_id,$node_requires_moderation) = $dbh->selectrow_array($sql); |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
# Only update node if it exists, and moderation isn't enabled on the node |
578
|
|
|
|
|
|
|
# Whatever happens, if it exists, generate a new version number |
579
|
0
|
0
|
|
|
|
0
|
if($exists) { |
580
|
|
|
|
|
|
|
# Get the new version number |
581
|
0
|
|
|
|
|
0
|
$sql = "SELECT max(content.version) FROM node |
582
|
|
|
|
|
|
|
INNER JOIN content ON (id = node_id) |
583
|
|
|
|
|
|
|
WHERE name=" . $dbh->quote($node); |
584
|
0
|
|
0
|
|
|
0
|
$version = @{ $dbh->selectcol_arrayref($sql) }[0] || 0; |
585
|
0
|
0
|
|
|
|
0
|
croak "Can't get version number" unless $version; |
586
|
0
|
|
|
|
|
0
|
$version++; |
587
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
# Update the node only if node doesn't require moderation |
589
|
0
|
0
|
|
|
|
0
|
if(!$node_requires_moderation) { |
590
|
0
|
|
|
|
|
0
|
$sql = "UPDATE node SET version=" . $dbh->quote($version) |
591
|
|
|
|
|
|
|
. ", text=" . $dbh->quote($self->charset_encode($content)) |
592
|
|
|
|
|
|
|
. ", modified=" . $dbh->quote($timestamp) |
593
|
|
|
|
|
|
|
. " WHERE name=" . $dbh->quote($self->charset_encode($node)); |
594
|
0
|
0
|
|
|
|
0
|
$dbh->do($sql) or croak "Error updating database: " . DBI->errstr; |
595
|
|
|
|
|
|
|
} |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
# You can't use this to enable moderation on an existing node |
598
|
0
|
0
|
|
|
|
0
|
if($requires_moderation) { |
599
|
0
|
|
|
|
|
0
|
warn("Moderation not added to existing node '$node', use normal moderation methods instead"); |
600
|
|
|
|
|
|
|
} |
601
|
|
|
|
|
|
|
} |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
# Now node is updated (if required), add to the history |
605
|
0
|
|
|
|
|
0
|
my $add_sql = |
606
|
|
|
|
|
|
|
"INSERT INTO content " |
607
|
|
|
|
|
|
|
." (node_id, version, text, modified, moderated) " |
608
|
|
|
|
|
|
|
."VALUES (?, ?, ?, ?, ?)"; |
609
|
0
|
|
|
|
|
0
|
my $add_sth = $dbh->prepare($add_sql); |
610
|
|
|
|
|
|
|
$add_sth->execute( |
611
|
0
|
0
|
|
|
|
0
|
map { $self->charset_encode($_) } |
|
0
|
|
|
|
|
0
|
|
612
|
|
|
|
|
|
|
($node_id, $version, $content, $timestamp, (1-$node_requires_moderation)) |
613
|
|
|
|
|
|
|
) or croak "Error updating database: " . DBI->errstr; |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
|
616
|
|
|
|
|
|
|
# Update the backlinks. |
617
|
0
|
0
|
|
|
|
0
|
$dbh->do("DELETE FROM internal_links WHERE link_from=" |
618
|
|
|
|
|
|
|
. $dbh->quote($self->charset_encode($node)) ) or croak $dbh->errstr; |
619
|
0
|
|
|
|
|
0
|
foreach my $links_to ( @links_to ) { |
620
|
|
|
|
|
|
|
$sql = "INSERT INTO internal_links (link_from, link_to) VALUES (" |
621
|
0
|
|
|
|
|
0
|
. join(", ", map { $dbh->quote($self->charset_encode($_)) } ( $node, $links_to ) ) . ")"; |
|
0
|
|
|
|
|
0
|
|
622
|
|
|
|
|
|
|
# Better to drop a backlink or two than to lose the whole update. |
623
|
|
|
|
|
|
|
# Shevek wants a case-sensitive wiki, Jerakeen wants a case-insensitive |
624
|
|
|
|
|
|
|
# one, MySQL compares case-sensitively on varchars unless you add |
625
|
|
|
|
|
|
|
# the binary keyword. Case-sensitivity to be revisited. |
626
|
0
|
|
|
|
|
0
|
eval { $dbh->do($sql); }; |
|
0
|
|
|
|
|
0
|
|
627
|
0
|
0
|
|
|
|
0
|
carp "Couldn't index backlink: " . $dbh->errstr if $@; |
628
|
|
|
|
|
|
|
} |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
# And also store any metadata. Note that any entries already in the |
631
|
|
|
|
|
|
|
# metadata table refer to old versions, so we don't need to delete them. |
632
|
0
|
0
|
|
|
|
0
|
my %metadata = %{ $metadata_ref || {} }; # default to no metadata |
|
0
|
|
|
|
|
0
|
|
633
|
0
|
|
|
|
|
0
|
foreach my $type ( keys %metadata ) { |
634
|
0
|
|
|
|
|
0
|
my $val = $metadata{$type}; |
635
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
# We might have one or many values; make an array now to merge cases. |
637
|
0
|
0
|
0
|
|
|
0
|
my @values = (ref $val and ref $val eq 'ARRAY') ? @$val : ( $val ); |
638
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
# Find out whether all values for this type are scalars. |
640
|
0
|
|
|
|
|
0
|
my $all_scalars = 1; |
641
|
0
|
|
|
|
|
0
|
foreach my $value (@values) { |
642
|
0
|
0
|
|
|
|
0
|
$all_scalars = 0 if ref $value; |
643
|
|
|
|
|
|
|
} |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
# For adding to metadata |
646
|
0
|
|
|
|
|
0
|
my $add_sql = |
647
|
|
|
|
|
|
|
"INSERT INTO metadata " |
648
|
|
|
|
|
|
|
." (node_id, version, metadata_type, metadata_value) " |
649
|
|
|
|
|
|
|
."VALUES (?, ?, ?, ?)"; |
650
|
0
|
|
|
|
|
0
|
my $add_sth = $dbh->prepare($add_sql); |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
# If all values for this type are scalars, strip out any duplicates |
653
|
|
|
|
|
|
|
# and store the data. |
654
|
0
|
0
|
|
|
|
0
|
if ( $all_scalars ) { |
655
|
0
|
|
|
|
|
0
|
my %unique = map { $_ => 1 } @values; |
|
0
|
|
|
|
|
0
|
|
656
|
0
|
|
|
|
|
0
|
@values = keys %unique; |
657
|
|
|
|
|
|
|
|
658
|
0
|
|
|
|
|
0
|
foreach my $value ( @values ) { |
659
|
|
|
|
|
|
|
$add_sth->execute( |
660
|
0
|
0
|
|
|
|
0
|
map { $self->charset_encode($_) } |
|
0
|
|
|
|
|
0
|
|
661
|
|
|
|
|
|
|
( $node_id, $version, $type, $value ) |
662
|
|
|
|
|
|
|
) or croak $dbh->errstr; |
663
|
|
|
|
|
|
|
} |
664
|
|
|
|
|
|
|
} else { |
665
|
|
|
|
|
|
|
# Otherwise grab a checksum and store that. |
666
|
0
|
|
|
|
|
0
|
my $type_to_store = "__" . $type . "__checksum"; |
667
|
0
|
|
|
|
|
0
|
my $value_to_store = $self->_checksum_hashes( @values ); |
668
|
|
|
|
|
|
|
$add_sth->execute( |
669
|
0
|
0
|
|
|
|
0
|
map { $self->charset_encode($_) } |
|
0
|
|
|
|
|
0
|
|
670
|
|
|
|
|
|
|
( $node_id, $version, $type_to_store, $value_to_store ) |
671
|
|
|
|
|
|
|
) or croak $dbh->errstr; |
672
|
|
|
|
|
|
|
} |
673
|
|
|
|
|
|
|
} |
674
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
# Finally call post_write on any plugins. |
676
|
0
|
0
|
|
|
|
0
|
my @postplugins = @{ $args{plugins} || [ ] }; |
|
0
|
|
|
|
|
0
|
|
677
|
0
|
|
|
|
|
0
|
foreach my $plugin (@postplugins) { |
678
|
0
|
0
|
|
|
|
0
|
if ( $plugin->can( "post_write" ) ) { |
679
|
0
|
|
|
|
|
0
|
$plugin->post_write( |
680
|
|
|
|
|
|
|
node => $node, |
681
|
|
|
|
|
|
|
node_id => $node_id, |
682
|
|
|
|
|
|
|
version => $version, |
683
|
|
|
|
|
|
|
content => $content, |
684
|
|
|
|
|
|
|
metadata => $metadata_ref ); |
685
|
|
|
|
|
|
|
} |
686
|
|
|
|
|
|
|
} |
687
|
|
|
|
|
|
|
|
688
|
0
|
|
|
|
|
0
|
return $version; |
689
|
|
|
|
|
|
|
} |
690
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
# Returns the timestamp of now, unless epoch is supplied. |
692
|
|
|
|
|
|
|
sub _get_timestamp { |
693
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
694
|
|
|
|
|
|
|
# I don't care about no steenkin' timezones (yet). |
695
|
0
|
|
0
|
|
|
0
|
my $time = shift || localtime; # Overloaded by Time::Piece. |
696
|
0
|
0
|
|
|
|
0
|
unless( ref $time ) { |
697
|
0
|
|
|
|
|
0
|
$time = localtime($time); # Make it into an object for strftime |
698
|
|
|
|
|
|
|
} |
699
|
0
|
|
|
|
|
0
|
return $time->strftime($timestamp_fmt); # global |
700
|
|
|
|
|
|
|
} |
701
|
|
|
|
|
|
|
|
702
|
|
|
|
|
|
|
=item B |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
$store->rename_node( |
705
|
|
|
|
|
|
|
old_name => $node, |
706
|
|
|
|
|
|
|
new_name => $new_node, |
707
|
|
|
|
|
|
|
wiki => $wiki, |
708
|
|
|
|
|
|
|
create_new_versions => $create_new_versions, |
709
|
|
|
|
|
|
|
); |
710
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
Renames a node, updating any references to it as required (assuming your |
712
|
|
|
|
|
|
|
chosen formatter supports rename, that is). |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
Uses the internal_links table to identify the nodes that link to this |
715
|
|
|
|
|
|
|
one, and re-writes any wiki links in these to point to the new name. |
716
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
=cut |
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
sub rename_node { |
720
|
0
|
|
|
0
|
1
|
0
|
my ($self, %args) = @_; |
721
|
|
|
|
|
|
|
my ($old_name,$new_name,$wiki,$create_new_versions) = |
722
|
0
|
|
|
|
|
0
|
@args{ qw( old_name new_name wiki create_new_versions ) }; |
723
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
724
|
0
|
|
|
|
|
0
|
my $formatter = $wiki->{_formatter}; |
725
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
# For formatters that support it, run the new name through the node name |
727
|
|
|
|
|
|
|
# to param conversion and back again, to make sure any necessary munging |
728
|
|
|
|
|
|
|
# gets done. |
729
|
0
|
0
|
0
|
|
|
0
|
if ( $formatter->can( "node_name_to_node_param" ) |
730
|
|
|
|
|
|
|
&& $formatter->can( "node_param_to_node_name" ) ) { |
731
|
0
|
|
|
|
|
0
|
$new_name = $formatter->node_param_to_node_name( |
732
|
|
|
|
|
|
|
$formatter->node_name_to_node_param( $new_name ) ); |
733
|
|
|
|
|
|
|
} |
734
|
|
|
|
|
|
|
|
735
|
0
|
|
|
|
|
0
|
my $timestamp = $self->_get_timestamp(); |
736
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
# Call pre_rename on any plugins, in case they want to tweak anything |
738
|
0
|
0
|
|
|
|
0
|
my @preplugins = @{ $args{plugins} || [ ] }; |
|
0
|
|
|
|
|
0
|
|
739
|
0
|
|
|
|
|
0
|
my $rename_allowed = 1; |
740
|
0
|
|
|
|
|
0
|
foreach my $plugin (@preplugins) { |
741
|
0
|
0
|
|
|
|
0
|
if ( $plugin->can( "pre_rename" ) ) { |
742
|
0
|
|
|
|
|
0
|
handle_pre_plugin_ret( |
743
|
|
|
|
|
|
|
\$rename_allowed, |
744
|
|
|
|
|
|
|
$plugin->pre_rename( |
745
|
|
|
|
|
|
|
old_name => \$old_name, |
746
|
|
|
|
|
|
|
new_name => \$new_name, |
747
|
|
|
|
|
|
|
create_new_versions => \$create_new_versions, |
748
|
|
|
|
|
|
|
) |
749
|
|
|
|
|
|
|
); |
750
|
|
|
|
|
|
|
} |
751
|
|
|
|
|
|
|
} |
752
|
0
|
0
|
|
|
|
0
|
if($rename_allowed < 1) { |
753
|
|
|
|
|
|
|
# The plugins didn't want to allow this action |
754
|
0
|
|
|
|
|
0
|
return -1; |
755
|
|
|
|
|
|
|
} |
756
|
|
|
|
|
|
|
|
757
|
|
|
|
|
|
|
# Get the ID of the node |
758
|
0
|
|
|
|
|
0
|
my $sql = "SELECT id FROM node WHERE name=?"; |
759
|
0
|
|
|
|
|
0
|
my $sth = $dbh->prepare($sql); |
760
|
0
|
|
|
|
|
0
|
$sth->execute($old_name); |
761
|
0
|
|
|
|
|
0
|
my ($node_id) = $sth->fetchrow_array; |
762
|
0
|
|
|
|
|
0
|
$sth->finish; |
763
|
|
|
|
|
|
|
|
764
|
|
|
|
|
|
|
|
765
|
|
|
|
|
|
|
# If the formatter supports it, get a list of the internal |
766
|
|
|
|
|
|
|
# links to the page, which will have their links re-written |
767
|
|
|
|
|
|
|
# (Do now before we update the name of the node, in case of |
768
|
|
|
|
|
|
|
# self links) |
769
|
0
|
|
|
|
|
0
|
my @links; |
770
|
0
|
0
|
|
|
|
0
|
if($formatter->can("rename_links")) { |
771
|
|
|
|
|
|
|
# Get a list of the pages that link to the page |
772
|
0
|
|
|
|
|
0
|
$sql = "SELECT id, name, version " |
773
|
|
|
|
|
|
|
."FROM internal_links " |
774
|
|
|
|
|
|
|
."INNER JOIN node " |
775
|
|
|
|
|
|
|
." ON (link_from = name) " |
776
|
|
|
|
|
|
|
."WHERE link_to = ?"; |
777
|
0
|
|
|
|
|
0
|
$sth = $dbh->prepare($sql); |
778
|
0
|
|
|
|
|
0
|
$sth->execute($old_name); |
779
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
# Grab them all, then update, so no locking problems |
781
|
0
|
|
|
|
|
0
|
while(my @l = $sth->fetchrow_array) { push (@links, \@l); } |
|
0
|
|
|
|
|
0
|
|
782
|
|
|
|
|
|
|
} |
783
|
|
|
|
|
|
|
|
784
|
|
|
|
|
|
|
|
785
|
|
|
|
|
|
|
# Rename the node |
786
|
0
|
|
|
|
|
0
|
$sql = "UPDATE node SET name=? WHERE id=?"; |
787
|
0
|
|
|
|
|
0
|
$sth = $dbh->prepare($sql); |
788
|
0
|
|
|
|
|
0
|
$sth->execute($new_name,$node_id); |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
|
791
|
|
|
|
|
|
|
# Fix the internal links from this page |
792
|
|
|
|
|
|
|
# (Otherwise write_node will get confused if we rename links later on) |
793
|
0
|
|
|
|
|
0
|
$sql = "UPDATE internal_links SET link_from=? WHERE link_from=?"; |
794
|
0
|
|
|
|
|
0
|
$sth = $dbh->prepare($sql); |
795
|
0
|
|
|
|
|
0
|
$sth->execute($new_name,$old_name); |
796
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
|
798
|
|
|
|
|
|
|
# Update the text of internal links, if the formatter supports it |
799
|
0
|
0
|
|
|
|
0
|
if($formatter->can("rename_links")) { |
800
|
|
|
|
|
|
|
# Update the linked pages (may include renamed page) |
801
|
0
|
|
|
|
|
0
|
foreach my $l (@links) { |
802
|
0
|
|
|
|
|
0
|
my ($page_id, $page_name, $page_version) = @$l; |
803
|
|
|
|
|
|
|
# Self link special case |
804
|
0
|
0
|
|
|
|
0
|
if($page_name eq $old_name) { $page_name = $new_name; } |
|
0
|
|
|
|
|
0
|
|
805
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
# Grab the latest version of that page |
807
|
0
|
|
|
|
|
0
|
my %page = $self->retrieve_node( |
808
|
|
|
|
|
|
|
name=>$page_name, version=>$page_version |
809
|
|
|
|
|
|
|
); |
810
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
# Update the content of the page |
812
|
|
|
|
|
|
|
my $new_content = |
813
|
0
|
|
|
|
|
0
|
$formatter->rename_links($old_name,$new_name,$page{'content'}); |
814
|
|
|
|
|
|
|
|
815
|
|
|
|
|
|
|
# Did it change? |
816
|
0
|
0
|
|
|
|
0
|
if($new_content ne $page{'content'}) { |
817
|
|
|
|
|
|
|
# Write the updated page out |
818
|
0
|
0
|
|
|
|
0
|
if($create_new_versions) { |
819
|
|
|
|
|
|
|
# Write out as a new version of the node |
820
|
|
|
|
|
|
|
# (This will also fix our internal links) |
821
|
|
|
|
|
|
|
$wiki->write_node( |
822
|
|
|
|
|
|
|
$page_name, |
823
|
|
|
|
|
|
|
$new_content, |
824
|
|
|
|
|
|
|
$page{checksum}, |
825
|
|
|
|
|
|
|
$page{metadata} |
826
|
0
|
|
|
|
|
0
|
); |
827
|
|
|
|
|
|
|
} else { |
828
|
|
|
|
|
|
|
# Just update the content |
829
|
0
|
|
|
|
|
0
|
my $update_sql_a = "UPDATE node SET text=? WHERE id=?"; |
830
|
0
|
|
|
|
|
0
|
my $update_sql_b = "UPDATE content SET text=? ". |
831
|
|
|
|
|
|
|
"WHERE node_id=? AND version=?"; |
832
|
|
|
|
|
|
|
|
833
|
0
|
|
|
|
|
0
|
my $u_sth = $dbh->prepare($update_sql_a); |
834
|
0
|
|
|
|
|
0
|
$u_sth->execute($new_content,$page_id); |
835
|
0
|
|
|
|
|
0
|
$u_sth = $dbh->prepare($update_sql_b); |
836
|
0
|
|
|
|
|
0
|
$u_sth->execute($new_content,$page_id,$page_version); |
837
|
|
|
|
|
|
|
} |
838
|
|
|
|
|
|
|
} |
839
|
|
|
|
|
|
|
} |
840
|
|
|
|
|
|
|
|
841
|
|
|
|
|
|
|
# Fix the internal links if we didn't create new versions of the node |
842
|
0
|
0
|
|
|
|
0
|
if(! $create_new_versions) { |
843
|
0
|
|
|
|
|
0
|
$sql = "UPDATE internal_links SET link_to=? WHERE link_to=?"; |
844
|
0
|
|
|
|
|
0
|
$sth = $dbh->prepare($sql); |
845
|
0
|
|
|
|
|
0
|
$sth->execute($new_name,$old_name); |
846
|
|
|
|
|
|
|
} |
847
|
|
|
|
|
|
|
} else { |
848
|
0
|
|
|
|
|
0
|
warn("Internal links not updated following node rename - unsupported by formatter"); |
849
|
|
|
|
|
|
|
} |
850
|
|
|
|
|
|
|
|
851
|
|
|
|
|
|
|
# Call post_rename on any plugins, in case they want to do anything |
852
|
0
|
0
|
|
|
|
0
|
my @postplugins = @{ $args{plugins} || [ ] }; |
|
0
|
|
|
|
|
0
|
|
853
|
0
|
|
|
|
|
0
|
foreach my $plugin (@postplugins) { |
854
|
0
|
0
|
|
|
|
0
|
if ( $plugin->can( "post_rename" ) ) { |
855
|
0
|
|
|
|
|
0
|
$plugin->post_rename( |
856
|
|
|
|
|
|
|
old_name => $old_name, |
857
|
|
|
|
|
|
|
new_name => $new_name, |
858
|
|
|
|
|
|
|
node_id => $node_id, |
859
|
|
|
|
|
|
|
); |
860
|
|
|
|
|
|
|
} |
861
|
|
|
|
|
|
|
} |
862
|
|
|
|
|
|
|
} |
863
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
=item B |
865
|
|
|
|
|
|
|
|
866
|
|
|
|
|
|
|
$store->moderate_node( |
867
|
|
|
|
|
|
|
name => $node, |
868
|
|
|
|
|
|
|
version => $version |
869
|
|
|
|
|
|
|
); |
870
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
Marks the given version of the node as moderated. If this is the |
872
|
|
|
|
|
|
|
highest moderated version, then update the node's contents to hold |
873
|
|
|
|
|
|
|
this version. |
874
|
|
|
|
|
|
|
|
875
|
|
|
|
|
|
|
=cut |
876
|
|
|
|
|
|
|
|
877
|
|
|
|
|
|
|
sub moderate_node { |
878
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
879
|
0
|
0
|
|
|
|
0
|
my %args = scalar @_ == 2 ? ( name => $_[0], version => $_[1] ) : @_; |
880
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
881
|
|
|
|
|
|
|
|
882
|
0
|
|
|
|
|
0
|
my ($name,$version) = ($args{name},$args{version}); |
883
|
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
# Call pre_moderate on any plugins. |
885
|
0
|
0
|
|
|
|
0
|
my @plugins = @{ $args{plugins} || [ ] }; |
|
0
|
|
|
|
|
0
|
|
886
|
0
|
|
|
|
|
0
|
my $moderation_allowed = 1; |
887
|
0
|
|
|
|
|
0
|
foreach my $plugin (@plugins) { |
888
|
0
|
0
|
|
|
|
0
|
if ( $plugin->can( "pre_moderate" ) ) { |
889
|
0
|
|
|
|
|
0
|
handle_pre_plugin_ret( |
890
|
|
|
|
|
|
|
\$moderation_allowed, |
891
|
|
|
|
|
|
|
$plugin->pre_moderate( |
892
|
|
|
|
|
|
|
node => \$name, |
893
|
|
|
|
|
|
|
version => \$version ) |
894
|
|
|
|
|
|
|
); |
895
|
|
|
|
|
|
|
} |
896
|
|
|
|
|
|
|
} |
897
|
0
|
0
|
|
|
|
0
|
if($moderation_allowed < 1) { |
898
|
|
|
|
|
|
|
# The plugins didn't want to allow this action |
899
|
0
|
|
|
|
|
0
|
return -1; |
900
|
|
|
|
|
|
|
} |
901
|
|
|
|
|
|
|
|
902
|
|
|
|
|
|
|
# Get the ID of this node |
903
|
0
|
|
|
|
|
0
|
my $id_sql = "SELECT id FROM node WHERE name=?"; |
904
|
0
|
|
|
|
|
0
|
my $id_sth = $dbh->prepare($id_sql); |
905
|
0
|
|
|
|
|
0
|
$id_sth->execute($name); |
906
|
0
|
|
|
|
|
0
|
my ($node_id) = $id_sth->fetchrow_array; |
907
|
0
|
|
|
|
|
0
|
$id_sth->finish; |
908
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
# Check what the current highest moderated version is |
910
|
0
|
|
|
|
|
0
|
my $hv_sql = |
911
|
|
|
|
|
|
|
"SELECT max(version) " |
912
|
|
|
|
|
|
|
."FROM content " |
913
|
|
|
|
|
|
|
."WHERE node_id = ? " |
914
|
|
|
|
|
|
|
."AND moderated = ?"; |
915
|
0
|
|
|
|
|
0
|
my $hv_sth = $dbh->prepare($hv_sql); |
916
|
0
|
0
|
|
|
|
0
|
$hv_sth->execute($node_id, "1") or croak $dbh->errstr; |
917
|
0
|
|
|
|
|
0
|
my ($highest_mod_version) = $hv_sth->fetchrow_array; |
918
|
0
|
|
|
|
|
0
|
$hv_sth->finish; |
919
|
0
|
0
|
|
|
|
0
|
unless($highest_mod_version) { $highest_mod_version = 0; } |
|
0
|
|
|
|
|
0
|
|
920
|
|
|
|
|
|
|
|
921
|
|
|
|
|
|
|
# Mark this version as moderated |
922
|
0
|
|
|
|
|
0
|
my $update_sql = |
923
|
|
|
|
|
|
|
"UPDATE content " |
924
|
|
|
|
|
|
|
."SET moderated = ? " |
925
|
|
|
|
|
|
|
."WHERE node_id = ? " |
926
|
|
|
|
|
|
|
."AND version = ?"; |
927
|
0
|
|
|
|
|
0
|
my $update_sth = $dbh->prepare($update_sql); |
928
|
0
|
0
|
|
|
|
0
|
$update_sth->execute("1", $node_id, $version) or croak $dbh->errstr; |
929
|
|
|
|
|
|
|
|
930
|
|
|
|
|
|
|
# Are we now the highest moderated version? |
931
|
0
|
0
|
|
|
|
0
|
if(int($version) > int($highest_mod_version)) { |
932
|
|
|
|
|
|
|
# Newly moderated version is newer than previous moderated version |
933
|
|
|
|
|
|
|
# So, make the current version the latest version |
934
|
0
|
|
|
|
|
0
|
my %new_data = $self->retrieve_node( name => $name, version => $version ); |
935
|
|
|
|
|
|
|
|
936
|
|
|
|
|
|
|
# Make sure last modified is properly null, if not set |
937
|
0
|
0
|
|
|
|
0
|
unless($new_data{last_modified}) { $new_data{last_modified} = undef; } |
|
0
|
|
|
|
|
0
|
|
938
|
|
|
|
|
|
|
|
939
|
0
|
|
|
|
|
0
|
my $newv_sql = |
940
|
|
|
|
|
|
|
"UPDATE node " |
941
|
|
|
|
|
|
|
."SET version=?, text=?, modified=? " |
942
|
|
|
|
|
|
|
."WHERE id = ?"; |
943
|
0
|
|
|
|
|
0
|
my $newv_sth = $dbh->prepare($newv_sql); |
944
|
|
|
|
|
|
|
$newv_sth->execute( |
945
|
|
|
|
|
|
|
$version, $self->charset_encode($new_data{content}), |
946
|
0
|
0
|
|
|
|
0
|
$new_data{last_modified}, $node_id |
947
|
|
|
|
|
|
|
) or croak $dbh->errstr; |
948
|
|
|
|
|
|
|
} else { |
949
|
|
|
|
|
|
|
# A higher version is already moderated, so don't change node |
950
|
|
|
|
|
|
|
} |
951
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
# TODO: Do something about internal links, if required |
953
|
|
|
|
|
|
|
|
954
|
|
|
|
|
|
|
# Finally call post_moderate on any plugins. |
955
|
0
|
0
|
|
|
|
0
|
@plugins = @{ $args{plugins} || [ ] }; |
|
0
|
|
|
|
|
0
|
|
956
|
0
|
|
|
|
|
0
|
foreach my $plugin (@plugins) { |
957
|
0
|
0
|
|
|
|
0
|
if ( $plugin->can( "post_moderate" ) ) { |
958
|
0
|
|
|
|
|
0
|
$plugin->post_moderate( |
959
|
|
|
|
|
|
|
node => $name, |
960
|
|
|
|
|
|
|
node_id => $node_id, |
961
|
|
|
|
|
|
|
version => $version ); |
962
|
|
|
|
|
|
|
} |
963
|
|
|
|
|
|
|
} |
964
|
|
|
|
|
|
|
|
965
|
0
|
|
|
|
|
0
|
return 1; |
966
|
|
|
|
|
|
|
} |
967
|
|
|
|
|
|
|
|
968
|
|
|
|
|
|
|
=item B |
969
|
|
|
|
|
|
|
|
970
|
|
|
|
|
|
|
$store->set_node_moderation( |
971
|
|
|
|
|
|
|
name => $node, |
972
|
|
|
|
|
|
|
required => $required |
973
|
|
|
|
|
|
|
); |
974
|
|
|
|
|
|
|
|
975
|
|
|
|
|
|
|
Sets if new node versions will require moderation or not |
976
|
|
|
|
|
|
|
|
977
|
|
|
|
|
|
|
=cut |
978
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
sub set_node_moderation { |
980
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
981
|
0
|
0
|
|
|
|
0
|
my %args = scalar @_ == 2 ? ( name => $_[0], required => $_[1] ) : @_; |
982
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
983
|
|
|
|
|
|
|
|
984
|
0
|
|
|
|
|
0
|
my ($name,$required) = ($args{name},$args{required}); |
985
|
|
|
|
|
|
|
|
986
|
|
|
|
|
|
|
# Get the ID of this node |
987
|
0
|
|
|
|
|
0
|
my $id_sql = "SELECT id FROM node WHERE name=?"; |
988
|
0
|
|
|
|
|
0
|
my $id_sth = $dbh->prepare($id_sql); |
989
|
0
|
|
|
|
|
0
|
$id_sth->execute($name); |
990
|
0
|
|
|
|
|
0
|
my ($node_id) = $id_sth->fetchrow_array; |
991
|
0
|
|
|
|
|
0
|
$id_sth->finish; |
992
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
# Check we really got an ID |
994
|
0
|
0
|
|
|
|
0
|
unless($node_id) { |
995
|
0
|
|
|
|
|
0
|
return 0; |
996
|
|
|
|
|
|
|
} |
997
|
|
|
|
|
|
|
|
998
|
|
|
|
|
|
|
# Mark it as requiring / not requiring moderation |
999
|
0
|
|
|
|
|
0
|
my $mod_sql = |
1000
|
|
|
|
|
|
|
"UPDATE node " |
1001
|
|
|
|
|
|
|
."SET moderate = ? " |
1002
|
|
|
|
|
|
|
."WHERE id = ? "; |
1003
|
0
|
|
|
|
|
0
|
my $mod_sth = $dbh->prepare($mod_sql); |
1004
|
0
|
0
|
|
|
|
0
|
$mod_sth->execute("$required", $node_id) or croak $dbh->errstr; |
1005
|
|
|
|
|
|
|
|
1006
|
0
|
|
|
|
|
0
|
return 1; |
1007
|
|
|
|
|
|
|
} |
1008
|
|
|
|
|
|
|
|
1009
|
|
|
|
|
|
|
=item B |
1010
|
|
|
|
|
|
|
|
1011
|
|
|
|
|
|
|
$store->delete_node( |
1012
|
|
|
|
|
|
|
name => $node, |
1013
|
|
|
|
|
|
|
version => $version, |
1014
|
|
|
|
|
|
|
wiki => $wiki |
1015
|
|
|
|
|
|
|
); |
1016
|
|
|
|
|
|
|
|
1017
|
|
|
|
|
|
|
C is optional. If it is supplied then only that version of |
1018
|
|
|
|
|
|
|
the node will be deleted. Otherwise the node and all its history will |
1019
|
|
|
|
|
|
|
be completely deleted. |
1020
|
|
|
|
|
|
|
|
1021
|
|
|
|
|
|
|
C is also optional, but if you care about updating the backlinks |
1022
|
|
|
|
|
|
|
you want to include it. |
1023
|
|
|
|
|
|
|
|
1024
|
|
|
|
|
|
|
Again, doesn't do any locking. You probably don't want to let anyone |
1025
|
|
|
|
|
|
|
except Wiki admins call this. You may not want to use it at all. |
1026
|
|
|
|
|
|
|
|
1027
|
|
|
|
|
|
|
Croaks on error, silently does nothing if the node or version doesn't |
1028
|
|
|
|
|
|
|
exist, returns true if no error. |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
=cut |
1031
|
|
|
|
|
|
|
|
1032
|
|
|
|
|
|
|
sub delete_node { |
1033
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1034
|
|
|
|
|
|
|
# Backwards compatibility. |
1035
|
0
|
0
|
|
|
|
0
|
my %args = ( scalar @_ == 1 ) ? ( name => $_[0] ) : @_; |
1036
|
|
|
|
|
|
|
|
1037
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
1038
|
0
|
|
|
|
|
0
|
my ($name, $version, $wiki) = @args{ qw( name version wiki ) }; |
1039
|
|
|
|
|
|
|
|
1040
|
|
|
|
|
|
|
# Grab the ID of this node |
1041
|
|
|
|
|
|
|
# (It will only ever have one entry in node, but might have entries |
1042
|
|
|
|
|
|
|
# for other versions in metadata and content) |
1043
|
0
|
|
|
|
|
0
|
my $id_sql = "SELECT id FROM node WHERE name=?"; |
1044
|
0
|
|
|
|
|
0
|
my $id_sth = $dbh->prepare($id_sql); |
1045
|
0
|
|
|
|
|
0
|
$id_sth->execute($name); |
1046
|
0
|
|
|
|
|
0
|
my ($node_id) = $id_sth->fetchrow_array; |
1047
|
0
|
|
|
|
|
0
|
$id_sth->finish; |
1048
|
|
|
|
|
|
|
|
1049
|
|
|
|
|
|
|
# Trivial case - delete the whole node and all its history. |
1050
|
0
|
0
|
|
|
|
0
|
unless ( $version ) { |
1051
|
0
|
|
|
|
|
0
|
my $sql; |
1052
|
|
|
|
|
|
|
# Should start a transaction here. FIXME. |
1053
|
|
|
|
|
|
|
# Do deletes |
1054
|
0
|
|
|
|
|
0
|
$sql = "DELETE FROM content WHERE node_id = $node_id"; |
1055
|
0
|
0
|
|
|
|
0
|
$dbh->do($sql) or croak "Deletion failed: " . DBI->errstr; |
1056
|
0
|
|
|
|
|
0
|
$sql = "DELETE FROM internal_links WHERE link_from=".$dbh->quote($name); |
1057
|
0
|
0
|
|
|
|
0
|
$dbh->do($sql) or croak $dbh->errstr; |
1058
|
0
|
|
|
|
|
0
|
$sql = "DELETE FROM metadata WHERE node_id = $node_id"; |
1059
|
0
|
0
|
|
|
|
0
|
$dbh->do($sql) or croak $dbh->errstr; |
1060
|
0
|
|
|
|
|
0
|
$sql = "DELETE FROM node WHERE id = $node_id"; |
1061
|
0
|
0
|
|
|
|
0
|
$dbh->do($sql) or croak "Deletion failed: " . DBI->errstr; |
1062
|
|
|
|
|
|
|
|
1063
|
|
|
|
|
|
|
# And finish it here. |
1064
|
0
|
|
|
|
|
0
|
post_delete_node($name,$node_id,$version,$args{plugins}); |
1065
|
0
|
|
|
|
|
0
|
return 1; |
1066
|
|
|
|
|
|
|
} |
1067
|
|
|
|
|
|
|
|
1068
|
|
|
|
|
|
|
# Skip out early if we're trying to delete a nonexistent version. |
1069
|
0
|
|
|
|
|
0
|
my %verdata = $self->retrieve_node( name => $name, version => $version ); |
1070
|
0
|
0
|
|
|
|
0
|
unless($verdata{version}) { |
1071
|
0
|
|
|
|
|
0
|
warn( "Asked to delete nonexistent version $version of node " |
1072
|
|
|
|
|
|
|
. "$node_id ($name)" ); |
1073
|
0
|
|
|
|
|
0
|
return 1; |
1074
|
|
|
|
|
|
|
} |
1075
|
|
|
|
|
|
|
|
1076
|
|
|
|
|
|
|
# Reduce to trivial case if deleting the only version. |
1077
|
0
|
|
|
|
|
0
|
my $sql = "SELECT COUNT(*) FROM content WHERE node_id = $node_id"; |
1078
|
0
|
|
|
|
|
0
|
my $sth = $dbh->prepare( $sql ); |
1079
|
0
|
0
|
|
|
|
0
|
$sth->execute() or croak "Deletion failed: " . $dbh->errstr; |
1080
|
0
|
|
|
|
|
0
|
my ($count) = $sth->fetchrow_array; |
1081
|
0
|
|
|
|
|
0
|
$sth->finish; |
1082
|
0
|
0
|
|
|
|
0
|
if($count == 1) { |
1083
|
|
|
|
|
|
|
# Only one version, so can do the non version delete |
1084
|
0
|
|
|
|
|
0
|
return $self->delete_node( name=>$name, plugins=>$args{plugins} ); |
1085
|
|
|
|
|
|
|
} |
1086
|
|
|
|
|
|
|
|
1087
|
|
|
|
|
|
|
# Check whether we're deleting the latest (moderated) version. |
1088
|
0
|
|
|
|
|
0
|
my %currdata = $self->retrieve_node( name => $name ); |
1089
|
0
|
0
|
|
|
|
0
|
if ( $currdata{version} == $version ) { |
1090
|
|
|
|
|
|
|
# Deleting latest version, so need to update the copy in node |
1091
|
|
|
|
|
|
|
# (Can't just grab version ($version - 1) since it may have been |
1092
|
|
|
|
|
|
|
# deleted itself, or might not be moderated.) |
1093
|
0
|
|
|
|
|
0
|
my $try = $version - 1; |
1094
|
0
|
|
|
|
|
0
|
my %prevdata; |
1095
|
0
|
|
0
|
|
|
0
|
until ( $prevdata{version} && $prevdata{moderated} ) { |
1096
|
0
|
|
|
|
|
0
|
%prevdata = $self->retrieve_node( |
1097
|
|
|
|
|
|
|
name => $name, |
1098
|
|
|
|
|
|
|
version => $try, |
1099
|
|
|
|
|
|
|
); |
1100
|
0
|
|
|
|
|
0
|
$try--; |
1101
|
|
|
|
|
|
|
} |
1102
|
|
|
|
|
|
|
|
1103
|
|
|
|
|
|
|
# Move to new (old) version |
1104
|
0
|
|
|
|
|
0
|
my $sql="UPDATE node |
1105
|
|
|
|
|
|
|
SET version=?, text=?, modified=? |
1106
|
|
|
|
|
|
|
WHERE name=?"; |
1107
|
0
|
|
|
|
|
0
|
my $sth = $dbh->prepare( $sql ); |
1108
|
0
|
0
|
|
|
|
0
|
$sth->execute( @prevdata{ qw( version content last_modified ) }, $name) |
1109
|
|
|
|
|
|
|
or croak "Deletion failed: " . $dbh->errstr; |
1110
|
|
|
|
|
|
|
|
1111
|
|
|
|
|
|
|
# Remove the current version from content |
1112
|
0
|
|
|
|
|
0
|
$sql = "DELETE FROM content |
1113
|
|
|
|
|
|
|
WHERE node_id = $node_id |
1114
|
|
|
|
|
|
|
AND version = $version"; |
1115
|
0
|
|
|
|
|
0
|
$sth = $dbh->prepare( $sql ); |
1116
|
0
|
0
|
|
|
|
0
|
$sth->execute() |
1117
|
|
|
|
|
|
|
or croak "Deletion failed: " . $dbh->errstr; |
1118
|
|
|
|
|
|
|
|
1119
|
|
|
|
|
|
|
# Update the internal links to reflect the new version |
1120
|
0
|
|
|
|
|
0
|
$sql = "DELETE FROM internal_links WHERE link_from=?"; |
1121
|
0
|
|
|
|
|
0
|
$sth = $dbh->prepare( $sql ); |
1122
|
0
|
0
|
|
|
|
0
|
$sth->execute( $name ) |
1123
|
|
|
|
|
|
|
or croak "Deletion failed: " . $dbh->errstr; |
1124
|
0
|
|
|
|
|
0
|
my @links_to; |
1125
|
0
|
|
|
|
|
0
|
my $formatter = $wiki->formatter; |
1126
|
0
|
0
|
|
|
|
0
|
if ( $formatter->can( "find_internal_links" ) ) { |
1127
|
|
|
|
|
|
|
# Supply $metadata to formatter in case it's needed to alter the |
1128
|
|
|
|
|
|
|
# behaviour of the formatter, eg for Wiki::Toolkit::Formatter::Multiple |
1129
|
|
|
|
|
|
|
my @all = $formatter->find_internal_links( |
1130
|
0
|
|
|
|
|
0
|
$prevdata{content}, $prevdata{metadata} ); |
1131
|
0
|
|
|
|
|
0
|
my %unique = map { $_ => 1 } @all; |
|
0
|
|
|
|
|
0
|
|
1132
|
0
|
|
|
|
|
0
|
@links_to = keys %unique; |
1133
|
|
|
|
|
|
|
} |
1134
|
0
|
|
|
|
|
0
|
$sql = "INSERT INTO internal_links (link_from, link_to) VALUES (?,?)"; |
1135
|
0
|
|
|
|
|
0
|
$sth = $dbh->prepare( $sql ); |
1136
|
0
|
|
|
|
|
0
|
foreach my $link ( @links_to ) { |
1137
|
0
|
|
|
|
|
0
|
eval { $sth->execute( $name, $link ); }; |
|
0
|
|
|
|
|
0
|
|
1138
|
0
|
0
|
|
|
|
0
|
carp "Couldn't index backlink: " . $dbh->errstr if $@; |
1139
|
|
|
|
|
|
|
} |
1140
|
|
|
|
|
|
|
|
1141
|
|
|
|
|
|
|
# Delete the metadata for the old version |
1142
|
0
|
|
|
|
|
0
|
$sql = "DELETE FROM metadata |
1143
|
|
|
|
|
|
|
WHERE node_id = $node_id |
1144
|
|
|
|
|
|
|
AND version = $version"; |
1145
|
0
|
|
|
|
|
0
|
$sth = $dbh->prepare( $sql ); |
1146
|
0
|
0
|
|
|
|
0
|
$sth->execute() |
1147
|
|
|
|
|
|
|
or croak "Deletion failed: " . $dbh->errstr; |
1148
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
# All done |
1150
|
0
|
|
|
|
|
0
|
post_delete_node($name,$node_id,$version,$args{plugins}); |
1151
|
0
|
|
|
|
|
0
|
return 1; |
1152
|
|
|
|
|
|
|
} |
1153
|
|
|
|
|
|
|
|
1154
|
|
|
|
|
|
|
# If we're still here, then we're deleting neither the latest |
1155
|
|
|
|
|
|
|
# nor the only version. |
1156
|
0
|
|
|
|
|
0
|
$sql = "DELETE FROM content |
1157
|
|
|
|
|
|
|
WHERE node_id = $node_id |
1158
|
|
|
|
|
|
|
AND version=?"; |
1159
|
0
|
|
|
|
|
0
|
$sth = $dbh->prepare( $sql ); |
1160
|
0
|
0
|
|
|
|
0
|
$sth->execute( $version ) |
1161
|
|
|
|
|
|
|
or croak "Deletion failed: " . $dbh->errstr; |
1162
|
0
|
|
|
|
|
0
|
$sql = "DELETE FROM metadata |
1163
|
|
|
|
|
|
|
WHERE node_id = $node_id |
1164
|
|
|
|
|
|
|
AND version=?"; |
1165
|
0
|
|
|
|
|
0
|
$sth = $dbh->prepare( $sql ); |
1166
|
0
|
0
|
|
|
|
0
|
$sth->execute( $version ) |
1167
|
|
|
|
|
|
|
or croak "Deletion failed: " . $dbh->errstr; |
1168
|
|
|
|
|
|
|
|
1169
|
|
|
|
|
|
|
# All done |
1170
|
0
|
|
|
|
|
0
|
post_delete_node($name,$node_id,$version,$args{plugins}); |
1171
|
0
|
|
|
|
|
0
|
return 1; |
1172
|
|
|
|
|
|
|
} |
1173
|
|
|
|
|
|
|
|
1174
|
|
|
|
|
|
|
# Returns the name of the node with the given ID |
1175
|
|
|
|
|
|
|
# Not normally used except when doing low-level maintenance |
1176
|
|
|
|
|
|
|
sub node_name_for_id { |
1177
|
0
|
|
|
0
|
0
|
0
|
my ($self, $node_id) = @_; |
1178
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
1179
|
|
|
|
|
|
|
|
1180
|
0
|
|
|
|
|
0
|
my $name_sql = "SELECT name FROM node WHERE id=?"; |
1181
|
0
|
|
|
|
|
0
|
my $name_sth = $dbh->prepare($name_sql); |
1182
|
0
|
|
|
|
|
0
|
$name_sth->execute($node_id); |
1183
|
0
|
|
|
|
|
0
|
my ($name) = $name_sth->fetchrow_array; |
1184
|
0
|
|
|
|
|
0
|
$name_sth->finish; |
1185
|
|
|
|
|
|
|
|
1186
|
0
|
|
|
|
|
0
|
return $name; |
1187
|
|
|
|
|
|
|
} |
1188
|
|
|
|
|
|
|
|
1189
|
|
|
|
|
|
|
# Internal Method |
1190
|
|
|
|
|
|
|
sub post_delete_node { |
1191
|
0
|
|
|
0
|
0
|
0
|
my ($name,$node_id,$version,$plugins) = @_; |
1192
|
|
|
|
|
|
|
|
1193
|
|
|
|
|
|
|
# Call post_delete on any plugins, having done the delete |
1194
|
0
|
0
|
|
|
|
0
|
my @plugins = @{ $plugins || [ ] }; |
|
0
|
|
|
|
|
0
|
|
1195
|
0
|
|
|
|
|
0
|
foreach my $plugin (@plugins) { |
1196
|
0
|
0
|
|
|
|
0
|
if ( $plugin->can( "post_delete" ) ) { |
1197
|
0
|
|
|
|
|
0
|
$plugin->post_delete( |
1198
|
|
|
|
|
|
|
node => $name, |
1199
|
|
|
|
|
|
|
node_id => $node_id, |
1200
|
|
|
|
|
|
|
version => $version ); |
1201
|
|
|
|
|
|
|
} |
1202
|
|
|
|
|
|
|
} |
1203
|
|
|
|
|
|
|
} |
1204
|
|
|
|
|
|
|
|
1205
|
|
|
|
|
|
|
=item B |
1206
|
|
|
|
|
|
|
|
1207
|
|
|
|
|
|
|
# Nodes changed in last 7 days - each node listed only once. |
1208
|
|
|
|
|
|
|
my @nodes = $store->list_recent_changes( days => 7 ); |
1209
|
|
|
|
|
|
|
|
1210
|
|
|
|
|
|
|
# Nodes added in the last 7 days. |
1211
|
|
|
|
|
|
|
my @nodes = $store->list_recent_changes( |
1212
|
|
|
|
|
|
|
days => 7, |
1213
|
|
|
|
|
|
|
new_only => 1, |
1214
|
|
|
|
|
|
|
); |
1215
|
|
|
|
|
|
|
|
1216
|
|
|
|
|
|
|
# All changes in last 7 days - nodes changed more than once will |
1217
|
|
|
|
|
|
|
# be listed more than once. |
1218
|
|
|
|
|
|
|
my @nodes = $store->list_recent_changes( |
1219
|
|
|
|
|
|
|
days => 7, |
1220
|
|
|
|
|
|
|
include_all_changes => 1, |
1221
|
|
|
|
|
|
|
); |
1222
|
|
|
|
|
|
|
|
1223
|
|
|
|
|
|
|
# Nodes changed between 1 and 7 days ago. |
1224
|
|
|
|
|
|
|
my @nodes = $store->list_recent_changes( between_days => [ 1, 7 ] ); |
1225
|
|
|
|
|
|
|
|
1226
|
|
|
|
|
|
|
# Nodes changed since a given time. |
1227
|
|
|
|
|
|
|
my @nodes = $store->list_recent_changes( since => 1036235131 ); |
1228
|
|
|
|
|
|
|
|
1229
|
|
|
|
|
|
|
# Most recent change and its details. |
1230
|
|
|
|
|
|
|
my @nodes = $store->list_recent_changes( last_n_changes => 1 ); |
1231
|
|
|
|
|
|
|
print "Node: $nodes[0]{name}"; |
1232
|
|
|
|
|
|
|
print "Last modified: $nodes[0]{last_modified}"; |
1233
|
|
|
|
|
|
|
print "Comment: $nodes[0]{metadata}{comment}"; |
1234
|
|
|
|
|
|
|
|
1235
|
|
|
|
|
|
|
# Last 5 restaurant nodes edited. |
1236
|
|
|
|
|
|
|
my @nodes = $store->list_recent_changes( |
1237
|
|
|
|
|
|
|
last_n_changes => 5, |
1238
|
|
|
|
|
|
|
metadata_is => { category => "Restaurants" } |
1239
|
|
|
|
|
|
|
); |
1240
|
|
|
|
|
|
|
|
1241
|
|
|
|
|
|
|
# Last 5 nodes edited by Kake. |
1242
|
|
|
|
|
|
|
my @nodes = $store->list_recent_changes( |
1243
|
|
|
|
|
|
|
last_n_changes => 5, |
1244
|
|
|
|
|
|
|
metadata_was => { username => "Kake" } |
1245
|
|
|
|
|
|
|
); |
1246
|
|
|
|
|
|
|
|
1247
|
|
|
|
|
|
|
# All minor edits made by Earle in the last week. |
1248
|
|
|
|
|
|
|
my @nodes = $store->list_recent_changes( |
1249
|
|
|
|
|
|
|
days => 7, |
1250
|
|
|
|
|
|
|
metadata_was => { username => "Earle", |
1251
|
|
|
|
|
|
|
edit_type => "Minor tidying." } |
1252
|
|
|
|
|
|
|
); |
1253
|
|
|
|
|
|
|
|
1254
|
|
|
|
|
|
|
# Last 10 changes that weren't minor edits. |
1255
|
|
|
|
|
|
|
my @nodes = $store->list_recent_changes( |
1256
|
|
|
|
|
|
|
last_n_changes => 10, |
1257
|
|
|
|
|
|
|
metadata_wasnt => { edit_type => "Minor tidying" } |
1258
|
|
|
|
|
|
|
); |
1259
|
|
|
|
|
|
|
|
1260
|
|
|
|
|
|
|
You I supply one of the following constraints: C |
1261
|
|
|
|
|
|
|
(integer), C (epoch), C (integer). |
1262
|
|
|
|
|
|
|
|
1263
|
|
|
|
|
|
|
You I also supply moderation => 1 if you only want to see versions |
1264
|
|
|
|
|
|
|
that are moderated. |
1265
|
|
|
|
|
|
|
|
1266
|
|
|
|
|
|
|
Another optional parameter is C, which if set to 1 will only |
1267
|
|
|
|
|
|
|
return newly added nodes. |
1268
|
|
|
|
|
|
|
|
1269
|
|
|
|
|
|
|
You I also supply I C (and optionally |
1270
|
|
|
|
|
|
|
C), I C (and optionally |
1271
|
|
|
|
|
|
|
C). Each of these should be a ref to a hash with |
1272
|
|
|
|
|
|
|
scalar keys and values. If the hash has more than one entry, then |
1273
|
|
|
|
|
|
|
only changes satisfying I criteria will be returned when using |
1274
|
|
|
|
|
|
|
C or C, but all changes which fail to |
1275
|
|
|
|
|
|
|
satisfy any one of the criteria will be returned when using |
1276
|
|
|
|
|
|
|
C or C. |
1277
|
|
|
|
|
|
|
|
1278
|
|
|
|
|
|
|
C and C look only at the metadata that the |
1279
|
|
|
|
|
|
|
node I has. C and C take into |
1280
|
|
|
|
|
|
|
account the metadata of previous versions of a node. Don't mix C |
1281
|
|
|
|
|
|
|
with C - there's no check for this, but the results are undefined. |
1282
|
|
|
|
|
|
|
|
1283
|
|
|
|
|
|
|
Returns results as an array, in reverse chronological order. Each |
1284
|
|
|
|
|
|
|
element of the array is a reference to a hash with the following entries: |
1285
|
|
|
|
|
|
|
|
1286
|
|
|
|
|
|
|
=over 4 |
1287
|
|
|
|
|
|
|
|
1288
|
|
|
|
|
|
|
=item * B: the name of the node |
1289
|
|
|
|
|
|
|
|
1290
|
|
|
|
|
|
|
=item * B: the version number of the node |
1291
|
|
|
|
|
|
|
|
1292
|
|
|
|
|
|
|
=item * B: timestamp showing when this version was written |
1293
|
|
|
|
|
|
|
|
1294
|
|
|
|
|
|
|
=item * B: a ref to a hash containing any metadata attached |
1295
|
|
|
|
|
|
|
to this version of the node |
1296
|
|
|
|
|
|
|
|
1297
|
|
|
|
|
|
|
=back |
1298
|
|
|
|
|
|
|
|
1299
|
|
|
|
|
|
|
Unless you supply C, C or |
1300
|
|
|
|
|
|
|
C, each node will only be returned once regardless of |
1301
|
|
|
|
|
|
|
how many times it has been changed recently. |
1302
|
|
|
|
|
|
|
|
1303
|
|
|
|
|
|
|
By default, the case-sensitivity of both C and |
1304
|
|
|
|
|
|
|
C depends on your database - if it will return rows |
1305
|
|
|
|
|
|
|
with an attribute value of "Pubs" when you asked for "pubs", or not. |
1306
|
|
|
|
|
|
|
If you supply a true value to the C parameter, then you |
1307
|
|
|
|
|
|
|
can be sure of its being case-insensitive. This is recommended. |
1308
|
|
|
|
|
|
|
|
1309
|
|
|
|
|
|
|
=cut |
1310
|
|
|
|
|
|
|
|
1311
|
|
|
|
|
|
|
sub list_recent_changes { |
1312
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1313
|
0
|
|
|
|
|
0
|
my %args = @_; |
1314
|
0
|
0
|
|
|
|
0
|
if ($args{since}) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1315
|
0
|
|
|
|
|
0
|
return $self->_find_recent_changes_by_criteria( %args ); |
1316
|
|
|
|
|
|
|
} elsif ($args{between_days}) { |
1317
|
0
|
|
|
|
|
0
|
return $self->_find_recent_changes_by_criteria( %args ); |
1318
|
|
|
|
|
|
|
} elsif ( $args{days} ) { |
1319
|
0
|
|
|
|
|
0
|
my $now = localtime; |
1320
|
0
|
|
|
|
|
0
|
my $then = $now - ( ONE_DAY * $args{days} ); |
1321
|
0
|
|
|
|
|
0
|
$args{since} = $then; |
1322
|
0
|
|
|
|
|
0
|
delete $args{days}; |
1323
|
0
|
|
|
|
|
0
|
return $self->_find_recent_changes_by_criteria( %args ); |
1324
|
|
|
|
|
|
|
} elsif ( $args{last_n_changes} ) { |
1325
|
0
|
|
|
|
|
0
|
$args{limit} = delete $args{last_n_changes}; |
1326
|
0
|
|
|
|
|
0
|
return $self->_find_recent_changes_by_criteria( %args ); |
1327
|
|
|
|
|
|
|
} else { |
1328
|
0
|
|
|
|
|
0
|
croak "Need to supply some criteria to list_recent_changes."; |
1329
|
|
|
|
|
|
|
} |
1330
|
|
|
|
|
|
|
} |
1331
|
|
|
|
|
|
|
|
1332
|
|
|
|
|
|
|
sub _find_recent_changes_by_criteria { |
1333
|
0
|
|
|
0
|
|
0
|
my ($self, %args) = @_; |
1334
|
|
|
|
|
|
|
my ($since, $limit, $between_days, $ignore_case, $new_only, |
1335
|
|
|
|
|
|
|
$metadata_is, $metadata_isnt, $metadata_was, $metadata_wasnt, |
1336
|
|
|
|
|
|
|
$moderation, $include_all_changes ) = |
1337
|
0
|
|
|
|
|
0
|
@args{ qw( since limit between_days ignore_case new_only |
1338
|
|
|
|
|
|
|
metadata_is metadata_isnt metadata_was metadata_wasnt |
1339
|
|
|
|
|
|
|
moderation include_all_changes) }; |
1340
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
1341
|
|
|
|
|
|
|
|
1342
|
0
|
|
|
|
|
0
|
my @where; |
1343
|
|
|
|
|
|
|
my @metadata_joins; |
1344
|
0
|
|
|
|
|
0
|
my $use_content_table; # some queries won't need this |
1345
|
|
|
|
|
|
|
|
1346
|
0
|
0
|
|
|
|
0
|
if ( $metadata_is ) { |
1347
|
0
|
|
|
|
|
0
|
my $main_table = "node"; |
1348
|
0
|
0
|
|
|
|
0
|
if ( $include_all_changes ) { |
1349
|
0
|
|
|
|
|
0
|
$main_table = "content"; |
1350
|
0
|
|
|
|
|
0
|
$use_content_table = 1; |
1351
|
|
|
|
|
|
|
} |
1352
|
0
|
|
|
|
|
0
|
my $i = 0; |
1353
|
0
|
|
|
|
|
0
|
foreach my $type ( keys %$metadata_is ) { |
1354
|
0
|
|
|
|
|
0
|
$i++; |
1355
|
0
|
|
|
|
|
0
|
my $value = $metadata_is->{$type}; |
1356
|
0
|
0
|
|
|
|
0
|
croak "metadata_is must have scalar values" if ref $value; |
1357
|
0
|
|
|
|
|
0
|
my $mdt = "md_is_$i"; |
1358
|
0
|
0
|
|
|
|
0
|
push @metadata_joins, "LEFT JOIN metadata AS $mdt |
1359
|
|
|
|
|
|
|
ON $main_table." |
1360
|
|
|
|
|
|
|
. ( ($main_table eq "node") ? "id" |
1361
|
|
|
|
|
|
|
: "node_id" ) |
1362
|
|
|
|
|
|
|
. "=$mdt.node_id |
1363
|
|
|
|
|
|
|
AND $main_table.version=$mdt.version\n"; |
1364
|
|
|
|
|
|
|
# Why is this inside 'if ( $metadata_is )'? |
1365
|
|
|
|
|
|
|
# Shouldn't it apply to all cases? |
1366
|
|
|
|
|
|
|
# What's it doing in @metadata_joins? |
1367
|
0
|
0
|
|
|
|
0
|
if (defined $moderation) { |
1368
|
0
|
|
|
|
|
0
|
push @metadata_joins, "AND $main_table.moderate=$moderation"; |
1369
|
|
|
|
|
|
|
} |
1370
|
0
|
|
|
|
|
0
|
push @where, "( " |
1371
|
|
|
|
|
|
|
. $self->_get_comparison_sql( |
1372
|
|
|
|
|
|
|
thing1 => "$mdt.metadata_type", |
1373
|
|
|
|
|
|
|
thing2 => $dbh->quote($type), |
1374
|
|
|
|
|
|
|
ignore_case => $ignore_case, |
1375
|
|
|
|
|
|
|
) |
1376
|
|
|
|
|
|
|
. " AND " |
1377
|
|
|
|
|
|
|
. $self->_get_comparison_sql( |
1378
|
|
|
|
|
|
|
thing1 => "$mdt.metadata_value", |
1379
|
|
|
|
|
|
|
thing2 => $dbh->quote( $self->charset_encode($value) ), |
1380
|
|
|
|
|
|
|
Ignore_case => $ignore_case, |
1381
|
|
|
|
|
|
|
) |
1382
|
|
|
|
|
|
|
. " )"; |
1383
|
|
|
|
|
|
|
} |
1384
|
|
|
|
|
|
|
} |
1385
|
|
|
|
|
|
|
|
1386
|
0
|
0
|
|
|
|
0
|
if ( $metadata_isnt ) { |
1387
|
0
|
|
|
|
|
0
|
foreach my $type ( keys %$metadata_isnt ) { |
1388
|
0
|
|
|
|
|
0
|
my $value = $metadata_isnt->{$type}; |
1389
|
0
|
0
|
|
|
|
0
|
croak "metadata_isnt must have scalar values" if ref $value; |
1390
|
|
|
|
|
|
|
} |
1391
|
0
|
|
|
|
|
0
|
my @omits = $self->_find_recent_changes_by_criteria( |
1392
|
|
|
|
|
|
|
since => $since, |
1393
|
|
|
|
|
|
|
between_days => $between_days, |
1394
|
|
|
|
|
|
|
metadata_is => $metadata_isnt, |
1395
|
|
|
|
|
|
|
ignore_case => $ignore_case, |
1396
|
|
|
|
|
|
|
); |
1397
|
0
|
|
|
|
|
0
|
foreach my $omit ( @omits ) { |
1398
|
|
|
|
|
|
|
push @where, "( node.name != " . $dbh->quote($omit->{name}) |
1399
|
|
|
|
|
|
|
. " OR node.version != " . $dbh->quote($omit->{version}) |
1400
|
0
|
|
|
|
|
0
|
. ")"; |
1401
|
|
|
|
|
|
|
} |
1402
|
|
|
|
|
|
|
} |
1403
|
|
|
|
|
|
|
|
1404
|
0
|
0
|
|
|
|
0
|
if ( $metadata_was ) { |
1405
|
0
|
|
|
|
|
0
|
$use_content_table = 1; |
1406
|
0
|
|
|
|
|
0
|
my $i = 0; |
1407
|
0
|
|
|
|
|
0
|
foreach my $type ( keys %$metadata_was ) { |
1408
|
0
|
|
|
|
|
0
|
$i++; |
1409
|
0
|
|
|
|
|
0
|
my $value = $metadata_was->{$type}; |
1410
|
0
|
0
|
|
|
|
0
|
croak "metadata_was must have scalar values" if ref $value; |
1411
|
0
|
|
|
|
|
0
|
my $mdt = "md_was_$i"; |
1412
|
0
|
|
|
|
|
0
|
push @metadata_joins, "LEFT JOIN metadata AS $mdt |
1413
|
|
|
|
|
|
|
ON content.node_id=$mdt.node_id |
1414
|
|
|
|
|
|
|
AND content.version=$mdt.version\n"; |
1415
|
0
|
|
|
|
|
0
|
push @where, "( " |
1416
|
|
|
|
|
|
|
. $self->_get_comparison_sql( |
1417
|
|
|
|
|
|
|
thing1 => "$mdt.metadata_type", |
1418
|
|
|
|
|
|
|
thing2 => $dbh->quote($type), |
1419
|
|
|
|
|
|
|
ignore_case => $ignore_case, |
1420
|
|
|
|
|
|
|
) |
1421
|
|
|
|
|
|
|
. " AND " |
1422
|
|
|
|
|
|
|
. $self->_get_comparison_sql( |
1423
|
|
|
|
|
|
|
thing1 => "$mdt.metadata_value", |
1424
|
|
|
|
|
|
|
thing2 => $dbh->quote( $self->charset_encode($value) ), |
1425
|
|
|
|
|
|
|
ignore_case => $ignore_case, |
1426
|
|
|
|
|
|
|
) |
1427
|
|
|
|
|
|
|
. " )"; |
1428
|
|
|
|
|
|
|
} |
1429
|
|
|
|
|
|
|
} |
1430
|
|
|
|
|
|
|
|
1431
|
0
|
0
|
|
|
|
0
|
if ( $metadata_wasnt ) { |
1432
|
0
|
|
|
|
|
0
|
foreach my $type ( keys %$metadata_wasnt ) { |
1433
|
0
|
|
|
|
|
0
|
my $value = $metadata_was->{$type}; |
1434
|
0
|
0
|
|
|
|
0
|
croak "metadata_was must have scalar values" if ref $value; |
1435
|
|
|
|
|
|
|
} |
1436
|
0
|
|
|
|
|
0
|
my @omits = $self->_find_recent_changes_by_criteria( |
1437
|
|
|
|
|
|
|
since => $since, |
1438
|
|
|
|
|
|
|
between_days => $between_days, |
1439
|
|
|
|
|
|
|
metadata_was => $metadata_wasnt, |
1440
|
|
|
|
|
|
|
ignore_case => $ignore_case, |
1441
|
|
|
|
|
|
|
); |
1442
|
0
|
|
|
|
|
0
|
foreach my $omit ( @omits ) { |
1443
|
|
|
|
|
|
|
push @where, "( node.name != " . $dbh->quote($omit->{name}) |
1444
|
|
|
|
|
|
|
. " OR content.version != " . $dbh->quote($omit->{version}) |
1445
|
0
|
|
|
|
|
0
|
. ")"; |
1446
|
|
|
|
|
|
|
} |
1447
|
0
|
|
|
|
|
0
|
$use_content_table = 1; |
1448
|
|
|
|
|
|
|
} |
1449
|
|
|
|
|
|
|
|
1450
|
|
|
|
|
|
|
# Figure out which table we should be joining to to check the dates and |
1451
|
|
|
|
|
|
|
# versions - node or content. |
1452
|
0
|
|
|
|
|
0
|
my $date_table = "node"; |
1453
|
0
|
0
|
0
|
|
|
0
|
if ( $include_all_changes || $new_only |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
1454
|
|
|
|
|
|
|
|| $metadata_was || $metadata_wasnt ) { |
1455
|
0
|
|
|
|
|
0
|
$date_table = "content"; |
1456
|
0
|
|
|
|
|
0
|
$use_content_table = 1; |
1457
|
|
|
|
|
|
|
} |
1458
|
0
|
0
|
|
|
|
0
|
if ( $new_only ) { |
1459
|
0
|
|
|
|
|
0
|
push @where, "content.version=1"; |
1460
|
|
|
|
|
|
|
} |
1461
|
|
|
|
|
|
|
|
1462
|
0
|
0
|
|
|
|
0
|
if ( $since ) { |
|
|
0
|
|
|
|
|
|
1463
|
0
|
|
|
|
|
0
|
my $timestamp = $self->_get_timestamp( $since ); |
1464
|
0
|
|
|
|
|
0
|
push @where, "$date_table.modified >= " . $dbh->quote($timestamp); |
1465
|
|
|
|
|
|
|
} elsif ( $between_days ) { |
1466
|
0
|
|
|
|
|
0
|
my $now = localtime; |
1467
|
|
|
|
|
|
|
# Start is the larger number of days ago. |
1468
|
0
|
|
|
|
|
0
|
my ($start, $end) = @$between_days; |
1469
|
0
|
0
|
|
|
|
0
|
($start, $end) = ($end, $start) if $start < $end; |
1470
|
0
|
|
|
|
|
0
|
my $ts_start = $self->_get_timestamp( $now - (ONE_DAY * $start) ); |
1471
|
0
|
|
|
|
|
0
|
my $ts_end = $self->_get_timestamp( $now - (ONE_DAY * $end) ); |
1472
|
0
|
|
|
|
|
0
|
push @where, "$date_table.modified >= " . $dbh->quote($ts_start); |
1473
|
0
|
|
|
|
|
0
|
push @where, "$date_table.modified <= " . $dbh->quote($ts_end); |
1474
|
|
|
|
|
|
|
} |
1475
|
|
|
|
|
|
|
|
1476
|
0
|
|
|
|
|
0
|
my $sql = "SELECT DISTINCT |
1477
|
|
|
|
|
|
|
node.name, |
1478
|
|
|
|
|
|
|
"; |
1479
|
0
|
0
|
0
|
|
|
0
|
if ( $include_all_changes || $new_only || $use_content_table ) { |
|
|
|
0
|
|
|
|
|
1480
|
0
|
|
|
|
|
0
|
$sql .= " content.version, content.modified "; |
1481
|
|
|
|
|
|
|
} else { |
1482
|
0
|
|
|
|
|
0
|
$sql .= " node.version, node.modified "; |
1483
|
|
|
|
|
|
|
} |
1484
|
0
|
|
|
|
|
0
|
$sql .= " FROM node "; |
1485
|
0
|
0
|
|
|
|
0
|
if ( $use_content_table ) { |
1486
|
0
|
|
|
|
|
0
|
$sql .= " INNER JOIN content ON (node.id = content.node_id ) "; |
1487
|
|
|
|
|
|
|
} |
1488
|
|
|
|
|
|
|
|
1489
|
0
|
0
|
|
|
|
0
|
$sql .= join("\n", @metadata_joins) |
|
|
0
|
|
|
|
|
|
1490
|
|
|
|
|
|
|
. ( |
1491
|
|
|
|
|
|
|
scalar @where |
1492
|
|
|
|
|
|
|
? " WHERE " . join(" AND ",@where) |
1493
|
|
|
|
|
|
|
: "" |
1494
|
|
|
|
|
|
|
) |
1495
|
|
|
|
|
|
|
. " ORDER BY " |
1496
|
|
|
|
|
|
|
. ( $use_content_table ? "content" : "node" ) |
1497
|
|
|
|
|
|
|
. ".modified DESC"; |
1498
|
0
|
0
|
|
|
|
0
|
if ( $limit ) { |
1499
|
0
|
0
|
|
|
|
0
|
croak "Bad argument $limit" unless $limit =~ /^\d+$/; |
1500
|
0
|
|
|
|
|
0
|
$sql .= " LIMIT $limit"; |
1501
|
|
|
|
|
|
|
} |
1502
|
0
|
|
|
|
|
0
|
my $nodesref = $dbh->selectall_arrayref($sql); |
1503
|
0
|
|
|
|
|
0
|
my @finds = map { { name => $_->[0], |
|
0
|
|
|
|
|
0
|
|
1504
|
|
|
|
|
|
|
version => $_->[1], |
1505
|
|
|
|
|
|
|
last_modified => $_->[2] } |
1506
|
|
|
|
|
|
|
} @$nodesref; |
1507
|
0
|
|
|
|
|
0
|
foreach my $find ( @finds ) { |
1508
|
0
|
|
|
|
|
0
|
my %metadata; |
1509
|
0
|
|
|
|
|
0
|
my $sth = $dbh->prepare( "SELECT metadata_type, metadata_value |
1510
|
|
|
|
|
|
|
FROM node |
1511
|
|
|
|
|
|
|
INNER JOIN metadata |
1512
|
|
|
|
|
|
|
ON (id = node_id) |
1513
|
|
|
|
|
|
|
WHERE name=? |
1514
|
|
|
|
|
|
|
AND metadata.version=?" ); |
1515
|
0
|
|
|
|
|
0
|
$sth->execute( $find->{name}, $find->{version} ); |
1516
|
0
|
|
|
|
|
0
|
while ( my ($type, $value) = $self->charset_decode( $sth->fetchrow_array ) ) { |
1517
|
0
|
0
|
|
|
|
0
|
if ( defined $metadata{$type} ) { |
1518
|
0
|
|
|
|
|
0
|
push @{$metadata{$type}}, $value; |
|
0
|
|
|
|
|
0
|
|
1519
|
|
|
|
|
|
|
} else { |
1520
|
0
|
|
|
|
|
0
|
$metadata{$type} = [ $value ]; |
1521
|
|
|
|
|
|
|
} |
1522
|
|
|
|
|
|
|
} |
1523
|
0
|
|
|
|
|
0
|
$find->{metadata} = \%metadata; |
1524
|
|
|
|
|
|
|
} |
1525
|
0
|
|
|
|
|
0
|
return @finds; |
1526
|
|
|
|
|
|
|
} |
1527
|
|
|
|
|
|
|
|
1528
|
|
|
|
|
|
|
=item B |
1529
|
|
|
|
|
|
|
|
1530
|
|
|
|
|
|
|
my @nodes = $store->list_all_nodes(); |
1531
|
|
|
|
|
|
|
print "First node is $nodes[0]\n"; |
1532
|
|
|
|
|
|
|
|
1533
|
|
|
|
|
|
|
my @nodes = $store->list_all_nodes( with_details=> 1 ); |
1534
|
|
|
|
|
|
|
print "First node is ".$nodes[0]->{'name'}." at version ".$nodes[0]->{'version'}."\n"; |
1535
|
|
|
|
|
|
|
|
1536
|
|
|
|
|
|
|
Returns a list containing the name of every existing node. The list |
1537
|
|
|
|
|
|
|
won't be in any kind of order; do any sorting in your calling script. |
1538
|
|
|
|
|
|
|
|
1539
|
|
|
|
|
|
|
Optionally also returns the id, version and moderation flag. |
1540
|
|
|
|
|
|
|
|
1541
|
|
|
|
|
|
|
=cut |
1542
|
|
|
|
|
|
|
|
1543
|
|
|
|
|
|
|
sub list_all_nodes { |
1544
|
0
|
|
|
0
|
1
|
0
|
my ($self,%args) = @_; |
1545
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
1546
|
0
|
|
|
|
|
0
|
my @nodes; |
1547
|
|
|
|
|
|
|
|
1548
|
0
|
0
|
|
|
|
0
|
if($args{with_details}) { |
1549
|
0
|
|
|
|
|
0
|
my $sql = "SELECT id, name, version, moderate FROM node;"; |
1550
|
0
|
|
|
|
|
0
|
my $sth = $dbh->prepare( $sql ); |
1551
|
0
|
|
|
|
|
0
|
$sth->execute(); |
1552
|
|
|
|
|
|
|
|
1553
|
0
|
|
|
|
|
0
|
while(my @results = $sth->fetchrow_array) { |
1554
|
0
|
|
|
|
|
0
|
my %data; |
1555
|
0
|
|
|
|
|
0
|
@data{ qw( node_id name version moderate ) } = @results; |
1556
|
0
|
|
|
|
|
0
|
push @nodes, \%data; |
1557
|
|
|
|
|
|
|
} |
1558
|
|
|
|
|
|
|
} else { |
1559
|
0
|
|
|
|
|
0
|
my $sql = "SELECT name FROM node;"; |
1560
|
0
|
|
|
|
|
0
|
my $raw_nodes = $dbh->selectall_arrayref($sql); |
1561
|
0
|
|
|
|
|
0
|
@nodes = ( map { $self->charset_decode( $_->[0] ) } (@$raw_nodes) ); |
|
0
|
|
|
|
|
0
|
|
1562
|
|
|
|
|
|
|
} |
1563
|
0
|
|
|
|
|
0
|
return @nodes; |
1564
|
|
|
|
|
|
|
} |
1565
|
|
|
|
|
|
|
|
1566
|
|
|
|
|
|
|
=item B |
1567
|
|
|
|
|
|
|
|
1568
|
|
|
|
|
|
|
my @all_versions = $store->list_node_all_versions( |
1569
|
|
|
|
|
|
|
name => 'HomePage', |
1570
|
|
|
|
|
|
|
with_content => 1, |
1571
|
|
|
|
|
|
|
with_metadata => 0 |
1572
|
|
|
|
|
|
|
); |
1573
|
|
|
|
|
|
|
|
1574
|
|
|
|
|
|
|
Returns all the versions of a node, optionally including the content |
1575
|
|
|
|
|
|
|
and metadata, as an array of hashes (newest versions first). |
1576
|
|
|
|
|
|
|
|
1577
|
|
|
|
|
|
|
=cut |
1578
|
|
|
|
|
|
|
|
1579
|
|
|
|
|
|
|
sub list_node_all_versions { |
1580
|
0
|
|
|
0
|
1
|
0
|
my ($self, %args) = @_; |
1581
|
|
|
|
|
|
|
|
1582
|
|
|
|
|
|
|
my ($node_id,$name,$with_content,$with_metadata) = |
1583
|
0
|
|
|
|
|
0
|
@args{ qw( node_id name with_content with_metadata ) }; |
1584
|
|
|
|
|
|
|
|
1585
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
1586
|
0
|
|
|
|
|
0
|
my $sql; |
1587
|
|
|
|
|
|
|
|
1588
|
|
|
|
|
|
|
# If they only gave us the node name, get the node id |
1589
|
0
|
0
|
|
|
|
0
|
unless ($node_id) { |
1590
|
0
|
|
|
|
|
0
|
$sql = "SELECT id FROM node WHERE name=" . $dbh->quote($name); |
1591
|
0
|
|
|
|
|
0
|
$node_id = $dbh->selectrow_array($sql); |
1592
|
|
|
|
|
|
|
} |
1593
|
|
|
|
|
|
|
|
1594
|
|
|
|
|
|
|
# If they didn't tell us what they wanted / we couldn't find it, |
1595
|
|
|
|
|
|
|
# return an empty array |
1596
|
0
|
0
|
|
|
|
0
|
return () unless($node_id); |
1597
|
|
|
|
|
|
|
|
1598
|
|
|
|
|
|
|
# Build up our SQL |
1599
|
0
|
|
|
|
|
0
|
$sql = "SELECT id, name, content.version, content.modified "; |
1600
|
0
|
0
|
|
|
|
0
|
if ( $with_content ) { |
1601
|
0
|
|
|
|
|
0
|
$sql .= ", content.text "; |
1602
|
|
|
|
|
|
|
} |
1603
|
0
|
0
|
|
|
|
0
|
if ( $with_metadata ) { |
1604
|
0
|
|
|
|
|
0
|
$sql .= ", metadata_type, metadata_value "; |
1605
|
|
|
|
|
|
|
} |
1606
|
0
|
|
|
|
|
0
|
$sql .= " FROM node INNER JOIN content ON (id = content.node_id) "; |
1607
|
0
|
0
|
|
|
|
0
|
if ( $with_metadata ) { |
1608
|
0
|
|
|
|
|
0
|
$sql .= " LEFT OUTER JOIN metadata ON " |
1609
|
|
|
|
|
|
|
. "(id = metadata.node_id AND content.version = metadata.version) "; |
1610
|
|
|
|
|
|
|
} |
1611
|
0
|
|
|
|
|
0
|
$sql .= " WHERE id = ? ORDER BY content.version DESC"; |
1612
|
|
|
|
|
|
|
|
1613
|
|
|
|
|
|
|
# Do the fetch |
1614
|
0
|
|
|
|
|
0
|
my $sth = $dbh->prepare( $sql ); |
1615
|
0
|
|
|
|
|
0
|
$sth->execute( $node_id ); |
1616
|
|
|
|
|
|
|
|
1617
|
|
|
|
|
|
|
# Need to hold onto the last row by hash ref, so we don't trash |
1618
|
|
|
|
|
|
|
# it every time |
1619
|
0
|
|
|
|
|
0
|
my %first_data; |
1620
|
0
|
|
|
|
|
0
|
my $dataref = \%first_data; |
1621
|
|
|
|
|
|
|
|
1622
|
|
|
|
|
|
|
# Haul out the data |
1623
|
0
|
|
|
|
|
0
|
my @versions; |
1624
|
0
|
|
|
|
|
0
|
while ( my @results = $sth->fetchrow_array ) { |
1625
|
0
|
|
|
|
|
0
|
my %data = %$dataref; |
1626
|
|
|
|
|
|
|
|
1627
|
|
|
|
|
|
|
# Is it the same version as last time? |
1628
|
0
|
0
|
0
|
|
|
0
|
if ( %data && $data{'version'} != $results[2] ) { |
1629
|
|
|
|
|
|
|
# New version |
1630
|
0
|
|
|
|
|
0
|
push @versions, $dataref; |
1631
|
0
|
|
|
|
|
0
|
%data = (); |
1632
|
|
|
|
|
|
|
} else { |
1633
|
|
|
|
|
|
|
# Same version as last time, must be more metadata |
1634
|
|
|
|
|
|
|
} |
1635
|
|
|
|
|
|
|
|
1636
|
|
|
|
|
|
|
# Grab the core data (will be the same on multi-row for metadata) |
1637
|
0
|
|
|
|
|
0
|
@data{ qw( node_id name version last_modified ) } = @results; |
1638
|
|
|
|
|
|
|
|
1639
|
0
|
|
|
|
|
0
|
my $i = 4; |
1640
|
0
|
0
|
|
|
|
0
|
if ( $with_content ) { |
1641
|
0
|
|
|
|
|
0
|
$data{'content'} = $results[$i]; |
1642
|
0
|
|
|
|
|
0
|
$i++; |
1643
|
|
|
|
|
|
|
} |
1644
|
0
|
0
|
|
|
|
0
|
if ( $with_metadata ) { |
1645
|
0
|
|
|
|
|
0
|
my ($m_type,$m_value) = @results[$i,($i+1)]; |
1646
|
0
|
0
|
|
|
|
0
|
unless ( $data{'metadata'} ) { $data{'metadata'} = {}; } |
|
0
|
|
|
|
|
0
|
|
1647
|
|
|
|
|
|
|
|
1648
|
0
|
0
|
|
|
|
0
|
if ( $m_type ) { |
1649
|
|
|
|
|
|
|
# If we have existing data, then put it into an array |
1650
|
0
|
0
|
|
|
|
0
|
if ( $data{'metadata'}->{$m_type} ) { |
1651
|
0
|
0
|
|
|
|
0
|
unless ( ref($data{'metadata'}->{$m_type}) eq "ARRAY" ) { |
1652
|
|
|
|
|
|
|
$data{'metadata'}->{$m_type} = |
1653
|
0
|
|
|
|
|
0
|
[ $data{'metadata'}->{$m_type} ]; |
1654
|
|
|
|
|
|
|
} |
1655
|
0
|
|
|
|
|
0
|
push @{$data{'metadata'}->{$m_type}}, $m_value; |
|
0
|
|
|
|
|
0
|
|
1656
|
|
|
|
|
|
|
} else { |
1657
|
|
|
|
|
|
|
# Otherwise, just store it in a normal string |
1658
|
0
|
|
|
|
|
0
|
$data{'metadata'}->{$m_type} = $m_value; |
1659
|
|
|
|
|
|
|
} |
1660
|
|
|
|
|
|
|
} |
1661
|
|
|
|
|
|
|
} |
1662
|
|
|
|
|
|
|
|
1663
|
|
|
|
|
|
|
# Save where we've got to |
1664
|
0
|
|
|
|
|
0
|
$dataref = \%data; |
1665
|
|
|
|
|
|
|
} |
1666
|
|
|
|
|
|
|
|
1667
|
|
|
|
|
|
|
# Handle final row saving |
1668
|
0
|
0
|
|
|
|
0
|
if ( $dataref ) { |
1669
|
0
|
|
|
|
|
0
|
push @versions, $dataref; |
1670
|
|
|
|
|
|
|
} |
1671
|
|
|
|
|
|
|
|
1672
|
|
|
|
|
|
|
# Return |
1673
|
0
|
|
|
|
|
0
|
return @versions; |
1674
|
|
|
|
|
|
|
} |
1675
|
|
|
|
|
|
|
|
1676
|
|
|
|
|
|
|
=item B |
1677
|
|
|
|
|
|
|
|
1678
|
|
|
|
|
|
|
# All documentation nodes. |
1679
|
|
|
|
|
|
|
my @nodes = $store->list_nodes_by_metadata( |
1680
|
|
|
|
|
|
|
metadata_type => "category", |
1681
|
|
|
|
|
|
|
metadata_value => "documentation", |
1682
|
|
|
|
|
|
|
ignore_case => 1, # optional but recommended (see below) |
1683
|
|
|
|
|
|
|
); |
1684
|
|
|
|
|
|
|
|
1685
|
|
|
|
|
|
|
# All pubs in Hammersmith. |
1686
|
|
|
|
|
|
|
my @pubs = $store->list_nodes_by_metadata( |
1687
|
|
|
|
|
|
|
metadata_type => "category", |
1688
|
|
|
|
|
|
|
metadata_value => "Pub", |
1689
|
|
|
|
|
|
|
); |
1690
|
|
|
|
|
|
|
my @hsm = $store->list_nodes_by_metadata( |
1691
|
|
|
|
|
|
|
metadata_type => "category", |
1692
|
|
|
|
|
|
|
metadata_value => "Hammersmith", |
1693
|
|
|
|
|
|
|
); |
1694
|
|
|
|
|
|
|
my @results = my_l33t_method_for_ANDing_arrays( \@pubs, \@hsm ); |
1695
|
|
|
|
|
|
|
|
1696
|
|
|
|
|
|
|
Returns a list containing the name of every node whose caller-supplied |
1697
|
|
|
|
|
|
|
metadata matches the criteria given in the parameters. |
1698
|
|
|
|
|
|
|
|
1699
|
|
|
|
|
|
|
By default, the case-sensitivity of both C and |
1700
|
|
|
|
|
|
|
C depends on your database - if it will return rows |
1701
|
|
|
|
|
|
|
with an attribute value of "Pubs" when you asked for "pubs", or not. |
1702
|
|
|
|
|
|
|
If you supply a true value to the C parameter, then you |
1703
|
|
|
|
|
|
|
can be sure of its being case-insensitive. This is recommended. |
1704
|
|
|
|
|
|
|
|
1705
|
|
|
|
|
|
|
If you don't supply any criteria then you'll get an empty list. |
1706
|
|
|
|
|
|
|
|
1707
|
|
|
|
|
|
|
This is a really really really simple way of finding things; if you |
1708
|
|
|
|
|
|
|
want to be more complicated then you'll need to call the method |
1709
|
|
|
|
|
|
|
multiple times and combine the results yourself, or write a plugin. |
1710
|
|
|
|
|
|
|
|
1711
|
|
|
|
|
|
|
=cut |
1712
|
|
|
|
|
|
|
|
1713
|
|
|
|
|
|
|
sub list_nodes_by_metadata { |
1714
|
0
|
|
|
0
|
1
|
0
|
my ($self, %args) = @_; |
1715
|
0
|
|
|
|
|
0
|
my ( $type, $value ) = @args{ qw( metadata_type metadata_value ) }; |
1716
|
0
|
0
|
|
|
|
0
|
return () unless $type; |
1717
|
|
|
|
|
|
|
|
1718
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
1719
|
0
|
0
|
|
|
|
0
|
if ( $args{ignore_case} ) { |
1720
|
0
|
|
|
|
|
0
|
$type = lc( $type ); |
1721
|
0
|
|
|
|
|
0
|
$value = lc( $value ); |
1722
|
|
|
|
|
|
|
} |
1723
|
|
|
|
|
|
|
my $sql = |
1724
|
0
|
|
|
|
|
0
|
$self->_get_list_by_metadata_sql( ignore_case => $args{ignore_case} ); |
1725
|
0
|
|
|
|
|
0
|
my $sth = $dbh->prepare( $sql ); |
1726
|
0
|
|
|
|
|
0
|
$sth->execute( $type, $self->charset_encode($value) ); |
1727
|
0
|
|
|
|
|
0
|
my @nodes; |
1728
|
0
|
|
|
|
|
0
|
while ( my ($id, $node) = $sth->fetchrow_array ) { |
1729
|
0
|
|
|
|
|
0
|
push @nodes, $node; |
1730
|
|
|
|
|
|
|
} |
1731
|
0
|
|
|
|
|
0
|
return @nodes; |
1732
|
|
|
|
|
|
|
} |
1733
|
|
|
|
|
|
|
|
1734
|
|
|
|
|
|
|
=item B |
1735
|
|
|
|
|
|
|
Returns nodes where either the metadata doesn't exist, or is blank |
1736
|
|
|
|
|
|
|
|
1737
|
|
|
|
|
|
|
Unlike list_nodes_by_metadata(), the metadata value is optional. |
1738
|
|
|
|
|
|
|
|
1739
|
|
|
|
|
|
|
# All nodes missing documentation |
1740
|
|
|
|
|
|
|
my @nodes = $store->list_nodes_by_missing_metadata( |
1741
|
|
|
|
|
|
|
metadata_type => "category", |
1742
|
|
|
|
|
|
|
metadata_value => "documentation", |
1743
|
|
|
|
|
|
|
ignore_case => 1, # optional but recommended (see below) |
1744
|
|
|
|
|
|
|
); |
1745
|
|
|
|
|
|
|
|
1746
|
|
|
|
|
|
|
# All nodes which don't have a latitude defined |
1747
|
|
|
|
|
|
|
my @nodes = $store->list_nodes_by_missing_metadata( |
1748
|
|
|
|
|
|
|
metadata_type => "latitude" |
1749
|
|
|
|
|
|
|
); |
1750
|
|
|
|
|
|
|
|
1751
|
|
|
|
|
|
|
=cut |
1752
|
|
|
|
|
|
|
|
1753
|
|
|
|
|
|
|
sub list_nodes_by_missing_metadata { |
1754
|
0
|
|
|
0
|
1
|
0
|
my ($self, %args) = @_; |
1755
|
0
|
|
|
|
|
0
|
my ( $type, $value ) = @args{ qw( metadata_type metadata_value ) }; |
1756
|
0
|
0
|
|
|
|
0
|
return () unless $type; |
1757
|
|
|
|
|
|
|
|
1758
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
1759
|
0
|
0
|
|
|
|
0
|
if ( $args{ignore_case} ) { |
1760
|
0
|
|
|
|
|
0
|
$type = lc( $type ); |
1761
|
0
|
|
|
|
|
0
|
$value = lc( $value ); |
1762
|
|
|
|
|
|
|
} |
1763
|
|
|
|
|
|
|
|
1764
|
0
|
|
|
|
|
0
|
my @nodes; |
1765
|
|
|
|
|
|
|
|
1766
|
|
|
|
|
|
|
# If the don't want to match by value, then we can do it with |
1767
|
|
|
|
|
|
|
# a LEFT OUTER JOIN, and either NULL or LENGTH() = 0 |
1768
|
0
|
0
|
|
|
|
0
|
if( ! $value ) { |
1769
|
|
|
|
|
|
|
my $sql = $self->_get_list_by_missing_metadata_sql( |
1770
|
|
|
|
|
|
|
ignore_case => $args{ignore_case} |
1771
|
0
|
|
|
|
|
0
|
); |
1772
|
0
|
|
|
|
|
0
|
my $sth = $dbh->prepare( $sql ); |
1773
|
0
|
|
|
|
|
0
|
$sth->execute( $type ); |
1774
|
|
|
|
|
|
|
|
1775
|
0
|
|
|
|
|
0
|
while ( my ($id, $node) = $sth->fetchrow_array ) { |
1776
|
0
|
|
|
|
|
0
|
push @nodes, $node; |
1777
|
|
|
|
|
|
|
} |
1778
|
|
|
|
|
|
|
} else { |
1779
|
|
|
|
|
|
|
# To find those without the value in this case would involve |
1780
|
|
|
|
|
|
|
# some seriously brain hurting SQL. |
1781
|
|
|
|
|
|
|
# So, cheat - find those with, and return everything else |
1782
|
0
|
|
|
|
|
0
|
my @with = $self->list_nodes_by_metadata(%args); |
1783
|
0
|
|
|
|
|
0
|
my %with_hash; |
1784
|
0
|
|
|
|
|
0
|
foreach my $node (@with) { $with_hash{$node} = 1; } |
|
0
|
|
|
|
|
0
|
|
1785
|
|
|
|
|
|
|
|
1786
|
0
|
|
|
|
|
0
|
my @all_nodes = $self->list_all_nodes(); |
1787
|
0
|
|
|
|
|
0
|
foreach my $node (@all_nodes) { |
1788
|
0
|
0
|
|
|
|
0
|
unless($with_hash{$node}) { |
1789
|
0
|
|
|
|
|
0
|
push @nodes, $node; |
1790
|
|
|
|
|
|
|
} |
1791
|
|
|
|
|
|
|
} |
1792
|
|
|
|
|
|
|
} |
1793
|
|
|
|
|
|
|
|
1794
|
0
|
|
|
|
|
0
|
return @nodes; |
1795
|
|
|
|
|
|
|
} |
1796
|
|
|
|
|
|
|
|
1797
|
|
|
|
|
|
|
=item B<_get_list_by_metadata_sql> |
1798
|
|
|
|
|
|
|
|
1799
|
|
|
|
|
|
|
Return the SQL to do a match by metadata. Should expect the metadata type |
1800
|
|
|
|
|
|
|
as the first SQL parameter, and the metadata value as the second. |
1801
|
|
|
|
|
|
|
|
1802
|
|
|
|
|
|
|
If possible, should take account of $args{ignore_case} |
1803
|
|
|
|
|
|
|
|
1804
|
|
|
|
|
|
|
=cut |
1805
|
|
|
|
|
|
|
|
1806
|
|
|
|
|
|
|
sub _get_list_by_metadata_sql { |
1807
|
|
|
|
|
|
|
# SQL 99 version |
1808
|
|
|
|
|
|
|
# Can be over-ridden by database-specific subclasses |
1809
|
0
|
|
|
0
|
|
0
|
my ($self, %args) = @_; |
1810
|
0
|
0
|
|
|
|
0
|
if ( $args{ignore_case} ) { |
1811
|
0
|
|
|
|
|
0
|
return "SELECT node.id, node.name " |
1812
|
|
|
|
|
|
|
. "FROM node " |
1813
|
|
|
|
|
|
|
. "INNER JOIN metadata " |
1814
|
|
|
|
|
|
|
. " ON (node.id = metadata.node_id " |
1815
|
|
|
|
|
|
|
. " AND node.version=metadata.version) " |
1816
|
|
|
|
|
|
|
. "WHERE ". $self->_get_lowercase_compare_sql("metadata.metadata_type") |
1817
|
|
|
|
|
|
|
. " AND ". $self->_get_lowercase_compare_sql("metadata.metadata_value"); |
1818
|
|
|
|
|
|
|
} else { |
1819
|
0
|
|
|
|
|
0
|
return "SELECT node.id, node.name " |
1820
|
|
|
|
|
|
|
. "FROM node " |
1821
|
|
|
|
|
|
|
. "INNER JOIN metadata " |
1822
|
|
|
|
|
|
|
. " ON (node.id = metadata.node_id " |
1823
|
|
|
|
|
|
|
. " AND node.version=metadata.version) " |
1824
|
|
|
|
|
|
|
. "WHERE ". $self->_get_casesensitive_compare_sql("metadata.metadata_type") |
1825
|
|
|
|
|
|
|
. " AND ". $self->_get_casesensitive_compare_sql("metadata.metadata_value"); |
1826
|
|
|
|
|
|
|
} |
1827
|
|
|
|
|
|
|
} |
1828
|
|
|
|
|
|
|
|
1829
|
|
|
|
|
|
|
=item B<_get_list_by_missing_metadata_sql> |
1830
|
|
|
|
|
|
|
|
1831
|
|
|
|
|
|
|
Return the SQL to do a match by missing metadata. Should expect the metadata |
1832
|
|
|
|
|
|
|
type as the first SQL parameter. |
1833
|
|
|
|
|
|
|
|
1834
|
|
|
|
|
|
|
If possible, should take account of $args{ignore_case} |
1835
|
|
|
|
|
|
|
|
1836
|
|
|
|
|
|
|
=cut |
1837
|
|
|
|
|
|
|
|
1838
|
|
|
|
|
|
|
sub _get_list_by_missing_metadata_sql { |
1839
|
|
|
|
|
|
|
# SQL 99 version |
1840
|
|
|
|
|
|
|
# Can be over-ridden by database-specific subclasses |
1841
|
0
|
|
|
0
|
|
0
|
my ($self, %args) = @_; |
1842
|
|
|
|
|
|
|
|
1843
|
0
|
|
|
|
|
0
|
my $sql = ""; |
1844
|
0
|
0
|
|
|
|
0
|
if ( $args{ignore_case} ) { |
1845
|
0
|
|
|
|
|
0
|
$sql = "SELECT node.id, node.name " |
1846
|
|
|
|
|
|
|
. "FROM node " |
1847
|
|
|
|
|
|
|
. "LEFT OUTER JOIN metadata " |
1848
|
|
|
|
|
|
|
. " ON (node.id = metadata.node_id " |
1849
|
|
|
|
|
|
|
. " AND node.version=metadata.version " |
1850
|
|
|
|
|
|
|
. " AND ". $self->_get_lowercase_compare_sql("metadata.metadata_type") |
1851
|
|
|
|
|
|
|
. ")"; |
1852
|
|
|
|
|
|
|
} else { |
1853
|
0
|
|
|
|
|
0
|
$sql = "SELECT node.id, node.name " |
1854
|
|
|
|
|
|
|
. "FROM node " |
1855
|
|
|
|
|
|
|
. "LEFT OUTER JOIN metadata " |
1856
|
|
|
|
|
|
|
. " ON (node.id = metadata.node_id " |
1857
|
|
|
|
|
|
|
. " AND node.version=metadata.version " |
1858
|
|
|
|
|
|
|
. " AND ". $self->_get_casesensitive_compare_sql("metadata.metadata_type") |
1859
|
|
|
|
|
|
|
. ")"; |
1860
|
|
|
|
|
|
|
} |
1861
|
|
|
|
|
|
|
|
1862
|
0
|
|
|
|
|
0
|
$sql .= "WHERE (metadata.metadata_value IS NULL OR LENGTH(metadata.metadata_value) = 0) "; |
1863
|
0
|
|
|
|
|
0
|
return $sql; |
1864
|
|
|
|
|
|
|
} |
1865
|
|
|
|
|
|
|
|
1866
|
|
|
|
|
|
|
sub _get_lowercase_compare_sql { |
1867
|
0
|
|
|
0
|
|
0
|
my ($self, $column) = @_; |
1868
|
|
|
|
|
|
|
# SQL 99 version |
1869
|
|
|
|
|
|
|
# Can be over-ridden by database-specific subclasses |
1870
|
0
|
|
|
|
|
0
|
return "lower($column) = ?"; |
1871
|
|
|
|
|
|
|
} |
1872
|
|
|
|
|
|
|
sub _get_casesensitive_compare_sql { |
1873
|
0
|
|
|
0
|
|
0
|
my ($self, $column) = @_; |
1874
|
|
|
|
|
|
|
# SQL 99 version |
1875
|
|
|
|
|
|
|
# Can be over-ridden by database-specific subclasses |
1876
|
0
|
|
|
|
|
0
|
return "$column = ?"; |
1877
|
|
|
|
|
|
|
} |
1878
|
|
|
|
|
|
|
|
1879
|
|
|
|
|
|
|
sub _get_comparison_sql { |
1880
|
0
|
|
|
0
|
|
0
|
my ($self, %args) = @_; |
1881
|
|
|
|
|
|
|
# SQL 99 version |
1882
|
|
|
|
|
|
|
# Can be over-ridden by database-specific subclasses |
1883
|
0
|
|
|
|
|
0
|
return "$args{thing1} = $args{thing2}"; |
1884
|
|
|
|
|
|
|
} |
1885
|
|
|
|
|
|
|
|
1886
|
|
|
|
|
|
|
sub _get_node_exists_ignore_case_sql { |
1887
|
|
|
|
|
|
|
# SQL 99 version |
1888
|
|
|
|
|
|
|
# Can be over-ridden by database-specific subclasses |
1889
|
0
|
|
|
0
|
|
0
|
return "SELECT name FROM node WHERE name = ? "; |
1890
|
|
|
|
|
|
|
} |
1891
|
|
|
|
|
|
|
|
1892
|
|
|
|
|
|
|
=item B |
1893
|
|
|
|
|
|
|
|
1894
|
|
|
|
|
|
|
my @nodes = $wiki->list_unmoderated_nodes(); |
1895
|
|
|
|
|
|
|
my @nodes = $wiki->list_unmoderated_nodes( |
1896
|
|
|
|
|
|
|
only_where_latest => 1 |
1897
|
|
|
|
|
|
|
); |
1898
|
|
|
|
|
|
|
|
1899
|
|
|
|
|
|
|
$nodes[0]->{'name'} # The name of the node |
1900
|
|
|
|
|
|
|
$nodes[0]->{'node_id'} # The id of the node |
1901
|
|
|
|
|
|
|
$nodes[0]->{'version'} # The version in need of moderation |
1902
|
|
|
|
|
|
|
$nodes[0]->{'moderated_version'} # The newest moderated version |
1903
|
|
|
|
|
|
|
|
1904
|
|
|
|
|
|
|
With only_where_latest set, return the id, name and version of all the |
1905
|
|
|
|
|
|
|
nodes where the most recent version needs moderation. |
1906
|
|
|
|
|
|
|
Otherwise, returns the id, name and version of all node versions that need |
1907
|
|
|
|
|
|
|
to be moderated. |
1908
|
|
|
|
|
|
|
|
1909
|
|
|
|
|
|
|
=cut |
1910
|
|
|
|
|
|
|
|
1911
|
|
|
|
|
|
|
sub list_unmoderated_nodes { |
1912
|
0
|
|
|
0
|
1
|
0
|
my ($self,%args) = @_; |
1913
|
|
|
|
|
|
|
|
1914
|
0
|
|
|
|
|
0
|
my $only_where_lastest = $args{'only_where_latest'}; |
1915
|
|
|
|
|
|
|
|
1916
|
0
|
|
|
|
|
0
|
my $sql = |
1917
|
|
|
|
|
|
|
"SELECT " |
1918
|
|
|
|
|
|
|
." id, name, " |
1919
|
|
|
|
|
|
|
." node.version AS last_moderated_version, " |
1920
|
|
|
|
|
|
|
." content.version AS version " |
1921
|
|
|
|
|
|
|
."FROM content " |
1922
|
|
|
|
|
|
|
."INNER JOIN node " |
1923
|
|
|
|
|
|
|
." ON (id = node_id) " |
1924
|
|
|
|
|
|
|
."WHERE moderated = ? " |
1925
|
|
|
|
|
|
|
; |
1926
|
0
|
0
|
|
|
|
0
|
if($only_where_lastest) { |
1927
|
0
|
|
|
|
|
0
|
$sql .= "AND node.version = content.version "; |
1928
|
|
|
|
|
|
|
} |
1929
|
0
|
|
|
|
|
0
|
$sql .= "ORDER BY name, content.version "; |
1930
|
|
|
|
|
|
|
|
1931
|
|
|
|
|
|
|
# Query |
1932
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
1933
|
0
|
|
|
|
|
0
|
my $sth = $dbh->prepare( $sql ); |
1934
|
0
|
|
|
|
|
0
|
$sth->execute( "0" ); |
1935
|
|
|
|
|
|
|
|
1936
|
0
|
|
|
|
|
0
|
my @nodes; |
1937
|
0
|
|
|
|
|
0
|
while(my @results = $sth->fetchrow_array) { |
1938
|
0
|
|
|
|
|
0
|
my %data; |
1939
|
0
|
|
|
|
|
0
|
@data{ qw( node_id name moderated_version version ) } = @results; |
1940
|
0
|
|
|
|
|
0
|
push @nodes, \%data; |
1941
|
|
|
|
|
|
|
} |
1942
|
|
|
|
|
|
|
|
1943
|
0
|
|
|
|
|
0
|
return @nodes; |
1944
|
|
|
|
|
|
|
} |
1945
|
|
|
|
|
|
|
|
1946
|
|
|
|
|
|
|
=item B |
1947
|
|
|
|
|
|
|
|
1948
|
|
|
|
|
|
|
List the last version of every node before a given date. |
1949
|
|
|
|
|
|
|
If no version existed before that date, will return undef for version. |
1950
|
|
|
|
|
|
|
Returns a hash of id, name, version and date |
1951
|
|
|
|
|
|
|
|
1952
|
|
|
|
|
|
|
my @nv = $wiki->list_last_version_before('2007-01-02 10:34:11') |
1953
|
|
|
|
|
|
|
foreach my $data (@nv) { |
1954
|
|
|
|
|
|
|
|
1955
|
|
|
|
|
|
|
} |
1956
|
|
|
|
|
|
|
|
1957
|
|
|
|
|
|
|
=cut |
1958
|
|
|
|
|
|
|
|
1959
|
|
|
|
|
|
|
sub list_last_version_before { |
1960
|
0
|
|
|
0
|
1
|
0
|
my ($self, $date) = @_; |
1961
|
|
|
|
|
|
|
|
1962
|
0
|
|
|
|
|
0
|
my $sql = |
1963
|
|
|
|
|
|
|
"SELECT " |
1964
|
|
|
|
|
|
|
." id, name, " |
1965
|
|
|
|
|
|
|
."MAX(content.version) AS version, MAX(content.modified) AS modified " |
1966
|
|
|
|
|
|
|
."FROM node " |
1967
|
|
|
|
|
|
|
."LEFT OUTER JOIN content " |
1968
|
|
|
|
|
|
|
." ON (id = node_id " |
1969
|
|
|
|
|
|
|
." AND content.modified <= ?) " |
1970
|
|
|
|
|
|
|
."GROUP BY id, name " |
1971
|
|
|
|
|
|
|
."ORDER BY id " |
1972
|
|
|
|
|
|
|
; |
1973
|
|
|
|
|
|
|
|
1974
|
|
|
|
|
|
|
# Query |
1975
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
1976
|
0
|
|
|
|
|
0
|
my $sth = $dbh->prepare( $sql ); |
1977
|
0
|
|
|
|
|
0
|
$sth->execute( $date ); |
1978
|
|
|
|
|
|
|
|
1979
|
0
|
|
|
|
|
0
|
my @nodes; |
1980
|
0
|
|
|
|
|
0
|
while(my @results = $sth->fetchrow_array) { |
1981
|
0
|
|
|
|
|
0
|
my %data; |
1982
|
0
|
|
|
|
|
0
|
@data{ qw( id name version modified ) } = @results; |
1983
|
0
|
|
|
|
|
0
|
$data{'node_id'} = $data{'id'}; |
1984
|
0
|
0
|
|
|
|
0
|
unless($data{'version'}) { $data{'version'} = undef; } |
|
0
|
|
|
|
|
0
|
|
1985
|
0
|
|
|
|
|
0
|
push @nodes, \%data; |
1986
|
|
|
|
|
|
|
} |
1987
|
|
|
|
|
|
|
|
1988
|
0
|
|
|
|
|
0
|
return @nodes; |
1989
|
|
|
|
|
|
|
} |
1990
|
|
|
|
|
|
|
|
1991
|
|
|
|
|
|
|
|
1992
|
|
|
|
|
|
|
# Internal function only, used when querying latest metadata |
1993
|
|
|
|
|
|
|
sub _current_node_id_versions { |
1994
|
0
|
|
|
0
|
|
0
|
my ($self) = @_; |
1995
|
|
|
|
|
|
|
|
1996
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
1997
|
|
|
|
|
|
|
|
1998
|
0
|
|
|
|
|
0
|
my $nv_sql = |
1999
|
|
|
|
|
|
|
"SELECT node_id, MAX(version) ". |
2000
|
|
|
|
|
|
|
"FROM content ". |
2001
|
|
|
|
|
|
|
"WHERE moderated ". |
2002
|
|
|
|
|
|
|
"GROUP BY node_id "; |
2003
|
0
|
|
|
|
|
0
|
my $sth = $dbh->prepare( $nv_sql ); |
2004
|
0
|
|
|
|
|
0
|
$sth->execute(); |
2005
|
|
|
|
|
|
|
|
2006
|
0
|
|
|
|
|
0
|
my @nv_where; |
2007
|
0
|
|
|
|
|
0
|
while(my @results = $sth->fetchrow_array) { |
2008
|
0
|
|
|
|
|
0
|
my ($node_id, $version) = @results; |
2009
|
0
|
|
|
|
|
0
|
my $where = "(node_id=$node_id AND version=$version)"; |
2010
|
0
|
|
|
|
|
0
|
push @nv_where, $where; |
2011
|
|
|
|
|
|
|
} |
2012
|
0
|
|
|
|
|
0
|
return @nv_where; |
2013
|
|
|
|
|
|
|
} |
2014
|
|
|
|
|
|
|
|
2015
|
|
|
|
|
|
|
=item B |
2016
|
|
|
|
|
|
|
|
2017
|
|
|
|
|
|
|
List all the currently defined values of the given type of metadata. |
2018
|
|
|
|
|
|
|
|
2019
|
|
|
|
|
|
|
Will only return data from the latest moderated version of each node |
2020
|
|
|
|
|
|
|
|
2021
|
|
|
|
|
|
|
# List all of the different metadata values with the type 'category' |
2022
|
|
|
|
|
|
|
my @categories = $wiki->list_metadata_by_type('category'); |
2023
|
|
|
|
|
|
|
|
2024
|
|
|
|
|
|
|
=cut |
2025
|
|
|
|
|
|
|
sub list_metadata_by_type { |
2026
|
0
|
|
|
0
|
1
|
0
|
my ($self, $type) = @_; |
2027
|
|
|
|
|
|
|
|
2028
|
0
|
0
|
|
|
|
0
|
return undef unless $type; |
2029
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
2030
|
|
|
|
|
|
|
|
2031
|
|
|
|
|
|
|
# Ideally we'd do this as one big query |
2032
|
|
|
|
|
|
|
# However, this would need a temporary table on many |
2033
|
|
|
|
|
|
|
# database engines, so we cheat and do it as two |
2034
|
0
|
|
|
|
|
0
|
my @nv_where = $self->_current_node_id_versions(); |
2035
|
|
|
|
|
|
|
|
2036
|
|
|
|
|
|
|
# Now the metadata bit |
2037
|
0
|
|
|
|
|
0
|
my $sql = |
2038
|
|
|
|
|
|
|
"SELECT DISTINCT metadata_value ". |
2039
|
|
|
|
|
|
|
"FROM metadata ". |
2040
|
|
|
|
|
|
|
"WHERE metadata_type = ? ". |
2041
|
|
|
|
|
|
|
"AND (". |
2042
|
|
|
|
|
|
|
join(" OR ", @nv_where). |
2043
|
|
|
|
|
|
|
")"; |
2044
|
0
|
|
|
|
|
0
|
my $sth = $dbh->prepare( $sql ); |
2045
|
0
|
|
|
|
|
0
|
$sth->execute($type); |
2046
|
|
|
|
|
|
|
|
2047
|
0
|
|
|
|
|
0
|
my $values = $sth->fetchall_arrayref([0]); |
2048
|
0
|
|
|
|
|
0
|
return ( map { $self->charset_decode( $_->[0] ) } (@$values) ); |
|
0
|
|
|
|
|
0
|
|
2049
|
|
|
|
|
|
|
} |
2050
|
|
|
|
|
|
|
|
2051
|
|
|
|
|
|
|
|
2052
|
|
|
|
|
|
|
=item B |
2053
|
|
|
|
|
|
|
|
2054
|
|
|
|
|
|
|
List all the currently defined kinds of metadata, eg Locale, Postcode |
2055
|
|
|
|
|
|
|
|
2056
|
|
|
|
|
|
|
Will only return data from the latest moderated version of each node |
2057
|
|
|
|
|
|
|
|
2058
|
|
|
|
|
|
|
# List all of the different kinds of metadata |
2059
|
|
|
|
|
|
|
my @metadata_types = $wiki->list_metadata_names() |
2060
|
|
|
|
|
|
|
|
2061
|
|
|
|
|
|
|
=cut |
2062
|
|
|
|
|
|
|
sub list_metadata_names { |
2063
|
0
|
|
|
0
|
1
|
0
|
my ($self) = @_; |
2064
|
|
|
|
|
|
|
|
2065
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
2066
|
|
|
|
|
|
|
|
2067
|
|
|
|
|
|
|
# Ideally we'd do this as one big query |
2068
|
|
|
|
|
|
|
# However, this would need a temporary table on many |
2069
|
|
|
|
|
|
|
# database engines, so we cheat and do it as two |
2070
|
0
|
|
|
|
|
0
|
my @nv_where = $self->_current_node_id_versions(); |
2071
|
|
|
|
|
|
|
|
2072
|
|
|
|
|
|
|
# Now the metadata bit |
2073
|
0
|
|
|
|
|
0
|
my $sql = |
2074
|
|
|
|
|
|
|
"SELECT DISTINCT metadata_type ". |
2075
|
|
|
|
|
|
|
"FROM metadata ". |
2076
|
|
|
|
|
|
|
"WHERE (". |
2077
|
|
|
|
|
|
|
join(" OR ", @nv_where). |
2078
|
|
|
|
|
|
|
")"; |
2079
|
0
|
|
|
|
|
0
|
my $sth = $dbh->prepare( $sql ); |
2080
|
0
|
|
|
|
|
0
|
$sth->execute(); |
2081
|
|
|
|
|
|
|
|
2082
|
0
|
|
|
|
|
0
|
my $types = $sth->fetchall_arrayref([0]); |
2083
|
0
|
|
|
|
|
0
|
return ( map { $self->charset_decode( $_->[0] ) } (@$types) ); |
|
0
|
|
|
|
|
0
|
|
2084
|
|
|
|
|
|
|
} |
2085
|
|
|
|
|
|
|
|
2086
|
|
|
|
|
|
|
|
2087
|
|
|
|
|
|
|
=item B |
2088
|
|
|
|
|
|
|
|
2089
|
|
|
|
|
|
|
my ($code_version, $db_version) = $store->schema_current; |
2090
|
|
|
|
|
|
|
if ($code_version == $db_version) |
2091
|
|
|
|
|
|
|
# Do stuff |
2092
|
|
|
|
|
|
|
} else { |
2093
|
|
|
|
|
|
|
# Bail |
2094
|
|
|
|
|
|
|
} |
2095
|
|
|
|
|
|
|
|
2096
|
|
|
|
|
|
|
=cut |
2097
|
|
|
|
|
|
|
|
2098
|
|
|
|
|
|
|
sub schema_current { |
2099
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
2100
|
0
|
|
|
|
|
0
|
my $dbh = $self->dbh; |
2101
|
0
|
|
|
|
|
0
|
my $sth; |
2102
|
0
|
|
|
|
|
0
|
eval { $sth = $dbh->prepare("SELECT version FROM schema_info") }; |
|
0
|
|
|
|
|
0
|
|
2103
|
0
|
0
|
|
|
|
0
|
if ($@) { |
2104
|
0
|
|
|
|
|
0
|
return ($SCHEMA_VER, 0); |
2105
|
|
|
|
|
|
|
} |
2106
|
0
|
|
|
|
|
0
|
eval { $sth->execute }; |
|
0
|
|
|
|
|
0
|
|
2107
|
0
|
0
|
|
|
|
0
|
if ($@) { |
2108
|
0
|
|
|
|
|
0
|
return ($SCHEMA_VER, 0); |
2109
|
|
|
|
|
|
|
} |
2110
|
0
|
|
|
|
|
0
|
my $version; |
2111
|
0
|
|
|
|
|
0
|
eval { $version = $sth->fetchrow_array }; |
|
0
|
|
|
|
|
0
|
|
2112
|
0
|
0
|
|
|
|
0
|
if ($@) { |
2113
|
0
|
|
|
|
|
0
|
return ($SCHEMA_VER, 0); |
2114
|
|
|
|
|
|
|
} else { |
2115
|
0
|
|
|
|
|
0
|
return ($SCHEMA_VER, $version); |
2116
|
|
|
|
|
|
|
} |
2117
|
|
|
|
|
|
|
} |
2118
|
|
|
|
|
|
|
|
2119
|
|
|
|
|
|
|
|
2120
|
|
|
|
|
|
|
=item B |
2121
|
|
|
|
|
|
|
|
2122
|
|
|
|
|
|
|
my $dbh = $store->dbh; |
2123
|
|
|
|
|
|
|
|
2124
|
|
|
|
|
|
|
Returns the database handle belonging to this storage backend instance. |
2125
|
|
|
|
|
|
|
|
2126
|
|
|
|
|
|
|
=cut |
2127
|
|
|
|
|
|
|
|
2128
|
|
|
|
|
|
|
sub dbh { |
2129
|
3
|
|
|
3
|
1
|
5
|
my $self = shift; |
2130
|
3
|
|
|
|
|
5
|
return $self->{_dbh}; |
2131
|
|
|
|
|
|
|
} |
2132
|
|
|
|
|
|
|
|
2133
|
|
|
|
|
|
|
=item B |
2134
|
|
|
|
|
|
|
|
2135
|
|
|
|
|
|
|
my $dbname = $store->dbname; |
2136
|
|
|
|
|
|
|
|
2137
|
|
|
|
|
|
|
Returns the name of the database used for backend storage. |
2138
|
|
|
|
|
|
|
|
2139
|
|
|
|
|
|
|
=cut |
2140
|
|
|
|
|
|
|
|
2141
|
|
|
|
|
|
|
sub dbname { |
2142
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
2143
|
0
|
|
|
|
|
0
|
return $self->{_dbname}; |
2144
|
|
|
|
|
|
|
} |
2145
|
|
|
|
|
|
|
|
2146
|
|
|
|
|
|
|
=item B |
2147
|
|
|
|
|
|
|
|
2148
|
|
|
|
|
|
|
my $dbuser = $store->dbuser; |
2149
|
|
|
|
|
|
|
|
2150
|
|
|
|
|
|
|
Returns the username used to connect to the database used for backend storage. |
2151
|
|
|
|
|
|
|
|
2152
|
|
|
|
|
|
|
=cut |
2153
|
|
|
|
|
|
|
|
2154
|
|
|
|
|
|
|
sub dbuser { |
2155
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
2156
|
0
|
|
|
|
|
0
|
return $self->{_dbuser}; |
2157
|
|
|
|
|
|
|
} |
2158
|
|
|
|
|
|
|
|
2159
|
|
|
|
|
|
|
=item B |
2160
|
|
|
|
|
|
|
|
2161
|
|
|
|
|
|
|
my $dbpass = $store->dbpass; |
2162
|
|
|
|
|
|
|
|
2163
|
|
|
|
|
|
|
Returns the password used to connect to the database used for backend storage. |
2164
|
|
|
|
|
|
|
|
2165
|
|
|
|
|
|
|
=cut |
2166
|
|
|
|
|
|
|
|
2167
|
|
|
|
|
|
|
sub dbpass { |
2168
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
2169
|
0
|
|
|
|
|
0
|
return $self->{_dbpass}; |
2170
|
|
|
|
|
|
|
} |
2171
|
|
|
|
|
|
|
|
2172
|
|
|
|
|
|
|
=item B |
2173
|
|
|
|
|
|
|
|
2174
|
|
|
|
|
|
|
my $dbhost = $store->dbhost; |
2175
|
|
|
|
|
|
|
|
2176
|
|
|
|
|
|
|
Returns the optional host used to connect to the database used for |
2177
|
|
|
|
|
|
|
backend storage. |
2178
|
|
|
|
|
|
|
|
2179
|
|
|
|
|
|
|
=cut |
2180
|
|
|
|
|
|
|
|
2181
|
|
|
|
|
|
|
sub dbhost { |
2182
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
2183
|
0
|
|
|
|
|
0
|
return $self->{_dbhost}; |
2184
|
|
|
|
|
|
|
} |
2185
|
|
|
|
|
|
|
|
2186
|
|
|
|
|
|
|
# Cleanup. |
2187
|
|
|
|
|
|
|
sub DESTROY { |
2188
|
3
|
|
|
3
|
|
6
|
my $self = shift; |
2189
|
3
|
50
|
|
|
|
15
|
return if $self->{_external_dbh}; |
2190
|
3
|
|
|
|
|
31
|
my $dbh = $self->dbh; |
2191
|
3
|
50
|
|
|
|
17
|
$dbh->disconnect if $dbh; |
2192
|
|
|
|
|
|
|
} |
2193
|
|
|
|
|
|
|
|
2194
|
|
|
|
|
|
|
# decode a string of octets into perl's internal encoding, based on the |
2195
|
|
|
|
|
|
|
# charset parameter we were passed. Takes a list, returns a list. |
2196
|
|
|
|
|
|
|
sub charset_decode { |
2197
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
2198
|
0
|
|
|
|
|
|
my @input = @_; |
2199
|
0
|
0
|
|
|
|
|
if ($CAN_USE_ENCODE) { |
2200
|
0
|
|
|
|
|
|
my @output; |
2201
|
0
|
|
|
|
|
|
for (@input) { |
2202
|
0
|
|
|
|
|
|
push( @output, Encode::decode( $self->{_charset}, $_ ) ); |
2203
|
|
|
|
|
|
|
} |
2204
|
0
|
|
|
|
|
|
return @output; |
2205
|
|
|
|
|
|
|
} |
2206
|
0
|
|
|
|
|
|
return @input; |
2207
|
|
|
|
|
|
|
} |
2208
|
|
|
|
|
|
|
|
2209
|
|
|
|
|
|
|
# convert a perl string into a series of octets we can put into the database |
2210
|
|
|
|
|
|
|
# takes a list, returns a list |
2211
|
|
|
|
|
|
|
sub charset_encode { |
2212
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
2213
|
0
|
|
|
|
|
|
my @input = @_; |
2214
|
0
|
0
|
|
|
|
|
if ($CAN_USE_ENCODE) { |
2215
|
0
|
|
|
|
|
|
my @output; |
2216
|
0
|
|
|
|
|
|
for (@input) { |
2217
|
0
|
|
|
|
|
|
push( @output, Encode::encode( $self->{_charset}, $_ ) ); |
2218
|
|
|
|
|
|
|
} |
2219
|
0
|
|
|
|
|
|
return @output; |
2220
|
|
|
|
|
|
|
} |
2221
|
0
|
|
|
|
|
|
return @input; |
2222
|
|
|
|
|
|
|
} |
2223
|
|
|
|
|
|
|
|
2224
|
|
|
|
|
|
|
=back |
2225
|
|
|
|
|
|
|
|
2226
|
|
|
|
|
|
|
=cut |
2227
|
|
|
|
|
|
|
|
2228
|
|
|
|
|
|
|
1; |