spree_core 3.4.6 → 3.5.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +5 -5
  2. data/app/assets/javascripts/spree.js.coffee +1 -1
  3. data/app/helpers/spree/base_helper.rb +4 -0
  4. data/app/models/concerns/spree/named_type.rb +1 -1
  5. data/app/models/concerns/spree/user_methods.rb +21 -4
  6. data/app/models/concerns/spree/user_reporting.rb +2 -2
  7. data/app/models/spree/address.rb +6 -12
  8. data/app/models/spree/adjustable/adjustments_updater.rb +2 -1
  9. data/app/models/spree/country.rb +2 -1
  10. data/app/models/spree/line_item.rb +8 -2
  11. data/app/models/spree/log_entry.rb +1 -1
  12. data/app/models/spree/order.rb +8 -6
  13. data/app/models/spree/order/checkout.rb +1 -0
  14. data/app/models/spree/order_contents.rb +20 -12
  15. data/app/models/spree/order_inventory.rb +24 -12
  16. data/app/models/spree/payment/processing.rb +2 -2
  17. data/app/models/spree/preferences/preferable.rb +1 -1
  18. data/app/models/spree/product/scopes.rb +1 -1
  19. data/app/models/spree/promotion.rb +15 -1
  20. data/app/models/spree/promotion/rules/option_value.rb +13 -5
  21. data/app/models/spree/promotion/rules/product.rb +2 -1
  22. data/app/models/spree/promotion/rules/taxon.rb +3 -1
  23. data/app/models/spree/promotion_action_line_item.rb +3 -0
  24. data/app/models/spree/promotion_handler/promotion_duplicator.rb +52 -0
  25. data/app/models/spree/refund.rb +1 -1
  26. data/app/models/spree/reimbursement.rb +1 -1
  27. data/app/models/spree/reimbursement/reimbursement_type_engine.rb +7 -18
  28. data/app/models/spree/reimbursement_performer.rb +3 -7
  29. data/app/models/spree/reimbursement_type/original_payment.rb +2 -2
  30. data/app/models/spree/reimbursement_type/reimbursement_helpers.rb +3 -7
  31. data/app/models/spree/reimbursement_type/store_credit.rb +2 -10
  32. data/app/models/spree/shipment.rb +10 -4
  33. data/app/models/spree/stock/availability_validator.rb +1 -1
  34. data/app/models/spree/stock/packer.rb +1 -1
  35. data/app/models/spree/stock/splitter/backordered.rb +5 -7
  36. data/app/models/spree/stock/splitter/base.rb +1 -0
  37. data/app/models/spree/stock/splitter/shipping_category.rb +9 -16
  38. data/app/models/spree/stock/splitter/weight.rb +18 -20
  39. data/app/models/spree/stock_transfer.rb +2 -1
  40. data/app/models/spree/store_credit_category.rb +13 -0
  41. data/app/models/spree/taxon.rb +7 -0
  42. data/app/models/spree/variant.rb +1 -1
  43. data/app/validators/email_validator.rb +7 -0
  44. data/config/locales/en.yml +18 -27
  45. data/db/default/spree/states.rb +9 -27
  46. data/db/migrate/20150128032538_remove_environment_from_tracker.rb +2 -0
  47. data/db/migrate/20171004223836_remove_icon_from_taxons.rb +8 -0
  48. data/db/migrate/20180222133746_add_unique_index_on_spree_promotions_code.rb +6 -0
  49. data/lib/generators/spree/dummy_model/dummy_model_generator.rb +23 -0
  50. data/lib/generators/spree/dummy_model/templates/migration.rb.tt +10 -0
  51. data/lib/generators/spree/dummy_model/templates/model.rb.tt +6 -0
  52. data/lib/spree/core/controller_helpers/auth.rb +1 -1
  53. data/lib/spree/core/controller_helpers/common.rb +4 -0
  54. data/lib/spree/core/controller_helpers/order.rb +6 -5
  55. data/lib/spree/core/engine.rb +10 -10
  56. data/lib/spree/core/environment_extension.rb +3 -0
  57. data/lib/spree/core/importer/order.rb +1 -1
  58. data/lib/spree/core/validators/email.rb +1 -0
  59. data/lib/spree/core/version.rb +1 -1
  60. data/lib/spree/money.rb +1 -5
  61. data/lib/spree/permitted_attributes.rb +1 -1
  62. data/lib/spree/testing_support/capybara_ext.rb +16 -13
  63. data/lib/spree/testing_support/common_rake.rb +4 -1
  64. data/lib/spree/testing_support/factories/inventory_unit_factory.rb +7 -0
  65. data/lib/spree/testing_support/factories/taxon_factory.rb +1 -1
  66. data/spree_core.gemspec +1 -1
  67. data/vendor/assets/javascripts/jsuri.js +458 -2
  68. metadata +13 -7
  69. data/app/models/spree/tracker.rb +0 -25
  70. data/lib/spree/testing_support/factories/tracker_factory.rb +0 -7
@@ -4,6 +4,9 @@ module Spree
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  def add_class(name)
7
+ ActiveSupport::Deprecation.warn(<<-EOS, caller)
8
+ EnvironmentExtension module is deprecated and will be removed in Spree 3.6
9
+ EOS
7
10
  instance_variable_set "@#{name}", Set.new
8
11
 
9
12
  create_method("#{name}=".to_sym) do |val|
@@ -94,7 +94,7 @@ module Spree
94
94
  if existing
95
95
  existing.quantity += 1
96
96
  else
97
- line_item = order.line_items.detect { |ln| ln.variant_id = inventory_unit_param[:variant_id] }
97
+ line_item = order.line_items.detect { |ln| ln.variant_id == inventory_unit_param[:variant_id] }
98
98
  inventory_units << InventoryUnit.new(line_item: line_item, order_id: order.id, variant: line_item.variant, quantity: 1)
99
99
  end
100
100
 
@@ -1,5 +1,6 @@
1
1
  class EmailValidator < ActiveModel::EachValidator
2
2
  def validate_each(record, attribute, value)
3
+ warn "`EmailValidator` in 'lib/spree/core' is deprecated. Use `EmailValidator` in 'app/validators' instead."
3
4
  unless value =~ /\A[^@\s]+@[^@\s]+\z/
4
5
  record.errors.add(attribute, :invalid, { value: value }.merge!(options))
5
6
  end
@@ -1,5 +1,5 @@
1
1
  module Spree
2
2
  def self.version
3
- '3.4.6'
3
+ '3.5.0.rc1'
4
4
  end
5
5
  end
@@ -15,17 +15,13 @@ module Spree
15
15
 
16
16
  attr_reader :money
17
17
 
18
- delegate :cents, :currency, to: :money
18
+ delegate :cents, to: :money
19
19
 
20
20
  def initialize(amount, options = {})
21
21
  @money = Monetize.parse([amount, (options[:currency] || Spree::Config[:currency])].join)
22
22
  @options = Spree::Money.default_formatting_rules.merge(options)
23
23
  end
24
24
 
25
- def amount_in_cents
26
- (cents / currency.subunit_to_unit.to_f * 100).round
27
- end
28
-
29
25
  def to_s
30
26
  @money.format(@options)
31
27
  end
@@ -90,7 +90,7 @@ module Spree
90
90
  @@stock_movement_attributes = [
91
91
  :quantity, :stock_item, :stock_item_id, :originator, :action]
92
92
 
93
- @@store_attributes = [:name, :url, :seo_title, :meta_keywords,
93
+ @@store_attributes = [:name, :url, :seo_title, :code, :meta_keywords,
94
94
  :meta_description, :default_currency, :mail_from_address]
95
95
 
96
96
  @@store_credit_attributes = [:amount, :category_id, :memo]
@@ -59,8 +59,6 @@ module CapybaraExt
59
59
  raise "Must pass a hash containing 'from'" if !options.is_a?(Hash) || !options.key?(:from)
60
60
 
61
61
  placeholder = options[:from]
62
- minlength = options[:minlength] || 4
63
-
64
62
  click_link placeholder
65
63
 
66
64
  select_select2_result(value)
@@ -101,12 +99,11 @@ module CapybaraExt
101
99
 
102
100
  # arg delay in seconds
103
101
  def wait_for_ajax(delay = Capybara.default_max_wait_time)
104
- counter = 0
105
- delay_threshold = delay * 10
106
- while page.evaluate_script("typeof($) === 'undefined' || $.active > 0")
107
- counter += 1
108
- sleep(0.1)
109
- raise "AJAX request took longer than #{delay} seconds." if counter >= delay_threshold
102
+ Timeout.timeout(delay) do
103
+ active = page.evaluate_script('typeof jQuery !== "undefined" && jQuery.active')
104
+ until active.zero?
105
+ active = page.evaluate_script('typeof jQuery !== "undefined" && jQuery.active')
106
+ end
110
107
  end
111
108
  end
112
109
 
@@ -114,6 +111,7 @@ module CapybaraExt
114
111
  #
115
112
  # Much better than a random sleep "here and there"
116
113
  # it will not cause any delay in case the condition is fullfilled on first cycle.
114
+
117
115
  def wait_for_condition(delay = Capybara.default_max_wait_time)
118
116
  counter = 0
119
117
  delay_threshold = delay * 10
@@ -124,17 +122,22 @@ module CapybaraExt
124
122
  end
125
123
  end
126
124
 
127
- def accept_alert
128
- page.evaluate_script('window.confirm = function() { return true; }')
129
- yield
130
- end
131
-
132
125
  def dismiss_alert
133
126
  page.evaluate_script('window.confirm = function() { return false; }')
134
127
  yield
135
128
  # Restore existing default
136
129
  page.evaluate_script('window.confirm = function() { return true; }')
137
130
  end
131
+
132
+ def spree_accept_alert
133
+ yield
134
+ rescue Selenium::WebDriver::Error::UnhandledAlertError
135
+ page.driver.browser.switch_to.alert.accept
136
+ end
137
+
138
+ def disable_html5_validation
139
+ page.execute_script('for(var f=document.forms,i=f.length;i--;)f[i].setAttribute("novalidate",i)')
140
+ end
138
141
  end
139
142
 
140
143
  Capybara.configure do |config|
@@ -3,6 +3,7 @@ unless defined?(Spree::InstallGenerator)
3
3
  end
4
4
 
5
5
  require 'generators/spree/dummy/dummy_generator'
6
+ require 'generators/spree/dummy_model/dummy_model_generator'
6
7
 
7
8
  desc 'Generates a dummy app for testing'
8
9
  namespace :common do
@@ -17,7 +18,9 @@ namespace :common do
17
18
  Spree::InstallGenerator.start ["--lib_name=#{ENV['LIB_NAME']}", '--auto-accept', '--migrate=false', '--seed=false', '--sample=false', '--quiet', '--copy_views=false', "--user_class=#{args[:user_class]}"]
18
19
 
19
20
  puts 'Setting up dummy database...'
20
- system("bundle exec rake db:drop db:create db:migrate > #{File::NULL}")
21
+ system("bundle exec rake db:drop db:create > #{File::NULL}")
22
+ Spree::DummyModelGenerator.start
23
+ system("bundle exec rake db:migrate > #{File::NULL}")
21
24
 
22
25
  begin
23
26
  require "generators/#{ENV['LIB_NAME']}/install/install_generator"
@@ -6,5 +6,12 @@ FactoryBot.define do
6
6
  state 'on_hand'
7
7
  association(:shipment, factory: :shipment, state: 'pending')
8
8
  # return_authorization
9
+
10
+ # this trait usage increases build speed ~ 2x
11
+ trait :without_assoc do
12
+ shipment nil
13
+ order nil
14
+ line_item nil
15
+ end
9
16
  end
10
17
  end
@@ -2,6 +2,6 @@ FactoryBot.define do
2
2
  factory :taxon, class: Spree::Taxon do
3
3
  sequence(:name) { |n| "taxon_#{n}" }
4
4
  taxonomy
5
- parent_id nil
5
+ parent_id { taxonomy.root.id }
6
6
  end
7
7
  end
@@ -35,7 +35,7 @@ Gem::Specification.new do |s|
35
35
  s.add_dependency 'paranoia', '~> 2.3.0'
36
36
  s.add_dependency 'premailer-rails'
37
37
  s.add_dependency 'acts-as-taggable-on', '~> 5.0'
38
- s.add_dependency 'rails', '~> 5.1.4'
38
+ s.add_dependency 'rails', '~> 5.1.5'
39
39
  s.add_dependency 'ransack', '~> 1.8.0'
40
40
  s.add_dependency 'responders'
41
41
  s.add_dependency 'state_machines-activerecord', '~> 0.5'
@@ -1,2 +1,458 @@
1
- /*! jsUri v1.1.1 | https://github.com/derek-watson/jsUri */
2
- var Query=function(a){"use strict";var b=function(a){var b=[],c,d,e,f;if(typeof a=="undefined"||a===null||a==="")return b;a.indexOf("?")===0&&(a=a.substring(1)),d=a.toString().split(/[&;]/);for(c=0;c<d.length;c++)e=d[c],f=e.split("="),b.push([f[0],f[1]]);return b},c=b(a),d=function(){var a="",b,d;for(b=0;b<c.length;b++)d=c[b],a.length>0&&(a+="&"),a+=d.join("=");return a.length>0?"?"+a:a},e=function(a){a=decodeURIComponent(a),a=a.replace("+"," ");return a},f=function(a){var b,d;for(d=0;d<c.length;d++){b=c[d];if(e(a)===e(b[0]))return b[1]}},g=function(a){var b=[],d,f;for(d=0;d<c.length;d++)f=c[d],e(a)===e(f[0])&&b.push(f[1]);return b},h=function(a,b){var d=[],f,g,h,i;for(f=0;f<c.length;f++)g=c[f],h=e(g[0])===e(a),i=e(g[1])===e(b),(arguments.length===1&&!h||arguments.length===2&&!h&&!i)&&d.push(g);c=d;return this},i=function(a,b,d){arguments.length===3&&d!==-1?(d=Math.min(d,c.length),c.splice(d,0,[a,b])):arguments.length>0&&c.push([a,b]);return this},j=function(a,b,d){var f=-1,g,j;if(arguments.length===3){for(g=0;g<c.length;g++){j=c[g];if(e(j[0])===e(a)&&decodeURIComponent(j[1])===e(d)){f=g;break}}h(a,d).addParam(a,b,f)}else{for(g=0;g<c.length;g++){j=c[g];if(e(j[0])===e(a)){f=g;break}}h(a),i(a,b,f)}return this};return{getParamValue:f,getParamValues:g,deleteParam:h,addParam:i,replaceParam:j,toString:d}},Uri=function(a){"use strict";var b=!1,c=function(a){var c={strict:/^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,loose:/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/},d=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],e={name:"queryKey",parser:/(?:^|&)([^&=]*)=?([^&]*)/g},f=c[b?"strict":"loose"].exec(a),g={},h=14;while(h--)g[d[h]]=f[h]||"";g[e.name]={},g[d[12]].replace(e.parser,function(a,b,c){b&&(g[e.name][b]=c)});return g},d=c(a||""),e=new Query(d.query),f=function(a){typeof a!="undefined"&&(d.protocol=a);return d.protocol},g=null,h=function(a){typeof a!="undefined"&&(g=a);return g===null?d.source.indexOf("//")!==-1:g},i=function(a){typeof a!="undefined"&&(d.userInfo=a);return d.userInfo},j=function(a){typeof a!="undefined"&&(d.host=a);return d.host},k=function(a){typeof a!="undefined"&&(d.port=a);return d.port},l=function(a){typeof a!="undefined"&&(d.path=a);return d.path},m=function(a){typeof a!="undefined"&&(e=new Query(a));return e},n=function(a){typeof a!="undefined"&&(d.anchor=a);return d.anchor},o=function(a){f(a);return this},p=function(a){h(a);return this},q=function(a){i(a);return this},r=function(a){j(a);return this},s=function(a){k(a);return this},t=function(a){l(a);return this},u=function(a){m(a);return this},v=function(a){n(a);return this},w=function(a){return m().getParamValue(a)},x=function(a){return m().getParamValues(a)},y=function(a,b){arguments.length===2?m().deleteParam(a,b):m().deleteParam(a);return this},z=function(a,b,c){arguments.length===3?m().addParam(a,b,c):m().addParam(a,b);return this},A=function(a,b,c){arguments.length===3?m().replaceParam(a,b,c):m().replaceParam(a,b);return this},B=function(){var a="",b=function(a){return a!==null&&a!==""};b(f())?(a+=f(),f().indexOf(":")!==f().length-1&&(a+=":"),a+="//"):h()&&b(j())&&(a+="//"),b(i())&&b(j())&&(a+=i(),i().indexOf("@")!==i().length-1&&(a+="@")),b(j())&&(a+=j(),b(k())&&(a+=":"+k())),b(l())?a+=l():b(j())&&(b(m().toString())||b(n()))&&(a+="/"),b(m().toString())&&(m().toString().indexOf("?")!==0&&(a+="?"),a+=m().toString()),b(n())&&(n().indexOf("#")!==0&&(a+="#"),a+=n());return a},C=function(){return new Uri(B())};return{protocol:f,hasAuthorityPrefix:h,userInfo:i,host:j,port:k,path:l,query:m,anchor:n,setProtocol:o,setHasAuthorityPrefix:p,setUserInfo:q,setHost:r,setPort:s,setPath:t,setQuery:u,setAnchor:v,getQueryParamValue:w,getQueryParamValues:x,deleteQueryParam:y,addQueryParam:z,replaceQueryParam:A,toString:B,clone:C}},jsUri=Uri;
1
+ /*!
2
+ * jsUri
3
+ * https://github.com/derek-watson/jsUri
4
+ *
5
+ * Copyright 2013, Derek Watson
6
+ * Released under the MIT license.
7
+ *
8
+ * Includes parseUri regular expressions
9
+ * http://blog.stevenlevithan.com/archives/parseuri
10
+ * Copyright 2007, Steven Levithan
11
+ * Released under the MIT license.
12
+ */
13
+
14
+ /*globals define, module */
15
+
16
+ (function(global) {
17
+
18
+ var re = {
19
+ starts_with_slashes: /^\/+/,
20
+ ends_with_slashes: /\/+$/,
21
+ pluses: /\+/g,
22
+ query_separator: /[&;]/,
23
+ uri_parser: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*)(?::([^:@]*))?)?@)?(\[[0-9a-fA-F:.]+\]|[^:\/?#]*)(?::(\d+|(?=:)))?(:)?)((((?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
24
+ };
25
+
26
+ /**
27
+ * Define forEach for older js environments
28
+ * @see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach#Compatibility
29
+ */
30
+ if (!Array.prototype.forEach) {
31
+ Array.prototype.forEach = function(callback, thisArg) {
32
+ var T, k;
33
+
34
+ if (this == null) {
35
+ throw new TypeError(' this is null or not defined');
36
+ }
37
+
38
+ var O = Object(this);
39
+ var len = O.length >>> 0;
40
+
41
+ if (typeof callback !== "function") {
42
+ throw new TypeError(callback + ' is not a function');
43
+ }
44
+
45
+ if (arguments.length > 1) {
46
+ T = thisArg;
47
+ }
48
+
49
+ k = 0;
50
+
51
+ while (k < len) {
52
+ var kValue;
53
+ if (k in O) {
54
+ kValue = O[k];
55
+ callback.call(T, kValue, k, O);
56
+ }
57
+ k++;
58
+ }
59
+ };
60
+ }
61
+
62
+ /**
63
+ * unescape a query param value
64
+ * @param {string} s encoded value
65
+ * @return {string} decoded value
66
+ */
67
+ function decode(s) {
68
+ if (s) {
69
+ s = s.toString().replace(re.pluses, '%20');
70
+ s = decodeURIComponent(s);
71
+ }
72
+ return s;
73
+ }
74
+
75
+ /**
76
+ * Breaks a uri string down into its individual parts
77
+ * @param {string} str uri
78
+ * @return {object} parts
79
+ */
80
+ function parseUri(str) {
81
+ var parser = re.uri_parser;
82
+ var parserKeys = ["source", "protocol", "authority", "userInfo", "user", "password", "host", "port", "isColonUri", "relative", "path", "directory", "file", "query", "anchor"];
83
+ var m = parser.exec(str || '');
84
+ var parts = {};
85
+
86
+ parserKeys.forEach(function(key, i) {
87
+ parts[key] = m[i] || '';
88
+ });
89
+
90
+ return parts;
91
+ }
92
+
93
+ /**
94
+ * Breaks a query string down into an array of key/value pairs
95
+ * @param {string} str query
96
+ * @return {array} array of arrays (key/value pairs)
97
+ */
98
+ function parseQuery(str) {
99
+ var i, ps, p, n, k, v, l;
100
+ var pairs = [];
101
+
102
+ if (typeof(str) === 'undefined' || str === null || str === '') {
103
+ return pairs;
104
+ }
105
+
106
+ if (str.indexOf('?') === 0) {
107
+ str = str.substring(1);
108
+ }
109
+
110
+ ps = str.toString().split(re.query_separator);
111
+
112
+ for (i = 0, l = ps.length; i < l; i++) {
113
+ p = ps[i];
114
+ n = p.indexOf('=');
115
+
116
+ if (n !== 0) {
117
+ k = decode(p.substring(0, n));
118
+ v = decode(p.substring(n + 1));
119
+ pairs.push(n === -1 ? [p, null] : [k, v]);
120
+ }
121
+
122
+ }
123
+ return pairs;
124
+ }
125
+
126
+ /**
127
+ * Creates a new Uri object
128
+ * @constructor
129
+ * @param {string} str
130
+ */
131
+ function Uri(str) {
132
+ this.uriParts = parseUri(str);
133
+ this.queryPairs = parseQuery(this.uriParts.query);
134
+ this.hasAuthorityPrefixUserPref = null;
135
+ }
136
+
137
+ /**
138
+ * Define getter/setter methods
139
+ */
140
+ ['protocol', 'userInfo', 'host', 'port', 'path', 'anchor'].forEach(function(key) {
141
+ Uri.prototype[key] = function(val) {
142
+ if (typeof val !== 'undefined') {
143
+ this.uriParts[key] = val;
144
+ }
145
+ return this.uriParts[key];
146
+ };
147
+ });
148
+
149
+ /**
150
+ * if there is no protocol, the leading // can be enabled or disabled
151
+ * @param {Boolean} val
152
+ * @return {Boolean}
153
+ */
154
+ Uri.prototype.hasAuthorityPrefix = function(val) {
155
+ if (typeof val !== 'undefined') {
156
+ this.hasAuthorityPrefixUserPref = val;
157
+ }
158
+
159
+ if (this.hasAuthorityPrefixUserPref === null) {
160
+ return (this.uriParts.source.indexOf('//') !== -1);
161
+ } else {
162
+ return this.hasAuthorityPrefixUserPref;
163
+ }
164
+ };
165
+
166
+ Uri.prototype.isColonUri = function (val) {
167
+ if (typeof val !== 'undefined') {
168
+ this.uriParts.isColonUri = !!val;
169
+ } else {
170
+ return !!this.uriParts.isColonUri;
171
+ }
172
+ };
173
+
174
+ /**
175
+ * Serializes the internal state of the query pairs
176
+ * @param {string} [val] set a new query string
177
+ * @return {string} query string
178
+ */
179
+ Uri.prototype.query = function(val) {
180
+ var s = '', i, param, l;
181
+
182
+ if (typeof val !== 'undefined') {
183
+ this.queryPairs = parseQuery(val);
184
+ }
185
+
186
+ for (i = 0, l = this.queryPairs.length; i < l; i++) {
187
+ param = this.queryPairs[i];
188
+ if (s.length > 0) {
189
+ s += '&';
190
+ }
191
+ if (param[1] === null) {
192
+ s += param[0];
193
+ } else {
194
+ s += param[0];
195
+ s += '=';
196
+ if (typeof param[1] !== 'undefined') {
197
+ s += encodeURIComponent(param[1]);
198
+ }
199
+ }
200
+ }
201
+ return s.length > 0 ? '?' + s : s;
202
+ };
203
+
204
+ /**
205
+ * returns the first query param value found for the key
206
+ * @param {string} key query key
207
+ * @return {string} first value found for key
208
+ */
209
+ Uri.prototype.getQueryParamValue = function (key) {
210
+ var param, i, l;
211
+ for (i = 0, l = this.queryPairs.length; i < l; i++) {
212
+ param = this.queryPairs[i];
213
+ if (key === param[0]) {
214
+ return param[1];
215
+ }
216
+ }
217
+ };
218
+
219
+ /**
220
+ * returns an array of query param values for the key
221
+ * @param {string} key query key
222
+ * @return {array} array of values
223
+ */
224
+ Uri.prototype.getQueryParamValues = function (key) {
225
+ var arr = [], i, param, l;
226
+ for (i = 0, l = this.queryPairs.length; i < l; i++) {
227
+ param = this.queryPairs[i];
228
+ if (key === param[0]) {
229
+ arr.push(param[1]);
230
+ }
231
+ }
232
+ return arr;
233
+ };
234
+
235
+ /**
236
+ * removes query parameters
237
+ * @param {string} key remove values for key
238
+ * @param {val} [val] remove a specific value, otherwise removes all
239
+ * @return {Uri} returns self for fluent chaining
240
+ */
241
+ Uri.prototype.deleteQueryParam = function (key, val) {
242
+ var arr = [], i, param, keyMatchesFilter, valMatchesFilter, l;
243
+
244
+ for (i = 0, l = this.queryPairs.length; i < l; i++) {
245
+
246
+ param = this.queryPairs[i];
247
+ keyMatchesFilter = decode(param[0]) === decode(key);
248
+ valMatchesFilter = param[1] === val;
249
+
250
+ if ((arguments.length === 1 && !keyMatchesFilter) || (arguments.length === 2 && (!keyMatchesFilter || !valMatchesFilter))) {
251
+ arr.push(param);
252
+ }
253
+ }
254
+
255
+ this.queryPairs = arr;
256
+
257
+ return this;
258
+ };
259
+
260
+ /**
261
+ * adds a query parameter
262
+ * @param {string} key add values for key
263
+ * @param {string} val value to add
264
+ * @param {integer} [index] specific index to add the value at
265
+ * @return {Uri} returns self for fluent chaining
266
+ */
267
+ Uri.prototype.addQueryParam = function (key, val, index) {
268
+ if (arguments.length === 3 && index !== -1) {
269
+ index = Math.min(index, this.queryPairs.length);
270
+ this.queryPairs.splice(index, 0, [key, val]);
271
+ } else if (arguments.length > 0) {
272
+ this.queryPairs.push([key, val]);
273
+ }
274
+ return this;
275
+ };
276
+
277
+ /**
278
+ * test for the existence of a query parameter
279
+ * @param {string} key check values for key
280
+ * @return {Boolean} true if key exists, otherwise false
281
+ */
282
+ Uri.prototype.hasQueryParam = function (key) {
283
+ var i, len = this.queryPairs.length;
284
+ for (i = 0; i < len; i++) {
285
+ if (this.queryPairs[i][0] == key)
286
+ return true;
287
+ }
288
+ return false;
289
+ };
290
+
291
+ /**
292
+ * replaces query param values
293
+ * @param {string} key key to replace value for
294
+ * @param {string} newVal new value
295
+ * @param {string} [oldVal] replace only one specific value (otherwise replaces all)
296
+ * @return {Uri} returns self for fluent chaining
297
+ */
298
+ Uri.prototype.replaceQueryParam = function (key, newVal, oldVal) {
299
+ var index = -1, len = this.queryPairs.length, i, param;
300
+
301
+ if (arguments.length === 3) {
302
+ for (i = 0; i < len; i++) {
303
+ param = this.queryPairs[i];
304
+ if (decode(param[0]) === decode(key) && decodeURIComponent(param[1]) === decode(oldVal)) {
305
+ index = i;
306
+ break;
307
+ }
308
+ }
309
+ if (index >= 0) {
310
+ this.deleteQueryParam(key, decode(oldVal)).addQueryParam(key, newVal, index);
311
+ }
312
+ } else {
313
+ for (i = 0; i < len; i++) {
314
+ param = this.queryPairs[i];
315
+ if (decode(param[0]) === decode(key)) {
316
+ index = i;
317
+ break;
318
+ }
319
+ }
320
+ this.deleteQueryParam(key);
321
+ this.addQueryParam(key, newVal, index);
322
+ }
323
+ return this;
324
+ };
325
+
326
+ /**
327
+ * Define fluent setter methods (setProtocol, setHasAuthorityPrefix, etc)
328
+ */
329
+ ['protocol', 'hasAuthorityPrefix', 'isColonUri', 'userInfo', 'host', 'port', 'path', 'query', 'anchor'].forEach(function(key) {
330
+ var method = 'set' + key.charAt(0).toUpperCase() + key.slice(1);
331
+ Uri.prototype[method] = function(val) {
332
+ this[key](val);
333
+ return this;
334
+ };
335
+ });
336
+
337
+ /**
338
+ * Scheme name, colon and doubleslash, as required
339
+ * @return {string} http:// or possibly just //
340
+ */
341
+ Uri.prototype.scheme = function() {
342
+ var s = '';
343
+
344
+ if (this.protocol()) {
345
+ s += this.protocol();
346
+ if (this.protocol().indexOf(':') !== this.protocol().length - 1) {
347
+ s += ':';
348
+ }
349
+ s += '//';
350
+ } else {
351
+ if (this.hasAuthorityPrefix() && this.host()) {
352
+ s += '//';
353
+ }
354
+ }
355
+
356
+ return s;
357
+ };
358
+
359
+ /**
360
+ * Same as Mozilla nsIURI.prePath
361
+ * @return {string} scheme://user:password@host:port
362
+ * @see https://developer.mozilla.org/en/nsIURI
363
+ */
364
+ Uri.prototype.origin = function() {
365
+ var s = this.scheme();
366
+
367
+ if (this.userInfo() && this.host()) {
368
+ s += this.userInfo();
369
+ if (this.userInfo().indexOf('@') !== this.userInfo().length - 1) {
370
+ s += '@';
371
+ }
372
+ }
373
+
374
+ if (this.host()) {
375
+ s += this.host();
376
+ if (this.port() || (this.path() && this.path().substr(0, 1).match(/[0-9]/))) {
377
+ s += ':' + this.port();
378
+ }
379
+ }
380
+
381
+ return s;
382
+ };
383
+
384
+ /**
385
+ * Adds a trailing slash to the path
386
+ */
387
+ Uri.prototype.addTrailingSlash = function() {
388
+ var path = this.path() || '';
389
+
390
+ if (path.substr(-1) !== '/') {
391
+ this.path(path + '/');
392
+ }
393
+
394
+ return this;
395
+ };
396
+
397
+ /**
398
+ * Serializes the internal state of the Uri object
399
+ * @return {string}
400
+ */
401
+ Uri.prototype.toString = function() {
402
+ var path, s = this.origin();
403
+
404
+ if (this.isColonUri()) {
405
+ if (this.path()) {
406
+ s += ':'+this.path();
407
+ }
408
+ } else if (this.path()) {
409
+ path = this.path();
410
+ if (!(re.ends_with_slashes.test(s) || re.starts_with_slashes.test(path))) {
411
+ s += '/';
412
+ } else {
413
+ if (s) {
414
+ s.replace(re.ends_with_slashes, '/');
415
+ }
416
+ path = path.replace(re.starts_with_slashes, '/');
417
+ }
418
+ s += path;
419
+ } else {
420
+ if (this.host() && (this.query().toString() || this.anchor())) {
421
+ s += '/';
422
+ }
423
+ }
424
+ if (this.query().toString()) {
425
+ s += this.query().toString();
426
+ }
427
+
428
+ if (this.anchor()) {
429
+ if (this.anchor().indexOf('#') !== 0) {
430
+ s += '#';
431
+ }
432
+ s += this.anchor();
433
+ }
434
+
435
+ return s;
436
+ };
437
+
438
+ /**
439
+ * Clone a Uri object
440
+ * @return {Uri} duplicate copy of the Uri
441
+ */
442
+ Uri.prototype.clone = function() {
443
+ return new Uri(this.toString());
444
+ };
445
+
446
+ /**
447
+ * export via AMD or CommonJS, otherwise leak a global
448
+ */
449
+ if (typeof define === 'function' && define.amd) {
450
+ define(function() {
451
+ return Uri;
452
+ });
453
+ } else if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
454
+ module.exports = Uri;
455
+ } else {
456
+ global.Uri = Uri;
457
+ }
458
+ }(this));