Страницы, статьи и новости
Инструменты и плагины
Заготовка заголовок


Описание как пример заготовки. Пример текстового контента для дальнейшей кастомизации шаблона "Index36".

Редактировать шаблон вы можете на свое усмотрение и как вам угодно. Если у вас на это нет времени или недостаточно знаний - вы всегда можете заказать адаптацию шаблона сайта написав мне по контактам на GitHub или в личные сообщения на публичной странице сайта маркетплейса цифровых товаров

17.02.2024 15:11

В этом руководстве вы узнаете о создании плагина "Права для каждого пользователя"

В этом руководстве рассматриваются следующие темы по разработке расширения Cotonti:

    Создание плагина.

    Использование базы данных в вашем плагине.

    Использование CoTemplate для предоставления интерфейса администрирования.

    Локализация интерфейса.

    Инструменты для администраторов в плагинах.

    Модификация поведения системы.

    Использование AJAX для реализации автозавершения.

1. Планирование функций и структуры

Плагин, который мы собираемся создать в этой статье, является давно запрашиваемым инструментом, который позволил бы администраторам сайта назначать пользовательские разрешения доступа для выбранных пользователей, не создавая пользовательских групп для этих пользователей и не влияя на групповые разрешения. Поскольку нам нужно какое-то официальное название плагина и код плагина (используемый в именах файлов и т.д.), давайте назовем это “Права для каждого пользователя” и "rightsperuser" соответственно.

Первое, что нам нужно сделать, это решить, какие функции наш плагин будет предоставлять своим конечным пользователям.

Поразмыслив над этим, мы могли бы прийти к чему-то подобному:

    Изменять разрешения на выбранный элемент для конкретного пользователя.
    Сохранять правила модификации в базе данных.
    Предоставлять интерфейс для добавления/обновления/удаления правил.

Затем нам нужно ответить на несколько вопросов о деталях реализации:

    Предоставляет ли плагин интерфейс пользователя? - да.
    Доступен ли интерфейс на нескольких языках? - да.
    Есть ли в плагине пользовательские таблицы базы данных? - да.
    Должен ли он предоставлять API для сторонних плагинов? - нет.
    Нужны ли для этого пользовательские строки ресурсов? - нет.
    Есть ли в нем какой-нибудь JavaScript? - да

Основываясь на этих ответах, нам нужно создать следующие подпапки в папке нашего плагина: 'tpl' для шаблонов пользовательского интерфейса, 'lang' для файлов локализации, 'setup' для SQL schema.
Ответы 4 и 5 означают, что нам не нужны включаемые файлы, но поскольку мы собираемся использовать имена таблиц базы данных (ответ 3) в разных частях нашего плагина, нам также нужна папка "inc" для хранения "rightsperuser.functions".файл php, в котором мы определяем такие имена таблиц.
Ответ на вопрос 6 означает, что нам также нужна папка "js" для хранения нашего пользовательского JavaScript.

Пока что наш плагин имеет следующую структуру:

dir 
	▼ ext_name 
		► inc		(инклуд файлы, ext_name.functions.php)
		► js		(JavaScript файлы для работы плагина)
		► lang		(файлы локализации ext_name.ru.lang.php)
		► setup		(SQL сценарии: ext_name.install.sql)
		► tpl		(шаблоны плагина ext_name.tpl)


                
Интерфейс администрирования означает, что нам нужна часть "rightsperuser.admin.php" в нашем плагине, которая будет предоставлять операции списка / добавления / обновления / удаления для пользовательских разрешений. Эта часть довольно большая, но простая. Более сложный вопрос для ответа заключается в том, как мы собираемся применять эти пользовательские разрешения, когда пользователь фактически действует на сайте. Чтобы ответить на этот вопрос, нам нужно проанализировать, как работают разрешения авторизации Cotonti. Если вы новичок в Cotonti и только задаетесь вопросом, как создавать плагины, вы можете пропустить следующую главу.

2. Копаем глубже: влияние на разрешения пользователя

Пожалуйста, сначала прочтите эту статью, если вы не знакомы с системой авторизации Cotonti. Тогда вы знаете, что разрешения для текущего пользователя всегда запрашиваются с помощью функции cot_auth(). Если мы хотим изменить разрешения, нам нужно знать, как работает эта функция.

Найдите исходный код функции в system/functions.php. Сигнатура функции такова:

/*
 * ================================= Authorization Subsystem ==================================
 */

/**
 * Returns specific access permissions
 *
 * @param string $area Cotonti area or extension code
 * @param ?string $option Option to access.
 *    Empty -  check if user has access to area (extension)
 *    'any' - if user has access to the extension or to any of its categories
 *    category code - if user has access to this category of area (extension)
 * @param string $mask Access mask
 * @return bool|array<string, bool>
 */
function cot_auth($area, $option = null, $mask = 'RWA')

Первый аргумент принимает "module" код модуля для модулей или "plug" для плагинов.

Второй аргумент принимает пользовательский элемент модуля для модулей или код плагина для плагинов.

Третий параметр устанавливает маску разрешений для элемента.

Маски преобразуются из строкового представления, такого как “RWA”, в целое число, так что двоичные операторы могут использоваться для проверки определенных видов разрешений.

Анализируя код функции cot_auth(), вы могли заметить, что она принимает фактические разрешения из следующей переменной:

$usr['auth'][$area][$option]

Это глобальная переменная, поэтому, если мы изменим ее перед любым вызовом cot_auth(), например, в global hook, она будет работать так, как мы хотим.

Вы также можете взглянуть на таблицу базы данных cot_auth в phpMyAdmin, чтобы получить представление о необработанных значениях области/параметра/маски (area/option/mask).

Часть нашего плагина, которая будет отвечать за модификацию $usr['auth'], будет называться 'rightsperuser.global.php', потому что она использует 'глобальный' хук.

3. Установочный файл плагина.


Мы должны создать файл 'rightsperuser.setup.php', чтобы предоставить метаданные о нашем плагине. Его содержимое выглядит следующим образом:

<?php
/* ====================
[BEGIN_COT_EXT]
Code=rightsperuser
Name=Rights per user
Description=Assigns custom permissions for specific users without affecting group permissions
Version=1.0
Date=2011-11-22
Author=Trustmaster
Copyright=Copyright (c) Vladimir Sibirov and Cotonti Team 2011
Notes=
Auth_guests=R
Lock_guests=12345A
Auth_members=R
Lock_members=
Requires_plugins=autocomplete
[END_COT_EXT]
 
[BEGIN_COT_EXT_CONFIG]
rules_perpage=01:select:5,10,20,30,50,100:30:Rules displayed per page
[END_COT_EXT_CONFIG]
==================== */
 
defined('COT_CODE') or die('Wrong URL');

Для получения дополнительной информации о установочных файлах, пожалуйста, посетите Введение в разработку расширений. Этот установочный файл не содержит ничего особенного, за исключением параметра “Requires_plugins”. Этот параметр сообщает установщику расширения, что для установки этого плагина требуется плагин автозаполнения (мы повторно используем его в нашем интерфейсе администратора), и в противном случае наш плагин не удастся установить.

У нашего плагина есть один параметр конфигурации, который определяет, сколько правил отображается на одной странице в инструменте администрирования. Для получения дополнительной информации о параметрах конфигурации, пожалуйста, смотрите раздел Значения конфигурации.

Поскольку наш плагин собирается использовать свои собственные таблицы базы данных, нам нужно определить их структуру в SQL-файле, который будет выполнен при установке плагина. Назовем его "rightsperuser.install.sql" и поместим в подпапку "setup" нашего плагина. Структура таблицы базы данных, которая нам нужна, очень похожа на cot_auth, но более проста:

/* Custom user permissions schema */
CREATE TABLE IF NOT EXISTS `cot_rightsperuser` (
    `ru_id` INT NOT NULL AUTO_INCREMENT, -- Rule ID
    `ru_user` INT NOT NULL REFERENCES `cot_users` (`user_id`), -- User ID
    `ru_code` VARCHAR(255) NOT NULL, -- Auth code
    `ru_option` VARCHAR(255) NOT NULL, -- Auth option
    `ru_rights` TINYINT UNSIGNED NOT NULL DEFAULT '0', -- Permissions
    PRIMARY KEY (`ru_id`)
) ;

Давайте создадим другие части плагина, файлы локализации и шаблоны, чтобы позже заполнить их реальным кодом. Пока мы можем просто оставить их пустыми, но структура каталогов плагинов в основном завершена:

dir 
	▼ ext_name 
		▼ inc
			►	rightsperuser.functions.php		
		▼ js
			►	rightsperuser.js
		▼ lang
			►	rightsperuser.en.lang.php
			►	rightsperuser.ru.lang.php
		▼ setup
			►	rightsperuser.install.sql
		▼ tpl
			►	rightsperuser.tpl
		►	rightsperuser.admin.php
		►	rightsperuser.ajax.php
		►	rightsperuser.global.php
		►	rightsperuser.setup.php

скрин с редактора кода

Теперь подготовительный этап завершен, и мы готовы приступить к работе с кодом!

4. Шаблон для инструмента редактирования правил

Один из подходов к разработке программного обеспечения заключается в том, чтобы представить программное обеспечение с точки зрения конечного пользователя, затем представить его более подробно, а затем перейти к конкретным функциям. Давайте сначала воспользуемся чем-то подобным и создадим шаблон пользовательского интерфейса для нашего инструмента.

Содержимое шаблона обычно помещается в блок BEGIN: MAIN / END: MAIN:

<!-- BEGIN: MAIN -->
 
содержимое блока находится здесь.
 
<!-- END: MAIN -->

Это связано с тем, что шаблон может содержать несколько блоков корневого уровня одновременно, но обычно требуется только один корневой блок. Содержимое нашего шаблона можно логически разделить на 2 части: таблицу для редактирования существующего правила и форму для добавления нового правила в таблицу. Есть также такие второстепенные элементы, как заголовки и разбивка на страницы.

Вот код для редактируемой таблицы правил:


<table class="cells centerall">
    <tr>
        <td class="coltop">{PHP.L.User}</td>
        <td class="coltop">{PHP.L.Code}</td>
        <td class="coltop">{PHP.L.Item}</td>
        <td class="coltop">{PHP.R.admin_icon_auth_r}</td>
        <td class="coltop">{PHP.R.admin_icon_auth_w}</td>
        <td class="coltop">{PHP.R.admin_icon_auth_1}</td>
        <td class="coltop">{PHP.R.admin_icon_auth_2}</td>
        <td class="coltop">{PHP.R.admin_icon_auth_3}</td>
        <td class="coltop">{PHP.R.admin_icon_auth_4}</td>
        <td class="coltop">{PHP.R.admin_icon_auth_5}</td>
        <td class="coltop">{PHP.R.admin_icon_auth_a}</td>
        <td class="coltop">{PHP.L.Update}</td>
        <td class="coltop">{PHP.L.Delete}</td>
    </tr>
    <!-- BEGIN: RIGHTSPERUSER_ROW -->
    <form action="{RIGHTSPERUSER_ROW_ACTION}" method="POST">
    <tr>
        <td>{RIGHTSPERUSER_ROW_USER}</td>
        <td>{RIGHTSPERUSER_ROW_CODE}</td>
        <td>{RIGHTSPERUSER_ROW_OPTION}</td>
        <td>{RIGHTSPERUSER_ROW_AUTH_R}</td>
        <td>{RIGHTSPERUSER_ROW_AUTH_W}</td>
        <td>{RIGHTSPERUSER_ROW_AUTH_1}</td>
        <td>{RIGHTSPERUSER_ROW_AUTH_2}</td>
        <td>{RIGHTSPERUSER_ROW_AUTH_3}</td>
        <td>{RIGHTSPERUSER_ROW_AUTH_4}</td>
        <td>{RIGHTSPERUSER_ROW_AUTH_5}</td>
        <td>{RIGHTSPERUSER_ROW_AUTH_A}</td>
        <td><input type="submit" class="submit" value="{PHP.L.Update}" /></td>
        <td><a href="{RIGHTSPERUSER_ROW_DELETE_URL}" class="button">{PHP.L.Delete}</a></td>
    </tr>
    </form>
    <!-- END: RIGHTSPERUSER_ROW -->
</table>

Этот список заслуживает подробного объяснения.
Строки 3-15 - это заголовки столбцов, которые взяты из языковых строк ({PHP.L.language_array_key}), и значки для типов разрешений, взятых из строк ресурсов ({PHP.R.resource_array_key}).
Классы “cells” и “coltop” являются традиционными в Cotonti для таблиц, которые на самом деле выглядят как таблицы и их заголовки столбцов.
Класс “centerall” устанавливает выравнивание по центру для всех элементов таблицы.

Строки 17-35 представляют фактическую строку таблицы, заключенную в блок BEGIN/END, которая должна быть проанализирована для каждой записи базы данных.
Каждой строке присваивается своя собственная HTML-форма.
Во многих случаях практичнее поместить всю базу данных в одну форму и обновлять все строки сразу, но в этом примере плагина мы будем обновлять по одной строке за раз для простоты.
Большинство входных данных генерируются в PHP-скрипте с функциями генерации форм, поэтому они представлены простыми тегами TPL. Кнопка с именем {PHP.L.Update}* отправляет форму для текущей строки.
Кнопка с именем {PHP.L.Delete}* - это гиперссылка с определенным URL-адресом и классом.
*{PHP.L.xxxx} получаем в шаблоне значение переменной $L['xxxx'] из файлов локализации, и если она уже существует в основном файле локализации CMS Cotonti, то дублировать ее в файлах локализации плагина не нужно.

Ячейка “Пользователь” будет реализована как текстовый ввод с возможностью автозаполнения. “Код” будет выпадающим списком выбора. “Опция” будет выпадающим списком с параметрами, загружаемыми через AJAX в зависимости от текущего выбора “Кода”.
Под таблицей мы разместили форму для добавления новых правил:

<form action="{RIGHTSPERUSER_ADD_ACTION}" method="POST">
<table class="cells centerall">
    <tr>
        <td class="coltop">{PHP.L.User}</td>
        <td class="coltop">{PHP.L.Code}</td>
        <td class="coltop">{PHP.L.Item}</td>
        <td class="coltop">{PHP.R.admin_icon_auth_r}</td>
        <td class="coltop">{PHP.R.admin_icon_auth_w}</td>
        <td class="coltop">{PHP.R.admin_icon_auth_1}</td>
        <td class="coltop">{PHP.R.admin_icon_auth_2}</td>
        <td class="coltop">{PHP.R.admin_icon_auth_3}</td>
        <td class="coltop">{PHP.R.admin_icon_auth_4}</td>
        <td class="coltop">{PHP.R.admin_icon_auth_5}</td>
        <td class="coltop">{PHP.R.admin_icon_auth_a}</td>
        <td class="coltop">{PHP.L.Add}</td>
    </tr>
    <tr>
        <td>{RIGHTSPERUSER_ADD_USER}</td>
        <td>{RIGHTSPERUSER_ADD_CODE}</td>
        <td>{RIGHTSPERUSER_ADD_OPTION}</td>
        <td>{RIGHTSPERUSER_ADD_AUTH_R}</td>
        <td>{RIGHTSPERUSER_ADD_AUTH_W}</td>
        <td>{RIGHTSPERUSER_ADD_AUTH_1}</td>
        <td>{RIGHTSPERUSER_ADD_AUTH_2}</td>
        <td>{RIGHTSPERUSER_ADD_AUTH_3}</td>
        <td>{RIGHTSPERUSER_ADD_AUTH_4}</td>
        <td>{RIGHTSPERUSER_ADD_AUTH_5}</td>
        <td>{RIGHTSPERUSER_ADD_AUTH_A}</td>
        <td><input type="submit" class="submit" value="{PHP.L.Add}" /></td>
    </tr>
</table>
</form>

Таблица/форма очень похожа на приведенную выше, но в ней есть только одна строка для добавления нового правила в базу данных.

Есть некоторые другие элементы интерфейса, которые мы добавим позже.

Мы начинаем кодировать код контроллера со скелета скрипта.
Наш скрипт находится в файле 'rightsperuser.admin.php', который использует (hook) хук, - 'tools' (hook для инструментов администратора), и к нему можно получить доступ по URL: admin.php?m=other&p=rightsperuser или (admin/other?p=rightsperuser с URL-адресами SEF).
Мы будем использовать общий параметр GET 'a' (обычно понимаемый как “действие”), чтобы дифференцировать поведение наших скриптов.
Существует 4 общих параметра такого рода, используемых в модулях Cotonti:

  1.     $m — “mode”; режим
  2.     $n — “secondary mode”; вторичный режим
  3.     $a — “action”; действие
  4.     $b — “secondary action”. вторичное действие

Они импортируются автоматически в виде буквенно-цифровых значений, они не обязательны, но часто используются в этих значениях.

Итак, наш скрипт выполнит следующие действия:

    'add' – добавление нового правила;

    'update' – обновление существующего правила;

    'delete' – удаление существующего правила.

Он отображает статус этих действий с помощью стандартного API обмена сообщениями, и независимо от того, какое действие было выполнено, он отображает таблицу правил и форму для добавления нового.

Вот сам скелет 'rightsperuser.admin.php'  (это не полный код всего файла):

<?php
/* ====================
[BEGIN_COT_EXT]
Hooks=tools
[END_COT_EXT]
==================== */
 
(defined('COT_CODE') && defined('COT_ADMIN')) or die('Wrong URL.');
 
$id = cot_import('id', 'G', 'INT');
 
if ($a == 'add')
// $a — это "action", - действие
// 'add' - выполняемое действие, - добавляем правило
{
    // Adding a new rule
}
elseif ($a == 'update' && $id > 0)
{
    // Updating an existing rule
}
elseif ($a == 'delete' && $id > 0)
{
    // Existing rule removal
}
 
// Render the rules table and form
$ru_t = new XTemplate(cot_tplfile('rightsperuser', 'plug'));

Строка 8 проверяет, был ли включен скрипт из области администрирования Cotonti.

Строка 10 отвечает за импорт переменной integer identifier из get parameters. Эта переменная используется для идентификации правил, которые необходимо обновить или удалить.

Строка 28 создает новый объект XTemplate из файла TPL, который находится с помощью вспомогательной функции, которая вычисляет путь к шаблону "rightsperuser" плагина.

Далее мы обернем эти “кости скелета” “плотью и тканями”, но перед этим нам нужно создать файл для хранения определения имени нашей таблицы базы данных.
Он называется "rightsperuser.functions.php" и находится во вложенной папке "inc".
Вот он:

<?php
/**
 * Rights per user API
 * 
 * @package rightsperuser
 * @author Trustmaster
 * @copyright (c) Vladimir Sibirov, 2011
 * @license BSD
 */

defined('COT_CODE') or die('Wrong URL');

// Calculate database table name if not set in config.php
// Вычислить имя таблицы базы данных, если оно не задано в config.php
$db_rightsperuser = (isset($db_rightsperuser)) ? $db_rightsperuser : $db_x . 'rightsperuser';

 

“Файлы функций” или “файлы API” встречаются довольно часто. Они используются, если часть кода плагина используется совместно с частями самого плагина или с другими расширениями. Обратите внимание, что он начинается с комментария "PHP Docs", содержащего информацию об этом файле. Настоятельно рекомендуется использовать PHP Docs для документирования ваших файлов, констант, функций и методов.
В данном конкретном случае мы используем такой файл, чтобы установить значение по умолчанию для $db_rightsperuser, используя префикс базы данных по умолчанию и имя таблицы. Эта переменная может быть легко перезаписана администратором сайта в файле 'datas/config.php'.

Затем мы добавляем включение этого и других необходимых API в верхней части 'rightsperuser.admin.php':

// We need Forms API to generate form elements
// Нам нужен Forms API для генерации элементов формы
require_once cot_incfile('forms');
// Own functions and globals
// Собственные функции и глобальные
require_once cot_incfile('rightsperuser', 'plug');

cot_incfile() - это вспомогательная функция, которая вычисляет путь к включаемому файлу.

Подробнее тут и в исходном коде в файле /system/functions.php функция ниже:

function cot_incfile($name, $type = 'core', $part = 'functions')

Учитывая 1 аргумент, он загружает базовый API, учитывая 2 аргумента, он загружает API заданного типа ("модуль" или "подключаемый модуль"), 3-й аргумент может использоваться для загрузки чего-либо, отличного от "функций".

5.1. Импорт данных POST

Следующий фрагмент кода используется для импорта элемента из данных HTTP POST (отправленной формы), его проверки и выдачи сообщений об ошибках, если таковые имеются:

if ($_SERVER['REQUEST_METHOD'] == 'POST')
{
    // Get the submitted item
    $rule = array();
    // We need an ID from database for the user name specified
    $user_name = cot_import('user', 'P', 'TXT');
    $res = $db->query("SELECT user_id, user_maingrp FROM $db_users
            WHERE user_name = ?", array($user_name));
    if ($res->rowCount() == 1)
    {
        // OK, user found
        $row = $res->fetch();
        if ($row['user_maingrp'] == COT_GROUP_SUPERADMINS)
        {
            // Don't affect superadmins
            cot_error('rightsperuser_superadmin', 'user');
        }
        else
        {
            // Got user ID
            $rule['ru_user'] = (int) $row['user_id'];
        }
    }
    else
    {
        // No such user
        cot_error('rightsperuser_invalid_user', 'user');
    }
    $rule['ru_code'] = cot_import('code', 'P', 'ALP');
    $rule['ru_option'] = cot_import('option', 'P', 'ALP');
    // Calculate rights byte
    $rights = 0;
    foreach ($mn as $key => $val)
    {
        if (cot_import('auth_'.$key, 'P', 'BOL'))
        {
            $rights += $val;
        }
    }
    $rule['ru_rights'] = $rights;
}

Файл довольно хорошо прокомментирован, поэтому мы уделим больше внимания деталям:

    Функция cot_import() используется для импорта входного параметра и его фильтрации. Первый аргумент - это имя параметра, второй - источник импорта, а третий - тип фильтра. Широко используемыми фильтрами являются 'ALP' (буквенно-цифровой), 'INT' (целое число), 'BOL' (логическое значение), 'TXT' (текст) и 'HTM' (нефильтрованный текст).

    функция cot_error() является частью стандартной обработки ошибок в Cotonti. Она сохраняет сообщение об ошибке в стеке ошибок и устанавливает флаг ошибки в значение true. Текущее значение флага ошибки можно проверить с помощью функции cot_error_found().

    $db - это глобальный экземпляр класса CotDB, который наследует все свойства и методы от класса PDO. $db->query() расширен таким образом, что он принимает заполнители PDO в своем первом аргументе с заменами, передаваемыми в качестве второго аргумента. Он возвращает и экземпляр класса PDOStatement.

    $res - это объект класса PDOStatement, который представляет набор строк базы данных.

    $rights вычисляется как целочисленная двоичная маска, комбинация битов, которые устанавливают различные виды разрешений. Обычно вам вообще не нужно заботиться об этом.

Если вам интересно, что такое $mn, это массив, который сопоставляет буквы разрешения с соответствующими битами:

$mn = array();
$mn['R'] = 1;
$mn['W'] = 2;
$mn['1'] = 4;
$mn['2'] = 8;
$mn['3'] = 16;
$mn['4'] = 32;
$mn['5'] = 64;
$mn['A'] = 128;

После выполнения вышеуказанной части $rule содержит импортированные поля. Эта переменная используется в качестве данных новой строки для действия "добавить", или в качестве обновленных данных строки для действия "обновить", или в качестве текущих входных значений при отображении формы для добавления новых элементов. В форме это гарантирует, что вводимые пользователем данные не будут потеряны, даже если возникнет ошибка.

5.2. Рендеринг правил

Чтобы отобразить таблицу правил, которая будет просматриваться и обновляться пользователем, нам нужно загрузить существующие правила из базы данных и отобразить каждую строку в виде набора тегов TPL и входных данных формы. Вот код, который это делает:

// Get available auth codes
$auth_codes = $db->query("SELECT DISTINCT auth_code
        FROM $db_auth")->fetchAll(PDO::FETCH_COLUMN);
 
// SELECT all rules
$res = $db->query("SELECT r.*, u.user_name
    FROM $db_rightsperuser AS r
        LEFT JOIN $db_users AS u ON r.ru_user = u.user_id
    ORDER BY ru_user, ru_code, ru_option");
     
// Loop through the result rowset
foreach ($res->fetchAll() as $row)
{
    // Generate checkboxes for each kind of permissions
    foreach ($mn as $key => $val)
    {
        $checked = (($row['ru_rights'] & $val) == $val);
        $rt->assign('RIGHTSPERUSER_ROW_AUTH_'.$key,
                cot_checkbox($checked, 'auth_'.$key));
    }
    // Get the options for selected code
    $options = $db->query("SELECT DISTINCT auth_option FROM $db_auth
            WHERE auth_code = ?", array($row['ru_code']))
            ->fetchAll(PDO::FETCH_COLUMN);
    // Generate other tags
    $id = $row['ru_id'];
    $rt->assign(array(
        'RIGHTSPERUSER_ROW_ACTION'
            => cot_url('admin', 'm=other&p=rightsperuser&a=update&id='.$id),
        'RIGHTSPERUSER_ROW_USER'
            => cot_inputbox('text', 'user', $row['user_name'],
                array('class' => 'user')),
        'RIGHTSPERUSER_ROW_CODE'
            => cot_selectbox($row['ru_code'], 'code', $auth_codes, $auth_codes,
                false, array('class' => 'code', 'id' => 'code_'.$id)),
        'RIGHTSPERUSER_ROW_OPTION'
            => cot_selectbox($row['ru_option'], 'option', $options, $options,
                false, array('class' => 'option', 'id' => 'opt_'.$id)),
        'RIGHTSPERUSER_ROW_DELETE_URL'
            => cot_url('admin', 'm=other&p=rightsperuser&a=delete&id='.$id)
    ));
    $rt->parse('MAIN.RIGHTSPERUSER_ROW');
}

Что интересного в этом коде:

    $auth_codes - это массив, содержащий все уникальные значения столбца auth_code в таблице cot_auth.

    fetchAll(PDO::FETCH_COLUMN) в PDOStatement возвращает массив со значениями одного и того же столбца во всем результирующем наборе запроса.

    $res->fetchAll() возвращает весь набор строк, содержащийся в PDOStatement $res, в виде массива, который можно легко повторить с помощью цикла foreach.

    $rt->assign() - это вызов метода XTemplate::assign(), который используется для назначения одного или массива тегов шаблона.

    cot_checkbox() - это функция Forms API, которая генерирует флажок с заданным именем и текущим значением.

    $options содержит уникальное значение столбца auth_option в таблице cot_auth для заданного значения auth_code.

    функция cot_url() используется для генерации всех URL-адресов в Cotonti. Это делает возможными SEF-адреса и расширенные манипуляции с URL-адресами.

    cot_inputbox() - это функция Forms API, которая генерирует элемент ввода HTML с заданным типом, именем, текущим значением и другими параметрами.

    cot_selectbox() - это функция Forms API, которая генерирует выпадающий список select с заданным именем, выбором, списком значений и заголовков и некоторыми другими параметрами.

    Мы присваиваем значения атрибутов класса, такие как “user”, “code” и “option”, а также значения атрибутов id, чтобы использовать их в нашем коде jQuery для реализации автозавершения.

    $rt->parse('MAIN.RIGHTSPERUSER_ROW') анализирует блок 'RIGHTSPERUSER_ROW' внутри блока 'MAIN' в шаблоне $rt. После этого вызова в объект шаблона добавляется полностью отрисованная строка таблицы.

 

Очень похожий, но гораздо более простой код используется для назначения тегов шаблона и отображения формы, которая используется для добавления нового правила:

$options = empty($rule['ru_code']) ? array('---')
    : $db->query("SELECT DISTINCT auth_option FROM $db_auth WHERE auth_code = ?",
        array($rule['ru_code']))->fetchAll(PDO::FETCH_COLUMN);
foreach ($mn as $key => $val)
{
    $checked = (($rule['ru_rights'] & $val) == $val);
    $rt->assign('RIGHTSPERUSER_ADD_AUTH_'.$key,
            cot_checkbox($checked, 'auth_'.$key));
}
$rt->assign(array(
    'RIGHTSPERUSER_ADD_ACTION'
        => cot_url('admin', 'm=other&p=rightsperuser&a=add'), 
    'RIGHTSPERUSER_ADD_USER'
        => cot_inputbox('text', 'user', $user_name,
            array('class' => 'user')),
    'RIGHTSPERUSER_ADD_CODE'
        => cot_selectbox($rule['ru_code'], 'code', $auth_codes, $auth_codes,
            true, array('class' => 'code', 'id' => 'code_add')),
    'RIGHTSPERUSER_ADD_OPTION'
        => cot_selectbox($rule['ru_option'], 'option', $options, $options,
            false, array('class' => 'option', 'id' => 'opt_add'))
));

Нам нужно добавить еще немного кода в конце нашего скрипта, чтобы весь шаблон был отрисован и отображался как основная часть в панели администрирования:

$rt->parse();
$plugin_body = $rt->text();

Примечание: в автономных плагинах $t используется в качестве объекта шаблона плагина, и этот фрагмент кода не нужен, поскольку он выполняется системой автоматически.

Контроллер отображения теперь готов к работе. Мы добавим к нему некоторые расширенные функции чуть позже, сейчас нам нужно добавить код, который будет обрабатывать действия по манипулированию данными.

5.3. Добавление нового правила

В нашем скрипте есть код, который импортирует данные нового правила, но пока нет кода, который, в частности, обрабатывает действие "add". Давайте добавим его внутри соответствующего "if":

if ($a == 'add')
{
    // Adding a new rule
    if ($db->query("SELECT COUNT(*) FROM $db_rightsperuser
            WHERE ru_user = ? AND ru_code = ? AND ru_option = ?",
            array($rule['ru_user'], $rule['ru_code'], $rule['ru_option']))
            ->fetchColumn() > 0)
    {
        cot_error('rightsperuser_duplicate');
    }
 
    if (!cot_error_found())
    {
        // Insert a new DB record
        $db->insert($db_rightsperuser, $rule);
        // Send success message
        cot_message('rightsperuser_added');
        // Back to main to avoid refresh accidents
        cot_redirect(cot_url('admin', 'm=other&p=rightsperuser', '', true));
    }
}

Сначала мы проверяем, нет ли повторяющейся записи в базе данных. Если найдено правило для одной и той же комбинации пользователя/кода/опции (user/code/option), мы выдаем сообщение об ошибке с кодом 'rightsperuser_duplicate' (позже в этом руководстве мы предоставим локализованные строки для всех сообщений).

Если во время импорта, проверки подлинности и дублирования ошибок обнаружено не было, мы вставляем новую строку в базу данных, отправляем локализованное сообщение об успешном завершении "rightsperuser_added" и перенаправляем обратно на основной URL-адрес инструмента.

Последнее не обязательно, но настоятельно рекомендуется, потому что если вы этого не сделаете и пользователь нажмет клавишу F5 (обновить) в своем браузере, данные будут отправлены снова.

Некоторые новые возможности для расширения вашего глоссария Котонти:

    Метод fetchColumn() класса PDOStatement извлекает значение из одного столбца.

    $db->insert() генерирует и выполняет SQL-запрос INSERT из имени таблицы и ассоциативного массива, содержащего данные строк, или набора таких массивов. Возвращает фактическое количество вставленных записей.

    функция cot_message() является более общим аналогом функции cot_error(), которая выдает все виды сообщений. В нашем случае она выдает статус successful.

    функция cot_redirect() немедленно перенаправляет на относительный URL, переданный в качестве аргумента.

    cot_url() должен использоваться с 4-м параметром, установленным в TRUE, в заголовках HTTP, коде JavaScript и других типах строк, которые не должны быть закодированы в html.

5.4. Обновление существующего правила

Это очень похоже на добавление, но нам нужно положительное подтверждение существования строки, и мы используем другой метод для обновления записи в базе данных:

elseif ($a == 'update' && $id > 0)
{
    // Updating an existing rule
    if ($db->query("SELECT COUNT(*) FROM $db_rightsperuser WHERE ru_id = ?",
            array($id))->fetchColumn() == 0)
    {
        cot_error('rightsperuser_notfound');
    }
 
    if (!cot_error_found())
    {
        // Update record in DB
        $db->update($db_rightsperuser, $rule, "ru_id = ?", array($id));
        // Send success message
        cot_message('rightsperuser_updated');
        // Back to main to avoid refresh accidents
        cot_redirect(cot_url('admin', 'm=other&p=rightsperuser', '', true));
    }
}

Единственное, что для вас ново, - это:

Метод $db->update(), который генерирует и запускает SQL-запрос на обновление из заданных аргументов. Возвращается количество затронутых строк.

5.5. Удаление правила

- Это самое простое действие:

elseif ($a == 'delete' && $id > 0)
{
    // Existing rule removal
    $num = $db->delete($db_rightsperuser, "ru_id = ?", array($id));
    // Send the message
    ($num > 0) ? cot_message('rightsperuser_deleted')
        : cot_error('rightsperuser_delete_error');
    // Back to main to avoid refresh accidents
    cot_redirect(cot_url('admin', 'm=other&p=rightsperuser', '', true));
}

 

$db->delete() удаляет все записи из таблицы, соответствующие заданному условию, и возвращает количество удаленных элементов. В зависимости от того, были удалены некоторые элементы или нет, мы показываем либо сообщение об успешном завершении, либо сообщение об ошибке. Теперь у нас есть минимальный код для работы нашего инструмента администрирования! Позже мы добавим в него некоторые расширенные функции.

6. Глобальная часть для применения правил

Теперь, когда у нас есть таблица, содержащая пользовательские правила аутентификации, и инструмент администратора для ее редактирования, пришло время добавить часть, которая будет применять эти правила.

Она называется "rightsperuser.global.php" и выполняется при каждом запросе.

Ему необходимо проверить, не является ли текущий пользователь гостем, и он должен загрузить и применить правила для этого пользователя. Вот он:

<?php
/* ====================
[BEGIN_COT_EXT]
Hooks=global
Order=9
[END_COT_EXT]
==================== */
 
defined('COT_CODE') or die('Wrong URL');
 
if ($usr['id'] > 0)
{
    // Self API required
    require_once cot_incfile('rightsperuser', 'plug');
     
    // Load all rules for current user
    $res = $db->query("SELECT * FROM $db_rightsperuser WHERE ru_user = ?", array($usr['id']));
    while ($row = $res->fetch())
    {
        $usr['auth'][$row['ru_code']][$row['ru_option']] = $row['ru_rights'];
    }
}

Сначала взгляните на заголовок этой части плагина.
Настройка перехватчиков (Hooks=global) является "глобальной", как мы говорили ранее.
Но что означает Order=9?
Что ж, это означает, что эта часть будет иметь более высокий приоритет, чем у большинства других плагинов, которые используют тот же хук, потому что порядок по умолчанию равен 10.
Это вполне логично, потому что нам нужно применить наши измененные разрешения, прежде чем какие-либо другие плагины смогут начать их использовать.
Тем не менее, те плагины, которым необходимо изменить разрешения перед нашим плагином, могут использовать порядки от 0 до 8, чтобы перезаписать их перед нашим плагином.

Остальное довольно просто, и мы видели такой код раньше. Ну, может быть, за исключением этого:

    метод fetch() класса PDOStatement возвращает следующую строку из результирующего набора в виде массива.
    В Cotonti он настроен так, что по умолчанию возвращает ассоциативный массив.

7. Локализация

Мы уже использовали языковые строки в нашем шаблоне через {PHP.L.some_key} и в нашем PHP-коде через $L['some_key'] или передавали 'some_key' в качестве первого аргумента функций cot_error() и cot_message(). Эти строки действительно должны быть чем-то заменены, и это означает, что мы должны предоставить их в языковых файлах, расположенных в подпапке "lang" нашего плагина.

Для "rightsperuser.en.lang.php", который является нашим файлом на английском языке :

<?php
 
defined('COT_CODE') or die('Wrong URL.');
 
// Strings and messages
$L['rightsperuser_add'] = 'New rule';
$L['rightsperuser_added'] = 'New rule has been added';
$L['rightsperuser_delete_error'] = 'Could not delete the rule';
$L['rightsperuser_deleted'] = 'The rule has been deleted';
$L['rightsperuser_duplicate'] = 'Such a rule already exists';
$L['rightsperuser_invalid_user'] = 'Invalid user name';
$L['rightsperuser_notfound'] = 'No such rule';
$L['rightsperuser_rules'] = 'Rules';
$L['rightsperuser_superadmin'] = 'Cannot affect super administrators';
$L['rightsperuser_title'] = 'Rights per user';
$L['rightsperuser_updated'] = 'The rule has been updated';

Для других языков также могут потребоваться строки для локализованного описания плагина (отображаемого при его установке) и параметров конфигурации плагина в 'rightsperuser.setup.php'. Например, русскоязычный файл для нашего плагина выглядит следующим образом:

<?php
 
defined('COT_CODE') or die('Wrong URL.');
 
// Strings and messages
$L['rightsperuser_add'] = 'Новое правило';
$L['rightsperuser_added'] = 'Новое правило добавлено';
$L['rightsperuser_delete_error'] = 'Невозможно удалить правило';
$L['rightsperuser_deleted'] = 'Правило удалено';
$L['rightsperuser_duplicate'] = 'Такое правило уже существует';
$L['rightsperuser_invalid_user'] = 'Неправильное имя пользователя';
$L['rightsperuser_notfound'] = 'Нет такого правила';
$L['rightsperuser_rules'] = 'Правила';
$L['rightsperuser_superadmin'] = 'Нельзя затрагивать супер-администраторов';
$L['rightsperuser_title'] = 'Индивидуальные права';
$L['rightsperuser_updated'] = 'Правило обновлено';
 
// Localized plugin description
$L['info_desc'] = 'Позволяет назначать права конкретным пользователям, не затрагивая права групп';
 
// Configuration options
$L['cfg_rules_perpage'] = array('Число правил, отображаемых на странице');

Рекомендуется сортировать ключи $L в алфавитном порядке, чтобы было проще поддерживать его по мере увеличения количества строк.

8. Улучшение плагина

Теперь, когда мы запустили базовый плагин, давайте рассмотрим более сложные темы.


8.1. Отображение ошибок и сообщений

Мы использовали функции cot_error() и cot_message() для генерации сообщений об ошибках и успешном завершении, мы использовали cot_error_found() для проверки наличия каких-либо ошибок, но мы не добавили никакого кода для их отображения.

Чтобы отобразить сообщения в нашем шаблоне, нам нужно включить стандартный файл шаблона, который определяет их макет. Итак, мы добавляем этот код в начало 'rightsperuser.tpl':
    
 

{FILE "{PHP.cfg.themes_dir}/{PHP.cfg.defaulttheme}/warnings.tpl"}

И чтобы отобразить их, нам нужно передать наш объект шаблона функции cot_display_messages() в теле нашего PHP-скрипта ('rightsperuser.admin.php'):

cot_display_messages($rt);

Теперь наша обработка ошибок завершена.

8.2. Разбивка на страницы

Наш инструмент администрирования хорош для нескольких правил, но что, если их десятки? Тогда нам нужно разбить их на несколько страниц, т.е. использовать разбиение на страницы.

Реализация разбиения на страницы состоит из 3 частей. Сначала нам нужно импортировать параметры разбивки на страницы из текущего URL и преобразовать их в номер страницы, смещение базы данных и параметр URL:

list($pg, $d, $durl) = cot_import_pagenav('d', $cfg['plugin']['rightsperuser']['rules_perpage']);

'd' - это имя параметра GET, используемого при разбиении на страницы.
$pg будет содержать номер текущей страницы,
$d - это смещение базы данных, используемое в SQL-запросах, а
$durl используется в URL-адресах.
$cfg['plugin']['rightsperuser']['rules_perpage'] - это значение параметра нашей конфигурации, которое устанавливает максимальное количество строк на странице.

Затем мы модифицируем основной запрос SELECT и добавляем к нему раздел LIMIT:

$res = $db->query("SELECT r.*, u.user_name
    FROM $db_rightsperuser AS r
        LEFT JOIN $db_users AS u ON r.ru_user = u.user_id
    ORDER BY ru_user, ru_code, ru_option
    LIMIT $d, {$cfg['plugin']['rightsperuser']['rules_perpage']}");

И последний шаг - это рендеринг тегов разбивки на страницы, которые будут отображаться в шаблоне:

$totalitems = $db->query("SELECT COUNT(*)
        FROM $db_rightsperuser")->fetchColumn();
$pagenav = cot_pagenav('admin', 'm=other&p=rightsperuser', $d, $totalitems,
        $cfg['plugin']['rightsperuser']['rules_perpage']);
 
$rt->assign(array(
    'RIGHTSPERUSER_PAGENAV_PREV' => $pagenav['prev'],
    'RIGHTSPERUSER_PAGENAV_MAIN' => $pagenav['main'],
    'RIGHTSPERUSER_PAGENAV_NEXT' => $pagenav['next']
));

Сначала мы получаем общее количество строк среди всех страниц. Затем мы генерируем массив разбивки на страницы, используя функцию cot_pagenav(). У нее много параметров, но в этом примере мы передаем только область URL, параметры URL, текущее смещение, общее количество элементов и количество элементов на странице. Возвращаемый массив содержит следующие компоненты:

    main – кнопки с номерами страниц;

    prev – ссылка на предыдущую страницу;

    next – ссылка на следующую страницу;

    first – ссылка на первую страницу;

    last – ссылка на последнюю страницу;

    firstlink – URL первой страницы;

    lastlink – URL последней страницы;

    prevlink – URL предыдущей страницы;

    nextlink – URL следующей страницы;

    currentpage – номер текущей страницы;

    total – общее количество страниц;

    onpage – элементы на текущей странице;

    entries – общее количество элементов.
    
После того, как теги будут назначены, мы можем добавить разбиение на страницы в наш TPL-файл 'rightsperuser.tpl':

<p class="paging">
    {RIGHTSPERUSER_PAGENAV_PREV}{RIGHTSPERUSER_PAGENAV_MAIN}{RIGHTSPERUSER_PAGENAV_NEXT}
</p>

8.3. Справка в admin tools

В Admin tools могут быть интерактивные разделы справки, дающие администратору некоторые подсказки. Чтобы добавить такой раздел в свой плагин, просто назначьте его HTML-код переменной $admin help.

Но нам нужно сохранить HTML-справку где-нибудь в шаблоне. Итак, давайте добавим еще один блок корневого уровня после MAIN в 'rightsperuser.tpl':

<!-- BEGIN: HELP -->
    <p>{PHP.R.admin_icon_auth_r}&nbsp; {PHP.L.Read}</p>
    <p>{PHP.R.admin_icon_auth_w}&nbsp; {PHP.L.Write}</p>
    <p>{PHP.R.admin_icon_auth_1}&nbsp; {PHP.L.Custom} #1</p>
    <p>{PHP.R.admin_icon_auth_2}&nbsp; {PHP.L.Custom} #2</p>
    <p>{PHP.R.admin_icon_auth_3}&nbsp; {PHP.L.Custom} #3</p>
    <p>{PHP.R.admin_icon_auth_4}&nbsp; {PHP.L.Custom} #4</p>
    <p>{PHP.R.admin_icon_auth_5}&nbsp; {PHP.L.Custom} #5</p>
    <p>{PHP.R.admin_icon_auth_a}&nbsp; {PHP.L.Administration}</p>
<!-- END: HELP -->

Тогда мы можем использовать этот блок в 'rightsperuser.admin.php' следующим образом:

$rt->parse('HELP');
$adminhelp = $rt->text('HELP');

8.4. Автозаполнение AJAX

В нашем редакторе правил есть 2 вида элементов, для которых требуется помощь AJAX и JavaScript.

Первый - это ввод имени пользователя. На сайте могут быть зарегистрированы тысячи пользователей, поэтому мы хотели бы предоставить функцию автозаполнения для ввода имени пользователя.

Второй - раскрывающийся список параметров аутентификации: когда пользователь выбирает код аутентификации, нам нужно предоставить параметры аутентификации именно для этого кода.

Мы будем повторно использовать автозаполнение для имен пользователей из плагина 'autocomplete'.

Поскольку этот плагин требуется установить, необходимая библиотека JS и серверная часть AJAX уже присутствуют в системе.

Нам нужно только применить автозаполнение к нашим входным данным с помощью класса 'user'.

В JavaScript это означает:

$('input.user').autocomplete('index.php?r=autocomplete', {minChars: 3});

Обновление списка опций немного сложнее.

Прежде всего, нам нужно добавить обработчик AJAX для этого в наш плагин.

Он должен использовать хук 'ajax'.

Давайте назовем его 'rightsperuser.ajax.php':

<?php
/* ====================
[BEGIN_COT_EXT]
Hooks=ajax
[END_COT_EXT]
==================== */
 
defined('COT_CODE') or die('Wrong URL');
 
$code = cot_import('code', 'G', 'ALP');
 
// Get options for given code
$options = $db->query("SELECT DISTINCT auth_option FROM $db_auth WHERE auth_code = ?",
        array($code))->fetchAll(PDO::FETCH_COLUMN);
 
// Output as JSON
header('Content-Type: application/json');
echo json_encode($options);

Он просто получает все параметры для данного кода из базы данных в виде массива, кодирует его как JSON и отправляет на вывод со старым добрым PHP echo.

JavaScript-часть нашего плагина сохраняется в файле 'js/rightsperuser.js'.

Полное содержимое этого файла включает:

$(function() {
    // Auto-complete for username
    $('input.user').autocomplete('index.php?r=autocomplete', {minChars: 3});
    // Load options via AJAX when code is changed
    $('select.code').change(function() {
        var id = ($(this).attr('id').split('_'))[1];
        $.ajax({
            url: "index.php?r=rightsperuser&code=" + $(this).val(),
            success: function(data) {
                var options = $('select#opt_'+id);
                var optHtml = '';
                for (var i = 0; i < data.length; i++) {
                    optHtml += '<option>' + data[i] + '</option>';
                }
                options.html(optHtml);
            }
        });
    });
});

Существует 2 способа создания ссылки на этот скрипт с веб-страницы.

Первый - использование интерфейса Cotonti “header resources”, он же “JS/CSS consolidation”.

Второй - прямая ссылка из tpl. Наш скрипт - это простой инструмент администрирования, который не должен быть виден нигде снаружи, поэтому мы выбираем простой второй способ и оставляем первый способ для некоторых других статей.

Итак, мы добавляем это в 'rightsperuser.tpl':

<script type="text/javascript"
    src="{PHP.cfg.plugins_dir}/rightsperuser/js/rightsperuser.js">
</script>

9. Заключение

Взгляните на плагин, который мы создали вместе:


Вы можете найти полный исходный код плагина на Github и загрузить его.

 

Это руководство охватывает не всё, и оно не очень подробное, но мы надеемся, что, прочитав его, вы сделали важный шаг в разработке расширения Cotonti.

Автор пособия
Vladimir Sibirov
trustmaster

Перевод с английского - 2024 (c) webitproff

Отредактировано: Administrator (17.02.2024 18:48, 2 года назад)
Аккаунт