Зміст

API Stigma

В Ubilling починаючи зі стабільного релізу 1.2.0 з'явилася можливість використовувати у ваших модулях загальну реалізацію стигматизації об'єктів. Власне, стигми являють собою якісь стани якихось рандомних об'єктів (items) у межах якоїсь рандомної категорії (scope). Використання API Stigma найближче за духом до API ADcomments.

Найпростіше пояснити роботу цього механізму на прикладі “а давайте зробимо можливість десь, у якомусь нашому модулі, відзначати, чи купив користувач у нас роутер, чи він жадібна свиня?”. Конфігурація цієї стигми починається з написання конфігураційного файлу, який зберігатиметься в config/stigma/, нехай це буде userbuyrouter.ini через запланований нами SCOPE “USERBUYROUTER”. Так, ім'я файлика конфігурації стигми для конкретного scope-а за замовчуванням - це цей самий scope у lowercase із розширенням *.ini. Власне він і буде у нас читатися при створенні об'єкта стигми з відповідним SCOPE.

userbuyrouter.ini
[stigmasettings]
TYPE=radiolist
BASECLASS=dashtask
ACTIVECLASS=todaysig
ANIMATION=1
RENDERER=iconic
 
[buy]
NAME="Bought a router"
ICON=sellrouter
 
[nobuy]
NAME="Greedy pig"
ICON=pig
 
[unknown]
NAME="We dont know"

Коротко про формат конфігурації

Секція “stigmasettings” є службовою та обов'язковою. Вона вказує на базову поведінку контролів у конкретному скоупі. Можливі типи (TYPE) на даний момент “radiolist” (вибір одного з усіх) або “checklist” (множинна вибірка). ANIMATION просто вказує, чи оновлювати з супутньою анімацією контейнер стигмати під час зміни стану, чи робити це “тихо і непомітно”. Оскільки стан “купив у нас роутер” не може бути одночасно і “купив”, і “не купив”, ми використовуємо тип “radiolist”. Опція ACTIVECLASS вказує просто, яким CSS-класом будуть підсвічуватися активні/вибрані стани об'єктів у межах цього scope. Починаючи з Ubilling 1.4.1 також можна опційно вказувати і BASECLASS, який буде використовуватись для всіх контролів за замовчуванням. Так, це теж ім'я CSS класу.

Далі всі секції, які не є stigmasettings, описують конкретні стани об'єктів (items) у межах конкретного SCOPE. Їх може бути скільки завгодно. Ім'я секції власне буде ідентифікатором стану, що зберігається в БД, і має бути якомога коротшим та унікальним. Базовими характеристиками стану крім його ідентифікатора є NAME (ім'я) і ICON (ви не повірите! іконка). Іконки станів за замовчуванням зберігаються в skins/stigma/ з розширенням *.png.

Крім усього цього, реалізовано механіку підвантаження кастомних файлів конфігурації та іконок, на випадок якщо користувачі самі захочуть розширити набір станів, або замінити іконки на якісь свої, і щоб це все пережило оновлення. Кастомні файли конфігурації та іконки, що мають пріоритет над замовчуваними, зберігатимуться в content/documents/mystigma у сабдиректоріях confs і icons

Практичне використання в коді

Повертаючись до початкової задачі, ми хочемо ловити GET параметром логін якогось користувача і виставляти йому стан.

  if (ubRouting::checkGet('username')) {
        $userLogin = ubRouting::get('username');
 
        //створюємо інстанс із потрібним нам scope-ом.
        $userRouter = new Stigma('USERBUYROUTER');
        //викличемо обробник фонових коллбеків на випадок, якщо у нас зміниться стан
        $userRouter->stigmaController();
        //показуємо інтерфейс зміни стану для нашого конкретного користувача
        show_window(__('User bought a router'), $userRouter->render($userLogin));
    }

Ну і власне одразу отримуємо результат у вигляді панелі управління станами користувача.

А може хочемо меншу панельку?

show_window(__('User bought a router'), $userRouter->render($userLogin, '64'));

типу такої?

А може хочемо read-only панельку для адміністраторів без якогось права, але яка нормально працює для адміністраторів, наділених цими правами? Тоді якось так

    if (ubRouting::checkGet('username')) {
        $userLogin = ubRouting::get('username');
        $userRouter = new Stigma('USERBUYROUTER');
        $userRouterReadOnlyFlag = true;
        if (cfr('USERROUTEREDIT')) {
            $userRouter->stigmaController();
            $userRouterReadOnlyFlag = false;
        }
 
        show_window(__('User bought a router'), $userRouter->render($userLogin, '64', $userRouterReadOnlyFlag));
    }

А якщо ми хочемо просто десь в іншому місці показати тупо список наявних станів цього користувача?

show_window(__('User bought a router as text'), $userRouter->textRender($userLogin);

типу так

чи взагалі тупо перевірити чи є якісь встановлені стани й отримати їх у вигляді масиву для наших подальших дофіга концептуальних бізнес-додатків?

 if ($userRouter->haveState($userLogin)) {
            $routerStates = $userRouter->getItemStates($userLogin);
        }

А ще ми можемо отримати всі можливі для скоупа стани з їхніми текстовими описами ось якось так

$userRouter = new Stigma('USERBUYROUTER');
$allStates=$userRouter->getAllStates();
debarr($allStates);

Коротше все настільки прямолінійно і брутально, наскільки це можливо. А взагалі поглянути на публічні методи і що у них у параметрах буде набагато продуктивніше і зрозуміліше.

Про оптимізацію швидкодії

У прикладі вище, ми створювали інстанс стигми як

$userRouter = new Stigma('USERBUYROUTER');

Що зі свого боку завантажувало з бази всі стани всіх айтемів у цьому scop-і. Це може бути корисно, коли ми одним і тим самим інстансом намагаємося працювати з різними айтемами одним інстансом. Наприклад читаючи/перевіряючи наявність станів для різних айтемів одним і тим же інстансом. Типу як

 $allUsers = zb_UserGetAllDataCache();
 $userRouter = new Stigma('USERBUYROUTER');
 
    if (!empty($allUsers)) {
        foreach ($allUsers as $eachLogin => $eachData) {
            if ($userRouter->haveState($eachLogin)) {
                $routerStates = $userRouter->getItemStates($eachLogin);
 
                if (isset($routerStates['buy'])) {
                    show_success($eachLogin);
                }
 
                if (isset($routerStates['nobuy'])) {
                    show_warning($eachLogin . ' ' . __('gotcha!'));
                }
            }
        }
    }

але в нашому першому прикладі, логічно було б завантажувати з БД тільки стан одного конкретного, що цікавить нас айтема, а точніше, конкретного користувача, якого ми зловили в GET-параметрі username. У такому разі створення нашого інстансу мало б виглядати так:

   $userRouter = new Stigma('USERBUYROUTER', $userLogin);

що призведе до завантаження даних про стани тільки для айтема $userLogin у межах scop-а USERBUYROUTER у процесі створення інстансу, і цілком собі дасть нам змогу з ними працювати. Але тільки ось для цього конкретного $userLogin.

Логування змін станів

Базово, підтримується три різноманітні режими логування змін станів, якщо вам це необхідно: TASKMAN, SYSTEM, CUSTOM.

Для логування до довільної таблиці, її структура має мати наступний формат:

CREATE TABLE IF NOT EXISTS `somecustomlog` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `date` datetime NOT NULL,
  `admin` VARCHAR(64) DEFAULT NULL,
  `scope` VARCHAR(64) DEFAULT NULL,
  `itemid` VARCHAR(128) NOT NULL,
  `action` VARCHAR(32) DEFAULT NULL,
  `state` VARCHAR(255) DEFAULT NULL,
   PRIMARY KEY (`id`),
   KEY `scope` (`scope`),
   KEY `itemid` (`itemid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

Логування TASKMAN: ви не використовуватимете з ймовірністю 99%, з логуванням SYSTEM:[стан] який ви можете ввімкнути при виклику обробника коллбеків, наступним чином:

   $leadSource->stigmaController('SYSTEM:якийсь_стан');

в системному лозі з'являтимуться записи наступного вигляду

STIGMA [поточний_SCOPE] CHANGE [ідентифікатор_айтема] `[якийсь_стан]` ON `[ідентифікатор_стану]`

Чим буде заповнюватись табличка somecustomlog при логуванні CUSTOM:somecustomlog викликаному отак:

   $leadSource->stigmaController('CUSTOM:somecustomlog');

теж доволі зрозуміло з іменування її полів.

Типи рендерингу

Починаючи з Ubilling 1.4.1 в секції конфігурації [stigmasettings] також можна опційно встановити опцію RENDERER, яка прямо вказує на тип відображення контролів конкретної стигми.

Можливі значення опції на даний момент: