bendasvadim

Динамическое изменение цен в корзине minishop2

Создаем кастомный хендлер

    core/components/minishop2/custom/cart/mscarthandlercustom.class.php

С кодом

class msCartHandlerCustom extends msCartHandler{
    public function status($data = array())
    {
        $status = array(
            'total_count' => 0,
            'total_cost' => 0,
            'total_weight' => 0,
        );
	$status['cart'] = $this->cart; // Добавляем только эту строчку к оригинальному методу
        foreach ($this->cart as $item) {
            if (empty($item['ctx']) || $item['ctx'] == $this->ctx) {
                $status['total_count'] += $item['count'];
                $status['total_cost'] += $item['price'] * $item['count'];
                $status['total_weight'] += $item['weight'] * $item['count'];
            }
        }
        $status = array_merge($data, $status);
 
        $response = $this->ms2->invokeEvent('msOnGetStatusCart', array(
            'status' => $status,
            'cart' => $this,
        ));
        if ($response['success']) {
            $status = $response['data']['status'];
        }
 
        return $status;
    }
}

Прописываем системную настройку http://joxi.ru/12MN3k4TlvKWEA

Создаем файл

    assets/components/minishop2/js/web/custom.js

С кодом, это копия но с чуть измененным кодом в разделе Cart.status, чтобы вывести данные на фронт

    (function (window, document, $, miniShop2Config) {
        var miniShop2 = miniShop2 || {};
        miniShop2Config.callbacksObjectTemplate = function () {
            return {
                // return false to prevent send data
                before: [],
                response: {
                    success: [],
                    error: []
                },
                ajax: {
                    done: [],
                    fail: [],
                    always: []
                }
            }
        };
        miniShop2.Callbacks = miniShop2Config.Callbacks = {
            Cart: {
                add: miniShop2Config.callbacksObjectTemplate(),
                remove: miniShop2Config.callbacksObjectTemplate(),
                change: miniShop2Config.callbacksObjectTemplate(),
                clean: miniShop2Config.callbacksObjectTemplate()
            },
            Order: {
                add: miniShop2Config.callbacksObjectTemplate(),
                getcost: miniShop2Config.callbacksObjectTemplate(),
                clean: miniShop2Config.callbacksObjectTemplate(),
                submit: miniShop2Config.callbacksObjectTemplate(),
                getrequired: miniShop2Config.callbacksObjectTemplate()
            },
        };
        miniShop2.Callbacks.add = function (path, name, func) {
            if (typeof func != 'function') {
                return false;
            }
            path = path.split('.');
            var obj = miniShop2.Callbacks;
            for (var i = 0; i < path.length; i++) {
                if (obj[path[i]] == undefined) {
                    return false;
                }
                obj = obj[path[i]];
            }
            if (typeof obj != 'object') {
                obj = [obj];
            }
            if (name != undefined) {
                obj[name] = func;
            }
            else {
                obj.push(func);
            }
            return true;
        };
        miniShop2.Callbacks.remove = function (path, name) {
            path = path.split('.');
            var obj = miniShop2.Callbacks;
            for (var i = 0; i < path.length; i++) {
                if (obj[path[i]] == undefined) {
                    return false;
                }
                obj = obj[path[i]];
            }
            if (obj[name] != undefined) {
                delete obj[name];
                return true;
            }
            return false;
        };
        miniShop2.ajaxProgress = false;
        miniShop2.setup = function () {
            // selectors & $objects
            this.actionName = 'ms2_action';
            this.action = ':submit[name=' + this.actionName + ']';
            this.form = '.ms2_form';
            this.$doc = $(document);
    
            this.sendData = {
                $form: null,
                action: null,
                formData: null
            };
    
            this.timeout = 300;
        };
        miniShop2.initialize = function () {
            miniShop2.setup();
            // Indicator of active ajax request
    
            //noinspection JSUnresolvedFunction
            miniShop2.$doc
                .ajaxStart(function () {
                    miniShop2.ajaxProgress = true;
                })
                .ajaxStop(function () {
                    miniShop2.ajaxProgress = false;
                })
                .on('submit', miniShop2.form, function (e) {
                    e.preventDefault();
                    var $form = $(this);
                    var action = $form.find(miniShop2.action).val();
    
                    if (action) {
                        var formData = $form.serializeArray();
                        formData.push({
                            name: miniShop2.actionName,
                            value: action
                        });
                        miniShop2.sendData = {
                            $form: $form,
                            action: action,
                            formData: formData
                        };
                        miniShop2.controller();
                    }
                });
            miniShop2.Cart.initialize();
            miniShop2.Message.initialize();
            miniShop2.Order.initialize();
            miniShop2.Gallery.initialize();
        };
        miniShop2.controller = function () {
            var self = this;
            switch (self.sendData.action) {
                case 'cart/add':
                    miniShop2.Cart.add();
                    break;
                case 'cart/remove':
                    miniShop2.Cart.remove();
                    break;
                case 'cart/change':
                    miniShop2.Cart.change();
                    break;
                case 'cart/clean':
                    miniShop2.Cart.clean();
                    break;
                case 'order/submit':
                    miniShop2.Order.submit();
                    break;
                case 'order/clean':
                    miniShop2.Order.clean();
                    break;
                default:
                    return;
            }
        };
        miniShop2.send = function (data, callbacks, userCallbacks) {
            var runCallback = function (callback, bind) {
                if (typeof callback == 'function') {
                    return callback.apply(bind, Array.prototype.slice.call(arguments, 2));
                }
                else if (typeof callback == 'object') {
                    for (var i in callback) {
                        if (callback.hasOwnProperty(i)) {
                            var response = callback[i].apply(bind, Array.prototype.slice.call(arguments, 2));
                            if (response === false) {
                                return false;
                            }
                        }
                    }
                }
                return true;
            };
            // set context
            if ($.isArray(data)) {
                data.push({
                    name: 'ctx',
                    value: miniShop2Config.ctx
                });
            }
            else if ($.isPlainObject(data)) {
                data.ctx = miniShop2Config.ctx;
            }
            else if (typeof data == 'string') {
                data += '&ctx=' + miniShop2Config.ctx;
            }
    
            // set action url
            var formActionUrl = (miniShop2.sendData.$form)
                ? miniShop2.sendData.$form.attr('action')
                : false;
            var url = (formActionUrl)
                ? formActionUrl
                : (miniShop2Config.actionUrl)
                          ? miniShop2Config.actionUrl
                          : document.location.href;
            // set request method
            var formMethod = (miniShop2.sendData.$form)
                ? miniShop2.sendData.$form.attr('method')
                : false;
            var method = (formMethod)
                ? formMethod
                : 'post';
    
            // callback before
            if (runCallback(callbacks.before) === false || runCallback(userCallbacks.before) === false) {
                return;
            }
            // send
            var xhr = function (callbacks, userCallbacks) {
                return $[method](url, data, function (response) {
                    if (response.success) {
                        if (response.message) {
                            miniShop2.Message.success(response.message);
                        }
                        runCallback(callbacks.response.success, miniShop2, response);
                        runCallback(userCallbacks.response.success, miniShop2, response);
                    }
                    else {
                        miniShop2.Message.error(response.message);
                        runCallback(callbacks.response.error, miniShop2, response);
                        runCallback(userCallbacks.response.error, miniShop2, response);
                    }
                }, 'json').done(function () {
                    runCallback(callbacks.ajax.done, miniShop2, xhr);
                    runCallback(userCallbacks.ajax.done, miniShop2, xhr);
                }).fail(function () {
                    runCallback(callbacks.ajax.fail, miniShop2, xhr);
                    runCallback(userCallbacks.ajax.fail, miniShop2, xhr);
                }).always(function () {
                    runCallback(callbacks.ajax.always, miniShop2, xhr);
                    runCallback(userCallbacks.ajax.always, miniShop2, xhr);
                });
            }(callbacks, userCallbacks);
        };
    
        miniShop2.Cart = {
            callbacks: {
                add: miniShop2Config.callbacksObjectTemplate(),
                remove: miniShop2Config.callbacksObjectTemplate(),
                change: miniShop2Config.callbacksObjectTemplate(),
                clean: miniShop2Config.callbacksObjectTemplate()
            },
            setup: function () {
                miniShop2.Cart.cart = '#msCart';
                miniShop2.Cart.miniCart = '#msMiniCart';
                miniShop2.Cart.miniCartNotEmptyClass = 'full';
                miniShop2.Cart.countInput = 'input[name=count]';
                miniShop2.Cart.totalWeight = '.ms2_total_weight';
                miniShop2.Cart.totalCount = '.ms2_total_count';
                miniShop2.Cart.totalCost = '.ms2_total_cost';
            },
            initialize: function () {
                miniShop2.Cart.setup();
                if (!$(miniShop2.Cart.cart).length) {
                    return;
                }
                miniShop2.$doc.on('change', miniShop2.Cart.cart + ' ' + miniShop2.Cart.countInput, function () {
                    if (!!$(this).val()) {
                        $(this).closest(miniShop2.form).submit();
                    }
                });
            },
            add: function () {
                var callbacks = miniShop2.Cart.callbacks;
                callbacks.add.response.success = function (response) {
                    this.Cart.status(response.data);
                };
                miniShop2.send(miniShop2.sendData.formData, miniShop2.Cart.callbacks.add, miniShop2.Callbacks.Cart.add);
            },
            remove: function () {
                var callbacks = miniShop2.Cart.callbacks;
                callbacks.remove.response.success = function (response) {
                    this.Cart.remove_position(miniShop2.Utils.getValueFromSerializedArray('key'));
                    this.Cart.status(response.data);
                };
                miniShop2.send(miniShop2.sendData.formData, miniShop2.Cart.callbacks.remove, miniShop2.Callbacks.Cart.remove);
            },
            change: function () {
                var callbacks = miniShop2.Cart.callbacks;
                callbacks.change.response.success = function (response) {
                    if (typeof(response.data.key) == 'undefined') {
                        this.Cart.remove_position(miniShop2.Utils.getValueFromSerializedArray('key'));
                    }
                    else {
                        $('#' + miniShop2.Utils.getValueFromSerializedArray('key')).find('');
                    }
                    this.Cart.status(response.data);
                };
                miniShop2.send(miniShop2.sendData.formData, miniShop2.Cart.callbacks.change, miniShop2.Callbacks.Cart.change);
            },
            status: function (status) {
                if (status['total_count'] < 1) {
                    location.reload();
                }
                else {
                    //var $cart = $(miniShop2.Cart.cart);
                    var $miniCart = $(miniShop2.Cart.miniCart);
                    if (status['total_count'] > 0 && !$miniCart.hasClass(miniShop2.Cart.miniCartNotEmptyClass)) {
                        $miniCart.addClass(miniShop2.Cart.miniCartNotEmptyClass);
                    }
                    $(miniShop2.Cart.totalWeight).text(miniShop2.Utils.formatWeight(status['total_weight']));
                    $(miniShop2.Cart.totalCount).text(status['total_count']);
                    $(miniShop2.Cart.totalCost).text(miniShop2.Utils.formatPrice(status['total_cost']));
    		$.each(status.cart, function(key, value) {
    		    $("#" + key).find(".js_row_price").text(miniShop2.Utils.formatPrice(parseFloat(value.price) * parseInt(value.count)));
    		});
    		
                    if ($(miniShop2.Order.orderCost, miniShop2.Order.order).length) {
                        miniShop2.Order.getcost();
                    }
                    $(miniShop2.Order.orderCost, miniShop2.Order.order).text(miniShop2.Utils.formatPrice(status['total_cost']));
                }
            },
            clean: function () {
                var callbacks = miniShop2.Cart.callbacks;
                callbacks.clean.response.success = function (response) {
                    this.Cart.status(response.data);
                };
    
                miniShop2.send(miniShop2.sendData.formData, miniShop2.Cart.callbacks.clean, miniShop2.Callbacks.Cart.clean);
            },
            remove_position: function (key) {
                $('#' + key).remove();
            }
        };
    
        miniShop2.Gallery = {
            setup: function () {
                miniShop2.Gallery.gallery = $('#msGallery');
                miniShop2.Gallery.files = miniShop2.Gallery.gallery.find('.fotorama');
            },
            initialize: function () {
                miniShop2.Gallery.setup();
                if (miniShop2.Gallery.files.length) {
                    $('', {
                        rel: 'stylesheet',
                        type: 'text/css',
                        href: miniShop2Config.cssUrl + 'lib/fotorama.min.css',
                    }).appendTo('head');
                    $('', {
                        type: 'text/javascript',
                        src: miniShop2Config.jsUrl + 'lib/fotorama.min.js',
                    }).appendTo('head');
                }
    
                // fix size gallery
                miniShop2.$doc.on('fotorama:ready', miniShop2.Gallery.files, function (e, Fotorama, extra) {
                    if ((src = Fotorama.activeFrame.src)) {
                        measure = $.Fotorama.measures[src];
                        if (measure === undefined) {
                            for (i in $.Fotorama.measures) {
                                measure = $.Fotorama.measures[i];
                                break;
                            }
                            Fotorama.resize(measure);
                        }
                    }
                });
    
            }
        };
    
        miniShop2.Order = {
            callbacks: {
                add: miniShop2Config.callbacksObjectTemplate(),
                getcost: miniShop2Config.callbacksObjectTemplate(),
                clean: miniShop2Config.callbacksObjectTemplate(),
                submit: miniShop2Config.callbacksObjectTemplate(),
                getrequired: miniShop2Config.callbacksObjectTemplate()
            },
            setup: function () {
                miniShop2.Order.order = '#msOrder';
                miniShop2.Order.deliveries = '#deliveries';
                miniShop2.Order.payments = '#payments';
                miniShop2.Order.deliveryInput = 'input[name="delivery"]';
                miniShop2.Order.inputParent = '.input-parent';
                miniShop2.Order.paymentInput = 'input[name="payment"]';
                miniShop2.Order.paymentInputUniquePrefix = 'input#payment_';
                miniShop2.Order.deliveryInputUniquePrefix = 'input#delivery_';
                miniShop2.Order.orderCost = '#ms2_order_cost'
            },
            initialize: function () {
                miniShop2.Order.setup();
                if ($(miniShop2.Order.order).length) {
                    miniShop2.$doc
                        .on('click', miniShop2.Order.order + ' [name="' + miniShop2.actionName + '"][value="order/clean"]', function (e) {
                            miniShop2.Order.clean();
                            e.preventDefault();
                        })
                        .on('change', miniShop2.Order.order + ' input,' + miniShop2.Order.order + ' textarea', function () {
                            var $this = $(this);
                            var key = $this.attr('name');
                            var value = $this.val();
                            miniShop2.Order.add(key, value);
                        });
                    var $deliveryInputChecked = $(miniShop2.Order.deliveryInput + ':checked', miniShop2.Order.order);
                    $deliveryInputChecked.trigger('change');
                }
            },
            updatePayments: function (payments) {
                var $paymentInputs = $(miniShop2.Order.paymentInput, miniShop2.Order.order);
                $paymentInputs.attr('disabled', true).prop('disabled', true).closest(miniShop2.Order.inputParent).hide();
                if (payments.length > 0) {
                    for (var i in payments) {
                        if (payments.hasOwnProperty(i)) {
                            $paymentInputs.filter(miniShop2.Order.paymentInputUniquePrefix + payments[i]).attr('disabled', false).prop('disabled', false).closest(miniShop2.Order.inputParent).show();
                        }
                    }
                }
                if ($paymentInputs.filter(':visible:checked').length == 0) {
                    $paymentInputs.filter(':visible:first').trigger('click');
                }
            },
            add: function (key, value) {
                var callbacks = miniShop2.Order.callbacks;
                var old_value = value;
                callbacks.add.response.success = function (response) {
                    (function (key, value, old_value) {
                        var $field = $('[name="' + key + '"]', miniShop2.Order.order);
                        switch (key) {
                            case 'delivery':
                                $field = $(miniShop2.Order.deliveryInputUniquePrefix + response.data[key]);
                                if (response.data[key] != old_value) {
                                    $field.trigger('click');
                                }
                                else {
                                    miniShop2.Order.getrequired(value);
                                    miniShop2.Order.updatePayments($field.data('payments'));
                                    miniShop2.Order.getcost();
                                }
                                break;
                            case 'payment':
                                $field = $(miniShop2.Order.paymentInputUniquePrefix + response.data[key]);
                                if (response.data[key] != old_value) {
                                    $field.trigger('click');
                                }
                                else {
                                    miniShop2.Order.getcost();
                                }
                                break;
                            //default:
                        }
                        $field.val(response.data[key]).removeClass('error').closest(miniShop2.Order.inputParent).removeClass('error');
                    })(key, value, old_value);
                };
                callbacks.add.response.error = function () {
                    (function (key) {
                        var $field = $('[name="' + key + '"]', miniShop2.Order.order);
                        if ($field.attr('type') == 'checkbox' || $field.attr('type') == 'radio') {
                            $field.closest(miniShop2.Order.inputParent).addClass('error');
                        }
                        else {
                            $field.addClass('error');
                        }
                    })(key);
                };
    
                var data = {
                    key: key,
                    value: value
                };
                data[miniShop2.actionName] = 'order/add';
                miniShop2.send(data, miniShop2.Order.callbacks.add, miniShop2.Callbacks.Order.add);
            },
            getcost: function () {
                var callbacks = miniShop2.Order.callbacks;
                callbacks.getcost.response.success = function (response) {
                    $(miniShop2.Order.orderCost, miniShop2.Order.order).text(miniShop2.Utils.formatPrice(response.data['cost']));
                };
                var data = {};
                data[miniShop2.actionName] = 'order/getcost';
                miniShop2.send(data, miniShop2.Order.callbacks.getcost, miniShop2.Callbacks.Order.getcost);
            },
            clean: function () {
                var callbacks = miniShop2.Order.callbacks;
                callbacks.clean.response.success = function () {
                    location.reload();
                };
    
                var data = {};
                data[miniShop2.actionName] = 'order/clean';
                miniShop2.send(data, miniShop2.Order.callbacks.clean, miniShop2.Callbacks.Order.clean);
            },
            submit: function () {
                miniShop2.Message.close();
    
                // Checking for active ajax request
                if (miniShop2.ajaxProgress) {
                    //noinspection JSUnresolvedFunction
                    miniShop2.$doc.ajaxComplete(function () {
                        miniShop2.ajaxProgress = false;
                        miniShop2.$doc.unbind('ajaxComplete');
                        miniShop2.Order.submit();
                    });
                    return false;
                }
    
                var callbacks = miniShop2.Order.callbacks;
                callbacks.submit.before = function () {
                    $(':button, a', miniShop2.Order.order).attr('disabled', true).prop('disabled', true);
                };
                callbacks.submit.response.success = function (response) {
                    if (response.data['redirect']) {
                        document.location.href = response.data['redirect'];
                    }
                    else if (response.data['msorder']) {
                        document.location.href = document.location.origin + document.location.pathname
                            + (document.location.search ? document.location.search + '&' : '?')
                            + 'msorder=' + response.data['msorder'];
                    }
                    else {
                        location.reload();
                    }
                };
                callbacks.submit.response.error = function (response) {
                    setTimeout((function () {
                        $(':button, a', miniShop2.Order.order).attr('disabled', false).prop('disabled', false);
                          $('.load-bg').css({ 'display':'none' });
                    }.bind(this)),3 * miniShop2.timeout);
                    $('[name]', miniShop2.Order.order).removeClass('error').closest(miniShop2.Order.inputParent).removeClass('error');
                    for (var i in response.data) {
                        if (response.data.hasOwnProperty(i)) {
                            var key = response.data[i];
                            //var $field = $('[name="' + response.data[i] + '"]', miniShop2.Order.order);
                            //$field.addClass('error').closest(miniShop2.Order.inputParent).addClass('error');
                            var $field = $('[name="' + key + '"]', miniShop2.Order.order);
                            if ($field.attr('type') == 'checkbox' || $field.attr('type') == 'radio') {
                                $field.closest(miniShop2.Order.inputParent).addClass('error');
                            }
                            else {
                                $field.addClass('error');
                            }
                        }
                    }
                };
                return miniShop2.send(miniShop2.sendData.formData, miniShop2.Order.callbacks.submit, miniShop2.Callbacks.Order.submit);
            },
            getrequired: function (value) {
                var callbacks = miniShop2.Order.callbacks;
                callbacks.getrequired.response.success = function (response) {
                    $('[name]', miniShop2.Order.order).removeClass('required').closest(miniShop2.Order.inputParent).removeClass('required');
                    var requires = response.data['requires'];
                    for (var i = 0, length = requires.length; i < length; i++) {
                        $('[name=' + requires[i] + ']', miniShop2.Order.order).addClass('required').closest(miniShop2.Order.inputParent).addClass('required');
                    }
                };
                callbacks.getrequired.response.error = function () {
                    $('[name]', miniShop2.Order.order).removeClass('required').closest(miniShop2.Order.inputParent).removeClass('required');
                };
    
                var data = {
                    id: value
                };
                data[miniShop2.actionName] = 'order/getrequired';
                miniShop2.send(data, miniShop2.Order.callbacks.getrequired, miniShop2.Callbacks.Order.getrequired);
            }
        };
    
        miniShop2.Message = {
            initialize: function () {
                miniShop2.Message.close = function () {
                };
                miniShop2.Message.show = function (message) {
                    if (message != '') {
                        alert(message);
                    }
                };
    
                if (typeof($.fn.jGrowl) != 'function') {
                    $.getScript(miniShop2Config.jsUrl + 'lib/jquery.jgrowl.min.js', function () {
                        miniShop2.Message.initialize();
                    });
                }
                else {
                    $.jGrowl.defaults.closerTemplate = '
[ ' + miniShop2Config.close_all_message + ' ]
'; miniShop2.Message.close = function () { $.jGrowl('close'); }; miniShop2.Message.show = function (message, options) { if (message != '') { $.jGrowl(message, options); } } } }, success: function (message) { miniShop2.Message.show(message, { theme: 'ms2-message-success', sticky: false }); }, error: function (message) { miniShop2.Message.show(message, { theme: 'ms2-message-error', sticky: false }); }, info: function (message) { miniShop2.Message.show(message, { theme: 'ms2-message-info', sticky: false }); } }; miniShop2.Utils = { empty: function (val) { return (typeof(val) == 'undefined' || val == 0 || val === null || val === false || (typeof(val) == 'string' && val.replace(/\s+/g, '') == '') || (typeof(val) == 'object' && val.length == 0)); }, formatPrice: function (price) { var pf = miniShop2Config.price_format; price = this.number_format(price, pf[0], pf[1], pf[2]); if (miniShop2Config.price_format_no_zeros && pf[0] > 0) { price = price.replace(/(0+)$/, ''); price = price.replace(/[^0-9]$/, ''); } return price; }, formatWeight: function (weight) { var wf = miniShop2Config.weight_format; weight = this.number_format(weight, wf[0], wf[1], wf[2]); if (miniShop2Config.weight_format_no_zeros && wf[0] > 0) { weight = weight.replace(/(0+)$/, ''); weight = weight.replace(/[^0-9]$/, ''); } return weight; }, // Format a number with grouped thousands, number_format: function (number, decimals, dec_point, thousands_sep) { // original by: Jonas Raoni Soares Silva (http://www.jsfromhell.com) // improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // bugfix by: Michael White (http://crestidg.com) var i, j, kw, kd, km; // input sanitation & defaults if (isNaN(decimals = Math.abs(decimals))) { decimals = 2; } if (dec_point == undefined) { dec_point = ','; } if (thousands_sep == undefined) { thousands_sep = '.'; } i = parseInt(number = (+number || 0).toFixed(decimals)) + ''; if ((j = i.length) > 3) { j = j % 3; } else { j = 0; } km = j ? i.substr(0, j) + thousands_sep : ''; kw = i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + thousands_sep); kd = (decimals ? dec_point + Math.abs(number - i).toFixed(decimals).replace(/-/, '0').slice(2) : ''); return km + kw + kd; }, getValueFromSerializedArray: function (name, arr) { if (!$.isArray(arr)) { arr = miniShop2.sendData.formData; } for (var i = 0, length = arr.length; i < length; i++) { if (arr[i].name == name) { return arr[i].value; } } return null; } }; $(document).ready(function ($) { miniShop2.initialize(); var html = $('html'); html.removeClass('no-js'); if (!html.hasClass('js')) { html.addClass('js'); } }); window.miniShop2 = miniShop2; })(window, document, jQuery, miniShop2Config);

И прописываем системную настройку http://joxi.ru/KAx7WQXfZ38DQ2

Класы для работы в корзине
js_row_price - для вывода общей суммы товара в корзине и ее изменения в зависимости от количества

12 ноября 2019, 08:00    bendasvadim MODX Revolution 0    2516 0

Комментарии ()

  1. Татьяна 05 января 2022, 20:01 # 0
    joxi.ru/KAx7WQXfZ38DQ2 уже нет файлов, чтобы посмотреть