Профили управления доступом к объектам в любой конфигурации на БСП

17.11.21

Разработка - БСП (Библиотека стандартных подсистем)

В данной статье рассмотрим механизмы стандартного управления профилями доступа к объектам в любой типовой конфигурации, в основе которой лежит БСП.

Введение

Всем доброго времени суток!

В данной статье я хочу рассмотреть базовый функционал подсистемы "Управление доступом", а именно управление профилями доступа для актуальной на текущий момент библиотеки стандартных подсистем (БСП). На момент написания публикации - версия БСП 3.1.5.208. Тестирование практических примеров осуществляется для Платформе 1с 8.3.19.1264.

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

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

Перейдем к первой части - проверка наличия ролей и программное добавление профилей пользователю:

 

Часть 1. Проверка наличия роли и программное добавление профиля пользователю.

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


Роль1 = УправлениеДоступом.ЕстьРоль("ДобавлениеИзменениеКурсовВалют",,Справочники.Пользователи.НайтиПоНаименованию("ТестПользователь"));
// истина

Данный код будет возвращать Истину, если у пользователя "ТестПользователь" есть роль "ДобавлениеИзменениеКурсовВалют", которая добавлена через профиль группы доступа пользователя:

 

Рис.1 Профиль пользователя с ролью "Добавление и изменение курсов валют".

 

Поскольку наш только созданный тестовый пользователь ТестПользователь не имеет право на вход в базу данных попробуем добавить профиль "Пользователь" пользователю ТестПользователь следующим кодом:

УправлениеДоступом.ВключитьПрофильПользователю(Справочники.Пользователи.НайтиПоНаименованию("ТестПользователь"),Справочники.ПрофилиГруппДоступа.НайтиПоНаименованию("Пользователь"));

 

Смотрим, профиль добавился:

 

Рис.2 Добавленный профиль "Пользователь" процедурой ВключитьПрофильПользователю.

 

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

 

Рис.3 Зашли тестовым пользователем в базу, при назначенном профиле "Пользователь".

 

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

УправлениеДоступом.ВключитьПрофильПользователю(Справочники.Пользователи.НайтиПоНаименованию("ТестПользователь"),Справочники.ПрофилиГруппДоступа.НайтиПоНаименованию("Администратор"));

 

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

УправлениеДоступом.ВыключитьПрофильПользователю(Справочники.Пользователи.НайтиПоНаименованию("ТестПользователь"),Справочники.ПрофилиГруппДоступа.НайтиПоНаименованию("Администратор"));

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

 

Часть 2. Программное обновление и создание профиля доступа на БСП.

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

 

Подраздел 2.1 Программное обновление поставляемого профиля доступа.

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

 
 Конструктор профиля доступа

 

ГуидПрофиля = "bfd56f51-313d-11e5-b1ac-e0cb4ed5f655"; // Этот гуид профиля "зашит" в базе

ПрограммныйПрофиль 				 = УправлениеДоступом.НовоеОписаниеПрофиляГруппДоступа();
ПрограммныйПрофиль.Имя           = "Тестовый профиль (изм2)";
ПрограммныйПрофиль.Идентификатор = ГуидПрофиля;
ПрограммныйПрофиль.Наименование  = НСтр("ru = 'Тестовый профиль (изм2)'", ОбщегоНазначения.КодОсновногоЯзыка());
	
ПрограммныйПрофиль.Описание = НСтр("ru = 'Программно изменил созданный профиль в предприятии'");
	
// Использование 1С:Предприятия.
ПрограммныйПрофиль.Роли.Добавить("ЗапускВебКлиента");
ПрограммныйПрофиль.Роли.Добавить("ЗапускТолстогоКлиента");
ПрограммныйПрофиль.Роли.Добавить("ЗапускТонкогоКлиента");
ПрограммныйПрофиль.Роли.Добавить("ЗапускМобильногоКлиента");
ПрограммныйПрофиль.Роли.Добавить("ВыводНаПринтерФайлБуферОбмена");
ПрограммныйПрофиль.Роли.Добавить("СохранениеДанныхПользователя");

// Использование программы.
ПрограммныйПрофиль.Роли.Добавить("БазовыеПраваБСП");
ПрограммныйПрофиль.Роли.Добавить("ПросмотрОписанияИзмененийПрограммы");
ПрограммныйПрофиль.Роли.Добавить("ИзменениеТекущегоПользователя");

 

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

Воспользуемся вот этой функцией обновления профиля (находится в менеджере справочника ПрофилиГруппДоступа):

//возврат истина или ложь

ПрофильОбновлен = Справочники.ПрофилиГруппДоступа.ОбновитьПрофильИлиПапкуПрофилей(ПрограммныйПрофиль,Ложь);

Типовая функция выглядит вот так (скопирую код в статью, т.к. далее, я буду вносить в эту функцию доработки):

 
 типовая функция ОбновитьПрофильИлиПапкуПрофилей

 

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

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

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

Выглядит эта функция вот так:

 
 Доработанная функция (убрал условия, сделал экспортной)

 

Функция ОбновитьПрофильИлиПапкуПрофилей(СвойстваПрофиля, НеОбновлятьРолиПользователей = Ложь) Экспорт
	
	ПрофильИзменен = Ложь;
	
	
	
	//ПрофильСсылка = ПоставляемыйПрофильПоИдентификатору(СвойстваПрофиля.Идентификатор);
	
	//Если ПрофильСсылка = Неопределено Тогда
		
		Если ЗначениеЗаполнено(СвойстваПрофиля.Имя) Тогда
			Запрос = Новый Запрос;
			Запрос.Текст =
			"ВЫБРАТЬ
			|	ПрофилиГруппДоступа.Ссылка КАК Ссылка,
			|	ПрофилиГруппДоступа.ИмяПредопределенныхДанных КАК ИмяПредопределенныхДанных
			|ИЗ
			|	Справочник.ПрофилиГруппДоступа КАК ПрофилиГруппДоступа
			|ГДЕ
			|	ПрофилиГруппДоступа.Предопределенный = ИСТИНА";
			Выборка = Запрос.Выполнить().Выбрать();
			
			Пока Выборка.Следующий() Цикл
				ИмяПредопределенного = Выборка.ИмяПредопределенныхДанных;
				Если ВРег(СвойстваПрофиля.Имя) = ВРег(ИмяПредопределенного) Тогда
					ПрофильСсылка = Выборка.Ссылка;
					Прервать;
				КонецЕсли;
			КонецЦикла;
		КонецЕсли;
		
		Если ПрофильСсылка = Неопределено
		   //И СвойстваПрофиля.ЭтоГруппа
		   И ЗначениеЗаполнено(СвойстваПрофиля.Наименование) Тогда
			
			Запрос = Новый Запрос;
			Запрос.Текст =
			"ВЫБРАТЬ
			|	ПрофилиГруппДоступа.Ссылка КАК Ссылка
			|ИЗ
			|	Справочник.ПрофилиГруппДоступа КАК ПрофилиГруппДоступа
			|ГДЕ
			|	ПрофилиГруппДоступа.Предопределенный = ЛОЖЬ
			|	И ПрофилиГруппДоступа.ЭтоГруппа
			|	И ПрофилиГруппДоступа.Наименование = &Наименование";
			Запрос.УстановитьПараметр("Наименование", СвойстваПрофиля.Наименование);
			
			Выборка = Запрос.Выполнить().Выбрать();
			Если Выборка.Следующий() Тогда
				ПрофильСсылка = Выборка.Ссылка;
			КонецЕсли;
		КонецЕсли;
		
		Если ПрофильСсылка = Неопределено Тогда
			// Поставляемый элемент не существует, нужно создать новый.
			//ПрофильОбъект = ?(СвойстваПрофиля.ЭтоГруппа, СоздатьГруппу(), СоздатьЭлемент());
			ПрофильОбъект = СоздатьЭлемент();
		Иначе
			// Поставляемый элемент связан с предопределенным элементом.
			ПрофильОбъект = ПрофильСсылка.ПолучитьОбъект();
		КонецЕсли;
		
		ПрофильОбъект.ИдентификаторПоставляемыхДанных =
			Новый УникальныйИдентификатор(СвойстваПрофиля.Идентификатор);
		
		ПрофильИзменен = Истина;
	//Иначе
	//	ПрофильОбъект = ПрофильСсылка.ПолучитьОбъект();
	//	ПрофильИзменен = ПоставляемыйПрофильИзменен(ПрофильОбъект);
	//КонецЕсли;
	//
	//Если ПрофильИзменен Тогда
		
		Если Не ОбновлениеИнформационнойБазы.ВыполняетсяОбновлениеИнформационнойБазы()
		   И Не ОбновлениеИнформационнойБазы.ЭтоВызовИзОбработчикаОбновления() Тогда
			
			ЗаблокироватьДанныеДляРедактирования(ПрофильОбъект.Ссылка, ПрофильОбъект.ВерсияДанных);
		КонецЕсли;
		
		ПрофильОбъект.Наименование = СвойстваПрофиля.Наименование;
		
		Если ЗначениеЗаполнено(СвойстваПрофиля.Родитель) Тогда
			Родитель = ПоставляемыйПрофильПоИдентификатору(СвойстваПрофиля.Родитель);
			Если ЗначениеЗаполнено(Родитель) Тогда
				ПрофильОбъект.Родитель = Родитель;
			КонецЕсли;
		КонецЕсли;
		
		//Если Не СвойстваПрофиля.ЭтоГруппа Тогда
			ПрофильОбъект.Роли.Очистить();
			Для каждого Роль Из ОписаниеРолейПрофиля(СвойстваПрофиля) Цикл
				МетаданныеРоли = Метаданные.Роли.Найти(Роль);
				Если МетаданныеРоли = Неопределено Тогда
					ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
						НСтр("ru = 'При обновлении поставляемого профиля ""%1""
						           |обнаружена несуществующая роль ""%2"".'"),
						СвойстваПрофиля.Наименование,
						Роль);
				КонецЕсли;
				ПрофильОбъект.Роли.Добавить().Роль =
					ОбщегоНазначения.ИдентификаторОбъектаМетаданных(МетаданныеРоли);
			КонецЦикла;
			
			ПрофильОбъект.ВидыДоступа.Очистить();
			Для каждого ОписаниеВидаДоступа Из СвойстваПрофиля.ВидыДоступа Цикл
				СвойстваВидаДоступа = УправлениеДоступомСлужебный.СвойстваВидаДоступа(ОписаниеВидаДоступа.Ключ);
				Строка = ПрофильОбъект.ВидыДоступа.Добавить();
				Строка.ВидДоступа        = СвойстваВидаДоступа.Ссылка;
				Строка.Предустановленный = ОписаниеВидаДоступа.Значение = "Предустановленный";
				Строка.ВсеРазрешены      = ОписаниеВидаДоступа.Значение = "ВначалеВсеРазрешены";
			КонецЦикла;
			
			ПрофильОбъект.ЗначенияДоступа.Очистить();
			Для каждого ОписаниеЗначенияДоступа Из СвойстваПрофиля.ЗначенияДоступа Цикл
				СвойстваВидаДоступа = УправлениеДоступомСлужебный.СвойстваВидаДоступа(ОписаниеЗначенияДоступа.ВидДоступа);
				СтрокаЗначения = ПрофильОбъект.ЗначенияДоступа.Добавить();
				СтрокаЗначения.ВидДоступа = СвойстваВидаДоступа.Ссылка;
				Запрос = Новый Запрос(СтрЗаменить("ВЫБРАТЬ Значение(ЗначениеДоступа) КАК Значение",
					"ЗначениеДоступа", ОписаниеЗначенияДоступа.ЗначениеДоступа));
				СтрокаЗначения.ЗначениеДоступа = Запрос.Выполнить().Выгрузить()[0].Значение;
			КонецЦикла;
			
			ПрофильОбъект.Назначение.Очистить();
			Для каждого ТипНазначения Из СвойстваПрофиля.Назначение Цикл
				СтрокаНазначения = ПрофильОбъект.Назначение.Добавить();
				СтрокаНазначения.ТипПользователей = ТипНазначения;
			КонецЦикла;
			
			Если НеОбновлятьРолиПользователей Тогда
				ПрофильОбъект.ДополнительныеСвойства.Вставить("НеОбновлятьРолиПользователей");
			КонецЕсли;
		//КонецЕсли;
		
		ОбновлениеИнформационнойБазы.ЗаписатьОбъект(ПрофильОбъект);
		
		Если Не ОбновлениеИнформационнойБазы.ВыполняетсяОбновлениеИнформационнойБазы()
		   И Не ОбновлениеИнформационнойБазы.ЭтоВызовИзОбработчикаОбновления() Тогда
			
			РазблокироватьДанныеДляРедактирования(ПрофильОбъект.Ссылка);
		КонецЕсли;
		
	//КонецЕсли;
	
	Возврат ПрофильИзменен;
	
КонецФункции

 

С помощью этой функции вносим изменения про поставляемый профиль менеджера (используя гуид профиля и конструктор выше). Результат выглядит вот так:

 

Рис.4.Профиль отличается от поставляемого

 

Теперь, перейдем к созданию своего профиля.

 

Подраздел 2.2 Программное создание профиля доступа.

Здесь, все тоже самое, только гуид мы создаем самостоятельно. Конструктор и код исполнения вот такой:

 
 Конструктор и код нового профиля

 

ГуидПрофиля = Новый УникальныйИдентификатор();
	
ПрограммныйПрофиль 				 = УправлениеДоступом.НовоеОписаниеПрофиляГруппДоступа();
ПрограммныйПрофиль.Имя           = "Программный профиль 1";
ПрограммныйПрофиль.Идентификатор = ГуидПрофиля;
ПрограммныйПрофиль.Наименование  = НСтр("ru = 'Программный профиль 1'", ОбщегоНазначения.КодОсновногоЯзыка());

	
ПрограммныйПрофиль.Описание = НСтр("ru = 'Программно изменил созданный профиль в предприятии'");

	
// Использование 1С:Предприятия.
ПрограммныйПрофиль.Роли.Добавить("ЗапускВебКлиента");
ПрограммныйПрофиль.Роли.Добавить("ЗапускТолстогоКлиента");
ПрограммныйПрофиль.Роли.Добавить("ЗапускТонкогоКлиента");
ПрограммныйПрофиль.Роли.Добавить("ЗапускМобильногоКлиента");
ПрограммныйПрофиль.Роли.Добавить("ВыводНаПринтерФайлБуферОбмена");
ПрограммныйПрофиль.Роли.Добавить("СохранениеДанныхПользователя");

// Использование программы.
ПрограммныйПрофиль.Роли.Добавить("БазовыеПраваБСП");
ПрограммныйПрофиль.Роли.Добавить("ПросмотрОписанияИзмененийПрограммы");
	

//истина или ложь	
СоздалиПрофиль = Справочники.ПрофилиГруппДоступа.ОбновитьПрофильИлиПапкуПрофилей(ПрограммныйПрофиль,Ложь);
	

 

Получаем программный профиль с ролями:

 

Рис.5.Программный профиль с ролями

 

На этом закончим второй раздел и перейдем к выводам и заключению.

 

Выводы и заключение

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

Есть еще одна идейка -  написать статью и поставить эксперименты с программными настройками видов доступа в профиле.

Надеюсь, что вам понравился данный материал и он будет вам полезен. Спасибо за внимание!

 

 

Другие мои материалы по подсистемам БСП

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

 

Администрирование с помощью БСП:

Журнал регистрации - основные методы работы через БСП

Базовые приемы работы с кластером 1С при помощи БСП

 

Длительные операции:

Запуск почти любых процедур и функции конфигураций в асинхронном режиме - БСП - Длительные операции [Часть 2]

БСП - рабочие примеры асинхронного запуска функций и процедур

 

Работа со штрихкодами и печатными макетами:

Генерация штрихкодов с помощью БСП для программистов

Полезные встроенные функции для работы с печатными формами и не только на УТ 11.4 и БП 3.0 (сравнение)

Печать макета MS Word в любом документе с помощью БСП

Вступайте в нашу телеграмм-группу Инфостарт

управление доступом роли бсп типовые конфигурации

См. также

Инструментарий разработчика Роли и права Запросы СКД Программист Руководитель проекта 1С v8.3 Управляемые формы Запросы Система компоновки данных Платные (руб)

Инструменты для разработчиков 1С 8.3: Infostart Toolkit. Автоматизация и ускорение разработки на управляемых формах. Легкость работы с 1С.

15500 руб.

02.09.2020    207337    1139    411    

1035

Инструменты администратора БД Инструментарий разработчика Роли и права Программист 1С v8.3 1C:Бухгалтерия Россия Платные (руб)

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

16000 руб.

10.11.2023    17015    74    39    

89

Зарплата Роли и права Системный администратор Бухгалтер 1С v8.3 Бухгалтерский учет Управление правами 1С:ERP Управление предприятием 2 1С:Бухгалтерия 3.0 1С:Комплексная автоматизация 2.х Молдова Россия Казахстан Бухгалтерский учет Платные (руб)

Расширение позволяет максимально полно ограничить доступ пользователей к данным по заработной плате, а именно закрывает доступ к документам начисления и выплаты заработной платы, не позволяет просматривать бухгалтерские отчеты по счету учета зарплаты а также убирает зарплатные проводки из журнала проводок. Расширение запрещает просматривать платежные документы на выплату зарплаты, так же не доступны регламентные отчеты в ПФР и ИФНС. Расширение предлагает готовые настроенные профили "Бухгалтер без зарплаты", "Только просмотр без зарплаты".

5940 руб.

27.05.2021    45025    343    107    

274

Инструменты администратора БД Роли и права Системный администратор Программист Пользователь 8.3.14 1С:Розница 2 1С:Управление нашей фирмой 1.6 1С:Документооборот 1С:Зарплата и кадры государственного учреждения 3 1С:Бухгалтерия 3.0 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х 1С:Зарплата и Управление Персоналом 3.x 1С:Управление нашей фирмой 3.0 1С:Розница 3.0 Платные (руб)

Роли… Вы тратите много времени и сил на подбор ролей среди около 2400 в ERP или 1500 в Рознице 2, пытаясь понять какими правами они обладают? Вы все время смотрите права в конфигураторе или отчетах чтоб создать нормальные профили доступа? Вы хотите наглядно видеть какие права дает профиль и редактировать все в простом виде? А может хотите просто указать подсистему и дать права на просмотр и добавление на объекты и не лезть в дебри прав и чтоб обработка сама подобрала нужные роли? Все это теперь стало возможно! Обновление от 17.06.2025, версия 1.3

19200 руб.

06.12.2023    16001    62    10    

95

БСП (Библиотека стандартных подсистем) Программист Платные (руб)

Синтакс-помощник БСП - cправочник по библиотекам стандартных подсистем и электронных документов. В состав справочника входит описание экспортных процедур и функций, размещенных в областях кода ПрограммныйИнтерфейс БСП и БЭД.

1800 руб.

21.11.2024    8395    42    24    

45

Логистика, склад и ТМЦ Роли и права Программист Бухгалтер Пользователь 1С v8.3 Бухгалтерский учет Управление правами 1С:Бухгалтерия 3.0 Россия Бухгалтерский учет Управленческий учет Платные (руб)

Расширение для 1С:Бухгалтерия 3.0, которое позволяет использовать отдельные роли для доступа к складским документам, для доступа к документам раздела "Производство" и для доступа к документам раздела "Покупки".

4560 руб.

21.05.2019    1697812    584    194    

142

Роли и права Системный администратор Бухгалтер Пользователь 1С v8.3 1С:Бухгалтерия 3.0 Россия Бухгалтерский учет Платные (руб)

Расширение предназначено для Бухгалтерии предприятия (версии ПРОФ и КОРП). Типовая конфигурация остается на поддержке. С помощью расширения менеджер по продажам будет иметь доступ к контрагентам и списку их документов только в случае, если он является для них ответственным. Пользователю с полными правами также доступна обработка «Назначение ответственных» для группового добавления/удаления ответственного в карточке контрагента. Есть версия данного расширения для клиентов Fresh - в магазине расширений (Fresh)

9360 руб.

14.09.2022    7222    12    6    

15
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. vld1973 97 17.11.21 10:46 Сейчас в теме
Плюсую, спасибо за статьи по БСП
2. aleksey2 89 17.11.21 13:07 Сейчас в теме
В бух 3.0 есть "Упрощенный Интерфейс" и "обычный". Они значительно! различаются.
Включается интерфейс в общ.модуле УправлениеДоступомПереопределяемый

// Определяет вид используемого интерфейса пользователя для настройки доступа.
// Параметры:
//  УпрощенныйИнтерфейс - Булево - начальное значение Ложь.
Процедура ПриОпределенииИнтерфейсаНастройкиДоступа(УпрощенныйИнтерфейс) Экспорт	
	УпрощенныйИнтерфейс = Истина;	
КонецПроцедуры

чтобы интерфейс был "обычный" нужно в расширение добавить строки:

&Вместо("ПриОпределенииИнтерфейсаНастройкиДоступа")
Процедура Расш1_ПриОпределенииИнтерфейсаНастройкиДоступа(УпрощенныйИнтерфейс)
	А=1;
КонецПроцедуры
Показать
5. user1859318 14.10.22 09:58 Сейчас в теме
(2)
УпрощенныйИнтерфейс = Истина;


Зачем они вообще сделали принудительное присвоение упрощенного интерфейса. Теперь что нужно переделывать чтобы работали группы доступа? Или что то изменилось в логике добавление дополнительных прав пользователям? Кто что подскажет по этому решению?
3. quazare 3958 17.11.21 13:23 Сейчас в теме
Идеология упрощенного и обычного интерфейса заложена в бсп как раз таки. Включение и выключение ролей пользователю работает при упрощеннном интерфейсе. Хотя, эту проверку можно отключить
4. wing 29 06.09.22 15:02 Сейчас в теме
В запросе еще надо убрать условие "И ПрофилиГруппДоступа.ЭтоГруппа". Иначе будут создаваться дубли вместо обновления.
ixijixi; WellMaster; +2 Ответить
6. WellMaster 104 04.10.23 17:31 Сейчас в теме
(4) Автор так и не удосужился исправить это в статье. Зашел оставить коммент об ошибке, но прочитал ваш.
Оставьте свое сообщение