====== API Stigma ======
В Ubilling починаючи зі стабільного релізу 1.2.0 з'явилася можливість використовувати у ваших модулях загальну реалізацію стигматизації об'єктів. Власне, стигми являють собою якісь стани якихось рандомних об'єктів (items) у межах якоїсь рандомної категорії (scope). Використання API Stigma найближче за духом до [[adcomments|API ADcomments]].
Найпростіше пояснити роботу цього механізму на прикладі "а давайте зробимо можливість десь, у якомусь нашому модулі, відзначати, чи купив користувач у нас роутер, чи він жадібна свиня?". Конфігурація цієї стигми починається з написання конфігураційного файлу, який зберігатиметься в **config/stigma/**, нехай це буде **userbuyrouter.ini** через запланований нами SCOPE "USERBUYROUTER". Так, ім'я файлика конфігурації стигми для конкретного scope-а за замовчуванням - це цей самий scope у lowercase із розширенням ***.ini**. Власне він і буде у нас читатися при створенні об'єкта стигми з відповідним SCOPE.
[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));
}
Ну і власне одразу отримуємо результат у вигляді панелі управління станами користувача.
{{:stigma1.png|}}
А може хочемо меншу панельку?
show_window(__('User bought a router'), $userRouter->render($userLogin, '64'));
типу такої?
{{:stigma2.png|}}
А може хочемо 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);
типу так
{{::stigma3.png|}}
чи взагалі тупо перевірити чи є якісь встановлені стани й отримати їх у вигляді масиву для наших подальших дофіга концептуальних бізнес-додатків?
if ($userRouter->haveState($userLogin)) {
$routerStates = $userRouter->getItemStates($userLogin);
}
А ще ми можемо отримати всі можливі для скоупа стани з їхніми текстовими описами ось якось так
$userRouter = new Stigma('USERBUYROUTER');
$allStates=$userRouter->getAllStates();
debarr($allStates);
Коротше все настільки прямолінійно і брутально, наскільки це можливо. А взагалі [[https://ubilling.net.ua/api_doc/classes/Stigma.xhtml|поглянути]] на публічні методи і що у них у параметрах буде набагато продуктивніше і зрозуміліше.
===== Про оптимізацію швидкодії =====
У прикладі вище, ми створювали інстанс стигми як
$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.
* 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**, яка прямо вказує на тип відображення контролів конкретної стигми.
Можливі значення опції на даний момент:
* iconic - рендерер, за замовчуванням у вигляді "натискаємих іконок". Він же використовується у випадку якщо опція RENDERER відсутня.
* selector - просто стандартний селектор у вигляді "вибирушки". Працює тільки з TYPE=radiolist, що доволі очевидно.
* textlink - зображає всі наявні стани в скоупі у вигляді простих текстових посилань.
* imagelink - зображає всі наявні стани в скоупі у вигляді простих текстових посилань з маленькими іконками станів.