Commit 4ef79c2dbe9eb60b91a367e48cfb5a4bcc0f7834

Authored by Rodrigo Souto
2 parents febc53cd f1e0dc83

Merge branch 'master' into move-article-folder

Conflicts:
	app/helpers/forms_helper.rb
Showing 159 changed files with 5612 additions and 622 deletions   Show diff stats
app/controllers/application_controller.rb
@@ -114,7 +114,9 @@ class ApplicationController < ActionController::Base @@ -114,7 +114,9 @@ class ApplicationController < ActionController::Base
114 # plugin to the current controller being initialized. 114 # plugin to the current controller being initialized.
115 def init_noosfero_plugins_controller_filters 115 def init_noosfero_plugins_controller_filters
116 plugins.each do |plugin| 116 plugins.each do |plugin|
117 - plugin.send(self.class.name.underscore + '_filters').each do |plugin_filter| 117 + filters = plugin.send(self.class.name.underscore + '_filters')
  118 + filters = [filters] if !filters.kind_of?(Array)
  119 + filters.each do |plugin_filter|
118 self.class.send(plugin_filter[:type], plugin.class.name.underscore + '_' + plugin_filter[:method_name], (plugin_filter[:options] || {})) 120 self.class.send(plugin_filter[:type], plugin.class.name.underscore + '_' + plugin_filter[:method_name], (plugin_filter[:options] || {}))
119 self.class.send(:define_method, plugin.class.name.underscore + '_' + plugin_filter[:method_name], plugin_filter[:block]) 121 self.class.send(:define_method, plugin.class.name.underscore + '_' + plugin_filter[:method_name], plugin_filter[:block])
120 end 122 end
app/controllers/box_organizer_controller.rb
@@ -68,7 +68,8 @@ class BoxOrganizerController < ApplicationController @@ -68,7 +68,8 @@ class BoxOrganizerController < ApplicationController
68 raise ArgumentError.new("Type %s is not allowed. Go away." % type) 68 raise ArgumentError.new("Type %s is not allowed. Go away." % type)
69 end 69 end
70 else 70 else
71 - @block_types = available_blocks 71 + @center_block_types = Box.acceptable_center_blocks & available_blocks
  72 + @side_block_types = Box.acceptable_side_blocks & available_blocks
72 @boxes = boxes_holder.boxes 73 @boxes = boxes_holder.boxes
73 render :action => 'add_block', :layout => false 74 render :action => 'add_block', :layout => false
74 end 75 end
app/controllers/my_profile/profile_design_controller.rb
@@ -5,7 +5,7 @@ class ProfileDesignController < BoxOrganizerController @@ -5,7 +5,7 @@ class ProfileDesignController < BoxOrganizerController
5 protect 'edit_profile_design', :profile 5 protect 'edit_profile_design', :profile
6 6
7 def available_blocks 7 def available_blocks
8 - blocks = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock, FeedReaderBlock, ProfileImageBlock, LocationBlock, SlideshowBlock, ProfileSearchBlock ] 8 + blocks = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock, FeedReaderBlock, ProfileImageBlock, LocationBlock, SlideshowBlock, ProfileSearchBlock, HighlightsBlock ]
9 9
10 # blocks exclusive for organizations 10 # blocks exclusive for organizations
11 if profile.has_members? 11 if profile.has_members?
app/controllers/public/account_controller.rb
@@ -25,11 +25,13 @@ class AccountController < ApplicationController @@ -25,11 +25,13 @@ class AccountController < ApplicationController
25 25
26 # action to perform login to the application 26 # action to perform login to the application
27 def login 27 def login
28 - @user = User.new  
29 - @person = @user.build_person  
30 store_location(request.referer) unless session[:return_to] 28 store_location(request.referer) unless session[:return_to]
31 return unless request.post? 29 return unless request.post?
32 - self.current_user = User.authenticate(params[:user][:login], params[:user][:password], environment) if params[:user] 30 +
  31 + self.current_user = plugins_alternative_authentication
  32 +
  33 + self.current_user ||= User.authenticate(params[:user][:login], params[:user][:password], environment) if params[:user]
  34 +
33 if logged_in? 35 if logged_in?
34 if params[:remember_me] == "1" 36 if params[:remember_me] == "1"
35 self.current_user.remember_me 37 self.current_user.remember_me
@@ -41,7 +43,6 @@ class AccountController < ApplicationController @@ -41,7 +43,6 @@ class AccountController < ApplicationController
41 end 43 end
42 else 44 else
43 session[:notice] = _('Incorrect username or password') if redirect? 45 session[:notice] = _('Incorrect username or password') if redirect?
44 - redirect_to :back if redirect?  
45 end 46 end
46 end 47 end
47 48
@@ -56,6 +57,11 @@ class AccountController < ApplicationController @@ -56,6 +57,11 @@ class AccountController < ApplicationController
56 57
57 # action to register an user to the application 58 # action to register an user to the application
58 def signup 59 def signup
  60 + if @plugins.dispatch(:allow_user_registration).include?(false)
  61 + redirect_back_or_default(:controller => 'home')
  62 + session[:notice] = _("This environment doesn't allow user registration.")
  63 + end
  64 +
59 @invitation_code = params[:invitation_code] 65 @invitation_code = params[:invitation_code]
60 begin 66 begin
61 if params[:user] 67 if params[:user]
@@ -125,6 +131,10 @@ class AccountController < ApplicationController @@ -125,6 +131,10 @@ class AccountController < ApplicationController
125 # 131 #
126 # Posts back. 132 # Posts back.
127 def forgot_password 133 def forgot_password
  134 + if @plugins.dispatch(:allow_password_recovery).include?(false)
  135 + redirect_back_or_default(:controller => 'home')
  136 + session[:notice] = _("This environment doesn't allow password recovery.")
  137 + end
128 @change_password = ChangePassword.new(params[:change_password]) 138 @change_password = ChangePassword.new(params[:change_password])
129 139
130 if request.post? 140 if request.post?
@@ -316,4 +326,13 @@ class AccountController < ApplicationController @@ -316,4 +326,13 @@ class AccountController < ApplicationController
316 end 326 end
317 end 327 end
318 328
  329 + def plugins_alternative_authentication
  330 + user = nil
  331 + @plugins.each do |plugin|
  332 + user = plugin.alternative_authentication
  333 + break unless user.nil?
  334 + end
  335 + user
  336 + end
  337 +
319 end 338 end
app/controllers/public/content_viewer_controller.rb
@@ -2,6 +2,8 @@ class ContentViewerController < ApplicationController @@ -2,6 +2,8 @@ class ContentViewerController < ApplicationController
2 2
3 needs_profile 3 needs_profile
4 4
  5 + before_filter :comment_author, :only => :edit_comment
  6 +
5 helper ProfileHelper 7 helper ProfileHelper
6 helper TagsHelper 8 helper TagsHelper
7 9
@@ -121,6 +123,27 @@ class ContentViewerController < ApplicationController @@ -121,6 +123,27 @@ class ContentViewerController < ApplicationController
121 end 123 end
122 end 124 end
123 125
  126 + def edit_comment
  127 + path = params[:page].join('/')
  128 + @page = profile.articles.find_by_path(path)
  129 + @form_div = 'opened'
  130 + @comment = @page.comments.find_by_id(params[:id])
  131 + if @comment
  132 + if request.post?
  133 + begin
  134 + @comment.update_attributes(params[:comment])
  135 + session[:notice] = _('Comment succesfully updated')
  136 + redirect_to :action => 'view_page', :profile => profile.identifier, :page => @comment.article.explode_path
  137 + rescue
  138 + session[:notice] = _('Comment could not be updated')
  139 + end
  140 + end
  141 + else
  142 + redirect_to @page.view_url
  143 + session[:notice] = _('Could not find the comment in the article')
  144 + end
  145 + end
  146 +
124 protected 147 protected
125 148
126 def add_comment 149 def add_comment
@@ -198,4 +221,13 @@ class ContentViewerController < ApplicationController @@ -198,4 +221,13 @@ class ContentViewerController < ApplicationController
198 end 221 end
199 end 222 end
200 223
  224 + def comment_author
  225 + comment = Comment.find_by_id(params[:id])
  226 + if comment
  227 + render_access_denied if comment.author.blank? || comment.author != user
  228 + else
  229 + render_not_found
  230 + end
  231 + end
  232 +
201 end 233 end
app/helpers/application_helper.rb
@@ -265,9 +265,9 @@ module ApplicationHelper @@ -265,9 +265,9 @@ module ApplicationHelper
265 265
266 VIEW_EXTENSIONS = %w[.rhtml .html.erb] 266 VIEW_EXTENSIONS = %w[.rhtml .html.erb]
267 267
268 - def partial_for_class_in_view_path(klass, view_path) 268 + def partial_for_class_in_view_path(klass, view_path, suffix = nil)
269 return nil if klass.nil? 269 return nil if klass.nil?
270 - name = klass.name.underscore 270 + name = [klass.name.underscore, suffix].compact.map(&:to_s).join('_')
271 271
272 search_name = String.new(name) 272 search_name = String.new(name)
273 if search_name.include?("/") 273 if search_name.include?("/")
@@ -285,28 +285,17 @@ module ApplicationHelper @@ -285,28 +285,17 @@ module ApplicationHelper
285 partial_for_class_in_view_path(klass.superclass, view_path) 285 partial_for_class_in_view_path(klass.superclass, view_path)
286 end 286 end
287 287
288 - def partial_for_class(klass) 288 + def partial_for_class(klass, suffix=nil)
289 raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' if klass.nil? 289 raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' if klass.nil?
290 name = klass.name.underscore 290 name = klass.name.underscore
291 @controller.view_paths.each do |view_path| 291 @controller.view_paths.each do |view_path|
292 - partial = partial_for_class_in_view_path(klass, view_path) 292 + partial = partial_for_class_in_view_path(klass, view_path, suffix)
293 return partial if partial 293 return partial if partial
294 end 294 end
295 295
296 raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' 296 raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?'
297 end 297 end
298 298
299 - def partial_for_task_class(klass, action)  
300 - raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' if klass.nil?  
301 -  
302 - name = "#{klass.name.underscore}_#{action.to_s}"  
303 - VIEW_EXTENSIONS.each do |ext|  
304 - return name if File.exists?(File.join(RAILS_ROOT, 'app', 'views', params[:controller], '_'+name+ext))  
305 - end  
306 -  
307 - partial_for_task_class(klass.superclass, action)  
308 - end  
309 -  
310 def view_for_profile_actions(klass) 299 def view_for_profile_actions(klass)
311 raise ArgumentError, 'No profile actions view for this class.' if klass.nil? 300 raise ArgumentError, 'No profile actions view for this class.' if klass.nil?
312 301
@@ -1323,6 +1312,19 @@ module ApplicationHelper @@ -1323,6 +1312,19 @@ module ApplicationHelper
1323 end 1312 end
1324 end 1313 end
1325 1314
  1315 + def expirable_link_to(expired, content, url, options = {})
  1316 + if expired
  1317 + options[:class] = (options[:class] || '') + ' disabled'
  1318 + content_tag('a', ' '+content_tag('span', content), options)
  1319 + else
  1320 + link_to content, url, options
  1321 + end
  1322 + end
  1323 +
  1324 + def remove_content_button(action)
  1325 + @plugins.dispatch("content_remove_#{action.to_s}", @page).include?(true)
  1326 + end
  1327 +
1326 def template_options(klass, field_name) 1328 def template_options(klass, field_name)
1327 return '' if klass.templates.count == 0 1329 return '' if klass.templates.count == 0
1328 return hidden_field_tag("#{field_name}[template_id]", klass.templates.first.id) if klass.templates.count == 1 1330 return hidden_field_tag("#{field_name}[template_id]", klass.templates.first.id) if klass.templates.count == 1
@@ -1388,4 +1390,19 @@ module ApplicationHelper @@ -1388,4 +1390,19 @@ module ApplicationHelper
1388 result 1390 result
1389 end 1391 end
1390 1392
  1393 + def expirable_content_reference(content, action, text, url, options = {})
  1394 + reason = @plugins.dispatch("content_expire_#{action.to_s}", content).first
  1395 + options[:title] = reason
  1396 + expirable_link_to reason.present?, text, url, options
  1397 + end
  1398 +
  1399 + def expirable_button(content, action, text, url, options = {})
  1400 + options[:class] = "button with-text icon-#{action.to_s}"
  1401 + expirable_content_reference content, action, text, url, options
  1402 + end
  1403 +
  1404 + def expirable_comment_link(content, action, text, url, options = {})
  1405 + options[:class] = "comment-footer comment-footer-link comment-footer-hide"
  1406 + expirable_content_reference content, action, text, url, options
  1407 + end
1391 end 1408 end
app/helpers/boxes_helper.rb
@@ -162,9 +162,6 @@ module BoxesHelper @@ -162,9 +162,6 @@ module BoxesHelper
162 # 162 #
163 # +box+ is always needed 163 # +box+ is always needed
164 def block_target(box, block = nil) 164 def block_target(box, block = nil)
165 - # FIXME hardcoded  
166 - return '' if box.position == 1  
167 -  
168 id = 165 id =
169 if block.nil? 166 if block.nil?
170 "end-of-box-#{box.id}" 167 "end-of-box-#{box.id}"
@@ -172,14 +169,11 @@ module BoxesHelper @@ -172,14 +169,11 @@ module BoxesHelper
172 "before-block-#{block.id}" 169 "before-block-#{block.id}"
173 end 170 end
174 171
175 - content_tag('div', ' ', :id => id, :class => 'block-target' ) + drop_receiving_element(id, :url => { :action => 'move_block', :target => id }, :accept => 'block', :hoverclass => 'block-target-hover') 172 + content_tag('div', ' ', :id => id, :class => 'block-target' ) + drop_receiving_element(id, :url => { :action => 'move_block', :target => id }, :accept => box.acceptable_blocks, :hoverclass => 'block-target-hover')
176 end 173 end
177 174
178 # makes the given block draggable so it can be moved away. 175 # makes the given block draggable so it can be moved away.
179 def block_handle(block) 176 def block_handle(block)
180 - # FIXME hardcoded  
181 - return '' if block.box.position == 1  
182 -  
183 draggable_element("block-#{block.id}", :revert => true) 177 draggable_element("block-#{block.id}", :revert => true)
184 end 178 end
185 179
@@ -211,7 +205,7 @@ module BoxesHelper @@ -211,7 +205,7 @@ module BoxesHelper
211 end 205 end
212 206
213 if block.editable? 207 if block.editable?
214 - buttons << lightbox_icon_button(:edit, _('Edit'), { :action => 'edit', :id => block.id }) 208 + buttons << colorbox_icon_button(:edit, _('Edit'), { :action => 'edit', :id => block.id })
215 end 209 end
216 210
217 if !block.main? 211 if !block.main?
app/helpers/cms_helper.rb
@@ -42,13 +42,25 @@ module CmsHelper @@ -42,13 +42,25 @@ module CmsHelper
42 42
43 def display_spread_button(profile, article) 43 def display_spread_button(profile, article)
44 if profile.person? 44 if profile.person?
45 - button_without_text :spread, _('Spread this'), :action => 'publish', :id => article.id 45 + expirable_button article, :spread, _('Spread this'), :action => 'publish', :id => article.id
46 elsif profile.community? && environment.portal_community 46 elsif profile.community? && environment.portal_community
47 - button_without_text :spread, _('Spread this'), :action => 'publish_on_portal_community', :id => article.id 47 + expirable_button article, :spread, _('Spread this'), :action => 'publish_on_portal_community', :id => article.id
48 end 48 end
49 end 49 end
50 50
51 def display_delete_button(article) 51 def display_delete_button(article)
52 - button_without_text :delete, _('Delete'), { :action => 'destroy', :id => article.id }, :method => :post, :confirm => delete_article_message(article) 52 + expirable_button article, :delete, _('Delete'), { :action => 'destroy', :id => article.id }, :method => :post, :confirm => delete_article_message(article)
  53 + end
  54 +
  55 + def expirable_button(content, action, title, url, options = {})
  56 + reason = @plugins.dispatch("content_expire_#{action.to_s}", content).first
  57 + if reason.present?
  58 + options[:class] = (options[:class] || '') + ' disabled'
  59 + options[:disabled] = 'disabled'
  60 + options.delete(:confirm)
  61 + options.delete(:method)
  62 + title = reason
  63 + end
  64 + button_without_text action.to_sym, title, url, options
53 end 65 end
54 end 66 end
app/helpers/colorbox_helper.rb
@@ -8,6 +8,10 @@ module ColorboxHelper @@ -8,6 +8,10 @@ module ColorboxHelper
8 button(type, label, url, colorbox_options(options)) 8 button(type, label, url, colorbox_options(options))
9 end 9 end
10 10
  11 + def colorbox_icon_button(type, label, url, options = {})
  12 + icon_button(type, label, url, colorbox_options(options))
  13 + end
  14 +
11 # options must be an HTML options hash as passed to link_to etc. 15 # options must be an HTML options hash as passed to link_to etc.
12 # 16 #
13 # returns a new hash with colorbox class added. Keeps existing classes. 17 # returns a new hash with colorbox class added. Keeps existing classes.
app/helpers/forms_helper.rb
@@ -155,6 +155,119 @@ module FormsHelper @@ -155,6 +155,119 @@ module FormsHelper
155 return result 155 return result
156 end 156 end
157 157
  158 + def date_field(name, value, format = '%Y-%m-%d', datepicker_options = {}, html_options = {})
  159 + datepicker_options[:disabled] ||= false
  160 + datepicker_options[:alt_field] ||= ''
  161 + datepicker_options[:alt_format] ||= ''
  162 + datepicker_options[:append_text] ||= ''
  163 + datepicker_options[:auto_size] ||= false
  164 + datepicker_options[:button_image] ||= ''
  165 + datepicker_options[:button_image_only] ||= false
  166 + datepicker_options[:button_text] ||= '...'
  167 + datepicker_options[:calculate_week] ||= 'jQuery.datepicker.iso8601Week'
  168 + datepicker_options[:change_month] ||= false
  169 + datepicker_options[:change_year] ||= false
  170 + datepicker_options[:close_text] ||= _('Done')
  171 + datepicker_options[:constrain_input] ||= true
  172 + datepicker_options[:current_text] ||= _('Today')
  173 + datepicker_options[:date_format] ||= 'mm/dd/yy'
  174 + datepicker_options[:day_names] ||= [_('Sunday'), _('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday')]
  175 + datepicker_options[:day_names_min] ||= [_('Su'), _('Mo'), _('Tu'), _('We'), _('Th'), _('Fr'), _('Sa')]
  176 + datepicker_options[:day_names_short] ||= [_('Sun'), _('Mon'), _('Tue'), _('Wed'), _('Thu'), _('Fri'), _('Sat')]
  177 + datepicker_options[:default_date] ||= nil
  178 + datepicker_options[:duration] ||= 'normal'
  179 + datepicker_options[:first_day] ||= 0
  180 + datepicker_options[:goto_current] ||= false
  181 + datepicker_options[:hide_if_no_prev_next] ||= false
  182 + datepicker_options[:is_rtl] ||= false
  183 + datepicker_options[:max_date] ||= nil
  184 + datepicker_options[:min_date] ||= nil
  185 + datepicker_options[:month_names] ||= [_('January'), _('February'), _('March'), _('April'), _('May'), _('June'), _('July'), _('August'), _('September'), _('October'), _('November'), _('December')]
  186 + datepicker_options[:month_names_short] ||= [_('Jan'), _('Feb'), _('Mar'), _('Apr'), _('May'), _('Jun'), _('Jul'), _('Aug'), _('Sep'), _('Oct'), _('Nov'), _('Dec')]
  187 + datepicker_options[:navigation_as_date_format] ||= false
  188 + datepicker_options[:next_text] ||= _('Next')
  189 + datepicker_options[:number_of_months] ||= 1
  190 + datepicker_options[:prev_text] ||= _('Prev')
  191 + datepicker_options[:select_other_months] ||= false
  192 + datepicker_options[:short_year_cutoff] ||= '+10'
  193 + datepicker_options[:show_button_panel] ||= false
  194 + datepicker_options[:show_current_at_pos] ||= 0
  195 + datepicker_options[:show_month_after_year] ||= false
  196 + datepicker_options[:show_on] ||= 'focus'
  197 + datepicker_options[:show_options] ||= {}
  198 + datepicker_options[:show_other_months] ||= false
  199 + datepicker_options[:show_week] ||= false
  200 + datepicker_options[:step_months] ||= 1
  201 + datepicker_options[:week_header] ||= _('Wk')
  202 + datepicker_options[:year_range] ||= 'c-10:c+10'
  203 + datepicker_options[:year_suffix] ||= ''
  204 +
  205 + element_id = html_options[:id] || 'datepicker-date'
  206 + value = value.strftime(format) if value.present?
  207 + method = datepicker_options[:time] ? 'datetimepicker' : 'datepicker'
  208 + result = text_field_tag(name, value, html_options)
  209 + result +=
  210 + "
  211 + <script type='text/javascript'>
  212 + jQuery('##{element_id}').#{method}({
  213 + disabled: #{datepicker_options[:disabled].to_json},
  214 + altField: #{datepicker_options[:alt_field].to_json},
  215 + altFormat: #{datepicker_options[:alt_format].to_json},
  216 + appendText: #{datepicker_options[:append_text].to_json},
  217 + autoSize: #{datepicker_options[:auto_size].to_json},
  218 + buttonImage: #{datepicker_options[:button_image].to_json},
  219 + buttonImageOnly: #{datepicker_options[:button_image_only].to_json},
  220 + buttonText: #{datepicker_options[:button_text].to_json},
  221 + calculateWeek: #{datepicker_options[:calculate_week].to_json},
  222 + changeMonth: #{datepicker_options[:change_month].to_json},
  223 + changeYear: #{datepicker_options[:change_year].to_json},
  224 + closeText: #{datepicker_options[:close_text].to_json},
  225 + constrainInput: #{datepicker_options[:constrain_input].to_json},
  226 + currentText: #{datepicker_options[:current_text].to_json},
  227 + dateFormat: #{datepicker_options[:date_format].to_json},
  228 + dayNames: #{datepicker_options[:day_names].to_json},
  229 + dayNamesMin: #{datepicker_options[:day_names_min].to_json},
  230 + dayNamesShort: #{datepicker_options[:day_names_short].to_json},
  231 + defaultDate: #{datepicker_options[:default_date].to_json},
  232 + duration: #{datepicker_options[:duration].to_json},
  233 + firstDay: #{datepicker_options[:first_day].to_json},
  234 + gotoCurrent: #{datepicker_options[:goto_current].to_json},
  235 + hideIfNoPrevNext: #{datepicker_options[:hide_if_no_prev_next].to_json},
  236 + isRTL: #{datepicker_options[:is_rtl].to_json},
  237 + maxDate: #{datepicker_options[:max_date].to_json},
  238 + minDate: #{datepicker_options[:min_date].to_json},
  239 + monthNames: #{datepicker_options[:month_names].to_json},
  240 + monthNamesShort: #{datepicker_options[:month_names_short].to_json},
  241 + navigationAsDateFormat: #{datepicker_options[:navigation_as_date_format].to_json},
  242 + nextText: #{datepicker_options[:next_text].to_json},
  243 + numberOfMonths: #{datepicker_options[:number_of_months].to_json},
  244 + prevText: #{datepicker_options[:prev_text].to_json},
  245 + selectOtherMonths: #{datepicker_options[:select_other_months].to_json},
  246 + shortYearCutoff: #{datepicker_options[:short_year_cutoff].to_json},
  247 + showButtonPanel: #{datepicker_options[:show_button_panel].to_json},
  248 + showCurrentAtPos: #{datepicker_options[:show_current_at_pos].to_json},
  249 + showMonthAfterYear: #{datepicker_options[:show_month_after_year].to_json},
  250 + showOn: #{datepicker_options[:show_on].to_json},
  251 + showOptions: #{datepicker_options[:show_options].to_json},
  252 + showOtherMonths: #{datepicker_options[:show_other_months].to_json},
  253 + showWeek: #{datepicker_options[:show_week].to_json},
  254 + stepMonths: #{datepicker_options[:step_months].to_json},
  255 + weekHeader: #{datepicker_options[:week_header].to_json},
  256 + yearRange: #{datepicker_options[:year_range].to_json},
  257 + yearSuffix: #{datepicker_options[:year_suffix].to_json}
  258 + })
  259 + </script>
  260 + "
  261 + result
  262 + end
  263 +
  264 + def date_range_field(from_name, to_name, from_value, to_value, format = '%Y-%m-%d', datepicker_options = {}, html_options = {})
  265 + from_id = html_options[:from_id] || 'datepicker-from-date'
  266 + to_id = html_options[:to_id] || 'datepicker-to-date'
  267 + return _('From') +' '+ date_field(from_name, from_value, format, datepicker_options, html_options.merge({:id => from_id})) +
  268 + ' ' + _('until') +' '+ date_field(to_name, to_value, format, datepicker_options, html_options.merge({:id => to_id}))
  269 + end
  270 +
158 protected 271 protected
159 def self.next_id_number 272 def self.next_id_number
160 if defined? @@id_num 273 if defined? @@id_num
app/models/box.rb
@@ -2,4 +2,76 @@ class Box &lt; ActiveRecord::Base @@ -2,4 +2,76 @@ class Box &lt; ActiveRecord::Base
2 belongs_to :owner, :polymorphic => true 2 belongs_to :owner, :polymorphic => true
3 acts_as_list :scope => 'owner_id = #{owner_id} and owner_type = \'#{owner_type}\'' 3 acts_as_list :scope => 'owner_id = #{owner_id} and owner_type = \'#{owner_type}\''
4 has_many :blocks, :dependent => :destroy, :order => 'position' 4 has_many :blocks, :dependent => :destroy, :order => 'position'
  5 +
  6 + def acceptable_blocks
  7 + to_css_class_name central? ? Box.acceptable_center_blocks : Box.acceptable_side_blocks
  8 + end
  9 +
  10 + def central?
  11 + position == 1
  12 + end
  13 +
  14 + def self.acceptable_center_blocks
  15 + [ ArticleBlock,
  16 + BlogArchivesBlock,
  17 + CategoriesBlock,
  18 + CommunitiesBlock,
  19 + EnterprisesBlock,
  20 + EnvironmentStatisticsBlock,
  21 + FansBlock,
  22 + FavoriteEnterprisesBlock,
  23 + FeedReaderBlock,
  24 + FriendsBlock,
  25 + HighlightsBlock,
  26 + LinkListBlock,
  27 + LoginBlock,
  28 + MainBlock,
  29 + MembersBlock,
  30 + MyNetworkBlock,
  31 + PeopleBlock,
  32 + ProfileImageBlock,
  33 + RawHTMLBlock,
  34 + RecentDocumentsBlock,
  35 + SellersSearchBlock,
  36 + TagsBlock ]
  37 + end
  38 +
  39 + def self.acceptable_side_blocks
  40 + [ ArticleBlock,
  41 + BlogArchivesBlock,
  42 + CategoriesBlock,
  43 + CommunitiesBlock,
  44 + DisabledEnterpriseMessageBlock,
  45 + EnterprisesBlock,
  46 + EnvironmentStatisticsBlock,
  47 + FansBlock,
  48 + FavoriteEnterprisesBlock,
  49 + FeaturedProductsBlock,
  50 + FeedReaderBlock,
  51 + FriendsBlock,
  52 + HighlightsBlock,
  53 + LinkListBlock,
  54 + LocationBlock,
  55 + LoginBlock,
  56 + MembersBlock,
  57 + MyNetworkBlock,
  58 + PeopleBlock,
  59 + ProductsBlock,
  60 + ProfileImageBlock,
  61 + ProfileInfoBlock,
  62 + ProfileSearchBlock,
  63 + RawHTMLBlock,
  64 + RecentDocumentsBlock,
  65 + SellersSearchBlock,
  66 + SlideshowBlock,
  67 + TagsBlock
  68 + ]
  69 + end
  70 +
  71 + private
  72 +
  73 + def to_css_class_name(blocks)
  74 + blocks.map{ |block| block.to_s.underscore.tr('_', '-') }
  75 + end
  76 +
5 end 77 end
app/models/comment.rb
@@ -28,6 +28,8 @@ class Comment &lt; ActiveRecord::Base @@ -28,6 +28,8 @@ class Comment &lt; ActiveRecord::Base
28 28
29 xss_terminate :only => [ :body, :title, :name ], :on => 'validation' 29 xss_terminate :only => [ :body, :title, :name ], :on => 'validation'
30 30
  31 + delegate :environment, :to => :source
  32 +
31 def action_tracker_target 33 def action_tracker_target
32 self.article.profile 34 self.article.profile
33 end 35 end
app/models/person.rb
@@ -71,10 +71,7 @@ class Person &lt; Profile @@ -71,10 +71,7 @@ class Person &lt; Profile
71 Friendship.find(:all, :conditions => { :friend_id => person.id}).each { |friendship| friendship.destroy } 71 Friendship.find(:all, :conditions => { :friend_id => person.id}).each { |friendship| friendship.destroy }
72 end 72 end
73 73
74 - after_destroy :destroy_user  
75 - def destroy_user  
76 - self.user.destroy if self.user  
77 - end 74 + belongs_to :user, :dependent => :delete
78 75
79 def can_control_scrap?(scrap) 76 def can_control_scrap?(scrap)
80 begin 77 begin
app/models/task.rb
@@ -31,7 +31,7 @@ class Task &lt; ActiveRecord::Base @@ -31,7 +31,7 @@ class Task &lt; ActiveRecord::Base
31 end 31 end
32 end 32 end
33 33
34 - belongs_to :requestor, :class_name => 'Person', :foreign_key => :requestor_id 34 + belongs_to :requestor, :class_name => 'Profile', :foreign_key => :requestor_id
35 belongs_to :target, :foreign_key => :target_id, :polymorphic => true 35 belongs_to :target, :foreign_key => :target_id, :polymorphic => true
36 36
37 validates_uniqueness_of :code, :on => :create 37 validates_uniqueness_of :code, :on => :create
app/models/user.rb
@@ -30,7 +30,7 @@ class User &lt; ActiveRecord::Base @@ -30,7 +30,7 @@ class User &lt; ActiveRecord::Base
30 30
31 after_create do |user| 31 after_create do |user|
32 user.person ||= Person.new 32 user.person ||= Person.new
33 - user.person.attributes = user.person_data.merge(:identifier => user.login, :user_id => user.id, :environment_id => user.environment_id) 33 + user.person.attributes = user.person_data.merge(:identifier => user.login, :user => user, :environment_id => user.environment_id)
34 user.person.name ||= user.login 34 user.person.name ||= user.login
35 user.person.visible = false unless user.activated? 35 user.person.visible = false unless user.activated?
36 user.person.save! 36 user.person.save!
@@ -88,13 +88,13 @@ class User &lt; ActiveRecord::Base @@ -88,13 +88,13 @@ class User &lt; ActiveRecord::Base
88 attr_protected :activated_at 88 attr_protected :activated_at
89 89
90 # Virtual attribute for the unencrypted password 90 # Virtual attribute for the unencrypted password
91 - attr_accessor :password 91 + attr_accessor :password, :name
92 92
93 validates_presence_of :login, :email 93 validates_presence_of :login, :email
94 validates_format_of :login, :with => Profile::IDENTIFIER_FORMAT, :if => (lambda {|user| !user.login.blank?}) 94 validates_format_of :login, :with => Profile::IDENTIFIER_FORMAT, :if => (lambda {|user| !user.login.blank?})
95 validates_presence_of :password, :if => :password_required? 95 validates_presence_of :password, :if => :password_required?
96 - validates_presence_of :password_confirmation, :if => :password_required?, :if => (lambda {|user| !user.password.blank?})  
97 - validates_length_of :password, :within => 4..40, :if => :password_required?, :if => (lambda {|user| !user.password.blank?}) 96 + validates_presence_of :password_confirmation, :if => :password_required?
  97 + validates_length_of :password, :within => 4..40, :if => :password_required?
98 validates_confirmation_of :password, :if => :password_required? 98 validates_confirmation_of :password, :if => :password_required?
99 validates_length_of :login, :within => 2..40, :if => (lambda {|user| !user.login.blank?}) 99 validates_length_of :login, :within => 2..40, :if => (lambda {|user| !user.login.blank?})
100 validates_length_of :email, :within => 3..100, :if => (lambda {|user| !user.email.blank?}) 100 validates_length_of :email, :within => 3..100, :if => (lambda {|user| !user.email.blank?})
@@ -228,7 +228,12 @@ class User &lt; ActiveRecord::Base @@ -228,7 +228,12 @@ class User &lt; ActiveRecord::Base
228 end 228 end
229 229
230 def name 230 def name
231 - person ? person.name : login 231 + name = (self[:name] || login)
  232 + person.nil? ? name : (person.name || name)
  233 + end
  234 +
  235 + def name= name
  236 + self[:name] = name
232 end 237 end
233 238
234 def enable_email! 239 def enable_email!
@@ -274,6 +279,11 @@ class User &lt; ActiveRecord::Base @@ -274,6 +279,11 @@ class User &lt; ActiveRecord::Base
274 15 # in minutes 279 15 # in minutes
275 end 280 end
276 281
  282 +
  283 + def not_require_password!
  284 + @is_password_required = false
  285 + end
  286 +
277 protected 287 protected
278 # before filter 288 # before filter
279 def encrypt_password 289 def encrypt_password
@@ -282,9 +292,13 @@ class User &lt; ActiveRecord::Base @@ -282,9 +292,13 @@ class User &lt; ActiveRecord::Base
282 self.password_type ||= User.system_encryption_method.to_s 292 self.password_type ||= User.system_encryption_method.to_s
283 self.crypted_password = encrypt(password) 293 self.crypted_password = encrypt(password)
284 end 294 end
285 - 295 +
286 def password_required? 296 def password_required?
287 - crypted_password.blank? || !password.blank? 297 + (crypted_password.blank? || !password.blank?) && is_password_required?
  298 + end
  299 +
  300 + def is_password_required?
  301 + @is_password_required.nil? ? true : @is_password_required
288 end 302 end
289 303
290 def make_activation_code 304 def make_activation_code
@@ -292,6 +306,7 @@ class User &lt; ActiveRecord::Base @@ -292,6 +306,7 @@ class User &lt; ActiveRecord::Base
292 end 306 end
293 307
294 def deliver_activation_code 308 def deliver_activation_code
  309 + return if person.is_template?
295 User::Mailer.deliver_activation_code(self) unless self.activation_code.blank? 310 User::Mailer.deliver_activation_code(self) unless self.activation_code.blank?
296 end 311 end
297 312
app/views/account/login.rhtml
@@ -13,6 +13,8 @@ @@ -13,6 +13,8 @@
13 13
14 <%= f.password_field :password %> 14 <%= f.password_field :password %>
15 15
  16 + <%= @plugins.dispatch(:login_extra_contents).collect { |content| instance_eval(&content) }.join("") %>
  17 +
16 <% button_bar do %> 18 <% button_bar do %>
17 <%= submit_button( 'login', _('Log in') )%> 19 <%= submit_button( 'login', _('Log in') )%>
18 <% if is_thickbox %> 20 <% if is_thickbox %>
@@ -23,8 +25,13 @@ @@ -23,8 +25,13 @@
23 <% end %> 25 <% end %>
24 26
25 <% button_bar do %> 27 <% button_bar do %>
26 - <%= button :add, _("New user"), :controller => 'account', :action => 'signup' %>  
27 - <%= button :help, _("I forgot my password!"), :controller => 'account', :action => 'forgot_password' %> 28 + <% unless @plugins.dispatch(:allow_user_registration).include?(false) %>
  29 + <%= button :add, _("New user"), :controller => 'account', :action => 'signup' %>
  30 + <% end %>
  31 +
  32 + <% unless @plugins.dispatch(:allow_password_recovery).include?(false) %>
  33 + <%= button :help, _("I forgot my password!"), :controller => 'account', :action => 'forgot_password' %>
  34 + <% end %>
28 <% end %> 35 <% end %>
29 36
30 </div><!-- end class="login-box" --> 37 </div><!-- end class="login-box" -->
app/views/account/login_block.rhtml
@@ -9,25 +9,30 @@ @@ -9,25 +9,30 @@
9 @user ||= User.new 9 @user ||= User.new
10 %> 10 %>
11 11
12 - <% labelled_form_for :user, @user,  
13 - :url => login_url do |f| %> 12 + <% labelled_form_for :user, @user, :url => login_url do |f| %>
14 13
15 - <%= f.text_field :login, :onchange => 'this.value = convToValidLogin( this.value )' %> 14 + <%= f.text_field :login, :onchange => 'this.value = convToValidLogin( this.value )' %>
16 15
17 - <%= f.password_field :password %> 16 + <%= f.password_field :password %>
  17 +
  18 + <%= @plugins.dispatch(:login_extra_contents).collect { |content| instance_eval(&content) }.join("") %>
18 19
19 <% button_bar do %> 20 <% button_bar do %>
20 <%= submit_button( 'login', _('Log in') )%> 21 <%= submit_button( 'login', _('Log in') )%>
21 - <%= link_to content_tag( 'span', _('New user') ),  
22 - { :controller => 'account', :action => 'signup' },  
23 - :class => 'button with-text icon-add' %> 22 + <% unless @plugins.dispatch(:allow_user_registration).include?(false) %>
  23 + <%= link_to content_tag( 'span', _('New user') ),
  24 + { :controller => 'account', :action => 'signup' },
  25 + :class => 'button with-text icon-add' %>
  26 + <% end %>
24 <% end %> 27 <% end %>
25 28
26 <% end %> 29 <% end %>
27 30
28 - <p class="forgot-passwd">  
29 - <%= link_to _("I forgot my password!"), :controller => 'account', :action => 'forgot_password' %>  
30 - </p> 31 + <% unless @plugins.dispatch(:allow_password_recovery).include?(false) %>
  32 + <p class="forgot-passwd">
  33 + <%= link_to _("I forgot my password!"), :controller => 'account', :action => 'forgot_password' %>
  34 + </p>
  35 + <% end %>
31 36
32 </div> 37 </div>
33 38
app/views/box_organizer/_block_types.rhtml 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +<% block_types.in_groups_of(2) do |block1, block2| %>
  2 + <div style='float: left; width: 48%; padding-top: 2px;'>
  3 + <%= labelled_radio_button(block1.description, :type, block1.name) %>
  4 + </div>
  5 + <% if block2 %>
  6 + <div style='float: left; width: 48%; padding-top: 2px;'>
  7 + <%= labelled_radio_button(block2.description, :type, block2.name) %>
  8 + </div>
  9 + <% end %>
  10 +<% end %>
app/views/box_organizer/_highlights_block.rhtml
1 <strong><%= _('Highlights') %></strong> 1 <strong><%= _('Highlights') %></strong>
2 -<div id='edit-highlights-block'> 2 +<div id='edit-highlights-block' style='width:450px'>
3 <table id='highlights' class='noborder'> 3 <table id='highlights' class='noborder'>
4 <tr><th><%= _('Image') %></th><th><%= _('Address') %></th><th><%= _('Position') %></th><th><%= _('Title') %></th></tr> 4 <tr><th><%= _('Image') %></th><th><%= _('Address') %></th><th><%= _('Position') %></th><th><%= _('Title') %></th></tr>
5 <% for image in @block.images do %> 5 <% for image in @block.images do %>
app/views/box_organizer/_link_list_block.rhtml
1 <strong><%= _('Links') %></strong> 1 <strong><%= _('Links') %></strong>
2 -<div id='edit-link-list-block'> 2 +<div id='edit-link-list-block' style='width:450px'>
3 <table id='links' class='noborder'> 3 <table id='links' class='noborder'>
4 <tr><th><%= _('Icon') %></th><th><%= _('Name') %></th><th><%= _('Address') %></th></tr> 4 <tr><th><%= _('Icon') %></th><th><%= _('Name') %></th><th><%= _('Address') %></th></tr>
5 <% for link in @block.links do %> 5 <% for link in @block.links do %>
app/views/box_organizer/_raw_html_block.rhtml
1 -<%= labelled_form_field(_('HTML code'), text_area(:block, :html, :style => 'width: 100%', :rows => 15)) %> 1 +<%= labelled_form_field(_('HTML code'), text_area(:block, :html, :style => 'width: 90%', :rows => 15)) %>
app/views/box_organizer/add_block.rhtml
1 -<% form_tag do %>  
2 -  
3 - <p><%= _('In what area do you want to put your new block?') %></p>  
4 -  
5 - <%# FIXME hardcoded stuff %>  
6 - <%= select_tag('box_id', options_for_select(@boxes.select { |item| item.position != 1 }.map {|item| [ _("Area %d") % item.position, item.id]})) %>  
7 -  
8 - <p><%= _('Select the type of block you want to add to your page.') %></p>  
9 -  
10 - <% @block_types.in_groups_of(2) do |block1, block2| %>  
11 - <div style='float: left; width: 48%; padding-top: 2px;'>  
12 - <%= radio_button_tag('type', block1.name) %>  
13 - <%= label_tag "type_#{block1.name.downcase}", block1.description %> 1 +<div style='height:350px'>
  2 + <% form_tag do %>
  3 +
  4 + <p><%= _('In what area do you want to put your new block?') %></p>
  5 +
  6 + <% @boxes.each do |box| %>
  7 + <%= labelled_radio_button(_("Area %d") % box.position, :box_id, box.id, box.central?, { :class => 'box-position', 'data-position' => box.position }) %>
  8 + <% end %>
  9 +
  10 + <script type="text/javascript">
  11 + (function ($) {
  12 + $(document).ready(function () {
  13 + $(".box-position").live('change', function () {
  14 + if ($(this).attr('data-position') == '1') {
  15 + $('#center-block-types').show();
  16 + $('#side-block-types').hide();
  17 + } else {
  18 + $('#center-block-types').hide();
  19 + $('#side-block-types').show();
  20 + };
  21 + });
  22 + })})(jQuery);
  23 + </script>
  24 +
  25 + <p><%= _('Select the type of block you want to add to your page.') %></p>
  26 +
  27 + <div id='center-block-types'>
  28 + <%= render :partial => 'block_types', :locals => { :block_types => @center_block_types } %>
14 </div> 29 </div>
15 - <% if block2 %>  
16 - <div style='float: left; width: 48%; padding-top: 2px;'>  
17 - <%= radio_button_tag('type', block2.name) %>  
18 - <%= label_tag "type_#{block2.name.downcase}", block2.description %>  
19 - </div> 30 +
  31 + <div id='side-block-types' style='display:none'>
  32 + <%= render :partial => 'block_types', :locals => { :block_types => @side_block_types } %>
  33 + </div>
  34 +
  35 + <br style='clear: both'/>
  36 +
  37 + <% button_bar do %>
  38 + <%= submit_button(:add, _("Add")) %>
  39 + <%= colorbox_close_button(_('Close')) %>
20 <% end %> 40 <% end %>
21 - <% end %>  
22 - <br style='clear: both'/>  
23 -  
24 - <% button_bar do %>  
25 - <%= submit_button(:add, _("Add")) %>  
26 - <%= lightbox_close_button(_('Close')) %>  
27 - <% end %>  
28 41
29 -<% end %> 42 + <% end %>
  43 +</div>
app/views/box_organizer/edit.rhtml
1 -<h2><%= _('Editing block') %></h2> 1 +<div style='width: 500px;'>
  2 + <h2><%= _('Editing block') %></h2>
2 3
3 -<% form_tag(:action => 'save', :id => @block.id) do %> 4 + <% form_tag(:action => 'save', :id => @block.id) do %>
4 5
5 - <%= labelled_form_field(_('Custom title for this block: '), text_field(:block, :title, :maxlength => 20)) %> 6 + <%= labelled_form_field(_('Custom title for this block: '), text_field(:block, :title, :maxlength => 20)) %>
6 7
7 - <%= render :partial => partial_for_class(@block.class) %> 8 + <%= render :partial => partial_for_class(@block.class) %>
8 9
9 - <%= labelled_form_field _('Display this block:'), '' %>  
10 - <div style='margin-left: 10px'>  
11 - <%= radio_button(:block, :display, 'always') %>  
12 - <%= label_tag('block_display_always', _('In all pages')) %>  
13 - <br/>  
14 - <%= radio_button(:block, :display, 'home_page_only') %>  
15 - <%= label_tag('block_display_home_page_only', _('Only in the homepage')) %>  
16 - <br/>  
17 - <%= radio_button(:block, :display, 'except_home_page') %>  
18 - <%= label_tag('block_display_except_home_page', _('In all pages, except in the homepage')) %>  
19 - <br/>  
20 - <%= radio_button(:block, :display, 'never') %>  
21 - <%= label_tag('block_display_never', _("Don't display")) %>  
22 - </div> 10 + <%= labelled_form_field _('Display this block:'), '' %>
  11 + <div style='margin-left: 10px'>
  12 + <%= radio_button(:block, :display, 'always') %>
  13 + <%= label_tag('block_display_always', _('In all pages')) %>
  14 + <br/>
  15 + <%= radio_button(:block, :display, 'home_page_only') %>
  16 + <%= label_tag('block_display_home_page_only', _('Only in the homepage')) %>
  17 + <br/>
  18 + <%= radio_button(:block, :display, 'except_home_page') %>
  19 + <%= label_tag('block_display_except_home_page', _('In all pages, except in the homepage')) %>
  20 + <br/>
  21 + <%= radio_button(:block, :display, 'never') %>
  22 + <%= label_tag('block_display_never', _("Don't display")) %>
  23 + </div>
23 24
24 - <%= labelled_form_field(_('Show for:'), select(:block, :language, [ [ _('all languages'), 'all']] + Noosfero.locales.map {|key, value| [value, key]} )) %> 25 + <%= labelled_form_field(_('Show for:'), select(:block, :language, [ [ _('all languages'), 'all']] + Noosfero.locales.map {|key, value| [value, key]} )) %>
25 26
26 - <% button_bar do %>  
27 - <%= submit_button(:save, _('Save')) %>  
28 - <%= lightbox_close_button(_('Cancel')) %>  
29 - <% end %> 27 + <% button_bar do %>
  28 + <%= submit_button(:save, _('Save')) %>
  29 + <%= colorbox_close_button(_('Cancel')) %>
  30 + <% end %>
30 31
31 -<% end %> 32 + <% end %>
  33 +</div>
app/views/box_organizer/index.rhtml
1 <h1><%= _('Editing sideboxes')%></h1> 1 <h1><%= _('Editing sideboxes')%></h1>
2 2
3 <% button_bar do %> 3 <% button_bar do %>
4 - <%= lightbox_button('add', _('Add a block'), { :action => 'add_block' }) %> 4 + <%= colorbox_button('add', _('Add a block'), { :action => 'add_block' }) %>
5 <%= button(:back, _('Back to control panel'), :controller => (profile.nil? ? 'admin_panel': 'profile_editor')) %> 5 <%= button(:back, _('Back to control panel'), :controller => (profile.nil? ? 'admin_panel': 'profile_editor')) %>
6 <% end %> 6 <% end %>
app/views/cms/view.rhtml
@@ -49,13 +49,13 @@ @@ -49,13 +49,13 @@
49 <%= article.class.short_description %> 49 <%= article.class.short_description %>
50 </td> 50 </td>
51 <td class="article-controls"> 51 <td class="article-controls">
52 - <%= button_without_text :edit, _('Edit'), :action => 'edit', :id => article.id %> 52 + <%= expirable_button article, :edit, _('Edit'), {:action => 'edit', :id => article.id} if !remove_content_button(:edit) %>
53 <%= button_without_text :eyes, _('Public view'), article.view_url %> 53 <%= button_without_text :eyes, _('Public view'), article.view_url %>
54 - <%= display_spread_button(profile, article) unless article.folder? %>  
55 - <% if !environment.enabled?('cant_change_homepage') %>  
56 - <%= button_without_text :home, _('Use as homepage'), { :action => 'set_home_page', :id => article.id }, :method => :post %> 54 + <%= display_spread_button(profile, article) unless article.folder? || remove_content_button(:spread)%>
  55 + <% if !environment.enabled?('cant_change_homepage') && !remove_content_button(:home) %>
  56 + <%= expirable_button article, :home, _('Use as homepage'), { :action => 'set_home_page', :id => article.id }, :method => :post %>
57 <% end %> 57 <% end %>
58 - <%= display_delete_button(article) %> 58 + <%= display_delete_button(article) if !remove_content_button(:delete) %>
59 </td> 59 </td>
60 </tr> 60 </tr>
61 <% end %> 61 <% end %>
app/views/content_viewer/_article_toolbar.rhtml
1 <div<%= user && " class='logged-in'" %>> 1 <div<%= user && " class='logged-in'" %>>
2 <div id="article-actions"> 2 <div id="article-actions">
3 3
4 - <% if @page.allow_edit?(user) %>  
5 - <%= link_to content_tag( 'span', label_for_edit_article(@page) ),  
6 - profile.admin_url.merge({ :controller => 'cms', :action => 'edit', :id => @page.id }),  
7 - :class => 'button with-text icon-edit' %> 4 +
  5 + <% if @page.allow_edit?(user) && !remove_content_button(:edit) %>
  6 + <% content = content_tag('span', label_for_edit_article(@page)) %>
  7 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'edit', :id => @page.id }) %>
  8 + <%= expirable_button @page, :edit, content, url %>
8 <% end %> 9 <% end %>
9 10
10 - <% if @page != profile.home_page && !@page.has_posts? && @page.allow_delete?(user) %>  
11 - <%= link_to content_tag( 'span', _('Delete') ),  
12 - profile.admin_url.merge({ :controller => 'cms', :action => 'destroy', :id => @page}),  
13 - :method => :post,  
14 - :class => 'button with-text icon-delete',  
15 - :confirm => delete_article_message(@page) %> 11 + <% if @page != profile.home_page && !@page.has_posts? && @page.allow_delete?(user) && !remove_content_button(:delete)%>
  12 + <% content = content_tag( 'span', _('Delete') ) %>
  13 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'destroy', :id => @page}) %>
  14 + <% options = {:method => :post, :confirm => delete_article_message(@page)} %>
  15 + <%= expirable_button @page, :delete, content, url, options %>
16 <% end %> 16 <% end %>
17 17
18 - <% if !@page.folder? && @page.allow_spread?(user) %> 18 + <% if !@page.folder? && @page.allow_spread?(user) && !remove_content_button(:spread) %>
  19 + <% content = content_tag( 'span', _('Spread this') ) %>
  20 + <% url = nil %>
19 <% if profile.kind_of?(Person) %> 21 <% if profile.kind_of?(Person) %>
20 - <%= link_to content_tag( 'span', _('Spread this') ),  
21 - profile.admin_url.merge({ :controller => 'cms', :action => 'publish', :id => @page }),  
22 - :class => 'button with-text icon-spread' %> 22 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'publish', :id => @page }) %>
23 <% elsif profile.kind_of?(Community) && environment.portal_community %> 23 <% elsif profile.kind_of?(Community) && environment.portal_community %>
24 - <%= link_to content_tag( 'span', _('Spread this') ),  
25 - profile.admin_url.merge({ :controller => 'cms', :action => 'publish_on_portal_community', :id => @page }),  
26 - :class => 'button with-text icon-spread' %> 24 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'publish_on_portal_community', :id => @page }) %>
27 <% end %> 25 <% end %>
  26 + <%= expirable_button @page, :spread, content, url if url %>
28 <% end %> 27 <% end %>
29 28
30 <% if !@page.gallery? && @page.allow_create?(user) %> 29 <% if !@page.gallery? && @page.allow_create?(user) %>
31 - <%= link_to _('Add translation'),  
32 - profile.admin_url.merge(:controller => 'cms', :action => 'new',  
33 - :parent_id => (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)),  
34 - :type => @page.type, :article => { :translation_of_id => @page.native_translation.id }),  
35 - :class => 'button with-text icon-locale' if @page.translatable? && !@page.native_translation.language.blank? %> 30 + <% if @page.translatable? && !@page.native_translation.language.blank? && !remove_content_button(:locale) %>
  31 + <% content = _('Add translation') %>
  32 + <% parent_id = (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)) %>
  33 + <% url = profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => parent_id, :type => @page.type, :article => { :translation_of_id => @page.native_translation.id })%>
  34 + <%= expirable_button @page, :locale, content, url %>
  35 + <% end %>
  36 +
36 <%= colorbox_button(:new, label_for_new_article(@page), profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)))) %> 37 <%= colorbox_button(:new, label_for_new_article(@page), profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)))) %>
37 <% end %> 38 <% end %>
38 39
@@ -40,8 +41,11 @@ @@ -40,8 +41,11 @@
40 <%= button('upload-file', _('Upload files'), profile.admin_url.merge(:controller => 'cms', :action => 'upload_files', :parent_id => (@page.folder? ? @page : @page.parent))) %> 41 <%= button('upload-file', _('Upload files'), profile.admin_url.merge(:controller => 'cms', :action => 'upload_files', :parent_id => (@page.folder? ? @page : @page.parent))) %>
41 <% end %> 42 <% end %>
42 43
43 - <% if !@page.allow_create?(user) && profile.community? && (@page.blog? || @page.parent && @page.parent.blog?) %>  
44 - <%= link_to content_tag( 'span', _('Suggest an article') ), profile.admin_url.merge({ :controller => 'cms', :action => 'suggest_an_article'}), :id => 'suggest-article-link', :class => 'button with-text icon-new' %> 44 + <% if !@page.allow_create?(user) && profile.community? && (@page.blog? || @page.parent && @page.parent.blog?) && !remove_content_button(:suggest) %>
  45 + <% content = content_tag( 'span', _('Suggest an article') ) %>
  46 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'suggest_an_article'}) %>
  47 + <% options = {:id => 'suggest-article-link'} %>
  48 + <%= expirable_button @page, :suggest, content, url, options %>
45 <% end %> 49 <% end %>
46 50
47 <%= report_abuse(profile, :link, @page) %> 51 <%= report_abuse(profile, :link, @page) %>
app/views/content_viewer/_comment.rhtml
@@ -63,6 +63,11 @@ @@ -63,6 +63,11 @@
63 <% end %> 63 <% end %>
64 <% end %> 64 <% end %>
65 65
  66 + <% if comment.author && comment.author == user %>
  67 + &nbsp;
  68 + <%= expirable_comment_link comment, :edit, _('Edit'), {:action => 'edit_comment', :id => comment.id, :profile => profile.identifier} %>
  69 + <% end %>
  70 +
66 <% if logged_in? && (user == profile || user == comment.author || user.has_permission?(:moderate_comments, profile)) %> 71 <% if logged_in? && (user == profile || user == comment.author || user.has_permission?(:moderate_comments, profile)) %>
67 &nbsp; 72 &nbsp;
68 <%= link_to_function(_('Remove'), 'remove_comment(this, %s, %s); return false ;' % [url_for(:profile => params[:profile], :remove_comment => comment.id, :view => params[:view]).to_json, _('Are you sure you want to remove this comment and all its replies?').to_json], :class => 'comment-footer comment-footer-link comment-footer-hide remove-children') %> 73 <%= link_to_function(_('Remove'), 'remove_comment(this, %s, %s); return false ;' % [url_for(:profile => params[:profile], :remove_comment => comment.id, :view => params[:view]).to_json, _('Are you sure you want to remove this comment and all its replies?').to_json], :class => 'comment-footer comment-footer-link comment-footer-hide remove-children') %>
app/views/content_viewer/_comment_form.rhtml
@@ -32,15 +32,17 @@ function submit_comment_form(button) { @@ -32,15 +32,17 @@ function submit_comment_form(button) {
32 32
33 <div class="post_comment_box <%= @form_div %>"> 33 <div class="post_comment_box <%= @form_div %>">
34 34
35 -<h4 onclick="var d = jQuery(this).parent('.post_comment_box');  
36 - if (d.hasClass('closed')) {  
37 - d.removeClass('closed');  
38 - d.addClass('opened');  
39 - d.find('input[name=comment[title]], textarea').val('');  
40 - d.find('.comment_form input[name=comment[<%= focus_on %>]]').focus();  
41 - }">  
42 - <%= content_tag('a', '', :name => 'comment_form') + _('Post a comment') %>  
43 -</h4> 35 +<% if display_link %>
  36 + <h4 onclick="var d = jQuery(this).parent('.post_comment_box');
  37 + if (d.hasClass('closed')) {
  38 + d.removeClass('closed');
  39 + d.addClass('opened');
  40 + d.find('input[name=comment[title]], textarea').val('');
  41 + d.find('.comment_form input[name=comment[<%= focus_on %>]]').focus();
  42 + }">
  43 + <%= content_tag('a', '', :name => 'comment_form') + _('Post a comment') %>
  44 + </h4>
  45 +<% end %>
44 46
45 <% unless pass_without_comment_captcha? %> 47 <% unless pass_without_comment_captcha? %>
46 <div id="recaptcha-container" style="display: none"> 48 <div id="recaptcha-container" style="display: none">
@@ -59,7 +61,7 @@ function submit_comment_form(button) { @@ -59,7 +61,7 @@ function submit_comment_form(button) {
59 </script> 61 </script>
60 <% end %> 62 <% end %>
61 63
62 -<% form_tag( url_for(@page.view_url.merge({:only_path => true})), { :class => 'comment_form' } ) do %> 64 +<% form_tag( url, { :class => 'comment_form' } ) do %>
63 <%= hidden_field_tag(:confirm, 'false') %> 65 <%= hidden_field_tag(:confirm, 'false') %>
64 66
65 <%= required_fields_message %> 67 <%= required_fields_message %>
@@ -84,7 +86,11 @@ function submit_comment_form(button) { @@ -84,7 +86,11 @@ function submit_comment_form(button) {
84 86
85 <% button_bar do %> 87 <% button_bar do %>
86 <%= submit_button('add', _('Post comment'), :onclick => "submit_comment_form(this); return false") %> 88 <%= submit_button('add', _('Post comment'), :onclick => "submit_comment_form(this); return false") %>
87 - <%= button_to_function :cancel, _('Cancel'), "f=jQuery(this).parents('.post_comment_box'); f.removeClass('opened'); f.addClass('closed'); return false" %> 89 + <% if cancel_triggers_hide %>
  90 + <%= button_to_function :cancel, _('Cancel'), "f=jQuery(this).parents('.post_comment_box'); f.removeClass('opened'); f.addClass('closed'); return false" %>
  91 + <% else %>
  92 + <%= button('cancel', _('Cancel'), {:action => 'view_page', :profile => profile.identifier, :page => @comment.article.explode_path})%>
  93 + <% end %>
88 <% end %> 94 <% end %>
89 <% end %> 95 <% end %>
90 96
app/views/content_viewer/edit_comment.html.erb 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +<h1><%= _('Edit comment') %></h1>
  2 +
  3 +<%= render :partial => 'comment_form', :locals => {:url => {:action => 'edit_comment', :profile => profile.identifier}, :display_link => false, :cancel_triggers_hide => false} %>
app/views/content_viewer/view_page.rhtml
@@ -98,7 +98,7 @@ @@ -98,7 +98,7 @@
98 </ul> 98 </ul>
99 99
100 <% if @page.accept_comments? %> 100 <% if @page.accept_comments? %>
101 - <div id="page-comment-form"><%= render :partial => 'comment_form' %></div> 101 + <div id="page-comment-form"><%= render :partial => 'comment_form', :locals => {:url => url_for(@page.view_url.merge({:only_path => true})), :display_link => true, :cancel_triggers_hide => true}%></div>
102 <% end %> 102 <% end %>
103 </div><!-- end class="comments" --> 103 </div><!-- end class="comments" -->
104 104
app/views/layouts/_javascript.rhtml
@@ -2,7 +2,8 @@ @@ -2,7 +2,8 @@
2 'jquery.noconflict.js', 'jquery.cycle.all.min.js', 'thickbox.js', 'lightbox', 'colorbox', 2 'jquery.noconflict.js', 'jquery.cycle.all.min.js', 'thickbox.js', 'lightbox', 'colorbox',
3 'jquery-ui-1.8.2.custom.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery-validation/jquery.validate', 3 'jquery-ui-1.8.2.custom.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery-validation/jquery.validate',
4 'jquery.cookie', 'jquery.ba-bbq.min.js', 'reflection', 'jquery.tokeninput', 4 'jquery.cookie', 'jquery.ba-bbq.min.js', 'reflection', 'jquery.tokeninput',
5 -'add-and-join', 'report-abuse', 'catalog', 'manage-products', :cache => 'cache-general' %> 5 +'add-and-join', 'report-abuse', 'catalog', 'manage-products',
  6 +'jquery-ui-timepicker-addon', :cache => 'cache-general' %>
6 7
7 <% language = FastGettext.locale %> 8 <% language = FastGettext.locale %>
8 <% %w{messages methods}.each do |type| %> 9 <% %w{messages methods}.each do |type| %>
app/views/layouts/application-ng.rhtml
@@ -56,10 +56,18 @@ @@ -56,10 +56,18 @@
56 <%= usermenu_logged_in %> 56 <%= usermenu_logged_in %>
57 </span> 57 </span>
58 <span class='not-logged-in' style='display: none'> 58 <span class='not-logged-in' style='display: none'>
59 - <%= _("<span class='login'>%s</span> <span class='or'>or</span> <span class='signup'>%s</span>") % [thickbox_inline_popup_link('<i class="icon-menu-login"></i><strong>' + _('Login') + '</strong>', login_url, 'inlineLoginBox', :id => 'link_login'), link_to('<strong>' + _('Sign up') + '</strong>', :controller => 'account', :action => 'signup') ] %> 59 +
  60 + <%= _("<span class='login'>%s</span>") % thickbox_inline_popup_link('<i class="icon-menu-login"></i><strong>' + _('Login') + '</strong>', login_url, 'inlineLoginBox', :id => 'link_login') %>
  61 + <%= @plugins.dispatch(:alternative_authentication_link).collect { |content| instance_eval(&content) }.join("") %>
  62 +
60 <div id='inlineLoginBox' style='display: none;'> 63 <div id='inlineLoginBox' style='display: none;'>
61 <%= render :file => 'account/login', :locals => { :is_thickbox => true } %> 64 <%= render :file => 'account/login', :locals => { :is_thickbox => true } %>
62 </div> 65 </div>
  66 +
  67 + <% unless @plugins.dispatch(:allow_user_registration).include?(false) %>
  68 + <%= _("<span class='or'>or</span> <span class='signup'>%s</span>") % link_to('<strong>' + _('Sign up') + '</strong>', :controller => 'account', :action => 'signup')%>
  69 + <% end %>
  70 +
63 </span> 71 </span>
64 <form action="/search" class="search_form" method="get" class="clean"> 72 <form action="/search" class="search_form" method="get" class="clean">
65 <input name="query" size="15" title="<%=_('Search...')%>" onfocus="this.form.className='focused';" onblur="this.form.className=''" /> 73 <input name="query" size="15" title="<%=_('Search...')%>" onfocus="this.form.className='focused';" onblur="this.form.className=''" />
app/views/profile/_comment.rhtml
@@ -62,6 +62,10 @@ @@ -62,6 +62,10 @@
62 </script> 62 </script>
63 <% end %> 63 <% end %>
64 <%= report_abuse(comment.author, :comment_link, comment) if comment.author %> 64 <%= report_abuse(comment.author, :comment_link, comment) if comment.author %>
  65 + <% if comment.author && comment.author == user %>
  66 + <%= expirable_comment_link comment, :edit, _('Edit'), {:action => 'edit_comment', :id => comment.id, :profile => profile.identifier} %>
  67 + <%= content_tag('span', ' | ', :class => 'comment-footer comment-footer-hide') %>
  68 + <% end %>
65 <%= link_to_function _('Reply'), 69 <%= link_to_function _('Reply'),
66 "var f = add_comment_reply_form(this, %s); f.find('input[name=comment[title]], textarea').val(''); return false" % comment.id, 70 "var f = add_comment_reply_form(this, %s); f.find('input[name=comment[title]], textarea').val(''); return false" % comment.id,
67 :class => 'comment-footer comment-footer-link comment-footer-hide', 71 :class => 'comment-footer comment-footer-link comment-footer-hide',
app/views/tasks/_task.rhtml
@@ -50,13 +50,13 @@ @@ -50,13 +50,13 @@
50 <% fields_for "tasks[#{task.id}][task]", task do |f| %> 50 <% fields_for "tasks[#{task.id}][task]", task do |f| %>
51 <% if task.accept_details %> 51 <% if task.accept_details %>
52 <div id="on-accept-information-<%=task.id%>" style="display: none"> 52 <div id="on-accept-information-<%=task.id%>" style="display: none">
53 - <%= render :partial => partial_for_task_class(task.class, :accept_details), :locals => {:task => task, :f => f} %> 53 + <%= render :partial => partial_for_class(task.class, :accept_details), :locals => {:task => task, :f => f} %>
54 </div> 54 </div>
55 <% end %> 55 <% end %>
56 56
57 <% if task.reject_details %> 57 <% if task.reject_details %>
58 <div id="on-reject-information-<%=task.id%>" style="display: none"> 58 <div id="on-reject-information-<%=task.id%>" style="display: none">
59 - <%= render :partial => partial_for_task_class(task.class, :reject_details), :locals => {:task => task, :f => f} %> 59 + <%= render :partial => partial_for_class(task.class, :reject_details), :locals => {:task => task, :f => f} %>
60 </div> 60 </div>
61 <% end %> 61 <% end %>
62 <% end %> 62 <% end %>
config/routes.rb
@@ -19,6 +19,7 @@ ActionController::Routing::Routes.draw do |map| @@ -19,6 +19,7 @@ ActionController::Routing::Routes.draw do |map|
19 19
20 # -- just remember to delete public/index.html. 20 # -- just remember to delete public/index.html.
21 # You can have the root of your site routed by hooking up '' 21 # You can have the root of your site routed by hooking up ''
  22 + map.root :controller => "home", :conditions => { :if => lambda { |env| !Domain.hosting_profile_at(env[:host]) } }
22 map.connect '', :controller => "home", :conditions => { :if => lambda { |env| !Domain.hosting_profile_at(env[:host]) } } 23 map.connect '', :controller => "home", :conditions => { :if => lambda { |env| !Domain.hosting_profile_at(env[:host]) } }
23 map.home 'site/:action', :controller => 'home' 24 map.home 'site/:action', :controller => 'home'
24 25
@@ -121,9 +122,12 @@ ActionController::Routing::Routes.draw do |map| @@ -121,9 +122,12 @@ ActionController::Routing::Routes.draw do |map|
121 # cache stuff - hack 122 # cache stuff - hack
122 map.cache 'public/:action/:id', :controller => 'public' 123 map.cache 'public/:action/:id', :controller => 'public'
123 124
  125 + map.connect ':profile/edit_comment/:id/*page', :controller => 'content_viewer', :action => 'edit_comment', :profile => /#{Noosfero.identifier_format}/
  126 +
124 # match requests for profiles that don't have a custom domain 127 # match requests for profiles that don't have a custom domain
125 map.homepage ':profile/*page', :controller => 'content_viewer', :action => 'view_page', :profile => /#{Noosfero.identifier_format}/, :conditions => { :if => lambda { |env| !Domain.hosting_profile_at(env[:host]) } } 128 map.homepage ':profile/*page', :controller => 'content_viewer', :action => 'view_page', :profile => /#{Noosfero.identifier_format}/, :conditions => { :if => lambda { |env| !Domain.hosting_profile_at(env[:host]) } }
126 129
  130 +
127 # match requests for content in domains hosted for profiles 131 # match requests for content in domains hosted for profiles
128 map.connect '*page', :controller => 'content_viewer', :action => 'view_page' 132 map.connect '*page', :controller => 'content_viewer', :action => 'view_page'
129 133
debian/noosfero-console
@@ -2,4 +2,7 @@ @@ -2,4 +2,7 @@
2 2
3 set -e 3 set -e
4 4
5 -cd /usr/share/noosfero && su noosfero -c "./script/console production" 5 +environment="$1"
  6 +test -z "$environment" && environment=production
  7 +
  8 +cd /usr/share/noosfero && su noosfero -c "./script/console $environment"
lib/noosfero/plugin.rb
@@ -12,7 +12,11 @@ class Noosfero::Plugin @@ -12,7 +12,11 @@ class Noosfero::Plugin
12 end 12 end
13 13
14 def init_system 14 def init_system
15 - Dir.glob(File.join(Rails.root, 'config', 'plugins', '*')).select do |entry| 15 + enabled_plugins = Dir.glob(File.join(Rails.root, 'config', 'plugins', '*'))
  16 + if Rails.env.test? && !enabled_plugins.include?(File.join(Rails.root, 'config', 'plugins', 'foo'))
  17 + enabled_plugins << File.join(Rails.root, 'plugins', 'foo')
  18 + end
  19 + enabled_plugins.select do |entry|
16 File.directory?(entry) 20 File.directory?(entry)
17 end.each do |dir| 21 end.each do |dir|
18 plugin_name = File.basename(dir) 22 plugin_name = File.basename(dir)
@@ -31,6 +35,11 @@ class Noosfero::Plugin @@ -31,6 +35,11 @@ class Noosfero::Plugin
31 if plugin_dependencies_ok 35 if plugin_dependencies_ok
32 Rails.configuration.controller_paths << File.join(dir, 'controllers') 36 Rails.configuration.controller_paths << File.join(dir, 'controllers')
33 ActiveSupport::Dependencies.load_paths << File.join(dir, 'controllers') 37 ActiveSupport::Dependencies.load_paths << File.join(dir, 'controllers')
  38 + controllers_folders = %w[public profile myprofile admin]
  39 + controllers_folders.each do |folder|
  40 + Rails.configuration.controller_paths << File.join(dir, 'controllers', folder)
  41 + ActiveSupport::Dependencies.load_paths << File.join(dir, 'controllers', folder)
  42 + end
34 [ ActiveSupport::Dependencies.load_paths, $:].each do |path| 43 [ ActiveSupport::Dependencies.load_paths, $:].each do |path|
35 path << File.join(dir, 'models') 44 path << File.join(dir, 'models')
36 path << File.join(dir, 'lib') 45 path << File.join(dir, 'lib')
@@ -211,28 +220,6 @@ class Noosfero::Plugin @@ -211,28 +220,6 @@ class Noosfero::Plugin
211 nil 220 nil
212 end 221 end
213 222
214 - # This is a generic hotspot for all controllers on Noosfero.  
215 - # If any plugin wants to define filters to run on any controller, the name of  
216 - # the hotspot must be in the following form: <underscored_controller_name>_filters.  
217 - # Example: for ProfileController the hotspot is profile_controller_filters  
218 - #  
219 - # -> Adds a filter to a controller  
220 - # returns = { :type => type,  
221 - # :method_name => method_name,  
222 - # :options => {:opt1 => opt1, :opt2 => opt2},  
223 - # :block => Proc or lambda block}  
224 - # type = 'before_filter' or 'after_filter'  
225 - # method_name = The name of the filter  
226 - # option = Filter options, like :only or :except  
227 - # block = Block that the filter will call  
228 - def method_missing(method, *args, &block)  
229 - if method.to_s =~ /^(.+)_controller_filters$/  
230 - []  
231 - else  
232 - super  
233 - end  
234 - end  
235 -  
236 # This method will be called just before a comment is saved to the database. 223 # This method will be called just before a comment is saved to the database.
237 # 224 #
238 # It can modify the comment in several ways. In special, a plugin can call 225 # It can modify the comment in several ways. In special, a plugin can call
@@ -333,4 +320,71 @@ class Noosfero::Plugin @@ -333,4 +320,71 @@ class Noosfero::Plugin
333 nil 320 nil
334 end 321 end
335 322
  323 + # -> Add an alternative authentication method.
  324 + # Your plugin have to make the access control and return the logged user.
  325 + # returns = User
  326 + def alternative_authentication
  327 + nil
  328 + end
  329 +
  330 + # -> Adds adicional link to make the user authentication
  331 + # returns = lambda block that creates html code
  332 + def alternative_authentication_link
  333 + nil
  334 + end
  335 +
  336 + # -> Allow or not user registration
  337 + # returns = boolean
  338 + def allow_user_registration
  339 + true
  340 + end
  341 +
  342 + # -> Allow or not password recovery by users
  343 + # returns = boolean
  344 + def allow_password_recovery
  345 + true
  346 + end
  347 +
  348 + # -> Adds fields to the login form
  349 + # returns = lambda block that creates html code
  350 + def login_extra_contents
  351 + nil
  352 + end
  353 +
  354 + def method_missing(method, *args, &block)
  355 + # This is a generic hotspot for all controllers on Noosfero.
  356 + # If any plugin wants to define filters to run on any controller, the name of
  357 + # the hotspot must be in the following form: <underscored_controller_name>_filters.
  358 + # Example: for ProfileController the hotspot is profile_controller_filters
  359 + #
  360 + # -> Adds a filter to a controller
  361 + # returns = { :type => type,
  362 + # :method_name => method_name,
  363 + # :options => {:opt1 => opt1, :opt2 => opt2},
  364 + # :block => Proc or lambda block}
  365 + # type = 'before_filter' or 'after_filter'
  366 + # method_name = The name of the filter
  367 + # option = Filter options, like :only or :except
  368 + # block = Block that the filter will call
  369 + if method.to_s =~ /^(.+)_controller_filters$/
  370 + []
  371 + # -> Removes the action button from the content
  372 + # returns = boolean
  373 + elsif method.to_s =~ /^content_remove_(#{content_actions.join('|')})$/
  374 + nil
  375 + # -> Expire the action button from the content
  376 + # returns = string with reason of expiration
  377 + elsif method.to_s =~ /^content_expire_(#{content_actions.join('|')})$/
  378 + nil
  379 + else
  380 + super
  381 + end
  382 + end
  383 +
  384 + private
  385 +
  386 + def content_actions
  387 + %w[edit delete spread locale suggest home]
  388 + end
  389 +
336 end 390 end
lib/noosfero/plugin/routes.rb
1 -Dir.glob(File.join(Rails.root, 'config', 'plugins', '*', 'controllers')) do |dir|  
2 - plugin_name = File.basename(File.dirname(dir)) 1 +plugins_root = Rails.env.test? ? 'plugins' : File.join('config', 'plugins')
  2 +
  3 +Dir.glob(File.join(Rails.root, plugins_root, '*', 'controllers')) do |controllers_dir|
  4 + prefixes_by_folder = {'public' => 'plugin',
  5 + 'profile' => 'profile/:profile/plugin',
  6 + 'myprofile' => 'myprofile/:profile/plugin',
  7 + 'admin' => 'admin/plugin'}
  8 +
  9 + controllers_by_folder = prefixes_by_folder.keys.inject({}) do |hash, folder|
  10 + hash.merge!({folder => Dir.glob(File.join(controllers_dir, folder, '*')).map {|full_names| File.basename(full_names).gsub(/_controller.rb$/,'')}})
  11 + end
  12 +
  13 + plugin_name = File.basename(File.dirname(controllers_dir))
  14 +
  15 + controllers_by_folder.each do |folder, controllers|
  16 + controllers.each do |controller|
  17 + controller_name = controller.gsub("#{plugin_name}_plugin_",'')
  18 + map.connect "#{prefixes_by_folder[folder]}/#{plugin_name}/#{controller_name}/:action/:id", :controller => controller
  19 + end
  20 + end
  21 +
3 map.connect 'plugin/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin' 22 map.connect 'plugin/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin'
4 - map.connect 'profile/:profile/plugins/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin_profile' 23 + map.connect 'profile/:profile/plugin/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin_profile'
5 map.connect 'myprofile/:profile/plugin/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin_myprofile' 24 map.connect 'myprofile/:profile/plugin/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin_myprofile'
6 map.connect 'admin/plugin/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin_admin' 25 map.connect 'admin/plugin/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin_admin'
7 end 26 end
8 -  
plugins/custom_forms/controllers/custom_forms_plugin_myprofile_controller.rb 0 → 100644
@@ -0,0 +1,147 @@ @@ -0,0 +1,147 @@
  1 +class CustomFormsPluginMyprofileController < MyProfileController
  2 +
  3 + protect 'post_content', :profile
  4 + def index
  5 + @forms = CustomFormsPlugin::Form.from(profile)
  6 + end
  7 +
  8 + def create
  9 + @form = CustomFormsPlugin::Form.new(:profile => profile)
  10 + @fields = []
  11 + @empty_field = CustomFormsPlugin::Field.new
  12 + if request.post?
  13 + begin
  14 + @form.update_attributes!(params[:form])
  15 + params[:fields] = format_kind(params[:fields])
  16 + params[:fields] = format_choices(params[:fields])
  17 + params[:fields] = set_form_id(params[:fields], @form.id)
  18 + create_fields(new_fields(params))
  19 + session['notice'] = _('Form created')
  20 + redirect_to :action => 'index'
  21 + rescue Exception => exception
  22 + logger.error(exception.to_s)
  23 + session['notice'] = _('Form could not be created')
  24 + end
  25 + end
  26 + end
  27 +
  28 + def edit
  29 + @form = CustomFormsPlugin::Form.find(params[:id])
  30 + @fields = @form.fields
  31 + @empty_field = CustomFormsPlugin::TextField.new
  32 + if request.post?
  33 + begin
  34 + @form.update_attributes!(params[:form])
  35 + params[:fields] = format_kind(params[:fields])
  36 + params[:fields] = format_choices(params[:fields])
  37 + remove_fields(params, @form)
  38 + create_fields(new_fields(params))
  39 + update_fields(edited_fields(params))
  40 + session['notice'] = _('Form updated')
  41 + redirect_to :action => 'index'
  42 + rescue Exception => exception
  43 + logger.error(exception.to_s)
  44 + session['notice'] = _('Form could not be updated')
  45 + end
  46 + end
  47 + end
  48 +
  49 + def remove
  50 + @form = CustomFormsPlugin::Form.find(params[:id])
  51 + begin
  52 + @form.destroy
  53 + session[:notice] = _('Form removed')
  54 + rescue
  55 + session[:notice] = _('Form could not be removed')
  56 + end
  57 + redirect_to :action => 'index'
  58 + end
  59 +
  60 + def submissions
  61 + @form = CustomFormsPlugin::Form.find(params[:id])
  62 + @submissions = @form.submissions
  63 + end
  64 +
  65 + def show_submission
  66 + @submission = CustomFormsPlugin::Submission.find(params[:id])
  67 + @form = @submission.form
  68 + end
  69 +
  70 + private
  71 +
  72 + def new_fields(params)
  73 + result = params[:fields].map {|id, hash| hash.has_key?(:real_id) ? nil : hash}.compact
  74 + result.delete_if {|field| field[:name].blank?}
  75 + result
  76 + end
  77 +
  78 + def edited_fields(params)
  79 + params[:fields].map {|id, hash| hash.has_key?(:real_id) ? hash : nil}.compact
  80 + end
  81 +
  82 + def create_fields(fields)
  83 + fields.each do |field|
  84 + case field[:type]
  85 + when 'text_field'
  86 + CustomFormsPlugin::TextField.create!(field)
  87 + when 'select_field'
  88 + CustomFormsPlugin::SelectField.create!(field)
  89 + else
  90 + CustomFormsPlugin::Field.create!(field)
  91 + end
  92 + end
  93 + end
  94 +
  95 + def update_fields(fields)
  96 + fields.each do |field_attrs|
  97 + field = CustomFormsPlugin::Field.find(field_attrs.delete(:real_id))
  98 + field.attributes = field_attrs
  99 + field.save! if field.changed?
  100 + end
  101 + end
  102 +
  103 + def format_kind(fields)
  104 + fields.each do |id, field|
  105 + next if field[:kind].blank?
  106 + kind = field.delete(:kind)
  107 + case kind
  108 + when 'radio'
  109 + field[:list] = false
  110 + field[:multiple] = false
  111 + when 'check_box'
  112 + field[:list] = false
  113 + field[:multiple] = true
  114 + when 'select'
  115 + field[:list] = true
  116 + field[:multiple] = false
  117 + when 'multiple_select'
  118 + field[:list] = true
  119 + field[:multiple] = true
  120 + end
  121 + end
  122 + fields
  123 + end
  124 +
  125 + def format_choices(fields)
  126 + fields.each do |id, field|
  127 + next if !field.has_key?(:choices)
  128 + field[:choices] = field[:choices].map {|key, value| value}.inject({}) do |result, choice|
  129 + hash = (choice[:name].blank? || choice[:value].blank?) ? {} : {choice[:name] => choice[:value]}
  130 + result.merge!(hash)
  131 + end
  132 + end
  133 + fields
  134 + end
  135 +
  136 + def remove_fields(params, form)
  137 + present_fields = params[:fields].map{|id, value| value}.collect {|field| field[:real_id]}.compact
  138 + form.fields.each {|field| field.destroy if !present_fields.include?(field.id.to_s) }
  139 + end
  140 +
  141 + def set_form_id(fields, form_id)
  142 + fields.each do |id, field|
  143 + field[:form_id] = form_id
  144 + end
  145 + fields
  146 + end
  147 +end
plugins/custom_forms/controllers/custom_forms_plugin_profile_controller.rb 0 → 100644
@@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
  1 +class CustomFormsPluginProfileController < ProfileController
  2 +
  3 + before_filter :has_access, :show
  4 +
  5 + def show
  6 + @form = CustomFormsPlugin::Form.find(params[:id])
  7 + if user
  8 + @submission ||= CustomFormsPlugin::Submission.find_by_form_id_and_profile_id(@form.id,user.id)
  9 + @submission ||= CustomFormsPlugin::Submission.new(:form_id => @form.id, :profile_id => user.id)
  10 + else
  11 + @submission ||= CustomFormsPlugin::Submission.new(:form_id => @form.id)
  12 + end
  13 + if request.post?
  14 + begin
  15 + extend(CustomFormsPlugin::Helper)
  16 + answers = build_answers(params[:submission], @form)
  17 + failed_answers = answers.select {|answer| !answer.valid? }
  18 + if failed_answers.empty?
  19 + if !user
  20 + @submission.author_name = params[:author_name]
  21 + @submission.author_email = params[:author_email]
  22 + end
  23 + @submission.save!
  24 + answers.map {|answer| answer.submission = @submission; answer.save!}
  25 + else
  26 + @submission.valid?
  27 + failed_answers.each do |answer|
  28 + @submission.errors.add(answer.field.name.to_sym, answer.errors[answer.field.slug.to_sym])
  29 + end
  30 + end
  31 + session[:notice] = _('Submission saved')
  32 + redirect_to :action => 'show'
  33 + rescue
  34 + session[:notice] = _('Submission could not be saved')
  35 + end
  36 + end
  37 + end
  38 +
  39 + private
  40 +
  41 + def has_access
  42 + form = CustomFormsPlugin::Form.find(params[:id])
  43 + render_access_denied if !form.accessible_to(user)
  44 + end
  45 +end
plugins/custom_forms/db/migrate/20120727162444_create_custom_forms_plugin_forms.rb 0 → 100644
@@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
  1 +class CreateCustomFormsPluginForms < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :custom_forms_plugin_forms do |t|
  4 + t.string :name
  5 + t.string :slug
  6 + t.text :description
  7 + t.references :profile
  8 + t.datetime :begining
  9 + t.datetime :ending
  10 + t.boolean :report_submissions, :default => false
  11 + t.boolean :on_membership, :default => false
  12 + t.string :access
  13 + t.timestamps
  14 + end
  15 + end
  16 +
  17 + def self.down
  18 + drop_table :custom_forms_plugin_forms
  19 + end
  20 +end
plugins/custom_forms/db/migrate/20120727174506_create_custom_forms_plugin_fields.rb 0 → 100644
@@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
  1 +class CreateCustomFormsPluginFields < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :custom_forms_plugin_fields do |t|
  4 + t.string :name
  5 + t.string :slug
  6 + t.string :type
  7 + t.string :default_value
  8 + t.string :choices
  9 + t.float :minimum
  10 + t.float :maximum
  11 + t.references :form
  12 + t.boolean :mandatory, :default => false
  13 + t.boolean :multiple
  14 + t.boolean :list
  15 + end
  16 + end
  17 +
  18 + def self.down
  19 + drop_table :custom_forms_plugin_fields
  20 + end
  21 +end
plugins/custom_forms/db/migrate/20120727175250_create_custom_forms_plugin_answers.rb 0 → 100644
@@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
  1 +class CreateCustomFormsPluginAnswers < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :custom_forms_plugin_answers do |t|
  4 + t.text :value
  5 + t.references :field
  6 + t.references :submission
  7 + end
  8 + end
  9 +
  10 + def self.down
  11 + drop_table :custom_forms_plugin_answers
  12 + end
  13 +end
plugins/custom_forms/db/migrate/20120727180512_create_custom_forms_plugin_submissions.rb 0 → 100644
@@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
  1 +class CreateCustomFormsPluginSubmissions < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :custom_forms_plugin_submissions do |t|
  4 + t.string :author_name
  5 + t.string :author_email
  6 + t.references :profile
  7 + t.references :form
  8 + t.timestamps
  9 + end
  10 + end
  11 +
  12 + def self.down
  13 + drop_table :custom_forms_plugin_submissions
  14 + end
  15 +end
plugins/custom_forms/lib/custom_forms_plugin.rb 0 → 100644
@@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
  1 +require 'ext/role_assignment_trigger'
  2 +
  3 +class CustomFormsPlugin < Noosfero::Plugin
  4 +
  5 + def self.plugin_name
  6 + "Custom Forms"
  7 + end
  8 +
  9 + def self.plugin_description
  10 + _("Enables the creation of forms.")
  11 + end
  12 +
  13 + def stylesheet?
  14 + true
  15 + end
  16 +
  17 + def control_panel_buttons
  18 + {:title => _('Manage Forms'), :icon => 'custom-forms', :url => {:controller => 'custom_forms_plugin_myprofile'}}
  19 + end
  20 +
  21 +end
plugins/custom_forms/lib/custom_forms_plugin/answer.rb 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +class CustomFormsPlugin::Answer < Noosfero::Plugin::ActiveRecord
  2 + belongs_to :field, :class_name => 'CustomFormsPlugin::Field'
  3 + belongs_to :submission, :class_name => 'CustomFormsPlugin::Submission'
  4 +
  5 + validates_presence_of :field
  6 + validate :value_mandatory, :if => 'field.present?'
  7 +
  8 + def value_mandatory
  9 + if field.mandatory && value.blank?
  10 + errors.add(field.slug.to_sym, _("is mandatory.").fix_i18n)
  11 + end
  12 + end
  13 +end
  14 +
plugins/custom_forms/lib/custom_forms_plugin/field.rb 0 → 100644
@@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
  1 +class CustomFormsPlugin::Field < ActiveRecord::Base
  2 + set_table_name :custom_forms_plugin_fields
  3 +
  4 + validates_presence_of :form, :name
  5 + validates_uniqueness_of :slug, :scope => :form_id
  6 +
  7 + belongs_to :form, :class_name => 'CustomFormsPlugin::Form', :dependent => :destroy
  8 + has_many :answers, :class_name => 'CustomFormsPlugin::Answer'
  9 +
  10 + serialize :choices, Hash
  11 +
  12 + before_validation do |field|
  13 + field.slug = field.name.to_slug if field.name.present?
  14 + end
  15 +end
  16 +
plugins/custom_forms/lib/custom_forms_plugin/form.rb 0 → 100644
@@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
  1 +class CustomFormsPlugin::Form < Noosfero::Plugin::ActiveRecord
  2 + belongs_to :profile
  3 +
  4 + has_many :fields, :class_name => 'CustomFormsPlugin::Field'
  5 + has_many :submissions, :class_name => 'CustomFormsPlugin::Submission'
  6 +
  7 + serialize :access
  8 +
  9 + validates_presence_of :profile, :name
  10 + validates_uniqueness_of :slug, :scope => :profile_id
  11 + validate :access_format
  12 +
  13 + before_validation do |form|
  14 + form.slug = form.name.to_slug if form.name.present?
  15 + form.access = nil if form.access.blank?
  16 + end
  17 +
  18 + named_scope :from, lambda {|profile| {:conditions => {:profile_id => profile.id}}}
  19 + named_scope :on_memberships, {:conditions => {:on_membership => true}}
  20 +=begin
  21 + named_scope :accessible_to lambda do |profile|
  22 + #TODO should verify is profile is associated with the form owner
  23 + profile_associated = ???
  24 + {:conditions => ["
  25 + access IS NULL OR
  26 + (access='logged' AND :profile_present) OR
  27 + (access='associated' AND :profile_associated) OR
  28 + :profile_id in access
  29 + ", {:profile_present => profile.present?, :profile_associated => ???, :profile_id => profile.id}]}
  30 + end
  31 +=end
  32 +
  33 + def expired?
  34 + (begining.present? && Time.now < begining) || (ending.present? && Time.now > ending)
  35 + end
  36 +
  37 + def accessible_to(target)
  38 + return true if access.nil? || target == profile
  39 + return false if target.nil?
  40 + return true if access == 'logged'
  41 + return true if access == 'associated' && ((profile.organization? && profile.members.include?(target)) || (profile.person? && profile.friends.include?(target)))
  42 + return true if access.kind_of?(Integer) && target.id == access
  43 + return true if access.kind_of?(Array) && access.include?(target.id)
  44 + end
  45 +
  46 + private
  47 +
  48 + def access_format
  49 + if access.present?
  50 + if access.kind_of?(String)
  51 + if access != 'logged' && access != 'associated'
  52 + errors.add(:access, _('Invalid string format of access.'))
  53 + end
  54 + elsif access.kind_of?(Integer)
  55 + if !Profile.exists?(access)
  56 + errors.add(:access, _('There is no profile with the provided id.'))
  57 + end
  58 + elsif access.kind_of?(Array)
  59 + access.each do |value|
  60 + if !value.kind_of?(Integer) || !Profile.exists?(value)
  61 + errors.add(:access, _('There is no profile with the provided id.'))
  62 + break
  63 + end
  64 + end
  65 + else
  66 + errors.add(:access, _('Invalid type format of access.'))
  67 + end
  68 + end
  69 + end
  70 +end
plugins/custom_forms/lib/custom_forms_plugin/helper.rb 0 → 100644
@@ -0,0 +1,119 @@ @@ -0,0 +1,119 @@
  1 +module CustomFormsPlugin::Helper
  2 + def access_text(form)
  3 + return _('Public') if form.access.nil?
  4 + return _('Logged users') if form.access == 'logged'
  5 + if form.access == 'associated'
  6 + return _('Members') if form.profile.organization?
  7 + return _('Friends') if form.profile.person?
  8 + end
  9 + return _('Custom')
  10 + end
  11 +
  12 + def period_range(form)
  13 + if form.begining.blank? && form.ending.blank?
  14 + _('Always')
  15 + elsif form.begining.present? && form.ending.blank?
  16 + ('From %s') % time_format(form.begining)
  17 + elsif form.begining.blank? && form.ending.present?
  18 + _('Until %s') % time_format(form.ending)
  19 + elsif form.begining.present? && form.ending.present?
  20 + _('From %s until %s') % [time_format(form.begining), time_format(form.ending)]
  21 + end
  22 + end
  23 +
  24 + def time_format(time)
  25 + minutes = (time.min == 0) ? '' : ':%M'
  26 + hour = (time.hour == 0 && minutes.blank?) ? '' : ' %H'
  27 + h = hour.blank? ? '' : 'h'
  28 + time.strftime("%Y-%m-%d#{hour+minutes+h}")
  29 + end
  30 +
  31 + # TODO add the custom option that should offer the user the hability to
  32 + # choose the profiles one by one, using something like tokeninput
  33 + def access_options(profile)
  34 + associated = profile.organization? ? _('Members') : _('Friends')
  35 + [
  36 + [_('Public'), nil ],
  37 + [_('Logged users'), 'logged' ],
  38 + [ associated, 'associated'],
  39 + ]
  40 + end
  41 +
  42 + def type_options
  43 + [
  44 + [_('Text'), 'text_field' ],
  45 + [_('Select'), 'select_field']
  46 + ]
  47 + end
  48 +
  49 + def type_to_label(type)
  50 + map = {
  51 + 'text_field' => _('Text'),
  52 + 'select_field' => _('Select')
  53 + }
  54 + map[type_for_options(type)]
  55 + end
  56 +
  57 + def type_for_options(type)
  58 + type.to_s.split(':').last.underscore
  59 + end
  60 +
  61 + def display_custom_field(field, submission, form)
  62 + answer = submission.answers.select{|answer| answer.field == field}.first
  63 + field_tag = send("display_#{type_for_options(field.class)}",field, answer, form)
  64 + if field.mandatory? && !radio_button?(field) && !check_box?(field) && submission.id.nil?
  65 + required(labelled_form_field(field.name, field_tag))
  66 + else
  67 + labelled_form_field(field.name, field_tag)
  68 + end
  69 + end
  70 +
  71 + def display_text_field(field, answer, form)
  72 + value = answer.present? ? answer.value : field.default_value
  73 + text_field(form, field.name.to_slug, :value => value, :disabled => answer.present?)
  74 + end
  75 +
  76 + def display_select_field(field, answer, form)
  77 + if field.list && field.multiple
  78 + selected = answer.present? ? answer.value.split(',') : []
  79 + select_tag "#{form}[#{field.name.to_slug}]", options_for_select(field.choices.to_a, selected), :multiple => true, :size => field.choices.size, :disabled => answer.present?
  80 + elsif !field.list && field.multiple
  81 + field.choices.map do |name, value|
  82 + default = answer.present? ? answer.value.split(',').include?(value) : false
  83 + labelled_check_box name, "#{form}[#{field.name.to_slug}][#{value}]", '1', default, :disabled => answer.present?
  84 + end.join("\n")
  85 + elsif field.list && !field.multiple
  86 + selected = answer.present? ? answer.value.split(',') : []
  87 + select_tag "#{form}[#{field.name.to_slug}]", options_for_select([['','']] + field.choices.to_a, selected), :disabled => answer.present?
  88 + elsif !field.list && !field.multiple
  89 + field.choices.map do |name, value|
  90 + default = answer.present? ? answer.value == value : true
  91 + labelled_radio_button name, "#{form}[#{field.name.to_slug}]", value, default, :disabled => answer.present?
  92 + end.join("\n")
  93 + end
  94 + end
  95 +
  96 + def radio_button?(field)
  97 + type_for_options(field.class) == 'select_field' && !field.list && !field.multiple
  98 + end
  99 +
  100 + def check_box?(field)
  101 + type_for_options(field.class) == 'select_field' && !field.list && field.multiple
  102 + end
  103 +
  104 + def build_answers(submission, form)
  105 + answers = []
  106 + submission.each do |slug, value|
  107 + field = form.fields.select {|field| field.slug==slug}.first
  108 + if value.kind_of?(String)
  109 + final_value = value
  110 + elsif value.kind_of?(Array)
  111 + final_value = value.join(',')
  112 + elsif value.kind_of?(Hash)
  113 + final_value = value.map {|option, present| present == '1' ? option : nil}.compact.join(',')
  114 + end
  115 + answers << CustomFormsPlugin::Answer.new(:field => field, :value => final_value)
  116 + end
  117 + answers
  118 + end
  119 +end
plugins/custom_forms/lib/custom_forms_plugin/membership_survey.rb 0 → 100644
@@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
  1 +class CustomFormsPlugin::MembershipSurvey < Task
  2 +
  3 + settings_items :form_id, :submission
  4 + validates_presence_of :form_id
  5 +
  6 + include CustomFormsPlugin::Helper
  7 +
  8 + def perform
  9 + form = CustomFormsPlugin::Form.find(form_id)
  10 + answers = build_answers(submission, form)
  11 + s = CustomFormsPlugin::Submission.create!(:form => form, :profile => target)
  12 + answers.map {|answer| answer.submission = s; answer.save!}
  13 + end
  14 +
  15 + def title
  16 + _("Membership survey")
  17 + end
  18 +
  19 + def subject
  20 + nil
  21 + end
  22 +
  23 + def linked_subject
  24 + nil
  25 + end
  26 +
  27 + def information
  28 + {:message => _('%{requestor} wants you to fill in some information.')}
  29 + end
  30 +
  31 + def accept_details
  32 + true
  33 + end
  34 +
  35 + def icon
  36 + {:type => :profile_image, :profile => requestor, :url => requestor.url}
  37 + end
  38 +
  39 + def target_notification_message
  40 + _('After joining %{requestor}, the administrators of this organization
  41 + wants you to fill in some further information.') % {:requestor => requestor.name}
  42 + end
  43 +
  44 + def target_notification_description
  45 + _('%{requestor} wants to fill in some further information.') % {:requestor => requestor.name}
  46 + end
  47 +end
plugins/custom_forms/lib/custom_forms_plugin/numeric.rb 0 → 100644
@@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
  1 +class CustomFormsPlugin::Numeric < CustomFormsPlugin::Field
  2 + set_table_name :custom_forms_plugin_fields
  3 +end
  4 +
plugins/custom_forms/lib/custom_forms_plugin/select_field.rb 0 → 100644
@@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
  1 +class CustomFormsPlugin::SelectField < CustomFormsPlugin::Field
  2 + set_table_name :custom_forms_plugin_fields
  3 + validates_presence_of :choices
  4 +end
plugins/custom_forms/lib/custom_forms_plugin/submission.rb 0 → 100644
@@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
  1 +class CustomFormsPlugin::Submission < Noosfero::Plugin::ActiveRecord
  2 + belongs_to :form, :class_name => 'CustomFormsPlugin::Form'
  3 + belongs_to :profile
  4 +
  5 + has_many :answers, :class_name => 'CustomFormsPlugin::Answer'
  6 +
  7 + validates_presence_of :form
  8 + validates_presence_of :author_name, :author_email, :if => lambda {|submission| submission.profile.nil?}
  9 + validates_uniqueness_of :author_email, :scope => :form_id, :allow_nil => true
  10 + validates_format_of :author_email, :with => Noosfero::Constants::EMAIL_FORMAT, :if => (lambda {|submission| !submission.author_email.blank?})
  11 +end
  12 +
plugins/custom_forms/lib/custom_forms_plugin/text_field.rb 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +class CustomFormsPlugin::TextField < CustomFormsPlugin::Field
  2 + set_table_name :custom_forms_plugin_fields
  3 +end
plugins/custom_forms/lib/ext/role_assignment_trigger.rb 0 → 100644
@@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
  1 +module RoleAssignmentTrigger
  2 + def self.included(base)
  3 + base.class_eval do
  4 + before_create do |ra|
  5 + profile = ra.resource
  6 + person = ra.accessor
  7 + ok = !profile.nil? && !person.nil? && profile.environment.present?
  8 + if ok && profile.environment.plugin_enabled?(CustomFormsPlugin) && !person.is_member_of?(profile)
  9 + CustomFormsPlugin::Form.from(profile).on_memberships.each do |form|
  10 + CustomFormsPlugin::MembershipSurvey.create!(:requestor => profile, :target => person, :form_id => form.id)
  11 + end
  12 + end
  13 + end
  14 +
  15 + after_destroy do |ra|
  16 + profile = ra.resource
  17 + person = ra.accessor
  18 + ok = !profile.nil? && !person.nil? && profile.environment.present?
  19 + if ok && profile.environment.plugin_enabled?(CustomFormsPlugin) && !person.is_member_of?(profile)
  20 + CustomFormsPlugin::Form.from(profile).on_memberships.each do |form|
  21 + task = person.tasks.pending.select {|task| task.kind_of?(CustomFormsPlugin::MembershipSurvey) && task.form_id == form.id}.first
  22 + task.cancel if task
  23 + end
  24 + end
  25 + end
  26 + end
  27 + end
  28 +end
  29 +
  30 +RoleAssignment.send :include, RoleAssignmentTrigger
plugins/custom_forms/public/field.js 0 → 100644
@@ -0,0 +1,141 @@ @@ -0,0 +1,141 @@
  1 +jQuery('.icon-edit').live('click', function() {
  2 + elem = this;
  3 + jQuery.fn.colorbox({
  4 + overlayClose: false,
  5 + escKey: false,
  6 + inline: true,
  7 + href: function(){
  8 + id = jQuery(elem).attr('field_id');
  9 + type = jQuery('#fields_'+id+'_type').val().split('_')[0];
  10 + selector = '#edit-'+type+'-'+id
  11 + jQuery(selector).show();
  12 + return selector
  13 + }
  14 + });
  15 + return false;
  16 +});
  17 +
  18 +jQuery('.remove-field').live('click', function(){
  19 + id = jQuery(this).attr('field_id');
  20 + jQuery('#field-'+id).slideDown(function(){
  21 + jQuery('#field-'+id).remove();
  22 + });
  23 + return false
  24 +});
  25 +
  26 +jQuery('.remove-option').live('click', function(){
  27 + field_id = jQuery(this).attr('field_id');
  28 + option_id = jQuery(this).attr('option_id');
  29 + selector = '#field-'+field_id+'-option-'+option_id
  30 + jQuery(selector).slideDown(function(){
  31 + jQuery(selector).remove();
  32 + jQuery.colorbox.resize();
  33 + });
  34 + return false
  35 +});
  36 +
  37 +function updateEditText(id){
  38 + new_id = id+1
  39 + jQuery('#edit-text-'+id).attr('id', 'edit-text-'+new_id);
  40 + input = jQuery('#edit-text-'+new_id+' input');
  41 + jQuery('#edit-text-'+new_id+' .colorbox-ok-button').attr('div_id', 'edit-text-'+new_id);
  42 + input.attr('id', input.attr('id').replace(id,new_id));
  43 + input.attr('name', input.attr('name').replace(id,new_id));
  44 + label = jQuery('#edit-text-'+new_id+' label');
  45 + label.attr('for', label.attr('for').replace(id,new_id));
  46 +}
  47 +
  48 +function updateEditSelect(id){
  49 + new_id = id+1
  50 + jQuery('#edit-select-'+id).attr('id', 'edit-select-'+new_id);
  51 + jQuery('#edit-select-'+new_id+' .colorbox-ok-button').attr('div_id', 'edit-select-'+new_id);
  52 + jQuery('tr[id^=field-'+id+'-option').each(function(id, element){
  53 + jQuery(element).attr('id', jQuery(element).attr('id').replace('field-'+id,'field-'+new_id));
  54 + });
  55 + jQuery('#edit-select-'+new_id+' label').each(function(index, element){
  56 + label = jQuery(element);
  57 + label.attr('for', label.attr('for').replace(id,new_id));
  58 + });
  59 + jQuery('#edit-select-'+new_id+' input').each(function(index, element){
  60 + input = jQuery(element);
  61 + input.attr('id', input.attr('id').replace(id,new_id));
  62 + input.attr('name', input.attr('name').replace(id,new_id));
  63 + });
  64 + jQuery('#edit-select-'+new_id+' .remove-option').each(function(index, element){
  65 + jQuery(element).attr('field_id',new_id);
  66 + });
  67 + jQuery('#edit-select-'+new_id+' .new-option').attr('field_id',new_id);
  68 + jQuery('#edit-select-'+new_id+' #empty-option-'+id).attr('id','empty-option-'+new_id);
  69 +}
  70 +
  71 +function updateEmptyField(id){
  72 + id = parseInt(id);
  73 + empty_field = jQuery('#empty-field');
  74 + empty_field.attr('last_id', (id + 1).toString());
  75 + jQuery('#empty-field input').each(function(index, element){
  76 + new_id = jQuery(element).attr('id').replace(id,id+1);
  77 + jQuery(element).attr('id', new_id);
  78 + new_name = jQuery(element).attr('name').replace(id,id+1);
  79 + jQuery(element).attr('name', new_name);
  80 + });
  81 + jQuery('#empty-field select').each(function(index, element){
  82 + new_id = jQuery(element).attr('id').replace(id,id+1);
  83 + jQuery(element).attr('id', new_id);
  84 + new_name = jQuery(element).attr('name').replace(id,id+1);
  85 + jQuery(element).attr('name', new_name);
  86 + });
  87 + jQuery('#empty-field a').each(function(index, element){
  88 + jQuery(element).attr('field_id', id+1);
  89 + });
  90 + updateEditText(id);
  91 + updateEditSelect(id);
  92 +}
  93 +
  94 +function updateEmptyOption(field_id, option_id){
  95 + field_id = parseInt(field_id);
  96 + option_id = parseInt(option_id);
  97 + new_option_id = option_id+1;
  98 + empty_option = jQuery('#empty-option-'+field_id);
  99 + empty_option.attr('option_id',new_option_id);
  100 + jQuery('#empty-option-'+field_id+' .remove-option').attr('option_id', new_option_id);
  101 +
  102 + name_id = ' #fields_'+field_id+'_choices_'+option_id+'_name';
  103 + jQuery('#empty-option-'+field_id+name_id).attr('name', 'fields['+field_id+'][choices]['+new_option_id+'][name]');
  104 + jQuery('#empty-option-'+field_id+name_id).attr('id', 'fields_'+field_id+'_choices_'+new_option_id+'_name');
  105 +
  106 + value_id = ' #fields_'+field_id+'_choices_'+option_id+'_value';
  107 + jQuery('#empty-option-'+field_id+value_id).attr('name', 'fields['+field_id+'][choices]['+new_option_id+'][value]');
  108 + jQuery('#empty-option-'+field_id+value_id).attr('id', 'fields_'+field_id+'_choices_'+new_option_id+'_value');
  109 +}
  110 +
  111 +jQuery('#new-field').live('click', function(){
  112 + empty_field = jQuery('#empty-field');
  113 + id = empty_field.attr('last_id');
  114 + edit_text = jQuery('#edit-text-'+id);
  115 + edit_select = jQuery('#edit-select-'+id);
  116 + new_field = empty_field.clone();
  117 + new_field.attr('id','field-'+id);
  118 + new_field.insertBefore(empty_field).slideDown();
  119 + edit_text.clone().insertAfter(edit_text);
  120 + edit_select.clone().insertAfter(edit_select);
  121 + updateEmptyField(id);
  122 + return false
  123 +});
  124 +
  125 +jQuery('.new-option').live('click', function(){
  126 + field_id = jQuery(this).attr('field_id');
  127 + empty_option = jQuery('#empty-option-'+field_id);
  128 + option_id = empty_option.attr('option_id');
  129 + new_option = empty_option.clone();
  130 + new_option.attr('id','field-'+field_id+'-option-'+option_id);
  131 + new_option.insertBefore(empty_option).slideDown();
  132 + jQuery.colorbox.resize();
  133 + updateEmptyOption(field_id, option_id);
  134 + return false
  135 +});
  136 +
  137 +jQuery('.colorbox-ok-button').live('click', function(){
  138 + jQuery('#'+jQuery(this).attr('div_id')).hide();
  139 + jQuery.colorbox.close();
  140 + return false
  141 +});
plugins/custom_forms/public/icons/custom-forms.png 0 → 100644

4.05 KB

plugins/custom_forms/public/style.css 0 → 100644
@@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
  1 +.controller-profile_editor a.control-panel-custom-forms,
  2 +.controller-profile_editor .msie6 a.control-panel-custom-forms {
  3 + background-image: url(/plugins/custom_forms/icons/custom-forms.png)
  4 +}
  5 +
  6 +.action-table {
  7 + width: 100%;
  8 + overflow: hidden;
  9 +}
  10 +
  11 +.action-table th,
  12 +.action-table td{
  13 + text-align: center;
  14 +}
  15 +
  16 +.action-table td{
  17 + cursor: move;
  18 +}
  19 +
  20 +.action-table .actions{
  21 + white-space: nowrap;
  22 +}
  23 +
  24 +.action-table .new-item{
  25 + background-color: #EEE;
  26 +}
  27 +
  28 +.edit-information {
  29 + display: none;
  30 +}
plugins/custom_forms/test/functional/custom_forms_plugin_myprofile_controller_test.rb 0 → 100644
@@ -0,0 +1,114 @@ @@ -0,0 +1,114 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +require File.dirname(__FILE__) + '/../../controllers/custom_forms_plugin_myprofile_controller'
  3 +
  4 +# Re-raise errors caught by the controller.
  5 +class CustomFormsPluginMyprofileController; def rescue_action(e) raise e end; end
  6 +
  7 +class CustomFormsPluginMyprofileControllerTest < ActionController::TestCase
  8 + def setup
  9 + @controller = CustomFormsPluginMyprofileController.new
  10 + @request = ActionController::TestRequest.new
  11 + @response = ActionController::TestResponse.new
  12 + @profile = create_user('profile').person
  13 + login_as(@profile.identifier)
  14 + environment = Environment.default
  15 + environment.enable_plugin(CustomFormsPlugin)
  16 + end
  17 +
  18 + attr_reader :profile
  19 +
  20 + should 'list forms associated with profile' do
  21 + another_profile = fast_create(Profile)
  22 + f1 = CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Free Software')
  23 + f2 = CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Open Source')
  24 + f3 = CustomFormsPlugin::Form.create!(:profile => another_profile, :name => 'Open Source')
  25 +
  26 + get :index, :profile => profile.identifier
  27 +
  28 + assert_includes assigns(:forms), f1
  29 + assert_includes assigns(:forms), f2
  30 + assert_not_includes assigns(:forms), f3
  31 + end
  32 +
  33 + should 'destroy form' do
  34 + form = CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Free Software')
  35 + assert CustomFormsPlugin::Form.exists?(form.id)
  36 + post :remove, :profile => profile.identifier, :id => form.id
  37 + assert !CustomFormsPlugin::Form.exists?(form.id)
  38 + end
  39 +
  40 + should 'create a form' do
  41 + format = '%Y-%m-%d %H:%M'
  42 + begining = Time.now.strftime(format)
  43 + ending = (Time.now + 1.day).strftime('%Y-%m-%d %H:%M')
  44 + assert_difference CustomFormsPlugin::Form, :count, 1 do
  45 + post :create, :profile => profile.identifier,
  46 + :form => {
  47 + :name => 'My Form',
  48 + :access => 'logged',
  49 + :begining => begining,
  50 + :ending => ending,
  51 + :description => 'Cool form'},
  52 + :fields => {
  53 + 1 => {
  54 + :name => 'Name',
  55 + :default_value => 'Jack',
  56 + :type => 'text_field'
  57 + },
  58 + 2 => {
  59 + :name => 'Color',
  60 + :list => '1',
  61 + :type => 'select_field',
  62 + :choices => {
  63 + 1 => {:name => 'Red', :value => 'red'},
  64 + 2 => {:name => 'Blue', :value => 'blue'},
  65 + 3 => {:name => 'Black', :value => 'black'}
  66 + }
  67 + }
  68 + }
  69 + end
  70 +
  71 + form = CustomFormsPlugin::Form.find_by_name('My Form')
  72 + assert_equal 'logged', form.access
  73 + assert_equal begining, form.begining.strftime(format)
  74 + assert_equal ending, form.ending.strftime(format)
  75 + assert_equal 'Cool form', form.description
  76 + assert_equal 2, form.fields.count
  77 +
  78 + f1 = form.fields.first
  79 + f2 = form.fields.last
  80 +
  81 + assert_equal 'Name', f1.name
  82 + assert_equal 'Jack', f1.default_value
  83 + assert f1.kind_of?(CustomFormsPlugin::TextField)
  84 +
  85 + assert_equal 'Color', f2.name
  86 + assert_equal 'red', f2.choices['Red']
  87 + assert_equal 'blue', f2.choices['Blue']
  88 + assert_equal 'black', f2.choices['Black']
  89 + assert f2.list
  90 + assert f2.kind_of?(CustomFormsPlugin::SelectField)
  91 + end
  92 +
  93 + should 'edit a form' do
  94 + form = CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Free Software')
  95 + field = CustomFormsPlugin::TextField.create!(:form => form, :name => 'License')
  96 + format = '%Y-%m-%d %H:%M'
  97 + begining = Time.now.strftime(format)
  98 + ending = (Time.now + 1.day).strftime('%Y-%m-%d %H:%M')
  99 +
  100 + post :edit, :profile => profile.identifier, :id => form.id,
  101 + :form => {:name => 'My Form', :access => 'logged', :begining => begining, :ending => ending, :description => 'Cool form'},
  102 + :fields => {1 => {:real_id => field.id.to_s, :name => 'Source'}}
  103 +
  104 + form.reload
  105 + field.reload
  106 +
  107 + assert_equal 'logged', form.access
  108 + assert_equal begining, form.begining.strftime(format)
  109 + assert_equal ending, form.ending.strftime(format)
  110 + assert_equal 'Cool form', form.description
  111 + assert_equal 'Source', field.name
  112 + end
  113 +end
  114 +
plugins/custom_forms/test/unit/custom_forms_plugin/answer_test.rb 0 → 100644
@@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
  1 +require File.dirname(__FILE__) + '/../../../../../test/test_helper'
  2 +
  3 +class CustomFormsPlugin::AnswerTest < ActiveSupport::TestCase
  4 + should 'validates presence of field' do
  5 + answer = CustomFormsPlugin::Answer.new
  6 + answer.valid?
  7 + assert answer.errors.invalid?(:field)
  8 +
  9 + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile))
  10 + field = CustomFormsPlugin::Field.create!(:name => 'License', :form => form)
  11 + answer.field = field
  12 + answer.valid?
  13 + assert !answer.errors.invalid?(:field)
  14 + end
  15 +
  16 + should 'belong to a submission' do
  17 + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile))
  18 + submission = CustomFormsPlugin::Submission.create!(:form => form, :profile => fast_create(Profile))
  19 + answer = CustomFormsPlugin::Answer.new
  20 + answer.submission = submission
  21 +
  22 + assert_equal submission, answer.submission
  23 + end
  24 +
  25 + should 'require presence of value if field is mandatory' do
  26 + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile))
  27 + field = CustomFormsPlugin::Field.create!(:name => 'License', :form => form, :mandatory => true)
  28 + answer = CustomFormsPlugin::Answer.new(:field => field)
  29 + answer.valid?
  30 + assert answer.errors.invalid?(field.slug.to_sym)
  31 +
  32 + answer.value = "GPL"
  33 + answer.valid?
  34 + assert !answer.errors.invalid?(field.slug.to_sym)
  35 + end
  36 +
  37 +end
  38 +
plugins/custom_forms/test/unit/custom_forms_plugin/field_test.rb 0 → 100644
@@ -0,0 +1,64 @@ @@ -0,0 +1,64 @@
  1 +require File.dirname(__FILE__) + '/../../../../../test/test_helper'
  2 +
  3 +class CustomFormsPlugin::FieldTest < ActiveSupport::TestCase
  4 + should 'validate presence of form' do
  5 + field = CustomFormsPlugin::Field.new
  6 + field.valid?
  7 + assert field.errors.invalid?(:form)
  8 + assert field.errors.invalid?(:name)
  9 +
  10 + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile))
  11 + field.form = form
  12 + field.name = 'License'
  13 + field.valid?
  14 + assert !field.errors.invalid?(:form)
  15 + assert !field.errors.invalid?(:name)
  16 + end
  17 +
  18 + should 'set slug before validation based on name' do
  19 + field = CustomFormsPlugin::Field.new(:name => 'Name')
  20 + field.valid?
  21 + assert_equal field.name.to_slug, field.slug
  22 + end
  23 +
  24 + should 'validate uniqueness of slug scoped on the form' do
  25 + form1 = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile))
  26 + form2 = CustomFormsPlugin::Form.create!(:name => 'Open Source', :profile => fast_create(Profile))
  27 + f1 = CustomFormsPlugin::Field.create!(:name => 'License', :form => form1)
  28 + f2 = CustomFormsPlugin::Field.new(:name => 'License', :form => form1)
  29 + f3 = CustomFormsPlugin::Field.new(:name => 'License', :form => form2)
  30 +
  31 + f2.valid?
  32 + f3.valid?
  33 +
  34 + assert f2.errors.invalid?(:slug)
  35 + assert !f3.errors.invalid?(:slug)
  36 + end
  37 +
  38 + should 'set mandatory field as false by default' do
  39 + field = CustomFormsPlugin::Field.new
  40 + assert !field.mandatory
  41 + end
  42 +
  43 + should 'have answers' do
  44 + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile))
  45 + field = CustomFormsPlugin::Field.create!(:name => 'License', :form => form)
  46 + a1 = CustomFormsPlugin::Answer.create!(:field => field)
  47 + a2 = CustomFormsPlugin::Answer.create!(:field => field)
  48 +
  49 + assert_includes field.answers, a1
  50 + assert_includes field.answers, a2
  51 + end
  52 +
  53 + should 'serialize choices into a hash' do
  54 + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile))
  55 + field = CustomFormsPlugin::Field.create!(:name => 'License', :form => form)
  56 + field.choices = {'First' => 1, 'Second' => 2, 'Third' => 3}
  57 + field.save!
  58 +
  59 + assert_equal 1, field.choices['First']
  60 + assert_equal 2, field.choices['Second']
  61 + assert_equal 3, field.choices['Third']
  62 + end
  63 +end
  64 +
plugins/custom_forms/test/unit/custom_forms_plugin/form_test.rb 0 → 100644
@@ -0,0 +1,172 @@ @@ -0,0 +1,172 @@
  1 +require File.dirname(__FILE__) + '/../../../../../test/test_helper'
  2 +
  3 +class CustomFormsPlugin::FormTest < ActiveSupport::TestCase
  4 + should 'validates presence of a profile and a name' do
  5 + form = CustomFormsPlugin::Form.new
  6 + form.valid?
  7 + assert form.errors.invalid?(:profile)
  8 + assert form.errors.invalid?(:name)
  9 +
  10 + form.profile = fast_create(Profile)
  11 + form.name = 'Free Software'
  12 + form.valid?
  13 + assert !form.errors.invalid?(:profile)
  14 + assert !form.errors.invalid?(:name)
  15 + end
  16 +
  17 + should 'have many fields including fields subclasses' do
  18 + form = CustomFormsPlugin::Form.create!(:profile => fast_create(Profile), :name => 'Free Software')
  19 + f1 = CustomFormsPlugin::Field.create!(:form => form, :name => 'License')
  20 + f2 = CustomFormsPlugin::Field.create!(:form => form, :name => 'Code')
  21 + f3 = CustomFormsPlugin::TextField.create!(:form => form, :name => 'Developer')
  22 +
  23 + assert_includes form.fields, f1
  24 + assert_includes form.fields, f2
  25 + assert_includes form.fields, f3
  26 + end
  27 +
  28 + should 'have many submissions' do
  29 + form = CustomFormsPlugin::Form.create!(:profile => fast_create(Profile), :name => 'Free Software')
  30 + s1 = CustomFormsPlugin::Submission.create!(:form => form, :profile => fast_create(Profile))
  31 + s2 = CustomFormsPlugin::Submission.create!(:form => form, :profile => fast_create(Profile))
  32 +
  33 + assert_includes form.submissions, s1
  34 + assert_includes form.submissions, s2
  35 + end
  36 +
  37 + should 'set slug before validation based on name' do
  38 + form = CustomFormsPlugin::Form.new(:name => 'Name')
  39 + form.valid?
  40 + assert_equal form.name.to_slug, form.slug
  41 + end
  42 +
  43 + should 'validates uniqueness of slug scoped on profile' do
  44 + profile = fast_create(Profile)
  45 + another_profile = fast_create(Profile)
  46 + CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Free Software')
  47 + form = CustomFormsPlugin::Form.new(:profile => profile, :name => 'Free Software')
  48 + form.valid?
  49 + assert form.errors.invalid?(:slug)
  50 +
  51 + form.profile = another_profile
  52 + form.valid?
  53 + assert !form.errors.invalid?(:slug)
  54 + end
  55 +
  56 + should 'define form expiration' do
  57 + form = CustomFormsPlugin::Form.new
  58 + assert !form.expired?
  59 +
  60 + form.begining = Time.now + 1.day
  61 + assert form.expired?
  62 +
  63 + form.begining = Time.now - 1.day
  64 + assert !form.expired?
  65 +
  66 + form.begining = nil
  67 + form.ending = Time.now + 1.day
  68 + assert !form.expired?
  69 +
  70 + form.ending = Time.now - 1.day
  71 + assert form.expired?
  72 +
  73 + form.begining = Time.now - 1.day
  74 + form.ending = Time.now + 1.day
  75 + assert !form.expired?
  76 + end
  77 +
  78 + should 'validates format of access' do
  79 + form = CustomFormsPlugin::Form.new
  80 + form.valid?
  81 + assert !form.errors.invalid?(:access)
  82 +
  83 + form.access = 'bli'
  84 + form.valid?
  85 + assert form.errors.invalid?(:access)
  86 +
  87 + form.access = 'logged'
  88 + form.valid?
  89 + assert !form.errors.invalid?(:access)
  90 +
  91 + form.access = 'associated'
  92 + form.valid?
  93 + assert !form.errors.invalid?(:access)
  94 +
  95 + form.access = {:bli => 1}
  96 + form.valid?
  97 + assert form.errors.invalid?(:access)
  98 +
  99 + form.access = 999
  100 + form.valid?
  101 + assert form.errors.invalid?(:access)
  102 +
  103 + p1 = fast_create(Profile)
  104 + form.access = p1.id
  105 + form.valid?
  106 + assert !form.errors.invalid?(:access)
  107 +
  108 + p2 = fast_create(Profile)
  109 + p3 = fast_create(Profile)
  110 + form.access = [p1,p2,p3].map(&:id)
  111 + form.valid?
  112 + assert !form.errors.invalid?(:access)
  113 + end
  114 +
  115 + should 'defines who is able to access the form' do
  116 + owner = fast_create(Community)
  117 + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => owner)
  118 + assert form.accessible_to(nil)
  119 +
  120 + form.access = 'logged'
  121 + assert !form.accessible_to(nil)
  122 + person = fast_create(Person)
  123 + assert form.accessible_to(person)
  124 +
  125 + form.access = 'associated'
  126 + assert !form.accessible_to(person)
  127 + owner.add_member(person)
  128 + assert form.accessible_to(person)
  129 +
  130 + p1 = fast_create(Profile)
  131 + form.access = p1.id
  132 + assert !form.accessible_to(person)
  133 + assert form.accessible_to(p1)
  134 +
  135 + p2 = fast_create(Profile)
  136 + form.access = [person.id, p1.id]
  137 + assert form.accessible_to(person)
  138 + assert form.accessible_to(p1)
  139 + assert !form.accessible_to(p2)
  140 + form.access << p2.id
  141 + assert form.accessible_to(p2)
  142 +
  143 + assert form.accessible_to(owner)
  144 + end
  145 +
  146 + should 'have a named_scope that retrieve forms from a profile' do
  147 + profile = fast_create(Profile)
  148 + another_profile = fast_create(Profile)
  149 + f1 = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => profile)
  150 + f2 = CustomFormsPlugin::Form.create!(:name => 'Open Source', :profile => profile)
  151 + f3 = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => another_profile)
  152 + scope = CustomFormsPlugin::Form.from(profile)
  153 +
  154 + assert_equal ActiveRecord::NamedScope::Scope, scope.class
  155 + assert_includes scope, f1
  156 + assert_includes scope, f2
  157 + assert_not_includes scope, f3
  158 + end
  159 +
  160 + should 'have a named_scope that retrieves all forms that are triggered on membership' do
  161 + profile = fast_create(Profile)
  162 + f1 = CustomFormsPlugin::Form.create!(:name => 'On membership 1', :profile => profile, :on_membership => true)
  163 + f2 = CustomFormsPlugin::Form.create!(:name => 'On membership 2', :profile => profile, :on_membership => true)
  164 + f3 = CustomFormsPlugin::Form.create!(:name => 'Not on memberhsip', :profile => profile, :on_membership => false)
  165 + scope = CustomFormsPlugin::Form.from(profile).on_memberships
  166 +
  167 + assert_equal ActiveRecord::NamedScope::Scope, scope.class
  168 + assert_includes scope, f1
  169 + assert_includes scope, f2
  170 + assert_not_includes scope, f3
  171 + end
  172 +end
plugins/custom_forms/test/unit/custom_forms_plugin/membership_survey_test.rb 0 → 100644
@@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
  1 +require File.dirname(__FILE__) + '/../../../../../test/test_helper'
  2 +
  3 +class CustomFormsPlugin::MembershipSurveyTest < ActiveSupport::TestCase
  4 + should 'validates presence of form_id' do
  5 + task = CustomFormsPlugin::MembershipSurvey.new
  6 + task.valid?
  7 + assert task.errors.invalid?(:form_id)
  8 +
  9 + task.form_id = 1
  10 + task.valid?
  11 + assert !task.errors.invalid?(:form_id)
  12 + end
  13 +
  14 + should 'create submission with answers on perform' do
  15 + profile = fast_create(Profile)
  16 + person = fast_create(Person)
  17 + form = CustomFormsPlugin::Form.create!(:name => 'Simple Form', :profile => profile)
  18 + field = CustomFormsPlugin::Field.create!(:name => 'Name', :form => form)
  19 + task = CustomFormsPlugin::MembershipSurvey.create!(:form_id => form.id, :submission => {'name' => 'Jack'}, :target => person, :requestor => profile)
  20 +
  21 + assert_difference CustomFormsPlugin::Submission, :count, 1 do
  22 + task.finish
  23 + end
  24 +
  25 + submission = CustomFormsPlugin::Submission.last
  26 + assert_equal submission.answers.count, 1
  27 +
  28 + answer = submission.answers.first
  29 + assert_equal answer.value, 'Jack'
  30 + end
  31 +end
plugins/custom_forms/test/unit/custom_forms_plugin/select_field_test.rb 0 → 100644
@@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
  1 +require File.dirname(__FILE__) + '/../../../../../test/test_helper'
  2 +
  3 +class CustomFormsPlugin::SelectFieldTest < ActiveSupport::TestCase
  4 + should 'validate presence of choices, multiple and list' do
  5 + select = CustomFormsPlugin::SelectField.new
  6 + select.valid?
  7 + assert select.errors.invalid?(:choices)
  8 +
  9 + select.choices = {'label' => 'value'}
  10 + select.valid?
  11 + assert !select.errors.invalid?(:choices)
  12 + end
  13 +end
plugins/custom_forms/test/unit/custom_forms_plugin/submission_test.rb 0 → 100644
@@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
  1 +require File.dirname(__FILE__) + '/../../../../../test/test_helper'
  2 +
  3 +class CustomFormsPlugin::SubmissionTest < ActiveSupport::TestCase
  4 + should 'validates presence of form' do
  5 + submission = CustomFormsPlugin::Submission.new
  6 + submission.valid?
  7 + assert submission.errors.invalid?(:form)
  8 +
  9 + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile))
  10 + submission.form = form
  11 + submission.valid?
  12 + assert !submission.errors.invalid?(:form)
  13 + end
  14 +
  15 + should 'belong to a profile' do
  16 + profile = fast_create(Profile)
  17 + submission = CustomFormsPlugin::Submission.new
  18 + submission.profile = profile
  19 + assert_equal profile, submission.profile
  20 + end
  21 +
  22 + should 'require presence of author name and email if profile is nil' do
  23 + submission = CustomFormsPlugin::Submission.new
  24 + submission.valid?
  25 + assert submission.errors.invalid?(:author_name)
  26 + assert submission.errors.invalid?(:author_email)
  27 +
  28 + submission.author_name = 'Jack Sparrow'
  29 + submission.author_email = 'jack@black-pearl.com'
  30 + submission.valid?
  31 + assert !submission.errors.invalid?(:author_name)
  32 + assert !submission.errors.invalid?(:author_email)
  33 + end
  34 +
  35 + should 'have answers' do
  36 + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile))
  37 + field = CustomFormsPlugin::Field.create!(:name => 'License', :form => form)
  38 + submission = CustomFormsPlugin::Submission.create!(:form => form, :profile => fast_create(Profile))
  39 + a1 = CustomFormsPlugin::Answer.create!(:field => field, :submission => submission)
  40 + a2 = CustomFormsPlugin::Answer.create!(:field => field, :submission => submission)
  41 +
  42 + assert_includes submission.answers, a1
  43 + assert_includes submission.answers, a2
  44 + end
  45 +end
  46 +
plugins/custom_forms/test/unit/ext/role_assingment_test.rb 0 → 100644
@@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
  1 +require File.dirname(__FILE__) + '/../../../../../test/test_helper'
  2 +
  3 +class RoleAssignmentsTest < ActiveSupport::TestCase
  4 + should 'create membership_surveys on membership creation' do
  5 + environment = Environment.default
  6 + environment.enable_plugin(CustomFormsPlugin)
  7 + organization = fast_create(Organization)
  8 + person = fast_create(Person)
  9 + f1 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 1', :on_membership => true)
  10 + f2 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 2', :on_membership => true)
  11 + f3 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 3', :on_membership => false)
  12 +
  13 + assert_difference CustomFormsPlugin::MembershipSurvey, :count, 2 do
  14 + organization.add_member(person)
  15 + end
  16 + end
  17 +
  18 + should 'create membership_survey on membership creation with form accessible to members only' do
  19 + environment = Environment.default
  20 + environment.enable_plugin(CustomFormsPlugin)
  21 + organization = fast_create(Organization)
  22 + person = fast_create(Person)
  23 + form = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form', :on_membership => true, :access => 'associated')
  24 +
  25 + assert_difference CustomFormsPlugin::MembershipSurvey, :count, 1 do
  26 + organization.add_member(person)
  27 + end
  28 + end
  29 +
  30 + should 'cancel membership_surveys if membership is undone and task is active' do
  31 + environment = Environment.default
  32 + environment.enable_plugin(CustomFormsPlugin)
  33 + organization = fast_create(Organization)
  34 + person = fast_create(Person)
  35 + form = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form', :on_membership => true)
  36 + organization.add_member(person)
  37 +
  38 + assert_difference CustomFormsPlugin::MembershipSurvey.pending, :count, -1 do
  39 + organization.remove_member(person)
  40 + end
  41 +
  42 + organization.add_member(person)
  43 + task = CustomFormsPlugin::MembershipSurvey.last
  44 + task.status = Task::Status::FINISHED
  45 + task.save!
  46 + assert_no_difference CustomFormsPlugin::MembershipSurvey.finished, :count do
  47 + organization.remove_member(person)
  48 + end
  49 + end
  50 +end
  51 +
plugins/custom_forms/views/custom_forms_plugin_myprofile/_edit_select.html.erb 0 → 100644
@@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
  1 +<% elem_id = "edit-select-#{counter}" %>
  2 +<div id=<%= elem_id %> class='edit-information'>
  3 + <h2><%= _('Options') %></h2>
  4 + <table class='action-table' style='width: 420px'>
  5 + <tr>
  6 + <th style='width: 40%'><%= _('Name') %></th>
  7 + <th style='width: 40%'><%= _('Value') %></th>
  8 + <th style='width: 20%'><%= _('Actions') %></th>
  9 + </tr>
  10 + <% option_counter = 1 %>
  11 + <% (field.choices || {}).each do |name, value| %>
  12 + <%= render :partial => 'option', :locals => {:name => name, :value => value, :counter => counter, :option_counter => option_counter} %>
  13 + <% option_counter += 1 %>
  14 + <% end %>
  15 + <%= render :partial => 'empty_option', :locals => {:counter => counter, :option_counter => option_counter} %>
  16 + <tr class='new-item'>
  17 + <td colspan='3'>
  18 + <%= button(:add, _('Add a new option'), '#', :class => 'new-option', :field_id => counter)%>
  19 + </td>
  20 + </tr>
  21 + </table>
  22 +
  23 + <h3><%= _('Type') %></h3>
  24 + <%= labelled_radio_button 'Radio', "fields[#{counter}][kind]", 'radio', !field.multiple && !field.list %><br />
  25 + <%= labelled_radio_button 'Checkbox', "fields[#{counter}][kind]", 'check_box', field.multiple && !field.list %><br />
  26 + <%= labelled_radio_button 'Select', "fields[#{counter}][kind]", 'select', !field.multiple && field.list %><br />
  27 + <%= labelled_radio_button 'Multiple Select', "fields[#{counter}][kind]", 'multiple_select', field.multiple && field.list %><br />
  28 +
  29 + <% button_bar do %>
  30 + <%= button :ok, _('Ok'), '#', :class => 'colorbox-ok-button', :div_id => elem_id %>
  31 + <% end %>
  32 +</div>
  33 +
plugins/custom_forms/views/custom_forms_plugin_myprofile/_edit_text.html.erb 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +<% elem_id = "edit-text-#{counter}" %>
  2 +<div id=<%= elem_id %> class='edit-information'>
  3 + <h2><%= _('Default value') %></h2>
  4 + <%= labelled_form_field _('Default value'), text_field("fields[#{counter}]", :default_value, :value => field.default_value) %>
  5 +
  6 + <% button_bar do %>
  7 + <%= button :ok, _('Ok'), '#', :class => 'colorbox-ok-button', :div_id => elem_id %>
  8 + <% end %>
  9 +</div>
  10 +
plugins/custom_forms/views/custom_forms_plugin_myprofile/_empty_field.html.erb 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +<tr id="empty-field" style='display: none' last_id=<%= counter %>>
  2 + <td style="text-align: left"><%= text_field "fields[#{counter}]", :name, :size => 25 %></td>
  3 + <td><%= select "fields[#{counter}]", :type, type_options, :selected => type_for_options(field.class) %></td>
  4 + <td><%= check_box "fields[#{counter}]", :mandatory %></td>
  5 + <%= hidden_field "fields[#{counter}]", :form_id, :value => @form.id %>
  6 + <td class='actions'>
  7 + <%= button_without_text :edit, _('Edit'), '', :field_id => counter %>
  8 + <%= button_without_text :remove, _('Remove'), '#', :class => 'remove-field', :field_id => counter, :confirm => _('Are you sure you want to remove this field?') %>
  9 + </td>
  10 +</tr>
plugins/custom_forms/views/custom_forms_plugin_myprofile/_empty_option.html.erb 0 → 100644
@@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
  1 +<tr id=<%= "empty-option-#{counter}" %> option_id=<%= option_counter %> style="display: none;">
  2 + <td><%= text_field_tag("fields[#{counter}][choices][#{option_counter}][name]") %></td>
  3 + <td><%= text_field_tag("fields[#{counter}][choices][#{option_counter}][value]", nil, :size => 15) %></td>
  4 + <td class='actions'>
  5 + <%= button_without_text :remove, _('Remove'), '#', :class => 'remove-option', :field_id => counter, :option_id => option_counter, :confirm => _('Are you sure you want to remove this option?') %>
  6 + </td>
  7 +</tr>
  8 +
plugins/custom_forms/views/custom_forms_plugin_myprofile/_field.html.erb 0 → 100644
@@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
  1 +<tr id=<%= "field-#{counter}" %>>
  2 + <td style="text-align: left"><%= text_field "fields[#{counter}]", :name, :value => field.name, :size => 25 %></td>
  3 + <td><%= type_to_label(field.type) %></td>
  4 + <%= hidden_field "fields[#{counter}]", :type, :value => type_for_options(field.class) %>
  5 + <td><%= check_box "fields[#{counter}]", :mandatory, :checked => field.mandatory %></td>
  6 + <%= hidden_field "fields[#{counter}]", :real_id, :value => field.id %>
  7 + <%= hidden_field "fields[#{counter}]", :form_id, :value => @form.id %>
  8 + <td class='actions'>
  9 + <%= button_without_text :edit, _('Edit'), '#', :field_id => counter %>
  10 + <%= button_without_text :remove, _('Remove'), '#', :class => 'remove-field', :field_id => counter, :confirm => _('Are you sure you want to remove this field?') %>
  11 + </td>
  12 +</tr>
plugins/custom_forms/views/custom_forms_plugin_myprofile/_form.html.erb 0 → 100644
@@ -0,0 +1,56 @@ @@ -0,0 +1,56 @@
  1 +<% self.extend(CustomFormsPlugin::Helper) %>
  2 +
  3 +<%= error_messages_for :form %>
  4 +
  5 +<% form_for :form, @form do |f| %>
  6 + <%= required labelled_form_field _('Name'), f.text_field(:name) %>
  7 + <%= labelled_form_field(_('Período'), (
  8 + date_range_field('form[begining]', 'form[ending]', @form.begining, @form.ending,
  9 + '%Y-%m-%d %H:%M',
  10 + { :time => true, :change_month => true, :change_year => true,
  11 + :date_format => 'yy-mm-dd', :time_format => 'hh:mm' },
  12 + { :size => 14 })
  13 + )) %>
  14 + <%= labelled_form_field _('Access'), f.select(:access, access_options(profile))%>
  15 + <% if profile.organization? %>
  16 + <%= labelled_form_field _('Triggered on membership'), f.check_box(:on_membership) %>
  17 + <% end %>
  18 + <%= labelled_form_field _('Description'), f.text_area(:description, :style => 'width: 100%') %>
  19 +
  20 + <h2><%= _('Fields') %></h2>
  21 + <table class="action-table" id='fields-table'>
  22 + <tr>
  23 + <th style='width: 40%'><%= _('Name') %></th>
  24 + <th style='width: 30%'><%= _('Type') %></th>
  25 + <th style='width: 10%'><%= _('Mandatory') %></th>
  26 + <th style='width: 20%'><%= _('Actions') %></th>
  27 + </tr>
  28 + <% counter = 1 %>
  29 + <% @fields.each do |field| %>
  30 + <%= render :partial => 'field', :locals => {:field => field, :counter => counter} %>
  31 + <% counter += 1 %>
  32 + <% end %>
  33 + <%= render :partial => 'empty_field', :locals => {:field => @empty_field, :counter => counter} %>
  34 + <tr class='new-item'>
  35 + <td colspan='5'>
  36 + <%= button(:add, _('Add a new field'), '#', :id => 'new-field')%>
  37 + </td>
  38 + </tr>
  39 + </table>
  40 +
  41 + <% counter = 1 %>
  42 + <% @fields.each do |field| %>
  43 + <%= render :partial => 'edit_text', :locals => {:field => field, :counter => counter} %>
  44 + <%= render :partial => 'edit_select', :locals => {:field => field, :counter => counter} %>
  45 + <% counter += 1 %>
  46 + <% end %>
  47 +
  48 + <%= render :partial => 'edit_text', :locals => {:field => @empty_field, :counter => counter} %>
  49 + <%= render :partial => 'edit_select', :locals => {:field => @empty_field, :counter => counter} %>
  50 +
  51 + <% button_bar do %>
  52 + <%= submit_button :save, _('Save'), :cancel => {:action => 'index'}%>
  53 + <% end %>
  54 +<% end %>
  55 +
  56 +<%= javascript_include_tag '../plugins/custom_forms/field' %>
plugins/custom_forms/views/custom_forms_plugin_myprofile/_option.html.erb 0 → 100644
@@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
  1 +<tr id=<%= "field-#{counter}-option-#{option_counter}" %> style="display: auto;">
  2 + <td><%= text_field_tag("fields[#{counter}][choices][#{option_counter}][name]", name) %></td>
  3 + <td><%= text_field_tag("fields[#{counter}][choices][#{option_counter}][value]", value) %></td>
  4 + <td class='actions'>
  5 + <%= button_without_text :remove, _('Remove'), '#', :class => 'remove-option', :field_id => counter, :option_id => option_counter, :confirm => _('Are you sure you want to remove this option?') %>
  6 + </td>
  7 +</tr>
plugins/custom_forms/views/custom_forms_plugin_myprofile/create.html.erb 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +<h1><%= _('New form') %></h1>
  2 +<%= render :partial => 'form' %>
plugins/custom_forms/views/custom_forms_plugin_myprofile/edit.html.erb 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +<h1><%= _('Edit form') %></h1>
  2 +<%= render :partial => 'form' %>
plugins/custom_forms/views/custom_forms_plugin_myprofile/index.html.erb 0 → 100644
@@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
  1 +<% self.extend CustomFormsPlugin::Helper %>
  2 +
  3 +<h1><%= _('Manage forms') %></h1>
  4 +<table class="action-table">
  5 + <tr>
  6 + <th style='width: 40%'><%= _('Name') %></th>
  7 + <th style='width: 30%'><%= _('Period') %></th>
  8 + <th style='width: 10%'><%= _('Submissions') %></th>
  9 + <th style='width: 10%'><%= _('Access') %></th>
  10 + <th style='width: 10%'><%= _('Actions') %></th>
  11 + </tr>
  12 + <% @forms.each do |form| %>
  13 + <tr>
  14 + <td><%= link_to form.name, {:controller => 'custom_forms_plugin_profile', :action => 'show', :id => form.id} %></td>
  15 + <td><%= period_range(form) %></td>
  16 + <td><%= form.submissions.count > 0 ? link_to(form.submissions.count, {:action => 'submissions', :id => form.id}) : 0 %></td>
  17 + <td><%= access_text(form) %></td>
  18 + <td class="actions">
  19 + <%= button_without_text :edit, _('Edit'), :action => 'edit', :id => form.id %>
  20 + <%= button_without_text :remove, _('Remove'), {:action => 'remove', :id => form.id}, :confirm => _('Are you sure you want to remove this form?') %></td>
  21 + </tr>
  22 + <% end %>
  23 + <tr id="new-item">
  24 + <td colspan='5'>
  25 + <%= button(:add, _('Add a new form'), :action => 'create')%>
  26 + </td>
  27 + </tr>
  28 +</table>
plugins/custom_forms/views/custom_forms_plugin_myprofile/show_submission.html.erb 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +<h1><%= @form.name %></h1>
  2 +<p><%= @form.description %></p>
  3 +
  4 +<% fields_for :submission, @submission do |f| %>
  5 + <%= render :partial => 'shared/form_submission', :locals => {:f => f} %>
  6 +<% end %>
  7 +
  8 +<% button_bar do %>
  9 + <%= button :back, _('Back to submissions'), :action => 'submissions', :id => @form.id %>
  10 +<% end %>
plugins/custom_forms/views/custom_forms_plugin_myprofile/submissions.html.erb 0 → 100644
@@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
  1 +<% self.extend CustomFormsPlugin::Helper %>
  2 +
  3 +<h1><%= _('Submissions for %s') % @form.name %></h1>
  4 +
  5 +<% if @form.submissions.empty? %>
  6 + <%= _('There are no submissions for this form.') %>
  7 +<% else %>
  8 + <table class="action-table">
  9 + <tr>
  10 + <th style='width: 50%'><%= _('Author') %></th>
  11 + <th style='width: 50%'><%= _('Time') %></th>
  12 + </tr>
  13 + <% @submissions.each do |submission| %>
  14 + <tr>
  15 + <% author = submission.profile.present? ? submission.profile.name : submission.author_name %>
  16 + <td><%= link_to(author, {:action => 'show_submission', :id => submission.id}) %></td>
  17 + <td><%= time_format(submission.created_at) %></td>
  18 + </tr>
  19 + <% end %>
  20 + </table>
  21 +<% end %>
  22 +
  23 +<% button_bar do %>
  24 + <%= button :back, _('Back to forms'), :action => 'index' %>
  25 +<% end %>
plugins/custom_forms/views/custom_forms_plugin_profile/show.html.erb 0 → 100644
@@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
  1 +<h1><%= @form.name %></h1>
  2 +<p><%= @form.description %></p>
  3 +
  4 +<% if @submission.id.nil? %>
  5 + <%= error_messages_for :submission %>
  6 +
  7 + <% form_for :submission, @submission do |f| %>
  8 + <% if !user %>
  9 + <%= required labelled_form_field _('Author name'), text_field_tag(:author_name, @submission.author_name) %>
  10 + <%= required labelled_form_field _('Author email'), text_field_tag(:author_email, @submission.author_email) %>
  11 + <% end %>
  12 +
  13 + <%= render :partial => 'shared/form_submission', :locals => {:f => f} %>
  14 +
  15 + <% button_bar do %>
  16 + <%= submit_button :save, _('Save'), :cancel => {:action => 'index'} %>
  17 + <% end %>
  18 + <% end %>
  19 +<% else %>
  20 + <% fields_for :submission, @submission do |f| %>
  21 + <%= render :partial => 'shared/form_submission', :locals => {:f => f} %>
  22 + <% end %>
  23 +<% end %>
plugins/custom_forms/views/shared/_form_submission.html.erb 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +<% self.extend(CustomFormsPlugin::Helper) %>
  2 +
  3 +<% @form.fields.each do |field| %>
  4 + <%= display_custom_field(field, @submission, f.object_name) %>
  5 +<% end %>
plugins/custom_forms/views/tasks/_membership_survey_accept_details.html.erb 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +<%= f.object_name.to_s %>
plugins/custom_forms/views/tasks/custom_forms_plugin/_membership_survey_accept_details.html.erb 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +<% @form = CustomFormsPlugin::Form.find(task.form_id) %>
  2 +<% @submission = CustomFormsPlugin::Submission.new(:form_id => @form.id, :profile_id => user.id) %>
  3 +
  4 +<h2><%= @form.name %></h2>
  5 +<p><%= @form.description %></p>
  6 +
  7 +<% f.fields_for :submission do |fi| %>
  8 + <%#= fi.error_messages_for :submission %>
  9 + <%= render :partial => 'shared/form_submission', :locals => {:f => fi} %>
  10 +<% end %>
plugins/foo/controllers/admin/foo_plugin_admin_bar_controller.rb 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +class FooPluginAdminBarController < AdminController
  2 +end
  3 +
plugins/foo/controllers/myprofile/foo_plugin_myprofile_bar_controller.rb 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +class FooPluginMyprofileBarController < MyProfileController
  2 +end
  3 +
plugins/foo/controllers/profile/foo_plugin_profile_bar_controller.rb 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +class FooPluginProfileBarController < ProfileController
  2 +end
  3 +
plugins/foo/controllers/public/foo_plugin_public_bar_controller.rb 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +class FooPluginPublicBarController < PublicController
  2 +end
plugins/mezuro/controllers/mezuro_plugin_myprofile_controller.rb
@@ -2,7 +2,15 @@ class MezuroPluginMyprofileController &lt; ProfileController @@ -2,7 +2,15 @@ class MezuroPluginMyprofileController &lt; ProfileController
2 2
3 append_view_path File.join(File.dirname(__FILE__) + '/../views') 3 append_view_path File.join(File.dirname(__FILE__) + '/../views')
4 4
5 - 5 + rescue_from Exception do |exception|
  6 + message = URI.escape(CGI.escape(exception.message),'.')
  7 + redirect_to_error_page message
  8 + end
  9 +
  10 + def error_page
  11 + @message = params[:message]
  12 + end
  13 +
6 def choose_base_tool 14 def choose_base_tool
7 @configuration_content = profile.articles.find(params[:id]) 15 @configuration_content = profile.articles.find(params[:id])
8 @base_tools = Kalibro::BaseTool.all_names 16 @base_tools = Kalibro::BaseTool.all_names
@@ -11,19 +19,23 @@ class MezuroPluginMyprofileController &lt; ProfileController @@ -11,19 +19,23 @@ class MezuroPluginMyprofileController &lt; ProfileController
11 def choose_metric 19 def choose_metric
12 @configuration_content = profile.articles.find(params[:id]) 20 @configuration_content = profile.articles.find(params[:id])
13 @base_tool = params[:base_tool] 21 @base_tool = params[:base_tool]
14 - @supported_metrics = Kalibro::BaseTool.find_by_name(@base_tool).supported_metrics 22 + base_tool = Kalibro::BaseTool.find_by_name(@base_tool)
  23 + @supported_metrics = base_tool.nil? ? [] : base_tool.supported_metrics
15 end 24 end
16 - 25 +
17 def new_metric_configuration 26 def new_metric_configuration
18 @configuration_content = profile.articles.find(params[:id]) 27 @configuration_content = profile.articles.find(params[:id])
19 @metric = Kalibro::BaseTool.find_by_name(params[:base_tool]).metric params[:metric_name] 28 @metric = Kalibro::BaseTool.find_by_name(params[:base_tool]).metric params[:metric_name]
20 end 29 end
21 - 30 +
22 def new_compound_metric_configuration 31 def new_compound_metric_configuration
23 @configuration_content = profile.articles.find(params[:id]) 32 @configuration_content = profile.articles.find(params[:id])
24 @metric_configurations = @configuration_content.metric_configurations 33 @metric_configurations = @configuration_content.metric_configurations
  34 + if configuration_content_has_errors?
  35 + redirect_to_error_page @configuration_content.errors[:base]
  36 + end
25 end 37 end
26 - 38 +
27 def edit_metric_configuration 39 def edit_metric_configuration
28 @configuration_content = profile.articles.find(params[:id]) 40 @configuration_content = profile.articles.find(params[:id])
29 @metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(@configuration_content.name, params[:metric_name]) 41 @metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(@configuration_content.name, params[:metric_name])
@@ -36,19 +48,29 @@ class MezuroPluginMyprofileController &lt; ProfileController @@ -36,19 +48,29 @@ class MezuroPluginMyprofileController &lt; ProfileController
36 @metric_configurations = @configuration_content.metric_configurations 48 @metric_configurations = @configuration_content.metric_configurations
37 @metric = @metric_configuration.metric 49 @metric = @metric_configuration.metric
38 end 50 end
39 - 51 +
40 def create_metric_configuration 52 def create_metric_configuration
41 id = params[:id] 53 id = params[:id]
42 metric_name = params[:metric_configuration][:metric][:name] 54 metric_name = params[:metric_configuration][:metric][:name]
43 - (Kalibro::MetricConfiguration.new(params[:metric_configuration])).save  
44 - redirect_to "/myprofile/#{profile.identifier}/plugin/mezuro/edit_metric_configuration?id=#{id}&metric_name=#{metric_name.gsub(/\s/, '+')}" 55 + metric_configuration = Kalibro::MetricConfiguration.new(params[:metric_configuration])
  56 + metric_configuration.save
  57 + if metric_configuration_has_errors? metric_configuration
  58 + redirect_to_error_page metric_configuration.errors[0].message
  59 + else
  60 + redirect_to "/myprofile/#{profile.identifier}/plugin/mezuro/edit_metric_configuration?id=#{id}&metric_name=#{metric_name.gsub(/\s/, '+')}"
  61 + end
45 end 62 end
46 - 63 +
47 def create_compound_metric_configuration 64 def create_compound_metric_configuration
48 id = params[:id] 65 id = params[:id]
49 metric_name = params[:metric_configuration][:metric][:name] 66 metric_name = params[:metric_configuration][:metric][:name]
50 - Kalibro::MetricConfiguration.new(params[:metric_configuration]).save  
51 - redirect_to "/myprofile/#{profile.identifier}/plugin/mezuro/edit_compound_metric_configuration?id=#{id}&metric_name=#{metric_name.gsub(/\s/, '+')}" 67 + metric_configuration = Kalibro::MetricConfiguration.new(params[:metric_configuration])
  68 + metric_configuration.save
  69 + if metric_configuration_has_errors? metric_configuration
  70 + redirect_to_error_page metric_configuration.errors[0].message
  71 + else
  72 + redirect_to "/myprofile/#{profile.identifier}/plugin/mezuro/edit_compound_metric_configuration?id=#{id}&metric_name=#{metric_name.gsub(/\s/, '+')}"
  73 + end
52 end 74 end
53 75
54 def update_metric_configuration 76 def update_metric_configuration
@@ -56,7 +78,11 @@ class MezuroPluginMyprofileController &lt; ProfileController @@ -56,7 +78,11 @@ class MezuroPluginMyprofileController &lt; ProfileController
56 metric_name = params[:metric_configuration][:metric][:name] 78 metric_name = params[:metric_configuration][:metric][:name]
57 metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(@configuration_content.name, metric_name) 79 metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(@configuration_content.name, metric_name)
58 metric_configuration.update_attributes params[:metric_configuration] 80 metric_configuration.update_attributes params[:metric_configuration]
59 - redirect_to "/#{profile.identifier}/#{@configuration_content.slug}" 81 + if metric_configuration_has_errors? metric_configuration
  82 + redirect_to_error_page metric_configuration.errors[0].message
  83 + else
  84 + redirect_to "/#{profile.identifier}/#{@configuration_content.slug}"
  85 + end
60 end 86 end
61 87
62 def update_compound_metric_configuration 88 def update_compound_metric_configuration
@@ -64,7 +90,11 @@ class MezuroPluginMyprofileController &lt; ProfileController @@ -64,7 +90,11 @@ class MezuroPluginMyprofileController &lt; ProfileController
64 metric_name = params[:metric_configuration][:metric][:name] 90 metric_name = params[:metric_configuration][:metric][:name]
65 metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(@configuration_content.name, metric_name) 91 metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(@configuration_content.name, metric_name)
66 metric_configuration.update_attributes params[:metric_configuration] 92 metric_configuration.update_attributes params[:metric_configuration]
67 - redirect_to "/#{profile.identifier}/#{@configuration_content.slug}" 93 + if metric_configuration_has_errors? metric_configuration
  94 + redirect_to_error_page metric_configuration.errors[0].message
  95 + else
  96 + redirect_to "/#{profile.identifier}/#{@configuration_content.slug}"
  97 + end
68 end 98 end
69 99
70 def remove_metric_configuration 100 def remove_metric_configuration
@@ -72,32 +102,41 @@ class MezuroPluginMyprofileController &lt; ProfileController @@ -72,32 +102,41 @@ class MezuroPluginMyprofileController &lt; ProfileController
72 metric_name = params[:metric_name] 102 metric_name = params[:metric_name]
73 metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(configuration_content.name, metric_name) 103 metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(configuration_content.name, metric_name)
74 metric_configuration.destroy 104 metric_configuration.destroy
75 - redirect_to "/#{profile.identifier}/#{configuration_content.slug}" 105 + if metric_configuration_has_errors? metric_configuration
  106 + redirect_to_error_page metric_configuration.errors[0].message
  107 + else
  108 + redirect_to "/#{profile.identifier}/#{configuration_content.slug}"
  109 + end
76 end 110 end
77 - 111 +
78 def new_range 112 def new_range
79 @configuration_content = profile.articles.find(params[:id]) 113 @configuration_content = profile.articles.find(params[:id])
80 @metric_name = params[:metric_name] 114 @metric_name = params[:metric_name]
81 @range = Kalibro::Range.new 115 @range = Kalibro::Range.new
  116 + @range_color = "#000000"
82 end 117 end
83 - 118 +
84 def edit_range 119 def edit_range
85 @configuration_content = profile.articles.find(params[:id]) 120 @configuration_content = profile.articles.find(params[:id])
86 @metric_name = params[:metric_name] 121 @metric_name = params[:metric_name]
87 @beginning_id = params[:beginning_id] 122 @beginning_id = params[:beginning_id]
88 metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(@configuration_content.name, @metric_name) 123 metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(@configuration_content.name, @metric_name)
89 @range = metric_configuration.ranges.find{|range| range.beginning == @beginning_id.to_f || @beginning_id =="-INF" } 124 @range = metric_configuration.ranges.find{|range| range.beginning == @beginning_id.to_f || @beginning_id =="-INF" }
  125 + @range_color = "#" + @range.color.to_s.gsub(/^ff/, "")
90 end 126 end
91 127
92 def create_range 128 def create_range
93 @configuration_content = profile.articles.find(params[:id]) 129 @configuration_content = profile.articles.find(params[:id])
94 @range = Kalibro::Range.new params[:range] 130 @range = Kalibro::Range.new params[:range]
95 metric_name = params[:metric_name] 131 metric_name = params[:metric_name]
96 - metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(@configuration_content.name, metric_name) 132 + metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(@configuration_content.name, metric_name)
97 metric_configuration.add_range(@range) 133 metric_configuration.add_range(@range)
98 metric_configuration.save 134 metric_configuration.save
  135 + if metric_configuration_has_errors? metric_configuration
  136 + redirect_to_error_page metric_configuration.errors[0].message
  137 + end
99 end 138 end
100 - 139 +
101 def update_range 140 def update_range
102 configuration_content = profile.articles.find(params[:id]) 141 configuration_content = profile.articles.find(params[:id])
103 metric_name = params[:metric_name] 142 metric_name = params[:metric_name]
@@ -106,8 +145,11 @@ class MezuroPluginMyprofileController &lt; ProfileController @@ -106,8 +145,11 @@ class MezuroPluginMyprofileController &lt; ProfileController
106 index = metric_configuration.ranges.index{ |range| range.beginning == beginning_id.to_f || beginning_id == "-INF" } 145 index = metric_configuration.ranges.index{ |range| range.beginning == beginning_id.to_f || beginning_id == "-INF" }
107 metric_configuration.ranges[index] = Kalibro::Range.new params[:range] 146 metric_configuration.ranges[index] = Kalibro::Range.new params[:range]
108 metric_configuration.save 147 metric_configuration.save
  148 + if metric_configuration_has_errors? metric_configuration
  149 + redirect_to_error_page metric_configuration.errors[0].message
  150 + end
109 end 151 end
110 - 152 +
111 def remove_range 153 def remove_range
112 configuration_content = profile.articles.find(params[:id]) 154 configuration_content = profile.articles.find(params[:id])
113 metric_name = params[:metric_name] 155 metric_name = params[:metric_name]
@@ -115,12 +157,31 @@ class MezuroPluginMyprofileController &lt; ProfileController @@ -115,12 +157,31 @@ class MezuroPluginMyprofileController &lt; ProfileController
115 metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(configuration_content.name, metric_name) 157 metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(configuration_content.name, metric_name)
116 metric_configuration.ranges.delete_if { |range| range.beginning == beginning_id.to_f || beginning_id == "-INF" } 158 metric_configuration.ranges.delete_if { |range| range.beginning == beginning_id.to_f || beginning_id == "-INF" }
117 metric_configuration.save 159 metric_configuration.save
118 - formatted_metric_name = metric_name.gsub(/\s/, '+')  
119 - if metric_configuration.metric.class == Kalibro::CompoundMetric  
120 - redirect_to "/myprofile/#{profile.identifier}/plugin/mezuro/edit_compound_metric_configuration?id=#{configuration_content.id}&metric_name=#{formatted_metric_name}" 160 + if metric_configuration_has_errors? metric_configuration
  161 + redirect_to_error_page metric_configuration.errors[0].message
121 else 162 else
122 - redirect_to "/myprofile/#{profile.identifier}/plugin/mezuro/edit_metric_configuration?id=#{configuration_content.id}&metric_name=#{formatted_metric_name}" 163 + formatted_metric_name = metric_name.gsub(/\s/, '+')
  164 + if metric_configuration.metric.class == Kalibro::CompoundMetric
  165 + redirect_to "/myprofile/#{profile.identifier}/plugin/mezuro/edit_compound_metric_configuration?id=#{configuration_content.id}&metric_name=#{formatted_metric_name}"
  166 + else
  167 + redirect_to "/myprofile/#{profile.identifier}/plugin/mezuro/edit_metric_configuration?id=#{configuration_content.id}&metric_name=#{formatted_metric_name}"
  168 + end
123 end 169 end
124 end 170 end
125 171
  172 + private
  173 +
  174 + def redirect_to_error_page(message)
  175 + message = URI.escape(CGI.escape(message),'.')
  176 + redirect_to "/myprofile/#{profile.identifier}/plugin/mezuro/error_page?message=#{message}"
  177 + end
  178 +
  179 + def configuration_content_has_errors?
  180 + not @configuration_content.errors[:base].nil?
  181 + end
  182 +
  183 + def metric_configuration_has_errors? metric_configuration
  184 + not metric_configuration.errors.empty?
  185 + end
  186 +
126 end 187 end
plugins/mezuro/controllers/mezuro_plugin_profile_controller.rb
1 class MezuroPluginProfileController < ProfileController 1 class MezuroPluginProfileController < ProfileController
2 2
3 append_view_path File.join(File.dirname(__FILE__) + '/../views') 3 append_view_path File.join(File.dirname(__FILE__) + '/../views')
4 - 4 +
  5 + def error_page
  6 + @message = params[:message]
  7 + end
  8 +
5 def project_state 9 def project_state
6 @content = profile.articles.find(params[:id]) 10 @content = profile.articles.find(params[:id])
7 project = @content.project 11 project = @content.project
8 - state = project.error.nil? ? project.state : "ERROR"  
9 - render :text => state 12 + if project_content_has_errors?
  13 + redirect_to_error_page(@content.errors[:base])
  14 + else
  15 + state = project.kalibro_error.nil? ? project.state : "ERROR"
  16 + render :text => state
  17 + end
10 end 18 end
11 19
12 def project_error 20 def project_error
13 @content = profile.articles.find(params[:id]) 21 @content = profile.articles.find(params[:id])
14 @project = @content.project 22 @project = @content.project
15 - render :partial => 'content_viewer/project_error' 23 + if project_content_has_errors?
  24 + redirect_to_error_page(@content.errors[:base])
  25 + else
  26 + render :partial => 'content_viewer/project_error'
  27 + end
16 end 28 end
17 29
18 def project_result 30 def project_result
19 @content = profile.articles.find(params[:id]) 31 @content = profile.articles.find(params[:id])
20 date = params[:date] 32 date = params[:date]
21 @project_result = date.nil? ? @content.project_result : @content.project_result_with_date(date) 33 @project_result = date.nil? ? @content.project_result : @content.project_result_with_date(date)
22 - render :partial => 'content_viewer/project_result'  
23 - end 34 + if project_content_has_errors?
  35 + redirect_to_error_page(@content.errors[:base])
  36 + else
  37 + render :partial => 'content_viewer/project_result'
  38 + end
  39 + end
24 40
25 def module_result 41 def module_result
26 @content = profile.articles.find(params[:id]) 42 @content = profile.articles.find(params[:id])
27 @module_result = @content.module_result(params) 43 @module_result = @content.module_result(params)
28 - render :partial => 'content_viewer/module_result' 44 + @module = @module_result.module
  45 + @module_label = "#{@module.name} (#{@module.granularity})"
  46 + if project_content_has_errors?
  47 + redirect_to_error_page(@content.errors[:base])
  48 + else
  49 + render :partial => 'content_viewer/module_result'
  50 + end
29 end 51 end
30 52
31 def project_tree 53 def project_tree
32 @content = profile.articles.find(params[:id]) 54 @content = profile.articles.find(params[:id])
33 date = params[:date] 55 date = params[:date]
34 project_result = date.nil? ? @content.project_result : @content.project_result_with_date(date) 56 project_result = date.nil? ? @content.project_result : @content.project_result_with_date(date)
35 - @project_name = @content.project.name  
36 - @source_tree = project_result.node_of(params[:module_name])  
37 - render :partial =>'content_viewer/source_tree' 57 + @project_name = @content.project.name if not @content.project.nil?
  58 + if project_content_has_errors?
  59 + redirect_to_error_page(@content.errors[:base])
  60 + else
  61 + @source_tree = project_result.node(params[:module_name])
  62 + render :partial =>'content_viewer/source_tree'
  63 + end
38 end 64 end
39 65
40 def module_metrics_history 66 def module_metrics_history
41 metric_name = params[:metric_name] 67 metric_name = params[:metric_name]
42 @content = profile.articles.find(params[:id]) 68 @content = profile.articles.find(params[:id])
43 module_history = @content.result_history(params[:module_name]) 69 module_history = @content.result_history(params[:module_name])
44 - @score_history = filtering_metric_history(metric_name, module_history)  
45 - render :partial => 'content_viewer/score_history' 70 + if project_content_has_errors?
  71 + redirect_to_error_page(@content.errors[:base])
  72 + else
  73 + @score_history = filtering_metric_history(metric_name, module_history)
  74 + render :partial => 'content_viewer/score_history'
  75 + end
46 end 76 end
47 77
48 def module_grade_history 78 def module_grade_history
49 @content = profile.articles.find(params[:id]) 79 @content = profile.articles.find(params[:id])
50 modules_results = @content.result_history(params[:module_name]) 80 modules_results = @content.result_history(params[:module_name])
51 - @score_history = modules_results.collect { |module_result| module_result.grade }  
52 - render :partial => 'content_viewer/score_history' 81 + if project_content_has_errors?
  82 + redirect_to_error_page(@content.errors[:base])
  83 + else
  84 + @score_history = modules_results.map do |module_result|
  85 + [module_result.grade, format_date_to_simple_form(module_result.date)]
  86 + end
  87 + render :partial => 'content_viewer/score_history'
  88 + end
53 end 89 end
54 - 90 +
55 private 91 private
56 - 92 +
57 def filtering_metric_history(metric_name, module_history) 93 def filtering_metric_history(metric_name, module_history)
58 metrics_history = module_history.map do |module_result| 94 metrics_history = module_history.map do |module_result|
59 - module_result.metric_results 95 + [module_result.metric_results, format_date_to_simple_form(module_result.date)]
60 end 96 end
61 - metric_history = metrics_history.map do |array_of_metric_result|  
62 - (array_of_metric_result.select do |metric_result| 97 + metric_history = metrics_history.map do |metric_results_with_date|
  98 + [(metric_results_with_date.first.select do |metric_result|
63 metric_result.metric.name.delete("() ") == metric_name 99 metric_result.metric.name.delete("() ") == metric_name
64 - end).first 100 + end).first, metric_results_with_date.last]
65 end 101 end
66 - metric_history.map do |metric_result|  
67 - metric_result.value 102 + metric_history.map do |metric_result_with_date|
  103 + [metric_result_with_date.first.value, metric_result_with_date.last]
68 end 104 end
69 end 105 end
  106 +
  107 + def redirect_to_error_page(message)
  108 + message = URI.escape(CGI.escape(message),'.')
  109 + redirect_to "/profile/#{profile.identifier}/plugins/mezuro/error_page?message=#{message}"
  110 + end
  111 +
  112 + def project_content_has_errors?
  113 + not @content.errors[:base].nil?
  114 + end
  115 +
  116 + def format_date_to_simple_form date
  117 + date.to_s[0..9]
  118 + end
  119 +
70 end 120 end
plugins/mezuro/lib/kalibro/configuration.rb
@@ -7,11 +7,7 @@ class Kalibro::Configuration &lt; Kalibro::Model @@ -7,11 +7,7 @@ class Kalibro::Configuration &lt; Kalibro::Model
7 end 7 end
8 8
9 def metric_configurations 9 def metric_configurations
10 - if @metric_configuration != nil  
11 - @metric_configuration  
12 - else  
13 - []  
14 - end 10 + @metric_configuration.nil? ? [] : @metric_configuration
15 end 11 end
16 12
17 def metric_configurations=(metric_configurations) 13 def metric_configurations=(metric_configurations)
@@ -19,19 +15,11 @@ class Kalibro::Configuration &lt; Kalibro::Model @@ -19,19 +15,11 @@ class Kalibro::Configuration &lt; Kalibro::Model
19 end 15 end
20 16
21 def self.find_by_name(configuration_name) 17 def self.find_by_name(configuration_name)
22 - begin  
23 - new request("Configuration", :get_configuration, {:configuration_name => configuration_name})[:configuration]  
24 - rescue Exception => error  
25 - nil  
26 - end 18 + new request("Configuration", :get_configuration, {:configuration_name => configuration_name})[:configuration]
27 end 19 end
28 20
29 def self.all_names 21 def self.all_names
30 - begin  
31 - request("Configuration", :get_configuration_names)[:configuration_name]  
32 - rescue Exception  
33 - []  
34 - end 22 + request("Configuration", :get_configuration_names)[:configuration_name]
35 end 23 end
36 24
37 def update_attributes(attributes={}) 25 def update_attributes(attributes={})
plugins/mezuro/lib/kalibro/metric_configuration.rb
@@ -49,10 +49,14 @@ class Kalibro::MetricConfiguration &lt; Kalibro::Model @@ -49,10 +49,14 @@ class Kalibro::MetricConfiguration &lt; Kalibro::Model
49 end 49 end
50 50
51 def destroy 51 def destroy
52 - self.class.request("MetricConfiguration", :remove_metric_configuration, { 52 + begin
  53 + self.class.request("MetricConfiguration", :remove_metric_configuration, {
53 :configuration_name => configuration_name, 54 :configuration_name => configuration_name,
54 :metric_name=> metric.name 55 :metric_name=> metric.name
55 }) 56 })
  57 + rescue Exception => exception
  58 + add_error exception
  59 + end
56 end 60 end
57 61
58 def to_hash 62 def to_hash
plugins/mezuro/lib/kalibro/model.rb
1 class Kalibro::Model 1 class Kalibro::Model
2 2
  3 + attr_accessor :errors
  4 +
3 def initialize(attributes={}) 5 def initialize(attributes={})
4 attributes.each { |field, value| send("#{field}=", value) if self.class.is_valid?(field) } 6 attributes.each { |field, value| send("#{field}=", value) if self.class.is_valid?(field) }
  7 + @errors = []
5 end 8 end
6 9
7 def to_hash(options={}) 10 def to_hash(options={})
8 hash = Hash.new 11 hash = Hash.new
9 excepts = !options[:except].nil? ? options[:except] : [] 12 excepts = !options[:except].nil? ? options[:except] : []
  13 + excepts << :errors
10 fields.each do |field| 14 fields.each do |field|
11 if(!excepts.include?(field)) 15 if(!excepts.include?(field))
12 field_value = send(field) 16 field_value = send(field)
@@ -46,15 +50,17 @@ class Kalibro::Model @@ -46,15 +50,17 @@ class Kalibro::Model
46 begin 50 begin
47 self.class.request(save_endpoint, save_action, save_params) 51 self.class.request(save_endpoint, save_action, save_params)
48 true 52 true
49 - rescue Exception => error  
50 - false 53 + rescue Exception => exception
  54 + add_error exception
  55 + false
51 end 56 end
52 end 57 end
53 58
54 def destroy 59 def destroy
55 begin 60 begin
56 self.class.request(destroy_endpoint, destroy_action, destroy_params) 61 self.class.request(destroy_endpoint, destroy_action, destroy_params)
57 - rescue Exception 62 + rescue Exception => exception
  63 + add_error exception
58 end 64 end
59 end 65 end
60 66
@@ -123,4 +129,9 @@ class Kalibro::Model @@ -123,4 +129,9 @@ class Kalibro::Model
123 {"#{class_name.underscore}_name".to_sym => self.name} 129 {"#{class_name.underscore}_name".to_sym => self.name}
124 end 130 end
125 131
  132 + def add_error(exception)
  133 + @errors << exception
  134 + end
  135 +
126 end 136 end
  137 +
plugins/mezuro/lib/kalibro/project.rb
1 class Kalibro::Project < Kalibro::Model 1 class Kalibro::Project < Kalibro::Model
2 2
3 - attr_accessor :name, :license, :description, :repository, :configuration_name, :state, :error 3 + attr_accessor :name, :license, :description, :repository, :configuration_name, :state, :kalibro_error
4 4
5 def self.all_names 5 def self.all_names
6 request("Project", :get_project_names)[:project_name] 6 request("Project", :get_project_names)[:project_name]
@@ -15,23 +15,35 @@ class Kalibro::Project &lt; Kalibro::Model @@ -15,23 +15,35 @@ class Kalibro::Project &lt; Kalibro::Model
15 end 15 end
16 16
17 def error=(value) 17 def error=(value)
18 - @error = Kalibro::Error.to_object value 18 + @kalibro_error = Kalibro::Error.to_object value
19 end 19 end
20 20
21 def process_project(days = '0') 21 def process_project(days = '0')
22 - if days.to_i.zero?  
23 - self.class.request("Kalibro", :process_project, {:project_name => name})  
24 - else  
25 - self.class.request("Kalibro", :process_periodically, {:project_name => name, :period_in_days => days})  
26 - end 22 + begin
  23 + if days.to_i.zero?
  24 + self.class.request("Kalibro", :process_project, {:project_name => name})
  25 + else
  26 + self.class.request("Kalibro", :process_periodically, {:project_name => name, :period_in_days => days})
  27 + end
  28 + rescue Exception => exception
  29 + add_error exception
  30 + end
27 end 31 end
28 32
29 def process_period 33 def process_period
30 - self.class.request("Kalibro", :get_process_period, {:project_name => name})[:period] 34 + begin
  35 + self.class.request("Kalibro", :get_process_period, {:project_name => name})[:period]
  36 + rescue Exception => exception
  37 + add_error exception
  38 + end
31 end 39 end
32 40
33 def cancel_periodic_process 41 def cancel_periodic_process
34 - self.class.request("Kalibro", :cancel_periodic_process, {:project_name => name}) 42 + begin
  43 + self.class.request("Kalibro", :cancel_periodic_process, {:project_name => name})
  44 + rescue Exception => exception
  45 + add_error exception
  46 + end
35 end 47 end
36 48
37 end 49 end
plugins/mezuro/lib/kalibro/project_result.rb
@@ -75,21 +75,17 @@ class Kalibro::ProjectResult &lt; Kalibro::Model @@ -75,21 +75,17 @@ class Kalibro::ProjectResult &lt; Kalibro::Model
75 ('%2d' % amount).sub(/\s/, '0') 75 ('%2d' % amount).sub(/\s/, '0')
76 end 76 end
77 77
78 - def node_of(module_name) 78 + def node(module_name)
79 if module_name.nil? or module_name == project.name 79 if module_name.nil? or module_name == project.name
80 node = source_tree 80 node = source_tree
81 else 81 else
82 - node = get_node(module_name)  
83 - end  
84 - end  
85 -  
86 - def get_node(module_name)  
87 - path = Kalibro::Module.parent_names(module_name)  
88 - parent = @source_tree  
89 - path.each do |node_name|  
90 - parent = get_leaf_from(parent, node_name)  
91 - end  
92 - return parent 82 + path = Kalibro::Module.parent_names(module_name)
  83 + parent = @source_tree
  84 + path.each do |node_name|
  85 + parent = get_leaf_from(parent, node_name)
  86 + end
  87 + parent
  88 + end
93 end 89 end
94 90
95 private 91 private
plugins/mezuro/lib/kalibro/range.rb
@@ -34,4 +34,12 @@ class Kalibro::Range &lt; Kalibro::Model @@ -34,4 +34,12 @@ class Kalibro::Range &lt; Kalibro::Model
34 @grade = value.to_f 34 @grade = value.to_f
35 end 35 end
36 36
  37 + def mezuro_color
  38 + @color.nil? ? "#e4ca2d" : @color.gsub(/^ff/, "#")
  39 + end
  40 +
  41 + def color=(new_color)
  42 + @color = new_color.gsub(/^#/, "ff")
  43 + end
  44 +
37 end 45 end
plugins/mezuro/lib/mezuro_plugin/configuration_content.rb
@@ -3,8 +3,8 @@ class MezuroPlugin::ConfigurationContent &lt; Article @@ -3,8 +3,8 @@ class MezuroPlugin::ConfigurationContent &lt; Article
3 3
4 settings_items :description, :configuration_to_clone_name 4 settings_items :description, :configuration_to_clone_name
5 5
6 - after_save :send_configuration_to_service  
7 - after_destroy :remove_configuration_from_service 6 + after_save :send_kalibro_configuration_to_service
  7 + after_destroy :remove_kalibro_configuration_from_service
8 8
9 def self.short_description 9 def self.short_description
10 'Kalibro configuration' 10 'Kalibro configuration'
@@ -21,54 +21,60 @@ class MezuroPlugin::ConfigurationContent &lt; Article @@ -21,54 +21,60 @@ class MezuroPlugin::ConfigurationContent &lt; Article
21 end 21 end
22 end 22 end
23 23
24 - def configuration  
25 - @configuration ||= Kalibro::Configuration.find_by_name(self.name)  
26 - if @configuration.nil?  
27 - errors.add_to_base("Kalibro Configuration not found") 24 + def kalibro_configuration
  25 + begin
  26 + @kalibro_configuration ||= Kalibro::Configuration.find_by_name(self.name)
  27 + rescue Exception => exception
  28 + errors.add_to_base(exception.message)
28 end 29 end
29 - @configuration 30 + @kalibro_configuration
30 end 31 end
31 32
32 def metric_configurations 33 def metric_configurations
33 - configuration.metric_configurations 34 + kalibro_configuration.metric_configurations
34 end 35 end
35 36
36 - def configuration_names  
37 - ["None"] + Kalibro::Configuration.all_names.sort 37 + def kalibro_configuration_names
  38 + begin
  39 + ["None"] + Kalibro::Configuration.all_names.sort
  40 + rescue Exception => exception
  41 + errors.add_to_base(exception.message)
  42 + ["None"]
  43 + end
38 end 44 end
39 45
40 private 46 private
41 47
42 def validate_kalibro_configuration_name 48 def validate_kalibro_configuration_name
43 - existing = configuration_names.map { |a| a.downcase} 49 + existing = kalibro_configuration_names.map { |a| a.downcase}
44 50
45 if existing.include?(name.downcase) 51 if existing.include?(name.downcase)
46 errors.add_to_base("Configuration name already exists in Kalibro") 52 errors.add_to_base("Configuration name already exists in Kalibro")
47 end 53 end
48 end 54 end
49 55
50 - def send_configuration_to_service  
51 - if editing_configuration?  
52 - configuration.update_attributes({:description => description}) 56 + def send_kalibro_configuration_to_service
  57 + if editing_kalibro_configuration?
  58 + kalibro_configuration.update_attributes({:description => description})
53 else 59 else
54 create_kalibro_configuration 60 create_kalibro_configuration
55 end 61 end
56 end 62 end
57 63
58 - def remove_configuration_from_service  
59 - configuration.destroy 64 + def remove_kalibro_configuration_from_service
  65 + kalibro_configuration.destroy unless kalibro_configuration.nil?
60 end 66 end
61 67
62 def create_kalibro_configuration 68 def create_kalibro_configuration
63 attributes = {:name => name, :description => description} 69 attributes = {:name => name, :description => description}
64 - if cloning_configuration? 70 + if cloning_kalibro_configuration?
65 attributes[:metric_configuration] = configuration_to_clone.metric_configurations_hash 71 attributes[:metric_configuration] = configuration_to_clone.metric_configurations_hash
66 end 72 end
67 Kalibro::Configuration.create attributes 73 Kalibro::Configuration.create attributes
68 end 74 end
69 75
70 - def editing_configuration?  
71 - configuration.present? 76 + def editing_kalibro_configuration?
  77 + kalibro_configuration.present?
72 end 78 end
73 79
74 def configuration_to_clone 80 def configuration_to_clone
@@ -76,10 +82,10 @@ class MezuroPlugin::ConfigurationContent &lt; Article @@ -76,10 +82,10 @@ class MezuroPlugin::ConfigurationContent &lt; Article
76 end 82 end
77 83
78 def find_configuration_to_clone 84 def find_configuration_to_clone
79 - configuration_to_clone_name.nil? ? nil : Kalibro::Configuration.find_by_name(configuration_to_clone_name) 85 + (configuration_to_clone_name == "None") ? nil : Kalibro::Configuration.find_by_name(configuration_to_clone_name)
80 end 86 end
81 87
82 - def cloning_configuration? 88 + def cloning_kalibro_configuration?
83 configuration_to_clone.present? 89 configuration_to_clone.present?
84 end 90 end
85 91
plugins/mezuro/lib/mezuro_plugin/helpers/content_viewer_helper.rb
1 class MezuroPlugin::Helpers::ContentViewerHelper 1 class MezuroPlugin::Helpers::ContentViewerHelper
  2 +
  3 + MAX_NUMBER_OF_LABELS = 5
  4 +
2 def self.format_grade(grade) 5 def self.format_grade(grade)
3 sprintf("%.2f", grade.to_f) 6 sprintf("%.2f", grade.to_f)
4 end 7 end
@@ -6,15 +9,31 @@ class MezuroPlugin::Helpers::ContentViewerHelper @@ -6,15 +9,31 @@ class MezuroPlugin::Helpers::ContentViewerHelper
6 def self.create_periodicity_options 9 def self.create_periodicity_options
7 [["Not Periodically", 0], ["1 day", 1], ["2 days", 2], ["Weekly", 7], ["Biweeky", 15], ["Monthly", 30]] 10 [["Not Periodically", 0], ["1 day", 1], ["2 days", 2], ["Weekly", 7], ["Biweeky", 15], ["Monthly", 30]]
8 end 11 end
9 -  
10 - def self.generate_chart(values)  
11 - Gchart.line( 12 +
  13 + def self.create_license_options
  14 + options = YAML.load_file("#{RAILS_ROOT}/plugins/mezuro/licenses.yaml")
  15 + options = options.split(";")
  16 + formated_options = []
  17 + options.each { |option| formated_options << [option, option] }
  18 + formated_options
  19 + end
  20 +
  21 + def self.generate_chart(score_history)
  22 + values = []
  23 + labels = []
  24 + score_history.each do |score_data|
  25 + values << score_data.first
  26 + labels << score_data.last
  27 + end
  28 + labels = discretize_array labels
  29 + Gchart.line(
12 :title_color => 'FF0000', 30 :title_color => 'FF0000',
13 :size => '600x180', 31 :size => '600x180',
14 :bg => {:color => 'efefef', :type => 'stripes'}, 32 :bg => {:color => 'efefef', :type => 'stripes'},
15 :line_colors => 'c4a000', 33 :line_colors => 'c4a000',
16 :data => values, 34 :data => values,
17 - :axis_with_labels => 'y', 35 + :labels => labels,
  36 + :axis_with_labels => ['y','x'],
18 :max_value => values.max, 37 :max_value => values.max,
19 :min_value => values.min 38 :min_value => values.min
20 ) 39 )
@@ -25,8 +44,33 @@ class MezuroPlugin::Helpers::ContentViewerHelper @@ -25,8 +44,33 @@ class MezuroPlugin::Helpers::ContentViewerHelper
25 selected_option = options.find { |option| option.last == index.to_i } 44 selected_option = options.find { |option| option.last == index.to_i }
26 selected_option.first 45 selected_option.first
27 end 46 end
28 - 47 +
29 def self.format_name(metric_result) 48 def self.format_name(metric_result)
30 metric_result.metric.name.delete("() ") 49 metric_result.metric.name.delete("() ")
31 end 50 end
  51 +
  52 + def self.get_license_option(selected)
  53 + options = YAML.load_file("#{RAILS_ROOT}/plugins/mezuro/licenses.yaml")
  54 + options.split(";")
  55 + selected_option = options.find { |license| license == selected }
  56 + end
  57 +
  58 + private
  59 +
  60 + def self.discretize_array(array)
  61 + if array.size > MAX_NUMBER_OF_LABELS
  62 + range_array.map { |i| discrete_element(array, i)}
  63 + else
  64 + array
  65 + end
  66 + end
  67 +
  68 + def self.range_array
  69 + (0..(MAX_NUMBER_OF_LABELS - 1)).to_a
  70 + end
  71 +
  72 + def self.discrete_element(array, i)
  73 + array[(i*(array.size - 1))/(MAX_NUMBER_OF_LABELS - 1)]
  74 + end
  75 +
32 end 76 end
plugins/mezuro/lib/mezuro_plugin/project_content.rb
1 class MezuroPlugin::ProjectContent < Article 1 class MezuroPlugin::ProjectContent < Article
2 include ActionView::Helpers::TagHelper 2 include ActionView::Helpers::TagHelper
3 3
4 - settings_items :license, :description, :repository_type, :repository_url, :configuration_name, :periodicity_in_days 4 + settings_items :project_license, :description, :repository_type, :repository_url, :configuration_name, :periodicity_in_days
5 5
6 validate_on_create :validate_kalibro_project_name 6 validate_on_create :validate_kalibro_project_name
7 validate_on_create :validate_repository_url 7 validate_on_create :validate_repository_url
@@ -26,6 +26,7 @@ class MezuroPlugin::ProjectContent &lt; Article @@ -26,6 +26,7 @@ class MezuroPlugin::ProjectContent &lt; Article
26 rescue Exception => error 26 rescue Exception => error
27 errors.add_to_base(error.message) 27 errors.add_to_base(error.message)
28 end 28 end
  29 + @project
29 end 30 end
30 31
31 def project_result 32 def project_result
@@ -34,6 +35,7 @@ class MezuroPlugin::ProjectContent &lt; Article @@ -34,6 +35,7 @@ class MezuroPlugin::ProjectContent &lt; Article
34 rescue Exception => error 35 rescue Exception => error
35 errors.add_to_base(error.message) 36 errors.add_to_base(error.message)
36 end 37 end
  38 + @project_result
37 end 39 end
38 40
39 def project_result_with_date(date) 41 def project_result_with_date(date)
@@ -43,6 +45,7 @@ Kalibro::ProjectResult.first_result_after(name, date) @@ -43,6 +45,7 @@ Kalibro::ProjectResult.first_result_after(name, date)
43 rescue Exception => error 45 rescue Exception => error
44 errors.add_to_base(error.message) 46 errors.add_to_base(error.message)
45 end 47 end
  48 + @project_result
46 end 49 end
47 50
48 def module_result(attributes) 51 def module_result(attributes)
@@ -53,6 +56,7 @@ Kalibro::ProjectResult.first_result_after(name, date) @@ -53,6 +56,7 @@ Kalibro::ProjectResult.first_result_after(name, date)
53 rescue Exception => error 56 rescue Exception => error
54 errors.add_to_base(error.message) 57 errors.add_to_base(error.message)
55 end 58 end
  59 + @module_result
56 end 60 end
57 61
58 def result_history(module_name) 62 def result_history(module_name)
@@ -73,6 +77,7 @@ Kalibro::ProjectResult.first_result_after(name, date) @@ -73,6 +77,7 @@ Kalibro::ProjectResult.first_result_after(name, date)
73 existing = Kalibro::Project.all_names 77 existing = Kalibro::Project.all_names
74 rescue Exception => error 78 rescue Exception => error
75 errors.add_to_base(error.message) 79 errors.add_to_base(error.message)
  80 + existing = []
76 end 81 end
77 82
78 if existing.any?{|existing_name| existing_name.casecmp(name)==0} # existing.include?(name) + case insensitive 83 if existing.any?{|existing_name| existing_name.casecmp(name)==0} # existing.include?(name) + case insensitive
@@ -94,7 +99,7 @@ Kalibro::ProjectResult.first_result_after(name, date) @@ -94,7 +99,7 @@ Kalibro::ProjectResult.first_result_after(name, date)
94 def create_kalibro_project 99 def create_kalibro_project
95 Kalibro::Project.create( 100 Kalibro::Project.create(
96 :name => name, 101 :name => name,
97 - :license => license, 102 + :license => project_license,
98 :description => description, 103 :description => description,
99 :repository => { 104 :repository => {
100 :type => repository_type, 105 :type => repository_type,
@@ -105,7 +110,7 @@ Kalibro::ProjectResult.first_result_after(name, date) @@ -105,7 +110,7 @@ Kalibro::ProjectResult.first_result_after(name, date)
105 end 110 end
106 111
107 def destroy_project_from_service 112 def destroy_project_from_service
108 - project.destroy 113 + project.destroy unless project.nil?
109 end 114 end
110 115
111 end 116 end
plugins/mezuro/licenses.yaml.example 0 → 100644
@@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
  1 +Academic Free License 3.0 (AFL-3.0);
  2 +Affero GNU Public License (AGPL-3.0);
  3 +Adaptive Public License (APL-1.0);
  4 +Apache License 2.0 (Apache-2.0);
  5 +Apple Public Source License (APSL-2.0);
  6 +Artistic license 2.0 (Artistic-2.0);
  7 +Attribution Assurance Licenses (AAL);
  8 +BSD 3-Clause "New" or "Revised" License (BSD-3-Clause);
  9 +BSD 2-Clause "Simplified" or "FreeBSD" License (BSD-2-Clause);
  10 +Boost Software License (BSL-1.0);
  11 +Computer Associates Trusted Open Source License 1.1 (CATOSL-1.1);
  12 +Common Development and Distribution License 1.0 (CDDL-1.0);
  13 +Common Public Attribution License 1.0 (CPAL-1.0);
  14 +CUA Office Public License Version 1.0 (CUA-OPL-1.0);
  15 +EU DataGrid Software License (EUDatagrid);
  16 +Eclipse Public License 1.0 (EPL-1.0);
  17 +Educational Community License, Version 2.0 (ECL-2.0);
  18 +Eiffel Forum License V2.0 (EFL-2.0);
  19 +Entessa Public License (Entessa);
  20 +European Union Public License, Version 1.1 (EUPL-1.1);
  21 +Fair License (FAIR);
  22 +Frameworx License (Frameworx-1.0);
  23 +GNU Affero General Public License v3 (AGPL-3.0);
  24 +GNU General Public License version 2.0 (GPL-2.0);
  25 +GNU General Public License version 3.0 (GPL-3.0);
  26 +GNU Library or "Lesser" General Public License version 2.1 (LGPL-2.1);
  27 +GNU Library or "Lesser" General Public License version 3.0 (LGPL-3.0);
  28 +Historical Permission Notice and Disclaimer (HPND);
  29 +IBM Public License 1.0 (IPL-1.0);
  30 +IPA Font License (IPA);
  31 +ISC License (ISC);
  32 +LaTeX Project Public License 1.3c (LPPL-1.3c);
  33 +Lucent Public License Version 1.02 (LPL-1.02);
  34 +MirOS Licence (MirOS);
  35 +Microsoft Public License (Ms-PL);
  36 +Microsoft Reciprocal License (Ms-RL);
  37 +MIT license (MIT);
  38 +Motosoto License (Motosoto);
  39 +Mozilla Public License 2.0 (MPL-2.0);
  40 +Multics License (Multics);
  41 +NASA Open Source Agreement 1.3 (NASA 1.3);
  42 +NTP License (NTP);
  43 +Naumen Public License (Naumen);
  44 +Nethack General Public License (NGPL);
  45 +Nokia Open Source License (Nokia);
  46 +Non-Profit Open Software License 3.0 (NPOSL-3.0);
  47 +OCLC Research Public License 2.0 (OCLC-2.0);
  48 +Open Font License 1.1 (OFL 1.1);
  49 +Open Group Test Suite License (OGTSL);
  50 +Open Software License 3.0 (OSL-3.0);
  51 +PHP License 3.0 (PHP-3.0);
  52 +The PostgreSQL License (PostgreSQL);
  53 +Python License (Python-2.0);
  54 +CNRI Python license (CNRI-Python);
  55 +Q Public License (QPL-1.0);
  56 +RealNetworks Public Source License V1.0 (RPSL-1.0);
  57 +Reciprocal Public License 1.5 (RPL-1.5);
  58 +Ricoh Source Code Public License (RSCPL);
  59 +Simple Public License 2.0 (SimPL-2.0);
  60 +Sleepycat License (Sleepycat);
  61 +Sun Public License 1.0 (SPL-1.0);
  62 +Sybase Open Watcom Public License 1.0 (Watcom-1.0);
  63 +University of Illinois/NCSA Open Source License (NCSA);
  64 +Vovida Software License v. 1.0 (VSL-1.0);
  65 +W3C License (W3C);
  66 +wxWindows Library License (WXwindows);
  67 +X.Net License (Xnet);
  68 +Zope Public License 2.0 (ZPL-2.0);
  69 +zlib/libpng license (Zlib);
plugins/mezuro/public/colorPicker.css 0 → 100644
@@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
  1 +div.colorPicker-picker {
  2 + height: 16px;
  3 + width: 16px;
  4 + padding: 0 !important;
  5 + border: 1px solid #ccc;
  6 + background: url(arrow.gif) no-repeat top right;
  7 + cursor: pointer;
  8 + line-height: 16px;
  9 +}
  10 +
  11 +div.colorPicker-palette {
  12 + width: 110px;
  13 + position: absolute;
  14 + border: 1px solid #598FEF;
  15 + background-color: #EFEFEF;
  16 + padding: 2px;
  17 + z-index: 9999;
  18 +}
  19 + div.colorPicker_hexWrap {width: 100%; float:left }
  20 + div.colorPicker_hexWrap label {font-size: 95%; color: #2F2F2F; margin: 5px 2px; width: 25%}
  21 + div.colorPicker_hexWrap input {margin: 5px 2px; padding: 0; font-size: 95%; border: 1px solid #000; width: 65%; }
  22 +
  23 +div.colorPicker-swatch {
  24 + height: 12px;
  25 + width: 12px;
  26 + border: 1px solid #000;
  27 + margin: 2px;
  28 + float: left;
  29 + cursor: pointer;
  30 + line-height: 12px;
  31 +}
plugins/mezuro/public/javascripts/colorPicker/LICENSE 0 → 100644
@@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
  1 +Copyright (c) 2012 Lakshan Perera
  2 +
  3 +Permission is hereby granted, free of charge, to any person
  4 +obtaining a copy of this software and associated documentation
  5 +files (the "Software"), to deal in the Software without
  6 +restriction, including without limitation the rights to use,
  7 +copy, modify, merge, publish, distribute, sublicense, and/or sell
  8 +copies of the Software, and to permit persons to whom the
  9 +Software is furnished to do so, subject to the following
  10 +conditions:
  11 +
  12 +The above copyright notice and this permission notice shall be
  13 +included in all copies or substantial portions of the Software.
  14 +
  15 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  16 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  17 +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18 +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  19 +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  20 +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  21 +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  22 +OTHER DEALINGS IN THE SOFTWARE.
plugins/mezuro/public/javascripts/colorPicker/jquery.colorPicker.js 0 → 100644
@@ -0,0 +1,328 @@ @@ -0,0 +1,328 @@
  1 +/**
  2 + * Really Simple Color Picker in jQuery
  3 + *
  4 + * Licensed under the MIT (MIT-LICENSE.txt) licenses.
  5 + *
  6 + * Copyright (c) 2008-2012
  7 + * Lakshan Perera (www.laktek.com) & Daniel Lacy (daniellacy.com)
  8 + *
  9 + * Permission is hereby granted, free of charge, to any person obtaining a copy
  10 + * of this software and associated documentation files (the "Software"), to
  11 + * deal in the Software without restriction, including without limitation the
  12 + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  13 + * sell copies of the Software, and to permit persons to whom the Software is
  14 + * furnished to do so, subject to the following conditions:
  15 + *
  16 + * The above copyright notice and this permission notice shall be included in
  17 + * all copies or substantial portions of the Software.
  18 + *
  19 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22 + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  24 + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  25 + * IN THE SOFTWARE.
  26 + */
  27 +
  28 +(function ($) {
  29 + /**
  30 + * Create a couple private variables.
  31 + **/
  32 + var selectorOwner,
  33 + activePalette,
  34 + cItterate = 0,
  35 + templates = {
  36 + control : $('<div class="colorPicker-picker">&nbsp;</div>'),
  37 + palette : $('<div id="colorPicker_palette" class="colorPicker-palette" />'),
  38 + swatch : $('<div class="colorPicker-swatch">&nbsp;</div>'),
  39 + hexLabel: $('<label for="colorPicker_hex">Hex</label>'),
  40 + hexField: $('<input type="text" id="colorPicker_hex" />')
  41 + },
  42 + transparent = "transparent",
  43 + lastColor;
  44 +
  45 + /**
  46 + * Create our colorPicker function
  47 + **/
  48 + $.fn.colorPicker = function (options) {
  49 +
  50 + return this.each(function () {
  51 + // Setup time. Clone new elements from our templates, set some IDs, make shortcuts, jazzercise.
  52 + var element = $(this),
  53 + opts = $.extend({}, $.fn.colorPicker.defaults, options),
  54 + defaultColor = $.fn.colorPicker.toHex(
  55 + (element.val().length > 0) ? element.val() : opts.pickerDefault
  56 + ),
  57 + newControl = templates.control.clone(),
  58 + newPalette = templates.palette.clone().attr('id', 'colorPicker_palette-' + cItterate),
  59 + newHexLabel = templates.hexLabel.clone(),
  60 + newHexField = templates.hexField.clone(),
  61 + paletteId = newPalette[0].id,
  62 + swatch;
  63 +
  64 +
  65 + /**
  66 + * Build a color palette.
  67 + **/
  68 + $.each(opts.colors, function (i) {
  69 + swatch = templates.swatch.clone();
  70 +
  71 + if (opts.colors[i] === transparent) {
  72 + swatch.addClass(transparent).text('X');
  73 + $.fn.colorPicker.bindPalette(newHexField, swatch, transparent);
  74 + } else {
  75 + swatch.css("background-color", "#" + this);
  76 + $.fn.colorPicker.bindPalette(newHexField, swatch);
  77 + }
  78 + swatch.appendTo(newPalette);
  79 + });
  80 +
  81 + newHexLabel.attr('for', 'colorPicker_hex-' + cItterate);
  82 +
  83 + newHexField.attr({
  84 + 'id' : 'colorPicker_hex-' + cItterate,
  85 + 'value' : defaultColor
  86 + });
  87 +
  88 + newHexField.bind("keydown", function (event) {
  89 + if (event.keyCode === 13) {
  90 + var hexColor = $.fn.colorPicker.toHex($(this).val());
  91 + $.fn.colorPicker.changeColor(hexColor ? hexColor : element.val());
  92 + }
  93 + if (event.keyCode === 27) {
  94 + $.fn.colorPicker.hidePalette();
  95 + }
  96 + });
  97 +
  98 + newHexField.bind("keyup", function (event) {
  99 + var hexColor = $.fn.colorPicker.toHex($(event.target).val());
  100 + $.fn.colorPicker.previewColor(hexColor ? hexColor : element.val());
  101 + });
  102 +
  103 + $('<div class="colorPicker_hexWrap" />').append(newHexLabel).appendTo(newPalette);
  104 +
  105 + newPalette.find('.colorPicker_hexWrap').append(newHexField);
  106 +
  107 + $("body").append(newPalette);
  108 +
  109 + newPalette.hide();
  110 +
  111 +
  112 + /**
  113 + * Build replacement interface for original color input.
  114 + **/
  115 + newControl.css("background-color", defaultColor);
  116 +
  117 + newControl.bind("click", function () {
  118 + $.fn.colorPicker.togglePalette($('#' + paletteId), $(this));
  119 + });
  120 +
  121 + if( options && options.onColorChange ) {
  122 + newControl.data('onColorChange', options.onColorChange);
  123 + } else {
  124 + newControl.data('onColorChange', function() {} );
  125 + }
  126 + element.after(newControl);
  127 +
  128 + element.bind("change", function () {
  129 + element.next(".colorPicker-picker").css(
  130 + "background-color", $.fn.colorPicker.toHex($(this).val())
  131 + );
  132 + });
  133 +
  134 + // Hide the original input.
  135 + element.val(defaultColor).hide();
  136 +
  137 + cItterate++;
  138 + });
  139 + };
  140 +
  141 + /**
  142 + * Extend colorPicker with... all our functionality.
  143 + **/
  144 + $.extend(true, $.fn.colorPicker, {
  145 + /**
  146 + * Return a Hex color, convert an RGB value and return Hex, or return false.
  147 + *
  148 + * Inspired by http://code.google.com/p/jquery-color-utils
  149 + **/
  150 + toHex : function (color) {
  151 + // If we have a standard or shorthand Hex color, return that value.
  152 + if (color.match(/[0-9A-F]{6}|[0-9A-F]{3}$/i)) {
  153 + return (color.charAt(0) === "#") ? color : ("#" + color);
  154 +
  155 + // Alternatively, check for RGB color, then convert and return it as Hex.
  156 + } else if (color.match(/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/)) {
  157 + var c = ([parseInt(RegExp.$1, 10), parseInt(RegExp.$2, 10), parseInt(RegExp.$3, 10)]),
  158 + pad = function (str) {
  159 + if (str.length < 2) {
  160 + for (var i = 0, len = 2 - str.length; i < len; i++) {
  161 + str = '0' + str;
  162 + }
  163 + }
  164 +
  165 + return str;
  166 + };
  167 +
  168 + if (c.length === 3) {
  169 + var r = pad(c[0].toString(16)),
  170 + g = pad(c[1].toString(16)),
  171 + b = pad(c[2].toString(16));
  172 +
  173 + return '#' + r + g + b;
  174 + }
  175 +
  176 + // Otherwise we wont do anything.
  177 + } else {
  178 + return false;
  179 +
  180 + }
  181 + },
  182 +
  183 + /**
  184 + * Check whether user clicked on the selector or owner.
  185 + **/
  186 + checkMouse : function (event, paletteId) {
  187 + var selector = activePalette,
  188 + selectorParent = $(event.target).parents("#" + selector.attr('id')).length;
  189 +
  190 + if (event.target === $(selector)[0] || event.target === selectorOwner[0] || selectorParent > 0) {
  191 + return;
  192 + }
  193 +
  194 + $.fn.colorPicker.hidePalette();
  195 + },
  196 +
  197 + /**
  198 + * Hide the color palette modal.
  199 + **/
  200 + hidePalette : function () {
  201 + $(document).unbind("mousedown", $.fn.colorPicker.checkMouse);
  202 +
  203 + $('.colorPicker-palette').hide();
  204 + },
  205 +
  206 + /**
  207 + * Show the color palette modal.
  208 + **/
  209 + showPalette : function (palette) {
  210 + var hexColor = selectorOwner.prev("input").val();
  211 +
  212 + palette.css({
  213 + top: selectorOwner.offset().top + (selectorOwner.outerHeight()),
  214 + left: selectorOwner.offset().left
  215 + });
  216 +
  217 + $("#color_value").val(hexColor);
  218 +
  219 + palette.show();
  220 +
  221 + $(document).bind("mousedown", $.fn.colorPicker.checkMouse);
  222 + },
  223 +
  224 + /**
  225 + * Toggle visibility of the colorPicker palette.
  226 + **/
  227 + togglePalette : function (palette, origin) {
  228 + // selectorOwner is the clicked .colorPicker-picker.
  229 + if (origin) {
  230 + selectorOwner = origin;
  231 + }
  232 +
  233 + activePalette = palette;
  234 +
  235 + if (activePalette.is(':visible')) {
  236 + $.fn.colorPicker.hidePalette();
  237 +
  238 + } else {
  239 + $.fn.colorPicker.showPalette(palette);
  240 +
  241 + }
  242 + },
  243 +
  244 + /**
  245 + * Update the input with a newly selected color.
  246 + **/
  247 + changeColor : function (value) {
  248 + selectorOwner.css("background-color", value);
  249 + selectorOwner.prev("input").val(value).change();
  250 +
  251 + $.fn.colorPicker.hidePalette();
  252 +
  253 + selectorOwner.data('onColorChange').call(selectorOwner, $(selectorOwner).prev("input").attr("id"), value);
  254 + },
  255 +
  256 +
  257 + /**
  258 + * Preview the input with a newly selected color.
  259 + **/
  260 + previewColor : function (value) {
  261 + selectorOwner.css("background-color", value);
  262 + },
  263 +
  264 + /**
  265 + * Bind events to the color palette swatches.
  266 + */
  267 + bindPalette : function (paletteInput, element, color) {
  268 + color = color ? color : $.fn.colorPicker.toHex(element.css("background-color"));
  269 +
  270 + element.bind({
  271 + click : function (ev) {
  272 + lastColor = color;
  273 +
  274 + $.fn.colorPicker.changeColor(color);
  275 + },
  276 + mouseover : function (ev) {
  277 + lastColor = paletteInput.val();
  278 +
  279 + $(this).css("border-color", "#598FEF");
  280 +
  281 + paletteInput.val(color);
  282 +
  283 + $.fn.colorPicker.previewColor(color);
  284 + },
  285 + mouseout : function (ev) {
  286 + $(this).css("border-color", "#000");
  287 +
  288 + paletteInput.val(selectorOwner.css("background-color"));
  289 +
  290 + paletteInput.val(lastColor);
  291 +
  292 + $.fn.colorPicker.previewColor(lastColor);
  293 + }
  294 + });
  295 + }
  296 + });
  297 +
  298 + /**
  299 + * Default colorPicker options.
  300 + *
  301 + * These are publibly available for global modification using a setting such as:
  302 + *
  303 + * $.fn.colorPicker.defaults.colors = ['151337', '111111']
  304 + *
  305 + * They can also be applied on a per-bound element basis like so:
  306 + *
  307 + * $('#element1').colorPicker({pickerDefault: 'efefef', transparency: true});
  308 + * $('#element2').colorPicker({pickerDefault: '333333', colors: ['333333', '111111']});
  309 + *
  310 + **/
  311 + $.fn.colorPicker.defaults = {
  312 + // colorPicker default selected color.
  313 + pickerDefault : "FFFFFF",
  314 +
  315 + // Default color set.
  316 + colors : [
  317 + '000000', '993300', '333300', '000080', '333399', '333333', '800000', 'FF6600',
  318 + '808000', '008000', '008080', '0000FF', '666699', '808080', 'FF0000', 'FF9900',
  319 + '99CC00', '339966', '33CCCC', '3366FF', '800080', '999999', 'FF00FF', 'FFCC00',
  320 + 'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0', 'FF99CC', 'FFCC99',
  321 + 'FFFF99', 'CCFFFF', '99CCFF', 'FFFFFF'
  322 + ],
  323 +
  324 + // If we want to simply add more colors to the default set, use addColors.
  325 + addColors : []
  326 + };
  327 +
  328 +})(jQuery);
plugins/mezuro/public/javascripts/colorPicker/jquery.colorPicker.min.js 0 → 100644
@@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
  1 +/**
  2 + * Really Simple Color Picker in jQuery
  3 + *
  4 + * Licensed under the MIT (MIT-LICENSE.txt) licenses.
  5 + *
  6 + * Copyright (c) 2008-2012
  7 + * Lakshan Perera (www.laktek.com) & Daniel Lacy (daniellacy.com)
  8 + *
  9 + * Permission is hereby granted, free of charge, to any person obtaining a copy
  10 + * of this software and associated documentation files (the "Software"), to
  11 + * deal in the Software without restriction, including without limitation the
  12 + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  13 + * sell copies of the Software, and to permit persons to whom the Software is
  14 + * furnished to do so, subject to the following conditions:
  15 + *
  16 + * The above copyright notice and this permission notice shall be included in
  17 + * all copies or substantial portions of the Software.
  18 + *
  19 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22 + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  24 + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  25 + * IN THE SOFTWARE.
  26 + */(function(a){var b,c,d=0,e={control:a('<div class="colorPicker-picker">&nbsp;</div>'),palette:a('<div id="colorPicker_palette" class="colorPicker-palette" />'),swatch:a('<div class="colorPicker-swatch">&nbsp;</div>'),hexLabel:a('<label for="colorPicker_hex">Hex</label>'),hexField:a('<input type="text" id="colorPicker_hex" />')},f="transparent",g;a.fn.colorPicker=function(b){return this.each(function(){var c=a(this),g=a.extend({},a.fn.colorPicker.defaults,b),h=a.fn.colorPicker.toHex(c.val().length>0?c.val():g.pickerDefault),i=e.control.clone(),j=e.palette.clone().attr("id","colorPicker_palette-"+d),k=e.hexLabel.clone(),l=e.hexField.clone(),m=j[0].id,n;a.each(g.colors,function(b){n=e.swatch.clone(),g.colors[b]===f?(n.addClass(f).text("X"),a.fn.colorPicker.bindPalette(l,n,f)):(n.css("background-color","#"+this),a.fn.colorPicker.bindPalette(l,n)),n.appendTo(j)}),k.attr("for","colorPicker_hex-"+d),l.attr({id:"colorPicker_hex-"+d,value:h}),l.bind("keydown",function(b){if(b.keyCode===13){var d=a.fn.colorPicker.toHex(a(this).val());a.fn.colorPicker.changeColor(d?d:c.val())}b.keyCode===27&&a.fn.colorPicker.hidePalette()}),l.bind("keyup",function(b){var d=a.fn.colorPicker.toHex(a(b.target).val());a.fn.colorPicker.previewColor(d?d:c.val())}),a('<div class="colorPicker_hexWrap" />').append(k).appendTo(j),j.find(".colorPicker_hexWrap").append(l),a("body").append(j),j.hide(),i.css("background-color",h),i.bind("click",function(){a.fn.colorPicker.togglePalette(a("#"+m),a(this))}),b&&b.onColorChange?i.data("onColorChange",b.onColorChange):i.data("onColorChange",function(){}),c.after(i),c.bind("change",function(){c.next(".colorPicker-picker").css("background-color",a.fn.colorPicker.toHex(a(this).val()))}),c.val(h).hide(),d++})},a.extend(!0,a.fn.colorPicker,{toHex:function(a){if(a.match(/[0-9A-F]{6}|[0-9A-F]{3}$/i))return a.charAt(0)==="#"?a:"#"+a;if(!a.match(/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/))return!1;var b=[parseInt(RegExp.$1,10),parseInt(RegExp.$2,10),parseInt(RegExp.$3,10)],c=function(a){if(a.length<2)for(var b=0,c=2-a.length;b<c;b++)a="0"+a;return a};if(b.length===3){var d=c(b[0].toString(16)),e=c(b[1].toString(16)),f=c(b[2].toString(16));return"#"+d+e+f}},checkMouse:function(d,e){var f=c,g=a(d.target).parents("#"+f.attr("id")).length;if(d.target===a(f)[0]||d.target===b[0]||g>0)return;a.fn.colorPicker.hidePalette()},hidePalette:function(){a(document).unbind("mousedown",a.fn.colorPicker.checkMouse),a(".colorPicker-palette").hide()},showPalette:function(c){var d=b.prev("input").val();c.css({top:b.offset().top+b.outerHeight(),left:b.offset().left}),a("#color_value").val(d),c.show(),a(document).bind("mousedown",a.fn.colorPicker.checkMouse)},togglePalette:function(d,e){e&&(b=e),c=d,c.is(":visible")?a.fn.colorPicker.hidePalette():a.fn.colorPicker.showPalette(d)},changeColor:function(c){b.css("background-color",c),b.prev("input").val(c).change(),a.fn.colorPicker.hidePalette(),b.data("onColorChange").call(b,a(b).prev("input").attr("id"),c)},previewColor:function(a){b.css("background-color",a)},bindPalette:function(c,d,e){e=e?e:a.fn.colorPicker.toHex(d.css("background-color")),d.bind({click:function(b){g=e,a.fn.colorPicker.changeColor(e)},mouseover:function(b){g=c.val(),a(this).css("border-color","#598FEF"),c.val(e),a.fn.colorPicker.previewColor(e)},mouseout:function(d){a(this).css("border-color","#000"),c.val(b.css("background-color")),c.val(g),a.fn.colorPicker.previewColor(g)}})}}),a.fn.colorPicker.defaults={pickerDefault:"FFFFFF",colors:["000000","993300","333300","000080","333399","333333","800000","FF6600","808000","008000","008080","0000FF","666699","808080","FF0000","FF9900","99CC00","339966","33CCCC","3366FF","800080","999999","FF00FF","FFCC00","FFFF00","00FF00","00FFFF","00CCFF","993366","C0C0C0","FF99CC","FFCC99","FFFF99","CCFFFF","99CCFF","FFFFFF"],addColors:[]}})(jQuery)
0 \ No newline at end of file 27 \ No newline at end of file
plugins/mezuro/public/javascripts/project_content.js
@@ -2,7 +2,6 @@ var processingTree = false; @@ -2,7 +2,6 @@ var processingTree = false;
2 var metricName; 2 var metricName;
3 jQuery(function (){ 3 jQuery(function (){
4 jQuery('.source-tree-link').live("click", reloadModule); 4 jQuery('.source-tree-link').live("click", reloadModule);
5 - jQuery('[data-show]').live("click", toggle_mezuro);  
6 jQuery('[show-metric-history]').live("click", display_metric_history); 5 jQuery('[show-metric-history]').live("click", display_metric_history);
7 jQuery('[show-grade-history]').live("click", display_grade_history); 6 jQuery('[show-grade-history]').live("click", display_grade_history);
8 jQuery('#project_date_submit').live("click", reloadProjectWithDate); 7 jQuery('#project_date_submit').live("click", reloadProjectWithDate);
@@ -16,7 +15,8 @@ function showProjectContent() { @@ -16,7 +15,8 @@ function showProjectContent() {
16 15
17 function display_metric_history() { 16 function display_metric_history() {
18 var module_name = jQuery(this).attr('data-module-name'); 17 var module_name = jQuery(this).attr('data-module-name');
19 - var metric_name = jQuery(this).attr('data-metric-name'); 18 + var metric_name = jQuery(this).attr('show-metric-history');
  19 + toggle_mezuro("." + metric_name);
20 metricName = metric_name; 20 metricName = metric_name;
21 callAction('module_metrics_history', {module_name: module_name, metric_name: metric_name}, show_metrics); 21 callAction('module_metrics_history', {module_name: module_name, metric_name: metric_name}, show_metrics);
22 return false; 22 return false;
@@ -24,6 +24,7 @@ function display_metric_history() { @@ -24,6 +24,7 @@ function display_metric_history() {
24 24
25 function display_grade_history() { 25 function display_grade_history() {
26 var module_name = jQuery(this).attr('data-module-name'); 26 var module_name = jQuery(this).attr('data-module-name');
  27 + toggle_mezuro("#historical-grade");
27 callAction('module_grade_history', {module_name: module_name}, show_grades); 28 callAction('module_grade_history', {module_name: module_name}, show_grades);
28 return false; 29 return false;
29 } 30 }
@@ -36,8 +37,7 @@ function show_grades(content) { @@ -36,8 +37,7 @@ function show_grades(content) {
36 jQuery('#historical-grade').html(content); 37 jQuery('#historical-grade').html(content);
37 } 38 }
38 39
39 -function toggle_mezuro(){  
40 - var element = jQuery(this).attr('data-show'); 40 +function toggle_mezuro(element){
41 jQuery(element).toggle(); 41 jQuery(element).toggle();
42 return false; 42 return false;
43 } 43 }
@@ -51,23 +51,8 @@ function reloadModule(){ @@ -51,23 +51,8 @@ function reloadModule(){
51 return false; 51 return false;
52 } 52 }
53 53
54 -function reloadProjectWithDate(){  
55 - var day = jQuery("#project_date_day").val();  
56 - var month = jQuery("#project_date_month").val();  
57 - var year = jQuery("#project_date_year").val();  
58 -  
59 - if(day.length == 1)  
60 - day = "0" + day;  
61 - if(month.length == 1)  
62 - month = "0" + month;  
63 -  
64 - var date = new Date(year + "-" + month + "-" + day + "T00:00:00+00:00");  
65 -  
66 - if(isNaN(date)){  
67 - alert("Invalid date! " + date);  
68 - return false;  
69 - }  
70 - reloadProject(date); 54 +function reloadProjectWithDate(date){
  55 + reloadProject(date + "T00:00:00+00:00");
71 return false; 56 return false;
72 } 57 }
73 58
plugins/mezuro/public/javascripts/validations.js
@@ -3,7 +3,9 @@ jQuery(function (){ @@ -3,7 +3,9 @@ jQuery(function (){
3 jQuery('#metric_configuration_submit').live("click", validate_metric_configuration); 3 jQuery('#metric_configuration_submit').live("click", validate_metric_configuration);
4 }); 4 });
5 5
6 - 6 +function validate_code(code){
  7 + return true;
  8 +}
7 9
8 function validate_metric_configuration(){ 10 function validate_metric_configuration(){
9 var code = jQuery('#metric_configuration_code').val(); 11 var code = jQuery('#metric_configuration_code').val();
@@ -37,14 +39,6 @@ function IsNotInfinite(value){ @@ -37,14 +39,6 @@ function IsNotInfinite(value){
37 return true; 39 return true;
38 } 40 }
39 41
40 -function IsNotHexadecimal(value){  
41 - if(value.match(/^[0-9a-fA-F]{1,8}$/))  
42 - {  
43 - return false;  
44 - }  
45 - return true;  
46 -}  
47 -  
48 function validate_new_range_configuration(event){ 42 function validate_new_range_configuration(event){
49 var label = jQuery("#range_label").val(); 43 var label = jQuery("#range_label").val();
50 var beginning = jQuery("#range_beginning").val(); 44 var beginning = jQuery("#range_beginning").val();
@@ -54,24 +48,20 @@ function validate_new_range_configuration(event){ @@ -54,24 +48,20 @@ function validate_new_range_configuration(event){
54 48
55 if (is_null(label) || is_null(beginning) || is_null(end) || is_null(color) || is_null(grade)) 49 if (is_null(label) || is_null(beginning) || is_null(end) || is_null(color) || is_null(grade))
56 { 50 {
57 - alert("Please fill all fields marked with (*)"); 51 + alert("Please fill all fields marked with (*).");
58 return false; 52 return false;
59 } 53 }
60 if ( (IsNotNumeric(beginning) && IsNotInfinite(beginning)) || (IsNotNumeric(end) && IsNotInfinite(end)) || IsNotNumeric(grade)) 54 if ( (IsNotNumeric(beginning) && IsNotInfinite(beginning)) || (IsNotNumeric(end) && IsNotInfinite(end)) || IsNotNumeric(grade))
61 { 55 {
62 - alert("Beginning, End and Grade must be numeric values"); 56 + alert("Beginning, End and Grade must be numeric values.");
63 return false; 57 return false;
64 } 58 }
65 if (parseInt(beginning) > parseInt(end)) 59 if (parseInt(beginning) > parseInt(end))
66 { 60 {
67 if(IsNotInfinite(beginning) && IsNotInfinite(end)){ 61 if(IsNotInfinite(beginning) && IsNotInfinite(end)){
68 - alert("End must be greater than Beginning"); 62 + alert("End must be greater than Beginning.");
69 return false; 63 return false;
70 } 64 }
71 } 65 }
72 - if (IsNotHexadecimal(color)){  
73 - alert("Color must be an hexadecimal value");  
74 - return false;  
75 - }  
76 return true; 66 return true;
77 } 67 }
plugins/mezuro/public/style.css
  1 +@import url('colorPicker.css');
  2 +
1 .link { 3 .link {
2 cursor: pointer; 4 cursor: pointer;
3 } 5 }
plugins/mezuro/test/fixtures/project_fixtures.rb
@@ -28,7 +28,7 @@ class ProjectFixtures @@ -28,7 +28,7 @@ class ProjectFixtures
28 def self.project_content 28 def self.project_content
29 content = MezuroPlugin::ProjectContent.new 29 content = MezuroPlugin::ProjectContent.new
30 content.name = 'Qt-Calculator' 30 content.name = 'Qt-Calculator'
31 - content.license = 'GPL' 31 + content.project_license = 'GPL'
32 content.description = 'Calculator for Qt' 32 content.description = 'Calculator for Qt'
33 content.repository_type = RepositoryFixtures.repository_hash[:type] 33 content.repository_type = RepositoryFixtures.repository_hash[:type]
34 content.repository_url = RepositoryFixtures.repository_hash[:address] 34 content.repository_url = RepositoryFixtures.repository_hash[:address]
plugins/mezuro/test/functional/mezuro_plugin_myprofile_controller_test.rb
@@ -27,7 +27,7 @@ class MezuroPluginMyprofileControllerTest &lt; ActionController::TestCase @@ -27,7 +27,7 @@ class MezuroPluginMyprofileControllerTest &lt; ActionController::TestCase
27 27
28 Kalibro::Configuration.expects(:all_names).returns([]) 28 Kalibro::Configuration.expects(:all_names).returns([])
29 @content = MezuroPlugin::ConfigurationContent.new(:profile => @profile, :name => @configuration.name) 29 @content = MezuroPlugin::ConfigurationContent.new(:profile => @profile, :name => @configuration.name)
30 - @content.expects(:send_configuration_to_service).returns(nil) 30 + @content.expects(:send_kalibro_configuration_to_service).returns(nil)
31 @content.stubs(:solr_save) 31 @content.stubs(:solr_save)
32 @content.save 32 @content.save
33 33
@@ -39,7 +39,7 @@ class MezuroPluginMyprofileControllerTest &lt; ActionController::TestCase @@ -39,7 +39,7 @@ class MezuroPluginMyprofileControllerTest &lt; ActionController::TestCase
39 @range = RangeFixtures.range_excellent 39 @range = RangeFixtures.range_excellent
40 @range_hash = RangeFixtures.range_excellent_hash 40 @range_hash = RangeFixtures.range_excellent_hash
41 end 41 end
42 - 42 +
43 should 'test choose base tool' do 43 should 'test choose base tool' do
44 Kalibro::BaseTool.expects(:request).with("BaseTool", :get_base_tool_names).returns({:base_tool_name => @base_tool.name}) 44 Kalibro::BaseTool.expects(:request).with("BaseTool", :get_base_tool_names).returns({:base_tool_name => @base_tool.name})
45 get :choose_base_tool, :profile => @profile.identifier, :id => @content.id 45 get :choose_base_tool, :profile => @profile.identifier, :id => @content.id
plugins/mezuro/test/functional/mezuro_plugin_profile_controller_test.rb
@@ -25,14 +25,14 @@ class MezuroPluginProfileControllerTest &lt; ActionController::TestCase @@ -25,14 +25,14 @@ class MezuroPluginProfileControllerTest &lt; ActionController::TestCase
25 @content.save 25 @content.save
26 end 26 end
27 27
28 - should 'test project state without error' do 28 + should 'test project state without kalibro_error' do
29 Kalibro::Project.expects(:request).with("Project", :get_project, :project_name => @project.name).returns({:project => @project.to_hash}) 29 Kalibro::Project.expects(:request).with("Project", :get_project, :project_name => @project.name).returns({:project => @project.to_hash})
30 get :project_state, :profile => @profile.identifier, :id => @content.id 30 get :project_state, :profile => @profile.identifier, :id => @content.id
31 assert_response 200 31 assert_response 200
32 assert_equal @content, assigns(:content) 32 assert_equal @content, assigns(:content)
33 end 33 end
34 34
35 - should 'test project state with error' do 35 + should 'test project state with kalibro_error' do
36 Kalibro::Project.expects(:request).with("Project", :get_project, :project_name => @project.name).returns({:project => @project.to_hash.merge({:error => ErrorFixtures.error_hash})}) 36 Kalibro::Project.expects(:request).with("Project", :get_project, :project_name => @project.name).returns({:project => @project.to_hash.merge({:error => ErrorFixtures.error_hash})})
37 get :project_state, :profile => @profile.identifier, :id => @content.id 37 get :project_state, :profile => @profile.identifier, :id => @content.id
38 assert_response 200 38 assert_response 200
@@ -126,7 +126,7 @@ class MezuroPluginProfileControllerTest &lt; ActionController::TestCase @@ -126,7 +126,7 @@ class MezuroPluginProfileControllerTest &lt; ActionController::TestCase
126 get :module_metrics_history, :profile => @profile.identifier, :id => @content.id, :module_name => @project.name, 126 get :module_metrics_history, :profile => @profile.identifier, :id => @content.id, :module_name => @project.name,
127 :metric_name => @module_result.metric_result.first.metric.name.delete("() ") 127 :metric_name => @module_result.metric_result.first.metric.name.delete("() ")
128 assert_equal @content, assigns(:content) 128 assert_equal @content, assigns(:content)
129 - assert_equal [@module_result.metric_result[0].value], assigns(:score_history) 129 + assert_equal [[@module_result.metric_result[0].value, @module_result.date.to_s[0..9]]], assigns(:score_history)
130 assert_response 200 130 assert_response 200
131 end 131 end
132 132
@@ -134,7 +134,7 @@ class MezuroPluginProfileControllerTest &lt; ActionController::TestCase @@ -134,7 +134,7 @@ class MezuroPluginProfileControllerTest &lt; ActionController::TestCase
134 Kalibro::ModuleResult.expects(:request).with("ModuleResult", :get_result_history, {:project_name => @project.name, :module_name => @project.name}).returns({:module_result => @module_result}) 134 Kalibro::ModuleResult.expects(:request).with("ModuleResult", :get_result_history, {:project_name => @project.name, :module_name => @project.name}).returns({:module_result => @module_result})
135 get :module_grade_history, :profile => @profile.identifier, :id => @content.id, :module_name => @project.name 135 get :module_grade_history, :profile => @profile.identifier, :id => @content.id, :module_name => @project.name
136 assert_equal @content, assigns(:content) 136 assert_equal @content, assigns(:content)
137 - assert_equal [@module_result.grade], assigns(:score_history) 137 + assert_equal [[@module_result.grade, @module_result.date.to_s[0..9]]], assigns(:score_history)
138 assert_response 200 138 assert_response 200
139 end 139 end
140 140
plugins/mezuro/test/unit/kalibro/configuration_test.rb
@@ -44,7 +44,9 @@ class ConfigurationTest &lt; ActiveSupport::TestCase @@ -44,7 +44,9 @@ class ConfigurationTest &lt; ActiveSupport::TestCase
44 should 'return nil when configuration doesnt exist' do 44 should 'return nil when configuration doesnt exist' do
45 request_body = {:configuration_name => @configuration.name} 45 request_body = {:configuration_name => @configuration.name}
46 Kalibro::Configuration.expects(:request).with("Configuration", :get_configuration, request_body).raises(Exception.new) 46 Kalibro::Configuration.expects(:request).with("Configuration", :get_configuration, request_body).raises(Exception.new)
47 - assert_nil Kalibro::Configuration.find_by_name(@configuration.name) 47 + assert_raise Exception do
  48 + Kalibro::Configuration.find_by_name(@configuration.name)
  49 + end
48 end 50 end
49 51
50 should 'destroy configuration by name' do 52 should 'destroy configuration by name' do
plugins/mezuro/test/unit/kalibro/project_result_test.rb
@@ -69,26 +69,20 @@ class ProjectResultTest &lt; ActiveSupport::TestCase @@ -69,26 +69,20 @@ class ProjectResultTest &lt; ActiveSupport::TestCase
69 assert_equal '00:00:01', @project_result.formatted_analysis_time 69 assert_equal '00:00:01', @project_result.formatted_analysis_time
70 end 70 end
71 71
72 - should 'retrieve module node' do  
73 - node = @project_result.get_node("main")  
74 - assert_equal @hash[:source_tree][:child][2], node.to_hash  
75 - end  
76 -  
77 should 'retrive complex module' do 72 should 'retrive complex module' do
78 - node = @project_result.get_node("org.Window")  
79 - assert_equal @hash[:source_tree][:child][0][:child].first, node.to_hash 73 + assert_equal @hash[:source_tree][:child][0][:child].first, @project_result.node("org.Window").to_hash
80 end 74 end
81 75
82 should 'return source tree node when nil is given' do 76 should 'return source tree node when nil is given' do
83 - assert_equal @hash[:source_tree], @project_result.node_of(nil).to_hash 77 + assert_equal @hash[:source_tree], @project_result.node(nil).to_hash
84 end 78 end
85 79
86 should 'return source tree node when project name is given' do 80 should 'return source tree node when project name is given' do
87 - assert_equal @hash[:source_tree], @project_result.node_of(@project_result.project.name).to_hash 81 + assert_equal @hash[:source_tree], @project_result.node(@project_result.project.name).to_hash
88 end 82 end
89 83
90 should 'return correct node when module name is given' do 84 should 'return correct node when module name is given' do
91 - assert_equal @hash[:source_tree][:child][2], @project_result.node_of("main").to_hash 85 + assert_equal @hash[:source_tree][:child][2], @project_result.node("main").to_hash
92 end 86 end
93 87
94 end 88 end
plugins/mezuro/test/unit/kalibro/range_test.rb
@@ -17,4 +17,16 @@ class RangeTest &lt; ActiveSupport::TestCase @@ -17,4 +17,16 @@ class RangeTest &lt; ActiveSupport::TestCase
17 assert_equal @hash, @range.to_hash 17 assert_equal @hash, @range.to_hash
18 end 18 end
19 19
  20 + should 'create a default color for new range' do
  21 + assert_equal "#e4ca2d", Kalibro::Range.new.mezuro_color
  22 + end
  23 +
  24 + should "convert color from 'ff' to '#'" do
  25 + assert_equal "#ff0000", @range.mezuro_color
  26 + end
  27 +
  28 + should "convert color from '#' to 'ff' when creating a new range" do
  29 + assert_equal "ffff0000", Kalibro::Range.new({:color => '#ff0000'}).color
  30 + end
  31 +
20 end 32 end
plugins/mezuro/test/unit/mezuro_plugin/configuration_content_test.rb
@@ -6,9 +6,7 @@ class ConfigurationContentTest &lt; ActiveSupport::TestCase @@ -6,9 +6,7 @@ class ConfigurationContentTest &lt; ActiveSupport::TestCase
6 6
7 def setup 7 def setup
8 @configuration = ConfigurationFixtures.configuration 8 @configuration = ConfigurationFixtures.configuration
9 - @content = MezuroPlugin::ConfigurationContent.new  
10 - @content.name = @configuration.name  
11 - @content.description = @configuration.description 9 + @content = ConfigurationFixtures.configuration_content("None")
12 end 10 end
13 11
14 should 'be an article' do 12 should 'be an article' do
@@ -35,19 +33,19 @@ class ConfigurationContentTest &lt; ActiveSupport::TestCase @@ -35,19 +33,19 @@ class ConfigurationContentTest &lt; ActiveSupport::TestCase
35 33
36 should 'get configuration from service' do 34 should 'get configuration from service' do
37 Kalibro::Configuration.expects(:find_by_name).with(@content.name).returns(@configuration) 35 Kalibro::Configuration.expects(:find_by_name).with(@content.name).returns(@configuration)
38 - assert_equal @configuration, @content.configuration 36 + assert_equal @configuration, @content.kalibro_configuration
39 end 37 end
40 38
41 should 'send configuration to service after saving' do 39 should 'send configuration to service after saving' do
42 - @content.expects :send_configuration_to_service 40 + @content.expects :send_kalibro_configuration_to_service
43 @content.stubs(:solr_save) 41 @content.stubs(:solr_save)
44 @content.run_callbacks :after_save 42 @content.run_callbacks :after_save
45 end 43 end
46 44
47 should 'create new configuration' do 45 should 'create new configuration' do
48 Kalibro::Configuration.expects(:create).with(:name => @content.name, :description => @content.description) 46 Kalibro::Configuration.expects(:create).with(:name => @content.name, :description => @content.description)
49 - Kalibro::Configuration.expects(:find_by_name).with(@content.name).returns(nil)  
50 - @content.send :send_configuration_to_service 47 + Kalibro::Configuration.expects(:find_by_name).with(@content.name)
  48 + @content.send :send_kalibro_configuration_to_service
51 end 49 end
52 50
53 should 'clone configuration' do 51 should 'clone configuration' do
@@ -55,25 +53,25 @@ class ConfigurationContentTest &lt; ActiveSupport::TestCase @@ -55,25 +53,25 @@ class ConfigurationContentTest &lt; ActiveSupport::TestCase
55 Kalibro::Configuration.expects(:create).with(:name => @content.name, :description => @content.description, :metric_configuration => @configuration.metric_configurations_hash) 53 Kalibro::Configuration.expects(:create).with(:name => @content.name, :description => @content.description, :metric_configuration => @configuration.metric_configurations_hash)
56 Kalibro::Configuration.expects(:find_by_name).with(@content.name).returns(nil) 54 Kalibro::Configuration.expects(:find_by_name).with(@content.name).returns(nil)
57 Kalibro::Configuration.expects(:find_by_name).with('clone name').returns(@configuration) 55 Kalibro::Configuration.expects(:find_by_name).with('clone name').returns(@configuration)
58 - @content.send :send_configuration_to_service 56 + @content.send :send_kalibro_configuration_to_service
59 end 57 end
60 58
61 should 'edit configuration' do 59 should 'edit configuration' do
62 Kalibro::Configuration.expects(:find_by_name).with(@content.name).returns(@configuration) 60 Kalibro::Configuration.expects(:find_by_name).with(@content.name).returns(@configuration)
63 @configuration.expects(:update_attributes).with(:description => @content.description) 61 @configuration.expects(:update_attributes).with(:description => @content.description)
64 - @content.send :send_configuration_to_service 62 + @content.send :send_kalibro_configuration_to_service
65 end 63 end
66 64
67 should 'send correct configuration to service but comunication fails' do 65 should 'send correct configuration to service but comunication fails' do
68 Kalibro::Configuration.expects(:find_by_name).with(@content.name).returns(@configuration) 66 Kalibro::Configuration.expects(:find_by_name).with(@content.name).returns(@configuration)
69 @configuration.expects(:save).returns(false) 67 @configuration.expects(:save).returns(false)
70 - @content.send :send_configuration_to_service 68 + @content.send :send_kalibro_configuration_to_service
71 end 69 end
72 70
73 should 'remove configuration from service' do 71 should 'remove configuration from service' do
74 Kalibro::Configuration.expects(:find_by_name).with(@content.name).returns(@configuration) 72 Kalibro::Configuration.expects(:find_by_name).with(@content.name).returns(@configuration)
75 @configuration.expects(:destroy) 73 @configuration.expects(:destroy)
76 - @content.send :remove_configuration_from_service 74 + @content.send :remove_kalibro_configuration_from_service
77 end 75 end
78 76
79 end 77 end
plugins/mezuro/views/cms/mezuro_plugin/_configuration_content.html.erb
1 <h1> <%= _(MezuroPlugin::ConfigurationContent.short_description) %> </h1> 1 <h1> <%= _(MezuroPlugin::ConfigurationContent.short_description) %> </h1>
2 2
3 <% 3 <%
4 - begin  
5 - configuration = @article.title.nil? ? nil : @article.configuration  
6 - rescue  
7 - configuration = nil  
8 - end 4 + kalibro_configuration = @article.title.nil? ? nil : @article.kalibro_configuration
  5 + kalibro_configuration_names = @article.kalibro_configuration_names
9 %> 6 %>
10 7
11 <%= error_messages_for 'kalibro_configuration' %> 8 <%= error_messages_for 'kalibro_configuration' %>
@@ -13,20 +10,19 @@ @@ -13,20 +10,19 @@
13 <%= hidden_field_tag 'kalibro_configuration[profile_id]', profile.id %> 10 <%= hidden_field_tag 'kalibro_configuration[profile_id]', profile.id %>
14 <%= hidden_field_tag 'id', @article.id %> 11 <%= hidden_field_tag 'id', @article.id %>
15 12
16 -<% configuration_names = @article.configuration_names %>  
17 -  
18 -<% selected = (configuration.nil? ? "None" : @article.configuration_to_clone_name) %> 13 +
  14 +<% selected = (kalibro_configuration.nil? ? "None" : @article.configuration_to_clone_name) %>
19 15
20 <%= required_fields_message %> 16 <%= required_fields_message %>
21 17
22 <%= required labelled_form_field _('Clone Configuration'), 18 <%= required labelled_form_field _('Clone Configuration'),
23 -if !configuration.nil? && !@article.id.nil?  
24 - f.select(:configuration_to_clone_name, configuration_names, {:selected => selected}, :disabled => 'true') 19 +if !kalibro_configuration.nil? && !@article.id.nil?
  20 + f.select(:configuration_to_clone_name, kalibro_configuration_names, {:selected => selected}, :disabled => 'true')
25 else 21 else
26 - f.select(:configuration_to_clone_name, configuration_names, {:selected => selected}) 22 + f.select(:configuration_to_clone_name, kalibro_configuration_names, {:selected => selected})
27 end %> 23 end %>
28 <br/> 24 <br/>
29 25
30 -<%= required f.text_field(:name, :disabled => !(configuration.nil? || @article.id.nil?)) %> 26 +<%= required f.text_field(:name, :disabled => !(kalibro_configuration.nil? || @article.id.nil?)) %>
31 27
32 <%= f.text_field :description %><br/> 28 <%= f.text_field :description %><br/>
plugins/mezuro/views/cms/mezuro_plugin/_project_content.html.erb
1 <h1> <%= _(MezuroPlugin::ProjectContent.short_description) %> </h1> 1 <h1> <%= _(MezuroPlugin::ProjectContent.short_description) %> </h1>
2 2
3 <% 3 <%
  4 + @project = @article.title.nil? ? nil : @article.project
4 begin 5 begin
5 - @project = @article.title.nil? ? nil : Kalibro::Project.find_by_name(@article.title)  
6 - rescue  
7 - @project = nil 6 + @repository_types = Kalibro::Repository.repository_types.sort
  7 + @configuration_names = Kalibro::Configuration.all_names.sort
  8 + rescue Exception => exception
  9 + @article.errors.add_to_base(exception.message)
  10 + @repository_types = []
  11 + @configuration_names = []
8 end 12 end
9 %> 13 %>
10 14
@@ -20,18 +24,18 @@ @@ -20,18 +24,18 @@
20 <%= required f.text_field(:name) %> 24 <%= required f.text_field(:name) %>
21 <% end %> 25 <% end %>
22 26
23 -<%= f.text_field :license %><br/> 27 +<% selected = (@project.nil? ? "" : @project.license) %>
  28 +<%= required labelled_form_field _('License'),
  29 + f.select(:project_license, MezuroPlugin::Helpers::ContentViewerHelper.create_license_options ,{:selected => selected}) %><br/>
24 30
25 <%= f.text_field :description %><br/> 31 <%= f.text_field :description %><br/>
26 32
27 -<% @repository_types = Kalibro::Repository.repository_types.sort %>  
28 <% @selected = (@project.nil? ? @repository_types : @project.repository.type) %> 33 <% @selected = (@project.nil? ? @repository_types : @project.repository.type) %>
29 <%= required labelled_form_field _('Repository type'), 34 <%= required labelled_form_field _('Repository type'),
30 f.select(:repository_type, @repository_types, {:selected => @selected}) %><br/> 35 f.select(:repository_type, @repository_types, {:selected => @selected}) %><br/>
31 36
32 <%= required f.text_field(:repository_url) %><br/> 37 <%= required f.text_field(:repository_url) %><br/>
33 38
34 -<% @configuration_names = Kalibro::Configuration.all_names.sort %>  
35 <% @selected = (@project.nil? ? @configuration_names[0] : @project.configuration_name) %> 39 <% @selected = (@project.nil? ? @configuration_names[0] : @project.configuration_name) %>
36 40
37 <% if !@project.nil? && !@article.id.nil? %> 41 <% if !@project.nil? && !@article.id.nil? %>
plugins/mezuro/views/content_viewer/_module_result.rhtml
1 -<% unless @content.errors[:base].nil? %>  
2 - <%= @content.errors[:base] %>  
3 -<% else %>  
4 - <% the_module = @module_result.module %>  
5 - <% module_label = "#{the_module.name} (#{the_module.granularity})" %> 1 +<h5><%= _('Metric results for: ') + @module_label %> </h5>
6 2
7 - <h5><%= _('Metric results for: ') + module_label %> </h5>  
8 -  
9 - <hr/>  
10 - <div class="zoomable-image">  
11 - <table>  
12 - <thead>  
13 - <tr>  
14 - <th>Metric</th>  
15 - <th>Value</th>  
16 - <th>Weight</th>  
17 - <th>Threshold</th>  
18 - </tr>  
19 - </thead>  
20 - <tbody>  
21 - <% @module_result.metric_results.each do |metric_result| %>  
22 - <% range = metric_result.range %>  
23 - <% if !range.nil? %>  
24 - <tr>  
25 - <td><a href="#" data-show=".<%= MezuroPlugin::Helpers::ContentViewerHelper.format_name(metric_result) %>"><%= metric_result.metric.name %></a></td>  
26 - <td><%= MezuroPlugin::Helpers::ContentViewerHelper.format_grade(metric_result.value) %></td>  
27 - <td><%= metric_result.weight %></td>  
28 - <td style="background-color: #<%= range.color[2..-1] %>"><%= range.label %></td>  
29 - </tr>  
30 - <tr class="<%= MezuroPlugin::Helpers::ContentViewerHelper.format_name(metric_result) %>" style="display: none;">  
31 - <td colspan="3">  
32 - <div id='historical-<%= MezuroPlugin::Helpers::ContentViewerHelper.format_name(metric_result) %>'>  
33 - <a href="#" show-metric-history="<%= MezuroPlugin::Helpers::ContentViewerHelper.format_name(metric_result) %>" data-module-name="<%= the_module.name %>" data-metric-name="<%= MezuroPlugin::Helpers::ContentViewerHelper.format_name(metric_result) %>"> <p style="text-indent: 3em;"> Get Historical Values </p> </a>  
34 - </div>  
35 - </td>  
36 - <td align="right">  
37 - <%= range.comments.nil? ? '' : range.comments %>  
38 - </td>  
39 - </tr>  
40 - <% end %> 3 +<hr/>
  4 +<div class="zoomable-image">
  5 +<table>
  6 + <thead>
  7 + <tr>
  8 + <th>Metric</th>
  9 + <th>Value</th>
  10 + <th>Weight</th>
  11 + <th>Threshold</th>
  12 + </tr>
  13 + </thead>
  14 + <tbody>
  15 + <% @module_result.metric_results.each do |metric_result| %>
  16 + <% range = metric_result.range %>
  17 + <% if !range.nil? %>
  18 + <tr>
  19 + <td><a href="#" show-metric-history="<%= MezuroPlugin::Helpers::ContentViewerHelper.format_name(metric_result) %>" data-module-name="<%= @module.name %>"><%= metric_result.metric.name %></a></td>
  20 + <td><%= MezuroPlugin::Helpers::ContentViewerHelper.format_grade(metric_result.value) %></td>
  21 + <td><%= metric_result.weight %></td>
  22 + <td style="background-color: #<%= range.color[2..-1] %>"><%= range.label %></td>
  23 + </tr>
  24 + <tr class="<%= MezuroPlugin::Helpers::ContentViewerHelper.format_name(metric_result) %>" style="display: none;">
  25 + <td colspan="3">
  26 + <div id='historical-<%= MezuroPlugin::Helpers::ContentViewerHelper.format_name(metric_result) %>'>
  27 + </div>
  28 + </td>
  29 + <td align="right">
  30 + <%= range.comments.nil? ? '' : range.comments %>
  31 + </td>
  32 + </tr>
41 <% end %> 33 <% end %>
42 - </tbody>  
43 - <tfoot>  
44 - <tr>  
45 - <td colspan = "3">  
46 - <div id='historical-grade'></div>  
47 - </td>  
48 - <td align = "right">  
49 - <a href="#" show-grade-history="<%= @module_result.module.name %>" data-module-name="<%= the_module.name%>" >  
50 - <strong>  
51 - <%= _('Grade:') %>  
52 - <%= "%.02f" % @module_result.grade %>  
53 - </strong>  
54 - </a>  
55 - </td>  
56 - </tr>  
57 - </tfoot>  
58 - </table>  
59 - </div>  
60 -<% end %> 34 + <% end %>
  35 + </tbody>
  36 + <tfoot>
  37 + <tr>
  38 + <td colspan = "3">
  39 + <div id='historical-grade' style="display: none;"></div>
  40 + </td>
  41 + <td align = "right">
  42 + <a href="#" show-grade-history="<%= @module_result.module.name %>" data-module-name="<%= @module.name%>" >
  43 + <strong>
  44 + <%= _('Grade:') %>
  45 + <%= "%.02f" % @module_result.grade %>
  46 + </strong>
  47 + </a>
  48 + </td>
  49 + </tr>
  50 + </tfoot>
  51 +</table>
  52 +</div>
plugins/mezuro/views/content_viewer/_project_error.rhtml
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 <p> 2 <p>
3 <%= "State when error ocurred: #{@project.state}" %> 3 <%= "State when error ocurred: #{@project.state}" %>
4 <br/> 4 <br/>
5 - <% error = @project.error %> 5 + <% error = @project.kalibro_error %>
6 <%= error.message %> 6 <%= error.message %>
7 <ul> 7 <ul>
8 <% error.stack_trace.each do |trace| %> 8 <% error.stack_trace.each do |trace| %>
plugins/mezuro/views/content_viewer/_project_result.rhtml
1 <% unless @content.errors[:base].nil? %> 1 <% unless @content.errors[:base].nil? %>
2 <%= @content.errors[:base] %> 2 <%= @content.errors[:base] %>
3 <% else %> 3 <% else %>
4 - <% form_for :project_date, :html=>{:id=>"project_history_date"} do |f| %>  
5 - <%= f.label :day, "Choose project date:" %>  
6 -  
7 - <table>  
8 - <tr>  
9 - <td>  
10 - Day  
11 - </td>  
12 - <td>  
13 - Month  
14 - </td>  
15 - <td>  
16 - Year  
17 - </td>  
18 - </tr>  
19 - <tr>  
20 - <td>  
21 - <%= f.text_field :day, :size => 1, :maxlength => 2, :placeholder =>"dd" %>  
22 - </td>  
23 - <td>  
24 - <%= f.text_field :month, :size => 1, :maxlength => 2, :placeholder =>"mm" %>  
25 - </td>  
26 - <td>  
27 - <%= f.text_field :year, :size => 1, :maxlength => 4, :placeholder =>"yyyy" %>  
28 - </td>  
29 - </tr>  
30 - </table>  
31 - <%= f.submit "Refresh" %>  
32 - <% end %>  
33 - 4 + <p> Choose a date to see specific project results: </p>
  5 + <div id="datepicker" data-date="<%= @project_result.date %>">
  6 + <input id="datepicker_field" style="display:none"/>
  7 + </div>
34 8
35 <h4><%= _('Last Result') %></h4> 9 <h4><%= _('Last Result') %></h4>
36 10
@@ -48,4 +22,18 @@ @@ -48,4 +22,18 @@
48 <td><%= @project_result.formatted_analysis_time %></td> 22 <td><%= @project_result.formatted_analysis_time %></td>
49 </tr> 23 </tr>
50 </table> 24 </table>
  25 +
  26 +
  27 + <script>
  28 + jQuery(document).ready(function($) {
  29 + $("#datepicker").datepicker({ altField: '#datepicker_field', showOn: 'button', dateFormat: "yy-mm-dd",
  30 + buttonImageOnly: true, buttonImage: '/images/calendar_date_select/calendar.png',
  31 + onSelect: function(dateText, inst) {
  32 + reloadProjectWithDate(dateText) } });
  33 + var date = jQuery("#datepicker").attr('data-date').substr(0,10);
  34 + $("#datepicker").datepicker( "setDate" , date );
  35 +
  36 + });
  37 + </script>
  38 +
51 <% end %> 39 <% end %>
plugins/mezuro/views/content_viewer/show_configuration.rhtml
1 <% @configuration_content = @page 1 <% @configuration_content = @page
2 -@configuration = @page.configuration %> 2 +@kalibro_configuration = @page.kalibro_configuration %>
  3 +<% unless @page.errors[:base].nil? %>
  4 + <% if @page.errors[:base] =~ /There is no configuration named/ %>
  5 + <h3>Warning:</h3>
  6 + <p>This Configuration doesn't exist on the Web Service. Do you want to <a href="/myprofile/<%= @page.profile.name %>/cms/destroy/<%= @page.id%>">delete</a> or <a href="/myprofile/<%= @page.profile.name %>/cms/edit/<%= @page.id%>">save it again</a>?</p>
  7 + <% else %>
  8 + <%= @page.errors[:base] %>
  9 + <% end %>
  10 +<% else %>
3 11
4 -<table id="project_info">  
5 - <tr>  
6 - <td><%= _('Name') %></td>  
7 - <td><%= @configuration.name %></td>  
8 - </tr>  
9 - <tr>  
10 - <td><%= _('Description') %></td>  
11 - <td><%= @configuration.description %></td>  
12 - </tr>  
13 -</table> 12 + <table id="project_info">
  13 + <tr>
  14 + <td><%= _('Name') %></td>
  15 + <td><%= @kalibro_configuration.name %></td>
  16 + </tr>
  17 + <tr>
  18 + <td><%= _('Description') %></td>
  19 + <td><%= @kalibro_configuration.description %></td>
  20 + </tr>
  21 + </table>
14 22
15 -<br/> 23 + <br/>
16 24
17 -<%= link_to "#{image_tag ('/plugins/mezuro/images/plus.png')}Add Metric", :controller => "mezuro_plugin_myprofile",  
18 -:action => "choose_base_tool", :params => { :id => @configuration_content.id } %><br/> 25 + <%= link_to "#{image_tag ('/plugins/mezuro/images/plus.png')}Add Metric", :controller => "mezuro_plugin_myprofile",
  26 + :action => "choose_base_tool", :params => { :id => @configuration_content.id } %><br/>
19 27
20 -<table>  
21 - <tr class="titles">  
22 - <td><h5>Metric Name</h5></td>  
23 - <td><h5>Collector Name</h5></td>  
24 - <td><h5>Metric Code</h5></td>  
25 - <td/><td/>  
26 - </tr>  
27 - <% @configuration.metric_configurations.each do |metric_configuration| %>  
28 - <tr class="metric">  
29 - <td><%= metric_configuration.metric.name %></td>  
30 - <% if metric_configuration.metric.instance_of? Kalibro::NativeMetric %>  
31 - <td>  
32 - <%= metric_configuration.metric.origin %>  
33 - </td>  
34 - <td><%= metric_configuration.code %></td>  
35 - <td><%= link_to "Edit", :controller => "mezuro_plugin_myprofile", :action => "edit_metric_configuration", :params =>  
36 - {:metric_name => metric_configuration.metric.name, :id => @configuration_content.id} %></td>  
37 - <% else %>  
38 - <td>  
39 - Compound Metric  
40 - </td>  
41 - <td><%= metric_configuration.code %></td>  
42 - <td><%= link_to "Edit", :controller => "mezuro_plugin_myprofile", :action => "edit_compound_metric_configuration", :params =>  
43 - {:metric_name => metric_configuration.metric.name, :id => @configuration_content.id} %></td>  
44 - <% end %>  
45 -  
46 - <td><%= link_to "Remove", :controller => "mezuro_plugin_myprofile", :action => "remove_metric_configuration", :params =>  
47 - {:metric_name => metric_configuration.metric.name, :id => @configuration_content.id} %></td> 28 + <table>
  29 + <tr class="titles">
  30 + <td><h5>Metric Name</h5></td>
  31 + <td><h5>Collector Name</h5></td>
  32 + <td><h5>Metric Code</h5></td>
  33 + <td/><td/>
48 </tr> 34 </tr>
49 - <% end %>  
50 -</table> 35 + <% @kalibro_configuration.metric_configurations.each do |metric_configuration| %>
  36 + <tr class="metric">
  37 + <td><%= metric_configuration.metric.name %></td>
  38 + <% if metric_configuration.metric.instance_of? Kalibro::NativeMetric %>
  39 + <td>
  40 + <%= metric_configuration.metric.origin %>
  41 + </td>
  42 + <td><%= metric_configuration.code %></td>
  43 + <td><%= link_to "Edit", :controller => "mezuro_plugin_myprofile", :action => "edit_metric_configuration", :params =>
  44 + {:metric_name => metric_configuration.metric.name, :id => @configuration_content.id} %></td>
  45 + <% else %>
  46 + <td>
  47 + Compound Metric
  48 + </td>
  49 + <td><%= metric_configuration.code %></td>
  50 + <td><%= link_to "Edit", :controller => "mezuro_plugin_myprofile", :action => "edit_compound_metric_configuration", :params =>
  51 + {:metric_name => metric_configuration.metric.name, :id => @configuration_content.id} %></td>
  52 + <% end %>
  53 +
  54 + <td><%= link_to "Remove", :controller => "mezuro_plugin_myprofile", :action => "remove_metric_configuration", :params =>
  55 + {:metric_name => metric_configuration.metric.name, :id => @configuration_content.id} %></td>
  56 + </tr>
  57 + <% end %>
  58 + </table>
  59 +<% end %>
plugins/mezuro/views/content_viewer/show_project.rhtml
@@ -2,60 +2,58 @@ @@ -2,60 +2,58 @@
2 2
3 <% @project = @page.project %> 3 <% @project = @page.project %>
4 <% unless @page.errors[:base].nil? %> 4 <% unless @page.errors[:base].nil? %>
5 - <%= @page.errors[:base] %>  
6 -<% else %>  
7 -  
8 - <% if (@project==nil) %> 5 + <% if @page.errors[:base] =~ /There is no project named/ %>
9 <h3>Warning:</h3> 6 <h3>Warning:</h3>
10 <p>This project doesn't exist on the Web Service. Do you want to <a href="/myprofile/<%= @page.profile.name %>/cms/destroy/<%= @page.id%>">delete</a> or <a href="/myprofile/<%= @page.profile.name %>/cms/edit/<%= @page.id%>">save it again</a>?</p> 7 <p>This project doesn't exist on the Web Service. Do you want to <a href="/myprofile/<%= @page.profile.name %>/cms/destroy/<%= @page.id%>">delete</a> or <a href="/myprofile/<%= @page.profile.name %>/cms/edit/<%= @page.id%>">save it again</a>?</p>
11 <% else %> 8 <% else %>
  9 + <%= @page.errors[:base] %>
  10 + <% end %>
  11 +<% else %>
12 12
  13 + <table>
  14 + <tr>
  15 + <td><%= _('Name') %></td>
  16 + <td><%= @project.name %></td>
  17 + </tr>
  18 + <tr>
  19 + <td><%= _('License') %></td>
  20 + <td><%= @project.license %></td>
  21 + </tr>
  22 + <tr>
  23 + <td><%= _('Description') %></td>
  24 + <td><%= @project.description %></td>
  25 + </tr>
  26 + <tr>
  27 + <td><%= _('Repository type') %></td>
  28 + <td><%= @project.repository.type %></td>
  29 + </tr>
  30 + <tr>
  31 + <td><%= _('Repository address') %></td>
  32 + <td><%= @project.repository.address %></td>
  33 + </tr>
  34 + <tr>
  35 + <td><%= _('Configuration') %></td>
  36 + <td><%= @project.configuration_name %></td>
  37 + </tr>
  38 + <tr>
  39 + <td><%= _('Periodicity') %></td>
  40 + <td><%= MezuroPlugin::Helpers::ContentViewerHelper.get_periodicity_option(@page.periodicity_in_days) %></td>
  41 + </tr>
  42 + <tr>
  43 + <td><%= _('Status')%></td>
  44 + <td>
  45 + <div id="project-state"><%= @project.state %></div>
  46 + <div id="msg-time"></div>
  47 + </td>
  48 + </tr>
  49 + </table>
13 50
14 - <table>  
15 - <tr>  
16 - <td><%= _('Name') %></td>  
17 - <td><%= @project.name %></td>  
18 - </tr>  
19 - <tr>  
20 - <td><%= _('License') %></td>  
21 - <td><%= @project.license %></td>  
22 - </tr>  
23 - <tr>  
24 - <td><%= _('Description') %></td>  
25 - <td><%= @project.description %></td>  
26 - </tr>  
27 - <tr>  
28 - <td><%= _('Repository type') %></td>  
29 - <td><%= @project.repository.type %></td>  
30 - </tr>  
31 - <tr>  
32 - <td><%= _('Repository address') %></td>  
33 - <td><%= @project.repository.address %></td>  
34 - </tr>  
35 - <tr>  
36 - <td><%= _('Configuration') %></td>  
37 - <td><%= @project.configuration_name %></td>  
38 - </tr>  
39 - <tr>  
40 - <td><%= _('Periodicity') %></td>  
41 - <td><%= MezuroPlugin::Helpers::ContentViewerHelper.get_periodicity_option(@page.periodicity_in_days) %></td>  
42 - </tr>  
43 - <tr>  
44 - <td><%= _('Status')%></td>  
45 - <td>  
46 - <div id="project-state"><%= @project.state %></div>  
47 - <div id="msg-time"></div>  
48 - </td>  
49 - </tr>  
50 - </table>  
51 -  
52 - <br /> 51 + <br />
53 52
54 - <div id="project-result" data-profile="<%= @page.profile.identifier %>" data-content="<%= @page.id %>"  
55 - data-project-name="<%= @project.name %>">  
56 - </div>  
57 - <div id="project-tree"></div>  
58 - <div id="module-result">  
59 - </div>  
60 - <% end %> 53 + <div id="project-result" data-profile="<%= @page.profile.identifier %>" data-content="<%= @page.id %>"
  54 + data-project-name="<%= @project.name %>">
  55 + </div>
  56 + <div id="project-tree"></div>
  57 + <div id="module-result">
  58 + </div>
61 <% end %> 59 <% end %>
plugins/mezuro/views/mezuro_plugin_myprofile/_range_form.html.erb
@@ -38,7 +38,7 @@ @@ -38,7 +38,7 @@
38 <%= f.label :color, "(*) Color:" %> 38 <%= f.label :color, "(*) Color:" %>
39 </td> 39 </td>
40 <td> 40 <td>
41 - <%= f.text_field :color %> 41 + <%= f.text_field(:color, :id => "range_color", :value => @range.mezuro_color) %>
42 </td> 42 </td>
43 </tr> 43 </tr>
44 <tr> 44 <tr>
@@ -51,3 +51,11 @@ @@ -51,3 +51,11 @@
51 </tr> 51 </tr>
52 </table> 52 </table>
53 <%= f.submit "Save Range" %> 53 <%= f.submit "Save Range" %>
  54 +
  55 +<script>jQuery(document).ready(function($) {
  56 + $('#range_color').colorPicker({
  57 + onColorChange : function(id, newValue) {
  58 + jQuery('#range_color').val(newValue);
  59 + }
  60 + });
  61 +});</script>
plugins/mezuro/views/mezuro_plugin_myprofile/edit_compound_metric_configuration.html.erb
  1 +<script src="/plugins/mezuro/javascripts/validations.js" type="text/javascript"></script>
  2 +<script src="/plugins/mezuro/javascripts/colorPicker/jquery.colorPicker.min.js" type="text/javascript"></script>
  3 +<script src="/plugins/mezuro/javascripts/colorPicker/jquery.colorPicker.js" type="text/javascript"></script>
  4 +
1 <h2><%= @configuration_content.name %> Configuration</h2> 5 <h2><%= @configuration_content.name %> Configuration</h2>
2 6
3 <% form_for :metric_configuration, :url => {:action =>"update_compound_metric_configuration", :controller => "mezuro_plugin_myprofile"}, :method => :get do |f| %> 7 <% form_for :metric_configuration, :url => {:action =>"update_compound_metric_configuration", :controller => "mezuro_plugin_myprofile"}, :method => :get do |f| %>
plugins/mezuro/views/mezuro_plugin_myprofile/edit_metric_configuration.html.erb
1 <script src="/plugins/mezuro/javascripts/validations.js" type="text/javascript"></script> 1 <script src="/plugins/mezuro/javascripts/validations.js" type="text/javascript"></script>
  2 +<script src="/plugins/mezuro/javascripts/colorPicker/jquery.colorPicker.min.js" type="text/javascript"></script>
  3 +<script src="/plugins/mezuro/javascripts/colorPicker/jquery.colorPicker.js" type="text/javascript"></script>
2 4
3 <h2><%= @configuration_content.name %> Configuration</h2> 5 <h2><%= @configuration_content.name %> Configuration</h2>
4 6
@@ -84,3 +86,4 @@ @@ -84,3 +86,4 @@
84 <%= link_to_remote "New Range", :url => {:action =>"new_range", :controller => "mezuro_plugin_myprofile", :id => @configuration_content.id, :metric_name => @metric.name} %> 86 <%= link_to_remote "New Range", :url => {:action =>"new_range", :controller => "mezuro_plugin_myprofile", :id => @configuration_content.id, :metric_name => @metric.name} %>
85 <div id="range_form" style="display:none"></div> 87 <div id="range_form" style="display:none"></div>
86 88
  89 +
plugins/mezuro/views/mezuro_plugin_myprofile/error_page.html.erb 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +<h2> An error occured: </h2>
  2 +<%= @message %>
  3 +<!-- The <%=h is for escaping the URL properly --!>
plugins/mezuro/views/mezuro_plugin_profile/error_page.html.erb 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +<h2> An error occured: </h2>
  2 +<%= @message %>
plugins/tolerance_time/controllers/tolerance_time_plugin_myprofile_controller.rb 0 → 100644
@@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
  1 +class ToleranceTimePluginMyprofileController < MyProfileController
  2 + def index
  3 + @tolerance = ToleranceTimePlugin::Tolerance.find_by_profile_id(profile.id) || ToleranceTimePlugin::Tolerance.create!(:profile => profile)
  4 + convert_values
  5 + if request.post?
  6 + begin
  7 + convert_params
  8 + @tolerance.update_attributes!(params[:tolerance])
  9 + convert_values
  10 + session[:notice] = _('Tolerance updated')
  11 + rescue
  12 + session[:notice] = _('Tolerance could not be updated')
  13 + end
  14 + end
  15 + end
  16 +
  17 + private
  18 +
  19 + def convert_params
  20 + params[:tolerance][:content_tolerance] = params[:tolerance][:content_tolerance].to_i * params[:content_tolerance_unit].to_i if !params[:tolerance][:content_tolerance].blank?
  21 + params[:tolerance][:comment_tolerance] = params[:tolerance][:comment_tolerance].to_i * params[:comment_tolerance_unit].to_i if !params[:tolerance][:comment_tolerance].blank?
  22 + end
  23 +
  24 + def convert_values
  25 + @content_default_unit = select_unit(@tolerance.content_tolerance)
  26 + @comment_default_unit = select_unit(@tolerance.comment_tolerance)
  27 + @tolerance.content_tolerance /= @content_default_unit if !@tolerance.content_tolerance.nil?
  28 + @tolerance.comment_tolerance /= @comment_default_unit if !@tolerance.comment_tolerance.nil?
  29 + end
  30 +
  31 + def select_unit(value)
  32 + return 1 if value.nil? || value == 0
  33 + return 3600 if value % 3600 == 0
  34 + return 60 if value % 60 == 0
  35 + return 1
  36 + end
  37 +end
plugins/tolerance_time/db/migrate/20120719090320_create_tolerance_time_plugin_tolerances.rb 0 → 100644
@@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
  1 +class CreateToleranceTimePluginTolerances < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :tolerance_time_plugin_tolerances do |t|
  4 + t.references :profile
  5 + t.integer :content_tolerance
  6 + t.integer :comment_tolerance
  7 + end
  8 + end
  9 +
  10 + def self.down
  11 + drop_table :tolerance_time_plugin_tolerances
  12 + end
  13 +end
plugins/tolerance_time/db/migrate/20120719095004_create_tolerance_time_plugin_publications.rb 0 → 100644
@@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
  1 +class CreateToleranceTimePluginPublications < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :tolerance_time_plugin_publications do |t|
  4 + t.references :target, :polymorphic => true
  5 + t.timestamps
  6 + end
  7 + end
  8 +
  9 + def self.down
  10 + drop_table :tolerance_time_plugin_publications
  11 + end
  12 +end
plugins/tolerance_time/lib/ext/article.rb 0 → 100644
@@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
  1 +require_dependency 'article'
  2 +
  3 +class Article
  4 + after_create do |article|
  5 + ToleranceTimePlugin::Publication.create!(:target => article) if article.published
  6 + end
  7 +
  8 + before_save do |article|
  9 + if article.published_changed?
  10 + if article.published
  11 + ToleranceTimePlugin::Publication.create!(:target => article)
  12 + else
  13 + publication = ToleranceTimePlugin::Publication.find_by_target(article)
  14 + publication.destroy if publication.present?
  15 + end
  16 + end
  17 + end
  18 +
  19 + before_destroy do |article|
  20 + publication = ToleranceTimePlugin::Publication.find_by_target(article)
  21 + publication.destroy if publication.present?
  22 + end
  23 +end
plugins/tolerance_time/lib/ext/comment.rb 0 → 100644
@@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
  1 +require_dependency 'comment'
  2 +
  3 +class Comment
  4 + after_create do |comment|
  5 + ToleranceTimePlugin::Publication.create!(:target => comment)
  6 + end
  7 +
  8 + before_destroy do |comment|
  9 + publication = ToleranceTimePlugin::Publication.find_by_target(comment)
  10 + publication.destroy if publication.present?
  11 + end
  12 +end
  13 +
plugins/tolerance_time/lib/tolerance_time_plugin.rb 0 → 100644
@@ -0,0 +1,71 @@ @@ -0,0 +1,71 @@
  1 +require_dependency 'ext/article'
  2 +require_dependency 'ext/comment'
  3 +
  4 +class ToleranceTimePlugin < Noosfero::Plugin
  5 +
  6 + def self.plugin_name
  7 + "Tolerance Time"
  8 + end
  9 +
  10 + def self.plugin_description
  11 + _("Adds a tolerance time for editing content after its publication")
  12 + end
  13 +
  14 + def self.expired?(content)
  15 + return false if content.kind_of?(Comment) && !content.article.kind_of?(Article)
  16 +
  17 + expirable = content.kind_of?(Comment) || (!content.folder? && content.published?)
  18 + publication = ToleranceTimePlugin::Publication.find_by_target(content)
  19 + publication = ToleranceTimePlugin::Publication.create!(:target => content) if expirable && publication.nil?
  20 + person_article = content.kind_of?(Article) && content.profile.kind_of?(Person)
  21 +
  22 + !person_article && expirable && publication.expired?
  23 + end
  24 +
  25 + def control_panel_buttons
  26 + {:title => _('Tolerance Adjustements'), :url => {:controller => 'tolerance_time_plugin_myprofile', :profile => context.profile.identifier}, :icon => 'tolerance-time' }
  27 + end
  28 +
  29 + def stylesheet?
  30 + true
  31 + end
  32 +
  33 + def cms_controller_filters
  34 + return if !context.environment.plugin_enabled?(ToleranceTimePlugin)
  35 + block = lambda do
  36 + content = Article.find(params[:id])
  37 + if ToleranceTimePlugin.expired?(content)
  38 + session[:notice] = _("This content can't be edited anymore because it expired the tolerance time")
  39 + redirect_to content.url
  40 + end
  41 + end
  42 +
  43 + { :type => 'before_filter',
  44 + :method_name => 'expired_content',
  45 + :options => {:only => 'edit'},
  46 + :block => block }
  47 + end
  48 +
  49 + def content_viewer_controller_filters
  50 + return if !context.environment.plugin_enabled?(ToleranceTimePlugin)
  51 + block = lambda do
  52 + content = Comment.find(params[:id])
  53 + if ToleranceTimePlugin.expired?(content)
  54 + session[:notice] = _("This content can't be edited anymore because it expired the tolerance time")
  55 + redirect_to content.article.url
  56 + end
  57 + end
  58 +
  59 + { :type => 'before_filter',
  60 + :method_name => 'expired_content',
  61 + :options => {:only => 'edit_comment'},
  62 + :block => block }
  63 + end
  64 +
  65 + def content_expire_edit(content)
  66 + if ToleranceTimePlugin.expired?(content)
  67 + _('The tolerance time for editing this content is over.')
  68 + end
  69 + end
  70 +
  71 +end
plugins/tolerance_time/lib/tolerance_time_plugin/publication.rb 0 → 100644
@@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
  1 +class ToleranceTimePlugin::Publication < Noosfero::Plugin::ActiveRecord
  2 + belongs_to :target, :polymorphic => true
  3 + validates_presence_of :target_id, :target_type
  4 + validates_uniqueness_of :target_id, :scope => :target_type
  5 +
  6 + class << self
  7 + def find_by_target(target)
  8 + kind = target.kind_of?(Article) ? 'Article' : 'Comment'
  9 + find_by_target_id_and_target_type(target.id, kind)
  10 + end
  11 + end
  12 +
  13 + def expired?
  14 + profile = (target.kind_of?(Article) ? target.profile : target.article.profile)
  15 + profile_tolerance = ToleranceTimePlugin::Tolerance.find_by_profile_id(profile.id)
  16 + content_tolerance = profile_tolerance ? profile_tolerance.content_tolerance : nil
  17 + comment_tolerance = profile_tolerance ? profile_tolerance.comment_tolerance : nil
  18 + if target.kind_of?(Article)
  19 + tolerance_time = content_tolerance || 1.0/0
  20 + elsif target.kind_of?(Comment)
  21 + tolerance_time = comment_tolerance || 1.0/0
  22 + else
  23 + tolerance_time = 1.0/0
  24 + end
  25 + created_at.to_f.to_i+tolerance_time < Time.now.to_i
  26 + end
  27 +end
plugins/tolerance_time/lib/tolerance_time_plugin/tolerance.rb 0 → 100644
@@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
  1 +class ToleranceTimePlugin::Tolerance < Noosfero::Plugin::ActiveRecord
  2 + belongs_to :profile
  3 + validates_presence_of :profile_id
  4 + validates_uniqueness_of :profile_id
  5 + validates_numericality_of :content_tolerance, :only_integer => true, :allow_nil => true
  6 + validates_numericality_of :comment_tolerance, :only_integer => true, :allow_nil => true
  7 +end
plugins/tolerance_time/public/icons/tolerance-time.png 0 → 100644

4.71 KB

plugins/tolerance_time/public/style.css 0 → 100644
@@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
  1 +.controller-profile_editor a.control-panel-tolerance-time,
  2 +.controller-profile_editor .msie6 a.control-panel-tolerance-time {
  3 + background-image: url(/plugins/tolerance_time/icons/tolerance-time.png)
  4 +}
plugins/tolerance_time/test/unit/article_test.rb 0 → 100644
@@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +
  3 +class ArticleTest < ActiveSupport::TestCase
  4 + should 'create a publication after publishing the article' do
  5 + article = fast_create(Article, :published => false, :profile_id => fast_create(Profile).id)
  6 + assert_nil ToleranceTimePlugin::Publication.find_by_target(article)
  7 +
  8 + article.published = true
  9 + article.save!
  10 + assert_not_nil ToleranceTimePlugin::Publication.find_by_target(article)
  11 + end
  12 +
  13 + should 'destroy publication if the article is destroyed' do
  14 + profile = fast_create(Profile)
  15 + article = fast_create(Article, :profile_id => profile.id)
  16 + article_publication = ToleranceTimePlugin::Publication.create!(:target => article)
  17 + article.destroy
  18 + assert_raise ActiveRecord::RecordNotFound do
  19 + article_publication.reload
  20 + end
  21 + end
  22 +
  23 + should 'destroy publication if the article is changed to not published' do
  24 + profile = fast_create(Profile)
  25 + article = fast_create(Article, :profile_id => profile.id)
  26 + article_publication = ToleranceTimePlugin::Publication.create!(:target => article)
  27 + article.published = false
  28 + article.save!
  29 + assert_raise ActiveRecord::RecordNotFound do
  30 + article_publication.reload
  31 + end
  32 + end
  33 +end
plugins/tolerance_time/test/unit/comment_test.rb 0 → 100644
@@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +
  3 +class CommentTest < ActiveSupport::TestCase
  4 + should 'create a publication after posting a comment' do
  5 + article = fast_create(Article, :profile_id => fast_create(Person).id)
  6 + comment = Comment.new(:author_id => fast_create(Person).id, :body => 'Hello There!', :source_id => article.id)
  7 + assert_difference ToleranceTimePlugin::Publication, :count do
  8 + comment.save!
  9 + end
  10 + assert_not_nil ToleranceTimePlugin::Publication.find_by_target(comment)
  11 + end
  12 +
  13 + should 'destroy publication if the comment is destroyed' do
  14 + profile = fast_create(Profile)
  15 + article = fast_create(Article, :profile_id => profile.id)
  16 + comment = fast_create(Comment, :source_id => article.id)
  17 + comment_publication = ToleranceTimePlugin::Publication.create!(:target => comment)
  18 + comment.destroy
  19 + assert_raise ActiveRecord::RecordNotFound do
  20 + comment_publication.reload
  21 + end
  22 + end
  23 +end
  24 +
plugins/tolerance_time/test/unit/tolerance_time_plugin/publication_test.rb 0 → 100644
@@ -0,0 +1,78 @@ @@ -0,0 +1,78 @@
  1 +require File.dirname(__FILE__) + '/../../../../../test/test_helper'
  2 +
  3 +class ToleranceTimePlugin::PublicationTest < ActiveSupport::TestCase
  4 + should 'validate presence of target' do
  5 + publication = ToleranceTimePlugin::Publication.new
  6 + publication.valid?
  7 + assert publication.errors.invalid?(:target_id)
  8 + assert publication.errors.invalid?(:target_type)
  9 +
  10 + publication.target = fast_create(Article)
  11 + publication.valid?
  12 + assert !publication.errors.invalid?(:target_id)
  13 + assert !publication.errors.invalid?(:target_type)
  14 + end
  15 +
  16 + should 'validate uniqueness of target' do
  17 + target = fast_create(Article)
  18 + p1 = ToleranceTimePlugin::Publication.create!(:target => target)
  19 + p2 = ToleranceTimePlugin::Publication.new(:target => target)
  20 + p2.valid?
  21 +
  22 + assert p2.errors.invalid?(:target_id)
  23 + end
  24 +
  25 + should 'be able to find publication by target' do
  26 + article = fast_create(Article)
  27 + publication = ToleranceTimePlugin::Publication.create!(:target => article)
  28 + assert_equal publication, ToleranceTimePlugin::Publication.find_by_target(article)
  29 + end
  30 +
  31 + should 'avaliate if the publication is expired' do
  32 + profile = fast_create(Profile)
  33 + author = fast_create(Person)
  34 + a1 = fast_create(Article, :profile_id => profile.id)
  35 + a2 = fast_create(Article, :profile_id => profile.id)
  36 + c1 = fast_create(Comment, :source_id => a1.id)
  37 + c2 = fast_create(Comment, :source_id => a2.id)
  38 + ToleranceTimePlugin::Tolerance.create!(:profile => profile, :content_tolerance => 10.minutes, :comment_tolerance => 5.minutes)
  39 + expired_article = ToleranceTimePlugin::Publication.create!(:target => a1)
  40 + expired_article.created_at = 15.minutes.ago
  41 + expired_article.save!
  42 + on_time_article = ToleranceTimePlugin::Publication.create!(:target => a2)
  43 + on_time_article.created_at = 5.minutes.ago
  44 + on_time_article.save!
  45 + expired_comment = ToleranceTimePlugin::Publication.create!(:target => c1)
  46 + expired_comment.created_at = 8.minutes.ago
  47 + expired_comment.save!
  48 + on_time_comment = ToleranceTimePlugin::Publication.create!(:target => c2)
  49 + on_time_comment.created_at = 2.minutes.ago
  50 + on_time_comment.save!
  51 +
  52 + assert expired_article.expired?
  53 + assert !on_time_article.expired?
  54 + assert expired_comment.expired?
  55 + assert !on_time_comment.expired?
  56 + end
  57 +
  58 + should 'consider tolerance infinity if not defined' do
  59 + profile = fast_create(Profile)
  60 + article = fast_create(Article, :profile_id => profile.id)
  61 + article_publication = ToleranceTimePlugin::Publication.create!(:target => article)
  62 + article_publication.created_at = 1000.years.ago
  63 + article_publication.save!
  64 + ToleranceTimePlugin::Tolerance.create!(:profile => profile)
  65 +
  66 + assert !article_publication.expired?
  67 + end
  68 +
  69 + should 'not crash if profile has no tolerance yet defined' do
  70 + profile = fast_create(Profile)
  71 + article = fast_create(Article, :profile_id => profile.id)
  72 + article_publication = ToleranceTimePlugin::Publication.create!(:target => article)
  73 + article_publication.created_at = 1000.years.ago
  74 + article_publication.save!
  75 +
  76 + assert !article_publication.expired?
  77 + end
  78 +end
plugins/tolerance_time/test/unit/tolerance_time_plugin/tolerance_test.rb 0 → 100644
@@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
  1 +require File.dirname(__FILE__) + '/../../../../../test/test_helper'
  2 +
  3 +class ToleranceTimePlugin::ToleranceTest < ActiveSupport::TestCase
  4 + should 'validate presence of profile' do
  5 + tolerance = ToleranceTimePlugin::Tolerance.new
  6 + tolerance.valid?
  7 + assert tolerance.errors.invalid?(:profile_id)
  8 +
  9 + tolerance.profile = fast_create(Profile)
  10 + tolerance.valid?
  11 + assert !tolerance.errors.invalid?(:profile_id)
  12 + end
  13 +
  14 + should 'validate uniqueness of profile' do
  15 + profile = fast_create(Profile)
  16 + t1 = ToleranceTimePlugin::Tolerance.create!(:profile => profile)
  17 + t2 = ToleranceTimePlugin::Tolerance.new(:profile => profile)
  18 + t2.valid?
  19 +
  20 + assert t2.errors.invalid?(:profile_id)
  21 + end
  22 +
  23 + should 'validate integer format for comment and content tolerance' do
  24 + tolerance = ToleranceTimePlugin::Tolerance.new(:profile => fast_create(Profile))
  25 + assert tolerance.valid?
  26 +
  27 + tolerance.comment_tolerance = 'sdfa'
  28 + tolerance.content_tolerance = 4.5
  29 + tolerance.valid?
  30 + assert tolerance.errors.invalid?(:comment_tolerance)
  31 + assert tolerance.errors.invalid?(:content_tolerance)
  32 +
  33 + tolerance.comment_tolerance = 3
  34 + tolerance.content_tolerance = 6
  35 + assert tolerance.valid?
  36 + end
  37 +end
plugins/tolerance_time/views/tolerance_time_plugin_myprofile/index.html.erb 0 → 100644
@@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
  1 +<h1><%= _('Tolerance Adjustments') %></h1>
  2 +
  3 +<%= error_messages_for :tolerance %>
  4 +
  5 +<% form_for :tolerance, @tolerance do |f| %>
  6 +
  7 + <% time_units = [[_('Seconds'), 1], [_('Minutes'), 60], [_('Hours'), 3600]]%>
  8 +
  9 + <% if profile.organization? %>
  10 + <%= labelled_form_field(_('Content edition tolerance time'),
  11 + f.text_field(:content_tolerance, :size => 2, :style => 'font-size: 14px; text-align: right') +
  12 + select_tag(:content_tolerance_unit, options_for_select(time_units, @content_default_unit) )) %>
  13 + <% end %>
  14 + <%= labelled_form_field(_('Comment edition tolerance time'),
  15 + f.text_field(:comment_tolerance, :size => 2, :style => 'font-size: 14px; text-align: right') +
  16 + select_tag(:comment_tolerance_unit, options_for_select(time_units, @comment_default_unit) )) %>
  17 +
  18 + <%= content_tag( 'small', _('Empty means unlimited and zero means right away.') ) %>
  19 +
  20 + <% button_bar do %>
  21 + <%= submit_button('save', _('Save'))%>
  22 + <%= button('back', _('Back'), {:controller => 'profile_editor'})%>
  23 + <% end %>
  24 +<% end %>
public/designs/icons/tango/ie6.css
1 .msie6 .icon-edit { background-image: url(ie6/Tango/16x16/apps/text-editor.gif) } 1 .msie6 .icon-edit { background-image: url(ie6/Tango/16x16/apps/text-editor.gif) }
2 .msie6 .icon-home { background-image: url(ie6/Tango/16x16/actions/go-home.gif) } 2 .msie6 .icon-home { background-image: url(ie6/Tango/16x16/actions/go-home.gif) }
3 -.msie6 .icon-new { background-image: url(ie6/Tango/16x16/actions/filenew.gif) } 3 +.msie6 .icon-new,
  4 +.msie6 .icon-suggest { background-image: url(ie6/Tango/16x16/actions/filenew.gif) }
4 .msie6 .icon-close { background-image: url(ie6/Tango/16x16/actions/gtk-cancel.gif) } 5 .msie6 .icon-close { background-image: url(ie6/Tango/16x16/actions/gtk-cancel.gif) }
5 .msie6 .icon-newfolder { background-image: url(ie6/Tango/16x16/actions/folder-new.gif) } 6 .msie6 .icon-newfolder { background-image: url(ie6/Tango/16x16/actions/folder-new.gif) }
6 .msie6 .icon-save { background-image: url(ie6/Tango/16x16/actions/filesave.gif) } 7 .msie6 .icon-save { background-image: url(ie6/Tango/16x16/actions/filesave.gif) }
public/designs/icons/tango/style.css
@@ -3,7 +3,8 @@ @@ -3,7 +3,8 @@
3 /******************SMALL ICONS********************/ 3 /******************SMALL ICONS********************/
4 .icon-edit { background-image: url(Tango/16x16/apps/text-editor.png) } 4 .icon-edit { background-image: url(Tango/16x16/apps/text-editor.png) }
5 .icon-home { background-image: url(Tango/16x16/actions/go-home.png) } 5 .icon-home { background-image: url(Tango/16x16/actions/go-home.png) }
6 -.icon-new { background-image: url(Tango/16x16/actions/filenew.png) } 6 +.icon-new,
  7 +.icon-suggest { background-image: url(Tango/16x16/actions/filenew.png) }
7 .icon-close { background-image: url(Tango/16x16/actions/gtk-cancel.png) } 8 .icon-close { background-image: url(Tango/16x16/actions/gtk-cancel.png) }
8 .icon-newfolder { background-image: url(Tango/16x16/actions/folder-new.png) } 9 .icon-newfolder { background-image: url(Tango/16x16/actions/folder-new.png) }
9 .icon-folder { background-image: url(Tango/16x16/places/folder.png) } 10 .icon-folder { background-image: url(Tango/16x16/places/folder.png) }
public/designs/themes/base/style.css
@@ -483,6 +483,17 @@ div#notice { @@ -483,6 +483,17 @@ div#notice {
483 padding: 0px; 483 padding: 0px;
484 } 484 }
485 485
  486 +#content .box-1 .people-block ul,
  487 +#content .box-1 .profile-list-block ul,
  488 +#content .box-1 .enterprises-block ul,
  489 +#content .box-1 .members-block ul,
  490 +#content .box-1 .communities-block ul,
  491 +#content .box-1 .friends-block ul,
  492 +#content .box-1 .fans-block ul {
  493 + width: auto;
  494 + display: block;
  495 +}
  496 +
486 #content .tags-block .block-footer-content a, 497 #content .tags-block .block-footer-content a,
487 #content .people-block .block-footer-content a, 498 #content .people-block .block-footer-content a,
488 #content .profile-list-block .block-footer-content a, 499 #content .profile-list-block .block-footer-content a,
@@ -588,33 +599,51 @@ div#notice { @@ -588,33 +599,51 @@ div#notice {
588 display: none; 599 display: none;
589 } 600 }
590 601
  602 +#content .box-1 .link-list-block {
  603 + margin: 0px;
  604 +}
  605 +
591 #content .link-list-block li { 606 #content .link-list-block li {
592 background: #FFF; 607 background: #FFF;
593 padding: 0px; 608 padding: 0px;
594 margin: 5px 0px; 609 margin: 5px 0px;
595 } 610 }
596 611
  612 +#content .box-1 .link-list-block li {
  613 + display: inline-block;
  614 +}
  615 +
597 #content .link-list-block li a { 616 #content .link-list-block li a {
598 font-size: 14px; 617 font-size: 14px;
599 line-height: 24px; 618 line-height: 24px;
600 color: #000; 619 color: #000;
601 background-color: #EEE; 620 background-color: #EEE;
602 background-position: 4px 50%; 621 background-position: 4px 50%;
603 - -moz-border-radius: 4px;  
604 - -webkit-border-radius: 4px; 622 + border-radius: 4px;
  623 +}
  624 +
  625 +#content .box-1 .link-list-block li a {
  626 + padding-left: 25px;
  627 + padding-right: 10px;
  628 + margin-right: 5px;
  629 + background-position: 5px 50%;
605 } 630 }
606 631
607 #content .link-list-block li a.link-this-page { 632 #content .link-list-block li a.link-this-page {
608 - -moz-border-radius-topright: 0px;  
609 - -moz-border-radius-bottomright: 0px;  
610 - -webkit-border-radius-topright: 0px;  
611 - -webkit-border-radius-bottomright: 0px; 633 + border-radius-topright: 0px;
  634 + border-radius-bottomright: 0px;
612 background-color: #cecece; 635 background-color: #cecece;
613 max-width: 175px; 636 max-width: 175px;
614 width: 200px; 637 width: 200px;
615 border-right: 2px solid #555753; 638 border-right: 2px solid #555753;
616 } 639 }
617 640
  641 +#content .box-1 .link-list-block li a.link-this-page {
  642 + width: auto;
  643 + border: none;
  644 + border-radius: 4px;
  645 +}
  646 +
618 #content .link-list-block li a:hover { 647 #content .link-list-block li a:hover {
619 background-color: #555753; 648 background-color: #555753;
620 color: #FFF; 649 color: #FFF;
@@ -908,6 +937,16 @@ hr.pre-posts, hr.sep-posts { @@ -908,6 +937,16 @@ hr.pre-posts, hr.sep-posts {
908 #article-actions a.button:hover { 937 #article-actions a.button:hover {
909 color: #555753; 938 color: #555753;
910 } 939 }
  940 +#content a.disabled,
  941 +#content a.disabled:hover {
  942 + color: #888;
  943 + text-decoration: none;
  944 +}
  945 +#content a.button.disabled,
  946 +#content a.button.disabled:hover {
  947 + background-color: #CCC;
  948 + border-color: #CCC;
  949 +}
911 950
912 #addThis { 951 #addThis {
913 text-align: right; 952 text-align: right;
public/filters.svg 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +<svg xmlns="http://www.w3.org/2000/svg">
  2 + <filter id="grayscale">
  3 + <feColorMatrix type="matrix" values="0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0"/>
  4 + </filter>
  5 +</svg>
public/javascripts/application.js
@@ -922,7 +922,7 @@ jQuery(function($) { @@ -922,7 +922,7 @@ jQuery(function($) {
922 $('.colorbox').live('click', function() { 922 $('.colorbox').live('click', function() {
923 $.fn.colorbox({ 923 $.fn.colorbox({
924 href:$(this).attr('href'), 924 href:$(this).attr('href'),
925 - maxWidth: '500', 925 + maxWidth: '600',
926 maxHeight: '550', 926 maxHeight: '550',
927 open:true 927 open:true
928 }); 928 });
public/javascripts/jquery-ui-timepicker-addon.js 0 → 100644
@@ -0,0 +1,1530 @@ @@ -0,0 +1,1530 @@
  1 +/*
  2 +* jQuery timepicker addon
  3 +* By: Trent Richardson [http://trentrichardson.com]
  4 +* Version 1.0.1
  5 +* Last Modified: 07/01/2012
  6 +*
  7 +* Copyright 2012 Trent Richardson
  8 +* You may use this project under MIT or GPL licenses.
  9 +* http://trentrichardson.com/Impromptu/GPL-LICENSE.txt
  10 +* http://trentrichardson.com/Impromptu/MIT-LICENSE.txt
  11 +*
  12 +* HERES THE CSS:
  13 +* .ui-timepicker-div .ui-widget-header { margin-bottom: 8px; }
  14 +* .ui-timepicker-div dl { text-align: left; }
  15 +* .ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; }
  16 +* .ui-timepicker-div dl dd { margin: 0 10px 10px 65px; }
  17 +* .ui-timepicker-div td { font-size: 90%; }
  18 +* .ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; }
  19 +*/
  20 +
  21 +/*jslint evil: true, maxlen: 300, white: false, undef: false, nomen: false, onevar: false */
  22 +
  23 +(function($) {
  24 +
  25 +// Prevent "Uncaught RangeError: Maximum call stack size exceeded"
  26 +$.ui.timepicker = $.ui.timepicker || {};
  27 +if ($.ui.timepicker.version) {
  28 + return;
  29 +}
  30 +
  31 +$.extend($.ui, { timepicker: { version: "1.0.1" } });
  32 +
  33 +/* Time picker manager.
  34 + Use the singleton instance of this class, $.timepicker, to interact with the time picker.
  35 + Settings for (groups of) time pickers are maintained in an instance object,
  36 + allowing multiple different settings on the same page. */
  37 +
  38 +function Timepicker() {
  39 + this.regional = []; // Available regional settings, indexed by language code
  40 + this.regional[''] = { // Default regional settings
  41 + currentText: 'Now',
  42 + closeText: 'Done',
  43 + ampm: false,
  44 + amNames: ['AM', 'A'],
  45 + pmNames: ['PM', 'P'],
  46 + timeFormat: 'hh:mm tt',
  47 + timeSuffix: '',
  48 + timeOnlyTitle: 'Choose Time',
  49 + timeText: 'Time',
  50 + hourText: 'Hour',
  51 + minuteText: 'Minute',
  52 + secondText: 'Second',
  53 + millisecText: 'Millisecond',
  54 + timezoneText: 'Time Zone'
  55 + };
  56 + this._defaults = { // Global defaults for all the datetime picker instances
  57 + showButtonPanel: true,
  58 + timeOnly: false,
  59 + showHour: true,
  60 + showMinute: true,
  61 + showSecond: false,
  62 + showMillisec: false,
  63 + showTimezone: false,
  64 + showTime: true,
  65 + stepHour: 1,
  66 + stepMinute: 1,
  67 + stepSecond: 1,
  68 + stepMillisec: 1,
  69 + hour: 0,
  70 + minute: 0,
  71 + second: 0,
  72 + millisec: 0,
  73 + timezone: null,
  74 + useLocalTimezone: false,
  75 + defaultTimezone: "+0000",
  76 + hourMin: 0,
  77 + minuteMin: 0,
  78 + secondMin: 0,
  79 + millisecMin: 0,
  80 + hourMax: 23,
  81 + minuteMax: 59,
  82 + secondMax: 59,
  83 + millisecMax: 999,
  84 + minDateTime: null,
  85 + maxDateTime: null,
  86 + onSelect: null,
  87 + hourGrid: 0,
  88 + minuteGrid: 0,
  89 + secondGrid: 0,
  90 + millisecGrid: 0,
  91 + alwaysSetTime: true,
  92 + separator: ' ',
  93 + altFieldTimeOnly: true,
  94 + showTimepicker: true,
  95 + timezoneIso8601: false,
  96 + timezoneList: null,
  97 + addSliderAccess: false,
  98 + sliderAccessArgs: null
  99 + };
  100 + $.extend(this._defaults, this.regional['']);
  101 +}
  102 +
  103 +$.extend(Timepicker.prototype, {
  104 + $input: null,
  105 + $altInput: null,
  106 + $timeObj: null,
  107 + inst: null,
  108 + hour_slider: null,
  109 + minute_slider: null,
  110 + second_slider: null,
  111 + millisec_slider: null,
  112 + timezone_select: null,
  113 + hour: 0,
  114 + minute: 0,
  115 + second: 0,
  116 + millisec: 0,
  117 + timezone: null,
  118 + defaultTimezone: "+0000",
  119 + hourMinOriginal: null,
  120 + minuteMinOriginal: null,
  121 + secondMinOriginal: null,
  122 + millisecMinOriginal: null,
  123 + hourMaxOriginal: null,
  124 + minuteMaxOriginal: null,
  125 + secondMaxOriginal: null,
  126 + millisecMaxOriginal: null,
  127 + ampm: '',
  128 + formattedDate: '',
  129 + formattedTime: '',
  130 + formattedDateTime: '',
  131 + timezoneList: null,
  132 +
  133 + /* Override the default settings for all instances of the time picker.
  134 + @param settings object - the new settings to use as defaults (anonymous object)
  135 + @return the manager object */
  136 + setDefaults: function(settings) {
  137 + extendRemove(this._defaults, settings || {});
  138 + return this;
  139 + },
  140 +
  141 + //########################################################################
  142 + // Create a new Timepicker instance
  143 + //########################################################################
  144 + _newInst: function($input, o) {
  145 + var tp_inst = new Timepicker(),
  146 + inlineSettings = {};
  147 +
  148 + for (var attrName in this._defaults) {
  149 + var attrValue = $input.attr('time:' + attrName);
  150 + if (attrValue) {
  151 + try {
  152 + inlineSettings[attrName] = eval(attrValue);
  153 + } catch (err) {
  154 + inlineSettings[attrName] = attrValue;
  155 + }
  156 + }
  157 + }
  158 + tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, o, {
  159 + beforeShow: function(input, dp_inst) {
  160 + if ($.isFunction(o.beforeShow)) {
  161 + return o.beforeShow(input, dp_inst, tp_inst);
  162 + }
  163 + },
  164 + onChangeMonthYear: function(year, month, dp_inst) {
  165 + // Update the time as well : this prevents the time from disappearing from the $input field.
  166 + tp_inst._updateDateTime(dp_inst);
  167 + if ($.isFunction(o.onChangeMonthYear)) {
  168 + o.onChangeMonthYear.call($input[0], year, month, dp_inst, tp_inst);
  169 + }
  170 + },
  171 + onClose: function(dateText, dp_inst) {
  172 + if (tp_inst.timeDefined === true && $input.val() !== '') {
  173 + tp_inst._updateDateTime(dp_inst);
  174 + }
  175 + if ($.isFunction(o.onClose)) {
  176 + o.onClose.call($input[0], dateText, dp_inst, tp_inst);
  177 + }
  178 + },
  179 + timepicker: tp_inst // add timepicker as a property of datepicker: $.datepicker._get(dp_inst, 'timepicker');
  180 + });
  181 + tp_inst.amNames = $.map(tp_inst._defaults.amNames, function(val) { return val.toUpperCase(); });
  182 + tp_inst.pmNames = $.map(tp_inst._defaults.pmNames, function(val) { return val.toUpperCase(); });
  183 +
  184 + if (tp_inst._defaults.timezoneList === null) {
  185 + var timezoneList = [];
  186 + for (var i = -11; i <= 12; i++) {
  187 + timezoneList.push((i >= 0 ? '+' : '-') + ('0' + Math.abs(i).toString()).slice(-2) + '00');
  188 + }
  189 + if (tp_inst._defaults.timezoneIso8601) {
  190 + timezoneList = $.map(timezoneList, function(val) {
  191 + return val == '+0000' ? 'Z' : (val.substring(0, 3) + ':' + val.substring(3));
  192 + });
  193 + }
  194 + tp_inst._defaults.timezoneList = timezoneList;
  195 + }
  196 +
  197 + tp_inst.timezone = tp_inst._defaults.timezone;
  198 + tp_inst.hour = tp_inst._defaults.hour;
  199 + tp_inst.minute = tp_inst._defaults.minute;
  200 + tp_inst.second = tp_inst._defaults.second;
  201 + tp_inst.millisec = tp_inst._defaults.millisec;
  202 + tp_inst.ampm = '';
  203 + tp_inst.$input = $input;
  204 +
  205 + if (o.altField) {
  206 + tp_inst.$altInput = $(o.altField)
  207 + .css({ cursor: 'pointer' })
  208 + .focus(function(){ $input.trigger("focus"); });
  209 + }
  210 +
  211 + if(tp_inst._defaults.minDate===0 || tp_inst._defaults.minDateTime===0)
  212 + {
  213 + tp_inst._defaults.minDate=new Date();
  214 + }
  215 + if(tp_inst._defaults.maxDate===0 || tp_inst._defaults.maxDateTime===0)
  216 + {
  217 + tp_inst._defaults.maxDate=new Date();
  218 + }
  219 +
  220 + // datepicker needs minDate/maxDate, timepicker needs minDateTime/maxDateTime..
  221 + if(tp_inst._defaults.minDate !== undefined && tp_inst._defaults.minDate instanceof Date) {
  222 + tp_inst._defaults.minDateTime = new Date(tp_inst._defaults.minDate.getTime());
  223 + }
  224 + if(tp_inst._defaults.minDateTime !== undefined && tp_inst._defaults.minDateTime instanceof Date) {
  225 + tp_inst._defaults.minDate = new Date(tp_inst._defaults.minDateTime.getTime());
  226 + }
  227 + if(tp_inst._defaults.maxDate !== undefined && tp_inst._defaults.maxDate instanceof Date) {
  228 + tp_inst._defaults.maxDateTime = new Date(tp_inst._defaults.maxDate.getTime());
  229 + }
  230 + if(tp_inst._defaults.maxDateTime !== undefined && tp_inst._defaults.maxDateTime instanceof Date) {
  231 + tp_inst._defaults.maxDate = new Date(tp_inst._defaults.maxDateTime.getTime());
  232 + }
  233 + return tp_inst;
  234 + },
  235 +
  236 + //########################################################################
  237 + // add our sliders to the calendar
  238 + //########################################################################
  239 + _addTimePicker: function(dp_inst) {
  240 + var currDT = (this.$altInput && this._defaults.altFieldTimeOnly) ?
  241 + this.$input.val() + ' ' + this.$altInput.val() :
  242 + this.$input.val();
  243 +
  244 + this.timeDefined = this._parseTime(currDT);
  245 + this._limitMinMaxDateTime(dp_inst, false);
  246 + this._injectTimePicker();
  247 + },
  248 +
  249 + //########################################################################
  250 + // parse the time string from input value or _setTime
  251 + //########################################################################
  252 + _parseTime: function(timeString, withDate) {
  253 + if (!this.inst) {
  254 + this.inst = $.datepicker._getInst(this.$input[0]);
  255 + }
  256 +
  257 + if (withDate || !this._defaults.timeOnly)
  258 + {
  259 + var dp_dateFormat = $.datepicker._get(this.inst, 'dateFormat');
  260 + try {
  261 + var parseRes = parseDateTimeInternal(dp_dateFormat, this._defaults.timeFormat, timeString, $.datepicker._getFormatConfig(this.inst), this._defaults);
  262 + if (!parseRes.timeObj) { return false; }
  263 + $.extend(this, parseRes.timeObj);
  264 + } catch (err)
  265 + {
  266 + return false;
  267 + }
  268 + return true;
  269 + }
  270 + else
  271 + {
  272 + var timeObj = $.datepicker.parseTime(this._defaults.timeFormat, timeString, this._defaults);
  273 + if(!timeObj) { return false; }
  274 + $.extend(this, timeObj);
  275 + return true;
  276 + }
  277 + },
  278 +
  279 + //########################################################################
  280 + // generate and inject html for timepicker into ui datepicker
  281 + //########################################################################
  282 + _injectTimePicker: function() {
  283 + var $dp = this.inst.dpDiv,
  284 + o = this._defaults,
  285 + tp_inst = this,
  286 + // Added by Peter Medeiros:
  287 + // - Figure out what the hour/minute/second max should be based on the step values.
  288 + // - Example: if stepMinute is 15, then minMax is 45.
  289 + hourMax = parseInt((o.hourMax - ((o.hourMax - o.hourMin) % o.stepHour)) ,10),
  290 + minMax = parseInt((o.minuteMax - ((o.minuteMax - o.minuteMin) % o.stepMinute)) ,10),
  291 + secMax = parseInt((o.secondMax - ((o.secondMax - o.secondMin) % o.stepSecond)) ,10),
  292 + millisecMax = parseInt((o.millisecMax - ((o.millisecMax - o.millisecMin) % o.stepMillisec)) ,10),
  293 + dp_id = this.inst.id.toString().replace(/([^A-Za-z0-9_])/g, '');
  294 +
  295 + // Prevent displaying twice
  296 + //if ($dp.find("div#ui-timepicker-div-"+ dp_id).length === 0) {
  297 + if ($dp.find("div#ui-timepicker-div-"+ dp_id).length === 0 && o.showTimepicker) {
  298 + var noDisplay = ' style="display:none;"',
  299 + html = '<div class="ui-timepicker-div" id="ui-timepicker-div-' + dp_id + '"><dl>' +
  300 + '<dt class="ui_tpicker_time_label" id="ui_tpicker_time_label_' + dp_id + '"' +
  301 + ((o.showTime) ? '' : noDisplay) + '>' + o.timeText + '</dt>' +
  302 + '<dd class="ui_tpicker_time" id="ui_tpicker_time_' + dp_id + '"' +
  303 + ((o.showTime) ? '' : noDisplay) + '></dd>' +
  304 + '<dt class="ui_tpicker_hour_label" id="ui_tpicker_hour_label_' + dp_id + '"' +
  305 + ((o.showHour) ? '' : noDisplay) + '>' + o.hourText + '</dt>',
  306 + hourGridSize = 0,
  307 + minuteGridSize = 0,
  308 + secondGridSize = 0,
  309 + millisecGridSize = 0,
  310 + size = null;
  311 +
  312 + // Hours
  313 + html += '<dd class="ui_tpicker_hour"><div id="ui_tpicker_hour_' + dp_id + '"' +
  314 + ((o.showHour) ? '' : noDisplay) + '></div>';
  315 + if (o.showHour && o.hourGrid > 0) {
  316 + html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>';
  317 +
  318 + for (var h = o.hourMin; h <= hourMax; h += parseInt(o.hourGrid,10)) {
  319 + hourGridSize++;
  320 + var tmph = (o.ampm && h > 12) ? h-12 : h;
  321 + if (tmph < 10) { tmph = '0' + tmph; }
  322 + if (o.ampm) {
  323 + if (h === 0) {
  324 + tmph = 12 +'a';
  325 + } else {
  326 + if (h < 12) { tmph += 'a'; }
  327 + else { tmph += 'p'; }
  328 + }
  329 + }
  330 + html += '<td>' + tmph + '</td>';
  331 + }
  332 +
  333 + html += '</tr></table></div>';
  334 + }
  335 + html += '</dd>';
  336 +
  337 + // Minutes
  338 + html += '<dt class="ui_tpicker_minute_label" id="ui_tpicker_minute_label_' + dp_id + '"' +
  339 + ((o.showMinute) ? '' : noDisplay) + '>' + o.minuteText + '</dt>'+
  340 + '<dd class="ui_tpicker_minute"><div id="ui_tpicker_minute_' + dp_id + '"' +
  341 + ((o.showMinute) ? '' : noDisplay) + '></div>';
  342 +
  343 + if (o.showMinute && o.minuteGrid > 0) {
  344 + html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>';
  345 +
  346 + for (var m = o.minuteMin; m <= minMax; m += parseInt(o.minuteGrid,10)) {
  347 + minuteGridSize++;
  348 + html += '<td>' + ((m < 10) ? '0' : '') + m + '</td>';
  349 + }
  350 +
  351 + html += '</tr></table></div>';
  352 + }
  353 + html += '</dd>';
  354 +
  355 + // Seconds
  356 + html += '<dt class="ui_tpicker_second_label" id="ui_tpicker_second_label_' + dp_id + '"' +
  357 + ((o.showSecond) ? '' : noDisplay) + '>' + o.secondText + '</dt>'+
  358 + '<dd class="ui_tpicker_second"><div id="ui_tpicker_second_' + dp_id + '"'+
  359 + ((o.showSecond) ? '' : noDisplay) + '></div>';
  360 +
  361 + if (o.showSecond && o.secondGrid > 0) {
  362 + html += '<div style="padding-left: 1px"><table><tr>';
  363 +
  364 + for (var s = o.secondMin; s <= secMax; s += parseInt(o.secondGrid,10)) {
  365 + secondGridSize++;
  366 + html += '<td>' + ((s < 10) ? '0' : '') + s + '</td>';
  367 + }
  368 +
  369 + html += '</tr></table></div>';
  370 + }
  371 + html += '</dd>';
  372 +
  373 + // Milliseconds
  374 + html += '<dt class="ui_tpicker_millisec_label" id="ui_tpicker_millisec_label_' + dp_id + '"' +
  375 + ((o.showMillisec) ? '' : noDisplay) + '>' + o.millisecText + '</dt>'+
  376 + '<dd class="ui_tpicker_millisec"><div id="ui_tpicker_millisec_' + dp_id + '"'+
  377 + ((o.showMillisec) ? '' : noDisplay) + '></div>';
  378 +
  379 + if (o.showMillisec && o.millisecGrid > 0) {
  380 + html += '<div style="padding-left: 1px"><table><tr>';
  381 +
  382 + for (var l = o.millisecMin; l <= millisecMax; l += parseInt(o.millisecGrid,10)) {
  383 + millisecGridSize++;
  384 + html += '<td>' + ((l < 10) ? '0' : '') + l + '</td>';
  385 + }
  386 +
  387 + html += '</tr></table></div>';
  388 + }
  389 + html += '</dd>';
  390 +
  391 + // Timezone
  392 + html += '<dt class="ui_tpicker_timezone_label" id="ui_tpicker_timezone_label_' + dp_id + '"' +
  393 + ((o.showTimezone) ? '' : noDisplay) + '>' + o.timezoneText + '</dt>';
  394 + html += '<dd class="ui_tpicker_timezone" id="ui_tpicker_timezone_' + dp_id + '"' +
  395 + ((o.showTimezone) ? '' : noDisplay) + '></dd>';
  396 +
  397 + html += '</dl></div>';
  398 + var $tp = $(html);
  399 +
  400 + // if we only want time picker...
  401 + if (o.timeOnly === true) {
  402 + $tp.prepend(
  403 + '<div class="ui-widget-header ui-helper-clearfix ui-corner-all">' +
  404 + '<div class="ui-datepicker-title">' + o.timeOnlyTitle + '</div>' +
  405 + '</div>');
  406 + $dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide();
  407 + }
  408 +
  409 + this.hour_slider = $tp.find('#ui_tpicker_hour_'+ dp_id).slider({
  410 + orientation: "horizontal",
  411 + value: this.hour,
  412 + min: o.hourMin,
  413 + max: hourMax,
  414 + step: o.stepHour,
  415 + slide: function(event, ui) {
  416 + tp_inst.hour_slider.slider( "option", "value", ui.value);
  417 + tp_inst._onTimeChange();
  418 + }
  419 + });
  420 +
  421 +
  422 + // Updated by Peter Medeiros:
  423 + // - Pass in Event and UI instance into slide function
  424 + this.minute_slider = $tp.find('#ui_tpicker_minute_'+ dp_id).slider({
  425 + orientation: "horizontal",
  426 + value: this.minute,
  427 + min: o.minuteMin,
  428 + max: minMax,
  429 + step: o.stepMinute,
  430 + slide: function(event, ui) {
  431 + tp_inst.minute_slider.slider( "option", "value", ui.value);
  432 + tp_inst._onTimeChange();
  433 + }
  434 + });
  435 +
  436 + this.second_slider = $tp.find('#ui_tpicker_second_'+ dp_id).slider({
  437 + orientation: "horizontal",
  438 + value: this.second,
  439 + min: o.secondMin,
  440 + max: secMax,
  441 + step: o.stepSecond,
  442 + slide: function(event, ui) {
  443 + tp_inst.second_slider.slider( "option", "value", ui.value);
  444 + tp_inst._onTimeChange();
  445 + }
  446 + });
  447 +
  448 + this.millisec_slider = $tp.find('#ui_tpicker_millisec_'+ dp_id).slider({
  449 + orientation: "horizontal",
  450 + value: this.millisec,
  451 + min: o.millisecMin,
  452 + max: millisecMax,
  453 + step: o.stepMillisec,
  454 + slide: function(event, ui) {
  455 + tp_inst.millisec_slider.slider( "option", "value", ui.value);
  456 + tp_inst._onTimeChange();
  457 + }
  458 + });
  459 +
  460 + this.timezone_select = $tp.find('#ui_tpicker_timezone_'+ dp_id).append('<select></select>').find("select");
  461 + $.fn.append.apply(this.timezone_select,
  462 + $.map(o.timezoneList, function(val, idx) {
  463 + return $("<option />")
  464 + .val(typeof val == "object" ? val.value : val)
  465 + .text(typeof val == "object" ? val.label : val);
  466 + })
  467 + );
  468 + if (typeof(this.timezone) != "undefined" && this.timezone !== null && this.timezone !== "") {
  469 + var local_date = new Date(this.inst.selectedYear, this.inst.selectedMonth, this.inst.selectedDay, 12);
  470 + var local_timezone = timeZoneString(local_date);
  471 + if (local_timezone == this.timezone) {
  472 + selectLocalTimeZone(tp_inst);
  473 + } else {
  474 + this.timezone_select.val(this.timezone);
  475 + }
  476 + } else {
  477 + if (typeof(this.hour) != "undefined" && this.hour !== null && this.hour !== "") {
  478 + this.timezone_select.val(o.defaultTimezone);
  479 + } else {
  480 + selectLocalTimeZone(tp_inst);
  481 + }
  482 + }
  483 + this.timezone_select.change(function() {
  484 + tp_inst._defaults.useLocalTimezone = false;
  485 + tp_inst._onTimeChange();
  486 + });
  487 +
  488 + // Add grid functionality
  489 + if (o.showHour && o.hourGrid > 0) {
  490 + size = 100 * hourGridSize * o.hourGrid / (hourMax - o.hourMin);
  491 +
  492 + $tp.find(".ui_tpicker_hour table").css({
  493 + width: size + "%",
  494 + marginLeft: (size / (-2 * hourGridSize)) + "%",
  495 + borderCollapse: 'collapse'
  496 + }).find("td").each( function(index) {
  497 + $(this).click(function() {
  498 + var h = $(this).html();
  499 + if(o.ampm) {
  500 + var ap = h.substring(2).toLowerCase(),
  501 + aph = parseInt(h.substring(0,2), 10);
  502 + if (ap == 'a') {
  503 + if (aph == 12) { h = 0; }
  504 + else { h = aph; }
  505 + } else if (aph == 12) { h = 12; }
  506 + else { h = aph + 12; }
  507 + }
  508 + tp_inst.hour_slider.slider("option", "value", h);
  509 + tp_inst._onTimeChange();
  510 + tp_inst._onSelectHandler();
  511 + }).css({
  512 + cursor: 'pointer',
  513 + width: (100 / hourGridSize) + '%',
  514 + textAlign: 'center',
  515 + overflow: 'hidden'
  516 + });
  517 + });
  518 + }
  519 +
  520 + if (o.showMinute && o.minuteGrid > 0) {
  521 + size = 100 * minuteGridSize * o.minuteGrid / (minMax - o.minuteMin);
  522 + $tp.find(".ui_tpicker_minute table").css({
  523 + width: size + "%",
  524 + marginLeft: (size / (-2 * minuteGridSize)) + "%",
  525 + borderCollapse: 'collapse'
  526 + }).find("td").each(function(index) {
  527 + $(this).click(function() {
  528 + tp_inst.minute_slider.slider("option", "value", $(this).html());
  529 + tp_inst._onTimeChange();
  530 + tp_inst._onSelectHandler();
  531 + }).css({
  532 + cursor: 'pointer',
  533 + width: (100 / minuteGridSize) + '%',
  534 + textAlign: 'center',
  535 + overflow: 'hidden'
  536 + });
  537 + });
  538 + }
  539 +
  540 + if (o.showSecond && o.secondGrid > 0) {
  541 + $tp.find(".ui_tpicker_second table").css({
  542 + width: size + "%",
  543 + marginLeft: (size / (-2 * secondGridSize)) + "%",
  544 + borderCollapse: 'collapse'
  545 + }).find("td").each(function(index) {
  546 + $(this).click(function() {
  547 + tp_inst.second_slider.slider("option", "value", $(this).html());
  548 + tp_inst._onTimeChange();
  549 + tp_inst._onSelectHandler();
  550 + }).css({
  551 + cursor: 'pointer',
  552 + width: (100 / secondGridSize) + '%',
  553 + textAlign: 'center',
  554 + overflow: 'hidden'
  555 + });
  556 + });
  557 + }
  558 +
  559 + if (o.showMillisec && o.millisecGrid > 0) {
  560 + $tp.find(".ui_tpicker_millisec table").css({
  561 + width: size + "%",
  562 + marginLeft: (size / (-2 * millisecGridSize)) + "%",
  563 + borderCollapse: 'collapse'
  564 + }).find("td").each(function(index) {
  565 + $(this).click(function() {
  566 + tp_inst.millisec_slider.slider("option", "value", $(this).html());
  567 + tp_inst._onTimeChange();
  568 + tp_inst._onSelectHandler();
  569 + }).css({
  570 + cursor: 'pointer',
  571 + width: (100 / millisecGridSize) + '%',
  572 + textAlign: 'center',
  573 + overflow: 'hidden'
  574 + });
  575 + });
  576 + }
  577 +
  578 + var $buttonPanel = $dp.find('.ui-datepicker-buttonpane');
  579 + if ($buttonPanel.length) { $buttonPanel.before($tp); }
  580 + else { $dp.append($tp); }
  581 +
  582 + this.$timeObj = $tp.find('#ui_tpicker_time_'+ dp_id);
  583 +
  584 + if (this.inst !== null) {
  585 + var timeDefined = this.timeDefined;
  586 + this._onTimeChange();
  587 + this.timeDefined = timeDefined;
  588 + }
  589 +
  590 + //Emulate datepicker onSelect behavior. Call on slidestop.
  591 + var onSelectDelegate = function() {
  592 + tp_inst._onSelectHandler();
  593 + };
  594 + this.hour_slider.bind('slidestop',onSelectDelegate);
  595 + this.minute_slider.bind('slidestop',onSelectDelegate);
  596 + this.second_slider.bind('slidestop',onSelectDelegate);
  597 + this.millisec_slider.bind('slidestop',onSelectDelegate);
  598 +
  599 + // slideAccess integration: http://trentrichardson.com/2011/11/11/jquery-ui-sliders-and-touch-accessibility/
  600 + if (this._defaults.addSliderAccess){
  601 + var sliderAccessArgs = this._defaults.sliderAccessArgs;
  602 + setTimeout(function(){ // fix for inline mode
  603 + if($tp.find('.ui-slider-access').length === 0){
  604 + $tp.find('.ui-slider:visible').sliderAccess(sliderAccessArgs);
  605 +
  606 + // fix any grids since sliders are shorter
  607 + var sliderAccessWidth = $tp.find('.ui-slider-access:eq(0)').outerWidth(true);
  608 + if(sliderAccessWidth){
  609 + $tp.find('table:visible').each(function(){
  610 + var $g = $(this),
  611 + oldWidth = $g.outerWidth(),
  612 + oldMarginLeft = $g.css('marginLeft').toString().replace('%',''),
  613 + newWidth = oldWidth - sliderAccessWidth,
  614 + newMarginLeft = ((oldMarginLeft * newWidth)/oldWidth) + '%';
  615 +
  616 + $g.css({ width: newWidth, marginLeft: newMarginLeft });
  617 + });
  618 + }
  619 + }
  620 + },0);
  621 + }
  622 + // end slideAccess integration
  623 +
  624 + }
  625 + },
  626 +
  627 + //########################################################################
  628 + // This function tries to limit the ability to go outside the
  629 + // min/max date range
  630 + //########################################################################
  631 + _limitMinMaxDateTime: function(dp_inst, adjustSliders){
  632 + var o = this._defaults,
  633 + dp_date = new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay);
  634 +
  635 + if(!this._defaults.showTimepicker) { return; } // No time so nothing to check here
  636 +
  637 + if($.datepicker._get(dp_inst, 'minDateTime') !== null && $.datepicker._get(dp_inst, 'minDateTime') !== undefined && dp_date){
  638 + var minDateTime = $.datepicker._get(dp_inst, 'minDateTime'),
  639 + minDateTimeDate = new Date(minDateTime.getFullYear(), minDateTime.getMonth(), minDateTime.getDate(), 0, 0, 0, 0);
  640 +
  641 + if(this.hourMinOriginal === null || this.minuteMinOriginal === null || this.secondMinOriginal === null || this.millisecMinOriginal === null){
  642 + this.hourMinOriginal = o.hourMin;
  643 + this.minuteMinOriginal = o.minuteMin;
  644 + this.secondMinOriginal = o.secondMin;
  645 + this.millisecMinOriginal = o.millisecMin;
  646 + }
  647 +
  648 + if(dp_inst.settings.timeOnly || minDateTimeDate.getTime() == dp_date.getTime()) {
  649 + this._defaults.hourMin = minDateTime.getHours();
  650 + if (this.hour <= this._defaults.hourMin) {
  651 + this.hour = this._defaults.hourMin;
  652 + this._defaults.minuteMin = minDateTime.getMinutes();
  653 + if (this.minute <= this._defaults.minuteMin) {
  654 + this.minute = this._defaults.minuteMin;
  655 + this._defaults.secondMin = minDateTime.getSeconds();
  656 + } else if (this.second <= this._defaults.secondMin){
  657 + this.second = this._defaults.secondMin;
  658 + this._defaults.millisecMin = minDateTime.getMilliseconds();
  659 + } else {
  660 + if(this.millisec < this._defaults.millisecMin) {
  661 + this.millisec = this._defaults.millisecMin;
  662 + }
  663 + this._defaults.millisecMin = this.millisecMinOriginal;
  664 + }
  665 + } else {
  666 + this._defaults.minuteMin = this.minuteMinOriginal;
  667 + this._defaults.secondMin = this.secondMinOriginal;
  668 + this._defaults.millisecMin = this.millisecMinOriginal;
  669 + }
  670 + }else{
  671 + this._defaults.hourMin = this.hourMinOriginal;
  672 + this._defaults.minuteMin = this.minuteMinOriginal;
  673 + this._defaults.secondMin = this.secondMinOriginal;
  674 + this._defaults.millisecMin = this.millisecMinOriginal;
  675 + }
  676 + }
  677 +
  678 + if($.datepicker._get(dp_inst, 'maxDateTime') !== null && $.datepicker._get(dp_inst, 'maxDateTime') !== undefined && dp_date){
  679 + var maxDateTime = $.datepicker._get(dp_inst, 'maxDateTime'),
  680 + maxDateTimeDate = new Date(maxDateTime.getFullYear(), maxDateTime.getMonth(), maxDateTime.getDate(), 0, 0, 0, 0);
  681 +
  682 + if(this.hourMaxOriginal === null || this.minuteMaxOriginal === null || this.secondMaxOriginal === null){
  683 + this.hourMaxOriginal = o.hourMax;
  684 + this.minuteMaxOriginal = o.minuteMax;
  685 + this.secondMaxOriginal = o.secondMax;
  686 + this.millisecMaxOriginal = o.millisecMax;
  687 + }
  688 +
  689 + if(dp_inst.settings.timeOnly || maxDateTimeDate.getTime() == dp_date.getTime()){
  690 + this._defaults.hourMax = maxDateTime.getHours();
  691 + if (this.hour >= this._defaults.hourMax) {
  692 + this.hour = this._defaults.hourMax;
  693 + this._defaults.minuteMax = maxDateTime.getMinutes();
  694 + if (this.minute >= this._defaults.minuteMax) {
  695 + this.minute = this._defaults.minuteMax;
  696 + this._defaults.secondMax = maxDateTime.getSeconds();
  697 + } else if (this.second >= this._defaults.secondMax) {
  698 + this.second = this._defaults.secondMax;
  699 + this._defaults.millisecMax = maxDateTime.getMilliseconds();
  700 + } else {
  701 + if(this.millisec > this._defaults.millisecMax) { this.millisec = this._defaults.millisecMax; }
  702 + this._defaults.millisecMax = this.millisecMaxOriginal;
  703 + }
  704 + } else {
  705 + this._defaults.minuteMax = this.minuteMaxOriginal;
  706 + this._defaults.secondMax = this.secondMaxOriginal;
  707 + this._defaults.millisecMax = this.millisecMaxOriginal;
  708 + }
  709 + }else{
  710 + this._defaults.hourMax = this.hourMaxOriginal;
  711 + this._defaults.minuteMax = this.minuteMaxOriginal;
  712 + this._defaults.secondMax = this.secondMaxOriginal;
  713 + this._defaults.millisecMax = this.millisecMaxOriginal;
  714 + }
  715 + }
  716 +
  717 + if(adjustSliders !== undefined && adjustSliders === true){
  718 + var hourMax = parseInt((this._defaults.hourMax - ((this._defaults.hourMax - this._defaults.hourMin) % this._defaults.stepHour)) ,10),
  719 + minMax = parseInt((this._defaults.minuteMax - ((this._defaults.minuteMax - this._defaults.minuteMin) % this._defaults.stepMinute)) ,10),
  720 + secMax = parseInt((this._defaults.secondMax - ((this._defaults.secondMax - this._defaults.secondMin) % this._defaults.stepSecond)) ,10),
  721 + millisecMax = parseInt((this._defaults.millisecMax - ((this._defaults.millisecMax - this._defaults.millisecMin) % this._defaults.stepMillisec)) ,10);
  722 +
  723 + if(this.hour_slider) {
  724 + this.hour_slider.slider("option", { min: this._defaults.hourMin, max: hourMax }).slider('value', this.hour);
  725 + }
  726 + if(this.minute_slider) {
  727 + this.minute_slider.slider("option", { min: this._defaults.minuteMin, max: minMax }).slider('value', this.minute);
  728 + }
  729 + if(this.second_slider){
  730 + this.second_slider.slider("option", { min: this._defaults.secondMin, max: secMax }).slider('value', this.second);
  731 + }
  732 + if(this.millisec_slider) {
  733 + this.millisec_slider.slider("option", { min: this._defaults.millisecMin, max: millisecMax }).slider('value', this.millisec);
  734 + }
  735 + }
  736 +
  737 + },
  738 +
  739 +
  740 + //########################################################################
  741 + // when a slider moves, set the internal time...
  742 + // on time change is also called when the time is updated in the text field
  743 + //########################################################################
  744 + _onTimeChange: function() {
  745 + var hour = (this.hour_slider) ? this.hour_slider.slider('value') : false,
  746 + minute = (this.minute_slider) ? this.minute_slider.slider('value') : false,
  747 + second = (this.second_slider) ? this.second_slider.slider('value') : false,
  748 + millisec = (this.millisec_slider) ? this.millisec_slider.slider('value') : false,
  749 + timezone = (this.timezone_select) ? this.timezone_select.val() : false,
  750 + o = this._defaults;
  751 +
  752 + if (typeof(hour) == 'object') { hour = false; }
  753 + if (typeof(minute) == 'object') { minute = false; }
  754 + if (typeof(second) == 'object') { second = false; }
  755 + if (typeof(millisec) == 'object') { millisec = false; }
  756 + if (typeof(timezone) == 'object') { timezone = false; }
  757 +
  758 + if (hour !== false) { hour = parseInt(hour,10); }
  759 + if (minute !== false) { minute = parseInt(minute,10); }
  760 + if (second !== false) { second = parseInt(second,10); }
  761 + if (millisec !== false) { millisec = parseInt(millisec,10); }
  762 +
  763 + var ampm = o[hour < 12 ? 'amNames' : 'pmNames'][0];
  764 +
  765 + // If the update was done in the input field, the input field should not be updated.
  766 + // If the update was done using the sliders, update the input field.
  767 + var hasChanged = (hour != this.hour || minute != this.minute ||
  768 + second != this.second || millisec != this.millisec ||
  769 + (this.ampm.length > 0 &&
  770 + (hour < 12) != ($.inArray(this.ampm.toUpperCase(), this.amNames) !== -1)) ||
  771 + timezone != this.timezone);
  772 +
  773 + if (hasChanged) {
  774 +
  775 + if (hour !== false) { this.hour = hour; }
  776 + if (minute !== false) { this.minute = minute; }
  777 + if (second !== false) { this.second = second; }
  778 + if (millisec !== false) { this.millisec = millisec; }
  779 + if (timezone !== false) { this.timezone = timezone; }
  780 +
  781 + if (!this.inst) { this.inst = $.datepicker._getInst(this.$input[0]); }
  782 +
  783 + this._limitMinMaxDateTime(this.inst, true);
  784 + }
  785 + if (o.ampm) { this.ampm = ampm; }
  786 +
  787 + //this._formatTime();
  788 + this.formattedTime = $.datepicker.formatTime(this._defaults.timeFormat, this, this._defaults);
  789 + if (this.$timeObj) { this.$timeObj.text(this.formattedTime + o.timeSuffix); }
  790 + this.timeDefined = true;
  791 + if (hasChanged) { this._updateDateTime(); }
  792 + },
  793 +
  794 + //########################################################################
  795 + // call custom onSelect.
  796 + // bind to sliders slidestop, and grid click.
  797 + //########################################################################
  798 + _onSelectHandler: function() {
  799 + var onSelect = this._defaults.onSelect;
  800 + var inputEl = this.$input ? this.$input[0] : null;
  801 + if (onSelect && inputEl) {
  802 + onSelect.apply(inputEl, [this.formattedDateTime, this]);
  803 + }
  804 + },
  805 +
  806 + //########################################################################
  807 + // left for any backwards compatibility
  808 + //########################################################################
  809 + _formatTime: function(time, format) {
  810 + time = time || { hour: this.hour, minute: this.minute, second: this.second, millisec: this.millisec, ampm: this.ampm, timezone: this.timezone };
  811 + var tmptime = (format || this._defaults.timeFormat).toString();
  812 +
  813 + tmptime = $.datepicker.formatTime(tmptime, time, this._defaults);
  814 +
  815 + if (arguments.length) { return tmptime; }
  816 + else { this.formattedTime = tmptime; }
  817 + },
  818 +
  819 + //########################################################################
  820 + // update our input with the new date time..
  821 + //########################################################################
  822 + _updateDateTime: function(dp_inst) {
  823 + dp_inst = this.inst || dp_inst;
  824 + var dt = $.datepicker._daylightSavingAdjust(new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay)),
  825 + dateFmt = $.datepicker._get(dp_inst, 'dateFormat'),
  826 + formatCfg = $.datepicker._getFormatConfig(dp_inst),
  827 + timeAvailable = dt !== null && this.timeDefined;
  828 + this.formattedDate = $.datepicker.formatDate(dateFmt, (dt === null ? new Date() : dt), formatCfg);
  829 + var formattedDateTime = this.formattedDate;
  830 + // remove following lines to force every changes in date picker to change the input value
  831 + // Bug descriptions: when an input field has a default value, and click on the field to pop up the date picker.
  832 + // If the user manually empty the value in the input field, the date picker will never change selected value.
  833 + //if (dp_inst.lastVal !== undefined && (dp_inst.lastVal.length > 0 && this.$input.val().length === 0)) {
  834 + // return;
  835 + //}
  836 +
  837 + if (this._defaults.timeOnly === true) {
  838 + formattedDateTime = this.formattedTime;
  839 + } else if (this._defaults.timeOnly !== true && (this._defaults.alwaysSetTime || timeAvailable)) {
  840 + formattedDateTime += this._defaults.separator + this.formattedTime + this._defaults.timeSuffix;
  841 + }
  842 +
  843 + this.formattedDateTime = formattedDateTime;
  844 +
  845 + if(!this._defaults.showTimepicker) {
  846 + this.$input.val(this.formattedDate);
  847 + } else if (this.$altInput && this._defaults.altFieldTimeOnly === true) {
  848 + this.$altInput.val(this.formattedTime);
  849 + this.$input.val(this.formattedDate);
  850 + } else if(this.$altInput) {
  851 + this.$altInput.val(formattedDateTime);
  852 + this.$input.val(formattedDateTime);
  853 + } else {
  854 + this.$input.val(formattedDateTime);
  855 + }
  856 +
  857 + this.$input.trigger("change");
  858 + }
  859 +
  860 +});
  861 +
  862 +$.fn.extend({
  863 + //########################################################################
  864 + // shorthand just to use timepicker..
  865 + //########################################################################
  866 + timepicker: function(o) {
  867 + o = o || {};
  868 + var tmp_args = arguments;
  869 +
  870 + if (typeof o == 'object') { tmp_args[0] = $.extend(o, { timeOnly: true }); }
  871 +
  872 + return $(this).each(function() {
  873 + $.fn.datetimepicker.apply($(this), tmp_args);
  874 + });
  875 + },
  876 +
  877 + //########################################################################
  878 + // extend timepicker to datepicker
  879 + //########################################################################
  880 + datetimepicker: function(o) {
  881 + o = o || {};
  882 + var tmp_args = arguments;
  883 +
  884 + if (typeof(o) == 'string'){
  885 + if(o == 'getDate') {
  886 + return $.fn.datepicker.apply($(this[0]), tmp_args);
  887 + }
  888 + else {
  889 + return this.each(function() {
  890 + var $t = $(this);
  891 + $t.datepicker.apply($t, tmp_args);
  892 + });
  893 + }
  894 + }
  895 + else {
  896 + return this.each(function() {
  897 + var $t = $(this);
  898 + $t.datepicker($.timepicker._newInst($t, o)._defaults);
  899 + });
  900 + }
  901 + }
  902 +});
  903 +
  904 +$.datepicker.parseDateTime = function(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings) {
  905 + var parseRes = parseDateTimeInternal(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings);
  906 + if (parseRes.timeObj)
  907 + {
  908 + var t = parseRes.timeObj;
  909 + parseRes.date.setHours(t.hour, t.minute, t.second, t.millisec);
  910 + }
  911 +
  912 + return parseRes.date;
  913 +};
  914 +
  915 +$.datepicker.parseTime = function(timeFormat, timeString, options) {
  916 +
  917 + //########################################################################
  918 + // pattern for standard and localized AM/PM markers
  919 + //########################################################################
  920 + var getPatternAmpm = function(amNames, pmNames) {
  921 + var markers = [];
  922 + if (amNames) {
  923 + $.merge(markers, amNames);
  924 + }
  925 + if (pmNames) {
  926 + $.merge(markers, pmNames);
  927 + }
  928 + markers = $.map(markers, function(val) { return val.replace(/[.*+?|()\[\]{}\\]/g, '\\$&'); });
  929 + return '(' + markers.join('|') + ')?';
  930 + };
  931 +
  932 + //########################################################################
  933 + // figure out position of time elements.. cause js cant do named captures
  934 + //########################################################################
  935 + var getFormatPositions = function( timeFormat ) {
  936 + var finds = timeFormat.toLowerCase().match(/(h{1,2}|m{1,2}|s{1,2}|l{1}|t{1,2}|z)/g),
  937 + orders = { h: -1, m: -1, s: -1, l: -1, t: -1, z: -1 };
  938 +
  939 + if (finds) {
  940 + for (var i = 0; i < finds.length; i++) {
  941 + if (orders[finds[i].toString().charAt(0)] == -1) {
  942 + orders[finds[i].toString().charAt(0)] = i + 1;
  943 + }
  944 + }
  945 + }
  946 + return orders;
  947 + };
  948 +
  949 + var o = extendRemove(extendRemove({}, $.timepicker._defaults), options || {});
  950 +
  951 + var regstr = '^' + timeFormat.toString()
  952 + .replace(/h{1,2}/ig, '(\\d?\\d)')
  953 + .replace(/m{1,2}/ig, '(\\d?\\d)')
  954 + .replace(/s{1,2}/ig, '(\\d?\\d)')
  955 + .replace(/l{1}/ig, '(\\d?\\d?\\d)')
  956 + .replace(/t{1,2}/ig, getPatternAmpm(o.amNames, o.pmNames))
  957 + .replace(/z{1}/ig, '(z|[-+]\\d\\d:?\\d\\d)?')
  958 + .replace(/\s/g, '\\s?') + o.timeSuffix + '$',
  959 + order = getFormatPositions(timeFormat),
  960 + ampm = '',
  961 + treg;
  962 +
  963 + treg = timeString.match(new RegExp(regstr, 'i'));
  964 +
  965 + var resTime = {hour: 0, minute: 0, second: 0, millisec: 0};
  966 +
  967 + if (treg) {
  968 + if (order.t !== -1) {
  969 + if (treg[order.t] === undefined || treg[order.t].length === 0) {
  970 + ampm = '';
  971 + resTime.ampm = '';
  972 + } else {
  973 + ampm = $.inArray(treg[order.t], o.amNames) !== -1 ? 'AM' : 'PM';
  974 + resTime.ampm = o[ampm == 'AM' ? 'amNames' : 'pmNames'][0];
  975 + }
  976 + }
  977 +
  978 + if (order.h !== -1) {
  979 + if (ampm == 'AM' && treg[order.h] == '12') {
  980 + resTime.hour = 0; // 12am = 0 hour
  981 + } else {
  982 + if (ampm == 'PM' && treg[order.h] != '12') {
  983 + resTime.hour = parseInt(treg[order.h],10) + 12; // 12pm = 12 hour, any other pm = hour + 12
  984 + }
  985 + else { resTime.hour = Number(treg[order.h]); }
  986 + }
  987 + }
  988 +
  989 + if (order.m !== -1) { resTime.minute = Number(treg[order.m]); }
  990 + if (order.s !== -1) { resTime.second = Number(treg[order.s]); }
  991 + if (order.l !== -1) { resTime.millisec = Number(treg[order.l]); }
  992 + if (order.z !== -1 && treg[order.z] !== undefined) {
  993 + var tz = treg[order.z].toUpperCase();
  994 + switch (tz.length) {
  995 + case 1: // Z
  996 + tz = o.timezoneIso8601 ? 'Z' : '+0000';
  997 + break;
  998 + case 5: // +hhmm
  999 + if (o.timezoneIso8601) {
  1000 + tz = tz.substring(1) == '0000' ?
  1001 + 'Z' :
  1002 + tz.substring(0, 3) + ':' + tz.substring(3);
  1003 + }
  1004 + break;
  1005 + case 6: // +hh:mm
  1006 + if (!o.timezoneIso8601) {
  1007 + tz = tz == 'Z' || tz.substring(1) == '00:00' ?
  1008 + '+0000' :
  1009 + tz.replace(/:/, '');
  1010 + } else {
  1011 + if (tz.substring(1) == '00:00') {
  1012 + tz = 'Z';
  1013 + }
  1014 + }
  1015 + break;
  1016 + }
  1017 + resTime.timezone = tz;
  1018 + }
  1019 +
  1020 +
  1021 + return resTime;
  1022 + }
  1023 +
  1024 + return false;
  1025 +};
  1026 +
  1027 +//########################################################################
  1028 +// format the time all pretty...
  1029 +// format = string format of the time
  1030 +// time = a {}, not a Date() for timezones
  1031 +// options = essentially the regional[].. amNames, pmNames, ampm
  1032 +//########################################################################
  1033 +$.datepicker.formatTime = function(format, time, options) {
  1034 + options = options || {};
  1035 + options = $.extend($.timepicker._defaults, options);
  1036 + time = $.extend({hour:0, minute:0, second:0, millisec:0, timezone:'+0000'}, time);
  1037 +
  1038 + var tmptime = format;
  1039 + var ampmName = options.amNames[0];
  1040 +
  1041 + var hour = parseInt(time.hour, 10);
  1042 + if (options.ampm) {
  1043 + if (hour > 11){
  1044 + ampmName = options.pmNames[0];
  1045 + if(hour > 12) {
  1046 + hour = hour % 12;
  1047 + }
  1048 + }
  1049 + if (hour === 0) {
  1050 + hour = 12;
  1051 + }
  1052 + }
  1053 + tmptime = tmptime.replace(/(?:hh?|mm?|ss?|[tT]{1,2}|[lz])/g, function(match) {
  1054 + switch (match.toLowerCase()) {
  1055 + case 'hh': return ('0' + hour).slice(-2);
  1056 + case 'h': return hour;
  1057 + case 'mm': return ('0' + time.minute).slice(-2);
  1058 + case 'm': return time.minute;
  1059 + case 'ss': return ('0' + time.second).slice(-2);
  1060 + case 's': return time.second;
  1061 + case 'l': return ('00' + time.millisec).slice(-3);
  1062 + case 'z': return time.timezone;
  1063 + case 't': case 'tt':
  1064 + if (options.ampm) {
  1065 + if (match.length == 1) {
  1066 + ampmName = ampmName.charAt(0);
  1067 + }
  1068 + return match.charAt(0) == 'T' ? ampmName.toUpperCase() : ampmName.toLowerCase();
  1069 + }
  1070 + return '';
  1071 + }
  1072 + });
  1073 +
  1074 + tmptime = $.trim(tmptime);
  1075 + return tmptime;
  1076 +};
  1077 +
  1078 +//########################################################################
  1079 +// the bad hack :/ override datepicker so it doesnt close on select
  1080 +// inspired: http://stackoverflow.com/questions/1252512/jquery-datepicker-prevent-closing-picker-when-clicking-a-date/1762378#1762378
  1081 +//########################################################################
  1082 +$.datepicker._base_selectDate = $.datepicker._selectDate;
  1083 +$.datepicker._selectDate = function (id, dateStr) {
  1084 + var inst = this._getInst($(id)[0]),
  1085 + tp_inst = this._get(inst, 'timepicker');
  1086 +
  1087 + if (tp_inst) {
  1088 + tp_inst._limitMinMaxDateTime(inst, true);
  1089 + inst.inline = inst.stay_open = true;
  1090 + //This way the onSelect handler called from calendarpicker get the full dateTime
  1091 + this._base_selectDate(id, dateStr);
  1092 + inst.inline = inst.stay_open = false;
  1093 + this._notifyChange(inst);
  1094 + this._updateDatepicker(inst);
  1095 + }
  1096 + else { this._base_selectDate(id, dateStr); }
  1097 +};
  1098 +
  1099 +//#############################################################################################
  1100 +// second bad hack :/ override datepicker so it triggers an event when changing the input field
  1101 +// and does not redraw the datepicker on every selectDate event
  1102 +//#############################################################################################
  1103 +$.datepicker._base_updateDatepicker = $.datepicker._updateDatepicker;
  1104 +$.datepicker._updateDatepicker = function(inst) {
  1105 +
  1106 + // don't popup the datepicker if there is another instance already opened
  1107 + var input = inst.input[0];
  1108 + if($.datepicker._curInst &&
  1109 + $.datepicker._curInst != inst &&
  1110 + $.datepicker._datepickerShowing &&
  1111 + $.datepicker._lastInput != input) {
  1112 + return;
  1113 + }
  1114 +
  1115 + if (typeof(inst.stay_open) !== 'boolean' || inst.stay_open === false) {
  1116 +
  1117 + this._base_updateDatepicker(inst);
  1118 +
  1119 + // Reload the time control when changing something in the input text field.
  1120 + var tp_inst = this._get(inst, 'timepicker');
  1121 + if(tp_inst) {
  1122 + tp_inst._addTimePicker(inst);
  1123 +
  1124 + if (tp_inst._defaults.useLocalTimezone) { //checks daylight saving with the new date.
  1125 + var date = new Date(inst.selectedYear, inst.selectedMonth, inst.selectedDay, 12);
  1126 + selectLocalTimeZone(tp_inst, date);
  1127 + tp_inst._onTimeChange();
  1128 + }
  1129 + }
  1130 + }
  1131 +};
  1132 +
  1133 +//#######################################################################################
  1134 +// third bad hack :/ override datepicker so it allows spaces and colon in the input field
  1135 +//#######################################################################################
  1136 +$.datepicker._base_doKeyPress = $.datepicker._doKeyPress;
  1137 +$.datepicker._doKeyPress = function(event) {
  1138 + var inst = $.datepicker._getInst(event.target),
  1139 + tp_inst = $.datepicker._get(inst, 'timepicker');
  1140 +
  1141 + if (tp_inst) {
  1142 + if ($.datepicker._get(inst, 'constrainInput')) {
  1143 + var ampm = tp_inst._defaults.ampm,
  1144 + dateChars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat')),
  1145 + datetimeChars = tp_inst._defaults.timeFormat.toString()
  1146 + .replace(/[hms]/g, '')
  1147 + .replace(/TT/g, ampm ? 'APM' : '')
  1148 + .replace(/Tt/g, ampm ? 'AaPpMm' : '')
  1149 + .replace(/tT/g, ampm ? 'AaPpMm' : '')
  1150 + .replace(/T/g, ampm ? 'AP' : '')
  1151 + .replace(/tt/g, ampm ? 'apm' : '')
  1152 + .replace(/t/g, ampm ? 'ap' : '') +
  1153 + " " +
  1154 + tp_inst._defaults.separator +
  1155 + tp_inst._defaults.timeSuffix +
  1156 + (tp_inst._defaults.showTimezone ? tp_inst._defaults.timezoneList.join('') : '') +
  1157 + (tp_inst._defaults.amNames.join('')) +
  1158 + (tp_inst._defaults.pmNames.join('')) +
  1159 + dateChars,
  1160 + chr = String.fromCharCode(event.charCode === undefined ? event.keyCode : event.charCode);
  1161 + return event.ctrlKey || (chr < ' ' || !dateChars || datetimeChars.indexOf(chr) > -1);
  1162 + }
  1163 + }
  1164 +
  1165 + return $.datepicker._base_doKeyPress(event);
  1166 +};
  1167 +
  1168 +//#######################################################################################
  1169 +// Override key up event to sync manual input changes.
  1170 +//#######################################################################################
  1171 +$.datepicker._base_doKeyUp = $.datepicker._doKeyUp;
  1172 +$.datepicker._doKeyUp = function (event) {
  1173 + var inst = $.datepicker._getInst(event.target),
  1174 + tp_inst = $.datepicker._get(inst, 'timepicker');
  1175 +
  1176 + if (tp_inst) {
  1177 + if (tp_inst._defaults.timeOnly && (inst.input.val() != inst.lastVal)) {
  1178 + try {
  1179 + $.datepicker._updateDatepicker(inst);
  1180 + }
  1181 + catch (err) {
  1182 + $.datepicker.log(err);
  1183 + }
  1184 + }
  1185 + }
  1186 +
  1187 + return $.datepicker._base_doKeyUp(event);
  1188 +};
  1189 +
  1190 +//#######################################################################################
  1191 +// override "Today" button to also grab the time.
  1192 +//#######################################################################################
  1193 +$.datepicker._base_gotoToday = $.datepicker._gotoToday;
  1194 +$.datepicker._gotoToday = function(id) {
  1195 + var inst = this._getInst($(id)[0]),
  1196 + $dp = inst.dpDiv;
  1197 + this._base_gotoToday(id);
  1198 + var tp_inst = this._get(inst, 'timepicker');
  1199 + selectLocalTimeZone(tp_inst);
  1200 + var now = new Date();
  1201 + this._setTime(inst, now);
  1202 + $( '.ui-datepicker-today', $dp).click();
  1203 +};
  1204 +
  1205 +//#######################################################################################
  1206 +// Disable & enable the Time in the datetimepicker
  1207 +//#######################################################################################
  1208 +$.datepicker._disableTimepickerDatepicker = function(target) {
  1209 + var inst = this._getInst(target);
  1210 + if (!inst) { return; }
  1211 +
  1212 + var tp_inst = this._get(inst, 'timepicker');
  1213 + $(target).datepicker('getDate'); // Init selected[Year|Month|Day]
  1214 + if (tp_inst) {
  1215 + tp_inst._defaults.showTimepicker = false;
  1216 + tp_inst._updateDateTime(inst);
  1217 + }
  1218 +};
  1219 +
  1220 +$.datepicker._enableTimepickerDatepicker = function(target) {
  1221 + var inst = this._getInst(target);
  1222 + if (!inst) { return; }
  1223 +
  1224 + var tp_inst = this._get(inst, 'timepicker');
  1225 + $(target).datepicker('getDate'); // Init selected[Year|Month|Day]
  1226 + if (tp_inst) {
  1227 + tp_inst._defaults.showTimepicker = true;
  1228 + tp_inst._addTimePicker(inst); // Could be disabled on page load
  1229 + tp_inst._updateDateTime(inst);
  1230 + }
  1231 +};
  1232 +
  1233 +//#######################################################################################
  1234 +// Create our own set time function
  1235 +//#######################################################################################
  1236 +$.datepicker._setTime = function(inst, date) {
  1237 + var tp_inst = this._get(inst, 'timepicker');
  1238 + if (tp_inst) {
  1239 + var defaults = tp_inst._defaults,
  1240 + // calling _setTime with no date sets time to defaults
  1241 + hour = date ? date.getHours() : defaults.hour,
  1242 + minute = date ? date.getMinutes() : defaults.minute,
  1243 + second = date ? date.getSeconds() : defaults.second,
  1244 + millisec = date ? date.getMilliseconds() : defaults.millisec;
  1245 + //check if within min/max times..
  1246 + // correct check if within min/max times.
  1247 + // Rewritten by Scott A. Woodward
  1248 + var hourEq = hour === defaults.hourMin,
  1249 + minuteEq = minute === defaults.minuteMin,
  1250 + secondEq = second === defaults.secondMin;
  1251 + var reset = false;
  1252 + if(hour < defaults.hourMin || hour > defaults.hourMax)
  1253 + reset = true;
  1254 + else if( (minute < defaults.minuteMin || minute > defaults.minuteMax) && hourEq)
  1255 + reset = true;
  1256 + else if( (second < defaults.secondMin || second > defaults.secondMax ) && hourEq && minuteEq)
  1257 + reset = true;
  1258 + else if( (millisec < defaults.millisecMin || millisec > defaults.millisecMax) && hourEq && minuteEq && secondEq)
  1259 + reset = true;
  1260 + if(reset) {
  1261 + hour = defaults.hourMin;
  1262 + minute = defaults.minuteMin;
  1263 + second = defaults.secondMin;
  1264 + millisec = defaults.millisecMin;
  1265 + }
  1266 + tp_inst.hour = hour;
  1267 + tp_inst.minute = minute;
  1268 + tp_inst.second = second;
  1269 + tp_inst.millisec = millisec;
  1270 + if (tp_inst.hour_slider) tp_inst.hour_slider.slider('value', hour);
  1271 + if (tp_inst.minute_slider) tp_inst.minute_slider.slider('value', minute);
  1272 + if (tp_inst.second_slider) tp_inst.second_slider.slider('value', second);
  1273 + if (tp_inst.millisec_slider) tp_inst.millisec_slider.slider('value', millisec);
  1274 +
  1275 + tp_inst._onTimeChange();
  1276 + tp_inst._updateDateTime(inst);
  1277 + }
  1278 +};
  1279 +
  1280 +//#######################################################################################
  1281 +// Create new public method to set only time, callable as $().datepicker('setTime', date)
  1282 +//#######################################################################################
  1283 +$.datepicker._setTimeDatepicker = function(target, date, withDate) {
  1284 + var inst = this._getInst(target);
  1285 + if (!inst) { return; }
  1286 +
  1287 + var tp_inst = this._get(inst, 'timepicker');
  1288 +
  1289 + if (tp_inst) {
  1290 + this._setDateFromField(inst);
  1291 + var tp_date;
  1292 + if (date) {
  1293 + if (typeof date == "string") {
  1294 + tp_inst._parseTime(date, withDate);
  1295 + tp_date = new Date();
  1296 + tp_date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec);
  1297 + }
  1298 + else { tp_date = new Date(date.getTime()); }
  1299 + if (tp_date.toString() == 'Invalid Date') { tp_date = undefined; }
  1300 + this._setTime(inst, tp_date);
  1301 + }
  1302 + }
  1303 +
  1304 +};
  1305 +
  1306 +//#######################################################################################
  1307 +// override setDate() to allow setting time too within Date object
  1308 +//#######################################################################################
  1309 +$.datepicker._base_setDateDatepicker = $.datepicker._setDateDatepicker;
  1310 +$.datepicker._setDateDatepicker = function(target, date) {
  1311 + var inst = this._getInst(target);
  1312 + if (!inst) { return; }
  1313 +
  1314 + var tp_date = (date instanceof Date) ? new Date(date.getTime()) : date;
  1315 +
  1316 + this._updateDatepicker(inst);
  1317 + this._base_setDateDatepicker.apply(this, arguments);
  1318 + this._setTimeDatepicker(target, tp_date, true);
  1319 +};
  1320 +
  1321 +//#######################################################################################
  1322 +// override getDate() to allow getting time too within Date object
  1323 +//#######################################################################################
  1324 +$.datepicker._base_getDateDatepicker = $.datepicker._getDateDatepicker;
  1325 +$.datepicker._getDateDatepicker = function(target, noDefault) {
  1326 + var inst = this._getInst(target);
  1327 + if (!inst) { return; }
  1328 +
  1329 + var tp_inst = this._get(inst, 'timepicker');
  1330 +
  1331 + if (tp_inst) {
  1332 + this._setDateFromField(inst, noDefault);
  1333 + var date = this._getDate(inst);
  1334 + if (date && tp_inst._parseTime($(target).val(), tp_inst.timeOnly)) { date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec); }
  1335 + return date;
  1336 + }
  1337 + return this._base_getDateDatepicker(target, noDefault);
  1338 +};
  1339 +
  1340 +//#######################################################################################
  1341 +// override parseDate() because UI 1.8.14 throws an error about "Extra characters"
  1342 +// An option in datapicker to ignore extra format characters would be nicer.
  1343 +//#######################################################################################
  1344 +$.datepicker._base_parseDate = $.datepicker.parseDate;
  1345 +$.datepicker.parseDate = function(format, value, settings) {
  1346 + var splitRes = splitDateTime(format, value, settings);
  1347 + return $.datepicker._base_parseDate(format, splitRes[0], settings);
  1348 +};
  1349 +
  1350 +//#######################################################################################
  1351 +// override formatDate to set date with time to the input
  1352 +//#######################################################################################
  1353 +$.datepicker._base_formatDate = $.datepicker._formatDate;
  1354 +$.datepicker._formatDate = function(inst, day, month, year){
  1355 + var tp_inst = this._get(inst, 'timepicker');
  1356 + if(tp_inst) {
  1357 + tp_inst._updateDateTime(inst);
  1358 + return tp_inst.$input.val();
  1359 + }
  1360 + return this._base_formatDate(inst);
  1361 +};
  1362 +
  1363 +//#######################################################################################
  1364 +// override options setter to add time to maxDate(Time) and minDate(Time). MaxDate
  1365 +//#######################################################################################
  1366 +$.datepicker._base_optionDatepicker = $.datepicker._optionDatepicker;
  1367 +$.datepicker._optionDatepicker = function(target, name, value) {
  1368 + var inst = this._getInst(target);
  1369 + if (!inst) { return null; }
  1370 +
  1371 + var tp_inst = this._get(inst, 'timepicker');
  1372 + if (tp_inst) {
  1373 + var min = null, max = null, onselect = null;
  1374 + if (typeof name == 'string') { // if min/max was set with the string
  1375 + if (name === 'minDate' || name === 'minDateTime' ) {
  1376 + min = value;
  1377 + }
  1378 + else {
  1379 + if (name === 'maxDate' || name === 'maxDateTime') {
  1380 + max = value;
  1381 + }
  1382 + else {
  1383 + if (name === 'onSelect') {
  1384 + onselect = value;
  1385 + }
  1386 + }
  1387 + }
  1388 + } else {
  1389 + if (typeof name == 'object') { //if min/max was set with the JSON
  1390 + if (name.minDate) {
  1391 + min = name.minDate;
  1392 + } else {
  1393 + if (name.minDateTime) {
  1394 + min = name.minDateTime;
  1395 + } else {
  1396 + if (name.maxDate) {
  1397 + max = name.maxDate;
  1398 + } else {
  1399 + if (name.maxDateTime) {
  1400 + max = name.maxDateTime;
  1401 + }
  1402 + }
  1403 + }
  1404 + }
  1405 + }
  1406 + }
  1407 + if(min) { //if min was set
  1408 + if (min === 0) {
  1409 + min = new Date();
  1410 + } else {
  1411 + min = new Date(min);
  1412 + }
  1413 +
  1414 + tp_inst._defaults.minDate = min;
  1415 + tp_inst._defaults.minDateTime = min;
  1416 + } else if (max) { //if max was set
  1417 + if(max===0) {
  1418 + max=new Date();
  1419 + } else {
  1420 + max= new Date(max);
  1421 + }
  1422 + tp_inst._defaults.maxDate = max;
  1423 + tp_inst._defaults.maxDateTime = max;
  1424 + } else if (onselect) {
  1425 + tp_inst._defaults.onSelect = onselect;
  1426 + }
  1427 + }
  1428 + if (value === undefined) {
  1429 + return this._base_optionDatepicker(target, name);
  1430 + }
  1431 + return this._base_optionDatepicker(target, name, value);
  1432 +};
  1433 +
  1434 +//#######################################################################################
  1435 +// jQuery extend now ignores nulls!
  1436 +//#######################################################################################
  1437 +function extendRemove(target, props) {
  1438 + $.extend(target, props);
  1439 + for (var name in props) {
  1440 + if (props[name] === null || props[name] === undefined) {
  1441 + target[name] = props[name];
  1442 + }
  1443 + }
  1444 + return target;
  1445 +}
  1446 +
  1447 +//#######################################################################################
  1448 +// Splits datetime string into date ans time substrings.
  1449 +// Throws exception when date can't be parsed
  1450 +// If only date is present, time substring eill be ''
  1451 +//#######################################################################################
  1452 +var splitDateTime = function(dateFormat, dateTimeString, dateSettings)
  1453 +{
  1454 + try {
  1455 + var date = $.datepicker._base_parseDate(dateFormat, dateTimeString, dateSettings);
  1456 + } catch (err) {
  1457 + if (err.indexOf(":") >= 0) {
  1458 + // Hack! The error message ends with a colon, a space, and
  1459 + // the "extra" characters. We rely on that instead of
  1460 + // attempting to perfectly reproduce the parsing algorithm.
  1461 + var dateStringLength = dateTimeString.length-(err.length-err.indexOf(':')-2);
  1462 + var timeString = dateTimeString.substring(dateStringLength);
  1463 +
  1464 + return [dateTimeString.substring(0, dateStringLength), dateTimeString.substring(dateStringLength)];
  1465 +
  1466 + } else {
  1467 + throw err;
  1468 + }
  1469 + }
  1470 + return [dateTimeString, ''];
  1471 +};
  1472 +
  1473 +//#######################################################################################
  1474 +// Internal function to parse datetime interval
  1475 +// Returns: {date: Date, timeObj: Object}, where
  1476 +// date - parsed date without time (type Date)
  1477 +// timeObj = {hour: , minute: , second: , millisec: } - parsed time. Optional
  1478 +//#######################################################################################
  1479 +var parseDateTimeInternal = function(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings)
  1480 +{
  1481 + var date;
  1482 + var splitRes = splitDateTime(dateFormat, dateTimeString, dateSettings);
  1483 + date = $.datepicker._base_parseDate(dateFormat, splitRes[0], dateSettings);
  1484 + if (splitRes[1] !== '')
  1485 + {
  1486 + var timeString = splitRes[1];
  1487 + var separator = timeSettings && timeSettings.separator ? timeSettings.separator : $.timepicker._defaults.separator;
  1488 + if ( timeString.indexOf(separator) !== 0) {
  1489 + throw 'Missing time separator';
  1490 + }
  1491 + timeString = timeString.substring(separator.length);
  1492 + var parsedTime = $.datepicker.parseTime(timeFormat, timeString, timeSettings);
  1493 + if (parsedTime === null) {
  1494 + throw 'Wrong time format';
  1495 + }
  1496 + return {date: date, timeObj: parsedTime};
  1497 + } else {
  1498 + return {date: date};
  1499 + }
  1500 +};
  1501 +
  1502 +//#######################################################################################
  1503 +// Internal function to set timezone_select to the local timezone
  1504 +//#######################################################################################
  1505 +var selectLocalTimeZone = function(tp_inst, date)
  1506 +{
  1507 + if (tp_inst && tp_inst.timezone_select) {
  1508 + tp_inst._defaults.useLocalTimezone = true;
  1509 + var now = typeof date !== 'undefined' ? date : new Date();
  1510 + var tzoffset = timeZoneString(now);
  1511 + if (tp_inst._defaults.timezoneIso8601) {
  1512 + tzoffset = tzoffset.substring(0, 3) + ':' + tzoffset.substring(3);
  1513 + }
  1514 + tp_inst.timezone_select.val(tzoffset);
  1515 + }
  1516 +};
  1517 +
  1518 +// Input: Date Object
  1519 +// Output: String with timezone offset, e.g. '+0100'
  1520 +var timeZoneString = function(date)
  1521 +{
  1522 + var off = date.getTimezoneOffset() * -10100 / 60;
  1523 + var timezone = (off >= 0 ? '+' : '-') + Math.abs(off).toString().substr(1);
  1524 + return timezone;
  1525 +};
  1526 +
  1527 +$.timepicker = new Timepicker(); // singleton instance
  1528 +$.timepicker.version = "1.0.1";
  1529 +
  1530 +})(jQuery);
public/stylesheets/application.css
@@ -1394,6 +1394,7 @@ a.comment-picture { @@ -1394,6 +1394,7 @@ a.comment-picture {
1394 position: relative; 1394 position: relative;
1395 display: inline; 1395 display: inline;
1396 } 1396 }
  1397 +#content #boxes .box-1 .article-block img,
1397 #content #article .article-body img { 1398 #content #article .article-body img {
1398 max-width: 100%; 1399 max-width: 100%;
1399 height: auto; 1400 height: auto;
@@ -1503,7 +1504,13 @@ a.button:hover, body.noosfero a.button:hover, input.button:hover, a.button.with- @@ -1503,7 +1504,13 @@ a.button:hover, body.noosfero a.button:hover, input.button:hover, a.button.with-
1503 body.noosfero a.button.with-text.icon-none, body.noosfero input.button.with-text.icon-none { 1504 body.noosfero a.button.with-text.icon-none, body.noosfero input.button.with-text.icon-none {
1504 padding-left: 2px; 1505 padding-left: 2px;
1505 } 1506 }
1506 -a.button.disabled, input.disabled { 1507 +a.disabled{
  1508 + filter: url(/filters.svg#grayscale); /* Firefox 3.5+ */
  1509 + filter: gray; /* IE6-9 */
  1510 + -webkit-filter: grayscale(1); /* Google Chrome & Safari 6+ */
  1511 + cursor: default;
  1512 +}
  1513 +input.disabled {
1507 opacity: 0.5; 1514 opacity: 0.5;
1508 filter-opacity: 50%; 1515 filter-opacity: 50%;
1509 } 1516 }
@@ -1766,6 +1773,9 @@ a.button.disabled, input.disabled { @@ -1766,6 +1773,9 @@ a.button.disabled, input.disabled {
1766 width: 92px; 1773 width: 92px;
1767 overflow: hidden; 1774 overflow: hidden;
1768 } 1775 }
  1776 +.box-1 .common-profile-list-block span {
  1777 + width: 102px;
  1778 +}
1769 .common-profile-list-block .profile-image { 1779 .common-profile-list-block .profile-image {
1770 width: 92px; 1780 width: 92px;
1771 display: table-cell; 1781 display: table-cell;
@@ -3974,7 +3984,7 @@ h1#agenda-title { @@ -3974,7 +3984,7 @@ h1#agenda-title {
3974 } 3984 }
3975 /* * * Profile search block * * * * * * * */ 3985 /* * * Profile search block * * * * * * * */
3976 3986
3977 -.profile-search-block .formfield input { 3987 +.profile-search-block .search-field .formfield input {
3978 width: 100%; 3988 width: 100%;
3979 } 3989 }
3980 .profile-search-block .button.icon-search { 3990 .profile-search-block .button.icon-search {
@@ -4330,6 +4340,14 @@ h1#agenda-title { @@ -4330,6 +4340,14 @@ h1#agenda-title {
4330 margin: 5px 0; 4340 margin: 5px 0;
4331 padding: 5px; 4341 padding: 5px;
4332 } 4342 }
  4343 +
  4344 +.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; }
  4345 +.ui-timepicker-div dl { text-align: left; }
  4346 +.ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; }
  4347 +.ui-timepicker-div dl dd { margin: 0 10px 10px 65px; }
  4348 +.ui-timepicker-div td { font-size: 90%; }
  4349 +.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; }
  4350 +
4333 /* Categories block stuff */ 4351 /* Categories block stuff */
4334 4352
4335 .categories-block ul { 4353 .categories-block ul {
script/quick-start
@@ -2,12 +2,12 @@ @@ -2,12 +2,12 @@
2 2
3 say() { 3 say() {
4 msg="$@" 4 msg="$@"
5 - printf "\033[33;01m%sm%s\033[m\n" "$msg"33[m\n" "$msg" 5 + printf "\033[1;34;49m%sm%s\033[m\n" "$msg"33[m\n" "$msg"
6 } 6 }
7 7
8 complain() { 8 complain() {
9 msg="$@" 9 msg="$@"
10 - printf "\033[1;31;40m%sm%s\033[m\n" "$msg"33[m\n" "$msg" 10 + printf "\033[1;31;49m%sm%s\033[m\n" "$msg"33[m\n" "$msg"
11 } 11 }
12 12
13 run() { 13 run() {
test/factories.rb
@@ -442,7 +442,7 @@ module Noosfero::Factory @@ -442,7 +442,7 @@ module Noosfero::Factory
442 442
443 def defaults_for_comment(params = {}) 443 def defaults_for_comment(params = {})
444 name = "comment_#{rand(1000)}" 444 name = "comment_#{rand(1000)}"
445 - { :title => name, :body => "my own comment", :source_id => 1 }.merge(params) 445 + { :title => name, :body => "my own comment", :source_id => 1, :source_type => 'Article' }.merge(params)
446 end 446 end
447 447
448 ############################################### 448 ###############################################
test/functional/account_controller_test.rb
@@ -11,6 +11,10 @@ class AccountControllerTest &lt; ActionController::TestCase @@ -11,6 +11,10 @@ class AccountControllerTest &lt; ActionController::TestCase
11 11
12 all_fixtures 12 all_fixtures
13 13
  14 + def teardown
  15 + Thread.current[:enabled_plugins] = nil
  16 + end
  17 +
14 def setup 18 def setup
15 @controller = AccountController.new 19 @controller = AccountController.new
16 @request = ActionController::TestRequest.new 20 @request = ActionController::TestRequest.new
@@ -20,24 +24,22 @@ class AccountControllerTest &lt; ActionController::TestCase @@ -20,24 +24,22 @@ class AccountControllerTest &lt; ActionController::TestCase
20 def test_local_files_reference 24 def test_local_files_reference
21 assert_local_files_reference 25 assert_local_files_reference
22 end 26 end
23 - 27 +
24 def test_valid_xhtml 28 def test_valid_xhtml
25 assert_valid_xhtml 29 assert_valid_xhtml
26 end 30 end
27 - 31 +
28 def test_should_login_and_redirect 32 def test_should_login_and_redirect
29 post :login, :user => {:login => 'johndoe', :password => 'test'} 33 post :login, :user => {:login => 'johndoe', :password => 'test'}
30 assert session[:user] 34 assert session[:user]
31 assert_response :redirect 35 assert_response :redirect
32 end 36 end
33 37
34 - should 'redirect to where user was on login' do  
35 - @request.env["HTTP_REFERER"] = '/bli'  
36 - u = new_user 38 + should 'display notice message if the login fail' do
37 @controller.stubs(:logged_in?).returns(false) 39 @controller.stubs(:logged_in?).returns(false)
38 post :login, :user => {:login => 'quire', :password => 'quire'} 40 post :login, :user => {:login => 'quire', :password => 'quire'}
39 41
40 - assert_redirected_to '/bli' 42 + assert session[:notice].include?('Incorrect')
41 end 43 end
42 44
43 should 'authenticate on the current environment' do 45 should 'authenticate on the current environment' do
@@ -46,23 +48,11 @@ class AccountControllerTest &lt; ActionController::TestCase @@ -46,23 +48,11 @@ class AccountControllerTest &lt; ActionController::TestCase
46 post :login, :user => { :login => 'fake', :password => 'fake' } 48 post :login, :user => { :login => 'fake', :password => 'fake' }
47 end 49 end
48 50
49 - should 'redirect to where was when login on other environment' do  
50 - e = fast_create(Environment, :name => 'other_environment')  
51 - e.domains << Domain.new(:name => 'other.environment')  
52 - e.save!  
53 - u = create_user('test_user', :environment => e).person  
54 -  
55 - @request.env["HTTP_REFERER"] = '/bli'  
56 - post :login, :user => {:login => 'test_user', :password => 'test_user'}  
57 -  
58 - assert_redirected_to '/bli'  
59 - end  
60 -  
61 def test_should_fail_login_and_not_redirect 51 def test_should_fail_login_and_not_redirect
62 @request.env["HTTP_REFERER"] = 'bli' 52 @request.env["HTTP_REFERER"] = 'bli'
63 post :login, :user => {:login => 'johndoe', :password => 'bad password'} 53 post :login, :user => {:login => 'johndoe', :password => 'bad password'}
64 assert_nil session[:user] 54 assert_nil session[:user]
65 - assert_response :redirect 55 + assert_response :success
66 end 56 end
67 57
68 def test_should_allow_signup 58 def test_should_allow_signup
@@ -120,7 +110,7 @@ class AccountControllerTest &lt; ActionController::TestCase @@ -120,7 +110,7 @@ class AccountControllerTest &lt; ActionController::TestCase
120 110
121 def test_shoud_save_with_acceptance_of_terms_of_use_on_signup 111 def test_shoud_save_with_acceptance_of_terms_of_use_on_signup
122 assert_difference User, :count do 112 assert_difference User, :count do
123 - Environment.default.update_attributes(:terms_of_use => 'some terms ...') 113 + Environment.default.update_attributes(:terms_of_use => 'some terms ...')
124 new_user(:terms_accepted => '1') 114 new_user(:terms_accepted => '1')
125 assert_response :success 115 assert_response :success
126 assert_not_nil assigns(:register_pending) 116 assert_not_nil assigns(:register_pending)
@@ -144,7 +134,7 @@ class AccountControllerTest &lt; ActionController::TestCase @@ -144,7 +134,7 @@ class AccountControllerTest &lt; ActionController::TestCase
144 post :login, :user => {:login => 'johndoe', :password => 'test'}, :remember_me => "0" 134 post :login, :user => {:login => 'johndoe', :password => 'test'}, :remember_me => "0"
145 assert_nil @response.cookies["auth_token"] 135 assert_nil @response.cookies["auth_token"]
146 end 136 end
147 - 137 +
148 def test_should_delete_token_on_logout 138 def test_should_delete_token_on_logout
149 login_as :johndoe 139 login_as :johndoe
150 get :logout 140 get :logout
@@ -344,7 +334,7 @@ class AccountControllerTest &lt; ActionController::TestCase @@ -344,7 +334,7 @@ class AccountControllerTest &lt; ActionController::TestCase
344 login_as(person.identifier) 334 login_as(person.identifier)
345 335
346 ent = fast_create(Enterprise, :name => 'test enterprise', :identifier => 'test_ent') 336 ent = fast_create(Enterprise, :name => 'test enterprise', :identifier => 'test_ent')
347 - 337 +
348 task = mock 338 task = mock
349 task.expects(:enterprise).returns(ent).at_least_once 339 task.expects(:enterprise).returns(ent).at_least_once
350 EnterpriseActivation.expects(:find_by_code).with('0123456789').returns(task).at_least_once 340 EnterpriseActivation.expects(:find_by_code).with('0123456789').returns(task).at_least_once
@@ -359,7 +349,7 @@ class AccountControllerTest &lt; ActionController::TestCase @@ -359,7 +349,7 @@ class AccountControllerTest &lt; ActionController::TestCase
359 login_as(person.identifier) 349 login_as(person.identifier)
360 350
361 ent = fast_create(Enterprise, :name => 'test enterprise', :identifier => 'test_ent', :enabled => false) 351 ent = fast_create(Enterprise, :name => 'test enterprise', :identifier => 'test_ent', :enabled => false)
362 - 352 +
363 task = mock 353 task = mock
364 task.expects(:enterprise).returns(ent).at_least_once 354 task.expects(:enterprise).returns(ent).at_least_once
365 EnterpriseActivation.expects(:find_by_code).with('0123456789').returns(task).at_least_once 355 EnterpriseActivation.expects(:find_by_code).with('0123456789').returns(task).at_least_once
@@ -555,7 +545,7 @@ class AccountControllerTest &lt; ActionController::TestCase @@ -555,7 +545,7 @@ class AccountControllerTest &lt; ActionController::TestCase
555 login_as(person.identifier) 545 login_as(person.identifier)
556 546
557 env = Environment.default 547 env = Environment.default
558 - env.terms_of_use = 'some terms' 548 + env.terms_of_use = 'some terms'
559 env.save! 549 env.save!
560 ent = fast_create(Enterprise, :name => 'test enterprise', :identifier => 'test_ent', :enabled => false) 550 ent = fast_create(Enterprise, :name => 'test enterprise', :identifier => 'test_ent', :enabled => false)
561 ent.update_attribute(:foundation_year, 1998) 551 ent.update_attribute(:foundation_year, 1998)
@@ -697,7 +687,6 @@ class AccountControllerTest &lt; ActionController::TestCase @@ -697,7 +687,6 @@ class AccountControllerTest &lt; ActionController::TestCase
697 assert_nil assigns(:message) 687 assert_nil assigns(:message)
698 post :login, :user => {:login => 'testuser', :password => 'test123'} 688 post :login, :user => {:login => 'testuser', :password => 'test123'}
699 assert_nil session[:user] 689 assert_nil session[:user]
700 - assert_redirected_to '/bli'  
701 end 690 end
702 691
703 should 'not activate user when activation code is incorrect' do 692 should 'not activate user when activation code is incorrect' do
@@ -707,7 +696,6 @@ class AccountControllerTest &lt; ActionController::TestCase @@ -707,7 +696,6 @@ class AccountControllerTest &lt; ActionController::TestCase
707 assert_nil assigns(:message) 696 assert_nil assigns(:message)
708 post :login, :user => {:login => 'testuser', :password => 'test123'} 697 post :login, :user => {:login => 'testuser', :password => 'test123'}
709 assert_nil session[:user] 698 assert_nil session[:user]
710 - assert_redirected_to '/bli'  
711 end 699 end
712 700
713 should 'be able to upload an image' do 701 should 'be able to upload an image' do
@@ -777,6 +765,122 @@ class AccountControllerTest &lt; ActionController::TestCase @@ -777,6 +765,122 @@ class AccountControllerTest &lt; ActionController::TestCase
777 assert_tag :tag => 'strong', :content => 'Plugin2 text' 765 assert_tag :tag => 'strong', :content => 'Plugin2 text'
778 end 766 end
779 767
  768 + should 'login with an alternative authentication defined by plugin' do
  769 + class Plugin1 < Noosfero::Plugin
  770 + def alternative_authentication
  771 + User.new(:login => 'testuser')
  772 + end
  773 + end
  774 + Environment.default.enable_plugin(Plugin1.name)
  775 +
  776 + post :login, :user => {:login => "testuser"}
  777 +
  778 + assert_equal 'testuser', assigns(:current_user).login
  779 + assert_response :redirect
  780 + end
  781 +
  782 + should "login with the default autentication if the alternative authentication method doesn't login the user" do
  783 + class Plugin1 < Noosfero::Plugin
  784 + def alternative_authentication
  785 + nil
  786 + end
  787 + end
  788 + Environment.default.enable_plugin(Plugin1.name)
  789 + post :login, :user => {:login => 'johndoe', :password => 'test'}
  790 + assert session[:user]
  791 + assert_equal 'johndoe', assigns(:current_user).login
  792 + assert_response :redirect
  793 + end
  794 +
  795 + should "redirect user on signup if a plugin doesn't allow user registration" do
  796 + class TestRegistrationPlugin < Noosfero::Plugin
  797 + def allow_user_registration
  798 + false
  799 + end
  800 + end
  801 + Noosfero::Plugin::Manager.any_instance.stubs(:enabled_plugins).returns([TestRegistrationPlugin.new])
  802 +
  803 + post :signup, :user => { :login => 'testuser', :password => '123456', :password_confirmation => '123456', :email => 'testuser@example.com' }
  804 + assert_response :redirect
  805 + end
  806 +
  807 + should "not display the new user button on login page if not allowed by any plugin" do
  808 + class Plugin1 < Noosfero::Plugin
  809 + def allow_user_registration
  810 + false
  811 + end
  812 + end
  813 +
  814 + class Plugin2 < Noosfero::Plugin
  815 + def allow_user_registration
  816 + true
  817 + end
  818 + end
  819 + Noosfero::Plugin::Manager.any_instance.stubs(:enabled_plugins).returns([Plugin1.new, Plugin2.new])
  820 +
  821 + get :login
  822 +
  823 + assert_no_tag :tag => 'a', :attributes => {:href => '/account/signup'}
  824 + end
  825 +
  826 + should "redirect user on forgot_password action if a plugin doesn't allow user to recover its password" do
  827 + class TestRegistrationPlugin < Noosfero::Plugin
  828 + def allow_password_recovery
  829 + false
  830 + end
  831 + end
  832 + Noosfero::Plugin::Manager.any_instance.stubs(:enabled_plugins).returns([TestRegistrationPlugin.new])
  833 +
  834 + #Redirect on get action
  835 + get :forgot_password
  836 + assert_response :redirect
  837 +
  838 + #Redirect on post action
  839 + post :forgot_password, :change_password => { :login => 'test', :email => 'test@localhost.localdomain' }
  840 + assert_response :redirect
  841 + end
  842 +
  843 + should "not display the forgot password button on login page if not allowed by any plugin" do
  844 + class Plugin1 < Noosfero::Plugin
  845 + def allow_password_recovery
  846 + false
  847 + end
  848 + end
  849 +
  850 + class Plugin2 < Noosfero::Plugin
  851 + def allow_password_recovery
  852 + true
  853 + end
  854 + end
  855 + Noosfero::Plugin::Manager.any_instance.stubs(:enabled_plugins).returns([Plugin1.new, Plugin2.new])
  856 +
  857 + get :login
  858 +
  859 + assert_no_tag :tag => 'a', :attributes => {:href => '/account/forgot_password'}
  860 + end
  861 +
  862 + should 'add extra content on login form from plugins' do
  863 + class Plugin1 < Noosfero::Plugin
  864 + def login_extra_contents
  865 + lambda {"<strong>Plugin1 text</strong>"}
  866 + end
  867 + end
  868 + class Plugin2 < Noosfero::Plugin
  869 + def login_extra_contents
  870 + lambda {"<strong>Plugin2 text</strong>"}
  871 + end
  872 + end
  873 +
  874 + Environment.default.enable_plugin(Plugin1.name)
  875 + Environment.default.enable_plugin(Plugin2.name)
  876 +
  877 + get :login
  878 +
  879 + assert_tag :tag => 'strong', :content => 'Plugin1 text'
  880 + assert_tag :tag => 'strong', :content => 'Plugin2 text'
  881 + end
  882 +
  883 +
780 protected 884 protected
781 def new_user(options = {}, extra_options ={}) 885 def new_user(options = {}, extra_options ={})
782 data = {:profile_data => person_data} 886 data = {:profile_data => person_data}
test/functional/content_viewer_controller_test.rb
@@ -1400,6 +1400,38 @@ end @@ -1400,6 +1400,38 @@ end
1400 end 1400 end
1401 end 1401 end
1402 1402
  1403 + should 'not display article actions button if any plugins says so' do
  1404 + class Plugin1 < Noosfero::Plugin
  1405 + def content_remove_edit(content); true; end
  1406 + end
  1407 + class Plugin2 < Noosfero::Plugin
  1408 + def content_remove_edit(content); false; end
  1409 + end
  1410 +
  1411 + environment.enable_plugin(Plugin1.name)
  1412 + environment.enable_plugin(Plugin2.name)
  1413 +
  1414 + login_as('testinguser')
  1415 + xhr :get, :view_page, :profile => 'testinguser', :page => [], :toolbar => true
  1416 + assert_no_tag :tag => 'div', :attributes => { :id => 'article-actions' }, :descendant => { :tag => 'a', :attributes => { :href => "/myprofile/testinguser/cms/edit/#{profile.home_page.id}" } }
  1417 + end
  1418 +
  1419 + should 'expire article actions button if any plugins says so' do
  1420 + class Plugin1 < Noosfero::Plugin
  1421 + def content_expire_edit(content); 'This button is expired.'; end
  1422 + end
  1423 + class Plugin2 < Noosfero::Plugin
  1424 + def content_expire_edit(content); nil; end
  1425 + end
  1426 +
  1427 + environment.enable_plugin(Plugin1.name)
  1428 + environment.enable_plugin(Plugin2.name)
  1429 +
  1430 + login_as('testinguser')
  1431 + xhr :get, :view_page, :profile => 'testinguser', :page => [], :toolbar => true
  1432 + assert_tag :tag => 'div', :attributes => { :id => 'article-actions' }, :descendant => { :tag => 'a', :attributes => { :title => 'This button is expired.', :class => 'button with-text icon-edit disabled' } }
  1433 + end
  1434 +
1403 should 'remove email from article followers when unfollow' do 1435 should 'remove email from article followers when unfollow' do
1404 profile = create_user('testuser').person 1436 profile = create_user('testuser').person
1405 follower_email = 'john@doe.br' 1437 follower_email = 'john@doe.br'
@@ -1432,4 +1464,42 @@ end @@ -1432,4 +1464,42 @@ end
1432 assert spam.spam? 1464 assert spam.spam?
1433 end 1465 end
1434 1466
  1467 + should 'be able to edit a comment' do
  1468 + login_as profile.identifier
  1469 + page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text', :accept_comments => false)
  1470 + comment = fast_create(Comment, :body => 'Original comment', :author_id => profile.id, :source_id => page.id, :source_type => 'Article')
  1471 +
  1472 + post :edit_comment, :id => comment.id, :profile => profile.identifier, :page => [ 'myarticle' ], :comment => { :body => 'Comment edited' }
  1473 + assert_equal 'Comment edited', Comment.find(comment.id).body
  1474 + end
  1475 +
  1476 + should 'edit comment from a page' do
  1477 + login_as profile.identifier
  1478 + page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text')
  1479 + comment = fast_create(Comment, :body => 'Original comment', :author_id => profile.id, :source_id => page.id, :source_type => 'Article')
  1480 +
  1481 + get :edit_comment, :id => comment.id, :profile => profile.identifier, :page => [ 'myarticle' ], :comment => { :body => 'Comment edited' }
  1482 + assert_tag :tag => 'h1', :content => 'Edit comment'
  1483 + end
  1484 +
  1485 + should 'not edit comment from other page' do
  1486 + login_as profile.identifier
  1487 + page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text')
  1488 + comment = fast_create(Comment, :body => 'Original comment', :author_id => profile.id, :source_id => page.id, :source_type => 'Article')
  1489 +
  1490 + other_page = profile.articles.create!(:name => 'my other article', :body => 'the body of the text')
  1491 + comment_on_other_page = fast_create(Comment, :body => 'Comment on other article', :author_id => profile.id, :source_id => other_page.id, :source_type => 'Article')
  1492 +
  1493 + get :edit_comment, :id => comment_on_other_page.id, :profile => profile.identifier, :page => [ 'myarticle' ], :comment => { :body => 'Comment edited' }
  1494 + assert_redirected_to page.url
  1495 + end
  1496 +
  1497 + should 'not crash on edit comment if comment does not exist' do
  1498 + login_as profile.identifier
  1499 + page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text')
  1500 +
  1501 + get :edit_comment, :id => 1000, :profile => profile.identifier, :page => [ 'myarticle' ], :comment => { :body => 'Comment edited' }
  1502 + assert_response 404
  1503 + end
  1504 +
1435 end 1505 end
test/functional/home_controller_test.rb
@@ -6,8 +6,11 @@ class HomeController; def rescue_action(e) raise e end; end @@ -6,8 +6,11 @@ class HomeController; def rescue_action(e) raise e end; end
6 6
7 class HomeControllerTest < ActionController::TestCase 7 class HomeControllerTest < ActionController::TestCase
8 8
9 -# all_fixtures:profiles, :environments, :domains  
10 -all_fixtures 9 + def teardown
  10 + Thread.current[:enabled_plugins] = nil
  11 + end
  12 +
  13 + all_fixtures
11 def setup 14 def setup
12 @controller = HomeController.new 15 @controller = HomeController.new
13 @request = ActionController::TestRequest.new 16 @request = ActionController::TestRequest.new
@@ -93,4 +96,44 @@ all_fixtures @@ -93,4 +96,44 @@ all_fixtures
93 assert_tag :content => /Noosfero terms of use/ 96 assert_tag :content => /Noosfero terms of use/
94 end 97 end
95 98
  99 + should 'provide a link to make the user authentication' do
  100 + class Plugin1 < Noosfero::Plugin
  101 + def alternative_authentication_link
  102 + lambda {"<a href='plugin1'>Plugin1 link</a>"}
  103 + end
  104 + end
  105 + class Plugin2 < Noosfero::Plugin
  106 + def alternative_authentication_link
  107 + lambda {"<a href='plugin2'>Plugin2 link</a>"}
  108 + end
  109 + end
  110 +
  111 + Environment.default.enable_plugin(Plugin1)
  112 + Environment.default.enable_plugin(Plugin2)
  113 +
  114 + get :index
  115 +
  116 + assert_tag :tag => 'a', :content => 'Plugin1 link'
  117 + assert_tag :tag => 'a', :content => 'Plugin2 link'
  118 + end
  119 +
  120 + should "not display the new user button on login page if now allowed by any plugin" do
  121 + class Plugin1 < Noosfero::Plugin
  122 + def allow_user_registration
  123 + false
  124 + end
  125 + end
  126 +
  127 + class Plugin2 < Noosfero::Plugin
  128 + def allow_user_registration
  129 + true
  130 + end
  131 + end
  132 + Noosfero::Plugin::Manager.any_instance.stubs(:enabled_plugins).returns([Plugin1.new, Plugin2.new])
  133 +
  134 + get :index
  135 +
  136 + assert_no_tag :tag => 'a', :attributes => {:href => '/account/signup'}
  137 + end
  138 +
96 end 139 end
test/functional/profile_design_controller_test.rb
@@ -5,12 +5,12 @@ class ProfileDesignController; def rescue_action(e) raise e end; end @@ -5,12 +5,12 @@ class ProfileDesignController; def rescue_action(e) raise e end; end
5 5
6 class ProfileDesignControllerTest < ActionController::TestCase 6 class ProfileDesignControllerTest < ActionController::TestCase
7 7
8 - COMMOM_BLOCKS = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock, FeedReaderBlock, ProfileImageBlock, LocationBlock, SlideshowBlock, ProfileSearchBlock ] 8 + COMMOM_BLOCKS = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock, FeedReaderBlock, ProfileImageBlock, LocationBlock, SlideshowBlock, ProfileSearchBlock, HighlightsBlock ]
9 PERSON_BLOCKS = COMMOM_BLOCKS + [FriendsBlock, FavoriteEnterprisesBlock, CommunitiesBlock, EnterprisesBlock ] 9 PERSON_BLOCKS = COMMOM_BLOCKS + [FriendsBlock, FavoriteEnterprisesBlock, CommunitiesBlock, EnterprisesBlock ]
10 PERSON_BLOCKS_WITH_MEMBERS = PERSON_BLOCKS + [MembersBlock] 10 PERSON_BLOCKS_WITH_MEMBERS = PERSON_BLOCKS + [MembersBlock]
11 PERSON_BLOCKS_WITH_BLOG = PERSON_BLOCKS + [BlogArchivesBlock] 11 PERSON_BLOCKS_WITH_BLOG = PERSON_BLOCKS + [BlogArchivesBlock]
12 12
13 - ENTERPRISE_BLOCKS = COMMOM_BLOCKS + [DisabledEnterpriseMessageBlock, HighlightsBlock, FeaturedProductsBlock, FansBlock] 13 + ENTERPRISE_BLOCKS = COMMOM_BLOCKS + [DisabledEnterpriseMessageBlock, FeaturedProductsBlock, FansBlock]
14 ENTERPRISE_BLOCKS_WITH_PRODUCTS_ENABLE = ENTERPRISE_BLOCKS + [ProductsBlock] 14 ENTERPRISE_BLOCKS_WITH_PRODUCTS_ENABLE = ENTERPRISE_BLOCKS + [ProductsBlock]
15 15
16 attr_reader :holder 16 attr_reader :holder
@@ -295,17 +295,17 @@ class ProfileDesignControllerTest &lt; ActionController::TestCase @@ -295,17 +295,17 @@ class ProfileDesignControllerTest &lt; ActionController::TestCase
295 should 'offer to create blog archives block only if has blog' do 295 should 'offer to create blog archives block only if has blog' do
296 holder.articles << Blog.new(:name => 'Blog test', :profile => holder) 296 holder.articles << Blog.new(:name => 'Blog test', :profile => holder)
297 get :add_block, :profile => 'designtestuser' 297 get :add_block, :profile => 'designtestuser'
298 - assert_tag :tag => 'input', :attributes => { :id => 'type_blogarchivesblock', :value => 'BlogArchivesBlock' } 298 + assert_tag :tag => 'input', :attributes => { :name => 'type', :value => 'BlogArchivesBlock' }
299 end 299 end
300 300
301 should 'not offer to create blog archives block if user dont have blog' do 301 should 'not offer to create blog archives block if user dont have blog' do
302 get :add_block, :profile => 'designtestuser' 302 get :add_block, :profile => 'designtestuser'
303 - assert_no_tag :tag => 'input', :attributes => { :id => 'type_blogarchivesblock', :value => 'BlogArchivesBlock' } 303 + assert_no_tag :tag => 'input', :attributes => { :name => 'type', :value => 'BlogArchivesBlock' }
304 end 304 end
305 305
306 should 'offer to create feed reader block' do 306 should 'offer to create feed reader block' do
307 get :add_block, :profile => 'designtestuser' 307 get :add_block, :profile => 'designtestuser'
308 - assert_tag :tag => 'input', :attributes => { :id => 'type_feedreaderblock', :value => 'FeedReaderBlock' } 308 + assert_tag :tag => 'input', :attributes => { :name => 'type', :value => 'FeedReaderBlock' }
309 end 309 end
310 310
311 should 'be able to edit FeedReaderBlock' do 311 should 'be able to edit FeedReaderBlock' do
@@ -421,14 +421,14 @@ class ProfileDesignControllerTest &lt; ActionController::TestCase @@ -421,14 +421,14 @@ class ProfileDesignControllerTest &lt; ActionController::TestCase
421 profile.stubs(:is_admin?).with(profile.environment).returns(true) 421 profile.stubs(:is_admin?).with(profile.environment).returns(true)
422 @controller.stubs(:user).returns(profile) 422 @controller.stubs(:user).returns(profile)
423 get :add_block, :profile => 'designtestuser' 423 get :add_block, :profile => 'designtestuser'
424 - assert_tag :tag => 'input', :attributes => { :id => 'type_rawhtmlblock', :value => 'RawHTMLBlock' } 424 + assert_tag :tag => 'input', :attributes => { :name => 'type', :value => 'RawHTMLBlock' }
425 end 425 end
426 426
427 should 'not allow normal users to add RawHTMLBlock' do 427 should 'not allow normal users to add RawHTMLBlock' do
428 profile.stubs(:is_admin?).with(profile.environment).returns(false) 428 profile.stubs(:is_admin?).with(profile.environment).returns(false)
429 @controller.stubs(:user).returns(profile) 429 @controller.stubs(:user).returns(profile)
430 get :add_block, :profile => 'designtestuser' 430 get :add_block, :profile => 'designtestuser'
431 - assert_no_tag :tag => 'input', :attributes => { :id => 'type_rawhtmlblock', :value => 'RawHTMLBlock' } 431 + assert_no_tag :tag => 'input', :attributes => { :name => 'type', :value => 'RawHTMLBlock' }
432 end 432 end
433 433
434 should 'editing article block displays right selected article' do 434 should 'editing article block displays right selected article' do
test/integration/routing_test.rb
@@ -221,4 +221,11 @@ class RoutingTest &lt; ActionController::IntegrationTest @@ -221,4 +221,11 @@ class RoutingTest &lt; ActionController::IntegrationTest
221 assert_routing('/chat/avatar/chemical-brothers', :controller => 'chat', :action => 'avatar', :id => 'chemical-brothers') 221 assert_routing('/chat/avatar/chemical-brothers', :controller => 'chat', :action => 'avatar', :id => 'chemical-brothers')
222 end 222 end
223 223
  224 + def test_plugins_generic_routes
  225 + assert_routing('/plugin/foo/public_bar/play/1', {:controller => 'foo_plugin_public_bar', :action => 'play', :id => '1'})
  226 + assert_routing('/profile/test/plugin/foo/profile_bar/play/1', {:controller => 'foo_plugin_profile_bar', :action => 'play', :id => '1', :profile => 'test'})
  227 + assert_routing('/myprofile/test/plugin/foo/myprofile_bar/play/1', {:controller => 'foo_plugin_myprofile_bar', :action => 'play', :id => '1', :profile => 'test'})
  228 + assert_routing('/admin/plugin/foo/admin_bar/play/1', {:controller => 'foo_plugin_admin_bar', :action => 'play', :id => '1'})
  229 + end
  230 +
224 end 231 end
test/unit/box_test.rb 0 → 100644
@@ -0,0 +1,79 @@ @@ -0,0 +1,79 @@
  1 +require File.dirname(__FILE__) + '/../test_helper'
  2 +
  3 +class BoxTest < ActiveSupport::TestCase
  4 +
  5 + should 'list allowed blocks for center box' do
  6 + blocks = Box.new(:position => 1).acceptable_blocks
  7 +
  8 + assert !blocks.include?('block')
  9 + assert !blocks.include?('disabled-enterprise-message-block')
  10 + assert !blocks.include?('featured-products-block')
  11 + assert !blocks.include?('products-block')
  12 + assert !blocks.include?('profile-info-block')
  13 + assert !blocks.include?('profile-list-block')
  14 + assert !blocks.include?('profile-search-block')
  15 + assert !blocks.include?('slideshow-block')
  16 + assert !blocks.include?('location-block')
  17 +
  18 + assert blocks.include?('article-block')
  19 + assert blocks.include?('blog-archives-block')
  20 + assert blocks.include?('categories-block')
  21 + assert blocks.include?('communities-block')
  22 + assert blocks.include?('enterprises-block')
  23 + assert blocks.include?('environment-statistics-block')
  24 + assert blocks.include?('fans-block')
  25 + assert blocks.include?('favorite-enterprises-block')
  26 + assert blocks.include?('feed-reader-block')
  27 + assert blocks.include?('friends-block')
  28 + assert blocks.include?('highlights-block')
  29 + assert blocks.include?('link-list-block')
  30 + assert blocks.include?('login-block')
  31 + assert blocks.include?('main-block')
  32 + assert blocks.include?('members-block')
  33 + assert blocks.include?('my-network-block')
  34 + assert blocks.include?('people-block')
  35 + assert blocks.include?('profile-image-block')
  36 + assert blocks.include?('raw-html-block')
  37 + assert blocks.include?('recent-documents-block')
  38 + assert blocks.include?('sellers-search-block')
  39 + assert blocks.include?('tags-block')
  40 + end
  41 +
  42 + should 'list allowed blocks for box at position 2' do
  43 + blocks = Box.new(:position => 2).acceptable_blocks
  44 +
  45 + assert !blocks.include?('main-block')
  46 + assert !blocks.include?('block')
  47 + assert !blocks.include?('profile-list-block')
  48 +
  49 + assert blocks.include?('article-block')
  50 + assert blocks.include?('blog-archives-block')
  51 + assert blocks.include?('categories-block')
  52 + assert blocks.include?('communities-block')
  53 + assert blocks.include?('disabled-enterprise-message-block')
  54 + assert blocks.include?('enterprises-block')
  55 + assert blocks.include?('environment-statistics-block')
  56 + assert blocks.include?('fans-block')
  57 + assert blocks.include?('favorite-enterprises-block')
  58 + assert blocks.include?('featured-products-block')
  59 + assert blocks.include?('feed-reader-block')
  60 + assert blocks.include?('friends-block')
  61 + assert blocks.include?('highlights-block')
  62 + assert blocks.include?('link-list-block')
  63 + assert blocks.include?('location-block')
  64 + assert blocks.include?('login-block')
  65 + assert blocks.include?('members-block')
  66 + assert blocks.include?('my-network-block')
  67 + assert blocks.include?('people-block')
  68 + assert blocks.include?('products-block')
  69 + assert blocks.include?('profile-image-block')
  70 + assert blocks.include?('profile-info-block')
  71 + assert blocks.include?('profile-search-block')
  72 + assert blocks.include?('raw-html-block')
  73 + assert blocks.include?('recent-documents-block')
  74 + assert blocks.include?('sellers-search-block')
  75 + assert blocks.include?('slideshow-block')
  76 + assert blocks.include?('tags-block')
  77 + end
  78 +
  79 +end
test/unit/cms_helper_test.rb
@@ -5,6 +5,7 @@ class CmsHelperTest &lt; ActiveSupport::TestCase @@ -5,6 +5,7 @@ class CmsHelperTest &lt; ActiveSupport::TestCase
5 include CmsHelper 5 include CmsHelper
6 include BlogHelper 6 include BlogHelper
7 include ApplicationHelper 7 include ApplicationHelper
  8 + include ActionView::Helpers::UrlHelper
8 9
9 should 'show default options for article' do 10 should 'show default options for article' do
10 CmsHelperTest.any_instance.stubs(:controller).returns(ActionController::Base.new) 11 CmsHelperTest.any_instance.stubs(:controller).returns(ActionController::Base.new)
@@ -47,14 +48,18 @@ class CmsHelperTest &lt; ActiveSupport::TestCase @@ -47,14 +48,18 @@ class CmsHelperTest &lt; ActiveSupport::TestCase
47 end 48 end
48 49
49 should 'display spread button when profile is a person' do 50 should 'display spread button when profile is a person' do
  51 + @controller = ApplicationController.new
  52 + @plugins.stubs(:dispatch).returns([])
50 profile = fast_create(Person) 53 profile = fast_create(Person)
51 article = fast_create(TinyMceArticle, :name => 'My article', :profile_id => profile.id) 54 article = fast_create(TinyMceArticle, :name => 'My article', :profile_id => profile.id)
52 - expects(:button_without_text).with(:spread, 'Spread this', :action => 'publish', :id => article.id) 55 + expects(:link_to).with('Spread this', {:action => 'publish', :id => article.id}, :class => 'button with-text icon-spread', :title => nil)
53 56
54 result = display_spread_button(profile, article) 57 result = display_spread_button(profile, article)
55 end 58 end
56 59
57 should 'display spread button when profile is a community and env has portal_community' do 60 should 'display spread button when profile is a community and env has portal_community' do
  61 + @controller = ApplicationController.new
  62 + @plugins.stubs(:dispatch).returns([])
58 env = fast_create(Environment) 63 env = fast_create(Environment)
59 env.expects(:portal_community).returns(true) 64 env.expects(:portal_community).returns(true)
60 profile = fast_create(Community, :environment_id => env.id) 65 profile = fast_create(Community, :environment_id => env.id)
@@ -62,12 +67,14 @@ class CmsHelperTest &lt; ActiveSupport::TestCase @@ -62,12 +67,14 @@ class CmsHelperTest &lt; ActiveSupport::TestCase
62 67
63 article = fast_create(TinyMceArticle, :name => 'My article', :profile_id => profile.id) 68 article = fast_create(TinyMceArticle, :name => 'My article', :profile_id => profile.id)
64 69
65 - expects(:button_without_text).with(:spread, 'Spread this', :action => 'publish_on_portal_community', :id => article.id) 70 + expects(:link_to).with('Spread this', {:action => 'publish_on_portal_community', :id => article.id}, :class => 'button with-text icon-spread', :title => nil)
66 71
67 result = display_spread_button(profile, article) 72 result = display_spread_button(profile, article)
68 end 73 end
69 74
70 should 'not display spread button when profile is a community and env has not portal_community' do 75 should 'not display spread button when profile is a community and env has not portal_community' do
  76 + @controller = ApplicationController.new
  77 + @plugins.stubs(:dispatch).returns([])
71 env = fast_create(Environment) 78 env = fast_create(Environment)
72 env.expects(:portal_community).returns(nil) 79 env.expects(:portal_community).returns(nil)
73 profile = fast_create(Community, :environment_id => env.id) 80 profile = fast_create(Community, :environment_id => env.id)
@@ -75,31 +82,37 @@ class CmsHelperTest &lt; ActiveSupport::TestCase @@ -75,31 +82,37 @@ class CmsHelperTest &lt; ActiveSupport::TestCase
75 82
76 article = fast_create(TinyMceArticle, :name => 'My article', :profile_id => profile.id) 83 article = fast_create(TinyMceArticle, :name => 'My article', :profile_id => profile.id)
77 84
78 - expects(:button_without_text).with(:spread, 'Spread this', :action => 'publish_on_portal_community', :id => article.id).never 85 + expects(:link_to).with('Spread this', {:action => 'publish_on_portal_community', :id => article.id}, :class => 'button with-text icon-spread', :title => nil).never
79 86
80 result = display_spread_button(profile, article) 87 result = display_spread_button(profile, article)
81 end 88 end
82 89
83 should 'display delete_button to folder' do 90 should 'display delete_button to folder' do
  91 + @controller = ApplicationController.new
  92 + @plugins.stubs(:dispatch).returns([])
84 profile = fast_create(Profile) 93 profile = fast_create(Profile)
85 name = 'My folder' 94 name = 'My folder'
86 folder = fast_create(Folder, :name => name, :profile_id => profile.id) 95 folder = fast_create(Folder, :name => name, :profile_id => profile.id)
87 confirm_message = "Are you sure that you want to remove the folder \"#{name}\"? Note that all the items inside it will also be removed!" 96 confirm_message = "Are you sure that you want to remove the folder \"#{name}\"? Note that all the items inside it will also be removed!"
88 - expects(:button_without_text).with(:delete, 'Delete', {:action => 'destroy', :id => folder.id}, :method => :post, :confirm => confirm_message) 97 + expects(:link_to).with('Delete', {:action => 'destroy', :id => folder.id}, :method => :post, :confirm => confirm_message, :class => 'button with-text icon-delete', :title => nil)
89 98
90 result = display_delete_button(folder) 99 result = display_delete_button(folder)
91 end 100 end
92 101
93 should 'display delete_button to article' do 102 should 'display delete_button to article' do
  103 + @controller = ApplicationController.new
  104 + @plugins.stubs(:dispatch).returns([])
94 profile = fast_create(Profile) 105 profile = fast_create(Profile)
95 name = 'My article' 106 name = 'My article'
96 article = fast_create(TinyMceArticle, :name => name, :profile_id => profile.id) 107 article = fast_create(TinyMceArticle, :name => name, :profile_id => profile.id)
97 confirm_message = "Are you sure that you want to remove the item \"#{name}\"?" 108 confirm_message = "Are you sure that you want to remove the item \"#{name}\"?"
98 - expects(:button_without_text).with(:delete, 'Delete', {:action => 'destroy', :id => article.id}, :method => :post, :confirm => confirm_message) 109 + expects(:link_to).with('Delete', {:action => 'destroy', :id => article.id}, :method => :post, :confirm => confirm_message, :class => 'button with-text icon-delete', :title => nil)
99 110
100 result = display_delete_button(article) 111 result = display_delete_button(article)
101 end 112 end
102 113
  114 + def link_to(text, *args); puts text; puts args.inspect; text; end
  115 +
103 end 116 end
104 117
105 module RssFeedHelper 118 module RssFeedHelper
test/unit/colorbox_helper_test.rb
@@ -27,4 +27,10 @@ class ColorboxHelperTest &lt; ActiveSupport::TestCase @@ -27,4 +27,10 @@ class ColorboxHelperTest &lt; ActiveSupport::TestCase
27 assert_equal '[button]', colorbox_button('type', 'label', { :action => 'popup'}) 27 assert_equal '[button]', colorbox_button('type', 'label', { :action => 'popup'})
28 end 28 end
29 29
  30 + should 'provide colorbox_icon_button' do
  31 + expects(:icon_button).with('type', 'label', { :action => 'popup'}, has_entries({ :class => 'colorbox' })).returns('[button]')
  32 +
  33 + assert_equal '[button]', colorbox_icon_button('type', 'label', { :action => 'popup'})
  34 + end
  35 +
30 end 36 end
test/unit/comment_test.rb
@@ -555,6 +555,14 @@ class CommentTest &lt; ActiveSupport::TestCase @@ -555,6 +555,14 @@ class CommentTest &lt; ActiveSupport::TestCase
555 assert_equal 'bar', c.referrer 555 assert_equal 'bar', c.referrer
556 end 556 end
557 557
  558 + should 'delegate environment to article' do
  559 + profile = fast_create(Profile, :environment_id => Environment.default)
  560 + article = fast_create(Article, :profile_id => profile.id)
  561 + comment = fast_create(Comment, :source_id => article.id, :source_type => 'Article')
  562 +
  563 + assert_equal Environment.default, comment.environment
  564 + end
  565 +
558 private 566 private
559 567
560 def create_comment(args = {}) 568 def create_comment(args = {})
test/unit/task_test.rb
@@ -14,7 +14,7 @@ class TaskTest &lt; ActiveSupport::TestCase @@ -14,7 +14,7 @@ class TaskTest &lt; ActiveSupport::TestCase
14 t.requestor = 1 14 t.requestor = 1
15 end 15 end
16 assert_nothing_raised do 16 assert_nothing_raised do
17 - t.requestor = Person.new 17 + t.requestor = Profile.new
18 end 18 end
19 end 19 end
20 20
test/unit/user_test.rb
@@ -403,12 +403,29 @@ class UserTest &lt; ActiveSupport::TestCase @@ -403,12 +403,29 @@ class UserTest &lt; ActiveSupport::TestCase
403 assert_equal 'Test User', user.name 403 assert_equal 'Test User', user.name
404 end 404 end
405 405
406 - should 'respond name with login, if there is no person related' do 406 + should 'respond name with login, if there is no person related and name defined' do
407 user = create_user('testuser') 407 user = create_user('testuser')
408 user.person = nil 408 user.person = nil
  409 + user.name = nil
409 assert_equal 'testuser', user.name 410 assert_equal 'testuser', user.name
410 end 411 end
411 412
  413 + should 'respond name with user name attribute' do
  414 + user = create_user('testuser')
  415 + user.person = nil
  416 + user.name = 'Another User'
  417 + user.login = 'Login User'
  418 + assert_equal 'Another User', user.name
  419 + end
  420 +
  421 + should 'respond name with related person name although user name attribute is defined' do
  422 + user = create_user('testuser')
  423 + user.person.name = 'Person Name'
  424 + user.name = 'Another User'
  425 + user.login = 'Login User'
  426 + assert_equal 'Person Name', user.name
  427 + end
  428 +
412 should 'have activation code' do 429 should 'have activation code' do
413 user = create_user('testuser') 430 user = create_user('testuser')
414 assert_respond_to user, :activation_code 431 assert_respond_to user, :activation_code
@@ -430,6 +447,13 @@ class UserTest &lt; ActiveSupport::TestCase @@ -430,6 +447,13 @@ class UserTest &lt; ActiveSupport::TestCase
430 assert_equal 'pending@activation.com', ActionMailer::Base.deliveries.last['to'].to_s 447 assert_equal 'pending@activation.com', ActionMailer::Base.deliveries.last['to'].to_s
431 end 448 end
432 449
  450 + should 'not try to deliver email to template users' do
  451 + Person.any_instance.stubs(:is_template?).returns(true)
  452 + assert_no_difference ActionMailer::Base.deliveries, :size do
  453 + new_user
  454 + end
  455 + end
  456 +
433 should 'not mass assign activated at' do 457 should 'not mass assign activated at' do
434 user = User.new :activated_at => 5.days.ago 458 user = User.new :activated_at => 5.days.ago
435 assert_nil user.activated_at 459 assert_nil user.activated_at
@@ -492,6 +516,12 @@ class UserTest &lt; ActiveSupport::TestCase @@ -492,6 +516,12 @@ class UserTest &lt; ActiveSupport::TestCase
492 assert !user.activate 516 assert !user.activate
493 end 517 end
494 518
  519 + should 'be able to skip the password requirement' do
  520 + user = User.new(:login => 'quire', :email => 'quire@example.com')
  521 + user.not_require_password!
  522 + assert user.save!
  523 + end
  524 +
495 protected 525 protected
496 def new_user(options = {}) 526 def new_user(options = {})
497 user = User.new({ :login => 'quire', :email => 'quire@example.com', :password => 'quire', :password_confirmation => 'quire' }.merge(options)) 527 user = User.new({ :login => 'quire', :email => 'quire@example.com', :password => 'quire', :password_confirmation => 'quire' }.merge(options))