]> git.openstreetmap.org Git - rails.git/blob - app/controllers/user_controller.rb
Return the body of a note comment as a rich text object
[rails.git] / app / controllers / user_controller.rb
1 class UserController < ApplicationController
2   layout :choose_layout
3
4   skip_before_filter :verify_authenticity_token, :only => [:api_read, :api_details, :api_gpx_files]
5   before_filter :disable_terms_redirect, :only => [:terms, :save, :logout, :api_details]
6   before_filter :authorize, :only => [:api_details, :api_gpx_files]
7   before_filter :authorize_web, :except => [:api_read, :api_details, :api_gpx_files]
8   before_filter :set_locale, :except => [:api_read, :api_details, :api_gpx_files]
9   before_filter :require_user, :only => [:account, :go_public, :make_friend, :remove_friend]
10   before_filter :check_database_readable, :except => [:login, :api_read, :api_details, :api_gpx_files]
11   before_filter :check_database_writable, :only => [:new, :account, :confirm, :confirm_email, :lost_password, :reset_password, :go_public, :make_friend, :remove_friend]
12   before_filter :check_api_readable, :only => [:api_read, :api_details, :api_gpx_files]
13   before_filter :require_allow_read_prefs, :only => [:api_details]
14   before_filter :require_allow_read_gpx, :only => [:api_gpx_files]
15   before_filter :require_cookies, :only => [:login, :confirm]
16   before_filter :require_administrator, :only => [:set_status, :delete, :list]
17   around_filter :api_call_handle_error, :only => [:api_read, :api_details, :api_gpx_files]
18   before_filter :lookup_user_by_id, :only => [:api_read]
19   before_filter :lookup_user_by_name, :only => [:set_status, :delete]
20
21   cache_sweeper :user_sweeper, :only => [:account, :set_status, :delete]
22
23   def terms
24     @legale = params[:legale] || OSM.IPToCountry(request.remote_ip) || DEFAULT_LEGALE
25     @text = OSM.legal_text_for_country(@legale)
26
27     if request.xhr?
28       render :partial => "terms"
29     elsif using_open_id?
30       # The redirect from the OpenID provider reenters here
31       # again and we need to pass the parameters through to
32       # the open_id_authentication function
33       @user = session.delete(:new_user)
34
35       openid_verify(nil, @user) do |user|
36       end
37
38       if @user.openid_url.nil? or @user.invalid?
39         render :action => 'new'
40       else
41         render :action => 'terms'
42       end
43     elsif params[:user] and Acl.no_account_creation(request.remote_ip, params[:user][:email].split("@").last)
44       render :action => 'blocked'
45     else
46       session[:referer] = params[:referer]
47
48       @title = t 'user.terms.title'
49       @user = User.new(params[:user]) if params[:user]
50
51       if params[:user] and params[:user][:openid_url] and @user.pass_crypt.empty?
52         # We are creating an account with OpenID and no password
53         # was specified so create a random one
54         @user.pass_crypt = SecureRandom.base64(16) 
55         @user.pass_crypt_confirmation = @user.pass_crypt 
56       end
57
58       if @user
59         if @user.invalid?
60           if @user.new_record?
61             # Something is wrong with a new user, so rerender the form
62             render :action => :new
63           else
64             # Error in existing user, so go to account settings
65             flash[:errors] = @user.errors
66             redirect_to :action => :account, :display_name => @user.display_name
67           end
68         elsif @user.terms_agreed?
69           # Already agreed to terms, so just show settings
70           redirect_to :action => :account, :display_name => @user.display_name
71         elsif params[:user] and params[:user][:openid_url] and not params[:user][:openid_url].empty?
72           # Verify OpenID before moving on
73           session[:new_user] = @user
74           openid_verify(params[:user][:openid_url], @user)
75         end
76       else
77         # Not logged in, so redirect to the login page
78         redirect_to :action => :login, :referer => request.fullpath
79       end
80     end
81   end
82
83   def save
84     @title = t 'user.new.title'
85
86     if params[:decline]
87       if @user
88         @user.terms_seen = true
89
90         if @user.save
91           flash[:notice] = t 'user.new.terms declined', :url => t('user.new.terms declined url')
92         end
93
94         if params[:referer]
95           redirect_to params[:referer]
96         else
97           redirect_to :action => :account, :display_name => @user.display_name
98         end
99       else
100         redirect_to t('user.terms.declined')
101       end
102     elsif @user
103       if !@user.terms_agreed?
104         @user.consider_pd = params[:user][:consider_pd]
105         @user.terms_agreed = Time.now.getutc
106         @user.terms_seen = true
107         if @user.save
108           flash[:notice] = t 'user.new.terms accepted'
109         end
110       end
111
112       if params[:referer]
113         redirect_to params[:referer]
114       else
115         redirect_to :action => :account, :display_name => @user.display_name
116       end
117     elsif Acl.no_account_creation(request.remote_ip, params[:user][:email].split("@").last)
118       render :action => 'blocked'
119     else
120       @user = User.new(params[:user])
121
122       @user.status = "pending"
123       @user.data_public = true
124       @user.description = "" if @user.description.nil?
125       @user.creation_ip = request.remote_ip
126       @user.languages = request.user_preferred_languages
127       @user.terms_agreed = Time.now.getutc
128       @user.terms_seen = true
129       @user.openid_url = nil if @user.openid_url and @user.openid_url.empty?
130       
131       if @user.save
132         flash[:piwik_goal] = PIWIK_SIGNUP_GOAL if defined?(PIWIK_SIGNUP_GOAL)
133         flash[:notice] = t 'user.new.flash create success message', :email => @user.email
134         Notifier.signup_confirm(@user, @user.tokens.create(:referer => session.delete(:referer))).deliver
135         session[:token] = @user.tokens.create.token
136         redirect_to :action => 'login', :referer => params[:referer]
137       else
138         render :action => 'new', :referer => params[:referer]
139       end
140     end
141   end
142
143   def account
144     @title = t 'user.account.title'
145     @tokens = @user.oauth_tokens.authorized
146
147     if params[:user] and params[:user][:display_name] and params[:user][:description]
148       if params[:user][:openid_url] and
149          params[:user][:openid_url].length > 0 and
150          params[:user][:openid_url] != @user.openid_url
151         # If the OpenID has changed, we want to check that it is a
152         # valid OpenID and one the user has control over before saving
153         # it as a password equivalent for the user.
154         session[:new_user_settings] = params
155         openid_verify(params[:user][:openid_url], @user)
156       else
157         update_user(@user, params)
158       end
159     elsif using_open_id?
160       # The redirect from the OpenID provider reenters here
161       # again and we need to pass the parameters through to
162       # the open_id_authentication function
163       settings = session.delete(:new_user_settings)
164       openid_verify(nil, @user) do |user|
165         update_user(user, settings)
166       end
167     end
168   end
169
170   def go_public
171     @user.data_public = true
172     @user.save
173     flash[:notice] = t 'user.go_public.flash success'
174     redirect_to :controller => 'user', :action => 'account', :display_name => @user.display_name
175   end
176
177   def lost_password
178     @title = t 'user.lost_password.title'
179
180     if params[:user] and params[:user][:email]
181       user = User.visible.find_by_email(params[:user][:email])
182
183       if user.nil?
184         users = User.visible.where("LOWER(email) = LOWER(?)", params[:user][:email])
185
186         if users.count == 1
187           user = users.first
188         end
189       end
190
191       if user
192         token = user.tokens.create
193         Notifier.lost_password(user, token).deliver
194         flash[:notice] = t 'user.lost_password.notice email on way'
195         redirect_to :action => 'login'
196       else
197         flash.now[:error] = t 'user.lost_password.notice email cannot find'
198       end
199     end
200   end
201
202   def reset_password
203     @title = t 'user.reset_password.title'
204
205     if params[:token]
206       token = UserToken.find_by_token(params[:token])
207
208       if token
209         @user = token.user
210
211         if params[:user]
212           @user.pass_crypt = params[:user][:pass_crypt]
213           @user.pass_crypt_confirmation = params[:user][:pass_crypt_confirmation]
214           @user.status = "active" if @user.status == "pending"
215           @user.email_valid = true
216
217           if @user.save
218             token.destroy
219             flash[:notice] = t 'user.reset_password.flash changed'
220             redirect_to :action => 'login'
221           end
222         end
223       else
224         flash[:error] = t 'user.reset_password.flash token bad'
225         redirect_to :action => 'lost_password'
226       end
227     end
228   end
229
230   def new
231     @title = t 'user.new.title'
232     @referer = params[:referer] || session[:referer]
233
234     if @user
235       # The user is logged in already, so don't show them the signup
236       # page, instead send them to the home page
237       if @referer
238         redirect_to @referer
239       else
240         redirect_to :controller => 'site', :action => 'index'
241       end
242     elsif params.key?(:openid)
243       @user = User.new(:email => params[:email],
244                        :email_confirmation => params[:email],
245                        :display_name => params[:nickname],
246                        :openid_url => params[:openid])
247
248       flash.now[:notice] = t 'user.new.openid association'
249     elsif Acl.no_account_creation(request.remote_ip)
250       render :action => 'blocked'
251     end
252   end
253
254   def login
255     if params[:username] or using_open_id?
256       session[:remember_me] ||= params[:remember_me]
257       session[:referer] ||= params[:referer]
258
259       if using_open_id?
260         openid_authentication(params[:openid_url])
261       else
262         password_authentication(params[:username], params[:password])
263       end
264     end
265   end
266
267   def logout
268     @title = t 'user.logout.title'
269
270     if params[:session] == request.session_options[:id]
271       if session[:token]
272         token = UserToken.find_by_token(session[:token])
273         if token
274           token.destroy
275         end
276         session.delete(:token)
277       end
278       session.delete(:user)
279       session_expires_automatically
280       if params[:referer]
281         redirect_to params[:referer]
282       else
283         redirect_to :controller => 'site', :action => 'index'
284       end
285     end
286   end
287
288   def confirm
289     if request.post?
290       if token = UserToken.find_by_token(params[:confirm_string])
291         if token.user.active?
292           flash[:error] = t('user.confirm.already active')
293           redirect_to :action => 'login'
294         else
295           user = token.user
296           user.status = "active"
297           user.email_valid = true
298           user.save!
299           referer = token.referer
300           token.destroy
301
302           if session[:token] 
303             token = UserToken.find_by_token(session[:token])
304             session.delete(:token)
305           else
306             token = nil
307           end
308
309           if token.nil? or token.user != user
310             flash[:notice] = t('user.confirm.success')
311             redirect_to :action => :login, :referer => referer
312           else
313             token.destroy
314
315             session[:user] = user.id
316             cookies.permanent["_osm_username"] = user.display_name
317
318             if referer.nil?
319               flash[:notice] = t('user.confirm.success') + "<br /><br />" + t('user.confirm.before you start')
320               redirect_to :action => :account, :display_name => user.display_name
321             else
322               flash[:notice] = t('user.confirm.success')
323               redirect_to referer
324             end
325           end
326         end
327       else
328         user = User.find_by_display_name(params[:display_name])
329
330         if user and user.active?
331           flash[:error] = t('user.confirm.already active')
332         elsif user
333           flash[:error] = t('user.confirm.unknown token') + t('user.confirm.reconfirm', :reconfirm => url_for(:action => 'confirm_resend', :display_name => params[:display_name]))
334         else
335           flash[:error] = t('user.confirm.unknown token')
336         end
337
338         redirect_to :action => 'login'
339       end
340     end
341   end
342
343   def confirm_resend
344     if user = User.find_by_display_name(params[:display_name])
345       Notifier.signup_confirm(user, user.tokens.create).deliver
346       flash[:notice] = t 'user.confirm_resend.success', :email => user.email
347     else
348       flash[:notice] = t 'user.confirm_resend.failure', :name => params[:display_name]
349     end
350
351     redirect_to :action => 'login'
352   end
353
354   def confirm_email
355     if request.post?
356       token = UserToken.find_by_token(params[:confirm_string])
357       if token and token.user.new_email?
358         @user = token.user
359         @user.email = @user.new_email
360         @user.new_email = nil
361         @user.email_valid = true
362         if @user.save
363           flash[:notice] = t 'user.confirm_email.success'
364         else
365           flash[:errors] = @user.errors
366         end
367         token.destroy
368         session[:user] = @user.id
369         cookies.permanent["_osm_username"] = @user.display_name
370         redirect_to :action => 'account', :display_name => @user.display_name
371       else
372         flash[:error] = t 'user.confirm_email.failure'
373         redirect_to :action => 'account', :display_name => @user.display_name
374       end
375     end
376   end
377
378   def api_read
379     render :nothing => true, :status => :gone unless @this_user.visible?
380   end
381
382   def api_details
383     @this_user = @user
384     render :action => :api_read
385   end
386
387   def api_gpx_files
388     doc = OSM::API.new.get_xml_doc
389     @user.traces.each do |trace|
390       doc.root << trace.to_xml_node() if trace.public? or trace.user == @user
391     end
392     render :text => doc.to_s, :content_type => "text/xml"
393   end
394
395   def view
396     @this_user = User.find_by_display_name(params[:display_name])
397
398     if @this_user and
399        (@this_user.visible? or (@user and @user.administrator?))
400       @title = @this_user.display_name
401     else
402       render_unknown_user params[:display_name]
403     end
404   end
405
406   def make_friend
407     @new_friend = User.find_by_display_name(params[:display_name])
408
409     if @new_friend
410       if request.post?
411         friend = Friend.new
412         friend.user_id = @user.id
413         friend.friend_user_id = @new_friend.id
414         unless @user.is_friends_with?(@new_friend)
415           if friend.save
416             flash[:notice] = t 'user.make_friend.success', :name => @new_friend.display_name
417             Notifier.friend_notification(friend).deliver
418           else
419             friend.add_error(t('user.make_friend.failed', :name => @new_friend.display_name))
420           end
421         else
422           flash[:warning] = t 'user.make_friend.already_a_friend', :name => @new_friend.display_name
423         end
424
425         if params[:referer]
426           redirect_to params[:referer]
427         else
428           redirect_to :controller => 'user', :action => 'view'
429         end
430       end
431     else
432       render_unknown_user params[:display_name]
433     end
434   end
435
436   def remove_friend
437     @friend = User.find_by_display_name(params[:display_name])
438
439     if @friend
440       if request.post?
441         if @user.is_friends_with?(@friend)
442           Friend.delete_all "user_id = #{@user.id} AND friend_user_id = #{@friend.id}"
443           flash[:notice] = t 'user.remove_friend.success', :name => @friend.display_name
444         else
445           flash[:error] = t 'user.remove_friend.not_a_friend', :name => @friend.display_name
446         end
447
448         if params[:referer]
449           redirect_to params[:referer]
450         else
451           redirect_to :controller => 'user', :action => 'view'
452         end
453       end
454     else
455       render_unknown_user params[:display_name]
456     end
457   end
458
459   ##
460   # sets a user's status
461   def set_status
462     @this_user.status = params[:status]
463     @this_user.save
464     redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
465   end
466
467   ##
468   # delete a user, marking them as deleted and removing personal data
469   def delete
470     @this_user.delete
471     redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
472   end
473
474   ##
475   # display a list of users matching specified criteria
476   def list
477     if request.post?
478       ids = params[:user].keys.collect { |id| id.to_i }
479
480       User.update_all("status = 'confirmed'", :id => ids) if params[:confirm]
481       User.update_all("status = 'deleted'", :id => ids) if params[:hide]
482
483       redirect_to url_for(:status => params[:status], :ip => params[:ip], :page => params[:page])
484     else
485       conditions = Hash.new
486       conditions[:status] = params[:status] if params[:status]
487       conditions[:creation_ip] = params[:ip] if params[:ip]
488
489       @user_pages, @users = paginate(:users,
490                                      :conditions => conditions,
491                                      :order => :id,
492                                      :per_page => 50)
493     end
494   end
495
496 private
497
498   ##
499   # handle password authentication
500   def password_authentication(username, password)
501     if user = User.authenticate(:username => username, :password => password)
502       successful_login(user)
503     elsif user = User.authenticate(:username => username, :password => password, :pending => true)
504       failed_login t('user.login.account not active', :reconfirm => url_for(:action => 'confirm_resend', :display_name => user.display_name))
505     elsif User.authenticate(:username => username, :password => password, :suspended => true)
506       failed_login t('user.login.account is suspended', :webmaster => "mailto:webmaster@openstreetmap.org")
507     else
508       failed_login t('user.login.auth failure')
509     end
510   end
511
512   ##
513   # handle OpenID authentication
514   def openid_authentication(openid_url)
515     # If we don't appear to have a user for this URL then ask the
516     # provider for some extra information to help with signup
517     if openid_url and User.find_by_openid_url(openid_url)
518       required = nil
519     else
520       required = [:nickname, :email, "http://axschema.org/namePerson/friendly", "http://axschema.org/contact/email"]
521     end
522
523     # Start the authentication
524     authenticate_with_open_id(openid_expand_url(openid_url), :method => :get, :required => required) do |result, identity_url, sreg, ax|
525       if result.successful?
526         # We need to use the openid url passed back from the OpenID provider
527         # rather than the one supplied by the user, as these can be different.
528         #
529         # For example, you can simply enter yahoo.com in the login box rather
530         # than a user specific url. Only once it comes back from the provider
531         # provider do we know the unique address for the user.
532         if user = User.find_by_openid_url(identity_url)
533           case user.status
534             when "pending" then
535               failed_login t('user.login.account not active', :reconfirm => url_for(:action => 'confirm_resend', :display_name => user.display_name))
536             when "active", "confirmed" then
537               successful_login(user)
538             when "suspended" then
539               failed_login t('user.login.account is suspended', :webmaster => "mailto:webmaster@openstreetmap.org")
540             else
541               failed_login t('user.login.auth failure')
542           end
543         else
544           # Guard against not getting any extension data
545           sreg = Hash.new if sreg.nil?
546           ax = Hash.new if ax.nil?
547
548           # We don't have a user registered to this OpenID, so redirect
549           # to the create account page with username and email filled
550           # in if they have been given by the OpenID provider through
551           # the simple registration protocol.
552           nickname = sreg["nickname"] || ax["http://axschema.org/namePerson/friendly"].first
553           email = sreg["email"] || ax["http://axschema.org/contact/email"].first
554           redirect_to :controller => 'user', :action => 'new', :nickname => nickname, :email => email, :openid => identity_url
555         end
556       elsif result.missing?
557         failed_login t('user.login.openid missing provider')
558       elsif result.invalid?
559         failed_login t('user.login.openid invalid')
560       else
561         failed_login t('user.login.auth failure')
562       end
563     end
564   end
565
566   ##
567   # verify an OpenID URL
568   def openid_verify(openid_url, user)
569     user.openid_url = openid_url
570
571     authenticate_with_open_id(openid_expand_url(openid_url), :method => :get) do |result, identity_url|
572       if result.successful?
573         # We need to use the openid url passed back from the OpenID provider
574         # rather than the one supplied by the user, as these can be different.
575         #
576         # For example, you can simply enter yahoo.com in the login box rather
577         # than a user specific url. Only once it comes back from the provider
578         # provider do we know the unique address for the user.
579         user.openid_url = identity_url
580         yield user
581       elsif result.missing?
582         flash.now[:error] = t 'user.login.openid missing provider'
583       elsif result.invalid?
584         flash.now[:error] = t 'user.login.openid invalid'
585       else
586         flash.now[:error] = t 'user.login.auth failure'
587       end
588     end
589   end
590
591   ##
592   # special case some common OpenID providers by applying heuristics to
593   # try and come up with the correct URL based on what the user entered
594   def openid_expand_url(openid_url)
595     if openid_url.nil?
596       return nil
597     elsif openid_url.match(/(.*)gmail.com(\/?)$/) or openid_url.match(/(.*)googlemail.com(\/?)$/)
598       # Special case gmail.com as it is potentially a popular OpenID
599       # provider and, unlike yahoo.com, where it works automatically, Google
600       # have hidden their OpenID endpoint somewhere obscure this making it
601       # somewhat less user friendly.
602       return 'https://www.google.com/accounts/o8/id'
603     else
604       return openid_url
605     end
606   end  
607
608   ##
609   # process a successful login
610   def successful_login(user)
611     cookies.permanent["_osm_username"] = user.display_name
612
613     session[:user] = user.id
614     session_expires_after 28.days if session[:remember_me]
615
616     target = session[:referer] || url_for(:controller => :site, :action => :index)
617
618     # The user is logged in, so decide where to send them:
619     #
620     # - If they haven't seen the contributor terms, send them there.
621     # - If they have a block on them, show them that.
622     # - If they were referred to the login, send them back there.
623     # - Otherwise, send them to the home page.
624     if REQUIRE_TERMS_SEEN and not user.terms_seen
625       redirect_to :controller => :user, :action => :terms, :referer => target
626     elsif user.blocked_on_view
627       redirect_to user.blocked_on_view, :referer => target
628     else
629       redirect_to target
630     end
631
632     session.delete(:remember_me)
633     session.delete(:referer)
634   end
635
636   ##
637   # process a failed login
638   def failed_login(message)
639     flash[:error] = message
640
641     redirect_to :action => 'login', :referer =>  session[:referer]
642
643     session.delete(:remember_me)
644     session.delete(:referer)
645   end
646
647   ##
648   # update a user's details
649   def update_user(user, params)
650     user.display_name = params[:user][:display_name]
651     user.new_email = params[:user][:new_email]
652
653     if params[:user][:pass_crypt].length > 0 or params[:user][:pass_crypt_confirmation].length > 0
654       user.pass_crypt = params[:user][:pass_crypt]
655       user.pass_crypt_confirmation = params[:user][:pass_crypt_confirmation]
656     end
657
658     if params[:user][:description] != user.description
659       user.description = params[:user][:description]
660       user.description_format = "markdown"
661     end
662
663     user.languages = params[:user][:languages].split(",")
664
665     case params[:image_action]
666     when "new" then user.image = params[:user][:image]
667     when "delete" then user.image = nil
668     end
669
670     user.home_lat = params[:user][:home_lat]
671     user.home_lon = params[:user][:home_lon]
672
673     if params[:user][:preferred_editor] == "default"
674       user.preferred_editor = nil
675     else
676       user.preferred_editor = params[:user][:preferred_editor]
677     end
678
679     user.openid_url = nil if params[:user][:openid_url].blank?
680
681     if user.save
682       set_locale
683
684       cookies.permanent["_osm_username"] = user.display_name
685
686       if user.new_email.blank?
687         flash.now[:notice] = t 'user.account.flash update success'
688       else
689         user.email = user.new_email
690
691         if user.valid?
692           flash.now[:notice] = t 'user.account.flash update success confirm needed'
693
694           begin
695             Notifier.email_confirm(user, user.tokens.create).deliver
696           rescue
697             # Ignore errors sending email
698           end
699         else
700           @user.errors.set(:new_email, @user.errors.get(:email))
701           @user.errors.set(:email, [])
702         end
703
704         user.reset_email!
705       end
706     end
707   end
708
709   ##
710   # require that the user is a administrator, or fill out a helpful error message
711   # and return them to the user page.
712   def require_administrator
713     if @user and not @user.administrator?
714       flash[:error] = t('user.filter.not_an_administrator')
715
716       if params[:display_name]
717         redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
718       else
719         redirect_to :controller => 'user', :action => 'login', :referer => request.fullpath
720       end
721     elsif not @user
722       redirect_to :controller => 'user', :action => 'login', :referer => request.fullpath
723     end
724   end
725
726   ##
727   # ensure that there is a "this_user" instance variable
728   def lookup_user_by_id
729     @this_user = User.find(params[:id])
730   end
731
732   ##
733   # ensure that there is a "this_user" instance variable
734   def lookup_user_by_name
735     @this_user = User.find_by_display_name(params[:display_name])
736   rescue ActiveRecord::RecordNotFound
737     redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name] unless @this_user
738   end
739
740   ##
741   # Choose the layout to use. See
742   # https://rails.lighthouseapp.com/projects/8994/tickets/5371-layout-with-onlyexcept-options-makes-other-actions-render-without-layouts
743   def choose_layout
744     oauth_url = url_for(:controller => :oauth, :action => :authorize, :only_path => true)
745
746     if [ 'api_details' ].include? action_name
747       nil
748     elsif params[:referer] and URI.parse(params[:referer]).path == oauth_url
749       'slim'
750     else
751       'site'
752     end
753   end
754
755   ##
756   #
757   def disable_terms_redirect
758     # this is necessary otherwise going to the user terms page, when 
759     # having not agreed already would cause an infinite redirect loop.
760     # it's .now so that this doesn't propagate to other pages.
761     flash.now[:skip_terms] = true
762   end
763 end