OSDN Git Service

move unused (deprecated) files
[newslash/newslash.git] / src / newslash_web / lib / Newslash / Model / Journals.pm
1 package Newslash::Model::Journals;
2 use Newslash::Model::Base -base;
3
4 #use Newslash::Model::Util;
5 use URI::Escape;
6 use Data::Dumper;
7 use POSIX qw(strftime);
8 use Newslash::Util::TextFormatter;
9
10 use constant JOURNAL_FORMATTER_TABLE => "ns_journal_formatter";
11
12 use constant DEFAULT_ALLOWED_TAGS => {
13                                       'b' => [],
14                                       'i' => [],
15                                       'p' => [],
16                                       'br' => [],
17                                       'a' => ['href',],
18                                       'ol' => ['start',],
19                                       'ul' => [],
20                                       'li' => [],
21                                       'dl' => [],
22                                       'dt' => [],
23                                       'dd' => [],
24                                       'em' => [],
25                                       'strong' => [],
26                                       'tt' => [],
27                                       'blockquote' => ['title', 'cite',],
28                                       'div' => [],
29                                       'ecode' => [],
30                                       'del' => [],
31                                       'ins' => [],
32                                       'sub' => [],
33                                       'sup' => [],
34                                       'quote' => [],
35                                       'strike' => [],
36                                      };
37
38 sub create_table {
39     my $self = shift;
40     my $dbh = $self->connect_db;
41     my $table_name = JOURNAL_FORMATTER_TABLE;
42
43     return 1 if $self->check_readonly;
44
45     my $sql = <<"EOSQL";
46 CREATE TABLE IF NOT EXISTS $table_name (
47   journal_id     int(8) unsigned NOT NULL,
48   formatter      varchar(128)    NOT NULL,
49   KEY journal_id  (journal_id)
50 ) DEFAULT CHARSET=utf8mb4
51 EOSQL
52     my $rs = $dbh->do($sql, undef);
53     $self->disconnect_db;
54     return 1 if $rs;
55
56     $self->logger->warn("Journals: journal formatter table create failed.");
57     return;
58 }
59
60 sub on_start_up {
61     my $self = shift;
62     if (!$self->table_exists(JOURNAL_FORMATTER_TABLE)) {
63         $self->logger->warn("Journals: journal formatter table not exists, create it.");
64         return $self->create_table;
65     }
66     return 1;
67 }
68
69
70 sub count {
71     my $self = shift;
72     my $join = 'LEFT JOIN firehose ON (journals.id = firehose.srcid AND firehose.type = "journal")';
73     my $where = 'firehose.public != "no"';
74     return $self->generic_count(table => "journals",
75                                 target => "id",
76                                 timestamp => "date",
77                                 join => $join,
78                                 where => $where,
79                                 @_);
80 }
81
82
83 #========================================================================
84
85 =head2 select()
86
87 get a journal.
88
89 =over 4
90
91 =item Parameters
92
93 =over 4
94
95 =item $query_type
96
97 query key, "id"
98
99 =item $value
100
101 value for query
102
103 =back
104
105 =item Return value
106
107 HASH of story contents
108
109 =back
110
111 =cut
112
113 sub select {
114     my $self = shift;
115     my $params = {@_};
116
117     my $unique_keys = { id => "journals.id",
118                         journal_id => "journals.id",
119                         discussion_id => "journals.discussion",
120                         discussion => "journals.discussion",
121                       };
122     my $keys = { uid => "journals.uid",
123                  user_id => "journals.uid",
124                  karma => "users_info.karma",
125                };
126     my $datetime_keys = { create_time => 'journals.date',
127                           update_time => 'journals.last_update',
128                         };
129
130     my ($where_clause, $where_values, $unique) = $self->build_where_clause(unique_keys => $unique_keys,
131                                                                            keys => $keys,
132                                                                            datetime_keys => $datetime_keys,
133                                                                            timestamp => 'journals.date',
134                                                                            params => $params);
135     my ($limit_clause, $limit_values) = $self->build_limit_clause(params => $params);
136     my ($orderby_clause, $orderby_values) = $self->build_order_by_clause(keys => $datetime_keys,
137                                                                         params => $params);
138     # show future story?
139     my @where_clauses;
140     if (!$params->{show_future}) {
141         push @where_clauses, "journals.date <= NOW()";
142     }
143
144     # show non-public story?
145     if (!$params->{show_nonpublic} && !$params->{public}) {
146         push @where_clauses, "firehose.public != 'no'";
147     }
148
149     if (@where_clauses) {
150         if ($where_clause) {
151             $where_clause = $where_clause . " AND ";
152         }
153         else {
154             $where_clause = "WHERE ";
155         }
156         $where_clause = $where_clause . join(" AND ", @where_clauses);
157     }
158
159     my @attr;
160     push @attr, @$where_values, @$limit_values, @$orderby_values;
161
162     my $dbh = $self->connect_db;
163     my $formatter_table = JOURNAL_FORMATTER_TABLE;
164     my $sql = <<"EOSQL";
165 SELECT
166     journals.*,
167     journals_text.*,
168     users.nickname as author,
169     firehose.toptags,
170     firehose.public,
171     topics.*,
172     discussions.commentcount AS comment_count,
173     discussions.type AS discussion_type,
174     discussions.commentstatus AS comment_status,
175     $formatter_table.formatter
176   FROM journals
177     LEFT JOIN journals_text ON journals.id = journals_text.id
178     LEFT JOIN users ON journals.uid = users.uid
179     LEFT JOIN users_info ON journals.uid = users_info.uid
180     LEFT JOIN firehose ON (journals.id = srcid AND firehose.type = 'journal')
181     LEFT JOIN topics ON journals.tid = topics.tid
182     LEFT JOIN discussions ON journals.discussion = discussions.id
183     LEFT JOIN $formatter_table ON $formatter_table.journal_id = journals.id
184   $where_clause
185   $orderby_clause
186   $limit_clause
187 EOSQL
188
189     my $sth = $dbh->prepare($sql);
190     $sth->execute(@attr);
191     my $rs = $sth->fetchall_arrayref({});
192     $self->disconnect_db() if !$params->{dbh};
193
194     if (@$rs == 0) {
195         return if $unique;
196         return [];
197     }
198
199     my $tags = $self->new_instance_of("Tags");
200     my $whole_tags = $tags->select_for_items("journal", "id", $rs);
201     if ($whole_tags) {
202         for my $item (@$rs) {
203             $item->{tags} = $whole_tags->{$item->{id}} || [];
204         }
205     }
206
207     for my $j (@$rs) {
208         $self->_generalize($j);
209     }
210
211     return $rs->[0] if $unique;
212     return $rs;
213 }
214
215 sub generate_bodytext {
216     my ($self, $journals) = @_;
217     my $target = (ref($journals) eq "HASH") ? [$journals,] : $journals;
218
219     for my $j (@$target) {
220         if ($j->{formatter} eq "modern") {
221             $j->{bodytext} = $self->article_to_bodytext($j->{article},
222                                                         $j->{posttype},
223                                                         {formatter => "modern"});
224             $j->{fulltext} = $self->article_to_fulltext($j->{article},
225                                                         $j->{posttype},
226                                                         {formatter => "modern"});
227         }
228         else {
229             $j->{introtext} = $self->article_to_introtext($j->{article},
230                                                           $j->{posttype});
231             $j->{bodytext} = $self->article_to_bodytext($j->{article},
232                                                         $j->{posttype});
233             $j->{fulltext} = $self->article_to_fulltext($j->{article},
234                                                         $j->{posttype});
235         }
236         $j->{body_text} = $j->{bodytext}; # for compatible
237         $j->{full_text} = $j->{fulltext}; # for compatible
238     }
239     return;
240 }
241
242 sub _generalize {
243     my ($self, $journal, $options) = @_;
244
245     # NTO-nized
246     $journal->{journal_id} = $journal->{id};
247     $journal->{create_time} = $journal->{date};
248     $journal->{update_time} = $journal->{last_update};
249
250     my @tags = split /\s+/, $journal->{toptags};
251     $journal->{toptags} = \@tags;
252     my $primary_topic = {};
253     for my $item (qw(tid keyword textname series image width height submittable searchable storypickable usesprite)) {
254         $primary_topic->{$item} = $journal->{$item};
255     }
256     $journal->{primary_topic} = $primary_topic;
257     $journal->{topics} = [$primary_topic,];
258     $journal->{content_type} = 'journal';
259
260     $journal->{title} = $journal->{description};
261
262     if (!$journal->{formatter}) {
263         $journal->{formatter} = "legacy";
264     }
265
266     ## parse process is not lightweight, so bypassed here
267     #
268     # if ($journal->{formatter} eq "modern") {
269     #     $journal->{bodytext} = $self->article_to_bodytext($journal->{article},
270     #                                                       $journal->{posttype},
271     #                                                       {formatter => "modern"});
272     #     $journal->{fulltext} = $self->article_to_fulltext($journal->{article},
273     #                                                       $journal->{posttype},
274     #                                                       {formatter => "modern"});
275     # }
276     # elsif ($journal->{formatter} eq "legacy") {
277     #     $journal->{introtext} = $self->article_to_introtext($journal->{article},
278     #                                                       $journal->{posttype});
279     #     $journal->{bodytext} = $self->article_to_bodytext($journal->{article},
280     #                                                       $journal->{posttype});
281     #     $journal->{fulltext} = $self->article_to_fulltext($journal->{article},
282     #                                                       $journal->{posttype});
283     # }
284
285
286     $journal->{intro_text} = $journal->{introtext}; # for compatible
287     $journal->{discussion_id} = $journal->{discussion};
288
289     # no public flag given, public is 'yes'
290     $journal->{public} = 'yes' if !$journal->{public};
291
292     # add tags_string
293     if ($journal->{tags}) {
294         my $tags = $self->new_instance_of("Tags");
295         my @tagnames;
296         for my $tag (@{$journal->{tags}}) {
297             next if $tags->is_reserved_name($tag->{tagname});
298             push @tagnames, $tag->{tagname};
299         }
300         $journal->{tags_string} = join(" ", @tagnames);
301     }
302
303     return $journal;
304 }
305
306
307 =head2 create(\%params, \%user, \%extra_params, \$opts)
308
309 create a journal.
310
311 =over 4
312
313 =item Parameters
314
315 =over 4
316
317 =item \%params
318
319 parameters
320
321 =item \user
322
323 author's user object
324
325 =item extra_params
326
327 extra parameters
328
329 =item opts
330
331 options
332
333 =back
334
335 =item Return value
336
337 journal id
338
339 =back
340
341 =cut
342
343 use constant COMMENT_STATUS => ('disabled', 'enabled', 'friends_only', 'friends_fof_only',
344                                 'no_foe', 'no_foe_eof' ,'logged_in');
345
346 sub create {
347     #my ($self, $params, $user, $extra_params, $opts) = @_;
348     my $self = shift;
349     return if $self->check_readonly;
350
351     my $params = {@_};
352     my $user = $params->{user};
353     return if (!$params || !$user);
354
355     # check title exists
356     my $title = $params->{description} || $params->{title};
357     if (!$title) {
358         $self->set_error("no title given", 1);
359         return;
360     }
361     # check title exists
362     my $article = $params->{article} || $params->{body_text} || $params->{bodytext};
363     if (!$article) {
364         $self->set_error("no journal text given", 1);
365         return;
366     }
367
368     # check promotetype
369     # check discussion
370     my $comment_status = $params->{commentstatus} || $params->{comment_status};
371     if (!$comment_status) {
372         $self->set_error("no comment_status given", 1);
373         return;
374     }
375     if (!grep {$comment_status eq $_} COMMENT_STATUS) {
376         $self->set_error("invalid comment_status given", 1);
377         return;
378     }
379
380     # posttype: 1: PLAINTEXT, 2: HTML, 3: EXTRANS, 4: CODE, 77: FULLHTML
381     my $post_type = $params->{posttype} || $params->{post_type} || 2;
382     my $topic_id = $params->{tid} || $params->{topic_id} || 2068, # tid=2068 is journal.,
383     my $promo_type = $params->{promotype} || $params->{promo_type} || 'publish';
384
385     my $dbh = $self->start_transaction;
386
387     my $discussion_id;
388     if ($comment_status ne 'disabled') {
389         # is discussion enabled, create discussion
390         my $discussions = $self->new_instance_of('Discussions');
391         my $escaped_nickname = uri_escape($user->{nickname});
392
393         $discussion_id = $discussions->create(title => $title,
394                                               url => "http://srad.jp/~$escaped_nickname/journal/",
395                                               topic => $topic_id,
396                                               kind => 'journal',
397                                               commentstatus => $comment_status,
398                                               uid => $user->{uid} || $user->{user_id},
399                                              );
400         if (!$discussion_id) {
401             $self->rollback;
402             $self->set_error("discussion create failed", $discussions->last_errorno);
403             return;
404         }
405     }
406
407     # create journal
408     my @values;
409     my $sql = <<"EOSQL";
410 INSERT INTO journals
411     (uid, date,  description, posttype, discussion, tid, promotetype, srcid_32, srcid_24)
412   VALUES
413     (?,   NOW(), ?,           ?,        ?,          ?,   ?,
414        CAST(CONV(?, 16, 10) AS UNSIGNED),
415        CAST(CONV(?, 16, 10) AS UNSIGNED))
416 EOSQL
417
418     push @values, ($user->{uid} || $user->{user_id});
419     push @values, $title;
420     push @values, $post_type;
421     push @values, $discussion_id;
422     push @values, $topic_id;
423     # promotype: publicize: make_submission, publish: post firehose, post: no firehose
424     push @values, $promo_type;
425     # srcid_32 and srcid_24 is decimal.
426     # warn: Hexadecimal number > 0xffffffff non-portable
427     push @values, $user->{srcids}{32};
428     push @values, $user->{srcids}{24};
429
430     my $rs = $dbh->do($sql, undef, @values);
431     if (!$rs) {
432         $self->rollback;
433         $self->set_error($dbh->errstr, $dbh->err);
434         return;
435     }
436     my $journal_id = $dbh->last_insert_id(undef, undef, undef, undef);
437
438     # insert into journals_text
439     $sql = <<"EOSQL";
440 INSERT INTO journals_text
441     (id, article, introtext)
442   VALUES
443     (?,  ?,       ?)
444 EOSQL
445
446     my $introtext = $self->article_to_introtext($article, $post_type, $params);
447     my $bodytext = $self->article_to_bodytext($article, $post_type, $params);
448
449     $rs = $dbh->do($sql, undef, $journal_id, $article, $introtext);
450     if (!$rs) {
451         $self->rollback;
452         $self->set_error($dbh->errstr, $dbh->err);
453         return;
454     }
455
456     # update discussion info
457     if ($discussion_id) {
458         my $discussions = $self->new_instance_of('Discussions');
459         my $escaped_nickname = uri_escape($user->{nickname});
460         $discussions->update(id => $discussion_id,
461                              url => "http://srad.jp/~$escaped_nickname/journal/$journal_id");
462     }
463
464     # insert formatter
465     if ($params->{formatter}) {
466         my $formatter_table = JOURNAL_FORMATTER_TABLE;
467         $sql = <<"EOSQL";
468 INSERT INTO $formatter_table
469     (journal_id, formatter)
470   VALUES
471     (?,          ?)
472 EOSQL
473         $rs = $dbh->do($sql, undef, $journal_id, $params->{formatter});
474         if (!$rs) {
475             $self->rollback;
476             $self->set_error($dbh->errstr, $dbh->err);
477             return;
478         }
479     }
480
481     # firehose item is not created, so select() cannot use.
482     #my $journal = $self->select(id => $journal_id, dbh => $dbh);
483     my $journal = {
484                    description => $title,
485                    id => $journal_id,
486                    promotetype => $promo_type,
487                    uid => $user->{uid},
488                    tid => $params->{tid} || 2068,
489                    posttype => $post_type,
490                    date => strftime("%F %T", gmtime),
491                    discussion => $discussion_id,
492                   };
493     # TODO: update firehose's timestamp.
494     # create firehose item
495     $rs = $self->create_firehose_item($journal, $introtext, $bodytext, $user);
496     if (!$rs) {
497         $self->rollback;
498         return;
499     }
500     my $globj_id = $rs->{globj_id};
501
502     # set tags
503     my $tags = $self->new_instance_of("Tags");
504     if ($params->{tags_string}) {
505         my @tag_names = split(/\s+/, $params->{tags_string});
506         for my $tag_name (@tag_names) {
507             next if $tags->is_reserved_name($tag_name);
508             my $new_id = $tags->set_tag(globj_id => $globj_id,
509                                         uid => $user->{uid},
510                                         name => $tag_name);
511             if (!$new_id) {
512                 $self->logger->warn("setting tag failed: $tag_name to $globj_id");
513             }
514         }
515     }
516     # add "journal" tag
517     my $new_id = $tags->set_tag(globj_id => $globj_id,
518                                 uid => $user->{uid},
519                                 name => "journal");
520     if (!$new_id) {
521         $self->logger->warn("setting tag failed: journal to $globj_id");
522     }
523
524     # update users_journal:
525     #   - set latest user's journal id to users_journal.jid
526     #   - set latest user's journal date to users_journal.date
527     #   - set number of journals to users_journal.count
528     #
529     $sql = <<"EOSQL";
530 UPDATE users_journal
531   SET jid = ?,
532       count = count + 1,
533       date = ?
534   WHERE uid = ?
535 EOSQL
536     $rs = $dbh->do($sql, undef, $journal->{id}, $journal->{date}, $journal->{uid});
537     if (!$rs) {
538         $self->rollback;
539         $self->set_error("update users_journal failed", $dbh->err);
540         return;
541     }
542
543     #my $achievements = getObject('Slash::Achievements');
544     #if ($achievements) {
545     #    $achievements->setUserAchievement('journal_posted', $user->{uid});
546     #}
547
548     # if url given, create url object. But, current implement has no url form.
549     #if ($form->{url_id}) {
550     #    my $url_id = $form->{url_id};
551     #    my $globjid = $slashdb->getGlobjidCreate('journals', $id);
552     #    $slashdb->addUrlForGlobj($url_id, $globjid);
553     #}
554
555     # transfer tag info
556     #getObject("Slash::Tags")->transferTags($fhitem->{globjid}, $journal_globjid);
557
558     # call hooks, in current srad.jp, call Slash::Journal::promoteJournal.
559     # slashHook('journal_save_success', { id => $id });
560
561     # if promotetype is "publicize", create submission
562     if ($promo_type && $promo_type eq "publicize") {
563         my $submissions = $self->new_instance_of("Submissions");
564         my $sub_id = $submissions->create(user => $user,
565                                           title => $title,
566                                           introtext => $article,
567                                           tid => $topic_id,
568                                          );
569         if (!$sub_id) {
570             $self->rollback;
571             $self->set_error("submission create failed", $submissions->last_errorno);
572             return;
573         }
574     }
575
576     # create messages
577
578     # done
579     $self->commit;
580     return $journal_id;
581 }
582
583 =head2 update(\%params, \%user, \%extra_params, \$opts)
584
585 update a journal.
586
587 =over 4
588
589 =item Parameters
590
591 =over 4
592
593 =item \%params
594
595 parameters
596
597 =item \user
598
599 author's user object
600
601 =item extra_params
602
603 extra parameters
604
605 =item opts
606
607 options
608
609 =back
610
611 =item Return value
612
613 journal id
614
615 =back
616
617 =cut
618
619 sub update {
620     #my ($self, $params, $user, $extra_params, $opts) = @_;
621     my $self = shift;
622     return if $self->check_readonly;
623
624     my $params = {@_};
625     my $user = $params->{user};
626     return if (!$params || !$user);
627
628     my $id = $params->{id} || $params->{journal_id};
629     return if !$id;
630
631     my $dbh = $self->start_transaction;
632
633     my @sql_clauses;
634     my @sql_params;
635     my $updated = 0;
636
637     my $article = $params->{article} || $params->{bodytext} || $params->{body_text};
638     my $description = $params->{description} || $params->{title};
639     my $posttype = $params->{posttype} || $params->{post_type};
640     my $discussion_id = $params->{discussion} || $params->{discussion_id};
641     my $tid = $params->{tid};
642     my $commentstatus = $params->{commentstatus} || $params->{comment_status};
643     my $formatter = $params->{formatter};
644
645     # check formatter
646     if ($formatter) {
647         my $formatter_table = JOURNAL_FORMATTER_TABLE;
648         my $sql = "UPDATE $formatter_table SET formatter = ? WHERE journal_id = ?";
649         my $rs = $dbh->do($sql, undef, $formatter, $id);
650         if (!defined $rs) {
651             $self->rollback;
652             $self->set_error("update $formatter_table failed", $dbh->err);
653             return;
654         }
655         $updated += $rs;
656         # force update journals table's last_update column.
657         push @sql_clauses, "last_update = DEFAULT";
658     }
659     else {
660         my $formatter_table = JOURNAL_FORMATTER_TABLE;
661         my $sql = "DELETE FROM $formatter_table WHERE journal_id = ?";
662         my $rs = $dbh->do($sql, undef, $id);
663         if (!defined $rs) {
664             $self->rollback;
665             $self->set_error("delete from $formatter_table failed", $dbh->err);
666             return;
667         }
668         $updated += 1;
669         # force update journals table's last_update column.
670         push @sql_clauses, "last_update = DEFAULT";
671     }
672
673     # check article body
674     if ($article) {
675         my $introtext = $self->article_to_introtext($article, $posttype || 2, $params);
676
677         my $sql = "UPDATE journals_text SET article = ?, introtext = ? WHERE id = ?";
678         my $rs = $dbh->do($sql, undef, $article, $introtext, $id);
679         if (!defined $rs) {
680             $self->rollback;
681             $self->set_error("update journals_text failed", $dbh->err);
682             return;
683         }
684         $updated += $rs;
685         # force update journals table's last_update column.
686         push @sql_clauses, "last_update = DEFAULT";
687     }
688
689     # check commentstatus
690     if ($commentstatus) {
691         if ($commentstatus ne 'disabled' && !$discussion_id) {
692             # is discussion enabled and not created, create it.
693             my $t = $description;
694             if (!$t) {
695                 my $journal = $self->select(id => $id);
696                 if (!$journal) {
697                     $self->rollback;
698                     $self->set_error("cannot select target journal", $dbh->err);
699                     return;
700                 }
701                 $t = $journal->{title};
702             }
703             my $discussions = $self->new_instance_of('Discussions');
704             my $escaped_nickname = uri_escape($user->{nickname});
705             $discussion_id = $discussions->create(title => $t,
706                                                   url => "http://srad.jp/~$escaped_nickname/journal/$id",
707                                                   topic => $tid || 2068, # tid=2068 is journal.
708                                                   kind => 'journal',
709                                                   commentstatus => $commentstatus,
710                                                   uid => $user->{uid} || $user->{user_id},
711                                                  );
712             if (!$discussion_id) {
713                 $self->rollback;
714                 $self->set_error("discussion create failed", $discussions->last_errorno);
715                 return;
716             }
717             push @sql_clauses, "discussion = ?";
718             push @sql_params, $discussion_id;
719         }
720     }
721
722     # check given params
723     my $tbl = { description => $description,
724                 posttype => $posttype,
725                 tid => $tid,
726                 promotetype => $params->{promotetype} || $params->{promote_type}
727               };
728     for my $k (qw(description posttype tid promotetype)) {
729         if (defined $tbl->{$k}) {
730             push @sql_clauses, "$k = ?";
731             push @sql_params, $tbl->{$k};
732         }
733     }
734
735     # update srcids
736     push @sql_clauses, "srcid_32 = CAST(CONV(?, 16, 10) AS UNSIGNED)";
737     push @sql_clauses, "srcid_24 = CAST(CONV(?, 16, 10) AS UNSIGNED)";
738     push @sql_params, $user->{srcids}{32};
739     push @sql_params, $user->{srcids}{24};
740
741     # do update table
742     my $set_clauses = join(", ", @sql_clauses);
743     my $sql = "UPDATE journals SET $set_clauses WHERE id = ?";
744     push @sql_params, $id;
745
746     my $rs = $dbh->do($sql, undef, @sql_params);
747     if (!defined $rs) {
748         $self->rollback;
749         $self->set_error("update journals_text failed", $dbh->err);
750         return;
751     }
752     $updated += $rs;
753
754     my $q_sql = <<"EOSQL";
755 SELECT id, globjid FROM firehose
756   WHERE type = 'journal'
757     AND srcid = ?
758 EOSQL
759     my $firehose_item = $dbh->selectall_arrayref($q_sql, {Slice => {}}, $id);
760     if (!$firehose_item || !@$firehose_item) {
761         $self->rollback;
762         $self->set_error("cannot get firehose items", $dbh->err);
763         return;
764     }
765     my $firehose_id = $firehose_item->[0]->{id};
766     my $globj_id = $firehose_item->[0]->{globjid};
767     # update firehose_text
768     if ($article || $description) {
769         my @sql_clauses;
770         my @sql_params;
771         if ($article) {
772             my $introtext = $self->article_to_introtext($article, $posttype || 2, $params);
773             my $bodytext = $self->article_to_bodytext($article, $posttype || 2, $params);
774             push @sql_clauses, "introtext = ?";
775             push @sql_params, $introtext;
776             push @sql_clauses, "bodytext = ?";
777             push @sql_params, $introtext . $bodytext; # TODO!
778         }
779         if ($description) {
780             push @sql_clauses, "title = ?";
781             push @sql_params, $description;
782         }
783         my $set_clauses = join(", ", @sql_clauses);
784         my $sql = "UPDATE firehose_text SET $set_clauses WHERE id = ?";
785         push @sql_params, $firehose_id;
786         my $rs = $dbh->do($sql, undef, @sql_params);
787         if (!defined $rs) {
788             $self->rollback;
789             $self->set_error("update journals_text failed", $dbh->err);
790             return;
791         }
792         $updated += $rs;
793     }
794
795     # update tags_string
796     if (defined $params->{tags_string}) {
797         my $tags = $self->new_instance_of("Tags");
798         my @tag_names = split(/\s+/, $params->{tags_string});
799         my @tag_filtered;
800         for my $tag_name (@tag_names) {
801             next if $tags->is_reserved_name($tag_name);
802             push @tag_filtered, $tag_name;
803         }
804         my $rs = $tags->set(globj_id => $globj_id,
805                             uid => $user->{uid},
806                             names => \@tag_filtered);
807         if ($rs) {
808             $updated++;
809         }
810     }
811
812     # if no updated item, done.
813     if (!$updated) {
814         return $id;
815     }
816
817     # update firehose
818     @sql_clauses = ();
819     @sql_params = ();
820     if ($discussion_id) {
821         push @sql_clauses, "discussion = ?";
822         push @sql_params, $discussion_id;
823     }
824     if ($params->{tid}) {
825         push @sql_clauses, "tid = ?";
826         push @sql_params, $tid;
827     }
828     push @sql_clauses, "last_update = NOW()";
829     $set_clauses = join(", ", @sql_clauses);
830     $sql = "UPDATE firehose SET $set_clauses WHERE id = ?";
831     push @sql_params, $firehose_id;
832     $rs = $dbh->do($sql, undef, @sql_params);
833     if (!$rs) {
834         $self->rollback;
835         $self->set_error("update firehose failed", $dbh->err);
836         return;
837     }
838
839     # done
840     $self->commit;
841     return $id;
842 }
843
844 sub hard_delete {
845     my $self = shift;
846     my $params = {@_};
847     my $journal_id = $params->{journal_id};
848
849     return if !$journal_id;
850     my $error;
851
852     my $dbh = $self->start_transaction;
853
854     for my $table (qw(journals journals_text)) {
855         my $sql = "DELETE FROM $table WHERE id = ?";
856         my $rs = $dbh->do($sql, undef, $journal_id);
857         if (!defined $rs || $rs == 0) {
858             Mojo::Log->new->warn("DELETE FROM $table failed. id is $journal_id.");
859             $error = 1;
860         }
861     }
862     # delete formatter_table
863     my $formatter_table = JOURNAL_FORMATTER_TABLE;
864     my $rs = $dbh->do("DELETE FROM $formatter_table WHERE journal_id = ?", undef, $journal_id);
865     if (!defined $rs) {
866         Mojo::Log->new->warn("DELETE FROM $formatter_table failed. id is $journal_id.");
867         $error = 1;
868     }
869
870     # delete from firehose
871     my $firehose = $self->new_instance_of("Firehose");
872     $firehose->hard_delete("journal", $journal_id);
873
874     $self->commit;
875     return !$error;
876
877
878 }
879
880
881 =pod
882
883 HTML形式:(HTML)
884 利用可能なHTMLタグを使って、投稿の体裁を整えることができます。
885
886 テキスト形式(HTML OK!):(EXTRANS)
887 HTMLタグを使用できます。HTML形式との違いは、改行のポイントで改行文字として<BR>が
888 自動的に挿入され、さらに空白文字は多少知的に改行なしのスペースに変わります。
889
890 ホントのテキスト形式:(PLAINTEXT)
891 &、 <、>が除外され、HTMLタグの利用ができないホントのテキスト形式です。
892
893 コード:(CODE)
894 HTMLを使えないテキスト形式ですが、さらに等幅フォントが使われ、適当にインデントもされます。
895
896 =cut
897
898 sub article_to_introtext {
899     my ($self, $article, $posttype, $options) = @_;
900     my $html = $self->article_to_fulltext($article, $posttype, $options);
901     return if !$html;
902     my $intro_text = $self->_extract_introtext($html, $posttype, $options);
903     $intro_text =~ s/\s+$//g;
904
905     if ($intro_text) {
906         $intro_text = Newslash::Util::TextFormatter::tidy_html($intro_text);
907     }
908     return $intro_text;
909 }
910
911 sub _extract_introtext {
912     my ($self, $html, $posttype, $options) = @_;
913     my $min_chars = 50;
914     my $max_chars = 500;
915     my $intro_text;
916     return if !$html;
917
918     if (length $html < $min_chars) {
919         return $html;
920     }
921
922     my $linebreak = qr{(?:
923                            <br>\s*<br> |
924                            </?p> |
925                            </(?:
926                                div | (?:block)?quote | [oud]l
927                            )>
928                        )}x;
929     $intro_text = $1 if $html =~ m/^(.{$min_chars,$max_chars})?$linebreak/s;
930     $intro_text =~ s/\s+$//g;
931     if (!$intro_text) {
932         return Newslash::Util::TextFormatter::contextual_strip($html, $max_chars, $min_chars);
933     }
934     return $intro_text;
935 }
936
937 sub article_to_bodytext {
938     my ($self, $article, $posttype, $options) = @_;
939     my $html = $self->article_to_fulltext($article, $posttype, $options);
940     my $min_chars = 50;
941     if (length $html < $min_chars) {
942         return "";
943     }
944     my $intro_text = $self->_extract_introtext($html, $posttype, $options);
945     my $body_text = substr $html, length($intro_text);
946     $body_text =~ s/\s+$//g;
947     if ($body_text) {
948         $body_text = Newslash::Util::TextFormatter::tidy_html($body_text);
949     }
950     return $body_text;
951 }
952
953 sub article_to_fulltext {
954     my ($self, $article, $posttype, $options) = @_;
955     $options ||= {};
956
957     my $html;
958     my $allowed_tags = $options->{allowed_tags} || $self->options(Editor => "allowed_tags") || DEFAULT_ALLOWED_TAGS;
959
960     my $formatter = $options->{formatter};
961     if ($formatter) {
962         if ($formatter eq "modern") {
963             $html = Newslash::Util::TextFormatter::clean_html($allowed_tags, $article);
964             return $html;
965         }
966     }
967
968     # compatibility mode
969     # posttype: 1: PLAINTEXT, 2: HTML, 3: EXTRANS, 4: CODE, 77: FULLHTML
970     $html = Newslash::Util::TextFormatter::strip_by_mode($article, $posttype, allowed_tags => $allowed_tags);
971     return $html;
972 }
973
974 sub create_firehose_item {
975     my ($self, $journal, $introtext, $bodytext, $user) = @_;
976     return if $self->check_readonly;
977     return if !$journal;
978
979     # create globjs
980     my $globjs = $self->new_instance_of('Globjs');
981     my $globj_id = $globjs->create(journals => $journal->{id});
982     if (!$globj_id) {
983         $self->set_error($globjs->last_error, $globjs->last_errorno);
984         return;
985     }
986
987     # create item
988     # TODO: sprite_info column don't have default value...
989     my $sql = <<"EOSQL";
990 INSERT INTO firehose
991     (uid, globjid, discussion, type, createtime, popularity, editorpop, public, attention_needed, primaryskid, tid, srcid,
992      url_id, email, emaildomain, name, ipid, subnetid, sprite_info)
993   VALUES
994     (?,  ?,        ?,          ?,    ?,          ?,          ?,         ?,      ?,                ?,           ?,   ?,
995      ?,      ?,     ?,           ?,    ?,    ?,        "")
996 EOSQL
997     my $dbh = $self->connect_db;
998
999     my $promo_type = $journal->{promotype} || $journal->{promo_type};
1000
1001     my $publicize  = $promo_type eq 'publicize';
1002     my $publish    = $promo_type eq 'publish';
1003     my $color_lvl  = $publicize ? 5 : $publish ? 6 : 7; # post == 7
1004     my $editor_lvl = $publicize ? 5 : $publish ? 6 : 8; # post == 8
1005
1006     my $mods = $self->new_instance_of('Moderations');
1007     my $popularity = $mods->getEntryPopularityForColorLevel($color_lvl);
1008     my $editorpop  = $mods->getEntryPopularityForColorLevel($editor_lvl);
1009
1010     my $primary_skid = 1; # journal's skid is 1 (mainpage)
1011
1012     my $rs = $dbh->do($sql, undef,
1013                       $journal->{uid} || $journal->{user_id}, #uid
1014                       $globj_id, #globjid
1015                       $journal->{discussion} || 0,
1016                       "journal", #type
1017                       $journal->{date} || $journal->{create_time}, #createtime
1018                       $popularity, #popularity
1019                       $editorpop, #editorpop
1020                       "yes", #public
1021                       "yes", #attention_needed
1022                       $primary_skid, #primaryskid
1023                       $journal->{tid} || $journal->{topic_id}, #tid
1024                       $journal->{id}, #srcid
1025                       0, #url_id
1026                       "", #email
1027                       "", #emaildomain
1028                       "", #name
1029                       $user->{ipid} || $user->{ip_id}, #ipid
1030                       $user->{subnetid} || $user->{subnet_id} #subnetid
1031                      );
1032     if (!$rs) {
1033         $self->set_error("insert into firehose failed", $dbh->err);
1034         $self->disconnect_db;
1035         return;
1036     }
1037     my $fh_id = $dbh->last_insert_id(undef, undef, undef, undef);
1038
1039     # insert firehose_text
1040     $sql = <<"EOSQL";
1041 INSERT INTO firehose_text
1042     (title, introtext, bodytext, media)
1043   VALUES
1044     (?,     ?,         ?,        NULL)
1045 EOSQL
1046     $rs = $dbh->do($sql, undef,
1047                    $journal->{description},
1048                    $introtext,
1049                    $introtext . $bodytext); # TODO!
1050     if (!$rs) {
1051         $self->set_error("insert into firehose_text failed", $dbh->err);
1052         $self->disconnect_db;
1053         return;
1054     }
1055
1056     # create tag
1057     my $tags_upbote_tagname = "nod";
1058     my $tags = $self->new_instance_of("Tags");
1059     my $tag_id = $tags->set_tag(uid => $journal->{uid} || $journal->{user_id},
1060                                 name => $tags_upbote_tagname,
1061                                 globj_id => $globj_id,
1062                                 private => 1);
1063     if (!$tag_id) {
1064         $self->set_error("insert into tags failed", $tags->last_errorno);
1065         $self->disconnect_db;
1066         return;
1067     }
1068
1069     $self->disconnect_db();
1070     return { fh_id => $fh_id,
1071              globj_id => $globj_id };
1072 }
1073
1074 1;