E-commerce: интересное за неделю (w49)

- Posted in Uncategorized by

На этот раз - статистика и факты об основных трендах электронной коммерции.

Онлайновые покупатели США в предрождественский сезон тратят в среднем 166$

97% украинских пользователей ежедневно проверяют свою электронную почту

  • почти 97% украинских пользователей ежедневно проверяют свои почтовые ящики, пишут и читают электронные сообщения. В среднем по миру этот показатель меньше почти на 10%, а в Западной Европе, например, и того ниже – около 83%.
  • общение в социальных сетях – эту ежедневную активность подтвердило чуть более 91% пользователей. Немалая доля украинцев также любит общаться посредством решений видео- и голосовой связи, например, Skype (около 79%) и при помощи сервисов мгновенных сообщений (почти 62%).
  • Ежедневно около 89% пользователей включают свои компьютеры для того, чтобы посмотреть видео в Сети, ещё 79% украинцев слушают музыку или радио в Интернете, и почти 65% проводят какую-то часть дня за онлайн-играми. Охотников за бесплатным программным обеспечением среди наших соотечественников тоже немало – почти 85%. А вот в той же Западной Европе, к примеру, таких любителей «бесплатного сыра» значительно меньше – около 48%.
  • покупки в Интернете с личных ПК ежедневно совершает более 55% украинцев. А чуть более 39% наших граждан активно пользуются системами онлайн-банкинга.

Мобильным Интернетом пользуется каждый десятый взрослый украинец

В 2012 году 13% интернет-трафика идет с мобильных устройств

  • в этом году количество интернет-пользователей по всему миру выросло на 8% и достигло 2.4 миллиарда.
  • По числу пользователей лидирует Китай (538 млн), Иран лидирует по темпам роста (до 205%), в то время как число пользователей в США выросло на 3%, и составило 244 млн. человек.
  • количество пользователей смартфонов выросло на 42%, достигнув 1.1 млрд человек. На первом месте снова Китай — 270 млн пользователей смартфонов (рост на 50%), а на втором — США со 172 млн пользователей смартфонов (рост на 50%).
  • 13% мирового интернет-трафика, по оценкам, представленным в отчете, идет с мобильных устройств, и, как предполагается, этот процент будет увеличиваться в связи с ростом числа пользователей смартфонов и планшетов. (2009 - 1%, 2010 - 4%, 2012 - 13%)

Куда деваются пользователи Android?

Оригинал: http://www.asymco.com/2012/11/26/the-android-engagement-paradox/
  • в США пользователей Android около 65 млн, а пользователей iPhone – чуть больше 40 млн
  • если выделить мобильный трафик из всего шоппингового трафика в период Дня Благодарения и Черной Пятницы (традиционно самых активных периодов распродаж в году), то количество этого трафика составит 24% (для сравнения – в 2010 г. он составлял около 5%, в 2011 г. – около 15%) из всего онлайн-трафика.
  • с 2010 по 2012 – пользовательская база Android выросла с 12 млн устройств до 65 млн
  • доля трафика Android в мобильном трафике с 2010 по 2012 упала с 27 до 21% (достигнув пика в 2011 г. с 29%)
  • Доля iOS-трафика за этот период колеблется от 71 до 77%. Причем в планшетах перевес устройств Apple заметен еще более сильно. Если из мобильного трафика вычленить трафик планшетов, то 88% планшетного трафика составляет iPad, Kindle и Nook – 5,5%, Galaxy Tab – 1,8%, а остальные планшеты (которых легион) – 4,4%.
  • одна из популярных и очевидных версий как раз и касается того, что более дешевые устройства на базе Android привлекают ту аудиторию, которая не готова тратить деньги онлайн (да и вообще – на покупку приложений, и т.д.). Однако, даже дорогие Android-смартфоны – тот же Galaxy S II и III – продаются довольно большими объемами, вполне сравнимыми с объемами продаж iPhone.
  • Еще одна лежащая на поверхности версия – люди пользуются Android-устройствами как «dumb-phone» (тупыми телефонами), то есть только совершая голосовые звонки и обмениваясь SMS-сообщениями.
  • прибыль с рынка смартфонов на сегодня делят между собой две компании: Apple и Samsung, собирая порядка 95% прибыли, причем из них Apple забирает в районе 65-70%.

Nielsen: пользователи тратят все больше времени на социальные сети

Оригинал: http://blog.nielsen.com/nielsenwire/social/2012/ http://blog.nielsen.com/nielsenwire/global/social-media-report-2012-social-media-comes-of-age/
  • в социальных сетях и тратят на них 20% всего времени работы за компьютером и более 30% своего мобильного пребывания в сети
  • в США в социальных медиа пользователи провели более 121 млрд. часов в июле 2012 года, это на 37% больше, чем в июле 2011 года, когда на социальные сети было потрачено 88 млрд. часов.
  • время, проведенное пользователями в социальных медиа посредством мобильных приложений, увеличилось в 2012 году на 63% по сравнению с 2011 годом.
  • Лидером среди социальных сетей остается Facebook, посещаемость которого составляет 152,2 млн. посетителей через ПК, 78,4 млн. - через мобильные приложения и 74,3 млн. - при помощи мобильного интернета.

Публичный саппорт: QIWI удалил кошелек с деньгами

Бизнес по-русски :(

E-commerce и webdev: интересное за неделю (w46,47)

- Posted in Uncategorized by

Электронная коммерция

«Электронная Торговля – 2012»: Незажженные звезды

Одной из самых интересных секций участниками была признана секция «Незажженные звезды», посвященная новым рынкам для развития электронной коммерции.

За месяц украинская аудитория Facebook уменьшилась на 45 тыс. человек

Cоциальные сети не принесут вам продаж, у них другая цель [Исследование]

[...] социальные сети почти никогда не становятся источниками покупок. Только жалкий 1% покупок напрямую обеспечен отметками «Мне нравится» в Facebook. Так что же является движителем продаж? Это старые добрые средства — email-маркетинг и поиск [...]

Размещение товаров на Яндекс.Маркете. Типичные проблемы и их решение

Как цвета влияют на продажи?

Как разработать продающую страницу товара в интернет-магазине: советы и результаты исследований

Как оптимизировать дизайн электронных писем для мобильных устройств

О важности использования тэгов «alt» и «title» к изображениям на вашем веб-сайте

Психология состоятельного мужчины: как и что они покупают

Повышаем конверсию: каталоги товаров для планшетных устройств

Полноценное использование данных о внутреннем поиске

Google Analytics: 6 основных инструментов для интернет-продавца

Веб-аналитика для брендов

Web development

PrestaShop is now on GitHub

MySQL Performance: InnoDB vs MyISAM in 5.6

E-commerce: интересное за неделю (w45)

- Posted in Uncategorized by

5 уроков по электронной коммерции - eMagnat

Поздравляя клиента правильно, повышаем свои продажи - eMagnat

4 аспекта, влияющие на комиссию в партнерских программах | Маркетинг и работа с товаром

Где располагать призывы к действию?

Почему первый экран — это миф, или где на самом деле располагать призывы к действию? Все зависит от мотивации. Насколько высока мотивация потенциального клиента нажать на кнопку? Насколько желанно для него предложение именно тогда, когда вы просите клиента кликнуть? Призывы к действию во втором экране обладают лучшей конверсией, и обязаны этому не самим призывам: это значит, что потенциальные клиенты были мотивированы принять предложение ПОСЛЕ того, как прочитают текст. Оригинал на английском: Why “The Fold” Is A Myth – And Where To Actually Put Your Calls To Action

SMM

Готовая стратегия контент-маркетинга

Существует две модели ведения бизнеса в интернете. Первая модель, назовем ее традиционная, может быть описана простой формулой: «приведи покупателя на сайт и продай». По такому алгоритму действует абсолютное большинство компаний. Реальность данного метода – это ежемесячные расходы на SEO и Директ, тотальная зависимость от поисковых алгоритмов и отсутствие хоть какой-либо лояльности посетителей. Другая модель ведения бизнеса в интернете опирается на совершенно иные принципы. Лояльность формируется до того, как человек познакомится с вашим коммерческим предложением. Вопрос «у кого купить?» перестает существовать в принципе – клиент точно знает, к кому обратиться. И это знание в корне отличает его от посетителя, которого привела обычная реклама.

Социальная коммерция: ленивым покупателям посвящается / Хабрахабр

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

Новая ступень развития Facebook: много брендов, мало друзей

Что-то идет не так? Многие владельцы страниц брендов отмечают, что в этом году, несмотря на рост числа подписчиков, трафик, генерируемый страницами, значительно снизился. Источник: Is Facebook “broken on purpose” to sell promoted posts? Traffic from fan pages drops as new option to pay for reach appears.

Другое

Онлайн-оплата "пластиком": бухгалтерские аспекты

Почему российские торговцы не любят карточки

E-commerce: интересное за неделю (w44)

- Posted in Uncategorized by

Математическая модель оценки качества страницы

Михаил Пискунов: Четыре составляющих «сарафанного» маркетинга

никакого «сарафанного» маркетинга не существует. Равно как и «партизанского», «доверительного», «вирусного» и всех иных. Маркетинг один – инструменты обретения и расширения доли рынка разные. Отсюда и такой сонм названий, только это совсем не означает, что тот же «сарафанный» маркетинг – это какой-то особый вид. Нужно четко понимать, что коммерческие слухи – всего лишь один из множества тактических инструментов для достижения стратегических целей компании. И при выборе его из всего арсенала маркетинговых приемов всегда следует обращать внимание на четыре основополагающих момента: вирусный потенциал предложения ценности, используемые выбранным вами агентством приемы «посева» слухов, возможность контроля за проведением слуховой кампании и ее стоимость. Теперь обо всем по порядку.

Как из контента сделать топливо для вашего маркетинга

Люди устали от агрессивной рекламы. Они просто игнорируют ее. Как же вам достучаться до покупателей? Как сделать так, чтобы они вас услышали и запомнили? Ответ прост: перестаньте продавать! Наоборот, сделайте им подарок — поделитесь с ними интересной и полезной информацией, лишенной рекламы. Майкл Стелзнер. Контент-маркетинг. Новые методы привлечения клиентов в эпоху Интернета / Michael Stelzner. Launch. How to Quickly Propel Your Business Beyond the Competition - отзывы, рецензии, главы из книги

Поиск с морфологией и релевантностью (модуль Opencart)

Opencart - автопубликация отзывов

- Posted in Uncategorized by

По умолчанию в Opencart'е включено модерирование отзывов к товарам, которые оставляют покупатели. То есть нужно зайти в панель администрирования, далее в "Каталог-Статьи", где будет список еще непроверенных отзывов. Затем заходим в каждый отзыв, и либо одобряем (включаем) его, либо не одобряем (не включаем).

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

Для этого нужно в файле catalog\model\catalog\review.php заменить функцию (в оригинале она расположена в одну строку):

public function addReview($product_id, $data) {
        $this->db->query("INSERT INTO " . DB_PREFIX . "review SET author = '" .
            $this->db->escape($data['name']) .
            "', customer_id = '" . (int)$this->customer->getId() .
            "', product_id = '" . (int)$product_id .
            "', text = '" . $this->db->escape($data['text']) .
            "', rating = '" . (int)$data['rating'] .
            "', date_added = NOW()");
    }

на такую:

public function addReview($product_id, $data) {
    $this->db->query("INSERT INTO " . DB_PREFIX . "review SET author = '" .
            $this->db->escape($data['name']) .
            "', customer_id = '" . (int)$this->customer->getId() .
            "', product_id = '" . (int)$product_id .
            "', text = '" . $this->db->escape($data['text']) .
            "', rating = '" . (int)$data['rating'] .
            "', status = 1, date_added = NOW()");
    }

Opencart - тюнинг полей описания (CKeditor)

- Posted in Opencart by

В Opencart для описаний товара, категории, статей и т.д. используются поля описания. Такие, как на рисунке ниже.

CKEditor custom toolbar

Технически это реализовано с помощью компонента CKEditor - свободного WYSIWYG-редактора, который может быть использован на веб-страницах. В поле описания отображается описание продукта, категории, статьи и т.д. в том виде, который вы увидите в магазине. Это основное достоинство WYSIWYG-редактора.

Разработчики CKEditor заложили в своё детище гораздо больше возможностей, чем первоначально доступно в Opencart. Настало время устранить эту историческую несправедливость :)

Всё гениальное просто

Этот способ подойдёт для большинства людей.

Чтобы включить скрытые возможности CKEditor, нам нужно в файле admin\view\javascript\ckeditor\config.js заменить строку config.toolbar = 'Custom'; строкой config.toolbar = 'Full';

Не правда ли, просто?

А вот что получим в итоге:

CKeditor full toolbar

Заметили, сколько новых кнопок появилось на тулбарах CKEditor’а? Неплохой профит, правда?

Тут и предварительный просмотр, и шаблоны, и поиск с заменой, возможность убрать форматирование (если вы откуда-то вставляете описание), цитаты, смайлики, стили, возможность отображать блоки...

Кстати, на просторах интернета можно найти различного рода vqmod-файлы, делающие то же самое, но более сложным путём: десятки строк кода, проблемы с совместимостью, необходимость устанавливать vqmod. Помните, простота - залог успеха.

В глубины CKEditor’а

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

Для начала пару ссылок:

Очень полезные ссылки. Документация хорошая и подробная. И всё, что написано здесь, можно найти в документации. Рекомендуется в закладки.

В опенкарте CKEditor расположен в папке admin\view\javascript\ckeditor\.

Наиболее полезные файлы -- это ckeditor.js (собственно, сам компонент) и config.js (конфигурация компонента).

Изучаем конфиг

Например, в строчке config.language = 'fr'; задаётся язык по умолчанию. Смело меняем fr на ru.

Строки config.filebrowserWindowWidth = '800'; и config.filebrowserWindowHeight = '500'; задают ширину и высоту компонента. Здесь интересна больше высота. Если у вас большие по объёму тексты описания, логичным будет увеличить высоту компонента. Будет удобней.

Строка config.resize_enabled = false; -- вкл/выкл возможность менять размер компонента. Странно, что она отключена. Ставим true вместо false.

Ещё одна полезная фича – использовать свою конфигурацию в отдельном файле. Удобно тем, что можно безболезненно обновлять CKEditor, не затрагивая конфигурацию. Делается так:

В файле конфигурации config.js добавляем строчку customConfig : 'myconfig.js';

То есть мы указали использовать в качестве конфига файл myconfig.js. Где взять этот файл? Делаем копию файла config.js, переименовываем её в myconfig.js, настраиваем как нужно.

Настриваем тулбары

Тулбары – это строки с иконками в верхней части компонента.

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

В конфиге тулбары описываются так:

config.toolbar_Full = [
        ['Source','-','Save','NewPage','Preview','-','Templates'],
        ['Cut','Copy','Paste','PasteText','PasteFromWord','-','Print', 'SpellChecker', 'Scayt'],
        ['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'],
        ['Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField'],
        '/',
        ['Bold','Italic','Underline','Strike','-','Subscript','Superscript'],
        ['NumberedList','BulletedList','-','Outdent','Indent','Blockquote'],
        ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock'],
        ['Link','Unlink','Anchor'],
        ['Image','Flash','Table','HorizontalRule','Smiley','SpecialChar','PageBreak'],
        '/',
        ['Styles','Format','Font','FontSize'],
        ['TextColor','BGColor'],
        ['Maximize', 'ShowBlocks','-','About']
    ];

Запись понятна и с ней не так уж сложно разобраться. Как вы можете видеть, символ '/' разделяет строки тулбара, символ '-' добавляет разделитель (вертикальная чёрточка) внутри строки, группы иконок в строке заключаются в символы […], название тулбара записывается в строчке config.toolbar_Full после символа подчёркивания.

Взяв за образец запись тулбара Full, Вы можете настроить его как Вам удобно, например, убрать невостребованные иконки, поменять их местами, удобно сгруппировать по строкам.

В динамике

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

Открываем файл admin\view\template\catalog\category_form.tpl

Ищем ближе к концу файла примерно такой код:

<script type="text/javascript" src="view/javascript/ckeditor/ckeditor.js"></script> 
<script type="text/javascript"><!--
<?php foreach ($languages as $language) { ?>
CKEDITOR.replace('description<?php echo $language['language_id']; ?>', {
    filebrowserBrowseUrl: 'index.php?route=common/filemanager&token=<?php echo $token; ?>',
    filebrowserImageBrowseUrl: 'index.php?route=common/filemanager&token=<?php echo $token; ?>',
    filebrowserFlashBrowseUrl: 'index.php?route=common/filemanager&token=<?php echo $token; ?>',
    filebrowserUploadUrl: 'index.php?route=common/filemanager&token=<?php echo $token; ?>',
    filebrowserImageUploadUrl: 'index.php?route=common/filemanager&token=<?php echo $token; ?>',
    filebrowserFlashUploadUrl: 'index.php?route=common/filemanager&token=<?php echo $token; ?>'
});
<?php } ?>
//--></script> 
<script type="text/javascript"><!--

Начиная со строки filebrowserBrowseUrl: 'index.php?route=common/filemanager&token=<?php echo $token; ?>', идут настройки CKEditor.

Мы можем добавить toolbar : 'Full', и в шаблоне CKEditor будет отображать полный тулбар. Т.е., можно делать по аналогии с файлом конфига. Но если в конфиге строчки начинаются со слова .config, то здесь их нужно писать без начального слова .config.

Динамически конфигурируя и настраивая CKEditor, вы можете настроить поля описания как вам удобно в каждом конкретном случае.

Secret Codes For Android Mobile Phones

- Posted in Mobile Gadgets by

1. Complete Information About your Phone

*#*#4636#*#*

This code can be used to get some interesting information about your phone and battery.

Usage statistics

2. Factory data reset

*#*#7780#*#*

This code can be used for a factory data reset. It'll remove following things:

  • Google account settings stored in your phone
  • System and application data and settings
  • Downloaded applications

It'll NOT remove:

  • Current system software and bundled application
  • SD card files e.g. photos, music files, etc.

Note: Once you give this code, you get a prompt screen asking you to click on "Reset phone" button. So you get a chance to cancel your operation.

3. Format Android Phone

*2767*3855#

Think before you give this code. This code is used for factory format. It'll remove all files and settings including the internal memory storage. It'll also reinstall the phone firmware.

Note: Once you give this code, there is no way to cancel the operation unless you remove the battery from the phone. So think twice before giving this code.

4. Phone Camera Update

*#*#34971539#*#*

This code is used to get information about phone camera. It shows following 4 menus:

  • Update camera firmware in image (Don't try this option)
  • Update camera firmware in SD card
  • Get camera firmware version
  • Get firmware update count

WARNING: Never use the first option otherwise your phone camera will stop working and you'll need to take your phone to service center to reinstall camera firmware.

5. End Call/Power

*#*#7594#*#*

This one is my favorite one. This code can be used to change the "End Call / Power" button action in your phone. By default, if you long press the button, it shows a screen asking you to select any option from Silent mode, AirPlane mode and Power off.

You can change this action using this code. You can enable direct power off on this button so you don't need to waste your time in selecting the option.

6. File Copy for Creating Backup

*#*#273283*255*663282*#*#*

This code opens a File copy screen where you can backup your media files e.g. Images, Sound, Video and Voice memo.

7. Service Mode

*#*#197328640#*#*

This code can be used to enter into Service mode. You can run various tests and change settings in the service mode.

8. WLAN, GPS and Bluetooth Test Codes

*#*#232339#*#* OR *#*#526#*#* OR *#*#528#*#* - WLAN test (Use "Menu" button to start various tests)

*#*#232338#*#* - Shows WiFi MAC address

*#*#1472365#*#* - GPS test

*#*#1575#*#* - Another GPS test

*#*#232331#*#* - Bluetooth test

*#*#232337#*# - Shows Bluetooth device address

9. Codes to get Firmware version information

*#*#4986*2650468#*#* - PDA, Phone, H/W, RFCallDate

*#*#1234#*#* - PDA and Phone

*#*#1111#*#* - FTA SW Version

*#*#2222#*#* - FTA HW Version

*#*#44336#*#* - PDA, Phone, CSC, Build Time, Changelist number

10. Codes to launch various Factory Tests

*#*#0283#*#* - Packet Loopback

*#*#0*#*#* - LCD test

*#*#0673#*#* OR *#*#0289#*#* - Melody test

*#*#0842#*#* - Device test (Vibration test and BackLight test)

*#*#2663#*#* - Touch screen version

*#*#2664#*#* - Touch screen test

*#*#0588#*#* - Proximity sensor test

*#*#3264#*#* - RAM version

See also


Я в отъезде, вернусь 12&#45;13 сентября

- Posted in Uncategorized by

Я в отъезде, вернусь приблизительно 12-13 сентября.

Буду почти без средств связи, так что ответить скорей всего не будет возможности.

UPD. Вернулся 20/09, в инбоксе больше 400 писем, постараюсь разобрать за пару дней.

Amazon AWS анонсировала &quot;холодильник&quot; для дешевого хранения больших объемов

- Posted in Uncategorized by

Несколько дней назад Amazon AWS (Amazon Web Services) анонсировала новый сервис Amazon Glacier (glacier - ледник).

Сервис нацелен на дешёвое (1 цент за гигабайт в месяц) хранение больших объёмов данных, доступ к которым нужен редко: архивы документов, проектов, оригиналы фото и видео. Насмотрелись, сложили и потом месяцами (а то и годами) не пользуемся.

Помимо стоимости хранения есть ещё цена за трафик, но тоже мизерная: первый гигабайт в месяц бесплатен, до 10 терабайт в месяц - 12 центов за гигабайт. Но это если вы скачиваете сохраненные там данные. Закачать их туда - бесплатное удовольствие. Ну и ещё там есть какая-то мелочь за запросы (5 центов за 1000 запросов на закачивание и скачивание).

Удачная удаленно-сетевая альтернатива покупке запасных HDD для бекапных целей, как мне кажется. Резервные копии важных данных надо хранить, как известно, в физически удаленных местах. И этот запасной "диск в сети" - весьма дешевая альтернатива получается.

Прикинем примерно по финансам...

HDD: около 70$, 5 лет, 160 GB

Стоимость гигабайта на HDD сейчас колеблется в районе 20-25 центов за гигабайт - это я смотрел диски объёмами 160-500 GB. Самыми дешевыми оказались терабайтные (1000 GB - цена около 80$, 8 центов за гигабайт). Смотрел новые исправные на Aukro.ua. Может в магазинах и дороже, но если и так, то вряд ли намного.

Ну и даже если диск с записями лежит без дела в шкафу или сейфе - раз в 5 или 10 лет его желательно обновить и переписать на новый. Во избежание сюрпризов. Лучше чаще, конечно же, но речь сейчас не о политиках резервирования.

Я для бекапов купил в 2007 или 2008 году 160-гигабайтный винт (в ноутбуке у меня тогда было вообще всего 20 гигабайт), который активно использовал для бекапов проектов, фото, книг, статей и т.п. Активно - первый год, может два. Потом начал сохранять там интересные "долгосрочные" фильмы, которыми в итоге и забился весь объём. Позже стали появляться облачные сервисы и проекты в основном туда перекочевали - в интернет-облака по 2-5 бесплатных гигабайт - если хранить проекты и документы, без фото, музыки, видео и фильмов, то места требуется не так уж много.

AWS: 5 лет, 160 GB - до 96$

Сейчас 2012. Про этот диск я вспоминаю редко - практически им не пользуюсь. Покупал я его долларов за 70. Сейчас такие около 40 стоят, если не ошибаюсь. Допустим, 5 лет: это 60 месяцев. Примерно 1.16$ в месяц. Если хранить тот же объём на Amazon Glacier, 160 гигабайт будут стоить 1.6$ в месяц, около $20 в год. 1.6 x 60 месяцев = 96$ за 5 лет.

Выводы и нюансы

Да, чуть дороже получается. Некоторые ещё могут сказать, что с учетом того, что диски дешевеют, их можно чаще менять и наращивать объем примерно за ту же цену. Всё это примерно так, согласен. Но откровенно говоря, дешевле будет подождать 3-4 года и купить тот же объём в 2 раза дешевле, чем заниматься регулярно перепродажей на вторичном рынке и терять на этом.

С другой стороны - я там буду хранить многое, но оно долго не будет дотягивать до 160 гиг. Например, вряд ли я буду там складировать фильмы. Хотя может и буду, кто знает. Дёшево ведь получается. Поэтому цена за 5 лет будет меньше и расти постепенно. Например, если ориентироваться на предыдущий опыт: до 160 диск у меня будет неспешно заполняться пару лет. Ну может год. То есть 10-20 месяцев объём там будет значительно меньше 160 гигабайт. Если, разумеется, вы не обильный фотограф или любитель снимать всю свою жизнь на видео. Там наверное другая история.

В общем, для более-менее обычных условий мне разница в цене кажется несущественной. А вариант - очень интересным.

Ссылки

Как переименовать фото в соответствии с датой&#45;временем файла или съёмки

- Posted in Uncategorized by

Для фотографий (из EXIF или даты файла)

Фотографии отличаются от "просто файлов" тем, что у них может быть внутри дополнительная информация - EXIF. Для переименования всех фотографий в текущей папке из невразумительных имен IMAGE01234.jpg во что-то наподобие 2012-08-12-235516.jpg делаем так:

#!/bin/bash
jhead -n%Y-%m-%d-%H%M%S *.{jpg,jpeg,JPG,JPEG}

jhead -n ищет данные сначала в EXIF, и если этой информации там нет, то берет за основу дату-время файла.

Файлы любых других типов (по дате файла)

Для любых других файлов (логов и так далее) используем ls -l (приведенному скрипту надо указать имя файла):

#!/bin/bash
mv "$file" `ls -l "$1" | awk '{ print $6"_"$7 }' | tr ':' '-'`.jpg

Если файлов много, то то же самое в цикле

#!/bin/bash
for file in *.jpg *.jpeg *.JPG *.JPEG; do
    mv "$file" `ls -l "$file" | awk '{ print $6"_"$7 }' | tr ':' '-'`.jpg
done

Мой текущий ~/bin/dts полностью

#!/bin/bash
## Rename all known media files to their DTS (date/time stamp)
## ATTN: renames ALL files in current directory, no backups or ability to restore old filename
## <hr /><hr />-- JPEG <hr /><hr /><hr />--
jhead -n%Y-%m-%d-%H%M%S *.{jpg,jpeg,JPG,JPEG}
## <hr /><hr />-- PNG  <hr /><hr /><hr />--
for file in *.png *.PNG; do
    mv "$file" `ls -l --full-time "$file" | awk '{ print $6"_"$7 }' | tr ':' '-'`.png
done
## <hr /><hr />-- 3GP <hr /><hr /><hr />--
for file in *.3gp; do
    mv "$file" `ls -l --full-time "$file" | awk '{ print $6"_"$7 }' | tr ':' '-'`.3gp
done

Немцы выложили законы на гитхаб для мержей и пулл&#45;реквестов

- Posted in Uncategorized by

@vessi at Juick:

*Германия *вин *git немцы выложили законы на гитхаб. Можно делать пулл-реквесты, после одобрения бундестагом они будут смержены.

https://github.com/bundestag/gesetze

German Federal Laws and Regulations This Git repository contains all German federal laws and regulations in Markdown format. The source is the XML version of the laws from www.gesetze-im-internet.de.

Opencart: картинка товара в истории заказов покупателя

- Posted in Uncategorized by
oc153.image-account-order-information_.png

При просмотре истории заказов зарегистрированный покупатель теперь сможет видеть фото товаров. Фото товара используется текущее (т.е. может не совпадать с тем, которое было на момент покупки).

Делалось на текущей версии Opencart из репозитория (т.е. 1531+, commit:a023998).

На 1541 тоже работает.

Кто хочет видеть меньшие размеры фотографий, вместо предложенных config_image_category_width может использовать config_image_additional_width. Некоторые "за" и "против" можно увидеть в комментариях.

commit 8914dee796792b5662cd40638829fd52e4664289
Author: Ruslan Brest <rb@labtodo.com>
Date:   Mon Aug 6 08:11:06 2012 +0300
    Product image on account/order/info page
diff --git a/upload/catalog/controller/account/order.php b/upload/catalog/controller/account/order.php
index 41dd890..016b996 100644
--- a/upload/catalog/controller/account/order.php
+++ b/upload/catalog/controller/account/order.php
@@ -306,7 +306,8 @@ class ControllerAccountOrder extends Controller {
            $this->data['shipping_method'] = $order_info['shipping_method'];
            
            $this->data['products'] = array();
-           
+           $this->load->model('tool/image');
+
            $products = $this->model_account_order->getOrderProducts($this->request->get['order_id']);
 
            foreach ($products as $product) {
@@ -327,8 +328,15 @@ class ControllerAccountOrder extends Controller {
                    );                  
                }
 
+               if (empty($product['image'])) {
+                   $thumb = '';
+               } else {
+                   $thumb = $this->model_tool_image->resize($product['image'], $this->config->get('config_image_category_width'), $this->config->get('config_image_category_height'));
+               }
+
                $this->data['products'][] = array(
                    'name'     => $product['name'],
+                   'thumb'    => $thumb,
                    'model'    => $product['model'],
                    'option'   => $option_data,
                    'quantity' => $product['quantity'],
diff --git a/upload/catalog/model/account/order.php b/upload/catalog/model/account/order.php
index 605c1ff..e26012f 100644
--- a/upload/catalog/model/account/order.php
+++ b/upload/catalog/model/account/order.php
@@ -116,7 +116,7 @@ class ModelAccountOrder extends Model {
    }
    
    public function getOrderProducts($order_id) {
-       $query = $this->db->query("SELECT * FROM " . DB_PREFIX . "order_product WHERE order_id = '" . (int)$order_id . "'");
+       $query = $this->db->query("SELECT op.*, p.image FROM `" . DB_PREFIX . "order_product` op LEFT JOIN `" . DB_PREFIX . "product` p ON (p.product_id = op.product_id) WHERE order_id = '" . (int)$order_id . "'");
    
        return $query->rows;
    }
diff --git a/upload/catalog/view/theme/default/stylesheet/stylesheet.css b/upload/catalog/view/theme/default/stylesheet/stylesheet.css
index e0084eb..1110151 100644
--- a/upload/catalog/view/theme/default/stylesheet/stylesheet.css
+++ b/upload/catalog/view/theme/default/stylesheet/stylesheet.css
@@ -508,6 +508,11 @@ table.list .center {
    text-align: center;
    padding: 7px;
 }
+table.list .left > img
+{
+   float: left;
+}
+
 table.radio {
    width: 100%;
    border-collapse: collapse;
diff --git a/upload/catalog/view/theme/default/template/account/order_info.tpl b/upload/catalog/view/theme/default/template/account/order_info.tpl
index c94aa0c..e9843be 100644
--- a/upload/catalog/view/theme/default/template/account/order_info.tpl
+++ b/upload/catalog/view/theme/default/template/account/order_info.tpl
@@ -62,11 +62,14 @@
     <tbody>
       <?php foreach ($products as $product) { ?>
       <tr>
-        <td class="left"><?php echo $product['name']; ?>
+        <td class="left">
+          <img src="<?php echo $product['thumb']; ?>" alt="<?php echo $product['name']; ?>" />
+          <?php echo $product['name']; ?>
           <?php foreach ($product['option'] as $option) { ?>
           
            <small> - <?php echo $option['name']; ?>: <?php echo $option['value']; ?></small>
-          <?php } ?></td>
+          <?php } ?>
+        </td>
         <td class="left"><?php echo $product['model']; ?></td>
         <td class="right"><?php echo $product['quantity']; ?></td>
         <td class="right"><?php echo $product['price']; ?></td>

Измененные файлы текущей версии Опенкарт (1.5.3.xyz). Как всегда, рекомендую не заменять, а сравнивать со своими (WinMerge.org, TotalCommander, Meld и т.п.):

Opencart: купоны и транзакции (кредит магазина)

- Posted in Uncategorized by
подскажите по использованию купонов. еще никогда не использовал, но хочу попробовать. 1 купон может быть использован 1 раз или нет? я хочу каждому покупателю дарить скидочны купон, скажем на 10 гривен. можно ли сделать чтобы всем был один и тот же купон или нужно создавать для каждого покупателя новый купон?

Количество использований купона одним покупателем зависит от настроек купона.

Можно также установить, чтобы купон можно было использовать суммарно не более N раз.

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

Может оказаться удобней воспользоваться кредитом магазина, а не купонами. В данном случае.

Это в редактировании покупателей - там есть вкладка "транзакции". Если начислить в ней 10 грн (кредит магазина), то при следующей покупке эта сумма покупателем будут использована в счет оплаты заказа.

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

Что такое reward points?

- Posted in Uncategorized by
Что такое Price in reward points: 200? Что это означает и для чего это?

Это один из вариантов скидки и повторного привлечения покупателей.

Как это работает?

В оформлении товара можно указать, какое кол-во бонусных баллов получит покупатель на свой счёт при покупке. Зарегистрированные покупатели могут накапливать их, а затем купить какой-то товар за накопленные бонусы.

То есть вы по сути даёте небольшую скидку в счёт будущей покупки: купив сперва что-то полезное, покупатель может получить некое количество бонусов и оказаться заинтересованным в том, чтобы набрать ещё какое-то количество баллов для получения "подарка на выбор". То есть их разумное использование влечёт за собой:

  • возможные лишние покупки ради бонусов: именно их наличие в "подвопросном" товаре может заставить купить его. Допустим, есть в магазине товар, который вроде бы и нужен, но несрочно, да и покупать его именно здесь нет никакого стимула - и так продается везде и всюду. А бонусы этот стимул дают: "Ладно, куплю это здесь, может даже чуточку дороже, чем на каждом углу, зато бонусы накоплю и куплю на них потом самовар/пылесос/самолёт";
  • для накопления бонусов покупателю придётся зарегистрироваться. А это ещё один канал повторных продаж, который можно использовать;
  • за каким-нибудь товаром, который есть и у конкурентов, покупатель вернется к вам, т.к. только здесь он может потратить накопленные бонусы;
  • если есть широкий выбор, где покупать однотипные товары, у "подсаженного на бонусы" покупателя будет лишняя причина вспоминать ваш магазин и проверять, нельзя ли там купить этот товар, получив за это ещё немного бонусов.

Почему плохо иметь поле купонов и скидок на странице оформления заказа

- Posted in Uncategorized by

Купоны и сертификаты в Опенкарт находятся на странице корзины, отдельно от заказа.

<

p>В этом есть своё удобство:

  • если активно пользуетесь ими, уберите из шаблона прямые ссылки на оформление и проводите всех покупателей через корзину;
  • если не хотите терять часть прибыли - просто берете деньги у готового их отдать покупателя. Он уже готов их отдать. Это тот момент, ради которого продавцы работают. И заставить покупателя усомниться в своем решении именно сейчас - верх непрофессионализма.

Представьте ситуацию, когда вы долго сомневались, выбирали, считали, сравнивали, уже наконец созрели и решились на покупку, подходите к кассе в предвкушении "ну вот, скоро долгожданное ЭТО будет у меня в руках", держите деньги в руках, а на кассе Вас внезапно спрашивают - ну и где купон на скидку? Какую ещё скидку? Моё настроение точно бы испортилось, если в ЭТОТ момент мне бы вдруг тонко намекнули, что это вообще-то доступно на 10-30-50% дешевле. И я то ли где-то не заметил этот код, то ли отношусь к какому-то не тому сорту людей, и мне это открытым текстом сообщают.

Понимаете? О скидке я должен узнать гораздо раньше, а не на кассе. Тогда я буду чувствовать себя особенным. Узнаю об этом на кассе - впечатление диаметрально противоположное.

В случае интернет-магазина я скорей всего пойду искать купон - и не факт, что вернусь именно на этот сайт и к этому заказу. Потому что могу увидеть и более интересные предложения. А если вернусь и сессия к тому времени протухнет, "выбросив" набранные в корзину товары - это не прибавит ни радости, ни удобства покупателю (заново искать все товары?!), ни продавцу (ох, там было набрано товаров и аксессуаров на кругленькую сумму и человек стоял на кассе! А теперь покупатель рассержен и наверняка видел чужие предложения в поисках купонов...)

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

По-моему, купоны и скидки надо применять для привлечения покупателей СНАРУЖИ сайта, чтобы они приходили из форумов и сообществ, привлеченные своей эксклюзивностью и неизвестной другими скидкой. А когда человек уже пришёл и деньги достал - ну какой продавец будет снижать цену ни с того, ни с сего?

Emart shadowQRcode

- Posted in Uncategorized by

Для повышения продаж в "дохлый" обеденный час, когда торговля замирает, южнокорейский ритейлер придумал дать людям необычный уличный QR-код, который работает только с двенадцати до часу. За месяц эксперимента продажи в обеденное время выросли на 25%, а число новых зарегистрированных пользователей - на 58% по сравнению с предыдущим месяцем!

Смотрите видео:

Emart - крупная сеть магазинов, расположенных по всей Южной Корее. Однако у неё есть слабое место: в обеденное время торговля очень сильно сокращается. Поэтому для решения задачи по поднятию продаж в этот "мертвый час" с 12 до 13 дня Emart решили дать людям уникальный опыт и впечатления, доступные только в это время!

Они установили на улицах объёмные конструкции, которые днём превращаются солнцем в QR-коды :) Их можно сфотографировать и перейти на закодированные в этих кодах страницы магазина со специальным предложением "Sunny Sale" и купоном на $12 скидку. Товар доставляется прямо на дом или в офис, поэтому многие получают удовольствие от такого необычного "индиана-джонсовского" развлечения и заказывают какие-то товары с доставкой -- ящик пива, пиццу и т.п.

"Sunny Sale" конструкции были установлены в 13 местах в Сеуле в феврале 2012 года, впоследствии их число увеличили до 36. В результате было продано 12000 SunnySale-купонов, а число новых пользователей Emart выросло на 58% по сравнению с предыдущим месяцем. Продажи в обеденный час выросли на 25%. И если посмотрите видео - увидите, сколько удовольствия люди получают от самого процесса! А такие эмоции - отличная реклама и запоминаются надолго.

via Google+

Обновлён русский перевод для Opencart 1.5.3

- Posted in Opencart by

Обновлён перевод для Opencart 1.5.3.

Перевод для 1.5.2 приведён в соответствие с релизом Opencart 1.5.2.1 (убрано лишнее, что попало в эту ветку в промежутке от 1521, 1522, 153). 1.5.3 выделена в отдельную ветку.

Opencart: как переименовать способ доставки во что&#45;то более подходящее?

- Posted in Uncategorized by
Как переименовать способ доставки во что-то более подходящее, например "Курьерская доставка по Москве и Московской области"? Где эти файлы лежат и как именно это исправить?

В папке catalog/language/russian/ находятся языковые файлы, которые используются на "витрине" магазина,

в admin/language/russian/ - тексты для админ-части.

Будем менять тексты включенного способа доставки "Фиксированная стоимость доставки". Поэтому нас интересуют файлы .../shipping/flat.php в обеих указанных местах. То есть:

  • catalog/language/russian/shipping/flat.php
  • admin/language/russian/shipping/flat.php

Когда откроете их - увидите строки, которые видны на экране, там уже интуитивно понятно, что на что менять.

В админке тоже поменяйте соответственно, чтобы названия способов доставки совпадали и не было путаницы при их настройке и включении-выключении.

ВНИМАНИЕ: на витрине после изменения этих строк корзину надо очистить от товаров и повторить процесс покупки - иначе эти изменения не будут сразу видны.

Улучшение сортировки на витрине Opencart

- Posted in Uncategorized by

Поступил вопрос:

можно ли сделать по умолчанию сортировку товаров на витрине, одновременно по двум параметрам: по цене и названию. Тоесть сортируются по цене, а те у кого цены одинаковые уже по имени. Я сделал по цене, но я заметил что иногда Опенкарт меняет местами товары с одинаковыми параметрами сортировки, например если менять кол-во выводимого товара на одну страницу, ощущение что их тусует в произвольном порядке.

Действительно, лучше это предусмотреть и заодно изменить стандартный способ сортировки по полю "sort_order" (которое мало кто использует в товарах) на сортировку по названию товара (а название у товара есть всегда), чтобы избежать популярной, но иногда незаметной ситуации, когда один и тот же товар может выводиться и на первой, и на второй странице (вообще-то на любых, если использовать стандартный способ сортировки, применяемый в Опенкарт).

Diff приведён ниже, здесь же приведу описание обычными словами, т.к изменение всего одно и небольшое:

Как внести изменения вручную

1. Открываем файл catalog/model/catalog/product.php

2. Ищем функцию function getProducts

3. Пролистываем почти до конца в поисках кода запроса с ORDER BY и следующего за ним LIMIT. Соответствующий участок кода выглядит так:

$sort_data = array(
                'pd.name',
                'p.model',
                'p.quantity',
                'p.price',
                'rating',
                'p.sort_order',
                'p.date_added'
            );
            if (isset($data['sort']) && in_array($data['sort'], $sort_data)) {
                if ($data['sort'] == 'pd.name' || $data['sort'] == 'p.model') {
                    $sql .= " ORDER BY LCASE(" . $data['sort'] . ")";
                } else {
                    $sql .= " ORDER BY " . $data['sort'];
                }
            } else {
                $sql .= " ORDER BY p.sort_order";
            }
            if (isset($data['order']) && ($data['order'] == 'DESC')) {
                $sql .= " DESC";
            } else {
                $sql .= " ASC";
            }
            if (isset($data['start']) || isset($data['limit'])) {
                if ($data['start'] < 0) {
                    $data['start'] = 0;
                }
                if ($data['limit'] < 1) {
                    $data['limit'] = 20;
                }
                $sql .= " LIMIT " . (int)$data['start'] . "," . (int)$data['limit'];
            }

4. Мы сделаем две вещи: для начала изменим способ сортировки товаров на витрине по умолчанию на сортировку по названию:

} else {
                $sql .= " ORDER BY p.sort_order";
            }

меняем на

} else {
                $sql .= " ORDER BY pd.name";
            }

5. И теперь добавляем вторичную сортировку по названию:

if (isset($data['order']) && ($data['order'] == 'DESC')) {
                $sql .= " DESC";
            } else {
                $sql .= " ASC";
            }
            // БУДЕМ ВСТАВЛЯТЬ КОД СЮДА
            // БУДЕМ ВСТАВЛЯТЬ КОД СЮДА
            // БУДЕМ ВСТАВЛЯТЬ КОД СЮДА
            // БУДЕМ ВСТАВЛЯТЬ КОД СЮДА
            if (isset($data['start']) || isset($data['limit'])) {

Всталяем в указанное место такой блок кода:

// Additional sort order by product name
            if(isset($data['sort']) && $data['sort'] != 'pd.name') {
                $sql .= ", pd.name ASC";
            }

Вот и всё.

То же самое в виде Diff-файла

commit c12debb0a37ade03e773535ed361a4b96bd54c91
Author: Ruslan Brest <rb@labtodo.com>
Date:   Mon May 21 10:26:39 2012 +0300
    [+] Better catalog sort order: additional sorting by product name
    
    Добавлена вторичная сортировка по имени товара для более аккуратного вывода.
    Без этого товары при выборе других сортировок, например по количеству, могут сортироваться
    в случайном порядке. То же самое при изменении количества товаров на странице: один и тот же
    товар может оказаться и на первой, и на второй странице. Сейчас товары сортируются по выбранному
    признаку, а затем по имени.
    
    [!] CHANGED: По умолчанию -- сортировка по имени, а не по sort_order
diff --git a/upload/catalog/model/catalog/product.php b/upload/catalog/model/catalog/product.php
index 8b15e26..c3dec4f 100644
--- a/upload/catalog/model/catalog/product.php
+++ b/upload/catalog/model/catalog/product.php
@@ -162,7 +162,7 @@ class ModelCatalogProduct extends Model {
                    $sql .= " ORDER BY " . $data['sort'];
                }
            } else {
-               $sql .= " ORDER BY p.sort_order";
+               $sql .= " ORDER BY pd.name";
            }
 
            if (isset($data['order']) && ($data['order'] == 'DESC')) {
@@ -171,6 +171,11 @@ class ModelCatalogProduct extends Model {
                $sql .= " ASC";
            }
 
+           // Additional sort order by product name
+           if(isset($data['sort']) && $data['sort'] != 'pd.name') {
+               $sql .= ", pd.name ASC";
+           }
+
            if (isset($data['start']) || isset($data['limit'])) {
                if ($data['start'] < 0) {
                    $data['start'] = 0;

2012-05: webdev links

- Posted in Webdev by

Живые блоги с публикациями о CodeIgniter:

Руководство по оформлению HTML/CSS кода от Google. Руководство вообще-то странное: местами непоследовательное и противоречащее самому себе, а местами и вовсе вредное, на мой взгляд. Видимо, сказываются особенности высоконагруженных проектов. В общем, я бы не стал слепо придерживаться всех изложенных там рекомендаций, но просмотреть список и причины появления таких правил -- полезно.

Обсудить

Как реализовать страхование товаров в Опенкарт для некоторых покупателей?

- Posted in Uncategorized by
при оформлении заказа необходимо предложить покупателю возможность застраховать свою посылку(товар), так как есть и те кто не желает доплачивать эту сумму, то нужна функция с возможностью выбора. То есть при оформлении заказа, если покупатель согласен, то эта сумма плюсуется к общей сумме, если нет, то сумма остаётся без изменений.

Заведите купон на -10% (например) и пусть в корзине применяет - вместо скидки цена будет увеличена на указанную сумму. В купонах может использоваться как процент, так и фиксированная сумма.

Отрицательная величина скидки может применяться и, как можно убедиться, приводит к увеличению суммы при оформлении.

Про бег на длинные дистанции (физиология)

- Posted in Uncategorized by

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

Почему я всё же пишу и рефлексирую? Да потому, что бегаю. Регулярно, не теряя интереса и не перенапрягаясь. И без вреда для здоровья, а с ощутимой пользой. На мой взгляд, этого достаточно для того, чтобы делиться своим опытом и собственными интерпретациями происходящего. Простым языком и "на пальцах".

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

Мышцы и связки -- то, что наиболее заметно и "на поверхности". Отчётливо ощущается, как это тренируется, развивается и привыкает к нагрузкам при регулярных занятиях. Особенно быстро это сказывается на связках: перестают болеть колени, сухожилия на пятках и т.п. Естественно, если не гнаться за скоростью и не "рвать себе жилы". При появлении болезненных ощущений - переход на ходьбу. А по возможности желательно и сменить поверхность: с асфальта и бетона на грунтовку или даже песок. Поэтому бег трусцой на длинные дистанции (5-10-15 км) хорош для оздоровления. Особенно поначалу. Подготовить организм и голову (психологически: многим бег в течение часа-двух кажется чем-то сверхъестественным).

У меня год регулярного бега (2010) на сравнительно длинные дистанции (8-12 км) ощутимо снизил ЧСС в спокойном состоянии: вместо 70-80 ударов в минуту -- теперь 50-60 (от силы 70). Так что влияние на сердце, сосуды и капилляры -- вот оно, заметно на глаз.

Лёгкие при этом учатся эффективнее усваивать кислород - увеличивается объём, глубина вдоха, кол-во альвеол и мелких капилляров. Организм учится эффективнее быстро насыщать кровь кислородом. Сердце становится больше и мощнее - чтобы успевать больше крови прокачивать во время повышенных нагрузок. Сосуды становятся крепче и шире. Капилляры - тоньше и разветвлённее, чтобы эффективнее доставлять кислород всем органам и выводить разную ерунду наружу. Лишнее тепло в том числе. Побочные эффекты - спортсмены здоровее, краснее и теплее. Ну и чище изнутри - всё лишнее у них обычно наружу выводится и из сосудов, и из тканей. Пульс в спокойном состоянии снижается, и довольно заметно, - поэтому не стоит слушать обывателей и паникёров, которые с ходу заявляют о брадикардии, не задав предварительно вопрос: "А ты случайно не спортсмен(-ка)?".

Первым при нагрузках расходуется гликоген (легко расщепляемые сахара). Их основные запасы находятся в печени и мышцах. В печени процентное содержание наиболее высокое среди всех органов, но поскольку мышц гораздо больше - больше всего гликогена оказывается там. Восполняются углеводами. Белки тоже расщепляются и расходуются, но позже. Поэтому (а) люди с мясом оказываются гораздо более выносливыми и (б) мышцам достается в первую очередь. Всё это активно расщепляется и дает энергию первые 40-50 минут бега трусцой. Примерно, разумеется: в организме ничего так резко не переключается, но мы сейчас не о точности, а об общем представлении. И только потом неохотно вступают в дело жировые залежи. Поэтому те, кто просто бегает, и делает это меньше 1-1.5 часов - теряют по большей части свою воду, а не жир (до 2-3 кило за тренировку - легко). Которую затем восполняют. Вес поэтому еле колеблется, жир остается на месте - это многим знакомо, потому и бросают, что "толку никакого".

Хорошая новость - можно терять жир быстрее, если знать, как всё работает. В общем, из основ - есть 3-5 главных фактора, влияющих на выносливость и происходящие процессы, и такое же кол-во разновидностей тренировок. Аэробные нагрузки (когда кислорода достаточно поступает в мышцы), анаэробные (избыток углекислого газа), максимально потребление кислорода (способность усваивать его) и что-то там ещё. Ещё есть длина и частота шагов. Но это всё надо знать, понимать, помнить и применять, если интересует результат (бегать далеко и быстро). Если интересует гораздо меньше - достаточно понять основы, пару раз понять, что именно и в каких примерно пропорциях и дозах надо, и делать в своё удовольствие. Если заглянуть в ответ и начинать с конца - лучше варьировать разные типы нагрузок (которых всего 3-5), чтобы всем внутренностям досталось и все росли. Подробности лучше в книгах уточнить. Для быстрого сжигания жиров некоторые рекомендуют интервальный бег - смесь ускорений и бега на полную катушку 100-800 метров и таких же интервалов спокойной трусцы для восстановления. Мне сложно подтвердить или опровергнуть: на моих запасах жира это не заметишь. Мне интервальный бег наиболее заметен при работе над скоростью.

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

Легкоусвояемая книга - Пит Фитзингер, Скотт Дуглас. Бег по шоссе для серьёзных бегунов. Дистанции от 5 км до марафона.

См. также:

Как модифицируются способы оплаты для использования с QCPM.1513

- Posted in Uncategorized by
QCPM из-за необходимости изменения способов оплаты делает невозможным одновременное использование стандартной формы и QCPM: либо одна, либо другая. Поэтому обязательно храните резервные копии изменяемых стандартных файлов (точный путь указан в документации - README или INSTALL файлах).

Суть изменений проста: в TPL файлах способов оплаты содержится самая последняя кнопка подтверждения заказа, на нажатие которой обычно вешается обработчик (javascript-функция). Эта функция делает 2 вещи: окончательно подтверждает заказ (после этого он формируется и становится виден в админке) и делает что-то требуемое этому способу оплаты (переадресация на внешний сайт плат. системы или другие действия).

В стандартной форме заказа все проверки производятся до действия этой кнопки.

В QCPM - нет, поскольку всё на одной странице и эта кнопка может быть нажата сразу после загрузки, первой. Поэтому нам требуется вмешаться в стандартный процесс:

  1. Сначала произвести валидацию формы заказа;
  2. Затем сделать то, что предполагалось сделать модулем по нажатию на кнопку "Подтвердить заказ" (передать управление стандартной функции модуля оплаты)

Изменяемые файлы модулей оплаты находятся в catalog/view/theme/default/template/payment/

Теперь на примерах: alertpay.tpl

<form action="<?php echo $action; ?>" method="post">
   <input type="hidden" name="ap_merchant" value="<?php echo $ap_merchant; ?>" />
   <input type="hidden" name="ap_amount" value="<?php echo $ap_amount; ?>" />
   <input type="hidden" name="ap_currency" value="<?php echo $ap_currency; ?>" />
   <input type="hidden" name="ap_purchasetype" value="<?php echo $ap_purchasetype; ?>" />
   <input type="hidden" name="ap_itemname" value="<?php echo $ap_itemname; ?>" />
   <input type="hidden" name="ap_itemcode" value="<?php echo $ap_itemcode; ?>" />
   <input type="hidden" name="ap_returnurl" value="<?php echo $ap_returnurl; ?>" />
   <input type="hidden" name="ap_cancelurl" value="<?php echo $ap_cancelurl; ?>" />
   <div class="buttons">
     <div class="right">
       <input type="submit" value="<?php echo $button_confirm; ?>" class="button" />
     </div>
   </div>
 </form>

Что мы видим? Есть форма, по нажатию кнопки происходит отсылка данных этой формы.

Вмешиваемся в этот процесс:

1. по нажатию на кнопку делаем валидацию формы заказа (поменяли кнопку на свою):

<div class="buttons">
     <div class="right"><a onclick="validate_generate();" class="button"><span><?php echo $button_confirm; ?></span></a></div>
   </div>

2. Обеспечиваем работу того, что было предусмотрено модулем. QCPM вызывает функцию payment_confirm(), поэтому надо просто переместить предусмотренную модулем функцию по нажатию на кнопку, в функцию с таким названием:

<script type="text/javascript"><!--
 function payment_confirm()
 {
         $('#payment').submit();
 }
 //--></script>

Этот механизм переделки стандартный для всех модулей: переносим стандартную реакцию на кнопку подтверждения заказа в ф-цию payment_confirm(), а на её место ставим вызов валидации формы заказа.

Другой пример: cheque.tpl

Была кнопка

<div class="buttons">
   <div class="right">
     <input type="button" value="<?php echo $button_confirm; ?>" id="button-confirm" class="button" />
   </div>
 </div>
 <script type="text/javascript"><!--
 $('#button-confirm').bind('click', function() {
         $.ajax({ 
                 type: 'GET',
                 url: 'index.php?route=payment/cheque/confirm',
                 success: function() {
                         location = '<?php echo $continue; ?>';
                 } 
         });
 });
 //--></script>

По ID кнопки (id="button-confirm") вызывается AJAX-обработчик. Который что-то там делает.

Мы переносим этот обработчик в функцию payment_confirm(), а нажатие на кнопку заменяем на валидацию формы:

<script type="text/javascript"><!--
 $('#button-confirm').bind('click', function() {
         validate_generate();
 });
 function payment_confirm()
 {
         $.ajax({
                 type: 'GET',
                 url: 'index.php?route=payment/cheque/confirm',
                 success: function() {
                         location = '<?php echo $continue; ?>';
                 }
         });
 }
 //--></script>

Перенесли стандартный обработчик в payment_confirm и вызвали validate_generate. Те же два действия.

Все остальные модули точно так же устроены и модифицируются аналогично. Они все похожи и используют буквално 2-3 очень похожих схемы работы.

Проще всего увидеть изменения, сравнив стандартный файл и модифицированный. Программы, которые это умеют делать: TotalCommander, WinMerge.org (Windows), Meld (Linux), про MacOS не знаю.

В них всё наглядно видно.

При использовании QCPM желательно все файлы модуля держать только в теме Default и не дублировать в используемую тему - файлы будут подхватываться оттуда. Иначе сложней обслуживать и обновлять.

Quickcheckout: как сделать необязательным поле email?

- Posted in Uncategorized by

Как скрыть "Адрес доставки: Адрес (продолжение):"

открыть файл catalog/view/theme/default/template/checkout/quickcheckout.tpl

найти там:

<tr>
                        <td><?php echo $entry_address_2; ?></td>
                        <td><input type="text" name="address_2" value="<?php echo $address_2; ?>" class="large-field"/></td>
                </tr>

и первую строку (<tr>) изменить на:

<tr style="display:none;">

Как сделать ввод почтового ящика необязательным

в этом же файле найдите чуть выше строку

<td><span class="required">*</span> <?php echo $entry_email; ?></td>

из неё надо убрать звёздочку:

<td><?php echo $entry_email; ?></td>

Дальше надо найти и открыть файл catalog/controller/checkout/quickcheckout_address.php

найти там строки (#30-33)

$l = utf8_strlen($this->request->post['email']);
                                if (($l < 1) || ($l > 96) || !preg_match('/^[^\@]+@.*\.[a-z]{2,6}$/i', $this->request->post['email'])) {
                                        $json['error']['email'] = $this->language->get('error_email');
                                }

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

После этого надо исправить в движке Опенкарт часть, которая отсылает письма

Надо открыть файл catalog/model/checkout/order.php

найти там строку:

// Admin Alert Mail

(у меня это строка #438)

Над ней будет блок, который выглядит так:

$mail = new Mail();
                                $mail->protocol = $this->config->get('config_mail_protocol');
                                $mail->parameter = $this->config->get('config_mail_parameter');
                                $mail->hostname = $this->config->get('config_smtp_host');
                                $mail->username = $this->config->get('config_smtp_username');
                                $mail->password = $this->config->get('config_smtp_password');
                                $mail->port = $this->config->get('config_smtp_port');
                                $mail->timeout = $this->config->get('config_smtp_timeout');
                                $mail->setTo($order_info['email']);
                                $mail->setFrom($this->config->get('config_email'));
                                $mail->setSender($order_info['store_name']);
                                $mail->setSubject($subject);
                                $mail->setHtml($html);
                                $mail->setText(html_entity_decode($text, ENT_QUOTES, 'UTF-8'));
                                $mail->addAttachment(DIR_IMAGE . $this->config->get('config_logo'), md5(basename($this->config->get('config
_logo'))));
                                $mail->send();

Его надо заменить на такой:

if( !empty($order_info['email']) )
                        {
                                $mail = new Mail();
                                $mail->protocol = $this->config->get('config_mail_protocol');
                                $mail->parameter = $this->config->get('config_mail_parameter');
                                $mail->hostname = $this->config->get('config_smtp_host');
                                $mail->username = $this->config->get('config_smtp_username');
                                $mail->password = $this->config->get('config_smtp_password');
                                $mail->port = $this->config->get('config_smtp_port');
                                $mail->timeout = $this->config->get('config_smtp_timeout');
                                $mail->setTo($order_info['email']);
                                $mail->setFrom($this->config->get('config_email'));
                                $mail->setSender($order_info['store_name']);
                                $mail->setSubject($subject);
                                $mail->setHtml($html);
                                $mail->setText(html_entity_decode($text, ENT_QUOTES, 'UTF-8'));
                                $mail->addAttachment(DIR_IMAGE . $this->config->get('config_logo'), md5(basename($this->config->get('config
_logo'))));
                                $mail->send();
                        }

то есть сверху добавится условие, снизу - закрывающая скобка.

После этого всё должно работать без обязательного заполнения покупателем поля Email.

См. также:

Добавлена поддержка Shoppica&#45;и в Quickcheckout со способами оплаты (QCPM.1513)

- Posted in Uncategorized by

На прошлой неделе обновился QC - QCPM.1513. Для работы требуется небольшая модификация используемых способов оплаты. В архив включены модифицированные TPL всех модулей, входящих в Opencart и ocStore. Остальные исправляются и добавляются в архив по мере поступления запросов.

Вся информация о заменяемых файлах есть в README. Обязательно делайте резервные копии указанной папки с TPL файлами во избежание проблем с восстановлением работоспособности: невозможно учесть все вариации и версии, поэтому если ко мне попала старая версия модуля, а у вас используется обновленная, или вы меняли что-то в шаблоне для себя, возможны проблемы. Для восстановления мне потребуется оригинал из вашего бекапа. Если используете другие модули, в т.ч. платные - см инструкции в README, обращайтесь, я обновляю их по запросу.

Вчера добавлена поддержка темы Shoppica (QCPM.1513/Shoppica).

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

Wallpaper calendar 2012

- Posted in Uncategorized by

http://willempirquin.be/blog/en/2012-wallpaper/

А вот не совсем wallpaper, но компактный календарь, формат которого мне очень нравится, пользуюсь уже несколько лет: http://davidseah.com/compact-calendar/. Ну и сделать из него обои на десктоп не проблема при желании.

Opera buttons

- Posted in Uncategorized by

http://creator-lab.ucoz.ru/index/0-4 - коллекция полезных кнопок для броузера Opera.

http://puzzleclub.ru/files/button_maker/ - генератор кнопок

Opencart module extract (Linux shell script)

- Posted in Uncategorized by

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

Навеяно модулем http://opencartforum.ru/topic/8272-module-extract-извлечение-модулей/, но мне показалось, что линуксоидам будет проще и удобней одной строкой в шелле это делать (локально или на сервере), без установки модуля в Opencart (установить, разрешить, зайти, найти, скопировать...)

Ни я, ни тот модуль не занимаются анализом PHP-кода и действительно полным выковыриванием всех используемых модулем файлов. Так что результат, конечно, не гарантирует полное и точное извлечение. Но будет работать в подавляющем большинстве случаев.

#!/bin/bash
# Opencart module extract
#
# Ищет введенную подстроку с начала имени файла и копирует найденные файлы
# в отдельную папку с сохранением структуры каталогов. Mодули для Opencart
# обычно используют одно и то же имя для контроллеров, моделей, view и языковых
# файлов, поэтому этот скрипт может сильно облегчить задачу по извлечению
# всех файлов модуля из рабочей копии магазина.
#
# @author: Ruslan Brest, http://rb.labtodo.com/
# @date: 2012-03-31
#
# See also: http://opencartforum.ru/topic/8272-module-extract-извлечение-модулей/
mkdir ".$1"
find -iname "$1*" | while read ff; do cp --parents -t "./$1/" "$ff"; done

Пользователям Windows полноценные линукс-радости командной строки и других полезных интструментов тоже доступны, см. например проекты http://www.cygwin.com/ или http://win-bash.sourceforge.net/

Музыка, удобная для бега

- Posted in Uncategorized by

Tayo These Are The Breaks (via di.fm/breaks, 2012-03-30/31)

http://aaronjae.com/?go=disc

http://www.endomondo.com/workouts/43363121

Что-то вроде Eminem - да. AC/DC - я вряд ли осилю. Хип-хоп, рэп, breakbeats идут на ура. Из наиболее удобных для бега - Prodigy, Propellerheads, BT, Pink, TicTacToe, Trombone Shorty, Fatboyslim. И немного Kimbra и ZAZ. Morcheeba по дороге домой :)

Вообще самое удобное для темпа между 5 и 6 минутами на километр - Prodigy (Colours, Piranha), Alonzo - Determine, La Fouine - Rien A Perdre. Последняя самая удобная: на правильном плеере я её просто зацикливаю, если хочу бежать много, в одном темпе и без остановок.

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

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

Жирным выделены более удобные треки.

Жирным и красным - мегаудобное, очень точно совпадающее с темпом при беге.

Понемногу корректируется.

.

|-- 10-Fatboy.Slim.(feat..Bootsy.Collins)._.Illuminati.mp3

|-- Aphrodite_See_Thru_It_Down_Boy_Aphrodite_Remix_feat_Holly_Valance_.mp3

|-- Aphrodite_See_Thru_It_Frou_Frou_Radio_Edit_.mp3

|-- Aphrodite_See_Thru_It_Lava_Flows.mp3

|-- A-Trak - Running Man Nike+ Original Run (Continuous Mix).mp3

|-- Billy Moon - Little Cabin Song.mp3

|-- breakbeat

| |-- [b] - The Scammers - Phatty Boom Boom!.mp3

| |-- More Than The Prodigy 1

| | |-- 01. GOON - MENTAL REFLEX.mp3

| | |-- 02. JUNKIE XL - BILLY CLUB.mp3

| | |-- 03. LUNATIC CALM .mp3

| | |-- 06. SUPERCHARGER .mp3

| | |-- 07. HEADRILLAZ - SPACEFUCK.mp3

| | `-- 13. HARDKNOX - PSYCHOPATH.mp3

| `-- The Scammers - Sally's Walk (The Scammers Meet The Great Wuga Wuga).mp3

|-- BT

| |-- 05-bt__the_roots_-_tao_of_the_machine.mp3

| |-- BT_10_Smartbomb.mp3

| |-- BT_2_Satellite.mp3

| |-- BT_5_Kimosabe.mp3

| |-- BT - Movement in still life.mp3

| `-- BT_The Revolution.mp3

|-- Da Octopusss - Motors (Musique Du Film).mp3

|-- george_thorogood_-_bad_to_the_bone.mp3

|-- Kimbra - Vows (2011)

| |-- 01 - Settle Down.mp3

| `-- 05 - Good Intent.mp3

|-- Massive_Attack_Teardrop.mp3

|-- Morcheeba - The Sea.mp3

|-- Morcheeba - World Looking In.mp3

|-- OST - Banlieue 13 Ultimatum

| |-- 01-alonzo-determine.mp3

| |-- 02-la_fouine-rien_a_perdre.mp3

| |-- 04-axiom-la_tour_des_miracles.mp3

| |-- 06-la_38eme_donne-on_fera_avec.mp3

| |-- 07-da_octopuss-le_mur.mp3

| |-- 08-green-green_money.mp3

| |-- 12-brasco-et_alors.mp3

| |-- 13-trak_invaders-the_music_box.mp3

| `-- 14-jamal-cri_de_guerre.mp3

|-- ost-housemd

| |-- Coconut.mp3

| |-- Elvis Costello - Beautiful.mp3

| |-- Feel Good Inc..mp3

| |-- Fluke - Atom bomb.mp3

| `-- Mungal_Nitin Sawhney - Awake.mp3

|-- Pink

| |-- Pink_DontLetMeGetMe.mp3

| `-- Pink - Get Party Started.mp3

|-- Pink Panther's Penthouse Party

| |-- 02-Ludovic Navarre Aka St. Germain _ The Pink Panther Theme Colored By.mp3

| |-- 04-Titan _ Corazon.mp3

| |-- 07-Fatboy Slim _ Weapon Of Choice.mp3

| |-- 13-Kinky _ San Antonio.mp3

| |-- 15-Nicola Conte _ Bossa Per Due.mp3

| |-- 16-Les Hommes _ Intraspettro.mp3

| `-- 17-Mocean Worker _ Tres Tres Chic.mp3

|-- Prodigy_2009_Invaders-Must-Die

| |-- The Prodigy - Colours.mp3

| |-- The Prodigy - Invaders Must Die.mp3

| |-- The Prodigy - Omen.mp3

| |-- The Prodigy - Piranha.mp3

| |-- The Prodigy - Run With The Wolves.mp3

| |-- The Prodigy - Stand Up.mp3

| |-- The Prodigy - Take Me To The Hospital.mp3

| |-- The Prodigy - Warriors Dance.mp3 (если вырезать первую минуту)

| `-- The Prodigy - World's On Fire.mp3

|-- Propellerheads

| `-- 01-Decksandrumsandrockandroll 1998

| |-- 01-Take California.mp3

| |-- 02-Velvet Pants.mp3

| |-- 03-Better.mp3

| |-- 04-360 degrees (Oh Yeah) [featuring de la soul].mp3

| |-- 05-History Repeating [featuring miss shirley bassey].mp3

| |-- 06-Winning Style.mp3

| |-- 07-Bang On!.mp3

| |-- 08-A Number of Microphones.mp3

| |-- 09-On Her Majesty's Secret Service.mp3

| |-- 10-Bigger.mp3

| |-- 11-Cominagetcha.mp3

| |-- 12-Spybreak!.mp3

| `-- 13-You Want It Back [featuring jungle brothers].mp3

|-- Pubblicita - Bayles - Morcheeba - Let Me See.mp3

|-- Saskia_Laroo---Vibes.mp3

|-- Tic.Tac.Toe

| |-- Tic Tac Toe - Isch liebe disch.mp3

| |-- Tic-Tac-Toe---Verpiss-dich.mp3

| `-- Tic Tac Toe - Warum.mp3

|-- tree.txt

|-- Trombone.Shorty.-.Backatown.(2010)

| |-- 01_-_Hurricane_Season.mp3

| |-- 02_-_On_Your_Way_Down.mp3

| |-- 03_-_Quiet_As_Kept.mp3

| |-- 04_-_Something_Beautiful.mp3

| |-- 05_-_Backatown.mp3

| |-- 06_-_Right_To_Complain.mp3

| |-- 07_-_Neph.mp3

| |-- 08_-_Suburbia.mp3

| |-- 09_-_In_The_6th.mp3

| |-- 10_-One_Night_Only(The_March).mp3

| |-- 11_-_Where_Y'_At.mp3

| |-- 12_-_Fallin'.mp3

| |-- 13_-_The_Cure.mp3

| `-- 14_-_928_Horn_Jam.mp3

|-- Two Fingers - Fools Rhythm.mp3

|-- U2

| |-- 01-U2 _ Elevation (Tomb Raider Mix).mp3

| |-- Mission impossible theme (U2).mp3

| `-- U2 - Diskotek.mp3

`-- Zaz - Je Veux

|-- 01. Les Passants.mp3

|-- 02. <strong>Je Veux.mp3</strong>

|-- 03. Le Long De La Route.mp3

|-- 06. <strong>Prends Garde A Ta Langue.mp3</strong>

|-- 07. Ni Oui Ni Non.mp3

`-- 11. Eblouie Par La Nuit.mp3</code>

См. также:

Opencart: чем отличаются купоны и подарочные сертификаты?

- Posted in Uncategorized by
В чем отличия подарочного сертификата от скидочного купона в ОС? есть ли принципиальные отличия?

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

Купон - это просто код и способ дать скидку любому, кто его знает. Можно использовать как способ отслеживать разные каналы продвижения: Вконтакте один код раздаёте, в фейсбуке другой, в блоге третий. Удобно также для более простой организации скидок без регистрации и включения покупателей в определенную группу. К тому же цены для этой группы придётся всем товарам прописывать (если не использовать какие-то модули в дополнение к стандартным возможностям Опенкарт).

Индикация цен опций товара заменена на абсолютную величину (вместо разницы +X руб, &#45;Y руб)

- Posted in Uncategorized by

Рецепт ниже.

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

commit 92114e01f55e00e4f1882302e5e7f6b7b1d638e5
Author: Ruslan Brest <rb@labtodo.com>
Date:   Fri Sep 2 17:55:44 2011 +0300
    re #725: Индикация цен опций товара заменена на абсолютную величину (вместо дельты: +X руб, -Y руб)
diff --git a/public_html/catalog/controller/product/product.php b/public_html/catalog/controller/product/product.php
index 5e4bba7..deb52e0 100644
--- a/public_html/catalog/controller/product/product.php
+++ b/public_html/catalog/controller/product/product.php
@@ -252,12 +252,28 @@ class ControllerProductProduct extends Controller {
                    
                    foreach ($option['option_value'] as $option_value) {
                        if (!$option_value['subtract'] || ($option_value['quantity'] > 0)) {
+                           if( $product_info['price'] )
+                           {
+                               $option_value['price_full'] = $product_info['price'];
+                               if( $option_value['price_prefix'] == "+" )
+                                   $option_value['price_full'] = $product_info['price'] + $option_value['price'];
+                               if( $option_value['price_prefix'] == "-" )
+                                   $option_value['price_full'] = $product_info['price'] - $option_value['price'];
+
+                               $option_value['price_full'] = $this->currency->format(
+                                   $this->tax->calculate(
+                                       $option_value['price_full'],
+                                       $product_info['tax_class_id'],
+                                       $this->config->get('config_tax')
+                                       ));
+                           }
                            $option_value_data[] = array(
                                'product_option_value_id' => $option_value['product_option_value_id'],
                                'option_value_id'         => $option_value['option_value_id'],
                                'name'                    => $option_value['name'],
                                'price'                   => (float)$option_value['price'] ? $this->currency->format($this->tax->calculate($option_value['price'], $product_info['tax_class_id'], $this->config->get('config_tax'))) : false,
-                               'price_prefix'            => $option_value['price_prefix']
+                               'price_prefix'            => $option_value['price_prefix'],
+                               'price_full'              => $option_value['price_full'],
                            );
                        }
                    }
@@ -568,4 +584,4 @@ class ControllerProductProduct extends Controller {
        $this->response->setOutput(Json::encode($json));      
    }
 }
-?>
\ No newline at end of file
+?>
diff --git a/public_html/catalog/view/theme/default/template/product/product.tpl b/public_html/catalog/view/theme/default/template/product/product.tpl
index e7cbf6a..b28db8e 100644
--- a/public_html/catalog/view/theme/default/template/product/product.tpl
+++ b/public_html/catalog/view/theme/default/template/product/product.tpl
@@ -69,7 +69,8 @@
             <?php foreach ($option['option_value'] as $option_value) { ?>
             <option value="<?php echo $option_value['product_option_value_id']; ?>"><?php echo $option_value['name']; ?>
             <?php if ($option_value['price']) { ?>
-            (<?php echo $option_value['price_prefix']; ?><?php echo $option_value['price']; ?>)
+            <!-- (<?php echo $option_value['price_prefix']; ?><?php echo $option_value['price']; ?>) -->
+            (<?php echo $option_value['price_full']; ?>)
             <?php } ?>
             </option>
             <?php } ?>
@@ -87,7 +88,8 @@
           <input type="radio" name="option[<?php echo $option['product_option_id']; ?>]" value="<?php echo $option_value['product_option_value_id']; ?>" id="option-value-<?php echo $option_value['product_option_value_id']; ?>" />
           <label for="option-value-<?php echo $option_value['product_option_value_id']; ?>"><?php echo $option_value['name']; ?>
             <?php if ($option_value['price']) { ?>
-            (<?php echo $option_value['price_prefix']; ?><?php echo $option_value['price']; ?>)
+            <!-- (<?php echo $option_value['price_prefix']; ?><?php echo $option_value['price']; ?>) -->
+            (<?php echo $option_value['price_full']; ?>)
             <?php } ?>
           </label>
           
@@ -105,7 +107,8 @@
           <input type="checkbox" name="option[<?php echo $option['product_option_id']; ?>][]" value="<?php echo $option_value['product_option_value_id']; ?>" id="option-value-<?php echo $option_value['product_option_value_id']; ?>" />
           <label for="option-value-<?php echo $option_value['product_option_value_id']; ?>"> <?php echo $option_value['name']; ?>
             <?php if ($option_value['price']) { ?>
-            (<?php echo $option_value['price_prefix']; ?><?php echo $option_value['price']; ?>)
+            <!-- (<?php echo $option_value['price_prefix']; ?><?php echo $option_value['price']; ?>) -->
+            (<?php echo $option_value['price_full']; ?>)
             <?php } ?>
           </label>
           
@@ -433,4 +436,4 @@ $('.datetime').datetimepicker({
 });
 $('.time').timepicker({timeFormat: 'h:m'});
 //--></script> 
-<?php echo $footer; ?>
\ No newline at end of file
+<?php echo $footer; ?>

Danny MacAskill

- Posted in Uncategorized by

Danny MacAskill

git&#45;flow: A collection of Git extensions to provide high&#45;level repository operations for Vincent Driessen&apos;s branching model

- Posted in Uncategorized by

git-flow

A collection of Git extensions to provide high-level repository operations for Vincent Driessen's branching model.

https://github.com/nvie/gitflow#readme

http://nvie.com/git-model

OpenCart 1.5.1.x exploited (RFI)

- Posted in Uncategorized by

OpenCart 1.5.1.x exploited (RFI) http://code.google.com/p/opencart/issues/detail?id=596

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

Не помешает также в папку download поместить файл .htaccess со строчкой SetHandler send-as-is.

Running workout (&#45;1C, ветер, 12km @ 1h16m)

- Posted in Uncategorized by

Mar 1, 2012 3:17 PM

12.26 km / 1h:16m:20s

6:14 min/km

-1C, холодный ветер. На косе полегче. Термофутболка с длинным рукавом + мембрана Marmot. Из-за резкого ветра куртка всю трассу бегала на мне, только немного плечи проветривал после 8-9 км. Где-то до -3..-4 - хорошая комбинация, только при ветре надо перчатки добавлять. Сегодня хватало длинных рукавов куртки при беге, прятал в них, когда замерзал.

Opencart 1.5.1.3 (backend): Продажи &#45; Заказы &#45; Товары: показ колонки SKU в списке товаров

- Posted in Uncategorized by

admin: Продажи - Заказы - Товары: добавлена колонка SKU

Выводится ТЕКУЩЕЕ значение SKU из базы. То есть если продали товар, затем SKU изменился - старый не сохраняется в отличие от названия товара, модели, цены и прочего. То есть при просмотре старых заказов могут быть неточности: вы увидите состояние SKU не на тот момент (когда был сделан заказ), а на нынешний.

commit 3b5e001193b5e9c3c64ee9886455c6a1761aaba8
Author: Ruslan Brest <rb@labtodo.com>
Date:   Fri Nov 11 13:21:33 2011 +0200
    [+] admin: Продажи - Заказы - Товары: добавлена колонка SKU
    
    Выводится ТЕКУЩЕЕ значение SKU из базы. То есть если продали товар, затем SKU изменился - старый не сохраняется
    в отличие от названия товара, модели, цены и прочего
diff --git a/upload/admin/controller/sale/order.php b/upload/admin/controller/sale/order.php
index 2a2178a..dbc181f 100644
--- a/upload/admin/controller/sale/order.php
+++ b/upload/admin/controller/sale/order.php
@@ -1111,6 +1111,7 @@ class ControllerSaleOrder extends Controller {
            $this->data['text_credit_remove'] = $this->language->get('text_credit_remove');
            
            $this->data['column_product'] = $this->language->get('column_product');
+           $this->data['column_sku']     = $this->language->get('column_sku');
            $this->data['column_model'] = $this->language->get('column_model');
            $this->data['column_quantity'] = $this->language->get('column_quantity');
            $this->data['column_price'] = $this->language->get('column_price');
@@ -1320,6 +1321,7 @@ class ControllerSaleOrder extends Controller {
                    'order_product_id' => $product['order_product_id'],
                    'product_id'       => $product['product_id'],
                    'name'             => $product['name'],
+                   'sku'              => $product['sku'],
                    'model'            => $product['model'],
                    'option'           => $option_data,
                    'quantity'         => $product['quantity'],
diff --git a/upload/admin/language/english/sale/order.php b/upload/admin/language/english/sale/order.php
index fe25149..fe4b35b 100644
--- a/upload/admin/language/english/sale/order.php
+++ b/upload/admin/language/english/sale/order.php
@@ -65,6 +65,7 @@ $_['column_date_added']       = 'Date Added';
 $_['column_date_modified']    = 'Date Modified';
 $_['column_total']            = 'Total';
 $_['column_product']          = 'Product';
+$_['column_sku']              = 'SKU';
 $_['column_model']            = 'Model';
 $_['column_quantity']         = 'Quantity';
 $_['column_price']            = 'Unit Price';
diff --git a/upload/admin/language/russian/sale/order.php b/upload/admin/language/russian/sale/order.php
index a8f4724..3efb7d1 100644
--- a/upload/admin/language/russian/sale/order.php
+++ b/upload/admin/language/russian/sale/order.php
@@ -65,6 +65,7 @@ $_['column_date_added']     = 'Дата добавления';
 $_['column_date_modified']    = 'Дата изменения';
 $_['column_total']          = 'Итого';
 $_['column_product']        = 'Товар';
+$_['column_sku']            = 'Артикул';
 $_['column_model']          = 'Модель';
 $_['column_quantity']       = 'Количество';
 $_['column_price']          = 'Цена за единицу';
diff --git a/upload/admin/model/sale/order.php b/upload/admin/model/sale/order.php
index 22483c2..8a4a449 100644
--- a/upload/admin/model/sale/order.php
+++ b/upload/admin/model/sale/order.php
@@ -365,7 +365,7 @@ class ModelSaleOrder extends Model {
    }
    
    public function getOrderProducts($order_id) {
-       $query = $this->db->query("SELECT * FROM " . DB_PREFIX . "order_product WHERE order_id = '" . (int)$order_id . "'");
+       $query = $this->db->query("SELECT op.*,p.sku FROM " . DB_PREFIX . "order_product op LEFT JOIN ".DB_PREFIX."product p ON (op.product_id = p.product_id)  WHERE order_id = '" . (int)$order_id . "'");
        
        return $query->rows;
    }
diff --git a/upload/admin/view/template/sale/order_info.tpl b/upload/admin/view/template/sale/order_info.tpl
index e5d04fe..3c3f13b 100644
--- a/upload/admin/view/template/sale/order_info.tpl
+++ b/upload/admin/view/template/sale/order_info.tpl
@@ -252,6 +252,7 @@
         <table id="product" class="list">
           <thead>
             <tr>
+              <td class="left"><?php echo $column_sku; ?></td>
               <td class="left"><?php echo $column_product; ?></td>
               <td class="left"><?php echo $column_model; ?></td>
               <td class="right"><?php echo $column_quantity; ?></td>
@@ -262,6 +263,7 @@
           <?php foreach ($products as $product) { ?>
           <tbody id="product-row<?php echo $product['order_product_id']; ?>">
             <tr>
+              <td class="left"><?php echo $product['sku']; ?></td>
               <td class="left"><?php if ($product['product_id']) { ?>
                 <a href="<?php echo $product['href']; ?>"><?php echo $product['name']; ?></a>
                 <?php } else { ?>
@@ -285,7 +287,7 @@
           <?php foreach ($totals as $totals) { ?>
           <tbody id="totals">
             <tr>
-              <td colspan="4" class="right"><?php echo $totals['title']; ?>:</td>
+              <td colspan="5" class="right"><?php echo $totals['title']; ?>:</td>
               <td class="right"><?php echo $totals['text']; ?></td>
             </tr>
           </tbody>

(oc1513)(admin) Ссылка на просмотр товара в магазине при просмотре заказа в админке

- Posted in Uncategorized by

При просмотре заказа в админке ссылка на заказываемый товар ведёт на редактирование товара. Мне показалось гораздо более удобным иметь здесь прямую ссылку на страницу товара в магазине.

В некоторых магазинах менеджер-продажник всё равно не имеет прав на редактирование товаров.

Есть здесь один момент: у нас ВСЕ товары имеют SEO Keyword, поэтому не делались никакие проверки. Гораздо более универсальным и более простым решением будет использовать ссылку HTTP_CATALOG . 'index.php?route=product/product&product_id=' . $product['product_id'].

В таком случае указанный код надо подставить в контроллере на место 'href_catalog', а запрос к базе в модели вообще не модифицировать.

commit da02db89ca4be51a282cb49cd58a3160cc4bf747
Author: Ruslan Brest <rb@labtodo.com>
Date:   Tue Feb 28 02:37:40 2012 +0200
    [+] backend: view order - link to frontend (product page)
diff --git a/upload/admin/controller/sale/order.php b/upload/admin/controller/sale/order.php
index 871c36f..029ba41 100644
--- a/upload/admin/controller/sale/order.php
+++ b/upload/admin/controller/sale/order.php
@@ -1327,7 +1327,8 @@ class ControllerSaleOrder extends Controller {
                    'quantity'         => $product['quantity'],
                    'price'            => $this->currency->format($product['price'], $order_info['currency_code'], $order_info['currency_value']),
                    'total'            => $this->currency->format($product['total'], $order_info['currency_code'], $order_info['currency_value']),
-                   'href'             => $this->url->link('catalog/product/update', 'token=' . $this->session->data['token'] . '&product_id=' . $product['product_id'], 'SSL')
+                   'href'             => $this->url->link('catalog/product/update', 'token=' . $this->session->data['token'] . '&product_id=' . $product['product_id'], 'SSL'),
+                   'href_catalog'     => HTTP_CATALOG . $product['keyword']
                );
            }
 
diff --git a/upload/admin/model/sale/order.php b/upload/admin/model/sale/order.php
index b580167..c702009 100644
--- a/upload/admin/model/sale/order.php
+++ b/upload/admin/model/sale/order.php
@@ -365,7 +365,8 @@ class ModelSaleOrder extends Model {
    }
 
    public function getOrderProducts($order_id) {
-       $query = $this->db->query("SELECT op.*,p.sku FROM " . DB_PREFIX . "order_product op LEFT JOIN ".DB_PREFIX."product p ON (op.product_id = p.product_id)  WHERE order_id = '" . (int)$order_id . "'");
+       //$query = $this->db->query("SELECT op.*,p.sku FROM " . DB_PREFIX . "order_product op LEFT JOIN ".DB_PREFIX."product p ON (op.product_id = p.product_id)  WHERE order_id = '" . (int)$order_id . "'");
+       $query = $this->db->query("SELECT op.*,p.sku,ua.* FROM " . DB_PREFIX . "order_product op LEFT JOIN ".DB_PREFIX."product p ON (op.product_id = p.product_id) LEFT JOIN ".DB_PREFIX."url_alias ua ON CONCAT('product_id=', op.product_id) = ua.query WHERE order_id = '" . (int)$order_id . "'");
 
        return $query->rows;
    }
diff --git a/upload/admin/view/image/url-icon.gif b/upload/admin/view/image/url-icon.gif
new file mode 100644
index 0000000..8165dc9
Binary files /dev/null and b/upload/admin/view/image/url-icon.gif differ
diff --git a/upload/admin/view/template/sale/order_info.tpl b/upload/admin/view/template/sale/order_info.tpl
index 673b599..55be799 100644
--- a/upload/admin/view/template/sale/order_info.tpl
+++ b/upload/admin/view/template/sale/order_info.tpl
@@ -269,6 +269,7 @@
               <td class="left"><?php echo $product['sku']; ?></td>
               <td class="left"><?php if ($product['product_id']) { ?>
                 <a href="<?php echo $product['href']; ?>"><?php echo $product['name']; ?></a>
+                <a href="<?php echo $product['href_catalog']; ?>" target="_blank"><img src="view/image/url-icon.gif" width="13" height="13" /></a>
                 <?php } else { ?>
                 <?php echo $product['name']; ?>
                 <?php } ?>

Opencart 1.5.1.3 (backend): улучшение поиска в фильтрах &#45; не только с начала строки или по полному совпадению (имя, email, IP, опции, атрибуты)

- Posted in Uncategorized by

Админка: улучшение поиска в фильтрах - не только с начала строки или по полному совпадению (имя, email, IP, опции, атрибуты).

Для тех, кто хочет искать не только по имени, но и фамилии покупателей, а также по части IP-адреса и почтовому домену, и т.д. и и т.п.

commit 860f41a57c7506331c4fef9d50d3ef8cb65ffa22
Author: Ruslan Brest <rb@labtodo.com>
Date:   Tue Feb 21 19:12:04 2012 +0200
    [!] backend: улучшение поиска в фильтрах - не только с начала строки или по полному совпадению (имя, email, IP, опции, атрибуты)
diff --git a/upload/admin/model/catalog/option.php b/upload/admin/model/catalog/option.php
index a5b65f8..29f25dc 100644
--- a/upload/admin/model/catalog/option.php
+++ b/upload/admin/model/catalog/option.php
@@ -72,7 +72,7 @@ class ModelCatalogOption extends Model {
        $sql = "SELECT * FROM `" . DB_PREFIX . "option` o LEFT JOIN " . DB_PREFIX . "option_description od ON (o.option_id = od.option_id) WHERE od.language_id = '" . (int)$this->config->get('config_language_id') . "'";
 
        if (isset($data['filter_name']) && !is_null($data['filter_name'])) {
-           $sql .= " AND LCASE(od.name) LIKE '" . $this->db->escape(utf8_strtolower($data['filter_name'])) . "%'";
+           $sql .= " AND LCASE(od.name) LIKE '%" . $this->db->escape(utf8_strtolower($data['filter_name'])) . "%'";
        }
 
        $sort_data = array(
diff --git a/upload/admin/model/catalog/product.php b/upload/admin/model/catalog/product.php
index f11247a..b83b224 100644
--- a/upload/admin/model/catalog/product.php
+++ b/upload/admin/model/catalog/product.php
@@ -346,11 +346,11 @@ class ModelCatalogProduct extends Model {
            $sql .= " WHERE pd.language_id = '" . (int)$this->config->get('config_language_id') . "'";
 
            if (!empty($data['filter_name'])) {
-               $sql .= " AND LCASE(pd.name) LIKE '" . $this->db->escape(utf8_strtolower($data['filter_name'])) . "%'";
+               $sql .= " AND LCASE(pd.name) LIKE '%" . $this->db->escape(utf8_strtolower($data['filter_name'])) . "%'";
            }
 
            if (!empty($data['filter_model'])) {
-               $sql .= " AND LCASE(p.model) LIKE '" . $this->db->escape(utf8_strtolower($data['filter_model'])) . "%'";
+               $sql .= " AND LCASE(p.model) LIKE '%" . $this->db->escape(utf8_strtolower($data['filter_model'])) . "%'";
            }
            if (!empty($data['filter_sku'])) {
                $sql .= " AND LCASE(p.sku) LIKE '%" . $this->db->escape(utf8_strtolower($data['filter_sku'])) . "%'";
diff --git a/upload/admin/model/sale/affiliate.php b/upload/admin/model/sale/affiliate.php
index 2d3809d..da3911a 100644
--- a/upload/admin/model/sale/affiliate.php
+++ b/upload/admin/model/sale/affiliate.php
@@ -35,11 +35,11 @@ class ModelSaleAffiliate extends Model {
        $implode = array();
 
        if (!empty($data['filter_name'])) {
-           $implode[] = "LCASE(CONCAT(a.firstname, ' ', a.lastname)) LIKE '" . $this->db->escape(utf8_strtolower($data['filter_name'])) . "%'";
+           $implode[] = "LCASE(CONCAT(a.firstname, ' ', a.lastname)) LIKE '%" . $this->db->escape(utf8_strtolower($data['filter_name'])) . "%'";
        }
 
        if (!empty($data['filter_email'])) {
-           $implode[] = "a.email = '" . $this->db->escape($data['filter_email']) . "'";
+           $implode[] = "a.email = '%" . $this->db->escape($data['filter_email']) . "'";
        }
 
        if (!empty($data['filter_code'])) {
diff --git a/upload/admin/model/sale/customer.php b/upload/admin/model/sale/customer.php
index 495d086..1c742b5 100644
--- a/upload/admin/model/sale/customer.php
+++ b/upload/admin/model/sale/customer.php
@@ -77,11 +77,11 @@ class ModelSaleCustomer extends Model {
        $implode = array();
 
        if (!empty($data['filter_name'])) {
-           $implode[] = "LCASE(CONCAT(c.firstname, ' ', c.lastname)) LIKE '" . $this->db->escape(utf8_strtolower($data['filter_name'])) . "%'";
+           $implode[] = "LCASE(CONCAT(c.firstname, ' ', c.lastname)) LIKE '%" . $this->db->escape(mb_strtolower($data['filter_name'], 'UTF-8')) . "%'";
        }
 
        if (!empty($data['filter_email'])) {
-           $implode[] = "LCASE(c.email) LIKE '" . $this->db->escape(utf8_strtolower($data['filter_email'])) . "%'";
+           $implode[] = "LCASE(c.email) LIKE '%" . $this->db->escape(utf8_strtolower($data['filter_email'])) . "%'";
        }
 
        if (!empty($data['filter_customer_group_id'])) {
@@ -97,7 +97,7 @@ class ModelSaleCustomer extends Model {
        }
 
        if (!empty($data['filter_ip'])) {
-           $implode[] = "c.customer_id IN (SELECT customer_id FROM " . DB_PREFIX . "customer_ip WHERE ip = '" . $this->db->escape($data['filter_ip']) . "')";
+           $implode[] = "c.customer_id IN (SELECT customer_id FROM " . DB_PREFIX . "customer_ip WHERE ip LIKE '%" . $this->db->escape($data['filter_ip']) . "%')";
        }
 
        if (!empty($data['filter_date_added'])) {

oc1513-backend-better-filter-search.diff_.zip 14

Opencart 1.5.x: удобный вид email&#45;уведомлений админу о заказе

- Posted in Uncategorized by

Формируемое в Опенкарт v1.5.x письмо-уведомление для администрации магазина о новом заказе выглядит криво-косо и неудобно для обработки поступивших заказов: не хватает информации о покупателе, а та информация, что есть, сложна для восприятия.

Можно сделать удобнее.

Формирование и отсылка писем происходит в файле catalog/model/checkout/order.php

Тексты частей писем находятся в папке catalog/language/russian/mail -- из содержащихся там фраз формируются письма.

commit 132d70db7615ff44fa658459db298af0e5ff62e3
Author: Ruslan Brest <rb@labtodo.com>
Date:   Fri Feb 24 11:54:40 2012 +0200
    [+] better admin alert email on new order
diff --git a/upload/catalog/language/russian/mail/order.php b/upload/catalog/language/russian/mail/order.php
index f42838d..8bb86a5 100644
--- a/upload/catalog/language/russian/mail/order.php
+++ b/upload/catalog/language/russian/mail/order.php
@@ -2,7 +2,7 @@
 // Text
 $_['text_new_subject']          = '%s - заказ %s';
 $_['text_new_greeting']         = 'Благодарим за интерес к товарам %s. Ваш заказ получен и поступит в обработку после подтверждения оплаты.';
-$_['text_new_received']         = 'Вы получили заказ.';
+$_['text_new_received']         = 'Вы получили заказ';
 $_['text_new_link']            = 'Для просмотра Вашего заказа перейдите по ссылке:';
 $_['text_new_order_detail']     = 'Детализация заказа';
 $_['text_new_instruction']      = 'Инструкции';
diff --git a/upload/catalog/model/checkout/order.php b/upload/catalog/model/checkout/order.php
index 6a187c2..360ea4d 100644
--- a/upload/catalog/model/checkout/order.php
+++ b/upload/catalog/model/checkout/order.php
@@ -440,14 +440,24 @@ class ModelCheckoutOrder extends Model {
                $subject = sprintf($language->get('text_new_subject'), html_entity_decode($this->config->get('config_name'), ENT_QUOTES, 'UTF-8'), $order_id);
 
                // Text
-               $text  = $language->get('text_new_received') . "\n\n";
-               $text .= $language->get('text_new_order_id') . ' ' . $order_id . "\n";
-               $text .= $language->get('text_new_date_added') . ' ' . date($language->get('date_format_short'), strtotime($order_info['date_added'])) . "\n";
-               $text .= $language->get('text_new_order_status') . ' ' . $order_status . "\n\n";
+               $text  = $language->get('text_new_received') . ' #'.$order_id . ' ('.date($language->get('date_format_short'), strtotime($order_info['date_added'])).')' . "\n";
+               // $text .= $language->get('text_new_order_id') . ' ' . $order_id . "\n";
+               // $text .= $language->get('text_new_date_added') . ' ' . date($language->get('date_format_short'), strtotime($order_info['date_added'])) . "\n";
+               $text .= $language->get('text_new_order_status') . ' ' . strip_tags($order_status) . "\n\n";
+
+               if( !empty($order_info['payment_method']) ) $text .= 'payment method: ' . $order_info['payment_method'] . "\n";
+               // $text .= 'shipping method: ' . $order_info['shipping_method'] . "\n";
+               if( !empty($order_info['email']) )     $text .= 'email: ' . $order_info['email'] . "\n";
+               if( !empty($order_info['telephone']) ) $text .= 'telephone: ' . $order_info['telephone'] . ", ";
+               $text .= 'IP: http://geoiptool.com/en/?IP=' . $order_info['ip'] . "\n\n";
+
                $text .= $language->get('text_new_products') . "\n";
 
                foreach ($order_product_query->rows as $result) {
-                   $text .= $result['quantity'] . 'x ' . $result['name'] . ' (' . $result['model'] . ') ' . html_entity_decode($this->currency->format($result['total'], $order_info['currency_code'], $order_info['currency_value']), ENT_NOQUOTES, 'UTF-8') . "\n";
+                   $text .= '  ';
+                   $text .= html_entity_decode($this->currency->format($result['total'], $order_info['currency_code'], $order_info['currency_value']), ENT_NOQUOTES, 'UTF-8');
+                   $text .= ' = ';
+                   $text .= $result['quantity'] . ' x ' . $result['name'] . ' (' . $result['model'] . ")\n";
 
                    $order_option_query = $this->db->query("SELECT * FROM " . DB_PREFIX . "order_option WHERE order_id = '" . (int)$order_id . "' AND order_product_id = '" . $result['order_product_id'] . "'");
 
@@ -457,10 +467,10 @@ class ModelCheckoutOrder extends Model {
                }
 
                $text .= "\n";
-
                $text .= $language->get('text_new_order_total') . "\n";
 
                foreach ($order_total_query->rows as $result) {
+                   $text .= '  ';
                    $text .= $result['title'] . ' ' . html_entity_decode($result['text'], ENT_NOQUOTES, 'UTF-8') . "\n";
                }

oc1513-admin-email-neworder.diff 145

Opencart 1.5.1.3: добавляем главную страницу в sitemap.xml

- Posted in Uncategorized by

Оказывается, в sitemap.xml (Google sitemap) нет ссылки на главную страницу сайта. Добавляем несколько строк в catalog/controller/feed/google_sitemap.php:

commit acf2aa3f70fdb171846ffe81b74f2682cce8e639
Author: Ruslan Brest <rb@labtodo.com>
Date:   Wed Feb 22 13:09:20 2012 +0200
    [+] Add home page address to sitemap.xml
diff --git a/upload/catalog/controller/feed/google_sitemap.php b/upload/catalog/controller/feed/google_sitemap.php
index 3d6d475..7a3f750 100644
--- a/upload/catalog/controller/feed/google_sitemap.php
+++ b/upload/catalog/controller/feed/google_sitemap.php
@@ -5,6 +5,12 @@ class ControllerFeedGoogleSitemap extends Controller {
         $output  = '<?xml version="1.0" encoding="UTF-8"?>';
         $output .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
 
+       $output .= '<url>';
+       $output .= '<loc>' . HTTP_SERVER . '</loc>';
+       $output .= '<changefreq>weekly</changefreq>';
+       $output .= '<priority>1.0</priority>';
+       $output .= '</url>';
+
         $this->load->model('catalog/product');
 
         $products = $this->model_catalog_product->getProducts();

Opencart 1.5.1.3: выдача HTTP/404 для товаров, которые отключены администратором

- Posted in Uncategorized by

В Опенкарт, если товар отключен, а внешняя ссылка на него где-то сохранилась, при переходе по ней будет отдан HTTP код 200 (OK) вместо более правильного 404 (Not Found).

Эту ситуацию исправляет одна строчка, см. патч ниже.

Спасибо: molotochek, см. http://opencartforum.ru/topic/418-%d0%bd%d0%b5-%d1%80%d0%b0%d0%b1%d0%be%d1%82%d0%b0%d0%b5%d1%82-404/page__view__findpost__p__46795

commit 6ef5a39e9e82c4e82901ce47d5304313867afb83
Author: Ruslan Brest <rb@labtodo.com>
Date:   Tue Feb 21 17:03:00 2012 +0200
    [!] HTTP/200 => HTTP/404 для товаров, которые отключены администратором
    и других страниц с ненайденными сущностями
    
    by: molotochek
    http://opencartforum.ru/topic/418-%d0%bd%d0%b5-%d1%80%d0%b0%d0%b1%d0%be%d1%82%d0%b0%d0%b5%d1%82-404/page__view__findpost__p__46795
diff --git a/upload/catalog/controller/product/product.php b/upload/catalog/controller/product/product.php
index c39ad77..c5579db 100644
--- a/upload/catalog/controller/product/product.php
+++ b/upload/catalog/controller/product/product.php
@@ -414,15 +414,13 @@ class ControllerProductProduct extends Controller {
            );
 
            $this->document->setTitle($this->language->get('text_error'));
-
            $this->data['heading_title'] = $this->language->get('text_error');
-
            $this->data['text_error'] = $this->language->get('text_error');
-
            $this->data['button_continue'] = $this->language->get('button_continue');
-
            $this->data['continue'] = $this->url->link('common/home');
 
+           $this->response->addHeader($this->request->server['SERVER_PROTOCOL'] . '/1.1 404 Not Found');
+
            if (file_exists(DIR_TEMPLATE . $this->config->get('config_template') . '/template/error/not_found.tpl')) {
                $this->template = $this->config->get('config_template') . '/template/error/not_found.tpl';
            } else {
diff --git a/upload/catalog/controller/account/download.php b/upload/catalog/controller/account/download.php
index 7b736d2..4cb0a0d 100644
--- a/upload/catalog/controller/account/download.php
+++ b/upload/catalog/controller/account/download.php
@@ -120,12 +120,11 @@ class ControllerAccountDownload extends Controller {
            $this->response->setOutput($this->render());
        } else {
            $this->data['heading_title'] = $this->language->get('heading_title');
-
            $this->data['text_error'] = $this->language->get('text_empty');
-
            $this->data['button_continue'] = $this->language->get('button_continue');
-
            $this->data['continue'] = $this->url->link('account/account', '', 'SSL');
+           
+           $this->response->addHeader($this->request->server['SERVER_PROTOCOL'] . '/1.1 404 Not Found');
 
            if (file_exists(DIR_TEMPLATE . $this->config->get('config_template') . '/template/error/not_found.tpl')) {
                $this->template = $this->config->get('config_template') . '/template/error/not_found.tpl';
diff --git a/upload/catalog/controller/account/order.php b/upload/catalog/controller/account/order.php
index cb49bef..5006970 100644
--- a/upload/catalog/controller/account/order.php
+++ b/upload/catalog/controller/account/order.php
@@ -434,6 +434,8 @@ class ControllerAccountOrder extends Controller {
            );
 
            $this->data['continue'] = $this->url->link('account/order', '', 'SSL');
+           
+           $this->response->addHeader($this->request->server['SERVER_PROTOCOL'] . '/1.1 404 Not Found');
 
            if (file_exists(DIR_TEMPLATE . $this->config->get('config_template') . '/template/error/not_found.tpl')) {
                $this->template = $this->config->get('config_template') . '/template/error/not_found.tpl';
diff --git a/upload/catalog/controller/account/return.php b/upload/catalog/controller/account/return.php
index 1575ff0..b9add37 100644
--- a/upload/catalog/controller/account/return.php
+++ b/upload/catalog/controller/account/return.php
@@ -283,12 +283,11 @@ class ControllerAccountReturn extends Controller {
            );
 
            $this->data['heading_title'] = $this->language->get('text_return');
-
            $this->data['text_error'] = $this->language->get('text_error');
-
            $this->data['button_continue'] = $this->language->get('button_continue');
-
            $this->data['continue'] = $this->url->link('account/return', '', 'SSL');
+           
+           $this->response->addHeader($this->request->server['SERVER_PROTOCOL'] . '/1.1 404 Not Found');
 
            if (file_exists(DIR_TEMPLATE . $this->config->get('config_template') . '/template/error/not_found.tpl')) {
                $this->template = $this->config->get('config_template') . '/template/error/not_found.tpl';
diff --git a/upload/catalog/controller/checkout/cart.php b/upload/catalog/controller/checkout/cart.php
index afc8660..964c180 100644
--- a/upload/catalog/controller/checkout/cart.php
+++ b/upload/catalog/controller/checkout/cart.php
@@ -277,12 +277,11 @@ class ControllerCheckoutCart extends Controller {
            $this->response->setOutput($this->render());
        } else {
            $this->data['heading_title'] = $this->language->get('heading_title');
-
            $this->data['text_error'] = $this->language->get('text_empty');
-
            $this->data['button_continue'] = $this->language->get('button_continue');
-
            $this->data['continue'] = $this->url->link('common/home');
+           
+           $this->response->addHeader($this->request->server['SERVER_PROTOCOL'] . '/1.1 404 Not Found');
 
            if (file_exists(DIR_TEMPLATE . $this->config->get('config_template') . '/template/error/not_found.tpl')) {
                $this->template = $this->config->get('config_template') . '/template/error/not_found.tpl';
diff --git a/upload/catalog/controller/error/not_found.php b/upload/catalog/controller/error/not_found.php
index 251a745..ab0758a 100644
--- a/upload/catalog/controller/error/not_found.php
+++ b/upload/catalog/controller/error/not_found.php
@@ -22,15 +22,12 @@ class ControllerErrorNotFound extends Controller {
        }
 
        $this->data['heading_title'] = $this->language->get('heading_title');
-
        $this->data['text_error'] = $this->language->get('text_error');
-
        $this->data['button_continue'] = $this->language->get('button_continue');
+       $this->data['continue'] = $this->url->link('common/home');
 
        $this->response->addHeader($this->request->server['SERVER_PROTOCOL'] . '/1.1 404 Not Found');
 
-       $this->data['continue'] = $this->url->link('common/home');
-
        if (file_exists(DIR_TEMPLATE . $this->config->get('config_template') . '/template/error/not_found.tpl')) {
            $this->template = $this->config->get('config_template') . '/template/error/not_found.tpl';
        } else {
diff --git a/upload/catalog/controller/information/information.php b/upload/catalog/controller/information/information.php
index e3b52ce..38df1c7 100644
--- a/upload/catalog/controller/information/information.php
+++ b/upload/catalog/controller/information/information.php
@@ -62,14 +62,12 @@ class ControllerInformationInformation extends Controller {
            );
 
            $this->document->setTitle($this->language->get('text_error'));
-
            $this->data['heading_title'] = $this->language->get('text_error');
-
            $this->data['text_error'] = $this->language->get('text_error');
-
            $this->data['button_continue'] = $this->language->get('button_continue');
-
            $this->data['continue'] = $this->url->link('common/home');
+           
+           $this->response->addHeader($this->request->server['SERVER_PROTOCOL'] . '/1.1 404 Not Found');
 
            if (file_exists(DIR_TEMPLATE . $this->config->get('config_template') . '/template/error/not_found.tpl')) {
                $this->template = $this->config->get('config_template') . '/template/error/not_found.tpl';
diff --git a/upload/catalog/controller/product/category.php b/upload/catalog/controller/product/category.php
index f30cb53..4ffea91 100644
--- a/upload/catalog/controller/product/category.php
+++ b/upload/catalog/controller/product/category.php
@@ -379,14 +379,12 @@ class ControllerProductCategory extends Controller {
            );
 
            $this->document->setTitle($this->language->get('text_error'));
-
            $this->data['heading_title'] = $this->language->get('text_error');
-
            $this->data['text_error'] = $this->language->get('text_error');
-
            $this->data['button_continue'] = $this->language->get('button_continue');
-
            $this->data['continue'] = $this->url->link('common/home');
+           
+           $this->response->addHeader($this->request->server['SERVER_PROTOCOL'] . '/1.1 404 Not Found');
 
            if (file_exists(DIR_TEMPLATE . $this->config->get('config_template') . '/template/error/not_found.tpl')) {
                $this->template = $this->config->get('config_template') . '/template/error/not_found.tpl';
diff --git a/upload/catalog/controller/product/manufacturer.php b/upload/catalog/controller/product/manufacturer.php
index 7dd5359..3ce6a78 100644
--- a/upload/catalog/controller/product/manufacturer.php
+++ b/upload/catalog/controller/product/manufacturer.php
@@ -414,14 +414,12 @@ class ControllerProductManufacturer extends Controller {
            );
 
            $this->document->setTitle($this->language->get('text_error'));
-
            $this->data['heading_title'] = $this->language->get('text_error');
-
            $this->data['text_error'] = $this->language->get('text_error');
-
            $this->data['button_continue'] = $this->language->get('button_continue');
-
            $this->data['continue'] = $this->url->link('common/home');
+           
+           $this->response->addHeader($this->request->server['SERVER_PROTOCOL'] . '/1.1 404 Not Found');
 
            if (file_exists(DIR_TEMPLATE . $this->config->get('config_template') . '/template/error/not_found.tpl')) {
                $this->template = $this->config->get('config_template') . '/template/error/not_found.tpl';
diff --git a/upload/catalog/controller/product/shoppica.php b/upload/catalog/controller/product/shoppica.php
index cf31414..0eb7eb0 100644
--- a/upload/catalog/controller/product/shoppica.php
+++ b/upload/catalog/controller/product/shoppica.php
@@ -338,6 +338,8 @@ class ControllerProductShoppica extends Controller
         $this->data['button_continue'] = $this->language->get('button_continue');
 
         $this->data['continue'] = HTTP_SERVER . 'index.php?route=common/home';
+        
+        $this->response->addHeader($this->request->server['SERVER_PROTOCOL'] . '/1.1 404 Not Found');
 
         if (file_exists(DIR_TEMPLATE . $this->config->get('config_template') . '/template/error/not_found.tpl')) {
             $this->template = $this->config->get('config_template') . '/template/error/not_found.tpl';
diff --git a/upload/catalog/model/checkout/order.php b/upload/catalog/model/checkout/order.php
index 8e8d414..afeb625 100644
--- a/upload/catalog/model/checkout/order.php
+++ b/upload/catalog/model/checkout/order.php
@@ -566,4 +566,4 @@ class ModelCheckoutOrder extends Model {
        }
    }
 }
-?>
+?>
\ No newline at end of file

Quickcheckout: контроль суммы заказа (не менее чем)

- Posted in Uncategorized by

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

Для тех, кому хочется подобного:

Файл: catalog/view/theme/default/template/checkout/quickcheckout_confirm.tpl

найти:

<div class="payment"><?php echo $payment; ?></div>

заменить на:

<?php if($total['value'] < 1000) { ?>
       <div class="warning">Сумма заказа должна быть не менее 1000 руб.!</div>
<?php } else { ?>
       <div class="payment"><?php echo $payment; ?></div>
<?php } ?>

Running workout (&#45;15C, 17km @ 2h02m)

- Posted in Uncategorized by

Endomondo Running Workout

Для статистики по одежде и погоде - записи на память.

2012-01-31. -15C, ветер. Футболка с длинным рукавом + виндблок = оптимум. При -5..-8 было жарко, снимал куртку. Но ходить прохладно. Луна светит так, что никакие фонарики не нужны.

Стандартно - баф на шею-нос, шапка из виндблока. Ну и перчатки (тоже виндблок).

Opencart 1.5.1.2: Модуль категорий: третий уровень (без оптимизации запросов)

- Posted in Uncategorized by

Запоздало публикую рецепт вывода 3-го уровня категорий для версии 1.5.1.2. Способ стандартный опенкартовский, чудовищно неоптимальный, но мне надо было сделать быстро, поэтому не стал голову морочить. Для 1.5.1.3 надо будет сделать получше (если это ещё не сделано кем-то ещё).

Количество категорий - 91. Первого уровня - около десятка. Визуально на скорости загрузки страниц это добавление третьего уровня никак не сказалось. Цифры не смотрел.

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

Тем, кому количество товаров в категориях захочется оставить - верните на место закомментированные в паре мест $product_total, я их оставил в коде. Но это вам и тормозов добавит. Я бы, если и оставлял отображение количества , то на небольших списках и желательно с короткими названиями (не многострочными).

commit 2f6f47af0cfe0bea4815b76579940eb8139f11cb
Author: Ruslan Brest <rb@labtodo.com>
Date:   Mon Jan 30 02:21:17 2012 +0200
    [+] oc1512 Модуль категорий: третий уровень (без оптимизации запросов)
    
    убрано отображение количества товаров в категориях (так гораздо аккуратней при большом кол-ве категорий)
    убраны точки и улучшены отступы -- для более аккуратного внешнего вида
diff --git a/public_html/catalog/controller/module/category.php b/public_html/catalog/controller/module/category.php
index 09a4bd1..5a9fcaa 100644
--- a/public_html/catalog/controller/module/category.php
+++ b/public_html/catalog/controller/module/category.php
@@ -23,6 +23,12 @@ class ControllerModuleCategory extends Controller {
            $this->data['child_id'] = 0;
        }
 
+       if (isset($parts[2])) {
+           $this->data['ch3_id'] = $parts[2];
+       } else {
+           $this->data['ch3_id'] = 0;
+       }
+
        $this->load->model('catalog/category');
        $this->load->model('catalog/product');
 
@@ -40,12 +46,22 @@ class ControllerModuleCategory extends Controller {
                    'filter_category_id'  => $child['category_id'],
                    'filter_sub_category' => true
                );
-
-               $product_total = $this->model_catalog_product->getTotalProducts($data);
+               // $product_total = $this->model_catalog_product->getTotalProducts($data);
+
+               $level3 = $this->model_catalog_category->getCategories($child['category_id']);
+               $l3_data = array();
+               foreach ($level3 as $l3) {
+                   $l3_data[] = array(
+                       'category_id' => $l3['category_id'],
+                       'name'        => $l3['name'],
+                       'href'        => $this->url->link('product/category', 'path=' . $category['category_id'] . '_' . $child['category_id']. '_' . $l3['category_id'])
+                   );
+               }
 
                $children_data[] = array(
                    'category_id' => $child['category_id'],
-                   'name'        => $child['name'] . ' (' . $product_total . ')',
+                   'name'        => $child['name'], // . ' (' . $product_total . ')',
+                   'children'    => $l3_data,
                    'href'        => $this->url->link('product/category', 'path=' . $category['category_id'] . '_' . $child['category_id'])
                );
            }
@@ -55,11 +71,11 @@ class ControllerModuleCategory extends Controller {
                'filter_sub_category' => true
            );
 
-           $product_total = $this->model_catalog_product->getTotalProducts($data);
+           // $product_total = $this->model_catalog_product->getTotalProducts($data);
 
            $this->data['categories'][] = array(
                'category_id' => $category['category_id'],
-               'name'        => $category['name'] . ' (' . $product_total . ')',
+               'name'        => $category['name'], // . ' (' . $product_total . ')',
                'children'    => $children_data,
                'href'        => $this->url->link('product/category', 'path=' . $category['category_id'])
            );
diff --git a/public_html/catalog/view/theme/crista_grey/stylesheet/stylesheet.css b/public_html/catalog/view/theme/crista_grey/stylesheet/stylesheet.css
index d215d29..7eff032 100644
--- a/public_html/catalog/view/theme/crista_grey/stylesheet/stylesheet.css
+++ b/public_html/catalog/view/theme/crista_grey/stylesheet/stylesheet.css
@@ -830,6 +830,12 @@ a.button:hover span {
 .box-category > ul > li ul > li > a.active {
    font-weight: bold;
 }
+
+.box-category > ul > li ul > li ul > li {
+   padding: 5px 0 0 10px;
+}
+
+
 /* content */
 #content .content {
    padding: 10px;
diff --git a/public_html/catalog/view/theme/default/template/module/category.tpl b/public_html/catalog/view/theme/default/template/module/category.tpl
index fca707f..5157259 100644
--- a/public_html/catalog/view/theme/default/template/module/category.tpl
+++ b/public_html/catalog/view/theme/default/template/module/category.tpl
@@ -16,17 +16,33 @@
                <?php foreach ($category['children'] as $child) { ?>
                    <li>
                    <?php if ($child['category_id'] == $child_id) { ?>
-                       <a href="<?php echo $child['href']; ?>" class="active">• <?php echo $child['name']; ?></a>
+                       <a href="<?php echo $child['href']; ?>" class="active"><?php echo $child['name']; ?></a>
                    <?php } else { ?>
-                       <a href="<?php echo $child['href']; ?>">· <?php echo $child['name']; ?></a>
+                       <a href="<?php echo $child['href']; ?>"><?php echo $child['name']; ?></a>
                    <?php } ?>
+
+                   <?php if ($child['children']) { ?>
+                   <ul>
+                   <?php foreach ($child['children'] as $ch3) { ?>
+                       <li>
+                       <?php if ($ch3['category_id'] == $ch3_id) { ?>
+                           <a href="<?php echo $ch3['href']; ?>" class="active"><?php echo $ch3['name']; ?></a>
+                       <?php } else { ?>
+                           <a href="<?php echo $ch3['href']; ?>"><?php echo $ch3['name']; ?></a>
+                       <?php } ?>
+                       </li>
+                   <?php } ?>
+                   </ul>
+                   <?php } ?>
+
+
                    </li>
                <?php } ?>
                </ul>
-           <?php } ?>
+               <?php } ?>
            </li>
        <?php } ?>
        </ul>
        </div>
    </div>
-</div>
+</div>
\ No newline at end of file

Форум для интеграции с Opencart

- Posted in Uncategorized by

Встречайте форум для Опенкарт 1.5.1.x. Интегрируется с базой Опенкарт (как пользователями, так и покупателями), настраивается из админки. Написан на CakePHP. Живёт на гитхабе.

GitHub: https://github.com/horechek/ocforum

Демо: http://demoshop.tvorzasp.com/forum/

Оригинал новости на сайте автора: http://tvorzasp.com/blog/forum-dlya-opencart

UPD 2012-09-02: Новый OC Forum 0.2.0alpha. -CakePHP, +Yii

Opencart 1.5.1.x: Указание размеров картинок в product/category

- Posted in Uncategorized by

Указание размеров картинок в product/category, чтобы не ехала вёрстка (при медленной подгрузке картинок некоторые описания иногда оставалось очень далеко от картинки).

См. также Opencartforum.ru: Кривое отображение при отсутствии картинки товара. Там есть иллюстрация того, от чего именно это спасает.

Проверено на v1.5.1.2 и 1.5.1.3 - нет никаких отличий.

commit 771675b8c92df253457d29eafe804b56aabc8240
Author: Ruslan Brest <rb@labtodo.com>
Date:   Tue Sep 27 14:04:59 2011 +0300
    [+] Указание размеров картинок в product/category
    
    чтобы не ехала вёрстка (при медленной подгрузке картинок некоторые описания
    иногда оставалось очень далеко от картинки)
diff --git a/public_html/catalog/controller/product/category.php b/public_html/catalog/controller/product/category.php
index 6cc4b86..cd86bfd 100644
--- a/public_html/catalog/controller/product/category.php
+++ b/public_html/catalog/controller/product/category.php
@@ -100,8 +100,10 @@ class ControllerProductCategory extends Controller {
 
            if ($category_info['image']) {
                $this->data['thumb'] = $this->model_tool_image->resize($category_info['image'], $this->config->get('config_image_category_width'), $this->config->get('config_image_category_height'));
+               $this->data['thumb_attr'] = 'width="'.$this->config->get('config_image_category_width').'" height="'.$this->config->get('config_image_category_height').'"';
            } else {
                $this->data['thumb'] = '';
+               $this->data['thumb_attr'] = '';
            }
 
            $this->data['description'] = html_entity_decode($category_info['description'], ENT_QUOTES, 'UTF-8');
@@ -190,6 +192,8 @@ class ControllerProductCategory extends Controller {
                $this->data['products'][] = array(
                    'product_id'  => $result['product_id'],
                    'thumb'       => $image,
+                   'thumb_w'     => $this->config->get('config_image_product_width'),
+                   'thumb_h'     => $this->config->get('config_image_product_height'),
                    'name'        => $result['name'],
                    'description' => $descr_plaintext,
                    'price'       => $price,
diff --git a/public_html/catalog/view/theme/default/template/product/category.tpl b/public_html/catalog/view/theme/default/template/product/category.tpl
index dd795de..e5e080c 100644
--- a/public_html/catalog/view/theme/default/template/product/category.tpl
+++ b/public_html/catalog/view/theme/default/template/product/category.tpl
@@ -9,7 +9,7 @@
   <?php if ($thumb || $description) { ?>
   <div class="category-info">
     <?php if ($thumb) { ?>
-    <div class="image"><img src="<?php echo $thumb; ?>" alt="<?php echo $heading_title; ?>" /></div>
+    <div class="image"><img src="<?php echo $thumb; ?>" alt="<?php echo $heading_title; ?>" <?php echo $thumb_attr; ?> /></div>
     <?php } ?>
     <?php if ($description) { ?>
     <?php echo $description; ?>
@@ -70,7 +70,7 @@
     <?php foreach ($products as $product) { ?>
     <div>
       <?php if ($product['thumb']) { ?>
-      <div class="image"><a href="<?php echo $product['href']; ?>"><img src="<?php echo $product['thumb']; ?>" title="<?php echo $product['name']; ?>" alt="<?php echo $product['name']; ?>" /></a></div>
+      <div class="image"><a href="<?php echo $product['href']; ?>"><img src="<?php echo $product['thumb']; ?>" width="<?php echo $product['thumb_w']; ?>" height="<?php echo $product['thumb_h']; ?>" title="<?php echo $product['name']; ?>" alt="<?php echo $product['name']; ?>" /></a></div>
       <?php } ?>
       <div class="name"><a href="<?php echo $product['href']; ?>"><?php echo $product['name']; ?></a></div>
       <div class="description"><?php echo $product['description']; ?></div>

Opencart 1.5.1.3 &#45; не отображаются заказы в админке

- Posted in Uncategorized by

Несколько человек обращались с одинаковым вопросом:

при заказе через модуль quickcheckout для opencart 1.5.1.3 не отображаются заказы в админке (Продажи->Заказы) 1.5.1.3. Тема нестандартная, но все скопировано и все работает (кроме заказов в админке). При этом в Отчеты->Продажи->Заказы отображается количество заказов. Подскажите пожалуйста как решить проблему.

Сообщаю, что я тут не при чём - это ошибка #616 в Opencart. Заказы не отображаются правильно и при заказах обычной формой. Там же описан кратенький рецепт решения (изменить is_null на empty в 6 или 7 строке).

Opencart 1.5.1.3 (admin): пара мелких улучшений Admin Dashboard

- Posted in Uncategorized by

Сегодня пара мелких, но полезных визуальных улучшений админки:

  • Выделение цветом разрешенных элементов в списках расширений (Дополнения / доставка, оплата и т.п.);
  • Dashboard - выделение красным пунктов, ожидающих модерации;
  • подсветка строки под курсором в таблицах (hover) (кажется было раньше, здесь для 1.5.1.3 уже за компанию попалось под руку)
  • Вспомните, как вы пропускали новые комментарии и ожидающих активации партнеров, а также с трудом пытались сориентироваться на большом экране, какой же модуль разрешён, а какой запрещён...

    Вспомнили? Тогда информация будет вам полезна:

    commit f9c192a6c30b7b8db5a5621b391680451c1c6d7d
    Author: Ruslan Brest <rb@labtodo.com>
    Date:   Fri Dec 30 20:37:51 2011 +0200
        [+] admin UI: Выделение цветом разрешенных элементов в списках расширений; Dashboard - выделение красным пунктов, ожидающих модерации
    diff --git a/upload/admin/controller/common/home.php b/upload/admin/controller/common/home.php
    index 06b5567..c2b1a7e 100644
    --- a/upload/admin/controller/common/home.php
    +++ b/upload/admin/controller/common/home.php
    @@ -147,16 +147,22 @@ class ControllerCommonHome extends Controller {
            
            $this->data['total_customer'] = $this->model_sale_customer->getTotalCustomers();
            $this->data['total_customer_approval'] = $this->model_sale_customer->getTotalCustomersAwaitingApproval();
    +       if( $this->data['total_customer_approval'] > 0 )
    +           $this->data['total_customer_approval'] = sprintf('<span class="attn">%s</span>', $this->data['total_customer_approval']);
            
            $this->load->model('catalog/review');
            
            $this->data['total_review'] = $this->model_catalog_review->getTotalReviews();
            $this->data['total_review_approval'] = $this->model_catalog_review->getTotalReviewsAwaitingApproval();
    +       if( $this->data['total_review_approval'] > 0 )
    +           $this->data['total_review_approval'] = sprintf('<span class="attn">%s</span>', $this->data['total_review_approval']);
            
            $this->load->model('sale/affiliate');
            
            $this->data['total_affiliate'] = $this->model_sale_affiliate->getTotalAffiliates();
            $this->data['total_affiliate_approval'] = $this->model_sale_affiliate->getTotalAffiliatesAwaitingApproval();
    +       if( $this->data['total_affiliate_approval'] > 0 )
    +           $this->data['total_affiliate_approval'] = sprintf('<span class="attn">%s</span>', $this->data['total_affiliate_approval']);
                    
            $this->data['orders'] = array(); 
            
    diff --git a/upload/admin/controller/extension/feed.php b/upload/admin/controller/extension/feed.php
    index 6d8cf42..8ba93ff 100644
    --- a/upload/admin/controller/extension/feed.php
    +++ b/upload/admin/controller/extension/feed.php
    @@ -87,7 +87,7 @@ class ControllerExtensionFeed extends Controller {
                                        
                    $this->data['extensions'][] = array(
                        'name'   => $this->language->get('heading_title'),
    -                   'status' => $this->config->get($extension . '_status') ? $this->language->get('text_enabled') : $this->language->get('text_disabled'),
    +                   'status' => $this->config->get($extension . '_status') ? '<span class="enabled">'.$this->language->get('text_enabled').'</span>' : $this->language->get('text_disabled'),
                        'action' => $action
                    );
                }
    diff --git a/upload/admin/controller/extension/payment.php b/upload/admin/controller/extension/payment.php
    index 50502c3..fc49d39 100644
    --- a/upload/admin/controller/extension/payment.php
    +++ b/upload/admin/controller/extension/payment.php
    @@ -97,7 +97,7 @@ class ControllerExtensionPayment extends Controller {
                    $this->data['extensions'][] = array(
                        'name'       => $this->language->get('heading_title'),
                        'link'       => $link,
    -                   'status'     => $this->config->get($extension . '_status') ? $this->language->get('text_enabled') : $this->language->get('text_disabled'),
    +                   'status'     => $this->config->get($extension . '_status') ? '<span class="enabled">'.$this->language->get('text_enabled').'</span>' : $this->language->get('text_disabled'),
                        'sort_order' => $this->config->get($extension . '_sort_order'),
                        'action'     => $action
                    );
    diff --git a/upload/admin/controller/extension/shipping.php b/upload/admin/controller/extension/shipping.php
    index e801811..e5c6b4c 100644
    --- a/upload/admin/controller/extension/shipping.php
    +++ b/upload/admin/controller/extension/shipping.php
    @@ -88,7 +88,7 @@ class ControllerExtensionShipping extends Controller {
                                            
                    $this->data['extensions'][] = array(
                        'name'       => $this->language->get('heading_title'),
    -                   'status'     => $this->config->get($extension . '_status') ? $this->language->get('text_enabled') : $this->language->get('text_disabled'),
    +                   'status'     => $this->config->get($extension . '_status') ? '<span class="enabled">'.$this->language->get('text_enabled').'</span>' : $this->language->get('text_disabled'),
                        'sort_order' => $this->config->get($extension . '_sort_order'),
                        'action'     => $action
                    );
    diff --git a/upload/admin/controller/extension/total.php b/upload/admin/controller/extension/total.php
    index acbb06f..ebde251 100644
    --- a/upload/admin/controller/extension/total.php
    +++ b/upload/admin/controller/extension/total.php
    @@ -88,7 +88,7 @@ class ControllerExtensionTotal extends Controller {
                                            
                    $this->data['extensions'][] = array(
                        'name'       => $this->language->get('heading_title'),
    -                   'status'     => $this->config->get($extension . '_status') ? $this->language->get('text_enabled') : $this->language->get('text_disabled'),
    +                   'status'     => $this->config->get($extension . '_status') ? '<span class="enabled">'.$this->language->get('text_enabled').'</span>' : $this->language->get('text_disabled'),
                        'sort_order' => $this->config->get($extension . '_sort_order'),
                        'action'     => $action
                    );
    diff --git a/upload/admin/view/stylesheet/stylesheet.css b/upload/admin/view/stylesheet/stylesheet.css
    index fe82124..53f488f 100644
    --- a/upload/admin/view/stylesheet/stylesheet.css
    +++ b/upload/admin/view/stylesheet/stylesheet.css
    @@ -517,3 +517,12 @@ table.form > tbody > tr > td {
        padding: 10px; 
        min-height: 180px;  
     }
    +
    +/**** UI improvement ****/
    +.enabled {color: green;}
    +.attn {
    +   padding: 3px;
    +   background: #FFD1D1 10px center no-repeat;
    +   border: 1px solid #F8ACAC;
    +   color: #555555;
    +}
    \ No newline at end of file
    commit f825c12b92afe6f20137b38814a7b72f3381e729
    Author: Ruslan Brest <rb@labtodo.com>
    Date:   Fri Dec 30 20:04:14 2011 +0200
        [+] admin UI: подсветка строки под курсором в таблицах (hover)
    diff --git a/upload/admin/view/stylesheet/stylesheet.css b/upload/admin/view/stylesheet/stylesheet.css
    index bb9e451..fe82124 100644
    --- a/upload/admin/view/stylesheet/stylesheet.css
    +++ b/upload/admin/view/stylesheet/stylesheet.css
    @@ -304,6 +304,10 @@ a.button, .list a.button {
        padding: 0px 5px;
        background: #FFFFFF;
     }
    +.list tbody tr:hover td {
    +   background:  #e6f1ff;
    +   /*background: #ffe9a3;*/
    +}
     .list .left {
        text-align: left;
        padding: 7px;

    Почему я уважаю email (и может ещё Jabber), а скайп, аську и прочую чатико&#45;переписку (VK, FB) &#45;&#45; нет

    - Posted in Uncategorized by

    Преимущества email:

    • не отвлекает тогда, когда ему хочется. В работе программиста это очень важный момент. Критически важный;
    • Переписка может без доп. усилий храниться в одном месте, на сервере. И быть доступной с нескольких компьютеров (в т.ч. чужих) и телефонов;
    • возможность сводить переписку в единое место -- это возможность настроить раз единые правила фильтрации и постепенно их дополнять;
    • простота - друг совершенства. Настроить уведомления на SMS при поступлении почты от определенной группы собеседников или при срабатывании какого-либо правила -- никаких проблем. Отфильтровать и настроить такое уведомление в ICQ/Skype... Ну-у-у, попробуйте. И учтите весь зоопарк устройств, которые могут понадобиться;
    • правила обработки email могут храниться и обрабатываться на сервере. Необязательно держать у себя включенное и подключенное к интернет устройство, чтобы оно занималось фильтрацией (а 99.99% времени - простаивало и жрало батарейку без всякой пользы). И не надо головной боли при смене устройства. Получил SMS о чем-то важном в почте - подключился, забрал почту, читаешь.

    Аська, скайп, телефон:

    • очень мешают тем, что могут прервать мыслительный процесс в самый неподходящий момент;
    • историю хранить в едином месте нереально. Особенно этим страдает Скайп и телефон;
    • в телефоне логов разговоров вообще не остается. Особенно при деловых разговорах. Или обсуждении деталей задач. Или условий. Человек иногда с удивлением узнает, что именно он говорил или писал, когда видит логи. Даже самый искренний. Потому что совершенно искренне считал, что сказал по-другому. И привычка фиксации часто спасает от скандалов. Речь вообще даже не о злом умысле. Просто об особенностях психологии и памяти человека в стрессовых условиях;
    • Скайп имеет самый идиотскую реализацию просмотра истории и поиска, которую я видел. А внутренности логов лежат не в виде текста, HTML или XML, а в бинарном виде. Но если бы это позволяло ему хотя бы быстро работать... Так нет же. Дождаться загрузки длинного лога - целая вечность проходит;
    • если угораздило обсуждать что-то важное в дороге и забыл или не смог перенести это вовремя на десктоп или в онлайн-хранилище -- найти потом, где это было, когда и на каком девайсе -- целый подвиг. Особенно если дел много. Перенести с телефона важные детали или присланные в процессе разговора длинные ссылки -- сплошная головная боль, потому что мобильные ICQ/Jabber клиенты обычно понятия не имеют об экспорте. Совсем другое дело при использовании email.
    • в аське часты сообщения "привет" и "ты здесь?", после которых нет ничего. Представьте себе, что чувствует занятый человек, просматривая раз в 10-30 минут накопившиеся сообщения, чтобы ответить на самое важное, а остальные минут 20-30 иметь возможность посвятить очередной задаче без отвлечений? Я на такие вопросы и приветы обычно вообще не отвечаю, и вот почему. Если есть что-то важное - человек обычно пишет о проблеме сразу, а я могу быстро отреагировать, когда в свободный промежуток увидел конкретный вопрос среди десятка других бестолковых сообщений-пустышек. Если там только "приветы", то обычно человеку просто одиноко и разговоры в аське превращаются не в решение проблем, а долгое обмусоливание и размышления вслух;
    • обсуждение одного и того же вопроса по email занимает 10-20 минут, в ICQ/скайпе -- минимум час-полтора. Поскольку в разговоре возникают паузы (то у одного, то у другого собеседника) и писать "словесного мусора" приходится гораздо больше, чем обычно пишешь в ответе на email. В общем, чат отвлекает от дел гораздо больше;
    • использование IM и чатов приводит к более частой отсылке отрывочных фраз, не всегда полностью завершённых, которые нетерпеливый собеседник может прервать новыми вопросами и обсуждениями, не дождавшись конца мысли. В итоге идёт винегрет обсуждения, часто нескольких вопросов, и разобраться в этой каше, что к чему относится, впоследствии становится очень трудно. Кто читал логи бурных быстрых обсуждений в попытке выловить из них конкретные задачи, планы и историю обсуждения вопроса или нескольких - поймёт, о чём речь. Кто пытался восстановить ситуацию по вчерашним-позавчерашним срочным разговорам, когда приходится переключаться между несколькими проектами (и все подробности в голове просто не удержишь) -- тоже в курсе проблемы;
    • иллюзия постоянной доступности человека в аське/скайпе/джаббере - лишь иллюзия. Когда именно я увижу сообщение - зависит лишь от наличия свободного времени, а не от сигнала. Чаще я смотреть туда не стану по каждому прерыванию, поскольку не в службе поддержки работаю, а для работы мне надо сосредоточиться и иметь возможность думать о задаче в течение достаточно длинных промежутков времени. Программисты так устроены: мы держим в голове массу деталей и связей, и нам надо построить в голове модель происходящих процессов, чтобы в ней ориентироваться. И если задачи сложные, а не 5-минутные, любое отвлечение способно разрушить весь этот воздушный замок и процесс "втягивания" приходится начинать заново.

    Резюме

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

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

    Поэтому я за почту.

    Скорость реакции на почту и IM - одинаковая (сюрприз!). Поскольку всё равно определяется скоростью моей реакции на ситуацию, а не скоростью света и чьими-то желаниями.

    Hint: как облегчить другим работу с вашими письмами

    Несколько мелочей и микро-правил, которые способны сделать другим жизнь проще при работе с вашими письмами.

    Необязательно, но весьма желательно:

    • Никакого HTML оформления. Забудьте. Только plain text;
    • приветствия в эл. письмах при регулярной переписке -- штука бесполезная. То же самое обычно можно сказать о цитировании и перечислении даты и времени письма, на которое отвечаем. Через некоторое время люди перестают обращать внимание на эти служебные строки, иногда там просто "слепое пятно" образуется. Если на большом экране эти 1-2 строки мы просто "пропускаем мимо глаз", то на смартфонах это занимает пол-экрана. Лучше, когда сразу идёт текст по делу;
    • то же, но в меньшей степени, относится к подписи. Уместно в первом письме к новому собеседнику включить полную подпись со всеми контактами и телефонами, а в последующих, если переписка уже длится некоторое время, лучше иметь короткий вариант: с 1-2 контактами или единственной ссылкой на сайт (на страницу со всеми контактами). То ли на персональном сайте, то ли где-то в соцсетях (Linkedin, VK, Facebook, Google+), то ли в блоге (Livejournal, Wordpress, Blogspot и т.п.) Иметь сейчас страницу с полным списком контактов совсем несложно, заводить для этого свой сайт-визитку необязательно.

    Внутренности писем:

    • разделяйте абзацы пустой строкой (два раза Enter, а не один). Если отвечаете после цитаты - также отделяйте ответ пустой строкой. Не пишите сразу на следующей строке после квотинга. Этим вы спасёте мозг и пальцы тех, кто работает с почтой на мобильных устройствах (смартфонах, в частности);
    • атомарность, одно письмо -- один вопрос. Лучше послать 10 коротких писем, чем одно большое с 10 разными вопросами. Это поможет легче работать с письмами как с задачами, а также спасёт в очередной раз мобильных пользователей (если не понимаете, о чём я - вы ни разу не пытались ответить на длинное письмо, находясь в пути и имея немного свободного времени). Как следствие - это способно значительно ускорить получение ответов: проще сразу ответить на 3-4 более простых вопроса с телефона, а пару более объёмных оставить для десктопа, чем если надо ответить на большое письмо с несколькими темами/проблемами. Редактировать большое письмо пальцами на смартфоне, пытаясь разбить его на более мелкие -- замучаешься;
    • если есть возможность - настройте почтовый клиент так, чтобы текст цитируемого письма располагался после подписи. Но в случае больших писем это будет неудобно. В случае с атомарными короткими сообщениями - очень удобно.

    Hint: как облегчить себе работу с почтой

    • Исповедуйте правило пустого инбокса;
    • не пользуйтесь почтовым ящиком как хранилищем. Всё, что касается конкретных проектов - сразу сохраняйте в папке с проектом (если письмо вообще стоит хранить) в виде обычного текста. Имя файла должно включать как минимум дату (в ISO формате: YYYY-MM-DD). Можно ещё время, email или ник отправителя. Совсем необязательно сохранять одно письмо в одном файле: цепочки обсуждений удобно записывать в один большой файл (txt или markdown), а аттачменты складывать рядом (предваряя имя файла датой-временем). Но разрастание файла быстро становится неудобным, так что пробуйте и выбирайте по обстоятельствам золотую середину;

    Не складируйте письма: всё, на что можно сразу ответить - отвечайте и удаляйте письмо. Не надо их разбрасывать по папкам, хранить в почтовом клиенте, сортировать с тем, чтобы когда-то почистить, создавать многочисленные правила автоматической сортировки. Всё это - пустая трата времени.

    Что даёт сохранение писем в архиве в виде обычных текстовых файлов?

    • По ним легко и просто искать: как по именам файлов, так и по содержимому;
    • Их можно поместить в систему контроля версий для более удобной и надёжной работы с изменениями;
    • Их легко архивировать и переносить с проектом, не дёргаясь и не собирая хвосты из рабочего и личного почтового ящика, аськи, скайпа, IRC, и ещё Вася что-то с клиентом обсуждал отдельно в четверг, и ещё-не-помню-где=кажется-что-то-было... Всё, относящееся к проекту, собрано в одной папке, а не разбросано в нескольких программах. Возможно, у вас есть багтрекер -- это замечательно. Но тоже накладывает определенные ограничения. Если вы постоянно работаете в команде, и всегда в одной - пользуйтесь багтрекером. Потому что в этом случае велик шанс, что у вас есть дизайнеры и директор, далёкие от VCS, плюс ACL и клиенты с возможностью пускать их в PM/CRM/helpdesk инструмент с веб-доступом. Главное, чтобы было одно централизованное место для поиска всей связанной с проектом информации;
    • Из текстового `markdown` формата легко получить всевозможные красиво оформленные форматы: html, PDF, doc/ods/docx;
    • Рекомендую завести одно персональное место в качестве хранилища знаний (внешнего мозга), плюс в каждом проекте складывать в папку doc всю переписку и дела по этому проекту. Это позволит легко дать доступ к материалам проекта другим участникам при росте команды, а вас быстро приучит разделять личное (в т.ч. и личные проекты) и рабочее. Таким образом, у вас будет всего 2 места для поисков: либо в папке проекта, либо в персональном "внешнем мозге". У меня ещё есть отдельное "более медленное" место для материалов из интернета и книг. Там я храню их в запакованном виде и ищу по именам файлов и папок (файловой системе). Поскольку эти материалы более фундаментальные и реже используются для регулярной оперативной работы и редактирования.

    См. также