1 package BSE::SubscriptionType;
3 # represents a subscription type from the database
6 @ISA = qw/Squirrel::Row/;
8 our $VERSION = "1.008";
11 return qw/id name title description frequency keyword archive
12 article_template html_template text_template parentId lastSent
17 my ($sub, $article, $opts) = @_;
19 my @cols = BSE::TB::Article->columns;
21 @$article{@cols} = ('') x @cols;
22 my $parent_id = $opts->{parentId} || $sub->{parentId} || -1;
25 $parent = BSE::TB::Articles->getByPkey($parent_id);
30 use BSE::Util::SQL qw(now_datetime now_sqldate);
31 $article->{body} = $opts->{body} || '';
32 $article->{title} = defined($opts->{title}) ? $opts->{title} : $sub->{title};
33 $article->{parentid} = $parent_id;
34 $article->{displayOrder} = time;
35 $article->{imagePos} = 'tr';
36 $article->{release} = now_sqldate;
37 $article->{expire} = $Constants::D_99;
39 exists($opts->{keyword}) ? $opts->{keyword} : $sub->{keyword};
40 $article->{generator} = 'BSE::Generate::Article';
41 $article->{level} = $parent ? $parent->{level} + 1 : 1;
42 $article->{listed} = 1;
43 $article->{lastModified} = now_datetime;
44 $article->{link} = '';
46 my $template = $opts->{html_template};
47 $template = $sub->{html_template} unless defined $template;
48 $article->{template} = $template;
49 $article->{generator} = 'BSE::Generate::Subscription';
52 $article->{threshold} = $parent->{threshold};
53 $article->{inherit_siteuser_rights} = 1;
54 $article->{summaryLength} = $parent->{summaryLength};
55 $article->{created} = now_datetime;
56 $article->{inherit_siteuser_rights} = 1;
57 $article->{force_dynamic} = 0;
58 $article->{cached_dynamic} = 0;
61 # for field value neatness
62 $article->{customDate1} = undef;
63 $article->{customDate2} = undef;
64 $article->{customStr1} = undef;
65 $article->{customStr2} = undef;
66 $article->{customInt1} = undef;
67 $article->{customInt2} = undef;
68 $article->{customInt3} = undef;
69 $article->{customInt4} = undef;
71 # $article->{titleImage} = '';
72 # $article->{thumbImage} = '';
73 # $article->{thumbWidth} = $article->{thumbHeight} = 0;
75 # $article->{admin} = '';
76 # $article->{link} = '';
83 while (length $text > 72) {
84 $text =~ s/^(.{1,72}\s+)(?=\S)//
85 or $text =~ s/^(\S+\s+)(?=\S)//
92 #$text =~ s/(.{1,72}\s+)(?=\S)/$1\n/g;
98 my ($cfg, $url, $title, $index) = @_;
100 my $fmt = $cfg->entry('subscriptions', 'text_link_inline',
110 $fmt =~ s/\$([123\$])/$replace{$1}/g;
116 my ($cfg, $urls, $url, $title, $rindex) = @_;
118 push @$urls, [ $url, $title ];
120 return _format_link($cfg, $url, $title, $$rindex++);
124 my ($cfg, $urls, $id, $title, $rindex) = @_;
127 if ($id =~ /^\d+$/) {
131 # try to find it in the config
132 my $work = $cfg->entry('articles', $id);
134 return ">> No article name '$id' in the [articles] section of bse.cfg <<";
136 $dispid = "$id ($work)";
139 require BSE::TB::Articles;
140 my $art = BSE::TB::Articles->getByPkey($id);
142 return ">> Cannot find article id $dispid <<";
145 # make the URL absolute
146 my $url = $art->{link};
147 $url = $cfg->entryErr('site', 'url') . $url
148 unless $url =~ /^\w+:/;
151 $title = $art->{title};
154 push @$urls, [ $url, $title ];
156 return _format_link($cfg, $url, $title, $$rindex++);
160 my ($cfg, $urls, $type, $content, $rindex) = @_;
162 if (lc $type eq 'link') {
163 if ($content =~ /^([^|]+)\|(.*)$/) {
164 return _body_link($cfg, $urls, $1, $2, $rindex);
170 else { # must be doclink
171 if ($content =~ /^([^|]+)\|(.*)$/) {
172 return _doclink($cfg, $urls, $1, $2, $rindex);
175 return _doclink($cfg, $urls, $content, undef, $rindex);
181 my ($cfg, $urls) = @_;
183 my $url_fmt = $cfg->entry('subscriptions', 'text_link_list',
188 my $sep = $cfg->entry('subscriptions', 'text_link_list_prefix',
191 $body .= $sep . "\n" if length $sep;
193 for my $url (@$urls) {
202 (my $work = $url_fmt) =~ s/\$([123\$n])/$replace{$1}/g;
211 my ($cfg, $article) = @_;
212 require BSE::Generate;
213 my $gen = BSE::Generate->new(cfg=>$cfg, top => $article);
214 my $body = $article->{body};
217 while ($body =~ s#(?:pop)?(doclink|link)\[([^\]\[]+)\]#_any_link($cfg, \@urls, $1, $2, \$url_index)#ie) {
220 $gen->remove_block('BSE::TB::Articles', [], \$body);
222 $body =~ s/[bi]\[([^\[\]]+)\]/$1/g
224 $body =~ s#(?<=\W)\[([^\]\[]+)\]#\003$1\004#g
229 $body =~ tr/\003\004/[]/;
230 $body =~ tr/\r//d; # in case
231 $body =~ s/(^(?:[ \t]*\n)?|\n[ \t]*\n)([^\n]{73,})(?=\n[ \t]*\n|\n?\z)/
232 $1 . _word_wrap($2)/ge;
235 $body .= "\n" unless $body =~ /\n$/;
236 $body .= _url_list($cfg, \@urls);
242 sub _text_format_low {
243 my ($sub, $cfg, $user, $opts, $article) = @_;
245 my $template = $opts->{text_template} || $sub->{text_template};
246 $article->{generator} = 'BSE::Generate::Subscription';
251 BSE::Util::Tags->static(\%acts, $cfg),
252 article=>sub { $article->{$_[0]} },
257 defined $user->{$args} or return '';
263 defined $user->{$_[0]} or return '';
268 _format_body($cfg, $article);
270 sub => sub { $sub->{$_[0]} },
273 require BSE::Template;
274 return BSE::Template->get_page($template, $cfg, \%acts);
278 my ($sub, $cfg, $user, $opts) = @_;
281 $sub->_build_article(\%article, $opts);
282 require BSE::DummyArticle;
283 bless \%article, "BSE::DummyArticle";
284 return $sub->_text_format_low($cfg, $user, $opts, \%article);
288 my ($sub, $cfg, $user, $opts) = @_;
291 $sub->_build_article(\%article, $opts);
292 require BSE::Generate::Subscription;
293 require BSE::DummyArticle;
294 bless \%article, "BSE::DummyArticle";
295 my $gen = BSE::Generate::Subscription->new(cfg=>$cfg, top => \%article);
296 $gen->set_user($user);
299 return $gen->generate(\%article, 'BSE::TB::Articles');
305 require BSE::TB::SiteUsers;
306 BSE::TB::SiteUsers->getSpecial('subRecipients', $sub->{id});
309 sub recipient_count {
312 my @rows = BSE::DB->query(subRecipientCount => $sub->{id});
317 my ($sub, $cfg, $opts, $callback, $recipients, $article) = @_;
319 $callback->('general', undef, scalar(@$recipients)." recipients to process");
320 require 'BSE/Mail.pm';
321 my $mailer = BSE::Mail->new(cfg=>$cfg);
322 $sub->_build_article($article, $opts);
323 require BSE::DummyArticle;
324 bless $article, "BSE::DummyArticle";
326 if ($article->{template}) {
327 #print STDERR "Making generator\n";
328 require BSE::Generate::Subscription;
329 $gen = BSE::Generate::Subscription->new(cfg=>$cfg, top=>$article);
332 my $from = $cfg->entryIfVar('subscriptions', 'from');
334 $from = $cfg->entry('shop', 'from', $Constants::SHOP_FROM);
337 $callback->('error', undef, "Configuration error: No from address configured, please set from in the subscriptions section of the config file, or \$SHOP_FROM in Constants.pm");
340 my $charset = $cfg->charset;
342 for my $user (@$recipients) {
343 $callback->('user', $user) if $callback;
344 my $text = $sub->_text_format_low($cfg, $user, $opts, $article);
347 $text = Encode::encode($cfg->charset, $text);
350 if ($gen && !$user->{textOnlyMail}) {
351 #print STDERR "Making HTML\n";
352 $gen->set_user($user);
354 %acts = $gen->baseActs("BSE::TB::Articles", \%acts, $article);
355 $html = BSE::Template->get_page($article->template, $cfg, \%acts,
356 undef, undef, $gen->variables);
359 $html = Encode::encode($cfg->charset, $html);
364 push(@headers, "MIME-Version: 1.0");
366 $html =~ tr/\cM/\cJ/;
367 my $boundary = "====" . time . "=_=" .int(rand(10000))."=";
368 push(@headers, qq!Content-Type: multipart/alternative; boundary="$boundary"!);
369 $content = "This is a multi-part message in MIME format\n\n"
371 $content .= qq!Content-Type: text/plain; charset="$charset"\n\n!
373 $content .= "--$boundary\n";
374 $content .= qq!Content-Type: text/html; charset="$charset"\n\n!
376 $content .= "--$boundary--\n";
379 push(@headers, qq!Content-Type: text/plain; charset="$charset"!);
381 $content .= "\n" unless $content =~ /\n$/;
383 unless ($mailer->send(from =>$from,
385 subject =>$article->{title},
386 headers =>join("\n", @headers, ""),
388 $callback->('error', $user, scalar($mailer->errstr));
394 # filter is an optional array ref of permitted subscriber ids
396 my ($sub, $cfg, $opts, $callback, $filter) = @_;
398 my @recipients = $sub->recipients;
400 unless (@recipients) {
401 $callback->('error', undef, 'This subscription has no recipients, no action taken');
405 # filter the recipients
407 my %filter = map { $_=>1 } @$filter;
408 @recipients = grep $filter{$_->{id}}, @recipients;
410 unless (@recipients) {
411 $callback->('error', undef, 'This subscription has no recipients after filters, no action taken');
417 $sub->_send($cfg, $opts, $callback, \@recipients, \%article);
419 if (exists $opts->{archive} ? $opts->{archive} : $sub->{archive}) {
420 $callback->('general', undef, "Archiving article");
421 require BSE::TB::Articles;
422 $article{template} = $opts->{article_template} || $sub->{article_template};
423 $article{generator} = 'BSE::Generate::Article';
424 $article{parentid} = $opts->{parentId} || $sub->{parentId};
425 my @cols = BSE::TB::Article->columns;
427 my $article = BSE::TB::Articles->add(@article{@cols});
428 use Constants qw(:edit $CGI_URI $ARTICLE_URI $LINK_TITLES);
429 my $link = "$ARTICLE_URI/$article->{id}.html";
431 (my $extra = lc $article->{title}) =~ tr/a-z0-9/_/sc;
434 $article->{link} = $link;
435 $article->setAdmin("$CGI_URI/admin/admin.pl?id=$article->{id}");
439 $callback->('general', undef, "Generating article");
440 BSE::Regen::generate_article('BSE::TB::Articles', $article, $cfg);
443 use BSE::Util::SQL qw/now_datetime/;
444 $sub->{lastSent} = now_datetime;
449 my ($sub, $cfg, $opts, $callback, $recipient) = @_;
451 my @recipients = ( $recipient );
454 $sub->_send($cfg, $opts, $callback, \@recipients, \%article);