OSDN Git Service

* Model::Cowrapper: rename build_orderby_clause() to build_order_by_clause() and...
[newslash/newslash.git] / src / newslash_web / lib / Newslash / Model / Stories.pm
1 package Newslash::Model::Stories;
2 use Newslash::Model::Base -base;
3
4 use Newslash::Model::SlashDB;
5
6 use DateTime::Format::MySQL;
7
8 use Data::Dumper;
9 use DateTime;
10 use Newslash::Util::Formatters qw(datetime_to_string);
11
12
13 sub _calculate_time_range {
14     my ($self, $params) = @_;
15     my $year = $params->{year};
16     my $month = $params->{month};
17     my $day = $params->{day};
18     my $offset_sec = $params->{offset_sec};
19
20     my $dt_from = DateTime->new(year => $year,
21                            month => $month,
22                            day => $day);
23     my $dt_to = DateTime->new(year => $year,
24                            month => $month,
25                               day => $day);
26     if ($offset_sec) {
27         $dt_from->add(seconds => -$offset_sec);
28         $dt_to->add(seconds => -$offset_sec);
29     }
30     $dt_to->add(days => 1);
31     return (DateTime::Format::MySQL->format_datetime($dt_from),
32             DateTime::Format::MySQL->format_datetime($dt_to));
33     #return ($dt_from->strftime('%F'), $dt_to->strftime('%F'));
34 }
35
36 sub count {
37     my $self = shift;
38     my $params = {@_};
39
40     my $target = "day";
41     return if !$params->{year};
42     $target = "month" if !$params->{day};
43     $target = "year" if !$params->{month};
44
45     my ($year, $month, $day) = ($params->{year}, $params->{month}, $params->{day});
46     $year = 1 if (!$year || $year !~ m/^[0-9]{4}$/);
47     $month = 1 if (!$month || $month !~ m/^(1[0-2]|0?[0-9])$/);
48     $day = 1 if (!$day || $day !~ m/^(3[0-1]|[1-2][0-9]|0?[0-9])$/);
49
50     my $offset = $params->{offset_sec} || 0;
51     $offset = 0 if $offset !~ m/^[+-]?[0-9]+$/;
52
53     my $dt = DateTime->new(year => $year,
54                              month => $month,
55                              day => $day);
56     $dt->add(seconds => -$offset);
57     my $dt_string = DateTime::Format::MySQL->format_datetime($dt);
58
59     # create end of term datetime
60     # why use "DATE_ADD(?, INTERVAL 1 MONTH)" ? bacause, this function add simply 30 days...
61     my $dt_end = DateTime->new(year => $year,
62                                month => $month,
63                                day => $day);
64     if ($target eq "month") {
65         $dt_end->add(months => 1);
66     }
67     elsif ($target eq "year") {
68         $dt_end->add(years => 1);
69     }
70     $dt->add(seconds => -$offset);
71     my $dt_end_string = DateTime::Format::MySQL->format_datetime($dt_end);
72
73     # we must consider timezone offset, so use relative day/month.
74
75     my $sql;
76     my @attrs;
77     if ($target eq "day") {
78         # `stories` table not contain display/non-display flag,
79         # so use firehose.
80         $sql = <<"EOSQL";
81 SELECT COUNT(stories.stoid) AS count FROM stories
82   LEFT JOIN firehose 
83     ON (stories.stoid = firehose.srcid AND firehose.type = "story")
84   WHERE firehose.public != "no"
85     AND stories.time >= ?
86     AND stories.time < DATE_ADD(?, INTERVAL 1 DAY)
87 EOSQL
88         push @attrs, $dt_string, $dt_string;
89     }
90     elsif ($target eq "month") {
91         $sql = <<"EOSQL";
92 SELECT TIMESTAMPDIFF(DAY, ?, stories.time) AS day,
93        COUNT(stories.stoid) AS count
94   FROM stories
95   LEFT JOIN firehose 
96     ON (stories.stoid = firehose.srcid AND firehose.type = "story")
97   WHERE firehose.public != "no"
98     AND stories.time >= ?
99     AND stories.time < ?
100   GROUP BY TIMESTAMPDIFF(DAY, ?, stories.time)
101   ORDER BY day ASC
102 EOSQL
103         push @attrs, $dt_string, $dt_string, $dt_end_string, $dt_string;
104     }
105     elsif ($target eq "year") {
106         $sql = <<"EOSQL";
107 SELECT TIMESTAMPDIFF(MONTH, ?, stories.time) AS month,
108        COUNT(stories.stoid) AS count
109   FROM stories
110   LEFT JOIN firehose 
111     ON (stories.stoid = firehose.srcid AND firehose.type = "story")
112   WHERE firehose.public != "no"
113     AND stories.time >= ?
114     AND stories.time < ?
115   GROUP BY TIMESTAMPDIFF(MONTH, ?, stories.time)
116   ORDER BY month ASC
117 EOSQL
118         push @attrs, $dt_string, $dt_string, $dt_end_string, $dt_string;
119     }
120     my $dbh = $self->connect_db;
121     my $sth = $dbh->prepare($sql);
122     $sth->execute(@attrs);
123     my $rs = $sth->fetchall_arrayref({});
124
125     if (!$rs) {
126         $self->disconnect_db();
127         return;
128     }
129     $self->disconnect_db();
130
131     #warn $sql;
132     #warn $dt_string;
133     #warn $dt_end_string;
134     #warn (Dumper($rs));
135     my $hash = {};
136     my $key;
137     if ($target eq "day") {
138         return $rs->[0]->{count};
139     }
140     elsif ($target eq "month") {
141         $key = "day";
142     }
143     elsif ($target eq "year") {
144         $key = "month";
145     }
146     else {
147         return;
148     }
149
150     for my $counts (@$rs) {
151         # day / month is differential from base datetime, so add 1
152         $hash->{$counts->{$key} + 1} = $counts->{count};
153     }
154     return $hash;
155 }
156
157 sub latest {
158     my $self = shift;
159     my $options = {@_};
160     my $limit = $options->{limit} || 10;
161     my $show_future = $options->{show_future} || 0;
162     my $show_nonpublic = $options->{show_nonpublic} || 0;
163     return $self->select(order_by => {time => 'desc'},
164                          limit => $limit,
165                          show_nonpublic => $show_nonpublic,
166                          show_future => $show_future
167                         );
168 }
169
170 #========================================================================
171
172 =head2 select($query_type, $value)
173
174 get a story.
175
176 =over 4
177
178 =item Parameters
179
180 =over 4
181
182 =item $query_type
183
184 query key, "sid" or "stoid"
185
186 =item $value
187
188 value for query
189
190 =back
191
192 =item Return value
193
194 HASH of story contents
195
196 =back
197
198 =cut
199
200 sub select {
201     my $self = shift;
202     my $params = {@_};
203     my $query_type;
204     my $value;
205     my $return_single = 0;
206
207     # check query type
208     for my $k (qw(sid stoid)) {
209         if ($params->{$k}) {
210             $query_type = $k;
211             $value = $params->{$k};
212             $return_single = 1;
213         }
214     }
215     for my $k (qw(submitter)) {
216         if ($params->{$k}) {
217             $query_type = $k;
218             $value = $params->{$k};
219         }
220     }
221
222     # build WHERE clause
223     my @where_clauses;
224     my @query_param;
225
226     if ($query_type && $value) {
227         push @where_clauses, "stories.$query_type = ?";
228         push @query_param, $value;
229     }
230
231
232     # show future story?
233     if (!$params->{show_future}) {
234         push @where_clauses, "stories.time <= NOW()";
235     }
236
237     # show non-public story?
238     if (!$params->{show_nonpublic}) {
239         push @where_clauses, "firehose.public != 'no'";
240     }
241
242     # year, month, day
243     if ($params->{year} && $params->{month} && $params->{day}) {
244         my ($time_from, $time_to) = $self->_calculate_time_range($params);
245         push @where_clauses, "stories.time > ? AND stories.time < ?";
246         push @query_param, $time_from, $time_to;
247     }
248
249     # until
250     if ($params->{until}) {
251         push @where_clauses, "stories.time <= ?";
252         push @query_param, $params->{until};
253     }
254
255     if ($params->{since}) {
256         push @where_clauses, "stories.time >= ?";
257         push @query_param, $params->{since};
258     }
259
260     # target period
261     my $date_limit = "";
262     my @units = qw(YEAR MONTH WEEK DAY HOUR MINUTE);
263     my $unit;
264     for my $term (qw(years months weeks days hours minutes)) {
265         $unit = shift @units;
266         if (defined $params->{$term}) {
267             $date_limit = $params->{$term};
268             last;
269         }
270     }
271     if (length $date_limit) {
272         push @where_clauses, "stories.time > NOW() - INTERVAL ? $unit";
273         push @query_param, $date_limit;
274     }
275
276     # build ORDER BY clause
277     # my $order_clause = "";
278     # my @safe_params = qw(commentcount hits time);
279     # if (defined $params->{order_by}) {
280     #     # check order_by's value
281     #     my $k = $params->{order_by};
282     #     if (grep {$_ eq $k} @safe_params) {
283     #         my $order = "DESC";
284     #         if (defined $params->{order} && $params->{order} eq "ASC") {
285     #             $order = "ASC";
286     #         }
287     #         $order_clause = "ORDER BY $k $order";
288     #     }
289     # }
290     my ($order_clause, $order_values) = $self->build_order_by_clause(keys => [qw(commentcount hits time)], params => $params);
291
292     # build LIMIT clause
293     my $limit_clause = "";
294     if (defined $params->{limit}) {
295         $limit_clause = "LIMIT ?";
296         push @query_param, $params->{limit};
297     }
298
299     # do SELECT
300     my $where_clause = "";
301     if (@where_clauses) {
302         $where_clause = "WHERE " . join("\n AND ", @where_clauses) . "\n";
303     }
304     my $dbh = $self->connect_db;
305     my $sql = <<"EOSQL";
306 SELECT stories.*, story_text.*, users.nickname as author, firehose.public
307   FROM stories
308     LEFT JOIN story_text ON stories.stoid = story_text.stoid
309     LEFT JOIN users ON stories.uid = users.uid
310     LEFT JOIN firehose 
311       ON (stories.stoid = firehose.srcid AND firehose.type = "story")
312     $where_clause
313     $order_clause
314     $limit_clause
315 EOSQL
316
317     #warn($sql);
318     #warn(Dumper(@query_param));
319
320     my $sth = $dbh->prepare($sql);
321     $sth->execute(@query_param);
322     my $stories = $sth->fetchall_arrayref({});
323
324     if (!$stories) {
325         $self->disconnect_db();
326         return;
327     }
328     if (@$stories == 0) {
329         $self->disconnect_db();
330         return $return_single ? undef : [];
331     }
332
333     # get tags
334     $sql = <<"EOSQL";
335 SELECT tags.*, tagnames.tagname, target.stoid
336   FROM (SELECT stories.stoid FROM stories 
337         LEFT JOIN firehose ON (stories.stoid = firehose.srcid AND firehose.type = "story")
338         $where_clause $order_clause $limit_clause) AS target
339     LEFT JOIN globjs
340       ON target.stoid = globjs.target_id
341     LEFT JOIN tags
342       ON globjs.globjid = tags.globjid
343     LEFT JOIN tagnames
344       ON tags.tagnameid = tagnames.tagnameid
345     WHERE globjs.gtid = 1
346 EOSQL
347
348     $sth = $dbh->prepare($sql);
349     $sth->execute(@query_param);
350     my $tags_table = $sth->fetchall_arrayref({});
351
352     # get topics
353     $sql = <<"EOSQL";
354 SELECT story_topics_rendered.*, story_topics_chosen.weight, topics.*
355   FROM (SELECT stories.stoid FROM stories
356         LEFT JOIN firehose ON (stories.stoid = firehose.srcid AND firehose.type = "story")
357         $where_clause $order_clause $limit_clause) AS target
358     LEFT JOIN story_topics_rendered
359       ON target.stoid = story_topics_rendered.stoid
360     LEFT JOIN story_topics_chosen 
361       ON story_topics_rendered.stoid = story_topics_chosen.stoid
362         AND story_topics_rendered.tid = story_topics_chosen.tid
363     LEFT JOIN topics
364       ON story_topics_rendered.tid = topics.tid
365 EOSQL
366
367     $sth = $dbh->prepare($sql);
368     $sth->execute(@query_param);
369     my $topics_table = $sth->fetchall_arrayref({});
370     $self->disconnect_db();
371
372
373     my $tags = {};
374     for my $tag (@$tags_table) {
375         my $stoid = $tag->{stoid};
376         if (!$tags->{$stoid}) {
377             $tags->{$stoid} = [];
378         }
379         push @{$tags->{$stoid}}, $tag;
380     }
381
382     my $topics = {};
383     for my $topic (@$topics_table) {
384         my $stoid = $topic->{stoid};
385         if (!$topics->{$stoid}) {
386             $topics->{$stoid} = [];
387         }
388         push @{$topics->{$stoid}}, $topic;
389     }
390
391     for my $story (@$stories) {
392         my $stoid = $story->{stoid};
393         $story->{tags} = $tags->{$stoid} if $tags->{$stoid};
394         $story->{topics} = $topics->{$stoid} if $topics->{$stoid};
395         $self->_generalize($story, $params);
396     }
397
398     return $stories->[0] if $return_single;
399     return $stories;
400 }
401
402 =head2 update
403
404 this implementation uses old slash's updateStory($sid, $data),
405 $sid is takable sid or stoid.
406
407 =cut
408
409 sub update {
410     my ($self, $params, $user, $extra_params, $opts) = @_;
411     $opts ||= {};
412     $opts->{update} = 1;
413     if (!$params->{stoid}) {
414         $self->set_error("stoid not given");
415         return;
416     }
417     return $self->create($params, $user, $extra_params, $opts);
418 }
419
420
421 =head2 create(\%params, $uid)
422
423 create a story.
424
425 =over 4
426
427 =item Parameters
428
429 =over 4
430
431 =item \%params
432
433 parameters
434
435 $params->{fhid}
436 $params->{subid}
437
438 =item $uid
439
440 author's uid
441
442 =back
443
444 =item Return value
445
446 stoid
447
448 =back
449
450 =cut
451
452 sub create {
453     my ($self, $params, $user, $extra_params, $opts) = @_;
454     return if $self->check_readonly;
455     $opts ||= {};
456
457     # check parameters
458     my $msg = "";
459     $msg = "no title" if !$params->{title};
460     $msg = "no introtext" if !$params->{introtext};
461     $msg = "no uid" if !$params->{uid};
462     $msg = "no topics" if !defined $params->{topics_chosen};
463     $msg = "invalid user" if ref($user) ne 'HASH';
464
465     if (length($params->{title}) > $self->{options}->{Story}->{title_max_byte}) {
466         $msg = "title too long. max: $self->{options}->{Story}->{title_max_byte} bytes";
467     }
468
469     $params->{commentstatus} ||= "enabled";
470     if (!grep /\A$params->{commentstatus}\z/, qw(disabled enabled friends_only friends_fof_only no_foe no_foe_eof logged_in)) {
471         $msg = "invalid commentstatus";
472     }
473
474     # check timestamp. use ISO8601 style timestamp like: 2006-08-14T02:34:56-0600
475     if ($params->{time}) {
476         my $rex_timestamp = qr/
477                                   ^(\d+)-(\d+)-(\d+)\D+(\d+):(\d+):(\d+(?:\.\d+)?)   # datetime
478                                   (?:Z|([+-])(\d+):(\d+))?$                          # tz
479                               /xi;
480         if ($params->{time} =~ $rex_timestamp) {
481             $params->{time} = "$1-$2-$3 $4:$5:$6";
482         }
483     }
484
485     # check parameters finish
486     if (length($msg) > 0) {
487         $self->set_error($msg, -1);
488         return;
489     }
490
491     $params->{neverdisplay} ||= 0;
492
493     # createStory deletes topics_chosen, so save before.
494     my $topics_chosen = $params->{topics_chosen};
495
496     my $slash_db = Newslash::Model::SlashDB->new($self->{options});
497     my ($sid, $stoid);
498     if ($opts->{update}) {
499         my $rs = $slash_db->updateStory($params->{stoid}, $params);
500         if (!$rs) {
501             return;
502         }
503         $sid = $params->{sid};
504         $stoid = $params->{stoid};
505     }
506     else {
507         ($sid, $stoid) = $slash_db->createStory($params);
508     }
509
510     my $globjs = $self->new_instance_of("Newslash::Model::Globjs");
511     my $globj_id = $globjs->getGlobjidFromTargetIfExists("stories", $params->{stoid});
512     if ($globj_id) {
513         # set tags
514         use Newslash::Model::Tags;
515         my $tags = $self->new_instance_of("Newslash::Model::Tags");
516         for my $tid (keys %$topics_chosen) {
517             my $ret = $tags->set_tag(uid => $user->{uid},
518                                      tagname_id => $tid,
519                                      globj_id => $globj_id,
520                                      private => 0,
521                                     );
522             #warn "set_tag fault..." if !$ret
523         }
524     }
525     #return $sid;
526     return $stoid;
527 }
528
529 sub createSid {
530     my ($self, $bogus_sid) = @_;
531     # yes, this format is correct, don't change it :-)
532     my $sidformat = '%02d/%02d/%02d/%02d%0d2%02d';
533     # Create a sid based on the current time.
534     my @lt;
535     my $start_time = time;
536     if ($bogus_sid) {
537         # If we were called being told that there's at
538         # least one sid that is invalid (already taken),
539         # then look backwards in time until we find it,
540         # then go one second further.
541         my $loops = 1000;
542         while (--$loops) {
543             $start_time--;
544             @lt = localtime($start_time);
545             $lt[5] %= 100; $lt[4]++; # year and month
546             last if $bogus_sid eq sprintf($sidformat, @lt[reverse 0..5]);
547         }
548         if ($loops) {
549             # Found the bogus sid by looking
550             # backwards.  Go one second further.
551             $start_time--;
552         } else {
553             # Something's wrong.  Skip ahead in
554             # time instead of back (not sure what
555             # else to do).
556             $start_time = time + 1;
557         }
558     }
559     @lt = localtime($start_time);
560     $lt[5] %= 100; $lt[4]++; # year and month
561     return sprintf($sidformat, @lt[reverse 0..5]);
562 }
563
564
565 =head2 get_histories
566
567 =cut
568
569 sub get_histories {
570 }
571
572 =head2 get_related_items($stoid)
573
574 get related links.
575
576 =over 4
577
578 =item Parameters
579
580 =over 4
581
582 =item $stoid
583
584 story id
585
586 =back
587
588 =item Return value
589
590 ARRAY of related links
591
592 =back
593
594 =cut
595
596 sub get_related_items {
597     my $self = shift;
598     my $params = {@_};
599     my $stoid = $params->{stoid};
600     return if !$stoid;
601
602     my $dbh = $self->connect_db;
603
604     my $sql = <<"EOSQL";
605 SELECT related.*, 
606        story_text.title as title2,
607        firehose.srcid,
608        topics.*
609   FROM (
610     SELECT * FROM related_stories
611       WHERE stoid = ?
612       ORDER BY ordernum ASC
613     ) AS related
614   LEFT JOIN story_text ON story_text.stoid = related.rel_stoid
615   LEFT JOIN firehose ON firehose.id = related.fhid
616   LEFT JOIN stories ON stories.stoid = related.rel_stoid
617   LEFT JOIN topics ON topics.tid = stories.tid
618 EOSQL
619
620     my $sth = $dbh->prepare($sql);
621     $sth->execute($stoid);
622     my $related = $sth->fetchall_arrayref({});
623     $self->disconnect_db();
624
625     for my $r (@$related) {
626         $r->{title} = $r->{title2} unless $r->{title};
627         if ($r->{rel_sid}) {
628             $r->{type} = "story";
629             $r->{key_id} = $r->{rel_sid};
630         } else {
631             $r->{type} = "submission";
632             $r->{key_id} = $r->{srcid};
633         }
634         $r->{primary_topic} = {};
635         $r->{primary_topic}->{tid} = $r->{tid};
636         for my $k (qw{keyword textname series image width height
637                       submittable searchable storypickable usesprite}) {
638             next if !$r->{$k};
639             $r->{primary_topic}->{$k} = $r->{$k};
640             delete $r->{$k};
641         }
642     }
643
644     return $related;
645 }
646
647 =head2 parameters($stoid)
648
649 get story parameters.
650
651 =over 4
652
653 =item Parameters
654
655 =over 4
656
657 =item $stoid
658
659 story id
660
661 =back
662
663 =item Return value
664
665 HASH of parameters
666
667 =back
668
669 =cut
670
671 sub parameters {
672     my ($self, $stoid) = @_;
673
674     my $dbh = $self->connect_db;
675
676     my $sql = <<"EOSQL";
677 SELECT * FROM story_param WHERE stoid = ?
678 EOSQL
679
680     my $sth = $dbh->prepare($sql);
681     $sth->execute($stoid);
682     my $params = $sth->fetchall_hashref('name');
683     $sth->finish;
684
685     # get next/prev story info
686     if ($params->{next_stoid} || $params->{prev_stoid}) {
687         $sql = <<"EOSQL";
688 SELECT stories.*, story_text.* FROM stories JOIN story_text ON stories.stoid = story_text.stoid WHERE stories.stoid = ? OR stories.stoid = ?
689 EOSQL
690         $sth = $dbh->prepare($sql);
691         my $next = $params->{next_stoid}->{value} || 0;
692         my $prev = $params->{prev_stoid}->{value} || 0;
693         $sth->execute($next, $prev);
694         my $sids = $sth->fetchall_hashref('stoid');
695         $params->{next_stoid}->{story} = $sids->{$next} if $sids->{$next};
696         $params->{prev_stoid}->{story} = $sids->{$prev} if $sids->{$prev};
697         $sth->finish;
698     }
699
700     $self->disconnect_db();
701     return $params;
702 }
703
704 #========================================================================
705
706 =head2 set_dirty($key, $id)
707
708 set writestatus dirty for the story.
709
710 =over 4
711
712 =item Parameters
713
714 =over 4
715
716 =item $key
717
718 'stoid'
719
720 =item $id
721
722 id of the story
723
724 =back
725
726 =item Return value
727
728 1/0
729
730 =back
731
732 =cut
733
734 sub set_dirty {
735     my ($self, $key, $id) = @_;
736     return if $self->check_readonly;
737
738     my $stoid;
739     if ($key eq 'stoid') {
740         $stoid = $id;
741     }
742     else {
743         return;
744     }
745
746     my $dbh = $self->connect_db;
747     my $sql = <<"EOSQL";
748 INSERT INTO story_dirty (stoid) VALUES (?)
749 EOSQL
750
751     my $rs = $dbh->do($sql, undef, $stoid);
752     if (!$rs) {
753         return;
754     }
755     return 1;
756 }
757
758 sub _generalize {
759     my ($self, $story, $params) = @_;
760     $params ||= {};
761
762     $story->{content_type} = "story";
763
764     #my $max_weight = 0;
765     for my $t (@{$story->{topics}}) {
766         if ($t->{tid} && $t->{tid} == $story->{tid}) {
767             $story->{primary_topic} = $t;
768         }
769         #if ($t->{weight} && $t->{weight} > $max_weight) {
770         #    $max_weight = $t->{weight};
771         #}
772     }
773
774     $story->{time_string} = datetime_to_string($story->{time});
775     $story->{createtime} = $story->{time};
776     $story->{discussion_id} = $story->{discussion};
777     $story->{id} = $story->{stoid};
778
779     # no public flag given, public is 'yes'
780     $story->{public} = 'yes' if !$story->{public};
781
782     # convert timestamp format
783     if (lc($params->{datetime_format}) eq "javascript") {
784         for my $k (qw{time day_published archive_last_update stuckendtime}) {
785             my $dt = DateTime::Format::MySQL->parse_datetime($story->{$k});
786             $story->{$k} = $dt->strftime('%FT%T');
787         }
788     }
789 }
790
791 # delete story from database
792 # this method is for test purpose only.
793 sub hard_delete {
794     my $self = shift;
795     return if $self->check_readonly;
796     my $params = {@_};
797
798     my $stoid = $params->{stoid};
799     return if !$stoid;
800
801     my $error = 0;
802     my $sql;
803
804     my $dbh = $self->connect_db({AutoCommit => 0,});
805     for my $table (qw(stories story_param story_text story_topics_chosen story_topics_rendered)) {
806         my $sql = "DELETE FROM $table WHERE stoid = ?";
807         my $rs = $dbh->do($sql, undef, $stoid);
808         if (!defined $rs || $rs == 0) {
809             Mojo::Log->new->warn("DELETE FROM $table failed. stoid is $stoid.");
810             $error = 1;
811         }
812     }
813     $dbh->commit;
814     $self->disconnect_db;
815
816     return !$error;
817
818     # delete firehose item
819     # delete firehose_text item
820     # delete firehose_topics_rendererd item
821     # delete globjs
822     # delete tags
823
824     return;
825 }
826
827 1;