Magento 1.5.x удаление заказов
При переносе проекта на другой хостинг, понадобилось удалить все заказы из magento, так как они были сделаны в тестовых целях. Оказалось, что в стандартных действия в админ панели magento нет удаления заказов. По соображениям разработчиков этого чудесного движка такая функция не доступна для заказов.
Поискав немного в интернете обнаружил немалое количество расширений, позволяющих удалять заказы, но все они оказались платные. Выход один — чистить руками в базе данных. Но какие таблицы необходимо почистить?
Ответ на этот вопрос я обнаружил на одном сайте: http://www.myscienceisbetter.info/delete-test-orders-in-magento-1-5-x.html
Итак, чтобы почистить заказы, нужно выполнить такой вот хитрый запрос:
SET FOREIGN_KEY_CHECKS=0; TRUNCATE `sales_flat_order`; TRUNCATE `sales_flat_order_address`; TRUNCATE `sales_flat_order_grid`; TRUNCATE `sales_flat_order_item`; TRUNCATE `sales_flat_order_status_history`; TRUNCATE `sales_flat_quote`; TRUNCATE `sales_flat_quote_address`; TRUNCATE `sales_flat_quote_address_item`; TRUNCATE `sales_flat_quote_item`; TRUNCATE `sales_flat_quote_item_option`; TRUNCATE `sales_flat_order_payment`; TRUNCATE `sales_flat_quote_payment`; TRUNCATE `sales_flat_shipment`; TRUNCATE `sales_flat_shipment_item`; TRUNCATE `sales_flat_shipment_grid`; TRUNCATE `sales_flat_invoice`; TRUNCATE `sales_flat_invoice_grid`; TRUNCATE `sales_flat_invoice_item`; TRUNCATE `sendfriend_log`; TRUNCATE `tag`; TRUNCATE `tag_relation`; TRUNCATE `tag_summary`; TRUNCATE `wishlist`; TRUNCATE `log_quote`; TRUNCATE `report_event`; ALTER TABLE `sales_flat_order` AUTO_INCREMENT=1; ALTER TABLE `sales_flat_order_address` AUTO_INCREMENT=1; ALTER TABLE `sales_flat_order_grid` AUTO_INCREMENT=1; ALTER TABLE `sales_flat_order_item` AUTO_INCREMENT=1; ALTER TABLE `sales_flat_order_status_history` AUTO_INCREMENT=1; ALTER TABLE `sales_flat_quote` AUTO_INCREMENT=1; ALTER TABLE `sales_flat_quote_address` AUTO_INCREMENT=1; ALTER TABLE `sales_flat_quote_address_item` AUTO_INCREMENT=1; ALTER TABLE `sales_flat_quote_item` AUTO_INCREMENT=1; ALTER TABLE `sales_flat_quote_item_option` AUTO_INCREMENT=1; ALTER TABLE `sendfriend_log` AUTO_INCREMENT=1; ALTER TABLE `sales_flat_order_payment` AUTO_INCREMENT=1; ALTER TABLE `sales_flat_quote_payment` AUTO_INCREMENT=1; ALTER TABLE `sales_flat_shipment` AUTO_INCREMENT=1; ALTER TABLE `sales_flat_shipment_item` AUTO_INCREMENT=1; ALTER TABLE `sales_flat_invoice` AUTO_INCREMENT=1; ALTER TABLE `sales_flat_invoice_grid` AUTO_INCREMENT=1; ALTER TABLE `sales_flat_invoice_item` AUTO_INCREMENT=1; ALTER TABLE `sales_flat_shipment_grid` AUTO_INCREMENT=1; ALTER TABLE `tag` AUTO_INCREMENT=1; ALTER TABLE `tag_relation` AUTO_INCREMENT=1; ALTER TABLE `tag_summary` AUTO_INCREMENT=1; ALTER TABLE `wishlist` AUTO_INCREMENT=1; ALTER TABLE `log_quote` AUTO_INCREMENT=1; ALTER TABLE `report_event` AUTO_INCREMENT=1; -- lets reset customers TRUNCATE `customer_address_entity`; TRUNCATE `customer_address_entity_datetime`; TRUNCATE `customer_address_entity_decimal`; TRUNCATE `customer_address_entity_int`; TRUNCATE `customer_address_entity_text`; TRUNCATE `customer_address_entity_varchar`; TRUNCATE `customer_entity`; TRUNCATE `customer_entity_datetime`; TRUNCATE `customer_entity_decimal`; TRUNCATE `customer_entity_int`; TRUNCATE `customer_entity_text`; TRUNCATE `customer_entity_varchar`; TRUNCATE `log_customer`; TRUNCATE `log_visitor`; TRUNCATE `log_visitor_info`; ALTER TABLE `customer_address_entity` AUTO_INCREMENT=1; ALTER TABLE `customer_address_entity_datetime` AUTO_INCREMENT=1; ALTER TABLE `customer_address_entity_decimal` AUTO_INCREMENT=1; ALTER TABLE `customer_address_entity_int` AUTO_INCREMENT=1; ALTER TABLE `customer_address_entity_text` AUTO_INCREMENT=1; ALTER TABLE `customer_address_entity_varchar` AUTO_INCREMENT=1; ALTER TABLE `customer_entity` AUTO_INCREMENT=1; ALTER TABLE `customer_entity_datetime` AUTO_INCREMENT=1; ALTER TABLE `customer_entity_decimal` AUTO_INCREMENT=1; ALTER TABLE `customer_entity_int` AUTO_INCREMENT=1; ALTER TABLE `customer_entity_text` AUTO_INCREMENT=1; ALTER TABLE `customer_entity_varchar` AUTO_INCREMENT=1; ALTER TABLE `log_customer` AUTO_INCREMENT=1; ALTER TABLE `log_visitor` AUTO_INCREMENT=1; ALTER TABLE `log_visitor_info` AUTO_INCREMENT=1; -- Now, lets Reset all ID counters TRUNCATE `eav_entity_store`; ALTER TABLE `eav_entity_store` AUTO_INCREMENT=1; SET FOREIGN_KEY_CHECKS=1;
Установка нескольких версий PHP на Apache (Ubuntu)
С выходом ветки PHP 5.3 при разработке проектов мы стали ориентироваться на эту версию, к тому же клиенты стали активно обновлять свои хостинги. Однако, большинство проектов было написано под PHP 5.2.x и адаптировать их под новую версию языка не всегда представляется возможным. В связи с этим возникла необходимость установить на компьютере разработчика как минимум две версии PHP (5.2.x и 5.3.x), чтобы они работали на одном сервере Apache.
Одним из популярных способов решения этой проблемы является подключение одной версий PHP к апачу как FastCGI приложение, а другой — обычным способом — как модуль Apache.
Один мой друг написал мануал по настройке сервера таким механизмом. Он призводил установку PHP с Apache на сервер Debian. Эти же шаги легко адаптировать на любую ветку Unix. Я производил настройку Ubuntu 11.04. Итак, руководствуясь инструкцией из блога, делаем настройку.
Для начала нужно определиться, какую версию PHP ставить как модуль, а какую подключать как FastCGI. В репозитории Ubuntu 11.04 по умолчанию идет PHP версии 5.3.5. Таким образом, эту версию можно установить «стандартным» способом как модуль apache.
Теперь, установим PHP 5.2.x, а именно соберем ее из исходников. Скачать ее можно отсюда: http://us3.php.net/get/php-5.2.17.tar.bz2/from/a/mirror
Последняя версия ветки 5.2 на данный момент — 5.2.17. В результате получится файл php-5.2.17.tar.gz. Распакуем его командой:
tar xzvf php-5.2.17.tar.gz
Установим необходимые для компиляции библиотеки:
apt-get install libxml2-dev libmysqlclient-dev libcurl4-gnutls-dev libcurl4-openssl-dev libpng12-dev libjpeg62-dev
Подготовим PHP к сборке:
./configure --prefix=/opt/php5.2 \ --with-config-file-path=/opt/php5.2 \ --with-mysqli \ --with-mysql \ --with-curl \ --with-gd \ --with-jpeg \ --with-jpeg-dir \ --enable-cli \ --enable-fastcgi \ --enable-discard-path \ --enable-force-cgi-redirect
Здесь хочу отметить, что Ubuntu поругалась на отсутствие двух библиотек libpng.so и lingjpeg.so. На само деле они установлены, но сами файлы библиотек PNG и JPEG включают номер версии. В моем случае это были:
libpng12.so
libjpeg.so.62
Для устранения ошибки достаточно создать символические ссылки с нужным именем:
ln -s /usr/lib/libjpeg.so.62 libjpeg.so ln -s /usr/lib/libpng12.so libpng.so
Теперь скомпилируем и установим PHP:
make make install
Если у apache не установлен модуль для работы с FastCGI установим его:
apt-get install libapache2-mod-fastcgi a2enmod fastcgi
Для удобства активируем модуль apache mod_actions:
a2enmod actions
Теперь создадим SH-скрипт, который будет запускать CGI приложение PHP для обработки скриптов:
vi /usr/lib/cgi-bin/php52-cgi
Запишем туда, следующие строчки:
#!/bin/sh PHPRC="/opt/php5.2/" export PHPRC PHP_FCGI_CHILDREN=4 export PHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS=5000 export PHP_FCGI_MAX_REQUESTS exec /opt/php5.2/bin/php-cgi
Дадим этому скрипту права на запись:
chmod +x /usr/lib/cgi-bin/php5-cgi
Создадим конфигурационный файл для apache с настройками обработчика PHP скриптов, нужно сказать серверу, что скрипты должны обрабатываться CGI-приложение PHP 5.2:
vi /etc/apache2/php52.conf
Пропишем там следующее:
<FilesMatch "\.php"> SetHandler application/x-httpd-php5 </FilesMatch> ScriptAlias /php52-cgi /usr/lib/cgi-bin/php52-cgi Action application/x-httpd-php5 /php52-cgi AddHandler application/x-httpd-php5 .php
Вот и все, теперь достаточно в настройках виртуального хоста добавить импорт php52.conf файла и для этого хоста будет работать PHP 5.2:
<VirtualHost *.80> ........................................ Include php52.conf ......................................... </VirtualHost>
Применение Sales Rules в Magento 1.5.x
Так получилось, что последние два проекта на работе были связаны с написанием интернет магазинов на популярном движке magento. Вся сложность заключалась в том, что дизайн, полученный от клиента никак не вписывался в «стандартный» движок magento. Более того в некоторых местах необходимо изменить обыкновенную логику движка, не влезая при этом в код. В связи с этим пришлось решить ряд проблем.
Одной из таких проблем стало применение Sales Rules в ShoppingCart. Добавление, изменение количества и удаление товара было реализовано с помощью ajax. Для этого были созданы внешние PHP скрипты, использующие классы magento. Здесь возникла проблема. После отработки ajax скрипта и обновлении данных shopping cart к ценам товаров не применяются правила скидок (SalesRules). После продолжительных поисков в сторону решения наткнулся на одну статью. Оказывается, правила скидок применяются в ценам по определенным событиям специальными объектами (Observers). И работают они в backend’е и frontend’е по-разному в зависимости от того, в каком окружении (areas) происходит работа (frontend, admin).
Простейший Ajax скрипт выглядит следующим образом:
include_once 'app/Mage.php'; // подключение файла ядра $app = Mage::app(); // инициализация приложения Mage::app()->getLocale()->setLocale('nl_NL'); Mage::app()->getTranslator()->init('frontend', true); ..........................................................
Подключается основной файл ядра magento, инициализируется приложение, а далее выполняются необходимые действия. Например, для добавления товара в карту происходит следующее:
$session = Mage::getSingleton('core/session', array('name'=>'frontend')); $cart = Mage::helper('checkout/cart')->getCart(); ...................................................... $cart->addProduct($product, $qty); $session->setLastAddedProductId($product->getId()); $session->setCartWasUpdated(true); $cart->save(); ......................................................
Очевидно в этом случае, magento грузит observer’ы которые прописаны в config.xml в разделе. Таким образом, чтобы решить проблему, надо «указать magento» в каком окружении мы хотим работать:
Mage::app()->loadArea($area);
В данном случае это FRONTEND. Для этого необходимо сразу после инициализации приложения добавить строку такого вида:
Mage::app()->loadArea(Mage_Core_Model_App_Area::AREA_FRONTEND);
Удаление папок .svn из проекта в Ubuntu
По долгу службы все разрабатываемые проекты у меня хранятся в репозитории SVN. Иногда, бывает необходимо получить «чистую» копию проекта из локального репозитория. Конечно, можно воспользоваться функцией svn export, но это не всегда бывает удобно. Иногда, я просто копирую содержимое репозитория и стираю служебные папки svn. Чтобы сделать это для всего проекта в ubuntu, необходимо выполнить команду вида:
rm -rf `find . -type d -name .svn`
Програмный resize изображения
Понадобилось програмно изменить размеры изображения из своей программы. Программа работает под управлением ОС Ubuntu.
Для решения этой задачи можно воспользоваться утилитой ImageMagick. Он позволяет производить множество действий с изображением. В том числе и изменять размеры. К тому же, похоже, она входит в дистрибутив Ubuntu. По крайней мере она уже был установлена на моем Ubuntu.
Итак, чтобы изменить изображение необходимо выполнить команду:
convert -resize widthxheight source-image destanation-image
Если нужно, отресайзить изображение бех сохранения aspect ratio, можно использовать команду:
convert -geometry widthxheight! source-image destanation-image
Выполнение команд при запуске Ubuntu
После некоторого времени работы в Ubuntu появилась необходимость при запуске ОС выполнять некоторые консольные команды. Эти команды предназначены для выполнения определенных действий необходимых для работы демона. Поискав немного в Интернете, я обнаружил довольно простой способ, как это можно сделать.
Можно создать SH скрипт, поместить его в /etc/init.d/ и сконфигурировать на запуск утилитой update-rc.d.
Например,
sudo vi /etc/init.d/local.autostart
Эта команда создат файл local.autostart в /etc/init.d/. Сюда можно помещать любые терминальные команды. И этот скрипт должен начинаться с
#!/bin/sh
Теперь необходимо проставить скрипту права на запуск:
sudo chmod +x /etc/init.d/local.autostart
И наконец, сконфигурировать систему, чтобы наш скрипт запускался при загрузке ОС:
sudo update-rc.d local.autostart defaults 80
Все, при следующей загрузке системы все команды, помещенные в local.startup, будут выполнены.
IE и expression
Решив недавно задачу с применением CSS параметров max-width и max-height для Internet Exlorer’ов я очень обрадовался. И, как оказалось, весьма поспешно.
В предыдущую задачу входило поставить ограничения на максимальную ширину и высоту для изображений на странице. Соответственно, проблему можно решить поставив с CSS max-width и max-height для тэга IMG. Все отлично работает, однако, если на странице довольно много изображений, Internet Explorer повисает на прочь (у некоторых даже наблюдалось полнейшнее повисание компьютера). В поисках решения наткнулся на одну запись в дневнике: http://lusever.livejournal.com/15868.html. Оказывается:
Есть несколько особенностей у expression:
- выполняется постоянно, если не переопределить CSS–свойство;
- выполняется мгновенно;
- ключевое слово this необязательно, выражение this.style равносильно простому style;
- работают комментарии
/* */
, несмотря на то, что мы находимся внутри css; - можно использовать внешние функции или библиотеки, если они объявлены или подключены в html;
- пробелы могут вызывать ошибку, хотя такое встречается редко;
«на каждое движение мышки по странице или выполнение JavaScript–кода происходит пересчет expression. Избежать пересчета удастся, просто переопределив фильтр в начале выражения:»
.button1, .button2, .button3, .button4
{ filter: expression( runtimeStyle.filter = 'alpha(opacity='+currentStyle.opacity*100+')' ) }
/** выдержки из статьи (Автор: Палсеич)**/
Вот как оказывается не все так просто. Последовав совету, я немного позанимался с runtime Style, и все получилось. Вот, что в итоге выходит для max-width и max-height:
.good img { max-height: 140px; max-width:140px; height : expression(runtimeStyle.height = this.offsetHeight > 140 ? "140px" : "auto"); width:expression(runtimeStyle.width = this.width > 140? "140px": "auto" ); }
MAX-WIDTH в IE6
В текущем проекте на работе возникла необходимость обрезать изображение, если его ширина превышает определенное значение. При этом никак нельзя изменять параметры в верстке. Как вариант можно было на уровне скрипта PHP при генерации тэга IMG проверять размеры картинки, и в зависимости от ее размера подставлять соответствующий стиль. Однако, хочется более элегантного решения. Такое вполне можно организовать с помощью CSS.
Существует параметр max-width, который корректно обрабатывается всеми современными броузерами. Достаточно указать максимальную ширину, и изображение будет автоматически ресайзиться:
#element img {max-width:800px}
Однако для IE6 такая конструкция уже не пройдет. Погуглив немного обнаружил конструкцию, которая прилично обрабатывается даже на ранних версиях IE, начиня с 5.0:
#element img {width:expression(document.body.clientWidth > 440? "440px": "auto" );}
Подробнее об expression можно почитать здесь
Создание демона (службы) в Linux
Чего я никак не мог предположить, так это того, что на моей работе мне представится возможность или скорее появится необходимости писать что-нибудь под linux. Однако такое событие произошло. Несмотря на то, что по долгу службы я являюсь PHP-разработчиком (веб-разработчиком) мне «предложили» написать утилиту под Ubuntu для начинающегося проекта. И не просто утилиту а демона — службу под linux. Еще пару лет назад подобного рода задача могла бы испугать меня. Но только не теперь. Тем более что для разработки под Linux совсем необязательно иметь специфическое программное обеспечение. Компилятор C++ уже имеется. Однако я давно собирался попробовать кросс платформенную среду для разработки ПО Lazarus. И я решил создавать демона на FreePascal.
Итак, здесь я попытаюсь показать пример создания простейшего демона в Lazarus.
Структура основной программы выглядит также, как и обыкновенного консольного приложения:
program mydaemon; {$mode objfpc}{$H+} uses {$IFDEF UNIX}{$IFDEF UseCThreads} cthreads, {$ENDIF}{$ENDIF} SysUtils Begin End.
Основная идея при создании демона заключается в следующем: создается еще один экземпляр приложения в памяти а текущая программа завершается. Таким образом и стартует демон. Для реализации этого подхода используется функция fpFork. Она создает копию текущего процесса и возвращает:
- PID, если процесс успешно создан;
- -1, если возникла ошибка при создании процесса;
- 0, если это уже копия процесса (потомок).
Таким образом старт демона осуществляется при помощи несложных операций:
pid := fpFork; case pid of 0 : begin { we are in the child } Close(input); { close standard in } Assign(input,'/dev/null'); ReWrite(input); Close(output); { close standard out } Assign(output,'/dev/null'); ReWrite(output); Close(stderr); { close standard error } pid:=fpGetPid; end; -1 :begin WriteLn('forking error, so halt 1'); halt(1); end; else begin Halt; { successful fork, so parent dies } end;
При запуске программы мы делаем копию нашего процесса, и если это прошло успешно, завершаем работы (попадаем в ветку case else). Дочерний же процесс начинает свою работу, попадая в ветку case 0 и следует далее по коду программы. Теперь демон переходит в режим ожидания и ждет сигнала, после которого он активизируется и выполнит определенные действия. В простейшем случае это можно организовать в виде цикла:
while not bTerm do begin ...... end;
Этот цикл будет выполняться до тех пор пока не придет сигнал об уничтожении. Теперь необходимо научить демон принимать внешние сигналы от ОС. Для этого создадим функцию, которая будет обрабатывать сигналы:
procedure DoSig(sig : longint);cdecl; begin case sig of SIGTERM : bTerm := true; end; end;
И теперь перенаправим обработку сигналов на эту функцию:
{$hints off} FpsigEmptySet(zerosigs); {$hints on} { set global daemon booleans } bTerm := false; { block all signals except -TERM } sSet := $ffffbffe; ps1 := @sSet; fpsigprocmask(sig_block,ps1,nil); { setup the signal handlers } new(aOld); new(aTerm); aTerm^.sa_handler{.sh} := SigactionHandler(@DoSig); aTerm^.sa_mask := zerosigs; aTerm^.sa_flags := 0; fpSigAction(SIGTERM,aTerm,aOld)
Теперь, если наш демон получит сигнал об уничтожении sigTerm, он будет завершен. Остается придумать механизм запуска и остановки демона. Сейчас я реализовал это довольно простым способом. При запуске демона проверяется какой параметр ему передан. Если start, то форким, создаем во временной директории файл daemonname.lock и пишем туда pid демона. Если же в качестве параметра передан stop открываем файл daemonname.lock, читаем pid, убиваем процесс с таким pid, удалем lock файл. Однако, вероятно, это не лучшее решение. Поэтому предстоит еще провести исследование в этом направлении.
Полный код демона:
program mydaemon; {$mode objfpc}{$H+} uses {$IFDEF UNIX}{$IFDEF UseCThreads} cthreads, {$ENDIF}{$ENDIF} SysUtils,BaseUnix, logger, mydb, VideoRecorder, socketserver; Var { vars for daemonizing } bTerm : boolean; aOld, aTerm: pSigActionRec; ps1 : psigset; sSet : cardinal; pid : pid_t; zerosigs : sigset_t; status: integer; secs : longint; FileEngine:TLogger; err: LongInt; counter:integer; db:TMyDB; Socket:TSocketServer; CurrentQueue:TQueueInfo; { handle SIGTERM } procedure DoSig(sig : longint);cdecl; begin case sig of SIGTERM : bTerm := true; end; end; procedure RunDaemon(); var Player:TVideoRecorder; begin db:=TMyDB.Create('localhost', 'root', 'college', 'daemon'); writeln('Connecting...'); if not db.ConnectToDatabase then begin writeln('Error connection to database:'); writeln(db.getError()); FileEngine.WriteLog(db.getError); halt; exit; end; {$hints off} FpsigEmptySet(zerosigs); {$hints on} { set global daemon booleans } bTerm := false; { block all signals except -TERM } sSet := $ffffbffe; ps1 := @sSet; fpsigprocmask(sig_block,ps1,nil); { setup the signal handlers } new(aOld); new(aTerm); aTerm^.sa_handler{.sh} := SigactionHandler(@DoSig); aTerm^.sa_mask := zerosigs; aTerm^.sa_flags := 0; fpSigAction(SIGTERM,aTerm,aOld); { daemonize } pid := fpFork; case pid of 0 : begin { we are in the child } Close(input); { close standard in } Assign(input,'/dev/null'); ReWrite(input); Close(output); { close standard out } //Assign(output,'dpsoutput.log'); //ReWrite(output); Assign(output,'/dev/null'); ReWrite(output); Close(stderr); { close standard error } pid:=fpGetPid; FileEngine.Lock(pid); writeln('Daemon Started'); end; -1 :begin WriteLn('forking error, so halt 1'); halt(1); end; else begin Halt; { successful fork, so parent dies } end; end; FileEngine.WriteLog('Daemon start'); { begin processing loop } counter:=0; Player:=TVideoRecorder.Create; repeat //Some actions until bTerm; Player.Free; FileEngine.UnLock; FileEngine.WriteLog('Daemon terminated'); db.Free; end; Begin {Init File Engine} FileEngine:=TLogger.Create('test'); {**Check for superuser rights**} { if fpGetUID<>0 then begin Writeln('Error: Need superuser rights'); halt; end;} {****} if ParamStr(1)='start' then begin if FileEngine.isLockFileExists then begin WriteLn('Process already running'); halt; end; Writeln('Startin Daemon...'); RunDaemon; end else if ParamStr(1)='stop' then begin writeln('Stoping daemon'); if FileEngine.isLockFileExists then begin pid:=FileEngine.getPID; if fpkill(pid, SIGTERM) < 0 then begin err := fpGetErrno; case err of ESysEsrch: begin Writeln(stderr,'Porcces with PID='+IntToStr(pid)+' not exists'); FileEngine.UnLock; end; ESysEperm: Writeln(stderr,'Permissions to kill process denied'); end; end else begin Writeln(stderr,'Daemon stopped'); end; end else Writeln('Daemon is not running'); end else writeln('Usage: ./mydaemon start|stop|restart'); End.