Thu, Nov 16, 2017
Nginx setting for A/B Testing container
The solution is based on browser cookie “ab_test”.
If user first time to access the website, we will randomly dispatch to the backend server, then we set cookie value with the server type(main/test), then for the next request, we will check the cookie value, and dispath it to the previous server.
map $host $MAIN_SERVER { default 127.0.0.2; }
map $host $TEST_SERVER { default 127.0.0.3; }
- 2. retrive some variables such as client cookie
map $http_user_agent $mobile_agent{
default 0;
~*(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge\s|maemo|midp|mmp|netfront|opera\sm(ob|in)i|palm(\sos)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows\s(ce|phone)|xda|xiino 1;
}
split_clients "app${remote_addr}${http_user_agent}${date_gmt}" $ab_test_token {
100% "main";
* "test";
}
map $cookie_ABTest $ABTEST_COOKIE_VALUE {
default $ab_test_token;
"test" "test";
"main" "main";
}
map "${ABTEST_COOKIE_VALUE}" $server_host {
default $MAIN_SERVER;
"main" $MAIN_SERVER;
"test" $TEST_SERVER;
}
map $https $req_port {
default 80;
"on" 443;
}
map $ABTEST_COOKIE_VALUE $cookie_expires {
default 1;
}
server {
server_name youservername.here;
listen 80;
listen 443 ssl;
#ssl setting begin
ssl_certificate /path/to/cert/file.crt;
ssl_certificate_key /path/to/cert/file.key;
ssl_protocols TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
#ssl setting end;
#get real ip begin
set_real_ip_from 172.31.0.0/16;
set_real_ip_from 127.0.0.1/32;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
set $real_x_forward_for $HTTP_X_FORWARDED_FOR;
if ($real_x_forward_for = "" ) {
set $real_x_forward_for $proxy_add_x_forwarded_for;
}
#get real ip end
# get domain name without "www" begin
set $domain $host;
if ($domain ~ "www\.(.*)" ) {
set $domain $1;
}
# get domain name end
location / {
proxy_redirect off;
proxy_cache off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $real_x_forward_for;
proxy_set_header X-Forwarded-Ssl $https;
add_header Front-End-Https $https;
add_header Set-Cookie "ABTest=${ABTEST_COOKIE_VALUE};Path=/;Max-Age=${cookie_expires};Domain=.${domain};";
proxy_pass $scheme://$server_host:$server_port;
}
}
server {
listen 127.0.0.2:80;
listen 127.0.0.2:443 ssl;
#ssl setting begin
ssl_certificate /path/to/cert/file.crt;
ssl_certificate_key /path/to/cert/file.key;
ssl_protocols TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
#ssl setting end;
set_real_ip_from 127.0.0.1/32;
set_real_ip_from 172.31.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location / {
#limit_req zone=one burst=1 nodelay;
}
}
server {
listen 127.0.0.3:80;
listen 127.0.0.3:443 ssl;
#ssl setting begin
ssl_certificate /path/to/cert/file.crt;
ssl_certificate_key /path/to/cert/file.key;
ssl_protocols TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
#ssl setting end;
set_real_ip_from 127.0.0.1/32;
set_real_ip_from 172.31.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location / {
limit_req zone=one burst=1 nodelay;
}
}
Thu, Nov 16, 2017
A wrapper for cron job log
When we setup a cron job, we redirect log to a /tmp/path file. But sometimes the /tmp/path will be delete by system.
So I wrote a very simple script to make sure the directory will be created, and the file will be sync to s3 automaticlly.
#!/bin/bash
CMD=$1
IFS='>' read -ra PARTS <<< "$CMD" #Convert string to array
#Print all names from array
LOGPART=''
for i in "${PARTS[@]}"; do
LOGPART=$i
done
if [ $LOGPART != '/dev/null' ]; then
if [ ! -f $LOGPART ]; then
mkdir -p $LOGPART
rm -rf $LOGPART
fi
fi
eval $CMD
LOGPART=`echo "${LOGPART}" | sed -e 's/^[ \t]*//'`
S3Backup="aws s3 cp ${LOGPART} s3://urlpath/log${LOGPART} --profile logiam > /dev/null"
eval $S3Backup
* * * * * cronrunner.sh "php yourfile.php params >> /tmp/log/log1/logs.txt"
Tue, Jul 18, 2017
Magento2 MageStore Onestepcheckout bug when using braintree
We purchased Magestore OneStepCheckout module for magento2 community edition. Recently we setuped braintree payment gateway.
But soon we some time when we go to checkout page and input correct credit card details and place order, and payment can not be processed, it returns invalid card numbers.
It defenerly is not the credit card’s problem.
So we just use chrome debuger to check the details, we found there are some js error log in the console.
“Can not replace element of id #credit-card-number”
“Can not replace element of id #expiration-month”
“Can not replace element of id #expiration-year”
At first we thought that’s magento braintree module’s bug, but when we debug into the UI component(I don’t like UI component), and it seems some time this component is rendered 3 or 4 times.
Finally we found the problem is because onestepcheckout assemble address, shipping, cart detals options in one page, and all these parts are UI component. Once these part got databinding, and it will update payment infomation which will re-calculate avalible payment method lists and set back to knockoutjs payment-service(which is an observerarray).
But for this version knockoutjs doesn’t handle multiple process access same observer array.
All these payment method UI component renderer are called by observerarray subscriber, so it will render many times.
If some time we are not lucky, last renderer not finish yet, and payment methods list are updated, it will render again. Then the error shows.
- 1. Define the new javascript that you overwrite from core module
We just override the default payment-service, put all payment list to the queue first, and set a 2secs timer, if in w2secs there’s no methodlist come in, we render the last one.
Put the Js file to your module view/frontend/web/js/payment-service.js
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
define(
[
'underscore',
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/model/payment/method-list',
'Magento_Checkout/js/action/select-payment-method'
],
function (_, quote, methodList, selectPaymentMethod) {
'use strict';
var freeMethodCode = 'free';
return {
isFreeAvailable: false,
methods_queue:[],
timer:null,
/**
* Populate the list of payment methods
* @param {Array} methods
*/
setPaymentMethods: function (methods) {
var self = this,
freeMethod,
filteredMethods,
methodIsAvailable;
freeMethod = _.find(methods, function (method) {
return method.method === freeMethodCode;
});
this.isFreeAvailable = freeMethod ? true : false;
if (self.isFreeAvailable && freeMethod && quote.totals().grand_total <= 0) {
methods.splice(0, methods.length, freeMethod);
selectPaymentMethod(freeMethod);
}
filteredMethods = _.without(methods, freeMethod);
if (filteredMethods.length === 1) {
selectPaymentMethod(filteredMethods[0]);
} else if (quote.paymentMethod()) {
methodIsAvailable = methods.some(function (item) {
return item.method === quote.paymentMethod().method;
});
//Unset selected payment method if not available
if (!methodIsAvailable) {
selectPaymentMethod(null);
}
}
self.methods_queue.push(methods);
if (self.timer != null) {
clearTimeout(self.timer);
}
self.timer = setTimeout(function() {
console.log('setpaymentmethod time out trigered');
methodList(self.methods_queue.pop());
self.methods_queue = [];
}, 2000);
//methodList(methods);
},
/**
* Get the list of available payment methods.
* @returns {Array}
*/
getAvailablePaymentMethods: function () {
var methods = [],
self = this;
_.each(methodList(), function (method) {
if (self.isFreeAvailable && (
quote.totals().grand_total <= 0 && method.method === freeMethodCode ||
quote.totals().grand_total > 0 && method.method !== freeMethodCode
) || !self.isFreeAvailable
) {
methods.push(method);
}
});
return methods;
}
};
}
);
- 2. Define it in your requirejs-config.js
var config = {
map: {
'*': {
"Magento_Checkout/js/model/payment-service" : 'Your_ModuleName/js/payment-service'
}
},
"shim": {
"Your_ModuleName/js/payment-service": {
"exports": "Magento_Checkout/js/model/payment-service"
}
}
};
Then enjoy it!
Wed, Dec 21, 2016
Magento2 Fulltext Indexer using swap table
Indexing in Magento2 is much faster than Magento1, but for fulltext indexing, still have few seconds user can search nothing.
I’ve developed the fulltext indexer plugin using swap table. Just index fulltext to a temporary table first, after indexing finished, swap the temporary table to the working table.
1. Define the plugin class
<?php
namespace Your\ModuleName\Plugin\Indexer\CatalogSearch;
use Magento\Framework\Search\Request\DimensionFactory;
use Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver;
use Magento\Framework\App\ResourceConnection;
class IndexerHandler {
private $dimensionFactory;
private $indexScopeResolver;
private $resource;
private $isFullReindex = false;
private $swapDimensions;
private $indexName = \Magento\CatalogSearch\Model\Indexer\Fulltext::INDEXER_ID;
private $getIndexNameClosure;
public function __construct(
DimensionFactory $dimensionFactory,
IndexScopeResolver $indexScopeResolver,
ResourceConnection $resource
) {
$this->dimensionFactory = $dimensionFactory;
$this->indexScopeResolver = $indexScopeResolver;
$this->resource = $resource;
}
/**
* {@inheritdoc}
*/
public function aroundCleanIndex($origin, $processor, $dimensions)
{
$this->isFullReindex = true;
$this->swapDimensions = [];
foreach($dimensions as $di) {
$this->swapDimensions[] = $this->dimensionFactory->create(['name' => 'swap', 'value' => $di->getValue()]);
}
$processor($this->swapDimensions);
}
public function aroundSaveIndex($origin,$processor, $dimensions, $documents)
{
$processor($this->isFullReindex ? $this->swapDimensions : $dimensions, $documents);
if ($this->isFullReindex) {
$swapTableName = $this->getTableName($this->swapDimensions);
$originTableName = $this->getTableName($dimensions);
$tmpTable = $originTableName . '_del';
$this->resource->getConnection()
->query(sprintf('DROP TABLE IF EXISTS %s;', $tmpTable));
$this->resource->getConnection()
->query(sprintf('CREATE TABLE IF NOT EXISTS %s(id int);', $originTableName));
$this->resource->getConnection()
->query(sprintf('RENAME TABLE %s TO %s, %s To %s;',
$originTableName,
$tmpTable,
$swapTableName,
$originTableName));
}
}
private function getTableName($dimensions) {
return $this->indexScopeResolver->resolve($this->indexName, $dimensions);
}
}
Add it to plugin settings “etc/di.xml”
<type name="Magento\CatalogSearch\Model\Indexer\IndexerHandler">
<plugin name="fulltext_table_swap"
type="Your\ModuleName\Plugin\Indexer\CatalogSearch\IndexerHandler"
sortOrder="1" />
</type>
All done!
Tue, Dec 20, 2016
Laravel ElasticSuit

This is a package to integrate Elasticsearch to Laravel5
It makes you do Elasticsearch just using Eloquent’s API.
Installation
- Require this package with composer:
composer require yong/elasticsuit dev-master
- Add service provider to config/app.php
Yong\ElasticSuit\Service\Provider;
- Add elasticsearch node configuration to the “connections” node of config/database.php
'elasticsearch' => [
'hosts'=>['127.0.0.1:9200'],
'ismultihandle'=>0,
'database'=> 'db*',
'prefix' => '',
'settings'=> ['number_of_shards'=>2,'number_of_replicas'=>0]
],
Usage
- Define a model for a elasticsearch type
class TestModel extends \Yong\ElasticSuit\Elasticsearch\Model {
protected $connection = 'elasticsearch';
protected $table = 'testmodel';
//relations
public function Childmodel () {
return $this->hasOne(OtherModel::class, '_id');
}
}
- Create a new document
$testmodel = new TestModel();
$testmodel->first_name = 'firstname';
$testmodel->last_name = 'lastname';
$testmodel->age = 20;
$testmodel->save();
- Search a collection
$collection = TestModel::where('first_name', 'like', 'firstname')
->whereIn('_id', [1,2,3,4,5])
->whereNotIn('_id', [5,6,7,8,9])
->where('_id', '=', 1)
->where('age', '>', 18)
->orWhere('last_name', 'like', 'lastname')
->whereNull('nick_name')
->whereNotNull('age')
->whereMultMatch(['last_name', 'description'], 'search words', '60%')
->skip(10)
->forPage(1, 20)
->take(10)
->limit(10)
->select(['first_name', 'last_name', 'age'])
->get();
* also support sum(), avg(), min(), max(), stats(), count()
* but not for all fields, only numeric fields can use aggregate
- Relations
It also support relations, but remember so far just support using default _id as primary key.
//get relations
TestModel::with('childmodel')->where('first_name', 'like', 'firstname')->get();
License
And of course:
MIT: http://rem.mit-license.org
Fri, Nov 25, 2016
Composer use private git packages
Some time your will not want your company’s code goes to public project, but you want composer to manage packages.
Here’s an example for how to let composer use private packages, and put these packages to customize folder.
- 1. Create mycomposer.php to do these automaticly
Here we use laravel as an example.
<?php
$now = strtotime('now');
$tmp_work_folder = sprintf('/tmp/mycomposer_%s', $now);
$current_folder = __DIR__;
$composer_json = $current_folder . '/composer.json';
$mycomposer_json = $current_folder . '/mycomposer.json';
if(!file_exists($composer_json)) {
rename($current_folder, $tmp_work_folder);
exec('composer create-project --prefer-dist laravel/laravel ' . $current_folder);
exec(sprintf('cp -a %s/* %s', $tmp_work_folder, $current_folder));
}
if (file_exists($mycomposer_json)) {
$composer_configs = json_decode(file_get_contents($composer_json), true);
$mycomposer_configs = json_decode(file_get_contents($mycomposer_json), true);
$requires = [];
foreach( $mycomposer_configs['repositories'] as $package_config) {
$requires[$package_config['package']['name']] = '*';
}
$mycomposer_configs['require'] = $requires;
$new_composer_configs = array_replace_recursive($composer_configs, $mycomposer_configs);
$new_composer_configs['repositories'] = $mycomposer_configs['repositories'];
file_put_contents($composer_json, json_encode($new_composer_configs, JSON_PRETTY_PRINT));
chdir($current_folder);
exec("cd $current_folder && composer update");
}
- 2. Create a JSON file name it as mycomposer.json
{
"repositories": [
{
"type": "package",
"package": {
"name": "../your/path/Module1",
"version":"0.0.0",
"source": {
"url": "https://url_to_your_git_repostory1.git",
"type": "git",
"reference": "master"
}
}
},
{
"type": "package",
"package": {
"name": "../your/path/Module2",
"version":"0.0.0",
"source": {
"url": "https://url_to_your_git_repostory2.git",
"type": "git",
"reference": "master"
}
}
}
],
"autoload": {
"psr-4": {
"YourNameSpace\\": "your/path"
}
}
},
"require": {
},
"require-dev": {
}
}
- 3. put mycomposer.php and mycomposer.json to your new project path and run the command:
php mycomposer.php
it’s done!
Tue, Nov 22, 2016
Magento2 How to overwrite Core module’s javascript
We purchased Magestore OneStepCheckout module for magento2 community edition. But for this module when we go to checkout page, update qty/remove item or just update shipping details, it will update payment methods.
If user have input payment details, then they want to update itme/shipping detials, because it will update payment methods as well, and all these input details will gone.
That’s why I need to overwrite core module’s javascript.
- 1. Define the new javascript that you overwrite from core module
Put the Js file to your module view/frontend/web/js/payment-service.js
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
define(
[
'underscore',
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/model/payment/method-list',
'Magento_Checkout/js/action/select-payment-method'
],
function (_, quote, methodList, selectPaymentMethod) {
'use strict';
var freeMethodCode = 'free';
return {
isFreeAvailable: false,
/**
* Populate the list of payment methods
* @param {Array} methods
*/
setPaymentMethods: function (methods) {
var self = this,
freeMethod,
filteredMethods,
methodIsAvailable;
freeMethod = _.find(methods, function (method) {
return method.method === freeMethodCode;
});
this.isFreeAvailable = freeMethod ? true : false;
if (self.isFreeAvailable && freeMethod && quote.totals().grand_total <= 0) {
methods.splice(0, methods.length, freeMethod);
selectPaymentMethod(freeMethod);
}
filteredMethods = _.without(methods, freeMethod);
if (filteredMethods.length === 1) {
selectPaymentMethod(filteredMethods[0]);
} else if (quote.paymentMethod()) {
methodIsAvailable = methods.some(function (item) {
return item.method === quote.paymentMethod().method;
});
//Unset selected payment method if not available
if (!methodIsAvailable) {
selectPaymentMethod(null);
} else {
selectPaymentMethod(quote.paymentMethod());
}
}
var oldMethods = methodList();
if (oldMethods.length === methods.length) {
for(var i=0; i < oldMethods.length; i++) {
if (oldMethods[i].title != methods[i].title) {
methodList(methods);
break;
}
}
} else {
methodList(methods);
}
//methodList(methods);
},
/**
* Get the list of available payment methods.
* @returns {Array}
*/
getAvailablePaymentMethods: function () {
var methods = [],
self = this;
_.each(methodList(), function (method) {
if (self.isFreeAvailable && (
quote.totals().grand_total <= 0 && method.method === freeMethodCode ||
quote.totals().grand_total > 0 && method.method !== freeMethodCode
) || !self.isFreeAvailable
) {
methods.push(method);
}
});
return methods;
}
};
}
);
- 2. Define it in your requirejs-config.js
var config = {
map: {
'*': {
"Magento_Checkout/js/model/payment-service" : 'Your_ModuleName/js/payment-service'
}
},
"shim": {
"Your_ModuleName/js/payment-service": {
"exports": "Magento_Checkout/js/model/payment-service"
}
}
};
Then enjoy it!
Wed, Oct 26, 2016
Magento2 Let angularjs work with knockout.js
Before Mangento2 we already using angularjs and also developed many angularjs resources(expecially modals).
So we just want to reuse these resources.
Here’s an example for Mangento2 admin panel. (For frontend, I recommend you just only use knockout.js)
- 1. Create a Block class Container Tab
This container tab is for containing angularjs template.
You can contain many templates you want by passing “child_templates” parameters via layout.xml
<?php
namespace Yong\Angularjs\Block\Adminhtml;
use Magento\Backend\Block\Widget\Tab;
use Magento\Backend\Block\Widget\Tabs;
use Magento\Backend\Block\Widget;
class ContainerTab extends Tab {
/**
* Prepare html output
*
* @return string
*/
protected function _toHtml() {
if ($this->hasData('child_templates')) {
foreach($this->getData('child_templates') as $name => $childTemplate) {
$child = $this->addChild($name,
'Magento\Backend\Block\Widget',
['template'=>$childTemplate]
);
}
}
return $this->getChildHtml();
}
}
- 2. Create a UI Modifier to prepare knockoutjs swap data fields
<?php
namespace Yong\Angularjs\Ui\DataProvider\Product\Form;
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\Catalog\Api\Data\ProductAttributeInterface;
use Magento\Framework\Stdlib\ArrayManager;
use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Framework\UrlInterface;
use Magento\Ui\Component\Container;
use Magento\Ui\Component\Form\Fieldset;
use Magento\Ui\Component\Form;
use Magento\Ui\Component\DynamicRows;
/**
* Customize Price field
*/
class Modifier extends AbstractModifier
{
const FIELD_NAME = 'angularjs_swap_data';
const FIELDSET_NAME = 'angularjs_swap_sets';
/**
* @var ArrayManager
*/
protected $arrayManager;
/**
* @var LocatorInterface
*/
protected $locator;
private $swapFieldNames;
/**
* @param LocatorInterface $locator
* @param ArrayManager $arrayManager
*/
public function __construct(
LocatorInterface $locator,
ArrayManager $arrayManager,
$swapFieldNames = [self::FIELD_NAME]
) {
$this->locator = $locator;
$this->arrayManager = $arrayManager;
if (!is_array($swapFieldNames)) {
$swapFieldNames = [$swapFieldNames];
}
$this->swapFieldNames = $swapFieldNames;
}
/**
* {@inheritdoc}
*/
public function modifyMeta(array $meta)
{
$this->meta = $meta;
$this->addFieldset();
return $this->meta;
}
public function modifyData(array $data) {
return $data;
}
protected function addFieldset()
{
$children = [];
$i = 0;
foreach($this->swapFieldNames as $swapFieldName) {
$children[$swapFieldName] = $this->getFieldConfig($swapFieldName, 10*$i++);
}
$this->meta = array_replace_recursive(
$this->meta,
[
static::FIELDSET_NAME => [
'arguments' => [
'data' => [
'config' => [
'label' =>'',
'componentType' => Fieldset::NAME,
'dataScope' => 'data',
'collapsible' => true,
"visible" => true,
'sortOrder' => 10,
],
],
],
'children' => $children
],
]
);
return $this;
}
protected function getFieldConfig($swapFieldName, $sortOrder)
{
return [
'arguments' => [
'data' => [
'config' => [
'label' => '',
'componentType' => \Magento\Ui\Component\Form\Field::NAME,
'formElement' => \Magento\Ui\Component\Form\Element\Input::NAME,
'dataScope' => $swapFieldName,
'dataType' => \Magento\Ui\Component\Form\Element\DataType\Text::NAME,
'sortOrder' => $sortOrder,
"visible" => true,
'required' => true,
],
],
],
'children' => [],
];
}
}
- 3. Reserve knockoutjs swap fields in di.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Yong\Angularjs\Ui\DataProvider\Product\Form\Modifier">
<arguments>
<argument name="locator" xsi:type="object">Magento\Catalog\Model\Locator\LocatorInterface</argument>
<argument name="arrayManager" xsi:type="object">Magento\Framework\Stdlib\ArrayManager</argument>
<argument name="swapFieldNames" xsi:type="string">angularjs_swap_data</argument>
</arguments>
</type>
<virtualType name="angularjs_swap_dataset1"
type="Yong\Angularjs\Ui\DataProvider\Product\Form\Modifier" >
<arguments>
<argument name="swapFieldNames" xsi:type="array">
<item name="field1" xsi:type="string">angularjs_swap_data_1</item>
<item name="field2" xsi:type="string">angularjs_swap_data_2</item>
</argument>
</arguments>
</virtualType>
<virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool">
<arguments>
<argument name="modifiers" xsi:type="array">
<item name="comboproduct" xsi:type="array">
<item name="class" xsi:type="string">angularjs_swap_dataset1</item>
<item name="sortOrder" xsi:type="number">125</item>
</item>
</argument>
</arguments>
</virtualType>
</config>
- 4. Put your container tab to layout.xml
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<update handle="catalog_product_form"/>
<body>
<referenceContainer name="product_form" >
<block class="Yong_Angularjs\Block\Adminhtml\ContainerTab">
<arguments>
<argument name="config" xsi:type="array">
<item name="label" xsi:type="string" translate="true">Angularjs tab</item>
<item name="collapsible" xsi:type="boolean">true</item>
<item name="opened" xsi:type="boolean">true</item>
<item name="sortOrder" xsi:type="string">2</item>
<item name="canShow" xsi:type="boolean">true</item>
<item name="componentType" xsi:type="string">fieldset</item>
</argument>
<argument name="child_templates" xsi:type="array">
<item name="subblock1" xsi:type="string">Yong_Angularjs::catalog/product/edit/tab/angularjs.tab.phtml</item>
</argument>
</arguments>
</block>
</referenceContainer>
</body>
</page>
- 5. Add js resource and config requirejs-config.js
var config = {
map: {
'*': {
angular: 'Yong_Angularjs/bower/angular/angular.min',
angularBootstrap: 'Yong_Angularjs/bower/angular-bootstrap/ui-bootstrap-tpls.min',
ngstorage: 'Yong_Angularjs/bower/ngstorage/ngStorage.min',
mymodalService: 'Yong_Angularjs/js/mymodalService',
modalScript: 'Yong_Angularjs/js/modalScript',
syncKo: 'Yong_Angularjs/js/syncKo',
mytabController: 'Yong_Angularjs/mytabController'
}
},
"shim": {
"Yong_Angularjs/bower/angular/angular.min": {
"exports": "angular"
},
"Yong_Angularjs/bower/angular-bootstrap/ui-bootstrap-tpls.min": {
"exports": "angularBootstrap"
},
"Yong_Angularjs/bower/ngstorage/ngStorage.min": {
"exports": "ngStorage"
},
"Yong_Angularjs/js/mymodalService": {
"exports": "mymodalService"
},
"Yong_Angularjs/js/modalScript": {
"exports": "modalScript"
},
"Yong_Angularjs/js/syncKo": {
"exports": "syncKo"
},
"Yong_Angularjs/mytabController": {
"exports": "mytabController"
}
}
};
- 6. Define angularjs directive “syncko” to sync angularjs object to knockoutjs swap data field(formart as JSON)
define(['jquery'], function($){
'use strict';
function syncKo() {
return function($scope, element, attrs) {
$scope.$watch(attrs.from, function(newValue, oldValue) {
if (newValue !== undefined) {
$('input[name='+ attrs.to +']').val(JSON.stringify(newValue)).trigger('change');
}
}, true);
};
}
return syncKo;
});
- 7. Use knockoutjs swap fields in angularjs template
html
<div class="container fieldset-wrapper" ng:controller="controller" id='ng-controller'
style="margin-left:20px;margin-top: 0" sync-ko from='products' to='angularjs_swap_data_1'>
Sun, Oct 23, 2016
useful tips
- grep get ip addr:
shell
grep -E -o "([0-9]{1,3}[\.]){3}[0-9]{1,3}"
- unique grep result:
shell
grep -E -o "([0-9]{1,3}[\.]){3}[0-9]{1,3}" | cut -d ' ' -f 4 | sort -u
- inotify:
install
yum --enablerepo=epel -y install inotify-tools
monitor.sh
#!/bin/bash
TARGET=$1
ACTION=$2
while inotifywait -e modify ${TARGET}; do
/bin/bash ${ACTION} ${TARGET}
done
action.sh
#!/bin/bash
#/var/log/nginx/error.log
TARGET=$1
cat ${TARGET} | grep 'limiting requests' | grep -E -o "([0-9]{1,3}[\.]){3}[0-9]{1,3}" | cut -d ' ' -f 4 | sort -u
use it:
./monitor.sh /var/log/nginx/error.log ./action.sh
Sun, Oct 23, 2016
Magento2 Debugbar

This is a package to integrate PHP Debug Bar with Magento 2.
It lightly dynamic inject and collect debug info for Magento2 for development/production mode.
You can configure enable it by specific IP address or specific cookie name and value matched, and you can extend it by your custom access control functions.
It bootstraps some Collectors to work with Magento2 and implements a couple custom DataCollectors, specific for Magento2.
It is configured to display Redirects and (jQuery) Ajax Requests. (Shown in a dropdown)
Read the documentation for more configuration options.
This package includes some custom collectors:
- QueryCollector: Show queries, including binding + timing
- ControllerCollector: Show information about the current/redirect Route Action.
- TemplateCollector: Show the currently loaded template files.
- ModelCollector: Show the loaded Models
- ProfileCollector: Shows the Magento2 profiler details
- RequestCollector: The default RequestCollector via PHPDebugbar
- MemoryCollector: The default MemoryCollector via PHPDebugbar
- MessagesCollector: The default MessagesCollector via PHPDebugbar
And it also replace Magento default exception handler as Whoops Error Handler.Read filp/whoops for more details.
It also provides config interface for easy dynamic extend your functionality.
Installation
Require this package with composer:
composer require yong/magento2debugbar dev-master
After updating composer, add ‘phpdebugbar’ configuration to app/etc/env.php
'phpdebugbar' =>
array (
'enabled' => 1,
'enable_checker' =>
array (
'cookie' =>
array(
'name' => 'php_debugbar',
'value' => 'cookievalue'
),
),
)
and then run
bin/magento module:enable Yong_Magento2DebugBar
Usage
Enable/Disable: go to file app/etc/env.php, set ‘enabled’ of array phpdebugbar as 0 for disable, 1 for enable(but still need cookie pair check)
enable_checker: When phpdebugbar is enabled, it will do further check, you need to set your cookie pair in app/etc/env.php, and also your browser has the same cookie pair. Then phpdebugbar will be launched. Otherwise it will not be launched and will collect nothing, it will not affect the performance. So you can deploy it on your production environment.
License
And of course:
MIT: http://rem.mit-license.org
Sat, Dec 12, 2015
Magento2 Let Category Name support last character +
The website I just developed which has some categories name the last character is “+”, and when we import product from CSV, it will automatically removed by Magento.
Normally it’s fine until we found issue below:
We have two categories:
1. CategoryName
2. CategoryName+
So we have to fix it.
Just simply using plugin to correct it and convert “+” as “-plus”.
Define the plugin class
<?php
namespace Your\ModuleName\Plugin\Framework\Filter;
class TranslitUrl
{
public function aroundFilter($origin, $process, $string)
{
$p1 = substr($string, 0, -1);
$p2 = substr($string, -1);
if ($p2 === '+') {
$p2 = '-plus';
}
if (strpos($string, 'AJ1800') > 0) {
echo "$string => $p1 . $p2";
}
$string = $p1 . $p2;
return $process($string);
}
}
Add it to plugin settings “etc/di.xml”
<type name="Magento\Framework\Filter\TranslitUrl">
<plugin name="urltranslit_fix"
type="Your\ModuleName\Plugin\Framework\Filter\TranslitUrl"
sortOrder="1" />
</type>
Sat, Dec 12, 2015
Yongcheng Tech host by AWS serverless