Commit 180ad5296e483134f6df021d56008e75c2e2dc0e

Authored by Braulio Bhavamitra
2 parents 6c114d19 be3b210f

Merge branch 'open_graph' into 'master'

Abstract plugin to federate networks via OpenGraph

Depends on: !362 !345 !473 !482

See merge request !512
Showing 43 changed files with 1557 additions and 2 deletions   Show diff stats
app/models/article.rb
... ... @@ -8,7 +8,8 @@ class Article < ActiveRecord::Base
8 8 :accept_comments, :feed, :published, :source, :source_name,
9 9 :highlighted, :notify_comments, :display_hits, :slug,
10 10 :external_feed_builder, :display_versions, :external_link,
11   - :image_builder, :show_to_followers
  11 + :image_builder, :show_to_followers,
  12 + :author
12 13  
13 14 acts_as_having_image
14 15  
... ...
app/models/uploaded_file.rb
... ... @@ -163,4 +163,8 @@ class UploadedFile < Article
163 163 true
164 164 end
165 165  
  166 + def notifiable?
  167 + true
  168 + end
  169 +
166 170 end
... ...
lib/tasks/plugins_tests.rake
... ... @@ -169,7 +169,7 @@ def test_sequence(plugins, tasks)
169 169 failed[plugin] << task
170 170 end
171 171 end
172   - disable_plugins(plugin)
  172 + disable_plugins
173 173 end
174 174 fail_flag = false
175 175 failed.each do |plugin, tasks|
... ...
plugins/open_graph/Gemfile 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +
  2 +gem 'jsonify-rails'
  3 +
... ...
plugins/open_graph/controllers/myprofile/open_graph_plugin/myprofile_controller.rb 0 → 100644
... ... @@ -0,0 +1,47 @@
  1 +class OpenGraphPlugin::MyprofileController < MyProfileController
  2 +
  3 + protect 'edit_profile', :profile
  4 + before_filter :set_context
  5 +
  6 + def enterprise_search
  7 + scope = environment.enterprises.enabled.public
  8 + profile_search scope
  9 + end
  10 + def community_search
  11 + scope = environment.communities.public
  12 + profile_search scope
  13 + end
  14 + def friend_search
  15 + scope = profile.friends
  16 + profile_search scope
  17 + end
  18 +
  19 + def track_config
  20 + profile.update_attributes! params[:profile_data]
  21 + render partial: 'track_form', locals: {context: context, reload: true}
  22 + end
  23 +
  24 + protected
  25 +
  26 + def profile_search scope
  27 + @query = params[:query]
  28 + @profiles = scope.limit(10).order('name ASC').
  29 + where(['name ILIKE ? OR name ILIKE ? OR identifier LIKE ?', "#{@query}%", "% #{@query}%", "#{@query}%"])
  30 + render partial: 'profile_search', locals: {profiles: @profiles}
  31 + end
  32 +
  33 + def context
  34 + :open_graph
  35 + end
  36 +
  37 + def set_context
  38 + OpenGraphPlugin.context = self.context
  39 + end
  40 +
  41 + def default_url_options
  42 + # avoid rails' use_relative_controller!
  43 + {use_route: '/'}
  44 + end
  45 +
  46 +end
  47 +
... ...
plugins/open_graph/db/migrate/20141031130250_create_open_graph_plugin_tracks.rb 0 → 100644
... ... @@ -0,0 +1,36 @@
  1 +class CreateOpenGraphPluginTracks < ActiveRecord::Migration
  2 + def up
  3 + create_table :open_graph_plugin_tracks do |t|
  4 + t.string :type
  5 + t.string :context
  6 + t.boolean :enabled, default: true
  7 +
  8 + t.integer :tracker_id
  9 +
  10 + t.integer :actor_id
  11 +
  12 + t.string :action
  13 +
  14 + t.string :object_type
  15 + t.text :object_data_url
  16 + t.integer :object_data_id
  17 + t.string :object_data_type
  18 +
  19 + t.timestamps
  20 + end
  21 +
  22 + add_index :open_graph_plugin_tracks, [:type]
  23 + add_index :open_graph_plugin_tracks, [:context]
  24 + add_index :open_graph_plugin_tracks, [:type, :context]
  25 + add_index :open_graph_plugin_tracks, [:actor_id]
  26 + add_index :open_graph_plugin_tracks, [:action]
  27 + add_index :open_graph_plugin_tracks, [:object_type]
  28 + add_index :open_graph_plugin_tracks, [:enabled]
  29 + add_index :open_graph_plugin_tracks, [:object_data_url]
  30 + add_index :open_graph_plugin_tracks, [:object_data_id, :object_data_type], name: 'index_open_graph_plugin_tracks_object_data_id_type'
  31 + end
  32 +
  33 + def down
  34 + drop_table :open_graph_plugin_tracks
  35 + end
  36 +end
... ...
plugins/open_graph/install.rb 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +system "script/noosfero-plugins -q enable metadata"
  2 +
... ...
plugins/open_graph/lib/ext/article.rb 0 → 100644
... ... @@ -0,0 +1,15 @@
  1 +require_dependency 'article'
  2 +
  3 +class Article
  4 +
  5 + after_update :open_graph_scrape
  6 +
  7 + protected
  8 +
  9 + def open_graph_scrape
  10 + activity = OpenGraphPlugin::Activity.where(object_data_id: self.id, object_data_type: self.class.base_class.name).first
  11 + activity.scrape if activity
  12 + end
  13 + handle_asynchronously :open_graph_scrape
  14 +
  15 +end
... ...
plugins/open_graph/lib/ext/profile.rb 0 → 100644
... ... @@ -0,0 +1,59 @@
  1 +require_dependency 'profile'
  2 +# hate to wrte this, but without Noosfero::Plugin::Settings is loaded instead
  3 +require 'open_graph_plugin/settings'
  4 +
  5 +# attr_accessible must be defined on subclasses
  6 +Profile.descendants.each do |subclass|
  7 + subclass.class_eval do
  8 + attr_accessible :open_graph_settings
  9 +
  10 + OpenGraphPlugin::TrackConfig::Types.each do |track, klass|
  11 + klass = "OpenGraphPlugin::#{klass}".constantize
  12 + attributes = "#{klass.association}_attributes"
  13 + profile_ids = "open_graph_#{track}_profiles_ids"
  14 +
  15 + attr_accessible attributes
  16 + attr_accessible profile_ids
  17 + end
  18 + end
  19 +end
  20 +
  21 +class Profile
  22 +
  23 + def open_graph_settings attrs = {}
  24 + @open_graph_settings ||= OpenGraphPlugin::Settings.new self, attrs
  25 + attrs.each{ |a, v| @open_graph_settings.send "#{a}=", v }
  26 + @open_graph_settings
  27 + end
  28 + alias_method :open_graph_settings=, :open_graph_settings
  29 +
  30 + has_many :open_graph_tracks, class_name: 'OpenGraphPlugin::Track', source: :tracker_id, foreign_key: :tracker_id
  31 +
  32 + has_many :open_graph_activities, class_name: 'OpenGraphPlugin::Activity', source: :tracker_id, foreign_key: :tracker_id
  33 +
  34 + has_many :open_graph_track_configs, class_name: 'OpenGraphPlugin::TrackConfig', source: :tracker_id, foreign_key: :tracker_id
  35 + OpenGraphPlugin::TrackConfig::Types.each do |track, klass|
  36 + klass = "OpenGraphPlugin::#{klass}".constantize
  37 + association = klass.association
  38 + profile_ids = "open_graph_#{track}_profiles_ids"
  39 +
  40 + has_many association, class_name: klass.name, foreign_key: :tracker_id
  41 + accepts_nested_attributes_for association, allow_destroy: true, reject_if: :open_graph_reject_empty_object_type
  42 +
  43 + define_method "#{profile_ids}=" do |ids|
  44 + cids = self.send(association).order('created_at ASC').map(&:object_data_id)
  45 + nids = if ids.is_a? Array then ids else ids.split ',' end
  46 + nids = nids.map(&:to_i)
  47 + Profile.where(id: nids-cids).each{ |profile| self.send(association).create! type: klass.name, object_data: profile }
  48 + self.send(association).each{ |c| c.destroy unless c.object_data_id.in? nids }
  49 + end
  50 +
  51 + end
  52 +
  53 + define_method :open_graph_reject_empty_object_type do |attributes|
  54 + exists = attributes[:id].present?
  55 + empty = attributes[:object_type].empty?
  56 + attributes.merge! _destroy: 1 if exists and empty
  57 + return (!exists and empty)
  58 + end
  59 +end
... ...
plugins/open_graph/lib/ext/profile_activity.rb 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +require_dependency 'profile_activity'
  2 +
  3 +class ProfileActivity
  4 +
  5 + # update happens with grouped ActionTracker
  6 + after_save :open_graph_publish
  7 +
  8 + def open_graph_publish
  9 + # Scrap not yet supported
  10 + if self.activity.is_a? ActionTracker::Record
  11 + verb = self.activity.verb.to_sym
  12 + return unless object = self.activity.target
  13 + return unless stories = OpenGraphPlugin::Stories::TrackerStories[verb]
  14 + OpenGraphPlugin::Stories.publish object, stories
  15 + end
  16 + end
  17 +
  18 +end
  19 +
... ...
plugins/open_graph/lib/ext/uploaded_file.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +require_dependency 'uploaded_file'
  2 +
  3 +class UploadedFile
  4 +
  5 + extend OpenGraphPlugin::AttachStories::ClassMethods
  6 + open_graph_attach_stories only: :add_an_image
  7 +
  8 +end
... ...
plugins/open_graph/lib/open_graph_plugin.rb 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +module OpenGraphPlugin
  2 +
  3 + extend Noosfero::Plugin::ParentMethods
  4 +
  5 + def self.plugin_name
  6 + I18n.t 'open_graph_plugin.lib.plugin.name'
  7 + end
  8 +
  9 + def self.plugin_description
  10 + I18n.t 'open_graph_plugin.lib.plugin.description'
  11 + end
  12 +
  13 + def self.context
  14 + Thread.current[:open_graph_context] || :open_graph
  15 + end
  16 + def self.context= value
  17 + Thread.current[:open_graph_context] = value
  18 + end
  19 +
  20 +end
  21 +
... ...
plugins/open_graph/lib/open_graph_plugin/attach_stories.rb 0 → 100644
... ... @@ -0,0 +1,44 @@
  1 +require_dependency 'open_graph_plugin/stories'
  2 +
  3 +# This is used when ActionTracker is not compartible with the way
  4 +module OpenGraphPlugin::AttachStories
  5 +
  6 + module ClassMethods
  7 +
  8 + def open_graph_attach_stories options={}
  9 + if stories = Array(options[:only])
  10 + callbacks = {}
  11 + stories.each do |story|
  12 + defs = OpenGraphPlugin::Stories::Definitions[story]
  13 + Array(defs[:on]).each do |on|
  14 + callbacks[on] ||= []
  15 + callbacks[on] << story
  16 + end
  17 + end
  18 + else
  19 + klass = self.name
  20 + callbacks = OpenGraphPlugin::Stories::ModelStories[klass.to_sym]
  21 + return if callbacks.blank?
  22 + end
  23 +
  24 + callbacks.each do |on, stories|
  25 + # subclasses may override this, but the callback is called only once
  26 + method = "open_graph_publish_after_#{on}"
  27 +
  28 + self.send "after_#{on}", method
  29 + # buggy with rails 3.2
  30 + #self.send "after_commit", method, on: on
  31 +
  32 + define_method method do
  33 + OpenGraphPlugin::Stories.publish self, stories
  34 + end
  35 + end
  36 + end
  37 +
  38 + end
  39 +
  40 + module InstanceMethods
  41 +
  42 + end
  43 +
  44 +end
... ...
plugins/open_graph/lib/open_graph_plugin/base.rb 0 → 100644
... ... @@ -0,0 +1,15 @@
  1 +
  2 +class OpenGraphPlugin::Base < Noosfero::Plugin
  3 +
  4 + def js_files
  5 + [].map{ |j| "javascripts/#{j}" }
  6 + end
  7 +
  8 + def stylesheet?
  9 + true
  10 + end
  11 +
  12 +end
  13 +
  14 +ActiveSupport.run_load_hooks :open_graph_plugin, OpenGraphPlugin
  15 +
... ...
plugins/open_graph/lib/open_graph_plugin/display_helper.rb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +
  2 +module OpenGraphPlugin::DisplayHelper
  3 +
  4 + def blah
  5 + puts 'here'
  6 + end
  7 +end
... ...
plugins/open_graph/lib/open_graph_plugin/publisher.rb 0 → 100644
... ... @@ -0,0 +1,166 @@
  1 +
  2 +class OpenGraphPlugin::Publisher
  3 +
  4 + attr_accessor :actions
  5 + attr_accessor :objects
  6 +
  7 + def self.default
  8 + @default ||= self.new
  9 + end
  10 +
  11 + def initialize attributes = {}
  12 + # defaults
  13 + self.actions = OpenGraphPlugin::Stories::DefaultActions
  14 + self.objects = OpenGraphPlugin::Stories::DefaultObjects
  15 +
  16 + attributes.each do |attr, value|
  17 + self.send "#{attr}=", value
  18 + end
  19 + end
  20 +
  21 + def publish actor, story_defs, object_data_url
  22 + raise 'abstract method called'
  23 + end
  24 +
  25 + def publish_stories object_data, actor, stories
  26 + stories.each do |story|
  27 + begin
  28 + self.publish_story object_data, actor, story
  29 + rescue => e
  30 + ExceptionNotifier.notify_exception e
  31 + end
  32 + end
  33 + end
  34 +
  35 + def update_delay
  36 + 1.day
  37 + end
  38 +
  39 + # only publish recent objects to avoid multiple publications
  40 + def recent_publish? actor, object_type, object_data_url
  41 + activity_params = {actor_id: actor.id, object_type: object_type, object_data_url: object_data_url}
  42 + activity = OpenGraphPlugin::Activity.where(activity_params).first
  43 + activity.present? and activity.created_at <= self.update_delay.from_now
  44 + end
  45 +
  46 + def publish_story object_data, actor, story
  47 + OpenGraphPlugin.context = self.context
  48 + defs = OpenGraphPlugin::Stories::Definitions[story]
  49 + passive = defs[:passive]
  50 +
  51 + print_debug "open_graph: publish_story #{story}" if debug? actor
  52 + match_criteria = if (ret = self.call defs[:criteria], object_data, actor).nil? then true else ret end
  53 + return unless match_criteria
  54 + print_debug "open_graph: #{story} match criteria" if debug? actor
  55 + match_condition = if (ret = self.call defs[:publish_if], object_data, actor).nil? then true else ret end
  56 + return unless match_condition
  57 + print_debug "open_graph: #{story} match publish_if" if debug? actor
  58 +
  59 + actors = self.story_trackers defs, actor, object_data
  60 + return if actors.blank?
  61 + print_debug "open_graph: #{story} has enabled trackers" if debug? actor
  62 +
  63 + if publish = defs[:publish]
  64 + begin
  65 + instance_exec actor, object_data, &publish
  66 + rescue => e
  67 + print_debug "open_graph: can't publish story: #{e.message}" if debug? actor
  68 + ExceptionNotifier.notify_exception e
  69 + end
  70 + else
  71 + # force profile identifier for custom domains and fixed host. see og_url_for
  72 + object_profile = self.call(story_defs[:object_profile], object_data) || object_data.profile rescue nil
  73 + extra_params = if object_profile then {profile: object_profile.identifier} else {} end
  74 +
  75 + custom_object_data_url = self.call defs[:object_data_url], object_data, actor
  76 + object_data_url = if passive then self.passive_url_for object_data, custom_object_data_url, defs, extra_params else self.url_for object_data, custom_object_data_url, extra_params end
  77 +
  78 + actors.each do |actor|
  79 + print_debug "open_graph: start publishing" if debug? actor
  80 + begin
  81 + self.publish actor, defs, object_data_url
  82 + rescue => e
  83 + print_debug "open_graph: can't publish story: #{e.message}" if debug? actor
  84 + ExceptionNotifier.notify_exception e
  85 + end
  86 + end
  87 + end
  88 + end
  89 +
  90 + def story_trackers story_defs, actor, object_data
  91 + passive = story_defs[:passive]
  92 + trackers = []
  93 +
  94 + track_configs = Array(story_defs[:track_config]).compact.map(&:constantize)
  95 + return if track_configs.empty?
  96 + print_debug "open_graph: using configs: #{track_configs.map(&:name).inspect}" if debug? actor
  97 +
  98 + if passive
  99 + object_profile = self.call(story_defs[:object_profile], object_data) || object_data.profile rescue nil
  100 + return unless object_profile
  101 +
  102 + track_configs.each do |c|
  103 + trackers.concat c.trackers_to_profile(object_profile)
  104 + end.flatten
  105 +
  106 + trackers.select! do |t|
  107 + track_configs.any?{ |c| c.enabled? self.context, t }
  108 + end
  109 + else #active
  110 + object_actor = self.call(story_defs[:object_actor], object_data) || object_data.profile rescue nil
  111 + return unless object_actor and object_actor.person?
  112 + custom_actor = self.call(story_defs[:custom_actor], object_data)
  113 + actor = custom_actor if custom_actor
  114 +
  115 + match_track = track_configs.any? do |c|
  116 + c.enabled?(self.context, actor) and
  117 + actor.send("open_graph_#{c.track_name}_track_configs").where(object_type: story_defs[:object_type]).first
  118 + end
  119 + trackers << actor if match_track
  120 + end
  121 +
  122 + trackers
  123 + end
  124 +
  125 + protected
  126 +
  127 + include MetadataPlugin::UrlHelper
  128 +
  129 + def register_publish attributes
  130 + OpenGraphPlugin::Activity.create! attributes
  131 + end
  132 +
  133 + # Call don't ask: move to a og_url method inside object
  134 + def url_for object, custom_url=nil, extra_params={}
  135 + return custom_url if custom_url.is_a? String
  136 + url = custom_url || if object.is_a? Profile then og_profile_url object else object.url end
  137 + # for profile when custom domain is used
  138 + url.merge! profile: object.profile.identifier if object.respond_to? :profile
  139 + url.merge! extra_params
  140 + self.og_url_for url
  141 + end
  142 +
  143 + def passive_url_for object, custom_url, story_defs, extra_params={}
  144 + object_type = story_defs[:object_type]
  145 + extra_params.merge! og_type: MetadataPlugin.og_types[object_type]
  146 + self.url_for object, custom_url, extra_params
  147 + end
  148 +
  149 + def call p, *args
  150 + p and instance_exec *args, &p
  151 + end
  152 +
  153 + def context
  154 + :open_graph
  155 + end
  156 +
  157 + def print_debug msg
  158 + puts msg
  159 + Delayed::Worker.logger.debug msg
  160 + end
  161 + def debug? actor=nil
  162 + !Rails.env.production?
  163 + end
  164 +
  165 +end
  166 +
... ...
plugins/open_graph/lib/open_graph_plugin/settings.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +class OpenGraphPlugin::Settings < Noosfero::Plugin::Settings
  2 +
  3 + def self.new base, attrs = {}
  4 + super base, self.parents.first, attrs
  5 + end
  6 +
  7 + OpenGraphPlugin::TrackConfig::Types.each do |track, klass|
  8 + define_method "#{track}_track_enabled=" do |value|
  9 + super ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
  10 + end
  11 + end
  12 +
  13 +end
  14 +
... ...
plugins/open_graph/lib/open_graph_plugin/stories.rb 0 → 100644
... ... @@ -0,0 +1,302 @@
  1 +
  2 +class OpenGraphPlugin::Stories
  3 +
  4 + class_attribute :publishers
  5 + self.publishers = []
  6 +
  7 + def self.register_publisher publisher
  8 + self.publishers << publisher
  9 + end
  10 +
  11 + def self.publish record, stories
  12 + actor = User.current.person rescue nil
  13 + return unless actor
  14 +
  15 + self.publishers.each do |publisher|
  16 + publisher = publisher.delay unless Rails.env.development? or Rails.env.test?
  17 + publisher.publish_stories record, actor, stories
  18 + end
  19 + end
  20 +
  21 + Definitions = {
  22 + # needed a patch on UploadedFile: def notifiable?; true; end
  23 + add_a_document: {
  24 + action_tracker_verb: :create_article,
  25 + track_config: 'OpenGraphPlugin::ActivityTrackConfig',
  26 + action: :add,
  27 + object_type: :uploaded_file,
  28 + models: :UploadedFile,
  29 + on: :create,
  30 + criteria: proc do |article, actor|
  31 + article.is_a? UploadedFile
  32 + end,
  33 + publish_if: proc do |uploaded_file, actor|
  34 + # done in add_an_image
  35 + next false if uploaded_file.image?
  36 + uploaded_file.published?
  37 + end,
  38 + object_data_url: proc do |uploaded_file, actor|
  39 + uploaded_file.url.merge view: true
  40 + end,
  41 + },
  42 + add_an_image: {
  43 + # :upload_image verb can't be used as it uses the parent Gallery as target
  44 + # hooked via open_graph_attach_stories
  45 + action_tracker_verb: nil,
  46 + track_config: 'OpenGraphPlugin::ActivityTrackConfig',
  47 + action: :add,
  48 + object_type: :gallery_image,
  49 + models: :UploadedFile,
  50 + on: :create,
  51 + criteria: proc do |article, actor|
  52 + article.is_a? UploadedFile
  53 + end,
  54 + publish_if: proc do |uploaded_file, actor|
  55 + uploaded_file.image? and uploaded_file.parent.is_a? Gallery
  56 + end,
  57 + object_data_url: proc do |uploaded_file, actor|
  58 + uploaded_file.url.merge view: true
  59 + end,
  60 + },
  61 + create_an_article: {
  62 + action_tracker_verb: :create_article,
  63 + track_config: 'OpenGraphPlugin::ActivityTrackConfig',
  64 + action: :create,
  65 + object_type: :blog_post,
  66 + models: :Article,
  67 + on: :create,
  68 + criteria: proc do |article, actor|
  69 + article.parent.is_a? Blog
  70 + end,
  71 + publish_if: proc do |article, actor|
  72 + article.published?
  73 + end,
  74 + },
  75 + create_an_event: {
  76 + action_tracker_verb: :create_article,
  77 + track_config: 'OpenGraphPlugin::ActivityTrackConfig',
  78 + action: :create,
  79 + object_type: :event,
  80 + models: :Event,
  81 + on: :create,
  82 + criteria: proc do |article, actor|
  83 + article.is_a? Event
  84 + end,
  85 + publish_if: proc do |event, actor|
  86 + event.published?
  87 + end,
  88 + },
  89 + start_a_discussion: {
  90 + action_tracker_verb: :create_article,
  91 + track_config: 'OpenGraphPlugin::ActivityTrackConfig',
  92 + action: :start,
  93 + object_type: :forum,
  94 + models: :Article,
  95 + on: :create,
  96 + criteria: proc do |article, actor|
  97 + article.parent.is_a? Forum
  98 + end,
  99 + publish_if: proc do |article, actor|
  100 + article.published?
  101 + end,
  102 + object_data_url: proc do |article, actor|
  103 + url = article.url
  104 + if og_type = MetadataPlugin::og_types[:forum]
  105 + url[:og_type] = og_type
  106 + end
  107 + url
  108 + end,
  109 + },
  110 +
  111 + # these a published as passive to give focus to the enterprise
  112 +=begin
  113 + add_a_sse_product: {
  114 + action_tracker_verb: :create_product,
  115 + track_config: 'OpenGraphPlugin::ActivityTrackConfig',
  116 + action: :announce_new,
  117 + models: :Product,
  118 + on: :create,
  119 + object_type: :product,
  120 + publish_if: proc do |product, actor|
  121 + product.profile.public?
  122 + end,
  123 + },
  124 + update_a_sse_product: {
  125 + action_tracker_verb: :update_product,
  126 + track_config: 'OpenGraphPlugin::ActivityTrackConfig',
  127 + action: :announce_update,
  128 + object_type: :product,
  129 + models: :Product,
  130 + on: :update,
  131 + publish_if: proc do |product, actor|
  132 + product.profile.public?
  133 + end,
  134 + },
  135 +=end
  136 +
  137 + favorite_a_sse_initiative: {
  138 + action_tracker_verb: :favorite_enterprise,
  139 + track_config: 'OpenGraphPlugin::ActivityTrackConfig',
  140 + action: :favorite,
  141 + object_type: :favorite_enterprise,
  142 + models: :FavoriteEnterprisePerson,
  143 + on: :create,
  144 + object_actor: proc do |favorite_enterprise_person|
  145 + favorite_enterprise_person.person
  146 + end,
  147 + object_profile: proc do |favorite_enterprise_person|
  148 + favorite_enterprise_person.enterprise
  149 + end,
  150 + object_data_url: proc do |favorite_enterprise_person, actor|
  151 + self.og_profile_url favorite_enterprise_person.enterprise
  152 + end,
  153 + },
  154 +
  155 +=begin
  156 + comment_a_discussion: {
  157 + action_tracker_verb: nil,
  158 + action: :comment,
  159 + object_type: :forum,
  160 + models: :Comment,
  161 + on: :create,
  162 + criteria: proc do |comment, actor|
  163 + source, parent = comment.source, comment.source.parent
  164 + source.is_a? Article and parent.is_a? Forum
  165 + end,
  166 + publish_if: proc do |comment, actor|
  167 + comment.source.parent.published?
  168 + end,
  169 + },
  170 + comment_an_article: {
  171 + action_tracker_verb: nil,
  172 + action: :comment,
  173 + object_type: :blog_post,
  174 + models: :Comment,
  175 + on: :create,
  176 + criteria: proc do |comment, actor|
  177 + source, parent = comment.source, comment.source.parent
  178 + source.is_a? Article and parent.is_a? Blog
  179 + end,
  180 + publish_if: proc do |comment, actor|
  181 + comment.source.parent.published?
  182 + end,
  183 + },
  184 +=end
  185 +
  186 + make_friendship_with: {
  187 + action_tracker_verb: :new_friendship,
  188 + track_config: 'OpenGraphPlugin::ActivityTrackConfig',
  189 + action: :make_friendship,
  190 + object_type: :friend,
  191 + models: :Friendship,
  192 + on: :create,
  193 + custom_actor: proc do |friendship|
  194 + friendship.person
  195 + end,
  196 + object_actor: proc do |friendship|
  197 + friendship.person
  198 + end,
  199 + object_profile: proc do |friendship|
  200 + friendship.friend
  201 + end,
  202 + object_data_url: proc do |friendship, actor|
  203 + self.og_profile_url friendship.friend
  204 + end,
  205 + },
  206 +
  207 + # PASSIVE STORIES
  208 + announce_news_from_a_sse_initiative: {
  209 + action_tracker_verb: :create_article,
  210 + track_config: 'OpenGraphPlugin::EnterpriseTrackConfig',
  211 + action: :announce_news,
  212 + object_type: :enterprise,
  213 + passive: true,
  214 + models: :Article,
  215 + on: :create,
  216 + criteria: proc do |article, actor|
  217 + article.profile.enterprise?
  218 + end,
  219 + publish_if: proc do |article, actor|
  220 + article.published?
  221 + end,
  222 + },
  223 + announce_a_new_sse_product: {
  224 + action_tracker_verb: :create_product,
  225 + track_config: 'OpenGraphPlugin::EnterpriseTrackConfig',
  226 + action: :announce_new,
  227 + object_type: :product,
  228 + passive: true,
  229 + models: :Product,
  230 + on: :create,
  231 + criteria: proc do |product, actor|
  232 + product.profile.enterprise?
  233 + end,
  234 + },
  235 + announce_an_update_of_sse_product: {
  236 + action_tracker_verb: :update_product,
  237 + track_config: 'OpenGraphPlugin::EnterpriseTrackConfig',
  238 + action: :announce_update,
  239 + object_type: :product,
  240 + passive: true,
  241 + models: :Product,
  242 + on: :update,
  243 + criteria: proc do |product, actor|
  244 + product.profile.enterprise?
  245 + end,
  246 + },
  247 +
  248 + announce_news_from_a_community: {
  249 + action_tracker_verb: :create_article,
  250 + track_config: 'OpenGraphPlugin::CommunityTrackConfig',
  251 + action: :announce_news,
  252 + object_type: :community,
  253 + passive: true,
  254 + models: :Article,
  255 + on: :create,
  256 + criteria: proc do |article, actor|
  257 + article.profile.community?
  258 + end,
  259 + publish_if: proc do |article, actor|
  260 + article.published?
  261 + end,
  262 + },
  263 +
  264 + }
  265 +
  266 + ValidObjectList = Definitions.map{ |story, data| data[:object_type] }.uniq
  267 + ValidActionList = Definitions.map{ |story, data| data[:action] }.uniq
  268 +
  269 + # TODO make this verification work
  270 + #raise "Each active story must use a unique object_type for configuration to work" if ValidObjectList.size < Definitions.size
  271 +
  272 + DefaultActions = ValidActionList.inject({}){ |h, a| h[a] = a; h }
  273 + DefaultObjects = ValidObjectList.inject({}){ |h, o| h[o] = o; h }
  274 +
  275 + TrackerStories = {}; Definitions.each do |story, data|
  276 + Array(data[:action_tracker_verb]).each do |verb|
  277 + next unless verb
  278 + TrackerStories[verb] ||= []
  279 + TrackerStories[verb] << story
  280 + end
  281 + end
  282 +
  283 + TrackConfigStories = {}; Definitions.each do |story, data|
  284 + Array(data[:track_config]).each do |track_config|
  285 + next unless track_config
  286 + TrackConfigStories[track_config] ||= []
  287 + TrackConfigStories[track_config] << [story, data]
  288 + end
  289 + end
  290 +
  291 + ModelStories = {}; Definitions.each do |story, data|
  292 + Array(data[:models]).each do |model|
  293 + ModelStories[model] ||= {}
  294 + Array(data[:on]).each do |on|
  295 + ModelStories[model][on] ||= []
  296 + ModelStories[model][on] << story
  297 + end
  298 + end
  299 + end
  300 +
  301 +end
  302 +
... ...
plugins/open_graph/locales/en-US.yml 0 → 100644
... ... @@ -0,0 +1,43 @@
  1 +
  2 +"en-US": &en-US
  3 +
  4 + open_graph_plugin:
  5 + lib:
  6 + plugin:
  7 + name: 'OpenGraph'
  8 + description: 'OpenGraph'
  9 + views:
  10 + track:
  11 + config:
  12 + activity:
  13 + configure: 'Configure'
  14 + label: "My activities: new photos on my albuns, blogs's posts and other contents"
  15 + objects:
  16 + blog_post: "Blogs' posts"
  17 + event: 'Creation of events'
  18 + favorite_enterprise: 'Quando eu favoritar um empreendimento'
  19 + forum: "Forum's topic posted"
  20 + friend: 'New friendships'
  21 + gallery_image: 'New images on my albuns'
  22 + uploaded_file: 'Files sent'
  23 + enterprise:
  24 + memberships: "Enterprises that I'm a member of"
  25 + favorites: "My favorite enterprises"
  26 + see_all: 'See enterprises'
  27 + label: "News from enterprises that I am a member and my favorites"
  28 + search_placeholder: "type to find enterprises"
  29 + favorites_how_to:
  30 + title: "How to add favorite Solidarity Economy initiatives"
  31 + body: "To add favorite Solidarity Economy initiatives, you should visit its page in Cirandas and click on button %{favorite_button} which is located below its logo, normally on the left side."
  32 + community:
  33 + label: "News from selected communities"
  34 + search_placeholder: "type to find communities"
  35 + friend:
  36 + label: "News from friends"
  37 + search_placeholder: "type to find friends"
  38 +
  39 +'en_US':
  40 + <<: *en-US
  41 +'en':
  42 + <<: *en-US
  43 +
... ...
plugins/open_graph/locales/pt-BR.yml 0 → 100644
... ... @@ -0,0 +1,40 @@
  1 +
  2 +"pt-BR": &pt-BR
  3 +
  4 + open_graph_plugin:
  5 + lib:
  6 + plugin:
  7 + name: 'OpenGraph'
  8 + description: 'OpenGraph'
  9 + views:
  10 + track:
  11 + config:
  12 + activity:
  13 + configure: 'Configurar'
  14 + label: 'Publicar minhas atividades do cirandas no meu mural do facebook'
  15 + objects:
  16 + blog_post: 'Quando eu criar um conteúdo ou artigo'
  17 + event: 'Quando eu adicionar eventos'
  18 + forum: 'Quando eu criar novos tópicos de fórum'
  19 + friend: 'Quando eu fizer amizades'
  20 + gallery_image: 'Quando eu adicionar imagens nos meus albuns'
  21 + uploaded_file: 'Quando eu enviar novos documentos'
  22 + favorite_enterprise: 'Quando eu favoritar um empreendimento'
  23 + enterprise:
  24 + memberships: "Empreendimentos dos quais faço parte"
  25 + favorites: "Meus empreendimentos favoritos"
  26 + see_all: 'Ver empreendimentos'
  27 + label: "Publicar as novidades dos meus empreendimentos favoritos e daqueles que faço parte"
  28 + search_placeholder: "busque o empreendimento"
  29 + favorites_how_to:
  30 + title: "Como adicionar empreendimentos favoritos"
  31 + body: "Para adicionar empreendimentos favoritos, basta você visitar a página do empreendimento desejado e clicar no botão %{favorite_button} que fica abaixo da logo do empreendimento, geralmente à esquerda."
  32 + community:
  33 + label: "Publicar novidades das seguintes comunidades"
  34 + search_placeholder: "escolha a comunidade"
  35 +
  36 +'pt_BR':
  37 + <<: *pt-BR
  38 +'pt':
  39 + <<: *pt-BR
  40 +
... ...
plugins/open_graph/models/open_graph_plugin/activity.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +# This is a log of activities, unlike ActivityTrack that is a configuration
  2 +class OpenGraphPlugin::Activity < OpenGraphPlugin::Track
  3 +
  4 + # subclass this to define (e.g. FbAppPlugin::Activity)
  5 + def scrape
  6 + end
  7 +
  8 +end
... ...
plugins/open_graph/models/open_graph_plugin/activity_track_config.rb 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +class OpenGraphPlugin::ActivityTrackConfig < OpenGraphPlugin::TrackConfig
  2 +
  3 + # workaround for STI bug
  4 + self.table_name = :open_graph_plugin_tracks
  5 +
  6 + self.track_name = :activity
  7 +
  8 + Objects = OpenGraphPlugin::Stories::TrackConfigStories[self.name].map do |story, data|
  9 + data[:object_type].to_s
  10 + end.uniq
  11 +
  12 + def self.objects
  13 + Objects
  14 + end
  15 +
  16 + validates_uniqueness_of :object_type, scope: [:tracker_id]
  17 + validates_inclusion_of :object_type, in: self.objects
  18 +
  19 + protected
  20 +
  21 +end
  22 +
... ...
plugins/open_graph/models/open_graph_plugin/community_track_config.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +class OpenGraphPlugin::CommunityTrackConfig < OpenGraphPlugin::TrackConfig
  2 +
  3 + # workaround for STI bug
  4 + self.table_name = :open_graph_plugin_tracks
  5 +
  6 + self.track_name = :community
  7 +
  8 +end
... ...
plugins/open_graph/models/open_graph_plugin/enterprise_track_config.rb 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +class OpenGraphPlugin::EnterpriseTrackConfig < OpenGraphPlugin::TrackConfig
  2 +
  3 + # workaround for STI bug
  4 + self.table_name = :open_graph_plugin_tracks
  5 +
  6 + self.track_name = :enterprise
  7 +
  8 + self.static_trackers = true
  9 +
  10 + def self.trackers_to_profile enterprise
  11 + trackers = enterprise.members.to_set
  12 + trackers.merge enterprise.fans if enterprise.respond_to? :fans
  13 + trackers.to_a
  14 + end
  15 +
  16 + def self.profile_track_objects profile
  17 + (profile.enterprises.public.enabled + profile.favorite_enterprises.public.enabled).uniq
  18 + end
  19 +
  20 +end
... ...
plugins/open_graph/models/open_graph_plugin/friend_track_config.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +class OpenGraphPlugin::FriendTrackConfig < OpenGraphPlugin::TrackConfig
  2 +
  3 + # workaround for STI bug
  4 + self.table_name = :open_graph_plugin_tracks
  5 +
  6 + self.track_name = :friend
  7 +
  8 +end
... ...
plugins/open_graph/models/open_graph_plugin/track.rb 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +class OpenGraphPlugin::Track < ActiveRecord::Base
  2 +
  3 + attr_accessible :type, :context, :tracker_id, :tracker, :actor_id, :action,
  4 + :object_type, :object_data, :object_data_id, :object_data_type, :object_data_url
  5 +
  6 + belongs_to :tracker, class_name: 'Profile'
  7 + belongs_to :actor, class_name: 'Profile'
  8 + belongs_to :object_data, polymorphic: true
  9 +
  10 + validates_presence_of :context
  11 + before_validation :set_context
  12 +
  13 + def self.objects
  14 + []
  15 + end
  16 +
  17 + def self.association
  18 + @association ||= "open_graph_#{self.name.demodulize.pluralize.underscore}".to_sym
  19 + end
  20 +
  21 + protected
  22 +
  23 + def set_context
  24 + self.context = OpenGraphPlugin.context
  25 + end
  26 +
  27 +end
  28 +
... ...
plugins/open_graph/models/open_graph_plugin/track_config.rb 0 → 100644
... ... @@ -0,0 +1,49 @@
  1 +class OpenGraphPlugin::TrackConfig < OpenGraphPlugin::Track
  2 +
  3 + Types = {
  4 + activity: 'ActivityTrackConfig',
  5 + enterprise: 'EnterpriseTrackConfig',
  6 + community: 'CommunityTrackConfig',
  7 + # TODO: not yet implemented
  8 + #friend: 'FriendTrackConfig',
  9 + }
  10 +
  11 + # define on subclasses (required)
  12 + class_attribute :track_name
  13 + def self.track_enabled_field
  14 + "#{self.track_name}_track_enabled"
  15 + end
  16 +
  17 + # true if do not depend on records (e.g. EnterpriseTrackConfig depends on friends)
  18 + # redefine on subclasses
  19 + class_attribute :static_trackers
  20 + self.static_trackers = false
  21 +
  22 + def self.enabled? context, actor
  23 + settings = actor.send "#{context}_settings"
  24 + settings.send "#{self.track_name}_track_enabled"
  25 + end
  26 +
  27 + scope :tracks_to_profile, lambda { |profile, exclude_actor=nil|
  28 + scope = where object_data_id: profile.id, object_data_type: profile.class.base_class
  29 + scope = scope.where context: OpenGraphPlugin.context
  30 + scope = scope.includes :tracker
  31 + scope = scope.where ['tracker_id <> ?', exclude_actor.id] if exclude_actor
  32 + scope
  33 + }
  34 +
  35 + # redefine on subclasses
  36 + def self.trackers_to_profile profile
  37 + tracks = self.tracks_to_profile profile
  38 + tracks = tracks.where type: self
  39 + tracks.map(&:tracker)
  40 + end
  41 +
  42 + def self.profile_tracks profile
  43 + profile.send self.association
  44 + end
  45 + def self.profile_track_objects profile
  46 + self.profile_tracks(profile).map(&:object_data).compact
  47 + end
  48 +
  49 +end
... ...
plugins/open_graph/plugin.yml 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +name: open_graph
  2 +dependencies:
  3 + - metadata
... ...
plugins/open_graph/public/javascripts/open_graph.js 0 → 100644
... ... @@ -0,0 +1,181 @@
  1 +open_graph = {
  2 +
  3 + track: {
  4 +
  5 + config: {
  6 +
  7 + view: {
  8 + form: null,
  9 + },
  10 +
  11 + init: function(reload) {
  12 + this.view.form = $('#track-form form')
  13 + this.view.form.find('.panel-heading').each(function(i, context) {
  14 + open_graph.track.config.headingToggle(context)
  15 + })
  16 + },
  17 +
  18 + submit: function() {
  19 + loading_overlay.show($('#track-config'))
  20 + open_graph.track.config.view.form.ajaxSubmit({
  21 + success: function(data) {
  22 + data = $(data)
  23 + // needs update to get ids from accepts_nested_attributes_for
  24 + $('#track-activity').html(data.find('#track-activity').html())
  25 + loading_overlay.hide($('#track-config'))
  26 + },
  27 + })
  28 + return false;
  29 + },
  30 +
  31 + // trigged on init state and on subcheckboxes change
  32 + headingToggle: function(context, open) {
  33 + var panel = $(context).parents('.panel')
  34 + var panelHeading = panel.find('.panel-heading')
  35 + var panelBody = panel.find('.panel-body')
  36 + var parentCheckbox = panel.find('.config-check')
  37 + var configButton = panel.find('.config-button')
  38 + var input = panel.find('.track-config-toggle')
  39 + var openWas = input.val() == 'true'
  40 + if (open === undefined)
  41 + open = input.val() == 'true' && (panelHeading.hasClass('enable-on-empty') || this.numberChecked(context) > 0)
  42 + // open is defined, that is an user action
  43 + else {
  44 + if (open) {
  45 + if (panelHeading.hasClass('open-on-enable'))
  46 + panelBody.collapse('show')
  47 + } else
  48 + panelBody.collapse('hide')
  49 + }
  50 +
  51 + configButton.toggle(open)
  52 + parentCheckbox.toggleClass('fa-toggle-on', open)
  53 + parentCheckbox.toggleClass('fa-toggle-off', !open)
  54 + input.prop('value', open)
  55 + if (openWas != open)
  56 + open_graph.track.config.submit()
  57 + },
  58 +
  59 + // the event of change
  60 + toggleEvent: function(context, event) {
  61 + var panel = $(context).parents('.panel')
  62 + var panelBody = panel.find('.panel-body')
  63 + var checkboxes = panelBody.find('input[type=checkbox]')
  64 + var open = panel.find('.track-config-toggle').val() == 'true'
  65 + open = !open;
  66 +
  67 + checkboxes.prop('checked', open)
  68 +
  69 + this.headingToggle(context, open)
  70 + return false;
  71 + },
  72 +
  73 + open: function(context) {
  74 + var panel = $(context).parents('.panel')
  75 + var panelBody = panel.find('.panel-body')
  76 + panelBody.collapse('show')
  77 + },
  78 +
  79 + toggleObjectType: function(checkbox) {
  80 + checkbox = $(checkbox)
  81 +
  82 + this.headingToggle(checkbox)
  83 +
  84 + checkbox.siblings("input[name*='[_destroy]']").val(!checkbox.is(':checked'))
  85 + open_graph.track.config.submit()
  86 + },
  87 +
  88 + numberChecked: function(context) {
  89 + var panel = $(context).parents('.panel')
  90 + var panelBody = panel.find('.panel-body')
  91 + var checkboxes = panel.find('.panel-body input[type=checkbox]')
  92 + var profilesInput = panel.find('.panel-body .select-profiles')
  93 +
  94 + var nObjects = checkboxes.filter(':checked').length
  95 + var nProfiles = profilesInput.length ? profilesInput.tokenfield('getTokens').length : 0;
  96 + var nChecked = nObjects + nProfiles;
  97 + var nTotal = checkboxes.length + nProfiles
  98 +
  99 + return nChecked
  100 + },
  101 +
  102 + enterprise: {
  103 + see_all: function(context) {
  104 + var panel = $(context).parents('.panel')
  105 + var panelBody = panel.find('.panel-body')
  106 + noosfero.modal.html(panelBody.html())
  107 + },
  108 + },
  109 +
  110 + initAutocomplete: function(track, url, items) {
  111 + var selector = '#select-'+track
  112 + var input = $(selector)
  113 + var tokenField = open_graph.autocomplete.init(url, selector, items)
  114 +
  115 + input.change(open_graph.track.config.submit)
  116 + tokenField
  117 + .on('tokenfield:createdtoken tokenfield:removedtoken', function() {
  118 + open_graph.track.config.headingToggle(this)
  119 + }).on('tokenfield:createtoken tokenfield:removetoken', function(event) {
  120 + input.val()
  121 + }).on('tokenfield:createtoken', function(event) {
  122 + var existingTokens = $(this).tokenfield('getTokens')
  123 + $.each(existingTokens, function(index, token) {
  124 + if (token.value === event.attrs.value)
  125 + event.preventDefault()
  126 + })
  127 + })
  128 +
  129 + return tokenField;
  130 + },
  131 +
  132 + },
  133 + },
  134 +
  135 + autocomplete: {
  136 + bloodhoundOptions: {
  137 + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
  138 + queryTokenizer: Bloodhound.tokenizers.whitespace,
  139 + ajax: {
  140 + beforeSend: function() {
  141 + input.addClass('small-loading')
  142 + },
  143 + complete: function() {
  144 + input.removeClass('small-loading')
  145 + },
  146 + },
  147 + },
  148 + tokenfieldOptions: {
  149 +
  150 + },
  151 + typeaheadOptions: {
  152 + minLength: 1,
  153 + highlight: true,
  154 + },
  155 +
  156 + init: function(url, selector, data, options) {
  157 + options = options || {}
  158 + var bloodhoundOptions = $.extend({}, this.bloodhoundOptions, options.bloodhound || {});
  159 + var typeaheadOptions = $.extend({}, this.typeaheadOptions, options.typeahead || {});
  160 + var tokenfieldOptions = $.extend({}, this.tokenfieldOptions, options.tokenfield || {});
  161 +
  162 + var input = $(selector)
  163 + bloodhoundOptions.remote = {
  164 + url: url,
  165 + replace: function(url, uriEncodedQuery) {
  166 + return $.param.querystring(url, {query:uriEncodedQuery});
  167 + },
  168 + }
  169 + var engine = new Bloodhound(bloodhoundOptions)
  170 + engine.initialize()
  171 +
  172 + tokenfieldOptions.typeahead = [typeaheadOptions, { displayKey: 'label', source: engine.ttAdapter() }]
  173 +
  174 + var tokenField = input.tokenfield(tokenfieldOptions)
  175 + input.tokenfield('setTokens', data)
  176 +
  177 + return input
  178 + },
  179 + },
  180 +}
  181 +
... ...
plugins/open_graph/public/style.scss 0 → 120000
... ... @@ -0,0 +1 @@
  1 +stylesheets/style.scss
0 2 \ No newline at end of file
... ...
plugins/open_graph/public/stylesheets/style.scss 0 → 100644
... ... @@ -0,0 +1,66 @@
  1 +#track-form {
  2 +
  3 + .panel-heading {
  4 + a, a:visited {
  5 + color: #fff;
  6 + display: block;
  7 + text-decoration: none;
  8 + }
  9 + a:hover {
  10 + text-decoration: underline;
  11 + }
  12 + a.btn {
  13 + display: inline-block;
  14 + text-decoration: none;
  15 + }
  16 + }
  17 +
  18 + // always use one line to fit placeholder
  19 + .tokenfield {
  20 +
  21 + .twitter-typeahead {
  22 + width: 100%;
  23 + display: block;
  24 +
  25 + .tt-input {
  26 + width: 100% !important;
  27 + }
  28 + }
  29 + }
  30 +
  31 + #track-config {
  32 + .panel-heading {
  33 + padding-left: 36px;
  34 + }
  35 + span.config-check {
  36 + font-weight: bold;
  37 + margin-left: -26px;
  38 + margin-right: 4px;
  39 + }
  40 + span.config-check.fa-toggle-off {
  41 + color: #99f;
  42 + }
  43 + .activity-config, .sse-config, .community-config {
  44 + float:right;
  45 + }
  46 + .activity-label, .sse-label, .community-label {
  47 + margin-right: 90px;
  48 + }
  49 + }
  50 +}
  51 +
  52 +// shown inside a popin
  53 +.open-graph-enterprises-modal {
  54 + overflow: hidden;
  55 +
  56 + h1 {
  57 + font-size: 22px;
  58 + }
  59 + #open-graph-favorite-enterprises-how-to {
  60 + clear:both;
  61 + padding-top: 1px;
  62 + }
  63 + p {
  64 + text-align:justify;
  65 + }
  66 +}
... ...
plugins/open_graph/test/functional/open_graph_graph/my_profile_controller_test.rb 0 → 100644
... ... @@ -0,0 +1,54 @@
  1 +require 'test_helper'
  2 +require 'open_graph_plugin/myprofile_controller'
  3 +
  4 +# Re-raise errors caught by the controller.
  5 +class OpenGraphPlugin::MyprofileController; def rescue_action(e) raise e end; end
  6 +
  7 +class OpenGraphPlugin::MyprofileControllerTest < ActionController::TestCase
  8 +
  9 + def setup
  10 + @controller = OpenGraphPlugin::MyprofileController.new
  11 + @request = ActionController::TestRequest.new
  12 + @response = ActionController::TestResponse.new
  13 + @actor = create_user.person
  14 + end
  15 +
  16 + should "save selected activities" do
  17 + login_as @actor.identifier
  18 + @myenterprise = @actor.environment.enterprises.create! name: 'mycoop', identifier: 'mycoop'
  19 + @myenterprise.add_member @actor
  20 + @enterprise = @actor.environment.enterprises.create! name: 'coop', identifier: 'coop'
  21 + @enterprise.fans << @actor
  22 +
  23 + post :track_config, profile: @actor.identifier, profile_data: {
  24 + open_graph_settings: {
  25 + activity_track_enabled: "true",
  26 + enterprise_track_enabled: "true",
  27 + community_track_enabled: "false",
  28 + },
  29 + open_graph_activity_track_configs_attributes: {
  30 + 0 => {
  31 + tracker_id: @actor.id,
  32 + object_type: 'blog_post',
  33 + },
  34 + },
  35 +
  36 + # ignored, enterprise uses static tracking
  37 + open_graph_enterprise_profiles_ids: [@enterprise.id],
  38 + }
  39 + @actor.reload
  40 +
  41 + assert_equal true, @actor.open_graph_settings.activity_track_enabled
  42 + assert_equal true, @actor.open_graph_settings.enterprise_track_enabled
  43 + assert_equal false, @actor.open_graph_settings.community_track_enabled
  44 +
  45 + assert_equal 1, @actor.open_graph_activity_track_configs.count
  46 + assert_equal 'blog_post', @actor.open_graph_activity_track_configs.first.object_type
  47 + assert_equal @actor.id, @actor.open_graph_activity_track_configs.first.tracker_id
  48 +
  49 + assert_equal [@actor], OpenGraphPlugin::EnterpriseTrackConfig.trackers_to_profile(@enterprise)
  50 + assert_equal [@actor], OpenGraphPlugin::EnterpriseTrackConfig.trackers_to_profile(@myenterprise)
  51 +
  52 + end
  53 +
  54 +end
... ...
plugins/open_graph/test/unit/open_graph_graph/publisher_test.rb 0 → 100644
... ... @@ -0,0 +1,111 @@
  1 +require "test_helper"
  2 +
  3 +class OpenGraphPlugin::PublisherTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + @actor = create_user.person
  7 + User.current = @actor.user
  8 + @stories = OpenGraphPlugin::Stories::Definitions
  9 + @publisher = OpenGraphPlugin::Publisher.new
  10 + OpenGraphPlugin::Stories.stubs(:publishers).returns([@publisher])
  11 + @publisher.stubs(:context).returns(:open_graph)
  12 + @publisher.stubs(:og_domain).returns('noosfero.net')
  13 + end
  14 +
  15 + should "publish only tracked stuff" do
  16 + @other_actor = create_user.person
  17 +
  18 + @myenterprise = @actor.environment.enterprises.create! name: 'mycoop', identifier: 'mycoop'
  19 + @myenterprise.add_member @actor
  20 + @enterprise = @actor.environment.enterprises.create! name: 'coop', identifier: 'coop'
  21 + # the original domain from open_graph should be used
  22 + @enterprise.domains.create! name: 'customdomain.com'
  23 +
  24 + @community = @actor.environment.communities.create! name: 'comm', identifier: 'comm', closed: false
  25 +
  26 + @actor.update_attributes!({
  27 + open_graph_settings: {
  28 + activity_track_enabled: "true",
  29 + enterprise_track_enabled: "true",
  30 + community_track_enabled: "true",
  31 + },
  32 + open_graph_activity_track_configs_attributes: {
  33 + 0 => { tracker_id: @actor.id, object_type: 'blog_post', },
  34 + 1 => { tracker_id: @actor.id, object_type: 'gallery_image', },
  35 + 2 => { tracker_id: @actor.id, object_type: 'uploaded_file', },
  36 + 3 => { tracker_id: @actor.id, object_type: 'event', },
  37 + 4 => { tracker_id: @actor.id, object_type: 'forum', },
  38 + 5 => { tracker_id: @actor.id, object_type: 'friend', },
  39 + 6 => { tracker_id: @actor.id, object_type: 'favorite_enterprise', },
  40 + },
  41 + open_graph_enterprise_profiles_ids: "#{@enterprise.id}",
  42 + open_graph_community_profiles_ids: "#{@community.id}",
  43 + })
  44 + @other_actor.update_attributes! open_graph_settings: { activity_track_enabled: "true", },
  45 + open_graph_activity_track_configs_attributes: { 0 => { tracker_id: @other_actor.id, object_type: 'friend', }, }
  46 +
  47 + # active
  48 + User.current = @actor.user
  49 +
  50 + blog = Blog.create! profile: @actor, name: 'blog'
  51 + blog_post = TinyMceArticle.new profile: User.current.person, parent: blog, name: 'blah', author: User.current.person
  52 + @publisher.expects(:publish).with(User.current.person, @stories[:create_an_article], @publisher.send(:url_for, blog_post))
  53 + blog_post.save!
  54 +
  55 + gallery = Gallery.create! name: 'gallery', profile: User.current.person
  56 + image = UploadedFile.new uploaded_data: fixture_file_upload('/files/rails.png', 'image/png'), parent: gallery, profile: User.current.person
  57 + @publisher.expects(:publish).with(User.current.person, @stories[:add_an_image], @publisher.send(:url_for, image, image.url.merge(view: true)))
  58 + image.save!
  59 +
  60 + document = UploadedFile.new uploaded_data: fixture_file_upload('/files/doctest.en.xhtml', 'text/html'), profile: User.current.person
  61 + @publisher.expects(:publish).with(User.current.person, @stories[:add_a_document], @publisher.send(:url_for, document, document.url.merge(view: true)))
  62 + document.save!
  63 +
  64 + event = Event.new name: 'event', profile: User.current.person
  65 + @publisher.expects(:publish).with(User.current.person, @stories[:create_an_event], @publisher.send(:url_for, event))
  66 + event.save!
  67 +
  68 + forum = Forum.create! name: 'forum', profile: User.current.person
  69 + topic = TinyMceArticle.new profile: User.current.person, parent: forum, name: 'blah2', author: User.current.person
  70 + @publisher.expects(:publish).with(User.current.person, @stories[:start_a_discussion], @publisher.send(:url_for, topic, topic.url.merge(og_type: MetadataPlugin.og_types[:forum])))
  71 + topic.save!
  72 +
  73 + @publisher.expects(:publish).with(@actor, @stories[:make_friendship_with], @publisher.send(:url_for, @other_actor)).twice
  74 + @publisher.expects(:publish).with(@other_actor, @stories[:make_friendship_with], @publisher.send(:url_for, @actor)).twice
  75 + AddFriend.create!(person: @actor, friend: @other_actor).finish
  76 + Friendship.remove_friendship @actor, @other_actor
  77 + # friend verb is groupable
  78 + AddFriend.create!(person: @actor, friend: @other_actor).finish
  79 +
  80 + @publisher.expects(:publish).with(User.current.person, @stories[:favorite_a_sse_initiative], @publisher.send(:url_for, @enterprise))
  81 + @enterprise.fans << User.current.person
  82 +
  83 + # active but published as passive
  84 + User.current = @actor.user
  85 +
  86 + blog_post = TinyMceArticle.new profile: @enterprise, parent: @enterprise.blog, name: 'blah', author: User.current.person
  87 + story = @stories[:announce_news_from_a_sse_initiative]
  88 + @publisher.expects(:publish).with(User.current.person, story, @publisher.send(:passive_url_for, blog_post, nil, story))
  89 + blog_post.save!
  90 +
  91 + # passive
  92 + User.current = @other_actor.user
  93 +
  94 + # fan
  95 + blog_post = TinyMceArticle.new profile: @enterprise, parent: @enterprise.blog, name: 'blah2', author: User.current.person
  96 + story = @stories[:announce_news_from_a_sse_initiative]
  97 + @publisher.expects(:publish).with(@actor, story, 'http://noosfero.net/coop/blog/blah2')
  98 + blog_post.save!
  99 + # member
  100 + blog_post = TinyMceArticle.new profile: @myenterprise, parent: @myenterprise.blog, name: 'blah2', author: User.current.person
  101 + story = @stories[:announce_news_from_a_sse_initiative]
  102 + @publisher.expects(:publish).with(@actor, story, 'http://noosfero.net/mycoop/blog/blah2')
  103 + blog_post.save!
  104 +
  105 + blog_post = TinyMceArticle.new profile: @community, parent: @community.blog, name: 'blah', author: User.current.person
  106 + story = @stories[:announce_news_from_a_community]
  107 + @publisher.expects(:publish).with(@actor, story, 'http://noosfero.net/comm/blog/blah')
  108 + blog_post.save!
  109 + end
  110 +
  111 +end
... ...
plugins/open_graph/views/open_graph_plugin/myprofile/_ac_profile.html.erb 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +<%= profile_image profile, :icon %>
  2 +<%= profile.short_name nil %>
... ...
plugins/open_graph/views/open_graph_plugin/myprofile/_heading.html.erb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +<span class='config-check fa'></span>
  2 +<%= f.fields_for "#{context}_settings" do |ff| %>
  3 + <%= ff.hidden_field klass.track_enabled_field, value: profile.send("#{context}_settings").send(klass.track_enabled_field), class: 'track-config-toggle' %>
  4 +<% end %>
  5 +<%= t("open_graph_plugin.views.track.config.#{track}.label") %>
... ...
plugins/open_graph/views/open_graph_plugin/myprofile/_profile_search.jsonify 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +self.formats = [:html]
  2 +profiles.each do |p|
  3 + json << {
  4 + value: p.id, label: render('open_graph_plugin/myprofile/ac_profile', profile: p),
  5 + }
  6 +end
  7 +
... ...
plugins/open_graph/views/open_graph_plugin/myprofile/_track_activity.html.erb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +<div class="panel-heading">
  2 + <%= button_to_function 'menu-ctrl-panel', t('open_graph_plugin.views.track.config.activity.configure'), "",
  3 + class: 'activity-config config-button', option: 'success', size: 'xs', 'data-target' => "#track-#{track}", 'data-toggle' => 'collapse', 'aria-controls' => 'collapseTwo' %>
  4 + <a href="#" onclick='return open_graph.track.config.toggleEvent(this, event)' class='activity-label'>
  5 + <%= render 'heading', f: f, track: track, context: context, klass: klass %>
  6 + </a>
  7 +</div>
  8 +
  9 +<div id="track-<%=track%>" class="panel-body collapse">
  10 + <%= render 'track_objects', f: f, track: track, objects: klass.objects, klass: klass, context: context %>
  11 +</div>
  12 +
... ...
plugins/open_graph/views/open_graph_plugin/myprofile/_track_community.html.erb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +<div class="panel-heading open-on-enable">
  2 + <%= button_to_function 'menu-ctrl-panel', t('open_graph_plugin.views.track.config.activity.configure'), "",
  3 + class: 'community-config config-button', option: 'success', size: 'xs', 'data-target' => "#track-#{track}", 'data-toggle' => 'collapse', 'aria-controls' => 'collapseTwo' %>
  4 + <a href="#track-<%=track%>" onclick='open_graph.track.config.toggleEvent(this, event)', class='community-label'>
  5 + <%= render 'heading', f: f, track: track, context: context, klass: klass %>
  6 + </a>
  7 +</div>
  8 +
  9 +<div id="track-<%=track%>" class="panel-body collapse">
  10 + <%= render 'track_objects', f: f, track: track, objects: klass.objects, klass: klass, context: context %>
  11 + <%= render 'track_profiles', f: f, track: track, context: context, klass: klass %>
  12 +</div>
... ...
plugins/open_graph/views/open_graph_plugin/myprofile/_track_enterprise.html.erb 0 → 100644
... ... @@ -0,0 +1,44 @@
  1 +<div class="panel-heading enable-on-empty">
  2 + <%= button_to_function 'menu-ctrl-panel', t('open_graph_plugin.views.track.config.enterprise.see_all'), 'open_graph.track.config.enterprise.see_all(this)',
  3 + class: 'sse-config config-button', option: 'success', size: 'xs' %>
  4 + <a href="#" onclick='return open_graph.track.config.toggleEvent(this, event)' style='sse-label'>
  5 + <%= render 'heading', f: f, track: track, context: context, klass: klass %>
  6 + </a>
  7 +</div>
  8 +
  9 +<div id="track-<%=track%>" class="panel-body collapse" style="display: none">
  10 + <% if user.enterprises.present? %>
  11 + <div id="enterprises-memberships" class="open-graph-enterprises-modal">
  12 + <h1>
  13 + <%= t('open_graph_plugin.views.track.config.enterprise.memberships') %>
  14 + </h1>
  15 + <div class="open-graph-enterprises-list">
  16 + <% user.enterprises.public.enabled.each do |enterprise| %>
  17 + <%= profile_image_link enterprise, :portrait, :div %>
  18 + <% end %>
  19 + </div>
  20 + </div>
  21 + <% end %>
  22 +
  23 + <div id="favorite-enterprises" class="open-graph-enterprises-modal">
  24 + <h1>
  25 + <%= t('open_graph_plugin.views.track.config.enterprise.favorites') %>
  26 + </h1>
  27 + <% if user.favorite_enterprises.present? %>
  28 + <div class="open-graph-enterprises-list">
  29 + <% user.favorite_enterprises.public.enabled.each do |enterprise| %>
  30 + <%= profile_image_link enterprise, :portrait, :div %>
  31 + <% end %>
  32 + </div>
  33 + <% end %>
  34 + <div id='open-graph-favorite-enterprises-how-to'>
  35 + <h1>
  36 + <%= t('open_graph_plugin.views.track.config.enterprise.favorites_how_to.title') %>
  37 + </h1>
  38 + <p>
  39 + <%= t 'open_graph_plugin.views.track.config.enterprise.favorites_how_to.body', favorite_button: button_to_function(:love, _('Add as favorite'), '', :title => _('Add enterprise as favorite'), :option => 'success') %>
  40 + </p>
  41 + </div>
  42 + </div>
  43 +</div>
  44 +
... ...
plugins/open_graph/views/open_graph_plugin/myprofile/_track_form.html.erb 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +<%
  2 + reload ||= false
  3 +%>
  4 +<%= javascript_tag do %>
  5 + open_graph.track.config.reload = <%= reload.to_json %>
  6 +<% end %>
  7 +
  8 +<%= form_for profile, as: :profile_data, remote: true, url: {action: :track_config},
  9 + html: {id: 'track-config', onsubmit: 'return open_graph.track.config.submit()'} do |f| %>
  10 +
  11 + <div class="panel-group" role="tablist" aria-multiselectable="true">
  12 + <% OpenGraphPlugin::TrackConfig::Types.each do |track, klass| %>
  13 + <div class="panel panel-primary">
  14 + <%= render "track_#{track}", f: f, track: track, klass: "OpenGraphPlugin::#{klass}".constantize, context: context %>
  15 + </div>
  16 + <% end %>
  17 + </div>
  18 +<% end %>
  19 +
  20 +<%= javascript_tag do %>
  21 + open_graph.track.config.init()
  22 +<% end %>
... ...
plugins/open_graph/views/open_graph_plugin/myprofile/_track_friend.html.erb 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +<div class="panel-heading">
  2 + <a href="#track-<%=track%>" data-toggle="collapse" aria-controls="collapseTwo" onclick='open_graph.track.config.toggleEvent(this, event)'>
  3 + <%= render 'heading', f: f, track: track, context: context, klass: klass %>
  4 + </a>
  5 +</div>
  6 +
  7 +<div id="track-<%=track%>" class="panel-body collapse">
  8 + <%= render 'track_objects', f: f, track: track, objects: klass.objects, klass: klass, context: context %>
  9 + <%= render 'track_profiles', f: f, track: track, context: context, klass: klass %>
  10 +</div>
... ...
plugins/open_graph/views/open_graph_plugin/myprofile/_track_objects.html.erb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +<%
  2 + tracks = profile.send klass.association
  3 +%>
  4 +
  5 +<% objects.each do |object| %>
  6 + <div id="object-<%= object %>" class="tracked-object">
  7 + <% track_record = tracks.find{ |t| t.object_type == object } || profile.send(klass.association).build %>
  8 + <%= f.fields_for klass.association, track_record do |ff| %>
  9 + <%= ff.hidden_field :id %>
  10 + <%= ff.hidden_field :tracker_id %>
  11 + <%= ff.check_box :object_type, {onchange: 'open_graph.track.config.toggleObjectType(this)'}, object, '' %>
  12 + <%= ff.label :object_type, t("open_graph_plugin.views.track.config.#{track}.objects.#{object}") %>
  13 + <%= ff.hidden_field :_destroy %>
  14 + <% end %>
  15 + </div>
  16 +<% end %>
  17 +
... ...
plugins/open_graph/views/open_graph_plugin/myprofile/_track_profiles.html.erb 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +<%
  2 + static = klass.static_trackers
  3 + profiles = klass.profile_track_objects profile
  4 +%>
  5 +<%= text_field_tag "#{f.object_name}[open_graph_#{track}_profiles_ids]", '', id: "select-#{track}", class: 'select-profiles',
  6 + placeholder: (t("open_graph_plugin.views.track.config.#{track}.search_placeholder") unless static),
  7 + disabled: ("disabled" if static) %>
  8 +
  9 +<%= javascript_tag do %>
  10 + $(document).ready(function () {
  11 + var input = open_graph.track.config.initAutocomplete(<%=track.to_json%>,
  12 + <%= url_for(action: "#{track}_search").to_json %>,
  13 + <%= profiles.map{ |p| {value: p.id, label: render('ac_profile', profile: p), } }.to_json %>
  14 + )
  15 + <% if static %>
  16 + input.tokenfield('readonly')
  17 + <% end %>
  18 + })
  19 +<% end %>
... ...