بيتريكس تدقيق بنفسك

تحية للجميع.

عندما كنت أبحث عن معلومات حول تسجيل الدخول (تدقيق الأحداث) في Bitrix ، لم يكن هناك أي شيء في Habr ، ولكن كان هناك شيء آخر ، ولكن من سيجدها هناك؟

لتجديد قاعدة المعرفة ، قررت أن أكتب هذا المقال: شارك تجربتي وحذر من أشعل النار.

صياغة المشكلة


كانت مهمتي هي تطوير أبسط نظام محاسبي للهياكل الإعلانية ، بموجب شروط عقد الدولة ، يجب أن يعمل النظام على أساس Bitrix (الإصدار 15).

كان من الممكن ركوب كل شيء على جانب Bitrix ، لكنني قررت أنه سيكون غير أمين للغاية فيما يتعلق بالعميل ، تم استخدام وظائف Bitrix إلى أقصى حد:

  • مصادقة المستخدم
  • نظام تخزين البيانات (EAV)
  • محرر البيانات
  • معالجات أحداث المراجعة
  • قدوة لتفويض إجراءات المستخدم
  • إدارةالمستخدم
  • العمل مع الدلائل

تتناول هذه المقالة بشكل أساسي أحداث المراجعة ، وسأتحدث أيضًا قليلاً عن التفويض وإضافة إشارات مرجعية مخصصة إلى بطاقة تسجيل كتلة المعلومات.

أحداث المراجعة


في النظام المطور ، يتم العمل مع البيانات من خلال واجهة برمجة التطبيقات (إضافة كائن جديد إلى الخريطة وتغيير إحداثياته) ، ويتم تحرير معلمات الكائن الأخرى من خلال محرر Bitrix ، نظرًا لأن API لا تعالج جميع طلبات تغيير البيانات ، فلن يكتمل التدقيق في واجهة برمجة التطبيقات فقط ، مما يعني نحتاج إلى مراجعة على جانب Bitrix.

ليس الأمر أنني لم أكن أعرف ما هو التدقيق ، ولكن ليس علي أن أكتبه كثيرًا. عادة يتم حل هذا عن طريق كتابة الإصدار القديم من الصف (البيانات القديمة) في جدول منفصل ، ويتم التنفيذ بالكامل على جانب DBMS. علاوة على ذلك ، لدينا في نفس الوقت حالة قبل التغييرات وحالة بعد التغييرات.

لكتابة تدقيق على مشغلات DBMS ، أو لكتابة تدقيق على أحداث Bitrix ، فإن الفرق ليس كبيرًا.
يتم تسجيل أحداث المعالجة في البرنامج النصي "bitrix / php_interface / init.php "، إذا لم يكن هناك ملف ، فأنت بحاجة إلى إنشائه:

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

هذا مثال على معالج واحد ، في تنفيذي هناك العديد منها ، يمكن عرض الرمز في المستودع ، سيكون الرابط في نهاية المقالة.

يحتوي توقيع طريقة معالجة الحدث لكل حدث على خاصته ، والتي ننظر إليها تحديدًا في وثائق Bitrix أو ظهورها الأول ونلقي نظرة على المصادر (بشكل أكثر وضوحًا ، يمكن أيضًا رؤية جميع الأحداث ولحظات معالجات الاتصال في المصادر أثناء تصحيح الأخطاء).

وهنا نواجه حقيقة أنه عند العمل مع DBMS ، لدينا كل من البيانات (القديمة) الحالية والبيانات التي سيتم تسجيلها (جديدة) ، وعند العمل مع Bitrix ، لدينا إما بيانات قديمة أو بيانات جديدة ، ولكن ليس عندما لن يكون هناك قديم وجديد في نفس الوقت.

مشكلة أخرى هي أن هناك العديد من الأحداث قبل تغيير البيانات ، ولم أتمكن من فهم الاختلاف بينهما ، القصة نفسها تدور حول إطلاق العديد من الأحداث بعد تغيير البيانات ، والعيون تحدق من ثروة الاختيار ، في الواقع هو مجرد وهم الاختيار.

رأيي المتواضع حول Bitrix
, , _ , ( Delphi PHP), .

depricated «», .

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

, :)

, , , , , , , ? .


باستخدام الدولة العالمية


لحفظ وتمرير الحالة بين معالجات الأحداث ، يجب أن يكون لديك متغيرات عامة. المتغيرات العامة هي شيء لا يمكن إعادة هيكلته ، لذلك أستخدم الخصائص الثابتة للفئة:

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

هنا تحتاج إلى تقديم تفسير حول استخدام "الحقول" و "الخصائص". الحقول هي المعلمات التي يحتوي عليها كل إدخال كتلة غير حصرية (معرف ، اسم ، وعلامة غير رئيسية ") ، والخصائص هي معلمات خاصة بسجلات نوع معين من القطع غير المرئي ، في نموذج EAV ، هذه سمات وقيمها.

"الأسماء" هي أسماء السمات ، في أحد الأحداث لدينا معرفات للسمات والأسماء ، وفي حدث آخر ليس لدينا سوى معرفات ، ولحساب جميل في المجلة ، نحتاج إلى حفظ الأسماء (يمكننا بالطبع طرح المعرّف ، ولكن لسبب ما لا تريده).

"العملية" هي العملية الحالية ، للعمل مع "الحقول" يعطي Bitrix أحداثًا معينة ، عند العمل مع "الخصائص" (وظائف "SetPropertyValues" و "SetPropertyValuesEx") ، لا يوجد حدث مع نوع العملية ، هناك حدث قبل وبعد المكالمة ، ولكنه لا من المعروف ما هي العملية التي يتم تنفيذها (إضافة / تحديث / حذف) ، وبالتالي يجب أيضًا نقل العملية من المعالج إلى المعالج.

ربما لم أتمكن من معرفة ذلك ، وفي Bitrix يمكنك أن ترى القيم في وقت واحد كما هي وكيف ستكون ، أو ربما يجب تسجيل الحالة السابقة والحالة بعد ذلك بشكل منفصل ، ولكن لسبب ما قررت أن إدخال السجل يجب أن يكون بتنسيق "الخاصية ٪ name٪ كان٪ value قبل التغيير٪ => أصبح٪ value بعد٪ ”وبالتالي هناك الكثير من المشاكل في الحفاظ على الحالة العالمية.

تحديد ما إذا كانت التغييرات مطلوبة


من الغريب ، ولكن سيتم استدعاء معالجنا لتغيير أي سجل لأي كتلة معلومات.
لذلك ، في المعالج ، نحتاج إلى فهم سجل كتلة المعلومات التي تلقيناها في المعلمات.
يتميز كتلة السجل بإحدى القيمتين: كتلة المعلومات نفسها ('IBLOCK_ID') وقسمها ('IBLOCK_SECTION_ID') ، وأحيانًا يكمن معرف القسم في مجموعة القيم مع الفهرس "IBLOCK_SECTION_ID" ، وأحيانًا "IBLOCK_SECTION" ، لذلك أقرأ معرف القسم من خلال كلا المفتاحين بأولوية "IBLOCK_SECTION".

علاوة على ذلك ، في بعض الحالات ، لا يمكن تحديد معرف القسم من حيث المبدأ ، وبالتالي ، يجب أن يتم التحقق من الحاجة إلى إضافة إدخال إلى سجل التدقيق فقط على أساس معرف كتلة المعلومات.

بعد تحديد كتلة المعلومات والقسم ، نقرر ضرورة تسجيل الحدث في المجلة.

حفظ الحالة قبل التغييرات


يتكون كل إدخال كتلة غير مؤلفة من جزأين ، جزء واحد هو "الحقول" ، ويتم تغيير هذه المعلمات بالطرق:

  • CIBlockElement :: إضافة ()
  • CIBlockElement :: تحديث ()
  • CIBlockElement :: حذف ()

الجزء الآخر هو طرق "الخصائص":
  • CIBlockElement :: SetPropertyValues ​​()
  • CIBlockElement :: SetPropertyValuesEx ()

لا أتذكر السبب ، ولكن من الأفضل الامتناع عن استخدام CIBlockElement :: SetPropertyValuesEx () في الكود الخاص بي.

كل جزء من البيانات في معالج الأحداث الخاص به منفصل عن الآخر ، وبالتالي ، بالنقر فوق الزر "حفظ" ، يمكن إنشاء إدخالين في سجل التدقيق.

تختلف تواقيع أحداث "الحقول" و "الخصائص" ، في الجزء الذي يتم فيه معالجة "الحقول" ، نحفظ الحالة الحالية للحقول ونعين القيمة للعملية ، وفي الجزء الذي يتم فيه معالجة "الخصائص" ، نحفظ الخصائص وأسمائها.

نقوم بذلك بالطبع في معالج "قبل".

تغيير التسجيل


في معالج "بعد" ، نكتب إلى السجل.

عند تجميع قائمة الاختلافات في السجلات ، هناك مشكلة في السمات التي يمكن أن تأخذ قيمًا متعددة. يختلف تنسيق تسجيل الحالة "قبل" والحالة "بعد" لدرجة أنه لا يمكن لأحدهما استنتاج الآخر ، لذلك عند تحديد ما إذا كان سيتم تسجيل التغييرات ، ستستنتج دائمًا أن التسجيل ضروري.
كود المصدر للتدقيق في المستودع في ملف src / Topliner / Scheme / Logger.php .

إضافة علامة تبويب مخصصة إلى بطاقة infoblock (في مصنف Bitrix)


لإضافة بطاقة ، يتم استخدام نهج مشابه للتدقيق - نضيف معالج:

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

في طريقة OnInit ، نحدد الطرق الأخرى للعمل مع علامة التبويب في محرر 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;
    }
}

أولاً ، بالطبع ، نتحقق من أنه بالنسبة لكتلة المعلومات هذه ، نحتاج إلى إظهار علامة تبويب المستخدم الخاصة بنا ، إذا لزم الأمر ، نقوم بتعيين الطرق.

لا يوجد شيء خاص لإخباره هنا ، راجع المصادر في مستودع src / Topliner / Scheme / PermitTab.php ، اقرأ الوثائق.

قدوة لتفويض إجراءات المستخدم


تعمل أساليب Bitrix للتفويض مع مقياس خطي للحقوق ، أي سيناريو حيث يمكن أن يكون الأول والثاني ، ولكن ليس الثالث ، والآخر يمكن أن يكون الثاني والثالث ، ولكن ليس الأول ، لا يمكنك تنفيذ Bitrix مباشرة.

بالنسبة لمثل هذه السيناريوهات الصعبة في Bitrix ، هناك ببساطة فحص لبعض الإذن ، وفي الكود عند تنفيذ العملية ، ترى ما إذا كان المستخدم لديه إذن أم لا ، وتسمح بالدورين الأول والثاني ، ويسمح الدوران الثاني والثالث بالدور الآخر.

        $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 () - معرف كتلة المعلومات
  2. $ constSec-> getSection () - معرف القسم
  3. BitrixPermission :: ELEMENT_ADD - رمز الإذن

في المصنف (دليل كتلة المعلومات) في كتل المعلومات والأقسام الضرورية ، تقوم بتعيين الحقوق المناسبة للأدوار. وزع الأدوار على المستخدمين وسيكون كل شيء تحت سيطرتك.

المصادقة


إذا كنت بحاجة إلى مصادقة المستخدم لبعض الصفحات ، فما عليك سوى إضافة:

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

//      

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

عند تحميل مثل هذه الصفحة ، سيعطي Bitrix نموذج تسجيل دخول إذا لم يقم المستخدم بتسجيل الدخول بعد.

إذا كنت تحتاج فقط إلى توصيل Bitrix في نوع من البرنامج النصي ، فعندئذٍ:

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

العمل مع الدلائل


تسمى الدلائل "الجديدة" (اعتبارًا من نوفمبر 2019) في Bitrix "HighloadBlock" ، والعمل معها غير قياسي إلى حد ما.

يمكن تخزين كل كتلة Hyload في جدول منفصل ، لذلك ، للوصول إلى الدليل ، يجب عليك في كل مرة تحديد اسم الجدول لقراءته. لكي لا تفعل ذلك باستمرار ، في Bitrix تحتاج إلى إنشاء مثيل لفئة للوصول إلى البيانات. يتم إنشاء مثيل في سطرين:

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

حيث "ConstructionTypes" هو الرمز المرجعي. لكي

لا تكتب هذين السطرين باستمرار ، يمكنك كتابة فصل سينشئ لنا أمثلة مرجعية ( src / Topliner / Bitrix / BitrixReference.php ).

بعد ذلك ، نعمل مع المثيل كما هو الحال مع فئة Bitrix ORM المعتادة (D7):

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

مستودع (بعناية ، ولصق الكثير من النسخ)

شكرا لاهتمامكم.

All Articles