1 package Newslash::Model::Metamoderations;
2 use Newslash::Model::Base -base;
5 use List::Util qw(sum any);
13 agreement_threshold_ratio => 0.66,
32 token_unfair_params => {
40 token_fair_params => {
51 my ($self, $user, $moderation_id, $value) = @_;
52 return if $self->check_readonly;
53 my $moderations = $self->new_instance_of("Moderations");
54 my $moderation = $moderations->select(id => $moderation_id);
56 $self->set_error("invalid moderation id");
61 if (!$moderations->is_metamoderatable($user, $moderation)
63 && !$user->{editor} ) {
64 $self->set_error("you cannot metamoderate the moderation");
68 # when the user already metamoderated, return.
69 my $m2check = $self->select(moderation_id => $moderation->{id},
71 if (!$m2check || @$m2check != 0) {
72 $self->set_error("you have already metamoderated");
76 my $metamods = $self->select(moderation_id => $moderation->{id});
77 if (!defined $metamods) {
78 $self->set_error("metamoderations retrive error");
81 my $mods = $moderations->select(cid => $moderation->{cid});
83 $self->set_error("moderations retrive error");
87 # calc unchanged moderation's fixed scores
88 my $unchanged_scores = [];
89 for my $mod (@$mods) {
90 next if $mod->{id} == $moderation->{id};
91 my $mms = $self->select(id => $mod->{id});
92 push @$unchanged_scores, $self->_get_fixed_scores($mod, $mms);
95 points => sum map { $a->{points} } $unchanged_scores,
96 karma => sum map { $a->{karma} } $unchanged_scores,
99 # calc changed moderation's old score
100 my $old_score = $self->_get_fixed_scores($moderation, $metamods);
102 points => floor($base_scores->{points} + $old_score->{points}),
103 karma => floor($base_scores->{karma} + $old_score->{karma}),
105 my $old_result = $self->_get_votes_result($moderation, $metamods);
106 my $old_moderator_scores = $self->_get_fixed_moderator_scores($moderation, $old_result);
108 $self->start_transaction;
111 my $m2id = $self->_create_metamodlog($user, $moderation, $value);
114 $self->set_error("metamodlog create failed");
118 # calc changed moderation's new score
122 mmid => $moderation->{id},
125 push @$metamods, $new_metamod;
126 my $new_score = $self->_get_fixed_scores($moderation, $metamods);
128 points => floor($base_scores->{points} + $new_score->{points}),
129 karma => floor($base_scores->{karma} + $new_score->{karma}),
132 points => $new_total->{points} - $old_total->{points},
133 karma => $new_total->{karma} - $old_total->{karma},
136 # update comment's point
137 my $comments = $self->new_instance_of("Comments");
138 my $rs = $comments->update(cid => $moderation->{cid},
139 points => { add => $delta->{points} });
142 $self->set_error("comments update failed");
146 # update comment author's karma
147 my $users = $self->new_instance_of("Users");
148 $rs = $users->update_info(uid => $moderation->{cuid},
149 karma => { add => $delta->{karma} });
152 $self->set_error("users update failed");
156 # update moderator's info
157 my $new_result = $self->_get_votes_result($moderation, $metamods);
158 my $new_moderator_scores = $self->_get_fixed_moderator_scores($moderation, $old_result);
160 for my $k (qw(karma tokens up_fair down_fair up_unfair down_unfair)) {
161 $u_delta->{$k} = $old_result->{$k} - $new_result->{$k};
163 $users->update_info(uid => $moderation->{uid},
164 tokens => { add => $u_delta->{tokens} },
165 up_fair => { add => $u_delta->{up_fair} },
166 down_fair => { add => $u_delta->{down_fair} },
167 up_unfair => { add => $u_delta->{up_unfair} },
168 down_unfair => { add => $u_delta->{down_unfair} });
170 # update metamoderator's info
172 $new_params->{m2_fair} = 1 if $value > 0;
173 $new_params->{m2_unfair} = 1 if $value < 0;
174 if (($value > 0) && ($moderation->{val} > 0)) {
175 $new_params->{m2voted_up_fair} = 1;
177 if (($value > 0) && ($moderation->{val} < 0)) {
178 $new_params->{m2voted_down_fair} = 1;
180 if (($value < 0) && ($moderation->{val} > 0)) {
181 $new_params->{m2voted_up_unfair} = 1;
183 if (($value < 0) && ($moderation->{val} < 0)) {
184 $new_params->{m2voted_down_unfair} = 1;
186 $users->update_info(uid => $user->{uid}, %$new_params);
192 sub _get_fixed_moderator_scores {
193 my ($self, $mod, $result) = @_;
203 return $rs if $result->{result} == 0;
207 my $votes = $result->{votes};
209 if ($result->{result} == 1) {
210 $p = $m2_params->{token_fair_params};
212 elsif ($result->{result} == -1) {
213 $p = $m2_params->{token_unfair_params};
217 if ($votes <= $p->{min_count}) {
218 $max_ratio = $p->{min};
220 elsif ($votes >= $p->{max_count}) {
221 $max_ratio = $p->{max};
225 log(($votes - $p->{min_count}) * $p->{alpha})/log(10) * $p->{beta} + $p->{min};
228 $rs->{tokens} = $result->{result} * $max_ratio * $result->{ratio};
230 if ($result->{result} == 1) {
232 $rs->{up_fair} = 1 if $mod->{val} > 0;
233 $rs->{down_fair} = 1 if $mod->{val} < 0;
235 elsif ($result->{result} == -1) {
237 $rs->{up_unfair} = 1 if $mod->{val} > 0;
238 $rs->{down_unfair} = 1 if $mod->{val} < 0;
243 sub _get_fixed_scores {
244 my ($self, $mod, $metamods) = @_;
245 return if !defined $mod;
250 return $rs if !$mod->{active};
251 return $rs if !defined $metamods;
253 my $affect = $self->_get_votes_result($mod, $metamods);
255 if ($affect->{result} > 0) {
257 $rs->{points} = $mod->{val} * (1.0 + $affect->{enforce});
258 $rs->{karma} = $mod->{val} * (1.0 + $affect->{enforce});
260 elsif ($affect->{result} < 0) {
262 $rs->{points} = $mod->{val} * (1.0 - $affect->{prevent});
263 $rs->{karma} = $mod->{val} * (1.0 - $affect->{prevent});
267 $rs->{points} = $mod->{val};
268 $rs->{karma} = $mod->{val};
273 sub _get_votes_result {
274 my ($self, $mod, $metamods) = @_;
275 my $count = @$metamods;
283 if ($count < $m2_params->{count_threshold}) {
287 my $nods = grep { $_->{val} == 1 && $_->{active} } @$metamods;
288 my $nixes = grep { $_->{val} == -1 && $_->{active} } @$metamods;
290 my $fraction = $nods / $count;
291 if ($fraction > $m2_params->{agreement_threshold_ratio}) {
293 $rs->{ratio} = $fraction;
296 $fraction = $nixes / $count;
297 if ($fraction > $m2_params->{agreement_threshold_ratio}) {
299 $rs->{ratio} = $fraction;
303 if ($rs->{result} == 0) {
307 elsif ($rs->{result} < 0) {
310 $self->_calc_ratio($m2_params->{prevent_params}, $count, $fraction);
312 elsif ($rs->{result} > 0) {
315 $self->_calc_ratio($m2_params->{enfoce_params}, $count, $fraction);
322 my ($self, $p, $votes, $agreement_ratio) = @_;
323 my $max_prevent_ratio;
324 if ($votes <= $p->{min_count}) {
325 $max_prevent_ratio = $p->{min};
327 elsif ($votes >= $p->{max_count}) {
328 $max_prevent_ratio = $p->{max};
332 log(($votes - $p->{min_count}) * $p->{alpha})/log(10) * $p->{beta} + $p->{min};
334 return $max_prevent_ratio - $p->{reduction} * (1.0 - $agreement_ratio);
342 if ($params->{cid}) {
343 $rs = $self->select_by_cid(%$params);
346 elsif ($params->{moderation_id}) {
347 $params->{mmid} = $params->{moderation_id};
348 my $uniques = [qw(id)];
349 my $keys = [qw(uid val mmid)];
350 $rs = $self->generic_select("metamodlog",
361 my $cid = $params->{cid};
364 my $moderations = $self->new_instance_of('Moderations');
366 SELECT metamodlog.*, moderatorlog.cid FROM moderatorlog
367 RIGHT JOIN metamodlog ON moderatorlog.id = metamodlog.mmid
368 WHERE moderatorlog.cid = ? AND metamodlog.active = 1;
370 my $dbh = $self->connect_db;
371 my $sth = $dbh->prepare($sql);
373 my $rs = $sth->fetchall_arrayref({});
374 $self->disconnect_db;
378 sub _create_metamodlog {
379 my ($self, $user, $moderation, $value) = @_;
380 return if $self->check_readonly;
381 my $dbh = $self->connect_db;
383 INSERT INTO metamodlog
384 (mmid, uid, val, ts, active)
389 my $rs = $dbh->do($sql, undef, $moderation->{id}, $user->{uid}, $value);
391 $self->disconnect_db;
394 my $m2id = $dbh->last_insert_id(undef, undef, undef, undef);
395 $self->disconnect_db;
401 # import from ::Metamod
403 my ($self, $user, $comment, $reason, $adjust) = @_;
406 my $mod = $self->getModForM2Inherit($user, $comment, $reason);
407 my $m2base = $mod ? $mod->{m2needed} : $m2_params->{m2_consensus};
408 my $m2needed = $m2base + $adjust;
409 $m2needed++ if !($m2needed % 2); # m2needed always odd value
415 # import from ::Metamod
416 sub getModForM2Inherit {
417 my ($self, $user, $comment, $reason, $mod_id) = @_;
419 my $moderations = $self->new_instance_of("Moderations");
420 my $reasons = $moderations->reasons;
421 my @m2able_reasons = sort grep { $reasons->{$_}->{m2able} } (keys %$reasons);
422 return if !@m2able_reasons;
424 my $m2able = join(", ", @m2able_reasons);
425 my @params = ( $comment->{cid},
431 $id_clause = " AND id != ?";
432 push @params, $mod_id;
436 SELECT * FROM moderatorlog
441 AND reason in ($m2able)
442 AND active = 1 $id_clause
443 ORDER BY id ASC LIMIT 1
445 my $dbh = $self->connect_db;
446 my $sth = $dbh->prepare($sql);
447 $sth->execute(@params);
448 my $rs = $sth->fetchrow_hashref;
449 $self->disconnect_db;
454 sub getInheritedM2sForMod {
455 my ($self, $user, $comment, $reason, $active, $mod_id) = @_;
458 my $mod = $self->getModForM2Inherit($user, $comment, $reason, $mod_id);
459 my $p_mid = defined $mod ? $mod->{id} : undef;
462 my $sql = "SELECT * FROM metamodlog WHERE mmid = ?";
463 my $dbh = $self->connect_db;
464 my $sth = $dbh->prepare($sql);
465 $sth->execute($p_mid);
466 my $m2s = $sth->fetchall_arrayref({});
470 $self->disconnect_db;
475 sub applyInheritedM2s {
476 my ($self, $mod_id, $m2s) = @_;
477 my $users = $self->new_instance_of("Users");
479 my $user = $users->select(uid => $m2->{uid});
480 my $m2 = { is_fair => $m2->{val} == 1 ? 1 : 0 };
481 $self->createInherited($mod_id, $m2, $user);
485 # createMetaMod with inherited = 1 and multi_max = 0
486 sub createInherited {
487 my ($self, $mod_id, $m2, $user) = @_;
488 return if $self->check_readonly;
489 my $mods = $self->new_instance_of("Moderations");
491 # We need to know whether the M1 IDs are for moderations
492 # that were up or down.
493 #my $m1_list = join(",", keys %$m2s);
494 #my $m2s_vals = $self->sqlSelectAllHashref("id", "id, val",
495 # "moderatorlog", "id IN ($m1_list)");
496 #for my $m1id (keys %$m2s_vals) {
497 # $m2s->{$m1id}{val} = $m2s_vals->{$m1id}{val};
499 my $mod = $mods->select(id => $mod_id);
501 $m2->{val} = $mod->{val};
503 # Whatever happens below, as soon as we get here, this user has
504 # done their M2 for the day and gets their list of OK mods cleared.
505 # The only exceptions are admins who didn't metamod any of their
506 # saved mods, and inherited M2s. In the case of inherited mods
507 # the m2_user isn't actively m2ing, their m2s are just being
508 # applied to another mod.
510 my $voted_up_fair = 0;
511 my $voted_down_fair = 0;
512 my $voted_up_unfair = 0;
513 my $voted_down_unfair = 0;
515 my $mod_uid = $mod->{uid};
516 my $is_fair = $m2->{is_fair};
519 if ($user->{is_admin}
520 || $user->{tokens} >= $m2_params->{m2_mintokens}) {
525 m2status = IF(m2count >= m2needed AND MOD(m2count, 2) = 1, 1, 0)
528 AND m2count < m2needed
531 my $dbh = $self->connect_db;
532 my $rs = $dbh->do($sql, undef, $mod_id);
533 $rows += $rs || 0; # if no error, returns 0E0 (true!), we want a numeric answer
534 $self->disconnect_db;
537 my $users = $self->new_instance_of("Users");
538 if ($is_fair && $m2->{val} > 0) {
540 #$users->update("info", field => "up_fair", add => 1, uid => $mod_uid);
541 $users->update_info(uid => $mod_uid, up_fair => {add => 1});
542 #$ui_hr->{-up_fair} = "up_fair+1";
543 } elsif ($is_fair && $m2->{val} < 0) {
545 #$users->update("info", field => "down_fair", add => 1, uid => $mod_uid);
546 $users->update_info(uid => $mod_uid, down_fair => {add => 1});
547 #$ui_hr->{-down_fair} = "down_fair+1";
548 } elsif (!$is_fair && $m2->{val} > 0) {
550 #$users->update("info", field => "up_unfair", add => 1, uid => $mod_uid);
551 $users->update_info(uid => $mod_uid, up_unfair => {add => 1});
552 #$ui_hr->{-up_unfair} = "up_unfair+1";
553 } elsif (!$is_fair && $m2->{val} < 0) {
554 ++$voted_down_unfair;
555 #$users->update("info", field => "down_unfair", add => 1, uid => $mod_uid);
556 $users->update_info(uid => $mod_uid, down_unfair => {add => 1});
557 #$ui_hr->{-down_unfair} = "down_unfair+1";
562 # If a row was successfully updated, insert a row
566 # If a row was not successfully updated, probably the
567 # moderation in question was assigned to more than
568 # $consensus users, and the other users pushed it up to
569 # the $consensus limit already. Or this user has
570 # gotten bad M2 and has negative tokens. Or the mod is
575 INSERT INTO metamodlog
576 (mmid, uid, val, ts, active)
580 my $dbh = $self->connect_db;
581 my $rs = $dbh->do($sql, undef,
582 $mod_id, $user->{uid}, ($is_fair ? '+1' : '-1'), $active);
586 $self->disconnect_db;
588 $users->update_info(uid => $mod_uid,
589 m2voted_up_fair => {add => $voted_up_fair},
590 m2voted_down_fair => {add => $voted_down_fair},
591 m2voted_up_unfair => {add => $voted_up_unfair},
592 m2voted_down_unfair => {add => $voted_down_unfair}
597 sub adjustForNewMod {
598 my ($self, $user, $comment, $reason, $active, $mod_id) = @_;
599 my $i_m2s = $self->getInheritedM2sForMod($user, $comment, $reason, $active, $mod_id);
600 $self->applyInheritedM2s($mod_id, $i_m2s);