disallow js on update too
[shapado:shapado.git] / app / models / group.rb
1 class Group
2   include Mongoid::Document
3   include Mongoid::Timestamps
4
5   include MongoidExt::Slugizer
6   include MongoidExt::Storage
7   include MongoidExt::Filter
8
9   include Shapado::Models::CustomHtmlMethods
10
11   BLACKLIST_GROUP_NAME = ["www", "net", "org", "admin", "ftp", "mail", "test", "blog",
12                  "bug", "bugs", "dev", "ftp", "forum", "community", "mail", "email",
13                  "webmail", "pop", "pop3", "imap", "smtp", "stage", "stats", "status",
14                  "support", "survey", "download", "downloads", "faqs", "wiki",
15                  "assets1", "assets2", "assets3", "assets4", "staging", "code"]
16
17   identity :type => String
18
19   field :name, :type => String
20   field :subdomain, :type => String
21   field :domain, :type => String
22   index :domain, :unique => true
23   field :legend, :type => String
24   field :description, :type => String
25   field :default_tags, :type => Array, :default => []
26   field :has_custom_ads, :type => Boolean, :default => true
27   field :state, :type => String, :default => "pending" #pending, active, closed
28   field :isolate, :type => Boolean, :default => false
29   field :private, :type => Boolean, :default => false
30   field :owner_id, :type => String
31   field :analytics_id, :type => String
32   field :analytics_vendor, :type => String
33   field :has_custom_analytics, :type => Boolean, :default => true
34
35   field :auth_providers, :type => Array, :default => %w[Google Twitter Facebook]
36   field :allow_any_openid, :type => Boolean, :default => true
37
38   field :language, :type => String
39   field :languages, :type => Set, :default => Set.new
40   index :languages
41
42   field :activity_rate, :type => Float, :default => 0.0
43   index :activity_rate
44
45   field :openid_only, :type => Boolean, :default => false
46   field :registered_only, :type => Boolean, :default => false
47   field :has_adult_content, :type => Boolean, :default => false
48
49   field :wysiwyg_editor, :type => Boolean, :default => false
50
51   field :enable_anonymous, :type => Boolean, :default => false
52
53   field :has_reputation_constrains, :type => Boolean, :default => true
54   field :reputation_rewards, :type => Hash, :default => REPUTATION_REWARDS
55   field :reputation_constrains, :type => Hash, :default => REPUTATION_CONSTRAINS
56   field :daily_cap, :type => Integer, :default => 0
57   field :forum, :type => Boolean, :default => false
58
59   embeds_one :custom_html
60   field :has_custom_html, :type => Boolean, :default => true
61   field :has_custom_js, :type => Boolean, :default => true
62   field :fb_button, :type => Boolean, :default => true
63
64   field :enable_latex, :type => Boolean, :default => false
65
66   # can be:
67   # * 'all': email, openid, oauth
68   # * 'noemail': openid and oauth only
69   # * 'social': only facebook, twitter, linkedin and identica
70   # * 'email': only email/password
71   field :signup_type, :type => String, :default => 'all'
72
73   field :logo_info, :type => Hash, :default => {"width" => 215, "height" => 60}
74   embeds_one :share
75
76   embeds_one :notification_opts, :class_name => "GroupNotificationConfig"
77
78   field :twitter_account, :type => Hash, :default => { }
79
80   field :invitations_perms, :type => String, :default => 'user' # can be "moderator", "owner"
81   field :columns, :type => Array, :default => ["column1", "column2", "column3"]
82
83   file_key :logo, :max_length => 2.megabytes
84   file_key :custom_css, :max_length => 256.kilobytes
85   file_key :custom_favicon, :max_length => 256.kilobytes
86   file_list :thumbnails
87
88   slug_key :name, :unique => true
89   filterable_keys :name
90
91   references_many :tags, :dependent => :destroy
92   references_many :activities, :dependent => :destroy
93
94   embeds_one :mainlist_widgets, :class_name => "WidgetList", :as => "group_mainlist_widgets"
95   embeds_one :question_widgets, :class_name => "WidgetList", :as => "group_questions"
96   embeds_one :external_widgets, :class_name => "WidgetList", :as => "group_external"
97
98   references_many :badges, :dependent => :destroy, :validate => false
99   references_many :questions, :dependent => :destroy, :validate => false
100   references_many :answers, :dependent => :destroy, :validate => false
101 #   references_many :votes, :dependent => :destroy # FIXME:
102   references_many :pages, :dependent => :destroy
103   references_many :announcements, :dependent => :destroy
104   references_many :constrains_configs, :dependent => :destroy
105   references_many :invitations, :dependent => :destroy
106   references_many :themes, :dependent => :destroy
107   references_many :memberships, :dependent => :destroy
108   referenced_in :current_theme, :class_name => "Theme"
109
110   referenced_in :owner, :class_name => "User"
111   embeds_many :comments
112
113   validates_presence_of     :owner
114   validates_presence_of     :name
115
116   validates_length_of       :name,           :in => 3..40
117   validates_length_of       :description,    :in => 3..10000, :allow_blank => true
118   validates_length_of       :legend,         :maximum => 50
119   validates_length_of       :default_tags,   :in => 0..15,
120       :message =>  I18n.t('activerecord.models.default_tags_message')
121   validates_uniqueness_of   :name
122   validates_uniqueness_of   :subdomain
123   validates_presence_of     :subdomain
124   validates_format_of       :subdomain, :with => /^[a-z0-9\-]+$/i
125   validates_length_of       :subdomain, :in => 3..32
126
127   validates_inclusion_of :language, :in => AVAILABLE_LANGUAGES, :allow_blank => true
128   #validates_inclusion_of :theme, :in => AVAILABLE_THEMES
129
130   validate :initialize_fields, :on => :create
131   validate :check_domain, :on => :create
132
133   validate :check_reputation_configs
134
135   validates_exclusion_of      :subdomain,
136                               :in => BLACKLIST_GROUP_NAME,
137                               :message => "Sorry, this group subdomain is reserved by"+
138                                           " our system, please choose another one"
139   validates_inclusion_of :invitations_perms, :in => %w[user moderator owner]
140   validates_inclusion_of :signup_type,  :in => %w[all noemail social email]
141
142   before_create :disallow_javascript
143   before_update :disallow_javascript
144   before_save :modify_attributes
145   before_create :create_widget_lists
146   before_create :set_default_theme
147
148   # TODO: store this variable
149   def has_custom_domain?
150     @has_custom_domain ||= self[:domain].to_s !~ /#{AppConfig.domain}/
151   end
152
153   def tag_list
154     TagList.where(:group_id => self.id).first || TagList.create(:group_id => self.id)
155   end
156
157   def top_tags(limit=5)
158     tags.desc(:count).limit(limit)
159   end
160
161   def top_tags_strings(limit=5)
162     tags = []
163     top_tags(limit).each do |tag|
164       tags << [tag.name]
165     end
166     tags
167   end
168
169   def top_users(limit=5)
170     User.where(:group_ids => self.id).desc(:followers_count).limit(limit)
171   end
172
173   def default_tags=(c)
174     if c.kind_of?(String)
175       c = c.downcase.split(",").join(" ").split(" ")
176     end
177     self[:default_tags] = c
178   end
179   alias :user :owner
180
181   def add_member(user, role)
182     user.join!(self) do |membership|
183       if membership.reputation < 5
184         membership.reputation = 5
185       end
186       membership.role = role
187     end
188   end
189
190   def is_member?(user)
191     user.member_of?(self)
192   end
193
194   def owners
195     self.memberships.where(:role => 'owner')
196   end
197
198   def mods
199     self.memberships.where(:role => 'moderator')
200   end
201   alias_method :moderators, :mods
202
203   def mods_owners
204     self.memberships.where(:role.in => ['owner', 'moderator'])
205   end
206
207   def pending?
208     state == "pending"
209   end
210
211   def on_activity(action)
212     value = 0
213     case action
214       when :ask_question
215         value = 0.1
216       when :answer_question
217         value = 0.3
218     end
219     self.increment(:activity_rate => value)
220   end
221
222   def language=(lang)
223     if lang != "none"
224       self[:language] = lang
225     else
226       self[:language] = nil
227     end
228   end
229
230   def self.humanize_reputation_constrain(key)
231     I18n.t("groups.shared.reputation_constrains.#{key}", :default => key.humanize)
232   end
233
234   def self.humanize_reputation_rewards(key)
235     I18n.t("groups.shared.reputation_rewards.#{key}", :default => key.humanize)
236   end
237
238   def self.find_file_from_params(params, request)
239     if request.path =~ /\/(logo|big|medium|small|css|favicon)\/([^\/\.?]+)/
240       @group = Group.find($2)
241
242       logo = @group.has_logo? ? @group.logo : Shapado::FileWrapper.new("#{Rails.root}/public/images/logo.png", "image/png")
243       case $1
244       when "logo"
245         logo
246       when "big"
247         @group.thumbnails["big"] ? @group.thumbnails.get("big") : logo
248       when "medium"
249         @group.thumbnails["medium"] ? @group.thumbnails.get("medium") : logo
250       when "small"
251         @group.thumbnails["small"] ? @group.thumbnails.get("small") : logo
252       when "css"
253         css=@group.current_theme.stylesheet
254         css.content_type = "text/css"
255         css
256       when "favicon"
257         @group.custom_favicon if @group.has_custom_favicon?
258       end
259     end
260   end
261
262   def reset_twitter_account
263     self.twitter_account = { }
264     self.save!
265   end
266
267   def update_twitter_account_with_oauth_token(token, secret, screen_name)
268     self.twitter_account = self.twitter_account ? self.twitter_account : { }
269     self.twitter_account["token"] = token
270     self.twitter_account["secret"] = secret
271     self.twitter_account["screen_name"] = screen_name
272     self.save!
273   end
274
275   def has_twitter_oauth?
276     self.twitter_account && self.twitter_account["token"] && self.twitter_account["secret"]
277   end
278
279   def twitter_client
280       if self.has_twitter_oauth? && (config = Multiauth.providers["Twitter"])
281         TwitterOAuth::Client.new(
282           :consumer_key => config["id"],
283           :consumer_secret => config["token"],
284           :token => self.twitter_account["token"],
285           :secret => self.twitter_account["secret"]
286         )
287       end
288   end
289
290   def reset_widgets!
291     self.question_widgets = WidgetList.new
292     self.mainlist_widgets = WidgetList.new
293     self.external_widgets = WidgetList.new
294     self.create_default_widgets
295
296   end
297
298   def create_default_widgets
299     [ContributorsWidget, QuestionBadgesWidget, RelatedQuestionsWidget].each do |w|
300       self.question_widgets.sidebar << w.new
301     end
302
303     [TagCloudWidget].each do |w|
304       self.mainlist_widgets.navbar << w.new
305     end
306
307     [BadgesWidget, PagesWidget, TopGroupsWidget, TopUsersWidget].each do |w|
308       self.mainlist_widgets.sidebar << w.new
309     end
310
311     self.external_widgets.sidebar << AskQuestionWidget.new
312   end
313
314   def is_all_signup?
315     signup_type == 'all'
316   end
317
318   def is_social_only_signup?
319     signup_type == 'social'
320   end
321
322   def is_email_only_signup?
323     signup_type == 'email'
324   end
325
326   def is_noemail_signup?
327     signup_type == 'noemail'
328   end
329
330   def has_facebook_login?
331     (self.auth_providers.include?("Facebook") && self.domain.index(AppConfig.domain)) || self.share.fb_active
332   end
333
334   protected
335   #validations
336   def initialize_fields
337     self["subdomain"] ||= self["slug"]
338     self.custom_html = CustomHtml.new
339     self.share = Share.new if self.share.nil?
340     self.notification_opts = NotificationConfig.new
341   end
342
343   def check_domain
344     if domain.blank?
345       self[:domain] = "#{self[:subdomain]}.#{AppConfig.domain}"
346     end
347   end
348
349   def check_reputation_configs
350     if self.reputation_constrains_changed?
351       self.reputation_constrains.each do |k,v|
352         self.reputation_constrains[k] = v.to_i
353         if !REPUTATION_CONSTRAINS.has_key?(k)
354           self.errors.add(:reputation_constrains, "Invalid key")
355           return false
356         end
357       end
358     end
359
360     if self.reputation_rewards_changed?
361       valid = true
362       [["vote_up_question", "undo_vote_up_question"],
363        ["vote_down_question", "undo_vote_down_question"],
364        ["question_receives_up_vote", "question_undo_up_vote"],
365        ["question_receives_down_vote", "question_undo_down_vote"],
366        ["vote_up_answer", "undo_vote_up_answer"],
367        ["vote_down_answer", "undo_vote_down_answer"],
368        ["answer_receives_up_vote", "answer_undo_up_vote"],
369        ["answer_receives_down_vote", "answer_undo_down_vote"],
370        ["answer_picked_as_solution", "answer_unpicked_as_solution"]].each do |action, undo|
371         if self.reputation_rewards[action].to_i > (self.reputation_rewards[undo].to_i*-1)
372           valid = false
373           self.errors.add(undo, "should be less than #{(self.reputation_rewards[action].to_i)*-1}")
374         end
375       end
376       return false unless valid
377
378       self.reputation_rewards.each do |k,v|
379         self.reputation_rewards[k] = v.to_i
380         if !REPUTATION_REWARDS.has_key?(k)
381           self.errors.add(:reputation_rewards, "Invalid key")
382           return false
383         end
384       end
385     end
386
387     return true
388   end
389
390   #callbacks
391   def modify_attributes
392     self.domain.downcase!
393     self.subdomain.downcase!
394     if !self.language.blank? && !self.languages.include?(self.language)
395       self.languages << self.language
396     end
397   end
398
399   def disallow_javascript
400     unless self.has_custom_js
401        %w[footer head _question_help _question_prompt head_tag].each do |key|
402          value = self.custom_html[key]
403          if value.kind_of?(Hash)
404            value.each do |k,v|
405              value[k] = v.to_s.gsub(/<*.?script.*?>/, "")
406            end
407          elsif value.kind_of?(String)
408            value = value.gsub(/<*.?script.*?>/, "")
409          end
410          self.custom_html[key] = value
411        end
412     end
413   end
414
415   def create_widget_lists
416     self.mainlist_widgets = WidgetList.new
417     self.question_widgets = WidgetList.new
418     self.external_widgets = WidgetList.new
419   end
420
421   def set_default_theme
422     theme = Theme.where(:is_default => true).only(:_id).first
423     if theme.nil?
424       theme = Theme.create_default
425     end
426     self.current_theme_id =theme.id
427   end
428 end