Revision 827
Date:
2021/09/08 15:14:01
Author:
ahitrov
Revision Log:
CDEK service
Files:
Legend:
Added
Removed
Modified
utf8/plugins/webshop/lib/webshop/Service/Base.pm
1
package webshop::Service::Base;
2
3
use strict;
4
use warnings;
5
6
use Digest::MD5;
7
use Data::Dumper;
8
use MIME::Base64;
9
use JSON::XS;
10
use JSON::XS::Boolean;
11
12
use parent 'Contenido::Accessor';
13
__PACKAGE__->mk_accessors(qw(token key api_url error));
14
15
use Contenido::Globals;
16
17
sub new {
18
my ($proto, $args) = @_;
19
my $class = ref($proto) || $proto;
20
$args //= {};
21
my $self = {};
22
bless $self, $class;
23
24
if ( ref $args ne 'HASH' ) {
25
$self->error("INIT: argument list is empty");
26
return $self;
27
}
28
29
if ( exists $args->{api_url} && $args->{api_url} ) {
30
$self->api_url($args->{api_url});
31
} else {
32
$self->error("INIT: API url is empty");
33
return $self;
34
}
35
36
if ( exists $args->{token} && $args->{token} ) {
37
$self->token($args->{token});
38
} else {
39
$self->error("INIT: API token is empty");
40
return $self;
41
}
42
43
if ( exists $args->{key} && $args->{key} ) {
44
$self->key($args->{auth_key});
45
} else {
46
$self->error("INIT: API auth key is empty");
47
return $self;
48
}
49
50
warn Dumper $self if $DEBUG;
51
return $self;
52
}
53
54
55
sub _MakeRequest {
56
my ($self, $url, $type, $body, $cache_control) = @_;
57
58
$type //= 'get';
59
60
my ($key, $file, $exists, $expired);
61
my $time = time();
62
my $cache_container = $state->{data_dir}. '/webshop_service_cache';
63
if ( ref $cache_control ) {
64
unless ( -e $cache_container ) {
65
`mkdir -p $cache_container`;
66
}
67
$key = 'service_api_'.Digest::MD5::md5_hex( $url );
68
$file = $cache_container . '/' . $key;
69
if ( -e $file ) {
70
$exists = 1;
71
my $created = (stat $file)[9];
72
if ( exists $cache_control->{expires_at} && $cache_control->{expires_at} > 0 ) {
73
if ( $time - $created > $cache_control->{expires_at} ) {
74
$expired = 1;
75
}
76
} elsif ( exists $cache_control->{expires_at} && $cache_control->{expires_at} < 0 ) {
77
$expired = 0;
78
} else {
79
$expired = 1;
80
}
81
} else {
82
$expired = 1;
83
}
84
if ( $exists && !$expired ) {
85
my $content = do $file;
86
return $content;
87
}
88
}
89
90
91
my $ua = LWP::UserAgent->new;
92
$ua->timeout( 10 );
93
$ua->agent('Mozilla/5.0');
94
95
if ( $self->token ) {
96
warn "Auth by token: ".$self->token."\n" if $DEBUG;
97
$ua->default_header( 'Authorization' => "AccessToken ".$self->token );
98
}
99
if ( $self->key ) {
100
warn "Auth by auth key: ".$self->key."\n" if $DEBUG;
101
$ua->default_header( 'X-User-Authorization' => "Basic ".$self->key );
102
}
103
$ua->default_header( 'Content-Type' => 'application/json' );
104
$ua->default_header( 'Accept' => 'application/json;charset=UTF-8' );
105
106
if ( ref $body ) {
107
$body = encode_json( $body );
108
}
109
110
my $uri;
111
if ( $url =~ /^http/ ) {
112
$uri = URI->new( $url );
113
} else {
114
$uri = URI->new( $self->api_url.($url =~ /^\// ? '' : '/').$url );
115
}
116
117
my $res;
118
if ( $type eq 'post' ) {
119
my $req = HTTP::Request->new(POST => $uri);
120
$req->content_type('application/json');
121
$req->content($body);
122
# warn "RUPOST post JSON: $body\n" if $DEBUG;
123
$res = $ua->request($req);
124
} elsif ( $type eq 'delete' ) {
125
$res = $ua->delete( $uri );
126
} else {
127
$res = $ua->get( $uri );
128
}
129
130
warn Dumper($res) if $DEBUG && $res->code != 200;
131
if ( $res->code != 200 && $exists ) {
132
my $content = do $file;
133
return $content;
134
}
135
136
my $result = {
137
code => $res->code,
138
status => $res->status_line,
139
content => JSON::XS->new->utf8->decode( $res->decoded_content ),
140
};
141
142
if ( $res->code == 200 && ref $cache_control ) {
143
my $fh;
144
if ( open($fh, '>', $file) ) {
145
local $Data::Dumper::Indent = 0;
146
print $fh Dumper( $result );
147
close $fh;
148
}
149
}
150
151
return $result;
152
}
153
154
1;
utf8/plugins/webshop/lib/webshop/Service/CDEK.pm
1
package webshop::Service::CDEK;
2
3
use strict;
4
use warnings 'all';
5
6
use base 'webshop::Service::Base';
7
use Contenido::Globals;
8
use payments::Keeper;
9
use URI::Escape;
10
use Types::Serialiser;
11
use Digest::MD5;
12
use Data::Dumper;
13
use utf8;
14
15
use constant {
16
COURIER_TARIFF_PRIORITY => 'courier',
17
PICKUP_TARIFF_PRIORITY => 'pickup',
18
};
19
20
our $translate = {
21
'rus' => {
22
'YOURCITY' => 'Ваш город',
23
'COURIER' => 'Курьер',
24
'PICKUP' => 'Самовывоз',
25
'TERM' => 'Срок',
26
'PRICE' => 'Стоимость',
27
'DAY' => 'дн.',
28
'RUB' => ' руб.',
29
'KZT' => 'KZT',
30
'USD' => 'USD',
31
'EUR' => 'EUR',
32
'GBP' => 'GBP',
33
'CNY' => 'CNY',
34
'BYN' => 'BYN',
35
'UAH' => 'UAH',
36
'KGS' => 'KGS',
37
'AMD' => 'AMD',
38
'TRY' => 'TRY',
39
'THB' => 'THB',
40
'KRW' => 'KRW',
41
'AED' => 'AED',
42
'UZS' => 'UZS',
43
'MNT' => 'MNT',
44
'NODELIV' => 'Нет доставки',
45
'CITYSEARCH' => 'Поиск города',
46
'ALL' => 'Все',
47
'PVZ' => 'Пункты выдачи',
48
'POSTOMAT' => 'Постаматы',
49
'MOSCOW' => 'Москва',
50
'RUSSIA' => 'Россия',
51
'COUNTING' => 'Идет расчет',
52
53
'NO_AVAIL' => 'Нет доступных способов доставки',
54
'CHOOSE_TYPE_AVAIL' => 'Выберите способ доставки',
55
'CHOOSE_OTHER_CITY' => 'Выберите другой населенный пункт',
56
57
'TYPE_ADDRESS' => 'Уточните адрес',
58
'TYPE_ADDRESS_HERE' => 'Введите адрес доставки',
59
60
'L_ADDRESS' => 'Адрес пункта выдачи заказов',
61
'L_TIME' => 'Время работы',
62
'L_WAY' => 'Как к нам проехать',
63
'L_CHOOSE' => 'Выбрать',
64
65
'H_LIST' => 'Список пунктов выдачи заказов',
66
'H_PROFILE' => 'Способ доставки',
67
'H_CASH' => 'Расчет картой',
68
'H_DRESS' => 'С примеркой',
69
'H_POSTAMAT' => 'Постаматы СДЭК',
70
'H_SUPPORT' => 'Служба поддержки',
71
'H_QUESTIONS' => 'Если у вас есть вопросы, можете<br> задать их нашим специалистам',
72
'ADDRESS_WRONG' => 'Невозможно определить выбранное местоположение. Уточните адрес из выпадающего списка в адресной строке.',
73
'ADDRESS_ANOTHER' => 'Ознакомьтесь с новыми условиями доставки для выбранного местоположения.'
74
},
75
'eng' => {
76
'YOURCITY' => 'Your city',
77
'COURIER' => 'Courier',
78
'PICKUP' => 'Pickup',
79
'TERM' => 'Term',
80
'PRICE' => 'Price',
81
'DAY' => 'days',
82
'RUB' => 'RUB',
83
'KZT' => 'KZT',
84
'USD' => 'USD',
85
'EUR' => 'EUR',
86
'GBP' => 'GBP',
87
'CNY' => 'CNY',
88
'BYN' => 'BYN',
89
'UAH' => 'UAH',
90
'KGS' => 'KGS',
91
'AMD' => 'AMD',
92
'TRY' => 'TRY',
93
'THB' => 'THB',
94
'KRW' => 'KRW',
95
'AED' => 'AED',
96
'UZS' => 'UZS',
97
'MNT' => 'MNT',
98
'NODELIV' => 'Not delivery',
99
'CITYSEARCH' => 'Search for a city',
100
'ALL' => 'All',
101
'PVZ' => 'Points of self-delivery',
102
'POSTOMAT' => 'Postamats',
103
'MOSCOW' => 'Moscow',
104
'RUSSIA' => 'Russia',
105
'COUNTING' => 'Calculation',
106
107
'NO_AVAIL' => 'No shipping methods available',
108
'CHOOSE_TYPE_AVAIL' => 'Choose a shipping method',
109
'CHOOSE_OTHER_CITY' => 'Choose another location',
110
111
'TYPE_ADDRESS' => 'Specify the address',
112
'TYPE_ADDRESS_HERE' => 'Enter the delivery address',
113
114
'L_ADDRESS' => 'Adress of self-delivery',
115
'L_TIME' => 'Working hours',
116
'L_WAY' => 'How to get to us',
117
'L_CHOOSE' => 'Choose',
118
119
'H_LIST' => 'List of self-delivery',
120
'H_PROFILE' => 'Shipping method',
121
'H_CASH' => 'Payment by card',
122
'H_DRESS' => 'Dressing room',
123
'H_POSTAMAT' => 'Postamats CDEK',
124
'H_SUPPORT' => 'Support',
125
'H_QUESTIONS' => 'If you have any questions,<br> you can ask them to our specialists',
126
127
'ADDRESS_WRONG' => 'Impossible to define address. Please, recheck the address.',
128
'ADDRESS_ANOTHER' => 'Read the new terms and conditions.'
129
},
130
};
131
132
133
sub new {
134
my ($proto, %params) = @_;
135
my $class = ref($proto) || $proto;
136
my $m = shift;
137
138
my $self = {};
139
bless $self, $class;
140
141
my $obj = $self->SUPER::new( { api_url => 'http://api.cdek.ru' } );
142
$obj->{m} = $m;
143
144
$obj->{courierTariffPriority} = $state->{webshop}->cdek_courier_tariff_priority;
145
$obj->{pickupTariffPriority} = $state->{webshop}->cdek_pickup_tariff_priority;
146
$obj->{account} = $state->{webshop}->cdek_account;
147
$obj->{key} = $state->{webshop}->cdek_key;
148
149
return $obj;
150
}
151
152
153
#############################################################################################
154
### Actions
155
#############################################################################################
156
sub getPVZ {
157
my $self = shift;
158
my $args = shift || {};
159
my $langPart = $args->{'lang'} && exists $translate->{$args->{'lang'}} ? '&lang=' . $args->{'lang'} : '';
160
my $cacheKey = 'cdek_getPVZ_' . $args->{'lang'} . '_cnt_' . $args->{country};
161
if ( $keeper->MEMD && !$args->{no_cache} ) {
162
my $cached = $keeper->MEMD->get($cacheKey);
163
if ( $cached ) {
164
return { 'pvz' => $cached };
165
}
166
}
167
my $result = $args->{raw_data} ? $args->{raw_data} : $self->_MakeRequest( 'https://integration.cdek.ru/pvzlist/v1/json?type=ALL' . $langPart, 'get', undef, { expires_at => 30 * 3600 } );
168
my $out = {
169
'PVZ' => {},
170
'CITY' => {},
171
'REGIONS' => {},
172
'CITYFULL' => {},
173
'COUNTRIES' => {},
174
};
175
my $countryRequested = Encode::decode('utf-8', $args->{country});
176
if ( $result->{code} == 200 && exists $result->{content}{pvz} && ref $result->{content}{pvz} eq 'ARRAY' ) {
177
# warn "getPVZ pvz found: ".scalar(@{$result->{content}{pvz}})."\n";
178
foreach my $val ( @{$result->{content}{pvz}} ) {
179
if ( $countryRequested ne 'all' && $countryRequested ne $val->{countryName} ) {
180
next;
181
}
182
my $cityCode = $val->{cityCode};
183
my $type = 'PVZ';
184
my $city = $val->{city};
185
if ( my $pos = index($city, '(') >= 0 ) {
186
$city = trim(substr($city, 0, $pos));
187
}
188
if ( my $pos = index($city, ',') >= 0 ) {
189
$city = trim(substr($city, 0, $pos));
190
}
191
my $code = $val->{code};
192
$out->{$type}{$cityCode}{$code} = {
193
'Name' => $val->{'name'},
194
'WorkTime' => $val->{'workTime'},
195
'Address' => $val->{'address'},
196
'Phone' => $val->{'phone'},
197
'Note' => $val->{'note'},
198
'cX' => $val->{'coordX'},
199
'cY' => $val->{'coordY'},
200
'Dressing' => Types::Serialiser::as_bool($val->{'isDressingRoom'}),
201
'Cash' => Types::Serialiser::as_bool($val->{'haveCashless'}),
202
'Postamat' => Types::Serialiser::as_bool(lc($val->{'type'}) eq 'postamat'),
203
'Station' => $val->{'nearestStation'},
204
'Site' => $val->{'site'},
205
'Metro' => $val->{'metroStation'},
206
'AddressComment' => $val->{'addressComment'},
207
'CityCode' => $val->{'CityCode'},
208
};
209
if ( exists $val->{weightLimit} ) {
210
$out->{$type}{$cityCode}{$code}{'WeightLim'} = {
211
'MIN' => $val->{weightLimit}{'weightMin'} * 1.0,
212
'MAX' => $val->{weightLimit}{'weightMax'} * 1.0,
213
};
214
}
215
my @arImgs;
216
if ( exists $val->{officeImageList} && ref $val->{officeImageList} eq 'ARRAY' ) {
217
foreach my $img ( @{$val->{officeImageList}} ) {
218
unless ( $img->{'url'} =~ /^http/ ) {
219
next;
220
}
221
push @arImgs, $img->{'url'};
222
}
223
}
224
225
if ( @arImgs ) {
226
$out->{$type}{$cityCode}{$code}{'Picture'} = \@arImgs;
227
}
228
if ( exists $val->{officeHowGo} ) {
229
$out->{$type}{$cityCode}{$code}{'Path'} = $val->{officeHowGo}{'url'};
230
}
231
232
if (!exists $out->{'CITY'}{$cityCode} ) {
233
$out->{'CITY'}{$cityCode} = $city;
234
$out->{'CITYREG'}{$cityCode} = int($val->{'regionCode'});
235
push @{ $out->{'REGIONSMAP'}{int($val->{'regionCode'})} }, int($cityCode);
236
$out->{'CITYFULL'}{$cityCode} = $val->{'countryName'} . ' ' . $val->{'regionName'} . ' ' . $city;
237
$out->{'REGIONS'}{$cityCode} = join(', ', $val->{'regionName'}, $val->{'countryName'} );
238
}
239
}
240
if ( $keeper->MEMD ) {
241
$keeper->MEMD->set( $cacheKey, $out, 108000 );
242
}
243
return { 'pvz' => $out };
244
}
245
return { 'error' => 'Some error PVZ' };
246
}
247
248
sub getLang {
249
my $self = shift;
250
my $args = shift || {};
251
my $lang = $args->{'lang'} && exists $translate->{$args->{'lang'}} ? $args->{'lang'} : '';
252
253
return { 'LANG' => $self->getValue( $translate, $lang, $translate->{'rus'} ) };
254
}
255
256
257
sub getCity {
258
my $self = shift;
259
my $args = shift || {};
260
261
warn Dumper $args;
262
my $city = $self->getValue($args, 'city');
263
warn "Let's find for $city\n";
264
if ( $city ) {
265
return $self->getCityByName( $city );
266
}
267
268
if ( my $address = $self->getValue($args, 'address') ) {
269
return $self->getCityByAddress(Encode::decode('utf-8', $address));
270
}
271
272
return { 'error' => 'No city to search given' };
273
}
274
275
sub calc {
276
my $self = shift;
277
my $args = shift || {};
278
279
my $shipment = $self->getRequestValue($args, 'shipment', {});
280
warn Dumper($shipment);
281
282
if ( !exists $shipment->{'tariffList'} ) {
283
$shipment->{'tariffList'} = $self->getTariffPriority( $shipment->{'type'} );
284
}
285
286
if ( $args->{referer} ) {
287
$shipment->{'ref'} = $args->{referer};
288
}
289
290
if ( !exists $shipment->{'cityToId'} ) {
291
my $cityTo = $self->getCityByName( $shipment->{'cityTo'} );
292
if ( !exists $cityTo->{error} && exists $cityTo->{id} ) {
293
$shipment->{'cityToId'} = $cityTo->{id};
294
}
295
}
296
warn Dumper($shipment) if $DEBUG;
297
298
if ( $shipment->{'cityToId'} ) {
299
my $answer = $self->calculate($shipment);
300
301
if ( $answer ) {
302
my $returnData = {
303
'result' => $answer,
304
'type' => $shipment->{'type'},
305
};
306
if ( $shipment->{'timestamp'} ) {
307
$returnData->{'timestamp'} = $shipment->{'timestamp'};
308
}
309
310
return $returnData;
311
}
312
}
313
314
315
return { 'error' => 'City to not found' };
316
}
317
### /Actions
318
319
#############################################################################################
320
### Helpers
321
#############################################################################################
322
sub calculate {
323
my $self = shift;
324
my $shipment = shift || {};
325
326
if ( !exists $shipment->{'goods'} || scalar @{$shipment->{'goods'}} == 0 ) {
327
return { 'error' => 'The dimensions of the goods are not defined' };
328
}
329
330
my $headers = $self->getHeaders();
331
332
my $arData = {
333
'dateExecute' => $self->getValue($headers, 'date'),
334
'version' => '1.0',
335
'authLogin' => $self->getValue($headers, 'account'),
336
'secure' => $self->getValue($headers, 'secure'),
337
'senderCityId' => $self->getValue( $shipment, 'cityFromId' ),
338
'receiverCityId' => $self->getValue($shipment, 'cityToId'),
339
'ref' => $self->getValue($shipment, 'ref'),
340
'widget' => 1,
341
'currency' => $self->getValue($shipment, 'currency', 'RUB'),
342
};
343
344
if ( exists $shipment->{'tariffList'} ) {
345
my $priority = 1;
346
foreach my $tariffId ( @{$shipment->{'tariffList'}} ) {
347
$tariffId = int($tariffId);
348
push @{$arData->{'tariffList'}}, {
349
'priority' => $priority++,
350
'id' => $tariffId,
351
};
352
}
353
}
354
355
$arData->{'goods'} = [];
356
foreach my $arGood ( @{$shipment->{'goods'}} ) {
357
push @{$arData->{'goods'}}, {
358
'weight' => $arGood->{'weight'},
359
'length' => $arGood->{'length'},
360
'width' => $arGood->{'width'},
361
'height' => $arGood->{'height'},
362
};
363
}
364
365
my $type = $self->getValue($shipment, 'type');
366
367
my $resultTariffs = $self->_MakeRequest(
368
'/calculator/calculate_tarifflist.php',
369
'post',
370
$arData,
371
);
372
if ( $resultTariffs && $resultTariffs->{'code'} == 200 ) {
373
if ( ref $resultTariffs->{'content'} && exists $resultTariffs->{'content'}{'result'} ) {
374
my @resultTariffs = grep { $_->{status} } @{$resultTariffs->{'content'}{'result'}};
375
my %resultTariffs;
376
foreach my $t ( @resultTariffs ) {
377
$resultTariffs{$t->{'tariffId'}} = $t->{result};
378
}
379
warn Dumper \@resultTariffs;
380
381
if ( $type && !(exists $arData->{'tariffId'} && $arData->{'tariffId'}) ) {
382
my $tariffListSorted = $self->getTariffPriority($type);
383
foreach my $id ( @$tariffListSorted ) {
384
if ( exists $resultTariffs{$id} ) {
385
return $resultTariffs{$id};
386
}
387
}
388
}
389
return shift @resultTariffs;
390
}
391
392
return { 'error' => 'Wrong server answer' };
393
}
394
395
return { 'error' => 'Wrong answer code from server : ' . $resultTariffs->{'status'} };
396
}
397
398
sub getTariffPriority {
399
my $self = shift;
400
my $type = shift // &COURIER_TARIFF_PRIORITY;
401
402
if ( $type ne &COURIER_TARIFF_PRIORITY && $type ne &PICKUP_TARIFF_PRIORITY ) {
403
warn "Unknown tariff type $type\n";
404
}
405
406
return $type eq &COURIER_TARIFF_PRIORITY ? $self->{courierTariffPriority} : $self->{pickupTariffPriority};
407
}
408
409
sub getCityByName {
410
my $self = shift;
411
my $name = shift;
412
my $single = shift // 1;
413
414
my $result = $self->_MakeRequest( '/city/getListByTerm/json.php?q=' . uri_escape($name) );
415
if ( $result->{code} == 200 ) {
416
unless ( $result->{content}{geonames} ) {
417
return (undef, 'No cities found');
418
} else {
419
if ($single) {
420
return {
421
'id' => $result->{content}->{geonames}->[0]->{id},
422
'city' => $result->{content}->{geonames}->[0]->{cityName},
423
'region' => $result->{content}->{geonames}->[0]->{regionName},
424
'country' => $result->{content}->{geonames}->[0]->{countryName}
425
};
426
} else {
427
my $arReturn = {'cities' => []};
428
foreach my $city ( @{$result->{content}{geonames}} ) {
429
push @{$arReturn->{'cities'}}, {
430
'id' => $city->{id},
431
'city' => $city->{cityName},
432
'region' => $city->{regionName},
433
'country' => $city->{countryName}
434
};
435
}
436
return $arReturn;
437
}
438
}
439
} else {
440
return { 'error' => 'Wrong answer code from server : ' . $result->{'code'} };
441
}
442
}
443
444
sub getCityByAddress {
445
my $self = shift;
446
my $address = shift;
447
448
my $arReturn = {};
449
my $arStages = { 'country' => undef, 'region' => undef, 'subregion' => undef };
450
my @arAddress = split /,/, $address;
451
452
my $ind = 0;
453
### finging country in address
454
if ( grep { Encode::decode('utf-8', $arAddress[0]) =~ /$_/i } $self->getCountries() ) {
455
my $country = lc(Encode::decode('utf-8', $arAddress[0]));
456
for ($country) {
457
s/^\s+//;
458
s/\s+$//;
459
}
460
$arStages->{'country'} = Encode::encode('utf-8', $country);
461
$ind++;
462
}
463
464
### finding region in address
465
foreach my $regionStr ( $self->getRegion() ) {
466
my $search = lc(Encode::decode('utf-8', $arAddress[$ind]));
467
for ($search) {
468
s/^\s+//;
469
s/\s+$//;
470
}
471
my $indSearch = index($search, $regionStr);
472
if ( $indSearch >= 0 ) {
473
if ($indSearch) {
474
$arStages->{'region'} = substr($search, 0, $indSearch);
475
} else {
476
$arStages->{'region'} = substr($search, length($regionStr));
477
}
478
for ( $arStages->{'region'} ) {
479
s/^\s+//;
480
s/\s+$//;
481
}
482
$ind++;
483
last;
484
}
485
}
486
487
### finding subregions
488
foreach my $subRegionStr ( $self->getSubRegion() ) {
489
my $search = lc(Encode::decode('utf-8', $arAddress[$ind]));
490
my $indSearch = index($search, $subRegionStr);
491
if ( $indSearch >= 0 ) {
492
if ($indSearch) {
493
$arStages->{'subregion'} = substr($search, 0, $indSearch);
494
} else {
495
$arStages->{'subregion'} = substr($search, length($subRegionStr));
496
}
497
for ( $arStages->{'subregion'} ) {
498
s/^\s+//;
499
s/\s+$//;
500
}
501
$ind++;
502
last;
503
}
504
}
505
506
### finding city
507
my $cityName = Encode::decode('utf-8', $arAddress[$ind]);
508
for ($cityName) {
509
s/^\s+//;
510
s/\s+$//;
511
}
512
my $cdekCity = $self->getCityByName(Encode::encode('utf-8', $cityName), 0);
513
514
if ( exists $cdekCity->{'error'} && $cdekCity->{'error'} ) {
515
foreach my $placeLbl ( $self->getCityDef() ) {
516
my $search = lc(Encode::decode('utf-8', $arAddress[$ind]));
517
for ( $search ) {
518
s/^\s+//;
519
s/\s+$//;
520
s/ё/е/sgi;
521
}
522
my $indSearch = index($search, $placeLbl);
523
if ( $indSearch >= 0 ) {
524
if ($indSearch) {
525
$search = substr($search, 0, $indSearch);
526
} else {
527
$search = substr($search, length($placeLbl));
528
}
529
for ( $search ) {
530
s/^\s+//;
531
s/\s+$//;
532
}
533
$cityName = $search;
534
$cdekCity = $self->getCityByName(Encode::encode('utf-8', $search), 0);
535
last;
536
}
537
}
538
}
539
540
my $pretend;
541
if ( $cdekCity->{'error'} ) {
542
$arReturn->{'error'} = $cdekCity->{'error'};
543
} else {
544
if ( ref $cdekCity->{'cities'} eq 'ARRAY' && scalar(@{$cdekCity->{'cities'}}) > 0) {
545
my $arPretend = [];
546
547
## parseCountry
548
if ( $arStages->{'country'} ) {
549
foreach my $arCity ( @{$cdekCity->{'cities'}} ) {
550
my $possCountry = lc( Encode::decode('utf-8', $arCity->{'country'}) );
551
if ( !$possCountry || index( Encode::decode('utf-8', $arStages->{'country'}), $possCountry) >= 0 ) {
552
push @$arPretend, $arCity;
553
}
554
}
555
} else {
556
$arPretend = $cdekCity->{'cities'};
557
}
558
559
## parseRegion
560
if ( $arStages->{'region'} && scalar @$arPretend > 1 ) {
561
my $_arPretend = [];
562
foreach my $arCity ( @$arPretend ) {
563
my $possRegion = lc( Encode::decode('utf-8', $arCity->{'region'}) );
564
my $arStagesRegion = lc( Encode::decode('utf-8', $arStages->{'region'}) );
565
foreach my $regpart ( $self->getRegion() ) {
566
$possRegion =~ s/$regpart//i;
567
$arStagesRegion =~ s/$regpart//i;
568
}
569
for ( $possRegion, $arStagesRegion ) {
570
s/^\s+//;
571
s/\s+$//;
572
}
573
if ( !$possRegion || index($possRegion, $arStagesRegion) >= 0 ) {
574
push @$_arPretend, $arCity;
575
}
576
}
577
$arPretend = $_arPretend;
578
}
579
580
## parseSubRegion
581
if ( $arStages->{'subregion'} && scalar @$arPretend > 1 ) {
582
my $_arPretend = [];
583
foreach my $arCity ( @$arPretend ) {
584
my $possSubRegion = lc( Encode::decode('utf-8', $arCity->{'city'}) );
585
if ( !$possSubRegion || index($possSubRegion, lc( Encode::decode('utf-8', $arStages->{'subregion'}) ) ) >= 0 ) {
586
push @$_arPretend, $arCity;
587
}
588
}
589
$arPretend = $_arPretend;
590
}
591
592
## parseUndefined
593
## not full city name
594
if ( scalar @$arPretend > 1 ) {
595
my $_arPretend = [];
596
foreach my $arCity ( @$arPretend ) {
597
if ( index($arCity->{'city'}, ',') >= 0 ) {
598
push @$_arPretend, $arCity;
599
}
600
}
601
$arPretend = $_arPretend;
602
}
603
604
if ( scalar @$arPretend > 1) {
605
my $_arPretend = [];
606
foreach my $arCity ( @$arPretend ) {
607
if ( length(Encode::decode('utf-8', $arCity->{'city'})) == length($cityName)) {
608
push @$_arPretend, $arCity;
609
}
610
}
611
$arPretend = $_arPretend;
612
}
613
614
## federalCities
615
if ( scalar @$arPretend > 1 ) {
616
my $_arPretend = [];
617
foreach my $arCity ( @$arPretend ) {
618
if ( $arCity->{'city'} eq $arCity->{'region'} ) {
619
push @$_arPretend, $arCity;
620
}
621
}
622
$arPretend = $_arPretend;
623
}
624
625
626
## end
627
if ( scalar @$arPretend ) {
628
$pretend = pop @$arPretend;
629
}
630
} else {
631
$pretend = $cdekCity->{'cities'}->[0];
632
}
633
if ( $pretend ) {
634
$arReturn->{'city'} = $pretend;
635
} else {
636
$arReturn->{'error'} = 'Undefined city';
637
}
638
}
639
return $arReturn;
640
}
641
642
sub getCountries {
643
return ('Россия', 'Беларусь', 'Армения', 'Казахстан', 'Киргизия', 'Молдова', 'Таджикистан', 'Узбекистан');
644
}
645
646
sub getRegion {
647
return ('автономная область', 'область', 'республика', 'автономный округ', 'округ', 'край', 'обл.');
648
}
649
650
sub getSubRegion {
651
return ('муниципальный район', 'район', 'городской округ');
652
}
653
654
sub getCityDef {
655
return(
656
'поселок городского типа',
657
'населенный пункт',
658
'курортный поселок',
659
'дачный поселок',
660
'рабочий поселок',
661
'почтовое отделение',
662
'сельское поселение',
663
'ж/д станция',
664
'станция',
665
'городок',
666
'деревня',
667
'микрорайон',
668
'станица',
669
'хутор',
670
'аул',
671
'поселок',
672
'село',
673
'снт'
674
);
675
}
676
677
sub getValue {
678
my ($self, $args, $key, $default) = @_;
679
$args //= {};
680
681
return (exists $args->{$key} && $args->{$key} ? $args->{$key} : $default);
682
}
683
684
sub getHeaders {
685
my $self = shift;
686
687
my $date = Contenido::DateTime->new()->ymd('-');
688
my $headers = {
689
'date' => $date,
690
};
691
if ( $self->{account} && $self->{key} ) {
692
$headers = {
693
'date' => $date,
694
'account' => $self->{account},
695
'secure' => Digest::MD5::md5_hex( $date . "&" . $self->{key} ),
696
};
697
}
698
699
return $headers;
700
}
701
702
sub getRequestValue {
703
my ($self, $args, $key, $default) = @_;
704
$args //= {};
705
706
my $out = {};
707
foreach my $k ( keys %$args ) {
708
if ( $k eq $key ) {
709
return $self->getValue($args, $key, $default);
710
} elsif ( $k =~ /^$key/ && index($k, '[') > 0 ) {
711
my @levels;
712
while ( $k =~ /\[(.*?)\]/g ) {
713
my $name = $1;
714
push @levels, { type => $name =~ /\D/ ? 'hash' : 'array', key => $name };
715
}
716
my $data = $out;
717
for ( my $i = 0; $i < scalar @levels; $i++ ) {
718
my $curr = $levels[$i];
719
my $next = $i+1 < scalar @levels ? $levels[$i+1] : undef;
720
if ( $next ) {
721
if ( $curr->{type} eq 'hash' && !exists $data->{$curr->{key}} ) {
722
$data->{$curr->{key}} = $next->{type} eq 'hash' ? {} : [];
723
} elsif ( $curr->{type} eq 'array' && !defined $data->[$curr->{key}] ) {
724
$data->[$curr->{key}] = $next->{type} eq 'hash' ? {} : [];
725
}
726
} else {
727
$data->{$curr->{key}} = $args->{$k};
728
last;
729
}
730
$data = $curr->{type} eq 'array' ? $data->[$curr->{key}] : $data->{$curr->{key}};
731
}
732
}
733
}
734
735
return scalar %$out ? $out : $default;
736
}
737
738
sub trim {
739
my $val = shift;
740
741
for ( $val ) {
742
s/^\s+//;
743
s/\s+$//;
744
}
745
746
return $val;
747
}
748
749
1;
Небольшая справка по веткам
cnddist – контейнер, в котором хранятся все дистрибутивы всех библиотек и программных пакетов, которые использовались при построении различных версий Contenido. Если какой-то библиотеки в данном хранилище нет, инсталлятор сделает попытку "подтянуть" ее с веба (например, с CPAN). Если библиотека слишком старая, есть очень большая вероятность, что ее там уже нет. Поэтому мы храним весь хлам от всех сборок. Если какой-то дистрибутив вдруг отсутствует в cnddist - напишите нам, мы положим его туда.
koi8 – отмирающая ветка, чей код, выдача и все внутренние библиотеки заточены на кодировку KOI8-R. Вносятся только те дополнения, которые касаются внешнего вида и функционала админки, баги ядра, обязательные обновления портов и мелочи, которые легко скопипастить. В дальнейшем планируется полная остановка поддержки по данной ветке.
utf8 – актуальная ветка, заточенная под UTF-8.
Внутри каждой ветки: core – исходники ядра; install – скрипт установки инсталляции; plugins – плагины; samples – "готовые к употреблению" проекты, которые можно поставить, запустить и посмотреть, как они работают.