Bitrix Audit à faire soi-même

Bonjour à tous.

Lorsque je cherchais des informations sur la journalisation (audit des événements) dans Bitrix, il n'y avait rien sur Habr, mais il y avait autre chose, mais qui le trouvera-t-il?

Pour reconstituer la base de connaissances, j'ai décidé d'écrire cet article: partager mon expérience et prévenir d'un éventuel rake.

Formulation du problème


Ma tâche était de développer le système de comptabilité le plus simple pour les structures publicitaires, selon les termes du contrat d'État, le système devrait fonctionner sur la base de Bitrix (version 15).

Il était possible de tout faire du vélo du côté de Bitrix, mais j'ai décidé que ce serait trop malhonnête par rapport au client, la fonctionnalité de Bitrix a été utilisée au maximum:

  • Authentification d'utilisateur
  • système de stockage de données (EAV)
  • éditeur de données
  • audit des gestionnaires d'événements
  • modèle de rôle pour l'autorisation des actions de l'utilisateur
  • gestion des utilisateurs
  • travailler avec des répertoires

Cet article concerne principalement les événements d'audit, je vais également parler un peu de l'autorisation et de l'ajout de signets personnalisés à la carte d'enregistrement du bloc d'informations.

Événements d'audit


Dans le système développé, le travail avec les données est effectué via l'API (ajout d'un nouvel objet à la carte et modification de ses coordonnées), les autres paramètres de l'objet sont modifiés via l'éditeur Bitrix, car l'API ne traite pas toutes les demandes de changement de données, alors l'audit uniquement dans l'API ne serait pas complet, ce qui signifie nous avons besoin d'un audit du côté de Bitrix.

Ce n'est pas que je ne savais pas ce qu'est l'audit, mais je n'ai pas à l'écrire souvent. Habituellement, cela est résolu en écrivant l'ancienne version de la ligne (anciennes données) dans une table distincte, et l'implémentation est entièrement effectuée du côté du SGBD. De plus, nous avons simultanément un état avant les changements et un état après les changements.

Pour écrire un audit sur les déclencheurs SGBD ou pour écrire un audit sur les événements Bitrix, la différence n'est pas grande.
Les événements à traiter sont enregistrés dans le script "bitrix / php_interface / init.php ", s'il n'y a pas de fichier, vous devez le créer:

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'));
//     ,  -    ,              ,   

Ceci est un exemple d'un gestionnaire, dans mon implémentation il y en a plusieurs, le code peut être consulté dans le référentiel, le lien sera à la fin de l'article.

La signature de la méthode de traitement de l'événement pour chaque événement a la sienne, que nous examinons spécifiquement dans la documentation ou les débuts de Bitrix et examinons les sources (plus clairement, tous les événements et moments d'appels de gestionnaires peuvent également être vus dans les sources pendant le débogage).

Et ici, nous sommes confrontés au fait que lorsque nous travaillons avec un SGBD, nous avons à la fois les (anciennes) données et les données qui seront enregistrées (nouvelles), et lorsque nous travaillons avec Bitrix, nous avons soit d'anciennes données soit de nouvelles données, mais pas lorsque il n'y aura pas d'ancien et de nouveau en même temps.

Un autre problème est qu'il y a plusieurs événements avant de modifier les données, et je ne pouvais pas comprendre la différence entre eux, la même histoire est lorsque plusieurs événements ont été déclenchés après la modification des données, les yeux regardent la richesse du choix, en fait, ce n'est qu'une illusion de choix.

mon humble avis sur Bitrix
, , _ , ( Delphi PHP), .

depricated «», .

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

, :)

, , , , , , , ? .


Utilisation de l'état global


Pour enregistrer et transmettre l'état entre les gestionnaires d'événements, vous devez disposer de variables globales. Les variables globales ne peuvent pas être refactorisées, j'utilise donc les propriétés statiques de la classe:

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;
}

Ici, vous devez faire une explication sur l'utilisation des "champs" et des "propriétés". Les champs sont les paramètres de chaque enregistrement de bloc info (identifiant, nom, «parent» du bloc info), les propriétés sont des paramètres spécifiques aux enregistrements d'un type particulier de bloc info, dans le modèle EAV, ce sont des attributs et leurs valeurs.

Les "noms" sont des noms d'attributs, dans un cas, nous avons des identifiants d'attributs et de noms, dans un autre événement, nous n'avons que des identifiants, et pour un beau dossier dans le journal, nous devons enregistrer les noms (nous pouvons bien sûr soustraire l'identifiant, mais pour une raison que vous ne voulez pas).

"Opération" est l'opération en cours, pour travailler avec des "champs" Bitrix donne des événements spécifiques, quand on travaille avec des "propriétés" (fonctions "SetPropertyValues" et "SetPropertyValuesEx"), il n'y a pas d'événement avec le type d'opération, il y a un événement avant et après l'appel, mais il n'y en a pas On sait quelle opération est effectuée (ajouter / mettre à jour / supprimer), par conséquent, l'opération doit également être transférée du gestionnaire au gestionnaire.

Peut-être que je ne l'ai pas compris, et dans Bitrix vous pouvez simultanément voir les valeurs telles qu'elles étaient et comment elles seront, ou peut-être que l'état avant et l'état après devraient être enregistrés séparément, mais pour une raison quelconque, j'ai décidé que l'entrée de journal devrait être au format «propriété % name% était% value avant le changement% => il est devenu% value après% »et donc il y a beaucoup de problèmes avec le maintien de l'état global.

Déterminer si des changements sont nécessaires


Curieusement, mais notre gestionnaire sera appelé pour modifier tout enregistrement d'un bloc d'informations.
Par conséquent, dans le gestionnaire, nous devons comprendre l'enregistrement de quel bloc d'informations nous avons reçu dans les paramètres.
L'infoblock d'un enregistrement est caractérisé par deux valeurs: l'infoblock lui-même ('IBLOCK_ID') et sa section ('IBLOCK_SECTION_ID'), et l'identifiant de section réside parfois dans le tableau de valeurs avec l'index 'IBLOCK_SECTION_ID', et parfois 'IBLOCK_SECTION', donc j'ai lu l'identifiant de section par les deux clés avec priorité pour 'IBLOCK_SECTION'.

De plus, dans certaines situations, l'identifiant de section ne peut pas être déterminé en principe, par conséquent, la vérification de la nécessité d'ajouter une entrée au journal d'audit ne doit être effectuée que sur la base de l'identifiant du bloc d'informations.

Après avoir déterminé le bloc d'informations et la section, nous décidons de la nécessité d'enregistrer l'événement dans le journal.

Enregistrement de l'état avant les modifications


Chaque entrée infoblock se compose de deux parties, une partie est «champs» et ces paramètres sont modifiés par des méthodes:

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

L'autre partie concerne les méthodes «propriétés»:
  • CIBlockElement :: SetPropertyValues ​​()
  • CIBlockElement :: SetPropertyValuesEx ()

Je ne me souviens pas pourquoi, mais il vaut mieux s’abstenir d’utiliser CIBlockElement :: SetPropertyValuesEx () dans mon code.

Chaque partie des données de son gestionnaire d'événements est distincte de l'autre, par conséquent, en cliquant sur le bouton "Enregistrer", deux entrées dans le journal d'audit peuvent être créées.

Les signatures d'événements pour les «champs» et les «propriétés» sont différentes, dans la partie avec le traitement des «champs», nous enregistrons l'état actuel des champs et définissons la valeur sur opération, dans la partie avec le traitement des «propriétés», nous enregistrons les propriétés et leurs noms.

Nous le faisons bien sûr dans le gestionnaire "avant".

Changer d'inscription


Dans le gestionnaire "après", nous écrivons dans le journal.

Lors de la compilation d'une liste de différences dans les enregistrements, il y a un problème avec les attributs qui peuvent prendre plusieurs valeurs. Le format d'enregistrement de leur état «avant» et de l'état «après» diffère tellement que l'un ne peut pas en déduire l'autre, donc lorsque vous déterminez s'il faut enregistrer les modifications, vous conclurez toujours que l'enregistrement est nécessaire.
Le code source de l'audit dans le référentiel dans le fichier src / Topliner / Scheme / Logger.php .

Ajout d'un onglet personnalisé à la carte infoblock (dans le classificateur Bitrix)


Pour ajouter une carte, une approche similaire à l'audit est utilisée - nous ajoutons un gestionnaire:

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

Dans la méthode 'OnInit', nous définissons les autres méthodes pour travailler avec l'onglet dans l'éditeur 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;
    }
}

Tout d'abord, bien sûr, nous vérifions que pour ce bloc d'informations, nous devons afficher notre onglet utilisateur, si nécessaire, nous définissons des méthodes.

Il n'y a rien de spécial à dire ici, voir les sources dans le référentiel src / Topliner / Scheme / PermitTab.php , lire la documentation.

Modèle de rôle pour l'autorisation des actions utilisateur


Les méthodes d'autorisation de Bitrix fonctionnent avec une échelle de droits linéaire, c'est-à-dire un scénario où l'un peut avoir le premier et le deuxième, mais pas le troisième, et l'autre peut être le deuxième et le troisième, mais pas le premier, vous ne pouvez pas implémenter Bitrix directement.

Pour ces scénarios délicats dans Bitrix, il y a simplement une vérification de certaines autorisations, et dans le code lors de l'exécution de l'opération, vous voyez si l'utilisateur a l'autorisation ou non, et vous autorisez les premier et deuxième rôles d'un rôle et les deuxième et troisième rôles du rôle.

        $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 () - identifiant du bloc d'informations
  2. $ constSec-> getSection () - identifiant de section
  3. BitrixPermission :: ELEMENT_ADD - code d'autorisation

Dans le classificateur (répertoire des blocs d'informations) des blocs d'informations et des sections nécessaires, vous attribuez les droits appropriés aux rôles. Distribuez les rôles aux utilisateurs et tout sera sous votre contrôle.

Authentification


Si vous avez besoin d'une authentification utilisateur pour certaines pages, ajoutez simplement:

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

//      

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

Lors du chargement d'une telle page, Bitrix enverra un formulaire de connexion si l'utilisateur ne s'est pas encore connecté.

Si vous avez juste besoin de connecter Bitrix dans une sorte de script, alors:

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

Travailler avec des répertoires


Les «nouveaux» répertoires (à partir de novembre 2019) dans Bitrix sont appelés «HighloadBlock», travailler avec eux est un peu non standard.

Chaque bloc Hyload peut donc être stocké dans une table distincte, pour accéder au répertoire, chaque fois que vous devez déterminer le nom de la table à lire. Pour ne pas le faire constamment, dans Bitrix, vous devez créer une instance d'une classe pour accéder aux données. Une instance est créée sur deux lignes:

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

Où «ConstructionTypes» est le code de référence. Afin de

ne pas écrire constamment ces deux lignes, vous pouvez écrire une classe qui créera pour nous des instances de référence ( src / Topliner / Bitrix / BitrixReference.php ).

Ensuite, nous travaillons avec l'instance comme avec la classe Bitrix ORM habituelle (D7):

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

Référentiel (soigneusement, beaucoup de copier-coller)

Merci de votre attention.

All Articles