Commit 4ef79c2dbe9eb60b91a367e48cfb5a4bcc0f7834
Exists in
master
and in
29 other branches
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 | 114 | # plugin to the current controller being initialized. |
115 | 115 | def init_noosfero_plugins_controller_filters |
116 | 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 | 120 | self.class.send(plugin_filter[:type], plugin.class.name.underscore + '_' + plugin_filter[:method_name], (plugin_filter[:options] || {})) |
119 | 121 | self.class.send(:define_method, plugin.class.name.underscore + '_' + plugin_filter[:method_name], plugin_filter[:block]) |
120 | 122 | end | ... | ... |
app/controllers/box_organizer_controller.rb
... | ... | @@ -68,7 +68,8 @@ class BoxOrganizerController < ApplicationController |
68 | 68 | raise ArgumentError.new("Type %s is not allowed. Go away." % type) |
69 | 69 | end |
70 | 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 | 73 | @boxes = boxes_holder.boxes |
73 | 74 | render :action => 'add_block', :layout => false |
74 | 75 | end | ... | ... |
app/controllers/my_profile/profile_design_controller.rb
... | ... | @@ -5,7 +5,7 @@ class ProfileDesignController < BoxOrganizerController |
5 | 5 | protect 'edit_profile_design', :profile |
6 | 6 | |
7 | 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 | 10 | # blocks exclusive for organizations |
11 | 11 | if profile.has_members? | ... | ... |
app/controllers/public/account_controller.rb
... | ... | @@ -25,11 +25,13 @@ class AccountController < ApplicationController |
25 | 25 | |
26 | 26 | # action to perform login to the application |
27 | 27 | def login |
28 | - @user = User.new | |
29 | - @person = @user.build_person | |
30 | 28 | store_location(request.referer) unless session[:return_to] |
31 | 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 | 35 | if logged_in? |
34 | 36 | if params[:remember_me] == "1" |
35 | 37 | self.current_user.remember_me |
... | ... | @@ -41,7 +43,6 @@ class AccountController < ApplicationController |
41 | 43 | end |
42 | 44 | else |
43 | 45 | session[:notice] = _('Incorrect username or password') if redirect? |
44 | - redirect_to :back if redirect? | |
45 | 46 | end |
46 | 47 | end |
47 | 48 | |
... | ... | @@ -56,6 +57,11 @@ class AccountController < ApplicationController |
56 | 57 | |
57 | 58 | # action to register an user to the application |
58 | 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 | 65 | @invitation_code = params[:invitation_code] |
60 | 66 | begin |
61 | 67 | if params[:user] |
... | ... | @@ -125,6 +131,10 @@ class AccountController < ApplicationController |
125 | 131 | # |
126 | 132 | # Posts back. |
127 | 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 | 138 | @change_password = ChangePassword.new(params[:change_password]) |
129 | 139 | |
130 | 140 | if request.post? |
... | ... | @@ -316,4 +326,13 @@ class AccountController < ApplicationController |
316 | 326 | end |
317 | 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 | 338 | end | ... | ... |
app/controllers/public/content_viewer_controller.rb
... | ... | @@ -2,6 +2,8 @@ class ContentViewerController < ApplicationController |
2 | 2 | |
3 | 3 | needs_profile |
4 | 4 | |
5 | + before_filter :comment_author, :only => :edit_comment | |
6 | + | |
5 | 7 | helper ProfileHelper |
6 | 8 | helper TagsHelper |
7 | 9 | |
... | ... | @@ -121,6 +123,27 @@ class ContentViewerController < ApplicationController |
121 | 123 | end |
122 | 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 | 147 | protected |
125 | 148 | |
126 | 149 | def add_comment |
... | ... | @@ -198,4 +221,13 @@ class ContentViewerController < ApplicationController |
198 | 221 | end |
199 | 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 | 233 | end | ... | ... |
app/helpers/application_helper.rb
... | ... | @@ -265,9 +265,9 @@ module ApplicationHelper |
265 | 265 | |
266 | 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 | 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 | 272 | search_name = String.new(name) |
273 | 273 | if search_name.include?("/") |
... | ... | @@ -285,28 +285,17 @@ module ApplicationHelper |
285 | 285 | partial_for_class_in_view_path(klass.superclass, view_path) |
286 | 286 | end |
287 | 287 | |
288 | - def partial_for_class(klass) | |
288 | + def partial_for_class(klass, suffix=nil) | |
289 | 289 | raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' if klass.nil? |
290 | 290 | name = klass.name.underscore |
291 | 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 | 293 | return partial if partial |
294 | 294 | end |
295 | 295 | |
296 | 296 | raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' |
297 | 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 | 299 | def view_for_profile_actions(klass) |
311 | 300 | raise ArgumentError, 'No profile actions view for this class.' if klass.nil? |
312 | 301 | |
... | ... | @@ -1323,6 +1312,19 @@ module ApplicationHelper |
1323 | 1312 | end |
1324 | 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 | 1328 | def template_options(klass, field_name) |
1327 | 1329 | return '' if klass.templates.count == 0 |
1328 | 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 | 1390 | result |
1389 | 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 | 1408 | end | ... | ... |
app/helpers/boxes_helper.rb
... | ... | @@ -162,9 +162,6 @@ module BoxesHelper |
162 | 162 | # |
163 | 163 | # +box+ is always needed |
164 | 164 | def block_target(box, block = nil) |
165 | - # FIXME hardcoded | |
166 | - return '' if box.position == 1 | |
167 | - | |
168 | 165 | id = |
169 | 166 | if block.nil? |
170 | 167 | "end-of-box-#{box.id}" |
... | ... | @@ -172,14 +169,11 @@ module BoxesHelper |
172 | 169 | "before-block-#{block.id}" |
173 | 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 | 173 | end |
177 | 174 | |
178 | 175 | # makes the given block draggable so it can be moved away. |
179 | 176 | def block_handle(block) |
180 | - # FIXME hardcoded | |
181 | - return '' if block.box.position == 1 | |
182 | - | |
183 | 177 | draggable_element("block-#{block.id}", :revert => true) |
184 | 178 | end |
185 | 179 | |
... | ... | @@ -211,7 +205,7 @@ module BoxesHelper |
211 | 205 | end |
212 | 206 | |
213 | 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 | 209 | end |
216 | 210 | |
217 | 211 | if !block.main? | ... | ... |
app/helpers/cms_helper.rb
... | ... | @@ -42,13 +42,25 @@ module CmsHelper |
42 | 42 | |
43 | 43 | def display_spread_button(profile, article) |
44 | 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 | 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 | 48 | end |
49 | 49 | end |
50 | 50 | |
51 | 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 | 65 | end |
54 | 66 | end | ... | ... |
app/helpers/colorbox_helper.rb
... | ... | @@ -8,6 +8,10 @@ module ColorboxHelper |
8 | 8 | button(type, label, url, colorbox_options(options)) |
9 | 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 | 15 | # options must be an HTML options hash as passed to link_to etc. |
12 | 16 | # |
13 | 17 | # returns a new hash with colorbox class added. Keeps existing classes. | ... | ... |
app/helpers/forms_helper.rb
... | ... | @@ -155,6 +155,119 @@ module FormsHelper |
155 | 155 | return result |
156 | 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 | 271 | protected |
159 | 272 | def self.next_id_number |
160 | 273 | if defined? @@id_num | ... | ... |
app/models/box.rb
... | ... | @@ -2,4 +2,76 @@ class Box < ActiveRecord::Base |
2 | 2 | belongs_to :owner, :polymorphic => true |
3 | 3 | acts_as_list :scope => 'owner_id = #{owner_id} and owner_type = \'#{owner_type}\'' |
4 | 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 | 77 | end | ... | ... |
app/models/comment.rb
app/models/person.rb
... | ... | @@ -71,10 +71,7 @@ class Person < Profile |
71 | 71 | Friendship.find(:all, :conditions => { :friend_id => person.id}).each { |friendship| friendship.destroy } |
72 | 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 | 76 | def can_control_scrap?(scrap) |
80 | 77 | begin | ... | ... |
app/models/task.rb
... | ... | @@ -31,7 +31,7 @@ class Task < ActiveRecord::Base |
31 | 31 | end |
32 | 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 | 35 | belongs_to :target, :foreign_key => :target_id, :polymorphic => true |
36 | 36 | |
37 | 37 | validates_uniqueness_of :code, :on => :create | ... | ... |
app/models/user.rb
... | ... | @@ -30,7 +30,7 @@ class User < ActiveRecord::Base |
30 | 30 | |
31 | 31 | after_create do |user| |
32 | 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 | 34 | user.person.name ||= user.login |
35 | 35 | user.person.visible = false unless user.activated? |
36 | 36 | user.person.save! |
... | ... | @@ -88,13 +88,13 @@ class User < ActiveRecord::Base |
88 | 88 | attr_protected :activated_at |
89 | 89 | |
90 | 90 | # Virtual attribute for the unencrypted password |
91 | - attr_accessor :password | |
91 | + attr_accessor :password, :name | |
92 | 92 | |
93 | 93 | validates_presence_of :login, :email |
94 | 94 | validates_format_of :login, :with => Profile::IDENTIFIER_FORMAT, :if => (lambda {|user| !user.login.blank?}) |
95 | 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 | 98 | validates_confirmation_of :password, :if => :password_required? |
99 | 99 | validates_length_of :login, :within => 2..40, :if => (lambda {|user| !user.login.blank?}) |
100 | 100 | validates_length_of :email, :within => 3..100, :if => (lambda {|user| !user.email.blank?}) |
... | ... | @@ -228,7 +228,12 @@ class User < ActiveRecord::Base |
228 | 228 | end |
229 | 229 | |
230 | 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 | 237 | end |
233 | 238 | |
234 | 239 | def enable_email! |
... | ... | @@ -274,6 +279,11 @@ class User < ActiveRecord::Base |
274 | 279 | 15 # in minutes |
275 | 280 | end |
276 | 281 | |
282 | + | |
283 | + def not_require_password! | |
284 | + @is_password_required = false | |
285 | + end | |
286 | + | |
277 | 287 | protected |
278 | 288 | # before filter |
279 | 289 | def encrypt_password |
... | ... | @@ -282,9 +292,13 @@ class User < ActiveRecord::Base |
282 | 292 | self.password_type ||= User.system_encryption_method.to_s |
283 | 293 | self.crypted_password = encrypt(password) |
284 | 294 | end |
285 | - | |
295 | + | |
286 | 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 | 302 | end |
289 | 303 | |
290 | 304 | def make_activation_code |
... | ... | @@ -292,6 +306,7 @@ class User < ActiveRecord::Base |
292 | 306 | end |
293 | 307 | |
294 | 308 | def deliver_activation_code |
309 | + return if person.is_template? | |
295 | 310 | User::Mailer.deliver_activation_code(self) unless self.activation_code.blank? |
296 | 311 | end |
297 | 312 | ... | ... |
app/views/account/login.rhtml
... | ... | @@ -13,6 +13,8 @@ |
13 | 13 | |
14 | 14 | <%= f.password_field :password %> |
15 | 15 | |
16 | + <%= @plugins.dispatch(:login_extra_contents).collect { |content| instance_eval(&content) }.join("") %> | |
17 | + | |
16 | 18 | <% button_bar do %> |
17 | 19 | <%= submit_button( 'login', _('Log in') )%> |
18 | 20 | <% if is_thickbox %> |
... | ... | @@ -23,8 +25,13 @@ |
23 | 25 | <% end %> |
24 | 26 | |
25 | 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 | 35 | <% end %> |
29 | 36 | |
30 | 37 | </div><!-- end class="login-box" --> | ... | ... |
app/views/account/login_block.rhtml
... | ... | @@ -9,25 +9,30 @@ |
9 | 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 | 20 | <% button_bar do %> |
20 | 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 | 27 | <% end %> |
25 | 28 | |
26 | 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 | 37 | </div> |
33 | 38 | ... | ... |
... | ... | @@ -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 | 1 | <strong><%= _('Highlights') %></strong> |
2 | -<div id='edit-highlights-block'> | |
2 | +<div id='edit-highlights-block' style='width:450px'> | |
3 | 3 | <table id='highlights' class='noborder'> |
4 | 4 | <tr><th><%= _('Image') %></th><th><%= _('Address') %></th><th><%= _('Position') %></th><th><%= _('Title') %></th></tr> |
5 | 5 | <% for image in @block.images do %> | ... | ... |
app/views/box_organizer/_link_list_block.rhtml
1 | 1 | <strong><%= _('Links') %></strong> |
2 | -<div id='edit-link-list-block'> | |
2 | +<div id='edit-link-list-block' style='width:450px'> | |
3 | 3 | <table id='links' class='noborder'> |
4 | 4 | <tr><th><%= _('Icon') %></th><th><%= _('Name') %></th><th><%= _('Address') %></th></tr> |
5 | 5 | <% for link in @block.links do %> | ... | ... |
app/views/box_organizer/_raw_html_block.rhtml
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 | 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 | 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 | 1 | <h1><%= _('Editing sideboxes')%></h1> |
2 | 2 | |
3 | 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 | 5 | <%= button(:back, _('Back to control panel'), :controller => (profile.nil? ? 'admin_panel': 'profile_editor')) %> |
6 | 6 | <% end %> | ... | ... |
app/views/cms/view.rhtml
... | ... | @@ -49,13 +49,13 @@ |
49 | 49 | <%= article.class.short_description %> |
50 | 50 | </td> |
51 | 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 | 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 | 57 | <% end %> |
58 | - <%= display_delete_button(article) %> | |
58 | + <%= display_delete_button(article) if !remove_content_button(:delete) %> | |
59 | 59 | </td> |
60 | 60 | </tr> |
61 | 61 | <% end %> | ... | ... |
app/views/content_viewer/_article_toolbar.rhtml
1 | 1 | <div<%= user && " class='logged-in'" %>> |
2 | 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 | 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 | 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 | 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 | 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 | 25 | <% end %> |
26 | + <%= expirable_button @page, :spread, content, url if url %> | |
28 | 27 | <% end %> |
29 | 28 | |
30 | 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 | 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 | 38 | <% end %> |
38 | 39 | |
... | ... | @@ -40,8 +41,11 @@ |
40 | 41 | <%= button('upload-file', _('Upload files'), profile.admin_url.merge(:controller => 'cms', :action => 'upload_files', :parent_id => (@page.folder? ? @page : @page.parent))) %> |
41 | 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 | 49 | <% end %> |
46 | 50 | |
47 | 51 | <%= report_abuse(profile, :link, @page) %> | ... | ... |
app/views/content_viewer/_comment.rhtml
... | ... | @@ -63,6 +63,11 @@ |
63 | 63 | <% end %> |
64 | 64 | <% end %> |
65 | 65 | |
66 | + <% if comment.author && comment.author == user %> | |
67 | + | |
68 | + <%= expirable_comment_link comment, :edit, _('Edit'), {:action => 'edit_comment', :id => comment.id, :profile => profile.identifier} %> | |
69 | + <% end %> | |
70 | + | |
66 | 71 | <% if logged_in? && (user == profile || user == comment.author || user.has_permission?(:moderate_comments, profile)) %> |
67 | 72 | |
68 | 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 | 32 | |
33 | 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 | 47 | <% unless pass_without_comment_captcha? %> |
46 | 48 | <div id="recaptcha-container" style="display: none"> |
... | ... | @@ -59,7 +61,7 @@ function submit_comment_form(button) { |
59 | 61 | </script> |
60 | 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 | 65 | <%= hidden_field_tag(:confirm, 'false') %> |
64 | 66 | |
65 | 67 | <%= required_fields_message %> |
... | ... | @@ -84,7 +86,11 @@ function submit_comment_form(button) { |
84 | 86 | |
85 | 87 | <% button_bar do %> |
86 | 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 | 94 | <% end %> |
89 | 95 | <% end %> |
90 | 96 | ... | ... |
app/views/content_viewer/view_page.rhtml
... | ... | @@ -98,7 +98,7 @@ |
98 | 98 | </ul> |
99 | 99 | |
100 | 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 | 102 | <% end %> |
103 | 103 | </div><!-- end class="comments" --> |
104 | 104 | ... | ... |
app/views/layouts/_javascript.rhtml
... | ... | @@ -2,7 +2,8 @@ |
2 | 2 | 'jquery.noconflict.js', 'jquery.cycle.all.min.js', 'thickbox.js', 'lightbox', 'colorbox', |
3 | 3 | 'jquery-ui-1.8.2.custom.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery-validation/jquery.validate', |
4 | 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 | 8 | <% language = FastGettext.locale %> |
8 | 9 | <% %w{messages methods}.each do |type| %> | ... | ... |
app/views/layouts/application-ng.rhtml
... | ... | @@ -56,10 +56,18 @@ |
56 | 56 | <%= usermenu_logged_in %> |
57 | 57 | </span> |
58 | 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 | 63 | <div id='inlineLoginBox' style='display: none;'> |
61 | 64 | <%= render :file => 'account/login', :locals => { :is_thickbox => true } %> |
62 | 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 | 71 | </span> |
64 | 72 | <form action="/search" class="search_form" method="get" class="clean"> |
65 | 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 | 62 | </script> |
63 | 63 | <% end %> |
64 | 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 | 69 | <%= link_to_function _('Reply'), |
66 | 70 | "var f = add_comment_reply_form(this, %s); f.find('input[name=comment[title]], textarea').val(''); return false" % comment.id, |
67 | 71 | :class => 'comment-footer comment-footer-link comment-footer-hide', | ... | ... |
app/views/tasks/_task.rhtml
... | ... | @@ -50,13 +50,13 @@ |
50 | 50 | <% fields_for "tasks[#{task.id}][task]", task do |f| %> |
51 | 51 | <% if task.accept_details %> |
52 | 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 | 54 | </div> |
55 | 55 | <% end %> |
56 | 56 | |
57 | 57 | <% if task.reject_details %> |
58 | 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 | 60 | </div> |
61 | 61 | <% end %> |
62 | 62 | <% end %> | ... | ... |
config/routes.rb
... | ... | @@ -19,6 +19,7 @@ ActionController::Routing::Routes.draw do |map| |
19 | 19 | |
20 | 20 | # -- just remember to delete public/index.html. |
21 | 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 | 23 | map.connect '', :controller => "home", :conditions => { :if => lambda { |env| !Domain.hosting_profile_at(env[:host]) } } |
23 | 24 | map.home 'site/:action', :controller => 'home' |
24 | 25 | |
... | ... | @@ -121,9 +122,12 @@ ActionController::Routing::Routes.draw do |map| |
121 | 122 | # cache stuff - hack |
122 | 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 | 127 | # match requests for profiles that don't have a custom domain |
125 | 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 | 131 | # match requests for content in domains hosted for profiles |
128 | 132 | map.connect '*page', :controller => 'content_viewer', :action => 'view_page' |
129 | 133 | ... | ... |
debian/noosfero-console
lib/noosfero/plugin.rb
... | ... | @@ -12,7 +12,11 @@ class Noosfero::Plugin |
12 | 12 | end |
13 | 13 | |
14 | 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 | 20 | File.directory?(entry) |
17 | 21 | end.each do |dir| |
18 | 22 | plugin_name = File.basename(dir) |
... | ... | @@ -31,6 +35,11 @@ class Noosfero::Plugin |
31 | 35 | if plugin_dependencies_ok |
32 | 36 | Rails.configuration.controller_paths << File.join(dir, 'controllers') |
33 | 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 | 43 | [ ActiveSupport::Dependencies.load_paths, $:].each do |path| |
35 | 44 | path << File.join(dir, 'models') |
36 | 45 | path << File.join(dir, 'lib') |
... | ... | @@ -211,28 +220,6 @@ class Noosfero::Plugin |
211 | 220 | nil |
212 | 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 | 223 | # This method will be called just before a comment is saved to the database. |
237 | 224 | # |
238 | 225 | # It can modify the comment in several ways. In special, a plugin can call |
... | ... | @@ -333,4 +320,71 @@ class Noosfero::Plugin |
333 | 320 | nil |
334 | 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 | 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 | 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 | 24 | map.connect 'myprofile/:profile/plugin/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin_myprofile' |
6 | 25 | map.connect 'admin/plugin/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin_admin' |
7 | 26 | end |
8 | - | ... | ... |
plugins/custom_forms/controllers/custom_forms_plugin_myprofile_controller.rb
0 → 100644
... | ... | @@ -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 @@ |
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 @@ |
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 @@ |
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 @@ |
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 @@ |
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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | + | ... | ... |
... | ... | @@ -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 | + | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 @@ |
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/select_field.rb
0 → 100644
plugins/custom_forms/lib/custom_forms_plugin/submission.rb
0 → 100644
... | ... | @@ -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,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 | ... | ... |
... | ... | @@ -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 | +}); | ... | ... |
4.05 KB
... | ... | @@ -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 @@ |
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 @@ |
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 @@ |
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 @@ |
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 @@ |
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 @@ |
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 @@ |
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 @@ |
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 @@ |
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 @@ |
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 @@ |
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 @@ |
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 @@ |
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 @@ |
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 @@ |
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
plugins/custom_forms/views/custom_forms_plugin_myprofile/edit.html.erb
0 → 100644
plugins/custom_forms/views/custom_forms_plugin_myprofile/index.html.erb
0 → 100644
... | ... | @@ -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 @@ |
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 @@ |
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 @@ |
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
plugins/custom_forms/views/tasks/_membership_survey_accept_details.html.erb
0 → 100644
... | ... | @@ -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 @@ |
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
plugins/foo/controllers/myprofile/foo_plugin_myprofile_bar_controller.rb
0 → 100644
plugins/foo/controllers/profile/foo_plugin_profile_bar_controller.rb
0 → 100644
plugins/foo/controllers/public/foo_plugin_public_bar_controller.rb
0 → 100644
plugins/mezuro/controllers/mezuro_plugin_myprofile_controller.rb
... | ... | @@ -2,7 +2,15 @@ class MezuroPluginMyprofileController < ProfileController |
2 | 2 | |
3 | 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 | 14 | def choose_base_tool |
7 | 15 | @configuration_content = profile.articles.find(params[:id]) |
8 | 16 | @base_tools = Kalibro::BaseTool.all_names |
... | ... | @@ -11,19 +19,23 @@ class MezuroPluginMyprofileController < ProfileController |
11 | 19 | def choose_metric |
12 | 20 | @configuration_content = profile.articles.find(params[:id]) |
13 | 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 | 24 | end |
16 | - | |
25 | + | |
17 | 26 | def new_metric_configuration |
18 | 27 | @configuration_content = profile.articles.find(params[:id]) |
19 | 28 | @metric = Kalibro::BaseTool.find_by_name(params[:base_tool]).metric params[:metric_name] |
20 | 29 | end |
21 | - | |
30 | + | |
22 | 31 | def new_compound_metric_configuration |
23 | 32 | @configuration_content = profile.articles.find(params[:id]) |
24 | 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 | 37 | end |
26 | - | |
38 | + | |
27 | 39 | def edit_metric_configuration |
28 | 40 | @configuration_content = profile.articles.find(params[:id]) |
29 | 41 | @metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(@configuration_content.name, params[:metric_name]) |
... | ... | @@ -36,19 +48,29 @@ class MezuroPluginMyprofileController < ProfileController |
36 | 48 | @metric_configurations = @configuration_content.metric_configurations |
37 | 49 | @metric = @metric_configuration.metric |
38 | 50 | end |
39 | - | |
51 | + | |
40 | 52 | def create_metric_configuration |
41 | 53 | id = params[:id] |
42 | 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 | 62 | end |
46 | - | |
63 | + | |
47 | 64 | def create_compound_metric_configuration |
48 | 65 | id = params[:id] |
49 | 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 | 74 | end |
53 | 75 | |
54 | 76 | def update_metric_configuration |
... | ... | @@ -56,7 +78,11 @@ class MezuroPluginMyprofileController < ProfileController |
56 | 78 | metric_name = params[:metric_configuration][:metric][:name] |
57 | 79 | metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(@configuration_content.name, metric_name) |
58 | 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 | 86 | end |
61 | 87 | |
62 | 88 | def update_compound_metric_configuration |
... | ... | @@ -64,7 +90,11 @@ class MezuroPluginMyprofileController < ProfileController |
64 | 90 | metric_name = params[:metric_configuration][:metric][:name] |
65 | 91 | metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(@configuration_content.name, metric_name) |
66 | 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 | 98 | end |
69 | 99 | |
70 | 100 | def remove_metric_configuration |
... | ... | @@ -72,32 +102,41 @@ class MezuroPluginMyprofileController < ProfileController |
72 | 102 | metric_name = params[:metric_name] |
73 | 103 | metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(configuration_content.name, metric_name) |
74 | 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 | 110 | end |
77 | - | |
111 | + | |
78 | 112 | def new_range |
79 | 113 | @configuration_content = profile.articles.find(params[:id]) |
80 | 114 | @metric_name = params[:metric_name] |
81 | 115 | @range = Kalibro::Range.new |
116 | + @range_color = "#000000" | |
82 | 117 | end |
83 | - | |
118 | + | |
84 | 119 | def edit_range |
85 | 120 | @configuration_content = profile.articles.find(params[:id]) |
86 | 121 | @metric_name = params[:metric_name] |
87 | 122 | @beginning_id = params[:beginning_id] |
88 | 123 | metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(@configuration_content.name, @metric_name) |
89 | 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 | 126 | end |
91 | 127 | |
92 | 128 | def create_range |
93 | 129 | @configuration_content = profile.articles.find(params[:id]) |
94 | 130 | @range = Kalibro::Range.new params[:range] |
95 | 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 | 133 | metric_configuration.add_range(@range) |
98 | 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 | 138 | end |
100 | - | |
139 | + | |
101 | 140 | def update_range |
102 | 141 | configuration_content = profile.articles.find(params[:id]) |
103 | 142 | metric_name = params[:metric_name] |
... | ... | @@ -106,8 +145,11 @@ class MezuroPluginMyprofileController < ProfileController |
106 | 145 | index = metric_configuration.ranges.index{ |range| range.beginning == beginning_id.to_f || beginning_id == "-INF" } |
107 | 146 | metric_configuration.ranges[index] = Kalibro::Range.new params[:range] |
108 | 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 | 151 | end |
110 | - | |
152 | + | |
111 | 153 | def remove_range |
112 | 154 | configuration_content = profile.articles.find(params[:id]) |
113 | 155 | metric_name = params[:metric_name] |
... | ... | @@ -115,12 +157,31 @@ class MezuroPluginMyprofileController < ProfileController |
115 | 157 | metric_configuration = Kalibro::MetricConfiguration.find_by_configuration_name_and_metric_name(configuration_content.name, metric_name) |
116 | 158 | metric_configuration.ranges.delete_if { |range| range.beginning == beginning_id.to_f || beginning_id == "-INF" } |
117 | 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 | 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 | 169 | end |
124 | 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 | 187 | end | ... | ... |
plugins/mezuro/controllers/mezuro_plugin_profile_controller.rb
1 | 1 | class MezuroPluginProfileController < ProfileController |
2 | 2 | |
3 | 3 | append_view_path File.join(File.dirname(__FILE__) + '/../views') |
4 | - | |
4 | + | |
5 | + def error_page | |
6 | + @message = params[:message] | |
7 | + end | |
8 | + | |
5 | 9 | def project_state |
6 | 10 | @content = profile.articles.find(params[:id]) |
7 | 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 | 18 | end |
11 | 19 | |
12 | 20 | def project_error |
13 | 21 | @content = profile.articles.find(params[:id]) |
14 | 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 | 28 | end |
17 | 29 | |
18 | 30 | def project_result |
19 | 31 | @content = profile.articles.find(params[:id]) |
20 | 32 | date = params[:date] |
21 | 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 | 41 | def module_result |
26 | 42 | @content = profile.articles.find(params[:id]) |
27 | 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 | 51 | end |
30 | 52 | |
31 | 53 | def project_tree |
32 | 54 | @content = profile.articles.find(params[:id]) |
33 | 55 | date = params[:date] |
34 | 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 | 64 | end |
39 | 65 | |
40 | 66 | def module_metrics_history |
41 | 67 | metric_name = params[:metric_name] |
42 | 68 | @content = profile.articles.find(params[:id]) |
43 | 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 | 76 | end |
47 | 77 | |
48 | 78 | def module_grade_history |
49 | 79 | @content = profile.articles.find(params[:id]) |
50 | 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 | 89 | end |
54 | - | |
90 | + | |
55 | 91 | private |
56 | - | |
92 | + | |
57 | 93 | def filtering_metric_history(metric_name, module_history) |
58 | 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 | 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 | 99 | metric_result.metric.name.delete("() ") == metric_name |
64 | - end).first | |
100 | + end).first, metric_results_with_date.last] | |
65 | 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 | 104 | end |
69 | 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 | 120 | end | ... | ... |
plugins/mezuro/lib/kalibro/configuration.rb
... | ... | @@ -7,11 +7,7 @@ class Kalibro::Configuration < Kalibro::Model |
7 | 7 | end |
8 | 8 | |
9 | 9 | def metric_configurations |
10 | - if @metric_configuration != nil | |
11 | - @metric_configuration | |
12 | - else | |
13 | - [] | |
14 | - end | |
10 | + @metric_configuration.nil? ? [] : @metric_configuration | |
15 | 11 | end |
16 | 12 | |
17 | 13 | def metric_configurations=(metric_configurations) |
... | ... | @@ -19,19 +15,11 @@ class Kalibro::Configuration < Kalibro::Model |
19 | 15 | end |
20 | 16 | |
21 | 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 | 19 | end |
28 | 20 | |
29 | 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 | 23 | end |
36 | 24 | |
37 | 25 | def update_attributes(attributes={}) | ... | ... |
plugins/mezuro/lib/kalibro/metric_configuration.rb
... | ... | @@ -49,10 +49,14 @@ class Kalibro::MetricConfiguration < Kalibro::Model |
49 | 49 | end |
50 | 50 | |
51 | 51 | def destroy |
52 | - self.class.request("MetricConfiguration", :remove_metric_configuration, { | |
52 | + begin | |
53 | + self.class.request("MetricConfiguration", :remove_metric_configuration, { | |
53 | 54 | :configuration_name => configuration_name, |
54 | 55 | :metric_name=> metric.name |
55 | 56 | }) |
57 | + rescue Exception => exception | |
58 | + add_error exception | |
59 | + end | |
56 | 60 | end |
57 | 61 | |
58 | 62 | def to_hash | ... | ... |
plugins/mezuro/lib/kalibro/model.rb
1 | 1 | class Kalibro::Model |
2 | 2 | |
3 | + attr_accessor :errors | |
4 | + | |
3 | 5 | def initialize(attributes={}) |
4 | 6 | attributes.each { |field, value| send("#{field}=", value) if self.class.is_valid?(field) } |
7 | + @errors = [] | |
5 | 8 | end |
6 | 9 | |
7 | 10 | def to_hash(options={}) |
8 | 11 | hash = Hash.new |
9 | 12 | excepts = !options[:except].nil? ? options[:except] : [] |
13 | + excepts << :errors | |
10 | 14 | fields.each do |field| |
11 | 15 | if(!excepts.include?(field)) |
12 | 16 | field_value = send(field) |
... | ... | @@ -46,15 +50,17 @@ class Kalibro::Model |
46 | 50 | begin |
47 | 51 | self.class.request(save_endpoint, save_action, save_params) |
48 | 52 | true |
49 | - rescue Exception => error | |
50 | - false | |
53 | + rescue Exception => exception | |
54 | + add_error exception | |
55 | + false | |
51 | 56 | end |
52 | 57 | end |
53 | 58 | |
54 | 59 | def destroy |
55 | 60 | begin |
56 | 61 | self.class.request(destroy_endpoint, destroy_action, destroy_params) |
57 | - rescue Exception | |
62 | + rescue Exception => exception | |
63 | + add_error exception | |
58 | 64 | end |
59 | 65 | end |
60 | 66 | |
... | ... | @@ -123,4 +129,9 @@ class Kalibro::Model |
123 | 129 | {"#{class_name.underscore}_name".to_sym => self.name} |
124 | 130 | end |
125 | 131 | |
132 | + def add_error(exception) | |
133 | + @errors << exception | |
134 | + end | |
135 | + | |
126 | 136 | end |
137 | + | ... | ... |
plugins/mezuro/lib/kalibro/project.rb
1 | 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 | 5 | def self.all_names |
6 | 6 | request("Project", :get_project_names)[:project_name] |
... | ... | @@ -15,23 +15,35 @@ class Kalibro::Project < Kalibro::Model |
15 | 15 | end |
16 | 16 | |
17 | 17 | def error=(value) |
18 | - @error = Kalibro::Error.to_object value | |
18 | + @kalibro_error = Kalibro::Error.to_object value | |
19 | 19 | end |
20 | 20 | |
21 | 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 | 31 | end |
28 | 32 | |
29 | 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 | 39 | end |
32 | 40 | |
33 | 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 | 47 | end |
36 | 48 | |
37 | 49 | end | ... | ... |
plugins/mezuro/lib/kalibro/project_result.rb
... | ... | @@ -75,21 +75,17 @@ class Kalibro::ProjectResult < Kalibro::Model |
75 | 75 | ('%2d' % amount).sub(/\s/, '0') |
76 | 76 | end |
77 | 77 | |
78 | - def node_of(module_name) | |
78 | + def node(module_name) | |
79 | 79 | if module_name.nil? or module_name == project.name |
80 | 80 | node = source_tree |
81 | 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 | 89 | end |
94 | 90 | |
95 | 91 | private | ... | ... |
plugins/mezuro/lib/kalibro/range.rb
... | ... | @@ -34,4 +34,12 @@ class Kalibro::Range < Kalibro::Model |
34 | 34 | @grade = value.to_f |
35 | 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 | 45 | end | ... | ... |
plugins/mezuro/lib/mezuro_plugin/configuration_content.rb
... | ... | @@ -3,8 +3,8 @@ class MezuroPlugin::ConfigurationContent < Article |
3 | 3 | |
4 | 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 | 9 | def self.short_description |
10 | 10 | 'Kalibro configuration' |
... | ... | @@ -21,54 +21,60 @@ class MezuroPlugin::ConfigurationContent < Article |
21 | 21 | end |
22 | 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 | 29 | end |
29 | - @configuration | |
30 | + @kalibro_configuration | |
30 | 31 | end |
31 | 32 | |
32 | 33 | def metric_configurations |
33 | - configuration.metric_configurations | |
34 | + kalibro_configuration.metric_configurations | |
34 | 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 | 44 | end |
39 | 45 | |
40 | 46 | private |
41 | 47 | |
42 | 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 | 51 | if existing.include?(name.downcase) |
46 | 52 | errors.add_to_base("Configuration name already exists in Kalibro") |
47 | 53 | end |
48 | 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 | 59 | else |
54 | 60 | create_kalibro_configuration |
55 | 61 | end |
56 | 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 | 66 | end |
61 | 67 | |
62 | 68 | def create_kalibro_configuration |
63 | 69 | attributes = {:name => name, :description => description} |
64 | - if cloning_configuration? | |
70 | + if cloning_kalibro_configuration? | |
65 | 71 | attributes[:metric_configuration] = configuration_to_clone.metric_configurations_hash |
66 | 72 | end |
67 | 73 | Kalibro::Configuration.create attributes |
68 | 74 | end |
69 | 75 | |
70 | - def editing_configuration? | |
71 | - configuration.present? | |
76 | + def editing_kalibro_configuration? | |
77 | + kalibro_configuration.present? | |
72 | 78 | end |
73 | 79 | |
74 | 80 | def configuration_to_clone |
... | ... | @@ -76,10 +82,10 @@ class MezuroPlugin::ConfigurationContent < Article |
76 | 82 | end |
77 | 83 | |
78 | 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 | 86 | end |
81 | 87 | |
82 | - def cloning_configuration? | |
88 | + def cloning_kalibro_configuration? | |
83 | 89 | configuration_to_clone.present? |
84 | 90 | end |
85 | 91 | ... | ... |
plugins/mezuro/lib/mezuro_plugin/helpers/content_viewer_helper.rb
1 | 1 | class MezuroPlugin::Helpers::ContentViewerHelper |
2 | + | |
3 | + MAX_NUMBER_OF_LABELS = 5 | |
4 | + | |
2 | 5 | def self.format_grade(grade) |
3 | 6 | sprintf("%.2f", grade.to_f) |
4 | 7 | end |
... | ... | @@ -6,15 +9,31 @@ class MezuroPlugin::Helpers::ContentViewerHelper |
6 | 9 | def self.create_periodicity_options |
7 | 10 | [["Not Periodically", 0], ["1 day", 1], ["2 days", 2], ["Weekly", 7], ["Biweeky", 15], ["Monthly", 30]] |
8 | 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 | 30 | :title_color => 'FF0000', |
13 | 31 | :size => '600x180', |
14 | 32 | :bg => {:color => 'efefef', :type => 'stripes'}, |
15 | 33 | :line_colors => 'c4a000', |
16 | 34 | :data => values, |
17 | - :axis_with_labels => 'y', | |
35 | + :labels => labels, | |
36 | + :axis_with_labels => ['y','x'], | |
18 | 37 | :max_value => values.max, |
19 | 38 | :min_value => values.min |
20 | 39 | ) |
... | ... | @@ -25,8 +44,33 @@ class MezuroPlugin::Helpers::ContentViewerHelper |
25 | 44 | selected_option = options.find { |option| option.last == index.to_i } |
26 | 45 | selected_option.first |
27 | 46 | end |
28 | - | |
47 | + | |
29 | 48 | def self.format_name(metric_result) |
30 | 49 | metric_result.metric.name.delete("() ") |
31 | 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 | 76 | end | ... | ... |
plugins/mezuro/lib/mezuro_plugin/project_content.rb
1 | 1 | class MezuroPlugin::ProjectContent < Article |
2 | 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 | 6 | validate_on_create :validate_kalibro_project_name |
7 | 7 | validate_on_create :validate_repository_url |
... | ... | @@ -26,6 +26,7 @@ class MezuroPlugin::ProjectContent < Article |
26 | 26 | rescue Exception => error |
27 | 27 | errors.add_to_base(error.message) |
28 | 28 | end |
29 | + @project | |
29 | 30 | end |
30 | 31 | |
31 | 32 | def project_result |
... | ... | @@ -34,6 +35,7 @@ class MezuroPlugin::ProjectContent < Article |
34 | 35 | rescue Exception => error |
35 | 36 | errors.add_to_base(error.message) |
36 | 37 | end |
38 | + @project_result | |
37 | 39 | end |
38 | 40 | |
39 | 41 | def project_result_with_date(date) |
... | ... | @@ -43,6 +45,7 @@ Kalibro::ProjectResult.first_result_after(name, date) |
43 | 45 | rescue Exception => error |
44 | 46 | errors.add_to_base(error.message) |
45 | 47 | end |
48 | + @project_result | |
46 | 49 | end |
47 | 50 | |
48 | 51 | def module_result(attributes) |
... | ... | @@ -53,6 +56,7 @@ Kalibro::ProjectResult.first_result_after(name, date) |
53 | 56 | rescue Exception => error |
54 | 57 | errors.add_to_base(error.message) |
55 | 58 | end |
59 | + @module_result | |
56 | 60 | end |
57 | 61 | |
58 | 62 | def result_history(module_name) |
... | ... | @@ -73,6 +77,7 @@ Kalibro::ProjectResult.first_result_after(name, date) |
73 | 77 | existing = Kalibro::Project.all_names |
74 | 78 | rescue Exception => error |
75 | 79 | errors.add_to_base(error.message) |
80 | + existing = [] | |
76 | 81 | end |
77 | 82 | |
78 | 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 | 99 | def create_kalibro_project |
95 | 100 | Kalibro::Project.create( |
96 | 101 | :name => name, |
97 | - :license => license, | |
102 | + :license => project_license, | |
98 | 103 | :description => description, |
99 | 104 | :repository => { |
100 | 105 | :type => repository_type, |
... | ... | @@ -105,7 +110,7 @@ Kalibro::ProjectResult.first_result_after(name, date) |
105 | 110 | end |
106 | 111 | |
107 | 112 | def destroy_project_from_service |
108 | - project.destroy | |
113 | + project.destroy unless project.nil? | |
109 | 114 | end |
110 | 115 | |
111 | 116 | end | ... | ... |
... | ... | @@ -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); | ... | ... |
... | ... | @@ -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 | +} | ... | ... |
... | ... | @@ -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 @@ |
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"> </div>'), | |
37 | + palette : $('<div id="colorPicker_palette" class="colorPicker-palette" />'), | |
38 | + swatch : $('<div class="colorPicker-swatch"> </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 @@ |
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"> </div>'),palette:a('<div id="colorPicker_palette" class="colorPicker-palette" />'),swatch:a('<div class="colorPicker-swatch"> </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 | 27 | \ No newline at end of file | ... | ... |
plugins/mezuro/public/javascripts/project_content.js
... | ... | @@ -2,7 +2,6 @@ var processingTree = false; |
2 | 2 | var metricName; |
3 | 3 | jQuery(function (){ |
4 | 4 | jQuery('.source-tree-link').live("click", reloadModule); |
5 | - jQuery('[data-show]').live("click", toggle_mezuro); | |
6 | 5 | jQuery('[show-metric-history]').live("click", display_metric_history); |
7 | 6 | jQuery('[show-grade-history]').live("click", display_grade_history); |
8 | 7 | jQuery('#project_date_submit').live("click", reloadProjectWithDate); |
... | ... | @@ -16,7 +15,8 @@ function showProjectContent() { |
16 | 15 | |
17 | 16 | function display_metric_history() { |
18 | 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 | 20 | metricName = metric_name; |
21 | 21 | callAction('module_metrics_history', {module_name: module_name, metric_name: metric_name}, show_metrics); |
22 | 22 | return false; |
... | ... | @@ -24,6 +24,7 @@ function display_metric_history() { |
24 | 24 | |
25 | 25 | function display_grade_history() { |
26 | 26 | var module_name = jQuery(this).attr('data-module-name'); |
27 | + toggle_mezuro("#historical-grade"); | |
27 | 28 | callAction('module_grade_history', {module_name: module_name}, show_grades); |
28 | 29 | return false; |
29 | 30 | } |
... | ... | @@ -36,8 +37,7 @@ function show_grades(content) { |
36 | 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 | 41 | jQuery(element).toggle(); |
42 | 42 | return false; |
43 | 43 | } |
... | ... | @@ -51,23 +51,8 @@ function reloadModule(){ |
51 | 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 | 56 | return false; |
72 | 57 | } |
73 | 58 | ... | ... |
plugins/mezuro/public/javascripts/validations.js
... | ... | @@ -3,7 +3,9 @@ jQuery(function (){ |
3 | 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 | 10 | function validate_metric_configuration(){ |
9 | 11 | var code = jQuery('#metric_configuration_code').val(); |
... | ... | @@ -37,14 +39,6 @@ function IsNotInfinite(value){ |
37 | 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 | 42 | function validate_new_range_configuration(event){ |
49 | 43 | var label = jQuery("#range_label").val(); |
50 | 44 | var beginning = jQuery("#range_beginning").val(); |
... | ... | @@ -54,24 +48,20 @@ function validate_new_range_configuration(event){ |
54 | 48 | |
55 | 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 | 52 | return false; |
59 | 53 | } |
60 | 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 | 57 | return false; |
64 | 58 | } |
65 | 59 | if (parseInt(beginning) > parseInt(end)) |
66 | 60 | { |
67 | 61 | if(IsNotInfinite(beginning) && IsNotInfinite(end)){ |
68 | - alert("End must be greater than Beginning"); | |
62 | + alert("End must be greater than Beginning."); | |
69 | 63 | return false; |
70 | 64 | } |
71 | 65 | } |
72 | - if (IsNotHexadecimal(color)){ | |
73 | - alert("Color must be an hexadecimal value"); | |
74 | - return false; | |
75 | - } | |
76 | 66 | return true; |
77 | 67 | } | ... | ... |
plugins/mezuro/public/style.css
plugins/mezuro/test/fixtures/project_fixtures.rb
... | ... | @@ -28,7 +28,7 @@ class ProjectFixtures |
28 | 28 | def self.project_content |
29 | 29 | content = MezuroPlugin::ProjectContent.new |
30 | 30 | content.name = 'Qt-Calculator' |
31 | - content.license = 'GPL' | |
31 | + content.project_license = 'GPL' | |
32 | 32 | content.description = 'Calculator for Qt' |
33 | 33 | content.repository_type = RepositoryFixtures.repository_hash[:type] |
34 | 34 | content.repository_url = RepositoryFixtures.repository_hash[:address] | ... | ... |
plugins/mezuro/test/functional/mezuro_plugin_myprofile_controller_test.rb
... | ... | @@ -27,7 +27,7 @@ class MezuroPluginMyprofileControllerTest < ActionController::TestCase |
27 | 27 | |
28 | 28 | Kalibro::Configuration.expects(:all_names).returns([]) |
29 | 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 | 31 | @content.stubs(:solr_save) |
32 | 32 | @content.save |
33 | 33 | |
... | ... | @@ -39,7 +39,7 @@ class MezuroPluginMyprofileControllerTest < ActionController::TestCase |
39 | 39 | @range = RangeFixtures.range_excellent |
40 | 40 | @range_hash = RangeFixtures.range_excellent_hash |
41 | 41 | end |
42 | - | |
42 | + | |
43 | 43 | should 'test choose base tool' do |
44 | 44 | Kalibro::BaseTool.expects(:request).with("BaseTool", :get_base_tool_names).returns({:base_tool_name => @base_tool.name}) |
45 | 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 < ActionController::TestCase |
25 | 25 | @content.save |
26 | 26 | end |
27 | 27 | |
28 | - should 'test project state without error' do | |
28 | + should 'test project state without kalibro_error' do | |
29 | 29 | Kalibro::Project.expects(:request).with("Project", :get_project, :project_name => @project.name).returns({:project => @project.to_hash}) |
30 | 30 | get :project_state, :profile => @profile.identifier, :id => @content.id |
31 | 31 | assert_response 200 |
32 | 32 | assert_equal @content, assigns(:content) |
33 | 33 | end |
34 | 34 | |
35 | - should 'test project state with error' do | |
35 | + should 'test project state with kalibro_error' do | |
36 | 36 | Kalibro::Project.expects(:request).with("Project", :get_project, :project_name => @project.name).returns({:project => @project.to_hash.merge({:error => ErrorFixtures.error_hash})}) |
37 | 37 | get :project_state, :profile => @profile.identifier, :id => @content.id |
38 | 38 | assert_response 200 |
... | ... | @@ -126,7 +126,7 @@ class MezuroPluginProfileControllerTest < ActionController::TestCase |
126 | 126 | get :module_metrics_history, :profile => @profile.identifier, :id => @content.id, :module_name => @project.name, |
127 | 127 | :metric_name => @module_result.metric_result.first.metric.name.delete("() ") |
128 | 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 | 130 | assert_response 200 |
131 | 131 | end |
132 | 132 | |
... | ... | @@ -134,7 +134,7 @@ class MezuroPluginProfileControllerTest < ActionController::TestCase |
134 | 134 | Kalibro::ModuleResult.expects(:request).with("ModuleResult", :get_result_history, {:project_name => @project.name, :module_name => @project.name}).returns({:module_result => @module_result}) |
135 | 135 | get :module_grade_history, :profile => @profile.identifier, :id => @content.id, :module_name => @project.name |
136 | 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 | 138 | assert_response 200 |
139 | 139 | end |
140 | 140 | ... | ... |
plugins/mezuro/test/unit/kalibro/configuration_test.rb
... | ... | @@ -44,7 +44,9 @@ class ConfigurationTest < ActiveSupport::TestCase |
44 | 44 | should 'return nil when configuration doesnt exist' do |
45 | 45 | request_body = {:configuration_name => @configuration.name} |
46 | 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 | 50 | end |
49 | 51 | |
50 | 52 | should 'destroy configuration by name' do | ... | ... |
plugins/mezuro/test/unit/kalibro/project_result_test.rb
... | ... | @@ -69,26 +69,20 @@ class ProjectResultTest < ActiveSupport::TestCase |
69 | 69 | assert_equal '00:00:01', @project_result.formatted_analysis_time |
70 | 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 | 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 | 74 | end |
81 | 75 | |
82 | 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 | 78 | end |
85 | 79 | |
86 | 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 | 82 | end |
89 | 83 | |
90 | 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 | 86 | end |
93 | 87 | |
94 | 88 | end | ... | ... |
plugins/mezuro/test/unit/kalibro/range_test.rb
... | ... | @@ -17,4 +17,16 @@ class RangeTest < ActiveSupport::TestCase |
17 | 17 | assert_equal @hash, @range.to_hash |
18 | 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 | 32 | end | ... | ... |
plugins/mezuro/test/unit/mezuro_plugin/configuration_content_test.rb
... | ... | @@ -6,9 +6,7 @@ class ConfigurationContentTest < ActiveSupport::TestCase |
6 | 6 | |
7 | 7 | def setup |
8 | 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 | 10 | end |
13 | 11 | |
14 | 12 | should 'be an article' do |
... | ... | @@ -35,19 +33,19 @@ class ConfigurationContentTest < ActiveSupport::TestCase |
35 | 33 | |
36 | 34 | should 'get configuration from service' do |
37 | 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 | 37 | end |
40 | 38 | |
41 | 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 | 41 | @content.stubs(:solr_save) |
44 | 42 | @content.run_callbacks :after_save |
45 | 43 | end |
46 | 44 | |
47 | 45 | should 'create new configuration' do |
48 | 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 | 49 | end |
52 | 50 | |
53 | 51 | should 'clone configuration' do |
... | ... | @@ -55,25 +53,25 @@ class ConfigurationContentTest < ActiveSupport::TestCase |
55 | 53 | Kalibro::Configuration.expects(:create).with(:name => @content.name, :description => @content.description, :metric_configuration => @configuration.metric_configurations_hash) |
56 | 54 | Kalibro::Configuration.expects(:find_by_name).with(@content.name).returns(nil) |
57 | 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 | 57 | end |
60 | 58 | |
61 | 59 | should 'edit configuration' do |
62 | 60 | Kalibro::Configuration.expects(:find_by_name).with(@content.name).returns(@configuration) |
63 | 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 | 63 | end |
66 | 64 | |
67 | 65 | should 'send correct configuration to service but comunication fails' do |
68 | 66 | Kalibro::Configuration.expects(:find_by_name).with(@content.name).returns(@configuration) |
69 | 67 | @configuration.expects(:save).returns(false) |
70 | - @content.send :send_configuration_to_service | |
68 | + @content.send :send_kalibro_configuration_to_service | |
71 | 69 | end |
72 | 70 | |
73 | 71 | should 'remove configuration from service' do |
74 | 72 | Kalibro::Configuration.expects(:find_by_name).with(@content.name).returns(@configuration) |
75 | 73 | @configuration.expects(:destroy) |
76 | - @content.send :remove_configuration_from_service | |
74 | + @content.send :remove_kalibro_configuration_from_service | |
77 | 75 | end |
78 | 76 | |
79 | 77 | end | ... | ... |
plugins/mezuro/views/cms/mezuro_plugin/_configuration_content.html.erb
1 | 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 | 8 | <%= error_messages_for 'kalibro_configuration' %> |
... | ... | @@ -13,20 +10,19 @@ |
13 | 10 | <%= hidden_field_tag 'kalibro_configuration[profile_id]', profile.id %> |
14 | 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 | 16 | <%= required_fields_message %> |
21 | 17 | |
22 | 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 | 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 | 23 | end %> |
28 | 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 | 28 | <%= f.text_field :description %><br/> | ... | ... |
plugins/mezuro/views/cms/mezuro_plugin/_project_content.html.erb
1 | 1 | <h1> <%= _(MezuroPlugin::ProjectContent.short_description) %> </h1> |
2 | 2 | |
3 | 3 | <% |
4 | + @project = @article.title.nil? ? nil : @article.project | |
4 | 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 | 12 | end |
9 | 13 | %> |
10 | 14 | |
... | ... | @@ -20,18 +24,18 @@ |
20 | 24 | <%= required f.text_field(:name) %> |
21 | 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 | 31 | <%= f.text_field :description %><br/> |
26 | 32 | |
27 | -<% @repository_types = Kalibro::Repository.repository_types.sort %> | |
28 | 33 | <% @selected = (@project.nil? ? @repository_types : @project.repository.type) %> |
29 | 34 | <%= required labelled_form_field _('Repository type'), |
30 | 35 | f.select(:repository_type, @repository_types, {:selected => @selected}) %><br/> |
31 | 36 | |
32 | 37 | <%= required f.text_field(:repository_url) %><br/> |
33 | 38 | |
34 | -<% @configuration_names = Kalibro::Configuration.all_names.sort %> | |
35 | 39 | <% @selected = (@project.nil? ? @configuration_names[0] : @project.configuration_name) %> |
36 | 40 | |
37 | 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 | 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
plugins/mezuro/views/content_viewer/_project_result.rhtml
1 | 1 | <% unless @content.errors[:base].nil? %> |
2 | 2 | <%= @content.errors[:base] %> |
3 | 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 | 9 | <h4><%= _('Last Result') %></h4> |
36 | 10 | |
... | ... | @@ -48,4 +22,18 @@ |
48 | 22 | <td><%= @project_result.formatted_analysis_time %></td> |
49 | 23 | </tr> |
50 | 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 | 39 | <% end %> | ... | ... |
plugins/mezuro/views/content_viewer/show_configuration.rhtml
1 | 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 | 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 | 2 | |
3 | 3 | <% @project = @page.project %> |
4 | 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 | 6 | <h3>Warning:</h3> |
10 | 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 | 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 | 59 | <% end %> | ... | ... |
plugins/mezuro/views/mezuro_plugin_myprofile/_range_form.html.erb
... | ... | @@ -38,7 +38,7 @@ |
38 | 38 | <%= f.label :color, "(*) Color:" %> |
39 | 39 | </td> |
40 | 40 | <td> |
41 | - <%= f.text_field :color %> | |
41 | + <%= f.text_field(:color, :id => "range_color", :value => @range.mezuro_color) %> | |
42 | 42 | </td> |
43 | 43 | </tr> |
44 | 44 | <tr> |
... | ... | @@ -51,3 +51,11 @@ |
51 | 51 | </tr> |
52 | 52 | </table> |
53 | 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 | 5 | <h2><%= @configuration_content.name %> Configuration</h2> |
2 | 6 | |
3 | 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 | 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 | 5 | <h2><%= @configuration_content.name %> Configuration</h2> |
4 | 6 | |
... | ... | @@ -84,3 +86,4 @@ |
84 | 86 | <%= link_to_remote "New Range", :url => {:action =>"new_range", :controller => "mezuro_plugin_myprofile", :id => @configuration_content.id, :metric_name => @metric.name} %> |
85 | 87 | <div id="range_form" style="display:none"></div> |
86 | 88 | |
89 | + | ... | ... |
plugins/mezuro/views/mezuro_plugin_myprofile/error_page.html.erb
0 → 100644
plugins/mezuro/views/mezuro_plugin_profile/error_page.html.erb
0 → 100644
plugins/tolerance_time/controllers/tolerance_time_plugin_myprofile_controller.rb
0 → 100644
... | ... | @@ -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 @@ |
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 @@ |
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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | + | ... | ... |
... | ... | @@ -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 @@ |
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 @@ |
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 | ... | ... |
4.71 KB
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 @@ |
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 @@ |
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 @@ |
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 | 1 | .msie6 .icon-edit { background-image: url(ie6/Tango/16x16/apps/text-editor.gif) } |
2 | 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 | 5 | .msie6 .icon-close { background-image: url(ie6/Tango/16x16/actions/gtk-cancel.gif) } |
5 | 6 | .msie6 .icon-newfolder { background-image: url(ie6/Tango/16x16/actions/folder-new.gif) } |
6 | 7 | .msie6 .icon-save { background-image: url(ie6/Tango/16x16/actions/filesave.gif) } | ... | ... |
public/designs/icons/tango/style.css
... | ... | @@ -3,7 +3,8 @@ |
3 | 3 | /******************SMALL ICONS********************/ |
4 | 4 | .icon-edit { background-image: url(Tango/16x16/apps/text-editor.png) } |
5 | 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 | 8 | .icon-close { background-image: url(Tango/16x16/actions/gtk-cancel.png) } |
8 | 9 | .icon-newfolder { background-image: url(Tango/16x16/actions/folder-new.png) } |
9 | 10 | .icon-folder { background-image: url(Tango/16x16/places/folder.png) } | ... | ... |
public/designs/themes/base/style.css
... | ... | @@ -483,6 +483,17 @@ div#notice { |
483 | 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 | 497 | #content .tags-block .block-footer-content a, |
487 | 498 | #content .people-block .block-footer-content a, |
488 | 499 | #content .profile-list-block .block-footer-content a, |
... | ... | @@ -588,33 +599,51 @@ div#notice { |
588 | 599 | display: none; |
589 | 600 | } |
590 | 601 | |
602 | +#content .box-1 .link-list-block { | |
603 | + margin: 0px; | |
604 | +} | |
605 | + | |
591 | 606 | #content .link-list-block li { |
592 | 607 | background: #FFF; |
593 | 608 | padding: 0px; |
594 | 609 | margin: 5px 0px; |
595 | 610 | } |
596 | 611 | |
612 | +#content .box-1 .link-list-block li { | |
613 | + display: inline-block; | |
614 | +} | |
615 | + | |
597 | 616 | #content .link-list-block li a { |
598 | 617 | font-size: 14px; |
599 | 618 | line-height: 24px; |
600 | 619 | color: #000; |
601 | 620 | background-color: #EEE; |
602 | 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 | 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 | 635 | background-color: #cecece; |
613 | 636 | max-width: 175px; |
614 | 637 | width: 200px; |
615 | 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 | 647 | #content .link-list-block li a:hover { |
619 | 648 | background-color: #555753; |
620 | 649 | color: #FFF; |
... | ... | @@ -908,6 +937,16 @@ hr.pre-posts, hr.sep-posts { |
908 | 937 | #article-actions a.button:hover { |
909 | 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 | 951 | #addThis { |
913 | 952 | text-align: right; | ... | ... |
public/javascripts/application.js
... | ... | @@ -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 | 1394 | position: relative; |
1395 | 1395 | display: inline; |
1396 | 1396 | } |
1397 | +#content #boxes .box-1 .article-block img, | |
1397 | 1398 | #content #article .article-body img { |
1398 | 1399 | max-width: 100%; |
1399 | 1400 | height: auto; |
... | ... | @@ -1503,7 +1504,13 @@ a.button:hover, body.noosfero a.button:hover, input.button:hover, a.button.with- |
1503 | 1504 | body.noosfero a.button.with-text.icon-none, body.noosfero input.button.with-text.icon-none { |
1504 | 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 | 1514 | opacity: 0.5; |
1508 | 1515 | filter-opacity: 50%; |
1509 | 1516 | } |
... | ... | @@ -1766,6 +1773,9 @@ a.button.disabled, input.disabled { |
1766 | 1773 | width: 92px; |
1767 | 1774 | overflow: hidden; |
1768 | 1775 | } |
1776 | +.box-1 .common-profile-list-block span { | |
1777 | + width: 102px; | |
1778 | +} | |
1769 | 1779 | .common-profile-list-block .profile-image { |
1770 | 1780 | width: 92px; |
1771 | 1781 | display: table-cell; |
... | ... | @@ -3974,7 +3984,7 @@ h1#agenda-title { |
3974 | 3984 | } |
3975 | 3985 | /* * * Profile search block * * * * * * * */ |
3976 | 3986 | |
3977 | -.profile-search-block .formfield input { | |
3987 | +.profile-search-block .search-field .formfield input { | |
3978 | 3988 | width: 100%; |
3979 | 3989 | } |
3980 | 3990 | .profile-search-block .button.icon-search { |
... | ... | @@ -4330,6 +4340,14 @@ h1#agenda-title { |
4330 | 4340 | margin: 5px 0; |
4331 | 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 | 4351 | /* Categories block stuff */ |
4334 | 4352 | |
4335 | 4353 | .categories-block ul { | ... | ... |
script/quick-start
... | ... | @@ -2,12 +2,12 @@ |
2 | 2 | |
3 | 3 | say() { |
4 | 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 | 8 | complain() { |
9 | 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 | 13 | run() { | ... | ... |
test/factories.rb
... | ... | @@ -442,7 +442,7 @@ module Noosfero::Factory |
442 | 442 | |
443 | 443 | def defaults_for_comment(params = {}) |
444 | 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 | 446 | end |
447 | 447 | |
448 | 448 | ############################################### | ... | ... |
test/functional/account_controller_test.rb
... | ... | @@ -11,6 +11,10 @@ class AccountControllerTest < ActionController::TestCase |
11 | 11 | |
12 | 12 | all_fixtures |
13 | 13 | |
14 | + def teardown | |
15 | + Thread.current[:enabled_plugins] = nil | |
16 | + end | |
17 | + | |
14 | 18 | def setup |
15 | 19 | @controller = AccountController.new |
16 | 20 | @request = ActionController::TestRequest.new |
... | ... | @@ -20,24 +24,22 @@ class AccountControllerTest < ActionController::TestCase |
20 | 24 | def test_local_files_reference |
21 | 25 | assert_local_files_reference |
22 | 26 | end |
23 | - | |
27 | + | |
24 | 28 | def test_valid_xhtml |
25 | 29 | assert_valid_xhtml |
26 | 30 | end |
27 | - | |
31 | + | |
28 | 32 | def test_should_login_and_redirect |
29 | 33 | post :login, :user => {:login => 'johndoe', :password => 'test'} |
30 | 34 | assert session[:user] |
31 | 35 | assert_response :redirect |
32 | 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 | 39 | @controller.stubs(:logged_in?).returns(false) |
38 | 40 | post :login, :user => {:login => 'quire', :password => 'quire'} |
39 | 41 | |
40 | - assert_redirected_to '/bli' | |
42 | + assert session[:notice].include?('Incorrect') | |
41 | 43 | end |
42 | 44 | |
43 | 45 | should 'authenticate on the current environment' do |
... | ... | @@ -46,23 +48,11 @@ class AccountControllerTest < ActionController::TestCase |
46 | 48 | post :login, :user => { :login => 'fake', :password => 'fake' } |
47 | 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 | 51 | def test_should_fail_login_and_not_redirect |
62 | 52 | @request.env["HTTP_REFERER"] = 'bli' |
63 | 53 | post :login, :user => {:login => 'johndoe', :password => 'bad password'} |
64 | 54 | assert_nil session[:user] |
65 | - assert_response :redirect | |
55 | + assert_response :success | |
66 | 56 | end |
67 | 57 | |
68 | 58 | def test_should_allow_signup |
... | ... | @@ -120,7 +110,7 @@ class AccountControllerTest < ActionController::TestCase |
120 | 110 | |
121 | 111 | def test_shoud_save_with_acceptance_of_terms_of_use_on_signup |
122 | 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 | 114 | new_user(:terms_accepted => '1') |
125 | 115 | assert_response :success |
126 | 116 | assert_not_nil assigns(:register_pending) |
... | ... | @@ -144,7 +134,7 @@ class AccountControllerTest < ActionController::TestCase |
144 | 134 | post :login, :user => {:login => 'johndoe', :password => 'test'}, :remember_me => "0" |
145 | 135 | assert_nil @response.cookies["auth_token"] |
146 | 136 | end |
147 | - | |
137 | + | |
148 | 138 | def test_should_delete_token_on_logout |
149 | 139 | login_as :johndoe |
150 | 140 | get :logout |
... | ... | @@ -344,7 +334,7 @@ class AccountControllerTest < ActionController::TestCase |
344 | 334 | login_as(person.identifier) |
345 | 335 | |
346 | 336 | ent = fast_create(Enterprise, :name => 'test enterprise', :identifier => 'test_ent') |
347 | - | |
337 | + | |
348 | 338 | task = mock |
349 | 339 | task.expects(:enterprise).returns(ent).at_least_once |
350 | 340 | EnterpriseActivation.expects(:find_by_code).with('0123456789').returns(task).at_least_once |
... | ... | @@ -359,7 +349,7 @@ class AccountControllerTest < ActionController::TestCase |
359 | 349 | login_as(person.identifier) |
360 | 350 | |
361 | 351 | ent = fast_create(Enterprise, :name => 'test enterprise', :identifier => 'test_ent', :enabled => false) |
362 | - | |
352 | + | |
363 | 353 | task = mock |
364 | 354 | task.expects(:enterprise).returns(ent).at_least_once |
365 | 355 | EnterpriseActivation.expects(:find_by_code).with('0123456789').returns(task).at_least_once |
... | ... | @@ -555,7 +545,7 @@ class AccountControllerTest < ActionController::TestCase |
555 | 545 | login_as(person.identifier) |
556 | 546 | |
557 | 547 | env = Environment.default |
558 | - env.terms_of_use = 'some terms' | |
548 | + env.terms_of_use = 'some terms' | |
559 | 549 | env.save! |
560 | 550 | ent = fast_create(Enterprise, :name => 'test enterprise', :identifier => 'test_ent', :enabled => false) |
561 | 551 | ent.update_attribute(:foundation_year, 1998) |
... | ... | @@ -697,7 +687,6 @@ class AccountControllerTest < ActionController::TestCase |
697 | 687 | assert_nil assigns(:message) |
698 | 688 | post :login, :user => {:login => 'testuser', :password => 'test123'} |
699 | 689 | assert_nil session[:user] |
700 | - assert_redirected_to '/bli' | |
701 | 690 | end |
702 | 691 | |
703 | 692 | should 'not activate user when activation code is incorrect' do |
... | ... | @@ -707,7 +696,6 @@ class AccountControllerTest < ActionController::TestCase |
707 | 696 | assert_nil assigns(:message) |
708 | 697 | post :login, :user => {:login => 'testuser', :password => 'test123'} |
709 | 698 | assert_nil session[:user] |
710 | - assert_redirected_to '/bli' | |
711 | 699 | end |
712 | 700 | |
713 | 701 | should 'be able to upload an image' do |
... | ... | @@ -777,6 +765,122 @@ class AccountControllerTest < ActionController::TestCase |
777 | 765 | assert_tag :tag => 'strong', :content => 'Plugin2 text' |
778 | 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 | 884 | protected |
781 | 885 | def new_user(options = {}, extra_options ={}) |
782 | 886 | data = {:profile_data => person_data} | ... | ... |
test/functional/content_viewer_controller_test.rb
... | ... | @@ -1400,6 +1400,38 @@ end |
1400 | 1400 | end |
1401 | 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 | 1435 | should 'remove email from article followers when unfollow' do |
1404 | 1436 | profile = create_user('testuser').person |
1405 | 1437 | follower_email = 'john@doe.br' |
... | ... | @@ -1432,4 +1464,42 @@ end |
1432 | 1464 | assert spam.spam? |
1433 | 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 | 1505 | end | ... | ... |
test/functional/home_controller_test.rb
... | ... | @@ -6,8 +6,11 @@ class HomeController; def rescue_action(e) raise e end; end |
6 | 6 | |
7 | 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 | 14 | def setup |
12 | 15 | @controller = HomeController.new |
13 | 16 | @request = ActionController::TestRequest.new |
... | ... | @@ -93,4 +96,44 @@ all_fixtures |
93 | 96 | assert_tag :content => /Noosfero terms of use/ |
94 | 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 | 139 | end | ... | ... |
test/functional/profile_design_controller_test.rb
... | ... | @@ -5,12 +5,12 @@ class ProfileDesignController; def rescue_action(e) raise e end; end |
5 | 5 | |
6 | 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 | 9 | PERSON_BLOCKS = COMMOM_BLOCKS + [FriendsBlock, FavoriteEnterprisesBlock, CommunitiesBlock, EnterprisesBlock ] |
10 | 10 | PERSON_BLOCKS_WITH_MEMBERS = PERSON_BLOCKS + [MembersBlock] |
11 | 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 | 14 | ENTERPRISE_BLOCKS_WITH_PRODUCTS_ENABLE = ENTERPRISE_BLOCKS + [ProductsBlock] |
15 | 15 | |
16 | 16 | attr_reader :holder |
... | ... | @@ -295,17 +295,17 @@ class ProfileDesignControllerTest < ActionController::TestCase |
295 | 295 | should 'offer to create blog archives block only if has blog' do |
296 | 296 | holder.articles << Blog.new(:name => 'Blog test', :profile => holder) |
297 | 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 | 299 | end |
300 | 300 | |
301 | 301 | should 'not offer to create blog archives block if user dont have blog' do |
302 | 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 | 304 | end |
305 | 305 | |
306 | 306 | should 'offer to create feed reader block' do |
307 | 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 | 309 | end |
310 | 310 | |
311 | 311 | should 'be able to edit FeedReaderBlock' do |
... | ... | @@ -421,14 +421,14 @@ class ProfileDesignControllerTest < ActionController::TestCase |
421 | 421 | profile.stubs(:is_admin?).with(profile.environment).returns(true) |
422 | 422 | @controller.stubs(:user).returns(profile) |
423 | 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 | 425 | end |
426 | 426 | |
427 | 427 | should 'not allow normal users to add RawHTMLBlock' do |
428 | 428 | profile.stubs(:is_admin?).with(profile.environment).returns(false) |
429 | 429 | @controller.stubs(:user).returns(profile) |
430 | 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 | 432 | end |
433 | 433 | |
434 | 434 | should 'editing article block displays right selected article' do | ... | ... |
test/integration/routing_test.rb
... | ... | @@ -221,4 +221,11 @@ class RoutingTest < ActionController::IntegrationTest |
221 | 221 | assert_routing('/chat/avatar/chemical-brothers', :controller => 'chat', :action => 'avatar', :id => 'chemical-brothers') |
222 | 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 | 231 | end | ... | ... |
... | ... | @@ -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 < ActiveSupport::TestCase |
5 | 5 | include CmsHelper |
6 | 6 | include BlogHelper |
7 | 7 | include ApplicationHelper |
8 | + include ActionView::Helpers::UrlHelper | |
8 | 9 | |
9 | 10 | should 'show default options for article' do |
10 | 11 | CmsHelperTest.any_instance.stubs(:controller).returns(ActionController::Base.new) |
... | ... | @@ -47,14 +48,18 @@ class CmsHelperTest < ActiveSupport::TestCase |
47 | 48 | end |
48 | 49 | |
49 | 50 | should 'display spread button when profile is a person' do |
51 | + @controller = ApplicationController.new | |
52 | + @plugins.stubs(:dispatch).returns([]) | |
50 | 53 | profile = fast_create(Person) |
51 | 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 | 57 | result = display_spread_button(profile, article) |
55 | 58 | end |
56 | 59 | |
57 | 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 | 63 | env = fast_create(Environment) |
59 | 64 | env.expects(:portal_community).returns(true) |
60 | 65 | profile = fast_create(Community, :environment_id => env.id) |
... | ... | @@ -62,12 +67,14 @@ class CmsHelperTest < ActiveSupport::TestCase |
62 | 67 | |
63 | 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 | 72 | result = display_spread_button(profile, article) |
68 | 73 | end |
69 | 74 | |
70 | 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 | 78 | env = fast_create(Environment) |
72 | 79 | env.expects(:portal_community).returns(nil) |
73 | 80 | profile = fast_create(Community, :environment_id => env.id) |
... | ... | @@ -75,31 +82,37 @@ class CmsHelperTest < ActiveSupport::TestCase |
75 | 82 | |
76 | 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 | 87 | result = display_spread_button(profile, article) |
81 | 88 | end |
82 | 89 | |
83 | 90 | should 'display delete_button to folder' do |
91 | + @controller = ApplicationController.new | |
92 | + @plugins.stubs(:dispatch).returns([]) | |
84 | 93 | profile = fast_create(Profile) |
85 | 94 | name = 'My folder' |
86 | 95 | folder = fast_create(Folder, :name => name, :profile_id => profile.id) |
87 | 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 | 99 | result = display_delete_button(folder) |
91 | 100 | end |
92 | 101 | |
93 | 102 | should 'display delete_button to article' do |
103 | + @controller = ApplicationController.new | |
104 | + @plugins.stubs(:dispatch).returns([]) | |
94 | 105 | profile = fast_create(Profile) |
95 | 106 | name = 'My article' |
96 | 107 | article = fast_create(TinyMceArticle, :name => name, :profile_id => profile.id) |
97 | 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 | 111 | result = display_delete_button(article) |
101 | 112 | end |
102 | 113 | |
114 | + def link_to(text, *args); puts text; puts args.inspect; text; end | |
115 | + | |
103 | 116 | end |
104 | 117 | |
105 | 118 | module RssFeedHelper | ... | ... |
test/unit/colorbox_helper_test.rb
... | ... | @@ -27,4 +27,10 @@ class ColorboxHelperTest < ActiveSupport::TestCase |
27 | 27 | assert_equal '[button]', colorbox_button('type', 'label', { :action => 'popup'}) |
28 | 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 | 36 | end | ... | ... |
test/unit/comment_test.rb
... | ... | @@ -555,6 +555,14 @@ class CommentTest < ActiveSupport::TestCase |
555 | 555 | assert_equal 'bar', c.referrer |
556 | 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 | 566 | private |
559 | 567 | |
560 | 568 | def create_comment(args = {}) | ... | ... |
test/unit/task_test.rb
test/unit/user_test.rb
... | ... | @@ -403,12 +403,29 @@ class UserTest < ActiveSupport::TestCase |
403 | 403 | assert_equal 'Test User', user.name |
404 | 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 | 407 | user = create_user('testuser') |
408 | 408 | user.person = nil |
409 | + user.name = nil | |
409 | 410 | assert_equal 'testuser', user.name |
410 | 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 | 429 | should 'have activation code' do |
413 | 430 | user = create_user('testuser') |
414 | 431 | assert_respond_to user, :activation_code |
... | ... | @@ -430,6 +447,13 @@ class UserTest < ActiveSupport::TestCase |
430 | 447 | assert_equal 'pending@activation.com', ActionMailer::Base.deliveries.last['to'].to_s |
431 | 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 | 457 | should 'not mass assign activated at' do |
434 | 458 | user = User.new :activated_at => 5.days.ago |
435 | 459 | assert_nil user.activated_at |
... | ... | @@ -492,6 +516,12 @@ class UserTest < ActiveSupport::TestCase |
492 | 516 | assert !user.activate |
493 | 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 | 525 | protected |
496 | 526 | def new_user(options = {}) |
497 | 527 | user = User.new({ :login => 'quire', :email => 'quire@example.com', :password => 'quire', :password_confirmation => 'quire' }.merge(options)) | ... | ... |