Compare View

switch
from
...
to
 
Commits (289)
Showing 1159 changed files   Show diff stats

Too many changes.

To preserve performance only 100 of 1159 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
... ... @@ -6,15 +6,19 @@ notifications:
6 6 template:
7 7 - "%{repository_slug} %{branch} %{commit} %{commit_subject} - %{result} %{build_url}"
8 8  
9   -# trusty constainers take more time to start
10   -#dist: trusty
  9 +# Ensure Container-based environment, as others can have some random failures
  10 +# specially with different Firefox versions and selenium tests.
  11 +# E.g. https://travis-ci.org/noosfero/noosfero/jobs/122918772#L1308
  12 +#
  13 +# Also container-based environments have the fatest boot times and
  14 +# are the only one with cache available for public projects.
  15 +# See https://docs.travis-ci.com/user/ci-environment/#Virtualization-environments
  16 +sudo: false
  17 +cache: bundler
11 18  
12 19 language: ruby
13 20 rvm:
14   - - 2.2
15   - # ruby 2.3 works but isn't stable on travis
16   -
17   -cache: bundler
  21 + - 2.3.1
18 22  
19 23 addons:
20 24 apt:
... ... @@ -25,11 +29,6 @@ addons:
25 29 paths:
26 30 - $(ls tmp/artifact* | tr "\n" ":")
27 31  
28   -# workaround for https://github.com/travis-ci/travis-ci/issues/4536
29   -before_install:
30   - - export GEM_HOME=$PWD/vendor/bundle/ruby/2.2.0
31   - - gem install bundler
32   -
33 32 before_script:
34 33 - mkdir -p tmp/{pids,cache} log cache
35 34 - script/noosfero-plugins disableall
... ... @@ -41,21 +40,36 @@ before_script:
41 40 - bundle exec rake db:migrate &>/dev/null
42 41  
43 42 env:
44   - - TASK=test:api
45   - - TASK=test:units
46   - - TASK=test:functionals
47   - - TASK=test:integration
48   - - SLICE=1/2 TASK=cucumber LANG=en
49   - - SLICE=2/2 TASK=cucumber LANG=en
50   - - SLICE=1/4 TASK=selenium
51   - - SLICE=2/4 TASK=selenium
52   - - SLICE=3/4 TASK=selenium
53   - - SLICE=4/4 TASK=selenium
54   - - SLICE=1/5 TASK=test:noosfero_plugins BUNDLE_OPTS=install
55   - - SLICE=2/5 TASK=test:noosfero_plugins BUNDLE_OPTS=install
56   - - SLICE=3/5 TASK=test:noosfero_plugins BUNDLE_OPTS=install
57   - - SLICE=4/5 TASK=test:noosfero_plugins BUNDLE_OPTS=install
58   - - 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
59 73  
60 74 script:
61 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,421 @@
  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_type.split(',').map do |content_type|
  411 + content_type.camelcase
  412 + end
  413 + end
  414 +
  415 + def period(from_date, until_date)
  416 + begin_period = from_date.nil? ? Time.at(0).to_datetime : from_date
  417 + end_period = until_date.nil? ? DateTime.now : until_date
  418 + begin_period..end_period
  419 + end
  420 + end
  421 +end
... ...
app/api/v1/activities.rb 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +module Api
  2 + module V1
  3 + class Activities < Grape::API
  4 + before { authenticate! }
  5 +
  6 + resource :profiles do
  7 +
  8 + get ':id/activities' do
  9 + profile = Profile.find_by id: params[:id]
  10 +
  11 + not_found! if profile.blank? || profile.secret || !profile.visible
  12 + forbidden! if !profile.secret && profile.visible && !profile.display_private_info_to?(current_person)
  13 +
  14 + activities = profile.activities.map(&:activity)
  15 + present activities, :with => Entities::Activity, :current_person => current_person
  16 + end
  17 + end
  18 + end
  19 + end
  20 +end
... ...
app/api/v1/articles.rb 0 → 100644
... ... @@ -0,0 +1,303 @@
  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 + desc 'Report a abuse and/or violent content in a article by id' do
  58 + detail 'Submit a abuse (in general, a content violation) report about a specific article'
  59 + params Entities::Article.documentation
  60 + failure [[400, 'Bad Request']]
  61 + named 'ArticleReportAbuse'
  62 + end
  63 + post ':id/report_abuse' do
  64 + article = find_article(environment.articles, params[:id])
  65 + profile = article.profile
  66 + begin
  67 + abuse_report = AbuseReport.new(:reason => params[:report_abuse])
  68 + if !params[:content_type].blank?
  69 + article = params[:content_type].constantize.find(params[:content_id])
  70 + abuse_report.content = article_reported_version(article)
  71 + end
  72 +
  73 + current_person.register_report(abuse_report, profile)
  74 +
  75 + if !params[:content_type].blank?
  76 + abuse_report = AbuseReport.find_by reporter_id: current_person.id, abuse_complaint_id: profile.opened_abuse_complaint.id
  77 + Delayed::Job.enqueue DownloadReportedImagesJob.new(abuse_report, article)
  78 + end
  79 +
  80 + {
  81 + :success => true,
  82 + :message => _('Your abuse report was registered. The administrators are reviewing your report.'),
  83 + }
  84 + rescue Exception => exception
  85 + #logger.error(exception.to_s)
  86 + render_api_error!(_('Your report couldn\'t be saved due to some problem. Please contact the administrator.'), 400)
  87 + end
  88 +
  89 + end
  90 +
  91 + desc "Returns the articles I voted" do
  92 + detail 'Get the Articles I make a vote'
  93 + failure [[403, 'Forbidden']]
  94 + named 'ArticleFollowers'
  95 + end
  96 + #FIXME refactor this method
  97 + get 'voted_by_me' do
  98 + present_articles(current_person.votes.where(:voteable_type => 'Article').collect(&:voteable))
  99 + end
  100 +
  101 + desc 'Perform a vote on a article by id' do
  102 + detail 'Vote on a specific article with values: 1 (if you like) or -1 (if not)'
  103 + params Entities::UserLogin.documentation
  104 + failure [[401,'Unauthorized']]
  105 + named 'ArticleVote'
  106 + end
  107 + post ':id/vote' do
  108 + authenticate!
  109 + value = (params[:value] || 1).to_i
  110 + # FIXME verify allowed values
  111 + render_api_error!('Vote value not allowed', 400) unless [-1, 1].include?(value)
  112 + article = find_article(environment.articles, params[:id])
  113 + begin
  114 + vote = Vote.new(:voteable => article, :voter => current_person, :vote => value)
  115 + {:vote => vote.save!}
  116 + rescue ActiveRecord::RecordInvalid => e
  117 + render_api_error!(e.message, 400)
  118 + end
  119 + end
  120 +
  121 + desc "Returns the total followers for the article" do
  122 + detail 'Get the followers of a specific article by id'
  123 + failure [[403, 'Forbidden']]
  124 + named 'ArticleFollowers'
  125 + end
  126 + get ':id/followers' do
  127 + article = find_article(environment.articles, params[:id])
  128 + total = article.person_followers.count
  129 + {:total_followers => total}
  130 + end
  131 +
  132 + desc "Return the articles followed by me"
  133 + get 'followed_by_me' do
  134 + present_articles_for_asset(current_person, 'following_articles')
  135 + end
  136 +
  137 + desc "Add a follower for the article" do
  138 + detail 'Add the current user identified by private token, like a follower of a article'
  139 + params Entities::UserLogin.documentation
  140 + failure [[401, 'Unauthorized']]
  141 + named 'ArticleFollow'
  142 + end
  143 + post ':id/follow' do
  144 + authenticate!
  145 + article = find_article(environment.articles, params[:id])
  146 + if article.article_followers.exists?(:person_id => current_person.id)
  147 + {:success => false, :already_follow => true}
  148 + else
  149 + article_follower = ArticleFollower.new
  150 + article_follower.article = article
  151 + article_follower.person = current_person
  152 + article_follower.save!
  153 + {:success => true}
  154 + end
  155 + end
  156 +
  157 + desc 'Return the children of a article identified by id' do
  158 + detail 'Get all children articles of a specific article'
  159 + params Entities::Article.documentation
  160 + failure [[403, 'Forbidden']]
  161 + named 'ArticleChildren'
  162 + end
  163 +
  164 + paginate per_page: MAX_PER_PAGE, max_per_page: MAX_PER_PAGE
  165 + get ':id/children' do
  166 + article = find_article(environment.articles, params[:id])
  167 +
  168 + #TODO make tests for this situation
  169 + votes_order = params.delete(:order) if params[:order]=='votes_score'
  170 + articles = select_filtered_collection_of(article, 'children', params)
  171 + articles = articles.display_filter(current_person, article.profile)
  172 +
  173 + #TODO make tests for this situation
  174 + if votes_order
  175 + articles = articles.joins('left join votes on articles.id=votes.voteable_id').group('articles.id').reorder('sum(coalesce(votes.vote, 0)) DESC')
  176 + end
  177 + Article.hit(articles)
  178 + present_articles(articles)
  179 + end
  180 +
  181 + desc 'Return one child of a article identified by id' do
  182 + detail 'Get a child of a specific article'
  183 + params Entities::Article.documentation
  184 + success Entities::Article
  185 + failure [[403, 'Forbidden']]
  186 + named 'ArticleChild'
  187 + end
  188 + get ':id/children/:child_id' do
  189 + article = find_article(environment.articles, params[:id])
  190 + child = find_article(article.children, params[:child_id])
  191 + child.hit
  192 + present_partial child, :with => Entities::Article
  193 + end
  194 +
  195 + desc 'Suggest a article to another profile' do
  196 + detail 'Suggest a article to another profile (person, community...)'
  197 + params Entities::Article.documentation
  198 + success Entities::Task
  199 + failure [[401,'Unauthorized']]
  200 + named 'ArticleSuggest'
  201 + end
  202 + post ':id/children/suggest' do
  203 + authenticate!
  204 + parent_article = environment.articles.find(params[:id])
  205 +
  206 + suggest_article = SuggestArticle.new
  207 + suggest_article.article = params[:article]
  208 + suggest_article.article[:parent_id] = parent_article.id
  209 + suggest_article.target = parent_article.profile
  210 + suggest_article.requestor = current_person
  211 +
  212 + unless suggest_article.save
  213 + render_api_errors!(suggest_article.article_object.errors.full_messages)
  214 + end
  215 + present_partial suggest_article, :with => Entities::Task
  216 + end
  217 +
  218 + # Example Request:
  219 + # POST api/v1/articles/:id/children?private_token=234298743290432&article[name]=title&article[body]=body
  220 + desc 'Add a child article to a parent identified by id' do
  221 + detail 'Create a new article and associate to a parent'
  222 + params Entities::Article.documentation
  223 + success Entities::Article
  224 + failure [[401,'Unauthorized']]
  225 + named 'ArticleAddChild'
  226 + end
  227 + post ':id/children' do
  228 + parent_article = environment.articles.find(params[:id])
  229 + params[:article][:parent_id] = parent_article.id
  230 + post_article(parent_article.profile, params)
  231 + end
  232 + end
  233 +
  234 + resource :profiles do
  235 + get ':id/home_page' do
  236 + profiles = environment.profiles
  237 + profiles = profiles.visible_for_person(current_person)
  238 + profile = profiles.find_by id: params[:id]
  239 + present_partial profile.home_page, :with => Entities::Article
  240 + end
  241 + end
  242 +
  243 + kinds = %w[profile community person enterprise]
  244 + kinds.each do |kind|
  245 + resource kind.pluralize.to_sym do
  246 + segment "/:#{kind}_id" do
  247 + resource :articles do
  248 +
  249 + desc "Return all articles associate with a profile of type #{kind}" do
  250 + detail 'Get a list of articles of a profile'
  251 + params Entities::Article.documentation
  252 + success Entities::Article
  253 + failure [[403, 'Forbidden']]
  254 + named 'ArticlesOfProfile'
  255 + end
  256 + get do
  257 + profile = environment.send(kind.pluralize).find(params["#{kind}_id"])
  258 +
  259 + if params[:path].present?
  260 + article = profile.articles.find_by path: params[:path]
  261 + if !article || !article.display_to?(current_person)
  262 + article = forbidden!
  263 + end
  264 +
  265 + present_partial article, :with => Entities::Article
  266 + else
  267 +
  268 + present_articles_for_asset(profile)
  269 + end
  270 + end
  271 +
  272 + desc "Return a article associate with a profile of type #{kind}" do
  273 + detail 'Get only one article of a profile'
  274 + params Entities::Article.documentation
  275 + success Entities::Article
  276 + failure [[403, 'Forbidden']]
  277 + named 'ArticleOfProfile'
  278 + end
  279 + get ':id' do
  280 + profile = environment.send(kind.pluralize).find(params["#{kind}_id"])
  281 + present_article(profile)
  282 + end
  283 +
  284 + # Example Request:
  285 + # POST api/v1/{people,communities,enterprises}/:asset_id/articles?private_token=234298743290432&article[name]=title&article[body]=body
  286 + desc "Add a new article associated with a profile of type #{kind}" do
  287 + detail 'Create a new article and associate with a profile'
  288 + params Entities::Article.documentation
  289 + success Entities::Article
  290 + failure [[403, 'Forbidden']]
  291 + named 'ArticleCreateToProfile'
  292 + end
  293 + post do
  294 + profile = environment.send(kind.pluralize).find(params["#{kind}_id"])
  295 + post_article(profile, params)
  296 + end
  297 + end
  298 + end
  299 + end
  300 + end
  301 + end
  302 + end
  303 +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
  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
  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,49 @@
  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 + options = params.select { |key,v| !['id','private_token'].include?(key) }.merge(:author => current_person, :source => article)
  38 + begin
  39 + comment = Comment.create!(options)
  40 + rescue ActiveRecord::RecordInvalid => e
  41 + render_api_error!(e.message, 400)
  42 + end
  43 + present comment, :with => Entities::Comment, :current_person => current_person
  44 + end
  45 + end
  46 +
  47 + end
  48 + end
  49 +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,42 @@
  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 + delete ':id' do
  27 + authenticate!
  28 + profiles = environment.profiles
  29 + profile = profiles.find_by id: params[:id]
  30 +
  31 + not_found! if profile.blank?
  32 +
  33 + if current_person.has_permission?(:destroy_profile, profile)
  34 + profile.destroy
  35 + else
  36 + forbidden!
  37 + end
  38 + end
  39 + end
  40 + end
  41 + end
  42 +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/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/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/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/tasks_controller.rb
... ... @@ -14,20 +14,23 @@ class TasksController &lt; MyProfileController
14 14 @filter_text = params[:filter_text].presence
15 15 @filter_responsible = params[:filter_responsible]
16 16 @task_types = Task.pending_types_for(profile)
17   -
18   - @tasks = Task.pending_all(profile, @filter_type, @filter_text).order_by('created_at', 'asc')
  17 + @tasks = Task.pending_all(profile, @filter_type, @filter_text).order_by('created_at', 'asc').paginate(:per_page => Task.per_page, :page => params[:page])
19 18 @tasks = @tasks.where(:responsible_id => @filter_responsible.to_i != -1 ? @filter_responsible : nil) if @filter_responsible.present?
20 19 @tasks = @tasks.paginate(:per_page => Task.per_page, :page => params[:page])
21   -
22 20 @failed = params ? params[:failed] : {}
23 21  
24 22 @responsible_candidates = profile.members.by_role(profile.roles.reject {|r| !r.has_permission?('perform_task')}) if profile.organization?
25 23  
26 24 @view_only = !current_person.has_permission?(:perform_task, profile)
  25 +
27 26 end
28 27  
29 28 def processed
30   - @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)
31 34 end
32 35  
33 36 def change_responsible
... ... @@ -95,4 +98,36 @@ class TasksController &lt; MyProfileController
95 98 @ticket = Ticket.where('(requestor_id = ? or target_id = ?) and id = ?', profile.id, profile.id, params[:id]).first
96 99 end
97 100  
  101 + def search_tasks
  102 + filter_type = params[:filter_type].presence
  103 + filter_text = params[:filter_text].presence
  104 + result = Task.pending_all(profile,filter_type, filter_text)
  105 +
  106 + render :json => result.map { |task| {:label => task.data[:name], :value => task.data[:name]} }
  107 + end
  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 +
98 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/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
... ... @@ -3,7 +3,10 @@ class SearchController &lt; PublicController
3 3 helper TagsHelper
4 4 include SearchHelper
5 5 include ActionView::Helpers::NumberHelper
  6 + include SanitizeParams
6 7  
  8 +
  9 + before_filter :sanitize_params
7 10 before_filter :redirect_asset_param, :except => [:assets, :suggestions]
8 11 before_filter :load_category, :except => :suggestions
9 12 before_filter :load_search_assets, :except => :suggestions
... ... @@ -49,7 +52,6 @@ class SearchController &lt; PublicController
49 52 [
50 53 [ :people, _('People'), :recent_people ],
51 54 [ :enterprises, _('Enterprises'), :recent_enterprises ],
52   - [ :products, _('Products'), :recent_products ],
53 55 [ :events, _('Upcoming events'), :upcoming_events ],
54 56 [ :communities, _('Communities'), :recent_communities ],
55 57 [ :articles, _('Contents'), :recent_articles ]
... ... @@ -75,16 +77,17 @@ class SearchController &lt; PublicController
75 77 full_text_search
76 78 end
77 79  
78   - def products
79   - @scope = @environment.products
80   - full_text_search
81   - end
82   -
83 80 def enterprises
84 81 @scope = visible_profiles(Enterprise)
85 82 full_text_search
86 83 end
87 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 +
88 91 def communities
89 92 @scope = visible_profiles(Community)
90 93 full_text_search
... ... @@ -134,7 +137,8 @@ class SearchController &lt; PublicController
134 137  
135 138 def tag
136 139 @tag = params[:tag]
137   - @tag_cache_key = "tag_#{CGI.escape(@tag.to_s)}_env_#{environment.id.to_s}_page_#{params[:npage]}"
  140 + tag_str = @tag.kind_of?(Array) ? @tag.join(" ") : @tag.to_str
  141 + @tag_cache_key = "tag_#{CGI.escape(tag_str)}_env_#{environment.id.to_s}_page_#{params[:npage]}"
138 142 if is_cache_expired?(@tag_cache_key)
139 143 @searches[@asset] = {:results => environment.articles.tagged_with(@tag).paginate(paginate_options)}
140 144 end
... ... @@ -182,7 +186,6 @@ class SearchController &lt; PublicController
182 186 people: _('People'),
183 187 communities: _('Communities'),
184 188 enterprises: _('Enterprises'),
185   - products: _('Products and Services'),
186 189 events: _('Events'),
187 190 }
188 191 end
... ... @@ -256,12 +259,11 @@ class SearchController &lt; PublicController
256 259 end
257 260  
258 261 def available_assets
259   - assets = {
  262 + {
260 263 articles: _('Contents'),
261 264 enterprises: _('Enterprises'),
262 265 people: _('People'),
263 266 communities: _('Communities'),
264   - products: _('Products and Services'),
265 267 }
266 268 end
267 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
... ... @@ -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)
... ... @@ -788,7 +780,7 @@ module ApplicationHelper
788 780 return "" if categories.blank?
789 781 content_tag(:ul) do
790 782 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 }
  783 + category_path = { :controller => 'search', :action => 'category_index', :category_path => category.explode_path }
792 784 if category.display_in_menu?
793 785 content_tag(:li) do
794 786 if !category.is_leaf_displayable_in_menu?
... ... @@ -911,7 +903,8 @@ module ApplicationHelper
911 903 end
912 904  
913 905 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') : ''
  906 + admin_icon = '<i class="icon-menu-admin"></i><strong>' + _('Administration') + '</strong>'
  907 + user.is_admin?(environment) ? link_to(admin_icon.html_safe, environment.admin_url, :title => _("Configure the environment"), :class => 'admin-link') : ''
915 908 end
916 909  
917 910 def usermenu_logged_in
... ... @@ -920,23 +913,39 @@ module ApplicationHelper
920 913 if count > 0
921 914 pending_tasks_count = link_to(count.to_s, user.tasks_url, :id => 'pending-tasks-count', :title => _("Manage your pending tasks"))
922 915 end
  916 + user_identifier = "<i style='background-image:url(#{user.profile_custom_icon(gravatar_default)})'></i><strong>#{user.identifier}</strong>"
  917 + welcome_link = link_to(user_identifier.html_safe, user.public_profile_url, :id => "homepage-link", :title => _('Go to your homepage'))
  918 + welcome_span = _("<span class='welcome'>Welcome,</span> %s") % welcome_link.html_safe
  919 + ctrl_panel_icon = '<i class="icon-menu-ctrl-panel"></i>'
  920 + ctrl_panel_section = '<strong>' + ctrl_panel_icon + _('Control panel') + '</strong>'
  921 + ctrl_panel_link = link_to(ctrl_panel_section.html_safe, user.admin_url, :class => 'ctrl-panel', :title => _("Configure your personal account and content"))
  922 + logout_icon = '<i class="icon-menu-logout"></i><strong>' + _('Logout') + '</strong>'
  923 + logout_link = link_to(logout_icon.html_safe, { :controller => 'account', :action => 'logout'} , :id => "logout", :title => _("Leave the system"))
  924 + join_result = safe_join(
  925 + [welcome_span.html_safe, render_environment_features(:usermenu).html_safe, admin_link.html_safe,
  926 + manage_enterprises.html_safe, manage_communities.html_safe, ctrl_panel_link.html_safe,
  927 + pending_tasks_count.html_safe, logout_link.html_safe], "")
  928 + join_result
  929 + end
923 930  
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"))
  931 + def usermenu_notlogged_in
  932 + login_str = '<i class="icon-menu-login"></i><strong>' + _('Login') + '</strong>'
  933 + ret = _("<span class='login'>%s</span>") % modal_inline_link_to(login_str.html_safe, login_url, '#inlineLoginBox', :id => 'link_login')
  934 + return ret.html_safe
932 935 end
933 936  
  937 + def usermenu_signup
  938 + signup_str = '<strong>' + _('Sign up') + '</strong>'
  939 + ret = _("<span class='or'>or</span> <span class='signup'>%s</span>") % link_to(signup_str.html_safe, :controller => 'account', :action => 'signup')
  940 + return ret.html_safe
  941 +
  942 + end
934 943 def limited_text_area(object_name, method, limit, text_area_id, options = {})
935   - content_tag(:div, [
  944 + content_tag(:div, safe_join([
936 945 text_area(object_name, method, { :id => text_area_id, :onkeyup => "limited_text_area('#{text_area_id}', #{limit})" }.merge(options)),
937 946 content_tag(:p, content_tag(:span, limit) + ' ' + _(' characters left'), :id => text_area_id + '_left'),
938 947 content_tag(:p, _('Limit of characters reached'), :id => text_area_id + '_limit', :style => 'display: none')
939   - ].join, :class => 'limited-text-area')
  948 + ]), :class => 'limited-text-area')
940 949 end
941 950  
942 951 def expandable_text_area(object_name, method, text_area_id, options = {})
... ... @@ -970,11 +979,11 @@ module ApplicationHelper
970 979  
971 980 def task_information(task)
972 981 values = {}
  982 + values.merge!(task.information[:variables]) if task.information[:variables]
973 983 values.merge!({:requestor => link_to(task.requestor.name, task.requestor.url)}) if task.requestor
974 984 values.merge!({:subject => content_tag('span', task.subject, :class=>'task_target')}) if task.subject
975 985 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
  986 + (task.information[:message] % values).html_safe
978 987 end
979 988  
980 989 def add_zoom_to_article_images
... ... @@ -1032,26 +1041,24 @@ module ApplicationHelper
1032 1041 end
1033 1042  
1034 1043 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]) }
  1044 + titles = tabs.inject(''.html_safe){ |result, tab| result << content_tag(:li, link_to(tab[:title], '#'+tab[:id]), :class => 'tab') }
  1045 + contents = tabs.inject(''.html_safe){ |result, tab| result << content_tag(:div, tab[:content], :id => tab[:id]) }
1037 1046  
1038 1047 content_tag(:div, content_tag(:ul, titles) + raw(contents), :class => 'ui-tabs')
1039 1048 end
1040 1049  
1041 1050 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   - )
  1051 + if article.folder?
  1052 + _("Are you sure that you want to remove the folder \"%s\"? Note that all the items inside it will also be removed!") % article.name
  1053 + else
  1054 + _("Are you sure that you want to remove the item \"%s\"?") % article.name
  1055 + end
1049 1056 end
1050 1057  
1051 1058 def expirable_link_to(expired, content, url, options = {})
1052 1059 if expired
1053 1060 options[:class] = (options[:class] || '') + ' disabled'
1054   - content_tag('a', '&nbsp;'+content_tag('span', content), options)
  1061 + content_tag('a', '&nbsp;'.html_safe+content_tag('span', content), options)
1055 1062 else
1056 1063 if options[:modal]
1057 1064 options.delete(:modal)
... ... @@ -1084,7 +1091,7 @@ module ApplicationHelper
1084 1091  
1085 1092 radios = templates.map do |template|
1086 1093 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")
  1094 + end.join("\n").html_safe
1088 1095  
1089 1096 content_tag('div', content_tag('label', _('Profile organization'), :for => 'template-options', :class => 'formlabel') +
1090 1097 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 +1131,7 @@ module ApplicationHelper
1124 1131 content_tag(:div, :class => 'errorExplanation', :id => 'errorExplanation') do
1125 1132 content_tag(:h2, _('Errors while saving')) +
1126 1133 content_tag(:ul) do
1127   - errors.map { |err| content_tag(:li, err) }.join
  1134 + safe_join(errors.map { |err| content_tag(:li, err) })
1128 1135 end
1129 1136 end
1130 1137 end
... ... @@ -1234,6 +1241,7 @@ module ApplicationHelper
1234 1241 :href=>"#",
1235 1242 :title=>_("Exit full screen mode")
1236 1243 })
  1244 + content.html_safe
1237 1245 end
1238 1246  
1239 1247 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', :action => 'unfollow_article', :article_id => page.id, :profile => page.profile.identifier}
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', :action => 'follow_article', :article_id => page.id, :profile => page.profile.identifier}
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
... ... @@ -136,17 +137,18 @@ module BoxesHelper
136 137  
137 138 result = filter_html(result, block)
138 139  
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)
  140 + join_result = safe_join([result, footer_content, box_decorator.block_edit_buttons(block)])
  141 + content_tag_inner_1 = content_tag('div', join_result, :class => 'block-inner-2')
  142 +
  143 + content_tag_inner_2 = content_tag('div', content_tag_inner_1, :class => 'block-inner-1')
  144 + content_tag_inner_3 = content_tag('div', content_tag_inner_2, options)
  145 + content_tag_inner_4 = box_decorator.block_target(block.box, block) + content_tag_inner_3
  146 + c = content_tag('div', content_tag_inner_4, :class => 'block-outer')
  147 + box_decorator_result = box_decorator.block_handle(block)
  148 + result_final = safe_join([c, box_decorator_result], "")
  149 +
  150 +
  151 + return result_final
150 152 end
151 153  
152 154 def wrap_main_content(content)
... ... @@ -156,17 +158,17 @@ module BoxesHelper
156 158 def extract_block_content(content)
157 159 case content
158 160 when Hash
159   - content_tag('iframe', '', :src => url_for(content))
  161 + content_tag('iframe', ''.html_safe, :src => url_for(content))
160 162 when String
161 163 if content.split("\n").size == 1 and content =~ /^https?:\/\//
162   - content_tag('iframe', '', :src => content)
  164 + content_tag('iframe', ''.html_safe, :src => content)
163 165 else
164 166 content
165 167 end
166 168 when Proc
167 169 self.instance_eval(&content)
168 170 when NilClass
169   - ''
  171 + ''.html_safe
170 172 else
171 173 raise "Unsupported content for block (#{content.class})"
172 174 end
... ... @@ -175,14 +177,14 @@ module BoxesHelper
175 177 module DontMoveBlocks
176 178 # does nothing
177 179 def self.block_target(box, block = nil)
178   - ''
  180 + ''.html_safe
179 181 end
180 182 # does nothing
181 183 def self.block_handle(block)
182   - ''
  184 + ''.html_safe
183 185 end
184 186 def self.block_edit_buttons(block)
185   - ''
  187 + ''.html_safe
186 188 end
187 189 def self.select_blocks box, arr, context
188 190 arr = arr.select{ |block| block.visible? context }
... ... @@ -229,9 +231,9 @@ module BoxesHelper
229 231 # makes the given block draggable so it can be moved away.
230 232 def block_handle(block)
231 233 return "" unless movable?(block)
232   - icon = "<div><div>#{display_icon(block.class)}</div><span>#{_(block.class.pretty_name)}</span></div>"
  234 + icon = "<div><div>#{display_icon(block.class)}</div><span>#{_(block.class.pretty_name)}</span></div>".html_safe
233 235 block_draggable("block-#{block.id}",
234   - :helper => "function() {return cloneDraggableBlock($(this), '#{icon}')}")
  236 + :helper => "function() {return cloneDraggableBlock($(this), '#{icon}')}".html_safe)
235 237 end
236 238  
237 239 def block_draggable(element_id, options={})
... ... @@ -302,7 +304,7 @@ module BoxesHelper
302 304 buttons << modal_inline_icon(:embed, _('Embed code'), {}, "#embed-code-box-#{block.id}") << html
303 305 end
304 306  
305   - content_tag('div', buttons.join("\n") + tag('br', :style => 'clear: left'), :class => 'button-bar')
  307 + content_tag('div', buttons.join("\n").html_safe + tag('br', :style => 'clear: left'), :class => 'button-bar')
306 308 end
307 309  
308 310 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/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/comment_helper.rb
... ... @@ -66,7 +66,7 @@ module CommentHelper
66 66  
67 67 def link_for_edit(comment)
68 68 if comment.can_be_updated_by?(user)
69   - {:link => expirable_comment_link(comment, :edit, _('Edit'), url_for(:profile => profile.identifier, :controller => :comment, :action => :edit, :id => comment.id),:class => 'modal')}
  69 + {:link => expirable_comment_link(comment, :edit, _('Edit'), url_for(:profile => profile.identifier, :controller => :comment, :action => :edit, :id => comment.id), :modal => true)}
70 70 end
71 71 end
72 72  
... ...
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/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/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/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/sanitize_helper.rb 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +module SanitizeHelper
  2 +
  3 + def sanitize_html(text, type= :full_sanitize)
  4 + sanitizer(type).sanitize(text, scrubber: permit_scrubber)
  5 + end
  6 +
  7 + def sanitize_link(text)
  8 + sanitizer(:white_list).sanitize(text, scrubber:permit_scrubber)
  9 + end
  10 +
  11 +protected
  12 +
  13 + def permit_scrubber
  14 + scrubber = Rails::Html::PermitScrubber.new
  15 + scrubber.tags = Rails.application.config.action_view.sanitized_allowed_tags
  16 + scrubber.attributes = Rails.application.config.action_view.sanitized_allowed_attributes
  17 + scrubber
  18 + end
  19 +
  20 + def sanitizer type = :full_sanitize
  21 + return HTML::WhiteListSanitizer.new if type == :white_list
  22 + HTML::FullSanitizer.new
  23 + end
  24 +
  25 +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/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/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/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,18 @@ 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 footer
  41 + true
  42 + end
  43 +
42 44 def icon
43 45 {:type => :profile_image, :profile => requestor, :url => requestor.url}
44 46 end
... ... @@ -63,4 +65,15 @@ class AddMember &lt; Task
63 65 suggestion.disable if suggestion
64 66 end
65 67  
  68 + def task_finished_message
  69 + _("You have been accepted at \"%{target}\" with the profile \"%{requestor}\"") %
  70 + {:target => self.target.name,
  71 + :requestor => self.requestor.name}
  72 + end
  73 +
  74 + def task_cancelled_message
  75 + _("Your request to enter community \"%{target} with the profile \"%{requestor}\" was not accepted. Please contact any profile admin from %{url} for more information.") %
  76 + {:target => self.target.name, :url => self.target.url,
  77 + :requestor => self.requestor.name}
  78 + end
66 79 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 +
  4 + include SanitizeHelper
3 5  
4 6 attr_accessible :name, :body, :abstract, :profile, :tag_list, :parent,
5 7 :allow_members_to_edit, :translation_of_id, :language,
... ... @@ -54,6 +56,7 @@ class Article &lt; ActiveRecord::Base
54 56 track_actions :create_article, :after_create, :keep_params => [:name, :url, :lead, :first_image], :if => Proc.new { |a| a.is_trackable? && !a.image? }
55 57  
56 58 # xss_terminate plugin can't sanitize array fields
  59 + # sanitize_tag_list is used with SanitizeHelper
57 60 before_save :sanitize_tag_list
58 61  
59 62 before_create do |article|
... ... @@ -601,7 +604,7 @@ class Article &lt; ActiveRecord::Base
601 604 end
602 605  
603 606 def accept_category?(cat)
604   - !cat.is_a?(ProductCategory)
  607 + true
605 608 end
606 609  
607 610 def public?
... ... @@ -803,11 +806,13 @@ class Article &lt; ActiveRecord::Base
803 806 end
804 807  
805 808 def body_images_paths
806   - Nokogiri::HTML.fragment(self.body.to_s).css('img[src]').collect do |i|
  809 + paths = Nokogiri::HTML.fragment(self.body.to_s).css('img[src]').collect do |i|
807 810 src = i['src']
808 811 src = URI.escape src if self.new_record? # xss_terminate runs on save
809 812 (self.profile && self.profile.environment) ? URI.join(self.profile.environment.top_url, src).to_s : src
810 813 end
  814 + paths.unshift(URI.join(self.profile.environment.top_url, self.image.public_filename).to_s) if self.image.present?
  815 + paths
811 816 end
812 817  
813 818 def more_comments_label
... ... @@ -859,6 +864,10 @@ class Article &lt; ActiveRecord::Base
859 864 HashWithIndifferentAccess.new :name => name, :abstract => abstract, :body => body, :id => id, :parent_id => parent_id, :author => author
860 865 end
861 866  
  867 + def self.can_display_blocks?
  868 + true
  869 + end
  870 +
862 871 private
863 872  
864 873 def sanitize_tag_list
... ... @@ -870,11 +879,6 @@ class Article &lt; ActiveRecord::Base
870 879 tag_name.gsub(/[<>]/, '')
871 880 end
872 881  
873   - def sanitize_html(text)
874   - sanitizer = HTML::FullSanitizer.new
875   - sanitizer.sanitize(text)
876   - end
877   -
878 882 def parent_archived?
879 883 if self.parent_id_changed? && self.parent && self.parent.archived?
880 884 errors.add(:parent_folder, N_('is archived!!'))
... ...
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
... ... @@ -314,6 +325,14 @@ class Block &lt; ActiveRecord::Base
314 325 self.observers << block
315 326 end
316 327  
  328 + def api_content
  329 + nil
  330 + end
  331 +
  332 + def display_api_content_by_default?
  333 + false
  334 + end
  335 +
317 336 private
318 337  
319 338 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/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'
... ...
app/models/comment.rb
1   -class Comment < ActiveRecord::Base
  1 +class Comment < ApplicationRecord
2 2  
3 3 SEARCHABLE_FIELDS = {
4 4 :title => {:label => _('Title'), :weight => 10},
... ...
app/models/community.rb
... ... @@ -2,6 +2,7 @@ class Community &lt; Organization
2 2  
3 3 attr_accessible :accessor_id, :accessor_type, :role_id, :resource_id, :resource_type
4 4 attr_accessible :address_reference, :district, :tag_list, :language, :description
  5 + attr_accessible :requires_email
5 6 after_destroy :check_invite_member_for_destroy
6 7  
7 8 def self.type_name
... ... @@ -12,6 +13,9 @@ class Community &lt; Organization
12 13 N_('Language')
13 14  
14 15 settings_items :language
  16 + settings_items :requires_email, :type => :boolean
  17 +
  18 + alias_method :requires_email?, :requires_email
15 19  
16 20 extend SetProfileRegionFromCityState::ClassMethods
17 21 set_profile_region_from_city_state
... ...
app/models/contact_list.rb
1   -class ContactList < ActiveRecord::Base
  1 +class ContactList < ApplicationRecord
2 2  
3 3 serialize :list, Array
4 4  
... ...
app/models/create_community.rb
... ... @@ -60,9 +60,9 @@ class CreateCommunity &lt; Task
60 60  
61 61 def information
62 62 if description.blank?
63   - { :message => _('%{requestor} wants to create community %{subject} with no description.') }
  63 + { :message => _('%{requestor} wants to create community %{subject} with no description.').html_safe }
64 64 else
65   - { :message => _('%{requestor} wants to create community %{subject} with this description:<p><em>%{description}</em></p>'),
  65 + { :message => _('%{requestor} wants to create community %{subject} with this description:<p><em>%{description}</em></p>').html_safe,
66 66 :variables => {:description => description} }
67 67 end
68 68 end
... ...
app/models/create_enterprise.rb
... ... @@ -163,7 +163,7 @@ class CreateEnterprise &lt; Task
163 163 end
164 164  
165 165 def information
166   - {:message => _('%{requestor} wants to create enterprise %{subject}.')}
  166 + {:message => _('%{requestor} wants to create enterprise %{subject}.').html_safe}
167 167 end
168 168  
169 169 def reject_details
... ...
app/models/custom_field.rb
1   -class CustomField < ActiveRecord::Base
  1 +class CustomField < ApplicationRecord
  2 +
2 3 attr_accessible :name, :default_value, :format, :extras, :customized_type, :active, :required, :signup, :environment, :moderation_task
3 4 serialize :customized_type
4 5 serialize :extras
... ...
app/models/custom_field_value.rb
1   -class CustomFieldValue < ActiveRecord::Base
  1 +class CustomFieldValue < ApplicationRecord
  2 +
2 3 belongs_to :custom_field
3 4 belongs_to :customized, :polymorphic => true
4 5 attr_accessible :value, :public, :customized, :custom_field, :customized_type
... ...
app/models/doc_item.rb
... ... @@ -17,7 +17,7 @@ class DocItem
17 17 else
18 18 match
19 19 end
20   - end
  20 + end.html_safe
21 21 end
22 22  
23 23 private
... ...
app/models/domain.rb
1 1 require 'noosfero/multi_tenancy'
2 2  
3   -class Domain < ActiveRecord::Base
  3 +class Domain < ApplicationRecord
4 4  
5 5 attr_accessible :name, :owner, :is_default
6 6  
... ...
app/models/email_template.rb
1   -class EmailTemplate < ActiveRecord::Base
  1 +class EmailTemplate < ApplicationRecord
2 2  
3 3 belongs_to :owner, :polymorphic => true
4 4  
... ...
app/models/enterprise.rb
1   -# An enterprise is a kind of organization. According to the system concept,
2   -# only enterprises can offer products and services.
3 1 class Enterprise < Organization
4 2  
5   - attr_accessible :business_name, :address_reference, :district, :tag_list, :organization_website, :historic_and_current_context, :activities_short_description, :products_per_catalog_page
  3 + attr_accessible :business_name, :address_reference, :district, :tag_list,
  4 + :organization_website, :historic_and_current_context, :activities_short_description
6 5  
7 6 SEARCH_FILTERS = {
8 7 :order => %w[more_recent more_popular more_active],
... ... @@ -17,11 +16,6 @@ class Enterprise &lt; Organization
17 16  
18 17 acts_as_trackable after_add: proc{ |p, t| notify_activity t }
19 18  
20   - has_many :products, :foreign_key => :profile_id, :dependent => :destroy
21   - has_many :product_categories, :through => :products
22   - has_many :inputs, :through => :products
23   - has_many :production_costs, :as => :owner
24   -
25 19 has_many :favorite_enterprise_people
26 20 has_many :fans, source: :person, through: :favorite_enterprise_people
27 21  
... ... @@ -29,10 +23,6 @@ class Enterprise &lt; Organization
29 23  
30 24 settings_items :organization_website, :historic_and_current_context, :activities_short_description
31 25  
32   - settings_items :products_per_catalog_page, :type => :integer, :default => 6
33   - alias_method :products_per_catalog_page_before_type_cast, :products_per_catalog_page
34   - validates_numericality_of :products_per_catalog_page, :allow_nil => true, :greater_than => 0
35   -
36 26 extend SetProfileRegionFromCityState::ClassMethods
37 27 set_profile_region_from_city_state
38 28  
... ... @@ -66,10 +56,6 @@ class Enterprise &lt; Organization
66 56 environment ? environment.active_enterprise_fields : []
67 57 end
68 58  
69   - def highlighted_products_with_image(options = {})
70   - Product.where(:highlighted => true).joins(:image)
71   - end
72   -
73 59 def required_fields
74 60 environment ? environment.required_enterprise_fields : []
75 61 end
... ... @@ -136,19 +122,14 @@ class Enterprise &lt; Organization
136 122 links = [
137 123 {:name => _("Enterprises's profile"), :address => '/profile/{profile}', :icon => 'ok'},
138 124 {:name => _('Blog'), :address => '/{profile}/blog', :icon => 'edit'},
139   - {:name => _('Products'), :address => '/catalog/{profile}', :icon => 'new'},
140 125 ]
141 126 blocks = [
142 127 [MainBlock.new],
143 128 [ ProfileImageBlock.new,
144 129 LinkListBlock.new(:links => links),
145   - ProductCategoriesBlock.new
146 130 ],
147 131 [LocationBlock.new]
148 132 ]
149   - if environment.enabled?('products_for_enterprises')
150   - blocks[2].unshift ProductsBlock.new
151   - end
152 133 blocks
153 134 end
154 135  
... ... @@ -189,14 +170,6 @@ class Enterprise &lt; Organization
189 170 {:title => _('Enterprise Info and settings'), :icon => 'edit-profile-enterprise'}
190 171 end
191 172  
192   - def create_product?
193   - true
194   - end
195   -
196   - def catalog_url
197   - { :profile => identifier, :controller => 'catalog'}
198   - end
199   -
200 173 def more_recent_label
201 174 ''
202 175 end
... ... @@ -205,5 +178,4 @@ class Enterprise &lt; Organization
205 178 super or self.fans.where(id: person.id).count > 0
206 179 end
207 180  
208   -
209 181 end
... ...
app/models/environment.rb
1 1 # A Environment is like a website to be hosted in the platform. It may
2 2 # contain multiple Profile's and can be identified by several different
3 3 # domains.
4   -class Environment < ActiveRecord::Base
  4 +class Environment < ApplicationRecord
5 5  
6 6 attr_accessible :name, :is_default, :signup_welcome_text_subject,
7 7 :signup_welcome_text_body, :terms_of_use,
... ... @@ -13,7 +13,9 @@ class Environment &lt; ActiveRecord::Base
13 13 :reports_lower_bound, :noreply_email,
14 14 :signup_welcome_screen_body, :members_whitelist_enabled,
15 15 :members_whitelist, :highlighted_news_amount,
16   - :portal_news_amount, :date_format, :signup_intro
  16 + :portal_news_amount, :date_format, :signup_intro,
  17 + :enable_feed_proxy, :http_feed_proxy, :https_feed_proxy,
  18 + :disable_feed_ssl
17 19  
18 20 has_many :users
19 21  
... ... @@ -126,7 +128,6 @@ class Environment &lt; ActiveRecord::Base
126 128 'disable_asset_enterprises' => _('Disable search for enterprises'),
127 129 'disable_asset_people' => _('Disable search for people'),
128 130 'disable_asset_communities' => _('Disable search for communities'),
129   - 'disable_asset_products' => _('Disable search for products'),
130 131 'disable_asset_events' => _('Disable search for events'),
131 132 'disable_categories' => _('Disable categories'),
132 133 'disable_header_and_footer' => _('Disable header/footer editing by users'),
... ... @@ -137,7 +138,6 @@ class Environment &lt; ActiveRecord::Base
137 138 'disable_contact_community' => _('Disable contact for groups/communities'),
138 139 'forbid_destroy_profile' => _('Forbid users of removing profiles'),
139 140  
140   - 'products_for_enterprises' => _('Enable products for enterprises'),
141 141 'enterprise_registration' => _('Enterprise registration'),
142 142 'enterprise_activation' => _('Enable activation of enterprises'),
143 143 'enterprises_are_disabled_when_created' => _('Enterprises are disabled when created'),
... ... @@ -224,7 +224,6 @@ class Environment &lt; ActiveRecord::Base
224 224  
225 225 has_many :organizations
226 226 has_many :enterprises
227   - has_many :products, :through => :enterprises
228 227 has_many :people
229 228 has_many :communities
230 229 has_many :licenses
... ... @@ -234,23 +233,16 @@ class Environment &lt; ActiveRecord::Base
234 233 order('display_color').where('display_color is not null and parent_id is null')
235 234 }, class_name: 'Category'
236 235  
237   - has_many :product_categories, -> { where type: 'ProductCategory'}
238 236 has_many :regions
239 237 has_many :states
240 238 has_many :cities
241 239  
242 240 has_many :roles, :dependent => :destroy
243 241  
244   - has_many :qualifiers
245   - has_many :certifiers
246   -
247 242 has_many :mailings, :class_name => 'EnvironmentMailing', :foreign_key => :source_id, :as => 'source'
248 243  
249 244 acts_as_accessible
250 245  
251   - has_many :units, -> { order 'position' }
252   - has_many :production_costs, :as => :owner
253   -
254 246 def superior_intances
255 247 [self, nil]
256 248 end
... ... @@ -439,9 +431,7 @@ class Environment &lt; ActiveRecord::Base
439 431 end
440 432  
441 433 DEFAULT_FEATURES = %w(
442   - disable_asset_products
443 434 disable_gender_icon
444   - products_for_enterprises
445 435 disable_select_city_for_contact
446 436 enterprise_registration
447 437 media_panel
... ... @@ -729,7 +719,7 @@ class Environment &lt; ActiveRecord::Base
729 719 url << (Noosfero.url_options.key?(:host) ? Noosfero.url_options[:host] : default_hostname)
730 720 url << ':' << Noosfero.url_options[:port].to_s if Noosfero.url_options.key?(:port)
731 721 url << Noosfero.root('')
732   - url
  722 + url.html_safe
733 723 end
734 724  
735 725 def to_s
... ... @@ -966,10 +956,6 @@ class Environment &lt; ActiveRecord::Base
966 956 end
967 957 end
968 958  
969   - def highlighted_products_with_image(options = {})
970   - self.products.where(highlighted: true).joins(:image).order('created_at ASC')
971   - end
972   -
973 959 settings_items :home_cache_in_minutes, :type => :integer, :default => 5
974 960 settings_items :general_cache_in_minutes, :type => :integer, :default => 15
975 961 settings_items :profile_cache_in_minutes, :type => :integer, :default => 15
... ...