Modified: branches/event_logging/app/helpers/application_helper.rb (1882 => 1883)
--- branches/event_logging/app/helpers/application_helper.rb 2008-10-22 17:22:21 UTC (rev 1882)
+++ branches/event_logging/app/helpers/application_helper.rb 2008-10-23 12:31:57 UTC (rev 1883)
@@ -584,14 +584,15 @@
# when set to "false" will pick up also all related contributors;
# 3-4) before, after - Time objects; the news will be generated in a time slice [after..before]
# 5) limit - number of news entries to generate
- def news(contributor, restrict_contributor=true, before=Time.now, after=Time.now-DEFAULT_NEWS_TIMEFRAME, limit=DEFAULT_NEWS_COUNT)
+ # 6) current_viewer - User instance, which requests to generate the news (NIL value indicates not logged in viewer)
+ def news(contributor, restrict_contributor=true, before=Time.now, after=Time.now-DEFAULT_NEWS_TIMEFRAME, limit=DEFAULT_NEWS_COUNT, current_viewer=nil)
hash = {}
# choose news generation method:
# - new (faster / more news types), but requires ActivityLogs to be enabled
# - old (slow), but collects news items from all across the DB
if USE_EVENT_LOG && GENERATE_NEWS_FROM_EVENT_LOG
- news_array = contributor_news_from_log(contributor, before, after, restrict_contributor, false, limit)
+ news_array = contributor_news_from_log(contributor, before, after, restrict_contributor, false, limit, current_viewer)
else
news_array = contributor_news(contributor, before, after, 0, (restrict_contributor ? contributor : nil))
end
@@ -1394,7 +1395,8 @@
# 5) return_raw_events - used for recursive calls; when set to "true" a set of events from ActivityLog table is returned;
# "false" will cause the events to be interpreted and returned as news item;
# 6) limit - the number of news entries to produce (the lower the value, the more positive effect on performance this has)
- def contributor_news_from_log(contributor, before, after, contributor_news_only, return_raw_events, limit)
+ # 7) current_viewer - User instance, who is currently requests news to be generated
+ def contributor_news_from_log(contributor, before, after, contributor_news_only, return_raw_events, limit, current_viewer)
# DEBUG
# puts "=================================="
@@ -1456,7 +1458,7 @@
# than some of the contributor in the original query)
events_for_related_contributor = []
related_contributors.each do |c|
- events_for_related_contributor = contributor_news_from_log(c, before, after, true, true, limit)
+ events_for_related_contributor = contributor_news_from_log(c, before, after, true, true, limit, current_viewer)
events.concat(events_for_related_contributor) unless events_for_related_contributor.empty?
end
return events if return_raw_events
@@ -1471,7 +1473,7 @@
rtn = []
news_item = nil # contributor_news_from_log!() will return NIL if event is not to be added to the feed for the current user
events.each do |e|
- news_item = contributor_news_from_log!(e)
+ news_item = contributor_news_from_log!(e, current_viewer)
rtn << news_item unless news_item.nil?
# no need to interpret more events than is required by the method call;
@@ -1483,9 +1485,13 @@
end
- # helper method that interprets event log entries from ActivityLog table into a news entry in a format of
- # [timestamp, news_entry_string]
- def contributor_news_from_log!(log_entry)
+ # helper method that interprets an event log entry from ActivityLog table
+ # into a news entry in a format of [timestamp, news_entry_string];
+ #
+ # NIL is returned when the user viewing the news is not authorized to see
+ # current news entry OR entry in the event log is no longer valid because of
+ # some of the referenced objects missing (perhaps, they were deleted over time)
+ def contributor_news_from_log!(log_entry, current_viewer)
rtn = [] # despite this, NIL will be returned on errors / when news entry not to be shown for current user
action = ""
loggable_type = log_entry.activity_loggable_type
@@ -1494,11 +1500,12 @@
# basic check that a "culprit" still exists
# (further checks for other related objects are carried out in the respective sections for every ActivityLoggable type)
- unless ["Membership", "User"].include? loggable_type.to_s
+ unless ["Membership", "User"].include? loggable_type
# for all "activity_loggables" apart from those above, the culprit is the "user";
# if "name" helper return 'nil' - this means that the user not found
# -> news item not to be shown then
culprit_link = name(log_entry.culprit_id)
+ my_event = (log_entry.culprit_id == current_viewer.id)
return nil if culprit_link.nil?
end
@@ -1512,7 +1519,10 @@
object, object_path = evaluate_object_instance_and_path(log_entry.activity_loggable_type, log_entry.activity_loggable_id)
object_visible_name = contributable_name_from_instance(object)
- rtn << [timestamp, "#{culprit_link} #{action}d the #{link_to object_visible_name, object_path} #{model_visible_name(loggable_type.to_s)}."]
+ # need to check that current viewer allowed to see news entry about the current contributable
+ if my_event || object.authorized?("view", current_viewer)
+ rtn << [timestamp, "#{culprit_link} #{action}d the #{link_to object_visible_name, object_path} #{model_visible_name(loggable_type.to_s)}."]
+ end
rescue ActiveRecord::RecordNotFound
# do nothing, but don't display the news entry for missing contributable item
end
@@ -1522,24 +1532,39 @@
case action
when "create"
begin
+ # to be authorized to view these news entries, current viewer
+ # has to be able to view the pack & the newly added item
+ authorized = true
+
# check if the pack is still there
pack = Pack.find(log_entry.referenced_id)
pack_link = link_to(contributable_name_from_instance(pack), pack_path(pack.id))
- if loggable_type.to_s == "PackContributableEntry"
+ # eliminate further processing if not authorized to view the pack anyway
+ authorized = false unless my_event || pack.authorized?("view", current_viewer)
+ return nil unless authorized
+
+ if loggable_type == "PackContributableEntry"
# ..and check if local entry (and the item it points to) can still be found
contributable_entry = PackContributableEntry.find(log_entry.activity_loggable_id)
object, object_path = evaluate_object_instance_and_path(contributable_entry.contributable_type, contributable_entry.contributable_id)
object_path += "?version=#{contributable_entry.contributable_version}" unless contributable_entry.contributable_version.nil?
object_visible_name = contributable_name_from_instance(object)
- rtn << [timestamp, "#{culprit_link} added #{link_to object_visible_name, object_path} #{model_visible_name(object.class.to_s)} to #{pack_link} Pack."]
+ authorized = false unless my_event || object.authorized?("view", current_viewer)
+
+ if authorized
+ rtn << [timestamp, "#{culprit_link} added #{link_to object_visible_name, object_path} #{model_visible_name(object.class.to_s)} to #{pack_link} Pack."]
+ end
else
# ..and check if external link can still be found
# (title can't be blank for external items)
remote_entry = PackRemoteEntry.find(log_entry.activity_loggable_id)
- rtn << [timestamp, "#{culprit_link} added external link to #{pack_link} Pack: #{link_to remote_entry.title, remote_entry.uri, :title => tooltip_title_attrib(h(remote_entry.uri))}."]
+ # if allowed to see the pack, no further checks required as anyone can see external items
+ if authorized
+ rtn << [timestamp, "#{culprit_link} added external link to #{pack_link} Pack: #{link_to remote_entry.title, remote_entry.uri, :title => tooltip_title_attrib(h(remote_entry.uri))}."]
+ end
end
rescue ActiveRecord::RecordNotFound
# do nothing, but don't display the news entry for missing pack / item entry
@@ -1563,29 +1588,44 @@
user = User.find(user_id)
network = Network.find(network_id)
network_admin = network.owner
+
+ # check if viewing is allowed for the current viewer
+ if ["accept", "destroy"].include? action
+ # everyone should be able to see how someone joins or leaves a group
+ authorized = true
+ elsif ["invite", "request", "reject"].include? action
+ # only group admin OR the user in this action allowed to view
+ authorized = (current_viewer.id == user_id || current_viewer.id == network_admin.id)
+ else
+ # unknown action, not authorized for viewing
+ authorized = false
+ end
# if no exception at this point, can generate the news item:
# (name() and title() will accept instance instead of ID of the object - to save an extra DB access)
-
- case action
- when "invite"
- rtn << [timestamp, "#{name(network_admin)} invited #{name(user)} to join the #{title(network)} Group."]
- when "request"
- rtn << [timestamp, "#{name(user)} requested to join the #{title(network)} Group."]
- when "accept"
- rtn << [timestamp, "#{name(user)} joined the #{title(network)} Group."]
- when "reject"
- if log_entry.culprit_type == "User"
- rtn << [timestamp, "#{name(user)} rejected invitation to join the #{title(network)} Group."]
- else
- rtn << [timestamp, "#{name(network_admin)} rejected #{name(user)}'s request to join the #{title(network)} Group."]
+ if authorized
+ case action
+ when "invite"
+ rtn << [timestamp, "#{name(network_admin)} invited #{name(user)} to join the #{title(network)} Group."]
+ when "request"
+ rtn << [timestamp, "#{name(user)} requested to join the #{title(network)} Group."]
+ when "accept"
+ rtn << [timestamp, "#{name(user)} joined the #{title(network)} Group."]
+ when "reject"
+ if log_entry.culprit_type == "User"
+ rtn << [timestamp, "#{name(user)} rejected invitation to join the #{title(network)} Group."]
+ else
+ rtn << [timestamp, "#{name(network_admin)} rejected #{name(user)}'s request to join the #{title(network)} Group."]
+ end
+ when "destroy"
+ if log_entry.culprit_type == "User"
+ rtn << [timestamp, "#{name(user)} has left the #{title(network)} Group."]
+ else
+ rtn << [timestamp, "#{name(network_admin)} removed #{name(user)} from the list of members of the #{title(network)} Group."]
end
- when "destroy"
- if log_entry.culprit_type == "User"
- rtn << [timestamp, "#{name(user)} has left the #{title(network)} Group."]
- else
- rtn << [timestamp, "#{name(network_admin)} removed #{name(user)} from the list of members of the #{title(network)} Group."]
end
+ else
+ return nil
end
rescue ActiveRecord::RecordNotFound
# do nothing, but don't display the news entry for missing group / member
@@ -1595,23 +1635,41 @@
# check that "referenced" user still exists
referenced_user = User.find(log_entry.referenced_id)
+ # check if viewing is allowed for the current viewer
+ if ["accept", "destroy"].include? action
+ # everyone should be able to see how someone becomes a friend / stops being a friend with someone else
+ authorized = true
+ elsif ["create", "reject"].include? action
+ # only the "user" OR the "friend" in this action allowed to view
+ authorized = (my_event || current_viewer.id == referenced_user.id)
+ else
+ # unknown action, not authorized for viewing
+ authorized = false
+ end
+
# seems to be existent, as no exception thrown;
# name() will accept instance of the User to save an extra DB access
- case action
- when "create"
- rtn << [timestamp, "#{culprit_link} requested friendship with #{name(referenced_user)}."]
- when "accept"
- rtn << [timestamp, "#{culprit_link} and #{name(referenced_user)} became friends."]
- when "reject"
- rtn << [timestamp, "#{culprit_link} rejected a friendship request from #{name(referenced_user)}."]
- when "destroy"
- rtn << [timestamp, "#{culprit_link} removed #{name(referenced_user)} from their friends list."]
+ if authorized
+ case action
+ when "create"
+ rtn << [timestamp, "#{culprit_link} requested friendship with #{name(referenced_user)}."]
+ when "accept"
+ rtn << [timestamp, "#{culprit_link} and #{name(referenced_user)} became friends."]
+ when "reject"
+ rtn << [timestamp, "#{culprit_link} rejected a friendship request from #{name(referenced_user)}."]
+ when "destroy"
+ rtn << [timestamp, "#{culprit_link} removed #{name(referenced_user)} from their friends list."]
+ end
+ else
+ return nil
end
when "Network"
if action == "create"
begin
network = Network.find(log_entry.activity_loggable_id)
+
+ # anyone is allowed to see new groups
rtn << [timestamp, "#{culprit_link} created the #{title(network)} Group."]
rescue ActiveRecord::RecordNotFound
# do nothing, but don't display the news entry for missing group
@@ -1624,8 +1682,13 @@
ann = GroupAnnouncement.find(log_entry.activity_loggable_id)
group = Network.find(log_entry.referenced_id)
- # title can't be blank for group announcements
- rtn << [timestamp, "#{culprit_link} made an announcement \"#{link_to ann.title, group_announcement_path(log_entry.referenced_id, ann.id)}\" for group #{title(group)}."]
+ # public announcements visible to anyone; private should only be shown to group members
+ authorized = ( my_event || ann.public || (!ann.public && group.member?(current_viewer.id)) )
+
+ if authorized
+ # title can't be blank for group announcements, so it's safe to assume it's going to be present;
+ rtn << [timestamp, "#{culprit_link} made an announcement \"#{link_to ann.title, group_announcement_path(log_entry.referenced_id, ann.id)}\" for group #{title(group)}."]
+ end
rescue ActiveRecord::RecordNotFound
# do nothing, but don't display the news entry for missing group announcement / group
end
@@ -1658,11 +1721,18 @@
case shared_with_entity.class.to_s
when "User"
shared_with_name_or_title = shared_with_entity.name
+ authorized = my_event || shared_with_entity.id == current_viewer.id
when "Network"
shared_with_name_or_title = shared_with_entity.title
+ authorized = my_event || shared_with_entity.member?(current_viewer.id)
else
shared_with_name_or_title = shared_with_entity.to_s
+ authorized = false # unknown type..
end
+
+ # save extra processing if showing this news item is not allowed anyway
+ return nil unless authorized
+
shared_with = link_to(shared_with_name_or_title, shared_with_entity_path)
shared_with += (" " + model_visible_name(shared_with_entity.class.to_s)) unless (shared_with_entity.class.to_s == "User")
@@ -1677,7 +1747,9 @@
shared_what_links_arr << (link_to(object_visible_name, path) + " " + model_visible_name(object.class.to_s))
end
- rtn << [timestamp, "#{culprit_link} shared #{access_rights} #{shared_what_links_arr.join(", ")} with #{shared_with}."]
+ if authorized
+ rtn << [timestamp, "#{culprit_link} shared #{access_rights} #{shared_what_links_arr.join(", ")} with #{shared_with}."]
+ end
end
rescue ActiveRecord::RecordNotFound
# do nothing, but don't display the news entry for missing permission / network or user to which it refers to
@@ -1692,7 +1764,12 @@
object, object_path = evaluate_object_instance_and_path(log_entry.referenced_type, log_entry.referenced_id)
object_visible_name = contributable_name_from_instance(object)
- rtn << [timestamp, "#{culprit_link} rated #{link_to object_visible_name, object_path} #{model_visible_name(log_entry.referenced_type.to_s)} #{rating.rating} out of 5."]
+ # the news item to be displayed if the current viewer is allowed to see the affected contributable
+ authorized = ( my_event || object.authorized?("view", current_viewer) )
+
+ if authorized
+ rtn << [timestamp, "#{culprit_link} rated #{link_to object_visible_name, object_path} #{model_visible_name(log_entry.referenced_type.to_s)} #{rating.rating} out of 5."]
+ end
rescue ActiveRecord::RecordNotFound
# do nothing, but don't display the news entry for missing rating / object
end
@@ -1710,7 +1787,12 @@
object, object_path = evaluate_object_instance_and_path(log_entry.referenced_type, log_entry.referenced_id)
object_visible_name = contributable_name_from_instance(object)
- rtn << [timestamp, "#{culprit_link} added #{link_to object_visible_name, object_path} #{model_visible_name(log_entry.referenced_type.to_s)} to #{link_to "favourites", user_path(log_entry.culprit_id) + "/favourites"}."]
+ # the news item to be displayed if the current viewer is allowed to see the affected contributable
+ authorized = ( my_event || object.authorized?("view", current_viewer) )
+
+ if authorized
+ rtn << [timestamp, "#{culprit_link} added #{link_to object_visible_name, object_path} #{model_visible_name(log_entry.referenced_type.to_s)} to #{link_to "favourites", user_path(log_entry.culprit_id) + "/favourites"}."]
+ end
rescue ActiveRecord::RecordNotFound
# do nothing, but don't display the news entry for missing bookmark / object
end
@@ -1724,7 +1806,12 @@
object, object_path = evaluate_object_instance_and_path(log_entry.referenced_type, log_entry.referenced_id)
object_visible_name = contributable_name_from_instance(object)
- rtn << [timestamp, "#{culprit_link} #{link_to "commented", object_path + "#comment_" + comment.id.to_s} on #{link_to object_visible_name, object_path} #{model_visible_name(log_entry.referenced_type.to_s)}."]
+ # the news item to be displayed if the current viewer is allowed to see the affected contributable
+ authorized = ( my_event || object.authorized?("view", current_viewer) )
+
+ if authorized
+ rtn << [timestamp, "#{culprit_link} #{link_to "commented", object_path + "#comment_" + comment.id.to_s} on #{link_to object_visible_name, object_path} #{model_visible_name(log_entry.referenced_type.to_s)}."]
+ end
rescue ActiveRecord::RecordNotFound
# do nothing, but don't display the news entry for missing comment / object
end
@@ -1747,7 +1834,12 @@
object, object_path = evaluate_object_instance_and_path(log_entry.referenced_type, log_entry.referenced_id)
object_visible_name = contributable_name_from_instance(object)
- rtn << [timestamp, "#{culprit_link} #{link_to_unless reviewed_link.nil?, "reviewed", reviewed_link} #{link_to object_visible_name, object_path} #{model_visible_name(log_entry.referenced_type.to_s)}#{review_title_link}."]
+ # the news item to be displayed if the current viewer is allowed to see the affected contributable
+ authorized = ( my_event || object.authorized?("view", current_viewer) )
+
+ if authorized
+ rtn << [timestamp, "#{culprit_link} #{link_to_unless reviewed_link.nil?, "reviewed", reviewed_link} #{link_to object_visible_name, object_path} #{model_visible_name(log_entry.referenced_type.to_s)}#{review_title_link}."]
+ end
rescue ActiveRecord::RecordNotFound
# do nothing, but don't display the news entry for missing review / object
end
@@ -1778,7 +1870,12 @@
end
end
- rtn << [timestamp, "#{culprit_link} credited #{credited_whom} for #{link_to object_visible_name, object_path} #{model_visible_name(object.class.to_s)}."]
+ # the news item to be displayed if the current viewer is allowed to see the affected contributable
+ authorized = ( my_event || object.authorized?("view", current_viewer) )
+
+ if authorized
+ rtn << [timestamp, "#{culprit_link} credited #{credited_whom} for #{link_to object_visible_name, object_path} #{model_visible_name(object.class.to_s)}."]
+ end
rescue ActiveRecord::RecordNotFound
# do nothing, but don't display the news entry for missing creditation / object
end
@@ -1790,10 +1887,20 @@
attribution = Attribution.find(log_entry.activity_loggable_id)
attributed_what, attributed_what_path = evaluate_object_instance_and_path(attribution.attributable_type, attribution.attributable_id)
attributed_what_visible_name = contributable_name_from_instance(attributed_what)
+
+ # the news item to be displayed if the current viewer is allowed to view WHAT and TO WHAT it was attributed;
+ # if current viewer is not allowed to see the first contributable, they shouldn't see the whole news item
+ authorized = ( my_event || attributed_what.authorized?("view", current_viewer) )
+ return nil unless authorized
+
attributed_to, attributed_to_path = evaluate_object_instance_and_path(log_entry.referenced_type, log_entry.referenced_id)
attributed_to_visible_name = contributable_name_from_instance(attributed_to)
- rtn << [timestamp, "#{culprit_link} attributed #{link_to attributed_what_visible_name, attributed_what_path} #{model_visible_name(attributed_what.class.to_s)} to #{link_to attributed_to_visible_name, attributed_to_path} #{model_visible_name(attributed_to.class.to_s)}."]
+ authorized = ( my_event || attributed_to.authorized?("view", current_viewer) )
+
+ if authorized
+ rtn << [timestamp, "#{culprit_link} attributed #{link_to attributed_what_visible_name, attributed_what_path} #{model_visible_name(attributed_what.class.to_s)} to #{link_to attributed_to_visible_name, attributed_to_path} #{model_visible_name(attributed_to.class.to_s)}."]
+ end
rescue ActiveRecord::RecordNotFound
# do nothing, but don't display the news entry for missing creditation / object
end
@@ -1807,7 +1914,12 @@
object, object_path = evaluate_object_instance_and_path(log_entry.referenced_type, log_entry.referenced_id)
object_visible_name = contributable_name_from_instance(object)
- rtn << [timestamp, "#{culprit_link} tagged #{link_to object_visible_name, object_path} #{model_visible_name(log_entry.referenced_type.to_s)} with \"#{link_to tag.name, tag_path(tag.id)}\"."]
+ # the news item to be displayed if the current viewer is allowed to see the affected contributable
+ authorized = ( my_event || object.authorized?("view", current_viewer) )
+
+ if authorized
+ rtn << [timestamp, "#{culprit_link} tagged #{link_to object_visible_name, object_path} #{model_visible_name(log_entry.referenced_type.to_s)} with \"#{link_to tag.name, tag_path(tag.id)}\"."]
+ end
rescue ActiveRecord::RecordNotFound
# do nothing, but don't display the news entry for missing tagging / tag / object
end
@@ -1824,7 +1936,12 @@
object_path = workflow_path(log_entry.referenced_id)
object_visible_name = contributable_name_from_instance(object)
- rtn << [timestamp, "#{culprit_link} added #{link_to "citation", workflow_citation_path(object.id, citation.id)} for #{link_to object_visible_name, object_path} #{model_visible_name(log_entry.referenced_type.to_s)}."]
+ # the news item to be displayed if the current viewer is allowed to see the affected contributable
+ authorized = ( my_event || object.authorized?("view", current_viewer) )
+
+ if authorized
+ rtn << [timestamp, "#{culprit_link} added #{link_to "citation", workflow_citation_path(object.id, citation.id)} for #{link_to object_visible_name, object_path} #{model_visible_name(log_entry.referenced_type.to_s)}."]
+ end
rescue ActiveRecord::RecordNotFound
# do nothing, but don't display the news entry for missing citation / cited item
end
@@ -1832,6 +1949,7 @@
when "PictureSelection"
# TODO: how to check that picture selection is still valid?
+ # anyone can see picture selections of others (news only generated for friends / user's networks' members anyway)
rtn << [timestamp, "#{culprit_link} selected a new profile picture #{link_to image_tag(avatar_url(log_entry.referenced_id, 50)), user_path(log_entry.culprit_id)}."]
when "Profile"
@@ -1839,15 +1957,24 @@
# in the news we're only interested in "update" action for profiles,
# as these are created / deleted along with the user account
when "update"
- rtn << [timestamp, "#{culprit_link} has updated their #{link_to "profile", user_path(log_entry.culprit_id)}"]
+ # TODO: who can view profile updates?
+ # for now only show this to the user themself
+ if my_event
+ rtn << [timestamp, "#{culprit_link} has updated their #{link_to "profile", user_path(log_entry.culprit_id)}"]
+ end
end
when "User"
case action
when "activate"
+ # anyone can see new users joining myExperiment
rtn << [timestamp, "#{name(log_entry.activity_loggable.id)} joined #{link_to "myExperiment", "/"}."]
when "update"
- rtn << [timestamp, "#{name(log_entry.activity_loggable.id)} updated their #{link_to "account", user_path(log_entry.activity_loggable.id)} settings."]
+ # TODO: who can view account updates?
+ # for now only show this to the user themself
+ if log_entry.activity_loggable_id == current_viewer.id
+ rtn << [timestamp, "#{name(log_entry.activity_loggable_id)} updated their #{link_to "account", user_path(log_entry.activity_loggable_id)} settings."]
+ end
end
when "Announcement"
@@ -1864,6 +1991,7 @@
ann_title_link = ": \"" + link_to(ann.title, ann_path) + "\""
end
+ # anyone allowed to see site announcements
rtn << [timestamp, "#{culprit_link} made a #{link_to_unless ann_link.nil?, "site announcement", ann_link}#{ann_title_link}."]
rescue ActiveRecord::RecordNotFound
# do nothing, but don't display the news entry for missing site announcement