spree_core 3.4.6 → 3.5.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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));