Как работает автоматическое определение языка в Опенкарт

- Posted in Opencart by

Для начала давайте разберёмся, как это всё устроено в браузерах и этих наших интернетах.

  1. Человек в настройках браузера обычно имеет возможность указать один или несколько языков, которые он понимает и предпочитает. Ищите в настройках что-то вроде "Preferred languages" (предпочитаемые языки). В браузерах попроще, с прицелом на чайников и минимумом настроек, локаль может браться системная или быть вшита в локализованный софт (без возможности настройки пользователем).

  2. Браузер отдаёт эту информацию сайтам в HTTP-заголовках Accept-Language при всех запросах. Этим он сообщает сайтам: если есть контент на одном из языков из моего списка - дайте мне его! (Если нет для первого - давайте для следующего по списку; если вообще ничего нет - давайте что там у вас есть по умолчанию). Варианты, которые встречаются у посетителей ваших сайтов, можно найти в Google Analytics, Яндекс.Вебмастер, Awstats, Piwik и других подобных системах веб-аналитики. Что отдаёт ваш браузер сайтам, вы можете увидеть в инструментах разработчика, посмотрев на HTTP-заголовки.

  3. Сайт может анализировать или игнорировать эти заголовки, реагируя на указанные предпочтения или нет.

Теперь Опенкарт. Мне кажется, 9 из 10 пользователей этого движка понятия не имеют, что это за строчка в описании языка в админке со всякими ru, ru-ru, uk, uk_UA и т.п. Теперь догадываетесь?

Это - простой список локалей, которые браузер может передать в заголовках. И на которые мы имеем возможность среагировать (админ магазина, движок). Если у нового пользователя опенкарт язык сайта ещё не выбран (и не записан в куках), то Опенкарт смотрит на HTTP-заголовки Accept-Language. И сравнивает их с тем, что мы понаписали в админке для русского, украинского и других языков.

Поскольку браузеры обычно дают выбирать языки и локали из списка, а не прописывать их вручную, то там чаще всего будет код языка или языка и региона. Например: ru, ru-ru, ru-RU, ru_ru, ru_RU. В Украине бывает uk (украинский язык), ru_UA, uk_UA. Ну и ru-UA, uk-UA до кучи.

Обычно локаль указывается с подчёркиванием "_", но жёстких стандартов по этому поводу кажется нет. И нередко можно встретить минус, тире или как там его правильно называть в типографике: "-". То же самое касается регистра символов в коде страны. Надо прописать все возможные комбинации, так как Опенкарт не приводит их к нижнему регистру при сравнении (смотрел на версии 2.1.0.2 - думаю, в остальных так же).

Итого: у вас в опенкарт админке в настройке языка должен быть список языков/локалей, которые могут запросить браузеры ваших посетителей (смотрите логи и аналитику и подстраивайтесь под своих посетителей). И этой настройкой вы определяете, какой язык им отдать при первом посещении вашего сайта ("угадать" его предпочтения).

Надеюсь, вам стала понятней эта внутренняя кухня автоматического определения языка.

И ещё очень хочется верить, что люди перестанут творить дурдом с выдачей языка по местоположению (GeoIP). Потому что француз, понимающий французский и английский (что указано в его браузерах на его ноутбуке) и который путешествует по миру, совсем не обрадуется, если ему будут выдавать контент на языке страны интернет-кафе.

Лайфхак: как разделять разговоры по темам в Skype

- Posted in Uncategorized by

Удалось найти решение давно мучившей проблемы: как разделить разговоры в скайпе по темам, сделать подобие тредов?

Решение - создавать чаты. И если на троих и более это не проблема, то попытка создать групповой чат (Conference Call) на двоих в скайпе приводит к звонку (!). Дятлы.

Решение кроется в скрытых командах скайпа, полный список которых можно увидеть, набрав в сообщении /help. Итак, чтобы создать чат на двоих и обсуждать там вопросы только по определённой теме (проекту), надо сперва из главного меню Скайпа создать групповой чат (Conference Call) на троих участников, а затем третьего убрать командой /kick skypename.

Надеюсь, это поможет тем, у кого случаются бурные обсуждения по нескольким вопросам, после которых лог общения превращается в винегрет со спагетти.

How to run rsync via non-standard ssh port

- Posted in Webdev by

Не знали, как запустить rsync через нестандартный порт (при работе через SSH, без ответного Rsync демона на сервере)?

Теперь знайте:

rsync -avz -e "ssh -p 12345" ./public_html/ username@yourserver.com:/var/www/html/

Здесь 12345 - номер порта, username - ваш логин и yourserver.com - доменное имя или IP вашего сервера.

Пришлось немного повозиться с чтением манов и поэкспериментировать. Надеюсь, кому-то сэкономит время.

Отправка копий вопроса с сайта всем админам магазина

- Posted in Opencart by

Оказалось, в Opencart 2 на странице обратной связи (information/contact) письмо отсылается только на главный email (владельцу магазина). Если какие-то адреса для дополнительных оповещений в админке заполнены - здесь они всё равно игнорируются.

Это неудивительно, поскольку в опенкарт функционал дублируется. Здесь - тупо забыли про дополнительные адреса.

Чтобы исправить, надо открыть файл catalog/controller/information/contact.php. В самой первой функции этого файла (public function index()) будет виден кусок кода отправки почты:

$mail = new Mail($this->config->get('config_mail'));
$mail->setTo($this->config->get('config_email'));
$mail->setFrom($this->request->post['email']);
$mail->setSender($this->request->post['name']);
$mail->setSubject(sprintf($this->language->get('email_subject'), $this->request->post['name']));
$mail->setText(strip_tags($this->request->post['enquiry']));
$mail->send();

Сразу после него надо добавить несколько строк:

// Send additional alert emails
$emails = explode(',', $this->config->get('config_mail_alert'));
foreach ($emails as $email) {
        $email = trim($email);
        if ($email && preg_match('/^[^\@]+@.*.[a-z]{2,15}$/i', $email)) {
                $mail->setTo($email);
                $mail->send();
        }
}

Теперь копии письма будут получать все администраторы магазина, перечисленные в поле дополнительных email-адресов (см. настройки магазина).

Совместимость

Приведённый код - из oc2011.

В oc2101/oc2102 кода чуть больше, но суть остаётся та же: добавлять после $mail->send(); и перед $this->response->....

Соответственно все версии, что между ними (oc2020, oc2031), лечатся аналогично. ocStore тоже.

Ocmod не писал, хотя наверное стоило бы - для тех, кто не хочет лазить по FTP и знает только админку. Если очень надо - напишите в комментарии, постараюсь найти время и выложить.

Проблемы лога ocmod.log

- Posted in Uncategorized by

Речь про версию oc2011. Вышедшую 1-2 дня назад oc2020 ещё не смотрел, хотя думаю, там та же беда.

В лог "ocmod.log" я уже заглядывал в самом начале знакомства с OC2 - там страшный бардак и чёрт ногу сломит. Этот лог - не помощник в поиске ошибок, а наоборот. Штука скорее бесполезная, которую надо взять и переделать.

Единственная хоть немного полезная в быту фича - возможность поискать строки "NOT FOUND!". И если они есть, можно отмотать вверх и убедиться - в твоём модуле ошибки случились или в каком-то другом.

Пока мне этого как-то хватало. Мало OCMOD-ами занимался. Но сегодня пришлось вплотную с ним разбираться и постигать логику находящихся в ocmod.log записей. И обнаружилось что-о-о-о? Правильно: очередной фееричный трындец.

Среди прочих записей у меня была такая:

CODE: <h2><?php echo $heading_title; ?></h2>
NOT FOUND!

Она оказалась самой информативной и точной. Немного отмотав лог вверх, можно найти строку "FILE: ..." и выяснить, в каком файле надо искать проблему. К слову: в этом блоке почему-то не было строки наподобие LINE: 16, которыми обычно сопровождаются как найденные, так и ненайденные блоки кода. Но если блок не найден, то вполне логично, что и номера строки не будет: не найден же. Так я подумал и принялся разгребать лог дальше.

Дальше была такая запись об ошибке:

CODE: <div class="col-sm-2"><img src="<?php echo $thumb; ?>" alt="<?php echo $heading_title; ?>" title="<?php echo $heading_title; ?>" class="img-thumbnail" /></div>
LINE: 24
NOT FOUND!

Здесь логика сломалась. Попытка обсудить этот кусок с коллегой, больше работавшим с OCMOD, привела к быстрой поломке и его логики. Код из XML найден, строка такая-то. (Строка в XML и в исходнике совпадает полностью, это было сразу же проверено.) Но почему она NOT FOUND? Сразу после того, как была FOUND?

Бился я над этим часа два - проверял и то, и это, и концы строк, и trim="true", и настройки FTP-клиента (вдруг концы строк на лету конвертирует), и чёрт знает что ещё. В общем, 2 часа. Мучился до тех пор, пока не обратил внимание, что эта строка в исходнике - 21-я, а не 24-я. "Обана!" - подумал я. И сравнил эти строки в TPL клиента и в оригинальном файле.

И таки да - именно в этой строке был исправлен один символ. И немного дальше в нашей XML-ке были правила, касающиеся той строки, изменённой. Но какой-то, блин, гений в лог записал совершенно другую строку, а не 24-ую! Сбив меня со следа очень конкретно и уведя в совершенно неправильном направлении. Пля! Чтоб ему икалось.

Не лог, а предательство сплошное.

Clipboard Catcher для линуксоидов (bash, xclip)

- Posted in Uncategorized by

Давно пользуюсь такой штукой, но поискал и оказалось, что мне не приходило в голову поделиться рецептом. Пользователи WikidPad поймут с полуслова, остальные... ну не знаю, поймут как-нибудь.

При запущенной утилитке всё, что копируется в клипборд, дублируется в текстовый файл. Очень удобно, когда предстоит много копи-паста: позволяет избавиться от Alt-Tab, Ctrl-V, Alt-Tab после каждого Ctrl-C.

Итак, `~/bin/cbc`:

#!/bin/bash
## Clipboard catcher
## first parameter (optional) -- log file name
##
## @author Ruslan Brest, mailto:r-b@mail.ru
## @date   05/07/2013
if [ -z "$1" ]; then
  FILE="00-clipboard-continous.txt"
else
  FILE="$1"
fi
if [ -z "$2" ]; then
  DELIMITER="\n"
else
  DELIMITER="\n-- -- -- --\n"
fi
echo "FILE: ${FILE}"
echo "DELIMITER: ${DELIMITER}"
# Prevent pasting previously existing clipboard content:
PREV_CB=`xclip -o -selection clipboard`
#PREV_CB=`xclip -o -selection primary`
CB=""
## Set log file name given by user
if [ "$1" != "" ]; then FILE=$1; fi
## capture clipboard content
while true
do
    ## Ctrl-C
    CB=`xclip -o -selection clipboard`
    ## Mouse selection
    # CB=`xclip -o -selection primary`
    if [ "$CB" != "$PREV_CB" ]
    then
        echo -e "$CB" >> $FILE
        echo -e $DELIMITER >> $FILE
        PREV_CB=$CB
    fi
    sleep 1s
done

Как пользоваться? Набираем в консоли `cbc`. Копируем, копируем, копируем, копируем (по Ctrl-C)... Переключаемся опять в консоль, где запущен `cbc`, и прерываем его по Ctrl-C. Ищем файл clipboard-continous.txt - там всё награбленное.

Если нет `xclip` - устанавливаем. Для убунту - sudo apt-get install xclip

Устройство Opencart 2.0 (OC2): работа инсталлятора, модификаций и модулей

- Posted in Uncategorized by

Мда… Синтаксические ошибки на этапе формирования модификации запросто укладывают весь сайт. OCMOD формирует файл с ошибкой, дальше ВСЁ… ФИНИШ. Пока не пофискишь - не поедет. Учитывая, что синтаксическая ошибка может запросто возникнуть при конфликне модулей, система будет слабо автоматизируемой. Должен работать квалифицированный админ. Сценарий примерно такой:

  • ставим модификацию
  • обновляем кэш
  • если система загнулась:
    • удаляем system/modification/*
    • заходим в систему как админ и удаляем проблемную модификацию
    • перегенерируем кэш
    • пишем рекламацию

Чем модификация отличается от инсталлятора

Модификация работает только в каталоге system/modification . Ей не нужны права на запись по всему движку: это инсталлеру надо. И достигается через фтп-функции и фтп-пользователя.

Модификация исходных файлов не меняет. Результат работы записывается в system/modification .

Что такое инсталлятор, модификация и модуль. Их взаимодействие

По большому счёту, в системе модуля нет вообще. Причина в том, что в принципе отсутствует интерфейс взаимодействия внешнего кода с ядром. Некоторые зачатки появились во второй версии с внесением events. И всё. В остальном - есть некоторое изменение кода, состоящее из новых файлов и изменения старых, а также механизм настройки параметров и визуальной привязки кода к layout. Вот это всё горделиво названо словом “модуль”. И дальше есть инсталлятор, который вносит новые файлы в систему и регистрирует xml для внесения изменений в существующие файлы. А модификация просто реализует эти изменения. Вот и всё.

Процедура инсталляции размещает по местам новые файлы и записывает в таблицу XML. Кроме того, она исполняет скрипты install.sql & install.php . Больше она не делает ничего.

Модификация при нажатии кнопки “Refresh”:

  • очищает кэш модификаций system/modification/*
  • ищет system/modification.xml и исполняет его
  • ищет system/*.ocmod.xml и исполняет их. Это “форточка” для девелопера.
  • последовательно извлекает из таблицы oc_modification все xml, запись за записью, и создаёт новый кэш из модифицированных файлов.

Всё

Opencart 2.0.1.1 bugfix: default language fallback

- Posted in Uncategorized by

Если вы пользуетесь не только английским языком, то вам наверняка попадались отсутствующие строки наподобие text_home, button_continue, button_login.

Раньше в этом случае вообще происходила ошибка и страница "ломалась": надо было следить за соответствием переводов и добавлять отсутствующие в переводе строки или дублировать файл целиком из английской версии модуля. В версиях Opencart 2.0 отсутствие некоторых строк в переводах наконец-то более-менее исправлено. Если раньше возникала ошибка, то сейчас берётся строка из языка по умолчанию (английского).

Но всё равно не учтён один момент. Если в английском работает логика наследования строки из english/default.php (например, переводы text_continue, text_yes, text_no и т.п. не дублируются в языковых файлах модулей и компонентах движка, а берутся из default.php), то в случае точно такой же ситуации в дополнительном языке языковый ресурс выбирается по неправльной цепочке:

  • надо: ru/module -> ru/default.php -> en/default.php
  • а происходит так: ru/module -> en/module

И вуаля: в русском строки нет (т.к. её не было в оригинале), в английском нет (т.к. там оно берётся из default.php), в результате - вывод служебного ключа строки, а не правильного перевода. Вот те самые text_home, button_continue, button_ok и тому подобные.

Лечится так:

commit b08fa836f2a55eb9ef2205ce8c3491b6d00c5319
Author: Ruslan Brest <rb@labtodo.com>
Date:   Tue Jan 13 11:01:54 2015 -0500
    [!][lang] fix language fallback:
    
    if translation string not found in lang/module/submodule,
    fall back to lang/default at first, then to english/default
diff --git a/html/extensions/system/library/language.php b/html/extensions/system/library/language.php
index c8ba268..5d10d50 100644
--- a/html/extensions/system/library/language.php
+++ b/html/extensions/system/library/language.php
@@ -15,16 +15,18 @@ class Language {
    public function load($filename) {
        $_ = array();
 
-       $file = DIR_LANGUAGE . $this->default . '/' . $filename . '.php';
 
-       if (file_exists($file)) {
-           require($file);
-       }
-
-       $file = DIR_LANGUAGE . $this->directory . '/' . $filename . '.php';
-
-       if (file_exists($file)) {
-           require($file);
+       $files = array(
+           DIR_LANGUAGE . $this->default   . '/' . $filename . '.php',
+           DIR_LANGUAGE . $this->directory . '/' . 'default.php',
+           DIR_LANGUAGE . $this->directory . '/' . $filename . '.php'
+           );
+
+       foreach($files as $file)
+       {
+           if (file_exists($file)) {
+               require($file);
+           }
        }
 
        $this->data = array_merge($this->data, $_);

Opencart 2.0.1.1 bugfix: отправление отзывов к товару

- Posted in Uncategorized by

Починил ошибку при отправлении email-уведомлений админу, когда покупатель оставляет отзыв к товару. Из-за неё скорей всего форма отзыва вела себя невнятно, не показывая покупателю, что всё уже случилось и жизнь прекрасна.

В результате было непонятно, что вообще произошло, пользователи отправляли по несколько отзывов (самые прилежные), а лог ошибок опенкарта заполнялся строчками, причину и место возникновения которых хрен угадаешь:

2014-12-30 18:44:07 - PHP Warning:  html_entity_decode() expects parameter 1 to be
string, array given in /..../system/library/mail.php on line 30
2014-12-30 18:44:07 - PHP Notice:  Error: E-Mail to required!
in /..../system/library/mail.php on line 63

Админ также не получал уведомлений о непроверенных отзывах, что тоже печально и грустно и не способствует повышению доверия между покупателем и магазином.

Лечится так

Простыми словами:

  • открываем catalog/model/catalog/review.php,
  • ищем строку $mail->setTo(array($this->config->get('config_email')));
  • и убираем `array(` с одной стороны и лишний `)` с другой.

Вторая добавка там же рядом, чуть ниже (см. diff) - чтобы избежать ошибок при отправке уведомлений по списку email-ов другим админам магазина. Уведомления отправляются на email владельца магазна (указывается в настройках магазина) и по списку дополнительных адресов в настройках, разделённому запятыми. Если у вас вокруг запятых затешутся пробелы - будут ошибки.

Цветными словами:

commit 870bb1469173e9ec28346408bc19838838c6fd17
Author: Ruslan Brest <rb@labtodo.com>
Date:   Sat Jan 10 20:47:00 2015 -0500
    [!] fix admin email notification sending when customer posts product review
diff --git a/catalog/model/catalog/review.php b/catalog/model/catalog/review.php
index 2eeab26..ae23828 100644
--- a/catalog/model/catalog/review.php
+++ b/catalog/model/catalog/review.php
@@ -22,7 +22,7 @@ class ModelCatalogReview extends Model {
                        $message .= $this->db->escape(strip_tags($data['text'])) . "\n\n";
 
                        $mail = new Mail($this->config->get('config_mail'));
-                       $mail->setTo(array($this->config->get('config_email')));
+                       $mail->setTo($this->config->get('config_email'));
                        $mail->setFrom($this->config->get('config_email'));
                        $mail->setSender($this->config->get('config_name'));
                        $mail->setSubject($subject);
@@ -33,6 +33,7 @@ class ModelCatalogReview extends Model {
                        $emails = explode(',', $this->config->get('config_mail_alert'));
 
                        foreach ($emails as $email) {
+                               $email = trim($email);
                                if ($email && preg_match('/^[^\@]+@.*.[a-z]{2,15}$/i', $email)) {
                                        $mail->setTo($email);
                                        $mail->send();

Opencart 2.0.1.1 bugfix: OCMOD Multiline fix

- Posted in Uncategorized by

В OCMOD нами добавлена поддержка атрибута "quote" (bool) в режиме regex. Это позволяет делать замену не строки, а набора строк. С этим переключателем используется функция preg_quote:

preg_quote() takes str and puts a backslash in front of every character that is part of the regular expression syntax. This is useful if you have a run-time string that you need to match in some text and the string may contain special regex characters. The special regular expression characters are: . \ + * ? [ ^ ] $ ( ) { } = ! < > | : -

После этого preg позволяет многострочную замену. Обычный режим работы продолжает работать по-старому: просто добавляется опция, с которой становится возможно использовать многострочные замены в ocmod XML.

Рекомендуется всем: стандартный функционал не затрагивается, появляется новый.

Также описано на OpencartJazz: OCJ :: OCMOD Multiline fix. Там прикреплён изменённый файл, но пока его можно скачать только после регистрации и "покупки" за 0.00. Будет время - починю это неудобство.

diff --git a/admin/controller/extension/modification.php b/admin/controller/extension/modification.php
index 086a65c..7159ffb 100644
--- a/admin/controller/extension/modification.php
+++ b/admin/controller/extension/modification.php
@@ -307,12 +307,18 @@ class ControllerExtensionModification extends Controller {
 } else {                                   
    $search = $operation->getElementsByTagName('search')->item(0)->textContent;
    $limit = $operation->getElementsByTagName('search')->item(0)->getAttribute('limit');
+   $quote = $operation->getElementsByTagName('search')->item(0)->getAttribute('quote');
    $replace = $operation->getElementsByTagName('add')->item(0)->textContent;
                                        
    // Limit
    if (!$limit) {
        $limit = -1;
    }
+
+   // Quote
+   if ($quote=='true') {
+       $search = preg_quote($search);
+   }
 
    // Log
    $match = array();
Page 1 of 8