public static function work(&$controllerContext, &$viewContext)
 {
     $viewContext->viewName = 'user-profile';
     $viewContext->meta->title = $viewContext->user->name . '\'s profile — ' . Config::$title;
     $viewContext->meta->description = $viewContext->user->name . '\'s profile.';
     WebMediaHelper::addEntries($viewContext);
     WebMediaHelper::addMiniSections($viewContext);
     WebMediaHelper::addCustom($viewContext);
     $viewContext->yearsOnMal = null;
     if (intval($viewContext->user->join_date)) {
         list($year, $month, $day) = explode('-', $viewContext->user->join_date);
         $time = mktime(0, 0, 0, $month, $day, $year);
         $diff = time() - $time;
         $diff /= 3600 * 24;
         $viewContext->yearsOnMal = $diff / 361.25;
     }
     $viewContext->friends = $viewContext->user->getFriends();
     $viewContext->finished = [];
     $viewContext->meanUserScore = [];
     $viewContext->meanGlobalScore = [];
     $viewContext->franchiseCount = [];
     $viewContext->mismatchedCount = [];
     foreach (Media::getConstList() as $media) {
         $list = $viewContext->user->getMixedUserMedia($media);
         $listFinished = UserMediaFilter::doFilter($list, UserMediaFilter::finished());
         $viewContext->finished[$media] = count($listFinished);
         unset($listFinished);
         $listNonPlanned = UserMediaFilter::doFilter($list, UserMediaFilter::nonPlanned());
         $viewContext->meanUserScore[$media] = RatingDistribution::fromEntries($listNonPlanned)->getMeanScore();
         $franchises = Model_MixedUserMedia::getFranchises($listNonPlanned);
         $viewContext->franchiseCount[$media] = count(array_filter($franchises, function ($franchise) {
             return count($franchise->ownEntries) > 1;
         }));
         unset($franchises);
         unset($listNonPlanned);
         if ($media == Media::Anime) {
             $viewContext->episodes = array_sum(array_map(function ($mixedMediaEntry) {
                 return $mixedMediaEntry->finished_episodes;
             }, $list));
         } else {
             $viewContext->chapters = array_sum(array_map(function ($mixedMediaEntry) {
                 return $mixedMediaEntry->finished_chapters;
             }, $list));
         }
         $mismatched = $viewContext->user->getMismatchedUserMedia($list);
         $viewContext->mismatchedCount[$media] = count($mismatched);
         unset($mismatched);
         unset($list);
         $globalsCache = file_exists(Config::$globalsCachePath) ? TextHelper::loadJson(Config::$globalsCachePath, true) : [];
         $viewContext->meanGlobalScore[$media] = array_map(function ($v) {
             return RatingDistribution::fromArray($v);
         }, $globalsCache['rating-dist'])[$media]->getMeanScore();
     }
 }
Пример #2
0
 public static function getRatingDistribution($media)
 {
     $query = 'SELECT score, COUNT(score) AS count FROM usermedia WHERE media = ? GROUP BY score';
     $result = R::getAll($query, [$media]);
     $dist[$media] = [];
     foreach ($result as $row) {
         $count = $row['count'];
         $score = $row['score'];
         $dist[$media][$score] = $count;
     }
     return RatingDistribution::fromArray($dist[$media]);
 }
 public static function work(&$controllerContext, &$viewContext)
 {
     $viewContext->viewName = 'user-profile';
     $viewContext->meta->title = 'MALgraph - ' . $viewContext->user->name . '’s profile';
     $viewContext->meta->description = $viewContext->user->name . '’s profile on MALgraph, an online tool that extends your MyAnimeList profile.';
     $viewContext->meta->keywords = array_merge($viewContext->meta->keywords, ['profile', 'list', 'achievements', 'ratings', 'history', 'favorites', 'suggestions', 'recommendations']);
     WebMediaHelper::addEntries($viewContext);
     WebMediaHelper::addMiniSections($viewContext);
     WebMediaHelper::addCustom($viewContext);
     $viewContext->yearsOnMal = null;
     if (intval($viewContext->user->join_date)) {
         list($year, $month, $day) = explode('-', $viewContext->user->join_date);
         $time = mktime(0, 0, 0, $month, $day, $year);
         $diff = time() - $time;
         $diff /= 3600 * 24;
         $viewContext->yearsOnMal = $diff / 361.25;
     }
     $viewContext->friends = $viewContext->user->getFriends();
     $viewContext->clubs = $viewContext->user->getClubs();
     $viewContext->finished = [];
     $viewContext->meanUserScore = [];
     $viewContext->meanGlobalScore = [];
     $viewContext->franchiseCount = [];
     $viewContext->mismatchedCount = [];
     foreach (Media::getConstList() as $media) {
         $list = $viewContext->user->getMixedUserMedia($media);
         $listFinished = UserMediaFilter::doFilter($list, UserMediaFilter::finished());
         $viewContext->finished[$media] = count($listFinished);
         unset($listFinished);
         $listNonPlanned = UserMediaFilter::doFilter($list, UserMediaFilter::nonPlanned());
         $viewContext->meanUserScore[$media] = RatingDistribution::fromEntries($listNonPlanned)->getMeanScore();
         $viewContext->meanGlobalScore[$media] = Model_MixedUserMedia::getRatingDistribution($media)->getMeanScore();
         $franchises = Model_MixedUserMedia::getFranchises($listNonPlanned);
         $viewContext->franchiseCount[$media] = count(array_filter($franchises, function ($franchise) {
             return count($franchise->ownEntries) > 1;
         }));
         unset($franchises);
         unset($listNonPlanned);
         if ($media == Media::Anime) {
             $viewContext->episodes = array_sum(array_map(function ($mixedMediaEntry) {
                 return $mixedMediaEntry->finished_episodes;
             }, $list));
         } else {
             $viewContext->chapters = array_sum(array_map(function ($mixedMediaEntry) {
                 return $mixedMediaEntry->finished_chapters;
             }, $list));
         }
         $mismatched = $viewContext->user->getMismatchedUserMedia($list);
         $viewContext->mismatchedCount[$media] = count($mismatched);
         unset($mismatched);
         unset($list);
     }
 }
Пример #4
0
 public function process(array $documents, &$context)
 {
     Database::delete('usermedia', ['user_id' => $context->user->id]);
     $context->user->cool = false;
     foreach (Media::getConstList() as $media) {
         $key = $media == Media::Anime ? self::URL_ANIMELIST : self::URL_MANGALIST;
         $isPrivate = strpos($documents[$key]->content, 'This list has been made private by the owner') !== false;
         $key = $media == Media::Anime ? self::URL_ANIMEINFO : self::URL_MANGAINFO;
         $doc = $documents[$key];
         $dom = self::getDOM($doc);
         $xpath = new DOMXPath($dom);
         if ($xpath->query('//myinfo')->length == 0) {
             throw new BadProcessorDocumentException($doc, 'myinfo block is missing');
         }
         if (strpos($doc->content, '</myanimelist>') === false) {
             throw new BadProcessorDocumentException($doc, 'list is only partially downloaded');
         }
         $nodes = $xpath->query('//anime | //manga');
         $data = [];
         foreach ($nodes as $root) {
             $mediaMalId = Strings::makeInteger(self::getNodeValue($xpath, 'series_animedb_id | series_mangadb_id', $root));
             $score = Strings::makeInteger(self::getNodeValue($xpath, 'my_score', $root));
             $startDate = Strings::makeDate(self::getNodeValue($xpath, 'my_start_date', $root));
             $finishDate = Strings::makeDate(self::getNodeValue($xpath, 'my_finish_date', $root));
             $status = Strings::makeEnum(self::getNodeValue($xpath, 'my_status', $root), [1 => UserListStatus::Completing, 2 => UserListStatus::Finished, 3 => UserListStatus::OnHold, 4 => UserListStatus::Dropped, 6 => UserListStatus::Planned], UserListStatus::Unknown);
             $finishedEpisodes = null;
             $finishedChapters = null;
             $finishedVolumes = null;
             switch ($media) {
                 case Media::Anime:
                     $finishedEpisodes = Strings::makeInteger(self::getNodeValue($xpath, 'my_watched_episodes', $root));
                     break;
                 case Media::Manga:
                     $finishedChapters = Strings::makeInteger(self::getNodeValue($xpath, 'my_read_chapters', $root));
                     $finishedVolumes = Strings::makeInteger(self::getNodeValue($xpath, 'my_read_volumes', $root));
                     break;
                 default:
                     throw new BadMediaException();
             }
             $data[] = ['user_id' => $context->user->id, 'mal_id' => $mediaMalId, 'media' => $media, 'score' => $score, 'start_date' => $startDate, 'end_date' => $finishDate, 'finished_episodes' => $finishedEpisodes, 'finished_chapters' => $finishedChapters, 'finished_volumes' => $finishedVolumes, 'status' => $status];
         }
         Database::insert('usermedia', $data);
         $dist = RatingDistribution::fromEntries(ReflectionHelper::arraysToClasses($data));
         $daysSpent = Strings::makeFloat(self::getNodeValue($xpath, '//user_days_spent_watching'));
         $user =& $context->user;
         $user->{Media::toString($media) . '_days_spent'} = $daysSpent;
         $user->{Media::toString($media) . '_private'} = $isPrivate;
         $user->cool |= ($dist->getRatedCount() >= 50 and $dist->getStandardDeviation() >= 1.5);
         R::store($user);
     }
 }
 public static function work(&$controllerContext, &$viewContext)
 {
     $viewContext->viewName = 'user-ratings';
     $viewContext->meta->title = 'MALgraph - ' . $viewContext->user->name . ' - rating statistics (' . Media::toString($viewContext->media) . ')';
     $viewContext->meta->description = $viewContext->user->name . '&rsquo;s ' . Media::toString($viewContext->media) . ' rating statistics on MALgraph, an online tool that extends your MyAnimeList profile.';
     $viewContext->meta->keywords = array_merge($viewContext->meta->keywords, ['profile', 'list', 'achievements', 'ratings', 'history', 'favorites', 'suggestions', 'recommendations']);
     WebMediaHelper::addHighcharts($viewContext);
     WebMediaHelper::addFarbtastic($viewContext);
     WebMediaHelper::addInfobox($viewContext);
     WebMediaHelper::addEntries($viewContext);
     WebMediaHelper::addCustom($viewContext);
     $list = $viewContext->user->getMixedUserMedia($viewContext->media);
     $list = UserMediaFilter::doFilter($list, UserMediaFilter::nonPlanned());
     $viewContext->ratingDistribution = RatingDistribution::fromEntries($list);
     $viewContext->ratingTimeDistribution = RatingTimeDistribution::fromEntries($list);
     $listNoMovies = UserMediaFilter::doFilter($list, UserMediaFilter::nonMovie());
     $viewContext->lengthDistribution = MediaLengthDistribution::fromEntries($listNoMovies);
     $f = explode('-', $viewContext->user->join_date);
     if (count($f) != 3) {
         $viewContext->earliestTimeKnown = null;
         $viewContext->meanTime = null;
     } else {
         list($year, $month, $day) = $f;
         $earliest = mktime(0, 0, 0, $month, $day, $year);
         $totalTime = 0;
         foreach ($list as $mixedUserMedia) {
             $totalTime += $mixedUserMedia->finished_duration;
             foreach ([$mixedUserMedia->start_date, $mixedUserMedia->end_date] as $k) {
                 $f = explode('-', $k);
                 if (count($f) != 3) {
                     continue;
                 }
                 $year = intval($f[0]);
                 $month = intval($f[1]);
                 $day = intval($f[2]);
                 if (!$year or !$month or !$day) {
                     continue;
                 }
                 $time = mktime(0, 0, 0, $month, $day, $year);
                 if ($time < $earliest) {
                     $earliest = $time;
                 }
             }
         }
         $viewContext->earliestTimeKnown = $earliest;
         $viewContext->meanTime = $totalTime / max(1, (time() - $earliest) / (24.0 * 3600.0));
     }
 }
Пример #6
0
 public static function work(&$controllerContext, &$viewContext)
 {
     $viewContext->viewName = 'user-ratings';
     $viewContext->meta->title = $viewContext->user->name . ' &#8212; Ratings (' . Media::toString($viewContext->media) . ') &#8212; ' . Config::$title;
     $viewContext->meta->description = $viewContext->user->name . '\'s ' . Media::toString($viewContext->media) . ' ratings.';
     WebMediaHelper::addHighcharts($viewContext);
     WebMediaHelper::addFarbtastic($viewContext);
     WebMediaHelper::addInfobox($viewContext);
     WebMediaHelper::addEntries($viewContext);
     WebMediaHelper::addCustom($viewContext);
     $list = $viewContext->user->getMixedUserMedia($viewContext->media);
     $list = UserMediaFilter::doFilter($list, UserMediaFilter::nonPlanned());
     $viewContext->ratingDistribution = RatingDistribution::fromEntries($list);
     $viewContext->ratingTimeDistribution = RatingTimeDistribution::fromEntries($list);
     $listNoMovies = UserMediaFilter::doFilter($list, UserMediaFilter::nonMovie());
     $viewContext->lengthDistribution = MediaLengthDistribution::fromEntries($listNoMovies);
     $f = explode('-', $viewContext->user->join_date);
     if (count($f) != 3) {
         $viewContext->earliestTimeKnown = null;
         $viewContext->meanTime = null;
     } else {
         list($year, $month, $day) = $f;
         $earliest = mktime(0, 0, 0, $month, $day, $year);
         $totalTime = 0;
         foreach ($list as $mixedUserMedia) {
             $totalTime += $mixedUserMedia->finished_duration;
             foreach ([$mixedUserMedia->start_date, $mixedUserMedia->end_date] as $k) {
                 $f = explode('-', $k);
                 if (count($f) != 3) {
                     continue;
                 }
                 $year = intval($f[0]);
                 $month = intval($f[1]);
                 $day = intval($f[2]);
                 if (!$year or !$month or !$day) {
                     continue;
                 }
                 $time = mktime(0, 0, 0, $month, $day, $year);
                 if ($time < $earliest) {
                     $earliest = $time;
                 }
             }
         }
         $viewContext->earliestTimeKnown = $earliest;
         $viewContext->meanTime = $totalTime / max(1, (time() - $earliest) / (24.0 * 3600.0));
     }
 }
 public static function work(&$controllerContext, &$viewContext)
 {
     $viewContext->viewName = 'index-globals';
     $viewContext->meta->title = 'MALgraph - global statistics';
     $viewContext->meta->description = 'Global community statistics on MALgraph, an online tool that extends your MyAnimeList profile.';
     WebMediaHelper::addHighcharts($viewContext);
     WebMediaHelper::addInfobox($viewContext);
     WebMediaHelper::addMiniSections($viewContext);
     WebMediaHelper::addCustom($viewContext);
     $globalsCache = file_exists(Config::$globalsCachePath) ? TextHelper::loadJson(Config::$globalsCachePath, true) : [];
     $viewContext->userCount = $globalsCache['user-count'];
     $viewContext->mediaCount = $globalsCache['media-count'];
     $viewContext->ratingDistribution = array_map(function ($v) {
         return RatingDistribution::fromArray($v);
     }, $globalsCache['rating-dist']);
     $viewContext->queuedUserCount = (new Queue(Config::$userQueuePath))->size();
     $viewContext->queueSizes = TextHelper::loadJson(Config::$userQueueSizesPath, true);
 }
 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)
 {
     $ratingDistribution = [];
     foreach (Media::getConstList() as $media) {
         $entries = $viewContext->user->getMixedUserMedia($media);
         $entriesNonPlanned = UserMediaFilter::doFilter($entries, [UserMEdiaFilter::nonPlanned()]);
         $ratingDistribution[$media] = RatingDistribution::fromEntries($entries);
     }
     //get input data from GET
     $userSettings = !empty($_GET['settings']) ? json_decode(base64_decode($_GET['settings']), true) : [];
     $imageType = !empty($userSettings[0]) ? $userSettings[0] : null;
     if (!in_array($imageType, [self::IMAGE_TYPE_ANIME, self::IMAGE_TYPE_MANGA, self::IMAGE_TYPE_ANIME_MANGA])) {
         $imageType = self::IMAGE_TYPE_ANIME;
     }
     $settings = new StdClass();
     $settings->font = Config::$mediaDirectory . DIRECTORY_SEPARATOR . 'font' . DIRECTORY_SEPARATOR . 'OpenSans-Regular.ttf';
     $settings->fontSizeSmall = 7;
     $settings->fontSizeNormal = 9;
     $settings->fontSizeBig = 10.5;
     $settings->barWidth = 220;
     $settings->barHeight = 13;
     $settings->barPadding = 1;
     $settings->colors = [self::COLOR_BARS1 => '00a4c0f4', self::COLOR_BARS2 => '0013459a', self::COLOR_BAR_GUIDES1 => 'eea4c0f4', self::COLOR_BAR_GUIDES2 => 'ee13459a', self::COLOR_BACKGROUND => 'ffffffff', self::COLOR_FONT_DARK => '00000000', self::COLOR_FONT_LIGHT => 'aa000000', self::COLOR_TITLE => '00577fc2', self::COLOR_LOGO => '00577fc2'];
     foreach (array_keys($settings->colors) as $key) {
         if (isset($userSettings[$key])) {
             $value = $userSettings[$key];
             assert(in_array(strlen($value), [6, 8]));
             $settings->colors[$key] = $value;
         }
     }
     $settings->colors = array_map(function ($color) {
         $value = array_map('hexdec', str_split($color, 2));
         if (count($value) == 3) {
             array_unshift($value, 0);
         }
         $value[0] >>= 1;
         $c = 0;
         while (!empty($value)) {
             $c <<= 8;
             $c |= array_shift($value);
         }
         return $c;
     }, $settings->colors);
     $margin = 6;
     $iconImage = self::getIconImage($settings);
     if ($imageType == self::IMAGE_TYPE_ANIME or $imageType == self::IMAGE_TYPE_MANGA) {
         $media = $imageType == self::IMAGE_TYPE_ANIME ? Media::Anime : Media::Manga;
         $barsImage = self::getBarsImage($settings, $ratingDistribution[$media], false);
         $barsAxesImage = self::getBarsAxesImage($settings, false);
         $barsBandsImage = self::getBarsBandsImage($settings, $ratingDistribution[$media], false);
         $footerImage = self::getFooterImage($settings, $ratingDistribution[$media], $media, false);
         $w = imagesx($barsImage) + imagesx($barsAxesImage) + imagesx($barsBandsImage);
         $h = imagesy($barsImage) + $margin + imagesy($footerImage);
         $img = imagecreatetruecolor($w, $h);
         imagealphablending($img, false);
         imagefilledrectangle($img, 0, 0, $w, $h, $settings->colors[self::COLOR_BACKGROUND]);
         $x = 0;
         $y = imagesy($barsAxesImage) + $margin;
         self::drawCopy($img, $barsAxesImage, $x, 0);
         $x += imagesx($barsAxesImage);
         self::drawCopy($img, $barsImage, $x, 0);
         self::drawCopy($img, $footerImage, $x, $y);
         $x += imagesx($barsImage);
         self::drawCopy($img, $barsBandsImage, $x, 0);
         $x += imagesx($barsBandsImage);
         $x = imagesx($barsAxesImage) - imagesx($iconImage) >> 1;
         self::drawCopy($img, $iconImage, $x, $y);
     } else {
         $barsImage1 = self::getBarsImage($settings, $ratingDistribution[Media::Anime], true);
         $barsImage2 = self::getBarsImage($settings, $ratingDistribution[Media::Manga], false);
         $barsAxesImage = self::getBarsAxesImage($settings, true);
         $barsBandsImage1 = self::getBarsBandsImage($settings, $ratingDistribution[Media::Anime], true);
         $barsBandsImage2 = self::getBarsBandsImage($settings, $ratingDistribution[Media::Manga], false);
         $footerImage1 = self::getFooterImage($settings, $ratingDistribution[Media::Anime], Media::Anime, true);
         $footerImage2 = self::getFooterImage($settings, $ratingDistribution[Media::Manga], Media::Manga, false);
         $w = imagesx($barsImage1) + imagesx($barsImage2) + imagesx($barsAxesImage) + imagesx($barsBandsImage1) + imagesx($barsBandsImage2);
         $h = imagesy($barsImage1) + $margin + imagesy($footerImage1);
         $img = imagecreatetruecolor($w, $h);
         imagealphablending($img, false);
         imagefilledrectangle($img, 0, 0, $w, $h, $settings->colors[self::COLOR_BACKGROUND]);
         $x = 0;
         $y = imagesy($barsAxesImage) + $margin;
         self::drawCopy($img, $barsBandsImage1, $x, 0);
         $x += imagesx($barsBandsImage1);
         self::drawCopy($img, $barsImage1, $x, 0);
         self::drawCopy($img, $footerImage1, $x, $y);
         $x += imagesx($barsImage1);
         self::drawCopy($img, $barsAxesImage, $x, 0);
         $x += imagesx($barsAxesImage);
         self::drawCopy($img, $barsImage2, $x, 0);
         self::drawCopy($img, $footerImage2, $x, $y);
         $x += imagesx($barsImage2);
         self::drawCopy($img, $barsBandsImage2, $x, 0);
         $x = imagesx($barsBandsImage1) + imagesx($barsImage1) + (imagesx($barsAxesImage) - imagesx($iconImage) >> 1);
         self::drawCopy($img, $iconImage, $x, $y);
     }
     imagesavealpha($img, true);
     $viewContext->layoutName = null;
     HttpHeadersHelper::setCurrentHeader('Cache-Control', 'no-cache, must-revalidate');
     HttpHeadersHelper::setCurrentHeader('Expires', 'Sat, 26 Jul 1997 05:00:00 GMT');
     imagepng($img);
 }
Пример #10
0
 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 static function work(&$controllerContext, &$viewContext)
 {
     $viewContext->viewName = 'user-achievements';
     $viewContext->meta->title = $viewContext->user->name . ' &#8212; Achievements (' . Media::toString($viewContext->media) . ') &#8212; ' . 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;
 }