• Как в Yii2 реализовать мультивалютность?

    Для реализации задачи нужно следующее:
    1. В таблице товаров, помимо поля price, ещё нужно иметь поле currency_code, в котором хранить код валюты данного товара (ведь исходная цена товара может быть в USD, или EUR - не менять же её постоянно при колебаниях курса).

    2. Создать модуль для валют (таблица currency в БД, модель Currency и удобный КРУД для управления валютами). В таблице валют уникальный должен быть code - код валюты, по внешнему ключу связать его с полем currency_code в таблице товаров. Также у валюты должны быть поля rate (курс) и default (валюта по умолчанию для сайта). Для валюты по умолчанию rate должен быть равен 1.

    3. Нужно написать маленький компонент app/components/Currency.php, в котором через работу с сессией, сохранять в сессию выбранную пользователем валюту на сайте (выбор валюты через виджет удобно сделать). Также, во всех местах проекта, где выводится цена товара, она должна выводиться через компонент валют, чтобы он её преобразовывал из исходной (той, что в базе указана в качестве валюты для цены товара) в ту, что выбрал пользователь. Преобразование, соответственно, заданному в админке курсу (КРУД для валют, поле rate).

    Пример модели валют
    <?php
    
    namespace app\modules\shop\models;
    
    use Yii;
    use yii\db\ActiveRecord;
    use yii\helpers\ArrayHelper;
    
    class Currency extends ActiveRecord
    {
        const STATUS_ACTIVE = 1;
        const STATUS_INACTIVE = 0;
    
        public function behaviors()
        {
            return [
                'timestamp' => [
                    'class' => \yii\behaviors\TimestampBehavior::class,
                ],
                'blameable' => [
                    'class' => \yii\behaviors\BlameableBehavior::class,
                ],
                'ip' => [
                    'class' => \yii\behaviors\AttributeBehavior::class,
                    'attributes' => [
                        ActiveRecord::EVENT_BEFORE_INSERT => 'created_ip',
                        ActiveRecord::EVENT_BEFORE_UPDATE => 'updated_ip',
                    ],
                    'value' => function ($event) {
                        return Yii::$app->request->getUserIP();
                    },
                ],
            ];
        }
    
        public static function tableName()
        {
            return 'currency';
        }
    
        public function rules()
        {
            return [
                [['name', 'code'], 'required'],
                [['is_default', 'sort', 'status'], 'integer'],
                [['decimal_places'], 'integer', 'max' => 9],
                [['rate'], 'number'],
                [['code'], 'string', 'length' => 3],
                [['code'], 'unique'],
                [['code'], 'match', 'pattern' => '/^[A-Z]+$/', 'message' => Yii::t('app', 'Это поле может содержать только строчные буквы, цифры и дефис')],
                [['name', 'symbol'], 'string', 'max' => 32],
            ];
        }
    
        public function attributeLabels()
        {
            return [
                'id' => Yii::t('app', 'ID'),
                'created_at' => Yii::t('app', 'Дата создания'),
                'updated_at' => Yii::t('app', 'Дата обновления'),
                'created_by' => Yii::t('app', 'Создано пользователем'),
                'updated_by' => Yii::t('app', 'Обновлено пользователем'),
                'created_ip' => Yii::t('app', 'Создано c IP'),
                'updated_ip' => Yii::t('app', 'Обновлено c IP'),
                'code' => Yii::t('app', 'Код'),
                'name' => Yii::t('app', 'Название'),
                'symbol' => Yii::t('app', 'Символ'),
                'rate' => Yii::t('app', 'Курс'),
                'decimal_places' => Yii::t('app', 'Количество знаков после запятой'),
                'is_default' => Yii::t('app', 'По умолчанию'),
                'sort' => Yii::t('app', 'Сортировка'),
                'status' => Yii::t('app', 'Опубликован'),
            ];
        }
       
        public function afterSave($insert, $changedAttributes)
        {
            return Yii::$app->session->remove('currency');
        }
        
        public static function listItems()
        {
            $items = self::find()
                ->select(['code'])
                ->where(['status' => self::STATUS_ACTIVE])
                ->orderBy(['sort' => SORT_ASC])
                ->asArray()
                ->all();
            return ArrayHelper::map($items, 'code', 'code');
        }
        
        public static function statuses()
        {
            return [
                self::STATUS_ACTIVE => Yii::t('app', 'Опубликован'),
                self::STATUS_INACTIVE => Yii::t('app', 'Не опубликован'),
            ];
        }
    }

    Пример компонента валют
    <?php
    
    namespace app\modules\shop\components;
    
    use Yii;
    use yii\base\BaseObject;
    use app\modules\shop\models\Currency;
    
    class CurrencyManager extends BaseObject
    {
        public $currencies;
        public $defaultCurrency;
        public $siteCurrency;
        
        public function init()
        {
            parent::init();
            
            $this->currencies = Currency::find()
                ->where(['status' => Currency::STATUS_ACTIVE])
                ->orderBy(['sort' => SORT_ASC])
                ->asArray()
                ->all();
            foreach ($this->currencies as $item) {
                if ($item['is_default'] == 1) {
                    $this->defaultCurrency = $item;
                }
            }
            if (!Yii::$app->session['currency']) {
                Yii::$app->session['currency'] = $this->defaultCurrency;
            }
            $this->siteCurrency = Yii::$app->session['currency'];
        }
        
        public function listCurrencies()
        {
            $result = [];
            foreach ($this->currencies as $item) {
                $result[$item['code']] = $item['code'];
            }
            return $result;
        }
        
        public function showPrice($offer, $symbol = true)
        {
            if ($offer['currency_code'] == $this->siteCurrency['code']) {
                $price = $offer['price'];
            } else {
                foreach ($this->currencies as $item) {
                    if ($item['code'] == $offer['currency_code']) {
                        $rate = $item['rate'];
                    }
                }
                $price = ($offer['price'] * $rate) / $this->siteCurrency['rate'];
            }
                    
            if ($symbol === true) {
                return number_format($price, $this->siteCurrency['decimal_places'], '.', '') . '&nbsp;' . $this->siteCurrency['symbol'];
            } else {
                //return round($price, $this->siteCurrency['decimal_places']);
                return number_format($price, $this->siteCurrency['decimal_places'], '.', '');
            }
        }
        
        public function showOldPrice($offer, $symbol = true)
        {
            if ($offer['currency_code'] == $this->siteCurrency['code']) {
                $price = $offer['old_price'];
            } else {
                foreach ($this->currencies as $item) {
                    if ($item['code'] == $offer['currency_code']) {
                        $rate = $item['rate'];
                    }
                }
                $price = ($offer['old_price'] * $rate) / $this->siteCurrency['rate'];
            }
                    
            if ($symbol === true) {
                return number_format($price, $this->siteCurrency['decimal_places'], '.', '') . '&nbsp;' . $this->siteCurrency['symbol'];
            } else {
                return round($price, $this->siteCurrency['decimal_places']);
            }
        }
    }

    Ответ написан
    Комментировать
  • Как натянуть Landing Page на MODx?

    Каждая секция Landing Page - это дочерний ресурс Главной страницы, со своим шаблоном и (если нужно) дополнительными полями.. Он должен быть не опубликован (чтобы не создавать ссылки). Выводить эти секции во фронтенде через pdoResources с указаниме анкора (для навигации). В идеале - контент менеджер вообще не должен видеть HTML-код. А значит - он должен иметь доступ только к ресурсами и их полям, чтобы легко отредактировать в нужный момент любую секцию.
    Ответ написан
    Комментировать
  • WordPress или MODx Revolution для корпоративного сайта?

    Wordpress стал таким популярным, благодаряч его простоте на "бытовом" уровне, для пользователя, которому нужен сайт "за пять минут". Его легко установить и настроить, не зная ни капли кода и технических знаний. Но только в качестве блога или домашней страницы. Если нужно что-то большее - то Wordpress - это просто помойка. Для разработчика вообще - ад. Там куча "говнокода" в стиле 2000х, устаревшая архитектура, нет базовых вещей "из коробки" (например, мультиязычности, кэширования или SEO). Чтобы добавить к странице простую кнопку загрузки дополнительного изображения - нужно жёстко править код. Чтобы в контактной форме на кнопке ОТПРАВИТЬ сменить текст на иконку - нужно жёстко править код. Чтобы добавить свой тип страниц - нужно править код. В MODX это всё делается несколькими кликами мышки из админки. То, что многие известные бренды держат свои сайты на Wordpress объясняется тем, что стоимость разработки на этой CMS в разы ниже, чем у других, из-за того, что очень высокая конкуренция среди разработчиков (опять же таки, из-за популярности платформы). Обычно, "крутые" сайты на WP изменены изнутри до неузнаваемости. Там от Wordpress может процентов 20-30 кода остаётся - он настолько неповоротливый и не гибкий, что практически всё нужно переписывать и править самостоятельно. А большинство плагинов - условно бесплатные и низкого качества.
    Modx тоже не лишён недостатков. На нём очень сложно сделать нормальный мультиязычный и мультивалютный магазин, он сложен в освоении и без настройки интерфейса под заказчика - не годится совсем (заказчик не поймёт админку, её нужно модифицировать, там много лишнего).
    Ответ написан
    1 комментарий
  • CMS на базе Yii2?

    Я пересмотрел несколько существующих разработок CMS (если их вообще можно так назвать на данном этапе) на базе Yii2 - Skeeks, Easyii, Dotplant итд. Это всё просто большие куски кода, эксперименты... Никакой чёткой концепции, структуры, ничего нет. Yii2 - это фреймворк не для создания CMS однозначно. У меня есть несколько готовых проектов (интернет-магазины) на базе этого фреймворка - все очень быстро работают, всё ОК. Я создавал свои модели товаров, категорий, статей, прайсов итд. Есть SEO, удобная админка с контролем заказов и быстрых заявок, мультиязычность, корзина, модуль мультивалютности, экспорт прайсов в XML, разделение цен по уровню доступа для оптовиков и прочие базовые фишки, которые используются интернет-магазинах. Даже обновление цен и наличия через Excel. Всё сделано под заказчика. Но... Они все заточены под конкретную задачу, "реюзать" их код не получится (разве что, частично с передалками). Зато очень удобно расширять функционал и поддерживать такие проекты - нет никаких ограничений. Да и с безопасностью всё нормально, никаких вам бекдоров или "детских" болезней, характерных для популярных CMS.
    Если ищете готовые CMS решения на базе Yii2 Framework - их нет. Лучше создать своё. А ещё лучше - скомбинировать. Например, как базовая CMS только для контента - Wordpress, Modx или Joomla (или любая другая популярная CMS с хорошим большим сообществом и большим количеством дополнений), а для дополнительного функционала - устанавливайте фреймворк Yii2 на поддомен и подключайте его через API или напрямую к базе основной CMS (через модели).
    Ответ написан
    1 комментарий
  • Какой PHP фреймворк выбрать для CRM/ERP?

    Меня всегда улыбали люди, которые говорят что-то вроде "... для проектов со сложной бизнес-логикой это не подойдёт..." А зачем ставить изначально себе палки в колёса и делать сложную бизнес-логику в проектах? Это только говорит о недостаточной компетентности и отсутствии навыков и фантазии для решения сложных задач простым путём. Я уверен, при грамотном проектировании можно любой сложный проект реализовать раз в 10 проще. Лично мне нравится Yii2 - отличный инструмент, где есть практически всё, что нужно. Из его преимуществ - скорость работы, понятная логика работы самого фреймворка, большое количество готовых дополнений, популярность. Недостатки - очень много взаимозависимых компонентов и неважная документация без наличия хороших примеров реализации популярных задач.
    Ответ написан
    Комментировать
  • Как можно "эмулировать" Win. драйвер для принтера в OC Linux?

    Единственный простой и проверенный способ - установить VirtualBox c расширениями для USB, туда поставить Windows XP и установить принтер непосредственно в Windows. Если надо что-то напечатать - включаете VirtualBox и из него печатаете. А сканер в Linux и так работает.
    Ответ написан
  • Мультиязычность в Yii2: как реализовать правила URL?

    Я использую сдедующий подход:
    1) В базе данных информация о страницах (или товарах, или других сущностях) хранится в двух таблицах: например, page и page_content. Первая таблица - это данные, которые не требуют перевода (например id, дата создания, дата обновления, слаг итд.). Вторая таблица - это текстовые данные, которые будут мультиязычными (meta_title, meta_description, meta_keywords, content). Во второй таблице (с контентом), должно быть поле для связи с первой таблицой (напр. поле page_id), а также поле для указания кода языка. Получается, что для одной записи в первой таблице существует несколько записей во второй таблице, каждая из которых - на разных языках.
    2) В моделях нужно прописать связь один к одному для этих таблиц, в которых связующими будут поля по ID и по полю языка (параметр берётся из настроек приложения Yii::$app->language):
    public function getPageContent()
    {
        return $this->hasOne(PageContent::className(), ['page_id' => 'id'])->andWhere(['page_content.language_code' => Yii::$app->language]);
    }

    Теперь при выборке данных из первой таблицы, будут подтягиваться тексты из второй таблицы, на текущем языке приложения.
    3) Для управления текущим языком приложения и удобного переключения с одного языка на другой (параметр языка хранится в ссылке), существует замечательный компонент Yii2 Locale URLs
    Ответ написан
    1 комментарий