]> git.openstreetmap.org Git - rails.git/commitdiff
Merge branch 'master' into openid
authorTom Hughes <tom@compton.nu>
Thu, 10 Feb 2011 14:10:19 +0000 (14:10 +0000)
committerTom Hughes <tom@compton.nu>
Thu, 10 Feb 2011 14:10:19 +0000 (14:10 +0000)
Conflicts:
app/controllers/user_controller.rb
app/views/user/login.html.erb
public/stylesheets/common.css

1  2 
app/controllers/user_controller.rb
app/models/user.rb
app/views/user/account.html.erb
app/views/user/login.html.erb
config/locales/en.yml
config/locales/is.yml
public/stylesheets/common.css
test/test_helper.rb

index d456c1353ef9f45a230927d09a804f61f0ba1cd4,6935af3bca5f3508fa7d2222a2f142975ebdc5b8..1193ec9106b151887fb7518bd727184bd19276ef
@@@ -26,53 -26,22 +26,53 @@@ class UserController < ApplicationContr
        render :update do |page|
          page.replace_html "contributorTerms", :partial => "terms", :locals => { :has_decline => params[:has_decline] }
        end
 +    elsif using_open_id?
 +      # The redirect from the OpenID provider reenters here
 +      # again and we need to pass the parameters through to
 +      # the open_id_authentication function
 +      @user = session.delete(:new_user)
 +
 +      openid_verify(nil, @user) do |user|
 +      end
 +
 +      if @user.openid_url.nil? or @user.invalid?
 +        render :action => 'new'
 +      else
 +        render :action => 'terms'
 +      end
      else
 +      session[:referer] = params[:referer]
 +
        @title = t 'user.terms.title'
        @user = User.new(params[:user]) if params[:user]
  
 +      if params[:user] and params[:user][:openid_url] and @user.pass_crypt.empty?
 +        # We are creating an account with OpenID and no password
 +        # was specified so create a random one
 +        @user.pass_crypt = ActiveSupport::SecureRandom.base64(16) 
 +        @user.pass_crypt_confirmation = @user.pass_crypt 
 +      end
 +
        if @user
          if @user.invalid?
            if @user.new_record?
 +            # Something is wrong with a new user, so rerender the form
              render :action => :new
            else
 +            # Error in existing user, so go to account settings
              flash[:errors] = @user.errors
              redirect_to :action => :account, :display_name => @user.display_name
            end
          elsif @user.terms_agreed?
 +          # Already agreed to terms, so just show settings
            redirect_to :action => :account, :display_name => @user.display_name
 +        elsif params[:user] and params[:user][:openid_url]
 +          # Verify OpenID before moving on
 +          session[:new_user] = @user
 +          openid_verify(params[:user][:openid_url], @user)
          end
        else
 +        # Not logged in, so redirect to the login page
          redirect_to :action => :login, :referer => request.request_uri
        end
      end
  
        if @user.save
          flash[:notice] = t 'user.new.flash create success message', :email => @user.email
 -        Notifier.deliver_signup_confirm(@user, @user.tokens.create(:referer => params[:referer]))
 +        Notifier.deliver_signup_confirm(@user, @user.tokens.create(:referer => session.delete(:referer)))
+         session[:token] = @user.tokens.create.token
          redirect_to :action => 'login'
        else
          render :action => 'new'
        @user.home_lat = params[:user][:home_lat]
        @user.home_lon = params[:user][:home_lon]
  
 -      if @user.save
 -        set_locale
+       if params[:user][:preferred_editor] == "default"
+         @user.preferred_editor = nil
+       else
+         @user.preferred_editor = params[:user][:preferred_editor]
+       end
 +      @user.openid_url = nil if params[:user][:openid_url].empty?
  
 -        if @user.new_email.nil? or @user.new_email.empty?
 -          flash[:notice] = t 'user.account.flash update success'
 -        else
 -          flash[:notice] = t 'user.account.flash update success confirm needed'
 -
 -          begin
 -            Notifier.deliver_email_confirm(@user, @user.tokens.create)
 -          rescue
 -            # Ignore errors sending email
 -          end
 -        end
 -
 -        redirect_to :action => "account", :display_name => @user.display_name
 +      if params[:user][:openid_url].length > 0 and
 +         params[:user][:openid_url] != @user.openid_url
 +        # If the OpenID has changed, we want to check that it is a
 +        # valid OpenID and one the user has control over before saving
 +        # it as a password equivalent for the user.
 +        session[:new_user] = @user
 +        openid_verify(params[:user][:openid_url], @user)
 +      else
 +        update_user(@user)
 +      end
 +    elsif using_open_id?
 +      # The redirect from the OpenID provider reenters here
 +      # again and we need to pass the parameters through to
 +      # the open_id_authentication function
 +      @user = session.delete(:new_user)
 +      openid_verify(nil, @user) do |user|
 +        update_user(user)
        end
      else
        if flash[:errors]
  
    def new
      @title = t 'user.new.title'
 -
 -    # The user is logged in already, so don't show them the signup
 -    # page, instead send them to the home page
 -    redirect_to :controller => 'site', :action => 'index' if session[:user]
 +    @referer = params[:referer] || session[:referer]
 +
 +    if session[:user]
 +      # The user is logged in already, so don't show them the signup
 +      # page, instead send them to the home page
 +      redirect_to :controller => 'site', :action => 'index'
 +    elsif not params['openid'].nil?
 +      flash.now[:notice] = t 'user.new.openid association'
 +    end
    end
  
    def login
 -    @title = t 'user.login.title'
 +    if params[:username] or using_open_id?
 +      session[:remember_me] ||= params[:remember_me]
 +      session[:referer] ||= params[:referer]
  
 -    if params[:user]
 -      email_or_display_name = params[:user][:email]
 -      pass = params[:user][:password]
 -      user = User.authenticate(:username => email_or_display_name, :password => pass)
 -
 -      if user
 -        session[:user] = user.id
 -        session_expires_after 1.month if params[:remember_me]
 -
 -        # The user is logged in, if the referer param exists, redirect
 -        # them to that unless they've also got a block on them, in
 -        # which case redirect them to the block so they can clear it.
 -        if user.blocked_on_view
 -          redirect_to user.blocked_on_view, :referer => params[:referer]
 -        elsif params[:referer]
 -          redirect_to params[:referer]
 -        else
 -          redirect_to :controller => 'site', :action => 'index'
 -        end
 -      elsif user = User.authenticate(:username => email_or_display_name, :password => pass, :pending => true)
 -        flash.now[:error] = t 'user.login.account not active', :reconfirm => url_for(:action => 'confirm_resend', :display_name => user.display_name)
 -      elsif User.authenticate(:username => email_or_display_name, :password => pass, :suspended => true)
 -        webmaster = link_to t('user.login.webmaster'), "mailto:webmaster@openstreetmap.org"
 -        flash.now[:error] = t 'user.login.account suspended', :webmaster => webmaster
 +      if using_open_id?
 +        openid_authentication(params[:openid_url])
        else
 -        flash.now[:error] = t 'user.login.auth failure'
 +        password_authentication(params[:username], params[:password])
        end
      elsif flash[:notice].nil?
        flash.now[:notice] =  t 'user.login.notice'
            user.save!
            referer = token.referer
            token.destroy
-           session[:user] = user.id
  
-           unless referer.nil?
+           if session[:token] 
+             token = UserToken.find_by_token(session[:token])
+             session.delete(:token)
+           else
+             token = nil
+           end
+           if token.nil? or token.user != user
              flash[:notice] = t('user.confirm.success')
-             redirect_to referer
+             redirect_to :action => :login, :referer => referer
            else
-             flash[:notice] = t('user.confirm.success') + "<br /><br />" + t('user.confirm.before you start')
-             redirect_to :action => 'account', :display_name => user.display_name
+             token.destroy
+             session[:user] = user.id
+             if referer.nil?
+               flash[:notice] = t('user.confirm.success') + "<br /><br />" + t('user.confirm.before you start')
+               redirect_to :action => :account, :display_name => user.display_name
+             else
+               flash[:notice] = t('user.confirm.success')
+               redirect_to referer
+             end
            end
          end
        else
  
  private
  
 +  ##
 +  # handle password authentication
 +  def password_authentication(username, password)
 +    if user = User.authenticate(:username => username, :password => password)
 +      successful_login(user)
 +    elsif user = User.authenticate(:username => username, :password => password, :pending => true)
 +      failed_login t('user.login.account not active', :reconfirm => url_for(:action => 'confirm_resend', :display_name => user.display_name))
 +    elsif User.authenticate(:username => username, :password => password, :suspended => true)
 +      webmaster = link_to t('user.login.webmaster'), "mailto:webmaster@openstreetmap.org"
 +      failed_login t('user.login.account suspended', :webmaster => webmaster)
 +    else
 +      failed_login t('user.login.auth failure')
 +    end
 +  end
 +
 +  ##
 +  # handle OpenID authentication
 +  def openid_authentication(openid_url)
 +    # If we don't appear to have a user for this URL then ask the
 +    # provider for some extra information to help with signup
 +    if openid_url and User.find_by_openid_url(openid_url)
 +      required = nil
 +    else
 +      required = [:nickname, :email, "http://axschema.org/namePerson/friendly", "http://axschema.org/contact/email"]
 +    end
 +
 +    # Start the authentication
 +    authenticate_with_open_id(openid_expand_url(openid_url), :required => required) do |result, identity_url, sreg, ax|
 +      if result.successful?
 +        # We need to use the openid url passed back from the OpenID provider
 +        # rather than the one supplied by the user, as these can be different.
 +        #
 +        # For example, you can simply enter yahoo.com in the login box rather
 +        # than a user specific url. Only once it comes back from the provider
 +        # provider do we know the unique address for the user.
 +        if user = User.find_by_openid_url(identity_url)
 +          case user.status
 +            when "pending" then
 +              failed_login t('user.login.account not active')
 +            when "active", "confirmed" then
 +              successful_login(user)
 +            when "suspended" then
 +              webmaster = link_to t('user.login.webmaster'), "mailto:webmaster@openstreetmap.org"
 +              failed_login t('user.login.account suspended', :webmaster => webmaster)
 +            else
 +              failed_login t('user.login.auth failure')
 +          end
 +        else
 +          # We don't have a user registered to this OpenID, so redirect
 +          # to the create account page with username and email filled
 +          # in if they have been given by the OpenID provider through
 +          # the simple registration protocol.
 +          nickname = sreg["nickname"] || ax["http://axschema.org/namePerson/friendly"]
 +          email = sreg["email"] || ax["http://axschema.org/contact/email"]
 +          redirect_to :controller => 'user', :action => 'new', :nickname => nickname, :email => email, :openid => identity_url
 +        end
 +      elsif result.missing?
 +        failed_login t('user.login.openid missing provider')
 +      elsif result.invalid?
 +        failed_login t('user.login.openid invalid')
 +      else
 +        failed_login t('user.login.auth failure')
 +      end
 +    end
 +  end
 +
 +  ##
 +  # verify an OpenID URL
 +  def openid_verify(openid_url, user)
 +    user.openid_url = openid_url
 +
 +    authenticate_with_open_id(openid_expand_url(openid_url)) do |result, identity_url|
 +      if result.successful?
 +        # We need to use the openid url passed back from the OpenID provider
 +        # rather than the one supplied by the user, as these can be different.
 +        #
 +        # For example, you can simply enter yahoo.com in the login box rather
 +        # than a user specific url. Only once it comes back from the provider
 +        # provider do we know the unique address for the user.
 +        user.openid_url = identity_url
 +        yield user
 +      elsif result.missing?
 +        flash.now[:error] = t 'user.login.openid missing provider'
 +      elsif result.invalid?
 +        flash.now[:error] = t 'user.login.openid invalid'
 +      else
 +        flash.now[:error] = t 'user.login.auth failure'
 +      end
 +    end
 +  end
 +
 +  ##
 +  # special case some common OpenID providers by applying heuristics to
 +  # try and come up with the correct URL based on what the user entered
 +  def openid_expand_url(openid_url)
 +    if openid_url.nil?
 +      return nil
 +    elsif openid_url.match(/(.*)gmail.com(\/?)$/) or openid_url.match(/(.*)googlemail.com(\/?)$/)
 +      # Special case gmail.com as it is potentially a popular OpenID
 +      # provider and, unlike yahoo.com, where it works automatically, Google
 +      # have hidden their OpenID endpoint somewhere obscure this making it
 +      # somewhat less user friendly.
 +      return 'https://www.google.com/accounts/o8/id'
 +    else
 +      return openid_url
 +    end
 +  end  
 +
 +  ##
 +  # process a successful login
 +  def successful_login(user)
 +    session[:user] = user.id
 +
 +    session_expires_after 1.month if session[:remember_me]
 +
 +    if user.blocked_on_view
 +      redirect_to user.blocked_on_view, :referer => params[:referer]
 +    elsif session[:referer]
 +      redirect_to session[:referer]
 +    else
 +      redirect_to :controller => 'site', :action => 'index'
 +    end
 +
 +    session.delete(:remember_me)
 +    session.delete(:referer)
 +  end
 +
 +  ##
 +  # process a failed login
 +  def failed_login(message)
 +    flash[:error] = message
 +
 +    redirect_to :action => 'login', :referer =>  session[:referer]
 +
 +    session.delete(:remember_me)
 +    session.delete(:referer)
 +  end
 +
 +  ##
 +  # update a user's details
 +  def update_user(user)
 +    if user.save
 +      set_locale
 +
 +      if user.new_email.nil? or user.new_email.empty?
 +        flash.now[:notice] = t 'user.account.flash update success'
 +      else
 +        flash.now[:notice] = t 'user.account.flash update success confirm needed'
 +
 +        begin
 +          Notifier.deliver_email_confirm(user, user.tokens.create)
 +        rescue
 +          # Ignore errors sending email
 +        end
 +      end
 +    end
 +  end
 +
    ##
    # require that the user is a administrator, or fill out a helpful error message
    # and return them to the user page.
diff --combined app/models/user.rb
index d5b75d073a2d14582a106c725f93d907b4e4d045,d2535bbd48f2c41e585abe4135176f8434e19765..79af5d71f66c1836e69213aa812c7af32b66fcb2
@@@ -10,7 -10,7 +10,7 @@@ class User < ActiveRecord::Bas
    has_many :friends, :include => :befriendee, :conditions => "users.status IN ('active', 'confirmed')"
    has_many :tokens, :class_name => "UserToken"
    has_many :preferences, :class_name => "UserPreference"
-   has_many :changesets
+   has_many :changesets, :order => 'created_at DESC'
  
    has_many :client_applications
    has_many :oauth_tokens, :class_name => "OauthToken", :order => "authorized_at desc", :include => [:client_application]
@@@ -23,7 -23,6 +23,7 @@@
    validates_confirmation_of :pass_crypt#, :message => ' must match the confirmation password'
    validates_uniqueness_of :display_name, :allow_nil => true
    validates_uniqueness_of :email
 +  validates_uniqueness_of :openid_url, :allow_nil => true
    validates_length_of :pass_crypt, :within => 8..255
    validates_length_of :display_name, :within => 3..255, :allow_nil => true
    validates_email_format_of :email
@@@ -34,6 -33,7 +34,7 @@@
    validates_numericality_of :home_lat, :allow_nil => true
    validates_numericality_of :home_lon, :allow_nil => true
    validates_numericality_of :home_zoom, :only_integer => true, :allow_nil => true
+   validates_inclusion_of :preferred_editor, :in => Editors::ALL_EDITORS, :allow_nil => true
  
    before_save :encrypt_password
  
      (languages & array.collect { |i| i.to_s }).first
    end
  
-   def nearby(radius = 50, num = 10)
+   def nearby(radius = NEARBY_RADIUS, num = NEARBY_USERS)
      if self.home_lon and self.home_lat 
        gc = OSM::GreatCircle.new(self.home_lat, self.home_lon)
        bounds = gc.bounds(radius)
  
      return score.to_i
    end
+   ##
+   # return an oauth access token for a specified application
+   def access_token(application_key)
+     return ClientApplication.find_by_key(application_key).access_token_for_user(self)
+   end
  end
index 3bfd7577ef09617ea3efdeb71adb9462f8da3a01,7047b5b8213e47cfa92b7b4047a6696b5f038b4a..161f8f4f9882290bbfed9bcdba59f20075e9c993
      <td><%= f.password_field :pass_crypt_confirmation, {:value => '', :size => 30, :maxlength => 255, :autocomplete => :off} %></td>
    </tr>
  
 +  <tr>
 +    <td class="fieldName" ><%= t 'user.account.openid.openid' %></td>
 +    <td><%= f.text_field :openid_url, {:class => "openid_url"} %> <span class="minorNote">(<a href="<%= t 'user.account.openid.link' %>" target="_new"><%= t 'user.account.openid.link text' %></a>)</span></td>
 +  </tr>
 +
    <tr>
      <td class="fieldName" valign="top"><%= t 'user.account.public editing.heading' %></td>
      <td>
      <td><%= f.text_field :languages %></td>
    </tr>
  
+   <tr>
+     <td class="fieldName" valign="top"><%= t 'user.account.preferred editor' %></td>
+     <td><%= f.select :preferred_editor, [[t("editor.default", :name => t("editor.#{DEFAULT_EDITOR}.name")), 'default']] + Editors::ALL_EDITORS.collect { |e| [t("editor.#{e}.description"), e] } %></td>
+   </tr>
    <tr>
      <td class="fieldName" valign="top">
        <%= t 'user.account.image' %>
index 72c768713e224aad75141638ad584e5420bad815,66a2fab9980bc0748da6df9e801466a2b46f3c79..4515f412ddec810c95c296d0413573a173fb06a0
@@@ -1,85 -1,25 +1,80 @@@
- <h1><%= t 'user.login.heading' %></h1>
+ <div id="login_wrapper">
 +
- <p><%= t 'user.login.please login', :create_user_link => link_to(t('user.login.create_account'), :controller => 'user', :action => 'new', :referer => params[:referer]) %></p>
+   <div id="login_login">
+     <h1><%= t 'user.login.heading' %></h1>
  
- <% form_tag({ :action => "login" }, { :id => "login_form" }) do %>
-   <%= hidden_field_tag('referer', h(params[:referer])) %>
 -    <p><%= t 'user.login.already have' %></p>
 -
 -    <% form_tag :action => 'login' do %>
++    <% form_tag({ :action => "login" }, { :id => "login_form" }) do %>
+       <%= hidden_field_tag('referer', h(params[:referer])) %>
 +
-   <div style="position: relative;">
-     <div class="loginBox">
-       <h3><%= t 'user.login.username_heading' %></h3>
-       <table>
-         <tr>
-           <td class="fieldName"><label for="user_email"><%= t 'user.login.email or username' %></label></td>
-           <td><%= text_field_tag("username", "", { :size => 28, :maxlength => 255, :tabindex => 1 }) %></td>
-         </tr>
-         <tr>
-           <td class="fieldName"><label for="user_password"><%= t 'user.login.password' %></label></td>
-           <td><%= password_field_tag("password", "", { :size => 28, :maxlength => 255, :tabindex => 2 }) %></td>
-         </tr>
++      <p><%= t 'user.login.with username' %></p>
++
+       <table id="loginForm">
+         <tr><td class="fieldName"><%= t 'user.login.email or username' %></td><td><%= text_field('user', 'email',{:value => "", :size => 28, :maxlength => 255, :tabindex => 1}) %></td></tr>
+         <tr><td class="fieldName"><%= t 'user.login.password' %></td><td><%= password_field('user', 'password',{:value => "", :size => 28, :maxlength => 255, :tabindex => 2}) %> <span class="minorNote">(<%= link_to t('user.login.lost password link'), :controller => 'user', :action => 'lost_password' %>)</span></td></tr>
+         <tr><td class="fieldName"><label for="remember_me"><%= t 'user.login.remember' %></label></td><td><%= check_box_tag "remember_me", "yes", false, :tabindex => 3 %></td></tr>
+       </table>
+       <%= submit_tag t('user.login.login_button'), :tabindex => 3 %>
++
++      <p><%= t 'user.login.with openid' %></p>
++
++      <table id="login_openid_buttons">
 +        <tr>
-           <td></td>
-           <td><span class="minorNote">(<%= link_to t('user.login.lost password link'), :controller => 'user', :action => 'lost_password' %>)</span></td>
++          <td>
++            <%=
++              link_to_function(image_tag("openid_large.png", :alt => t("user.login.openid_providers.openid.title")), nil, :title => t("user.login.openid_providers.openid.title")) do |page|
++                page[:login_form][:openid_url].value = "http://"
++                page[:login_openid_buttons].hide
++                page[:login_openid_url].show
++                page[:login_openid_submit].show
++              end
++            %>
++          </td>
++          <td><%= openid_button "yahoo", "me.yahoo.com" %></td>
++          <td><%= openid_button "google", "gmail.com" %></td>
 +        </tr>
 +        <tr>
-           <td class="fieldName"><label for="remember_me"><%= t 'user.login.remember' %></label></td>
-           <td><%= check_box_tag "remember_me", "yes", false, :tabindex => 3 %></td>
++          <td><%= openid_button "myopenid", "myopenid.com" %></td>
++          <td><%= openid_button "wordpress", "wordpress.com" %></td>
++          <td><%= openid_button "myspace", "myspace.com" %></td>
 +        </tr>
 +      </table>
 +
-       <%= submit_tag t('user.login.login_button'), :tabindex => 4 %>
-     </div>
-     <div style="float:left; width: 20px; padding: 10px;">
-     </div>
-     <div class="loginBox">
-       <h3><%= t 'user.login.openid_heading' %></h3>
-       <div id="openid_buttons">
-         <%=
-           link_to_function(image_tag("openid_large.png", :alt => t("user.login.openid_providers.openid.title")), nil, :title => t("user.login.openid_providers.openid.title")) do |page|
-             page[:login_form][:openid_url].value = "http://"
-             page[:openid_buttons].hide
-             page[:openid_url].show
-             page[:openid_url_hint].show
-             page[:openid_submit].show
-           end
-         %>
-         <%= openid_button "yahoo", "me.yahoo.com" %>
-         <%= openid_button "google", "gmail.com" %>
-         <%= openid_button "myopenid", "myopenid.com" %>
-         <%= openid_button "wordpress", "wordpress.com" %>
-         <%= openid_button "myspace", "myspace.com" %>
-       </div>
 +      <table>
-         <tr id="openid_url">
++        <tr id="login_openid_url">
 +          <td class="fieldName nowrap">
 +            <%= t 'user.login.openid', :logo => openid_logo %>
 +          </td>
-           <td><%= text_field_tag("openid_url", "", { :size => 28, :maxlength => 255, :tabindex => 3, :class => "openid_url" }) %></td>
-         </tr>
-         <tr id="openid_url_hint">
-           <td></td>
 +          <td>
++            <%= text_field_tag("openid_url", "", { :size => 28, :maxlength => 255, :tabindex => 3, :class => "openid_url" }) %>
 +            <span class="minorNote">(<a href="<%= t 'user.account.openid.link' %>" target="_new"><%= t 'user.account.openid.link text' %></a>)</span>
 +          </td>
 +        </tr>
 +        <tr>
 +          <td class="fieldName nowrap" id="remember_me_label"><label for="remember_me"><%= t 'user.login.remember' %></label></td>
 +          <td width="100%"><%= check_box_tag "remember_me", "yes", false, :tabindex => 5 %></td>
 +        </tr>
 +      </table>
 +
-       <%= submit_tag t('user.login.login_button'), :tabindex => 6, :id => "openid_submit" %>
-     </div>
++      <%= submit_tag t('user.login.login_button'), :tabindex => 6, :id => "login_openid_submit" %>
+     <% end %>
++
+     <br clear="both">
    </div>
- <% end %>
++
+   <div id="login_signup">
+     <h2><%= t 'user.login.new to osm' %></h2>
+     <p><%= t 'user.login.to make changes' %></p>
+     <p><%= t 'user.login.create account minute' %></p></p>
+     <p><%= button_to t('user.login.register now'), :action => :new, :referer => params[:referer] %></p>
++
+     <br clear="both">
+   </div>
++
+ </div>
 +
 +<%=
 +  update_page_tag do |page|
-     page[:openid_url].hide
-     page[:openid_url_hint].hide
-     page[:openid_submit].hide
++    page[:login_openid_url].hide
++    page[:login_openid_submit].hide
 +  end
 +%>
diff --combined config/locales/en.yml
index ebd53720b32b0852e2188cdb2d7b725c537df78d,497959a14043e15f1c1f4d0fc8fca49e6a854f22..1fa2831ead11a07eb64a181785e08bff883d8426
@@@ -79,6 -79,17 +79,17 @@@ en
      with_id: "{{id}}"
      with_version: "{{id}}, v{{version}}"
      with_name: "{{name}} ({{id}})"
+   editor:
+     default: "Default (currently {{name}})"
+     potlatch:
+       name: "Potlatch 1"
+       description: "Potlatch 1 (in-browser editor)"
+     potlatch2:
+       name: "Potlatch 2"
+       description: "Potlatch 2 (in-browser editor)"
+     remote:
+       name: "Remote Control"
+       description: "Remote Control (JOSM or Merkaartor)"
    browse:
      changeset:
        title: "Changeset"
      gps_traces_tooltip: Manage GPS traces
      user_diaries: User Diaries
      user_diaries_tooltip: View user diaries
+     edit_with: Edit with {{editor}}
      tag_line: The Free Wiki World Map
      intro_1: "OpenStreetMap is a free editable map of the whole world. It is made by people like you."
      intro_2: "OpenStreetMap allows you to view, edit and use geographical data in a collaborative way from anywhere on Earth."
      osm_read_only: "The OpenStreetMap database is currently in read-only mode while essential database maintenance work is carried out."
      donate: "Support OpenStreetMap by {{link}} to the Hardware Upgrade Fund."
      donate_link_text: donating
-     help_and_wiki: "{{help}} & {{wiki}}"
      help: Help
+     help_centre: Help Centre
      help_url: http://help.openstreetmap.org/
      help_title: Help site for the project
      wiki: Wiki
      wiki_url: http://wiki.openstreetmap.org/
      wiki_title: Wiki site for the project
+     documentation: Documentation
+     documentation_title: Documentation for the project
      copyright: "Copyright &amp; License"
-     news_blog: "News blog"
-     news_blog_tooltip: "News blog about OpenStreetMap, free geographical data, etc."
-     shop: Shop
-     shop_tooltip: Shop with branded OpenStreetMap merchandise
-     shop_url: http://wiki.openstreetmap.org/wiki/Merchandise
+     community_blogs: "Community Blogs"
+     community_blogs_title: "Blogs from members of the OpenStreetMap community"
+     foundation: Foundation
+     foundation_title: The OpenStreetMap Foundation
      license:
        alt: CC by-sa 2.0
        title: OpenStreetMap data is licensed under the Creative Commons Attribution-Share Alike 2.0 Generic License
          license_url: "http://creativecommons.org/licenses/by-sa/2.0/"
          project_name: "OpenStreetMap project"
          project_url: "http://openstreetmap.org"
+       remote_failed: "Editing failed - make sure JOSM or Merkaartor is loaded and the remote control option is enabled"
      edit:
        not_public: "You have not set your edits to be public."
        not_public_description: "You can no longer edit the map unless you do so. You can set your edits as public from your {{user_page}}."
        anon_edits_link_text: "Find out why this is the case."
        flash_player_required: 'You need a Flash player to use Potlatch, the OpenStreetMap Flash editor. You can <a href="http://www.adobe.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash">download Flash Player from Adobe.com</a>. <a href="http://wiki.openstreetmap.org/wiki/Editing">Several other options</a> are also available for editing OpenStreetMap.'
        potlatch_unsaved_changes: "You have unsaved changes. (To save in Potlatch, you should deselect the current way or point, if editing in live mode, or click save if you have a save button.)"
+       potlatch2_unsaved_changes: "You have unsaved changes. (To save in Potlatch 2, you should click save.)"
+       no_iframe_support: "Your browser doesn't support HTML iframes, which are necessary for this feature."
      sidebar:
        search_results: Search Results
        close: Close
        submit_text: "Go"
        search_help: "examples: 'Alkmaar', 'Regent Street, Cambridge', 'CB2 5AQ', or 'post offices near Lünen' <a href='http://wiki.openstreetmap.org/wiki/Search'>more examples...</a>"
      key:
-       map_key: "Map key"
+       map_key: "Map Key"
        map_key_tooltip: "Key for the map"
        table:
          entry:
      login:
        title: "Login"
        heading: "Login"
--      please login: "Please login or {{create_user_link}}."
--      create_account: "create an account"
        email or username: "Email Address or Username:"
        password: "Password:"
-       username_heading: "Login with username and password:"
-       openid_heading: "Login with OpenID:"
 +      openid: "{{logo}} OpenID:"
        remember: "Remember me:"
        lost password link: "Lost your password?"
        login_button: "Login"
 -      already have: Already have an OpenStreetMap account? Please login.
+       register now: Register now
++      with username: "Already have an OpenStreetMap account? Please login with your username and password:"
++      with openid: "Alternatively please use your OpenID to login:"
+       new to osm: New to OpenStreetMap?
+       to make changes: To make changes to the OpenStreetMap data, you must have an account.
+       create account minute: Create an account. It only takes a minute.
        account not active: "Sorry, your account is not active yet.<br />Please use the link in the account confirmation email to activate your account, or <a href=\"{{reconfirm}}\">request a new confirmation email</a>."
        account suspended: Sorry, your account has been suspended due to suspicious activity.<br />Please contact the {{webmaster}} if you wish to discuss this.
        webmaster: webmaster
        auth failure: "Sorry, could not log in with those details."
        notice: "<a href=\"http://www.osmfoundation.org/wiki/License/We_Are_Changing_The_License\">Find out more about OpenStreetMap's upcoming license change</a> (<a href=\"http://wiki.openstreetmap.org/wiki/ODbL/We_Are_Changing_The_License\">translations</a>) (<a href=\"http://wiki.openstreetmap.org/wiki/Talk:ODbL/Upcoming\">discussion</a>)"
 +      openid missing provider: "Sorry, could not contact your OpenID provider"
 +      openid invalid: "Sorry, your OpenID seems to be malformed"
 +      openid_logo_alt: "Log in with an OpenID"
 +      openid_providers:
 +        openid:
 +          title: Login with an OpenID URL
 +          alt: Login with an OpenID URL
 +        yahoo:
 +          title: Login with a Yahoo! OpenID
 +          alt: Login with a Yahoo! OpenID
 +        google:
 +          title: Login with a Google OpenID
 +          alt: Login with a Google OpenID
 +        myopenid:
 +          title: Login with a myOpenID OpenID
 +          alt: Login with a myOpenID OpenID
 +        wordpress:
 +          title: Login with a Wordpress.com OpenID
 +          alt: Login with a Wordpress.com OpenID
 +        myspace:
 +          title: Login with a MySpace OpenID
 +          alt: Login with a MySpace OpenID
      logout:
        title: "Logout"
        heading: "Logout from OpenStreetMap"
        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: "Display Name:"
        display name description: "Your publicly displayed username. You can change this later in the preferences."
 +      openid: "{{logo}} OpenID:"
        password: "Password:"
        confirm password: "Confirm Password:"
 +      use openid: "Alternatively, use {{logo}} OpenID to login"
 +      openid no password: "With OpenID a password is not required, but some extra tools or server may still need one."
 +      openid association: |
 +        <p>Your OpenID is not associated with a OpenStreetMap account yet.</p>
 +        <ul>
 +          <li>If you are new to OpenStreetMap, please create a new account using the form below.</li>
 +          <li>
 +            If you already have an account, you can login to your account
 +            using your username and password and then associate the account
 +            with your OpenID in your user settings.
 +          </li>
 +        </ul> 
        continue: Continue
        flash create success message: "Thanks for signing up. We've sent a confirmation note to {{email}} and as soon as you confirm your account you'll be able to get mapping.<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 accepted: "Thanks for accepting the new contributor terms!"
        add as friend: add as friend
        mapper since: "Mapper since:"
        ago: "({{time_in_words_ago}} ago)"
+       latest edit: "Latest edit {{ago}}:"
        email address: "Email address:"
        created from: "Created from:"
        status: "Status:"
        current email address: "Current Email Address:"
        new email address: "New Email Address:"
        email never displayed publicly: "(never displayed publicly)"
 +      openid:
 +        openid: "OpenID:"
 +        link: "http://wiki.openstreetmap.org/wiki/OpenID"
 +        link text: "what is this?"
        public editing:
          heading: "Public editing:"
          enabled: "Enabled. Not anonymous and can edit data."
          link text: "what is this?"
        profile description: "Profile Description:"
        preferred languages: "Preferred Languages:"
+       preferred editor: "Preferred Editor:"
        image: "Image:"
        new image: "Add an image"
        keep image: "Keep the current image"
diff --combined config/locales/is.yml
index fe7c8c05ba5968ed045fcaf718120a5cdf12b2c1,dd90f8986d6cc11a19ee4c1a173761080f3452ed..be2f08452827372e2a041eb3907a27a029f0b9fd
@@@ -601,13 -601,8 +601,8 @@@ is
      make_a_donation: 
        text: Fjárframlagssíða
        title: Hjálpaðu OpenStreetMap verkefninu með fjárframlagi
-     news_blog: Fréttablogg
-     news_blog_tooltip: Blogg um OpenStreetMap, frjáls kortagögn o.fl.
      osm_offline: OpenStreetMap gagnagrunnurinn er niðri vegna viðhalds.
      osm_read_only: Ekki er hægt að skrifa í OpenStreetMap gagnagrunninn í augnablikinu vegna viðhalds.
-     shop: Verslun
-     shop_tooltip: Verslun með vörum tengdum OpenStreetMap
-     shop_url: http://wiki.openstreetmap.org/index.php?title=Merchandise&uselang=is
      sign_up: búa til aðgang
      sign_up_tooltip: Búaðu til aðgang til að geta breytt kortinu
      tag_line: Frjálsa wiki heimskortið
            unclassified: Héraðsvegur
            unsurfaced: Óbundið slitlag
            wood: Náttúrulegur skógur
-         heading: Kortaskýringar fyrir þys {{zoom_level}}
      search: 
        search: Leita
        search_help: "dæmi: „Akureyri“, „Laugavegur, Reykjavík“ eða „post offices near Lünen“. Sjá einnig <a href='http://wiki.openstreetmap.org/index.php?uselang=is&title=Search'>leitarhjálpina</a>."
        current email address: "Núverandi netfang:"
        delete image: Eyða þessari mynd
        email never displayed publicly: (aldrei sýnt opinberlega)
 +      openid:
 +        link text: "hvað er openID?"
        flash update success: Stillingarnar þínar voru uppfærðar.
        flash update success confirm needed: Stillingarnar þínar voru uppfærðar. Póstur var sendur á netfangið þitt sem þú þarft að bregðast við til að netfangið þitt verði staðfest.
        home location: "Staðsetning:"
        update home location on click: Uppfæra staðsetninguna þegar ég smelli á kortið
      confirm: 
        button: Staðfesta
-       failure: Notandi hefur þegar verið staðfestur með þessum lykli.
        heading: Staðfesta notanda
        press confirm button: Hér getur þú staðfest að þú viljir búa til notanda..
        success: Notandinn þinn hefur verið staðfestur.
        remember: "Muna innskráninguna:"
        title: Innskrá
        webmaster: vefstjóra
 +      openid_heading: "Innskráning með OpenID:"
 +      username_heading: "Innskráning með OpenStreetMap aðgang:"
 +      openid_logo_alt: "Innskrá með OpenID"
 +      openid_providers:
 +        openid:
 +          title: Innskrá með OpenID slóð
 +          alt: Innskrá með OpenID slóð
 +        yahoo:
 +          title: Innsrká með Yahoo! OpenID
 +          alt: Innsrká með Yahoo! OpenID
 +        google:
 +          title: Innsrká með Google OpenID
 +          alt: Innsrká með Google OpenID
 +        myopenid:
 +          title: Innsrká með myOpenID OpenID
 +          alt: Innsrká með myOpenID OpenID
 +        wordpress:
 +          title: Innsrká með Wordpress.com OpenID
 +          alt: Innsrká með Wordpress.com OpenID
 +        myspace:
 +          title: Innsrká með MySpace OpenID
 +          alt: Innsrká með MySpace OpenID
      logout: 
        heading: Útskrá
        logout_button: Útskrá
        no_auto_account_create: Því miður getum við eki búið til reikning fyrir þig sjálfkrafa.
        not displayed publicly: Ekki sýnt opinberlega (sjá <a href="http://wiki.openstreetmap.org/index.php?uselang=is&title=Privacy_Policy" title="Meðferð persónuupplýsinga, þ.á.m. netfanga">meðferð persónuupplýsinga</a>)
        password: "Lykilorð:"
 +      openID associate: "Tengja OpenID við þennan aðgang"
 +      openID: "OpenID:"
 +      openID description: '(Valfrjálst) Ef þú ert með <a href="http://wiki.openstreetmap.org/wiki/openID">OpenID</a> getur þú tengt það við nýja aðganginn þinn.'
 +      openID nopassword: "Með OpenID þarft þú ekki að gefa upp lykilorð við innskráningu. Í stað þess notar þú OpenID."
 +      openID association: |
 +        Þetta OpenID er ekki tengt við neinn OpenStreetMap aðgang.
 +        <ul>
 +          <li>Ef þú ert ekki með OpenStreetMap aðgang getur þú búið til nýjan aðgang hér fyrir neðan.</li>
 +          <li>
 +            Ef þú ert þegar með aðgang skaltu innskrá þig með
 +            honum. Svo getur þú tengt OpenID við aðganginn þinn á
 +            stillingarsíðunni.
 +          </li>
 +        </ul> 
 +      signup: Nýskrá
        title: Nýskrá
      no_such_user: 
        body: Það er ekki til notandi með nafninu {{user}}. Kannski slóstu nafnið rangt inn eða fylgdir ógildum tengli.
index 36966f3bae46a07401a05d1f8323960991fc9402,c830f406b2b12e1798520745068bb9cf3a15a9ac..3051f52d16663b0f868efef0e54644f8073d4fe2
@@@ -34,20 -34,22 +34,22 @@@ hr 
  
  #left {
    position: absolute;
-   top: -8px;
-   min-width: 150px;
+   top: 0px;
+   min-width: 170px;
  }
  
  /* Rules for the OpenStreetMap logo in the top left corner */
  
  #logo {
-   width: 150px;
-   min-width: 150px;
-   padding: 10px;
-   margin: 10px;
+   width: 170px;
+   min-width: 170px;
+   padding: 5px;
+   margin: 5px;
    height: 150px;
    background: #fff;
    border: 1px solid #ccd;
+   border-radius: 5px;
+   -moz-border-radius: 5px;
  }
  
  #logo h1 {
  
  #intro {
    width: 170px;
-   margin: 10px;
+   padding: 5px;
+   margin: 5px;
    border: 1px solid #ccc;
    font-size: 11px;
+   border-radius: 5px;
+   -moz-border-radius: 5px;
  }
  
- #intro p { margin: 10px; }
+ #intro p { margin: 5px; }
  
  /*
   * Rules for alert boxes shown in the left sidebar when important
   */
  
  #alert {
-   width: 150px;
-   margin: 10px;
-   padding: 10px;
+   width: 170px;
+   margin: 5px;
+   padding: 5px;
    border: 1px solid #ccc;
    background: #d00;
    line-height: 1.2em;
    text-align: left;
    font-size: 14px;
+   border-radius: 5px;
+   -moz-border-radius: 5px;
  }
  
  /*
   */
  
  .notice {
-   width: 150px;
-   margin: 10px;
-   padding: 10px;
+   width: 170px;
+   margin: 5px;
+   padding: 5px;
    border: 1px solid #ccc;
    background: #ea0;
    line-height: 1.2em;
    text-align: left;
    font-size: 14px;
+   border-radius: 5px;
+   -moz-border-radius: 5px;
  }
  
  /* Rules for the menu displayed in the left sidebar */
  
  .left_menu {
-   width: 150px;
-   min-width: 150px;
-   margin: 10px;
-   padding: 10px;
+   width: 170px;
+   min-width: 170px;
+   margin: 5px;
+   padding: 5px;
    border: 1px solid #ccc;
    left: 0px;
    background: #ddd;
    text-align: Left;
    font-size: 14px;
    font-weight: bold;
+   border-radius: 5px;
+   -moz-border-radius: 5px;
  }
  
  .left_menu td {
  }
  
  .left_menu ul {
-   padding-left: 10px;
+   padding-left: 0px;
    margin: 0px;
+   list-style-type: none;
  }
  
  .left_menu li {
   */
  
  .optionalbox {
-   width: 150px;
-   min-width: 150px;
-   margin: 10px;
-   padding: 10px;
+   width: 170px;
+   min-width: 170px;
+   margin: 5px;
+   padding: 5px;
    border: 1px solid #ccc;
    left: 0px;
    line-height: 1.2em;
    text-align: left;
    font-size: 12px;
    background: #eee;
+   border-radius: 5px;
+   -moz-border-radius: 5px;
  }
  
  .optionalbox h1 {
  }
  
  #search_field input[type="text"] {
-   width: 116px;
+   width: 136px;
  }
  
  #search_field input[type="submit"] {
    width: 26px;
+   padding-left: 0px;
+   padding-right: 0px;
  }
  
  .search_help {
  /* Rules for donation request box */
  
  .donate {
-   width: 150px;
-   margin: 10px;
-   padding: 10px;
+   width: 170px;
+   margin: 5px;
+   padding: 5px;
    border: 1px solid #ccc;
    background: #cbeea7;
    line-height: 1.2em;
    text-align: center;
    font-size: 14px;
+   border-radius: 5px;
+   -moz-border-radius: 5px;
  }
  
  /* Rules for Creative Commons logo button */
    border: 1px solid #ccc;
    text-decoration: none;
    color: #333;
+   border-top-right-radius: 5px;
+   -moz-border-radius-topright: 5px;
+   border-top-left-radius: 5px;
+   -moz-border-radius-topleft: 5px;
  }
  
  #tabnav a:link.active, #tabnav a:visited.active
    text-align: right;
  }
  
+ /* Rules for edit menu */
+ #editmenu {
+   display: none;
+   z-index: 10000;
+   position: absolute;
+   background-color: #ffffff;
+   border: 1px solid black;
+ }
+ #editmenu ul {
+   margin-top: 10px;
+   margin-bottom: 10px;
+   padding-left: 10px;
+   padding-right: 10px;
+ }
+ #editmenu li {
+   list-style-type: none;
+ }
  /* Rules for attribution text under the main map shown on printouts */
  
  #attribution {
  }
  
  #user_list tr {
-   vertical-align: center;
+   vertical-align: middle;
  }
  
  #user_list p {
    margin-top: 10px;
  }
  
- /* Rules for the login form */
+ /* Rules for the login page */
  
- .loginBox {
-   float: left;
-   border-style: solid;
-   border-width: 1px;
-   padding-left: 10px;
-   padding-right: 10px;
-   padding-bottom: 10px;
+ #login_wrapper {
+   float: left; /* ensures the child divs are the same size, and only as wide as they need to be */
  }
  
- .loginBox table {
-   width: 100%;
+ #login_wrapper div {
+   margin: 5px;
+   padding: 15px;
+   border-radius: 15px;
+   -moz-border-radius: 15px;
  }
  
.loginBox img {
-   border: 0;
#login_wrapper input[type=submit] {
+   float: right;
  }
  
.loginBox #openid_buttons img {
-   vertical-align: middle;
#login_login {
+   background-color: #f0f0f0;
  }
  
.loginBox input[type="submit"] {
-   float: right;
#login_login h1 {
+   margin-top: 5px;
  }
  
- #openid_buttons {
-   margin-bottom: 20px;
++#login_openid_buttons img {
++  border: 0;
++}
++
+ #login_signup form.button-to div {
+   margin: 0px;
+   padding: 0px;
  }
  
  /* Rules for the account confirmation page */
@@@ -715,6 -757,8 +761,8 @@@ form#termsForm input#agree 
    padding: 7px;
    background-color: #fff0f0;
    margin-bottom: 20px;
+   border-radius: 5px;
+   -moz-border-radius: 5px;
  }
  
  #warning {
    padding: 7px;
    background-color: #fff6f0;
    margin-bottom: 20px;
+   border-radius: 5px;
+   -moz-border-radius: 5px;
  }
  
  #notice {
    padding: 7px;
    background-color: #f0fff0;
    margin-bottom: 20px;
+   border-radius: 5px;
+   -moz-border-radius: 5px;
  }
  
  /* Rules for highlighting fields with rails validation errors */
@@@ -790,11 -838,6 +842,11 @@@ input[type="submit"] 
    border: 1px solid black;
  }
  
-   background: url('../images/openid_input.png') repeat-y left;
 +input.openid_url { 
++  background: url('../images/openid_input.png') repeat-y left white;
 +  padding-left: 16px;
 +}
 +
  /* Rules for user images */
  
  img.user_image {
@@@ -837,10 -880,3 +889,10 @@@ abbr.geo 
  .table1 { 
    background: #fff;
  }
 +
 +/* Rules for OpenID logo */
 +
 +.openid_logo {
 +  vertical-align: text-bottom;
 +  border: 0;
 +}
diff --combined test/test_helper.rb
index 507f9e7fce5c96a762e96c9d1b4bd1d2af81218f,6a618c8d3e6073bf24e1f2c7bbf77fd61269a4ba..b91c0779f8a22f81504c9403aa707fc27c278438
@@@ -3,43 -3,6 +3,43 @@@ require File.expand_path(File.dirname(_
  require 'test_help'
  load 'composite_primary_keys/fixtures.rb'
  
 +# This monkey patch is to make tests where a rack module alters
 +# the response work with rails 2 - it can be dropped when we move
 +# to rails 3.
 +module ActionController
 +  module Integration
 +    class Session
 +      def process_with_capture(method, path, parameters = nil, headers = nil)
 +        status = process_without_capture(method, path, parameters, headers)
 +        @controller = ActionController::Base.last_controller
 +        @request = @controller.request
 +        @response.session = @controller.response.session
 +        @response.template = @controller.response.template
 +        @response.redirected_to = @response.location
 +        status
 +      end
 +
 +      alias_method_chain :process, :capture
 +    end
 +
 +    module ControllerCapture
 +      module ClassMethods
 +        mattr_accessor :last_controller
 +
 +        def clear_last_instantiation!
 +          self.last_controller = nil
 +        end
 +
 +        def new_with_capture(*args)
 +          controller = new_without_capture(*args)
 +          self.last_controller ||= controller
 +          controller
 +        end
 +      end
 +    end
 +  end
 +end
 +
  class ActiveSupport::TestCase
    # Transactional fixtures accelerate your tests by wrapping each test method
    # in a transaction that's rolled back on completion.  This ensures that the
      @request.env["HTTP_AUTHORIZATION"] = "Basic %s" % Base64.encode64("#{user}:#{pass}")
    end
  
+   def error_format(format)
+     @request.env["HTTP_X_ERROR_FORMAT"] = format
+   end
    def content(c)
      @request.env["RAW_POST_DATA"] = c.to_s
    end
    def assert_no_missing_translations(msg="")
      assert_select "span[class=translation_missing]", false, "Missing translation #{msg}"
    end
 +
 +  def openid_request(openid_request_uri)
 +    openid_response = Net::HTTP.get_response(URI.parse(openid_request_uri))
 +    openid_response_uri = URI(openid_response['Location'])
 +    openid_response_qs = Rack::Utils.parse_query(openid_response_uri.query)
 +
 +    return openid_response_qs
 +  end
 +
    
    # Add more helper methods to be used by all tests here...
  end