Fix creating questions in groups with only one language
[shapado:piglops-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 :theme, :type => String, :default => "plain"
31   field :owner_id, :type => String
32   field :analytics_id, :type => String
33   field :analytics_vendor, :type => String
34   field :has_custom_analytics, :type => Boolean, :default => true
35
36   field :language, :type => String
37   field :languages, :type => Set, :default => Set.new
38   index :languages
39
40   field :activity_rate, :type => Float, :default => 0.0
41   index :activity_rate
42
43   field :openid_only, :type => Boolean, :default => false
44   field :registered_only, :type => Boolean, :default => false
45   field :has_adult_content, :type => Boolean, :default => false
46
47   field :wysiwyg_editor, :type => Boolean, :default => false
48
49   field :has_reputation_constrains, :type => Boolean, :default => true
50   field :reputation_rewards, :type => Hash, :default => REPUTATION_REWARDS
51   field :reputation_constrains, :type => Hash, :default => REPUTATION_CONSTRAINS
52   field :forum, :type => Boolean, :default => false
53
54   embeds_one :custom_html
55   field :has_custom_html, :type => Boolean, :default => true
56   field :has_custom_js, :type => Boolean, :default => true
57   field :fb_button, :type => Boolean, :default => true
58
59   field :enable_latex, :type => Boolean, :default => false
60
61   # can be:
62   # * 'all': email, openid, oauth
63   # * 'noemail': openid and oauth only
64   # * 'social': only facebook, twitter, linkedin and identica
65   # * 'email': only email/password
66   field :signup_type, :type => String, :default => 'all'
67
68   field :logo_info, :type => Hash, :default => {"width" => 215, "height" => 60}
69   embeds_one :share
70
71   embeds_one :notification_opts, :class_name => "GroupNotificationConfig"
72
73   field :twitter_account, :type => Hash, :default => { }
74
75   field :invitations_perms, :type => String, :default => 'user' # can be "moderator", "owner"
76
77   file_key :logo, :max_length => 2.megabytes
78   file_key :custom_css, :max_length => 256.kilobytes
79   file_key :custom_favicon, :max_length => 256.kilobytes
80   file_list :thumbnails
81
82   slug_key :name, :unique => true
83   filterable_keys :name
84
85   references_many :ads, :dependent => :destroy
86   references_many :tags, :dependent => :destroy
87   references_many :activities, :dependent => :destroy
88
89   embeds_one :mainlist_widgets, :class_name => "WidgetList", :as => "group_mainlist_widgets"
90   embeds_one :question_widgets, :class_name => "WidgetList", :as => "group_questions"
91   embeds_one :external_widgets, :class_name => "WidgetList", :as => "group_external"
92
93   references_many :badges, :dependent => :destroy, :validate => false
94   references_many :questions, :dependent => :destroy, :validate => false
95   references_many :answers, :dependent => :destroy, :validate => false
96 #   references_many :votes, :dependent => :destroy # FIXME:
97   references_many :pages, :dependent => :destroy
98   references_many :announcements, :dependent => :destroy
99   references_many :constrains_configs, :dependent => :destroy
100   references_many :invitations, :dependent => :destroy
101
102   referenced_in :owner, :class_name => "User"
103   embeds_many :comments
104
105   validates_presence_of     :owner
106   validates_presence_of     :name
107
108   validates_length_of       :name,           :in => 3..40
109   validates_length_of       :description,    :in => 3..10000, :allow_blank => true
110   validates_length_of       :legend,         :maximum => 50
111   validates_length_of       :default_tags,   :in => 0..15,
112       :message =>  I18n.t('activerecord.models.default_tags_message')
113   validates_uniqueness_of   :name
114   validates_uniqueness_of   :subdomain
115   validates_presence_of     :subdomain
116   validates_format_of       :subdomain, :with => /^[a-z0-9\-]+$/i
117   validates_length_of       :subdomain, :in => 3..32
118
119   validates_inclusion_of :language, :in => AVAILABLE_LANGUAGES, :allow_blank => true
120   #validates_inclusion_of :theme, :in => AVAILABLE_THEMES
121
122   validate :initialize_fields, :on => :create
123   validate :check_domain, :on => :create
124
125   validate :check_reputation_configs
126
127   validates_exclusion_of      :subdomain,
128                               :in => BLACKLIST_GROUP_NAME,
129                               :message => "Sorry, this group subdomain is reserved by"+
130                                           " our system, please choose another one"
131   validates_inclusion_of :invitations_perms, :in => %w[user moderator owner]
132   validates_inclusion_of :signup_type,  :in => %w[all noemail social email]
133
134   before_create :disallow_javascript
135   before_save :modify_attributes
136   before_create :create_widget_lists
137
138   # TODO: store this variable
139   def has_custom_domain?
140     @has_custom_domain ||= self[:domain].to_s !~ /#{AppConfig.domain}/
141   end
142
143   def tag_list
144     TagList.where(:group_id => self.id).first || TagList.create(:group_id => self.id)
145   end
146
147   def top_tags(limit=5)
148     tags.desc(:count).limit(limit)
149   end
150
151   def top_tags_strings(limit=5)
152     tags = []
153     top_tags(limit).each do |tag|
154       tags << [tag.name]
155     end
156     tags
157   end
158
159   def top_users(limit=5)
160     users.desc(:followers_count).limit(limit)
161   end
162
163   def default_tags=(c)
164     if c.kind_of?(String)
165       c = c.downcase.split(",").join(" ").split(" ")
166     end
167     self[:default_tags] = c
168   end
169   alias :user :owner
170
171   def add_member(user, role)
172     membership = user.config_for(self.id, true)
173     if membership.reputation < 5
174       membership.reputation = 5
175     end
176     membership.role = role
177
178     user.save
179   end
180
181   def is_member?(user)
182     user.member_of?(self)
183   end
184
185   def users(conditions = {})
186     conditions.merge!("membership_list.#{self.id}.reputation" => {:$exists => true})
187
188     unless conditions[:near]
189       User.where(conditions)
190     else
191       user_point = conditions.delete(:near)
192       User.near(:position => user_point).where(conditions)
193     end
194   end
195   alias_method :members, :users
196
197   def owners
198     users({ "membership_list.#{self.id}.role" => 'owner' })
199   end
200
201   def mods
202     users({ "membership_list.#{self.id}.role" => 'moderator' })
203   end
204   alias_method :moderators, :mods
205
206   def mods_owners
207     users({ "membership_list.#{self.id}.role" => {:$in => ['moderator', 'owner']} })
208   end
209
210   def pending?
211     state == "pending"
212   end
213
214   def on_activity(action)
215     value = 0
216     case action
217       when :ask_question
218         value = 0.1
219       when :answer_question
220         value = 0.3
221     end
222     self.increment(:activity_rate => value)
223   end
224
225   def language=(lang)
226     if lang != "none"
227       self[:language] = lang
228     else
229       self[:language] = nil
230     end
231   end
232
233   def self.humanize_reputation_constrain(key)
234     I18n.t("groups.shared.reputation_constrains.#{key}", :default => key.humanize)
235   end
236
237   def self.humanize_reputation_rewards(key)
238     I18n.t("groups.shared.reputation_rewards.#{key}", :default => key.humanize)
239   end
240
241   def self.find_file_from_params(params, request)
242     if request.path =~ /\/(logo|big|medium|small|css|favicon)\/([^\/\.?]+)/
243       @group = Group.find($2)
244       case $1
245       when "logo"
246         @group.logo
247       when "big"
248         @group.thumbnails["big"] ? @group.thumbnails.get("big") : @group.logo
249       when "medium"
250         @group.thumbnails["medium"] ? @group.thumbnails.get("medium") : @group.logo
251       when "small"
252         @group.thumbnails["small"] ? @group.thumbnails.get("small") : @group.logo
253       when "css"
254         if @group.has_custom_css?
255           css=@group.custom_css
256           css.content_type = "text/css"
257           css
258         end
259       when "favicon"
260         @group.custom_favicon if @group.has_custom_favicon?
261       end
262     end
263   end
264
265   def reset_twitter_account
266     self.twitter_account = { }
267     self.save!
268   end
269
270   def update_twitter_account_with_oauth_token(token, secret, screen_name)
271     self.twitter_account = self.twitter_account ? self.twitter_account : { }
272     self.twitter_account["token"] = token
273     self.twitter_account["secret"] = secret
274     self.twitter_account["screen_name"] = screen_name
275     self.save!
276   end
277
278   def has_twitter_oauth?
279     self.twitter_account && self.twitter_account["token"] && self.twitter_account["secret"]
280   end
281
282   def twitter_client
283       if self.has_twitter_oauth? && (config = Multiauth.providers["Twitter"])
284         TwitterOAuth::Client.new(
285           :consumer_key => config["id"],
286           :consumer_secret => config["token"],
287           :token => self.twitter_account["token"],
288           :secret => self.twitter_account["secret"]
289         )
290       end
291   end
292
293   def reset_widgets!
294     self.question_widgets = WidgetList.new
295     self.mainlist_widgets = WidgetList.new
296     self.external_widgets = WidgetList.new
297     self.create_default_widgets
298
299   end
300
301   def create_default_widgets
302     [ModInfoWidget, QuestionBadgesWidget, QuestionTagsWidget, RelatedQuestionsWidget,
303      TagListWidget, CurrentTagsWidget].each do |w|
304       self.question_widgets.sidebar << w.new
305     end
306
307     [BadgesWidget, PagesWidget, TopGroupsWidget, TopUsersWidget, TagCloudWidget].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   protected
331   #validations
332   def initialize_fields
333     self["subdomain"] ||= self["slug"]
334     self.custom_html = CustomHtml.new
335     self.share = Share.new if self.share.nil?
336     self.notification_opts = NotificationConfig.new
337   end
338
339   def check_domain
340     if domain.blank?
341       self[:domain] = "#{self[:subdomain]}.#{AppConfig.domain}"
342     end
343   end
344
345   def check_reputation_configs
346     if self.reputation_constrains_changed?
347       self.reputation_constrains.each do |k,v|
348         self.reputation_constrains[k] = v.to_i
349         if !REPUTATION_CONSTRAINS.has_key?(k)
350           self.errors.add(:reputation_constrains, "Invalid key")
351           return false
352         end
353       end
354     end
355
356     if self.reputation_rewards_changed?
357       valid = true
358       [["vote_up_question", "undo_vote_up_question"],
359        ["vote_down_question", "undo_vote_down_question"],
360        ["question_receives_up_vote", "question_undo_up_vote"],
361        ["question_receives_down_vote", "question_undo_down_vote"],
362        ["vote_up_answer", "undo_vote_up_answer"],
363        ["vote_down_answer", "undo_vote_down_answer"],
364        ["answer_receives_up_vote", "answer_undo_up_vote"],
365        ["answer_receives_down_vote", "answer_undo_down_vote"],
366        ["answer_picked_as_solution", "answer_unpicked_as_solution"]].each do |action, undo|
367         if self.reputation_rewards[action].to_i > (self.reputation_rewards[undo].to_i*-1)
368           valid = false
369           self.errors.add(undo, "should be less than #{(self.reputation_rewards[action].to_i)*-1}")
370         end
371       end
372       return false unless valid
373
374       self.reputation_rewards.each do |k,v|
375         self.reputation_rewards[k] = v.to_i
376         if !REPUTATION_REWARDS.has_key?(k)
377           self.errors.add(:reputation_rewards, "Invalid key")
378           return false
379         end
380       end
381     end
382
383     return true
384   end
385
386   #callbacks
387   def modify_attributes
388     self.domain.downcase!
389     self.subdomain.downcase!
390     if self.language && !self.languages.include?(self.language)
391       self.languages << self.language
392     end
393   end
394
395   def disallow_javascript
396     unless self.has_custom_js
397        %w[footer _head _question_help _question_prompt head_tag].each do |key|
398          value = self.custom_html[key]
399          if value.kind_of?(Hash)
400            value.each do |k,v|
401              value[k] = v.to_s.gsub(/<*.?script.*?>/, "")
402            end
403          elsif value.kind_of?(String)
404            value = value.gsub(/<*.?script.*?>/, "")
405          end
406          self.custom_html[key] = value
407        end
408     end
409   end
410
411   def create_widget_lists
412     self.mainlist_widgets = WidgetList.new
413     self.question_widgets = WidgetList.new
414     self.external_widgets = WidgetList.new
415   end
416 end