/** * @dataProvider provideUserTitleTimestamp */ public function testFromUserTitle($user, $linkTarget, $timestamp) { $store = $this->getMockWatchedItemStore(); $store->expects($this->once())->method('loadWatchedItem')->with($user, $linkTarget)->will($this->returnValue(new WatchedItem($user, $linkTarget, $timestamp))); $this->setService('WatchedItemStore', $store); $item = WatchedItem::fromUserTitle($user, $linkTarget, User::IGNORE_USER_RIGHTS); $this->assertEquals($user, $item->getUser()); $this->assertEquals($linkTarget, $item->getLinkTarget()); $this->assertEquals($timestamp, $item->getNotificationTimestamp()); }
/** * Get the timestamp when this page was updated since the user last saw it. * * @param User $user * @return string|null */ public function getNotificationTimestamp($user = null) { global $wgUser; // Assume current user if none given if (!$user) { $user = $wgUser; } // Check cache first $uid = $user->getId(); if (!$uid) { return false; } // avoid isset here, as it'll return false for null entries if (array_key_exists($uid, $this->mNotificationTimestamp)) { return $this->mNotificationTimestamp[$uid]; } // Don't cache too much! if (count($this->mNotificationTimestamp) >= self::CACHE_MAX) { $this->mNotificationTimestamp = array(); } $watchedItem = WatchedItem::fromUserTitle($user, $this); $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp(); return $this->mNotificationTimestamp[$uid]; }
/** * Stop watching an article */ function removeWatch($title) { $wl = WatchedItem::fromUserTitle($this, $title); $wl->removeWatch(); $this->invalidateCache(); }
/** * Adds users to watchlist if: * - User is watching parent page * - User has 'watchlistsubpages' turned on * * @param $watchers array of user ID * @param $title Title object * @param $editor User object * @param $notificationTimeoutSql string timeout to the watchlist * * @author Jakub Kurcek <*****@*****.**> */ public static function NotifyOnSubPageChange($watchers, $title, $editor, $notificationTimeoutSql) { wfProfileIn(__METHOD__); // Gets parent data $arrTitle = explode('/', $title->getDBkey()); if (empty($arrTitle)) { wfProfileOut(__METHOD__); return true; } // make Title $t = reset($arrTitle); $newTitle = Title::newFromDBkey($t); if (!$newTitle instanceof Title) { return true; } $dbw = wfGetDB(DB_MASTER); /** @var $dbw Database */ $res = $dbw->select(array('watchlist'), array('wl_user'), array('wl_title' => $newTitle->getDBkey(), 'wl_namespace' => $newTitle->getNamespace(), 'wl_user != ' . intval($editor->getID()), $notificationTimeoutSql), __METHOD__); // Gets user settings $parentpageWatchers = array(); while ($row = $dbw->fetchObject($res)) { $userId = intval($row->wl_user); $tmpUser = User::newFromId($userId); if ($tmpUser->getBoolOption(self::PREFERENCES_ENTRY)) { $parentpageWatchers[] = $userId; } unset($tmpUser); } // Updates parent watchlist timestamp for $parentOnlyWatchers. $parentOnlyWatchers = array_diff($parentpageWatchers, $watchers); $wl = WatchedItem::fromUserTitle($editor, $newTitle); $wl->updateWatch($parentOnlyWatchers); wfProfileOut(__METHOD__); return true; }
/** * Get a WatchedItem for this user and $title. * * @since 1.22 $checkRights parameter added * @param Title $title * @param int $checkRights Whether to check 'viewmywatchlist'/'editmywatchlist' rights. * Pass WatchedItem::CHECK_USER_RIGHTS or WatchedItem::IGNORE_USER_RIGHTS. * @return WatchedItem */ public function getWatchedItem($title, $checkRights = WatchedItem::CHECK_USER_RIGHTS) { $key = $checkRights . ':' . $title->getNamespace() . ':' . $title->getDBkey(); if (isset($this->mWatchedItems[$key])) { return $this->mWatchedItems[$key]; } if (count($this->mWatchedItems) >= self::MAX_WATCHED_ITEMS_CACHE) { $this->mWatchedItems = array(); } $this->mWatchedItems[$key] = WatchedItem::fromUserTitle($this, $title, $checkRights); return $this->mWatchedItems[$key]; }
public function testGetNotificationTimestamp_falseOnNotWatched() { $user = $this->getUser(); $title = Title::newFromText('WatchedItemIntegrationTestPage'); WatchedItem::fromUserTitle($user, $title)->removeWatch(); $this->assertFalse(WatchedItem::fromUserTitle($user, $title)->isWatched()); $this->assertFalse(WatchedItem::fromUserTitle($user, $title)->getNotificationTimestamp()); }
/** * Clear the user's notification timestamp for the given title. * If e-notif e-mails are on, they will receive notification mails on * the next change of the page if it's watched etc. * @param $title Title of the article to look at */ public function clearNotification(&$title) { global $wgUseEnotif, $wgShowUpdatedMarker; # Do nothing if the database is locked to writes if (wfReadOnly()) { return; } if ($title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName()) { if (!wfRunHooks('UserClearNewTalkNotification', array(&$this))) { return; } $this->setNewtalk(false); } if (!$wgUseEnotif && !$wgShowUpdatedMarker) { return; } if ($this->isAnon()) { // Nothing else to do... return; } // Only update the timestamp if the page is being watched. // The query to find out if it is watched is cached both in memcached and per-invocation, // and when it does have to be executed, it can be on a slave // If this is the user's newtalk page, we always update the timestamp if ($title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName()) { $watched = true; } else { $watched = $this->isWatched($title); } // If the page is watched by the user (or may be watched), update the timestamp on any // any matching rows if ($watched) { $wl = WatchedItem::fromUserTitle($this, $title); $wl->clearWatch(); } }
/** * Allow the user to clear their watchlist * * @param $out Output object * @param $request Request object * @param $par Parameters passed to the watchlist page * @return bool True if it's been taken care of; false indicates the watchlist * code needs to do something further */ function wlHandleClear(&$out, &$request, $par) { # Check this function has something to do if ($request->getText('action') == 'clear' || $par == 'clear') { global $wgUser; $out->setPageTitle(wfMsgHtml('clearwatchlist')); $count = wlCountItems($wgUser); if ($count > 0) { # See if we're clearing or confirming if ($request->wasPosted() && $wgUser->matchEditToken($request->getText('token'), 'clearwatchlist')) { # Clearing, so do it and report the result $dbw =& wfGetDB(DB_MASTER); // WERELATE - don't remove pages in your trees; call WatchedItem::removeWatch // $dbw->delete( 'watchlist', array( 'wl_user' => $wgUser->mId ), 'wlHandleClear' ); $sql = 'SELECT wl_namespace, wl_title FROM watchlist where wl_user='******' AND NOT EXISTS (SELECT fp_tree_id FROM familytree_page WHERE fp_namespace=(wl_namespace & ~1) AND fp_title=wl_title AND fp_user_id=wl_user)'; $rows = $dbw->query($sql, 'wlHandleClear'); while ($row = $dbw->fetchObject($rows)) { $title = Title::makeTitle($row->wl_namespace, $row->wl_title); if ($title) { $wl = WatchedItem::fromUserTitle($wgUser, $title); $wl->removeWatch(); } } $dbw->freeResult($rows); $out->addWikiText(wfMsg('watchlistcleardone', $count)); $out->returnToMain(); } else { # Confirming, so show a form $wlTitle = Title::makeTitle(NS_SPECIAL, 'Watchlist'); $out->addHTML(wfElement('form', array('method' => 'post', 'action' => $wlTitle->getLocalUrl('action=clear')), NULL)); $out->addWikiText(wfMsg('watchlistcount', $count)); $out->addWikiText(wfMsg('watchlistcleartext')); $out->addHTML(wfElement('input', array('type' => 'hidden', 'name' => 'token', 'value' => $wgUser->editToken('clearwatchlist')), '')); $out->addHTML(wfElement('input', array('type' => 'submit', 'name' => 'submit', 'value' => wfMsgHtml('watchlistclearbutton')), '')); $out->addHTML(wfCloseElement('form')); } return true; } else { # Nothing on the watchlist; nothing to do here $out->addWikiText(wfMsg('nowatchlist')); $out->returnToMain(); return true; } } else { return false; } }
/** * Back-end article deletion * Deletes the article with database consistency, writes logs, purges caches * Returns success */ function doDeleteArticle($reason, $rc = true) { global $wgUseSquid, $wgDeferredUpdateList; global $wgPostCommitUpdateList, $wgUseTrackbacks; wfDebug(__METHOD__ . "\n"); $dbw =& wfGetDB(DB_MASTER); $ns = $this->mTitle->getNamespace(); $t = $this->mTitle->getDBkey(); $id = $this->mTitle->getArticleID(); if ($t == '' || $id == 0) { return false; } $u = new SiteStatsUpdate(0, 1, -(int) $this->isCountable($this->getContent()), -1); array_push($wgDeferredUpdateList, $u); // For now, shunt the revision data into the archive table. // Text is *not* removed from the text table; bulk storage // is left intact to avoid breaking block-compression or // immutable storage schemes. // // For backwards compatibility, note that some older archive // table entries will have ar_text and ar_flags fields still. // // In the future, we may keep revisions and mark them with // the rev_deleted field, which is reserved for this purpose. $dbw->insertSelect('archive', array('page', 'revision'), array('ar_namespace' => 'page_namespace', 'ar_title' => 'page_title', 'ar_comment' => 'rev_comment', 'ar_user' => 'rev_user', 'ar_user_text' => 'rev_user_text', 'ar_timestamp' => 'rev_timestamp', 'ar_minor_edit' => 'rev_minor_edit', 'ar_rev_id' => 'rev_id', 'ar_text_id' => 'rev_text_id'), array('page_id' => $id, 'page_id = rev_page'), __METHOD__); # Now that it's safely backed up, delete it $dbw->delete('revision', array('rev_page' => $id), __METHOD__); $dbw->delete('page', array('page_id' => $id), __METHOD__); if ($wgUseTrackbacks) { $dbw->delete('trackbacks', array('tb_page' => $id), __METHOD__); } # Clean up recentchanges entries... $dbw->delete('recentchanges', array('rc_namespace' => $ns, 'rc_title' => $t), __METHOD__); // WERELATE: remove from watchlists on delete if (Namespac::isMain($ns)) { $rows = $dbw->select(array('watchlist', 'user'), array('user_name'), array('wl_user=user_id', 'wl_namespace' => $ns, 'wl_title' => $t)); while ($row = $dbw->fetchObject($rows)) { $user = User::newFromName($row->user_name, false); $wl = WatchedItem::fromUserTitle($user, $this->mTitle); $wl->removeWatch(); } $dbw->freeResult($rows); } # Finally, clean up the link tables $t = $this->mTitle->getPrefixedDBkey(); # Clear caches Article::onArticleDelete($this->mTitle); # Delete outgoing links $dbw->delete('pagelinks', array('pl_from' => $id)); $dbw->delete('imagelinks', array('il_from' => $id)); $dbw->delete('categorylinks', array('cl_from' => $id)); $dbw->delete('templatelinks', array('tl_from' => $id)); $dbw->delete('externallinks', array('el_from' => $id)); $dbw->delete('langlinks', array('ll_from' => $id)); # Log the deletion $log = new LogPage('delete', $rc); // WERELATE - add id as a log param $log->addEntry('delete', $this->mTitle, $reason, array($id)); # Clear the cached article id so the interface doesn't act like we exist $this->mTitle->resetArticleID(0); $this->mTitle->mArticleID = 0; return true; }
/** * Update wl_notificationtimestamp for all watching users except the editor */ private function updateWatchedItem(array $watchers) { $wl = WatchedItem::fromUserTitle($this->editor, $this->title); $wl->updateWatch($watchers, $this->timestamp); }
/** * update local watchlist */ private function updateLocalWatchlistForUser($iUserId, $sTitle, $iNamespace) { $dbw = wfGetDB(DB_MASTER); $oUser = User::newFromId($iUserId); if (!is_object($oUser)) { return false; } $oTitle = Title::makeTitle($iNamespace, $sTitle); if (!is_object($oTitle)) { return false; } $wl = WatchedItem::fromUserTitle($oUser, $oTitle); $wl->clearWatch(); return true; }
/** * auto-unwatch all comments if blog post was unwatched * * @access public * @static */ public static function UnwatchBlogComments($oUser, $oArticle) { wfProfileIn(__METHOD__); if (wfReadOnly()) { wfProfileOut(__METHOD__); return true; } /* @var $oUser User */ if (!$oUser instanceof User) { wfProfileOut(__METHOD__); return true; } /* @var $oArticle WikiPage */ if (!$oArticle instanceof Article) { wfProfileOut(__METHOD__); return true; } /* @var $oTitle Title */ $oTitle = $oArticle->getTitle(); if (!$oTitle instanceof Title) { wfProfileOut(__METHOD__); return true; } $list = array(); $dbr = wfGetDB(DB_SLAVE); $like = $dbr->buildLike(sprintf("%s/", $oTitle->getDBkey()), $dbr->anyString()); $res = $dbr->select('watchlist', '*', array('wl_user' => $oUser->getId(), 'wl_namespace' => NS_BLOG_ARTICLE_TALK, "wl_title {$like}"), __METHOD__); if ($res->numRows() > 0) { while ($row = $res->fetchObject()) { $oCommentTitle = Title::makeTitleSafe($row->wl_namespace, $row->wl_title); if ($oCommentTitle instanceof Title) { $list[] = $oCommentTitle; } } $dbr->freeResult($res); } if (!empty($list)) { foreach ($list as $oCommentTitle) { $oWItem = WatchedItem::fromUserTitle($oUser, $oCommentTitle); $oWItem->removeWatch(); } $oUser->invalidateCache(); } wfProfileOut(__METHOD__); return true; }
/** * Creates or changes a review for a page. Called by remote handler. * @return bool Allow other hooked methods to be executed. Always true. */ public static function doEditReview() { if (BsCore::checkAccessAdmission('workflowedit') === false) { return true; } $aAnswer = array('success' => true, 'errors' => array(), 'messages' => array()); $oUser = BsCore::loadCurrentUser(); $oReview = BsExtensionManager::getExtension('Review'); $userIsSysop = in_array('sysop', $oUser->getGroups()); //TODO: getEffectiveGroups()? if (!$userIsSysop && !$oUser->isAllowed('workflowedit')) { $aAnswer['success'] = false; $aAnswer['messages'][] = wfMessage('bs-review-save-norights')->plain(); return json_encode($aAnswer); } global $wgRequest; $paramRvPid = $wgRequest->getInt('pid', -1); // Check for id 0 prevents special pages to be put on a review if (empty($paramRvPid)) { $aAnswer['success'] = false; $aAnswer['messages'][] = wfMessage('bs-review-save-noid')->plain(); return json_encode($aAnswer); } $oReviewProcess = BsReviewProcess::newFromPid($paramRvPid); $bIsEdit = false; if (is_object($oReviewProcess) && $oReviewProcess->hasSteps()) { $bIsEdit = true; } if (!$userIsSysop && $oReviewProcess && BsConfig::get('MW::Review::CheckOwner') && $oReviewProcess->owner != $oUser->getID()) { $aAnswer['success'] = false; $aAnswer['messages'][] = wfMessage('bs-review-save-norights')->plain(); return json_encode($aAnswer); } $paramCmd = $wgRequest->getVal('cmd', ''); $paramSaveTmpl = $wgRequest->getInt('save_tmpl', 0); if (!($paramCmd === false)) { switch ($paramCmd) { case 'insert': $aErrors = array(); $review = BsReviewProcess::newFromJSON($wgRequest->getVal('review', ''), $aErrors); if (is_array($aErrors) && count($aErrors) > 0) { $aAnswer['success'] = false; foreach ($aErrors as $sError) { $aAnswer['messages'][] = wfMessage('bs-review-' . $sError)->plain(); } return json_encode($aAnswer); } $review->setOwner($oUser->getID()); $oOldReview = BsReviewProcess::newFromPid($paramRvPid); $update = is_object($oOldReview) ? $oOldReview->getPid() : false; BsReviewProcess::removeReviewSteps($paramRvPid); if ($paramSaveTmpl == 1) { $paramTmplChoice = $wgRequest->getInt('tmpl_choice', -1); $paramTmplName = $wgRequest->getVal('tmpl_name', ''); $review->asTemplate($paramTmplChoice, $paramTmplName); } if (!is_array($review->steps)) { $aAnswer['success'] = false; $aAnswer['messages'][] = wfMessage('bs-review-save-nosteps')->plain(); return json_encode($aAnswer); } if ($review->store($update)) { $oTitle = Title::newFromID($paramRvPid); $oTitle->invalidateCache(); $oWatchlist = WatchedItem::fromUserTitle($oUser, $oTitle); if (!$oWatchlist->isWatched()) { $oWatchlist->addWatch(); } $aParams = array('action' => $bIsEdit ? 'modify' : 'create', 'target' => $oTitle, 'comment' => '', 'params' => null, 'doer' => $oUser); $oReview->oLogger->addEntry($aParams['action'], $aParams['target'], $aParams['comment'], $aParams['params'], $aParams['doer']); $aAnswer['messages'][] = wfMessage('bs-review-save-success')->plain(); // Identify owner $oReviewProcess = BsReviewProcess::newFromPid($paramRvPid); $oReview->emailNotifyNextUsers($oReviewProcess); return json_encode($aAnswer); } else { $aAnswer['success'] = false; $aAnswer['messages'][] = wfMessage('bs-review-save-error')->plain(); return json_encode($aAnswer); } break; // 22.08.13 STM: WTF? // 22.08.13 STM: WTF? case 'delete': BsReviewProcess::removeReviews($paramRvPid); $oTitle = Title::newFromID($paramRvPid); $oTitle->invalidateCache(); $oWatchlist = WatchedItem::fromUserTitle($oUser, $oTitle); if ($oWatchlist->isWatched()) { $oWatchlist->removeWatch(); } $aParams = array('action' => 'delete', 'target' => $oTitle, 'comment' => '', 'params' => null, 'doer' => $oUser); $oReview->oLogger->addEntry($aParams['action'], $aParams['target'], $aParams['comment'], $aParams['params'], $aParams['doer']); $aAnswer['messages'][] = wfMessage('bs-review-save-removed')->plain(); return json_encode($aAnswer); break; } } return true; }
/** * Send emails corresponding to the user $editor editing the page $title. * Also updates wl_notificationtimestamp. * * May be deferred via the job queue. * * @param $editor User object * @param $title Title object * @param $timestamp * @param $summary * @param $minorEdit * @param $oldid (default: false) * @param $action(Wikia) * @param $otherParam(Wikia) */ public function notifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid = false, $action = '', $otherParam = array()) { global $wgEnotifUseJobQ, $wgEnotifWatchlist, $wgShowUpdatedMarker, $wgEnotifMinorEdits, $wgUsersNotifiedOnAllChanges, $wgEnotifUserTalk; if ($title->getNamespace() < 0) { return; } # <Wikia> if (!wfRunHooks('AllowNotifyOnPageChange', array($editor, $title))) { return false; } if (!empty($otherParam['watchers'])) { $watchers = $otherParam['watchers']; } # </Wikia> // Build a list of users to notfiy $watchers = array(); if ($wgEnotifWatchlist || $wgShowUpdatedMarker) { /* Wikia change begin - @author: wladek & tomek */ /* RT#55604: Add a timeout to the watchlist email block */ global $wgEnableWatchlistNotificationTimeout, $wgWatchlistNotificationTimeout; if (!empty($otherParam['notisnull'])) { $notificationTimeoutSql = "1"; } elseif (!empty($wgEnableWatchlistNotificationTimeout) && isset($wgWatchlistNotificationTimeout)) { $blockTimeout = wfTimestamp(TS_MW, wfTimestamp(TS_UNIX, $timestamp) - intval($wgWatchlistNotificationTimeout)); $notificationTimeoutSql = "wl_notificationtimestamp IS NULL OR wl_notificationtimestamp < '{$blockTimeout}'"; } else { $notificationTimeoutSql = 'wl_notificationtimestamp IS NULL'; } /* Wikia change end */ $dbw = wfGetDB(DB_MASTER); $res = $dbw->select(array('watchlist'), array('wl_user'), array('wl_title' => $title->getDBkey(), 'wl_namespace' => $title->getNamespace(), 'wl_user != ' . intval($editor->getID()), $notificationTimeoutSql), __METHOD__); foreach ($res as $row) { $watchers[] = intval($row->wl_user); } if ($watchers) { // Update wl_notificationtimestamp for all watching users except // the editor $wl = WatchedItem::fromUserTitle($editor, $title); $wl->updateWatch($watchers, $timestamp); } /* Wikia change begin - @author: Jakub Kurcek */ wfRunHooks('NotifyOnSubPageChange', array($watchers, $title, $editor, $notificationTimeoutSql)); /* Wikia change end */ } $sendEmail = true; // If nobody is watching the page, and there are no users notified on all changes // don't bother creating a job/trying to send emails // $watchers deals with $wgEnotifWatchlist if (!count($watchers) && !count($wgUsersNotifiedOnAllChanges)) { $sendEmail = false; // Only send notification for non minor edits, unless $wgEnotifMinorEdits if (!$minorEdit || $wgEnotifMinorEdits && !$editor->isAllowed('nominornewtalk')) { $isUserTalkPage = $title->getNamespace() == NS_USER_TALK; if ($wgEnotifUserTalk && $isUserTalkPage && $this->canSendUserTalkEmail($editor, $title, $minorEdit)) { $sendEmail = true; } } } if (!$sendEmail) { return; } if ($wgEnotifUseJobQ) { $params = array('editor' => $editor->getName(), 'editorID' => $editor->getID(), 'timestamp' => $timestamp, 'summary' => $summary, 'minorEdit' => $minorEdit, 'oldid' => $oldid, 'watchers' => $watchers, 'action' => $action, 'otherParam' => $othersParam); $job = new EnotifNotifyJob($title, $params); $job->insert(); } else { $this->actuallyNotifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers, $action, $otherParam); } }
/** * Do the rename operation */ function rename() { global $wgMemc, $wgDBname, $wgAuth; $fname = 'RenameuserSQL::rename'; wfProfileIn($fname); $dbw =& wfGetDB(DB_MASTER); foreach ($this->tables as $table => $field) { $dbw->update($table, array($field => $this->new), array($field => $this->old), $fname); } $fields = array('user_touched' => $dbw->timestamp()); // WERELATE: added isVandal if ($this->isVandal) { $fields = $fields + array('user_password' => '12345678901234567890123456789012', 'user_email' => ''); } $dbw->update('user', $fields, array('user_name' => $this->new), $fname); // Clear the user cache $wgMemc->delete("{$wgDBname}:user:id:{$this->uid}"); //WERELATE - newFromId doesn't exist // $user = User::newFromId( $this->uid ); $user = User::newFromName($this->new, false); $user->setID($this->uid); // WERELATE: added isVandal if ($this->isVandal) { // delete watchlist $rows = $dbw->select('watchlist', array('wl_namespace', 'wl_title'), array('wl_user' => $this->uid)); while ($row = $dbw->fetchObject($rows)) { $title = Title::makeTitle($row->wl_namespace, $row->wl_title); if ($title && !$title->isTalkPage()) { $wl = WatchedItem::fromUserTitle($user, $title); $wl->removeWatch(); } } $dbw->freeResult($rows); } // Inform authentication plugin of the change $wgAuth->updateExternalDB($user); wfProfileOut($fname); }