Bitrix Auditoría de bricolaje

Hola a todos.

Cuando buscaba información sobre el registro (auditoría de eventos) en Bitrix, no había nada en Habr, pero había algo más en el resto, pero ¿quién lo encontrará allí?

Para reponer la base de conocimiento, decidí escribir este artículo: compartir mi experiencia y advertir sobre un posible rastrillo.

Formulación del problema


Mi tarea consistía en desarrollar el sistema de contabilidad más simple para las estructuras publicitarias, según los términos del contrato estatal, el sistema debería funcionar sobre la base de Bitrix (versión 15).

Era posible montar en bicicleta todo al lado de Bitrix, pero decidí que sería demasiado deshonesto en relación con el cliente, la funcionalidad de Bitrix se utilizó al máximo:

  • autenticacion de usuario
  • sistema de almacenamiento de datos (EAV)
  • editor de datos
  • controladores de eventos de auditoría
  • modelo a seguir para autorizar acciones de usuario
  • Gestión de usuarios
  • trabajar con directorios

Este artículo trata principalmente de eventos de auditoría, también hablaré un poco sobre la autorización y la adición de marcadores personalizados a la tarjeta de registro del bloque de información.

Eventos de auditoria


En el sistema desarrollado, el trabajo con datos se realiza a través de la API (agregando un nuevo objeto al mapa y cambiando sus coordenadas), otros parámetros del objeto se editan a través del editor de Bitrix, ya que la API no procesa todas las solicitudes de cambio de datos, por lo que la auditoría solo en la API no estaría completa, lo que significa Necesitamos una auditoría del lado de Bitrix.

No es que no supiera qué es la auditoría, pero no tengo que escribirla con frecuencia. Por lo general, esto se resuelve escribiendo la versión anterior de la fila (datos antiguos) en una tabla separada, y la implementación se realiza completamente en el lado DBMS. Además, simultáneamente tenemos un estado antes de los cambios y un estado después de los cambios.

Para escribir una auditoría en los desencadenantes de DBMS, o para escribir una auditoría en eventos de Bitrix, la diferencia no es grande.
Los eventos para el procesamiento se registran en el script "bitrix / php_interface / init.php ", si no hay un archivo, debe crearlo:

use Topliner\Scheme\Logger;
//  
require_once($_SERVER['DOCUMENT_ROOT'] . '/vendor/autoload.php');
//        
if (!defined('IBLOCK_MODULE')) {
    define('IBLOCK_MODULE', 'iblock');
}
//    
AddEventHandler(IBLOCK_MODULE, 'OnIBlockElementAdd',
    Array(Logger::class, 'OnAdd'));
//     ,  -    ,              ,   

Este es un ejemplo de un controlador, en mi implementación hay varios de ellos, el código se puede ver en el repositorio, el enlace estará al final del artículo.

La firma del método para procesar el evento para cada evento tiene la suya, que observamos específicamente en la documentación de Bitrix o debutamos y miramos las fuentes (más claramente, todos los eventos y momentos de los controladores de llamadas también se pueden ver en las fuentes durante la depuración).

Y aquí nos enfrentamos al hecho de que cuando trabajamos con un DBMS, tenemos tanto datos actuales (antiguos) como datos que se registrarán (nuevos), y cuando trabajamos con Bitrix, tenemos datos antiguos o nuevos, pero no cuando no habrá viejos y nuevos al mismo tiempo.

Otro problema es que hay varios eventos antes de cambiar los datos, y no pude entender la diferencia entre ellos, la misma historia es cuando se dispararon varios eventos después de que se cambiaron los datos, los ojos miran fijamente desde la gran cantidad de opciones, de hecho, es solo una ilusión de elección.

mi humilde opinión sobre Bitrix
, , _ , ( Delphi PHP), .

depricated «», .

« » ( ) «» (, , ) , . , «» .

, :)

, , , , , , , ? .


Usando el estado global


Para guardar y pasar el estado entre los controladores de eventos, debe tener variables globales. Las variables globales son algo que no se puede refactorizar, por lo que utilizo las propiedades estáticas de la clase:

class Logger
{
    const CHANGE = 'change';
    const REMOVE = 'remove';
    const CREATE = 'create';
    const UNDEFINED = 'undefined';

    public static $operation = self::UNDEFINED;

    const NO_VALUE = [];

    private static $fields = self::NO_VALUE;
    private static $properties = self::NO_VALUE;
    private static $names = self::NO_VALUE;
}

Aquí debe hacer una explicación sobre el uso de "campos" y "propiedades". Los campos son los parámetros que tiene cada registro de bloque de información (identificador, nombre, bloque de información "padre"), las propiedades son parámetros específicos para registros de un tipo particular de bloque de información, en el modelo EAV, estos son atributos y sus valores.

Los "nombres" son nombres de atributos, en un evento tenemos identificadores de atributos y nombres, en otro evento solo tenemos identificadores, y para un hermoso registro en el diario necesitamos guardar los nombres (por supuesto, podemos restar el identificador, pero por alguna razón no desea).

"Operación" es la operación actual, para trabajar con "campos" Bitrix proporciona eventos específicos, cuando se trabaja con "propiedades" (funciones "SetPropertyValues" y "SetPropertyValuesEx"), no hay evento con el tipo de operación, hay un evento antes y después de la llamada, pero no Se sabe qué operación se realiza (agregar / actualizar / eliminar), por lo tanto, la operación también debe transferirse del controlador al controlador.

Tal vez no lo descubrí, y en Bitrix puede ver simultáneamente los valores como estaban y cómo serán, o tal vez fue necesario registrar por separado el estado anterior y el posterior, pero por alguna razón decidí que la entrada del registro debería estar en el formato "propiedad % name% era% value antes del cambio% => se convirtió en% value después de% ”y, por lo tanto, existen muchos problemas para mantener el estado global.

Determinar si se requieren cambios


Por extraño que parezca, se llamará a nuestro controlador para cambiar cualquier registro de cualquier bloque de información.
Por lo tanto, en el controlador, necesitamos comprender el registro de qué bloque de información recibimos en los parámetros.
El bloque de información de un registro se caracteriza por dos valores: el bloque de información en sí ('IBLOCK_ID') y su sección ('IBLOCK_SECTION_ID'), y el identificador de sección a veces se encuentra en la matriz de valores con el índice 'IBLOCK_SECTION_ID', y a veces 'IBLOCK_SECTION', así que leo el identificador de sección por ambas teclas con prioridad para 'IBLOCK_SECTION'.

Además, en algunas situaciones, el identificador de sección no puede determinarse en principio, por lo tanto, la verificación de la necesidad de agregar una entrada al registro de auditoría debe realizarse solo en función del identificador del bloque de información.

Después de determinar el bloque de información y la sección, decidimos la necesidad de registrar el evento en el diario.

Guardar estado antes de cambios


Cada entrada del bloque de información consta de dos partes, una parte es "campos", y estos parámetros se cambian por métodos:

  • CIBlockElement :: Add ()
  • CIBlockElement :: Update ()
  • CIBlockElement :: Delete ()

La otra parte son los métodos de "propiedades":
  • CIBlockElement :: SetPropertyValues ​​()
  • CIBlockElement :: SetPropertyValuesEx ()

No recuerdo por qué, pero es mejor abstenerse de usar CIBlockElement :: SetPropertyValuesEx () en mi código.

Cada parte de los datos en su controlador de eventos es independiente de la otra, por lo tanto, al hacer clic en el botón "Guardar", se pueden crear dos entradas en el registro de auditoría.

Las firmas de eventos para "campos" y "propiedades" son diferentes, en la parte con el procesamiento de "campos" guardamos el estado actual de los campos y establecemos el valor para la operación, en la parte con el procesamiento de "propiedades" guardamos las propiedades y sus nombres.

Por supuesto, hacemos esto en el manejador "antes".

Cambiar registro


En el controlador "después", escribimos en el registro.

Al compilar una lista de diferencias en los registros, hay un problema con los atributos que pueden tomar múltiples valores. El formato para registrar su estado “antes” y el estado “después” difiere tanto que uno no puede deducir el otro, por lo que al determinar si se registran cambios, siempre concluirá que el registro es necesario.
El código fuente de la auditoría en el repositorio en el archivo src / Topliner / Scheme / Logger.php .

Agregar una pestaña personalizada a la tarjeta de bloque de información (en el clasificador de Bitrix)


Para agregar una tarjeta, se utiliza un enfoque similar a la auditoría: agregamos un controlador:

AddEventHandler('main', 'OnAdminIBlockElementEdit',
    Array(PermitTab::class, 'OnInit'));

En el método 'OnInit', definimos los otros métodos para trabajar con la pestaña en el editor de Bitrix:

class PermitTab
{
    const LINKS = 'links';

    function OnInit($arArgs)
    {
        $permits = BitrixScheme::getPermits();
        $pubPermits = BitrixScheme::getPublishedPermits();
        $blockId = (int)$arArgs['IBLOCK']['ID'];
        $letShow = $blockId === $permits->getBlock()
            || $blockId === $pubPermits->getBlock();
        $result = false;
        if ($letShow) {
            $result = [
                'TABSET' => 'LinksToConstructs',
                'GetTabs' => [static::class, 'GetTabs'],
                'ShowTab' => [static::class, 'ShowTab'],
                'Action' => [static::class, 'Action'],
                'Check' => [static::class, 'Check'],
            ];
        }

        return $result;
    }
}

Primero, por supuesto, verificamos que para este bloque de información necesitamos mostrar nuestra pestaña de usuario, si es necesario, establecemos métodos.

No hay nada especial que contar aquí, vea las fuentes en el repositorio src / Topliner / Scheme / PermitTab.php , lea la documentación.

Modelo a seguir para autorizar acciones de usuario


Los métodos de autorización de Bitrix funcionan con una escala de derechos lineal, es decir, un escenario en el que uno puede tener el primero y el segundo, pero no el tercero, y el otro puede ser el segundo y el tercero, pero no el primero, no puede implementar Bitrix directamente.

Para escenarios tan complicados en Bitrix, simplemente hay una verificación de algún permiso, y en el código al realizar la operación, puede ver si el usuario tiene permiso o no, y permite el primer y segundo roles, y el segundo y el tercero permiten el otro rol.

        $constSec = BitrixScheme::getConstructs();
        $isAllow = CIBlockSectionRights::UserHasRightTo(
            $constSec->getBlock(), $constSec->getSection(),
            BitrixPermission::ELEMENT_ADD, false);
        if (!$isAllow) {
            $output['message'] = 'Forbidden, not enough permission;';
        }

  1. $ constSec-> getBlock () - identificador de bloque de información
  2. $ constSec-> getSection () - identificador de sección
  3. BitrixPermission :: ELEMENT_ADD - código de permiso

En el clasificador (directorio de bloques de información) en los bloques y secciones de información necesarios, asigna los derechos apropiados a los roles. Distribuya roles a los usuarios y todo estará bajo su control.

Autenticación


Si necesita autenticación de usuario para algunas páginas, simplemente agregue:

<?php
define("NEED_AUTH", true);
require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/header.php");
?>

//      

<?php
require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/footer.php");
?>

Al cargar dicha página, Bitrix entregará un formulario de inicio de sesión si el usuario aún no ha iniciado sesión.

Si solo necesita conectar Bitrix en algún tipo de script, entonces:

require_once($_SERVER['DOCUMENT_ROOT']
    . '/bitrix/modules/main/include/prolog_before.php');

Trabajar con directorios


Los directorios "nuevos" (a partir de noviembre de 2019) en Bitrix se llaman "HighloadBlock", trabajar con ellos es un poco no estándar.

Cada bloque Hyload se puede almacenar en una tabla separada, por lo tanto, para acceder al directorio, cada vez que necesite determinar el nombre de la tabla a leer. Para no hacer esto constantemente, en Bitrix necesita crear una instancia de una clase para acceder a los datos. Se crea una instancia en dos líneas:

CModule::IncludeModule('highloadblock');
$entity = HighloadBlockTable::compileEntity('ConstructionTypes');
$reference = $entity->getDataClass();

Donde 'ConstructionTypes' es el código de referencia. Para

no escribir constantemente estas dos líneas, puede escribir una clase que creará instancias de referencia para nosotros ( src / Topliner / Bitrix / BitrixReference.php ).

A continuación, trabajamos con la instancia como con la clase habitual de Bitrix ORM (D7):

/* @var $reference DataManager */
$data = $reference::getList(array(
    'select' => array('UF_NAME', 'UF_XML_ID'),
    'filter' => array('UF_TYPE_ID' => 0)
    ));

Repositorio (cuidadosamente, mucha copia y pegado)

Gracias por su atención.

All Articles