Программирование


В этом уроке мы научимся создавать страницы в Wordpress и поймем, как с ними можно работать.


Мы продолжаем линейку материалов по WordPress. В этом уроке мы научимся создавать страницы, создавать отдельные шаблоны для этих страниц и посмотрим исходные данные в массиве.




Применение MVC на стороне клиента становится популярным, т.к. браузерные приложения становятся все более сложными. Фреймворки позволяют организовать JavaScript код используя MVC. Backbone.js является одним из самых популярных фреймворков подобного рода и наиболее часто выбираемым среди остальных.


Сегодня мы сделаем форму выбора услуг по списку на Backbone.js. Итоговая стоимость будет рассчитываться в реальном времени исходя из стоимости выбранных услуг.


Что такое Backbone.js

Это фреймворк, который вносит структурность в веб-приложения давая возможность использовать модели, коллекции и виды. Данные на сервер поступают в виде REST JSON, и могут автоматически выбирать и сохранять данные. В этом уроке мы не будем отправлять данные на сервер — все будет храниться на стороне клиента. Кстати, Backbone.js не зависит и не заменят jQuery, так что вместе они работают отлично! Не стоит забывать о том, что backbone.js — это не волшебная палочка, которая сделает все за вас. Конечный результат и структура зависит только от вас. Организация кода может быть ужасной, если вы не знакомы с MVC. Так же, не стоит использовать фреймворк для небольших проектов, где пара строк jQuery могут решить вашу задачу. В этом уроке мы познакомимся с фундаментальными возможностями backbone.js, которые есть у этого фреймворка под капотом.


Что мы будем создавать

My Services

total: $0


Шаг 1. HTML

Мы начинаем с обычного HTML5 документа. В данном примере мы не стали реализовывать работу html5 в браузере IE.

index.html

<!DOCTYPE html>
<html>

    <head>
        <meta charset="utf-8" />
        <title>Your first Backbone.js App | Tutorialzine </title>

        <!-- Google web fonts -->
        <link href="http://fonts.googleapis.com/css?family=PT+Sans:400,700" rel='stylesheet' />

        <!-- The main CSS file -->
        <link href="assets/css/style.css" rel="stylesheet" />

    </head>

    <body>

        <form id="main" method="post" action="submit.php">
            <h1>My Services</h1>

            <ul id="services">
                <!-- The services will be inserted here -->
            </ul>

            <p id="total">total: <span>$0</span></p>

            <input type="submit" id="order" value="Order" />

        </form>

        <!-- JavaScript Includes -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js"></script>

        <script src="assets/js/script.js"></script>

    </body>
</html>

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

Перед тем, как закрыть </body> я добавил jQuery, Backbone.js, и библиотеку Underscore (backbone зависит от ее функций). Последним идет файл скрипта script.js. Готовый скрипт ниже.


Шаг 2. JavaScript

  1. Сначала мы создадим модель для наших услуг. Она будет содержать свойства для имени, цены и проверки статуса (checked) — выбрана наша услуга или нет. Объект этого класса будет создан для каждой услуги, которую мы предлагаем;
  2. Затем мы создадим коллекцию Backbone. С помощью коллекций становится достаточно просто отслеживать те или иные события, производимые над объектами класса. В больших приложениях вам будет необходимо отслеживать, когда элементы добавляются или удаляются из коллекций и обновляются виды (views). В нашем же случае элементы коллекции преопределены, мы ничего добавлять не будем. Нам необходимо только отслеживать изменения и менять HTML в этой зависимости. Тут мы отслеживаем события при клике и изменении свойства модели.
  3. После этого мы определим виды (или шаблоны) для наших услуг. Каждый вид будет привязан к нашей модели и отправлять ее свойства в HTML.
  4. Наконец, мы создадим главный шаблон, который будет содержать в себе все наши услуги из коллекции и создавать виды (шаблоны) для них. Тут так же отслеживается событие на изменения в коллекции и обновляется итоговая стоимость.

Файл: script.js

$(function(){

    var Service = Backbone.Model.extend({

        defaults:{
            title: 'My service',
            price: 100,
            checked: false
        },

        toggle: function(){
            this.set('checked', !this.get('checked'));
        }
    });

    var ServiceList = Backbone.Collection.extend({

        model: Service,

        getChecked: function(){
            return this.where({checked:true});
        }
    });

    var services = new ServiceList([
        new Service({ title: 'web development', price: 200}),
        new Service({ title: 'web design', price: 250}),
        new Service({ title: 'photography', price: 100}),
        new Service({ title: 'coffee drinking', price: 10})
    ]);

    var ServiceView = Backbone.View.extend({
        tagName: 'li',

        events:{
            'click': 'toggleService'
        },

        initialize: function(){

            this.listenTo(this.model, 'change', this.render);
        },

        render: function(){

            this.$el.html('<input type="checkbox" name="' + this.model.get('title') + '" value="1" /> ' + this.model.get('title') + '<span>$' + this.model.get('price') + '</span>');
            this.$('input').prop('checked', this.model.get('checked'));

            return this;
        },

        toggleService: function(){
            this.model.toggle();
        }
    });

    var App = Backbone.View.extend({

        el: $('#main'),

        initialize: function(){

            this.total = $('#total span');
            this.list = $('#services');

            this.listenTo(services, 'change', this.render);

            services.each(function(service){

                var view = new ServiceView({ model: service });
                this.list.append(view.render().el);

            }, this);
        },

        render: function(){

            var total = 0;

            _.each(services.getChecked(), function(elem){
                total += elem.get('price');
            });

            this.total.text('$'+total);

            return this;
        }
    });

    new App();

});


Готово!

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




  /    /  Комментариев нет


В этом уроке мы разработаем ajax-форму для загрузки файлов с поддержкой Drag-and-drop, которая позволит посетителям загружать файлы через браузер. Мы будем использовать мощный плагин для мультизагрузки файлов jQuery File Upload совместно с jQuery Knob для отображения прогресса загрузки средствами CSS3/JS.


1. HTML и CSS

Как обычно, всё начинается с создания стандартного HTML5-документа и таблицы стилей:

<!DOCTYPE html>
<html>

	<head>
		<meta charset="utf-8"/>
		<title>Mini Ajax File Upload Form</title>

		<!-- Google web fonts -->
		<link href="http://fonts.googleapis.com/css?family=PT+Sans+Narrow:400,700" rel='stylesheet' />

		<!-- CSS -->
		<link href="css/style.css" rel="stylesheet" />
	</head>

	<body>

		<form id="upload" method="post" action="upload.php" enctype="multipart/form-data">
			<div id="drop">
				Drop Here

				<a>Обзор</a>
				<input type="file" name="upl" multiple />
			</div>

			<ul>
				<!-- Загруженные файлы будут отображаться здесь -->
			</ul>

		</form>

		<!-- JavaScript -->
		<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
		<script src="js/jquery.knob.js"></script>

		<!-- jQuery File Upload Dependencies -->
		<script src="js/jquery.ui.widget.js"></script>
		<script src="js/jquery.iframe-transport.js"></script>
		<script src="js/jquery.fileupload.js"></script>

		<!-- Основной файл JS -->
		<script src="js/script.js"></script>

	</body>
</html>

В секции документа я подключил два шрифта Google Webfonts, а перед закрывающим тегом </body> вы можете увидеть несколько JavaScript-библиотек: jQuery, jQuery Knob plugin, jQuery File Upload plugin и зависимые от него библиотеки.

/*-------------------------
    Simple reset
--------------------------*/

*{
    margin:0;
    padding:0;
}

/*-------------------------
    General Styles
--------------------------*/

html{
    background-color:#ebebec;

    background-image:-webkit-radial-gradient(center, #ebebec, #b4b4b4);
    background-image:-moz-radial-gradient(center, #ebebec, #b4b4b4);
    background-image:radial-gradient(center, #ebebec, #b4b4b4);

    min-height:900px;
}

body{
    font:15px/1.3 Arial, sans-serif;
    color: #4f4f4f;
}

a, a:visited {
    outline:none;
    color:#389dc1;
}

a:hover{
    text-decoration:none;
}

section, footer, header, aside{
    display: block;
}

/*----------------------------
    The file upload form
-----------------------------*/

#upload{
    font-family:'PT Sans Narrow', sans-serif;
    background-color:#373a3d;

    background-image:-webkit-linear-gradient(top, #373a3d, #313437);
    background-image:-moz-linear-gradient(top, #373a3d, #313437);
    background-image:linear-gradient(top, #373a3d, #313437);

    width:250px;
    padding:30px;
    border-radius:3px;

    margin:200px auto 100px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}

#drop{
    background-color: #2E3134;
    padding: 40px 50px;
    margin-bottom: 30px;
    border: 20px solid rgba(0, 0, 0, 0);
    border-radius: 3px;
    border-image: url('../img/border-image.png') 25 repeat;
    text-align: center;
    text-transform: uppercase;

    font-size:16px;
    font-weight:bold;
    color:#7f858a;
}

#drop a{
    background-color:#007a96;
    padding:12px 26px;
    color:#fff;
    font-size:14px;
    border-radius:2px;
    cursor:pointer;
    display:inline-block;
    margin-top:12px;
    line-height:1;
}

#drop a:hover{
    background-color:#0986a3;
}

#drop input{
    display:none;
}

#upload ul{
    list-style:none;
    margin:0 -30px;
    border-top:1px solid #2b2e31;
    border-bottom:1px solid #3d4043;
}

#upload ul li{

    background-color:#333639;

    background-image:-webkit-linear-gradient(top, #333639, #303335);
    background-image:-moz-linear-gradient(top, #333639, #303335);
    background-image:linear-gradient(top, #333639, #303335);

    border-top:1px solid #3d4043;
    border-bottom:1px solid #2b2e31;
    padding:15px;
    height: 52px;

    position: relative;
}

#upload ul li input{
    display: none;
}

#upload ul li p{
    width: 144px;
    overflow: hidden;
    white-space: nowrap;
    color: #EEE;
    font-size: 16px;
    font-weight: bold;
    position: absolute;
    top: 20px;
    left: 100px;
}

#upload ul li i{
    font-weight: normal;
    font-style:normal;
    color:#7f7f7f;
    display:block;
}

#upload ul li canvas{
    top: 15px;
    left: 32px;
    position: absolute;
}

#upload ul li span{
    width: 15px;
    height: 12px;
    background: url('../img/icons.png') no-repeat;
    position: absolute;
    top: 34px;
    right: 33px;
    cursor:pointer;
}

#upload ul li.working span{
    height: 16px;
    background-position: 0 -12px;
}

#upload ul li.error p{
    color:red;
}

Основным элементом страницы является форма #upload. Внутри формы расположен div с id #drop (используется для drag-and-drop) и маркированный список. Каждый элемент этого списка будет содержать информацию о каждом загруженном файле и будет выглядеть примерно так:

<li class="working">
    <input type="text" value="0" data-width="48" data-height="48" data-fgColor="#0788a5" data-readOnly="1" data-bgColor="#3e4043" />
    <p>Sunset.jpg <i>145 KB</i></p>
    <span></span>
</li>

Элемент <input> скрыт с помощью CSS. Плагин jQuery Knob, в свою очередь, после инициализации создаст все необходимые элементы управления на основе Canvas. Так же элемент <input> будет содержать несколько атрибутов data-*, значение которых будет обновляться и использоваться Knob-ом для отображения статуса загрузки и перерисовки canvas-элементов. Элемент <span> будет содержать значок результата загрузки файла.

AJAX-форма загрузки файлов


2. jQuery

Выбор и загрузка файлов осуществляется двумя способами:

  • Простым перетаскиванием файлов на div#drop (кроме IE);
  • Нажатимем на кнопку “Обзор” (“Browse”). Нажатие имитирует клик по скрытому полю <input>, которое открывает диалог выбора файлов в системе. Заметьте, скрытое поле имеет параметр multiple, что позволяет выбрать сразу несколько файлов для загрузки, при этом каждый файл всё равно будет закачан по отдельности.

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

Файл script.js:

$(function(){

    var ul = $('#upload ul');

    $('#drop a').click(function(){
        // Имитация клика по input
        $(this).parent().find('input').click();
    });

    // Инициализация jQuery File Upload plugin
    $('#upload').fileupload({

        // Контейнер для drag-and-drop
        dropZone: $('#drop'),

        // Функция, вызываемая после выбора файла
        add: function (e, data) {

            var tpl = $('<li class="working"><input type="text" value="0" data-width="48" data-height="48"'+
                ' data-fgColor="#0788a5" data-readOnly="1" data-bgColor="#3e4043" /><p></p><span></span></li>');

            // Добавляем имя файла и его размер
            tpl.find('p').text(data.files[0].name)
                         .append('<i>' + formatFileSize(data.files[0].size) + '</i>');

            // Добавляем описание файлв в список загрузок
            data.context = tpl.appendTo(ul);

            // Инициализируем Knob plugin
            tpl.find('input').knob();

            // Отслеживание кликов по кнопке отмены
            tpl.find('span').click(function(){

                if(tpl.hasClass('working')){
                    jqXHR.abort();
                }

                tpl.fadeOut(function(){
                    tpl.remove();
                });

            });

            // Автоматическая загрузка файла после его добавления
            var jqXHR = data.submit();
        },

        progress: function(e, data){

            // Высчитываем статус загрузки
            var progress = parseInt(data.loaded / data.total * 100, 10);

            // Обновляем скрытый input
            data.context.find('input').val(progress).change();

            if(progress == 100){
                data.context.removeClass('working');
            }
        },

        fail:function(e, data){
            // Ошибка
            data.context.addClass('error');
        }

    });

    // Отмена стандартного действия брацзера, если выполнено действие drop
    $(document).on('drop dragover', function (e) {
        e.preventDefault();
    });

    // Вспомогательная функция для переода размера файла в другие велечины
    function formatFileSize(bytes) {
        if (typeof bytes !== 'number') {
            return '';
        }

        if (bytes >= 1000000000) {
            return (bytes / 1000000000).toFixed(2) + ' GB';
        }

        if (bytes >= 1000000) {
            return (bytes / 1000000).toFixed(2) + ' MB';
        }

        return (bytes / 1000).toFixed(2) + ' KB';
    }

});

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

  • dropZone — селектор, определяющий элемент, который будет реагировать на событие drag/drop. Файлы, перемещённые сюда, будут добавлены в очередь загрузки.
  • add — callback-функция, вызываемая после добавления файлов в очередь. Внутри неё мы создадим HTML-представление файла, которое будет добавляться в наш маркированный список UL, а так же вызывать метод data.submit() для немедленной загрузки файла.
  • progress — callback-функция, вызываемая каждые 100мс (настраивается). Второй аргумент (data) хранит в себе размер файла и количество переданных байт. Это позволит нам отслеживать статус загрузки файла в процентном соотношении.
  • fail — callback-функция, вызываемая при возникновении проблем на стороне сервера, например, отсутствие PHP скрипта или ошибка в нём.

Полный список возможных параметров можно посмотреть здесь.


3. PHP

Плагин jQuery File Upload поставляется с PHP скриптом, управляющим загрузкой файлов на сервер, но в этом уроке мы напишем свой собственный скрипт. Отправка файлов на сервер с помощью представленного плагина практически ничем не отличается от загрузки файлов с помощью обычной HTML-формы — вся передаваемая информация хранится в глобальном массиве $_FILES:

<?php

// Разрешённые для загрузки файлы
$allowed = array('png', 'jpg', 'gif','zip');

if(isset($_FILES['upl']) && $_FILES['upl']['error'] == 0){

    $extension = pathinfo($_FILES['upl']['name'], PATHINFO_EXTENSION);

    if(!in_array(strtolower($extension), $allowed)){
        echo '{"status":"error"}';
        exit;
    }

    if(move_uploaded_file($_FILES['upl']['tmp_name'], 'uploads/'.$_FILES['upl']['name'])){
        echo '{"status":"success"}';
        exit;
    }
}

echo '{"status":"error"}';
exit;

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


Вот и всё!

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

 

P.S. Скачать демо-пример со всеми стилями и скриптами можно здесь.



Это стоит посмотреть



  /    /  Комментариев нет

Обновлено:


В этом небольшом уроке мы поработаем с нативным меню в WordPress!


Шаг 1. Разрешим использовать меню WordPress в теме

Чтобы в нашей теме был доступен стандартный, мощный функционал по созданию меню, необходимо включить его в файле functions.php вашей темы.

За включение отвечает функция add_theme_support. По умолчанию при создании новой темы для WordPress возможности работы с «меню» отключены. Чтобы включить возможность управлять навигацией добавьте строку приведенную ниже в файл functions.php:

add_theme_support( 'menus' );

Если файл «function.php» отсутствует — создайте его.


Шаг 2. Создание меню для вордпресс в панели управления

Теперь, когда вы добавили поддержку меню в вашей теме, в пункте «внешний вид» административной панели появится пункт «Меню».

Управление навигационными меню в административной части WordPress

Управление навигационными меню в административной части WordPress

 

Чтобы создать новое меню в wordpress для использования в теме, необходимо ввести его название (можно как на латинице, так и на кириллице):

Создание нового меню в WordPress

Создание нового меню в WordPress

 

После — добавьте в него необходимые пункты, например «Главная», «О компании» и т.д.

Добавлять пункты позволяют блоки «Записи» или «Страницы».

 

Блоки записей, страниц и проивзольных ссылок для вывода пунктов меню в WordPress

Просто выделите необходимые пункты и нажмите кнопку «Добавить в меню».

Не забывайте нажать кнопку «Создать/Сохранить меню».


Шаг 3. Выведем пункты меню в шаблоне

Тут не сложнее предыдущих шагов. Для вывода меню в теме в WordPress предусмотрена специальная функция wp_nav_menu(). Она имеет множество настроек. Среди них есть и параметр «menu=>»», значение которого указывает на то, какое именно меню использовать в конкретном случае, ведь в шаблоне может быть более 1 меню.

Вставьте следующий код, например в файле header.php:

<?php 
    wp_nav_menu( array("menu" => "Новое меню", "menu_class" => "new_menu") );
?>

Мы создали меню в вордпресс в 3 шага

Мы подошли к логическому завершению урока. У нас теперь выводится наше меню с классом «new_menu». Его можно стилизовать с помощью CSS.

У функции wp_nav_menu(), как уже говорилось, есть масса параметров, с помощью которых вы можете более тонко управлять отображением и содержанием. Подробнее об этой функции в кодексе WordPress.



Это стоит посмотреть



  /    /  Комментариев нет

Обновлено:


Форум является самым простым и распространённым инструментом для организации интернет-сообществ. В сети можно найти десятки бесплатных форумных движков, однако, если ваш сайт работает под управлением WordPress, мы предлагаем рассмотреть вам «bbPress» — простой и шустрый форум, который работает с WordPress как единое целое.


Что такое bbPress?

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


Как установить плагин bbPress

Плагин bbPress доступен в репозитории для WordPress, поэтому вы без проблем можете его установить, зайдя через  консоль (административную панель сайта) в меню ПлагиныДобавить новый. Наберите в строке поиска «bbPress«, после чего установите и активируйте его. После активации вас перебросит на окно приветствия плагина.

Окно приветствия bbPress


Создание нового форума

После установки, в меню вы увидите три новых пункта: ‘Forums’, ‘Topics’ и ‘Replies’.Пункт меню bbPress

Щёлкните на ForumNew Forum. Откроется страница добавления нового форума, внешне похожая на страницу создания нового поста или страницы. Введите заголовок для форума и небольшое описание, после чего нажмите кнопку Опубликовать.

Окно создания темы форума bbPress


Отображение форума на сайте

После того, как вы создали несколько форумов, самое время отобразить их на вашем сайте. Создайте новую страницу, назовите её, например, «Форум» или «Сообщество» и вставьте в область контента следующий шорт-код:

[bbp-forum-index]

Отключите для этой страницы Комментарии и обратные ссылки (галочки «Разрешить комментарии» и «Разрешить обратные ссылки и уведомления» в блоке «Обсуждение«. Если этого блока нет, то вверху страницы щёлкните по ссылке «Настройки экрана» и установите галочку «Обсуждение»).

Перейдите в меню Внешний видМеню и добавьте ссылку на эту страницу, чтобы она появилась в меню на сайте.

Теперь, при переходе на эту страницу, пользователь увидит список созданных вами форумов.

Список форумов и тем bbPress на сайте


Интеграция форума bbPress в шаблон

Чтобы открыть регистрацию пользователей на форуме, необходимо в разделе ПараметрыОбщие установить галочку «Любой может зарегистрироваться». Для создания страницы с формой регистрации перейдите в СтраницыДобавить новую, введите заголовок (например, «Регистрация»), а в контент добавьте шорт-код:

[bbp-register]

Не забудьте опубликовать эту страницу.

Так же нам потребуется форма для восстановления пароля. Создайте ещё одну страницу с именем «Восстановление пароля» со следующим шорт-кодом:

[bbp-lost-pass]

 

Хочется отметить, что в состав bbPress входят несколько виджетов, которые можно найти в меню Внешний видВиджеты. Наиболее интересным для нас является виджет (bbPress) Login Widget. Перенесите его в любую область вашего шаблона, поддерживающую виджеты. Как правило, это сайдбар. Введите заголовок для виджета, который будет отображаться на сайте, а так же укажите ссылки на страницы регистрации и восстановления пароля.

Окно настройки виджета для bbPress

Теперь форма входа, а так же ссылки для восстановления пароля, будут отображаться в сайдбаре вашего сайта. Авторизованные пользователи вместо формы будут видеть своё «имя пользователя» и кнопку выхода.

Виджет bbPress на странице


Настройки bbPress

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

Раздел настроект форума bbPress в WordPress

Первая опция «Disallow editing after» по умолчанию имеет значение 5 минут. Эта настройка позволяет редактировать вашим посетителям свои сообщения в течение указанного времени после их публикации. Не распространяется на пользователей со статусом «Keymaster» и «Moderator».

Вторая опция «Throttle posting every» позволяет защитить ваш форум от флуда, разрешая публикацию сообщений не чаще заданного промежутка времени (по умолчанию 10 секунд).

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


Пользовательские роли

В bbPress предопределены 5 пользовательских ролей, каждая из которых имеет собственные права доступа.

1. Keymaster — владелец сайта и форума. Любому пользователю WordPress с правами администратора автоматически назначается эта роль. Keymaster может создавать и удалять форумы, создавать, редактировать и удалять все сообщения и темы других пользователей.

2. Moderators — могут редактировать форумы, темы и сообщения.

Список форумов и тем bbPress в административной части WordPress

3. Participants — пользовательская роль по умолчанию. Позволяет создавать и редактировать их собственные темы и сообщения.

4. Spectators — доступ только для чтения.

5. Blocked — заблокированные пользователи, не имеющие возможность участвовать в форумах.


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

Мы надеемся, что эта статья помогла вам разобраться с установкой и настройкой bbPress на ваш сайт. Если у вас есть какие-либо советы или вы хотите поделиться своим опытом по работе с этим форумом, пожалуйста, оставьте свой комментарий ниже.





В этом коротком уроке я покажу, как убрать мета-тег name=generator на вашем сайт или блоге на WordPress.


Мета-теги

Содержат служебную информацию для поисковых систем, социальных сетей и браузеров. В WP тоже есть ряд служебных мета-тегов, которые встраиваются автоматически при вызове функции wp_head(); в шаблоне вашего сайта или блога.

Среди них есть и мета-тег generator. Он говорит об используемой версии wordpress. Его необходимо убрать, т.к. это бьет по безопасности вашего ресурса. Например, вы используете версию wordpress 2.x, где на протяжении всей ветви разработки были некоторые изъяны в безопасности, о которых известно миллионам разработчиков.


Как убрать

Самый простой и, пожалуй, единственный способ это сделать — убрать вызов функции wp_generator в хуке wp_head().

Для этого выполним функцию удаления (remove_action) в файле functions.php вашей темы:

remove_action( 'wp_head', 'wp_generator' );

Теперь мета-тег не будет отображаться в исходном коде страницы.




  /    /  Комментариев нет


При разработке сайтов на Wordpress иногда возникает необходимость ограничить доступ клиента к некоторым разделам панели управления, а раз так, то почему бы не упростить административное меню, убрав из него всё лишнее? И вот как это делается…


Специалисты по созданию и продвижению сайтов в colorflicks выработали правило — всегда предоставлять оптимальные решения для своих клиентов. Это касается и административной части wordpress. Зачем клиенту лишняя информация?

Шаг 1. Установка и настройка плагина

Как обычно всё начинается с установки нового плагина. Зайдите в директорию ‘/wp-content/plugins/’ и создайте там папку под названием ‘simple-admin’. После этого создайте в ней новый PHP-файл с именем ‘simple-admin.php’. В самом начале файла разместите заголовок плагина:

<?php
/*
Plugin Name: Simple Admin
Plugin URI: http://tutsday.ru/wordpress/uproshhaem-administrativnuyu-panel-dlya-vashih-klientov/?preview=true&preview_id=509&preview_nonce=eac52acff0
Description: Упрощение административной панели
Version: 0.1
Author: Japh
License: GPL2
*/
?>

Мы напишем этот плагин с помощью объектно-ориентированного программирования, поэтому сразу под заголовком разместите следующее:

<?php
class Simple_Admin {

    function __construct() {
        // Здесь мы добавим хуки...
    }

}

$simple_admin = new Simple_Admin();
?>

Уже на данном этапе вы можете активировать плагин, зайдя в консоль вашего сайта.


Шаг 2. Скрываем пункты меню

Предположим, что вашим клиентам нет необходимости использовать раздел «Ссылки», «Инструменты» и «Настройки» (в конце концов, крутить настройки это ваша работа, не так ли?). Давайте скроем их из меню. Для этого код плагина должен выглядеть так:

<?php
class Simple_Admin {

    function __construct() {
        // Хук для 'admin_menu', удаляем пункты меню
        add_action( 'admin_menu', array( $this, 'hide_menus' ) );
    }

    // Эта функция удаляет пункты меню, используя Page Hook Suffix ( http://codex.wordpress.org/Administration_Menus#Page_Hook_Suffix )
    function hide_menus() {
        // Ссылки
        remove_menu_page( 'link-manager.php' );
        // Инструменты
        remove_menu_page( 'tools.php' );
        // Настройки
        remove_menu_page( 'options-general.php' );
    }

}

$simple_admin = new Simple_Admin();
?>

Шаг 3. Убираем виджеты из консоли

Не знаю как вы, но я уже давно заметил в панели управления WordPress несколько совершенно ненужных мне, и уж тем более моим клиентам, виджетов, а именно: ‘Входщий ссылки’, ‘Плагины’, ‘Блог WordPress’ и ‘Другие новости WordPress’. Многие могут сказать, что ‘Входщие ссылки‘ это очень полезный и мощный инструмент, однако я предпочитаю скрыть его.

Немного доработав наш плагин, мы легко и просто избавляемся от этих виджетов:

<?php
class Simple_Admin {

    function __construct() {
        // Хук для 'admin_menu', удаляем пункты меню
        add_action( 'admin_menu', array( $this, 'hide_menus' ) );
        // Хук для 'admin_menu', удаляем виджеты из консоли
        add_action( 'admin_menu', array( $this, 'remove_dashboard_widgets' ) );
    }

    // Эта функция удаляет пункты меню, используя Page Hook Suffix ( http://codex.wordpress.org/Administration_Menus#Page_Hook_Suffix )
    function hide_menus() {
        // Ссылки
        remove_menu_page( 'link-manager.php' );
        // Инструменты
        remove_menu_page( 'tools.php' );
        // Настройки
        remove_menu_page( 'options-general.php' );
    }

    // Эта функция удаляет виджеты
    function remove_dashboard_widgets() {
        // Удаляем перечисленные виджеты: Входящие ссылки, Плагины, Блог WordPress и Другие новости WordPress
        remove_meta_box('dashboard_incoming_links', 'dashboard', 'core');
        remove_meta_box('dashboard_plugins', 'dashboard', 'core');
        remove_meta_box('dashboard_primary', 'dashboard', 'core');
        remove_meta_box('dashboard_secondary', 'dashboard', 'core');
    }

}

$simple_admin = new Simple_Admin();
?>

Шаг 4. Скрываем колонки из списков с постами

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

Допишем ещё несколько строк, которые скроют эту колонку, и получим окончательный код плагина:

<?php
class Simple_Admin {

    function __construct() {
        // Активируем функцию удаления пунктов меню с помощью хука для 'admin_menu'
        add_action( 'admin_menu', array( $this, 'hide_menus' ) );
        // Хук для 'admin_menu', удаляем виджеты из консоли
        add_action( 'admin_menu', array( $this, 'remove_dashboard_widgets' ) );
		// Хук для записей и постов, удаляем ненужные колонки
		add_filter( 'manage_pages_columns', array( $this, 'remove_columns' ) );
		add_filter( 'manage_posts_columns', array( $this, 'remove_columns' ) );
    }

    // Эта функция удаляет пункты меню, используя Page Hook Suffix ( http://codex.wordpress.org/Administration_Menus#Page_Hook_Suffix )
    function hide_menus() {
        // Ссылки
        remove_menu_page( 'link-manager.php' );
        // Инструменты
        remove_menu_page( 'tools.php' );
        // Настройки
        remove_menu_page( 'options-general.php' );
    }

    // Эта функция удаляет виджеты
    function remove_dashboard_widgets() {
        // Удаляем перечисленные виджеты: Входящие ссылки, Плагины, Блог WordPress и Другие новости WordPress
        remove_meta_box('dashboard_incoming_links', 'dashboard', 'core');
        remove_meta_box('dashboard_plugins', 'dashboard', 'core');
        remove_meta_box('dashboard_primary', 'dashboard', 'core');
        remove_meta_box('dashboard_secondary', 'dashboard', 'core');
    }

	// Эта функция удаляет перечисленные колонки из списка с записями / страницами.
	// В нашем случае — удаляем колонку Автор
    function remove_columns( $defaults ) {
        unset( $defaults['author'] );
        return $defaults;
    }

}

$simple_admin = new Simple_Admin();
?>

Заключение

Подобные небольшие изменения позволяют сделать административную панель WordPress чуточку проще для ваших клиентов, ведь теперь им не придётся путаться среди множества ненужных им меню и виджетов.

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

А каким образом вы упрощаете консоль WordPress для своих клиентов?


Теги:



Небольшой урок о том, как в jQuery плагине Masked Input Plugin убрать автоматическое стирание не до конца введенной информации в поле форм по маске ввода.


Стандартное поведение маски ввода в поле формы

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

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

По умолчанию, при задании маски ввода, форма работает следующим образом:

Если ввести в поле цифры, но заполнив маску не до конца, то при клике вне поля введенные данные сотрутся и пользователю придется вводить все заново.


Маска ввода без автоматического удаления информации

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

Теперь форма работает вот таким образом:


Решение

Суть находится в файле плагина  в функции checkVal(). Найдите следующий кусок кода:

function checkVal(allow) {

Далее, в этой функции необходимо найти условие:

if (allow) {
	writeBuffer();
} else if (lastMatch + 1 < partialPosition) {
	input.val("");
	clearBuffer(0, len);
} else {
	writeBuffer();
	input.val(input.val().substring(0, lastMatch + 1));
}

Это примерно 275 строка кода.

Все, что нам остается сделать, чтобы плагин не удалял введенные данные в поле формы, при условии, что маска введена не полностью, это удалить или закомментировать строки:

input.val("");
clearBuffer(0, len);

Получается, что мы не удаляем данные, если количество знаков меньше, чем количество знаков в маске.

if (allow) {
	writeBuffer();
} else if (lastMatch + 1 < partialPosition) {
	//input.val(""); 
	//clearBuffer(0, len);
} else {
	writeBuffer();
	input.val(input.val().substring(0, lastMatch + 1));
}

Версия плагина 1.3.1. Подробнее про работу плагина Masked Input на сайте разработчика.

 





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


1. Подготовьте биографию заранее

Если вы еще не наполняли информацию об авторе, то вы можете добавить ее, зайдя в  панель управления wordpress, группа «пользователи», выбрать пользователя и наполнить окно краткой автобиографией. Мы также собираемся добавить аватар автора, поэтому убедитесь, что вы ввели адрес электронной почты. Поле для заполнения краткой биографии в wordpress


2. Создание необходимых файлов

Нам понадобится создать один файл, который бы содержал функциональное наполнение  нашего плагина. Зайдите через FTP в папку wp-content/plugins/, создайте новый каталог с произвольным именем, затем в этом каталоге создайте файл под названием ntauthorbio.php. Чтобы wordpress мог распознавать наш плагин, в  файле необходимо создать комментарий, подобный тому, что вы обычно делаете в верхней части style.css. Вставьте следующий код в ваш файл и, конечно же, внесите соответствующие коррективы:

/* 
Plugin Name: TUTSDAY Author Bio 
Plugin URI: http://tutsday.ru 
Description: Этот плагин добавляет авторскую биографию в пост
Author: Alexey Serge
Version: 0.1 
Author URI: http://tutsday.ru
*/

3. Функции и Экшены (Actions)

Теперь мы создадим основной каркас для нашего плагина. Вставьте нижеприведенный код после системного комментария в файле ntauthorbio.php.

function author_bio_display($content)  
{
    //тут мы напишем функцию отображения биографии
}  
function author_bio_style()  
{  
    //тут мы опишем оформление для нашего блока с биографией
}  
function author_bio_settings()  
{  
    //тут мы опишем функцию вывода опций в администраторском разделе
}  
function author_bio_admin_menu()  
{  
    //тут мы добавим плагин в меню панели администрирования
}  
add_action('the_content', 'author_bio_display'); 
add_action('admin_menu', 'author_bio_admin_menu'); 
add_action('wp_head', 'author_bio_style');

Выше мы создали 4 функции, которые понадобятся плагину для полноценной работы. У каждой функции своя собственная цель (как было сказано выше), также каждая из функций связана с определенной последовательностью операций (кроме author_bio_settings, которая запускаются из другой функции).

При создании плагинов важно понимать, что такое «hook» — это место в цикле, откуда можно подключиться к wordpress и запустить ту или иную функцию. Например, мы использовали хук для нашей функции author_bio_display в the_content. Это значит, что когда будет вызвана функция the_content (эта функция позволяет выводить содержимое записей на страницу), сначала запустится наша функция.

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

  • the_content – при помощи него отображается контент страницы/поста
  • admin_menu – необходим для создания пункта меню панели администрирования
  • wp_head – позволяет добавлять в теги <head>. Вот поэтому вы вызываете функцию wp_head() перед закрывающим тегом </head>, когда проектируете темы оформления.

4. Функция отображения

Самая важная функция плагина – это функция вывода на экран, которая управляет процессом  отображения информации. Прежде чем начать, важно отметить, что эта функция имеет такой параметр, как $content. Это значит, что контент страницы или поста передается нашей функции, благодаря чему в последствии можно присоединить в конце материала автобиографию. Начнем с простого if/else условия:

function author_bio_display($content)  
{  
    if ( is_single() || is_page() ) 
    {  
        $bio_box = // placeholder;  
        return $content . $bio_box;  
    } else {  
        return $content;  
    }  
}

Необходимо, чтобы автобиография отображалась только на одиночной странице (single page / page.php) или одиночном посте (single post / single.php), т.е. не в архиве, не списках разделов, не на главной странице. Для этого мы воспользуемся стандартными функциями (условными тегами / Conditional Tags) — is_single() и is_page() для проверки. Теперь необходимо добавить код, чтобы появилось поле для автобиографии. Измените $bio_box, чтобы он совпадал со следующим кодом:

$bio_box =  
'<div id="author-bio-box"> 
    '.get_avatar( get_the_author_meta('user_email'), '80' ).' 
    <span class="author-name">'.get_the_author_meta('display_name').'</span> 
    <p>'.get_the_author_meta('description').'</p> 
    <div class="spacer"></div> 
</div>';

Стиль, конечно же, можно потом поменять на ваш вкус, но  пока что будем использовать простое поле и добавим немного CSS, чтобы его оформить. Используем несколько указанных выше функций, чтобы загрузить требуемые данные. get_avatar()  — это встроенная функция в WP, которая вернет пользовательский аватар, если  есть адрес электронной почты. Передадим функции  get_avatar() 2 параметра: авторский адрес электронной почты и размер фотоизображения (в нашем случае это 80px*80px). Функция get_the_author_meta поможет  загрузить любую  информацию о зарегистрированном в WP пользователе. Весь  список параметров можно посмотреть в кодексе WordPress Codex.

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

Не стилизованный блок биографии автора

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


5. Стили оформления

Если вы дизайнер, то вот ваш шанс сделать так, как вам нравится! Кода внизу вполне достаточно, чтобы освежить и сделать блок более привлекательным и читабельным. Нам необходимо вставить CSS код в рамки тега <head>. Для этого используем хук (hook) в wp_head. Этого делать не обязательно, можно просто поместить этот код в вашу таблицу стилей. Функция author_bio_style() необходима, чтобы поместить CSS блок.

function author_bio_style()  
{  
    echo  
    '<style type='text/css'>  
    #author-bio-box {  
        border: 1px solid #bbb;  
        background: #eee;  
        padding: 5px;  
    }  
    #author-bio-box img {  
        float: left;  
        margin-right: 10px;  
    }  
    #author-bio-box .author-name {  
        font-weight: bold;  
        margin: 0px;  
        font-size: 14px;  
    }  
    #author-bio-box p {  
        font-size: 10px;  
        line-height: 14px;  
        font-style: italic;  
    }  
    .spacer { display: block; clear: both; }  
    </style>';  
}

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

Ваше новоизобретенное поле должно выглядеть более привлекательно.

Стилизованный блок биографии пользователя


6. Создание страницы настроек плагина

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

Создаем страницу

Необходимо создать страницу в панели администрирования. Для этого нам надо сообщить WP, что делать, когда он вызывает экшн admin_menu. Отредактируем функцию author_bio_admin_menu() , чтобы та приобрела следующий вид:

function author_bio_admin_menu()
{
//добавляем плагин в меню
add_options_page('Author Bio', 'Author Bio', 9, basename(__FILE__), 'author_bio_settings');
}

Этот код создает страницу с опциями в панели управления и передает следующие параметры:

add_options_page( $page_title, $menu_title, $capability, $function);

$page_title — Заголовок страницы.

$menu_title — Название пункта меню в панели управления.

$capability — доступность пункта меню (9 и выше — только для администраторов).

$function — функция, которая будет запускаться при переходе на страницу настроек.

В нашем случае это author_bio_settings(). Необходимо снабдить страницу контентом. Так как при создании страницы мы обратились к author_bio_settings(), эту функцию мы будем использовать для отображения формы опций.

Функция настроек плагина

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

function author_bio_settings()

{

//здесь отображаем опции администратора

$options['page'] = get_option('bio_on_page');

$options['post'] = get_option('bio_on_post');

echo '

<div class="wrap">

'.$message.'

<div id="icon-options-general" class="icon32"><br /></div>

<h2>Настройки автобиографии</h2>

<form method="post" action="">

<input type="hidden" name="action" value="update" />

<h3>Где отображать автобиографию пользователя?</h3>

<input name="show_pages" type="checkbox" id="show_pages" '.$options['page'].' /> Страницы<br />

<input name="show_posts" type="checkbox" id="show_posts" '.$options['post'].'/> Посты<br />

<br />

<input type="submit" class="button-primary" value="Сохранить" />

</form>

</div>';

}

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

Страница настроек плагина автобиографии

Теперь попробуем добавить код, чтобы отправить данные формы и обновить их в базе данных. Форма содержит скрытый показатель action, который настроен на апдейт. Проверим, установлен ли этот показатель, и если это так, обновим настройки. Код должен быть размещен вверху функции autor_bio_settings().

if ($_POST['action'] == 'update')

{

$_POST['show_pages'] == 'on' ? update_option('bio_on_page', 'checked') :
update_option('bio_on_page', '');

$_POST['show_posts'] == 'on' ? update_option('bio_on_post', 'checked') :
update_option('bio_on_post', '');

$message = '<div id="message" class="updated fade"><p><strong>Настройки сохранены</strong></p></div>';

}

Чтобы проверить, были ли отправлены данные формы мы использовали тернарный оператор на проверку значений. Если данные были отправлены — установим значение опции в качестве checked, в противном случае отправится пустое. Затем выведем сообщение об успешном изменении.

Дополнительная настройка отображения автобиографии

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

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

$options['page'] = get_option('bio_on_page');
$options['post'] = get_option('bio_on_post');

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

if ( (is_single() && $options['post']) || (is_page() && $options['page']) )

Тут мы добавили наши значения опций к уже имеющимся условиям и если значение опций не будет пустым — условие сработает.

Ниже приведен полный код плагина:

 

<?php  
/*
Plugin Name: TUTSDAY Author Bio
Plugin URI: http://tutsday.ru
Description: Этот плагин добавляет авторскую биографию в пост
Author: Alexey Serge
Version: 0.1
Author URI: http://tutsday.ru
*/

function author_bio_display($content)  
{  

    //тут мы напишем функцию отображения биографии 

    $options["page"] = get_option("bio_on_page");  
    $options["post"] = get_option("bio_on_post");  
    if ( (is_single() && $options["post"]) || (is_page() && $options["page"]) )  
    {  
        $bio_box = 
		'<div id="author-bio-box">
			'.get_avatar( get_the_author_meta('user_email'), '80' ).'
			<span class="author-name">'.get_the_author_meta('display_name').'</span>
			<p>'.get_the_author_meta('description').'</p>
			<div class="spacer"></div>
		</div>'; 
        return $content . $bio_box;  
    } else {  
        return $content;  
    }  
}  

function author_bio_style()  
{  
	//тут мы опишем оформление для нашего блока с биографией

    echo  
    '<style type="text/css"> 
    #author-bio-box { 
        border: 1px solid #bbb; 
        background: #eee; 
        padding: 5px; 
    } 
    #author-bio-box img { 
        float: left; 
        margin-right: 10px; 
    } 
    #author-bio-box .author-name { 
        font-weight: bold; 
        margin: 0px; 
        font-size: 14px; 
    } 
    #author-bio-box p { 
        font-size: 10px; 
        line-height: 14px; 
        font-style: italic; 
    } 
    .spacer { display: block; clear: both; } 
    </style>';  
}  

function author_bio_settings()  
{  

	//тут мы опишем функцию вывода опций в администраторском разделе

    if ($_POST["action"] == "update")  
    {  
        $_POST["show_pages"] == "on" ? update_option("bio_on_page", "checked") : update_option("bio_on_page", "");  
        $_POST["show_posts"] == "on" ? update_option("bio_on_post", "checked") : update_option("bio_on_post", "");  
        $message = '<div id="message" class="updated fade"><p><strong>Настройки сохранены</strong></p></div>';  
    }  
    $options['page'] = get_option('bio_on_page');
	$options['post'] = get_option('bio_on_post');

	echo '

	<div class="wrap">

	'.$message.'

	<div id="icon-options-general" class="icon32"><br /></div>

	<h2>Настройки автобиографии</h2>

	<form method="post" action="">
		<input type="hidden" name="action" value="update" />
		<h3>Где отображать автобиографию пользователя?</h3>
		<input name="show_pages" type="checkbox" id="show_pages" '.$options['page'].' /> Страницы<br />
		<input name="show_posts" type="checkbox" id="show_posts" '.$options['post'].'/> Посты<br />
		<br />
		<input type="submit" class="button-primary" value="Сохранить" />
	</form>

	</div>'; 
}  

function author_bio_admin_menu()  
{  
    //тут мы добавим плагин в меню панели администрирования
    add_options_page("Author Bio", "Author Bio", 9, basename(__FILE__), "author_bio_settings");  
}  

add_action("the_content", "author_bio_display");  
add_action("admin_menu", "author_bio_admin_menu");  
add_action("wp_head", "author_bio_style");  

?>

Вуаля!

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

Если у вас возникли вопросы, оставляйте комментарии!


Теги: