public static function addWhitelistPage($pageTitle) { $key = PropagationManager::getPageKey($pageTitle); if ($key) { if (!is_array(PropagationManager::$whitelistTitles)) { PropagationManager::$whitelistTitles = array(); } PropagationManager::$whitelistTitles[$key] = 1; } else { error_log("addWhitelistPage bad title"); } }
public function propagateSINMoveDeleteUndelete($thisTitle, $tag, $oldTitleString, $newTitleString, &$propagatedData, &$text, &$textChanged) { $result = true; $newTitle = $newTitleString ? Title::newFromText($newTitleString, $tag == 'person' ? NS_PERSON : NS_FAMILY) : null; foreach ($propagatedData['images'] as $i) { $imageTitle = Title::newFromText((string) $i['filename'], NS_IMAGE); PropagationManager::addPropagatedAction($thisTitle, 'delimage', $imageTitle); if ($newTitle) { PropagationManager::addPropagatedAction($newTitle, 'addimage', $imageTitle); } // don't need to check propagated action before calling updateImage, because propagateSINMoveDeleteUndelete is never part of a loop $result = $result && $this->updateImage($thisTitle, $imageTitle, $tag, $oldTitleString, $newTitleString, $propagatedData, $text, $textChanged); } return $result; }
/** * Propagate move, delete, or undelete to other articles if necessary * * @param String $newTitleString null in case of delete; same as this title string in case of undelete * @param String $text text of article * @param bool $textChanged set to true if we change the text * @return bool true if success */ protected function propagateMoveDeleteUndelete($newTitleString, $newNs, &$text, &$textChanged) { global $wgESINHandler; $result = true; $newTitle = $newTitleString ? Title::newFromText($newTitleString, NS_FAMILY) : null; // if we're undeleting, add additional people from WLH, getting updated person data if ($this->titleString == $newTitleString) { $wlh = simplexml_load_string($this->getPageTextFromWLH(false)); // get text for all people // TODO is propagateEditData called after Undelete? if so, then we could get updated person attributes there $personText = $this->getPersonData($wlh->husband, $this->xml->husband, 'husband') . $this->getPersonData($wlh->wife, $this->xml->wife, 'wife') . $this->getPersonData($wlh->child, $this->xml->child, 'child') . $wgESINHandler->getImageData($wlh->image, $this->xml->image); // update text: replace old family information with new $text = preg_replace("\$<(husband|wife|child|image) [^>]*>\n\$", '', $text); $text = preg_replace('$</family>$', StructuredData::protectRegexReplace($personText . '</family>'), $text, 1); $this->xml = StructuredData::getXml($this->tagName, $text); $textChanged = true; } // get data to propagate $propagatedData = Family::getPropagatedData($this->xml); foreach ($propagatedData['husbands'] as $p) { $personTitle = Title::newFromText((string) $p, NS_PERSON); PropagationManager::addPropagatedAction($this->title, 'delspouse', $personTitle); if ($newTitle) { PropagationManager::addPropagatedAction($newTitle, 'addspouse', $personTitle); } // don't need to check propagated action before calling updatePerson, because propagateMoveDeleteUndelete is never part of a loop $result = $result && $this->updatePerson($personTitle, 'husband', 'spouse_of_family', $newTitleString, $text, $textChanged); } foreach ($propagatedData['wives'] as $p) { $personTitle = Title::newFromText((string) $p, NS_PERSON); PropagationManager::addPropagatedAction($this->title, 'delspouse', $personTitle); if ($newTitle) { PropagationManager::addPropagatedAction($newTitle, 'addspouse', $personTitle); } // don't need to check propagated action before calling updatePerson, because propagateMoveDeleteUndelete is never part of a loop $result = $result && $this->updatePerson($personTitle, 'wife', 'spouse_of_family', $newTitleString, $text, $textChanged); } foreach ($propagatedData['children'] as $p) { $personTitle = Title::newFromText((string) $p, NS_PERSON); PropagationManager::addPropagatedAction($this->title, 'delchild', $personTitle); if ($newTitle) { PropagationManager::addPropagatedAction($newTitle, 'addchild', $personTitle); } // don't need to check propagated action before calling updatePerson, because propagateMoveDeleteUndelete is never part of a loop $result = $result && $this->updatePerson($personTitle, 'child', 'child_of_family', $newTitleString, $text, $textChanged); } if (StructuredData::removeDuplicateLinks('husband|wife|child', $text)) { $textChanged = true; } $result = $result && $wgESINHandler->propagateSINMoveDeleteUndelete($this->title, 'family', $this->titleString, $newTitleString, $propagatedData, $text, $textChanged); if (!$result) { error_log("ERROR! Family move/delete/undelete not propagated: {$this->titleString} -> " . ($newTitleString ? $newTitleString : "[delete]") . "\n"); } return $result; }
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; }
/** * Propagate an article rollback * Sets xml property and calls the abstract function propagateRollbackData($title) * @param Article $article contains text being replaced * @return bool true if propagate succeeded */ public function propagateRollback($article) { global $wgUser, $wrBotUserID; $result = true; // we do propagate bot edits // if ($wgUser->getID() != $wrBotUserID) { $title = $article->getTitle(); $oldText =& $article->fetchContent(); // $article contains the text being replaced; $rollbackRevision contains the new text $rollbackRevision = StructuredData::getRevision($title, false, true); $text =& $rollbackRevision->getText(); $this->xml = StructuredData::getXml($this->tagName, $text); $textChanged = false; $result = $this->propagateEditData($oldText, $text, $textChanged); if ($result && $textChanged) { PropagationManager::enablePropagation(false); $result = $article->doEdit($text, self::PROPAGATE_MESSAGE, PROPAGATE_EDIT_FLAGS); PropagationManager::enablePropagation(true); } // } return $result; }
/** * Propagate move, delete, or undelete to other articles if necessary * * @param String $newTitleString null in case of delete; same as this title string in case of undelete * @param String $text text of article * @param bool $textChanged set to true if we change the text * @return bool true if success */ protected function propagateMoveDeleteUndelete($newTitleString, $newNs, &$text, &$textChanged) { global $wgESINHandler; $result = true; $newTitle = $newTitleString ? Title::newFromText($newTitleString, NS_PERSON) : null; // if we're undeleting, add additional families from WLH if ($this->titleString == $newTitleString) { $wlh = simplexml_load_string($this->getPageTextFromWLH(false)); // get text for all families $familyText = $this->getFamilyData($wlh->child_of_family, $this->xml->child_of_family, 'child_of_family') . $this->getFamilyData($wlh->spouse_of_family, $this->xml->spouse_of_family, 'spouse_of_family') . $wgESINHandler->getImageData($wlh->image, $this->xml->image); // update text: replace old family information with new $text = preg_replace("\$<((child|spouse)_of_family|image) [^>]*>\n\$", '', $text); $text = preg_replace('$</person>$', StructuredData::protectRegexReplace($familyText . '</person>'), $text, 1); $this->xml = StructuredData::getXml($this->tagName, $text); $textChanged = true; } // get data to propagate $propagatedData = Person::getPropagatedData($this->xml); foreach ($propagatedData['parentFamilies'] as $f) { $familyTitle = Title::newFromText((string) $f, NS_FAMILY); PropagationManager::addPropagatedAction($this->title, 'delchild_of_family', $familyTitle); if ($newTitle) { PropagationManager::addPropagatedAction($newTitle, 'addchild_of_family', $familyTitle); } // don't need to check propagated action before calling updateFamily, because propagateMoveDeleteUndelete is never part of a loop $result = $result && $this->updateFamily($familyTitle, 'child_of_family', 'child', $newTitleString, $propagatedData, $text, $textChanged); } $spouseTag = $propagatedData['gender'] == 'F' ? 'wife' : 'husband'; foreach ($propagatedData['spouseFamilies'] as $f) { $familyTitle = Title::newFromText((string) $f, NS_FAMILY); PropagationManager::addPropagatedAction($this->title, 'delspouse_of_family', $familyTitle); if ($newTitle) { PropagationManager::addPropagatedAction($newTitle, 'addspouse_of_family', $familyTitle); } // don't need to check propagated action before calling updateFamily, because propagateMoveDeleteUndelete is never part of a loop $result = $result && $this->updateFamily($familyTitle, 'spouse_of_family', $spouseTag, $newTitleString, $propagatedData, $text, $textChanged); } if (StructuredData::removeDuplicateLinks('child_of_family|spouse_of_family', $text)) { $textChanged = true; } $result = $result && $wgESINHandler->propagateSINMoveDeleteUndelete($this->title, 'person', $this->titleString, $newTitleString, $propagatedData, $text, $textChanged); if (!$result) { error_log("ERROR! Person move/delete/undelete not propagated: {$this->titleString} -> " . ($newTitleString ? $newTitleString : "[delete]") . "\n"); } return $result; }
public function unmerge() { global $wgUser; $nonFamilyPages = array(); // contains revid, next_revid, title $familyPages = array(); // ditto $manualPages = array(); // ditto $unchangedPages = array(); // ditto $dbw =& wfGetDB(DB_MASTER); // break into different arrays $seenTitles = array(); foreach ($this->merges as $merge) { $fields = explode('|', $merge); $role = $fields[0]; $revidSets = explode('#', $fields[1]); foreach ($revidSets as $revidSet) { if ($revidSet) { $revids = explode('/', $revidSet); foreach ($revids as $revid) { if ($revid) { // get two following revisions $rows = $dbw->query('SELECT r2.rev_page, r2.rev_id, r2.rev_comment FROM revision AS r1, revision AS r2' . ' WHERE r1.rev_id = ' . $revid . ' AND r1.rev_page = r2.rev_page AND r2.rev_id > ' . $revid . ' ORDER BY r2.rev_id LIMIT 2'); $cnt = 0; $cmt = ''; $nextRevid = ''; $pageId = ''; while ($row = $dbw->fetchObject($rows)) { $cnt++; if ($cnt == 1) { $pageId = $row->rev_page; $nextRevid = $row->rev_id; $cmt = $row->rev_comment; } } $dbw->freeResult($rows); if ($cnt == 0) { $revision = Revision::newFromId($revid); if ($revision) { $title = $revision->getTitle(); } else { // page must have been deleted $title = null; } } else { $title = Title::newFromId($pageId); } // TODO if title doesn't exist, then unmerged pages could have red links; oh well if ($title && !@$seenTitles[$title->getPrefixedText()]) { $seenTitles[$title->getPrefixedText()] = 1; $entry = array('revid' => $revid, 'next_revid' => $nextRevid, 'title' => $title); if ($cnt == 0 || strpos($cmt, '[[Special:ReviewMerge/' . $this->mergeId . '|') === false) { $unchangedPages[] = $entry; // page was not edited in the merge } else { if ($cnt > 1) { $manualPages[] = $entry; } else { if ($role == 'Family') { PropagationManager::addBlacklistPage($title); $familyPages[] = $entry; } else { PropagationManager::addBlacklistPage($title); $nonFamilyPages[] = $entry; } } } } } } } } } // update nonFamilyPages foreach ($nonFamilyPages as $page) { $this->revert($page, false); } // update familyPages foreach ($familyPages as $page) { $this->revert($page, true); } // update mergelog $dbw->update('mergelog', array('ml_unmerge_user' => $wgUser->getID(), 'ml_unmerge_timestamp' => $dbw->timestamp(wfTimestampNow())), array('ml_id' => $this->mergeId), 'ReviewForm::unmerge'); // add log and RC $mergeComment = 'Unmerge [[' . $this->mergeTitle->getPrefixedText() . ']]' . ($this->comment ? ' - ' . $this->comment : ''); $log = new LogPage('merge', false); $t = Title::makeTitle(NS_SPECIAL, "ReviewMerge/{$this->mergeId}"); $log->addEntry('unmerge', $t, $mergeComment); RecentChange::notifyLog(wfTimestampNow(), $t, $wgUser, $mergeComment, '', 'merge', 'unmerge', $t->getPrefixedText(), $mergeComment, '', 0, 0); // list pages $output = ''; $skin =& $wgUser->getSkin(); if (count($manualPages) == 0) { $output .= "<h2>Unmerge successful</h2>\n"; } else { $output .= "<h2>Unmerge partially completed</h2><p><b><font color=\"red\">The following page(s) must still be unmerged</font></b></p><ul>"; foreach ($manualPages as $page) { $output .= "<li>" . htmlspecialchars($page['title']->getPrefixedText()) . ' <b>' . $skin->makeKnownLinkObj($page['title'], 'changes to undo', 'diff=' . $page['next_revid'] . '&oldid=' . $page['revid']) . '</b> => <b>' . $skin->makeKnownLinkObj($page['title'], 'edit', 'action=edit') . "</b></li>\n"; error_log("Unmerge may be needed: {$page['title']->getPrefixedText()} id={$this->mergeId}"); } $output .= <<<END </ul> <p>For each page listed above, click on the <b>changes to undo</b> link to see what changes need to be manually undone: <ol> <li>For each item that was removed in the merge (listed on the left side of the screen in yellow), you need to add that item back to the page.</li> <li>For each item that was added in the merge (listed on the right side of the screen in green), you need to remove that item from the page.</li> </ol></p> <p>Once you know which items need to be added/removed, click on the <b>edit</b> link to edit the page and add/remove the items.</p> <p>Additional notes: <ul> <li>Some of the items may have been added/removed already.</li> <li>You can ignore place changes that simply fill in missing place levels.</li> <li>When reviewing changes to Family pages, you can ignore changes to personal information (birth, death, etc.) associated with the family members: <i>husband</i>, <i>wife</i>, or <i>child</i>. Just re-add family members that were removed in the merge, and remove ones that were added.</li> </ul></p> <p>If you have questions, send an email to <b>solveig@quass.org</b> containing the titles of the pages listed above and the URL appearing at the top of your browser window and we will finish the unmerge for you.</p> <p> </p> END; } if (count($familyPages) + count($nonFamilyPages) + count($unchangedPages) > 0) { $output .= "<p><b>The following pages have been successfully unmerged:</b></p><ul>\n"; foreach ($familyPages as $page) { $output .= "<li>" . $skin->makeKnownLinkObj($page['title']) . "</li>\n"; } foreach ($nonFamilyPages as $page) { $output .= "<li>" . $skin->makeKnownLinkObj($page['title']) . "</li>\n"; } // $output .= '</ul>'; // } // if (count($unchangedPages) > 0) { // $output .= "<p><b>The following pages were not merged and so did not need to be unmerged:</b></p><ul>\n"; foreach ($unchangedPages as $page) { $output .= "<li>" . $skin->makeKnownLinkObj($page['title']) . "</li>\n"; } $output .= "</ul>"; } return $output; }
/** * Run a refreshLinks job * @return boolean success */ function run() { global $wgUser, $wgTitle, $wrIsTreeDeletion; $status = FTE_SUCCESS; $user = $this->params['user']; $wgUser = User::newFromName($user); $treeId = $this->params['tree_id']; if ($treeId == 9662) { return false; } $delPages = $this->params['delete_pages'] == 1; $wgTitle = $this->title; // FakeTitle (the default) generates errors when accessed, and sometimes I log wgTitle, so set it to something else $dbw =& wfGetDB(DB_MASTER); $dbw->begin(); $dbw->ignoreErrors(true); if ($delPages) { // Delete the page if it is in this tree // and is in one of the 4 deletable namespaces // and nobody else is watching the page // and it is not in another of the users trees // Keep this query in sync with SpecialTreeDeletionImpact.php $sql = 'SELECT fp_namespace, fp_title FROM familytree_page AS fp1' . ' WHERE fp_tree_id=' . $dbw->addQuotes($treeId) . ' and fp_namespace in (' . NS_IMAGE . ',' . NS_PERSON . ',' . NS_FAMILY . ',' . NS_MYSOURCE . ')' . ' and not exists (SELECT 1 FROM watchlist WHERE wl_namespace = fp_namespace and wl_title = fp_title and wl_user <> fp_user_id)' . ' and not exists (SELECT 1 FROM familytree_page AS fp2 WHERE fp2.fp_namespace = fp1.fp_namespace and fp2.fp_title = fp1.fp_title and fp2.fp_user_id = fp1.fp_user_id and fp2.fp_tree_id <> fp1.fp_tree_id)'; $rows = $dbw->query($sql); $errno = $dbw->lastErrno(); if ($errno > 0) { $status = FTE_DB_ERROR; } else { if ($rows !== false) { $titles = array(); while ($row = $dbw->fetchObject($rows)) { $title = Title::makeTitle($row->fp_namespace, $row->fp_title); $talkTitle = $title->getTalkPage(); if ($title->exists()) { $titles[] = $title; PropagationManager::addBlacklistPage($title); } if ($talkTitle->exists()) { $titles[] = $talkTitle; } } $dbw->freeResult($rows); $wrIsTreeDeletion = true; foreach ($titles as $title) { $status = ftDelPage($title, false); if ($status == FTE_NOT_AUTHORIZED) { $this->error = "While deleting {$treeId} tried to delete a page not authorized to delete: " . $title->getPrefixedText() . "\n"; } if ($status != FTE_SUCCESS) { break; } } } } // remove remaining pages from watchlist if ($status == FTE_SUCCESS) { $sql = 'SELECT fp_namespace, fp_title FROM familytree_page AS fp1' . ' WHERE fp_tree_id=' . $dbw->addQuotes($treeId) . ' and not exists (SELECT 1 FROM familytree_page AS fp2 WHERE fp2.fp_namespace = fp1.fp_namespace and fp2.fp_title = fp1.fp_title and fp2.fp_user_id = fp1.fp_user_id and fp2.fp_tree_id <> fp1.fp_tree_id)'; $rows = $dbw->query($sql); $errno = $dbw->lastErrno(); if ($errno > 0) { $status = FTE_DB_ERROR; } else { if ($rows !== false) { while ($row = $dbw->fetchObject($rows)) { $title = Title::makeTitle($row->fp_namespace, $row->fp_title); $wgUser->removeWatch($title); } $dbw->freeResult($rows); } } } // remove familytree_page's // If we delete pages that are unwatched by others but in someone else's tree, then this code won't delete them from the others' trees // We need to ensure that all pages in trees are watched. if ($status == FTE_SUCCESS) { $dbw->delete('familytree_page', array('fp_tree_id' => $treeId)); $errno = $dbw->lastErrno(); if ($errno > 0) { $status = FTE_DB_ERROR; } } if ($status == FTE_SUCCESS) { // remove familytree_data's $dbw->delete('familytree_data', array('fd_tree_id' => $treeId)); $errno = $dbw->lastErrno(); if ($errno > 0) { $status = FTE_DB_ERROR; } // keep familytree_gedcom in case we want to look at it later } if ($status == FTE_SUCCESS) { $dbw->commit(); return true; } else { $dbw->rollback(); if (!$this->error) { $this->error = "Error deleting tree; status={$status}\n"; } return false; } } }
/** * Generate family tree page * * @param unknown_type $args user, name, ns, title * @return GE_SUCCESS, GE_INVALID_ARG, GE_NOT_LOGGED_IN, GE_NOT_AUTHORIZED, GE_NOT_FOUND, GE_DUP_KEY, GE_DB_ERROR */ function wfGenerateFamilyTreePage($args) { global $wgUser, $wgAjaxCachePolicy, $wrBotUserID, $wrIsGedcomUpload; // set cache policy $wgAjaxCachePolicy->setPolicy(0); $status = GE_SUCCESS; $ns = ''; $text = ''; $oldText = ''; $titleString = ''; $editFlags = 0; $wrIsGedcomUpload = true; if (!$wgUser->isLoggedIn()) { $status = GE_NOT_LOGGED_IN; } else { if (wfReadOnly() || $wgUser->getID() != $wrBotUserID) { $status = GE_NOT_AUTHORIZED; } else { $xml = simplexml_load_string($args); $ns = (int) $xml['namespace']; $titleString = (string) $xml['title']; PropagationManager::setWhitelist(); // only pages to propagate to are on the whitelist $existingTitles = (string) $xml['existing_titles']; if ($existingTitles) { $existingTitles = explode('|', $existingTitles); foreach ($existingTitles as $existingTitle) { PropagationManager::addWhitelistPage(Title::newFromText($existingTitle)); } } $treeId = (int) $xml['tree_id']; $uid = (string) $xml['uid']; // wfDebug("wfGenerateFamilyTreePage ns=$ns title=$titleString treeId=$treeId\n"); if (!$titleString || !$treeId) { //wfDebug("wfGenerate parmerr $treeId:$titleString\n"); $status = GE_INVALID_ARG; } } } if ($status == GE_SUCCESS) { $dbr =& wfGetDB(DB_SLAVE); $dbr->ignoreErrors(true); $userName = $dbr->selectField('familytree', 'ft_user', array('ft_tree_id' => $treeId)); $errno = $dbr->lastErrno(); if ($errno > 0) { $status = GE_DB_ERROR; } else { if ($userName === false) { $status = GE_NOT_FOUND; } else { $wgUser = User::newFromName($userName, false); // switch the global user if (!$wgUser) { $status = GE_NOT_FOUND; } } } } if ($status == GE_SUCCESS) { $title = Title::newFromText($titleString, $ns); $text = $xml->content; if ($title == null || !$treeId) { //wfDebug("wfGenerate error $treeId $ns $titleString\n"); $status = GE_INVALID_ARG; } else { $article = new Article($title, 0); if (!$article->exists()) { $editFlags = EDIT_NEW; } else { $oldText = $article->getContent(); $editFlags = EDIT_UPDATE; } // else if ($ns == NS_MYSOURCE) { // $existingMysource = true; // $revid = $title->getLatestRevID(GAID_FOR_UPDATE); // } // // TODO during re-upload, we need to notify users of changes if others are watching; should we not suppress RC in this case? // // also, decide whether FamilyTreePropagator should update ftp or not // // (FamilyTreePropagator also processes the tree checkboxes, so we probably don't want it called) // else { //// $editFlags = EDIT_UPDATE; // $status = GE_DUP_KEY; // } } } if ($status == GE_SUCCESS && ($editFlags == EDIT_NEW || $text != $oldText)) { $isUpdatable = true; if ($editFlags == EDIT_UPDATE) { $revision = Revision::newFromId($article->getLatest()); if ($revision && $revision->getComment() != 'gedcom upload') { $isUpdatable = false; error_log("Cannot update existing user-edited page: " . $article->getTitle()->getPrefixedText()); } } if ($isUpdatable) { // NOTE: This doesn't execute the code in FamilyTreePropagator to update familytree_page, so if you edit a page, you'll have to update // the familytree_page.fp_latest yourself. Also, FamilyTreePropagator adds the page to the tree (based upon request checkboxes), but we do this below if (!$article->doEdit($text, 'gedcom upload', $editFlags | EDIT_SUPPRESS_RC)) { $status = GE_WIKI_ERROR; } // TODO remove this if ($ns == NS_PERSON) { $xml = StructuredData::getXml('person', $text); $summaryFields = explode('|', Person::getSummary($xml, $title)); $birthDate = $summaryFields[3]; $deathDate = $summaryFields[5]; $birthYear = ''; $deathYear = ''; if (preg_match('/\\d\\d\\d\\d/', $birthDate, $matches)) { $birthYear = $matches[0]; } if (preg_match('/\\d\\d\\d\\d/', $deathDate, $matches)) { $deathYear = $matches[0]; } if (($birthYear && $birthYear < 1750 || $deathYear && $deathYear < 1750) && !in_array($wgUser->getName(), explode('|', wfMsg('trustedgedcomuploaders')))) { error_log($title->getPrefixedText() . "\n", 3, '/opt/wr/logs/earlypeople.txt'); } } } } if ($status == GE_SUCCESS) { $dbw =& wfGetDB(DB_MASTER); $dbw->ignoreErrors(true); $dbw->begin(); // if ($status == GE_SUCCESS) { // // save the data // $data = $xml->data->asXML(); // if ($data) { // $dataVersion = 1; // $status == fgSaveData($dbw, $treeId, $title, $data, true); // } // else { // $dataVersion = 0; // } // } // add the page to the tree if ($status == GE_SUCCESS) { if (!FamilyTreeUtil::addPage($dbw, $wgUser, $treeId, $title, 0, 0, 0, $uid, 0)) { $status = GE_DB_ERROR; } } // watch the page if ($status == GE_SUCCESS) { StructuredData::addWatch($wgUser, $article, true); } if ($status == GE_SUCCESS) { $dbw->commit(); } else { $dbw->rollback(); } } // return status $titleString = StructuredData::escapeXml($titleString); return "<generate status=\"{$status}\" ns=\"{$ns}\" title=\"{$titleString}\"></generate>"; }
/** * Propagate move, delete, or undelete to other articles if necessary * * @param String $newTitleString null in case of delete; same as this title string in case of undelete * @param String $text text of article * @param bool $textChanged set to true if we change the text * @return bool true if success */ protected function propagateMoveDeleteUndelete($newTitleString, $newNs, &$text, &$textChanged) { $result = true; $newTitle = $newTitleString ? Title::newFromText($newTitleString, NS_IMAGE) : null; // if we're undeleting, add additional people from WLH, getting updated person data if ($this->titleString == $newTitleString) { list($people, $families) = SDImage::getPropagatedData($this->xml); $this->addWhatLinksHere($people, $families); $pageText = $this->getPageElements($people, 'person', NS_PERSON) . $this->getPageElements($families, 'family', NS_FAMILY); // update text: replace old family information with new $text = preg_replace("\$<(person|family) [^>]*>\n\$", '', $text); $text = preg_replace('$</' . $this->tagName . '>$', StructuredData::protectRegexReplace($pageText . '</' . $this->tagName . '>'), $text, 1); $this->xml = StructuredData::getXml($this->tagName, $text); $textChanged = true; } // get data to propagate list($people, $families) = SDImage::getPropagatedData($this->xml); foreach ($people as $p) { $linkTitle = Title::newFromText((string) $p, NS_PERSON); PropagationManager::addPropagatedAction($this->title, 'dellink', $linkTitle); if ($newTitle) { PropagationManager::addPropagatedAction($newTitle, 'addlink', $linkTitle); } // don't need to check propagated action before calling updatePage, because propagateMoveDeleteUndelete is never part of a loop $result = $result && $this->updatePage($linkTitle, 'person', $newTitleString, $text, $textChanged); } foreach ($families as $p) { $linkTitle = Title::newFromText((string) $p, NS_FAMILY); PropagationManager::addPropagatedAction($this->title, 'dellink', $linkTitle); if ($newTitle) { PropagationManager::addPropagatedAction($newTitle, 'addlink', $linkTitle); } // don't need to check propagated action before calling updatePage, because propagateMoveDeleteUndelete is never part of a loop $result = $result && $this->updatePage($linkTitle, 'family', $newTitleString, $text, $textChanged); } if (!$result) { error_log("ERROR! Family move/delete/undelete not propagated: {$this->titleString} -> " . ($newTitleString ? $newTitleString : "[delete]") . "\n"); } return $result; }