Commit 78a0bcf54fcd1a5721915b4b3dbd086daf85ea92

Authored by Antonio Terceiro
2 parents 2c2aa084 824e34a2

Merge branch 'master' into AI2371

Conflicts:
	app/controllers/my_profile/cms_controller.rb
Showing 571 changed files with 133905 additions and 22540 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 571 files displayed.

HACKING
... ... @@ -40,6 +40,8 @@ commands and make sure you understand what you are doing):
40 40 cp config/database.yml.sqlite3 config/database.yml
41 41 # create tmp directory if it doesn't exist
42 42 mkdir tmp
  43 + # start Solr
  44 + rake solr:start
43 45 # create the development database
44 46 rake db:schema:load
45 47 # run pending migrations
... ...
INSTALL
... ... @@ -13,8 +13,7 @@ You need to install some packages Noosfero depends on. On Debian GNU/Linux or
13 13 Debian-based systems, all of these packages are available through the Debian
14 14 archive. You can install them with the following command:
15 15  
16   - # apt-get install ruby rake po4a libgettext-ruby-util libgettext-ruby1.8 libsqlite3-ruby rcov librmagick-ruby libredcloth-ruby libwill-paginate-ruby iso-codes libfeedparser-ruby libferret-ruby libdaemons-ruby thin tango-icon-theme libhpricot-ruby
17   -
  16 + # apt-get install ruby rake po4a libgettext-ruby-util libgettext-ruby1.8 libsqlite3-ruby rcov librmagick-ruby libredcloth-ruby libwill-paginate-ruby iso-codes libfeedparser-ruby openjdk-6-jre libdaemons-ruby thin tango-icon-theme libhpricot-ruby
18 17  
19 18 On other systems, they may or may not be available through your regular package
20 19 management system. Below are the links to their homepages.
... ... @@ -24,7 +23,7 @@ management system. Below are the links to their homepages.
24 23 * po4a: http://po4a.alioth.debian.org/
25 24 * Ruby-sqlite3: http://rubyforge.org/projects/sqlite-ruby
26 25 * rcov: http://eigenclass.org/hiki/rcov
27   -* Ferret: http://ferret.davebalmain.com/trac
  26 +* Solr: http://lucene.apache.org/solr
28 27 * RMagick: http://rmagick.rubyforge.org/
29 28 * RedCloth: http://redcloth.org/
30 29 * will_paginate: http://github.com/mislav/will_paginate/wikis
... ... @@ -104,7 +103,7 @@ $ tar -zxvf noosfero-0.35.0.tar.gz
104 103 $ ln -s noosfero-0.35.0 current
105 104 $ cd current
106 105  
107   -Copy config/ferret_server.yml.dist to config/ferret_server.yml. You will
  106 +Copy config/solr.yml.dist to config/solr.yml. You will
108 107 probably not need to customize this configuration, but have a look at it.
109 108  
110 109 Create the thin configuration file:
... ... @@ -242,6 +241,10 @@ Compile the translations:
242 241  
243 242 $ RAILS_ENV=production rake noosfero:translations:compile
244 243  
  244 +Run Solr:
  245 +
  246 +$ rake solr:start
  247 +
245 248 Now we have to create some initial data. To create your default environment
246 249 (the first one), run the command below:
247 250  
... ...
Rakefile
... ... @@ -7,4 +7,6 @@ require 'rake'
7 7 require 'rake/testtask'
8 8 require 'rake/rdoctask'
9 9  
  10 +ACTS_AS_SEARCHABLE_ENABLED = false if Rake.application.top_level_tasks.detect{|t| t == 'db:data:minimal'}
  11 +
10 12 require 'tasks/rails'
... ...
app/controllers/my_profile/cms_controller.rb
... ... @@ -265,9 +265,10 @@ class CmsController < MyProfileController
265 265  
266 266 def search
267 267 query = params[:q]
268   - results = query.blank? ? [] : profile.files.published.find_by_contents(query)
  268 + results = query.blank? ? [] : profile.files.published.find_by_contents(query)[:results]
269 269 render :text => article_list_to_json(results), :content_type => 'application/json'
270 270 end
  271 +
271 272 def media_upload
272 273 files_uploaded = []
273 274 parent = check_parent(params[:parent_id])
... ...
app/controllers/my_profile/maps_controller.rb
... ... @@ -6,16 +6,48 @@ class MapsController < MyProfileController
6 6 @profile_data = profile
7 7 if request.post?
8 8 begin
  9 + country = params[:profile_data][:country]
  10 + city = params[:profile_data][:city]
  11 + state = params[:profile_data][:state]
  12 + nregion = NationalRegion.validate!(city, state, country)
  13 + unless nregion.blank?
  14 + params[:profile_data][:national_region_code] = nregion.national_region_code
  15 + end
  16 +
9 17 Profile.transaction do
10 18 if profile.update_attributes!(params[:profile_data])
11 19 session[:notice] = _('Address was updated successfully!')
12 20 redirect_to :action => 'edit_location'
13 21 end
14 22 end
15   - rescue
16   - flash[:error] = _('Address could not be saved.')
  23 + rescue Exception => exc
  24 + flash[:error] = exc.message
17 25 end
18 26 end
19 27 end
20 28  
  29 + def google_map
  30 + render :partial => 'google_map.js'
  31 + end
  32 +
  33 + def search_city
  34 +
  35 + term = params[:term];
  36 +
  37 + regions = NationalRegion.search_city(term + "%", true).map {|r|{ :label => r.city , :category => r.state}}
  38 +
  39 + render :json => regions
  40 +
  41 + end
  42 +
  43 + def search_state
  44 +
  45 + term = params[:term];
  46 +
  47 + regions = NationalRegion.search_state(term + "%", true).map {|r|{ :label => r.state}}
  48 +
  49 + render :json => regions
  50 +
  51 + end
  52 +
21 53 end
... ...
app/controllers/public/browse_controller.rb
... ... @@ -1,77 +0,0 @@
1   -class BrowseController < PublicController
2   -
3   - no_design_blocks
4   -
5   - FILTERS = %w(
6   - more_recent
7   - more_active
8   - more_popular
9   - more_comments
10   - more_views
11   - )
12   -
13   - def per_page
14   - 27
15   - end
16   -
17   - def people
18   - @filter = filter
19   - @title = self.filter_description(params[:action] + '_' + @filter )
20   -
21   - @results = @environment.people.visible.send(@filter)
22   -
23   - if !params[:query].blank?
24   - @results = @results.find_by_contents(params[:query])
25   - end
26   - @results = @results.compact.paginate(:per_page => per_page, :page => params[:page])
27   - end
28   -
29   - def communities
30   - @filter = filter
31   - @title = self.filter_description(params[:action] + '_' + @filter )
32   -
33   - @results = @environment.communities.visible.send(@filter)
34   -
35   - if !params[:query].blank?
36   - @results = @results.find_by_contents(params[:query])
37   - end
38   - @results = @results.compact.paginate(:per_page => per_page, :page => params[:page])
39   - end
40   -
41   - def contents
42   - @filter = filter
43   - @title = self.filter_description(params[:action] + '_' + @filter )
44   -
45   - @results = @environment.articles.published.text_articles.send(@filter)
46   -
47   - if !params[:query].blank?
48   - @results = @results.find_by_contents(params[:query])
49   - end
50   - @results = @results.compact.paginate(:per_page => per_page, :page => params[:page])
51   - end
52   -
53   - protected
54   -
55   - def filter
56   - if FILTERS.include?(params[:filter])
57   - params[:filter]
58   - else
59   - 'more_recent'
60   - end
61   - end
62   -
63   - def filter_description(str)
64   - {
65   - 'people_more_recent' => _('More recent people'),
66   - 'people_more_active' => _('More active people'),
67   - 'people_more_popular' => _('More popular people'),
68   - 'communities_more_recent' => _('More recent communities'),
69   - 'communities_more_active' => _('More active communities'),
70   - 'communities_more_popular' => _('More popular communities'),
71   - 'contents_more_recent' => _('More recent contents'),
72   - 'contents_more_views' => _('Most viewed contents'),
73   - 'contents_more_comments' => _('Most commented contents'),
74   - }[str] || str
75   - end
76   -
77   -end
app/controllers/public/map_balloon_controller.rb 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +class MapBalloonController < PublicController
  2 +
  3 + helper SearchHelper
  4 +
  5 + before_filter :load_profile, :only => [:person, :enterprise, :community]
  6 +
  7 + def product
  8 + @product = Product.find(params[:id])
  9 + render :action => 'product', :layout => false
  10 + end
  11 +
  12 + def person
  13 + render :action => 'profile', :layout => false
  14 + end
  15 +
  16 + def enterprise
  17 + render :action => 'profile', :layout => false
  18 + end
  19 +
  20 + def community
  21 + render :action => 'profile', :layout => false
  22 + end
  23 +
  24 + protected
  25 +
  26 + def load_profile
  27 + @profile = Profile.find(params[:id])
  28 + end
  29 +
  30 +end
... ...
app/controllers/public/profile_controller.rb
... ... @@ -3,17 +3,15 @@ class ProfileController &lt; PublicController
3 3 needs_profile
4 4 before_filter :check_access_to_profile, :except => [:join, :join_not_logged, :index, :add]
5 5 before_filter :store_location, :only => [:join, :join_not_logged, :report_abuse]
6   - before_filter :login_required, :only => [:add, :join, :join_not_logged, :leave, :unblock, :leave_scrap, :remove_scrap, :remove_activity, :view_more_scraps, :view_more_activities, :view_more_network_activities, :report_abuse, :register_report]
  6 + before_filter :login_required, :only => [:add, :join, :join_not_logged, :leave, :unblock, :leave_scrap, :remove_scrap, :remove_activity, :view_more_activities, :view_more_network_activities, :report_abuse, :register_report, :leave_comment_on_activity]
7 7  
8 8 helper TagsHelper
9 9  
10 10 def index
11   - @activities = @profile.tracked_actions.paginate(:per_page => 30, :page => params[:page])
12   - @wall_items = []
13   - @network_activities = !@profile.is_a?(Person) ? @profile.tracked_notifications.paginate(:per_page => 30, :page => params[:page]) : []
  11 + @network_activities = !@profile.is_a?(Person) ? @profile.tracked_notifications.visible.paginate(:per_page => 15, :page => params[:page]) : []
14 12 if logged_in? && current_person.follows?(@profile)
15   - @network_activities = @profile.tracked_notifications.paginate(:per_page => 30, :page => params[:page]) if @network_activities.empty?
16   - @wall_items = @profile.scraps_received.not_replies.paginate(:per_page => 30, :page => params[:page])
  13 + @network_activities = @profile.tracked_notifications.visible.paginate(:per_page => 15, :page => params[:page]) if @network_activities.empty?
  14 + @activities = @profile.activities.paginate(:per_page => 15, :page => params[:page])
17 15 end
18 16 @tags = profile.article_tags
19 17 unless profile.display_info_to?(user)
... ... @@ -180,22 +178,33 @@ class ProfileController &lt; PublicController
180 178 @scrap.receiver= receiver
181 179 @tab_action = params[:tab_action]
182 180 @message = @scrap.save ? _("Message successfully sent.") : _("You can't leave an empty message.")
183   - @scraps = @profile.scraps_received.not_replies.paginate(:per_page => 30, :page => params[:page]) if params[:not_load_scraps].nil?
184   - render :partial => 'leave_scrap'
  181 + activities = @profile.activities.paginate(:per_page => 15, :page => params[:page]) if params[:not_load_scraps].nil?
  182 + render :partial => 'profile_activities_list', :locals => {:activities => activities}
185 183 end
186 184  
187   - def view_more_scraps
188   - @scraps = @profile.scraps_received.not_replies.paginate(:per_page => 30, :page => params[:page])
189   - render :partial => 'profile_scraps', :locals => {:scraps => @scraps}
  185 + def leave_comment_on_activity
  186 + @comment = Comment.new(params[:comment])
  187 + @comment.author = user
  188 + @activity = ActionTracker::Record.find(params[:source_id])
  189 + @comment.source_type, @comment.source_id = (@activity.target_type == 'Article' ? ['Article', @activity.target_id] : [@activity.class.to_s, @activity.id])
  190 + @tab_action = params[:tab_action]
  191 + @message = @comment.save ? _("Comment successfully added.") : _("You can't leave an empty comment.")
  192 + if @tab_action == 'wall'
  193 + activities = @profile.activities.paginate(:per_page => 15, :page => params[:page]) if params[:not_load_scraps].nil?
  194 + render :partial => 'profile_activities_list', :locals => {:activities => activities}
  195 + else
  196 + network_activities = @profile.tracked_notifications.visible.paginate(:per_page => 15, :page => params[:page])
  197 + render :partial => 'profile_network_activities', :locals => {:network_activities => network_activities}
  198 + end
190 199 end
191 200  
192 201 def view_more_activities
193   - @activities = @profile.tracked_actions.paginate(:per_page => 30, :page => params[:page])
194   - render :partial => 'profile_activities', :locals => {:activities => @activities}
  202 + @activities = @profile.activities.paginate(:per_page => 10, :page => params[:page])
  203 + render :partial => 'profile_activities_list', :locals => {:activities => @activities}
195 204 end
196 205  
197 206 def view_more_network_activities
198   - @activities = @profile.tracked_notifications.paginate(:per_page => 30, :page => params[:page])
  207 + @activities = @profile.tracked_notifications.paginate(:per_page => 10, :page => params[:page])
199 208 render :partial => 'profile_network_activities', :locals => {:network_activities => @activities}
200 209 end
201 210  
... ... @@ -213,7 +222,11 @@ class ProfileController &lt; PublicController
213 222 begin
214 223 raise if !can_edit_profile
215 224 activity = ActionTracker::Record.find(params[:activity_id])
216   - activity.destroy
  225 + if params[:only_hide]
  226 + activity.update_attribute(:visible, false)
  227 + else
  228 + activity.destroy
  229 + end
217 230 render :text => _('Activity successfully removed.')
218 231 rescue
219 232 render :text => _('You could not remove this activity')
... ... @@ -285,6 +298,16 @@ class ProfileController &lt; PublicController
285 298 end
286 299 end
287 300  
  301 + def remove_comment
  302 + #FIXME Check whether these permissions are enough
  303 + @comment = Comment.find(params[:comment_id])
  304 + if (user == @comment.author || user == profile || user.has_permission?(:moderate_comments, profile))
  305 + @comment.destroy
  306 + session[:notice] = _('Comment successfully deleted')
  307 + end
  308 + redirect_to :action => :index
  309 + end
  310 +
288 311 protected
289 312  
290 313 def check_access_to_profile
... ...
app/controllers/public/profile_search_controller.rb
... ... @@ -8,11 +8,10 @@ class ProfileSearchController &lt; PublicController
8 8 def index
9 9 @q = params[:q]
10 10 unless @q.blank?
11   - @filtered_query = remove_stop_words(@q)
12 11 if params[:where] == 'environment'
13 12 redirect_to :controller => 'search', :query => @q
14 13 else
15   - @results = profile.articles.published.find_by_contents(@filtered_query).paginate(:per_page => 10, :page => params[:page])
  14 + @results = profile.articles.published.find_by_contents(@q, {:per_page => 10, :page => params[:page]})[:results]
16 15 end
17 16 end
18 17 end
... ...
app/controllers/public/search_controller.rb
1 1 class SearchController < PublicController
2 2  
3 3 helper TagsHelper
  4 + include SearchHelper
  5 + include ActionView::Helpers::NumberHelper
4 6  
5 7 before_filter :load_category
6   - before_filter :prepare_filter
7   - before_filter :check_search_whole_site
8 8 before_filter :load_search_assets
9   - before_filter :check_valid_assets, :only => [ :assets ]
  9 + before_filter :load_query
10 10  
11 11 no_design_blocks
12 12  
13   - protected
  13 + def facets_browse
  14 + @asset = params[:asset]
  15 + @asset_class = asset_class(@asset)
14 16  
15   - def load_search_assets
16   - @search_in = where_to_search
17   - @searching = {}
18   - @search_in.each do |key, name|
19   - @searching[key] = (params[:asset].blank? && (params[:find_in].nil? || params[:find_in].empty? || params[:find_in].include?(key.to_s))) || (params[:asset] == key.to_s)
20   - end
  17 + @facets_only = true
  18 + send(@asset)
  19 +
  20 + @facet = @asset_class.map_facets_for(environment).find { |facet| facet[:id] == params[:facet_id] }
  21 + raise 'Facet not found' if @facet.nil?
  22 +
  23 + render :layout => false
21 24 end
22 25  
23   - def prepare_filter
24   - if @category
25   - @noosfero_finder = CategoryFinder.new(@category)
  26 + def articles
  27 + if !@empty_query
  28 + full_text_search ['public:true']
26 29 else
27   - @noosfero_finder = EnvironmentFinder.new(@environment)
  30 + @results[@asset] = @environment.articles.public.send(@filter).paginate(paginate_options)
28 31 end
29 32 end
30 33  
31   - def check_search_whole_site
32   - if params[:search_whole_site_yes] or params[:search_whole_site] == 'yes'
33   - redirect_to params.merge(:category_path => [], :search_whole_site => nil, :search_whole_site_yes => nil)
34   - end
  34 + def contents
  35 + redirect_to params.merge(:action => :articles)
35 36 end
36 37  
37   - def check_valid_assets
38   - @asset = params[:asset].to_sym
39   - if !where_to_search.map(&:first).include?(@asset)
40   - render :text => 'go away', :status => 403
41   - return
  38 + def people
  39 + if !@empty_query
  40 + full_text_search ['public:true']
  41 + else
  42 + @results[@asset] = @environment.people.visible.send(@filter).paginate(paginate_options)
42 43 end
43 44 end
44 45  
45   - def events
46   - @category_id = @category ? @category.id : nil
47   -
48   - @selected_day = nil
49   - @events_of_the_day = []
50   - date = build_date(params[:year], params[:month], params[:day])
51   -
52   - if params[:day] || !params[:year] && !params[:month]
53   - @selected_day = date
54   - if @category_id and Category.exists?(@category_id)
55   - @events_of_the_day = environment.events.by_day(@selected_day).in_category(Category.find(@category_id))
  46 + def products
  47 + public_filters = ['public:true', 'enabled:true']
  48 + if !@empty_query
  49 + full_text_search public_filters
  50 + else
  51 + @one_page = true
  52 + @geosearch = logged_in? && current_user.person.lat && current_user.person.lng
  53 +
  54 + extra_limit = LIST_SEARCH_LIMIT*5
  55 + sql_options = {:limit => LIST_SEARCH_LIMIT, :order => 'random()'}
  56 + if @geosearch
  57 + full_text_search public_filters, :sql_options => sql_options, :extra_limit => extra_limit,
  58 + :alternate_query => "{!boost b=recip(geodist(),#{"%e" % (1.to_f/DistBoost)},1,1)}",
  59 + :radius => DistFilt, :latitude => current_user.person.lat, :longitude => current_user.person.lng
56 60 else
57   - @events_of_the_day = environment.events.by_day(@selected_day)
  61 + full_text_search public_filters, :sql_options => sql_options, :extra_limit => extra_limit,
  62 + :boost_functions => ['recip(ms(NOW/HOUR,updated_at),1.3e-10,1,1)']
58 63 end
59 64 end
60   -
61   - events = @results[:events]
62   -
63   - @calendar = populate_calendar(date, events)
64   - @previous_calendar = populate_calendar(date - 1.month, events)
65   - @next_calendar = populate_calendar(date + 1.month, events)
66 65 end
67 66  
68   - def people
69   - #nothing, just to enable
70   - end
71 67 def enterprises
72   - load_product_categories_menu(:enterprises)
73   - @categories_menu = true
74   - end
75   - def communities
76   - #nothing, just to enable
77   - end
78   - def articles
79   - #nothins, just to enable
80   - end
81   -
82   - def products
83   - load_product_categories_menu(:products)
84   - @categories_menu = true
  68 + if !@empty_query
  69 + full_text_search ['public:true']
  70 + else
  71 + @filter_title = _('Enterprises from network')
  72 + @results[@asset] = @environment.enterprises.visible.paginate(paginate_options)
  73 + end
85 74 end
86 75  
87   - def load_product_categories_menu(asset)
88   - @results[asset].uniq!
89   - # REFACTOR DUPLICATED CODE inner loop doing the same thing that outter loop
90   -
91   - if !@query.blank? || @region && !params[:radius].blank?
92   - @result_ids = @noosfero_finder.find(asset, @filtered_query, calculate_find_options(asset, nil, params[:page], @product_category, @region, params[:radius], params[:year], params[:month]).merge({:limit => :all}))
  76 + def communities
  77 + if !@empty_query
  78 + full_text_search ['public:true']
  79 + else
  80 + @results[@asset] = @environment.communities.visible.send(@filter).paginate(paginate_options)
93 81 end
94   -
95 82 end
96 83  
97   - def calculate_find_options(asset, limit, page, product_category, region, radius, year, month)
98   - result = { :product_category => product_category, :per_page => limit, :page => page }
99   - if [:enterprises, :people, :products].include?(asset) && region
100   - result.merge!(:within => radius, :region => region.id)
101   - end
  84 + def events
  85 + year = (params[:year] ? params[:year].to_i : Date.today.year)
  86 + month = (params[:month] ? params[:month].to_i : Date.today.month)
  87 + day = (params[:day] ? params[:day].to_i : Date.today.day)
  88 + date = build_date(params[:year], params[:month], params[:day])
  89 + date_range = (date - 1.month)..(date + 1.month).at_end_of_month
102 90  
103   - if month || year
104   - date = Date.new(year.to_i, month.to_i, 1)
105   - result[:date_range] = (date - 1.month)..(date + 1.month).at_end_of_month
  91 + @selected_day = nil
  92 + @events_of_the_day = []
  93 + if params[:day] || !params[:year] && !params[:month]
  94 + @selected_day = date
  95 + @events_of_the_day = @category ?
  96 + environment.events.by_day(@selected_day).in_category(Category.find(@category_id)) :
  97 + environment.events.by_day(@selected_day)
106 98 end
107 99  
108   - result
109   - end
110   -
111   - # limit the number of results per page
112   - # TODO: dont hardcore like this
113   - def limit
114   - searching = @searching.values.select{|v|v}
115   - if params[:display] == 'map'
116   - 2000
  100 + if !@empty_query
  101 + full_text_search
117 102 else
118   - (searching.size == 1) ? 20 : 6
  103 + @results[@asset] = date_range ? environment.events.by_range(date_range) : environment.events
119 104 end
120   - end
121   -
122   - public
123   -
124   - include SearchHelper
125   -
126   - ######################################################
127   -
128   - def where_to_search
129   - [
130   - [ :articles, N_('Articles') ],
131   - [ :enterprises, N_('Enterprises') ],
132   - [ :people, N_('People') ],
133   - [ :communities, N_('Communities') ],
134   - [ :products, N_('Products') ],
135   - [ :events, N_('Events') ]
136   - ].select {|key, name| !environment.enabled?('disable_asset_' + key.to_s) }
137   - end
138 105  
139   - def cities
140   - @cities = City.find(:all, :order => 'name', :conditions => ['parent_id = ? and lat is not null and lng is not null', params[:state_id]])
141   - render :action => 'cities', :layout => false
142   - end
143   -
144   - def complete_region
145   - # FIXME this logic should be in the model
146   - @regions = Region.find(:all, :conditions => [ '(name like ? or name like ?) and lat is not null and lng is not null', '%' + params[:region][:name] + '%', '%' + params[:region][:name].capitalize + '%' ])
147   - render :action => 'complete_region', :layout => false
  106 + events = @results[@asset]
  107 + @calendar = populate_calendar(date, events)
  108 + @previous_calendar = populate_calendar(date - 1.month, events)
  109 + @next_calendar = populate_calendar(date + 1.month, events)
148 110 end
149 111  
150 112 def index
151   - @query = params[:query] || ''
152   - @filtered_query = remove_stop_words(@query)
153   - @product_category = ProductCategory.find(params[:product_category]) if params[:product_category]
154   -
155   - @region = City.find_by_id(params[:city]) if !params[:city].blank? && params[:city] =~ /^\d+$/
156   -
157   - # how many assets we are searching for?
158   - number_of_result_assets = @searching.values.select{|v| v}.size
159   -
160 113 @results = {}
161 114 @order = []
162 115 @names = {}
  116 + @results_only = true
163 117  
164   - where_to_search.select { |key,description| @searching[key] }.each do |key, description|
  118 + @enabled_searches.select { |key,description| @searching[key] }.each do |key, description|
  119 + load_query
  120 + @asset = key
  121 + send(key)
165 122 @order << key
166   - @results[key] = @noosfero_finder.find(key, @filtered_query, calculate_find_options(key, limit, params[:page], @product_category, @region, params[:radius], params[:year], params[:month]))
167 123 @names[key] = getterm(description)
168 124 end
  125 + @asset = nil
  126 + @facets = {}
169 127  
170   - if @results.keys.size == 1
171   - specific_action = @results.keys.first
172   - if respond_to?(specific_action)
173   - @asset_name = getterm(@names[@results.keys.first])
174   - send(specific_action)
175   - render :action => specific_action
176   - return
177   - end
178   - end
179   -
180   - render :action => 'index'
  128 + render :action => @results.keys.first if @results.keys.size == 1
181 129 end
182 130  
183   - alias :assets :index
184   -
185   - #######################################################
  131 + # keep old URLs workings
  132 + def assets
  133 + params[:action] = params[:asset].is_a?(Array) ? :index : params.delete(:asset)
  134 + redirect_to params
  135 + end
186 136  
187 137 # view the summary of one category
188 138 def category_index
189 139 @results = {}
190 140 @order = []
191 141 @names = {}
  142 + limit = MULTIPLE_SEARCH_LIMIT
192 143 [
193   - [ :people, _('People'), @noosfero_finder.recent('people', limit) ],
194   - [ :enterprises, __('Enterprises'), @noosfero_finder.recent('enterprises', limit) ],
195   - [ :products, _('Products'), @noosfero_finder.recent('products', limit) ],
196   - [ :events, _('Upcoming events'), @noosfero_finder.upcoming_events({:per_page => limit}) ],
197   - [ :communities, __('Communities'), @noosfero_finder.recent('communities', limit) ],
198   - [ :most_commented_articles, _('Most commented articles'), @noosfero_finder.most_commented_articles(limit) ],
199   - [ :articles, _('Articles'), @noosfero_finder.recent('text_articles', limit) ]
200   - ].each do |key, name, list|
201   - @order << key
202   - @results[key] = list
203   - @names[key] = name
  144 + [ :people, _('People'), :recent_people ],
  145 + [ :enterprises, _('Enterprises'), :recent_enterprises ],
  146 + [ :products, _('Products'), :recent_products ],
  147 + [ :events, _('Upcoming events'), :upcoming_events ],
  148 + [ :communities, _('Communities'), :recent_communities ],
  149 + [ :articles, _('Contents'), :recent_articles ]
  150 + ].each do |asset, name, filter|
  151 + @order << asset
  152 + @results[asset] = @category.send(filter, limit)
  153 + raise "No total_entries for: #{asset}" unless @results[asset].respond_to?(:total_entries)
  154 + @names[asset] = name
204 155 end
205 156 end
206   - attr_reader :category
207 157  
208 158 def tags
209 159 @tags_cache_key = "tags_env_#{environment.id.to_s}"
... ... @@ -216,25 +166,137 @@ class SearchController &lt; PublicController
216 166 @tag = params[:tag]
217 167 @tag_cache_key = "tag_#{CGI.escape(@tag.to_s)}_env_#{environment.id.to_s}_page_#{params[:npage]}"
218 168 if is_cache_expired?(@tag_cache_key)
219   - @tagged = environment.articles.find_tagged_with(@tag).paginate(:per_page => 10, :page => params[:npage])
  169 + @results[@asset] = environment.articles.find_tagged_with(@tag).paginate(paginate_options)
220 170 end
221 171 end
222 172  
  173 + def events_by_day
  174 + @selected_day = build_date(params[:year], params[:month], params[:day])
  175 + @events_of_the_day = environment.events.by_day(@selected_day)
  176 + render :partial => 'events/events_by_day'
  177 + end
  178 +
223 179 #######################################################
  180 + protected
  181 +
  182 + def load_query
  183 + @asset = params[:action].to_sym
  184 + @order ||= [@asset]
  185 + @results ||= {}
  186 + @filter = filter
  187 + @filter_title = filter_description(@asset, @filter)
224 188  
225   - def popup
226   - @regions = Region.find(:all).select{|r|r.lat && r.lng}
227   - render :action => 'popup', :layout => false
  189 + @query = params[:query] || ''
  190 + @empty_query = @category.nil? && @query.blank?
228 191 end
229 192  
230   - def events_by_day
231   - @selected_day = build_date(params[:year], params[:month], params[:day])
232   - if params[:category_id] and Category.exists?(params[:category_id])
233   - @events_of_the_day = environment.events.by_day(@selected_day).in_category(Category.find(params[:category_id]))
  193 + def load_category
  194 + if params[:category_path].blank?
  195 + render_not_found if params[:action] == 'category_index'
234 196 else
235   - @events_of_the_day = environment.events.by_day(@selected_day)
  197 + path = params[:category_path].join('/')
  198 + @category = environment.categories.find_by_path(path)
  199 + if @category.nil?
  200 + render_not_found(path)
  201 + else
  202 + @category_id = @category.id
  203 + end
236 204 end
237   - render :partial => 'events/events_by_day'
  205 + end
  206 +
  207 + FILTERS = %w(
  208 + more_recent
  209 + more_active
  210 + more_popular
  211 + )
  212 + def filter
  213 + if FILTERS.include?(params[:filter])
  214 + params[:filter]
  215 + else
  216 + 'more_recent'
  217 + end
  218 + end
  219 +
  220 + def filter_description(asset, filter)
  221 + {
  222 + 'articles_more_recent' => _('More recent contents from network'),
  223 + 'articles_more_popular' => _('More viewed contents from network'),
  224 + 'people_more_recent' => _('More recent people from network'),
  225 + 'people_more_active' => _('More active people from network'),
  226 + 'people_more_popular' => _('More popular people from network'),
  227 + 'communities_more_recent' => _('More recent communities from network'),
  228 + 'communities_more_active' => _('More active communities from network'),
  229 + 'communities_more_popular' => _('More popular communities from network'),
  230 + 'products_more_recent' => _('Highlights'),
  231 + }[asset.to_s + '_' + filter]
  232 + end
  233 +
  234 + def load_search_assets
  235 + if Searches.keys.include?(params[:action].to_sym) and environment.enabled?("disable_asset_#{params[:action]}")
  236 + render_not_found
  237 + return
  238 + end
  239 +
  240 + @enabled_searches = Searches.select {|key, name| environment.disabled?("disable_asset_#{params[:action]}") }
  241 + @searching = {}
  242 + @titles = {}
  243 + @enabled_searches.each do |key, name|
  244 + @titles[key] = name
  245 + @searching[key] = params[:action] == 'index' || params[:action] == key.to_s
  246 + end
  247 + @names = @titles if @names.nil?
  248 + end
  249 +
  250 + def limit
  251 + searching = @searching.values.select{ |v| v }
  252 + if params[:display] == 'map'
  253 + MAP_SEARCH_LIMIT
  254 + elsif searching.size <= 1
  255 + if [:people, :communities].include? @asset
  256 + BLOCKS_SEARCH_LIMIT
  257 + elsif @asset == :enterprises and @empty_query
  258 + BLOCKS_SEARCH_LIMIT
  259 + else
  260 + LIST_SEARCH_LIMIT
  261 + end
  262 + else
  263 + MULTIPLE_SEARCH_LIMIT
  264 + end
  265 + end
  266 +
  267 + def paginate_options(page = params[:page])
  268 + { :per_page => limit, :page => page }
  269 + end
  270 +
  271 + def full_text_search(filters = [], options = {})
  272 + paginate_options = paginate_options(params[:page])
  273 + asset_class = asset_class(@asset)
  274 +
  275 + solr_options = options
  276 + if !@results_only and asset_class.respond_to? :facets
  277 + solr_options.merge! asset_class.facets_find_options(params[:facet])
  278 + solr_options[:all_facets] = true
  279 + solr_options[:limit] = 0 if @facets_only
  280 + end
  281 + solr_options[:filter_queries] ||= []
  282 + solr_options[:filter_queries] += filters
  283 + solr_options[:filter_queries] << "environment_id:#{environment.id}"
  284 + solr_options[:filter_queries] << asset_class.facet_category_query.call(@category) if @category
  285 +
  286 + solr_options[:boost_functions] ||= []
  287 + params[:order_by] = nil if params[:order_by] == 'none'
  288 + if params[:order_by]
  289 + order = SortOptions[@asset][params[:order_by].to_sym]
  290 + raise "Unknown order by" if order.nil?
  291 + order[:solr_opts].each do |opt, value|
  292 + solr_options[opt] = value.is_a?(Proc) ? instance_eval(&value) : value
  293 + end
  294 + end
  295 +
  296 + ret = asset_class.find_by_contents(@query, paginate_options, solr_options)
  297 + @results[@asset] = ret[:results]
  298 + @facets = ret[:facets]
  299 + @all_facets = ret[:all_facets]
238 300 end
239 301  
240 302 end
... ...
app/helpers/application_helper.rb
... ... @@ -4,7 +4,7 @@ require &#39;redcloth&#39;
4 4 # application.
5 5 module ApplicationHelper
6 6  
7   - include PermissionName
  7 + include PermissionNameHelper
8 8  
9 9 include LightboxHelper
10 10  
... ... @@ -574,7 +574,7 @@ module ApplicationHelper
574 574 end
575 575 extra_info = extra_info.nil? ? '' : content_tag( 'span', extra_info, :class => 'extra_info' )
576 576 links = links_for_balloon(profile)
577   - content_tag tag,
  577 + content_tag('div', content_tag(tag,
578 578 (environment.enabled?(:show_balloon_with_profile_links_when_clicked) ? link_to( content_tag( 'span', _('Profile links')), '#', :onclick => "toggleSubmenu(this, '#{profile.short_name}', #{links.to_json}); return false", :class => "menu-submenu-trigger #{trigger_class}", :url => url) : "") +
579 579 link_to(
580 580 content_tag( 'span', profile_image( profile, size ), :class => 'profile-image' ) +
... ... @@ -584,7 +584,7 @@ module ApplicationHelper
584 584 :class => 'profile_link url',
585 585 :help => _('Click on this icon to go to the <b>%s</b>\'s home page') % profile.name,
586 586 :title => profile.name ),
587   - :class => 'vcard'
  587 + :class => 'vcard'), :class => 'common-profile-list-block')
588 588 end
589 589  
590 590 def gravatar_url_for(email, options = {})
... ... @@ -890,22 +890,6 @@ module ApplicationHelper
890 890 result
891 891 end
892 892  
893   - def search_page_title(title, options={})
894   - title = "<h1>" + title + "</h1>"
895   - title += "<h2 class='query'>" + _("Searched for '%s'") % options[:query] + "</h2>" if !options[:query].blank?
896   - title += "<h2 class='query'>" + _("In category %s") % options[:category] + "</h2>" if !options[:category].blank?
897   - title += "<h2 class='query'>" + _("within %d km from %s") % [options[:distance], options[:region]] + "</h2>" if !options[:distance].blank? && !options[:region].blank?
898   - title += "<h2 class='query'>" + _("%d results found") % options[:total_results] + "</h2>" if !options[:total_results].blank?
899   - title
900   - end
901   -
902   - def search_page_link_to_all(options={})
903   - if options[:category]
904   - title = "<div align='center'>" + _('In all categories') + "</div>"
905   - link_to title, :action => 'assets', :asset => options[:asset], :category_path => []
906   - end
907   - end
908   -
909 893 def template_stylesheet_path
910 894 if profile.nil?
911 895 "/designs/templates/#{environment.layout_template}/stylesheets/style.css"
... ... @@ -982,6 +966,7 @@ module ApplicationHelper
982 966 def noosfero_stylesheets
983 967 [
984 968 'application',
  969 + 'search',
985 970 'thickbox',
986 971 'lightbox',
987 972 'colorpicker',
... ... @@ -1001,7 +986,7 @@ module ApplicationHelper
1001 986 end
1002 987  
1003 988 def tokeninput_stylesheets
1004   - ['token-input', 'token-input-facebook', 'token-input-mac']
  989 + ['token-input', 'token-input-facebook', 'token-input-mac', 'token-input-facet']
1005 990 end
1006 991  
1007 992 def noosfero_layout_features
... ... @@ -1100,35 +1085,51 @@ module ApplicationHelper
1100 1085 ") : '')
1101 1086 end
1102 1087  
1103   - def browse_people_menu
  1088 + def search_contents_menu
  1089 + links = [
  1090 + {s_('contents|More Recent') => {:href => url_for({:controller => 'search', :action => 'contents', :filter => 'more_recent'})}},
  1091 + {s_('contents|More Viewed') => {:href => url_for({:controller => 'search', :action => 'contents', :filter => 'more_popular'})}}
  1092 + ]
  1093 + if logged_in?
  1094 + links.push(_('New Content') => lightbox_options({:href => url_for({:controller => 'cms', :action => 'new', :profile => current_user.login, :cms => true})}))
  1095 + end
  1096 +
  1097 + link_to(content_tag(:span, _('Contents'), :class => 'icon-menu-articles'), {:controller => "search", :action => 'contents', :category_path => ''}, :id => 'submenu-contents') +
  1098 + link_to(content_tag(:span, _('Contents Menu')), '#', :onclick => "toggleSubmenu(this,'',#{links.to_json}); return false", :class => 'menu-submenu-trigger up', :id => 'submenu-contents-trigger')
  1099 + end
  1100 + alias :browse_contents_menu :search_contents_menu
  1101 +
  1102 + def search_people_menu
1104 1103 links = [
1105   - {s_('people|More Recent') => {:href => url_for({:controller => 'browse', :action => 'people', :filter => 'more_recent'})}},
1106   - {s_('people|More Active') => {:href => url_for({:controller => 'browse', :action => 'people', :filter => 'more_active'})}},
1107   - {s_('people|More Popular') => {:href => url_for({:controller => 'browse', :action => 'people', :filter => 'more_popular'})}}
  1104 + {s_('people|More Recent') => {:href => url_for({:controller => 'search', :action => 'people', :filter => 'more_recent'})}},
  1105 + {s_('people|More Active') => {:href => url_for({:controller => 'search', :action => 'people', :filter => 'more_active'})}},
  1106 + {s_('people|More Popular') => {:href => url_for({:controller => 'search', :action => 'people', :filter => 'more_popular'})}}
1108 1107 ]
1109 1108 if logged_in?
1110 1109 links.push(_('My friends') => {:href => url_for({:profile => current_user.login, :controller => 'friends'})})
1111 1110 links.push(_('Invite friends') => {:href => url_for({:profile => current_user.login, :controller => 'invite', :action => 'friends'})})
1112 1111 end
1113 1112  
1114   - link_to(content_tag(:span, _('People'), :class => 'icon-menu-people'), {:controller => "browse", :action => 'people'}, :id => 'submenu-people') +
  1113 + link_to(content_tag(:span, _('People'), :class => 'icon-menu-people'), {:controller => "search", :action => 'people', :category_path => ''}, :id => 'submenu-people') +
1115 1114 link_to(content_tag(:span, _('People Menu')), '#', :onclick => "toggleSubmenu(this,'',#{links.to_json}); return false", :class => 'menu-submenu-trigger up', :id => 'submenu-people-trigger')
1116 1115 end
  1116 + alias :browse_people_menu :search_people_menu
1117 1117  
1118   - def browse_communities_menu
  1118 + def search_communities_menu
1119 1119 links = [
1120   - {s_('communities|More Recent') => {:href => url_for({:controller => 'browse', :action => 'communities', :filter => 'more_recent'})}},
1121   - {s_('communities|More Active') => {:href => url_for({:controller => 'browse', :action => 'communities', :filter => 'more_active'})}},
1122   - {s_('communities|More Popular') => {:href => url_for({:controller => 'browse', :action => 'communities', :filter => 'more_popular'})}}
  1120 + {s_('communities|More Recent') => {:href => url_for({:controller => 'search', :action => 'communities', :filter => 'more_recent'})}},
  1121 + {s_('communities|More Active') => {:href => url_for({:controller => 'search', :action => 'communities', :filter => 'more_active'})}},
  1122 + {s_('communities|More Popular') => {:href => url_for({:controller => 'search', :action => 'communities', :filter => 'more_popular'})}}
1123 1123 ]
1124 1124 if logged_in?
1125 1125 links.push(_('My communities') => {:href => url_for({:profile => current_user.login, :controller => 'memberships'})})
1126 1126 links.push(_('New community') => {:href => url_for({:profile => current_user.login, :controller => 'memberships', :action => 'new_community'})})
1127 1127 end
1128 1128  
1129   - link_to(content_tag(:span, _('Communities'), :class => 'icon-menu-community'), {:controller => "browse", :action => 'communities'}, :id => 'submenu-communities') +
  1129 + link_to(content_tag(:span, _('Communities'), :class => 'icon-menu-community'), {:controller => "search", :action => 'communities'}, :id => 'submenu-communities') +
1130 1130 link_to(content_tag(:span, _('Communities Menu')), '#', :onclick => "toggleSubmenu(this,'',#{links.to_json}); return false", :class => 'menu-submenu-trigger up', :id => 'submenu-communities-trigger')
1131 1131 end
  1132 + alias :browse_communities_menu :search_communities_menu
1132 1133  
1133 1134 def browse_contents_menu
1134 1135 links = [
... ... @@ -1217,15 +1218,12 @@ module ApplicationHelper
1217 1218 else _('1 minute')
1218 1219 end
1219 1220  
1220   - when 2..44 then _('%{distance} minutes') % { :distance => distance_in_minutes }
1221   - when 45..89 then _('about 1 hour')
1222   - when 90..1439 then _('about %{distance} hours') % { :distance => (distance_in_minutes.to_f / 60.0).round }
1223   - when 1440..2879 then _('1 day')
1224   - when 2880..43199 then _('%{distance} days') % { :distance => (distance_in_minutes / 1440).round }
1225   - when 43200..86399 then _('about 1 month')
1226   - when 86400..525599 then _('%{distance} months') % { :distance => (distance_in_minutes / 43200).round }
1227   - when 525600..1051199 then _('about 1 year')
1228   - else _('over %{distance} years') % { :distance => (distance_in_minutes / 525600).round }
  1221 + when 2..44 then _('%{distance} minutes ago') % { :distance => distance_in_minutes }
  1222 + when 45..89 then _('about 1 hour ago')
  1223 + when 90..1439 then _('about %{distance} hours ago') % { :distance => (distance_in_minutes.to_f / 60.0).round }
  1224 + when 1440..2879 then _('1 day ago')
  1225 + when 2880..10079 then _('%{distance} days ago') % { :distance => (distance_in_minutes / 1440).round }
  1226 + else show_time(from_time)
1229 1227 end
1230 1228 end
1231 1229  
... ... @@ -1330,6 +1328,10 @@ module ApplicationHelper
1330 1328 end
1331 1329 end
1332 1330  
  1331 + def jquery_token_input_messages_json(hintText = _('Type in an keyword'), noResultsText = _('No results'), searchingText = _('Searching...'))
  1332 + "hintText: '#{hintText}', noResultsText: '#{noResultsText}', searchingText: '#{searchingText}'"
  1333 + end
  1334 +
1333 1335 def delete_article_message(article)
1334 1336 if article.folder?
1335 1337 _("Are you sure that you want to remove the folder \"#{article.name}\"? Note that all the items inside it will also be removed!")
... ...
app/helpers/display_helper.rb
... ... @@ -26,15 +26,19 @@ module DisplayHelper
26 26 product.enterprise.enabled? ? product.enterprise.public_profile_url.merge(:controller => 'manage_products', :action => 'show', :id => product) : product.enterprise.url
27 27 end
28 28  
29   - def link_to_category(category, full = true)
  29 + def link_to_tag(tag, html_options = {})
  30 + link_to tag.name, {:controller => 'search', :action => 'tag', :tag => tag.name}, html_options
  31 + end
  32 +
  33 + def link_to_category(category, full = true, html_options = {})
30 34 return _('Uncategorized product') unless category
31 35 name = full ? category.full_name(' &rarr; ') : category.name
32   - link_to name, Noosfero.url_options.merge({:controller => 'search', :action => 'category_index', :category_path => category.path.split('/'),:host => category.environment.default_hostname })
  36 + link_to name, Noosfero.url_options.merge({:controller => 'search', :action => 'category_index', :category_path => category.path.split('/'),:host => category.environment.default_hostname }), html_options
33 37 end
34 38  
35 39 def link_to_product_category(category)
36 40 if category
37   - link_to(category.name, :controller => 'search', :action => 'assets', :asset => 'products', :product_category => category.id, :host => category.environment.default_hostname)
  41 + link_to(category.name, :controller => 'search', :action => 'products', :category_path => category.explode_path)
38 42 else
39 43 _('Uncategorized product')
40 44 end
... ...
app/helpers/forum_helper.rb
... ... @@ -42,9 +42,9 @@ module ForumHelper
42 42 def last_topic_update(article)
43 43 info = article.info_from_last_update
44 44 if info[:author_url]
45   - time_ago_as_sentence(info[:date]) + ' ' + _('ago') + ' ' + _('by') + ' ' + link_to(info[:author_name], info[:author_url])
  45 + time_ago_as_sentence(info[:date]) + ' ' + _('by') + ' ' + link_to(info[:author_name], info[:author_url])
46 46 else
47   - time_ago_as_sentence(info[:date]) + ' ' + _('ago') + ' ' + _('by') + ' ' + info[:author_name]
  47 + time_ago_as_sentence(info[:date]) + ' ' + _('by') + ' ' + info[:author_name]
48 48 end
49 49 end
50 50  
... ...
app/helpers/lightbox_helper.rb
... ... @@ -22,10 +22,7 @@ module LightboxHelper
22 22 def lightbox_options(options, lightbox_type = 'lbOn')
23 23 the_class = lightbox_type
24 24 the_class << " #{options[:class]}" if options.has_key?(:class)
25   - options.merge(
26   - :class => the_class,
27   - :onclick => 'alert("%s"); return false' % _('Please, try again when the page loading completes.')
28   - )
  25 + options.merge(:class => the_class)
29 26 end
30 27  
31 28 def lightbox?
... ...
app/helpers/manage_products_helper.rb
... ... @@ -241,6 +241,10 @@ module ManageProductsHelper
241 241 end
242 242 end
243 243  
  244 + def remove_qualifier_button
  245 + button_to_function(:delete, content_tag('span', _('Delete qualifier')), "jQuery(this).parents('tr').remove()")
  246 + end
  247 +
244 248 def select_unit(object)
245 249 collection_select(object.class.name.downcase, :unit_id, environment.units, :id, :singular, {:include_blank => _('Select the unit')})
246 250 end
... ... @@ -278,7 +282,7 @@ module ManageProductsHelper
278 282 error_msg = _('Something went wrong. Please, try again')
279 283 select_tag('price_details[][production_cost_id]',
280 284 '<option value="" disabled="disabled">' + _('Select...') + '</option>' +
281   - options_for_select(product.available_production_costs.map {|item| [truncate(item.name, 10, '...'), item.id]} + [[_('Other cost'), '']], selected),
  285 + options_for_select(product.available_production_costs.map {|item| [truncate(item.name, {:length => 10, :omission => '...'}), item.id]} + [[_('Other cost'), '']], selected),
282 286 {:class => 'production-cost-selection',
283 287 :onchange => "productionCostTypeChange(this, '#{url}', '#{prompt_msg}', '#{error_msg}')"})
284 288 end
... ...
app/helpers/product_category_viewer_helper.rb
... ... @@ -1,2 +0,0 @@
1   -module ProductCategoryViewerHelper
2   -end
app/helpers/search_helper.rb
1 1 module SearchHelper
2 2  
  3 + MAP_SEARCH_LIMIT = 2000
  4 + LIST_SEARCH_LIMIT = 20
  5 + BLOCKS_SEARCH_LIMIT = 18
  6 + MULTIPLE_SEARCH_LIMIT = 8
  7 + DistFilt = 200
  8 + DistBoost = 50
  9 +
  10 + Searches = ActiveSupport::OrderedHash[
  11 + :articles, _('Contents'),
  12 + :enterprises, _('Enterprises'),
  13 + :people, _('People'),
  14 + :communities, _('Communities'),
  15 + :products, _('Products and Services'),
  16 + :events, _('Events'),
  17 + ]
  18 +
  19 + SortOptions = {
  20 + :products => ActiveSupport::OrderedHash[ :none, {:label => _('Relevance')},
  21 + :more_recent, {:label => _('More Recent'), :solr_opts => {:sort => 'updated_at desc, score desc'}},
  22 + :name, {:label => _('Name'), :solr_opts => {:sort => 'name_sortable asc'}},
  23 + :closest, {:label => _('Closest to me'), :if => proc{ logged_in? && (profile=current_user.person).lat && profile.lng },
  24 + :solr_opts => {:sort => "geodist() asc",
  25 + :latitude => proc{ current_user.person.lat }, :longitude => proc{ current_user.person.lng }}},
  26 + ],
  27 + :events => ActiveSupport::OrderedHash[ :none, {:label => _('Relevance')},
  28 + :name, {:label => _('Name'), :solr_opts => {:sort => 'name_sortable asc'}},
  29 + ],
  30 + :articles => ActiveSupport::OrderedHash[ :none, {:label => _('Relevance')},
  31 + :name, {:label => _('Name'), :solr_opts => {:sort => 'name_sortable asc'}},
  32 + :more_recent, {:label => _('More recent'), :solr_opts => {:sort => 'updated_at desc, score desc'}},
  33 + ],
  34 + :enterprises => ActiveSupport::OrderedHash[ :none, {:label => _('Relevance')},
  35 + :name, {:label => _('Name'), :solr_opts => {:sort => 'name_sortable asc'}},
  36 + ],
  37 + :people => ActiveSupport::OrderedHash[ :none, {:label => _('Relevance')},
  38 + :name, {:label => _('Name'), :solr_opts => {:sort => 'name_sortable asc'}},
  39 + ],
  40 + :communities => ActiveSupport::OrderedHash[ :none, {:label => _('Relevance')},
  41 + :name, {:label => _('Name'), :solr_opts => {:sort => 'name_sortable asc'}},
  42 + ],
  43 + }
  44 +
3 45 # FIXME remove it after search_controler refactored
4 46 include EventsHelper
5 47  
6   - STOP_WORDS = {
7   - 'pt_BR' => Ferret::Analysis::FULL_PORTUGUESE_STOP_WORDS,
8   - 'en' => Ferret::Analysis::FULL_ENGLISH_STOP_WORDS,
9   - }
10   -
11   - def relevance_for(hit)
12   - n = (hit.ferret_score if hit.respond_to?(:ferret_score))
13   - n ||= 1.0
14   - (n * 100.0).round
  48 + def search_page_title(title, category = nil)
  49 + title = "<h1>" + title
  50 + title += '<small>' + category.name + '</small>' if category
  51 + title + "</h1>"
15 52 end
16 53  
17   - def remove_stop_words(query)
18   - (query.downcase.scan(/"[^"]*"?|'[^']*'?|[^'"\s]+/) - (STOP_WORDS[locale] || [])).join(' ')
  54 + def category_context(category, url)
  55 + content_tag('div', category.full_name + _(', ') +
  56 + link_to(_('search in all categories'),
  57 + url.merge(:category_path => [], :action => (params[:action] == 'category_index' ? 'index' : params[:action]) )),
  58 + :align => 'center', :class => 'search-category-context') if category
19 59 end
20 60  
21   - def display_results(use_map = true)
22   -
23   - unless use_map && GoogleMaps.enabled?(environment.default_hostname)
24   - return render(:partial => 'display_results')
  61 + def display_results(use_map = false)
  62 + if params[:display] == 'map' && use_map
  63 + partial = 'google_maps'
  64 + klass = 'map'
  65 + else
  66 + partial = 'display_results'
  67 + klass = 'list'
25 68 end
26 69  
27   - data =
28   - if params[:display] == 'map'
29   - {
30   - :partial => 'google_maps',
31   - :toggle => button(:search, _('Display in list'), params.merge(:display => 'list'), :class => "map-toggle-button" ),
32   - :class => 'map' ,
33   - }
  70 + content_tag('div', render(:partial => partial), :class => "map-or-list-search-results #{klass}")
  71 + end
  72 +
  73 + def display_map_list_button
  74 + button(:search, params[:display] == 'map' ? _('Display in list') : _('Display in map'),
  75 + params.merge(:display => (params[:display] == 'map' ? 'list' : 'map')),
  76 + :class => "map-toggle-button" )
  77 + end
  78 +
  79 + def city_with_state(city)
  80 + if city and city.kind_of?(City)
  81 + s = city.parent
  82 + if s and s.kind_of?(State) and s.acronym
  83 + city.name + ', ' + s.acronym
34 84 else
35   - {
36   - :partial => 'display_results',
37   - :toggle => button(:search, _('Display in map'), params.merge(:display => 'map'), :class => "map-toggle-button" ),
38   - :class => 'list' ,
39   - }
  85 + city.name
40 86 end
  87 + else
  88 + nil
  89 + end
  90 + end
41 91  
42   - content_tag('div', data[:toggle] + (render :partial => data[:partial]), :class => "map-or-list-search-results #{data[:class]}")
  92 + def facets_menu(asset, _facets)
  93 + @asset_class = asset_class(asset)
  94 + @facets = _facets
  95 + render(:partial => 'facets_menu')
43 96 end
44 97  
45   - def display_item_map_info(item)
46   - if item.kind_of?(Profile)
47   - display_profile_info(item)
48   - elsif item.kind_of?(Product)
49   - display_product_info(item)
50   - end
  98 + def facets_unselect_menu(asset)
  99 + @asset_class = asset_class(asset)
  100 + render(:partial => 'facets_unselect_menu')
51 101 end
52 102  
53   - def display_profile_info(profile)
54   - data = ''
55   - unless profile.contact_email.nil?
56   - data << content_tag('strong', _('E-Mail: ')) + profile.contact_email + '<br/>'
57   - end
58   - unless profile.contact_phone.nil?
59   - data << content_tag('strong', _('Phone(s): ')) + profile.contact_phone + '<br/>'
60   - end
61   - unless profile.region.nil?
62   - data << content_tag('strong', _('Location: ')) + profile.region.name + '<br/>'
63   - end
64   - unless profile.address.nil?
65   - data << content_tag('strong', _('Address: ')) + profile.address + '<br/>'
66   - end
67   - unless profile.products.empty?
68   - data << content_tag('strong', _('Products/Services: ')) + profile.products.map{|i| link_to(i.name, :controller => 'manage_products', :profile => profile.identifier, :action => 'show', :id => i.id)}.join(', ') + '<br/>'
69   - end
70   - if profile.respond_to?(:distance) and !profile.distance.nil?
71   - data << content_tag('strong', _('Distance: ')) + "%.2f%" % profile.distance + '<br/>'
72   - end
73   - content_tag('table',
74   - content_tag('tr',
75   - content_tag('td', content_tag('div', profile_image(profile, :thumb), :class => 'profile-info-picture')) +
76   - content_tag('td', content_tag('strong', link_to(profile.name, url_for(profile.url))) + '<br/>' + data
77   - )
78   - ),
79   - :class => 'profile-info'
80   - )
  103 + def facet_javascript(input_id, facet, array)
  104 + array = [] if array.nil?
  105 + hintText = _('Type in an option')
  106 + text_field_tag('facet['+input_id+']', '', :id => input_id) +
  107 + javascript_tag("jQuery.TokenList(jQuery('##{input_id}'), #{array.to_json},
  108 + {searchDelay: 0, permanentDropdown: true, theme: 'facet', dontAdd: true, preventDuplicates: true,
  109 + #{jquery_token_input_messages_json(hintText)}});")
81 110 end
82 111  
83   - def display_product_info(product)
84   - data = ''
85   - unless product.price.nil?
86   - data << content_tag('strong', _('Price: ')) + product.price + '<br/>'
87   - end
88   - unless product.enterprise.nil?
89   - data << content_tag('strong', _('Provider: ')) + link_to_profile(product.enterprise.name, product.enterprise.identifier)
90   - end
91   - unless product.product_category.nil?
92   - data << content_tag('strong', _('Category: ')) + link_to(product.product_category.name, :controller => 'search', :action => 'assets', :asset => 'products', :product_category => product.product_category.id)
  112 + def facet_link_html(facet, params, value, label, count)
  113 + params = params ? params.dup : {}
  114 + has_extra = label.kind_of?(Array)
  115 + link_label = has_extra ? label[0] : label
  116 + id = facet[:solr_field].to_s
  117 + params[:facet] ||= {}
  118 + params[:facet][id] ||= {}
  119 + params[:page] = {} if params[:page]
  120 +
  121 + selected = facet[:label_id].nil? ? params[:facet][id] == value : params[:facet][id][facet[:label_id]].to_a.include?(value)
  122 +
  123 + if count > 0
  124 + url = params.merge(:facet => params[:facet].merge(
  125 + id => facet[:label_id].nil? ? value : params[:facet][id].merge( facet[:label_id] => params[:facet][id][facet[:label_id]].to_a | [value] )
  126 + ))
  127 + else
  128 + # preserve others filters and change this filter
  129 + url = params.merge(:facet => params[:facet].merge(
  130 + id => facet[:label_id].nil? ? value : { facet[:label_id] => value }
  131 + ))
93 132 end
94   - content_tag('table',
95   - content_tag('tr',
96   - content_tag('td', content_tag('div', image_tag(product.image ? product.image.public_filename(:thumb) : '/images/icons-app/product-default-pic-portrait.png'), :class => 'profile-info-picture')) +
97   - content_tag('td', content_tag('strong', link_to(product.name, :controller => 'catalog', :profile => product.enterprise.identifier, :action => 'show', :id => product)) + '<br/>' + data)
98   - ), :class => 'profile-info')
  133 +
  134 + content_tag 'div', link_to(link_label, url, :class => 'facet-result-link-label') +
  135 + content_tag('span', (has_extra ? label[1] : ''), :class => 'facet-result-extra-label') +
  136 + (count > 0 ? content_tag('span', " (#{count})", :class => 'facet-result-count') : ''),
  137 + :class => 'facet-menu-item' + (selected ? ' facet-result-link-selected' : '')
99 138 end
100 139  
101   - def product_categories_menu(asset, product_category, object_ids = nil)
102   - cats = ProductCategory.menu_categories(@product_category, environment)
103   - cats += cats.select { |c| c.children_count > 0 }.map(&:children).flatten
104   - product_categories_ids = cats.map(&:id)
105   -
106   - counts = @noosfero_finder.product_categories_count(asset, product_categories_ids, object_ids)
107   -
108   - product_categories_menu = ProductCategory.menu_categories(product_category, environment).map do |cat|
109   - hits = counts[cat.id]
110   - childs = []
111   - if hits
112   - if cat.children_count > 0
113   - childs = cat.children.map do |child|
114   - child_hits = counts[child.id]
115   - [child, child_hits]
116   - end.select{|child, child_hits| child_hits }
117   - else
118   - childs = []
  140 + def facet_selecteds_html_for(environment, klass, params)
  141 + def name_with_extra(klass, facet, value)
  142 + name = klass.facet_result_name(facet, value)
  143 + name = name[0] + name[1] if name.kind_of?(Array)
  144 + name
  145 + end
  146 +
  147 + ret = []
  148 + params = params.dup
  149 + params[:facet].each do |id, value|
  150 + facet = klass.facet_by_id(id.to_sym)
  151 + if value.kind_of?(Hash)
  152 + label_hash = facet[:label].call(environment)
  153 + value.each do |label_id, value|
  154 + facet[:label_id] = label_id
  155 + facet[:label] = label_hash[label_id]
  156 + value.to_a.each do |value|
  157 + ret << [facet[:label], name_with_extra(klass, facet, value),
  158 + params.merge(:facet => params[:facet].merge(id => params[:facet][id].merge(label_id => params[:facet][id][label_id].to_a.reject{ |v| v == value })))]
  159 + end
119 160 end
  161 + else
  162 + ret << [klass.facet_label(facet), name_with_extra(klass, facet, value),
  163 + params.merge(:facet => params[:facet].reject{ |k,v| k == id })]
120 164 end
121   - [cat, hits, childs]
122   - end.select{|cat, hits| hits }
  165 + end
  166 +
  167 + ret.map do |label, name, url|
  168 + content_tag('div', content_tag('span', label, :class => 'facet-selected-label') +
  169 + content_tag('span', name, :class => 'facet-selected-name') +
  170 + link_to('', url, :class => 'facet-selected-remove', :title => 'remove facet'), :class => 'facet-selected')
  171 + end.join
  172 + end
  173 +
  174 + def order_by(asset)
  175 + options = SortOptions[asset].map do |name, options|
  176 + next if options[:if] and ! instance_eval(&options[:if])
  177 + [_(options[:label]), name.to_s]
  178 + end.compact
  179 +
  180 + content_tag('div', _('Sort results by ') +
  181 + select_tag(asset.to_s + '[order]', options_for_select(options, params[:order_by] || 'none'),
  182 + {:onchange => "window.location = jQuery.param.querystring(window.location.href, { 'order_by' : this.options[this.selectedIndex].value})"}),
  183 + :class => "search-ordering")
  184 + end
  185 +
  186 + def label_total_found(asset, total_found)
  187 + labels = {
  188 + :products => _("%s products offers found"),
  189 + :articles => _("%s articles found"),
  190 + :events => _("%s events found"),
  191 + :people => _("%s people found"),
  192 + :enterprises => _("%s enterprises found"),
  193 + :communities => _("%s communities found"),
  194 + }
  195 + content_tag('span', labels[asset] % total_found,
  196 + :class => "total-pages-found") if labels[asset]
  197 + end
  198 +
  199 + def asset_class(asset)
  200 + asset.to_s.singularize.camelize.constantize
  201 + end
123 202  
124   - render(:partial => 'product_categories_menu', :object => product_categories_menu)
  203 + def asset_table(asset)
  204 + asset_class(asset).table_name
125 205 end
126 206  
127 207 end
... ...
app/models/action_tracker_notification.rb
... ... @@ -3,6 +3,8 @@ class ActionTrackerNotification &lt; ActiveRecord::Base
3 3 belongs_to :profile
4 4 belongs_to :action_tracker, :class_name => 'ActionTracker::Record', :foreign_key => 'action_tracker_id'
5 5  
  6 + has_many :comments, :through => :action_tracker, :class_name => 'Comment', :foreign_key => 'source_id'
  7 +
6 8 validates_presence_of :profile_id, :action_tracker_id
7 9 validates_uniqueness_of :action_tracker_id, :scope => :profile_id
8 10  
... ...
app/models/article.rb
... ... @@ -2,9 +2,13 @@ require &#39;hpricot&#39;
2 2  
3 3 class Article < ActiveRecord::Base
4 4  
5   - track_actions :create_article, :after_create, :keep_params => [:name, :url], :if => Proc.new { |a| a.is_trackable? && !a.image? }, :custom_target => :action_tracker_target
6   - track_actions :update_article, :before_update, :keep_params => [:name, :url], :if => Proc.new { |a| a.is_trackable? && (a.body_changed? || a.name_changed?) }, :custom_target => :action_tracker_target
7   - track_actions :remove_article, :before_destroy, :keep_params => [:name], :if => Proc.new { |a| a.is_trackable? }, :custom_target => :action_tracker_target
  5 + # use for internationalizable human type names in search facets
  6 + # reimplement on subclasses
  7 + def self.type_name
  8 + _('Content')
  9 + end
  10 +
  11 + track_actions :create_article, :after_create, :keep_params => [:name, :url, :lead, :first_image], :if => Proc.new { |a| a.is_trackable? && !a.image? }
8 12  
9 13 # xss_terminate plugin can't sanitize array fields
10 14 before_save :sanitize_tag_list
... ... @@ -17,11 +21,14 @@ class Article &lt; ActiveRecord::Base
17 21  
18 22 belongs_to :last_changed_by, :class_name => 'Person', :foreign_key => 'last_changed_by_id'
19 23  
20   - has_many :comments, :dependent => :destroy, :order => 'created_at asc'
  24 + has_many :comments, :class_name => 'Comment', :foreign_key => 'source_id', :dependent => :destroy, :order => 'created_at asc'
21 25  
22 26 has_many :article_categorizations, :conditions => [ 'articles_categories.virtual = ?', false ]
23 27 has_many :categories, :through => :article_categorizations
24 28  
  29 + has_many :article_categorizations_including_virtual, :class_name => 'ArticleCategorization', :dependent => :destroy
  30 + has_many :categories_including_virtual, :through => :article_categorizations_including_virtual, :source => :category
  31 +
25 32 acts_as_having_settings :field => :setting
26 33  
27 34 settings_items :display_hits, :type => :boolean, :default => true
... ... @@ -47,7 +54,7 @@ class Article &lt; ActiveRecord::Base
47 54 xss_terminate :only => [ :name ], :on => 'validation', :with => 'white_list'
48 55  
49 56 named_scope :in_category, lambda { |category|
50   - {:include => 'categories', :conditions => { 'categories.id' => category.id }}
  57 + {:include => 'categories_including_virtual', :conditions => { 'categories.id' => category.id }}
51 58 }
52 59  
53 60 named_scope :by_range, lambda { |range| {
... ... @@ -65,7 +72,7 @@ class Article &lt; ActiveRecord::Base
65 72 validate :translation_must_have_language
66 73  
67 74 def is_trackable?
68   - self.published? && self.notifiable? && self.advertise?
  75 + self.published? && self.notifiable? && self.advertise? && self.profile.public_profile
69 76 end
70 77  
71 78 def external_link=(link)
... ... @@ -96,11 +103,13 @@ class Article &lt; ActiveRecord::Base
96 103 @pending_categorizations ||= []
97 104 end
98 105  
99   - def add_category(c)
100   - if self.id
101   - ArticleCategorization.add_category_to_article(c, self)
102   - else
  106 + def add_category(c, reload=false)
  107 + if new_record?
103 108 pending_categorizations << c
  109 + else
  110 + ArticleCategorization.add_category_to_article(c, self)
  111 + self.categories(reload)
  112 + self.solr_save
104 113 end
105 114 end
106 115  
... ... @@ -109,6 +118,7 @@ class Article &lt; ActiveRecord::Base
109 118 ids.uniq.each do |item|
110 119 add_category(Category.find(item)) unless item.to_i.zero?
111 120 end
  121 + self.categories(true)
112 122 end
113 123  
114 124 after_create :create_pending_categorizations
... ... @@ -116,6 +126,8 @@ class Article &lt; ActiveRecord::Base
116 126 pending_categorizations.each do |item|
117 127 ArticleCategorization.add_category_to_article(item, self)
118 128 end
  129 + self.categories(true)
  130 + self.solr_save
119 131 pending_categorizations.clear
120 132 end
121 133  
... ... @@ -126,8 +138,6 @@ class Article &lt; ActiveRecord::Base
126 138  
127 139 acts_as_versioned
128 140  
129   - acts_as_searchable :additional_fields => [ :comment_data ]
130   -
131 141 def comment_data
132 142 comments.map {|item| [item.title, item.body].join(' ') }.join(' ')
133 143 end
... ... @@ -135,20 +145,40 @@ class Article &lt; ActiveRecord::Base
135 145 before_update do |article|
136 146 article.advertise = true
137 147 end
138   -
  148 +
139 149 # retrieves all articles belonging to the given +profile+ that are not
140 150 # sub-articles of any other article.
141 151 named_scope :top_level_for, lambda { |profile|
142 152 {:conditions => [ 'parent_id is null and profile_id = ?', profile.id ]}
143 153 }
144 154  
  155 + named_scope :join_profile, :joins => [:profile]
  156 +
  157 + named_scope :public,
  158 + :conditions => [ "advertise = ? AND published = ? AND profiles.visible = ? AND profiles.public_profile = ?", true, true, true, true ]
  159 +
  160 + named_scope :more_recent,
  161 + :conditions => [ "advertise = ? AND published = ? AND profiles.visible = ? AND profiles.public_profile = ? AND
  162 + ((articles.type != ?) OR articles.type is NULL)",
  163 + true, true, true, true, 'RssFeed'
  164 + ],
  165 + :order => 'articles.published_at desc, articles.id desc'
  166 +
  167 + # retrives the most commented articles, sorted by the comment count (largest
  168 + # first)
  169 + def self.most_commented(limit)
  170 + paginate(:order => 'comments_count DESC', :page => 1, :per_page => limit)
  171 + end
  172 +
  173 + named_scope :more_popular, :order => 'hits DESC'
  174 +
145 175 # retrieves the latest +limit+ articles, sorted from the most recent to the
146 176 # oldest.
147 177 #
148 178 # Only includes articles where advertise == true
149   - def self.recent(limit, extra_conditions = {})
  179 + def self.recent(limit = nil, extra_conditions = {})
150 180 # FIXME this method is a horrible hack
151   - options = { :limit => limit,
  181 + options = { :page => 1, :per_page => limit,
152 182 :conditions => [
153 183 "advertise = ? AND
154 184 published = ? AND
... ... @@ -166,20 +196,14 @@ class Article &lt; ActiveRecord::Base
166 196 options.delete(:include)
167 197 end
168 198 if extra_conditions == {}
169   - self.find(:all, options)
  199 + self.paginate(options)
170 200 else
171 201 with_scope :find => {:conditions => extra_conditions} do
172   - self.find(:all, options)
  202 + self.paginate(options)
173 203 end
174 204 end
175 205 end
176 206  
177   - # retrives the most commented articles, sorted by the comment count (largest
178   - # first)
179   - def self.most_commented(limit)
180   - find(:all, :order => 'comments_count DESC', :limit => limit)
181   - end
182   -
183 207 # produces the HTML code that is to be displayed as this article's contents.
184 208 #
185 209 # The implementation in this class just provides the +body+ attribute as the
... ... @@ -421,7 +445,7 @@ class Article &lt; ActiveRecord::Base
421 445 end
422 446  
423 447 def comments_updated
424   - ferret_update
  448 + solr_save
425 449 end
426 450  
427 451 def accept_category?(cat)
... ... @@ -571,6 +595,114 @@ class Article &lt; ActiveRecord::Base
571 595 _('Created at: ')
572 596 end
573 597  
  598 + def activity
  599 + ActionTracker::Record.find_by_target_type_and_target_id 'Article', self.id
  600 + end
  601 +
  602 + def create_activity
  603 + if is_trackable? && !image?
  604 + save_action_for_verb 'create_article', [:name, :url, :lead, :first_image], Proc.new{}, :author
  605 + end
  606 + end
  607 +
  608 + def first_image
  609 + img = Hpricot(self.lead.to_s).search('img[@src]').first || Hpricot(self.body.to_s).search('img').first
  610 + img.nil? ? '' : img.attributes['src']
  611 + end
  612 +
  613 + private
  614 +
  615 + # FIXME: workaround for development env.
  616 + # Subclasses aren't (re)loaded, and acts_as_solr
  617 + # depends on subclasses method to search
  618 + # see http://stackoverflow.com/questions/4138957/activerecordsubclassnotfound-error-when-using-sti-in-rails/4139245
  619 + UploadedFile
  620 + TextArticle
  621 + TinyMceArticle
  622 + TextileArticle
  623 + Folder
  624 + EnterpriseHomepage
  625 + Gallery
  626 + Blog
  627 + Forum
  628 + Event
  629 +
  630 + def self.f_type_proc(klass)
  631 + klass.constantize.type_name
  632 + end
  633 +
  634 + def self.f_profile_type_proc(klass)
  635 + klass.constantize.type_name
  636 + end
  637 +
  638 + def f_type
  639 + #join common types
  640 + case self.class.name
  641 + when 'TinyMceArticle', 'TextileArticle'
  642 + TextArticle.name
  643 + else
  644 + self.class.name
  645 + end
  646 + end
  647 +
  648 + def f_profile_type
  649 + self.profile.class.name
  650 + end
  651 +
  652 + def f_published_at
  653 + self.published_at
  654 + end
  655 +
  656 + def f_category
  657 + self.categories.collect(&:name)
  658 + end
  659 +
  660 + delegate :region, :region_id, :environment, :environment_id, :to => :profile
  661 + def name_sortable # give a different name for solr
  662 + name
  663 + end
  664 +
  665 + def public
  666 + self.public?
  667 + end
  668 +
  669 + def category_filter
  670 + categories_including_virtual_ids
  671 + end
  672 +
  673 + public
  674 +
  675 + acts_as_faceted :fields => {
  676 + :f_type => {:label => _('Type'), :proc => proc{|klass| f_type_proc(klass)}},
  677 + :f_published_at => {:type => :date, :label => _('Published date'), :queries => {'[* TO NOW-1YEARS/DAY]' => _("Older than one year"),
  678 + '[NOW-1YEARS TO NOW/DAY]' => _("In the last year"), '[NOW-1MONTHS TO NOW/DAY]' => _("In the last month"), '[NOW-7DAYS TO NOW/DAY]' => _("In the last week"), '[NOW-1DAYS TO NOW/DAY]' => _("In the last day")},
  679 + :queries_order => ['[NOW-1DAYS TO NOW/DAY]', '[NOW-7DAYS TO NOW/DAY]', '[NOW-1MONTHS TO NOW/DAY]', '[NOW-1YEARS TO NOW/DAY]', '[* TO NOW-1YEARS/DAY]']},
  680 + :f_profile_type => {:label => _('Profile'), :proc => proc{|klass| f_profile_type_proc(klass)}},
  681 + :f_category => {:label => _('Categories')},
  682 + }, :category_query => proc { |c| "category_filter:\"#{c.id}\"" },
  683 + :order => [:f_type, :f_published_at, :f_profile_type, :f_category]
  684 +
  685 + acts_as_searchable :fields => facets_fields_for_solr + [
  686 + # searched fields
  687 + {:name => {:type => :text, :boost => 2.0}},
  688 + {:slug => :text}, {:body => :text},
  689 + {:abstract => :text}, {:filename => :text},
  690 + # filtered fields
  691 + {:public => :boolean}, {:environment_id => :integer},
  692 + {:profile_id => :integer}, :language,
  693 + {:category_filter => :integer},
  694 + # ordered/query-boosted fields
  695 + {:name_sortable => :string}, :last_changed_by_id, :published_at, :is_image,
  696 + :updated_at, :created_at,
  697 + ], :include => [
  698 + {:profile => {:fields => [:name, :identifier, :address, :nickname, :region_id, :lat, :lng]}},
  699 + {:comments => {:fields => [:title, :body, :author_name, :author_email]}},
  700 + {:categories => {:fields => [:name, :path, :slug, :lat, :lng, :acronym, :abbreviation]}},
  701 + ], :facets => facets_option_for_solr,
  702 + :boost => proc { |a| 10 if a.profile && a.profile.enabled },
  703 + :if => proc{ |a| ! ['RssFeed'].include?(a.class.name) }
  704 + handle_asynchronously :solr_save
  705 +
574 706 private
575 707  
576 708 def sanitize_tag_list
... ...
app/models/blog.rb
... ... @@ -9,6 +9,10 @@ class Blog &lt; Folder
9 9 end
10 10 alias_method_chain :posts, :no_folders
11 11  
  12 + def self.type_name
  13 + _('Blog')
  14 + end
  15 +
12 16 def self.short_description
13 17 _('Blog')
14 18 end
... ...
app/models/category.rb
... ... @@ -15,13 +15,14 @@ class Category &lt; ActiveRecord::Base
15 15  
16 16 acts_as_filesystem
17 17  
18   - has_many :article_categorizations
  18 + has_many :article_categorizations, :dependent => :destroy
19 19 has_many :articles, :through => :article_categorizations
20 20 has_many :comments, :through => :articles
21 21  
22 22 has_many :events, :through => :article_categorizations, :class_name => 'Event', :source => :article
23 23  
24   - has_many :profile_categorizations
  24 + has_many :profile_categorizations, :dependent => :destroy
  25 + has_many :profiles, :through => :profile_categorizations, :source => :profile
25 26 has_many :enterprises, :through => :profile_categorizations, :source => :profile, :class_name => 'Enterprise'
26 27 has_many :people, :through => :profile_categorizations, :source => :profile, :class_name => 'Person'
27 28 has_many :communities, :through => :profile_categorizations, :source => :profile, :class_name => 'Community'
... ... @@ -36,18 +37,38 @@ class Category &lt; ActiveRecord::Base
36 37 { :conditions => [ "type IN (?) OR type IS NULL", types.reject{ |t| t.blank? } ] }
37 38 }
38 39  
  40 + def recent_people(limit = 10)
  41 + self.people.paginate(:order => 'created_at DESC, id DESC', :page => 1, :per_page => limit)
  42 + end
  43 +
  44 + def recent_enterprises(limit = 10)
  45 + self.enterprises.paginate(:order => 'created_at DESC, id DESC', :page => 1, :per_page => limit)
  46 + end
  47 +
  48 + def recent_communities(limit = 10)
  49 + self.communities.paginate(:order => 'created_at DESC, id DESC', :page => 1, :per_page => limit)
  50 + end
  51 +
  52 + def recent_products(limit = 10)
  53 + self.products.paginate(:order => 'created_at DESC, id DESC', :page => 1, :per_page => limit)
  54 + end
  55 +
39 56 def recent_articles(limit = 10)
40 57 self.articles.recent(limit)
41 58 end
42 59  
43 60 def recent_comments(limit = 10)
44   - comments.find(:all, :order => 'created_at DESC, comments.id DESC', :limit => limit)
  61 + comments.paginate(:all, :order => 'created_at DESC, comments.id DESC', :page => 1, :per_page => limit)
45 62 end
46 63  
47 64 def most_commented_articles(limit = 10)
48 65 self.articles.most_commented(limit)
49 66 end
50 67  
  68 + def upcoming_events(limit = 10)
  69 + self.events.paginate(:conditions => [ 'start_date >= ?', Date.today ], :order => 'start_date', :page => 1, :per_page => limit)
  70 + end
  71 +
51 72 def display_in_menu?
52 73 display_in_menu
53 74 end
... ... @@ -69,4 +90,23 @@ class Category &lt; ActiveRecord::Base
69 90 self.children.find(:all, :conditions => {:display_in_menu => true}).empty?
70 91 end
71 92  
  93 + private
  94 + def name_sortable # give a different name for solr
  95 + name
  96 + end
  97 + public
  98 +
  99 + acts_as_searchable :fields => [
  100 + # searched fields
  101 + {:name => {:type => :text, :boost => 2.0}},
  102 + {:path => :text}, {:slug => :text},
  103 + {:abbreviation => :text}, {:acronym => :text},
  104 + # filtered fields
  105 + :parent_id,
  106 + # ordered/query-boosted fields
  107 + {:name_sortable => :string},
  108 + ]
  109 + after_save_reindex [:articles, :profiles], :with => :delayed_job
  110 + handle_asynchronously :solr_save
  111 +
72 112 end
... ...
app/models/category_finder.rb
... ... @@ -1,144 +0,0 @@
1   -class CategoryFinder
2   -
3   - def initialize(cat)
4   - @category = cat
5   - @category_id = @category.id
6   - end
7   -
8   - attr_reader :category_id
9   -
10   - def find(asset, query='', options={})
11   - @region = Region.find_by_id(options.delete(:region)) if options.has_key?(:region)
12   - if @region && options[:within]
13   - options[:origin] = [@region.lat, @region.lng]
14   - else
15   - options.delete(:within)
16   - end
17   -
18   - date_range = options.delete(:date_range)
19   -
20   - options = {:page => 1, :per_page => options.delete(:limit)}.merge(options)
21   -
22   - if asset == :events
23   - finder_method = 'find'
24   - options.delete(:page)
25   - options.delete(:per_page)
26   - else
27   - finder_method = 'paginate'
28   - end
29   -
30   - if query.blank?
31   - asset_class(asset).send(finder_method, :all, options_for_find(asset_class(asset), {:order => "#{asset_table(asset)}.name"}.merge(options), date_range))
32   - else
33   - ferret_options = {:page => options.delete(:page), :per_page => options.delete(:per_page)}
34   - asset_class(asset).find_by_contents(query, ferret_options, options_for_find(asset_class(asset), options, date_range))
35   - end
36   - end
37   -
38   - def recent(asset, limit = nil)
39   - find(asset, nil, :limit => limit, :order => 'created_at DESC, id DESC')
40   - end
41   -
42   - def most_commented_articles(limit=10, options={})
43   - options = {:page => 1, :per_page => limit, :order => 'comments_count DESC'}.merge(options)
44   - Article.paginate(:all, options_for_find(Article, options))
45   - end
46   -
47   - def current_events(year, month, options={})
48   - options.delete(:page)
49   - options.delete(:per_page)
50   -
51   - range = Event.date_range(year, month)
52   -
53   - Event.find(:all, {:include => :categories, :conditions => { 'categories.id' => category_id, :start_date => range }}.merge(options))
54   - end
55   -
56   - def upcoming_events(options = {})
57   - options.delete(:page)
58   - options.delete(:per_page)
59   -
60   - Event.find(:all, {:include => :categories, :conditions => [ 'categories.id = ? and start_date >= ?', category_id, Date.today ], :order => 'start_date' }.merge(options))
61   - end
62   -
63   - def product_categories_count(asset, product_categories_ids, objects_ids=nil)
64   - conditions = [ "product_categorizations.category_id in (?) and #{ProfileCategorization.table_name}.category_id = ?", product_categories_ids, category_id]
65   -
66   - if asset == :products
67   - if objects_ids
68   - conditions[0] += ' and product_categorizations.product_id in (?)'
69   - conditions << objects_ids
70   - end
71   - ProductCategory.find(
72   - :all,
73   - :select => 'categories.id, count(*) as total',
74   - :joins => "inner join product_categorizations on (product_categorizations.category_id = categories.id) inner join products on (products.id = product_categorizations.product_id) inner join #{ProfileCategorization.table_name} on (#{ProfileCategorization.table_name}.profile_id = products.enterprise_id)",
75   - :group => 'categories.id',
76   - :conditions => conditions
77   - )
78   - elsif asset == :enterprises
79   - if objects_ids
80   - conditions[0] += ' and products.enterprise_id in (?)'
81   - conditions << objects_ids
82   - end
83   - ProductCategory.find(
84   - :all,
85   - :select => 'categories.id, count(distinct products.enterprise_id) as total',
86   - :joins => "inner join product_categorizations on (product_categorizations.category_id = categories.id) inner join products on (products.id = product_categorizations.product_id) inner join #{ProfileCategorization.table_name} on (#{ProfileCategorization.table_name}.profile_id = products.enterprise_id)",
87   - :group => 'categories.id',
88   - :conditions => conditions
89   - )
90   - else
91   - raise ArgumentError, 'only products and enterprises supported'
92   - end.inject({}) do |results,pc|
93   - results[pc.id]= pc.total.to_i
94   - results
95   - end
96   - end
97   -
98   - protected
99   -
100   - def options_for_find(klass, options={}, date_range = nil)
101   - if defined? options[:product_category]
102   - prod_cat = options.delete(:product_category)
103   - end
104   -
105   - case klass.name
106   - when 'Comment'
107   - {:joins => 'inner join articles_categories on articles_categories.article_id = comments.article_id', :conditions => ['articles_categories.category_id = (?)', category_id]}.merge!(options)
108   - when 'Product'
109   - if prod_cat
110   - {:joins => 'inner join categories_profiles on products.enterprise_id = categories_profiles.profile_id inner join product_categorizations on (product_categorizations.product_id = products.id)', :conditions => ['categories_profiles.category_id = (?) and product_categorizations.category_id = (?)', category_id, prod_cat.id]}.merge!(options)
111   - else
112   - {:joins => 'inner join categories_profiles on products.enterprise_id = categories_profiles.profile_id', :conditions => ['categories_profiles.category_id = (?)', category_id]}.merge!(options)
113   - end
114   - when 'Article', 'TextArticle'
115   - {:joins => 'inner join articles_categories on (articles_categories.article_id = articles.id)', :conditions => ['articles_categories.category_id = (?)', category_id]}.merge!(options)
116   - when 'Event'
117   - conditions =
118   - if date_range
119   - ['articles_categories.category_id = (:category_id) and (start_date BETWEEN :start_day AND :end_day OR end_date BETWEEN :start_day AND :end_day)', {:category_id => category_id, :start_day => date_range.first, :end_day => date_range.last} ]
120   - else
121   - ['articles_categories.category_id = (?) ', category_id ]
122   - end
123   - {:joins => 'inner join articles_categories on (articles_categories.article_id = articles.id)', :conditions => conditions}.merge!(options)
124   - when 'Enterprise'
125   - if prod_cat
126   - {:joins => 'inner join categories_profiles on (categories_profiles.profile_id = profiles.id) inner join products on (products.enterprise_id = profiles.id) inner join product_categorizations on (product_categorizations.product_id = products.id)', :conditions => ['categories_profiles.category_id = (?) and product_categorizations.category_id = (?)', category_id, prod_cat.id]}.merge!(options)
127   - else
128   - {:joins => 'inner join categories_profiles on (categories_profiles.profile_id = profiles.id)', :conditions => ['categories_profiles.category_id = (?)', category_id]}.merge!(options)
129   - end
130   - when 'Person', 'Community'
131   - {:joins => 'inner join categories_profiles on (categories_profiles.profile_id = profiles.id)', :conditions => ['categories_profiles.category_id = (?)', category_id]}.merge!(options)
132   - else
133   - raise "unreconized class #{klass.name}"
134   - end
135   - end
136   -
137   - def asset_class(asset)
138   - asset.to_s.singularize.camelize.constantize
139   - end
140   -
141   - def asset_table(asset)
142   - asset_class(asset).table_name
143   - end
144   -end
app/models/certifier.rb
... ... @@ -2,12 +2,20 @@ class Certifier &lt; ActiveRecord::Base
2 2  
3 3 belongs_to :environment
4 4  
5   - has_many :qualifier_certifiers
  5 + has_many :qualifier_certifiers, :dependent => :destroy
6 6 has_many :qualifiers, :through => :qualifier_certifiers
7 7  
  8 + has_many :product_qualifiers
  9 + has_many :products, :through => :product_qualifiers, :source => :product
  10 +
8 11 validates_presence_of :environment_id
9 12 validates_presence_of :name
10 13  
  14 + def destroy
  15 + product_qualifiers.each { |pq| pq.update_attributes! :certifier => nil }
  16 + super
  17 + end
  18 +
11 19 def link
12 20 self[:link] || ''
13 21 end
... ... @@ -16,4 +24,6 @@ class Certifier &lt; ActiveRecord::Base
16 24 self.name.downcase.transliterate <=> b.name.downcase.transliterate
17 25 end
18 26  
  27 + after_save_reindex [:products], :with => :delayed_job
  28 +
19 29 end
... ...
app/models/comment.rb
1 1 class Comment < ActiveRecord::Base
2 2  
3   - track_actions :leave_comment, :after_create, :keep_params => ["article.title", "article.url", "title", "url", "body"], :custom_target => :action_tracker_target
4   -
5 3 validates_presence_of :body
6   - belongs_to :article, :counter_cache => true
  4 +
  5 + belongs_to :source, :counter_cache => true, :polymorphic => true
  6 + alias :article :source
  7 + alias :article= :source=
  8 +
7 9 belongs_to :author, :class_name => 'Person', :foreign_key => 'author_id'
8 10 has_many :children, :class_name => 'Comment', :foreign_key => 'reply_of_id', :dependent => :destroy
9 11 belongs_to :reply_of, :class_name => 'Comment', :foreign_key => 'reply_of_id'
... ... @@ -70,13 +72,25 @@ class Comment &lt; ActiveRecord::Base
70 72 after_save :notify_article
71 73 after_destroy :notify_article
72 74 def notify_article
73   - article.comments_updated
  75 + article.comments_updated if article.kind_of?(Article)
74 76 end
75 77  
76 78 after_create do |comment|
77   - if comment.article.notify_comments? && !comment.article.profile.notification_emails.empty?
  79 + if comment.source.kind_of?(Article) && comment.article.notify_comments? && !comment.article.profile.notification_emails.empty?
78 80 Comment::Notifier.deliver_mail(comment)
79 81 end
  82 +
  83 + if comment.source.kind_of?(Article)
  84 + comment.article.create_activity if comment.article.activity.nil?
  85 + if comment.article.activity
  86 + comment.article.activity.increment!(:comments_count)
  87 + comment.article.activity.update_attribute(:visible, true)
  88 + end
  89 + end
  90 + end
  91 +
  92 + after_destroy do |comment|
  93 + comment.article.activity.decrement!(:comments_count) if comment.source.kind_of?(Article) && comment.article.activity
80 94 end
81 95  
82 96 def replies
... ...
app/models/communities_block.rb
... ... @@ -21,7 +21,7 @@ class CommunitiesBlock &lt; ProfileListBlock
21 21 end
22 22 when Environment
23 23 lambda do
24   - link_to s_('communities|View all'), :controller => 'browse', :action => 'communities'
  24 + link_to s_('communities|View all'), :controller => 'search', :action => 'communities'
25 25 end
26 26 else
27 27 ''
... ...
app/models/community.rb
1 1 class Community < Organization
  2 +
  3 + def self.type_name
  4 + _('Community')
  5 + end
  6 +
2 7 N_('Community')
3 8 N_('Language')
4 9  
5 10 settings_items :language
6 11 settings_items :zip_code, :city, :state, :country
7 12  
  13 + extend SetProfileRegionFromCityState::ClassMethods
  14 + set_profile_region_from_city_state
  15 +
8 16 before_create do |community|
9 17 community.moderated_articles = true if community.environment.enabled?('organizations_are_moderated_by_default')
10 18 end
... ... @@ -79,4 +87,8 @@ class Community &lt; Organization
79 87 {:title => __('Community Info and settings'), :icon => 'edit-profile-group'}
80 88 end
81 89  
  90 + def activities
  91 + Scrap.find_by_sql("SELECT id, updated_at, '#{Scrap.to_s}' AS klass FROM #{Scrap.table_name} WHERE scraps.receiver_id = #{self.id} AND scraps.scrap_id IS NULL UNION SELECT id, updated_at, '#{ActionTracker::Record.to_s}' AS klass FROM #{ActionTracker::Record.table_name} WHERE action_tracker.target_id = #{self.id} UNION SELECT at.id, at.updated_at, '#{ActionTracker::Record.to_s}' AS klass FROM #{ActionTracker::Record.table_name} at INNER JOIN articles a ON at.target_id = a.id WHERE a.profile_id = #{self.id} AND at.target_type = 'Article' ORDER BY updated_at DESC")
  92 + end
  93 +
82 94 end
... ...
app/models/domain.rb
... ... @@ -8,9 +8,9 @@ class Domain &lt; ActiveRecord::Base
8 8 # validations
9 9 #############
10 10  
11   - # <tt>name</tt> must be a sequence of word characters (a to z, plus 0 to 9,
12   - # plus '_'). Letters must be lowercase
13   - validates_format_of :name, :with => /^([a-z0-9_-]+\.)+[a-z0-9_-]+$/, :message => N_('%{fn} must be composed only of lowercase latters (a to z), numbers (0 to 9), "_" and "-"').fix_i18n
  11 + # <tt>name</tt> must be sequences of alphanumeric characters (a to z,
  12 + # 0 to 9), plus '_' or '-', separated by dots. Letters must be lowercase.
  13 + validates_format_of :name, :with => /^([a-z0-9_-]+\.)+[a-z0-9_-]+$/, :message => N_('%{fn} must be composed of sequences of lowercase letters (a to z), numbers (0 to 9), "_" and "-", separated by dots.').fix_i18n
14 14  
15 15 # checks validations that could not be expressed using Rails' predefined
16 16 # validations. In particular:
... ...
app/models/enterprise.rb
... ... @@ -2,6 +2,10 @@
2 2 # only enterprises can offer products and services.
3 3 class Enterprise < Organization
4 4  
  5 + def self.type_name
  6 + _('Enterprise')
  7 + end
  8 +
5 9 N_('Enterprise')
6 10  
7 11 has_many :products, :dependent => :destroy, :order => 'name ASC'
... ... @@ -10,12 +14,19 @@ class Enterprise &lt; Organization
10 14  
11 15 has_and_belongs_to_many :fans, :class_name => 'Person', :join_table => 'favorite_enteprises_people'
12 16  
  17 + after_save_reindex [:products], :with => :delayed_job
13 18 extra_data_for_index :product_categories
  19 + def product_categories
  20 + products.map{|p| p.category_full_name}.compact
  21 + end
14 22  
15 23 N_('Organization website'); N_('Historic and current context'); N_('Activities short description'); N_('City'); N_('State'); N_('Country'); N_('ZIP code')
16 24  
17 25 settings_items :organization_website, :historic_and_current_context, :activities_short_description, :zip_code, :city, :state, :country
18 26  
  27 + extend SetProfileRegionFromCityState::ClassMethods
  28 + set_profile_region_from_city_state
  29 +
19 30 before_save do |enterprise|
20 31 enterprise.organization_website = enterprise.maybe_add_http(enterprise.organization_website)
21 32 end
... ... @@ -67,22 +78,6 @@ class Enterprise &lt; Organization
67 78 environment ? environment.signup_enterprise_fields : []
68 79 end
69 80  
70   - def product_categories
71   - products.map{|p| p.category_full_name}.compact
72   - end
73   -
74   - def product_updated
75   - ferret_update
76   - end
77   -
78   - after_save do |e|
79   - e.delay.update_products_position
80   - end
81   -
82   - def update_products_position
83   - products.each{ |p| p.enterprise_updated(self) }
84   - end
85   -
86 81 def closed?
87 82 true
88 83 end
... ... @@ -167,6 +162,10 @@ class Enterprise &lt; Organization
167 162 end
168 163 end
169 164  
  165 + def control_panel_settings_button
  166 + {:title => __('Enterprise Info and settings'), :icon => 'edit-profile-enterprise'}
  167 + end
  168 +
170 169 settings_items :enable_contact_us, :type => :boolean, :default => true
171 170  
172 171 def enable_contact?
... ... @@ -181,4 +180,8 @@ class Enterprise &lt; Organization
181 180 true
182 181 end
183 182  
  183 + def activities
  184 + Scrap.find_by_sql("SELECT id, updated_at, 'Scrap' AS klass FROM scraps WHERE scraps.receiver_id = #{self.id} AND scraps.scrap_id IS NULL UNION SELECT id, updated_at, 'ActionTracker::Record' AS klass FROM action_tracker WHERE action_tracker.target_id = #{self.id} UNION SELECT action_tracker.id, action_tracker.updated_at, 'ActionTracker::Record' AS klass FROM action_tracker INNER JOIN articles ON action_tracker.target_id = articles.id WHERE articles.profile_id = #{self.id} AND action_tracker.target_type = 'Article' ORDER BY action_tracker.updated_at DESC")
  185 + end
  186 +
184 187 end
... ...
app/models/enterprise_homepage.rb
1 1 class EnterpriseHomepage < Article
2 2  
  3 + def self.type_name
  4 + _('Homepage')
  5 + end
  6 +
3 7 def self.short_description
4 8 __('Enterprise homepage.')
5 9 end
... ...
app/models/environment.rb
... ... @@ -248,6 +248,10 @@ class Environment &lt; ActiveRecord::Base
248 248  
249 249 settings_items :enabled_plugins, :type => Array, :default => []
250 250  
  251 + settings_items :search_hints, :type => Hash, :default => {}
  252 +
  253 + settings_items :top_level_category_as_facet_ids, :type => Array, :default => []
  254 +
251 255 def news_amount_by_folder=(amount)
252 256 settings[:news_amount_by_folder] = amount.to_i
253 257 end
... ... @@ -277,6 +281,9 @@ class Environment &lt; ActiveRecord::Base
277 281 def enabled?(feature)
278 282 self.settings["#{feature}_enabled".to_sym] == true
279 283 end
  284 + def disabled?(feature)
  285 + !self.enabled?(feature)
  286 + end
280 287  
281 288 def plugin_enabled?(plugin)
282 289 enabled_plugins.include?(plugin.to_s)
... ...
app/models/environment_finder.rb
... ... @@ -1,107 +0,0 @@
1   -class EnvironmentFinder
2   -
3   - def initialize env
4   - @environment = env
5   - end
6   -
7   - def find(asset, query = nil, options={}, finder_method = 'paginate')
8   - @region = Region.find_by_id(options.delete(:region)) if options.has_key?(:region)
9   - if @region && options[:within]
10   - options[:origin] = [@region.lat, @region.lng]
11   - else
12   - options.delete(:within)
13   - end
14   -
15   - product_category = options.delete(:product_category)
16   -
17   - date_range = options.delete(:date_range)
18   -
19   - # FIXME this test is in more than one place
20   - if finder_method == 'paginate'
21   - options = {:page => 1, :per_page => options.delete(:limit)}.merge(options)
22   - end
23   -
24   - if query.blank?
25   - # FIXME this test is in more than one place
26   - if finder_method == 'paginate'
27   - options = {:order => "#{asset_table(asset)}.name"}.merge(options)
28   - end
29   - if product_category && asset == :products
30   - @environment.send(asset).send(finder_method, :all, options.merge(:include => 'product_categorizations', :conditions => ['product_categorizations.category_id = (?)', product_category.id]))
31   - elsif product_category && asset == :enterprises
32   - @environment.send(asset).send(finder_method, :all, options.merge( :order => 'profiles.name', :joins => 'inner join products on (products.enterprise_id = profiles.id) inner join product_categorizations on (product_categorizations.product_id = products.id)', :conditions => ['product_categorizations.category_id = (?)', product_category.id]))
33   - else
34   - if asset == :events
35   - # Ignore pagination for asset events
36   - options.delete(:per_page)
37   - options.delete(:page)
38   - if date_range
39   - @environment.send(asset).send('find', :all, options.merge(:conditions => [
40   - 'start_date BETWEEN :start_day AND :end_day OR end_date BETWEEN :start_day AND :end_day',
41   - {:start_day => date_range.first, :end_day => date_range.last}
42   - ]))
43   - else
44   - @environment.send(asset).send('find', :all, options)
45   - end
46   - else
47   - @environment.send(asset).send(finder_method, :all, options)
48   - end
49   - end
50   - else
51   - ferret_options = {:page => options.delete(:page), :per_page => options.delete(:per_page)}
52   - if product_category && asset == :products
53   - # SECURITY no risk of SQL injection, since product_category_ids comes from trusted source
54   - @environment.send(asset).find_by_contents(query, ferret_options, options.merge({:include => 'product_categorizations', :conditions => 'product_categorizations.category_id = (%s)' % product_category.id }))
55   - elsif product_category && asset == :enterprises
56   - @environment.send(asset).find_by_contents(query, ferret_options, options.merge(:joins => 'inner join products on products.enterprise_id = profiles.id inner join product_categorizations on (product_categorizations.product_id = products.id)', :conditions => "product_categorizations.category_id = (#{product_category.id})"))
57   - else
58   - @environment.send(asset).find_by_contents(query, ferret_options, options)
59   - end
60   - end
61   - end
62   -
63   - def recent(asset, limit = nil)
64   - find(asset, nil, :limit => limit)
65   - end
66   -
67   - def product_categories_count(asset, product_categories_ids, objects_ids=nil)
68   - conditions = ['product_categorizations.category_id in (?)', product_categories_ids]
69   -
70   - if asset == :products
71   - if objects_ids
72   - conditions[0] += ' and product_categorizations.product_id in (?)'
73   - conditions << objects_ids
74   - end
75   - ProductCategory.find(:all, :select => 'categories.id, count(*) as total', :joins => 'inner join product_categorizations on (product_categorizations.category_id = categories.id)', :group => 'categories.id', :conditions => conditions )
76   - elsif asset == :enterprises
77   - if objects_ids
78   - conditions[0] += ' and products.enterprise_id in (?)'
79   - conditions << objects_ids
80   - end
81   - ProductCategory.find(
82   - :all,
83   - :select => 'categories.id, count(distinct products.enterprise_id) as total',
84   - :joins => 'inner join product_categorizations on (product_categorizations.category_id = categories.id) inner join products on (products.id = product_categorizations.product_id)',
85   - :group => 'categories.id',
86   - :conditions => conditions
87   - )
88   - else
89   - raise ArgumentError, 'only products and enterprises supported'
90   - end.inject({}) do |results,pc|
91   - results[pc.id]= pc.total.to_i
92   - results
93   - end
94   -
95   - end
96   -
97   - protected
98   -
99   - def asset_class(asset)
100   - asset.to_s.singularize.camelize.constantize
101   - end
102   -
103   - def asset_table(asset)
104   - asset_class(asset).table_name
105   - end
106   -
107   -end
app/models/event.rb
1 1 class Event < Article
2 2  
  3 + def self.type_name
  4 + _('Event')
  5 + end
  6 +
3 7 settings_items :address, :type => :string
4 8  
5 9 def link=(value)
... ... @@ -120,6 +124,10 @@ class Event &lt; Article
120 124 true
121 125 end
122 126  
  127 + def notifiable?
  128 + true
  129 + end
  130 +
123 131 include Noosfero::TranslatableContent
124 132 include MaybeAddHttp
125 133  
... ...
app/models/external_feed.rb
... ... @@ -14,7 +14,9 @@ class ExternalFeed &lt; ActiveRecord::Base
14 14 article = TinyMceArticle.new(:name => title, :profile => blog.profile, :body => content, :published_at => date, :source => link, :profile => blog.profile, :parent => blog)
15 15 unless blog.children.exists?(:slug => article.slug)
16 16 article.save!
  17 + article.delay.create_activity
17 18 end
  19 + article.valid?
18 20 end
19 21  
20 22 def clear
... ...
app/models/folder.rb
1 1 class Folder < Article
2 2  
  3 + def self.type_name
  4 + _('Folder')
  5 + end
  6 +
3 7 validate :not_belong_to_blog
4 8  
5 9 def not_belong_to_blog
... ...
app/models/forum.rb
... ... @@ -2,6 +2,10 @@ class Forum &lt; Folder
2 2  
3 3 acts_as_having_posts :order => 'updated_at DESC'
4 4  
  5 + def self.type_name
  6 + _('Forum')
  7 + end
  8 +
5 9 def self.short_description
6 10 _('Forum')
7 11 end
... ... @@ -24,4 +28,14 @@ class Forum &lt; Folder
24 28 def self.icon_name(article = nil)
25 29 'forum'
26 30 end
  31 +
  32 + def notifiable?
  33 + true
  34 + end
  35 +
  36 + def first_paragraph
  37 + return '' if body.blank?
  38 + paragraphs = Hpricot(body).search('p')
  39 + paragraphs.empty? ? '' : paragraphs.first.to_html
  40 + end
27 41 end
... ...
app/models/gallery.rb
1 1 class Gallery < Folder
2 2  
  3 + def self.type_name
  4 + _('Gallery')
  5 + end
  6 +
3 7 def self.short_description
4 8 _('Gallery')
5 9 end
... ...
app/models/google_maps.rb
1 1 class GoogleMaps
2 2  
3   - extend ActionView::Helpers::TagHelper
4   -
5   - class << self
6   -
7   - include ApplicationHelper
8   -
9   - def enabled?(domain)
10   - domain = Domain.find_by_name(domain)
11   - domain ? !domain.google_maps_key.nil? : false
12   - end
13   -
14   - def key(domainname)
15   - domain = Domain.find_by_name(domainname)
16   - domain && domain.google_maps_key || ''
17   - end
18   -
19   - def initial_zoom
20   - NOOSFERO_CONF['googlemaps_initial_zoom'] || 4
21   - end
22   -
23   - def api_url(domain)
24   - "http://maps.google.com/maps?file=api&amp;v=2&amp;key=#{key(domain)}"
25   - end
26   -
  3 + def self.initial_zoom
  4 + NOOSFERO_CONF['googlemaps_initial_zoom'] || 4
27 5 end
  6 +
28 7 end
... ...
app/models/image.rb
... ... @@ -13,7 +13,7 @@ class Image &lt; ActiveRecord::Base
13 13 :thumbnails => { :big => '150x150',
14 14 :thumb => '100x100',
15 15 :portrait => '64x64',
16   - :minor => '50x50',
  16 + :minor => '50x50>',
17 17 :icon => '20x20!' },
18 18 :max_size => 5.megabytes # remember to update validate message below
19 19  
... ...
app/models/input.rb
... ... @@ -59,4 +59,7 @@ class Input &lt; ActiveRecord::Base
59 59 return 0 if self.amount_used.blank? || self.price_per_unit.blank?
60 60 self.amount_used * self.price_per_unit
61 61 end
  62 +
  63 + alias_method :price, :cost
  64 +
62 65 end
... ...
app/models/location_block.rb
... ... @@ -17,10 +17,10 @@ class LocationBlock &lt; Block
17 17 if profile.lat
18 18 block_title(title) +
19 19 content_tag('div',
20   - '<img src="http://maps.google.com/staticmap?center=' + profile.lat.to_s() +
  20 + '<img src="http://maps.google.com/maps/api/staticmap?center=' + profile.lat.to_s() +
21 21 ',' + profile.lng.to_s() + '&zoom=' + zoom.to_s() +
22 22 '&size=190x250&maptype=' + map_type + '&markers=' + profile.lat.to_s() + ',' +
23   - profile.lng.to_s() + ',green&key=' + GoogleMaps::key(profile.default_hostname) + '&sensor=false"/>',
  23 + profile.lng.to_s() + ',green' + '&sensor=false"/>',
24 24 :class => 'the-localization-map' )
25 25 else
26 26 content_tag('i', _('This profile has no geographical position registered.'))
... ...
app/models/national_region.rb 0 → 100644
... ... @@ -0,0 +1,76 @@
  1 +class NationalRegion < ActiveRecord::Base
  2 +
  3 + def self.search_city(city_name, like = false, state = nil)
  4 +
  5 + operator = "="
  6 + find_return = :first
  7 + adtional_contions = "";
  8 +
  9 + if like
  10 + operator = "like"
  11 + find_return = :all
  12 + end
  13 +
  14 + if state
  15 + adtional_contions = " AND nr.name = :state "
  16 + end
  17 +
  18 +
  19 + conditions = ["national_regions.name #{operator} :name AND
  20 + national_regions.national_region_type_id = :type" + adtional_contions,
  21 + {:name => city_name ,
  22 + :type => NationalRegionType::CITY,
  23 + :state => state}];
  24 +
  25 + region = NationalRegion.find(find_return,
  26 + :select => "national_regions.name as city, nr.name as state, national_regions.national_region_code",
  27 + :conditions => conditions,
  28 + :joins => "LEFT JOIN national_regions as nr ON national_regions.parent_national_region_code = nr.national_region_code",
  29 + :limit => 10
  30 + )
  31 + return region
  32 + end
  33 +
  34 + def self.search_state(state_name, like = false)
  35 + operator = "="
  36 + find_return = :first
  37 +
  38 + if like
  39 + operator = "like"
  40 + find_return = :all
  41 + end
  42 +
  43 + conditions = ["national_regions.name #{operator} :name AND
  44 + national_regions.national_region_type_id = :type",
  45 + {:name => state_name,
  46 + :type => NationalRegionType::STATE}];
  47 +
  48 + region = NationalRegion.find(find_return,
  49 + :select => "national_regions.name as state, national_regions.national_region_code",
  50 + :conditions => conditions,
  51 + :limit => 10
  52 + )
  53 + return region
  54 + end
  55 +
  56 + def self.validate!(city, state, country)
  57 +
  58 + country_region = NationalRegion.find_by_national_region_code(country,
  59 + :conditions => ["national_region_type_id = :type",
  60 + {:type => NationalRegionType::COUNTRY}])
  61 +
  62 + if(country_region)
  63 +
  64 + nregion = NationalRegion.search_city(city, false, state);
  65 +
  66 + if nregion == nil
  67 + raise _('Invalid city or state name.')
  68 + end
  69 +
  70 + end
  71 +
  72 + return nregion
  73 +
  74 + end
  75 +
  76 +end
... ...
app/models/national_region_type.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class NationalRegionType < ActiveRecord::Base
  2 + COUNTRY = 1
  3 + STATE = 2
  4 + CITY = 3
  5 +end
... ...
app/models/people_block.rb
... ... @@ -18,7 +18,7 @@ class PeopleBlock &lt; ProfileListBlock
18 18  
19 19 def footer
20 20 lambda do
21   - link_to _('View all'), :controller => 'browse', :action => 'people'
  21 + link_to _('View all'), :controller => 'search', :action => 'people'
22 22 end
23 23 end
24 24  
... ...
app/models/person.rb
1 1 # A person is the profile of an user holding all relationships with the rest of the system
2 2 class Person < Profile
3 3  
  4 + def self.type_name
  5 + _('Person')
  6 + end
  7 +
4 8 acts_as_trackable :after_add => Proc.new {|p,t| notify_activity(t)}
5 9 acts_as_accessor
6 10  
... ... @@ -187,6 +191,9 @@ class Person &lt; Profile
187 191 N_('Contact information'); N_('City'); N_('State'); N_('Country'); N_('Sex'); N_('Zip code')
188 192 settings_items :photo, :contact_information, :sex, :city, :state, :country, :zip_code
189 193  
  194 + extend SetProfileRegionFromCityState::ClassMethods
  195 + set_profile_region_from_city_state
  196 +
190 197 def self.conditions_for_profiles(conditions, person)
191 198 new_conditions = sanitize_sql(['role_assignments.accessor_id = ?', person])
192 199 new_conditions << ' AND ' + sanitize_sql(conditions) unless conditions.blank?
... ... @@ -435,6 +442,10 @@ class Person &lt; Profile
435 442 user.save!
436 443 end
437 444  
  445 + def activities
  446 + Scrap.find_by_sql("SELECT id, updated_at, '#{Scrap.to_s}' AS klass FROM #{Scrap.table_name} WHERE scraps.receiver_id = #{self.id} AND scraps.scrap_id IS NULL UNION SELECT id, updated_at, '#{ActionTracker::Record.to_s}' AS klass FROM #{ActionTracker::Record.table_name} WHERE action_tracker.user_id = #{self.id} ORDER BY updated_at DESC")
  447 + end
  448 +
438 449 protected
439 450  
440 451 def followed_by?(profile)
... ...
app/models/product.rb
1 1 class Product < ActiveRecord::Base
2 2 belongs_to :enterprise
  3 + has_one :region, :through => :enterprise
  4 + validates_presence_of :enterprise
  5 +
3 6 belongs_to :product_category
4   - has_many :product_categorizations
5   - has_many :product_qualifiers
6   - has_many :qualifiers, :through => :product_qualifiers
  7 +
7 8 has_many :inputs, :dependent => :destroy, :order => 'position'
8 9 has_many :price_details, :dependent => :destroy
9 10 has_many :production_costs, :through => :price_details
10 11  
  12 + has_many :product_qualifiers, :dependent => :destroy
  13 + has_many :qualifiers, :through => :product_qualifiers
  14 + has_many :certifiers, :through => :product_qualifiers
  15 +
11 16 validates_uniqueness_of :name, :scope => :enterprise_id, :allow_nil => true
12 17 validates_presence_of :product_category_id
13 18 validates_associated :product_category
... ... @@ -15,35 +20,20 @@ class Product &lt; ActiveRecord::Base
15 20 validates_numericality_of :price, :allow_nil => true
16 21 validates_numericality_of :discount, :allow_nil => true
17 22  
18   - after_update :save_image
  23 + named_scope :more_recent, :order => "created_at DESC"
19 24  
20   - before_create do |p|
21   - if p.enterprise
22   - p['lat'] = p.enterprise.lat
23   - p['lng'] = p.enterprise.lng
24   - end
25   - end
  25 + after_update :save_image
26 26  
27   - after_save do |p|
28   - p.enterprise.product_updated if p.enterprise
  27 + def lat
  28 + self.enterprise.lat
29 29 end
30   -
31   - after_save do |p|
32   - if (p.product_category && !ProductCategorization.find(:first, :conditions => {:category_id => p.product_category.id, :product_id => p.id})) || (!p.product_category)
33   - ProductCategorization.remove_all_for(p)
34   - if p.product_category
35   - ProductCategorization.add_category_to_product(p.product_category, p)
36   - end
37   - end
  30 + def lng
  31 + self.enterprise.lng
38 32 end
39 33  
40   - acts_as_searchable :fields => [ :name, :description, :category_full_name ]
41   -
42 34 xss_terminate :only => [ :name ], :on => 'validation'
43 35 xss_terminate :only => [ :description ], :with => 'white_list', :on => 'validation'
44 36  
45   - acts_as_mappable
46   -
47 37 belongs_to :unit
48 38  
49 39 include FloatHelper
... ... @@ -89,18 +79,12 @@ class Product &lt; ActiveRecord::Base
89 79 self.find(:all, :order => 'id desc', :limit => limit)
90 80 end
91 81  
92   - def enterprise_updated(e)
93   - self.lat = e.lat
94   - self.lng = e.lng
95   - save!
96   - end
97   -
98 82 def url
99 83 enterprise.public_profile_url.merge(:controller => 'manage_products', :action => 'show', :id => id)
100 84 end
101 85  
102 86 def public?
103   - enterprise.public_profile
  87 + enterprise.public?
104 88 end
105 89  
106 90 def formatted_value(method)
... ... @@ -128,7 +112,6 @@ class Product &lt; ActiveRecord::Base
128 112 end
129 113 end
130 114  
131   - # Note: will probably be completely overhauled for AI1413
132 115 def inputs_prices?
133 116 return false if self.inputs.count <= 0
134 117 self.inputs.each do |input|
... ... @@ -155,7 +138,7 @@ class Product &lt; ActiveRecord::Base
155 138 def qualifiers_list=(qualifiers)
156 139 self.product_qualifiers.destroy_all
157 140 qualifiers.each do |qualifier_id, certifier_id|
158   - self.product_qualifiers.create(:qualifier_id => qualifier_id, :certifier_id => certifier_id)
  141 + self.product_qualifiers.create(:qualifier_id => qualifier_id, :certifier_id => certifier_id) if qualifier_id != 'nil'
159 142 end
160 143 end
161 144  
... ... @@ -213,4 +196,102 @@ class Product &lt; ActiveRecord::Base
213 196 url_for({:host => enterprise.default_hostname, :controller => 'manage_products', :action => 'display_inputs_cost', :profile => enterprise.identifier, :id => self.id }.merge(Noosfero.url_options))
214 197 end
215 198  
  199 + def percentage_from_solidarity_economy
  200 + se_i = t_i = 0
  201 + self.inputs(true).each{ |i| t_i += 1; se_i += 1 if i.is_from_solidarity_economy }
  202 + t_i = 1 if t_i == 0 # avoid division by 0
  203 + p = case (se_i.to_f/t_i)*100
  204 + when 0..24.999 then [0, _("0%")];
  205 + when 25..49.999 then [25, _("25%")];
  206 + when 50..74.999 then [50, _("50%")];
  207 + when 75..99.999 then [75, _("75%")];
  208 + when 100 then [100, _("100%")];
  209 + end
  210 + end
  211 +
  212 + private
  213 + def f_category
  214 + self.product_category.name
  215 + end
  216 + def f_region
  217 + self.enterprise.region.id if self.enterprise.region
  218 + end
  219 + def self.f_region_proc(id)
  220 + c = Region.find(id)
  221 + s = c.parent
  222 + if c and c.kind_of?(City) and s and s.kind_of?(State) and s.acronym
  223 + [c.name, ', ' + s.acronym]
  224 + else
  225 + c.name
  226 + end
  227 + end
  228 + def self.f_qualifier_proc(ids)
  229 + array = ids.split
  230 + qualifier = Qualifier.find_by_id array[0]
  231 + certifier = Certifier.find_by_id array[1]
  232 + certifier ? [qualifier.name, _(' cert. ') + certifier.name] : qualifier.name
  233 + end
  234 + def f_qualifier
  235 + product_qualifiers.map do |pq|
  236 + "#{pq.qualifier_id} #{pq.certifier_id}"
  237 + end
  238 + end
  239 +
  240 + alias_method :name_sortable, :name
  241 + delegate :enabled, :region, :region_id, :environment, :environment_id, :to => :enterprise
  242 + def name_sortable # give a different name for solr
  243 + name
  244 + end
  245 + def public
  246 + self.public?
  247 + end
  248 + def price_sortable
  249 + (price.nil? or price.zero?) ? nil : price
  250 + end
  251 + def category_filter
  252 + enterprise.categories_including_virtual_ids << product_category_id
  253 + end
  254 + public
  255 +
  256 + acts_as_faceted :fields => {
  257 + :f_category => {:label => _('Related products')},
  258 + :f_region => {:label => _('City'), :proc => proc { |id| f_region_proc(id) }},
  259 + :f_qualifier => {:label => _('Qualifiers'), :proc => proc { |id| f_qualifier_proc(id) }},
  260 + }, :category_query => proc { |c| "category_filter:#{c.id}" },
  261 + :order => [:f_category, :f_region, :f_qualifier]
  262 +
  263 + Boosts = [
  264 + [:image, 0.55, proc{ |p| p.image ? 1 : 0}],
  265 + [:qualifiers, 0.45, proc{ |p| p.product_qualifiers.count > 0 ? 1 : 0}],
  266 + [:open_price, 0.45, proc{ |p| p.price_described? ? 1 : 0}],
  267 + [:solidarity, 0.45, proc{ |p| p.percentage_from_solidarity_economy[0].to_f/100 }],
  268 + [:available, 0.35, proc{ |p| p.available ? 1 : 0}],
  269 + [:price, 0.35, proc{ |p| (!p.price.nil? and p.price > 0) ? 1 : 0}],
  270 + [:new_product, 0.35, proc{ |p| (p.updated_at.to_i - p.created_at.to_i) < 24*3600 ? 1 : 0}],
  271 + [:description, 0.3, proc{ |p| !p.description.blank? ? 1 : 0}],
  272 + [:enabled, 0.2, proc{ |p| p.enterprise.enabled ? 1 : 0}],
  273 + ]
  274 +
  275 + acts_as_searchable :fields => facets_fields_for_solr + [
  276 + # searched fields
  277 + {:name => {:type => :text, :boost => 2.0}},
  278 + {:description => :text}, {:category_full_name => :text},
  279 + # filtered fields
  280 + {:public => :boolean}, {:environment_id => :integer},
  281 + {:enabled => :boolean}, {:category_filter => :integer},
  282 + # ordered/query-boosted fields
  283 + {:price_sortable => :decimal}, {:name_sortable => :string},
  284 + {:lat => :float}, {:lng => :float},
  285 + :updated_at, :created_at,
  286 + ], :include => [
  287 + {:product_category => {:fields => [:name, :path, :slug, :lat, :lng, :acronym, :abbreviation]}},
  288 + {:region => {:fields => [:name, :path, :slug, :lat, :lng]}},
  289 + {:enterprise => {:fields => [:name, :identifier, :address, :nickname, :lat, :lng]}},
  290 + {:qualifiers => {:fields => [:name]}},
  291 + {:certifiers => {:fields => [:name]}},
  292 + ], :facets => facets_option_for_solr,
  293 + :boost => proc{ |p| boost = 1; Boosts.each{ |b| boost = boost * (1 - ((1 - b[2].call(p)) * b[1])) }; boost}
  294 + handle_asynchronously :solr_save
  295 + after_save_reindex [:enterprise], :with => :delayed_job
  296 +
216 297 end
... ...
app/models/product_categorization.rb
... ... @@ -1,13 +0,0 @@
1   -class ProductCategorization < ActiveRecord::Base
2   - belongs_to :product_category, :foreign_key => 'category_id'
3   - belongs_to :product
4   -
5   - extend Categorization
6   -
7   - class << self
8   - alias :add_category_to_product :add_category_to_object
9   - def object_id_column
10   - :product_id
11   - end
12   - end
13   -end
app/models/product_category.rb
1 1 class ProductCategory < Category
  2 + # FIXME: do not allow category with products or inputs to be destroyed
2 3 has_many :products
3 4 has_many :inputs
4 5  
... ... @@ -9,4 +10,7 @@ class ProductCategory &lt; Category
9 10 def self.menu_categories(top_category, env)
10 11 top_category ? top_category.children : top_level_for(env).select{|c|c.kind_of?(ProductCategory)}
11 12 end
  13 +
  14 + after_save_reindex [:products], :with => :delayed_job
  15 +
12 16 end
... ...
app/models/profile.rb
... ... @@ -3,6 +3,12 @@
3 3 # which by default is the one returned by Environment:default.
4 4 class Profile < ActiveRecord::Base
5 5  
  6 + # use for internationalizable human type names in search facets
  7 + # reimplement on subclasses
  8 + def self.type_name
  9 + _('Profile')
  10 + end
  11 +
6 12 module Roles
7 13 def self.admin(env_id)
8 14 find_role('admin', env_id)
... ... @@ -74,8 +80,6 @@ class Profile &lt; ActiveRecord::Base
74 80  
75 81 acts_as_having_boxes
76 82  
77   - acts_as_searchable :additional_fields => [ :extra_data_for_index ]
78   -
79 83 acts_as_taggable
80 84  
81 85 def self.qualified_column_names
... ... @@ -125,8 +129,6 @@ class Profile &lt; ActiveRecord::Base
125 129  
126 130 validates_length_of :description, :maximum => 550, :allow_nil => true
127 131  
128   - acts_as_mappable :default_units => :kms
129   -
130 132 # Valid identifiers must match this format.
131 133 IDENTIFIER_FORMAT = /^#{Noosfero.identifier_format}$/
132 134  
... ... @@ -183,8 +185,20 @@ class Profile &lt; ActiveRecord::Base
183 185 has_many :profile_categorizations, :conditions => [ 'categories_profiles.virtual = ?', false ]
184 186 has_many :categories, :through => :profile_categorizations
185 187  
  188 + has_many :profile_categorizations_including_virtual, :class_name => 'ProfileCategorization'
  189 + has_many :categories_including_virtual, :through => :profile_categorizations_including_virtual, :source => :category
  190 +
186 191 has_many :abuse_complaints, :foreign_key => 'requestor_id'
187 192  
  193 + def top_level_categorization
  194 + ret = {}
  195 + self.profile_categorizations.each do |c|
  196 + p = c.category.top_ancestor
  197 + ret[p] = (ret[p] || []) + [c.category]
  198 + end
  199 + ret
  200 + end
  201 +
188 202 def interests
189 203 categories.select {|item| !item.is_a?(Region)}
190 204 end
... ... @@ -219,12 +233,15 @@ class Profile &lt; ActiveRecord::Base
219 233 @pending_categorizations ||= []
220 234 end
221 235  
222   - def add_category(c)
223   - if self.id
224   - ProfileCategorization.add_category_to_profile(c, self)
225   - else
  236 + def add_category(c, reload=false)
  237 + if new_record?
226 238 pending_categorizations << c
  239 + else
  240 + ProfileCategorization.add_category_to_profile(c, self)
  241 + self.categories(true)
  242 + self.solr_save
227 243 end
  244 + self.categories(reload)
228 245 end
229 246  
230 247 def category_ids=(ids)
... ... @@ -529,6 +546,7 @@ private :generate_url, :url_options
529 546 other.top_level_articles.each do |a|
530 547 copy_article_tree a
531 548 end
  549 + self.articles.reload
532 550 end
533 551  
534 552 def copy_article_tree(article, parent=nil)
... ... @@ -824,18 +842,105 @@ private :generate_url, :url_options
824 842 name
825 843 end
826 844  
827   - protected
  845 + # Override in your subclasses
  846 + def activities
  847 + []
  848 + end
  849 +
  850 + private
  851 + def self.f_categories_label_proc(environment)
  852 + ids = environment.top_level_category_as_facet_ids
  853 + r = Category.find(ids)
  854 + map = {}
  855 + ids.map{ |id| map[id.to_s] = r.detect{|c| c.id == id}.name }
  856 + map
  857 + end
  858 + def self.f_categories_proc(facet, id)
  859 + id = id.to_i
  860 + return if id.zero?
  861 + c = Category.find(id)
  862 + c.name if c.top_ancestor.id == facet[:label_id].to_i or facet[:label_id] == 0
  863 + end
  864 + def f_categories
  865 + category_ids - [region_id]
  866 + end
828 867  
829   - def followed_by?(person)
830   - person.is_member_of?(self)
  868 + def f_region
  869 + self.region_id
  870 + end
  871 + def self.f_region_proc(id)
  872 + c = Region.find(id)
  873 + s = c.parent
  874 + if c and c.kind_of?(City) and s and s.kind_of?(State) and s.acronym
  875 + [c.name, ', ' + s.acronym]
  876 + else
  877 + c.name
831 878 end
  879 + end
832 880  
833   - def display_private_info_to?(user)
834   - if user.nil?
835   - false
836   - else
837   - (user == self) || (user.is_admin?(self.environment)) || user.is_admin?(self) || user.memberships.include?(self)
838   - end
  881 + def self.f_enabled_proc(enabled)
  882 + enabled = enabled == "true" ? true : false
  883 + enabled ? _('Enabled') : _('Not enabled')
  884 + end
  885 + def f_enabled
  886 + self.enabled
  887 + end
  888 +
  889 + def name_sortable # give a different name for solr
  890 + name
  891 + end
  892 + def public
  893 + self.public?
  894 + end
  895 + def category_filter
  896 + categories_including_virtual_ids
  897 + end
  898 + public
  899 +
  900 + acts_as_faceted :fields => {
  901 + :f_enabled => {:label => _('Situation'), :type_if => proc { |klass| klass.kind_of?(Enterprise) },
  902 + :proc => proc { |id| f_enabled_proc(id) }},
  903 + :f_region => {:label => _('City'), :proc => proc { |id| f_region_proc(id) }},
  904 + :f_categories => {:multi => true, :proc => proc {|facet, id| f_categories_proc(facet, id)},
  905 + :label => proc { |env| f_categories_label_proc(env) }, :label_abbrev => proc{ |env| f_categories_label_abbrev_proc(env) }},
  906 + }, :category_query => proc { |c| "category_filter:#{c.id}" },
  907 + :order => [:f_region, :f_categories, :f_enabled]
  908 +
  909 + acts_as_searchable :fields => facets_fields_for_solr + [:extra_data_for_index,
  910 + # searched fields
  911 + {:name => {:type => :text, :boost => 2.0}},
  912 + {:identifier => :text}, {:address => :text}, {:nickname => :text},
  913 + # filtered fields
  914 + {:public => :boolean}, {:environment_id => :integer},
  915 + {:category_filter => :integer},
  916 + # ordered/query-boosted fields
  917 + {:name_sortable => :string}, {:user_id => :integer},
  918 + :enabled, :active, :validated, :public_profile,
  919 + {:lat => :float}, {:lng => :float},
  920 + :updated_at, :created_at,
  921 + ],
  922 + :include => [
  923 + {:region => {:fields => [:name, :path, :slug, :lat, :lng]}},
  924 + {:categories => {:fields => [:name, :path, :slug, :lat, :lng, :acronym, :abbreviation]}},
  925 + ], :facets => facets_option_for_solr,
  926 + :boost => proc{ |p| 10 if p.enabled }
  927 + after_save_reindex [:articles], :with => :delayed_job
  928 + handle_asynchronously :solr_save
  929 +
  930 + def control_panel_settings_button
  931 + {:title => _('Profile Info and settings'), :icon => 'edit-profile'}
  932 + end
  933 +
  934 + def followed_by?(person)
  935 + person.is_member_of?(self)
  936 + end
  937 +
  938 + def display_private_info_to?(user)
  939 + if user.nil?
  940 + false
  941 + else
  942 + (user == self) || (user.is_admin?(self.environment)) || user.is_admin?(self) || user.memberships.include?(self)
839 943 end
  944 + end
840 945  
841 946 end
... ...
app/models/qualifier.rb
... ... @@ -2,16 +2,19 @@ class Qualifier &lt; ActiveRecord::Base
2 2  
3 3 belongs_to :environment
4 4  
5   - has_many :qualifier_certifiers
  5 + has_many :qualifier_certifiers, :dependent => :destroy
6 6 has_many :certifiers, :through => :qualifier_certifiers
7 7  
  8 + has_many :product_qualifiers, :dependent => :destroy
  9 + has_many :products, :through => :product_qualifiers, :source => :product
  10 +
8 11 validates_presence_of :environment_id
9 12 validates_presence_of :name
10 13  
11   - has_many :product_qualifiers, :dependent => :destroy
12   -
13 14 def <=>(b)
14 15 self.name.downcase.transliterate <=> b.name.downcase.transliterate
15 16 end
16 17  
  18 + after_save_reindex [:products], :with => :delayed_job
  19 +
17 20 end
... ...
app/models/qualifier_certifier.rb
1 1 class QualifierCertifier < ActiveRecord::Base
2 2 belongs_to :qualifier
3 3 belongs_to :certifier
  4 +
  5 + validates_presence_of :qualifier
4 6 end
... ...
app/models/raw_html_article.rb
1 1 class RawHTMLArticle < TextArticle
2 2  
  3 + def self.type_name
  4 + _('HTML')
  5 + end
  6 +
3 7 def self.short_description
4 8 _('Raw HTML text article.')
5 9 end
... ...
app/models/region.rb
... ... @@ -5,19 +5,17 @@ class Region &lt; Category
5 5 require_dependency 'enterprise' # enterprises can also be validators
6 6  
7 7 # searches for organizations that could become validators for this region.
8   - # <tt>search</tt> is passed as is to ferret's find_by_contents on Organizatio
9   - # find_by_contents on Organization class.
  8 + # <tt>search</tt> is passed as is to find_by_contents on Organization.
10 9 def search_possible_validators(search)
11   - Organization.find_by_contents(search).reject {|item| self.validator_ids.include?(item.id) }
  10 + Organization.find_by_contents(search)[:results].docs.reject {|item| self.validator_ids.include?(item.id) }
12 11 end
13 12  
14 13 def has_validator?
15 14 validators.count > 0
16 15 end
17 16  
18   - def self.with_validators
19   - Region.find(:all, :joins => 'INNER JOIN region_validators on (region_validators.region_id = categories.id)', :select => "distinct #{table_name}.*")
20   - end
  17 + named_scope :with_validators, :group => 'id',
  18 + :joins => 'INNER JOIN region_validators on (region_validators.region_id = categories.id)'
21 19  
22 20 end
23 21  
... ...
app/models/rss_feed.rb
1 1 class RssFeed < Article
2 2  
  3 + def self.type_name
  4 + _('RssFeed')
  5 + end
  6 +
3 7 # i dont know why before filter dont work here
4 8 def initialize(*args)
5 9 super(*args)
... ...
app/models/scrap.rb
... ... @@ -12,6 +12,7 @@ class Scrap &lt; ActiveRecord::Base
12 12 named_scope :not_replies, :conditions => {:scrap_id => nil}
13 13  
14 14 track_actions :leave_scrap, :after_create, :keep_params => ['sender.name', 'content', 'receiver.name', 'receiver.url'], :if => Proc.new{|s| s.receiver != s.sender}, :custom_target => :action_tracker_target
  15 +
15 16 track_actions :leave_scrap_to_self, :after_create, :keep_params => ['sender.name', 'content'], :if => Proc.new{|s| s.receiver == s.sender}
16 17  
17 18 after_create do |scrap|
... ...
app/models/task.rb
... ... @@ -4,7 +4,7 @@
4 4 #
5 5 # The specific types of tasks <em>must</em> override the #perform method, so
6 6 # the actual action associated to the type of task can be performed. See the
7   -# documentation of the #perform method for details.
  7 +# documentation of the #perform method for details.
8 8 #
9 9 # This class has a +data+ field of type <tt>text</tt>, where you can store any
10 10 # type of data (as serialized Ruby objects) you need for your subclass (which
... ... @@ -64,7 +64,9 @@ class Task &lt; ActiveRecord::Base
64 64  
65 65 begin
66 66 target_msg = task.target_notification_message
67   - TaskMailer.deliver_target_notification(task, target_msg) if target_msg
  67 + if target_msg && task.target && !task.target.notification_emails.empty?
  68 + TaskMailer.deliver_target_notification(task, target_msg)
  69 + end
68 70 rescue NotImplementedError => ex
69 71 RAILS_DEFAULT_LOGGER.info ex.to_s
70 72 end
... ... @@ -192,7 +194,7 @@ class Task &lt; ActiveRecord::Base
192 194  
193 195 # The message that will be sent to the *target* of the task when it is
194 196 # created. The indent of this message is to notify the target about the
195   - # request that was just created for him/her.
  197 + # request that was just created for him/her.
196 198 #
197 199 # The implementation in this class returns +nil+, what makes the notification
198 200 # not to be sent. If you want to send a notification to the target upon task
... ... @@ -225,7 +227,9 @@ class Task &lt; ActiveRecord::Base
225 227  
226 228 begin
227 229 target_msg = target_notification_message
228   - TaskMailer.deliver_target_notification(self, target_msg) if target_msg
  230 + if target_msg && self.target && !self.target.notification_emails.empty?
  231 + TaskMailer.deliver_target_notification(self, target_msg)
  232 + end
229 233 rescue NotImplementedError => ex
230 234 RAILS_DEFAULT_LOGGER.info ex.to_s
231 235 end
... ... @@ -253,7 +257,7 @@ class Task &lt; ActiveRecord::Base
253 257  
254 258 # sends notification e-mail about a task, if the task has a requestor.
255 259 #
256   - # If
  260 + # If
257 261 def send_notification(action)
258 262 if sends_email?
259 263 if self.requestor
... ...
app/models/text_article.rb
... ... @@ -3,6 +3,10 @@ class TextArticle &lt; Article
3 3  
4 4 xss_terminate :only => [ :name ], :on => 'validation'
5 5  
  6 + def self.type_name
  7 + _('Article')
  8 + end
  9 +
6 10 include Noosfero::TranslatableContent
7 11  
8 12 def self.icon_name(article = nil)
... ...
app/models/uploaded_file.rb
... ... @@ -4,7 +4,11 @@
4 4 # of the file itself is kept. (FIXME?)
5 5 class UploadedFile < Article
6 6  
7   - track_actions :upload_image, :after_create, :keep_params => ["view_url", "thumbnail_path", "parent.url", "parent.name"], :if => Proc.new { |a| a.published? && a.image? && !a.parent.nil? && a.parent.gallery? }
  7 + def self.type_name
  8 + _('File')
  9 + end
  10 +
  11 + track_actions :upload_image, :after_create, :keep_params => ["view_url", "thumbnail_path", "parent.url", "parent.name"], :if => Proc.new { |a| a.published? && a.image? && !a.parent.nil? && a.parent.gallery? }, :custom_target => :action_tracker_target
8 12  
9 13 include ShortFilename
10 14  
... ... @@ -25,7 +29,7 @@ class UploadedFile &lt; Article
25 29 end
26 30  
27 31 def thumbnail_path
28   - self.image? ? self.full_filename(:thumb).gsub(File.join(RAILS_ROOT, 'public'), '') : nil
  32 + self.image? ? self.full_filename(:display).gsub(File.join(RAILS_ROOT, 'public'), '') : nil
29 33 end
30 34  
31 35 def display_title
... ... @@ -140,4 +144,9 @@ class UploadedFile &lt; Article
140 144 def uploaded_file?
141 145 true
142 146 end
  147 +
  148 + def action_tracker_target
  149 + self
  150 + end
  151 +
143 152 end
... ...
app/views/account/signup.rhtml
... ... @@ -5,7 +5,7 @@
5 5 <p><%= _("Firstly, some tips for getting started:") %></p>
6 6 <h4><%= _("Confirm your account!") %></h4>
7 7 <p><%= _("You should receive a welcome email from us shortly. Please take a second to follow the link within to confirm your account.") %></p>
8   - <p><%= _("You won't appear as %s until your account is confirmed.") % link_to(_('user'), {:controller => :browse, :action => :people, :filter => 'more_recent'}, :target => '_blank') %></p>
  8 + <p><%= _("You won't appear as %s until your account is confirmed.") % link_to(_('user'), {:controller => :search, :action => :people, :filter => 'more_recent'}, :target => '_blank') %></p>
9 9 <h4><%= _("What to do next?") %></h4>
10 10 <p><%= _("%s. Upload an avatar and let your friends find you easily :)") % link_to(_('Customize your profile'), {:controller => 'doc', :section => 'user', :topic => 'editing-person-info'}, :target => '_blank') %></p>
11 11 <p><%= _("Learn the guidelines. Read the %s for more details on how to use this social network!") % link_to(_('Documentation'), {:controller => 'doc'}, :target => '_blank') %></p>
... ...
app/views/browse/_article.rhtml
... ... @@ -1,11 +0,0 @@
1   -<li class="<%= 'browse-results-type-content ' + icon_for_article(result) %>">
2   - <strong><%= link_to(result.title, result.view_url) %></strong>
3   - <div class="item_meta">
4   - <span class="item_by">
5   - <%= _('by %s') % link_to(result.author.name, result.author.url) %>
6   - </span>
7   - <span class="extra-info">
8   - <%= (@filter == 'more_recent' ? result.send(@filter + '_label') + show_date(result.created_at) : result.send(@filter + '_label')) %>
9   - </span>
10   - </div>
11   -</li>
app/views/browse/_display_results.rhtml
... ... @@ -1,19 +0,0 @@
1   -<div id="browse-results">
2   -
3   - <div class='browse-results-innerbox'>
4   - <% if @results.empty? %>
5   - <div class="browse-results-type-empty">
6   - <div> <%= _('None') %> </div>
7   - </div><!-- end class="browse-results-innerbox" -->
8   - <% end %>
9   - <ul class='common-profile-list-block'>
10   - <% @results.each do |result| %>
11   - <%= render :partial => partial_for_class(result.class), :locals => {:result => result} %>
12   - <% end %>
13   - </ul>
14   - <br style='clear: both;'>
15   - </div>
16   -
17   - <br style="clear:both" />
18   -</div><!-- end id="browse-results" -->
19   -
app/views/browse/_person.rhtml
... ... @@ -1,3 +0,0 @@
1   -<%= profile_image_link result, :portrait, 'li',
2   - "<span class='adr'>#{result.city}</span>" +
3   - (@filter == 'more_recent' ? result.send(@filter + '_label') + show_date(result.created_at) : result.send(@filter + '_label')) %>
app/views/browse/_profile.rhtml
... ... @@ -1 +0,0 @@
1   -<%= profile_image_link result, :portrait, 'li', @filter == 'more_recent' ? result.send(@filter + '_label') + show_date(result.created_at) : result.send(@filter + '_label') %>
app/views/browse/_search_form.rhtml
... ... @@ -1,10 +0,0 @@
1   -<% form_tag( { :controller => 'browse', :action => action}, :method => 'get', :class => 'search_form' ) do %>
2   -
3   - <div class="search-field">
4   - <span class="formfield">
5   - <%= text_field_tag 'query', @query, :size => 50 %>
6   - </span>
7   - <%= submit_button(:search, _('Search')) %>
8   - </div>
9   -
10   -<% end %>
app/views/browse/communities.rhtml
... ... @@ -1,9 +0,0 @@
1   -<%= search_page_title( @title, { :query => @query} ) %>
2   -
3   -<%= render :partial => 'search_form', :locals => {:action => 'communities'} %>
4   -
5   -<%= render :partial => 'display_results' %>
6   -
7   -<%= pagination_links @results %>
8   -
9   -<br style="clear:both" />
app/views/browse/contents.rhtml
... ... @@ -1,9 +0,0 @@
1   -<%= search_page_title( @title, { :query => @query} ) %>
2   -
3   -<%= render :partial => 'search_form', :locals => {:action => 'contents'} %>
4   -
5   -<%= render :partial => 'display_results' %>
6   -
7   -<%= pagination_links @results %>
8   -
9   -<br style="clear:both" />
app/views/browse/people.rhtml
... ... @@ -1,9 +0,0 @@
1   -<%= search_page_title( @title, { :query => @query} ) %>
2   -
3   -<%= render :partial => 'search_form', :locals => {:action => 'people'} %>
4   -
5   -<%= render :partial => 'display_results' %>
6   -
7   -<%= pagination_links @results %>
8   -
9   -<br style="clear:both" />
app/views/events/events.rhtml
1   -<h1 id='agenda-title'>
2   - <div id='agenda-toolbar'>
3   - <%= button :back, _('Back to %s') % profile.name, profile.url %>
4   - <% if user && user.has_permission?('post_content', profile) %>
5   - <%= button :new, _('New event'), myprofile_url(:controller => 'cms', :action => 'new', :type => 'Event') %>
6   - <% end %>
7   - </div>
8   - <%= _("%s's events") % profile.name %>
9   -</h1>
  1 +<h1 id='agenda-title'><%= _("%s's events") % profile.name %></h1>
  2 +
  3 +<div id='agenda-toolbar'>
  4 + <%= button :back, _('Back to %s') % profile.name, profile.url %>
  5 + <% if user && user.has_permission?('post_content', profile) %>
  6 + <%= button :new, _('New event'), myprofile_url(:controller => 'cms', :action => 'new', :type => 'Event') %>
  7 + <% end %>
  8 +</div>
  9 +
  10 +<div style="clear: both"></div>
10 11  
11 12 <%= render :partial => 'agenda' %>
... ...
app/views/layouts/_javascript.rhtml
1   -<%= javascript_include_tag :defaults, 'jquery-latest.js', 'jquery.noconflict.js', 'jquery.cycle.all.min.js', 'thickbox.js', 'lightbox', 'jquery-ui-1.8.2.custom.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery.cookie', 'reflection', 'add-and-join', 'jquery.tokeninput', 'report-abuse','colorbox', 'jquery-validation/jquery.validate', 'catalog', 'manage-products', :cache => 'cache-general' %>
  1 +<%= javascript_include_tag :defaults, 'jquery-latest.js',
  2 +'jquery.noconflict.js', 'jquery.cycle.all.min.js', 'thickbox.js', 'lightbox', 'colorbox',
  3 +'jquery-ui-1.8.2.custom.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery-validation/jquery.validate',
  4 +'jquery.cookie', 'jquery.ba-bbq.min.js', 'reflection', 'jquery.tokeninput',
  5 +'add-and-join', 'report-abuse', 'catalog', 'manage-products', :cache => 'cache-general' %>
2 6  
3 7 <% language = FastGettext.locale %>
4 8 <% %w{messages methods}.each do |type| %>
... ...
app/views/layouts/application-ng.rhtml
... ... @@ -61,16 +61,10 @@
61 61 <%= render :file => 'account/login', :locals => { :is_thickbox => true } %>
62 62 </div>
63 63 </span>
64   - <form action="/search" class="search_form clean" method="get" id="top-search">
65   - <input name="query" size="15" value="<%=_('Search...')%>"
66   - onfocus="this.form.className='focused';
67   - if(this.value=='<%=_('Search...')%>'){this.value=''}"
68   - onblur="this.form.className='';
69   - if(/^\s*$/.test(this.value)){
70   - this.value='<%=_('Search...')%>';
71   - this.form.className='clean'
72   - }" />
  64 + <form action="/search" class="search_form" method="get" class="clean">
  65 + <input name="query" size="15" title="<%=_('Search...')%>" onfocus="this.form.className='focused';" onblur="this.form.className=''" />
73 66 <div><%=_('Press <strong>Enter</strong> to send the search query.')%></div>
  67 + <%= javascript_tag 'jQuery("#user form input").hint();' %>
74 68 </form>
75 69 </div><!-- end id="user" -->
76 70  
... ...
app/views/manage_products/_certifiers_for_selection.rhtml
1   -<%= select_certifiers(@qualifier) %>
2   -<%= button_to_function(:delete, '', "jQuery(this).parents('tr').remove()") %>
  1 +<%= select_certifiers(@qualifier) + remove_qualifier_button %>
3 2 <% javascript_tag do %>
4 3 jQuery('#product-qualifiers-list *').removeClass('small-loading')
5 4 <% end %>
... ...
app/views/manage_products/_display_image.rhtml
1 1 <div id='display-product-image'>
2   - <%= image_tag (@product.reload.default_image('thumb')), :class => 'product-pic' %>
  2 + <%= image_tag (@product.reload.default_image('big')), :class => 'product-pic' %>
3 3 </div>
4 4  
  5 +<% if @product.image %>
  6 + <%= link_to content_tag(:span, _('Zoom in')), @product.image.public_filename,
  7 + :class => 'zoomify-image' %>
  8 +<% end %>
  9 +<%= add_zoom_to_images %>
  10 +
5 11 <%= edit_product_link_to_remote(@product, 'image', _('Change image')) %>
... ...
app/views/manage_products/_edit_info.rhtml
... ... @@ -39,8 +39,7 @@
39 39 <%= select_qualifiers(@product, qualifier.id) %>
40 40 </td>
41 41 <td id='certifier-area-<%= index %>'>
42   - <%= select_certifiers(qualifier, @product) %>
43   - <%= button_to_function(:delete, content_tag('span', 'Delete qualifier'), "jQuery(this).parents('tr').remove()") %>
  42 + <%= select_certifiers(qualifier, @product) + remove_qualifier_button %>
44 43 </td>
45 44 </tr>
46 45 <% end %>
... ... @@ -48,9 +47,9 @@
48 47 <%= button_to_function(
49 48 :add,
50 49 _('Add new qualifier'),
51   - "new_qualifier_row('#product-qualifiers-list', '#{escape_javascript(select_qualifiers(@product))}')"
  50 + "new_qualifier_row('#product-qualifiers-list', '#{escape_javascript(select_qualifiers(@product))}', '#{escape_javascript(remove_qualifier_button)}')"
52 51 ) %>
53   - <%= hidden_field_tag "product[qualifiers_list]" %>
  52 + <%= hidden_field_tag "product[qualifiers_list][nil]" %>
54 53 <% end %>
55 54  
56 55 <%= hidden_field_tag 'info-bar-update-url', @product.price_composition_bar_display_url, :class => 'bar-update-url' %>
... ...
app/views/manage_products/show.rhtml
... ... @@ -9,7 +9,7 @@
9 9 </div>
10 10  
11 11 <div id='product-details'>
12   - <div id='product-image'>
  12 + <div id='product-image' class="zoomable-image">
13 13 <%= render :partial => 'manage_products/display_image' %>
14 14 </div>
15 15 <div id='product-extra-content'>
... ...
app/views/map_balloon/product.rhtml 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +<div id="balloon">
  2 + <%= render :partial => 'search/product', :locals => {:product => @product} %>
  3 +</div>
... ...
app/views/map_balloon/profile.rhtml 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +<div id="balloon">
  2 + <table class='profile-info'>
  3 + <tr>
  4 + <td><div class='profile-info-picture'><%= profile_image(@profile, :thumb) %></div></td>
  5 + <td>
  6 + <strong><%= link_to(@profile.name, url_for(@profile.url)) %></strong><br/>
  7 + <% unless @profile.contact_email.nil? %>
  8 + <strong><%= _('E-Mail: ') + @profile.contact_email %></strong><br/>
  9 + <% end %>
  10 + <% unless @profile.contact_phone.nil? %>
  11 + <strong><%= _('Phone(s): ') + @profile.contact_phone %></strong><br/>
  12 + <% end %>
  13 + <% unless @profile.region.nil? %>
  14 + <strong><%= _('Location: ') + @profile.region.name %></strong><br/>
  15 + <% end %>
  16 + <% unless @profile.address.nil? %>
  17 + <strong><%= _('Address: ') + @profile.address %></strong><br/>
  18 + <% end %>
  19 + <% if @profile.respond_to?(:products) and !@profile.products.blank? %>
  20 + <strong><%= _('Products/Services: ') + @profile.products.map{|i| link_to(i.name, :controller => 'manage_products', :profile => @profile.identifier, :action => 'show', :id => i.id)}.join(', ') %></strong><br/>
  21 + <% end %>
  22 + <% if @profile.respond_to?(:distance) and !@profile.distance.nil? %>
  23 + <strong><%= _('Distance: ') + "%.2f%" % @profile.distance %></strong><br/>
  24 + <% end %>
  25 + </td>
  26 + </tr>
  27 + </table>
  28 +</div>
... ...
app/views/maps/_google_map.js.erb 0 → 100644
... ... @@ -0,0 +1,252 @@
  1 +
  2 +var geocoder;
  3 +var map;
  4 +var marker;
  5 +var center;
  6 +var move = true;
  7 +var previousCenter;
  8 +
  9 +function getAddress(latlng) {
  10 + $('location-fields').addClassName("loading");
  11 +
  12 + if (latlng != null) {
  13 + geocoder.geocode( {'latLng': latlng}, showAddress);
  14 + }
  15 +}
  16 +
  17 +function codeAddress() {
  18 + $('location-fields').addClassName("loading");
  19 +
  20 + var country_option = $('profile_data_country').value;
  21 + var address = $('profile_data_address').value + "-" + $('profile_data_zip_code').value + "," + $('profile_data_city').value+ "-" + $('profile_data_state').value + "," + country_option;
  22 +
  23 + if (geocoder) {
  24 + geocoder.geocode( { 'address': address}, function(results, status) {
  25 + if (status == google.maps.GeocoderStatus.OK) {
  26 + map.setCenter(results[0].geometry.location);
  27 + marker.setPosition(results[0].geometry.location);
  28 + getAddress(marker.getPosition());
  29 +
  30 + $('profile_data_lat').value = results[0].geometry.location.lat();
  31 + $('profile_data_lng').value = results[0].geometry.location.lng();
  32 + $('location-fields').removeClassName("loading");
  33 + enable_save();
  34 + } else {
  35 + $('location-fields').removeClassName("loading");
  36 + alert('<%=_("Address not found, reason:")%>' + translate_status(status));
  37 + }
  38 + });
  39 + }
  40 +
  41 + map.setZoom(11);
  42 +
  43 + return false;
  44 +}
  45 +
  46 +function translate_status(status)
  47 +{
  48 + var translated_status = '';
  49 +
  50 + if (google.maps.GeocoderStatus.INVALID_REQUEST == status)
  51 + translated_status = '<%= _('Invalid address') %>';
  52 + else if (google.maps.GeocoderStatus.REQUEST_DENIED == status)
  53 + translated_status = '<%= _('Request denied') %>';
  54 + else if (google.maps.GeocoderStatus.OVER_QUERY_LIMIT == status)
  55 + translated_status = '<%= _('Over query limit') %>';
  56 + else if (google.maps.GeocoderStatus.ZERO_RESULTS == status)
  57 + translated_status = "<%= _('Address do not exist') %>";
  58 +
  59 + return translated_status;
  60 +}
  61 +
  62 +function getAddressData() {
  63 + var text = '';
  64 + var fields = [
  65 + 'profile_data_country',
  66 + 'profile_data_state',
  67 + 'profile_data_city',
  68 + 'profile_data_address',
  69 + 'profile_data_zip_code'
  70 + ];
  71 + for (var i = 0; i < fields.length; i++) {
  72 + var field = fields[i];
  73 + if ($(field)) {
  74 + text += $(field).value + " ";
  75 + }
  76 + }
  77 + return text;
  78 +}
  79 +
  80 +function showAddress(results, status) {
  81 +
  82 + if (status == google.maps.GeocoderStatus.OK) {
  83 + map.setCenter(results[0].geometry.location);
  84 + updateFields(results[0]);
  85 +
  86 + } else {
  87 + alert("<%=_("Address not found, reason:")%>" + translate_status(status));
  88 + }
  89 +
  90 +}
  91 +
  92 +function updateFields(place) {
  93 + var position = marker.getPosition();
  94 + $('profile_data_lat').value = position.lat();
  95 + $('profile_data_lng').value = position.lng();
  96 + $('location-fields').removeClassName("loading");
  97 +
  98 + form = jQuery('#location-form')[0];
  99 + form.lat = marker.getPosition().lat();
  100 + form.lng = marker.getPosition().lng();
  101 +
  102 + var components_len = place.address_components.size();
  103 +
  104 + if(components_len < 2)
  105 + {
  106 + return false;
  107 + }
  108 +
  109 + var components = place.address_components;
  110 + var address = "";
  111 + var zip_code = "";
  112 + var city = "";
  113 + var state = "";
  114 + var country_code = "";
  115 + var i = 0;
  116 +
  117 + for( i =0 ; i < components_len; i ++)
  118 + {
  119 +
  120 + if (components[i].types[0] == 'country')
  121 + country_code = components[i].short_name;
  122 + else if (components[i].types[0] == 'administrative_area_level_1')
  123 + state = components[i].long_name;
  124 + else if (components[i].types[0] == 'locality')
  125 + city = components[i].long_name;
  126 + else if (components[i].types[0] == 'sublocality')
  127 + address = components[i].long_name + "-" + address;
  128 + else if (components[i].types[0] == "route")
  129 + address = components[i].long_name + address;
  130 + else if (components[i].types[0] == "street_number")
  131 + address = address + "," + components[i].short_name ;
  132 + else if (components[i].types[0] == 'postal_code')
  133 + zip_code = components[i].short_name;
  134 + }
  135 +
  136 + $('profile_data_country').value = country_code;
  137 + $('profile_data_state').value = state;
  138 + $('profile_data_address').value = address;
  139 + $('profile_data_city').value = city;
  140 + $('profile_data_zip_code').value = zip_code;
  141 +}
  142 +
  143 +
  144 +function initialize_map() {
  145 + geocoder = new google.maps.Geocoder();
  146 +
  147 + var lat = <%= profile.lat || 'false' %>;
  148 + var lng = <%= profile.lng || 'false' %>;
  149 +
  150 + if ( !(lat && lng) ) {
  151 + lat = -15.7605361485013;
  152 + lng = -47.933349609375;
  153 + }
  154 +
  155 + var latlng = new google.maps.LatLng(lat,lng);
  156 +
  157 + var myOptions = {
  158 + zoom: 8,
  159 + center: latlng,
  160 + mapTypeId: google.maps.MapTypeId.ROADMAP
  161 + }
  162 +
  163 + center = latlng;
  164 +
  165 + map = new google.maps.Map(document.getElementById("location-map"), myOptions);
  166 +
  167 + continueLoadMapV3()
  168 +}
  169 +
  170 +function continueLoadMapV3() {
  171 +
  172 + marker = new google.maps.Marker({
  173 + position: center,
  174 + map: map,
  175 + draggable: true
  176 + });
  177 +
  178 + google.maps.event.addListener(marker, "dragend", function() {
  179 + move = false;
  180 + getAddress(marker.getPosition());
  181 + enable_save();
  182 + });
  183 +
  184 +}
  185 +
  186 +window.onload = initialize_map;
  187 +
  188 +var delay_autocomplete = 500;
  189 +
  190 +jQuery.noConflict();
  191 +jQuery(document).ready(function (){
  192 +
  193 + jQuery.widget( "custom.catcomplete",jQuery.ui.autocomplete, {
  194 + _renderMenu: function( ul, items ) {
  195 + var self = this,
  196 + currentCategory = "";
  197 + jQuery.each( items, function( index, item ) {
  198 + if ( item.category != currentCategory ) {
  199 + ul.append( "<li class='ui-autocomplete-category'>" + item.category + "</li>" );
  200 + currentCategory = item.category;
  201 + }
  202 + self._renderItem( ul, item );
  203 + });
  204 + }
  205 + });
  206 +
  207 +
  208 + jQuery("#profile_data_city").catcomplete({
  209 + source: "../maps/search_city",
  210 + minLength: 3,
  211 + delay: delay_autocomplete,
  212 + select: function( event, ui ) { $('profile_data_state').value =( ui.item ? ui.item.category : this.value ); }
  213 + });
  214 +
  215 + jQuery("#profile_data_state").autocomplete({
  216 + source: "../maps/search_state",
  217 + minLength: 3,
  218 + delay: delay_autocomplete
  219 + });
  220 +
  221 + jQuery("#profile_data_city").keyup(function(){
  222 +
  223 + disable_save();
  224 +
  225 + });
  226 +
  227 + jQuery("#profile_data_state").keyup(function(){
  228 +
  229 + disable_save();
  230 +
  231 + });
  232 +
  233 + jQuery("#profile_data_country").change(function(){
  234 +
  235 + disable_save();
  236 +
  237 + });
  238 +
  239 +});
  240 +
  241 +function disable_save()
  242 +{
  243 + jQuery('input[type="submit"]').attr("disabled", "true");
  244 + jQuery('input[type="submit"]').val('<%=_("Localize before save")%>');
  245 + jQuery('input[type="submit"]').addClass('disabled');
  246 +}
  247 +function enable_save()
  248 +{
  249 + jQuery('input[type="submit"]').removeAttr("disabled");
  250 + jQuery('input[type="submit"]').val('<%=_("Save")%>');
  251 + jQuery('input[type="submit"]').removeClass('disabled');
  252 +}
... ...
app/views/maps/_google_map.rhtml
... ... @@ -1,136 +0,0 @@
1   -<%= content_tag('script', '', :src => GoogleMaps.api_url(profile.default_hostname), :type => 'text/javascript') %>
2   -
3   -<script type="text/javascript" >
4   - var geocoder;
5   - var map;
6   - var marker;
7   - var center;
8   - var move = true;
9   - var previousCenter;
10   -
11   - function getAddress(overlay, latlng) {
12   - $('location-fields').addClassName("loading");
13   - if (latlng != null) {
14   - geocoder.getLocations(latlng, showAddress);
15   - }
16   - }
17   -
18   - function getAddressData() {
19   - var text = '';
20   - var fields = [
21   - 'profile_data_country',
22   - 'profile_data_state',
23   - 'profile_data_city',
24   - 'profile_data_address',
25   - 'profile_data_zip_code'
26   - ];
27   - for (var i = 0; i < fields.length; i++) {
28   - var field = fields[i];
29   - if ($(field)) {
30   - text += $(field).value + " ";
31   - }
32   - }
33   - return text;
34   - }
35   -
36   - function showAddress(response) {
37   - var message;
38   - place = geoCodeAddress(response);
39   - if ( place ) {
40   - if ( move ) {
41   - center = new GLatLng(place.Point.coordinates[1],place.Point.coordinates[0]);
42   - marker.setLatLng(center);
43   - } else {
44   - move = true;
45   - }
46   - message = showMessage(place);
47   - updateFields(place);
48   - } else {
49   - message = showNotFoundMessage();
50   - }
51   - map.addOverlay(marker);
52   - map.setCenter(marker.getLatLng());
53   - marker.openInfoWindowHtml(message, {maxWidth:300});
54   - }
55   -
56   - function geoCodeAddress(response) {
57   - if (!response || (response && response.Status.code != '200')) {
58   - return false;
59   - } else {
60   - place = response.Placemark[0];
61   - return place;
62   - }
63   - }
64   -
65   - function showMessage(place) {
66   - var message = '<b><%= _('Address:') %></b> ' + place.address + '<br>' +
67   - '<b><%= _('Coordinates:') %></b> ' + place.Point.coordinates[0] + "," + place.Point.coordinates[1] + '<br>' +
68   - '<b><%= _('Country code:') %></b> ' + place.AddressDetails.Country.CountryNameCode + '<br>';
69   - return message;
70   - }
71   -
72   - function showNotFoundMessage() {
73   - var message = '<%= _('Address not found') %>' + '<br>' +
74   - '<b><%= _('Coordinates:') %></b> ' + marker.getLatLng().lng() + "," + marker.getLatLng().lat();
75   - return message;
76   - }
77   -
78   - function updateFields(response) {
79   - var position = marker.getLatLng();
80   - $('profile_data_lat').value = position.lat();
81   - $('profile_data_lng').value = position.lng();
82   - $('location-fields').removeClassName("loading");
83   - }
84   -
85   - function loadMap() {
86   - if (GBrowserIsCompatible()) {
87   - map = new GMap2(document.getElementById("location-map"));
88   - geocoder = new GClientGeocoder();
89   - var lat = <%= profile.lat || 'false' %>;
90   - var lng = <%= profile.lng || 'false' %>;
91   - if ( lat && lng ) {
92   - center = new GLatLng( lat, lng );
93   - continueLoadMap();
94   - } else {
95   - geocoder.getLocations('<%= profile.geolocation %>', loadAddress);
96   - }
97   - }
98   - }
99   -
100   - function loadAddress(response) {
101   - place = geoCodeAddress(response);
102   - if ( move ) {
103   - center = new GLatLng(place.Point.coordinates[1],place.Point.coordinates[0]);
104   - }
105   - continueLoadMap();
106   - }
107   -
108   - function continueLoadMap() {
109   - marker = new GMarker(center, {draggable: true});
110   - map.setCenter(center, 4);
111   -
112   - map.addControl(new GLargeMapControl());
113   - map.addControl(new GScaleControl());
114   - map.addControl(new GMapTypeControl());
115   -
116   - GEvent.addListener(marker, "dragstart", function() {
117   - previousCenter = marker.getLatLng();
118   - map.closeInfoWindow();
119   - });
120   -
121   - GEvent.addListener(marker, "dragend", function() {
122   - move = false;
123   - getAddress(overlay, marker.getLatLng());
124   - });
125   -
126   - GEvent.addListener(marker, "click", function() {
127   - move = false;
128   - getAddress(overlay, marker.getLatLng());
129   - });
130   -
131   - map.addOverlay(marker);
132   - }
133   -
134   - window.onload = loadMap;
135   - window.unload = GUnload();
136   -</script>
app/views/maps/edit_location.rhtml
1 1 <h1><%= _('Location') %></h1>
  2 +<div class="error">
  3 + <%= flash[:error] %>
  4 +</div>
2 5  
3   -<% form_for :profile_data, :url => {:action => 'edit_location'} do |f| %>
  6 +<% form_for :profile_data, :url => {:action => 'edit_location'}, :html => {:id => 'location-form'} do |f| %>
4 7  
5 8 <div id='location-fields'>
6   - <%= optional_field(profile, 'country', select_country(_('Country'), 'profile_data', 'country', {:class => 'type-select'})) %>
7   - <%= optional_field(profile, 'state', labelled_form_field(_('State'), f.text_field(:state))) %>
8   - <%= optional_field(profile, 'city', labelled_form_field(_('City'), f.text_field(:city))) %>
9   - <%= optional_field(profile, 'zip_code', labelled_form_field(_('ZIP code'), text_field(:profile_data, :zip_code))) %>
10   - <%= optional_field(profile, 'address', labelled_form_field(_('Address (street and number)'), text_field(:profile_data, :address))) %>
  9 + <%= select_country _('Country'), 'profile_data', 'country', {:class => 'type-select'} %>
  10 + <%= labelled_form_field _('State'), f.text_field(:state) %>
  11 + <%= labelled_form_field _('City'), f.text_field(:city) %>
  12 + <%= labelled_form_field _('ZIP code'), text_field(:profile_data, :zip_code) %>
  13 + <%= labelled_form_field _('Address (street and number)'), text_field(:profile_data, :address) %>
11 14 <% button_bar do %>
12   - <%= button_to_function :search, _('Locate in the map'), "getAddress(null, getAddressData())", :title => _("Locate the address informed above in the map below (note that you'll probably need to adjust the marker to get a precise position)") %>
  15 + <%= button_to_function :search, _('Locate in the map'), "codeAddress()", :title => _("Locate the address informed above in the map below (note that you'll probably need to adjust the marker to get a precise position)") %>
13 16 <%= submit_button 'save', _('Save') %>
14 17 <%= button(:back, _('Back to control panel'), :controller => 'profile_editor') %>
15 18 <% end %>
16 19 </div>
17 20  
  21 + <p><%= _('Drag the balloon to find the exact location.') %> </p>
18 22  
19 23 <div style='overflow: hidden'>
20 24 <p><div id="location-map"></div></p>
21 25 </div>
22 26  
23   - <%= f.hidden_field(:lat) %>
24   - <%= f.hidden_field(:lng) %>
25   -
26   -
27   -
28   - <% button_bar do %>
29   - <%= submit_button 'save', _('Save') %>
30   - <%= button(:back, _('Back to control panel'), :controller => 'profile_editor') %>
31   - <% end %>
  27 + <%= f.hidden_field :lat %>
  28 + <%= f.hidden_field :lng %>
32 29  
33 30 <% end %>
34 31  
35   -<%= render :partial => 'google_map'%>
  32 +<%= content_tag('script', '', :src => "http://maps.googleapis.com/maps/api/js?sensor=false", :type => 'text/javascript') %>
  33 +<%= content_tag('script', '', :src => url_for(:controller => :maps, :action => :google_map), :type => 'text/javascript') %>
... ...
app/views/profile/_add_member_in_community.rhtml 0 → 100644
... ... @@ -0,0 +1 @@
  1 +<%= render :partial => 'default_activity', :locals => { :activity => activity, :tab_action => tab_action } %>
... ...
app/views/profile/_comment.rhtml 0 → 100644
... ... @@ -0,0 +1,67 @@
  1 +<% Comment %>
  2 +<% Profile %>
  3 +<% Person %>
  4 +
  5 +<li class="article-comment" style='border-bottom:none;'>
  6 + <div class="article-comment-inner">
  7 +
  8 + <div class="comment-content comment-logged-in">
  9 +
  10 + <% if comment.author %>
  11 + <%= link_to image_tag(profile_icon(comment.author, :minor)),
  12 + Person.find(comment.author_id).url,
  13 + :class => 'comment-picture',
  14 + :title => comment.author_name
  15 + %>
  16 + <% end %>
  17 +
  18 + <div class="comment-details">
  19 + <div class="comment-text">
  20 + <%= link_to(comment.author_name, comment.author.url) %>
  21 + <% unless comment.title.blank? %>
  22 + <span class="comment-title"><%= comment.title %></span><br/>
  23 + <% end %>
  24 + <%= txt2html comment.body %>
  25 + </div>
  26 + <div class="profile-activity-time">
  27 + <%= time_ago_as_sentence(comment.created_at) %>
  28 + </div>
  29 + </div>
  30 +
  31 + <% if logged_in? && (user == profile || user == comment.author || user.has_permission?(:moderate_comments, profile)) %>
  32 + <% button_bar(:style => 'float: right; margin-top: 0px;') do %>
  33 + <%= icon_button(:delete, _('Remove'), { :action => :remove_comment, :comment_id => comment.id }, :method => :get, :confirm => _('Are you sure you want to remove this comment and all its replies?')) %>
  34 + <% end %>
  35 + <% end %>
  36 + <br style="clear: both;" />
  37 +
  38 + <div class="comment_reply post_comment_box closed">
  39 + <% if @comment && @comment.errors.any? && @comment.reply_of_id.to_i == comment.id %>
  40 + <%= error_messages_for :comment %>
  41 + <script type="text/javascript">
  42 + jQuery(function() {
  43 + document.location.href = '#<%= comment.anchor %>';
  44 + add_comment_reply_form('#comment-reply-to-<%= comment.id %>', <%= comment.id %>);
  45 + });
  46 + </script>
  47 + <% end %>
  48 + <%= report_abuse(comment.author, :comment_link, comment) if comment.author %>
  49 + <%= link_to_function _('Reply'),
  50 + "var f = add_comment_reply_form(this, %s); f.find('input[name=comment[title]], textarea').val(''); return false" % comment.id,
  51 + :class => 'comment-footer comment-footer-link comment-footer-hide',
  52 + :id => 'comment-reply-to-' + comment.id.to_s
  53 + %>
  54 + </div>
  55 +
  56 + </div>
  57 +
  58 + <% unless comment.replies.blank? %>
  59 + <ul class="comment-replies">
  60 + <% comment.replies.each do |reply| %>
  61 + <%= render :partial => 'comment', :locals => { :comment => reply } %>
  62 + <% end %>
  63 + </ul>
  64 + <% end %>
  65 +
  66 + </div>
  67 +</li>
... ...
app/views/profile/_create_article.rhtml 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +<div class='profile-activity-image'>
  2 + <%= link_to(profile_image(activity.user, :minor), activity.user.url) %>
  3 +</div>
  4 +<div class='profile-activity-description profile-activity-article-<%= activity.target.class.icon_name %>'>
  5 + <p class='profile-activity-text'>
  6 + <%= link_to activity.user.short_name(20), activity.user.url %>
  7 + <%= _("on community %s") % link_to(activity.target.profile.short_name(20), activity.target.profile.url) if activity.target.profile.is_a?(Community) %>
  8 + </p>
  9 + <div class='profile-activity-lead'>
  10 + <div class='article-name'><%= link_to(activity.params['name'], activity.params['url']) %></div>
  11 + <span title='<%= activity.target.class.short_description %>' class='profile-activity-icon icon-new icon-new<%= activity.target.class.icon_name %>'></span>
  12 + <%= image_tag(activity.params['first_image']) unless activity.params['first_image'].blank? %><%= strip_tags(truncate(activity.params['lead'], :length => 1000, :ommision => '...')).gsub(/(\xA0|\xC2|\s)+/, ' ').gsub(/^\s+/, '') %> <small><%= link_to(_('See more'), activity.params['url']) unless activity.get_lead.blank? %></small>
  13 + </div>
  14 + <%= content_tag(:p, link_to(_('See complete forum'), activity.get_url), :class => 'see-forum') if activity.target.is_a?(Forum) %>
  15 + <p class='profile-activity-time'><%= time_ago_as_sentence(activity.created_at) %></p>
  16 + <div class='profile-wall-actions'>
  17 + <%= link_to s_('profile|Comment'), '#', { :class => 'focus-on-comment'} %>
  18 + <%= link_to_remote(content_tag(:span, _('Remove')), :url =>{:action => 'remove_activity', :activity_id => activity.id, :only_hide => true}, :confirm => _('Are you sure?'), :update => "profile-activity-item-#{activity.id}") if logged_in? && current_person == @profile %>
  19 + </div>
  20 +</div>
  21 +
  22 +<%= render :partial => 'profile_comments', :locals => { :activity => activity, :tab_action => tab_action } %>
... ...
app/views/profile/_default_activity.rhtml 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +<div class='profile-activity-image'>
  2 + <%= link_to(profile_image(activity.user, :minor), activity.user.url) %>
  3 +</div>
  4 +<div class='profile-activity-description'>
  5 + <p class='profile-activity-text'><%= link_to activity.user.name, activity.user.url %> <%= describe activity %></p>
  6 + <p class='profile-activity-time'><%= time_ago_as_sentence(activity.created_at) %></p>
  7 + <div class='profile-wall-actions'>
  8 + <%= link_to s_('profile|Comment'), '#', { :class => 'focus-on-comment'} %>
  9 + <%= link_to_remote(content_tag(:span, _('Remove')), :confirm => _('Are you sure?'), :url =>{:action => 'remove_activity', :activity_id => activity.id}, :update => "profile-activity-item-#{activity.id}") if logged_in? && current_person == @profile %>
  10 + </div>
  11 +</div>
  12 +
  13 +<%= render :partial => 'profile_comments', :locals => { :activity => activity, :tab_action => tab_action } %>
... ...
app/views/profile/_join_community.rhtml 0 → 120000
... ... @@ -0,0 +1 @@
  1 +_add_member_in_community.rhtml
0 2 \ No newline at end of file
... ...
app/views/profile/_leave_comment_on_activity.rhtml 0 → 100644
app/views/profile/_leave_scrap.rhtml
1   -<%= @message %>
2   -<% unless @scraps.nil? %>
3   - <%= render :partial => 'profile_scraps', :locals => {:scraps => @scraps} %>
4   -<% end %>
  1 +<div class='profile-activity-image'>
  2 + <%= link_to(profile_image(activity.user, :minor), activity.user.url) %>
  3 +</div>
  4 +<div class='profile-activity-description'>
  5 + <p class='profile-activity-text'><%= link_to activity.user.name, activity.user.url %> <%= describe activity %></p>
  6 + <p class='profile-activity-time'><%= time_ago_as_sentence(activity.created_at) %></p>
  7 + <div class='profile-wall-actions'>
  8 + <%= link_to_remote(content_tag(:span, _('Remove')), :confirm => _('Are you sure?'), :url =>{:action => 'remove_activity', :activity_id => activity.id}, :update => "profile-activity-item-#{activity.id}") if logged_in? && current_person == @profile %>
  9 + </div>
  10 +</div>
  11 +
  12 +<br/>
... ...
app/views/profile/_leave_scrap_to_self.rhtml 0 → 120000
... ... @@ -0,0 +1 @@
  1 +_leave_scrap.rhtml
0 2 \ No newline at end of file
... ...
app/views/profile/_new_friendship.rhtml 0 → 120000
... ... @@ -0,0 +1 @@
  1 +_add_member_in_community.rhtml
0 2 \ No newline at end of file
... ...
app/views/profile/_profile.rhtml
No preview for this file type
app/views/profile/_profile_activities.rhtml
... ... @@ -1,18 +0,0 @@
1   -<% activities.each do |activity| %>
2   - <li class='profile-activity-item <%= activity.verb %>' id='profile-activity-item-<%= activity.id %>'>
3   - <div class='profile-activity-image'>
4   - <%= link_to(profile_image(activity.user, :minor), activity.user.url) %>
5   - </div>
6   - <div class='profile-activity-description'>
7   - <p class='profile-activity-time'><%= time_ago_as_sentence(activity.created_at) + ' ' + _('ago') %></p>
8   - <p class='profile-activity-text'><%= link_to activity.user.name, activity.user.url %> <%= describe activity %></p>
9   - <%= button_to_remote(:delete, content_tag(:span, _('Remove')), :url =>{:action => 'remove_activity', :activity_id => activity.id}, :update => "profile-activity-item-#{activity.id}") if can_edit_profile %>
10   - </div>
11   - <hr />
12   - </li>
13   -<% end %>
14   -<% if activities.current_page < activities.total_pages %>
15   - <div id='profile_activities_page_<%= activities.current_page %>'>
16   - <%= button_to_remote :add, _('View more'), :url => {:action => 'view_more_activities', :page => (activities.current_page + 1)}, :update => "profile_activities_page_#{activities.current_page}" %>
17   - </div>
18   -<% end %>
app/views/profile/_profile_activities_list.rhtml 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +<% unless activities.nil? %>
  2 + <% activities.each do |a| %>
  3 + <% activity = a.klass.constantize.find(a.id) %>
  4 + <% if activity.kind_of?(ActionTracker::Record) %>
  5 + <%= render :partial => 'profile_activity', :locals => { :activity => activity, :tab_action => 'wall' } if activity.visible? %>
  6 + <% else %>
  7 + <%= render :partial => 'profile_scrap', :locals => {:scrap => activity } %>
  8 + <% end %>
  9 + <% end %>
  10 +<% end %>
  11 +
  12 +<% if activities.current_page < activities.total_pages %>
  13 + <div id='profile_activities_page_<%= activities.current_page %>'>
  14 + <%= button_to_remote :add, _('View more'), :url => {:action => 'view_more_activities', :page => (activities.current_page + 1)}, :update => "profile_activities_page_#{activities.current_page}" %>
  15 + </div>
  16 +<% end %>
... ...
app/views/profile/_profile_activities_scraps.rhtml 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +NÂO DEVE APARECER!!
  2 +
... ...
app/views/profile/_profile_activity.rhtml
1   -<div id='profile-activity'>
2   - <h3><%= _("%s's activity") % @profile.name %></h3>
3   - <ul>
4   - <%= render :partial => 'profile_activities', :locals => {:activities => @activities} %>
5   - </ul>
6   -</div>
  1 +<li class='profile-activity-item <%= activity.verb %>' id='profile-activity-item-<%= activity.id %>'>
  2 + <%= render :partial => activity.verb, :locals => { :activity => activity, :tab_action => tab_action }%>
  3 + <hr />
  4 +</li>
... ...
app/views/profile/_profile_comment_form.rhtml 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +<div id='profile-wall-reply-<%= activity.id%>'>
  2 + <div id='profile-wall-reply-form-<%= activity.id%>'>
  3 + <p class='profile-wall-reply'>
  4 + <% update_area = tab_action == 'wall' ? 'profile_activities' : 'network-activities' %>
  5 + <% form_remote_tag :url => {:controller => 'profile', :action => 'leave_comment_on_activity', :tab_action => tab_action}, :html => { :class => 'profile-wall-reply-form', 'data-update' => update_area } do %>
  6 + <%= text_area :comment, :body, {:id => "reply_content_#{activity.id}",
  7 + :cols => 50,
  8 + :rows => 1,
  9 + :class => 'submit-with-keypress',
  10 + :title => _('Leave your comment'),
  11 + :onfocus => ('if(this.value==this.title){this.value="";this.style.color="#000"};this.style.backgroundImage="url(' + profile_icon(current_person, :icon, false) + ')"' if logged_in?),
  12 + :onblur => ('if(this.value==""){this.value=this.title;this.style.color="#ccc"};this.style.backgroundImage="none"' if logged_in?),
  13 + :value => _('Leave your comment'),
  14 + :style => 'color: #ccc' } %>
  15 + <%= hidden_field_tag :source_id, activity.id, :id => "activity_id_#{activity.id}" %>
  16 + <% end %>
  17 + </p>
  18 + </div>
  19 + <div id='profile-wall-message-response-<%=activity.id%>' class='profile-wall-message-response'></div>
  20 +</div>
... ...
app/views/profile/_profile_comments.rhtml 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +<hr />
  2 +
  3 +<% if activity.comments_count > 2 %>
  4 + <div class='view-all-comments icon-chat'>
  5 + <%= link_to(_("View all %s comments") % activity.comments_count, '#') %>
  6 + </div>
  7 +<% end %>
  8 +
  9 +<ul class="profile-wall-activities-comments" style="<%= 'display:none;' if (activity.comments_count > 2) %>" >
  10 + <%= render :partial => 'comment', :collection => activity.comments_as_thread %>
  11 +</ul>
  12 +
  13 +<%= render :partial => 'profile_comment_form', :locals => { :activity => activity, :tab_action => tab_action } %>
... ...
app/views/profile/_profile_network.rhtml
1 1 <h3><%= _("%s's network activity") % @profile.name %></h3>
2   -<ul>
  2 +<ul id='network-activities' class='profile-activities'>
3 3 <%= render :partial => 'profile_network_activities', :locals => {:network_activities => @network_activities} %>
4 4 </ul>
... ...
app/views/profile/_profile_network_activities.rhtml
1   - <% network_activities.each do |activity| %>
2   - <li class='profile-network-item <%= activity.verb %>' id='profile-network-item-<%= activity.id %>'>
3   - <div class='profile-network-image'>
4   - <%= link_to(profile_image(activity.user, :minor), activity.user.url) %>
5   - <% if logged_in? && current_person.follows?(activity.user) && current_person != activity.user %>
6   - <p class='profile-network-send-message'><%= link_to_function _('Scrap'), "hide_and_show(['#profile-network-message-response-#{activity.id}'],['#profile-network-message-#{activity.id}', '#profile-network-form-#{activity.id}']);$('content_#{activity.id}').value='';return false", :class => "profile-send-message", :title => _("Send a message to %s") % activity.user.name %></p>
7   - <% end %>
8   - </div>
9   - <div class='profile-network-description'>
10   - <p class='profile-network-time'><%= time_ago_as_sentence(activity.created_at) + ' ' + _('ago') %></p>
11   - <p class='profile-network-text'><%= link_to activity.user.name, activity.user.url %> <%= describe activity %></p>
12   - <%= button_to_remote(:delete, content_tag(:span, _('Remove')), :url =>{:action => 'remove_notification', :activity_id => activity.id}, :update => "profile-network-item-#{activity.id}") if can_edit_profile %>
13   - <p class='profile-network-where'><%= _('In community %s') % link_to(activity.target.name, activity.target.url) if !profile.is_a?(Community) && activity.target.is_a?(Community) %></p>
14   - </div>
15   - <div id='profile-network-message-<%= activity.id%>' style='display:none;'>
16   - <div id='profile-network-form-<%= activity.id%>' style='display:none;'>
17   - <p class='profile-network-message'>
18   - <% form_remote_tag :url => {:controller => 'profile', :action => 'leave_scrap', :not_load_scraps => true}, :update => "profile-network-message-response-#{activity.id}", :success =>"hide_and_show(['#profile-network-form-#{activity.id}'],['#profile-network-message-response-#{activity.id}'])" do %>
19   - <%= limited_text_area :scrap, :content, 420, "content_#{activity.id}", :cols => 50, :rows => 2 %>
20   - <%= hidden_field_tag 'receiver_id', activity.user.id %>
21   - <%= submit_button :add, _('Leave a message') %>
22   - <%= button_to_function :cancel, _('Cancel'), "hide_and_show(['#profile-network-message-#{activity.id}'],[]);return false" %>
23   - <% end %>
24   - </p>
25   - </div>
26   - <div id='profile-network-message-response-<%=activity.id%>' class='profile-network-message-response'></div>
27   - </div>
28   - <hr />
29   - </li>
30   - <% end %>
  1 +<% network_activities.each do |activity| %>
  2 + <%= render :partial => 'profile_activity', :locals => { :activity => activity, :tab_action => 'network' } if activity.visible? %>
  3 +<% end %>
31 4 <% if network_activities.current_page < network_activities.total_pages %>
32 5 <div id='profile_network_activities_page_<%= network_activities.current_page %>'>
33 6 <%= button_to_remote :add, _('View more'), :url => {:action => 'view_more_network_activities', :page => (network_activities.current_page + 1)}, :update => "profile_network_activities_page_#{network_activities.current_page}" %>
... ...
app/views/profile/_profile_scrap.rhtml
1   -<li class='profile-wall-item' id='profile-wall-item-<%= scrap.id %>'>
2   - <div class='profile-wall-image'>
  1 +<li class='profile-activity-item' id='profile-activity-item-<%= scrap.id %>'>
  2 + <div class='profile-activity-image'>
3 3 <%= link_to(profile_image(scrap.sender, :minor), scrap.sender.url) %>
4   - <% if logged_in? && current_person.follows?(scrap.sender) && current_person != scrap.sender %>
5   - <p class='profile-wall-send-message'><%= link_to_function _('Scrap'), "hide_and_show(['#profile-wall-message-response-#{scrap.id}'],['#profile-wall-message-#{scrap.id}', '#profile-wall-form-#{scrap.id}']);$('content_#{scrap.id}').value='';return false", :class => "profile-send-message", :title => _("Send a message to %s") % scrap.sender.name %></p>
6   - <% end %>
7 4 </div>
8   - <% comment_balloon :class => 'profile-wall-description' do %>
9   - <p class='profile-wall-sender'><%= link_to scrap.sender.name, scrap.sender.url %></p>
10   - <p class='profile-wall-time'><%= time_ago_as_sentence(scrap.created_at) + ' ' + _('ago') %></p>
11   - <p class='profile-wall-text'><%= txt2html scrap.content %></p>
12   - <%= button_to_remote(:delete, content_tag(:span, _('Remove')), :url =>{:action => 'remove_scrap', :scrap_id => scrap.id}, :update => "profile-wall-item-#{scrap.id}") if logged_in? && user.can_control_scrap?(scrap) %>
13   - <% if logged_in? && current_person.follows?(scrap.sender) && scrap.root.nil? %>
14   - <p class='profile-wall-send-reply'><%= link_to_function _('Reply'), "hide_and_show(['#profile-wall-reply-response-#{scrap.id}'],['#profile-wall-reply-#{scrap.id}', '#profile-wall-reply-form-#{scrap.id}']);$('reply_content_#{scrap.id}').value='';$('scrap_id_#{scrap.id}').value='#{scrap.id}';return false", :class => "profile-send-reply icon-reply" %></p>
  5 + <div class='profile-activity-description'>
  6 + <p class='profile-activity-sender'><%= link_to scrap.sender.name, scrap.sender.url %></p>
  7 + <p class='profile-activity-text'><%= txt2html scrap.content %></p>
  8 + <p class='profile-activity-time'><%= time_ago_as_sentence(scrap.created_at) %></p>
  9 + <div class='profile-wall-actions'>
  10 + <% if logged_in? && current_person.follows?(scrap.sender) %>
  11 + <span class='profile-activity-send-reply'>
  12 + <%= link_to_function s_('profile|Comment'), "hide_and_show(['#profile-wall-message-response-#{scrap.id}'],['#profile-wall-reply-#{scrap.id}', '#profile-wall-reply-form-#{scrap.id}']);$('reply_content_#{scrap.id}').value='';$('reply_content_#{scrap.id}').focus();return false", :class => "profile-send-reply" %>
  13 + </span>
15 14 <% end %>
  15 + <%= link_to_remote(content_tag(:span, _('Remove')), :confirm => _('Are you sure?'), :url =>{:action => 'remove_scrap', :scrap_id => scrap.id}, :update => "profile-activity-item-#{scrap.id}") if logged_in? && user.can_control_scrap?(scrap) %>
  16 + </div>
  17 + </div>
  18 +
  19 + <% if scrap.replies.count > 2 %>
  20 + <div class='view-all-comments icon-chat'>
  21 + <%= link_to(_("View all %s comments") % scrap.replies.count, '#') %>
  22 + </div>
16 23 <% end %>
17   - <ul class="profile-wall-scrap-replies">
  24 +
  25 + <ul class="profile-wall-activities-comments scrap-replies" style="width: auto; <%= 'display:none;' if (scrap.replies.count > 2) %>" >
18 26 <% scrap.replies.map do |reply| %>
19 27 <%= render :partial => 'profile_scrap', :locals => {:scrap => reply} %>
20 28 <% end %>
21 29 </ul>
22   - <div id='profile-wall-message-<%= scrap.id%>' style='display:none;'>
23   - <div id='profile-wall-form-<%= scrap.id%>' style='display:none;'>
24   - <p class='profile-wall-message'>
25   - <% form_remote_tag :url => {:controller => 'profile', :action => 'leave_scrap', :not_load_scraps => true}, :update => "profile-wall-message-response-#{scrap.id}", :success =>"hide_and_show(['#profile-wall-form-#{scrap.id}'],['#profile-wall-message-response-#{scrap.id}'])" do %>
26   - <%= limited_text_area :scrap, :content, 420, "content_#{scrap.id}", :cols => 50, :rows => 2 %>
27   - <%= hidden_field_tag 'receiver_id', scrap.sender.id %>
28   - <%= submit_button :add, _('Leave a scrap') %>
29   - <%= button_to_function :cancel, _('Cancel'), "hide_and_show(['#profile-wall-message-#{scrap.id}'],[]);return false" %>
30   - <% end %>
31   - </p>
32   - </div>
33   - <div id='profile-wall-message-response-<%=scrap.id%>' class='profile-wall-message-response'></div>
34   - </div>
35   - <div id='profile-wall-reply-<%= scrap.id%>' style='display:none;'>
36   - <div id='profile-wall-reply-form-<%= scrap.id%>' style='display:none;'>
37   - <p class='profile-wall-reply'>
38   - <% form_remote_tag :url => {:controller => 'profile', :action => 'leave_scrap'}, :update => "profile_scraps", :success =>"hide_and_show(['#profile-wall-reply-form-#{scrap.id}'],['#profile-wall-reply-response-#{scrap.id}'])" do %>
39   - <%= limited_text_area :scrap, :content, 420, "reply_content_#{scrap.id}", :cols => 50, :rows => 2 %>
40   - <%= hidden_field :scrap, :scrap_id, :id => "scrap_id_#{scrap.id}" %>
41   - <%= hidden_field_tag 'receiver_id', scrap.sender.id %>
42   - <%= submit_button :add, _('Leave a scrap') %>
43   - <%= button_to_function :cancel, _('Cancel'), "hide_and_show(['#profile-wall-reply-#{scrap.id}'],[]);return false" %>
44   - <% end %>
45   - </p>
46   - </div>
47   - <div id='profile-wall-message-response-<%=scrap.id%>' class='profile-wall-message-response'></div>
48   - </div>
  30 + <%= render :partial => 'profile_scrap_reply_form', :locals => { :scrap => scrap } %>
49 31 <hr />
50 32 </li>
... ...
app/views/profile/_profile_scrap_reply_form.rhtml 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +<div id='profile-wall-reply-<%= scrap.id%>' style='display:none'>
  2 + <div id='profile-wall-reply-form-<%= scrap.id%>' style='display:none'>
  3 + <p class='profile-wall-reply'>
  4 + <% form_remote_tag :url => {:controller => 'profile', :action => 'leave_scrap'}, :update => "profile_activities", :html => { :class => 'profile-wall-reply-form'} do %>
  5 + <%= text_area :scrap, :content, { :id => "reply_content_#{scrap.id}",
  6 + :cols => 50,
  7 + :rows => 1,
  8 + :class => 'submit-with-keypress',
  9 + :title => _('Leave your comment'),
  10 + :onfocus => ('if(this.value==this.title){this.value="";this.style.color="#000"};this.style.backgroundImage="url(' + profile_icon(current_person, :icon, false) + ')"' if logged_in?),
  11 + :onblur => ('if(this.value==""){this.value=this.title;this.style.color="#ccc"};this.style.backgroundImage="none"' if logged_in?),
  12 + :value => _('Leave your comment')
  13 + } %>
  14 + <%= hidden_field_tag 'scrap[scrap_id]', scrap.id %>
  15 + <%= hidden_field_tag 'receiver_id', scrap.sender.id %>
  16 + <% end %>
  17 + </p>
  18 + </div>
  19 + <div id='profile-wall-message-response-<%=scrap.id%>' class='profile-wall-message-response'></div>
  20 +</div>
... ...
app/views/profile/_profile_scraps.rhtml
1   -<% scraps.map do |scrap| %>
2   - <%= render :partial => 'profile_scrap', :locals => {:scrap => scrap} %>
3   -<% end %>
4   -<% if scraps.current_page < scraps.total_pages %>
5   - <div id='profile_scraps_page_<%= scraps.current_page %>'>
6   - <%= button_to_remote :add, _('View more'), :url => {:action => 'view_more_scraps', :page => (scraps.current_page + 1)}, :update => "profile_scraps_page_#{scraps.current_page}" %>
7   - </div>
8   -<% end %>
  1 +NÃO DEVE APARECER
... ...
app/views/profile/_profile_wall.rhtml
1 1 <h3><%= _("%s's wall") % @profile.name %></h3>
2 2 <div id='leave_scrap'>
3 3 <%= flash[:error] %>
4   - <% form_remote_tag :url => {:controller => 'profile', :action => 'leave_scrap', :tab_action => 'wall' }, :update => 'profile_scraps', :success => "$('leave_scrap_content').value=''" do %>
  4 + <% form_remote_tag :url => {:controller => 'profile', :action => 'leave_scrap', :tab_action => 'wall' }, :update => 'profile_activities', :success => "$('leave_scrap_content').value=''" do %>
5 5 <%= limited_text_area :scrap, :content, 420, 'leave_scrap_content', :cols => 50, :rows => 2 %>
6   - <%= submit_button :scrap, _('Leave a scrap') %>
  6 + <%= submit_button :new, _('Share') %>
7 7 <% end %>
8 8 </div>
9 9 <div id='leave_scrap_response'></div>
10   -<ul id='profile_scraps'>
11   - <%= render :partial => 'profile_scraps', :locals => {:scraps => @wall_items} %>
  10 +<ul id='profile_activities' class='profile-activities'>
  11 + <%= render :partial => 'profile_activities_list', :locals => {:activities => @activities} %>
12 12 </ul>
... ...