diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 index eb3489a..38c4877 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,7 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. -# -# If you find yourself ignoring temporary files generated by your text editor -# or operating system, you probably want to add a global ignore instead: -# git config --global core.excludesfile ~/.gitignore_global - -# Ignore bundler config -/.bundle - -# Ignore the default SQLite database. -/db/*.sqlite3 - -# Ignore all logfiles and tempfiles. -/log/*.log -/tmp +log +config/database.yml +.*.sw? +config/site.rb +tmp +mail_temp +config/site.rb diff --git a/AUTHORS.markdown b/AUTHORS old mode 100755 new mode 100644 similarity index 65% rename from AUTHORS.markdown rename to AUTHORS index 2bdfb37..d291795 --- a/AUTHORS.markdown +++ b/AUTHORS @@ -1,7 +1,5 @@ -## Authors - * Luben Manolov * Nick Penkov * Eugene Korbut * Emilio Blanco -* Wojciech Todryk +* Wojciech Todryk diff --git a/CHANGES.markdown b/CHANGES.markdown deleted file mode 100755 index 50e4d0f..0000000 --- a/CHANGES.markdown +++ /dev/null @@ -1,47 +0,0 @@ -## Changes - -#### 0.9.5 - - * favicon added - -#### 0.9.4 - - * bump gems - -#### 0.9.3 - - * handle Cc & Bcc adresses fix - -#### 0.9.2 - - * fixes in handling draft folder - -#### 0.9.1 - - * nowrap to edit column in contacts & links - * decoded changed in mail gem - * fixes in pl locale - -#### 0.9.0 - - * switch to Rails 3.2.x - * Tweeter Bootstrap as default theme - * many fixes - -#### 0.8.6 - - * new calendar view - -#### 0.8.5 - - * servers view - * identity modification - -#### 0.8.4 - - * calendar view as separate gem - * adding bluecloth for rendering markdown text - -#### 0.8.3 - - * export, imports of contact diff --git a/Gemfile b/Gemfile deleted file mode 100755 index 31e4d9d..0000000 --- a/Gemfile +++ /dev/null @@ -1,47 +0,0 @@ -source 'https://rubygems.org' - -gem 'rails', '>= 3.2.11' - -# Bundle edge Rails instead: -# gem 'rails', :git => 'git://github.com/rails/rails.git' - -gem 'mysql2' - -gem 'json', '>= 1.7.6' - -# Gems used only for assets and not required -# in production environments by default. -group :assets do - gem 'sass-rails' - gem 'coffee-rails' - - # See https://github.com/sstephenson/execjs#readme for more supported runtimes - # gem 'therubyracer' - - gem 'uglifier' -end - -gem 'jquery-rails' - -# To use ActiveModel has_secure_password -# gem 'bcrypt-ruby', '~> 3.0.0' - -# To use Jbuilder templates for JSON -# gem 'jbuilder' - -# Use unicorn as the app server -# gem 'unicorn' - -# Deploy with Capistrano -# gem 'capistrano' - -# To use debugger -# gem 'ruby-debug' - -gem 'will_paginate' -gem "ezcrypto" -gem 'calendar_view' -gem 'bluecloth' -gem 'sass' -gem 'haml' -#gem 'twitter_bootstrap_form_for' diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index ed43337..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,125 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - actionmailer (3.2.11) - actionpack (= 3.2.11) - mail (~> 2.4.4) - actionpack (3.2.11) - activemodel (= 3.2.11) - activesupport (= 3.2.11) - builder (~> 3.0.0) - erubis (~> 2.7.0) - journey (~> 1.0.4) - rack (~> 1.4.0) - rack-cache (~> 1.2) - rack-test (~> 0.6.1) - sprockets (~> 2.2.1) - activemodel (3.2.11) - activesupport (= 3.2.11) - builder (~> 3.0.0) - activerecord (3.2.11) - activemodel (= 3.2.11) - activesupport (= 3.2.11) - arel (~> 3.0.2) - tzinfo (~> 0.3.29) - activeresource (3.2.11) - activemodel (= 3.2.11) - activesupport (= 3.2.11) - activesupport (3.2.11) - i18n (~> 0.6) - multi_json (~> 1.0) - arel (3.0.2) - bluecloth (2.2.0) - builder (3.0.4) - calendar_view (0.0.7) - rails (>= 3.0.0) - coffee-rails (3.2.2) - coffee-script (>= 2.2.0) - railties (~> 3.2.0) - coffee-script (2.2.0) - coffee-script-source - execjs - coffee-script-source (1.4.0) - erubis (2.7.0) - execjs (1.4.0) - multi_json (~> 1.0) - ezcrypto (0.7.2) - haml (3.1.7) - hike (1.2.1) - i18n (0.6.1) - journey (1.0.4) - jquery-rails (2.2.0) - railties (>= 3.0, < 5.0) - thor (>= 0.14, < 2.0) - json (1.7.6) - mail (2.4.4) - i18n (>= 0.4.0) - mime-types (~> 1.16) - treetop (~> 1.4.8) - mime-types (1.19) - multi_json (1.5.0) - mysql2 (0.3.11) - polyglot (0.3.3) - rack (1.4.4) - rack-cache (1.2) - rack (>= 0.4) - rack-ssl (1.3.3) - rack - rack-test (0.6.2) - rack (>= 1.0) - rails (3.2.11) - actionmailer (= 3.2.11) - actionpack (= 3.2.11) - activerecord (= 3.2.11) - activeresource (= 3.2.11) - activesupport (= 3.2.11) - bundler (~> 1.0) - railties (= 3.2.11) - railties (3.2.11) - actionpack (= 3.2.11) - activesupport (= 3.2.11) - rack-ssl (~> 1.3.2) - rake (>= 0.8.7) - rdoc (~> 3.4) - thor (>= 0.14.6, < 2.0) - rake (10.0.3) - rdoc (3.12) - json (~> 1.4) - sass (3.2.5) - sass-rails (3.2.6) - railties (~> 3.2.0) - sass (>= 3.1.10) - tilt (~> 1.3) - sprockets (2.2.2) - hike (~> 1.2) - multi_json (~> 1.0) - rack (~> 1.0) - tilt (~> 1.1, != 1.3.0) - thor (0.17.0) - tilt (1.3.3) - treetop (1.4.12) - polyglot - polyglot (>= 0.3.1) - tzinfo (0.3.35) - uglifier (1.3.0) - execjs (>= 0.3.0) - multi_json (~> 1.0, >= 1.0.2) - will_paginate (3.0.4) - -PLATFORMS - ruby - -DEPENDENCIES - bluecloth - calendar_view - coffee-rails - ezcrypto - haml - jquery-rails - json (>= 1.7.6) - mysql2 - rails (>= 3.2.11) - sass - sass-rails - uglifier - will_paginate diff --git a/README b/README new file mode 100644 index 0000000..6a4374f --- /dev/null +++ b/README @@ -0,0 +1,31 @@ + +Installation Guide +Requirements + + * Ruby 1.8.7 + * Rails 2.3.2 + +Installation + + 1. Checkout the source code + 2. If you need to override some of the default constants used in the application take a look at config/default_site.rb. Then create config/site.rb that contains only the keys which you want to override. Example content of config/site.rb is: + + module CDF + + LOCALCONFIG = { + :imap_server => 'your.imap.server' + } + end + + 3. Configure SMTP settings + # initializers/smtp_settings.rb + ActionMailer::Base.smtp_settings = { + :address => "mail.example.com.py", + :port => 26, + :authentication => :plain, + :enable_starttls_auto => true, + :user_name => "emilio@example.com.py", + :password => "yourpass" + } + + 4 Use it diff --git a/README.markdown b/README.markdown deleted file mode 100755 index 06c49cf..0000000 --- a/README.markdown +++ /dev/null @@ -1,38 +0,0 @@ -[![Dependency Status](https://gemnasium.com/musashimm/mailr.png)](https://gemnasium.com/musashimm/mailr) - -## Introduction -_MailR_ is a IMAP mail client based on _Ruby on Rails_ platform. - -**NOTE** All path and filenames are based on _Rails.root_ directory. - -## Requirements - -In _Rails 3_ and above all dependencies should be defined in file _Gemfile_. All needed gems can be installed using bundler. - -## Installation procedure - -* Checkout the source code. -* Install all dependiences. Check if proper gems (sqlite3/mysql/postgresql) are defined in _Gemfile_ and installed. Use _bundler_ for that: - -```shell -bundle install -``` - -* Check _config/settings.yml_ for proper values. (see _config/settings.yml.example_). -* Prepare config/database.yml file (see _config/database.yml.example_). -* Migrate database (rake db:migrate) -* Start rails server if applicable -* Point your browser to application URL: - For local access: http://localhost:3000 - For remote access: http://some_url/mailr -* Using browser do basic setup. If You make a mistake delete all data from DB using rake task: - -```shell -rake db:clear_data -``` - -* Use it. - -## Specific configuration - -None diff --git a/Rakefile b/Rakefile old mode 100755 new mode 100644 index c1d2ddb..cffd19f --- a/Rakefile +++ b/Rakefile @@ -1,7 +1,10 @@ -#!/usr/bin/env rake # Add your own tasks in files placed in lib/tasks ending in .rake, -# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. +# for example lib/tasks/switchtower.rake, and they will automatically be available to Rake. -require File.expand_path('../config/application', __FILE__) +require(File.join(File.dirname(__FILE__), 'config', 'boot')) -Mailr::Application.load_tasks +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +require 'tasks/rails' \ No newline at end of file diff --git a/TODO.markdown b/TODO.markdown deleted file mode 100755 index f1d3348..0000000 --- a/TODO.markdown +++ /dev/null @@ -1,21 +0,0 @@ -## Todo - - * add themes - -app/controllers/folders_controller.rb: - - * [ 29] [TODO] recreate local copy of folders - * [ 98] [TODO] save system folders - -app/controllers/messages_controller.rb: - - * [101] [FIXME] missing fields and support arrays - -app/controllers/messages_ops_controller.rb: - - * [261] [FIXME] edit does not support attachments - * [325] [TODO] check if email address is valid if not get address from contacts - -app/models/prefs.rb: - - * [ 21] [TODO] move refresh to prefs and make refresh page with messages diff --git a/UNLICENSE.markdown b/UNLICENSE old mode 100755 new mode 100644 similarity index 99% rename from UNLICENSE.markdown rename to UNLICENSE index d5d5664..68a49da --- a/UNLICENSE.markdown +++ b/UNLICENSE @@ -1,5 +1,3 @@ -## License - This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or diff --git a/app/assets/images/glyphicons-halflings-white.png b/app/assets/images/glyphicons-halflings-white.png deleted file mode 100755 index a20760b..0000000 Binary files a/app/assets/images/glyphicons-halflings-white.png and /dev/null differ diff --git a/app/assets/images/glyphicons-halflings.png b/app/assets/images/glyphicons-halflings.png deleted file mode 100755 index 92d4445..0000000 Binary files a/app/assets/images/glyphicons-halflings.png and /dev/null differ diff --git a/app/assets/images/logo.png b/app/assets/images/logo.png deleted file mode 100755 index 28b9079..0000000 Binary files a/app/assets/images/logo.png and /dev/null differ diff --git a/app/assets/images/rails.png b/app/assets/images/rails.png deleted file mode 100755 index d5edc04..0000000 Binary files a/app/assets/images/rails.png and /dev/null differ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js deleted file mode 100755 index 9097d83..0000000 --- a/app/assets/javascripts/application.js +++ /dev/null @@ -1,15 +0,0 @@ -// This is a manifest file that'll be compiled into application.js, which will include all the files -// listed below. -// -// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, -// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. -// -// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the -// the compiled file. -// -// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD -// GO AFTER THE REQUIRES BELOW. -// -//= require jquery -//= require jquery_ujs -//= require_tree . diff --git a/app/assets/javascripts/bootstrap.min.js b/app/assets/javascripts/bootstrap.min.js deleted file mode 100755 index 97dc88e..0000000 --- a/app/assets/javascripts/bootstrap.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(a){a(function(){"use strict",a.support.transition=function(){var b=document.body||document.documentElement,c=b.style,d=c.transition!==undefined||c.WebkitTransition!==undefined||c.MozTransition!==undefined||c.MsTransition!==undefined||c.OTransition!==undefined;return d&&{end:function(){var b="TransitionEnd";return a.browser.webkit?b="webkitTransitionEnd":a.browser.mozilla?b="transitionend":a.browser.opera&&(b="oTransitionEnd"),b}()}}()})}(window.jQuery),!function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype={constructor:c,close:function(b){function f(){e.trigger("closed").remove()}var c=a(this),d=c.attr("data-target"),e;d||(d=c.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),e=a(d),e.trigger("close"),b&&b.preventDefault(),e.length||(e=c.hasClass("alert")?c:c.parent()),e.trigger("close").removeClass("in"),a.support.transition&&e.hasClass("fade")?e.on(a.support.transition.end,f):f()}},a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("alert");e||d.data("alert",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.alert.Constructor=c,a(function(){a("body").on("click.alert.data-api",b,c.prototype.close)})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.button.defaults,c)};b.prototype={constructor:b,setState:function(a){var b="disabled",c=this.$element,d=c.data(),e=c.is("input")?"val":"html";a+="Text",d.resetText||c.data("resetText",c[e]()),c[e](d[a]||this.options[a]),setTimeout(function(){a=="loadingText"?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},toggle:function(){var a=this.$element.parent('[data-toggle="buttons-radio"]');a&&a.find(".active").removeClass("active"),this.$element.toggleClass("active")}},a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("button"),f=typeof c=="object"&&c;e||d.data("button",e=new b(this,f)),c=="toggle"?e.toggle():c&&e.setState(c)})},a.fn.button.defaults={loadingText:"loading..."},a.fn.button.Constructor=b,a(function(){a("body").on("click.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle")})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.carousel.defaults,c),this.options.slide&&this.slide(this.options.slide)};b.prototype={cycle:function(){return this.interval=setInterval(a.proxy(this.next,this),this.options.interval),this},to:function(b){var c=this.$element.find(".active"),d=c.parent().children(),e=d.index(c),f=this;if(b>d.length-1||b<0)return;return this.sliding?this.$element.one("slid",function(){f.to(b)}):e==b?this.pause().cycle():this.slide(b>e?"next":"prev",a(d[b]))},pause:function(){return clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(b,c){var d=this.$element.find(".active"),e=c||d[b](),f=this.interval,g=b=="next"?"left":"right",h=b=="next"?"first":"last",i=this;if(!e.length)return;return this.sliding=!0,f&&this.pause(),e=e.length?e:this.$element.find(".item")[h](),!a.support.transition&&this.$element.hasClass("slide")?(this.$element.trigger("slide"),d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")):(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),this.$element.trigger("slide"),this.$element.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)})),f&&this.cycle(),this}},a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("carousel"),f=typeof c=="object"&&c;e||d.data("carousel",e=new b(this,f)),typeof c=="number"?e.to(c):typeof c=="string"||(c=f.slide)?e[c]():e.cycle()})},a.fn.carousel.defaults={interval:5e3},a.fn.carousel.Constructor=b,a(function(){a("body").on("click.carousel.data-api","[data-slide]",function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=!e.data("modal")&&a.extend({},e.data(),c.data());e.carousel(f),b.preventDefault()})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.collapse.defaults,c),this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.prototype={constructor:b,dimension:function(){var a=this.$element.hasClass("width");return a?"width":"height"},show:function(){var b=this.dimension(),c=a.camelCase(["scroll",b].join("-")),d=this.$parent&&this.$parent.find(".in"),e;d&&d.length&&(e=d.data("collapse"),d.collapse("hide"),e||d.data("collapse",null)),this.$element[b](0),this.transition("addClass","show","shown"),this.$element[b](this.$element[0][c])},hide:function(){var a=this.dimension();this.reset(this.$element[a]()),this.transition("removeClass","hide","hidden"),this.$element[a](0)},reset:function(a){var b=this.dimension();this.$element.removeClass("collapse")[b](a||"auto")[0].offsetWidth,this.$element.addClass("collapse")},transition:function(b,c,d){var e=this,f=function(){c=="show"&&e.reset(),e.$element.trigger(d)};this.$element.trigger(c)[b]("in"),a.support.transition&&this.$element.hasClass("collapse")?this.$element.one(a.support.transition.end,f):f()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("collapse"),f=typeof c=="object"&&c;e||d.data("collapse",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.collapse.defaults={toggle:!0},a.fn.collapse.Constructor=b,a(function(){a("body").on("click.collapse.data-api","[data-toggle=collapse]",function(b){var c=a(this),d,e=c.attr("data-target")||b.preventDefault()||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),f=a(e).data("collapse")?"toggle":c.data();a(e).collapse(f)})})}(window.jQuery),!function(a){function d(){a(b).parent().removeClass("open")}"use strict";var b='[data-toggle="dropdown"]',c=function(b){var c=a(b).on("click.dropdown.data-api",this.toggle);a("html").on("click.dropdown.data-api",function(){c.parent().removeClass("open")})};c.prototype={constructor:c,toggle:function(b){var c=a(this),e=c.attr("data-target"),f,g;return e||(e=c.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,"")),f=a(e),f.length||(f=c.parent()),g=f.hasClass("open"),d(),!g&&f.toggleClass("open"),!1}},a.fn.dropdown=function(b){return this.each(function(){var d=a(this),e=d.data("dropdown");e||d.data("dropdown",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.dropdown.Constructor=c,a(function(){a("html").on("click.dropdown.data-api",d),a("body").on("click.dropdown.data-api",b,c.prototype.toggle)})}(window.jQuery),!function(a){function c(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),d.call(b)},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),d.call(b)})}function d(a){this.$element.hide().trigger("hidden"),e.call(this)}function e(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a(''; + + div.innerHTML = html; + + document.body.appendChild(div); + + tinyMCE._currentDialog = id; + } + /*}*/ + } +}; + +TinyMCE.prototype.closeDialog = function() { + // Remove div or close window + if (tinyMCE.settings["dialog_type"] == "div") { + var div = document.getElementById(tinyMCE._currentDialog); + if (div) + div.parentNode.removeChild(div); + } else + window.close(); +}; + +TinyMCE.prototype.getVisualAidClass = function(class_name, state) { + var aidClass = tinyMCE.settings['visual_table_class']; + + if (typeof(state) == "undefined") + state = tinyMCE.settings['visual']; + + // Split + var classNames = new Array(); + var ar = class_name.split(' '); + for (var i=0; i 0) + className += " "; + + className += classNames[i]; + } + + return className; +}; + +TinyMCE.prototype.handleVisualAid = function(element, deep, state) { + if (!element) + return; + + var tableElement = null; + + switch (element.nodeName.toLowerCase()) { + case "table": + var oldW = element.style.width; + var oldH = element.style.height; + + element.className = tinyMCE.getVisualAidClass(element.className, state && element.getAttribute("border") == 0); + + element.style.width = oldW; + element.style.height = oldH; + + for (var y=0; y'; + return; + } + + break;*/ + } + + if (deep && element.hasChildNodes()) { + for (var i=0; i

breaks runtime? + if (tinyMCE.isMSIE) { + var re = new RegExp('


', 'g'); + html_content = html_content.replace(re, "
"); + } + + doc.body.innerHTML = html_content; + + // Content duplication bug fix + if (tinyMCE.isMSIE && tinyMCE.settings['fix_content_duplication']) { + // Remove P elements in P elements + var paras = doc.getElementsByTagName("P"); + for (var i=0; i<\/o:p>", "
"); + html = tinyMCE.regexpReplace(html, " <\/o:p>", ""); + html = tinyMCE.regexpReplace(html, "", ""); + html = tinyMCE.regexpReplace(html, "

<\/p>", ""); + html = tinyMCE.regexpReplace(html, "

<\/p>\r\n

<\/p>", ""); + html = tinyMCE.regexpReplace(html, "

 <\/p>", "
"); + html = tinyMCE.regexpReplace(html, "

\s*(

\s*)?", "

"); + html = tinyMCE.regexpReplace(html, "<\/p>\s*(<\/p>\s*)?", "

"); + } + + // Always set the htmlText output + doc.body.innerHTML = html; + } +}; + +TinyMCE.prototype.getImageSrc = function(str) { + var pos = -1; + + if (!str) + return ""; + + if ((pos = str.indexOf('this.src=')) != -1) { + var src = str.substring(pos + 10); + + src = src.substring(0, src.indexOf('\'')); + + return src; + } + + return ""; +}; + +TinyMCE.prototype._getElementById = function(element_id) { + var elm = document.getElementById(element_id); + if (!elm) { + // Check for element in forms + for (var j=0; j 0) { + var csses = null; + + // Just ignore any errors + eval("try {var csses = tinyMCE.isMSIE ? doc.styleSheets(0).rules : doc.styleSheets[0].cssRules;} catch(e) {}"); + if (!csses) + return new Array(); + + for (var i=0; i 0) + tinyMCE.cssClasses = output; + + return output; +}; + +TinyMCE.prototype.regexpReplace = function(in_str, reg_exp, replace_str, opts) { + if (typeof(opts) == "undefined") + opts = 'g'; + + var re = new RegExp(reg_exp, opts); + return in_str.replace(re, replace_str); +}; + +TinyMCE.prototype.cleanupEventStr = function(str) { + str = "" + str; + str = str.replace('function anonymous()\n{\n', ''); + str = str.replace('\n}', ''); + + return str; +}; + +TinyMCE.prototype.getAbsPosition = function(node) { + var pos = new Object(); + + pos.absLeft = pos.absTop = 0; + + var parentNode = node; + while (parentNode) { + pos.absLeft += parentNode.offsetLeft; + pos.absTop += parentNode.offsetTop; + + parentNode = parentNode.offsetParent; + } + + return pos; +}; + +TinyMCE.prototype.openFileBrowser = function(field_name, url, type, win) { + var cb = tinyMCE.getParam("file_browser_callback"); + + this.setWindowArg("window", win); + + // Call to external callback + if(eval('typeof('+cb+')') == "undefined") + alert("Callback function: " + cb + " could not be found."); + else + eval(cb + "(field_name, url, type, win);"); +}; + +TinyMCE.prototype.getControlHTML = function(control_name) { + var themePlugins = tinyMCE.getParam('plugins', '', true, ','); + var templateFunction; + + // Is it defined in any plugins + for (var i=themePlugins.length; i>=0; i--) { + templateFunction = 'TinyMCE_' + themePlugins[i] + "_getControlHTML"; + if (eval("typeof(" + templateFunction + ")") != 'undefined') { + var html = eval(templateFunction + "('" + control_name + "');"); + if (html != "") + return tinyMCE.replaceVar(html, "pluginurl", tinyMCE.baseURL + "/plugins/" + themePlugins[i]); + } + } + + return eval('TinyMCE_' + tinyMCE.settings['theme'] + "_getControlHTML" + "('" + control_name + "');"); +}; + +TinyMCE.prototype._themeExecCommand = function(editor_id, element, command, user_interface, value) { + var themePlugins = tinyMCE.getParam('plugins', '', true, ','); + var templateFunction; + + // Is it defined in any plugins + for (var i=themePlugins.length; i>=0; i--) { + templateFunction = 'TinyMCE_' + themePlugins[i] + "_execCommand"; + if (eval("typeof(" + templateFunction + ")") != 'undefined') { + if (eval(templateFunction + "(editor_id, element, command, user_interface, value);")) + return true; + } + } + + // Theme funtion + templateFunction = 'TinyMCE_' + tinyMCE.settings['theme'] + "_execCommand"; + if (eval("typeof(" + templateFunction + ")") != 'undefined') + return eval(templateFunction + "(editor_id, element, command, user_interface, value);"); + + // Pass to normal + return false; +}; + +TinyMCE.prototype._getThemeFunction = function(suffix, skip_plugins) { + if (skip_plugins) + return 'TinyMCE_' + tinyMCE.settings['theme'] + suffix; + + var themePlugins = tinyMCE.getParam('plugins', '', true, ','); + var templateFunction; + + // Is it defined in any plugins + for (var i=themePlugins.length; i>=0; i--) { + templateFunction = 'TinyMCE_' + themePlugins[i] + suffix; + if (eval("typeof(" + templateFunction + ")") != 'undefined') + return templateFunction; + } + + return 'TinyMCE_' + tinyMCE.settings['theme'] + suffix; +}; + + +TinyMCE.prototype.isFunc = function(func_name) { + if (func_name == null || func_name == "") + return false; + + return eval("typeof(" + func_name + ")") != "undefined"; +}; + +TinyMCE.prototype.exec = function(func_name, args) { + var str = func_name + '('; + + // Add all arguments + for (var i=3; i 1 && tinyMCE.currentConfig != this.settings['index']) { + tinyMCE.settings = this.settings; + tinyMCE.currentConfig = this.settings['index']; + } +}; + +TinyMCEControl.prototype.fixBrokenURLs = function() { + var body = this.getBody(); + + var elms = body.getElementsByTagName("img"); + for (var i=0; i 0) + rng.selectNodeContents(nodes[0]); + else + rng.selectNodeContents(node); + } else + rng.selectNode(node); + + if (collapse) { + // Special treatment of textnode collapse + if (!to_start && node.nodeType == 3) { + rng.setStart(node, node.nodeValue.length); + rng.setEnd(node, node.nodeValue.length); + } else + rng.collapse(to_start); + } + + sel.removeAllRanges(); + sel.addRange(rng); + } + + this.scrollToNode(node); + + // Set selected element + tinyMCE.selectedElement = null; + if (node.nodeType == 1) + tinyMCE.selectedElement = node; +}; + +TinyMCEControl.prototype.scrollToNode = function(node) { + // Scroll to node position + var pos = tinyMCE.getAbsPosition(node); + var doc = this.getDoc(); + var scrollX = doc.body.scrollLeft + doc.documentElement.scrollLeft; + var scrollY = doc.body.scrollTop + doc.documentElement.scrollTop; + var height = tinyMCE.isMSIE ? document.getElementById(this.editorId).style.pixelHeight : this.targetElement.clientHeight; + + // Only scroll if out of visible area + if (!tinyMCE.settings['auto_resize'] && !(node.absTop > scrollY && node.absTop < (scrollY - 25 + height))) + this.contentWindow.scrollTo(pos.absLeft, pos.absTop - height + 25); +}; + +TinyMCEControl.prototype.getBody = function() { + return this.getDoc().body; +}; + +TinyMCEControl.prototype.getDoc = function() { + return this.contentWindow.document; +}; + +TinyMCEControl.prototype.getWin = function() { + return this.contentWindow; +}; + +TinyMCEControl.prototype.getSel = function() { + if (tinyMCE.isMSIE) + return this.getDoc().selection; + + var sel = this.contentWindow.getSelection(); + + // Fake getRangeAt + if (tinyMCE.isSafari && !sel.getRangeAt) { + var newSel = new Object(); + var doc = this.getDoc(); + + function getRangeAt(idx) { + var rng = new Object(); + + rng.startContainer = this.focusNode; + rng.endContainer = this.anchorNode; + rng.commonAncestorContainer = this.focusNode; + rng.createContextualFragment = function (html) { + // Seems to be a tag + if (html.charAt(0) == '<') { + var elm = doc.createElement("div"); + + elm.innerHTML = html; + + return elm.firstChild; + } + + return doc.createTextNode("UNSUPPORTED, DUE TO LIMITATIONS IN SAFARI!"); + }; + + rng.deleteContents = function () { + doc.execCommand("Delete", false, ""); + }; + + return rng; + } + + // Patch selection + + newSel.focusNode = sel.baseNode; + newSel.focusOffset = sel.baseOffset; + newSel.anchorNode = sel.extentNode; + newSel.anchorOffset = sel.extentOffset; + newSel.getRangeAt = getRangeAt; + newSel.text = "" + sel; + newSel.realSelection = sel; + + newSel.toString = function () {return this.text;}; + + return newSel; + } + + return sel; +}; + +TinyMCEControl.prototype.getRng = function() { + var sel = this.getSel(); + if (sel == null) + return null; + + if (tinyMCE.isMSIE) + return sel.createRange(); + + return this.getSel().getRangeAt(0); +}; + +TinyMCEControl.prototype._insertPara = function(e) { + function isEmpty(para) { + function isEmptyHTML(html) { + return html.replace(new RegExp('[ \t\r\n]+', 'g'), '').toLowerCase() == ""; + } + + // Check for images + if (para.getElementsByTagName("img").length > 0) + return false; + + // Check for tables + if (para.getElementsByTagName("table").length > 0) + return false; + + // Check for HRs + if (para.getElementsByTagName("hr").length > 0) + return false; + + // Check all textnodes + var nodes = tinyMCE.getNodeTree(para, new Array(), 3); + for (var i=0; i <" + blockName + "> "; + paraAfter = body.childNodes[1]; + } + + this.selectNode(paraAfter, true, true); + + return true; + } + + // Place first part within new paragraph + if (startChop.nodeName == blockName) + rngBefore.setStart(startChop, 0); + else + rngBefore.setStartBefore(startChop); + rngBefore.setEnd(startNode, startOffset); + paraBefore.appendChild(rngBefore.cloneContents()); + + // Place secound part within new paragraph + rngAfter.setEndAfter(endChop); + rngAfter.setStart(endNode, endOffset); + var contents = rngAfter.cloneContents(); + if (contents.firstChild && contents.firstChild.nodeName == blockName) { + var nodes = contents.firstChild.childNodes; + for (var i=0; i 0) + rng.pasteHTML('
' + rng.htmlText + "
"); + + tinyMCE.triggerNodeChange(); + return; + } + } + } + + switch (command) { + case "mceSelectNode": + this.selectNode(value); + tinyMCE.triggerNodeChange(); + tinyMCE.selectedNode = value; + break; + + case "FormatBlock": + if (value == null || value == "") { + var elm = tinyMCE.getParentElement(this.getFocusElement(), "p,div,h1,h2,h3,h4,h5,h6,pre,address"); + + if (elm) + this.execCommand("mceRemoveNode", false, elm); + } else + this.getDoc().execCommand("FormatBlock", false, value); + + tinyMCE.triggerNodeChange(); + + break; + + case "mceRemoveNode": + if (!value) + value = tinyMCE.getParentElement(this.getFocusElement()); + + if (tinyMCE.isMSIE) { + value.outerHTML = value.innerHTML; + } else { + var rng = value.ownerDocument.createRange(); + rng.setStartBefore(value); + rng.setEndAfter(value); + rng.deleteContents(); + rng.insertNode(rng.createContextualFragment(value.innerHTML)); + } + + tinyMCE.triggerNodeChange(); + + break; + + case "mceSelectNodeDepth": + var parentNode = this.getFocusElement(); + for (var i=0; parentNode; i++) { + if (parentNode.nodeName.toLowerCase() == "body") + break; + + if (parentNode.nodeName.toLowerCase() == "#text") { + i--; + parentNode = parentNode.parentNode; + continue; + } + + if (i == value) { + this.selectNode(parentNode, false); + tinyMCE.triggerNodeChange(); + tinyMCE.selectedNode = parentNode; + return; + } + + parentNode = parentNode.parentNode; + } + + break; + + case "HiliteColor": + if (tinyMCE.isGecko) { + this.getDoc().execCommand("useCSS", false, false); + this.getDoc().execCommand('hilitecolor', false, value); + this.getDoc().execCommand("useCSS", false, true); + } else + this.getDoc().execCommand('BackColor', false, value); + + break; + + case "Cut": + case "Copy": + case "Paste": + var cmdFailed = false; + + // Try executing command + eval('try {this.getDoc().execCommand(command, user_interface, value);} catch (e) {cmdFailed = true;}'); + + // Alert error in gecko if command failed + if (tinyMCE.isGecko && cmdFailed) { + // Confirm more info + if (confirm(tinyMCE.getLang('lang_clipboard_msg'))) + window.open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', 'mceExternal'); + + return; + } else + tinyMCE.triggerNodeChange(); + break; + + case "mceSetContent": + if (!value) + value = ""; + + // Call custom cleanup code + value = tinyMCE._customCleanup("insert_to_editor", value); + tinyMCE._setHTML(doc, value); + doc.body.innerHTML = tinyMCE._cleanupHTML(doc, tinyMCE.settings, doc.body); + tinyMCE.handleVisualAid(doc.body, true, this.visualAid); + return true; + + case "mceLink": + var selectedText = ""; + + if (tinyMCE.isMSIE) { + var rng = doc.selection.createRange(); + selectedText = rng.text; + } else + selectedText = this.getSel().toString(); + + if (!tinyMCE.linkElement) { + if ((tinyMCE.selectedElement.nodeName.toLowerCase() != "img") && (selectedText.length <= 0)) + return; + } + + var href = "", target = "", title = "", onclick = "", action = "insert", style_class = ""; + + if (tinyMCE.selectedElement.nodeName.toLowerCase() == "a") + tinyMCE.linkElement = tinyMCE.selectedElement; + + // Is anchor not a link + if (tinyMCE.linkElement != null && tinyMCE.getAttrib(tinyMCE.linkElement, 'href') == "") + tinyMCE.linkElement = null; + + if (tinyMCE.linkElement) { + href = tinyMCE.getAttrib(tinyMCE.linkElement, 'href'); + target = tinyMCE.getAttrib(tinyMCE.linkElement, 'target'); + title = tinyMCE.getAttrib(tinyMCE.linkElement, 'title'); + onclick = tinyMCE.getAttrib(tinyMCE.linkElement, 'mce_onclick'); + style_class = tinyMCE.getAttrib(tinyMCE.linkElement, 'class'); + + // Try old onclick to if copy/pasted content + if (onclick == "") + onclick = tinyMCE.getAttrib(tinyMCE.linkElement, 'onclick'); + + onclick = tinyMCE.cleanupEventStr(onclick); + + // Fix for drag-drop/copy paste bug in Mozilla + mceRealHref = tinyMCE.getAttrib(tinyMCE.linkElement, 'mce_real_href'); + if (mceRealHref != "") + href = mceRealHref; + + href = eval(tinyMCE.settings['urlconverter_callback'] + "(href, tinyMCE.linkElement, true);"); + action = "update"; + } + + if (this.settings['insertlink_callback']) { + var returnVal = eval(this.settings['insertlink_callback'] + "(href, target, title, onclick, action, style_class);"); + if (returnVal && returnVal['href']) + tinyMCE.insertLink(returnVal['href'], returnVal['target'], returnVal['title'], returnVal['onclick'], returnVal['style_class']); + } else { + tinyMCE.openWindow(this.insertLinkTemplate, {href : href, target : target, title : title, onclick : onclick, action : action, className : style_class}); + } + break; + + case "mceAttachment": + var selectedText = ""; + + if (tinyMCE.isMSIE) { + var rng = doc.selection.createRange(); + selectedText = rng.text; + } else + selectedText = this.getSel().toString(); + + if (!tinyMCE.linkElement) { + if ((tinyMCE.selectedElement.nodeName.toLowerCase() != "img") && (selectedText.length <= 0)) + return; + } + + var href = "", target = "", title = "", onclick = "", action = "insert"; + + if (tinyMCE.selectedElement.nodeName.toLowerCase() == "a") + tinyMCE.linkElement = tinyMCE.selectedElement; + + // Is anchor not a link + if (tinyMCE.linkElement != null && tinyMCE.getAttrib(tinyMCE.linkElement, 'href') == "") + tinyMCE.linkElement = null; + + if (tinyMCE.linkElement) { + href = tinyMCE.getAttrib(tinyMCE.linkElement, 'href'); + target = tinyMCE.getAttrib(tinyMCE.linkElement, 'target'); + title = tinyMCE.getAttrib(tinyMCE.linkElement, 'title'); + onclick = tinyMCE.getAttrib(tinyMCE.linkElement, 'mce_onclick'); + + // Try old onclick to if copy/pasted content + if (onclick == "") + onclick = tinyMCE.getAttrib(tinyMCE.linkElement, 'onclick'); + + onclick = tinyMCE.cleanupEventStr(onclick); + + // Fix for drag-drop/copy paste bug in Mozilla + mceRealHref = tinyMCE.getAttrib(tinyMCE.linkElement, 'mce_real_href'); + if (mceRealHref != "") + href = mceRealHref; + + href = eval(tinyMCE.settings['urlconverter_callback'] + "(href, tinyMCE.linkElement, true);"); + action = "update"; + } + + if (this.settings['insertlink_callback']) { + var returnVal = eval(this.settings['insertlink_callback'] + "(href, target, title, onclick, action);"); + if (returnVal && returnVal['href']) + tinyMCE.insertLink(returnVal['href'], returnVal['target'], returnVal['title'], returnVal['onclick']); + } else { + tinyMCE.openWindow(this.insertAttachmentTemplate, {href : href, target : target, title : title, onclick : onclick, action : action}); + } + break; + + case "mceImage": + var src = "", alt = "", border = "", hspace = "", vspace = "", width = "", height = "", align = ""; + var title = "", onmouseover = "", onmouseout = "", action = "insert"; + var img = tinyMCE.imgElement; + + if (tinyMCE.selectedElement != null && tinyMCE.selectedElement.nodeName.toLowerCase() == "img") { + img = tinyMCE.selectedElement; + tinyMCE.imgElement = img; + } + + if (img) { + // Is it a internal MCE visual aid image, then skip this one. + if (tinyMCE.getAttrib(img, 'name').indexOf('mce_') == 0) + return; + + src = tinyMCE.getAttrib(img, 'src'); + alt = tinyMCE.getAttrib(img, 'alt'); + + // Try polling out the title + if (alt == "") + alt = tinyMCE.getAttrib(img, 'title'); + + // Fix width/height attributes if the styles is specified + if (tinyMCE.isGecko) { + var w = img.style.width; + if (w != null && w != "") + img.setAttribute("width", w); + + var h = img.style.height; + if (h != null && h != "") + img.setAttribute("height", h); + } + + border = tinyMCE.getAttrib(img, 'border'); + hspace = tinyMCE.getAttrib(img, 'hspace'); + vspace = tinyMCE.getAttrib(img, 'vspace'); + width = tinyMCE.getAttrib(img, 'width'); + height = tinyMCE.getAttrib(img, 'height'); + align = tinyMCE.getAttrib(img, 'align'); + onmouseover = tinyMCE.getAttrib(img, 'onmouseover'); + onmouseout = tinyMCE.getAttrib(img, 'onmouseout'); + title = tinyMCE.getAttrib(img, 'title'); + + // Is realy specified? + if (tinyMCE.isMSIE) { + width = img.attributes['width'].specified ? width : ""; + height = img.attributes['height'].specified ? height : ""; + } + + onmouseover = tinyMCE.getImageSrc(tinyMCE.cleanupEventStr(onmouseover)); + onmouseout = tinyMCE.getImageSrc(tinyMCE.cleanupEventStr(onmouseout)); + + // Fix for drag-drop/copy paste bug in Mozilla + mceRealSrc = tinyMCE.getAttrib(img, 'mce_real_src'); + if (mceRealSrc != "") + src = mceRealSrc; + + src = eval(tinyMCE.settings['urlconverter_callback'] + "(src, img, true);"); + + if (onmouseover != "") + onmouseover = eval(tinyMCE.settings['urlconverter_callback'] + "(onmouseover, img, true);"); + + if (onmouseout != "") + onmouseout = eval(tinyMCE.settings['urlconverter_callback'] + "(onmouseout, img, true);"); + + action = "update"; + } + + if (this.settings['insertimage_callback']) { + var returnVal = eval(this.settings['insertimage_callback'] + "(src, alt, border, hspace, vspace, width, height, align, title, onmouseover, onmouseout, action);"); + if (returnVal && returnVal['src']) + tinyMCE.insertImage(returnVal['src'], returnVal['alt'], returnVal['border'], returnVal['hspace'], returnVal['vspace'], returnVal['width'], returnVal['height'], returnVal['align'], returnVal['title'], returnVal['onmouseover'], returnVal['onmouseout']); + } else + tinyMCE.openWindow(this.insertImageTemplate, {src : src, alt : alt, border : border, hspace : hspace, vspace : vspace, width : width, height : height, align : align, title : title, onmouseover : onmouseover, onmouseout : onmouseout, action : action}); + break; + + case "mceCleanupWord": + if (tinyMCE.isMSIE) { + var html = this.getBody().createTextRange().htmlText; + + if (html.indexOf('="mso') != -1) { + tinyMCE._setHTML(this.contentDocument, this.getBody().innerHTML); + html = tinyMCE._cleanupHTML(this.contentDocument, this.settings, this.getBody(), this.visualAid); + } + + this.getBody().innerHTML = html; + } + break; + + case "mceCleanup": + tinyMCE._setHTML(this.contentDocument, this.getBody().innerHTML); + this.getBody().innerHTML = tinyMCE._cleanupHTML(this.contentDocument, this.settings, this.getBody(), this.visualAid); + tinyMCE.handleVisualAid(this.getBody(), true, this.visualAid); + this.repaint(); + tinyMCE.triggerNodeChange(); + break; + + case "mceAnchor": + if (!user_interface) { + var aElm = tinyMCE.getParentElement(this.getFocusElement(), "a", "name"); + if (aElm) { + if (value == null || value == "") { + if (tinyMCE.isMSIE) { + aElm.outerHTML = aElm.innerHTML; + } else { + var rng = aElm.ownerDocument.createRange(); + rng.setStartBefore(aElm); + rng.setEndAfter(aElm); + rng.deleteContents(); + rng.insertNode(rng.createContextualFragment(aElm.innerHTML)); + } + } else + aElm.setAttribute('name', value); + } else { + this.getDoc().execCommand("fontname", false, "#mce_temp_font#"); + var elementArray = tinyMCE.getElementsByAttributeValue(this.getBody(), "font", "face", "#mce_temp_font#"); + for (var x=0; x 0) { + value = tinyMCE.replaceVar(value, "selection", selectedText); + tinyMCE.execCommand('mceInsertContent', false, value); + } + + tinyMCE.triggerNodeChange(); + break; + + case "mceSetAttribute": + if (typeof(value) == 'object') { + var targetElms = (typeof(value['targets']) == "undefined") ? "p,img,span,div,td,h1,h2,h3,h4,h5,h6,pre,address" : value['targets']; + var targetNode = tinyMCE.getParentElement(this.getFocusElement(), targetElms); + + if (targetNode) { + targetNode.setAttribute(value['name'], value['value']); + tinyMCE.triggerNodeChange(); + } + } + break; + + case "mceSetCSSClass": + var selectedText = false; + + if (tinyMCE.isMSIE) { + var rng = doc.selection.createRange(); + selectedText = (rng.text && rng.text.length > 0); + } else + selectedText = (this.getSel().toString().length > 0); + + // Use selectedNode instead if defined + if (tinyMCE.selectedNode) + tinyMCE.selectedElement = tinyMCE.selectedNode; + + if (selectedText && !tinyMCE.selectedNode) { + this.getDoc().execCommand("RemoveFormat", false, null); + if (value == null) + return this.execCommand("RemoveFormat", false, null); + + this.getDoc().execCommand("fontname", false, "#mce_temp_font#"); + var elementArray = tinyMCE.getElementsByAttributeValue(this.getBody(), "font", "face", "#mce_temp_font#"); + + // Change them all + for (var x=0; x customUndoLevels) { + for (var i=0; i 0) { + this.undoIndex--; + this.getBody().innerHTML = this.undoLevels[this.undoIndex]; + } + + // debug("Undo - undo levels:" + this.undoLevels.length + ", undo index: " + this.undoIndex); + tinyMCE.triggerNodeChange(); + } else + this.getDoc().execCommand(command, user_interface, value); + break; + + case "Redo": + if (tinyMCE.settings['custom_undo_redo']) { + if (this.undoIndex < (this.undoLevels.length-1)) { + this.undoIndex++; + this.getBody().innerHTML = this.undoLevels[this.undoIndex]; + // debug("Redo - undo levels:" + this.undoLevels.length + ", undo index: " + this.undoIndex); + } + + tinyMCE.triggerNodeChange(); + } else + this.getDoc().execCommand(command, user_interface, value); + break; + + case "mceToggleVisualAid": + this.visualAid = !this.visualAid; + tinyMCE.handleVisualAid(this.getBody(), true, this.visualAid); + tinyMCE.triggerNodeChange(); + break; + + case "removeformat": + var text = this.getSelectedText(); + + if (tinyMCE.isMSIE) { + try { + win.focus(); + var rng = doc.selection.createRange(); + rng.execCommand("RemoveFormat", false, null); + rng.pasteHTML(rng.text); + } catch (e) { + // Do nothing + } + } else + this.getDoc().execCommand(command, user_interface, value); + + // Remove class + if (text.length == 0) + this.execCommand("mceSetCSSClass", false, ""); + + tinyMCE.triggerNodeChange(); + break; + + default: + this.getDoc().execCommand(command, user_interface, value); + tinyMCE.triggerNodeChange(); + } +}; + +TinyMCEControl.prototype.queryCommandValue = function(command) { + return this.getDoc().queryCommandValue(command); +}; + +TinyMCEControl.prototype.queryCommandState = function(command) { + return this.getDoc().queryCommandState(command); +}; + +TinyMCEControl.prototype.onAdd = function(replace_element, form_element_name, target_document) { + var targetDoc = target_document ? target_document : document; + + this.targetDoc = targetDoc; + + tinyMCE.themeURL = tinyMCE.baseURL + "/themes/" + this.settings['theme']; + this.settings['themeurl'] = tinyMCE.themeURL; + + if (!replace_element) { + alert("Error: Could not find the target element."); + return false; + } + + var templateFunction = tinyMCE._getThemeFunction('_getInsertLinkTemplate'); + if (eval("typeof(" + templateFunction + ")") != 'undefined') + this.insertLinkTemplate = eval(templateFunction + '(this.settings);'); + + var templateFunction = tinyMCE._getThemeFunction('_getInsertAttachmentTemplate'); + if (eval("typeof(" + templateFunction + ")") != 'undefined') + this.insertAttachmentTemplate = eval(templateFunction + '(this.settings);'); + + var templateFunction = tinyMCE._getThemeFunction('_getInsertImageTemplate'); + if (eval("typeof(" + templateFunction + ")") != 'undefined') + this.insertImageTemplate = eval(templateFunction + '(this.settings);'); + + var templateFunction = tinyMCE._getThemeFunction('_getEditorTemplate'); + if (eval("typeof(" + templateFunction + ")") == 'undefined') { + alert("Error: Could not find the template function: " + templateFunction); + return false; + } + + var editorTemplate = eval(templateFunction + '(this.settings, this.editorId);'); + + var deltaWidth = editorTemplate['delta_width'] ? editorTemplate['delta_width'] : 0; + var deltaHeight = editorTemplate['delta_height'] ? editorTemplate['delta_height'] : 0; + var html = '' + editorTemplate['html']; + + var templateFunction = tinyMCE._getThemeFunction('_handleNodeChange', true); + if (eval("typeof(" + templateFunction + ")") != 'undefined') + this.settings['handleNodeChangeCallback'] = templateFunction; + + html = tinyMCE.replaceVar(html, "editor_id", this.editorId); + html = tinyMCE.replaceVar(html, "default_document", tinyMCE.baseURL + "/blank.htm"); + this.settings['default_document'] = tinyMCE.baseURL + "/blank.htm"; + + this.settings['old_width'] = this.settings['width']; + this.settings['old_height'] = this.settings['height']; + + // Set default width, height + if (this.settings['width'] == -1) + this.settings['width'] = replace_element.offsetWidth; + + if (this.settings['height'] == -1) + this.settings['height'] = replace_element.offsetHeight; + + // Try the style width + if (this.settings['width'] == 0) + this.settings['width'] = replace_element.style.width; + + // Try the style height + if (this.settings['height'] == 0) + this.settings['height'] = replace_element.style.height; + + // If no width/height then default to 320x240, better than nothing + if (this.settings['width'] == 0) + this.settings['width'] = 320; + + if (this.settings['height'] == 0) + this.settings['height'] = 240; + + this.settings['area_width'] = parseInt(this.settings['width']); + this.settings['area_height'] = parseInt(this.settings['height']); + this.settings['area_width'] += deltaWidth; + this.settings['area_height'] += deltaHeight; + + // Special % handling + if (("" + this.settings['width']).indexOf('%') != -1) + this.settings['area_width'] = "100%"; + + if (("" + this.settings['height']).indexOf('%') != -1) + this.settings['area_height'] = "100%"; + + if (("" + replace_element.style.width).indexOf('%') != -1) { + this.settings['width'] = replace_element.style.width; + this.settings['area_width'] = "100%"; + } + + if (("" + replace_element.style.height).indexOf('%') != -1) { + this.settings['height'] = replace_element.style.height; + this.settings['area_height'] = "100%"; + } + + html = tinyMCE.applyTemplate(html); + + this.settings['width'] = this.settings['old_width']; + this.settings['height'] = this.settings['old_height']; + + this.visualAid = this.settings['visual']; + this.formTargetElementId = form_element_name; + + // Get replace_element contents + if (replace_element.nodeName.toLowerCase() == "textarea") + this.startContent = replace_element.value; + else + this.startContent = replace_element.innerHTML; + + // If not text area + if (replace_element.nodeName.toLowerCase() != "textarea") { + this.oldTargetElement = replace_element.cloneNode(true); + + // Debug mode + if (tinyMCE.settings['debug']) + html += ''; + else + html += ''; + + html += ''; + + // Output HTML and set editable + if (!tinyMCE.isMSIE) { + var rng = replace_element.ownerDocument.createRange(); + rng.setStartBefore(replace_element); + + var fragment = rng.createContextualFragment(html); + replace_element.parentNode.replaceChild(fragment, replace_element); + } else + replace_element.outerHTML = html; + } else { + html += ''; + + // Just hide the textarea element + this.oldTargetElement = replace_element; + + if (!tinyMCE.settings['debug']) + this.oldTargetElement.style.display = "none"; + + // Output HTML and set editable + if (!tinyMCE.isMSIE) { + var rng = replace_element.ownerDocument.createRange(); + rng.setStartBefore(replace_element); + + var fragment = rng.createContextualFragment(html); + replace_element.parentNode.insertBefore(fragment, replace_element); + } else + replace_element.insertAdjacentHTML("beforeBegin", html); + } + + // Setup iframe + var dynamicIFrame = false; + var tElm = targetDoc.getElementById(this.editorId); + + if (!tinyMCE.isMSIE) { + if (tElm && tElm.nodeName.toLowerCase() == "span") { + tElm = tinyMCE._createIFrame(tElm); + dynamicIFrame = true; + } + + this.targetElement = tElm; + this.iframeElement = tElm; + this.contentDocument = tElm.contentDocument; + this.contentWindow = tElm.contentWindow; + + //this.getDoc().designMode = "on"; + } else { + if (tElm && tElm.nodeName.toLowerCase() == "span") + tElm = tinyMCE._createIFrame(tElm); + else + tElm = targetDoc.frames[this.editorId]; + + this.targetElement = tElm; + this.iframeElement = targetDoc.getElementById(this.editorId); + this.contentDocument = tElm.window.document; + this.contentWindow = tElm.window; + this.getDoc().designMode = "on"; + } + + // Setup base HTML + var doc = this.contentDocument; + if (dynamicIFrame) { + var html = "" + + '' + + '' + + '' + + '' + + 'blank_page' + + '' + + '' + + '' + + '' + + ''; + + try { + this.getDoc().designMode = "on"; + doc.open(); + doc.write(html); + doc.close(); + } catch (e) { + // Failed Mozilla 1.3 + this.getDoc().location.href = tinyMCE.baseURL + "/blank.htm"; + } + } + + // This timeout is needed in MSIE 5.5 for some odd reason + // it seems that the document.frames isn't initialized yet? + if (tinyMCE.isMSIE) + window.setTimeout("TinyMCE.prototype.addEventHandlers('" + this.editorId + "');", 1); + + tinyMCE.setupContent(this.editorId, true); + + return true; +}; + +TinyMCEControl.prototype.getFocusElement = function() { + if (tinyMCE.isMSIE) { + var doc = this.getDoc(); + var rng = doc.selection.createRange(); + + if (rng.collapse) + rng.collapse(true); + + var elm = rng.item ? rng.item(0) : rng.parentElement(); + } else { + var sel = this.getSel(); + var elm = (sel && sel.anchorNode) ? sel.anchorNode : null; + + if (tinyMCE.selectedElement != null && tinyMCE.selectedElement.nodeName.toLowerCase() == "img") + elm = tinyMCE.selectedElement; + } + + return elm; +}; + +// Global instances +var tinyMCE = new TinyMCE(); +var tinyMCELang = new Array(); + +function debug() { + var msg = ""; + + var elm = document.getElementById("tinymce_debug"); + if (!elm) { + var debugDiv = document.createElement("div"); + debugDiv.setAttribute("className", "debugger"); + debugDiv.className = "debugger"; + debugDiv.innerHTML = '\ + Debug output:\ + '; + + document.body.appendChild(debugDiv); + elm = document.getElementById("tinymce_debug"); + } + + var args = this.debug.arguments; + for (var i=0; i 0 +end + +puts "Done - #{total_replacements} total replacements made." diff --git a/script/localize b/script/localize new file mode 100755 index 0000000..1757ecf --- /dev/null +++ b/script/localize @@ -0,0 +1,48 @@ +#!/usr/bin/env ruby + +# we need fileutils for easy file access +require 'fileutils' +require 'rubygems' +include FileUtils +# we will need the modified rgettext.rb that can read erb templates +require File.dirname(__FILE__) + '/rgettext' + + +# RAILS_ROOT is just one up +RAILS_ROOT = File.expand_path(File.dirname(__FILE__) + '/..') +# $DEBUG = true +# goto RAILS_ROOT so we can address all files relatively from there +Dir.chdir(RAILS_ROOT) + +# the potfile will hold the temporary data before it is merged; note the +# filename .messages.pot (if you don't prepend a dot to the filename Dir.glob +# will get confused later on) +potfile = "#{RAILS_ROOT}/locale/.messages.pot" + +# if the potfile exists from the previous run, delete it +rm_f potfile + +# directories and extensions to harvest +dirpattern = '{app,components,config,custom,lib}' +extpattern = 'r{b,html,xml}' +files = Dir.glob("#{dirpattern}/**/*.#{extpattern}") + +# run the harvester on the collected filenames and output to potfile +RGettext.new.start files, potfile + +# now iterate through all locale dirs and update/merge +Dir.glob('locale/*').each do |dir| + # check if every dir has a pofile to begin with, else msmerge will fail + # if not, use the potfile and don't merge + pofile = "#{RAILS_ROOT}/#{dir}/LC_MESSAGES/messages.po" + if File.exists?(pofile) + print "Updating pofile #{pofile} " + system "msgmerge --force-po --no-location --update #{pofile} #{potfile}" + else + print "The pofile '#{pofile}' does not exist. I will create it for you " + path_to_pofile = File.dirname(pofile) + mkdir path_to_pofile unless File.exists?(path_to_pofile) + cp potfile, pofile + puts ' .... done.' + end +end diff --git a/script/performance/benchmarker b/script/performance/benchmarker new file mode 100755 index 0000000..462cae1 --- /dev/null +++ b/script/performance/benchmarker @@ -0,0 +1,3 @@ +#!/usr/bin/ruby18 +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/benchmarker' diff --git a/script/performance/profiler b/script/performance/profiler new file mode 100755 index 0000000..8b3a633 --- /dev/null +++ b/script/performance/profiler @@ -0,0 +1,3 @@ +#!/usr/bin/ruby18 +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/profiler' diff --git a/script/performance/request b/script/performance/request new file mode 100755 index 0000000..ae3f38c --- /dev/null +++ b/script/performance/request @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/request' diff --git a/script/plugin b/script/plugin new file mode 100755 index 0000000..87cd207 --- /dev/null +++ b/script/plugin @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/plugin' diff --git a/script/process/inspector b/script/process/inspector new file mode 100755 index 0000000..bf25ad8 --- /dev/null +++ b/script/process/inspector @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/inspector' diff --git a/script/process/reaper b/script/process/reaper new file mode 100755 index 0000000..af0b34d --- /dev/null +++ b/script/process/reaper @@ -0,0 +1,3 @@ +#!/usr/bin/ruby18 +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/reaper' diff --git a/script/process/spawner b/script/process/spawner new file mode 100755 index 0000000..ffb55ae --- /dev/null +++ b/script/process/spawner @@ -0,0 +1,3 @@ +#!/usr/bin/ruby18 +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/spawner' diff --git a/script/process/spinner b/script/process/spinner new file mode 100755 index 0000000..c1ce730 --- /dev/null +++ b/script/process/spinner @@ -0,0 +1,3 @@ +#!/usr/bin/ruby18 +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/spinner' diff --git a/script/profiler b/script/profiler new file mode 100755 index 0000000..77c9fbe --- /dev/null +++ b/script/profiler @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +if ARGV.empty? + $stderr.puts "Usage: profiler 'Person.expensive_method(10)' [times]" + exit(1) +end + +# Keep the expensive require out of the profile. +$stderr.puts 'Loading Rails...' +require File.dirname(__FILE__) + '/../config/environment' + +# Define a method to profile. +if ARGV[1] and ARGV[1].to_i > 1 + eval "def profile_me() #{ARGV[1]}.times { #{ARGV[0]} } end" +else + eval "def profile_me() #{ARGV[0]} end" +end + +# Use the ruby-prof extension if available. Fall back to stdlib profiler. +begin + require 'prof' + $stderr.puts 'Using the ruby-prof extension.' + Prof.clock_mode = Prof::GETTIMEOFDAY + Prof.start + profile_me + results = Prof.stop + require 'rubyprof_ext' + Prof.print_profile(results, $stderr) +rescue LoadError + $stderr.puts 'Using the standard Ruby profiler.' + Profiler__.start_profile + profile_me + Profiler__.stop_profile + Profiler__.print_profile($stderr) +end diff --git a/script/rails b/script/rails deleted file mode 100755 index b97de07..0000000 --- a/script/rails +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env ruby18 -# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. - -APP_PATH = File.expand_path('../../config/application', __FILE__) -require File.expand_path('../../config/boot', __FILE__) -require 'rails/commands' diff --git a/script/rgettext.rb b/script/rgettext.rb new file mode 100755 index 0000000..3702f47 --- /dev/null +++ b/script/rgettext.rb @@ -0,0 +1,215 @@ +#!/usr/bin/env ruby +=begin + rgettext - ruby version of xgettext + Copyright (C) 2005 Sascha Ebach + Copyright (C) 2003,2004 Masao Mutoh + Copyright (C) 2001,2002 Yasushi Shoji, Masao Mutoh + + Yasushi Shoji + Masao Mutoh + Sascha Ebach + + You may redistribute it and/or modify it under the same + license terms as Ruby. + + 2005-03-12: Added support for eruby templates (Sascha Ebach) + 2005-03-20: Added second parameter to RGetext.start to allow different + output when not called from the command line. Pulled out + RGetext.set_output(). + +=end + +require 'gettext/parser/ruby' +require 'gettext/parser/glade' + +require 'getoptlong' +require 'gettext' + +require 'tempfile' +require 'erb' + +class RGettext + include GetText + + # constant values + VERSION = %w($Revision: 1.15 $)[1].scan(/\d+/).collect {|s| s.to_i} + DATE = %w($Date: 2004/11/05 18:19:08 $)[1] + MAX_LINE_LEN = 70 + + def start(files=ARGV, output = nil) + opt = check_options + opt['output'] = output unless output.nil? + set_output(opt) + + + if files.empty? + print_help + exit + end + + ary = [] + files.each do |file| + begin + $stderr.puts "Processing #{file}" + if glade_file?(file) + ary = GladeParser.parse(file, ary) + elsif erb_file?(file) + content = File.open(file, 'r') {|f| f.read } + tf = Tempfile.new('erb-gettext') + tf.puts ERB.new(content).src + tf.close + old_index = ary.size - 1 + ary = GetText::RubyParser.parse(tf.path, ary) + #replace tokens with /tmp/... with real file names + for i in old_index..ary.size-1 + for j in 0..ary[i].size-1 + ary[i][j] = ary[i][j].gsub("#{tf.path}", file) + end + end + tf.close true + else + ary = RubyParser.parse(file, ary) + end + rescue + puts $! + exit 1 + end + end + generate_pot_header + generate_pot(ary) + @out.close + end + + # following methods are + private + XML_RE = /<\?xml/ + GLADE_RE = /glade-2.0.dtd/ + + def erb_file?(file) + File.extname(file) == '.rhtml' + end + + def glade_file?(file) + data = IO.readlines(file) + if XML_RE =~ data[0] + if GLADE_RE =~ data[1] + return true + else + raise _("%s is not glade-2.0 format.") % [file] + end + else + return false + end + end + + def initialize + bindtextdomain("rgettext") + end + + def generate_pot_header + time = Time.now.strftime("%Y-%m-%d %H:%M%z") + @out << "# SOME DESCRIPTIVE TITLE.\n" + @out << "# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n" + @out << "# This file is distributed under the same license as the PACKAGE package.\n" + @out << "# FIRST AUTHOR , YEAR.\n" + @out << "#\n" + @out << "#, fuzzy\n" + @out << "msgid \"\"\n" + @out << "msgstr \"\"\n" + @out << "\"Project-Id-Version: PACKAGE VERSION\\n\"\n" + @out << "\"POT-Creation-Date: #{time}\\n\"\n" + @out << "\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n" + @out << "\"Last-Translator: FULL NAME \\n\"\n" + @out << "\"Language-Team: LANGUAGE \\n\"\n" + @out << "\"MIME-Version: 1.0\\n\"\n" + @out << "\"Content-Type: text/plain; charset=UTF-8\\n\"\n" + @out << "\"Content-Transfer-Encoding: 8bit\\n\"\n" + @out << "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n" + end + + def generate_pot(ary) + result = Array.new + ary.each do |key| + msgid = key.shift + curr_pos = MAX_LINE_LEN + key.each do |e| + if curr_pos + e.size > MAX_LINE_LEN + @out << "\n#:" + curr_pos = 3 + else + curr_pos += (e.size + 1) + end + @out << " " << e + end + msgid.gsub!(/"/, '\"') + msgid.gsub!(/\r/, '') + if msgid.include?("\000") + ids = msgid.split(/\000/) + @out << "\nmsgid \"" << ids[0] << "\"\n" + @out << "msgid_plural \"" << ids[1] << "\"\n" + @out << "msgstr[0] \"\"\n" + @out << "msgstr[1] \"\"\n" + else + @out << "\nmsgid \"" << msgid << "\"\n" + @out << "msgstr \"\"\n" + end + end + end + + def print_help + printf _("Usage: %s input.rb -o output.pot\n"), $0 + print _("Extract translatable strings from given input files.\n\n") + end + + def check_options + command_options = [ + ['--help', '-h', GetoptLong::NO_ARGUMENT], #'print this help and exit'], + ['--version', '-v', GetoptLong::NO_ARGUMENT], #'print version info and exit'], + ['--output', '-o', GetoptLong::REQUIRED_ARGUMENT]#, ['FILE', 'write output to specified file']] + ] + + parser = GetoptLong.new + parser.set_options(*command_options) + + opt = Hash.new + parser.each do |name, arg| + opt.store(name.sub(/^--/, ""), arg || true) + end + + if opt['version'] + print "#{$0} #{VERSION.join('.')} \(#{DATE}\)\n\n" + exit + end + + if opt['help'] + print_help + exit + end + + opt + end + + def set_output(opt) + if opt['output'] + unless FileTest.exist? opt['output'] + @out = File.new(File.expand_path(opt['output']), "w+") + else + if $>.tty? + # FIXME + printf $stderr, "File '#{opt['output']}' already exists\n" + exit 1 + else + printf $stderr, "File '#{opt['output']}' already exists" + exit 1 + end + end + else + @out = STDOUT + end + end +end # class RGettext + +if __FILE__ == $0 # in case we want to start it from somewhere else + rgettext = RGettext.new + rgettext.start +end diff --git a/script/runner b/script/runner new file mode 100755 index 0000000..57211c6 --- /dev/null +++ b/script/runner @@ -0,0 +1,3 @@ +#!/usr/bin/ruby18 +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/runner' \ No newline at end of file diff --git a/script/scgi_rails b/script/scgi_rails new file mode 100755 index 0000000..dc470d8 --- /dev/null +++ b/script/scgi_rails @@ -0,0 +1,339 @@ +#!/usr/bin/env ruby + +require 'stringio' +require 'yaml' +require 'digest/sha1' +require 'logger' +require 'fileutils' +require 'socket' +require 'cgi' +require 'rubygems' +require 'cmdparse' +require 'monitor' + +def log(msg) + $stderr.print msg,"\n" +end + +def error(msg, exc=nil) + if exc + $stderr.print "ERROR: #{msg}: #{exc}\n" + $stderr.puts exc.backtrace + else + $stderr.print "ERROR: #{msg}\n" + end +end + + +# Modifies CGI so that we can use it. +class SCGIFixed < ::CGI + public :env_table + + def initialize(params, data, out, *args) + @env_table = params + @args = *args + @input = StringIO.new(data) + @out = out + super(*args) + end + def args + @args + end + def env_table + @env_table + end + def stdinput + @input + end + def stdoutput + @out + end +end + + +class SCGIProcessor < Monitor + + def initialize(settings) + @env = settings[:env] || "development" + @debug = settings[:debug] || false + @host = settings[:host] || "127.0.0.1" + @port = settings[:port] || "9999" + @children = settings[:children] || 1 + @pid_file = settings[:pid_file] || "children.yaml" + @status_dir = settings[:status_dir] || "/tmp" + @log_file = settings[:logfile] || "log/scgi.log" + @maxconns = settings[:maxconns] + @busy_msg = settings[:busy_msg] || "BUSY" + @settings = settings + @started = Time.now + @conns = 0 + @total_conns = 0 + @errors = 0 + + if @maxconns + @maxconns = @maxconns.to_i + else + @maxconns = 2**30-1 + end + + if settings[:conns_second] + @throttle_sleep = 1.0/settings[:conns_second].to_i + end + + super() + end + + def run + ENV['RAILS_ENV'] = @env + + begin + require_gem 'rails' + require "config/environment" + rescue Object + error("loading rails environment", $!) + end + + server = TCPServer.new(@host, @port) + + if @debug + log("Listening for connections on #@host:#@port") + listen(server) + else + childpids = [] + @children.to_i.times do + # fork each child listening to the same port. very simple yet effective way to spread the load + # to multiple processes without using threads and still using high performance libevent + begin + pid = fork do + $stderr = open(@log_file,"w") + $stderr.sync = false + listen(server) + end + childpids << pid + Process.detach(pid) + rescue Object + error("Could not fork child processes. Your system might not support fork. Use -D instead.", $!) + end + end + + # tell the user what the sha1 is so they can check for modification later + log("#@pid_file will have SHA1 #{Digest::SHA1.hexdigest(YAML.dump(childpids))}") + log("Record this somewhere so you know if it was modified later by someone else.") + # all children forked and the pids are now ready to write to the pid file + open(@pid_file,"w") { |f| f.write(YAML.dump(childpids)) } + end + end + + + def listen(socket) + thread = Thread.new do + while true + handle_client(socket.accept) + sleep @throttle_sleep if @throttle_sleep + + @total_conns += 1 + end + end + + begin + thread.join + rescue Interrupt + log("Shutting down from SIGINT.") + rescue Object + error("while listening for connections on #@host:#@port", $!) + end + end + + + def handle_client(socket) + Thread.new do + begin + synchronize { @conns += 1} + + len = "" + # we only read 10 bytes of the length. any request longer than this is invalid + while len.length <= 10 + c = socket.read(1) + if c == ':' + # found the terminal, len now has a length in it so read the payload + break + else + len << c + end + end + + # we should now either have a payload length to get + payload = socket.read(len.to_i) + if (c = socket.read(1)) != ',' + error("Malformed request, does not end with ','") + else + read_header(socket, payload, @conns) + end + rescue IOError + error("received IOError #$! when handling client. Your web server doesn't like me.") + rescue Object + @errors += 1 + error("after accepting client #@host:#@port -- #{$!.class}", $!) + ensure + synchronize { @conns -= 1} + socket.close if not socket.closed? + end + end + + end + + + def read_header(socket, payload, conns) + return if socket.closed? + request = split_body(payload) + if request and request["CONTENT_LENGTH"] + length = request["CONTENT_LENGTH"].to_i + if length > 0 + body = socket.read(length) + else + body = "" + end + + if @conns > @maxconns + socket.write("Content-type: text/plain\r\n\r\n") + socket.write(@busy_msg) + else + process_request(request, body, socket) + end + end + end + + + def process_request(request, body, socket) + return if socket.closed? + cgi = SCGIFixed.new(request, body, socket) + begin + synchronize do + # unfortuneatly, the dependencies.rb file is not thread safe and will throw exceptions + # claiming that Dispatcher is not defined, or that other classes are missing. We have + # to sync the dispatch call to get around this. + Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput) + end + rescue IOError + error("received IOError #$! when handling client. Your web server doesn't like me.") + rescue Object => rails_error + error("calling Dispatcher.dispatch", rails_error) + end + end + + + def split_body(data) + result = {} + el = data.split("\0") + i = 0 + len = el.length + while i < len + result[el[i]] = el[i+1] + i += 1 + end + + return result + end + + def status + pid = Process.pid + open("#@status_dir/scgi_rails-status.#{pid}","w") do |f| + status = { + 'time' => Time.now, 'pid' => pid, 'settings' => @settings, + 'env' => @env, 'status_dir' => @status_dir, 'started' => @started, + 'max_conns' => @maxconns, 'total_conns' => @total_conns, + 'conns' => @conns, 'errors' => @errors, 'systimes' => Process.times + } + f.write(YAML.dump(status)) + end + end +end + + +def signal_children(pidfile, signal) + if not File.exists? pidfile + log("No #{pidfile} as specified. Probably nothing running or wrong path.") + exit 1 + end + + childpids = YAML.load_file(pidfile) + childpids.each do |pid| + begin + log("Signaling pid #{pid}") + Process.kill(signal, pid) + rescue Object + log("Couldn't send #{signal} signal to #{pid} pid.") + end + end +end + + +def make_command(parent, name, desc, options) + cmd = CmdParse::Command.new(name, false ) + cmd.short_desc = desc + settings = {} + cmd.options = CmdParse::OptionParserWrapper.new do |opt| + options.each do |short, long, info, symbol| + opt.on(short, long, info) {|val| settings[symbol] = val} + end + end + cmd.set_execution_block do |args| + yield(settings, args) + end + parent.add_command(cmd) +end + + +cmd = CmdParse::CommandParser.new( true ) +cmd.program_name = "scgi_rails" +cmd.program_version = [0, 2, 1] +cmd.options = CmdParse::OptionParserWrapper.new do |opt| + opt.separator "Global options:" + opt.on("--verbose", "Be verbose when outputting info") {|t| $verbose = true } +end + +cmd.add_command( CmdParse::HelpCommand.new ) +cmd.add_command( CmdParse::VersionCommand.new ) + +make_command(cmd, 'start', "Start Rails Application", +[['-e','--env STRING','Rails environment', :env], +['-D','--[no-]debug', 'Do not fork children, stay in foreground.', :debug], +['-h','--host STRING', 'IP address to bind as server', :host], +['-p','--port NUMBER', 'Port to bind to', :port], +['-c','--children NUMBER', 'Number of children to start (not win32)', :children], +['-f','--pid-file PATH', 'Where to read the list of running children', :pid_file], +['-l','--log-file PATH', 'Use a different log from from log/scgi.log', :logfile], +['-t','--throttle NUMBER', 'Max conn/second to allow.', :conns_second], +['-m','--max-conns NUMBER', 'Max simultaneous connections before the busy message', :maxconns], +['-b','--busy-msg', 'Busy message given to clients over the max connections ("busy")', :busy_msg], +['-s','--status-dir PATH', 'Where to put the status files', :status_dir]]) do |settings, args| + scgi = SCGIProcessor.new(settings) + begin + trap("HUP") { scgi.status } + rescue Object + error("Could not setup a SIGHUP handler. You won't be able to get status.") + end + + scgi.run +end + + +make_command(cmd, 'status', "Get status from all running children", +[['-s','--status-dir PATH', 'Where to put the status files', :status_dir], +['-f','--pid-file PATH', 'Where to read the list of running children', :pid_file]]) do |settings, args| + signal_children(settings[:pid_file] || 'children.yaml', "HUP") + log("Status files for each child should show up in the configured status directory (/tmp by default).") +end + +make_command(cmd, 'stop', "Stop all running children", +[['-s','--sig SIGNAL', 'Where to put the status files', :signal], +['-n','--[no-]delete', 'Keep the children.yaml file rather than delete', :nodelete], +['-f','--pid-file PATH', 'Where to read the list of running children', :pid_file]]) do |settings, args| + pid_file = settings[:pid_file] || "children.yaml" + signal_children(pid_file, settings[:signal] || "INT") + if not settings[:nodelete] and File.exist?(pid_file) + File.unlink(pid_file) + end +end + +cmd.parse \ No newline at end of file diff --git a/script/server b/script/server new file mode 100755 index 0000000..9436fde --- /dev/null +++ b/script/server @@ -0,0 +1,3 @@ +#!/usr/bin/ruby18 +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/server' \ No newline at end of file diff --git a/test/fixtures/.gitkeep b/test/fixtures/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/test/fixtures/expressions.yml b/test/fixtures/expressions.yml new file mode 100644 index 0000000..e3fa03c --- /dev/null +++ b/test/fixtures/expressions.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first_expression: + id: 1 +another_expression: + id: 2 diff --git a/test/fixtures/filters.yml b/test/fixtures/filters.yml new file mode 100644 index 0000000..b36dd3b --- /dev/null +++ b/test/fixtures/filters.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first_filter: + id: 1 +another_filter: + id: 2 diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml new file mode 100644 index 0000000..c884985 --- /dev/null +++ b/test/fixtures/users.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first_user: + id: 1 +another_user: + id: 2 diff --git a/test/functional/.gitkeep b/test/functional/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/test/integration/.gitkeep b/test/integration/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/test/performance/browsing_test.rb b/test/performance/browsing_test.rb deleted file mode 100755 index 3fea27b..0000000 --- a/test/performance/browsing_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'test_helper' -require 'rails/performance_test_help' - -class BrowsingTest < ActionDispatch::PerformanceTest - # Refer to the documentation for all available options - # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory] - # :output => 'tmp/performance', :formats => [:flat] } - - def test_homepage - get '/' - end -end diff --git a/test/test_helper.rb b/test/test_helper.rb old mode 100755 new mode 100644 index 8bf1192..0a4be59 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,13 +1,13 @@ ENV["RAILS_ENV"] = "test" -require File.expand_path('../../config/environment', __FILE__) -require 'rails/test_help' +require File.expand_path(File.dirname(__FILE__) + "/../config/environment") +require 'test_help' -class ActiveSupport::TestCase - # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. - # - # Note: You'll currently still have to declare fixtures explicitly in integration tests - # -- they do not yet inherit this setting - fixtures :all +class Test::Unit::TestCase + # Turn off transactional fixtures if you're working with MyISAM tables in MySQL + self.use_transactional_fixtures = true + + # Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david) + self.use_instantiated_fixtures = false # Add more helper methods to be used by all tests here... -end +end \ No newline at end of file diff --git a/test/unit/.gitkeep b/test/unit/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/test/unit/expression_test.rb b/test/unit/expression_test.rb new file mode 100644 index 0000000..94ff355 --- /dev/null +++ b/test/unit/expression_test.rb @@ -0,0 +1,14 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class ExpressionTest < Test::Unit::TestCase + fixtures :expressions + + def setup + @expression = Expression.find(1) + end + + # Replace this with your real tests. + def test_truth + assert_kind_of Expression, @expression + end +end diff --git a/test/unit/filter_test.rb b/test/unit/filter_test.rb new file mode 100644 index 0000000..019aec9 --- /dev/null +++ b/test/unit/filter_test.rb @@ -0,0 +1,14 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class FilterTest < Test::Unit::TestCase + fixtures :filters + + def setup + @filter = Filter.find(1) + end + + # Replace this with your real tests. + def test_truth + assert_kind_of Filter, @filter + end +end diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb new file mode 100644 index 0000000..24c6258 --- /dev/null +++ b/test/unit/user_test.rb @@ -0,0 +1,14 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class UserTest < Test::Unit::TestCase + fixtures :users + + def setup + @user = User.find(1) + end + + # Replace this with your real tests. + def test_truth + assert_kind_of User, @user + end +end diff --git a/vendor/assets/javascripts/.gitkeep b/vendor/assets/javascripts/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/vendor/assets/stylesheets/.gitkeep b/vendor/assets/stylesheets/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/vendor/ezcrypto-0.1.1/._README b/vendor/ezcrypto-0.1.1/._README new file mode 100644 index 0000000..a0c1410 Binary files /dev/null and b/vendor/ezcrypto-0.1.1/._README differ diff --git a/vendor/ezcrypto-0.1.1/._rakefile b/vendor/ezcrypto-0.1.1/._rakefile new file mode 100644 index 0000000..8d0ff5d Binary files /dev/null and b/vendor/ezcrypto-0.1.1/._rakefile differ diff --git a/vendor/ezcrypto-0.1.1/MIT-LICENSE b/vendor/ezcrypto-0.1.1/MIT-LICENSE new file mode 100644 index 0000000..26f55e7 --- /dev/null +++ b/vendor/ezcrypto-0.1.1/MIT-LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2004 David Heinemeier Hansson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/vendor/ezcrypto-0.1.1/README b/vendor/ezcrypto-0.1.1/README new file mode 100644 index 0000000..e360dc6 --- /dev/null +++ b/vendor/ezcrypto-0.1.1/README @@ -0,0 +1,130 @@ += EzCrypto - Easy to use Crypto for Ruby + +EzCrypto is an easy to use wrapper around the poorly documented OpenSSL ruby library. + +== Features + +* Defaults to AES 128 CBC +* Will use the systems OpenSSL library for transparent hardware crypto support +* Single class object oriented access to most commonly used features +* Ruby like + +== Simple examples + +==== To encrypt: + +Generate a key using a password and a salt. Use the keys encrypt method to encrypt a strings worth of data: + + @key=EzCrypto::Key.with_password "password", "system salt" + @encrypted=@key.encrypt "Top secret should not be revealed" + +==== To decrypt: + +Same procedure as encrypt. Generate a key using a password and a salt. Use the keys decrypt method to decrypt a strings worth of data: + + @key=EzCrypto::Key.with_password "password", "system salt" + @key.decrypt @encrypted + +==== One liners: + +These simple examples use one line each: + + @encrypted=EzCrypto::Key.encrypt_with_password "password", @salt,"Top secret should not be revealed" + + EzCrypto::Key.decrypt_with_password "password", @salt,@encrypted + +== Keys + +The only class you need to know for most uses og EzCrypto is the Key class. You don't need understand ciphers or the encryption life cycle. + +==== Generating a random key + +The most secure type of key is the randomly generated key: + + @key=EzCrypto::Key.generate + +==== Initializing a key with raw key data + +If you already have a key from some other source, you simply have to call the constructor with the raw data: + + @key=EzCrypto::Key.new @binarykey + +==== Initializing a Key with a Base64 encoded key + +As seen above you can create a key from a password. This should be used if you don't want the key to be stored on disk for example: + + @key=EzCrypto::Key.with_password "Secret password" + +==== Initializing a Key with a Base64 encoded key + +If you already have a key from some other source in the popular Base64 encoded format, you use the decode class method: + + @key=EzCrypto::Key.decode @binarykey + +==== Exporting the key + +To export or save a key use the encode method (or to_s) method for a Base64 encoded key or raw as the raw binary data. + + puts @key.encode + puts @key.raw + +The raw method could be used for storing in a database using a tinyblob column. + +== Encryption and Decryption + +EzCrypto is optimized for simple encryption and decryption of strings. There are encrypt/decrypt pairs for normal binary use as well as for Base64 encoded use. + +==== Regular raw use + +Assuming you have generated a key using one of the above methods: + + @encrypted=@key.encrypt("clear text") + @decrypted=@key.decrypt(@encrypted) + assert "clear text", @decrypted + +==== Base64 encoded use + +This uses the encrypt64 and decrypt64 methods. Otherwise it is all the same: + + @encrypted=@key.encrypt64("clear text") + @decrypted=@key.decrypt64(@encrypted) + assert "clear text", @decrypted + +== FAQ + +=== What algorithm does this use? + +It uses as the default algorithm the AES 128 bit standard. This is a very fast and highly secure algorithm specified as the national standard in the US. For more information see: + +http://en.wikipedia.org/wiki/AES + +=== Only 128 bits. Is that enough? + +While it might sound like more would make it more secure, there is really no real security advantage for most commercial applications to use more than 128 bit AES. + +=== What is Base64 encoding? + +This is the most efficient and commonly used encoding scheme for binary data. This is used amongst other things for email attachments. It is also very common to use it for encrypted data. + +=== What is a Salt? + +A salt is just a piece of data we hash in with the password to create the key. If it is a server based application you could use store a salt within your source file. The salt must be the same for both encryption and decryption. + + +== License + +Action Web Service is released under the MIT license. + + +== Support + +To contact the author, send mail to pelleb@gmail.com + +Also see my blogs at: +http://stakeventures.com and +http://neubia.com + +This project was based on code used in my project StakeItOut, where you can securely share web services with your partners. +https://stakeitout.com + +(C) 2005 Pelle Braendgaard diff --git a/vendor/ezcrypto-0.1.1/lib/ezcrypto.rb b/vendor/ezcrypto-0.1.1/lib/ezcrypto.rb new file mode 100644 index 0000000..37acdff --- /dev/null +++ b/vendor/ezcrypto-0.1.1/lib/ezcrypto.rb @@ -0,0 +1,357 @@ +require 'openssl' +require 'digest/sha2' +require 'digest/sha1' +require 'base64' + +module EzCrypto + + +=begin rdoc +The Key is the only class you need to understand for simple use. + +=== Algorithms + +The crypto algorithms default to aes-128-cbc however on any of the class methods you can change it to one of the standard openssl cipher names using the +optional :algorithm=>alg name parameter. + +Eg. + Key.new @raw, :algorithm=>"des" + Key.generate :algorithm=>"blowfish" + Key.with_password @pwd,@salt,:algorithm=>"aes256" + + +== License + +Action Web Service is released under the MIT license. + + +== Support + +To contact the author, send mail to pelleb@gmail.com + +Also see my blogs at: +http://stakeventures.com and +http://neubia.com + +This project was based on code used in my project StakeItOut, where you can securely share web services with your partners. +https://stakeitout.com + +(C) 2005 Pelle Braendgaard + +=end + + class Key + attr_reader :raw,:algorithm + +=begin rdoc +Initialize the key with raw unencoded binary key data. This needs to be at least +16 bytes long for the default aes-128 algorithm. +=end + def initialize(raw,options = {}) + @raw=raw + @algorithm=options[:algorithm]||"aes-128-cbc" + end + +=begin rdoc +Generate random key. +=end + def self.generate(options = {}) + Key.new(EzCrypto::Digester.generate_key(calculate_key_size(options[:algorithm])),options) + end + +=begin rdoc +Create key generated from the given password and salt +=end + def self.with_password(password,salt,options = {}) + Key.new(EzCrypto::Digester.get_key(password,salt,calculate_key_size(options[:algorithm])),options) + end + +=begin rdoc +Initialize the key with Base64 encoded key data. +=end + def self.decode(encoded,options = {}) + Key.new(Base64.decode64(encoded),options) + end + +=begin rdoc +Encrypts the data with the given password and a salt. Short hand for: + + key=Key.with_password(password,salt,options) + key.encrypt(data) + +=end + def self.encrypt_with_password(password,salt,data,options = {}) + key=Key.with_password(password,salt,options) + key.encrypt(data) + end + +=begin rdoc +Decrypts the data with the given password and a salt. Short hand for: + + key=Key.with_password(password,salt,options) + key.decrypt(data) + + +=end + def self.decrypt_with_password(password,salt,data,options = {}) + key=Key.with_password(password,salt,options) + key.decrypt(data) + end + +=begin rdoc +Given an algorithm this calculates the keysize. This is used by both the generate and with_password methods. This is not yet 100% complete. +=end + def self.calculate_key_size(algorithm) + if !algorithm.nil? + algorithm=~/^([[:alnum:]]+)(-(\d+))?/ + if $3 + size=($3.to_i)/8 + else + case $1 + when "bf" + size = 16 + when "blowfish" + size = 16 + when "des" + size = 8 + when "des3" + size = 24 + when "aes128" + size = 16 + when "aes192" + size = 24 + when "aes256" + size = 32 + when "rc2" + size = 16 + when "rc4" + size = 16 + else + size = 16 + end + end + end + if size.nil? + size = 16 + end + + size + end + +=begin rdoc +returns the Base64 encoded key. +=end + def encode + Base64.encode64 @raw + end + +=begin rdoc +returns the Base64 encoded key. Synonym for encode. +=end + def to_s + encode + end + +=begin rdoc +Encrypts the data and returns it in encrypted binary form. +=end + def encrypt(data) + @cipher=EzCrypto::Encrypter.new(self,"",@algorithm) + @cipher.encrypt(data) + end + +=begin rdoc +Encrypts the data and returns it in encrypted Base64 encoded form. +=end + def encrypt64(data) + Base64.encode64(encrypt(data)) + end + +=begin rdoc +Decrypts the data passed to it in binary format. +=end + def decrypt(data) + @cipher=EzCrypto::Decrypter.new(self,"",@algorithm) + @cipher.gulp(data) + rescue + puts @algorithm + throw $! + end + +=begin rdoc +Decrypts a Base64 formatted string +=end + def decrypt64(data) + decrypt(Base64.decode64(data)) + end + + + end +=begin rdoc +Abstract Wrapper around OpenSSL's Cipher object. Extended by Encrypter and Decrypter. + +You probably should be using the Key class instead. + +Warning! The interface may change. + +=end + class CipherWrapper + +=begin rdoc + +=end + def initialize(key,target,mode,algorithm) + @cipher = OpenSSL::Cipher::Cipher.new(algorithm) + if mode + @cipher.encrypt + else + @cipher.decrypt + end + @cipher.key=key.raw + @cipher.padding=1 + @target=target + @finished=false + end + +=begin rdoc +Process the givend data with the cipher. +=end + def update(data) + reset if @finished + @target<< @cipher.update(data) + end + +=begin rdoc + +=end + def <<(data) + update(data) + end + +=begin rdoc +Finishes up any last bits of data in the cipher and returns the final result. +=end + def final + @target<< @cipher.final + @finished=true + @target + end + +=begin rdoc +Processes the entire data string using update and performs a final on it returning the data. +=end + def gulp(data) + update(data) + final + end + +=begin rdoc + +=end + def reset(target="") + @target=target + @finished=false + end + end + +=begin rdoc +Wrapper around OpenSSL Cipher for Encryption use. + +You probably should be using Key instead. + +Warning! The interface may change. + +=end + class Encrypter [ :test ] + +# Run the unit tests +Rake::TestTask.new { |t| + t.libs << "test" + t.pattern = 'test/*_test.rb' + t.verbose = true +} + + +# Genereate the RDoc documentation +Rake::RDocTask.new { |rdoc| + rdoc.rdoc_dir = 'doc' + rdoc.title = "EzCrypto - Simplified Crypto Library" + rdoc.options << '--line-numbers --inline-source --main README' + rdoc.template = "#{ENV['template']}.rb" if ENV['template'] + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('lib/ezcrypto.rb') +# rdoc.rdoc_files.include('lib/ezcrypto/*.rb') +} + + +# Create compressed packages +spec = Gem::Specification.new do |s| + s.platform = Gem::Platform::RUBY + s.name = PKG_NAME + s.summary = "Simplified encryption library." + s.description = %q{Makes it easier and safer to write crypto code.} + s.version = PKG_VERSION + + s.author = "Pelle Braendgaard" + s.email = "pelle@stakeitout.com" + s.rubyforge_project = "ezcrypto" + s.homepage = "http://ezcrypto.rubyforge.org" + + + s.has_rdoc = true + s.requirements << 'none' + s.require_path = 'lib' + + s.files = [ "rakefile", "README", "MIT-LICENSE" ] + s.files = s.files + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) } + s.files = s.files + Dir.glob( "test/**/*" ).delete_if { |item| item.include?( "\.svn" ) } +end + +Rake::GemPackageTask.new(spec) do |p| + p.gem_spec = spec + p.need_tar = true + p.need_zip = true +end + + +desc "Publish the API documentation" +task :pgem => [:package] do + Rake::SshFilePublisher.new("pelleb@rubyforge.org", "/var/www/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload +end + +desc "Publish the API documentation" +task :pdoc => [:rdoc] do + Rake::SshDirPublisher.new("pelleb@rubyforge.org", "/var/www/gforge-projects/ezcrypto", "doc").upload +end + +desc "Publish the release files to RubyForge." +task :release => [:package] do + files = ["gem", "tgz", "zip"].map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" } + + if RUBY_FORGE_PROJECT then + require 'net/http' + require 'open-uri' + + project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/" + project_data = open(project_uri) { |data| data.read } + group_id = project_data[/[?&]group_id=(\d+)/, 1] + raise "Couldn't get group id" unless group_id + + # This echos password to shell which is a bit sucky + if ENV["RUBY_FORGE_PASSWORD"] + password = ENV["RUBY_FORGE_PASSWORD"] + else + print "#{RUBY_FORGE_USER}@rubyforge.org's password: " + password = STDIN.gets.chomp + end + + login_response = Net::HTTP.start("rubyforge.org", 80) do |http| + data = [ + "login=1", + "form_loginname=#{RUBY_FORGE_USER}", + "form_pw=#{password}" + ].join("&") + http.post("/account/login.php", data) + end + + cookie = login_response["set-cookie"] + raise "Login failed" unless cookie + headers = { "Cookie" => cookie } + + release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}" + release_data = open(release_uri, headers) { |data| data.read } + package_id = release_data[/[?&]package_id=(\d+)/, 1] + raise "Couldn't get package id" unless package_id + + first_file = true + release_id = "" + + files.each do |filename| + basename = File.basename(filename) + file_ext = File.extname(filename) + file_data = File.open(filename, "rb") { |file| file.read } + + puts "Releasing #{basename}..." + + release_response = Net::HTTP.start("rubyforge.org", 80) do |http| + release_date = Time.now.strftime("%Y-%m-%d %H:%M") + type_map = { + ".zip" => "3000", + ".tgz" => "3110", + ".gz" => "3110", + ".gem" => "1400" + }; type_map.default = "9999" + type = type_map[file_ext] + boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor" + + query_hash = if first_file then + { + "group_id" => group_id, + "package_id" => package_id, + "release_name" => RELEASE_NAME, + "release_date" => release_date, + "type_id" => type, + "processor_id" => "8000", # Any + "release_notes" => "", + "release_changes" => "", + "preformatted" => "1", + "submit" => "1" + } + else + { + "group_id" => group_id, + "release_id" => release_id, + "package_id" => package_id, + "step2" => "1", + "type_id" => type, + "processor_id" => "8000", # Any + "submit" => "Add This File" + } + end + + query = "?" + query_hash.map do |(name, value)| + [name, URI.encode(value)].join("=") + end.join("&") + + data = [ + "--" + boundary, + "Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"", + "Content-Type: application/octet-stream", + "Content-Transfer-Encoding: binary", + "", file_data, "" + ].join("\x0D\x0A") + + release_headers = headers.merge( + "Content-Type" => "multipart/form-data; boundary=#{boundary}" + ) + + target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php" + http.post(target + query, data, release_headers) + end + + if first_file then + release_id = release_response.body[/release_id=(\d+)/, 1] + raise("Couldn't get release id") unless release_id + end + + first_file = false + end + end +end diff --git a/vendor/ezcrypto-0.1.1/test/ezcrypto_test.rb b/vendor/ezcrypto-0.1.1/test/ezcrypto_test.rb new file mode 100644 index 0000000..cf407c7 --- /dev/null +++ b/vendor/ezcrypto-0.1.1/test/ezcrypto_test.rb @@ -0,0 +1,112 @@ +$:.unshift(File.dirname(__FILE__) + "/../lib/") + +require 'test/unit' +require 'ezcrypto' +require 'base64' + +class EzCryptoTest < Test::Unit::TestCase + + def setup + end + + def test_generate_alg_key + assert_generate_alg_key "aes-128-cbc",16 + assert_generate_alg_key "aes-192-cbc",24 + assert_generate_alg_key "aes-256-cbc",32 + assert_generate_alg_key "rc2-40-cbc",5 + assert_generate_alg_key "rc2-64-cbc",8 + assert_generate_alg_key "rc4-64" ,8 + assert_generate_alg_key "blowfish" ,16 + assert_generate_alg_key "des" ,8 + end + + def test_with_password + assert_with_password "","secret","aes-128-cbc",16 + assert_with_password "test","secret","aes-128-cbc",16 + assert_with_password "password","secret","aes-128-cbc",16 + assert_with_password "aæsldfad8q5æ34j2æl4j24l6j2456","secret","aes-128-cbc",16 + + assert_with_password "","secret","aes-192-cbc",24 + assert_with_password "test","secret","aes-192-cbc",24 + assert_with_password "password","secret","aes-192-cbc",24 + assert_with_password "aæsldfad8q5æ34j2æl4j24l6j2456","secret","aes-192-cbc",24 + + assert_with_password "","secret","aes-256-cbc",32 + assert_with_password "test","secret","aes-256-cbc",32 + assert_with_password "password","secret","aes-256-cbc",32 + assert_with_password "aæsldfad8q5æ34j2æl4j24l6j2456","secret","aes-256-cbc",32 + + end + + def test_encoded + 0.upto 32 do |size| + assert_encoded_keys size + end + end + + def test_encrypt + 0.upto(CLEAR_TEXT.size-1) do |size| + assert_encrypt CLEAR_TEXT[0..size] + end + end + + def test_decrypt + + 0.upto(CLEAR_TEXT.size) do |size| + assert_decrypt CLEAR_TEXT[0..size] + end + end + + def test_decrypt64 + 0.upto(CLEAR_TEXT.size) do |size| + assert_decrypt64 CLEAR_TEXT[0..size] + end + end + + def assert_key_size(size,key) + assert_equal size,key.raw.size + end + + def assert_generate_alg_key(algorithm,size) + key=EzCrypto::Key.generate :algorithm=>algorithm + assert_key_size size,key + end + + def assert_with_password(password,salt,algorithm,size) + key=EzCrypto::Key.with_password password,salt,:algorithm=>algorithm + assert_key_size size,key + assert_equal key.raw,EzCrypto::Key.with_password( password,salt,:algorithm=>algorithm).raw + end + + def assert_encoded_keys(size) + key=EzCrypto::Key.generate size + key2=EzCrypto::Key.decode(key.encode) + assert_equal key.raw, key2.raw + end + + def assert_encrypt(clear) + ALGORITHMS.each do |alg| + key=EzCrypto::Key.generate :algorithm=>alg + encrypted=key.encrypt clear + assert_not_nil encrypted + end + end + + def assert_decrypt(clear) + ALGORITHMS.each do |alg| + key=EzCrypto::Key.generate :algorithm=>alg + encrypted=key.encrypt clear + assert_not_nil encrypted + assert_equal clear,key.decrypt(encrypted) + end + end + def assert_decrypt64(clear) + key=EzCrypto::Key.generate + encrypted=key.encrypt64 clear + assert_not_nil encrypted + assert_equal clear,key.decrypt64(encrypted) + end + ALGORITHMS=["aes128","bf","blowfish","des","des3","rc4","rc2"] + CLEAR_TEXT="Lorem ipsum dolor sit amet, suspendisse id interdum mus leo id. Sapien tempus consequat nullam, platea vitae sociis sed elementum et fermentum, vel praesent eget. Sed blandit augue, molestie mus sed habitant, semper voluptatibus neque, nullam a augue. Aptent imperdiet curabitur, quam quis laoreet. Dolor magna. Quis vestibulum amet eu arcu fringilla nibh, mi urna sunt dictumst nulla, elit quisque purus eros, sem hendrerit. Vulputate tortor rhoncus ac nonummy tortor nulla. Nunc id nunc luctus ligula." +end + diff --git a/vendor/plugins/.gitkeep b/vendor/plugins/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/vendor/plugins/auto_complete/README b/vendor/plugins/auto_complete/README new file mode 100644 index 0000000..e08a815 --- /dev/null +++ b/vendor/plugins/auto_complete/README @@ -0,0 +1,23 @@ +Example: + + # Controller + class BlogController < ApplicationController + auto_complete_for :post, :title + end + + # View + <%= text_field_with_auto_complete :post, title %> + +By default, auto_complete_for limits the results to 10 entries, +and sorts by the given field. + +auto_complete_for takes a third parameter, an options hash to +the find method used to search for the records: + + auto_complete_for :post, :title, :limit => 15, :order => 'created_at DESC' + +For more examples, see script.aculo.us: +* http://script.aculo.us/demos/ajax/autocompleter +* http://script.aculo.us/demos/ajax/autocompleter_customized + +Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license diff --git a/vendor/plugins/auto_complete/Rakefile b/vendor/plugins/auto_complete/Rakefile new file mode 100644 index 0000000..5af4e82 --- /dev/null +++ b/vendor/plugins/auto_complete/Rakefile @@ -0,0 +1,22 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +desc 'Default: run unit tests.' +task :default => :test + +desc 'Test auto_complete plugin.' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +desc 'Generate documentation for auto_complete plugin.' +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'Auto Complete' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/vendor/plugins/auto_complete/init.rb b/vendor/plugins/auto_complete/init.rb new file mode 100644 index 0000000..87bf027 --- /dev/null +++ b/vendor/plugins/auto_complete/init.rb @@ -0,0 +1,2 @@ +ActionController::Base.send :include, AutoComplete +ActionController::Base.helper AutoCompleteMacrosHelper \ No newline at end of file diff --git a/vendor/plugins/auto_complete/lib/auto_complete.rb b/vendor/plugins/auto_complete/lib/auto_complete.rb new file mode 100644 index 0000000..4afc7c2 --- /dev/null +++ b/vendor/plugins/auto_complete/lib/auto_complete.rb @@ -0,0 +1,47 @@ +module AutoComplete + + def self.included(base) + base.extend(ClassMethods) + end + + # + # Example: + # + # # Controller + # class BlogController < ApplicationController + # auto_complete_for :post, :title + # end + # + # # View + # <%= text_field_with_auto_complete :post, title %> + # + # By default, auto_complete_for limits the results to 10 entries, + # and sorts by the given field. + # + # auto_complete_for takes a third parameter, an options hash to + # the find method used to search for the records: + # + # auto_complete_for :post, :title, :limit => 15, :order => 'created_at DESC' + # + # For help on defining text input fields with autocompletion, + # see ActionView::Helpers::JavaScriptHelper. + # + # For more examples, see script.aculo.us: + # * http://script.aculo.us/demos/ajax/autocompleter + # * http://script.aculo.us/demos/ajax/autocompleter_customized + module ClassMethods + def auto_complete_for(object, method, options = {}) + define_method("auto_complete_for_#{object}_#{method}") do + find_options = { + :conditions => [ "LOWER(#{method}) LIKE ?", '%' + params[object][method].downcase + '%' ], + :order => "#{method} ASC", + :limit => 10 }.merge!(options) + + @items = object.to_s.camelize.constantize.find(:all, find_options) + + render :inline => "<%= auto_complete_result @items, '#{method}' %>" + end + end + end + +end \ No newline at end of file diff --git a/vendor/plugins/auto_complete/lib/auto_complete_macros_helper.rb b/vendor/plugins/auto_complete/lib/auto_complete_macros_helper.rb new file mode 100644 index 0000000..1d25ee4 --- /dev/null +++ b/vendor/plugins/auto_complete/lib/auto_complete_macros_helper.rb @@ -0,0 +1,143 @@ +module AutoCompleteMacrosHelper + # Adds AJAX autocomplete functionality to the text input field with the + # DOM ID specified by +field_id+. + # + # This function expects that the called action returns an HTML
    list, + # or nothing if no entries should be displayed for autocompletion. + # + # You'll probably want to turn the browser's built-in autocompletion off, + # so be sure to include an autocomplete="off" attribute with your text + # input field. + # + # The autocompleter object is assigned to a Javascript variable named field_id_auto_completer. + # This object is useful if you for example want to trigger the auto-complete suggestions through + # other means than user input (for that specific case, call the activate method on that object). + # + # Required +options+ are: + # :url:: URL to call for autocompletion results + # in url_for format. + # + # Addtional +options+ are: + # :update:: Specifies the DOM ID of the element whose + # innerHTML should be updated with the autocomplete + # entries returned by the AJAX request. + # Defaults to field_id + '_auto_complete' + # :with:: A JavaScript expression specifying the + # parameters for the XMLHttpRequest. This defaults + # to 'fieldname=value'. + # :frequency:: Determines the time to wait after the last keystroke + # for the AJAX request to be initiated. + # :indicator:: Specifies the DOM ID of an element which will be + # displayed while autocomplete is running. + # :tokens:: A string or an array of strings containing + # separator tokens for tokenized incremental + # autocompletion. Example: :tokens => ',' would + # allow multiple autocompletion entries, separated + # by commas. + # :min_chars:: The minimum number of characters that should be + # in the input field before an Ajax call is made + # to the server. + # :on_hide:: A Javascript expression that is called when the + # autocompletion div is hidden. The expression + # should take two variables: element and update. + # Element is a DOM element for the field, update + # is a DOM element for the div from which the + # innerHTML is replaced. + # :on_show:: Like on_hide, only now the expression is called + # then the div is shown. + # :after_update_element:: A Javascript expression that is called when the + # user has selected one of the proposed values. + # The expression should take two variables: element and value. + # Element is a DOM element for the field, value + # is the value selected by the user. + # :select:: Pick the class of the element from which the value for + # insertion should be extracted. If this is not specified, + # the entire element is used. + # :method:: Specifies the HTTP verb to use when the autocompletion + # request is made. Defaults to POST. + def auto_complete_field(field_id, options = {}) + function = "var #{field_id}_auto_completer = new Ajax.Autocompleter(" + function << "'#{field_id}', " + function << "'" + (options[:update] || "#{field_id}_auto_complete") + "', " + function << "'#{url_for(options[:url])}'" + + js_options = {} + js_options[:tokens] = array_or_string_for_javascript(options[:tokens]) if options[:tokens] + js_options[:callback] = "function(element, value) { return #{options[:with]} }" if options[:with] + js_options[:indicator] = "'#{options[:indicator]}'" if options[:indicator] + js_options[:select] = "'#{options[:select]}'" if options[:select] + js_options[:paramName] = "'#{options[:param_name]}'" if options[:param_name] + js_options[:frequency] = "#{options[:frequency]}" if options[:frequency] + js_options[:method] = "'#{options[:method].to_s}'" if options[:method] + + { :after_update_element => :afterUpdateElement, + :on_show => :onShow, :on_hide => :onHide, :min_chars => :minChars }.each do |k,v| + js_options[v] = options[k] if options[k] + end + + function << (', ' + options_for_javascript(js_options) + ')') + + javascript_tag(function) + end + + # Use this method in your view to generate a return for the AJAX autocomplete requests. + # + # Example action: + # + # def auto_complete_for_item_title + # @items = Item.find(:all, + # :conditions => [ 'LOWER(description) LIKE ?', + # '%' + request.raw_post.downcase + '%' ]) + # render :inline => "<%= auto_complete_result(@items, 'description') %>" + # end + # + # The auto_complete_result can of course also be called from a view belonging to the + # auto_complete action if you need to decorate it further. + def auto_complete_result(entries, field, phrase = nil) + return unless entries + items = entries.map { |entry| content_tag("li", phrase ? highlight(entry[field], phrase) : h(entry[field])) } + content_tag("ul", items.uniq) + end + + # Wrapper for text_field with added AJAX autocompletion functionality. + # + # In your controller, you'll need to define an action called + # auto_complete_for to respond the AJAX calls, + # + def text_field_with_auto_complete(object, method, tag_options = {}, completion_options = {}) + (completion_options[:skip_style] ? "" : auto_complete_stylesheet) + + text_field(object, method, tag_options) + + content_tag("div", "", :id => "#{object}_#{method}_auto_complete", :class => "auto_complete") + + auto_complete_field("#{object}_#{method}", { :url => { :action => "auto_complete_for_#{object}_#{method}" } }.update(completion_options)) + end + + private + def auto_complete_stylesheet + content_tag('style', <<-EOT, :type => Mime::CSS) + div.auto_complete { + width: 350px; + background: #fff; + } + div.auto_complete ul { + border:1px solid #888; + margin:0; + padding:0; + width:100%; + list-style-type:none; + } + div.auto_complete ul li { + margin:0; + padding:3px; + } + div.auto_complete ul li.selected { + background-color: #ffb; + } + div.auto_complete ul strong.highlight { + color: #800; + margin:0; + padding:0; + } + EOT + end + +end diff --git a/vendor/plugins/auto_complete/test/auto_complete_test.rb b/vendor/plugins/auto_complete/test/auto_complete_test.rb new file mode 100644 index 0000000..dc9a5c9 --- /dev/null +++ b/vendor/plugins/auto_complete/test/auto_complete_test.rb @@ -0,0 +1,67 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '../../../../test/test_helper')) + +class AutoCompleteTest < Test::Unit::TestCase + include AutoComplete + include AutoCompleteMacrosHelper + + include ActionView::Helpers::UrlHelper + include ActionView::Helpers::TagHelper + include ActionView::Helpers::TextHelper + include ActionView::Helpers::FormHelper + include ActionView::Helpers::CaptureHelper + + def setup + @controller = Class.new do + def url_for(options) + url = "http://www.example.com/" + url << options[:action].to_s if options and options[:action] + url + end + end + @controller = @controller.new + end + + + def test_auto_complete_field + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :tokens => ','); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :tokens => [',']); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :min_chars => 3); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :on_hide => "function(element, update){alert('me');}"); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :frequency => 2); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, + :after_update_element => "function(element,value){alert('You have chosen: '+value)}"); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :param_name => 'huidriwusch'); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :method => :get); + end + + def test_auto_complete_result + result = [ { :title => 'test1' }, { :title => 'test2' } ] + assert_equal %(
    • test1
    • test2
    ), + auto_complete_result(result, :title) + assert_equal %(
    • test1
    • test2
    ), + auto_complete_result(result, :title, "est") + + resultuniq = [ { :title => 'test1' }, { :title => 'test1' } ] + assert_equal %(
    • test1
    ), + auto_complete_result(resultuniq, :title, "est") + end + + def test_text_field_with_auto_complete + assert_match %( + + +

    Samples of pagination styling for will_paginate

    +

    + Find these styles in "examples/pagination.css" of will_paginate library. + There is a Sass version of it for all you sassy people. +

    +

    + Read about good rules for pagination: + Pagination 101 +

    +

    + Warning: + page links below don't lead anywhere (so don't click on them). +

    +

    + Unstyled pagination (ewww!) +

    +
    + « Previous 1 3 4 5 6 7 8 9 29 30 +
    +

    Digg.com

    +
    + « Previous 1 3 4 5 6 7 8 9 29 30 +
    +

    Digg-style, no page links

    +
    + « Previous +
    +

    Code that renders this:

    +
    +    <%= will_paginate @posts, :page_links => false %>
    +  
    +

    Digg-style, extra content

    +
    +
    + Displaying entries 1 - 6 of 180 in total +
    + « Previous 1 3 4 5 6 7 8 9 29 30 +
    +

    Code that renders this:

    +
    +    <div class="digg_pagination">
    +      <div clas="page_info">
    +        <%= page_entries_info @posts %>
    +      </div>
    +      <%= will_paginate @posts, :container => false %>
    +    </div>
    +  
    +

    Apple.com store

    +
    + « Previous 1 3 4 5 6 7 8 9 29 30 +
    +

    Flickr.com

    +
    + « Previous 1 3 4 5 6 7 8 9 29 30 +
    (118 photos)
    +
    + diff --git a/vendor/plugins/will_paginate/examples/pagination.css b/vendor/plugins/will_paginate/examples/pagination.css new file mode 100644 index 0000000..b55e977 --- /dev/null +++ b/vendor/plugins/will_paginate/examples/pagination.css @@ -0,0 +1,90 @@ +.digg_pagination { + background: white; + /* self-clearing method: */ } + .digg_pagination a, .digg_pagination span { + padding: .2em .5em; + display: block; + float: left; + margin-right: 1px; } + .digg_pagination span.disabled { + color: #999; + border: 1px solid #DDD; } + .digg_pagination span.current { + font-weight: bold; + background: #2E6AB1; + color: white; + border: 1px solid #2E6AB1; } + .digg_pagination a { + text-decoration: none; + color: #105CB6; + border: 1px solid #9AAFE5; } + .digg_pagination a:hover, .digg_pagination a:focus { + color: #003; + border-color: #003; } + .digg_pagination .page_info { + background: #2E6AB1; + color: white; + padding: .4em .6em; + width: 22em; + margin-bottom: .3em; + text-align: center; } + .digg_pagination .page_info b { + color: #003; + background: #6aa6ed; + padding: .1em .25em; } + .digg_pagination:after { + content: "."; + display: block; + height: 0; + clear: both; + visibility: hidden; } + * html .digg_pagination { + height: 1%; } + *:first-child+html .digg_pagination { + overflow: hidden; } + +.apple_pagination { + background: #F1F1F1; + border: 1px solid #E5E5E5; + text-align: center; + padding: 1em; } + .apple_pagination a, .apple_pagination span { + padding: .2em .3em; } + .apple_pagination span.disabled { + color: #AAA; } + .apple_pagination span.current { + font-weight: bold; + background: transparent url(apple-circle.gif) no-repeat 50% 50%; } + .apple_pagination a { + text-decoration: none; + color: black; } + .apple_pagination a:hover, .apple_pagination a:focus { + text-decoration: underline; } + +.flickr_pagination { + text-align: center; + padding: .3em; } + .flickr_pagination a, .flickr_pagination span { + padding: .2em .5em; } + .flickr_pagination span.disabled { + color: #AAA; } + .flickr_pagination span.current { + font-weight: bold; + color: #FF0084; } + .flickr_pagination a { + border: 1px solid #DDDDDD; + color: #0063DC; + text-decoration: none; } + .flickr_pagination a:hover, .flickr_pagination a:focus { + border-color: #003366; + background: #0063DC; + color: white; } + .flickr_pagination .page_info { + color: #aaa; + padding-top: .8em; } + .flickr_pagination .prev_page, .flickr_pagination .next_page { + border-width: 2px; } + .flickr_pagination .prev_page { + margin-right: 1em; } + .flickr_pagination .next_page { + margin-left: 1em; } diff --git a/vendor/plugins/will_paginate/examples/pagination.sass b/vendor/plugins/will_paginate/examples/pagination.sass new file mode 100644 index 0000000..737a97b --- /dev/null +++ b/vendor/plugins/will_paginate/examples/pagination.sass @@ -0,0 +1,91 @@ +.digg_pagination + :background white + a, span + :padding .2em .5em + :display block + :float left + :margin-right 1px + span.disabled + :color #999 + :border 1px solid #DDD + span.current + :font-weight bold + :background #2E6AB1 + :color white + :border 1px solid #2E6AB1 + a + :text-decoration none + :color #105CB6 + :border 1px solid #9AAFE5 + &:hover, &:focus + :color #003 + :border-color #003 + .page_info + :background #2E6AB1 + :color white + :padding .4em .6em + :width 22em + :margin-bottom .3em + :text-align center + b + :color #003 + :background = #2E6AB1 + 60 + :padding .1em .25em + + /* self-clearing method: + &:after + :content "." + :display block + :height 0 + :clear both + :visibility hidden + * html & + :height 1% + *:first-child+html & + :overflow hidden + +.apple_pagination + :background #F1F1F1 + :border 1px solid #E5E5E5 + :text-align center + :padding 1em + a, span + :padding .2em .3em + span.disabled + :color #AAA + span.current + :font-weight bold + :background transparent url(apple-circle.gif) no-repeat 50% 50% + a + :text-decoration none + :color black + &:hover, &:focus + :text-decoration underline + +.flickr_pagination + :text-align center + :padding .3em + a, span + :padding .2em .5em + span.disabled + :color #AAA + span.current + :font-weight bold + :color #FF0084 + a + :border 1px solid #DDDDDD + :color #0063DC + :text-decoration none + &:hover, &:focus + :border-color #003366 + :background #0063DC + :color white + .page_info + :color #aaa + :padding-top .8em + .prev_page, .next_page + :border-width 2px + .prev_page + :margin-right 1em + .next_page + :margin-left 1em diff --git a/vendor/plugins/will_paginate/init.rb b/vendor/plugins/will_paginate/init.rb new file mode 100644 index 0000000..838d30e --- /dev/null +++ b/vendor/plugins/will_paginate/init.rb @@ -0,0 +1 @@ +require 'will_paginate' diff --git a/vendor/plugins/will_paginate/lib/will_paginate.rb b/vendor/plugins/will_paginate/lib/will_paginate.rb new file mode 100644 index 0000000..ee81c5d --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate.rb @@ -0,0 +1,43 @@ +require 'will_paginate/deprecation' + +# = You *will* paginate! +# +# First read about WillPaginate::Finder::ClassMethods, then see +# WillPaginate::ViewHelpers. The magical array you're handling in-between is +# WillPaginate::Collection. +# +# Happy paginating! +module WillPaginate + def self.enable + Deprecation.warn "WillPaginate::enable() doesn't do anything anymore" + end + + # Enable named_scope, a feature of Rails 2.1, even if you have older Rails + # (tested on Rails 2.0.2 and 1.2.6). + # + # You can pass +false+ for +patch+ parameter to skip monkeypatching + # *associations*. Use this if you feel that named_scope broke + # has_many, has_many :through or has_and_belongs_to_many associations in + # your app. By passing +false+, you can still use named_scope in + # your models, but not through associations. + def self.enable_named_scope(patch = true) + return if defined? ActiveRecord::NamedScope + require 'will_paginate/finders/active_record/named_scope' + require 'will_paginate/finders/active_record/named_scope_patch' if patch + + ActiveRecord::Base.send :include, WillPaginate::NamedScope + end +end + +if defined?(Rails) + require 'will_paginate/view_helpers/action_view' if defined?(ActionController) + require 'will_paginate/finders/active_record' if defined?(ActiveRecord) +end + +if defined?(Merb::Plugins) + require 'will_paginate/collection' + require 'will_paginate/view_helpers/base' + require 'will_paginate/view_helpers/link_renderer' + # this only includes will_paginate view stuff in Merb (not finder adapters) + Merb::AbstractController.send(:include, WillPaginate::ViewHelpers::Base) +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/array.rb b/vendor/plugins/will_paginate/lib/will_paginate/array.rb new file mode 100644 index 0000000..1076760 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/array.rb @@ -0,0 +1,33 @@ +require 'will_paginate/collection' + +class Array + # Paginates a static array (extracting a subset of it). The result is a + # WillPaginate::Collection instance, which is an array with few more + # properties about its paginated state. + # + # Parameters: + # * :page - current page, defaults to 1 + # * :per_page - limit of items per page, defaults to 30 + # * :total_entries - total number of items in the array, defaults to + # array.length (obviously) + # + # Example: + # arr = ['a', 'b', 'c', 'd', 'e'] + # paged = arr.paginate(:per_page => 2) #-> ['a', 'b'] + # paged.total_entries #-> 5 + # arr.paginate(:page => 2, :per_page => 2) #-> ['c', 'd'] + # arr.paginate(:page => 3, :per_page => 2) #-> ['e'] + # + # This method was originally {suggested by Desi + # McAdam}[http://www.desimcadam.com/archives/8] and later proved to be the + # most useful method of will_paginate library. + def paginate(options = {}) + raise ArgumentError, "parameter hash expected (got #{options.inspect})" unless Hash === options + + WillPaginate::Collection.create options[:page] || 1, + options[:per_page] || 30, + options[:total_entries] || self.length do |pager| + pager.replace self[pager.offset, pager.per_page].to_a + end + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/collection.rb b/vendor/plugins/will_paginate/lib/will_paginate/collection.rb new file mode 100644 index 0000000..89d992f --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/collection.rb @@ -0,0 +1,145 @@ +module WillPaginate + # = Invalid page number error + # This is an ArgumentError raised in case a page was requested that is either + # zero or negative number. You should decide how do deal with such errors in + # the controller. + # + # If you're using Rails 2, then this error will automatically get handled like + # 404 Not Found. The hook is in "will_paginate.rb": + # + # ActionController::Base.rescue_responses['WillPaginate::InvalidPage'] = :not_found + # + # If you don't like this, use your preffered method of rescuing exceptions in + # public from your controllers to handle this differently. The +rescue_from+ + # method is a nice addition to Rails 2. + # + # This error is *not* raised when a page further than the last page is + # requested. Use WillPaginate::Collection#out_of_bounds? method to + # check for those cases and manually deal with them as you see fit. + class InvalidPage < ArgumentError + def initialize(page, page_num) + super "#{page.inspect} given as value, which translates to '#{page_num}' as page number" + end + end + + # = The key to pagination + # Arrays returned from paginating finds are, in fact, instances of this little + # class. You may think of WillPaginate::Collection as an ordinary array with + # some extra properties. Those properties are used by view helpers to generate + # correct page links. + # + # WillPaginate::Collection also assists in rolling out your own pagination + # solutions: see +create+. + # + # If you are writing a library that provides a collection which you would like + # to conform to this API, you don't have to copy these methods over; simply + # make your plugin/gem dependant on the "will_paginate" gem: + # + # gem 'will_paginate' + # require 'will_paginate/collection' + # + # # now use WillPaginate::Collection directly or subclass it + class Collection < Array + attr_reader :current_page, :per_page, :total_entries, :total_pages + + # Arguments to the constructor are the current page number, per-page limit + # and the total number of entries. The last argument is optional because it + # is best to do lazy counting; in other words, count *conditionally* after + # populating the collection using the +replace+ method. + def initialize(page, per_page, total = nil) + @current_page = page.to_i + raise InvalidPage.new(page, @current_page) if @current_page < 1 + @per_page = per_page.to_i + raise ArgumentError, "`per_page` setting cannot be less than 1 (#{@per_page} given)" if @per_page < 1 + + self.total_entries = total if total + end + + # Just like +new+, but yields the object after instantiation and returns it + # afterwards. This is very useful for manual pagination: + # + # @entries = WillPaginate::Collection.create(1, 10) do |pager| + # result = Post.find(:all, :limit => pager.per_page, :offset => pager.offset) + # # inject the result array into the paginated collection: + # pager.replace(result) + # + # unless pager.total_entries + # # the pager didn't manage to guess the total count, do it manually + # pager.total_entries = Post.count + # end + # end + # + # The possibilities with this are endless. For another example, here is how + # WillPaginate used to define pagination for Array instances: + # + # Array.class_eval do + # def paginate(page = 1, per_page = 15) + # WillPaginate::Collection.create(page, per_page, size) do |pager| + # pager.replace self[pager.offset, pager.per_page].to_a + # end + # end + # end + # + # The Array#paginate API has since then changed, but this still serves as a + # fine example of WillPaginate::Collection usage. + def self.create(page, per_page, total = nil, &block) + pager = new(page, per_page, total) + yield pager + pager + end + + # Helper method that is true when someone tries to fetch a page with a + # larger number than the last page. Can be used in combination with flashes + # and redirecting. + def out_of_bounds? + current_page > total_pages + end + + # Current offset of the paginated collection. If we're on the first page, + # it is always 0. If we're on the 2nd page and there are 30 entries per page, + # the offset is 30. This property is useful if you want to render ordinals + # besides your records: simply start with offset + 1. + def offset + (current_page - 1) * per_page + end + + # current_page - 1 or nil if there is no previous page + def previous_page + current_page > 1 ? (current_page - 1) : nil + end + + # current_page + 1 or nil if there is no next page + def next_page + current_page < total_pages ? (current_page + 1) : nil + end + + def total_entries=(number) + @total_entries = number.to_i + @total_pages = (@total_entries / per_page.to_f).ceil + end + + # This is a magic wrapper for the original Array#replace method. It serves + # for populating the paginated collection after initialization. + # + # Why magic? Because it tries to guess the total number of entries judging + # by the size of given array. If it is shorter than +per_page+ limit, then we + # know we're on the last page. This trick is very useful for avoiding + # unnecessary hits to the database to do the counting after we fetched the + # data for the current page. + # + # However, after using +replace+ you should always test the value of + # +total_entries+ and set it to a proper value if it's +nil+. See the example + # in +create+. + def replace(array) + result = super + + # The collection is shorter then page limit? Rejoice, because + # then we know that we are on the last page! + if total_entries.nil? and length < per_page and (current_page == 1 or length > 0) + self.total_entries = offset + length + end + + result + end + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb b/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb new file mode 100644 index 0000000..4601f00 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb @@ -0,0 +1,58 @@ +require 'set' +require 'will_paginate/array' + +## Everything below blatantly stolen from ActiveSupport :o + +unless Hash.instance_methods.include? 'except' + Hash.class_eval do + # Returns a new hash without the given keys. + def except(*keys) + rejected = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys) + reject { |key,| rejected.include?(key) } + end + + # Replaces the hash without only the given keys. + def except!(*keys) + replace(except(*keys)) + end + end +end + +unless Hash.instance_methods.include? 'slice' + Hash.class_eval do + # Returns a new hash with only the given keys. + def slice(*keys) + allowed = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys) + reject { |key,| !allowed.include?(key) } + end + + # Replaces the hash with only the given keys. + def slice!(*keys) + replace(slice(*keys)) + end + end +end + +unless String.instance_methods.include? 'constantize' + String.class_eval do + def constantize + unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self + raise NameError, "#{self.inspect} is not a valid constant name!" + end + + Object.module_eval("::#{$1}", __FILE__, __LINE__) + end + end +end + +unless String.instance_methods.include? 'underscore' + String.class_eval do + def underscore + self.to_s.gsub(/::/, '/'). + gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). + gsub(/([a-z\d])([A-Z])/,'\1_\2'). + tr("-", "_"). + downcase + end + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/deprecation.rb b/vendor/plugins/will_paginate/lib/will_paginate/deprecation.rb new file mode 100644 index 0000000..2c44d1e --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/deprecation.rb @@ -0,0 +1,50 @@ +# borrowed from ActiveSupport::Deprecation +module WillPaginate + module Deprecation + def self.debug() @debug; end + def self.debug=(value) @debug = value; end + self.debug = false + + # Choose the default warn behavior according to RAILS_ENV. + # Ignore deprecation warnings in production. + BEHAVIORS = { + 'test' => Proc.new { |message, callstack| + $stderr.puts(message) + $stderr.puts callstack.join("\n ") if debug + }, + 'development' => Proc.new { |message, callstack| + logger = defined?(::RAILS_DEFAULT_LOGGER) ? ::RAILS_DEFAULT_LOGGER : Logger.new($stderr) + logger.warn message + logger.debug callstack.join("\n ") if debug + } + } + + def self.warn(message, callstack = caller) + if behavior + message = 'WillPaginate: ' + message.strip.gsub(/\s+/, ' ') + behavior.call(message, callstack) + end + end + + def self.default_behavior + if defined?(RAILS_ENV) + BEHAVIORS[RAILS_ENV.to_s] + else + BEHAVIORS['test'] + end + end + + # Behavior is a block that takes a message argument. + def self.behavior() @behavior; end + def self.behavior=(value) @behavior = value; end + self.behavior = default_behavior + + def self.silence + old_behavior = self.behavior + self.behavior = nil + yield + ensure + self.behavior = old_behavior + end + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/finders.rb b/vendor/plugins/will_paginate/lib/will_paginate/finders.rb new file mode 100644 index 0000000..ca41f5b --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/finders.rb @@ -0,0 +1,9 @@ +require 'will_paginate/core_ext' + +module WillPaginate + # Database logic for different ORMs + # + # See WillPaginate::Finders::Base + module Finders + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/finders/active_record.rb b/vendor/plugins/will_paginate/lib/will_paginate/finders/active_record.rb new file mode 100644 index 0000000..84c99ff --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/finders/active_record.rb @@ -0,0 +1,204 @@ +require 'will_paginate/finders/base' +require 'active_record' + +module WillPaginate::Finders + # = Paginating finders for ActiveRecord models + # + # WillPaginate adds +paginate+, +per_page+ and other methods to + # ActiveRecord::Base class methods and associations. It also hooks into + # +method_missing+ to intercept pagination calls to dynamic finders such as + # +paginate_by_user_id+ and translate them to ordinary finders + # (+find_all_by_user_id+ in this case). + # + # In short, paginating finders are equivalent to ActiveRecord finders; the + # only difference is that we start with "paginate" instead of "find" and + # that :page is required parameter: + # + # @posts = Post.paginate :all, :page => params[:page], :order => 'created_at DESC' + # + # In paginating finders, "all" is implicit. There is no sense in paginating + # a single record, right? So, you can drop the :all argument: + # + # Post.paginate(...) => Post.find :all + # Post.paginate_all_by_something => Post.find_all_by_something + # Post.paginate_by_something => Post.find_all_by_something + # + # == The importance of the :order parameter + # + # In ActiveRecord finders, :order parameter specifies columns for + # the ORDER BY clause in SQL. It is important to have it, since + # pagination only makes sense with ordered sets. Without the ORDER + # BY clause, databases aren't required to do consistent ordering when + # performing SELECT queries; this is especially true for + # PostgreSQL. + # + # Therefore, make sure you are doing ordering on a column that makes the + # most sense in the current context. Make that obvious to the user, also. + # For perfomance reasons you will also want to add an index to that column. + module ActiveRecord + include WillPaginate::Finders::Base + + # Wraps +find_by_sql+ by simply adding LIMIT and OFFSET to your SQL string + # based on the params otherwise used by paginating finds: +page+ and + # +per_page+. + # + # Example: + # + # @developers = Developer.paginate_by_sql ['select * from developers where salary > ?', 80000], + # :page => params[:page], :per_page => 3 + # + # A query for counting rows will automatically be generated if you don't + # supply :total_entries. If you experience problems with this + # generated SQL, you might want to perform the count manually in your + # application. + # + def paginate_by_sql(sql, options) + WillPaginate::Collection.create(*wp_parse_options(options)) do |pager| + query = sanitize_sql(sql.dup) + original_query = query.dup + # add limit, offset + add_limit! query, :offset => pager.offset, :limit => pager.per_page + # perfom the find + pager.replace find_by_sql(query) + + unless pager.total_entries + count_query = original_query.sub /\bORDER\s+BY\s+[\w`,\s]+$/mi, '' + count_query = "SELECT COUNT(*) FROM (#{count_query})" + + unless ['oracle', 'oci'].include?(self.connection.adapter_name.downcase) + count_query << ' AS count_table' + end + # perform the count query + pager.total_entries = count_by_sql(count_query) + end + end + end + + def respond_to?(method, include_priv = false) #:nodoc: + super(method.to_s.sub(/^paginate/, 'find'), include_priv) + end + + protected + + def method_missing_with_paginate(method, *args, &block) #:nodoc: + # did somebody tried to paginate? if not, let them be + unless method.to_s.index('paginate') == 0 + return method_missing_without_paginate(method, *args, &block) + end + + # paginate finders are really just find_* with limit and offset + finder = method.to_s.sub('paginate', 'find') + finder.sub!('find', 'find_all') if finder.index('find_by_') == 0 + + options = args.pop + raise ArgumentError, 'parameter hash expected' unless options.respond_to? :symbolize_keys + options = options.dup + options[:finder] = finder + args << options + + paginate(*args, &block) + end + + def wp_query(options, pager, args, &block) + finder = (options.delete(:finder) || 'find').to_s + find_options = options.except(:count).update(:offset => pager.offset, :limit => pager.per_page) + + if finder == 'find' + if Array === args.first and !pager.total_entries + pager.total_entries = args.first.size + end + args << :all if args.empty? + end + + args << find_options + pager.replace send(finder, *args, &block) + + unless pager.total_entries + # magic counting + pager.total_entries = wp_count(options, args, finder) + end + end + + # Does the not-so-trivial job of finding out the total number of entries + # in the database. It relies on the ActiveRecord +count+ method. + def wp_count(options, args, finder) + # find out if we are in a model or an association proxy + klass = (@owner and @reflection) ? @reflection.klass : self + count_options = wp_parse_count_options(options, klass) + + # we may have to scope ... + counter = Proc.new { count(count_options) } + + count = if finder.index('find_') == 0 and klass.respond_to?(scoper = finder.sub('find', 'with')) + # scope_out adds a 'with_finder' method which acts like with_scope, if it's present + # then execute the count with the scoping provided by the with_finder + send(scoper, &counter) + elsif finder =~ /^find_(all_by|by)_([_a-zA-Z]\w*)$/ + # extract conditions from calls like "paginate_by_foo_and_bar" + attribute_names = $2.split('_and_') + conditions = construct_attributes_from_arguments(attribute_names, args) + with_scope(:find => { :conditions => conditions }, &counter) + else + counter.call + end + + count.respond_to?(:length) ? count.length : count + end + + def wp_parse_count_options(options, klass) + excludees = [:count, :order, :limit, :offset, :readonly] + + unless ::ActiveRecord::Calculations::CALCULATIONS_OPTIONS.include?(:from) + # :from parameter wasn't supported in count() before this change + excludees << :from + end + + # Use :select from scope if it isn't already present. + options[:select] = scope(:find, :select) unless options[:select] + + if options[:select] and options[:select] =~ /^\s*DISTINCT\b/i + # Remove quoting and check for table_name.*-like statement. + if options[:select].gsub('`', '') =~ /\w+\.\*/ + options[:select] = "DISTINCT #{klass.table_name}.#{klass.primary_key}" + end + else + excludees << :select + end + + # count expects (almost) the same options as find + count_options = options.except *excludees + + # merge the hash found in :count + # this allows you to specify :select, :order, or anything else just for the count query + count_options.update options[:count] if options[:count] + + # forget about includes if they are irrelevant (Rails 2.1) + if count_options[:include] and + klass.private_methods.include?('references_eager_loaded_tables?') and + !klass.send(:references_eager_loaded_tables?, count_options) + count_options.delete :include + end + + count_options + end + end +end + +ActiveRecord::Base.class_eval do + extend WillPaginate::Finders::ActiveRecord + class << self + alias_method_chain :method_missing, :paginate + end +end + +# support pagination on associations +a = ActiveRecord::Associations +returning([ a::AssociationCollection ]) { |classes| + # detect http://dev.rubyonrails.org/changeset/9230 + unless a::HasManyThroughAssociation.superclass == a::HasManyAssociation + classes << a::HasManyThroughAssociation + end +}.each do |klass| + klass.send :include, WillPaginate::Finders::ActiveRecord + klass.class_eval { alias_method_chain :method_missing, :paginate } +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/finders/active_record/named_scope.rb b/vendor/plugins/will_paginate/lib/will_paginate/finders/active_record/named_scope.rb new file mode 100644 index 0000000..21fc168 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/finders/active_record/named_scope.rb @@ -0,0 +1,170 @@ +module WillPaginate + # This is a feature backported from Rails 2.1 because of its usefullness not only with will_paginate, + # but in other aspects when managing complex conditions that you want to be reusable. + module NamedScope + # All subclasses of ActiveRecord::Base have two named_scopes: + # * all, which is similar to a find(:all) query, and + # * scoped, which allows for the creation of anonymous scopes, on the fly: Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions) + # + # These anonymous scopes tend to be useful when procedurally generating complex queries, where passing + # intermediate values (scopes) around as first-class objects is convenient. + def self.included(base) + base.class_eval do + extend ClassMethods + named_scope :scoped, lambda { |scope| scope } + end + end + + module ClassMethods + def scopes + read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {}) + end + + # Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query, + # such as :conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions. + # + # class Shirt < ActiveRecord::Base + # named_scope :red, :conditions => {:color => 'red'} + # named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true] + # end + # + # The above calls to named_scope define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red, + # in effect, represents the query Shirt.find(:all, :conditions => {:color => 'red'}). + # + # Unlike Shirt.find(...), however, the object returned by Shirt.red is not an Array; it resembles the association object + # constructed by a has_many declaration. For instance, you can invoke Shirt.red.find(:first), Shirt.red.count, + # Shirt.red.find(:all, :conditions => {:size => 'small'}). Also, just + # as with the association objects, name scopes acts like an Array, implementing Enumerable; Shirt.red.each(&block), + # Shirt.red.first, and Shirt.red.inject(memo, &block) all behave as if Shirt.red really were an Array. + # + # These named scopes are composable. For instance, Shirt.red.dry_clean_only will produce all shirts that are both red and dry clean only. + # Nested finds and calculations also work with these compositions: Shirt.red.dry_clean_only.count returns the number of garments + # for which these criteria obtain. Similarly with Shirt.red.dry_clean_only.average(:thread_count). + # + # All scopes are available as class methods on the ActiveRecord::Base descendent upon which the scopes were defined. But they are also available to + # has_many associations. If, + # + # class Person < ActiveRecord::Base + # has_many :shirts + # end + # + # then elton.shirts.red.dry_clean_only will return all of Elton's red, dry clean + # only shirts. + # + # Named scopes can also be procedural. + # + # class Shirt < ActiveRecord::Base + # named_scope :colored, lambda { |color| + # { :conditions => { :color => color } } + # } + # end + # + # In this example, Shirt.colored('puce') finds all puce shirts. + # + # Named scopes can also have extensions, just as with has_many declarations: + # + # class Shirt < ActiveRecord::Base + # named_scope :red, :conditions => {:color => 'red'} do + # def dom_id + # 'red_shirts' + # end + # end + # end + # + # + # For testing complex named scopes, you can examine the scoping options using the + # proxy_options method on the proxy itself. + # + # class Shirt < ActiveRecord::Base + # named_scope :colored, lambda { |color| + # { :conditions => { :color => color } } + # } + # end + # + # expected_options = { :conditions => { :colored => 'red' } } + # assert_equal expected_options, Shirt.colored('red').proxy_options + def named_scope(name, options = {}, &block) + name = name.to_sym + scopes[name] = lambda do |parent_scope, *args| + Scope.new(parent_scope, case options + when Hash + options + when Proc + options.call(*args) + end, &block) + end + (class << self; self end).instance_eval do + define_method name do |*args| + scopes[name].call(self, *args) + end + end + end + end + + class Scope + attr_reader :proxy_scope, :proxy_options + + [].methods.each do |m| + unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|^find$|count|sum|average|maximum|minimum|paginate|first|last|empty\?|respond_to\?)/ + delegate m, :to => :proxy_found + end + end + + delegate :scopes, :with_scope, :to => :proxy_scope + + def initialize(proxy_scope, options, &block) + [options[:extend]].flatten.each { |extension| extend extension } if options[:extend] + extend Module.new(&block) if block_given? + @proxy_scope, @proxy_options = proxy_scope, options.except(:extend) + end + + def reload + load_found; self + end + + def first(*args) + if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash)) + proxy_found.first(*args) + else + find(:first, *args) + end + end + + def last(*args) + if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash)) + proxy_found.last(*args) + else + find(:last, *args) + end + end + + def empty? + @found ? @found.empty? : count.zero? + end + + def respond_to?(method, include_private = false) + super || @proxy_scope.respond_to?(method, include_private) + end + + protected + def proxy_found + @found || load_found + end + + private + def method_missing(method, *args, &block) + if scopes.include?(method) + scopes[method].call(self, *args) + else + with_scope :find => proxy_options do + proxy_scope.send(method, *args, &block) + end + end + end + + def load_found + @found = find(:all) + end + end + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/finders/active_record/named_scope_patch.rb b/vendor/plugins/will_paginate/lib/will_paginate/finders/active_record/named_scope_patch.rb new file mode 100644 index 0000000..bdc1997 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/finders/active_record/named_scope_patch.rb @@ -0,0 +1,39 @@ +## based on http://dev.rubyonrails.org/changeset/9084 + +ActiveRecord::Associations::AssociationProxy.class_eval do + protected + def with_scope(*args, &block) + @reflection.klass.send :with_scope, *args, &block + end +end + +[ ActiveRecord::Associations::AssociationCollection, + ActiveRecord::Associations::HasManyThroughAssociation ].each do |klass| + klass.class_eval do + protected + alias :method_missing_without_scopes :method_missing_without_paginate + def method_missing_without_paginate(method, *args, &block) + if @reflection.klass.scopes.include?(method) + @reflection.klass.scopes[method].call(self, *args, &block) + else + method_missing_without_scopes(method, *args, &block) + end + end + end +end + +# Rails 1.2.6 +ActiveRecord::Associations::HasAndBelongsToManyAssociation.class_eval do + protected + def method_missing(method, *args, &block) + if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method)) + super + elsif @reflection.klass.scopes.include?(method) + @reflection.klass.scopes[method].call(self, *args) + else + @reflection.klass.with_scope(:find => { :conditions => @finder_sql, :joins => @join_sql, :readonly => false }) do + @reflection.klass.send(method, *args, &block) + end + end + end +end if ActiveRecord::Base.respond_to? :find_first diff --git a/vendor/plugins/will_paginate/lib/will_paginate/finders/active_resource.rb b/vendor/plugins/will_paginate/lib/will_paginate/finders/active_resource.rb new file mode 100644 index 0000000..9ba0236 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/finders/active_resource.rb @@ -0,0 +1,48 @@ +require 'will_paginate/finders/base' +require 'active_resource' + +module WillPaginate::Finders + # Paginate your ActiveResource models. + # + # @posts = Post.paginate :all, :params => { :page => params[:page], :order => 'created_at DESC' } + module ActiveResource + include WillPaginate::Finders::Base + + protected + + def wp_query(options, pager, args, &block) + unless args.empty? or args.first == :all + raise ArgumentError, "finder arguments other than :all are not supported for pagination (#{args.inspect} given)" + end + params = (options[:params] ||= {}) + params[:page] = pager.current_page + params[:per_page] = pager.per_page + + pager.replace find_every(options, &block) + end + + # Takes the format that Hash.from_xml produces out of an unknown type + # (produced by WillPaginate::Collection#to_xml_with_collection_type), + # parses it into a WillPaginate::Collection, + # and forwards the result to the former +instantiate_collection+ method. + # It only does this for hashes that have a :type => "collection". + def instantiate_collection_with_collection(collection, prefix_options = {}) + if collection.is_a?(Hash) && collection["type"] == "collection" + collectables = collection.values.find{ |c| c.is_a?(Hash) || c.is_a?(Array) } + collectables = [collectables].compact unless collectables.kind_of?(Array) + instantiated_collection = WillPaginate::Collection.create(collection["current_page"], collection["per_page"], collection["total_entries"]) do |pager| + pager.replace instantiate_collection_without_collection(collectables, prefix_options) + end + else + instantiate_collection_without_collection(collection, prefix_options) + end + end + end +end + +ActiveResource::Base.class_eval do + extend WillPaginate::Finders::ActiveResource + class << self + # alias_method_chain :instantiate_collection, :collection + end +end \ No newline at end of file diff --git a/vendor/plugins/will_paginate/lib/will_paginate/finders/base.rb b/vendor/plugins/will_paginate/lib/will_paginate/finders/base.rb new file mode 100644 index 0000000..f643244 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/finders/base.rb @@ -0,0 +1,80 @@ +require 'will_paginate/core_ext' + +module WillPaginate + module Finders + # Database-agnostic finder logic + module Base + def per_page + @per_page ||= 30 + end + + def per_page=(limit) + @per_page = limit.to_i + end + + # This is the main paginating finder. + # + # == Special parameters for paginating finders + # * :page -- REQUIRED, but defaults to 1 if false or nil + # * :per_page -- defaults to CurrentModel.per_page (which is 30 if not overridden) + # * :total_entries -- use only if you manually count total entries + # * :count -- additional options that are passed on to +count+ + # * :finder -- name of the finder method to use (default: "find") + # + # All other options (+conditions+, +order+, ...) are forwarded to +find+ + # and +count+ calls. + def paginate(*args, &block) + options = args.pop + page, per_page, total_entries = wp_parse_options(options) + + WillPaginate::Collection.create(page, per_page, total_entries) do |pager| + query_options = options.except :page, :per_page, :total_entries + wp_query(query_options, pager, args, &block) + end + end + + # Iterates through all records by loading one page at a time. This is useful + # for migrations or any other use case where you don't want to load all the + # records in memory at once. + # + # It uses +paginate+ internally; therefore it accepts all of its options. + # You can specify a starting page with :page (default is 1). Default + # :order is "id", override if necessary. + # + # {Jamis Buck describes this}[http://weblog.jamisbuck.org/2007/4/6/faking-cursors-in-activerecord] + # and also uses a more efficient way for MySQL. + def paginated_each(options = {}, &block) + options = { :order => 'id', :page => 1 }.merge options + options[:page] = options[:page].to_i + options[:total_entries] = 0 # skip the individual count queries + total = 0 + + begin + collection = paginate(options) + total += collection.each(&block).size + options[:page] += 1 + end until collection.size < collection.per_page + + total + end + + protected + + def wp_parse_options(options) #:nodoc: + raise ArgumentError, 'parameter hash expected' unless Hash === options + raise ArgumentError, ':page parameter required' unless options.key? :page + + if options[:count] and options[:total_entries] + raise ArgumentError, ':count and :total_entries are mutually exclusive' + end + + page = options[:page] || 1 + per_page = options[:per_page] || self.per_page + total = options[:total_entries] + + return [page, per_page, total] + end + + end + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/finders/data_mapper.rb b/vendor/plugins/will_paginate/lib/will_paginate/finders/data_mapper.rb new file mode 100644 index 0000000..c31c5fb --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/finders/data_mapper.rb @@ -0,0 +1,30 @@ +require 'will_paginate/finders/base' +require 'dm-core' + +module WillPaginate::Finders + module DataMapper + include WillPaginate::Finders::Base + + protected + + def wp_query(options, pager, args, &block) + find_options = options.except(:count).update(:offset => pager.offset, :limit => pager.per_page) + + pager.replace all(find_options, &block) + + unless pager.total_entries + pager.total_entries = wp_count(options) + end + end + + def wp_count(options) + count_options = options.except(:count, :order) + # merge the hash found in :count + count_options.update options[:count] if options[:count] + + count_options.empty?? count() : count(count_options) + end + end +end + +DataMapper::Model.send(:include, WillPaginate::Finders::DataMapper) diff --git a/vendor/plugins/will_paginate/lib/will_paginate/version.rb b/vendor/plugins/will_paginate/lib/will_paginate/version.rb new file mode 100644 index 0000000..ba92b54 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/version.rb @@ -0,0 +1,9 @@ +module WillPaginate #:nodoc: + module VERSION #:nodoc: + MAJOR = 2 + MINOR = 5 + TINY = 0 + + STRING = [MAJOR, MINOR, TINY].join('.') + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb new file mode 100644 index 0000000..9917cc5 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb @@ -0,0 +1,39 @@ +require 'will_paginate/deprecation' + +module WillPaginate + # = Will Paginate view helpers + # + # Currently there is only one view helper: +will_paginate+. It renders the + # pagination links for the given collection. The helper itself is lightweight + # and serves only as a wrapper around link renderer instantiation; the + # renderer then does all the hard work of generating the HTML. + # + # == Global options for helpers + # + # Options for pagination helpers are optional and get their default values from the + # WillPaginate::ViewHelpers.pagination_options hash. You can write to this hash to + # override default options on the global level: + # + # WillPaginate::ViewHelpers.pagination_options[:previous_label] = 'Previous page' + # + # By putting this into your environment.rb you can easily translate link texts to previous + # and next pages, as well as override some other defaults to your liking. + module ViewHelpers + def self.pagination_options() @pagination_options; end + def self.pagination_options=(value) @pagination_options = value; end + + self.pagination_options = { + :class => 'pagination', + :previous_label => '« Previous', + :next_label => 'Next »', + :inner_window => 4, # links around the current page + :outer_window => 1, # links around beginning and end + :separator => ' ', # single space is friendly to spiders and non-graphic browsers + :param_name => :page, + :params => nil, + :renderer => 'WillPaginate::ViewHelpers::LinkRenderer', + :page_links => true, + :container => true + } + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/action_view.rb b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/action_view.rb new file mode 100644 index 0000000..51e8525 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/action_view.rb @@ -0,0 +1,82 @@ +require 'will_paginate/view_helpers/base' +require 'action_view' +require 'will_paginate/view_helpers/link_renderer' + +module WillPaginate + module ViewHelpers + # ActionView helpers for Rails integration + module ActionView + include WillPaginate::ViewHelpers::Base + + def will_paginate(collection = nil, options = {}) + options, collection = collection, nil if collection.is_a? Hash + collection ||= infer_collection_from_controller + + super(collection, options.symbolize_keys) + end + + def page_entries_info(collection = nil, options = {}) + options, collection = collection, nil if collection.is_a? Hash + collection ||= infer_collection_from_controller + + super(collection, options.symbolize_keys) + end + + # Wrapper for rendering pagination links at both top and bottom of a block + # of content. + # + # <% paginated_section @posts do %> + #
      + # <% for post in @posts %> + #
    1. ...
    2. + # <% end %> + #
    + # <% end %> + # + # will result in: + # + # + #
      + # ... + #
    + # + # + # Arguments are passed to a will_paginate call, so the same options + # apply. Don't use the :id option; otherwise you'll finish with two + # blocks of pagination links sharing the same ID (which is invalid HTML). + def paginated_section(*args, &block) + pagination = will_paginate(*args).to_s + content = pagination + capture(&block) + pagination + concat content, block.binding + end + + protected + + def infer_collection_from_controller + collection_name = "@#{controller.controller_name}" + collection = instance_variable_get(collection_name) + raise ArgumentError, "The #{collection_name} variable appears to be empty. Did you " + + "forget to pass the collection object for will_paginate?" if collection.nil? + collection + end + end + end +end + +ActionView::Base.send :include, WillPaginate::ViewHelpers::ActionView + +if defined?(ActionController::Base) and ActionController::Base.respond_to? :rescue_responses + ActionController::Base.rescue_responses['WillPaginate::InvalidPage'] = :not_found +end + +WillPaginate::ViewHelpers::LinkRenderer.class_eval do + protected + + def default_url_params + { :escape => false } + end + + def generate_url(params) + @template.url_for(params) + end +end \ No newline at end of file diff --git a/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/base.rb b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/base.rb new file mode 100644 index 0000000..c5a0ecb --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/base.rb @@ -0,0 +1,137 @@ +require 'will_paginate/core_ext' +require 'will_paginate/view_helpers' + +module WillPaginate + module ViewHelpers + module Base + # Renders Digg/Flickr-style pagination for a WillPaginate::Collection + # object. Nil is returned if there is only one page in total; no point in + # rendering the pagination in that case... + # + # ==== Options + # * :class -- CSS class name for the generated DIV (default: "pagination") + # * :previous_label -- default: "« Previous" + # * :next_label -- default: "Next »" + # * :inner_window -- how many links are shown around the current page (default: 4) + # * :outer_window -- how many links are around the first and the last page (default: 1) + # * :separator -- string separator for page HTML elements (default: single space) + # * :param_name -- parameter name for page number in URLs (default: :page) + # * :params -- additional parameters when generating pagination links + # (eg. :controller => "foo", :action => nil) + # * :renderer -- class name, class or instance of a link renderer (default: + # WillPaginate::LinkRenderer) + # * :page_links -- when false, only previous/next links are rendered (default: true) + # * :container -- toggles rendering of the DIV container for pagination links, set to + # false only when you are rendering your own pagination markup (default: true) + # * :id -- HTML ID for the container (default: nil). Pass +true+ to have the ID + # automatically generated from the class name of objects in collection: for example, paginating + # ArticleComment models would yield an ID of "article_comments_pagination". + # + # All options beside listed ones are passed as HTML attributes to the container + # element for pagination links (the DIV). For example: + # + # <%= will_paginate @posts, :id => 'wp_posts' %> + # + # ... will result in: + # + # + # + # ==== Using the helper without arguments + # If the helper is called without passing in the collection object, it will + # try to read from the instance variable inferred by the controller name. + # For example, calling +will_paginate+ while the current controller is + # PostsController will result in trying to read from the @posts + # variable. Example: + # + # <%= will_paginate :id => true %> + # + # ... will result in @post collection getting paginated: + # + # + # + def will_paginate(collection, options = {}) + # early exit if there is nothing to render + return nil unless collection.total_pages > 1 + + options = WillPaginate::ViewHelpers.pagination_options.merge(options) + + if options[:prev_label] + WillPaginate::Deprecation::warn(":prev_label view parameter is now :previous_label; the old name has been deprecated.") + options[:previous_label] = options.delete(:prev_label) + end + + # get the renderer instance + renderer = case options[:renderer] + when String + options[:renderer].constantize.new + when Class + options[:renderer].new + else + options[:renderer] + end + # render HTML for pagination + renderer.prepare collection, options, self + renderer.to_html + end + + # Renders a helpful message with numbers of displayed vs. total entries. + # You can use this as a blueprint for your own, similar helpers. + # + # <%= page_entries_info @posts %> + # #-> Displaying posts 6 - 10 of 26 in total + # + # By default, the message will use the humanized class name of objects + # in collection: for instance, "project types" for ProjectType models. + # Override this to your liking with the :entry_name parameter: + # + # <%= page_entries_info @posts, :entry_name => 'item' %> + # #-> Displaying items 6 - 10 of 26 in total + # + # Entry name is entered in singular and pluralized with + # String#pluralize method from ActiveSupport. If it isn't + # loaded, specify plural with :plural_name parameter: + # + # <%= page_entries_info @posts, :entry_name => 'item', :plural_name => 'items' %> + # + # By default, this method produces HTML output. You can trigger plain + # text output by passing :html => false in options. + def page_entries_info(collection, options = {}) + entry_name = options[:entry_name] || (collection.empty?? 'entry' : + collection.first.class.name.underscore.gsub('_', ' ')) + + plural_name = if options[:plural_name] + options[:plural_name] + elsif entry_name == 'entry' + plural_name = 'entries' + elsif entry_name.respond_to? :pluralize + plural_name = entry_name.pluralize + else + entry_name + 's' + end + + unless options[:html] == false + b = '' + eb = '' + sp = ' ' + else + b = eb = '' + sp = ' ' + end + + if collection.total_pages < 2 + case collection.size + when 0; "No #{plural_name} found" + when 1; "Displaying #{b}1#{eb} #{entry_name}" + else; "Displaying #{b}all #{collection.size}#{eb} #{plural_name}" + end + else + %{Displaying #{plural_name} #{b}%d#{sp}-#{sp}%d#{eb} of #{b}%d#{eb} in total} % [ + collection.offset + 1, + collection.offset + collection.length, + collection.total_entries + ] + end + end + end + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/link_renderer.rb b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/link_renderer.rb new file mode 100644 index 0000000..305155d --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/link_renderer.rb @@ -0,0 +1,177 @@ +require 'cgi' +require 'will_paginate/core_ext' +require 'will_paginate/view_helpers/link_renderer_base' + +module WillPaginate + module ViewHelpers + # This class does the heavy lifting of actually building the pagination + # links. It is used by +will_paginate+ helper internally. + class LinkRenderer < LinkRendererBase + + # * +collection+ is a WillPaginate::Collection instance or any other object + # that conforms to that API + # * +options+ are forwarded from +will_paginate+ view helper + # * +template+ is the reference to the template being rendered + def prepare(collection, options, template) + super(collection, options) + @template = template + @container_attributes = @base_url_params = nil + end + + # Process it! This method returns the complete HTML string which contains + # pagination links. Feel free to subclass LinkRenderer and change this + # method as you see fit. + def to_html + html = pagination.map do |item| + item.is_a?(Fixnum) ? + page_number(item) : + send(item) + end.join(@options[:separator]) + + @options[:container] ? html_container(html) : html + end + + # Returns the subset of +options+ this instance was initialized with that + # represent HTML attributes for the container element of pagination links. + def container_attributes + @container_attributes ||= begin + attributes = @options.except *(WillPaginate::ViewHelpers.pagination_options.keys - [:class]) + # pagination of Post models will have the ID of "posts_pagination" + if @options[:container] and @options[:id] === true + attributes[:id] = @collection.first.class.name.underscore.pluralize + '_pagination' + end + attributes + end + end + + protected + + def page_number(page) + unless page == current_page + link(page, page, :rel => rel_value(page)) + else + tag(:em, page) + end + end + + def gap + '' + end + + def previous_page + previous_or_next_page(@collection.previous_page, @options[:previous_label], 'previous_page') + end + + def next_page + previous_or_next_page(@collection.next_page, @options[:next_label], 'next_page') + end + + def previous_or_next_page(page, text, classname) + if page + link(text, page, :class => classname) + else + tag(:span, text, :class => classname + ' disabled') + end + end + + def html_container(html) + tag(:div, html, container_attributes) + end + + # Returns URL params for +page_link_or_span+, taking the current GET params + # and :params option into account. + def url(page) + @base_url_params ||= begin + url_params = base_url_params + merge_optional_params(url_params) + url_params + end + + url_params = @base_url_params.dup + add_current_page_param(url_params, page) + + generate_url(url_params) + end + + def default_url_params + { } + end + + def base_url_params + url_params = default_url_params + # page links should preserve GET parameters + symbolized_update(url_params, @template.params) if get_request? + url_params + end + + def merge_optional_params(url_params) + symbolized_update(url_params, @options[:params]) if @options[:params] + end + + def add_current_page_param(url_params, page) + unless param_name.index(/[^\w-]/) + url_params[param_name.to_sym] = page + else + page_param = (defined?(CGIMethods) ? CGIMethods : ActionController::AbstractRequest). + parse_query_parameters(param_name + '=' + page.to_s) + + symbolized_update(url_params, page_param) + end + end + + def get_request? + @template.request.get? + end + + def generate_url(params) + @template.url(params) + end + + private + + def link(text, target, attributes = {}) + if target.is_a? Fixnum + attributes[:rel] = rel_value(target) + target = url(target) + end + attributes[:href] = target + tag(:a, text, attributes) + end + + def tag(name, value, attributes = {}) + string_attributes = attributes.inject('') do |attrs, pair| + unless pair.last.nil? + attrs << %( #{pair.first}="#{CGI::escapeHTML(pair.last.to_s)}") + end + attrs + end + "<#{name}#{string_attributes}>#{value}" + end + + def rel_value(page) + case page + when @collection.previous_page; 'prev' + (page == 1 ? ' start' : '') + when @collection.next_page; 'next' + when 1; 'start' + end + end + + def symbolized_update(target, other) + other.each do |key, value| + key = key.to_sym + existing = target[key] + + if value.is_a?(Hash) + target[key] = existing = {} if existing.nil? + if existing.is_a?(Hash) + symbolized_update(existing, value) + return + end + end + + target[key] = value + end + end + end + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/link_renderer_base.rb b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/link_renderer_base.rb new file mode 100644 index 0000000..26f0f72 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/link_renderer_base.rb @@ -0,0 +1,76 @@ +require 'will_paginate/view_helpers' + +module WillPaginate + module ViewHelpers + # This class does the heavy lifting of actually building the pagination + # links. It is used by +will_paginate+ helper internally. + class LinkRendererBase + + # * +collection+ is a WillPaginate::Collection instance or any other object + # that conforms to that API + # * +options+ are forwarded from +will_paginate+ view helper + def prepare(collection, options) + @collection = collection + @options = options + + # reset values in case we're re-using this instance + @total_pages = @param_name = nil + end + + def pagination + items = @options[:page_links] ? windowed_page_numbers : [] + items.unshift :previous_page + items.push :next_page + end + + protected + + # Calculates visible page numbers using the :inner_window and + # :outer_window options. + def windowed_page_numbers + inner_window, outer_window = @options[:inner_window].to_i, @options[:outer_window].to_i + window_from = current_page - inner_window + window_to = current_page + inner_window + + # adjust lower or upper limit if other is out of bounds + if window_to > total_pages + window_from -= window_to - total_pages + window_to = total_pages + end + if window_from < 1 + window_to += 1 - window_from + window_from = 1 + window_to = total_pages if window_to > total_pages + end + + visible = (1..total_pages).to_a + left_gap = (2 + outer_window)...window_from + right_gap = (window_to + 1)...(total_pages - outer_window) + + # replace page numbers that shouldn't be visible with `:gap` + [right_gap, left_gap].each do |gap| + if (gap.last - gap.first) > 1 + visible -= gap.to_a + visible.insert(gap.first - 1, :gap) + end + end + + visible + end + + private + + def current_page + @collection.current_page + end + + def total_pages + @collection.total_pages + end + + def param_name + @param_name ||= @options[:param_name].to_s + end + end + end +end diff --git a/vendor/plugins/will_paginate/spec/collection_spec.rb b/vendor/plugins/will_paginate/spec/collection_spec.rb new file mode 100644 index 0000000..4d71dc9 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/collection_spec.rb @@ -0,0 +1,147 @@ +require 'will_paginate/array' +require 'spec_helper' + +describe WillPaginate::Collection do + + before :all do + @simple = ('a'..'e').to_a + end + + it "should be a subset of original collection" do + @simple.paginate(:page => 1, :per_page => 3).should == %w( a b c ) + end + + it "can be shorter than per_page if on last page" do + @simple.paginate(:page => 2, :per_page => 3).should == %w( d e ) + end + + it "should include whole collection if per_page permits" do + @simple.paginate(:page => 1, :per_page => 5).should == @simple + end + + it "should be empty if out of bounds" do + @simple.paginate(:page => 2, :per_page => 5).should be_empty + end + + it "should default to 1 as current page and 30 per-page" do + result = (1..50).to_a.paginate + result.current_page.should == 1 + result.size.should == 30 + end + + describe "old API" do + it "should fail with numeric params" do + Proc.new { [].paginate(2) }.should raise_error(ArgumentError) + Proc.new { [].paginate(2, 10) }.should raise_error(ArgumentError) + end + + it "should fail with both options and numeric param" do + Proc.new { [].paginate({}, 5) }.should raise_error(ArgumentError) + end + end + + it "should give total_entries precedence over actual size" do + %w(a b c).paginate(:total_entries => 5).total_entries.should == 5 + end + + it "should be an augmented Array" do + entries = %w(a b c) + collection = create(2, 3, 10) do |pager| + pager.replace(entries).should == entries + end + + collection.should == entries + for method in %w(total_pages each offset size current_page per_page total_entries) + collection.should respond_to(method) + end + collection.should be_kind_of(Array) + collection.entries.should be_instance_of(Array) + # TODO: move to another expectation: + collection.offset.should == 3 + collection.total_pages.should == 4 + collection.should_not be_out_of_bounds + end + + describe "previous/next pages" do + it "should have previous_page nil when on first page" do + collection = create(1, 1, 3) + collection.previous_page.should be_nil + collection.next_page.should == 2 + end + + it "should have both prev/next pages" do + collection = create(2, 1, 3) + collection.previous_page.should == 1 + collection.next_page.should == 3 + end + + it "should have next_page nil when on last page" do + collection = create(3, 1, 3) + collection.previous_page.should == 2 + collection.next_page.should be_nil + end + end + + it "should show out of bounds when page number is too high" do + create(2, 3, 2).should be_out_of_bounds + end + + it "should not show out of bounds when inside collection" do + create(1, 3, 2).should_not be_out_of_bounds + end + + describe "guessing total count" do + it "can guess when collection is shorter than limit" do + collection = create { |p| p.replace array } + collection.total_entries.should == 8 + end + + it "should allow explicit total count to override guessed" do + collection = create(2, 5, 10) { |p| p.replace array } + collection.total_entries.should == 10 + end + + it "should not be able to guess when collection is same as limit" do + collection = create { |p| p.replace array(5) } + collection.total_entries.should be_nil + end + + it "should not be able to guess when collection is empty" do + collection = create { |p| p.replace array(0) } + collection.total_entries.should be_nil + end + + it "should be able to guess when collection is empty and this is the first page" do + collection = create(1) { |p| p.replace array(0) } + collection.total_entries.should == 0 + end + end + + it "should raise WillPaginate::InvalidPage on invalid input" do + for bad_input in [0, -1, nil, '', 'Schnitzel'] + Proc.new { create bad_input }.should raise_error(WillPaginate::InvalidPage) + end + end + + it "should raise Argument error on invalid per_page setting" do + Proc.new { create(1, -1) }.should raise_error(ArgumentError) + end + + it "should not respond to page_count anymore" do + Proc.new { create.page_count }.should raise_error(NoMethodError) + end + + private + + def create(page = 2, limit = 5, total = nil, &block) + if block_given? + WillPaginate::Collection.create(page, limit, total, &block) + else + WillPaginate::Collection.new(page, limit, total) + end + end + + def array(size = 3) + Array.new(size) + end +end diff --git a/vendor/plugins/will_paginate/spec/console b/vendor/plugins/will_paginate/spec/console new file mode 100755 index 0000000..0d3a360 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/console @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb' +libs = [] + +libs << 'irb/completion' +libs << 'console_fixtures' + +exec "#{irb} -Ilib:spec#{libs.map{ |l| " -r #{l}" }.join} --simple-prompt" diff --git a/vendor/plugins/will_paginate/spec/console_fixtures.rb b/vendor/plugins/will_paginate/spec/console_fixtures.rb new file mode 100644 index 0000000..1f0853f --- /dev/null +++ b/vendor/plugins/will_paginate/spec/console_fixtures.rb @@ -0,0 +1,8 @@ +require 'will_paginate/finders/active_record' +require 'finders/activerecord_test_connector' +ActiverecordTestConnector.setup + +# load all fixtures +Fixtures.create_fixtures(ActiverecordTestConnector::FIXTURES_PATH, ActiveRecord::Base.connection.tables) + + diff --git a/vendor/plugins/will_paginate/spec/database.yml b/vendor/plugins/will_paginate/spec/database.yml new file mode 100644 index 0000000..7ef1e73 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/database.yml @@ -0,0 +1,22 @@ +sqlite3: + database: ":memory:" + adapter: sqlite3 + timeout: 500 + +sqlite2: + database: ":memory:" + adapter: sqlite2 + +mysql: + adapter: mysql + username: rails + password: mislav + encoding: utf8 + database: will_paginate_unittest + +postgres: + adapter: postgresql + username: mislav + password: mislav + database: will_paginate_unittest + min_messages: warning diff --git a/vendor/plugins/will_paginate/spec/finders/active_record_spec.rb b/vendor/plugins/will_paginate/spec/finders/active_record_spec.rb new file mode 100644 index 0000000..48866e1 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/finders/active_record_spec.rb @@ -0,0 +1,460 @@ +require 'spec_helper' +require 'will_paginate/finders/active_record' +require File.dirname(__FILE__) + '/activerecord_test_connector' + +require 'will_paginate' +WillPaginate::enable_named_scope + +class ArProject < ActiveRecord::Base + def self.column_names + ["id"] + end + + named_scope :distinct, :select => "DISTINCT #{table_name}.*" +end + +ActiverecordTestConnector.setup + +describe WillPaginate::Finders::ActiveRecord do + + extend ActiverecordTestConnector::FixtureSetup + + it "should integrate with ActiveRecord::Base" do + ActiveRecord::Base.should respond_to(:paginate) + end + + it "should paginate" do + ArProject.expects(:find).with(:all, { :limit => 5, :offset => 0 }).returns([]) + ArProject.paginate(:page => 1, :per_page => 5) + end + + it "should respond to paginate_by_sql" do + ArProject.should respond_to(:paginate_by_sql) + end + + it "should support explicit :all argument" do + ArProject.expects(:find).with(:all, instance_of(Hash)).returns([]) + ArProject.paginate(:all, :page => nil) + end + + it "should put implicit all in dynamic finders" do + ArProject.expects(:find_all_by_foo).returns([]) + ArProject.expects(:count).returns(0) + ArProject.paginate_by_foo :page => 2 + end + + it "should leave extra parameters intact" do + ArProject.expects(:find).with(:all, {:foo => 'bar', :limit => 4, :offset => 0 }).returns(Array.new(5)) + ArProject.expects(:count).with({:foo => 'bar'}).returns(1) + + ArProject.paginate :foo => 'bar', :page => 1, :per_page => 4 + end + + describe "counting" do + it "should ignore nil in :count parameter" do + ArProject.expects(:find).returns([]) + lambda { ArProject.paginate :page => nil, :count => nil }.should_not raise_error + end + + it "should guess the total count" do + ArProject.expects(:find).returns(Array.new(2)) + ArProject.expects(:count).never + + result = ArProject.paginate :page => 2, :per_page => 4 + result.total_entries.should == 6 + end + + it "should guess that there are no records" do + ArProject.expects(:find).returns([]) + ArProject.expects(:count).never + + result = ArProject.paginate :page => 1, :per_page => 4 + result.total_entries.should == 0 + end + end + + it "should not ignore :select parameter when it says DISTINCT" do + ArProject.stubs(:find).returns([]) + ArProject.expects(:count).with(:select => 'DISTINCT salary').returns(0) + ArProject.paginate :select => 'DISTINCT salary', :page => 2 + end + + it "should count with scoped select when :select => DISTINCT" do + ArProject.stubs(:find).returns([]) + ArProject.expects(:count).with(:select => 'DISTINCT ar_projects.id').returns(0) + ArProject.distinct.paginate :page => 2 + end + + it "should use :with_foo for scope-out compatibility" do + ArProject.expects(:find_best).returns(Array.new(5)) + ArProject.expects(:with_best).returns(1) + + ArProject.paginate_best :page => 1, :per_page => 4 + end + + describe "paginate_by_sql" do + it "should paginate" do + ArProject.expects(:find_by_sql).with(regexp_matches(/sql LIMIT 3(,| OFFSET) 3/)).returns([]) + ArProject.expects(:count_by_sql).with('SELECT COUNT(*) FROM (sql) AS count_table').returns(0) + + ArProject.paginate_by_sql 'sql', :page => 2, :per_page => 3 + end + + it "should respect total_entrier setting" do + ArProject.expects(:find_by_sql).returns([]) + ArProject.expects(:count_by_sql).never + + entries = ArProject.paginate_by_sql 'sql', :page => 1, :total_entries => 999 + entries.total_entries.should == 999 + end + + it "should strip the order when counting" do + ArProject.expects(:find_by_sql).returns([]) + ArProject.expects(:count_by_sql).with("SELECT COUNT(*) FROM (sql\n ) AS count_table").returns(0) + + ArProject.paginate_by_sql "sql\n ORDER\nby foo, bar, `baz` ASC", :page => 2 + end + + it "shouldn't change the original query string" do + query = 'SQL QUERY' + original_query = query.dup + ArProject.expects(:find_by_sql).returns([]) + + ArProject.paginate_by_sql(query, :page => 1) + query.should == original_query + end + end + + # TODO: counts would still be wrong! + it "should be able to paginate custom finders" do + # acts_as_taggable defines find_tagged_with(tag, options) + ArProject.expects(:find_tagged_with).with('will_paginate', :offset => 5, :limit => 5).returns([]) + ArProject.expects(:count).with({}).returns(0) + + ArProject.paginate_tagged_with 'will_paginate', :page => 2, :per_page => 5 + end + + it "should not skip count when given an array argument to a finder" do + ids = (1..8).to_a + ArProject.expects(:find_all_by_id).returns([]) + ArProject.expects(:count).returns(0) + + ArProject.paginate_by_id(ids, :per_page => 3, :page => 2, :order => 'id') + end + + # Is this Rails 2.0? Find out by testing find_all which was removed in [6998] + unless ActiveRecord::Base.respond_to? :find_all + it "should paginate array of IDs" do + # AR finders also accept arrays of IDs + # (this was broken in Rails before [6912]) + lambda { + result = Developer.paginate((1..8).to_a, :per_page => 3, :page => 2, :order => 'id') + result.map(&:id).should == (4..6).to_a + result.total_entries.should == 8 + }.should run_queries(1) + end + end + + it "doesn't mangle options" do + ArProject.expects(:find).returns([]) + options = { :page => 1 } + options.expects(:delete).never + options_before = options.dup + + ArProject.paginate(options) + options.should == options_before + end + + if ::ActiveRecord::Calculations::CALCULATIONS_OPTIONS.include?(:from) + # for ActiveRecord 2.1 and newer + it "keeps the :from parameter in count" do + ArProject.expects(:find).returns([1]) + ArProject.expects(:count).with {|options| options.key?(:from) }.returns(0) + ArProject.paginate(:page => 2, :per_page => 1, :from => 'projects') + end + else + it "excludes :from parameter from count" do + ArProject.expects(:find).returns([1]) + ArProject.expects(:count).with {|options| !options.key?(:from) }.returns(0) + ArProject.paginate(:page => 2, :per_page => 1, :from => 'projects') + end + end + + if ActiverecordTestConnector.able_to_connect + fixtures :topics, :replies, :users, :projects, :developers_projects + + it "should get first page of Topics with a single query" do + lambda { + result = Topic.paginate :page => nil + result.current_page.should == 1 + result.total_pages.should == 1 + result.size.should == 4 + }.should run_queries(1) + end + + it "should get second (inexistent) page of Topics, requiring 2 queries" do + lambda { + result = Topic.paginate :page => 2 + result.total_pages.should == 1 + result.should be_empty + }.should run_queries(2) + end + + it "should paginate with :order" do + result = Topic.paginate :page => 1, :order => 'created_at DESC' + result.should == topics(:futurama, :harvey_birdman, :rails, :ar).reverse + result.total_pages.should == 1 + end + + it "should paginate with :conditions" do + result = Topic.paginate :page => 1, :conditions => ["created_at > ?", 30.minutes.ago] + result.should == topics(:rails, :ar) + result.total_pages.should == 1 + end + + it "should paginate with :include and :conditions" do + result = Topic.paginate \ + :page => 1, + :include => :replies, + :conditions => "replies.content LIKE 'Bird%' ", + :per_page => 10 + + expected = Topic.find :all, + :include => 'replies', + :conditions => "replies.content LIKE 'Bird%' ", + :limit => 10 + + result.should == expected + result.total_entries.should == 1 + end + + it "should paginate with :include and :order" do + result = nil + lambda { + result = Topic.paginate \ + :page => 1, + :include => :replies, + :order => 'replies.created_at asc, topics.created_at asc', + :per_page => 10 + }.should run_queries(2) + + expected = Topic.find :all, + :include => 'replies', + :order => 'replies.created_at asc, topics.created_at asc', + :limit => 10 + + result.should == expected + result.total_entries.should == 4 + end + + # detect ActiveRecord 2.1 + if ActiveRecord::Base.private_methods.include?('references_eager_loaded_tables?') + it "should remove :include for count" do + Developer.expects(:find).returns([1]) + Developer.expects(:count).with({}).returns(0) + + Developer.paginate :page => 1, :per_page => 1, :include => :projects + end + + it "should keep :include for count when they are referenced in :conditions" do + Developer.expects(:find).returns([1]) + Developer.expects(:count).with({ :include => :projects, :conditions => 'projects.id > 2' }).returns(0) + + Developer.paginate :page => 1, :per_page => 1, + :include => :projects, :conditions => 'projects.id > 2' + end + end + + describe "associations" do + it "should paginate with include" do + project = projects(:active_record) + + result = project.topics.paginate \ + :page => 1, + :include => :replies, + :conditions => ["replies.content LIKE ?", 'Nice%'], + :per_page => 10 + + expected = Topic.find :all, + :include => 'replies', + :conditions => ["project_id = #{project.id} AND replies.content LIKE ?", 'Nice%'], + :limit => 10 + + result.should == expected + end + + it "should paginate" do + dhh = users(:david) + expected_name_ordered = projects(:action_controller, :active_record) + expected_id_ordered = projects(:active_record, :action_controller) + + lambda { + # with association-specified order + result = dhh.projects.paginate(:page => 1) + result.should == expected_name_ordered + result.total_entries.should == 2 + }.should run_queries(2) + + # with explicit order + result = dhh.projects.paginate(:page => 1, :order => 'projects.id') + result.should == expected_id_ordered + result.total_entries.should == 2 + + lambda { + dhh.projects.find(:all, :order => 'projects.id', :limit => 4) + }.should_not raise_error + + result = dhh.projects.paginate(:page => 1, :order => 'projects.id', :per_page => 4) + result.should == expected_id_ordered + + # has_many with implicit order + topic = Topic.find(1) + expected = replies(:spam, :witty_retort) + # FIXME: wow, this is ugly + topic.replies.paginate(:page => 1).map(&:id).sort.should == expected.map(&:id).sort + topic.replies.paginate(:page => 1, :order => 'replies.id ASC').should == expected.reverse + end + + it "should paginate through association extension" do + project = Project.find(:first) + expected = [replies(:brave)] + + lambda { + result = project.replies.paginate_recent :page => 1 + result.should == expected + }.should run_queries(1) + end + end + + it "should paginate with joins" do + result = nil + join_sql = 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id' + + lambda { + result = Developer.paginate :page => 1, :joins => join_sql, :conditions => 'project_id = 1' + result.size.should == 2 + developer_names = result.map(&:name) + developer_names.should include('David') + developer_names.should include('Jamis') + }.should run_queries(1) + + lambda { + expected = result.to_a + result = Developer.paginate :page => 1, :joins => join_sql, + :conditions => 'project_id = 1', :count => { :select => "users.id" } + result.should == expected + result.total_entries.should == 2 + }.should run_queries(1) + end + + it "should paginate with group" do + result = nil + lambda { + result = Developer.paginate :page => 1, :per_page => 10, + :group => 'salary', :select => 'salary', :order => 'salary' + }.should run_queries(1) + + expected = users(:david, :jamis, :dev_10, :poor_jamis).map(&:salary).sort + result.map(&:salary).should == expected + end + + it "should paginate with dynamic finder" do + expected = replies(:witty_retort, :spam) + Reply.paginate_by_topic_id(1, :page => 1).should == expected + + result = Developer.paginate :conditions => { :salary => 100000 }, :page => 1, :per_page => 5 + result.total_entries.should == 8 + Developer.paginate_by_salary(100000, :page => 1, :per_page => 5).should == result + end + + it "should paginate with dynamic finder and conditions" do + result = Developer.paginate_by_salary(100000, :page => 1, :conditions => ['id > ?', 6]) + result.total_entries.should == 4 + result.map(&:id).should == (7..10).to_a + end + + it "should raise error when dynamic finder is not recognized" do + lambda { + Developer.paginate_by_inexistent_attribute 100000, :page => 1 + }.should raise_error(NoMethodError) + end + + it "should paginate with_scope" do + result = Developer.with_poor_ones { Developer.paginate :page => 1 } + result.size.should == 2 + result.total_entries.should == 2 + end + + describe "named_scope" do + it "should paginate" do + result = Developer.poor.paginate :page => 1, :per_page => 1 + result.size.should == 1 + result.total_entries.should == 2 + end + + it "should paginate on habtm association" do + project = projects(:active_record) + lambda { + result = project.developers.poor.paginate :page => 1, :per_page => 1 + result.size.should == 1 + result.total_entries.should == 1 + }.should run_queries(2) + end + + it "should paginate on hmt association" do + project = projects(:active_record) + expected = [replies(:brave)] + + lambda { + result = project.replies.recent.paginate :page => 1, :per_page => 1 + result.should == expected + result.total_entries.should == 1 + }.should run_queries(2) + end + + it "should paginate on has_many association" do + project = projects(:active_record) + expected = [topics(:ar)] + + lambda { + result = project.topics.mentions_activerecord.paginate :page => 1, :per_page => 1 + result.should == expected + result.total_entries.should == 1 + }.should run_queries(2) + end + end + + it "should paginate with :readonly option" do + lambda { Developer.paginate :readonly => true, :page => 1 }.should_not raise_error + end + + end + + protected + + def run_queries(num) + QueryCountMatcher.new(num) + end + +end + +class QueryCountMatcher + def initialize(num) + @queries = num + @old_query_count = $query_count + end + + def matches?(block) + block.call + @queries_run = $query_count - @old_query_count + @queries == @queries_run + end + + def failure_message + "expected #{@queries} queries, got #{@queries_run}" + end + + def negative_failure_message + "expected query count not to be #{$queries}" + end +end \ No newline at end of file diff --git a/vendor/plugins/will_paginate/spec/finders/active_resource_spec.rb b/vendor/plugins/will_paginate/spec/finders/active_resource_spec.rb new file mode 100644 index 0000000..e00bb12 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/finders/active_resource_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' +require 'will_paginate/finders/active_resource' +require 'active_resource/http_mock' + +class AresProject < ActiveResource::Base + self.site = 'http://localhost:4000' +end + +describe WillPaginate::Finders::ActiveResource do + + before :all do + # ActiveResource::HttpMock.respond_to do |mock| + # mock.get "/ares_projects.xml?page=1&per_page=5", {}, [].to_xml + # end + end + + it "should integrate with ActiveResource::Base" do + ActiveResource::Base.should respond_to(:paginate) + end + + it "should error when no parameters for #paginate" do + lambda { AresProject.paginate }.should raise_error(ArgumentError) + end + + it "should paginate" do + AresProject.expects(:find_every).with(:params => { :page => 1, :per_page => 5 }).returns([]) + AresProject.paginate(:page => 1, :per_page => 5) + end + + it "should have 30 per_page as default" do + AresProject.expects(:find_every).with(:params => { :page => 1, :per_page => 30 }).returns([]) + AresProject.paginate(:page => 1) + end + + it "should support #paginate(:all)" do + lambda { AresProject.paginate(:all) }.should raise_error(ArgumentError) + end + + it "should error #paginate(:other)" do + lambda { AresProject.paginate(:first) }.should raise_error(ArgumentError) + end + + protected + + def create(page = 2, limit = 5, total = nil, &block) + if block_given? + WillPaginate::Collection.create(page, limit, total, &block) + else + WillPaginate::Collection.new(page, limit, total) + end + end +end diff --git a/vendor/plugins/will_paginate/spec/finders/activerecord_test_connector.rb b/vendor/plugins/will_paginate/spec/finders/activerecord_test_connector.rb new file mode 100644 index 0000000..fdbb233 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/finders/activerecord_test_connector.rb @@ -0,0 +1,107 @@ +require 'active_record' +require 'active_record/version' +require 'active_record/fixtures' + +class ActiverecordTestConnector + cattr_accessor :able_to_connect + cattr_accessor :connected + + FIXTURES_PATH = File.join(File.dirname(__FILE__), '..', 'fixtures') + + # Set our defaults + self.connected = false + self.able_to_connect = true + + def self.setup + unless self.connected || !self.able_to_connect + setup_connection + load_schema + add_load_path FIXTURES_PATH + self.connected = true + end + rescue Exception => e # errors from ActiveRecord setup + $stderr.puts "\nSkipping ActiveRecord tests: #{e}\n\n" + self.able_to_connect = false + end + + private + + def self.add_load_path(path) + dep = defined?(ActiveSupport::Dependencies) ? ActiveSupport::Dependencies : ::Dependencies + dep.load_paths.unshift path + end + + def self.setup_connection + db = ENV['DB'].blank?? 'sqlite3' : ENV['DB'] + + configurations = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'database.yml')) + raise "no configuration for '#{db}'" unless configurations.key? db + configuration = configurations[db] + + ActiveRecord::Base.logger = Logger.new(STDOUT) if $0 == 'irb' + puts "using #{configuration['adapter']} adapter" unless ENV['DB'].blank? + + ActiveRecord::Base.establish_connection(configuration) + ActiveRecord::Base.configurations = { db => configuration } + prepare ActiveRecord::Base.connection + + unless Object.const_defined?(:QUOTED_TYPE) + Object.send :const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type') + end + end + + def self.load_schema + ActiveRecord::Base.silence do + ActiveRecord::Migration.verbose = false + load File.join(FIXTURES_PATH, 'schema.rb') + end + end + + def self.prepare(conn) + class << conn + IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SHOW FIELDS /] + + def execute_with_counting(sql, name = nil, &block) + $query_count ||= 0 + $query_count += 1 unless IGNORED_SQL.any? { |r| sql =~ r } + execute_without_counting(sql, name, &block) + end + + alias_method_chain :execute, :counting + end + end + + module FixtureSetup + def fixtures(*tables) + table_names = tables.map { |t| t.to_s } + + fixtures = Fixtures.create_fixtures ActiverecordTestConnector::FIXTURES_PATH, table_names + @@loaded_fixtures = {} + @@fixture_cache = {} + + unless fixtures.nil? + if fixtures.instance_of?(Fixtures) + @@loaded_fixtures[fixtures.table_name] = fixtures + else + fixtures.each { |f| @@loaded_fixtures[f.table_name] = f } + end + end + + table_names.each do |table_name| + define_method(table_name) do |*fixtures| + @@fixture_cache[table_name] ||= {} + + instances = fixtures.map do |fixture| + if @@loaded_fixtures[table_name][fixture.to_s] + @@fixture_cache[table_name][fixture] ||= @@loaded_fixtures[table_name][fixture.to_s].find + else + raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'" + end + end + + instances.size == 1 ? instances.first : instances + end + end + end + end +end diff --git a/vendor/plugins/will_paginate/spec/finders_spec.rb b/vendor/plugins/will_paginate/spec/finders_spec.rb new file mode 100644 index 0000000..0782fbe --- /dev/null +++ b/vendor/plugins/will_paginate/spec/finders_spec.rb @@ -0,0 +1,76 @@ +require 'spec_helper' +require 'will_paginate/finders/base' + +class Model + extend WillPaginate::Finders::Base +end + +describe WillPaginate::Finders::Base do + it "should define default per_page of 30" do + Model.per_page.should == 30 + end + + it "should allow to set custom per_page" do + begin + Model.per_page = 25 + Model.per_page.should == 25 + ensure + Model.per_page = 30 + end + end + + it "should result with WillPaginate::Collection" do + Model.expects(:wp_query) + Model.paginate(:page => nil).should be_instance_of(WillPaginate::Collection) + end + + it "should delegate pagination to wp_query" do + Model.expects(:wp_query).with({}, instance_of(WillPaginate::Collection), []) + Model.paginate :page => nil + end + + it "should complain when no hash parameters given" do + lambda { + Model.paginate + }.should raise_error(ArgumentError, 'parameter hash expected') + end + + it "should complain when no :page parameter present" do + lambda { + Model.paginate :per_page => 6 + }.should raise_error(ArgumentError, ':page parameter required') + end + + it "should complain when both :count and :total_entries are given" do + lambda { + Model.paginate :page => 1, :count => {}, :total_entries => 1 + }.should raise_error(ArgumentError, ':count and :total_entries are mutually exclusive') + end + + it "should never mangle options" do + options = { :page => 1 } + options.expects(:delete).never + options_before = options.dup + + Model.expects(:wp_query) + Model.paginate(options) + + options.should == options_before + end + + it "should provide paginated_each functionality" do + collection = stub('collection', :size => 5, :empty? => false, :per_page => 5) + collection.expects(:each).times(2).returns(collection) + last_collection = stub('collection', :size => 4, :empty? => false, :per_page => 5) + last_collection.expects(:each).returns(last_collection) + + params = { :order => 'id', :total_entries => 0 } + + Model.expects(:paginate).with(params.merge(:page => 2)).returns(collection) + Model.expects(:paginate).with(params.merge(:page => 3)).returns(collection) + Model.expects(:paginate).with(params.merge(:page => 4)).returns(last_collection) + + total = Model.paginated_each(:page => '2') { } + total.should == 14 + end +end diff --git a/vendor/plugins/will_paginate/spec/fixtures/admin.rb b/vendor/plugins/will_paginate/spec/fixtures/admin.rb new file mode 100644 index 0000000..1d5e7f3 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/admin.rb @@ -0,0 +1,3 @@ +class Admin < User + has_many :companies, :finder_sql => 'SELECT * FROM companies' +end diff --git a/vendor/plugins/will_paginate/spec/fixtures/developer.rb b/vendor/plugins/will_paginate/spec/fixtures/developer.rb new file mode 100644 index 0000000..7105355 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/developer.rb @@ -0,0 +1,13 @@ +class Developer < User + has_and_belongs_to_many :projects, :include => :topics, :order => 'projects.name' + + def self.with_poor_ones(&block) + with_scope :find => { :conditions => ['salary <= ?', 80000], :order => 'salary' } do + yield + end + end + + named_scope :poor, :conditions => ['salary <= ?', 80000], :order => 'salary' + + def self.per_page() 10 end +end diff --git a/vendor/plugins/will_paginate/spec/fixtures/developers_projects.yml b/vendor/plugins/will_paginate/spec/fixtures/developers_projects.yml new file mode 100644 index 0000000..cee359c --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/developers_projects.yml @@ -0,0 +1,13 @@ +david_action_controller: + developer_id: 1 + project_id: 2 + joined_on: 2004-10-10 + +david_active_record: + developer_id: 1 + project_id: 1 + joined_on: 2004-10-10 + +jamis_active_record: + developer_id: 2 + project_id: 1 \ No newline at end of file diff --git a/vendor/plugins/will_paginate/spec/fixtures/project.rb b/vendor/plugins/will_paginate/spec/fixtures/project.rb new file mode 100644 index 0000000..0f85ef5 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/project.rb @@ -0,0 +1,15 @@ +class Project < ActiveRecord::Base + has_and_belongs_to_many :developers, :uniq => true + + has_many :topics + # :finder_sql => 'SELECT * FROM topics WHERE (topics.project_id = #{id})', + # :counter_sql => 'SELECT COUNT(*) FROM topics WHERE (topics.project_id = #{id})' + + has_many :replies, :through => :topics do + def find_recent(params = {}) + with_scope :find => { :conditions => ['replies.created_at > ?', 15.minutes.ago] } do + find :all, params + end + end + end +end diff --git a/vendor/plugins/will_paginate/spec/fixtures/projects.yml b/vendor/plugins/will_paginate/spec/fixtures/projects.yml new file mode 100644 index 0000000..74f3c32 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/projects.yml @@ -0,0 +1,6 @@ +active_record: + id: 1 + name: Active Record +action_controller: + id: 2 + name: Active Controller diff --git a/vendor/plugins/will_paginate/spec/fixtures/replies.yml b/vendor/plugins/will_paginate/spec/fixtures/replies.yml new file mode 100644 index 0000000..9a83c00 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/replies.yml @@ -0,0 +1,29 @@ +witty_retort: + id: 1 + topic_id: 1 + content: Birdman is better! + created_at: <%= 6.hours.ago.to_s(:db) %> + +another: + id: 2 + topic_id: 2 + content: Nuh uh! + created_at: <%= 1.hour.ago.to_s(:db) %> + +spam: + id: 3 + topic_id: 1 + content: Nice site! + created_at: <%= 1.hour.ago.to_s(:db) %> + +decisive: + id: 4 + topic_id: 4 + content: "I'm getting to the bottom of this" + created_at: <%= 30.minutes.ago.to_s(:db) %> + +brave: + id: 5 + topic_id: 4 + content: "AR doesn't scare me a bit" + created_at: <%= 10.minutes.ago.to_s(:db) %> diff --git a/vendor/plugins/will_paginate/spec/fixtures/reply.rb b/vendor/plugins/will_paginate/spec/fixtures/reply.rb new file mode 100644 index 0000000..ecaf3c1 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/reply.rb @@ -0,0 +1,7 @@ +class Reply < ActiveRecord::Base + belongs_to :topic, :include => [:replies] + + named_scope :recent, :conditions => ['replies.created_at > ?', 15.minutes.ago] + + validates_presence_of :content +end diff --git a/vendor/plugins/will_paginate/spec/fixtures/schema.rb b/vendor/plugins/will_paginate/spec/fixtures/schema.rb new file mode 100644 index 0000000..8831aad --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/schema.rb @@ -0,0 +1,38 @@ +ActiveRecord::Schema.define do + + create_table "users", :force => true do |t| + t.column "name", :text + t.column "salary", :integer, :default => 70000 + t.column "created_at", :datetime + t.column "updated_at", :datetime + t.column "type", :text + end + + create_table "projects", :force => true do |t| + t.column "name", :text + end + + create_table "developers_projects", :id => false, :force => true do |t| + t.column "developer_id", :integer, :null => false + t.column "project_id", :integer, :null => false + t.column "joined_on", :date + t.column "access_level", :integer, :default => 1 + end + + create_table "topics", :force => true do |t| + t.column "project_id", :integer + t.column "title", :string + t.column "subtitle", :string + t.column "content", :text + t.column "created_at", :datetime + t.column "updated_at", :datetime + end + + create_table "replies", :force => true do |t| + t.column "content", :text + t.column "created_at", :datetime + t.column "updated_at", :datetime + t.column "topic_id", :integer + end + +end diff --git a/vendor/plugins/will_paginate/spec/fixtures/topic.rb b/vendor/plugins/will_paginate/spec/fixtures/topic.rb new file mode 100644 index 0000000..77be0dd --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/topic.rb @@ -0,0 +1,6 @@ +class Topic < ActiveRecord::Base + has_many :replies, :dependent => :destroy, :order => 'replies.created_at DESC' + belongs_to :project + + named_scope :mentions_activerecord, :conditions => ['topics.title LIKE ?', '%ActiveRecord%'] +end diff --git a/vendor/plugins/will_paginate/spec/fixtures/topics.yml b/vendor/plugins/will_paginate/spec/fixtures/topics.yml new file mode 100644 index 0000000..0a26904 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/topics.yml @@ -0,0 +1,30 @@ +futurama: + id: 1 + title: Isnt futurama awesome? + subtitle: It really is, isnt it. + content: I like futurama + created_at: <%= 1.day.ago.to_s(:db) %> + updated_at: + +harvey_birdman: + id: 2 + title: Harvey Birdman is the king of all men + subtitle: yup + content: He really is + created_at: <%= 2.hours.ago.to_s(:db) %> + updated_at: + +rails: + id: 3 + project_id: 1 + title: Rails is nice + subtitle: It makes me happy + content: except when I have to hack internals to fix pagination. even then really. + created_at: <%= 20.minutes.ago.to_s(:db) %> + +ar: + id: 4 + project_id: 1 + title: ActiveRecord sometimes freaks me out + content: "I mean, what's the deal with eager loading?" + created_at: <%= 15.minutes.ago.to_s(:db) %> diff --git a/vendor/plugins/will_paginate/spec/fixtures/user.rb b/vendor/plugins/will_paginate/spec/fixtures/user.rb new file mode 100644 index 0000000..4a57cf0 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/user.rb @@ -0,0 +1,2 @@ +class User < ActiveRecord::Base +end diff --git a/vendor/plugins/will_paginate/spec/fixtures/users.yml b/vendor/plugins/will_paginate/spec/fixtures/users.yml new file mode 100644 index 0000000..ed2c03a --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/users.yml @@ -0,0 +1,35 @@ +david: + id: 1 + name: David + salary: 80000 + type: Developer + +jamis: + id: 2 + name: Jamis + salary: 150000 + type: Developer + +<% for digit in 3..10 %> +dev_<%= digit %>: + id: <%= digit %> + name: fixture_<%= digit %> + salary: 100000 + type: Developer +<% end %> + +poor_jamis: + id: 11 + name: Jamis + salary: 9000 + type: Developer + +admin: + id: 12 + name: admin + type: Admin + +goofy: + id: 13 + name: Goofy + type: Admin diff --git a/vendor/plugins/will_paginate/spec/rcov.opts b/vendor/plugins/will_paginate/spec/rcov.opts new file mode 100644 index 0000000..6b17c32 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/rcov.opts @@ -0,0 +1,2 @@ +--exclude ^\/,^spec\/,core_ext.rb,deprecation.rb +--no-validator-links \ No newline at end of file diff --git a/vendor/plugins/will_paginate/spec/spec.opts b/vendor/plugins/will_paginate/spec/spec.opts new file mode 100644 index 0000000..14f5f13 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/spec.opts @@ -0,0 +1,2 @@ +--colour +--reverse diff --git a/vendor/plugins/will_paginate/spec/spec_helper.rb b/vendor/plugins/will_paginate/spec/spec_helper.rb new file mode 100644 index 0000000..46a26e5 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/spec_helper.rb @@ -0,0 +1,76 @@ +require 'rubygems' +gem 'rspec', '~> 1.1.4' +require 'spec' + +module MyExtras + protected + + def include_phrase(string) + PhraseMatcher.new(string) + end + + def collection(params = {}) + if params[:total_pages] + params[:per_page] = 1 + params[:total_entries] = params[:total_pages] + end + WillPaginate::Collection.new(params[:page] || 1, params[:per_page] || 30, params[:total_entries]) + end + + def have_deprecation + DeprecationMatcher.new + end +end + +Spec::Runner.configure do |config| + # config.include My::Pony, My::Horse, :type => :farm + config.include MyExtras + # config.predicate_matchers[:swim] = :can_swim? + + config.mock_with :mocha +end + +class PhraseMatcher + def initialize(string) + @string = string + @pattern = /\b#{string}\b/ + end + + def matches?(actual) + @actual = actual.to_s + @actual =~ @pattern + end + + def failure_message + "expected #{@actual.inspect} to contain phrase #{@string.inspect}" + end + + def negative_failure_message + "expected #{@actual.inspect} not to contain phrase #{@string.inspect}" + end +end + +class DeprecationMatcher + def initialize + @old_behavior = WillPaginate::Deprecation.behavior + @messages = [] + WillPaginate::Deprecation.behavior = lambda { |message, callstack| + @messages << message + } + end + + def matches?(block) + block.call + !@messages.empty? + ensure + WillPaginate::Deprecation.behavior = @old_behavior + end + + def failure_message + "expected block to raise a deprecation warning" + end + + def negative_failure_message + "expected block not to raise deprecation warnings, #{@messages.size} raised" + end +end diff --git a/vendor/plugins/will_paginate/spec/tasks.rake b/vendor/plugins/will_paginate/spec/tasks.rake new file mode 100644 index 0000000..cb04366 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/tasks.rake @@ -0,0 +1,34 @@ +require 'spec/rake/spectask' + +spec_opts = 'spec/spec.opts' + +desc 'Run all specs' +Spec::Rake::SpecTask.new(:spec) do |t| + t.libs << 'lib' << 'spec' + t.spec_opts = ['--options', spec_opts] +end + +namespace :spec do + desc 'Analyze spec coverage with RCov' + Spec::Rake::SpecTask.new(:rcov) do |t| + t.libs << 'lib' << 'spec' + t.spec_opts = ['--options', spec_opts] + t.rcov = true + t.rcov_opts = lambda do + IO.readlines('spec/rcov.opts').map { |l| l.chomp.split(" ") }.flatten + end + end + + desc 'Print Specdoc for all specs' + Spec::Rake::SpecTask.new(:doc) do |t| + t.libs << 'lib' << 'spec' + t.spec_opts = ['--format', 'specdoc', '--dry-run'] + end + + desc 'Generate HTML report' + Spec::Rake::SpecTask.new(:html) do |t| + t.libs << 'lib' << 'spec' + t.spec_opts = ['--format', 'html:doc/spec_results.html', '--diff'] + t.fail_on_error = false + end +end diff --git a/vendor/plugins/will_paginate/spec/view_helpers/action_view_spec.rb b/vendor/plugins/will_paginate/spec/view_helpers/action_view_spec.rb new file mode 100644 index 0000000..8b34a1b --- /dev/null +++ b/vendor/plugins/will_paginate/spec/view_helpers/action_view_spec.rb @@ -0,0 +1,343 @@ +require 'spec_helper' +require 'action_controller' +require 'view_helpers/view_example_group' +require 'will_paginate/view_helpers/action_view' +require 'will_paginate/collection' + +ActionController::Routing::Routes.draw do |map| + map.connect 'dummy/page/:page', :controller => 'dummy' + map.connect 'dummy/dots/page.:page', :controller => 'dummy', :action => 'dots' + map.connect 'ibocorp/:page', :controller => 'ibocorp', + :requirements => { :page => /\d+/ }, + :defaults => { :page => 1 } + + map.connect ':controller/:action/:id' +end + +describe WillPaginate::ViewHelpers::ActionView do + before(:each) do + @view = ActionView::Base.new + @view.controller = DummyController.new + @view.request = @view.controller.request + @template = '<%= will_paginate collection, options %>' + end + + def request + @view.request + end + + def render(locals) + @view.render(:inline => @template, :locals => locals) + end + + ## basic pagination ## + + it "should render" do + paginate do |pagination| + assert_select 'a[href]', 3 do |elements| + validate_page_numbers [2,3,2], elements + assert_select elements.last, ':last-child', "Next »" + end + assert_select 'span', 1 + assert_select 'span.disabled:first-child', '« Previous' + assert_select 'em', '1' + pagination.first.inner_text.should == '« Previous 1 2 3 Next »' + end + end + + it "should render nothing when there is only 1 page" do + paginate(:per_page => 30).should be_empty + end + + it "should paginate with options" do + paginate({ :page => 2 }, :class => 'will_paginate', :previous_label => 'Prev', :next_label => 'Next') do + assert_select 'a[href]', 4 do |elements| + validate_page_numbers [1,1,3,3], elements + # test rel attribute values: + assert_select elements[1], 'a', '1' do |link| + link.first['rel'].should == 'prev start' + end + assert_select elements.first, 'a', "Prev" do |link| + link.first['rel'].should == 'prev start' + end + assert_select elements.last, 'a', "Next" do |link| + link.first['rel'].should == 'next' + end + end + assert_select 'em', '2' + end + end + + it "should paginate using a custom renderer class" do + paginate({}, :renderer => AdditionalLinkAttributesRenderer) do + assert_select 'a[default=true]', 3 + end + end + + it "should paginate using a custom renderer instance" do + renderer = WillPaginate::ViewHelpers::LinkRenderer.new + def renderer.gap() '~~' end + + paginate({ :per_page => 2 }, :inner_window => 0, :outer_window => 0, :renderer => renderer) do + assert_select 'span.my-gap', '~~' + end + + renderer = AdditionalLinkAttributesRenderer.new(:title => 'rendered') + paginate({}, :renderer => renderer) do + assert_select 'a[title=rendered]', 3 + end + end + + it "should have classnames on previous/next links" do + paginate do |pagination| + assert_select 'span.disabled.previous_page:first-child' + assert_select 'a.next_page[href]:last-child' + end + end + + it "should warn about :prev_label being deprecated" do + lambda { + paginate({ :page => 2 }, :prev_label => 'Deprecated') do + assert_select 'a[href]:first-child', 'Deprecated' + end + }.should have_deprecation + end + + it "should match expected markup" do + paginate + expected = <<-HTML + + HTML + expected.strip!.gsub!(/\s{2,}/, ' ') + expected_dom = HTML::Document.new(expected).root + + html_document.root.should == expected_dom + end + + it "should output escaped URLs" do + paginate({:page => 1, :per_page => 1, :total_entries => 2}, + :page_links => false, :params => { :tag => '
    ' }) + + assert_select 'a[href]', 1 do |links| + query = links.first['href'].split('?', 2)[1] + query.split('&').sort.should == %w(page=2 tag=%3Cbr%3E) + end + end + + ## advanced options for pagination ## + + it "should be able to render without container" do + paginate({}, :container => false) + assert_select 'div.pagination', 0, 'main DIV present when it shouldn\'t' + assert_select 'a[href]', 3 + end + + it "should be able to render without page links" do + paginate({ :page => 2 }, :page_links => false) do + assert_select 'a[href]', 2 do |elements| + validate_page_numbers [1,3], elements + end + end + end + + it "should have magic HTML ID for the container" do + paginate do |div| + div.first['id'].should be_nil + end + + # magic ID + paginate({}, :id => true) do |div| + div.first['id'].should == 'fixnums_pagination' + end + + # explicit ID + paginate({}, :id => 'custom_id') do |div| + div.first['id'].should == 'custom_id' + end + end + + ## other helpers ## + + it "should render a paginated section" do + @template = <<-ERB + <% paginated_section collection, options do %> + <%= content_tag :div, '', :id => "developers" %> + <% end %> + ERB + + paginate + assert_select 'div.pagination', 2 + assert_select 'div.pagination + div#developers', 1 + end + + ## parameter handling in page links ## + + it "should preserve parameters on GET" do + request.params :foo => { :bar => 'baz' } + paginate + assert_links_match /foo%5Bbar%5D=baz/ + end + + it "should not preserve parameters on POST" do + request.post + request.params :foo => 'bar' + paginate + assert_no_links_match /foo=bar/ + end + + it "should add additional parameters to links" do + paginate({}, :params => { :foo => 'bar' }) + assert_links_match /foo=bar/ + end + + it "should add anchor parameter" do + paginate({}, :params => { :anchor => 'anchor' }) + assert_links_match /#anchor$/ + end + + it "should remove arbitrary parameters" do + request.params :foo => 'bar' + paginate({}, :params => { :foo => nil }) + assert_no_links_match /foo=bar/ + end + + it "should override default route parameters" do + paginate({}, :params => { :controller => 'baz', :action => 'list' }) + assert_links_match %r{\Wbaz/list\W} + end + + it "should paginate with custom page parameter" do + paginate({ :page => 2 }, :param_name => :developers_page) do + assert_select 'a[href]', 4 do |elements| + validate_page_numbers [1,1,3,3], elements, :developers_page + end + end + end + + it "should paginate with complex custom page parameter" do + request.params :developers => { :page => 2 } + + paginate({ :page => 2 }, :param_name => 'developers[page]') do + assert_select 'a[href]', 4 do |links| + assert_links_match /\?developers%5Bpage%5D=\d+$/, links + validate_page_numbers [1,1,3,3], links, 'developers[page]' + end + end + end + + it "should paginate with custom route page parameter" do + request.symbolized_path_parameters.update :controller => 'dummy', :action => nil + paginate :per_page => 2 do + assert_select 'a[href]', 6 do |links| + assert_links_match %r{/page/(\d+)$}, links, [2, 3, 4, 5, 6, 2] + end + end + end + + it "should paginate with custom route with dot separator page parameter" do + request.symbolized_path_parameters.update :controller => 'dummy', :action => 'dots' + paginate :per_page => 2 do + assert_select 'a[href]', 6 do |links| + assert_links_match %r{/page\.(\d+)$}, links, [2, 3, 4, 5, 6, 2] + end + end + end + + it "should paginate with custom route and first page number implicit" do + request.symbolized_path_parameters.update :controller => 'ibocorp', :action => nil + paginate :page => 2, :per_page => 2 do + assert_select 'a[href]', 7 do |links| + assert_links_match %r{/ibocorp(?:/(\d+))?$}, links, [nil, nil, 3, 4, 5, 6, 3] + end + end + end + + ## internal hardcore stuff ## + + it "should be able to guess the collection name" do + collection = mock + collection.expects(:total_pages).returns(1) + + @template = '<%= will_paginate options %>' + @view.controller.controller_name = 'developers' + @view.assigns['developers'] = collection + + paginate(nil) + end + + it "should fail if the inferred collection is nil" do + @template = '<%= will_paginate options %>' + @view.controller.controller_name = 'developers' + + lambda { + paginate(nil) + }.should raise_error(ArgumentError, /@developers/) + end + + if ActionController::Base.respond_to? :rescue_responses + # only on Rails 2 + it "should set rescue response hook" do + ActionController::Base.rescue_responses['WillPaginate::InvalidPage'].should == :not_found + end + end +end + +class AdditionalLinkAttributesRenderer < WillPaginate::ViewHelpers::LinkRenderer + def initialize(link_attributes = nil) + super() + @additional_link_attributes = link_attributes || { :default => 'true' } + end + + def link(text, target, attributes = {}) + super(text, target, attributes.merge(@additional_link_attributes)) + end +end + +class DummyController + attr_reader :request + attr_accessor :controller_name + + def initialize + @request = DummyRequest.new + @url = ActionController::UrlRewriter.new(@request, @request.params) + end + + def params + @request.params + end + + def url_for(params) + @url.rewrite(params) + end +end + +class DummyRequest + attr_accessor :symbolized_path_parameters + + def initialize + @get = true + @params = {} + @symbolized_path_parameters = { :controller => 'foo', :action => 'bar' } + end + + def get? + @get + end + + def post + @get = false + end + + def relative_url_root + '' + end + + def params(more = nil) + @params.update(more) if more + @params + end +end diff --git a/vendor/plugins/will_paginate/spec/view_helpers/base_spec.rb b/vendor/plugins/will_paginate/spec/view_helpers/base_spec.rb new file mode 100644 index 0000000..ed91a9f --- /dev/null +++ b/vendor/plugins/will_paginate/spec/view_helpers/base_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' +require 'will_paginate/view_helpers/base' +require 'will_paginate/array' + +describe WillPaginate::ViewHelpers::Base do + + include WillPaginate::ViewHelpers::Base + + describe "will_paginate" do + it "should render" do + collection = WillPaginate::Collection.new(1, 2, 4) + renderer = mock 'Renderer' + renderer.expects(:prepare).with(collection, instance_of(Hash), self) + renderer.expects(:to_html).returns('') + + will_paginate(collection, :renderer => renderer).should == '' + end + + it "should return nil for single-page collections" do + collection = mock 'Collection', :total_pages => 1 + will_paginate(collection).should be_nil + end + end + + describe "page_entries_info" do + before :all do + @array = ('a'..'z').to_a + end + + def info(params, options = {}) + options[:html] ||= false unless options.key?(:html) and options[:html].nil? + collection = Hash === params ? @array.paginate(params) : params + page_entries_info collection, options + end + + it "should display middle results and total count" do + info(:page => 2, :per_page => 5).should == "Displaying strings 6 - 10 of 26 in total" + end + + it "should output HTML by default" do + info({ :page => 2, :per_page => 5 }, :html => nil).should == + "Displaying strings 6 - 10 of 26 in total" + end + + it "should display shortened end results" do + info(:page => 7, :per_page => 4).should include_phrase('strings 25 - 26') + end + + it "should handle longer class names" do + collection = @array.paginate(:page => 2, :per_page => 5) + collection.first.stubs(:class).returns(mock('Class', :name => 'ProjectType')) + info(collection).should include_phrase('project types') + end + + it "should adjust output for single-page collections" do + info(('a'..'d').to_a.paginate(:page => 1, :per_page => 5)).should == "Displaying all 4 strings" + info(['a'].paginate(:page => 1, :per_page => 5)).should == "Displaying 1 string" + end + + it "should display 'no entries found' for empty collections" do + info([].paginate(:page => 1, :per_page => 5)).should == "No entries found" + end + end +end diff --git a/vendor/plugins/will_paginate/spec/view_helpers/link_renderer_base_spec.rb b/vendor/plugins/will_paginate/spec/view_helpers/link_renderer_base_spec.rb new file mode 100644 index 0000000..a240f3f --- /dev/null +++ b/vendor/plugins/will_paginate/spec/view_helpers/link_renderer_base_spec.rb @@ -0,0 +1,84 @@ +require 'spec_helper' +require 'will_paginate/view_helpers/link_renderer_base' +require 'will_paginate/collection' + +describe WillPaginate::ViewHelpers::LinkRendererBase do + + before do + @renderer = WillPaginate::ViewHelpers::LinkRendererBase.new + end + + it "should raise error when unprepared" do + lambda { + @renderer.send :param_name + }.should raise_error + end + + it "should prepare with collection and options" do + prepare({}, :param_name => 'mypage') + @renderer.send(:current_page).should == 1 + @renderer.send(:param_name).should == 'mypage' + end + + it "should have total_pages accessor" do + prepare :total_pages => 42 + lambda { + @renderer.send(:total_pages).should == 42 + }.should_not have_deprecation + end + + it "should clear old cached values when prepared" do + prepare({ :total_pages => 1 }, :param_name => 'foo') + @renderer.send(:total_pages).should == 1 + @renderer.send(:param_name).should == 'foo' + # prepare with different object and options: + prepare({ :total_pages => 2 }, :param_name => 'bar') + @renderer.send(:total_pages).should == 2 + @renderer.send(:param_name).should == 'bar' + end + + it "should have pagination definition" do + prepare({ :total_pages => 1 }, :page_links => true) + @renderer.pagination.should == [:previous_page, 1, :next_page] + end + + describe "visible page numbers" do + it "should calculate windowed visible links" do + prepare({ :page => 6, :total_pages => 11 }, :inner_window => 1, :outer_window => 1) + showing_pages 1, 2, :gap, 5, 6, 7, :gap, 10, 11 + end + + it "should eliminate small gaps" do + prepare({ :page => 6, :total_pages => 11 }, :inner_window => 2, :outer_window => 1) + # pages 4 and 8 appear instead of the gap + showing_pages 1..11 + end + + it "should support having no windows at all" do + prepare({ :page => 4, :total_pages => 7 }, :inner_window => 0, :outer_window => 0) + showing_pages 1, :gap, 4, :gap, 7 + end + + it "should adjust upper limit if lower is out of bounds" do + prepare({ :page => 1, :total_pages => 10 }, :inner_window => 2, :outer_window => 1) + showing_pages 1, 2, 3, 4, 5, :gap, 9, 10 + end + + it "should adjust lower limit if upper is out of bounds" do + prepare({ :page => 10, :total_pages => 10 }, :inner_window => 2, :outer_window => 1) + showing_pages 1, 2, :gap, 6, 7, 8, 9, 10 + end + + def showing_pages(*pages) + pages = pages.first.to_a if Array === pages.first or Range === pages.first + @renderer.send(:windowed_page_numbers).should == pages + end + end + + protected + + def prepare(collection_options, options = {}) + @renderer.prepare(collection(collection_options), options) + end + +end diff --git a/vendor/plugins/will_paginate/spec/view_helpers/view_example_group.rb b/vendor/plugins/will_paginate/spec/view_helpers/view_example_group.rb new file mode 100644 index 0000000..cdeb0b1 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/view_helpers/view_example_group.rb @@ -0,0 +1,111 @@ +unless $:.find { |p| p =~ %r{/html-scanner$} } + unless actionpack_path = $:.find { |p| p =~ %r{/actionpack(-[\d.]+)?/lib$} } + raise "cannot find ActionPack in load paths" + end + html_scanner_path = "#{actionpack_path}/action_controller/vendor/html-scanner" + $:.unshift(html_scanner_path) +end + +require 'action_controller/assertions/selector_assertions' + +class ViewExampleGroup < Spec::Example::ExampleGroup + + include ActionController::Assertions::SelectorAssertions + + def assert(value, message) + raise message unless value + end + + def paginate(collection = {}, options = {}, &block) + if collection.instance_of? Hash + page_options = { :page => 1, :total_entries => 11, :per_page => 4 }.merge(collection) + collection = [1].paginate(page_options) + end + + locals = { :collection => collection, :options => options } + + @render_output = render(locals) + @html_document = nil + + if block_given? + classname = options[:class] || WillPaginate::ViewHelpers.pagination_options[:class] + assert_select("div.#{classname}", 1, 'no main DIV', &block) + end + + @render_output + end + + def html_document + @html_document ||= HTML::Document.new(@render_output, true, false) + end + + def response_from_page_or_rjs + html_document.root + end + + def validate_page_numbers(expected, links, param_name = :page) + param_pattern = /\W#{CGI.escape(param_name.to_s)}=([^&]*)/ + + links.map { |e| + e['href'] =~ param_pattern + $1 ? $1.to_i : $1 + }.should == expected + end + + def assert_links_match(pattern, links = nil, numbers = nil) + links ||= assert_select 'div.pagination a[href]' do |elements| + elements + end + + pages = [] if numbers + + links.each do |el| + el['href'].should =~ pattern + if numbers + el['href'] =~ pattern + pages << ($1.nil?? nil : $1.to_i) + end + end + + pages.should == numbers if numbers + end + + def assert_no_links_match(pattern) + assert_select 'div.pagination a[href]' do |elements| + elements.each do |el| + el['href'] !~ pattern + end + end + end + + def build_message(message, pattern, *args) + built_message = pattern.dup + for value in args + built_message.sub! '?', value.inspect + end + built_message + end + +end + +Spec::Example::ExampleGroupFactory.register(:view_helpers, ViewExampleGroup) + +module HTML + Node.class_eval do + def inner_text + children.map(&:inner_text).join('') + end + end + + Text.class_eval do + def inner_text + self.to_s + end + end + + Tag.class_eval do + def inner_text + childless?? '' : super + end + end +end diff --git a/vendor/plugins/will_paginate/will_paginate.gemspec b/vendor/plugins/will_paginate/will_paginate.gemspec new file mode 100644 index 0000000..ef5f4df --- /dev/null +++ b/vendor/plugins/will_paginate/will_paginate.gemspec @@ -0,0 +1,20 @@ +Gem::Specification.new do |s| + s.name = 'will_paginate' + s.version = '2.5.0' + # s.date = '2008-10-27' + + s.summary = "Most awesome pagination solution for every web app" + s.description = "The will_paginate library provides a simple, yet powerful and extensible API for pagination and rendering of page links in templates." + + s.authors = ['Mislav Marohnić', 'PJ Hyett'] + s.email = 'mislav.marohnic@gmail.com' + s.homepage = 'http://github.com/mislav/will_paginate/wikis' + + s.has_rdoc = true + s.rdoc_options = ['--main', 'README.rdoc'] + s.rdoc_options << '--inline-source' << '--charset=UTF-8' + s.extra_rdoc_files = ['README.rdoc', 'LICENSE', 'CHANGELOG.rdoc'] + + s.files = %w(CHANGELOG.rdoc LICENSE README.rdoc Rakefile examples examples/apple-circle.gif examples/index.haml examples/index.html examples/pagination.css examples/pagination.sass init.rb lib lib/will_paginate lib/will_paginate.rb lib/will_paginate/array.rb lib/will_paginate/collection.rb lib/will_paginate/core_ext.rb lib/will_paginate/deprecation.rb lib/will_paginate/finders lib/will_paginate/finders.rb lib/will_paginate/finders/active_record lib/will_paginate/finders/active_record.rb lib/will_paginate/finders/active_record/named_scope.rb lib/will_paginate/finders/active_record/named_scope_patch.rb lib/will_paginate/finders/active_resource.rb lib/will_paginate/finders/base.rb lib/will_paginate/finders/data_mapper.rb lib/will_paginate/version.rb lib/will_paginate/view_helpers lib/will_paginate/view_helpers.rb lib/will_paginate/view_helpers/action_view.rb lib/will_paginate/view_helpers/base.rb lib/will_paginate/view_helpers/link_renderer.rb lib/will_paginate/view_helpers/link_renderer_base.rb spec spec/collection_spec.rb spec/console spec/console_fixtures.rb spec/database.yml spec/finders spec/finders/active_record_spec.rb spec/finders/active_resource_spec.rb spec/finders/activerecord_test_connector.rb spec/finders_spec.rb spec/fixtures spec/fixtures/admin.rb spec/fixtures/developer.rb spec/fixtures/developers_projects.yml spec/fixtures/project.rb spec/fixtures/projects.yml spec/fixtures/replies.yml spec/fixtures/reply.rb spec/fixtures/schema.rb spec/fixtures/topic.rb spec/fixtures/topics.yml spec/fixtures/user.rb spec/fixtures/users.yml spec/rcov.opts spec/spec.opts spec/spec_helper.rb spec/tasks.rake spec/view_helpers spec/view_helpers/action_view_spec.rb spec/view_helpers/base_spec.rb spec/view_helpers/link_renderer_base_spec.rb spec/view_helpers/view_example_group.rb) + s.test_files = %w(spec/collection_spec.rb spec/console spec/console_fixtures.rb spec/database.yml spec/finders spec/finders/active_record_spec.rb spec/finders/active_resource_spec.rb spec/finders/activerecord_test_connector.rb spec/finders_spec.rb spec/fixtures spec/fixtures/admin.rb spec/fixtures/developer.rb spec/fixtures/developers_projects.yml spec/fixtures/project.rb spec/fixtures/projects.yml spec/fixtures/replies.yml spec/fixtures/reply.rb spec/fixtures/schema.rb spec/fixtures/topic.rb spec/fixtures/topics.yml spec/fixtures/user.rb spec/fixtures/users.yml spec/rcov.opts spec/spec.opts spec/spec_helper.rb spec/tasks.rake spec/view_helpers spec/view_helpers/action_view_spec.rb spec/view_helpers/base_spec.rb spec/view_helpers/link_renderer_base_spec.rb spec/view_helpers/view_example_group.rb) +end \ No newline at end of file