diff --git a/Gemfile b/Gemfile index a132f5be..e5ad6a76 100644 --- a/Gemfile +++ b/Gemfile @@ -11,7 +11,7 @@ gem 'devise', '1.3.4' gem 'devise_bushido_authenticatable', '1.0.0.alpha10', :require => 'devise_cas_authenticatable' gem 'mongoid', '~> 2.0.2' -gem 'bson_ext', '~> 1.3.0' +gem 'bson_ext', '~> 1.4.0' gem 'locomotive_mongoid_acts_as_tree', '0.1.5.7', :require => 'mongoid_acts_as_tree' gem 'will_paginate', '~> 3.0.0' @@ -32,7 +32,7 @@ gem 'fog', '0.8.2' gem 'mimetype-fu' gem 'actionmailer-with-request', :require => 'actionmailer_with_request' gem 'heroku', '1.19.1' -gem 'httparty', '>= 0.6.1' +gem 'httparty', '0.7.8' gem 'RedCloth', '4.2.8' gem 'delayed_job', '2.1.4' gem 'delayed_job_mongoid', '1.0.2' diff --git a/Gemfile.lock b/Gemfile.lock index 41135040..8a13e225 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,7 +7,7 @@ GEM Platform (0.4.0) RedCloth (4.2.8) SystemTimer (1.2.3) - ZenTest (4.6.1) + ZenTest (4.6.2) abstract (1.0.0) actionmailer (3.0.10) actionpack (= 3.0.10) @@ -42,8 +42,8 @@ GEM autotest (4.4.6) ZenTest (>= 4.4.1) bcrypt-ruby (2.1.4) - bson (1.3.1) - bson_ext (1.3.1) + bson (1.4.0) + bson_ext (1.4.0) builder (2.1.2) bushido (0.0.35) highline (>= 1.6.1) @@ -53,7 +53,7 @@ GEM bushido_stub (0.0.3) activesupport (>= 3.0.7) cancan (1.6.5) - capybara (1.0.1) + capybara (1.1.1) mime-types (>= 1.16) nokogiri (>= 1.3.3) rack (>= 1.0.0) @@ -62,20 +62,20 @@ GEM xpath (~> 0.1.4) carrierwave (0.5.6) activesupport (~> 3.0) - cells (3.6.5) + cells (3.6.6) actionpack (~> 3.0) railties (~> 3.0) - childprocess (0.2.1) + childprocess (0.2.2) ffi (~> 1.0.6) columnize (0.3.4) configuration (1.3.1) crack (0.1.8) - cucumber (1.0.2) + cucumber (1.0.6) builder (>= 2.1.2) diff-lcs (>= 1.1.2) - gherkin (~> 2.4.5) + gherkin (~> 2.4.18) json (>= 1.4.6) - term-ansicolor (>= 1.0.5) + term-ansicolor (>= 1.0.6) cucumber-rails (1.0.2) capybara (>= 1.0.0) cucumber (~> 1.0.0) @@ -99,15 +99,16 @@ GEM devise devise (>= 1.0.6) rubycas-client (>= 2.2.1) - diff-lcs (1.1.2) - dragonfly (0.9.5) + diff-lcs (1.1.3) + dragonfly (0.9.8) rack erubis (2.6.6) abstract (>= 1.0.0) - excon (0.6.5) - factory_girl (2.0.4) - factory_girl_rails (1.1.0) - factory_girl (~> 2.0.0) + excon (0.6.6) + factory_girl (2.1.2) + activesupport + factory_girl_rails (1.2.0) + factory_girl (~> 2.1.0) railties (>= 3.0.0) ffi (1.0.9) fog (0.8.2) @@ -119,12 +120,12 @@ GEM net-ssh (>= 2.1.3) nokogiri (>= 1.4.4) ruby-hmac - formatador (0.2.0) + formatador (0.2.1) formtastic (1.2.4) actionpack (>= 2.3.7) activesupport (>= 2.3.7) i18n (~> 0.4) - gherkin (2.4.6) + gherkin (2.4.21) json (>= 1.4.6) growl-glue (1.0.7) haml (3.1.2) @@ -132,7 +133,7 @@ GEM heroku (1.19.1) activesupport (>= 2.1.0) launchy (~> 0.3.2) - rest-client (>= 1.4.0, < 1.7.0) + rest-client (< 1.7.0, >= 1.4.0) highline (1.6.2) httparty (0.7.8) crack (= 0.1.8) @@ -142,8 +143,8 @@ GEM responders (~> 0.6.0) jammit (0.6.3) yui-compressor (>= 0.9.3) - json (1.5.3) - json_pure (1.5.3) + json (1.6.1) + json_pure (1.6.1) kgio (2.6.0) launchy (0.3.7) configuration (>= 0.0.5) @@ -167,23 +168,23 @@ GEM mimemagic (0.1.8) mimetype-fu (0.1.2) mocha (0.9.12) - mongo (1.3.1) - bson (>= 1.3.1) + mongo (1.4.0) + bson (= 1.4.0) mongoid (2.0.2) activemodel (~> 3.0) mongo (~> 1.3) tzinfo (~> 0.3.22) - net-ssh (2.1.4) + net-ssh (2.2.1) nokogiri (1.5.0) open4 (1.1.0) orm_adapter (0.0.5) - pickle (0.4.8) + pickle (0.4.10) cucumber (>= 0.8) rake polyglot (0.3.2) proxies (0.2.1) - rack (1.2.3) - rack-cache (1.0.2) + rack (1.2.4) + rack-cache (1.1) rack (>= 0.4) rack-mount (0.6.14) rack (>= 1.0.0) @@ -207,7 +208,7 @@ GEM rake (0.9.2) rdoc (3.9.4) responders (0.6.4) - rest-client (1.6.3) + rest-client (1.6.7) mime-types (>= 1.16) rmagick (2.12.2) rspec (2.6.0) @@ -249,9 +250,9 @@ GEM s3 (0.3.8) proxies (~> 0.2.0) sanitize (2.0.3) - nokogiri (>= 1.4.4, < 1.6) + nokogiri (< 1.6, >= 1.4.4) sass (3.1.2) - selenium-webdriver (2.4.0) + selenium-webdriver (2.7.0) childprocess (>= 0.2.1) ffi (>= 1.0.7) json_pure @@ -263,13 +264,13 @@ GEM polyglot polyglot (>= 0.3.1) tzinfo (0.3.29) - unicorn (4.0.1) + unicorn (4.1.1) kgio (~> 2.4) rack raindrops (~> 0.6) warden (1.0.5) rack (>= 1.0) - will_paginate (3.0.0) + will_paginate (3.0.2) xpath (0.1.4) nokogiri (~> 1.3) yui-compressor (0.9.6) @@ -284,7 +285,7 @@ DEPENDENCIES ZenTest actionmailer-with-request autotest - bson_ext (~> 1.3.0) + bson_ext (~> 1.4.0) bushido (= 0.0.35) bushido_stub (= 0.0.3) cancan @@ -307,7 +308,7 @@ DEPENDENCIES haml (= 3.1.2) heroku (= 1.19.1) highline - httparty (>= 0.6.1) + httparty (= 0.7.8) inherited_resources (~> 1.1.2) launchy linecache (= 0.43) diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb index c7efd941..048234c7 100644 --- a/app/controllers/admin/base_controller.rb +++ b/app/controllers/admin/base_controller.rb @@ -15,6 +15,8 @@ module Admin before_filter :set_locale + before_filter :set_current_thread_variables + helper_method :sections, :current_site_url, :site_url, :page_url, :current_ability # https://rails.lighthouseapp.com/projects/8994/tickets/1905-apphelpers-within-plugin-not-being-mixed-in @@ -42,6 +44,11 @@ module Admin protected + def set_current_thread_variables + Thread.current[:admin] = current_admin + Thread.current[:site] = current_site + end + def current_ability @current_ability ||= Ability.new(current_admin, current_site) end diff --git a/app/models/ability.rb b/app/models/ability.rb index e4838493..745bb03d 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -65,7 +65,9 @@ class Ability can :manage, Membership - cannot :change_role, Membership do |membership| + cannot :grant_admin, Membership + + cannot [:update, :destroy], Membership do |membership| @membership.account_id == membership.account_id || # can not edit myself membership.admin? # can not modify an administrator end @@ -74,7 +76,7 @@ class Ability def setup_admin_permissions! can :manage, :all - cannot :change_role, Membership do |membership| + cannot [:update, :destroy], Membership do |membership| @membership.account_id == membership.account_id # can not edit myself end end diff --git a/app/models/account.rb b/app/models/account.rb index 5e0b6600..b50e9aa3 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -47,12 +47,12 @@ class Account def remove_memberships! self.sites.each do |site| - site.memberships.delete_if { |m| m.account_id == self._id } + membership = site.memberships.where(:account_id => self._id).first - if site.admin_memberships.empty? + if site.admin_memberships.size == 1 && membership.admin? raise I18n.t('errors.messages.needs_admin_account') else - site.save + membership.destroy end end end diff --git a/app/models/content_instance.rb b/app/models/content_instance.rb index 0322ff86..527c76ba 100644 --- a/app/models/content_instance.rb +++ b/app/models/content_instance.rb @@ -50,6 +50,14 @@ class ContentInstance def visible? self._visible || self._visible.nil? end + + def next + content_type.contents.where(:_position_in_list => _position_in_list + 1).first() + end + + def previous + content_type.contents.where(:_position_in_list => _position_in_list - 1).first() + end def errors_to_hash Hash.new.replace(self.errors) diff --git a/app/models/membership.rb b/app/models/membership.rb index 834c0f53..208e7feb 100644 --- a/app/models/membership.rb +++ b/app/models/membership.rb @@ -11,6 +11,7 @@ class Membership ## validations ## validates_presence_of :account + validate :can_change_role, :if => :role_changed? ## callbacks ## before_save :define_role @@ -55,4 +56,18 @@ class Membership self.role = Ability::ROLES.include?(role.downcase) ? role.downcase : Ability::ROLES.first end + # Users should not be able to set the role of another user to be higher than + # their own. A designer for example should not be able to set another user to + # be an administrator + def can_change_role + current_site = Thread.current[:site] + current_membership = current_site.memberships.where(:account_id => Thread.current[:admin].id).first if current_site.present? + + if current_membership.present? + # The role cannot be set higher than the current one (we use the index in + # the roles array to check role presidence) + errors.add(:role, :invalid) if Ability::ROLES.index(role) < Ability::ROLES.index(current_membership.role) + end + end + end diff --git a/app/views/admin/current_site/_form.html.haml b/app/views/admin/current_site/_form.html.haml index a864df52..cb0426fb 100644 --- a/app/views/admin/current_site/_form.html.haml +++ b/app/views/admin/current_site/_form.html.haml @@ -54,11 +54,14 @@ %em.email= account.email - - if can?(:change_role, membership) + - if can?(:update, membership) .role %em.editable= t("admin.memberships.roles.#{membership.role}") - = fm.select :role, Ability::ROLES.collect { |r| [t("admin.memberships.roles.#{r}"), r] }, :include_blank => false + - if can?(:grant_admin, membership) + = fm.select :role, Ability::ROLES.map { |r| [t("admin.memberships.roles.#{r}"), r] }, :include_blank => false + - else + = fm.select :role, (Ability::ROLES - ['admin']).map { |r| [t("admin.memberships.roles.#{r}"), r] }, :include_blank => false %span.actions = link_to image_tag('admin/form/icons/trash.png'), admin_membership_url(membership), :class => 'remove first', :confirm =>t('admin.messages.confirm'), :method => :delete @@ -73,4 +76,4 @@ = f.custom_input :robots_txt, :css => 'code full', :with_label => false do = f.label :robots_txt %code{ :class => 'html' } - = f.text_area :robots_txt, :class => 'small' \ No newline at end of file + = f.text_area :robots_txt, :class => 'small' diff --git a/config/locales/default.ru.yml b/config/locales/default.ru.yml index c8d93dd5..58fc5f8b 100644 --- a/config/locales/default.ru.yml +++ b/config/locales/default.ru.yml @@ -29,7 +29,7 @@ ru: title: "Страница не найдена" body: "Содержимое страницы 404" other: - body: "{% расширяет 'parent' %}" + body: "{% extends 'parent' %}" mongoid: attributes: diff --git a/features/admin/authorization/current_site.feature b/features/admin/authorization/current_site.feature index 591b6f6b..f16312ab 100644 --- a/features/admin/authorization/current_site.feature +++ b/features/admin/authorization/current_site.feature @@ -35,7 +35,7 @@ Background: And I should see "Access points" And I should not see the role dropdown on myself And I should not see the role dropdown on the "admin" - And I should see the role dropdown on the "author" + And I should see the role dropdown on the "author" without the "Administrator" option And I should not see delete on the "admin" And I should not see delete on myself And I should see delete on the "author" diff --git a/features/step_definitions/current_site_steps.rb b/features/step_definitions/current_site_steps.rb index bfb76706..e2dc0545 100644 --- a/features/step_definitions/current_site_steps.rb +++ b/features/step_definitions/current_site_steps.rb @@ -1,5 +1,9 @@ Then /^I should see the role dropdown on the "([^"]*)"$/ do |user| - find(:css, "li.membership[data-role=#{user}] select").text.should == 'AdministratorDesignerAuthor' + find(:css, "li.membership[data-role=#{user}] select").should be_present +end + +Then /^I should see the role dropdown on the "([^"]*)" without the "([^"]*)" option$/ do |user, option| + find(:css, "li.membership[data-role=#{user}] select").text.should_not include option end Then /^I should see the role dropdown on myself$/ do @@ -33,3 +37,7 @@ end Then /^I should not see any delete buttons$/ do page.has_css?('li.membership .actions a.remove').should be_false end + +When /^I select the "([^"]*)" role for the "author" user/ do |role| + Given %{I select "#{role}" from "site[memberships_attributes][2][role]"} +end diff --git a/features/step_definitions/site_steps.rb b/features/step_definitions/site_steps.rb index 748d929c..075a451d 100644 --- a/features/step_definitions/site_steps.rb +++ b/features/step_definitions/site_steps.rb @@ -5,6 +5,7 @@ # - I have the site: "some site" set up with name: "Something", domain: "test2" # Given /^I have the site: "([^"]*)" set up(?: with #{capture_fields})?$/ do |site_factory, fields| + Thread.current[:site] = nil @site = FactoryGirl.create(site_factory, parse_fields(fields)) @site.should_not be_nil diff --git a/features/support/selectors.rb b/features/support/selectors.rb index 32339920..6b5a6bc2 100644 --- a/features/support/selectors.rb +++ b/features/support/selectors.rb @@ -22,6 +22,9 @@ module HtmlSelectorsHelpers when "the has many selector" ".has-many-selector ul li.template" + + when 'the role' + '.role' # Add more mappings here. # Here is an example that pulls values out of the Regexp: diff --git a/lib/locomotive/import/content_types.rb b/lib/locomotive/import/content_types.rb index 4e5feeb3..f2e17000 100644 --- a/lib/locomotive/import/content_types.rb +++ b/lib/locomotive/import/content_types.rb @@ -210,7 +210,7 @@ module Locomotive end def set_highlighted_field_name(content_type) - field = content_type.content_custom_fields.detect { |f| f._alias == content_type.highlighted_field_name } + field = content_type.content_custom_fields.detect { |f| f._alias == content_type.highlighted_field_name.to_s } content_type.highlighted_field_name = field._name if field end diff --git a/lib/locomotive/liquid/drops/content.rb b/lib/locomotive/liquid/drops/content.rb index 1aefc759..2e3fcb37 100644 --- a/lib/locomotive/liquid/drops/content.rb +++ b/lib/locomotive/liquid/drops/content.rb @@ -8,6 +8,32 @@ module Locomotive def _id self._source._id.to_s end + + # Returns the next content for the parent content type. + # If no content is found, nil is returned. + # + # Usage: + # + # {% if article.next %} + # Read next article + # {% endif %} + # + def next + self._source.next.to_liquid + end + + # Returns the previous content for the parent content type. + # If no content is found, nil is returned. + # + # Usage: + # + # {% if article.previous %} + # Read previous article + # {% endif %} + # + def previous + self._source.previous.to_liquid + end def before_method(meth) return '' if self._source.nil? diff --git a/lib/locomotive/liquid/tags/with_scope.rb b/lib/locomotive/liquid/tags/with_scope.rb index ea23b339..7f79675d 100644 --- a/lib/locomotive/liquid/tags/with_scope.rb +++ b/lib/locomotive/liquid/tags/with_scope.rb @@ -23,9 +23,9 @@ module Locomotive def decode(attributes, context) attributes.each_pair do |key, value| attributes[key] = (case value - when /true|false/ then value == 'true' - when /[0-9]+/ then value.to_i - when /'(\S+)'/ then $1 + when /^true|false$/i then value == 'true' + when /^[0-9]+$/ then value.to_i + when /^["|'](.+)["|']$/ then $1.gsub(/^["|']/, '').gsub(/["|']$/, '') else context[value] end) diff --git a/locomotive_cms.gemspec b/locomotive_cms.gemspec index d4a3559b..11f824a6 100644 --- a/locomotive_cms.gemspec +++ b/locomotive_cms.gemspec @@ -17,12 +17,12 @@ Gem::Specification.new do |s| s.required_rubygems_version = '>= 1.3.6' s.rubyforge_project = 'nowarning' - s.add_dependency 'rails', '>= 3.0.10' + s.add_dependency 'rails', '~> 3.0.10' s.add_dependency 'warden' s.add_dependency 'devise', '1.3.4' s.add_dependency 'devise_bushido_authenticatable', '1.0.0.alpha10' - s.add_dependency 'mongoid', '2.0.2' - s.add_dependency 'bson_ext', '~> 1.3.0' + s.add_dependency 'mongoid', '~> 2.0.2' + s.add_dependency 'bson_ext', '~> 1.4.0' s.add_dependency 'locomotive_mongoid_acts_as_tree', '0.1.5.7' s.add_dependency 'will_paginate', '~> 3.0.0' @@ -49,7 +49,7 @@ Gem::Specification.new do |s| s.add_dependency 'fog', '0.8.2' s.add_dependency 'mimetype-fu' s.add_dependency 'actionmailer-with-request' - s.add_dependency 'httparty', '>= 0.6.1' + s.add_dependency 'httparty', '0.7.8' s.add_dependency 'RedCloth', '4.2.8' s.add_dependency 'delayed_job', '2.1.4' s.add_dependency 'delayed_job_mongoid', '1.0.2' diff --git a/spec/fixtures/themes/default.zip b/spec/fixtures/themes/default.zip index 8489be3d..47258673 100644 Binary files a/spec/fixtures/themes/default.zip and b/spec/fixtures/themes/default.zip differ diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 1b43d581..237426ae 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -125,6 +125,15 @@ describe Ability do end end + context 'granting admin' do + it 'should allow only admins to grant admin role' do + should allow_permission_from :grant_admin, @admin + should_not allow_permission_from :grant_admin, @designer + should_not allow_permission_from :grant_admin, @author + end + + end + end end diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index b57d614a..c8f429a8 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -47,13 +47,14 @@ describe Account do end it 'should also delete memberships' do - Site.any_instance.stubs(:admin_memberships).returns(['junk']) + Site.any_instance.stubs(:admin_memberships).returns(['junk', 'dirt']) + @site_1.memberships.first.expects(:destroy) + @site_2.memberships.first.expects(:destroy) @account.destroy - @site_1.memberships.should be_empty - @site_2.memberships.should be_empty end it 'should raise an exception if account is the only remaining admin' do + @site_1.memberships.first.stubs(:admin?).returns(true) @site_1.stubs(:admin_memberships).returns(['junk']) lambda { @account.destroy diff --git a/spec/models/content_instance_spec.rb b/spec/models/content_instance_spec.rb index f34e2957..9669ab61 100644 --- a/spec/models/content_instance_spec.rb +++ b/spec/models/content_instance_spec.rb @@ -42,6 +42,34 @@ describe ContentInstance do end + describe "#navigation" do + before(:each) do + %w(first second third).each_with_index do |item,index| + content = build_content({:title => item.to_s}) + content._position_in_list = index + instance_variable_set "@#{item}", content + end + end + + it 'should find previous item when available' do + @second.previous.custom_field_1.should == "first" + @second.previous._position_in_list.should == 0 + end + + it 'should find next item when available' do + @second.next.custom_field_1.should == "third" + @second.next._position_in_list.should == 2 + end + + it 'should return nil when fetching previous item on first in list' do + @first.previous.should == nil + end + + it 'should return nil when fetching next item on last in list' do + @third.next.should == nil + end + end + describe '#permalink' do before(:each) do