Esempio n. 1
0
 /**
  * Get a list of actual surnames and variants, based on a "root" surname.
  *
  * @param Tree   $tree   only fetch individuals from this tree
  * @param string $surn   if set, only fetch people with this surname
  * @param string $salpha if set, only consider surnames starting with this letter
  * @param bool   $marnm  if set, include married names
  * @param bool   $fams   if set, only consider individuals with FAMS records
  *
  * @return array
  */
 public static function surnames(Tree $tree, $surn, $salpha, $marnm, $fams)
 {
     $sql = "SELECT SQL_CACHE n2.n_surn, n1.n_surname, n1.n_id" . " FROM `##name` n1 " . ($fams ? " JOIN `##link` ON n_id = l_from AND n_file = l_file AND l_type = 'FAMS' " : "") . " JOIN (SELECT n_surn COLLATE :collate_0 AS n_surn, n_file FROM `##name`" . " WHERE n_file = :tree_id" . ($marnm ? "" : " AND n_type != '_MARNM'");
     $args = array('tree_id' => $tree->getTreeId(), 'collate_0' => I18N::collation());
     if ($surn) {
         $sql .= " AND n_surn COLLATE :collate_1 = :surn";
         $args['collate_1'] = I18N::collation();
         $args['surn'] = $surn;
     } elseif ($salpha === ',') {
         $sql .= " AND n_surn = ''";
     } elseif ($salpha === '@') {
         $sql .= " AND n_surn = '@N.N.'";
     } elseif ($salpha) {
         $sql .= " AND " . self::getInitialSql('n_surn', $salpha);
     } else {
         // All surnames
         $sql .= " AND n_surn NOT IN ('', '@N.N.')";
     }
     $sql .= " GROUP BY n_surn COLLATE :collate_2, n_file) AS n2 ON (n1.n_surn = n2.n_surn COLLATE :collate_3 AND n1.n_file = n2.n_file)";
     $args['collate_2'] = I18N::collation();
     $args['collate_3'] = I18N::collation();
     $list = array();
     foreach (Database::prepare($sql)->execute($args)->fetchAll() as $row) {
         $list[I18N::strtoupper($row->n_surn)][$row->n_surname][$row->n_id] = true;
     }
     return $list;
 }
 /**
  * Generate the HTML content of this block.
  *
  * @param int      $block_id
  * @param bool     $template
  * @param string[] $cfg
  *
  * @return string
  */
 public function getBlock($block_id, $template = true, $cfg = array())
 {
     global $WT_TREE, $ctype;
     $COMMON_NAMES_REMOVE = $WT_TREE->getPreference('COMMON_NAMES_REMOVE');
     $COMMON_NAMES_THRESHOLD = $WT_TREE->getPreference('COMMON_NAMES_THRESHOLD');
     $num = $this->getBlockSetting($block_id, 'num', '10');
     $infoStyle = $this->getBlockSetting($block_id, 'infoStyle', 'table');
     $block = $this->getBlockSetting($block_id, 'block', '0');
     foreach (array('num', 'infoStyle', 'block') as $name) {
         if (array_key_exists($name, $cfg)) {
             ${$name} = $cfg[$name];
         }
     }
     // This next function is a bit out of date, and doesn't cope well with surname variants
     $top_surnames = FunctionsDb::getTopSurnames($WT_TREE->getTreeId(), $COMMON_NAMES_THRESHOLD, $num);
     // Remove names found in the "Remove Names" list
     if ($COMMON_NAMES_REMOVE) {
         foreach (preg_split("/[,; ]+/", $COMMON_NAMES_REMOVE) as $delname) {
             unset($top_surnames[$delname]);
             unset($top_surnames[I18N::strtoupper($delname)]);
         }
     }
     $all_surnames = array();
     $i = 0;
     foreach (array_keys($top_surnames) as $top_surname) {
         $all_surnames = array_merge($all_surnames, QueryName::surnames($WT_TREE, $top_surname, '', false, false));
         if (++$i == $num) {
             break;
         }
     }
     if ($i < $num) {
         $num = $i;
     }
     $id = $this->getName() . $block_id;
     $class = $this->getName() . '_block';
     if ($ctype === 'gedcom' && Auth::isManager($WT_TREE) || $ctype === 'user' && Auth::check()) {
         $title = '<a class="icon-admin" title="' . I18N::translate('Configure') . '" href="block_edit.php?block_id=' . $block_id . '&amp;ged=' . $WT_TREE->getNameHtml() . '&amp;ctype=' . $ctype . '"></a>';
     } else {
         $title = '';
     }
     if ($num == 1) {
         // I18N: i.e. most popular surname.
         $title .= I18N::translate('Top surname');
     } else {
         // I18N: Title for a list of the most common surnames, %s is a number. Note that a separate translation exists when %s is 1
         $title .= I18N::plural('Top %s surname', 'Top %s surnames', $num, I18N::number($num));
     }
     switch ($infoStyle) {
         case 'tagcloud':
             uksort($all_surnames, '\\Fisharebest\\Webtrees\\I18N::strcasecmp');
             $content = FunctionsPrintLists::surnameTagCloud($all_surnames, 'indilist.php', true, $WT_TREE);
             break;
         case 'list':
             uasort($all_surnames, '\\Fisharebest\\Webtrees\\Module\\TopSurnamesModule::surnameCountSort');
             $content = FunctionsPrintLists::surnameList($all_surnames, '1', true, 'indilist.php', $WT_TREE);
             break;
         case 'array':
             uasort($all_surnames, '\\Fisharebest\\Webtrees\\Module\\TopSurnamesModule::surnameCountSort');
             $content = FunctionsPrintLists::surnameList($all_surnames, '2', true, 'indilist.php', $WT_TREE);
             break;
         case 'table':
         default:
             uasort($all_surnames, '\\Fisharebest\\Webtrees\\Module\\TopSurnamesModule::surnameCountSort');
             $content = FunctionsPrintLists::surnameTable($all_surnames, 'indilist.php', $WT_TREE);
             break;
     }
     if ($template) {
         if ($block) {
             $class .= ' small_inner_block';
         }
         return Theme::theme()->formatBlock($id, $title, $class, $content);
     } else {
         return $content;
     }
 }
Esempio n. 3
0
 /**
  * Create a chart of common surnames.
  *
  * @param string[] $params
  *
  * @return string
  */
 public function chartCommonSurnames($params = array())
 {
     $WT_STATS_CHART_COLOR1 = Theme::theme()->parameter('distribution-chart-no-values');
     $WT_STATS_CHART_COLOR2 = Theme::theme()->parameter('distribution-chart-high-values');
     $WT_STATS_S_CHART_X = Theme::theme()->parameter('stats-small-chart-x');
     $WT_STATS_S_CHART_Y = Theme::theme()->parameter('stats-small-chart-y');
     if (isset($params[0]) && $params[0] != '') {
         $size = strtolower($params[0]);
     } else {
         $size = $WT_STATS_S_CHART_X . "x" . $WT_STATS_S_CHART_Y;
     }
     if (isset($params[1]) && $params[1] != '') {
         $color_from = strtolower($params[1]);
     } else {
         $color_from = $WT_STATS_CHART_COLOR1;
     }
     if (isset($params[2]) && $params[2] != '') {
         $color_to = strtolower($params[2]);
     } else {
         $color_to = $WT_STATS_CHART_COLOR2;
     }
     if (isset($params[3]) && $params[3] != '') {
         $threshold = strtolower($params[3]);
     } else {
         $threshold = $this->tree->getPreference('COMMON_NAMES_THRESHOLD');
     }
     if (isset($params[4]) && $params[4] != '') {
         $maxtoshow = strtolower($params[4]);
     } else {
         $maxtoshow = 7;
     }
     $sizes = explode('x', $size);
     $tot_indi = $this->totalIndividualsQuery();
     $surnames = FunctionsDb::getCommonSurnames($threshold, $this->tree);
     if (count($surnames) <= 0) {
         return '';
     }
     $SURNAME_TRADITION = $this->tree->getPreference('SURNAME_TRADITION');
     uasort($surnames, '\\Fisharebest\\Webtrees\\Stats::nameTotalReverseSort');
     $surnames = array_slice($surnames, 0, $maxtoshow);
     $all_surnames = array();
     foreach (array_keys($surnames) as $n => $surname) {
         if ($n >= $maxtoshow) {
             break;
         }
         $all_surnames = array_merge($all_surnames, QueryName::surnames($this->tree, I18N::strtoupper($surname), '', false, false));
     }
     $tot = 0;
     foreach ($surnames as $surname) {
         $tot += $surname['match'];
     }
     $chd = '';
     $chl = array();
     foreach ($all_surnames as $surns) {
         $count_per = 0;
         $max_name = 0;
         $top_name = '';
         foreach ($surns as $spfxsurn => $indis) {
             $per = count($indis);
             $count_per += $per;
             // select most common surname from all variants
             if ($per > $max_name) {
                 $max_name = $per;
                 $top_name = $spfxsurn;
             }
         }
         switch ($SURNAME_TRADITION) {
             case 'polish':
                 // most common surname should be in male variant (Kowalski, not Kowalska)
                 $top_name = preg_replace(array('/ska$/', '/cka$/', '/dzka$/', '/żka$/'), array('ski', 'cki', 'dzki', 'żki'), $top_name);
         }
         $per = round(100 * $count_per / $tot_indi, 0);
         $chd .= $this->arrayToExtendedEncoding(array($per));
         $chl[] = $top_name . ' - ' . I18N::number($count_per);
     }
     $per = round(100 * ($tot_indi - $tot) / $tot_indi, 0);
     $chd .= $this->arrayToExtendedEncoding(array($per));
     $chl[] = I18N::translate('Other') . ' - ' . I18N::number($tot_indi - $tot);
     $chart_title = implode(I18N::$list_separator, $chl);
     $chl = implode('|', $chl);
     return '<img src="https://chart.googleapis.com/chart?cht=p3&amp;chd=e:' . $chd . '&amp;chs=' . $size . '&amp;chco=' . $color_from . ',' . $color_to . '&amp;chf=bg,s,ffffff00&amp;chl=' . rawurlencode($chl) . '" width="' . $sizes[0] . '" height="' . $sizes[1] . '" alt="' . $chart_title . '" title="' . $chart_title . '" />';
 }
Esempio n. 4
0
 /**
  * Print a list of surnames.
  *
  * @param string[][] $surnames array (of SURN, of array of SPFX_SURN, of array of PID)
  * @param int $style 1=bullet list, 2=semicolon-separated list, 3=tabulated list with up to 4 columns
  * @param bool $totals show totals after each name
  * @param string $script indilist or famlist
  * @param Tree $tree Link back to the individual list in this tree
  *
  * @return string
  */
 public static function surnameList($surnames, $style, $totals, $script, Tree $tree)
 {
     $html = array();
     foreach ($surnames as $surn => $surns) {
         // Each surname links back to the indilist
         if ($surn) {
             $url = $script . '?surname=' . urlencode($surn) . '&amp;ged=' . $tree->getNameUrl();
         } else {
             $url = $script . '?alpha=,&amp;ged=' . $tree->getNameUrl();
         }
         // If all the surnames are just case variants, then merge them into one
         // Comment out this block if you want SMITH listed separately from Smith
         $first_spfxsurn = null;
         foreach ($surns as $spfxsurn => $indis) {
             if ($first_spfxsurn) {
                 if (I18N::strtoupper($spfxsurn) == I18N::strtoupper($first_spfxsurn)) {
                     $surns[$first_spfxsurn] = array_merge($surns[$first_spfxsurn], $surns[$spfxsurn]);
                     unset($surns[$spfxsurn]);
                 }
             } else {
                 $first_spfxsurn = $spfxsurn;
             }
         }
         $subhtml = '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml(implode(I18N::$list_separator, array_keys($surns))) . '</a>';
         if ($totals) {
             $subtotal = 0;
             foreach ($surns as $indis) {
                 $subtotal += count($indis);
             }
             $subhtml .= '&nbsp;(' . I18N::number($subtotal) . ')';
         }
         $html[] = $subhtml;
     }
     switch ($style) {
         case 1:
             return '<ul><li>' . implode('</li><li>', $html) . '</li></ul>';
         case 2:
             return implode(I18N::$list_separator, $html);
         case 3:
             $i = 0;
             $count = count($html);
             if ($count > 36) {
                 $col = 4;
             } elseif ($count > 18) {
                 $col = 3;
             } elseif ($count > 6) {
                 $col = 2;
             } else {
                 $col = 1;
             }
             $newcol = ceil($count / $col);
             $html2 = '<table class="list_table"><tr>';
             $html2 .= '<td class="list_value" style="padding: 14px;">';
             foreach ($html as $surns) {
                 $html2 .= $surns . '<br>';
                 $i++;
                 if ($i == $newcol && $i < $count) {
                     $html2 .= '</td><td class="list_value" style="padding: 14px;">';
                     $newcol = $i + ceil($count / $col);
                 }
             }
             $html2 .= '</td></tr></table>';
             return $html2;
     }
 }
Esempio n. 5
0
 /**
  * Get array of common surnames
  *
  * This function returns a simple array of the most common surnames
  * found in the individuals list.
  *
  * @param int $min The number of times a surname must occur before it is added to the array
  * @param Tree $tree
  *
  * @return mixed[][]
  */
 public static function getCommonSurnames($min, Tree $tree)
 {
     $COMMON_NAMES_ADD = $tree->getPreference('COMMON_NAMES_ADD');
     $COMMON_NAMES_REMOVE = $tree->getPreference('COMMON_NAMES_REMOVE');
     $topsurns = self::getTopSurnames($tree->getTreeId(), $min, 0);
     foreach (explode(',', $COMMON_NAMES_ADD) as $surname) {
         if ($surname && !array_key_exists($surname, $topsurns)) {
             $topsurns[$surname] = $min;
         }
     }
     foreach (explode(',', $COMMON_NAMES_REMOVE) as $surname) {
         unset($topsurns[I18N::strtoupper($surname)]);
     }
     //-- check if we found some, else recurse
     if (empty($topsurns) && $min > 2) {
         return self::getCommonSurnames($min / 2, $tree);
     } else {
         uksort($topsurns, '\\Fisharebest\\Webtrees\\I18N::strcasecmp');
         foreach ($topsurns as $key => $value) {
             $topsurns[$key] = array('name' => $key, 'match' => $value);
         }
         return $topsurns;
     }
 }
Esempio n. 6
0
 /**
  * Create a chart of common surnames.
  *
  * @param string[] $params
  *
  * @return string
  */
 public function chartCommonSurnames($params = array())
 {
     $WT_STATS_CHART_COLOR1 = Theme::theme()->parameter('distribution-chart-no-values');
     $WT_STATS_CHART_COLOR2 = Theme::theme()->parameter('distribution-chart-high-values');
     $WT_STATS_S_CHART_X = Theme::theme()->parameter('stats-small-chart-x');
     $WT_STATS_S_CHART_Y = Theme::theme()->parameter('stats-small-chart-y');
     $size = empty($params[0]) ? $WT_STATS_S_CHART_X . "x" . $WT_STATS_S_CHART_Y : strtolower($params[0]);
     $color_from = empty($params[1]) ? $WT_STATS_CHART_COLOR1 : strtolower($params[1]);
     $color_to = empty($params[2]) ? $WT_STATS_CHART_COLOR2 : strtolower($params[2]);
     $number_of_surnames = empty($params[3]) ? 10 : (int) $params[3];
     $sizes = explode('x', $size);
     $tot_indi = $this->totalIndividualsQuery();
     $surnames = FunctionsDb::getTopSurnames($this->tree->getTreeId(), 0, $number_of_surnames);
     if (empty($surnames)) {
         return '';
     }
     $SURNAME_TRADITION = $this->tree->getPreference('SURNAME_TRADITION');
     $all_surnames = array();
     $tot = 0;
     foreach ($surnames as $surname => $num) {
         $all_surnames = array_merge($all_surnames, QueryName::surnames($this->tree, I18N::strtoupper($surname), '', false, false));
         $tot += $num;
     }
     $chd = '';
     $chl = array();
     foreach ($all_surnames as $surns) {
         $count_per = 0;
         $max_name = 0;
         $top_name = '';
         foreach ($surns as $spfxsurn => $indis) {
             $per = count($indis);
             $count_per += $per;
             // select most common surname from all variants
             if ($per > $max_name) {
                 $max_name = $per;
                 $top_name = $spfxsurn;
             }
         }
         switch ($SURNAME_TRADITION) {
             case 'polish':
                 // most common surname should be in male variant (Kowalski, not Kowalska)
                 $top_name = preg_replace(array('/ska$/', '/cka$/', '/dzka$/', '/żka$/'), array('ski', 'cki', 'dzki', 'żki'), $top_name);
         }
         $per = round(100 * $count_per / $tot_indi, 0);
         $chd .= $this->arrayToExtendedEncoding(array($per));
         $chl[] = $top_name . ' - ' . I18N::number($count_per);
     }
     $per = round(100 * ($tot_indi - $tot) / $tot_indi, 0);
     $chd .= $this->arrayToExtendedEncoding(array($per));
     $chl[] = I18N::translate('Other') . ' - ' . I18N::number($tot_indi - $tot);
     $chart_title = implode(I18N::$list_separator, $chl);
     $chl = implode('|', $chl);
     return '<img src="https://chart.googleapis.com/chart?cht=p3&amp;chd=e:' . $chd . '&amp;chs=' . $size . '&amp;chco=' . $color_from . ',' . $color_to . '&amp;chf=bg,s,ffffff00&amp;chl=' . rawurlencode($chl) . '" width="' . $sizes[0] . '" height="' . $sizes[1] . '" alt="' . $chart_title . '" title="' . $chart_title . '" />';
 }
Esempio n. 7
0
 /**
  * Generate the HTML content of this block.
  *
  * @param int      $block_id
  * @param bool     $template
  * @param string[] $cfg
  *
  * @return string
  */
 public function getBlock($block_id, $template = true, $cfg = array())
 {
     global $WT_TREE, $ctype;
     $COMMON_NAMES_REMOVE = $WT_TREE->getPreference('COMMON_NAMES_REMOVE');
     $COMMON_NAMES_THRESHOLD = $WT_TREE->getPreference('COMMON_NAMES_THRESHOLD');
     $num = $this->getBlockSetting($block_id, 'num', '10');
     // The “Minimum number of occurrences” input field from Control Panel’s Preferences page
     // has to be copied here otherwise user doesn't understand why the result is different
     // than expected (input of add and remove surnames may also be copied to make this module
     // indenpendent from module statistics; it is not copied yet).  The data of original input
     // field is still used in the bottom of the statistics module
     $threshold = $this->getBlockSetting($block_id, 'threshold', '5');
     $infoStyle = $this->getBlockSetting($block_id, 'infoStyle', 'table');
     $block = $this->getBlockSetting($block_id, 'block', '0');
     foreach (array('num', 'infoStyle', 'block') as $name) {
         if (array_key_exists($name, $cfg)) {
             ${$name} = $cfg[$name];
         }
     }
     // This next function is a bit out of date, and doesn't cope well with surname variants
     // First defining the upper limit of surname occurrences to get at least one row in
     // result list if user sets too high number in “Minimum number of occurrences” field.
     // Without this definition a short message “No data available in table” appears by
     // webtrees\packages\datatables-1.10.7\js\jquery.dataTables.min.js JavaScript file
     // without the possibility of translating (and only if presentation style is table;
     // all other styles result no message at all)
     $top_surnames_in_DB = FunctionsDb::getCommonSurnames($WT_TREE->getPreference('COMMON_NAMES_THRESHOLD'), $WT_TREE);
     $max_occurrences = 0;
     foreach ($top_surnames_in_DB as $array) {
         if ($array['match'] > $max_occurrences) {
             $max_occurrences = $array['match'];
         }
     }
     if ($threshold > $max_occurrences) {
         $threshold = $max_occurrences;
     }
     $top_surnames = FunctionsDb::getTopSurnames($WT_TREE->getTreeId(), $threshold, $num);
     // Remove names found in the "Remove Names" list
     if ($COMMON_NAMES_REMOVE) {
         foreach (preg_split("/[,; ]+/", $COMMON_NAMES_REMOVE) as $delname) {
             unset($top_surnames[$delname]);
             unset($top_surnames[I18N::strtoupper($delname)]);
         }
     }
     $all_surnames = array();
     $i = 0;
     foreach (array_keys($top_surnames) as $top_surname) {
         $all_surnames = array_merge($all_surnames, QueryName::surnames($WT_TREE, $top_surname, '', false, false));
         if (++$i == $num) {
             break;
         }
     }
     if ($i < $num) {
         $num = $i;
     }
     $id = $this->getName() . $block_id;
     $class = $this->getName() . '_block';
     if ($ctype === 'gedcom' && Auth::isManager($WT_TREE) || $ctype === 'user' && Auth::check()) {
         $title = '<a class="icon-admin" title="' . I18N::translate('Configure') . '" href="block_edit.php?block_id=' . $block_id . '&amp;ged=' . $WT_TREE->getNameHtml() . '&amp;ctype=' . $ctype . '"></a>';
     } else {
         $title = '';
     }
     if ($num == 1) {
         // I18N: i.e. most popular surname.
         $title .= I18N::translate('Top surname');
     } else {
         // I18N: Title for a list of the most common surnames, %s is a number.  Note that a separate translation exists when %s is 1
         $title .= I18N::plural('Top %s surname', 'Top %s surnames', $num, I18N::number($num));
     }
     switch ($infoStyle) {
         case 'tagcloud':
             uksort($all_surnames, '\\Fisharebest\\Webtrees\\I18N::strcasecmp');
             $content = FunctionsPrintLists::surnameTagCloud($all_surnames, 'indilist.php', true, $WT_TREE);
             break;
         case 'list':
             uasort($all_surnames, '\\Fisharebest\\Webtrees\\Module\\TopSurnamesModule::surnameCountSort');
             $content = FunctionsPrintLists::surnameList($all_surnames, '1', true, 'indilist.php', $WT_TREE);
             break;
         case 'array':
             uasort($all_surnames, '\\Fisharebest\\Webtrees\\Module\\TopSurnamesModule::surnameCountSort');
             $content = FunctionsPrintLists::surnameList($all_surnames, '2', true, 'indilist.php', $WT_TREE);
             break;
         case 'table':
         default:
             uasort($all_surnames, '\\Fisharebest\\Webtrees\\Module\\TopSurnamesModule::surnameCountSort');
             $content = FunctionsPrintLists::surnameTable($all_surnames, 'indilist.php', $WT_TREE);
             break;
     }
     if ($template) {
         if ($block) {
             $class .= ' small_inner_block';
         }
         return Theme::theme()->formatBlock($id, $title, $class, $content);
     } else {
         return $content;
     }
 }
Esempio n. 8
0
 /**
  * Search the repositories
  *
  * @param string[] $query Search terms
  * @param Tree[] $trees The trees to search
  *
  * @return Repository[]
  */
 public static function searchRepositories(array $query, array $trees)
 {
     // Convert the query into a regular expression
     $queryregex = array();
     $sql = "SELECT o_id AS xref, o_file AS gedcom_id, o_gedcom AS gedcom FROM `##other` WHERE o_type = 'REPO'";
     $args = array();
     foreach ($query as $n => $q) {
         $queryregex[] = preg_quote(I18N::strtoupper($q), '/');
         $sql .= " AND o_gedcom COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')";
         $args['collate_' . $n] = I18N::collation();
         $args['query_' . $n] = Filter::escapeLike($q);
     }
     $sql .= " AND o_file IN (";
     foreach ($trees as $n => $tree) {
         $sql .= $n ? ", " : "";
         $sql .= ":tree_id_" . $n;
         $args['tree_id_' . $n] = $tree->getTreeId();
     }
     $sql .= ")";
     $list = array();
     $rows = Database::prepare($sql)->execute($args)->fetchAll();
     foreach ($rows as $row) {
         // SQL may have matched on private data or gedcom tags, so check again against privatized data.
         $record = Repository::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
         // Ignore non-genealogy data
         $gedrec = preg_replace('/\\n\\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|RESN) .*/', '', $record->getGedcom());
         // Ignore links and tags
         $gedrec = preg_replace('/\\n\\d ' . WT_REGEX_TAG . '( @' . WT_REGEX_XREF . '@)?/', '', $gedrec);
         // Ignore tags
         $gedrec = preg_replace('/\\n\\d ' . WT_REGEX_TAG . ' ?/', '', $gedrec);
         // Re-apply the filtering
         $gedrec = I18N::strtoupper($gedrec);
         foreach ($queryregex as $regex) {
             if (!preg_match('/' . $regex . '/', $gedrec)) {
                 continue 2;
             }
         }
         $list[] = $record;
     }
     $list = array_filter($list, function (Repository $x) {
         return $x->canShowName();
     });
     return $list;
 }