В сегодняшнем уроке я расскажу, как написать свою сущность. Писать мы будем с применением модуля Entity API, поэтому нам необходимо скачать и установить этот модуль. Для примера, создадим сущность product. Файловая структура модуля у меня получилась такая:
- product
- product.info
- product.install
- product.module
- product.pages.inc
- product.admin.inc
- js
- product-fieldset-summaries.js
- templates
- product.tpl.php
1. Создаем информацию об модуле в product.info:
name = Product
description = Defines the product entity and associated features.
core = 7.x
dependencies[] = entity
2. В product.install описываем таблицу, в которой будет хранится информация об сущностях:
/**
* Implements hook_schema().
*/
function product_schema() {
$schema['product'] = array(
'description' => 'The base table for products.',
'fields' => array(
'id' => array(
'description' => 'The primary identifier for a product.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'title' => array(
'description' => 'The title of this product, always treated as non-markup plain text.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'uid' => array(
'description' => 'The {users}.uid that owns this product.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'status' => array(
'description' => 'Boolean indicating whether the status of this product',
'type' => 'int',
'not null' => TRUE,
'default' => 1,
),
'created' => array(
'description' => 'The Unix timestamp when the product was created.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'changed' => array(
'description' => 'The Unix timestamp when the product was most recently saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('id'),
'indexes' => array(
'uid' => array('uid'),
'product_changed' => array('changed'),
'product_created' => array('created'),
'product_status' => array('status'),
),
);
return $schema;
}
3. Перейдем к product.module. В таблице я создал колонку статус, в которой будет хранится статус продукта, всего у меня 3 статуса, для каждого из статусов создаем константу:
/**
* Product is pending.
*/
define('PRODUCT_PENDING', 0);
/**
* Product is completed.
*/
define('PRODUCT_COMPLETED', 1);
/**
* Product is canceled.
*/
define('PRODUCT_CANCELED', 2);
4. Описываем информацию об нашей сущности в хуке hook_entity_info():
/**
* Implements hook_entity_info().
*/
function product_entity_info() {
$return = array(
// Ключ 'product' - машинное название сущности.
'product' => array(
'label' => t('Product'), // Человеко-понятное название сущности.
'entity class' => 'Entity', // Класс сущности.
'controller class' => 'EntityAPIController', // Контроллер сущности.
'base table' => 'product', // Таблица, в которой хранится информация об сущностях.
'uri callback' => 'product_uri', // Функция, которая возваращает uri сущности.
'fieldable' => TRUE, // Позволяем прикреплять поля к сущности.
'entity keys' => array('id' => 'id'),
// Массив, в котором описываются типы сущности Product (не знаю как правильно выразится),
// если привести анологию с модулем Node, то это типы материалов.
'bundles' => array(
// Ключ 'product' - машинное название типа.
'product' => array(
'label' => t('Product'), // Человеко-понятное название типа.
'admin' => array(
'path' => 'admin/config/product/products', // Путь, по которому доступна админка.
'access arguments' => array('configure products settings'), // Права доступа в админку.
),
),
),
// Режимы отображения сущности.
'view modes' => array(
// Ключи 'full' и 'administrator' - машинные названия режимов.
'full' => array(
'label' => t('Full'), // Человеко-понятное название режима.
'custom settings' => TRUE, // Режим включен по умолчанию.
),
'administrator' => array(
'label' => t('Administrator'),
'custom settings' => TRUE,
),
),
'module' => 'product',
),
);
return $return;
}
5. Создаем функцию product_uri(), которая возвращает uri сущности:
/**
* Implements callback_entity_info_uri().
*/
function product_uri($product) {
return array(
'path' => 'product/' . $product->id,
);
}
6. Создаем права доступа, которые нам потребуются в дальнейшем, для этого имплементируем хук hook_permission():
/**
* Implements hook_permission().
*/
function product_permission() {
return array(
'configure products settings' => array(
'title' => t('Configure products settings'),
'description' => t('Allows users to configure products settings.'),
'restrict access' => TRUE,
),
'administer products' => array(
'title' => t('Administer products'),
'restrict access' => TRUE,
),
'edit any products' => array(
'title' => t('Edit any product'),
'restrict access' => TRUE,
),
'edit own products' => array(
'title' => t('Edit own products'),
'restrict access' => TRUE,
),
'delete any products' => array(
'title' => t('Delete any products'),
'restrict access' => TRUE,
),
'delete own products' => array(
'title' => t('Delete own products'),
'restrict access' => TRUE,
),
'create products' => array(
'title' => t('Create new products'),
),
'view products' => array(
'title' => t('View products'),
),
);
}
7. Создаем функцию product_access(), которая будет проверять, может ли пользователь совершать какие либо операции над сущностью:
/**
* Determines whether the current user may perform the operation on the product.
*
* @param $op
* The operation to be performed on the product. Possible values are:
* - "view"
* - "update"
* - "delete"
* - "create"
* @param $entity_type
* The entity type on which the operation is to be perform.
* @param $product
* The product object on which the operation is to be performed.
* @param $account
* Optional, a user object representing the user for whom the operation is to
* be performed. Determines access for a user other than the current user.
*
* @return bool
* TRUE if the operation may be performed, FALSE otherwise.
*/
function product_access($op, $entity_type, $product = NULL, $account = NULL) {
$rights = &drupal_static(__FUNCTION__, array());
if (!in_array($op, array('view', 'update', 'delete', 'create'), TRUE)) {
// Если $op не равен ни одной из поддерживаемых операций, возвращаем "доступ запрещен".
return FALSE;
}
// Если в функцию не передан пользователь, то проверяем права для текущего пользователя.
if (empty($account)) {
global $user;
$account = $user;
}
// $product может быть объектом или не существовать, поэтому испольем его id,
// или $entity_type в качестве статичного идентификатора, который будет использоваться ключом кеша.
$cid = is_object($product) && !empty($product->id) ? $product->id : $entity_type;
// Если мы уже проверяли для данной сущности и пользователя права доступа,
// то возвращаем их из кеша.
if (isset($rights[$account->uid][$cid][$op])) {
return $rights[$account->uid][$cid][$op];
}
// Проверяем, может ли пользователь создавать новые продукты.
if ($op == 'create' && user_access('create products', $account)) {
$rights[$account->uid][$cid][$op] = TRUE;
return TRUE;
}
if ($op == 'update') {
// Проверяем, может ли пользователь редактирвоать любые продукты.
if (user_access('edit any products', $account)) {
$rights[$account->uid][$cid][$op] = TRUE;
return TRUE;
}
// Проверяем, может ли пользователь редактирвоать свои продукты.
elseif (user_access('edit own products', $account) && $product->uid == $account->uid) {
$rights[$account->uid][$cid][$op] = TRUE;
return TRUE;
}
}
elseif ($op == 'delete') {
// Проверяем, может ли пользователь удалять любые продукты.
if (user_access('delete any products', $account)) {
$rights[$account->uid][$cid][$op] = TRUE;
return TRUE;
}
// Проверяем, может ли пользователь удалять свои продукты.
elseif (user_access('delete own products', $account) && $product->uid == $account->uid) {
$rights[$account->uid][$cid][$op] = TRUE;
return TRUE;
}
}
// Проверяем, может ли пользователь просматривать продукты.
elseif ($op == 'view' && user_access('view products', $account)) {
$rights[$account->uid][$cid][$op] = TRUE;
return TRUE;
}
return FALSE;
}
8. Создаем функции, которые будут загружать сущность из базы данных:
/**
* Loads a product by ID.
*/
function product_load($product_id) {
$products = product_load_multiple(array($product_id), array());
return $products ? reset($products) : FALSE;
}
/**
* Loads multiple products by ID or based on a set of matching conditions.
*
* @see entity_load()
*
* @param $product_ids
* An array of product IDs.
* @param $conditions
* An array of conditions to filter loaded products by on the {product}
* table in the form 'field' => $value.
* @param $reset
* Whether to reset the internal product loading cache.
*
* @return
* An array of product objects indexed by product_id.
*/
function product_load_multiple($product_ids = array(), $conditions = array(), $reset = FALSE) {
return entity_load('product', $product_ids, $conditions, $reset);
}
/**
* Implements hook_entity_load().
*/
function product_entity_load($entities, $type) {
// По умолчанию, сущность имеет только uid пользователя, который создал ее,
// добавляем этот код, чтобы получить еще и имя пользователя.
if ($type == 'product') {
foreach ($entities as $entity) {
if ($entity->uid) {
if ($account = user_load($entity->uid)) {
$entity->name = $account->name;
}
}
else {
$entity->name = variable_get('anonymous', t('Anonymous'));
}
}
}
}
9. Создаем функции, которые будут удалять сущность из базы данных:
/**
* Deletes a product.
*
* @param $product_id
* A product ID.
*/
function product_delete($product_id) {
product_delete_multiple(array($product_id));
}
/**
* Deletes multiple products.
*
* @param $product_ids
* An array of product IDs.
*/
function product_delete_multiple($product_ids) {
entity_delete_multiple('product', $product_ids);
}
10. Создаем функцию темизации сущности, для этого имплементируем hook_theme():
/**
* Implements hook_theme().
*/
function product_theme() {
return array(
'product' => array(
'render element' => 'elements',
'template' => 'templates/product',
),
);
}
/**
* Processes variables for product.tpl.php
*/
function template_preprocess_product(&$variables) {
$variables['view_mode'] = $variables['elements']['#view_mode'];
$variables['product'] = $variables['elements']['#product'];
$product = $variables['product'];
$variables['date'] = format_date($product->created);
$variables['name'] = theme('username', array('account' => $product));
$uri = product_uri($product);
$variables['product_url'] = url($uri['path']);
$variables['title'] = check_plain($product->title);
$variables['page'] = $variables['view_mode'] == 'full';
$variables = array_merge((array) $product, $variables);
$variables += array('content' => array());
foreach (element_children($variables['elements']) as $key) {
$variables['content'][$key] = $variables['elements'][$key];
}
// Делаем поля доступными в качестве переменных для соответствующего языка.
field_attach_preprocess('product', $product, $variables['content'], $variables);
$variables['submitted'] = t('Submitted by !username on !datetime', array('!username' => $variables['name'], '!datetime' => $variables['date']));
$variables['title_attributes_array']['class'][] = 'product-title';
if ($variables['status'] == PRODUCT_PENDING) {
$variables['classes_array'][] = 'product-pending';
}
elseif ($variables['status'] == PRODUCT_COMPLETED) {
$variables['classes_array'][] = 'product-completed';
}
elseif ($variables['status'] == PRODUCT_CANCELED) {
$variables['classes_array'][] = 'product-canceled';
}
}
11. Создаем функции, которые будут выводить сущности:
/**
* Constructs a drupal_render() style array from an array of loaded products.
*
* @param $products
* An array of products as returned by product_load_multiple().
* @param $view_mode
* View mode, e.g. 'teaser', 'full'...
* @param $langcode
* (optional) A language code to use for rendering. Defaults to NULL which is
* the global content language of the current request.
*
* @return array
* An array in the format expected by drupal_render().
*/
function product_view_multiple($products, $view_mode = 'full', $langcode = NULL) {
// Подготавливаем данные для отображения.
field_attach_prepare_view('product', $products, $view_mode, $langcode);
entity_prepare_view('product', $products, $langcode);
$build = array();
$weight = 0;
foreach ($products as $product) {
$build['products'][$product->id] = product_view($product, $view_mode, $langcode);
$build['products'][$product->id]['#weight'] = $weight++;
}
$build['products']['#sorted'] = TRUE;
return $build;
}
/**
* Generates an array for rendering the given product.
*
* @param $product
* A product object.
* @param $view_mode
* View mode, e.g. 'teaser', 'full'...
* @param $langcode
* (optional) A language code to use for rendering. Defaults to the global
* content language of the current request.
*
* @return array
* An array as expected by drupal_render().
*/
function product_view($product, $view_mode = 'full', $langcode = NULL) {
if (!isset($langcode)) {
global $language;
$langcode = $language->language;
}
// Заполняем $product->content данными в виде рендерного массива.
product_build_content($product, $view_mode, $langcode);
$build = $product->content;
// Скрываем данные, чтобы избежать их дублирования при выводе $product->content.
unset($product->content);
$build += array(
'#theme' => 'product',
'#product' => $product,
'#view_mode' => $view_mode,
'#language' => $langcode,
);
return $build;
}
/**
* Builds a structured array representing the product's content.
*
* @param $product
* A product object.
* @param $view_mode
* View mode, e.g. 'teaser', 'full'...
* @param $langcode
* (optional) A language code to use for rendering. Defaults to the global
* content language of the current request.
*/
function product_build_content($product, $view_mode = 'full', $langcode = NULL) {
if (!isset($langcode)) {
global $language;
$langcode = $language->language;
}
// Удаляем существующий конент, если существует.
$product->content = array();
field_attach_prepare_view('product', array($product->id => $product), $view_mode, $langcode);
entity_prepare_view('product', array($product->id => $product), $langcode);
$product->content += field_attach_view('product', $product, $view_mode, $langcode);
$product->content += array('#view_mode' => $view_mode);
$product->content['links'] = array(
'#theme' => 'links__product',
'#pre_render' => array('drupal_pre_render_links'),
'#attributes' => array('class' => array('links')),
);
}
12. В hook_menu() создаем страницы, которые потребуются для работы модуля:
/**
* Implements hook_menu().
*/
function product_menu() {
// Top level "Product" container.
$items['admin/config/product'] = array(
'title' => 'Product',
'description' => 'Administration tools.',
'page callback' => 'system_admin_menu_block_page',
'access arguments' => array('access administration pages'),
'file' => 'system.admin.inc',
'file path' => drupal_get_path('module', 'system'),
);
$items['admin/config/product/products'] = array(
'title' => 'Products',
'description' => 'Configure general product settings, fields, and displays.',
'page callback' => 'product_admin_products',
'access arguments' => array('administer products'),
'type' => MENU_NORMAL_ITEM,
'file' => 'product.admin.inc',
);
$items['admin/config/product/products/list'] = array(
'title' => 'Products',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
// Страница создания продукта.
$items['product/add'] = array(
'title' => 'Add product',
'page callback' => 'product_page_add',
'access callback' => 'product_access',
'access arguments' => array('create', 'product'),
'file' => 'product.pages.inc',
);
// Страница просмотра продукта.
$items['product/%product'] = array(
'title callback' => 'product_page_title',
'title arguments' => array(1),
'page callback' => 'product_page_view',
'page arguments' => array(1),
'access callback' => 'product_access',
'access arguments' => array('view', 'product', 1),
'file' => 'product.pages.inc',
);
$items['product/%product/view'] = array(
'title' => 'View',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => 0,
);
// Страница редактирования продукта.
$items['product/%product/edit'] = array(
'title' => 'Edit',
'page callback' => 'product_page_edit',
'page arguments' => array(1),
'access callback' => 'product_access',
'access arguments' => array('update', 'product', 1),
'weight' => 1,
'type' => MENU_LOCAL_TASK,
'file' => 'product.pages.inc',
);
// Страница удаления продукта.
$items['product/%product/delete'] = array(
'title' => 'Delete',
'page callback' => 'drupal_get_form',
'page arguments' => array('product_delete_confirm', 1),
'access callback' => 'product_access',
'access arguments' => array('delete', 'product', 1),
'file' => 'product.pages.inc',
);
return $items;
}
13. С помощью hook_admin_paths(), говорим системе, какие страницы являются административными:
/**
* Implements hook_admin_paths().
*/
function product_admin_paths() {
$paths = array(
'product/add' => TRUE,
'product/*/edit' => TRUE,
'product/*/delete' => TRUE,
);
return $paths;
}
14. Последней функцией в product.module будет product_status_get_title(), которая по id статуса продукта будет возвращать человеко-понятное название статуса:
/**
* Returns the human readable title of any or all product statuses.
*
* @param $id
* Optional parameter specifying the id of the product status whose title
* to return.
*
* @return mixed
* Either an array of all product status titles keyed by the status_id or a
* string containing the human readable title for the specified status.
*/
function product_status_get_title($id = NULL) {
$ids = array(
PRODUCT_PENDING => t('Pending'),
PRODUCT_COMPLETED => t('Completed'),
PRODUCT_CANCELED => t('Canceled'),
);
if (isset($id)) {
return $ids[$id];
}
return $ids;
}
15. С product.module закончили, теперь переходим к product.pages.inc, в этом файле создадим все колбеки для страниц. Создаем функции, которые будут показывать сущность по адресу product/%product:
/**
* Title callback: Returns the title of the product.
*
* @param $product
* The product object.
*
* @return
* An unsanitized string that is the title of the product.
*
* @see product_menu()
*/
function product_page_title($product) {
return $product->title;
}
/**
* Menu callback: Displays a single product.
*
* @param $product
* The product object.
*
* @return array
* A page array suitable for use by drupal_render().
*
* @see product_menu()
*/
function product_page_view($product) {
// For markup consistency with other pages, use product_view_multiple() rather than product_view().
$products = product_view_multiple(array($product->id => $product), 'full');
return $products;
}
16. Создаем функцию product_page_add(), которая будет выводить форму создания сущности по адресу product/add:
/*
* Create a basic entity structure to be used and passed to the validation
* and submission functions.
*/
function product_page_add() {
global $user;
$name = variable_get('anonymous', t('Anonymous'));
if ($user->uid && $account = user_load($user->uid)) {
$name = $account->name;
}
$product = entity_create('product', array('uid' => $user->uid, 'name' => $name));
return drupal_get_form('product_form', $product, 'create');
}
17. Создаем функцию product_page_edit(), которая будет показывать форму редактирования сущности, по адресу product/%product/edit:
/*
* Presents the product editing form.
*/
function product_page_edit($product) {
drupal_set_title(t('<em>Edit</em> @title', array('@title' => $product->title)), PASS_THROUGH);
return drupal_get_form('product_form', $product);
}
18. Как вы могли заметить, для создания и редактирвоания сущности у меня используется одна и таже форма product_form, создаем функцию, формирующую эту форму:
/**
* Form constructor for the product add/edit form.
*
* @see product_form_validate()
* @see product_form_submit()
* @see product_form_delete_submit()
* @ingroup forms
*/
function product_form($form, &$form_state, $product, $op = 'update') {
$form['#attributes']['class'][] = 'product-form';
$form['product'] = array(
'#type' => 'value',
'#value' => $product,
);
$form['title'] = array(
'#type' => 'textfield',
'#title' => t('Title'),
'#default_value' => !empty($product->title) ? $product->title : '',
'#required' => TRUE,
);
$form['additional_settings'] = array(
'#type' => 'vertical_tabs',
'#weight' => 99,
);
// Информация об авторе продукта.
$form['author'] = array(
'#type' => 'fieldset',
'#access' => user_access('administer products'),
'#title' => t('Authoring information'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#group' => 'additional_settings',
'#attributes' => array(
'class' => array('product-form-author'),
),
'#attached' => array(
'js' => array(
drupal_get_path('module', 'product') . '/js/product-fieldset-summaries.js',
array(
'type' => 'setting',
'data' => array('anonymous' => variable_get('anonymous', t('Anonymous'))),
),
),
),
'#weight' => 90,
);
$form['author']['name'] = array(
'#type' => 'textfield',
'#title' => t('Authored by'),
'#maxlength' => 60,
'#autocomplete_path' => 'user/autocomplete',
'#default_value' => !empty($product->name) ? $product->name : '',
'#weight' => -1,
'#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', t('Anonymous')))),
);
$form['author']['date'] = array(
'#type' => 'textfield',
'#title' => t('Authored on'),
'#maxlength' => 25,
'#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the time zone offset from UTC. Leave blank to use the time of form submission.', array('%time' => !empty($product->created) ? format_date($product->created, 'custom', 'Y-m-d H:i:s O') : format_date(REQUEST_TIME, 'custom', 'Y-m-d H:i:s O'), '%timezone' => !empty($product->created) ? format_date($product->created, 'custom', 'O') : format_date(REQUEST_TIME, 'custom', 'O'))),
'#default_value' => !empty($product->created) ? format_date($product->created, 'custom', 'Y-m-d H:i:s O') : '',
);
// Статусы продукта.
$form['options'] = array(
'#type' => 'fieldset',
'#access' => user_access('administer products'),
'#title' => t('Product status'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#group' => 'additional_settings',
'#attributes' => array(
'class' => array('product-form-options'),
),
'#attached' => array(
drupal_get_path('module', 'product') . '/js/product-fieldset-summaries.js',
),
'#weight' => 95,
);
$form['options']['status'] = array(
'#type' => 'radios',
'#title' => t('Status'),
'#default_value' => isset($product->status) ? $product->status : PRODUCT_PENDING,
'#options' => array(
PRODUCT_PENDING => product_status_get_title(PRODUCT_PENDING),
PRODUCT_COMPLETED => product_status_get_title(PRODUCT_COMPLETED),
PRODUCT_CANCELED => product_status_get_title(PRODUCT_CANCELED),
),
);
// Кнопка отправки формы.
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
'#weight' => 5,
);
// Если пользователь редактирует продукт и у него есть разрешение для удаления продукта,
// то добавляем кнопку "Удалить".
if ($op == 'update' && product_access('delete', 'product', $product)) {
$form['actions']['delete'] = array(
'#type' => 'submit',
'#value' => t('Delete'),
'#weight' => 15,
'#submit' => array('product_form_delete_submit'),
);
}
// Прикрепляем все созданные поля сущности к форме.
field_attach_form('product', $product, $form, $form_state);
return $form;
}
/**
* Form validation handler for product_form().
*
* @see product_form()
* @see product_form_submit()
*/
function product_form_validate($form, &$form_state) {
$product = $form_state['values']['product'];
// Проверяем корректность автора.
$product->uid = 0;
if (!empty($form_state['values']['name'])) {
if ($account = user_load_by_name($form_state['values']['name'])) {
$product->uid = $account->uid;
}
elseif ($form_state['values']['name'] != variable_get('anonymous', t('Anonymous'))) {
form_set_error('name', t('The username %name does not exist.', array('%name' => $form_state['values']['name'])));
}
}
// Проверяем корректность даты публикации.
if (!empty($form_state['values']['date'])) {
if (strtotime($form_state['values']['date']) === FALSE) {
form_set_error('date', t('You have to specify a valid date.'));
}
else {
$date = date_create($form_state['values']['date']);
$product->created = $date->getTimestamp();
}
}
else {
$product->created = REQUEST_TIME;
}
}
/**
* Form submission handler for product_form().
*
* @see product_form()
* @see product_form_validate()
*/
function product_form_submit($form, &$form_state) {
$product = $form_state['values']['product'];
$product->title = $form_state['values']['title'];
$product->changed = REQUEST_TIME;
$product->status = $form_state['values']['status'];
field_attach_submit('product', $product, $form, $form_state);
$is_new = !empty($product->is_new) ? TRUE : FALSE;
$product->save();
if ($is_new) {
drupal_set_message(t('%title has been created.', array('%title' => $product->title)));
}
else {
drupal_set_message(t('%title has been updated.', array('%title' => $product->title)));
}
$form_state['redirect'] = array('product/' . $product->id);
}
/**
* Form submission handler for product_form().
*
* Handles the 'Delete' button on the product form.
*
* @see product_form()
* @see product_form_validate()
*/
function product_form_delete_submit($form, &$form_state) {
$destination = array();
if (isset($_GET['destination'])) {
$destination = drupal_get_destination();
unset($_GET['destination']);
}
$product = $form_state['values']['product'];
$form_state['redirect'] = array('product/' . $product->id . '/delete', array('query' => $destination));
}
19. Создаем форму удаления сущности:
/**
* Form constructor for the product deletion confirmation form.
*
* @see product_delete_confirm_submit()
*/
function product_delete_confirm($form, &$form_state, $product) {
$form['product_id'] = array('#type' => 'value', '#value' => $product->id);
return confirm_form($form,
t('Are you sure you want to delete %title?', array('%title' => $product->title)),
'products',
t('This action cannot be undone.'),
t('Delete'),
t('Cancel')
);
}
/**
* Executes product deletion.
*
* @see product_delete_confirm()
*/
function product_delete_confirm_submit($form, &$form_state) {
if ($form_state['values']['confirm']) {
$product = product_load($form_state['values']['product_id']);
product_delete($form_state['values']['product_id']);
drupal_set_message(t('%title has been deleted.', array('%title' => $product->title)));
}
$form_state['redirect'] = 'admin/config/product/products';
}
20. С product.pages.inc закончили, теперь переходим к product.admin.inc, в этом файле будет одна функция product_admin_products(), которая будет выводить список всех созданных продуктов по адресу admin/config/product/products:
/**
* Page callback: Builds the product administration overview.
*/
function product_admin_products() {
// Формируем сортируемую шапку для таблицы.
$header = array(
'title' => array('data' => t('Title'), 'field' => 'p.title'),
'author' => t('Author'),
'status' => array('data' => t('Status'), 'field' => 'p.status'),
'changed' => array('data' => t('Updated'), 'field' => 'p.changed', 'sort' => 'desc'),
'operations' => array('data' => t('Operations')),
);
$query = db_select('product', 'p')->extend('PagerDefault')->extend('TableSort');
$product_ids = $query->fields('p', array('id'))
->limit(50)
->orderByHeader($header)
->execute()
->fetchCol();
$products = product_load_multiple($product_ids);
// Подготавливаем список продуктов.
$destination = drupal_get_destination();
$rows = array();
foreach ($products as $product) {
$rows[$product->id] = array(
'title' => array(
'data' => array(
'#type' => 'link',
'#title' => $product->title,
'#href' => 'product/' . $product->id,
),
),
'author' => theme('username', array('account' => $product)),
'status' => product_status_get_title($product->status),
'changed' => format_date($product->changed, 'short'),
);
// Формируем список доступных операций над текущим продуктом.
$operations = array();
if (product_access('update', 'product', $product)) {
$operations['edit'] = array(
'title' => t('edit'),
'href' => 'product/' . $product->id . '/edit',
'query' => $destination,
);
}
if (product_access('delete', 'product', $product)) {
$operations['delete'] = array(
'title' => t('delete'),
'href' => 'product/' . $product->id . '/delete',
'query' => $destination,
);
}
$rows[$product->id]['operations'] = array();
if (count($operations) > 1) {
// Выводим операции в виде списка.
$rows[$product->id]['operations'] = array(
'data' => array(
'#theme' => 'links__product_operations',
'#links' => $operations,
'#attributes' => array('class' => array('links', 'inline')),
),
);
}
elseif (!empty($operations)) {
// Выводим первую и единственную операцию.
$link = reset($operations);
$rows[$product->id]['operations'] = array(
'data' => array(
'#type' => 'link',
'#title' => $link['title'],
'#href' => $link['href'],
'#options' => array('query' => $link['query']),
),
);
}
}
$page = array();
$page['products'] = array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
'#empty' => t('No products available.'),
);
$page['pager'] = array('#markup' => theme('pager'));
return $page;
}
21. Теперь переходим к шаблону product.tpl.php:
<div id="product-<?php print $product->id; ?>" class="<?php print $classes; ?> clearfix"<?php print $attributes; ?>>
<?php print render($title_prefix); ?>
<?php if (!$page): ?>
<h2<?php print $title_attributes; ?>><?php print $title; ?></h2>
<?php endif; ?>
<?php print render($title_suffix); ?>
<div class="submitted">
<?php print $submitted; ?>
</div>
<div class="content"<?php print $content_attributes; ?>>
<?php print render($content); ?>
</div>
</div>
21. Осталось дело за малым, если помните, в форме создания/редактирования продукта мы сделали вериткальные вкладки, поэтому в файл product-fieldset-summaries.js добавляем несколько строк кода для украшения этих вкладок:
(function ($) {
Drupal.behaviors.productFieldsetSummaries = {
attach: function (context) {
$('fieldset.product-form-author', context).drupalSetSummary(function (context) {
return $('div.form-item-name input', context).val();
});
$('fieldset.product-form-author', context).drupalSetSummary(function (context) {
var name = $('div.form-item-name input', context).val() || Drupal.settings.anonymous,
date = $('div.form-item-date input', context).val();
return date ?
Drupal.t('By @name on @date', { '@name': name, '@date': date }) :
Drupal.t('By @name', { '@name': name });
});
$('fieldset.product-form-options', context).drupalSetSummary(function (context) {
var vals = [];
var statusId = $('div.form-item-status input:checked', context).attr('id');
var status = $('div.form-item-status label[for=' + statusId + ']', context).text();
vals.push(Drupal.checkPlain($.trim(status)));
return vals.join(', ');
});
}
};
})(jQuery);
Вот и все. Так же читайте как связать сущность с модулем Views.