Commit c4a8f9bfc6095396090061adbfd34b1601e7bea7

Authored by Larissa Reis
2 parents 28beaa39 14b34706

Merge branch 'master' into federation

Updates branch federation with current master
Showing 1204 changed files with 19570 additions and 18872 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 1204 files displayed.

.gitlab-ci.yml
... ... @@ -30,14 +30,47 @@ integration:
30 30 script: bundle exec rake test:integration
31 31 stage: all-tests
32 32  
33   -cucumber:
34   - script: bundle exec rake cucumber
  33 +cucumber-1:
  34 + script: SLICE=1/2 bundle exec rake cucumber
  35 + stage: all-tests
  36 +cucumber-2:
  37 + script: SLICE=2/2 bundle exec rake cucumber
35 38 stage: all-tests
36 39  
37   -selenium:
38   - script: bundle exec rake selenium
  40 +selenium-1:
  41 + script: SLICE=1/6 bundle exec rake selenium
  42 + stage: all-tests
  43 +selenium-2:
  44 + script: SLICE=2/6 bundle exec rake selenium
  45 + stage: all-tests
  46 +selenium-3:
  47 + script: SLICE=3/6 bundle exec rake selenium
  48 + stage: all-tests
  49 +selenium-4:
  50 + script: SLICE=4/6 bundle exec rake selenium
  51 + stage: all-tests
  52 +selenium-5:
  53 + script: SLICE=5/6 bundle exec rake selenium
  54 + stage: all-tests
  55 +selenium-6:
  56 + script: SLICE=6/6 bundle exec rake selenium
39 57 stage: all-tests
40 58  
41   -plugins:
42   - script: bundle exec rake test:noosfero_plugins
  59 +# NOOSFERO_BUNDLE_OPTS=install makes migrations fails
  60 +# probably because of rubygems-integration
  61 +plugins-1:
  62 + script: SLICE=1/5 bundle exec rake test:noosfero_plugins
43 63 stage: all-tests
  64 +plugins-2:
  65 + script: SLICE=2/5 bundle exec rake test:noosfero_plugins
  66 + stage: all-tests
  67 +plugins-3:
  68 + script: SLICE=3/5 bundle exec rake test:noosfero_plugins
  69 + stage: all-tests
  70 +plugins-4:
  71 + script: SLICE=4/5 bundle exec rake test:noosfero_plugins
  72 + stage: all-tests
  73 +plugins-5:
  74 + script: SLICE=5/5 bundle exec rake test:noosfero_plugins
  75 + stage: all-tests
  76 +
... ...
.travis.yml
... ... @@ -18,7 +18,7 @@ cache: bundler
18 18  
19 19 language: ruby
20 20 rvm:
21   - - 2.3.0
  21 + - 2.3.1
22 22  
23 23 addons:
24 24 apt:
... ... @@ -29,11 +29,6 @@ addons:
29 29 paths:
30 30 - $(ls tmp/artifact* | tr "\n" ":")
31 31  
32   -# workaround for https://github.com/travis-ci/travis-ci/issues/4536
33   -before_install:
34   - - export GEM_HOME=$PWD/vendor/bundle/ruby/2.2.0
35   - - gem install bundler
36   -
37 32 before_script:
38 33 - mkdir -p tmp/{pids,cache} log cache
39 34 - script/noosfero-plugins disableall
... ... @@ -45,21 +40,36 @@ before_script:
45 40 - bundle exec rake db:migrate &>/dev/null
46 41  
47 42 env:
48   - - TASK=test:api
49   - - TASK=test:units
50   - - TASK=test:functionals
51   - - TASK=test:integration
52   - - SLICE=1/2 TASK=cucumber LANG=en
53   - - SLICE=2/2 TASK=cucumber LANG=en
54   - - SLICE=1/4 TASK=selenium
55   - - SLICE=2/4 TASK=selenium
56   - - SLICE=3/4 TASK=selenium
57   - - SLICE=4/4 TASK=selenium
58   - - SLICE=1/5 TASK=test:noosfero_plugins BUNDLE_OPTS=install
59   - - SLICE=2/5 TASK=test:noosfero_plugins BUNDLE_OPTS=install
60   - - SLICE=3/5 TASK=test:noosfero_plugins BUNDLE_OPTS=install
61   - - SLICE=4/5 TASK=test:noosfero_plugins BUNDLE_OPTS=install
62   - - SLICE=5/5 TASK=test:noosfero_plugins BUNDLE_OPTS=install
  43 + global:
  44 + - LANG=en
  45 + matrix:
  46 + - TASK=test:api
  47 + - TASK=test:units
  48 + - TASK=test:functionals
  49 + - TASK=test:integration
  50 + - SLICE=1/2 TASK=cucumber
  51 + - SLICE=2/2 TASK=cucumber
  52 + - SLICE=1/4 TASK=selenium
  53 + - SLICE=2/4 TASK=selenium
  54 + - SLICE=3/4 TASK=selenium
  55 + - SLICE=4/4 TASK=selenium
  56 + - SLICE=1/5 TASK=test:noosfero_plugins NOOSFERO_BUNDLE_OPTS=install
  57 + - SLICE=2/5 TASK=test:noosfero_plugins NOOSFERO_BUNDLE_OPTS=install
  58 + - SLICE=3/5 TASK=test:noosfero_plugins NOOSFERO_BUNDLE_OPTS=install
  59 + - SLICE=4/5 TASK=test:noosfero_plugins NOOSFERO_BUNDLE_OPTS=install
  60 + - SLICE=5/5 TASK=test:noosfero_plugins NOOSFERO_BUNDLE_OPTS=install
  61 + # chrome hanging on travis
  62 + #- SLICE=1/4 TASK=selenium SELENIUM_DRIVER=chrome
  63 + #- SLICE=2/4 TASK=selenium SELENIUM_DRIVER=chrome
  64 + #- SLICE=3/4 TASK=selenium SELENIUM_DRIVER=chrome
  65 + #- SLICE=4/4 TASK=selenium SELENIUM_DRIVER=chrome
  66 +
  67 +matrix:
  68 + allow_failures:
  69 + - env: SLICE=1/4 TASK=selenium SELENIUM_DRIVER=chrome
  70 + - env: SLICE=2/4 TASK=selenium SELENIUM_DRIVER=chrome
  71 + - env: SLICE=3/4 TASK=selenium SELENIUM_DRIVER=chrome
  72 + - env: SLICE=4/4 TASK=selenium SELENIUM_DRIVER=chrome
63 73  
64 74 script:
65 75 - bundle exec rake $TASK
... ...
Gemfile
... ... @@ -77,6 +77,7 @@ group :cucumber do
77 77 gem 'cucumber-rails', '~> 1.4.2', :require => false
78 78 gem 'database_cleaner', '~> 1.3'
79 79 gem 'selenium-webdriver', '>= 2.50'
  80 + gem 'chromedriver-helper' if ENV['SELENIUM_DRIVER'] == 'chrome'
80 81 end
81 82  
82 83 # Requires custom dependencies
... ...
INSTALL.varnish.md
... ... @@ -19,7 +19,6 @@ Install the RPAF apache module (or skip this step if not using apache):
19 19  
20 20 3a) Edit `/etc/apache2/ports.conf`, and:
21 21  
22   - * change `NameVirtualHost *:80` to `NameVirtualHost *:8080`
23 22 * change `Listen 80` to `Listen 127.0.0.1:8080`
24 23  
25 24 3b) Edit `/etc/apache2/sites-enabled/*`, and change `<VirtualHost *:80>` to `<VirtualHost *:8080>`
... ... @@ -30,6 +29,7 @@ Install the RPAF apache module (or skip this step if not using apache):
30 29  
31 30 * change the line that says `START=no` to say `START=yes`
32 31 * change `-a :6081` to `-a :80`
  32 + * add parameter `-p vcc_allow_inline_c=on` on `DAEMON_OPTS`
33 33  
34 34 4b) Edit `/etc/varnish/default.vcl` and add the following lines at the end:
35 35  
... ...
README.rails.md
... ... @@ -99,7 +99,7 @@ Description of contents
99 99 Holds controllers that should be named like weblog_controller.rb for automated URL mapping. All controllers should descend from `ActionController::Base`.
100 100  
101 101 * `app/models`
102   - Holds models that should be named like post.rb. Most models will descend from `ActiveRecord::Base`.
  102 + Holds models that should be named like post.rb. Most models will descend from `ApplicationRecord`.
103 103  
104 104 * `app/views`
105 105 Holds the template files for the view that should be named like `weblog/index.rhtml` for the `WeblogController#index` action. All views use eRuby syntax. This directory can also be used to keep stylesheets, images, and so on that can be symlinked to public.
... ...
app/api/app.rb 0 → 100644
... ... @@ -0,0 +1,89 @@
  1 +require_dependency 'api/helpers'
  2 +
  3 +module Api
  4 + class App < Grape::API
  5 + use Rack::JSONP
  6 +
  7 + logger = Logger.new(File.join(Rails.root, 'log', "#{ENV['RAILS_ENV'] || 'production'}_api.log"))
  8 + logger.formatter = GrapeLogging::Formatters::Default.new
  9 + #use GrapeLogging::Middleware::RequestLogger, { logger: logger }
  10 +
  11 + rescue_from :all do |e|
  12 + logger.error e
  13 + error! e.message, 500
  14 + end unless Rails.env.test?
  15 +
  16 + @@NOOSFERO_CONF = nil
  17 + def self.NOOSFERO_CONF
  18 + if @@NOOSFERO_CONF
  19 + @@NOOSFERO_CONF
  20 + else
  21 + file = Rails.root.join('config', 'noosfero.yml')
  22 + @@NOOSFERO_CONF = File.exists?(file) ? YAML.load_file(file)[Rails.env] || {} : {}
  23 + end
  24 + end
  25 +
  26 + before { set_locale }
  27 + before { setup_multitenancy }
  28 + before { detect_stuff_by_domain }
  29 + before { filter_disabled_plugins_endpoints }
  30 + before { init_noosfero_plugins }
  31 + after { set_session_cookie }
  32 +
  33 + version 'v1'
  34 + prefix [ENV['RAILS_RELATIVE_URL_ROOT'], "api"].compact.join('/')
  35 + format :json
  36 + content_type :txt, "text/plain"
  37 +
  38 + helpers Helpers
  39 +
  40 + mount V1::Session
  41 + mount V1::Articles
  42 + mount V1::Comments
  43 + mount V1::Users
  44 + mount V1::Communities
  45 + mount V1::People
  46 + mount V1::Enterprises
  47 + mount V1::Categories
  48 + mount V1::Tasks
  49 + mount V1::Tags
  50 + mount V1::Environments
  51 + mount V1::Search
  52 + mount V1::Contacts
  53 + mount V1::Boxes
  54 + mount V1::Blocks
  55 + mount V1::Profiles
  56 + mount V1::Activities
  57 +
  58 + # hook point which allow plugins to add Grape::API extensions to Api::App
  59 + #finds for plugins which has api mount points classes defined (the class should extends Grape::API)
  60 + @plugins = Noosfero::Plugin.all.map { |p| p.constantize }
  61 + @plugins.each do |klass|
  62 + if klass.public_methods.include? :api_mount_points
  63 + klass.api_mount_points.each do |mount_class|
  64 + mount mount_class if mount_class && ( mount_class < Grape::API )
  65 + end
  66 + end
  67 + end
  68 +
  69 + def self.endpoint_unavailable?(endpoint, environment)
  70 + api_class = endpoint.options[:app] || endpoint.options[:for]
  71 + if api_class.present?
  72 + klass = api_class.name.deconstantize.constantize
  73 + return klass < Noosfero::Plugin && !environment.plugin_enabled?(klass)
  74 + end
  75 + end
  76 +
  77 + class << self
  78 + def endpoints_with_plugins(environment = nil)
  79 + if environment.present?
  80 + cloned_endpoints = endpoints_without_plugins.dup
  81 + cloned_endpoints.delete_if { |endpoint| endpoint_unavailable?(endpoint, environment) }
  82 + else
  83 + endpoints_without_plugins
  84 + end
  85 + end
  86 + alias_method_chain :endpoints, :plugins
  87 + end
  88 + end
  89 +end
... ...
app/api/entities.rb 0 → 100644
... ... @@ -0,0 +1,269 @@
  1 +module Api
  2 + module Entities
  3 +
  4 + Entity.format_with :timestamp do |date|
  5 + date.strftime('%Y/%m/%d %H:%M:%S') if date
  6 + end
  7 +
  8 + PERMISSIONS = {
  9 + :admin => 0,
  10 + :self => 10,
  11 + :private_content => 20,
  12 + :logged_user => 30,
  13 + :anonymous => 40
  14 + }
  15 +
  16 + def self.can_display_profile_field? profile, options, permission_options={}
  17 + permissions={:field => "", :permission => :private_content}
  18 + permissions.merge!(permission_options)
  19 + field = permissions[:field]
  20 + permission = permissions[:permission]
  21 + return true if profile.public? && profile.public_fields.map{|f| f.to_sym}.include?(field.to_sym)
  22 +
  23 + current_person = options[:current_person]
  24 +
  25 + current_permission = if current_person.present?
  26 + if current_person.is_admin?
  27 + :admin
  28 + elsif current_person == profile
  29 + :self
  30 + elsif profile.display_private_info_to?(current_person)
  31 + :private_content
  32 + else
  33 + :logged_user
  34 + end
  35 + else
  36 + :anonymous
  37 + end
  38 + PERMISSIONS[current_permission] <= PERMISSIONS[permission]
  39 + end
  40 +
  41 + class Image < Entity
  42 + root 'images', 'image'
  43 +
  44 + expose :url do |image, options|
  45 + image.public_filename
  46 + end
  47 +
  48 + expose :icon_url do |image, options|
  49 + image.public_filename(:icon)
  50 + end
  51 +
  52 + expose :minor_url do |image, options|
  53 + image.public_filename(:minor)
  54 + end
  55 +
  56 + expose :portrait_url do |image, options|
  57 + image.public_filename(:portrait)
  58 + end
  59 +
  60 + expose :thumb_url do |image, options|
  61 + image.public_filename(:thumb)
  62 + end
  63 + end
  64 +
  65 + class CategoryBase < Entity
  66 + root 'categories', 'category'
  67 + expose :name, :id, :slug
  68 + end
  69 +
  70 + class Category < CategoryBase
  71 + root 'categories', 'category'
  72 + expose :full_name do |category, options|
  73 + category.full_name
  74 + end
  75 + expose :parent, :using => CategoryBase, if: { parent: true }
  76 + expose :children, :using => CategoryBase, if: { children: true }
  77 + expose :image, :using => Image
  78 + expose :display_color
  79 + end
  80 +
  81 + class Region < Category
  82 + root 'regions', 'region'
  83 + expose :parent_id
  84 + end
  85 +
  86 + class Block < Entity
  87 + root 'blocks', 'block'
  88 + expose :id, :type, :settings, :position, :enabled
  89 + expose :mirror, :mirror_block_id, :title
  90 + expose :api_content, if: lambda { |object, options| options[:display_api_content] || object.display_api_content_by_default? }
  91 + end
  92 +
  93 + class Box < Entity
  94 + root 'boxes', 'box'
  95 + expose :id, :position
  96 + expose :blocks, :using => Block do |box, options|
  97 + box.blocks.select {|block| block.visible_to_user?(options[:current_person]) }
  98 + end
  99 + end
  100 +
  101 + class Profile < Entity
  102 + expose :identifier, :name, :id
  103 + expose :created_at, :format_with => :timestamp
  104 + expose :updated_at, :format_with => :timestamp
  105 + expose :additional_data do |profile, options|
  106 + hash ={}
  107 + profile.public_values.each do |value|
  108 + hash[value.custom_field.name]=value.value
  109 + end
  110 +
  111 + private_values = profile.custom_field_values - profile.public_values
  112 + private_values.each do |value|
  113 + if Entities.can_display_profile_field?(profile,options)
  114 + hash[value.custom_field.name]=value.value
  115 + end
  116 + end
  117 + hash
  118 + end
  119 + expose :image, :using => Image
  120 + expose :region, :using => Region
  121 + expose :type
  122 + expose :custom_header
  123 + expose :custom_footer
  124 + end
  125 +
  126 + class UserBasic < Entity
  127 + expose :id
  128 + expose :login
  129 + end
  130 +
  131 + class Person < Profile
  132 + root 'people', 'person'
  133 + expose :user, :using => UserBasic, documentation: {type: 'User', desc: 'The user data of a person' }
  134 + expose :vote_count
  135 + expose :comments_count do |person, options|
  136 + person.comments.count
  137 + end
  138 + expose :following_articles_count do |person, options|
  139 + person.following_articles.count
  140 + end
  141 + expose :articles_count do |person, options|
  142 + person.articles.count
  143 + end
  144 + end
  145 +
  146 + class Enterprise < Profile
  147 + root 'enterprises', 'enterprise'
  148 + end
  149 +
  150 + class Community < Profile
  151 + root 'communities', 'community'
  152 + expose :description
  153 + expose :admins, :if => lambda { |community, options| community.display_info_to? options[:current_person]} do |community, options|
  154 + community.admins.map{|admin| {"name"=>admin.name, "id"=>admin.id, "username" => admin.identifier}}
  155 + end
  156 + expose :categories, :using => Category
  157 + expose :members, :using => Person , :if => lambda{ |community, options| community.display_info_to? options[:current_person] }
  158 + end
  159 +
  160 + class CommentBase < Entity
  161 + expose :body, :title, :id
  162 + expose :created_at, :format_with => :timestamp
  163 + expose :author, :using => Profile
  164 + expose :reply_of, :using => CommentBase
  165 + end
  166 +
  167 + class Comment < CommentBase
  168 + root 'comments', 'comment'
  169 + expose :children, as: :replies, :using => Comment
  170 + end
  171 +
  172 + class ArticleBase < Entity
  173 + root 'articles', 'article'
  174 + expose :id
  175 + expose :body
  176 + expose :abstract, documentation: {type: 'String', desc: 'Teaser of the body'}
  177 + expose :created_at, :format_with => :timestamp
  178 + expose :updated_at, :format_with => :timestamp
  179 + expose :title, :documentation => {:type => "String", :desc => "Title of the article"}
  180 + expose :created_by, :as => :author, :using => Profile, :documentation => {type: 'Profile', desc: 'The profile author that create the article'}
  181 + expose :profile, :using => Profile, :documentation => {type: 'Profile', desc: 'The profile associated with the article'}
  182 + expose :categories, :using => Category
  183 + expose :image, :using => Image
  184 + expose :votes_for
  185 + expose :votes_against
  186 + expose :setting
  187 + expose :position
  188 + expose :hits
  189 + expose :start_date
  190 + expose :end_date, :documentation => {type: 'DateTime', desc: 'The date of finish of the article'}
  191 + expose :tag_list
  192 + expose :children_count
  193 + expose :slug, :documentation => {:type => "String", :desc => "Trimmed and parsed name of a article"}
  194 + expose :path
  195 + expose :followers_count
  196 + expose :votes_count
  197 + expose :comments_count
  198 + expose :archived, :documentation => {:type => "Boolean", :desc => "Defines if a article is readonly"}
  199 + expose :type
  200 + expose :comments, using: CommentBase, :if => lambda{|obj,opt| opt[:params] && ['1','true',true].include?(opt[:params][:show_comments])}
  201 + expose :published
  202 + expose :accept_comments?, as: :accept_comments
  203 + end
  204 +
  205 + class Article < ArticleBase
  206 + root 'articles', 'article'
  207 + expose :parent, :using => ArticleBase
  208 + expose :children, :using => ArticleBase do |article, options|
  209 + article.children.published.limit(V1::Articles::MAX_PER_PAGE)
  210 + end
  211 + end
  212 +
  213 + class User < Entity
  214 + root 'users', 'user'
  215 +
  216 + attrs = [:id,:login,:email,:activated?]
  217 + aliases = {:activated? => :activated}
  218 +
  219 + attrs.each do |attribute|
  220 + name = aliases.has_key?(attribute) ? aliases[attribute] : attribute
  221 + expose attribute, :as => name, :if => lambda{|user,options| Entities.can_display_profile_field?(user.person, options, {:field => attribute})}
  222 + end
  223 +
  224 + expose :person, :using => Person, :if => lambda{|user,options| user.person.display_info_to? options[:current_person]}
  225 + expose :permissions, :if => lambda{|user,options| Entities.can_display_profile_field?(user.person, options, {:field => :permissions, :permission => :self})} do |user, options|
  226 + output = {}
  227 + user.person.role_assignments.map do |role_assigment|
  228 + if role_assigment.resource.respond_to?(:identifier) && !role_assigment.role.nil?
  229 + output[role_assigment.resource.identifier] = role_assigment.role.permissions
  230 + end
  231 + end
  232 + output
  233 + end
  234 + end
  235 +
  236 + class UserLogin < User
  237 + root 'users', 'user'
  238 + expose :private_token, documentation: {type: 'String', desc: 'A valid authentication code for post/delete api actions'}, if: lambda {|object, options| object.activated? }
  239 + end
  240 +
  241 + class Task < Entity
  242 + root 'tasks', 'task'
  243 + expose :id
  244 + expose :type
  245 + end
  246 +
  247 + class Environment < Entity
  248 + expose :name
  249 + expose :id
  250 + expose :description
  251 + expose :settings, if: lambda { |instance, options| options[:is_admin] }
  252 + end
  253 +
  254 + class Tag < Entity
  255 + root 'tags', 'tag'
  256 + expose :name
  257 + end
  258 +
  259 + class Activity < Entity
  260 + root 'activities', 'activity'
  261 + expose :id, :params, :verb, :created_at, :updated_at, :comments_count, :visible
  262 + expose :user, :using => Profile
  263 + expose :target do |activity, opts|
  264 + type_map = {Profile => ::Profile, ArticleBase => ::Article}.find {|h| activity.target.kind_of?(h.last)}
  265 + type_map.first.represent(activity.target) unless type_map.nil?
  266 + end
  267 + end
  268 + end
  269 +end
... ...
app/api/entity.rb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +module Api
  2 + class Entity < Grape::Entity
  3 +
  4 + def initialize(object, options = {})
  5 + object = nil if object.is_a? Exception
  6 + super object, options
  7 + end
  8 +
  9 + def self.represent(objects, options = {})
  10 + if options[:has_exception]
  11 + data = super objects, options.merge(is_inner_data: true)
  12 + if objects.is_a? Exception
  13 + data.merge ok: false, error: {
  14 + type: objects.class.name,
  15 + message: objects.message
  16 + }
  17 + else
  18 + data = data.serializable_hash if data.is_a? Entity
  19 + data.merge ok: true, error: { type: 'Success', message: '' }
  20 + end
  21 + else
  22 + super objects, options
  23 + end
  24 + end
  25 +
  26 + end
  27 +end
... ...
app/api/helpers.rb 0 → 100644
... ... @@ -0,0 +1,423 @@
  1 +require 'base64'
  2 +require 'tempfile'
  3 +
  4 +module Api
  5 + module Helpers
  6 + PRIVATE_TOKEN_PARAM = :private_token
  7 + DEFAULT_ALLOWED_PARAMETERS = [:parent_id, :from, :until, :content_type, :author_id, :identifier, :archived]
  8 +
  9 + include SanitizeParams
  10 + include Noosfero::Plugin::HotSpot
  11 + include ForgotPasswordHelper
  12 + include SearchTermHelper
  13 +
  14 + def set_locale
  15 + I18n.locale = (params[:lang] || request.env['HTTP_ACCEPT_LANGUAGE'] || 'en')
  16 + end
  17 +
  18 + def init_noosfero_plugins
  19 + plugins
  20 + end
  21 +
  22 + def current_user
  23 + private_token = (params[PRIVATE_TOKEN_PARAM] || headers['Private-Token']).to_s
  24 + @current_user ||= User.find_by private_token: private_token
  25 + @current_user ||= plugins.dispatch("api_custom_login", request).first
  26 + @current_user
  27 + end
  28 +
  29 + def current_person
  30 + current_user.person unless current_user.nil?
  31 + end
  32 +
  33 + def is_admin?(environment)
  34 + return false unless current_user
  35 + return current_person.is_admin?(environment)
  36 + end
  37 +
  38 + def logout
  39 + @current_user = nil
  40 + end
  41 +
  42 + def environment
  43 + @environment
  44 + end
  45 +
  46 + def present_partial(model, options)
  47 + if(params[:fields].present?)
  48 + begin
  49 + fields = JSON.parse(params[:fields])
  50 + if fields.present?
  51 + options.merge!(fields.symbolize_keys.slice(:only, :except))
  52 + end
  53 + rescue
  54 + fields = params[:fields]
  55 + fields = fields.split(',') if fields.kind_of?(String)
  56 + options[:only] = Array.wrap(fields)
  57 + end
  58 + end
  59 + present model, options
  60 + end
  61 +
  62 + include FindByContents
  63 +
  64 + ####################################################################
  65 + #### SEARCH
  66 + ####################################################################
  67 + def multiple_search?(searches=nil)
  68 + ['index', 'category_index'].include?(params[:action]) || (searches && searches.size > 1)
  69 + end
  70 + ####################################################################
  71 +
  72 + def logger
  73 + logger = Logger.new(File.join(Rails.root, 'log', "#{ENV['RAILS_ENV'] || 'production'}_api.log"))
  74 + logger.formatter = GrapeLogging::Formatters::Default.new
  75 + logger
  76 + end
  77 +
  78 + def limit
  79 + limit = params[:limit].to_i
  80 + limit = default_limit if limit <= 0
  81 + limit
  82 + end
  83 +
  84 + def period(from_date, until_date)
  85 + return nil if from_date.nil? && until_date.nil?
  86 +
  87 + begin_period = from_date.nil? ? Time.at(0).to_datetime : from_date
  88 + end_period = until_date.nil? ? DateTime.now : until_date
  89 +
  90 + begin_period..end_period
  91 + end
  92 +
  93 + def parse_content_type(content_type)
  94 + return nil if content_type.blank?
  95 + content_type.split(',').map do |content_type|
  96 + content_type.camelcase
  97 + end
  98 + end
  99 +
  100 + def find_article(articles, id)
  101 + article = articles.find(id)
  102 + article.display_to?(current_person) ? article : forbidden!
  103 + end
  104 +
  105 + def post_article(asset, params)
  106 + return forbidden! unless current_person.can_post_content?(asset)
  107 +
  108 + klass_type = params[:content_type] || params[:article].delete(:type) || TinyMceArticle.name
  109 + return forbidden! unless klass_type.constantize <= Article
  110 +
  111 + article = klass_type.constantize.new(params[:article])
  112 + article.last_changed_by = current_person
  113 + article.created_by= current_person
  114 + article.profile = asset
  115 +
  116 + if !article.save
  117 + render_api_errors!(article.errors.full_messages)
  118 + end
  119 + present_partial article, :with => Entities::Article
  120 + end
  121 +
  122 + def present_article(asset)
  123 + article = find_article(asset.articles, params[:id])
  124 + present_partial article, :with => Entities::Article, :params => params
  125 + end
  126 +
  127 + def present_articles_for_asset(asset, method = 'articles')
  128 + articles = find_articles(asset, method)
  129 + present_articles(articles)
  130 + end
  131 +
  132 + def present_articles(articles)
  133 + present_partial paginate(articles), :with => Entities::Article, :params => params
  134 + end
  135 +
  136 + def find_articles(asset, method = 'articles')
  137 + articles = select_filtered_collection_of(asset, method, params)
  138 + if current_person.present?
  139 + articles = articles.display_filter(current_person, nil)
  140 + else
  141 + articles = articles.published
  142 + end
  143 + articles
  144 + end
  145 +
  146 + def find_task(asset, id)
  147 + task = asset.tasks.find(id)
  148 + current_person.has_permission?(task.permission, asset) ? task : forbidden!
  149 + end
  150 +
  151 + def post_task(asset, params)
  152 + klass_type= params[:content_type].nil? ? 'Task' : params[:content_type]
  153 + return forbidden! unless klass_type.constantize <= Task
  154 +
  155 + task = klass_type.constantize.new(params[:task])
  156 + task.requestor_id = current_person.id
  157 + task.target_id = asset.id
  158 + task.target_type = 'Profile'
  159 +
  160 + if !task.save
  161 + render_api_errors!(task.errors.full_messages)
  162 + end
  163 + present_partial task, :with => Entities::Task
  164 + end
  165 +
  166 + def present_task(asset)
  167 + task = find_task(asset, params[:id])
  168 + present_partial task, :with => Entities::Task
  169 + end
  170 +
  171 + def present_tasks(asset)
  172 + tasks = select_filtered_collection_of(asset, 'tasks', params)
  173 + tasks = tasks.select {|t| current_person.has_permission?(t.permission, asset)}
  174 + return forbidden! if tasks.empty? && !current_person.has_permission?(:perform_task, asset)
  175 + present_partial tasks, :with => Entities::Task
  176 + end
  177 +
  178 + def make_conditions_with_parameter(params = {})
  179 + parsed_params = parser_params(params)
  180 + conditions = {}
  181 + from_date = DateTime.parse(parsed_params.delete(:from)) if parsed_params[:from]
  182 + until_date = DateTime.parse(parsed_params.delete(:until)) if parsed_params[:until]
  183 +
  184 + conditions[:type] = parse_content_type(parsed_params.delete(:content_type)) unless parsed_params[:content_type].nil?
  185 +
  186 + conditions[:created_at] = period(from_date, until_date) if from_date || until_date
  187 + conditions.merge!(parsed_params)
  188 +
  189 + conditions
  190 + end
  191 +
  192 + # changing make_order_with_parameters to avoid sql injection
  193 + def make_order_with_parameters(object, method, params)
  194 + order = "created_at DESC"
  195 + unless params[:order].blank?
  196 + if params[:order].include? '\'' or params[:order].include? '"'
  197 + order = "created_at DESC"
  198 + elsif ['RANDOM()', 'RANDOM'].include? params[:order].upcase
  199 + order = 'RANDOM()'
  200 + else
  201 + field_name, direction = params[:order].split(' ')
  202 + assoc = object.class.reflect_on_association(method.to_sym)
  203 + if !field_name.blank? and assoc
  204 + if assoc.klass.attribute_names.include? field_name
  205 + if direction.present? and ['ASC','DESC'].include? direction.upcase
  206 + order = "#{field_name} #{direction.upcase}"
  207 + end
  208 + end
  209 + end
  210 + end
  211 + end
  212 + return order
  213 + end
  214 +
  215 + def make_timestamp_with_parameters_and_method(params, method)
  216 + timestamp = nil
  217 + if params[:timestamp]
  218 + datetime = DateTime.parse(params[:timestamp])
  219 + table_name = method.to_s.singularize.camelize.constantize.table_name
  220 + timestamp = "#{table_name}.updated_at >= '#{datetime}'"
  221 + end
  222 +
  223 + timestamp
  224 + end
  225 +
  226 + def by_reference(scope, params)
  227 + reference_id = params[:reference_id].to_i == 0 ? nil : params[:reference_id].to_i
  228 + if reference_id.nil?
  229 + scope
  230 + else
  231 + created_at = scope.find(reference_id).created_at
  232 + scope.send("#{params.key?(:oldest) ? 'older_than' : 'younger_than'}", created_at)
  233 + end
  234 + end
  235 +
  236 + def by_categories(scope, params)
  237 + category_ids = params[:category_ids]
  238 + if category_ids.nil?
  239 + scope
  240 + else
  241 + scope.joins(:categories).where(:categories => {:id => category_ids})
  242 + end
  243 + end
  244 +
  245 + def select_filtered_collection_of(object, method, params)
  246 + conditions = make_conditions_with_parameter(params)
  247 + order = make_order_with_parameters(object,method,params)
  248 + timestamp = make_timestamp_with_parameters_and_method(params, method)
  249 +
  250 + objects = object.send(method)
  251 + objects = by_reference(objects, params)
  252 + objects = by_categories(objects, params)
  253 +
  254 + objects = objects.where(conditions).where(timestamp).reorder(order)
  255 +
  256 + params[:page] ||= 1
  257 + params[:per_page] ||= limit
  258 + paginate(objects)
  259 + end
  260 +
  261 + def authenticate!
  262 + unauthorized! unless current_user
  263 + end
  264 +
  265 + def profiles_for_person(profiles, person)
  266 + if person
  267 + profiles.listed_for_person(person)
  268 + else
  269 + profiles.visible
  270 + end
  271 + end
  272 +
  273 + # Checks the occurrences of uniqueness of attributes, each attribute must be present in the params hash
  274 + # or a Bad Request error is invoked.
  275 + #
  276 + # Parameters:
  277 + # keys (unique) - A hash consisting of keys that must be unique
  278 + def unique_attributes!(obj, keys)
  279 + keys.each do |key|
  280 + cant_be_saved_request!(key) if obj.find_by(key.to_s => params[key])
  281 + end
  282 + end
  283 +
  284 + def attributes_for_keys(keys)
  285 + attrs = {}
  286 + keys.each do |key|
  287 + attrs[key] = params[key] if params[key].present? or (params.has_key?(key) and params[key] == false)
  288 + end
  289 + attrs
  290 + end
  291 +
  292 + ##########################################
  293 + # error helpers #
  294 + ##########################################
  295 +
  296 + def not_found!
  297 + render_api_error!('404 Not found', 404)
  298 + end
  299 +
  300 + def forbidden!
  301 + render_api_error!('403 Forbidden', 403)
  302 + end
  303 +
  304 + def cant_be_saved_request!(attribute)
  305 + message = _("(Invalid request) %s can't be saved") % attribute
  306 + render_api_error!(message, 400)
  307 + end
  308 +
  309 + def bad_request!(attribute)
  310 + message = _("(Invalid request) %s not given") % attribute
  311 + render_api_error!(message, 400)
  312 + end
  313 +
  314 + def something_wrong!
  315 + message = _("Something wrong happened")
  316 + render_api_error!(message, 400)
  317 + end
  318 +
  319 + def unauthorized!
  320 + render_api_error!(_('Unauthorized'), 401)
  321 + end
  322 +
  323 + def not_allowed!
  324 + render_api_error!(_('Method Not Allowed'), 405)
  325 + end
  326 +
  327 + # javascript_console_message is supposed to be executed as console.log()
  328 + def render_api_error!(user_message, status, log_message = nil, javascript_console_message = nil)
  329 + message_hash = {'message' => user_message, :code => status}
  330 + message_hash[:javascript_console_message] = javascript_console_message if javascript_console_message.present?
  331 + log_msg = "#{status}, User message: #{user_message}"
  332 + log_msg = "#{log_message}, #{log_msg}" if log_message.present?
  333 + log_msg = "#{log_msg}, Javascript Console Message: #{javascript_console_message}" if javascript_console_message.present?
  334 + logger.error log_msg unless Rails.env.test?
  335 + error!(message_hash, status)
  336 + end
  337 +
  338 + def render_api_errors!(messages)
  339 + messages = messages.to_a if messages.class == ActiveModel::Errors
  340 + render_api_error!(messages.join(','), 400)
  341 + end
  342 +
  343 + protected
  344 +
  345 + def set_session_cookie
  346 + cookies['_noosfero_api_session'] = { value: @current_user.private_token, httponly: true } if @current_user.present?
  347 + end
  348 +
  349 + def setup_multitenancy
  350 + Noosfero::MultiTenancy.setup!(request.host)
  351 + end
  352 +
  353 + def detect_stuff_by_domain
  354 + @domain = Domain.by_name(request.host)
  355 + if @domain.nil?
  356 + @environment = Environment.default
  357 + if @environment.nil? && Rails.env.development?
  358 + # This should only happen in development ...
  359 + @environment = Environment.create!(:name => "Noosfero", :is_default => true)
  360 + end
  361 + else
  362 + @environment = @domain.environment
  363 + end
  364 + end
  365 +
  366 + def filter_disabled_plugins_endpoints
  367 + not_found! if Api::App.endpoint_unavailable?(self, @environment)
  368 + end
  369 +
  370 + def asset_with_image params
  371 + if params.has_key? :image_builder
  372 + asset_api_params = params
  373 + asset_api_params[:image_builder] = base64_to_uploadedfile(asset_api_params[:image_builder])
  374 + return asset_api_params
  375 + end
  376 + params
  377 + end
  378 +
  379 + def base64_to_uploadedfile(base64_image)
  380 + tempfile = base64_to_tempfile base64_image
  381 + converted_image = base64_image
  382 + converted_image[:tempfile] = tempfile
  383 + return {uploaded_data: ActionDispatch::Http::UploadedFile.new(converted_image)}
  384 + end
  385 +
  386 + def base64_to_tempfile base64_image
  387 + base64_img_str = base64_image[:tempfile]
  388 + decoded_base64_str = Base64.decode64(base64_img_str)
  389 + tempfile = Tempfile.new(base64_image[:filename])
  390 + tempfile.write(decoded_base64_str.encode("ascii-8bit").force_encoding("utf-8"))
  391 + tempfile.rewind
  392 + tempfile
  393 + end
  394 + private
  395 +
  396 + def parser_params(params)
  397 + parsed_params = {}
  398 + params.map do |k,v|
  399 + parsed_params[k.to_sym] = v if DEFAULT_ALLOWED_PARAMETERS.include?(k.to_sym)
  400 + end
  401 + parsed_params
  402 + end
  403 +
  404 + def default_limit
  405 + 20
  406 + end
  407 +
  408 + def parse_content_type(content_type)
  409 + return nil if content_type.blank?
  410 + content_types = content_type.split(',').map do |content_type|
  411 + content_type = content_type.camelcase
  412 + content_type == 'TextArticle' ? Article.text_article_types : content_type
  413 + end
  414 + content_types.flatten.uniq
  415 + end
  416 +
  417 + def period(from_date, until_date)
  418 + begin_period = from_date.nil? ? Time.at(0).to_datetime : from_date
  419 + end_period = until_date.nil? ? DateTime.now : until_date
  420 + begin_period..end_period
  421 + end
  422 + end
  423 +end
... ...
app/api/v1/activities.rb 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +module Api
  2 + module V1
  3 + class Activities < Grape::API
  4 +
  5 + resource :profiles do
  6 +
  7 + get ':id/activities' do
  8 + profile = Profile.find_by id: params[:id]
  9 +
  10 + not_found! if profile.blank? || profile.secret || !profile.visible
  11 + forbidden! if !profile.display_private_info_to?(current_person)
  12 +
  13 + activities = profile.activities.map(&:activity)
  14 + present activities, :with => Entities::Activity, :current_person => current_person
  15 + end
  16 + end
  17 + end
  18 + end
  19 +end
... ...
app/api/v1/articles.rb 0 → 100644
... ... @@ -0,0 +1,314 @@
  1 +module Api
  2 + module V1
  3 + class Articles < Grape::API
  4 +
  5 + ARTICLE_TYPES = Article.descendants.map{|a| a.to_s}
  6 +
  7 + MAX_PER_PAGE = 50
  8 +
  9 + resource :articles do
  10 +
  11 + paginate max_per_page: MAX_PER_PAGE
  12 + # Collect articles
  13 + #
  14 + # Parameters:
  15 + # from - date where the search will begin. If nothing is passed the default date will be the date of the first article created
  16 + # oldest - Collect the oldest articles. If nothing is passed the newest articles are collected
  17 + # limit - amount of articles returned. The default value is 20
  18 + #
  19 + # Example Request:
  20 + # GET host/api/v1/articles?from=2013-04-04-14:41:43&until=2015-04-04-14:41:43&limit=10&private_token=e96fff37c2238fdab074d1dcea8e6317
  21 +
  22 + desc 'Return all articles of all kinds' do
  23 + detail 'Get all articles filtered by fields in query params'
  24 + params Entities::Article.documentation
  25 + success Entities::Article
  26 + failure [[403, 'Forbidden']]
  27 + named 'ArticlesList'
  28 + headers [
  29 + 'Per-Page' => {
  30 + description: 'Total number of records',
  31 + required: false
  32 + }
  33 + ]
  34 + end
  35 + get do
  36 + present_articles_for_asset(environment)
  37 + end
  38 +
  39 + desc "Return one article by id" do
  40 + detail 'Get only one article by id. If not found the "forbidden" http error is showed'
  41 + params Entities::Article.documentation
  42 + success Entities::Article
  43 + failure [[403, 'Forbidden']]
  44 + named 'ArticleById'
  45 + end
  46 + get ':id', requirements: {id: /[0-9]+/} do
  47 + present_article(environment)
  48 + end
  49 +
  50 + post ':id' do
  51 + article = environment.articles.find(params[:id])
  52 + return forbidden! unless article.allow_edit?(current_person)
  53 + article.update_attributes!(asset_with_image(params[:article]))
  54 + present_partial article, :with => Entities::Article
  55 + end
  56 +
  57 + delete ':id' do
  58 + article = environment.articles.find(params[:id])
  59 + return forbidden! unless article.allow_delete?(current_person)
  60 + begin
  61 + article.destroy
  62 + { :success => true }
  63 + rescue Exception => exception
  64 + render_api_error!(_('The article couldn\'t be removed due to some problem. Please contact the administrator.'), 400)
  65 + end
  66 + end
  67 +
  68 + desc 'Report a abuse and/or violent content in a article by id' do
  69 + detail 'Submit a abuse (in general, a content violation) report about a specific article'
  70 + params Entities::Article.documentation
  71 + failure [[400, 'Bad Request']]
  72 + named 'ArticleReportAbuse'
  73 + end
  74 + post ':id/report_abuse' do
  75 + article = find_article(environment.articles, params[:id])
  76 + profile = article.profile
  77 + begin
  78 + abuse_report = AbuseReport.new(:reason => params[:report_abuse])
  79 + if !params[:content_type].blank?
  80 + article = params[:content_type].constantize.find(params[:content_id])
  81 + abuse_report.content = article_reported_version(article)
  82 + end
  83 +
  84 + current_person.register_report(abuse_report, profile)
  85 +
  86 + if !params[:content_type].blank?
  87 + abuse_report = AbuseReport.find_by reporter_id: current_person.id, abuse_complaint_id: profile.opened_abuse_complaint.id
  88 + Delayed::Job.enqueue DownloadReportedImagesJob.new(abuse_report, article)
  89 + end
  90 +
  91 + {
  92 + :success => true,
  93 + :message => _('Your abuse report was registered. The administrators are reviewing your report.'),
  94 + }
  95 + rescue Exception => exception
  96 + #logger.error(exception.to_s)
  97 + render_api_error!(_('Your report couldn\'t be saved due to some problem. Please contact the administrator.'), 400)
  98 + end
  99 +
  100 + end
  101 +
  102 + desc "Returns the articles I voted" do
  103 + detail 'Get the Articles I make a vote'
  104 + failure [[403, 'Forbidden']]
  105 + named 'ArticleFollowers'
  106 + end
  107 + #FIXME refactor this method
  108 + get 'voted_by_me' do
  109 + present_articles(current_person.votes.where(:voteable_type => 'Article').collect(&:voteable))
  110 + end
  111 +
  112 + desc 'Perform a vote on a article by id' do
  113 + detail 'Vote on a specific article with values: 1 (if you like) or -1 (if not)'
  114 + params Entities::UserLogin.documentation
  115 + failure [[401,'Unauthorized']]
  116 + named 'ArticleVote'
  117 + end
  118 + post ':id/vote' do
  119 + authenticate!
  120 + value = (params[:value] || 1).to_i
  121 + # FIXME verify allowed values
  122 + render_api_error!('Vote value not allowed', 400) unless [-1, 1].include?(value)
  123 + article = find_article(environment.articles, params[:id])
  124 + begin
  125 + vote = Vote.new(:voteable => article, :voter => current_person, :vote => value)
  126 + {:vote => vote.save!}
  127 + rescue ActiveRecord::RecordInvalid => e
  128 + render_api_error!(e.message, 400)
  129 + end
  130 + end
  131 +
  132 + desc "Returns the total followers for the article" do
  133 + detail 'Get the followers of a specific article by id'
  134 + failure [[403, 'Forbidden']]
  135 + named 'ArticleFollowers'
  136 + end
  137 + get ':id/followers' do
  138 + article = find_article(environment.articles, params[:id])
  139 + total = article.person_followers.count
  140 + {:total_followers => total}
  141 + end
  142 +
  143 + desc "Return the articles followed by me"
  144 + get 'followed_by_me' do
  145 + present_articles_for_asset(current_person, 'following_articles')
  146 + end
  147 +
  148 + desc "Add a follower for the article" do
  149 + detail 'Add the current user identified by private token, like a follower of a article'
  150 + params Entities::UserLogin.documentation
  151 + failure [[401, 'Unauthorized']]
  152 + named 'ArticleFollow'
  153 + end
  154 + post ':id/follow' do
  155 + authenticate!
  156 + article = find_article(environment.articles, params[:id])
  157 + if article.article_followers.exists?(:person_id => current_person.id)
  158 + {:success => false, :already_follow => true}
  159 + else
  160 + article_follower = ArticleFollower.new
  161 + article_follower.article = article
  162 + article_follower.person = current_person
  163 + article_follower.save!
  164 + {:success => true}
  165 + end
  166 + end
  167 +
  168 + desc 'Return the children of a article identified by id' do
  169 + detail 'Get all children articles of a specific article'
  170 + params Entities::Article.documentation
  171 + failure [[403, 'Forbidden']]
  172 + named 'ArticleChildren'
  173 + end
  174 +
  175 + paginate per_page: MAX_PER_PAGE, max_per_page: MAX_PER_PAGE
  176 + get ':id/children' do
  177 + article = find_article(environment.articles, params[:id])
  178 +
  179 + #TODO make tests for this situation
  180 + votes_order = params.delete(:order) if params[:order]=='votes_score'
  181 + articles = select_filtered_collection_of(article, 'children', params)
  182 + articles = articles.display_filter(current_person, article.profile)
  183 +
  184 + #TODO make tests for this situation
  185 + if votes_order
  186 + articles = articles.joins('left join votes on articles.id=votes.voteable_id').group('articles.id').reorder('sum(coalesce(votes.vote, 0)) DESC')
  187 + end
  188 + Article.hit(articles)
  189 + present_articles(articles)
  190 + end
  191 +
  192 + desc 'Return one child of a article identified by id' do
  193 + detail 'Get a child of a specific article'
  194 + params Entities::Article.documentation
  195 + success Entities::Article
  196 + failure [[403, 'Forbidden']]
  197 + named 'ArticleChild'
  198 + end
  199 + get ':id/children/:child_id' do
  200 + article = find_article(environment.articles, params[:id])
  201 + child = find_article(article.children, params[:child_id])
  202 + child.hit
  203 + present_partial child, :with => Entities::Article
  204 + end
  205 +
  206 + desc 'Suggest a article to another profile' do
  207 + detail 'Suggest a article to another profile (person, community...)'
  208 + params Entities::Article.documentation
  209 + success Entities::Task
  210 + failure [[401,'Unauthorized']]
  211 + named 'ArticleSuggest'
  212 + end
  213 + post ':id/children/suggest' do
  214 + authenticate!
  215 + parent_article = environment.articles.find(params[:id])
  216 +
  217 + suggest_article = SuggestArticle.new
  218 + suggest_article.article = params[:article]
  219 + suggest_article.article[:parent_id] = parent_article.id
  220 + suggest_article.target = parent_article.profile
  221 + suggest_article.requestor = current_person
  222 +
  223 + unless suggest_article.save
  224 + render_api_errors!(suggest_article.article_object.errors.full_messages)
  225 + end
  226 + present_partial suggest_article, :with => Entities::Task
  227 + end
  228 +
  229 + # Example Request:
  230 + # POST api/v1/articles/:id/children?private_token=234298743290432&article[name]=title&article[body]=body
  231 + desc 'Add a child article to a parent identified by id' do
  232 + detail 'Create a new article and associate to a parent'
  233 + params Entities::Article.documentation
  234 + success Entities::Article
  235 + failure [[401,'Unauthorized']]
  236 + named 'ArticleAddChild'
  237 + end
  238 + post ':id/children' do
  239 + parent_article = environment.articles.find(params[:id])
  240 + params[:article][:parent_id] = parent_article.id
  241 + post_article(parent_article.profile, params)
  242 + end
  243 + end
  244 +
  245 + resource :profiles do
  246 + get ':id/home_page' do
  247 + profiles = environment.profiles
  248 + profiles = profiles.visible_for_person(current_person)
  249 + profile = profiles.find_by id: params[:id]
  250 + present_partial profile.home_page, :with => Entities::Article
  251 + end
  252 + end
  253 +
  254 + kinds = %w[profile community person enterprise]
  255 + kinds.each do |kind|
  256 + resource kind.pluralize.to_sym do
  257 + segment "/:#{kind}_id" do
  258 + resource :articles do
  259 +
  260 + desc "Return all articles associate with a profile of type #{kind}" do
  261 + detail 'Get a list of articles of a profile'
  262 + params Entities::Article.documentation
  263 + success Entities::Article
  264 + failure [[403, 'Forbidden']]
  265 + named 'ArticlesOfProfile'
  266 + end
  267 + get do
  268 + profile = environment.send(kind.pluralize).find(params["#{kind}_id"])
  269 +
  270 + if params[:path].present?
  271 + article = profile.articles.find_by path: params[:path]
  272 + if !article || !article.display_to?(current_person)
  273 + article = forbidden!
  274 + end
  275 +
  276 + present_partial article, :with => Entities::Article
  277 + else
  278 +
  279 + present_articles_for_asset(profile)
  280 + end
  281 + end
  282 +
  283 + desc "Return a article associate with a profile of type #{kind}" do
  284 + detail 'Get only one article of a profile'
  285 + params Entities::Article.documentation
  286 + success Entities::Article
  287 + failure [[403, 'Forbidden']]
  288 + named 'ArticleOfProfile'
  289 + end
  290 + get ':id' do
  291 + profile = environment.send(kind.pluralize).find(params["#{kind}_id"])
  292 + present_article(profile)
  293 + end
  294 +
  295 + # Example Request:
  296 + # POST api/v1/{people,communities,enterprises}/:asset_id/articles?private_token=234298743290432&article[name]=title&article[body]=body
  297 + desc "Add a new article associated with a profile of type #{kind}" do
  298 + detail 'Create a new article and associate with a profile'
  299 + params Entities::Article.documentation
  300 + success Entities::Article
  301 + failure [[403, 'Forbidden']]
  302 + named 'ArticleCreateToProfile'
  303 + end
  304 + post do
  305 + profile = environment.send(kind.pluralize).find(params["#{kind}_id"])
  306 + post_article(profile, params)
  307 + end
  308 + end
  309 + end
  310 + end
  311 + end
  312 + end
  313 + end
  314 +end
... ...
app/api/v1/blocks.rb 0 → 100644
... ... @@ -0,0 +1,15 @@
  1 +module Api
  2 + module V1
  3 +
  4 + class Blocks < Grape::API
  5 + resource :blocks do
  6 + get ':id' do
  7 + block = Block.find(params["id"])
  8 + return forbidden! unless block.visible_to_user?(current_person)
  9 + present block, :with => Entities::Block, display_api_content: true
  10 + end
  11 + end
  12 + end
  13 +
  14 + end
  15 +end
... ...
app/api/v1/boxes.rb 0 → 100644
... ... @@ -0,0 +1,45 @@
  1 +module Api
  2 + module V1
  3 +
  4 + class Boxes < Grape::API
  5 +
  6 + kinds = %w[profile community person enterprise]
  7 + kinds.each do |kind|
  8 +
  9 + resource kind.pluralize.to_sym do
  10 +
  11 + segment "/:#{kind}_id" do
  12 + resource :boxes do
  13 + get do
  14 + profile = environment.send(kind.pluralize).find(params["#{kind}_id"])
  15 + return forbidden! unless profile.display_info_to?(current_person)
  16 + present profile.boxes, with: Entities::Box, current_person: current_person
  17 + end
  18 + end
  19 + end
  20 + end
  21 +
  22 + end
  23 +
  24 + resource :environments do
  25 + [ '/default', '/context', ':environment_id' ].each do |route|
  26 + segment route do
  27 + resource :boxes do
  28 + get do
  29 + if (route.match(/default/))
  30 + env = Environment.default
  31 + elsif (route.match(/context/))
  32 + env = environment
  33 + else
  34 + env = Environment.find(params[:environment_id])
  35 + end
  36 + present env.boxes, with: Entities::Box, current_person: current_person
  37 + end
  38 + end
  39 + end
  40 + end
  41 + end
  42 + end
  43 +
  44 + end
  45 +end
... ...
app/api/v1/categories.rb 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +module Api
  2 + module V1
  3 + class Categories < Grape::API
  4 +
  5 + resource :categories do
  6 +
  7 + get do
  8 + type = params[:category_type]
  9 + include_parent = params[:include_parent] == 'true'
  10 + include_children = params[:include_children] == 'true'
  11 +
  12 + categories = type.nil? ? environment.categories : environment.categories.where(:type => type)
  13 + present categories, :with => Entities::Category, parent: include_parent, children: include_children
  14 + end
  15 +
  16 + desc "Return the category by id"
  17 + get ':id' do
  18 + present environment.categories.find(params[:id]), :with => Entities::Category, parent: true, children: true
  19 + end
  20 +
  21 + end
  22 +
  23 + end
  24 + end
  25 +end
... ...
app/api/v1/comments.rb 0 → 100644
... ... @@ -0,0 +1,63 @@
  1 +module Api
  2 + module V1
  3 + class Comments < Grape::API
  4 + MAX_PER_PAGE = 20
  5 +
  6 +
  7 + resource :articles do
  8 + paginate max_per_page: MAX_PER_PAGE
  9 + # Collect comments from articles
  10 + #
  11 + # Parameters:
  12 + # reference_id - comment id used as reference to collect comment
  13 + # oldest - Collect the oldest comments from reference_id comment. If nothing is passed the newest comments are collected
  14 + # limit - amount of comments returned. The default value is 20
  15 + #
  16 + # Example Request:
  17 + # GET /articles/12/comments?oldest&limit=10&reference_id=23
  18 + get ":id/comments" do
  19 + article = find_article(environment.articles, params[:id])
  20 + comments = select_filtered_collection_of(article, :comments, params)
  21 + comments = comments.without_spam
  22 + comments = comments.without_reply if(params[:without_reply].present?)
  23 + comments = plugins.filter(:unavailable_comments, comments)
  24 + present paginate(comments), :with => Entities::Comment, :current_person => current_person
  25 + end
  26 +
  27 + get ":id/comments/:comment_id" do
  28 + article = find_article(environment.articles, params[:id])
  29 + present article.comments.find(params[:comment_id]), :with => Entities::Comment, :current_person => current_person
  30 + end
  31 +
  32 + # Example Request:
  33 + # POST api/v1/articles/12/comments?private_token=2298743290432&body=new comment&title=New
  34 + post ":id/comments" do
  35 + authenticate!
  36 + article = find_article(environment.articles, params[:id])
  37 + return forbidden! unless article.accept_comments?
  38 + options = params.select { |key,v| !['id','private_token'].include?(key) }.merge(:author => current_person, :source => article)
  39 + begin
  40 + comment = Comment.create!(options)
  41 + rescue ActiveRecord::RecordInvalid => e
  42 + render_api_error!(e.message, 400)
  43 + end
  44 + present comment, :with => Entities::Comment, :current_person => current_person
  45 + end
  46 +
  47 + delete ":id/comments/:comment_id" do
  48 + article = find_article(environment.articles, params[:id])
  49 + comment = article.comments.find_by_id(params[:comment_id])
  50 + return not_found! if comment.nil?
  51 + return forbidden! unless comment.can_be_destroyed_by?(current_person)
  52 + begin
  53 + comment.destroy
  54 + present comment, with: Entities::Comment, :current_person => current_person
  55 + rescue => e
  56 + render_api_error!(e.message, 500)
  57 + end
  58 + end
  59 + end
  60 +
  61 + end
  62 + end
  63 +end
... ...
app/api/v1/communities.rb 0 → 100644
... ... @@ -0,0 +1,82 @@
  1 +module Api
  2 + module V1
  3 + class Communities < Grape::API
  4 +
  5 + resource :communities do
  6 +
  7 + # Collect comments from articles
  8 + #
  9 + # Parameters:
  10 + # from - date where the search will begin. If nothing is passed the default date will be the date of the first article created
  11 + # oldest - Collect the oldest comments from reference_id comment. If nothing is passed the newest comments are collected
  12 + # limit - amount of comments returned. The default value is 20
  13 + #
  14 + # Example Request:
  15 + # GET /communities?from=2013-04-04-14:41:43&until=2014-04-04-14:41:43&limit=10
  16 + # GET /communities?reference_id=10&limit=10&oldest
  17 + get do
  18 + communities = select_filtered_collection_of(environment, 'communities', params)
  19 + communities = profiles_for_person(communities, current_person)
  20 + communities = communities.by_location(params) # Must be the last. May return Exception obj
  21 + present communities, :with => Entities::Community, :current_person => current_person
  22 + end
  23 +
  24 +
  25 + # Example Request:
  26 + # POST api/v1/communties?private_token=234298743290432&community[name]=some_name
  27 + # for each custom field for community, add &community[field_name]=field_value to the request
  28 + post do
  29 + authenticate!
  30 + params[:community] ||= {}
  31 +
  32 + params[:community][:custom_values]={}
  33 + params[:community].keys.each do |key|
  34 + params[:community][:custom_values][key]=params[:community].delete(key) if Community.custom_fields(environment).any?{|cf| cf.name==key}
  35 + end
  36 +
  37 + begin
  38 + community = Community.create_after_moderation(current_person, params[:community].merge({:environment => environment}))
  39 + rescue
  40 + community = Community.new(params[:community])
  41 + end
  42 +
  43 + if !community.save
  44 + render_api_errors!(community.errors.full_messages)
  45 + end
  46 +
  47 + present community, :with => Entities::Community, :current_person => current_person
  48 + end
  49 +
  50 + get ':id' do
  51 + community = profiles_for_person(environment.communities, current_person).find_by_id(params[:id])
  52 + present community, :with => Entities::Community, :current_person => current_person
  53 + end
  54 +
  55 + end
  56 +
  57 + resource :people do
  58 +
  59 + segment '/:person_id' do
  60 +
  61 + resource :communities do
  62 +
  63 + get do
  64 + person = environment.people.find(params[:person_id])
  65 +
  66 + not_found! if person.blank?
  67 + forbidden! if !person.display_info_to?(current_person)
  68 +
  69 + communities = select_filtered_collection_of(person, 'communities', params)
  70 + communities = communities.visible
  71 + present communities, :with => Entities::Community, :current_person => current_person
  72 + end
  73 +
  74 + end
  75 +
  76 + end
  77 +
  78 + end
  79 +
  80 + end
  81 + end
  82 +end
... ...
app/api/v1/contacts.rb 0 → 100644
... ... @@ -0,0 +1,26 @@
  1 +module Api
  2 + module V1
  3 + class Contacts < Grape::API
  4 +
  5 + resource :communities do
  6 +
  7 + resource ':id/contact' do
  8 + #contact => {:name => 'some name', :email => 'test@mail.com', :subject => 'some title', :message => 'some message'}
  9 + desc "Send a contact message"
  10 + post do
  11 + profile = environment.communities.find(params[:id])
  12 + forbidden! unless profile.present?
  13 + contact = Contact.new params[:contact].merge(dest: profile)
  14 + if contact.deliver
  15 + {:success => true}
  16 + else
  17 + {:success => false}
  18 + end
  19 + end
  20 +
  21 + end
  22 + end
  23 +
  24 + end
  25 + end
  26 +end
... ...
app/api/v1/enterprises.rb 0 → 100644
... ... @@ -0,0 +1,55 @@
  1 +module Api
  2 + module V1
  3 + class Enterprises < Grape::API
  4 +
  5 + resource :enterprises do
  6 +
  7 + # Collect enterprises from environment
  8 + #
  9 + # Parameters:
  10 + # from - date where the search will begin. If nothing is passed the default date will be the date of the first article created
  11 + # oldest - Collect the oldest comments from reference_id comment. If nothing is passed the newest comments are collected
  12 + # limit - amount of comments returned. The default value is 20
  13 + # georef params - read `Profile.by_location` for more information.
  14 + #
  15 + # Example Request:
  16 + # GET /enterprises?from=2013-04-04-14:41:43&until=2014-04-04-14:41:43&limit=10
  17 + # GET /enterprises?reference_id=10&limit=10&oldest
  18 + get do
  19 + enterprises = select_filtered_collection_of(environment, 'enterprises', params)
  20 + enterprises = enterprises.visible
  21 + enterprises = enterprises.by_location(params) # Must be the last. May return Exception obj.
  22 + present enterprises, :with => Entities::Enterprise, :current_person => current_person
  23 + end
  24 +
  25 + desc "Return one enterprise by id"
  26 + get ':id' do
  27 + enterprise = environment.enterprises.visible.find_by(id: params[:id])
  28 + present enterprise, :with => Entities::Enterprise, :current_person => current_person
  29 + end
  30 +
  31 + end
  32 +
  33 + resource :people do
  34 +
  35 + segment '/:person_id' do
  36 +
  37 + resource :enterprises do
  38 +
  39 + get do
  40 + person = environment.people.find(params[:person_id])
  41 + enterprises = select_filtered_collection_of(person, 'enterprises', params)
  42 + enterprises = enterprises.visible.by_location(params)
  43 + present enterprises, :with => Entities::Enterprise, :current_person => current_person
  44 + end
  45 +
  46 + end
  47 +
  48 + end
  49 +
  50 + end
  51 +
  52 +
  53 + end
  54 + end
  55 +end
... ...
app/api/v1/environments.rb 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +module Api
  2 + module V1
  3 + class Environments < Grape::API
  4 +
  5 + resource :environment do
  6 +
  7 + desc "Return the person information"
  8 + get '/signup_person_fields' do
  9 + present environment.signup_person_fields
  10 + end
  11 +
  12 + get ':id' do
  13 + local_environment = nil
  14 + if (params[:id] == "default")
  15 + local_environment = Environment.default
  16 + elsif (params[:id] == "context")
  17 + local_environment = environment
  18 + else
  19 + local_environment = Environment.find(params[:id])
  20 + end
  21 + present_partial local_environment, :with => Entities::Environment, :is_admin => is_admin?(local_environment)
  22 + end
  23 +
  24 + end
  25 +
  26 + end
  27 + end
  28 +end
... ...
app/api/v1/people.rb 0 → 100644
... ... @@ -0,0 +1,127 @@
  1 +module Api
  2 + module V1
  3 + class People < Grape::API
  4 +
  5 + MAX_PER_PAGE = 50
  6 +
  7 + desc 'API Root'
  8 +
  9 + resource :people do
  10 + paginate max_per_page: MAX_PER_PAGE
  11 +
  12 + # -- A note about privacy --
  13 + # We wold find people by location, but we must test if the related
  14 + # fields are public. We can't do it now, with SQL, while the location
  15 + # data and the fields_privacy are a serialized settings.
  16 + # We must build a new table for profile data, where we can set meta-data
  17 + # like:
  18 + # | id | profile_id | key | value | privacy_level | source |
  19 + # | 1 | 99 | city | Salvador | friends | user |
  20 + # | 2 | 99 | lng | -38.521 | me only | automatic |
  21 +
  22 + # Collect people from environment
  23 + #
  24 + # Parameters:
  25 + # from - date where the search will begin. If nothing is passed the default date will be the date of the first article created
  26 + # oldest - Collect the oldest comments from reference_id comment. If nothing is passed the newest comments are collected
  27 + # limit - amount of comments returned. The default value is 20
  28 + #
  29 + # Example Request:
  30 + # GET /people?from=2013-04-04-14:41:43&until=2014-04-04-14:41:43&limit=10
  31 + # GET /people?reference_id=10&limit=10&oldest
  32 +
  33 + desc "Find environment's people"
  34 + get do
  35 + people = select_filtered_collection_of(environment, 'people', params)
  36 + people = people.visible
  37 + present_partial people, :with => Entities::Person, :current_person => current_person
  38 + end
  39 +
  40 + desc "Return the logged user information"
  41 + get "/me" do
  42 + authenticate!
  43 + present_partial current_person, :with => Entities::Person, :current_person => current_person
  44 + end
  45 +
  46 + desc "Return the person information"
  47 + get ':id' do
  48 + person = environment.people.visible.find_by(id: params[:id])
  49 + return not_found! if person.blank?
  50 + present person, :with => Entities::Person, :current_person => current_person
  51 + end
  52 +
  53 + desc "Update person information"
  54 + post ':id' do
  55 + authenticate!
  56 + return forbidden! if current_person.id.to_s != params[:id]
  57 + current_person.update_attributes!(asset_with_image(params[:person]))
  58 + present current_person, :with => Entities::Person, :current_person => current_person
  59 + end
  60 +
  61 + # POST api/v1/people?person[login]=some_login&person[password]=some_password&person[name]=Jack
  62 + # for each custom field for person, add &person[field_name]=field_value to the request
  63 + desc "Create person"
  64 + post do
  65 + authenticate!
  66 + user_data = {}
  67 + user_data[:login] = params[:person].delete(:login) || params[:person][:identifier]
  68 + user_data[:email] = params[:person].delete(:email)
  69 + user_data[:password] = params[:person].delete(:password)
  70 + user_data[:password_confirmation] = params[:person].delete(:password_confirmation)
  71 +
  72 + params[:person][:custom_values]={}
  73 + params[:person].keys.each do |key|
  74 + params[:person][:custom_values][key]=params[:person].delete(key) if Person.custom_fields(environment).any?{|cf| cf.name==key}
  75 + end
  76 +
  77 + user = User.build(user_data, asset_with_image(params[:person]), environment)
  78 +
  79 + begin
  80 + user.signup!
  81 + rescue ActiveRecord::RecordInvalid
  82 + render_api_errors!(user.errors.full_messages)
  83 + end
  84 +
  85 + present user.person, :with => Entities::Person, :current_person => user.person
  86 + end
  87 +
  88 + desc "Return the person friends"
  89 + get ':id/friends' do
  90 + person = environment.people.visible.find_by(id: params[:id])
  91 + return not_found! if person.blank?
  92 + friends = person.friends.visible
  93 + present friends, :with => Entities::Person, :current_person => current_person
  94 + end
  95 +
  96 + desc "Return the person permissions on other profiles"
  97 + get ":id/permissions" do
  98 + authenticate!
  99 + person = environment.people.find(params[:id])
  100 + return not_found! if person.blank?
  101 + return forbidden! unless current_person == person || environment.admins.include?(current_person)
  102 +
  103 + output = {}
  104 + person.role_assignments.map do |role_assigment|
  105 + if role_assigment.resource.respond_to?(:identifier)
  106 + output[role_assigment.resource.identifier] = role_assigment.role.permissions
  107 + end
  108 + end
  109 + present output
  110 + end
  111 + end
  112 +
  113 + resource :profiles do
  114 + segment '/:profile_id' do
  115 + resource :members do
  116 + paginate max_per_page: MAX_PER_PAGE
  117 + get do
  118 + profile = environment.profiles.find_by id: params[:profile_id]
  119 + members = select_filtered_collection_of(profile, 'members', params)
  120 + present members, :with => Entities::Person, :current_person => current_person
  121 + end
  122 + end
  123 + end
  124 + end
  125 + end
  126 + end
  127 +end
... ...
app/api/v1/profiles.rb 0 → 100644
... ... @@ -0,0 +1,51 @@
  1 +module Api
  2 + module V1
  3 + class Profiles < Grape::API
  4 +
  5 + resource :profiles do
  6 +
  7 + get do
  8 + profiles = select_filtered_collection_of(environment, 'profiles', params)
  9 + profiles = profiles.visible
  10 + profiles = profiles.by_location(params) # Must be the last. May return Exception obj.
  11 + present profiles, :with => Entities::Profile, :current_person => current_person
  12 + end
  13 +
  14 + get ':id' do
  15 + profiles = environment.profiles
  16 + profiles = profiles.visible
  17 + profile = profiles.find_by id: params[:id]
  18 +
  19 + if profile
  20 + present profile, :with => Entities::Profile, :current_person => current_person
  21 + else
  22 + not_found!
  23 + end
  24 + end
  25 +
  26 + desc "Update profile information"
  27 + post ':id' do
  28 + authenticate!
  29 + profile = environment.profiles.find_by(id: params[:id])
  30 + return forbidden! unless current_person.has_permission?(:edit_profile, profile)
  31 + profile.update_attributes!(params[:profile])
  32 + present profile, :with => Entities::Profile, :current_person => current_person
  33 + end
  34 +
  35 + delete ':id' do
  36 + authenticate!
  37 + profiles = environment.profiles
  38 + profile = profiles.find_by id: params[:id]
  39 +
  40 + not_found! if profile.blank?
  41 +
  42 + if current_person.has_permission?(:destroy_profile, profile)
  43 + profile.destroy
  44 + else
  45 + forbidden!
  46 + end
  47 + end
  48 + end
  49 + end
  50 + end
  51 +end
... ...
app/api/v1/search.rb 0 → 100644
... ... @@ -0,0 +1,37 @@
  1 +module Api
  2 + module V1
  3 + class Search < Grape::API
  4 +
  5 + resource :search do
  6 + resource :article do
  7 + paginate max_per_page: 200
  8 + get do
  9 + # Security checks
  10 + sanitize_params_hash(params)
  11 + # Api::Helpers
  12 + asset = :articles
  13 + context = environment
  14 +
  15 + profile = environment.profiles.find(params[:profile_id]) if params[:profile_id]
  16 + scope = profile.nil? ? environment.articles.is_public : profile.articles.is_public
  17 + scope = scope.where(:type => params[:type]) if params[:type] && !(params[:type] == 'Article')
  18 + scope = scope.where(make_conditions_with_parameter(params))
  19 + scope = scope.joins(:categories).where(:categories => {:id => params[:category_ids]}) if params[:category_ids].present?
  20 + scope = scope.where('articles.children_count > 0') if params[:has_children].present?
  21 + query = params[:query] || ""
  22 + order = "more_recent"
  23 +
  24 + options = {:filter => order, :template_id => params[:template_id]}
  25 +
  26 + search_result = find_by_contents(asset, context, scope, query, {:page => 1}, options)
  27 +
  28 + articles = search_result[:results]
  29 +
  30 + present_articles(articles)
  31 + end
  32 + end
  33 + end
  34 +
  35 + end
  36 + end
  37 +end
... ...
app/api/v1/session.rb 0 → 100644
... ... @@ -0,0 +1,157 @@
  1 +require "uri"
  2 +
  3 +module Api
  4 + module V1
  5 + class Session < Grape::API
  6 +
  7 + # Login to get token
  8 + #
  9 + # Parameters:
  10 + # login (*required) - user login or email
  11 + # password (required) - user password
  12 + #
  13 + # Example Request:
  14 + # POST http://localhost:3000/api/v1/login?login=adminuser&password=admin
  15 + post "/login" do
  16 + begin
  17 + user ||= User.authenticate(params[:login], params[:password], environment)
  18 + rescue User::UserNotActivated => e
  19 + render_api_error!(e.message, 401)
  20 + end
  21 +
  22 + return unauthorized! unless user
  23 + @current_user = user
  24 + present user, :with => Entities::UserLogin, :current_person => current_person
  25 + end
  26 +
  27 + post "/login_from_cookie" do
  28 + return unauthorized! if cookies[:auth_token].blank?
  29 + user = User.where(remember_token: cookies[:auth_token]).first
  30 + return unauthorized! unless user && user.activated?
  31 + @current_user = user
  32 + present user, :with => Entities::UserLogin, :current_person => current_person
  33 + end
  34 +
  35 + # Create user.
  36 + #
  37 + # Parameters:
  38 + # email (required) - Email
  39 + # password (required) - Password
  40 + # login - login
  41 + # Example Request:
  42 + # POST /register?email=some@mail.com&password=pas&password_confirmation=pas&login=some
  43 + params do
  44 + requires :email, type: String, desc: _("Email")
  45 + requires :login, type: String, desc: _("Login")
  46 + requires :password, type: String, desc: _("Password")
  47 + end
  48 +
  49 + post "/register" do
  50 + attrs = attributes_for_keys [:email, :login, :password, :password_confirmation] + environment.signup_person_fields
  51 + name = params[:name].present? ? params[:name] : attrs[:email]
  52 + attrs[:password_confirmation] = attrs[:password] if !attrs.has_key?(:password_confirmation)
  53 + user = User.new(attrs.merge(:name => name))
  54 +
  55 + begin
  56 + user.signup!
  57 + user.generate_private_token! if user.activated?
  58 + present user, :with => Entities::UserLogin, :current_person => user.person
  59 + rescue ActiveRecord::RecordInvalid
  60 + message = user.errors.as_json.merge((user.person.present? ? user.person.errors : {}).as_json).to_json
  61 + render_api_error!(message, 400)
  62 + end
  63 + end
  64 +
  65 + params do
  66 + requires :activation_code, type: String, desc: _("Activation token")
  67 + end
  68 +
  69 + # Activate a user.
  70 + #
  71 + # Parameter:
  72 + # activation_code (required) - Activation token
  73 + # Example Request:
  74 + # PATCH /activate?activation_code=28259abd12cc6a64ef9399cf3286cb998b96aeaf
  75 + patch "/activate" do
  76 + user = User.find_by activation_code: params[:activation_code]
  77 + if user
  78 + unless user.environment.enabled?('admin_must_approve_new_users')
  79 + if user.activate
  80 + user.generate_private_token!
  81 + present user, :with => Entities::UserLogin, :current_person => current_person
  82 + end
  83 + else
  84 + if user.create_moderate_task
  85 + user.activation_code = nil
  86 + user.save!
  87 +
  88 + # Waiting for admin moderate user registration
  89 + status 202
  90 + body({
  91 + :message => 'Waiting for admin moderate user registration'
  92 + })
  93 + end
  94 + end
  95 + else
  96 + # Token not found in database
  97 + render_api_error!(_('Token is invalid'), 412)
  98 + end
  99 + end
  100 +
  101 + # Request a new password.
  102 + #
  103 + # Parameters:
  104 + # value (required) - Email or login
  105 + # Example Request:
  106 + # POST /forgot_password?value=some@mail.com
  107 + post "/forgot_password" do
  108 + requestors = fetch_requestors(params[:value])
  109 + not_found! if requestors.blank?
  110 + remote_ip = (request.respond_to?(:remote_ip) && request.remote_ip) || (env && env['REMOTE_ADDR'])
  111 + requestors.each do |requestor|
  112 + ChangePassword.create!(:requestor => requestor)
  113 + end
  114 + end
  115 +
  116 + # Resend activation code.
  117 + #
  118 + # Parameters:
  119 + # value (required) - Email or login
  120 + # Example Request:
  121 + # POST /resend_activation_code?value=some@mail.com
  122 + post "/resend_activation_code" do
  123 + requestors = fetch_requestors(params[:value])
  124 + not_found! if requestors.blank?
  125 + remote_ip = (request.respond_to?(:remote_ip) && request.remote_ip) || (env && env['REMOTE_ADDR'])
  126 + requestors.each do |requestor|
  127 + requestor.user.resend_activation_code
  128 + end
  129 + present requestors.map(&:user), :with => Entities::UserLogin
  130 + end
  131 +
  132 + params do
  133 + requires :code, type: String, desc: _("Forgot password code")
  134 + end
  135 + # Change password
  136 + #
  137 + # Parameters:
  138 + # code (required) - Change password code
  139 + # password (required)
  140 + # password_confirmation (required)
  141 + # Example Request:
  142 + # PATCH /new_password?code=xxxx&password=secret&password_confirmation=secret
  143 + patch "/new_password" do
  144 + change_password = ChangePassword.find_by code: params[:code]
  145 + not_found! if change_password.nil?
  146 +
  147 + if change_password.update_attributes(:password => params[:password], :password_confirmation => params[:password_confirmation])
  148 + change_password.finish
  149 + present change_password.requestor.user, :with => Entities::UserLogin, :current_person => current_person
  150 + else
  151 + something_wrong!
  152 + end
  153 + end
  154 +
  155 + end
  156 + end
  157 +end
... ...
app/api/v1/tags.rb 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +module Api
  2 + module V1
  3 + class Tags < Grape::API
  4 + resource :articles do
  5 + resource ':id/tags' do
  6 + get do
  7 + article = find_article(environment.articles, params[:id])
  8 + present article.tag_list
  9 + end
  10 +
  11 + desc "Add a tag to an article"
  12 + post do
  13 + authenticate!
  14 + article = find_article(environment.articles, params[:id])
  15 + article.tag_list=params[:tags]
  16 + article.save
  17 + present article.tag_list
  18 + end
  19 + end
  20 + end
  21 +
  22 + resource :environment do
  23 + desc 'Return the tag counts for this environment'
  24 + get '/tags' do
  25 + present environment.tag_counts
  26 + end
  27 + end
  28 + end
  29 + end
  30 +end
... ...
app/api/v1/tasks.rb 0 → 100644
... ... @@ -0,0 +1,57 @@
  1 +module Api
  2 + module V1
  3 + class Tasks < Grape::API
  4 +# before { authenticate! }
  5 +
  6 +# ARTICLE_TYPES = Article.descendants.map{|a| a.to_s}
  7 +
  8 + resource :tasks do
  9 +
  10 + # Collect tasks
  11 + #
  12 + # Parameters:
  13 + # from - date where the search will begin. If nothing is passed the default date will be the date of the first article created
  14 + # oldest - Collect the oldest articles. If nothing is passed the newest articles are collected
  15 + # limit - amount of articles returned. The default value is 20
  16 + #
  17 + # Example Request:
  18 + # GET host/api/v1/tasks?from=2013-04-04-14:41:43&until=2015-04-04-14:41:43&limit=10&private_token=e96fff37c2238fdab074d1dcea8e6317
  19 + get do
  20 + tasks = select_filtered_collection_of(environment, 'tasks', params)
  21 + tasks = tasks.select {|t| current_person.has_permission?(t.permission, environment)}
  22 + present_partial tasks, :with => Entities::Task
  23 + end
  24 +
  25 + desc "Return the task id"
  26 + get ':id' do
  27 + task = find_task(environment, params[:id])
  28 + present_partial task, :with => Entities::Task
  29 + end
  30 + end
  31 +
  32 + kinds = %w[community person enterprise]
  33 + kinds.each do |kind|
  34 + resource kind.pluralize.to_sym do
  35 + segment "/:#{kind}_id" do
  36 + resource :tasks do
  37 + get do
  38 + profile = environment.send(kind.pluralize).find(params["#{kind}_id"])
  39 + present_tasks(profile)
  40 + end
  41 +
  42 + get ':id' do
  43 + profile = environment.send(kind.pluralize).find(params["#{kind}_id"])
  44 + present_task(profile)
  45 + end
  46 +
  47 + post do
  48 + profile = environment.send(kind.pluralize).find(params["#{kind}_id"])
  49 + post_task(profile, params)
  50 + end
  51 + end
  52 + end
  53 + end
  54 + end
  55 + end
  56 + end
  57 +end
... ...
app/api/v1/users.rb 0 → 100644
... ... @@ -0,0 +1,43 @@
  1 +module Api
  2 + module V1
  3 + class Users < Grape::API
  4 +
  5 + resource :users do
  6 +
  7 + get do
  8 + users = select_filtered_collection_of(environment, 'users', params)
  9 + users = users.select{|u| u.person.display_info_to? current_person}
  10 + present users, :with => Entities::User, :current_person => current_person
  11 + end
  12 +
  13 + get "/me" do
  14 + authenticate!
  15 + present current_user, :with => Entities::User, :current_person => current_person
  16 + end
  17 +
  18 + get ":id" do
  19 + user = environment.users.find_by id: params[:id]
  20 + if user
  21 + present user, :with => Entities::User, :current_person => current_person
  22 + else
  23 + not_found!
  24 + end
  25 + end
  26 +
  27 + get ":id/permissions" do
  28 + authenticate!
  29 + user = environment.users.find(params[:id])
  30 + output = {}
  31 + user.person.role_assignments.map do |role_assigment|
  32 + if role_assigment.resource.respond_to?(:identifier) && role_assigment.resource.identifier == params[:profile]
  33 + output[:permissions] = role_assigment.role.permissions
  34 + end
  35 + end
  36 + present output
  37 + end
  38 +
  39 + end
  40 +
  41 + end
  42 + end
  43 +end
... ...
app/concerns/authenticated_system.rb 0 → 100644
... ... @@ -0,0 +1,169 @@
  1 +module AuthenticatedSystem
  2 +
  3 + protected
  4 +
  5 + extend ActiveSupport::Concern
  6 +
  7 + included do
  8 + if self < ActionController::Base
  9 + around_filter :user_set_current
  10 + before_filter :override_user
  11 + before_filter :login_from_cookie
  12 + end
  13 +
  14 + # Inclusion hook to make #current_user and #logged_in?
  15 + # available as ActionView helper methods.
  16 + helper_method :current_user, :logged_in?
  17 + end
  18 +
  19 + # Returns true or false if the user is logged in.
  20 + # Preloads @current_user with the user model if they're logged in.
  21 + def logged_in?
  22 + current_user != nil
  23 + end
  24 +
  25 + # Accesses the current user from the session.
  26 + def current_user user_id = session[:user]
  27 + @current_user ||= begin
  28 + user = User.find_by id: user_id if user_id
  29 + user.session = session if user
  30 + User.current = user
  31 + user
  32 + end
  33 + end
  34 +
  35 + # Store the given user in the session.
  36 + def current_user=(new_user)
  37 + if new_user.nil?
  38 + session.delete(:user)
  39 + else
  40 + session[:user] = new_user.id
  41 + new_user.session = session
  42 + new_user.register_login
  43 + end
  44 + @current_user = User.current = new_user
  45 + end
  46 +
  47 + # See impl. from http://stackoverflow.com/a/2513456/670229
  48 + def user_set_current
  49 + User.current = current_user
  50 + yield
  51 + ensure
  52 + # to address the thread variable leak issues in Puma/Thin webserver
  53 + User.current = nil
  54 + end
  55 +
  56 + # Check if the user is authorized.
  57 + #
  58 + # Override this method in your controllers if you want to restrict access
  59 + # to only a few actions or if you want to check if the user
  60 + # has the correct rights.
  61 + #
  62 + # Example:
  63 + #
  64 + # # only allow nonbobs
  65 + # def authorize?
  66 + # current_user.login != "bob"
  67 + # end
  68 + def authorized?
  69 + true
  70 + end
  71 +
  72 + # Filter method to enforce a login requirement.
  73 + #
  74 + # To require logins for all actions, use this in your controllers:
  75 + #
  76 + # before_filter :login_required
  77 + #
  78 + # To require logins for specific actions, use this in your controllers:
  79 + #
  80 + # before_filter :login_required, :only => [ :edit, :update ]
  81 + #
  82 + # To skip this in a subclassed controller:
  83 + #
  84 + # skip_before_filter :login_required
  85 + #
  86 + def login_required
  87 + username, passwd = get_auth_data
  88 + if username && passwd
  89 + self.current_user ||= User.authenticate(username, passwd) || nil
  90 + end
  91 + if logged_in? && authorized?
  92 + true
  93 + else
  94 + if params[:require_login_popup]
  95 + render :json => { :require_login_popup => true }
  96 + else
  97 + access_denied
  98 + end
  99 + end
  100 + end
  101 +
  102 + # Redirect as appropriate when an access request fails.
  103 + #
  104 + # The default action is to redirect to the login screen.
  105 + #
  106 + # Override this method in your controllers if you want to have special
  107 + # behavior in case the user is not authorized
  108 + # to access the requested action. For example, a popup window might
  109 + # simply close itself.
  110 + def access_denied
  111 + respond_to do |accepts|
  112 + accepts.html do
  113 + if request.xhr?
  114 + render :text => _('Access denied'), :status => 401
  115 + else
  116 + store_location
  117 + redirect_to :controller => '/account', :action => 'login'
  118 + end
  119 + end
  120 + accepts.xml do
  121 + headers["Status"] = "Unauthorized"
  122 + headers["WWW-Authenticate"] = %(Basic realm="Web Password")
  123 + render :text => "Could't authenticate you", :status => '401 Unauthorized'
  124 + end
  125 + end
  126 + false
  127 + end
  128 +
  129 + # Store the URI of the current request in the session.
  130 + #
  131 + # We can return to this location by calling #redirect_back_or_default.
  132 + def store_location(location = request.url)
  133 + session[:return_to] = location
  134 + end
  135 +
  136 + # Redirect to the URI stored by the most recent store_location call or
  137 + # to the passed default.
  138 + def redirect_back_or_default(default)
  139 + if session[:return_to]
  140 + redirect_to(session.delete(:return_to))
  141 + else
  142 + redirect_to(default)
  143 + end
  144 + end
  145 +
  146 + def override_user
  147 + return if params[:override_user].blank?
  148 + return unless logged_in? and user.is_admin? environment
  149 + @current_user = nil
  150 + current_user params[:override_user]
  151 + end
  152 +
  153 + # When called with before_filter :login_from_cookie will check for an :auth_token
  154 + # cookie and log the user back in if apropriate
  155 + def login_from_cookie
  156 + return if cookies[:auth_token].blank? or logged_in?
  157 + user = User.where(remember_token: cookies[:auth_token]).first
  158 + self.current_user = user if user and user.remember_token?
  159 + end
  160 +
  161 + private
  162 + @@http_auth_headers = %w(X-HTTP_AUTHORIZATION HTTP_AUTHORIZATION Authorization)
  163 + # gets BASIC auth info
  164 + def get_auth_data
  165 + auth_key = @@http_auth_headers.detect { |h| request.env.has_key?(h) }
  166 + auth_data = request.env[auth_key].to_s.split unless auth_key.blank?
  167 + return auth_data && auth_data[0] == 'Basic' ? Base64.decode64(auth_data[1]).split(':')[0..1] : [nil, nil]
  168 + end
  169 +end
... ...
app/controllers/admin/categories_controller.rb
... ... @@ -7,7 +7,6 @@ class CategoriesController &lt; AdminController
7 7 def index
8 8 @categories = environment.categories.where("parent_id is null AND type is null")
9 9 @regions = environment.regions.where(:parent_id => nil)
10   - @product_categories = environment.product_categories.where(:parent_id => nil)
11 10 end
12 11  
13 12 def get_children
... ...
app/controllers/admin/edit_template_controller.rb
1 1 class EditTemplateController < AdminController
2   -
  2 +
3 3 protect 'edit_environment_design', :environment
4   -
  4 +
5 5 #FIXME
6 6 #design_editor :holder => 'environment', :autosave => true, :block_types => :block_types
7 7  
... ... @@ -9,7 +9,6 @@ class EditTemplateController &lt; AdminController
9 9 %w[
10 10 FavoriteLinks
11 11 ListBlock
12   - SellersSearchBlock
13 12 ]
14 13 end
15 14  
... ...
app/controllers/admin/environment_design_controller.rb
1 1 class EnvironmentDesignController < BoxOrganizerController
2   -
  2 +
3 3 protect 'edit_environment_design', :environment
4 4  
5 5 def available_blocks
6   - @available_blocks ||= [ ArticleBlock, LoginBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock, TagsBlock ]
  6 + @available_blocks ||= [ ArticleBlock, LoginBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, CategoriesBlock, RawHTMLBlock, TagsBlock ]
7 7 @available_blocks += plugins.dispatch(:extra_blocks, :type => Environment)
8 8 end
9 9  
... ...
app/controllers/admin/users_controller.rb
... ... @@ -45,7 +45,6 @@ class UsersController &lt; AdminController
45 45 redirect_to :action => :index, :q => params[:q], :filter => params[:filter]
46 46 end
47 47  
48   -
49 48 def destroy_user
50 49 if request.post?
51 50 person = environment.people.find_by id: params[:id]
... ... @@ -58,7 +57,6 @@ class UsersController &lt; AdminController
58 57 redirect_to :action => :index, :q => params[:q], :filter => params[:filter]
59 58 end
60 59  
61   -
62 60 def download
63 61 respond_to do |format|
64 62 format.html
... ... @@ -87,8 +85,11 @@ class UsersController &lt; AdminController
87 85 end
88 86  
89 87 def send_mail
90   - @mailing = environment.mailings.build(params[:mailing])
91 88 if request.post?
  89 + @mailing = environment.mailings.build(params[:mailing])
  90 + @mailing.recipients_roles = []
  91 + @mailing.recipients_roles << "profile_admin" if params[:recipients][:profile_admins].include?("true")
  92 + @mailing.recipients_roles << "environment_administrator" if params[:recipients][:env_admins].include?("true")
92 93 @mailing.locale = locale
93 94 @mailing.person = user
94 95 if @mailing.save
... ...
app/controllers/application_controller.rb
... ... @@ -13,6 +13,13 @@ class ApplicationController &lt; ActionController::Base
13 13 before_filter :verify_members_whitelist, :if => [:private_environment?, :user]
14 14 before_filter :redirect_to_current_user
15 15  
  16 + before_filter :set_session_theme
  17 + def set_session_theme
  18 + if params[:theme]
  19 + session[:theme] = environment.theme_ids.include?(params[:theme]) ? params[:theme] : nil
  20 + end
  21 + end
  22 +
16 23 def require_login_for_environment
17 24 login_required
18 25 end
... ...
app/controllers/my_profile/cms_controller.rb
... ... @@ -103,12 +103,10 @@ class CmsController &lt; MyProfileController
103 103 end
104 104 end
105 105 end
106   -
107   - escape_fields @article
108 106 end
109 107  
110 108 def new
111   - # FIXME this method should share some logic wirh edit !!!
  109 + # FIXME this method should share some logic with edit !!!
112 110  
113 111 @success_back_to = params[:success_back_to]
114 112 # user must choose an article type first
... ... @@ -174,9 +172,6 @@ class CmsController &lt; MyProfileController
174 172 return
175 173 end
176 174 end
177   -
178   - escape_fields @article
179   -
180 175 render :action => 'edit'
181 176 end
182 177  
... ... @@ -365,7 +360,7 @@ class CmsController &lt; MyProfileController
365 360 def search
366 361 query = params[:q]
367 362 results = find_by_contents(:uploaded_files, profile, profile.files.published, query)[:results]
368   - render :text => article_list_to_json(results), :content_type => 'application/json'
  363 + render :text => article_list_to_json(results).html_safe, :content_type => 'application/json'
369 364 end
370 365  
371 366 def search_article_privacy_exceptions
... ... @@ -409,9 +404,6 @@ class CmsController &lt; MyProfileController
409 404 ]
410 405 articles += special_article_types if params && params[:cms]
411 406 parent_id = params ? params[:parent_id] : nil
412   - if profile.enterprise?
413   - articles << EnterpriseHomepage
414   - end
415 407 if @parent && @parent.blog?
416 408 articles -= Article.folder_types.map(&:constantize)
417 409 end
... ... @@ -451,9 +443,7 @@ class CmsController &lt; MyProfileController
451 443 end
452 444  
453 445 def refuse_blocks
454   - if ['TinyMceArticle', 'TextileArticle', 'Event', 'EnterpriseHomepage'].include?(@type)
455   - @no_design_blocks = true
456   - end
  446 + @no_design_blocks = @type.present? && valid_article_type?(@type) ? !@type.constantize.can_display_blocks? : false
457 447 end
458 448  
459 449 def per_page
... ... @@ -521,10 +511,4 @@ class CmsController &lt; MyProfileController
521 511 end
522 512 end
523 513  
524   - def escape_fields article
525   - unless article.kind_of?(RssFeed)
526   - @escaped_body = CGI::escapeHTML(article.body || '')
527   - @escaped_abstract = CGI::escapeHTML(article.abstract || '')
528   - end
529   - end
530 514 end
... ...
app/controllers/my_profile/manage_products_controller.rb
... ... @@ -1,229 +0,0 @@
1   -class ManageProductsController < ApplicationController
2   - needs_profile
3   -
4   - protect 'manage_products', :profile, :except => [:show]
5   - before_filter :check_environment_feature
6   - before_filter :login_required, :except => [:show]
7   - before_filter :create_product?, :only => [:new]
8   -
9   - protected
10   -
11   - def check_environment_feature
12   - unless profile.environment.enabled?('products_for_enterprises')
13   - render_not_found
14   - return
15   - end
16   - end
17   -
18   - def create_product?
19   - if !profile.create_product?
20   - render_access_denied
21   - return
22   - end
23   - end
24   -
25   - public
26   -
27   - def index
28   - @products = @profile.products.paginate(:per_page => 10, :page => params[:page])
29   - end
30   -
31   - def show
32   - @product = @profile.products.find(params[:id])
33   - @inputs = @product.inputs
34   - @allowed_user = user && user.has_permission?('manage_products', profile)
35   - end
36   -
37   - def categories_for_selection
38   - @category = environment.categories.find_by id: params[:category_id]
39   - @object_name = params[:object_name]
40   - if @category
41   - @categories = @category.children
42   - @level = @category.leaf? ? @category.level : @categories.first.level
43   - else
44   - @categories = ProductCategory.top_level_for(environment)
45   - @level = 0
46   - end
47   - render :partial => 'categories_for_selection', :locals => { :categories => @categories, :level => @level }
48   - end
49   -
50   - def new
51   - @no_design_blocks = true
52   - @category = params[:selected_category_id] ? Category.find(params[:selected_category_id]) : nil
53   - @product = @profile.products.build(:product_category => @category)
54   - @categories = ProductCategory.top_level_for(environment)
55   - @level = 0
56   - if request.post?
57   - if @product.save
58   - session[:notice] = _('Product succesfully created')
59   - render :partial => 'shared/redirect_via_javascript',
60   - :locals => { :url => url_for(:controller => 'manage_products', :action => 'show', :id => @product) }
61   - else
62   - render_dialog_error_messages 'product'
63   - end
64   - end
65   - end
66   -
67   - def edit
68   - @product = @profile.products.find(params[:id])
69   - field = params[:field]
70   - if request.post?
71   - begin
72   - @product.update!(params[:product])
73   - render :partial => "display_#{field}", :locals => {:product => @product}
74   - rescue Exception => e
75   - render :partial => "edit_#{field}", :locals => {:product => @product, :errors => true}
76   - end
77   - else
78   - render :partial => "edit_#{field}", :locals => {:product => @product, :errors => false}
79   - end
80   - end
81   -
82   - def edit_category
83   - @product = @profile.products.find(params[:id])
84   - @category = @product.product_category || ProductCategory.first
85   - @categories = ProductCategory.top_level_for(environment)
86   - @edit = true
87   - @level = @category.level
88   - if request.post?
89   - if @product.update({:product_category_id => params[:selected_category_id]}, :without_protection => true)
90   - render :partial => 'shared/redirect_via_javascript',
91   - :locals => { :url => url_for(:controller => 'manage_products', :action => 'show', :id => @product) }
92   - else
93   - render_dialog_error_messages 'product'
94   - end
95   - end
96   - end
97   -
98   - def show_category_tree
99   - @category = environment.categories.find params[:category_id]
100   - render :partial => 'selected_category_tree'
101   - end
102   -
103   - def search_categories
104   - @term = params[:term].downcase
105   - conditions = ['LOWER(name) LIKE ? OR LOWER(name) LIKE ?', "#{@term}%", "% #{@term}%"]
106   - @categories = ProductCategory.where(conditions).limit(10)
107   - render :json => (@categories.map do |category|
108   - {:label => category.name, :value => category.id}
109   - end)
110   - end
111   -
112   - def add_input
113   - @product = @profile.products.find(params[:id])
114   - @input = @product.inputs.build
115   - @categories = ProductCategory.top_level_for(environment)
116   - @level = 0
117   - if request.post?
118   - if @input.update(:product_category_id => params[:selected_category_id])
119   - @inputs = @product.inputs
120   - render :partial => 'display_inputs'
121   - else
122   - render_dialog_error_messages 'product'
123   - end
124   - else
125   - render :partial => 'add_input'
126   - end
127   - end
128   -
129   - def manage_product_details
130   - @product = @profile.products.find(params[:id])
131   - if request.post?
132   - @product.update_price_details(params[:price_details]) if params[:price_details]
133   - render :partial => 'display_price_details'
134   - else
135   - render :partial => 'manage_product_details'
136   - end
137   - end
138   -
139   - def remove_price_detail
140   - @product = @profile.products.find(params[:product])
141   - @price_detail = @product.price_details.find(params[:id])
142   - @product = @price_detail.product
143   - if request.post?
144   - @price_detail.destroy
145   - render :nothing => true
146   - end
147   - end
148   -
149   - def display_price_composition_bar
150   - @product = @profile.products.find(params[:id])
151   - render :partial => 'price_composition_bar'
152   - end
153   -
154   - def display_inputs_cost
155   - @product = @profile.products.find(params[:id])
156   - render :inline => "<%= float_to_currency(@product.inputs_cost) %>"
157   - end
158   -
159   - def destroy
160   - @product = @profile.products.find(params[:id])
161   - if @product.destroy
162   - session[:notice] = _('Product succesfully removed')
163   - redirect_back_or_default :action => 'index'
164   - else
165   - session[:notice] = _('Could not remove the product')
166   - redirect_back_or_default :action => 'show', :id => @product
167   - end
168   - end
169   -
170   - def edit_input
171   - if request.xhr?
172   - @input = @profile.inputs.find_by id: params[:id]
173   - if @input
174   - if request.post?
175   - if @input.update(params[:input])
176   - render :partial => 'display_input', :locals => {:input => @input}
177   - else
178   - render :partial => 'edit_input'
179   - end
180   - else
181   - render :partial => 'edit_input'
182   - end
183   - else
184   - render :text => _('The input was not found')
185   - end
186   - end
187   - end
188   -
189   - def order_inputs
190   - @product = @profile.products.find(params[:id])
191   - @product.order_inputs!(params[:input]) if params[:input]
192   - render :nothing => true
193   - end
194   -
195   - def remove_input
196   - @input = @profile.inputs.find(params[:id])
197   - @product = @input.product
198   - if request.post?
199   - if @input.destroy
200   - @inputs = @product.inputs
201   - render :partial => 'display_inputs'
202   - else
203   - render_dialog_error_messages 'input'
204   - end
205   - end
206   - end
207   -
208   - def certifiers_for_selection
209   - @qualifier = Qualifier.exists?(:id => params[:id]) ? Qualifier.find(params[:id]) : nil
210   - render :update do |page|
211   - page.replace_html params[:certifier_area], :partial => 'certifiers_for_selection'
212   - end
213   - end
214   -
215   - def create_production_cost
216   - cost = @profile.production_costs.create(:name => params[:id])
217   - if cost.valid?
218   - cost.save
219   - render :text => {:name => cost.name,
220   - :id => cost.id,
221   - :ok => true
222   - }.to_json
223   - else
224   - render :text => {:ok => false,
225   - :error_msg => _(cost.errors['name'].join('\n')) % {:fn => _('Name')}
226   - }.to_json
227   - end
228   - end
229   -end
app/controllers/my_profile/profile_design_controller.rb
... ... @@ -45,17 +45,10 @@ class ProfileDesignController &lt; BoxOrganizerController
45 45 if profile.enterprise?
46 46 blocks << DisabledEnterpriseMessageBlock
47 47 blocks << HighlightsBlock
48   - blocks << ProductCategoriesBlock
49   - blocks << FeaturedProductsBlock
50 48 blocks << FansBlock
51 49 blocks += plugins.dispatch(:extra_blocks, :type => Enterprise)
52 50 end
53 51  
54   - # product block exclusive for enterprises in environments that permits it
55   - if profile.enterprise? && profile.environment.enabled?('products_for_enterprises')
56   - blocks << ProductsBlock
57   - end
58   -
59 52 # block exclusive to profiles that have blog
60 53 if profile.has_blog?
61 54 blocks << BlogArchivesBlock
... ...
app/controllers/my_profile/profile_editor_controller.rb
... ... @@ -29,6 +29,7 @@ class ProfileEditorController &lt; MyProfileController
29 29 Image.transaction do
30 30 begin
31 31 @plugins.dispatch(:profile_editor_transaction_extras)
  32 + # TODO: This is unsafe! Add sanitizer
32 33 @profile_data.update!(params[:profile_data])
33 34 redirect_to :action => 'index', :profile => profile.identifier
34 35 rescue Exception => ex
... ...
app/controllers/my_profile/profile_themes_controller.rb
... ... @@ -63,12 +63,12 @@ class ProfileThemesController &lt; ThemesController
63 63 end
64 64  
65 65 def start_test
66   - session[:theme] = params[:id]
  66 + session[:user_theme] = params[:id]
67 67 redirect_to :controller => 'content_viewer', :profile => profile.identifier, :action => 'view_page'
68 68 end
69 69  
70 70 def stop_test
71   - session[:theme] = nil
  71 + session[:user_theme] = nil
72 72 redirect_to :action => 'index'
73 73 end
74 74  
... ...
app/controllers/my_profile/tasks_controller.rb
... ... @@ -26,7 +26,11 @@ class TasksController &lt; MyProfileController
26 26 end
27 27  
28 28 def processed
29   - @tasks = Task.to(profile).without_spam.closed.sort_by(&:created_at)
  29 + @tasks = Task.to(profile).without_spam.closed.order('tasks.created_at DESC')
  30 + @filter = params[:filter] || {}
  31 + @tasks = filter_tasks(@filter, @tasks)
  32 + @tasks = @tasks.paginate(:per_page => Task.per_page, :page => params[:page])
  33 + @task_types = Task.closed_types_for(profile)
30 34 end
31 35  
32 36 def change_responsible
... ... @@ -102,4 +106,28 @@ class TasksController &lt; MyProfileController
102 106 render :json => result.map { |task| {:label => task.data[:name], :value => task.data[:name]} }
103 107 end
104 108  
  109 + protected
  110 +
  111 + def filter_tasks(filter, tasks)
  112 + tasks = tasks.eager_load(:requestor, :closed_by)
  113 + tasks = tasks.of(filter[:type].presence)
  114 + tasks = tasks.where(:status => filter[:status]) unless filter[:status].blank?
  115 +
  116 + filter[:created_from] = Date.parse(filter[:created_from]) unless filter[:created_from].blank?
  117 + filter[:created_until] = Date.parse(filter[:created_until]) unless filter[:created_until].blank?
  118 + filter[:closed_from] = Date.parse(filter[:closed_from]) unless filter[:closed_from].blank?
  119 + filter[:closed_until] = Date.parse(filter[:closed_until]) unless filter[:closed_until].blank?
  120 +
  121 + tasks = tasks.from_creation_date filter[:created_from] unless filter[:created_from].blank?
  122 + tasks = tasks.until_creation_date filter[:created_until] unless filter[:created_until].blank?
  123 +
  124 + tasks = tasks.from_closed_date filter[:closed_from] unless filter[:closed_from].blank?
  125 + tasks = tasks.until_closed_date filter[:closed_until] unless filter[:closed_until].blank?
  126 +
  127 + tasks = tasks.where('profiles.name LIKE ?', filter[:requestor]) unless filter[:requestor].blank?
  128 + tasks = tasks.where('closed_bies_tasks.name LIKE ?', filter[:closed_by]) unless filter[:closed_by].blank?
  129 + tasks = tasks.where('tasks.data LIKE ?', "%#{filter[:text]}%") unless filter[:text].blank?
  130 + tasks
  131 + end
  132 +
105 133 end
... ...
app/controllers/public/api_controller.rb
... ... @@ -13,7 +13,7 @@ class ApiController &lt; PublicController
13 13 private
14 14  
15 15 def endpoints
16   - Noosfero::API::API.endpoints(environment)
  16 + Api::App.endpoints(environment)
17 17 end
18 18  
19 19 end
... ...
app/controllers/public/catalog_controller.rb
... ... @@ -1,19 +0,0 @@
1   -class CatalogController < PublicController
2   - needs_profile
3   -
4   - before_filter :check_enterprise_and_environment
5   -
6   - def index
7   - extend CatalogHelper
8   - catalog_load_index
9   - end
10   -
11   - protected
12   -
13   - def check_enterprise_and_environment
14   - unless profile.enterprise? && @profile.environment.enabled?('products_for_enterprises')
15   - redirect_to :controller => 'profile', :profile => profile.identifier, :action => 'index'
16   - end
17   - end
18   -
19   -end
app/controllers/public/content_viewer_controller.rb
... ... @@ -209,7 +209,7 @@ class ContentViewerController &lt; ApplicationController
209 209 end
210 210  
211 211 if @page.published && @page.uploaded_file?
212   - redirect_to @page.public_filename
  212 + redirect_to "#{Noosfero.root}#{@page.public_filename}"
213 213 else
214 214 send_data data, @page.download_headers
215 215 end
... ...
app/controllers/public/profile_controller.rb
... ... @@ -86,8 +86,16 @@ class ProfileController &lt; PublicController
86 86 @articles = profile.top_level_articles.includes([:profile, :parent])
87 87 end
88 88  
  89 + def join_modal
  90 + profile.add_member(user)
  91 + session[:notice] = _('%s administrator still needs to accept you as member.') % profile.name
  92 + redirect_to :action => :index
  93 + end
  94 +
89 95 def join
90 96 if !user.memberships.include?(profile)
  97 + return if profile.community? && show_confirmation_modal?(profile)
  98 +
91 99 profile.add_member(user)
92 100 if !profile.members.include?(user)
93 101 render :text => {:message => _('%s administrator still needs to accept you as member.') % profile.name}.to_json
... ...
app/controllers/public/search_controller.rb
... ... @@ -52,7 +52,6 @@ class SearchController &lt; PublicController
52 52 [
53 53 [ :people, _('People'), :recent_people ],
54 54 [ :enterprises, _('Enterprises'), :recent_enterprises ],
55   - [ :products, _('Products'), :recent_products ],
56 55 [ :events, _('Upcoming events'), :upcoming_events ],
57 56 [ :communities, _('Communities'), :recent_communities ],
58 57 [ :articles, _('Contents'), :recent_articles ]
... ... @@ -78,16 +77,17 @@ class SearchController &lt; PublicController
78 77 full_text_search
79 78 end
80 79  
81   - def products
82   - @scope = @environment.products
83   - full_text_search
84   - end
85   -
86 80 def enterprises
87 81 @scope = visible_profiles(Enterprise)
88 82 full_text_search
89 83 end
90 84  
  85 + # keep URL compatibility
  86 + def products
  87 + return render_not_found unless defined? ProductsPlugin
  88 + redirect_to url_for(params.merge controller: 'products_plugin/search', action: :products)
  89 + end
  90 +
91 91 def communities
92 92 @scope = visible_profiles(Community)
93 93 full_text_search
... ... @@ -186,7 +186,6 @@ class SearchController &lt; PublicController
186 186 people: _('People'),
187 187 communities: _('Communities'),
188 188 enterprises: _('Enterprises'),
189   - products: _('Products and Services'),
190 189 events: _('Events'),
191 190 }
192 191 end
... ... @@ -260,12 +259,11 @@ class SearchController &lt; PublicController
260 259 end
261 260  
262 261 def available_assets
263   - assets = {
  262 + {
264 263 articles: _('Contents'),
265 264 enterprises: _('Enterprises'),
266 265 people: _('People'),
267 266 communities: _('Communities'),
268   - products: _('Products and Services'),
269 267 }
270 268 end
271 269  
... ...
app/helpers/action_tracker_helper.rb
... ... @@ -5,22 +5,22 @@ module ActionTrackerHelper
5 5 end
6 6  
7 7 def new_friendship_description ta
8   - n_('has made 1 new friend:<br />%{name}', 'has made %{num} new friends:<br />%{name}', ta.get_friend_name.size) % {
  8 + n_('has made 1 new friend:<br />%{name}', 'has made %{num} new friends:<br />%{name}', ta.get_friend_name.size).html_safe % {
9 9 num: ta.get_friend_name.size,
10   - name: ta.collect_group_with_index(:friend_name) do |n,i|
  10 + name: safe_join(ta.collect_group_with_index(:friend_name) do |n,i|
11 11 link_to image_tag(ta.get_friend_profile_custom_icon[i] || default_or_themed_icon("/images/icons-app/person-icon.png")),
12 12 ta.get_friend_url[i], title: n
13   - end.join
  13 + end)
14 14 }
15 15 end
16 16  
17 17 def join_community_description ta
18   - n_('has joined 1 community:<br />%{name}', 'has joined %{num} communities:<br />%{name}', ta.get_resource_name.size) % {
  18 + n_('has joined 1 community:<br />%{name}'.html_safe, 'has joined %{num} communities:<br />%{name}'.html_safe, ta.get_resource_name.size) % {
19 19 num: ta.get_resource_name.size,
20 20 name: ta.collect_group_with_index(:resource_name) do |n,i|
21   - link_to image_tag(ta.get_resource_profile_custom_icon[i] || default_or_themed_icon("/images/icons-app/community-icon.png")),
  21 + link = link_to image_tag(ta.get_resource_profile_custom_icon[i] || default_or_themed_icon("/images/icons-app/community-icon.png")),
22 22 ta.get_resource_url[i], title: n
23   - end.join
  23 + end.join.html_safe
24 24 }
25 25 end
26 26  
... ... @@ -67,24 +67,6 @@ module ActionTrackerHelper
67 67 }
68 68 end
69 69  
70   - def create_product_description ta
71   - _('created the product %{title}') % {
72   - title: link_to(truncate(ta.get_name), ta.get_url),
73   - }
74   - end
75   -
76   - def update_product_description ta
77   - _('updated the product %{title}') % {
78   - title: link_to(truncate(ta.get_name), ta.get_url),
79   - }
80   - end
81   -
82   - def remove_product_description ta
83   - _('removed the product %{title}') % {
84   - title: truncate(ta.get_name),
85   - }
86   - end
87   -
88 70 def favorite_enterprise_description ta
89 71 _('favorited enterprise %{title}') % {
90 72 title: link_to(truncate(ta.get_enterprise_name), ta.get_enterprise_url),
... ...
app/helpers/application_helper.rb
... ... @@ -44,8 +44,6 @@ module ApplicationHelper
44 44  
45 45 include TokenHelper
46 46  
47   - include CatalogHelper
48   -
49 47 include PluginsHelper
50 48  
51 49 include ButtonsHelper
... ... @@ -56,6 +54,8 @@ module ApplicationHelper
56 54  
57 55 include TaskHelper
58 56  
  57 + include MembershipsHelper
  58 +
59 59 def locale
60 60 (@page && !@page.language.blank?) ? @page.language : FastGettext.locale
61 61 end
... ... @@ -99,7 +99,6 @@ module ApplicationHelper
99 99 #
100 100 # TODO: implement correcly the 'Help' button click
101 101 def help(content = nil, link_name = nil, options = {}, &block)
102   -
103 102 link_name ||= _('Help')
104 103  
105 104 @help_message_id ||= 1
... ... @@ -122,7 +121,7 @@ module ApplicationHelper
122 121 button = link_to_function(content_tag('span', link_name), "Element.show('#{help_id}')", options )
123 122 close_button = content_tag("div", link_to_function(_("Close"), "Element.hide('#{help_id}')", :class => 'close_help_button'))
124 123  
125   - text = content_tag('div', button + content_tag('div', content_tag('div', content) + close_button, :class => 'help_message', :id => help_id, :style => 'display: none;'), :class => 'help_box')
  124 + text = content_tag('div', button + content_tag('div', content_tag('div', content.html_safe) + close_button, :class => 'help_message', :id => help_id, :style => 'display: none;'), :class => 'help_box')
126 125  
127 126 unless block.nil?
128 127 concat(text)
... ... @@ -234,13 +233,6 @@ module ApplicationHelper
234 233 link_to(content_tag('span', text), url, html_options.merge(:class => the_class, :title => text))
235 234 end
236 235  
237   - def button_bar(options = {}, &block)
238   - options[:class].nil? ?
239   - options[:class]='button-bar' :
240   - options[:class]+=' button-bar'
241   - concat(content_tag('div', capture(&block).to_s + tag('br', :style => 'clear: left;'), options))
242   - end
243   -
244 236 def render_profile_actions klass
245 237 name = klass.to_s.underscore
246 238 begin
... ... @@ -352,7 +344,7 @@ module ApplicationHelper
352 344 end
353 345  
354 346 def is_testing_theme
355   - !controller.session[:theme].nil?
  347 + !controller.session[:user_theme].nil?
356 348 end
357 349  
358 350 def theme_owner
... ... @@ -362,8 +354,8 @@ module ApplicationHelper
362 354 def popover_menu(title,menu_title,links,html_options={})
363 355 html_options[:class] = "" unless html_options[:class]
364 356 html_options[:class] << " menu-submenu-trigger"
365   - html_options[:onclick] = "toggleSubmenu(this, '#{menu_title}', #{CGI::escapeHTML(links.to_json)}); return false"
366 357  
  358 + html_options[:onclick] = "toggleSubmenu(this, '#{menu_title}', #{CGI::escapeHTML(links.to_json)}); return false".html_safe
367 359 link_to(content_tag(:span, title), '#', html_options)
368 360 end
369 361  
... ... @@ -473,9 +465,9 @@ module ApplicationHelper
473 465 map(&:role)
474 466 names = []
475 467 roles.each do |role|
476   - names << content_tag('span', role.name, :style => "color: #{role_color(role, resource.environment.id)}")
  468 + names << content_tag('span', role.name, :style => "color: #{role_color(role, resource.environment.id)}").html_safe
477 469 end
478   - names.join(', ')
  470 + safe_join(names, ', ')
479 471 end
480 472  
481 473 def role_color(role, env_id)
... ... @@ -601,8 +593,8 @@ module ApplicationHelper
601 593 end
602 594  
603 595 if block
604   - field_html ||= ''
605   - field_html += capture(&block)
  596 + field_html ||= ''.html_safe
  597 + field_html = [field_html, capture(&block)].safe_join
606 598 end
607 599  
608 600 if controller.action_name == 'signup' || controller.action_name == 'new_community' || (controller.controller_name == "enterprise_registration" && controller.action_name == 'index') || (controller.controller_name == 'home' && controller.action_name == 'index' && user.nil?)
... ... @@ -611,7 +603,9 @@ module ApplicationHelper
611 603 end
612 604 else
613 605 if profile.active_fields.include?(name)
614   - result = content_tag('div', field_html + profile_field_privacy_selector(profile, name), :class => 'field-with-privacy-selector')
  606 + result = content_tag :div, class: 'field-with-privacy-selector' do
  607 + [field_html, profile_field_privacy_selector(profile, name)].safe_join
  608 + end
615 609 end
616 610 end
617 611  
... ... @@ -619,10 +613,6 @@ module ApplicationHelper
619 613 result = required(result)
620 614 end
621 615  
622   - if block
623   - concat(result)
624   - end
625   -
626 616 result
627 617 end
628 618  
... ... @@ -788,7 +778,7 @@ module ApplicationHelper
788 778 return "" if categories.blank?
789 779 content_tag(:ul) do
790 780 categories.map do |category|
791   - category_path = category.kind_of?(ProductCategory) ? {:controller => 'search', :action => 'assets', :asset => 'products', :product_category => category.id} : { :controller => 'search', :action => 'category_index', :category_path => category.explode_path }
  781 + category_path = { :controller => 'search', :action => 'category_index', :category_path => category.explode_path }
792 782 if category.display_in_menu?
793 783 content_tag(:li) do
794 784 if !category.is_leaf_displayable_in_menu?
... ... @@ -867,7 +857,7 @@ module ApplicationHelper
867 857 alias :browse_communities_menu :search_communities_menu
868 858  
869 859 def pagination_links(collection, options={})
870   - options = {:previous_label => content_tag(:span, '&laquo; ', :class => 'previous-arrow') + _('Previous'), :next_label => _('Next') + content_tag(:span, ' &raquo;', :class => 'next-arrow'), :inner_window => 1, :outer_window => 0 }.merge(options)
  860 + options = {:previous_label => content_tag(:span, '&laquo; '.html_safe, :class => 'previous-arrow') + _('Previous'), :next_label => _('Next') + content_tag(:span, ' &raquo;'.html_safe, :class => 'next-arrow'), :inner_window => 1, :outer_window => 0 }.merge(options)
871 861 will_paginate(collection, options)
872 862 end
873 863  
... ... @@ -911,7 +901,8 @@ module ApplicationHelper
911 901 end
912 902  
913 903 def admin_link
914   - user.is_admin?(environment) ? link_to('<i class="icon-menu-admin"></i><strong>' + _('Administration') + '</strong>', environment.admin_url, :title => _("Configure the environment"), :class => 'admin-link') : ''
  904 + admin_icon = '<i class="icon-menu-admin"></i><strong>' + _('Administration') + '</strong>'
  905 + user.is_admin?(environment) ? link_to(admin_icon.html_safe, environment.admin_url, :title => _("Configure the environment"), :class => 'admin-link') : ''
915 906 end
916 907  
917 908 def usermenu_logged_in
... ... @@ -920,23 +911,39 @@ module ApplicationHelper
920 911 if count > 0
921 912 pending_tasks_count = link_to(count.to_s, user.tasks_url, :id => 'pending-tasks-count', :title => _("Manage your pending tasks"))
922 913 end
  914 + user_identifier = "<i style='background-image:url(#{user.profile_custom_icon(gravatar_default)})'></i><strong>#{user.identifier}</strong>"
  915 + welcome_link = link_to(user_identifier.html_safe, user.public_profile_url, :id => "homepage-link", :title => _('Go to your homepage'))
  916 + welcome_span = _("<span class='welcome'>Welcome,</span> %s") % welcome_link.html_safe
  917 + ctrl_panel_icon = '<i class="icon-menu-ctrl-panel"></i>'
  918 + ctrl_panel_section = '<strong>' + ctrl_panel_icon + _('Control panel') + '</strong>'
  919 + ctrl_panel_link = link_to(ctrl_panel_section.html_safe, user.admin_url, :class => 'ctrl-panel', :title => _("Configure your personal account and content"))
  920 + logout_icon = '<i class="icon-menu-logout"></i><strong>' + _('Logout') + '</strong>'
  921 + logout_link = link_to(logout_icon.html_safe, { :controller => 'account', :action => 'logout'} , :id => "logout", :title => _("Leave the system"))
  922 + join_result = safe_join(
  923 + [welcome_span.html_safe, render_environment_features(:usermenu).html_safe, admin_link.html_safe,
  924 + manage_enterprises.html_safe, manage_communities.html_safe, ctrl_panel_link.html_safe,
  925 + pending_tasks_count.html_safe, logout_link.html_safe], "")
  926 + join_result
  927 + end
923 928  
924   - (_("<span class='welcome'>Welcome,</span> %s") % link_to("<i style='background-image:url(#{user.profile_custom_icon(gravatar_default)})'></i><strong>#{user.identifier}</strong>", user.url, :id => "homepage-link", :title => _('Go to your homepage'))) +
925   - render_environment_features(:usermenu) +
926   - admin_link +
927   - manage_enterprises +
928   - manage_communities +
929   - link_to('<i class="icon-menu-ctrl-panel"></i><strong>' + _('Control panel') + '</strong>', user.admin_url, :class => 'ctrl-panel', :title => _("Configure your personal account and content")) +
930   - pending_tasks_count +
931   - link_to('<i class="icon-menu-logout"></i><strong>' + _('Logout') + '</strong>', { :controller => 'account', :action => 'logout'} , :id => "logout", :title => _("Leave the system"))
  929 + def usermenu_notlogged_in
  930 + login_str = '<i class="icon-menu-login"></i><strong>' + _('Login') + '</strong>'
  931 + ret = _("<span class='login'>%s</span>") % modal_inline_link_to(login_str.html_safe, login_url, '#inlineLoginBox', :id => 'link_login')
  932 + return ret.html_safe
932 933 end
933 934  
  935 + def usermenu_signup
  936 + signup_str = '<strong>' + _('Sign up') + '</strong>'
  937 + ret = _("<span class='or'>or</span> <span class='signup'>%s</span>") % link_to(signup_str.html_safe, :controller => 'account', :action => 'signup')
  938 + return ret.html_safe
  939 +
  940 + end
934 941 def limited_text_area(object_name, method, limit, text_area_id, options = {})
935   - content_tag(:div, [
  942 + content_tag(:div, safe_join([
936 943 text_area(object_name, method, { :id => text_area_id, :onkeyup => "limited_text_area('#{text_area_id}', #{limit})" }.merge(options)),
937 944 content_tag(:p, content_tag(:span, limit) + ' ' + _(' characters left'), :id => text_area_id + '_left'),
938 945 content_tag(:p, _('Limit of characters reached'), :id => text_area_id + '_limit', :style => 'display: none')
939   - ].join, :class => 'limited-text-area')
  946 + ]), :class => 'limited-text-area')
940 947 end
941 948  
942 949 def expandable_text_area(object_name, method, text_area_id, options = {})
... ... @@ -970,11 +977,12 @@ module ApplicationHelper
970 977  
971 978 def task_information(task)
972 979 values = {}
  980 + values.merge!(task.information[:variables]) if task.information[:variables]
973 981 values.merge!({:requestor => link_to(task.requestor.name, task.requestor.url)}) if task.requestor
  982 + values.merge!({:target => link_to(task.target.name, task.target.url)}) if (task.target && task.target.respond_to?(:url))
974 983 values.merge!({:subject => content_tag('span', task.subject, :class=>'task_target')}) if task.subject
975 984 values.merge!({:linked_subject => link_to(content_tag('span', task.linked_subject[:text], :class => 'task_target'), task.linked_subject[:url])}) if task.linked_subject
976   - values.merge!(task.information[:variables]) if task.information[:variables]
977   - task.information[:message] % values
  985 + (task.information[:message] % values).html_safe
978 986 end
979 987  
980 988 def add_zoom_to_article_images
... ... @@ -1032,26 +1040,24 @@ module ApplicationHelper
1032 1040 end
1033 1041  
1034 1042 def render_tabs(tabs)
1035   - titles = tabs.inject(''){ |result, tab| result << content_tag(:li, link_to(tab[:title], '#'+tab[:id]), :class => 'tab') }
1036   - contents = tabs.inject(''){ |result, tab| result << content_tag(:div, tab[:content], :id => tab[:id]) }
  1043 + titles = tabs.inject(''.html_safe){ |result, tab| result << content_tag(:li, link_to(tab[:title], '#'+tab[:id]), :class => 'tab') }
  1044 + contents = tabs.inject(''.html_safe){ |result, tab| result << content_tag(:div, tab[:content], :id => tab[:id]) }
1037 1045  
1038 1046 content_tag(:div, content_tag(:ul, titles) + raw(contents), :class => 'ui-tabs')
1039 1047 end
1040 1048  
1041 1049 def delete_article_message(article)
1042   - CGI.escapeHTML(
1043   - if article.folder?
1044   - _("Are you sure that you want to remove the folder \"%s\"? Note that all the items inside it will also be removed!") % article.name
1045   - else
1046   - _("Are you sure that you want to remove the item \"%s\"?") % article.name
1047   - end
1048   - )
  1050 + if article.folder?
  1051 + _("Are you sure that you want to remove the folder \"%s\"? Note that all the items inside it will also be removed!") % article.name
  1052 + else
  1053 + _("Are you sure that you want to remove the item \"%s\"?") % article.name
  1054 + end
1049 1055 end
1050 1056  
1051 1057 def expirable_link_to(expired, content, url, options = {})
1052 1058 if expired
1053 1059 options[:class] = (options[:class] || '') + ' disabled'
1054   - content_tag('a', '&nbsp;'+content_tag('span', content), options)
  1060 + content_tag('a', '&nbsp;'.html_safe+content_tag('span', content), options)
1055 1061 else
1056 1062 if options[:modal]
1057 1063 options.delete(:modal)
... ... @@ -1084,7 +1090,7 @@ module ApplicationHelper
1084 1090  
1085 1091 radios = templates.map do |template|
1086 1092 content_tag('li', labelled_radio_button(link_to(template.name, template.url, :target => '_blank'), "#{field_name}[template_id]", template.id, environment.is_default_template?(template)))
1087   - end.join("\n")
  1093 + end.join("\n").html_safe
1088 1094  
1089 1095 content_tag('div', content_tag('label', _('Profile organization'), :for => 'template-options', :class => 'formlabel') +
1090 1096 content_tag('p', _('Your profile will be created according to the selected template. Click on the options to view them.'), :style => 'margin: 5px 15px;padding: 0px 10px;') +
... ... @@ -1124,7 +1130,7 @@ module ApplicationHelper
1124 1130 content_tag(:div, :class => 'errorExplanation', :id => 'errorExplanation') do
1125 1131 content_tag(:h2, _('Errors while saving')) +
1126 1132 content_tag(:ul) do
1127   - errors.map { |err| content_tag(:li, err) }.join
  1133 + safe_join(errors.map { |err| content_tag(:li, err.html_safe) })
1128 1134 end
1129 1135 end
1130 1136 end
... ... @@ -1234,6 +1240,7 @@ module ApplicationHelper
1234 1240 :href=>"#",
1235 1241 :title=>_("Exit full screen mode")
1236 1242 })
  1243 + content.html_safe
1237 1244 end
1238 1245  
1239 1246 end
... ...
app/helpers/article_helper.rb
... ... @@ -69,14 +69,14 @@ module ArticleHelper
69 69 content_tag('div',
70 70 content_tag('div',
71 71 radio_button(:article, :published, true) +
72   - content_tag('span', '&nbsp;', :class => 'access-public-icon') +
  72 + content_tag('span', '&nbsp;'.html_safe, :class => 'access-public-icon') +
73 73 content_tag('label', _('Public'), :for => 'article_published_true') +
74 74 content_tag('span', _('Visible to other people'), :class => 'access-note'),
75 75 :class => 'access-item'
76 76 ) +
77 77 content_tag('div',
78 78 radio_button(:article, :published, false) +
79   - content_tag('span', '&nbsp;', :class => 'access-private-icon') +
  79 + content_tag('span', '&nbsp;'.html_safe, :class => 'access-private-icon') +
80 80 content_tag('label', _('Private'), :for => 'article_published_false', :id => "label_private") +
81 81 content_tag('span', _('Limit visibility of this article'), :class => 'access-note'),
82 82 :class => 'access-item'
... ... @@ -187,9 +187,9 @@ module ArticleHelper
187 187 def following_button(page, user)
188 188 if !user.blank? and user != page.author
189 189 if page.is_followed_by? user
190   - button :cancel, unfollow_button_text(page), {:controller => 'profile', :action => 'unfollow_article', :article_id => page.id}
  190 + button :cancel, unfollow_button_text(page), {controller: :profile, profile: page.profile.identifier, action: :unfollow_article, article_id: page.id}
191 191 else
192   - button :add, follow_button_text(page), {:controller => 'profile', :action => 'follow_article', :article_id => page.id}
  192 + button :add, follow_button_text(page), {controller: :profile, profile: page.profile.identifier, action: :follow_article, article_id: page.id}
193 193 end
194 194 end
195 195 end
... ...
app/helpers/block_helper.rb
... ... @@ -3,13 +3,13 @@ module BlockHelper
3 3 def block_title(title, subtitle=nil)
4 4 block_header = block_heading title
5 5 block_header += block_heading(subtitle, 'h4') if subtitle
6   - content_tag 'div', block_header, :class => 'block-header'
  6 + content_tag('div', block_header, :class => 'block-header').html_safe
7 7 end
8 8  
9 9 def block_heading(title, heading='h3')
10 10 tag_class = 'block-' + (heading == 'h3' ? 'title' : 'subtitle')
11 11 tag_class += ' empty' if title.empty?
12   - content_tag heading, content_tag('span', h(title)), :class => tag_class
  12 + content_tag heading, content_tag('span', h(title)), :class => tag_class.html_safe
13 13 end
14 14  
15 15 def highlights_block_config_image_fields(block, image={}, row_number=nil)
... ... @@ -28,7 +28,7 @@ module BlockHelper
28 28 }</label></td>
29 29 <td>#{button_without_text(:delete, _('Remove'), '#', class: 'delete-highlight', data: {confirm: _('Are you sure you want to remove this highlight')})}</td>
30 30 </tr>
31   - "
  31 + ".html_safe
32 32 end
33 33  
34 34 end
... ...
app/helpers/blog_helper.rb
... ... @@ -41,12 +41,12 @@ module BlogHelper
41 41 css_add << position
42 42 content << (content_tag 'div', id: "post-#{art.id}", class: css_add do
43 43 content_tag 'div', class: position + '-inner blog-post-inner' do
44   - display_post(art, conf[:format]).html_safe +
  44 + display_post(art, conf[:format]) +
45 45 '<br style="clear:both"/>'.html_safe
46 46 end
47   - end)
  47 + end).html_safe
48 48 }
49   - content.join("\n<hr class='sep-posts'/>\n") + (pagination or '')
  49 + safe_join(content, "\n<hr class='sep-posts'/>\n".html_safe) + (pagination or '').html_safe
50 50 end
51 51  
52 52 def display_post(article, format = 'full')
... ... @@ -61,7 +61,8 @@ module BlogHelper
61 61 else
62 62 '<div class="post-pic" style="background-image:url('+img+')"></div>'
63 63 end
64   - end.to_s + title + html
  64 + end.to_s.html_safe +
  65 + title.html_safe + html
65 66 end
66 67  
67 68 def display_compact_format(article)
... ...
app/helpers/box_organizer_helper.rb
... ... @@ -38,7 +38,7 @@ module BoxOrganizerHelper
38 38 content_tag(:ul,
39 39 images_path.map do |preview|
40 40 content_tag(:li, image_tag(preview, height: '240', alt: ''))
41   - end.join("\n")
  41 + end.join("\n").html_safe
42 42 )
43 43 end
44 44  
... ...
app/helpers/boxes_helper.rb
... ... @@ -34,7 +34,7 @@ module BoxesHelper
34 34  
35 35 def display_boxes_editor(holder)
36 36 with_box_decorator self do
37   - content_tag('div', display_boxes(holder, '&lt;' + _('Main content') + '&gt;'), :id => 'box-organizer')
  37 + content_tag('div', display_boxes(holder, '<' + _('Main content') + '>'), :id => 'box-organizer')
38 38 end
39 39 end
40 40  
... ... @@ -44,7 +44,7 @@ module BoxesHelper
44 44  
45 45 def display_boxes(holder, main_content)
46 46 boxes = holder.boxes.with_position.first(boxes_limit(holder))
47   - content = boxes.reverse.map { |item| display_box(item, main_content) }.join("\n")
  47 + content = safe_join(boxes.reverse.map { |item| display_box(item, main_content) }, "\n")
48 48 content = main_content if (content.blank?)
49 49  
50 50 content_tag('div', content, :class => 'boxes', :id => 'boxes' )
... ... @@ -52,9 +52,9 @@ module BoxesHelper
52 52  
53 53 def maybe_display_custom_element(holder, element, options = {})
54 54 if holder.respond_to?(element)
55   - content_tag('div', holder.send(element), options)
  55 + content_tag('div', holder.send(element).to_s.html_safe, options)
56 56 else
57   - ''
  57 + ''.html_safe
58 58 end
59 59 end
60 60  
... ... @@ -64,15 +64,16 @@ module BoxesHelper
64 64  
65 65 def display_updated_box(box)
66 66 with_box_decorator self do
67   - display_box_content(box, '&lt;' + _('Main content') + '&gt;')
  67 + display_box_content(box, '<' + _('Main content') + '>')
68 68 end
69 69 end
70 70  
71 71 def display_box_content(box, main_content)
72 72 context = { :article => @page, :request_path => request.path, :locale => locale, :params => request.params, :user => user, :controller => controller }
73   - box_decorator.select_blocks(box, box.blocks.includes(:box), context).map do |item|
  73 + blocks = box_decorator.select_blocks(box, box.blocks.includes(:box), context).map do |item|
74 74 display_block item, main_content
75   - end.join("\n") + box_decorator.block_target(box)
  75 + end
  76 + safe_join(blocks, "\n") + box_decorator.block_target(box)
76 77 end
77 78  
78 79 def select_blocks box, arr, context
... ... @@ -98,15 +99,10 @@ module BoxesHelper
98 99 end
99 100  
100 101 def render_block_content block
101   - # FIXME: this conditional should be removed after all
102   - # block footer from plugins methods get refactored into helpers and views.
103   - # They are a failsafe until all of them are done.
104   - return block.content if block.method(:content).owner != Block
105 102 render_block block
106 103 end
107 104  
108 105 def render_block_footer block
109   - return block.footer if block.method(:footer).owner != Block
110 106 render_block block, 'footers/'
111 107 end
112 108  
... ... @@ -136,17 +132,18 @@ module BoxesHelper
136 132  
137 133 result = filter_html(result, block)
138 134  
139   - content_tag('div',
140   - box_decorator.block_target(block.box, block) +
141   - content_tag('div',
142   - content_tag('div',
143   - content_tag('div',
144   - result + footer_content + box_decorator.block_edit_buttons(block),
145   - :class => 'block-inner-2'),
146   - :class => 'block-inner-1'),
147   - options),
148   - :class => 'block-outer') +
149   - box_decorator.block_handle(block)
  135 + join_result = safe_join([result, footer_content, box_decorator.block_edit_buttons(block)])
  136 + content_tag_inner_1 = content_tag('div', join_result, :class => 'block-inner-2')
  137 +
  138 + content_tag_inner_2 = content_tag('div', content_tag_inner_1, :class => 'block-inner-1')
  139 + content_tag_inner_3 = content_tag('div', content_tag_inner_2, options)
  140 + content_tag_inner_4 = box_decorator.block_target(block.box, block) + content_tag_inner_3
  141 + c = content_tag('div', content_tag_inner_4, :class => 'block-outer')
  142 + box_decorator_result = box_decorator.block_handle(block)
  143 + result_final = safe_join([c, box_decorator_result], "")
  144 +
  145 +
  146 + return result_final
150 147 end
151 148  
152 149 def wrap_main_content(content)
... ... @@ -156,17 +153,17 @@ module BoxesHelper
156 153 def extract_block_content(content)
157 154 case content
158 155 when Hash
159   - content_tag('iframe', '', :src => url_for(content))
  156 + content_tag('iframe', ''.html_safe, :src => url_for(content))
160 157 when String
161 158 if content.split("\n").size == 1 and content =~ /^https?:\/\//
162   - content_tag('iframe', '', :src => content)
  159 + content_tag('iframe', ''.html_safe, :src => content)
163 160 else
164 161 content
165 162 end
166 163 when Proc
167 164 self.instance_eval(&content)
168 165 when NilClass
169   - ''
  166 + ''.html_safe
170 167 else
171 168 raise "Unsupported content for block (#{content.class})"
172 169 end
... ... @@ -175,14 +172,14 @@ module BoxesHelper
175 172 module DontMoveBlocks
176 173 # does nothing
177 174 def self.block_target(box, block = nil)
178   - ''
  175 + ''.html_safe
179 176 end
180 177 # does nothing
181 178 def self.block_handle(block)
182   - ''
  179 + ''.html_safe
183 180 end
184 181 def self.block_edit_buttons(block)
185   - ''
  182 + ''.html_safe
186 183 end
187 184 def self.select_blocks box, arr, context
188 185 arr = arr.select{ |block| block.visible? context }
... ... @@ -229,9 +226,9 @@ module BoxesHelper
229 226 # makes the given block draggable so it can be moved away.
230 227 def block_handle(block)
231 228 return "" unless movable?(block)
232   - icon = "<div><div>#{display_icon(block.class)}</div><span>#{_(block.class.pretty_name)}</span></div>"
  229 + icon = "<div><div>#{display_icon(block.class)}</div><span>#{_(block.class.pretty_name)}</span></div>".html_safe
233 230 block_draggable("block-#{block.id}",
234   - :helper => "function() {return cloneDraggableBlock($(this), '#{icon}')}")
  231 + :helper => "function() {return cloneDraggableBlock($(this), '#{icon}')}".html_safe)
235 232 end
236 233  
237 234 def block_draggable(element_id, options={})
... ... @@ -302,7 +299,7 @@ module BoxesHelper
302 299 buttons << modal_inline_icon(:embed, _('Embed code'), {}, "#embed-code-box-#{block.id}") << html
303 300 end
304 301  
305   - content_tag('div', buttons.join("\n") + tag('br', :style => 'clear: left'), :class => 'button-bar')
  302 + content_tag('div', buttons.join("\n").html_safe + tag('br', :style => 'clear: left'), :class => 'button-bar')
306 303 end
307 304  
308 305 def current_blocks
... ...
app/helpers/buttons_helper.rb
1 1 module ButtonsHelper
  2 +
  3 + def button_bar(options = {}, &block)
  4 + options[:class] ||= ''
  5 + options[:class] << ' button-bar'
  6 +
  7 + content_tag :div, options do
  8 + [
  9 + capture(&block).to_s,
  10 + tag(:br, style: 'clear: left;'),
  11 + ].safe_join
  12 + end
  13 + end
  14 +
2 15 def button(type, label, url, html_options = {})
3 16 html_options ||= {}
4 17 the_class = 'with-text'
... ... @@ -15,9 +28,9 @@ module ButtonsHelper
15 28 end
16 29 the_title = html_options[:title] || label
17 30 if html_options[:disabled]
18   - content_tag('a', '&nbsp;'+content_tag('span', label), html_options.merge(:class => the_class, :title => the_title))
  31 + content_tag('a', '&nbsp;'.html_safe+content_tag('span', label), html_options.merge(:class => the_class, :title => the_title))
19 32 else
20   - link_to('&nbsp;'+content_tag('span', label), url, html_options.merge(:class => the_class, :title => the_title))
  33 + link_to('&nbsp;'.html_safe+content_tag('span', label), url, html_options.merge(:class => the_class, :title => the_title))
21 34 end
22 35 end
23 36  
... ...
app/helpers/catalog_helper.rb
... ... @@ -1,45 +0,0 @@
1   -module CatalogHelper
2   -
3   - include DisplayHelper
4   - include ManageProductsHelper
5   -
6   - def catalog_load_index options = {:page => params[:page], :show_categories => true}
7   - if options[:show_categories]
8   - @category = params[:level] ? ProductCategory.find(params[:level]) : nil
9   - @categories = ProductCategory.on_level(params[:level]).order(:name)
10   - end
11   -
12   - @products = profile.products.from_category(@category).
13   - reorder('available desc, highlighted desc, name asc').
14   - paginate(:per_page => @profile.products_per_catalog_page, :page => options[:page])
15   - end
16   -
17   - def breadcrumb(category)
18   - start = link_to(_('Start'), {:controller => :catalog, :action => 'index'})
19   - ancestors = category.ancestors.map { |c| link_to(c.name, {:controller => :catalog, :action => 'index', :level => c.id}) }.reverse
20   - current_level = content_tag('strong', category.name)
21   - all_items = [start] + ancestors + [current_level]
22   - content_tag('div', all_items.join(' &rarr; '), :id => 'breadcrumb')
23   - end
24   -
25   - def category_link(category)
26   - count = profile.products.from_category(category).count
27   - name = truncate(category.name, :length => 22 - count.to_s.size)
28   - link = link_to(name, {:controller => 'catalog', :action => 'index', :level => category.id}, :title => category.name)
29   - content_tag('div', "#{link} <span class=\"count\">#{count}</span>") if count > 0
30   - end
31   -
32   - def category_with_sub_list(category)
33   - content_tag 'li', "#{category_link(category)}\n#{sub_category_list(category)}"
34   - end
35   -
36   - def sub_category_list(category)
37   - sub_categories = []
38   - category.children.order(:name).each do |sub_category|
39   - cat_link = category_link sub_category
40   - sub_categories << content_tag('li', cat_link) unless cat_link.nil?
41   - end
42   - content_tag('ul', sub_categories.join) if sub_categories.size > 0
43   - end
44   -
45   -end
app/helpers/categories_helper.rb
... ... @@ -2,7 +2,6 @@ module CategoriesHelper
2 2  
3 3 TYPES = [
4 4 [ _('General Category'), Category.to_s ],
5   - [ _('Product Category'), ProductCategory.to_s ],
6 5 [ _('Region'), Region.to_s ],
7 6 ]
8 7  
... ... @@ -20,7 +19,7 @@ module CategoriesHelper
20 19 def selected_category_link(cat)
21 20 js_remove = "jQuery('#selected-category-#{cat.id}').remove();"
22 21 content_tag('div', button_to_function_without_text(:remove, _('Remove'), js_remove) +
23   - link_to_function(cat.full_name(' &rarr; '), js_remove, :id => "remove-selected-category-#{cat.id}-button", :class => 'select-subcategory-link'),
  22 + link_to_function(cat.full_name(' &rarr; ').html_safe, js_remove, :id => "remove-selected-category-#{cat.id}-button", :class => 'select-subcategory-link'),
24 23 :class => 'selected-category'
25 24 )
26 25 end
... ...
app/helpers/content_viewer_helper.rb
... ... @@ -7,7 +7,8 @@ module ContentViewerHelper
7 7 def display_number_of_comments(n)
8 8 base_str = "<span class='comment-count hide'>#{n}</span>"
9 9 amount_str = n == 0 ? _('no comments yet') : (n == 1 ? _('One comment') : _('%s comments') % n)
10   - base_str + "<span class='comment-count-write-out'>#{amount_str}</span>"
  10 + base_str += "<span class='comment-count-write-out'>#{amount_str}</span>"
  11 + base_str.html_safe
11 12 end
12 13  
13 14 def number_of_comments(article)
... ... @@ -19,16 +20,16 @@ module ContentViewerHelper
19 20 title = content_tag('h1', h(title), :class => 'title')
20 21 if article.belongs_to_blog? || article.belongs_to_forum?
21 22 unless args[:no_link]
22   - title = content_tag('h1', link_to(article.name, article.url), :class => 'title')
  23 + title = content_tag('h1', link_to(article.name, url_for(article.url)), :class => 'title')
23 24 end
24 25 comments = ''
25 26 unless args[:no_comments] || !article.accept_comments
26   - comments = (" - %s") % link_to_comments(article)
  27 + comments = (" - %s").html_safe % link_to_comments(article)
27 28 end
28 29 date_format = show_with_right_format_date article
29 30 title << content_tag('span',
30 31 date_format +
31   - content_tag('span', _(", by %s") % (article.author ? link_to(article.author_name, article.author_url) : article.author_name), :class => 'author') +
  32 + content_tag('span', _(", by %s").html_safe % (article.author ? link_to(article.author_name, article.author_url) : article.author_name), :class => 'author') +
32 33 content_tag('span', comments, :class => 'comments'),
33 34 :class => 'publishing-info'
34 35 )
... ...
app/helpers/custom_fields_helper.rb
... ... @@ -61,6 +61,6 @@ module CustomFieldsHelper
61 61  
62 62 def form_for_format(customized_type, format)
63 63 field = CustomField.new(:format => format, :customized_type => customized_type, :environment => environment)
64   - CGI::escapeHTML((render(:partial => 'features/custom_fields/form', :locals => {:field => field})))
  64 + CGI::escapeHTML((render(:partial => 'features/custom_fields/form', :locals => {:field => field}))).html_safe
65 65 end
66 66 end
... ...
app/helpers/display_helper.rb
1 1 module DisplayHelper
2 2  
3   - def link_to_product(product, opts={})
4   - return _('No product') unless product
5   - target = product_path(product)
6   - link_to content_tag( 'span', product.name ),
7   - target,
8   - opts
9   - end
10   -
11 3 def themed_path(file)
12 4 if File.exists?(File.join(Rails.root, 'public', theme_path, file))
13 5 File.join(theme_path, file)
... ... @@ -16,55 +8,35 @@ module DisplayHelper
16 8 end
17 9 end
18 10  
19   - def image_link_to_product(product, opts={})
20   - return _('No product') unless product
21   - target = product_path(product)
22   - link_to image_tag(product.default_image(:big), :alt => product.name),
23   - target,
24   - opts
25   - end
26   -
27 11 def price_span(price, options = {})
28 12 content_tag 'span',
29 13 number_to_currency(price, :unit => environment.currency_unit, :delimiter => environment.currency_delimiter, :separator => environment.currency_separator),
30 14 options
31 15 end
32 16  
33   - def product_path(product)
34   - product.enterprise.enabled? ? product.enterprise.public_profile_url.merge(:controller => 'manage_products', :action => 'show', :id => product) : product.enterprise.url
35   - end
36   -
37 17 def link_to_tag(tag, html_options = {})
38 18 link_to tag.name, {:controller => 'search', :action => 'tag', :tag => tag.name}, html_options
39 19 end
40 20  
41 21 def link_to_category(category, full = true, html_options = {})
42   - return _('Uncategorized product') unless category
43 22 name = full ? category.full_name(' &rarr; ') : category.name
44 23 link_to name, Noosfero.url_options.merge({:controller => 'search', :action => 'category_index', :category_path => category.path.split('/'),:host => category.environment.default_hostname }), html_options
45 24 end
46 25  
47   - def link_to_product_category(category)
48   - if category
49   - link_to(category.name, :controller => 'search', :action => 'products', :category_path => category.explode_path)
50   - else
51   - _('Uncategorized product')
52   - end
53   - end
54   -
55 26 def txt2html(txt)
56   - txt.strip.
  27 + ret = txt.strip.
57 28 gsub( /\s*\n\s*\n\s*/, "\r<p/>\r" ).
58 29 gsub( /\s*\n\s*/, "\n<br/>\n" ).
59 30 gsub( /\r/, "\n" ).
60 31 gsub( /(^|\s)(www\.[^\s]+|https?:\/\/[^\s]+)/ ) do
61 32 pre_char, href = $1, $2
62 33 href = 'http://'+href if ! href.match /^https?:/
63   - content = href.gsub(/^https?:\/\//, '').scan(/.{1,4}/).join('&#x200B;')
  34 + content = safe_join(href.gsub(/^https?:\/\//, '').scan(/.{1,4}/), '&#x200B;'.html_safe)
64 35 pre_char +
65 36 content_tag(:a, content, :href => href, :target => '_blank',
66   - :rel => 'nofolow', :onclick => "return confirm('%s')" %
  37 + :rel => 'nofolow', :onclick => "return confirm('%s')".html_safe %
67 38 _('Are you sure you want to visit this web site?'))
68 39 end
  40 + ret.html_safe
69 41 end
70 42 end
... ...
app/helpers/enterprise_homepage_helper.rb
... ... @@ -1,25 +0,0 @@
1   -module EnterpriseHomepageHelper
2   -
3   - def display_profile_info(profile)
4   - data = ''
5   - [
6   - [ _('Contact person:'), :contact_person ],
7   - [ _('e-Mail:'), :contact_email ],
8   - [ _('Phone(s):'), :contact_phone ],
9   - [ _('Location:'), :location ],
10   - [ _('Address:'), :address ],
11   - [ _('Economic activity:'), :economic_activity ]
12   - ].each { | name, att |
13   - if profile.send( att ) and not profile.send( att ).blank?
14   - data << content_tag( 'li', content_tag('strong', name) +' '+ profile.send( att ).to_s ) +"\n"
15   - end
16   - }
17   - if profile.respond_to?(:distance) and !profile.distance.nil?
18   - data << content_tag( 'li',
19   - content_tag('strong',_('Distance:')) +' '+
20   - "%.2f%" % profile.distance
21   - ) + "\n"
22   - end
23   - content_tag('div', content_tag('ul', data), :class => 'enterprise-info')
24   - end
25   -end
app/helpers/events_helper.rb
1 1 module EventsHelper
2 2  
3 3 include DatesHelper
  4 + include ActionView::Helpers::OutputSafetyHelper
  5 +
4 6 def list_events(date, events)
5 7 title = _('Events for %s') % show_date_month(date)
  8 + user_events = events.select { |item| item.display_to?(user) }
  9 + events_for_month = safe_join(user_events.map {|item| display_event_in_listing(item)}, '')
6 10 content_tag('h2', title) +
7 11 content_tag('div',
8 12 (events.any? ?
9   - content_tag('table', events.select { |item| item.display_to?(user) }.map {|item| display_event_in_listing(item)}.join('')) :
10   - content_tag('em', _('No events for this month'), :class => 'no-events')
  13 + content_tag('table', events_for_month) :
  14 + content_tag('em', _('No events for this month'), :class => 'no-events')
11 15 ), :id => 'agenda-items'
12 16 )
13 17 end
... ...
app/helpers/forms_helper.rb
... ... @@ -101,7 +101,7 @@ module FormsHelper
101 101  
102 102 def required_fields_message
103 103 content_tag('p', content_tag('span',
104   - _("The <label class='pseudoformlabel'>highlighted</label> fields are mandatory."),
  104 + _("The <label class='pseudoformlabel'>highlighted</label> fields are mandatory.").html_safe,
105 105 :class => 'required-field'
106 106 ))
107 107 end
... ... @@ -112,10 +112,11 @@ module FormsHelper
112 112 options_for_select = container.inject([]) do |options, element|
113 113 text, value = option_text_and_value(element)
114 114 selected_attribute = ' selected="selected"' if option_value_selected?(value, selected)
115   - options << %(<option title="#{html_escape(text.to_s)}" value="#{html_escape(value.to_s)}"#{selected_attribute}>#{html_escape(text.to_s)}</option>)
  115 + opt = %(<option title="#{html_escape(text.to_s)}" value="#{html_escape(value.to_s)}"#{selected_attribute}>#{html_escape(text.to_s)}</option>)
  116 + options << opt.html_safe
116 117 end
117 118  
118   - options_for_select.join("\n")
  119 + safe_join(options_for_select, "\n")
119 120 end
120 121  
121 122 def balanced_table(items, per_row=3)
... ... @@ -248,8 +249,8 @@ module FormsHelper
248 249 def date_range_field(from_name, to_name, from_value, to_value, datepicker_options = {}, html_options = {})
249 250 from_id = html_options[:from_id] || 'datepicker-from-date'
250 251 to_id = html_options[:to_id] || 'datepicker-to-date'
251   - return _('From') +' '+ date_field(from_name, from_value, datepicker_options, html_options.merge({:id => from_id})) +
252   - ' ' + _('until') +' '+ date_field(to_name, to_value, datepicker_options, html_options.merge({:id => to_id}))
  252 + return (_('From') +' '+ date_field(from_name, from_value, datepicker_options, html_options.merge({:id => from_id})) +
  253 + ' ' + _('until') +' '+ date_field(to_name, to_value, datepicker_options, html_options.merge({:id => to_id}))).html_safe
253 254 end
254 255  
255 256 def select_folder(label_text, field_id, collection, default_value=nil, html_options = {}, js_options = {})
... ...
app/helpers/forum_helper.rb
... ... @@ -35,7 +35,7 @@ module ForumHelper
35 35 :id => "post-#{art.id}"
36 36 )
37 37 }
38   - content_tag('table', content.join) + (pagination or '')
  38 + content_tag('table', safe_join(content, "")) + (pagination or '').html_safe
39 39 end
40 40  
41 41 def last_topic_update(article)
... ...
app/helpers/language_helper.rb
... ... @@ -40,7 +40,7 @@ module LanguageHelper
40 40 else
41 41 link_to(name, params.merge(:lang => code), :rel => 'nofollow')
42 42 end
43   - end.join(separator)
  43 + end.join(separator).html_safe
44 44 content_tag('div', languages, :id => 'language-chooser', :help => _('The language you choose here is the language used for options, buttons, etc. It does not affect the language of the content created by other users.'))
45 45 end
46 46 end
... ...
app/helpers/layout_helper.rb
... ... @@ -40,7 +40,8 @@ module LayoutHelper
40 40  
41 41 output += templete_javascript_ng.to_s
42 42  
43   - output
  43 + # This output should be safe!
  44 + output.html_safe
44 45 end
45 46  
46 47 def noosfero_stylesheets
... ... @@ -64,7 +65,9 @@ module LayoutHelper
64 65 output << stylesheet_link_tag(global_css_pub)
65 66 end
66 67 output << stylesheet_link_tag(theme_stylesheet_path)
67   - output.join "\n"
  68 +
  69 + # This output should be safe!
  70 + output.join("\n").html_safe
68 71 end
69 72  
70 73 def noosfero_layout_features
... ... @@ -94,9 +97,7 @@ module LayoutHelper
94 97 end
95 98  
96 99 def addthis_javascript
97   - if NOOSFERO_CONF['addthis_enabled']
98   - '<script src="https://s7.addthis.com/js/152/addthis_widget.js"></script>'
99   - end
  100 + NOOSFERO_CONF['addthis_enabled'] ? '<script src="https://s7.addthis.com/js/152/addthis_widget.js"></script>' : ''
100 101 end
101 102  
102 103 end
... ...
app/helpers/macros_helper.rb
... ... @@ -32,7 +32,7 @@ module MacrosHelper
32 32 }
33 33 });
34 34 }"
35   - end
  35 + end.html_safe
36 36 end
37 37  
38 38 def include_macro_js_files
... ...
app/helpers/manage_products_helper.rb
... ... @@ -1,285 +0,0 @@
1   -# encoding: UTF-8
2   -
3   -module ManageProductsHelper
4   -
5   - def remote_function_to_update_categories_selection(container_id, options = {})
6   - remote_function({
7   - :update => container_id,
8   - :url => { :action => "categories_for_selection" },
9   - :loading => "loading('hierarchy_navigation', '#{ _('loading…') }'); loading('#{container_id}', '&nbsp;')",
10   - :complete => "loading_done('hierarchy_navigation'); loading_done('#{container_id}')"
11   - }.merge(options))
12   - end
13   -
14   - def hierarchy_category_item(category, make_links, title = nil)
15   - title ||= category.name
16   - if make_links
17   - link_to(title, '#',
18   - :title => title,
19   - :onclick => remote_function_to_update_categories_selection("categories_container_level#{ category.level + 1 }",
20   - :with => "'category_id=#{ category.id }'"
21   - )
22   - )
23   - else
24   - title
25   - end
26   - end
27   -
28   - def hierarchy_category_navigation(current_category, options = {})
29   - hierarchy = []
30   - if current_category
31   - hierarchy << current_category.name unless options[:hide_current_category]
32   - ancestors = current_category.ancestors
33   - ancestors.each do |category|
34   - hierarchy << hierarchy_category_item(category, options[:make_links])
35   - end
36   - end
37   - hierarchy.reverse.join(options[:separator] || ' &rarr; ')
38   - end
39   -
40   - def options_for_select_categories(categories, selected = nil)
41   - categories.sort_by{|cat| cat.name.transliterate}.map do |category|
42   - selected_attribute = selected.nil? ? '' : (category == selected ? "selected='selected'" : '')
43   - "<option value='#{category.id}' title='#{category.name}' #{selected_attribute}>#{category.name + (category.leaf? ? '': ' &raquo;')}</option>"
44   - end.join("\n")
45   - end
46   -
47   - def build_selects_for_ancestors(ancestors, current_category)
48   - current_ancestor = ancestors.shift
49   - if current_ancestor.nil?
50   - select_for_new_category(current_category.children, current_category.level + 1)
51   - else
52   - content_tag('div',
53   - select_tag('category_id',
54   - options_for_select_categories(current_ancestor.siblings + [current_ancestor], current_ancestor),
55   - :size => 10,
56   - :onchange => remote_function_to_update_categories_selection("categories_container_level#{ current_ancestor.level + 1 }", :with => "'category_id=' + this.value")
57   - ) +
58   - build_selects_for_ancestors(ancestors, current_category),
59   - :class => 'categories_container',
60   - :id => "categories_container_level#{ current_ancestor.level }"
61   - )
62   - end
63   - end
64   -
65   - def selects_for_all_ancestors(current_category)
66   - build_selects_for_ancestors(current_category.ancestors.reverse + [current_category], current_category)
67   - end
68   -
69   - def select_for_new_category(categories, level)
70   - content_tag('div',
71   - render(:partial => 'categories_for_selection', :locals => { :categories => categories, :level => level }),
72   - :class => 'categories_container',
73   - :id => "categories_container_level#{ level }"
74   - )
75   - end
76   -
77   - def categories_container(categories_selection_html, hierarchy_html = '')
78   - content_tag 'div',
79   - render('categories_autocomplete') +
80   - hidden_field_tag('selected_category_id') +
81   - content_tag('div', hierarchy_html, :id => 'hierarchy_navigation') +
82   - content_tag('div', categories_selection_html, :id => 'categories_container_wrapper'),
83   - :id => 'categories-container'
84   - end
85   -
86   - def select_for_categories(categories, level = 0)
87   - if categories.empty?
88   - content_tag('div', '', :id => 'no_subcategories')
89   - else
90   - select_tag('category_id',
91   - options_for_select_categories(categories),
92   - :size => 10,
93   - :onchange => remote_function_to_update_categories_selection("categories_container_level#{ level + 1 }", :with => "'category_id=' + this.value")
94   - ) +
95   - content_tag('div', '', :class => 'categories_container', :id => "categories_container_level#{ level + 1 }")
96   - end
97   - end
98   -
99   - def edit_link(label, url, html_options = {})
100   - return '' unless (user && user.has_permission?('manage_products', profile))
101   - link_to(label, url, html_options)
102   - end
103   -
104   - def edit_product_link_to_remote(product, field, label, html_options = {})
105   - return '' unless (user && user.has_permission?('manage_products', profile))
106   - options = html_options.merge(:id => 'link-edit-product-' + field)
107   - options[:class] = options[:class] ? options[:class] + ' link-to-remote' : 'link-to-remote'
108   -
109   - link_to_remote(label,
110   - {:update => "product-#{field}",
111   - :url => { :controller => 'manage_products', :action => "edit", :id => product.id, :field => field },
112   - :method => :get,
113   - :loading => "loading_for_button('#link-edit-product-#{field}')"},
114   - options)
115   - end
116   -
117   - def edit_button(type, label, url, html_options = {})
118   - return '' unless (user && user.has_permission?('manage_products', profile))
119   - button(type, label, url, html_options)
120   - end
121   -
122   - def edit_product_button_to_remote(product, field, label, html_options = {})
123   - the_class = 'button with-text icon-edit'
124   - if html_options.has_key?(:class)
125   - the_class << ' ' << html_options[:class]
126   - end
127   - edit_product_link_to_remote(product, field, label, html_options.merge(:class => the_class))
128   - end
129   -
130   - def edit_ui_button(label, url, html_options = {})
131   - return '' unless (user && user.has_permission?('manage_products', profile))
132   - ui_button(label, url, html_options)
133   - end
134   -
135   - def edit_product_ui_button_to_remote(product, field, label, html_options = {})
136   - return '' unless (user && user.has_permission?('manage_products', profile))
137   - id = 'edit-product-remote-button-ui-' + field
138   - options = html_options.merge(:id => id)
139   -
140   - ui_button_to_remote(label,
141   - {:update => "product-#{field}",
142   - :url => { :controller => 'manage_products', :action => "edit", :id => product.id, :field => field },
143   - :complete => "jQuery('#edit-product-button-ui-#{field}').hide()",
144   - :method => :get,
145   - :loading => "loading_for_button('##{id}')"},
146   - options)
147   - end
148   -
149   - def cancel_edit_product_link(product, field, html_options = {})
150   - return '' unless (user && user.has_permission?('manage_products', profile))
151   - button_to_function(:cancel, _('Cancel'), nil, html_options) do |page|
152   - page.replace_html "product-#{field}", CGI::escapeHTML(render :partial => "display_#{field}", :locals => {:product => product})
153   - end
154   - end
155   -
156   - def edit_product_category_link(product, html_options = {})
157   - return '' unless (user && user.has_permission?('manage_products', profile))
158   - options = html_options.merge(:id => 'link-edit-product-category')
159   - link_to(_('Change category'), { :action => 'edit_category', :id => product.id}, options)
160   - end
161   -
162   - def display_value(product)
163   - price = product.price
164   - return '' if price.blank? || price.zero?
165   - discount = product.discount
166   - if discount.blank? || discount.zero?
167   - result = display_price(_('Price: '), price)
168   - else
169   - result = display_price_with_discount(price, product.price_with_discount)
170   - end
171   - content_tag('span', content_tag('span', result, :class => 'product-price'), :class => "#{product.available? ? '' : 'un'}available-product")
172   - end
173   -
174   - def display_availability(product)
175   - if !product.available?
176   - ui_highlight(_('Product not available!'))
177   - end
178   - end
179   -
180   - def display_price(label, price)
181   - content_tag('span', label, :class => 'field-name') +
182   - content_tag('span', float_to_currency(price), :class => 'field-value')
183   - end
184   -
185   - def display_price_with_discount(price, price_with_discount)
186   - original_value = content_tag('span', display_price(_('List price: '), price), :class => 'list-price')
187   - discount_value = content_tag('span', display_price(_('On sale: '), price_with_discount), :class => 'on-sale-price')
188   - original_value + tag('br') + discount_value
189   - end
190   -
191   - def display_qualifiers(product)
192   - data = ''
193   - product.product_qualifiers.each do |pq|
194   - certified_by = ''
195   - certifier = pq.certifier
196   - if certifier
197   - certifier_name = certifier.link.blank? ? certifier.name : link_to(certifier.name, certifier.link)
198   - certified_by = _('certified by %s') % certifier_name
199   - else
200   - certified_by = _('(Self declared)')
201   - end
202   - data << content_tag('li', "✔ #{pq.qualifier.name} #{certified_by}", :class => 'product-qualifiers-item')
203   - end
204   - content_tag('ul', data, :id => 'product-qualifiers')
205   - end
206   -
207   - def qualifiers_for_select
208   - [[_('Select...'), nil]] + environment.qualifiers.sort.map{ |c| [c.name, c.id] }
209   - end
210   - def certifiers_for_select(qualifier)
211   - [[_('Self declared'), nil]] + qualifier.certifiers.sort.map{ |c| [c.name, c.id] }
212   - end
213   - def select_qualifiers(product, selected = nil)
214   - select_tag('selected_qualifier', options_for_select(qualifiers_for_select, selected),
215   - :onchange => remote_function(
216   - :url => {:action => 'certifiers_for_selection'},
217   - :with => "'id=' + value + '&certifier_area=' + jQuery(this).parent().next().attr('id')",
218   - :before => "small_loading(jQuery(this).parent().next().attr('id'), '&nbsp;')"
219   - ),
220   - :id => nil
221   - )
222   - end
223   - def select_certifiers(qualifier, product = nil)
224   - if qualifier
225   - selected = product ? product.product_qualifiers.find_by(qualifier_id: qualifier.id).certifier_id : nil
226   - select_tag("product[qualifiers_list][#{qualifier.id}]", options_for_select(certifiers_for_select(qualifier), selected))
227   - else
228   - select_tag("product[qualifiers_list][nil]")
229   - end
230   - end
231   -
232   - def remove_qualifier_button
233   - button_to_function(:delete, content_tag('span', _('Delete qualifier')), "jQuery(this).parents('tr').remove()")
234   - end
235   -
236   - def select_unit(object)
237   - collection_select(object.class.name.downcase, :unit_id, environment.units, :id, :singular, {:include_blank => _('Select the unit')})
238   - end
239   -
240   - def input_icon(input)
241   - if input.is_from_solidarity_economy?
242   - hint = _('Product from solidarity economy')
243   - image_tag("/images/solidarity-economy.png", :class => 'solidatiry-economy-icon', :alt => hint, :title => hint)
244   - end
245   - end
246   -
247   - def display_price_by(unit)
248   - selected_unit = content_tag('span', unit, :class => 'selected-unit')
249   - content_tag('span', _('by') + ' ' + selected_unit, :class => 'price-by-unit')
250   - end
251   -
252   - def label_amount_used(input)
253   - product_unit = input.product.unit
254   - if product_unit.blank?
255   - _('Amount used in this product or service')
256   - else
257   - _('Amount used by %s of this product or service') % product_unit.singular.downcase
258   - end
259   - end
260   -
261   - def display_unit(input)
262   - input_amount_used = content_tag('span', input.formatted_amount, :class => 'input-amount-used')
263   - return input_amount_used if input.unit.blank?
264   - n_('1 %{singular_unit}', '%{num} %{plural_unit}', input.amount_used.to_f) % { :num => input_amount_used, :singular_unit => content_tag('span', input.unit.singular, :class => 'input-unit'), :plural_unit => content_tag('span', input.unit.plural, :class => 'input-unit') }
265   - end
266   -
267   - def select_production_cost(product,selected=nil)
268   - url = url_for( :controller => 'manage_products', :action => 'create_production_cost' )
269   - prompt_msg = _('Insert the name of the new cost:')
270   - error_msg = _('Something went wrong. Please, try again')
271   - select_tag('price_details[][production_cost_id]',
272   - '<option value="" disabled="disabled">' + _('Select...') + '</option>' +
273   - options_for_select(product.available_production_costs.map {|item| [truncate(item.name, {:length => 10, :omission => '...'}), item.id]} + [[_('Other cost'), '']], selected),
274   - {:class => 'production-cost-selection',
275   - :onchange => "productionCostTypeChange(this, '#{url}', '#{prompt_msg}', '#{error_msg}')"})
276   - end
277   -
278   - def price_composition_progressbar_text(product, args = {})
279   - currency = environment.currency_unit
280   - production_cost = args[:production_cost_value] || product.formatted_value(:total_production_cost)
281   - product_price = args[:product_price] || product.formatted_value(:price)
282   -
283   - _("%{currency} %{production_cost} of %{currency} %{product_price}") % {:currency => currency, :production_cost => content_tag('span', production_cost, :class => 'production_cost'), :product_price => content_tag('span', product_price, :class => 'product_price')}
284   - end
285   -end
app/helpers/memberships_helper.rb
1 1 module MembershipsHelper
  2 +
  3 + def join_community_button options={:logged => false}
  4 + url = options[:logged] ? profile.join_url : profile.join_not_logged_url
  5 +
  6 + if show_confirmation_modal? profile
  7 + modal_button :add, _('Join this community'), url, class: 'join-community'
  8 + else
  9 + button :add, _('Join this community'), url, class: 'join-community'
  10 + end
  11 + end
  12 +
  13 + def show_confirmation_modal? profile
  14 + profile.requires_email? && current_person && !current_person.public_fields.include?("email")
  15 + end
  16 +
2 17 end
... ...
app/helpers/partials_helper.rb
... ... @@ -29,14 +29,49 @@ module PartialsHelper
29 29 raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?'
30 30 end
31 31  
32   - def render_partial_for_class klass, *args
  32 +
  33 + def partial_for_class(klass, prefix=nil, suffix=nil)
33 34 raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' if klass.nil?
  35 + name = klass.name.underscore
  36 + controller.view_paths.each do |view_path|
  37 + partial = partial_for_class_in_view_path(klass, view_path, prefix, suffix)
  38 + return partial if partial
  39 + end
  40 +
  41 + raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?'
  42 + end
  43 +
  44 + ##
  45 + # Calculate partial name with prefix and suffix
  46 + # Togheter with render_partial_for_class,
  47 + # it should replace #partial_for_class_in_view_path in the future
  48 + #
  49 + def partial_name_for underscore, prefix = nil, suffix = nil
  50 + parts = underscore.split '/'
  51 + if prefix or suffix
  52 + partial = [prefix, parts.last, suffix].compact.map(&:to_s).join '_'
  53 + else
  54 + partial = parts.last
  55 + end
  56 + if parts.size > 1
  57 + "#{params[:controller]}/#{parts.first}/#{partial}"
  58 + else
  59 + partial
  60 + end
  61 + end
  62 +
  63 + def render_for_class klass, *args, &block
  64 + raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' unless klass
34 65 begin
35   - partial = klass.name.underscore
36   - partial = "#{params[:controller]}/#{partial}" if params[:controller] and partial.index '/'
37   - return render partial, *args
  66 + capture klass, &block
38 67 rescue ActionView::MissingTemplate
39   - return render_partial_for_class klass.superclass, *args
  68 + render_for_class klass.superclass, *args, &block
  69 + end
  70 + end
  71 +
  72 + def render_partial_for_class klass, *args
  73 + render_for_class klass do |klass|
  74 + render partial_name_for(klass.name.underscore), *args
40 75 end
41 76 end
42 77  
... ...
app/helpers/profile_editor_helper.rb
... ... @@ -129,7 +129,11 @@ module ProfileEditorHelper
129 129 else
130 130 domains = environment.domains
131 131 end
132   - labelled_form_field(_('Preferred domain name:'), select(object, :preferred_domain_id, domains.map {|item| [item.name, item.id]}, :prompt => '&lt;' + _('Select domain') + '&gt;'))
  132 + select_domain_prompt = '&lt;'.html_safe + _('Select domain').html_safe + '&gt;'.html_safe
  133 + select_field = select(object, :preferred_domain_id, domains.map {
  134 + |item| [item.name, item.id]}, :prompt => select_domain_prompt.html_safe)
  135 +
  136 + labelled_form_field(_('Preferred domain name:'), select_field)
133 137 end
134 138  
135 139 def control_panel(&block)
... ...
app/helpers/profile_helper.rb
... ... @@ -84,7 +84,7 @@ module ProfileHelper
84 84 entries.map do |entry|
85 85 content = self.send("treat_#{field}", entry)
86 86 unless content.blank?
87   - content_tag('tr', content_tag('td', title(field, entry), :class => 'field-name') + content_tag('td', content))
  87 + content_tag('tr', content_tag('td', title(field, entry), :class => 'field-name') + content_tag('td', content.to_s.html_safe))
88 88 end
89 89 end.join("\n")
90 90 end
... ...
app/helpers/profile_image_helper.rb
... ... @@ -61,6 +61,8 @@ module ProfileImageHelper
61 61 image_tag(profile_icon(profile, size), opt )
62 62 end
63 63  
  64 + include MembershipsHelper
  65 +
64 66 def links_for_balloon(profile)
65 67 if environment.enabled?(:show_balloon_with_profile_links_when_clicked)
66 68 if profile.kind_of?(Person)
... ... @@ -76,13 +78,12 @@ module ProfileImageHelper
76 78 {_('Wall') => {:href => url_for(profile.public_profile_url)}},
77 79 {_('Members') => {:href => url_for(:controller => :profile, :action => :members, :profile => profile.identifier)}},
78 80 {_('Agenda') => {:href => url_for(:controller => :profile, :action => :events, :profile => profile.identifier)}},
79   - {_('Join') => {:href => url_for(profile.join_url), :class => 'join-community', :style => 'display: none'}},
  81 + {_('Join') => {:href => url_for(profile.join_url), :class => 'join-community'+ (show_confirmation_modal?(profile) ? ' modal-toggle' : '') , :style => 'display: none'}},
80 82 {_('Leave community') => {:href => url_for(profile.leave_url), :class => 'leave-community', :style => 'display: none'}},
81 83 {_('Send an e-mail') => {:href => url_for(:profile => profile.identifier, :controller => 'contact', :action => 'new'), :class => 'send-an-email', :style => 'display: none'}}
82 84 ]
83 85 elsif profile.kind_of?(Enterprise)
84 86 [
85   - {_('Products') => {:href => catalog_path(profile.identifier)}},
86 87 {_('Members') => {:href => url_for(:controller => :profile, :action => :members, :profile => profile.identifier)}},
87 88 {_('Agenda') => {:href => url_for(:controller => :profile, :action => :events, :profile => profile.identifier)}},
88 89 {_('Send an e-mail') => {:href => url_for(:profile => profile.identifier, :controller => 'contact', :action => 'new'), :class => 'send-an-email', :style => 'display: none'}},
... ... @@ -131,7 +132,7 @@ module ProfileImageHelper
131 132 links = links_for_balloon(profile)
132 133 content_tag('div', content_tag(tag,
133 134 (environment.enabled?(:show_balloon_with_profile_links_when_clicked) ?
134   - popover_menu(_('Profile links'),profile.short_name,links,{:class => trigger_class, :url => url}) : "") +
  135 + popover_menu(_('Profile links'),profile.short_name,links,{:class => trigger_class, :url => url}) : "").html_safe +
135 136 link_to(
136 137 content_tag( 'span', profile_image( profile, size ), :class => img_class ) +
137 138 content_tag( 'span', h(name), :class => ( profile.class == Person ? 'fn' : 'org' ) ) +
... ... @@ -139,7 +140,7 @@ module ProfileImageHelper
139 140 profile.url,
140 141 :class => 'profile_link url',
141 142 :help => _('Click on this icon to go to the <b>%s</b>\'s home page') % profile.name,
142   - :title => profile.name ),
  143 + :title => profile.name ).html_safe,
143 144 :class => 'vcard'), :class => 'common-profile-list-block')
144 145 end
145 146 end
... ...
app/helpers/search_helper.rb
... ... @@ -124,10 +124,10 @@ module SearchHelper
124 124 def filters(asset)
125 125 return if !asset
126 126 klass = asset_class(asset)
127   - content_tag('div', klass::SEARCH_FILTERS.map do |name, options|
  127 + content_tag('div', safe_join(klass::SEARCH_FILTERS.map do |name, options|
128 128 default = klass.respond_to?("default_search_#{name}") ? klass.send("default_search_#{name}".to_s) : nil
129 129 select_filter(name, options, default)
130   - end.join("\n"), :id => 'search-filters')
  130 + end, "\n"), :id => 'search-filters')
131 131 end
132 132  
133 133 def assets_menu(selected)
... ... @@ -137,11 +137,11 @@ module SearchHelper
137 137 # menu.
138 138 assets.delete(:events)
139 139 content_tag('ul',
140   - assets.map do |asset|
  140 + safe_join(assets.map do |asset|
141 141 options = {}
142 142 options.merge!(:class => 'selected') if selected.to_s == asset.to_s
143 143 content_tag('li', asset_link(asset), options)
144   - end.join("\n"),
  144 + end, "\n"),
145 145 :id => 'assets-menu')
146 146 end
147 147  
... ...
app/helpers/tags_helper.rb
... ... @@ -58,7 +58,7 @@ module TagsHelper
58 58  
59 59 if options[:show_count]
60 60 display_count = options[:show_count] ? "<small><sup>(#{count})</sup></small>" : ""
61   - link_to tag + display_count, destination, :style => style
  61 + link_to (tag + display_count).html_safe, destination, :style => style
62 62 else
63 63 link_to h(tag) , destination, :style => style,
64 64 :title => n_( 'one item', '%d items', count ) % count
... ...
app/helpers/theme_loader_helper.rb
... ... @@ -2,8 +2,8 @@ module ThemeLoaderHelper
2 2 def current_theme
3 3 @current_theme ||=
4 4 begin
5   - if session[:theme]
6   - session[:theme]
  5 + if session[:user_theme]
  6 + session[:user_theme]
7 7 else
8 8 # utility for developers: set the theme to 'random' in development mode and
9 9 # you will get a different theme every request. This is interesting for
... ... @@ -11,8 +11,8 @@ module ThemeLoaderHelper
11 11 if Rails.env.development? && environment.theme == 'random'
12 12 @random_theme ||= Dir.glob('public/designs/themes/*').map { |f| File.basename(f) }.rand
13 13 @random_theme
14   - elsif Rails.env.development? && respond_to?(:params) && params[:theme] && File.exists?(Rails.root.join('public/designs/themes', params[:theme]))
15   - params[:theme]
  14 + elsif Rails.env.development? && respond_to?(:params) && params[:user_theme] && File.exists?(Rails.root.join('public/designs/themes', params[:user_theme]))
  15 + params[:user_theme]
16 16 else
17 17 if profile && !profile.theme.nil?
18 18 profile.theme
... ... @@ -34,8 +34,10 @@ module ThemeLoaderHelper
34 34 end
35 35  
36 36 def theme_path
37   - if session[:theme]
  37 + if session[:user_theme]
38 38 '/user_themes/' + current_theme
  39 + elsif session[:theme]
  40 + '/designs/themes/' + session[:theme]
39 41 else
40 42 '/designs/themes/' + current_theme
41 43 end
... ...
app/helpers/tinymce_helper.rb
... ... @@ -7,7 +7,7 @@ module TinymceHelper
7 7 output += javascript_include_tag 'tinymce/js/tinymce/jquery.tinymce.min.js'
8 8 output += javascript_include_tag 'tinymce.js'
9 9 output += include_macro_js_files.to_s
10   - output
  10 + output.html_safe
11 11 end
12 12  
13 13 def tinymce_init_js options = {}
... ... @@ -37,7 +37,7 @@ module TinymceHelper
37 37 #cleanup non tinymce options
38 38 options = options.except :mode
39 39  
40   - "noosfero.tinymce.init(#{options.to_json})"
  40 + "noosfero.tinymce.init(#{options.to_json})".html_safe
41 41 end
42 42  
43 43 def menubar mode
... ...
app/helpers/url_helper.rb
... ... @@ -4,4 +4,12 @@ module UrlHelper
4 4 'javascript:history.back()'
5 5 end
6 6  
  7 + def default_url_options
  8 + options = {}
  9 +
  10 + options[:override_user] = params[:override_user] if params[:override_user].present?
  11 +
  12 + options
  13 + end
  14 +
7 15 end
... ...
app/helpers/users_helper.rb
1 1 module UsersHelper
2 2  
3   - FILTER_TRANSLATION = {
  3 + def filter_translation
  4 + {
4 5 'all_users' => _('All users'),
5 6 'admin_users' => _('Admin users'),
6 7 'activated_users' => _('Activated users'),
7 8 'deactivated_users' => _('Deativated users'),
8   - }
  9 + }
  10 + end
9 11  
10 12 def filter_selector(filter, float = 'right')
11   - options = options_for_select(FILTER_TRANSLATION.map {|key, name| [name, key]}, :selected => filter)
  13 + options = options_for_select(filter_translation.map {|key, name| [name, key]}, :selected => filter)
12 14 url_params = url_for(params.merge(:filter => 'FILTER'))
13 15 onchange = "document.location.href = '#{url_params}'.replace('FILTER', this.value)"
14 16 select_field = select_tag(:filter, options, :onchange => onchange)
... ... @@ -19,7 +21,7 @@ module UsersHelper
19 21 end
20 22  
21 23 def users_filter_title(filter)
22   - FILTER_TRANSLATION[filter]
  24 + filter_translation[filter]
23 25 end
24 26  
25 27 end
... ...
app/jobs/activities_counter_cache_job.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +class ActivitiesCounterCacheJob
  2 +
  3 + def perform
  4 + person_activities_counts = ApplicationRecord.connection.execute("SELECT profiles.id, count(action_tracker.id) as count FROM profiles LEFT OUTER JOIN action_tracker ON profiles.id = action_tracker.user_id WHERE (action_tracker.created_at >= #{ApplicationRecord.connection.quote(ActionTracker::Record::RECENT_DELAY.days.ago.to_s(:db))}) AND ( (profiles.type = 'Person' ) ) GROUP BY profiles.id;")
  5 + organization_activities_counts = ApplicationRecord.connection.execute("SELECT profiles.id, count(action_tracker.id) as count FROM profiles LEFT OUTER JOIN action_tracker ON profiles.id = action_tracker.target_id WHERE (action_tracker.created_at >= #{ApplicationRecord.connection.quote(ActionTracker::Record::RECENT_DELAY.days.ago.to_s(:db))}) AND ( (profiles.type = 'Community' OR profiles.type = 'Enterprise' OR profiles.type = 'Organization' ) ) GROUP BY profiles.id;")
  6 + activities_counts = person_activities_counts.entries + organization_activities_counts.entries
  7 + activities_counts.each do |count|
  8 + update_sql = ApplicationRecord.__send__(:sanitize_sql, ["UPDATE profiles SET activities_count=? WHERE profiles.id=?;", count['count'].to_i, count['id'] ], '')
  9 + ApplicationRecord.connection.execute(update_sql)
  10 + end
  11 + Delayed::Job.enqueue(ActivitiesCounterCacheJob.new, {:priority => -3, :run_at => 1.day.from_now})
  12 + end
  13 +
  14 +end
... ...
app/jobs/create_thumbnails_job.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +class CreateThumbnailsJob < Struct.new(:class_name, :file_id)
  2 + def perform
  3 + return unless class_name.constantize.exists?(file_id)
  4 + file = class_name.constantize.find(file_id)
  5 + file.create_thumbnails
  6 + article = Article.where(:image_id => file_id).first
  7 + if article
  8 + article.touch
  9 + end
  10 + end
  11 +end
... ...
app/jobs/download_reported_images_job.rb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +class DownloadReportedImagesJob < Struct.new(:abuse_report, :article)
  2 + def perform
  3 + images_paths = article.image? ? [File.join(article.profile.environment.top_url, article.public_filename(:display))] : article.body_images_paths
  4 + images_paths.each do |image_path|
  5 + image = get_image(image_path)
  6 + reported_image = ReportedImage.create!( :abuse_report => abuse_report,
  7 + :uploaded_data => image,
  8 + :filename => File.basename(image_path),
  9 + :size => image.size )
  10 + abuse_report.content = parse_content(abuse_report, image_path, reported_image)
  11 + end
  12 + abuse_report.save!
  13 + end
  14 +
  15 + def get_image(image_path)
  16 + image = ActionController::UploadedTempfile.new('reported_image')
  17 + image.write(Net::HTTP.get(URI.parse(image_path)))
  18 + image.original_path = 'tmp/' + File.basename(image_path)
  19 + image.content_type = 'image/' + File.extname(image_path).gsub('.','')
  20 + image
  21 + end
  22 +
  23 + def parse_content(report, old_path, image)
  24 + old_path = old_path.gsub(report.reporter.environment.top_url, '')
  25 + report.content.gsub(/#{old_path}/, image.public_filename)
  26 + end
  27 +end
... ...
app/jobs/get_email_contacts_job.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +class GetEmailContactsJob < Struct.new(:import_from, :login, :password, :contact_list_id)
  2 + def perform
  3 + begin
  4 + Invitation.get_contacts(import_from, login, password, contact_list_id)
  5 + rescue Contacts::AuthenticationError => ex
  6 + ContactList.exists?(contact_list_id) && ContactList.find(contact_list_id).register_auth_error
  7 + rescue Exception => ex
  8 + ContactList.exists?(contact_list_id) && ContactList.find(contact_list_id).register_error
  9 + end
  10 + end
  11 +end
... ...
app/jobs/invitation_job.rb 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +class InvitationJob < Struct.new(:person_id, :contacts_to_invite, :message, :profile_id, :contact_list_id, :locale)
  2 + def perform
  3 + Noosfero.with_locale(locale) do
  4 + person = Person.find(person_id)
  5 + profile = Profile.find(profile_id)
  6 + Invitation.invite(person, contacts_to_invite, message, profile)
  7 + ContactList.exists?(contact_list_id) && ContactList.find(contact_list_id).destroy
  8 + end
  9 + end
  10 +end
... ...
app/jobs/log_memory_consumption_job.rb 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +class LogMemoryConsumptionJob < Struct.new(:last_stat)
  2 + # Number of entries do display
  3 + N = 20
  4 +
  5 + def perform
  6 + logpath = File.join(Rails.root, 'log', "#{ENV['RAILS_ENV']}_memory_consumption.log")
  7 + logger = Logger.new(logpath)
  8 + stats = Hash.new(0)
  9 + ObjectSpace.each_object {|o| stats[o.class.to_s] += 1}
  10 + i = 1
  11 +
  12 + logger << "[#{Time.now.strftime('%F %T %z')}]\n"
  13 + stats.sort {|(k1,v1),(k2,v2)| v2 <=> v1}.each do |k,v|
  14 + logger << (sprintf "%-60s %10d", k, v)
  15 + logger << (sprintf " | delta %10d", (v - last_stat[k])) if last_stat && last_stat[k]
  16 + logger << "\n"
  17 + break if i > N
  18 + i += 1
  19 + end
  20 + logger << "\n"
  21 + end
  22 +end
... ...
app/jobs/mailing_job.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +class MailingJob < Struct.new(:mailing_id)
  2 + def perform
  3 + mailing = Mailing.find(mailing_id)
  4 + Noosfero.with_locale(mailing.locale) do
  5 + mailing.deliver
  6 + end
  7 + end
  8 +end
... ...
app/jobs/notify_activity_to_profiles_job.rb 0 → 100644
... ... @@ -0,0 +1,44 @@
  1 +class NotifyActivityToProfilesJob < Struct.new(:tracked_action_id)
  2 + NOTIFY_ONLY_COMMUNITY = [
  3 + 'add_member_in_community'
  4 + ]
  5 +
  6 + NOT_NOTIFY_COMMUNITY = [
  7 + 'join_community'
  8 + ]
  9 + def perform
  10 + return unless ActionTracker::Record.exists?(tracked_action_id)
  11 + tracked_action = ActionTracker::Record.find(tracked_action_id)
  12 + return unless tracked_action.user.present?
  13 + target = tracked_action.target
  14 + if target.is_a?(Community) && ( NOTIFY_ONLY_COMMUNITY.include?(tracked_action.verb) || ! target.public_profile )
  15 + ActionTrackerNotification.create(:profile_id => target.id, :action_tracker_id => tracked_action.id)
  16 + return
  17 + end
  18 +
  19 + # Notify the user
  20 + ActionTrackerNotification.create(:profile_id => tracked_action.user.id, :action_tracker_id => tracked_action.id)
  21 +
  22 + # Notify all friends
  23 + ActionTrackerNotification.connection.execute("insert into action_tracker_notifications(profile_id, action_tracker_id) select f.friend_id, #{tracked_action.id} from friendships as f where person_id=#{tracked_action.user.id} and f.friend_id not in (select atn.profile_id from action_tracker_notifications as atn where atn.action_tracker_id = #{tracked_action.id})")
  24 +
  25 + if tracked_action.user.is_a? Organization
  26 + ActionTrackerNotification.connection.execute "insert into action_tracker_notifications(profile_id, action_tracker_id) " +
  27 + "select distinct accessor_id, #{tracked_action.id} from role_assignments where resource_id = #{tracked_action.user.id} and resource_type='Profile' " +
  28 + if tracked_action.user.is_a? Enterprise then "union select distinct person_id, #{tracked_action.id} from favorite_enterprise_people where enterprise_id = #{tracked_action.user.id}" else "" end
  29 + end
  30 +
  31 + if target.is_a?(Community)
  32 + ActionTrackerNotification.create(:profile_id => target.id, :action_tracker_id => tracked_action.id) unless NOT_NOTIFY_COMMUNITY.include?(tracked_action.verb)
  33 +
  34 + ActionTrackerNotification.connection.execute("insert into action_tracker_notifications(profile_id, action_tracker_id) select distinct profiles.id, #{tracked_action.id} from role_assignments, profiles where profiles.type = 'Person' and profiles.id = role_assignments.accessor_id and profiles.id != #{tracked_action.user.id} and profiles.id not in (select atn.profile_id from action_tracker_notifications as atn where atn.action_tracker_id = #{tracked_action.id}) and role_assignments.resource_type = 'Profile' and role_assignments.resource_id = #{target.id}")
  35 + end
  36 +
  37 + if target.is_a?(Article) && target.profile.is_a?(Community)
  38 + ActionTrackerNotification.create(:profile_id => target.profile.id, :action_tracker_id => tracked_action.id) unless NOT_NOTIFY_COMMUNITY.include?(tracked_action.verb)
  39 +
  40 + ActionTrackerNotification.connection.execute("insert into action_tracker_notifications(profile_id, action_tracker_id) select distinct profiles.id, #{tracked_action.id} from role_assignments, profiles where profiles.type = 'Person' and profiles.id = role_assignments.accessor_id and profiles.id != #{tracked_action.user.id} and profiles.id not in (select atn.profile_id from action_tracker_notifications as atn where atn.action_tracker_id = #{tracked_action.id}) and role_assignments.resource_type = 'Profile' and role_assignments.resource_id = #{target.profile.id}")
  41 + end
  42 +
  43 + end
  44 +end
... ...
app/jobs/profile_suggestions_job.rb 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +class ProfileSuggestionsJob < Struct.new(:person_id)
  2 +
  3 + def self.exists?(person_id)
  4 + !find(person_id).empty?
  5 + end
  6 +
  7 + def self.find(person_id)
  8 + Delayed::Job.by_handler("--- !ruby/struct:ProfileSuggestionsJob\nperson_id: #{person_id}\n")
  9 + end
  10 +
  11 + def perform
  12 + logger = Delayed::Worker.logger
  13 + begin
  14 + person = Person.find(person_id)
  15 + ProfileSuggestion.calculate_suggestions(person)
  16 + UserMailer.profiles_suggestions_email(person).deliver if person.email_suggestions
  17 + rescue Exception => exception
  18 + logger.error("Error with suggestions for person ID %d: %s" % [person_id, exception.to_s])
  19 + end
  20 + end
  21 +
  22 +end
... ...
app/jobs/user_activation_job.rb 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +class UserActivationJob < Struct.new(:user_id)
  2 + def perform
  3 + user = User.find(user_id)
  4 + user.destroy unless user.activated? || user.person.is_template? || user.moderate_registration_pending?
  5 + end
  6 +end
... ...
app/mailers/environment_mailing.rb
1 1 class EnvironmentMailing < Mailing
2 2  
  3 + settings_items :recipients_roles, :type => :array
  4 + attr_accessible :recipients_roles
  5 +
3 6 def recipients(offset=0, limit=100)
4   - source.people.order(:id).offset(offset).limit(limit)
  7 + recipients_by_role.order(:id).offset(offset).limit(limit)
5 8 .joins("LEFT OUTER JOIN mailing_sents m ON (m.mailing_id = #{id} AND m.person_id = profiles.id)")
6 9 .where("m.person_id" => nil)
7 10 end
8 11  
  12 + def recipients_by_role
  13 + if recipients_roles.blank?
  14 + source.people
  15 + else
  16 + roles = Environment::Role.where("key in (?)", self.recipients_roles)
  17 + Person.by_role(roles).where(environment_id: self.source_id)
  18 + end
  19 + end
  20 +
9 21 def each_recipient
10 22 offset = 0
11 23 limit = 100
... ...
app/mailers/mailing.rb
1 1 require_dependency 'mailing_job'
2 2  
3   -class Mailing < ActiveRecord::Base
  3 +class Mailing < ApplicationRecord
4 4  
5 5 acts_as_having_settings :field => :data
6 6  
... ...
app/models/abuse_report.rb
1   -class AbuseReport < ActiveRecord::Base
  1 +class AbuseReport < ApplicationRecord
2 2  
3 3 attr_accessible :content, :reason
4 4  
... ...
app/models/action_tracker_notification.rb
1   -class ActionTrackerNotification < ActiveRecord::Base
  1 +class ActionTrackerNotification < ApplicationRecord
2 2  
3 3 belongs_to :profile
4 4 belongs_to :action_tracker, :class_name => 'ActionTracker::Record', :foreign_key => 'action_tracker_id'
... ...
app/models/add_member.rb
... ... @@ -29,16 +29,22 @@ class AddMember &lt; Task
29 29 end
30 30  
31 31 def information
32   - requestor_email = " (#{requestor.email})" if requestor.may_display_field_to?("email")
33   -
34   - {:message => _("%{requestor}%{requestor_email} wants to be a member of '%{organization}'."),
35   - variables: {requestor: requestor.name, requestor_email: requestor_email, organization: organization.name}}
  32 + {:message => _("%{requestor} wants to be a member of '%{target}'."),
  33 + variables: {requestor: requestor.name, target: target.name}}
36 34 end
37 35  
38 36 def accept_details
39 37 true
40 38 end
41 39  
  40 + def reject_details
  41 + true
  42 + end
  43 +
  44 + def footer
  45 + true
  46 + end
  47 +
42 48 def icon
43 49 {:type => :profile_image, :profile => requestor, :url => requestor.url}
44 50 end
... ... @@ -63,4 +69,16 @@ class AddMember &lt; Task
63 69 suggestion.disable if suggestion
64 70 end
65 71  
  72 + def task_finished_message
  73 + _("You have been accepted at \"%{target}\" with the profile \"%{requestor}\"") %
  74 + {:target => self.target.name,
  75 + :requestor => self.requestor.name}
  76 + end
  77 +
  78 + def task_cancelled_message
  79 + _("Your request to enter community \"%{target}\" with the profile \"%{requestor}\" was not accepted. Please contact any profile admin from %{target} for more information. The following explanation was given: \n\n\"%{explanation}\"") %
  80 + {:target => self.target.name,
  81 + :requestor => self.requestor.name,
  82 + :explanation => self.reject_explanation}
  83 + end
66 84 end
... ...
app/models/application_record.rb 0 → 100644
... ... @@ -0,0 +1,61 @@
  1 +class ApplicationRecord < ActiveRecord::Base
  2 +
  3 + self.abstract_class = true
  4 + self.store_full_sti_class = true
  5 +
  6 + # an ActionView instance for rendering views on models
  7 + def self.action_view
  8 + @action_view ||= begin
  9 + view_paths = ::ActionController::Base.view_paths
  10 + action_view = ::ActionView::Base.new view_paths
  11 + # for using Noosfero helpers inside render calls
  12 + action_view.extend ::ApplicationHelper
  13 + action_view
  14 + end
  15 + end
  16 +
  17 + # default value needed for the above ActionView
  18 + def to_partial_path
  19 + self.class.name.underscore
  20 + end
  21 +
  22 + alias :meta_cache_key :cache_key
  23 + def cache_key
  24 + key = [Noosfero::VERSION, meta_cache_key]
  25 + key.unshift ApplicationRecord.connection.schema_search_path
  26 + key.join('/')
  27 + end
  28 +
  29 + def self.like_search(query, options={})
  30 + if defined?(self::SEARCHABLE_FIELDS) || options[:fields].present?
  31 + fields_per_table = {}
  32 + fields_per_table[table_name] = (options[:fields].present? ? options[:fields] : self::SEARCHABLE_FIELDS.keys.map(&:to_s)) & column_names
  33 +
  34 + if options[:joins].present?
  35 + join_asset = options[:joins].to_s.classify.constantize
  36 + if defined?(join_asset::SEARCHABLE_FIELDS) || options[:fields].present?
  37 + fields_per_table[join_asset.table_name] = (options[:fields].present? ? options[:fields] : join_asset::SEARCHABLE_FIELDS.keys.map(&:to_s)) & join_asset.column_names
  38 + end
  39 + end
  40 +
  41 + query = query.downcase.strip
  42 + fields_per_table.delete_if { |table,fields| fields.blank? }
  43 + conditions = fields_per_table.map do |table,fields|
  44 + fields.map do |field|
  45 + "lower(#{table}.#{field}) LIKE '%#{query}%'"
  46 + end.join(' OR ')
  47 + end.join(' OR ')
  48 +
  49 + if options[:joins].present?
  50 + joins(options[:joins]).where(conditions)
  51 + else
  52 + where(conditions)
  53 + end
  54 +
  55 + else
  56 + raise "No searchable fields defined for #{self.name}"
  57 + end
  58 + end
  59 +
  60 +end
  61 +
... ...
app/models/approve_article.rb
... ... @@ -86,7 +86,7 @@ class ApproveArticle &lt; Task
86 86  
87 87 def information
88 88 if article
89   - {:message => _('%{requestor} wants to publish the article: %{linked_subject}.')}
  89 + {:message => _('%{requestor} wants to publish the article: %{linked_subject}.').html_safe}
90 90 else
91 91 {:message => _("The article was removed.")}
92 92 end
... ...
app/models/article.rb
1 1  
2   -class Article < ActiveRecord::Base
  2 +class Article < ApplicationRecord
3 3  
4 4 include SanitizeHelper
5 5  
... ... @@ -29,6 +29,8 @@ class Article &lt; ActiveRecord::Base
29 29 :display => %w[full]
30 30 }
31 31  
  32 + N_('article')
  33 +
32 34 def initialize(*params)
33 35 super
34 36 if params.present? && params.first.present?
... ... @@ -604,7 +606,7 @@ class Article &lt; ActiveRecord::Base
604 606 end
605 607  
606 608 def accept_category?(cat)
607   - !cat.is_a?(ProductCategory)
  609 + true
608 610 end
609 611  
610 612 def public?
... ... @@ -806,11 +808,13 @@ class Article &lt; ActiveRecord::Base
806 808 end
807 809  
808 810 def body_images_paths
809   - Nokogiri::HTML.fragment(self.body.to_s).css('img[src]').collect do |i|
  811 + paths = Nokogiri::HTML.fragment(self.body.to_s).css('img[src]').collect do |i|
810 812 src = i['src']
811 813 src = URI.escape src if self.new_record? # xss_terminate runs on save
812 814 (self.profile && self.profile.environment) ? URI.join(self.profile.environment.top_url, src).to_s : src
813 815 end
  816 + paths.unshift(URI.join(self.profile.environment.top_url, self.image.public_filename).to_s) if self.image.present?
  817 + paths
814 818 end
815 819  
816 820 def more_comments_label
... ... @@ -862,6 +866,10 @@ class Article &lt; ActiveRecord::Base
862 866 HashWithIndifferentAccess.new :name => name, :abstract => abstract, :body => body, :id => id, :parent_id => parent_id, :author => author
863 867 end
864 868  
  869 + def self.can_display_blocks?
  870 + true
  871 + end
  872 +
865 873 private
866 874  
867 875 def sanitize_tag_list
... ...
app/models/article_categorization.rb
1   -class ArticleCategorization < ActiveRecord::Base
  1 +class ArticleCategorization < ApplicationRecord
2 2 self.table_name = :articles_categories
3 3  
4 4 belongs_to :article
... ...
app/models/article_follower.rb
1   -class ArticleFollower < ActiveRecord::Base
  1 +class ArticleFollower < ApplicationRecord
2 2  
3 3 attr_accessible :article_id, :person_id
4 4 belongs_to :article, :counter_cache => :followers_count
... ...
app/models/block.rb
1   -class Block < ActiveRecord::Base
  1 +class Block < ApplicationRecord
2 2  
3 3 attr_accessible :title, :subtitle, :display, :limit, :box_id, :posts_per_page,
4 4 :visualization_format, :language, :display_user,
... ... @@ -76,6 +76,17 @@ class Block &lt; ActiveRecord::Base
76 76 true
77 77 end
78 78  
  79 + def visible_to_user?(user)
  80 + visible = self.display_to_user?(user)
  81 + if self.owner.kind_of?(Profile)
  82 + visible &= self.owner.display_info_to?(user)
  83 + visible &= (self.visible? || user && user.has_permission?(:edit_profile_design, self.owner))
  84 + elsif self.owner.kind_of?(Environment)
  85 + visible &= (self.visible? || user && user.has_permission?(:edit_environment_design, self.owner))
  86 + end
  87 + visible
  88 + end
  89 +
79 90 def display_to_user?(user)
80 91 display_user == 'all' || (user.nil? && display_user == 'not_logged') || (user && display_user == 'logged') || (user && display_user == 'followers' && user.follows?(owner))
81 92 end
... ... @@ -170,30 +181,6 @@ class Block &lt; ActiveRecord::Base
170 181 "/images/block_preview.png"
171 182 end
172 183  
173   - # Returns the content to be used for this block.
174   - #
175   - # This method can return several types of objects:
176   - #
177   - # * <tt>String</tt>: if the string starts with <tt>http://</tt> or <tt>https://</tt>, then it is assumed to be address of an IFRAME. Otherwise it's is used as regular HTML.
178   - # * <tt>Hash</tt>: the hash is used to build an URL that is used as the address for a IFRAME.
179   - # * <tt>Proc</tt>: the Proc is evaluated in the scope of BoxesHelper. The
180   - # block can then use <tt>render</tt>, <tt>link_to</tt>, etc.
181   - #
182   - # The method can also return <tt>nil</tt>, which means "no content".
183   - #
184   - # See BoxesHelper#extract_block_content for implementation details.
185   - def content(args={})
186   - "This is block number %d" % self.id
187   - end
188   -
189   - # A footer to be appended to the end of the block. Returns <tt>nil</tt>.
190   - #
191   - # Override in your subclasses. You can return the same types supported by
192   - # #content.
193   - def footer
194   - nil
195   - end
196   -
197 184 # Is this block editable? (Default to <tt>true</tt>)
198 185 def editable?(user=nil)
199 186 self.edit_modes == "all"
... ... @@ -314,6 +301,14 @@ class Block &lt; ActiveRecord::Base
314 301 self.observers << block
315 302 end
316 303  
  304 + def api_content
  305 + nil
  306 + end
  307 +
  308 + def display_api_content_by_default?
  309 + false
  310 + end
  311 +
317 312 private
318 313  
319 314 def home_page_path
... ...
app/models/box.rb
1   -class Box < ActiveRecord::Base
  1 +class Box < ApplicationRecord
2 2  
3 3 acts_as_list scope: -> box { where owner_id: box.owner_id, owner_type: box.owner_type }
4 4  
... ... @@ -41,7 +41,6 @@ class Box &lt; ActiveRecord::Base
41 41 ProfileImageBlock,
42 42 RawHTMLBlock,
43 43 RecentDocumentsBlock,
44   - SellersSearchBlock,
45 44 TagsBlock ]
46 45 end
47 46  
... ... @@ -54,21 +53,17 @@ class Box &lt; ActiveRecord::Base
54 53 EnterprisesBlock,
55 54 FansBlock,
56 55 FavoriteEnterprisesBlock,
57   - FeaturedProductsBlock,
58 56 FeedReaderBlock,
59 57 HighlightsBlock,
60 58 LinkListBlock,
61 59 LocationBlock,
62 60 LoginBlock,
63 61 MyNetworkBlock,
64   - ProductsBlock,
65   - ProductCategoriesBlock,
66 62 ProfileImageBlock,
67 63 ProfileInfoBlock,
68 64 ProfileSearchBlock,
69 65 RawHTMLBlock,
70 66 RecentDocumentsBlock,
71   - SellersSearchBlock,
72 67 SlideshowBlock,
73 68 TagsBlock
74 69 ]
... ...
app/models/category.rb
1   -class Category < ActiveRecord::Base
  1 +class Category < ApplicationRecord
2 2  
3 3 attr_accessible :name, :parent_id, :display_color, :display_in_menu, :image_builder, :environment, :parent
4 4  
... ... @@ -35,8 +35,6 @@ class Category &lt; ActiveRecord::Base
35 35 has_many :people, :through => :profile_categorizations, :source => :profile, :class_name => 'Person'
36 36 has_many :communities, :through => :profile_categorizations, :source => :profile, :class_name => 'Community'
37 37  
38   - has_many :products, :through => :enterprises
39   -
40 38 acts_as_having_image
41 39  
42 40 before_save :normalize_display_color
... ... @@ -64,10 +62,6 @@ class Category &lt; ActiveRecord::Base
64 62 self.communities.reorder('created_at DESC, id DESC').paginate(page: 1, per_page: limit)
65 63 end
66 64  
67   - def recent_products(limit = 10)
68   - self.products.reorder('created_at DESC, id DESC').paginate(page: 1, per_page: limit)
69   - end
70   -
71 65 def recent_articles(limit = 10)
72 66 self.articles.recent(limit)
73 67 end
... ...
app/models/certifier.rb
... ... @@ -1,35 +0,0 @@
1   -class Certifier < ActiveRecord::Base
2   -
3   - attr_accessible :name, :environment
4   -
5   - SEARCHABLE_FIELDS = {
6   - :name => {:label => _('Name'), :weight => 10},
7   - :description => {:label => _('Description'), :weight => 3},
8   - :link => {:label => _('Link'), :weight => 1},
9   - }
10   -
11   - belongs_to :environment
12   -
13   - has_many :qualifier_certifiers, :dependent => :destroy
14   - has_many :qualifiers, :through => :qualifier_certifiers
15   -
16   - has_many :product_qualifiers
17   - has_many :products, :through => :product_qualifiers, :source => :product
18   -
19   - validates_presence_of :environment_id
20   - validates_presence_of :name
21   -
22   - def destroy
23   - product_qualifiers.each { |pq| pq.update! :certifier => nil }
24   - super
25   - end
26   -
27   - def link
28   - self[:link] || ''
29   - end
30   -
31   - def <=>(b)
32   - self.name.downcase.transliterate <=> b.name.downcase.transliterate
33   - end
34   -
35   -end
app/models/chat_message.rb
1   -class ChatMessage < ActiveRecord::Base
  1 +class ChatMessage < ApplicationRecord
  2 +
2 3 attr_accessible :body, :from, :to
3 4  
4 5 belongs_to :to, :class_name => 'Profile'
... ...