Bitrix Do-it-yourself-Audit

Hallo alle zusammen.

Als ich nach Informationen über die Protokollierung (Prüfung von Ereignissen) in Bitrix suchte, gab es auf Habr nichts, den Rest haben Sie etwas ausgeführt, aber wer wird es dort finden?

Um die Wissensbasis aufzufüllen, habe ich beschlossen, diesen Artikel zu schreiben: Teilen Sie meine Erfahrungen und warnen Sie vor einem möglichen Rechen.

Formulierung des Problems


Meine Aufgabe war es, das einfachste Buchhaltungssystem für Werbestrukturen zu entwickeln. Gemäß den Bestimmungen des Staatsvertrags sollte das System auf der Grundlage von Bitrix (Version 15) funktionieren.

Es war möglich, alles auf der Seite von Bitrix zu fahren, aber ich entschied, dass es in Bezug auf den Kunden zu unehrlich wäre, die Funktionalität von Bitrix wurde maximal genutzt:

  • Benutzerauthentifizierung
  • Datenspeichersystem (EAV)
  • Dateneditor
  • Audit Event Handler
  • Vorbild für die Autorisierung von Benutzeraktionen
  • Benutzerverwaltung
  • arbeite mit Verzeichnissen

In diesem Artikel geht es hauptsächlich um Überwachungsereignisse. Ich werde auch ein wenig über die Autorisierung und das Hinzufügen benutzerdefinierter Lesezeichen zur Infoblock-Aufzeichnungskarte sprechen.

Audit-Ereignisse


In dem entwickelten System wird die Arbeit mit Daten über die API ausgeführt (Hinzufügen eines neuen Objekts zur Karte und Ändern seiner Koordinaten), andere Parameter des Objekts werden über den Bitrix-Editor bearbeitet, da die API nicht alle Datenänderungsanforderungen verarbeitet, und die Prüfung nur in der API nicht abgeschlossen wäre Wir brauchen ein Audit auf der Seite von Bitrix.

Es ist nicht so, dass ich nicht wusste, was Audit ist, aber ich muss es nicht oft schreiben. Normalerweise wird dies gelöst, indem die alte Version der Zeile (alte Daten) in eine separate Tabelle geschrieben wird und die Implementierung vollständig auf der DBMS-Seite durchgeführt wird. Darüber hinaus haben wir gleichzeitig einen Zustand vor den Änderungen und einen Zustand nach den Änderungen.

Um eine Prüfung für DBMS-Trigger oder eine Prüfung für Bitrix-Ereignisse zu schreiben, ist der Unterschied nicht groß.
Ereignisse zur Verarbeitung werden im Skript "bitrix / php_interface / init.php "Wenn keine Datei vorhanden ist, müssen Sie diese erstellen:

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

Dies ist ein Beispiel für einen Handler. In meiner Implementierung gibt es mehrere davon. Der Code kann im Repository angezeigt werden. Der Link befindet sich am Ende des Artikels.

Die Signatur der Methode zur Verarbeitung des Ereignisses für jedes Ereignis hat eine eigene, die wir speziell in der Bitrix-Dokumentation oder im Debüt betrachten und die Quellen betrachten (klarer gesagt , alle Ereignisse und Momente des Aufrufs von Handlern können auch in den Quellen während des Debuggens angezeigt werden).

Und hier sehen wir uns mit der Tatsache konfrontiert, dass wir bei der Arbeit mit einem DBMS sowohl aktuelle (alte) Daten als auch die Daten haben, die aufgezeichnet werden (neu), und bei der Arbeit mit Bitrix entweder alte oder neue Daten haben, aber nicht wann Es wird kein Altes und Neues gleichzeitig geben.

Ein weiteres Problem ist, dass es mehrere Ereignisse gibt, bevor die Daten geändert werden, und ich konnte den Unterschied zwischen ihnen nicht verstehen. In derselben Geschichte geht es um das Auslösen mehrerer Ereignisse nach dem Ändern der Daten. Die Augen starren auf die Fülle der Auswahl, tatsächlich ist es nur eine Illusion der Auswahl.

meine bescheidene Meinung zu Bitrix
, , _ , ( Delphi PHP), .

depricated «», .

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

, :)

, , , , , , , ? .


Verwenden des globalen Status


Um den Status zwischen Ereignishandlern zu speichern und zu übergeben, müssen globale Variablen vorhanden sein. Globale Variablen können nicht überarbeitet werden, daher verwende ich die statischen Eigenschaften der Klasse:

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

Hier müssen Sie die Verwendung von "Feldern" und "Eigenschaften" verdeutlichen. Felder sind die Parameter, über die jeder Infoblock-Datensatz verfügt (Kennung, Name, Infoblock-Elternteil). Eigenschaften sind Parameter, die für Datensätze eines bestimmten Infoblock-Typs spezifisch sind. Im EAV-Modell sind dies Attribute und deren Werte.

"Namen" sind Namen von Attributen, in einem Fall haben wir Bezeichner von Attributen und Namen, in einem anderen Fall haben wir nur Bezeichner, und für einen schönen Datensatz im Journal müssen wir die Namen speichern (wir können den Bezeichner natürlich subtrahieren, aber aus irgendeinem Grund möchten Sie nicht).

"Operation" ist die aktuelle Operation. Für die Arbeit mit "Feldern" gibt Bitrix bestimmte Ereignisse aus. Wenn Sie mit "Eigenschaften" (Funktionen "SetPropertyValues" und "SetPropertyValuesEx") arbeiten, gibt es kein Ereignis mit der Art der Operation, es gibt ein Ereignis vor und nach dem Aufruf, aber nicht Es ist bekannt, welche Operation ausgeführt wird (Hinzufügen / Aktualisieren / Löschen), daher muss die Operation auch vom Handler zum Handler übertragen werden.

Vielleicht habe ich es nicht herausgefunden und in Bitrix können Sie gleichzeitig die Werte so sehen, wie sie waren und wie sie sein werden, oder vielleicht sollte der Status vor und der Status danach separat aufgezeichnet werden, aber aus irgendeinem Grund habe ich beschlossen, dass der Protokolleintrag im Format "Eigenschaft" vorliegen sollte % name% war% value vor der Änderung% => es wurde% value nach% ”und daher gibt es viele Probleme bei der Aufrechterhaltung des globalen Zustands.

Feststellen, ob Änderungen erforderlich sind


Seltsamerweise wird unser Handler aufgerufen, um jeden Datensatz eines Informationsblocks zu ändern.
Daher müssen wir im Handler verstehen, welchen Informationsblock wir in den Parametern erhalten haben.
Der Infoblock eines Datensatzes ist durch zwei Werte gekennzeichnet: den Infoblock selbst ('IBLOCK_ID') und seinen Abschnitt ('IBLOCK_SECTION_ID'), und die Abschnittskennung liegt manchmal im Wertearray mit dem Index 'IBLOCK_SECTION_ID' und manchmal 'IBLOCK_SECTION', also lese ich die Abschnittskennung durch beide Schlüssel mit Priorität für 'IBLOCK_SECTION'.

Darüber hinaus kann in einigen Situationen die Abschnittskennung im Prinzip nicht bestimmt werden, weshalb die Notwendigkeit, einen Eintrag zum Überwachungsprotokoll hinzuzufügen, nur auf der Grundlage der Informationsblockkennung überprüft werden muss.

Nachdem wir den Informationsblock und den Abschnitt festgelegt haben, entscheiden wir, ob das Ereignis im Journal registriert werden muss.

Status vor Änderungen speichern


Jeder Infoblock-Eintrag besteht aus zwei Teilen, ein Teil besteht aus "Feldern", und diese Parameter werden durch Methoden geändert:

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

Der andere Teil sind die "Eigenschaften" -Methoden:
  • CIBlockElement :: SetPropertyValues ​​()
  • CIBlockElement :: SetPropertyValuesEx ()

Ich weiß nicht mehr warum, aber es ist besser, CIBlockElement :: SetPropertyValuesEx () in meinem Code nicht zu verwenden.

Jeder Teil der Daten in der Ereignisbehandlungsroutine ist vom anderen getrennt. Durch Klicken auf die Schaltfläche "Speichern" können daher zwei Einträge im Überwachungsprotokoll erstellt werden.

Die Signaturen der Ereignisse für "Felder" und "Eigenschaften" sind unterschiedlich. In dem Teil mit der Verarbeitung von "Feldern" speichern wir den aktuellen Status für Felder und setzen den Wert auf Operation. In dem Teil mit der Verarbeitung von "Eigenschaften" speichern wir Eigenschaften und deren Namen.

Wir machen das natürlich im "Vorher" -Handler.

Registrierung ändern


Im "After" -Handler schreiben wir in das Protokoll.

Beim Zusammenstellen einer Liste von Unterschieden in Datensätzen tritt ein Problem mit Attributen auf, die mehrere Werte annehmen können. Das Format für die Aufzeichnung des Status "vor" und des Status "nach" unterscheidet sich so stark, dass einer den anderen nicht ableiten kann. Wenn Sie also entscheiden, ob Änderungen registriert werden sollen, werden Sie immer zu dem Schluss kommen, dass eine Registrierung erforderlich ist.
Der Quellcode des Audits im Repository in der Datei src / Topliner / Scheme / Logger.php .

Hinzufügen einer benutzerdefinierten Registerkarte zur Infoblock-Karte (im Bitrix-Klassifikator)


Um eine Karte hinzuzufügen, wird ein ähnlicher Ansatz wie beim Auditing verwendet - wir fügen einen Handler hinzu:

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

In der 'OnInit'-Methode definieren wir die anderen Methoden für die Arbeit mit der Registerkarte im Bitrix-Editor:

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

Zuerst überprüfen wir natürlich, ob wir für diesen Informationsblock unsere Benutzerregisterkarte anzeigen müssen, falls erforderlich, setzen wir Methoden.

Hier gibt es nichts Besonderes zu erzählen. Lesen Sie die Quellen im Repository src / Topliner / Scheme / PermitTab.php und lesen Sie die Dokumentation.

Vorbild für die Autorisierung von Benutzeraktionen


Die Autorisierungsmethoden von Bitrix arbeiten mit einer linearen Rechte-Skala, dh einem Szenario, in dem eine die erste und zweite, aber nicht die dritte und die andere die zweite und dritte, aber nicht die erste sein kann. Sie können Bitrix nicht direkt implementieren.

Für solche kniffligen Szenarien in Bitrix wird lediglich eine Berechtigung überprüft, und im Code wird beim Ausführen des Vorgangs angezeigt, ob der Benutzer über eine Berechtigung verfügt oder nicht, und Sie erlauben die erste und zweite Rolle, und die zweite und dritte erlauben die andere Rolle.

        $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 () - Informationsblockkennung
  2. $ constSec-> getSection () - Abschnittskennung
  3. BitrixPermission :: ELEMENT_ADD - Berechtigungscode

Im Klassifikator (Informationsblockverzeichnis) in den erforderlichen Informationsblöcken und Abschnitten weisen Sie den Rollen die entsprechenden Rechte zu. Verteilen Sie Rollen an Benutzer und alles wird unter Ihrer Kontrolle sein.

Authentifizierung


Wenn Sie für einige Seiten eine Benutzerauthentifizierung benötigen, fügen Sie einfach Folgendes hinzu:

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

//      

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

Beim Laden einer solchen Seite gibt Bitrix ein Anmeldeformular aus, wenn sich der Benutzer noch nicht angemeldet hat.

Wenn Sie Bitrix nur in einem Skript verbinden müssen, dann:

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

Arbeiten Sie mit Verzeichnissen


Die "neuen" Verzeichnisse (Stand November 2019) in Bitrix heißen "HighloadBlock", die Arbeit mit ihnen ist etwas unüblich.

Jeder Hyload-Block kann daher in einer separaten Tabelle gespeichert werden, um jedes Mal auf das Verzeichnis zuzugreifen, wenn Sie den Tabellennamen zum Lesen ermitteln müssen. Um dies nicht ständig zu tun, müssen Sie in Bitrix eine Instanz einer Klasse für den Zugriff auf Daten erstellen. Eine Instanz wird in zwei Zeilen erstellt:

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

Wobei 'ConstructionTypes' der Referenzcode ist. Um

diese beiden Zeilen nicht ständig zu schreiben, können Sie eine Klasse schreiben, die Referenzinstanzen für uns erstellt ( src / Topliner / Bitrix / BitrixReference.php ).

Als nächstes arbeiten wir mit der Instanz wie mit der üblichen Bitrix ORM-Klasse (D7):

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

Repository (sorgfältig, viel kopieren und einfügen)

Vielen Dank für Ihre Aufmerksamkeit.

All Articles