droom 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE +20 -0
- data/README.md +23 -0
- data/Rakefile +27 -0
- data/app/assets/images/droom/arrows.png +0 -0
- data/app/assets/images/droom/big_icons.png +0 -0
- data/app/assets/images/droom/blueblob.png +0 -0
- data/app/assets/images/droom/close.png +0 -0
- data/app/assets/images/droom/cross.png +0 -0
- data/app/assets/images/droom/download.png +0 -0
- data/app/assets/images/droom/greyblob.png +0 -0
- data/app/assets/images/droom/help/goodreader.jpg +0 -0
- data/app/assets/images/droom/ical.png +0 -0
- data/app/assets/images/droom/icons.png +0 -0
- data/app/assets/images/droom/medium_icons.png +0 -0
- data/app/assets/images/droom/medium_object_icons.png +0 -0
- data/app/assets/images/droom/minimonth.png +0 -0
- data/app/assets/images/droom/minisymbols.png +0 -0
- data/app/assets/images/droom/object_icons.png +0 -0
- data/app/assets/images/droom/pinkblob.png +0 -0
- data/app/assets/images/droom/place_busy.png +0 -0
- data/app/assets/images/droom/place_quiet.png +0 -0
- data/app/assets/images/droom/plus_bullet.png +0 -0
- data/app/assets/images/droom/search.png +0 -0
- data/app/assets/images/droom/setup.png +0 -0
- data/app/assets/images/droom/small_go.png +0 -0
- data/app/assets/images/droom/smallbullet.png +0 -0
- data/app/assets/images/droom/smallspinner.gif +0 -0
- data/app/assets/images/droom/spinner.gif +0 -0
- data/app/assets/images/droom/spr_toolbar_icons_r2.png +0 -0
- data/app/assets/images/droom/tablesort.png +0 -0
- data/app/assets/images/droom/tick.png +0 -0
- data/app/assets/images/droom/tinysymbols.png +0 -0
- data/app/assets/images/droom/twister.png +0 -0
- data/app/assets/images/droom/vcard.png +0 -0
- data/app/assets/images/droom/venue_bullet.png +0 -0
- data/app/assets/javascripts/droom.js.coffee +88 -0
- data/app/assets/javascripts/droom/calendar.js.coffee +121 -0
- data/app/assets/javascripts/droom/drag_sort.js.coffee +22 -0
- data/app/assets/javascripts/droom/forms.js.coffee +746 -0
- data/app/assets/javascripts/droom/lib/extensions.js.coffee +60 -0
- data/app/assets/javascripts/droom/lib/jquery.animate-colors.js +109 -0
- data/app/assets/javascripts/droom/lib/jquery.cookie.js +71 -0
- data/app/assets/javascripts/droom/lib/jquery.sortable.js +97 -0
- data/app/assets/javascripts/droom/lib/kalendae.js +1692 -0
- data/app/assets/javascripts/droom/lib/modernizr.js +4 -0
- data/app/assets/javascripts/droom/lib/parser_rules/advanced.js +553 -0
- data/app/assets/javascripts/droom/lib/parser_rules/simple.js +32 -0
- data/app/assets/javascripts/droom/lib/wysihtml5.js +9550 -0
- data/app/assets/javascripts/droom/map.js.coffee +123 -0
- data/app/assets/javascripts/droom/sort.js.coffee +126 -0
- data/app/assets/javascripts/droom/suggester.js.coffee +230 -0
- data/app/assets/stylesheets/droom.css.sass +1151 -0
- data/app/assets/stylesheets/lib/_kalendae.css.sass +142 -0
- data/app/assets/stylesheets/lib/_toolbar.css.sass +192 -0
- data/app/controllers/droom/dashboard_controller.rb +27 -0
- data/app/controllers/droom/document_attachments_controller.rb +19 -0
- data/app/controllers/droom/documents_controller.rb +116 -0
- data/app/controllers/droom/engine_controller.rb +43 -0
- data/app/controllers/droom/events_controller.rb +120 -0
- data/app/controllers/droom/group_invitations_controller.rb +47 -0
- data/app/controllers/droom/groups_controller.rb +67 -0
- data/app/controllers/droom/invitations_controller.rb +43 -0
- data/app/controllers/droom/memberships_controller.rb +47 -0
- data/app/controllers/droom/pages_controller.rb +61 -0
- data/app/controllers/droom/people_controller.rb +92 -0
- data/app/controllers/droom/suggestions_controller.rb +58 -0
- data/app/controllers/droom/venues_controller.rb +39 -0
- data/app/helpers/droom/droom_helper.rb +74 -0
- data/app/models/droom/agenda_category.rb +8 -0
- data/app/models/droom/category.rb +27 -0
- data/app/models/droom/document.rb +110 -0
- data/app/models/droom/document_attachment.rb +71 -0
- data/app/models/droom/document_link.rb +31 -0
- data/app/models/droom/event.rb +409 -0
- data/app/models/droom/event_set.rb +6 -0
- data/app/models/droom/group.rb +66 -0
- data/app/models/droom/group_invitation.rb +23 -0
- data/app/models/droom/invitation.rb +30 -0
- data/app/models/droom/membership.rb +27 -0
- data/app/models/droom/page.rb +26 -0
- data/app/models/droom/person.rb +302 -0
- data/app/models/droom/personal_document.rb +98 -0
- data/app/models/droom/recurrence_rule.rb +82 -0
- data/app/models/droom/venue.rb +125 -0
- data/app/views/droom/dashboard/_marginalia.html.haml +3 -0
- data/app/views/droom/dashboard/_my_future_events.html.haml +9 -0
- data/app/views/droom/dashboard/_my_group_documents.html.haml +6 -0
- data/app/views/droom/dashboard/_my_past_events.haml +6 -0
- data/app/views/droom/dashboard/index.html.haml +5 -0
- data/app/views/droom/documents/_created.html.haml +2 -0
- data/app/views/droom/documents/_document.html.haml +14 -0
- data/app/views/droom/documents/_document_line.html.haml +2 -0
- data/app/views/droom/documents/_documents_list.html.haml +31 -0
- data/app/views/droom/documents/_documents_table.html.haml +13 -0
- data/app/views/droom/documents/_event_document_form.html.haml +15 -0
- data/app/views/droom/documents/_form.html.haml +22 -0
- data/app/views/droom/documents/_listing.html.haml +8 -0
- data/app/views/droom/documents/_suggested.html.haml +9 -0
- data/app/views/droom/documents/_table_document.html.haml +31 -0
- data/app/views/droom/documents/edit.html.haml +1 -0
- data/app/views/droom/documents/index.html.haml +29 -0
- data/app/views/droom/documents/new.html.haml +1 -0
- data/app/views/droom/errors/bang.html.haml +12 -0
- data/app/views/droom/errors/not_allowed.html.haml +12 -0
- data/app/views/droom/errors/not_found.html.haml +12 -0
- data/app/views/droom/events/_attachment.html.haml +1 -0
- data/app/views/droom/events/_attachment_list.html.haml +4 -0
- data/app/views/droom/events/_calendar.html.haml +54 -0
- data/app/views/droom/events/_created.html.haml +2 -0
- data/app/views/droom/events/_event.html.haml +77 -0
- data/app/views/droom/events/_event_line.html.haml +13 -0
- data/app/views/droom/events/_events.html.haml +2 -0
- data/app/views/droom/events/_form.html.haml +35 -0
- data/app/views/droom/events/_invitations.html.haml +20 -0
- data/app/views/droom/events/_other_page_parts.html.haml +0 -0
- data/app/views/droom/events/_popup_event.html.haml +6 -0
- data/app/views/droom/events/_suggested.html.haml +12 -0
- data/app/views/droom/events/_views.html.haml +7 -0
- data/app/views/droom/events/edit.html.haml +1 -0
- data/app/views/droom/events/index.html.haml +12 -0
- data/app/views/droom/events/index.rss.builder +20 -0
- data/app/views/droom/events/new.html.haml +1 -0
- data/app/views/droom/events/show.html.haml +4 -0
- data/app/views/droom/group_invitations/_attending_groups.html.haml +8 -0
- data/app/views/droom/group_invitations/_created.html.haml +3 -0
- data/app/views/droom/group_invitations/_form.html.haml +4 -0
- data/app/views/droom/group_invitations/new.html.haml +1 -0
- data/app/views/droom/groups/_created.html.haml +3 -0
- data/app/views/droom/groups/_form.html.haml +12 -0
- data/app/views/droom/groups/_group.html.haml +11 -0
- data/app/views/droom/groups/_groups.html.haml +3 -0
- data/app/views/droom/groups/edit.html.haml +1 -0
- data/app/views/droom/groups/index.html.haml +7 -0
- data/app/views/droom/groups/show.html.haml +13 -0
- data/app/views/droom/invitations/_attending_people.html.haml +8 -0
- data/app/views/droom/invitations/_created.html.haml +3 -0
- data/app/views/droom/invitations/_form.html.haml +5 -0
- data/app/views/droom/invitations/new.html.haml +1 -0
- data/app/views/droom/memberships/_button.html.haml +11 -0
- data/app/views/droom/memberships/_created.html.haml +4 -0
- data/app/views/droom/memberships/_form.html.haml +7 -0
- data/app/views/droom/memberships/_member.html.haml +33 -0
- data/app/views/droom/memberships/_memberships.html.haml +4 -0
- data/app/views/droom/pages/_contents.html.haml +10 -0
- data/app/views/droom/pages/_form.html.haml +36 -0
- data/app/views/droom/pages/_full_page.html.haml +17 -0
- data/app/views/droom/pages/_page.html.haml +5 -0
- data/app/views/droom/pages/_pages.html.haml +2 -0
- data/app/views/droom/pages/admin.html.haml +24 -0
- data/app/views/droom/pages/edit.html.haml +1 -0
- data/app/views/droom/pages/index.html.haml +10 -0
- data/app/views/droom/pages/new.html.haml +4 -0
- data/app/views/droom/pages/show.html.haml +5 -0
- data/app/views/droom/people/_created.html.haml +6 -0
- data/app/views/droom/people/_form.html.haml +40 -0
- data/app/views/droom/people/_people.html.haml +29 -0
- data/app/views/droom/people/_person.html.haml +34 -0
- data/app/views/droom/people/_suggested.html.haml +9 -0
- data/app/views/droom/people/edit.html.haml +1 -0
- data/app/views/droom/people/index.html.haml +11 -0
- data/app/views/droom/people/new.html.haml +1 -0
- data/app/views/droom/people/show.html.haml +1 -0
- data/app/views/droom/shared/_calendar_and_search.html.haml +2 -0
- data/app/views/droom/shared/_calendar_holder.haml +3 -0
- data/app/views/droom/shared/_controls.html.haml +8 -0
- data/app/views/droom/shared/_navigation.html.haml +8 -0
- data/app/views/droom/shared/_search_form.html.haml +4 -0
- data/app/views/droom/shared/_suggestions.html.haml +11 -0
- data/app/views/droom/shared/_toolbar.html.haml +17 -0
- data/app/views/droom/venues/_suggested.html.haml +6 -0
- data/app/views/droom/venues/index.html.haml +6 -0
- data/app/views/droom/venues/show.html.haml +21 -0
- data/app/views/layouts/droom/application.html.haml +36 -0
- data/config/initializers/dav.rb +2 -0
- data/config/initializers/paperclip.rb +26 -0
- data/config/initializers/snail.rb +2 -0
- data/config/locales/en.yml +191 -0
- data/config/routes.rb +51 -0
- data/db/migrate/20120910075016_create_droom_data.rb +134 -0
- data/db/migrate/20120917095804_agenda_sections.rb +13 -0
- data/db/migrate/20120918121352_add_postal_address_to_people.rb +10 -0
- data/db/migrate/20121009075049_give_groups_descriptions.rb +5 -0
- data/db/migrate/20121009105244_more_names.rb +5 -0
- data/db/migrate/20121009145944_event_agenda_sections.rb +13 -0
- data/db/migrate/20121011091230_create_group_invitations.rb +10 -0
- data/db/migrate/20121012144720_give_people_positions.rb +5 -0
- data/db/migrate/20121012154558_help_pages.rb +13 -0
- data/db/migrate/20121012163201_category_slugs.rb +5 -0
- data/db/migrate/20121101160102_document_links.rb +14 -0
- data/db/migrate/20121101181247_people_visibility.rb +7 -0
- data/db/migrate/20121102094738_shy_people.rb +6 -0
- data/db/migrate/20121102095856_visibility_defaults.rb +8 -0
- data/lib/droom.rb +73 -0
- data/lib/droom/dav_resource.rb +36 -0
- data/lib/droom/engine.rb +8 -0
- data/lib/droom/helpers.rb +25 -0
- data/lib/droom/monkeys.rb +15 -0
- data/lib/droom/renderers.rb +13 -0
- data/lib/droom/validators.rb +5 -0
- data/lib/droom/version.rb +3 -0
- data/lib/generators/droom/dashboard/dashboard_generator.rb +16 -0
- data/lib/generators/droom/install/USAGE +9 -0
- data/lib/generators/droom/install/install_generator.rb +15 -0
- data/lib/generators/droom/install/templates/droom_initializer.rb +6 -0
- data/lib/generators/droom/views/views_generator.rb +9 -0
- data/lib/tasks/droom_tasks.rake +4 -0
- metadata +635 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module Droom
|
|
2
|
+
class SuggestionsController < Droom::EngineController
|
|
3
|
+
respond_to :json, :js
|
|
4
|
+
before_filter :authenticate_user!
|
|
5
|
+
before_filter :get_current_person
|
|
6
|
+
before_filter :get_classes
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
fragment = params[:term]
|
|
10
|
+
max = params[:limit] || 10
|
|
11
|
+
|
|
12
|
+
if @types.include?('event') && span = Chronic.parse(fragment, :guess => false)
|
|
13
|
+
@suggestions = Droom::Event.falling_within(span).visible_to(@current_person)
|
|
14
|
+
|
|
15
|
+
else
|
|
16
|
+
@suggestions = @klasses.collect {|klass|
|
|
17
|
+
klass.constantize.visible_to(@current_person).name_matching(fragment).limit(max)
|
|
18
|
+
}.flatten.sort_by(&:name).slice(0, max)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
respond_with @suggestions do |format|
|
|
22
|
+
format.json {
|
|
23
|
+
render :json => @suggestions.map { |suggestion| {
|
|
24
|
+
:type => suggestion.identifier,
|
|
25
|
+
:text => suggestion.name,
|
|
26
|
+
:id => suggestion.id
|
|
27
|
+
}}.to_json
|
|
28
|
+
}
|
|
29
|
+
format.js {
|
|
30
|
+
render :partial => "droom/shared/suggestions"
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
protected
|
|
36
|
+
|
|
37
|
+
def get_classes
|
|
38
|
+
if params[:type].blank?
|
|
39
|
+
@types = searchable_classes.keys
|
|
40
|
+
@klasses = searchable_classes.values
|
|
41
|
+
else
|
|
42
|
+
@types = searchable_classes.keys & [params[:type]].flatten
|
|
43
|
+
@klasses = searchable_classes.values_at(*@types)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def searchable_classes
|
|
48
|
+
{
|
|
49
|
+
"event" => "Droom::Event",
|
|
50
|
+
"person" => "Droom::Person",
|
|
51
|
+
"document" => "Droom::Document",
|
|
52
|
+
"group" => "Droom::Group",
|
|
53
|
+
"venue" => "Droom::Venue"
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Droom
|
|
2
|
+
class VenuesController < Droom::EngineController
|
|
3
|
+
respond_to :json, :html
|
|
4
|
+
|
|
5
|
+
before_filter :authenticate_user!
|
|
6
|
+
before_filter :get_current_person
|
|
7
|
+
before_filter :get_venues, :only => ["index"]
|
|
8
|
+
before_filter :get_venue, :only => [:show, :update]
|
|
9
|
+
|
|
10
|
+
def index
|
|
11
|
+
respond_with @venues do |format|
|
|
12
|
+
format.json {
|
|
13
|
+
render :json => @venues.to_json(:person => @person)
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def show
|
|
19
|
+
respond_with @venue
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def update
|
|
23
|
+
@venue.update_attributes(params[:venue])
|
|
24
|
+
respond_with @venue
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
protected
|
|
28
|
+
|
|
29
|
+
def get_venues
|
|
30
|
+
@venues = Venue.all
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def get_venue
|
|
34
|
+
@venue = Venue.find(params[:id])
|
|
35
|
+
@events = @venue.events.visible_to(@current_person).future_and_current
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module Droom
|
|
2
|
+
module DroomHelper
|
|
3
|
+
|
|
4
|
+
def nav_link_to(name, url, options={})
|
|
5
|
+
options[:class] ||= ""
|
|
6
|
+
options[:class] << "here" if (request.path == url) || (request.path =~ /^#{url}/ && url != "/")
|
|
7
|
+
link_to name, url, options
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def month_header_for(date)
|
|
11
|
+
content_tag('h3', l(date, :format => :month_header))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def pagination_summary(collection, options = {})
|
|
15
|
+
entry_name = options[:entry_name] || (collection.empty?? 'entry' : collection.first.class.name.underscore.sub('_', ' '))
|
|
16
|
+
summary = if collection.num_pages < 2
|
|
17
|
+
case collection.total_count
|
|
18
|
+
when 0; "No #{entry_name.pluralize} found"
|
|
19
|
+
when 1; "Displaying <strong>1</strong> #{entry_name}:"
|
|
20
|
+
else; "Displaying <strong>all #{collection.total_count}</strong> #{entry_name.pluralize}:"
|
|
21
|
+
end
|
|
22
|
+
else
|
|
23
|
+
offset = (collection.current_page - 1) * collection.limit_value
|
|
24
|
+
%{Displaying <strong>%d - %d</strong> of <strong>%d</strong> #{entry_name.pluralize}: } % [
|
|
25
|
+
offset + 1,
|
|
26
|
+
offset + collection.length,
|
|
27
|
+
collection.total_count
|
|
28
|
+
]
|
|
29
|
+
end
|
|
30
|
+
summary.html_safe
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# This will apply cloud-weighting to any list of items.
|
|
34
|
+
# They must have a 'weight' attribute
|
|
35
|
+
# and be ready to accept a 'cloud_size' attribute.
|
|
36
|
+
|
|
37
|
+
def cloud(these, threshold=0, biggest=3.0, smallest=1.3)
|
|
38
|
+
counts = these.map{|t| t.weight.to_i}.compact
|
|
39
|
+
if counts.any?
|
|
40
|
+
max = counts.max
|
|
41
|
+
min = counts.min
|
|
42
|
+
if max == min
|
|
43
|
+
these.each do |this|
|
|
44
|
+
this.cloud_size = sprintf("%.2f", biggest/2 + smallest/2)
|
|
45
|
+
end
|
|
46
|
+
else
|
|
47
|
+
steepness = Math.log(max - (min-1))/(biggest - smallest)
|
|
48
|
+
these.each do |this|
|
|
49
|
+
offset = Math.log(this.weight.to_i - (min-1))/steepness
|
|
50
|
+
this.cloud_size = sprintf("%.2f", smallest + offset)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
if block_given?
|
|
54
|
+
these.each do |this|
|
|
55
|
+
yield this
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def url_for_month(date)
|
|
62
|
+
calendar_url(:year => date.year, :month => date.month)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def url_for_date(date)
|
|
66
|
+
calendar_url(:year => date.year, :month => date.month, :mday => date.day)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def day_names
|
|
70
|
+
["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Droom
|
|
2
|
+
class Category < ActiveRecord::Base
|
|
3
|
+
attr_accessible :name, :description, :slug
|
|
4
|
+
|
|
5
|
+
belongs_to :created_by, :class_name => 'User'
|
|
6
|
+
has_many :document_attachments
|
|
7
|
+
|
|
8
|
+
before_validation :check_slug
|
|
9
|
+
validates :slug, :presence => true, :uniqueness => true
|
|
10
|
+
|
|
11
|
+
default_scope order("droom_categories.name ASC")
|
|
12
|
+
|
|
13
|
+
# *for_selection* returns a set of [name, id] pairs suitable for use as select options.
|
|
14
|
+
def self.for_selection
|
|
15
|
+
categories = self.all.map{|c| [c.name, c.id] }
|
|
16
|
+
categories.unshift(['', ''])
|
|
17
|
+
categories
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
protected
|
|
21
|
+
|
|
22
|
+
def check_slug
|
|
23
|
+
ensure_presence_and_uniqueness_of(:slug, name.parameterize)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
module Droom
|
|
2
|
+
class Document < ActiveRecord::Base
|
|
3
|
+
attr_accessible :name, :file, :description, :attachment_category_id, :event_id
|
|
4
|
+
|
|
5
|
+
# attachment_category and event_id are used on document creation to create an associated attachment
|
|
6
|
+
# this is a temporary shortcut
|
|
7
|
+
attr_accessor :old_version, :attachment_category_id, :event_id
|
|
8
|
+
|
|
9
|
+
belongs_to :created_by, :class_name => 'User'
|
|
10
|
+
|
|
11
|
+
has_many :document_attachments, :dependent => :destroy
|
|
12
|
+
has_many :document_links, :through => :document_attachments
|
|
13
|
+
has_many :people, :through => :document_links
|
|
14
|
+
|
|
15
|
+
has_attached_file :file
|
|
16
|
+
|
|
17
|
+
before_save :set_version
|
|
18
|
+
validates :file, :presence => true
|
|
19
|
+
|
|
20
|
+
scope :all_private, where("secret = 1")
|
|
21
|
+
scope :not_private, where("secret <> 1")
|
|
22
|
+
scope :all_public, where("public = 1 AND secret <> 1")
|
|
23
|
+
scope :not_public, where("public <> 1 OR secret = 1)")
|
|
24
|
+
|
|
25
|
+
scope :visible_to, lambda { |person|
|
|
26
|
+
if person
|
|
27
|
+
select('droom_documents.*')
|
|
28
|
+
.joins('LEFT OUTER JOIN droom_document_attachments ON droom_documents.id = droom_document_attachments.document_id')
|
|
29
|
+
.joins('LEFT OUTER JOIN droom_document_links ON droom_document_attachments.id = droom_document_links.document_attachment_id')
|
|
30
|
+
.where(["(droom_documents.public = 1 OR droom_document_links.person_id = ?)", person.id])
|
|
31
|
+
.group('droom_documents.id')
|
|
32
|
+
else
|
|
33
|
+
all_public
|
|
34
|
+
end
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
scope :name_matching, lambda { |fragment|
|
|
38
|
+
fragment = "%#{fragment}%"
|
|
39
|
+
where('droom_documents.name like ?', fragment)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
scope :attached_to_these_groups, lambda { |groups|
|
|
43
|
+
placeholders = groups.map{'?'}.join(',')
|
|
44
|
+
select('droom_documents.*')
|
|
45
|
+
.joins('INNER JOIN droom_document_attachments ON droom_documents.id = droom_document_attachments.document_id AND droom_document_attachments.attachee_type = "Droom::Group"')
|
|
46
|
+
.where(["droom_document_attachments.attachee_id IN(#{placeholders})", *groups.map(&:id)])
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
scope :with_latest_event,
|
|
50
|
+
select('droom_documents.*, droom_categories.name AS category_name, droom_events.id AS latest_event_id, droom_events.name AS latest_event_name')
|
|
51
|
+
.joins('LEFT OUTER JOIN droom_document_attachments AS dda ON droom_documents.id = dda.document_id
|
|
52
|
+
LEFT OUTER JOIN droom_categories ON dda.category_id = droom_categories.id
|
|
53
|
+
LEFT OUTER JOIN droom_events ON dda.attachee_id = droom_events.id AND dda.attachee_type = "Droom::Event"')
|
|
54
|
+
.group('droom_documents.id')
|
|
55
|
+
|
|
56
|
+
# so that we can apply the joined finders above to an existing object
|
|
57
|
+
#
|
|
58
|
+
scope :this_document, lambda { |doc|
|
|
59
|
+
where(["droom_documents.id = ?", doc.id])
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
scope :by_date, order("droom_documents.updated_at DESC, droom_documents.created_at DESC")
|
|
63
|
+
|
|
64
|
+
def identifier
|
|
65
|
+
'document'
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def file_ok?
|
|
69
|
+
file.exists?
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def changed_since_creation?
|
|
73
|
+
file_updated_at > created_at
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def attachment_category_id=(id)
|
|
77
|
+
attach_to(Droom::Event.find(event_id), {:category_id => id})
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def attach_to(attachee, attributes={})
|
|
81
|
+
save!
|
|
82
|
+
document_attachments.create(attributes.merge(:attachee => attachee))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def detach_from(attachee)
|
|
86
|
+
document_attachments.attached_to(attachee).destroy_all
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def file_extension
|
|
90
|
+
if file_file_name
|
|
91
|
+
File.extname(file_file_name).sub(/^\./, '')
|
|
92
|
+
else
|
|
93
|
+
""
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def with_event
|
|
98
|
+
self.class.this_document(self).with_latest_event.first
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
protected
|
|
102
|
+
|
|
103
|
+
def set_version
|
|
104
|
+
if file.dirty?
|
|
105
|
+
self.version = (version || 0) + 1
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Droom
|
|
2
|
+
class DocumentAttachment < ActiveRecord::Base
|
|
3
|
+
attr_accessible :attachee, :document, :agenda_section, :category_id
|
|
4
|
+
|
|
5
|
+
belongs_to :document
|
|
6
|
+
belongs_to :attachee, :polymorphic => true
|
|
7
|
+
belongs_to :category
|
|
8
|
+
belongs_to :created_by, :class_name => "User"
|
|
9
|
+
|
|
10
|
+
has_many :document_links, :dependent => :destroy
|
|
11
|
+
has_many :people, :through => :document_links
|
|
12
|
+
|
|
13
|
+
after_destroy :remove_private_document_if_unattached
|
|
14
|
+
after_create :link_people
|
|
15
|
+
|
|
16
|
+
default_scope order("(CASE WHEN droom_document_attachments.category_id IS NULL THEN 0 ELSE 1 END), droom_documents.updated_at ASC, droom_documents.created_at ASC").includes(:document)
|
|
17
|
+
|
|
18
|
+
scope :unfiled, where("category_id IS NULL")
|
|
19
|
+
|
|
20
|
+
scope :attached_to, lambda { |attachee|
|
|
21
|
+
where(["droom_document_attachments.attachee_type = :class AND droom_document_attachments.attachee_id = :id", :class => attachee.class.to_s, :id => attachee.id])
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
scope :to_event, lambda { |event|
|
|
25
|
+
where(["droom_document_attachments.attachee_type = 'Droom::Event' AND droom_document_attachments.attachee_id = ?", event.id])
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
scope :to_group, lambda { |group|
|
|
29
|
+
where(["droom_document_attachments.attachee_type = 'Droom::Group' AND droom_document_attachments.attachee_id = ?", group.id])
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
scope :to_events, lambda { |events|
|
|
33
|
+
placeholders = events.map{'?'}.join(',')
|
|
34
|
+
ids = events.map(&:id)
|
|
35
|
+
where(["droom_document_attachments.attachee_type = 'Droom::Event' AND droom_document_attachments.attachee_id IN (#{placeholders})", *ids])
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
scope :to_groups, lambda { |groups|
|
|
39
|
+
placeholders = groups.map{'?'}.join(',')
|
|
40
|
+
ids = groups.map(&:id)
|
|
41
|
+
where(["droom_document_attachments.attachee_type = 'Droom::Group' AND droom_document_attachments.attachee_id IN (#{placeholders})", *ids])
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def slug
|
|
45
|
+
if attachee
|
|
46
|
+
attachee.slug
|
|
47
|
+
else
|
|
48
|
+
'Unattached'
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def category_name
|
|
53
|
+
category ? category.name : "uncategorised"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
protected
|
|
57
|
+
|
|
58
|
+
def remove_private_document_if_unattached
|
|
59
|
+
unless document.public? || document.document_attachments.count > 0
|
|
60
|
+
document.destroy
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Upon creation we create document links to all the people who are newly entitled to see the document.
|
|
65
|
+
# Deletion takes care of itself, as document_links are :dependent => :destroy.
|
|
66
|
+
#
|
|
67
|
+
def link_people
|
|
68
|
+
people << attachee.people if attachee
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# the links between a person and document are too various and too extended to make them
|
|
2
|
+
# easy to retrieve in a single query. Instead we maintain an index of person-document links.
|
|
3
|
+
|
|
4
|
+
module Droom
|
|
5
|
+
class DocumentLink < ActiveRecord::Base
|
|
6
|
+
attr_accessible :person, :document_attachment
|
|
7
|
+
belongs_to :person
|
|
8
|
+
belongs_to :document_attachment
|
|
9
|
+
has_one :personal_document, :dependent => :destroy
|
|
10
|
+
|
|
11
|
+
delegate :slug, :category, :to => :document_attachment
|
|
12
|
+
|
|
13
|
+
def document
|
|
14
|
+
document_attachment.document
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def document=(doc)
|
|
18
|
+
create_document_attachment(:document => doc)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def ensure_personal_document
|
|
22
|
+
create_personal_document unless personal_document
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.repair
|
|
26
|
+
Droom::Person.each do |person|
|
|
27
|
+
person.repair_document_links
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
require 'open-uri'
|
|
2
|
+
require 'zip/zip'
|
|
3
|
+
require 'uuidtools'
|
|
4
|
+
require 'chronic'
|
|
5
|
+
require 'ri_cal'
|
|
6
|
+
|
|
7
|
+
module Droom
|
|
8
|
+
class Event < ActiveRecord::Base
|
|
9
|
+
attr_accessible :start, :finish, :name, :description, :event_set_id, :created_by_id, :uuid, :all_day, :master_id, :url, :start_date, :start_time, :finish_date, :finish_time, :venue, :private, :public, :venue_name
|
|
10
|
+
|
|
11
|
+
belongs_to :created_by, :class_name => 'User'
|
|
12
|
+
|
|
13
|
+
has_many :invitations, :dependent => :destroy
|
|
14
|
+
has_many :people, :through => :invitations
|
|
15
|
+
|
|
16
|
+
has_many :group_invitations, :dependent => :destroy
|
|
17
|
+
has_many :groups, :through => :group_invitations
|
|
18
|
+
|
|
19
|
+
has_many :document_attachments, :as => :attachee, :dependent => :destroy
|
|
20
|
+
has_many :documents, :through => :document_attachments
|
|
21
|
+
|
|
22
|
+
has_many :agenda_categories, :dependent => :destroy
|
|
23
|
+
has_many :categories, :through => :agenda_categories
|
|
24
|
+
|
|
25
|
+
belongs_to :venue
|
|
26
|
+
accepts_nested_attributes_for :venue
|
|
27
|
+
|
|
28
|
+
belongs_to :event_set
|
|
29
|
+
accepts_nested_attributes_for :event_set
|
|
30
|
+
|
|
31
|
+
belongs_to :master, :class_name => 'Event'
|
|
32
|
+
has_many :occurrences, :class_name => 'Event', :foreign_key => 'master_id', :dependent => :destroy
|
|
33
|
+
has_many :recurrence_rules, :dependent => :destroy, :conditions => {:active => true}
|
|
34
|
+
accepts_nested_attributes_for :recurrence_rules, :allow_destroy => true
|
|
35
|
+
|
|
36
|
+
validates :start, :presence => true, :date => true
|
|
37
|
+
validates :finish, :date => {:after => :start, :allow_nil => true}
|
|
38
|
+
validates :uuid, :presence => true, :uniqueness => true
|
|
39
|
+
validates :name, :presence => true
|
|
40
|
+
|
|
41
|
+
before_validation :set_uuid
|
|
42
|
+
before_save :check_slug
|
|
43
|
+
after_save :update_occurrences
|
|
44
|
+
|
|
45
|
+
scope :primary, where("master_id IS NULL")
|
|
46
|
+
scope :recurrent, where(:conditions => "master_id IS NOT NULL")
|
|
47
|
+
default_scope order('start ASC').includes(:venue)
|
|
48
|
+
|
|
49
|
+
## Event retrieval in various ways
|
|
50
|
+
#
|
|
51
|
+
scope :visible_to, lambda { |person|
|
|
52
|
+
if person
|
|
53
|
+
select('droom_events.*')
|
|
54
|
+
.joins('LEFT OUTER JOIN droom_invitations ON droom_events.id = droom_invitations.event_id')
|
|
55
|
+
.where(["(droom_events.public = 1 OR droom_invitations.person_id = ?)", person.id])
|
|
56
|
+
.group('droom_events.id')
|
|
57
|
+
else
|
|
58
|
+
all_public
|
|
59
|
+
end
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
scope :after, lambda { |datetime| # datetime. eg calendar.occurrences.after(Time.now)
|
|
63
|
+
where(['start > ?', datetime])
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
scope :before, lambda { |datetime| # datetime. eg calendar.occurrences.before(Time.now)
|
|
67
|
+
where(['start < :date AND (finish IS NULL or finish < :date)', :date => datetime])
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
scope :between, lambda { |start, finish| # datetimable objects. eg. Event.between(reader.last_login, Time.now)
|
|
71
|
+
where(['start > :start AND start < :finish AND (finish IS NULL or finish < :finish)', :start => start, :finish => finish])
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
scope :future_and_current, lambda {
|
|
75
|
+
where(['(finish > :now) OR (finish IS NULL AND start > :now)', :now => Time.now])
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
scope :unfinished, lambda { |start| # datetimable object.
|
|
79
|
+
where(['start < :start AND finish > :start', :start => start])
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
scope :by_finish, order("finish ASC")
|
|
83
|
+
|
|
84
|
+
scope :coincident_with, lambda { |start, finish| # datetimable objects.
|
|
85
|
+
where(['(start < :finish AND finish > :start) OR (finish IS NULL AND start > :start AND start < :finish)', {:start => start, :finish => finish}])
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
scope :limited_to, lambda { |limit|
|
|
89
|
+
limit(limit)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
scope :at_venue, lambda { |venue| # EventVenue object
|
|
93
|
+
where(["venue_id = ?", venue.id])
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
scope :except_these_uuids, lambda { |uuids| # array of uuid strings
|
|
97
|
+
placeholders = uuids.map{'?'}.join(',')
|
|
98
|
+
where(["uuid NOT IN (#{placeholders})", *uuids])
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
scope :without_invitations_to, lambda { |person| # Person object
|
|
102
|
+
select("droom_events.*")
|
|
103
|
+
.joins("LEFT OUTER JOIN droom_invitations ON droom_events.id = droom_invitations.event_id AND droom_invitations.person_id = #{sanitize(person.id)}")
|
|
104
|
+
.group("droom_events.id")
|
|
105
|
+
.having("COUNT(droom_invitations.id) = 0")
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
scope :with_documents,
|
|
109
|
+
select("droom_events.*")
|
|
110
|
+
.joins("INNER JOIN droom_document_attachments ON droom_events.id = droom_document_attachments.attachee_id AND droom_document_attachments.attachee_type = 'Droom::Event'")
|
|
111
|
+
.group("droom_events.id")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
scope :all_private, where("private = 1 OR private = 't'")
|
|
115
|
+
scope :not_private, where("private = 0 OR private = 'f'")
|
|
116
|
+
scope :all_public, where("public = 1 OR public = 't'")
|
|
117
|
+
scope :not_public, where("public = 0 OR public = 'f'")
|
|
118
|
+
|
|
119
|
+
scope :name_matching, lambda { |fragment|
|
|
120
|
+
fragment = "%#{fragment}%"
|
|
121
|
+
where('droom_events.name like ?', fragment)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# All of these class methods also return scopes.
|
|
125
|
+
#
|
|
126
|
+
def self.in_the_last(period) # seconds. eg calendar.occurrences.in_the_last(1.week)
|
|
127
|
+
finish = Time.now
|
|
128
|
+
start = finish - period
|
|
129
|
+
between(start, finish)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def self.in_year(year) # just a number. eg calendar.occurrences.in_year(2010)
|
|
133
|
+
start = DateTime.civil(year)
|
|
134
|
+
finish = start + 1.year
|
|
135
|
+
between(start, finish)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def self.in_month(year, month) # numbers. eg calendar.occurrences.in_month(2010, 12)
|
|
139
|
+
start = DateTime.civil(year, month)
|
|
140
|
+
finish = start + 1.month
|
|
141
|
+
between(start, finish)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def self.in_week(year, week) # numbers, with a commercial week: eg calendar.occurrences.in_week(2010, 35)
|
|
145
|
+
start = DateTime.commercial(year, week)
|
|
146
|
+
finish = start + 1.week
|
|
147
|
+
between(start, finish)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def self.on_day (year, month, day) # numbers: eg calendar.occurrences.on_day(2010, 12, 12)
|
|
151
|
+
start = DateTime.civil(year, month, day)
|
|
152
|
+
finish = start + 1.day
|
|
153
|
+
between(start, finish)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def self.in_span(span) # Chronic::Span
|
|
157
|
+
between(span.begin, span.end)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def self.falling_within(span) # Chronic::Span
|
|
161
|
+
coincident_with(span.begin, span.end)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def self.future
|
|
165
|
+
after(Time.now)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def self.past
|
|
169
|
+
before(Time.now)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
## Instance methods
|
|
173
|
+
#
|
|
174
|
+
def invite(person)
|
|
175
|
+
self.people << person
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def attach(doc)
|
|
179
|
+
self.documents << doc
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def identifier
|
|
183
|
+
'event'
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# We store the start and end points of the event as a single DateTime value to make comparison simple.
|
|
187
|
+
# The setters for date and time are overridden to pass strings through chronic's natural language parser
|
|
188
|
+
# and to treat numbers as epoch seconds. These should all work as you'd expect:
|
|
189
|
+
#
|
|
190
|
+
# event.start = "Tuesday at 11pm"
|
|
191
|
+
# event.start = "12/12/1969 at 10pm"
|
|
192
|
+
# event.start = "1347354111"
|
|
193
|
+
# event.start = Time.now + 1.hour
|
|
194
|
+
#
|
|
195
|
+
def start=(value)
|
|
196
|
+
write_attribute :start, parse_date(value)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def finish=(value)
|
|
200
|
+
write_attribute :finish, parse_date(value)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# For interface purposes we often want to separate date and time parts. These getters will return the
|
|
204
|
+
# corresponding Date or Time object.
|
|
205
|
+
#
|
|
206
|
+
# The `time_of_day` gem makes time handling a bit more intuitive by concealing the date part of a Time object.
|
|
207
|
+
#
|
|
208
|
+
def start_time
|
|
209
|
+
start.time_of_day if start
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def start_date
|
|
213
|
+
start.to_date if start
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def finish_time
|
|
217
|
+
finish.time_of_day if finish
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def finish_date
|
|
221
|
+
finish.to_date if finish
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# And these setters will adjust the current value so that its date or time part corresponds to the given
|
|
225
|
+
# value. The value is passed through the same parsing mechanism as above, so:
|
|
226
|
+
#
|
|
227
|
+
# event.start = "Tuesday at 11pm" -> next Tuesday at 11pm
|
|
228
|
+
# event.start_time = "8pm" -> next Tuesday at 8pm
|
|
229
|
+
# event.start_date = "Wednesday" -> next Wednesday at 8pm
|
|
230
|
+
# event.start_date = "26 February 2016" -> 26/2/16 at 8pm
|
|
231
|
+
# event.start_time = "18:00" -> 26/2/16 at 6pm
|
|
232
|
+
#
|
|
233
|
+
# If the time is set before the date, we default to that time today. Times default to 00:00 in the usual way.
|
|
234
|
+
#
|
|
235
|
+
def start_time=(value)
|
|
236
|
+
self.start = (start_date || Date.today).to_time + parse_date(value).seconds_since_midnight
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def start_date=(value)
|
|
240
|
+
self.start = parse_date(value).to_date# + start_time
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def finish_time=(value)
|
|
244
|
+
self.finish = (finish_date || start_date || Date.today).to_time + parse_date(value).seconds_since_midnight
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def finish_date=(value)
|
|
248
|
+
self.finish = parse_date(value).to_date# + finish_time
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def duration
|
|
252
|
+
if finish
|
|
253
|
+
finish - start
|
|
254
|
+
else
|
|
255
|
+
0
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def venue_name
|
|
260
|
+
venue.name if venue
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def venue_name=(name)
|
|
264
|
+
self.venue = Droom::Venue.find_or_create_by_name(name)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Agenda sections are global, and we don't want to have to manage the extra workload of associating a section with an event.
|
|
268
|
+
# This call retrieves all the agenda sections that are associated with any of our document attachments, and is suitable for looping
|
|
269
|
+
# on a template to display attachments per section.
|
|
270
|
+
#
|
|
271
|
+
def agenda_sections
|
|
272
|
+
Droom::AgendaSection.associated_with_event(self)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# and this sorts our attachment list into agenda section buckets so that we don't have to go back to the database for each section.
|
|
276
|
+
def attachments_by_category
|
|
277
|
+
cats = {}
|
|
278
|
+
document_attachments.each_with_object({}) do |att, hash|
|
|
279
|
+
key = att.category_name
|
|
280
|
+
cats[key] ||= []
|
|
281
|
+
cats[key].push(att)
|
|
282
|
+
end
|
|
283
|
+
categories.each do |category|
|
|
284
|
+
key = category.name
|
|
285
|
+
cats[key] ||= []
|
|
286
|
+
end
|
|
287
|
+
cats
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def categories_for_selection
|
|
291
|
+
cats = categories.map{|c| [c.name, c.id] }
|
|
292
|
+
cats.unshift(['', ''])
|
|
293
|
+
cats
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def visible_to?(user_or_person)
|
|
297
|
+
return true if self.public?
|
|
298
|
+
return false unless user_or_person
|
|
299
|
+
return true if user_or_person.admin?
|
|
300
|
+
return true if user_or_person.person.invited_to?(self)
|
|
301
|
+
return false if self.private?
|
|
302
|
+
return true
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def one_day?
|
|
306
|
+
all_day? && within_day?
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def within_day?
|
|
310
|
+
(!finish || start.to.jd == finish.to.jd || finish == start + 1.day)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def continuing?
|
|
314
|
+
finish && start < Time.now && finish > Time.now
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def finished?
|
|
318
|
+
start < Time.now && (!finish || finish < Time.now)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def recurs?
|
|
322
|
+
master || occurrences.any?
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def recurrence
|
|
326
|
+
recurrence_rules.first.to_s
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def add_recurrence(rule)
|
|
330
|
+
self.recurrence_rules << Droom::RecurrenceRule.from(rule)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def documents_zipped
|
|
334
|
+
if self.documents.any?
|
|
335
|
+
tempfile = Tempfile.new("droom-temp-#{slug}-#{Time.now}.zip")
|
|
336
|
+
Zip::ZipOutputStream.open(tempfile.path) do |z|
|
|
337
|
+
self.documents.each do |doc|
|
|
338
|
+
z.add(doc.file_file_name, open(doc.file.url))
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
tempfile
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def to_rical
|
|
346
|
+
RiCal.Event do |cal_event|
|
|
347
|
+
cal_event.uid = uuid
|
|
348
|
+
cal_event.summary = name
|
|
349
|
+
cal_event.description = description if description
|
|
350
|
+
cal_event.dtstart = (all_day? ? start_date : start) if start
|
|
351
|
+
cal_event.dtend = (all_day? ? finish_date : finish) if finish
|
|
352
|
+
cal_event.url = url if url
|
|
353
|
+
cal_event.rrules = recurrence_rules.map(&:to_rical) if recurrence_rules.any?
|
|
354
|
+
cal_event.location = venue.name if venue
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def to_ics
|
|
359
|
+
to_rical.to_s
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def as_json(options={})
|
|
363
|
+
json = super
|
|
364
|
+
json[:datestring] = I18n.l start, :format => :natural_with_date
|
|
365
|
+
json
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
protected
|
|
369
|
+
|
|
370
|
+
def check_slug
|
|
371
|
+
ensure_presence_and_uniqueness_of(:slug, "#{start.strftime("%Y %m %d")} #{name}".parameterize)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def set_uuid
|
|
375
|
+
self.uuid = UUIDTools::UUID.timestamp_create.to_s if uuid.blank?
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# doesn't yet observe exceptions
|
|
379
|
+
def update_occurrences
|
|
380
|
+
occurrences.destroy_all
|
|
381
|
+
if recurrence_rules.any?
|
|
382
|
+
recurrence_horizon = Time.now + 10.years
|
|
383
|
+
to_rical.occurrences(:before => recurrence_horizon).each do |occ|
|
|
384
|
+
occurrences.create!({
|
|
385
|
+
:name => self.name,
|
|
386
|
+
:url => self.url,
|
|
387
|
+
:description => self.description,
|
|
388
|
+
:venue => self.venue,
|
|
389
|
+
:start => occ.dtstart,
|
|
390
|
+
:finish => occ.dtend,
|
|
391
|
+
:uuid => nil
|
|
392
|
+
}) unless occ.dtstart == self.start
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def parse_date(value)
|
|
398
|
+
case value
|
|
399
|
+
when Numeric
|
|
400
|
+
Time.at(value)
|
|
401
|
+
when String
|
|
402
|
+
Chronic.parse(value)
|
|
403
|
+
else
|
|
404
|
+
value
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
end
|
|
409
|
+
end
|