С чего начать создание темы Cotonti
Популярные запросы: тема Omnis, плагин Pagelist, Cotonti 0.9.25, ЧПУ, Font Face
- 604 просмотра
- 20 января, 2023
- Обновлено: 16 октября, 2023
- admin
- Время чтения: 11 минут
После того, как вы развернули на удаленном или локальном сервере дистрибутив Cotonti, необходимо создать новую тему оформления. В эпоху Seditio ее называли скин (skin).
Как правило, тема для Котонти создается "с нуля". Для этого необходимо использовать коробочную тему Nemesis и произвести над ней некоторые действия, чтобы уникализировать ее и добавить немного необходимого функционала. Если в дальнейшем планируется разработка новых тем, рекомендую сохранить созданный каркас, например, на Github для того, чтобы дальнейшие процедуры клонирования занимали меньше времени и усилий.
Копирование и переименование
Откроем папку /themes. Здесь находятся две темы: Nemesis и Sumisun. Последнюю сразу удаляем -- это модельная тема давно не обновлялась и уже потеряла свою актуальность. Работать мы будем с темой Nemesis.
Прежде всего, придумаем название новой темы и соответствующим образом переименуем папку. Например, nemesis -> kudos. Аналогичным образом поступим с четырьмя "именными" файлами в папке:
- nemesis.php -> kudos.php
- nemesis.en.lang.php -> kudos.en.lang.php
- nemesis.rc.php -> kudos.rc.php
- nemesis.ru.lang.php -> kudos.ru.lang.php
Откроем файл kudos.php и отредактируем "шапку":
/* ==================== [BEGIN_COT_THEME] Name=Kudos Version=1.00b Schemes=default:Default [END_COT_THEME] ==================== */
Аналогичным образом поступим с файлами локализаций:
/** * User English Language File for Kudos theme * * @package Cotonti * @copyright (c) Cotonti Team * @license https://github.com/Cotonti/Cotonti/blob/master/License.txt */
и
/** * User Russian Language File for Kudos theme * * @package Cotonti * @copyright (c) Cotonti Team * @license https://github.com/Cotonti/Cotonti/blob/master/License.txt */
Также исправим header файла-загрузчика:
/** * JavaScript and CSS loader for Kudos theme * * @package Cotonti * @copyright (c) Cotonti Team * @license https://github.com/Cotonti/Cotonti/blob/master/License.txt */
Редактирование основных файлов
Очистим основные файлы для дальнейшей работы с ними. В первую очередь нас интересуют файлы TPL-шаблонов.
Правка файлов-шаблонов
Откроем header.tpl и удалим верстку ниже открывающего тега body. Тег HEADER_META_KEYWORDS нам тоже уже не понадобится. В итоге получим:
<!-- BEGIN: HEADER -->
<!DOCTYPE html>
<html lang="{PHP.cfg.defaultlang}">
<head>
<title>{HEADER_TITLE}</title>
<!-- IF {HEADER_META_DESCRIPTION} -->
<meta name="description" content="{HEADER_META_DESCRIPTION}" />
<!-- ENDIF -->
<!-- IF {HEADER_META_KEYWORDS} -->
<meta name="keywords" content="{HEADER_META_KEYWORDS}" />
<!-- ENDIF -->
<meta http-equiv="content-type" content="{HEADER_META_CONTENTTYPE}; charset=UTF-8" />
<meta name="generator" content="Cotonti https://www.cotonti.com" />
<link rel="canonical" href="{HEADER_CANONICAL_URL}" />
{HEADER_BASEHREF}
{HEADER_HEAD}
<link rel="shortcut icon" href="favicon.ico" />
<link rel="apple-touch-icon" href="apple-touch-icon.png" />
</head>
<body>
<header class="py-3 bg-secondary-subtle">
<div class="container">
<div class="row">
<div class="col">
<p class="text-center mb-0">
This is header <a href="{PHP.cfg.mainurl}">Back Home</a>
</p>
</div>
</div>
</div>
</header>
<!-- END: HEADER -->
Так же поступим с шаблоном footer.tpl:
<!-- BEGIN: FOOTER -->
<footer class="py-3 bg-secondary-subtle">
<div class="container">
<div class="row">
<div class="col">
<p class="text-center mb-0">
This is footer
</p>
</div>
</div>
</div>
</footer>
{FOOTER_RC}
</body>
</html>
<!-- END: FOOTER -->
Шаблон index.tpl очищаем полностью:
<!-- BEGIN: MAIN -->
<main id="home" class="my-4">
<div class="container">
<div class="row">
<div class="col">
<p class="text-center mb-0">
This is index
</p>
</div>
</div>
</div>
</main>
<!-- END: MAIN -->
Пройдемся по остальным шаблонам. Самым трудоемким будет login.tpl. Будем считать, что в качестве CSS-фреймворка используется Bootstrap:
<!-- BEGIN: MAIN -->
<main id="login" class="my-4">
<div class="container">
<div class="row">
<div class="mx-auto col col-lg-7 col-xl-6 col-xxl-5">
{FILE "{PHP.cfg.themes_dir}/{PHP.theme}/warnings.tpl"}
<div class="title mb-3">
<ul class="breadcrumb">
<li class="breadcrumb-item">
<a href="{PHP.cfg.mainurl}" title="{PHP.L.Home}">{PHP.L.Home}</a>
</li>
<li class="breadcrumb-item">{PHP.L.Login}</li>
</ul>
<h1 class="display-6 lh-1">{USERS_AUTH_TITLE}</h1>
</div>
<!-- IF {PHP.usr.id} -->
<div class="alert alert-info">
<p class="mb-0">
{PHP.L.users_loggedinas} <strong>{PHP.usr.name}</strong>. {PHP.L.users_logoutfirst}
</p>
</div>
<div class="btn-group w-100">
<!-- IF {PHP.usr.maingrp} == 5 OR {PHP.usr.maingrp} == 6 -->
<a class="btn btn-danger btn flex-fill" href="{PHP|cot_url('admin')}">{PHP.L.Adminpanel}</a>
<!-- ENDIF -->
<a class="btn btn-success btn flex-fill" href="{PHP|cot_url('users', 'm=profile')}">{PHP.L.Profile}</a>
<a class="btn btn-success btn flex-fill" href="{PHP.sys.xk|cot_url('login','out=1&x=$this', '', 0, 1)}">{PHP.L.Logout}</a>
</div>
<!-- ELSE -->
<form name="login" action="{USERS_AUTH_SEND}" method="post">
<div class="input-group mb-3">
<span class="input-group-text small w-25">{PHP.L.Name}:</span>
{USERS_AUTH_USER}
</div>
<div class="input-group mb-3">
<span class="input-group-text small w-25">{PHP.L.Password}:</span>
{USERS_AUTH_PASSWORD}
<div class="input-group-text">
{USERS_AUTH_REMEMBER}
</div>
</div>
<div class="btn-group w-100">
<button type="submit" name="rlogin" value="0" class="btn btn-success btn flex-fill">{PHP.L.Login}</button>
<!-- IF !{PHP.cfg.users.disablereg} -->
<a href="{PHP|cot_url('users','m=register')}" class="btn btn-primary btn flex-fill">{PHP.L.Registration}</a>
<!-- ENDIF -->
<a href="{PHP|cot_url('users','m=passrecover')}" class="btn btn-primary btn flex-fill">{PHP.L.users_lostpass}</a>
</div>
</form>
<!-- ENDIF -->
<!-- BEGIN: USERS_AUTH_MAINTENANCE -->
<div class="alert alert-warning mb-0" role="alert">
<h4>
{PHP.L.users_maintenance1}
<!-- IF {PHP.cfg.maintenancereason} -->
({PHP.cfg.maintenancereason})
<!-- ENDIF -->
</h4>
<p class="mb-0">
{PHP.L.users_maintenance2}
</p>
</div>
<!-- END: USERS_AUTH_MAINTENANCE -->
</div>
</div>
</div>
</main>
<!-- END: MAIN -->
Следующий в очереди error.tpl:
<!-- BEGIN: MAIN -->
<!DOCTYPE html>
<html lang="{PHP.cfg.defaultlang}">
<head>
<title>{MESSAGE_TITLE}</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta name="generator" content="Cotonti http://www.cotonti.com" />
{MESSAGE_BASEHREF}
{MESSAGE_STYLESHEET}
{MESSAGE_REDIRECT}
</head>
<body>
<main id="error">
<div class="container">
<div class="row">
<div class="col">
<h1>{MESSAGE_TITLE}</h1>
<div>
{MESSAGE_BODY}
</div>
</div>
</div>
</div>
</main>
</body>
</html>
<!-- END: MAIN -->
За ним message.tpl:
<!-- BEGIN: MAIN -->
<!-- IF !{AJAX_MODE} -->
<main id="message" class="my-4">
<div class="container">
<div class="row">
<div class="col">
<div class="title p-0">
<h1 class="lh-1 text-center mb-3">{MESSAGE_TITLE}</h1>
</div>
<!-- ENDIF -->
<div class="alert alert-warning mb-0" role="alert">
<p class="text-center mb-0">
{MESSAGE_BODY}
</p>
<div class="d-flex justify-content-center">
<!-- BEGIN: MESSAGE_CONFIRM -->
<a id="confirmYes" href="{MESSAGE_CONFIRM_YES}" class="confirmButton btn btn-success btn-sm fw-bold mx-2 text-uppercase w-25">{PHP.L.Yes}</a>
<a id="confirmNo" href="{MESSAGE_CONFIRM_NO}" class="confirmButton btn btn-danger btn-sm bold fw-bold text-uppercase mx-2 w-25">{PHP.L.No}</a>
<!-- END: MESSAGE_CONFIRM -->
</div>
</div>
<!-- IF !{AJAX_MODE} -->
</div>
</div>
</div>
</main>
<!-- ENDIF -->
<!-- END: MAIN -->
За ним plugin.tpl:
<!-- BEGIN: MAIN -->
<div class="container">
<div class="row">
<div class="col">
<h1>{PLUGIN_TITLE}</h1>
<div>
{PLUGIN_BODY}
</div>
</div>
</div>
</div>
<!-- END: MAIN -->
Далее poput.tpl:
<!-- BEGIN: MAIN -->
<!DOCTYPE html>
<html lang="{PHP.cfg.defaultlang}">
<head>
{POPUP_METAS}
{POPUP_JAVASCRIPT}
<base href="{PHP.cfg.mainurl}/" />
<script type="text/javascript">
//<![CDATA[
function add(text) {
insertText(document, "{POPUP_C2}", text);
}
//]]>
</script>
<link href="themes/{PHP.theme}/css/{PHP.scheme}.css" type="text/css" rel="stylesheet" />
</head>
<body>
{POPUP_BODY}
</body>
</html>
<!-- END: MAIN -->
И в завершение warnings.tpl:
<!-- BEGIN: ERROR -->
<div class="alert alert-danger" role="alert">
<h4>{PHP.L.Error}</h4>
<p class="m-0">
<!-- BEGIN: ERROR_ROW -->
<span>{ERROR_ROW_MSG}</span>
<!-- END: ERROR_ROW -->
</p>
</div>
<!-- END: ERROR -->
<!-- BEGIN: WARNING -->
<div class="alert alert-warning" role="alert">
<h4>{PHP.L.Warning}</h4>
<p class="m-0">
<!-- BEGIN: WARNING_ROW -->
<span>{WARNING_ROW_MSG}</span>
<!-- END: WARNING_ROW -->
</p>
</div>
<!-- END: WARNING -->
<!-- BEGIN: DONE -->
<div class="alert alert-success" role="alert">
<h4>{PHP.L.Done}</h4>
<p class="m-0">
<!-- BEGIN: DONE_ROW -->
<span>{DONE_ROW_MSG}</span>
<!-- END: DONE_ROW -->
</p>
</div>
<!-- END: DONE -->
Папки img и inc очищаем полностью, за исключением блокирующих просмотр файлов index.html.
Правка файлов стилей
Здесь будет немного радикальных перестановок. Настраивать стили будем под использование LESS -- динамического языка стилей, с помощью которого можно более гибко и просто работать со стилями нашего будущего веб-сайта:
- переименуем папку css в папку less,
- удалим файл reset.css (его функционал уже реализован в библиотеке Bootstrap),
- файл default.css очистим и переименуем в default.less,
- файл extras.css очистим и переименуем в responsive.css,
- файл modalbox.css переименуем в modalbox.less,
- создадим пустую папку css, в которой будут размещаться скомпилированные и минифицированные файлы стилей.
Настроим компилятор LESS и отразим наши исправления в файле-загрузчике:
<?php
/**
* JavaScript and CSS loader for Kudos Theme
*
* @package Kudos
* @copyright (c) Cotonti Team
* @license https://github.com/Cotonti/Cotonti/blob/master/License.txt
*/
defined('COT_CODE') or die('Wrong URL.');
$R['theme-version'] = 1;
Resources::linkFileFooter($cfg['themes_dir'].'/'.$usr['theme'].'/css/modalbox.css');
Resources::linkFileFooter($cfg['themes_dir'].'/'.$usr['theme'].'/css/default.css?v='.$R['theme-version']);
Resources::linkFileFooter($cfg['themes_dir'].'/'.$usr['theme'].'/css/responsive.css?v='.$R['theme-version']);
Resources::linkFileFooter($cfg['themes_dir'].'/'.$usr['theme'].'/js/js.js');
Поясним такой выбор: стили в default.less прописываются м грузятся в первую очередь (mobile first), а в responsive.less мы будем списывать стили, использующие медиа-запросы (media queries). Переменная theme-version в загрузчике используется для кэширования стилей.
Чтобы использовать все возможности LESS и сделать работу со стилями еще более комфортной и продуктивной, создадим в папке less файл с переменными vars.less и подключим его в default.less и responsive.less директивой @import:
@import "vars.less";
Добавляем шаблоны модулей и плагинов
Прежде всего нам понадобятся четыре основных шаблона модуля Page, необходимые для просмотра, добавления и правки страницы, а также для просмотра раздела.
Шаблон page.tpl:
<!-- BEGIN: MAIN -->
<main id="page_{PAGE_ID}" class="my-4">
<div class="container">
<div class="row">
<div class="col">
<div class="title mb-3">
<h1 class="lh-1">{PAGE_SHORTTITLE}</h1>
{PAGE_CRUMBS}
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="textbox">
{PAGE_TEXT}
</div>
{PAGE_COMMENTS_DISPLAY}
</div>
</div>
<!-- IF {PHP.usr.isadmin} -->
{FILE "{PHP.cfg.themes_dir}/{PHP.theme}/inc/admin-page.tpl"}
<!-- ENDIF -->
</div>
</main>
<!-- END: MAIN -->
Шаблон page.add.tpl:
<!-- BEGIN: MAIN -->
<main id="page_add" class="my-4">
<div class="container">
<div class="row">
<div class="col">
{FILE "themes/{PHP.theme}/warnings.tpl"}
<h1 class="lh-1 mb-3">{PAGEADD_PAGETITLE}</h1>
<form action="{PAGEADD_FORM_SEND}" enctype="multipart/form-data" method="post" name="pageform" class="mb-3">
<div class="table-responsive">
<table class="table table-striped m-0">
<tr>
<td class="w-25">{PHP.L.Category}:</td>
<td class="w-75">{PAGEADD_FORM_CAT}</td>
</tr>
<tr>
<td>{PHP.L.Title}:</td>
<td>{PAGEADD_FORM_TITLE}</td>
</tr>
<tr>
<td>{PHP.L.Description}:</td>
<td>{PAGEADD_FORM_DESC}</td>
</tr>
<tr>
<td>{PHP.L.Author}:</td>
<td>{PAGEADD_FORM_AUTHOR}</td>
</tr>
<tr>
<td>{PHP.L.Begin}:</td>
<td>{PAGEADD_FORM_BEGIN}</td>
</tr>
<tr>
<td>{PHP.L.Expire}:</td>
<td>{PAGEADD_FORM_EXPIRE}</td>
</tr>
<tr>
<td>{PHP.L.Alias}:</td>
<td>{PAGEADD_FORM_ALIAS}</td>
</tr>
<tr>
<td>{PHP.L.page_metatitle}:</td>
<td>{PAGEADD_FORM_METATITLE}</td>
</tr>
<tr>
<td>{PHP.L.page_metadesc}:</td>
<td>{PAGEADD_FORM_METADESC}</td>
</tr>
<!-- BEGIN: TAGS -->
<tr>
<td>{PAGEADD_TOP_TAGS}:</td>
<td>{PAGEADD_FORM_TAGS} ({PAGEADD_TOP_TAGS_HINT})</td>
</tr>
<!-- END: TAGS -->
<tr>
<td>{PHP.L.Owner}:</td>
<td>{PAGEADD_FORM_OWNER}</td>
</tr>
<tr>
<td>{PHP.L.Parser}:</td>
<td>{PAGEADD_FORM_PARSER}</td>
</tr>
<tr>
<td colspan="2">
{PAGEADD_FORM_TEXT}
</td>
</tr>
<tr>
<td colspan="2">
<!-- IF {PHP.usr_can_publish} -->
<button type="submit" name="rpagestate" value="0" class="btn btn-success btn-sm">{PHP.L.Publish}</button>
<!-- ENDIF -->
<button type="submit" name="rpagestate" value="2" class="submit btn btn-warning btn-sm">{PHP.L.Saveasdraft}</button>
<button type="submit" name="rpagestate" value="1" class="btn btn-danger btn-sm">{PHP.L.Submitforapproval}</button>
</td>
</tr>
</table>
</div>
</form>
</div>
</div>
</div>
</main>
Шаблон page.edit.tpl:
<!-- BEGIN: MAIN -->
<main id="page_edit" class="my-4">
<div class="container">
<div class="row">
<div class="col">
{FILE "themes/{PHP.theme}/warnings.tpl"}
<h1 class="lh-1 mb-3">{PAGEEDIT_PAGETITLE} #{PAGEEDIT_FORM_ID} ({PAGEEDIT_FORM_LOCALSTATUS})</h1>
<form action="{PAGEEDIT_FORM_SEND}" enctype="multipart/form-data" method="post" name="pageform">
<div class="table-responsive">
<table class="table table-striped m-0">
<tr>
<td class="w-25">{PHP.L.Category}:</td>
<td class="w-75">{PAGEEDIT_FORM_CAT}</td>
</tr>
<tr>
<td>{PHP.L.Title}:</td>
<td>{PAGEEDIT_FORM_TITLE}</td>
</tr>
<tr>
<td>{PHP.L.Description}:</td>
<td>{PAGEEDIT_FORM_DESC}</td>
</tr>
<tr>
<td>{PHP.L.Author}:</td>
<td>{PAGEEDIT_FORM_AUTHOR}</td>
</tr>
<tr>
<td>{PHP.L.Date}:</td>
<td>
{PAGEEDIT_FORM_DATE}
<div class="form-group m0">
<div class="checkbox mb0">
<label class="checkbox">
<span class="title pt-1">
<input type="checkbox" value="1" name="rpagedatenow" class="me-2">{PHP.L.page_date_now}
</span>
</label>
</div>
</div>
</td>
</tr>
<tr>
<td>{PHP.L.Begin}:</td>
<td>{PAGEEDIT_FORM_BEGIN}</td>
</tr>
<tr>
<td>{PHP.L.Expire}:</td>
<td>{PAGEEDIT_FORM_EXPIRE}</td>
</tr>
<tr>
<td>{PHP.L.Alias}:</td>
<td>{PAGEEDIT_FORM_ALIAS}</td>
</tr>
<tr>
<td>{PHP.L.page_metatitle}:</td>
<td>{PAGEEDIT_FORM_METATITLE}</td>
</tr>
<tr>
<td>{PHP.L.page_metadesc}:</td>
<td>{PAGEEDIT_FORM_METADESC}</td>
</tr>
<!-- BEGIN: TAGS -->
<tr>
<td>{PAGEEDIT_TOP_TAGS}:</td>
<td>{PAGEEDIT_FORM_TAGS} ({PAGEEDIT_TOP_TAGS_HINT})</td>
</tr>
<!-- END: TAGS -->
<!-- BEGIN: ADMIN -->
<tr>
<td>{PHP.L.Hits}:</td>
<td>{PAGEEDIT_FORM_PAGECOUNT}</td>
</tr>
<tr>
<td>{PHP.L.Owner}:</td>
<td>{PAGEEDIT_FORM_OWNERID}</td>
</tr>
<!-- END: ADMIN -->
<tr>
<td>{PHP.L.Parser}:</td>
<td>{PAGEEDIT_FORM_PARSER}</td>
</tr>
<tr>
<td colspan="2">
{PAGEEDIT_FORM_TEXT}
</td>
</tr>
<tr>
<td>{PHP.L.page_deletepage}:</td>
<td>{PAGEEDIT_FORM_DELETE}</td>
</tr>
<tr>
<td colspan="2">
<!-- IF {PHP.usr_can_publish} -->
<button type="submit" name="rpagestate" value="0" class="btn btn-success btn-sm">{PHP.L.Publish}</button>
<!-- ENDIF -->
<button type="submit" name="rpagestate" value="2" class="submit btn btn-warning btn-sm">{PHP.L.Saveasdraft}</button>
<button type="submit" name="rpagestate" value="1" class="btn btn-danger btn-sm">{PHP.L.Submitforapproval}</button>
</td>
</tr>
</table>
</div>
</form>
</div>
</div>
</div>
</main>
<!-- END: MAIN -->
Шаблон page.list.tpl:
<!-- BEGIN: MAIN -->
<main id="list_{PHP.cat.id}" class="my-4">
<div class="container">
<div class="row">
<div class="col">
<div class="title mb-3">
<h1 class="lh-1">{LIST_CATTITLE}</h1>
{LIST_CRUMBS}
</div>
</div>
</div>
<div class="row">
<div class="col">
<!-- IF {LIST_ROW_NUM} -->
<!-- BEGIN: LIST_ROW -->
<article class="card py-2 px-3">
<a href="{LIST_ROW_URL}">{LIST_ROW_SHORTTITLE}</a>
<ul class="list-inline mb-0">
<li class="list-inline-item">
{PHP.L.Views}: {LIST_ROW_COUNT}
</li>
<li class="list-inline-item">
{PHP.L.Date}: {LIST_ROW_DATE}
</li>
</ul>
<!-- IF {LIST_ROW_DESC} -->
<p class="mb-0">
{LIST_ROW_DESC}
</p>
<!-- ENDIF -->
</article>
<!-- END: LIST_ROW -->
<!-- IF {LIST_TOP_PAGINATION} -->
<nav id="pagination-container">
<ul class="pagination">
{LIST_TOP_PAGEPREV}{LIST_TOP_PAGINATION}{LIST_TOP_PAGENEXT}
</ul>
</nav>
<!-- ENDIF -->
<!-- ENDIF -->
</div>
</div>
<!-- IF {PHP.usr.isadmin} -->
{FILE "{PHP.cfg.themes_dir}/{PHP.theme}/inc/admin-list.tpl"}
<!-- ENDIF -->
</div>
</main>
<!-- END: MAIN -->
Чтобы задействовать функционал "хлебных крошек", загрузим и установим плагин Crumbs. В админке (раздел Конфигурация / Темы) включим опцию "Ссылка на главную страницу в навигационной цепочке".
В папке inc разместим файлы подгрузки блоков администрирования страниц и разделов:
admin-page.tpl:
<div id="adminblock" class="row">
<div class="col">
<ul class="list-inline mt-3 mb-0">
<li class="list-inline-item">
<a href="{PHP|cot_url('admin')}">{PHP.L.Adminpanel}</a>
</li>
<li class="list-inline-item">{PAGE_ADMIN_UNVALIDATE}</li>
<li class="list-inline-item">
<a href="{PAGE_CAT|cot_url('page','m=add&c=$this')}">{PHP.L.page_addtitle}</a>
</li>
<li class="list-inline-item">
{PAGE_ADMIN_EDIT}
</li>
<li class="list-inline-item">
{PAGE_ADMIN_CLONE}
</li>
<li class="list-inline-item">
{PAGE_ADMIN_DELETE}
</li>
<!-- IF {PHP|cot_auth('plug', 'attach2', 'W')} -->
<li class="list-inline-item">
{PAGE_ID|att_widget('page', $this, 'attach2.link')}
</li>
<!-- ENDIF -->
<li class="list-inline-item">
{PHP.out.loginout}
</li>
</ul>
</div>
</div>
admin-list.tpl:
<div id="adminblock" class="row">
<div class="col">
<ul class="list-inline mt-3 mb-0">
<li class="list-inline-item">
<a href="{PHP|cot_url('admin')}">{PHP.L.Adminpanel}</a>
</li>
<li class="list-inline-item">
<a href="admin.php?m=structure&n=page&id={PHP.cat.id}&x={PHP.sys.xk}">Настройки раздела</a>
</li>
<li class="list-inline-item">
{LIST_SUBMITNEWPAGE}
</li>
<!-- IF {PHP|cot_auth('plug', 'attach2', 'W')} -->
<li class="list-inline-item">
{PHP.cat.id|att_widget('list',$this,'attach2.link')}
</li>
<!-- ENDIF -->
<li class="list-inline-item">{PHP.out.loginout}</li>
</ul>
</div>
</div>
Для того, чтобы единообразить внешний вид кнопок, используем файл js.js, попутно проверив его загрузку:
$().ready(function() {
if ($('div#adminblock').length) {
$('div#adminblock ul > li > a').each(function() {
$(this).addClass('btn btn-primary btn-sm');
})
}
});
Теперь займемся шаблонами модуля users.
users.details.tpl:
<!-- BEGIN: MAIN -->
<main id="users_details" class="my-4">
<div class="container">
<div class="row">
<div class="col">
{FILE "{PHP.cfg.themes_dir}/{PHP.cfg.defaulttheme}/warnings.tpl"}
<div class="title mb-3">
<h1 class="lh-1">
{PHP.L.User} {USERS_DETAILS_NICKNAME}
<!-- BEGIN: USERS_DETAILS_ADMIN -->
[ {USERS_DETAILS_ADMIN_EDIT} ]
<!-- END: USERS_DETAILS_ADMIN -->
</h1>
<ul class="breadcrumb">
<li class="breadcrumb-item">
<a href="{PHP.cfg.mainurl}" title="{PHP.L.Home}">{PHP.L.Home}</a>
</li>
<li class="breadcrumb-item">
<a href="{PHP|cot_url('users')}">{PHP.L.Users}</a>
</li>
<li class="breadcrumb-item">
{USERS_DETAILS_NICKNAME}
</li>
</ul>
</div>
<div class="table-responsive">
<table class="table table-striped table-hover mb-0">
<!-- IF {PHP.cot_modules.pm} -->
<tr>
<td>{PHP.L.users_sendpm}:</td>
<td>{USERS_DETAILS_PM}</td>
</tr>
<!-- ENDIF -->
<tr>
<td class="w-25">{PHP.L.Maingroup}:</td>
<td class="w-75">{USERS_DETAILS_MAINGRP}</td>
</tr>
<tr>
<td>{PHP.L.Groupsmembership}:</td>
<td>{USERS_DETAILS_GROUPS}</td>
</tr>
<tr>
<td>{PHP.L.Country}:</td>
<td>{USERS_DETAILS_COUNTRYFLAG} {USERS_DETAILS_COUNTRY}</td>
</tr>
<tr>
<td>{PHP.L.Timezone}:</td>
<td>{USERS_DETAILS_TIMEZONE}</td>
</tr>
<tr>
<td>{PHP.L.Birthdate}:</td>
<td>{USERS_DETAILS_BIRTHDATE}</td>
</tr>
<tr>
<td>{PHP.L.Age}:</td>
<td>{USERS_DETAILS_AGE}</td>
</tr>
<tr>
<td>{PHP.L.Gender}:</td>
<td>{USERS_DETAILS_GENDER}</td>
</tr>
<tr>
<td>{PHP.L.Signature}:</td>
<td>{USERS_DETAILS_TEXT}</td>
</tr>
<tr>
<td>{PHP.L.Registered}:</td>
<td>{USERS_DETAILS_REGDATE}</td>
</tr>
<!-- IF {PHP.usr.isadmin} -->
<tr>
<td>
Last seen:
</td>
<td>
{USERS_DETAILS_LASTLOG_STAMP|cot_date('H:i j F Y', $this)}
</td>
</tr>
<!-- ENDIF -->
<!-- IF {USERS_DETAILS_AVATAR} -->
<tr>
<td>{PHP.L.Avatar}:</td>
<td>{USERS_DETAILS_AVATAR}</td>
</tr>
<!-- ENDIF -->
<!-- IF {USERS_DETAILS_PHOTO} -->
<tr>
<td>{PHP.L.Photo}:</td>
<td>{USERS_DETAILS_PHOTO}</td>
</tr>
<!-- ENDIF -->
</table>
</div>
</div>
</div>
</div>
</main>
<!-- END: MAIN -->
users.edit.tpl:
<!-- BEGIN: MAIN -->
<main id="users_edit" class="my-4">
<div class="container">
<div class="row">
<div class="col">
{FILE "{PHP.cfg.themes_dir}/{PHP.cfg.defaulttheme}/warnings.tpl"}
<div class="title mb-3">
<h1 class="lh-1">{PHP.L.Edit} {PHP.urr.user_name}</h1>
<ul class="breadcrumb">
<li class="breadcrumb-item">
<a href="{PHP.cfg.mainurl}" title="{PHP.L.Home}">{PHP.L.Home}</a>
</li>
<li class="breadcrumb-item">
<a href="{PHP|cot_url('users')}">{PHP.L.Users}</a>
</li>
<li class="breadcrumb-item">
{PHP.urr.user_name}
</li>
</ul>
</div>
<form action="{USERS_EDIT_SEND}" method="post" name="useredit" enctype="multipart/form-data">
<input type="hidden" name="id" value="{USERS_EDIT_ID}" />
<div class="table-responsive">
<table class="table table-striped table-hover mb-0">
<tr>
<td class="w-25">{PHP.L.users_id}:</td>
<td class="w-75">#{USERS_EDIT_ID}</td>
</tr>
<tr>
<td>{PHP.L.Username}:</td>
<td>{USERS_EDIT_NAME}</td>
</tr>
<tr>
<td>{PHP.L.Groupsmembership}:</td>
<td>
{PHP.L.Maingroup}:
{USERS_EDIT_GROUPS}
</td>
</tr>
<tr>
<td>{PHP.L.Country}:</td>
<td>{USERS_EDIT_COUNTRY}</td>
</tr>
<tr>
<td>{PHP.L.Timezone}:</td>
<td>{USERS_EDIT_TIMEZONE}</td>
</tr>
<tr>
<td>{PHP.L.Theme}:</td>
<td>{USERS_EDIT_THEME}</td>
</tr>
<tr>
<td>{PHP.L.Language}:</td>
<td>{USERS_EDIT_LANG}</td>
</tr>
<!-- IF {USERS_EDIT_AVATAR} -->
<tr>
<td>{PHP.L.Avatar}:</td>
<td>{USERS_EDIT_AVATAR}</td>
</tr>
<!-- ENDIF -->
<!-- IF {USERS_EDIT_SIGNATURE} -->
<tr>
<td>{PHP.L.Signature}:</td>
<td>{USERS_EDIT_SIGNATURE}</td>
</tr>
<!-- ENDIF -->
<!-- IF {USERS_EDIT_PHOTO} -->
<tr>
<td>{PHP.L.Photo}:</td>
<td>{USERS_EDIT_PHOTO}</td>
</tr>
<!-- ENDIF -->
<tr>
<td>{PHP.L.users_newpass}:</td>
<td>
{USERS_EDIT_NEWPASS}
<p class="small">{PHP.L.users_newpasshint1}</p>
</td>
</tr>
<tr>
<td>{PHP.L.Email}:</td>
<td>{USERS_EDIT_EMAIL}</td>
</tr>
<tr>
<td>{PHP.L.users_hideemail}:</td>
<td>{USERS_EDIT_HIDEEMAIL}</td>
</tr>
<!-- IF {PHP.cot_modules.pm} -->
<tr>
<td>{PHP.L.users_pmnotify}:</td>
<td>{USERS_EDIT_PMNOTIFY}<br />{PHP.themelang.usersedit.PMnotifyhint}</td>
</tr>
<!-- ENDIF -->
<tr>
<td>{PHP.L.Birthdate}:</td>
<td>{USERS_EDIT_BIRTHDATE}</td>
</tr>
<tr>
<td>{PHP.L.Gender}:</td>
<td>{USERS_EDIT_GENDER}</td>
</tr>
<tr>
<td>{PHP.L.Signature}:</td>
<td>{USERS_EDIT_TEXT}</td>
</tr>
<tr>
<td>{PHP.L.Registered}:</td>
<td>{USERS_EDIT_REGDATE}</td>
</tr>
<tr>
<td>{PHP.L.Lastlogged}:</td>
<td>{USERS_EDIT_LASTLOG}</td>
</tr>
<tr>
<td>{PHP.L.users_lastip}:</td>
<td>{USERS_EDIT_LASTIP}</td>
</tr>
<tr>
<td>{PHP.L.users_logcounter}:</td>
<td>{USERS_EDIT_LOGCOUNT}</td>
</tr>
<tr>
<td>{PHP.L.users_deleteuser}:</td>
<td>{USERS_EDIT_DELETE}</td>
</tr>
<tr>
<td colspan="2">
<button type="submit" class="btn btn-primary">{PHP.L.Update}</button>
</td>
</tr>
</table>
</div>
</form>
</div>
</div>
</div>
</main>
<!-- END: MAIN -->
users.passrecover.tpl:
<!-- BEGIN: MAIN -->
<main id="users_passrecover" class="my-4">
<div class="container">
<div class="row">
<div class="mx-auto col col-lg-7 col-xl-6 col-xxl-5">
{FILE "{PHP.cfg.themes_dir}/{PHP.theme}/warnings.tpl"}
<div class="title mb-3">
<h1 class="lh-1">{PHP.L.pasrec_title}</h1>
<ul class="breadcrumb">
<li class="breadcrumb-item">
<a href="{PHP.cfg.mainurl}">{PHP.cfg.maintitle}</a>
</li>
<li class="breadcrumb-item">{PHP.L.pasrec_title}</li>
</ul>
</div>
<!-- IF {PHP.msg} == 'request' -->
<div class="alert alert-info mb-0">
<p class="mb-0">
{PHP.L.pasrec_mailsent}
</p>
</div>
<!-- ENDIF -->
<!-- IF {PHP.msg} == 'auth' -->
<div class="alert alert-info mb-0">
<p class="mb-0">
{PHP.L.pasrec_mailsent2}
</p>
</div>
<!-- ENDIF -->
<!-- IF !{PHP.msg} -->
<form role="form" name="reqauth" action="{PASSRECOVER_URL_FORM}" method="post" class="">
<ol>
<li>{PHP.L.pasrec_explain1}</li>
<li>{PHP.L.pasrec_explain2}</li>
<li>{PHP.L.pasrec_explain3}</li>
</ol>
<div class="input-group mb-3">
<input type="text" class="form-control" name="email" />
<button type="submit" class="btn btn-primary">{PHP.L.pasrec_request}</button>
</div>
<div class="alert alert-info mb-0">
{PHP.L.pasrec_explain4}
</div>
</form>
<!-- ENDIF -->
</div>
</div>
</div>
</main>
<!-- END: MAIN -->
users.profile.tpl:
<!-- BEGIN: MAIN -->
<main id="users_profile" class="mb-4">
<div class="container">
<div class="row">
<div class="col">
{FILE "{PHP.cfg.themes_dir}/{PHP.theme}/warnings.tpl"}
<div class="title mb-3">
<h1 class="lh-1">{PHP.L.Profile}</h1>
<ul class="breadcrumb">
<li class="breadcrumb-item">
<a href="{PHP.cfg.mainurl}" title="{PHP.L.Home}">{PHP.L.Home}</a>
</li>
<li class="breadcrumb-item">
{USERS_PROFILE_TITLE}
</li>
</ul>
</div>
<form action="{USERS_PROFILE_FORM_SEND}" method="post" enctype="multipart/form-data" name="profile">
<input type="hidden" name="userid" value="{USERS_PROFILE_ID}" />
<div class="table-responsive">
<table class="table table-striped table-hover mb-0">
<tr>
<td class="w-25">{PHP.L.Username}:</td>
<td class="w-75">{USERS_PROFILE_NAME}</td>
</tr>
<tr>
<td>{PHP.L.Groupsmembership}:</td>
<td>{USERS_PROFILE_GROUPS}</td>
</tr>
<tr>
<td>{PHP.L.Registered}:</td>
<td>{USERS_PROFILE_REGDATE}</td>
</tr>
<!-- BEGIN: USERS_PROFILE_EMAILCHANGE -->
<tr>
<td>{PHP.L.Email}:</td>
<td>
{PHP.L.Email}:<br />{USERS_PROFILE_EMAIL}
<!-- BEGIN: USERS_PROFILE_EMAILPROTECTION -->
<script type="text/javascript">
$(document).ready(function(){
$("#emailnotes").hide();
$("#emailtd").click(function(){$("#emailnotes").slideDown();});
});
</script>
<div>
{PHP.themelang.usersprofile.Emailpassword}:<br />{USERS_PROFILE_EMAILPASS}
</div>
<div>{PHP.themelang.usersprofile.Emailnotes}</div>
<!-- END: USERS_PROFILE_EMAILPROTECTION -->
</td>
</tr>
<!-- END: USERS_PROFILE_EMAILCHANGE -->
<tr>
<td>{PHP.L.users_hideemail}:</td>
<td>{USERS_PROFILE_HIDEEMAIL}</td>
</tr>
<!-- IF {PHP.cot_modules.pm} -->
<tr>
<td>{PHP.L.users_pmnotify}:</td>
<td>
{USERS_PROFILE_PMNOTIFY}
<p class="small mb-0">{PHP.L.users_pmnotifyhint}</p>
</td>
</tr>
<!-- ENDIF -->
<tr>
<td>{PHP.L.Theme}:</td>
<td>{USERS_PROFILE_THEME}</td>
</tr>
<tr>
<td>{PHP.L.Language}:</td>
<td>{USERS_PROFILE_LANG}</td>
</tr>
<tr>
<td>{PHP.L.Country}:</td>
<td>{USERS_PROFILE_COUNTRY}</td>
</tr>
<tr>
<td>{PHP.L.Timezone}:</td>
<td>{USERS_PROFILE_TIMEZONE}</td>
</tr>
<tr>
<td>{PHP.L.Birthdate}:</td>
<td>{USERS_PROFILE_BIRTHDATE}
</td>
</tr>
<tr>
<td>{PHP.L.Gender}:</td>
<td>{USERS_PROFILE_GENDER}</td>
</tr>
<!-- IF {USERS_PROFILE_AVATAR} -->
<tr>
<td>{PHP.L.Avatar}:</td>
<td>{USERS_PROFILE_AVATAR}</td>
</tr>
<!-- ENDIF -->
<!-- IF {USERS_PROFILE_PHOTO} -->
<tr>
<td>{PHP.L.Photo}:</td>
<td>{USERS_PROFILE_PHOTO}</td>
</tr>
<!-- ENDIF -->
<tr>
<td>{PHP.L.Signature}:</td>
<td>{USERS_PROFILE_TEXT}</td>
</tr>
<tr>
<td>
{PHP.L.users_newpass}:
<p class="small">{PHP.L.users_newpasshint1}</p>
</td>
<td>
{USERS_PROFILE_OLDPASS}
<p class="small mb-2">{PHP.L.users_oldpasshint}</p>
{USERS_PROFILE_NEWPASS1} {USERS_PROFILE_NEWPASS2}
<p class="small m-0">{PHP.L.users_newpasshint2}</p>
</td>
</tr>
<tr>
<td colspan="2">
<button type="submit" class="btn btn-primary">{PHP.L.Update}</button>
</td>
</tr>
</table>
</div>
</form>
</div>
</div>
</div>
</main>
<!-- END: MAIN -->
users.register.tpl:
<!-- BEGIN: MAIN -->
<main id="users_register" class="my-4">
<div class="container">
<div class="row">
<div class="mx-auto col col-lg-7 col-xl-6 col-xxl-5">
{FILE "{PHP.cfg.themes_dir}/{PHP.theme}/warnings.tpl"}
<div class="title mb-3">
<h1 class="lh-1">{USERS_REGISTER_TITLE}</h1>
<ul class="breadcrumb">
<li class="breadcrumb-item">
<a href="{PHP.cfg.mainurl}">{PHP.cfg.maintitle}</a>
</li>
<li class="breadcrumb-item">
{PHP.L.Register}
</li>
</ul>
</div>
<form name="login" action="{USERS_REGISTER_SEND}" method="post" enctype="multipart/form-data">
<div class="mb-3">
<label class="control-label" for="">{PHP.L.Username}</label>
{USERS_REGISTER_USER}
</div>
<div class="mb-3">
<label class="control-label" for="">{PHP.L.users_validemail} {PHP.L.users_validemailhint}</label>
{USERS_REGISTER_EMAIL}
</div>
<div class="mb-3">
<label class="control-label" for="">{PHP.L.Password}</label>
{USERS_REGISTER_PASSWORD}
</div>
<div class="mb-3">
<label class="control-label" for="">{PHP.L.users_confirmpass}</label>
{USERS_REGISTER_PASSWORDREPEAT}
</div>
<div class="mb-3">
<label class="control-label" for="">{USERS_REGISTER_VERIFYIMG}</label>
{USERS_REGISTER_VERIFYINPUT}
</div>
<div class="btn-group w-100">
<button type="submit" class="btn btn-success btn flex-fill">{PHP.L.Registration}</button>
<a href="{PHP|cot_url('login')}" class="btn btn-primary btn flex-fill">{PHP.L.Login}</a>
<a href="{PHP|cot_url('users','m=passrecover')}" class="btn btn-primary btn flex-fill">{PHP.L.users_lostpass}</a>
</div>
</form>
</div>
</div>
</div>
</main>
<!-- END: MAIN -->
users.tpl:
<!-- BEGIN: MAIN -->
<main id="users" class="my-4">
<div class="container">
<div class="row">
<div class="col">
{FILE "{PHP.cfg.themes_dir}/{PHP.cfg.defaulttheme}/warnings.tpl"}
<div class="title mb-3">
<h1 class="lh-1">{PHP.L.Users}</h1>
<ul class="breadcrumb">
<li class="breadcrumb-item">
<a href="{PHP.cfg.mainurl}" title="{PHP.L.Home}">{PHP.L.Home}</a>
</li>
<li class="breadcrumb-item">
{PHP.L.Users}
</li>
</ul>
</div>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr class="text-center">
<th class="w-5">{USERS_TOP_PM}</th>
<th class="w-25">{USERS_TOP_NAME}</th>
<th class="w-25">{USERS_TOP_GRPTITLE}</th>
<th class="w-20">{USERS_TOP_GRPLEVEL}</th>
<th class="w-25">{USERS_TOP_REGDATE}</th>
</tr>
</thead>
<tbody>
<!-- BEGIN: USERS_ROW -->
<tr class="text-center">
<td>{USERS_ROW_PM}</td>
<td>{USERS_ROW_NAME} {USERS_ROW_TAG}</td>
<td>{USERS_ROW_MAINGRP}</td>
<td>{USERS_ROW_MAINGRPSTARS}</td>
<td>{USERS_ROW_REGDATE}</td>
</tr>
<!-- END: USERS_ROW -->
</tbody>
</table>
<p class="text-center mb-0">
{PHP.L.users_usersperpage}: {USERS_TOP_MAXPERPAGE} | {PHP.L.users_usersinthissection}: {USERS_TOP_TOTALUSERS}
</p>
<!-- IF {USERS_TOP_PAGNAV} -->
<nav id="pagination-container">
<ul class="pagination">
{USERS_TOP_PAGEPREV}{USERS_TOP_PAGNAV}{USERS_TOP_PAGENEXT}
</ul>
</nav>
<!-- ENDIF -->
</div>
</div>
</div>
</div>
</main>
<!-- END: MAIN -->
Для того, чтобы вся эта история заработала, подключим Bootstrap. Например, при помощи нашего плагина Bootstrap.
Практически все стало красиво. Исключение -- некоторые локации модуля users:
- users.details, users.edit, users.profile -- список групп пользователя выводится в контейнере ul;
- login -- теги USERS_AUTH_USER, USERS_AUTH_PASSWORD и USERS_AUTH_REMEMBER не имеют классов Bootstrap;
- register -- теги USERS_REGISTER_USER, USERS_REGISTER_EMAIL, USERS_REGISTER_PASSWORD, USERS_REGISTER_PASSWORDREPEAT, USERS_REGISTER_VERIFYINPUT не имеют классов Bootstrap.
Можно исправить эти проблемы двумя способами:
- используя jQuery;
- переназначив соответствующие ресурсы миниплагином.
Поскольку jQuery мы уже использовали выше, выберем второй способ и создадим плагин Kudos с тремя частями и кастомным файлом ресурсов:
kudos.resources.php:
<?php /* * Redefine some Users module resources */ $R['users_code_grplist_begin'] = '<ul class="list-unstyled mb-0">'; $R['form_guest_remember'] = '<input type="checkbox" name="rremember" class="form-check-input" title="'.$L['users_rememberme'].'" />'; $R['form_guest_remember_forced'] = '<input type="checkbox" name="rremember" class="form-check-input" title="'.$L['users_rememberme'].'" checked="checked" disabled="disabled" />';
kudos.login.php:
<?php
/* ====================
[BEGIN_COT_EXT]
Hooks=users.auth.tags
[END_COT_EXT]
==================== */
/**
* Customization for Kudos theme
*
* @package Kudos
* @copyright (c) Cotonti Team
* @license https://github.com/Cotonti/Cotonti/blob/master/License.txt
*/
defined('COT_CODE') or die('Wrong URL');
require_once cot_incfile('kudos', 'plug', 'resources');
$t->assign(array(
'USERS_AUTH_USER' => cot_inputbox('text', 'rusername', $rusername, array('size' => '12', 'maxlength' => '100', 'class' => 'form-control')),
'USERS_AUTH_PASSWORD' => cot_inputbox('password', 'rpassword', '', array('size' => '12', 'maxlength' => '32', 'class' => 'form-control')),
'USERS_AUTH_REMEMBER' => Cot::$cfg['forcerememberme'] ? Cot::$R['form_guest_remember_forced'] : Cot::$R['form_guest_remember']
));
kudos.register.php:
<?php
/* ====================
[BEGIN_COT_EXT]
Hooks=users.register.tags
[END_COT_EXT]
==================== */
/**
* Customization for Kudos theme
*
* @package Kudos
* @copyright (c) Cotonti Team
* @license https://github.com/Cotonti/Cotonti/blob/master/License.txt
*/
defined('COT_CODE') or die('Wrong URL');
$t->assign(array(
'USERS_REGISTER_USER' => cot_inputbox('text', 'rusername', $ruser['user_name'], array('size' => 24, 'maxlength' => 100, 'class' => 'form-control')),
'USERS_REGISTER_EMAIL' => cot_inputbox('text', 'ruseremail', $ruser['user_email'], array('size' => 24, 'maxlength' => 64, 'class' => 'form-control')),
'USERS_REGISTER_PASSWORD' => cot_inputbox('password', 'rpassword1', '', array('size' => 12, 'maxlength' => 32, 'class' => 'form-control')),
'USERS_REGISTER_PASSWORDREPEAT' => cot_inputbox('password', 'rpassword2', '', array('size' => 12, 'maxlength' => 32, 'class' => 'form-control')),
'USERS_REGISTER_VERIFYINPUT' => cot_inputbox('text', 'rverify', '', array('size' => 10, 'maxlength' => 20, 'class' => 'form-control')),
));
kudos.users.php:
<?php
/* ====================
[BEGIN_COT_EXT]
Hooks=users.edit.first,users.details.first,users.profile.first
[END_COT_EXT]
==================== */
/**
* Customization for Kudos theme
*
* @package Kudos
* @copyright (c) Cotonti Team
* @license https://github.com/Cotonti/Cotonti/blob/master/License.txt
*/
defined('COT_CODE') or die('Wrong URL');
require_once cot_incfile('kudos', 'plug', 'resources');
Ничего сверхъестественного плагин не делает, всего лишь переназначает необходимые нам ресурсные строки, добавляя им необходимые CSS-классы.
Заключение
Мы создали стартовую (модельную) тему для движка Cotonti. Фактически, это тема-пустышка, адаптированная под Bootstrap, в которой проработаны все служебные шаблоны. Добавьте к ней тему админки Yukon, и у Вас будет готовая сборка для создания нового проекта "с нуля".
Для кастомизации данной стартовой темы нам понадобилось написать небольшой плагин Kudos и установить готовые плагины для загрузки Bootstrap и генерации "хлебных крошек". Дальнейшая разработка сайта, возможно, потребует большего их количества, однако этого не стоит опасаться: Cotonti именно так и создана. Тема универсальной "заготовки" будет развиваться и дальше.
Спасибо за статью. Берём на вооружение :)
Новый комментарий
Ошибка
Выполнено