Commit a067af0109ddb54ee05fbbf3f4ca588e28eac23b

Authored by Braulio Bhavamitra
2 parents 945956c1 fb028d5a

Merge branch 'metadata-plugin' into 'master'

metadata: allow customization of og_type via param

Also:
- define controllers code via class to allow customization via `alias_method_chain` for monkey patching
- define plugin using Base class to avoid conflicts with the `Controllers` class defined by RSpec

See merge request !493
app/models/environment.rb
... ... @@ -285,7 +285,20 @@ class Environment < ActiveRecord::Base
285 285 settings_items :activation_blocked_text, :type => String
286 286 settings_items :message_for_disabled_enterprise, :type => String,
287 287 :default => _('This enterprise needs to be enabled.')
288   - settings_items :location, :type => String
  288 +
  289 + settings_items :contact_phone, type: String
  290 + settings_items :address, type: String
  291 + settings_items :city, type: String
  292 + settings_items :state, type: String
  293 + settings_items :country_name, type: String
  294 + settings_items :lat, type: Float
  295 + settings_items :lng, type: Float
  296 + settings_items :postal_code, type: String
  297 + settings_items :location, type: String
  298 +
  299 + alias_method :zip_code=, :postal_code
  300 + alias_method :zip_code, :postal_code
  301 +
289 302 settings_items :layout_template, :type => String, :default => 'default'
290 303 settings_items :homepage, :type => String
291 304 settings_items :description, :type => String, :default => '<div style="text-align: center"><a href="http://noosfero.org/"><img src="/images/noosfero-network.png" alt="Noosfero"/></a></div>'
... ...
plugins/metadata/config.yml.dist
1 1 open_graph:
2 2 domain: domainregisteredonfacebook.com
3 3 environment_logo: 'http://example.com/designs/themes/environmenttheme/images/logo.png'
  4 + namespace: app_scope
4 5 types:
5 6 article: article
6   - product: facebook_app:sse_product
7   - uploaded_file: facebook_app:document
8   - image: facebook_app:picture
9   - profile: facebook_app:profile
10   - person: facebook_app:user
11   - community: facebook_app:community
12   - enterprise: facebook_app:sse_initiative
  7 + event: app_scope:event
  8 + forum: app_scope:discussion
  9 + uploaded_file: app_scope:document
  10 + image: app_scope:picture
  11 +
  12 + product: app_scope:sse_product
  13 +
  14 + profile: app_scope:profile
  15 + person: app_scope:user
  16 + community: app_scope:community
  17 + enterprise: app_scope:sse_initiative
... ...
plugins/metadata/lib/ext/article.rb
... ... @@ -3,14 +3,19 @@ require_dependency &#39;article&#39;
3 3 class Article
4 4  
5 5 metadata_spec namespace: :og, key_attr: :property, tags: {
6   - type: MetadataPlugin.og_types[:article] || :article,
7   - url: proc{ |a, plugin| plugin.og_url_for a.url },
  6 + type: proc do |a, plugin|
  7 + plugin.context.params[:og_type] || MetadataPlugin.og_types[:article] || :article
  8 + end,
  9 + url: proc do |a, plugin|
  10 + url = a.url.merge! profile: a.profile.identifier, og_type: plugin.context.params[:og_type]
  11 + plugin.og_url_for url
  12 + end,
8 13 title: proc{ |a, plugin| "#{a.title} - #{a.profile.name}" },
9 14 image: proc do |a, plugin|
10   - result = a.body_images_paths
11   - result = "#{a.profile.environment.top_url}#{a.profile.image.public_filename}" if a.profile.image if result.blank?
12   - result ||= MetadataPlugin.config[:open_graph][:environment_logo] rescue nil if result.blank?
13   - result
  15 + img = a.body_images_paths
  16 + img = "#{a.profile.environment.top_url}#{a.profile.image.public_filename}" if a.profile.image if img.blank?
  17 + img ||= MetadataPlugin.config[:open_graph][:environment_logo] rescue nil if img.blank?
  18 + img
14 19 end,
15 20 see_also: [],
16 21 site_name: proc{ |a, c| a.profile.name },
... ...
plugins/metadata/lib/ext/community.rb
... ... @@ -4,7 +4,7 @@ require_dependency &quot;#{File.dirname __FILE__}/profile&quot;
4 4 class Community
5 5  
6 6 metadata_spec namespace: :og, tags: {
7   - type: MetadataPlugin.og_types[:community] || :community,
  7 + type: proc{ |c, plugin| plugin.context.params[:og_type] || MetadataPlugin.og_types[:community] || :community },
8 8 }
9 9  
10 10 end
... ...
plugins/metadata/lib/ext/enterprise.rb
... ... @@ -4,17 +4,24 @@ require_dependency &quot;#{File.dirname __FILE__}/profile&quot;
4 4 class Enterprise
5 5  
6 6 metadata_spec namespace: :og, tags: {
7   - type: MetadataPlugin.og_types[:enterprise] || :enterprise,
  7 + type: proc{ |e, plugin| plugin.context.params[:og_type] || MetadataPlugin.og_types[:enterprise] || :enterprise },
  8 + }
  9 +
  10 + # required for businness
  11 + metadata_spec namespace: 'place:location', tags: {
  12 + latitude: proc{ |e, plugin| if e.lat.present? then e.lat else e.environment.lat end },
  13 + longitude: proc{ |e, plugin| if e.lng.present? then e.lng else e.environment.lng end },
8 14 }
9 15  
10 16 metadata_spec namespace: 'business:contact_data', tags: {
11   - email: proc{ |e, plugin| e.contact_email },
12   - phone_number: proc{ |e, plugin| e.contact_phone },
13   - street_address: proc{ |e, plugin| e.address },
14   - locality: proc{ |e, plugin| e.city },
15   - region: proc{ |e, plugin| e.state },
16   - postal_code: proc{ |e, plugin| e.zip_code },
17   - country_name: proc{ |e, plugin| e.country },
  17 + # all required
  18 + email: proc{ |e, plugin| if e.contact_email.present? then e.contact_email else e.environment.contact_email end },
  19 + phone_number: proc{ |e, plugin| if e.contact_phone.present? then e.contact_phone else e.environment.contact_phone end },
  20 + street_address: proc{ |e, plugin| if e.address.present? then e.address else e.environment.address end },
  21 + locality: proc{ |e, plugin| if e.city.present? then e.city else e.environment.city end },
  22 + region: proc{ |e, plugin| if e.state.present? then e.state else e.environment.state end },
  23 + postal_code: proc{ |e, plugin| if e.zip_code.present? then e.zip_code else e.environment.postal_code end },
  24 + country_name: proc{ |e, plugin| if e.country.present? then e.country else e.environment.country_name end },
18 25 }
19 26  
20 27 end
... ...
plugins/metadata/lib/ext/environment.rb
... ... @@ -7,7 +7,7 @@ class Environment
7 7 }
8 8  
9 9 metadata_spec namespace: :og, tags: {
10   - type: 'website',
  10 + type: proc{ |e, plugin| plugin.context.params[:og_type] || 'website' },
11 11 title: proc{ |e, plugin| e.name },
12 12 site_name: proc{ |e, plugin| e.name },
13 13 description: proc{ |e, plugin| e.name },
... ...
plugins/metadata/lib/ext/event.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +require_dependency 'event'
  2 +
  3 +class Event
  4 +
  5 + metadata_spec namespace: :og, tags: {
  6 + type: proc{ |p, plugin| plugin.context.params[:og_type] || MetadataPlugin.og_types[:event] || :event },
  7 + }
  8 +
  9 +end
... ...
plugins/metadata/lib/ext/forum.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +require_dependency 'forum'
  2 +
  3 +class Forum
  4 +
  5 + metadata_spec namespace: :og, tags: {
  6 + type: proc{ |p, plugin| plugin.context.params[:og_type] || MetadataPlugin.og_types[:forum] || :forum },
  7 + }
  8 +
  9 +end
... ...
plugins/metadata/lib/ext/person.rb
... ... @@ -4,7 +4,7 @@ require_dependency &quot;#{File.dirname __FILE__}/profile&quot;
4 4 class Person
5 5  
6 6 metadata_spec namespace: :og, tags: {
7   - type: MetadataPlugin.og_types[:person] || :person,
  7 + type: proc{ |p, plugin| plugin.context.params[:og_type] || MetadataPlugin.og_types[:person] || :person },
8 8 }
9 9  
10 10 end
... ...
plugins/metadata/lib/ext/product.rb
... ... @@ -3,14 +3,22 @@ require_dependency &#39;product&#39;
3 3 class Product
4 4  
5 5 metadata_spec namespace: :og, tags: {
6   - type: MetadataPlugin.og_types[:product] || :product,
7   - url: proc{ |p, plugin| plugin.og_url_for p.url },
  6 + type: proc{ |p, plugin| plugin.context.params[:og_type] || MetadataPlugin.og_types[:product] || :product },
  7 + url: proc do |p, plugin|
  8 + url = p.url.merge! profile: p.profile.identifier, og_type: plugin.context.params[:og_type]
  9 + plugin.og_url_for url
  10 + end,
8 11 gr_hascurrencyvalue: proc{ |p, plugin| p.price.to_f },
9 12 gr_hascurrency: proc{ |p, plugin| p.environment.currency_unit },
10 13 title: proc{ |p, plugin| "#{p.name} - #{p.profile.name}" if p },
11 14 description: proc{ |p, plugin| ActionView::Base.full_sanitizer.sanitize p.description },
12 15  
13   - image: proc{ |p, plugin| "#{p.environment.top_url}#{p.image.public_filename}" if p.image },
  16 + image: proc do |p, plugin|
  17 + img = "#{p.environment.top_url}#{p.image.public_filename}" if p.image
  18 + img = "#{p.environment.top_url}#{p.profile.image.public_filename}" if img.blank? and p.profile.image
  19 + img ||= MetadataPlugin.config[:open_graph][:environment_logo] rescue nil if img.blank?
  20 + img
  21 + end,
14 22 'image:type' => proc{ |p, plugin| p.image.content_type if p.image },
15 23 'image:height' => proc{ |p, plugin| p.image.height if p.image },
16 24 'image:width' => proc{ |p, plugin| p.image.width if p.image },
... ...
plugins/metadata/lib/ext/profile.rb
... ... @@ -3,13 +3,14 @@ require_dependency &#39;profile&#39;
3 3 class Profile
4 4  
5 5 metadata_spec namespace: :og, tags: {
6   - type: MetadataPlugin.og_types[:profile] || :profile,
7   - image: proc{ |p, plugin| "#{p.environment.top_url}#{p.image.public_filename}" if p.image },
8   - title: proc{ |p, plugin| if p.nickname.present? then p.nickname else p.name end },
9   - url: proc do |p, plugin|
10   - #force profile identifier for custom domains and fixed host. see og_url_for
11   - plugin.og_url_for p.url.merge(profile: p.identifier)
  6 + type: proc{ |p, plugin| plugin.context.params[:og_type] || MetadataPlugin.og_types[:profile] || :profile },
  7 + image: proc do |p, plugin|
  8 + img = "#{p.environment.top_url}#{p.image.public_filename}" if p.image
  9 + img ||= MetadataPlugin.config[:open_graph][:environment_logo] rescue nil if img.blank?
  10 + img
12 11 end,
  12 + title: proc{ |p, plugin| if p.nickname.present? then p.nickname else p.name end },
  13 + url: proc{ |p, plugin| plugin.og_url_for plugin.og_profile_url(p) },
13 14 description: proc{ |p, plugin| p.description },
14 15 updated_time: proc{ |p, plugin| p.updated_at.iso8601 },
15 16 'locale:locale' => proc{ |p, plugin| p.environment.default_language },
... ...
plugins/metadata/lib/ext/uploaded_file.rb
... ... @@ -6,9 +6,12 @@ class UploadedFile
6 6 metadata_spec namespace: :og, tags: {
7 7 type: proc do |u, plugin|
8 8 type = if u.image? then :image else :uploaded_file end
9   - MetadataPlugin.og_types[type] || type
  9 + plugin.context.params[:og_type] || MetadataPlugin.og_types[type] || type
  10 + end,
  11 + url: proc do |u, plugin|
  12 + url = u.url.merge! profile: u.profile.identifier, view: true, og_type: plugin.context.params[:og_type]
  13 + plugin.og_url_for url
10 14 end,
11   - url: proc{ |u, plugin| plugin.og_url_for u.url.merge(view: true) },
12 15 title: proc{ |u, plugin| u.title },
13 16 image: proc{ |u, plugin| "#{u.environment.top_url}#{u.public_filename}" if u.image? },
14 17 description: proc{ |u, plugin| u.abstract || u.title },
... ...
plugins/metadata/lib/metadata_plugin.rb
1 1  
2   -class MetadataPlugin < Noosfero::Plugin
  2 +module MetadataPlugin
  3 +
  4 + extend Noosfero::Plugin::ParentMethods
3 5  
4 6 def self.plugin_name
5 7 _('Export metadata')
... ... @@ -13,89 +15,14 @@ class MetadataPlugin &lt; Noosfero::Plugin
13 15 @config ||= HashWithIndifferentAccess.new(YAML.load File.read("#{File.dirname __FILE__}/../config.yml")) rescue {}
14 16 end
15 17  
16   - def self.og_types
17   - @og_types ||= self.config[:open_graph][:types] rescue {}
18   - end
19   -
20   - CONTROLLERS = {
21   - manage_products: {
22   - variable: :@product,
23   - },
24   - content_viewer: {
25   - variable: proc do
26   - if profile and @page and profile.home_page_id == @page.id
27   - @profile
28   - elsif @page.respond_to? :encapsulated_file
29   - @page.encapsulated_file
30   - else
31   - @page
32   - end
33   - end,
34   - },
35   - profile: {
36   - variable: :@profile,
37   - },
38   - # fallback
39   - environment: {
40   - variable: :@environment,
41   - },
42   - }
43   -
44   - def head_ending
45   - plugin = self
46   - lambda do
47   - options = MetadataPlugin::CONTROLLERS[controller.controller_path.to_sym]
48   - options ||= MetadataPlugin::CONTROLLERS[:profile] if controller.is_a? ProfileController
49   - options ||= MetadataPlugin::CONTROLLERS[:environment]
50   - return unless options
51   -
52   - return unless object = case variable = options[:variable]
53   - when Proc then instance_exec(&variable)
54   - else instance_variable_get variable
55   - end
56   - return if object.respond_to? :public? and not object.public?
57   - return unless specs = (object.class.metadata_specs rescue nil)
58   -
59   - r = []
60   - specs.each do |namespace, spec|
61   - namespace = "#{namespace}:" if namespace.present?
62   - key_attr = spec[:key_attr] || :property
63   - value_attr = spec[:value_attr] || :content
64   - tags = spec[:tags]
65   -
66   - tags.each do |key, values|
67   - key = "#{namespace}#{key}"
68   - values = values.call(object, plugin) if values.is_a? Proc
69   - next if values.blank?
70   -
71   - Array(values).each do |value|
72   - value = value.call(object, plugin) if value.is_a? Proc
73   - next if value.blank?
74   - r << tag(:meta, key_attr => key, value_attr => value)
75   - end
76   - end
77   - end
78   - r.join
79   - end
80   - end
81   -
82   - # context HELPERS
83   - def og_url_for options
84   - options.delete :port
85   - options[:host] = self.class.config[:open_graph][:domain] rescue context.send(:environment).default_hostname
86   - Noosfero::Application.routes.url_helpers.url_for options
  18 + def self.og_config
  19 + @og_config ||= self.config[:open_graph] rescue {}
87 20 end
88   -
89   - def helpers
90   - self.context.class.helpers
  21 + def self.og_types
  22 + @og_types ||= self.og_config[:types] rescue {}
91 23 end
92 24  
93   - protected
94   -
95   -end
  25 + mattr_accessor :controllers
  26 + self.controllers = MetadataPlugin::Controllers.new
96 27  
97   -ActiveSupport.run_load_hooks :metadata_plugin, MetadataPlugin
98   -ActiveSupport.on_load :active_record do
99   - ActiveRecord::Base.extend MetadataPlugin::Specs::ClassMethods
100 28 end
101   -
... ...
plugins/metadata/lib/metadata_plugin/base.rb 0 → 100644
... ... @@ -0,0 +1,75 @@
  1 +
  2 +class MetadataPlugin::Base < Noosfero::Plugin
  3 +
  4 + def self.plugin_name
  5 + _('Export metadata')
  6 + end
  7 +
  8 + def self.plugin_description
  9 + _('Export metadata for models on meta tags')
  10 + end
  11 +
  12 + def self.config
  13 + @config ||= HashWithIndifferentAccess.new(YAML.load File.read("#{File.dirname __FILE__}/../config.yml")) rescue {}
  14 + end
  15 +
  16 + def self.og_types
  17 + @og_types ||= self.config[:open_graph][:types] rescue {}
  18 + end
  19 +
  20 + class_attribute :controllers
  21 + self.controllers = MetadataPlugin::Controllers.new
  22 +
  23 + def head_ending
  24 + plugin = self
  25 + lambda do
  26 + variable = plugin.class.controllers.send controller.controller_path rescue nil
  27 + variable ||= plugin.class.controllers.send :profile if controller.is_a? ProfileController
  28 + variable ||= plugin.class.controllers.send :home
  29 + return unless variable
  30 +
  31 + return unless object = case variable
  32 + when Proc then instance_exec(&variable)
  33 + else instance_variable_get variable
  34 + end
  35 + return if object.respond_to? :public? and not object.public?
  36 + return unless specs = (object.class.metadata_specs rescue nil)
  37 +
  38 + r = []
  39 + specs.each do |namespace, spec|
  40 + namespace = "#{namespace}:" if namespace.present?
  41 + key_attr = spec[:key_attr] || :property
  42 + value_attr = spec[:value_attr] || :content
  43 + tags = spec[:tags]
  44 +
  45 + tags.each do |key, values|
  46 + key = "#{namespace}#{key}"
  47 + values = values.call(object, plugin) if values.is_a? Proc rescue nil
  48 + next if values.blank?
  49 +
  50 + Array(values).each do |value|
  51 + value = value.call(object, plugin) if value.is_a? Proc rescue nil
  52 + next if value.blank?
  53 + r << tag(:meta, key_attr => key, value_attr => CGI.escape_html(value.to_s))
  54 + end
  55 + end
  56 + end
  57 + r.join
  58 + end
  59 + end
  60 +
  61 + include MetadataPlugin::UrlHelper
  62 +
  63 + def helpers
  64 + self.context.class.helpers
  65 + end
  66 +
  67 + protected
  68 +
  69 +end
  70 +
  71 +ActiveSupport.run_load_hooks :metadata_plugin, MetadataPlugin
  72 +ActiveSupport.on_load :active_record do
  73 + ActiveRecord::Base.extend MetadataPlugin::Specs::ClassMethods
  74 +end
  75 +
... ...
plugins/metadata/lib/metadata_plugin/controllers.rb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +class MetadataPlugin::Controllers
  2 +
  3 + def manage_products
  4 + :@product
  5 + end
  6 +
  7 + def content_viewer
  8 + lambda do
  9 + if profile and @page and profile.home_page_id == @page.id
  10 + @profile
  11 + elsif @page.respond_to? :encapsulated_file
  12 + @page.encapsulated_file
  13 + else
  14 + @page
  15 + end
  16 + end
  17 + end
  18 +
  19 + def profile
  20 + :@profile
  21 + end
  22 +
  23 + def home
  24 + :@environment
  25 + end
  26 +
  27 +end
... ...
plugins/metadata/lib/metadata_plugin/url_helper.rb 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +module MetadataPlugin::UrlHelper
  2 +
  3 + def og_domain
  4 + MetadataPlugin.config[:open_graph][:domain] rescue context.send(:environment).default_hostname
  5 + end
  6 +
  7 + def og_url_for options
  8 + options.delete :port
  9 + options[:host] = self.og_domain
  10 + Noosfero::Application.routes.url_helpers.url_for options
  11 + end
  12 +
  13 + def og_profile_url profile
  14 + # open_graph client don't like redirects, give the exact url
  15 + if profile.home_page_id.present?
  16 + # force profile identifier for custom domains and fixed host. see og_url_for
  17 + profile.url.merge profile: profile.identifier
  18 + else
  19 + {controller: :profile, profile: profile.identifier}
  20 + end
  21 + end
  22 +
  23 +end
... ...
plugins/metadata/test/functional/home_controller_test.rb
... ... @@ -11,8 +11,9 @@ class HomeControllerTest &lt; ActionController::TestCase
11 11 @request = ActionController::TestRequest.new
12 12 @response = ActionController::TestResponse.new
13 13  
14   - Noosfero::Plugin.stubs(:all).returns([MetadataPlugin.name])
15   - Noosfero::Plugin::Manager.any_instance.stubs(:enabled_plugins).returns([MetadataPlugin.new(@controller)])
  14 + @environment = Environment.default
  15 + @environment.enabled_plugins += ['MetadataPlugin']
  16 + @environment.save!
16 17 end
17 18  
18 19 should 'display meta tags for social media' do
... ...
plugins/metadata/test/functional/manage_products_controller_test.rb
... ... @@ -16,8 +16,8 @@ class ManageProductsControllerTest &lt; ActionController::TestCase
16 16 @environment.enable('products_for_enterprises')
17 17 login_as :test_user
18 18  
19   - Noosfero::Plugin.stubs(:all).returns([MetadataPlugin.name])
20   - Noosfero::Plugin::Manager.any_instance.stubs(:enabled_plugins).returns([MetadataPlugin.new(@controller)])
  19 + @environment.enabled_plugins += ['MetadataPlugin']
  20 + @environment.save!
21 21 end
22 22  
23 23 should "not crash on new products" do
... ...