<![CDATA[MageBR]]>http://blog.magebr.com/http://blog.magebr.com/favicon.pngMageBRhttp://blog.magebr.com/Ghost 3.15Tue, 19 May 2020 02:34:49 GMT60<![CDATA[New MageBR!]]>http://blog.magebr.com/new-magebr/5ea399a3c4833f6c62345d27Sat, 25 Apr 2020 02:07:06 GMT

Welcome to our new blog! For the past couple of years we have been using Drupal and before that we used Wordpress. I love maintaining servers, but this is a simple blog with text and some images. Most of the content on our blog is static.

That's why we have decided to move to Ghost blog platform, it's simple to use, simple to maintain and it's freaking FAST! And to make things much better, our blog is running 100% serverless!

Yes! This blog is running on AWS S3 and Cloudflare is our CDN provider to make sure the files are distributed across the globe. I will have an explanation of how we accomplished this in our new blog about Cloud!
Yep! We are building a new blog!

Don't get me wrong, Drupal is a VERY powerful system, but it was WAY more than what we need. Plus the constant upgrades (which is great) was too much for me to keep up and maintain a safe environment for all of you.

Our blog is much safer, there is no data being hosted by us at all, every single page in this blog is a html file. We do have some content like Disqus and Mailchimp for emails (don't forget to subscribe to our newsletter).

We have a LOT to do, translation is not perfect (we also have the Portuguese version), it takes some time to generate the html files after I write a new post, but I still think this is the right move.

I have a ton of material, so make sure you subscribe to our newsletter because I plan to release some great tutorials!

]]>
<![CDATA[MageBR News]]>http://blog.magebr.com/magebr-news/5ea39959c4833f6c62345d19Wed, 15 Apr 2020 02:27:00 GMT

This post will be quick.

In the near future I will be migrating our MageBR to a new blog tool and new environment. I have used Wordpress in the past and I have decided to not go back.
I also have used Drupal for the past 3 years and as much as I love it, I think it's time to move to a more simpler solution. I have been looking at a few other solutions and been testing one in particular. I would like to reveal only when the time comes, but this time we won't delete all our posts like we did moving from Wordpress to Drupal.

I will make sure all blog posts are intact and I believe that will allow me more time to write new tutorials. I will also open a new blog about cloud in general, I will post the link to the new blog once we release the new version of MageBR.

Keep an eye in the next couple of weeks for something new!

]]>
<![CDATA[Issues when deploying static content on EFS]]>http://blog.magebr.com/issues-when-deploying-static-content-on-efs/5ea39922c4833f6c62345d0dWed, 24 Apr 2019 22:29:00 GMT

And this article is another article about how annoying Magento 2 can be and how it wasn't built to be a scalable application.

Anyway, we have been getting several errors when running a static content deploy with the --jobs option:

magento setup:static-content:deploy --jobs 5

Right now each store only has one theme being used, but they are dependent on a base custom theme and also Magento blank theme. So once you run the command without --jobs it will deploy the blank theme, then the base theme and finally your theme.

But if you have several themes or even one on EFS, you will have some bottleneck as EFS is not very fast and it's all done over a NFS connection, so it won't be as fast as deploying on a local SSD and that's when you add the --jobs option to the static content deploy. But on EFS if you have several files to be deployed on each theme, it might take several minutes and that's when you get this nice error:

In Queue.php line 366:

Error while waiting for package deployed: 42; Status: 0


setup:static-content:deploy [-f|--force] [-s|--strategy [STRATEGY]] [-a|--area [AREA]] [--exclude-area [EXCLUDE-AREA]] [-t|--theme [THEME]] [--exclude-theme [EXCLUDE-THEME]] [-l|--language [LANGUAGE]] [--exclude-language [EXCLUDE-LANGUAGE]] [-j|--jobs [JOBS]] [--symlink-locale] [--content-version CONTENT-VERSION] [--refresh-content-version-only] [--no-javascript] [--no-css] [--no-less] [--no-images] [--no-fonts] [--no-html] [--no-misc] [--no-html-minify] [--] [<languages>...]

There is an issue open on Magento 2 repository and I even added a comment there: https://github.com/magento/magento2/issues/21852

This issue might be caused by this: https://github.com/magento/magento2/blob/2.3-develop/app/code/Magento/Deploy/Process/Queue.php#L391
And the function for the timeout: https://github.com/magento/magento2/blob/2.3-develop/app/code/Magento/Deploy/Process/Queue.php#L372
All points to const DEFAULT_MAX_EXEC_TIME = 400;
https://github.com/magento/magento2/blob/2.3-develop/app/code/Magento/Deploy/Process/Queue.php#L32

So it looks like if the static code deploy runs for over 400 seconds, it will fail. But Magento has fixed their ECE tool to add an option to increase the timeout: https://github.com/magento/ece-tools/pull/418
But for now this is not fixed on the Magento Open Source or Commerce edition.

So what can you do? Well, for now what we are going to do is to store the theme information for a store somewhere and when a code is deployed we get the theme and deploy only the theme assigned to a store. So for example if MageBR/mytheme is assigned to my store, I will run:

magento setup:static-content:deploy --theme MageBR/mytheme

It will speed things up and not fail as we can see below:

Deploy using quick strategy
frontend/Magento/blank/en_US 2456/2456 ============================ 100% % 1 min
frontend/MageBR/base/en_US 2765/2765 ============================ 100% % 2 mins
frontend/MageBR/mytheme/en_US 2765/2765 ============================ 100% % 2 mins

Execution time: 483.77401208878

But it would still be great to run that command with jobs to speed up the process even more. We should be safe to run the static content deploy with the theme and jobs option if it doesn't take too long, but it would be nice to have an option to increase the timeout directly in the command line.

I hope you liked this article and please leave your comment and don't forget to share!

]]>
<![CDATA[Running Magento 2 Crons With Queue Consumers on Kubernetes or Docker]]>http://blog.magebr.com/running-magento-2-crons-with-queue-consumers-on-kubernetes-or-docker/5ea398d3c4833f6c62345d00Sun, 21 Apr 2019 21:01:00 GMT

Let me start saying that this article will be quick and simple, but it was a headache to find out the issues we encountered with running Magento crons with queue consumers on Kubernetes (or even Docker).
Configuring the cron is pretty simple and Magento has a nice documentation here: https://devdocs.magento.com/guides/v2.3/config-guide/cli/config-cli-subcommands-cron.html

But the problem is not just running the crons, it's if you run crons while you have queues enabled using RabbitMQ, which will require to use the consumers. You can read more about the queues and consumers here: https://devdocs.magento.com/guides/v2.3/config-guide/mq/rabbitmq-overview.html

Let's say you added a new module that will consume the queues or you just added to your app/etc/env.php:

'queue' =>
array (
'amqp' =>
array (
'host' => 'rabbitmq.example.com',
'port' => '11213',
'user' => 'magento',
'password' => 'magento',
'virtualhost' => '/',
'ssl' => true,
'ssl_options' => [
'cafile' => '/etc/pki/tls/certs/DigiCertCA.crt',
'certfile' => '/path/to/magento/app/etc/ssl/test-rabbit.crt',
'keyfile' => '/path/to/magento/app/etc/ssl/test-rabbit.key'
],
),
),

Or you added the piece of code below during installation:

--amqp-host="<hostname>" --amqp-port="5672" --amqp-user="<user_name>" --amqp-password="<password>" --amqp-virtualhost="/"

Good, now Magento will use consumers to work the queues. Now we encounter two problems. The first one is that every time a cron runs, it will create background processes with the consumers, for example:

$ kubectl exec -n my-store magento-75788af261-adwdc -c magento ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 18048 1932 ? Ss 14:13 0:00 /bin/bash /setup.sh
root 16 0.0 0.0 4284 588 ? S 14:13 0:00 /bin/sh /usr/sbin/apache2ctl -D FOREGROUND
root 18 0.0 0.0 274652 19168 ? S 14:13 0:00 /usr/sbin/apache2 -D FOREGROUND
www-data 20 0.9 0.2 344228 90216 ? S 14:13 1:50 /usr/sbin/apache2 -D FOREGROUND
www-data 24 0.9 0.2 344232 86344 ? S 14:13 1:41 /usr/sbin/apache2 -D FOREGROUND
www-data 59 0.0 0.2 291524 94128 ? S 14:15 0:03 /usr/local/bin/php /var/www/html/bin/magento queue:consumers:start commonErrorManagement --pid-file-path=commonErrorManagement-magento76799dc865rglqw.pid --max-messages=10000
www-data 61 0.0 0.2 291524 94024 ? S 14:15 0:03 /usr/local/bin/php /var/www/html/bin/magento queue:consumers:start ping --pid-file-path=ping-magento76799dc865rglqw.pid --max-messages=10000
www-data 63 0.0 0.3 317288 118348 ? S 14:15 0:05 /usr/local/bin/php /var/www/html/bin/magento queue:consumers:start productConsumer --pid-file-path=productConsumer-magento76799dc865rglqw.pid --max-messages=10000
www-data 65 0.0 0.2 293740 95408 ? S 14:15 0:03 /usr/local/bin/php /var/www/html/bin/magento queue:consumers:start stockConsumer --pid-file-path=stockConsumer-magento76799dc865rglqw.pid --max-messages=10000
www-data 67 0.0 0.2 293752 95652 ? S 14:15 0:03 /usr/local/bin/php /var/www/html/bin/magento queue:consumers:start productReturnUpdatedConsumer --pid-file-path=productReturnUpdatedConsumer-magento76799dc865rglqw.pid --max-messages=10000
www-data 69 0.0 0.3 293704 97224 ? S 14:15 0:03 /usr/local/bin/php /var/www/html/bin/magento queue:consumers:start refundUpdatedConsumer --pid-file-path=refundUpdatedConsumer-magento76799dc865rglqw.pid --max-messages=10000
www-data 71 0.0 0.3 317288 118240 ? S 14:15 0:04 /usr/local/bin/php /var/www/html/bin/magento queue:consumers:start orderCancellation --pid-file-path=orderCancellation-magento76799dc865rglqw.pid --max-messages=10000
www-data 73 0.0 0.3 317288 118116 ? S 14:15 0:04 /usr/local/bin/php /var/www/html/bin/magento queue:consumers:start orderGetNotifications --pid-file-path=orderGetNotifications-magento76799dc865rglqw.pid --max-messages=10000
www-data 75 0.0 0.3 317288 118440 ? S 14:15 0:04 /usr/local/bin/php /var/www/html/bin/magento queue:consumers:start orderResendNotification --pid-file-path=orderResendNotification-magento76799dc865rglqw.pid --max-messages=10000
www-data 77 0.0 0.3 317292 118508 ? S 14:15 0:04 /usr/local/bin/php /var/www/html/bin/magento queue:consumers:start orderConsumer --pid-file-path=orderConsumer-magento76799dc865rglqw.pid --max-messages=10000
www-data 79 0.0 0.2 291524 93944 ? S 14:15 0:03 /usr/local/bin/php /var/www/html/bin/magento queue:consumers:start orderUpdatedConsumer --pid-file-path=orderUpdatedConsumer-magento76799dc865rglqw.pid --max-messages=10000
www-data 82 0.0 0.3 295756 97992 ? S 14:15 0:03 /usr/local/bin/php /var/www/html/bin/magento queue:consumers:start customerShipmentDoneConsumer --pid-file-path=customerShipmentDoneConsumer-magento76799dc865rglqw.pid --max-messages=10000
www-data 84 0.0 0.2 291656 93784 ? S 14:15 0:03 /usr/local/bin/php /var/www/html/bin/magento queue:consumers:start orderLineCancelledConsumer --pid-file-path=orderLineCancelledConsumer-magento76799dc865rglqw.pid --max-messages=10000
www-data 86 0.0 0.2 291656 94968 ? S 14:15 0:03 /usr/local/bin/php /var/www/html/bin/magento queue:consumers:start shippingAddressUpdated --pid-file-path=shippingAddressUpdated-magento76799dc865rglqw.pid --max-messages=10000
www-data 92 0.0 0.3 317292 119656 ? S 14:15 0:04 /usr/local/bin/php /var/www/html/bin/magento queue:consumers:start serviceBusDiscover --pid-file-path=serviceBusDiscover-magento76799dc865rglqw.pid --max-messages=10000
www-data 94 0.0 0.3 317292 118524 ? S 14:15 0:04 /usr/local/bin/php /var/www/html/bin/magento queue:consumers:start async.operations.all --pid-file-path=async.operations.all-magento76799dc865rglqw.pid --max-messages=10000
www-data 178 1.6 0.2 336144 81796 ? S 16:24 0:53 /usr/sbin/apache2 -D FOREGROUND
www-data 179 1.2 0.2 336220 83776 ? S 16:24 0:42 /usr/sbin/apache2 -D FOREGROUND
www-data 195 1.3 0.2 336160 81964 ? S 16:27 0:42 /usr/sbin/apache2 -D FOREGROUND
www-data 198 1.0 0.2 333196 74692 ? S 16:30 0:31 /usr/sbin/apache2 -D FOREGROUND
www-data 199 1.0 0.2 333196 74696 ? S 16:30 0:31 /usr/sbin/apache2 -D FOREGROUND
www-data 200 1.0 0.2 333212 74708 ? S 16:30 0:29 /usr/sbin/apache2 -D FOREGROUND
www-data 201 1.3 0.2 333140 74632 ? S 16:30 0:40 /usr/sbin/apache2 -D FOREGROUND
www-data 210 1.1 0.2 333140 74636 ? S 16:30 0:34 /usr/sbin/apache2 -D FOREGROUND
root 440 0.0 0.0 36640 2712 ? Rs 17:19 0:00 ps aux

As you can see from above there are several consumers that started, they are all from Magento. If you have any custom queue, it will add the consumer to the list as well. That's when the second issue comes up.
Magento has on its documentation that each consumer processes 1000 messages and then terminates: https://devdocs.magento.com/guides/v2.3/config-guide/mq/manage-message-queues.html#configuration

max_messages - the maximum number of messages for each consumer that must be processed before consumer terminate, by default is 1000. If it is 0, then the consumer never stops working.

But as you can see above it is set to 10000 and we can confirm on their repo: https://github.com/magento/magento2/blob/2.3-develop/app/code/Magento/MessageQueue/Model/Cron/ConsumersRunner.php#L114

All right, no big deal if you are running the cron on a single or multi servers. But once you start the cron, it has these background processes that will only end if the queue has completed 10000 messages (by default). The cron will run until Kubernetes timesout (and the default is one hour).
We could then set --max-messages to 1, but the way Magento works it will ALWAYS wait for that one, instead of just checking if queue is empty or not and finish if it's empty. There is an issue opened right now on their repository, but no sing that they will ever fix it: https://github.com/magento/magento2/issues/17951

So what to do?

Well, for now we just decided to run Supervisord on its own pod and the consumers inside Supervisord. And don't remember to disable the consumers from running during cron on env.php file:

...
'cron_consumers_runner' => array(
'cron_run' => false
),
...

I would just hope that Magento would consider building its application DevOps friendly.
If you managed to get this working a different way, don't forget to add your comment below.

]]>
<![CDATA[Configure Catalog Search Engine with Magento CLI]]>http://blog.magebr.com/configure-catalog-search-engine-with-magento-cli/5ea39869c4833f6c62345ce4Fri, 15 Feb 2019 17:44:00 GMT

Since we installed Magento 2.3 we started receiving a notice that Catalog Search using MySQL will go away as it is deprecated. This is ok as we are going to use Elasticsearch anyway, but we have to setup Magento to connect to the correct hostname when the container starts and not manually in the admin or using CLI, but the CLI is helpful as we can run scripts after the container is up and running.

If you want more information on how to do via the admin you will find the tutorial in the Magento Docs: https://devdocs.magento.com/guides/v2.3/config-guide/elasticsearch/configure-magento.html

You just really need to configure two settings via the CLI and they are:

magento config:set catalog/search/engine 'elasticsearch6'
magento config:set catalog/search/elasticsearch6_server_hostname 'elasticsearch-hostname.env'

The first option is the engine which it can be: mysql, elasticsearch, elasticsearch5 or elasticsearch6.
The second option is the server hostname. You can either use IP or a DNS. We have Magento set up on a Kubernetes cluster, so elasticsearch has its own container running and the hostname is based on the store name, so you can also use variables:

magento config:set catalog/search/elasticsearch6_server_hostname "elasticsearch-$MY_VARIABLE.env"

Just pay attention that now it needs double quotes. You can also set other configurations:

magento config:set catalog/search/elasticsearch6_server_port '9200'
magento config:set catalog/search/elasticsearch6_index_prefix 'magento2'
magento config:set catalog/search/elasticsearch6_enable_auth '0'
magento config:set catalog/search/elasticsearch6_server_timeout '15'

These are the default settings for elasticsearch and elasticsearch6, so change what you need to.

I hope you find this article helpful and see you on the next one.

]]>
<![CDATA[Upload download link from orders]]>http://blog.magebr.com/upload-download-link-from-orders/5ea39831c4833f6c62345cd6Thu, 27 Dec 2018 01:20:00 GMT

If you create a pre-order downloadable product (you will need some module for that) and let's say you still don't have the link or file that will be used in the product, but you need to create the pre-order product and allow users to buy right now. Most likely you will ended up adding some link like "http://TBD/" or just write "TBD" or upload some file that is not the right one as customers still can't buy. This also applies to backorders or regular orders that for some reason you want customers to buy, but the link/file won't be available right away because you don't have the final link/file.

It also applies in case you need to update a link/file as it might change and you need all your customers to have the updated link/file. Unfortunately Magento doesn't update the orders with the new links/files and this is VERY annoying when you have a lot of orders like these examples. For that I created a simple module that will do just that. When you update the link/file in the product it will also update ALL orders for that product.

This is the table responsible for the purchased downloadable links: downloadable_link_purchased_item
So I did a test order on my local environment and if I check the link_url from that table, I will see the link configured in the product:

SELECT link_url FROM downloadable_link_purchased_item;
+----------------------+
| link_url |
+----------------------+
| http://tbd/somefile.zip |
+----------------------+
1 row in set (0.00 sec)

And then we can confirm that this link is "linked" to a product:

SELECT product_id, sku, link_url
-> FROM downloadable_link
-> INNER JOIN catalog_product_entity
-> WHERE entity_id = 3;
+------------+-----------------+----------------------+
| product_id | sku | link_url |
+------------+-----------------+----------------------+
| 3 | digital_product | http://tbd/somefile.zip |
+------------+-----------------+----------------------+

And we can confirm there is an order for that product (sorry for the queries, it's just so you can visualize):

SELECT c.product_id, b.sku, c.link_url, b.order_id
-> FROM downloadable_link c
-> INNER JOIN catalog_product_entity a
-> INNER JOIN sales_flat_order_item b
-> WHERE entity_id = 3;
+------------+-----------------+----------------------+----------+
| product_id | sku | link_url | order_id |
+------------+-----------------+----------------------+----------+
| 3 | digital_product | http://tbd/somefile.zip | 1 |
+------------+-----------------+----------------------+----------+

So I am going to do is update the link url from somefile.zip to myrealfile.zip in the Magento admin and we will see that nothing happens to that order from table downloadable_link_purchased_item.
First let's check the downloadable_link to make sure link was updated:

SELECT link_url FROM downloadable_link WHERE product_id = 3;
+---------------------------+
| link_url |
+---------------------------+
| http://tbd/myrealfile.zip |
+---------------------------+

Ok, so the product has the new link. Now let's see the orders:

SELECT item_id, link_url FROM downloadable_link_purchased_item;
+---------+-------------------------+
| item_id | link_url |
+---------+-------------------------+
| 1 | http://tbd/somefile.zip |
+---------+-------------------------+

Well, it looks like it still has the old link, but any new order will use myrealfile.zip. But it would be nice if any updated link or file would also update older orders. And that's why I created module MageBR_UpdateLinksPurchased and it's free for use. You can download latest version here: https://github.com/CajuCLC/MageBR_UpdateLinksPurchased/releases/latest
Or if you would like to contribute and make it better: https://github.com/CajuCLC/MageBR_UpdateLinksPurchased

After installing the module and refreshing caches, let's update the downloadable product again, now we will change link from myrealfile.zip to updatedlink.zip.
Let's check if the product has the new link:

SELECT link_url FROM downloadable_link WHERE product_id = 3;
+----------------------------+
| link_url |
+----------------------------+
| http://tbd/updatedlink.zip |
+----------------------------+

And the order item should also contain the new link:

SELECT item_id, link_url FROM downloadable_link_purchased_item;
+---------+----------------------------+
| item_id | link_url |
+---------+----------------------------+
| 1 | http://tbd/updatedlink.zip |
+---------+----------------------------+

There we are, all older orders will always have the updated link or file. BUT we are not done yet. If you get an error saying you can't save the product because order id can't be null, it means that you have orders that were deleted instead of canceled. When you delete an order it leaves all products hanging with no order (NULL). You can check by running these MySQL queries:

SELECT * FROM downloadable_link_purchased WHERE order_id IS NULL;
SELECT * FROM downloadable_link_purchased_item WHERE order_item_id IS NULL;

If any of the queries return some values, you will need to remove the those hanging orders and order items. Most of the time it won't be an issue, but please create a backup and make sure you can perform the next queries as they are destructive and we are not responsible for any issues:

DELETE FROM downloadable_link_purchased_item WHERE order_item_id IS NULL;
DELETE FROM downloadable_link_purchased WHERE order_id IS NULL;

And that's it, everything should be working now.

If you liked this tutorial/module please don't forget to share.

]]>
<![CDATA[Upgrade Magento to version 1.9]]>http://blog.magebr.com/upgrade-magento-to-version-1-9/5ea397ffc4833f6c62345ccbFri, 30 Mar 2018 02:25:00 GMT

It would be great if you had a button in the admin to do the update, but it's not that simple. You may be thinking, "but what about Magento Connect?" He's not that efficient, believe me. Today in this tutorial I will explain how to upgrade your Magento store. This tutorial has been tested with version 1.5.1.0 to 1.9.0.1.

For this tutorial you will need:

* SSH access to server
* Shell command knowledge
* Knowledge of Magento
* Knowledge of MySQL

If you do not have the necessary knowledge STOP now and hire someone to do your update. This tutorial CAN and WILL break your store if you do not know what you are doing. Any problem caused by this tutorial is not the responsibility of MagentoBR and there is no guarantee of operation or support!
We recommend that the upgrade be done in the development environment and not in the production environment! Run multiple tests in your development environment before you upgrade your production environment.
First of all you need to BACKUP!

For this step you need to check the path to your store.
My example: /var/www/vhosts/store.com
Open the folder /var/www/vhosts:

cd /var/www/vhosts

Now that we are going to save the entire store.com folder:

tar -cvf backup_store.tar store.com

Now we have the backup of the files, we need to make a backup of the database. You need to know the name of the database you use, for our example will be: store_magento.
Run the following command (you will need to enter the password):

mysqldump -u root -p store_magento > store_magentosql

Now that we have the backup we will update Magento. First open the folder where Magento is.

cd /var/www/vhosts/store.com

Our second step is to download Magento 1.9:

wget http://www.magentocommerce.com/downloads/assets/1.9.0.1/magento-1.9.0.1.tar.gz
tar xvf magento-1.9.0.1.tar.gz

Let's open the .htaccess file and change the maximum memory it can use and the maximum execution time of PHP. Be very careful with numbers, decrease or increase if necessary.

php_value memory_limit 2048M
php_value max_execution_time 90000

Now let's clear all caches and update apache / nginx. If you use Ubuntu / Debian use apache2, in the example we use CentOS / RHEL. If you have Varnish add another line with Varnish.

rm -rf var/cache/* var/session/* var/locks/* var/full_page_cache/* tmp/*
rm -rf downloader/pearlib/cache/* downloader/pearlib/download/*
service httpd restart

Let's now remove the downloader and app/design/frontend/base folder. We will remove these files so that you have no problems with files that have changed. Run the command:

rm -rf downloader
rm -rf app/design/frontend/base

Our next step is to copy the folders we removed directly from the new version, run the command:

cp -a magento/downloader .
cp -a magento/app/design/frontend/base/ app/design/frontend/

Let's also use the mage file of the new version. Execute:

cp magento/mage .
chmod 755 ./mage

Before doing the upgrade we need to make sure that Connect is set to Stable. Run the command:

./mage config-set preferred_state stable

Now let's copy all the new Magento files. To make sure all files have been copied, let's run the same command 3 times. Run the following command:

for i in {1..3}; do yes | cp -Rf magento/* .; done

Now we will do the update using mage, to do this execute the command:

./mage mage-setup .
./mage sync --force
./mage install http://connect20.magentocommerce.com/community Mage_All_Latest --force

After finishing the update we will clear the cache and change the permission of the mage file. Execute:

rm -rf var/cache/* var/session/* var/locks/* var/full_page_cache/* tmp/*
rm -rf downloader/pearlib/cache/* downloader/pearlib/download/*
chmod 755 mage

Let's update store contents. You may encounter an error in this step, ignore and continue with the tutorial. Run the command:

php shell/indexer.php reindexall

Be calm, we have a long road still. Let's now upgrade the modules that are not the base of Magento.

./mage upgrade-all --force

Now that we've finished updating the Magento files, let's set the correct permissions:

find . -type f -exec chmod 644 {} \;
find . -type d -exec chmod 777 {} \;
chmod 755 mage
chmod o+w var var/.htaccess app/etc
chmod -R o+w media
chmod -R 777 var/package var/locks var/report var/export downloader

Remember to update the user permission in the Magento folder. In this example I will use apache (CentOS / RHEL). For Ubuntu / Debian use www-data:

chown -R apache:apache *

There, the ARCHIVES update is complete. The database is missing. This part is automated, you just need to open your store.
Soon you will see that the store will not open, only read and nothing. This means that the database is being updated. This step may take VARIOUS minutes and you will have to be patient.
I recommend that you go watch TV, play something, eat, whatever. For you to see if something is happening in your database you can run the command:

watch -n 5 mysqladmin -p processlist

If an error occurs, as it happened to me, the error will appear on the homepage. Usually there is some table in the database that does not exist, this information will be in error. We need to create this table in the database, but we need to know what the structure is. To do so, visit the following site that contains all the tables: http://www.magereverse.com/index/magento-sql-structure/version/1-7-0-2

Do the search for the table that is in the error and copy the part referring to the table. Now using some MySQL administration system (phpMyAdmin) you can create the required table using the information that was copied.
After creating the table you can update the homepage of your store and you will see that the page will read again without opening. If the store stops working, check the database if something is happening. It may be that the maximum execution time of PHP is finished.

Once you have been updated you will need to remove some files from Google Checkout due to an error that happens when opening the admin, run the command below:

rm -rf app/code/core/Mage/GoogleCheckout/etc/adminhtml.xml
rm -rf app/code/core/Mage/GoogleCheckout/etc/system.xml
rm -rf app/code/core/Mage/GoogleCheckout/etc/wsdl.xml
rm -rf app/code/core/Mage/GoogleCheckout/etc/wsi.xml

We will update the indexes again and this time no errors should appear:

php shell/indexer.php reindexall

Your store should already be without problems to access and the admin should already be working. But soon at the beginning of this tutorial we remove the app/design/frontend/base folder and several modules place their files here. You will need to upload your module files here.

Source: http://duntuk.com/magento-upgrade

I hope this tutorial helped you upgrade your store.

]]>
<![CDATA[Installing PHP-FPM with Apache]]>http://blog.magebr.com/installing-php-fpm-with-apache/5ea397bac4833f6c62345cc0Fri, 30 Mar 2018 02:20:00 GMT

Right after the installation of Magento we have a large amount of PHP files:

find . -type f -name "*.php" | wc -l

Total 8.112.

Some reasons to use PHP-FPM with Apache:

  • You want the performance and flexibility of FPM;
  • You want to run PHP as the user who owns the code (not Apache) to avoid permission problems;
  • You need to use .htaccess files;
  • You prefer the support and experience with Apache and do not want to use nginx.

Let's now install, the tutorial was done with RHEL / CentOS, but can be changed to any distro.

Installing FPM:

yum install php-fpm

By default you will have FPM running as apache and configured in the file /etc/php-fpm.d/www.conf.You may need to configure multiple files per domain and user so that you do not have permission problems.
Let's configure the FPM pool to run over the socket instead of TCP over port 9000.
Also configure the user and permission for the socket, usually the same as the sFTP user.

Execute the commands below:

cd /etc/php-fpm.d
cp www.conf usuario.conf
mv www.conf www.conf.disabled
vim usuario.conf

Make the following changes:

;listen = 127.0.0.1:9000
listen = /dev/shm/usuario-php.sock

listen.owner = usuario
listen.group = apache
listen.mode = 0660

user = usuario

pm.max_children = 100
pm.start_servers = 35
pm.min_spare_servers = 35

php_admin_value[error_log] = /var/log/php/dominio.com-error.log
php_admin_flag[log_errors] = on
php_admin_value[memory_limit] = 512M

Installing mod_fastcgi:

We need to install fastcgi to communicate with FPM, there are not many packages available so we have the option of compiling or using a version made by Nexcess where a but of Magento with CGI has been fixed where it duplicates headers causing a 500 error:
… aborted: error parsing headers: duplicate header ‘Content-Type’

Let's use the Nexcess rpm to do the installation, execute the commands below:

yum -y install httpd-devel rpm-build -y
mkdir -p /~/fastcgi
cd /~/fastcgi
wget http://pubfiles.nexcess.net/misc/mod_fastcgi-2.4.6-3.el6.src.rpm
rpmbuild --rebuild mod_fastcgi-2.4.6-3.el6.src.rpm
rpm -ivh /~/rpmbuild/RPMS/x86_64/mod_fastcgi-2.4.6-3.el6.x86_64.rpm
rm -f /etc/httpd/conf.d/mod_fastcgi.conf

Configuring mod_fastcgi:
Create the file/etc/httpd/conf.d/mod_fastcgi.conf with the content below. Change where user is typed to the user you configured to run the code. For multiple pools, add more FastCGIExternalServer directives.

LoadModule fastcgi_module modules/mod_fastcgi.so
<IfModule mod_fastcgi.c>
Alias /php5.fcgi /var/www/php5.fcgi

FastCGIExternalServer /var/www/php5.fcgi -socket /dev/shm/someuser-php.sock -flush -idle-timeout 1800

AddType application/x-httpd-fastphp5 .php
Action application/x-httpd-fastphp5 /php5.fcgi
</IfModule>

Disable mod_php:
Run the following command to disable mod_php:

mv /etc/httpd/conf.d/php.conf /etc/httpd/conf.d/php.disabled
echo "# mod_php disabled, using mod_fastcgi with PHP-FPM instead" >> /etc/httpd/conf.d/php.conf

PHP-FPM is very good with the use of memory. When your site has multiple visits to the site, run the following command:

for pid in $(ps aux | grep fpm | grep "pool www" | awk '{print $2}'); do pmap -d $pid | tail -1 ; done | sed 's/K//' | awk '{sum+=$4} END {print sum/NR/1024}'

The command will show the average usage in MB of each FPM process for the www pool. Change to the pool with the name that you created to have the correct answer. This way you can predict the memory usage of your PHP.

]]>
<![CDATA[Present Quantity of Available Products]]>http://blog.magebr.com/present-quantity-of-available-products/5ea39557c4833f6c62345c87Fri, 30 Mar 2018 02:17:00 GMT

For simple products, there are a few ways to do this and I'll show you 2.

The first way would be to change the file app/design/frontend/base/default/template/catalog/product/view/type/default.phtml

Search for:

<p class="availability in-stock"><?php echo $this->__('Availability:') ?> <span><?php echo $this->__('In stock') ?></span></p>

Replace with:

<p class="availability in-stock"><?php echo $this->__('Availability: ') ?><?=(int)Mage::getModel('cataloginventory/stock_item')->loadByProduct($_product)->getQty()?><?php echo $this->__(' in stock.') ?></p>

It will look like this:

Present Quantity of Available Products

The second way would be to change the same file.

Search for:

<p class="availability in−stock">__('Availability:') ?> __('In stock') ?>

Just below the code add:

<div class="divider"></div>
<div class="inventory-qty">
<!--inv-qty--><br />
<?php<br />
$__manStock = $_product->getStockItem()->getManageStock();<br />
$__invAmt = (int)Mage::getModel('cataloginventory/stock_item')->loadByProduct($_product)->getQty();<br />
if ($__manStock > 0)<br />
{<br />
echo $this->__("Somente $__invAmt disponiveis em estoque");<br />
}<br />
?>
</div>

It will look like this:

Present Quantity of Available Products

To change grouped files need to change some more codes, but nothing too complicated.
Let's edit the file: app/design/frontend/base/default/template/catalog/product/view/type/grouped.phtml

Search for:

<td class="a-center">
<?php if ($_item->isSaleable()) : ?><br />
<input name="super_group[<?php echo $_item->getId() ?>]" value="<?php echo $_item->getQty()*1 ?>" type="text" class="input-text qty" /><br />
<?php else: ?><br />
<?php echo $this->__('Out of stock.') ?><br />
<?php endif; ?>
</td>

Now enter above this code the following code:

<td class="a-center">
<?php if($_product->isSaleable()): ?><br />
<?= (int) Mage::getModel('cataloginventory/stock_item')->loadByProduct($_item)->getQty()?><br />
<?php else: ?><?php echo $this->__('Out of Stock') ?><?php endif; ?>
</td>

Lastly, look for:

<thead>
<tr>
<th><?php echo $this->__('Product Name') ?></th>
<th class="a-right"><?php echo $this->__('Price') ?></th>
<p> <?php if ($_product->isSaleable()): ?></p>
<th class="a-center"><?php echo $this->__('Qty') ?></th>
<p> <?php endif; ?><br />
</tr>
</thead>

And replace for:

<thead>
<tr>
<th><?php echo $this->__('Product Name') ?></th>
<th class="a-left"><?php echo $this->__('Price') ?></th>
<th class="a-right"><?php echo $this->__('Availability') ?></th>
<p> <?php if ($_product->isSaleable()): ?></p>
<th class="a-center"><?php echo $this->__('Qty') ?></th>
<p> <?php endif; ?><br />
</tr>
</thead>

Okay, now the grouped products will look like this:

Present Quantity of Available Products

I hope you enjoyed the tutorial and leave your comments!

]]>
<![CDATA[Keeping Your Admin Safe]]>http://blog.magebr.com/keeping-your-admin-safe/5ea39503c4833f6c62345c78Fri, 30 Mar 2018 02:12:00 GMT
  • Use a strong password

Keeping Your Admin Safe

A strong password can protect you from intruders, remember to use a multi-character password, blending lowercase with citation, number and punctuation. I recommend using the Strong Password Generator website: http://strongpasswordgenerator.com/
No use of passwords containing dates, known names or easy words.

  • Do not use your password in another location.

It is recommended to have a different password for each location, never repeat your password elsewhere.

  • Do not use the default /admin path.

By default Magento uses the / admin path to access the administrative panel. This path is very easy and will always be used by hackers or bots to access your admin.
You can change this path by opening the app/etc/local.xml file and by locating:

<frontName><![CDATA[admin]]></frontName>

Change to a name where only you know, something like: supersecretadminpath

  • Use unknown email.

Magento has a function to recover the password by email and this can be very useful and also very dangerous.
If your email has been compromised, your admin may be at great risk as well.
When registering accounts with access to the administrative panel use emails that will not be easily recognized or that are not published on the site or elsewhere.

  • SSL.

Use secure connection in administrative panel, it will be more security against attacks.
To enable SSL in admin, go to SYSTEM -> Configuration -> WEB -> HTTPS.
In BASE URL verify that the link is with https.
Now check the USE HTTPS ON ADMIN option instead.

  • Two-factor authentication.

Unfortunately today having an admin insurance is not enough. To further improve your admin's protection, use two-factor authentication.
This type of authentication will prompt you for a code only after you have entered your password correctly. Typically this code is generated by an application on your phone or a device that generates a token.
Some modules:

  1. Rublon – Free: http://www.magentocommerce.com/magento-connect/rublon.html
  2. Two-Factor Authentication – Paid: http://www.magentocommerce.com/magento-connect/two-factor-authentication.html

There are other forms of security like limiting IP access, but I have decided not to put this tutorial since IPs in Brazil usually change frequently and this can be a problem for anyone who wants to access the administrative panel of another location.
If you have any other tips, do not hesitate to comment below.

]]>
<![CDATA[Fixing Error “[unknown object].fireEvent()”]]>http://blog.magebr.com/fixing-error-unknown-object-fireevent/5ea39499c4833f6c62345c6bFri, 30 Mar 2018 02:09:00 GMT
Fixing Error “[unknown object].fireEvent()”

Many people have already encountered this error in Magento when opening a client's registry or creating an order by admin. This is the error:

error: error in [unknown object].fireEvent(): 
event name: address_country_changed 
error message: zipElement.up(...).down(...) is undefined

This is how we can solve this:

  • Open file: app/design/adminhtml/default/default/template/directory/js/optional_zip_countries.phtml
  • Search for function setPostcodeOptional(zipElement, country)
  • Find the line below:
    zipElement.up(1).down('label > span.required').hide();

     

  • Replace for the code below:
    var zipElementLabel = zipElement.up(1).down('label > span.required');
    if (zipElementLabel)
        zipElementLabel.hide();

     

  •  In the same file find the line below:
    zipElement.up(1).down('label > span.required').show();

     

  •  And replace for the following code:
    var zipElementLabel = zipElement.up(1).down('label > span.required');
    if (zipElementLabel)
        zipElementLabel.show();

     

Save the file, refresh cache and you will see this error is gone.

Source: http://premius.net/blog/php/116-fixing-magento-error-error-in-unknown-object-fireevent.html

]]>
<![CDATA[Extension Error When Exporting Report in XML or CSV]]>http://blog.magebr.com/new-relic-bug-email-transicional/5e9c0bfe6d58ee6404277983Fri, 30 Mar 2018 02:05:00 GMT

Hello everyone, we have another new tutorial for you to fix a possible problem in your store.

Have you ever come across the problem of when exporting your report in XML or CSV the file comes with an extension like this:

invoiced.csv-, attachment
invoiced.xml-, attachment

Well, this is quite common for you to use some versions of Magento and also some versions of some browsers. The solution is quite simple.

Open file: app/code/core/Mage/Core/Controller/Varien/Action.php

Locate:

->setHeader('Content-Disposition', 'attachment; filename="'.$fileName.'"', true)

Replace with:

->setHeader('Content-Disposition', 'attachment; filename="'.$fileName.'";', true)

Done, refresh cache and test again. See you on another tutorial here on MageBR.

]]>
<![CDATA[Magento MySQL Master/Slave]]>http://blog.magebr.com/magento-mysql-master-slave/5ea39378c4833f6c62345c5eWed, 28 Mar 2018 04:46:00 GMT
Magento MySQL Master/Slave

This is a really simple tutorial on how you can set up Magento to work with multiple MySQL servers in case you have Master/Slave configuration.

First you will need a MySQL master/slave configuration, if you don't have one you can always use this tutorial to configure: https://dev.mysql.com/doc/refman/5.7/en/replication-howto.html

If you are using a Database service like AWS RDS this is much easier, just use the replica DNS in the configuration below.

Now on Magento, open the file app/etc/local.xml and you will find this part on the file:

            <default_setup>
                <connection>
                    <host><![CDATA[10.x.x.x]]></host>
                    <username><![CDATA[magento]]></username>
                    <password><![CDATA[PassWord]]></password>
                    <dbname><![CDATA[magento]]></dbname>
                    <initStatements><![CDATA[SET NAMES utf8]]></initStatements>
                    <model><![CDATA[mysql4]]></model>
                    <type><![CDATA[pdo_mysql]]></type>
                    <pdoType><![CDATA[]]></pdoType>
                    <active>1</active>
                </connection>
            </default_setup>

This is the main MySQL connection configuration. On this part you will change the information to match your Master MySQL configuration.

To set up the Slave configuration, let's add this right after the last part:

			<default_read>
				<connection>
					<use/>
					<host><![CDATA[10.x.x.x]]></host>
					<username><![CDATA[magento]]></username>
					<password><![CDATA[PassWord]]></password>
					<dbname><![CDATA[magento]]></dbname>
					<type>pdo_mysql</type>
					<model>mysql4</model>
					<initStatements>SET NAMES utf8</initStatements>
					<active>1</active>
				</connection>
			</default_read>

You can have as many slaves you want. 

I hope you find this tutorial helpful and if you have any questions let us know.

]]>
<![CDATA[[Video] Telephone DDD Mask (Brazilian code)]]>http://blog.magebr.com/video-telephone-ddd-mask-brazilian-code/5ea392e0c4833f6c62345c4eTue, 27 Mar 2018 21:13:00 GMT
With this tutorial we are able to add the DDD mask for customer's telephone.
 
  • mascara.js:
    //MÁSCARA DE TELEFONE
    function txtBoxFormat(objeto, sMask, evtKeyPress) {
    var i, nCount, sValue, fldLen, mskLen,bolMask, sCod, nTecla;
    if(document.all) { // Internet Explorer
    nTecla = evtKeyPress.keyCode;
    } else if(document.layers) { // Nestcape
    nTecla = evtKeyPress.which;
    } else {
    nTecla = evtKeyPress.which;
    if (nTecla == 8) {
    return true;
    }
    }
    sValue = objeto.value;
    // Limpa todos os caracteres de formatação que
    // já estiverem no campo.
    sValue = sValue.toString().replace( "-", "" );
    sValue = sValue.toString().replace( "-", "" );
    sValue = sValue.toString().replace( ".", "" );
    sValue = sValue.toString().replace( ".", "" );
    sValue = sValue.toString().replace( "/", "" );
    sValue = sValue.toString().replace( "/", "" );
    sValue = sValue.toString().replace( ":", "" );
    sValue = sValue.toString().replace( ":", "" );
    sValue = sValue.toString().replace( "(", "" );
    sValue = sValue.toString().replace( "(", "" );
    sValue = sValue.toString().replace( ")", "" );
    sValue = sValue.toString().replace( ")", "" );
    sValue = sValue.toString().replace( " ", "" );
    sValue = sValue.toString().replace( " ", "" );
    fldLen = sValue.length;
    mskLen = sMask.length;
    i = 0;
    nCount = 0;
    sCod = "";
    mskLen = fldLen;
    while (i <= mskLen) {
    bolMask = ((sMask.charAt(i) == "-") || (sMask.charAt(i) == ".") || (sMask.charAt(i) == "/") || 
    
    
    (sMask.charAt(i) == ":"))
    bolMask = bolMask || ((sMask.charAt(i) == "(") || (sMask.charAt(i) == ")") || (sMask.charAt(i) == " "))
    if (bolMask) {
    sCod += sMask.charAt(i);
    mskLen++; }
    else {
    sCod += sValue.charAt(nCount);
    nCount++;
    }
    i++;
    }
    objeto.value = sCod;
    if (nTecla != 8) { // backspace
    if (sMask.charAt(i-1) == "9") { // apenas números...
    return ((nTecla > 47) && (nTecla < 58)); }
    else { // qualquer caracter...
    return true;
    }
    }
    else {
    return true;
    }
    }
    [Video] Telephone DDD Mask (Brazilian code)

     

  •  page.xml
    <action method="addJs"><script>ddd/mascara.js</script></action>

     

  • billing.phtml
    onkeypress="return txtBoxFormat(this, '(99) 9999-9999', event);" maxlength="14" size="30 class="input-text <?php echo $this->helper('customer/address')->getAttributeValidationClass('Telephone') ?>"

     

]]>
<![CDATA[Add Login on Sidebar]]>http://blog.magebr.com/add-login-on-sidebar/5e9c0cc86d58ee64042779a4Tue, 27 Mar 2018 08:34:00 GMT

Very simple tutorial and very helpful.

Open file /app/design/frontend/base/default/layout/customer.xml

Loof for this code and uncomment the reference name="right":

<customer_logged_out>
<!---<reference name="right">
<block type="customer/form_login" name="customer_form_mini_login" before="-" template="customer/form/mini.login.phtml"/>
</reference>-->

For some reason Magento will update all pages with the title LOG IN. To fix this you can edit (not recommended) or duplicate this file on the local folder:
app/code/core/Mage/Customer/Block/Form/Login.php

It will be: app/code/local/Mage/Customer/Block/Form/Login.php
Let's comment this line:

$this->getLayout()->getBlock(’head’)->setTitle(Mage::helper(’customer’)->__(’Customer Login’));

That's it! Happy coding.

]]>