public static function work(&$controllerContext, &$viewContext)
 {
     $sender = $_GET['sender'];
     $filterParam = isset($_GET['filter-param']) ? $_GET['filter-param'] : null;
     if (isset($_GET['media']) and in_array($_GET['media'], Media::getConstList())) {
         $viewContext->media = $_GET['media'];
     }
     $viewContext->viewName = 'user-entries-' . $sender;
     $viewContext->layoutName = 'layout-ajax';
     $viewContext->filterParam = $filterParam;
     $list = $viewContext->user->getMixedUserMedia($viewContext->media);
     $computeMeanScore = null;
     switch ($sender) {
         case 'ratings':
             $filter = UserMediaFilter::combine(UserMediaFilter::nonPlanned(), UserMediaFilter::score($filterParam));
             break;
         case 'length':
             $filter = UserMediaFilter::combine(UserMediaFilter::nonPlanned(), UserMediaFilter::nonMovie(), UserMediaFilter::lengthGroup($filterParam));
             $computeMeanScore = true;
             break;
         case 'type':
             $filter = UserMediaFilter::combine(UserMediaFilter::nonPlanned(), UserMediaFilter::type($filterParam));
             $computeMeanScore = true;
             break;
         case 'year':
             $filter = UserMediaFilter::combine(UserMediaFilter::nonPlanned(), UserMediaFilter::publishedYear($filterParam));
             $computeMeanScore = true;
             break;
         case 'decade':
             $filter = UserMediaFilter::combine(UserMediaFilter::nonPlanned(), UserMediaFilter::publishedDecade($filterParam));
             $computeMeanScore = true;
             break;
         case 'creator':
             $filter = UserMediaFilter::combine(UserMediaFilter::nonPlanned(), UserMediaFilter::creator($filterParam, $list));
             switch ($viewContext->media) {
                 case Media::Anime:
                     $table = 'animeproducer';
                     break;
                 case Media::Manga:
                     $table = 'mangaauthor';
                     break;
                 default:
                     throw new BadMediaException();
             }
             $computeMeanScore = true;
             break;
         case 'genre':
             $filter = UserMediaFilter::combine(UserMediaFilter::nonPlanned(), UserMediaFilter::genre($filterParam, $list));
             $computeMeanScore = true;
             break;
         case 'franchises':
             $filter = UserMediaFilter::nonPlanned();
             break;
         case 'mismatches':
             $filter = null;
             break;
         case 'watched':
             $filterParam = explode(":", $filterParam);
             $filterParam[2] = boolval($filterParam[2]);
             if ($filterParam[2]) {
                 $yearOrDecade = UserMediaFilter::publishedDecade($filterParam[1]);
             } else {
                 $yearOrDecade = UserMediaFilter::publishedYear($filterParam[1]);
             }
             if ($filterParam[0] == 1) {
                 $filter = UserMediaFilter::combine(UserMediaFilter::nonPlanned(), UserMediaFilter::nonDropped(), UserMediaFilter::notOnHold(), $yearOrDecade, UserMediaFilter::type($filterParam[0]));
             } else {
                 if ($filterParam[0] == 2) {
                     $filter = UserMediaFilter::combine(UserMediaFilter::nonPlanned(), UserMediaFilter::nonDropped(), UserMediaFilter::notOnHold(), $yearOrDecade, UserMediaFilter::notType(1));
                 } else {
                     if ($filterParam[0] == 3) {
                         $filter = UserMediaFilter::combine(UserMediaFilter::nonPlanned(), UserMediaFilter::notCompleting(), UserMediaFilter::notFinished(), $yearOrDecade);
                     }
                 }
             }
             $computeMeanScore = true;
             $viewContext->filterParam = $filterParam;
             break;
         default:
             throw new Exception('Unknown sender (' . $sender . ')');
     }
     $list = UserMediaFilter::doFilter($list, $filter);
     $isPrivate = $viewContext->user->isUserMediaPrivate($viewContext->media);
     if (!$isPrivate) {
         if ($computeMeanScore) {
             $dist = RatingDistribution::fromEntries($list);
             $viewContext->meanScore = $dist->getMeanScore();
         }
         if ($sender == 'franchises') {
             $franchises = Model_MixedUserMedia::getFranchises($list);
             foreach ($franchises as &$franchise) {
                 $dist = RatingDistribution::fromEntries($franchise->ownEntries);
                 $franchise->meanScore = $dist->getMeanScore();
             }
             unset($franchise);
             DataSorter::sort($franchises, DataSorter::MeanScore);
             $viewContext->franchises = array_filter($franchises, function ($franchise) {
                 return count($franchise->ownEntries) > 1;
             });
         } elseif ($sender == 'mismatches') {
             $entries = $viewContext->user->getMismatchedUserMedia($list);
             DataSorter::sort($entries, DataSorter::Title);
             $viewContext->entries = $entries;
         } else {
             DataSorter::sort($list, DataSorter::Title);
             $viewContext->entries = $list;
         }
     }
     $viewContext->isPrivate = $isPrivate;
 }
 public function getMissingTitles()
 {
     $dontRecommend = [];
     foreach ($this->list as $entry) {
         $dontRecommend[$entry->media . $entry->mal_id] = true;
     }
     $franchises = [];
     foreach ($this->allFranchises as &$franchise) {
         $franchise->allEntries = array_filter($franchise->allEntries, function ($entry) use($dontRecommend) {
             if ($entry->media != $this->media) {
                 return false;
             }
             if (isset($dontRecommend[$entry->media . $entry->mal_id])) {
                 return false;
             }
             return true;
         });
         if (empty($franchise->allEntries)) {
             continue;
         }
         DataSorter::sort($franchise->allEntries, DataSorter::MediaMalId);
         DataSorter::sort($franchise->ownEntries, DataSorter::MediaMalId);
         $dist = RatingDistribution::fromEntries($franchise->ownEntries);
         $franchise->meanScore = $dist->getMeanScore();
         $franchises[] = $franchise;
     }
     DataSorter::sort($franchises, DataSorter::MeanScore);
     return $franchises;
 }
 public static function work(&$controllerContext, &$viewContext)
 {
     $viewContext->viewName = 'user-achievements';
     $viewContext->meta->title = $viewContext->user->name . ' — Achievements (' . Media::toString($viewContext->media) . ') — ' . Config::$title;
     $viewContext->meta->description = $viewContext->user->name . '\'s ' . Media::toString($viewContext->media) . ' achievements.';
     WebMediaHelper::addEntries($viewContext);
     WebMediaHelper::addCustom($viewContext);
     $achList = self::getAchievementsDefinitions();
     $list = $viewContext->user->getMixedUserMedia($viewContext->media);
     $listFinished = UserMediaFilter::doFilter($list, UserMediaFilter::finished());
     $listNonPlanned = UserMediaFilter::doFilter($list, UserMediaFilter::nonPlanned());
     $listDropped = UserMediaFilter::doFilter($list, UserMediaFilter::dropped());
     $distribution = RatingDistribution::fromEntries($listNonPlanned);
     $evaluators = ['given-titles' => function ($groupData) use($listFinished) {
         $entriesOwned = UserMediaFilter::doFilter($listFinished, UserMediaFilter::givenMedia($groupData->requirement->titles));
         return [count($entriesOwned), $entriesOwned];
     }, 'genre-titles' => function ($groupData) use($viewContext, $listFinished) {
         $entriesOwned1 = UserMediaFilter::doFilter($listFinished, UserMediaFilter::genre($groupData->requirement->genre, $listFinished));
         $entriesOwned2 = !empty($groupData->requirement->titles) ? UserMediaFilter::doFilter($listFinished, UserMediaFilter::givenMedia($groupData->requirement->titles)) : [];
         $entriesOwned = array_merge($entriesOwned1, $entriesOwned2);
         #array unique w/ callback
         $entriesOwned = array_intersect_key($entriesOwned, array_unique(array_map(function ($e) {
             return $e->media . $e->mal_id;
         }, $entriesOwned)));
         return [count($entriesOwned), $entriesOwned];
     }, 'pervert' => function ($groupData) use($viewContext, $listFinished) {
         $entriesTotalCount = count($listFinished);
         if ($entriesTotalCount > 0) {
             $entriesEcchi = UserMediaFilter::doFilter($listFinished, UserMediaFilter::genre(9, $listFinished));
             $entriesHentai = UserMediaFilter::doFilter($listFinished, UserMediaFilter::genre(12, $listFinished));
             $score = 100 / $entriesTotalCount * (count($entriesEcchi) * 2 + count($entriesHentai) * 4);
             if ($score > 100) {
                 $score = 100;
             }
             $entries = array_merge($entriesEcchi, $entriesHentai);
             $entries = array_intersect_key($entries, array_unique(array_map(function ($e) {
                 return $e->media . $e->mal_id;
             }, $entries)));
             return [$score, $entries];
         }
         return [0, null];
     }, 'finished-titles' => function ($groupData) use($listFinished) {
         return [count($listFinished), null];
     }, 'mean-score' => function ($groupData) use($listNonPlanned, $distribution) {
         if ($distribution->getRatedCount() > 0) {
             return [$distribution->getMeanScore(), null];
         }
         return [null, null];
     }, 'no-drop' => function ($groupData) use($listFinished, $listDropped) {
         if (count($listDropped) > 0) {
             return [0, null];
         }
         return [count($listFinished), null];
     }, 'old-titles' => function ($groupData) use($listFinished) {
         $entriesOwned = UserMediaFilter::doFilter($listFinished, function ($row) {
             $year = substr($row->published_to, 0, 4);
             return $year != '0000' and intval($year) <= 1980;
         });
         return [count($entriesOwned), $entriesOwned];
     }];
     $viewContext->rito = "";
     $achievements = [];
     $hiddenCount = 0;
     foreach ($achList[$viewContext->media] as $group => $groupData) {
         //get subject and entries basing on requirement type
         $evaluator = $evaluators[$groupData->requirement->type];
         list($subject, $entriesOwned) = $evaluator($groupData);
         $groupData->achievements = array_reverse($groupData->achievements);
         if ($subject === null) {
             continue;
         }
         //give first achievement for which the subject fits into its threshold
         $localAchievements = [];
         foreach ($groupData->achievements as &$ach) {
             list($a, $b) = self::getThreshold($ach);
             $ach->thresholdLeft = $a;
             $ach->thresholdRight = $b;
             $ach->earned = (($subject >= $a or $a === null) and ($subject <= $b or $b === null));
             if ($ach->next and $ach->next->earned) {
                 $ach->earned = true;
                 $ach->hidden = true;
                 $hiddenCount++;
             } else {
                 $ach->hidden = false;
             }
             if ($ach->earned) {
                 //put additional info
                 if (!empty($entriesOwned)) {
                     DataSorter::sort($entriesOwned, DataSorter::Title);
                     $ach->entries = $entriesOwned;
                 }
                 $ach->progress = 100;
                 $ach->subject = round($subject, 2);
                 if ($ach->next) {
                     $ach->progress = ($subject - $a) * 100.0 / ($ach->next->thresholdLeft - $a);
                 }
                 $localAchievements[] = $ach;
             }
         }
         $achievements = array_merge($achievements, array_reverse($localAchievements));
     }
     $viewContext->achievements = $achievements;
     $viewContext->private = $viewContext->user->isUserMediaPrivate($viewContext->media);
     $viewContext->hiddenCount = $hiddenCount;
 }