Merge branch 'master' into terms
authorTom Hughes <tom@compton.nu>
Fri, 7 May 2010 21:23:20 +0000 (22:23 +0100)
committerTom Hughes <tom@compton.nu>
Fri, 7 May 2010 21:23:20 +0000 (22:23 +0100)
Conflicts:
config/application.yml
lib/osm.rb
public/stylesheets/common.css

1  2 
app/controllers/user_controller.rb
config/application.yml
config/environment.rb
config/locales/de.yml
config/locales/en.yml
config/routes.rb
lib/osm.rb
public/stylesheets/common.css

index 43a6db790bc8e25375f15bf0ef57778b31d9dc0f,222840a0610a60ed1117594c65e746eca7742ef5..8fdb89b227c628c78807a87535d42c5f961edb6e
@@@ -11,45 -11,26 +11,45 @@@ class UserController < ApplicationContr
    before_filter :require_allow_read_prefs, :only => [:api_details]
    before_filter :require_allow_read_gpx, :only => [:api_gpx_files]
    before_filter :require_cookies, :only => [:login, :confirm]
-   before_filter :require_administrator, :only => [:activate, :deactivate, :hide, :unhide, :delete]
-   before_filter :lookup_this_user, :only => [:activate, :deactivate, :hide, :unhide, :delete]
+   before_filter :require_administrator, :only => [:set_status, :delete, :list]
+   before_filter :lookup_this_user, :only => [:set_status, :delete]
  
    filter_parameter_logging :password, :pass_crypt, :pass_crypt_confirmation
  
-   cache_sweeper :user_sweeper, :only => [:account, :hide, :unhide, :delete]
+   cache_sweeper :user_sweeper, :only => [:account, :set_status, :delete]
  
 +  def terms
 +    @title = t 'user.new.title'
 +    @user = User.new(params[:user])
 +
 +    @legale = params[:legale] || OSM.IPToCountry(request.remote_ip) || APP_CONFIG['default_legale']
 +    @text = OSM.legal_text_for_country(@legale)
 +
 +    if request.xhr?
 +      render :update do |page|
 +        page.replace_html "contributorTerms", :partial => "terms"
 +      end
 +    elsif @user.invalid?
 +      render :action => 'new'
 +    end
 +  end
 +
    def save
      @title = t 'user.new.title'
  
      if Acl.find_by_address(request.remote_ip, :conditions => {:k => "no_account_creation"})
        render :action => 'new'
 +    elsif params[:decline]
 +      redirect_to t 'user.terms.declined'
      else
        @user = User.new(params[:user])
  
-       @user.visible = true
+       @user.status = "pending"
        @user.data_public = true
        @user.description = "" if @user.description.nil?
        @user.creation_ip = request.remote_ip
        @user.languages = request.user_preferred_languages
 +      @user.terms_agreed = Time.now.getutc
  
        if @user.save
          flash[:notice] = t 'user.new.flash create success message'
@@@ -89,9 -70,9 +89,9 @@@
          set_locale
  
          if @user.new_email.nil? or @user.new_email.empty?
-           flash.now[:notice] = t 'user.account.flash update success'
+           flash[:notice] = t 'user.account.flash update success'
          else
-           flash.now[:notice] = t 'user.account.flash update success confirm needed'
+           flash[:notice] = t 'user.account.flash update success confirm needed'
  
            begin
              Notifier.deliver_email_confirm(@user, @user.tokens.create)
@@@ -99,6 -80,8 +99,8 @@@
              # Ignore errors sending email
            end
          end
+         redirect_to :action => "account", :display_name => @user.display_name
        end
      else
        if flash[:errors]
      @title = t 'user.lost_password.title'
  
      if params[:user] and params[:user][:email]
-       user = User.find_by_email(params[:user][:email], :conditions => {:visible => true})
+       user = User.find_by_email(params[:user][:email], :conditions => {:status => ["pending", "active", "confirmed"]})
  
        if user
          token = user.tokens.create
          if params[:user]
            @user.pass_crypt = params[:user][:pass_crypt]
            @user.pass_crypt_confirmation = params[:user][:pass_crypt_confirmation]
-           @user.active = true
+           @user.status = "active"
            @user.email_valid = true
  
            if @user.save
          else
            redirect_to :controller => 'site', :action => 'index'
          end
-        elsif User.authenticate(:username => email_or_display_name, :password => pass, :inactive => true)
+       elsif User.authenticate(:username => email_or_display_name, :password => pass, :pending => true)
          flash.now[:error] = t 'user.login.account not active'
+       elsif User.authenticate(:username => email_or_display_name, :password => pass, :suspended => true)
+         flash.now[:error] = t 'user.login.account suspended'
        else
          flash.now[:error] = t 'user.login.auth failure'
        end
        token = UserToken.find_by_token(params[:confirm_string])
        if token and !token.user.active?
          @user = token.user
-         @user.active = true
+         @user.status = "active"
          @user.email_valid = true
          @user.save!
          referer = token.referer
          @user = token.user
          @user.email = @user.new_email
          @user.new_email = nil
-         @user.active = true
          @user.email_valid = true
          if @user.save
            flash[:notice] = t 'user.confirm_email.success'
    def make_friend
      if params[:display_name]
        name = params[:display_name]
-       new_friend = User.find_by_display_name(name, :conditions => {:visible => true})
+       new_friend = User.find_by_display_name(name, :conditions => {:status => ["active", "confirmed"]})
        friend = Friend.new
        friend.user_id = @user.id
        friend.friend_user_id = new_friend.id
    def remove_friend
      if params[:display_name]
        name = params[:display_name]
-       friend = User.find_by_display_name(name, :conditions => {:visible => true})
+       friend = User.find_by_display_name(name, :conditions => {:status => ["active", "confirmed"]})
        if @user.is_friends_with?(friend)
          Friend.delete_all "user_id = #{@user.id} AND friend_user_id = #{friend.id}"
          flash[:notice] = t 'user.remove_friend.success', :name => friend.display_name
    end
  
    ##
-   # activate a user, allowing them to log in
-   def activate
-     @this_user.update_attributes(:active => true)
+   # sets a user's status
+   def set_status
+     @this_user.update_attributes(:status => params[:status])
      redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
    end
  
    ##
-   # deactivate a user, preventing them from logging in
-   def deactivate
-     @this_user.update_attributes(:active => false)
+   # delete a user, marking them as deleted and removing personal data
+   def delete
+     @this_user.delete
      redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
    end
  
    ##
-   # hide a user, marking them as logically deleted
-   def hide
-     @this_user.update_attributes(:visible => false)
-     redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
-   end
+   # display a list of users matching specified criteria
+   def list
+     if request.post?
+       ids = params[:user].keys.collect { |id| id.to_i }
  
-   ##
-   # unhide a user, clearing the logically deleted flag
-   def unhide
-     @this_user.update_attributes(:visible => true)
-     redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
-   end
+       User.update_all("status = 'confirmed'", :id => ids) if params[:confirm]
+       User.update_all("status = 'deleted'", :id => ids) if params[:hide]
  
-   ##
-   # delete a user, marking them as deleted and removing personal data
-   def delete
-     @this_user.delete
-     redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
+       redirect_to url_for(:status => params[:status], :ip => params[:ip], :page => params[:page])
+     else
+       conditions = Hash.new
+       conditions[:status] = params[:status] if params[:status]
+       conditions[:creation_ip] = params[:ip] if params[:ip]
+       @user_pages, @users = paginate(:users,
+                                      :conditions => conditions,
+                                      :order => :id,
+                                      :per_page => 50)
+     end
    end
  private
    ##
    # require that the user is a administrator, or fill out a helpful error message
    # and return them to the user page.
    def require_administrator
-     unless @user.administrator?
+     if @user and not @user.administrator?
        flash[:error] = t('user.filter.not_an_administrator')
-       redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
+       if params[:display_name]
+         redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
+       else
+         redirect_to :controller => 'user', :action => 'login', :referer => request.request_uri
+       end
+     elsif not @user
+       redirect_to :controller => 'user', :action => 'login', :referer => request.request_uri
      end
    end
  
diff --combined config/application.yml
index 7b2c603f087ae364e24123293dd238442caa0c6f,a1fbd887a4e81410b765d2c86a0088c6ecfd4e61..d405709657166f85a59903e359d1da4269dbc199
@@@ -22,8 -22,8 +22,10 @@@ standard_settings: &standard_setting
    # Quova authentication details
    #quova_username: ""
    #quova_password: ""
+   # Spam threshold
+   spam_threshold: 50
 +  # Default legale (jurisdiction location) for contributor terms
 +  default_legale: GB
   
  development:
    <<: *standard_settings
diff --combined config/environment.rb
index d9ffcc965bdd83e51cd740c3246b403df9eb0d65,d8f9b2fc85ca0503146b1c55ff390c86bf1003cc..93b6e86d2a16415409fd5b0f116a0c3e7a670491
@@@ -8,7 -8,7 +8,7 @@@ ENV['RAILS_ENV'] ||= 'production
  RAILS_GEM_VERSION = '2.3.5' unless defined? RAILS_GEM_VERSION
  
  # Set the server URL
 -SERVER_URL = ENV['OSM_SERVER_URL'] || 'www.openstreetmap.org'
 +SERVER_URL = ENV['OSM_SERVER_URL'] || 'contributor_terms.dev.openstreetmap.org'
  
  # Set the generator
  GENERATOR = ENV['OSM_SERVER_GENERATOR'] || 'OpenStreetMap server'
@@@ -100,7 -100,7 +100,7 @@@ Rails::Initializer.run do |config
    config.active_record.schema_format = :sql
  
    # Activate observers that should always be running
-   # config.active_record.observers = :cacher, :garbage_collector
+   config.active_record.observers = :spam_observer
  
    # Make Active Record use UTC-base instead of local time
    config.active_record.default_timezone = :utc
diff --combined config/locales/de.yml
index 348091964bc96c4fa4bf199fafbd27eda9c510eb,5d61a478db2d01081a4a5434035b15cb071e9409..417d53eecf82c537fcf02033d4ae871b13d38317
@@@ -885,7 -885,7 +885,7 @@@ de
        history_tooltip: Änderungen für diesen Bereich anzeigen
        history_zoom_alert: Du musst näher heranzoomen, um die Chronik zu sehen
    layouts: 
-     copyright: Urheberrechtsinformation + Lizenz
+     copyright: Urheberrecht + Lizenz
      donate: Unterstütze die OpenStreetMap-Hardwarespendenaktion durch eine eigene {{link}}.
      donate_link_text: Spende
      edit: Bearbeiten
      make_a_donation: 
        text: Spenden
        title: Unterstütze OpenStreetMap mit einer Geldspende
-     news_blog: News-Blog
+     news_blog: Neuigkeiten-Blog
      news_blog_tooltip: News-Blog über OpenStreetMap, freie geographische Daten, etc.
      osm_offline: Die OpenStreetMap-Datenbank ist im Moment wegen wichtiger Wartungsarbeiten nicht verfügbar.
      osm_read_only: Die OpenStreetMap-Datenbank ist im Moment wegen wichtiger Wartungsarbeiten im „Nur-Lesen-Modus“.
      welcome_user_link_tooltip: Eigene Benutzerseite
    license_page: 
      foreign: 
-       english_link: das englischsprachige Original
-       text: Für den Fall einer Abweichung zwischen der vorliegenden Übersetzung und {{english_original_link}}, ist die englischsprachige Seite maßgebend
+       english_link: dem englischsprachigen Original
+       text: Für den Fall einer Abweichung zwischen der vorliegenden Übersetzung und {{english_original_link}}, ist die englischsprachige Seite maßgebend.
        title: Über diese Übersetzung
-     legal_babble: "<h2>Urheberrechtsinformation und Lizenz</h2>\n\n<p>\n  OpenStreetMap besteht aus <i>freien Daten</i>, die gemäß der Lizenz <a href=\"http://creativecommons.org/licenses/by-sa/2.0/\">Creative Commons Attribution-ShareAlike 2.0</a> (CC-BY-SA) verfügbar sind.\n</p>\n<p>\n  Es steht Dir frei unsere Daten und Karten zu kopieren, weiterzugeben, zu übermittelt sowie anzupassen, sofern Du OpenStreetMap und die daran Mitwirkenden angibst. Für den Fall, dass Du auf Basis unsere Daten und Karten Veränderungen vornimmst oder als Basis für weitere Bearbeitungen verwendest, kannst Du das Ergebnis auch nur gemäß der selben Lizenz weitergeben. Der vollständige Lizenztext ist unter <a href=\"http://creativecommons.org/licenses/by-sa/2.0/legalcode\">Lizenz</a> einsehbar und erläutert Deine Rechte und Pflichten.\n</p>\n\n<h3>So ist auf die Urheberschaft von OpenStreetMap hinzuweisen</h3>\n<p>\n  Sofern Du Bilder von OpenStreetMap verwendest, so ist mindestens &bdquo;&copy; OpenStreetMap und Mitwirkende, CC-BY-SA&ldquo; anzugeben. Werden hingegen ausschließlich Geodaten genutzt, so ist mindestens &bdquo;Geodaten &copy; OpenStreetMap und Mitwirkende, CC-BY-SA&ldquo; anzugeben.\n</p>\n<p>\n  Wo möglich muss ein Hyperlink auf OpenStreetMap <a href=\"http://www.openstreetmap.org/\">http://www.openstreetmap.org/</a> und die Lizenz CC-BY-SA <a   href=\"http://creativecommons.org/licenses/by-sa/2.0/\">http://creativecommons.org/licenses/by-sa/2.0/</a> gesetzt werden. Für den Fall, dass Du ein Medium einsetzt, bei dem keine derartigen Verweise möglich sind (z. B. ein gedrucktes Buch), schlagen wir vor, dass Du Deine Leser auf www.openstreetmap.org und www.creativecommons.org verweist.\n</p>\n\n<h3>Mehr hierzu in Erfahrung bringen</h3>\n<p>\n  Mehr dazu, wie unsere Daten verwendet werden können ist unter <a href=\"http://wiki.openstreetmap.org/wiki/Legal_FAQ\">Häufige rechtliche Fragen</a> nachzulesen.\n</p>\n<p>\n  Die Bearbeiter von OpenStreetMap möchten wird darauf hinweisen, dass Sie keinesfalls Daten aus urheberrechtlich geschützten Quellen verwenden dürfen (z. B. Google Maps oder gedruckte Kartenwerke) ohne vorher die ausdrückliche Erlaubnis des Rechteinhabers erhalten zu haben.\n</p>\n<p>\n  Obzwar OpenStreetMap aus freien Daten besteht, können wir Dritten keine kostenfreie Programmierschnittstelle (API) für Karten bereitstellen.\n  \n  Siehe hierzu die <a href=\"http://wiki.openstreetmap.org/wiki/API_usage_policy\">Richtlinie zur Nutzung einer API</a>, die <a href=\"http://wiki.openstreetmap.org/wiki/Tile_usage_policy\">Richtlinie zur Nutzung von Kachelgrafiken</a> und die <a href=\"http://wiki.openstreetmap.org/wiki/Nominatim#Usage_Policy\">Nutzungsrichtlinie bezüglich Daten von Nominatim</a>.\n</p>\n\n<h3>Unsere Mitwirkenden</h3>\n<p>\n  Die von uns verwendete Lizenz CC-BY-SA verlangt, dass Du &bdquo;für das betreffende Medium oder Mittel angemessen, auf die ursprünglichen Bearbeiter hinweist.&ldquo; Einige an OpenStreetMap Mitwirkende verlangen keine über den Vermerk &bdquo;OpenStreetMap und Mitwirkende&ldquo; hinausgehende Hinweise. Wo allerdings Daten von Nationalen Kartografierungsinstitutionen oder aus anderen umfangreichen Quellen einbezogen wurden, ist es sinnvoll, deren Lizenzhinweise direkt wiederzugeben oder auf Sie auf dieser Website zu verlinken.\n</p>\n\n<ul id=\"contributors\">\n   <li><strong>Australien</strong>: Enthält Daten zu Siedlungen, die auf Daten des Australian Bureau of Statistics basieren.</li>\n   <li><strong>Kanada</strong>: Enthält Daten von GeoBase&reg;, GeoGratis (&copy; Department of Natural Resources Canada), CanVec (&copy; Department of Natural Resources Canada) und StatCan (Geography Division, Statistics Canada).</li>\n   <li><strong>Neuseeland</strong>: Enthält Daten aus Land Information New Zealand. Urheberrecht vorbehalten.</li>\n   <li><strong>Vereinigtes Königreich</strong>: Enthält Daten des Ordnance Survey &copy; Urheber- und Datenbankrecht 2010.</li>\n</ul>\n\n<p>\n  Die Einbeziehung von Daten bei OpenStreetMap impliziert nicht, das der ursprüngliche Datenlieferant OpenStreetMap unterstützt, Gewährleistung gibt noch Haftung übernimmt.\n</p>"
+     legal_babble: "<h2>Urheberrecht und Lizenz</h2>\n\n<p>\n  OpenStreetMap besteht aus <i>freien Daten</i>, die gemäß der Lizenz <a href=\"http://creativecommons.org/licenses/by-sa/2.0/\">Creative Commons Attribution-ShareAlike 2.0</a> (CC-BY-SA) verfügbar sind.\n</p>\n<p>\n  Es steht Dir frei unsere Daten und Karten zu kopieren, weiterzugeben, zu übermittelt sowie anzupassen, sofern Du OpenStreetMap und die Mitwirkenden als Quelle angibst. Für den Fall, dass Du auf Basis unserer Daten und Karten Anpassungen vornimmst, oder sie als Basis für weitere Bearbeitungen verwendest, kannst Du das Ergebnis auch nur gemäß der selben Lizenz weitergeben. Der vollständige Lizenztext ist unter <a href=\"http://creativecommons.org/licenses/by-sa/2.0/legalcode\">Lizenz</a> einsehbar und erläutert Deine Rechte und Pflichten.\n</p>\n\n<h3>So ist auf die Urheberschaft von OpenStreetMap hinzuweisen</h3>\n<p>\n  Sofern Du Bilder von OpenStreetMap verwendest, so ist mindestens &bdquo;&copy; OpenStreetMap und Mitwirkende, CC-BY-SA&ldquo; als Quelle anzugeben. Werden hingegen ausschließlich Geodaten genutzt, so ist mindestens &bdquo;Geodaten &copy; OpenStreetMap und Mitwirkende, CC-BY-SA&ldquo; anzugeben.\n</p>\n<p>\n  Wo möglich, muss ein Hyperlink auf OpenStreetMap <a href=\"http://www.openstreetmap.org/\">http://www.openstreetmap.org/</a> und die Lizenz CC-BY-SA <a   href=\"http://creativecommons.org/licenses/by-sa/2.0/\">http://creativecommons.org/licenses/by-sa/2.0/</a> gesetzt werden. Für den Fall, dass Du ein Medium einsetzt, bei dem keine derartigen Verweise möglich sind (z. B. ein gedrucktes Buch), schlagen wir vor, dass Du Deine Leser auf www.openstreetmap.org und www.creativecommons.org hinweist.\n</p>\n\n<h3>Mehr hierzu in Erfahrung bringen</h3>\n<p>\n  Mehr dazu, wie unsere Daten verwendet werden können, ist unter <a href=\"http://wiki.openstreetmap.org/wiki/Legal_FAQ\">Häufige rechtliche Fragen</a> nachzulesen.\n</p>\n<p>\n  Die Mitwirkenden von OpenStreetMap weisen wir darauf hin, dass Sie keinesfalls Daten aus urheberrechtlich geschützten Quellen verwenden dürfen (z. B. Google Maps oder gedruckte Kartenwerke), ohne vorher die ausdrückliche Erlaubnis des Rechteinhabers erhalten zu haben.\n</p>\n<p>\n  Obzwar OpenStreetMap aus freien Daten besteht, können wir Dritten keine kostenfreie Programmierschnittstelle (API) für Karten bereitstellen.\n  \n  Siehe hierzu die <a href=\"http://wiki.openstreetmap.org/wiki/API_usage_policy\">Richtlinie zur Nutzung einer API</a>, die <a href=\"http://wiki.openstreetmap.org/wiki/Tile_usage_policy\">Richtlinie zur Nutzung von Kachelgrafiken</a> und die <a href=\"http://wiki.openstreetmap.org/wiki/Nominatim#Usage_Policy\">Nutzungsrichtlinie bezüglich Daten von Nominatim</a>.\n</p>\n\n<h3>Unsere Mitwirkenden</h3>\n<p>\n  Die von uns verwendete Lizenz CC-BY-SA verlangt, dass Du &bdquo;für das betreffende Medium oder Mittel in angemessener Weise, auf die ursprünglichen Bearbeiter hinweist.&ldquo; Einige an OpenStreetMap Mitwirkende verlangen keine über den Vermerk &bdquo;OpenStreetMap und Mitwirkende&ldquo; hinausgehende Hinweise. Wo allerdings Daten von nationalen Kartografierungsinstitutionen oder aus anderen umfangreichen Quellen einbezogen wurden, ist es sinnvoll, deren Lizenzhinweise direkt wiederzugeben oder auf diese auf dieser Website zu verlinken.\n</p>\n\n<ul id=\"contributors\">\n   <li><strong>Australien</strong>: Enthält Daten zu Siedlungen, die auf Daten des Australian Bureau of Statistics basieren.</li>\n   <li><strong>Kanada</strong>: Enthält Daten von GeoBase&reg;, GeoGratis (&copy; Department of Natural Resources Canada), CanVec (&copy; Department of Natural Resources Canada) und StatCan (Geography Division, Statistics Canada).</li>\n   <li><strong>Neuseeland</strong>: Enthält Daten aus Land Information New Zealand. Urheberrecht vorbehalten.</li>\n   <li><strong>Vereinigtes Königreich</strong>: Enthält Daten des Ordnance Survey &copy; Urheber- und Datenbankrecht 2010.</li>\n</ul>\n\n<p>\n  Die Einbeziehung von Daten bei OpenStreetMap impliziert nicht, das der ursprüngliche Datenlieferant OpenStreetMap unterstützt, Gewährleistung gibt, noch Haftung übernimmt.\n</p>"
      native: 
        mapping_link: mit dem Kartieren anfangen
-       native_link: THIS_LANGUAGE_NAME_HERE Sprachversion
-       text: Du befindest dich auf der Seite mit der englischsprachigen Version der Urheberrechtsinformationen. Du kannst zur {{native_link}} dieser Seite zurückkehren oder das Lesen der Urheberrechtsinformationen beenden und {{mapping_link}}.
+       native_link: deutschen Sprachversion
+       text: Du befindest Dich auf der Seite mit der englischsprachigen Version der Urheberrechts- und Lizensierungsinformationen. Du kannst zur {{native_link}} dieser Seite zurückkehren oder das Lesen der Urheberrechtsinformationen beenden und {{mapping_link}}.
        title: Über diese Seite
    message: 
      delete: 
        no_auto_account_create: Im Moment ist das automatische Erstellen eines Benutzerkontos leider nicht möglich.
        not displayed publicly: Nicht öffentlich sichtbar (<a href="http://wiki.openstreetmap.org/wiki/Privacy_Policy">Datenschutzrichtlinie</a>)
        password: "Passwort:"
 -      signup: Registrieren
 +      signup: Fortsetzen
        title: Benutzerkonto erstellen
      no_such_user: 
        body: Es gibt leider keinen Benutzer mit dem Namen {{user}}. Bitte überprüfe deine Schreibweise oder der Link war beschädigt.
diff --combined config/locales/en.yml
index 3b83dece8140e1bb3427c252d9251d8561324e2f,fbf8e7a0462ab620397b0f0f7133c744718234a4..5abae7222ec5b3617d17e3ab10790e243ffa3af3
@@@ -1489,6 -1489,7 +1489,7 @@@ en
        lost password link: "Lost your password?"
        login_button: "Login"
        account not active: "Sorry, your account is not active yet.<br />Please click on the link in the account confirmation email to activate your account."
+       account suspended: Sorry, your account has been suspended due to suspicious activity.<br />Please contact the <a href="mailto:webmaster@openstreetmap.org">webmaster</a> if you wish to discuss this.
        auth failure: "Sorry, could not log in with those details."
      logout:
        title: "Logout"
        no_auto_account_create: "Unfortunately we are not currently able to create an account for you automatically."
        contact_webmaster: 'Please contact the <a href="mailto:webmaster@openstreetmap.org">webmaster</a> to arrange for an account to be created - we will try and deal with the request as quickly as possible.'
        fill_form: "Fill in the form and we will send you a quick email to activate your account."
 -      license_agreement: 'By creating an account, you agree that all data you submit to the Openstreetmap project is to be (non-exclusively) licensed under <a href="http://creativecommons.org/licenses/by-sa/2.0/">this Creative Commons license (by-sa)</a>.'
 +      license_agreement: 'When you confirm your account you will need to agree to the <a href="http://www.osmfoundation.org/wiki/License/Contributor_Terms">contributor terms</a>.'
        email address: "Email Address:"
        confirm email address: "Confirm Email Address:"
        not displayed publicly: 'Not displayed publicly (see <a href="http://wiki.openstreetmap.org/wiki/Privacy_Policy" title="wiki privacy policy including section on email addresses">privacy policy</a>)'
        display name description: "Your publicly displayed username. You can change this later in the preferences."
        password: "Password:"
        confirm password: "Confirm Password:"
 -      signup: Signup
 +      continue: Continue
        flash create success message: "User was successfully created. Check your email for a confirmation note, and you will be mapping in no time :-)<br /><br />Please note that you will not be able to login until you've received and confirmed your email address.<br /><br />If you use an antispam system which sends confirmation requests then please make sure you whitelist webmaster@openstreetmap.org as we are unable to reply to any confirmation requests."
 +    terms:
 +      heading: "Contributor terms"
 +      press accept button: "Please read the agreement below and press the agree button to create your account."
 +      consider_pd: "I consider my contributions to be in the Public Domain"
 +      consider_pd_why: "what's this?"
 +      consider_pd_why_url: http://wiki.openstreetmap.org/wiki/Why_would_I_want_my_contributions_to_be_public_domain
 +      agree: Agree
 +      declined: "http://wiki.openstreetmap.org/wiki/Contributor_Terms_Declined"
 +      decline: "Decline"
 +      legale_select: "Please select your country of residence:"
 +      legale_button: "Go"
 +      legale_names:
 +        france: "France"
 +        italy: "Italy"
 +        rest_of_world: "Rest of the world"
      no_such_user:
        title: "No such user"
        heading: "The user {{user}} does not exist"
        ago: "({{time_in_words_ago}} ago)"
        email address: "Email address:"
        created from: "Created from:"
+       status: "Status:"
+       spam score: "Spam Score:"
        description: Description
        user location: User location
        if set location: "If you set your location, a pretty map and stuff will appear here. You can set your home location on your {{settings_link}} page."
        create_block: "block this user"
        activate_user: "activate this user"
        deactivate_user: "deactivate this user"
+       confirm_user: "confirm this user"
        hide_user: "hide this user"
        unhide_user: "unhide this user"
        delete_user: "delete this user"
        not_a_friend: "{{name}} is not one of your friends."
      filter:
        not_an_administrator: "You need to be an administrator to perform that action."
+     list:
+       title: Users
+       heading: Users
+       showing:
+         one: Showing page {{page}} ({{first_item}} of {{items}})
+         other: Showing page {{page}} ({{first_item}}-{{last_item}} of {{items}})
+       summary: "{{name}} created from {{ip_address}} on {{date}}"
+       summary_no_ip: "{{name}} created on {{date}}"
+       confirm: Confirm Selected Users
+       hide: Hide Selected Users
+       empty: No matching users found
+     suspended:
+       title: Account Suspended
+       heading: Account Suspended
+       body: |
+         <p>
+           Sorry, your account has been automatically suspended due to
+           suspicious activity.
+         </p>
+         <p>
+           This decision will be reviewed by an administrator shortly, or
+           you may contact the <a href="mailto:{{webmaster}}">webmaster</a> if
+           you wish to discuss this.
+         </p>
    user_role:
      filter:
        not_an_administrator: "Only administrators can perform user role management, and you are not an administrator."
diff --combined config/routes.rb
index 11f86e000e7094dbaf200e3664813c95d7ca115a,7e27ee19ef70714f526111597df042e94c2f4bb7..1c67eaa6eb7dde8a115b84920a0b5d45b4348ee1
@@@ -98,13 -98,13 +98,14 @@@ ActionController::Routing::Routes.draw 
    map.connect '/offline', :controller => 'site', :action => 'offline'
    map.connect '/key', :controller => 'site', :action => 'key'
    map.connect '/user/new', :controller => 'user', :action => 'new'
 +  map.connect '/user/terms', :controller => 'user', :action => 'terms'
    map.connect '/user/save', :controller => 'user', :action => 'save'
    map.connect '/user/confirm', :controller => 'user', :action => 'confirm'
    map.connect '/user/confirm-email', :controller => 'user', :action => 'confirm_email'
    map.connect '/user/go_public', :controller => 'user', :action => 'go_public'
    map.connect '/user/reset-password', :controller => 'user', :action => 'reset_password'
    map.connect '/user/forgot-password', :controller => 'user', :action => 'lost_password'
+   map.connect '/user/suspended', :controller => 'user', :action => 'suspended'
  
    map.connect '/index.html', :controller => 'site', :action => 'index'
    map.connect '/edit.html', :controller => 'site', :action => 'edit'
    map.connect '/user/:display_name/diary/:id/hide', :controller => 'diary_entry', :action => 'hide', :id => /\d+/
    map.connect '/user/:display_name/diary/:id/hidecomment/:comment', :controller => 'diary_entry', :action => 'hidecomment', :id => /\d+/, :comment => /\d+/
    map.connect '/user/:display_name/account', :controller => 'user', :action => 'account'
-   map.connect '/user/:display_name/activate', :controller => 'user', :action => 'activate'
-   map.connect '/user/:display_name/deactivate', :controller => 'user', :action => 'deactivate'
-   map.connect '/user/:display_name/hide', :controller => 'user', :action => 'hide'
-   map.connect '/user/:display_name/unhide', :controller => 'user', :action => 'unhide'
+   map.connect '/user/:display_name/set_status', :controller => 'user', :action => 'set_status'
    map.connect '/user/:display_name/delete', :controller => 'user', :action => 'delete'
    map.connect '/diary/new', :controller => 'diary_entry', :action => 'new'
    map.connect '/diary', :controller => 'diary_entry', :action => 'list'
    map.connect '/diary/:language', :controller => 'diary_entry', :action => 'list'
    map.connect '/diary/:language/rss', :controller => 'diary_entry', :action => 'rss'
  
-   
+   # user lists
+   map.connect '/users', :controller => 'user', :action => 'list'
+   map.connect '/users/:status', :controller => 'user', :action => 'list'
    # test pages
    map.connect '/test/populate/:table/:from/:count', :controller => 'test', :action => 'populate'
    map.connect '/test/populate/:table/:count', :controller => 'test', :action => 'populate', :from => 1
diff --combined lib/osm.rb
index b5f3cb1aa4fa92aea0c32133c865c27b90615c00,46b90447781b23938a6cc3820403381c8ba30272..c92655aeea207b48393645e6bb9aeb4bcf478c4c
@@@ -7,6 -7,7 +7,7 @@@ module OS
    require 'xml/libxml'
    require 'digest/md5'
    require 'RMagick'
+   require 'nokogiri'
  
    # The base class for API Errors.
    class APIError < RuntimeError
      end
    end
  
 -  def self.IPLocation(ip_address)
 +  def self.IPToCountry(ip_address)
      Timeout::timeout(4) do
        ipinfo = Quova::IpInfo.new(ip_address)
  
            country = "GB" if country == "UK"
          end
        end
 +      
 +      return country.upcase
 +    end
  
 -      country = Country.find_by_code(country.upcase)
 +    return nil
 +  rescue Exception
 +    return nil
 +  end
 +
 +  def self.IPLocation(ip_address)
 +    code = OSM.IPToCountry(ip_address)
 +
 +    unless code.nil?
 +      country = Country.find_by_code(code)
  
        return { :minlon => country.min_lon, :minlat => country.min_lat, :maxlon => country.max_lon, :maxlat => country.max_lat }
      end
  
 -    return nil
 -  rescue Exception
      return nil
    end
  
      return "#{tilesql} AND #{prefix}latitude BETWEEN #{minlat} AND #{maxlat} AND #{prefix}longitude BETWEEN #{minlon} AND #{maxlon}"
    end
  
+   # Return a spam score for a chunk of text
+   def self.spam_score(text)
+     link_count = 0
+     link_size = 0
+     doc = Nokogiri::HTML(text)
+     if doc.content.length > 0
+       doc.xpath("//a").each do |link|
+         link_count += 1
+         link_size += link.content.length
+       end
+       link_proportion = link_size.to_f / doc.content.length.to_f
+     else
+       link_proportion = 0
+     end
+     return [link_proportion - 0.2, 0.0].max * 200 + link_count * 20
+   end
++
 +  def self.legal_text_for_country(country_code)
 +    file_name = File.join(RAILS_ROOT, "config", "legales", country_code.to_s + ".yml")
 +    file_name = File.join(RAILS_ROOT, "config", "legales", APP_CONFIG['default_legale'] + ".yml") unless File.exist? file_name
 +    YAML::load_file(file_name)
 +  end
  end
index 69bac58b5261ddd2ef93ada9d251a1a943e10aed,73f0fb788b1e4391c614c6ed4f78d28b702e2b79..2590c2d5f655a3d456bb320c7447643884e9b83a
@@@ -562,47 -562,27 +562,68 @@@ hr 
    color: gray;
  }
  
+ /* Rules for the user list */
+ #user_list {
+   width: 100%;
+   font-size: small;
+ }
+ #user_list tr {
+   vertical-align: center;
+ }
+ #user_list p {
+   margin-top: 0px;
+   margin-bottom: 0px;
+ }
+ #user_list_actions {
+   float: right;
+   margin-top: 10px;
+ }
 +/* Rules for the account confirmation page */
 +
 +div#contributorTerms {
 +  border: 1px solid black;
 +  padding: 4px;
 +  overflow: auto;
 +  width: 80%;
 +  height: 60%;
 +}
 +
 +div#contributorTerms p#first {
 +  margin-top: 0px;
 +}
 +
 +div#contributorTerms p#last {
 +  margin-bottom: 0px;
 +}
 +
 +div#contributorTerms ol {
 +  margin-bottom: 0px;
 +}
 +
 +div#contributorTerms img {
 +  display: block;
 +  margin-left: auto;
 +  margin-right: auto;
 +  margin-top: 10%;
 +}
 +
 +form#termsForm {
 +  width: 80%;
 +}
 +
 +form#termsForm div#buttons {
 +  float: right;
 +}
 +
 +form#termsForm input#agree {
 +  margin-left: 50px;
 +}
 +
  /* Rules for the account settings page */
  
  #accountForm td {