====== 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 - зображає всі наявні стани в скоупі у вигляді простих текстових посилань з маленькими іконками станів.