3 # represents an article from the database
5 use BSE::TB::SiteCommon;
8 @ISA = qw/Squirrel::Row BSE::TB::SiteCommon BSE::TB::TagOwner/;
11 our $VERSION = "1.012";
15 Article - article objects for BSE.
19 use BSE::API qw(bse_make_article);
21 my $article = bse_make_article(...)
23 my $article = Articles->getByPkey($id);
27 Implements the base article object for BSE.
36 return qw/id parentid displayOrder title titleImage body
37 thumbImage thumbWidth thumbHeight imagePos
38 release expire keyword template link admin threshold
39 summaryLength generator level listed lastModified flags
40 customDate1 customDate2 customStr1 customStr2
41 customInt1 customInt2 customInt3 customInt4
42 lastModifiedBy created createdBy author pageTitle
43 force_dynamic cached_dynamic inherit_siteuser_rights
44 metaDescription metaKeywords summary menu titleAlias linkAlias
53 qw(id listed parentid threshold summaryLength level
54 customInt1 customInt2 customInt3 customInt4 menu);
59 Return the article's section.
67 while ($section->{parentid} > 0
68 and my $parent = Articles->getByPkey($section->{parentid})) {
77 Return the article's parent.
83 $self->{parentid} == -1 and return;
84 return Articles->getByPkey($self->{parentid});
88 my ($self, $cfg) = @_;
90 $cfg && $cfg->can('entry')
91 or confess 'update_dynamic called without $cfg';
93 # conditional in case something strange is in the config file
94 my $dynamic = $cfg->entry('basic', 'all_dynamic', 0) ? 1 : 0;
96 if (!$dynamic && $self->generator =~ /\bCatalog\b/) {
98 my @tiers = Products->pricing_tiers;
99 @tiers and $dynamic = 1;
102 $dynamic or $dynamic = $self->{force_dynamic};
104 $dynamic or $dynamic = $self->is_access_controlled;
106 $dynamic or $dynamic = $self->force_dynamic_inherited;
108 $self->{cached_dynamic} = $dynamic;
113 Return true if the article is rendered dynamically.
118 $_[0]{cached_dynamic};
121 sub is_accessible_to {
122 my ($self, $group) = @_;
124 my $groupid = ref $group ? $group->{id} : $group;
126 my @rows = BSE::DB->query(articleAccessibleToGroup => $self->{id}, $groupid);
134 map $_->{id}, BSE::DB->query(siteuserGroupsForArticle => $self->{id});
138 my ($self, $id) = @_;
141 BSE::DB->single->run(articleAddSiteUserGroup => $self->{id}, $id);
145 sub remove_group_id {
146 my ($self, $id) = @_;
148 BSE::DB->single->run(articleDeleteSiteUserGroup => $self->{id}, $id);
151 sub is_access_controlled {
154 my @group_ids = $self->group_ids;
155 return 1 if @group_ids;
158 unless $self->{inherit_siteuser_rights};
160 my $parent = $self->parent
163 return $parent->is_access_controlled;
166 sub force_dynamic_inherited {
169 my $parent = $self->parent
172 $parent->{force_dynamic} && $parent->{flags} =~ /F/
175 return $parent->force_dynamic_inherited;
178 sub link_to_filename {
179 my ($self, $cfg, $link) = @_;
181 $cfg ||= BSE::Cfg->single;
183 defined $link or $link = $self->{link};
185 length $link or return;
187 my $filename = $link;
189 # remove any appended title,
190 $filename =~ s!(.)/\w+$!$1!;
191 $filename =~ s{^\w+://[\w.-]+(?::\d+)?}{};
192 $filename = $cfg->content_base_path() . $filename;
193 if ($filename =~ m(/$)) {
194 $filename .= $cfg->entry("basic", "index_file", "index.html");
196 $filename =~ s!//+!/!;
201 sub cached_filename {
202 my ($self, $cfg) = @_;
204 $cfg ||= BSE::Cfg->single;
206 my $dynamic_path = $cfg->entryVar('paths', 'dynamic_cache');
207 return $dynamic_path . "/" . $self->{id} . ".html";
211 my ($self, $cfg) = @_;
213 $cfg ||= BSE::Cfg->single;
215 return $self->is_dynamic
216 ? $self->cached_filename($cfg)
217 : $self->link_to_filename($cfg);
221 my ($self, $cfg) = @_;
223 my $filename = $self->html_filename($cfg)
233 my ($self, $cfg) = @_;
235 $cfg or confess "No \$cfg supplied to ", ref $self, "->remove";
239 $self->remove_images($cfg);
241 for my $file ($self->files) {
245 # remove any step(child|parent) links
246 require OtherParents;
247 my @steprels = OtherParents->anylinks($self->{id});
248 for my $link (@steprels) {
252 # remove the static page
253 $self->remove_html($cfg);
255 $self->SUPER::remove();
261 my @result = $self->step_parents;
262 if ($self->{parentid} > 0 && !grep $_->{id} eq $self->{parentid}, @result) {
263 push @result, $self->parent;
269 sub is_step_ancestor {
270 my ($self, $other, $max) = @_;
272 my $other_id = ref $other ? $other->{id} : $other;
277 # early exit if possible
278 return 1 if $self->{parentid} == $other_id;
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;
284 my $entry = shift @work;
285 my ($level, $workart) = @$entry;
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;
298 sub possible_stepparents {
301 return BSE::DB->query(articlePossibleStepparents => $self->{id}, $self->{id});
304 sub possible_stepchildren {
307 return BSE::DB->query(articlePossibleStepchildren => $self->{id}, $self->{id});
311 my ($self, $cfg) = @_;
313 if ($self->flags =~ /P/) {
314 my $parent = $self->parent;
315 $parent and return $parent->link($cfg);
321 $cfg ||= BSE::Cfg->single;
323 unless ($self->{linkAlias} && $cfg->entry('basic', 'use_alias', 1)) {
324 return $self->{link};
327 my $prefix = $cfg->entry('basic', 'alias_prefix', '');
329 if ($cfg->entry('basic', 'alias_recursive')) {
330 my @steps = $self->{linkAlias};
332 while ($article = $article->parent) {
333 if ($article->{linkAlias}) {
334 unshift @steps, $article->{linkAlias};
337 $link = join('/', $prefix, @steps);
340 $link = $prefix . '/' . $self->{linkAlias};
342 if ($cfg->entry('basic', 'alias_suffix', 1)) {
343 my $title = $self->{title};
344 $title =~ tr/a-zA-Z0-9/_/cs;
345 $link .= '/' . $title;
352 Return the admin link for the article.
359 return BSE::Cfg->single->admin_url("admin", { id => $self->id });
365 return $self->flags !~ /D/;
372 # the time used for expiry/release comparisons
373 sub _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);
380 Returns true if the article expiry date has passed.
387 return $self->expire lt _expire_release_datetime();
392 Returns true if the article release date has passed (ie. the article
400 return $self->release le _expire_release_datetime();
405 Return true if the article should be listed in menus.
412 return $self->listed == 1;
417 Returns a list of ancestors of self.
424 unless ($self->{_ancestors}) {
427 while ($work->parentid != -1) {
428 $work = $work->parent;
429 push @ancestors, $work;
432 $self->{_ancestors} = \@ancestors;
435 return @{$self->{_ancestors}};
438 =item is_descendant_of($ancestor)
440 Return true if the supplied article is a descendant of self.
444 sub is_descendant_of {
445 my ($self, $ancestor) = @_;
447 for my $anc ($self->ancestors) {
448 return 1 if $anc->id == $ancestor->id;
454 sub restricted_methods {
455 my ($self, $name) = @_;
457 return $self->SUPER::restricted_methods($name)
458 || $name =~ /^(?:update_|remove_|add_)/;
469 L<BSE::TB::SiteCommon>
477 Tony Cook <tony@develop-help.com>