(bug 454) Merge e-notif 2.00
authorBrion Vibber <brion@users.mediawiki.org>
Sat, 18 Dec 2004 03:47:11 +0000 (03:47 +0000)
committerBrion Vibber <brion@users.mediawiki.org>
Sat, 18 Dec 2004 03:47:11 +0000 (03:47 +0000)
From http://bugzilla.wikipedia.org/attachment.cgi?id=171&action=view
and extra files from http://www.tgries.de/mw/cvs15+enea200-minus-cvs.tgz

Undid a couple minor bits like the broken changing of minoreditletter.
Have not yet made general corrections/reworkings. Since this changes
the user and watchlist tables, you must run the updaters (installer or
update.php) on an existing database.

34 files changed:
README
RELEASE-NOTES
config/index.php
includes/Article.php
includes/ChangesList.php
includes/DefaultSettings.php
includes/PageHistory.php
includes/RecentChange.php
includes/Setup.php
includes/Skin.php
includes/SkinTemplate.php
includes/SpecialEmailuser.php
includes/SpecialPreferences.php
includes/SpecialRecentchanges.php
includes/SpecialRecentchangeslinked.php
includes/SpecialUserlevels.php
includes/SpecialUserlogin.php
includes/SpecialWatchlist.php
includes/User.php
includes/UserMailer.php
includes/WatchedItem.php
includes/templates/Userlogin.php
languages/Language.php
languages/LanguageDe.php
maintenance/archives/patch-drop-user_newtalk.sql [new file with mode: 0644]
maintenance/archives/patch-email-authentication.sql [new file with mode: 0644]
maintenance/archives/patch-email-notification.sql [new file with mode: 0644]
maintenance/tables.sql
maintenance/updaters.inc
skins/MonoBook.php
skins/amethyst/main.css
skins/chick/main.css
skins/common/common.css
skins/monobook/main.css

diff --git a/README b/README
index 73056dd..33aa184 100644 (file)
--- a/README
+++ b/README
@@ -59,6 +59,10 @@ Documentation and discussion on new features may be found at:
   http://meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide
   http://meta.wikipedia.org/wiki/MediaWiki_development
   
+Documentation about the E-Mail notification can be found on:
+
+  http://meta.wikipedia.org/Enotif
+
 If you are setting up your own wiki based on this software, it is highly
 recommended that you subscribe to mediawiki-l:
 
index ab17213..d41040b 100644 (file)
@@ -3,6 +3,111 @@
 Security reminder: MediaWiki does not require PHP's register_globals
 setting since version 1.2.0. If you have it on, turn it *off* if you can.
 
+== Version Enotif+Eauthent EN+EA v2.00/CVS, 14.12.2004 ==
+written by Thomas Gries, Berlin and Markus Arndt, Munich
+
+Executive summary for the impatient reader:
+
+Enotif adds e-mail notification to MediaWiki and sends e-mails
+to watching users when a watch-listed page or user_talk page is changed
+http://bugzilla.wikipedia.org/show_bug.cgi?id=454
+Visit the complete documentation on http://meta.wikipedia.org/Enotif
+
+Eauthent is a mechanism to use a temporary one-time password cycle
+to check whether the email address a user has entered is a valid one.
+http://bugzilla.wikipedia.org/show_bug.cgi?id=866
+Visit the complete documentation on http://meta.wikipedia.org/Eauthent
+
+The current patch has only been checked for (see DefaultSettings.php):
+
+- php mail()
+  ( = not using PEAR:Mail() module --- I do not know anyone who uses that)
+  $wgSMTP = false;
+- MySQL database
+  ( = not using PostgreSQL --- I do not know anyone who uses that)
+  $wgDBtype = "mysql";
+  $wgSearchType = "MyISAM";
+
+- STILL TODO:
+  NEW (newpageletter) and CORR (minoreditletter) markers needs
+  corresponding "spacers"
+
+- table user_newtalk dropped; changes on usertalk pages and their
+  notifications are now fully handled via existing table watchlist
+  The user interface and behavious is unchanged to previous version.
+- updaters.inc for compatibility with older mediawiki tables:
+  the conversion script converts existing user_newtalk entries
+  watchlist table entries
+  (user_newtalk) id ==> (watchlist) NS_USER_TALK:namefromId(id) timestamp=1
+- minor bug fixes:
+  updated marker now correctly shown on watchlist page
+  watching users number display with enhanced RC view + RCUseModStyle
+- wfUrlencode() instead of rawurlencode() in enotif mails
+- duplicate enotif code moved from UserTalkUpdate.php and
+  merged into UserMailer.php and using usermailer() solely
+- fixed an enotif mail text error for user names with spaces
+- fixed missing part for suppressing watching user number
+
+v1.36:
+magic watchlist shows and counts now only the content page, notwithstanding
+the content and talk page are stored separately in watchlist.
+* bug fixed: rawurlencode for pagetitles in enotif mails
+* bug fixed: link to userpages of anonymous user are correct now
+
+* Enotif v1.34
+ bugs fixed regarding missing $oldid parameter
+
+* v1.33
+* Details:
+ Implements almost all enotif options as user preferences.
+ These are only shown on the user preference page, if they are globally
+ enabled by the corresponding admin option in DefaultSettings.php.
+ Added admin feature to let enotifs appear to come from the page editor.
+ This facilitates automatic mail sorting and anti-spam filtering; feature was
+ originally proposed by Nick Triantos, thank you !
+ Page editor's email address is however only shown, if this user enabled
+ the option "reveal my email address" in user preferences. Otherwise,
+ the enotifs appear to come from WikiAdmin as usual (tricky to program,
+ but simply trust the algorithm. or look into UserMailer.php and
+ UserTalkPage.php).
+
+* Changes from previous enotif versions
+* v1.31 is an improved version with many security and also cosmetic changes
+ applied after two first reviews by Brion Vibber. v1.31 is basically the same as
+ the older Enotif v1.30 and v1.22 versions.
+
+ Added UseMod style for recent changes view so that only the most recent
+ change of any page is listed. The (diff) and (hist) still allow to retrieve the
+ older versions at users' discretion, but the RC view is much cleaner for
+ trusted environments such as medium-size companies or family wikis.
+
+* Enotif v1.30 redesign after review by Brion Vibber 25.10.2003
+
+* v1.22 "updated (since my last visit)" also shown for users without stored
+ email address in preferences, so that they can see, what watched pages
+ have changed.
+* show "updated (since my last visit)" markers in RC, history and watchlist
+* Systemvariables to suppress updated marker in all views
+* show number of watching users in RC and on bottom of articles in
+ classic skin and in monobook skin
+* Systemvariables in DefaultSettings.php to enable or disable features
+* v1.21 now suppresses displaying the marker "updated (since my last visit)"
+ in recent changes view for the older (already visited) versions of watched
+ pages - i.e. page versions before the enotif was sent do not bear that
+ marker any longer.
+* enotif mails come with a link to the diff view between current and last
+ visited version of the watching user.
+*database structure is changed automatically when installing via the
+ recommended way (starting index.php and re-using the old database name).
+       run php /maintenance/update.php
+ OR see /maintenance/archives/patch-email-notification.sql and apply the
+ command
+       ALTER TABLE  watchlist
+       ADD (wl_notificationtimestamp varchar(14) binary NOT NULL default '0');
+ manually to your database, which does not harm the non notification versions
+
+*adding a page x to the watchlist does automatically add a watch
+  for the accompanying talk_page talk:x and vice versa;
 
 == MediaWiki 1.4 BETA ==
 
index c5c65c5..efed31f 100644 (file)
@@ -80,7 +80,7 @@ header( "Content-type: text/html; charset=utf-8" );
 
  <b><a href="http://www.mediawiki.org/">MediaWiki</a></b> is
  Copyright (C) 2001-2004 by Magnus Manske, Brion Vibber, Lee Daniel Crocker,
- Tim Starling, Erik M&ouml;ller, Gabriel Wicke and others.</p>
+ Tim Starling, Erik M&ouml;ller, Gabriel Wicke, Thomas Gries and others.</p>
 
  <ul>
  <li><a href="../README">Readme</a></li>
@@ -387,6 +387,12 @@ if ( $conf->Shm == 'memcached' && $conf->MCServers ) {
        $errs["MCServers"] = "Please specify at least one server if you wish to use memcached";
 }
 
+/* default values for installation */
+$conf->Email   =importRequest("Email", "email_enabled");
+$conf->Emailuser=importRequest("Emailuser", "emailuser_enabled");
+$conf->Enotif  =importRequest("Enotif", "enotif_allpages");
+$conf->Eauthent        =importRequest("Eauthent", "eauthent_enabled");
+
 if( $conf->posted && ( 0 == count( $errs ) ) ) {
        do { /* So we can 'continue' to end prematurely */
                $conf->Root = ($conf->RootPW != "");
@@ -636,7 +642,8 @@ if( count( $errs ) ) {
        <dt>
                This will be used as the return address for password reminders and
                may be displayed in some error conditions so visitors can get in
-               touch with you.
+               touch with you. It is also be used as the default sender address of e-mail
+               notifications (enotifs).
        </dt>
 
        <dd>
@@ -733,6 +740,87 @@ if( count( $errs ) ) {
                use Turck shared memory if the wiki will be running on a single Apache server.
        </dl>
 
+<h2>E-mail, e-mail notification and authentification setup</h2>
+
+<dl class="setup">
+       <dd>
+               <label class='column'>E-mail (general)</label>
+               <div>Select one:</div>
+
+               <ul class="plain">
+               <li><?php aField( $conf, "Email", "enabled", "radio", "email_enabled" ); ?></li>
+               <li><?php aField( $conf, "Email", "disabled", "radio", "email_disabled" ); ?></li>
+               </ul>
+       </dd>
+       <dt>
+               Use this to disable all e-mail functions (send a password reminder, user-to-user e-mail and e-mail notification),
+               if sending e-mails on your server doesn't work.
+       </dt>
+       <dd>
+               <label class='column'>User-to-user e-mail</label>
+               <div>Select one:</div>
+
+               <ul class="plain">
+               <li><?php aField( $conf, "Emailuser", "enabled", "radio", "emailuser_enabled" ); ?></li>
+               <li><?php aField( $conf, "Emailuser", "disabled", "radio", "emailuser_disabled" ); ?></li>
+               </ul>
+       </dd>
+       <dt>
+               Use this to disable only the user-to-user e-mail function (EmailUser).
+       </dt>
+       <dd>
+               <label class='column'>E-mail notification</label>
+               <div>Select one:</div>
+
+               <ul class="plain">
+               <li><?php aField( $conf, "Enotif", "disabled", "radio", "enotif_disabled" ); ?></li>
+               <li><?php aField( $conf, "Enotif", "enabled for changes of watch-listed and user_talk pages (recommended for small wikis; perhaps not suited for large wikis)", "radio", "enotif_allpages" ); ?></li>
+               <li><?php aField( $conf, "Enotif", "enabled for changes of user_talk pages only (suited for small and large wikis)", "radio", "enotif_usertalk" ); ?></li>
+               </ul>
+       </dd>
+       <dt>
+               <p><?php
+                       $ccEnotif = htmlspecialchars( 'http://meta.wikipedia.org/Enotif' );
+                       print "<a href=\"$ccEnotif\">E-mail notification</a>";
+               ?>
+                sends a notification e-mail to a user, when the user_talk page is changed
+                and/or when watch-listed pages are changed, depending on the above settings.
+               When testing this feature, be reminded, that obviously an e-mail address must be present in your preferences
+               and that your own changes never trigger notifications to be sent to yourself.</p>
+
+               <p>Users get corresponding options to select or deselect in their users' preferences.
+               The user options are not shown on the preference page, if e-mail notification is disabled.</p>
+
+               <p>There are additional options for fine tuning in /includes/DefaultSettings.php .</p>
+       </dt>
+
+       <dd>
+               <label class='column'>E-mail address authentication</label>
+               <div>Select one:</div>
+
+               <ul class="plain">
+               <li><?php aField( $conf, "Eauthent", "disabled", "radio", "eauthent_disabled" ); ?></li>
+               <li><?php aField( $conf, "Eauthent", "enabled", "radio", "eauthent_enabled" ); ?></li>
+               </ul>
+       </dd>
+       <dt>
+               <p><?php
+                       $ccEauthent = htmlspecialchars( 'http://meta.wikipedia.org/Eauthent' );
+                       print "<a href=\"$ccEnotif\">E-mail address authentication</a>";
+               ?>
+                uses a scheme to authenticate e-mail addresses of the users. The user who initially enters or who changes his/her stored e-mail address
+               gets a one-time temporary password mailed to that address. The user can use the original password as long as wanted, however, the stored e-mail address
+               is only authenticated at the moment when the user logs in with the one-time temporary password.<p>
+
+               <p>The e-mail address stays authenticated as long as the user does not change it; the time of authentication is indicated
+               on the user preference page.</p>
+
+               <p>If the option is enabled, only authenticated e-mail addresses can receive EmailUser mails and/or
+               e-mail notification mails.</p>
+       </dt>
+
+       </dl>
+
 <h2>Database config</h2>
 
 <dl class="setup">
@@ -853,6 +941,30 @@ function writeLocalSettings( $conf ) {
                        $turck = '#';
        }
 
+       if ( $conf->Email == 'email_enabled' ) {
+               $enableemail = 'true';
+               $enableuseremail = ( $conf->Emailuser == 'emailuser_enabled' ) ? 'true' : 'false' ;
+               $eauthent = ( $conf->Eauthent == 'eauthent_enabled' ) ? 'true' : 'false' ;
+               switch ( $conf->Enotif ) {
+                       case 'enotif_usertalk':
+                               $enotifusertalk = 'true';
+                               $enotifwatchlist = 'false';
+                               break;
+                       case 'enotif_allpages':
+                               $enotifusertalk = 'true';
+                               $enotifwatchlist = 'true';
+                               break;
+                       default:
+                               $enotifusertalk = 'false';
+                               $enotifwatchlist = 'false';
+               }
+       } else {
+               $enableuseremail = 'false';
+               $enableemail = 'false';
+               $eauthent = 'false';
+               $enotifusertalk = 'false';
+               $enotifwatchlist = 'false';
+       }
 
        $file = @fopen( "/dev/urandom", "r" );
        if ( $file ) {
@@ -912,9 +1024,21 @@ if ( \$wgCommandLineMode ) {
 \$wgUploadPath       = \"\$wgScriptPath/images\";
 \$wgUploadDirectory  = \"\$IP/images\";
 
+\$wgEnableEmail = $enableemail;
+\$wgEnableUserEmail = $enableuseremail;
+
 \$wgEmergencyContact = \"{$slconf['EmergencyContact']}\";
 \$wgPasswordSender     = \"{$slconf['PasswordSender']}\";
 
+## For a detailed description of the following switches see
+## http://meta.wikimedia.org/Enotif and http://meta.wikimedia.org/Eauthent
+## There are many more options for fine tuning available see
+## /includes/DefaultSettings.php
+## UPO means: this is also a user preference option
+\$wgEmailNotificationForUserTalkPages = $enotifusertalk; # UPO
+\$wgEmailNotificationForWatchlistPages = $enotifwatchlist; # UPO
+\$wgEmailAuthentication = $eauthent;
+
 \$wgDBserver         = \"{$slconf['DBserver']}\";
 \$wgDBname           = \"{$slconf['DBname']}\";
 \$wgDBuser           = \"{$slconf['DBuser']}\";
index d6f71c5..8702382 100644 (file)
@@ -681,6 +681,7 @@ class Article {
        function view() {
                global $wgUser, $wgOut, $wgRequest, $wgOnlySysopsCanPatrol, $wgLang;
                global $wgLinkCache, $IP, $wgEnableParserCache, $wgStylePath, $wgUseRCPatrol;
+               global $wgEnotif;
                $sk = $wgUser->getSkin();
 
                $fname = 'Article::view';
@@ -806,6 +807,10 @@ class Article {
 
                $this->viewUpdates();
                wfProfileOut( $fname );
+
+               include_once( "UserMailer.php" );
+               $wgEnotif = new EmailNotification ();
+               $wgEnotif->Clear( $wgUser->getID(), $this->mTitle->getDBkey(), $this->mTitle->getNamespace() );
        }
 
        /**
@@ -878,9 +883,10 @@ class Article {
                        array(  'cur_namespace' => $talkns, 'cur_title' => $ttl ), $fname );
 
                # standard deferred updates
-               $this->editUpdates( $text );
+               $this->editUpdates( $text, $summary, $isminor, $now );
 
-               $this->showArticle( $text, wfMsg( 'newarticle' ) );
+               $oldid = 0; # new article
+               $this->showArticle( $text, wfMsg( 'newarticle' ), false, $isminor, $now, $summary, $oldid );
        }
 
 
@@ -1094,7 +1100,7 @@ class Article {
                        }
                }
                # standard deferred updates
-               $this->editUpdates( $text );
+               $this->editUpdates( $text, $summary, $minor, $now );
 
 
                $urls = array();
@@ -1117,7 +1123,7 @@ class Article {
                        $u->doUpdate();
                }
 
-               $this->showArticle( $text, wfMsg( 'updated' ), $sectionanchor );
+               $this->showArticle( $text, wfMsg( 'updated' ), $sectionanchor, $me2, $now, $summary, $oldid );
                }
                return $good;
        }
@@ -1126,8 +1132,8 @@ class Article {
         * After we've either updated or inserted the article, update
         * the link tables and redirect to the new page.
         */
-       function showArticle( $text, $subtitle , $sectionanchor = '' ) {
-               global $wgOut, $wgUser, $wgLinkCache;
+       function showArticle( $text, $subtitle , $sectionanchor = '', $me2, $now, $summary, $oldid ) {
+               global $wgOut, $wgUser, $wgLinkCache, $wgEnotif;
 
                $wgLinkCache = new LinkCache();
                # Select for update
@@ -1149,6 +1155,13 @@ class Article {
                else
                        $r = '';
                $wgOut->redirect( $this->mTitle->getFullURL( $r ).$sectionanchor );
+
+               # this call would better fit into RecentChange::notifyEdit and RecentChange::notifyNew .
+               # this will be improved later (to-do)
+
+               include_once( "UserMailer.php" );
+               $wgEnotif = new EmailNotification ();
+               $wgEnotif->NotifyOnPageChange( $wgUser->getID(), $this->mTitle->getDBkey(), $this->mTitle->getNamespace(),$now, $summary, $me2, $oldid );
        }
 
        /**
@@ -1857,12 +1870,10 @@ class Article {
                # talk page
 
                global $wgUser;
-               
                if ($this->mTitle->getNamespace() == NS_USER_TALK &&
-                       $this->mTitle->getText() == $wgUser->getName())
-               {
-                       $wgUser->setNewtalk(0);
-                       $wgUser->saveNewtalk();
+                       $this->mTitle->getText() == $wgUser->getName()) {
+                       require_once( 'UserTalkUpdate.php' );
+                       $u = new UserTalkUpdate( 0, $this->mTitle->getNamespace(), $this->mTitle->getDBkey(), false, false, false );
                }
        }
 
@@ -1872,7 +1883,7 @@ class Article {
         * @private
         * @param string $text
         */
-       function editUpdates( $text ) {
+       function editUpdates( $text, $summary, $minoredit, $timestamp_of_pagechange) {
                global $wgDeferredUpdateList, $wgDBname, $wgMemc;
                global $wgMessageCache, $wgUser;
 
@@ -1897,15 +1908,13 @@ class Article {
                        $u = new SearchUpdate( $id, $title, $text );
                        array_push( $wgDeferredUpdateList, $u );
 
-                       # If this is another user's talk page, save a
-                       # newtalk notification for them
+                       # If this is another user's talk page,
+                       # create a watchlist entry for this page
                        
                        if ($this->mTitle->getNamespace() == NS_USER_TALK &&
-                               $shortTitle != $wgUser->getName())
-                       {
-                               $other = User::newFromName($shortTitle);
-                               $other->setNewtalk(1);
-                               $other->saveNewtalk();
+                               $shortTitle != $wgUser->getName()) {
+                               require_once( 'UserTalkUpdate.php' );
+                               $u = new UserTalkUpdate( 1, $this->mTitle->getNamespace(), $shortTitle, $summary, $minoredit, $timestamp_of_pagechange);
                        }
 
                        if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
index 96575d7..96a91d4 100644 (file)
@@ -79,6 +79,10 @@ class ChangesList {
                if ( $rcObj->watched ) $link = '<strong>'.$link.'</strong>' ;
                $r .= $link ;
 
+               if ($rcObj->notificationtimestamp) {
+                       $r .= wfMsg( 'updatedmarker' );
+               }
+
                # Diff
                $r .= ' (' ;
                $r .= $rcObj->difflink ;
@@ -97,6 +101,10 @@ class ChangesList {
                        $r .= $wgContLang->emphasize( ' ('.$rc_comment.')' );
                }
 
+               if ($rcObj->numberofWatchingusers > 0) {
+                       $r .= wfMsg('number_of_watching_users_RCview',  $wgContLang->formatNum($rcObj->numberofWatchingusers));
+               }
+
                $r .= "<br />\n" ;
                return $r ;
        }
@@ -158,9 +166,9 @@ class ChangesList {
                else $r .= '&nbsp;' ;
                $r .= '&nbsp;' ; # Minor
                if ( $unpatrolled ) {
-                       $r .= "!";
+                       $r .= '!';
                } else {
-                       $r .= "&nbsp;";
+                       $r .= '&nbsp;';
                }
 
                # Timestamp
@@ -172,6 +180,10 @@ class ChangesList {
                if ( $block[0]->watched ) $link = '<strong>'.$link.'</strong>' ;
                $r .= $link ;
 
+               if ($block[0]->notificationtimestamp) {
+                       $r .= wfMsg( 'updatedmarker' );
+               }
+
                $curIdEq = 'curid=' . $block[0]->mAttribs['rc_cur_id'];
                if ( $block[0]->mAttribs['rc_type'] != RC_LOG ) {
                        # Changes
@@ -187,6 +199,10 @@ class ChangesList {
                }
 
                $r .= $users ;
+
+               if ($block[0]->numberofWatchingusers > 0) {
+                       $r .= wfMsg('number_of_watching_users_RCview',  $wgContLang->formatNum($block[0]->numberofWatchingusers));
+               }
                $r .= "<br />\n" ;
 
                # Sub-entries
@@ -210,9 +226,9 @@ class ChangesList {
                        }
 
                        if ( $rcObj->unpatrolled ) {
-                               $r .= "!";
+                               $r .= '!';
                        } else {
-                               $r .= "&nbsp;";
+                               $r .= '&nbsp;';
                        }
 
                        $r .= '&nbsp;</tt>' ;
@@ -271,7 +287,7 @@ class ChangesList {
         * Either returns the line, or caches it for later use
         */
        function recentChangesLine( &$rc, $watched = false ) {
-               global $wgUser ;
+               global $wgUser;
                $usenew = $wgUser->getOption( 'usenewrc' );
                if ( $usenew )
                        $line = $this->recentChangesLineNew ( $rc, $watched ) ;
@@ -280,6 +296,7 @@ class ChangesList {
                return $line ;
        }
 
+
        function recentChangesLineOld( &$rc, $watched = false ) {
                $fname = 'Skin::recentChangesLineOld';
                wfProfileIn( $fname );
@@ -350,9 +367,9 @@ class ChangesList {
                        $s .= ') . . ';
 
                        # M, N and ! (minor, new and unpatrolled)
-                       if ( $rc_minor ) { $s .= ' <span class="minor">'.$message["minoreditletter"].'</span>'; }
-                       if ( $rc_type == RC_NEW ) { $s .= '<span class="newpage">'.$message["newpageletter"].'</span>'; }
-                       if ( $unpatrolled ) { $s .= ' <span class="unpatrolled">!</span>'; }
+                       if ( $rc_minor ) { $s .= ' <span class="minoreditletter">'.$message["minoreditletter"].'</span>'; }
+                       if ( $rc_type == RC_NEW ) { $s .= '<span class="newpageletter">'.$message["newpageletter"].'</span>'; }
+                       if ( !$rc_patrolled ) { $s .= ' <span class="unpatrolled">!</span>'; }
 
                        # Article link
                        # If it's a new article, there is no diff link, but if it hasn't been
@@ -365,6 +382,11 @@ class ChangesList {
                        if ( $watched ) {
                                $articleLink = '<strong>'.$articleLink.'</strong>';
                        }
+
+                       if ($rc->notificationtimestamp) {
+                               $articleLink .= wfMsg( 'updatedmarker' );
+                       }
+
                        $s .= ' '.$articleLink;
                        wfProfileOut("$fname-page");
                }
@@ -412,6 +434,11 @@ class ChangesList {
                        $rc_comment = $this->skin->formatComment($rc_comment,$rc->getTitle());
                        $s .= $wgContLang->emphasize(' (' . $rc_comment . ')');
                }
+
+               if ($rc->numberofWatchingusers > 0) {
+                       $s .= ' ' . wfMsg('number_of_watching_users_RCview',  $wgContLang->formatNum($rc->numberofWatchingusers));
+               }
+
                $s .= "</li>\n";
 
                wfProfileOut( "$fname-rest" );
@@ -477,6 +504,8 @@ class ChangesList {
                $rc->watched = $watched ;
                $rc->link = $clink ;
                $rc->timestamp = $time;
+               $rc->notificationtimestamp = $baseRC->notificationtimestamp;
+               $rc->numberofWatchingusers = $baseRC->numberofWatchingusers;
 
                # Make "cur" and "diff" links
                $titleObj = $rc->getTitle();
index 79e6a6f..2e0b485 100644 (file)
@@ -164,6 +164,14 @@ $wgEmergencyContact = 'wikiadmin@' . $wgServerName;
  */
 $wgPasswordSender      = 'Wikipedia Mail <apache@' . $wgServerName . '>';
 
+/**
+ * dummy address which should be accepted during mail send action
+ * It might be necessay to adapt the address or to set it equal
+ * to the $wgEmergencyContact address
+ */
+#$wgNoReplyAddress     = $wgEmergencyContact;
+$wgNoReplyAddress      = 'reply@not.possible';
+
 /**
  * Set to true to enable the e-mail basic features:
  * Password reminders, etc. If sending e-mail on your
@@ -495,9 +503,63 @@ $wgFileCacheDirectory = "{$wgUploadDirectory}/cache";
 # Requires zlib support enabled in PHP.
 $wgUseGzip = false;
 
+/*     T. Gries Aug.-Nov.2004
 
-$wgCookieExpiration = 2592000;
+       THESE ARE MY SUGGESTED FIRST TEST global admin options FOR ENOTIF.
+       Attention: the defaults might differ from standard media wiki distributions.
+
+       However, I suggest to start with these which allow to evaluate almost all new features quickly.
+       user preferences default options SEE AS USUAL /languages/Language.php
+*/
+
+########################### CAUTION ################# ATTENTION ###############################################
+#
+# T. Gries Aug.-Dec. 2004
+#
+# SECTION FOR DEVELOPERS and SERVER ADMINS
+#
+# HERE COME TWO OPTIONS, WHICH ALLOW ACOUSTIC SIGNALS on the server beeper WHEN AN EMAIL IS ACTUALLY SENT OUT.
+# YOU MIGHT WISH TO ENABLE THESE, BUT BE CAREFUL, AS A system() CALL IS PERFORMED using the shown parameters.
+# I FIND IT VERY USEFUL, but this is a very personal comment. T. Gries
+#
+# The system speaker beeps when the wiki actually sends out a notification mail (safe default = disabled).
+#
+# The following string is passed as parameter to a system() call in UserMailer.php
+# The system() calls call - in the shown example - the beep 1.2.2 program (LINUX) with frequency f [Hz] and length l [msec]
+#
+# Beep 1.2.2 can be found on http://freshmeat.net/projects/beep/ and I like it.
+#
+########################### SAFE #################### SAFE #####################################################
+$wgEmailNotificationSystembeep = ''; # empty string disables this feature; this appears to be safe
+######ENTER#AT#YOUR#OWN#RISK###CAUTION###### ATTENTION ###################################################
+#$wgEmailNotificationSystembeep        = '/usr/bin/beep -f 4000 -l 20 &';      # a system() call with exactly this string as parameter is executed when such a mail is sent
+########################### CAUTION ################# ATTENTION ###############################################
+
+# For email notification on page changes T.Gries/M.Arndt 01.11.2004
+$wgPasswordSender                              = $wgEmergencyContact;
+$wgEmailNotificationMailsSentFromPageEditor    = false; # false: Enotif mails appear to come from $wgEmergencyContact
+#                                                       # true: from PageEditor if s/he opted-in
+
+# If set to true, users get a corresponding option in their preferences and can choose to enable or disable at their discretion
+# If set to false, the corresponding input form on the user preference page is suppressed
+# It call this to be a "user-preferences-option (UPO)"
+$wgEmailAuthentication                         = true; # UPO (if this is set to false, texts referring to authentication are suppressed)
+$wgEmailNotificationForWatchlistPages          = true; # UPO
+$wgEmailNotificationForUserTalkPages           = true; # UPO
+$wgEmailNotificationRevealPageEditorAddress    = true; # UPO; reply-to address may be filled with page editor's address (if user allowed this in the preferences)
+$wgEmailNotificationForMinorEdits              = true; # UPO; false: "minor edits" on pages do not trigger notification mails.
+#                                                      # Attention: _every_ change on a user_talk page trigger a notification mail (if the user is not yet notified)
+
+# Show recent changes in UseMod style, i.e. only the recent change of a page is listed
+$wgRCUseModStyle                               = true; # UPO
+# Show watching users in recent changes, watchlist and page history views
+$wgRCShowWatchingUsers                                 = true; # UPO
+# Show watching users in Page views
+$wgPageShowWatchingUsers                       = true;
+# Show "Updated (since my last visit)" marker in RC view, watchlist and history view for watched pages with new changes
+$wgShowUpdatedMarker                           = true; # UPO
 
+$wgCookieExpiration = 2592000;
 
 # Squid-related settings
 #
@@ -743,10 +805,10 @@ $wgDefaultSkin = 'monobook';
 $wgDefaultUserOptions = array();
 
 # Whether or not to allow real name fields. Defaults to true.
+# If set to false, the corresponding input forms on the log-in page and on the user preference page is suppressed.
 $wgAllowRealName = true;
 
 # Use XML parser?
-
 $wgUseXMLparser = false ;
 
 # Extensions
index f229792..754a2d1 100644 (file)
@@ -22,7 +22,7 @@ class PageHistory {
        # This shares a lot of issues (and code) with Recent Changes
 
        function history() {
-               global $wgUser, $wgOut, $wgLang;
+               global $wgUser, $wgOut, $wgLang, $wgShowUpdatedMarker;
 
                # If page hasn't changed, client can cache this
 
@@ -59,8 +59,17 @@ class PageHistory {
 
                $namespace = $this->mTitle->getNamespace();
                $title = $this->mTitle->getText();
-
+               $uid = $wgUser->getID();
                $db =& wfGetDB( DB_SLAVE );
+               if ($wgShowUpdatedMarker && $wgUser->getOption( 'showupdated' )) {
+                       $dbr =& wfGetDB( DB_MASTER );
+                       $row = $dbr->selectRow( 'watchlist',
+                               array( 'wl_notificationtimestamp' ),
+                               array( 'wl_namespace' => $namespace, 'wl_title' => $this->mTitle->getDBkey(), 'wl_user' => $wgUser->getID() ),
+                               $fname );
+                       $notificationtimestamp = $row->wl_notificationtimestamp;
+               } else $notificationtimestamp = false ;
+
                $use_index = $db->useIndexClause( 'name_title_timestamp' );
                $oldtable = $db->tableName( 'old' );
 
@@ -103,7 +112,8 @@ class PageHistory {
                                $this->mArticle->getUserText(), $namespace,
                                $title, 0, $this->mArticle->getComment(),
                                ( $this->mArticle->getMinorEdit() > 0 ),
-                               $counter++
+                               $counter++,
+                               $notificationtimestamp
                        );
                }
                while ( $line = $db->fetchObject( $res ) ) {
@@ -112,7 +122,8 @@ class PageHistory {
                                $line->old_user_text, $namespace,
                                $title, $line->old_id,
                                $line->old_comment, ( $line->old_minor_edit > 0 ),
-                               $counter++
+                               $counter++,
+                               $notificationtimestamp
                        );
                }
                $s .= $this->endHistoryList( !$atend );
@@ -143,7 +154,7 @@ class PageHistory {
                return $s;
        }
 
-       function historyLine( $ts, $u, $ut, $ns, $ttl, $oid, $c, $isminor, $counter = '' ) {
+       function historyLine( $ts, $u, $ut, $ns, $ttl, $oid, $c, $isminor, $counter = '', $notificationtimestamp = false ) {
                global $wgLang, $wgContLang;
 
                static $message;
@@ -206,6 +217,9 @@ class PageHistory {
                        $c = $this->mSkin->formatcomment( $c, $this->mTitle );
                        $s .= " <em>($c)</em>";
                }
+               if ($notificationtimestamp && ($ts >= $notificationtimestamp)) {
+                       $s .= wfMsg( 'updatedmarker' );
+               }
                $s .= '</li>';
 
                $this->lastline = $s;
index 248cb2f..f64191d 100644 (file)
@@ -40,6 +40,10 @@ define( 'RC_MOVE_OVER_REDIRECT', 4);
  *     lastTimestamp   timestamp of previous entry, used in WHERE clause during update
  *     lang            the interwiki prefix, automatically set in save()
  * 
+ * temporary:          not stored in the database
+ *      notificationtimestamp
+ *      numberofWatchingusers
+ *
  * @todo document functions and variables
  * @package MediaWiki
  */
@@ -61,6 +65,8 @@ class RecentChange
        {
                $rc = new RecentChange;
                $rc->loadFromCurRow( $row );
+               $rc->notificationtimestamp = false;
+               $rc->numberofWatchingusers = false;
                return $rc;
        }
 
index d9d3ca2..0f55446 100644 (file)
@@ -80,8 +80,6 @@ require_once( 'HistoryBlob.php' );
        
 $wgRequest = new WebRequest();
 
-
-
 wfProfileOut( $fname.'-includes' );
 wfProfileIn( $fname.'-misc1' );
 global $wgUser, $wgLang, $wgContLang, $wgOut, $wgTitle;
index 4978cdb..ebb3018 100644 (file)
@@ -797,8 +797,8 @@ class Skin {
        }
 
        function pageStats() {
-               global $wgOut, $wgLang, $wgArticle, $wgRequest;
-               global $wgDisableCounters, $wgMaxCredits, $wgShowCreditsIfMax;
+               global $wgOut, $wgLang, $wgArticle, $wgRequest, $wgUser;
+               global $wgDisableCounters, $wgMaxCredits, $wgShowCreditsIfMax, $wgTitle, $wgPageShowWatchingUsers;
 
                extract( $wgRequest->getValues( 'oldid', 'diff' ) );
                if ( ! $wgOut->isArticle() ) { return ''; }
@@ -820,6 +820,17 @@ class Skin {
                    $s .= $this->lastModified();
                }
 
+               if ($wgPageShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' )) {
+                       $dbr =& wfGetDB( DB_SLAVE );
+                       extract( $dbr->tableNames( 'watchlist' ) );
+                       $sql = "SELECT COUNT(*) AS n FROM $watchlist
+                               WHERE wl_title='" . $dbr->strencode($wgTitle->getDBKey()) .
+                               "' AND  wl_namespace=" . $wgTitle->getNamespace() ;
+                       $res = $dbr->query( $sql, 'Skin::pageStats');
+                       $x = $dbr->fetchObject( $res );
+                       $s .= ' ' . wfMsg('number_of_watching_users_pageview', $x->n );
+               }
+
                return $s . ' ' .  $this->getCopyright();
        }
 
@@ -1101,7 +1112,10 @@ class Skin {
                return $wgEnableEmail &&
                       $wgEnableUserEmail &&
                       0 != $wgUser->getID() && # show only to signed in users
-                      0 != $id; # can only email non-anons
+                      0 != $id; # we can only email to non-anons ..
+#                     '' != $id->getEmail() && # who must have an email address stored ..
+#                     0 != $id->getEmailauthenticationtimestamp() && # .. which is authenticated
+#                     1 != $wgUser->getOption('disablemail'); # and not disabled
        }
        
        function emailUserLink() {
@@ -1928,7 +1942,6 @@ class Skin {
                return '<a href="'.$url.'"'.$style.'>'.$text.'</a>';
        }
 
-
        /**
         * This function is called by all recent changes variants, by the page history,
         * and by the user contributions list. It is responsible for formatting edit
index 6298deb..4577584 100644 (file)
@@ -148,6 +148,7 @@ class SkinTemplate extends Skin {
                global $wgMimeType, $wgOutputEncoding, $wgUseDatabaseMessages, $wgRequest;
                global $wgDisableCounters, $wgLogo, $action, $wgFeedClasses, $wgSiteNotice;
                global $wgMaxCredits, $wgShowCreditsIfMax;
+               global $wgPageShowWatchingUsers;
 
                $fname = 'SkinTemplate::outputPage';
                wfProfileIn( $fname );
@@ -282,6 +283,25 @@ class SkinTemplate extends Skin {
                                        $tpl->set('viewcount', false);
                                }
                        }
+
+                       if ($wgPageShowWatchingUsers) {
+                               $dbr =& wfGetDB( DB_SLAVE );
+                               extract( $dbr->tableNames( 'watchlist' ) );
+                               $sql = "SELECT COUNT(*) AS n FROM $watchlist
+                                       WHERE wl_title='" . $dbr->strencode($wgTitle->getDBKey()) .
+                                       "' AND  wl_namespace=" . $wgTitle->getNamespace() ;
+                               $res = $dbr->query( $sql, 'SkinPHPTal::outputPage');
+                               $x = $dbr->fetchObject( $res );
+                               $numberofwatchingusers = $x->n;
+                               if ($numberofwatchingusers > 0) {
+                                       $tpl->set('numberofwatchingusers', wfMsg('number_of_watching_users_pageview', $numberofwatchingusers));
+                               } else {
+                                       $tpl->set('numberofwatchingusers', false);
+                               };
+                       } else {
+                               $tpl->set('numberofwatchingusers', false);
+                       }
+
                        $tpl->set('lastmod', $this->lastModified());
                        $tpl->set('copyright',$this->getCopyright());
 
@@ -316,6 +336,7 @@ class SkinTemplate extends Skin {
                $tpl->setRef( 'debug', $out->mDebugtext );
                $tpl->set( 'reporttime', $out->reportTime() );
                $tpl->set( 'sitenotice', $wgSiteNotice );
+               $tpl->set( 'tagline', wfMsg('tagline') );
 
                $printfooter = "<div class=\"printfooter\">\n" . $this->printSource() . "</div>\n";
                $out->mBodytext .= $printfooter ;
index 6006b25..860d25b 100644 (file)
@@ -19,7 +19,7 @@ function wfSpecialEmailuser( $par ) {
        }
        
        if ( 0 == $wgUser->getID() ||
-               ( false === strpos( $wgUser->getEmail(), "@" ) ) ) {
+               ( !$wgUser->isValidEmailAddr( $wgUser->getEmail() ) ) ) {
                $wgOut->errorpage( "mailnologin", "mailnologintext" );
                return;
        }
@@ -48,8 +48,9 @@ function wfSpecialEmailuser( $par ) {
 
        $address = $nu->getEmail();
 
-       if ( ( false === strpos( $address, "@" ) ) ||
-         ( 1 == $nu->getOption( "disablemail" ) ) ) {
+       if ( ( !$nu->isValidEmailAddr( $address ) ) ||
+            ( 1 == $nu->getOption( "disablemail" ) ) ||
+            ( 0 == $nu->getEmailauthenticationtimestamp() ) ) {
                $wgOut->errorpage( "noemailtitle", "noemailtext" );
                return;
        }
index 74cc649..bffacf4 100644 (file)
@@ -166,6 +166,8 @@ class PreferencesForm {
         */
        function savePreferences() {
                global $wgUser, $wgLang, $wgOut;
+               global $wgEnableUserEmail, $wgEnableEmail;
+               global $wgEmailAuthentication;
 
                if ( '' != $this->mNewpass ) {
                        if ( $this->mNewpass != $this->mRetypePass ) {
@@ -179,10 +181,6 @@ class PreferencesForm {
                        }
                        $wgUser->setPassword( $this->mNewpass );
                }
-               global $wgEnableEmail;
-               if( $wgEnableEmail ) {
-                       $wgUser->setEmail( $this->mUserEmail );
-               }
                $wgUser->setRealName( $this->mRealName );
                $wgUser->setOption( 'language', $this->mUserLanguage );
         $wgUser->setOption( 'variant', $this->mUserVariant );
@@ -206,7 +204,6 @@ class PreferencesForm {
                        $wgUser->setOption( "searchNs{$i}", $value );
                }
                
-               global $wgEnableUserEmail;
                if( $wgEnableEmail && $wgEnableUserEmail ) {
                        $wgUser->setOption( 'disablemail', $this->mEmailFlag );
                }
@@ -216,8 +213,38 @@ class PreferencesForm {
                        $wgUser->setOption( $tname, $tvalue );
                }
                $wgUser->setCookies();
+               $wgUser->saveSettings();
                
+               if( $wgEnableEmail ) {
+                       $newadr = strtolower( $this->mUserEmail );
+                       $oldadr = strtolower($wgUser->getEmail());
+                       if (($newadr <> '') && ($newadr <> $oldadr)) { # the user has supplied a new email address on the login page
+                               # prepare for authentication and mail a temporary password to newadr
+                               require_once( 'SpecialUserlogin.php' );
+                               if ( !$wgUser->isValidEmailAddr( $newadr ) ) {
+                                       $this->mainPrefsForm( wfMsg( 'invalidemailaddress' ) );
+                                       return;
+                               }
+                               $wgUser->mEmail = $newadr; # new behaviour: set this new emailaddr from login-page into user database record
+                               $wgUser->mEmailAuthenticationtimestamp = 0; # but flag as "dirty" = unauthenticated
                $wgUser->saveSettings();
+                               if ($wgEmailAuthentication) {
+                                       # mail a temporary password to the dirty address
+                                       # on "save options", this user will be logged-out automatically
+                                       $error = LoginForm::mailPasswordInternal( $wgUser, true, $dummy );
+                                       if ($error === '') {
+                                               return LoginForm::mainLoginForm( wfMsg( 'passwordsentforemailauthentication', $wgUser->getName() ) );
+                                       } else {
+                                               return LoginForm::mainLoginForm( wfMsg( 'mailerror', $error ) );
+                                       }
+                                       # if user returns, that new email address gets authenticated in checkpassword()
+                               }
+                       } else {
+                               $wgUser->setEmail( strtolower($this->mUserEmail) );
+                               $wgUser->setCookies();
+                               $wgUser->saveSettings();
+                       }
+               }
 
                $wgOut->setParserOptions( ParserOptions::newFromUser( $wgUser ) );
                $po = ParserOptions::newFromUser( $wgUser );
@@ -232,6 +259,7 @@ class PreferencesForm {
 
                $this->mOldpass = $this->mNewpass = $this->mRetypePass = '';
                $this->mUserEmail = $wgUser->getEmail();
+               $this->mUserEmailAuthenticationtimestamp = $wgUser->getEmailAuthenticationtimestamp();
                $this->mRealName = ($wgAllowRealName) ? $wgUser->getRealName() : '';
                $this->mUserLanguage = $wgUser->getOption( 'language' );
                if( empty( $this->mUserLanguage ) ) {
@@ -306,7 +334,7 @@ class PreferencesForm {
        }
 
 
-       function getToggle( $tname ) {
+       function getToggle( $tname, $trailer = false) {
                global $wgUser, $wgLang;
                
                $this->mUsedToggles[$tname] = true;
@@ -317,8 +345,9 @@ class PreferencesForm {
                } else {
                        $checked = '';
                }               
+               $trailer =($trailer) ? $trailer : '';
                return "<div><input type='checkbox' value=\"1\" "
-                 . "id=\"$tname\" name=\"wpOp$tname\"$checked /><label for=\"$tname\">$ttext</label></div>\n";
+                 . "id=\"$tname\" name=\"wpOp$tname\"$checked /><label for=\"$tname\">$ttext</label>$trailer</div>\n";
        }
 
        /**
@@ -328,7 +357,11 @@ class PreferencesForm {
                global $wgUser, $wgOut, $wgLang, $wgContLang, $wgUseDynamicDates, $wgValidSkinNames;
                global $wgAllowRealName, $wgImageLimits;
                global $wgLanguageNames, $wgDisableLangConversion;
+               global $wgEmailNotificationForWatchlistPages, $wgEmailNotificationForUserTalkPages,$wgEmailNotificationForMinorEdits;
+               global $wgRCUseModStyle, $wgRCShowWatchingUsers, $wgEmailNotificationRevealPageEditorAddress;
+               global $wgEnableEmail, $wgEnableUserEmail, $wgEmailAuthentication;
                global $wgContLanguageCode;
+
                $wgOut->setPageTitle( wfMsg( 'preferences' ) );
                $wgOut->setArticleRelated( false );
                $wgOut->setRobotpolicy( 'noindex,nofollow' );
@@ -396,21 +429,50 @@ class PreferencesForm {
                if ( $this->mEmailFlag ) { $emfc = 'checked="checked"'; }
                else { $emfc = ''; }
 
+               if ($wgEmailAuthentication && ($this->mUserEmail != '') ) {
+                       if ($wgUser->getEmailAuthenticationtimestamp() != 0) {
+                               $emailauthenticated = wfMsg('emailauthenticated',$wgLang->timeanddate($wgUser->getEmailAuthenticationtimestamp(), true ) ).'<br>';
+                               $disabled = '';
+                       } else {
+                               $emailauthenticated = wfMsg('emailnotauthenticated').'<br>';
+                               $disabled = ' '.wfMsg('disableduntilauthent');
+                       }
+               } else {
+                       $emailauthenticated = '';
+               }
+
+               if ($this->mUserEmail == '') {
+                       $disabled = ' '.wfMsg('disablednoemail');
+               }
+
                $ps = $this->namespacesCheckboxes();
 
+               $enotifwatchlistpages = ($wgEmailNotificationForWatchlistPages) ? $this->getToggle( 'enotifwatchlistpages', $disabled) : '';
+               $enotifusertalkpages = ($wgEmailNotificationForUserTalkPages) ? $this->getToggle( 'enotifusertalkpages', $disabled) : '';
+               $enotifminoredits = ($wgEmailNotificationForMinorEdits) ? $this->getToggle( 'enotifminoredits', $disabled) : '';
+               $enotifrevealaddr = ($wgEmailNotificationRevealPageEditorAddress) ? $this->getToggle( 'enotifrevealaddr', $disabled) : '';
+               $prefs_help_email_enotif = ( $wgEmailNotificationForWatchlistPages || $wgEmailNotificationForUserTalkPages) ? ' ' . wfMsg('prefs-help-email-enotif') : '';
+               $prefs_help_realname = '';
+
                $wgOut->addHTML( "<fieldset>
                <legend>".wfMsg('prefs-personal')."</legend>");
+
                        if ($wgAllowRealName) {
                        $wgOut->addHTML("<div><label>$yrn: <input type='text' name=\"wpRealName\" value=\"{$this->mRealName}\" size='20' /></label></div>");
+                       $prefs_help_realname = wfMsg('prefs-help-realname').'<br>';
                }
                
-               global $wgEnableEmail, $wgEnableUserEmail;
                if( $wgEnableEmail ) {
                        $wgOut->addHTML("
                        <div><label>$yem: <input type='text' name=\"wpUserEmail\" value=\"{$this->mUserEmail}\" size='20' /></label></div>" );
                        if( $wgEnableUserEmail ) {
-                               $wgOut->addHTML("
-                               <div><label><input type='checkbox' $emfc value=\"1\" name=\"wpEmailFlag\" />$emf</label></div>" );
+                               $wgOut->addHTML(
+                               $emailauthenticated.
+                               $enotifrevealaddr.
+                               $enotifwatchlistpages.
+                               $enotifusertalkpages.
+                               $enotifminoredits.
+                               "<div><label><input type='checkbox' $emfc value=\"1\" name=\"wpEmailFlag\" />$emf.$disabled</label></div>" );
                        }
                }
                
@@ -465,7 +527,7 @@ class PreferencesForm {
        <div><label>$rpw: <input type='password' name=\"wpRetypePass\" value=\"{$this->mRetypePass}\" size='20' /></label></div>
        " . $this->getToggle( "rememberpassword" ) . "
        </fieldset>
-       <div class='prefsectiontip'>".wfMsg('prefs-help-userdata')."</div>\n</fieldset>\n" );
+       <div class='prefsectiontip'>".$prefs_help_realname.wfMsg('prefs-help-email').$prefs_help_email_enotif."</div>\n</fieldset>\n" );
 
        
                # Quickbar setting
@@ -559,12 +621,15 @@ class PreferencesForm {
                <div class='prefsectiontip'>* {$tzt}</div>
        </fieldset>\n\n" );
 
+               $shownumberswatching = ($wgRCShowWatchingUsers) ? $this->getToggle('shownumberswatching') : '';
+               $rcusemodstyle = ($wgRCUseModStyle) ? $this->getToggle('rcusemodstyle') : '';
+
                $wgOut->addHTML( "
        <fieldset><legend>".wfMsg('prefs-rc')."</legend>
-               <div><label>$rcc: <input type='text' name=\"wpRecent\" value=\"$this->mRecent\" size='6' /></label></div>
-               " . $this->getToggle( "hideminor" ) .
-               $this->getToggle( "usenewrc" ) . "
-               <div><label>$stt: <input type='text' name=\"wpStubs\" value=\"$this->mStubs\" size='6' /></label></div>
+               <div><label>$rcc: <input type='text' name=\"wpRecent\" value=\"$this->mRecent\" size='6' /></label></div>" .
+               $this->getToggle( "hideminor" ) . $shownumberswatching .
+               $this->getToggle( "usenewrc" ) . $rcusemodstyle . $this->getToggle('showupdated', wfMsg('updatedmarker')) .
+               "<div><label>$stt: <input type='text' name=\"wpStubs\" value=\"$this->mStubs\" size='6' /></label></div>
                 <div><label>".wfMsg('imagemaxsize')."<select name=\"wpImageSize\">");
                
                $imageLimitOptions='';
index 2a833a3..2e24661 100644 (file)
@@ -18,6 +18,7 @@ function wfSpecialRecentchanges( $par ) {
        global $wgUser, $wgOut, $wgLang, $wgContLang, $wgTitle, $wgMemc, $wgDBname;
        global $wgRequest, $wgSitename, $wgLanguageCode, $wgContLanguageCode;
        global $wgFeedClasses, $wgUseRCPatrol;
+       global $wgRCUseModStyle, $wgRCShowWatchingUsers, $wgShowUpdatedMarker;
        $fname = 'wfSpecialRecentchanges';
 
        # Get query parameters
@@ -115,10 +116,13 @@ function wfSpecialRecentchanges( $par ) {
        $patrLink = $sk->makeKnownLink( $wgContLang->specialPage( 'Recentchanges' ),
          $showhide[1-$hidepatrolled], wfArrayToCGI( array( 'hidepatrolled' => 1-$hidepatrolled ), $urlparams ) );
 
+       $RCUseModStyle = ($wgRCUseModStyle && $wgUser->getOption('rcusemodstyle')) ? 'AND rc_this_oldid=0 '  :  '' ;
+
        $uid = $wgUser->getID();
-       $sql2 = "SELECT $recentchanges.*" . ($uid ? ",wl_user" : "") . " FROM $recentchanges " .
-         ($uid ? "LEFT OUTER JOIN $watchlist ON wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace & 65534 " : "") .
-         "WHERE rc_timestamp > '{$cutoff}' {$hidem} " .
+       # Patch for showing "updated since last visit" marker
+       $sql2 = "SELECT $recentchanges.*" . ($uid ? ",wl_user,wl_notificationtimestamp" : "") . " FROM $recentchanges " .
+         ($uid ? "LEFT OUTER JOIN $watchlist ON wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace " : "") .
+         "WHERE rc_timestamp > '{$cutoff}' {$hidem} " . $RCUseModStyle .
          "ORDER BY rc_timestamp DESC LIMIT {$limit}";
 
        $res = $dbr->query( $sql2, DB_SLAVE, $fname );
@@ -161,6 +165,24 @@ function wfSpecialRecentchanges( $par ) {
                             ! ( $hidepatrolled && $obj->rc_patrolled ) ) {
                                $rc = RecentChange::newFromRow( $obj );
                                $rc->counter = $counter++;
+
+                               if ($wgShowUpdatedMarker
+                                       && $wgUser->getOption( 'showupdated' )
+                                       && $obj->wl_notificationtimestamp
+                                       && ($obj->rc_timestamp >= $obj->wl_notificationtimestamp)) {
+                                               $rc->notificationtimestamp = true;
+                               } else {
+                                       $rc->notificationtimestamp = false;
+                               }
+
+                               if ($wgRCShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' )) {
+                                       $sql3 = "SELECT COUNT(*) AS n FROM $watchlist WHERE wl_title='" . $dbr->strencode($obj->rc_title) ."' AND wl_namespace=$obj->rc_namespace" ;
+                                       $res3 = $dbr->query( $sql3, 'wfSpecialRecentChanges');
+                                       $x = $dbr->fetchObject( $res3 );
+                                       $rc->numberofWatchingusers = $x->n;
+                               } else {
+                                       $rc->numberofWatchingusers = 0;
+                               }
                                $s .= $list->recentChangesLine( $rc, !empty( $obj->wl_user ) );
                                --$limit;
                        }
index 9029923..be254df 100644 (file)
@@ -82,6 +82,7 @@ function wfSpecialRecentchangeslinked( $par = NULL ) {
                          "GROUP BY cur_id,cur_namespace,cur_title,cur_user,cur_comment,cur_user_text," .
                  "cur_timestamp,cur_minor_edit,cur_is_new,inverse_timestamp ORDER BY inverse_timestamp LIMIT {$limit}";
        }
+
        $res = $dbr->query( $sql, $fname );
 
        $wgOut->addHTML("&lt; ".$sk->makeKnownLinkObj($nt, "", "redirect=no" )."<br />\n");
index 78581d0..4f484a9 100644 (file)
@@ -142,7 +142,7 @@ class UserlevelsForm extends HTMLForm {
 
        /**
         * The entry form
-        * It allow a user to select or eventually add a group as well as looking up
+        * It allows a user to select or eventually add a group as well as looking up
         * for a username.
         */
        function switchForm() {
index 87c004c..75ebb10 100644 (file)
@@ -6,12 +6,7 @@
  */
 
 /**
- *
- */
-require_once('UserMailer.php');
-
-/**
- * consutrctor
+ * constructor
  */
 function wfSpecialUserlogin() {
        global $wgCommandLineMode;
@@ -36,6 +31,7 @@ class LoginForm {
        
        function LoginForm( &$request ) {
                global $wgLang, $wgAllowRealName, $wgEnableEmail;
+               global $wgEmailAuthentication;
 
                $this->mName = $request->getText( 'wpName' );
                $this->mPassword = $request->getText( 'wpPassword' );
@@ -92,6 +88,7 @@ class LoginForm {
         */
        function addNewAccountMailPassword() {
                global $wgOut;
+               global $wgEmailAuthentication;
                
                if ('' == $this->mEmail) {
                        $this->mainLoginForm( wfMsg( 'noemail', htmlspecialchars( $this->mName ) ) );
@@ -104,20 +101,37 @@ class LoginForm {
                        return;
                }
 
-               $u->saveSettings();
-               $error = $this->mailPasswordInternal($u);
+               $newadr = strtolower($this->mEmail);
+
+               # prepare for authentication and mail a temporary password to newadr
+               if ( !$u->isValidEmailAddr( $newadr ) ) {
+                       return $this->mainLoginForm( wfMsg( 'invalidemailaddress', $error ) );
+               }
+               $u->mEmail = $newadr; # new behaviour: set this new emailaddr from login-page into user database record
+               $u->mEmailAuthenticationtimestamp = 0; # but flag as "dirty" = unauthenticated
+
+               if ($wgEmailAuthentication) {
+                       $error = $this->mailPasswordInternal( $u, true, $dummy ); # mail a temporary password to the dirty address
+               }
 
                $wgOut->setPageTitle( wfMsg( 'accmailtitle' ) );
                $wgOut->setRobotpolicy( 'noindex,nofollow' );
                $wgOut->setArticleRelated( false );
        
-               if ( $error === '' ) {
-                       $wgOut->addWikiText( wfMsg( 'accmailtext', $u->getName(), $u->getEmail() ) );
-                       $wgOut->returnToMain( false );
+               if ($wgEmailAuthentication) {
+                       if ($error === '') {
+                               return $this->mainLoginForm( wfMsg( 'passwordsentforemailauthentication', $u->getName() ) );
                } else {
-                       $this->mainLoginForm( wfMsg( 'mailerror', $error ) );
+                               return $this->mainLoginForm( wfMsg( 'mailerror', $error ) );
                }
-
+                       # if user returns, that new email address gets authenticated in checkpassword()
+               }
+#              if ( $error === '' ) {
+#                      $wgOut->addWikiText( wfMsg( 'accmailtext', $u->getName(), $u->getEmail() ) );
+#                      $wgOut->returnToMain( false );
+#              } else {
+#                      $this->mainLoginForm( wfMsg( 'mailerror', $error ) );
+#              }
                $u = 0;
        }
 
@@ -127,6 +141,7 @@ class LoginForm {
         */
        function addNewAccount() {
                global $wgUser, $wgOut;
+               global $wgEmailAuthentication;
 
                $u = $this->addNewAccountInternal();
 
@@ -134,6 +149,27 @@ class LoginForm {
                        return;
                }
 
+               $newadr = strtolower($this->mEmail);
+               if ($newadr != '') {            # prepare for authentication and mail a temporary password to newadr
+                       if ( !$u->isValidEmailAddr( $newadr ) ) {
+                               return $this->mainLoginForm( wfMsg( 'invalidemailaddress', $error ) );
+                       }
+                       $u->mEmail = $newadr; # new behaviour: set this new emailaddr from login-page into user database record
+                       $u->mEmailAuthenticationtimestamp = 0; # but flag as "dirty" = unauthenticated
+
+                       if ($wgEmailAuthentication) {
+                               # mail a temporary password to the dirty address
+
+                               $error = $this->mailPasswordInternal( $u, true, $dummy );
+                               if ($error === '') {
+                                       return $this->mainLoginForm( wfMsg( 'passwordsentforemailauthentication', $u->getName() ) );
+                               } else {
+                                       return $this->mainLoginForm( wfMsg( 'mailerror', $error ) );
+                               }
+                               # if user returns, that new email address gets authenticated in checkpassword()
+                       }
+               }
+
                $wgUser = $u;
                $wgUser->setCookies();
 
@@ -168,7 +204,7 @@ class LoginForm {
                $u = User::newFromName( $name );
                if ( is_null( $u ) ||
                  ( '' == $name ) ||
-                 preg_match( "/\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}/", $name ) ||
+                 $wgUser->isIP( $name ) ||
                  (strpos( $name, "/" ) !== false) ||
                  (strlen( $name ) > $wgMaxNameChars) ||
                  ucFirst($name) != $u->getName() ) 
@@ -229,7 +265,8 @@ class LoginForm {
         * @access private
         */
        function processLogin() {
-               global $wgUser;
+               global $wgUser, $wgLang;
+               global $wgEmailAuthentication;
 
                if ( '' == $this->mName ) {
                        $this->mainLoginForm( wfMsg( 'noname' ) );
@@ -258,6 +295,15 @@ class LoginForm {
                } else {
                        $u->loadFromDatabase();
                }
+
+               # store temporarily the status before the password check is performed
+               $mailmsg = '';
+               $oldadr = strtolower($u->getEmail());
+               $newadr = strtolower($this->mEmail);
+               $alreadyauthenticated = (( $u->mEmailAuthenticationtimestamp != 0 ) || ($oldadr == '')) ;
+
+               # checkPassword sets EmailAuthenticationtimestamp, if the newPassword is used
+
                if (!$u->checkPassword( $this->mPassword )) {
                        $this->mainLoginForm( wfMsg( 'wrongpassword' ) );
                        return;
@@ -272,13 +318,54 @@ class LoginForm {
                }
                $u->setOption( 'rememberpassword', $r );
 
+               /* check if user with correct password has entered a new email address */
+               if (($newadr <> '') && ($newadr <> $oldadr)) { # the user supplied a new email address on the login page
+
+                       # prepare for authentication and mail a temporary password to newadr
+                       if ( !$u->isValidEmailAddr( $newadr ) ) {
+                               return $this->mainLoginForm( wfMsg( 'invalidemailaddress', $error ) );
+                       }
+                       $u->mEmail = $newadr; # new behaviour: store this new emailaddr from login-page now into user database record ...
+                       $u->mEmailAuthenticationtimestamp = 0; # ... but flag the address as "dirty" (unauthenticated)
+                       $alreadyauthenticated = false;
+
+                       if ($wgEmailAuthentication) {
+
+                               # mail a temporary one-time password to the dirty address and return here to complete the user login
+                               # if the user returns now or later using this temp. password, then the new email address $newadr
+                               # - which is already stored in his user record - gets authenticated in checkpassword()
+
+                               $error = $this->mailPasswordInternal( $u, false, $newpassword_temp);
+                               $u->mNewpassword = $newpassword_temp;
+
+                               #       The temporary password is mailed. The user is logged-in as he entered his correct password
+                               #       This appears to be more intuitive than alternative 2.
+
+                               if ($error === '') {
+                                       $mailmsg = '<br>' . wfMsg( 'passwordsentforemailauthentication', $u->getName() );
+                               } else {
+                                       $mailmsg = '<br>' . wfMsg( 'mailerror', $error ) ;
+                               }
+                       }
+               }
+
                $wgUser = $u;
                $wgUser->setCookies();
 
+               # save all settings (incl. new email address and/or temporary password, if applicable)
                $wgUser->saveSettings();
                
+               if ( !$wgEmailAuthentication || $alreadyauthenticated ) {
+                       $authenticated = '';
+                       $mailmsg = '';
+               } elseif ($u->mEmailAuthenticationtimestamp != 0) {
+                               $authenticated = ' ' . wfMsg( 'emailauthenticated', $wgLang->timeanddate( $u->mEmailAuthenticationtimestamp, true ) );
+                       } else {
+                               $authenticated = ' ' . wfMsg( 'emailnotauthenticated' );
+                       }
+
                if( $this->hasSessionCookie() ) {
-                       return $this->successfulLogin( wfMsg( 'loginsuccess', $wgUser->getName() ) );
+                       return $this->successfulLogin( wfMsg( 'loginsuccess', $wgUser->getName() ) . $authenticated .  $mailmsg );
                } else {
                        return $this->cookieRedirectCheck( 'login' );
                }
@@ -307,39 +394,47 @@ class LoginForm {
 
                $u->loadFromDatabase();
 
-               $error = $this->mailPasswordInternal( $u );
+               $error = $this->mailPasswordInternal( $u, true, $dummy );
                if ($error === '') {
                        $this->mainLoginForm( wfMsg( 'passwordsent', $u->getName() ) );
                } else {
                        $this->mainLoginForm( wfMsg( 'mailerror', $error ) );
                }
-
+               return;
        }
 
 
        /**
         * @access private
         */
-       function mailPasswordInternal( $u ) {
-               global $wgDeferredUpdateList, $wgOutputEncoding;
+       function mailPasswordInternal( $u, $savesettings = true, &$newpassword_out ) {
                global $wgPasswordSender, $wgDBname, $wgIP;
                global $wgCookiePath, $wgCookieDomain;
 
                if ( '' == $u->getEmail() ) {
                        return wfMsg( 'noemail', $u->getName() );
                }
-               $np = User::randomPassword();
+
+               $np = $u->randomPassword();
                $u->setNewpassword( $np );
 
+               # we want to store this new password together with other values in the calling function
+               $newpassword_out = $u->mNewpassword;
+
+               # WHY IS THIS HERE ? SHOULDN'T IT BE User::setcookie ???
                setcookie( "{$wgDBname}Token", '', time() - 3600, $wgCookiePath, $wgCookieDomain );
+
+               if ($savesettings) {
                $u->saveSettings();
+               }
 
                $ip = $wgIP;
                if ( '' == $ip ) { $ip = '(Unknown)'; }
 
-               $m = wfMsg( 'passwordremindertext', $ip, $u->getName(), $np );
+               $m = wfMsg( 'passwordremindermailbody', $ip, $u->getName(), wfUrlencode($u->getName()), $np );
 
-               $error = userMailer( $u->getEmail(), $wgPasswordSender, wfMsg( 'passwordremindertitle' ), $m );
+               require_once('UserMailer.php');
+               $error = userMailer( $u->getEmail(), $wgPasswordSender, wfMsg( 'passwordremindermailsubject' ), $m );
                
                return htmlspecialchars( $error );
        }
@@ -350,7 +445,6 @@ class LoginForm {
         */
        function successfulLogin( $msg ) {
                global $wgUser;
-               global $wgDeferredUpdateList;
                global $wgOut;
 
                # Run any hooks; ignore results
@@ -382,6 +476,7 @@ class LoginForm {
        function mainLoginForm( $err ) {
                global $wgUser, $wgOut, $wgLang;
                global $wgDBname, $wgAllowRealName, $wgEnableEmail;
+               global $wgEmailAuthentication;
 
                if ( '' == $this->mName ) {
                        if ( 0 != $wgUser->getID() ) {
@@ -397,7 +492,6 @@ class LoginForm {
                }
                $titleObj = Title::makeTitle( NS_SPECIAL, 'Userlogin' );
 
-
                require_once( 'templates/Userlogin.php' );
                $template =& new UserloginTemplate();
                
@@ -410,9 +504,10 @@ class LoginForm {
                $template->set( 'action', $titleObj->getLocalUrl( $q ) );
                $template->set( 'error', $err );
                $template->set( 'create', $wgUser->isAllowedToCreateAccount() );
-               $template->set( 'createemail', $wgEnableEmail && $wgUser->getID() != 0 );
+               $template->set( 'createemail', $wgEnableEmail && ($wgUser->getID() != 0) );
                $template->set( 'userealname', $wgAllowRealName );
                $template->set( 'useemail', $wgEnableEmail );
+               $template->set( 'useemailauthent', $wgEmailAuthentication );
                $template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) );
                
                $wgOut->setPageTitle( wfMsg( 'userlogin' ) );
index 0e9ac0c..046dabb 100644 (file)
@@ -17,6 +17,7 @@ require_once( "WatchedItem.php" );
 function wfSpecialWatchlist() {
        global $wgUser, $wgOut, $wgLang, $wgTitle, $wgMemc, $wgRequest;
        global $wgUseWatchlistCache, $wgWLCacheTimeout, $wgDBname;
+       global $wgEnotif, $wgShowUpdatedMarker, $wgRCShowWatchingUsers;
        $fname = "wfSpecialWatchlist";
 
        $wgOut->setPagetitle( wfMsg( "watchlist" ) );
@@ -38,6 +39,12 @@ function wfSpecialWatchlist() {
        $remove = $wgRequest->getVal( 'remove' );
        $id = $wgRequest->getVal( 'id' );
 
+       $wgOut->addHTML( wfMsg( "email_notification_infotext" ) );
+
+       include_once( "UserMailer.php" );
+       $wgEnotif = new EmailNotification ();
+       $wgEnotif->ClearAll($wgUser->getID());
+
        if(($action == "submit") && isset($remove) && is_array($id)) {
                $wgOut->addHTML( wfMsg( "removingchecked" ) );
                foreach($id as $one) {
@@ -70,9 +77,14 @@ function wfSpecialWatchlist() {
        extract( $dbr->tableNames( 'cur', 'watchlist', 'recentchanges' ) );
 
        $sql = "SELECT COUNT(*) AS n FROM $watchlist WHERE wl_user=$uid";
-       $res = $dbr->query( $sql );
+       $res = $dbr->query( $sql, $fname );
        $s = $dbr->fetchObject( $res );
+
+#      Patch *** A1 *** (see A2 below)
+#      adjust for page X, talk:page X, which are both stored separately, but treated together
+#      $nitems = $s->n / 2;
        $nitems = $s->n;
+
        if($nitems == 0) {
         $wgOut->addHTML( wfMsg( "nowatchlist" ) );
         return;
@@ -99,7 +111,7 @@ function wfSpecialWatchlist() {
                  ( $cutoff = $dbr->timestamp( time() - intval( $days * 86400 ) ) )
                  . "'";
                $sql = "SELECT COUNT(*) AS n FROM $cur WHERE cur_timestamp>'$cutoff'";
-               $res = $dbr->query( $sql );
+               $res = $dbr->query( $sql, $fname );
                $s = $dbr->fetchObject( $res );
                $npages = $s->n;
 
@@ -113,8 +125,13 @@ function wfSpecialWatchlist() {
                        $specialTitle->escapeLocalUrl( "action=submit" ) .
                        "' method='post'>\n" .
                        "<ul>\n" );
+
+#              Patch A2
+#              The following was proposed by KTurner 07.11.2004 to T.Gries
+#              $sql = "SELECT distinct (wl_namespace & ~1),wl_title FROM $watchlist WHERE wl_user=$uid";
                $sql = "SELECT wl_namespace,wl_title FROM $watchlist WHERE wl_user=$uid";
-               $res = $dbr->query( $sql );
+
+               $res = $dbr->query( $sql, $fname );
                $sk = $wgUser->getSkin();
                while( $s = $dbr->fetchObject( $res ) ) {
                        $t = Title::makeTitle( $s->wl_namespace, $s->wl_title );
@@ -145,11 +162,17 @@ function wfSpecialWatchlist() {
        if( $cutoff && ( $nitems*1.15 > $npages ) ) {
                $x = "cur_timestamp";
                $y = wfMsg( "watchmethod-recent" );
-               $z = "wl_namespace=cur_namespace&65534";
+               # TG patch: here we do not consider pages and their talk pages equivalent - why should we ?
+               # The change results in talk-pages not automatically included in watchlists, when their parent page is included
+               # $z = "wl_namespace=cur_namespace & ~1";
+               $z = "wl_namespace=cur_namespace";
        } else {
                $x = "name_title_timestamp";
                $y = wfMsg( "watchmethod-list" );
-               $z = "(wl_namespace=cur_namespace OR wl_namespace+1=cur_namespace)";
+               # TG patch: here we do not consider pages and their talk pages equivalent - why should we ?
+               # The change results in talk-pages not automatically included in watchlists, when their parent page is included
+               # $z = "(wl_namespace=cur_namespace OR wl_namespace+1=cur_namespace)";
+               $z = "wl_namespace=cur_namespace";
        }
 
 
@@ -160,7 +183,7 @@ function wfSpecialWatchlist() {
        $use_index = $dbr->useIndexClause( $x );
        $sql = "SELECT
   cur_namespace,cur_title,cur_comment, cur_id,
-  cur_user,cur_user_text,cur_timestamp,cur_minor_edit,cur_is_new
+  cur_user,cur_user_text,cur_timestamp,cur_minor_edit,cur_is_new,wl_notificationtimestamp
   FROM $watchlist,$cur $use_index
   WHERE wl_user=$uid
   AND $z
@@ -194,7 +217,23 @@ function wfSpecialWatchlist() {
                # Make fake RC entry
                $rc = RecentChange::newFromCurRow( $obj );
                $rc->counter = $counter++;
-               $s .= $list->recentChangesLine( $rc, true );
+
+               if ($wgShowUpdatedMarker && $wgUser->getOption( 'showupdated' )) {
+                       $rc->notificationtimestamp = $obj->wl_notificationtimestamp;
+               } else {
+                       $rc->notificationtimestamp = false;
+               }
+
+               if ($wgRCShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' )) {
+                       $sql3 = "SELECT COUNT(*) AS n FROM $watchlist WHERE wl_title='" .wfStrencode($obj->cur_title). "' AND wl_namespace='{$obj->cur_namespace}'" ;
+                       $res3 = $dbr->query( $sql3, DB_READ, $fname );
+                       $x = $dbr->fetchObject( $res3 );
+                       $rc->numberofWatchingusers = $x->n;
+               } else {
+                       $rc->numberofWatchingusers = 0;
+               }
+
+               $s .= $list->recentChangesLine( $rc, true);
        }
        $s .= $list->endRecentChangesList();
 
index f46c9f0..58fb9db 100644 (file)
@@ -23,6 +23,7 @@ class User {
         * @access private
         */
        var $mId, $mName, $mPassword, $mEmail, $mNewtalk;
+       var $mEmailAuthenticationtimestamp;
        var $mRights, $mOptions;
        var $mDataLoaded, $mNewpassword;
        var $mSkin;
@@ -115,6 +116,15 @@ class User {
                return preg_match("/^\d{1,3}\.\d{1,3}.\d{1,3}\.\d{1,3}$/",$name);
        }
 
+       /**
+        * does the string match roughly an email address ?
+        * @param string $addr email address
+        * @static
+        */
+       function isValidEmailAddr ( $addr ) {
+               return preg_match( '/^([a-z0-9_.-]+([a-z0-9_.-]+)*\@[a-z0-9_-]+([a-z0-9_.-]+)*([a-z.]{2,})+)$/', strtolower($addr));
+       }
+
        /**
         * probably return a random password
         * @return string probably a random password
@@ -150,10 +160,10 @@ class User {
                $this->mNewtalk = -1;
                $this->mName = $wgIP;
                $this->mRealName = $this->mEmail = '';
+               $this->mEmailAuthenticationtimestamp = 0;
                $this->mPassword = $this->mNewpassword = '';
                $this->mRights = array();
                $this->mGroups = array();
-               
                // Getting user defaults only if we have an available language
                if( isset( $wgContLang ) ) {
                        $this->loadDefaultFromLanguage();
@@ -394,12 +404,14 @@ class User {
                
                $dbr =& wfGetDB( DB_SLAVE );
                $s = $dbr->selectRow( 'user', array( 'user_name','user_password','user_newpassword','user_email',
+                 'user_emailauthenticationtimestamp',
                  'user_real_name','user_options','user_touched', 'user_token' ),
                  array( 'user_id' => $this->mId ), $fname );
                
                if ( $s !== false ) {
                        $this->mName = $s->user_name;
                        $this->mEmail = $s->user_email;
+                       $this->mEmailAuthenticationtimestamp = $s->user_emailauthenticationtimestamp;
                        $this->mRealName = $s->user_real_name;
                        $this->mPassword = $s->user_password;
                        $this->mNewpassword = $s->user_newpassword;
@@ -458,8 +470,12 @@ class User {
                if ( $this->mNewtalk == -1 ) {
                        $this->mNewtalk=0; # reset talk page status
                        $dbr =& wfGetDB( DB_SLAVE );
+                       extract( $dbr->tableNames( 'watchlist' ) );
                        if($this->mId) {
-                               $res = $dbr->select( 'user_newtalk', 1, array( 'user_id' => $this->mId ), $fname );
+                               $sql = "SELECT wl_user FROM $watchlist
+                                       WHERE wl_title='" . $dbr->strencode( str_replace( ' ', '_', $this->mName) )."'  AND wl_namespace = " . NS_USER_TALK .
+                                       " AND wl_user=" . $this->mId . " AND wl_notificationtimestamp != 0";
+                               $res = $dbr->query( $sql,'User::get:Newtalk');
 
                                if ( $dbr->numRows($res)>0 ) {
                                        $this->mNewtalk= 1;
@@ -470,7 +486,11 @@ class User {
                                $key = "$wgDBname:newtalk:ip:{$this->mName}";
                                $newtalk = $wgMemc->get( $key );
                                if( ! is_integer( $newtalk ) ){
-                                       $res = $dbr->select( 'user_newtalk', 1, array( 'user_ip' => $this->mName ), $fname );
+                                       extract( $dbr->tableNames( 'watchlist' ) );
+                                       $sql = "SELECT wl_user FROM $watchlist
+                                               WHERE wl_title='" . $dbr->strencode( str_replace( ' ', '_', $this->mName) )."'  AND wl_namespace = " . NS_USER_TALK .
+                                               " AND wl_user =" . 0 . " AND wl_notificationtimestamp != 0";
+                                       $res = $dbr->query( $sql,'User::getNewtalk');
 
                                        $this->mNewtalk = $dbr->numRows( $res ) > 0 ? 1 : 0;
                                        $dbr->freeResult( $res );
@@ -565,6 +585,11 @@ class User {
                return $this->mEmail;
        }
 
+       function getEmailAuthenticationtimestamp() {
+               $this->loadFromDatabase();
+               return $this->mEmailAuthenticationtimestamp;
+       }
+
        function setEmail( $str ) {
                $this->loadFromDatabase();
                $this->mEmail = $str;
@@ -839,12 +864,19 @@ class User {
                global $wgMemc, $wgDBname;
                $fname = 'User::saveSettings';
 
-               $this->saveNewtalk();
+               $dbw =& wfGetDB( DB_MASTER );
+               if ( ! $this->getNewtalk() ) {
+                       # Delete the watchlist entry for user_talk page X watched by user X
+                       if( $this->mId ) {
+                               $dbw->delete( 'watchlist', array( 'wl_user' => $this->mId, 'wl_title' => str_replace('_', ' ', $this->mName) ,'wl_namespace' => NS_USER_TALK ), $fname );
+                       } else {
+                               $dbw->delete( 'watchlist', array( 'wl_user' => 0, 'wl_title' => $this->mName, 'wl_namespace' => NS_USER_TALK ), $fname );
+                               $wgMemc->delete( "$wgDBname:newtalk:ip:{$this->mName}" );
+                       }
+               }
 
                if ( 0 == $this->mId ) { return; }
                
-               $dbw =& wfGetDB( DB_MASTER );
-               
                $dbw->update( 'user',
                        array( /* SET */
                                'user_name' => $this->mName,
@@ -852,6 +884,7 @@ class User {
                                'user_newpassword' => $this->mNewpassword,
                                'user_real_name' => $this->mRealName,
                                'user_email' => $this->mEmail,
+                               'user_emailauthenticationtimestamp' => $this->mEmailAuthenticationtimestamp,
                                'user_options' => $this->encodeOptions(),
                                'user_touched' => $dbw->timestamp($this->mTouched),
                                'user_token' => $this->mToken
@@ -878,38 +911,6 @@ class User {
                }
        }
 
-       /**
-        * Save value of new talk flag.
-        */
-       function saveNewtalk() {
-               global $wgDBname, $wgMemc;
-               
-               $fname = 'User::saveNewtalk';
-
-               if ($this->getID() != 0) {
-                       $field = 'user_id';
-                       $value = $this->getID();
-                       $key = "$wgDBname:user:id:$this->mId";
-               } else {
-                       $field = 'user_ip';
-                       $value = $this->mName;
-                       $key = "$wgDBname:newtalk:ip:$this->mName";
-               }
-               
-               $dbr =& wfGetDB( DB_SLAVE );
-               $dbw =& wfGetDB( DB_MASTER );
-
-               $res = $dbr->selectField('user_newtalk', $field,
-                                                                array($field => $value), $fname);
-
-               if ($res !== false && $this->mNewtalk == 0) {
-                       $dbw->delete('user_newtalk', array($field => $value), $fname);
-                       $wgMemc->delete($key);
-               } else if ($res === false && $this->mNewtalk == 1) {
-                       $dbw->insert('user_newtalk', array($field => $value), $fname);
-                       $wgMemc->delete($key);                  
-               }
-       }
        
        /**
         * Checks if a user with the given name exists, returns the ID
@@ -943,6 +944,7 @@ class User {
                                'user_password' => $this->mPassword,
                                'user_newpassword' => $this->mNewpassword,
                                'user_email' => $this->mEmail,
+                               'user_emailauthenticationtimestamp' => $this->mEmailAuthenticationtimestamp,
                                'user_real_name' => $this->mRealName,
                                'user_options' => $this->encodeOptions(),
                                'user_token' => $this->mToken
@@ -1087,20 +1089,22 @@ class User {
         * @return bool True if the given password is correct otherwise False.
         */
        function checkPassword( $password ) {
+               global $wgAuth;
                $this->loadFromDatabase();
                
-               global $wgAuth;
                if( $wgAuth->authenticate( $this->getName(), $password ) ) {
                        return true;
                } elseif( $wgAuth->strict() ) {
                        /* Auth plugin doesn't allow local authentication */
                        return false;
                }
-               
                $ep = $this->encryptPassword( $password );
                if ( 0 == strcmp( $ep, $this->mPassword ) ) {
                        return true;
-               } elseif ( 0 == strcmp( $ep, $this->mNewpassword ) ) {
+               } elseif ( ($this->mNewpassword != '') && (0 == strcmp( $ep, $this->mNewpassword )) ) {
+                       $this->mEmailAuthenticationtimestamp = wfTimestampNow();
+                       $this->mNewpassword = ''; # use the temporary one-time password only once: clear it now !
+                       $this->saveSettings();
                        return true;
                } elseif ( function_exists( 'iconv' ) ) {
                        # Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
index 92b379d..422f1a8 100644 (file)
@@ -1,10 +1,59 @@
 <?php
+/** Copyright (C) 2004 Thomas Gries <mail@tgries.de>
+# 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
+**/
+
 /**
  * Provide mail capabilities
  *
  * @package MediaWiki
  */
 
+function wfQuotedPrintable_name_and_emailaddr( $string ) {
+       /*      it takes formats like "name <emailaddr>" into account,
+               for which the partial string "name" will be quoted-printable converted
+               but not "<emailaddr>".
+
+               The php mail() function does not accept the email address partial string
+               to be quotedprintable, it does not accept inputs as quotedprintable("name <emailaddr>") .
+
+               case 1:
+               input:  "name <emailaddr> rest"
+               output: "quoted-printable(name) <emailaddr>"
+
+               case 2: (should not happen)
+               input:  "<emailaddr> rest"
+               output: "<emailaddr>"
+
+               case 3: (should not happen)
+               input:  "name"
+               output: "quoted-printable(name)"
+
+               T. Gries 18.11.2004
+       */
+
+       /* do not quote printable for email address string <emailaddr>, but only for the (leading) string, usually the name */
+       preg_match( '/^([^<]*)?(<([A-z0-9_.-]+([A-z0-9_.-]+)*\@[A-z0-9_-]+([A-z0-9_.-]+)*([A-z.]{2,})+)>)?$/', $string, $part );
+       if ( !isset($part[1]) ) return $part[2];
+       if ( !isset($part[2]) ) return wfQuotedprintable($part[1]);
+       return wfQuotedprintable($part[1]) . $part[2] ;
+}
+
 /**
  * This function will perform a direct (authenticated) login to
  * a SMTP Server to use for mail relaying if 'wgSMTP' specifies an
  * @param string $subject email's subject
  * @param string $body email's text
  */
-function userMailer( $to, $from, $subject, $body ) {
-       global $wgUser, $wgSMTP, $wgOutputEncoding, $wgErrorString;
+function userMailer( $to, $from, $subject, $body, $replyto=false ) {
+       global $wgUser, $wgSMTP, $wgOutputEncoding, $wgErrorString, $wgEmergencyContact;
        
-       $qto = wfQuotedPrintable( $to );
+       $qto = wfQuotedPrintable_name_and_emailaddr( $to );
        
-       if (is_array( $wgSMTP ))
-       {
+       if (is_array( $wgSMTP )) {
                require_once( 'Mail.php' );
                
                $timestamp = time();
@@ -30,13 +78,16 @@ function userMailer( $to, $from, $subject, $body ) {
                $headers['From'] = $from;
 /* removing to: field as it should be set by the send() function below
    UNTESTED - Hashar */
-//             $headers["To"] = $qto;
+//             $headers['To'] = $qto;
+/* Reply-To:
+   UNTESTED - Tom Gries */
+               $headers['Reply-To'] = $replyto;
                $headers['Subject'] = $subject;
                $headers['MIME-Version'] = '1.0';
                $headers['Content-type'] = 'text/plain; charset='.$wgOutputEncoding;
                $headers['Content-transfer-encoding'] = '8bit';
                $headers['Message-ID'] = "<{$timestamp}" . $wgUser->getName() . '@' . $wgSMTP['IDHost'] . '>';
-               $headers['X-Mailer'] = 'MediaWiki interuser e-mailer';
+               $headers['X-Mailer'] = 'MediaWiki mailer';
        
                // Create the mail object using the Mail::factory method
                $mail_object =& Mail::factory('smtp', $wgSMTP);
@@ -50,20 +101,23 @@ function userMailer( $to, $from, $subject, $body ) {
                        return $mailResult->getMessage();
                else
                        return 'Mail object return unknown error.';
-       }
-       
-       else
-       {
+       } else  {
+               # In the following $headers = expression we removed "Reply-To: {$from}\r\n" , because it is treated differently
+               # (fifth parameter of the PHP mail function, see some lines below)
                $headers =
                        "MIME-Version: 1.0\n" .
                        "Content-type: text/plain; charset={$wgOutputEncoding}\n" .
-                       "Content-transfer-encoding: 8bit\n" .
-                       "From: {$from}\n" .
-                       "X-Mailer: MediaWiki interuser e-mailer";
+                       "Content-Transfer-Encoding: 8bit\n" .
+                       "X-Mailer: MediaWiki mailer\n".
+                       'From:' . wfQuotedPrintable_name_and_emailaddr($from) . "\n";
+               if ($replyto) {
+                       $headers .= 'Reply-To: '.wfQuotedPrintable_name_and_emailaddr($replyto)."\n";
+               }
 
                $wgErrorString = '';
                set_error_handler( 'mailErrorHandler' );
-               mail( $to, $subject, $body, $headers );
+               # added -f parameter, see PHP manual for the fifth parameter when using the mail function
+               mail( wfQuotedPrintable_name_and_emailaddr($to), $subject, $body, $headers, "-f{$wgEmergencyContact}\n");
                restore_error_handler();
 
                return $wgErrorString;
@@ -77,4 +131,244 @@ function mailErrorHandler( $code, $string ) {
        global $wgErrorString;
        $wgErrorString = preg_replace( "/^mail\(\): /", "", $string );
 }
+
+class EmailNotification {
+
+var $to, $subject, $body, $replyto, $from;
+
+# Patch for email notification on page changes T.Gries/M.Arndt 11.09.2004
+#
+#      This new module processes the email notifications when the current page is changed.
+#      It looks up the table watchlist to find out which users are watching that page.
+#
+#      The current implementation sends independent emails to each watching user for the following reason:
+#
+#      -       Each watching user will be notified about the page edit time expressed in his/her local time (UTC is shown additionally).
+#              To achieve this, we need to find the individual timeoffset of each watching user from the preferences..
+#
+#              Suggested improvement to slack down the number of sent emails:
+#              We could think of sending out bulk mails (bcc:user1,user2...) for all these users having the same timeoffset in their preferences.
+#
+#      -       Visit the documentation pages under http://meta.wikipedia.com/Enotif
+
+function NotifyOnPageChange($currentUser, $currentPage, $currentNs, $timestamp, $currentSummary, $currentMinorEdit, $oldid=false) {
+
+       # we use $wgEmergencyContact as sender's address
+       global $wgUser, $wgLang, $wgEmergencyContact;
+       global $wgEmailNotificationForWatchlistPages, $wgEmailNotificationForMinorEdits;
+       global $wgEmailNotificationSystembeep, $wgEmailNotificationForUserTalkPages;
+       global $wgEmailNotificationRevealPageEditorAddress;
+       global $wgEmailNotificationMailsSentFromPageEditor;
+       global $wgEmailAuthentication;
+       global $beeped;
+
+       # The following code is only run, if several conditions are met:
+       # 1. EmailNotification for pages (other than user_talk pages) must be enabled
+       # 2. minor edits (changes) are only regarded if the global flag indicates so
+
+       $isUserTalkPage = ($currentNs == NS_USER_TALK);
+       $enotifusertalkpage = ($isUserTalkPage && $wgEmailNotificationForUserTalkPages);
+       $enotifwatchlistpage = (!$isUserTalkPage && $wgEmailNotificationForWatchlistPages);
+
+       if ( ($enotifusertalkpage || $enotifwatchlistpage)
+               && (!$currentMinorEdit || $wgEmailNotificationForMinorEdits) ) {
+
+               $dbr =& wfGetDB( DB_MASTER );
+               extract( $dbr->tableNames( 'watchlist' ) );
+               $sql = "SELECT wl_user FROM $watchlist
+                       WHERE wl_title='" . $dbr->strencode($currentPage)."'  AND wl_namespace = " . $currentNs .
+                       " AND wl_user <>" . $currentUser . " AND wl_notificationtimestamp <= 1";
+               $res = $dbr->query( $sql,'UserMailer::NotifyOnChange');
+
+               if ( $dbr->numRows( $res ) > 0 ) { # if anyone is watching ... set up the email message text which is common for all receipients ...
+
+                       # This is a switch for one beep on the server when sending notification mails
+                       $beeped = false;
+
+                       $article->mTimestamp = $timestamp;
+                       $article->mSummary = $currentSummary;
+                       $article->mMinorEdit = $currentMinorEdit;
+                       $article->mNamespace = $currentNs;
+                       $article->mTitle = $currentPage;
+                       $article->mOldid = $oldid;
+
+                       $mail = $this->ComposeCommonMailtext( $wgUser, $article );
+                       $watchingUser = new User();
+
+                       for ($i = 1; $i <= $dbr->numRows( $res ); $i++) { # ... now do for all watching users ... if the options fit
+
+                               $wuser = $dbr->fetchObject( $res );
+                               $watchingUser->setID($wuser->wl_user);
+                               if ( ( $enotifwatchlistpage && $watchingUser->getOption('enotifwatchlistpages') ) ||
+                                       ( $enotifusertalkpage && $watchingUser->getOption('enotifusertalkpages') )
+                               && (!$currentMinorEdit || ($wgEmailNotificationForMinorEdits && $watchingUser->getOption('enotifminoredits') ) )
+                               && ($watchingUser->getEmail() != '')
+                               && (!$wgEmailAuthentication || ($watchingUser->getEmailAuthenticationtimestamp() != 0 ) ) ) {
+                                       # ... adjust remaining text and page edit time placeholders
+                                       # which needs to be personalized for each user
+                                       $sent = $this->ComposeAndSendPersonalisedMail( $watchingUser, $mail, $article );
+                                       /* the beep here beeps once when a watched-listed page is changed */
+                                       if ($sent && !$beeped && ($wgEmailNotificationSystembeep != '') ) {
+                                               $last_line = system($wgEmailNotificationSystembeep);
+                                               $beeped=true;
+                                       }
+                               } # if the watching user has an email address in the preferences
+                               # 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 );
+                               $succes = $dbw->update( 'watchlist',
+                                       array( /* SET */
+                                               'wl_notificationtimestamp' => $article->mTimestamp
+                                       ), array( /* WHERE */
+                                               'wl_title' => $currentPage,
+                                               'wl_namespace' => $currentNs,
+                                               'wl_user' => $wuser->wl_user
+                                       ), 'UserMailer::NotifyOnChange'
+                               );
+                       } # for every watching user
+               } # if anyone is watching
+       } # if $wgEmailNotificationForWatchlistPages = true
+} # function NotifyOnChange
+
+function ComposeCommonMailtext ( $pageeditorUser, $article ) {
+
+       global $wgLang, $wgEmergencyContact;
+       global $wgEmailNotificationRevealPageEditorAddress, $wgEmailNotificationMailsSentFromPageEditor;
+       global $wgNoReplyAddress;
+
+       $summary = ($article->mSummary == '') ? ' - ' : $article->mSummary;
+       $medit = ($article->mMinorEdit) ? wfMsg( 'minoredit' ) : '';
+
+       # You as the WikiAdmin and Sysops can make use of plenty of named variables when composing
+       # your notification emails while simply editing the Meta pages
+
+       $to             = wfMsg( 'email_notification_to' );
+       $subject        = wfMsg( 'email_notification_subject' );
+       $body           = wfMsg( 'email_notification_body' );
+       $from           = ''; /* fail safe */
+       $replyto        = ''; /* fail safe */
+
+       # regarding the use of oldid as an indicator for the last visited version, see also
+       # http://bugzilla.wikipeda.org/show_bug.cgi?id=603 "Delete + undelete cycle doesn't preserve old_id"
+       # However, in the case of a new page which is already watched, we have no previous version to compare
+       if ($article->mOldid) {
+               $body = str_replace('$NEWPAGE', wfMsg( 'email_notification_lastvisitedrevisiontext' ), $body);
+               $body = str_replace('$OLDID', $article->mOldid , $body);
+       } else {
+               $body = str_replace('$NEWPAGE', wfMsg( 'email_notification_newpagetext' ), $body );
+               $body = str_replace('$OLDID', '', $body); # clear $OLDID placeholder in the message template
+       }
+
+       $pagetitle = $article->mTitle;
+       if ($article->mNamespace != 0) { $pagetitle = $wgLang->getNsText($article->mNamespace).':'.$pagetitle; };
+       $subject = str_replace('$PAGETITLE_QP', wfQuotedPrintable(str_replace( '_', ' ', $pagetitle)), $subject);
+       $body = str_replace('%24PAGETITLE_RAWURL', wfUrlencode($pagetitle), $body);
+       $body = str_replace('$PAGETITLE_RAWURL', wfUrlencode($pagetitle), $body);
+       $body = str_replace('%24PAGETITLE', $pagetitle, $body); # needed for the {{localurl:$PAGETITLE}} in the messagetext, "$" appears here as "%24"
+       $body = str_replace('$PAGETITLE', $pagetitle, $body);
+       $body = str_replace('$PAGETIMESTAMP', $article->mTimestamp, $body);     # this is the raw internal timestamp - can be useful, too
+       $body = str_replace('$PAGEEDITDATEUTC', $wgLang->timeanddate( $article->mTimestamp, false, false, false, true ), $body);
+       $body = str_replace('$PAGEMINOREDIT', $medit,$body);
+       $body = str_replace('$PAGESUMMARY', $summary, $body);
+
+       $pageeditor_qp = wfQuotedPrintable($pageeditorUser->getName());
+       # the user who edited is $pageeditorUser->getName():
+       # reveal the page editor's address as REPLY-TO address only if the user has not opted-out
+       if (($pageeditorUser->getEmail() != '')
+       && $pageeditorUser->getOption('enotifrevealaddr')
+       && $wgEmailNotificationRevealPageEditorAddress ) {
+               if ($wgEmailNotificationMailsSentFromPageEditor) {
+                       $from = $pageeditorUser->getName().' <'.$pageeditorUser->getEmail().'>';
+               } else {
+                       $from = 'WikiAdmin <'.$wgEmergencyContact.'>';
+                       if ($wgEmailNotificationRevealPageEditorAddress) {
+                               $replyto = $pageeditorUser->getName().' <'.$pageeditorUser->getEmail().'>';
+                       }
+               }
+               $body = str_replace('$PAGEEDITORNAMEANDEMAILADDR', $pageeditorUser->getName().' <'.$pageeditorUser->getEmail().'>', $body);
+       } else {
+               $from = 'WikiAdmin <'.$wgEmergencyContact.'>';
+               $replyto = $wgNoReplyAddress;
+               $body = str_replace('$PAGEEDITORNAMEANDEMAILADDR', $replyto, $body);
+       }
+
+       if ($pageeditorUser->isIP($pageeditorUser->getName())) { #real anon (user:xxx.xxx.xxx.xxx)
+               $subject = str_replace('$PAGEEDITOR_QP', 'anonymous user ' . $pageeditorUser->getName(), $subject);
+               $body = str_replace('$PAGEEDITOR_RAWURL', wfUrlencode($pageeditorUser->getName()) . ' (anonymous user)' , $body);
+               $body = str_replace('%24PAGEEDITOR_RAWURL', wfUrlencode($pageeditorUser->getName()) . ' (anonymous user)' , $body);
+               $body = str_replace('%24PAGEEDITORE', $pageeditorUser->getName() . ' (anonymous user)' , $body);
+               $body = str_replace('$PAGEEDITORE', $pageeditorUser->getName() . ' (anonymous user)' , $body);
+               $body = str_replace('$PAGEEDITOR', 'anonymous user ' . $pageeditorUser->getName(), $body);
+       } else {
+               $subject = str_replace('$PAGEEDITOR_QP', $pageeditor_qp, $subject);
+               $body = str_replace('$PAGEEDITOR_RAWURL', wfUrlencode($pageeditorUser->getName()), $body);
+               $body = str_replace('%24PAGEEDITOR_RAWURL', wfUrlencode($pageeditorUser->getName()), $body);
+               $body = str_replace('%24PAGEEDITORE', str_replace( ' ', '_', $pageeditorUser->getName()), $body);
+               $body = str_replace('$PAGEEDITORE', str_replace( ' ', '_', $pageeditorUser->getName()), $body);
+               $body = str_replace('$PAGEEDITOR',$pageeditorUser->getName(), $body);
+       }
+
+       # now save this as the constant user-independent part of the message
+       $this->to       = $to;
+       $this->from     = $from;
+       $this->replyto  = $replyto;
+       $this->subject  = $subject;
+       $this->body     = $body;
+       return $this;
+}
+
+
+function ComposeAndSendPersonalisedMail( $watchingUser, $mail, $article) {
+
+       /* returns true if the mail was sent successfully */
+       global $wgLang;
+
+       $to = $watchingUser->getName().' <'.$watchingUser->getEmail().'>';
+       $body = str_replace('$WATCHINGUSERNAME', $watchingUser->getName() , $mail->body);
+       $body = str_replace('$WATCHINGUSEREMAILADDR', $watchingUser->getEmail(), $body);
+
+       $timecorrection = $watchingUser->getOption('timecorrection');
+       if (!$timecorrection) { $timecorrection = '00:00'; }    # fail safe - I prefer it. TomGries
+       #  $PAGEEDITDATE is the time and date of the page change expressed in terms of individual local time of the notification recipient, i.e. watching user
+       $body = str_replace('$PAGEEDITDATE', $wgLang->timeanddate($article->mTimestamp, true, false, $timecorrection, true), $body);
+       return (userMailer($to, $mail->from, $mail->subject, $body, $mail->replyto) == '');
+}
+
+function Clear($currentUser, $currentPage, $currentNs) {
+       $dbw =& wfGetDB( DB_MASTER );
+       $success = $dbw->update( 'watchlist',
+                       array( /* SET */
+                               'wl_notificationtimestamp' => 0
+                       ), array( /* WHERE */
+                               'wl_title' => $currentPage,
+                               'wl_namespace' => $currentNs,
+                               'wl_user' => $currentUser
+                       ), 'UserMailer::Clear'
+       );
+}
+
+function ClearAll($currentUser) {
+       global $_REQUEST;
+
+       if ($currentUser != 0)  {
+
+               if( $_REQUEST['reset'] == 'all') {
+
+                       $dbw =& wfGetDB( DB_MASTER );
+                       $success = $dbw->update( 'watchlist',
+                               array( /* SET */
+                                       'wl_notificationtimestamp' => 0
+                               ), array( /* WHERE */
+                                       'wl_user' => $currentUser
+                               ), 'UserMailer::ClearAll'
+                       );
+
+               #       we also need to clear here the "you have new message" notification for the own user_talk page
+               #       This is cleared one page view later in Article::viewUpdates();
+               }
+       }
+}
+
+} # end of class EmailNotification
 ?>
index 0d0f70f..b364e11 100644 (file)
@@ -17,7 +17,12 @@ class WatchedItem {
                $wl->mUser =& $user;
                $wl->mTitle =& $title;
                $wl->id = $user->getId();
-               $wl->ns = $title->getNamespace() & ~1;
+# Patch (also) for email notification on page changes T.Gries/M.Arndt 11.09.2004
+# TG patch: here we do not consider pages and their talk pages equivalent - why should we ?
+# The change results in talk-pages not automatically included in watchlists, when their parent page is included
+#              $wl->ns = $title->getNamespace() & ~1;
+               $wl->ns = $title->getNamespace();
+
                $wl->ti = $title->getDBkey();
                return $wl;
        }
@@ -57,11 +62,23 @@ class WatchedItem {
                # REPLACE instead of INSERT because occasionally someone
                # accidentally reloads a watch-add operation.
                $dbw =& wfGetDB( DB_MASTER );
-               $dbw->replace( 'watchlist', array(array('wl_user', 'wl_namespace', 'wl_title')),
+               $dbw->replace( 'watchlist', array(array('wl_user', 'wl_namespace', 'wl_title', 'wl_notificationtimestamp')),
                  array( 
                    'wl_user' => $this->id,
-                       'wl_namespace' => $this->ns,
+                       'wl_namespace' => ($this->ns & ~1),
+                       'wl_title' => $this->ti,
+                       'wl_notificationtimestamp' => '0'
+                 ), $fname );
+
+               # the following code compensates the new behaviour, introduced by the enotif patch,
+               # that every single watched page needs now to be listed in watchlist
+               # namespace:page and namespace_talk:page need separate entries: create them
+               $dbw->replace( 'watchlist', array(array('wl_user', 'wl_namespace', 'wl_title', 'wl_notificationtimestamp')),
+                 array(
+                       'wl_user' => $this->id,
+                       'wl_namespace' => ($this->ns | 1 ),
                        'wl_title' => $this->ti,
+                       'wl_notificationtimestamp' => '0'
                  ), $fname );
 
                global $wgMemc;
@@ -76,7 +93,18 @@ class WatchedItem {
                $dbw->delete( 'watchlist', 
                        array( 
                                'wl_user' => $this->id, 
-                               'wl_namespace' => $this->ns,  
+                               'wl_namespace' => ($this->ns & ~1),
+                               'wl_title' => $this->ti
+                       ), $fname
+               );
+
+               # the following code compensates the new behaviour, introduced by the enotif patch,
+               # that every single watched page needs now to be listed in watchlist
+               # namespace:page and namespace_talk:page had separate entries: clear them
+               $dbw->delete( 'watchlist',
+                       array(
+                               'wl_user' => $this->id,
+                               'wl_namespace' => ($this->ns | 1),
                                'wl_title' => $this->ti
                        ), $fname
                );
index b9d00d1..d416896 100644 (file)
@@ -94,7 +94,11 @@ class UserloginTemplate extends QuickTemplate {
                                <p>
                                        <?php $this->msgHtml( 'emailforlost' ) ?><br />
                                        <input tabindex='10' type='submit' name="wpMailmypassword"
-                                               value="<?php $this->msg('mailmypassword') ?>" />
+                                               value="<?php if ( $this->data['useemailauthent'] ) {
+                                                               $this->msg('mailmypasswordauthent') ?>" />
+                                                       <?php } else {
+                                                               $this->msg('mailmypassword') ?>" />
+                                                       <?php } ?>
                                </p>
                        </td>
                </tr>
index 1b4254b..261e6bb 100644 (file)
@@ -51,14 +51,35 @@ if(isset($wgExtraNamespaces)) {
 }
 
 /* private */ $wgDefaultUserOptionsEn = array(
-       'quickbar' => 1, 'underline' => 1, 'hover' => 1,
-       'cols' => 80, 'rows' => 25, 'searchlimit' => 20,
-       'contextlines' => 5, 'contextchars' => 50,
-       'skin' => $wgDefaultSkin, 'math' => 1, 'rcdays' => 7, 'rclimit' => 50,
-       'highlightbroken' => 1, 'stubthreshold' => 0,
-       'previewontop' => 1, 'editsection'=>1,'editsectiononrightclick'=>0, 'showtoc'=>1,
-       'showtoolbar' =>1,
-       'date' => 0, 'imagesize' => 2
+       'quickbar'              => 1,
+       'underline'             => 1,
+       'hover'                 => 1,
+       'cols'                  => 80,
+       'rows'                  => 25,
+       'searchlimit'           => 20,
+       'contextlines'          => 5,
+       'contextchars'          => 50,
+       'skin'                  => $wgDefaultSkin,
+       'math'                  => 1,
+       'rcdays'                => 7,
+       'rclimit'               => 50,
+       'highlightbroken'       => 1,
+       'stubthreshold'         => 0,
+       'previewontop'          => 1,
+       'editsection'           => 1,
+       'editsectiononrightclick'=> 0,
+       'showtoc'               => 1,
+       'showtoolbar'           => 1,
+       'date'                  => 0,
+       'imagesize'             => 2,
+       'rememberpassword'      => 0,
+       'enotifwatchlistpages'  => 1,
+       'enotifusertalkpages'   => 1,
+       'enotifminoredits'      => 0,
+       'enotifrevealaddr'      => 0,
+       'shownumberswatching'   => 1,
+       'rcusemodstyle'         => 1,
+       'showupdated'           => 1
 );
 
 /* private */ $wgQuickbarSettingsEn = array(
@@ -127,6 +148,13 @@ define( 'MW_DATE_USER_FORMAT', true );
        'previewontop',
        'previewonfirst',
        'nocache',
+       'enotifwatchlistpages',
+       'enotifusertalkpages',
+       'enotifminoredits',
+       'enotifrevealaddr',
+       'shownumberswatching',
+       'rcusemodstyle',
+       'showupdated',
 );
 
 /* private */ $wgBookstoreListEn = array(
@@ -229,11 +257,11 @@ global $wgRightsText;
 'tog-hideminor' => 'Hide minor edits in recent changes',
 'tog-usenewrc' => 'Enhanced recent changes (not for all browsers)',
 'tog-numberheadings' => 'Auto-number headings',
-'tog-showtoolbar'=>'Show edit toolbar',
+'tog-showtoolbar'              => 'Show edit toolbar',
 'tog-editondblclick' => 'Edit pages on double click (JavaScript)',
-'tog-editsection'=>'Enable section editing via [edit] links',
-'tog-editsectiononrightclick'=>'Enable section editing by right clicking<br /> on section titles (JavaScript)',
-'tog-showtoc'=>'Show table of contents<br />(for pages with more than 3 headings)',
+'tog-editsection'              => 'Enable section editing via [edit] links',
+'tog-editsectiononrightclick'  => 'Enable section editing by right clicking<br /> on section titles (JavaScript)',
+'tog-showtoc'                  => 'Show table of contents<br />(for pages with more than 3 headings)',
 'tog-rememberpassword' => 'Remember password across sessions',
 'tog-editwidth' => 'Edit box has full width',
 'tog-watchdefault' => 'Add pages you edit to your watchlist',
@@ -241,6 +269,13 @@ global $wgRightsText;
 'tog-previewontop' => 'Show preview before edit box and not after it',
 'tog-previewonfirst' => 'Show preview on first edit',
 'tog-nocache' => 'Disable page caching',
+'tog-enotifwatchlistpages'     => 'Send me an email on page changes (remark: existing notification flags need to be cleared manually in the watchlist)',
+'tog-enotifusertalkpages'      => 'Send me an email when my user talk page is changed (remark: existing notification flags need to be cleared manually in the watchlist)',
+'tog-enotifminoredits'                 => 'Send me an email also for minor edits of pages (which usually do not trigger notification mails)',
+'tog-enotifrevealaddr'                 => 'Reveal my email address in notification mails (when I change a page, it allows watching users to reply quickly to me)',
+'tog-shownumberswatching'      => 'Show the number of watching users (in recent changes view, watchlist and article page footers)',
+'tog-rcusemodstyle'            => 'Show recent changes in UseMod style: only the most recent change of any page is listed.',
+'tog-showupdated'              => 'Show update marker ',
 
 # dates
 'sunday' => 'Sunday',
@@ -494,11 +529,11 @@ You can view and copy the source of this page:",
 # Login and logout pages
 #
 "logouttitle"  => 'User logout',
-"logouttext" => "You are now logged out.
+"logouttext"           => "You are now logged out.<br>
 You can continue to use {{SITENAME}} anonymously, or you can log in
 again as the same or as a different user. Note that some pages may
 continue to be displayed as if you were still logged in, until you clear
-your browser cache\n",
+your browser cache.\n",
 
 'welcomecreation' => "== Welcome, $1! ==
 
@@ -523,13 +558,17 @@ Your account has been created. Don't forget to change your {{SITENAME}} preferen
 'createaccountmail'    => 'by email',
 'badretype'            => 'The passwords you entered do not match.',
 'userexists'   => 'The user name you entered is already in use. Please choose a different name.',
-'youremail'            => 'Your email*',
+'youremail'            => 'Your email**',
 'yourrealname'         => 'Your real name*',
 'yourlanguage' => 'Interface language',
 'yourvariant'  => 'Language variant',
 'yournick'             => 'Your nickname (for signatures)',
-'emailforlost' => "Fields marked with a star (*) are optional.  Storing an email address enables people to contact you through the website without you having to reveal your
+'emailforlost'         => "Fields marked with stars (*, **) are optional.  Storing an email address enables people to contact you through the website without you having to reveal your
 email address to them, and it can be used to send you a new password if you forget it.<br /><br />Your real name, if you choose to provide it, will be used for giving you attribution for your work.",
+'prefs-help-email'     => '** <strong>Email</strong> (optional): Enables others to contact you through your user or user_talk page without the need of revealing your
+email address. It also allows the wiki to sent you a temporary password in case you forgot your current one.',
+'prefs-help-email-enotif' => 'This address is also used to send you email notifications if you enabled the options.',
+'prefs-help-realname'  => '* <strong>Real name</strong> (optional): if you choose to provide it this will be used for giving you attribution for your work.',
 'prefs-help-userdata' => '* <strong>Real name</strong> (optional): if you choose to provide it this will be used for giving you attribution for your work.<br />
 * <strong>Email</strong> (optional): Enables people to contact you through the website without you having to reveal your
 email address to them, and it can be used to send you a new password if you forget it.',
@@ -542,20 +581,40 @@ email address to them, and it can be used to send you a new password if you forg
 'nosuchuser'   => "There is no user by the name \"$1\".
 Check your spelling, or use the form below to create a new user account.",
 'nosuchusershort'      => "There is no user by the name \"$1\". Check your spelling.",
-'wrongpassword'        => 'The password you entered is incorrect. Please try again.',
-'mailmypassword' => 'Mail me a new password',
-'passwordremindertitle' => "Password reminder from {{SITENAME}}",
-'passwordremindertext' => "Someone (probably you, from IP address $1)
-requested that we send you a new {{SITENAME}} login password.
-The password for user \"$2\" is now \"$3\".
-You should log in and change your password now.",
+'wrongpassword'                => 'The password you entered is incorrect (or missing). Please try again.',
+'mailmypassword'       => 'Mail me a temporary password
+because I forgot my password',
+'mailmypasswordauthent'        => 'Mail me a temporary password
+because I forgot my password or
+for authentication of my email address',
+'passwordremindermailsubject' => "Email address authentication and temporary login password from {{SITENAME}}",
+'passwordremindermailbody'     => "Someone, probably you from IP address $1,
+requested that we send you a temporary one-time login password for {{SITENAME}}.
+
+This mail is also be sent for the purpose of authentication of your email address.
+The password for user \"$2\" is now \"$4\".
+
+You can now log in with this temporary password, which is valid for only one login.
+You may wish to keep using your old password if you remember it or to set a new one.
+
+{{SERVER}}{{localurl:Special:Userlogin|wpName=$3&wpPassword=$4&returnto=Special:Preferences}}",
 'noemail'              => "There is no e-mail address recorded for user \"$1\".",
-'passwordsent' => "A new password has been sent to the e-mail address
+'passwordsent'         => "A temporary password has been sent to the e-mail address
 registered for \"$1\".
 Please log in again after you receive it.",
+'passwordsentforemailauthentication'
+                       =>  "A temporary password has been sent to the e-mail address newly
+registered for \"$1\".
+Please re-login with that for authentication purposes.",
 'loginend'             => '&nbsp;',
 'mailerror' => "Error sending mail: $1",
 'acct_creation_throttle_hit' => 'Sorry, you have already created $1 accounts. You can\'t make any more.',
+'emailauthenticated'   => 'Your email address was authenticated on $1.',
+'emailnotauthenticated'        => 'Your email address is <strong>not yet authenticated</strong> and the advanced email features are disabled until authentication <strong>(d.u.a.)</strong>.<br>
+To authenticate, please login in with the temporary password which has been mailed to you, or request a new one on the login page.',
+'invalidemailaddress'  => 'The email address cannot be accepted as it appears to have an invalid format. Please enter a well-formatted address or empty that field.',
+'disableduntilauthent' => '<strong>(d.u.a.)</strong>',
+'disablednoemail'      => '<strong>(disabled; no email address)</strong>',
 
 # Edit page toolbar
 'bold_sample'=>'Bold text',
@@ -586,7 +645,7 @@ Please log in again after you receive it.",
 #
 'summary'              => 'Summary',
 'subject'              => 'Subject/headline',
-'minoredit'            => 'This is a minor edit',
+'minoredit'            => 'This is a minor edit.',
 'watchthis'            => 'Watch this page',
 'savearticle'  => 'Save page',
 'preview'              => 'Preview',
@@ -866,6 +925,8 @@ Unselected groups will not be changed. You can unselect a group by using CTRL +
 'minoreditletter' => 'm',
 'newpageletter' => 'N',
 'sectionlink' => '&rarr;',
+'number_of_watching_users_RCview'      => '[$1]',
+'number_of_watching_users_pageview'    => '[$1 watching user/s]',
 
 # Upload
 #
@@ -1145,9 +1206,8 @@ $3...
 'removechecked'        => 'Remove checked items from watchlist',
 'watchlistcontains' => "Your watchlist contains $1 pages.",
 'watcheditlist'                => 'Here\'s an alphabetical list of your
-watched pages. Check the boxes of pages you want to remove
-from your watchlist and click the \'remove checked\' button
-at the bottom of the screen.',
+watched content pages. Check the boxes of pages you want to remove from your watchlist and click the \'remove checked\' button
+at the bottom of the screen (deleting a content page also deletes the accompanying talk page and vice versa).',
 'removingchecked'      => 'Removing requested items from watchlist...',
 'couldntremove'        => "Couldn't remove item '$1'...",
 'iteminvalidname'      => "Problem with item '$1', invalid name...",
@@ -1155,6 +1215,48 @@ at the bottom of the screen.',
 'wlshowlast'           => "Show last $1 hours $2 days $3",
 'wlsaved'                      => 'This is a saved version of your watchlist.',
 
+'updatedmarker'                => '<span class=\'updatedmarker\'>&nbsp;updated (since my last visit)&nbsp;</span>',
+
+'email_notification_mailer'            => '{{SITENAME}} Notification Mailer',
+'email_notification_infotext'          => '
+<strong>Email Notification</strong><p>
+<form action=\'{{localurl:Special:Watchlist|action=submit&magic=yes}}\' method=\'post\'>
+<ul>
+<li>You will be notified by email when someone changes a page which is listed in your watchlist.</li>
+<li>A flag is set so that you receive only one email on the first change.</li>
+<li>Your own edits do <b>not</b> trigger the sending of notifications.
+<li>{{MediaWiki:updatedmarker}} means that the <b><i>page has changed since your last visit and that a notification mail has been sent to you.</i></b>.</li>
+<li>The flag is automatically cleared when you visit such pages.</li>
+<li> Alternatively, you can reset all flags at once by clicking on&nbsp; <input type="submit" name="dummy" value=\'Reset all notification flags (set their status to "visited")\'><input type="hidden" name="reset" value="all"></form></li>
+</ul>
+<hr>',
+'email_notification_newpagetext'=> 'This is a new page.',
+'email_notification_to'        => '$WATCHINGUSERNAME_QP <$WATCHINGUSEREMAILADDR>',
+'email_notification_subject'   => '{{SITENAME}} page $PAGETITLE_QP has been changed by $PAGEEDITOR_QP',
+'email_notification_lastvisitedrevisiontext' => 'See {{SERVER}}{{localurl:$PAGETITLE_RAWURL|diff=0&oldid=$OLDID}} for all changes since your last visit.',
+'email_notification_body' => 'Dear $WATCHINGUSERNAME,
+
+the {{SITENAME}} page $PAGETITLE has been changed on $PAGEEDITDATE by $PAGEEDITOR,
+see {{SERVER}}{{localurl:$PAGETITLE_RAWURL}} for the current version.
+
+$NEWPAGE
+
+Editor\'s summary: $PAGESUMMARY $PAGEMINOREDIT
+Contact the editor:
+mail {{SERVER}}{{localurl:Special:Emailuser|target=$PAGEEDITOR_RAWURL}}
+wiki {{SERVER}}{{localurl:User:$PAGEEDITOR_RAWURL}}
+
+There will be no other notifications in case of further changes unless you visit this page.
+You could also reset the notification flags for all your watched pages on your watchlist.
+
+             Your friendly {{SITENAME}} notification system
+
+--
+To change your watchlist settings, visit
+{{SERVER}}{{localurl:Special:Watchlist|magic=yes}}
+
+Feedback and further assistance:
+{{SERVER}}{{localurl:WikiHelpdesk}}',
 
 # Delete/protect/revert
 #
@@ -1772,10 +1874,13 @@ class Language {
                        return wfMsg($wgWeekdayNamesEn[$key-1]);
        }
 
-       function userAdjust( $ts ) {
+       function userAdjust( $ts, $tz = false ) {
                global $wgUser, $wgLocalTZoffset;
 
+               if (!$tz) {
                $tz = $wgUser->getOption( 'timecorrection' );
+               }
+
                if ( $tz === '' ) {
                        $hrDiff = isset( $wgLocalTZoffset ) ? $wgLocalTZoffset : 0;
                        $minDiff = 0;
@@ -1798,11 +1903,13 @@ class Language {
                return date( 'YmdHis', $t );
        }
 
-       function date( $ts, $adj = false, $format = MW_DATE_USER_FORMAT ) {
+       function date( $ts, $adj = false, $format = MW_DATE_USER_FORMAT, $timecorrection = false ) {
                global $wgAmericanDates, $wgUser, $wgUseDynamicDates;
 
                $ts=wfTimestamp(TS_MW,$ts);
-               if ( $adj ) { $ts = $this->userAdjust( $ts ); }
+
+               if ( $adj ) { $ts = $this->userAdjust( $ts, $timecorrection ); }
+
                if ( $wgUseDynamicDates ) {
                        if ( $format == MW_DATE_USER_FORMAT ) {
                                $datePreference = $wgUser->getOption( 'date' );
@@ -1828,10 +1935,10 @@ class Language {
                }
        }
 
-       function time( $ts, $adj = false, $seconds = false ) {
+       function time( $ts, $adj = false, $seconds = false, $timecorrection = false ) {
                $ts=wfTimestamp(TS_MW,$ts);
 
-               if ( $adj ) { $ts = $this->userAdjust( $ts ); }
+               if ( $adj ) { $ts = $this->userAdjust( $ts, $timecorrection ); }
 
                $t = substr( $ts, 8, 2 ) . ':' . substr( $ts, 10, 2 );
                if ( $seconds ) {
@@ -1840,10 +1947,14 @@ class Language {
                return $this->formatNum( $t );
        }
 
-       function timeanddate( $ts, $adj = false, $format = MW_DATE_USER_FORMAT ) {
+       function timeanddate( $ts, $adj = false, $format = MW_DATE_USER_FORMAT, $timecorrection = false, $dateandtime = false) {
                $ts=wfTimestamp(TS_MW,$ts);
-
-               return $this->time( $ts, $adj ) . ', ' . $this->date( $ts, $adj, $format );
+               if ($dateandtime) {
+                       $ret = $this->date( $ts, $adj, $format, $timecorrection ) . ', ' . $this->time( $ts, $adj, false, $timecorrection );
+               } else {
+                       $ret = $this->time( $ts, $adj, false, $timecorrection ) . ', ' . $this->date( $ts, $adj, $format, $timecorrection );
+               }
+               return $ret;
        }
 
        function rfc1123( $ts ) {
index bb9c7ed..f1eb8b0 100644 (file)
@@ -684,10 +684,13 @@ Alle Zeiten sind UTC.
 "largefile"            => "Bitte keine Bilder über 100 KByte hochladen.",
 'emptyfile'            => "Die hochgeladene Datei ist leer. Der Grund kann ein Tippfehler im Dateinamen sein. Bitte kontrollieren Sie, ob Sie die Datei wirklich hochladen wollen.",
 "successfulupload" => "Erfolgreich hochgeladen",
-"fileuploaded" => "Die Datei \"$1\" wurde erfolgreich hochgeladen. Bitte
-verwenden Sie diesen ($2) Link zur Beschreibungsseite und füllen Sie die
-Informationen über die Datei aus, insbesondere seine Herkunft, von wem und wann es
-gemacht wurde und besondere Angaben zum Copyright, falls notwendig.",
+"fileuploaded" => "Die Datei \"$1\" wurde erfolgreich hochgeladen.
+Bitte verwenden Sie diesen ($2) Link zur Beschreibungsseite und füllen Sie die Informationen über die Datei
+ aus, insbesondere seine Herkunft, von wem und wann es
+ gemacht wurde und besondere Angaben zum Copyright, falls notwendig.
+ Falls es sich um ein Bild handelte, so können Sie mit
+ <tt><nowiki>[[Image:$1|thumb|Description]]</nowiki></tt> ein Vorschaubild
+ auf der Seite erzeugen lassen.",
 "uploadwarning" => "Warnung",
 "savefile"             => "Datei speichern",
 "uploadedimage" => "\"$1\" hochgeladen",
diff --git a/maintenance/archives/patch-drop-user_newtalk.sql b/maintenance/archives/patch-drop-user_newtalk.sql
new file mode 100644 (file)
index 0000000..6ec84fb
--- /dev/null
@@ -0,0 +1,3 @@
+-- Patch for email authentication T.Gries/M.Arndt 27.11.2004
+-- Table user_newtalk is dropped, as the table watchlist is now also used for storing user_talk-page notifications
+DROP TABLE /*$wgDBprefix*/user_newtalk;
diff --git a/maintenance/archives/patch-email-authentication.sql b/maintenance/archives/patch-email-authentication.sql
new file mode 100644 (file)
index 0000000..7a33ac4
--- /dev/null
@@ -0,0 +1,3 @@
+-- Patch for email authentication T.Gries/M.Arndt 27.11.2004
+-- A new column is added to the table 'user'.
+ALTER TABLE /*$wgDBprefix*/user ADD (user_emailauthenticationtimestamp varchar(14) binary NOT NULL default '0');
diff --git a/maintenance/archives/patch-email-notification.sql b/maintenance/archives/patch-email-notification.sql
new file mode 100644 (file)
index 0000000..02a32a0
--- /dev/null
@@ -0,0 +1,11 @@
+-- Patch for email notification on page changes T.Gries/M.Arndt 11.09.2004
+
+-- A new column 'wl_notificationtimestamp' is added to the table 'watchlist'.
+-- When a page watched by a user X is changed by someone else, an email is sent to the watching user X
+-- if and only if the field 'wl_notificationtimestamp' is '0'. The time/date of sending the mail is then stored in that field.
+-- Further pages changes do not trigger new notification mails as long as user X has not re-visited that page.
+-- The field is reset to '0' when user X re-visits the page or when he or she resets all notification timestamps
+-- ("notification flags") at once by clicking the new button on his/her watchlist page.
+-- T. Gries/M. Arndt  11.09.2004 - December 2004
+
+ALTER TABLE /*$wgDBprefix*/watchlist ADD (wl_notificationtimestamp varchar(14) binary NOT NULL default '0');
index 67596d7..d7a56f0 100644 (file)
@@ -11,6 +11,7 @@ CREATE TABLE /*$wgDBprefix*/user (
   user_password tinyblob NOT NULL default '',
   user_newpassword tinyblob NOT NULL default '',
   user_email tinytext NOT NULL default '',
+  user_emailauthenticationtimestamp varchar(14) binary NOT NULL default '0',
   user_options blob NOT NULL default '',
   user_touched char(14) binary NOT NULL default '',
   user_token char(32) binary NOT NULL default '',
@@ -25,12 +26,14 @@ CREATE TABLE /*$wgDBprefix*/user_rights (
   UNIQUE KEY ur_user (ur_user)
 );
 
-CREATE TABLE /*$wgDBprefix*/user_newtalk (
-  user_id int(5) NOT NULL default '0',
-  user_ip varchar(40) NOT NULL default '',
-  INDEX user_id (user_id),
-  INDEX user_ip (user_ip)
-);
+-- The following table is no longer needed with Enotif >= 2.00
+-- Entries for newtalk on user_talk page are handled like in the watchlist table
+-- CREATE TABLE /*$wgDBprefix*/user_newtalk (
+--  user_id int(5) NOT NULL default '0',
+--  user_ip varchar(40) NOT NULL default '',
+--  INDEX user_id (user_id),
+--  INDEX user_ip (user_ip)
+-- );
 
 CREATE TABLE /*$wgDBprefix*/cur (
   cur_id int(8) unsigned NOT NULL auto_increment,
@@ -241,6 +244,7 @@ CREATE TABLE /*$wgDBprefix*/watchlist (
   wl_user int(5) unsigned NOT NULL,
   wl_namespace tinyint(2) unsigned NOT NULL default '0',
   wl_title varchar(255) binary NOT NULL default '',
+  wl_notificationtimestamp varchar(14) binary NOT NULL default '0',
   UNIQUE KEY (wl_user, wl_namespace, wl_title),
   KEY namespace_title (wl_namespace,wl_title)
 );
index e96fd75..9ce63eb 100644 (file)
@@ -146,6 +146,82 @@ function do_image_name_unique_update() {
        }
 }
 
+function do_watchlist_update() {
+       global $wgDatabase;
+       if( $wgDatabase->fieldExists( 'watchlist', 'wl_notificationtimestamp' ) ) {
+               echo "ENOTIF: The watchlist table is already set up for email notification.\n";
+       } else {
+               echo "ENOTIF: Adding wl_notificationtimestamp field for email notification management.";
+               /* ALTER TABLE watchlist ADD (wl_notificationtimestamp varchar(14) binary NOT NULL default '0'); */
+               dbsource( "maintenance/archives/patch-email-notification.sql", $wgDatabase );
+               echo "ok\n";
+       }
+}
+
+function do_copy_newtalk_to_watchlist() {
+       global $wgDatabase;
+       global $wgCommandLineMode;      # this needs to be saved while getID() and getName() are called
+
+       $CommandLineMode_save = $wgCommandLineMode;
+       $wgCommandLineMode = false;     # otherwise User:loadfromDatabase() early returns, but we need it herein
+
+       if ( $wgDatabase->tableExists( 'user_newtalk' ) ) {
+               $res = $wgDatabase->safeQuery( 'SELECT user_id, user_ip FROM !',
+                       $wgDatabase->tableName( 'user_newtalk' ) );
+               $num_newtalks=$wgDatabase->numRows($res);
+               echo "ENOTIF: Now converting ".$num_newtalks." user_newtalk entries to watchlist table entries ... \n";
+
+               $user = new User();
+               for ( $i = 1; $i <= $num_newtalks; $i++ ) {
+                       $wluser = $wgDatabase->fetchObject( $res );
+                       echo 'ENOTIF: <= user_newtalk: user_id='.$wluser->user_id.' user_ip='.$wluser->user_ip."\n";
+                       if ($wluser->user_id == 0) { # anonymous users ... have IP numbers as "names"
+                               if ($user->isIP($wluser->user_ip)) { # do only if it really looks like an IP number (double checked)
+                                       $wgDatabase->replace( 'watchlist',
+                                               array(array('wl_user','wl_namespace', 'wl_title', 'wl_notificationtimestamp' )),
+                                                 array('wl_user'                       => 0,
+                                                       'wl_namespace'                  => NS_USER_TALK,
+                                                       'wl_title'                      => $wluser->user_ip,
+                                                       'wl_notificationtimestamp'      => '19700101000000'
+                                                       ), 'updaters.inc::do_watchlist_update2'
+                                               );
+                                       echo 'ENOTIF: ====> watchlist: user_id=0 '.$wluser->user_ip."\n";
+                               }
+                       } else { # normal users ... have user_ids
+                               $user->setID($wluser->user_id);
+                               $wgDatabase->replace( 'watchlist',
+                                       array(array('wl_user','wl_namespace', 'wl_title', 'wl_notificationtimestamp' )),
+                                         array('wl_user'                       => $user->getID(),
+                                               'wl_namespace'                  => NS_USER_TALK,
+                                               'wl_title'                      => $user->getName(),
+                                               'wl_notificationtimestamp'      => '19700101000000'
+                                               ), 'updaters.inc::do_watchlist_update3'
+                                       );
+                               echo 'ENOTIF: ====> watchlist: user_id='.$user->getID().' '.$user->getName()."\n";
+                       }
+               }
+               echo "ENOTIF: The watchlist table has got the former user_newtalk entries.\n";
+               dbsource( "maintenance/archives/patch-drop-user_newtalk.sql", $wgDatabase );
+               echo "ENOTIF: Deleting the user_newtalk table as its entries are now in the watchlist table.\n";
+       } else {
+               echo "ENOTIF: No user_newtalk table found. Nothing to convert to watchlist table entries.\n";
+       }
+       $wgCommandLineMode = $CommandLineMode_save;
+}
+
+
+function do_user_update() {
+       global $wgDatabase;
+       if( $wgDatabase->fieldExists( 'user', 'user_emailauthenticationtimestamp' ) ) {
+               echo "EAUTHENT: The user table is already set up for email authentication.\n";
+       } else {
+               echo "EAUTHENT: Adding user_emailauthenticationtimestamp field for email authentication management.";
+               /* ALTER TABLE user ADD (user_emailauthenticationtimestamp varchar(14) binary NOT NULL default '0'); */
+               dbsource( "maintenance/archives/patch-email-authentication.sql", $wgDatabase );
+               echo "ok\n";
+       }
+}
+
 # Assumes that the group table has been added.
 function do_group_update() {
        global $wgDatabase;
@@ -199,6 +275,9 @@ function do_all_updates() {
        do_linkscc_1_3_update(); flush();
        convertLinks(); flush();
        do_image_name_unique_update(); flush();
+       do_watchlist_update(); flush();
+       do_copy_newtalk_to_watchlist(); flush();
+       do_user_update(); flush();
 
        if ( isTemplateInitialised() ) {
                print "Template namespace already initialised\n";
index 6a8a4b6..8fa1cef 100644 (file)
@@ -180,11 +180,13 @@ class MonoBookTemplate extends QuickTemplate {
        <ul id="f-list">
          <?php if($this->data['lastmod'   ]) { ?><li id="f-lastmod"><?php    $this->html('lastmod')    ?></li><?php } ?>
          <?php if($this->data['viewcount' ]) { ?><li id="f-viewcount"><?php  $this->html('viewcount')  ?></li><?php } ?>
+         <?php if($this->data['numberofwatchingusers' ]) { ?><li id="f-numberofwatchingusers"><?php  $this->html('numberofwatchingusers') ?></li><?php } ?>
          <?php if($this->data['credits'   ]) { ?><li id="f-credits"><?php    $this->html('credits')    ?></li><?php } ?>
          <?php if($this->data['copyright' ]) { ?><li id="f-copyright"><?php  $this->html('copyright')  ?></li><?php } ?>
          <?php if($this->data['about'     ]) { ?><li id="f-about"><?php      $this->html('about')      ?></li><?php } ?>
          <?php if($this->data['disclaimer']) { ?><li id="f-disclaimer"><?php $this->html('disclaimer') ?></li><?php } ?>
        </ul>
+       <?php if($data['tagline']) { ?><li id="f-tagline"><?php echo $data['tagline'] ?></li><?php } ?>
       </div>
     </div>
     <?php $this->html('reporttime') ?>
index 3e3925b..e696f49 100644 (file)
@@ -967,3 +967,17 @@ span.newpage, span.minor, span.searchmatch {
 span.searchmatch {
        color: red;
 }
+
+span.updatedmarker {
+       color:black;
+       background-color:#00FF00;
+}
+span.newpageletter {
+       font-weight:bold
+       color:black;
+       background-color:yellow;
+}
+span.minoreditletter {
+       color:black;
+       background-color:#C5FFE6;
+}
index 294f8d1..4e798bb 100755 (executable)
@@ -430,3 +430,18 @@ div.patrollink {
 span.newpage, span.minor {
        font-weight: bold;
 }
+
+span.updatedmarker {
+       color:black;
+       background-color:#00FF00;
+}
+span.newpageletter {
+       font-weight:bold
+       color:black;
+       background-color:yellow;
+}
+span.minoreditletter {
+       color:black;
+       background-color:#C5FFE6;
+}
+
index 97063f2..95c68b2 100644 (file)
@@ -212,3 +212,16 @@ span.unpatrolled {
        color:red;
 }
 
+span.updatedmarker {
+       color:black;
+       background-color:#00FF00;
+}
+span.newpageletter {
+       font-weight:bold
+       color:black;
+       background-color:yellow;
+}
+span.minoreditletter {
+       color:black;
+       background-color:#C5FFE6;
+}
index 8f15bc0..754af28 100644 (file)
@@ -935,3 +935,17 @@ span.searchmatch {
 .sharedUploadNotice {
         font-style: italic;
 }
+
+span.updatedmarker {
+       color:black;
+       background-color:#00FF00;
+}
+span.newpageletter {
+       font-weight:bold
+       color:black;
+       background-color:yellow;
+}
+span.minoreditletter {
+       color:black;
+       background-color:#C5FFE6;
+}