1
0
mirror of https://github.com/danbee/danbarberphoto synced 2025-03-04 08:49:07 +00:00

Removed localised paperclip plugin and added the gem. Added :preview

image size to photo attachment for admin interface.
This commit is contained in:
Dan Barber 2010-12-20 12:11:07 -05:00
parent 67e7f0eb12
commit 8621da4d3b
94 changed files with 1932 additions and 6394 deletions

View File

@ -32,8 +32,10 @@ gem 'sqlite3-ruby', :require => 'sqlite3'
gem "exception_notification", :git => "git://github.com/rails/exception_notification", :require => 'exception_notifier'
gem 'pg'
gem 'meta_where'
gem 'typus', :git => 'git://github.com/fesplugas/typus.git'
gem 'typus', :git => 'https://github.com/fesplugas/typus.git'
gem 'mini_exiftool'
gem 'will_paginate', :git => 'http://github.com/mislav/will_paginate.git', :branch => 'rails3'
gem 'rdiscount'
gem 'paperclip'
gem 'acts_as_markup'
gem 'jquery-rails', '>= 0.2.6'

View File

@ -1,9 +1,3 @@
GIT
remote: git://github.com/fesplugas/typus.git
revision: e0ce90d39989a82662e6d8c6c6c091860b5d9e51
specs:
typus (1.0.0.pre8)
GIT
remote: git://github.com/rails/exception_notification
revision: 192a49a02d63d28b23ed41cebadfedd490929cf1
@ -17,6 +11,14 @@ GIT
specs:
will_paginate (3.0.pre3)
GIT
remote: https://github.com/fesplugas/typus.git
revision: d03b28cc20c9b5cb82a34065d14348d491018b27
specs:
typus (3.0.3)
render_inheritable
will_paginate (~> 3.0.pre2)
GEM
remote: http://rubygems.org/
specs:
@ -59,18 +61,25 @@ GEM
builder (2.1.2)
erubis (2.6.6)
abstract (>= 1.0.0)
i18n (0.4.1)
mail (2.2.7)
i18n (0.4.2)
jquery-rails (0.2.6)
rails (~> 3.0)
thor (~> 0.14.4)
mail (2.2.12)
activesupport (>= 2.3.6)
mime-types
treetop (>= 1.4.5)
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
meta_where (0.9.6)
activerecord (~> 3.0.0)
activesupport (~> 3.0.0)
arel (~> 1.0.1)
mime-types (1.16)
mini_exiftool (1.0.1)
pg (0.9.0)
paperclip (2.3.8)
activerecord
activesupport
pg (0.10.0)
polyglot (0.3.1)
rack (1.2.1)
rack-mount (0.6.13)
@ -92,9 +101,11 @@ GEM
thor (~> 0.14.0)
rake (0.8.7)
rdiscount (1.6.5)
sqlite3-ruby (1.3.1)
thor (0.14.3)
treetop (1.4.8)
render_inheritable (1.0.0)
rails (~> 3.0)
sqlite3-ruby (1.3.2)
thor (0.14.6)
treetop (1.4.9)
polyglot (>= 0.3.1)
tzinfo (0.3.23)
wikitext (2.1.1)
@ -105,8 +116,10 @@ PLATFORMS
DEPENDENCIES
acts_as_markup
exception_notification!
jquery-rails (>= 0.2.6)
meta_where
mini_exiftool
paperclip
pg
rails (= 3.0.0)
rdiscount

View File

@ -3,7 +3,8 @@ require 'mini_exiftool'
class Photo < ActiveRecord::Base
has_and_belongs_to_many :categories
has_attached_file :photo, :styles => { :size17 => "476x476#",
has_attached_file :photo, :styles => { :preview => "600x600",
:size17 => "476x476#",
:size11 => "308x308#",
:size8 => "224x224#",
:size5 => "140x140#",

View File

@ -1,16 +1,18 @@
Typus.setup do |config|
# Application name.
config.admin_title = "Dan Barber Photography"
config.admin_title = "danbarberphoto"
# config.admin_sub_title = ""
# When mailer_sender is set, password recover is enabled. This email
# address will be used in Admin::Mailer.
# config.mailer_sender = "admin@example.com"
# Define file attachment settings.
# config.file_preview = :typus_preview
# config.file_thumbnail = :typus_thumbnail
# Define paperclip attachment styles.
# config.file_preview = :medium
# config.file_thumbnail = :thumb
config.file_preview = :preview
config.file_thumbnail = :size2
# Authentication: +:none+, +:http_basic+
# Run `rails g typus:migration` if you need an advanced authentication system.
@ -20,7 +22,10 @@ Typus.setup do |config|
# config.username = "admin"
# config.password = "columbia"
# Define available languages on the admin interface.
# config.available_locales = [:en]
# Pagination options:
# These options are passed to `will_paginate`. You can see the available
# options in the plugin source. (https://github.com/mislav/will_paginate/blob/rails3/lib/will_paginate/view_helpers.rb)
# config.pagination = { :previous_label => "&larr; " + Typus::I18n.t("Previous"),
# :next_label => Typus::I18n.t("Next") + " &rarr;" }
end

View File

@ -15,9 +15,6 @@ Typus::Resources.setup do |config|
# Defines minute_step.
# config.minute_step = 5
# Defines nil.
# config.human_nil = "nil"
# Defines only_user_items.
# config.only_user_items = false

View File

@ -34,7 +34,7 @@ common settings.
application: Application
description: Some text to describe the model
options:
action_after_save: :index
action_after_save: index
default_action_on_item: show
end_year: 2015
form_rows: 25

View File

@ -14,7 +14,7 @@ Category:
Photo:
fields:
default: title, sort, enabled, featured
default: photo, title, enabled, featured, views
form: photo, title, description, flickr_url, enabled, featured, sort
order_by:
relationships: categories

View File

@ -1,6 +1,2 @@
// Place your application-specific JavaScript functions and classes for
// the admin panel here.
$(function() {
$("#quicksearch").searchField();
});

View File

@ -0,0 +1,167 @@
/*!
* jQuery JavaScript Library v1.4.4
* http://jquery.com/
*
* Copyright 2010, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* Includes Sizzle.js
* http://sizzlejs.com/
* Copyright 2010, The Dojo Foundation
* Released under the MIT, BSD, and GPL Licenses.
*
* Date: Thu Nov 11 19:04:53 2010 -0500
*/
(function(E,B){function ka(a,b,d){if(d===B&&a.nodeType===1){d=a.getAttribute("data-"+b);if(typeof d==="string"){try{d=d==="true"?true:d==="false"?false:d==="null"?null:!c.isNaN(d)?parseFloat(d):Ja.test(d)?c.parseJSON(d):d}catch(e){}c.data(a,b,d)}else d=B}return d}function U(){return false}function ca(){return true}function la(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function Ka(a){var b,d,e,f,h,l,k,o,x,r,A,C=[];f=[];h=c.data(this,this.nodeType?"events":"__events__");if(typeof h==="function")h=
h.events;if(!(a.liveFired===this||!h||!h.live||a.button&&a.type==="click")){if(a.namespace)A=RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)");a.liveFired=this;var J=h.live.slice(0);for(k=0;k<J.length;k++){h=J[k];h.origType.replace(X,"")===a.type?f.push(h.selector):J.splice(k--,1)}f=c(a.target).closest(f,a.currentTarget);o=0;for(x=f.length;o<x;o++){r=f[o];for(k=0;k<J.length;k++){h=J[k];if(r.selector===h.selector&&(!A||A.test(h.namespace))){l=r.elem;e=null;if(h.preType==="mouseenter"||
h.preType==="mouseleave"){a.type=h.preType;e=c(a.relatedTarget).closest(h.selector)[0]}if(!e||e!==l)C.push({elem:l,handleObj:h,level:r.level})}}}o=0;for(x=C.length;o<x;o++){f=C[o];if(d&&f.level>d)break;a.currentTarget=f.elem;a.data=f.handleObj.data;a.handleObj=f.handleObj;A=f.handleObj.origHandler.apply(f.elem,arguments);if(A===false||a.isPropagationStopped()){d=f.level;if(A===false)b=false;if(a.isImmediatePropagationStopped())break}}return b}}function Y(a,b){return(a&&a!=="*"?a+".":"")+b.replace(La,
"`").replace(Ma,"&")}function ma(a,b,d){if(c.isFunction(b))return c.grep(a,function(f,h){return!!b.call(f,h,f)===d});else if(b.nodeType)return c.grep(a,function(f){return f===b===d});else if(typeof b==="string"){var e=c.grep(a,function(f){return f.nodeType===1});if(Na.test(b))return c.filter(b,e,!d);else b=c.filter(b,e)}return c.grep(a,function(f){return c.inArray(f,b)>=0===d})}function na(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var e=c.data(a[d++]),f=c.data(this,
e);if(e=e&&e.events){delete f.handle;f.events={};for(var h in e)for(var l in e[h])c.event.add(this,h,e[h][l],e[h][l].data)}}})}function Oa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function oa(a,b,d){var e=b==="width"?a.offsetWidth:a.offsetHeight;if(d==="border")return e;c.each(b==="width"?Pa:Qa,function(){d||(e-=parseFloat(c.css(a,"padding"+this))||0);if(d==="margin")e+=parseFloat(c.css(a,
"margin"+this))||0;else e-=parseFloat(c.css(a,"border"+this+"Width"))||0});return e}function da(a,b,d,e){if(c.isArray(b)&&b.length)c.each(b,function(f,h){d||Ra.test(a)?e(a,h):da(a+"["+(typeof h==="object"||c.isArray(h)?f:"")+"]",h,d,e)});else if(!d&&b!=null&&typeof b==="object")c.isEmptyObject(b)?e(a,""):c.each(b,function(f,h){da(a+"["+f+"]",h,d,e)});else e(a,b)}function S(a,b){var d={};c.each(pa.concat.apply([],pa.slice(0,b)),function(){d[this]=a});return d}function qa(a){if(!ea[a]){var b=c("<"+
a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d==="")d="block";ea[a]=d}return ea[a]}function fa(a){return c.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var t=E.document,c=function(){function a(){if(!b.isReady){try{t.documentElement.doScroll("left")}catch(j){setTimeout(a,1);return}b.ready()}}var b=function(j,s){return new b.fn.init(j,s)},d=E.jQuery,e=E.$,f,h=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,l=/\S/,k=/^\s+/,o=/\s+$/,x=/\W/,r=/\d/,A=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,
C=/^[\],:{}\s]*$/,J=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,w=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,I=/(?:^|:|,)(?:\s*\[)+/g,L=/(webkit)[ \/]([\w.]+)/,g=/(opera)(?:.*version)?[ \/]([\w.]+)/,i=/(msie) ([\w.]+)/,n=/(mozilla)(?:.*? rv:([\w.]+))?/,m=navigator.userAgent,p=false,q=[],u,y=Object.prototype.toString,F=Object.prototype.hasOwnProperty,M=Array.prototype.push,N=Array.prototype.slice,O=String.prototype.trim,D=Array.prototype.indexOf,R={};b.fn=b.prototype={init:function(j,
s){var v,z,H;if(!j)return this;if(j.nodeType){this.context=this[0]=j;this.length=1;return this}if(j==="body"&&!s&&t.body){this.context=t;this[0]=t.body;this.selector="body";this.length=1;return this}if(typeof j==="string")if((v=h.exec(j))&&(v[1]||!s))if(v[1]){H=s?s.ownerDocument||s:t;if(z=A.exec(j))if(b.isPlainObject(s)){j=[t.createElement(z[1])];b.fn.attr.call(j,s,true)}else j=[H.createElement(z[1])];else{z=b.buildFragment([v[1]],[H]);j=(z.cacheable?z.fragment.cloneNode(true):z.fragment).childNodes}return b.merge(this,
j)}else{if((z=t.getElementById(v[2]))&&z.parentNode){if(z.id!==v[2])return f.find(j);this.length=1;this[0]=z}this.context=t;this.selector=j;return this}else if(!s&&!x.test(j)){this.selector=j;this.context=t;j=t.getElementsByTagName(j);return b.merge(this,j)}else return!s||s.jquery?(s||f).find(j):b(s).find(j);else if(b.isFunction(j))return f.ready(j);if(j.selector!==B){this.selector=j.selector;this.context=j.context}return b.makeArray(j,this)},selector:"",jquery:"1.4.4",length:0,size:function(){return this.length},
toArray:function(){return N.call(this,0)},get:function(j){return j==null?this.toArray():j<0?this.slice(j)[0]:this[j]},pushStack:function(j,s,v){var z=b();b.isArray(j)?M.apply(z,j):b.merge(z,j);z.prevObject=this;z.context=this.context;if(s==="find")z.selector=this.selector+(this.selector?" ":"")+v;else if(s)z.selector=this.selector+"."+s+"("+v+")";return z},each:function(j,s){return b.each(this,j,s)},ready:function(j){b.bindReady();if(b.isReady)j.call(t,b);else q&&q.push(j);return this},eq:function(j){return j===
-1?this.slice(j):this.slice(j,+j+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(N.apply(this,arguments),"slice",N.call(arguments).join(","))},map:function(j){return this.pushStack(b.map(this,function(s,v){return j.call(s,v,s)}))},end:function(){return this.prevObject||b(null)},push:M,sort:[].sort,splice:[].splice};b.fn.init.prototype=b.fn;b.extend=b.fn.extend=function(){var j,s,v,z,H,G=arguments[0]||{},K=1,Q=arguments.length,ga=false;
if(typeof G==="boolean"){ga=G;G=arguments[1]||{};K=2}if(typeof G!=="object"&&!b.isFunction(G))G={};if(Q===K){G=this;--K}for(;K<Q;K++)if((j=arguments[K])!=null)for(s in j){v=G[s];z=j[s];if(G!==z)if(ga&&z&&(b.isPlainObject(z)||(H=b.isArray(z)))){if(H){H=false;v=v&&b.isArray(v)?v:[]}else v=v&&b.isPlainObject(v)?v:{};G[s]=b.extend(ga,v,z)}else if(z!==B)G[s]=z}return G};b.extend({noConflict:function(j){E.$=e;if(j)E.jQuery=d;return b},isReady:false,readyWait:1,ready:function(j){j===true&&b.readyWait--;
if(!b.readyWait||j!==true&&!b.isReady){if(!t.body)return setTimeout(b.ready,1);b.isReady=true;if(!(j!==true&&--b.readyWait>0))if(q){var s=0,v=q;for(q=null;j=v[s++];)j.call(t,b);b.fn.trigger&&b(t).trigger("ready").unbind("ready")}}},bindReady:function(){if(!p){p=true;if(t.readyState==="complete")return setTimeout(b.ready,1);if(t.addEventListener){t.addEventListener("DOMContentLoaded",u,false);E.addEventListener("load",b.ready,false)}else if(t.attachEvent){t.attachEvent("onreadystatechange",u);E.attachEvent("onload",
b.ready);var j=false;try{j=E.frameElement==null}catch(s){}t.documentElement.doScroll&&j&&a()}}},isFunction:function(j){return b.type(j)==="function"},isArray:Array.isArray||function(j){return b.type(j)==="array"},isWindow:function(j){return j&&typeof j==="object"&&"setInterval"in j},isNaN:function(j){return j==null||!r.test(j)||isNaN(j)},type:function(j){return j==null?String(j):R[y.call(j)]||"object"},isPlainObject:function(j){if(!j||b.type(j)!=="object"||j.nodeType||b.isWindow(j))return false;if(j.constructor&&
!F.call(j,"constructor")&&!F.call(j.constructor.prototype,"isPrototypeOf"))return false;for(var s in j);return s===B||F.call(j,s)},isEmptyObject:function(j){for(var s in j)return false;return true},error:function(j){throw j;},parseJSON:function(j){if(typeof j!=="string"||!j)return null;j=b.trim(j);if(C.test(j.replace(J,"@").replace(w,"]").replace(I,"")))return E.JSON&&E.JSON.parse?E.JSON.parse(j):(new Function("return "+j))();else b.error("Invalid JSON: "+j)},noop:function(){},globalEval:function(j){if(j&&
l.test(j)){var s=t.getElementsByTagName("head")[0]||t.documentElement,v=t.createElement("script");v.type="text/javascript";if(b.support.scriptEval)v.appendChild(t.createTextNode(j));else v.text=j;s.insertBefore(v,s.firstChild);s.removeChild(v)}},nodeName:function(j,s){return j.nodeName&&j.nodeName.toUpperCase()===s.toUpperCase()},each:function(j,s,v){var z,H=0,G=j.length,K=G===B||b.isFunction(j);if(v)if(K)for(z in j){if(s.apply(j[z],v)===false)break}else for(;H<G;){if(s.apply(j[H++],v)===false)break}else if(K)for(z in j){if(s.call(j[z],
z,j[z])===false)break}else for(v=j[0];H<G&&s.call(v,H,v)!==false;v=j[++H]);return j},trim:O?function(j){return j==null?"":O.call(j)}:function(j){return j==null?"":j.toString().replace(k,"").replace(o,"")},makeArray:function(j,s){var v=s||[];if(j!=null){var z=b.type(j);j.length==null||z==="string"||z==="function"||z==="regexp"||b.isWindow(j)?M.call(v,j):b.merge(v,j)}return v},inArray:function(j,s){if(s.indexOf)return s.indexOf(j);for(var v=0,z=s.length;v<z;v++)if(s[v]===j)return v;return-1},merge:function(j,
s){var v=j.length,z=0;if(typeof s.length==="number")for(var H=s.length;z<H;z++)j[v++]=s[z];else for(;s[z]!==B;)j[v++]=s[z++];j.length=v;return j},grep:function(j,s,v){var z=[],H;v=!!v;for(var G=0,K=j.length;G<K;G++){H=!!s(j[G],G);v!==H&&z.push(j[G])}return z},map:function(j,s,v){for(var z=[],H,G=0,K=j.length;G<K;G++){H=s(j[G],G,v);if(H!=null)z[z.length]=H}return z.concat.apply([],z)},guid:1,proxy:function(j,s,v){if(arguments.length===2)if(typeof s==="string"){v=j;j=v[s];s=B}else if(s&&!b.isFunction(s)){v=
s;s=B}if(!s&&j)s=function(){return j.apply(v||this,arguments)};if(j)s.guid=j.guid=j.guid||s.guid||b.guid++;return s},access:function(j,s,v,z,H,G){var K=j.length;if(typeof s==="object"){for(var Q in s)b.access(j,Q,s[Q],z,H,v);return j}if(v!==B){z=!G&&z&&b.isFunction(v);for(Q=0;Q<K;Q++)H(j[Q],s,z?v.call(j[Q],Q,H(j[Q],s)):v,G);return j}return K?H(j[0],s):B},now:function(){return(new Date).getTime()},uaMatch:function(j){j=j.toLowerCase();j=L.exec(j)||g.exec(j)||i.exec(j)||j.indexOf("compatible")<0&&n.exec(j)||
[];return{browser:j[1]||"",version:j[2]||"0"}},browser:{}});b.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(j,s){R["[object "+s+"]"]=s.toLowerCase()});m=b.uaMatch(m);if(m.browser){b.browser[m.browser]=true;b.browser.version=m.version}if(b.browser.webkit)b.browser.safari=true;if(D)b.inArray=function(j,s){return D.call(s,j)};if(!/\s/.test("\u00a0")){k=/^[\s\xA0]+/;o=/[\s\xA0]+$/}f=b(t);if(t.addEventListener)u=function(){t.removeEventListener("DOMContentLoaded",u,
false);b.ready()};else if(t.attachEvent)u=function(){if(t.readyState==="complete"){t.detachEvent("onreadystatechange",u);b.ready()}};return E.jQuery=E.$=b}();(function(){c.support={};var a=t.documentElement,b=t.createElement("script"),d=t.createElement("div"),e="script"+c.now();d.style.display="none";d.innerHTML=" <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";var f=d.getElementsByTagName("*"),h=d.getElementsByTagName("a")[0],l=t.createElement("select"),
k=l.appendChild(t.createElement("option"));if(!(!f||!f.length||!h)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(h.getAttribute("style")),hrefNormalized:h.getAttribute("href")==="/a",opacity:/^0.55$/.test(h.style.opacity),cssFloat:!!h.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:k.selected,deleteExpando:true,optDisabled:false,checkClone:false,
scriptEval:false,noCloneEvent:true,boxModel:null,inlineBlockNeedsLayout:false,shrinkWrapBlocks:false,reliableHiddenOffsets:true};l.disabled=true;c.support.optDisabled=!k.disabled;b.type="text/javascript";try{b.appendChild(t.createTextNode("window."+e+"=1;"))}catch(o){}a.insertBefore(b,a.firstChild);if(E[e]){c.support.scriptEval=true;delete E[e]}try{delete b.test}catch(x){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function r(){c.support.noCloneEvent=
false;d.detachEvent("onclick",r)});d.cloneNode(true).fireEvent("onclick")}d=t.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=t.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var r=t.createElement("div");r.style.width=r.style.paddingLeft="1px";t.body.appendChild(r);c.boxModel=c.support.boxModel=r.offsetWidth===2;if("zoom"in r.style){r.style.display="inline";r.style.zoom=
1;c.support.inlineBlockNeedsLayout=r.offsetWidth===2;r.style.display="";r.innerHTML="<div style='width:4px;'></div>";c.support.shrinkWrapBlocks=r.offsetWidth!==2}r.innerHTML="<table><tr><td style='padding:0;display:none'></td><td>t</td></tr></table>";var A=r.getElementsByTagName("td");c.support.reliableHiddenOffsets=A[0].offsetHeight===0;A[0].style.display="";A[1].style.display="none";c.support.reliableHiddenOffsets=c.support.reliableHiddenOffsets&&A[0].offsetHeight===0;r.innerHTML="";t.body.removeChild(r).style.display=
"none"});a=function(r){var A=t.createElement("div");r="on"+r;var C=r in A;if(!C){A.setAttribute(r,"return;");C=typeof A[r]==="function"}return C};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=f=h=null}})();var ra={},Ja=/^(?:\{.*\}|\[.*\])$/;c.extend({cache:{},uuid:0,expando:"jQuery"+c.now(),noData:{embed:true,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:true},data:function(a,b,d){if(c.acceptData(a)){a=a==E?ra:a;var e=a.nodeType,f=e?a[c.expando]:null,h=
c.cache;if(!(e&&!f&&typeof b==="string"&&d===B)){if(e)f||(a[c.expando]=f=++c.uuid);else h=a;if(typeof b==="object")if(e)h[f]=c.extend(h[f],b);else c.extend(h,b);else if(e&&!h[f])h[f]={};a=e?h[f]:h;if(d!==B)a[b]=d;return typeof b==="string"?a[b]:a}}},removeData:function(a,b){if(c.acceptData(a)){a=a==E?ra:a;var d=a.nodeType,e=d?a[c.expando]:a,f=c.cache,h=d?f[e]:e;if(b){if(h){delete h[b];d&&c.isEmptyObject(h)&&c.removeData(a)}}else if(d&&c.support.deleteExpando)delete a[c.expando];else if(a.removeAttribute)a.removeAttribute(c.expando);
else if(d)delete f[e];else for(var l in a)delete a[l]}},acceptData:function(a){if(a.nodeName){var b=c.noData[a.nodeName.toLowerCase()];if(b)return!(b===true||a.getAttribute("classid")!==b)}return true}});c.fn.extend({data:function(a,b){var d=null;if(typeof a==="undefined"){if(this.length){var e=this[0].attributes,f;d=c.data(this[0]);for(var h=0,l=e.length;h<l;h++){f=e[h].name;if(f.indexOf("data-")===0){f=f.substr(5);ka(this[0],f,d[f])}}}return d}else if(typeof a==="object")return this.each(function(){c.data(this,
a)});var k=a.split(".");k[1]=k[1]?"."+k[1]:"";if(b===B){d=this.triggerHandler("getData"+k[1]+"!",[k[0]]);if(d===B&&this.length){d=c.data(this[0],a);d=ka(this[0],a,d)}return d===B&&k[1]?this.data(k[0]):d}else return this.each(function(){var o=c(this),x=[k[0],b];o.triggerHandler("setData"+k[1]+"!",x);c.data(this,a,b);o.triggerHandler("changeData"+k[1]+"!",x)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var e=
c.data(a,b);if(!d)return e||[];if(!e||c.isArray(d))e=c.data(a,b,c.makeArray(d));else e.push(d);return e}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),e=d.shift();if(e==="inprogress")e=d.shift();if(e){b==="fx"&&d.unshift("inprogress");e.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===B)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,
a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var sa=/[\n\t]/g,ha=/\s+/,Sa=/\r/g,Ta=/^(?:href|src|style)$/,Ua=/^(?:button|input)$/i,Va=/^(?:button|input|object|select|textarea)$/i,Wa=/^a(?:rea)?$/i,ta=/^(?:radio|checkbox)$/i;c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",
colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};c.fn.extend({attr:function(a,b){return c.access(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(x){var r=c(this);r.addClass(a.call(this,x,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ha),d=0,e=this.length;d<e;d++){var f=this[d];if(f.nodeType===
1)if(f.className){for(var h=" "+f.className+" ",l=f.className,k=0,o=b.length;k<o;k++)if(h.indexOf(" "+b[k]+" ")<0)l+=" "+b[k];f.className=c.trim(l)}else f.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(o){var x=c(this);x.removeClass(a.call(this,o,x.attr("class")))});if(a&&typeof a==="string"||a===B)for(var b=(a||"").split(ha),d=0,e=this.length;d<e;d++){var f=this[d];if(f.nodeType===1&&f.className)if(a){for(var h=(" "+f.className+" ").replace(sa," "),
l=0,k=b.length;l<k;l++)h=h.replace(" "+b[l]+" "," ");f.className=c.trim(h)}else f.className=""}return this},toggleClass:function(a,b){var d=typeof a,e=typeof b==="boolean";if(c.isFunction(a))return this.each(function(f){var h=c(this);h.toggleClass(a.call(this,f,h.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var f,h=0,l=c(this),k=b,o=a.split(ha);f=o[h++];){k=e?k:!l.hasClass(f);l[k?"addClass":"removeClass"](f)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,
"__className__",this.className);this.className=this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(sa," ").indexOf(a)>-1)return true;return false},val:function(a){if(!arguments.length){var b=this[0];if(b){if(c.nodeName(b,"option")){var d=b.attributes.value;return!d||d.specified?b.value:b.text}if(c.nodeName(b,"select")){var e=b.selectedIndex;d=[];var f=b.options;b=b.type==="select-one";
if(e<0)return null;var h=b?e:0;for(e=b?e+1:f.length;h<e;h++){var l=f[h];if(l.selected&&(c.support.optDisabled?!l.disabled:l.getAttribute("disabled")===null)&&(!l.parentNode.disabled||!c.nodeName(l.parentNode,"optgroup"))){a=c(l).val();if(b)return a;d.push(a)}}return d}if(ta.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Sa,"")}return B}var k=c.isFunction(a);return this.each(function(o){var x=c(this),r=a;if(this.nodeType===1){if(k)r=
a.call(this,o,x.val());if(r==null)r="";else if(typeof r==="number")r+="";else if(c.isArray(r))r=c.map(r,function(C){return C==null?"":C+""});if(c.isArray(r)&&ta.test(this.type))this.checked=c.inArray(x.val(),r)>=0;else if(c.nodeName(this,"select")){var A=c.makeArray(r);c("option",this).each(function(){this.selected=c.inArray(c(this).val(),A)>=0});if(!A.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},
attr:function(a,b,d,e){if(!a||a.nodeType===3||a.nodeType===8)return B;if(e&&b in c.attrFn)return c(a)[b](d);e=a.nodeType!==1||!c.isXMLDoc(a);var f=d!==B;b=e&&c.props[b]||b;var h=Ta.test(b);if((b in a||a[b]!==B)&&e&&!h){if(f){b==="type"&&Ua.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");if(d===null)a.nodeType===1&&a.removeAttribute(b);else a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&
b.specified?b.value:Va.test(a.nodeName)||Wa.test(a.nodeName)&&a.href?0:B;return a[b]}if(!c.support.style&&e&&b==="style"){if(f)a.style.cssText=""+d;return a.style.cssText}f&&a.setAttribute(b,""+d);if(!a.attributes[b]&&a.hasAttribute&&!a.hasAttribute(b))return B;a=!c.support.hrefNormalized&&e&&h?a.getAttribute(b,2):a.getAttribute(b);return a===null?B:a}});var X=/\.(.*)$/,ia=/^(?:textarea|input|select)$/i,La=/\./g,Ma=/ /g,Xa=/[^\w\s.|`]/g,Ya=function(a){return a.replace(Xa,"\\$&")},ua={focusin:0,focusout:0};
c.event={add:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(c.isWindow(a)&&a!==E&&!a.frameElement)a=E;if(d===false)d=U;else if(!d)return;var f,h;if(d.handler){f=d;d=f.handler}if(!d.guid)d.guid=c.guid++;if(h=c.data(a)){var l=a.nodeType?"events":"__events__",k=h[l],o=h.handle;if(typeof k==="function"){o=k.handle;k=k.events}else if(!k){a.nodeType||(h[l]=h=function(){});h.events=k={}}if(!o)h.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,
arguments):B};o.elem=a;b=b.split(" ");for(var x=0,r;l=b[x++];){h=f?c.extend({},f):{handler:d,data:e};if(l.indexOf(".")>-1){r=l.split(".");l=r.shift();h.namespace=r.slice(0).sort().join(".")}else{r=[];h.namespace=""}h.type=l;if(!h.guid)h.guid=d.guid;var A=k[l],C=c.event.special[l]||{};if(!A){A=k[l]=[];if(!C.setup||C.setup.call(a,e,r,o)===false)if(a.addEventListener)a.addEventListener(l,o,false);else a.attachEvent&&a.attachEvent("on"+l,o)}if(C.add){C.add.call(a,h);if(!h.handler.guid)h.handler.guid=
d.guid}A.push(h);c.event.global[l]=true}a=null}}},global:{},remove:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(d===false)d=U;var f,h,l=0,k,o,x,r,A,C,J=a.nodeType?"events":"__events__",w=c.data(a),I=w&&w[J];if(w&&I){if(typeof I==="function"){w=I;I=I.events}if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(f in I)c.event.remove(a,f+b)}else{for(b=b.split(" ");f=b[l++];){r=f;k=f.indexOf(".")<0;o=[];if(!k){o=f.split(".");f=o.shift();x=RegExp("(^|\\.)"+
c.map(o.slice(0).sort(),Ya).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(A=I[f])if(d){r=c.event.special[f]||{};for(h=e||0;h<A.length;h++){C=A[h];if(d.guid===C.guid){if(k||x.test(C.namespace)){e==null&&A.splice(h--,1);r.remove&&r.remove.call(a,C)}if(e!=null)break}}if(A.length===0||e!=null&&A.length===1){if(!r.teardown||r.teardown.call(a,o)===false)c.removeEvent(a,f,w.handle);delete I[f]}}else for(h=0;h<A.length;h++){C=A[h];if(k||x.test(C.namespace)){c.event.remove(a,r,C.handler,h);A.splice(h--,1)}}}if(c.isEmptyObject(I)){if(b=
w.handle)b.elem=null;delete w.events;delete w.handle;if(typeof w==="function")c.removeData(a,J);else c.isEmptyObject(w)&&c.removeData(a)}}}}},trigger:function(a,b,d,e){var f=a.type||a;if(!e){a=typeof a==="object"?a[c.expando]?a:c.extend(c.Event(f),a):c.Event(f);if(f.indexOf("!")>=0){a.type=f=f.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[f]&&c.each(c.cache,function(){this.events&&this.events[f]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===
8)return B;a.result=B;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(e=d.nodeType?c.data(d,"handle"):(c.data(d,"__events__")||{}).handle)&&e.apply(d,b);e=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+f]&&d["on"+f].apply(d,b)===false){a.result=false;a.preventDefault()}}catch(h){}if(!a.isPropagationStopped()&&e)c.event.trigger(a,b,e,true);else if(!a.isDefaultPrevented()){var l;e=a.target;var k=f.replace(X,""),o=c.nodeName(e,"a")&&k===
"click",x=c.event.special[k]||{};if((!x._default||x._default.call(d,a)===false)&&!o&&!(e&&e.nodeName&&c.noData[e.nodeName.toLowerCase()])){try{if(e[k]){if(l=e["on"+k])e["on"+k]=null;c.event.triggered=true;e[k]()}}catch(r){}if(l)e["on"+k]=l;c.event.triggered=false}}},handle:function(a){var b,d,e,f;d=[];var h=c.makeArray(arguments);a=h[0]=c.event.fix(a||E.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;if(!b){e=a.type.split(".");a.type=e.shift();d=e.slice(0).sort();e=RegExp("(^|\\.)"+
d.join("\\.(?:.*\\.)?")+"(\\.|$)")}a.namespace=a.namespace||d.join(".");f=c.data(this,this.nodeType?"events":"__events__");if(typeof f==="function")f=f.events;d=(f||{})[a.type];if(f&&d){d=d.slice(0);f=0;for(var l=d.length;f<l;f++){var k=d[f];if(b||e.test(k.namespace)){a.handler=k.handler;a.data=k.data;a.handleObj=k;k=k.handler.apply(this,h);if(k!==B){a.result=k;if(k===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
fix:function(a){if(a[c.expando])return a;var b=a;a=c.Event(b);for(var d=this.props.length,e;d;){e=this.props[--d];a[e]=b[e]}if(!a.target)a.target=a.srcElement||t;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=t.documentElement;d=t.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(a.which==null&&(a.charCode!=null||a.keyCode!=null))a.which=a.charCode!=null?a.charCode:a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==B)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,Y(a.origType,a.selector),c.extend({},a,{handler:Ka,guid:a.handler.guid}))},remove:function(a){c.event.remove(this,
Y(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,d){if(c.isWindow(this))this.onbeforeunload=d},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};c.removeEvent=t.removeEventListener?function(a,b,d){a.removeEventListener&&a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent&&a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=a;this.type=a.type}else this.type=a;this.timeStamp=
c.now();this[c.expando]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=ca;var a=this.originalEvent;if(a)if(a.preventDefault)a.preventDefault();else a.returnValue=false},stopPropagation:function(){this.isPropagationStopped=ca;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=ca;this.stopPropagation()},isDefaultPrevented:U,isPropagationStopped:U,isImmediatePropagationStopped:U};
var va=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},wa=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?wa:va,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?wa:va)}}});if(!c.support.submitBubbles)c.event.special.submit={setup:function(){if(this.nodeName.toLowerCase()!==
"form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length){a.liveFired=B;return la("submit",this,arguments)}});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13){a.liveFired=B;return la("submit",this,arguments)}})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};if(!c.support.changeBubbles){var V,
xa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(e){return e.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},Z=function(a,b){var d=a.target,e,f;if(!(!ia.test(d.nodeName)||d.readOnly)){e=c.data(d,"_change_data");f=xa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",f);if(!(e===B||f===e))if(e!=null||f){a.type="change";a.liveFired=
B;return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:Z,beforedeactivate:Z,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return Z.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return Z.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,"_change_data",xa(a))}},setup:function(){if(this.type===
"file")return false;for(var a in V)c.event.add(this,a+".specialChange",V[a]);return ia.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return ia.test(this.nodeName)}};V=c.event.special.change.filters;V.focus=V.beforeactivate}t.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(e){e=c.event.fix(e);e.type=b;return c.event.trigger(e,null,e.target)}c.event.special[b]={setup:function(){ua[b]++===0&&t.addEventListener(a,d,true)},teardown:function(){--ua[b]===
0&&t.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,e,f){if(typeof d==="object"){for(var h in d)this[b](h,e,d[h],f);return this}if(c.isFunction(e)||e===false){f=e;e=B}var l=b==="one"?c.proxy(f,function(o){c(this).unbind(o,l);return f.apply(this,arguments)}):f;if(d==="unload"&&b!=="one")this.one(d,e,f);else{h=0;for(var k=this.length;h<k;h++)c.event.add(this[h],d,l,e)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&!a.preventDefault)for(var d in a)this.unbind(d,
a[d]);else{d=0;for(var e=this.length;d<e;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,e){return this.live(b,d,e,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){var d=c.Event(a);d.preventDefault();d.stopPropagation();c.event.trigger(d,b,this[0]);return d.result}},toggle:function(a){for(var b=arguments,d=
1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(e){var f=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,f+1);e.preventDefault();return b[f].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var ya={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,e,f,h){var l,k=0,o,x,r=h||this.selector;h=h?this:c(this.context);if(typeof d===
"object"&&!d.preventDefault){for(l in d)h[b](l,e,d[l],r);return this}if(c.isFunction(e)){f=e;e=B}for(d=(d||"").split(" ");(l=d[k++])!=null;){o=X.exec(l);x="";if(o){x=o[0];l=l.replace(X,"")}if(l==="hover")d.push("mouseenter"+x,"mouseleave"+x);else{o=l;if(l==="focus"||l==="blur"){d.push(ya[l]+x);l+=x}else l=(ya[l]||l)+x;if(b==="live"){x=0;for(var A=h.length;x<A;x++)c.event.add(h[x],"live."+Y(l,r),{data:e,selector:r,handler:f,origType:l,origHandler:f,preType:o})}else h.unbind("live."+Y(l,r),f)}}return this}});
c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){c.fn[b]=function(d,e){if(e==null){e=d;d=null}return arguments.length>0?this.bind(b,d,e):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});E.attachEvent&&!E.addEventListener&&c(E).bind("unload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});
(function(){function a(g,i,n,m,p,q){p=0;for(var u=m.length;p<u;p++){var y=m[p];if(y){var F=false;for(y=y[g];y;){if(y.sizcache===n){F=m[y.sizset];break}if(y.nodeType===1&&!q){y.sizcache=n;y.sizset=p}if(y.nodeName.toLowerCase()===i){F=y;break}y=y[g]}m[p]=F}}}function b(g,i,n,m,p,q){p=0;for(var u=m.length;p<u;p++){var y=m[p];if(y){var F=false;for(y=y[g];y;){if(y.sizcache===n){F=m[y.sizset];break}if(y.nodeType===1){if(!q){y.sizcache=n;y.sizset=p}if(typeof i!=="string"){if(y===i){F=true;break}}else if(k.filter(i,
[y]).length>0){F=y;break}}y=y[g]}m[p]=F}}}var d=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,f=Object.prototype.toString,h=false,l=true;[0,0].sort(function(){l=false;return 0});var k=function(g,i,n,m){n=n||[];var p=i=i||t;if(i.nodeType!==1&&i.nodeType!==9)return[];if(!g||typeof g!=="string")return n;var q,u,y,F,M,N=true,O=k.isXML(i),D=[],R=g;do{d.exec("");if(q=d.exec(R)){R=q[3];D.push(q[1]);if(q[2]){F=q[3];
break}}}while(q);if(D.length>1&&x.exec(g))if(D.length===2&&o.relative[D[0]])u=L(D[0]+D[1],i);else for(u=o.relative[D[0]]?[i]:k(D.shift(),i);D.length;){g=D.shift();if(o.relative[g])g+=D.shift();u=L(g,u)}else{if(!m&&D.length>1&&i.nodeType===9&&!O&&o.match.ID.test(D[0])&&!o.match.ID.test(D[D.length-1])){q=k.find(D.shift(),i,O);i=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]}if(i){q=m?{expr:D.pop(),set:C(m)}:k.find(D.pop(),D.length===1&&(D[0]==="~"||D[0]==="+")&&i.parentNode?i.parentNode:i,O);u=q.expr?k.filter(q.expr,
q.set):q.set;if(D.length>0)y=C(u);else N=false;for(;D.length;){q=M=D.pop();if(o.relative[M])q=D.pop();else M="";if(q==null)q=i;o.relative[M](y,q,O)}}else y=[]}y||(y=u);y||k.error(M||g);if(f.call(y)==="[object Array]")if(N)if(i&&i.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&k.contains(i,y[g])))n.push(u[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&n.push(u[g]);else n.push.apply(n,y);else C(y,n);if(F){k(F,p,n,m);k.uniqueSort(n)}return n};k.uniqueSort=function(g){if(w){h=
l;g.sort(w);if(h)for(var i=1;i<g.length;i++)g[i]===g[i-1]&&g.splice(i--,1)}return g};k.matches=function(g,i){return k(g,null,null,i)};k.matchesSelector=function(g,i){return k(i,null,null,[g]).length>0};k.find=function(g,i,n){var m;if(!g)return[];for(var p=0,q=o.order.length;p<q;p++){var u,y=o.order[p];if(u=o.leftMatch[y].exec(g)){var F=u[1];u.splice(1,1);if(F.substr(F.length-1)!=="\\"){u[1]=(u[1]||"").replace(/\\/g,"");m=o.find[y](u,i,n);if(m!=null){g=g.replace(o.match[y],"");break}}}}m||(m=i.getElementsByTagName("*"));
return{set:m,expr:g}};k.filter=function(g,i,n,m){for(var p,q,u=g,y=[],F=i,M=i&&i[0]&&k.isXML(i[0]);g&&i.length;){for(var N in o.filter)if((p=o.leftMatch[N].exec(g))!=null&&p[2]){var O,D,R=o.filter[N];D=p[1];q=false;p.splice(1,1);if(D.substr(D.length-1)!=="\\"){if(F===y)y=[];if(o.preFilter[N])if(p=o.preFilter[N](p,F,n,y,m,M)){if(p===true)continue}else q=O=true;if(p)for(var j=0;(D=F[j])!=null;j++)if(D){O=R(D,p,j,F);var s=m^!!O;if(n&&O!=null)if(s)q=true;else F[j]=false;else if(s){y.push(D);q=true}}if(O!==
B){n||(F=y);g=g.replace(o.match[N],"");if(!q)return[];break}}}if(g===u)if(q==null)k.error(g);else break;u=g}return F};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var o=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},relative:{"+":function(g,i){var n=typeof i==="string",m=n&&!/\W/.test(i);n=n&&!m;if(m)i=i.toLowerCase();m=0;for(var p=g.length,q;m<p;m++)if(q=g[m]){for(;(q=q.previousSibling)&&q.nodeType!==1;);g[m]=n||q&&q.nodeName.toLowerCase()===
i?q||false:q===i}n&&k.filter(i,g,true)},">":function(g,i){var n,m=typeof i==="string",p=0,q=g.length;if(m&&!/\W/.test(i))for(i=i.toLowerCase();p<q;p++){if(n=g[p]){n=n.parentNode;g[p]=n.nodeName.toLowerCase()===i?n:false}}else{for(;p<q;p++)if(n=g[p])g[p]=m?n.parentNode:n.parentNode===i;m&&k.filter(i,g,true)}},"":function(g,i,n){var m,p=e++,q=b;if(typeof i==="string"&&!/\W/.test(i)){m=i=i.toLowerCase();q=a}q("parentNode",i,p,g,m,n)},"~":function(g,i,n){var m,p=e++,q=b;if(typeof i==="string"&&!/\W/.test(i)){m=
i=i.toLowerCase();q=a}q("previousSibling",i,p,g,m,n)}},find:{ID:function(g,i,n){if(typeof i.getElementById!=="undefined"&&!n)return(g=i.getElementById(g[1]))&&g.parentNode?[g]:[]},NAME:function(g,i){if(typeof i.getElementsByName!=="undefined"){for(var n=[],m=i.getElementsByName(g[1]),p=0,q=m.length;p<q;p++)m[p].getAttribute("name")===g[1]&&n.push(m[p]);return n.length===0?null:n}},TAG:function(g,i){return i.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,i,n,m,p,q){g=" "+g[1].replace(/\\/g,
"")+" ";if(q)return g;q=0;for(var u;(u=i[q])!=null;q++)if(u)if(p^(u.className&&(" "+u.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))n||m.push(u);else if(n)i[q]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},CHILD:function(g){if(g[1]==="nth"){var i=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=i[1]+(i[2]||1)-0;g[3]=i[3]-0}g[0]=e++;return g},ATTR:function(g,i,n,
m,p,q){i=g[1].replace(/\\/g,"");if(!q&&o.attrMap[i])g[1]=o.attrMap[i];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,i,n,m,p){if(g[1]==="not")if((d.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,i);else{g=k.filter(g[3],i,n,true^p);n||m.push.apply(m,g);return false}else if(o.match.POS.test(g[0])||o.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===
true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,i,n){return!!k(n[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===
g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},setFilters:{first:function(g,i){return i===0},last:function(g,i,n,m){return i===m.length-1},even:function(g,i){return i%2===0},odd:function(g,i){return i%2===1},lt:function(g,i,n){return i<n[3]-0},gt:function(g,i,n){return i>n[3]-0},nth:function(g,i,n){return n[3]-
0===i},eq:function(g,i,n){return n[3]-0===i}},filter:{PSEUDO:function(g,i,n,m){var p=i[1],q=o.filters[p];if(q)return q(g,n,i,m);else if(p==="contains")return(g.textContent||g.innerText||k.getText([g])||"").indexOf(i[3])>=0;else if(p==="not"){i=i[3];n=0;for(m=i.length;n<m;n++)if(i[n]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+p)},CHILD:function(g,i){var n=i[1],m=g;switch(n){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(n===
"first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":n=i[2];var p=i[3];if(n===1&&p===0)return true;var q=i[0],u=g.parentNode;if(u&&(u.sizcache!==q||!g.nodeIndex)){var y=0;for(m=u.firstChild;m;m=m.nextSibling)if(m.nodeType===1)m.nodeIndex=++y;u.sizcache=q}m=g.nodeIndex-p;return n===0?m===0:m%n===0&&m/n>=0}},ID:function(g,i){return g.nodeType===1&&g.getAttribute("id")===i},TAG:function(g,i){return i==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===
i},CLASS:function(g,i){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(i)>-1},ATTR:function(g,i){var n=i[1];n=o.attrHandle[n]?o.attrHandle[n](g):g[n]!=null?g[n]:g.getAttribute(n);var m=n+"",p=i[2],q=i[4];return n==null?p==="!=":p==="="?m===q:p==="*="?m.indexOf(q)>=0:p==="~="?(" "+m+" ").indexOf(q)>=0:!q?m&&n!==false:p==="!="?m!==q:p==="^="?m.indexOf(q)===0:p==="$="?m.substr(m.length-q.length)===q:p==="|="?m===q||m.substr(0,q.length+1)===q+"-":false},POS:function(g,i,n,m){var p=o.setFilters[i[2]];
if(p)return p(g,n,i,m)}}},x=o.match.POS,r=function(g,i){return"\\"+(i-0+1)},A;for(A in o.match){o.match[A]=RegExp(o.match[A].source+/(?![^\[]*\])(?![^\(]*\))/.source);o.leftMatch[A]=RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[A].source.replace(/\\(\d+)/g,r))}var C=function(g,i){g=Array.prototype.slice.call(g,0);if(i){i.push.apply(i,g);return i}return g};try{Array.prototype.slice.call(t.documentElement.childNodes,0)}catch(J){C=function(g,i){var n=0,m=i||[];if(f.call(g)==="[object Array]")Array.prototype.push.apply(m,
g);else if(typeof g.length==="number")for(var p=g.length;n<p;n++)m.push(g[n]);else for(;g[n];n++)m.push(g[n]);return m}}var w,I;if(t.documentElement.compareDocumentPosition)w=function(g,i){if(g===i){h=true;return 0}if(!g.compareDocumentPosition||!i.compareDocumentPosition)return g.compareDocumentPosition?-1:1;return g.compareDocumentPosition(i)&4?-1:1};else{w=function(g,i){var n,m,p=[],q=[];n=g.parentNode;m=i.parentNode;var u=n;if(g===i){h=true;return 0}else if(n===m)return I(g,i);else if(n){if(!m)return 1}else return-1;
for(;u;){p.unshift(u);u=u.parentNode}for(u=m;u;){q.unshift(u);u=u.parentNode}n=p.length;m=q.length;for(u=0;u<n&&u<m;u++)if(p[u]!==q[u])return I(p[u],q[u]);return u===n?I(g,q[u],-1):I(p[u],i,1)};I=function(g,i,n){if(g===i)return n;for(g=g.nextSibling;g;){if(g===i)return-1;g=g.nextSibling}return 1}}k.getText=function(g){for(var i="",n,m=0;g[m];m++){n=g[m];if(n.nodeType===3||n.nodeType===4)i+=n.nodeValue;else if(n.nodeType!==8)i+=k.getText(n.childNodes)}return i};(function(){var g=t.createElement("div"),
i="script"+(new Date).getTime(),n=t.documentElement;g.innerHTML="<a name='"+i+"'/>";n.insertBefore(g,n.firstChild);if(t.getElementById(i)){o.find.ID=function(m,p,q){if(typeof p.getElementById!=="undefined"&&!q)return(p=p.getElementById(m[1]))?p.id===m[1]||typeof p.getAttributeNode!=="undefined"&&p.getAttributeNode("id").nodeValue===m[1]?[p]:B:[]};o.filter.ID=function(m,p){var q=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&q&&q.nodeValue===p}}n.removeChild(g);
n=g=null})();(function(){var g=t.createElement("div");g.appendChild(t.createComment(""));if(g.getElementsByTagName("*").length>0)o.find.TAG=function(i,n){var m=n.getElementsByTagName(i[1]);if(i[1]==="*"){for(var p=[],q=0;m[q];q++)m[q].nodeType===1&&p.push(m[q]);m=p}return m};g.innerHTML="<a href='#'></a>";if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")o.attrHandle.href=function(i){return i.getAttribute("href",2)};g=null})();t.querySelectorAll&&
function(){var g=k,i=t.createElement("div");i.innerHTML="<p class='TEST'></p>";if(!(i.querySelectorAll&&i.querySelectorAll(".TEST").length===0)){k=function(m,p,q,u){p=p||t;m=m.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!u&&!k.isXML(p))if(p.nodeType===9)try{return C(p.querySelectorAll(m),q)}catch(y){}else if(p.nodeType===1&&p.nodeName.toLowerCase()!=="object"){var F=p.getAttribute("id"),M=F||"__sizzle__";F||p.setAttribute("id",M);try{return C(p.querySelectorAll("#"+M+" "+m),q)}catch(N){}finally{F||
p.removeAttribute("id")}}return g(m,p,q,u)};for(var n in g)k[n]=g[n];i=null}}();(function(){var g=t.documentElement,i=g.matchesSelector||g.mozMatchesSelector||g.webkitMatchesSelector||g.msMatchesSelector,n=false;try{i.call(t.documentElement,"[test!='']:sizzle")}catch(m){n=true}if(i)k.matchesSelector=function(p,q){q=q.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(p))try{if(n||!o.match.PSEUDO.test(q)&&!/!=/.test(q))return i.call(p,q)}catch(u){}return k(q,null,null,[p]).length>0}})();(function(){var g=
t.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){o.order.splice(1,0,"CLASS");o.find.CLASS=function(i,n,m){if(typeof n.getElementsByClassName!=="undefined"&&!m)return n.getElementsByClassName(i[1])};g=null}}})();k.contains=t.documentElement.contains?function(g,i){return g!==i&&(g.contains?g.contains(i):true)}:t.documentElement.compareDocumentPosition?
function(g,i){return!!(g.compareDocumentPosition(i)&16)}:function(){return false};k.isXML=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false};var L=function(g,i){for(var n,m=[],p="",q=i.nodeType?[i]:i;n=o.match.PSEUDO.exec(g);){p+=n[0];g=g.replace(o.match.PSEUDO,"")}g=o.relative[g]?g+"*":g;n=0;for(var u=q.length;n<u;n++)k(g,q[n],m);return k.filter(p,m)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=k.getText;c.isXMLDoc=k.isXML;
c.contains=k.contains})();var Za=/Until$/,$a=/^(?:parents|prevUntil|prevAll)/,ab=/,/,Na=/^.[^:#\[\.,]*$/,bb=Array.prototype.slice,cb=c.expr.match.POS;c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,e=0,f=this.length;e<f;e++){d=b.length;c.find(a,this[e],b);if(e>0)for(var h=d;h<b.length;h++)for(var l=0;l<d;l++)if(b[l]===b[h]){b.splice(h--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,e=b.length;d<e;d++)if(c.contains(this,b[d]))return true})},
not:function(a){return this.pushStack(ma(this,a,false),"not",a)},filter:function(a){return this.pushStack(ma(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){var d=[],e,f,h=this[0];if(c.isArray(a)){var l,k={},o=1;if(h&&a.length){e=0;for(f=a.length;e<f;e++){l=a[e];k[l]||(k[l]=c.expr.match.POS.test(l)?c(l,b||this.context):l)}for(;h&&h.ownerDocument&&h!==b;){for(l in k){e=k[l];if(e.jquery?e.index(h)>-1:c(h).is(e))d.push({selector:l,elem:h,level:o})}h=
h.parentNode;o++}}return d}l=cb.test(a)?c(a,b||this.context):null;e=0;for(f=this.length;e<f;e++)for(h=this[e];h;)if(l?l.index(h)>-1:c.find.matchesSelector(h,a)){d.push(h);break}else{h=h.parentNode;if(!h||!h.ownerDocument||h===b)break}d=d.length>1?c.unique(d):d;return this.pushStack(d,"closest",a)},index:function(a){if(!a||typeof a==="string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var d=typeof a==="string"?c(a,b||this.context):
c.makeArray(a),e=c.merge(this.get(),d);return this.pushStack(!d[0]||!d[0].parentNode||d[0].parentNode.nodeType===11||!e[0]||!e[0].parentNode||e[0].parentNode.nodeType===11?e:c.unique(e))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,
2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,
b){c.fn[a]=function(d,e){var f=c.map(this,b,d);Za.test(a)||(e=d);if(e&&typeof e==="string")f=c.filter(e,f);f=this.length>1?c.unique(f):f;if((this.length>1||ab.test(e))&&$a.test(a))f=f.reverse();return this.pushStack(f,a,bb.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return b.length===1?c.find.matchesSelector(b[0],a)?[b[0]]:[]:c.find.matches(a,b)},dir:function(a,b,d){var e=[];for(a=a[b];a&&a.nodeType!==9&&(d===B||a.nodeType!==1||!c(a).is(d));){a.nodeType===1&&
e.push(a);a=a[b]}return e},nth:function(a,b,d){b=b||1;for(var e=0;a;a=a[d])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var za=/ jQuery\d+="(?:\d+|null)"/g,$=/^\s+/,Aa=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Ba=/<([\w:]+)/,db=/<tbody/i,eb=/<|&#?\w+;/,Ca=/<(?:script|object|embed|option|style)/i,Da=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/\=([^="'>\s]+\/)>/g,P={option:[1,
"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};P.optgroup=P.option;P.tbody=P.tfoot=P.colgroup=P.caption=P.thead;P.th=P.td;if(!c.support.htmlSerialize)P._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==B)return this.empty().append((this[0]&&this[0].ownerDocument||t).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,e;(e=this[d])!=null;d++)if(!a||c.filter(a,[e]).length){if(!b&&e.nodeType===1){c.cleanData(e.getElementsByTagName("*"));c.cleanData([e])}e.parentNode&&e.parentNode.removeChild(e)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,e=this.ownerDocument;if(!d){d=e.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(za,"").replace(fb,'="$1">').replace($,"")],e)[0]}else return this.cloneNode(true)});if(a===true){na(this,b);na(this.find("*"),b.find("*"))}return b},html:function(a){if(a===B)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(za,""):null;
else if(typeof a==="string"&&!Ca.test(a)&&(c.support.leadingWhitespace||!$.test(a))&&!P[(Ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Aa,"<$1></$2>");try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(e){this.empty().append(a)}}else c.isFunction(a)?this.each(function(f){var h=c(this);h.html(a.call(this,f,h.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=
c(this),e=d.html();d.replaceWith(a.call(this,b,e))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){var e,f,h,l=a[0],k=[];if(!c.support.checkClone&&arguments.length===3&&typeof l==="string"&&Da.test(l))return this.each(function(){c(this).domManip(a,
b,d,true)});if(c.isFunction(l))return this.each(function(x){var r=c(this);a[0]=l.call(this,x,b?r.html():B);r.domManip(a,b,d)});if(this[0]){e=l&&l.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:c.buildFragment(a,this,k);h=e.fragment;if(f=h.childNodes.length===1?h=h.firstChild:h.firstChild){b=b&&c.nodeName(f,"tr");f=0;for(var o=this.length;f<o;f++)d.call(b?c.nodeName(this[f],"table")?this[f].getElementsByTagName("tbody")[0]||this[f].appendChild(this[f].ownerDocument.createElement("tbody")):
this[f]:this[f],f>0||e.cacheable||this.length>1?h.cloneNode(true):h)}k.length&&c.each(k,Oa)}return this}});c.buildFragment=function(a,b,d){var e,f,h;b=b&&b[0]?b[0].ownerDocument||b[0]:t;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===t&&!Ca.test(a[0])&&(c.support.checkClone||!Da.test(a[0]))){f=true;if(h=c.fragments[a[0]])if(h!==1)e=h}if(!e){e=b.createDocumentFragment();c.clean(a,b,e,d)}if(f)c.fragments[a[0]]=h?e:1;return{fragment:e,cacheable:f}};c.fragments={};c.each({appendTo:"append",
prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var e=[];d=c(d);var f=this.length===1&&this[0].parentNode;if(f&&f.nodeType===11&&f.childNodes.length===1&&d.length===1){d[b](this[0]);return this}else{f=0;for(var h=d.length;f<h;f++){var l=(f>0?this.clone(true):this).get();c(d[f])[b](l);e=e.concat(l)}return this.pushStack(e,a,d.selector)}}});c.extend({clean:function(a,b,d,e){b=b||t;if(typeof b.createElement==="undefined")b=b.ownerDocument||
b[0]&&b[0].ownerDocument||t;for(var f=[],h=0,l;(l=a[h])!=null;h++){if(typeof l==="number")l+="";if(l){if(typeof l==="string"&&!eb.test(l))l=b.createTextNode(l);else if(typeof l==="string"){l=l.replace(Aa,"<$1></$2>");var k=(Ba.exec(l)||["",""])[1].toLowerCase(),o=P[k]||P._default,x=o[0],r=b.createElement("div");for(r.innerHTML=o[1]+l+o[2];x--;)r=r.lastChild;if(!c.support.tbody){x=db.test(l);k=k==="table"&&!x?r.firstChild&&r.firstChild.childNodes:o[1]==="<table>"&&!x?r.childNodes:[];for(o=k.length-
1;o>=0;--o)c.nodeName(k[o],"tbody")&&!k[o].childNodes.length&&k[o].parentNode.removeChild(k[o])}!c.support.leadingWhitespace&&$.test(l)&&r.insertBefore(b.createTextNode($.exec(l)[0]),r.firstChild);l=r.childNodes}if(l.nodeType)f.push(l);else f=c.merge(f,l)}}if(d)for(h=0;f[h];h++)if(e&&c.nodeName(f[h],"script")&&(!f[h].type||f[h].type.toLowerCase()==="text/javascript"))e.push(f[h].parentNode?f[h].parentNode.removeChild(f[h]):f[h]);else{f[h].nodeType===1&&f.splice.apply(f,[h+1,0].concat(c.makeArray(f[h].getElementsByTagName("script"))));
d.appendChild(f[h])}return f},cleanData:function(a){for(var b,d,e=c.cache,f=c.event.special,h=c.support.deleteExpando,l=0,k;(k=a[l])!=null;l++)if(!(k.nodeName&&c.noData[k.nodeName.toLowerCase()]))if(d=k[c.expando]){if((b=e[d])&&b.events)for(var o in b.events)f[o]?c.event.remove(k,o):c.removeEvent(k,o,b.handle);if(h)delete k[c.expando];else k.removeAttribute&&k.removeAttribute(c.expando);delete e[d]}}});var Ea=/alpha\([^)]*\)/i,gb=/opacity=([^)]*)/,hb=/-([a-z])/ig,ib=/([A-Z])/g,Fa=/^-?\d+(?:px)?$/i,
jb=/^-?\d/,kb={position:"absolute",visibility:"hidden",display:"block"},Pa=["Left","Right"],Qa=["Top","Bottom"],W,Ga,aa,lb=function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){if(arguments.length===2&&b===B)return this;return c.access(this,a,b,true,function(d,e,f){return f!==B?c.style(d,e,f):c.css(d,e)})};c.extend({cssHooks:{opacity:{get:function(a,b){if(b){var d=W(a,"opacity","opacity");return d===""?"1":d}else return a.style.opacity}}},cssNumber:{zIndex:true,fontWeight:true,opacity:true,
zoom:true,lineHeight:true},cssProps:{"float":c.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,d,e){if(!(!a||a.nodeType===3||a.nodeType===8||!a.style)){var f,h=c.camelCase(b),l=a.style,k=c.cssHooks[h];b=c.cssProps[h]||h;if(d!==B){if(!(typeof d==="number"&&isNaN(d)||d==null)){if(typeof d==="number"&&!c.cssNumber[h])d+="px";if(!k||!("set"in k)||(d=k.set(a,d))!==B)try{l[b]=d}catch(o){}}}else{if(k&&"get"in k&&(f=k.get(a,false,e))!==B)return f;return l[b]}}},css:function(a,b,d){var e,f=c.camelCase(b),
h=c.cssHooks[f];b=c.cssProps[f]||f;if(h&&"get"in h&&(e=h.get(a,true,d))!==B)return e;else if(W)return W(a,b,f)},swap:function(a,b,d){var e={},f;for(f in b){e[f]=a.style[f];a.style[f]=b[f]}d.call(a);for(f in b)a.style[f]=e[f]},camelCase:function(a){return a.replace(hb,lb)}});c.curCSS=c.css;c.each(["height","width"],function(a,b){c.cssHooks[b]={get:function(d,e,f){var h;if(e){if(d.offsetWidth!==0)h=oa(d,b,f);else c.swap(d,kb,function(){h=oa(d,b,f)});if(h<=0){h=W(d,b,b);if(h==="0px"&&aa)h=aa(d,b,b);
if(h!=null)return h===""||h==="auto"?"0px":h}if(h<0||h==null){h=d.style[b];return h===""||h==="auto"?"0px":h}return typeof h==="string"?h:h+"px"}},set:function(d,e){if(Fa.test(e)){e=parseFloat(e);if(e>=0)return e+"px"}else return e}}});if(!c.support.opacity)c.cssHooks.opacity={get:function(a,b){return gb.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var d=a.style;d.zoom=1;var e=c.isNaN(b)?"":"alpha(opacity="+b*100+")",f=
d.filter||"";d.filter=Ea.test(f)?f.replace(Ea,e):d.filter+" "+e}};if(t.defaultView&&t.defaultView.getComputedStyle)Ga=function(a,b,d){var e;d=d.replace(ib,"-$1").toLowerCase();if(!(b=a.ownerDocument.defaultView))return B;if(b=b.getComputedStyle(a,null)){e=b.getPropertyValue(d);if(e===""&&!c.contains(a.ownerDocument.documentElement,a))e=c.style(a,d)}return e};if(t.documentElement.currentStyle)aa=function(a,b){var d,e,f=a.currentStyle&&a.currentStyle[b],h=a.style;if(!Fa.test(f)&&jb.test(f)){d=h.left;
e=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;h.left=b==="fontSize"?"1em":f||0;f=h.pixelLeft+"px";h.left=d;a.runtimeStyle.left=e}return f===""?"auto":f};W=Ga||aa;if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=a.offsetHeight;return a.offsetWidth===0&&b===0||!c.support.reliableHiddenOffsets&&(a.style.display||c.css(a,"display"))==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var mb=c.now(),nb=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
ob=/^(?:select|textarea)/i,pb=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,qb=/^(?:GET|HEAD)$/,Ra=/\[\]$/,T=/\=\?(&|$)/,ja=/\?/,rb=/([?&])_=[^&]*/,sb=/^(\w+:)?\/\/([^\/?#]+)/,tb=/%20/g,ub=/#.*$/,Ha=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!=="string"&&Ha)return Ha.apply(this,arguments);else if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var f=a.slice(e,a.length);a=a.slice(0,e)}e="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b===
"object"){b=c.param(b,c.ajaxSettings.traditional);e="POST"}var h=this;c.ajax({url:a,type:e,dataType:"html",data:b,complete:function(l,k){if(k==="success"||k==="notmodified")h.html(f?c("<div>").append(l.responseText.replace(nb,"")).find(f):l.responseText);d&&h.each(d,[l.responseText,k,l])}});return this},serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&
!this.disabled&&(this.checked||ob.test(this.nodeName)||pb.test(this.type))}).map(function(a,b){var d=c(this).val();return d==null?null:c.isArray(d)?c.map(d,function(e){return{name:b.name,value:e}}):{name:b.name,value:d}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:e})},
getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:e})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return new E.XMLHttpRequest},accepts:{xml:"application/xml, text/xml",html:"text/html",
script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},ajax:function(a){var b=c.extend(true,{},c.ajaxSettings,a),d,e,f,h=b.type.toUpperCase(),l=qb.test(h);b.url=b.url.replace(ub,"");b.context=a&&a.context!=null?a.context:b;if(b.data&&b.processData&&typeof b.data!=="string")b.data=c.param(b.data,b.traditional);if(b.dataType==="jsonp"){if(h==="GET")T.test(b.url)||(b.url+=(ja.test(b.url)?"&":"?")+(b.jsonp||"callback")+"=?");else if(!b.data||
!T.test(b.data))b.data=(b.data?b.data+"&":"")+(b.jsonp||"callback")+"=?";b.dataType="json"}if(b.dataType==="json"&&(b.data&&T.test(b.data)||T.test(b.url))){d=b.jsonpCallback||"jsonp"+mb++;if(b.data)b.data=(b.data+"").replace(T,"="+d+"$1");b.url=b.url.replace(T,"="+d+"$1");b.dataType="script";var k=E[d];E[d]=function(m){if(c.isFunction(k))k(m);else{E[d]=B;try{delete E[d]}catch(p){}}f=m;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);r&&r.removeChild(A)}}if(b.dataType==="script"&&b.cache===null)b.cache=
false;if(b.cache===false&&l){var o=c.now(),x=b.url.replace(rb,"$1_="+o);b.url=x+(x===b.url?(ja.test(b.url)?"&":"?")+"_="+o:"")}if(b.data&&l)b.url+=(ja.test(b.url)?"&":"?")+b.data;b.global&&c.active++===0&&c.event.trigger("ajaxStart");o=(o=sb.exec(b.url))&&(o[1]&&o[1].toLowerCase()!==location.protocol||o[2].toLowerCase()!==location.host);if(b.dataType==="script"&&h==="GET"&&o){var r=t.getElementsByTagName("head")[0]||t.documentElement,A=t.createElement("script");if(b.scriptCharset)A.charset=b.scriptCharset;
A.src=b.url;if(!d){var C=false;A.onload=A.onreadystatechange=function(){if(!C&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){C=true;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);A.onload=A.onreadystatechange=null;r&&A.parentNode&&r.removeChild(A)}}}r.insertBefore(A,r.firstChild);return B}var J=false,w=b.xhr();if(w){b.username?w.open(h,b.url,b.async,b.username,b.password):w.open(h,b.url,b.async);try{if(b.data!=null&&!l||a&&a.contentType)w.setRequestHeader("Content-Type",
b.contentType);if(b.ifModified){c.lastModified[b.url]&&w.setRequestHeader("If-Modified-Since",c.lastModified[b.url]);c.etag[b.url]&&w.setRequestHeader("If-None-Match",c.etag[b.url])}o||w.setRequestHeader("X-Requested-With","XMLHttpRequest");w.setRequestHeader("Accept",b.dataType&&b.accepts[b.dataType]?b.accepts[b.dataType]+", */*; q=0.01":b.accepts._default)}catch(I){}if(b.beforeSend&&b.beforeSend.call(b.context,w,b)===false){b.global&&c.active--===1&&c.event.trigger("ajaxStop");w.abort();return false}b.global&&
c.triggerGlobal(b,"ajaxSend",[w,b]);var L=w.onreadystatechange=function(m){if(!w||w.readyState===0||m==="abort"){J||c.handleComplete(b,w,e,f);J=true;if(w)w.onreadystatechange=c.noop}else if(!J&&w&&(w.readyState===4||m==="timeout")){J=true;w.onreadystatechange=c.noop;e=m==="timeout"?"timeout":!c.httpSuccess(w)?"error":b.ifModified&&c.httpNotModified(w,b.url)?"notmodified":"success";var p;if(e==="success")try{f=c.httpData(w,b.dataType,b)}catch(q){e="parsererror";p=q}if(e==="success"||e==="notmodified")d||
c.handleSuccess(b,w,e,f);else c.handleError(b,w,e,p);d||c.handleComplete(b,w,e,f);m==="timeout"&&w.abort();if(b.async)w=null}};try{var g=w.abort;w.abort=function(){w&&Function.prototype.call.call(g,w);L("abort")}}catch(i){}b.async&&b.timeout>0&&setTimeout(function(){w&&!J&&L("timeout")},b.timeout);try{w.send(l||b.data==null?null:b.data)}catch(n){c.handleError(b,w,null,n);c.handleComplete(b,w,e,f)}b.async||L();return w}},param:function(a,b){var d=[],e=function(h,l){l=c.isFunction(l)?l():l;d[d.length]=
encodeURIComponent(h)+"="+encodeURIComponent(l)};if(b===B)b=c.ajaxSettings.traditional;if(c.isArray(a)||a.jquery)c.each(a,function(){e(this.name,this.value)});else for(var f in a)da(f,a[f],b,e);return d.join("&").replace(tb,"+")}});c.extend({active:0,lastModified:{},etag:{},handleError:function(a,b,d,e){a.error&&a.error.call(a.context,b,d,e);a.global&&c.triggerGlobal(a,"ajaxError",[b,a,e])},handleSuccess:function(a,b,d,e){a.success&&a.success.call(a.context,e,d,b);a.global&&c.triggerGlobal(a,"ajaxSuccess",
[b,a])},handleComplete:function(a,b,d){a.complete&&a.complete.call(a.context,b,d);a.global&&c.triggerGlobal(a,"ajaxComplete",[b,a]);a.global&&c.active--===1&&c.event.trigger("ajaxStop")},triggerGlobal:function(a,b,d){(a.context&&a.context.url==null?c(a.context):c.event).trigger(b,d)},httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===1223}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),
e=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(e)c.etag[b]=e;return a.status===304},httpData:function(a,b,d){var e=a.getResponseHeader("content-type")||"",f=b==="xml"||!b&&e.indexOf("xml")>=0;a=f?a.responseXML:a.responseText;f&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b==="json"||!b&&e.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&e.indexOf("javascript")>=0)c.globalEval(a);return a}});
if(E.ActiveXObject)c.ajaxSettings.xhr=function(){if(E.location.protocol!=="file:")try{return new E.XMLHttpRequest}catch(a){}try{return new E.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}};c.support.ajax=!!c.ajaxSettings.xhr();var ea={},vb=/^(?:toggle|show|hide)$/,wb=/^([+\-]=)?([\d+.\-]+)(.*)$/,ba,pa=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b,d){if(a||a===0)return this.animate(S("show",
3),a,b,d);else{d=0;for(var e=this.length;d<e;d++){a=this[d];b=a.style.display;if(!c.data(a,"olddisplay")&&b==="none")b=a.style.display="";b===""&&c.css(a,"display")==="none"&&c.data(a,"olddisplay",qa(a.nodeName))}for(d=0;d<e;d++){a=this[d];b=a.style.display;if(b===""||b==="none")a.style.display=c.data(a,"olddisplay")||""}return this}},hide:function(a,b,d){if(a||a===0)return this.animate(S("hide",3),a,b,d);else{a=0;for(b=this.length;a<b;a++){d=c.css(this[a],"display");d!=="none"&&c.data(this[a],"olddisplay",
d)}for(a=0;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b,d){var e=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||e?this.each(function(){var f=e?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(S("toggle",3),a,b,d);return this},fadeTo:function(a,b,d,e){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d,e)},animate:function(a,b,d,e){var f=c.speed(b,
d,e);if(c.isEmptyObject(a))return this.each(f.complete);return this[f.queue===false?"each":"queue"](function(){var h=c.extend({},f),l,k=this.nodeType===1,o=k&&c(this).is(":hidden"),x=this;for(l in a){var r=c.camelCase(l);if(l!==r){a[r]=a[l];delete a[l];l=r}if(a[l]==="hide"&&o||a[l]==="show"&&!o)return h.complete.call(this);if(k&&(l==="height"||l==="width")){h.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY];if(c.css(this,"display")==="inline"&&c.css(this,"float")==="none")if(c.support.inlineBlockNeedsLayout)if(qa(this.nodeName)===
"inline")this.style.display="inline-block";else{this.style.display="inline";this.style.zoom=1}else this.style.display="inline-block"}if(c.isArray(a[l])){(h.specialEasing=h.specialEasing||{})[l]=a[l][1];a[l]=a[l][0]}}if(h.overflow!=null)this.style.overflow="hidden";h.curAnim=c.extend({},a);c.each(a,function(A,C){var J=new c.fx(x,h,A);if(vb.test(C))J[C==="toggle"?o?"show":"hide":C](a);else{var w=wb.exec(C),I=J.cur()||0;if(w){var L=parseFloat(w[2]),g=w[3]||"px";if(g!=="px"){c.style(x,A,(L||1)+g);I=(L||
1)/J.cur()*I;c.style(x,A,I+g)}if(w[1])L=(w[1]==="-="?-1:1)*L+I;J.custom(I,L,g)}else J.custom(I,C,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);this.each(function(){for(var e=d.length-1;e>=0;e--)if(d[e].elem===this){b&&d[e](true);d.splice(e,1)}});b||this.dequeue();return this}});c.each({slideDown:S("show",1),slideUp:S("hide",1),slideToggle:S("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){c.fn[a]=function(d,e,f){return this.animate(b,
d,e,f)}});c.extend({speed:function(a,b,d){var e=a&&typeof a==="object"?c.extend({},a):{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};e.duration=c.fx.off?0:typeof e.duration==="number"?e.duration:e.duration in c.fx.speeds?c.fx.speeds[e.duration]:c.fx.speeds._default;e.old=e.complete;e.complete=function(){e.queue!==false&&c(this).dequeue();c.isFunction(e.old)&&e.old.call(this)};return e},easing:{linear:function(a,b,d,e){return d+e*a},swing:function(a,b,d,e){return(-Math.cos(a*
Math.PI)/2+0.5)*e+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||c.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a=parseFloat(c.css(this.elem,this.prop));return a&&a>-1E4?a:0},custom:function(a,b,d){function e(l){return f.step(l)}
var f=this,h=c.fx;this.startTime=c.now();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;this.pos=this.state=0;e.elem=this.elem;if(e()&&c.timers.push(e)&&!ba)ba=setInterval(h.tick,h.interval)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;
this.custom(this.cur(),0)},step:function(a){var b=c.now(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var e in this.options.curAnim)if(this.options.curAnim[e]!==true)d=false;if(d){if(this.options.overflow!=null&&!c.support.shrinkWrapBlocks){var f=this.elem,h=this.options;c.each(["","X","Y"],function(k,o){f.style["overflow"+o]=h.overflow[k]})}this.options.hide&&c(this.elem).hide();if(this.options.hide||
this.options.show)for(var l in this.options.curAnim)c.style(this.elem,l,this.options.orig[l]);this.options.complete.call(this.elem)}return false}else{a=b-this.startTime;this.state=a/this.options.duration;b=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||b](this.state,a,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=
c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||c.fx.stop()},interval:13,stop:function(){clearInterval(ba);ba=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===
b.elem}).length};var xb=/^t(?:able|d|h)$/i,Ia=/^(?:body|html)$/i;c.fn.offset="getBoundingClientRect"in t.documentElement?function(a){var b=this[0],d;if(a)return this.each(function(l){c.offset.setOffset(this,a,l)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);try{d=b.getBoundingClientRect()}catch(e){}var f=b.ownerDocument,h=f.documentElement;if(!d||!c.contains(h,b))return d||{top:0,left:0};b=f.body;f=fa(f);return{top:d.top+(f.pageYOffset||c.support.boxModel&&
h.scrollTop||b.scrollTop)-(h.clientTop||b.clientTop||0),left:d.left+(f.pageXOffset||c.support.boxModel&&h.scrollLeft||b.scrollLeft)-(h.clientLeft||b.clientLeft||0)}}:function(a){var b=this[0];if(a)return this.each(function(x){c.offset.setOffset(this,a,x)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d,e=b.offsetParent,f=b.ownerDocument,h=f.documentElement,l=f.body;d=(f=f.defaultView)?f.getComputedStyle(b,null):b.currentStyle;
for(var k=b.offsetTop,o=b.offsetLeft;(b=b.parentNode)&&b!==l&&b!==h;){if(c.offset.supportsFixedPosition&&d.position==="fixed")break;d=f?f.getComputedStyle(b,null):b.currentStyle;k-=b.scrollTop;o-=b.scrollLeft;if(b===e){k+=b.offsetTop;o+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&xb.test(b.nodeName))){k+=parseFloat(d.borderTopWidth)||0;o+=parseFloat(d.borderLeftWidth)||0}e=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&d.overflow!=="visible"){k+=
parseFloat(d.borderTopWidth)||0;o+=parseFloat(d.borderLeftWidth)||0}d=d}if(d.position==="relative"||d.position==="static"){k+=l.offsetTop;o+=l.offsetLeft}if(c.offset.supportsFixedPosition&&d.position==="fixed"){k+=Math.max(h.scrollTop,l.scrollTop);o+=Math.max(h.scrollLeft,l.scrollLeft)}return{top:k,left:o}};c.offset={initialize:function(){var a=t.body,b=t.createElement("div"),d,e,f,h=parseFloat(c.css(a,"marginTop"))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",
height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";a.insertBefore(b,a.firstChild);d=b.firstChild;e=d.firstChild;f=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=e.offsetTop!==5;this.doesAddBorderForTableAndCells=
f.offsetTop===5;e.style.position="fixed";e.style.top="20px";this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15;e.style.position=e.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==h;a.removeChild(b);c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.css(a,
"marginTop"))||0;d+=parseFloat(c.css(a,"marginLeft"))||0}return{top:b,left:d}},setOffset:function(a,b,d){var e=c.css(a,"position");if(e==="static")a.style.position="relative";var f=c(a),h=f.offset(),l=c.css(a,"top"),k=c.css(a,"left"),o=e==="absolute"&&c.inArray("auto",[l,k])>-1;e={};var x={};if(o)x=f.position();l=o?x.top:parseInt(l,10)||0;k=o?x.left:parseInt(k,10)||0;if(c.isFunction(b))b=b.call(a,d,h);if(b.top!=null)e.top=b.top-h.top+l;if(b.left!=null)e.left=b.left-h.left+k;"using"in b?b.using.call(a,
e):f.css(e)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),e=Ia.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.css(a,"marginTop"))||0;d.left-=parseFloat(c.css(a,"marginLeft"))||0;e.top+=parseFloat(c.css(b[0],"borderTopWidth"))||0;e.left+=parseFloat(c.css(b[0],"borderLeftWidth"))||0;return{top:d.top-e.top,left:d.left-e.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||t.body;a&&!Ia.test(a.nodeName)&&
c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(e){var f=this[0],h;if(!f)return null;if(e!==B)return this.each(function(){if(h=fa(this))h.scrollTo(!a?e:c(h).scrollLeft(),a?e:c(h).scrollTop());else this[d]=e});else return(h=fa(f))?"pageXOffset"in h?h[a?"pageYOffset":"pageXOffset"]:c.support.boxModel&&h.document.documentElement[d]||h.document.body[d]:f[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();
c.fn["inner"+b]=function(){return this[0]?parseFloat(c.css(this[0],d,"padding")):null};c.fn["outer"+b]=function(e){return this[0]?parseFloat(c.css(this[0],d,e?"margin":"border")):null};c.fn[d]=function(e){var f=this[0];if(!f)return e==null?null:this;if(c.isFunction(e))return this.each(function(l){var k=c(this);k[d](e.call(this,l,k[d]()))});if(c.isWindow(f))return f.document.compatMode==="CSS1Compat"&&f.document.documentElement["client"+b]||f.document.body["client"+b];else if(f.nodeType===9)return Math.max(f.documentElement["client"+
b],f.body["scroll"+b],f.documentElement["scroll"+b],f.body["offset"+b],f.documentElement["offset"+b]);else if(e===B){f=c.css(f,d);var h=parseFloat(f);return c.isNaN(h)?f:h}else return this.css(d,typeof e==="string"?e:e+"px")}})})(window);

View File

@ -0,0 +1,16 @@
$(document).ready(function() {
$("#quicksearch").searchField();
$('.resource :input', document.myForm).bind("change", function() { setConfirmUnload(true); });
$("a.fancybox").fancybox({
'titlePosition': 'over',
'type': 'image'
});
});
function setConfirmUnload(on) {
window.onbeforeunload = (on) ? unloadMessage : null;
}
function unloadMessage() {
return "You have entered new data on this page. If you navigate away from this page without first saving your data, the changes will be lost.";
}

View File

@ -1,3 +1,12 @@
/*
* jquery-ujs
*
* http://github.com/rails/jquery-ujs/blob/master/src/rails.js
*
* This rails.js file supports jQuery 1.4.3 and 1.4.4 .
*
*/
jQuery(function ($) {
var csrf_token = $('meta[name=csrf-token]').attr('content'),
csrf_param = $('meta[name=csrf-param]').attr('content');
@ -19,26 +28,34 @@ jQuery(function ($) {
},
/**
* Handles execution of remote calls firing overridable events along the way
* Handles execution of remote calls. Provides following callbacks:
*
* - ajax:beforeSend - is executed before firing ajax call
* - ajax:success - is executed when status is success
* - ajax:complete - is executed when the request finishes, whether in failure or success.
* - ajax:error - is execute in case of error
*/
callRemote: function () {
var el = this,
method = el.attr('method') || el.attr('data-method') || 'GET',
url = el.attr('action') || el.attr('href'),
dataType = el.attr('data-type') || 'script';
dataType = el.attr('data-type') || ($.ajaxSettings && $.ajaxSettings.dataType);
if (url === undefined) {
throw "No URL specified for remote call (action or href must be present).";
throw "No URL specified for remote call (action or href must be present).";
} else {
if (el.triggerAndReturn('ajax:before')) {
var data = el.is('form') ? el.serializeArray() : [];
var $this = $(this), data = el.is('form') ? el.serializeArray() : [];
$.ajax({
url: url,
data: data,
dataType: dataType,
type: method.toUpperCase(),
beforeSend: function (xhr) {
el.trigger('ajax:loading', xhr);
xhr.setRequestHeader("Accept", "text/javascript");
if ($this.triggerHandler('ajax:beforeSend') === false) {
return false;
}
},
success: function (data, status, xhr) {
el.trigger('ajax:success', [data, status, xhr]);
@ -47,12 +64,9 @@ jQuery(function ($) {
el.trigger('ajax:complete', xhr);
},
error: function (xhr, status, error) {
el.trigger('ajax:failure', [xhr, status, error]);
el.trigger('ajax:error', [xhr, status, error]);
}
});
}
el.trigger('ajax:after');
}
}
});
@ -60,7 +74,8 @@ jQuery(function ($) {
/**
* confirmation handler
*/
$('a[data-confirm],input[data-confirm]').live('click', function () {
$('body').delegate('a[data-confirm], button[data-confirm], input[data-confirm]', 'click.rails', function () {
var el = $(this);
if (el.triggerAndReturn('confirm')) {
if (!confirm(el.attr('data-confirm'))) {
@ -70,28 +85,34 @@ jQuery(function ($) {
});
/**
* remote handlers
*/
$('form[data-remote]').live('submit', function (e) {
$('form[data-remote]').live('submit.rails', function (e) {
$(this).callRemote();
e.preventDefault();
});
$('a[data-remote],input[data-remote]').live('click', function (e) {
$('a[data-remote],input[data-remote]').live('click.rails', function (e) {
$(this).callRemote();
e.preventDefault();
});
$('a[data-method]:not([data-remote])').live('click', function (e){
/**
* <%= link_to "Delete", user_path(@user), :method => :delete, :confirm => "Are you sure?" %>
*
* <a href="/users/5" data-confirm="Are you sure?" data-method="delete" rel="nofollow">Delete</a>
*/
$('a[data-method]:not([data-remote])').live('click.rails', function (e){
var link = $(this),
href = link.attr('href'),
method = link.attr('data-method'),
form = $('<form method="post" action="'+href+'"></form>'),
metadata_input = '<input name="_method" value="'+method+'" type="hidden" />';
if (csrf_param != null && csrf_token != null) {
metadata_input += '<input name="'+csrf_param+'" value="'+csrf_token+'" type="hidden" />';
if (csrf_param !== undefined && csrf_token !== undefined) {
metadata_input += '<input name="'+csrf_param+'" value="'+csrf_token+'" type="hidden" />';
}
form.hide()
@ -105,9 +126,9 @@ jQuery(function ($) {
/**
* disable-with handlers
*/
var disable_with_input_selector = 'input[data-disable-with]';
var disable_with_form_remote_selector = 'form[data-remote]:has(' + disable_with_input_selector + ')';
var disable_with_form_not_remote_selector = 'form:not([data-remote]):has(' + disable_with_input_selector + ')';
var disable_with_input_selector = 'input[data-disable-with]',
disable_with_form_remote_selector = 'form[data-remote]:has(' + disable_with_input_selector + ')',
disable_with_form_not_remote_selector = 'form:not([data-remote]):has(' + disable_with_input_selector + ')';
var disable_with_input_function = function () {
$(this).find(disable_with_input_selector).each(function () {
@ -118,10 +139,10 @@ jQuery(function ($) {
});
};
$(disable_with_form_remote_selector).live('ajax:before', disable_with_input_function);
$(disable_with_form_not_remote_selector).live('submit', disable_with_input_function);
$(disable_with_form_remote_selector).live('ajax:before.rails', disable_with_input_function);
$(disable_with_form_not_remote_selector).live('submit.rails', disable_with_input_function);
$(disable_with_form_remote_selector).live('ajax:complete', function () {
$(disable_with_form_remote_selector).live('ajax:complete.rails', function () {
$(this).find(disable_with_input_selector).each(function () {
var input = $(this);
input.removeAttr('disabled')
@ -129,4 +150,11 @@ jQuery(function ($) {
});
});
var jqueryVersion = $().jquery;
if (!( (jqueryVersion === '1.4.3') || (jqueryVersion === '1.4.4'))){
alert('This rails.js does not support the jQuery version you are using. Please read documentation.');
}
});

View File

@ -24,12 +24,7 @@ html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abb
/* @group Defaults */
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
html, body {}
body {
font-family: "Lucida Grande", Sans-serif;
@ -61,7 +56,7 @@ li { color: #777; }
a,
a:visited,
a:link { color: #000; font-weight: normal; }
a:hover { color: #333; text-decoration: underline; }
a:hover { color: #333; text-decoration: underline!important; }
/* @end */
@ -109,6 +104,7 @@ a:hover { color: #333; text-decoration: underline; }
/* @group Content */
#content { margin: 0 0 0 20em; }
#content.headless { margin: 1em; }
#content h2 { font-size: 1.5em; margin: 0.5em 0; }
#content h2 small { font-size: 0.75em; }
@ -159,21 +155,13 @@ a:hover { color: #333; text-decoration: underline; }
#sidebar ul li { padding: 0.25em 1em; }
#sidebar a { text-decoration: none; }
#sidebar a:hover { text-decoration: underline; }
#sidebar li a:hover { text-decoration: underline; }
/* @end */
/* @group Footer */
#footer_wrapper {
bottom: 0;
height: 50px;
position: absolute;
width: 100%;
z-index: -100;
}
#footer { font-size: 0.9em; padding: 20px; }
#footer_wrapper { display: none; }
/* @end */
@ -182,7 +170,8 @@ a:hover { color: #333; text-decoration: underline; }
fieldset { margin: 0; padding: 0; }
fieldset ol { margin: 0!important; }
fieldset ol li { list-style: none!important; margin: 1em 0!important; }
fieldset small { font-size: 0.9em; font-weight: normal; }
fieldset small { font-size: 0.9em; font-weight: normal; margin: 0 0 0 0.25em; }
fieldset small a { text-decoration: none; }
fieldset.inputs { }
@ -194,7 +183,7 @@ fieldset.inputs { }
}
fieldset.inputs input[type='text']:focus,
fieldset.inputs input[type='password']:focus, {
fieldset.inputs input[type='password']:focus {
background: #FFFCE1;
}
@ -244,12 +233,12 @@ fieldset.buttons { }
margin: 0 0 1em 0;
}
#account #box h1 { margin: 0.5em 0 0.5em 0; }
#account #box h1 { margin: 0.5em 0; }
#account #box ul { margin: 1.5em 0 0.5em 0; }
#account #box li { margin: 1em 0; }
#account #box input.text { font-size: 2em; padding: 0.25em; width: 17.25em; }
#account #box input.button { margin: 0.5em 0 0 0; }
#account #box input.button { margin: 0.5em 1em 0 0; }
#account #footer { font-size: 0.9em; line-height: 1.8em; text-align: center; }
#account #footer a { font-weight: bold; }
@ -258,15 +247,23 @@ fieldset.buttons { }
/* @group Pagination */
#pagination { margin: 1.5em auto; text-align: center; }
.pagination {
border-top: 1px solid #D3D3D3;
margin: 1.5em auto;
padding: 1em 0;
text-align: center;
}
#pagination a { padding: 3px 5px; text-decoration: none; }
#pagination a:hover { background: #000; color: #FFF; }
.pagination em { font-style: normal!important; }
.pagination em,
.pagination a { padding: 3px 5px; text-decoration: none; }
.pagination a:hover,
.pagination em { background: #000; color: #FFF; }
#pagination span.disabled { color: #D3D3D3; margin: 2px; padding: 2px 3px; }
.pagination span.disabled { color: #D3D3D3; margin: 2px; padding: 2px 3px; }
#pagination .left { float: left; }
#pagination .right { float: right; }
.pagination .previous_page { float: left; }
.pagination .next_page { float: right; }
/* @end */
@ -276,7 +273,7 @@ fieldset.buttons { }
.alert,
.notice {
padding: 0.8em;
margin-bottom: 1em;
margin: 1em 0;
border: 1px solid #ddd;
-moz-border-radius: 8px;
-webkit-border-radius: 8px;
@ -327,14 +324,6 @@ fieldset.buttons { }
/* @end */
/* @group Sprite */
.sprite { background: url(../images/ui-icons.png) no-repeat; text-indent: -10000px; }
.trash { background-position: -177px -97px; height: 15px; width: 15px; }
.unrelate { background-position: -145px -129px; height: 15px; width: 15px; }
/* @end */
/* @group Filters */
#content .filters {
@ -356,3 +345,25 @@ fieldset.buttons { }
#search input { font-size: 1em; padding: 3px; }
/* @end */
/* @group Misc */
ul.predefined_filters {
margin: 1em 0 0.5em 0!important;
}
ul.predefined_filters li {
display: inline;
margin: 0 0.5em 0 0;
}
.actions {
font-size: 0.9em;
margin: 0.5em 0;
height: 1em;
}
tr .hidden { display: none; }
tr:hover .hidden { display: block; }
/* @end */

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -5,33 +5,33 @@
* to offer multiple easing options
*
* TERMS OF USE - jQuery Easing
*
* Open source under the BSD License.
*
*
* Open source under the BSD License.
*
* Copyright © 2008 George McGinley Smith
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of
*
* Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* Neither the name of the author nor the names of contributors may be used to endorse
*
* Neither the name of the author nor the names of contributors may be used to endorse
* or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
@ -41,32 +41,32 @@ eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a
/*
*
* TERMS OF USE - EASING EQUATIONS
*
* Open source under the BSD License.
*
*
* Open source under the BSD License.
*
* Copyright © 2001 Robert Penner
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of
*
* Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* Neither the name of the author nor the names of contributors may be used to endorse
*
* Neither the name of the author nor the names of contributors may be used to endorse
* or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/

View File

@ -0,0 +1,359 @@
/*
* FancyBox - jQuery Plugin
* Simple and fancy lightbox alternative
*
* Examples and documentation at: http://fancybox.net
*
* Copyright (c) 2008 - 2010 Janis Skarnelis
* That said, it is hardly a one-person project. Many people have submitted bugs, code, and offered their advice freely. Their support is greatly appreciated.
*
* Version: 1.3.4 (11/11/2010)
* Requires: jQuery v1.3+
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*/
#fancybox-loading {
position: fixed;
top: 50%;
left: 50%;
width: 40px;
height: 40px;
margin-top: -20px;
margin-left: -20px;
cursor: pointer;
overflow: hidden;
z-index: 1104;
display: none;
}
#fancybox-loading div {
position: absolute;
top: 0;
left: 0;
width: 40px;
height: 480px;
background-image: url('fancybox.png');
}
#fancybox-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
z-index: 1100;
display: none;
}
#fancybox-tmp {
padding: 0;
margin: 0;
border: 0;
overflow: auto;
display: none;
}
#fancybox-wrap {
position: absolute;
top: 0;
left: 0;
padding: 20px;
z-index: 1101;
outline: none;
display: none;
}
#fancybox-outer {
position: relative;
width: 100%;
height: 100%;
background: #fff;
}
#fancybox-content {
width: 0;
height: 0;
padding: 0;
outline: none;
position: relative;
overflow: hidden;
z-index: 1102;
border: 0px solid #fff;
}
#fancybox-hide-sel-frame {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: transparent;
z-index: 1101;
}
#fancybox-close {
position: absolute;
top: -15px;
right: -15px;
width: 30px;
height: 30px;
background: transparent url('fancybox.png') -40px 0px;
cursor: pointer;
z-index: 1103;
display: none;
}
#fancybox-error {
color: #444;
font: normal 12px/20px Arial;
padding: 14px;
margin: 0;
}
#fancybox-img {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
border: none;
outline: none;
line-height: 0;
vertical-align: top;
}
#fancybox-frame {
width: 100%;
height: 100%;
border: none;
display: block;
}
#fancybox-left, #fancybox-right {
position: absolute;
bottom: 0px;
height: 100%;
width: 35%;
cursor: pointer;
outline: none;
background: transparent url('blank.gif');
z-index: 1102;
display: none;
}
#fancybox-left {
left: 0px;
}
#fancybox-right {
right: 0px;
}
#fancybox-left-ico, #fancybox-right-ico {
position: absolute;
top: 50%;
left: -9999px;
width: 30px;
height: 30px;
margin-top: -15px;
cursor: pointer;
z-index: 1102;
display: block;
}
#fancybox-left-ico {
background-image: url('fancybox.png');
background-position: -40px -30px;
}
#fancybox-right-ico {
background-image: url('fancybox.png');
background-position: -40px -60px;
}
#fancybox-left:hover, #fancybox-right:hover {
visibility: visible; /* IE6 */
}
#fancybox-left:hover span {
left: 20px;
}
#fancybox-right:hover span {
left: auto;
right: 20px;
}
.fancybox-bg {
position: absolute;
padding: 0;
margin: 0;
border: 0;
width: 20px;
height: 20px;
z-index: 1001;
}
#fancybox-bg-n {
top: -20px;
left: 0;
width: 100%;
background-image: url('fancybox-x.png');
}
#fancybox-bg-ne {
top: -20px;
right: -20px;
background-image: url('fancybox.png');
background-position: -40px -162px;
}
#fancybox-bg-e {
top: 0;
right: -20px;
height: 100%;
background-image: url('fancybox-y.png');
background-position: -20px 0px;
}
#fancybox-bg-se {
bottom: -20px;
right: -20px;
background-image: url('fancybox.png');
background-position: -40px -182px;
}
#fancybox-bg-s {
bottom: -20px;
left: 0;
width: 100%;
background-image: url('fancybox-x.png');
background-position: 0px -20px;
}
#fancybox-bg-sw {
bottom: -20px;
left: -20px;
background-image: url('fancybox.png');
background-position: -40px -142px;
}
#fancybox-bg-w {
top: 0;
left: -20px;
height: 100%;
background-image: url('fancybox-y.png');
}
#fancybox-bg-nw {
top: -20px;
left: -20px;
background-image: url('fancybox.png');
background-position: -40px -122px;
}
#fancybox-title {
font-family: Helvetica;
font-size: 12px;
z-index: 1102;
}
.fancybox-title-inside {
padding-bottom: 10px;
text-align: center;
color: #333;
background: #fff;
position: relative;
}
.fancybox-title-outside {
padding-top: 10px;
color: #fff;
}
.fancybox-title-over {
position: absolute;
bottom: 0;
left: 0;
color: #FFF;
text-align: left;
}
#fancybox-title-over {
padding: 10px;
background-image: url('fancy_title_over.png');
display: block;
}
.fancybox-title-float {
position: absolute;
left: 0;
bottom: -20px;
height: 32px;
}
#fancybox-title-float-wrap {
border: none;
border-collapse: collapse;
width: auto;
}
#fancybox-title-float-wrap td {
border: none;
white-space: nowrap;
}
#fancybox-title-float-left {
padding: 0 0 0 15px;
background: url('fancybox.png') -40px -90px no-repeat;
}
#fancybox-title-float-main {
color: #FFF;
line-height: 29px;
font-weight: bold;
padding: 0 0 3px 0;
background: url('fancybox-x.png') 0px -40px;
}
#fancybox-title-float-right {
padding: 0 0 0 15px;
background: url('fancybox.png') -55px -90px no-repeat;
}
/* IE6 */
.fancybox-ie6 #fancybox-close { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_close.png', sizingMethod='scale'); }
.fancybox-ie6 #fancybox-left-ico { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_nav_left.png', sizingMethod='scale'); }
.fancybox-ie6 #fancybox-right-ico { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_nav_right.png', sizingMethod='scale'); }
.fancybox-ie6 #fancybox-title-over { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_over.png', sizingMethod='scale'); zoom: 1; }
.fancybox-ie6 #fancybox-title-float-left { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_left.png', sizingMethod='scale'); }
.fancybox-ie6 #fancybox-title-float-main { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_main.png', sizingMethod='scale'); }
.fancybox-ie6 #fancybox-title-float-right { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_right.png', sizingMethod='scale'); }
.fancybox-ie6 #fancybox-bg-w, .fancybox-ie6 #fancybox-bg-e, .fancybox-ie6 #fancybox-left, .fancybox-ie6 #fancybox-right, #fancybox-hide-sel-frame {
height: expression(this.parentNode.clientHeight + "px");
}
#fancybox-loading.fancybox-ie6 {
position: absolute; margin-top: 0;
top: expression( (-20 + (document.documentElement.clientHeight ? document.documentElement.clientHeight/2 : document.body.clientHeight/2 ) + ( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop )) + 'px');
}
#fancybox-loading.fancybox-ie6 div { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_loading.png', sizingMethod='scale'); }
/* IE6, IE7, IE8 */
.fancybox-ie .fancybox-bg { background: transparent !important; }
.fancybox-ie #fancybox-bg-n { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_n.png', sizingMethod='scale'); }
.fancybox-ie #fancybox-bg-ne { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_ne.png', sizingMethod='scale'); }
.fancybox-ie #fancybox-bg-e { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_e.png', sizingMethod='scale'); }
.fancybox-ie #fancybox-bg-se { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_se.png', sizingMethod='scale'); }
.fancybox-ie #fancybox-bg-s { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_s.png', sizingMethod='scale'); }
.fancybox-ie #fancybox-bg-sw { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_sw.png', sizingMethod='scale'); }
.fancybox-ie #fancybox-bg-w { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_w.png', sizingMethod='scale'); }
.fancybox-ie #fancybox-bg-nw { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_nw.png', sizingMethod='scale'); }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,46 @@
/*
* FancyBox - jQuery Plugin
* Simple and fancy lightbox alternative
*
* Examples and documentation at: http://fancybox.net
*
* Copyright (c) 2008 - 2010 Janis Skarnelis
* That said, it is hardly a one-person project. Many people have submitted bugs, code, and offered their advice freely. Their support is greatly appreciated.
*
* Version: 1.3.4 (11/11/2010)
* Requires: jQuery v1.3+
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*/
;(function(b){var m,t,u,f,D,j,E,n,z,A,q=0,e={},o=[],p=0,d={},l=[],G=null,v=new Image,J=/\.(jpg|gif|png|bmp|jpeg)(.*)?$/i,W=/[^\.]\.(swf)\s*$/i,K,L=1,y=0,s="",r,i,h=false,B=b.extend(b("<div/>")[0],{prop:0}),M=b.browser.msie&&b.browser.version<7&&!window.XMLHttpRequest,N=function(){t.hide();v.onerror=v.onload=null;G&&G.abort();m.empty()},O=function(){if(false===e.onError(o,q,e)){t.hide();h=false}else{e.titleShow=false;e.width="auto";e.height="auto";m.html('<p id="fancybox-error">The requested content cannot be loaded.<br />Please try again later.</p>');
F()}},I=function(){var a=o[q],c,g,k,C,P,w;N();e=b.extend({},b.fn.fancybox.defaults,typeof b(a).data("fancybox")=="undefined"?e:b(a).data("fancybox"));w=e.onStart(o,q,e);if(w===false)h=false;else{if(typeof w=="object")e=b.extend(e,w);k=e.title||(a.nodeName?b(a).attr("title"):a.title)||"";if(a.nodeName&&!e.orig)e.orig=b(a).children("img:first").length?b(a).children("img:first"):b(a);if(k===""&&e.orig&&e.titleFromAlt)k=e.orig.attr("alt");c=e.href||(a.nodeName?b(a).attr("href"):a.href)||null;if(/^(?:javascript)/i.test(c)||
c=="#")c=null;if(e.type){g=e.type;if(!c)c=e.content}else if(e.content)g="html";else if(c)g=c.match(J)?"image":c.match(W)?"swf":b(a).hasClass("iframe")?"iframe":c.indexOf("#")===0?"inline":"ajax";if(g){if(g=="inline"){a=c.substr(c.indexOf("#"));g=b(a).length>0?"inline":"ajax"}e.type=g;e.href=c;e.title=k;if(e.autoDimensions)if(e.type=="html"||e.type=="inline"||e.type=="ajax"){e.width="auto";e.height="auto"}else e.autoDimensions=false;if(e.modal){e.overlayShow=true;e.hideOnOverlayClick=false;e.hideOnContentClick=
false;e.enableEscapeButton=false;e.showCloseButton=false}e.padding=parseInt(e.padding,10);e.margin=parseInt(e.margin,10);m.css("padding",e.padding+e.margin);b(".fancybox-inline-tmp").unbind("fancybox-cancel").bind("fancybox-change",function(){b(this).replaceWith(j.children())});switch(g){case "html":m.html(e.content);F();break;case "inline":if(b(a).parent().is("#fancybox-content")===true){h=false;break}b('<div class="fancybox-inline-tmp" />').hide().insertBefore(b(a)).bind("fancybox-cleanup",function(){b(this).replaceWith(j.children())}).bind("fancybox-cancel",
function(){b(this).replaceWith(m.children())});b(a).appendTo(m);F();break;case "image":h=false;b.fancybox.showActivity();v=new Image;v.onerror=function(){O()};v.onload=function(){h=true;v.onerror=v.onload=null;e.width=v.width;e.height=v.height;b("<img />").attr({id:"fancybox-img",src:v.src,alt:e.title}).appendTo(m);Q()};v.src=c;break;case "swf":e.scrolling="no";C='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+e.width+'" height="'+e.height+'"><param name="movie" value="'+c+
'"></param>';P="";b.each(e.swf,function(x,H){C+='<param name="'+x+'" value="'+H+'"></param>';P+=" "+x+'="'+H+'"'});C+='<embed src="'+c+'" type="application/x-shockwave-flash" width="'+e.width+'" height="'+e.height+'"'+P+"></embed></object>";m.html(C);F();break;case "ajax":h=false;b.fancybox.showActivity();e.ajax.win=e.ajax.success;G=b.ajax(b.extend({},e.ajax,{url:c,data:e.ajax.data||{},error:function(x){x.status>0&&O()},success:function(x,H,R){if((typeof R=="object"?R:G).status==200){if(typeof e.ajax.win==
"function"){w=e.ajax.win(c,x,H,R);if(w===false){t.hide();return}else if(typeof w=="string"||typeof w=="object")x=w}m.html(x);F()}}}));break;case "iframe":Q()}}else O()}},F=function(){var a=e.width,c=e.height;a=a.toString().indexOf("%")>-1?parseInt((b(window).width()-e.margin*2)*parseFloat(a)/100,10)+"px":a=="auto"?"auto":a+"px";c=c.toString().indexOf("%")>-1?parseInt((b(window).height()-e.margin*2)*parseFloat(c)/100,10)+"px":c=="auto"?"auto":c+"px";m.wrapInner('<div style="width:'+a+";height:"+c+
";overflow: "+(e.scrolling=="auto"?"auto":e.scrolling=="yes"?"scroll":"hidden")+';position:relative;"></div>');e.width=m.width();e.height=m.height();Q()},Q=function(){var a,c;t.hide();if(f.is(":visible")&&false===d.onCleanup(l,p,d)){b.event.trigger("fancybox-cancel");h=false}else{h=true;b(j.add(u)).unbind();b(window).unbind("resize.fb scroll.fb");b(document).unbind("keydown.fb");f.is(":visible")&&d.titlePosition!=="outside"&&f.css("height",f.height());l=o;p=q;d=e;if(d.overlayShow){u.css({"background-color":d.overlayColor,
opacity:d.overlayOpacity,cursor:d.hideOnOverlayClick?"pointer":"auto",height:b(document).height()});if(!u.is(":visible")){M&&b("select:not(#fancybox-tmp select)").filter(function(){return this.style.visibility!=="hidden"}).css({visibility:"hidden"}).one("fancybox-cleanup",function(){this.style.visibility="inherit"});u.show()}}else u.hide();i=X();s=d.title||"";y=0;n.empty().removeAttr("style").removeClass();if(d.titleShow!==false){if(b.isFunction(d.titleFormat))a=d.titleFormat(s,l,p,d);else a=s&&s.length?
d.titlePosition=="float"?'<table id="fancybox-title-float-wrap" cellpadding="0" cellspacing="0"><tr><td id="fancybox-title-float-left"></td><td id="fancybox-title-float-main">'+s+'</td><td id="fancybox-title-float-right"></td></tr></table>':'<div id="fancybox-title-'+d.titlePosition+'">'+s+"</div>":false;s=a;if(!(!s||s==="")){n.addClass("fancybox-title-"+d.titlePosition).html(s).appendTo("body").show();switch(d.titlePosition){case "inside":n.css({width:i.width-d.padding*2,marginLeft:d.padding,marginRight:d.padding});
y=n.outerHeight(true);n.appendTo(D);i.height+=y;break;case "over":n.css({marginLeft:d.padding,width:i.width-d.padding*2,bottom:d.padding}).appendTo(D);break;case "float":n.css("left",parseInt((n.width()-i.width-40)/2,10)*-1).appendTo(f);break;default:n.css({width:i.width-d.padding*2,paddingLeft:d.padding,paddingRight:d.padding}).appendTo(f)}}}n.hide();if(f.is(":visible")){b(E.add(z).add(A)).hide();a=f.position();r={top:a.top,left:a.left,width:f.width(),height:f.height()};c=r.width==i.width&&r.height==
i.height;j.fadeTo(d.changeFade,0.3,function(){var g=function(){j.html(m.contents()).fadeTo(d.changeFade,1,S)};b.event.trigger("fancybox-change");j.empty().removeAttr("filter").css({"border-width":d.padding,width:i.width-d.padding*2,height:e.autoDimensions?"auto":i.height-y-d.padding*2});if(c)g();else{B.prop=0;b(B).animate({prop:1},{duration:d.changeSpeed,easing:d.easingChange,step:T,complete:g})}})}else{f.removeAttr("style");j.css("border-width",d.padding);if(d.transitionIn=="elastic"){r=V();j.html(m.contents());
f.show();if(d.opacity)i.opacity=0;B.prop=0;b(B).animate({prop:1},{duration:d.speedIn,easing:d.easingIn,step:T,complete:S})}else{d.titlePosition=="inside"&&y>0&&n.show();j.css({width:i.width-d.padding*2,height:e.autoDimensions?"auto":i.height-y-d.padding*2}).html(m.contents());f.css(i).fadeIn(d.transitionIn=="none"?0:d.speedIn,S)}}}},Y=function(){if(d.enableEscapeButton||d.enableKeyboardNav)b(document).bind("keydown.fb",function(a){if(a.keyCode==27&&d.enableEscapeButton){a.preventDefault();b.fancybox.close()}else if((a.keyCode==
37||a.keyCode==39)&&d.enableKeyboardNav&&a.target.tagName!=="INPUT"&&a.target.tagName!=="TEXTAREA"&&a.target.tagName!=="SELECT"){a.preventDefault();b.fancybox[a.keyCode==37?"prev":"next"]()}});if(d.showNavArrows){if(d.cyclic&&l.length>1||p!==0)z.show();if(d.cyclic&&l.length>1||p!=l.length-1)A.show()}else{z.hide();A.hide()}},S=function(){if(!b.support.opacity){j.get(0).style.removeAttribute("filter");f.get(0).style.removeAttribute("filter")}e.autoDimensions&&j.css("height","auto");f.css("height","auto");
s&&s.length&&n.show();d.showCloseButton&&E.show();Y();d.hideOnContentClick&&j.bind("click",b.fancybox.close);d.hideOnOverlayClick&&u.bind("click",b.fancybox.close);b(window).bind("resize.fb",b.fancybox.resize);d.centerOnScroll&&b(window).bind("scroll.fb",b.fancybox.center);if(d.type=="iframe")b('<iframe id="fancybox-frame" name="fancybox-frame'+(new Date).getTime()+'" frameborder="0" hspace="0" '+(b.browser.msie?'allowtransparency="true""':"")+' scrolling="'+e.scrolling+'" src="'+d.href+'"></iframe>').appendTo(j);
f.show();h=false;b.fancybox.center();d.onComplete(l,p,d);var a,c;if(l.length-1>p){a=l[p+1].href;if(typeof a!=="undefined"&&a.match(J)){c=new Image;c.src=a}}if(p>0){a=l[p-1].href;if(typeof a!=="undefined"&&a.match(J)){c=new Image;c.src=a}}},T=function(a){var c={width:parseInt(r.width+(i.width-r.width)*a,10),height:parseInt(r.height+(i.height-r.height)*a,10),top:parseInt(r.top+(i.top-r.top)*a,10),left:parseInt(r.left+(i.left-r.left)*a,10)};if(typeof i.opacity!=="undefined")c.opacity=a<0.5?0.5:a;f.css(c);
j.css({width:c.width-d.padding*2,height:c.height-y*a-d.padding*2})},U=function(){return[b(window).width()-d.margin*2,b(window).height()-d.margin*2,b(document).scrollLeft()+d.margin,b(document).scrollTop()+d.margin]},X=function(){var a=U(),c={},g=d.autoScale,k=d.padding*2;c.width=d.width.toString().indexOf("%")>-1?parseInt(a[0]*parseFloat(d.width)/100,10):d.width+k;c.height=d.height.toString().indexOf("%")>-1?parseInt(a[1]*parseFloat(d.height)/100,10):d.height+k;if(g&&(c.width>a[0]||c.height>a[1]))if(e.type==
"image"||e.type=="swf"){g=d.width/d.height;if(c.width>a[0]){c.width=a[0];c.height=parseInt((c.width-k)/g+k,10)}if(c.height>a[1]){c.height=a[1];c.width=parseInt((c.height-k)*g+k,10)}}else{c.width=Math.min(c.width,a[0]);c.height=Math.min(c.height,a[1])}c.top=parseInt(Math.max(a[3]-20,a[3]+(a[1]-c.height-40)*0.5),10);c.left=parseInt(Math.max(a[2]-20,a[2]+(a[0]-c.width-40)*0.5),10);return c},V=function(){var a=e.orig?b(e.orig):false,c={};if(a&&a.length){c=a.offset();c.top+=parseInt(a.css("paddingTop"),
10)||0;c.left+=parseInt(a.css("paddingLeft"),10)||0;c.top+=parseInt(a.css("border-top-width"),10)||0;c.left+=parseInt(a.css("border-left-width"),10)||0;c.width=a.width();c.height=a.height();c={width:c.width+d.padding*2,height:c.height+d.padding*2,top:c.top-d.padding-20,left:c.left-d.padding-20}}else{a=U();c={width:d.padding*2,height:d.padding*2,top:parseInt(a[3]+a[1]*0.5,10),left:parseInt(a[2]+a[0]*0.5,10)}}return c},Z=function(){if(t.is(":visible")){b("div",t).css("top",L*-40+"px");L=(L+1)%12}else clearInterval(K)};
b.fn.fancybox=function(a){if(!b(this).length)return this;b(this).data("fancybox",b.extend({},a,b.metadata?b(this).metadata():{})).unbind("click.fb").bind("click.fb",function(c){c.preventDefault();if(!h){h=true;b(this).blur();o=[];q=0;c=b(this).attr("rel")||"";if(!c||c==""||c==="nofollow")o.push(this);else{o=b("a[rel="+c+"], area[rel="+c+"]");q=o.index(this)}I()}});return this};b.fancybox=function(a,c){var g;if(!h){h=true;g=typeof c!=="undefined"?c:{};o=[];q=parseInt(g.index,10)||0;if(b.isArray(a)){for(var k=
0,C=a.length;k<C;k++)if(typeof a[k]=="object")b(a[k]).data("fancybox",b.extend({},g,a[k]));else a[k]=b({}).data("fancybox",b.extend({content:a[k]},g));o=jQuery.merge(o,a)}else{if(typeof a=="object")b(a).data("fancybox",b.extend({},g,a));else a=b({}).data("fancybox",b.extend({content:a},g));o.push(a)}if(q>o.length||q<0)q=0;I()}};b.fancybox.showActivity=function(){clearInterval(K);t.show();K=setInterval(Z,66)};b.fancybox.hideActivity=function(){t.hide()};b.fancybox.next=function(){return b.fancybox.pos(p+
1)};b.fancybox.prev=function(){return b.fancybox.pos(p-1)};b.fancybox.pos=function(a){if(!h){a=parseInt(a);o=l;if(a>-1&&a<l.length){q=a;I()}else if(d.cyclic&&l.length>1){q=a>=l.length?0:l.length-1;I()}}};b.fancybox.cancel=function(){if(!h){h=true;b.event.trigger("fancybox-cancel");N();e.onCancel(o,q,e);h=false}};b.fancybox.close=function(){function a(){u.fadeOut("fast");n.empty().hide();f.hide();b.event.trigger("fancybox-cleanup");j.empty();d.onClosed(l,p,d);l=e=[];p=q=0;d=e={};h=false}if(!(h||f.is(":hidden"))){h=
true;if(d&&false===d.onCleanup(l,p,d))h=false;else{N();b(E.add(z).add(A)).hide();b(j.add(u)).unbind();b(window).unbind("resize.fb scroll.fb");b(document).unbind("keydown.fb");j.find("iframe").attr("src",M&&/^https/i.test(window.location.href||"")?"javascript:void(false)":"about:blank");d.titlePosition!=="inside"&&n.empty();f.stop();if(d.transitionOut=="elastic"){r=V();var c=f.position();i={top:c.top,left:c.left,width:f.width(),height:f.height()};if(d.opacity)i.opacity=1;n.empty().hide();B.prop=1;
b(B).animate({prop:0},{duration:d.speedOut,easing:d.easingOut,step:T,complete:a})}else f.fadeOut(d.transitionOut=="none"?0:d.speedOut,a)}}};b.fancybox.resize=function(){u.is(":visible")&&u.css("height",b(document).height());b.fancybox.center(true)};b.fancybox.center=function(a){var c,g;if(!h){g=a===true?1:0;c=U();!g&&(f.width()>c[0]||f.height()>c[1])||f.stop().animate({top:parseInt(Math.max(c[3]-20,c[3]+(c[1]-j.height()-40)*0.5-d.padding)),left:parseInt(Math.max(c[2]-20,c[2]+(c[0]-j.width()-40)*0.5-
d.padding))},typeof a=="number"?a:200)}};b.fancybox.init=function(){if(!b("#fancybox-wrap").length){b("body").append(m=b('<div id="fancybox-tmp"></div>'),t=b('<div id="fancybox-loading"><div></div></div>'),u=b('<div id="fancybox-overlay"></div>'),f=b('<div id="fancybox-wrap"></div>'));D=b('<div id="fancybox-outer"></div>').append('<div class="fancybox-bg" id="fancybox-bg-n"></div><div class="fancybox-bg" id="fancybox-bg-ne"></div><div class="fancybox-bg" id="fancybox-bg-e"></div><div class="fancybox-bg" id="fancybox-bg-se"></div><div class="fancybox-bg" id="fancybox-bg-s"></div><div class="fancybox-bg" id="fancybox-bg-sw"></div><div class="fancybox-bg" id="fancybox-bg-w"></div><div class="fancybox-bg" id="fancybox-bg-nw"></div>').appendTo(f);
D.append(j=b('<div id="fancybox-content"></div>'),E=b('<a id="fancybox-close"></a>'),n=b('<div id="fancybox-title"></div>'),z=b('<a href="javascript:;" id="fancybox-left"><span class="fancy-ico" id="fancybox-left-ico"></span></a>'),A=b('<a href="javascript:;" id="fancybox-right"><span class="fancy-ico" id="fancybox-right-ico"></span></a>'));E.click(b.fancybox.close);t.click(b.fancybox.cancel);z.click(function(a){a.preventDefault();b.fancybox.prev()});A.click(function(a){a.preventDefault();b.fancybox.next()});
b.fn.mousewheel&&f.bind("mousewheel.fb",function(a,c){if(h)a.preventDefault();else if(b(a.target).get(0).clientHeight==0||b(a.target).get(0).scrollHeight===b(a.target).get(0).clientHeight){a.preventDefault();b.fancybox[c>0?"prev":"next"]()}});b.support.opacity||f.addClass("fancybox-ie");if(M){t.addClass("fancybox-ie6");f.addClass("fancybox-ie6");b('<iframe id="fancybox-hide-sel-frame" src="'+(/^https/i.test(window.location.href||"")?"javascript:void(false)":"about:blank")+'" scrolling="no" border="0" frameborder="0" tabindex="-1"></iframe>').prependTo(D)}}};
b.fn.fancybox.defaults={padding:10,margin:40,opacity:false,modal:false,cyclic:false,scrolling:"auto",width:560,height:340,autoScale:true,autoDimensions:true,centerOnScroll:false,ajax:{},swf:{wmode:"transparent"},hideOnOverlayClick:true,hideOnContentClick:false,overlayShow:true,overlayOpacity:0.7,overlayColor:"#777",titleShow:true,titlePosition:"float",titleFormat:null,titleFromAlt:false,transitionIn:"fade",transitionOut:"fade",speedIn:300,speedOut:300,changeSpeed:300,changeFade:"fast",easingIn:"swing",
easingOut:"swing",showCloseButton:true,showNavArrows:true,enableEscapeButton:true,enableKeyboardNav:true,onStart:function(){},onCancel:function(){},onComplete:function(){},onCleanup:function(){},onClosed:function(){},onError:function(){}};b(document).ready(function(){b.fancybox.init()})})(jQuery);

View File

@ -0,0 +1,14 @@
/*! Copyright (c) 2010 Brandon Aaron (http://brandonaaron.net)
* Licensed under the MIT License (LICENSE.txt).
*
* Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
* Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
* Thanks to: Seamus Leahy for adding deltaX and deltaY
*
* Version: 3.0.4
*
* Requires: 1.2.2+
*/
(function(d){function g(a){var b=a||window.event,i=[].slice.call(arguments,1),c=0,h=0,e=0;a=d.event.fix(b);a.type="mousewheel";if(a.wheelDelta)c=a.wheelDelta/120;if(a.detail)c=-a.detail/3;e=c;if(b.axis!==undefined&&b.axis===b.HORIZONTAL_AXIS){e=0;h=-1*c}if(b.wheelDeltaY!==undefined)e=b.wheelDeltaY/120;if(b.wheelDeltaX!==undefined)h=-1*b.wheelDeltaX/120;i.unshift(a,c,h,e);return d.event.handle.apply(this,i)}var f=["DOMMouseScroll","mousewheel"];d.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var a=
f.length;a;)this.addEventListener(f[--a],g,false);else this.onmousewheel=g},teardown:function(){if(this.removeEventListener)for(var a=f.length;a;)this.removeEventListener(f[--a],g,false);else this.onmousewheel=null}};d.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})})(jQuery);

View File

@ -1,10 +1,8 @@
require 'test_helper'
class Admin::AdminUsersControllerTest < ActionController::TestCase
# Replace this with your real tests.
test "the truth" do
assert true
end
end

View File

@ -1,10 +1,8 @@
require 'test_helper'
class Admin::CategoriesControllerTest < ActionController::TestCase
# Replace this with your real tests.
test "the truth" do
assert true
end
end

View File

@ -1,10 +1,8 @@
require 'test_helper'
class Admin::PagesControllerTest < ActionController::TestCase
# Replace this with your real tests.
test "the truth" do
assert true
end
end

View File

@ -1,10 +1,8 @@
require 'test_helper'
class Admin::PhotosControllerTest < ActionController::TestCase
# Replace this with your real tests.
test "the truth" do
assert true
end
end

View File

@ -1,26 +0,0 @@
LICENSE
The MIT License
Copyright (c) 2008 Jon Yurek and thoughtbot, inc.
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.

View File

@ -1,182 +0,0 @@
=Paperclip
Paperclip is intended as an easy file attachment library for ActiveRecord. The
intent behind it was to keep setup as easy as possible and to treat files as
much like other attributes as possible. This means they aren't saved to their
final locations on disk, nor are they deleted if set to nil, until
ActiveRecord::Base#save is called. It manages validations based on size and
presence, if required. It can transform its assigned image into thumbnails if
needed, and the prerequisites are as simple as installing ImageMagick (which,
for most modern Unix-based systems, is as easy as installing the right
packages). Attached files are saved to the filesystem and referenced in the
browser by an easily understandable specification, which has sensible and
useful defaults.
See the documentation for +has_attached_file+ in Paperclip::ClassMethods for
more detailed options.
The complete RDoc[http://rdoc.info/projects/thoughtbot/paperclip] is online.
==Quick Start
In your model:
class User < ActiveRecord::Base
has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100>" }
end
In your migrations:
class AddAvatarColumnsToUser < ActiveRecord::Migration
def self.up
add_column :users, :avatar_file_name, :string
add_column :users, :avatar_content_type, :string
add_column :users, :avatar_file_size, :integer
add_column :users, :avatar_updated_at, :datetime
end
def self.down
remove_column :users, :avatar_file_name
remove_column :users, :avatar_content_type
remove_column :users, :avatar_file_size
remove_column :users, :avatar_updated_at
end
end
In your edit and new views:
<% form_for :user, @user, :url => user_path, :html => { :multipart => true } do |form| %>
<%= form.file_field :avatar %>
<% end %>
In your controller:
def create
@user = User.create( params[:user] )
end
In your show view:
<%= image_tag @user.avatar.url %>
<%= image_tag @user.avatar.url(:medium) %>
<%= image_tag @user.avatar.url(:thumb) %>
==Usage
The basics of paperclip are quite simple: Declare that your model has an
attachment with the has_attached_file method, and give it a name. Paperclip
will wrap up up to four attributes (all prefixed with that attachment's name,
so you can have multiple attachments per model if you wish) and give the a
friendly front end. The attributes are <attachment>_file_name,
<attachment>_file_size, <attachment>_content_type, and <attachment>_updated_at.
Only <attachment>_file_name is required for paperclip to operate. More
information about the options to has_attached_file is available in the
documentation of Paperclip::ClassMethods.
Attachments can be validated with Paperclip's validation methods,
validates_attachment_presence, validates_attachment_content_type, and
validates_attachment_size.
==Storage
The files that are assigned as attachments are, by default, placed in the
directory specified by the :path option to has_attached_file. By default, this
location is ":rails_root/public/system/:attachment/:id/:style/:filename". This
location was chosen because on standard Capistrano deployments, the
public/system directory is symlinked to the app's shared directory, meaning it
will survive between deployments. For example, using that :path, you may have a
file at
/data/myapp/releases/20081229172410/public/system/avatars/13/small/my_pic.png
NOTE: This is a change from previous versions of Paperclip, but is overall a
safer choice for the default file store.
You may also choose to store your files using Amazon's S3 service. You can find
more information about S3 storage at the description for
Paperclip::Storage::S3.
Files on the local filesystem (and in the Rails app's public directory) will be
available to the internet at large. If you require access control, it's
possible to place your files in a different location. You will need to change
both the :path and :url options in order to make sure the files are unavailable
to the public. Both :path and :url allow the same set of interpolated
variables.
==Post Processing
Paperclip supports an extensible selection of post-processors. When you define
a set of styles for an attachment, by default it is expected that those
"styles" are actually "thumbnails". However, you can do much more than just
thumbnail images. By defining a subclass of Paperclip::Processor, you can
perform any processing you want on the files that are attached. Any file in
your Rails app's lib/paperclip_processors directory is automatically loaded by
paperclip, allowing you to easily define custom processors. You can specify a
processor with the :processors option to has_attached_file:
has_attached_file :scan, :styles => { :text => { :quality => :better } },
:processors => [:ocr]
This would load the hypothetical class Paperclip::Ocr, which would have the
hash "{ :quality => :better }" passed to it along with the uploaded file. For
more information about defining processors, see Paperclip::Processor.
The default processor is Paperclip::Thumbnail. For backwards compatability
reasons, you can pass a single geometry string or an array containing a
geometry and a format, which the file will be converted to, like so:
has_attached_file :avatar, :styles => { :thumb => ["32x32#", :png] }
This will convert the "thumb" style to a 32x32 square in png format, regardless
of what was uploaded. If the format is not specified, it is kept the same (i.e.
jpgs will remain jpgs).
Multiple processors can be specified, and they will be invoked in the order
they are defined in the :processors array. Each successive processor will
be given the result of the previous processor's execution. All processors will
receive the same parameters, which are what you define in the :styles hash.
For example, assuming we had this definition:
has_attached_file :scan, :styles => { :text => { :quality => :better } },
:processors => [:rotator, :ocr]
then both the :rotator processor and the :ocr processor would receive the
options "{ :quality => :better }". This parameter may not mean anything to one
or more or the processors, and they are expected to ignore it.
NOTE: Because processors operate by turning the original attachment into the
styles, no processors will be run if there are no styles defined.
==Events
Before and after the Post Processing step, Paperclip calls back to the model
with a few callbacks, allowing the model to change or cancel the processing
step. The callbacks are "before_post_process" and "after_post_process" (which
are called before and after the processing of each attachment), and the
attachment-specific "before_<attachment>_post_process" and
"after_<attachment>_post_process". The callbacks are intended to be as close to
normal ActiveRecord callbacks as possible, so if you return false (specifically
- returning nil is not the same) in a before_ filter, the post processing step
will halt. Returning false in an after_ filter will not halt anything, but you
can access the model and the attachment if necessary.
NOTE: Post processing will not even *start* if the attachment is not valid
according to the validations. Your callbacks and processors will *only* be
called with valid attachments.
==Testing
Paperclip provides rspec-compatible matchers for testing attachments. See the
documentation on Paperclip::Shoulda::Matchers for more information.
==Contributing
If you'd like to contribute a feature or bugfix: Thanks! To make sure your
fix/feature has a high chance of being included, please read the following
guidelines:
1. Ask on the mailing list[http://groups.google.com/group/paperclip-plugin], or
post a new GitHub Issue[http://github.com/thoughtbot/paperclip/issues].
2. Make sure there are tests! We will not accept any patch that is not tested.
It's a rare time when explicit tests aren't needed. If you have questions
about writing tests for paperclip, please ask the mailing list.

View File

@ -1,76 +0,0 @@
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
$LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
require 'paperclip'
desc 'Default: run unit tests.'
task :default => [:clean, :test]
desc 'Test the paperclip plugin under all supported Rails versions.'
task :all do |t|
exec('rake RAILS_VERSION=2.1 && rake RAILS_VERSION=2.3 && rake RAILS_VERSION=3.0')
end
desc 'Test the paperclip plugin.'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib' << 'profile'
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end
desc 'Start an IRB session with all necessary files required.'
task :shell do |t|
chdir File.dirname(__FILE__)
exec 'irb -I lib/ -I lib/paperclip -r rubygems -r active_record -r tempfile -r init'
end
desc 'Generate documentation for the paperclip plugin.'
Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = 'Paperclip'
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.rdoc_files.include('README*')
rdoc.rdoc_files.include('lib/**/*.rb')
end
desc 'Update documentation on website'
task :sync_docs => 'rdoc' do
`rsync -ave ssh doc/ dev@dev.thoughtbot.com:/home/dev/www/dev.thoughtbot.com/paperclip`
end
desc 'Clean up files.'
task :clean do |t|
FileUtils.rm_rf "doc"
FileUtils.rm_rf "tmp"
FileUtils.rm_rf "pkg"
FileUtils.rm_rf "public"
FileUtils.rm "test/debug.log" rescue nil
FileUtils.rm "test/paperclip.db" rescue nil
Dir.glob("paperclip-*.gem").each{|f| FileUtils.rm f }
end
desc 'Build the gemspec.'
task :gemspec do |t|
exec 'gem build paperclip.gemspec'
end
desc "Print a list of the files to be put into the gem"
task :manifest => :clean do
spec.files.each do |file|
puts file
end
end
desc "Generate a gemspec file for GitHub"
task :gemspec => :clean do
File.open("#{spec.name}.gemspec", 'w') do |f|
f.write spec.to_ruby
end
end
desc "Build the gem into the current directory"
task :gem => :gemspec do
`gem build #{spec.name}.gemspec`
end

View File

@ -1,6 +0,0 @@
When /^I attach an? "([^\"]*)" "([^\"]*)" file to an? "([^\"]*)" on S3$/ do |attachment, extension, model|
stub_paperclip_s3(model, attachment, extension)
attach_file attachment,
"features/support/paperclip/#{model.gsub(" ", "_").underscore}/#{attachment}.#{extension}"
end

View File

@ -1,17 +0,0 @@
Feature: Running paperclip in a Rails app
Scenario: Basic utilization
Given I have a rails application
And I save the following as "app/models/user.rb"
"""
class User < ActiveRecord::Base
has_attached_file :avatar
end
"""
When I visit /users/new
And I fill in "user_name" with "something"
And I attach the file "test/fixtures/5k.png" to "user_avatar"
And I press "Submit"
Then I should see "Name: something"
And I should see an image with a path of "/system/avatars/1/original/5k.png"
And the file at "/system/avatars/1/original/5k.png" is the same as "test/fixtures/5k.png"

View File

@ -1,27 +0,0 @@
Feature: Running paperclip in a Rails app using basic S3 support
Scenario: Basic utilization
Given I have a rails application
And I save the following as "app/models/user.rb"
"""
class User < ActiveRecord::Base
has_attached_file :avatar,
:storage => :s3,
:path => "/:attachment/:id/:style/:filename",
:s3_credentials => Rails.root.join("config/s3.yml")
end
"""
And I validate my S3 credentials
And I save the following as "config/s3.yml"
"""
bucket: <%= ENV['PAPERCLIP_TEST_BUCKET'] || 'paperclip' %>
access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
"""
When I visit /users/new
And I fill in "user_name" with "something"
And I attach the file "test/fixtures/5k.png" to "user_avatar"
And I press "Submit"
Then I should see "Name: something"
And I should see an image with a path of "http://s3.amazonaws.com/paperclip/avatars/1/original/5k.png"
And the file at "http://s3.amazonaws.com/paperclip/avatars/1/original/5k.png" is the same as "test/fixtures/5k.png"

View File

@ -1,14 +0,0 @@
Then %r{I should see an image with a path of "([^"]*)"} do |path|
page.should have_css("img[src^='#{path}']")
end
Then %r{^the file at "([^"]*)" is the same as "([^"]*)"$} do |web_file, path|
expected = IO.read(path)
actual = if web_file.match %r{^https?://}
Net::HTTP.get(URI.parse(web_file))
else
visit(web_file)
page.body
end
actual.should == expected
end

View File

@ -1,90 +0,0 @@
Given "I have a rails application" do
steps %{
Given I generate a rails application
And this plugin is available
And I have a "users" resource with "name:string"
And I turn off class caching
Given I save the following as "app/models/user.rb"
"""
class User < ActiveRecord::Base
end
"""
And I save the following as "config/s3.yml"
"""
access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
bucket: paperclip
"""
And I save the following as "app/views/users/new.html.erb"
"""
<% form_for @user, :html => { :multipart => true } do |f| %>
<%= f.text_field :name %>
<%= f.file_field :avatar %>
<%= submit_tag "Submit" %>
<% end %>
"""
And I save the following as "app/views/users/show.html.erb"
"""
<p>Name: <%= @user.name %></p>
<p>Avatar: <%= image_tag @user.avatar.url %></p>
"""
And I run "script/generate paperclip user avatar"
And the rails application is prepped and running
}
end
Given %r{I generate a rails application} do
FileUtils.rm_rf TEMP_ROOT
FileUtils.mkdir_p TEMP_ROOT
Dir.chdir(TEMP_ROOT) do
`rails _2.3.8_ #{APP_NAME}`
end
end
When %r{I save the following as "([^"]*)"} do |path, string|
FileUtils.mkdir_p(File.join(CUC_RAILS_ROOT, File.dirname(path)))
File.open(File.join(CUC_RAILS_ROOT, path), 'w') { |file| file.write(string) }
end
When %r{I turn off class caching} do
Dir.chdir(CUC_RAILS_ROOT) do
file = "config/environments/test.rb"
config = IO.read(file)
config.gsub!(%r{^\s*config.cache_classes.*$},
"config.cache_classes = false")
File.open(file, "w"){|f| f.write(config) }
end
end
When %r{the rails application is prepped and running$} do
When "I reset the database"
When "the rails application is running"
end
When %r{I reset the database} do
When %{I run "rake db:drop db:create db:migrate"}
end
When %r{the rails application is running} do
Dir.chdir(CUC_RAILS_ROOT) do
require "config/environment"
require "capybara/rails"
end
end
When %r{this plugin is available} do
$LOAD_PATH << "#{PROJECT_ROOT}/lib"
require 'paperclip'
When %{I save the following as "vendor/plugins/paperclip/rails/init.rb"},
IO.read("#{PROJECT_ROOT}/rails/init.rb")
end
When %r{I run "([^"]*)"} do |command|
Dir.chdir(CUC_RAILS_ROOT) do
`#{command}`
end
end
When %r{I have a "([^"]*)" resource with "([^"]*)"} do |resource, fields|
When %{I run "script/generate scaffold #{resource} #{fields}"}
end

View File

@ -1,9 +0,0 @@
Given /I validate my S3 credentials/ do
key = ENV['AWS_ACCESS_KEY_ID']
secret = ENV['AWS_SECRET_ACCESS_KEY']
key.should_not be_nil
secret.should_not be_nil
assert_credentials(key, secret)
end

View File

@ -1,227 +0,0 @@
# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
# It is recommended to regenerate this file in the future when you upgrade to a
# newer version of cucumber-rails. Consider adding your own code to a new file
# instead of editing this one. Cucumber will automatically load all features/**/*.rb
# files.
require 'uri'
require 'cgi'
require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths"))
module WithinHelpers
def with_scope(locator)
locator ? within(locator) { yield } : yield
end
end
World(WithinHelpers)
Given /^(?:|I )am on (.+)$/ do |page_name|
visit path_to(page_name)
end
When /^(?:|I )go to (.+)$/ do |page_name|
visit path_to(page_name)
end
When /^(?:|I )visit (\/.+)$/ do |page_path|
visit page_path
end
When /^(?:|I )press "([^"]*)"(?: within "([^"]*)")?$/ do |button, selector|
with_scope(selector) do
click_button(button)
end
end
When /^(?:|I )follow "([^"]*)"(?: within "([^"]*)")?$/ do |link, selector|
with_scope(selector) do
click_link(link)
end
end
When /^(?:|I )fill in "([^"]*)" with "([^"]*)"(?: within "([^"]*)")?$/ do |field, value, selector|
with_scope(selector) do
fill_in(field, :with => value)
end
end
When /^(?:|I )fill in "([^"]*)" for "([^"]*)"(?: within "([^"]*)")?$/ do |value, field, selector|
with_scope(selector) do
fill_in(field, :with => value)
end
end
# Use this to fill in an entire form with data from a table. Example:
#
# When I fill in the following:
# | Account Number | 5002 |
# | Expiry date | 2009-11-01 |
# | Note | Nice guy |
# | Wants Email? | |
#
# TODO: Add support for checkbox, select og option
# based on naming conventions.
#
When /^(?:|I )fill in the following(?: within "([^"]*)")?:$/ do |selector, fields|
with_scope(selector) do
fields.rows_hash.each do |name, value|
When %{I fill in "#{name}" with "#{value}"}
end
end
end
When /^(?:|I )select "([^"]*)" from "([^"]*)"(?: within "([^"]*)")?$/ do |value, field, selector|
with_scope(selector) do
select(value, :from => field)
end
end
When /^(?:|I )check "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector|
with_scope(selector) do
check(field)
end
end
When /^(?:|I )uncheck "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector|
with_scope(selector) do
uncheck(field)
end
end
When /^(?:|I )choose "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector|
with_scope(selector) do
choose(field)
end
end
When /^(?:|I )attach the file "([^"]*)" to "([^"]*)"(?: within "([^"]*)")?$/ do |path, field, selector|
with_scope(selector) do
attach_file(field, path)
end
end
Then /^(?:|I )should see JSON:$/ do |expected_json|
require 'json'
expected = JSON.pretty_generate(JSON.parse(expected_json))
actual = JSON.pretty_generate(JSON.parse(response.body))
expected.should == actual
end
Then /^(?:|I )should see "([^"]*)"(?: within "([^"]*)")?$/ do |text, selector|
with_scope(selector) do
if page.respond_to? :should
page.should have_content(text)
else
assert page.has_content?(text)
end
end
end
Then /^(?:|I )should see \/([^\/]*)\/(?: within "([^"]*)")?$/ do |regexp, selector|
regexp = Regexp.new(regexp)
with_scope(selector) do
if page.respond_to? :should
page.should have_xpath('//*', :text => regexp)
else
assert page.has_xpath?('//*', :text => regexp)
end
end
end
Then /^(?:|I )should not see "([^"]*)"(?: within "([^"]*)")?$/ do |text, selector|
with_scope(selector) do
if page.respond_to? :should
page.should have_no_content(text)
else
assert page.has_no_content?(text)
end
end
end
Then /^(?:|I )should not see \/([^\/]*)\/(?: within "([^"]*)")?$/ do |regexp, selector|
regexp = Regexp.new(regexp)
with_scope(selector) do
if page.respond_to? :should
page.should have_no_xpath('//*', :text => regexp)
else
assert page.has_no_xpath?('//*', :text => regexp)
end
end
end
Then /^the "([^"]*)" field(?: within "([^"]*)")? should contain "([^"]*)"$/ do |field, selector, value|
with_scope(selector) do
field = find_field(field)
field_value = (field.tag_name == 'textarea') ? field.text : field.value
if field_value.respond_to? :should
field_value.should =~ /#{value}/
else
assert_match(/#{value}/, field_value)
end
end
end
Then /^the "([^"]*)" field(?: within "([^"]*)")? should not contain "([^"]*)"$/ do |field, selector, value|
with_scope(selector) do
field = find_field(field)
field_value = (field.tag_name == 'textarea') ? field.text : field.value
if field_value.respond_to? :should_not
field_value.should_not =~ /#{value}/
else
assert_no_match(/#{value}/, field_value)
end
end
end
Then /^the "([^"]*)" checkbox(?: within "([^"]*)")? should be checked$/ do |label, selector|
with_scope(selector) do
field_checked = find_field(label)['checked']
if field_checked.respond_to? :should
field_checked.should be_true
else
assert field_checked
end
end
end
Then /^the "([^"]*)" checkbox(?: within "([^"]*)")? should not be checked$/ do |label, selector|
with_scope(selector) do
field_checked = find_field(label)['checked']
if field_checked.respond_to? :should
field_checked.should be_false
else
assert !field_checked
end
end
end
Then /^(?:|I )should be on (.+)$/ do |page_name|
current_path = URI.parse(current_url).path
if current_path.respond_to? :should
current_path.should == path_to(page_name)
else
assert_equal path_to(page_name), current_path
end
end
Then /^(?:|I )should have the following query string:$/ do |expected_pairs|
query = URI.parse(current_url).query
actual_params = query ? CGI.parse(query) : {}
expected_params = {}
expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')}
if actual_params.respond_to? :should
actual_params.should == expected_params
else
assert_equal expected_params, actual_params
end
end
Then /^I save and open the page$/ do
save_and_open_page
end
Then /^show me the page$/ do
save_and_open_page
end

View File

@ -1,3 +0,0 @@
require 'capybara/cucumber'
require 'test/unit/assertions'
World(Test::Unit::Assertions)

View File

@ -1,35 +0,0 @@
module NavigationHelpers
# Maps a name to a path. Used by the
#
# When /^I go to (.+)$/ do |page_name|
#
# step definition in web_steps.rb
#
def path_to(page_name)
case page_name
when /the new user page/
'/users/new'
when /the home\s?page/
'/'
# Add more mappings here.
# Here is an example that pulls values out of the Regexp:
#
# when /^(.*)'s profile page$/i
# user_profile_path(User.find_by_login($1))
else
begin
page_name =~ /the (.*) page/
path_components = $1.split(/\s+/)
self.send(path_components.push('path').join('_').to_sym)
rescue Object => e
raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
"Now, go and add a mapping in #{__FILE__}"
end
end
end
end
World(NavigationHelpers)

View File

@ -1,5 +0,0 @@
PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')).freeze
TEMP_ROOT = File.join(PROJECT_ROOT, 'tmp').freeze
APP_NAME = 'testapp'.freeze
CUC_RAILS_ROOT = File.join(TEMP_ROOT, APP_NAME).freeze
ENV['RAILS_ENV'] = 'test'

View File

@ -1,25 +0,0 @@
module AWSS3Methods
def load_s3
begin
require 'aws/s3'
rescue LoadError => e
fail "You do not have aws-s3 installed."
end
end
def assert_credentials(key, secret)
load_s3
begin
AWS::S3::Base.establish_connection!(
:access_key_id => key,
:secret_access_key => secret
)
AWS::S3::Service.buckets
rescue AWS::S3::ResponseError => e
fail "Could not connect using AWS credentials in AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. " +
"Please make sure these are set in your environment."
end
end
end
World(AWSS3Methods)

View File

@ -1,5 +0,0 @@
Usage:
script/generate paperclip Class attachment1 (attachment2 ...)
This will create a migration that will add the proper columns to your class's table.

View File

@ -1,27 +0,0 @@
class PaperclipGenerator < Rails::Generator::NamedBase
attr_accessor :attachments, :migration_name
def initialize(args, options = {})
super
@class_name, @attachments = args[0], args[1..-1]
end
def manifest
file_name = generate_file_name
@migration_name = file_name.camelize
record do |m|
m.migration_template "paperclip_migration.rb.erb",
File.join('db', 'migrate'),
:migration_file_name => file_name
end
end
private
def generate_file_name
names = attachments.map{|a| a.underscore }
names = names[0..-2] + ["and", names[-1]] if names.length > 1
"add_attachments_#{names.join("_")}_to_#{@class_name.underscore}"
end
end

View File

@ -1,19 +0,0 @@
class <%= migration_name %> < ActiveRecord::Migration
def self.up
<% attachments.each do |attachment| -%>
add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_name, :string
add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_content_type, :string
add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_size, :integer
add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_updated_at, :datetime
<% end -%>
end
def self.down
<% attachments.each do |attachment| -%>
remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_name
remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_content_type
remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_size
remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_updated_at
<% end -%>
end
end

View File

@ -1 +0,0 @@
require File.join(File.dirname(__FILE__), "lib", "paperclip")

View File

@ -1,8 +0,0 @@
Description:
Explain the generator
Example:
rails generate paperclip Thing
This will create:
what/will/it/create

View File

@ -1,31 +0,0 @@
require 'rails/generators/active_record'
class PaperclipGenerator < ActiveRecord::Generators::Base
desc "Create a migration to add paperclip-specific fields to your model."
argument :attachment_names, :required => true, :type => :array, :desc => "The names of the attachment(s) to add.",
:banner => "attachment_one attachment_two attachment_three ..."
def self.source_root
@source_root ||= File.expand_path('../templates', __FILE__)
end
def generate_migration
migration_template "paperclip_migration.rb.erb", "db/migrate/#{migration_file_name}"
end
protected
def migration_name
"add_attachment_#{attachment_names.join("_")}_to_#{name.underscore}"
end
def migration_file_name
"#{migration_name}.rb"
end
def migration_class_name
migration_name.camelize
end
end

View File

@ -1,19 +0,0 @@
class <%= migration_class_name %> < ActiveRecord::Migration
def self.up
<% attachment_names.each do |attachment| -%>
add_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_file_name, :string
add_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_content_type, :string
add_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_file_size, :integer
add_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_updated_at, :datetime
<% end -%>
end
def self.down
<% attachment_names.each do |attachment| -%>
remove_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_file_name
remove_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_content_type
remove_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_file_size
remove_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_updated_at
<% end -%>
end
end

View File

@ -1,370 +0,0 @@
# Paperclip allows file attachments that are stored in the filesystem. All graphical
# transformations are done using the Graphics/ImageMagick command line utilities and
# are stored in Tempfiles until the record is saved. Paperclip does not require a
# separate model for storing the attachment's information, instead adding a few simple
# columns to your table.
#
# Author:: Jon Yurek
# Copyright:: Copyright (c) 2008-2009 thoughtbot, inc.
# License:: MIT License (http://www.opensource.org/licenses/mit-license.php)
#
# Paperclip defines an attachment as any file, though it makes special considerations
# for image files. You can declare that a model has an attached file with the
# +has_attached_file+ method:
#
# class User < ActiveRecord::Base
# has_attached_file :avatar, :styles => { :thumb => "100x100" }
# end
#
# user = User.new
# user.avatar = params[:user][:avatar]
# user.avatar.url
# # => "/users/avatars/4/original_me.jpg"
# user.avatar.url(:thumb)
# # => "/users/avatars/4/thumb_me.jpg"
#
# See the +has_attached_file+ documentation for more details.
require 'erb'
require 'digest'
require 'tempfile'
require 'paperclip/version'
require 'paperclip/upfile'
require 'paperclip/iostream'
require 'paperclip/geometry'
require 'paperclip/processor'
require 'paperclip/thumbnail'
require 'paperclip/interpolations'
require 'paperclip/style'
require 'paperclip/attachment'
require 'paperclip/storage'
require 'paperclip/callback_compatability'
require 'paperclip/command_line'
require 'paperclip/railtie'
if defined?(Rails.root) && Rails.root
Dir.glob(File.join(File.expand_path(Rails.root), "lib", "paperclip_processors", "*.rb")).each do |processor|
require processor
end
end
# The base module that gets included in ActiveRecord::Base. See the
# documentation for Paperclip::ClassMethods for more useful information.
module Paperclip
class << self
# Provides configurability to Paperclip. There are a number of options available, such as:
# * whiny: Will raise an error if Paperclip cannot process thumbnails of
# an uploaded image. Defaults to true.
# * log: Logs progress to the Rails log. Uses ActiveRecord's logger, so honors
# log levels, etc. Defaults to true.
# * command_path: Defines the path at which to find the command line
# programs if they are not visible to Rails the system's search path. Defaults to
# nil, which uses the first executable found in the user's search path.
# * image_magick_path: Deprecated alias of command_path.
def options
@options ||= {
:whiny => true,
:image_magick_path => nil,
:command_path => nil,
:log => true,
:log_command => true,
:swallow_stderr => true
}
end
def configure
yield(self) if block_given?
end
def interpolates key, &block
Paperclip::Interpolations[key] = block
end
# The run method takes a command to execute and an array of parameters
# that get passed to it. The command is prefixed with the :command_path
# option from Paperclip.options. If you have many commands to run and
# they are in different paths, the suggested course of action is to
# symlink them so they are all in the same directory.
#
# If the command returns with a result code that is not one of the
# expected_outcodes, a PaperclipCommandLineError will be raised. Generally
# a code of 0 is expected, but a list of codes may be passed if necessary.
# These codes should be passed as a hash as the last argument, like so:
#
# Paperclip.run("echo", "something", :expected_outcodes => [0,1,2,3])
#
# This method can log the command being run when
# Paperclip.options[:log_command] is set to true (defaults to false). This
# will only log if logging in general is set to true as well.
def run cmd, *params
if options[:image_magick_path]
Paperclip.log("[DEPRECATION] :image_magick_path is deprecated and will be removed. Use :command_path instead")
end
CommandLine.path = options[:command_path] || options[:image_magick_path]
CommandLine.new(cmd, *params).run
end
def processor name #:nodoc:
name = name.to_s.camelize
processor = Paperclip.const_get(name)
unless processor.ancestors.include?(Paperclip::Processor)
raise PaperclipError.new("Processor #{name} was not found")
end
processor
end
# Log a paperclip-specific line. Uses ActiveRecord::Base.logger
# by default. Set Paperclip.options[:log] to false to turn off.
def log message
logger.info("[paperclip] #{message}") if logging?
end
def logger #:nodoc:
ActiveRecord::Base.logger
end
def logging? #:nodoc:
options[:log]
end
end
class PaperclipError < StandardError #:nodoc:
end
class PaperclipCommandLineError < PaperclipError #:nodoc:
attr_accessor :output
def initialize(msg = nil, output = nil)
super(msg)
@output = output
end
end
class StorageMethodNotFound < PaperclipError
end
class CommandNotFoundError < PaperclipError
end
class NotIdentifiedByImageMagickError < PaperclipError #:nodoc:
end
class InfiniteInterpolationError < PaperclipError #:nodoc:
end
module Glue
def self.included base #:nodoc:
base.extend ClassMethods
if base.respond_to?("set_callback")
base.send :include, Paperclip::CallbackCompatability::Rails3
else
base.send :include, Paperclip::CallbackCompatability::Rails21
end
end
end
module ClassMethods
# +has_attached_file+ gives the class it is called on an attribute that maps to a file. This
# is typically a file stored somewhere on the filesystem and has been uploaded by a user.
# The attribute returns a Paperclip::Attachment object which handles the management of
# that file. The intent is to make the attachment as much like a normal attribute. The
# thumbnails will be created when the new file is assigned, but they will *not* be saved
# until +save+ is called on the record. Likewise, if the attribute is set to +nil+ is
# called on it, the attachment will *not* be deleted until +save+ is called. See the
# Paperclip::Attachment documentation for more specifics. There are a number of options
# you can set to change the behavior of a Paperclip attachment:
# * +url+: The full URL of where the attachment is publically accessible. This can just
# as easily point to a directory served directly through Apache as it can to an action
# that can control permissions. You can specify the full domain and path, but usually
# just an absolute path is sufficient. The leading slash *must* be included manually for
# absolute paths. The default value is
# "/system/:attachment/:id/:style/:filename". See
# Paperclip::Attachment#interpolate for more information on variable interpolaton.
# :url => "/:class/:attachment/:id/:style_:filename"
# :url => "http://some.other.host/stuff/:class/:id_:extension"
# * +default_url+: The URL that will be returned if there is no attachment assigned.
# This field is interpolated just as the url is. The default value is
# "/:attachment/:style/missing.png"
# has_attached_file :avatar, :default_url => "/images/default_:style_avatar.png"
# User.new.avatar_url(:small) # => "/images/default_small_avatar.png"
# * +styles+: A hash of thumbnail styles and their geometries. You can find more about
# geometry strings at the ImageMagick website
# (http://www.imagemagick.org/script/command-line-options.php#resize). Paperclip
# also adds the "#" option (e.g. "50x50#"), which will resize the image to fit maximally
# inside the dimensions and then crop the rest off (weighted at the center). The
# default value is to generate no thumbnails.
# * +default_style+: The thumbnail style that will be used by default URLs.
# Defaults to +original+.
# has_attached_file :avatar, :styles => { :normal => "100x100#" },
# :default_style => :normal
# user.avatar.url # => "/avatars/23/normal_me.png"
# * +whiny+: Will raise an error if Paperclip cannot post_process an uploaded file due
# to a command line error. This will override the global setting for this attachment.
# Defaults to true. This option used to be called :whiny_thumbanils, but this is
# deprecated.
# * +convert_options+: When creating thumbnails, use this free-form options
# array to pass in various convert command options. Typical options are "-strip" to
# remove all Exif data from the image (save space for thumbnails and avatars) or
# "-depth 8" to specify the bit depth of the resulting conversion. See ImageMagick
# convert documentation for more options: (http://www.imagemagick.org/script/convert.php)
# Note that this option takes a hash of options, each of which correspond to the style
# of thumbnail being generated. You can also specify :all as a key, which will apply
# to all of the thumbnails being generated. If you specify options for the :original,
# it would be best if you did not specify destructive options, as the intent of keeping
# the original around is to regenerate all the thumbnails when requirements change.
# has_attached_file :avatar, :styles => { :large => "300x300", :negative => "100x100" }
# :convert_options => {
# :all => "-strip",
# :negative => "-negate"
# }
# NOTE: While not deprecated yet, it is not recommended to specify options this way.
# It is recommended that :convert_options option be included in the hash passed to each
# :styles for compatability with future versions.
# NOTE: Strings supplied to :convert_options are split on space in order to undergo
# shell quoting for safety. If your options require a space, please pre-split them
# and pass an array to :convert_options instead.
# * +storage+: Chooses the storage backend where the files will be stored. The current
# choices are :filesystem and :s3. The default is :filesystem. Make sure you read the
# documentation for Paperclip::Storage::Filesystem and Paperclip::Storage::S3
# for backend-specific options.
def has_attached_file name, options = {}
include InstanceMethods
write_inheritable_attribute(:attachment_definitions, {}) if attachment_definitions.nil?
attachment_definitions[name] = {:validations => []}.merge(options)
after_save :save_attached_files
before_destroy :destroy_attached_files
define_paperclip_callbacks :post_process, :"#{name}_post_process"
define_method name do |*args|
a = attachment_for(name)
(args.length > 0) ? a.to_s(args.first) : a
end
define_method "#{name}=" do |file|
attachment_for(name).assign(file)
end
define_method "#{name}?" do
attachment_for(name).file?
end
validates_each(name) do |record, attr, value|
attachment = record.attachment_for(name)
attachment.send(:flush_errors)
end
end
# Places ActiveRecord-style validations on the size of the file assigned. The
# possible options are:
# * +in+: a Range of bytes (i.e. +1..1.megabyte+),
# * +less_than+: equivalent to :in => 0..options[:less_than]
# * +greater_than+: equivalent to :in => options[:greater_than]..Infinity
# * +message+: error message to display, use :min and :max as replacements
# * +if+: A lambda or name of a method on the instance. Validation will only
# be run is this lambda or method returns true.
# * +unless+: Same as +if+ but validates if lambda or method returns false.
def validates_attachment_size name, options = {}
min = options[:greater_than] || (options[:in] && options[:in].first) || 0
max = options[:less_than] || (options[:in] && options[:in].last) || (1.0/0)
range = (min..max)
message = options[:message] || "file size must be between :min and :max bytes."
message = message.gsub(/:min/, min.to_s).gsub(/:max/, max.to_s)
validates_inclusion_of :"#{name}_file_size",
:in => range,
:message => message,
:if => options[:if],
:unless => options[:unless],
:allow_nil => true
end
# Adds errors if thumbnail creation fails. The same as specifying :whiny_thumbnails => true.
def validates_attachment_thumbnails name, options = {}
warn('[DEPRECATION] validates_attachment_thumbnail is deprecated. ' +
'This validation is on by default and will be removed from future versions. ' +
'If you wish to turn it off, supply :whiny => false in your definition.')
attachment_definitions[name][:whiny_thumbnails] = true
end
# Places ActiveRecord-style validations on the presence of a file.
# Options:
# * +if+: A lambda or name of a method on the instance. Validation will only
# be run is this lambda or method returns true.
# * +unless+: Same as +if+ but validates if lambda or method returns false.
def validates_attachment_presence name, options = {}
message = options[:message] || "must be set."
validates_presence_of :"#{name}_file_name",
:message => message,
:if => options[:if],
:unless => options[:unless]
end
# Places ActiveRecord-style validations on the content type of the file
# assigned. The possible options are:
# * +content_type+: Allowed content types. Can be a single content type
# or an array. Each type can be a String or a Regexp. It should be
# noted that Internet Explorer upload files with content_types that you
# may not expect. For example, JPEG images are given image/pjpeg and
# PNGs are image/x-png, so keep that in mind when determining how you
# match. Allows all by default.
# * +message+: The message to display when the uploaded file has an invalid
# content type.
# * +if+: A lambda or name of a method on the instance. Validation will only
# be run is this lambda or method returns true.
# * +unless+: Same as +if+ but validates if lambda or method returns false.
# NOTE: If you do not specify an [attachment]_content_type field on your
# model, content_type validation will work _ONLY upon assignment_ and
# re-validation after the instance has been reloaded will always succeed.
def validates_attachment_content_type name, options = {}
validation_options = options.dup
allowed_types = [validation_options[:content_type]].flatten
validates_each(:"#{name}_content_type", validation_options) do |record, attr, value|
if !allowed_types.any?{|t| t === value } && !(value.nil? || value.blank?)
if record.errors.method(:add).arity == -2
message = options[:message] || "is not one of #{allowed_types.join(", ")}"
record.errors.add(:"#{name}_content_type", message)
else
record.errors.add(:"#{name}_content_type", :inclusion, :default => options[:message], :value => value)
end
end
end
end
# Returns the attachment definitions defined by each call to
# has_attached_file.
def attachment_definitions
read_inheritable_attribute(:attachment_definitions)
end
end
module InstanceMethods #:nodoc:
def attachment_for name
@_paperclip_attachments ||= {}
@_paperclip_attachments[name] ||= Attachment.new(name, self, self.class.attachment_definitions[name])
end
def each_attachment
self.class.attachment_definitions.each do |name, definition|
yield(name, attachment_for(name))
end
end
def save_attached_files
Paperclip.log("Saving attachments.")
each_attachment do |name, attachment|
attachment.send(:save)
end
end
def destroy_attached_files
Paperclip.log("Deleting attachments.")
each_attachment do |name, attachment|
attachment.send(:queue_existing_for_delete)
attachment.send(:flush_deletes)
end
end
end
end

View File

@ -1,340 +0,0 @@
# encoding: utf-8
module Paperclip
# The Attachment class manages the files for a given attachment. It saves
# when the model saves, deletes when the model is destroyed, and processes
# the file upon assignment.
class Attachment
def self.default_options
@default_options ||= {
:url => "/system/:attachment/:id/:style/:filename",
:path => ":rails_root/public:url",
:styles => {},
:processors => [:thumbnail],
:convert_options => {},
:default_url => "/:attachment/:style/missing.png",
:default_style => :original,
:storage => :filesystem,
:use_timestamp => true,
:whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails]
}
end
attr_reader :name, :instance, :default_style, :convert_options, :queued_for_write, :whiny, :options
# Creates an Attachment object. +name+ is the name of the attachment,
# +instance+ is the ActiveRecord object instance it's attached to, and
# +options+ is the same as the hash passed to +has_attached_file+.
def initialize name, instance, options = {}
@name = name
@instance = instance
options = self.class.default_options.merge(options)
@url = options[:url]
@url = @url.call(self) if @url.is_a?(Proc)
@path = options[:path]
@path = @path.call(self) if @path.is_a?(Proc)
@styles = options[:styles]
@normalized_styles = nil
@default_url = options[:default_url]
@default_style = options[:default_style]
@storage = options[:storage]
@use_timestamp = options[:use_timestamp]
@whiny = options[:whiny_thumbnails] || options[:whiny]
@convert_options = options[:convert_options]
@processors = options[:processors]
@options = options
@queued_for_delete = []
@queued_for_write = {}
@errors = {}
@dirty = false
initialize_storage
end
def styles
unless @normalized_styles
@normalized_styles = {}
(@styles.respond_to?(:call) ? @styles.call(self) : @styles).each do |name, args|
@normalized_styles[name] = Paperclip::Style.new(name, args.dup, self)
end
end
@normalized_styles
end
def processors
@processors.respond_to?(:call) ? @processors.call(instance) : @processors
end
# What gets called when you call instance.attachment = File. It clears
# errors, assigns attributes, and processes the file. It
# also queues up the previous file for deletion, to be flushed away on
# #save of its host. In addition to form uploads, you can also assign
# another Paperclip attachment:
# new_user.avatar = old_user.avatar
def assign uploaded_file
ensure_required_accessors!
if uploaded_file.is_a?(Paperclip::Attachment)
uploaded_file = uploaded_file.to_file(:original)
close_uploaded_file = uploaded_file.respond_to?(:close)
end
return nil unless valid_assignment?(uploaded_file)
uploaded_file.binmode if uploaded_file.respond_to? :binmode
self.clear
return nil if uploaded_file.nil?
@queued_for_write[:original] = uploaded_file.to_tempfile
instance_write(:file_name, uploaded_file.original_filename.strip)
instance_write(:content_type, uploaded_file.content_type.to_s.strip)
instance_write(:file_size, uploaded_file.size.to_i)
instance_write(:fingerprint, uploaded_file.fingerprint)
instance_write(:updated_at, Time.now)
@dirty = true
post_process
# Reset the file size if the original file was reprocessed.
instance_write(:file_size, @queued_for_write[:original].size.to_i)
instance_write(:fingerprint, @queued_for_write[:original].fingerprint)
ensure
uploaded_file.close if close_uploaded_file
end
# Returns the public URL of the attachment, with a given style. Note that
# this does not necessarily need to point to a file that your web server
# can access and can point to an action in your app, if you need fine
# grained security. This is not recommended if you don't need the
# security, however, for performance reasons. Set use_timestamp to false
# if you want to stop the attachment update time appended to the url
def url(style_name = default_style, use_timestamp = @use_timestamp)
url = original_filename.nil? ? interpolate(@default_url, style_name) : interpolate(@url, style_name)
use_timestamp && updated_at ? [url, updated_at].compact.join(url.include?("?") ? "&" : "?") : url
end
# Returns the path of the attachment as defined by the :path option. If the
# file is stored in the filesystem the path refers to the path of the file
# on disk. If the file is stored in S3, the path is the "key" part of the
# URL, and the :bucket option refers to the S3 bucket.
def path style_name = default_style
original_filename.nil? ? nil : interpolate(@path, style_name)
end
# Alias to +url+
def to_s style_name = nil
url(style_name)
end
# Returns an array containing the errors on this attachment.
def errors
@errors
end
# Returns true if there are changes that need to be saved.
def dirty?
@dirty
end
# Saves the file, if there are no errors. If there are, it flushes them to
# the instance's errors and returns false, cancelling the save.
def save
flush_deletes
flush_writes
@dirty = false
true
end
# Clears out the attachment. Has the same effect as previously assigning
# nil to the attachment. Does NOT save. If you wish to clear AND save,
# use #destroy.
def clear
queue_existing_for_delete
@errors = {}
end
# Destroys the attachment. Has the same effect as previously assigning
# nil to the attachment *and saving*. This is permanent. If you wish to
# wipe out the existing attachment but not save, use #clear.
def destroy
clear
save
end
# Returns the name of the file as originally assigned, and lives in the
# <attachment>_file_name attribute of the model.
def original_filename
instance_read(:file_name)
end
# Returns the size of the file as originally assigned, and lives in the
# <attachment>_file_size attribute of the model.
def size
instance_read(:file_size) || (@queued_for_write[:original] && @queued_for_write[:original].size)
end
# Returns the hash of the file as originally assigned, and lives in the
# <attachment>_fingerprint attribute of the model.
def fingerprint
instance_read(:fingerprint) || (@queued_for_write[:original] && @queued_for_write[:original].fingerprint)
end
# Returns the content_type of the file as originally assigned, and lives
# in the <attachment>_content_type attribute of the model.
def content_type
instance_read(:content_type)
end
# Returns the last modified time of the file as originally assigned, and
# lives in the <attachment>_updated_at attribute of the model.
def updated_at
time = instance_read(:updated_at)
time && time.to_f.to_i
end
# Paths and URLs can have a number of variables interpolated into them
# to vary the storage location based on name, id, style, class, etc.
# This method is a deprecated access into supplying and retrieving these
# interpolations. Future access should use either Paperclip.interpolates
# or extend the Paperclip::Interpolations module directly.
def self.interpolations
warn('[DEPRECATION] Paperclip::Attachment.interpolations is deprecated ' +
'and will be removed from future versions. ' +
'Use Paperclip.interpolates instead')
Paperclip::Interpolations
end
# This method really shouldn't be called that often. It's expected use is
# in the paperclip:refresh rake task and that's it. It will regenerate all
# thumbnails forcefully, by reobtaining the original file and going through
# the post-process again.
def reprocess!
new_original = Tempfile.new("paperclip-reprocess")
new_original.binmode
if old_original = to_file(:original)
new_original.write( old_original.respond_to?(:get) ? old_original.get : old_original.read )
new_original.rewind
@queued_for_write = { :original => new_original }
post_process
old_original.close if old_original.respond_to?(:close)
save
else
true
end
end
# Returns true if a file has been assigned.
def file?
!original_filename.blank?
end
# Writes the attachment-specific attribute on the instance. For example,
# instance_write(:file_name, "me.jpg") will write "me.jpg" to the instance's
# "avatar_file_name" field (assuming the attachment is called avatar).
def instance_write(attr, value)
setter = :"#{name}_#{attr}="
responds = instance.respond_to?(setter)
self.instance_variable_set("@_#{setter.to_s.chop}", value)
instance.send(setter, value) if responds || attr.to_s == "file_name"
end
# Reads the attachment-specific attribute on the instance. See instance_write
# for more details.
def instance_read(attr)
getter = :"#{name}_#{attr}"
responds = instance.respond_to?(getter)
cached = self.instance_variable_get("@_#{getter}")
return cached if cached
instance.send(getter) if responds || attr.to_s == "file_name"
end
private
def ensure_required_accessors! #:nodoc:
%w(file_name).each do |field|
unless @instance.respond_to?("#{name}_#{field}") && @instance.respond_to?("#{name}_#{field}=")
raise PaperclipError.new("#{@instance.class} model missing required attr_accessor for '#{name}_#{field}'")
end
end
end
def log message #:nodoc:
Paperclip.log(message)
end
def valid_assignment? file #:nodoc:
file.nil? || (file.respond_to?(:original_filename) && file.respond_to?(:content_type))
end
def initialize_storage #:nodoc:
storage_class_name = @storage.to_s.capitalize
begin
@storage_module = Paperclip::Storage.const_get(storage_class_name)
rescue NameError
raise StorageMethodNotFound, "Cannot load storage module '#{storage_class_name}'"
end
self.extend(@storage_module)
end
def extra_options_for(style) #:nodoc:
all_options = convert_options[:all]
all_options = all_options.call(instance) if all_options.respond_to?(:call)
style_options = convert_options[style]
style_options = style_options.call(instance) if style_options.respond_to?(:call)
[ style_options, all_options ].compact.join(" ")
end
def post_process #:nodoc:
return if @queued_for_write[:original].nil?
instance.run_paperclip_callbacks(:post_process) do
instance.run_paperclip_callbacks(:"#{name}_post_process") do
post_process_styles
end
end
end
def post_process_styles #:nodoc:
styles.each do |name, style|
begin
raise RuntimeError.new("Style #{name} has no processors defined.") if style.processors.blank?
@queued_for_write[name] = style.processors.inject(@queued_for_write[:original]) do |file, processor|
Paperclip.processor(processor).make(file, style.processor_options, self)
end
rescue PaperclipError => e
log("An error was received while processing: #{e.inspect}")
(@errors[:processing] ||= []) << e.message if @whiny
end
end
end
def interpolate pattern, style_name = default_style #:nodoc:
Paperclip::Interpolations.interpolate(pattern, self, style_name)
end
def queue_existing_for_delete #:nodoc:
return unless file?
@queued_for_delete += [:original, *styles.keys].uniq.map do |style|
path(style) if exists?(style)
end.compact
instance_write(:file_name, nil)
instance_write(:content_type, nil)
instance_write(:file_size, nil)
instance_write(:updated_at, nil)
end
def flush_errors #:nodoc:
@errors.each do |error, message|
[message].flatten.each {|m| instance.errors.add(name, m) }
end
end
end
end

View File

@ -1,61 +0,0 @@
module Paperclip
module CallbackCompatability
module Rails21
def self.included(base)
base.extend(Defining)
base.send(:include, Running)
end
module Defining
def define_paperclip_callbacks(*args)
args.each do |callback|
define_callbacks("before_#{callback}")
define_callbacks("after_#{callback}")
end
end
end
module Running
def run_paperclip_callbacks(callback, opts = nil, &blk)
# The overall structure of this isn't ideal since after callbacks run even if
# befores return false. But this is how rails 3's callbacks work, unfortunately.
if run_callbacks(:"before_#{callback}"){ |result, object| result == false } != false
blk.call
end
run_callbacks(:"after_#{callback}"){ |result, object| result == false }
end
end
end
module Rails3
def self.included(base)
base.extend(Defining)
base.send(:include, Running)
end
module Defining
def define_paperclip_callbacks(*callbacks)
define_callbacks *[callbacks, {:terminator => "result == false"}].flatten
callbacks.each do |callback|
eval <<-end_callbacks
def before_#{callback}(*args, &blk)
set_callback(:#{callback}, :before, *args, &blk)
end
def after_#{callback}(*args, &blk)
set_callback(:#{callback}, :after, *args, &blk)
end
end_callbacks
end
end
end
module Running
def run_paperclip_callbacks(callback, opts = nil, &block)
run_callbacks(callback, opts, &block)
end
end
end
end
end

View File

@ -1,80 +0,0 @@
module Paperclip
class CommandLine
class << self
attr_accessor :path
end
def initialize(binary, params = "", options = {})
@binary = binary.dup
@params = params.dup
@options = options.dup
@swallow_stderr = @options.has_key?(:swallow_stderr) ? @options.delete(:swallow_stderr) : Paperclip.options[:swallow_stderr]
@expected_outcodes = @options.delete(:expected_outcodes)
@expected_outcodes ||= [0]
end
def command
cmd = []
cmd << full_path(@binary)
cmd << interpolate(@params, @options)
cmd << bit_bucket if @swallow_stderr
cmd.join(" ")
end
def run
Paperclip.log(command)
begin
output = self.class.send(:'`', command)
rescue Errno::ENOENT
raise Paperclip::CommandNotFoundError
end
if $?.exitstatus == 127
raise Paperclip::CommandNotFoundError
end
unless @expected_outcodes.include?($?.exitstatus)
raise Paperclip::PaperclipCommandLineError, "Command '#{command}' returned #{$?.exitstatus}. Expected #{@expected_outcodes.join(", ")}"
end
output
end
private
def full_path(binary)
[self.class.path, binary].compact.join("/")
end
def interpolate(pattern, vars)
# interpolates :variables and :{variables}
pattern.gsub(%r#:(?:\w+|\{\w+\})#) do |match|
key = match[1..-1]
key = key[1..-2] if key[0,1] == '{'
if invalid_variables.include?(key)
raise PaperclipCommandLineError,
"Interpolation of #{key} isn't allowed."
end
shell_quote(vars[key.to_sym])
end
end
def invalid_variables
%w(expected_outcodes swallow_stderr)
end
def shell_quote(string)
return "" if string.nil? or string.blank?
if self.class.unix?
string.split("'").map{|m| "'#{m}'" }.join("\\'")
else
%{"#{string}"}
end
end
def bit_bucket
self.class.unix? ? "2>/dev/null" : "2>NUL"
end
def self.unix?
File.exist?("/dev/null")
end
end
end

View File

@ -1,115 +0,0 @@
module Paperclip
# Defines the geometry of an image.
class Geometry
attr_accessor :height, :width, :modifier
# Gives a Geometry representing the given height and width
def initialize width = nil, height = nil, modifier = nil
@height = height.to_f
@width = width.to_f
@modifier = modifier
end
# Uses ImageMagick to determing the dimensions of a file, passed in as either a
# File or path.
def self.from_file file
file = file.path if file.respond_to? "path"
geometry = begin
Paperclip.run("identify", "-format %wx%h :file", :file => "#{file}[0]")
rescue PaperclipCommandLineError
""
end
parse(geometry) ||
raise(NotIdentifiedByImageMagickError.new("#{file} is not recognized by the 'identify' command."))
end
# Parses a "WxH" formatted string, where W is the width and H is the height.
def self.parse string
if match = (string && string.match(/\b(\d*)x?(\d*)\b([\>\<\#\@\%^!])?/i))
Geometry.new(*match[1,3])
end
end
# True if the dimensions represent a square
def square?
height == width
end
# True if the dimensions represent a horizontal rectangle
def horizontal?
height < width
end
# True if the dimensions represent a vertical rectangle
def vertical?
height > width
end
# The aspect ratio of the dimensions.
def aspect
width / height
end
# Returns the larger of the two dimensions
def larger
[height, width].max
end
# Returns the smaller of the two dimensions
def smaller
[height, width].min
end
# Returns the width and height in a format suitable to be passed to Geometry.parse
def to_s
s = ""
s << width.to_i.to_s if width > 0
s << "x#{height.to_i}" if height > 0
s << modifier.to_s
s
end
# Same as to_s
def inspect
to_s
end
# Returns the scaling and cropping geometries (in string-based ImageMagick format)
# neccessary to transform this Geometry into the Geometry given. If crop is true,
# then it is assumed the destination Geometry will be the exact final resolution.
# In this case, the source Geometry is scaled so that an image containing the
# destination Geometry would be completely filled by the source image, and any
# overhanging image would be cropped. Useful for square thumbnail images. The cropping
# is weighted at the center of the Geometry.
def transformation_to dst, crop = false
if crop
ratio = Geometry.new( dst.width / self.width, dst.height / self.height )
scale_geometry, scale = scaling(dst, ratio)
crop_geometry = cropping(dst, ratio, scale)
else
scale_geometry = dst.to_s
end
[ scale_geometry, crop_geometry ]
end
private
def scaling dst, ratio
if ratio.horizontal? || ratio.square?
[ "%dx" % dst.width, ratio.width ]
else
[ "x%d" % dst.height, ratio.height ]
end
end
def cropping dst, ratio, scale
if ratio.horizontal? || ratio.square?
"%dx%d+%d+%d" % [ dst.width, dst.height, 0, (self.height * scale - dst.height) / 2 ]
else
"%dx%d+%d+%d" % [ dst.width, dst.height, (self.width * scale - dst.width) / 2, 0 ]
end
end
end
end

View File

@ -1,113 +0,0 @@
module Paperclip
# This module contains all the methods that are available for interpolation
# in paths and urls. To add your own (or override an existing one), you
# can either open this module and define it, or call the
# Paperclip.interpolates method.
module Interpolations
extend self
# Hash assignment of interpolations. Included only for compatability,
# and is not intended for normal use.
def self.[]= name, block
define_method(name, &block)
end
# Hash access of interpolations. Included only for compatability,
# and is not intended for normal use.
def self.[] name
method(name)
end
# Returns a sorted list of all interpolations.
def self.all
self.instance_methods(false).sort
end
# Perform the actual interpolation. Takes the pattern to interpolate
# and the arguments to pass, which are the attachment and style name.
def self.interpolate pattern, *args
all.reverse.inject( pattern.dup ) do |result, tag|
result.gsub(/:#{tag}/) do |match|
send( tag, *args )
end
end
end
# Returns the filename, the same way as ":basename.:extension" would.
def filename attachment, style_name
"#{basename(attachment, style_name)}.#{extension(attachment, style_name)}"
end
# Returns the interpolated URL. Will raise an error if the url itself
# contains ":url" to prevent infinite recursion. This interpolation
# is used in the default :path to ease default specifications.
def url attachment, style_name
raise InfiniteInterpolationError if caller.any?{|b| b.index("#{__FILE__}:#{__LINE__ + 1}") }
attachment.url(style_name, false)
end
# Returns the timestamp as defined by the <attachment>_updated_at field
def timestamp attachment, style_name
attachment.instance_read(:updated_at).to_s
end
# Returns the Rails.root constant.
def rails_root attachment, style_name
Rails.root
end
# Returns the Rails.env constant.
def rails_env attachment, style_name
Rails.env
end
# Returns the underscored, pluralized version of the class name.
# e.g. "users" for the User class.
# NOTE: The arguments need to be optional, because some tools fetch
# all class names. Calling #class will return the expected class.
def class attachment = nil, style_name = nil
return super() if attachment.nil? && style_name.nil?
attachment.instance.class.to_s.underscore.pluralize
end
# Returns the basename of the file. e.g. "file" for "file.jpg"
def basename attachment, style_name
attachment.original_filename.gsub(/#{File.extname(attachment.original_filename)}$/, "")
end
# Returns the extension of the file. e.g. "jpg" for "file.jpg"
# If the style has a format defined, it will return the format instead
# of the actual extension.
def extension attachment, style_name
((style = attachment.styles[style_name]) && style[:format]) ||
File.extname(attachment.original_filename).gsub(/^\.+/, "")
end
# Returns the id of the instance.
def id attachment, style_name
attachment.instance.id
end
# Returns the fingerprint of the instance.
def fingerprint attachment, style_name
attachment.fingerprint
end
# Returns the id of the instance in a split path form. e.g. returns
# 000/001/234 for an id of 1234.
def id_partition attachment, style_name
("%09d" % attachment.instance.id).scan(/\d{3}/).join("/")
end
# Returns the pluralized form of the attachment name. e.g.
# "avatars" for an attachment of :avatar
def attachment attachment, style_name
attachment.name.to_s.downcase.pluralize
end
# Returns the style, or the default style if nil is supplied.
def style attachment, style_name
style_name || attachment.default_style
end
end
end

View File

@ -1,59 +0,0 @@
# Provides method that can be included on File-type objects (IO, StringIO, Tempfile, etc) to allow stream copying
# and Tempfile conversion.
module IOStream
# Returns a Tempfile containing the contents of the readable object.
def to_tempfile
name = respond_to?(:original_filename) ? original_filename : (respond_to?(:path) ? path : "stream")
tempfile = Paperclip::Tempfile.new(["stream", File.extname(name)])
tempfile.binmode
self.stream_to(tempfile)
end
# Copies one read-able object from one place to another in blocks, obviating the need to load
# the whole thing into memory. Defaults to 8k blocks. If this module is included in both
# StringIO and Tempfile, then either can have its data copied anywhere else without typing
# worries or memory overhead worries. Returns a File if a String is passed in as the destination
# and returns the IO or Tempfile as passed in if one is sent as the destination.
def stream_to path_or_file, in_blocks_of = 8192
dstio = case path_or_file
when String then File.new(path_or_file, "wb+")
when IO then path_or_file
when Tempfile then path_or_file
end
buffer = ""
self.rewind
while self.read(in_blocks_of, buffer) do
dstio.write(buffer)
end
dstio.rewind
dstio
end
end
class IO #:nodoc:
include IOStream
end
%w( Tempfile StringIO ).each do |klass|
if Object.const_defined? klass
Object.const_get(klass).class_eval do
include IOStream
end
end
end
# Corrects a bug in Windows when asking for Tempfile size.
if defined? Tempfile
class Tempfile
def size
if @tmpfile
@tmpfile.fsync
@tmpfile.flush
@tmpfile.stat.size
else
0
end
end
end
end

View File

@ -1,33 +0,0 @@
require 'paperclip/matchers/have_attached_file_matcher'
require 'paperclip/matchers/validate_attachment_presence_matcher'
require 'paperclip/matchers/validate_attachment_content_type_matcher'
require 'paperclip/matchers/validate_attachment_size_matcher'
module Paperclip
module Shoulda
# Provides rspec-compatible matchers for testing Paperclip attachments.
#
# In spec_helper.rb, you'll need to require the matchers:
#
# require "paperclip/matchers"
#
# And include the module:
#
# Spec::Runner.configure do |config|
# config.include Paperclip::Shoulda::Matchers
# end
#
# Example:
# describe User do
# it { should have_attached_file(:avatar) }
# it { should validate_attachment_presence(:avatar) }
# it { should validate_attachment_content_type(:avatar).
# allowing('image/png', 'image/gif').
# rejecting('text/plain', 'text/xml') }
# it { should validate_attachment_size(:avatar).
# less_than(2.megabytes) }
# end
module Matchers
end
end
end

View File

@ -1,57 +0,0 @@
module Paperclip
module Shoulda
module Matchers
# Ensures that the given instance or class has an attachment with the
# given name.
#
# Example:
# describe User do
# it { should have_attached_file(:avatar) }
# end
def have_attached_file name
HaveAttachedFileMatcher.new(name)
end
class HaveAttachedFileMatcher
def initialize attachment_name
@attachment_name = attachment_name
end
def matches? subject
@subject = subject
@subject = @subject.class unless Class === @subject
responds? && has_column? && included?
end
def failure_message
"Should have an attachment named #{@attachment_name}"
end
def negative_failure_message
"Should not have an attachment named #{@attachment_name}"
end
def description
"have an attachment named #{@attachment_name}"
end
protected
def responds?
methods = @subject.instance_methods.map(&:to_s)
methods.include?("#{@attachment_name}") &&
methods.include?("#{@attachment_name}=") &&
methods.include?("#{@attachment_name}?")
end
def has_column?
@subject.column_names.include?("#{@attachment_name}_file_name")
end
def included?
@subject.ancestors.include?(Paperclip::InstanceMethods)
end
end
end
end
end

View File

@ -1,75 +0,0 @@
module Paperclip
module Shoulda
module Matchers
# Ensures that the given instance or class validates the content type of
# the given attachment as specified.
#
# Example:
# describe User do
# it { should validate_attachment_content_type(:icon).
# allowing('image/png', 'image/gif').
# rejecting('text/plain', 'text/xml') }
# end
def validate_attachment_content_type name
ValidateAttachmentContentTypeMatcher.new(name)
end
class ValidateAttachmentContentTypeMatcher
def initialize attachment_name
@attachment_name = attachment_name
end
def allowing *types
@allowed_types = types.flatten
self
end
def rejecting *types
@rejected_types = types.flatten
self
end
def matches? subject
@subject = subject
@subject = @subject.class unless Class === @subject
@allowed_types && @rejected_types &&
allowed_types_allowed? && rejected_types_rejected?
end
def failure_message
"Content types #{@allowed_types.join(", ")} should be accepted" +
" and #{@rejected_types.join(", ")} rejected by #{@attachment_name}"
end
def negative_failure_message
"Content types #{@allowed_types.join(", ")} should be rejected" +
" and #{@rejected_types.join(", ")} accepted by #{@attachment_name}"
end
def description
"validate the content types allowed on attachment #{@attachment_name}"
end
protected
def allow_types?(types)
types.all? do |type|
file = StringIO.new(".")
file.content_type = type
(subject = @subject.new).attachment_for(@attachment_name).assign(file)
subject.valid?
subject.errors[:"#{@attachment_name}_content_type"].blank?
end
end
def allowed_types_allowed?
allow_types?(@allowed_types)
end
def rejected_types_rejected?
not allow_types?(@rejected_types)
end
end
end
end
end

View File

@ -1,54 +0,0 @@
module Paperclip
module Shoulda
module Matchers
# Ensures that the given instance or class validates the presence of the
# given attachment.
#
# describe User do
# it { should validate_attachment_presence(:avatar) }
# end
def validate_attachment_presence name
ValidateAttachmentPresenceMatcher.new(name)
end
class ValidateAttachmentPresenceMatcher
def initialize attachment_name
@attachment_name = attachment_name
end
def matches? subject
@subject = subject
@subject = @subject.class unless Class === @subject
error_when_not_valid? && no_error_when_valid?
end
def failure_message
"Attachment #{@attachment_name} should be required"
end
def negative_failure_message
"Attachment #{@attachment_name} should not be required"
end
def description
"require presence of attachment #{@attachment_name}"
end
protected
def error_when_not_valid?
(subject = @subject.new).send(@attachment_name).assign(nil)
subject.valid?
not subject.errors[:"#{@attachment_name}_file_name"].blank?
end
def no_error_when_valid?
@file = StringIO.new(".")
(subject = @subject.new).send(@attachment_name).assign(@file)
subject.valid?
subject.errors[:"#{@attachment_name}_file_name"].blank?
end
end
end
end
end

View File

@ -1,95 +0,0 @@
module Paperclip
module Shoulda
module Matchers
# Ensures that the given instance or class validates the size of the
# given attachment as specified.
#
# Examples:
# it { should validate_attachment_size(:avatar).
# less_than(2.megabytes) }
# it { should validate_attachment_size(:icon).
# greater_than(1024) }
# it { should validate_attachment_size(:icon).
# in(0..100) }
def validate_attachment_size name
ValidateAttachmentSizeMatcher.new(name)
end
class ValidateAttachmentSizeMatcher
def initialize attachment_name
@attachment_name = attachment_name
@low, @high = 0, (1.0/0)
end
def less_than size
@high = size
self
end
def greater_than size
@low = size
self
end
def in range
@low, @high = range.first, range.last
self
end
def matches? subject
@subject = subject
@subject = @subject.class unless Class === @subject
lower_than_low? && higher_than_low? && lower_than_high? && higher_than_high?
end
def failure_message
"Attachment #{@attachment_name} must be between #{@low} and #{@high} bytes"
end
def negative_failure_message
"Attachment #{@attachment_name} cannot be between #{@low} and #{@high} bytes"
end
def description
"validate the size of attachment #{@attachment_name}"
end
protected
def override_method object, method, &replacement
(class << object; self; end).class_eval do
define_method(method, &replacement)
end
end
def passes_validation_with_size(new_size)
file = StringIO.new(".")
override_method(file, :size){ new_size }
override_method(file, :to_tempfile){ file }
(subject = @subject.new).send(@attachment_name).assign(file)
subject.valid?
subject.errors[:"#{@attachment_name}_file_size"].blank?
end
def lower_than_low?
not passes_validation_with_size(@low - 1)
end
def higher_than_low?
passes_validation_with_size(@low + 1)
end
def lower_than_high?
return true if @high == (1.0/0)
passes_validation_with_size(@high - 1)
end
def higher_than_high?
return true if @high == (1.0/0)
not passes_validation_with_size(@high + 1)
end
end
end
end
end

View File

@ -1,58 +0,0 @@
module Paperclip
# Paperclip processors allow you to modify attached files when they are
# attached in any way you are able. Paperclip itself uses command-line
# programs for its included Thumbnail processor, but custom processors
# are not required to follow suit.
#
# Processors are required to be defined inside the Paperclip module and
# are also required to be a subclass of Paperclip::Processor. There is
# only one method you *must* implement to properly be a subclass:
# #make, but #initialize may also be of use. Both methods accept 3
# arguments: the file that will be operated on (which is an instance of
# File), a hash of options that were defined in has_attached_file's
# style hash, and the Paperclip::Attachment itself.
#
# All #make needs to return is an instance of File (Tempfile is
# acceptable) which contains the results of the processing.
#
# See Paperclip.run for more information about using command-line
# utilities from within Processors.
class Processor
attr_accessor :file, :options, :attachment
def initialize file, options = {}, attachment = nil
@file = file
@options = options
@attachment = attachment
end
def make
end
def self.make file, options = {}, attachment = nil
new(file, options, attachment).make
end
end
# Due to how ImageMagick handles its image format conversion and how Tempfile
# handles its naming scheme, it is necessary to override how Tempfile makes
# its names so as to allow for file extensions. Idea taken from the comments
# on this blog post:
# http://marsorange.com/archives/of-mogrify-ruby-tempfile-dynamic-class-definitions
class Tempfile < ::Tempfile
# This is Ruby 1.8.7's implementation.
if RUBY_VERSION <= "1.8.6"
def make_tmpname(basename, n)
case basename
when Array
prefix, suffix = *basename
else
prefix, suffix = basename, ''
end
t = Time.now.strftime("%y%m%d")
path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}-#{n}#{suffix}"
end
end
end
end

View File

@ -1,24 +0,0 @@
require 'paperclip'
module Paperclip
if defined? Rails::Railtie
require 'rails'
class Railtie < Rails::Railtie
initializer 'paperclip.insert_into_active_record' do
ActiveSupport.on_load :active_record do
Paperclip::Railtie.insert
end
end
rake_tasks do
load "tasks/paperclip.rake"
end
end
end
class Railtie
def self.insert
ActiveRecord::Base.send(:include, Paperclip::Glue)
File.send(:include, Paperclip::Upfile)
end
end
end

View File

@ -1,2 +0,0 @@
require "paperclip/storage/filesystem"
require "paperclip/storage/s3"

View File

@ -1,73 +0,0 @@
module Paperclip
module Storage
# The default place to store attachments is in the filesystem. Files on the local
# filesystem can be very easily served by Apache without requiring a hit to your app.
# They also can be processed more easily after they've been saved, as they're just
# normal files. There is one Filesystem-specific option for has_attached_file.
# * +path+: The location of the repository of attachments on disk. This can (and, in
# almost all cases, should) be coordinated with the value of the +url+ option to
# allow files to be saved into a place where Apache can serve them without
# hitting your app. Defaults to
# ":rails_root/public/:attachment/:id/:style/:basename.:extension"
# By default this places the files in the app's public directory which can be served
# directly. If you are using capistrano for deployment, a good idea would be to
# make a symlink to the capistrano-created system directory from inside your app's
# public directory.
# See Paperclip::Attachment#interpolate for more information on variable interpolaton.
# :path => "/var/app/attachments/:class/:id/:style/:basename.:extension"
module Filesystem
def self.extended base
end
def exists?(style_name = default_style)
if original_filename
File.exist?(path(style_name))
else
false
end
end
# Returns representation of the data of the file assigned to the given
# style, in the format most representative of the current storage.
def to_file style_name = default_style
@queued_for_write[style_name] || (File.new(path(style_name), 'rb') if exists?(style_name))
end
def flush_writes #:nodoc:
@queued_for_write.each do |style_name, file|
file.close
FileUtils.mkdir_p(File.dirname(path(style_name)))
log("saving #{path(style_name)}")
FileUtils.mv(file.path, path(style_name))
FileUtils.chmod(0644, path(style_name))
end
@queued_for_write = {}
end
def flush_deletes #:nodoc:
@queued_for_delete.each do |path|
begin
log("deleting #{path}")
FileUtils.rm(path) if File.exist?(path)
rescue Errno::ENOENT => e
# ignore file-not-found, let everything else pass
end
begin
while(true)
path = File.dirname(path)
FileUtils.rmdir(path)
break if File.exists?(path) # Ruby 1.9.2 does not raise if the removal failed.
end
rescue Errno::EEXIST, Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL, Errno::ENOTDIR
# Stop trying to remove parent directories
rescue SystemCallError => e
log("There was an unexpected error while deleting directories: #{e.class}")
# Ignore it
end
end
@queued_for_delete = []
end
end
end
end

View File

@ -1,191 +0,0 @@
module Paperclip
module Storage
# Amazon's S3 file hosting service is a scalable, easy place to store files for
# distribution. You can find out more about it at http://aws.amazon.com/s3
# There are a few S3-specific options for has_attached_file:
# * +s3_credentials+: Takes a path, a File, or a Hash. The path (or File) must point
# to a YAML file containing the +access_key_id+ and +secret_access_key+ that Amazon
# gives you. You can 'environment-space' this just like you do to your
# database.yml file, so different environments can use different accounts:
# development:
# access_key_id: 123...
# secret_access_key: 123...
# test:
# access_key_id: abc...
# secret_access_key: abc...
# production:
# access_key_id: 456...
# secret_access_key: 456...
# This is not required, however, and the file may simply look like this:
# access_key_id: 456...
# secret_access_key: 456...
# In which case, those access keys will be used in all environments. You can also
# put your bucket name in this file, instead of adding it to the code directly.
# This is useful when you want the same account but a different bucket for
# development versus production.
# * +s3_permissions+: This is a String that should be one of the "canned" access
# policies that S3 provides (more information can be found here:
# http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAccessPolicy.html#RESTCannedAccessPolicies)
# The default for Paperclip is :public_read.
# * +s3_protocol+: The protocol for the URLs generated to your S3 assets. Can be either
# 'http' or 'https'. Defaults to 'http' when your :s3_permissions are :public_read (the
# default), and 'https' when your :s3_permissions are anything else.
# * +s3_headers+: A hash of headers such as {'Expires' => 1.year.from_now.httpdate}
# * +bucket+: This is the name of the S3 bucket that will store your files. Remember
# that the bucket must be unique across all of Amazon S3. If the bucket does not exist
# Paperclip will attempt to create it. The bucket name will not be interpolated.
# You can define the bucket as a Proc if you want to determine it's name at runtime.
# Paperclip will call that Proc with attachment as the only argument.
# * +s3_host_alias+: The fully-qualified domain name (FQDN) that is the alias to the
# S3 domain of your bucket. Used with the :s3_alias_url url interpolation. See the
# link in the +url+ entry for more information about S3 domains and buckets.
# * +url+: There are three options for the S3 url. You can choose to have the bucket's name
# placed domain-style (bucket.s3.amazonaws.com) or path-style (s3.amazonaws.com/bucket).
# Lastly, you can specify a CNAME (which requires the CNAME to be specified as
# :s3_alias_url. You can read more about CNAMEs and S3 at
# http://docs.amazonwebservices.com/AmazonS3/latest/index.html?VirtualHosting.html
# Normally, this won't matter in the slightest and you can leave the default (which is
# path-style, or :s3_path_url). But in some cases paths don't work and you need to use
# the domain-style (:s3_domain_url). Anything else here will be treated like path-style.
# NOTE: If you use a CNAME for use with CloudFront, you can NOT specify https as your
# :s3_protocol; This is *not supported* by S3/CloudFront. Finally, when using the host
# alias, the :bucket parameter is ignored, as the hostname is used as the bucket name
# by S3.
# * +path+: This is the key under the bucket in which the file will be stored. The
# URL will be constructed from the bucket and the path. This is what you will want
# to interpolate. Keys should be unique, like filenames, and despite the fact that
# S3 (strictly speaking) does not support directories, you can still use a / to
# separate parts of your file name.
module S3
def self.extended base
begin
require 'aws/s3'
rescue LoadError => e
e.message << " (You may need to install the aws-s3 gem)"
raise e
end
base.instance_eval do
@s3_credentials = parse_credentials(@options[:s3_credentials])
@bucket = @options[:bucket] || @s3_credentials[:bucket]
@bucket = @bucket.call(self) if @bucket.is_a?(Proc)
@s3_options = @options[:s3_options] || {}
@s3_permissions = @options[:s3_permissions] || :public_read
@s3_protocol = @options[:s3_protocol] || (@s3_permissions == :public_read ? 'http' : 'https')
@s3_headers = @options[:s3_headers] || {}
@s3_host_alias = @options[:s3_host_alias]
unless @url.to_s.match(/^:s3.*url$/)
@path = @path.gsub(/:url/, @url)
@url = ":s3_path_url"
end
AWS::S3::Base.establish_connection!( @s3_options.merge(
:access_key_id => @s3_credentials[:access_key_id],
:secret_access_key => @s3_credentials[:secret_access_key]
))
end
Paperclip.interpolates(:s3_alias_url) do |attachment, style|
"#{attachment.s3_protocol}://#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
end
Paperclip.interpolates(:s3_path_url) do |attachment, style|
"#{attachment.s3_protocol}://s3.amazonaws.com/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
end
Paperclip.interpolates(:s3_domain_url) do |attachment, style|
"#{attachment.s3_protocol}://#{attachment.bucket_name}.s3.amazonaws.com/#{attachment.path(style).gsub(%r{^/}, "")}"
end
end
def expiring_url(time = 3600)
AWS::S3::S3Object.url_for(path, bucket_name, :expires_in => time )
end
def bucket_name
@bucket
end
def s3_host_alias
@s3_host_alias
end
def parse_credentials creds
creds = find_credentials(creds).stringify_keys
(creds[Rails.env] || creds).symbolize_keys
end
def exists?(style = default_style)
if original_filename
AWS::S3::S3Object.exists?(path(style), bucket_name)
else
false
end
end
def s3_protocol
@s3_protocol
end
# Returns representation of the data of the file assigned to the given
# style, in the format most representative of the current storage.
def to_file style = default_style
return @queued_for_write[style] if @queued_for_write[style]
filename = path(style).split(".")
extname = File.extname(filename)
basename = File.basename(filename, extname)
file = Tempfile.new(basename, extname)
file.write(AWS::S3::S3Object.value(path(style), bucket_name))
file.rewind
return file
end
def create_bucket
AWS::S3::Bucket.create(bucket_name)
end
def flush_writes #:nodoc:
@queued_for_write.each do |style, file|
begin
log("saving #{path(style)}")
AWS::S3::S3Object.store(path(style),
file,
bucket_name,
{:content_type => instance_read(:content_type),
:access => @s3_permissions,
}.merge(@s3_headers))
rescue AWS::S3::NoSuchBucket => e
create_bucket
retry
rescue AWS::S3::ResponseError => e
raise
end
end
@queued_for_write = {}
end
def flush_deletes #:nodoc:
@queued_for_delete.each do |path|
begin
log("deleting #{path}")
AWS::S3::S3Object.delete(path, bucket_name)
rescue AWS::S3::ResponseError
# Ignore this.
end
end
@queued_for_delete = []
end
def find_credentials creds
case creds
when File
YAML::load(ERB.new(File.read(creds.path)).result)
when String, Pathname
YAML::load(ERB.new(File.read(creds)).result)
when Hash
creds
else
raise ArgumentError, "Credentials are not a path, file, or hash."
end
end
private :find_credentials
end
end
end

View File

@ -1,90 +0,0 @@
# encoding: utf-8
module Paperclip
# The Style class holds the definition of a thumbnail style, applying
# whatever processing is required to normalize the definition and delaying
# the evaluation of block parameters until useful context is available.
class Style
attr_reader :name, :attachment, :format
# Creates a Style object. +name+ is the name of the attachment,
# +definition+ is the style definition from has_attached_file, which
# can be string, array or hash
def initialize name, definition, attachment
@name = name
@attachment = attachment
if definition.is_a? Hash
@geometry = definition.delete(:geometry)
@format = definition.delete(:format)
@processors = definition.delete(:processors)
@other_args = definition
else
@geometry, @format = [definition, nil].flatten[0..1]
@other_args = {}
end
@format = nil if @format.blank?
end
# retrieves from the attachment the processors defined in the has_attached_file call
# (which method (in the attachment) will call any supplied procs)
# There is an important change of interface here: a style rule can set its own processors
# by default we behave as before, though.
def processors
@processors || attachment.processors
end
# retrieves from the attachment the whiny setting
def whiny
attachment.whiny
end
# returns true if we're inclined to grumble
def whiny?
!!whiny
end
def convert_options
attachment.send(:extra_options_for, name)
end
# returns the geometry string for this style
# if a proc has been supplied, we call it here
def geometry
@geometry.respond_to?(:call) ? @geometry.call(attachment.instance) : @geometry
end
# Supplies the hash of options that processors expect to receive as their second argument
# Arguments other than the standard geometry, format etc are just passed through from
# initialization and any procs are called here, just before post-processing.
def processor_options
args = {}
@other_args.each do |k,v|
args[k] = v.respond_to?(:call) ? v.call(attachment) : v
end
[:processors, :geometry, :format, :whiny, :convert_options].each do |k|
(arg = send(k)) && args[k] = arg
end
args
end
# Supports getting and setting style properties with hash notation to ensure backwards-compatibility
# eg. @attachment.styles[:large][:geometry]@ will still work
def [](key)
if [:name, :convert_options, :whiny, :processors, :geometry, :format].include?(key)
send(key)
elsif defined? @other_args[key]
@other_args[key]
end
end
def []=(key, value)
if [:name, :convert_options, :whiny, :processors, :geometry, :format].include?(key)
send("#{key}=".intern, value)
else
@other_args[key] = value
end
end
end
end

View File

@ -1,79 +0,0 @@
module Paperclip
# Handles thumbnailing images that are uploaded.
class Thumbnail < Processor
attr_accessor :current_geometry, :target_geometry, :format, :whiny, :convert_options, :source_file_options
# Creates a Thumbnail object set to work on the +file+ given. It
# will attempt to transform the image into one defined by +target_geometry+
# which is a "WxH"-style string. +format+ will be inferred from the +file+
# unless specified. Thumbnail creation will raise no errors unless
# +whiny+ is true (which it is, by default. If +convert_options+ is
# set, the options will be appended to the convert command upon image conversion
def initialize file, options = {}, attachment = nil
super
geometry = options[:geometry]
@file = file
@crop = geometry[-1,1] == '#'
@target_geometry = Geometry.parse geometry
@current_geometry = Geometry.from_file @file
@source_file_options = options[:source_file_options]
@convert_options = options[:convert_options]
@whiny = options[:whiny].nil? ? true : options[:whiny]
@format = options[:format]
@source_file_options = @source_file_options.split(/\s+/) if @source_file_options.respond_to?(:split)
@convert_options = @convert_options.split(/\s+/) if @convert_options.respond_to?(:split)
@current_format = File.extname(@file.path)
@basename = File.basename(@file.path, @current_format)
end
# Returns true if the +target_geometry+ is meant to crop.
def crop?
@crop
end
# Returns true if the image is meant to make use of additional convert options.
def convert_options?
!@convert_options.nil? && !@convert_options.empty?
end
# Performs the conversion of the +file+ into a thumbnail. Returns the Tempfile
# that contains the new image.
def make
src = @file
dst = Tempfile.new([@basename, @format ? ".#{@format}" : ''])
dst.binmode
begin
parameters = []
parameters << source_file_options
parameters << ":source"
parameters << transformation_command
parameters << convert_options
parameters << ":dest"
parameters = parameters.flatten.compact.join(" ").strip.squeeze(" ")
success = Paperclip.run("convert", parameters, :source => "#{File.expand_path(src.path)}[0]", :dest => File.expand_path(dst.path))
rescue PaperclipCommandLineError => e
raise PaperclipError, "There was an error processing the thumbnail for #{@basename}" if @whiny
end
dst
end
# Returns the command ImageMagick's +convert+ needs to transform the image
# into the thumbnail.
def transformation_command
scale, crop = @current_geometry.transformation_to(@target_geometry, crop?)
trans = []
trans << "-resize" << %["#{scale}"] unless scale.nil? || scale.empty?
trans << "-crop" << %["#{crop}"] << "+repage" if crop
trans
end
end
end

View File

@ -1,60 +0,0 @@
module Paperclip
# The Upfile module is a convenience module for adding uploaded-file-type methods
# to the +File+ class. Useful for testing.
# user.avatar = File.new("test/test_avatar.jpg")
module Upfile
# Infer the MIME-type of the file from the extension.
def content_type
type = (self.path.match(/\.(\w+)$/)[1] rescue "octet-stream").downcase
case type
when %r"jp(e|g|eg)" then "image/jpeg"
when %r"tiff?" then "image/tiff"
when %r"png", "gif", "bmp" then "image/#{type}"
when "txt" then "text/plain"
when %r"html?" then "text/html"
when "js" then "application/js"
when "csv", "xml", "css" then "text/#{type}"
else
# On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
content_type = (Paperclip.run("file", "-b --mime-type :file", :file => self.path).split(':').last.strip rescue "application/x-#{type}")
content_type = "application/x-#{type}" if content_type.match(/\(.*?\)/)
content_type
end
end
# Returns the file's normal name.
def original_filename
File.basename(self.path)
end
# Returns the size of the file.
def size
File.size(self)
end
# Returns the hash of the file.
def fingerprint
Digest::MD5.hexdigest(self.read)
end
end
end
if defined? StringIO
class StringIO
attr_accessor :original_filename, :content_type, :fingerprint
def original_filename
@original_filename ||= "stringio.txt"
end
def content_type
@content_type ||= "text/plain"
end
def fingerprint
@fingerprint ||= Digest::MD5.hexdigest(self.string)
end
end
end
class File #:nodoc:
include Paperclip::Upfile
end

View File

@ -1,3 +0,0 @@
module Paperclip
VERSION = "2.3.4" unless defined? Paperclip::VERSION
end

View File

@ -1,79 +0,0 @@
def obtain_class
class_name = ENV['CLASS'] || ENV['class']
raise "Must specify CLASS" unless class_name
@klass = Object.const_get(class_name)
end
def obtain_attachments
name = ENV['ATTACHMENT'] || ENV['attachment']
raise "Class #{@klass.name} has no attachments specified" unless @klass.respond_to?(:attachment_definitions)
if !name.blank? && @klass.attachment_definitions.keys.include?(name)
[ name ]
else
@klass.attachment_definitions.keys
end
end
def for_all_attachments
klass = obtain_class
names = obtain_attachments
ids = klass.connection.select_values(klass.send(:construct_finder_sql, :select => 'id'))
ids.each do |id|
instance = klass.find(id)
names.each do |name|
result = if instance.send("#{ name }?")
yield(instance, name)
else
true
end
print result ? "." : "x"; $stdout.flush
end
end
puts " Done."
end
namespace :paperclip do
desc "Refreshes both metadata and thumbnails."
task :refresh => ["paperclip:refresh:metadata", "paperclip:refresh:thumbnails"]
namespace :refresh do
desc "Regenerates thumbnails for a given CLASS (and optional ATTACHMENT)."
task :thumbnails => :environment do
errors = []
for_all_attachments do |instance, name|
result = instance.send(name).reprocess!
errors << [instance.id, instance.errors] unless instance.errors.blank?
result
end
errors.each{|e| puts "#{e.first}: #{e.last.full_messages.inspect}" }
end
desc "Regenerates content_type/size metadata for a given CLASS (and optional ATTACHMENT)."
task :metadata => :environment do
for_all_attachments do |instance, name|
if file = instance.send(name).to_file
instance.send("#{name}_file_name=", instance.send("#{name}_file_name").strip)
instance.send("#{name}_content_type=", file.content_type.strip)
instance.send("#{name}_file_size=", file.size) if instance.respond_to?("#{name}_file_size")
instance.save(false)
else
true
end
end
end
end
desc "Cleans out invalid attachments. Useful after you've added new validations."
task :clean => :environment do
for_all_attachments do |instance, name|
instance.send(name).send(:validate)
if instance.send(name).valid?
true
else
instance.send("#{name}=", nil)
instance.save
end
end
end
end

View File

@ -1,34 +0,0 @@
$LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
require 'paperclip/version'
include_files = ["README*", "LICENSE", "Rakefile", "init.rb", "{lib,tasks,test,rails,generators,shoulda_macros}/**/*"].map do |glob|
Dir[glob]
end.flatten
exclude_files = ["test/s3.yml", "test/debug.log", "test/paperclip.db", "test/doc", "test/doc/*", "test/pkg", "test/pkg/*", "test/tmp", "test/tmp/*"].map do |glob|
Dir[glob]
end.flatten
spec = Gem::Specification.new do |s|
s.name = "paperclip"
s.version = Paperclip::VERSION
s.author = "Jon Yurek"
s.email = "jyurek@thoughtbot.com"
s.homepage = "http://www.thoughtbot.com/projects/paperclip"
s.description = "Easy upload management for ActiveRecord"
s.platform = Gem::Platform::RUBY
s.summary = "File attachments as attributes for ActiveRecord"
s.files = include_files - exclude_files
s.require_path = "lib"
s.test_files = Dir["test/**/test_*.rb"]
s.rubyforge_project = "paperclip"
s.has_rdoc = true
s.extra_rdoc_files = Dir["README*"]
s.rdoc_options << '--line-numbers' << '--inline-source'
s.requirements << "ImageMagick"
s.add_dependency 'activerecord'
s.add_dependency 'activesupport'
s.add_development_dependency 'shoulda'
s.add_development_dependency 'mocha'
s.add_development_dependency 'aws-s3'
s.add_development_dependency 'sqlite3-ruby'
end

View File

@ -1,2 +0,0 @@
require 'paperclip/railtie'
Paperclip::Railtie.insert

View File

@ -1,118 +0,0 @@
require 'paperclip/matchers'
module Paperclip
# =Paperclip Shoulda Macros
#
# These macros are intended for use with shoulda, and will be included into
# your tests automatically. All of the macros use the standard shoulda
# assumption that the name of the test is based on the name of the model
# you're testing (that is, UserTest is the test for the User model), and
# will load that class for testing purposes.
module Shoulda
include Matchers
# This will test whether you have defined your attachment correctly by
# checking for all the required fields exist after the definition of the
# attachment.
def should_have_attached_file name
klass = self.name.gsub(/Test$/, '').constantize
matcher = have_attached_file name
should matcher.description do
assert_accepts(matcher, klass)
end
end
# Tests for validations on the presence of the attachment.
def should_validate_attachment_presence name
klass = self.name.gsub(/Test$/, '').constantize
matcher = validate_attachment_presence name
should matcher.description do
assert_accepts(matcher, klass)
end
end
# Tests that you have content_type validations specified. There are two
# options, :valid and :invalid. Both accept an array of strings. The
# strings should be a list of content types which will pass and fail
# validation, respectively.
def should_validate_attachment_content_type name, options = {}
klass = self.name.gsub(/Test$/, '').constantize
valid = [options[:valid]].flatten
invalid = [options[:invalid]].flatten
matcher = validate_attachment_content_type(name).allowing(valid).rejecting(invalid)
should matcher.description do
assert_accepts(matcher, klass)
end
end
# Tests to ensure that you have file size validations turned on. You
# can pass the same options to this that you can to
# validate_attachment_file_size - :less_than, :greater_than, and :in.
# :less_than checks that a file is less than a certain size, :greater_than
# checks that a file is more than a certain size, and :in takes a Range or
# Array which specifies the lower and upper limits of the file size.
def should_validate_attachment_size name, options = {}
klass = self.name.gsub(/Test$/, '').constantize
min = options[:greater_than] || (options[:in] && options[:in].first) || 0
max = options[:less_than] || (options[:in] && options[:in].last) || (1.0/0)
range = (min..max)
matcher = validate_attachment_size(name).in(range)
should matcher.description do
assert_accepts(matcher, klass)
end
end
# Stubs the HTTP PUT for an attachment using S3 storage.
#
# @example
# stub_paperclip_s3('user', 'avatar', 'png')
def stub_paperclip_s3(model, attachment, extension)
definition = model.gsub(" ", "_").classify.constantize.
attachment_definitions[attachment.to_sym]
path = "http://s3.amazonaws.com/:id/#{definition[:path]}"
path.gsub!(/:([^\/\.]+)/) do |match|
"([^\/\.]+)"
end
begin
FakeWeb.register_uri(:put, Regexp.new(path), :body => "OK")
rescue NameError
raise NameError, "the stub_paperclip_s3 shoulda macro requires the fakeweb gem."
end
end
# Stub S3 and return a file for attachment. Best with Factory Girl.
# Uses a strict directory convention:
#
# features/support/paperclip
#
# This method is used by the Paperclip-provided Cucumber step:
#
# When I attach a "demo_tape" "mp3" file to a "band" on S3
#
# @example
# Factory.define :band_with_demo_tape, :parent => :band do |band|
# band.demo_tape { band.paperclip_fixture("band", "demo_tape", "png") }
# end
def paperclip_fixture(model, attachment, extension)
stub_paperclip_s3(model, attachment, extension)
base_path = File.join(File.dirname(__FILE__), "..", "..",
"features", "support", "paperclip")
File.new(File.join(base_path, model, "#{attachment}.#{extension}"))
end
end
end
if defined?(ActionController::Integration::Session)
class ActionController::Integration::Session #:nodoc:
include Paperclip::Shoulda
end
end
class Factory
include Paperclip::Shoulda #:nodoc:
end
class Test::Unit::TestCase #:nodoc:
extend Paperclip::Shoulda
end

View File

@ -1 +0,0 @@
debug.log

View File

@ -1,804 +0,0 @@
# encoding: utf-8
require 'test/helper'
class Dummy
# This is a dummy class
end
class AttachmentTest < Test::Unit::TestCase
should "return the path based on the url by default" do
@attachment = attachment :url => "/:class/:id/:basename"
@model = @attachment.instance
@model.id = 1234
@model.avatar_file_name = "fake.jpg"
assert_equal "#{Rails.root}/public/fake_models/1234/fake", @attachment.path
end
context "Attachment default_options" do
setup do
rebuild_model
@old_default_options = Paperclip::Attachment.default_options.dup
@new_default_options = @old_default_options.merge({
:path => "argle/bargle",
:url => "fooferon",
:default_url => "not here.png"
})
end
teardown do
Paperclip::Attachment.default_options.merge! @old_default_options
end
should "be overrideable" do
Paperclip::Attachment.default_options.merge!(@new_default_options)
@new_default_options.keys.each do |key|
assert_equal @new_default_options[key],
Paperclip::Attachment.default_options[key]
end
end
context "without an Attachment" do
setup do
@dummy = Dummy.new
end
should "return false when asked exists?" do
assert !@dummy.avatar.exists?
end
end
context "on an Attachment" do
setup do
@dummy = Dummy.new
@attachment = @dummy.avatar
end
Paperclip::Attachment.default_options.keys.each do |key|
should "be the default_options for #{key}" do
assert_equal @old_default_options[key],
@attachment.instance_variable_get("@#{key}"),
key
end
end
context "when redefined" do
setup do
Paperclip::Attachment.default_options.merge!(@new_default_options)
@dummy = Dummy.new
@attachment = @dummy.avatar
end
Paperclip::Attachment.default_options.keys.each do |key|
should "be the new default_options for #{key}" do
assert_equal @new_default_options[key],
@attachment.instance_variable_get("@#{key}"),
key
end
end
end
end
end
context "An attachment with similarly named interpolations" do
setup do
rebuild_model :path => ":id.omg/:id-bbq/:idwhat/:id_partition.wtf"
@dummy = Dummy.new
@dummy.stubs(:id).returns(1024)
@file = File.new(File.join(File.dirname(__FILE__),
"fixtures",
"5k.png"), 'rb')
@dummy.avatar = @file
end
teardown { @file.close }
should "make sure that they are interpolated correctly" do
assert_equal "1024.omg/1024-bbq/1024what/000/001/024.wtf", @dummy.avatar.path
end
end
context "An attachment with a :rails_env interpolation" do
setup do
@rails_env = "blah"
@id = 1024
rebuild_model :path => ":rails_env/:id.png"
@dummy = Dummy.new
@dummy.stubs(:id).returns(@id)
@file = StringIO.new(".")
@dummy.avatar = @file
Rails.stubs(:env).returns(@rails_env)
end
should "return the proper path" do
assert_equal "#{@rails_env}/#{@id}.png", @dummy.avatar.path
end
end
context "An attachment with a default style and an extension interpolation" do
setup do
@attachment = attachment :path => ":basename.:extension",
:styles => { :default => ["100x100", :png] },
:default_style => :default
@file = StringIO.new("...")
@file.stubs(:original_filename).returns("file.jpg")
end
should "return the right extension for the path" do
@attachment.assign(@file)
assert_equal "file.png", @attachment.path
end
end
context "An attachment with :convert_options" do
setup do
rebuild_model :styles => {
:thumb => "100x100",
:large => "400x400"
},
:convert_options => {
:all => "-do_stuff",
:thumb => "-thumbnailize"
}
@dummy = Dummy.new
@dummy.avatar
end
should "report the correct options when sent #extra_options_for(:thumb)" do
assert_equal "-thumbnailize -do_stuff", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect
end
should "report the correct options when sent #extra_options_for(:large)" do
assert_equal "-do_stuff", @dummy.avatar.send(:extra_options_for, :large)
end
end
context "An attachment with :convert_options that is a proc" do
setup do
rebuild_model :styles => {
:thumb => "100x100",
:large => "400x400"
},
:convert_options => {
:all => lambda{|i| i.all },
:thumb => lambda{|i| i.thumb }
}
Dummy.class_eval do
def all; "-all"; end
def thumb; "-thumb"; end
end
@dummy = Dummy.new
@dummy.avatar
end
should "report the correct options when sent #extra_options_for(:thumb)" do
assert_equal "-thumb -all", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect
end
should "report the correct options when sent #extra_options_for(:large)" do
assert_equal "-all", @dummy.avatar.send(:extra_options_for, :large)
end
end
context "An attachment with :path that is a proc" do
setup do
rebuild_model :path => lambda{ |attachment| "path/#{attachment.instance.other}.:extension" }
@file = File.new(File.join(File.dirname(__FILE__),
"fixtures",
"5k.png"), 'rb')
@dummyA = Dummy.new(:other => 'a')
@dummyA.avatar = @file
@dummyB = Dummy.new(:other => 'b')
@dummyB.avatar = @file
end
teardown { @file.close }
should "return correct path" do
assert_equal "path/a.png", @dummyA.avatar.path
assert_equal "path/b.png", @dummyB.avatar.path
end
end
context "An attachment with :styles that is a proc" do
setup do
rebuild_model :styles => lambda{ |attachment| {:thumb => "50x50#", :large => "400x400"} }
@attachment = Dummy.new.avatar
end
should "have the correct geometry" do
assert_equal "50x50#", @attachment.styles[:thumb][:geometry]
end
end
context "An attachment with :url that is a proc" do
setup do
rebuild_model :url => lambda{ |attachment| "path/#{attachment.instance.other}.:extension" }
@file = File.new(File.join(File.dirname(__FILE__),
"fixtures",
"5k.png"), 'rb')
@dummyA = Dummy.new(:other => 'a')
@dummyA.avatar = @file
@dummyB = Dummy.new(:other => 'b')
@dummyB.avatar = @file
end
teardown { @file.close }
should "return correct url" do
assert_equal "path/a.png", @dummyA.avatar.url(:original, false)
assert_equal "path/b.png", @dummyB.avatar.url(:original, false)
end
end
geometry_specs = [
[ lambda{|z| "50x50#" }, :png ],
lambda{|z| "50x50#" },
{ :geometry => lambda{|z| "50x50#" } }
]
geometry_specs.each do |geometry_spec|
context "An attachment geometry like #{geometry_spec}" do
setup do
rebuild_model :styles => { :normal => geometry_spec }
@attachment = Dummy.new.avatar
end
context "when assigned" do
setup do
@file = StringIO.new(".")
@attachment.assign(@file)
end
should "have the correct geometry" do
assert_equal "50x50#", @attachment.styles[:normal][:geometry]
end
end
end
end
context "An attachment with both 'normal' and hash-style styles" do
setup do
rebuild_model :styles => {
:normal => ["50x50#", :png],
:hash => { :geometry => "50x50#", :format => :png }
}
@dummy = Dummy.new
@attachment = @dummy.avatar
end
[:processors, :whiny, :convert_options, :geometry, :format].each do |field|
should "have the same #{field} field" do
assert_equal @attachment.styles[:normal][field], @attachment.styles[:hash][field]
end
end
end
context "An attachment with :processors that is a proc" do
setup do
rebuild_model :styles => { :normal => '' }, :processors => lambda { |a| [ :test ] }
@attachment = Dummy.new.avatar
end
context "when assigned" do
setup do
@attachment.assign(StringIO.new("."))
end
should "have the correct processors" do
assert_equal [ :test ], @attachment.styles[:normal][:processors]
end
end
end
context "An attachment with erroring processor" do
setup do
rebuild_model :processor => [:thumbnail], :styles => { :small => '' }, :whiny_thumbnails => true
@dummy = Dummy.new
Paperclip::Thumbnail.expects(:make).raises(Paperclip::PaperclipError, "cannot be processed.")
@file = StringIO.new("...")
@file.stubs(:to_tempfile).returns(@file)
@dummy.avatar = @file
end
should "correctly forward processing error message to the instance" do
@dummy.valid?
assert_contains @dummy.errors.full_messages, "Avatar cannot be processed."
end
end
context "An attachment with multiple processors" do
setup do
class Paperclip::Test < Paperclip::Processor; end
@style_params = { :once => {:one => 1, :two => 2} }
rebuild_model :processors => [:thumbnail, :test], :styles => @style_params
@dummy = Dummy.new
@file = StringIO.new("...")
@file.stubs(:to_tempfile).returns(@file)
Paperclip::Test.stubs(:make).returns(@file)
Paperclip::Thumbnail.stubs(:make).returns(@file)
end
context "when assigned" do
setup { @dummy.avatar = @file }
before_should "call #make on all specified processors" do
Paperclip::Thumbnail.expects(:make).with(any_parameters).returns(@file)
Paperclip::Test.expects(:make).with(any_parameters).returns(@file)
end
before_should "call #make with the right parameters passed as second argument" do
expected_params = @style_params[:once].merge({:processors => [:thumbnail, :test], :whiny => true, :convert_options => ""})
Paperclip::Thumbnail.expects(:make).with(anything, expected_params, anything).returns(@file)
end
before_should "call #make with attachment passed as third argument" do
Paperclip::Test.expects(:make).with(anything, anything, @dummy.avatar).returns(@file)
end
end
end
should "include the filesystem module when loading the filesystem storage" do
rebuild_model :storage => :filesystem
@dummy = Dummy.new
assert @dummy.avatar.is_a?(Paperclip::Storage::Filesystem)
end
should "include the filesystem module even if capitalization is wrong" do
rebuild_model :storage => :FileSystem
@dummy = Dummy.new
assert @dummy.avatar.is_a?(Paperclip::Storage::Filesystem)
end
should "raise an error if you try to include a storage module that doesn't exist" do
rebuild_model :storage => :not_here
@dummy = Dummy.new
assert_raises(Paperclip::StorageMethodNotFound) do
@dummy.avatar
end
end
context "An attachment with styles but no processors defined" do
setup do
rebuild_model :processors => [], :styles => {:something => '1'}
@dummy = Dummy.new
@file = StringIO.new("...")
end
should "raise when assigned to" do
assert_raises(RuntimeError){ @dummy.avatar = @file }
end
end
context "An attachment without styles and with no processors defined" do
setup do
rebuild_model :processors => [], :styles => {}
@dummy = Dummy.new
@file = StringIO.new("...")
end
should "not raise when assigned to" do
@dummy.avatar = @file
end
end
context "Assigning an attachment with post_process hooks" do
setup do
rebuild_class :styles => { :something => "100x100#" }
Dummy.class_eval do
before_avatar_post_process :do_before_avatar
after_avatar_post_process :do_after_avatar
before_post_process :do_before_all
after_post_process :do_after_all
def do_before_avatar; end
def do_after_avatar; end
def do_before_all; end
def do_after_all; end
end
@file = StringIO.new(".")
@file.stubs(:to_tempfile).returns(@file)
@dummy = Dummy.new
Paperclip::Thumbnail.stubs(:make).returns(@file)
@attachment = @dummy.avatar
end
should "call the defined callbacks when assigned" do
@dummy.expects(:do_before_avatar).with()
@dummy.expects(:do_after_avatar).with()
@dummy.expects(:do_before_all).with()
@dummy.expects(:do_after_all).with()
Paperclip::Thumbnail.expects(:make).returns(@file)
@dummy.avatar = @file
end
should "not cancel the processing if a before_post_process returns nil" do
@dummy.expects(:do_before_avatar).with().returns(nil)
@dummy.expects(:do_after_avatar).with()
@dummy.expects(:do_before_all).with().returns(nil)
@dummy.expects(:do_after_all).with()
Paperclip::Thumbnail.expects(:make).returns(@file)
@dummy.avatar = @file
end
should "cancel the processing if a before_post_process returns false" do
@dummy.expects(:do_before_avatar).never
@dummy.expects(:do_after_avatar).never
@dummy.expects(:do_before_all).with().returns(false)
@dummy.expects(:do_after_all)
Paperclip::Thumbnail.expects(:make).never
@dummy.avatar = @file
end
should "cancel the processing if a before_avatar_post_process returns false" do
@dummy.expects(:do_before_avatar).with().returns(false)
@dummy.expects(:do_after_avatar)
@dummy.expects(:do_before_all).with().returns(true)
@dummy.expects(:do_after_all)
Paperclip::Thumbnail.expects(:make).never
@dummy.avatar = @file
end
end
context "Assigning an attachment" do
setup do
rebuild_model :styles => { :something => "100x100#" }
@file = StringIO.new(".")
@file.stubs(:original_filename).returns("5k.png\n\n")
@file.stubs(:content_type).returns("image/png\n\n")
@file.stubs(:to_tempfile).returns(@file)
@dummy = Dummy.new
Paperclip::Thumbnail.expects(:make).returns(@file)
@attachment = @dummy.avatar
@dummy.avatar = @file
end
should "strip whitespace from original_filename field" do
assert_equal "5k.png", @dummy.avatar.original_filename
end
should "strip whitespace from content_type field" do
assert_equal "image/png", @dummy.avatar.instance.avatar_content_type
end
end
context "Attachment with strange letters" do
setup do
rebuild_model
@not_file = mock
@tempfile = mock
@not_file.stubs(:nil?).returns(false)
@not_file.stubs(:fingerprint).returns('bd94545193321376b70136f8b223abf8')
@tempfile.stubs(:fingerprint).returns('bd94545193321376b70136f8b223abf8')
@not_file.expects(:size).returns(10)
@tempfile.expects(:size).returns(10)
@not_file.expects(:to_tempfile).returns(@tempfile)
@not_file.expects(:original_filename).returns("sheep_say_bæ.png\r\n")
@not_file.expects(:content_type).returns("image/png\r\n")
@dummy = Dummy.new
@attachment = @dummy.avatar
@attachment.expects(:valid_assignment?).with(@not_file).returns(true)
@attachment.expects(:queue_existing_for_delete)
@attachment.expects(:post_process)
@dummy.avatar = @not_file
end
should "not remove strange letters" do
assert_equal "sheep_say_bæ.png", @dummy.avatar.original_filename
end
end
context "An attachment" do
setup do
@old_defaults = Paperclip::Attachment.default_options.dup
Paperclip::Attachment.default_options.merge!({
:path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
})
FileUtils.rm_rf("tmp")
rebuild_model
@instance = Dummy.new
@instance.stubs(:id).returns 123
@attachment = Paperclip::Attachment.new(:avatar, @instance)
@file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
end
teardown do
@file.close
Paperclip::Attachment.default_options.merge!(@old_defaults)
end
should "raise if there are not the correct columns when you try to assign" do
@other_attachment = Paperclip::Attachment.new(:not_here, @instance)
assert_raises(Paperclip::PaperclipError) do
@other_attachment.assign(@file)
end
end
should "return its default_url when no file assigned" do
assert @attachment.to_file.nil?
assert_equal "/avatars/original/missing.png", @attachment.url
assert_equal "/avatars/blah/missing.png", @attachment.url(:blah)
end
should "return nil as path when no file assigned" do
assert @attachment.to_file.nil?
assert_equal nil, @attachment.path
assert_equal nil, @attachment.path(:blah)
end
context "with a file assigned in the database" do
setup do
@attachment.stubs(:instance_read).with(:file_name).returns("5k.png")
@attachment.stubs(:instance_read).with(:content_type).returns("image/png")
@attachment.stubs(:instance_read).with(:file_size).returns(12345)
dtnow = DateTime.now
@now = Time.now
Time.stubs(:now).returns(@now)
@attachment.stubs(:instance_read).with(:updated_at).returns(dtnow)
end
should "return a correct url even if the file does not exist" do
assert_nil @attachment.to_file
assert_match %r{^/system/avatars/#{@instance.id}/blah/5k\.png}, @attachment.url(:blah)
end
should "make sure the updated_at mtime is in the url if it is defined" do
assert_match %r{#{@now.to_i}$}, @attachment.url(:blah)
end
should "make sure the updated_at mtime is NOT in the url if false is passed to the url method" do
assert_no_match %r{#{@now.to_i}$}, @attachment.url(:blah, false)
end
context "with the updated_at field removed" do
setup do
@attachment.stubs(:instance_read).with(:updated_at).returns(nil)
end
should "only return the url without the updated_at when sent #url" do
assert_match "/avatars/#{@instance.id}/blah/5k.png", @attachment.url(:blah)
end
end
should "return the proper path when filename has a single .'s" do
assert_equal File.expand_path("./test/../tmp/avatars/dummies/original/#{@instance.id}/5k.png"), File.expand_path(@attachment.path)
end
should "return the proper path when filename has multiple .'s" do
@attachment.stubs(:instance_read).with(:file_name).returns("5k.old.png")
assert_equal File.expand_path("./test/../tmp/avatars/dummies/original/#{@instance.id}/5k.old.png"), File.expand_path(@attachment.path)
end
context "when expecting three styles" do
setup do
styles = {:styles => { :large => ["400x400", :png],
:medium => ["100x100", :gif],
:small => ["32x32#", :jpg]}}
@attachment = Paperclip::Attachment.new(:avatar,
@instance,
styles)
end
context "and assigned a file" do
setup do
now = Time.now
Time.stubs(:now).returns(now)
@attachment.assign(@file)
end
should "be dirty" do
assert @attachment.dirty?
end
context "and saved" do
setup do
@attachment.save
end
should "return the real url" do
file = @attachment.to_file
assert file
assert_match %r{^/system/avatars/#{@instance.id}/original/5k\.png}, @attachment.url
assert_match %r{^/system/avatars/#{@instance.id}/small/5k\.jpg}, @attachment.url(:small)
file.close
end
should "commit the files to disk" do
[:large, :medium, :small].each do |style|
io = @attachment.to_file(style)
# p "in commit to disk test, io is #{io.inspect} and @instance.id is #{@instance.id}"
assert File.exists?(io)
assert ! io.is_a?(::Tempfile)
io.close
end
end
should "save the files as the right formats and sizes" do
[[:large, 400, 61, "PNG"],
[:medium, 100, 15, "GIF"],
[:small, 32, 32, "JPEG"]].each do |style|
cmd = %Q[identify -format "%w %h %b %m" "#{@attachment.path(style.first)}"]
out = `#{cmd}`
width, height, size, format = out.split(" ")
assert_equal style[1].to_s, width.to_s
assert_equal style[2].to_s, height.to_s
assert_equal style[3].to_s, format.to_s
end
end
should "still have its #file attribute not be nil" do
assert ! (file = @attachment.to_file).nil?
file.close
end
context "and trying to delete" do
setup do
@existing_names = @attachment.styles.keys.collect do |style|
@attachment.path(style)
end
end
should "delete the files after assigning nil" do
@attachment.expects(:instance_write).with(:file_name, nil)
@attachment.expects(:instance_write).with(:content_type, nil)
@attachment.expects(:instance_write).with(:file_size, nil)
@attachment.expects(:instance_write).with(:updated_at, nil)
@attachment.assign nil
@attachment.save
@existing_names.each{|f| assert ! File.exists?(f) }
end
should "delete the files when you call #clear and #save" do
@attachment.expects(:instance_write).with(:file_name, nil)
@attachment.expects(:instance_write).with(:content_type, nil)
@attachment.expects(:instance_write).with(:file_size, nil)
@attachment.expects(:instance_write).with(:updated_at, nil)
@attachment.clear
@attachment.save
@existing_names.each{|f| assert ! File.exists?(f) }
end
should "delete the files when you call #delete" do
@attachment.expects(:instance_write).with(:file_name, nil)
@attachment.expects(:instance_write).with(:content_type, nil)
@attachment.expects(:instance_write).with(:file_size, nil)
@attachment.expects(:instance_write).with(:updated_at, nil)
@attachment.destroy
@existing_names.each{|f| assert ! File.exists?(f) }
end
end
end
end
end
end
context "when trying a nonexistant storage type" do
setup do
rebuild_model :storage => :not_here
end
should "not be able to find the module" do
assert_raise(Paperclip::StorageMethodNotFound){ Dummy.new.avatar }
end
end
end
context "An attachment with only a avatar_file_name column" do
setup do
ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
table.column :avatar_file_name, :string
end
rebuild_class
@dummy = Dummy.new
@file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
end
teardown { @file.close }
should "not error when assigned an attachment" do
assert_nothing_raised { @dummy.avatar = @file }
end
should "return the time when sent #avatar_updated_at" do
now = Time.now
Time.stubs(:now).returns(now)
@dummy.avatar = @file
assert now, @dummy.avatar.updated_at
end
should "return nil when reloaded and sent #avatar_updated_at" do
@dummy.save
@dummy.reload
assert_nil @dummy.avatar.updated_at
end
should "return the right value when sent #avatar_file_size" do
@dummy.avatar = @file
assert_equal @file.size, @dummy.avatar.size
end
context "and avatar_updated_at column" do
setup do
ActiveRecord::Base.connection.add_column :dummies, :avatar_updated_at, :timestamp
rebuild_class
@dummy = Dummy.new
end
should "not error when assigned an attachment" do
assert_nothing_raised { @dummy.avatar = @file }
end
should "return the right value when sent #avatar_updated_at" do
now = Time.now
Time.stubs(:now).returns(now)
@dummy.avatar = @file
assert_equal now.to_i, @dummy.avatar.updated_at
end
end
context "and avatar_content_type column" do
setup do
ActiveRecord::Base.connection.add_column :dummies, :avatar_content_type, :string
rebuild_class
@dummy = Dummy.new
end
should "not error when assigned an attachment" do
assert_nothing_raised { @dummy.avatar = @file }
end
should "return the right value when sent #avatar_content_type" do
@dummy.avatar = @file
assert_equal "image/png", @dummy.avatar.content_type
end
end
context "and avatar_file_size column" do
setup do
ActiveRecord::Base.connection.add_column :dummies, :avatar_file_size, :integer
rebuild_class
@dummy = Dummy.new
end
should "not error when assigned an attachment" do
assert_nothing_raised { @dummy.avatar = @file }
end
should "return the right value when sent #avatar_file_size" do
@dummy.avatar = @file
assert_equal @file.size, @dummy.avatar.size
end
should "return the right value when saved, reloaded, and sent #avatar_file_size" do
@dummy.avatar = @file
@dummy.save
@dummy = Dummy.find(@dummy.id)
assert_equal @file.size, @dummy.avatar.size
end
end
context "and avatar_fingerprint column" do
setup do
ActiveRecord::Base.connection.add_column :dummies, :avatar_fingerprint, :string
rebuild_class
@dummy = Dummy.new
end
should "not error when assigned an attachment" do
assert_nothing_raised { @dummy.avatar = @file }
end
should "return the right value when sent #avatar_fingerprint" do
@dummy.avatar = @file
assert_equal 'aec488126c3b33c08a10c3fa303acf27', @dummy.avatar_fingerprint
end
should "return the right value when saved, reloaded, and sent #avatar_fingerprint" do
@dummy.avatar = @file
@dummy.save
@dummy = Dummy.find(@dummy.id)
assert_equal 'aec488126c3b33c08a10c3fa303acf27', @dummy.avatar_fingerprint
end
end
end
end

View File

@ -1,133 +0,0 @@
require 'test/helper'
class CommandLineTest < Test::Unit::TestCase
def setup
Paperclip::CommandLine.path = nil
File.stubs(:exist?).with("/dev/null").returns(true)
end
should "take a command and parameters and produce a shell command for bash" do
cmd = Paperclip::CommandLine.new("convert", "a.jpg b.png", :swallow_stderr => false)
assert_equal "convert a.jpg b.png", cmd.command
end
should "be able to set a path and produce commands with that path" do
Paperclip::CommandLine.path = "/opt/bin"
cmd = Paperclip::CommandLine.new("convert", "a.jpg b.png", :swallow_stderr => false)
assert_equal "/opt/bin/convert a.jpg b.png", cmd.command
end
should "be able to interpolate quoted variables into the parameters" do
cmd = Paperclip::CommandLine.new("convert",
":one :{two}",
:one => "a.jpg",
:two => "b.png",
:swallow_stderr => false)
assert_equal "convert 'a.jpg' 'b.png'", cmd.command
end
should "quote command line options differently if we're on windows" do
File.stubs(:exist?).with("/dev/null").returns(false)
cmd = Paperclip::CommandLine.new("convert",
":one :{two}",
:one => "a.jpg",
:two => "b.png",
:swallow_stderr => false)
assert_equal 'convert "a.jpg" "b.png"', cmd.command
end
should "be able to quote and interpolate dangerous variables" do
cmd = Paperclip::CommandLine.new("convert",
":one :two",
:one => "`rm -rf`.jpg",
:two => "ha'ha.png",
:swallow_stderr => false)
assert_equal "convert '`rm -rf`.jpg' 'ha'\\''ha.png'", cmd.command
end
should "be able to quote and interpolate dangerous variables even on windows" do
File.stubs(:exist?).with("/dev/null").returns(false)
cmd = Paperclip::CommandLine.new("convert",
":one :two",
:one => "`rm -rf`.jpg",
:two => "ha'ha.png",
:swallow_stderr => false)
assert_equal %{convert "`rm -rf`.jpg" "ha'ha.png"}, cmd.command
end
should "add redirection to get rid of stderr in bash" do
File.stubs(:exist?).with("/dev/null").returns(true)
cmd = Paperclip::CommandLine.new("convert",
"a.jpg b.png",
:swallow_stderr => true)
assert_equal "convert a.jpg b.png 2>/dev/null", cmd.command
end
should "add redirection to get rid of stderr in cmd.exe" do
File.stubs(:exist?).with("/dev/null").returns(false)
cmd = Paperclip::CommandLine.new("convert",
"a.jpg b.png",
:swallow_stderr => true)
assert_equal "convert a.jpg b.png 2>NUL", cmd.command
end
should "raise if trying to interpolate :swallow_stderr or :expected_outcodes" do
cmd = Paperclip::CommandLine.new("convert",
":swallow_stderr :expected_outcodes",
:swallow_stderr => false,
:expected_outcodes => [0, 1])
assert_raise(Paperclip::PaperclipCommandLineError) do
cmd.command
end
end
should "run the #command it's given and return the output" do
cmd = Paperclip::CommandLine.new("convert", "a.jpg b.png", :swallow_stderr => false)
cmd.class.stubs(:"`").with("convert a.jpg b.png").returns(:correct_value)
with_exitstatus_returning(0) do
assert_equal :correct_value, cmd.run
end
end
should "raise a PaperclipCommandLineError if the result code isn't expected" do
cmd = Paperclip::CommandLine.new("convert", "a.jpg b.png", :swallow_stderr => false)
cmd.class.stubs(:"`").with("convert a.jpg b.png").returns(:correct_value)
with_exitstatus_returning(1) do
assert_raises(Paperclip::PaperclipCommandLineError) do
cmd.run
end
end
end
should "not raise a PaperclipCommandLineError if the result code is expected" do
cmd = Paperclip::CommandLine.new("convert",
"a.jpg b.png",
:expected_outcodes => [0, 1],
:swallow_stderr => false)
cmd.class.stubs(:"`").with("convert a.jpg b.png").returns(:correct_value)
with_exitstatus_returning(1) do
assert_nothing_raised do
cmd.run
end
end
end
should "log the command" do
cmd = Paperclip::CommandLine.new("convert", "a.jpg b.png", :swallow_stderr => false)
cmd.class.stubs(:'`')
Paperclip.expects(:log).with("convert a.jpg b.png")
cmd.run
end
should "detect that the system is unix or windows based on presence of /dev/null" do
File.stubs(:exist?).returns(true)
assert Paperclip::CommandLine.unix?
end
should "detect that the system is not unix or windows based on absence of /dev/null" do
File.stubs(:exist?).returns(false)
assert ! Paperclip::CommandLine.unix?
end
end

View File

@ -1,4 +0,0 @@
test:
adapter: sqlite3
database: ":memory:"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -1 +0,0 @@
This is not an image.

View File

@ -1,8 +0,0 @@
development:
key: 54321
production:
key: 12345
test:
bucket: <%= ENV['S3_BUCKET'] %>
access_key_id: <%= ENV['S3_KEY'] %>
secret_access_key: <%= ENV['S3_SECRET'] %>

View File

@ -1,177 +0,0 @@
require 'test/helper'
class GeometryTest < Test::Unit::TestCase
context "Paperclip::Geometry" do
should "correctly report its given dimensions" do
assert @geo = Paperclip::Geometry.new(1024, 768)
assert_equal 1024, @geo.width
assert_equal 768, @geo.height
end
should "set height to 0 if height dimension is missing" do
assert @geo = Paperclip::Geometry.new(1024)
assert_equal 1024, @geo.width
assert_equal 0, @geo.height
end
should "set width to 0 if width dimension is missing" do
assert @geo = Paperclip::Geometry.new(nil, 768)
assert_equal 0, @geo.width
assert_equal 768, @geo.height
end
should "be generated from a WxH-formatted string" do
assert @geo = Paperclip::Geometry.parse("800x600")
assert_equal 800, @geo.width
assert_equal 600, @geo.height
end
should "be generated from a xH-formatted string" do
assert @geo = Paperclip::Geometry.parse("x600")
assert_equal 0, @geo.width
assert_equal 600, @geo.height
end
should "be generated from a Wx-formatted string" do
assert @geo = Paperclip::Geometry.parse("800x")
assert_equal 800, @geo.width
assert_equal 0, @geo.height
end
should "be generated from a W-formatted string" do
assert @geo = Paperclip::Geometry.parse("800")
assert_equal 800, @geo.width
assert_equal 0, @geo.height
end
should "ensure the modifier is nil if not present" do
assert @geo = Paperclip::Geometry.parse("123x456")
assert_nil @geo.modifier
end
should "treat x and X the same in geometries" do
@lower = Paperclip::Geometry.parse("123x456")
@upper = Paperclip::Geometry.parse("123X456")
assert_equal 123, @lower.width
assert_equal 123, @upper.width
assert_equal 456, @lower.height
assert_equal 456, @upper.height
end
['>', '<', '#', '@', '%', '^', '!', nil].each do |mod|
should "ensure the modifier #{mod.inspect} is preserved" do
assert @geo = Paperclip::Geometry.parse("123x456#{mod}")
assert_equal mod, @geo.modifier
assert_equal "123x456#{mod}", @geo.to_s
end
end
['>', '<', '#', '@', '%', '^', '!', nil].each do |mod|
should "ensure the modifier #{mod.inspect} is preserved with no height" do
assert @geo = Paperclip::Geometry.parse("123x#{mod}")
assert_equal mod, @geo.modifier
assert_equal "123#{mod}", @geo.to_s
end
end
should "make sure the modifier gets passed during transformation_to" do
assert @src = Paperclip::Geometry.parse("123x456")
assert @dst = Paperclip::Geometry.parse("123x456>")
assert_equal ["123x456>", nil], @src.transformation_to(@dst)
end
should "generate correct ImageMagick formatting string for W-formatted string" do
assert @geo = Paperclip::Geometry.parse("800")
assert_equal "800", @geo.to_s
end
should "generate correct ImageMagick formatting string for Wx-formatted string" do
assert @geo = Paperclip::Geometry.parse("800x")
assert_equal "800", @geo.to_s
end
should "generate correct ImageMagick formatting string for xH-formatted string" do
assert @geo = Paperclip::Geometry.parse("x600")
assert_equal "x600", @geo.to_s
end
should "generate correct ImageMagick formatting string for WxH-formatted string" do
assert @geo = Paperclip::Geometry.parse("800x600")
assert_equal "800x600", @geo.to_s
end
should "be generated from a file" do
file = File.join(File.dirname(__FILE__), "fixtures", "5k.png")
file = File.new(file, 'rb')
assert_nothing_raised{ @geo = Paperclip::Geometry.from_file(file) }
assert @geo.height > 0
assert @geo.width > 0
end
should "be generated from a file path" do
file = File.join(File.dirname(__FILE__), "fixtures", "5k.png")
assert_nothing_raised{ @geo = Paperclip::Geometry.from_file(file) }
assert @geo.height > 0
assert @geo.width > 0
end
should "not generate from a bad file" do
file = "/home/This File Does Not Exist.omg"
assert_raise(Paperclip::NotIdentifiedByImageMagickError){ @geo = Paperclip::Geometry.from_file(file) }
end
[['vertical', 900, 1440, true, false, false, 1440, 900, 0.625],
['horizontal', 1024, 768, false, true, false, 1024, 768, 1.3333],
['square', 100, 100, false, false, true, 100, 100, 1]].each do |args|
context "performing calculations on a #{args[0]} viewport" do
setup do
@geo = Paperclip::Geometry.new(args[1], args[2])
end
should "#{args[3] ? "" : "not"} be vertical" do
assert_equal args[3], @geo.vertical?
end
should "#{args[4] ? "" : "not"} be horizontal" do
assert_equal args[4], @geo.horizontal?
end
should "#{args[5] ? "" : "not"} be square" do
assert_equal args[5], @geo.square?
end
should "report that #{args[6]} is the larger dimension" do
assert_equal args[6], @geo.larger
end
should "report that #{args[7]} is the smaller dimension" do
assert_equal args[7], @geo.smaller
end
should "have an aspect ratio of #{args[8]}" do
assert_in_delta args[8], @geo.aspect, 0.0001
end
end
end
[[ [1000, 100], [64, 64], "x64", "64x64+288+0" ],
[ [100, 1000], [50, 950], "x950", "50x950+22+0" ],
[ [100, 1000], [50, 25], "50x", "50x25+0+237" ]]. each do |args|
context "of #{args[0].inspect} and given a Geometry #{args[1].inspect} and sent transform_to" do
setup do
@geo = Paperclip::Geometry.new(*args[0])
@dst = Paperclip::Geometry.new(*args[1])
@scale, @crop = @geo.transformation_to @dst, true
end
should "be able to return the correct scaling transformation geometry #{args[2]}" do
assert_equal args[2], @scale
end
should "be able to return the correct crop transformation geometry #{args[3]}" do
assert_equal args[3], @crop
end
end
end
end
end

View File

@ -1,158 +0,0 @@
require 'rubygems'
require 'tempfile'
require 'test/unit'
require 'shoulda'
require 'mocha'
case ENV['RAILS_VERSION']
when '2.1' then
gem 'activerecord', '~>2.1.0'
gem 'activesupport', '~>2.1.0'
when '3.0' then
gem 'activerecord', '~>3.0.0'
gem 'activesupport', '~>3.0.0'
else
gem 'activerecord', '~>2.3.0'
gem 'activesupport', '~>2.3.0'
end
require 'active_record'
require 'active_record/version'
require 'active_support'
puts "Testing against version #{ActiveRecord::VERSION::STRING}"
`ruby -e 'exit 0'` # Prime $? with a value.
begin
require 'ruby-debug'
rescue LoadError => e
puts "debugger disabled"
end
ROOT = File.join(File.dirname(__FILE__), '..')
def silence_warnings
old_verbose, $VERBOSE = $VERBOSE, nil
yield
ensure
$VERBOSE = old_verbose
end
class Test::Unit::TestCase
def setup
silence_warnings do
Object.const_set(:Rails, stub('Rails', :root => ROOT, :env => 'test'))
end
end
end
$LOAD_PATH << File.join(ROOT, 'lib')
$LOAD_PATH << File.join(ROOT, 'lib', 'paperclip')
require File.join(ROOT, 'lib', 'paperclip.rb')
require 'shoulda_macros/paperclip'
FIXTURES_DIR = File.join(File.dirname(__FILE__), "fixtures")
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new(File.dirname(__FILE__) + "/debug.log")
ActiveRecord::Base.establish_connection(config['test'])
def reset_class class_name
ActiveRecord::Base.send(:include, Paperclip::Glue)
Object.send(:remove_const, class_name) rescue nil
klass = Object.const_set(class_name, Class.new(ActiveRecord::Base))
klass.class_eval{ include Paperclip::Glue }
klass
end
def reset_table table_name, &block
block ||= lambda { |table| true }
ActiveRecord::Base.connection.create_table :dummies, {:force => true}, &block
end
def modify_table table_name, &block
ActiveRecord::Base.connection.change_table :dummies, &block
end
def rebuild_model options = {}
ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
table.column :other, :string
table.column :avatar_file_name, :string
table.column :avatar_content_type, :string
table.column :avatar_file_size, :integer
table.column :avatar_updated_at, :datetime
table.column :avatar_fingerprint, :string
end
rebuild_class options
end
def rebuild_class options = {}
ActiveRecord::Base.send(:include, Paperclip::Glue)
Object.send(:remove_const, "Dummy") rescue nil
Object.const_set("Dummy", Class.new(ActiveRecord::Base))
Dummy.class_eval do
include Paperclip::Glue
has_attached_file :avatar, options
end
end
class FakeModel
attr_accessor :avatar_file_name,
:avatar_file_size,
:avatar_last_updated,
:avatar_content_type,
:avatar_fingerprint,
:id
def errors
@errors ||= []
end
def run_paperclip_callbacks name, *args
end
end
def attachment options
Paperclip::Attachment.new(:avatar, FakeModel.new, options)
end
def silence_warnings
old_verbose, $VERBOSE = $VERBOSE, nil
yield
ensure
$VERBOSE = old_verbose
end
def should_accept_dummy_class
should "accept the class" do
assert_accepts @matcher, @dummy_class
end
should "accept an instance of that class" do
assert_accepts @matcher, @dummy_class.new
end
end
def should_reject_dummy_class
should "reject the class" do
assert_rejects @matcher, @dummy_class
end
should "reject an instance of that class" do
assert_rejects @matcher, @dummy_class.new
end
end
def with_exitstatus_returning(code)
saved_exitstatus = $?.nil? ? 0 : $?.exitstatus
begin
`ruby -e 'exit #{code.to_i}'`
yield
ensure
`ruby -e 'exit #{saved_exitstatus.to_i}'`
end
end

View File

@ -1,482 +0,0 @@
require 'test/helper'
class IntegrationTest < Test::Unit::TestCase
context "Many models at once" do
setup do
rebuild_model
@file = File.new(File.join(FIXTURES_DIR, "5k.png"), 'rb')
300.times do |i|
Dummy.create! :avatar => @file
end
end
should "not exceed the open file limit" do
assert_nothing_raised do
dummies = Dummy.find(:all)
dummies.each { |dummy| dummy.avatar }
end
end
end
context "An attachment" do
setup do
rebuild_model :styles => { :thumb => "50x50#" }
@dummy = Dummy.new
@file = File.new(File.join(File.dirname(__FILE__),
"fixtures",
"5k.png"), 'rb')
@dummy.avatar = @file
assert @dummy.save
end
teardown { @file.close }
should "create its thumbnails properly" do
assert_match /\b50x50\b/, `identify "#{@dummy.avatar.path(:thumb)}"`
end
context "redefining its attachment styles" do
setup do
Dummy.class_eval do
has_attached_file :avatar, :styles => { :thumb => "150x25#" }
has_attached_file :avatar, :styles => { :thumb => "150x25#", :dynamic => lambda { |a| '50x50#' } }
end
@d2 = Dummy.find(@dummy.id)
@d2.avatar.reprocess!
@d2.save
end
should "create its thumbnails properly" do
assert_match /\b150x25\b/, `identify "#{@dummy.avatar.path(:thumb)}"`
assert_match /\b50x50\b/, `identify "#{@dummy.avatar.path(:dynamic)}"`
end
end
end
context "A model that modifies its original" do
setup do
rebuild_model :styles => { :original => "2x2#" }
@dummy = Dummy.new
@file = File.new(File.join(File.dirname(__FILE__),
"fixtures",
"5k.png"), 'rb')
@dummy.avatar = @file
end
should "report the file size of the processed file and not the original" do
assert_not_equal @file.size, @dummy.avatar.size
end
teardown { @file.close }
end
context "A model with attachments scoped under an id" do
setup do
rebuild_model :styles => { :large => "100x100",
:medium => "50x50" },
:path => ":rails_root/tmp/:id/:attachments/:style.:extension"
@dummy = Dummy.new
@file = File.new(File.join(File.dirname(__FILE__),
"fixtures",
"5k.png"), 'rb')
@dummy.avatar = @file
end
teardown { @file.close }
context "when saved" do
setup do
@dummy.save
@saved_path = @dummy.avatar.path(:large)
end
should "have a large file in the right place" do
assert File.exists?(@dummy.avatar.path(:large))
end
context "and deleted" do
setup do
@dummy.avatar.clear
@dummy.save
end
should "not have a large file in the right place anymore" do
assert ! File.exists?(@saved_path)
end
should "not have its next two parent directories" do
assert ! File.exists?(File.dirname(@saved_path))
assert ! File.exists?(File.dirname(File.dirname(@saved_path)))
end
before_should "not die if an unexpected SystemCallError happens" do
FileUtils.stubs(:rmdir).raises(Errno::EPIPE)
end
end
end
end
context "A model with no attachment validation" do
setup do
rebuild_model :styles => { :large => "300x300>",
:medium => "100x100",
:thumb => ["32x32#", :gif] },
:default_style => :medium,
:url => "/:attachment/:class/:style/:id/:basename.:extension",
:path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
@dummy = Dummy.new
end
should "have its definition return false when asked about whiny_thumbnails" do
assert ! Dummy.attachment_definitions[:avatar][:whiny_thumbnails]
end
context "when validates_attachment_thumbnails is called" do
setup do
Dummy.validates_attachment_thumbnails :avatar
end
should "have its definition return true when asked about whiny_thumbnails" do
assert_equal true, Dummy.attachment_definitions[:avatar][:whiny_thumbnails]
end
end
context "redefined to have attachment validations" do
setup do
rebuild_model :styles => { :large => "300x300>",
:medium => "100x100",
:thumb => ["32x32#", :gif] },
:whiny_thumbnails => true,
:default_style => :medium,
:url => "/:attachment/:class/:style/:id/:basename.:extension",
:path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
end
should "have its definition return true when asked about whiny_thumbnails" do
assert_equal true, Dummy.attachment_definitions[:avatar][:whiny_thumbnails]
end
end
end
context "A model with no convert_options setting" do
setup do
rebuild_model :styles => { :large => "300x300>",
:medium => "100x100",
:thumb => ["32x32#", :gif] },
:default_style => :medium,
:url => "/:attachment/:class/:style/:id/:basename.:extension",
:path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
@dummy = Dummy.new
end
should "have its definition return nil when asked about convert_options" do
assert ! Dummy.attachment_definitions[:avatar][:convert_options]
end
context "redefined to have convert_options setting" do
setup do
rebuild_model :styles => { :large => "300x300>",
:medium => "100x100",
:thumb => ["32x32#", :gif] },
:convert_options => "-strip -depth 8",
:default_style => :medium,
:url => "/:attachment/:class/:style/:id/:basename.:extension",
:path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
end
should "have its definition return convert_options value when asked about convert_options" do
assert_equal "-strip -depth 8", Dummy.attachment_definitions[:avatar][:convert_options]
end
end
end
context "A model with a filesystem attachment" do
setup do
rebuild_model :styles => { :large => "300x300>",
:medium => "100x100",
:thumb => ["32x32#", :gif] },
:whiny_thumbnails => true,
:default_style => :medium,
:url => "/:attachment/:class/:style/:id/:basename.:extension",
:path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
@dummy = Dummy.new
@file = File.new(File.join(FIXTURES_DIR, "5k.png"), 'rb')
@bad_file = File.new(File.join(FIXTURES_DIR, "bad.png"), 'rb')
assert @dummy.avatar = @file
assert @dummy.valid?, @dummy.errors.full_messages.join(", ")
assert @dummy.save
end
should "write and delete its files" do
[["434x66", :original],
["300x46", :large],
["100x15", :medium],
["32x32", :thumb]].each do |geo, style|
cmd = %Q[identify -format "%wx%h" "#{@dummy.avatar.path(style)}"]
assert_equal geo, `#{cmd}`.chomp, cmd
end
saved_paths = [:thumb, :medium, :large, :original].collect{|s| @dummy.avatar.path(s) }
@d2 = Dummy.find(@dummy.id)
assert_equal "100x15", `identify -format "%wx%h" "#{@d2.avatar.path}"`.chomp
assert_equal "434x66", `identify -format "%wx%h" "#{@d2.avatar.path(:original)}"`.chomp
assert_equal "300x46", `identify -format "%wx%h" "#{@d2.avatar.path(:large)}"`.chomp
assert_equal "100x15", `identify -format "%wx%h" "#{@d2.avatar.path(:medium)}"`.chomp
assert_equal "32x32", `identify -format "%wx%h" "#{@d2.avatar.path(:thumb)}"`.chomp
@dummy.avatar = "not a valid file but not nil"
assert_equal File.basename(@file.path), @dummy.avatar_file_name
assert @dummy.valid?
assert @dummy.save
saved_paths.each do |p|
assert File.exists?(p)
end
@dummy.avatar.clear
assert_nil @dummy.avatar_file_name
assert @dummy.valid?
assert @dummy.save
saved_paths.each do |p|
assert ! File.exists?(p)
end
@d2 = Dummy.find(@dummy.id)
assert_nil @d2.avatar_file_name
end
should "work exactly the same when new as when reloaded" do
@d2 = Dummy.find(@dummy.id)
assert_equal @dummy.avatar_file_name, @d2.avatar_file_name
[:thumb, :medium, :large, :original].each do |style|
assert_equal @dummy.avatar.path(style), @d2.avatar.path(style)
end
saved_paths = [:thumb, :medium, :large, :original].collect{|s| @dummy.avatar.path(s) }
@d2.avatar.clear
assert @d2.save
saved_paths.each do |p|
assert ! File.exists?(p)
end
end
should "know the difference between good files, bad files, and not files" do
expected = @dummy.avatar.to_file
@dummy.avatar = "not a file"
assert @dummy.valid?
assert_equal expected.path, @dummy.avatar.path
expected.close
@dummy.avatar = @bad_file
assert ! @dummy.valid?
end
should "know the difference between good files, bad files, and not files when validating" do
Dummy.validates_attachment_presence :avatar
@d2 = Dummy.find(@dummy.id)
@d2.avatar = @file
assert @d2.valid?, @d2.errors.full_messages.inspect
@d2.avatar = @bad_file
assert ! @d2.valid?
end
should "be able to reload without saving and not have the file disappear" do
@dummy.avatar = @file
assert @dummy.save
@dummy.avatar.clear
assert_nil @dummy.avatar_file_name
@dummy.reload
assert_equal "5k.png", @dummy.avatar_file_name
end
context "that is assigned its file from another Paperclip attachment" do
setup do
@dummy2 = Dummy.new
@file2 = File.new(File.join(FIXTURES_DIR, "12k.png"), 'rb')
assert @dummy2.avatar = @file2
@dummy2.save
end
should "work when assigned a file" do
assert_not_equal `identify -format "%wx%h" "#{@dummy.avatar.path(:original)}"`,
`identify -format "%wx%h" "#{@dummy2.avatar.path(:original)}"`
assert @dummy.avatar = @dummy2.avatar
@dummy.save
assert_equal `identify -format "%wx%h" "#{@dummy.avatar.path(:original)}"`,
`identify -format "%wx%h" "#{@dummy2.avatar.path(:original)}"`
end
end
end
context "A model with an attachments association and a Paperclip attachment" do
setup do
Dummy.class_eval do
has_many :attachments, :class_name => 'Dummy'
end
@dummy = Dummy.new
@dummy.avatar = File.new(File.join(File.dirname(__FILE__),
"fixtures",
"5k.png"), 'rb')
end
should "should not error when saving" do
assert_nothing_raised do
@dummy.save!
end
end
end
if ENV['S3_TEST_BUCKET']
def s3_files_for attachment
[:thumb, :medium, :large, :original].inject({}) do |files, style|
data = `curl "#{attachment.url(style)}" 2>/dev/null`.chomp
t = Tempfile.new("paperclip-test")
t.binmode
t.write(data)
t.rewind
files[style] = t
files
end
end
def s3_headers_for attachment, style
`curl --head "#{attachment.url(style)}" 2>/dev/null`.split("\n").inject({}) do |h,head|
split_head = head.chomp.split(/\s*:\s*/, 2)
h[split_head.first.downcase] = split_head.last unless split_head.empty?
h
end
end
context "A model with an S3 attachment" do
setup do
rebuild_model :styles => { :large => "300x300>",
:medium => "100x100",
:thumb => ["32x32#", :gif] },
:storage => :s3,
:whiny_thumbnails => true,
:s3_credentials => File.new(File.join(File.dirname(__FILE__), "s3.yml")),
:default_style => :medium,
:bucket => ENV['S3_TEST_BUCKET'],
:path => ":class/:attachment/:id/:style/:basename.:extension"
@dummy = Dummy.new
@file = File.new(File.join(FIXTURES_DIR, "5k.png"), 'rb')
@bad_file = File.new(File.join(FIXTURES_DIR, "bad.png"), 'rb')
assert @dummy.avatar = @file
assert @dummy.valid?
assert @dummy.save
@files_on_s3 = s3_files_for @dummy.avatar
end
should "have the same contents as the original" do
@file.rewind
assert_equal @file.read, @files_on_s3[:original].read
end
should "write and delete its files" do
[["434x66", :original],
["300x46", :large],
["100x15", :medium],
["32x32", :thumb]].each do |geo, style|
cmd = %Q[identify -format "%wx%h" "#{@files_on_s3[style].path}"]
assert_equal geo, `#{cmd}`.chomp, cmd
end
@d2 = Dummy.find(@dummy.id)
@d2_files = s3_files_for @d2.avatar
[["434x66", :original],
["300x46", :large],
["100x15", :medium],
["32x32", :thumb]].each do |geo, style|
cmd = %Q[identify -format "%wx%h" "#{@d2_files[style].path}"]
assert_equal geo, `#{cmd}`.chomp, cmd
end
@dummy.avatar = "not a valid file but not nil"
assert_equal File.basename(@file.path), @dummy.avatar_file_name
assert @dummy.valid?
assert @dummy.save
[:thumb, :medium, :large, :original].each do |style|
assert @dummy.avatar.exists?(style)
end
@dummy.avatar.clear
assert_nil @dummy.avatar_file_name
assert @dummy.valid?
assert @dummy.save
[:thumb, :medium, :large, :original].each do |style|
assert ! @dummy.avatar.exists?(style)
end
@d2 = Dummy.find(@dummy.id)
assert_nil @d2.avatar_file_name
end
should "work exactly the same when new as when reloaded" do
@d2 = Dummy.find(@dummy.id)
assert_equal @dummy.avatar_file_name, @d2.avatar_file_name
[:thumb, :medium, :large, :original].each do |style|
assert_equal @dummy.avatar.to_file(style).read, @d2.avatar.to_file(style).read
end
saved_keys = [:thumb, :medium, :large, :original].collect{|s| @dummy.avatar.to_file(s) }
@d2.avatar.clear
assert @d2.save
[:thumb, :medium, :large, :original].each do |style|
assert ! @dummy.avatar.exists?(style)
end
end
should "know the difference between good files, bad files, not files, and nil" do
expected = @dummy.avatar.to_file
@dummy.avatar = "not a file"
assert @dummy.valid?
assert_equal expected.read, @dummy.avatar.to_file.read
@dummy.avatar = @bad_file
assert ! @dummy.valid?
@dummy.avatar = nil
assert @dummy.valid?
Dummy.validates_attachment_presence :avatar
@d2 = Dummy.find(@dummy.id)
@d2.avatar = @file
assert @d2.valid?
@d2.avatar = @bad_file
assert ! @d2.valid?
@d2.avatar = nil
assert ! @d2.valid?
end
should "be able to reload without saving and not have the file disappear" do
@dummy.avatar = @file
assert @dummy.save
@dummy.avatar = nil
assert_nil @dummy.avatar_file_name
@dummy.reload
assert_equal "5k.png", @dummy.avatar_file_name
end
should "have the right content type" do
headers = s3_headers_for(@dummy.avatar, :original)
assert_equal 'image/png', headers['content-type']
end
end
end
end

View File

@ -1,127 +0,0 @@
require 'test/helper'
class InterpolationsTest < Test::Unit::TestCase
should "return all methods but the infrastructure when sent #all" do
methods = Paperclip::Interpolations.all
assert ! methods.include?(:[])
assert ! methods.include?(:[]=)
assert ! methods.include?(:all)
methods.each do |m|
assert Paperclip::Interpolations.respond_to?(m)
end
end
should "return the Rails.root" do
assert_equal Rails.root, Paperclip::Interpolations.rails_root(:attachment, :style)
end
should "return the Rails.env" do
assert_equal Rails.env, Paperclip::Interpolations.rails_env(:attachment, :style)
end
should "return the class of the Interpolations module when called with no params" do
assert_equal Module, Paperclip::Interpolations.class
end
should "return the class of the instance" do
attachment = mock
attachment.expects(:instance).returns(attachment)
attachment.expects(:class).returns("Thing")
assert_equal "things", Paperclip::Interpolations.class(attachment, :style)
end
should "return the basename of the file" do
attachment = mock
attachment.expects(:original_filename).returns("one.jpg").times(2)
assert_equal "one", Paperclip::Interpolations.basename(attachment, :style)
end
should "return the extension of the file" do
attachment = mock
attachment.expects(:original_filename).returns("one.jpg")
attachment.expects(:styles).returns({})
assert_equal "jpg", Paperclip::Interpolations.extension(attachment, :style)
end
should "return the extension of the file as the format if defined in the style" do
attachment = mock
attachment.expects(:original_filename).never
attachment.expects(:styles).returns({:style => {:format => "png"}})
assert_equal "png", Paperclip::Interpolations.extension(attachment, :style)
end
should "return the id of the attachment" do
attachment = mock
attachment.expects(:id).returns(23)
attachment.expects(:instance).returns(attachment)
assert_equal 23, Paperclip::Interpolations.id(attachment, :style)
end
should "return the partitioned id of the attachment" do
attachment = mock
attachment.expects(:id).returns(23)
attachment.expects(:instance).returns(attachment)
assert_equal "000/000/023", Paperclip::Interpolations.id_partition(attachment, :style)
end
should "return the name of the attachment" do
attachment = mock
attachment.expects(:name).returns("file")
assert_equal "files", Paperclip::Interpolations.attachment(attachment, :style)
end
should "return the style" do
assert_equal :style, Paperclip::Interpolations.style(:attachment, :style)
end
should "return the default style" do
attachment = mock
attachment.expects(:default_style).returns(:default_style)
assert_equal :default_style, Paperclip::Interpolations.style(attachment, nil)
end
should "reinterpolate :url" do
attachment = mock
attachment.expects(:url).with(:style, false).returns("1234")
assert_equal "1234", Paperclip::Interpolations.url(attachment, :style)
end
should "raise if infinite loop detcted reinterpolating :url" do
attachment = Object.new
class << attachment
def url(*args)
Paperclip::Interpolations.url(self, :style)
end
end
assert_raises(Paperclip::InfiniteInterpolationError){ Paperclip::Interpolations.url(attachment, :style) }
end
should "return the filename as basename.extension" do
attachment = mock
attachment.expects(:styles).returns({})
attachment.expects(:original_filename).returns("one.jpg").times(3)
assert_equal "one.jpg", Paperclip::Interpolations.filename(attachment, :style)
end
should "return the filename as basename.extension when format supplied" do
attachment = mock
attachment.expects(:styles).returns({:style => {:format => :png}})
attachment.expects(:original_filename).returns("one.jpg").times(2)
assert_equal "one.png", Paperclip::Interpolations.filename(attachment, :style)
end
should "return the timestamp" do
now = Time.now
attachment = mock
attachment.expects(:instance_read).with(:updated_at).returns(now)
assert_equal now.to_s, Paperclip::Interpolations.timestamp(attachment, :style)
end
should "call all expected interpolations with the given arguments" do
Paperclip::Interpolations.expects(:id).with(:attachment, :style).returns(1234)
Paperclip::Interpolations.expects(:attachment).with(:attachment, :style).returns("attachments")
Paperclip::Interpolations.expects(:notreal).never
value = Paperclip::Interpolations.interpolate(":notreal/:id/:attachment", :attachment, :style)
assert_equal ":notreal/1234/attachments", value
end
end

View File

@ -1,78 +0,0 @@
require 'test/helper'
class IOStreamTest < Test::Unit::TestCase
context "IOStream" do
should "be included in IO, File, Tempfile, and StringIO" do
[IO, File, Tempfile, StringIO].each do |klass|
assert klass.included_modules.include?(IOStream), "Not in #{klass}"
end
end
end
context "A file" do
setup do
@file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
end
teardown { @file.close }
context "that is sent #stream_to" do
context "and given a String" do
setup do
FileUtils.mkdir_p(File.join(ROOT, 'tmp'))
assert @result = @file.stream_to(File.join(ROOT, 'tmp', 'iostream.string.test'))
end
should "return a File" do
assert @result.is_a?(File)
end
should "contain the same data as the original file" do
@file.rewind; @result.rewind
assert_equal @file.read, @result.read
end
end
context "and given a Tempfile" do
setup do
tempfile = Tempfile.new('iostream.test')
tempfile.binmode
assert @result = @file.stream_to(tempfile)
end
should "return a Tempfile" do
assert @result.is_a?(Tempfile)
end
should "contain the same data as the original file" do
@file.rewind; @result.rewind
assert_equal @file.read, @result.read
end
end
end
context "that is sent #to_tempfile" do
setup do
assert @tempfile = @file.to_tempfile
end
should "convert it to a Paperclip Tempfile" do
assert @tempfile.is_a?(Paperclip::Tempfile)
end
should "have the name be based on the original_filename" do
name = File.basename(@file.path)
extension = File.extname(name)
basename = File.basename(name, extension)
assert_match %r[^stream.*?#{Regexp.quote(extension)}], File.basename(@tempfile.path)
end
should "have the Tempfile contain the same data as the file" do
@file.rewind; @tempfile.rewind
assert_equal @file.read, @tempfile.read
end
end
end
end

View File

@ -1,24 +0,0 @@
require 'test/helper'
class HaveAttachedFileMatcherTest < Test::Unit::TestCase
context "have_attached_file" do
setup do
@dummy_class = reset_class "Dummy"
reset_table "dummies"
@matcher = self.class.have_attached_file(:avatar)
end
context "given a class with no attachment" do
should_reject_dummy_class
end
context "given a class with an attachment" do
setup do
modify_table("dummies"){|d| d.string :avatar_file_name }
@dummy_class.has_attached_file :avatar
end
should_accept_dummy_class
end
end
end

View File

@ -1,47 +0,0 @@
require 'test/helper'
class ValidateAttachmentContentTypeMatcherTest < Test::Unit::TestCase
context "validate_attachment_content_type" do
setup do
reset_table("dummies") do |d|
d.string :title
d.string :avatar_file_name
d.string :avatar_content_type
end
@dummy_class = reset_class "Dummy"
@dummy_class.has_attached_file :avatar
@matcher = self.class.validate_attachment_content_type(:avatar).
allowing(%w(image/png image/jpeg)).
rejecting(%w(audio/mp3 application/octet-stream))
end
context "given a class with no validation" do
should_reject_dummy_class
end
context "given a class with a validation that doesn't match" do
setup do
@dummy_class.validates_attachment_content_type :avatar, :content_type => %r{audio/.*}
end
should_reject_dummy_class
end
context "given a class with a matching validation" do
setup do
@dummy_class.validates_attachment_content_type :avatar, :content_type => %r{image/.*}
end
should_accept_dummy_class
end
context "given a class with other validations but matching types" do
setup do
@dummy_class.validates_presence_of :title
@dummy_class.validates_attachment_content_type :avatar, :content_type => %r{image/.*}
end
should_accept_dummy_class
end
end
end

View File

@ -1,26 +0,0 @@
require 'test/helper'
class ValidateAttachmentPresenceMatcherTest < Test::Unit::TestCase
context "validate_attachment_presence" do
setup do
reset_table("dummies") do |d|
d.string :avatar_file_name
end
@dummy_class = reset_class "Dummy"
@dummy_class.has_attached_file :avatar
@matcher = self.class.validate_attachment_presence(:avatar)
end
context "given a class with no validation" do
should_reject_dummy_class
end
context "given a class with a matching validation" do
setup do
@dummy_class.validates_attachment_presence :avatar
end
should_accept_dummy_class
end
end
end

View File

@ -1,51 +0,0 @@
require 'test/helper'
class ValidateAttachmentSizeMatcherTest < Test::Unit::TestCase
context "validate_attachment_size" do
setup do
reset_table("dummies") do |d|
d.string :avatar_file_name
d.integer :avatar_file_size
end
@dummy_class = reset_class "Dummy"
@dummy_class.has_attached_file :avatar
end
context "of limited size" do
setup{ @matcher = self.class.validate_attachment_size(:avatar).in(256..1024) }
context "given a class with no validation" do
should_reject_dummy_class
end
context "given a class with a validation that's too high" do
setup { @dummy_class.validates_attachment_size :avatar, :in => 256..2048 }
should_reject_dummy_class
end
context "given a class with a validation that's too low" do
setup { @dummy_class.validates_attachment_size :avatar, :in => 0..1024 }
should_reject_dummy_class
end
context "given a class with a validation that matches" do
setup { @dummy_class.validates_attachment_size :avatar, :in => 256..1024 }
should_accept_dummy_class
end
end
context "validates_attachment_size with infinite range" do
setup{ @matcher = self.class.validate_attachment_size(:avatar) }
context "given a class with an upper limit" do
setup { @dummy_class.validates_attachment_size :avatar, :less_than => 1 }
should_accept_dummy_class
end
context "given a class with no upper limit" do
setup { @dummy_class.validates_attachment_size :avatar, :greater_than => 1 }
should_accept_dummy_class
end
end
end
end

View File

@ -1,254 +0,0 @@
require 'test/helper'
class PaperclipTest < Test::Unit::TestCase
context "Calling Paperclip.run" do
setup do
Paperclip.options[:image_magick_path] = nil
Paperclip.options[:command_path] = nil
Paperclip::CommandLine.stubs(:'`')
end
should "execute the right command with :image_magick_path" do
Paperclip.options[:image_magick_path] = "/usr/bin"
Paperclip.expects(:log).with(includes('[DEPRECATION]'))
Paperclip.expects(:log).with(regexp_matches(%r{/usr/bin/convert ['"]one.jpg['"] ['"]two.jpg['"]}))
Paperclip::CommandLine.expects(:"`").with(regexp_matches(%r{/usr/bin/convert ['"]one.jpg['"] ['"]two.jpg['"]}))
Paperclip.run("convert", ":one :two", :one => "one.jpg", :two => "two.jpg")
end
should "execute the right command with :command_path" do
Paperclip.options[:command_path] = "/usr/bin"
Paperclip::CommandLine.expects(:"`").with(regexp_matches(%r{/usr/bin/convert ['"]one.jpg['"] ['"]two.jpg['"]}))
Paperclip.run("convert", ":one :two", :one => "one.jpg", :two => "two.jpg")
end
should "execute the right command with no path" do
Paperclip::CommandLine.expects(:"`").with(regexp_matches(%r{convert ['"]one.jpg['"] ['"]two.jpg['"]}))
Paperclip.run("convert", ":one :two", :one => "one.jpg", :two => "two.jpg")
end
should "tell you the command isn't there if the shell returns 127" do
with_exitstatus_returning(127) do
assert_raises(Paperclip::CommandNotFoundError) do
Paperclip.run("command")
end
end
end
should "tell you the command isn't there if an ENOENT is raised" do
assert_raises(Paperclip::CommandNotFoundError) do
Paperclip::CommandLine.stubs(:"`").raises(Errno::ENOENT)
Paperclip.run("command")
end
end
end
should "raise when sent #processor and the name of a class that exists but isn't a subclass of Processor" do
assert_raises(Paperclip::PaperclipError){ Paperclip.processor(:attachment) }
end
should "raise when sent #processor and the name of a class that doesn't exist" do
assert_raises(NameError){ Paperclip.processor(:boogey_man) }
end
should "return a class when sent #processor and the name of a class under Paperclip" do
assert_equal ::Paperclip::Thumbnail, Paperclip.processor(:thumbnail)
end
context "An ActiveRecord model with an 'avatar' attachment" do
setup do
rebuild_model :path => "tmp/:class/omg/:style.:extension"
@file = File.new(File.join(FIXTURES_DIR, "5k.png"), 'rb')
end
teardown { @file.close }
should "not error when trying to also create a 'blah' attachment" do
assert_nothing_raised do
Dummy.class_eval do
has_attached_file :blah
end
end
end
context "that is attr_protected" do
setup do
Dummy.class_eval do
attr_protected :avatar
end
@dummy = Dummy.new
end
should "not assign the avatar on mass-set" do
@dummy.attributes = { :other => "I'm set!",
:avatar => @file }
assert_equal "I'm set!", @dummy.other
assert ! @dummy.avatar?
end
should "still allow assigment on normal set" do
@dummy.other = "I'm set!"
@dummy.avatar = @file
assert_equal "I'm set!", @dummy.other
assert @dummy.avatar?
end
end
context "with a subclass" do
setup do
class ::SubDummy < Dummy; end
end
should "be able to use the attachment from the subclass" do
assert_nothing_raised do
@subdummy = SubDummy.create(:avatar => @file)
end
end
should "be able to see the attachment definition from the subclass's class" do
assert_equal "tmp/:class/omg/:style.:extension",
SubDummy.attachment_definitions[:avatar][:path]
end
teardown do
Object.send(:remove_const, "SubDummy") rescue nil
end
end
should "have an #avatar method" do
assert Dummy.new.respond_to?(:avatar)
end
should "have an #avatar= method" do
assert Dummy.new.respond_to?(:avatar=)
end
context "that is valid" do
setup do
@dummy = Dummy.new
@dummy.avatar = @file
end
should "be valid" do
assert @dummy.valid?
end
end
context "a validation with an if guard clause" do
setup do
Dummy.send(:"validates_attachment_presence", :avatar, :if => lambda{|i| i.foo })
@dummy = Dummy.new
@dummy.stubs(:avatar_file_name).returns(nil)
end
should "attempt validation if the guard returns true" do
@dummy.expects(:foo).returns(true)
assert ! @dummy.valid?
end
should "not attempt validation if the guard returns false" do
@dummy.expects(:foo).returns(false)
assert @dummy.valid?
end
end
context "a validation with an unless guard clause" do
setup do
Dummy.send(:"validates_attachment_presence", :avatar, :unless => lambda{|i| i.foo })
@dummy = Dummy.new
@dummy.stubs(:avatar_file_name).returns(nil)
end
should "attempt validation if the guard returns true" do
@dummy.expects(:foo).returns(false)
assert ! @dummy.valid?
end
should "not attempt validation if the guard returns false" do
@dummy.expects(:foo).returns(true)
assert @dummy.valid?
end
end
should "not have Attachment in the ActiveRecord::Base namespace" do
assert_raises(NameError) do
ActiveRecord::Base::Attachment
end
end
def self.should_validate validation, options, valid_file, invalid_file
context "with #{validation} validation and #{options.inspect} options" do
setup do
rebuild_class
Dummy.send(:"validates_attachment_#{validation}", :avatar, options)
@dummy = Dummy.new
end
context "and assigning nil" do
setup do
@dummy.avatar = nil
@dummy.valid?
end
if validation == :presence
should "have an error on the attachment" do
assert @dummy.errors[:avatar_file_name]
end
else
should "not have an error on the attachment" do
assert @dummy.errors.blank?, @dummy.errors.full_messages.join(", ")
end
end
end
context "and assigned a valid file" do
setup do
@dummy.avatar = valid_file
@dummy.valid?
end
should "not have an error when assigned a valid file" do
assert_equal 0, @dummy.errors.length, @dummy.errors.full_messages.join(", ")
end
end
context "and assigned an invalid file" do
setup do
@dummy.avatar = invalid_file
@dummy.valid?
end
should "have an error when assigned a valid file" do
assert @dummy.errors.length > 0
end
end
end
end
[[:presence, {}, "5k.png", nil],
[:size, {:in => 1..10240}, "5k.png", "12k.png"],
[:size, {:less_than => 10240}, "5k.png", "12k.png"],
[:size, {:greater_than => 8096}, "12k.png", "5k.png"],
[:content_type, {:content_type => "image/png"}, "5k.png", "text.txt"],
[:content_type, {:content_type => "text/plain"}, "text.txt", "5k.png"],
[:content_type, {:content_type => %r{image/.*}}, "5k.png", "text.txt"]].each do |args|
validation, options, valid_file, invalid_file = args
valid_file &&= File.open(File.join(FIXTURES_DIR, valid_file), "rb")
invalid_file &&= File.open(File.join(FIXTURES_DIR, invalid_file), "rb")
should_validate validation, options, valid_file, invalid_file
end
context "with size validation and less_than 10240 option" do
context "and assigned an invalid file" do
setup do
Dummy.send(:"validates_attachment_size", :avatar, :less_than => 10240)
@dummy = Dummy.new
@dummy.avatar &&= File.open(File.join(FIXTURES_DIR, "12k.png"), "rb")
@dummy.valid?
end
should "have a file size min/max error message" do
assert [@dummy.errors[:avatar_file_size]].flatten.any?{|error| error =~ %r/between 0 and 10240 bytes/ }
end
end
end
end
end

View File

@ -1,10 +0,0 @@
require 'test/helper'
class ProcessorTest < Test::Unit::TestCase
should "instantiate and call #make when sent #make to the class" do
processor = mock
processor.expects(:make).with()
Paperclip::Processor.expects(:new).with(:one, :two, :three).returns(processor)
Paperclip::Processor.make(:one, :two, :three)
end
end

View File

@ -1,358 +0,0 @@
require 'test/helper'
require 'aws/s3'
class StorageTest < Test::Unit::TestCase
def rails_env(env)
silence_warnings do
Object.const_set(:Rails, stub('Rails', :env => env))
end
end
context "Parsing S3 credentials" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:bucket => "testing",
:s3_credentials => {:not => :important}
@dummy = Dummy.new
@avatar = @dummy.avatar
end
should "get the correct credentials when RAILS_ENV is production" do
rails_env("production")
assert_equal({:key => "12345"},
@avatar.parse_credentials('production' => {:key => '12345'},
:development => {:key => "54321"}))
end
should "get the correct credentials when RAILS_ENV is development" do
rails_env("development")
assert_equal({:key => "54321"},
@avatar.parse_credentials('production' => {:key => '12345'},
:development => {:key => "54321"}))
end
should "return the argument if the key does not exist" do
rails_env("not really an env")
assert_equal({:test => "12345"}, @avatar.parse_credentials(:test => "12345"))
end
end
context "" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:s3_credentials => {},
:bucket => "bucket",
:path => ":attachment/:basename.:extension",
:url => ":s3_path_url"
@dummy = Dummy.new
@dummy.avatar = StringIO.new(".")
end
should "return a url based on an S3 path" do
assert_match %r{^http://s3.amazonaws.com/bucket/avatars/stringio.txt}, @dummy.avatar.url
end
end
context "" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:s3_credentials => {},
:bucket => "bucket",
:path => ":attachment/:basename.:extension",
:url => ":s3_domain_url"
@dummy = Dummy.new
@dummy.avatar = StringIO.new(".")
end
should "return a url based on an S3 subdomain" do
assert_match %r{^http://bucket.s3.amazonaws.com/avatars/stringio.txt}, @dummy.avatar.url
end
end
context "" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:s3_credentials => {
:production => { :bucket => "prod_bucket" },
:development => { :bucket => "dev_bucket" }
},
:s3_host_alias => "something.something.com",
:path => ":attachment/:basename.:extension",
:url => ":s3_alias_url"
@dummy = Dummy.new
@dummy.avatar = StringIO.new(".")
end
should "return a url based on the host_alias" do
assert_match %r{^http://something.something.com/avatars/stringio.txt}, @dummy.avatar.url
end
end
context "Generating a url with an expiration" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:s3_credentials => {
:production => { :bucket => "prod_bucket" },
:development => { :bucket => "dev_bucket" }
},
:s3_host_alias => "something.something.com",
:path => ":attachment/:basename.:extension",
:url => ":s3_alias_url"
rails_env("production")
@dummy = Dummy.new
@dummy.avatar = StringIO.new(".")
AWS::S3::S3Object.expects(:url_for).with("avatars/stringio.txt", "prod_bucket", { :expires_in => 3600 })
@dummy.avatar.expiring_url
end
should "should succeed" do
assert true
end
end
context "Parsing S3 credentials with a bucket in them" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:s3_credentials => {
:production => { :bucket => "prod_bucket" },
:development => { :bucket => "dev_bucket" }
}
@dummy = Dummy.new
end
should "get the right bucket in production" do
rails_env("production")
assert_equal "prod_bucket", @dummy.avatar.bucket_name
end
should "get the right bucket in development" do
rails_env("development")
assert_equal "dev_bucket", @dummy.avatar.bucket_name
end
end
context "An attachment with S3 storage" do
setup do
rebuild_model :storage => :s3,
:bucket => "testing",
:path => ":attachment/:style/:basename.:extension",
:s3_credentials => {
'access_key_id' => "12345",
'secret_access_key' => "54321"
}
end
should "be extended by the S3 module" do
assert Dummy.new.avatar.is_a?(Paperclip::Storage::S3)
end
should "not be extended by the Filesystem module" do
assert ! Dummy.new.avatar.is_a?(Paperclip::Storage::Filesystem)
end
context "when assigned" do
setup do
@file = File.new(File.join(File.dirname(__FILE__), 'fixtures', '5k.png'), 'rb')
@dummy = Dummy.new
@dummy.avatar = @file
end
teardown { @file.close }
should "not get a bucket to get a URL" do
@dummy.avatar.expects(:s3).never
@dummy.avatar.expects(:s3_bucket).never
assert_match %r{^http://s3\.amazonaws\.com/testing/avatars/original/5k\.png}, @dummy.avatar.url
end
context "and saved" do
setup do
AWS::S3::S3Object.stubs(:store).with(@dummy.avatar.path, anything, 'testing', :content_type => 'image/png', :access => :public_read)
@dummy.save
end
should "succeed" do
assert true
end
end
context "and saved without a bucket" do
setup do
class AWS::S3::NoSuchBucket < AWS::S3::ResponseError
# Force the class to be created as a proper subclass of ResponseError thanks to AWS::S3's autocreation of exceptions
end
AWS::S3::Bucket.expects(:create).with("testing")
AWS::S3::S3Object.stubs(:store).raises(AWS::S3::NoSuchBucket.new(:message, :response)).then.returns(true)
@dummy.save
end
should "succeed" do
assert true
end
end
context "and remove" do
setup do
AWS::S3::S3Object.stubs(:exists?).returns(true)
AWS::S3::S3Object.stubs(:delete)
@dummy.destroy_attached_files
end
should "succeed" do
assert true
end
end
end
end
context "An attachment with S3 storage and bucket defined as a Proc" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:bucket => lambda { |attachment| "bucket_#{attachment.instance.other}" },
:s3_credentials => {:not => :important}
end
should "get the right bucket name" do
assert "bucket_a", Dummy.new(:other => 'a').avatar.bucket_name
assert "bucket_b", Dummy.new(:other => 'b').avatar.bucket_name
end
end
context "An attachment with S3 storage and specific s3 headers set" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
rebuild_model :storage => :s3,
:bucket => "testing",
:path => ":attachment/:style/:basename.:extension",
:s3_credentials => {
'access_key_id' => "12345",
'secret_access_key' => "54321"
},
:s3_headers => {'Cache-Control' => 'max-age=31557600'}
end
context "when assigned" do
setup do
@file = File.new(File.join(File.dirname(__FILE__), 'fixtures', '5k.png'), 'rb')
@dummy = Dummy.new
@dummy.avatar = @file
end
teardown { @file.close }
context "and saved" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
AWS::S3::S3Object.stubs(:store).with(@dummy.avatar.path,
anything,
'testing',
:content_type => 'image/png',
:access => :public_read,
'Cache-Control' => 'max-age=31557600')
@dummy.save
end
should "succeed" do
assert true
end
end
end
end
context "with S3 credentials supplied as Pathname" do
setup do
ENV['S3_KEY'] = 'pathname_key'
ENV['S3_BUCKET'] = 'pathname_bucket'
ENV['S3_SECRET'] = 'pathname_secret'
rails_env('test')
rebuild_model :storage => :s3,
:s3_credentials => Pathname.new(File.join(File.dirname(__FILE__))).join("fixtures/s3.yml")
Dummy.delete_all
@dummy = Dummy.new
end
should "parse the credentials" do
assert_equal 'pathname_bucket', @dummy.avatar.bucket_name
assert_equal 'pathname_key', AWS::S3::Base.connection.options[:access_key_id]
assert_equal 'pathname_secret', AWS::S3::Base.connection.options[:secret_access_key]
end
end
context "with S3 credentials in a YAML file" do
setup do
ENV['S3_KEY'] = 'env_key'
ENV['S3_BUCKET'] = 'env_bucket'
ENV['S3_SECRET'] = 'env_secret'
rails_env('test')
rebuild_model :storage => :s3,
:s3_credentials => File.new(File.join(File.dirname(__FILE__), "fixtures/s3.yml"))
Dummy.delete_all
@dummy = Dummy.new
end
should "run it the file through ERB" do
assert_equal 'env_bucket', @dummy.avatar.bucket_name
assert_equal 'env_key', AWS::S3::Base.connection.options[:access_key_id]
assert_equal 'env_secret', AWS::S3::Base.connection.options[:secret_access_key]
end
end
unless ENV["S3_TEST_BUCKET"].blank?
context "Using S3 for real, an attachment with S3 storage" do
setup do
rebuild_model :styles => { :thumb => "100x100", :square => "32x32#" },
:storage => :s3,
:bucket => ENV["S3_TEST_BUCKET"],
:path => ":class/:attachment/:id/:style.:extension",
:s3_credentials => File.new(File.join(File.dirname(__FILE__), "s3.yml"))
Dummy.delete_all
@dummy = Dummy.new
end
should "be extended by the S3 module" do
assert Dummy.new.avatar.is_a?(Paperclip::Storage::S3)
end
context "when assigned" do
setup do
@file = File.new(File.join(File.dirname(__FILE__), 'fixtures', '5k.png'), 'rb')
@dummy.avatar = @file
end
teardown { @file.close }
should "still return a Tempfile when sent #to_file" do
assert_equal Paperclip::Tempfile, @dummy.avatar.to_file.class
end
context "and saved" do
setup do
@dummy.save
end
should "be on S3" do
assert true
end
end
end
end
end
end

View File

@ -1,141 +0,0 @@
# encoding: utf-8
require 'test/helper'
class StyleTest < Test::Unit::TestCase
context "A style rule" do
setup do
@attachment = attachment :path => ":basename.:extension",
:styles => { :foo => {:geometry => "100x100#", :format => :png} }
@style = @attachment.styles[:foo]
end
should "be held as a Style object" do
assert_kind_of Paperclip::Style, @style
end
should "get processors from the attachment definition" do
assert_equal [:thumbnail], @style.processors
end
should "have the right geometry" do
assert_equal "100x100#", @style.geometry
end
should "be whiny if the attachment is" do
@attachment.expects(:whiny).returns(true)
assert @style.whiny?
end
should "respond to hash notation" do
assert_equal [:thumbnail], @style[:processors]
assert_equal "100x100#", @style[:geometry]
end
end
context "A style rule with properties supplied as procs" do
setup do
@attachment = attachment :path => ":basename.:extension",
:whiny_thumbnails => true,
:processors => lambda {|a| [:test]},
:styles => {
:foo => lambda{|a| "300x300#"},
:bar => {
:geometry => lambda{|a| "300x300#"}
}
}
end
should "defer processing of procs until they are needed" do
assert_kind_of Proc, @attachment.styles[:foo].instance_variable_get("@geometry")
assert_kind_of Proc, @attachment.styles[:bar].instance_variable_get("@geometry")
assert_kind_of Proc, @attachment.instance_variable_get("@processors")
end
should "call procs when they are needed" do
assert_equal "300x300#", @attachment.styles[:foo].geometry
assert_equal "300x300#", @attachment.styles[:bar].geometry
assert_equal [:test], @attachment.styles[:foo].processors
assert_equal [:test], @attachment.styles[:bar].processors
end
end
context "An attachment with style rules in various forms" do
setup do
@attachment = attachment :path => ":basename.:extension",
:styles => {
:aslist => ["100x100", :png],
:ashash => {:geometry => "100x100", :format => :png},
:asstring => "100x100"
}
end
should "have the right number of styles" do
assert_kind_of Hash, @attachment.styles
assert_equal 3, @attachment.styles.size
end
should "have styles as Style objects" do
[:aslist, :ashash, :aslist].each do |s|
assert_kind_of Paperclip::Style, @attachment.styles[s]
end
end
should "have the right geometries" do
[:aslist, :ashash, :aslist].each do |s|
assert_equal @attachment.styles[s].geometry, "100x100"
end
end
should "have the right formats" do
assert_equal @attachment.styles[:aslist].format, :png
assert_equal @attachment.styles[:ashash].format, :png
assert_nil @attachment.styles[:asstring].format
end
end
context "An attachment with :convert_options" do
setup do
@attachment = attachment :path => ":basename.:extension",
:styles => {:thumb => "100x100", :large => "400x400"},
:convert_options => {:all => "-do_stuff", :thumb => "-thumbnailize"}
@style = @attachment.styles[:thumb]
@file = StringIO.new("...")
@file.stubs(:original_filename).returns("file.jpg")
end
before_should "not have called extra_options_for(:thumb/:large) on initialization" do
@attachment.expects(:extra_options_for).never
end
should "call extra_options_for(:thumb/:large) when convert options are requested" do
@attachment.expects(:extra_options_for).with(:thumb)
@attachment.styles[:thumb].convert_options
end
end
context "A style rule with its own :processors" do
setup do
@attachment = attachment :path => ":basename.:extension",
:styles => {
:foo => {
:geometry => "100x100#",
:format => :png,
:processors => [:test]
}
},
:processors => [:thumbnail]
@style = @attachment.styles[:foo]
end
should "not get processors from the attachment" do
@attachment.expects(:processors).never
assert_not_equal [:thumbnail], @style.processors
end
should "report its own processors" do
assert_equal [:test], @style.processors
end
end
end

View File

@ -1,228 +0,0 @@
require 'test/helper'
class ThumbnailTest < Test::Unit::TestCase
context "A Paperclip Tempfile" do
setup do
@tempfile = Paperclip::Tempfile.new(["file", ".jpg"])
end
should "have its path contain a real extension" do
p @tempfile.path
assert_equal ".jpg", File.extname(@tempfile.path)
end
should "be a real Tempfile" do
assert @tempfile.is_a?(::Tempfile)
end
end
context "Another Paperclip Tempfile" do
setup do
@tempfile = Paperclip::Tempfile.new("file")
end
should "not have an extension if not given one" do
assert_equal "", File.extname(@tempfile.path)
end
should "still be a real Tempfile" do
assert @tempfile.is_a?(::Tempfile)
end
end
context "An image" do
setup do
@file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
end
teardown { @file.close }
[["600x600>", "434x66"],
["400x400>", "400x61"],
["32x32<", "434x66"]
].each do |args|
context "being thumbnailed with a geometry of #{args[0]}" do
setup do
@thumb = Paperclip::Thumbnail.new(@file, :geometry => args[0])
end
should "start with dimensions of 434x66" do
cmd = %Q[identify -format "%wx%h" "#{@file.path}"]
assert_equal "434x66", `#{cmd}`.chomp
end
should "report the correct target geometry" do
assert_equal args[0], @thumb.target_geometry.to_s
end
context "when made" do
setup do
@thumb_result = @thumb.make
end
should "be the size we expect it to be" do
cmd = %Q[identify -format "%wx%h" "#{@thumb_result.path}"]
assert_equal args[1], `#{cmd}`.chomp
end
end
end
end
context "being thumbnailed at 100x50 with cropping" do
setup do
@thumb = Paperclip::Thumbnail.new(@file, :geometry => "100x50#")
end
should "report its correct current and target geometries" do
assert_equal "100x50#", @thumb.target_geometry.to_s
assert_equal "434x66", @thumb.current_geometry.to_s
end
should "report its correct format" do
assert_nil @thumb.format
end
should "have whiny turned on by default" do
assert @thumb.whiny
end
should "have convert_options set to nil by default" do
assert_equal nil, @thumb.convert_options
end
should "send the right command to convert when sent #make" do
Paperclip::CommandLine.expects(:"`").with do |arg|
arg.match %r{convert ["']#{File.expand_path(@thumb.file.path)}\[0\]["'] -resize ["']x50["'] -crop ["']100x50\+114\+0["'] \+repage ["'].*?["']}
end
@thumb.make
end
should "create the thumbnail when sent #make" do
dst = @thumb.make
assert_match /100x50/, `identify "#{dst.path}"`
end
end
context "being thumbnailed with source file options set" do
setup do
@thumb = Paperclip::Thumbnail.new(@file,
:geometry => "100x50#",
:source_file_options => "-strip")
end
should "have source_file_options value set" do
assert_equal ["-strip"], @thumb.source_file_options
end
should "send the right command to convert when sent #make" do
Paperclip::CommandLine.expects(:"`").with do |arg|
arg.match %r{convert -strip ["']#{File.expand_path(@thumb.file.path)}\[0\]["'] -resize ["']x50["'] -crop ["']100x50\+114\+0["'] \+repage ["'].*?["']}
end
@thumb.make
end
should "create the thumbnail when sent #make" do
dst = @thumb.make
assert_match /100x50/, `identify "#{dst.path}"`
end
context "redefined to have bad source_file_options setting" do
setup do
@thumb = Paperclip::Thumbnail.new(@file,
:geometry => "100x50#",
:source_file_options => "-this-aint-no-option")
end
should "error when trying to create the thumbnail" do
assert_raises(Paperclip::PaperclipError) do
@thumb.make
end
end
end
end
context "being thumbnailed with convert options set" do
setup do
@thumb = Paperclip::Thumbnail.new(@file,
:geometry => "100x50#",
:convert_options => "-strip -depth 8")
end
should "have convert_options value set" do
assert_equal %w"-strip -depth 8", @thumb.convert_options
end
should "send the right command to convert when sent #make" do
Paperclip::CommandLine.expects(:"`").with do |arg|
arg.match %r{convert ["']#{File.expand_path(@thumb.file.path)}\[0\]["'] -resize ["']x50["'] -crop ["']100x50\+114\+0["'] \+repage -strip -depth 8 ["'].*?["']}
end
@thumb.make
end
should "create the thumbnail when sent #make" do
dst = @thumb.make
assert_match /100x50/, `identify "#{dst.path}"`
end
context "redefined to have bad convert_options setting" do
setup do
@thumb = Paperclip::Thumbnail.new(@file,
:geometry => "100x50#",
:convert_options => "-this-aint-no-option")
end
should "error when trying to create the thumbnail" do
assert_raises(Paperclip::PaperclipError) do
@thumb.make
end
end
end
end
context "being thumbnailed with a blank geometry string" do
setup do
@thumb = Paperclip::Thumbnail.new(@file,
:geometry => "",
:convert_options => "-gravity center -crop \"300x300+0-0\"")
end
should "not get resized by default" do
assert !@thumb.transformation_command.include?("-resize")
end
end
end
context "A multipage PDF" do
setup do
@file = File.new(File.join(File.dirname(__FILE__), "fixtures", "twopage.pdf"), 'rb')
end
teardown { @file.close }
should "start with two pages with dimensions 612x792" do
cmd = %Q[identify -format "%wx%h" "#{@file.path}"]
assert_equal "612x792"*2, `#{cmd}`.chomp
end
context "being thumbnailed at 100x100 with cropping" do
setup do
@thumb = Paperclip::Thumbnail.new(@file, :geometry => "100x100#", :format => :png)
end
should "report its correct current and target geometries" do
assert_equal "100x100#", @thumb.target_geometry.to_s
assert_equal "612x792", @thumb.current_geometry.to_s
end
should "report its correct format" do
assert_equal :png, @thumb.format
end
should "create the thumbnail when sent #make" do
dst = @thumb.make
assert_match /100x100/, `identify "#{dst.path}"`
end
end
end
end

View File

@ -1,36 +0,0 @@
require 'test/helper'
class UpfileTest < Test::Unit::TestCase
{ %w(jpg jpe jpeg) => 'image/jpeg',
%w(tif tiff) => 'image/tiff',
%w(png) => 'image/png',
%w(gif) => 'image/gif',
%w(bmp) => 'image/bmp',
%w(txt) => 'text/plain',
%w(htm html) => 'text/html',
%w(csv) => 'text/csv',
%w(xml) => 'text/xml',
%w(css) => 'text/css',
%w(js) => 'application/js',
%w(foo) => 'application/x-foo'
}.each do |extensions, content_type|
extensions.each do |extension|
should "return a content_type of #{content_type} for a file with extension .#{extension}" do
file = stub('file', :path => "basename.#{extension}")
class << file
include Paperclip::Upfile
end
assert_equal content_type, file.content_type
end
end
end
should "return a content_type of text/plain on a real file whose content_type is determined with the file command" do
file = File.new(File.join(File.dirname(__FILE__), "..", "LICENSE"))
class << file
include Paperclip::Upfile
end
assert_equal 'text/plain', file.content_type
end
end