From fd07367ebc2c40dbb1964a82be4d2a581d42d991 Mon Sep 17 00:00:00 2001 From: Sven Fuchs Date: Fri, 11 Jul 2008 00:08:57 +0200 Subject: [PATCH] adding a plugin api --- config/environment.rb | 2 +- db/schema.rb | 259 ++++++++ .../admin/plugins_controller_spec.rb | 85 +++ spec/models/content_spec.rb | 576 +++++++++--------- .../spec_helpers/spec_resource_path_helper.rb | 2 +- spec/views/admin/plugin_views_spec.rb | 102 ++++ stories/steps/article.rb | 53 +- stories/steps/site.rb | 3 + stories/stories/admin/blog_article.txt | 35 ++ stories/stories/admin/plugin.txt | 18 + stories/stories/blog_article.txt | 2 +- .../controllers/admin/plugins_controller.rb | 27 + .../views/admin/plugins/_action_nav.html.erb | 8 + .../app/views/admin/plugins/_form.html.erb | 12 + .../app/views/admin/plugins/_plugin.html.erb | 17 + .../app/views/admin/plugins/index.html.erb | 23 + .../app/views/admin/plugins/show.html.erb | 35 ++ .../views/widgets/admin/_menu_global.html.erb | 2 +- vendor/engines/adva_cms/routes.rb | 6 +- vendor/engines/adva_forum/init.rb | 3 +- .../20080710000000_create_plugin_configs.rb | 13 + vendor/plugins/engines_config/init.rb | 1 + .../lib/engines/plugin/configurable.rb | 70 +++ vendor/spec/plugin_test/about.yml | 7 + vendor/spec/plugin_test/init.rb | 5 + 25 files changed, 1055 insertions(+), 311 deletions(-) create mode 100644 spec/controllers/admin/plugins_controller_spec.rb create mode 100644 spec/views/admin/plugin_views_spec.rb create mode 100644 stories/stories/admin/plugin.txt create mode 100644 vendor/engines/adva_cms/app/controllers/admin/plugins_controller.rb create mode 100644 vendor/engines/adva_cms/app/views/admin/plugins/_action_nav.html.erb create mode 100644 vendor/engines/adva_cms/app/views/admin/plugins/_form.html.erb create mode 100644 vendor/engines/adva_cms/app/views/admin/plugins/_plugin.html.erb create mode 100644 vendor/engines/adva_cms/app/views/admin/plugins/index.html.erb create mode 100644 vendor/engines/adva_cms/app/views/admin/plugins/show.html.erb create mode 100644 vendor/plugins/engines_config/db/migrate/20080710000000_create_plugin_configs.rb create mode 100644 vendor/plugins/engines_config/init.rb create mode 100644 vendor/plugins/engines_config/lib/engines/plugin/configurable.rb create mode 100644 vendor/spec/plugin_test/about.yml create mode 100644 vendor/spec/plugin_test/init.rb diff --git a/config/environment.rb b/config/environment.rb index d91ef8885..1d6d0ea49 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -38,7 +38,7 @@ # in vendor/plugins are loaded in alphabetical order. # :all can be used as a placeholder for all plugins not explicitly named # config.plugins = [ :exception_notification, :ssl_requirement, :all ] - config.plugins = [ :theme_support, :better_nested_set, :safemode, :all ] + config.plugins = [ :engines_config, :theme_support, :better_nested_set, :safemode, :all ] config.plugin_paths << "#{RAILS_ROOT}/vendor/engines" config.plugin_paths << "#{RAILS_ROOT}/vendor/spec" if Rails.env == 'test' diff --git a/db/schema.rb b/db/schema.rb index b81ae5a56..6d7747e2a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,4 +11,263 @@ ActiveRecord::Schema.define(:version => 0) do + create_table "activities", :force => true do |t| + t.integer "site_id" + t.integer "section_id" + t.integer "author_id" + t.string "author_type" + t.string "author_name", :limit => 40 + t.string "author_email", :limit => 40 + t.string "author_homepage" + t.string "actions" + t.integer "object_id" + t.string "object_type", :limit => 15 + t.text "object_attributes" + t.datetime "created_at", :null => false + end + + create_table "anonymouses", :force => true do |t| + t.string "name", :limit => 40 + t.string "email", :limit => 100 + t.string "homepage" + t.string "ip" + t.string "agent" + t.string "referer" + t.string "token_key", :limit => 40 + t.datetime "token_expiration" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "asset_assignments", :force => true do |t| + t.integer "content_id" + t.integer "asset_id" + t.string "label" + t.datetime "created_at" + t.boolean "active" + end + + create_table "assets", :force => true do |t| + t.integer "site_id" + t.integer "parent_id" + t.integer "user_id" + t.string "content_type" + t.string "filename" + t.integer "size" + t.string "thumbnail" + t.integer "width" + t.integer "height" + t.string "title" + t.integer "thumbnails_count", :default => 0 + t.datetime "created_at" + end + + create_table "cached_page_references", :force => true do |t| + t.integer "cached_page_id" + t.integer "object_id" + t.string "object_type" + t.string "method" + end + + create_table "cached_pages", :force => true do |t| + t.integer "site_id" + t.integer "section_id" + t.string "url" + t.datetime "updated_at" + t.datetime "cleared_at" + end + + create_table "categories", :force => true do |t| + t.integer "section_id" + t.integer "parent_id" + t.integer "lft", :default => 0, :null => false + t.integer "rgt", :default => 0, :null => false + t.string "title" + t.string "path" + t.string "permalink" + end + + create_table "category_assignments", :force => true do |t| + t.integer "content_id" + t.integer "category_id" + end + + create_table "comments", :force => true do |t| + t.integer "site_id" + t.integer "section_id" + t.integer "commentable_id" + t.string "commentable_type" + t.integer "author_id" + t.string "author_type" + t.string "author_name", :limit => 40 + t.string "author_email", :limit => 40 + t.string "author_homepage" + t.text "body" + t.text "body_html" + t.integer "approved", :default => 0, :null => false + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "content_versions", :force => true do |t| + t.integer "content_id" + t.integer "version" + t.integer "site_id" + t.integer "section_id" + t.integer "position" + t.string "title" + t.string "permalink" + t.text "excerpt" + t.text "excerpt_html" + t.text "body" + t.text "body_html" + t.integer "author_id" + t.string "author_type" + t.string "author_name", :limit => 40 + t.string "author_email", :limit => 40 + t.string "author_homepage" + t.string "filter" + t.integer "comment_age", :default => 0 + t.datetime "published_at" + t.datetime "created_at" + t.datetime "updated_at" + t.string "versioned_type", :limit => 20 + end + + create_table "contents", :force => true do |t| + t.integer "site_id" + t.integer "section_id" + t.string "type", :limit => 20 + t.integer "position" + t.string "title" + t.string "permalink" + t.text "excerpt" + t.text "excerpt_html" + t.text "body" + t.text "body_html" + t.integer "author_id" + t.string "author_type" + t.string "author_name", :limit => 40 + t.string "author_email", :limit => 40 + t.string "author_homepage" + t.integer "version" + t.string "filter" + t.integer "comment_age", :default => 0 + t.string "cached_tag_list" + t.integer "assets_count", :default => 0 + t.datetime "published_at" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "counters", :force => true do |t| + t.integer "owner_id" + t.string "owner_type" + t.string "name", :limit => 25 + t.integer "count", :default => 0 + end + + create_table "memberships", :force => true do |t| + t.integer "site_id" + t.integer "user_id" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "roles", :force => true do |t| + t.integer "user_id" + t.integer "context_id" + t.string "context_type" + t.string "type", :limit => 25 + end + + create_table "sections", :force => true do |t| + t.string "type" + t.integer "site_id" + t.integer "parent_id" + t.integer "lft", :default => 0, :null => false + t.integer "rgt", :default => 0, :null => false + t.string "path" + t.string "permalink" + t.string "title" + t.string "layout" + t.string "template" + t.text "options" + t.integer "contents_count" + t.integer "comment_age" + t.string "content_filter" + t.text "permissions" + end + + create_table "sites", :force => true do |t| + t.string "name" + t.string "host" + t.string "title" + t.string "subtitle" + t.string "email" + t.string "timezone" + t.string "theme_names" + t.text "ping_urls" + t.string "akismet_key", :limit => 100 + t.string "akismet_url" + t.boolean "approve_comments" + t.integer "comment_age" + t.string "comment_filter" + t.string "search_path" + t.string "tag_path" + t.string "tag_layout" + t.string "permalink_style" + t.text "permissions" + end + + create_table "taggings", :force => true do |t| + t.integer "tag_id" + t.integer "taggable_id" + t.string "taggable_type" + t.datetime "created_at" + end + + add_index "taggings", ["taggable_id", "taggable_type"], :name => "index_taggings_on_taggable_id_and_taggable_type" + add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id" + + create_table "tags", :force => true do |t| + t.string "name" + end + + create_table "topics", :force => true do |t| + t.integer "site_id" + t.integer "section_id" + t.string "title" + t.integer "sticky", :default => 0 + t.boolean "locked", :default => false + t.integer "comments_count", :default => 0 + t.integer "hits", :default => 0 + t.integer "last_comment_id" + t.integer "last_author_id" + t.string "last_author_type" + t.string "last_author_name" + t.string "permalink" + t.datetime "created_at" + t.datetime "updated_at" + t.datetime "last_updated_at" + end + + create_table "users", :force => true do |t| + t.string "name", :limit => 40 + t.string "email", :limit => 100 + t.string "homepage" + t.string "about" + t.string "signature" + t.string "login", :limit => 40 + t.string "password_hash", :limit => 40 + t.string "password_salt", :limit => 40 + t.string "remember_me", :limit => 40 + t.string "token_key", :limit => 40 + t.datetime "token_expiration" + t.datetime "created_at" + t.datetime "updated_at" + t.datetime "verified_at" + t.datetime "deleted_at" + end + end diff --git a/spec/controllers/admin/plugins_controller_spec.rb b/spec/controllers/admin/plugins_controller_spec.rb new file mode 100644 index 000000000..30b0620ea --- /dev/null +++ b/spec/controllers/admin/plugins_controller_spec.rb @@ -0,0 +1,85 @@ +require File.dirname(__FILE__) + "/../../spec_helper" + +describe Admin::PluginsController do + include SpecControllerHelper + + before :each do + scenario :empty_site + + @plugin = Engines.plugins[:plugin_test] + set_resource_paths :plugin, '/admin/sites/1/' + + @controller.stub! :require_authentication + @controller.stub!(:has_permission?).and_return true + end + + it "should be an Admin::BaseController" do + controller.should be_kind_of(Admin::BaseController) + end + + describe "routing" do + with_options :path_prefix => '/admin/sites/1/', :site_id => "1" do |route| + route.it_maps :get, "plugins", :index + route.it_maps :get, "plugins/plugin_test", :show, :id => 'plugin_test' + route.it_maps :get, "plugins/plugin_test/edit", :edit, :id => 'plugin_test' + route.it_maps :put, "plugins/plugin_test", :update, :id => 'plugin_test' + end + end + + describe "GET to :index" do + act! { request_to :get, @collection_path } + # it_guards_permissions :show, :plugin + it_assigns :plugins + it_renders_template :index + end + + describe "GET to :show" do + act! { request_to :get, @member_path } + # it_guards_permissions :show, :plugin + it_assigns :plugin + it_renders_template :show + end + + describe "PUT to :update" do + act! { request_to :put, @member_path, :plugin => {'name' => 'value'} } + it_assigns :plugin + it_redirects_to { @member_path } + it_assigns_flash_cookie :notice => :not_nil + + it "updates the plugin's config options" do + @plugin.should_receive(:options=).with 'name' => 'value' + act! + end + + it "saves the plugin" do + @plugin.should_receive(:save!) + act! + end + end + + # describe "DELETE to :destroy" do + # act! { request_to :delete, @member_path } + # it_assigns :plugin + # + # it "fetches a plugin from section.plugins" do + # @section.plugins.should_receive(:find).and_return @plugin + # act! + # end + # + # it "should try to destroy the plugin" do + # @plugin.should_receive :destroy + # act! + # end + # + # describe "when destroy succeeds" do + # it_redirects_to { @collection_path } + # it_assigns_flash_cookie :notice => :not_nil + # end + # + # describe "when destroy fails" do + # before :each do @plugin.stub!(:destroy).and_return false end + # it_renders_template :edit + # it_assigns_flash_cookie :error => :not_nil + # end + # end +end \ No newline at end of file diff --git a/spec/models/content_spec.rb b/spec/models/content_spec.rb index 44a1e242c..25b7dec11 100755 --- a/spec/models/content_spec.rb +++ b/spec/models/content_spec.rb @@ -13,294 +13,294 @@ :published_at => @time_now end - # describe "class extensions:" do - # it "acts as a taggable" do - # Content.should act_as_taggable - # end - # - # it "acts as a role context for the author role" do - # Content.should act_as_role_context(:roles => :author) - # end - # - # it "acts as a commentable" do - # Content.should act_as_commentable - # end - # - # it "acts as versioned" do - # Content.should act_as_versioned - # end - # - # it "is configured to save a new version when the title, body or excerpt attribute changes" do - # Content.tracked_attributes.should == ["title", "body", "excerpt"] - # end - # - # it "is configured to save up to 5 versions" do - # Content.max_version_limit.should == 5 - # end - # - # it "ignores the columns cached_tag_list, assets_count and state" do - # defaults = ["id", "type", "version", "lock_version", "versioned_type"] - # Content.non_versioned_columns.should == defaults + ["cached_tag_list", "assets_count", "state"] - # end - # - # it "instantiates with single table inheritance" do - # Content.should instantiate_with_sti - # end - # - # it "has a permalink generated from the title" do - # Content.should have_a_permalink(:title) - # end - # - # it "filters the excerpt" do - # @content.should filter_column(:excerpt) - # end - # - # it "filters the body" do - # @content.should filter_column(:body) - # end - # - # it "has a comments counter" do - # Content.should have_counter(:comments) - # end - # end - # - # describe "associations" do - # it "belongs to a site" do - # @content.should belong_to(:site) - # end - # - # it "belongs to a section" do - # @content.should belong_to(:section) - # end - # - # it "belongs to an author" do - # @content.should belong_to(:author) - # end - # - # it "has many assets" do - # @content.should have_many(:assets) - # end - # - # it "has many asset_assignments" do - # @content.should have_many(:asset_assignments) - # end - # - # it "has many categories" do - # @content.should have_many(:categories) - # end - # - # it "has many category_assignments" do - # @content.should have_many(:category_assignments) - # end - # end - # - # describe "callbacks" do - # it "sets the site before validation" do - # Content.before_validation.should include(:set_site) - # end - # - # it "generates the permalink before validation" do - # Content.before_validation.should include(:create_unique_permalink) - # end - # - # it "apply filters before save" do - # Content.before_save.should include(:process_filters) - # end - # end - # - # describe "validations" do - # it "validate presence of a title" do - # @content.should validate_presence_of(:title) - # end - # - # it "validate presence of a body" do - # @content.should validate_presence_of(:body) - # end - # - # it "validate presence of an author (through belongs_to_author)" do - # @content.should validate_presence_of(:author) - # end - # - # it "validate presence of an author_name (through belongs_to_author)" do - # @content.author.stub!(:name).and_return nil - # @content.should validate_presence_of(:author_name) - # end - # - # it "validate presence of an author_email (through belongs_to_author)" do - # @content.author.stub!(:email).and_return nil - # @content.should validate_presence_of(:author_email) - # end - # - # it "validates the uniqueness of the permalink per site" do - # @content.should validate_uniqueness_of(:permalink) # :scope => :site_id - # end - # - # end - # - # describe "class methods:" do - # before :each do - # @attributes = {:title => 'title', :body => 'body', :section => stub_section, :author => stub_user} - # end - # - # describe "#find_published" do - # it "finds published articles" do - # article = Content.create! @attributes.update(:published_at => 1.hour.ago) - # Content.find_published(:all).should include(article) - # end - # - # it "#find_published does not find unpublished articles" do - # article = Content.create! @attributes - # Content.find_published(:all).should_not include(article) - # end - # end - # - # describe "#find_in_time_delta" do - # it "finds articles in the given time delta" do - # published_at = date = 1.hour.ago - # delta = date.year, date.month, date.day - # article = Content.create! @attributes.update(:published_at => published_at) - # Content.find_in_time_delta(*delta).should include(article) - # end - # - # it "#find_in_time_delta finds articles prior the given time delta" do - # published_at = 1.hour.ago - # date = 2.months.ago - # delta = date.year, date.month, date.day - # article = Content.create! @attributes.update(:published_at => published_at) - # Content.find_in_time_delta(*delta).should_not include(Content.new) - # end - # - # it "#find_in_time_delta finds articles after the given time delta" do - # published_at = 2.month.ago - # date = Time.zone.now - # delta = date.year, date.month, date.day - # article = Content.create! @attributes.update(:published_at => published_at) - # Content.find_in_time_delta(*delta).should_not include(article) - # end - # end - # - # describe "#find_every" do - # it "does not apply the default_find_options (order) if :order option is given" do - # Content.should_receive(:find_by_sql).with(/ORDER BY id/).and_return [@article] - # Content.find :all, :order => :id - # end - # - # it "applies the default_find_options (order) if :order option is not given" do - # order = /ORDER BY #{Content.default_find_options[:order]}/ - # Content.should_receive(:find_by_sql).with(order).and_return [@article] - # Content.find :all - # end - # - # it "finds articles tagged with :tags if the option :tags is given" do - # Content.should_receive(:find_options_for_find_tagged_with).and_return({}) - # Content.find :all, :tags => %w(foo bar) - # end - # end - # end - # - # describe "instance methods:" do - # it "#owner returns the section" do - # @content.stub!(:section).and_return @section - # @content.owner.should == @section - # end - # - # it "#attributes= calls update_categories if attributes include a :category_ids key" do - # @content.should_receive(:update_categories) - # @content.attributes = { :category_ids => [1, 2, 3] } - # end - # - # describe "#diff_against_version" do - # before :each do - # HtmlDiff.stub!(:diff) - # @other = Content.new :body_html => 'body', :excerpt_html => 'excerpt' - # @content.body_html = 'body' - # @content.excerpt_html = 'excerpt' - # @content.versions.stub!(:find_by_version).and_return @other - # end - # - # it "creates a diff" do - # HtmlDiff.should_receive(:diff) - # @content.diff_against_version(1) - # end - # - # it "diffs excerpt_html + body_html" do - # [@content, @other].each do |target| [:body_html, :excerpt_html].each do |method| - # target.should_receive(method).and_return method.to_s - # end end - # @content.diff_against_version(1) - # end - # end - # - # describe "#comments_expired_at" do - # it "returns a date 1 day after the published_at date if comments expire after 1 day" do - # @content.stub!(:comment_age).and_return 1 - # @content.comments_expired_at.to_date.should == 1.day.from_now.to_date - # end - # - # it "returns the published_at date if comments are not allowed (i.e. expire after 0 days)" do - # @content.stub!(:comment_age).and_return 0 - # @content.comments_expired_at.to_date.should == @time_now.to_date - # end - # - # it "returns something else? if comments never expire. hu?" do - # @content.stub!(:comment_age).and_return -1 - # @content.comments_expired_at.should == 9999.years.from_now - # end - # end - # - # it "#set_site sets the site_id from the section" do - # @content.section.should_receive(:site_id) - # @content.should_receive(:site_id=) - # @content.send :set_site - # end - # - # # describe "#update_categories updates the associated categories to match the given category ids" do - # # before :each do - # # scenario :category - # # @content.stub!(:categories).and_return [stub_category] - # # @content.categories.stub!(:delete) - # # Category.stub!(:find) - # # @category_ids = ['2', '3'] - # # end - # # - # # it 'removes associated categories that are not included in passed category_ids' do - # # @content.categories.should_receive(:delete) #.with(1) - # # @content.send :update_categories, @category_ids - # # end - # # - # # it 'finds (and assigns) categories that are included in passed category_ids but not already associated' do - # # @content.stub!(:categories).and_return [] - # # Category.should_receive(:find).and_return stub_category(:child) - # # @content.send :update_categories, @category_ids - # # @content.categories.should include(stub_category(:child)) - # # end - # # end - # end - # - # describe "versioning" do - # it "does not create a new version if neither title, excerpt nor body attributes have changed" do - # @content.save! - # @content.save_version?.should be_false - # end - # - # it "creates a new version if the title attribute has changed" do - # @content.save! - # @content.title = 'another title' - # @content.save_version?.should be_true - # end - # - # it "creates a new version if the excerpt attribute has changed" do - # @content.save! - # @content.excerpt = 'another excerpt' - # @content.save_version?.should be_true - # end - # - # it "creates a new version if the body attribute has changed" do - # @content.save! - # @content.body = 'another body' - # @content.save_version?.should be_true - # end - # end + describe "class extensions:" do + it "acts as a taggable" do + Content.should act_as_taggable + end + + it "acts as a role context for the author role" do + Content.should act_as_role_context(:roles => :author) + end + + it "acts as a commentable" do + Content.should act_as_commentable + end + + it "acts as versioned" do + Content.should act_as_versioned + end + + it "is configured to save a new version when the title, body or excerpt attribute changes" do + Content.tracked_attributes.should == ["title", "body", "excerpt"] + end + + it "is configured to save up to 5 versions" do + Content.max_version_limit.should == 5 + end + + it "ignores the columns cached_tag_list, assets_count and state" do + defaults = ["id", "type", "version", "lock_version", "versioned_type"] + Content.non_versioned_columns.should == defaults + ["cached_tag_list", "assets_count", "state"] + end + + it "instantiates with single table inheritance" do + Content.should instantiate_with_sti + end + + it "has a permalink generated from the title" do + Content.should have_a_permalink(:title) + end + + it "filters the excerpt" do + @content.should filter_column(:excerpt) + end + + it "filters the body" do + @content.should filter_column(:body) + end + + it "has a comments counter" do + Content.should have_counter(:comments) + end + end + + describe "associations" do + it "belongs to a site" do + @content.should belong_to(:site) + end + + it "belongs to a section" do + @content.should belong_to(:section) + end + + it "belongs to an author" do + @content.should belong_to(:author) + end + + it "has many assets" do + @content.should have_many(:assets) + end + + it "has many asset_assignments" do + @content.should have_many(:asset_assignments) + end + + it "has many categories" do + @content.should have_many(:categories) + end + + it "has many category_assignments" do + @content.should have_many(:category_assignments) + end + end + + describe "callbacks" do + it "sets the site before validation" do + Content.before_validation.should include(:set_site) + end + + it "generates the permalink before validation" do + Content.before_validation.should include(:create_unique_permalink) + end + + it "apply filters before save" do + Content.before_save.should include(:process_filters) + end + end + + describe "validations" do + it "validate presence of a title" do + @content.should validate_presence_of(:title) + end + + it "validate presence of a body" do + @content.should validate_presence_of(:body) + end + + it "validate presence of an author (through belongs_to_author)" do + @content.should validate_presence_of(:author) + end + + it "validate presence of an author_name (through belongs_to_author)" do + @content.author.stub!(:name).and_return nil + @content.should validate_presence_of(:author_name) + end + + it "validate presence of an author_email (through belongs_to_author)" do + @content.author.stub!(:email).and_return nil + @content.should validate_presence_of(:author_email) + end + + it "validates the uniqueness of the permalink per site" do + @content.should validate_uniqueness_of(:permalink) # :scope => :site_id + end + + end + + describe "class methods:" do + before :each do + @attributes = {:title => 'title', :body => 'body', :section => stub_section, :author => stub_user} + end + + describe "#find_published" do + it "finds published articles" do + article = Content.create! @attributes.update(:published_at => 1.hour.ago) + Content.find_published(:all).should include(article) + end + + it "#find_published does not find unpublished articles" do + article = Content.create! @attributes + Content.find_published(:all).should_not include(article) + end + end + + describe "#find_in_time_delta" do + it "finds articles in the given time delta" do + published_at = date = 1.hour.ago + delta = date.year, date.month, date.day + article = Content.create! @attributes.update(:published_at => published_at) + Content.find_in_time_delta(*delta).should include(article) + end + + it "#find_in_time_delta finds articles prior the given time delta" do + published_at = 1.hour.ago + date = 2.months.ago + delta = date.year, date.month, date.day + article = Content.create! @attributes.update(:published_at => published_at) + Content.find_in_time_delta(*delta).should_not include(Content.new) + end + + it "#find_in_time_delta finds articles after the given time delta" do + published_at = 2.month.ago + date = Time.zone.now + delta = date.year, date.month, date.day + article = Content.create! @attributes.update(:published_at => published_at) + Content.find_in_time_delta(*delta).should_not include(article) + end + end + + describe "#find_every" do + it "does not apply the default_find_options (order) if :order option is given" do + Content.should_receive(:find_by_sql).with(/ORDER BY id/).and_return [@article] + Content.find :all, :order => :id + end + + it "applies the default_find_options (order) if :order option is not given" do + order = /ORDER BY #{Content.default_find_options[:order]}/ + Content.should_receive(:find_by_sql).with(order).and_return [@article] + Content.find :all + end + + it "finds articles tagged with :tags if the option :tags is given" do + Content.should_receive(:find_options_for_find_tagged_with).and_return({}) + Content.find :all, :tags => %w(foo bar) + end + end + end + + describe "instance methods:" do + it "#owner returns the section" do + @content.stub!(:section).and_return @section + @content.owner.should == @section + end + + it "#attributes= calls update_categories if attributes include a :category_ids key" do + @content.should_receive(:update_categories) + @content.attributes = { :category_ids => [1, 2, 3] } + end + + describe "#diff_against_version" do + before :each do + HtmlDiff.stub!(:diff) + @other = Content.new :body_html => 'body', :excerpt_html => 'excerpt' + @content.body_html = 'body' + @content.excerpt_html = 'excerpt' + @content.versions.stub!(:find_by_version).and_return @other + end + + it "creates a diff" do + HtmlDiff.should_receive(:diff) + @content.diff_against_version(1) + end + + it "diffs excerpt_html + body_html" do + [@content, @other].each do |target| [:body_html, :excerpt_html].each do |method| + target.should_receive(method).and_return method.to_s + end end + @content.diff_against_version(1) + end + end + + describe "#comments_expired_at" do + it "returns a date 1 day after the published_at date if comments expire after 1 day" do + @content.stub!(:comment_age).and_return 1 + @content.comments_expired_at.to_date.should == 1.day.from_now.to_date + end + + it "returns the published_at date if comments are not allowed (i.e. expire after 0 days)" do + @content.stub!(:comment_age).and_return 0 + @content.comments_expired_at.to_date.should == @time_now.to_date + end + + it "returns something else? if comments never expire. hu?" do + @content.stub!(:comment_age).and_return -1 + @content.comments_expired_at.should == 9999.years.from_now + end + end + + it "#set_site sets the site_id from the section" do + @content.section.should_receive(:site_id) + @content.should_receive(:site_id=) + @content.send :set_site + end + + # describe "#update_categories updates the associated categories to match the given category ids" do + # before :each do + # scenario :category + # @content.stub!(:categories).and_return [stub_category] + # @content.categories.stub!(:delete) + # Category.stub!(:find) + # @category_ids = ['2', '3'] + # end + # + # it 'removes associated categories that are not included in passed category_ids' do + # @content.categories.should_receive(:delete) #.with(1) + # @content.send :update_categories, @category_ids + # end + # + # it 'finds (and assigns) categories that are included in passed category_ids but not already associated' do + # @content.stub!(:categories).and_return [] + # Category.should_receive(:find).and_return stub_category(:child) + # @content.send :update_categories, @category_ids + # @content.categories.should include(stub_category(:child)) + # end + # end + end + + describe "versioning" do + it "does not create a new version if neither title, excerpt nor body attributes have changed" do + @content.save! + @content.save_version?.should be_false + end + + it "creates a new version if the title attribute has changed" do + @content.save! + @content.title = 'another title' + @content.save_version?.should be_true + end + + it "creates a new version if the excerpt attribute has changed" do + @content.save! + @content.excerpt = 'another excerpt' + @content.save_version?.should be_true + end + + it "creates a new version if the body attribute has changed" do + @content.save! + @content.body = 'another body' + @content.save_version?.should be_true + end + end describe "tagging" do it "works with the quotes on a taglist" do diff --git a/spec/spec_helpers/spec_resource_path_helper.rb b/spec/spec_helpers/spec_resource_path_helper.rb index f72fa79fe..bd044572c 100644 --- a/spec/spec_helpers/spec_resource_path_helper.rb +++ b/spec/spec_helpers/spec_resource_path_helper.rb @@ -15,6 +15,6 @@ def resource_collection_path(path_prefix, name, action = nil) end def resource_member_path(path_prefix, name, member, action = nil) - "#{resource_collection_path(path_prefix, name)}/#{member.id}" + (action ? "/#{action}" : '') + "#{resource_collection_path(path_prefix, name)}/#{member.to_param}" + (action ? "/#{action}" : '') end end \ No newline at end of file diff --git a/spec/views/admin/plugin_views_spec.rb b/spec/views/admin/plugin_views_spec.rb new file mode 100644 index 000000000..8fd15aee1 --- /dev/null +++ b/spec/views/admin/plugin_views_spec.rb @@ -0,0 +1,102 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +describe "Admin::Plugins:" do + include SpecViewHelper + + before :each do + @plugins = Engines.plugins + @plugin = Engines.plugins[:plugin_test] + + assigns[:site] = @site = stub_site + + set_resource_paths :plugin, '/admin/sites/1/' + + template.stub!(:admin_plugins_path).and_return(@collection_path) + template.stub!(:admin_plugin_path).and_return(@member_path) + + template.stub!(:pluralize_str) + template.stub!(:will_paginate) + end + + describe "the :index view" do + before :each do + assigns[:plugins] = @plugins + template.stub_render hash_including(:partial => 'plugin') + end + + it "should display a list of cached pages" do + render "admin/plugins/index" + response.should have_tag('table[id=?]', 'plugins') + end + + it "should render the plugin partial with the plugins collection" do + template.expect_render hash_including(:partial => 'plugin', :collection => @plugins) + render "admin/plugins/index" + end + end + + describe "the :show view" do + before :each do + assigns[:plugin] = @plugin + template.stub_render hash_including(:partial => 'form') + end + + it "should display author information for the plugin" do + render "admin/plugins/show" + response[:sidebar].should have_text(/#{@plugin.about['author']}/) + end + + it "should display homepage information for the plugin" do + render "admin/plugins/show" + response[:sidebar].should have_text(/#{@plugin.about['homepage']}/) + end + + it "should display a summary for the plugin" do + render "admin/plugins/show" + response[:sidebar].should have_text(/#{@plugin.about['summary']}/) + end + + it "should display a description for the plugin" do + render "admin/plugins/show" + response.should have_text(/#{@plugin.about['description']}/) + end + + it "should render the form partial" do + template.expect_render hash_including(:partial => 'form') + render "admin/plugins/show" + end + end + + describe "the action_nav partial" do + before :each do + assigns[:plugin] = @plugin + end + + it "should display a link to the index view" do + render "admin/plugins/_action_nav" + response[:action_nav].should have_tag('a[href=?]', admin_plugins_path(@site), 'Plugins') + end + + it "should display a link to restore the default values" do + render "admin/plugins/_action_nav" + response[:action_nav].should have_tag('a[href=?]', '#', 'Restore Defaults') + end + end + + describe "the form partial" do + before :each do + assigns[:plugin] = @plugin + template.stub!(:f).and_return ActionView::Base.default_form_builder.new(:plugin, @plugin, template, {}, nil) + end + + it "should render a string field for the string option" do + render "admin/plugins/_form" + response.should have_tag('input[name=?][value=?]', 'plugin[string]', 'default string') + end + + it "should render a text field for the text option" do + render "admin/plugins/_form" + response.should have_tag('textarea[name=?]', 'plugin[text]', 'default text') + end + end +end \ No newline at end of file diff --git a/stories/steps/article.rb b/stories/steps/article.rb index 668d62d59..15ec325a4 100644 --- a/stories/steps/article.rb +++ b/stories/steps/article.rb @@ -28,21 +28,30 @@ end Then "a new article is saved" do - raise "this step expects @article_count to be set" unless @article_count + raise "step expects @article_count to be set" unless @article_count (@article_count + 1).should == Article.count end Then "the article is deleted" do - raise "this step expects @article_count to be set" unless @article_count + raise "step expects @article_count to be set" unless @article_count (@article_count - 1).should == Article.find(:all).size end # ADMIN VIEW When "the user visits the admin $section articles list page" do |section| - raise "this step expects the variable @blog or @section to be set" unless @blog or @section - object = (@blog or @section) - get admin_articles_path(object.site, object) + raise "step expects the variable @blog or @section to be set" unless @blog or @section + section = @blog || @section + get admin_articles_path(section.site, section) + end + + When "the user creates and publishes a new article" do + lambda { + When "the user visits the admin blog article creation page" + When "the user fills in the admin article creation form with valid values" + When "the user unchecks 'Yes, save this article as a draft'" + When "the user clicks the 'Save article' button" + }.should change(Article, :count).by(1) end When "the user fills in the admin article creation form with valid values" do @@ -52,29 +61,35 @@ end When "the user clicks on the article link" do - raise "this step expects the variable @article to be set" unless @article + raise "step expects the variable @article to be set" unless @article When "the user clicks on '#{@article.title}'" end + When "the user visits the admin blog article creation page" do + raise "step expects the variable @blog or @section to be set" unless @blog + get new_admin_article_path(@blog.site, @blog) + @article_count = 0 + end + When "the user visits the admin $section article edit page" do |section| - raise "this step expects the variable @article and @blog or @section to be set" unless @article and (@blog or @section) - object = (@blog or @section) - get edit_admin_article_path(object.site, object, @article) + raise "step expects the variable @article and @blog or @section to be set" unless @article and (@blog or @section) + section = @blog || @section + get edit_admin_article_path(section.site, section, @article) @article_count = Article.count end Then "the page has an admin article creation form" do - raise "this step expects the variable @section or @blog to be set" unless @blog or @section - object = (@blog or @section) - action = admin_articles_path(object.site, object) + raise "step expects the variable @section or @blog to be set" unless @blog or @section + section = @blog || @section + action = admin_articles_path(section.site, section) response.should have_form_posting_to(action) @article_count = Article.count end Then "the page has an admin article editing form" do - raise "this step expects the variable @article and @blog or @section to be set" unless @article and (@blog or @section) - object = (@blog or @section) - action = admin_article_path(object.site, object, @article) + raise "step expects the variable @article and @blog or @section to be set" unless @article and (@blog or @section) + section = @blog || @section + action = admin_article_path(section.site, section, @article) response.should have_form_putting_to(action) @article_count = Article.count end @@ -102,17 +117,21 @@ response.should have_tag("input#article-draft[type=?][value=?]", 'checkbox', 1) end + Then "the blog has sent pings" do + + end + # TODO sections seems to have other problems too, because newly created section # from website leads to home and not to correct section. #Then "the page displays the article" do - # raise "this step expects the variable @article to be set" unless @article + # raise "step expects the variable @article to be set" unless @article # response.should have_tag("div#article_#{@article.id}.entry") #end # TODO Preview for section does not work. # Preview link from section articles leads to empty page. #Then "the page displays the article as preview" do - # raise "this step expects the variable @article to be set" unless @article + # raise "step expects the variable @article to be set" unless @article # response.should have_tag("div#article_#{@article.id}[class=?]", 'entry clearing') #end diff --git a/stories/steps/site.rb b/stories/steps/site.rb index 9651a21f6..f5dba1525 100644 --- a/stories/steps/site.rb +++ b/stories/steps/site.rb @@ -25,6 +25,9 @@ get edit_admin_site_path(@site) end + # When "the user visits the admin plugin list page" do + # end + When "the user fills in the admin site creation form with valid values" do fills_in 'website name', :with => 'a new site name' fills_in 'website title', :with => 'a new site title' diff --git a/stories/stories/admin/blog_article.txt b/stories/stories/admin/blog_article.txt index 2d8a2ce1d..45b191012 100644 --- a/stories/stories/admin/blog_article.txt +++ b/stories/stories/admin/blog_article.txt @@ -37,6 +37,41 @@ Story: Publishing a blog article And the user goes to the url / Then the page displays the article +# Story: Sending pings when publishing an article +# As an admin +# I want the blog to send pings to ping services when I publish an article +# So my article gets more traffic +# +# Scenario: Publishing a new article triggers pings +# Given a blog with no articles +# And the user is logged in as admin +# When the user creates and publishes a new article +# Then the blog has sent pings +# +# Scenario: Publishing an existing article triggers pings +# Given a blog with an article +# When the user is logged in as admin +# When the user updates and publishes the article +# Then the blog has sent pings +# +# Scenario: Updating a published article does not trigger pings +# Given a blog with a published article +# When the user is logged in as admin +# When the user updates the article +# Then the blog has not sent any pings +# +# Scenario: Updating an unpublished article does not trigger pings +# Given a blog with an unpublished article +# When the user is logged in as admin +# When the user updates the article +# Then the blog has not sent any pings +# +# Scenario: Unpublishing an article does not trigger pings +# Given a blog with a published article +# When the user is logged in as admin +# When the user unpublishes the article +# Then the blog has not sent any pings + Story: Deleting a blog article As an admin I want to delete a blog article in the admin area diff --git a/stories/stories/admin/plugin.txt b/stories/stories/admin/plugin.txt new file mode 100644 index 000000000..bb16e181c --- /dev/null +++ b/stories/stories/admin/plugin.txt @@ -0,0 +1,18 @@ +Story: Managing a site's plugins + As an admin + I want to manage my site's plugins + So I can see what's installed and change config options + + Scenario: An admin reviews the plugin list + Given a site + And the user is logged in as admin + When the user visits the admin plugin list page + Then the list contains all the plugins installed + + Scenario: An admin reviews the details of a plugin + Given a site + And the user is logged in as admin + When the user visits the admin plugin show page for the test plugin + Then the page shows the test plugin about info + And the page has a plugin config edit form + \ No newline at end of file diff --git a/stories/stories/blog_article.txt b/stories/stories/blog_article.txt index 1cfd63c4d..b14000f71 100644 --- a/stories/stories/blog_article.txt +++ b/stories/stories/blog_article.txt @@ -59,7 +59,7 @@ Story: Viewing a blog article page When the user goes to the url /2008/1/1/the-article-title Then the request does not succeed And the page is not cached - + Story: Previewing a blog article page As an admin diff --git a/vendor/engines/adva_cms/app/controllers/admin/plugins_controller.rb b/vendor/engines/adva_cms/app/controllers/admin/plugins_controller.rb new file mode 100644 index 000000000..8defb6c5d --- /dev/null +++ b/vendor/engines/adva_cms/app/controllers/admin/plugins_controller.rb @@ -0,0 +1,27 @@ +class Admin::PluginsController < Admin::BaseController + before_filter :set_plugins, :only => :index + before_filter :set_plugin, :only => [:show, :edit, :update] + + def index + end + + def show + end + + def update + @plugin.options = params[:plugin] + @plugin.save! + flash[:notice] = "The plugin settings have been updated." + redirect_to admin_plugin_path(@site, @plugin) + end + + protected + + def set_plugins + @plugins = Engines.plugins + end + + def set_plugin + @plugin = Engines.plugins[params[:id]] or raise ActiveRecord::RecordNotFound + end +end \ No newline at end of file diff --git a/vendor/engines/adva_cms/app/views/admin/plugins/_action_nav.html.erb b/vendor/engines/adva_cms/app/views/admin/plugins/_action_nav.html.erb new file mode 100644 index 000000000..380deca4e --- /dev/null +++ b/vendor/engines/adva_cms/app/views/admin/plugins/_action_nav.html.erb @@ -0,0 +1,8 @@ +<% content_for :action_nav do %> + +<% end %> diff --git a/vendor/engines/adva_cms/app/views/admin/plugins/_form.html.erb b/vendor/engines/adva_cms/app/views/admin/plugins/_form.html.erb new file mode 100644 index 000000000..30f3c2787 --- /dev/null +++ b/vendor/engines/adva_cms/app/views/admin/plugins/_form.html.erb @@ -0,0 +1,12 @@ +
+ <% @plugin.default_options.each do |name, option| %> +

+ <%= f.label name, name %> + <% if option[:type] == :text %> + <%= f.text_area name, :class => 'short' %> + <% else %> + <%= f.text_field name %> + <% end %> +

+ <% end %> +
\ No newline at end of file diff --git a/vendor/engines/adva_cms/app/views/admin/plugins/_plugin.html.erb b/vendor/engines/adva_cms/app/views/admin/plugins/_plugin.html.erb new file mode 100644 index 000000000..5c7f4933b --- /dev/null +++ b/vendor/engines/adva_cms/app/views/admin/plugins/_plugin.html.erb @@ -0,0 +1,17 @@ +"> + + <%= link_to plugin.name, admin_plugin_path(@site, plugin) %> + + + <%= plugin.about['version'] %> + + + <%= plugin.about['author'] %> + + + <%= plugin.about['homepage'] %> + + + <%= plugin.configurable? ? 'yes' : '' %> + + \ No newline at end of file diff --git a/vendor/engines/adva_cms/app/views/admin/plugins/index.html.erb b/vendor/engines/adva_cms/app/views/admin/plugins/index.html.erb new file mode 100644 index 000000000..453bc0007 --- /dev/null +++ b/vendor/engines/adva_cms/app/views/admin/plugins/index.html.erb @@ -0,0 +1,23 @@ +<%= render :partial => 'action_nav' %> + +<% if @plugins.size > 0 -%> + + + + + + + + + + + + <%= render :partial => 'plugin', :collection => @plugins %> + +
NameVersionAuthorHomepageConfigurable?
+ +<% else %> +
+ There currently aren't any plugins installed. +
+<% end %> diff --git a/vendor/engines/adva_cms/app/views/admin/plugins/show.html.erb b/vendor/engines/adva_cms/app/views/admin/plugins/show.html.erb new file mode 100644 index 000000000..adef8ae05 --- /dev/null +++ b/vendor/engines/adva_cms/app/views/admin/plugins/show.html.erb @@ -0,0 +1,35 @@ +<%= render :partial => 'action_nav' %> + +<% content_for :sidebar do %> +

About

+ <% if @plugin.about['summary'] %> +

<%= @plugin.about['summary'] %>

+ <% end %> + <% if @plugin.about['version'] %> +

Version: <%= @plugin.about['version'] %>

+ <% end %> + <% if @plugin.about['author'] %> +

Author: <%= @plugin.about['author'] %>

+ <% end %> + <% if @plugin.about['homepage'] %> +

Homepage: <%= @plugin.about['homepage'] %>

+ <% end %> +<% end %> + +<% if @plugin.about['description'] %> +

Plugin: <%= @plugin.name %>

+

<%= @plugin.about['description'] %>

+<% end %> + +

Settings

+ +<% unless @plugin.default_options.empty? %> + <% form_for :plugin, @plugin, :url => admin_plugin_path(@site), :html => { :method => :put } do |f| -%> + <%= render :partial => 'form', :locals => {:f => f} %> +

+ <%= f.submit :Save %> +

+ <% end -%> +<% else %> +

This plugin does not have any config options listed.

+<% end %> \ No newline at end of file diff --git a/vendor/engines/adva_cms/app/views/widgets/admin/_menu_global.html.erb b/vendor/engines/adva_cms/app/views/widgets/admin/_menu_global.html.erb index f047c18c1..28255b2db 100644 --- a/vendor/engines/adva_cms/app/views/widgets/admin/_menu_global.html.erb +++ b/vendor/engines/adva_cms/app/views/widgets/admin/_menu_global.html.erb @@ -2,7 +2,7 @@ <% if Site.multi_sites_enabled %>
  • <%= site_select_tag %>
  • <% end %> -
  • <%= link_to 'Plugins', '#' %>
  • +
  • <%= link_to 'Plugins', admin_plugins_path(@site) %>
  • <%= link_to 'Account', '#' %>
  • <%= link_to 'Logout', logout_path, :method => :delete %>
  • diff --git a/vendor/engines/adva_cms/routes.rb b/vendor/engines/adva_cms/routes.rb index 69a902b2e..3fd93af98 100644 --- a/vendor/engines/adva_cms/routes.rb +++ b/vendor/engines/adva_cms/routes.rb @@ -58,7 +58,11 @@ :name_prefix => 'admin_', :conditions => { :method => :delete } -map.resources :cached_pages, :controller => 'admin/cached_pages', # TODO map manually, we only use two of these +map.resources :cached_pages, :controller => 'admin/cached_pages', # TODO map manually? we only use some of these + :path_prefix => 'admin/sites/:site_id', + :name_prefix => 'admin_' + +map.resources :plugins, :controller => 'admin/plugins', # TODO map manually? we only use some of these :path_prefix => 'admin/sites/:site_id', :name_prefix => 'admin_' diff --git a/vendor/engines/adva_forum/init.rb b/vendor/engines/adva_forum/init.rb index 15bc39608..5d6f80a33 100644 --- a/vendor/engines/adva_forum/init.rb +++ b/vendor/engines/adva_forum/init.rb @@ -3,4 +3,5 @@ config.to_prepare do Section.register_type 'Forum' -end \ No newline at end of file +end + diff --git a/vendor/plugins/engines_config/db/migrate/20080710000000_create_plugin_configs.rb b/vendor/plugins/engines_config/db/migrate/20080710000000_create_plugin_configs.rb new file mode 100644 index 000000000..4936c2026 --- /dev/null +++ b/vendor/plugins/engines_config/db/migrate/20080710000000_create_plugin_configs.rb @@ -0,0 +1,13 @@ +class CreatePluginConfigs < ActiveRecord::Migration + def self.up + create_table :plugin_configs, :force => true do |t| + t.references :site + t.string :name + t.text :options + end + end + + def self.down + drop_table :plugin_configs + end +end diff --git a/vendor/plugins/engines_config/init.rb b/vendor/plugins/engines_config/init.rb new file mode 100644 index 000000000..3d64c0d2c --- /dev/null +++ b/vendor/plugins/engines_config/init.rb @@ -0,0 +1 @@ +Engines::Plugin.send :include, Engines::Plugin::Configurable \ No newline at end of file diff --git a/vendor/plugins/engines_config/lib/engines/plugin/configurable.rb b/vendor/plugins/engines_config/lib/engines/plugin/configurable.rb new file mode 100644 index 000000000..170170a63 --- /dev/null +++ b/vendor/plugins/engines_config/lib/engines/plugin/configurable.rb @@ -0,0 +1,70 @@ +# TODO make this use has_options? + +module Engines + class Plugin + module Configurable + class Config < ActiveRecord::Base + set_table_name 'plugin_configs' + serialize :options, Hash + + def after_initialize + write_attribute(:options, {}) if read_attribute(:options).nil? + end + end + + def configurable? + not default_options.empty? + end + + def default_options + @default_options ||= {} + end + + def option(name, default, type = :string) + raise "can't use #{name.inspect} as an option name" if respond_to? name + instance_eval <<-END, __FILE__, __LINE__ + def #{name} + config.options[#{name.inspect}] || #{default.inspect} + end + def #{name}=(value) + config.options[#{name.inspect}] = value + end + END + default_options[name] = {:default => default, :type => type} + end + + def options=(options) + config.options = options + end + + def id + name + end + + def to_param + name + end + + def save! + config.save! + end + + def destroy + config.destroy + @config = nil + end + + private + + def config + @config ||= Config.find_or_initialize_by_name(conf_name) + end + + # allow subclasses to hook in here + def conf_name + name + end + end + end +end + diff --git a/vendor/spec/plugin_test/about.yml b/vendor/spec/plugin_test/about.yml new file mode 100644 index 000000000..9ab9c13ae --- /dev/null +++ b/vendor/spec/plugin_test/about.yml @@ -0,0 +1,7 @@ +author: the author +email: the.author@email.org +homepage: http://www.the-homepage.org +summary: The plugin's summary +description: The plugin's description +license: MIT +version: 0.0.1 \ No newline at end of file diff --git a/vendor/spec/plugin_test/init.rb b/vendor/spec/plugin_test/init.rb new file mode 100644 index 000000000..2468e5a84 --- /dev/null +++ b/vendor/spec/plugin_test/init.rb @@ -0,0 +1,5 @@ +# This plugin is here to be able to spec the plugin interface. Will be loaded +# only in the test environment. + +option :string, 'default string', :string +option :text, 'default text', :text \ No newline at end of file