вторник, 28 ноября 2017 г.

суббота, 25 ноября 2017 г.

Установка Eclipse Che

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

Eclipse Che имеет веб интерфейс.

Документация: https://www.eclipse.org/che/docs/setup/getting-started/

Чтобы установить эту IDE на ubuntu 16.04 я обошелся всего 2-мя командами.

1. sudo apt install docker.io
2. docker run -it --rm -v /var/run/docker.sock:/var/run/docker.sock -v ~/EclipseChe:/data eclipse/che start

2 команда запускает Eclipse Che и если запуск в первый раз, то заодно и скачает все что нужно для запуска.

пятница, 24 ноября 2017 г.

четверг, 23 ноября 2017 г.

Установка утилиты yandex disk для ubuntu

Команда для установки:

echo "deb http://repo.yandex.ru/yandex-disk/deb/ stable main" | sudo tee -a /etc/apt/sources.list.d/yandex.list > /dev/null && wget http://repo.yandex.ru/yandex-disk/YANDEX-DISK-KEY.GPG -O- | sudo apt-key add - && sudo apt-get update && sudo apt-get install -y yandex-disk

Мастер настройки

$ yandex-disk setup

Мануал по использованию
https://yandex.ru/support/disk/cli-clients.html#cli-install

Как избавиться от мерцания моделей в AngularJS

Если для Вас важно, чтобы не высвечивались строки отображения моделей, вроде {{mymodel}} в процессе загрузки страницы есть очень простой способ это предотвратить, вместо этого синтаксиса используйте:

<span ng-bind="mymodel"></span>
 
И ничего лишнего не будет выведено!

также для устранения мерцания в определенном контексте можно использовать директиву ng-cloack

Как построить RestFull контроллер в Yii2

Все CRUD операции для RestFull контроллера уже предусмотрены разработчиками Yii. Находятся они в классе yii\rest\ActiveController. То есть для того, чтобы Вы могли создать свой Rest контроллер Вы должны просто унаследовать этот класс вместо обычного yii\web\Controller , а также Вы должны передать обязательное свойство public $modelClass, которое должно хранить полное имя класса модели ActiveRecord, с которой будет работать этот CRUD контроллер.
Пример:

<?php

namespace app\controllers;

use yii\rest\ActiveController;

class TaskRestController extends ActiveController {
    // указываем класс модели, который будет использоваться
    public $modelClass = 'app\models\Task';

    public function behaviors() {
        return
                \yii\helpers\ArrayHelper::merge(parent::behaviors(), [
                    'corsFilter' => [
                        'class' => \yii\filters\Cors::className(),
                    ],
        ]);
    }
}
 
Также в примере применен фильтр, который позволяет обращаться к этому контроллеру с разных доменов, т.к. RestFull приложения обычно обрабатывают несколько доменов.

Отправка пост запроса AngularJS

Недавно столкнулся с проблемой, что у меня не отправлялся пост запрос из приложения AngularJS. Был следующий код у меня:
var req = {
                    method: 'POST',
                    url: 'http://url',
                    headers: {
                        'Content-Type':  undefined
                    },
                    data: {test: 'test'}
                };
                $http(req).success(function(data) {
                   console.log(data); 
                });
 
Дело в том, что я только взялся за Angular и этот код впринципе взят из книги, мне сразу показалось странным, что content-type undefined. После замены content-type, все стало работать!

var req = {
                    method: 'POST',
                    url: 'http://url',
                    headers: {
                        'Content-Type':  'application/x-www-form-urlencoded'
                    },
                    data: {test: 'test'}
                };
                $http(req).success(function(data) {
                   console.log(data); 
                });
 
ХОТЯ НЕТ ПОСТОЙТЕ!!! Этого все-таки оказалось недостаточно, потому что приходит примерно вот такой post запрос

array(1) {
[1] => '{test:"test"}'
}
 
Который естественно не проходит валидацию в модели, поэтому нужно использовать вот такой код

var xsrf = {
                    name: $scope.name,
                    user_id: $scope.userid
                };
                $http({
                    method: 'POST',
                    url: 'http://url',
                    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
                    transformRequest: function (obj) {
                        var str = [];
                        for (var p in obj)
                            str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
                        return str.join("&");
                    },
                    data: xsrf
                }).success(function (data) {
                    console.log(data);
                }).error(function (data, status, headers, config) {
                    console.log("**** ERROR ****");
                    console.log(status);
                });
Ответ нашелся здесь

Инверсия зависимостей на примере Yii2

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

Зачем это нужно?

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

Рассмотрим пример

создадим таблицу Users
CREATE TABLE `users` (
  `id` int(11) NOT NULL,
  `name` varchar(50) NOT NULL,
  `email` varchar(50) NOT NULL,
  `lastname` varchar(50) NOT NULL
);
дальше сгенерируем модель Users c помощью gii, заходим на роут ?r=gii выбираем генератор моделей и если у на есть подключение к БД, вписываем первую букву таблицы "u" и gii сам заполнит необходимые поля, нажимаем "Generate" и идем дальше.
Для того, чтобы отделить бизнес-логику программы от обычных объектов я сделал папку app/models/logic получилась следующая структура файлов и папок:
- app
  - models
    -logic
     - user
       - UserContract.php // Интерфейс
       - UserService.php  // Бизнес логика работы с пользователями
в UserContract.php я поместил следующий код:
<?php

namespace app\models\logic\user;

interface UserContract {
    public function getName();
    public function getLastName();
    public function getEmail();    
}
В UserService.php - следующий:
<?php

namespace app\models\logic\user;

class UserService {
    public function getUser(UserContract $user) {
        return [
            'name' => $user->getName(),
            'email' => $user->getEmail(),
            'lastName' => $user->getLastName(),
        ];
    }
}
Из кода видно, что наша логика работает именно с ИНТЕРФЕЙСОМ! И каждый объект, который "захочет" поучаствовать в предоставлении данных в логику метода UserService::getUser должен реализовать интерфейс/контракт UserConctract.
Конечно этот подход кажется немного утомительным, потому что приходится создавать контракты и постоянно отделять объекты от логики приложения, а ведь частенько хочется просто создать объект и использовать его все время!
Но этот подход помогает улучшить общую архитектуру приложения! И насколько я заметил при использовании инверсии зависимостей часто появляются классы, в именах которых есть слово Service, это говорит именно о том, что абстракция данного класса настолько высока, что ему не важно какие конкретно объекты получают его методы.
В заключении посмотрим как можно использовать созданный нами пример в контроллере:
<?php

namespace app\controllers;

use yii\web\Controller;

class SiteController extends Controller {

    public function actionUserView($id) {
        $user = \app\models\Users::findOne($id);  
        if(empty($user)) { return 'error'; }              
        $userService= new \app\models\logic\user\UserService();    
        $userData = $userService->getUser($user);
        return $this->render('/user/view',compact('userData'));
    }

}
В результате в $userData в виде Мы получаем массив с данными о пользователе!
Еще хочется сказать, что такая разбивка логики очень сильно способствует упрощению тестирования, потому что гораздо легче написать модульный тест для класса UserService , чем мучиться попытками протестировать жесткую логику находящуюся в самом контроллере!

Метод тыка для удаления расширений через composer в Yii2

Удаление расширешний через composer можно назвать интуитивно понятным, я задал себе этот вопрос и сам же на него и ответил не прибегая к поиску в Google.
Для удаления ненужного расширения просто удаляем строку этого расширения из файла composer.json и запускаем уже известную команду.
composer update
В результате будет получен такой красноречивый ответ от composer
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Removing mozarcik/yii-datepicker (dev-master 13acaa5)
Writing lock file
Generating autoload files
Это все, так просто!

Метод тыка при установке расширений через composer вместе с Yii2

Хочу рассказать о моем подходе к использованию composer вместе с Yii2 с применение метода "Тыка".
Это кончено очень большой недостаток, что у меня не хватило терпения прочитать документацию по Composer, но я обязательно когда нибудь заполню этот пробел. А сейчас работаем с composer на уровне подготовки "ниже плинтуса"!
Берем любой свой рабочий проект на yii2, рабочий нужен именно для того, чтобы видеть какие проблемы будут вначале, и научиться их преодолевать.
Я для примера разверну basic шаблон приложения на своем localhost. Так как я чайник я просто скачаю архив с сайта yiiframework.com распакую его и выполню инициализацию.

Установка через командную строку

Хотя нет решил в первый раз попробовать не качать архив а все таки установить через composer, для этого ввел команду
composer global require "fxp/composer-asset-plugin:~1.1.1"
Дальше ввел команду для создания нового проекта Yii2
composer create-project yiisoft/yii2-app-basic basic 2.0.7
Важно заметить, что я ввожу не php composer.phar a composer, потому что я использую Windows и composer установлен у меня как обычное приложение.
После двух введенных команд composer попросил у меня сгенерировать токен на gitHub, и вежливо предложил ссылку для генерации токена, я вставил ссылку в браузер сгенирировал токен, и вставил назад в консоль, токен успешно сохранился.
После этого приложение установилось успешно.

Установка через архив

Для установки basic приложения c помощью архива скачайте архив с сайта, распакуйте файлы в нужную папку, перейдите в эту папку в командной строке, например, если ваш проект в папке C:\web\basic, введите команду cd C:\web\basic и запустите инициализацию приложения с помощью команды init - в Windows или php init - В другой ОС.
Хочется заметить, что установка через composer гораздо быстрее и удобнее!
Результат на картинке 

Отлично! теперь пробуем установить какое-нибудь расширение, например, как и делают многие я попробую установить vova07/yii2-imperavi-widget, захожу на Packagist.com вот сюда.
Прокрутив немного вниз вижу команду для установки
composer require --prefer-dist vova07/yii2-imperavi-widget "*"
Ввожу ее в консоль и нажимаю <Enter>. Да забыл напомнить, что мы должны находиться в дирректории с нашим проектом, в моем случае это C:\web\basic.
В ответ я получил Исключение:
[InvalidArgumentException]
  Could not find package yii.bat at any version for your minimum-stability (s
  table). Check the package spelling or your minimum-stability
Это исключение говорит о том, что уровень минимальной стабильности нашего приложения установлен как Стабильный, и он не соответствует уровню стабильности виджета, который соответствует dev. открываем composer.json, который лежит в корне нашего шаблона и меняем
"minimum-stability": "stable",
меняем на dev.
И в результате получаем то же самое исключение! Неожиданно правда! Вроде бы все сделали нормально! Не знаю как решить эту проблему, поэтому идем обходным путем, используем второй вариант установки вписываем строку:
"vova07/yii2-imperavi-widget": "*"
В секцию require composer.json файла. и выполняем команду:
composer update
В результате Вы скорее всего увидите кучу сообщений Fail, что-то там.. не знаю о чем конкретно они говорят, но значат они то, что тот модуль о котором говорит текущее сообщение уже установлен, ну а наш виджет скачается удачно, и все зависимости сгенерируются удачно! А это то что нам нужно! Теперь можем использовать редактор, вот результат:
 


Хочется заметить, что Вам не стоит путать composer update с командой composer install, потому что именно после вызова composer install, на сколько я понимаю, composer конечно скачает вам виджет, но вместе с этим он удалит все файлы yii2, потому что в composer.lock установлены данные настройки.

Заключение

На этом впринципе и все! процесс установки расширений через composer очень прост, особенно если учитывать, что в composer.json для Yii2 всегда есть нужная структура зависимостей. И также хочется напомнить, чтобы Вы не забывали, чтобы в корневой дирректории лежал файл composer.lock, который не даст composer поновой загружать всю кодовую базу yii2.

Где храниться свойство adminEmail

Мне пришлось настраивать форму контакта на сайте с Yii2 и как обычно я посмотрел в исходник SiteController.php базового шаблона Yii2. Увидел я там следующий код:
public function actionContact()
   {
        $model = new ContactForm();
        if ($model->load(Yii::$app->request->post()) &&
            $model->contact(<b>Yii::$app->params['adminEmail']</b>)) {
            Yii::$app->session->setFlash('contactFormSubmitted');
            return $this->refresh();
        }
        return $this->render('contact', compact('model'));
    }
Значит Yii по-умолчанию берет email администратора из конфигов, первым делом я зашел в web.php конфиг, но к сожалению там я не нашел параметра adminEmail, оказалось что этот параметр сохранен в params.php
Конечно эту настройку можно найти очень быстро, но иногда даже такие простые вещи могут поставить в тупик!

Абстракция для выполнения запросов к серверу jquery

Для выполнения запросов на сервер на мой взгляд лучше сразу ввести хоть какой-нибудь слой абстракции, но не использовать стандартный $.ajax метод jquery.
В моем случае я использую вот такой объект:
ServerSinchronizator = {
    endPoints: {
        "common": "/json/"
    },
    availableActions: {
        "data":           { method: "GET",  url: ""                        },
        "userLogin":      { method: "POST", url: "?action=login"           },
        "useBonuses":     { method: "POST", url: "?action=use_bonus"       },
        "useCertificate": { method: "POST", url: "?action=use_certificate" },
        "sendOrder":      { method: "POST", url: "?action=new_order"       },
        "cartAdd":        { method: "POST", url: "?action=add"             },
        "cartUpdate":     { method: "POST", url: "?action=update"          },
        "cartRemove":     { method: "POST", url: "?action=delete"          },
    },
    cachedHandlers: [],

    initialize: function() {},

    do: function(endPoint, config) {
        var url = '',
            action = '',
            cached,
            data = {},
            success = function() {},
            fail = function() {};
        if (typeof this.endPoints[endPoint] === 'undefined') {
            throw new Error("No point" + endPoint);
        }
        url = this.endPoints[endPoint];
        if (typeof config['action'] === 'undefined') {
            throw new Error("Unknown action");
        }
        action = this.availableActions[config['action']];
        if (typeof action === 'undefined') {
            throw new Error("Unknown action");
        }
        if (typeof config['data'] !== 'undefined') {
            data = config['data'];
        }
        if (typeof config['success'] !== 'undefined') {
            success = config['success'];
        }
        if (typeof config['fail'] !== 'undefined') {
            fail = config['fail'];
        }
        cached = typeof config['fromCache'] !== 'undefined';
        if (cached) {
            this.cachedHandlers.push({
                "action": config["action"],
                "callBack": success
            });
            return;
        }
        $.ajax({
            type: action["method"],
            url: url + action["url"],
            data: data,
            dataType: "html"
        }).done(function( _data ) {
            success(_data);
            ServerSinchronizator.doCachedHandlersFor(config['action'], _data);
        }).fail(function( _data ) {
            fail(_data);
        });
    },

    doCachedHandlersFor: function(action, data) {
        var curHanlder, call;
        for(var handler in this.cachedHandlers) {
            if (this.cachedHandlers.hasOwnProperty(handler)) {
                curHanlder = this.cachedHandlers[handler];
                if (curHanlder["action"] === action) {
                    call = curHanlder["callBack"];
                    call(data);
                }
            }
        }
    },

    getFromCache: function(action) {
        if (typeof this.cachedData[action] !== 'undefined') {
            return this.cachedData[action];
        }
        return false;
    },
};
Обращаться к серверу через этот объект можно вот таким образом:
ServerSynchronizator.do("common", {
    action: "userLogin",
    data: {hello: "world"},
    success: function(data) {
        console.log(data);
    },
    fail: function(data) {
        conosle.log(data);
    }    
});

Простой шаблонизатор для js приложений

Очень простая функция для выполнения шаблонизации есть вот здесь.
Посмотреть пример онлайн
Пример функции шаблонизатора:
(function(){
  var cache = {};

  this.tmpl = function tmpl(str, data){
    var fn = !/\W/.test(str) ?
      cache[str] = cache[str] ||
        tmpl(document.getElementById(str).innerHTML) :

      new Function("obj",
        "var p=[],print=function(){p.push.apply(p,arguments);};" +
        "with(obj){p.push('" +
        str
          .replace(/[\r\t\n]/g, " ")
          .split("<%").join("\t")
          .replace(/((^|%>)[^\t]*)'/g, "$1\r")
          .replace(/\t=(.*?)%>/g, "',$1,'")
          .split("\t").join("');")
          .split("%>").join("p.push('")
          .split("\r").join("\\'")
      + "');}return p.join('');");

    return data ? fn( data ) : fn;
  };
})();
Использование в HTML
<script type="text/html" id="item_tmpl">
   <div class="sizes">
       <% for(var i=0; i < sizes.length; i++) { %>
       <div class="size__item">
           <%= sizes[i]["count"] %>
       </div>
       <% } %>
   </div>
</script>
   <div id="result">
   </div>
Настройка перед первым вызовом
var results = document.getElementById("result"),
dataObject = {
   sizes: [
      {
         count: 2
      },
      {
         count: 3
      },
      {
         count: 4
      }
  ]
};
results.innerHTML = tmpl("item_tmpl", dataObject);

Git Flow полный push на сервер

При работе с git flow часто возникает необходимость слить мастер и девелоп и отправить все изменения на сервер..

Для того, чтобы не писать все команды по отдельность можно сделать простой скрипт (super-push.sh):


#!/usr/bin/bash
git checkout master
git merge develop
git checkout develop
git merge master
git push origin master
git push origin develop
git push --tags
 
Этот BASH скрипт поможет вам слить master и develop и отправить все изменения из этих веток на сервер плюс еще и отправить все ваши тэги.
Важно не забыть поставить скрипту права на исполнение

$ sudo chmod a+x super-push.sh
 
И все можно пушить все сразу из вашего репозитория примерно вот так:
$ ./super-push.sh

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

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

find . -name "*.js" -exec iconv -f UTF-8 -t CP1251 {} -o ./{} \;
 
Здесь все достаточно просто find находит все файлы по переданной маске и для каждого выполняет команду iconv результат сохраняется относительно текущей директории в которой была запущена команда.

Конкатенация html в NODEjs вторая попытка

У меня уже были функции, с помощью которых можно объединить несколько HTML файлов c помощью nodejs, но та функция не поддерживала запуск без вотчера.. Теперь есть поддержка простого запуска этой функции на дирректорию
var fs = require('fs'),
    path   = require('path');

gulp.task("concatHtml", function() {<p>    concatHtml('./pages/');
});</p>
function concatHtml(event) {
    var isDir, clearFileName;
    fs.stat(event, function(err, stats) {
        var filesToConcat = [];
        isDir = stats.isDirectory();
        if (isDir) {
            fs.readdir(event, function (err, files) {
                files.forEach(function (file) {
                    clearFileName = path.basename(file);
                    filesToConcat.push(clearFileName);
                });
                realConcat(filesToConcat);
            })
        } else {
            clearFileName = path.basename(event.path);
            filesToConcat.push(clearFileName);
            realConcat(filesToConcat);
        }
    });
}

function realConcat(filesToConcat) {
    for(var curFile in filesToConcat) {
        gulp.src(['./blocks/header.html',
                  './blocks/menu.html',
                  './pages/' + filesToConcat[curFile],
                  './blocks/footer.html'])
            .pipe(concat(filesToConcat[curFile]))
            .pipe(gulp.dest('./'));
        console.log('html is builded success!');
    }
}
В результате файлы из директории pages попадают в корневую директорию, при этом объядиняясь с хедером футером и меню.

BASH скрипт для транслитерации текущего каталога на латинице

Полезный скрипт, который может выполнить транслитирацию файлов и папок текущего каталога на латиницу.
#!/bin/bash
# Перекодирует рекурсивно в текущем каталоге имена
# файлов и каталогов в транслит.

# shopt встроенная команда оболочки. Управляет опциями оболочки.
# Если в каталоге нет ни одного файла, соответствующего шаблону,
# то за имя файла принимается сам шаблон.
# Ключ nullglob исправляет эту ситуацию
shopt -s nullglob
# Перебираем все файлы в текущем каталоге
for NAME in * ; do
# sed-ом заменяем символы кирилицы на символы латиницы
 TRS=`echo $NAME | sed "y/абвгдезийклмнопрстуфхцы/abvgdezijklmnoprstufxcy/"`
 TRS=`echo $TRS | sed "y/АБВГДЕЗИЙКЛМНОПРСТУФХЦЫ/ABVGDEZIJKLMNOPRSTUFXCY/"`
 TRS=${TRS//ч/ch};
 TRS=${TRS//Ч/CH} TRS=${TRS//ш/sh};
 TRS=${TRS//Ш/SH} TRS=${TRS//ё/jo};
 TRS=${TRS//Ё/JO} TRS=${TRS//ж/zh};
 TRS=${TRS//Ж/ZH} TRS=${TRS//щ/sh\'};
 TRS=${TRS///SH\'} TRS=${TRS//э/je};
 TRS=${TRS//Э/JE} TRS=${TRS//ю/ju};
 TRS=${TRS//Ю/JU} TRS=${TRS//я/ja};
 TRS=${TRS//Я/JA} TRS=${TRS//ъ/\`};
 TRS=${TRS//ъ\`} TRS=${TRS//ь/\'};
 TRS=${TRS//Ь/\'}
 TRS=${TRS// /_}
 # переименовываем
 mv -v "$NAME" "$TRS"
# Если это каталог, заходим в него
 if [[ `file -b "$TRS"` == directory ]]; then
 cd "$TRS"
 "$0"
 cd ..
 fi
done
Источник

Использование памяти в PHP

В последнее время начал задумываться об использовании памяти в тех ЯП, которыми пользуюсь.
По PHP нашел вот такую статью по использвоанию памяти. Очень понравилась эта статья.
Дальше некоторые данные по использованию памяти разными конструкциями языка PHP.
Скалярная переменная 76 байт
$a = array(); 164 байта
class A { } $a = new A(); 184 байта
$a = new stdClass(); 272 байта
цикл foreach Увеличивает потребление памяти, с каждой итерацией, видимо сохраняя значения каждой итерации в промежуточные внутренние структуры, но после завершения цикла, эта память будет освобождена.
Понравился пример, в котором значение в цикле foreach передается по ссылке и при удалении ключа и значения на каждой итерации цикла мы не теряем память.
foreach ($a as $k=>&$v) {
   $a[$k] = someBigValue();
   $v = someBigValue();unset($k, $v);
   echo 'In FOREACH cycle.'.PHP_EOL;
   memoryUsage(memory_get_usage(), $base_memory_usage);
}
И еще статья по потреблению памяти объектами.

Настройка WebDriverIO для работы с CodeceptionJs

При работе с codeceptionjs я столкнулся с такой проблемой, что если использовать phantomjs в качестве браузера для тестирвоания, то практически невозможно нормально протестировать клики на страницах и прочие вещи, связанные с активными действиями.
Поэтому я решил подробнее изучить проблему и пришел к такому выводу, что вместо phantomjs лучше использовать selenium.
О том как установить и заппускать selenium хорошо написано вот здесь. После того, как вы скачаете selenium вам потрубуется скачать либо chromedriver либо geckodriver(firefox) это уже на ваш вкус.
Я лично скачал драйвер для Google Chrome вот из этого мануала. И положил бинарный файл в свою директорию /usr/bin и начаначил ему соответствующие права на запуск.
$ sudo chmod a+x /usr/bin/chromedriver
Для запуска selenium java-версии используется вот такая строка
$ java -jar selenium-server-standalone-2.xx.xxx.jar
Это сразу можно поправить с помощью .bashrc прописав алиас вот таким образом:
alias selenium="java -jar selenium-server-standalone-2.xx.xxx.jar"
Все после этого вы сможете запустить Selenium одной командой
$ selenium
Этого будет достаточно чтобы в тестах начали нормально работать методы .click, .wait, .fill и пр. им подобные.
И в заключении не забудьте подправить конфигурационный (codecept.json) файл для использования chrome. Примерно вот так:
{
    "tests": "*_test.js",
    "timeout": 10000,
    "output": "./output",
    "helpers": {
        "WebDriverIO": {
            "url": "http://localhost",
            "browser": "chrome"
        },
        "SeleniumWebdriver": {
            "url": "http://localhost",
            "browser": "chrome"
        }
    },
    "include": {
        "I": "./steps_file.js"
    },
    "bootstrap": false,
    "mocha": {},
    "name": "codeceptjs"
}

Bitrix Проверка принадлежности текущей категории определенному родительскому инфоблоку

Иногда необходимо проверить что какой-либо раздел каталога является потомком какого-то другого раздела, но при этом мы не знаем сколько уровней вложености может разделять две категории.
В таким случая очень сильно помогает концепция вложеных множеств, которую реализует механизм инфоблоков Bitrix.
Основываясь на этих вложеных множествах можно применить вот такой код:
$checkFilterAvailable = function($currentId, $parentId) {
        $rs = CIBlockSection::GetList(
            array(),
            array('ID'=>$parentId)
        );
        $ar = $rs->GetNext();
        $rs = CIBlockSection::GetList(
            array('LEFT_MARGIN'=>'ASC'),
            array(
                'ID' => $currentId,
                '>LEFT_MARGIN'=>$ar['LEFT_MARGIN'],
                '<RIGHT_MARGIN'=>$ar['RIGHT_MARGIN'],
            )
        );
        if ($rs->GetNext()) {
            return true;
        }
        return false;
    };

Простенький скрипт для просмотра локальной почты php Apache

В этой статье хочу немного рассказать о том, как я сделал локальный скрипт на PHP с помощью которого можно выполнять обработку почтовых сообщений моих локальных тестовых сайтов.
В результате у меня получился локальный домен, в котором я могу получить конкретное сообщение по адресу http://mail/18 - это будет сообщение номер 18 и получить список сообщений по адресу http://mail/list.
Далее приведу пример своего Г**** кода в котором все смешано, но обработка выполняется так как нужно:
<html>
    <head>
        <meta charset="utf-8">
        <title>Просмотр сообщения</title>
    </head>
    <body style="background: #ccc">
        <?php
        $f = isset($_GET['id']) ? $_GET['id'] : null;
        $rootDir = '/var/mail/sendmail/new';
        $standartPath = $rootDir . '/letter_';
        ?>
        <h1
            style="margin-top: 30px;
                   color: blue;
                   text-align: center">
            Самопальный почтовый клиент online
        </h1>
        <h4 style="text-align: center;">
            <a href="/list">Список сообщений</a>
        </h4>
        <div
            style="border: solid 1px #999;
                   padding: 30px;
                   width: 900px;
                   margin: 20px auto;
                   background: #fff;
                   border-radius: 10px;">
            <?php
            if ($f === 'list') {
                $files = getList($rootDir);
                uasort($files, function($a, $b) {
                    $lA = preg_replace("/[^0-9]/", '', $a['name']);
                    $lB = preg_replace("/[^0-9]/", '', $b['name']);
                    return $lA > $lB;
                });
            ?>
            <table
                style="width: 100%;border: solid 1px #ccc">
                <thead>
                    <tr style="font-weight: bolder">
                        <td style="text-align: center">Номер</td>
                        <td>Название</td>
                        <td>Дата изменения</td>
                    </tr>
                </thead>
                <tbody>
                    <?php $padding = 10 ?>
                    <?php foreach ($files as $file) : ?>
                        <tr>
                            <?php $link = preg_replace("/[^0-9]/", '', $file['name']); ?>
                            <td style="padding: <?= $padding ?>px;text-align: center">
                                <?= $link ?>
                            </td>
                            <td style="padding: <?= $padding ?>px;border: solid 1px #ccc;">
                                <a href="/<?= $link ?>">
                                    <?= $file['name']; ?>
                                </a>
                            </td>
                            <td style="padding: <?= $padding ?>px">
                                <?= $file['time']; ?>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
            <?php
            } elseif ($f) {
                $file = file_get_contents($standartPath . $f . '.eml');
                echo quoted_printable_decode($file);
            } else {
                echo 'id of file required!';
            }
            function getList($path) {
                $files = array();
                global $rootDir;
                if ($dir = opendir($path))  {
                    while (false !== ($file = readdir($dir))) {
                        if (
                            $file == "."
                                  || $file == ".."
                                           || (is_dir($path . "/" . $file))
                        ) continue;
                        $files[] = [
                            'name' => $file,
                            'time' => date('Y-m-d H:i:s', filemtime($rootDir . '/' . $file))
                        ];
                        $i++;
                    }
                    closedir($dir);
                }
                return $files;
            }
            ?>
        </div>
    </body>
Далее останется только забросить в директорию со скриптом файл .htaccess с таким содержимым:
RewriteEngine On
      RewriteRule ^([0-9a-z]+)$ index.php?id=$1 [L]
      RewriteRule ^([0-9a-z]+)/$ index.php?id=$1 [L]
И настроить виртуальных хост mail в вашей системе.Вот например таким способом.
Надеюсь кому-нибудь пригодится этот небольшой и не очень красивый скрипт... С помощью которого можно посмотреть тестовый сообщения из нужной вам папки.
PS. О перенаправлении сообщений в папку можно почитать вот здесь.

Создание приемочных тестов с помощью CodeceptJS

С помощью codecept JS можно выполнять приемочное тестирование
Ссылка на проект.
При запуске codeceptjs может возникнуть ошибка:
/usr/local/lib/node_modules/codeceptjs/lib/config.js:2
let fs = require('fs');
^^^
SyntaxError: Unexpected strict mode reserved word
    at Module._compile (module.js:439:25)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
Чтобы избавиться от этой проблемы необходимо обновить nodejs.
Инструкция по обновлению nodejs здесь.
Quickstart находится вот тут.

Что делать если объект I пустой?

Если в ваших тестах объект I оказался пустым, то это скорее всего значит, что вы не выбрали один из этих вариантов:
? What helpers do you want to use?
❯◉ WebDriverIO
 ◯ Protractor
 ◯ SeleniumWebdriver
 ◯ Nightmare
 ◯ FileSystem
Выбираются они пробелом, после чего нажимается клавиша Enter.

Что делать если тесты говорят что Selenium не запускается?

Если вы получили подобную ошибку значит на вашем http://localhost:4444 не запущен сервер. Я нашел только один способ с помощью которого можно исправить эту ошибку.
Устанавливаем вот эти пакеты:
npm install -g webdriverio
npm install -g selenium-webdriver
также нужно установить PhantomJS если он у вас еще не установлен:
npm install -g phantomjs
После этого можно запустить phantomjs в режиме демона ( как я понял ) вот таким способом:
phantomjs --webdriver=4444
После всех проделанных операций по адресу http://localhost:4444 должен появиться ответ в json формате, запускаем тесты! Все должно заработать!
Надеюсь эта подборка была кому-нибудь полезна! Я когда разбирался с запуском этого тестового фреймворка такой подборки не нашел!

Пользователи и группы в линукс.

Нашел хорошую и достаточно подробную статью по управлению пользователями и группами в linux, прочитал и оставил здесь на память.
http://www.oldnix.org/users-groups/

Bitrix дополнительные свойства раздела.

Иногда необходимо в битриксе получить значения дополнительных свойств раздела, чтобы вывести их где нибудь на сайте.
Вот эта статья мне очень помогла в этой проблеме http://skarzhinets.com/info/blog/bitriks-dop-svois...
Надеюсь еще кому-нибудь она поможет.
В моем случае получилась вот такая функция:
<?php

/* Получить иконки категории */
function getCategoryIcons($parentSection) {
    if (empty($parentSection)) return false;

    /* Данные для списка */
    $db_list = CIBlockSection::GetList(Array(SORT=>"ASC"), $arFilter = Array(
        "IBLOCK_ID"=>$parentSection["IBLOCK_ID"],
        "ID"=>$parentSection["ID"]),
    true,
    $arSelect=Array("UF_CATEGORY_ICON")
    );

    /* Вывод списка */
    while($ar_result = $db_list->GetNext()){
        if (!$ar_result["UF_CATEGORY_ICON"]) break;
        echo '<ul class="category-icons">';
        foreach($ar_result["UF_CATEGORY_ICON"] as $PROP){
            $rsEnum = CUserFieldEnum::GetList(array(), array("ID" =>$PROP)); 
            $arEnum = $rsEnum->GetNext();
            $id = $arEnum["XML_ID"];
            $value = $arEnum["VALUE"];
            echo '<li class="category-icon-item" id="'
                . $id . '" data-title="'
                . $value . '">';
            echo '</li>';
        }
        echo '</ul>';
    }
}

/* Запускаем процесс поиска иконок */
getCategoryIcons($parentSection);

Небольшая записка об использовании утверждений.

Утверждения - это давно всем известная методика управления сложностью программного обеспечения. Но очень не многие программисты действительно серьезно воспринимают этот подход. Почему так происходит? На мой взгляд это связано с тем, что любой нормальный программист не любит писать лишний код, из - за этого можно услышать такие фразы "Утверждения мусорят код" и все в этом духе.
Я люблю утверждения! Все, что я здесь напишу - это мое субъективное мнение, так что если оно не совпало с Вашим, прошу прощения.
Утверждения это совсем не тесты! Очень важно это понимать и не использовать утверждения так же как вы используете тесты, также если вы используете утверждения это совсем не значит, что вам не нужно писать тесты! Для меня утверждения - это способ доказать себе что я получил то что я ожидал получить. Основная польза от утверждений заключается не в том, чтобы доказать работу программы а в том чтобы быть уверенным, что программа не сделает лишних действий.
Ну вроде бы все что касается теории и все чем я хотел поделиться я рассказал. Теперь приведу некоторые правила, следуя которым можно составить систему использования утверждений в исходном коде программ.

1. Утверждение после присвоение значения переменной.

Если Вы присваиваете какой нибудь переменной значение задайте себе вопрос какое это значение, какого типа, какие пределы будут у этого значения? Ниже и далее функция утверждения будет абстрактной.
<?php

$new_var = something_strange_function();
assert('=', true, is_array($new_var) || is_null($new_var));
В этом примере мы может быть и не знаем что должна вернуть функция something_strange_function, но мы точно знаем что нам нужен только массив или Null.

2. Утверждение перед циклами\условиями.

Перед вызовом функции или условием обязательно проверьте, что параметры с которыми работает цикл такие, какие вам нужны.
<?php

assert('>', 0, count($peoples));
assert('<', 100, count($peoples));
if (count($peoples) > 3) {
   echo '2 сопровождающих';        
} else {
   echo '1 сопровождающий';    
} 

3. Утверждения после циклов и условий.

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

Также утверждения должны стоять после и перед вызывами функций или методов классов. Думаю на этом все.

Собственный тестовый фреймворк, для тестирования верстки сайта на JS.

В продолжении моих изысканий в создании тестовых фреймворков я сделал расшерение для jQuery в котором можно регистрировать тесты на проверку верстки вашего сайта и в случае нарушения теста результаты проверок будут либо выведены в консоль браузера, либо будут сохранены в переданный php обработчик.
Код получился достаточно крупным, но зато он обрабатывает достаточно много случаев. Со временем код может измениться, поэтому за последними изменениями обращаться на GitHub в мой репозиторий.
(function($) {
    "use strict";

    function clone(o) {
        var obj = [];
        for(var i in o){
            obj[i] = o[i];
        }
        return obj;
    }

    /**
     * Помощник для выполнения тестирования
     * стилей сайта
     */
    var th = {
        testsCount        : 0,
        testsFailed       : 0,
        testsSuccess      : 0,
        testsSkipped      : 0,
        canTest           : false,
        currentElement    : null,
        currentSelector   : null,
        allowConsolePrint : false,
        waitTime          : 1000,
        limits            : 10,
        phpErrorHandler   : false,
        elements          : [],

        resolveParameter: function(real_parameter) {
            if (!this.canTest) return false;
            var parameter = real_parameter;
            switch(real_parameter) {
            case "top"         : parameter = this.currentElement.offset().top; break;
            case "bottom"      : parameter = this.currentElement.offset().bottom; break;
            case "parent-top"  : parameter = this.currentElement.position().top; break;
            case "parent-left" : parameter = this.currentElement.position().left; break;
            case "font-size"   : parameter = this.currentElement.css('font-size'); break;
            default            : parameter = this.currentElement[parameter]();
            }
            return parameter;
        },

        /**
         * Утверждение, выполняющее проверку равенства
         * свойства элемента ожидаемому значению
         */
        assertEqual : function(parameter, expected) {
            var real = this.resolveParameter(parameter),
                i,
                max_limit = 0,
                min_limit = 0,
                expected_values = (expected+"").split('|'),
                cur_expect,
                checkResult = false;
            if (!this.canTest) return false;
            for (i = 0; i < expected_values.length; i++) {
                cur_expect = expected_values[i].trim() * 1;
                min_limit = cur_expect - this.limits;
                max_limit = cur_expect + this.limits;
                // Если совпало хотя бы одно значение - тест пройден
                if ((min_limit <= real && real <= max_limit)) {
                    checkResult = true;
                    break;
                }
            }
            this.countTestLike(checkResult, parameter, expected, real);
            this.elements = [];
        },

        /**
         * Считаем тест либо пройденым либо
         * Нет в зависимости от входного
         * Параметра
         */
        countTestLike: function(status, parameter, expected, real) {
            if (!status) {
                var message = "Test Failed wrong "
                    + this.currentSelector + '->'
                    + parameter + " ("
                    + "expected: " + expected + " / real:" + real
                    + " / limits: " + this.limits
                    + ")";
                this.consolePrint(message);
                if (this.phpErrorHandler) {
                    $.ajax({
                        type: "POST",
                        url: this.phpErrorHandler,
                        data: {
                            "message": message,
                            "agent"  : navigator.userAgent
                        },
                        dataType: "text"
                    });
                }
                this.testsFailed++;
            } else {
                this.testsSuccess++;
            }
        },

        /**
         * Выполнение сравнения всех элементов
         * По переданому параметру
         */
        assertAllEqualsBy: function(by_parameter) {
            var i,param,etalon = 'no';
            for (i = 0; i < this.elements.length; i++) {
                this.currentElement = this.elements[i]["element"];
                if (this.currentElement.length !== 0) {
                    param = this.resolveParameter(by_parameter);
                    if (etalon === 'no')
                        etalon = param


                    this.countTestLike(etalon === param, by_parameter, etalon, param);
                }
            }
            this.elements = [];
        },

        /**
         * Конфигурирование помошника
         */
        configure: function(conf_object) {
            if (this.notEmpty(conf_object['limits']))
                this.limits = conf_object['limits'];
            if (this.notEmpty(conf_object['waitTime']))
                this.waitTime = conf_object['waitTime'];
            if (this.notEmpty(conf_object['allowConsolePrint']))
                this.allowConsolePrint = conf_object['allowConsolePrint'];
            if (this.notEmpty(conf_object['phpErrorHandler']))
                this.phpErrorHandler = conf_object['phpErrorHandler'];
        },

        /**
         * Проверка того, что элемент не пустой
         */
        notEmpty: function(value) {
            return typeof value !== 'undefined';
        },

        /**
         * Получение текущего элемента и возвращение
         * хелпера, чтобы можно было выполнять цепь
         * вызывов.
         * @selector Селектор объекта с которым работаем
         * @position Необяз параметр определющий позицию элемента
         */
        element: function(selector, position) {
            this.testsCount++;
            this.currentSelector = selector;
            if (this.notEmpty(position))
                this.currentElement = $(selector).eq(position);
            else
                this.currentElement = $(selector);
            this.elements.push({
                "selector": clone(this.currentSelector),
                "element" : clone(this.currentElement)
            });
            this.canTest = this.currentElement.length !== 0;
            if (!this.canTest) this.testsSkipped++;
            return this;
        },

        /**
         * Результат тестирования суммарно.
         */
        summorize: function() {
            this.consolePrint('ALL     tests: ' + this.testsCount);
            this.consolePrint('SKIPPED tests: ' + this.testsSkipped);
            this.consolePrint('FAILED  tests: ' + this.testsFailed);
            this.consolePrint('SUCCESS tests: ' + this.testsSuccess);
        },

        /**
         * Тестовая подборка для отложеного выполнения
         * тестирования проекта
         */
        testBundle: function(_callback) {
            this.consolePrint('Tests is runing, please wait ' + (this.waitTime/1000) + 's .' );
            setTimeout(_callback, this.waitTime, th);
        },

        /**
         * Вывод сообщений в консоль с проверкой
         * возможности вывода этих сообщений
         */
        consolePrint: function(message) {
            if (this.allowConsolePrint)
                console.log(message);
        }
    };

    /**
     * Обезъянья правка jQuery для добавления
     * Возможности тестирования.
     */
    $.tst = function(_configure) {
        var callback = _configure['bundle'];
        th.configure(_configure);
        th.testBundle(function() {
            callback(th);
            th.summorize();
        });
    };

})(jQuery)
Подключив этот плагин к своему проекту вы будете вправе выполнить вот такой код для регистрации тестов:
$( function() {
        $.tst({
            allowConsolePrint: true,
            waitTime         : 2000,
            phpErrorHandler  : '/css-error-saver.php',
            bundle: function(th) {
                // Можно проверить конкретное свойство
                th.element('.parameters-section').assertEqual('outerWidth', 495);
                // Можно проверить соответствие набору значений            
                th.element('.parameters-section').assertEqual('height', "583|781|352|676|867");
                // Можно проверить набор элементов на равенство по свойству    
                th.element('.footer-list').element('.footer-right')<p>                    .assertAllEqualsBy('parent-top');
        });</p>    } );
После выполнения этого кода, если были сделаны настройки на вывод результатов в консоль, то Вы должны увидеть общие данные от метода summarize() в которых будет описано сколько тестов прошло, сколько провалилось, и сколько их было вообще.
PHP обработчик для тестовых пакетов может быть написан Вами самостоятельно, я лично на скорую руку написал вот такой обработчик:
<?php

$message = filter_input(INPUT_POST, 'message', FILTER_SANITIZE_SPECIAL_CHARS);
$agent = filter_input(INPUT_POST, 'agent', FILTER_SANITIZE_SPECIAL_CHARS);

if ($message && $agent) {
    $was = file_get_contents('css_errors.txt');
    file_put_contents('css_errors.txt', $was . "\n"
    . "Date: " . date('Y-m-d H:i:s')
    . " Agent:" . $agent
    . " Message: " . $message);
}
echo 'ok';

Настройка и кастомизация FancyBox

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

Как открыть существующий контент в окне fancybox?

Это очень важная проблема, так как иногда на существующем контенте уже висят какие-нибудь обработчики событий и не хотелось бы их потерять... Fancybox может открыть нужный вам контент правильно, без потери обработчиков, делается это примерно вот так:
$( 'a.fancypops' ).on( "click", function() {
        var title = $(this).attr('title');

        $.fancybox.open( {href : "#your-container-id"},{
            padding     : 30,
            fitToView   : true,
            autoSize    : true,
            closeClick  : false,
            openEffect  : 'fade',
            closeEffect : 'fade',
            wrapCSS     : 'custom-fancypops',
            helpers     : {
                title : { type : 'inside' },
                overlay : {
                    opacity : 0.4,
                    locked: true
                }
            },
            afterLoad : function(e, i) {
                this.title = title;
            }
        } );
});
Разберем самое важное из приведенного кода:
wrapCss - Это CSS-класс который будет назначен враперу, с помощью этого класса можно кастомизировать вид fancybox.
helpers.title - Вывод заголовка fancybox.
afterload.this.title - Установка значения для заголовка

Как закрыть все FancyBox?

Чтобы закрыть все модальные окна можно использовать такой код:
if ($('.fancybox-wrap').length) {
    $.fancybox.close();
}
Этот код выполняет проверку существования модальных окон и закрывает их в случае прохождения проверки.

Yii2 запуск миграций из любой директории

Для этого можно использовать ключ migrationPath php yii migrate --migrationPath="vendor\yiisoft\yii2\rbac\migrations"