summaryrefslogtreecommitdiff
path: root/app/Fact.php
diff options
context:
space:
mode:
Diffstat (limited to 'app/Fact.php')
-rw-r--r--app/Fact.php193
1 files changed, 127 insertions, 66 deletions
diff --git a/app/Fact.php b/app/Fact.php
index 3180a1b683..3532ddf29d 100644
--- a/app/Fact.php
+++ b/app/Fact.php
@@ -17,6 +17,7 @@ declare(strict_types=1);
namespace Fisharebest\Webtrees;
+use Closure;
use Fisharebest\Webtrees\Functions\FunctionsPrint;
use Fisharebest\Webtrees\Services\GedcomService;
use InvalidArgumentException;
@@ -156,8 +157,8 @@ class Fact
/** @var Place The place of this fact, from the “2 PLAC …” attribute */
private $place;
- /** @var int Temporary(!) variable Used by Functions::sortFacts() */
- public $sortOrder;
+ /** @var int Used by Functions::sortFacts() */
+ private $sortOrder;
/**
* Create an event object from a gedcom fragment.
@@ -579,45 +580,42 @@ class Fact
}
/**
- * Static Helper functions to sort events
+ * Helper functions to sort facts
*
- * @param Fact $a Fact one
- * @param Fact $b Fact two
- *
- * @return int
+ * @return Closure
*/
- public static function compareDate(Fact $a, Fact $b): int
+ private static function dateComparator(): Closure
{
- if ($a->date()->isOK() && $b->date()->isOK()) {
- // If both events have dates, compare by date
- $ret = Date::compare($a->date(), $b->date());
-
- if ($ret === 0) {
- // If dates are the same, compare by fact type
- $ret = self::compareType($a, $b);
+ return function (Fact $a, Fact $b): int {
+ if ($a->date()->isOK() && $b->date()->isOK()) {
+ // If both events have dates, compare by date
+ $ret = Date::compare($a->date(), $b->date());
- // If the fact type is also the same, retain the initial order
if ($ret === 0) {
- $ret = $a->sortOrder - $b->sortOrder;
+ // If dates overlap, compare by fact type
+ $ret = self::typeComparator()($a, $b);
+
+ // If the fact type is also the same, retain the initial order
+ if ($ret === 0) {
+ $ret = $a->sortOrder <=> $b->sortOrder;
+ }
}
+
+ return $ret;
}
- return $ret;
- }
+ // One or both events have no date - retain the initial order
+ return $a->sortOrder <=> $b->sortOrder;
- // One or both events have no date - retain the initial order
- return $a->sortOrder - $b->sortOrder;
+ };
}
/**
- * Static method to compare two events by their type.
+ * Helper functions to sort facts.
*
- * @param Fact $a Fact one
- * @param Fact $b Fact two
- *
- * @return int
+ * @return Closure
*/
- public static function compareType(Fact $a, Fact $b): int
+ public static function typeComparator(): Closure
{
static $factsort = [];
@@ -625,60 +623,123 @@ class Fact
$factsort = array_flip(self::FACT_ORDER);
}
- // Facts from same families stay grouped together
- // Keep MARR and DIV from the same families from mixing with events from other FAMs
- // Use the original order in which the facts were added
- if ($a->record instanceof Family && $b->record instanceof Family && $a->record !== $b->record) {
- return $a->sortOrder - $b->sortOrder;
- }
+ return function (Fact $a, Fact $b) use ($factsort): int {
+ // Facts from same families stay grouped together
+ // Keep MARR and DIV from the same families from mixing with events from other FAMs
+ // Use the original order in which the facts were added
+ if ($a->record instanceof Family && $b->record instanceof Family && $a->record !== $b->record) {
+ return $a->sortOrder - $b->sortOrder;
+ }
- $atag = $a->getTag();
- $btag = $b->getTag();
+ $atag = $a->getTag();
+ $btag = $b->getTag();
- // Events not in the above list get mapped onto one that is.
- if (!array_key_exists($atag, $factsort)) {
- if (preg_match('/^(_(BIRT|MARR|DEAT|BURI)_)/', $atag, $match)) {
- $atag = $match[1];
- } else {
- $atag = '_????_';
+ // Events not in the above list get mapped onto one that is.
+ if (!array_key_exists($atag, $factsort)) {
+ if (preg_match('/^(_(BIRT|MARR|DEAT|BURI)_)/', $atag, $match)) {
+ $atag = $match[1];
+ } else {
+ $atag = '_????_';
+ }
}
- }
- if (!array_key_exists($btag, $factsort)) {
- if (preg_match('/^(_(BIRT|MARR|DEAT|BURI)_)/', $btag, $match)) {
- $btag = $match[1];
- } else {
- $btag = '_????_';
+ if (!array_key_exists($btag, $factsort)) {
+ if (preg_match('/^(_(BIRT|MARR|DEAT|BURI)_)/', $btag, $match)) {
+ $btag = $match[1];
+ } else {
+ $btag = '_????_';
+ }
}
- }
- // - Don't let dated after DEAT/BURI facts sort non-dated facts before DEAT/BURI
- // - Treat dated after BURI facts as BURI instead
- if ($a->attribute('DATE') !== '' && $factsort[$atag] > $factsort['BURI'] && $factsort[$atag] < $factsort['CHAN']) {
- $atag = 'BURI';
- }
+ // - Don't let dated after DEAT/BURI facts sort non-dated facts before DEAT/BURI
+ // - Treat dated after BURI facts as BURI instead
+ if ($a->attribute('DATE') !== '' && $factsort[$atag] > $factsort['BURI'] && $factsort[$atag] < $factsort['CHAN']) {
+ $atag = 'BURI';
+ }
- if ($b->attribute('DATE') !== '' && $factsort[$btag] > $factsort['BURI'] && $factsort[$btag] < $factsort['CHAN']) {
- $btag = 'BURI';
- }
+ if ($b->attribute('DATE') !== '' && $factsort[$btag] > $factsort['BURI'] && $factsort[$btag] < $factsort['CHAN']) {
+ $btag = 'BURI';
+ }
+
+ $ret = $factsort[$atag] - $factsort[$btag];
- $ret = $factsort[$atag] - $factsort[$btag];
+ // If facts are the same then put dated facts before non-dated facts
+ if ($ret == 0) {
+ if ($a->attribute('DATE') !== '' && $b->attribute('DATE') === '') {
+ return -1;
+ }
+
+ if ($b->attribute('DATE') !== '' && $a->attribute('DATE') === '') {
+ return 1;
+ }
+
+ // If no sorting preference, then keep original ordering
+ $ret = $a->sortOrder - $b->sortOrder;
+ }
+
+ return $ret;
+ };
+ }
+
+ /**
+ * A multi-key sort
+ * 1. First divide the facts into two arrays one set with dates and one set without dates
+ * 2. Sort each of the two new arrays, the date using the compare date function, the non-dated
+ * using the compare type function
+ * 3. Then merge the arrays back into the original array using the compare type function
+ *
+ * @param Fact[] $unsorted
+ *
+ * @return Fact[]
+ */
+ public static function sortFacts($unsorted): array
+ {
+ $dated = [];
+ $nondated = [];
+ $sorted = [];
- // If facts are the same then put dated facts before non-dated facts
- if ($ret == 0) {
- if ($a->attribute('DATE') !== '' && $b->attribute('DATE') === '') {
- return -1;
+ // Split the array into dated and non-dated arrays
+ $order = 0;
+
+ foreach ($unsorted as $fact) {
+ $fact->sortOrder = $order;
+ $order++;
+
+ if ($fact->date()->isOK()) {
+ $dated[] = $fact;
+ } else {
+ $nondated[] = $fact;
}
+ }
+
+ usort($dated, self::dateComparator());
+ usort($nondated, self::typeComparator());
+
+ // Merge the arrays
+ $dc = count($dated);
+ $nc = count($nondated);
+ $i = 0;
+ $j = 0;
- if ($b->attribute('DATE') !== '' && $a->attribute('DATE') === '') {
- return 1;
+ // while there is anything in the dated array continue merging
+ while ($i < $dc) {
+ // compare each fact by type to merge them in order
+ if ($j < $nc && self::typeComparator()($dated[$i], $nondated[$j]) > 0) {
+ $sorted[] = $nondated[$j];
+ $j++;
+ } else {
+ $sorted[] = $dated[$i];
+ $i++;
}
+ }
- // If no sorting preference, then keep original ordering
- $ret = $a->sortOrder - $b->sortOrder;
+ // get anything that might be left in the nondated array
+ while ($j < $nc) {
+ $sorted[] = $nondated[$j];
+ $j++;
}
- return $ret;
+ return $sorted;
}
/**