Creation of Bitrix24 REST activity (actions) with an embedded application for parameter preprocessing

An action or activity in Bitrix24 is a component of a business process that has its own parameters and performs some kind of action.

The REST API has a “bizproc.activity.add” method, it creates an activity and can assign a file with a handler to embed a frame in the portal interface, which allows you to place the “front” of applications in your action settings as a preprocessor (we automate complex logic, validate data ) or a more convenient interface (we make lists with a choice of human-readable items, instead of searching for an id or a symbolic code to enter in a field).

Let’s create an action to search for smart process elements on a certain status using REST, php, js. We will also use vue.js, but this is not necessary, the main thing is to implement ajax requests. And, of course, you will need hosting to host handlers with https access.


Structure

We need to implement an application of 3 files:

one. index.php – a file for installing and removing activities;

index.php
<?php
header('Content-Type: text/html; charset=UTF-8');

$protocol = $_SERVER['SERVER_PORT'] == '443' ? 'https' : 'http';
$host = explode(':', $_SERVER['HTTP_HOST']);
$host = $host[0];

define('BP_APP_HANDLER', $protocol.'://'.$host.explode('?', $_SERVER['REQUEST_URI'])[0]);
?>
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <script src="https://api.bitrix24.com/api/v1/"></script>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    </head>
    <body>
        <h1 style="text-align: center;margin-bottom: 2rem;width: 100%">Активити оптим</h1>
        <div style="margin-left: 30px;max-width: 26rem;">
            <div class="item">
                <h3 style="text-align: center;">"Выборка элементов СП"</h3>
                <button class="btn btn-primary" style="margin-right: 8px;" onclick="installActivity();"><i class="bi bi-download"></i> Установить действие БП</button>
                <button class="btn btn-primary" onclick="uninstallActivity('getspel');"><i class="bi bi-x-square"></i> Удалить действие</button>
            </div>
        </div>
        <script type="text/javascript">
            function installActivity()
            {
                var params = {
					'CODE': 'getspel', //код, уникальный для портала
					'HANDLER': 'https://example.com/example.app/handler.php',//ваш обработчик
					'AUTH_USER_ID': 1,
					'USE_SUBSCRIPTION': '',
					'NAME': 'Получить элементы СП',
                    'USE_PLACEMENT': 'Y',
                    'PLACEMENT_HANDLER': 'https://example.com/example.app/setting.php',//ваш файл настроек
					'DESCRIPTION': 'Принимает тип СП, категорию и стадию, выдаёт массив id элементов на стадии',
					'PROPERTIES': { //здесь параметры, которые будут задаваться через setting, чтобы не отлавливать символьные коды руками
						'typeSP': {
							'Name': 'Тип СП',
							'Type': 'string',
							'Required': 'Y',
							'Multiple': 'N'
						},
						'categoryID': { 
							'Name': 'Категория',
							'Type': 'string',
							'Required': 'Y',
							'Multiple': 'N'
						},
						'statusID': { 
							'Name': 'Статус',
							'Type': 'string',
							'Required': 'Y',
							'Multiple': 'N'
						},
                        'sTypeSP': {
							'Name': 'Тип СП',
							'Type': 'string',
							'Required': 'Y',
							'Multiple': 'N'
						},
						'sCategoryID': { 
							'Name': 'Категория',
							'Type': 'string',
							'Required': 'Y',
							'Multiple': 'N'
						},
						'sStatusID': { 
							'Name': 'Статус',
							'Type': 'string',
							'Required': 'Y',
							'Multiple': 'N'
						}
					},
                    'RETURN_PROPERTIES': { //вернём массив ID привязанных СП
                        'outputString': {
                            'Name': {
                                'ru': 'IDs',
                                'en': 'IDs'
                            },
                            'Type': 'string',
                            'Multiple': 'Y',
                            'Default': null
                        }
                    }
			    };

                BX24.callMethod(
                    'bizproc.activity.add',
                    params,
                    function(result)
                    {
                        if(result.error())
                            alert("Error: " + result.error());
                        else
                            alert("Успешно: " + result.data());
                    }
                );
            }

            function uninstallActivity(code)
            {
                let params = {
                    'CODE': code
                };

                BX24.callMethod(
                    'bizproc.activity.delete',
                    params,
                    function(result)
                    {
                        if(result.error())
                            alert('Error: ' + result.error());
                        else
                            alert("Успешно: " + result.data());
                    }
                );
            }
        </script>
    </body>
</html>

2. handler.php – handler, accepts parameters, returns a result;

handler.php
<?php
$protocol = $_SERVER['SERVER_PORT'] == '443' ? 'https' : 'http';
$host = explode(':', $_SERVER['HTTP_HOST']);
$host = $host[0];

define('BP_APP_HANDLER', $protocol.'://'.$host.$_SERVER['REQUEST_URI']);

if (!empty($_REQUEST['workflow_id']))//добавим простые проверки - измените под себя
{
    if (!empty($_REQUEST['properties']['typeSP'])){

        $par = array( //сформируем параметры для выборки элементов нужного СП на выбранном статусе
            'entityTypeId' => $_REQUEST['properties']['typeSP'], 
            'select'       => ['id'],
            'order'        => null, 
            'filter'       => ['categoryId' => $_REQUEST['properties']['categoryID'], 'stageId' => $_REQUEST['properties']['statusID']],
        );

        //используем вебхук с правами на CRM, чтобы не отвлекаться на Crest - настраивайте под задачу
        $result = callB24Method('https://example.bitrix24.ru/rest/1/59i35rrrzqg0np/','crm.item.list', $par); //запрашиваем ID's элементов СП

        $arr = [];
        foreach($result['result']['items'] as $item){ //готовим простой массив
            $arr[] = $item['id'];
        }

        //берем авторизацию из пришедшего БП, добавляем массив, возвращаем в БП
        $params = array(
            "auth" => $_REQUEST['auth']["access_token"],
            "event_token" => $_REQUEST["event_token"],
            "log_message" => "Элементы получены",
            "return_values" => array(
                "outputString" => $arr,
            )
        );
        $r = callB24Method('https://example.bitrix24.ru/rest/','bizproc.event.send', $params);
    }

}


function callB24Method($bitrix, $method, $params){ //напишем функцию для отправки запросов через вебхук
    $c = curl_init($bitrix . $method . '.json');

    curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($c, CURLOPT_POST, true);
    curl_setopt($c, CURLOPT_POSTFIELDS, http_build_query($params));

    $response = curl_exec($c);
    $response = json_decode($response, true);

    return $response;
}

3. settings.php – frontend frame in activity settings, our preprocessor.

settings.php
<?php
header('Content-Type: text/html; charset=UTF-8');

$protocol = $_SERVER['SERVER_PORT'] == '443' ? 'https' : 'http';
$host = explode(':', $_SERVER['HTTP_HOST']);
$host = $host[0];

define('BP_APP_HANDLER', $protocol.'://'.$host.explode('?', $_SERVER['REQUEST_URI'])[0]);

$obj = json_decode($_POST['PLACEMENT_OPTIONS']); //объект с параметрами, отрисуем сохранённые параметры
$sp       = $obj->current_values->sTypeSP;
$category = $obj->current_values->sCategoryID;
$status   = $obj->current_values->sStatusID;
?>
<!DOCTYPE html>
<html lang="en" dir="ltr">
    <head>
        <meta charset="utf-8">
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
        <script src="https://api.bitrix24.com/api/v1/"></script>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
        <style>
            .select {
                min-width: 25%;
                margin-bottom: 20px;
                padding-bottom: 3px;
                padding-top: 3px;
            }
            .label {
                font-size: 0.9rem;
                margin-top: 1rem;
                margin-left: 20rem;
            }
        </style>
    </head>
    <body>
        <!-- вёрстка может быть любой, главное - вызвать BX24.placement.call и сохранить новые параметры -->
        <div id="app" style="background-color:#f5f9f9; height: 250px;width: 853px;" >
            <div style="width: 635px;padding-right: 42px; float: right; display: inline-block;margin-top: 15px;">
                <div>
                    <select class="form-select select" v-model="typeSearch">
                        <option value="" disabled selected><?=$sp ?></option>
                        <option v-for="type in types" v-bind:value="{ id: type.entityTypeId, name: type.title }"> {{ type.title }}</option>
                    </select>
                </div>
                <div>
                    <select class="form-select select" v-model="categorySearch">
                        <option value="" disabled selected><?=$category ?></option>
                        <option v-for="category in categories" v-bind:value="{ id: category.id, name: category.name }"> {{ category.name }}</option>
                    </select>
                </div>
                <div >
                    <select class="form-select select" v-model="statusSearch">
                        <option value="" disabled selected><?=$status ?></option>
                        <option v-for="status in statuses" v-bind:value="{ id: status.STATUS_ID, name: status.NAME }"> {{ status.NAME }}</option>
                    </select>
                </div>
            </div>
            <div style="float: left; display: inline-block;margin-top: 15px;margin-left: 5rem;">
                <div> 
                    <p style="font-size: 0.9rem;text-align: end;padding-top: 4px;">Выберите СП:</p>
                </div>
                <div>
                    <p style="font-size: 0.9rem;text-align: end;margin-top: 29px;">Выберите воронку:</p>
                </div>
                <div>
                    <p style="font-size: 0.9rem;text-align: end;margin-top: 29px;">Выберите статус:</p>
                </div>
            </div>
        </div>
        <script>
            let app = new Vue({
                el: '#app',
                data: {
                    types: [],
                    categories: [],
                    statuses: [],
                    typeSearch: '',
                    categorySearch: '',
                    statusSearch: ''
                },
                created: function() { //получаем список СП после отрисовки фрейма
                    BX24.resizeWindow(853, 250);
                    BX24.callMethod(
                        'crm.type.list',
                        '',
                        function(result)
                        {
                            if(result.error())
                                alert("Error: " + result.error());
                            else

                                app.types = result.data().types;
                        }
                    );
                },
                watch: {
                    typeSearch: function() { //выбрали СП - подгружаем его воронки
                        BX24.callMethod(
                            'crm.category.list',
                            {"entityTypeId": app.typeSearch.id},
                            function(result)
                            {
                                if(result.error())
                                    alert("Error");
                                else

                                app.categories = result.data().categories;
                            }
                        );
                    },
                    categorySearch: function() {//выбрали воронку - подгружаем статусы
                        BX24.callMethod(
                            'crm.status.list',
                            {'filter': { "ENTITY_ID": 'DYNAMIC_' + app.typeSearch.id + '_STAGE_' + app.categorySearch.id}},
                            function(result)
                            {
                                if(result.error())
                                    alert("Error");
                                else

                                app.statuses = result.data();
                            }
                        );
                    },
                    statusSearch: function() {
                        BX24.placement.call( //обновим параметры после заполнения каждого пункта
                            'setPropertyValue',
                            {'typeSP': app.typeSearch.id, 'categoryID': app.categorySearch.id, 'statusID': app.statusSearch.id, 'sTypeSP': app.typeSearch.name, 'sCategoryID': app.categorySearch.name, 'sStatusID': app.statusSearch.name}
                        )
                    }
                }
            })
        </script>
    </body>
</html>

Implementation by example

We need an activity that will run on a deal and return the JV element at the selected stage as an array with an ID to iterate over with an iterator. Elements are documents, there are a lot of them, SPs are different, BP scenarios are different – we decided to make activations with preprocessing in the settings.

We recommend downloading all the code, deploying it on the server and following the article – we will cover all the key points that require customization of the example (links in index.php and handler.php)

  1. Let’s create index.phpcalled B24. The file in our case contains php check, html layout (header-buttons) and js script on the install/delete buttons. Below is an object with parameters for bizproc.activity.addpay attention to USE_PLACEMENT:

var params = {
			'CODE': 'getspel', //код, уникальный для портала
			'HANDLER': 'https:///example.com/example.app/handler.php',//ваш обработчик
			'AUTH_USER_ID': 1,
			'USE_SUBSCRIPTION': '',
			'NAME': 'Получить элементы СП',
            'USE_PLACEMENT': 'Y',
            'PLACEMENT_HANDLER': 'example.com/example.app/setting.php',//ваш файл настроек
			'DESCRIPTION': 'Принимает тип СП, категорию и стадию, выдаёт массив id элементов на стадии',
			'PROPERTIES': { //здесь параметры, которые будут задаваться через setting, чтобы не отлавливать символьные коды руками
    			'typeSP': {
            		'Name': 'Тип СП',
    				'Type': 'string',
    				'Required': 'Y',
    				'Multiple': 'N'
            	},
                  ....
			},
            'RETURN_PROPERTIES': { //вернём массив ID привязанных СП
                  'outputString': {
                      'Name': {
                          'ru': 'IDs',
                          'en': 'IDs'
                      },
                      'Type': 'string',
                      'Multiple': 'Y',
                      'Default': null
                    }
            }
};
  1. Let’s add a local application to the portal, specify a link to index.phpset the rights:

We need a CRM to access Smart Processes, a BP to create activities, and the right to embed apps in the activity settings card.
We need a CRM to access Smart Processes, a BP to create activities, and the right to embed apps in the activity settings card.
  1. Let’s go to the application, try to install (user rights are used in the application frame, so install and configure the activity from under the admin), in which case we will work out the errors:

    Successfully installed!
    Successfully installed!

    Let’s see the result:

    Activiti is available, but there will be an error in the settings
    Activiti is available, but there will be an error in the settings
  2. Let’s write the parameter settings code, the key point is to accept the parameters and update them after saving the settings, no code change is required:

<?php
//объект с параметрами, отрисуем в вёрстке
$obj = json_decode($_POST['PLACEMENT_OPTIONS']); 
$sp       = $obj->current_values->sTypeSP;
$category = $obj->current_values->sCategoryID;
$status   = $obj->current_values->sStatusID;

…Here is the layout…

BX24.placement.call( //обновим параметры после заполнения полей
                            'setPropertyValue',
                            {'typeSP': app.typeSearch.id, 'categoryID': app.categorySearch.id, 'statusID': app.statusSearch.id, 'sTypeSP': app.typeSearch.name, 'sCategoryID': app.categorySearch.name, 'sStatusID': app.statusSearch.name}
                        )
  1. Look at the settings card:

    Fill in the parameters and save.  We put the result in a string multiple field for demonstration
    Fill in the parameters and save. We put the result in a string multiple field for demonstration
  2. Now let’s go to handler.php. Here you will need to replace the authorization or place your webhook with CRM rights:

<?php
//добавим простые проверки - измените под себя
if (!empty($_REQUEST['workflow_id']))
{
  //сформируем параметры для выборки элементов нужного СП на выбранном статусе
  $par = array(
      'entityTypeId' => $_REQUEST['properties']['typeSP'], 
      'select'       => ['id'],
      'order'        => null, 
      'filter'       => ['categoryId' => $_REQUEST['properties']['categoryID'],'stageId' => $_REQUEST['properties']['statusID']],
  );

  //используем вебхук с правами на CRM, чтобы не отвлекаться на Crest - 
  //настраивайте под задачу
  $result = callB24Method('https://example.bitrix24.ru/rest/1/59itd45y6rzqg0np/',                    
                          'crm.item.list', $par); //запрашиваем ID's элементов СП

  $arr = [];
  foreach($result['result']['items'] as $item){ //готовим простой массив
          $arr[] = $item['id'];
  }

  //берем авторизацию из пришедшего БП, добавляем массив, возвращаем в БП
  $params = array(
      "auth" => $_REQUEST['auth']["access_token"],
      "event_token" => $_REQUEST["event_token"],
      "log_message" => "Элементы получены",
      "return_values" => array("outputString" => $arr)
  );
  $r = callB24Method('https://example.bitrix24.ru/rest/','bizproc.event.send',$params);
}
  1. We call the business process, we look at the result:

That’s all, thank you for your attention! We will be grateful for feedback in the comments and will try to answer questions.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *