domainnameapi 1.0.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +47 -0
- data/LICENSE +201 -0
- data/README.md +995 -0
- data/lib/domainnameapi/client.rb +294 -0
- data/lib/domainnameapi/php.rb +322 -0
- data/lib/domainnameapi/rest.rb +1747 -0
- data/lib/domainnameapi/telemetry.rb +472 -0
- data/lib/domainnameapi/transport.rb +89 -0
- data/lib/domainnameapi/version.rb +6 -0
- data/lib/domainnameapi.rb +16 -0
- metadata +56 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DomainNameApi
|
|
4
|
+
# Public facade, the Ruby counterpart of php-dna's DomainNameAPI_PHPLibrary.
|
|
5
|
+
#
|
|
6
|
+
# dna = DomainNameApi::Client.new('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 'your-api-key')
|
|
7
|
+
# dna.get_details('example.com')
|
|
8
|
+
# # => { 'data' => { 'DomainName' => 'example.com', ... }, 'result' => 'OK' }
|
|
9
|
+
#
|
|
10
|
+
# Every network method returns php-dna's result structure (Hashes with
|
|
11
|
+
# String keys) and never raises for API or transport errors: failures come
|
|
12
|
+
# back as { 'result' => 'ERROR', 'error' => { 'Code', 'Message', 'Details' } }
|
|
13
|
+
# (get_current_balance uses its own OperationResult envelope).
|
|
14
|
+
class Client
|
|
15
|
+
attr_reader :rest
|
|
16
|
+
|
|
17
|
+
# @param reseller_id [String] Reseller ID (UUID) from the panel's API settings
|
|
18
|
+
# @param api_key [String] API key from the panel's API settings
|
|
19
|
+
# @param test_mode [Boolean, String] true (or "true"/"1"/"on"/"yes") for the OTE test gateway
|
|
20
|
+
# @param transport [#call, nil] custom HTTP transport (see NetHttpTransport)
|
|
21
|
+
# @param sentry_dsn [String, nil] override the telemetry DSN ('' disables telemetry)
|
|
22
|
+
# @param zone_min_interval [Numeric, nil] seconds between /domains/zones calls (default 1.0)
|
|
23
|
+
def initialize(reseller_id = '', api_key = '', test_mode = false, transport: nil, sentry_dsn: nil,
|
|
24
|
+
zone_min_interval: nil)
|
|
25
|
+
reseller_id = Php.trim(reseller_id)
|
|
26
|
+
api_key = Php.trim(api_key)
|
|
27
|
+
test_mode = test_mode.is_a?(String) ? Php.filter_bool(test_mode) : Php.truthy?(test_mode)
|
|
28
|
+
@rest = Rest.new(reseller_id, api_key, test_mode, transport: transport, sentry_dsn: sentry_dsn,
|
|
29
|
+
zone_min_interval: zone_min_interval)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Account
|
|
33
|
+
|
|
34
|
+
def get_reseller_details
|
|
35
|
+
@rest.get_reseller_details
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def get_current_balance(currency_id = 'USD')
|
|
39
|
+
@rest.get_current_balance(currency_id)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Search and lists
|
|
43
|
+
|
|
44
|
+
def check_availability(domains, extensions, period, command = 'create')
|
|
45
|
+
@rest.check_availability(domains, extensions, period, command)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def get_list(extra_parameters = {})
|
|
49
|
+
@rest.get_list(extra_parameters)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def get_tld_list(count = 20)
|
|
53
|
+
@rest.get_tld_list(count)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Domain
|
|
57
|
+
|
|
58
|
+
def get_details(domain_name)
|
|
59
|
+
@rest.get_details(domain_name)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def sync_from_registry(domain_name)
|
|
63
|
+
@rest.sync_from_registry(domain_name)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def get_contacts(domain_name)
|
|
67
|
+
@rest.get_contacts(domain_name)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def save_contacts(domain_name, contacts)
|
|
71
|
+
@rest.save_contacts(domain_name, contacts)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def modify_name_server(domain_name, name_servers)
|
|
75
|
+
@rest.modify_name_server(domain_name, name_servers)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def enable_theft_protection_lock(domain_name)
|
|
79
|
+
@rest.enable_theft_protection_lock(domain_name)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def disable_theft_protection_lock(domain_name)
|
|
83
|
+
@rest.disable_theft_protection_lock(domain_name)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# `status` accepts true/false and 1/"1"/"on"/"true"/"yes"; `reason` is
|
|
87
|
+
# accepted for compatibility and not sent (the gateway has no such field).
|
|
88
|
+
def modify_privacy_protection_status(domain_name, status, reason = 'Owner request')
|
|
89
|
+
@rest.modify_privacy_protection_status(domain_name, status, reason)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def renew(domain_name, period)
|
|
93
|
+
@rest.renew(domain_name, period)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def register_with_contact_info(domain_name, period, contacts, name_servers = [], epp_lock = true,
|
|
97
|
+
privacy_lock = false, additional_attributes = {})
|
|
98
|
+
@rest.register_with_contact_info(domain_name, period, contacts, name_servers, epp_lock, privacy_lock,
|
|
99
|
+
additional_attributes)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Child nameservers
|
|
103
|
+
|
|
104
|
+
def add_child_name_server(domain_name, name_server, ip_address)
|
|
105
|
+
@rest.add_child_name_server(domain_name, name_server, ip_address)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def delete_child_name_server(domain_name, name_server)
|
|
109
|
+
@rest.delete_child_name_server(domain_name, name_server)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def modify_child_name_server(domain_name, name_server, ip_address)
|
|
113
|
+
@rest.modify_child_name_server(domain_name, name_server, ip_address)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Transfers
|
|
117
|
+
|
|
118
|
+
# Contacts are never sent with a transfer: the registrar handles them,
|
|
119
|
+
# so the request always carries an empty contact list.
|
|
120
|
+
def transfer(domain_name, epp_code, period)
|
|
121
|
+
@rest.transfer(domain_name, epp_code, period)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def cancel_transfer(domain_name)
|
|
125
|
+
@rest.cancel_transfer(domain_name)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def approve_transfer(domain_name)
|
|
129
|
+
@rest.approve_transfer(domain_name)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def reject_transfer(domain_name)
|
|
133
|
+
@rest.reject_transfer(domain_name)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def check_transfer(domain_name, auth_code)
|
|
137
|
+
@rest.check_transfer(domain_name, auth_code)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# DNS zone
|
|
141
|
+
|
|
142
|
+
def get_resource_records(domain_name)
|
|
143
|
+
@rest.get_resource_records(domain_name)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def add_resource_record(domain_name, name, type, content, ttl = 3600, apply = true)
|
|
147
|
+
@rest.add_resource_record(domain_name, name, type, content, ttl, apply)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def edit_resource_record(domain_name, record_name, name, type, content, ttl = 3600, apply = true)
|
|
151
|
+
@rest.edit_resource_record(domain_name, record_name, name, type, content, ttl, apply)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def delete_resource_record(domain_name, name, record, record_type, apply = true)
|
|
155
|
+
@rest.delete_resource_record(domain_name, name, record, record_type, apply)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def apply_resource_records(domain_name)
|
|
159
|
+
@rest.apply_resource_records(domain_name)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def set_resource_record_set(domain_name, name, type, contents, ttl = 3600, apply = true)
|
|
163
|
+
@rest.set_resource_record_set(domain_name, name, type, contents, ttl, apply)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def delete_resource_record_set(domain_name, name, type, apply = true, known_value = '')
|
|
167
|
+
@rest.delete_resource_record_set(domain_name, name, type, apply, known_value)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def add_resource_record_value(domain_name, name, type, value, ttl = nil, apply = true)
|
|
171
|
+
@rest.add_resource_record_value(domain_name, name, type, value, ttl, apply)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def remove_resource_record_value(domain_name, name, type, value, apply = true)
|
|
175
|
+
@rest.remove_resource_record_value(domain_name, name, type, value, apply)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def replace_resource_record_value(domain_name, name, type, old_value, new_value, ttl = nil, apply = true)
|
|
179
|
+
@rest.replace_resource_record_value(domain_name, name, type, old_value, new_value, ttl, apply)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Forwarding
|
|
183
|
+
|
|
184
|
+
def get_forwarding(domain_name)
|
|
185
|
+
@rest.get_forwarding(domain_name)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def set_forwarding(domain_name, redirect_address, forward_type = 'Standard')
|
|
189
|
+
@rest.set_forwarding(domain_name, redirect_address, forward_type)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def delete_forwarding(domain_name)
|
|
193
|
+
@rest.delete_forwarding(domain_name)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Utilities (no network)
|
|
197
|
+
|
|
198
|
+
def is_tr_tld(domain)
|
|
199
|
+
@rest.is_tr_tld(domain)
|
|
200
|
+
end
|
|
201
|
+
alias tr_tld? is_tr_tld
|
|
202
|
+
|
|
203
|
+
def validate_contact(contact)
|
|
204
|
+
@rest.validate_contact(contact)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Diagnostics
|
|
208
|
+
|
|
209
|
+
def get_request_data
|
|
210
|
+
@rest.get_request_data
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def set_request_data(request)
|
|
214
|
+
@rest.set_request_data(request)
|
|
215
|
+
self
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def get_response_data
|
|
219
|
+
@rest.get_response_data
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def set_response_data(response)
|
|
223
|
+
@rest.set_response_data(response)
|
|
224
|
+
self
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def get_response_headers
|
|
228
|
+
@rest.get_response_headers
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def set_response_headers(headers)
|
|
232
|
+
@rest.set_response_headers(headers)
|
|
233
|
+
self
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def get_parsed_response_data
|
|
237
|
+
@rest.get_parsed_response_data
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def set_parsed_response_data(response)
|
|
241
|
+
@rest.set_parsed_response_data(response)
|
|
242
|
+
self
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def get_last_function
|
|
246
|
+
@rest.get_last_function
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def set_last_function(function)
|
|
250
|
+
@rest.set_last_function(function)
|
|
251
|
+
self
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def get_service_url
|
|
255
|
+
@rest.get_service_url
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def set_service_url(url)
|
|
259
|
+
@rest.set_service_url(url)
|
|
260
|
+
nil
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Settings
|
|
264
|
+
|
|
265
|
+
def set_error_reporting_enabled(enabled)
|
|
266
|
+
@rest.set_error_reporting_enabled(enabled)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def set_telemetry_minimal(minimal)
|
|
270
|
+
@rest.set_telemetry_minimal(minimal)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def set_ssl_verification(enabled)
|
|
274
|
+
@rest.set_ssl_verification(enabled)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def set_sentry_dsn(dsn)
|
|
278
|
+
@rest.set_sentry_dsn(dsn)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def set_telemetry_sender(sender)
|
|
282
|
+
@rest.set_telemetry_sender(sender)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def set_transport(transport)
|
|
286
|
+
@rest.set_transport(transport)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def set_zone_min_interval(seconds)
|
|
290
|
+
@rest.zone_min_interval = seconds.to_f
|
|
291
|
+
nil
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module DomainNameApi
|
|
6
|
+
# PHP value semantics used by php-dna (empty(), casts, number_format(),
|
|
7
|
+
# http_build_query(), ...). The library mirrors php-dna's results exactly,
|
|
8
|
+
# so these helpers reproduce what PHP does rather than what Ruby would do.
|
|
9
|
+
# Internal API.
|
|
10
|
+
module Php
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
TRIM_CHARS = " \t\n\r\0\x0B"
|
|
14
|
+
NUMERIC_RE = /\A[ \t\n\r\v\f]*[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?[ \t\n\r\v\f]*\z/.freeze
|
|
15
|
+
LEADING_NUMERIC_RE = /\A[ \t\n\r\v\f]*[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?/.freeze
|
|
16
|
+
|
|
17
|
+
# A PHP array: either a Ruby Hash (associative) or Array (list).
|
|
18
|
+
def array?(value)
|
|
19
|
+
value.is_a?(Hash) || value.is_a?(Array)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# PHP empty(): null, false, 0, 0.0, "", "0" and empty arrays.
|
|
23
|
+
def empty?(value)
|
|
24
|
+
case value
|
|
25
|
+
when nil, false then true
|
|
26
|
+
when Integer, Float then value.zero?
|
|
27
|
+
when String then value.empty? || value == '0'
|
|
28
|
+
when Hash, Array then value.empty?
|
|
29
|
+
else false
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# PHP truthiness ((bool) cast).
|
|
34
|
+
def truthy?(value)
|
|
35
|
+
!empty?(value)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# $array[$key] with PHP's ?? semantics: nil when the container is not an
|
|
39
|
+
# array or the key is absent. Several keys walk nested arrays.
|
|
40
|
+
def get(container, *keys)
|
|
41
|
+
keys.each do |key|
|
|
42
|
+
container = fetch_one(container, key)
|
|
43
|
+
return nil if container.nil?
|
|
44
|
+
end
|
|
45
|
+
container
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def fetch_one(container, key)
|
|
49
|
+
case container
|
|
50
|
+
when Hash
|
|
51
|
+
return container[key] if container.key?(key)
|
|
52
|
+
|
|
53
|
+
skey = key.is_a?(Symbol) ? key.to_s : key
|
|
54
|
+
return container[skey] if container.key?(skey)
|
|
55
|
+
return container[key.to_s] if key.is_a?(Integer) && container.key?(key.to_s)
|
|
56
|
+
return container[key.to_i] if key.is_a?(String) && key =~ /\A(0|-?[1-9]\d*)\z/ && container.key?(key.to_i)
|
|
57
|
+
|
|
58
|
+
nil
|
|
59
|
+
when Array
|
|
60
|
+
index = if key.is_a?(Integer)
|
|
61
|
+
key
|
|
62
|
+
elsif key.is_a?(String) && key =~ /\A(0|[1-9]\d*)\z/
|
|
63
|
+
key.to_i
|
|
64
|
+
end
|
|
65
|
+
index.nil? || index.negative? ? nil : container[index]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# PHP isset($a[$k]...): present and not null.
|
|
70
|
+
def isset?(container, *keys)
|
|
71
|
+
!get(container, *keys).nil?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# foreach ($array as $v): the values of a Hash or Array.
|
|
75
|
+
def values(value)
|
|
76
|
+
case value
|
|
77
|
+
when Hash then value.values
|
|
78
|
+
when Array then value
|
|
79
|
+
else []
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# foreach ($array as $k => $v) pairs; list indexes become integer keys.
|
|
84
|
+
def pairs(value)
|
|
85
|
+
case value
|
|
86
|
+
when Hash then value.to_a
|
|
87
|
+
when Array then value.each_with_index.map { |v, i| [i, v] }
|
|
88
|
+
else []
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# (array) cast.
|
|
93
|
+
def to_array(value)
|
|
94
|
+
case value
|
|
95
|
+
when nil then {}
|
|
96
|
+
when Hash, Array then value
|
|
97
|
+
else [value]
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# A Hash view of a PHP array (list indexes become string keys).
|
|
102
|
+
def to_hash(value)
|
|
103
|
+
case value
|
|
104
|
+
when Hash then stringify_keys(value)
|
|
105
|
+
when Array then value.each_with_index.to_h { |v, i| [i.to_s, v] }
|
|
106
|
+
else {}
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Deep copy with String keys (Ruby callers may pass Symbol keys).
|
|
111
|
+
def stringify_keys(value)
|
|
112
|
+
case value
|
|
113
|
+
when Hash then value.each_with_object({}) { |(k, v), h| h[k.is_a?(Symbol) ? k.to_s : k] = stringify_keys(v) }
|
|
114
|
+
when Array then value.map { |v| stringify_keys(v) }
|
|
115
|
+
else value
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# (string) cast.
|
|
120
|
+
def to_s(value)
|
|
121
|
+
case value
|
|
122
|
+
when nil, false then ''
|
|
123
|
+
when true then '1'
|
|
124
|
+
when String then value
|
|
125
|
+
when Float then float_to_s(value)
|
|
126
|
+
when Integer then value.to_s
|
|
127
|
+
when Symbol then value.to_s
|
|
128
|
+
when Hash, Array then 'Array'
|
|
129
|
+
else value.to_s
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# (string) of a float, PHP 8 style (precision=14).
|
|
134
|
+
def float_to_s(value)
|
|
135
|
+
return 'NAN' if value.nan?
|
|
136
|
+
return(value.positive? ? 'INF' : '-INF') if value.infinite?
|
|
137
|
+
return '-0' if value.zero? && (1.0 / value).negative?
|
|
138
|
+
return '0' if value.zero?
|
|
139
|
+
|
|
140
|
+
str = format('%.14G', value)
|
|
141
|
+
if str.include?('E')
|
|
142
|
+
mantissa, exp = str.split('E')
|
|
143
|
+
mantissa = mantissa.sub(/(\.\d*?)0+\z/, '\1').sub(/\.\z/, '')
|
|
144
|
+
mantissa += '.0' unless mantissa.include?('.')
|
|
145
|
+
sign = exp.start_with?('-') ? '-' : '+'
|
|
146
|
+
"#{mantissa}E#{sign}#{exp.sub(/\A[+-]/, '').sub(/\A0+(?=\d)/, '')}"
|
|
147
|
+
else
|
|
148
|
+
str.include?('.') ? str.sub(/0+\z/, '').sub(/\.\z/, '') : str
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# (int) cast.
|
|
153
|
+
def to_i(value)
|
|
154
|
+
case value
|
|
155
|
+
when nil, false then 0
|
|
156
|
+
when true then 1
|
|
157
|
+
when Integer then value
|
|
158
|
+
when Float then value.nan? || value.infinite? ? 0 : value.truncate
|
|
159
|
+
when String
|
|
160
|
+
m = LEADING_NUMERIC_RE.match(value)
|
|
161
|
+
return 0 unless m
|
|
162
|
+
return m[0].strip.to_i if m[2].nil? && m[3].nil? && !m[0].include?('.')
|
|
163
|
+
|
|
164
|
+
f = m[0].strip.to_f
|
|
165
|
+
f.nan? || f.infinite? ? 0 : f.truncate
|
|
166
|
+
when Hash, Array then value.empty? ? 0 : 1
|
|
167
|
+
else 0
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# (float) cast.
|
|
172
|
+
def to_f(value)
|
|
173
|
+
case value
|
|
174
|
+
when nil, false then 0.0
|
|
175
|
+
when true then 1.0
|
|
176
|
+
when Integer, Float then value.to_f
|
|
177
|
+
when String
|
|
178
|
+
m = LEADING_NUMERIC_RE.match(value)
|
|
179
|
+
m ? m[0].strip.to_f : 0.0
|
|
180
|
+
when Hash, Array then value.empty? ? 0.0 : 1.0
|
|
181
|
+
else 0.0
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# is_numeric().
|
|
186
|
+
def numeric?(value)
|
|
187
|
+
case value
|
|
188
|
+
when Integer, Float then true
|
|
189
|
+
when String then NUMERIC_RE.match?(value)
|
|
190
|
+
else false
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# trim() with PHP's default character list (or a custom one).
|
|
195
|
+
def trim(value, chars = TRIM_CHARS)
|
|
196
|
+
rtrim(ltrim(value, chars), chars)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def ltrim(value, chars = TRIM_CHARS)
|
|
200
|
+
str = to_s(value)
|
|
201
|
+
i = 0
|
|
202
|
+
i += 1 while i < str.length && chars.include?(str[i])
|
|
203
|
+
str[i..] || ''
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def rtrim(value, chars = TRIM_CHARS)
|
|
207
|
+
str = to_s(value)
|
|
208
|
+
j = str.length
|
|
209
|
+
j -= 1 while j.positive? && chars.include?(str[j - 1])
|
|
210
|
+
str[0, j] || ''
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# strtolower()/strtoupper()/ucfirst(): ASCII only, like PHP 8.
|
|
214
|
+
def lower(value)
|
|
215
|
+
to_s(value).downcase(:ascii)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def upper(value)
|
|
219
|
+
to_s(value).upcase(:ascii)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def ucfirst(value)
|
|
223
|
+
str = to_s(value)
|
|
224
|
+
str.empty? ? str : str[0].upcase(:ascii) + str[1..]
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# strcasecmp($a, $b) === 0
|
|
228
|
+
def casecmp_eq?(a, b)
|
|
229
|
+
lower(a) == lower(b)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# number_format($value, $decimals, '.', '')
|
|
233
|
+
def number_format(value, decimals)
|
|
234
|
+
f = to_f(value)
|
|
235
|
+
return f.to_s if f.nan? || f.infinite?
|
|
236
|
+
|
|
237
|
+
rounded = Rational(f.to_s.sub(/e/, 'E')).round(decimals, half: :up)
|
|
238
|
+
negative = rounded.negative?
|
|
239
|
+
rounded = rounded.abs
|
|
240
|
+
scaled = (rounded * (10**decimals)).to_i
|
|
241
|
+
int_part = scaled / (10**decimals)
|
|
242
|
+
frac_part = scaled % (10**decimals)
|
|
243
|
+
str = int_part.to_s
|
|
244
|
+
str += ".#{frac_part.to_s.rjust(decimals, '0')}" if decimals.positive?
|
|
245
|
+
negative && scaled.positive? ? "-#{str}" : str
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# http_build_query($data): nulls skipped, true/false as 1/0, nested
|
|
249
|
+
# arrays as key[0]=..., RFC 1738 encoding (space as +).
|
|
250
|
+
def http_build_query(data, prefix = nil)
|
|
251
|
+
parts = []
|
|
252
|
+
pairs(data).each do |key, value|
|
|
253
|
+
next if value.nil?
|
|
254
|
+
|
|
255
|
+
name = prefix ? "#{prefix}[#{key}]" : key.to_s
|
|
256
|
+
if array?(value)
|
|
257
|
+
nested = http_build_query(value, name)
|
|
258
|
+
parts << nested unless nested.empty?
|
|
259
|
+
else
|
|
260
|
+
parts << "#{urlencode(name)}=#{urlencode(query_scalar(value))}"
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
parts.join('&')
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def query_scalar(value)
|
|
267
|
+
case value
|
|
268
|
+
when true then '1'
|
|
269
|
+
when false then '0'
|
|
270
|
+
else to_s(value)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# urlencode(): everything except [A-Za-z0-9_.-] is %XX, space is +.
|
|
275
|
+
def urlencode(value)
|
|
276
|
+
to_s(value).b.gsub(/[^A-Za-z0-9_.\-]/n) do |c|
|
|
277
|
+
c == ' ' ? '+' : format('%%%02X', c.ord)
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# json_decode($body, true): nil for empty or invalid JSON.
|
|
282
|
+
def json_decode(body)
|
|
283
|
+
return nil unless body.is_a?(String)
|
|
284
|
+
body = body.dup.force_encoding(Encoding::UTF_8) unless body.encoding == Encoding::UTF_8
|
|
285
|
+
return nil unless body.valid_encoding?
|
|
286
|
+
return nil if body.strip.empty?
|
|
287
|
+
|
|
288
|
+
JSON.parse(body)
|
|
289
|
+
rescue JSON::ParserError, EncodingError
|
|
290
|
+
nil
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# json_encode() of a value already built in PHP's shape.
|
|
294
|
+
def json_encode(value)
|
|
295
|
+
JSON.generate(value)
|
|
296
|
+
rescue StandardError
|
|
297
|
+
JSON.generate(scrub_utf8(value))
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def scrub_utf8(value)
|
|
301
|
+
case value
|
|
302
|
+
when String then value.dup.force_encoding(Encoding::UTF_8).scrub("�")
|
|
303
|
+
when Hash then value.each_with_object({}) { |(k, v), h| h[scrub_utf8(k)] = scrub_utf8(v) }
|
|
304
|
+
when Array then value.map { |v| scrub_utf8(v) }
|
|
305
|
+
else value
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)
|
|
310
|
+
IPV4_OCTET = '(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)'
|
|
311
|
+
IPV4_RE = /\A#{IPV4_OCTET}(\.#{IPV4_OCTET}){3}\z/.freeze
|
|
312
|
+
|
|
313
|
+
def ipv4?(value)
|
|
314
|
+
value.is_a?(String) || value.is_a?(Integer) ? IPV4_RE.match?(to_s(value)) : false
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# filter_var($value, FILTER_VALIDATE_BOOLEAN)
|
|
318
|
+
def filter_bool(value)
|
|
319
|
+
%w[1 true on yes].include?(trim(value).downcase)
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|