[Git][noosfero/noosfero][master] 2 commits: Add abstract plugin to federate networks via OpenGraph

Bráulio Bhavamitra gitlab at gitlab.com
Wed Aug 12 23:30:05 BRT 2015


Bráulio Bhavamitra pushed to branch master at Noosfero / noosfero


Commits:
be3b210f by Braulio Bhavamitra at 2015-08-12T23:14:30Z
Add abstract plugin to federate networks via OpenGraph

- - - - -
180ad529 by Bráulio Bhavamitra at 2015-08-13T02:29:49Z
Merge branch 'open_graph' into 'master'

Abstract plugin to federate networks via OpenGraph

Depends on: !362 !345 !473 !482

See merge request !512

- - - - -


43 changed files:

- app/models/article.rb
- app/models/uploaded_file.rb
- lib/tasks/plugins_tests.rake
- + plugins/open_graph/Gemfile
- + plugins/open_graph/controllers/myprofile/open_graph_plugin/myprofile_controller.rb
- + plugins/open_graph/db/migrate/20141031130250_create_open_graph_plugin_tracks.rb
- + plugins/open_graph/install.rb
- + plugins/open_graph/lib/ext/article.rb
- + plugins/open_graph/lib/ext/profile.rb
- + plugins/open_graph/lib/ext/profile_activity.rb
- + plugins/open_graph/lib/ext/uploaded_file.rb
- + plugins/open_graph/lib/open_graph_plugin.rb
- + plugins/open_graph/lib/open_graph_plugin/attach_stories.rb
- + plugins/open_graph/lib/open_graph_plugin/base.rb
- + plugins/open_graph/lib/open_graph_plugin/display_helper.rb
- + plugins/open_graph/lib/open_graph_plugin/publisher.rb
- + plugins/open_graph/lib/open_graph_plugin/settings.rb
- + plugins/open_graph/lib/open_graph_plugin/stories.rb
- + plugins/open_graph/locales/en-US.yml
- + plugins/open_graph/locales/pt-BR.yml
- + plugins/open_graph/models/open_graph_plugin/activity.rb
- + plugins/open_graph/models/open_graph_plugin/activity_track_config.rb
- + plugins/open_graph/models/open_graph_plugin/community_track_config.rb
- + plugins/open_graph/models/open_graph_plugin/enterprise_track_config.rb
- + plugins/open_graph/models/open_graph_plugin/friend_track_config.rb
- + plugins/open_graph/models/open_graph_plugin/track.rb
- + plugins/open_graph/models/open_graph_plugin/track_config.rb
- + plugins/open_graph/plugin.yml
- + plugins/open_graph/public/javascripts/open_graph.js
- + plugins/open_graph/public/style.scss
- + plugins/open_graph/public/stylesheets/style.scss
- + plugins/open_graph/test/functional/open_graph_graph/my_profile_controller_test.rb
- + plugins/open_graph/test/unit/open_graph_graph/publisher_test.rb
- + plugins/open_graph/views/open_graph_plugin/myprofile/_ac_profile.html.erb
- + plugins/open_graph/views/open_graph_plugin/myprofile/_heading.html.erb
- + plugins/open_graph/views/open_graph_plugin/myprofile/_profile_search.jsonify
- + plugins/open_graph/views/open_graph_plugin/myprofile/_track_activity.html.erb
- + plugins/open_graph/views/open_graph_plugin/myprofile/_track_community.html.erb
- + plugins/open_graph/views/open_graph_plugin/myprofile/_track_enterprise.html.erb
- + plugins/open_graph/views/open_graph_plugin/myprofile/_track_form.html.erb
- + plugins/open_graph/views/open_graph_plugin/myprofile/_track_friend.html.erb
- + plugins/open_graph/views/open_graph_plugin/myprofile/_track_objects.html.erb
- + plugins/open_graph/views/open_graph_plugin/myprofile/_track_profiles.html.erb


Changes:

=====================================
app/models/article.rb
=====================================
--- a/app/models/article.rb
+++ b/app/models/article.rb
@@ -8,7 +8,8 @@ class Article < ActiveRecord::Base
                   :accept_comments, :feed, :published, :source, :source_name,
                   :highlighted, :notify_comments, :display_hits, :slug,
                   :external_feed_builder, :display_versions, :external_link,
-                  :image_builder, :show_to_followers
+                  :image_builder, :show_to_followers,
+                  :author
 
   acts_as_having_image
 


=====================================
app/models/uploaded_file.rb
=====================================
--- a/app/models/uploaded_file.rb
+++ b/app/models/uploaded_file.rb
@@ -163,4 +163,8 @@ class UploadedFile < Article
     true
   end
 
+  def notifiable?
+    true
+  end
+
 end


=====================================
lib/tasks/plugins_tests.rake
=====================================
--- a/lib/tasks/plugins_tests.rake
+++ b/lib/tasks/plugins_tests.rake
@@ -169,7 +169,7 @@ def test_sequence(plugins, tasks)
         failed[plugin] << task
       end
     end
-    disable_plugins(plugin)
+    disable_plugins
   end
   fail_flag = false
   failed.each do |plugin, tasks|


=====================================
plugins/open_graph/Gemfile
=====================================
--- /dev/null
+++ b/plugins/open_graph/Gemfile
@@ -0,0 +1,3 @@
+
+gem 'jsonify-rails'
+


=====================================
plugins/open_graph/controllers/myprofile/open_graph_plugin/myprofile_controller.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/controllers/myprofile/open_graph_plugin/myprofile_controller.rb
@@ -0,0 +1,47 @@
+class OpenGraphPlugin::MyprofileController < MyProfileController
+
+  protect 'edit_profile', :profile
+  before_filter :set_context
+
+  def enterprise_search
+    scope = environment.enterprises.enabled.public
+    profile_search scope
+  end
+  def community_search
+    scope = environment.communities.public
+    profile_search scope
+  end
+  def friend_search
+    scope = profile.friends
+    profile_search scope
+  end
+
+  def track_config
+    profile.update_attributes! params[:profile_data]
+    render partial: 'track_form', locals: {context: context, reload: true}
+  end
+
+  protected
+
+  def profile_search scope
+    @query = params[:query]
+    @profiles = scope.limit(10).order('name ASC').
+      where(['name ILIKE ? OR name ILIKE ? OR identifier LIKE ?', "#{@query}%", "% #{@query}%", "#{@query}%"])
+    render partial: 'profile_search', locals: {profiles: @profiles}
+  end
+
+  def context
+    :open_graph
+  end
+
+  def set_context
+    OpenGraphPlugin.context = self.context
+  end
+
+  def default_url_options
+    # avoid rails' use_relative_controller!
+    {use_route: '/'}
+  end
+
+end
+


=====================================
plugins/open_graph/db/migrate/20141031130250_create_open_graph_plugin_tracks.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/db/migrate/20141031130250_create_open_graph_plugin_tracks.rb
@@ -0,0 +1,36 @@
+class CreateOpenGraphPluginTracks < ActiveRecord::Migration
+  def up
+    create_table :open_graph_plugin_tracks do |t|
+      t.string :type
+      t.string :context
+      t.boolean :enabled, default: true
+
+      t.integer :tracker_id
+
+      t.integer :actor_id
+
+      t.string :action
+
+      t.string :object_type
+      t.text :object_data_url
+      t.integer :object_data_id
+      t.string :object_data_type
+
+      t.timestamps
+    end
+
+    add_index :open_graph_plugin_tracks, [:type]
+    add_index :open_graph_plugin_tracks, [:context]
+    add_index :open_graph_plugin_tracks, [:type, :context]
+    add_index :open_graph_plugin_tracks, [:actor_id]
+    add_index :open_graph_plugin_tracks, [:action]
+    add_index :open_graph_plugin_tracks, [:object_type]
+    add_index :open_graph_plugin_tracks, [:enabled]
+    add_index :open_graph_plugin_tracks, [:object_data_url]
+    add_index :open_graph_plugin_tracks, [:object_data_id, :object_data_type], name: 'index_open_graph_plugin_tracks_object_data_id_type'
+  end
+
+  def down
+    drop_table :open_graph_plugin_tracks
+  end
+end


=====================================
plugins/open_graph/install.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/install.rb
@@ -0,0 +1,2 @@
+system "script/noosfero-plugins -q enable metadata"
+


=====================================
plugins/open_graph/lib/ext/article.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/lib/ext/article.rb
@@ -0,0 +1,15 @@
+require_dependency 'article'
+
+class Article
+
+  after_update :open_graph_scrape
+
+  protected
+
+  def open_graph_scrape
+    activity = OpenGraphPlugin::Activity.where(object_data_id: self.id, object_data_type: self.class.base_class.name).first
+    activity.scrape if activity
+  end
+  handle_asynchronously :open_graph_scrape
+
+end


=====================================
plugins/open_graph/lib/ext/profile.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/lib/ext/profile.rb
@@ -0,0 +1,59 @@
+require_dependency 'profile'
+# hate to wrte this, but without Noosfero::Plugin::Settings is loaded instead
+require 'open_graph_plugin/settings'
+
+# attr_accessible must be defined on subclasses
+Profile.descendants.each do |subclass|
+  subclass.class_eval do
+    attr_accessible :open_graph_settings
+
+    OpenGraphPlugin::TrackConfig::Types.each do |track, klass|
+      klass = "OpenGraphPlugin::#{klass}".constantize
+      attributes = "#{klass.association}_attributes"
+      profile_ids = "open_graph_#{track}_profiles_ids"
+
+      attr_accessible attributes
+      attr_accessible profile_ids
+    end
+  end
+end
+
+class Profile
+
+  def open_graph_settings attrs = {}
+    @open_graph_settings ||= OpenGraphPlugin::Settings.new self, attrs
+    attrs.each{ |a, v| @open_graph_settings.send "#{a}=", v }
+    @open_graph_settings
+  end
+  alias_method :open_graph_settings=, :open_graph_settings
+
+  has_many :open_graph_tracks, class_name: 'OpenGraphPlugin::Track', source: :tracker_id, foreign_key: :tracker_id
+
+  has_many :open_graph_activities, class_name: 'OpenGraphPlugin::Activity', source: :tracker_id, foreign_key: :tracker_id
+
+  has_many :open_graph_track_configs, class_name: 'OpenGraphPlugin::TrackConfig', source: :tracker_id, foreign_key: :tracker_id
+  OpenGraphPlugin::TrackConfig::Types.each do |track, klass|
+    klass = "OpenGraphPlugin::#{klass}".constantize
+    association = klass.association
+    profile_ids = "open_graph_#{track}_profiles_ids"
+
+    has_many association, class_name: klass.name, foreign_key: :tracker_id
+    accepts_nested_attributes_for association, allow_destroy: true, reject_if: :open_graph_reject_empty_object_type
+
+    define_method "#{profile_ids}=" do |ids|
+      cids = self.send(association).order('created_at ASC').map(&:object_data_id)
+      nids = if ids.is_a? Array then ids else ids.split ',' end
+      nids = nids.map(&:to_i)
+      Profile.where(id: nids-cids).each{ |profile| self.send(association).create! type: klass.name, object_data: profile }
+      self.send(association).each{ |c| c.destroy unless c.object_data_id.in? nids }
+    end
+
+  end
+
+  define_method :open_graph_reject_empty_object_type do |attributes|
+    exists = attributes[:id].present?
+    empty = attributes[:object_type].empty?
+    attributes.merge! _destroy: 1 if exists and empty
+    return (!exists and empty)
+  end
+end


=====================================
plugins/open_graph/lib/ext/profile_activity.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/lib/ext/profile_activity.rb
@@ -0,0 +1,19 @@
+require_dependency 'profile_activity'
+
+class ProfileActivity
+
+  # update happens with grouped ActionTracker
+  after_save :open_graph_publish
+
+  def open_graph_publish
+    # Scrap not yet supported
+    if self.activity.is_a? ActionTracker::Record
+      verb = self.activity.verb.to_sym
+      return unless object = self.activity.target
+      return unless stories = OpenGraphPlugin::Stories::TrackerStories[verb]
+      OpenGraphPlugin::Stories.publish object, stories
+    end
+  end
+
+end
+


=====================================
plugins/open_graph/lib/ext/uploaded_file.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/lib/ext/uploaded_file.rb
@@ -0,0 +1,8 @@
+require_dependency 'uploaded_file'
+
+class UploadedFile
+
+  extend OpenGraphPlugin::AttachStories::ClassMethods
+  open_graph_attach_stories only: :add_an_image
+
+end


=====================================
plugins/open_graph/lib/open_graph_plugin.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/lib/open_graph_plugin.rb
@@ -0,0 +1,21 @@
+module OpenGraphPlugin
+
+  extend Noosfero::Plugin::ParentMethods
+
+  def self.plugin_name
+    I18n.t 'open_graph_plugin.lib.plugin.name'
+  end
+
+  def self.plugin_description
+    I18n.t 'open_graph_plugin.lib.plugin.description'
+  end
+
+  def self.context
+    Thread.current[:open_graph_context] || :open_graph
+  end
+  def self.context= value
+    Thread.current[:open_graph_context] = value
+  end
+
+end
+


=====================================
plugins/open_graph/lib/open_graph_plugin/attach_stories.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/lib/open_graph_plugin/attach_stories.rb
@@ -0,0 +1,44 @@
+require_dependency 'open_graph_plugin/stories'
+
+# This is used when ActionTracker is not compartible with the way
+module OpenGraphPlugin::AttachStories
+
+  module ClassMethods
+
+    def open_graph_attach_stories options={}
+      if stories = Array(options[:only])
+        callbacks = {}
+        stories.each do |story|
+          defs = OpenGraphPlugin::Stories::Definitions[story]
+          Array(defs[:on]).each do |on|
+            callbacks[on] ||= []
+            callbacks[on] << story
+          end
+        end
+      else
+        klass = self.name
+        callbacks = OpenGraphPlugin::Stories::ModelStories[klass.to_sym]
+        return if callbacks.blank?
+      end
+
+      callbacks.each do |on, stories|
+        # subclasses may override this, but the callback is called only once
+        method = "open_graph_publish_after_#{on}"
+
+        self.send "after_#{on}", method
+        # buggy with rails 3.2
+        #self.send "after_commit", method, on: on
+
+        define_method method do
+          OpenGraphPlugin::Stories.publish self, stories
+        end
+      end
+    end
+
+  end
+
+  module InstanceMethods
+
+  end
+
+end


=====================================
plugins/open_graph/lib/open_graph_plugin/base.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/lib/open_graph_plugin/base.rb
@@ -0,0 +1,15 @@
+
+class OpenGraphPlugin::Base < Noosfero::Plugin
+
+  def js_files
+    [].map{ |j| "javascripts/#{j}" }
+  end
+
+  def stylesheet?
+    true
+  end
+
+end
+
+ActiveSupport.run_load_hooks :open_graph_plugin, OpenGraphPlugin
+


=====================================
plugins/open_graph/lib/open_graph_plugin/display_helper.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/lib/open_graph_plugin/display_helper.rb
@@ -0,0 +1,7 @@
+
+module OpenGraphPlugin::DisplayHelper
+
+  def blah
+    puts 'here'
+  end
+end


=====================================
plugins/open_graph/lib/open_graph_plugin/publisher.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/lib/open_graph_plugin/publisher.rb
@@ -0,0 +1,166 @@
+
+class OpenGraphPlugin::Publisher
+
+  attr_accessor :actions
+  attr_accessor :objects
+
+  def self.default
+    @default ||= self.new
+  end
+
+  def initialize attributes = {}
+    # defaults
+    self.actions = OpenGraphPlugin::Stories::DefaultActions
+    self.objects = OpenGraphPlugin::Stories::DefaultObjects
+
+    attributes.each do |attr, value|
+      self.send "#{attr}=", value
+    end
+  end
+
+  def publish actor, story_defs, object_data_url
+    raise 'abstract method called'
+  end
+
+  def publish_stories object_data, actor, stories
+    stories.each do |story|
+      begin
+        self.publish_story object_data, actor, story
+      rescue => e
+        ExceptionNotifier.notify_exception e
+      end
+    end
+  end
+
+  def update_delay
+    1.day
+  end
+
+  # only publish recent objects to avoid multiple publications
+  def recent_publish? actor, object_type, object_data_url
+    activity_params = {actor_id: actor.id, object_type: object_type, object_data_url: object_data_url}
+    activity = OpenGraphPlugin::Activity.where(activity_params).first
+    activity.present? and activity.created_at <= self.update_delay.from_now
+  end
+
+  def publish_story object_data, actor, story
+    OpenGraphPlugin.context = self.context
+    defs = OpenGraphPlugin::Stories::Definitions[story]
+    passive = defs[:passive]
+
+    print_debug "open_graph: publish_story #{story}" if debug? actor
+    match_criteria = if (ret = self.call defs[:criteria], object_data, actor).nil? then true else ret end
+    return unless match_criteria
+    print_debug "open_graph: #{story} match criteria" if debug? actor
+    match_condition = if (ret = self.call defs[:publish_if], object_data, actor).nil? then true else ret end
+    return unless match_condition
+    print_debug "open_graph: #{story} match publish_if" if debug? actor
+
+    actors = self.story_trackers defs, actor, object_data
+    return if actors.blank?
+    print_debug "open_graph: #{story} has enabled trackers" if debug? actor
+
+    if publish = defs[:publish]
+      begin
+        instance_exec actor, object_data, &publish
+      rescue => e
+        print_debug "open_graph: can't publish story: #{e.message}" if debug? actor
+        ExceptionNotifier.notify_exception e
+      end
+    else
+      # force profile identifier for custom domains and fixed host. see og_url_for
+      object_profile = self.call(story_defs[:object_profile], object_data) || object_data.profile rescue nil
+      extra_params = if object_profile then {profile: object_profile.identifier} else {} end
+
+      custom_object_data_url = self.call defs[:object_data_url], object_data, actor
+      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
+
+      actors.each do |actor|
+        print_debug "open_graph: start publishing" if debug? actor
+        begin
+          self.publish actor, defs, object_data_url
+        rescue => e
+          print_debug "open_graph: can't publish story: #{e.message}" if debug? actor
+          ExceptionNotifier.notify_exception e
+        end
+      end
+    end
+  end
+
+  def story_trackers story_defs, actor, object_data
+    passive = story_defs[:passive]
+    trackers = []
+
+    track_configs = Array(story_defs[:track_config]).compact.map(&:constantize)
+    return if track_configs.empty?
+    print_debug "open_graph: using configs: #{track_configs.map(&:name).inspect}" if debug? actor
+
+    if passive
+      object_profile = self.call(story_defs[:object_profile], object_data) || object_data.profile rescue nil
+      return unless object_profile
+
+      track_configs.each do |c|
+        trackers.concat c.trackers_to_profile(object_profile)
+      end.flatten
+
+      trackers.select! do |t|
+        track_configs.any?{ |c| c.enabled? self.context, t }
+      end
+    else #active
+      object_actor = self.call(story_defs[:object_actor], object_data) || object_data.profile rescue nil
+      return unless object_actor and object_actor.person?
+      custom_actor = self.call(story_defs[:custom_actor], object_data)
+      actor = custom_actor if custom_actor
+
+      match_track = track_configs.any? do |c|
+        c.enabled?(self.context, actor) and
+          actor.send("open_graph_#{c.track_name}_track_configs").where(object_type: story_defs[:object_type]).first
+      end
+      trackers << actor if match_track
+    end
+
+    trackers
+  end
+
+  protected
+
+  include MetadataPlugin::UrlHelper
+
+  def register_publish attributes
+    OpenGraphPlugin::Activity.create! attributes
+  end
+
+  # Call don't ask: move to a og_url method inside object
+  def url_for object, custom_url=nil, extra_params={}
+    return custom_url if custom_url.is_a? String
+    url = custom_url || if object.is_a? Profile then og_profile_url object else object.url end
+    # for profile when custom domain is used
+    url.merge! profile: object.profile.identifier if object.respond_to? :profile
+    url.merge! extra_params
+    self.og_url_for url
+  end
+
+  def passive_url_for object, custom_url, story_defs, extra_params={}
+    object_type = story_defs[:object_type]
+    extra_params.merge! og_type: MetadataPlugin.og_types[object_type]
+    self.url_for object, custom_url, extra_params
+  end
+
+  def call p, *args
+    p and instance_exec *args, &p
+  end
+
+  def context
+    :open_graph
+  end
+
+  def print_debug msg
+    puts msg
+    Delayed::Worker.logger.debug msg
+  end
+  def debug? actor=nil
+    !Rails.env.production?
+  end
+
+end
+


=====================================
plugins/open_graph/lib/open_graph_plugin/settings.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/lib/open_graph_plugin/settings.rb
@@ -0,0 +1,14 @@
+class OpenGraphPlugin::Settings < Noosfero::Plugin::Settings
+
+  def self.new base, attrs = {}
+    super base, self.parents.first, attrs
+  end
+
+  OpenGraphPlugin::TrackConfig::Types.each do |track, klass|
+    define_method "#{track}_track_enabled=" do |value|
+      super ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
+    end
+  end
+
+end
+


=====================================
plugins/open_graph/lib/open_graph_plugin/stories.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/lib/open_graph_plugin/stories.rb
@@ -0,0 +1,302 @@
+
+class OpenGraphPlugin::Stories
+
+  class_attribute :publishers
+  self.publishers = []
+
+  def self.register_publisher publisher
+    self.publishers << publisher
+  end
+
+  def self.publish record, stories
+    actor = User.current.person rescue nil
+    return unless actor
+
+    self.publishers.each do |publisher|
+      publisher = publisher.delay unless Rails.env.development? or Rails.env.test?
+      publisher.publish_stories record, actor, stories
+    end
+  end
+
+  Definitions = {
+    # needed a patch on UploadedFile: def notifiable?; true; end
+    add_a_document: {
+      action_tracker_verb: :create_article,
+      track_config: 'OpenGraphPlugin::ActivityTrackConfig',
+      action: :add,
+      object_type: :uploaded_file,
+      models: :UploadedFile,
+      on: :create,
+      criteria: proc do |article, actor|
+        article.is_a? UploadedFile
+      end,
+      publish_if: proc do |uploaded_file, actor|
+        # done in add_an_image
+        next false if uploaded_file.image?
+        uploaded_file.published?
+      end,
+      object_data_url: proc do |uploaded_file, actor|
+        uploaded_file.url.merge view: true
+      end,
+    },
+    add_an_image: {
+      # :upload_image verb can't be used as it uses the parent Gallery as target
+      # hooked via open_graph_attach_stories
+      action_tracker_verb: nil,
+      track_config: 'OpenGraphPlugin::ActivityTrackConfig',
+      action: :add,
+      object_type: :gallery_image,
+      models: :UploadedFile,
+      on: :create,
+      criteria: proc do |article, actor|
+        article.is_a? UploadedFile
+      end,
+      publish_if: proc do |uploaded_file, actor|
+        uploaded_file.image? and uploaded_file.parent.is_a? Gallery
+      end,
+      object_data_url: proc do |uploaded_file, actor|
+        uploaded_file.url.merge view: true
+      end,
+    },
+    create_an_article: {
+      action_tracker_verb: :create_article,
+      track_config: 'OpenGraphPlugin::ActivityTrackConfig',
+      action: :create,
+      object_type: :blog_post,
+      models: :Article,
+      on: :create,
+      criteria: proc do |article, actor|
+        article.parent.is_a? Blog
+      end,
+      publish_if: proc do |article, actor|
+        article.published?
+      end,
+    },
+    create_an_event: {
+      action_tracker_verb: :create_article,
+      track_config: 'OpenGraphPlugin::ActivityTrackConfig',
+      action: :create,
+      object_type: :event,
+      models: :Event,
+      on: :create,
+      criteria: proc do |article, actor|
+        article.is_a? Event
+      end,
+      publish_if: proc do |event, actor|
+        event.published?
+      end,
+    },
+    start_a_discussion: {
+      action_tracker_verb: :create_article,
+      track_config: 'OpenGraphPlugin::ActivityTrackConfig',
+      action: :start,
+      object_type: :forum,
+      models: :Article,
+      on: :create,
+      criteria: proc do |article, actor|
+        article.parent.is_a? Forum
+      end,
+      publish_if: proc do |article, actor|
+        article.published?
+      end,
+      object_data_url: proc do |article, actor|
+        url = article.url
+        if og_type = MetadataPlugin::og_types[:forum]
+          url[:og_type] = og_type
+        end
+        url
+      end,
+    },
+
+    # these a published as passive to give focus to the enterprise
+=begin
+    add_a_sse_product: {
+      action_tracker_verb: :create_product,
+      track_config: 'OpenGraphPlugin::ActivityTrackConfig',
+      action: :announce_new,
+      models: :Product,
+      on: :create,
+      object_type: :product,
+      publish_if: proc do |product, actor|
+        product.profile.public?
+      end,
+    },
+    update_a_sse_product: {
+      action_tracker_verb: :update_product,
+      track_config: 'OpenGraphPlugin::ActivityTrackConfig',
+      action: :announce_update,
+      object_type: :product,
+      models: :Product,
+      on: :update,
+      publish_if: proc do |product, actor|
+        product.profile.public?
+      end,
+    },
+=end
+
+    favorite_a_sse_initiative: {
+      action_tracker_verb: :favorite_enterprise,
+      track_config: 'OpenGraphPlugin::ActivityTrackConfig',
+      action: :favorite,
+      object_type: :favorite_enterprise,
+      models: :FavoriteEnterprisePerson,
+      on: :create,
+      object_actor: proc do |favorite_enterprise_person|
+        favorite_enterprise_person.person
+      end,
+      object_profile: proc do |favorite_enterprise_person|
+        favorite_enterprise_person.enterprise
+      end,
+      object_data_url: proc do |favorite_enterprise_person, actor|
+        self.og_profile_url favorite_enterprise_person.enterprise
+      end,
+    },
+
+=begin
+    comment_a_discussion: {
+      action_tracker_verb: nil,
+      action: :comment,
+      object_type: :forum,
+      models: :Comment,
+      on: :create,
+      criteria: proc do |comment, actor|
+        source, parent = comment.source, comment.source.parent
+        source.is_a? Article and parent.is_a? Forum
+      end,
+      publish_if: proc do |comment, actor|
+        comment.source.parent.published?
+      end,
+    },
+    comment_an_article: {
+      action_tracker_verb: nil,
+      action: :comment,
+      object_type: :blog_post,
+      models: :Comment,
+      on: :create,
+      criteria: proc do |comment, actor|
+        source, parent = comment.source, comment.source.parent
+        source.is_a? Article and parent.is_a? Blog
+      end,
+      publish_if: proc do |comment, actor|
+        comment.source.parent.published?
+      end,
+    },
+=end
+
+    make_friendship_with: {
+      action_tracker_verb: :new_friendship,
+      track_config: 'OpenGraphPlugin::ActivityTrackConfig',
+      action: :make_friendship,
+      object_type: :friend,
+      models: :Friendship,
+      on: :create,
+      custom_actor: proc do |friendship|
+        friendship.person
+      end,
+      object_actor: proc do |friendship|
+        friendship.person
+      end,
+      object_profile: proc do |friendship|
+        friendship.friend
+      end,
+      object_data_url: proc do |friendship, actor|
+        self.og_profile_url friendship.friend
+      end,
+    },
+
+    # PASSIVE STORIES
+    announce_news_from_a_sse_initiative: {
+      action_tracker_verb: :create_article,
+      track_config: 'OpenGraphPlugin::EnterpriseTrackConfig',
+      action: :announce_news,
+      object_type: :enterprise,
+      passive: true,
+      models: :Article,
+      on: :create,
+      criteria: proc do |article, actor|
+        article.profile.enterprise?
+      end,
+      publish_if: proc do |article, actor|
+        article.published?
+      end,
+    },
+    announce_a_new_sse_product: {
+      action_tracker_verb: :create_product,
+      track_config: 'OpenGraphPlugin::EnterpriseTrackConfig',
+      action: :announce_new,
+      object_type: :product,
+      passive: true,
+      models: :Product,
+      on: :create,
+      criteria: proc do |product, actor|
+        product.profile.enterprise?
+      end,
+    },
+    announce_an_update_of_sse_product: {
+      action_tracker_verb: :update_product,
+      track_config: 'OpenGraphPlugin::EnterpriseTrackConfig',
+      action: :announce_update,
+      object_type: :product,
+      passive: true,
+      models: :Product,
+      on: :update,
+      criteria: proc do |product, actor|
+        product.profile.enterprise?
+      end,
+    },
+
+    announce_news_from_a_community: {
+      action_tracker_verb: :create_article,
+      track_config: 'OpenGraphPlugin::CommunityTrackConfig',
+      action: :announce_news,
+      object_type: :community,
+      passive: true,
+      models: :Article,
+      on: :create,
+      criteria: proc do |article, actor|
+        article.profile.community?
+      end,
+      publish_if: proc do |article, actor|
+        article.published?
+      end,
+    },
+
+  }
+
+  ValidObjectList = Definitions.map{ |story, data| data[:object_type] }.uniq
+  ValidActionList = Definitions.map{ |story, data| data[:action] }.uniq
+
+  # TODO make this verification work
+  #raise "Each active story must use a unique object_type for configuration to work" if ValidObjectList.size < Definitions.size
+
+  DefaultActions = ValidActionList.inject({}){ |h, a| h[a] = a; h }
+  DefaultObjects = ValidObjectList.inject({}){ |h, o| h[o] = o; h }
+
+  TrackerStories = {}; Definitions.each do |story, data|
+    Array(data[:action_tracker_verb]).each do |verb|
+      next unless verb
+      TrackerStories[verb] ||= []
+      TrackerStories[verb] << story
+    end
+  end
+
+  TrackConfigStories = {}; Definitions.each do |story, data|
+    Array(data[:track_config]).each do |track_config|
+      next unless track_config
+      TrackConfigStories[track_config] ||= []
+      TrackConfigStories[track_config] << [story, data]
+    end
+  end
+
+  ModelStories = {}; Definitions.each do |story, data|
+    Array(data[:models]).each do |model|
+      ModelStories[model] ||= {}
+      Array(data[:on]).each do |on|
+        ModelStories[model][on] ||= []
+        ModelStories[model][on] << story
+      end
+    end
+  end
+
+end
+


=====================================
plugins/open_graph/locales/en-US.yml
=====================================
--- /dev/null
+++ b/plugins/open_graph/locales/en-US.yml
@@ -0,0 +1,43 @@
+
+"en-US": &en-US
+
+  open_graph_plugin:
+    lib:
+      plugin:
+        name: 'OpenGraph'
+        description: 'OpenGraph'
+    views:
+      track:
+        config:
+          activity:
+            configure: 'Configure'
+            label: "My activities: new photos on my albuns, blogs's posts and other contents"
+            objects:
+              blog_post: "Blogs' posts"
+              event: 'Creation of events'
+              favorite_enterprise: 'Quando eu favoritar um empreendimento'
+              forum: "Forum's topic posted"
+              friend: 'New friendships'
+              gallery_image: 'New images on my albuns'
+              uploaded_file: 'Files sent'
+          enterprise:
+            memberships: "Enterprises that I'm a member of"
+            favorites: "My favorite enterprises" 
+            see_all: 'See enterprises'
+            label: "News from enterprises that I am a member and my favorites"
+            search_placeholder: "type to find enterprises"
+            favorites_how_to:
+              title: "How to add favorite Solidarity Economy initiatives"
+              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."
+          community:
+            label: "News from selected communities"
+            search_placeholder: "type to find communities"
+          friend:
+            label: "News from friends"
+            search_placeholder: "type to find friends"
+
+'en_US':
+  <<: *en-US
+'en':
+  <<: *en-US
+


=====================================
plugins/open_graph/locales/pt-BR.yml
=====================================
--- /dev/null
+++ b/plugins/open_graph/locales/pt-BR.yml
@@ -0,0 +1,40 @@
+
+"pt-BR": &pt-BR
+
+  open_graph_plugin:
+    lib:
+      plugin:
+        name: 'OpenGraph'
+        description: 'OpenGraph'
+    views:
+      track:
+        config:
+          activity:
+            configure: 'Configurar'
+            label: 'Publicar minhas atividades do cirandas no meu mural do facebook'
+            objects:
+              blog_post: 'Quando eu criar um conteúdo ou artigo'
+              event: 'Quando eu adicionar eventos'
+              forum: 'Quando eu criar novos tópicos de fórum'
+              friend: 'Quando eu fizer amizades'
+              gallery_image: 'Quando eu adicionar imagens nos meus albuns'
+              uploaded_file: 'Quando eu enviar novos documentos'
+              favorite_enterprise: 'Quando eu favoritar um empreendimento'
+          enterprise:
+            memberships: "Empreendimentos dos quais faço parte"
+            favorites: "Meus empreendimentos favoritos" 
+            see_all: 'Ver empreendimentos'
+            label: "Publicar as novidades dos meus empreendimentos favoritos e daqueles que faço parte"
+            search_placeholder: "busque o empreendimento"
+            favorites_how_to:
+              title: "Como adicionar empreendimentos favoritos"
+              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."
+          community:
+            label: "Publicar novidades das seguintes comunidades"
+            search_placeholder: "escolha a comunidade"
+
+'pt_BR':
+  <<: *pt-BR
+'pt':
+  <<: *pt-BR
+


=====================================
plugins/open_graph/models/open_graph_plugin/activity.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/models/open_graph_plugin/activity.rb
@@ -0,0 +1,8 @@
+# This is a log of activities, unlike ActivityTrack that is a configuration
+class OpenGraphPlugin::Activity < OpenGraphPlugin::Track
+
+  # subclass this to define (e.g. FbAppPlugin::Activity)
+  def scrape
+  end
+
+end


=====================================
plugins/open_graph/models/open_graph_plugin/activity_track_config.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/models/open_graph_plugin/activity_track_config.rb
@@ -0,0 +1,22 @@
+class OpenGraphPlugin::ActivityTrackConfig < OpenGraphPlugin::TrackConfig
+
+  # workaround for STI bug
+  self.table_name = :open_graph_plugin_tracks
+
+  self.track_name = :activity
+
+  Objects = OpenGraphPlugin::Stories::TrackConfigStories[self.name].map do |story, data|
+    data[:object_type].to_s
+  end.uniq
+
+  def self.objects
+    Objects
+  end
+
+  validates_uniqueness_of :object_type, scope: [:tracker_id]
+  validates_inclusion_of :object_type, in: self.objects
+
+  protected
+
+end
+


=====================================
plugins/open_graph/models/open_graph_plugin/community_track_config.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/models/open_graph_plugin/community_track_config.rb
@@ -0,0 +1,8 @@
+class OpenGraphPlugin::CommunityTrackConfig < OpenGraphPlugin::TrackConfig
+
+  # workaround for STI bug
+  self.table_name = :open_graph_plugin_tracks
+
+  self.track_name = :community
+
+end


=====================================
plugins/open_graph/models/open_graph_plugin/enterprise_track_config.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/models/open_graph_plugin/enterprise_track_config.rb
@@ -0,0 +1,20 @@
+class OpenGraphPlugin::EnterpriseTrackConfig < OpenGraphPlugin::TrackConfig
+
+  # workaround for STI bug
+  self.table_name = :open_graph_plugin_tracks
+
+  self.track_name = :enterprise
+
+  self.static_trackers = true
+
+  def self.trackers_to_profile enterprise
+    trackers = enterprise.members.to_set
+    trackers.merge enterprise.fans if enterprise.respond_to? :fans
+    trackers.to_a
+  end
+
+  def self.profile_track_objects profile
+    (profile.enterprises.public.enabled + profile.favorite_enterprises.public.enabled).uniq
+  end
+
+end


=====================================
plugins/open_graph/models/open_graph_plugin/friend_track_config.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/models/open_graph_plugin/friend_track_config.rb
@@ -0,0 +1,8 @@
+class OpenGraphPlugin::FriendTrackConfig < OpenGraphPlugin::TrackConfig
+
+  # workaround for STI bug
+  self.table_name = :open_graph_plugin_tracks
+
+  self.track_name = :friend
+
+end


=====================================
plugins/open_graph/models/open_graph_plugin/track.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/models/open_graph_plugin/track.rb
@@ -0,0 +1,28 @@
+class OpenGraphPlugin::Track < ActiveRecord::Base
+
+  attr_accessible :type, :context, :tracker_id, :tracker, :actor_id, :action,
+    :object_type, :object_data, :object_data_id, :object_data_type, :object_data_url
+
+  belongs_to :tracker, class_name: 'Profile'
+  belongs_to :actor, class_name: 'Profile'
+  belongs_to :object_data, polymorphic: true
+
+  validates_presence_of :context
+  before_validation :set_context
+
+  def self.objects
+    []
+  end
+
+  def self.association
+    @association ||= "open_graph_#{self.name.demodulize.pluralize.underscore}".to_sym
+  end
+
+  protected
+
+  def set_context
+    self.context = OpenGraphPlugin.context
+  end
+
+end
+


=====================================
plugins/open_graph/models/open_graph_plugin/track_config.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/models/open_graph_plugin/track_config.rb
@@ -0,0 +1,49 @@
+class OpenGraphPlugin::TrackConfig < OpenGraphPlugin::Track
+
+  Types = {
+    activity: 'ActivityTrackConfig',
+    enterprise: 'EnterpriseTrackConfig',
+    community: 'CommunityTrackConfig',
+    # TODO: not yet implemented
+    #friend: 'FriendTrackConfig',
+  }
+
+  # define on subclasses (required)
+  class_attribute :track_name
+  def self.track_enabled_field
+    "#{self.track_name}_track_enabled"
+  end
+
+  # true if do not depend on records (e.g. EnterpriseTrackConfig depends on friends)
+  # redefine on subclasses
+  class_attribute :static_trackers
+  self.static_trackers = false
+
+  def self.enabled? context, actor
+    settings = actor.send "#{context}_settings"
+    settings.send "#{self.track_name}_track_enabled"
+  end
+
+  scope :tracks_to_profile, lambda { |profile, exclude_actor=nil|
+    scope = where object_data_id: profile.id, object_data_type: profile.class.base_class
+    scope = scope.where context: OpenGraphPlugin.context
+    scope = scope.includes :tracker
+    scope = scope.where ['tracker_id <> ?', exclude_actor.id] if exclude_actor
+    scope
+  }
+
+  # redefine on subclasses
+  def self.trackers_to_profile profile
+    tracks = self.tracks_to_profile profile
+    tracks = tracks.where type: self
+    tracks.map(&:tracker)
+  end
+
+  def self.profile_tracks profile
+    profile.send self.association
+  end
+  def self.profile_track_objects profile
+    self.profile_tracks(profile).map(&:object_data).compact
+  end
+
+end


=====================================
plugins/open_graph/plugin.yml
=====================================
--- /dev/null
+++ b/plugins/open_graph/plugin.yml
@@ -0,0 +1,3 @@
+name: open_graph
+dependencies:
+  - metadata


=====================================
plugins/open_graph/public/javascripts/open_graph.js
=====================================
--- /dev/null
+++ b/plugins/open_graph/public/javascripts/open_graph.js
@@ -0,0 +1,181 @@
+open_graph = {
+
+  track: {
+
+    config: {
+
+      view: {
+        form: null,
+      },
+
+      init: function(reload) {
+        this.view.form = $('#track-form form')
+        this.view.form.find('.panel-heading').each(function(i, context) {
+          open_graph.track.config.headingToggle(context)
+        })
+      },
+
+      submit: function() {
+        loading_overlay.show($('#track-config'))
+        open_graph.track.config.view.form.ajaxSubmit({
+          success: function(data) {
+            data = $(data)
+            // needs update to get ids from accepts_nested_attributes_for
+            $('#track-activity').html(data.find('#track-activity').html())
+            loading_overlay.hide($('#track-config'))
+          },
+        })
+        return false;
+      },
+
+      // trigged on init state and on subcheckboxes change
+      headingToggle: function(context, open) {
+        var panel = $(context).parents('.panel')
+        var panelHeading = panel.find('.panel-heading')
+        var panelBody = panel.find('.panel-body')
+        var parentCheckbox = panel.find('.config-check')
+        var configButton = panel.find('.config-button')
+        var input = panel.find('.track-config-toggle')
+        var openWas = input.val() == 'true'
+        if (open === undefined)
+          open = input.val() == 'true' && (panelHeading.hasClass('enable-on-empty') || this.numberChecked(context) > 0)
+        // open is defined, that is an user action
+        else {
+          if (open) {
+            if (panelHeading.hasClass('open-on-enable'))
+              panelBody.collapse('show')
+          } else
+            panelBody.collapse('hide')
+        }
+
+        configButton.toggle(open)
+        parentCheckbox.toggleClass('fa-toggle-on', open)
+        parentCheckbox.toggleClass('fa-toggle-off', !open)
+        input.prop('value', open)
+        if (openWas != open)
+          open_graph.track.config.submit()
+      },
+
+      // the event of change
+      toggleEvent: function(context, event) {
+        var panel = $(context).parents('.panel')
+        var panelBody = panel.find('.panel-body')
+        var checkboxes = panelBody.find('input[type=checkbox]')
+        var open = panel.find('.track-config-toggle').val() == 'true'
+        open = !open;
+
+        checkboxes.prop('checked', open)
+
+        this.headingToggle(context, open)
+        return false;
+      },
+
+      open: function(context) {
+        var panel = $(context).parents('.panel')
+        var panelBody = panel.find('.panel-body')
+        panelBody.collapse('show')
+      },
+
+      toggleObjectType: function(checkbox) {
+        checkbox = $(checkbox)
+
+        this.headingToggle(checkbox)
+
+        checkbox.siblings("input[name*='[_destroy]']").val(!checkbox.is(':checked'))
+        open_graph.track.config.submit()
+      },
+
+      numberChecked: function(context) {
+        var panel = $(context).parents('.panel')
+        var panelBody = panel.find('.panel-body')
+        var checkboxes = panel.find('.panel-body input[type=checkbox]')
+        var profilesInput = panel.find('.panel-body .select-profiles')
+
+        var nObjects = checkboxes.filter(':checked').length
+        var nProfiles = profilesInput.length ? profilesInput.tokenfield('getTokens').length : 0;
+        var nChecked = nObjects + nProfiles;
+        var nTotal = checkboxes.length + nProfiles
+
+        return nChecked
+      },
+
+      enterprise: {
+        see_all: function(context) {
+          var panel = $(context).parents('.panel')
+          var panelBody = panel.find('.panel-body')
+          noosfero.modal.html(panelBody.html())
+        },
+      },
+
+      initAutocomplete: function(track, url, items) {
+        var selector = '#select-'+track
+        var input = $(selector)
+        var tokenField = open_graph.autocomplete.init(url, selector, items)
+
+        input.change(open_graph.track.config.submit)
+        tokenField
+          .on('tokenfield:createdtoken tokenfield:removedtoken', function() {
+            open_graph.track.config.headingToggle(this)
+          }).on('tokenfield:createtoken tokenfield:removetoken', function(event) {
+            input.val()
+          }).on('tokenfield:createtoken', function(event) {
+            var existingTokens = $(this).tokenfield('getTokens')
+            $.each(existingTokens, function(index, token) {
+              if (token.value === event.attrs.value)
+                event.preventDefault()
+            })
+          })
+
+        return tokenField;
+      },
+
+    },
+  },
+
+  autocomplete: {
+    bloodhoundOptions: {
+      datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
+      queryTokenizer: Bloodhound.tokenizers.whitespace,
+      ajax: {
+        beforeSend: function() {
+          input.addClass('small-loading')
+        },
+        complete: function() {
+          input.removeClass('small-loading')
+        },
+      },
+    },
+    tokenfieldOptions: {
+
+    },
+    typeaheadOptions: {
+      minLength: 1,
+      highlight: true,
+    },
+
+    init: function(url, selector, data, options) {
+      options = options || {}
+      var bloodhoundOptions = $.extend({}, this.bloodhoundOptions, options.bloodhound || {});
+      var typeaheadOptions = $.extend({}, this.typeaheadOptions, options.typeahead || {});
+      var tokenfieldOptions = $.extend({}, this.tokenfieldOptions, options.tokenfield || {});
+
+      var input = $(selector)
+      bloodhoundOptions.remote = {
+        url: url,
+        replace: function(url, uriEncodedQuery) {
+          return $.param.querystring(url, {query:uriEncodedQuery});
+        },
+      }
+      var engine = new Bloodhound(bloodhoundOptions)
+      engine.initialize()
+
+      tokenfieldOptions.typeahead = [typeaheadOptions, { displayKey: 'label', source: engine.ttAdapter() }]
+
+      var tokenField = input.tokenfield(tokenfieldOptions)
+      input.tokenfield('setTokens', data)
+
+      return input
+    },
+  },
+}
+


=====================================
plugins/open_graph/public/style.scss
=====================================
--- /dev/null
+++ b/plugins/open_graph/public/style.scss
@@ -0,0 +1 @@
+stylesheets/style.scss
\ No newline at end of file


=====================================
plugins/open_graph/public/stylesheets/style.scss
=====================================
--- /dev/null
+++ b/plugins/open_graph/public/stylesheets/style.scss
@@ -0,0 +1,66 @@
+#track-form {
+
+  .panel-heading {
+    a, a:visited {
+      color: #fff;
+      display: block;
+      text-decoration: none;
+    }
+    a:hover {
+      text-decoration: underline;
+    }
+    a.btn {
+      display: inline-block;
+      text-decoration: none;
+    }
+  }
+
+  // always use one line to fit placeholder
+  .tokenfield {
+
+    .twitter-typeahead {
+      width: 100%;
+      display: block;
+
+      .tt-input {
+        width: 100% !important;
+      }
+    }
+  }
+
+  #track-config {
+    .panel-heading {
+      padding-left: 36px;
+    }
+    span.config-check {
+      font-weight: bold;
+      margin-left: -26px;
+      margin-right: 4px;
+    }
+    span.config-check.fa-toggle-off {
+      color: #99f;
+    }
+    .activity-config, .sse-config, .community-config {
+      float:right;
+    }
+    .activity-label, .sse-label, .community-label {
+      margin-right: 90px;
+    }
+  }
+}
+
+// shown inside a popin
+.open-graph-enterprises-modal {
+  overflow: hidden;
+  
+  h1 {
+    font-size: 22px;
+  }
+  #open-graph-favorite-enterprises-how-to {
+    clear:both;
+    padding-top: 1px;
+  }
+  p {
+    text-align:justify;
+  }
+}


=====================================
plugins/open_graph/test/functional/open_graph_graph/my_profile_controller_test.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/test/functional/open_graph_graph/my_profile_controller_test.rb
@@ -0,0 +1,54 @@
+require 'test_helper'
+require 'open_graph_plugin/myprofile_controller'
+
+# Re-raise errors caught by the controller.
+class OpenGraphPlugin::MyprofileController; def rescue_action(e) raise e end; end
+
+class OpenGraphPlugin::MyprofileControllerTest < ActionController::TestCase
+
+  def setup
+    @controller = OpenGraphPlugin::MyprofileController.new
+    @request = ActionController::TestRequest.new
+    @response = ActionController::TestResponse.new
+    @actor = create_user.person
+  end
+
+  should "save selected activities" do
+    login_as @actor.identifier
+    @myenterprise = @actor.environment.enterprises.create! name: 'mycoop', identifier: 'mycoop'
+    @myenterprise.add_member @actor
+    @enterprise = @actor.environment.enterprises.create! name: 'coop', identifier: 'coop'
+    @enterprise.fans << @actor
+
+    post :track_config, profile: @actor.identifier, profile_data: {
+      open_graph_settings: {
+        activity_track_enabled: "true",
+        enterprise_track_enabled: "true",
+        community_track_enabled: "false",
+      },
+      open_graph_activity_track_configs_attributes: {
+        0 => {
+          tracker_id: @actor.id,
+          object_type: 'blog_post',
+        },
+      },
+
+      # ignored, enterprise uses static tracking
+      open_graph_enterprise_profiles_ids: [@enterprise.id],
+    }
+    @actor.reload
+
+    assert_equal true, @actor.open_graph_settings.activity_track_enabled
+    assert_equal true, @actor.open_graph_settings.enterprise_track_enabled
+    assert_equal false, @actor.open_graph_settings.community_track_enabled
+
+    assert_equal 1, @actor.open_graph_activity_track_configs.count
+    assert_equal 'blog_post', @actor.open_graph_activity_track_configs.first.object_type
+    assert_equal @actor.id, @actor.open_graph_activity_track_configs.first.tracker_id
+
+    assert_equal [@actor], OpenGraphPlugin::EnterpriseTrackConfig.trackers_to_profile(@enterprise)
+    assert_equal [@actor], OpenGraphPlugin::EnterpriseTrackConfig.trackers_to_profile(@myenterprise)
+
+  end
+
+end


=====================================
plugins/open_graph/test/unit/open_graph_graph/publisher_test.rb
=====================================
--- /dev/null
+++ b/plugins/open_graph/test/unit/open_graph_graph/publisher_test.rb
@@ -0,0 +1,111 @@
+require "test_helper"
+
+class OpenGraphPlugin::PublisherTest < ActiveSupport::TestCase
+
+  def setup
+    @actor = create_user.person
+    User.current = @actor.user
+    @stories = OpenGraphPlugin::Stories::Definitions
+    @publisher = OpenGraphPlugin::Publisher.new
+    OpenGraphPlugin::Stories.stubs(:publishers).returns([@publisher])
+    @publisher.stubs(:context).returns(:open_graph)
+    @publisher.stubs(:og_domain).returns('noosfero.net')
+  end
+
+  should "publish only tracked stuff" do
+    @other_actor = create_user.person
+
+    @myenterprise = @actor.environment.enterprises.create! name: 'mycoop', identifier: 'mycoop'
+    @myenterprise.add_member @actor
+    @enterprise = @actor.environment.enterprises.create! name: 'coop', identifier: 'coop'
+    # the original domain from open_graph should be used
+    @enterprise.domains.create! name: 'customdomain.com'
+
+    @community = @actor.environment.communities.create! name: 'comm', identifier: 'comm', closed: false
+
+    @actor.update_attributes!({
+      open_graph_settings: {
+        activity_track_enabled: "true",
+        enterprise_track_enabled: "true",
+        community_track_enabled: "true",
+      },
+      open_graph_activity_track_configs_attributes: {
+        0 => { tracker_id: @actor.id, object_type: 'blog_post', },
+        1 => { tracker_id: @actor.id, object_type: 'gallery_image', },
+        2 => { tracker_id: @actor.id, object_type: 'uploaded_file', },
+        3 => { tracker_id: @actor.id, object_type: 'event', },
+        4 => { tracker_id: @actor.id, object_type: 'forum', },
+        5 => { tracker_id: @actor.id, object_type: 'friend', },
+        6 => { tracker_id: @actor.id, object_type: 'favorite_enterprise', },
+      },
+      open_graph_enterprise_profiles_ids: "#{@enterprise.id}",
+      open_graph_community_profiles_ids: "#{@community.id}",
+    })
+    @other_actor.update_attributes! open_graph_settings: { activity_track_enabled: "true", },
+      open_graph_activity_track_configs_attributes: { 0 => { tracker_id: @other_actor.id, object_type: 'friend', }, }
+
+    # active
+    User.current = @actor.user
+
+    blog = Blog.create! profile: @actor, name: 'blog'
+    blog_post = TinyMceArticle.new profile: User.current.person, parent: blog, name: 'blah', author: User.current.person
+    @publisher.expects(:publish).with(User.current.person, @stories[:create_an_article], @publisher.send(:url_for, blog_post))
+    blog_post.save!
+
+    gallery = Gallery.create! name: 'gallery', profile: User.current.person
+    image = UploadedFile.new uploaded_data: fixture_file_upload('/files/rails.png', 'image/png'), parent: gallery, profile: User.current.person
+    @publisher.expects(:publish).with(User.current.person, @stories[:add_an_image], @publisher.send(:url_for, image, image.url.merge(view: true)))
+    image.save!
+
+    document = UploadedFile.new uploaded_data: fixture_file_upload('/files/doctest.en.xhtml', 'text/html'), profile: User.current.person
+    @publisher.expects(:publish).with(User.current.person, @stories[:add_a_document], @publisher.send(:url_for, document, document.url.merge(view: true)))
+    document.save!
+
+    event = Event.new name: 'event', profile: User.current.person
+    @publisher.expects(:publish).with(User.current.person, @stories[:create_an_event], @publisher.send(:url_for, event))
+    event.save!
+
+    forum = Forum.create! name: 'forum', profile: User.current.person
+    topic = TinyMceArticle.new profile: User.current.person, parent: forum, name: 'blah2', author: User.current.person
+    @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])))
+    topic.save!
+
+    @publisher.expects(:publish).with(@actor, @stories[:make_friendship_with], @publisher.send(:url_for, @other_actor)).twice
+    @publisher.expects(:publish).with(@other_actor, @stories[:make_friendship_with], @publisher.send(:url_for, @actor)).twice
+    AddFriend.create!(person: @actor, friend: @other_actor).finish
+    Friendship.remove_friendship @actor, @other_actor
+    # friend verb is groupable
+    AddFriend.create!(person: @actor, friend: @other_actor).finish
+
+    @publisher.expects(:publish).with(User.current.person, @stories[:favorite_a_sse_initiative], @publisher.send(:url_for, @enterprise))
+    @enterprise.fans << User.current.person
+
+    # active but published as passive
+    User.current = @actor.user
+
+    blog_post = TinyMceArticle.new profile: @enterprise, parent: @enterprise.blog, name: 'blah', author: User.current.person
+    story = @stories[:announce_news_from_a_sse_initiative]
+    @publisher.expects(:publish).with(User.current.person, story, @publisher.send(:passive_url_for, blog_post, nil, story))
+    blog_post.save!
+
+    # passive
+    User.current = @other_actor.user
+
+    # fan
+    blog_post = TinyMceArticle.new profile: @enterprise, parent: @enterprise.blog, name: 'blah2', author: User.current.person
+    story = @stories[:announce_news_from_a_sse_initiative]
+    @publisher.expects(:publish).with(@actor, story, 'http://noosfero.net/coop/blog/blah2')
+    blog_post.save!
+    # member
+    blog_post = TinyMceArticle.new profile: @myenterprise, parent: @myenterprise.blog, name: 'blah2', author: User.current.person
+    story = @stories[:announce_news_from_a_sse_initiative]
+    @publisher.expects(:publish).with(@actor, story, 'http://noosfero.net/mycoop/blog/blah2')
+    blog_post.save!
+
+    blog_post = TinyMceArticle.new profile: @community, parent: @community.blog, name: 'blah', author: User.current.person
+    story = @stories[:announce_news_from_a_community]
+    @publisher.expects(:publish).with(@actor, story, 'http://noosfero.net/comm/blog/blah')
+    blog_post.save!
+  end
+
+end


=====================================
plugins/open_graph/views/open_graph_plugin/myprofile/_ac_profile.html.erb
=====================================
--- /dev/null
+++ b/plugins/open_graph/views/open_graph_plugin/myprofile/_ac_profile.html.erb
@@ -0,0 +1,2 @@
+<%= profile_image profile, :icon %>
+<%= profile.short_name nil %>


=====================================
plugins/open_graph/views/open_graph_plugin/myprofile/_heading.html.erb
=====================================
--- /dev/null
+++ b/plugins/open_graph/views/open_graph_plugin/myprofile/_heading.html.erb
@@ -0,0 +1,5 @@
+<span class='config-check fa'></span>
+<%= f.fields_for "#{context}_settings" do |ff| %>
+  <%= ff.hidden_field klass.track_enabled_field, value: profile.send("#{context}_settings").send(klass.track_enabled_field), class: 'track-config-toggle' %>
+<% end %>
+<%= t("open_graph_plugin.views.track.config.#{track}.label") %>


=====================================
plugins/open_graph/views/open_graph_plugin/myprofile/_profile_search.jsonify
=====================================
--- /dev/null
+++ b/plugins/open_graph/views/open_graph_plugin/myprofile/_profile_search.jsonify
@@ -0,0 +1,7 @@
+self.formats = [:html]
+profiles.each do |p|
+  json << {
+    value: p.id, label: render('open_graph_plugin/myprofile/ac_profile', profile: p),
+  }
+end
+


=====================================
plugins/open_graph/views/open_graph_plugin/myprofile/_track_activity.html.erb
=====================================
--- /dev/null
+++ b/plugins/open_graph/views/open_graph_plugin/myprofile/_track_activity.html.erb
@@ -0,0 +1,12 @@
+<div class="panel-heading">
+  <%= button_to_function 'menu-ctrl-panel', t('open_graph_plugin.views.track.config.activity.configure'), "",
+    class: 'activity-config config-button', option: 'success', size: 'xs', 'data-target' => "#track-#{track}", 'data-toggle' => 'collapse', 'aria-controls' => 'collapseTwo' %>
+  <a href="#" onclick='return open_graph.track.config.toggleEvent(this, event)' class='activity-label'>
+    <%= render 'heading', f: f, track: track, context: context, klass: klass %>
+  </a>
+</div>
+
+<div id="track-<%=track%>" class="panel-body collapse">
+  <%= render 'track_objects', f: f, track: track, objects: klass.objects, klass: klass, context: context %>
+</div>
+


=====================================
plugins/open_graph/views/open_graph_plugin/myprofile/_track_community.html.erb
=====================================
--- /dev/null
+++ b/plugins/open_graph/views/open_graph_plugin/myprofile/_track_community.html.erb
@@ -0,0 +1,12 @@
+<div class="panel-heading open-on-enable">
+  <%= button_to_function 'menu-ctrl-panel', t('open_graph_plugin.views.track.config.activity.configure'), "",
+    class: 'community-config config-button', option: 'success', size: 'xs', 'data-target' => "#track-#{track}", 'data-toggle' => 'collapse', 'aria-controls' => 'collapseTwo' %>
+  <a href="#track-<%=track%>" onclick='open_graph.track.config.toggleEvent(this, event)', class='community-label'>
+    <%= render 'heading', f: f, track: track, context: context, klass: klass %>
+  </a>
+</div>
+
+<div id="track-<%=track%>" class="panel-body collapse">
+  <%= render 'track_objects', f: f, track: track, objects: klass.objects, klass: klass, context: context %>
+  <%= render 'track_profiles', f: f, track: track, context: context, klass: klass %>
+</div>


=====================================
plugins/open_graph/views/open_graph_plugin/myprofile/_track_enterprise.html.erb
=====================================
--- /dev/null
+++ b/plugins/open_graph/views/open_graph_plugin/myprofile/_track_enterprise.html.erb
@@ -0,0 +1,44 @@
+<div class="panel-heading enable-on-empty">
+  <%= button_to_function 'menu-ctrl-panel', t('open_graph_plugin.views.track.config.enterprise.see_all'), 'open_graph.track.config.enterprise.see_all(this)',
+    class: 'sse-config config-button', option: 'success', size: 'xs' %>
+  <a href="#" onclick='return open_graph.track.config.toggleEvent(this, event)' style='sse-label'>
+    <%= render 'heading', f: f, track: track, context: context, klass: klass %>
+  </a>
+</div>
+
+<div id="track-<%=track%>" class="panel-body collapse" style="display: none">
+  <% if user.enterprises.present? %>
+    <div id="enterprises-memberships" class="open-graph-enterprises-modal">
+      <h1>
+        <%= t('open_graph_plugin.views.track.config.enterprise.memberships') %>
+      </h1>
+      <div class="open-graph-enterprises-list">
+        <% user.enterprises.public.enabled.each do |enterprise| %>
+          <%= profile_image_link enterprise, :portrait, :div %>
+        <% end %>
+      </div>
+    </div>
+  <% end %>
+
+  <div id="favorite-enterprises" class="open-graph-enterprises-modal">
+    <h1>
+      <%= t('open_graph_plugin.views.track.config.enterprise.favorites') %>
+    </h1>
+    <% if user.favorite_enterprises.present? %>
+      <div class="open-graph-enterprises-list">
+        <% user.favorite_enterprises.public.enabled.each do |enterprise| %>
+          <%= profile_image_link enterprise, :portrait, :div %>
+        <% end %>
+      </div>
+    <% end %>
+      <div id='open-graph-favorite-enterprises-how-to'>
+        <h1>
+          <%= t('open_graph_plugin.views.track.config.enterprise.favorites_how_to.title') %>
+        </h1>
+        <p>
+          <%= 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') %>
+        </p>
+      </div>
+  </div>
+</div>
+


=====================================
plugins/open_graph/views/open_graph_plugin/myprofile/_track_form.html.erb
=====================================
--- /dev/null
+++ b/plugins/open_graph/views/open_graph_plugin/myprofile/_track_form.html.erb
@@ -0,0 +1,22 @@
+<%
+  reload ||= false
+%>
+<%= javascript_tag do %>
+  open_graph.track.config.reload = <%= reload.to_json %>
+<% end %>
+
+<%= form_for profile, as: :profile_data, remote: true, url: {action: :track_config},
+  html: {id: 'track-config', onsubmit: 'return open_graph.track.config.submit()'} do |f| %>
+
+  <div class="panel-group" role="tablist" aria-multiselectable="true">
+    <% OpenGraphPlugin::TrackConfig::Types.each do |track, klass| %>
+      <div class="panel panel-primary">
+        <%= render "track_#{track}", f: f, track: track, klass: "OpenGraphPlugin::#{klass}".constantize, context: context %>
+      </div>
+    <% end %>
+  </div>
+<% end %>
+
+<%= javascript_tag do %>
+  open_graph.track.config.init()
+<% end %>


=====================================
plugins/open_graph/views/open_graph_plugin/myprofile/_track_friend.html.erb
=====================================
--- /dev/null
+++ b/plugins/open_graph/views/open_graph_plugin/myprofile/_track_friend.html.erb
@@ -0,0 +1,10 @@
+<div class="panel-heading">
+  <a href="#track-<%=track%>" data-toggle="collapse" aria-controls="collapseTwo" onclick='open_graph.track.config.toggleEvent(this, event)'>
+    <%= render 'heading', f: f, track: track, context: context, klass: klass %>
+  </a>
+</div>
+
+<div id="track-<%=track%>" class="panel-body collapse">
+  <%= render 'track_objects', f: f, track: track, objects: klass.objects, klass: klass, context: context %>
+  <%= render 'track_profiles', f: f, track: track, context: context, klass: klass %>
+</div>


=====================================
plugins/open_graph/views/open_graph_plugin/myprofile/_track_objects.html.erb
=====================================
--- /dev/null
+++ b/plugins/open_graph/views/open_graph_plugin/myprofile/_track_objects.html.erb
@@ -0,0 +1,17 @@
+<%
+  tracks = profile.send klass.association
+%>
+
+<% objects.each do |object| %>
+  <div id="object-<%= object %>" class="tracked-object">
+    <% track_record = tracks.find{ |t| t.object_type == object } || profile.send(klass.association).build %>
+    <%= f.fields_for klass.association, track_record do |ff| %>
+      <%= ff.hidden_field :id %>
+      <%= ff.hidden_field :tracker_id %>
+      <%= ff.check_box :object_type, {onchange: 'open_graph.track.config.toggleObjectType(this)'}, object, '' %>
+      <%= ff.label :object_type, t("open_graph_plugin.views.track.config.#{track}.objects.#{object}") %>
+      <%= ff.hidden_field :_destroy %>
+    <% end %>
+  </div>
+<% end %>
+


=====================================
plugins/open_graph/views/open_graph_plugin/myprofile/_track_profiles.html.erb
=====================================
--- /dev/null
+++ b/plugins/open_graph/views/open_graph_plugin/myprofile/_track_profiles.html.erb
@@ -0,0 +1,19 @@
+<%
+  static = klass.static_trackers
+  profiles = klass.profile_track_objects profile
+%>
+<%= text_field_tag "#{f.object_name}[open_graph_#{track}_profiles_ids]", '', id: "select-#{track}", class: 'select-profiles',
+  placeholder: (t("open_graph_plugin.views.track.config.#{track}.search_placeholder") unless static),
+  disabled: ("disabled" if static) %>
+
+<%= javascript_tag do %>
+  $(document).ready(function () {
+    var input = open_graph.track.config.initAutocomplete(<%=track.to_json%>,
+      <%= url_for(action: "#{track}_search").to_json %>,
+      <%= profiles.map{ |p| {value: p.id, label: render('ac_profile', profile: p), } }.to_json %>
+    )
+    <% if static %>
+      input.tokenfield('readonly')
+    <% end %>
+  })
+<% end %>



View it on GitLab: https://gitlab.com/noosfero/noosfero/compare/6c114d19fdd68478b9612111c345b356486b5adf...180ad5296e483134f6df021d56008e75c2e2dc0e
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://listas.softwarelivre.org/pipermail/noosfero-dev/attachments/20150813/75467a5d/attachment-0001.html>


More information about the Noosfero-dev mailing list