allow use of the new template system from static pages
[bse.git] / site / cgi-bin / modules / Article.pm
CommitLineData
41b9d8ec 1package Article;
9d29b58f 2use strict;
41b9d8ec
TC
3# represents an article from the database
4use Squirrel::Row;
7646d96e 5use BSE::TB::SiteCommon;
76c6b28e 6use BSE::TB::TagOwner;
41b9d8ec 7use vars qw/@ISA/;
76c6b28e 8@ISA = qw/Squirrel::Row BSE::TB::SiteCommon BSE::TB::TagOwner/;
efcc5a30 9use Carp 'confess';
41b9d8ec 10
599fe373
TC
11our $VERSION = "1.012";
12
13=head1 NAME
14
15Article - article objects for BSE.
16
17=head1 SYNOPSIS
18
19 use BSE::API qw(bse_make_article);
20
21 my $article = bse_make_article(...)
22
23 my $article = Articles->getByPkey($id);
24
25=head1 DESCRIPTION
26
27Implements the base article object for BSE.
28
29=head1 USEFUL METHODS
30
31=over
32
33=cut
cb7fd78d 34
41b9d8ec
TC
35sub columns {
36 return qw/id parentid displayOrder title titleImage body
37 thumbImage thumbWidth thumbHeight imagePos
38 release expire keyword template link admin threshold
331fd099 39 summaryLength generator level listed lastModified flags
9063386f 40 customDate1 customDate2 customStr1 customStr2
9604a90c 41 customInt1 customInt2 customInt3 customInt4
efcc5a30 42 lastModifiedBy created createdBy author pageTitle
12bcb7ac 43 force_dynamic cached_dynamic inherit_siteuser_rights
dbfbfb12
TC
44 metaDescription metaKeywords summary menu titleAlias linkAlias
45 category/;
41b9d8ec
TC
46}
47
58baa27b
TC
48sub table {
49 'article';
50}
51
41f10371
TC
52sub numeric {
53 qw(id listed parentid threshold summaryLength level
0a66f55c 54 customInt1 customInt2 customInt3 customInt4 menu);
41f10371
TC
55}
56
599fe373
TC
57=item section
58
59Return the article's section.
60
61=cut
62
ff9130e4
TC
63sub section {
64 my ($self) = @_;
65
66 my $section = $self;
67 while ($section->{parentid} > 0
68 and my $parent = Articles->getByPkey($section->{parentid})) {
69 $section = $parent;
70 }
71
72 return $section;
73}
74
599fe373
TC
75=item parent
76
77Return the article's parent.
78
79=cut
80
08123550
TC
81sub parent {
82 my ($self) = @_;
83 $self->{parentid} == -1 and return;
84 return Articles->getByPkey($self->{parentid});
85}
86
efcc5a30
TC
87sub update_dynamic {
88 my ($self, $cfg) = @_;
89
90 $cfg && $cfg->can('entry')
91 or confess 'update_dynamic called without $cfg';
92
93 # conditional in case something strange is in the config file
94 my $dynamic = $cfg->entry('basic', 'all_dynamic', 0) ? 1 : 0;
95
dfd483db
TC
96 if (!$dynamic && $self->generator =~ /\bCatalog\b/) {
97 require Products;
98 my @tiers = Products->pricing_tiers;
99 @tiers and $dynamic = 1;
100 }
101
efcc5a30
TC
102 $dynamic or $dynamic = $self->{force_dynamic};
103
63e99d77 104 $dynamic or $dynamic = $self->is_access_controlled;
c2096d67 105
63e99d77 106 $dynamic or $dynamic = $self->force_dynamic_inherited;
efcc5a30
TC
107
108 $self->{cached_dynamic} = $dynamic;
109}
110
599fe373
TC
111=item is_dynamic
112
113Return true if the article is rendered dynamically.
114
115=cut
116
efcc5a30
TC
117sub is_dynamic {
118 $_[0]{cached_dynamic};
119}
120
c2096d67
TC
121sub is_accessible_to {
122 my ($self, $group) = @_;
123
124 my $groupid = ref $group ? $group->{id} : $group;
125
126 my @rows = BSE::DB->query(articleAccessibleToGroup => $self->{id}, $groupid);
127
128 scalar @rows;
129}
130
131sub group_ids {
132 my ($self) = @_;
133
134 map $_->{id}, BSE::DB->query(siteuserGroupsForArticle => $self->{id});
135}
136
137sub add_group_id {
138 my ($self, $id) = @_;
139
140 eval {
141 BSE::DB->single->run(articleAddSiteUserGroup => $self->{id}, $id);
142 };
143}
144
145sub remove_group_id {
146 my ($self, $id) = @_;
147
148 BSE::DB->single->run(articleDeleteSiteUserGroup => $self->{id}, $id);
149}
150
b873a8fa
TC
151sub is_access_controlled {
152 my ($self) = @_;
153
154 my @group_ids = $self->group_ids;
155 return 1 if @group_ids;
156
157 return 0
158 unless $self->{inherit_siteuser_rights};
159
160 my $parent = $self->parent
161 or return 0;
162
163 return $parent->is_access_controlled;
164}
165
63e99d77
TC
166sub force_dynamic_inherited {
167 my ($self) = @_;
168
169 my $parent = $self->parent
170 or return 0;
171
172 $parent->{force_dynamic} && $parent->{flags} =~ /F/
173 and return 1;
174
175 return $parent->force_dynamic_inherited;
176}
177
178sub link_to_filename {
179 my ($self, $cfg, $link) = @_;
180
a739c25d 181 $cfg ||= BSE::Cfg->single;
16901a2a 182
63e99d77
TC
183 defined $link or $link = $self->{link};
184
8f84f3f1
TC
185 length $link or return;
186
63e99d77 187 my $filename = $link;
981d07ba
TC
188
189 # remove any appended title,
190 $filename =~ s!(.)/\w+$!$1!;
63e99d77 191 $filename =~ s{^\w+://[\w.-]+(?::\d+)?}{};
5abe2da5 192 $filename = $cfg->content_base_path() . $filename;
981d07ba
TC
193 if ($filename =~ m(/$)) {
194 $filename .= $cfg->entry("basic", "index_file", "index.html");
195 }
63e99d77
TC
196 $filename =~ s!//+!/!;
197
198 return $filename;
199}
200
16901a2a
TC
201sub cached_filename {
202 my ($self, $cfg) = @_;
203
a739c25d 204 $cfg ||= BSE::Cfg->single;
16901a2a
TC
205
206 my $dynamic_path = $cfg->entryVar('paths', 'dynamic_cache');
207 return $dynamic_path . "/" . $self->{id} . ".html";
208}
209
a739c25d
TC
210sub html_filename {
211 my ($self, $cfg) = @_;
212
213 $cfg ||= BSE::Cfg->single;
214
215 return $self->is_dynamic
216 ? $self->cached_filename($cfg)
217 : $self->link_to_filename($cfg);
218}
219
220sub remove_html {
221 my ($self, $cfg) = @_;
222
223 my $filename = $self->html_filename($cfg)
224 or return 1;
225
226 unlink $filename
227 or return;
228
229 return 1;
230}
231
bf87e97c
TC
232sub remove {
233 my ($self, $cfg) = @_;
234
235 $cfg or confess "No \$cfg supplied to ", ref $self, "->remove";
236
76c6b28e
TC
237 $self->remove_tags;
238
bf87e97c 239 $self->remove_images($cfg);
16901a2a
TC
240
241 for my $file ($self->files) {
242 $file->remove($cfg);
243 }
244
245 # remove any step(child|parent) links
246 require OtherParents;
247 my @steprels = OtherParents->anylinks($self->{id});
248 for my $link (@steprels) {
249 $link->remove();
250 }
251
252 # remove the static page
a739c25d 253 $self->remove_html($cfg);
16901a2a
TC
254
255 $self->SUPER::remove();
256}
257
201e926c
TC
258sub all_parents {
259 my ($self) = @_;
260
261 my @result = $self->step_parents;
262 if ($self->{parentid} > 0 && !grep $_->{id} eq $self->{parentid}, @result) {
263 push @result, $self->parent;
264 }
265
266 return @result;
267}
268
269sub is_step_ancestor {
270 my ($self, $other, $max) = @_;
271
272 my $other_id = ref $other ? $other->{id} : $other;
273 my %seen;
274
275 $max ||= 10;
276
277 # early exit if possible
278 return 1 if $self->{parentid} == $other_id;
279
280 my @all_parents = $self->all_parents;
281 return 1 if grep $_->{id} == $other_id, @all_parents;
282 my @work = map [ 0, $_], grep !$seen{$_}++, @all_parents;
283 while (@work) {
284 my $entry = shift @work;
285 my ($level, $workart) = @$entry;
286
287 $level++;
288 if ($level < $max) {
289 @all_parents = $workart->all_parents;
290 return 1 if grep $_->{id} == $other_id, @all_parents;
291 push @work, map [ $level, $_ ], grep !$seen{$_}++, @all_parents;
292 }
293 }
294
295 return 0;
296}
297
147e99e8
TC
298sub possible_stepparents {
299 my $self = shift;
300
301 return BSE::DB->query(articlePossibleStepparents => $self->{id}, $self->{id});
302}
303
304sub possible_stepchildren {
305 my $self = shift;
306
307 return BSE::DB->query(articlePossibleStepchildren => $self->{id}, $self->{id});
308}
309
c76e86ea
TC
310sub link {
311 my ($self, $cfg) = @_;
312
538b7aee
TC
313 if ($self->flags =~ /P/) {
314 my $parent = $self->parent;
315 $parent and return $parent->link($cfg);
316 }
317
a739c25d
TC
318 $self->is_linked
319 or return "";
320
538b7aee 321 $cfg ||= BSE::Cfg->single;
089bef32 322
cddcd8c0
TC
323 unless ($self->{linkAlias} && $cfg->entry('basic', 'use_alias', 1)) {
324 return $self->{link};
325 }
326
327 my $prefix = $cfg->entry('basic', 'alias_prefix', '');
328 my $link;
329 if ($cfg->entry('basic', 'alias_recursive')) {
330 my @steps = $self->{linkAlias};
331 my $article = $self;
332 while ($article = $article->parent) {
333 if ($article->{linkAlias}) {
334 unshift @steps, $article->{linkAlias};
335 }
73e6b73a 336 }
cddcd8c0 337 $link = join('/', $prefix, @steps);
c76e86ea
TC
338 }
339 else {
cddcd8c0
TC
340 $link = $prefix . '/' . $self->{linkAlias};
341 }
342 if ($cfg->entry('basic', 'alias_suffix', 1)) {
343 my $title = $self->{title};
344 $title =~ tr/a-zA-Z0-9/_/cs;
345 $link .= '/' . $title;
c76e86ea 346 }
cddcd8c0 347 return $link;
c76e86ea
TC
348}
349
599fe373
TC
350=item admin
351
352Return the admin link for the article.
353
354=cut
355
ec5a2133
TC
356sub admin {
357 my ($self) = @_;
358
359 return BSE::Cfg->single->admin_url("admin", { id => $self->id });
360}
361
a739c25d
TC
362sub is_linked {
363 my ($self) = @_;
364
70c6e1e1 365 return $self->flags !~ /D/;
a739c25d
TC
366}
367
76c6b28e
TC
368sub tag_owner_type {
369 return "BA";
370}
371
8eaf4ceb
TC
372# the time used for expiry/release comparisons
373sub _expire_release_datetime {
374 my ($year, $month, $day) = (localtime)[5,4,3];
375 my $today = sprintf("%04d-%02d-%02d 00:00:00ZZZ", $year+1900, $month+1, $day);
376}
377
378=item is_expired
379
380Returns true if the article expiry date has passed.
381
382=cut
383
384sub is_expired {
385 my $self = shift;
386
387 return $self->expire lt _expire_release_datetime();
388}
389
390=item is_released
391
392Returns true if the article release date has passed (ie. the article
393has been released.)
394
395=cut
396
397sub is_released {
398 my $self = shift;
399
400 return $self->release le _expire_release_datetime();
401}
402
599fe373
TC
403=item listed_in_menu
404
405Return true if the article should be listed in menus.
406
407=cut
408
409sub listed_in_menu {
410 my $self = shift;
411
412 return $self->listed == 1;
413}
414
415=item ancestors
416
417Returns a list of ancestors of self.
418
419=cut
420
421sub ancestors {
422 my ($self) = @_;
423
424 unless ($self->{_ancestors}) {
425 my @ancestors;
426 my $work = $self;
427 while ($work->parentid != -1) {
428 $work = $work->parent;
429 push @ancestors, $work;
430 }
431
432 $self->{_ancestors} = \@ancestors;
433 }
434
435 return @{$self->{_ancestors}};
436}
437
438=item is_descendant_of($ancestor)
439
440Return true if the supplied article is a descendant of self.
441
442=cut
443
444sub is_descendant_of {
445 my ($self, $ancestor) = @_;
446
447 for my $anc ($self->ancestors) {
448 return 1 if $anc->id == $ancestor->id;
449 }
450
451 return 0;
452}
453
86dc77f9
TC
454sub restricted_methods {
455 my ($self, $name) = @_;
456
457 return $self->SUPER::restricted_methods($name)
458 || $name =~ /^(?:update_|remove_|add_)/;
459}
460
41b9d8ec 4611;
599fe373
TC
462
463__END__
464
465=back
466
467=head1 BASE CLASSES
468
469L<BSE::TB::SiteCommon>
470
471L<BSE::TB::TagOwner>
472
473L<Squirrel::Row>
474
475=head1 AUTHOR
476
477Tony Cook <tony@develop-help.com>
478
479=cut