/**
  * Execute the API call: Pull the requested feedback
  */
 public function execute()
 {
     $params = $this->extractRequestParams();
     $pageId = $params['pageid'];
     $flag = $params['flagtype'];
     $direction = isset($params['direction']) ? $params['direction'] : 'increase';
     $counts = array('increment' => array(), 'decrement' => array());
     $counters = array('abuse', 'helpful', 'unhelpful');
     $flags = array('oversight', 'hide', 'delete');
     $results = array();
     $helpful = null;
     $error = null;
     $where = array('af_id' => $params['feedbackid']);
     # load feedback record, bail if we don't have one
     $record = $this->fetchRecord($params['feedbackid']);
     if ($record === false || !$record->af_id) {
         // no-op, because this is already broken
         $error = 'articlefeedbackv5-invalid-feedback-id';
     } elseif (in_array($flag, $flags)) {
         $count = null;
         switch ($flag) {
             case 'hide':
                 $field = 'af_is_hidden';
                 $count = 'invisible';
                 break;
             case 'oversight':
                 $field = 'af_needs_oversight';
                 $count = 'needsoversight';
                 break;
             case 'delete':
                 $field = 'af_is_deleted';
                 $count = 'deleted';
                 break;
             default:
                 return;
                 # return error, ideally.
         }
         if ($direction == 'increase') {
             $update[] = "{$field} = TRUE";
         } else {
             $update[] = "{$field} = FALSE";
         }
         // Increment or decrement whichever flag is being set.
         $countDirection = $direction == 'increase' ? 'increment' : 'decrement';
         $counts[$countDirection][] = $count;
         // If this is hiding/deleting, decrement the visible count.
         if (($count == 'hide' || $count == 'deleted') && $direction == 'increase') {
             $counts['decrement'][] = 'visible';
         }
         // If this is unhiding/undeleting, increment the visible count.
         if (($count == 'hide' || $count == 'deleted') && $direction == 'decrease') {
             $counts['increment'][] = 'visible';
         }
     } elseif (in_array($flag, $counters)) {
         // Probably this doesn't need validation, since the API
         // will handle it, but if it's getting interpolated into
         // the SQL, I'm really wary not re-validating it.
         $field = 'af_' . $flag . '_count';
         // Add another where condition to confirm that
         // the new flag value is at or above 0 (we use
         // unsigned ints, so negatives cause errors.
         if ($direction == 'increase') {
             $update[] = "{$field} = {$field} + 1";
             // If this is already less than 0,
             // don't do anything - it'll just
             // throw a SQL error, so don't bother.
             // Incrementing from 0 is still valid.
             $where[] = "{$field} >= 0";
         } else {
             $update[] = "{$field} = {$field} - 1";
             // If this is already 0 or less,
             // don't decrement it, that would
             // throw an error.
             // Decrementing from 0 is not allowed.
             $where[] = "{$field} > 0";
         }
         // Adding a new abuse flag: abusive++
         if ($flag == 'abuse' && $direction == 'increase' && $record->af_abuse_count == 0) {
             $counts['increment'][] = 'abusive';
         }
         // Removing the last abuse flag: abusive--
         if ($flag == 'abuse' && $direction == 'decrease' && $record->af_abuse_count == 1) {
             $counts['decrement'][] = 'abusive';
         }
         // note that a net helpfulness of 0 is neither helpful nor unhelpful
         $netHelpfulness = $record->af_net_helpfulness;
         // increase helpful OR decrease unhelpful
         if ($flag == 'helpful' && $direction == 'increase' || $flag == 'unhelpful' && $direction == 'decrease') {
             // net was -1: no longer unhelpful
             if ($netHelpfulness == -1) {
                 $counts['decrement'] = 'unhelpful';
             }
             // net was 0: now helpful
             if ($netHelpfulness == -1) {
                 $counts['increment'] = 'helpful';
             }
         }
         // increase unhelpful OR decrease unhelpful
         if ($flag == 'unhelpful' && $direction == 'increase' || $flag == 'helpful' && $direction == 'decrease') {
             // net was 1: no longer helpful
             if ($netHelpfulness == 1) {
                 $counts['decrement'] = 'helpful';
             }
             // net was 0: now unhelpful
             if ($netHelpfulness == 0) {
                 $counts['increment'] = 'unhelpful';
             }
         }
     } else {
         $error = 'articlefeedbackv5-invalid-feedback-flag';
     }
     if (!$error) {
         $dbw = wfGetDB(DB_MASTER);
         $success = $dbw->update('aft_article_feedback', $update, $where, __METHOD__);
         // If the query worked...
         if ($success) {
             // Update the filter count rollups.
             ApiArticleFeedbackv5Utils::incrementFilterCounts($pageId, $counts['increment']);
             ApiArticleFeedbackv5Utils::decrementFilterCounts($pageId, $counts['decrement']);
             // Update helpful/unhelpful display count after submission.
             if ($flag == 'helpful' || $flag == 'unhelpful') {
                 $helpful = $record->af_helpful_count;
                 $unhelpful = $record->af_unhelpful_count;
                 if ($flag == 'helpful' && $direction == 'increase') {
                     $helpful++;
                 } elseif ($flag == 'helpful' && $direction == 'decrease') {
                     $helpful--;
                 } elseif ($flag == 'unhelpful' && $direction == 'increase') {
                     $unhelpful++;
                 } elseif ($flag == 'unhelpful' && $direction == 'decrease') {
                     $unhelpful--;
                 }
                 $results['helpful'] = wfMessage('articlefeedbackv5-form-helpful-votes', $helpful, $unhelpful)->escaped();
                 // Update net_helpfulness after flagging as helpful/unhelpful.
                 $dbw->update('aft_article_feedback', array('af_net_helpfulness = CONVERT(af_helpful_count, SIGNED) - CONVERT(af_unhelpful_count, SIGNED)'), array('af_id' => $params['feedbackid']), __METHOD__);
             }
         }
         // Conditional formatting for abuse flag
         global $wgArticleFeedbackv5AbusiveThreshold, $wgArticleFeedbackv5HideAbuseThreshold;
         $results['abuse_count'] = $record->af_abuse_count;
         if ($flag == 'abuse') {
             // Make the abuse count in the result reflect this vote.
             if ($direction == 'increase') {
                 $results['abuse_count']++;
             } else {
                 $results['abuse_count']--;
             }
             // Return a flag in the JSON, that turns the link red.
             if ($results['abuse_count'] >= $wgArticleFeedbackv5AbusiveThreshold) {
                 $results['abusive'] = 1;
             }
             // Return a flag in the JSON, that knows to kill the row
             if ($results['abuse_count'] >= $wgArticleFeedbackv5HideAbuseThreshold) {
                 $results['abuse-hidden'] = 1;
             }
             $dbw->update('aft_article_feedback', array('af_is_hidden = TRUE'), array('af_id' => $params['feedbackid'], "af_abuse_count >= " . intval($wgArticleFeedbackv5HideAbuseThreshold)), __METHOD__);
         }
     }
     if ($error) {
         $results['result'] = 'Error';
         $results['reason'] = $error;
     } else {
         $results['result'] = 'Success';
         $results['reason'] = null;
     }
     $this->getResult()->addValue(null, $this->getModuleName(), $results);
 }
 /**
  * Gets the parameter descriptions
  *
  * @return array the descriptions, indexed by allowed key
  */
 public function getParamDescription()
 {
     $ret = array('pageid' => 'Page ID to submit feedback for', 'revid' => 'Revision ID to submit feedback for', 'anontoken' => 'Token for anonymous users', 'bucket' => 'Which feedback widget was shown to the user', 'link' => 'Which link the user clicked on to get to the widget');
     $fields = ApiArticleFeedbackv5Utils::getFields();
     foreach ($fields as $f) {
         $ret[$f['afi_name']] = 'Optional feedback field, only appears on certain "buckets".';
     }
     return $ret;
 }
 /**
  * decrements the per-page-per-filter count rollups used on the feedback
  * page.
  *
  * @param $pageId      int   the ID of the page (page.page_id)
  * @param $filterNames array values are names of filters to decrement
  */
 public static function decrementFilterCounts($pageId, $filterNames)
 {
     ApiArticleFeedbackv5Utils::updateFilterCounts($pageId, $filterNames, true);
 }
 public function fetchFeedback($pageId, $filter = 'visible', $filterValue = null, $sort = 'age', $sortOrder = 'desc', $limit = 25, $continue = null, $continueId)
 {
     $dbr = wfGetDB(DB_SLAVE);
     $ids = array();
     $rows = array();
     $rv = array();
     $direction = strtolower($sortOrder) == 'asc' ? 'ASC' : 'DESC';
     $continueDirection = $direction == 'ASC' ? '>' : '<';
     $order;
     $continueSql;
     $sortField;
     $ratingField = 0;
     $commentField = 0;
     // This is in memcache so I don't feel that bad re-fetching it.
     // Needed to join in the comment and rating tables, for filtering
     // and sorting, respectively.
     foreach (ApiArticleFeedbackv5Utils::getFields() as $field) {
         if ($field['afi_bucket_id'] == 1 && $field['afi_name'] == 'comment') {
             $commentField = $field['afi_id'];
         }
         if ($field['afi_bucket_id'] == 1 && $field['afi_name'] == 'found') {
             $ratingField = $field['afi_id'];
         }
     }
     // Build ORDER BY clause.
     switch ($sort) {
         case 'helpful':
             $sortField = 'af_net_helpfulness';
             $order = "af_net_helpfulness {$direction}, af_id {$direction}";
             $continueSql = "(af_net_helpfulness {$continueDirection} " . intVal($continue) . " OR (af_net_helpfulness = " . intVal($continue) . " AND af_id {$continueDirection} " . intval($continueId) . ") )";
             break;
         case 'rating':
             # TODO: null ratings don't seem to show up at all. Need to sort that one out.
             $sortField = 'rating';
             $order = "rating {$direction}, af_id {$direction}";
             $continueSql = "(rating.aa_response_boolean {$continueDirection} " . intVal($continue) . " OR (rating.aa_response_boolean = " . intVal($continue) . " AND af_id {$continueDirection} " . intval($continueId) . ") )";
             break;
         case 'age':
             # Default field, fall through
         # Default field, fall through
         default:
             $sortField = 'af_id';
             $order = "af_id {$direction}";
             $continueSql = "af_id {$continueDirection} " . intVal($continue);
             break;
     }
     // Build WHERE clause.
     // Filter applied , if any:
     $where = $this->getFilterCriteria($filter, $filterValue);
     // PageID:
     $where['af_page_id'] = $pageId;
     // Continue SQL, if any:
     if ($continue !== null) {
         $where[] = $continueSql;
     }
     // Only show bucket 1 (per Fabrice on 1/25)
     $where['af_bucket_id'] = 1;
     // Fetch the feedback IDs we need.
     /* I'd really love to do this in one big query, but MySQL
        doesn't support LIMIT inside IN() subselects, and since
        we don't know the number of answers for each feedback
        record until we fetch them, this is the only way to make
        sure we get all answers for the exact IDs we want. */
     $id_query = $dbr->select(array('aft_article_feedback', 'rating' => 'aft_article_answer', 'comment' => 'aft_article_answer'), array('af_id', 'af_net_helpfulness', 'rating.aa_response_boolean AS rating'), $where, __METHOD__, array('LIMIT' => $limit + 1, 'ORDER BY' => $order), array('rating' => array('LEFT JOIN', 'rating.aa_feedback_id = af_id AND rating.aa_field_id = ' . intval($ratingField)), 'comment' => array('LEFT JOIN', 'comment.aa_feedback_id = af_id AND comment.aa_field_id = ' . intval($commentField))));
     foreach ($id_query as $id) {
         $ids[] = $id->af_id;
         // Get the continue values from the last counted item.
         if (count($ids) == $limit) {
             $this->continue = $id->{$sortField};
             $this->continueId = $id->af_id;
         }
     }
     if (!count($ids)) {
         return array();
     }
     // Returned an extra row, meaning there's more to show.
     // Also, pop that extra one off, so we don't render it.
     if (count($ids) > $limit) {
         $this->showMore = true;
         array_pop($ids);
     }
     $rows = $dbr->select(array('aft_article_feedback', 'rating' => 'aft_article_answer', 'answer' => 'aft_article_answer', 'aft_article_field', 'aft_article_field_option', 'user', 'page'), array('af_id', 'af_bucket_id', 'afi_name', 'afo_name', 'answer.aa_response_text', 'answer.aa_response_boolean', 'answer.aa_response_rating', 'answer.aa_response_option_id', 'afi_data_type', 'af_created', 'user_name', 'af_user_ip', 'af_is_hidden', 'af_abuse_count', 'af_helpful_count', 'af_unhelpful_count', 'af_is_deleted', 'af_needs_oversight', 'af_revision_id', 'af_net_helpfulness', 'af_revision_id', 'page_latest', 'page_title', 'page_namespace', 'rating.aa_response_boolean AS rating'), array('af_id' => $ids), __METHOD__, array('ORDER BY' => $order), array('rating' => array('LEFT JOIN', 'rating.aa_feedback_id = af_id AND rating.aa_field_id = ' . intval($ratingField)), 'answer' => array('LEFT JOIN', 'af_id = answer.aa_feedback_id'), 'aft_article_field' => array('LEFT JOIN', 'afi_id = answer.aa_field_id'), 'aft_article_field_option' => array('LEFT JOIN', 'answer.aa_response_option_id = afo_option_id'), 'user' => array('LEFT JOIN', 'user_id = af_user_id'), 'page' => array('JOIN', 'page_id = af_page_id')));
     foreach ($rows as $row) {
         if (!array_key_exists($row->af_id, $rv)) {
             $rv[$row->af_id] = array();
             $rv[$row->af_id][0] = $row;
             $rv[$row->af_id][0]->user_name = $row->user_name ? $row->user_name : $row->af_user_ip;
         }
         $rv[$row->af_id][$row->afi_name] = $row;
     }
     return $rv;
 }