From d815ca352f8fde14bc00f84b2eec11c1fef7f066 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 7 Dec 2005 11:52:34 +0000 Subject: [PATCH] * (bug 4201) Fix user-talk mode for Enotif, and general code cleanup - treat NULL properly in watchlist lookup for notification sending - consolidate a lot of ugly code that fiddles in those tables - use user_newtalk consistently in enotif mode; watchlist for email notifications, user_newtalk for on-screen message and status check - clean up handling of other peoples' user_talk pages when in the talk-only limited enotif: only your own will get sent, not other people watching your page - and others watching your page _will_ work in watchable enotif mode - add a watch on page + talk page consistently for the auto-add of the user talk page, not just half the page - don't unwatch the user talk page on view! that's just wacky - removed UserTalkUpdate, now redundant - have User::setNewTalk() apply immediately - clear newtalk from User::clearNotification() and User::clearAllNotifications() --- RELEASE-NOTES | 1 + includes/Article.php | 52 +++----- includes/Database.php | 7 -- includes/DatabaseOracle.php | 6 - includes/User.php | 231 ++++++++++++++++++------------------ includes/UserMailer.php | 106 ++++++++++------- includes/UserTalkUpdate.php | 116 ------------------ 7 files changed, 196 insertions(+), 323 deletions(-) delete mode 100644 includes/UserTalkUpdate.php diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 51686e12cc..1c1d9906f3 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -301,6 +301,7 @@ fully support the editing toolbar, but was found to be too confusing. * (bug 4170) Decode HTML character escapes in sort key * Avoid FATAL ERROR when creating thumbnail of non-existing image * (bug 4192) Remove silly 'The Free Encyclopedia' default sitesubtitle +* (bug 4201) Fix user-talk mode for Enotif, and general code cleanup === Caveats === diff --git a/includes/Article.php b/includes/Article.php index 832f1dad42..0950e6163c 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -1413,7 +1413,7 @@ class Article { * the link tables and redirect to the new page. */ function showArticle( $text, $subtitle , $sectionanchor = '', $me2, $now, $summary, $oldid, $newid ) { - global $wgUseDumbLinkUpdate, $wgAntiLockFlags, $wgOut, $wgUser, $wgLinkCache, $wgEnotif; + global $wgUseDumbLinkUpdate, $wgAntiLockFlags, $wgOut, $wgUser, $wgLinkCache; global $wgUseEnotif; $fname = 'Article::showArticle'; @@ -1457,8 +1457,8 @@ class Article { if ( $wgUseEnotif ) { # this would be better as an extension hook include_once( "UserMailer.php" ); - $wgEnotif = new EmailNotification (); - $wgEnotif->notifyOnPageChange( $this->mTitle, $now, $summary, $me2, $oldid ); + $enotif = new EmailNotification (); + $enotif->notifyOnPageChange( $this->mTitle, $now, $summary, $me2, $oldid ); } wfProfileOut( $fname ); } @@ -2141,7 +2141,7 @@ class Article { * @private */ function viewUpdates() { - global $wgDeferredUpdateList, $wgUseEnotif; + global $wgDeferredUpdateList; if ( 0 != $this->getID() ) { global $wgDisableCounters; @@ -2152,24 +2152,9 @@ class Article { } } - # Update newtalk status if user is reading their own - # talk page - + # Update newtalk / watchlist notification status global $wgUser; - if ($this->mTitle->getNamespace() == NS_USER_TALK && - $this->mTitle->getText() == $wgUser->getName()) - { - if ( $wgUseEnotif ) { - require_once( 'UserTalkUpdate.php' ); - $u = new UserTalkUpdate( 0, $this->mTitle->getNamespace(), $this->mTitle->getDBkey(), false, false, false ); - } else { - $wgUser->setNewtalk(0); - $wgUser->saveNewtalk(); - } - } elseif ( $wgUseEnotif ) { - $wgUser->clearNotification( $this->mTitle ); - } - + $wgUser->clearNotification( $this->mTitle ); } /** @@ -2179,7 +2164,7 @@ class Article { * @param string $text */ function editUpdates( $text, $summary, $minoredit, $timestamp_of_pagechange) { - global $wgDeferredUpdateList, $wgMessageCache, $wgUser, $wgUseEnotif; + global $wgDeferredUpdateList, $wgMessageCache, $wgUser; if ( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) { wfSeedRandom(); @@ -2210,21 +2195,14 @@ class Article { # If this is another user's talk page, update newtalk if ($this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $wgUser->getName()) { - if ( $wgUseEnotif ) { - require_once( 'UserTalkUpdate.php' ); - $u = new UserTalkUpdate( 1, $this->mTitle->getNamespace(), $shortTitle, $summary, - $minoredit, $timestamp_of_pagechange); - } else { - $other = User::newFromName( $shortTitle ); - if( is_null( $other ) && User::isIP( $shortTitle ) ) { - // An anonymous user - $other = new User(); - $other->setName( $shortTitle ); - } - if( $other ) { - $other->setNewtalk(1); - $other->saveNewtalk(); - } + $other = User::newFromName( $shortTitle ); + if( is_null( $other ) && User::isIP( $shortTitle ) ) { + // An anonymous user + $other = new User(); + $other->setName( $shortTitle ); + } + if( $other ) { + $other->setNewtalk( true ); } } diff --git a/includes/Database.php b/includes/Database.php index f573ae06a5..df56815236 100644 --- a/includes/Database.php +++ b/includes/Database.php @@ -1668,13 +1668,6 @@ class Database { function encodeBlob($b) { return $b; } - - function notNullTimestamp() { - return "!= 0"; - } - function isNullTimestamp() { - return "= '0'"; - } } /** diff --git a/includes/DatabaseOracle.php b/includes/DatabaseOracle.php index c5cb1420e1..f41f169d49 100644 --- a/includes/DatabaseOracle.php +++ b/includes/DatabaseOracle.php @@ -462,12 +462,6 @@ class DatabaseOracle extends Database { # return "TO_TIMESTAMP('" . $this->strencode(wfTimestamp(TS_DB, $ts)) . "', 'RRRR-MM-DD HH24:MI:SS')"; } - function notNullTimestamp() { - return "IS NOT NULL"; - } - function isNullTimestamp() { - return "IS NULL"; - } /** * Return aggregated value function call */ diff --git a/includes/User.php b/includes/User.php index 0b47fa84b4..057c070dc5 100644 --- a/includes/User.php +++ b/includes/User.php @@ -758,13 +758,11 @@ class User { } function getNewtalk() { - global $wgUseEnotif; - $fname = 'User::getNewtalk'; $this->loadFromDatabase(); # Load the newtalk status if it is unloaded (mNewtalk=-1) - if( $this->mNewtalk == -1 ) { - $this->mNewtalk = 0; # reset talk page status + if( $this->mNewtalk === -1 ) { + $this->mNewtalk = false; # reset talk page status # Check memcached separately for anons, who have no # entire User object stored in there. @@ -773,49 +771,122 @@ class User { $key = "$wgDBname:newtalk:ip:" . $this->getName(); $newtalk = $wgMemc->get( $key ); if( is_integer( $newtalk ) ) { - $this->mNewtalk = $newtalk ? 1 : 0; - return (bool)$this->mNewtalk; - } - } - - $dbr =& wfGetDB( DB_SLAVE ); - if ( $wgUseEnotif ) { - $res = $dbr->select( 'watchlist', - array( 'wl_user' ), - array( 'wl_title' => $this->getTitleKey(), - 'wl_namespace' => NS_USER_TALK, - 'wl_user' => $this->mId, - 'wl_notificationtimestamp ' . $dbr->notNullTimestamp() ), - 'User::getNewtalk' ); - if( $dbr->numRows($res) > 0 ) { - $this->mNewtalk = 1; - } - $dbr->freeResult( $res ); - } elseif ( $this->mId ) { - $res = $dbr->select( 'user_newtalk', 1, array( 'user_id' => $this->mId ), $fname ); - - if ( $dbr->numRows($res)>0 ) { - $this->mNewtalk= 1; + $this->mNewtalk = (bool)$newtalk; + } else { + $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() ); + $wgMemc->set( $key, $this->mNewtalk, time() ); // + 1800 ); } - $dbr->freeResult( $res ); } else { - $res = $dbr->select( 'user_newtalk', 1, array( 'user_ip' => $this->getName() ), $fname ); - $this->mNewtalk = $dbr->numRows( $res ) > 0 ? 1 : 0; - $dbr->freeResult( $res ); - } - - if( !$this->mId ) { - $wgMemc->set( $key, $this->mNewtalk, time() ); // + 1800 ); + $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId ); } } - return ( 0 != $this->mNewtalk ); + return (bool)$this->mNewtalk; } + /** + * Perform a user_newtalk check on current slaves; if the memcached data + * is funky we don't want newtalk state to get stuck on save, as that's + * damn annoying. + * + * @param string $field + * @param mixed $id + * @return bool + * @access private + */ + function checkNewtalk( $field, $id ) { + $fname = 'User::checkNewtalk'; + $dbr =& wfGetDB( DB_SLAVE ); + $ok = $dbr->selectField( 'user_newtalk', $field, + array( $field => $id ), $fname ); + return $ok !== false; + } + + /** + * Add or update the + * @param string $field + * @param mixed $id + * @access private + */ + function updateNewtalk( $field, $id ) { + $fname = 'User::updateNewtalk'; + if( $this->checkNewtalk( $field, $id ) ) { + wfDebug( "$fname already set ($field, $id), ignoring\n" ); + return false; + } + $dbw =& wfGetDB( DB_MASTER ); + $dbw->insert( 'user_newtalk', + array( $field => $id ), + $fname, + 'IGNORE' ); + wfDebug( "$fname: set on ($field, $id)\n" ); + return true; + } + + /** + * @param string $field + * @param mixed $id + * @access private + */ + function deleteNewtalk( $field, $id ) { + $fname = 'User::deleteNewtalk'; + if( !$this->checkNewtalk( $field, $id ) ) { + wfDebug( "$fname: already gone ($field, $id), ignoring\n" ); + return false; + } + $dbw =& wfGetDB( DB_MASTER ); + $dbw->delete( 'user_newtalk', + array( $field => $id ), + $fname ); + wfDebug( "$fname: killed on ($field, $id)\n" ); + return true; + } + + /** + * Update the 'You have new messages!' status. + * @param bool $val + */ function setNewtalk( $val ) { + if( wfReadOnly() ) { + return; + } + $this->loadFromDatabase(); $this->mNewtalk = $val; - $this->invalidateCache(); + + $fname = 'User::setNewtalk'; + + if( $this->isAnon() ) { + $field = 'user_ip'; + $id = $this->getName(); + } else { + $field = 'user_id'; + $id = $this->getId(); + } + + if( $val ) { + $changed = $this->updateNewtalk( $field, $id ); + } else { + $changed = $this->deleteNewtalk( $field, $id ); + } + + if( $changed ) { + if( $this->isAnon() ) { + // Anons have a separate memcached space, since + // user records aren't kept for them. + global $wgDBname, $wgMemc; + $key = "$wgDBname:newtalk:ip:$value"; + $wgMemc->set( $key, $val ? 1 : 0 ); + } else { + if( $val ) { + // Make sure the user page is watched, so a notification + // will be sent out if enabled. + $this->addWatch( $this->getTalkPage() ); + } + } + $this->invalidateCache(); + $this->saveSettings(); + } } function invalidateCache() { @@ -1150,12 +1221,17 @@ class User { function clearNotification( &$title ) { global $wgUser, $wgUseEnotif; - if ( !$wgUseEnotif ) { + if ($title->getNamespace() == NS_USER_TALK && + $title->getText() == $this->getName() ) { + $this->setNewtalk( false ); + } + + if( !$wgUseEnotif ) { return; } - $userid = $this->getID(); - if ($userid==0) { + if( $this->isAnon() ) { + // Nothing else to do... return; } @@ -1202,6 +1278,7 @@ class User { function clearAllNotifications( $currentUser ) { global $wgUseEnotif; if ( !$wgUseEnotif ) { + $this->setNewtalk( false ); return; } if( $currentUser != 0 ) { @@ -1291,7 +1368,6 @@ class User { $fname = 'User::saveSettings'; if ( wfReadOnly() ) { return; } - $this->saveNewtalk(); if ( 0 == $this->mId ) { return; } $dbw =& wfGetDB( DB_MASTER ); @@ -1313,79 +1389,6 @@ class User { $wgMemc->delete( "$wgDBname:user:id:$this->mId" ); } - /** - * Save value of new talk flag. - */ - function saveNewtalk() { - global $wgDBname, $wgMemc, $wgUseEnotif; - - $fname = 'User::saveNewtalk'; - - $changed = false; - - if ( wfReadOnly() ) { return ; } - $dbr =& wfGetDB( DB_SLAVE ); - $dbw =& wfGetDB( DB_MASTER ); - $changed = false; - if ( $wgUseEnotif ) { - if ( ! $this->getNewtalk() ) { - # Delete the watchlist entry for user_talk page X watched by user X - $dbw->delete( 'watchlist', - array( 'wl_user' => $this->mId, - 'wl_title' => $this->getTitleKey(), - 'wl_namespace' => NS_USER_TALK ), - $fname ); - if ( $dbw->affectedRows() ) { - $changed = true; - } - if( !$this->mId ) { - # Anon users have a separate memcache space for newtalk - # since they don't store their own info. Trim... - $wgMemc->delete( "$wgDBname:newtalk:ip:" . $this->getName() ); - } - } - } else { - if ($this->getID() != 0) { - $field = 'user_id'; - $value = $this->getID(); - $key = false; - } else { - $field = 'user_ip'; - $value = $this->getName(); - $key = "$wgDBname:newtalk:ip:$value"; - } - - $dbr =& wfGetDB( DB_SLAVE ); - $dbw =& wfGetDB( DB_MASTER ); - - $res = $dbr->selectField('user_newtalk', $field, - array($field => $value), $fname); - - $changed = true; - if ($res !== false && $this->mNewtalk == 0) { - $dbw->delete('user_newtalk', array($field => $value), $fname); - if ( $key ) { - $wgMemc->set( $key, 0 ); - } - } else if ($res === false && $this->mNewtalk == 1) { - $dbw->insert('user_newtalk', array($field => $value), $fname); - if ( $key ) { - $wgMemc->set( $key, 1 ); - } - } else { - $changed = false; - } - } - - # Update user_touched, so that newtalk notifications in the client cache are invalidated - if ( $changed && $this->getID() ) { - $dbw->update('user', - /*SET*/ array( 'user_touched' => $this->mTouched ), - /*WHERE*/ array( 'user_id' => $this->getID() ), - $fname); - $wgMemc->set( "$wgDBname:user:id:{$this->mId}", $this, 86400 ); - } - } /** * Checks if a user with the given name exists, returns the ID diff --git a/includes/UserMailer.php b/includes/UserMailer.php index d510b76d01..91899cd4a5 100644 --- a/includes/UserMailer.php +++ b/includes/UserMailer.php @@ -68,7 +68,7 @@ function userMailer( $to, $from, $subject, $body, $replyto=false ) { // Create the mail object using the Mail::factory method $mail_object =& Mail::factory('smtp', $wgSMTP); - wfDebug( "Sending mail via PEAR::Mail\n" ); + wfDebug( "Sending mail via PEAR::Mail to $to\n" ); $mailResult =& $mail_object->send($to, $headers, $body); # Based on the result return an error string, @@ -94,7 +94,7 @@ function userMailer( $to, $from, $subject, $body, $replyto=false ) { $wgErrorString = ''; set_error_handler( 'mailErrorHandler' ); - wfDebug( "Sending mail via internal mail() function\n" ); + wfDebug( "Sending mail via internal mail() function to $to\n" ); mail( $to, $subject, $body, $headers ); restore_error_handler(); @@ -171,47 +171,67 @@ class EmailNotification { $isUserTalkPage = ($title->getNamespace() == NS_USER_TALK); $enotifusertalkpage = ($isUserTalkPage && $wgEnotifUserTalk); - $enotifwatchlistpage = (!$isUserTalkPage && $wgEnotifWatchlist); - - - if ( ($enotifusertalkpage || $enotifwatchlistpage) && (!$minorEdit || $wgEnotifMinorEdits) ) { - $dbr =& wfGetDB( DB_MASTER ); - extract( $dbr->tableNames( 'watchlist' ) ); - $res = $dbr->select( 'watchlist', array( 'wl_user' ), - array( - 'wl_title' => $title->getDBkey(), - 'wl_namespace' => $title->getNamespace(), - 'wl_user <> ' . $wgUser->getID(), - 'wl_notificationtimestamp ' . $dbr->isNullTimestamp(), - ), $fname ); - - # if anyone is watching ... set up the email message text which is - # common for all receipients ... - if ( $dbr->numRows( $res ) > 0 ) { - $this->user &= $wgUser; - $this->title =& $title; - $this->timestamp = $timestamp; - $this->summary = $summary; - $this->minorEdit = $minorEdit; - $this->oldid = $oldid; - - $this->composeCommonMailtext(); - $watchingUser = new User(); - - # ... now do for all watching users ... if the options fit - for ($i = 1; $i <= $dbr->numRows( $res ); $i++) { - - $wuser = $dbr->fetchObject( $res ); - $watchingUser->setID($wuser->wl_user); - if ( ( $enotifwatchlistpage && $watchingUser->getOption('enotifwatchlistpages') ) || - ( $enotifusertalkpage && $watchingUser->getOption('enotifusertalkpages') ) - && (!$minorEdit || ($wgEnotifMinorEdits && $watchingUser->getOption('enotifminoredits') ) ) - && ($watchingUser->isEmailConfirmed() ) ) { - # ... adjust remaining text and page edit time placeholders - # which needs to be personalized for each user - $this->composeAndSendPersonalisedMail( $watchingUser ); - - } # if the watching user has an email address in the preferences + $enotifwatchlistpage = $wgEnotifWatchlist; + + if ( (!$minorEdit || $wgEnotifMinorEdits) ) { + if( $wgEnotifWatchlist ) { + // Send updates to watchers other than the current editor + $userCondition = 'wl_user <> ' . intval( $wgUser->getId() ); + } elseif( $wgEnotifUserTalk && $title->getNamespace() == NS_USER_TALK ) { + $targetUser = User::newFromName( $title->getText() ); + if( is_null( $targetUser ) ) { + wfDebug( "$fname: user-talk-only mode; no such user\n" ); + $userCondition = false; + } elseif( $targetUser->getId() == $wgUser->getId() ) { + wfDebug( "$fname: user-talk-only mode; editor is target user\n" ); + $userCondition = false; + } else { + // Don't notify anyone other than the owner of the talk page + $userCondition = 'wl_user = ' . intval( $targetUser->getId() ); + } + } else { + // Notifications disabled + $userCondition = false; + } + if( $userCondition ) { + $dbr =& wfGetDB( DB_MASTER ); + extract( $dbr->tableNames( 'watchlist' ) ); + + $res = $dbr->select( 'watchlist', array( 'wl_user' ), + array( + 'wl_title' => $title->getDBkey(), + 'wl_namespace' => $title->getNamespace(), + $userCondition, + 'wl_notificationtimestamp IS NULL', + ), $fname ); + + # if anyone is watching ... set up the email message text which is + # common for all receipients ... + if ( $dbr->numRows( $res ) > 0 ) { + $this->title =& $title; + $this->timestamp = $timestamp; + $this->summary = $summary; + $this->minorEdit = $minorEdit; + $this->oldid = $oldid; + + $this->composeCommonMailtext(); + $watchingUser = new User(); + + # ... now do for all watching users ... if the options fit + for ($i = 1; $i <= $dbr->numRows( $res ); $i++) { + + $wuser = $dbr->fetchObject( $res ); + $watchingUser->setID($wuser->wl_user); + if ( ( $enotifwatchlistpage && $watchingUser->getOption('enotifwatchlistpages') ) || + ( $enotifusertalkpage && $watchingUser->getOption('enotifusertalkpages') ) + && (!$minorEdit || ($wgEnotifMinorEdits && $watchingUser->getOption('enotifminoredits') ) ) + && ($watchingUser->isEmailConfirmed() ) ) { + # ... adjust remaining text and page edit time placeholders + # which needs to be personalized for each user + $this->composeAndSendPersonalisedMail( $watchingUser ); + + } # if the watching user has an email address in the preferences + } } } # if anyone is watching } # if $wgEnotifWatchlist = true diff --git a/includes/UserTalkUpdate.php b/includes/UserTalkUpdate.php deleted file mode 100644 index d04acbc097..0000000000 --- a/includes/UserTalkUpdate.php +++ /dev/null @@ -1,116 +0,0 @@ - - * http://www.mediawiki.org/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * See deferred.txt - * - * @package MediaWiki - * @author - */ - -/** - * - * @package MediaWiki - */ -class UserTalkUpdate { - - /**#@+ - * @access private - */ - var $mAction, $mNamespace, $mTitle, $mSummary, $mMinorEdit, $mTimestamp; - /**#@-*/ - - /** - * @todo document - * @param string $action - * @param integer $ns - * @param $title - * @param $summary - * @param $minoredit - * @param $timestamp - */ - function UserTalkUpdate( $action, $ns, $title, $summary, $minoredit, $timestamp) { - global $wgUser, $wgMemc, $wgDBname; - - $this->mAction = $action; - $this->mNamespace = $ns; - $this->mTitle = $title; # str_replace( '_', ' ', $title ); # I do not know, why this was needed . T. Gries 23.11.2004 - $this->mSummary = $summary; - $this->mMinorEdit = $minoredit; - $this->mTimestamp = $timestamp; - - # If namespace isn't User_talk:, do nothing. - if ( $this->mNamespace != NS_USER_TALK ) { - return; - } - - # If the user talk page is our own, clear the flag - # when we are reading it or writing it. - if ( 0 == strcmp( $this->mTitle, $wgUser->getName() ) ) { - $wgUser->setNewtalk( 0 ); - $wgUser->saveSettings(); - } else { - # Not ours. If writing, then mark it as modified. - $sql = false; - if ( 1 == $this->mAction ) { - $user = new User(); - $user->setID(User::idFromName($this->mTitle)); - - if ($id=$user->getID()) { - $sql = true; - $wgMemc->delete( "$wgDBname:user:id:$id" ); - } else { - if ( $wgUser->isIP($this->mTitle) ) { # anonymous - $dbw =& wfGetDB( DB_MASTER ); - $dbw->replace( 'watchlist', - array(array('wl_user','wl_namespace', 'wl_title', 'wl_notificationtimestamp')), - array('wl_user' => 0, - 'wl_namespace' => NS_USER_TALK, - 'wl_title' => $this->mTitle, - 'wl_notificationtimestamp' => 1 - ), 'UserTalkUpdate' - ); - $sql = true; - $wgMemc->delete( "$wgDBname:newtalk:ip:$this->mTitle" ); - } - } - - if($sql && !$user->getNewtalk() ) { - # create an artificial watchlist table entry for the owner X of the user_talk page X - # only insert if X is a real user and the page is not yet watched - # mark the changed watch-listed page with a timestamp, so that the page is listed with - # an "updated since your last visit" icon in the watch list, ... - # ... no matter, if the watching user has or has not indicated an email address in his/her preferences. - # We memorise the event of sending out a notification and use this as a flag to suppress - # further mails for changes on the same page for that watching user - $dbw =& wfGetDB( DB_MASTER ); - $dbw->replace( 'watchlist', - array(array('wl_user','wl_namespace', 'wl_title', 'wl_notificationtimestamp')), - array('wl_user' => $id, - 'wl_namespace' => NS_USER_TALK, - 'wl_title' => $this->mTitle, - 'wl_notificationtimestamp' => 1 - ), 'UserTalkUpdate' - ); - } - } - } - } -} - -?> -- 2.20.1