notification state only needed in openfate
[opensuse:openfate.git] / app / controllers / feature_controller.rb
1 class FeatureController < ApplicationController
2
3   include Leftbee::HTMLHelpers
4
5   before_filter :require_auth, :except => [:index, :vote_statistics, :votes_graph, :votes, :tags]
6   before_filter :set_products
7   before_filter :get_feature, :except => [:new, :create, :revert, :new_comment, :tags, :votes, :auto_complete_tag]
8   before_filter :load_tags, :except => [:new, :create, :auto_complete_tag]
9   before_filter :load_votes, :except => [:new, :create, :auto_complete_tag]
10   #skip_before_filter :verify_authenticity_token, :only => [:votes, :votes_graph, :vote_statistics, :tags, :get_editbox]
11   before_filter :require_xhr_post, :only => [:vote_up, :vote_neutral, :vote_down, :tag, :tags, :remove_tag]
12
13   # no layout for ajax requests
14   layout proc{ |c| c.request.xhr? ? false : "application" }
15
16   # Render a single feature
17   def index
18     if @feature.nil?
19       flash[:notice] = "Feature ##{params[:id]} does either not exist or you are not authorized to access it."
20       redirect_to :controller => "main", :action => "index" and return
21     end
22     
23     if !session[:user].isAuthenticated 
24       flash.now[:info] = "Please login or register to be able to edit or vote this feature."
25     end
26     @last_comment_id = @feature.last_visible_comment_id
27     @actors = @feature.actors
28     @added_comments = @feature.added_comments session[:user], @client
29
30     if (params[:contenttype] == 'text/xml')
31       render(:text => @feature.xml, :content_type => 'text/xml' )
32     elsif (params[:contenttype] == 'text/plain')
33       render(:text => @feature.render_txt, :content_type => 'text/plain' )
34     elsif (params[:contenttype] == 'text/print')
35       @css = 'feature-print.css'
36       render :template => 'feature/index', :layout => false
37     else
38       render :template => 'feature/index'
39     end
40   end
41   
42   
43   def votes
44     if @votes.nil? then
45       render :text => "<p>No voting data available.</p>"
46     elsif (@votes.class == Hash && @votes["status"] && @votes["status"] != "0")
47       render :text => "<p>There was an error: #{@votes["status"]} - #{@votes["message"]}</p>"
48     else
49       render :partial => "voting"
50     end
51   end
52
53
54   def vote_statistics
55     render :partial => "vote_statistics"
56   end
57
58
59   def votes_graph
60     send_data(Feature.graph(params[:id]), :disposition => 'inline', :type => 'image/png', :filename => "#{params[:id]}.png")
61   end
62
63
64   def vote_up
65     logger.info( "#{session[:user].uid} is voting up  #{params[:id]} " )
66     Feature.vote_up(params[:id], session[:user].uid)
67     Rails.cache.delete "votes_#{params[:id]}"
68     redirect_to :action => :votes, :id => params[:id]
69   end
70
71
72   def vote_neutral
73     logger.info( "#{session[:user].uid} is voting neutral  #{params[:id]} " )
74     Feature.vote_neutral(params[:id], session[:user].uid)
75     Rails.cache.delete "votes_#{params[:id]}"
76     redirect_to :action => :votes, :id => params[:id]
77   end
78   
79   
80   def vote_down
81     logger.info( "#{session[:user].uid} is voting down  #{params[:id]} " )
82     Feature.vote_down(params[:id], session[:user].uid)
83     Rails.cache.delete "votes_#{params[:id]}"
84     redirect_to :action => :votes, :id => params[:id]
85   end
86   
87   
88   def tags
89     if @tags.nil?
90       render :text => "<p>No tagging data availble.</p>"
91     elsif (@tags.class == Hash && !@tags["status"].nil? && @tags["status"] != "0")
92       render :text => "<p>There was an error: #{@tags["status"]} - #{@tags["message"]}</p>"
93     else
94       render :partial => "tagging"
95     end
96   end
97   
98   
99   def tag
100     if !params[:tag].blank?
101       logger.info( "#{session[:user].uid} is tagging #{params[:id]} with #{params[:tag]}" )
102       params[:tag].split(' ').each do |t|
103         Feature.tag(params[:id], t)
104       end
105       Rails.cache.delete "tags_#{params[:id]}"
106       Rails.cache.delete  "#{@client}_#{get_user_org}_tags"
107       load_tags
108     end
109     render :partial => "tagging"
110   end
111   
112   
113   def remove_tag
114     unless params[:tag].blank?
115       logger.info( "#{session[:user].uid} is removing tag #{params[:tag]} from #{params[:id]}" )
116       Feature.remove_tag(params[:id], params[:tag])
117       Rails.cache.delete "tags_#{params[:id]}"
118       Rails.cache.delete  "#{@client}_#{get_user_org}_tags"
119       load_tags
120     end
121     render :partial => "tagging"
122   end
123   
124
125   def auto_complete_tag
126     @thetags = get_all_tags.sort.collect(&:first).select { |t| /^#{params[:q]}/.match( t ) }
127     render :text => @thetags.join("\n")
128   end
129
130
131   def attachment
132   end
133
134
135   def edit_stakeholder
136     sb = richtextify(params[:newpartnerbenefit])
137     if( validate_richtext( sb ) )
138       if( sb == "<p></p>" ) then sb = nil end
139       params[:nda_date] = nil if params[:nda_date].blank?
140       params[:external_id] = nil if params[:external_id].blank?
141       if (@feature.set_stakeholder_data( CGI.escapeHTML(params[:external_id]), CGI.escapeHTML(params[:nda_date]), sb) )
142         session[:editedFeatures][params[:id]] = @feature.xml.to_s
143         flash[:info] = "Stakeholder data updated."
144       end
145     else
146       logger.debug "Validation failed: " + params[:stakeholder_benefit] + sb
147       flash[:error] = @validation_error
148       logger.debug @validation_error
149       @newpartnerbenefit_value = sb
150     end
151     redirect_to :action => :index, :id => @feature.id
152   end
153
154
155   def remove_product
156     if (@feature.products.size == 1)
157       flash.now[:warn] = "You cannot remove all products from a feature."
158       flash.now[:fade] = true      
159     elsif( @feature.delete_product( params[:productid] ) )
160       @scrollup = true
161       session[:editedFeatures][params[:id]] = @feature.xml.to_s
162       flash[:info] = "Product #{params[:productname]} has been removed."
163       flash[:fade] = true
164     end
165     redirect_to :action => :index, :id => @feature.id
166   end
167   
168
169   def add_product
170     if( @feature.add_product( params[:productid], params[:reqpriority] ) )
171       session[:editedFeatures][params[:id]] = @feature.xml.to_s
172       @scrollup = true
173       flash[:info] = "Your product has been added."
174       flash[:fade] = true
175     end
176     redirect_to :action => :index, :id => @feature.id
177   end
178
179   
180   def edit_product
181     begin
182       if( @feature.change_product( params[:productid], params[:status], session[:user].uid,
183             { :reqpriority => params[:reqpriority], :pmpriority => params[:pmpriority],
184               :prjmgrpriority => params[:prjmgrpriority], :duplicateid => params[:duplicateid],
185               :rejectreason => params[:rejectreason], :client => @client } ) )
186         session[:editedFeatures][params[:id]] = @feature.xml.to_s
187         flash[:info] = "Product '#{params[:productname]}' updated."
188       end
189     rescue => e
190       flash[:error] = "Updating '#{params[:productname]}' failed: #{e}"
191     end
192     redirect_to :action => :index, :id => @feature.id
193   end
194
195   
196   def new
197     if @newproducts.length == 0 then
198       flash[:error] = "There are currently no products open for new features."
199       redirect_to :controller => :main, :action => "index"
200       return
201     end
202   end
203
204   
205   def create
206     errors = Array.new
207     if !params[:title] || params[:title].empty? then
208       errors << "Please enter a title"
209     end
210     if !params[:description] || params[:description].empty? then
211       errors << "Please enter a description"
212     else
213       params[:description] = richtextify(params[:description])
214     end
215     if !params[:products] || params[:products].length == 0 then
216       errors << "Please select at least one product"
217     end
218     if !params[:priority] || params[:priority].empty? then
219       errors << "Please select at a priority"
220     end
221     
222     if( !validate_richtext( params[:description] ) ) then
223       errors << "The description is not valid richtext: " + @validation_error
224     end
225     
226     if params[:usecase] && !params[:usecase].empty? then
227       params[:usecase] = richtextify(params[:usecase])
228       if( !validate_richtext( params[:usecase] ) ) then
229         errors << "The usecase is not valid richtext: " + @validation_error
230       end
231     end
232     
233     if params[:testcase] && !params[:testcase].empty? then
234       params[:testcase] = richtextify(params[:testcase])
235       if( !validate_richtext( params[:testcase] ) ) then
236         errors << "The testcase is not valid richtext: " + @validation_error
237       end
238     end
239
240     if params[:partner_benefit] && !params[:partner_benefit].empty? then
241       params[:partner_benefit] = richtextify(params[:partner_benefit])
242       if( !validate_richtext( params[:partner_benefit] ) ) then
243         errors << "The partner benefit is not valid richtext: " + @validation_error
244       end
245     end
246     
247     if !errors.empty? then 
248       flash.now[:error] = "There were errors:<br/><ul>"
249       errors.each do |e|
250         flash.now[:error] << "<li>#{e}</li>"
251       end
252       flash.now[:error] << "</ul>"
253       render :action => :new
254       return
255     end
256     
257     begin
258       xml = %Q[
259         <feature xmlns:k="http://inttools.suse.de/sxkeeper/schema/keeper" k:schemarevision="12" >
260         <title>#{CGI.escapeHTML(params[:title])}</title>
261         <description><richtext>#{params[:description]}</richtext></description>
262         <partnercontext><organization>#{get_user_org()}</organization>]
263       if params[:external_id] && !params[:external_id].empty? then
264         xml += %Q[
265           <externalid>#{CGI.escapeHTML(params[:external_id])}</externalid>]
266       end
267       if params[:partner_benefit] && !params[:partner_benefit].empty? then
268         xml += %Q[
269           <partnerbenefit><richtext>#{params[:partner_benefit]}</richtext></partnerbenefit>]
270       end
271       if params[:nda_date] && !params[:nda_date].empty? then
272         xml += %Q[
273           <nda><expires>#{CGI.escapeHTML(params[:nda_date])}</expires></nda>]
274       end
275       xml += %Q[</partnercontext>]
276       params[:products].each do |p|
277         xml += %Q[
278           <productcontext>
279             <product>
280               <productid>#{p}</productid>
281             </product>
282             <status><unconfirmed/></status>
283             <priority>
284               <#{params[:priority]}/>
285               <owner><role>requester</role></owner>
286             </priority>
287           </productcontext>]
288       end
289       xml += create_requester_element get_user_org()
290       
291       if params[:testcase] && !params[:testcase].empty? then
292         xml += %Q[
293           <testcase><richtext>#{params[:testcase]}</richtext></testcase>]
294       end
295       if params[:usecase] && !params[:usecase].empty? then
296         xml += %Q[
297           <usecase><richtext>#{params[:usecase]}</richtext></usecase>]
298       end
299       
300       xml += "</feature>"
301       logger.debug xml
302       
303       f = Feature.new xml
304       response = f.save(session[:user].uid, @client)
305       if response.code == "201" then
306         xml = XML::Document.string(response.body)
307         id = xml.find_first("/k:docchange/k:id", ["k:http://inttools.suse.de/sxkeeper/schema/keeper"]).content
308         flash[:success] = "Your feature has been created with id #{id}."
309         if Hermes::get_user_notification_status(session[:user].uid) != 'true' && isOpenFate
310           flash[:success] += "<br/>Please subscribe at <u><a href='http://hermes.opensuse.org'>hermes</a></u> " +
311             "if you want to receive notifications on feature changes."
312         end
313         Feature.vote_up(id, session[:user].uid) if isOpenFate
314         redirect_to :action => :index, :id => id and return
315       else
316         raise Exception, response.body
317       end
318     rescue Exception => e
319       logger.error "Creating Feature ##{params[:id]} failed: \n#{extract_errormessage(e.to_s)}"
320       flash.now[:error] = "Creating Feature ##{params[:id]} failed: \n#{extract_errormessage(e.to_s)}"
321       ExceptionNotification::Notifier.deliver_exception_notification(e, self, request, {})
322     end
323     render :template => 'feature/new'
324   end
325   
326   
327   def add_comment
328     valid_http_methods(:post)
329     comment = richtextify(params[:comment].strip)
330     if( validate_richtext( comment ) && validate_content( comment ) &&
331           @feature.add_comment( comment, session[:user], params[:replyto] ) )
332       session[:editedFeatures][params[:id]] = @feature.xml.to_s
333       flash[:info] = "Your comment has been added."
334       redirect_to :action => :index, :id => @feature.id and return
335     else
336       @replyto = params[:replyto]
337       @oldcomment = comment
338       flash.now[:error] = "Adding comment failed: " + @validation_error
339     end
340     index
341   end
342
343
344   def edit_comment
345     valid_http_methods(:post)
346     comment = richtextify(params[:comment].strip)
347     if( validate_richtext( comment ) && validate_content( comment ) &&
348           @feature.edit_comment( comment, session[:user], params[:comment_id], @client ) )
349       session[:editedFeatures][params[:id]] = @feature.xml.to_s
350       flash[:info] = "Your comment has been changed."
351       redirect_to :action => :index, :id => @feature.id and return
352     else
353       @edit_comment_id = params[:comment_id]
354       @edit_comment = comment
355       flash.now[:error] = "Changing comment failed: " + @validation_error
356     end
357     index
358   end
359   
360   
361   def edit_title
362     logger.debug "Setting title: " + CGI.escapeHTML(params[:newtitle])
363     if (@feature.set_field( "title", CGI.escapeHTML(params[:newtitle]) ) )
364       session[:editedFeatures][params[:id]] = @feature.xml.to_s
365       flash[:info] = "The title was updated."
366       flash[:fade] = true
367     end
368     redirect_to :action => :index, :id => @feature.id
369   end
370   
371   
372   def edit_description
373     desc = richtextify(params[:newdescription])
374     if( validate_richtext( desc ) )
375       logger.debug "Setting description: " + params[:newdescription]
376       if (@feature.set_richtextfield("description", desc) )
377         session[:editedFeatures][params[:id]] = @feature.xml.to_s
378       end
379       @scrollup = true
380       flash[:info] = "The description was updated."
381       flash[:fade] = true
382     else
383       logger.debug "Validation failed: " + params[:newdescription] + desc
384       @newdescription_value = desc
385       @newdescription_error = @validation_error
386       logger.debug @newdescription_error
387       flash[:error] = @validation_error
388     end
389     redirect_to :action => :index, :id => @feature.id
390   end
391
392
393   def edit_references
394     references = richtextify(params[:newreferences])
395     if( validate_richtext( references ) )
396       logger.debug "Setting references: " + params[:newreferences]
397       if (@feature.set_richtextfield("references", references) )
398         session[:editedFeatures][params[:id]] = @feature.xml.to_s
399       end
400       flash[:info] = "The field references was updated."
401     else
402       logger.debug "Validation failed: " + params[:newreferences] + references
403       @newreferences_value = references
404       @newreferences_error = @validation_error
405       logger.debug @newreferences_error
406       flash[:error] = @validation_error
407     end
408     redirect_to :action => :index, :id => @feature.id
409   end
410   
411   
412   def edit_usecase
413     uc = richtextify(params[:newusecase])
414     if( validate_richtext( uc ) )
415       if( uc == "<p></p>" ) then uc = nil end
416       logger.debug "Setting usecase: #{uc}"
417       if (@feature.set_richtextfield("usecase", uc) ) 
418         session[:editedFeatures][params[:id]] = @feature.xml.to_s
419       end
420       @scrollup = true
421       flash[:info] = "The usecase was updated."
422       flash[:fade] = true
423     else
424       logger.debug "Validation failed: " + params[:newusecase] + uc
425       @newusecase_value = uc
426       @newusecase_error = @validation_error
427       logger.debug @newusecase_error
428       flash[:error] = @validation_error
429     end
430     redirect_to :action => :index, :id => @feature.id
431   end
432   
433   
434   def edit_testcase
435     tc = richtextify(params[:newtestcase])
436     if( validate_richtext( tc ) )
437       if( tc == "<p></p>" ) then tc = nil end
438       logger.debug "Setting testcase: #{tc}"
439       if (@feature.set_richtextfield("testcase", tc) )
440         session[:editedFeatures][params[:id]] = @feature.xml.to_s
441       end
442       @scrollup = true
443       flash[:info] = "The testcase was updated."
444       flash[:fade] = true
445     else
446       logger.debug "Validation failed: " + params[:newtestcase] + tc
447       @newtestcase_value = tc
448       @newtestcase_error = @validation_error
449       logger.debug @newtestcase_error
450       flash[:error] = @validation_error
451     end
452     redirect_to :action => :index, :id => @feature.id
453   end
454   
455   
456   def remove_actor
457     @feature.remove_actor(params[:role], {:userid => params[:uid], :email => params[:email], :fullname => params[:fullname]})
458     session[:editedFeatures][params[:id]] = @feature.xml.to_s
459     flash[:info] = "#{params[:uid]}#{params[:email]}#{params[:fullname]} has been removed with role #{params[:role]}."
460     redirect_to :action => :index, :id => @feature.id
461   end
462
463   def get_actor_from_cache ( uid, email, fullname )
464     if $usernames.has_key?( uid ) && !uid.blank?
465       $usernames[uid] 
466     elsif !email.blank? && $usernames.select { | k, v | v[:email] == email }.length == 1
467       $usernames.select { | k, v | v[:email] == email }.first.last
468     elsif !fullname.blank? && $usernames.select { | k,v |  v[:fullname] == fullname }.length == 1
469       $usernames.select { | k,v |  v[:fullname] == fullname }.first.last
470     end
471   end
472   private :get_actor_from_cache
473
474   def verify_actor ( uid, email, fullname )
475     user = get_actor_from_cache( uid, email, fullname )
476     if !user && (!uid.blank? || !email.blank? || !fullname.blank?)
477       # TODO query LDAP and throw exception if not existant
478       user = {:uid => uid, :email => email, :fullname => fullname}
479     end
480     return user
481   end
482   private :verify_actor
483   
484   def add_user
485     actor = verify_actor(params[:userid], params[:email], params[:fullname])
486     unless actor.blank?
487       if @feature.add_actor(params[:role], actor[:uid], actor[:email], actor[:fullname] )
488         session[:editedFeatures][params[:id]] = @feature.xml.to_s
489         flash[:info] = "Added user #{actor[:uid]} #{actor[:email]} #{actor[:fullname]} as #{params[:role]}."
490         if (isOpenFate)
491           notification = Hermes::get_user_notification_status(actor[:uid], actor[:email]) if !actor[:uid].blank?
492           case notification
493           when 'true':
494               ;
495           when 'false':
496               if params[:email].blank?
497               flash[:info] += "<br/>This user currently doesn't receive notifications."
498             else
499               flash[:info] += "<br/>Currently this User doesn't receive notifications."
500             end
501           else
502             flash[:info] += "<br/> Notification status of user cannot be determined."
503           end
504         end
505       else
506         flash[:info] = "Not adding user #{actor[:fullname]} (#{actor[:uid]}). "\
507           "User already in role #{params[:role]}"
508       end
509     else
510       flash[:info] = "Verification of user "\
511         "#{params[:fullname]} <#{params[:email]}> (#{params[:userid]}) failed: User doesn't exist"
512     end
513     redirect_to :action => :index, :id => @feature.id
514   end
515
516   def remove_relation
517     @scrollup = true
518     if @feature.remove_relation(params[:type], params[:target])
519       session[:editedFeatures][params[:id]] = @feature.xml.to_s
520       flash[:success] = "Relation '#{params[:type]}' removed."
521     else
522       flash[:error] = "Relation not found."
523     end
524     redirect_to :action => :index, :id => @feature.id
525   end
526   
527   def add_relation
528     if !$editablerelationtypes.map{|t| t.first}.include? params[:type]
529       flash[:error] = "Invalid relation type: #{params[:type]}"
530     elsif params[:title].blank?
531       flash[:error] = "Please enter a title for the relation."
532     elsif params[:target].blank?
533       flash[:error] = "Please enter a target for the relation."
534     else
535       @feature.add_relation CGI.escapeHTML(params[:title]), CGI.escapeHTML(params[:target]), params[:type]
536       session[:editedFeatures][params[:id]] = @feature.xml.to_s
537       flash[:success] = "Relation added."
538     end
539     redirect_to :action => :index, :id => @feature.id
540   end
541
542
543   def edit_relation
544     if !$editablerelationtypes.map{|t| t.first}.include? params[:type]
545       flash[:error] = "Invalid relation type: #{params[:type]}"
546     elsif params[:title].blank?
547       flash[:error] = "Please enter a title for the relation."
548     elsif params[:target].blank?
549       flash[:error] = "Please enter a target for the relation."
550     else
551       @feature.edit_relation params[:oldtype], params[:oldtarget],
552         CGI.escapeHTML(params[:title]), CGI.escapeHTML(params[:target]), params[:type]
553       session[:editedFeatures][params[:id]] = @feature.xml.to_s
554       flash[:success] = "Relation changed."
555     end
556     redirect_to :action => :index, :id => @feature.id
557   end
558   
559   
560   def save
561     if request.post?
562       begin
563         @feature.save( session[:user].uid, @client )
564         session[:editedFeatures].delete(params[:id])
565         flash[:success] = "Feature ##{params[:id]} saved."
566         if( Hermes::get_user_notification_status(session[:user].uid) != 'true' )
567           flash[:success] += "<br/>Please subscribe at <u><a href='http://hermes.opensuse.org'>hermes</a></u> " +
568             "if you want to receive notifications on feature changes."
569         end
570       rescue Exception => e
571         logger.error "Saving Feature ##{params[:id]} failed: \n#{extract_errormessage(e.to_s)}"
572         allowed_codes = [ '452', '409' ]
573         if (allowed_codes.include?(extract_errorcode(e.to_s)))
574           flash[:error] = "Saving Feature ##{params[:id]} failed: " + extract_errormessage(e.to_s)
575         else
576           ExceptionNotification::Notifier.deliver_exception_notification(e, self, request, {})
577           flash[:error] = "Saving Feature ##{params[:id]} failed due to an internal error. The development team has been notified."
578         end
579       end
580     end
581     if request.get?
582       flash[:error] = "You entered an malformed url!"
583     end
584     redirect_to :action => :index, :id => params[:id]
585   end
586   
587   
588   def revert
589     valid_http_methods(:post)
590     if session[:editedFeatures].has_key?(params[:id])
591       session[:editedFeatures].delete(params[:id])
592       flash[:notice] = "Your changes to feature ##{params[:id]} have been reverted."
593       flash[:fade] = true
594     end
595     redirect_to :action => :index, :id => params[:id]
596   end
597
598   
599   def changes
600     @original_feature = Feature.loadFeature( params[:id], session[:user], @client )
601     file1 = Tempfile.new 'test', "#{Rails.root}/tmp"
602     file1 << @original_feature.render_txt + "\n"
603     file1.flush
604     file1.close
605     file2 = Tempfile.new 'test', "#{Rails.root}/tmp"
606     file2 << @feature.render_txt  + "\n"
607     file2.flush
608     file2.close
609     @diff = `diff -U9999 -a --label 'Original feature' --label 'Changed feature' #{file1.path} #{file2.path}`
610   end
611   
612
613   private
614   
615   def require_xhr_post
616     if !request.post? || !request.xhr?
617       logger.error "Not a XHR POST request to #{request}"
618       flash[:error] = "You entered an malformed url!"
619       redirect_to :controller => "feature", :id => params[:id]
620     end
621   end
622   
623
624   def load_tags
625     @tags = @feature.tags if @feature
626   end
627
628
629   def load_votes
630     @votes = Rails.cache.fetch("votes_#{params[:id]}", :expires_in => 10.minutes) do
631       Feature.votes_for_object(params[:id], session[:user].uid)
632     end
633     unless @votes.nil? then
634       @voting_total = @votes["total"]
635       @voting_sum = @votes["sum"]
636       @voting_up = @votes["up"]
637       @voting_neutral = @votes["neutral"]
638       @voting_down = @votes["down"]
639       @voting_user= @votes["has_voted"]
640     end
641   end
642   
643   
644   def validate_params
645     if params.has_key?(:id) and /\A\d*\z/.match(params[:id]).nil? then
646       flash[:error] = "'#{params[:id]}' is not a valid id."
647       redirect_to :controller => "main", :action => :index
648       return
649     end
650     if params.has_key?(:replyto) and /\A\d*\z/.match(params[:replyto]).nil? then
651       flash[:error] = "'#{params[:replyto]}' is not a valid comment number."
652       redirect_to :controller => "main", :action => :index
653       return
654     end
655   end
656
657   
658   # is called on incoming field values to create a valid richtext from user input
659   def richtextify(content)
660     logger.debug content
661
662     # WYSIWYG is too smart, so decode the encoded entities
663     content.gsub!( /&gt;/, "&amp;gt;" ) # Escape XML-Tags
664     content.gsub!( /&lt;/, "&amp;lt;" ) # Escape XML-Tags
665     content = decode_entities(content)
666
667     content.gsub!( /&/, "&amp;" )
668     # Escape all < and > that are not part of an element declaration
669     content.gsub!( />/, "&gt;" )
670     content.gsub!( /</, "&lt;" )
671     content.gsub!( /&lt;(\/?\w+)(\s\w+=['"][^'"]*['"])*[\/]?&gt;/, "<\\1\\2>" )
672     
673     # now fix the entities that we just broke... this is getting a mess
674     content.gsub!( /&amp;gt;/, "&gt;" )
675     content.gsub!( /&amp;lt;/, "&lt;" )
676     
677     # if the text starts and ends with an element or starts/ends with <p>, don't add elements
678     if( content.index(/\A<.*>\Z/m).nil? &&
679           content.index(/\A[^<]*<p>/m).nil? && content.index(/<\/p>[^>]*\Z/m).nil? ) then
680       content = "<p>" + content + "</p>"
681     end
682     # make a new paragraph on double newlines
683     content.gsub!( /(<p>[^<]*)\r?\n\r?\n/, "\\1</p>\n<p>" )
684     fix_mce_tags content
685     logger.debug "Richtextified content: " + content
686     return content
687   end
688
689   # change some tags from mce editor
690   def fix_mce_tags content
691     content.gsub!( /<strong>/, "<b>" )
692     content.gsub!( /<\/strong>/, "</b>" )
693     content.gsub!( /&lt;br\s*\/?&gt;/, "\n" )
694     content.gsub!( /<br\s*\/?>/, "\n" )
695     content.gsub!( /\302\240/, ' ')
696     content
697   end
698
699
700   def validate_richtext(content)
701     dtd = LibXML::XML::Dtd.new(<<EOF)
702 <!ELEMENT richtext ( p | a | b | ul | ol | em | pre | h3 | tt )*>
703
704 <!ELEMENT p (#PCDATA | b | ul | ol | a | tt | em | pre)*>
705 <!ELEMENT a (#PCDATA)>
706 <!ATTLIST a
707           href CDATA #REQUIRED>
708 <!ELEMENT b (#PCDATA | b | ul | ol | a | tt | em | p | pre)*>
709 <!ATTLIST b
710           class CDATA #IMPLIED>
711 <!ELEMENT ul (li+)>
712 <!ELEMENT ol (li)+>
713 <!ELEMENT li (#PCDATA | b | tt | em | a | p | ul)*>
714 <!ELEMENT em (#PCDATA)>
715 <!ELEMENT pre (#PCDATA | tt | b | em | a )*>
716 <!ATTLIST pre
717           class CDATA #IMPLIED>
718 <!ELEMENT h3 (#PCDATA)>
719 <!ELEMENT tt (#PCDATA | b | ul | ol | a | tt | em | p | pre)*>
720 <!ATTLIST tt
721           class CDATA #IMPLIED>
722 EOF
723
724     begin
725       doc = LibXML::XML::Document.string( "<richtext>" + content + "</richtext>" )
726       doc.validate(dtd)
727     rescue => e
728       @validation_error = e.to_s
729       return false
730     else
731       return true
732     end
733   end
734
735
736   def validate_content(content)
737     if( content.length <= 15 && ( content.include?( '+1' ) || content.include?( '-1' )) )
738       @validation_error = "Your comment is too short. Please use the voting widget in the upper right corner " +
739         "to vote for a feature, so the discussion stays readable."
740       return false
741     elsif content.length <= 8
742       @validation_error = "Please enter a valid comment"
743     end
744     return true
745   end
746
747
748   def check_member
749     if (isOpenFate && !session[:user].isMember && !session[:user].isAdmin?)
750       flash[:notice] = "Sorry, only approved openSUSE members can create new feature requests at the moment. \n" + 
751         "Please check <a href='http://en.opensuse.org/Members'>http://en.opensuse.org/Members</a> for information about openSUSE membership."
752       redirect_to :controller => "main", :action => "index"
753     end
754   end
755   
756   # for partnerfate, we need to set the requester to the tam
757   def create_requester_element partnerorg
758     xml = "<actor><role>requester</role><person>"
759     if isPartnerFate
760       if $partner.items[partnerorg]["tam"].nil?
761         raise "There is a problem with your assigned TAM. Please contact your TAM or featureadmin@suse.de."
762       end
763       xml += "<email>#{$partner.items[partnerorg]["tam"]}</email>"
764     else
765       xml += "<userid>#{session[:user].uid}</userid>"
766     end
767     return xml + "</person></actor>"
768   end
769
770   def self.logger 
771     RAILS_DEFAULT_LOGGER
772   end
773
774 end