/** * Return HTML for displaying search results * @return string HTML */ public function getCompareResults() { global $wgUser; $this->setMergeTargets(); list($compareData, $compareChildren, $maxChildren) = $this->readCompareData(); $dataLabels = $this->getDataLabels(); list($compareClass, $husbandScores, $wifeScores, $totalScores) = $this->scoreCompareData($dataLabels, $compareData); $output = ''; $semiProtected = false; $isLoggedIn = $wgUser->isLoggedIn(); if (!$isLoggedIn) { $output .= '<p><font color=red>You need to sign in to match and merge pages</font></p>'; } $gedcomExtra = $this->gedcomDataString ? '?gedcomtab=' . htmlspecialchars($this->gedcomTab) . '&gedcomkey=' . htmlspecialchars($this->gedcomKey) : ''; $output .= "<form id=\"compare\" name=\"compare\" action=\"/wiki/Special:Merge{$gedcomExtra}\" method=\"post\">" . "<input type=\"hidden\" id=\"ns\" name=\"ns\" value=\"{$this->namespace}\">" . "<input type=\"hidden\" id=\"maxpages\" name=\"maxpages\" value=\"" . count($this->compareTitles) . "\">" . ($this->namespace == 'Family' ? "<input id=\"maxchildren\" type=\"hidden\" name=\"maxchildren\" value=\"{$maxChildren}\">" : '') . '<table border="0" cellspacing="0" cellpadding="4">'; foreach ($dataLabels as $label) { $labelClass = CompareForm::getLabelClass($label); if ($labelClass == self::$COMPARE_ULC_CLASS) { $output .= CompareForm::insertEmptyRow(count($this->compareTitles) + 1); } $found = !in_array($label, self::$OPTIONAL_LABELS); if (!$found) { foreach ($this->compareTitles as $t) { if (is_array(@$compareData[$t][$label]) && count($compareData[$t][$label]) > 0) { $found = true; break; } } } if ($found) { $output .= "<tr><td class=\"{$labelClass}\">" . CompareForm::formatLabel($label) . "</td>"; // show target left if gedcom; right otherwise $i = $this->gedcomDataString ? 0 : count($this->compareTitles) - 1; while ($this->gedcomDataString && $i < count($this->compareTitles) || !$this->gedcomDataString && $i >= 0) { $t = $this->compareTitles[$i]; $class = $compareClass[$label][$i]; $output .= "<td class=\"{$class}\">"; $first = true; if (is_array(@$compareData[$t][$label])) { foreach ($compareData[$t][$label] as $value) { if (!$first) { $output .= '<br>'; } $output .= CompareForm::formatValue($label, $value); $first = false; } } if ($first) { $output .= ' '; } if ($class == self::$COMPARE_PAGE_CLASS) { $titlesCnt = count(@$compareData[$t][$label]); $baseTitlesCnt = count(@$compareData[$this->compareTitles[0]][$label]); if ($titlesCnt > 0) { $value = htmlspecialchars($compareData[$t][$label][0]); if ($label == 'husbandTitle') { $relative = 'husband'; $relativeAbbrev = 'h'; $score = $husbandScores[$i]; } else { if ($label == 'wifeTitle') { $relative = 'wife'; $relativeAbbrev = 'w'; $score = $wifeScores[$i]; } else { $relative = ''; $relativeAbbrev = ''; $score = 0; } } if (!$compareData[$t]['Exists']) { // base person/family not found if (!$relative) { $output .= '<br>Not found'; } } else { if ($compareData[$t]['Redirect']) { // base person/family has been merged if (!$relative) { $output .= '<br>Already merged'; } } else { if ($compareData[$t][$relative . 'Redirect']) { // husband/wife has been merged $output .= '<br>Already merged'; } else { if ($i == 0 && $this->gedcomDataString) { if (@$compareData[$t][$relative . 'GedcomMatchTitle']) { $skin =& $wgUser->getSkin(); $gedcomMatchTitle = $compareData[$t][$relative . 'GedcomMatchTitle']; $temp = Title::newFromText($gedcomMatchTitle, StructuredData::endsWith($label, 'familyTitle', true) ? NS_FAMILY : NS_PERSON); $title = $skin->makeLinkObj($temp, htmlspecialchars($gedcomMatchTitle)); $output .= "<br>Matched with {$title}"; } $output .= "<input type=\"hidden\" id=\"m{$relativeAbbrev}_{$i}\" name=\"m{$relativeAbbrev}_{$i}\" value=\"{$value}\">"; } else { if ($titlesCnt == 1 && $baseTitlesCnt <= 1) { // if ($i == 0 || $baseTitlesCnt == 0 || $compareData[$this->compareTitles[0]][$label][0] != $compareData[$t][$label][0]) { // don't allow merge if same title if ($i == 0) { $extra = " checked"; } else { if ($relative) { // always check spouses $extra = 'disabled checked'; //if ($score > self::$SPOUSE_MATCH_THRESHOLD) $extra .= ' checked'; } else { $extra = ''; } } if ($this->namespace == 'Family' && !$relative) { $extra .= " onClick=\"compareClick({$i})\""; } $output .= "<br><input id=\"m{$relativeAbbrev}_{$i}\" type=\"checkbox\" name=\"m{$relativeAbbrev}_{$i}\" value=\"{$value}\" {$extra}> Match"; // } } else { $output .= "<br>Multiple spouses - merge after merging family"; } } } } } $nomergeTitles = $this->getNomergeTitleMatches($compareData, $t, -1, $relative, '<br>'); if ($nomergeTitles) { $output .= '<br><b>Do not merge with</b><br>' . $nomergeTitles; } if ((!$this->gedcomDataString || $i > 0) && !$compareData[$t][$relative . 'Updatable']) { $output .= "<br><font color=\"red\">Semi-protected</font> (see below)"; $semiProtected = true; } } } $output .= '</td>'; $i += $this->gedcomDataString ? 1 : -1; } $output .= '</tr>'; } } if ($this->namespace == 'Family') { for ($c = 0; $c < $maxChildren; $c++) { foreach (CompareForm::$CHILD_COMPARE_LABELS as $label) { $labelClass = CompareForm::getLabelClass($label); if ($labelClass == self::$COMPARE_ULC_CLASS) { $output .= CompareForm::insertEmptyRow(count($this->compareTitles) + 1); } $found = !in_array($label, self::$OPTIONAL_LABELS); if (!$found) { foreach ($this->compareTitles as $t) { if (is_array(@$compareChildren[$t][$c][$label]) && count($compareChildren[$t][$c][$label]) > 0) { $found = true; break; } } } if ($found) { $output .= "<tr><td class=\"{$labelClass}\">" . CompareForm::formatLabel($label, $c + 1) . "</td>"; $baseStdValues =& CompareForm::standardizeValues($label, @$compareChildren[$this->compareTitles[0]][$c][$label]); $i = $this->gedcomDataString ? 0 : count($this->compareTitles) - 1; while ($this->gedcomDataString && $i < count($this->compareTitles) || !$this->gedcomDataString && $i >= 0) { $t = $this->compareTitles[$i]; if ($i == 0) { $stdValues = $baseStdValues; } else { $stdValues =& CompareForm::standardizeValues($label, @$compareChildren[$t][$c][$label]); } list($score, $class) = CompareForm::getCompareScoreClass($i == 0, $label, $baseStdValues, $stdValues); $output .= "<td class=\"{$class}\">"; $children =& $compareChildren[$t]; $first = true; if (count($children) > $c) { if (is_array(@$children[$c][$label])) { foreach ($children[$c][$label] as $value) { if (!$first) { $output .= '<br>'; } $output .= CompareForm::formatValue($label, $value); $first = false; } } } if ($first) { $output .= ' '; } if ($class == self::$COMPARE_PAGE_CLASS) { if (count(@$compareChildren[$t][$c][$label]) == 1) { $value = htmlspecialchars($compareChildren[$t][$c][$label][0]); $output .= "<input type=\"hidden\" id=\"mc_{$i}_{$c}\" name=\"mc_{$i}_{$c}\" value=\"{$value}\">"; if ($compareData[$t]['Redirect']) { // family has already been merged } else { if ($compareChildren[$t][$c]['childRedirect']) { // child has already been merged $output .= '<br>Already merged'; } else { if ($i == 0 && $this->gedcomDataString) { if (@$compareChildren[$t][$c]['childGedcomMatchTitle']) { $skin =& $wgUser->getSkin(); $gedcomMatchTitle = $compareChildren[$t][$c]['childGedcomMatchTitle']; $temp = Title::newFromText($gedcomMatchTitle, StructuredData::endsWith($label, 'familyTitle', true) ? NS_FAMILY : NS_PERSON); $title = $skin->makeLinkObj($temp, htmlspecialchars($gedcomMatchTitle)); $output .= "<br>Matched with {$title}"; } $c1 = $c + 1; $output .= "<input type=\"hidden\" id=\"mcr_0_{$c}\" name=\"mcr_0_{$c}\" value=\"{$c1}\">"; } else { // else if (@$compareChildren[$this->compareTitles[0]][$c][$label][0] != $compareChildren[$t][$c][$label][0]) { // don't allow merge if same title // $mergeChild = (count(@$compareChildren[$this->compareTitles[0]][$c][$label]) == 1 ? $c+1 : 0); $mergeChild = $c + 1; $extra = $i == 0 ? '' : 'disabled'; $mergeChildSelectOptions = $this->getMergeChildSelectOptions($c, $maxChildren); $output .= '<br>' . StructuredData::addSelectToHtml(0, "mcr_{$i}_{$c}", $mergeChildSelectOptions, $mergeChild, $extra, false); } } } $nomergeTitles = $this->getNomergeTitleMatches($compareChildren, $t, $c, 'child', '<br>'); if ($nomergeTitles) { $output .= '<br><b>Do not merge with</b><br>' . $nomergeTitles; } if ((!$this->gedcomDataString || $i > 0) && !$compareChildren[$t][$c]['childUpdatable']) { $output .= "<br><font color=\"red\">Semi-protected</font> (see below)"; $semiProtected = true; } } } $output .= '</td>'; $i += $this->gedcomDataString ? 1 : -1; } $output .= '</tr>'; } } } } if ($this->gedcomDataString) { $mergeLabel = 'Prepare to update'; $mergeFunction = 'doGedcomPrepareToMerge()'; $notMatchFunction = 'doGedcomNotMatch()'; $notMatchTitle = 'GEDCOM family does not match any of the families shown'; } else { $mergeLabel = 'Prepare to merge'; $mergeFunction = 'doPrepareToMerge()'; $notMatchFunction = 'doNotMatch()'; $notMatchTitle = 'Notify others not to merge the selected ' . ($this->namespace == 'Family' ? 'families' : 'people'); } $mergeTitle = 'Prepare to combine the selected ' . ($this->namespace == 'Family' ? 'families' : 'people'); $output .= '<tr><td align=right colspan="' . (count($this->compareTitles) + 1) . '"><input type="hidden" name="formAction">' . ($this->gedcomDataString ? '<input type="button" title="Match people in your GEDCOM to people in the selected family" value="Match" onClick="doGedcomMatch()"/> ' : '') . ($this->gedcomDataString ? '' : "<input type=\"button\" title=\"{$mergeTitle}\" value=\"{$mergeLabel}\" onClick=\"{$mergeFunction}\"/> ") . "<input type=\"button\" title=\"{$notMatchTitle}\" value=\"Not a match\" onClick=\"{$notMatchFunction}\"/>"; '</td></tr></table></form>'; if ($semiProtected) { $output .= CompareForm::getSemiprotectedMessage(CompareForm::isTrustedMerger($wgUser, $this->gedcomDataString)); } return $output; }
public function doMerge() { global $wgOut, $wgUser; $skin =& $wgUser->getSkin(); if ($this->isGedcom()) { $editFlags = EDIT_UPDATE; $mergeText = 'updated'; } else { // create a mergelog record $mergeScore = $this->getMergeScore(); $isTrustedUser = CompareForm::isTrustedMerger($wgUser, false); $isTrustedMerge = MergeForm::isTrustedMerge($mergeScore, $isTrustedUser); $mergeLogId = $this->logMerge($mergeScore, $isTrustedMerge); wfDebug("MERGESCORE mergeLogId={$mergeLogId} total score={$mergeScore}\n"); if (!$isTrustedUser && $mergeScore < self::$LOW_MATCH_THRESHOLD) { error_log("WARNING suspect merge: id={$mergeLogId} user={$wgUser->getName()} score={$mergeScore}"); } $editFlags = EDIT_UPDATE | EDIT_FORCE_BOT; $mergeText = 'merged'; } // get merging people and families // add merging people and families to blacklist so propagation doesn't also try to update them $mergingPeople = array(); $mergingFamilies = array(); for ($m = 0; $m < count($this->data); $m++) { for ($p = 0; $p < count($this->data[$m]); $p++) { $titleString = $this->data[$m][$p]['title']; if ($this->namespace == 'Family' && $m == 0) { if ($p > 0) { $mergingFamilies[$titleString] = $this->data[$m][0]['title']; } $ns = NS_FAMILY; } else { if ($p > 0) { $mergingPeople[$titleString] = $this->data[$m][0]['title']; } $ns = NS_PERSON; } if (!GedcomUtil::isGedcomTitle($titleString)) { $title = Title::newFromText($titleString, $ns); PropagationManager::addBlacklistPage($title); } } } $output = "<H2>Pages {$mergeText} successfully</H2>"; $output .= $this->getWarnings(); $output .= '<ul>'; $outputRows = array(); $emptyRequest = new FauxRequest(array(), true); $mergeCmtSuffix = $this->isGedcom() ? '' : " - [[Special:ReviewMerge/{$mergeLogId}|review/undo]]"; if ($this->namespace == 'Family' && !$this->isGedcom()) { $t = Title::newFromText($this->data[0][0]['title'], NS_FAMILY); $mergeCmtFamily = $this->namespace == 'Family' ? " in merge of [[{$t->getPrefixedText()}]]" : ''; } else { $mergeCmtFamily = ''; } // backwards, because you must merge family last, so that propagated person data in family xml is correct // and so that mergeCmtFamily can be cleared at the end and mergeSummary and mergeTargetTitle are correct after the for loop for ($m = count($this->data) - 1; $m >= 0; $m--) { $requestData = array(); $contents = ''; $talkContents = ''; $outputRow = ''; $mainOutput = ''; $talkOutput = ''; $nameCount = $eventCount = $sourceCount = $imageCount = $noteCount = $husbandCount = $wifeCount = $childrenCount = $parentFamilyCount = $spouseFamilyCount = 0; $primaryNameFound = $primaryImageFound = $birthFound = $christeningFound = $deathFound = $burialFound = $marriageFound = false; if ($this->namespace == 'Family' && $m == 0) { $mergeTargetNs = NS_FAMILY; $mergeTargetTalkNs = NS_FAMILY_TALK; $mergeCmtFamily = ''; } else { $mergeTargetNs = NS_PERSON; $mergeTargetTalkNs = NS_PERSON_TALK; } $mergeTargetTitle = Title::newFromText($this->data[$m][0]['title'], $mergeTargetNs); if ($mergeTargetTitle->getNamespace() != $mergeTargetNs) { error_log("Merge glitch:{$mergeTargetNs}:{$this->data[$m][0]['title']}:{$mergeTargetTitle->getNamespace()}:"); } $mergeTargetTalkTitle = Title::newFromText($this->data[$m][0]['title'], $mergeTargetTalkNs); $mergeSummary = ''; $talkMergeSummary = ''; $keepKeys = $this->generateKeepKeys($this->data[$m], $this->add[$m], $this->key[$m]); $notesMap = array(); $noteAdoptions = array(); $this->generateMapAdoptions('notes', 'N', $mergeTargetNs, $this->data[$m], $this->add[$m], $this->key[$m], $keepKeys, $notesMap, $noteAdoptions); $sourcesMap = array(); $sourceAdoptions = array(); $this->generateMapAdoptions('sources', 'S', $mergeTargetNs, $this->data[$m], $this->add[$m], $this->key[$m], $keepKeys, $sourcesMap, $sourceAdoptions); $imagesMap = array(); $imageAdoptions = array(); $this->generateMapAdoptions('images', 'I', $mergeTargetNs, $this->data[$m], $this->add[$m], $this->key[$m], $keepKeys, $imagesMap, $imageAdoptions); // get request data for merge target for ($p = 0; $p < count($this->data[$m]); $p++) { if ($this->isMergeable($this->data[$m][$p])) { $this->addNotesToRequestData($requestData, $keepKeys[$p], $noteCount, $notesMap[$p], $this->data[$m][$p]['notes']); $this->addImagesToRequestData($requestData, $keepKeys[$p], $imageCount, $imagesMap[$p], $primaryImageFound, $this->data[$m][$p]['images']); $this->addSourcesToRequestData($requestData, $keepKeys[$p], $sourceCount, $sourcesMap[$p], $notesMap[$p], $imagesMap[$p], $this->data[$m][$p]['sources'], $noteAdoptions, $imageAdoptions); $this->addEventsToRequestData($requestData, $keepKeys[$p], $eventCount, $mergeTargetNs == NS_PERSON ? Person::$STD_EVENT_TYPES : Family::$STD_EVENT_TYPES, $birthFound, $christeningFound, $deathFound, $burialFound, $marriageFound, $notesMap[$p], $imagesMap[$p], $sourcesMap[$p], $this->data[$m][$p]['events'], $noteAdoptions, $sourceAdoptions, $imageAdoptions); if ($mergeTargetNs == NS_PERSON) { $this->addNamesToRequestData($requestData, $keepKeys[$p], $nameCount, $primaryNameFound, $notesMap[$p], $sourcesMap[$p], $this->data[$m][$p]['names'], $noteAdoptions, $sourceAdoptions); $this->addFamilyToRequestData($requestData, $mergingFamilies, 'child_of_family', $parentFamilyCount, $this->data[$m][$p]['child_of_families']); $this->addFamilyToRequestData($requestData, $mergingFamilies, 'spouse_of_family', $spouseFamilyCount, $this->data[$m][$p]['spouse_of_families']); } else { $this->addFamilyMembersToRequestData($requestData, $mergingPeople, 'husband', $husbandCount, $this->data[$m][$p]['husbands']); $this->addFamilyMembersToRequestData($requestData, $mergingPeople, 'wife', $wifeCount, $this->data[$m][$p]['wives']); $this->addFamilyMembersToRequestData($requestData, $mergingPeople, 'child', $childrenCount, $this->data[$m][$p]['children']); } $pageContents = $this->mapContents($sourcesMap[$p], $imagesMap[$p], $notesMap[$p], $this->data[$m][$p]['contents']); $this->addContents($contents, $keepKeys[$p], $pageContents); if ($p > 0) { if ($mergeSummary) { $mergeSummary .= ', '; } if ($mainOutput) { $mainOutput .= ', '; } if ($this->data[$m][$p]['gedcom']) { $mergeSummary .= 'gedcom'; $mainOutput .= htmlspecialchars(($mergeTargetNs == NS_FAMILY ? 'Family:' : 'Person:') . $this->data[$m][$p]['title']); } else { $title = Title::newFromText($this->data[$m][$p]['title'], $mergeTargetNs); $mergeSummary .= "[[" . $title->getPrefixedText() . "]]"; $mainOutput .= $skin->makeKnownLinkObj($title, htmlspecialchars($title->getPrefixedText()), 'redirect=no'); } } } } // redirect other pages to merge target $redir = "#REDIRECT [[" . $mergeTargetTitle->getPrefixedText() . "]]"; $talkRedir = "#REDIRECT [[" . $mergeTargetTalkTitle->getPrefixedText() . "]]"; for ($p = 1; $p < count($this->data[$m]); $p++) { if (!$this->data[$m][$p]['gedcom']) { $obj = $this->data[$m][$p]['object']; $comment = $this->makeComment($this->userComment, "merge into [[" . $mergeTargetTitle->getPrefixedText() . "]]" . $mergeCmtFamily, $mergeCmtSuffix); $obj->editPage($emptyRequest, $redir, $comment, $editFlags, false); // redir talk page as well if ($this->data[$m][$p]['talkrevid']) { // if talk page exists $talkTitle = Title::newFromText($this->data[$m][$p]['title'], $mergeTargetTalkNs); $article = new Article($talkTitle, 0); if ($article) { $this->addTalkContents($talkContents, $talkTitle, $article->fetchContent()); if ($talkMergeSummary) { $talkMergeSummary .= ', '; } if ($talkOutput) { $talkOutput .= ', '; } $talkMergeSummary .= "[[" . $talkTitle->getPrefixedText() . "]]"; $talkOutput .= $skin->makeKnownLinkObj($talkTitle, htmlspecialchars($talkTitle->getPrefixedText()), 'redirect=no'); $comment = $this->makeComment($this->userComment, "merge into [[" . $mergeTargetTalkTitle->getPrefixedText() . "]]" . $mergeCmtFamily, $mergeCmtSuffix); $article->doEdit($talkRedir, $comment, $editFlags); } } } } // update merge target talk if ($talkContents) { $article = new Article($mergeTargetTalkTitle, 0); if ($article) { $mergeTargetTalkContents = $article->fetchContent(); if ($mergeTargetTalkContents) { $mergeTargetTalkContents = rtrim($mergeTargetTalkContents) . "\n\n"; } $comment = $this->makeComment($this->userComment, 'merged with ' . $talkMergeSummary . $mergeCmtFamily, $mergeCmtSuffix); $article->doEdit($mergeTargetTalkContents . $talkContents, $comment, $editFlags); if ($this->addWatches) { StructuredData::addWatch($wgUser, $article, true); } } $outputRow .= '<li>Merged ' . $talkOutput . ' into ' . $skin->makeKnownLinkObj($mergeTargetTalkTitle, htmlspecialchars($mergeTargetTalkTitle->getPrefixedText())) . "</li>"; } $obj = $this->data[$m][0]['object']; if ($mergeTargetNs == NS_PERSON) { Person::addGenderToRequestData($requestData, $this->data[$m][0]['gender']); } else { // family $obj->isMerging(true); // to read propagated data from person pages, not from prev family revision } // update merge target $req = new FauxRequest($requestData, true); $comment = $this->makeComment($this->userComment, ($mergeSummary == 'gedcom' ? 'Add data from gedcom' : 'merged with ' . $mergeSummary) . $mergeCmtFamily, $mergeCmtSuffix); $obj->editPage($req, $contents, $comment, $editFlags, $this->addWatches); $outputRow .= '<li>Merged ' . $mainOutput . ' into ' . $skin->makeKnownLinkObj($mergeTargetTitle, htmlspecialchars($mergeTargetTitle->getPrefixedText())) . "</li>"; $outputRows[] = $outputRow; } // add log and recent changes if (!$this->isGedcom()) { if (!$mergeSummary) { $mergeSummary = 'members of other families'; } $mergeComment = 'Merge [[' . $mergeTargetTitle->getPrefixedText() . ']] and ' . $mergeSummary; $log = new LogPage('merge', false); $t = Title::makeTitle(NS_SPECIAL, "ReviewMerge/{$mergeLogId}"); $log->addEntry('merge', $t, $mergeComment); RecentChange::notifyLog(wfTimestampNow(), $t, $wgUser, $mergeComment, '', 'merge', 'merge', $t->getPrefixedText(), $mergeComment, '', $isTrustedMerge, 0); } $nonmergedPages = $this->getNonmergedPages(); $output .= join("\n", array_reverse($outputRows)) . '</ul>' . ($nonmergedPages ? '<p>In addition to the people listed above, the following have also been included in the target family' . ($this->isGedcom() ? '<br/>(GEDCOM people listed will be added when the GEDCOM is imported)' : '') . $nonmergedPages . "</p>\n" : '') . ($this->isGedcom() ? '' : '<p>' . $skin->makeKnownLinkObj(Title::makeTitle(NS_SPECIAL, 'ReviewMerge/' . $mergeLogId), htmlspecialchars("Review/undo merge")) . '<br>' . $skin->makeKnownLinkObj(Title::makeTitle(NS_SPECIAL, 'ShowDuplicates'), htmlspecialchars("Show more duplicates")) . '</p>'); return $output; }
function writeMerge($userid, &$userMerge) { $foundFamilyTitle = null; $foundPeopleCount = 0; $familyCount = 0; $mergeCount = count($userMerge->merges); $preMergeTimestamp = wfTimestamp(TS_MW, wfTimestamp(TS_UNIX, $userMerge->firstTimestamp) - 2); foreach ($userMerge->merges as $merge) { $mergeNs = $merge->target->title->getNamespace(); if ($mergeNs != NS_PERSON && $mergeNs != NS_FAMILY && $mergeNs != NS_PERSON_TALK && $mergeNs != NS_FAMILY_TALK) { return "invalid target for merge: title={$merge->target->title->getPrefixedText()}"; } if ($foundFamilyTitle && $mergeNs == NS_FAMILY) { return "invalid merge sequence - {$merge->target->title->getPrefixedText()} after family: {$foundFamilyTitle->getPrefixedText()}"; } if ($mergeNs == NS_FAMILY) { $familyCount = count($merge->sources) + 1; $foundFamilyTitle = $merge->target->title; } else { if ($mergeNs == NS_PERSON) { $foundPeopleCount++; } } // get target preRevid $revid = StructuredData::getRevidForTimestamp($merge->target->title, $preMergeTimestamp); // its ok if revid was created during the merge for talk pages if ($revid == 0 || $revid > $userMerge->lastRevid || $revid >= $userMerge->firstRevid && $mergeNs != NS_PERSON_TALK && $mergeNs != NS_FAMILY_TALK) { return "revision not found for target={$merge->target->title->getPrefixedText()} revid={$revid} firstRevid={$userMerge->firstRevid} timestamp={$preMergeTimestamp}"; } $merge->target->preRevid = $revid >= $userMerge->firstRevid ? 0 : $revid; // 0 if created during the merge $revision = Revision::newFromId($revid); $merge->target->revPage = $revision->getPage(); // get target postRevid and comment list($revid, $comment) = $this->getNextRevidComment($merge->target->revPage, $merge->target->preRevid); if ($revid != 0 && $revid <= $userMerge->lastRevid) { // if target was edited in the merge $merge->target->postRevid = $revid; $merge->target->comment = $comment; } // get source preRevid's and titles foreach ($merge->sources as $source) { $revid = $this->getPrevRevid($source->revPage, $source->postRevid); if (!$revid) { return "previous revision not found for source={$source->postRevid}"; } $source->preRevid = $revid; $revision = Revision::newFromId($revid); $title = $revision->getTitle(); if (!$title) { return "title not found for source={$source->postRevid}"; } $source->title = $title; if ($title->getNamespace() != $mergeNs) { return "invalid source={$title->getPrefixedText()} target={$merge->target->title->getPrefixedText()}"; } } } if ($foundPeopleCount > 1 && !$foundFamilyTitle) { return "invalid merge sequence: multiple merges without a family: count={$mergeCount}"; } if (!$foundFamilyTitle && $foundPeopleCount == 0) { return "invalid merge sequence: no people or families count={$mergeCount}"; } // put merge revids into their proper rows and columns, and add roles $mergeRows = array(); for ($m = 0; $m < count($userMerge->merges); $m++) { $mergeRows[$m] = (object) array('role' => '', 'revids' => array()); if ($foundFamilyTitle && $userMerge->merges[$m]->target->title->getNamespace() == NS_PERSON) { for ($p = 0; $p < count($familyCount); $p++) { $mergeRows[$m]->revids[$p] = array(); } } } $mainTitle = null; $totalScore = 0; $totalCount = 0; $seenRevids = array(); for ($m = 0; $m < count($userMerge->merges); $m++) { $merge =& $userMerge->merges[$m]; $ns = $merge->target->title->getNamespace(); // read baseData and data, calc match score if ($ns == NS_PERSON || $ns == NS_FAMILY) { $baseData = $this->readData($merge->target->title, $merge->target->preRevid); if (!$baseData) { return "unable to read data for {$merge->target->title->getPrefixedText()} revid={$merge->target->preRevid}"; } if ($ns == NS_FAMILY) { $baseStdData = MergeForm::standardizeFamilyData($baseData); } else { $baseStdData = MergeForm::standardizePersonData($baseData); } $data = array(); for ($p = 0; $p < count($merge->sources); $p++) { $data[$p] = $this->readData($merge->sources[$p]->title, $merge->sources[$p]->preRevid); if (!$data[$p]) { return "unable to read data for {$merge->sources[$p]->title->getPrefixedText()} revid={$merge->sources[$p]->preRevid}"; } if ($ns == NS_FAMILY) { $stdData = MergeForm::standardizeFamilyData($data[$p]); } else { $stdData = MergeForm::standardizePersonData($data[$p]); } $totalScore += MergeForm::calcMatchScore($baseStdData, $stdData); $totalCount++; } } if ($foundFamilyTitle && $ns == NS_FAMILY) { // use pre-merge family revisions to get roles, row numbers $this->recordFamilyMembers($baseData['husbands'], 'husband', 0, $userMerge->merges, $preMergeTimestamp, $familyCount, $mergeRows, $seenRevids); $this->recordFamilyMembers($baseData['wives'], 'wife', 0, $userMerge->merges, $preMergeTimestamp, $familyCount, $mergeRows, $seenRevids); $this->recordFamilyMembers($baseData['children'], 'child', 0, $userMerge->merges, $preMergeTimestamp, $familyCount, $mergeRows, $seenRevids); for ($p = 0; $p < count($data); $p++) { $this->recordFamilyMembers($data[$p]['husbands'], 'husband', $p + 1, $userMerge->merges, $preMergeTimestamp, $familyCount, $mergeRows, $seenRevids); $this->recordFamilyMembers($data[$p]['wives'], 'wife', $p + 1, $userMerge->merges, $preMergeTimestamp, $familyCount, $mergeRows, $seenRevids); $this->recordFamilyMembers($data[$p]['children'], 'child', $p + 1, $userMerge->merges, $preMergeTimestamp, $familyCount, $mergeRows, $seenRevids); } } if (!$foundFamilyTitle || $ns != NS_PERSON) { // handle people in families above $mergeRows[$m]->revids[0] = array(); $mergeRows[$m]->revids[0][] = $merge->target->preRevid; for ($p = 0; $p < count($merge->sources); $p++) { $mergeRows[$m]->revids[$p + 1] = array(); $mergeRows[$m]->revids[$p + 1][] = $merge->sources[$p]->preRevid; } if ($ns == NS_PERSON_TALK || $ns == NS_FAMILY_TALK) { $mergeRows[$m]->role = 'talk'; } else { if ($ns == NS_PERSON) { $mergeRows[$m]->role = 'Person'; } else { if ($ns == NS_FAMILY) { $mergeRows[$m]->role = 'Family'; } } } } if ($ns == NS_FAMILY || $ns == NS_PERSON && !$foundFamilyTitle) { $mainTitle = $merge->target->title; } } if ($foundFamilyTitle && $mainTitle->getPrefixedText() != $foundFamilyTitle->getPrefixedText()) { return "family titles not equal: {$mainTitle->getPrefixedText()} and {$foundFamilyTitle->getPrefixedText()}"; } if ($foundFamilyTitle) { // verify that all merging people have been recorded in mergeRows somewhere foreach ($userMerge->merges as &$merge) { if ($merge->target->title->getNamespace() == NS_PERSON) { if (!@$seenRevids[$merge->target->preRevid]) { return "target person not found: {$merge->target->title->getPrefixedText()}"; } foreach ($merge->sources as &$source) { if (!@$seenRevids[$source->preRevid]) { return "source person not found: {$source->title->getPrefixedText()}"; } } } } } // write mergelog record $mergeScore = $totalCount > 0 ? $totalScore / $totalCount : 0; $isTrustedUser = CompareForm::isTrustedMerger(User::newFromName(User::whois($userid))); $isTrustedMerge = MergeForm::isTrustedMerge($mergeScore, $isTrustedUser); $mergeId = $this->writeMergelog($userid, $userMerge, $mergeScore, $isTrustedMerge, $mainTitle, $mergeRows); // update post-merge comments with ml_id $this->updateMergeComments($userMerge, $mainTitle, $mergeId); $this->log("INFO", "writeMerge id={$mergeId} score={$mergeScore} isTrusted={$isTrustedMerge} merges={$mergeCount} title={$mainTitle->getPrefixedText()}", $userid, $userMerge->firstTimestamp); return ''; }
/** * Return HTML for displaying search results * @return string HTML */ public function getReviewResults() { $data = array(); $roles = array(); $this->getCompareData($data, $roles); $cols = count($data[0]); $tblCols = $cols + 1; $childNum = 0; $output = <<<END <form name="unmerge" action="/wiki/Special:ReviewMerge/{$this->mergeId}" method="get"> <input type="hidden" name="action" value="unmerge"/> <table border="0" cellspacing="0" cellpadding="4"> END; for ($i = 0; $i < count($data); $i++) { $output .= CompareForm::insertEmptyRow($tblCols); if ($roles[0] == 'Person') { $labels = self::$PERSON_COMPARE_LABELS; } else { if ($i == 0) { $labels = self::$FAMILY_COMPARE_LABELS; } else { $labels = self::$FAMILY_MEMBER_COMPARE_LABELS; } } foreach ($labels as $label) { $found = !in_array($label, self::$OPTIONAL_LABELS); if (!$found) { for ($j = 0; $j < $cols; $j++) { if (is_array(@$data[$i][$j][$label]) && count($data[$i][$j][$label]) > 0) { $found = true; break; } } } if ($found) { $baseStdValues =& CompareForm::standardizeValues($label, @$data[$i][1][$label]); if ($label == 'Title') { $role = $roles[$i]; if ($role == 'child') { $roleLabel = $role . $label; $childNum++; } else { if ($role == 'Person' || $role == 'Family') { $roleLabel = $label; } else { $roleLabel = $role . $label; } } } else { $roleLabel = $label; } if ($label == 'familyTitle') { $revidLabel = 'Revid'; } else { if (strpos($label, 'Title') !== false) { $revidLabel = str_replace('Title', 'Revid', $label); } else { $revidLabel = ''; } } $labelClass = CompareForm::getLabelClass($roleLabel); $output .= "<tr><td class=\"{$labelClass}\">" . CompareForm::formatLabel($roleLabel, $childNum) . "</td>"; for ($j = $cols - 1; $j >= 0; $j--) { if ($j == 1) { $stdValues = $baseStdValues; } else { $stdValues =& CompareForm::standardizeValues($label, @$data[$i][$j][$label]); } list($score, $class) = CompareForm::getCompareScoreClass($j == 0 || $j == 1, $label, $baseStdValues, $stdValues); $output .= "<td class=\"{$class}\">"; $valueFound = false; if (is_array(@$data[$i][$j][$label])) { for ($v = 0; $v < count($data[$i][$j][$label]); $v++) { $value = $data[$i][$j][$label][$v]; if (strpos($label, 'Title') !== false) { if (($label == 'familyTitle' || $label == 'Title') && @$data[$i][$j]['mergeresult']) { $formattedValue = 'Merge<br>result'; } else { if (GedcomUtil::getGedcomMergeLogKey(@$data[$i][$j]['Revid'][$v])) { $formattedValue = '<b>GEDCOM page</b>'; } else { $formattedValue = CompareForm::formatValue($label, $value, StructuredData::endsWith($label, 'FamilyTitle') ? '' : 'oldid=' . $data[$i][$j][$revidLabel][$v]); } } } else { $formattedValue = CompareForm::formatValue($label, $value); } if ($v) { $output .= '<hr>'; } $output .= $formattedValue; $valueFound = true; } } if (!$valueFound) { $output .= ' '; } if ($class == CompareForm::$COMPARE_PAGE_CLASS) { $titlesCnt = count(@$data[$i][$j][$label]); if ($titlesCnt > 0) { if (!$data[$i][$j]['Exists']) { // person/family not found $output .= '<br>Not found'; } else { if ($data[$i][$j]['Redirect']) { // person/family has been merged $output .= '<br>Already merged'; } else { if ($data[$i][$j]['mergeTarget']) { $output .= '<br>' . ($data[$i][$j][$label][0] == $data[$i][$j]['mergeTarget'] ? 'Same as ' : 'Merged with ') . htmlspecialchars($data[$i][$j]['mergeTarget']); } else { if ($j == 1 && @$data[$i][0]['mergeresult']) { $output .= '<br>(merge target)'; } } } } } else { if (@$data[$i][$j]['deleted']) { $output .= 'Page has been deleted'; } } } $output .= '</td>'; } $output .= '</tr>'; } } } if ($this->unmergeTimestamp) { $unmergeButton = 'Merge has been undone'; } else { $unmergeButton = 'Unmerge reason: <input type="text" name="comment" size=36/><br><input type="submit" value="Unmerge"/>'; } $output .= <<<END <tr><td align=right colspan="{$tblCols}">{$unmergeButton}</td></tr> </table> </form> END; return $output; }
function wfMatchFamily($args) { global $wgUser, $wgAjaxCachePolicy; $wgAjaxCachePolicy->setPolicy(0); $status = GE_SUCCESS; $result = ''; $gedcomId = GedcomUtil::getGedcomId($args); if (!$wgUser->isLoggedIn()) { $status = GE_NOT_LOGGED_IN; } else { if ($wgUser->isBlocked() || wfReadOnly()) { $status = GE_NOT_AUTHORIZED; } else { if (!$args || !$gedcomId || !preg_match('/^<gedcom [^>]*id="([^"]+)"/', $args, $idMatches) || !preg_match('/^<gedcom [^>]*match="([^"]+)"/', $args, $titleMatches)) { $status = GE_INVALID_ARG; } else { // calc family match scores $id = $idMatches[1]; $matchTitle = Title::newFromText($titleMatches[1], NS_FAMILY); $matchTitleString = $matchTitle->getText(); $gedcomData = GedcomUtil::getGedcomDataMap($args); $gedcomTitleString = $gedcomData[$id]['title']; if (strpos($gedcomTitleString, 'Person:') === 0 || strpos($gedcomTitleString, 'Family:') === 0) { $gedcomTitleString = substr($gedcomTitleString, 7); } $compare = new CompareForm('Family', array($gedcomTitleString, $matchTitleString), $gedcomData, $args); list($compareData, $compareChildren, $maxChildren) = $compare->readCompareData(); $dataLabels = $compare->getDataLabels(); list($compareClass, $husbandScores, $wifeScores, $totalScores) = $compare->scoreCompareData($dataLabels, $compareData); // matched if not multiple husbands/wives, total score > threshold, husband + wife scores > threshold, and all gedcom children matched $gedcomHusbandCount = count($compareData[$gedcomTitleString]['husbandTitle']); $gedcomWifeCount = count($compareData[$gedcomTitleString]['wifeTitle']); $matchHusbandCount = count($compareData[$matchTitleString]['husbandTitle']); $matchWifeCount = count($compareData[$matchTitleString]['wifeTitle']); $matched = $gedcomHusbandCount <= 1 && $gedcomWifeCount <= 1 && $matchHusbandCount <= 1 && $matchWifeCount <= 1 && $totalScores[1] >= 4 && ($gedcomHusbandCount == 0 || $matchHusbandCount == 0 || $husbandScores[1] > 0) && ($gedcomWifeCount == 0 || $matchWifeCount == 0 || $wifeScores[1] > 0); //wfDebug("MATCHCOMPARE gedcomTitle=$gedcomTitleString matchTitle=$matchTitleString ghc=$gedcomHusbandCount gwc=$gedcomWifeCount mhc=$matchHusbandCount mwc=$matchWifeCount". // " totalScore={$totalScores[1]} husbandScore={$husbandScores[1]} wifeScore={$wifeScores[1]} matched=$matched\n"); if ($matched) { $unmatchedGedcomChild = false; $unmatchedWikiChild = false; for ($i = 0; $i < $maxChildren; $i++) { if (count(@$compareChildren[$gedcomTitleString][$i]['childTitle']) == 1 && count(@$compareChildren[$matchTitleString][$i]['childTitle']) != 1) { $unmatchedGedcomChild = true; } else { if (count(@$compareChildren[$gedcomTitleString][$i]['childTitle']) != 1 && count(@$compareChildren[$matchTitleString][$i]['childTitle']) == 1) { $unmatchedWikiChild = true; } } } if ($unmatchedGedcomChild && $unmatchedWikiChild) { // could be a mismatched child $matched = false; //wfDebug("MATCHCOMPARE failed on children\n"); } } if ($matched) { // save matches and return related matches $keys = array(); $keys[] = $id; $matches = array(); $matches[] = $matchTitle->getPrefixedText(); $merged = array(); $merged[] = 'false'; if ($gedcomHusbandCount == 1 && $matchHusbandCount == 1) { $key = GedcomUtil::getKeyFromTitle($compareData[$gedcomTitleString]['husbandTitle'][0]); if ($key) { // not already merged $keys[] = $key; $matches[] = 'Person:' . $compareData[$matchTitleString]['husbandTitle'][0]; $merged[] = 'false'; } } if ($gedcomWifeCount == 1 && $matchWifeCount == 1) { $key = GedcomUtil::getKeyFromTitle($compareData[$gedcomTitleString]['wifeTitle'][0]); if ($key) { $keys[] = $key; $matches[] = 'Person:' . $compareData[$matchTitleString]['wifeTitle'][0]; $merged[] = 'false'; } } for ($i = 0; $i < $maxChildren; $i++) { if (count(@$compareChildren[$gedcomTitleString][$i]['childTitle']) == 1 && count(@$compareChildren[$matchTitleString][$i]['childTitle']) == 1) { $key = GedcomUtil::getKeyFromTitle($compareChildren[$gedcomTitleString][$i]['childTitle'][0]); if ($key) { $keys[] = $key; $matches[] = 'Person:' . $compareChildren[$matchTitleString][$i]['childTitle'][0]; $merged[] = 'false'; } } } $status = fgUpdateMatches($gedcomId, $keys, $matches, $merged); if ($status == GE_SUCCESS) { $result = fgGetRelatedMatches($keys, $matches); } } else { // save as potential match and return $keys = array($id); $matches = array($matchTitleString); $status = fgUpdatePotentialMatches($gedcomId, $keys, $matches); if ($status == GE_SUCCESS) { $result = '<match id="' . StructuredData::escapeXml($id) . '" matches="' . StructuredData::escapeXml($matchTitleString) . '"/>'; } } } } } return "<matchfamily status=\"{$status}\">{$result}</matchfamily>"; }