docs.php
См. документацию.
1 <?php
2 
3 /**
4  * @file
5  * @brief описания в документации
6  *
7  */
8 
9 
10 
11 
12 
13 
14 
15  /**
16  * @mainpage Установка
17  *
18  * Системные требования:
19  *
20  * - <b>MariaDB >= 10.3</b>
21  * - <b>PHP >= 7.0</b>
22  * - <b>Composer >= 2.4</b>
23  * - <b>nodejs >= 16</b> (если планируете делать сборки фронтендов)
24  *
25  * Система пока не поставляется в сборке docker-compose вместе с настроенным http сервером и БД. На время пришлось отказаться от этой идеи, но в будущем она есть и будет реализована.
26  *
27  *
28  * @code
29  * git clone https://github.com/avtobys/wrong-mvc.git
30  * cd wrong-mvc/
31  * composer install
32  * npm install
33  * @endcode
34  *
35  * Разверните проект в своей структуре. Переменная <code>$_SERVER['DOCUMENT_ROOT']</code> должна указывать на каталог public_html/ именно он будет доступен по http, а
36  * всё что находится выше данного каталога по http недоступно.
37  *
38  * Если ваша структура не имеет каталогов public_html/ или структура проекта будет мешаться с другой,
39  * вы должны настроить свой nginx/apache2 сервер соответсвующим образом под данную структуру.
40  *
41  *
42  * @attention
43  * Меняя структуру своего DOCUMENT_ROOT в настройках nginx/apache2 учитывайте, что вам может потребоваться перенастроить в конфигах Web сервера также <a target="_blank" href="https://www.php.net/manual/ru/ini.core.php#ini.open-basedir"><b>open_basedir</b></a> директиву, для того
44  * чтобы php обрабатывал файлы на уровень выше каталога public_html/ вашего проекта, иначе включаемые файлы и классы не смогут быть выполнены и вы не запустите даже установщик. Поскольку open_basedir может быть ограничена у вас именно настройками apache2/nginx. Но скорее всего вам придётся наоборот "опускать" структуру на уровень ниже, создав дополнительный public_html каталог. Например в типичной isp manager конфигурации каталогов /var/www/username/data/www/example.com/{доступ по http} вам может понадобится поправить web конфиги на такую структуру /var/www/username/data/www/example.com/public_html/{доступ по http} чтобы не мешать файлы проекта с другими доменами пользователя username
45  *
46  * Весь необходимый роутинг реализован в uri-router.php Для apache .htaccess файл есть в архиве, а если у вас nginx то достаточно прописать минимальные реврайт директивы
47  * @code
48  * location / {
49  * try_files $uri $uri/ @handler;
50  *
51  * location ~ [^/]\.ph(p\d*|tml)$ {
52  * try_files /does_not_exists @php;
53  * }
54  *
55  * location ~* ^.+\.(jpg|jpeg|gif|png|svg|js|css|mp3|ogg|mpe?g|avi|zip|gz|bz2?|rar|swf)$ {
56  * expires max;
57  * }
58  * }
59  *
60  * location @handler {
61  * rewrite ^(.*) /?$1 last;
62  * }
63  * @endcode
64  *
65  *
66  * @b Подключение к БД
67  *
68  * Создайте базу данных и пользователя mysql
69  *
70  * @b Запуск
71  *
72  * Перейдите на главную страницу сайта и в окне установщика укажите доступы к БД и основного администратора системы
73  *
74  */
75 
76 
77 
78 
79 
80 
81 
82 
83 
84 /**
85  * @page castomization Frontend кастомизация
86  *
87  * В данной документации не отображена структура каталогов и файлов js/css сборки фронтенда.
88  * Для кастомизации и сборки, используйте gulpfile.mjs(в корневой директории проекта). Js/css исходники вы найдете в каталоге app/
89  *
90  * Сборка фронтенда в продакшн запускается командой
91  * @code
92  * gulp build
93  * @endcode
94  *
95  * Командой
96  * @code
97  * gulp
98  * @endcode
99  * на порту 3000 вы можете поднять локальный browser-sync сервер, в вашем браузере откроется целый "комбайн" с несколькими панелями, предназначенный
100  * для bootstrap вёрски и кастомизации.
101  *
102  * Различные UI элементы, иконки fontawesome, чистая страница, заметки.
103  *
104  * <a data-fancybox="true" href="/assets/system/img/layout.png"><img style="max-width:100%;width:600px;" src="/assets/system/img/layout.png"></a>
105  *
106  * изменения в scss прослушиваются и тут же применяются без перезагрузки, верстайте в своё удовольствие и на свой вкус - всё с системой вы делаете на свой страх и риск 😉
107  *
108  * @attention
109  * Учитывайте что при сборке очищаются следующие каталоги указанные в gulpfile.mjs
110  *
111  * @code
112  * /public_html/assets/system/css/
113  * /public_html/assets/system/js/
114  * /public_html/assets/system/img/
115  * @endcode
116  *
117  * В указанные каталоги командой сборки автоматически собираются файлы из каталогов
118  *
119  * @code
120  * /app/scss/ => /public_html/assets/system/css/
121  * /app/js/ => /public_html/assets/system/js/
122  * /app/img/ => /public_html/assets/system/img/
123  * @endcode
124  *
125  * @note
126  * На данном примере показаны логические каталоги <b>assets/system</b> для системного фронтенда, любые свои фронтенд сборки для сайтов-проектов вы можете
127  * собирать в другой каталог, и это будет логично <b>assets/my-frontend-dirname</b>. Для этого поправьте пути в <b>gulpfile.mjs</b>
128  *
129  * Вы можете собирать любые свои кастомизированные бустраповские фронтенд css/js сборки для ваших проектов работающих вне админ панели.
130  * Картинки при сборке сжимаются, файлы минифицируются, у минифицированных версий удаляются комментарии.
131  *
132  * @note
133  * Для динамического вызова модальных окон и действий из бекенда по триггерам, вы должны включить в свою сборку <b>app/js/main.js</b>
134  * Там нет лишнего функционала админ панели и модулей, всё это специально вынесено в отдельные js файлы.
135  *
136  * <b>app/js/main.js</b> импортирует в единую сборку только jQuery v3.6.0, js модули Bootstrap v4.6.1 и js файл взаимодействия с бекендом по триггерам - <b>app/js/import/wrong.js</b>
137  *
138  * Все остальные библиотеки, js, css вы подключаете по мере необходимости для вашего конкретного проекта и задач.
139  * Ваша локальная среда для сборки фронтенда - это каталог <b>app/</b> и команды gulp.
140  *
141  * В разделе <a target="_blank" href="/docs/layout.html"><b>шаблонизация, своя вёрстка, стили</b></a> подробно показаны способы подключения
142  * стилей и скриптов в шаблонах ваших страниц, таких способов несколько.
143  */
144 
145 
146 
147 
148 
149 
150 
151  /**
152  *
153  * @page settings Настройки, переменные среды
154  *
155  * Чтобы увидеть все переменные среды приложения вызовите
156  * @code
157  * dd(Wrong\Start\Env::$e);
158  * @endcode
159  * Есть хранимые переменные среды, которые сохраняются при установке в файле .env а также есть хранимые в бд переменные.
160  * И есть динамические переменные среды, к которым относятся ip пользователя и его CSRF токен
161  *
162  * Хост, ip сервера и порт автоматически обновляются скриптом в .env в случае их изменения. К хранимым переменным также относятся переменные настроек системы,
163  * которые вы задаёте в админ панели, данные переменные подтягиваются из бд, после её подключения. Прочие переменные добавляются динамически.
164  *
165  * Установка дополнительных переменных среды происходит в файле include/session.php
166  *
167  * За переменные среды отвечает класс Start/Env.php
168  *
169  * В окне настроек всё интуитивно понятно, и вряд ли требует дополнительных пояснений. Обратите внимание, основной администратор системы, состоит в группе <b>Система</b>,
170  * но при этом данная группа не является подчинённой ему, она не подчинена никому.
171  * Это исключение, в остальных случаях, группы в которых вы состоите являются вам подчиненными.
172  * Поэтому при включении настройки "отображать только модели подчиненных групп", все группы принадлежащие группе Система будут скрыты у всех.
173  *
174  * <a data-fancybox="true" href="/assets/system/img/settings.png"><img style="max-width:100%;" src="/assets/system/img/settings.png"></a>
175  *
176  *
177  */
178 
179 
180 
181 
182 
183 
184 
185 
186  /**
187  * @page groups_users Группы и пользователи
188  *
189  *
190  * <b>Иерархия прав доступа и действий над моделями</b> в системе реализована посредством групп и пользователей. Пользователь может состоять одновременно в различных группах.
191  * У каждой группы есть <a href="/docs/weight.html" target="_blank" rel="noopener noreferrer"><b>системный вес</b></a>, определяющий приоритет прав одной группы над другой. Если пользователь состоит в группах с различным весом, при расчете его приоритета
192  * над другим пользователем, либо имуществом другой группы(моделями), рассчитывается максимальный вес группы в которых он состоит.
193  *
194  * <b>Доступность модели(чтение)</b> пользователю оределяется наличием группы в которых состоит пользователь в группах доступа модели, либо же одна из групп в которых состоит пользователь является владельцем модели. Модели отключенных групп недоступны.
195  *
196  * <b>Возможность действий над моделью(запись)</b> определяется принадлежностью пользователя к группе владельцу модели или приоритетом
197  * максимального системного веса групп пользователя над группой владельцем модели. При этом функционал для данных действий должен быть тоже доступен.
198  *
199  * Есть 2 особых группы пользователей, неявная группа <b>Гости</b> - условно id 0, это неавторизованные пользователи. Данной группы нет в таблице групп, поскольку она бесправная(системный вес = 0) и для неё настраивать нечего, но она есть в системе.
200  * А также есть особая группа <b>Система</b> id 1, с максимальным весом, в которой состоит основной администратор системы и которой принадлежат все системные модели.
201  *
202  * <b>Моделями</b> в системе является всё что имеет группу владельца, это:
203  * - Группы пользователей
204  * - Пользователи
205  * - Шаблоны
206  * - Страницы
207  * - Выборки
208  * - Модальные окна
209  * - Действия
210  * - Cron задачи
211  *
212  * У моделей типа <b>Страницы, Выборки, Модальные окна, Действия</b> помимо группы владельца, есть группы доступа - это группы которым такая модель доступна по http через контроллер include/uri-router.php
213  * т.е. пользователи состоящие в указанных группах доступа модели могут её вызывать. Но они не могут произоводить действия надо этой моделью, включать/отключать её, изменять её группы, владельцев, переименовывать.
214  *
215  * Для модели типа <b>Шаблоны</b> группы доступа - это те группы, которым данный шаблон будет доступен при создании моделей, а также использование и встраивание таких шаблонов.
216  *
217  * Для модели типа <b>Пользователи</b> группы доступа - это те группы в которых состоит пользователь.
218  *
219  * Для группы владельца модели его модель всегда доступна, даже если она отключена.
220  *
221  * Модели принадлежащие особой группе <b>Система</b> защищены от удаления и переименования, основному администратору системы(id 1) доступна лишь часть действий над ними,
222  * несмотря на то, что администатор сам состоит в группе <b>Система</b>.
223  * Он может лишь включать/отключать системные модели, копировать, экспортировать, импортировать их, и менять у них группы доступа.
224  * Это исключение из общей логики работы с моделями владельцами которых вы являетесь.
225  * @attention
226  * Группы доступа у системных моделей следует менять с осторожностью, а также с осторожностью отключать их. Например, если вы отключите функционал отвечающий за авторизацию,
227  * то не сможете войти в систему, и вам придётся включать его через бд - есть поле act у каждой модели, отвечающее за это. Впрочем, перед каждым подобным действием, вам
228  * будет показано предупреждение.
229  *
230  * <b>Владельцы моделей</b> - это группа владелец которым принадлежит модель.
231  * Владелец может делать со своей группой всё что угодно - удалять, или менять любые её свойства - переназначать группу владельца(из числа подчиненных групп), изменять для модели группы доступа,
232  * переименовывать файлы обработчики(они автоматически будут перемещены в фс и созданы каталоги под них),
233  * переименовывать request http запросы, по которым доступна модель, включать и отключать её.
234  *
235  * <b>Подчиненные группы</b> - это группы в которых состоит пользователь и группы с меньшим системным весом, чем максимальный вес групп в которых состоит пользователь.
236  * С моделями, принадлежащими подчиненным группам, пользователь также может делать всё что угодно, если у него включен и доступен соответствующий функционал моделей для этого.
237  *
238  * Например действие удаление - api/action/system/global/rm.php(модель /api/action/rm) наша группа пользователя должна быть включена в группы доступа у модели rm,
239  * если мы хотим удалить какую либо модель подчиненной или своей группы.
240  *
241  * Для группы может быть назначен лимит моделей, более которого ей не может быть создано/назначено/копировано/импортировано принадлежащих моделей. По умолчанию лимит - 0, это безлимит.
242  *
243  * При добавлении новой группы, чтобы не перебирать затем каждую модель, её можно автоматически включить в группы доступа всех моделей у которых назначен доступ для "всех".
244  * А также новую группу можно автоматически включить в доступы всем моделям доступным самому владельцу новой группы.
245  *
246  * Если <b>отключить группу</b>, скрипт ведет себя так, будто этой группы у пользователя не существует. Если <b>отключить пользователя</b>, он во всех моделях получает ошибку(страницу, если эта страница) 403 - доступ запрещен.
247  *
248  * <b>Включенные модели</b> доступны включенным группам доступа пользователей и группе владельцу.
249  *
250  * <b>Отключенные модели</b> доступны только группе владельцу.
251  *
252  * <b>Выключение модели</b> делает её недоступной всем кроме группы владельца.
253  *
254  * <b>Модели выключенных групп</b> недоступны на чтение.
255  *
256  * <b>Выключение группы</b> пользователей исключает её из групп пользователя(временно, виртуально), т.е пользователь просто лишается всех прав выключенной группы.
257  *
258  * <b>Выключение пользователя</b> отключает ему весь функционал на всех моделях. Доступна становится лишь одна страница - 403.
259  *
260  * <b>Изменять свойства модели</b> могут группы владельцы и старшие по системному весу группы(имеющие доступ к функционалу изменения этих свойств).
261  */
262 
263 
264 
265 
266 
267 
268 
269 
270  /**
271  * @page weight Системный вес
272  *
273  * <b>Системный вес группы</b> определяет приоритеты(права действий) одних групп пользователей над другими в системе. Пользователь может состоять одновременно в разных группах
274  * с различным весом, при этом берется максимальный вес этих групп при расчете приоритетов над другим пользователем(его моделью).
275  *
276  * <b>Системный вес пользователя</b> - максимальный вес из активных(включенных) групп в которых он находится.
277  *
278  * <b>Подчиненными группами</b> пользователя являются все группы в которых он состоит, и группы с меньшим весом чем максимальный вес тех групп, в которых состоит пользователь.
279  * Исключение - группа <b>Система</b>, данная группа не подчинена никому, и группа <b>Гости</b>. Модели группы <b>Система</b> защищены таким образом, а группа <b>Гости</b> не может являться владельцем
280  * каких либо моделей, и может находиться лишь в группах доступа.
281  *
282  * Над моделями подчиненных групп можно производить любые доступные действия. Владельцами моделей можно назначать только подчиненные группы.
283  *
284  * Например: в группе <b>Demo</b> администраторы, включены и работают практически все модели-действия, включая удаление, изменение, создание моделей.
285  * Но при создании/изменении модели от группы <b>Demo</b> вы не сможете назначить ей владельца <b>Администраторы</b>.
286  * Вы не можете удалять модели принадлежащие <b>Администраторам</b>. Вы не можете изменять их любые свойства. Всё это вы можете делать только над моделями подчиненных
287  * вам по системному весу групп.
288  *
289  * <a data-fancybox="true" href="/assets/system/img/weight.png"><img style="max-width:100%;" src="/assets/system/img/weight.png"></a>
290  */
291 
292 
293  /**
294  * @page access Проверка прав доступов пользователя
295  *
296  * В методе access() класса User.php собирается класс Access.php реализующий программную проверку прав доступа пользователя к моделям.
297  * Как уже сказано в разделе <a target="_blank" href="/docs/groups_users.html"><b>группы и пользователи</b></a> таких типов доступов всего 2:
298  *
299  * - <b>Доступность модели(чтение)</b>
300  * - <b>Возможность действий над моделью(запись)</b>
301  *
302  * Модели можно проверять на чтение и запись передавая в метод проверки аргумент $row - объект строки модели. На чтение(доступность) модели можно проверять
303  * по аргументам её request запроса или по её id
304  *
305  * @htmlonly
306  * <div id="jq-load-1"><script>$("#jq-load-1").load("/docs/class_wrong_1_1_auth_1_1_user.html h2:contains('access()')");</script></div>
307  * <div id="jq-load-2"><script>$("#jq-load-2").load("/docs/class_wrong_1_1_auth_1_1_user.html h2:contains('access()')+div");</script></div>
308  * @endhtmlonly
309  *
310  *
311  *
312  */
313 
314 
315 
316 
317 
318  /**
319  * @page from_uid Вход из под другого пользователя
320  *
321  * Пользователи(группы), которым доступно действие from-user.php могут входить в систему из под аккаунтов других пользователей, которые входят в
322  * <a target="_blank" href="/docs/weight.html"><b>подчиненные по системному весу группы</b></a>
323  *
324  * Для входа из под другого пользователя наведите курсор и нажмите на id пользователя
325  *
326  * <a data-fancybox="gallery" href="/assets/system/img/from-uid-1.png"><img style="max-width:100%;width:600px;" src="/assets/system/img/from-uid-1.png"></a>
327  *
328  * Если у вас достаточно прав для действия, вы зайдете под чужой авторизацией, при этом время онлайна, ip, и крайний request запрос фиксироваться
329  * от вашего присутствия не будут. Вы будете действовать полностью только с правами данного пользователя, но сможете вернуться в свой аккаунт.
330  *
331  * <a data-fancybox="gallery" href="/assets/system/img/from-uid-2.png"><img style="max-width:100%;width:600px;" src="/assets/system/img/from-uid-2.png"></a>
332  *
333  * Для того чтобы зафиксировать действия при разборе "полётов" воспользуйтесь <a target="_blank" href="/docs/logs.html"><b>записью логов</b></a>, там можно будет найти,
334  * кто когда куда входил, с каким ip и что делал.
335  *
336  * @note
337  * В таблице моделей, у пользователей, которые не входят в подчиненные группы скрыты звездочками конфиденциальные данные такие как ip, X-Auth-Token, email
338  *
339  */
340 
341 
342 
343 
344 
345 
346  /**
347  * @page models Модели(компоненты)
348  *
349  * @section groups Группы
350  *
351  * <a data-fancybox="true" href="/assets/system/img/groups.png"><img style="width:600px;max-width:100%;" src="/assets/system/img/groups.png"></a>
352  *
353  * Предусмотрено добавление практически неограниченного числа групп, с любой иерархией и системным весом. У каждой группы, как и у любой модели есть группа владелец.
354  * Вы можете назначать любого владельца из числа <a target="_blank" href="/docs/weight.html"><b>подчиненных вам по системному весу групп</b></a>.
355  * И устанавливать группе любой системный вес меньше вашего собственного <a target="_blank" href="/docs/weight.html"><b>максимального системного веса</b></a>.
356  *
357  * Группам назначается лимит моделей(0 - безлимит) и каталог группы по умолчанию в файловой системе при создании файлов обработчиков моделей. При удалении группы или
358  * её очистке от моделей удаляются все файлы обработчики и модели принадлжежащие данной группе. Если файл обработчик указан так же в моделях других владельцев - такой файл удалён не будет.
359  *
360  * Подчинённые вам группы можно отключать. Если группа отключена и пользователь состоит в данной группе, система ведет себя так будто данной группы не существует, т.е. просто
361  * игнорирует наличие пользователя в группе. При этом все модели становятся недоступными на чтение.
362  *
363  * Пользователям групп можно массово добавлять другие группы и массово исключать пользователей из групп. Посредством групп вы можете
364  * <a target="_blank" href="/docs/related_functionality.html"><b>управлять связанным функционалом</b></a> целиком, а не только по отдельным компонентам(моделям).
365  *
366  * Для групп включается и отключается запись логов действий.
367  *
368  * @section users Пользователи
369  *
370  * <a data-fancybox="true" href="/assets/system/img/users.png"><img style="width:600px;max-width:100%;" src="/assets/system/img/users.png"></a>
371  *
372  * При добавлении пользователя из админ панели вы можете назначать его группы доступа и владельца только из числа подчинённых вам групп. Вы видите конфиденциальные
373  * данные(ip, x-oauth-token, email) только подчинённых вам по системному весу пользователей. У каждого пользователя отключается x-oauth-token авторизация -
374  * от него не будут работать api запросы и соответсвенно <a target="_blank" href="/docs/cron.html"><b>cron задачи</b></a> выполняющиеся от его аккаунта.
375  *
376  * Вы можете входить под сессией подчиненных вам пользователей, кликнув на его id в таблице. В таблице пользователей выводятся не все данные, часть из них скрыта по умолчанию, для
377  * экономии места. Это изменяется в верхней панели, над таблицей. Есть кнопочка включающая асинхронное автообновление таблицы. Чтобы наблюдать за активностью пользователей и
378  * страниц на которых они находятся, воспользуйтесь этим и соответсвующими сортировками, поиском, фильтрами - все эти состояния сохраняются при перезагрузках в любых таблицах моделей.
379  *
380  * Отключение пользователя выключает для него любой функционал и страницы, он везде получает ошибку/страницу 403. Отключать можно только подчинённых пользователей, как и любые
381  * действия производимые с изменением свойств любой модели.
382  *
383  * @section templates Шаблоны
384  *
385  * <a data-fancybox="true" href="/assets/system/img/templates.png"><img style="width:600px;max-width:100%;" src="/assets/system/img/templates.png"></a>
386  *
387  * При создании шаблона пользователь может устанавливать любые группы доступа. Группы доступа шаблона - это те группы, которым будет доступен данный шаблон
388  * на чтение(может быть встроен - использоваться). Группу владельца, как и в любой модели, можно устанавливать только из числа подчинённых групп.
389  *
390  * Можно добавлять неограниченное количество шаблонов, с различными вёрстками, различных типов и с различными доступами.
391  *
392  * <a target="_blank" href="/docs/layout.html"><b>Более подробно о шаблонах и их типах</b></a>
393  *
394  * Все доступные шаблоны находятся и создаются в каталоге <a href="/docs/dir_096b0de097b3e98f91191e9d894d4363.html"><b>templates/</b></a>
395  *
396  * @section pages Страницы
397  *
398  * <a data-fancybox="true" href="/assets/system/img/page.png"><img style="max-width:100%;width:600px;" src="/assets/system/img/page.png"></a>
399  *
400  * При добавлении страниц вы можете указывать абсолютно любые группы доступа, в том числе с правами выше ваших. Даже если у вас будут пересекаться группы и у пользователей
401  * уже будут страницы с такими request uri, это никак не навредит им, поскольку эта <a target="_blank" href="/docs/routing.html"><b>логика разрешится uri роутером</b></a>.
402  *
403  * Группа владелец указывается только из числа подчиненных вам групп(на всех моделях!).
404  *
405  * У различных страниц может быть указан один и тот же файл обработчик, и разные шаблоны, или наоборот, здесь вариаций - ваша фантазия.
406  * Отключение страницы делает её недоступной всем(403), кроме владельца. На доступных страницах линки на отключённые страницы(как и триггеры модалок и действий),
407  * автоматически скрываются средствами внедряющегося css - им добавляется правило display:none!important; За это отвечает Html/Hideout.php метод hide
408  * вызывающийся в include/session.php по завершении работы скрипта.
409  *
410  * @note
411  * Можно создавать страницы, шаблоны и спокойно натягивать например новую вёрстку, прямо на продакшене, а затем только останется переключить модели.
412  *
413  * Любые страницы можно кешировать, указывая количество секунд актальности кеша. Ключем кеша страницы будет полный REQUEST_URI запроса(включая строку запроса).
414  *
415  *
416  * @section selects Выборки
417  *
418  * <a data-fancybox="true" href="/assets/system/img/selects.png"><img style="max-width:100%;width:600px;" src="/assets/system/img/selects.png"></a>
419  *
420  * Работа api выборок по логике аналогична моделям страниц. Единственное отличие, запрос к выборке должен начинаться с /api/select/your-path/...
421  *
422  * В обработчиках можно указывать любую свою логику. В контексте системных моделей - это json ответы плагину Datatables, в вашем случае это наверняка будет иная логика.
423  *
424  * Любые выборки можно кешировать, указывая количество секунд актальности кеша. Ключем кеша выборки будет полный REQUEST_URI запроса(включая строку запроса).
425  *
426  * @section modals Модальные окна
427  *
428  * <a data-fancybox="true" href="/assets/system/img/modals.png"><img style="max-width:100%;width:600px;" src="/assets/system/img/modals.png"></a>
429  *
430  * При добавлении модели модального окна логика групп доступа и владельца, та же что и при добавлении страниц/выборок.
431  * Также указывается шаблон модалки, из числа доступных данной группе шаблонов. Дополнительно можно указать флажок создать действие, вместе
432  * с модальным окном будет создана модель действия с теми же доступами. При этом, если добавляется окно с формой(а именно для этого обычно они и связываются логикой),
433  * то в форме модального окна будет автоматически установлен атрибут action соответствующий request uri созданного действия.
434  *
435  * <a data-fancybox="true" href="/assets/system/img/action.png"><img style="max-width:100%;" src="/assets/system/img/action.png"></a>
436  *
437  * В модальном окне с формой по умолчанию так же идёт универсальный ajax обработчик реализующий submit и отправку формы, и её закрытие с перезагрузкой Datatable.
438  * Всё ненужное вы можете легко убрать или добавить свою логику в данный обработчик. При отправке формы она и вся страница автоматически блокируются прелоадерами -
439  * не беспокойтесь за повторные отправки, здесь всё предусмотрено!
440  *
441  * <a data-fancybox="true" href="/assets/system/img/submit.png"><img style="max-width:100%;" src="/assets/system/img/submit.png"></a>
442  *
443  * Поскольку код модальных окон уничтожается из DOM при их закрытии, и добавляется вновь при вызове, мы можем вешать в нём каждый раз обработчики типа:
444  *
445  * @code
446  * $("#<?= $basename ?> .classname").on("click", function() {
447  * console.log("Hello");
448  * });
449  * @endcode
450  *
451  * не беспокоясь о том что он будет вызван дважды, при повторном вызове окна.
452  *
453  * @attention
454  * Не стоит использовать в модальном окне обработчики типа:
455  *
456  * @code
457  * $(document).on("click", "#<?= $basename ?> .classname", function() {
458  * console.log("Hello");
459  * });
460  * @endcode
461  *
462  * Такой обработчик будет повешен на document, а не на элемент, каждый раз при вызове окна, и соответственно при клике на элементе .classname может выполниться
463  * несколько раз(сколько раз вызывалась форма). Для отключения такого поведения используйте метод jquery .off() Но лучше вешать обработчики, как сказал выше,
464  * непосредственно на элемент.
465  *
466  * Для модальных окон предусмотрен удобный <a target="_blank" href="/docs/triggers.html"><b>конструктор кнопок - триггеров</b></a>
467  *
468  * Не обязательно создавать модели-компоненты модальных окон с вызовом их через api, для этого можно воспользоваться шаблонами и встроить их
469  * в любую страницу по принципу incode шаблонов, наделив данные шаблоны нужными правами. А если не требуются ограничения прав доступа,
470  * то можно встраивать и вызывать окна напрямую стандартным способом. Окна, которые изначально присутствовали в DOM не будут уничтожены при закрытии, и даже более
471  * того, окна которые вы вызвали из api, их тоже можно не уничтожать из DOM после закрытия. Для такого поведения им нужно добавить атрибут data-noremove.
472  *
473  * @section actions Действия
474  *
475  * <a data-fancybox="true" href="/assets/system/img/actions.png"><img style="max-width:100%;width:600px;" src="/assets/system/img/actions.png"></a>
476  *
477  * Логика добавления обработчиков действий аналогична модальным окнам относительно доступов. Дополнительно можно указать флажок создать модальное окно, вместе
478  * с действием будет создана модель модального окна, причем для него будет автоматически выбран шаблон с формой и в action атрибуте установлен
479  * соответствующий request uri созданного действия.
480  *
481  * <a data-fancybox="true" href="/assets/system/img/action.png"><img style="max-width:100%;" src="/assets/system/img/action.png"></a>
482  *
483  * Логику, предусматривающую модели форма в модалке + действие, лучше создавать именно так. Рекомендуется идентично
484  * <a target="_blank" href="/docs/userlandnaming.html"><b>именовать uri запросы модальных окон и действий</b></a>, хотя это и не обязательно.
485  *
486  * @warning
487  * При назначении групп доступа действиям, следует быть более внимательным(кому и что позволяем), чем при назначении групп доступа другим моделям,
488  * потому что действия иногда приводят к необратимым последствиям, нежели просто визуализация(выборки, модалки, странички).
489  *
490  * @section crontabs Задачи
491  *
492  * <a data-fancybox="gallery" href="/assets/system/img/cron.png"><img style="max-width:100%;width:600px;" src="/assets/system/img/cron.png"></a>
493  *
494  * Встроенные cron задачи позволяют реализовать практически любую логику изменения состояний системных моделей по расписанию - включение, отключение, замена,
495  * удаление, очистка, смена шаблонов с вёрсткой, страниц, uri.
496  *
497  * Синтаксис расписаний такой же как в Linux. В остальном это такая же модель как и остальные, имеющая свою группу владельца, вы не сможете поставить задачу
498  * от не подчиненного вам по системному весу пользователя.
499  *
500  * <a target="_blank" href="/docs/cron.html"><b>Более подробно о встроенном cron здесь</b></a>
501  *
502  *
503  */
504 
505 
506 
507  /**
508  * @page related_functionality Управление связанным функционалом
509  *
510  * Предположим у вас есть некоторый функционал, который состоит не из одного компонента(модели), а включает в себя их некое множество. И им необходимо
511  * эффективно управлять целиком(пакетно).
512  *
513  * Например, у вас есть функционал "оформление заявок", который включает в себя определённые incode(встраиваемые) шаблоны, формы/действия, модальные окна, страницы.
514  * И вам нужно иметь возможность отключать и выключать целиком весь этот связанный функционал, а не по отдельности.
515  * Для этого создайте группу "оформление заявок" и свяжите группы доступа всех моделей лишь с данным функционалом(группой). И вы сможете управлять им с кнопки включения
516  * и отключения группы.
517  *
518  * Выключение группы делает все её модели недоступными на чтение и виртуально исключает из неё пользователей. Распределяя связанный функционал по группам вы можете
519  * управлять его доступностью пакетно.
520  *
521  * Кроме этого, в управлении группами вы можете массово добавлять пользователей определённых групп в другие группы и массово исключать их из указанных групп.
522  * Соответственно вы сможете легко одномоментно "вводить" функционал для опредёленных групп, или "забирать" функционал у определённых групп пользователей.
523  *
524  *
525  * <a data-fancybox="true" href="/assets/system/img/related_functionality.png"><img style="max-width:100%;width:600px;" src="/assets/system/img/related_functionality.png"></a>
526  *
527  * Помимо этого, вы можете отключать и шаблоны, отключая например пакетно все страницы использующие данный шаблон.
528  *
529  */
530 
531 
532 
533 
534 
535  /**
536  * @page userlandnaming Руководство по именованию
537  *
538  * Именование request для запросов модальных окон должно иметь следующий вид:
539  * @code
540  * /api/modal/my-modal
541  * @endcode
542  *
543  * Именование request для запросов действий может иметь любую вложенность, но должно начинаться с
544  * @code
545  * /api/action/.../my-action
546  * @endcode
547  *
548  * @note
549  * Хорошей практикой будет давать одни имена модальным окнам и обработчикам их форм.
550  *
551  *
552  * Именование request для запросов выборок может иметь любую вложенность, но должно начинаться с
553  * @code
554  * /api/select/.../my-select
555  * @endcode
556  *
557  * Именование request для страниц может иметь любую вложенность либо не иметь её вовсе.
558  * @code
559  * /...
560  * @endcode
561  *
562  *
563  * Для моделей с динамическими uri используйте всего одну запись в таблице моделей и один request и дописывайте необходимую php логику в include/uri-router.php
564  * В данном файле есть закомментированный пример реализации. Для контента используйте свою отдельную таблицу страниц. Назвать динамический запрос вы можете как угодно:
565  * @code
566  * /request-dinamic-model-name
567  * @endcode
568  *
569  * @warning
570  * Будет плохой практикой создавать отдельную модель <b>Страница</b> под каждую динамическую страницу вашего сайта!
571  *
572  * <a href="/docs/dinamic.html"><b>Более подробно о динамических страницах</b></a>
573  *
574  * Именование файлов обработчиков для действий, модальных окон, выборок и страниц
575  * @code
576  * /api/action/group-path/.../my-action.php
577  * /api/modal/group-path/.../my-action.php
578  * /api/select/group-path/.../my-select.php
579  * /api/page/group-path/.../my-page.php
580  * @endcode
581  * где group-path это один из доступных каталогов групп к которым принадлежит ваш аккаунт, далее может быть(или отсутствовать) любая вложенность
582  *
583  *
584  * @note
585  * Хорошей практикой будет объединять обработчики в дополнительные каталоги по смыслу архитектуры логики вашего проекта.
586  * Например, <a href="/docs/dir_1a6228b500bc1e8329862059492cfac8.html"><b>системные обработчики действий</b></a> и <a href="/docs/dir_3cbab2261c191b72730af3bc6a348131.html"><b>модальных окон</b></a>, ниже system каталога имеют дополнительную вложенность - эта разбивка по смыслу в чисто информативных целях
587  */
588 
589 
590 
591 
592 
593 
594 
595 
596 
597 
598  /**
599  * @page dinamic Динамические модели страниц
600  *
601  * Для моделей с динамическими uri используйте одну запись в таблице моделей и один request и дописывайте необходимую php логику в include/uri-router.php
602  *
603  * А для контента используйте свою отдельную таблицу страниц. Например создаем модель с request:
604  * @code
605  * /request-dinamic-model-name
606  * @endcode
607  *
608  *
609  * пример запроса к динамическим страницам
610  *
611  * - my-categories - таблица в бд с вашими динамическими категориями
612  * - my-pages - таблица в бд с вашим контентом динамических страниц
613  * - url - поля в бд ваших категорий и страниц для формирования запросов
614  *
615  * - /request-dinamic-model-name - ваша модель страницы (укажем только 1 уникальный request для неё и 1 обработчик)
616  *
617  * - $data_page - данные вашей страницы, которые будут доступны в контексте её файла
618  * - /any-category-url/any-page-url - запросы по которым будет доступна ваша динамическая модель
619  *
620  * @code
621  * $rx = "#^/(" . implode('|', array_column(Wrong\Database\Controller::all('', 'id', 'my-categories'), 'url')) . ")/([^/]+)$#";
622  * if (
623  * preg_match($rx, $request, $matches) &&
624  * ($data_page = Wrong\Database\Controller::find($matches[2], 'url', 'my-pages')) && ($arr = Wrong\Models\Pages::all('/request-dinamic-model-name', 'request'))
625  * ) {
626  * $arr = Wrong\Rights\Group::weightSort($arr);
627  * $arr = array_filter($arr, function ($row) use ($user) {
628  * return $user->access()->read($row);
629  * });
630  * if (!$arr) {
631  * $request = '/forbidden';
632  * goto routing_start;
633  * }
634  * foreach ($arr as $row) {
635  * if (file_exists($_SERVER['DOCUMENT_ROOT'] . $row->file)) {
636  * if (
637  * ($template = Wrong\Models\Templates::find($row->template_id)) &&
638  * $user->access()->read($template) &&
639  * file_exists($_SERVER['DOCUMENT_ROOT'] . $template->file)
640  * ) { // шаблон доступен
641  * require $_SERVER['DOCUMENT_ROOT'] . $template->file;
642  * } else if ($request != '/forbidden') { // шаблон недоступен - 403
643  * $request = '/forbidden';
644  * goto routing_start;
645  * }
646  * $user->set_request($request);
647  * exit;
648  * }
649  * }
650  * }
651  * @endcode
652  *
653  *
654  * @warning
655  * Будет плохой практикой создавать отдельную модель <b>Страница</b> под каждую динамическую страницу вашего сайта! Используйте код выше.
656  */
657 
658 
659 
660 
661 
662 
663 
664 
665 
666 
667 
668  /**
669  * @page routing Роутинг, контроллеры URI
670  *
671  * Исходя из логики описанной в разделе <a href="/docs/groups_users.html"><b>Группы и пользователи</b></a> доступ по http к модели возможен в совокупности следующих свойств:
672  *
673  * - Модель должна быть включена;
674  * - Одна из активных групп пользователя должна входить в группы доступа модели;
675  *
676  * или же:
677  *
678  * - Модель может быть выключена;
679  * - Одна из активных групп пользователя является владельцем данной модели;
680  *
681  *
682  * Роутинг запросов к request моделей учитывает <a href="/docs/weight.html"><b>системный вес пользователя</b></a> и сортирует модели с одинаковыми request на основе наибольшего минимального системного веса групп доступа моделей.
683  *
684  * <a data-fancybox="gallery" href="/assets/system/img/uri.png"><img style="max-width:100%;" src="/assets/system/img/uri.png"></a>
685  *
686  * Иными словами, контроллер может отдавать модели с одинаковым REQUEST_URI, но с разными файлами обработчиками и контентом в них, разными группами доступов и владельцев моделей.
687  * При этом пользователи могут находится в одних и тех же пересекаемых группах. Например пользователь 1 состоит в группе "администраторы" и "пользователи", а пользователь 2 состоит в группе "пользователи".
688  * Вес групп пользователя 1 больше. И есть 2 разных модели типа "страница" с одинаковым REQUEST_URI.
689  * Но для пользователя 1 доступны обе страницы, а для пользователя 2 только одна. В результате пользователям будут отданы разные страницы,
690  * т.к. пользователю 1 будет отдана страница наиболее подходящая по его весу групп.
691  *
692  *
693  * Разберем наглядный пример из самых простейших вариантов:
694  *
695  * <a data-fancybox="gallery" href="/assets/system/img/example-routing.png"><img style="max-width:100%;" src="/assets/system/img/example-routing.png"></a>
696  *
697  * Минимальный системный вес групп доступа у модели запроса id 1 = 0. А что если так:
698  *
699  * <a data-fancybox="gallery" href="/assets/system/img/example-routing2.png"><img style="max-width:100%;" src="/assets/system/img/example-routing2.png"></a>
700  *
701  * Минимальный системный вес групп доступа для модели запроса id 1 по прежнему равен 0.
702  *
703  * Wrong\Rights\Group::weightSort($arr) осуществляет сортировку именно по этому параметру и приоритет для запроса в любом случае
704  * будет отдан именно модели нашей админ панели, даже несмотря на то, что мы включили группу Администраторы в группы доступа модели id 1:
705  *
706  * <a data-fancybox="gallery" href="/assets/system/img/example-routing3.png"><img style="max-width:100%;" src="/assets/system/img/example-routing3.png"></a>
707  *
708  * Поэтому в группы доступа своих моделей любой пользователь может назначать любые даже старшие по весу группы.
709  * Даже если у них есть модели с такими же request uri, им это никак не навредит.
710  * Это как в Linux вы назначили своему файлу права rwx-rwx-rwx(0777) - делайте что хотите, ваши проблемы, не хотите не делайте, колхоз дело добровольное.
711  *
712  * А теперь такой пример:
713  *
714  * <a data-fancybox="gallery" href="/assets/system/img/example-routing4.png"><img style="max-width:100%;" src="/assets/system/img/example-routing4.png"></a>
715  *
716  * Мы отключили модель id 2(главную админ панели), но для группы Система она по прежнему доступна, а вот для всех остальных уже нет.
717  * И разрешили доступ всем к главной id 1(не авторизованных). Проверим - зайдем из под Demo пользователя и убедимся:
718  *
719  * <a data-fancybox="gallery" href="/assets/system/img/example-routing5.png"><img style="max-width:100%;" src="/assets/system/img/example-routing5.png"></a>
720  *
721  * Demo группе доступна теперь только такая главная. Аналогичного эффекта можно добиться, если отключить группу Demo.
722  * Скрипт будет вести себя так, будто этой группы нет. И пользователи группы Demo, если они не состоят в других активных группах,
723  * станут Гостями с нулевыми правами не авторизованных.
724  *
725  * Выше лишь базовый пример работы роутинг контроллера. Возможности комбинаций групп доступа, владельцев моделей и системного веса,
726  * обработчиков и шаблонов на основе этих компонентов ограничены лишь вашей фантазией!
727  *
728  * @warning
729  * Фантазия должна ограничиваться здравым смыслом;)
730  *
731  * Тем не менее ваши первичные действия в новом проекте всегда будут начинаться с главной. Вы же не собираетесь оставлять текущую главную страницу wrong-mvc.
732  * Не стоит её изменять. Просто отключите её, но сначала создайте свою <a href="/docs/your_first_model.html"><b>первую модель - главную страницу вашего проекта</b></a>
733  */
734 
735 
736 
737 
738 
739 
740 /**
741  * @page your_first_model Ваша первая модель - главная
742  *
743  * Какими вероятнее всего будут ваши первичные действия при создании вашего нового проекта на базе wrong-mvc?
744  *
745  * Вам нужно создать свою главную страницу, со своим контентом и своим шаблоном, в котором будет подключен ваш дизайн, вёрстка и т.п. Верно?
746  *
747  * Для этого вы отключаете модель Страница id 1 - главная для не авторизованных. И создаёте свою любую модель главной с любым шаблоном и с запросом / с группой доступа <b>Гости</b>.
748  * Не забудьте включить свою главную, а то ваши пользователи увидят 403, ведь по умолчанию модели добавляются в выключенном состоянии. Вот и всё. Ваш сайт готов 😉
749  *
750  * <a data-fancybox="true" href="/assets/system/img/page.png"><img style="max-width:100%;width:600px;" src="/assets/system/img/page.png"></a>
751  *
752  * Конечно, вероятнее всего, главная - это будет уже ваша вторая модель. Первой моделью вы <a href="/docs/layout.html"><b>создадите шаблон</b></a> для неё и подключите в него <a href="/docs/castomization.html"><b>свои стили/кастомизированную сборку</b></a>.
753  *
754  * Входить в систему вы можете с отдельной страницы <a target="_blank" href="/enter"><b>/enter</b></a> или прописать в своём шаблоне вызовы соответствующих триггеров вызова модальных окон авторизации.
755  *
756  * Но вот вы вошли в раж, и погнали клепать разделы своего проекта, один за другим. А их модели схожи между собой(всего-то чуток поменять).
757  * Тут вам пригодится <a href="/docs/import_export.html"><b>копирование моделей</b></a>
758  *
759  */
760 
761 
762 
763 
764 
765 
766 
767  /**
768  * @page layout Шаблонизация, своя вёрстка, стили
769  *
770  * В системе предусмотрено 5 типов шаблонов, имеющие свою специфику и различные варианты добавления и встраивания.
771  *
772  * Шаблоны как и все остальные компоненты наследуют <a href="/docs/groups_users.html"><b>политики прав доступов</b></a>. Группы не имеющие прав доступа к шаблону не смогут его использовать. Если это page шаблон страницы, то все страницы с данным шаблоном будут недоступны(кроме владельца шаблона).
773  * Если это incode шаблон - он не будет встроен в страницу.
774  * Также, шаблон, к которому нет прав доступа, невозможно использовать при создании компонента(модели). Включая и выключая шаблоны - вы отключаете их доступность
775  * у всех кроме группы-владельца шаблона.
776  *
777  *
778  * Каждый тип разберем по отдельности и покажем, как использовать шаблоны различного типа.
779  *
780  * @section page Шаблоны типа: page
781  *
782  * Шаблоны данного типа - это основные(каркасные) шаблоны страниц, в которые автоматически встраивается контент(содержимое) страниц. Если можно так назвать - это wrapper или каркас шаблоны.
783  * То есть, вы <a href="/docs/models.html#pages"><b>создаете страницу</b></a>, с "голым" контентом-кодом страницы указывая для неё нужный шаблон.
784  * А код страницы уже автоматически встраивается в указанный для неё шаблон. Таким образом вы подтягиваете на страницу какой то общий каркас вёрстки,
785  * подключение стилей, скриптов и т.д.
786  *
787  * Есть несколько способов подключения стилей и скриптов, разберем их подробно.
788  * В моделях шаблоны создайте новый пустой шаблон для страниц своего сайта. По умолчанию ваш новый пустой шаблон уже содержит в head код подключения js и css системы:
789  *
790  * @htmlonly
791  * <div id="jq-load-1"><script>$("#jq-load-1").load("/docs/page_2empty_8php_source.html .contents");</script></div>
792  * @endhtmlonly
793  *
794  * давайте разберем его.
795  * - main.min.css - это кастомизированный Bootstrap css админ панели со сборкой иконок от fontawesome и кастомными стилями для всплывающих сообщений
796  * - main.min.js - это системная сборка jQuery v3.6.0 + js модули Bootstrap v4.6.1 + js файл взаимодействия с бекендом по триггерам - app/js/import/wrong.js
797  * - $row->name - это название страницы модели, которое автоматически подтянется из модели страницы для которой будет указан данный шаблон.
798  * - <?php require $CONTENT_PAGE_FILE; ?> - это код, который автоматически подтянется из модели страницы для которой будет указан данный шаблон.
799  *
800  * вместо main.min.css вы укажите свою css сборку, а файл main.min.js вы должны оставить как есть.
801  *
802  * Скачайте готовую или <a href="/docs/castomization.html"><b>соберите свою кастомную Bootstrap css сборку</b></a> и подключите её вместо main.min.css
803  *
804  * Вы можете создавать любое количество шаблонов с разными дизайнами и менять их как перчатки.
805  * Например, у вас есть шаблон новогодний, включите логи для своей группы, поменяйте у страницы шаблон и верните на место,
806  * <a target="_blank" href="/docs/logs.html"><b>посмотрите в логах данные</b></a>, которые были отправлены на обработчик.
807  * И просто автоматизируйте эти действия через <a href="/docs/cron.html"><b>встроенные cron задачи</b></a>, включая новогодний шаблон 10 декабря и выключая 20 января например.
808  *
809  * Все доступные шаблоны находятся и создаются в каталоге <a href="/docs/dir_096b0de097b3e98f91191e9d894d4363.html"><b>templates/</b></a>
810  *
811  * <b>Способ подключения 1:</b>
812  *
813  * Вы можете подключать в свои проекты как минифицированные js/css файлы так нет, при этом не минифицированные версии будут автоматически минифицированы 1 раз, а если изменится
814  * исходник, файл снова будет минифицирован. Например:
815  *
816  * Данный код размещенный в head любого php шаблона будет просто выводить содержимое файлов в html странице обрамленным в тегах <style> и <script>
817  *
818  * @code
819  * <?= Wrong\Html\Get::style($_SERVER['DOCUMENT_ROOT'] . '/assets/system/css/system-admin.min.css') ?>
820  * <?= Wrong\Html\Get::style($_SERVER['DOCUMENT_ROOT'] . '/assets/system/css/main.min.css') ?>
821  * <?= Wrong\Html\Get::script($_SERVER['DOCUMENT_ROOT'] . '/assets/system/js/main.min.js') ?>
822  * <?= Wrong\Html\Get::script($_SERVER['DOCUMENT_ROOT'] . '/assets/system/js/system-admin.min.js') ?>
823  * @endcode
824  *
825  * А данный код, автоматически минифицирует и создаст .min версии и будет выводить их содержимое в тегах <style> и <script>, а в случае изменения исходников, минифицирует файлы заново.
826  *
827  * @code
828  * <?= Wrong\Html\Get::style($_SERVER['DOCUMENT_ROOT'] . '/assets/system/css/system-admin.css') ?>
829  * <?= Wrong\Html\Get::style($_SERVER['DOCUMENT_ROOT'] . '/assets/system/css/main.css') ?>
830  * <?= Wrong\Html\Get::script($_SERVER['DOCUMENT_ROOT'] . '/assets/system/js/main.js') ?>
831  * <?= Wrong\Html\Get::script($_SERVER['DOCUMENT_ROOT'] . '/assets/system/js/system-admin.js') ?>
832  * @endcode
833  *
834  * @note
835  * Этот способ не всегда удобен, если вы не собираете стили, а просто помещаете их в каталог assets, в этом случае, есть ещё 2 способа подключения стилей и скриптов
836  *
837  * <b>Способ подключения 2:</b>
838  *
839  * @code
840  * <?= Wrong\Html\Get::stylesrc($_SERVER['DOCUMENT_ROOT'] . '/assets/system/css/system-admin.min.css') ?>
841  * <?= Wrong\Html\Get::scriptsrc($_SERVER['DOCUMENT_ROOT'] . '/assets/system/js/system-admin.js') ?>
842  * @endcode
843  *
844  * данный код, инжектит уже теги подключения с аттрибутами src(link и script), но помимо этого добавляет в строку запроса время модификации файла, что автоматически
845  * всегда сбросит кеш. Указанные файлы также минифицируются автоматически.
846  *
847  * Этот способ обеспечивает сохранение логики всех относительных путей указанных в файлах, в отличии от способа 1, когда содержимое css, js встраивается
848  * непосредственно в html код страницы.
849  *
850  * <b>Способ подключения 3 (самый крутой):</b>
851  *
852  * Вы скачали некий шаблон верстки и положили его например в /assets/examples/tivo-1.0.0/ у каждого шаблона своя логика размещения css, js, картинок.
853  * Неправда ли муторно это всё изменять и прописывать?
854  *
855  * В файле шаблона просто укажите "магическую" константу USE_ASSETS_PATH:
856  *
857  * @code
858  * const USE_ASSETS_PATH = '/assets/examples/tivo-1.0.0';
859  * @endcode
860  *
861  * и оставьте дурную работу - дуракам, в тегах script, img всё само автоматически подставится в атрибуты src, а в тегах link в атрибуты href, причем так же с временем модификации файла.
862  * Как вот в этом шаблоне example1.php А в шаблоне example2.php успешно обрабатываются даже background-image: url(); Кстати заметьте, там наша "магическая" константа указана несколько иначе,
863  * потому как относительные пути шаблона несколько иные, но это никак не влияет на результат. В константе вы просто указываете путь к каталогу с ресурсами шаблона!
864  *
865  * Посмотрите какая прелесть, клепай шаблоны - не хочу:
866  *
867  * <a data-fancybox="gallery" href="/assets/system/img/use_assets_path.png"><img style="max-width:100%;width:600px;" src="/assets/system/img/use_assets_path.png"></a>
868  *
869  *
870  * @note
871  * Иногда может понадобиться скрыть/показать только на определённой странице какой то элемент, но при этом у вас общий шаблон под все страницы. Воспользуйтесь атрибутами: <b>data-visible-page</b> или <b>data-hide-page</b>
872  *
873  * - data-visible-page="/example-request" элемент будет виден только на /example-request
874  * - data-hide-page="/example-request" элемент будет виден везде и скрыт только на /example-request
875  *
876  * пример использования <b>data-visible-page</b> в шаблоне tivo.php для ссылки PRICING, она показывается лишь на главной странице шаблона, а на остальных скрыта
877  *
878  * <b>Способ подключения 4 (асинхронный):</b>
879  *
880  * Любой css, js код плагина или библиотеки, вы можете подключить асинхронно, не сразу а по мере выполнения каких то действий на странице, для этого
881  * воспользуйтесь <a target="_blank" href="/docs/loadlibs.html"><b>функцией подгрузки библиотек</b></a>.
882  *
883  * @section incode Шаблоны типа: incode
884  *
885  * Каркасными шаблонами о которых сказано выше, не всегда удобно распределять более детальную логику блоков, особенно с учетом разграничения прав.
886  * Тут на помощь приходит тип встраивания и шаблоны - incode. Шаблон incode - это уже подключаемый шаблон непосредственно при помощи специального метода в абсолютно любом месте
887  * вашего приложения, будь то страницы, выборки, модальные окна или даже действия.
888  *
889  * Для php это просто встраиваемый кусок кода, но здесь дополнительно перед встраиванием осуществляются проверки прав доступа к шаблону -
890  * он не будет встроен для групп пользователей, у которых нет доступов на чтение модели данного шаблона. Т.е. код данного шаблона может содержать какие то блоки и любой код,
891  * доступные лишь определённым группам пользователей - определяются они группами доступа шаблона(а также определяемые включением и отключением самого шаблона)
892  *
893  * @code
894  * Wrong\Html\Template::require(777); // где 777 идентификатор любого шаблона
895  * @endcode
896  *
897  * Пусть вас не смущает область видимости ваших переменных - несмотря на то, что подключение файла осуществляется в методе класса,
898  * абосолютно все ваши переменные объявленные вне кода шаблона, будут доступны внутри него с теми же именами, точно так же, как если бы вы встроили файл шаблона
899  * обычной require конструкцией. Только здесь перед встраиванием автоматически делается проверка доступов к шаблону.
900  *
901  * И вы не только имеете доступ ко всем своим переменным объявленным ранее вне кода шаблона, вы можете их переназначать, объявлять новые,
902  * пользоваться объявленными экземплярами своих классов и т.д - все переменные автоматически доступны как будто из глобальной области видимости скрипта. Предположим код:
903  *
904  * @code
905  * $a = 1;
906  * Wrong\Html\Template::require(777); // здесь внутри кода шаблона $a изменилась на 2
907  * Wrong\Html\Template::require(999); // здесь $a изменилась уже на 3
908  * var_dump($a == 3); // true
909  * @endcode
910  *
911  *
912  * @section modal Шаблоны типа: modal
913  *
914  * Шаблоны данного типа можно встраивать 2 способами:
915  * - создавая модель модального окна, которая будет запрошена из api
916  * - или встраивая шаблон по принципу incode
917  *
918  * Если вы создаёте api модель - код выбранного шаблона будет скопирован в каталог /api/modal/... и уже не будет относиться к данному шаблону. Данной модели отдельно
919  * вы сможете задать права доступа и у неё будет свой отдельный код, который она просто возьмет из выбранного шаблона при создании. Кода окна в DOM при этом
920  * изначально не будет, он будет запрашиваться из api и уничтожаться при закрытии окна.
921  *
922  * А можно встраивать шаблоны модальных окон сразу же в текущий код, тем же способом что и incode:
923  * @code
924  * Wrong\Html\Template::require(777); // где 777 идентификатор шаблона окна
925  * @endcode
926  *
927  * В данном случае, если шаблон соответсвует правам доступа на чтение, он будет сразу встроен в код где он подключен.
928  *
929  * @section select Шаблоны типа: select
930  *
931  * При создании выборки код выбранного шаблона будет скопирован в каталог /api/select/... и уже не будет относиться к данному шаблону. Данной модели отдельно
932  * вы сможете задать права доступа и у неё будет свой отдельный код, который она просто возьмет из выбранного шаблона при создании модели(компонента).
933  *
934  * @section action Шаблоны типа: action
935  *
936  * При создании действия код выбранного шаблона будет скопирован в каталог /api/action/... и уже не будет относиться к данному шаблону. Данной модели отдельно
937  * вы сможете задать права доступа и у неё будет свой отдельный код, который она просто возьмет из выбранного шаблона при создании модели(компонента).
938  *
939  * @section template Кеширование шаблонов
940  *
941  * Встроенные по принципу incode шаблоны, а также каркасные шаблоны типа page можно кешировать, указывая количество секунд для кеширования.
942  * Для встроенных шаблонов будет отдаваться кешированный html участок шаблона. Для шаблонов типа page кеширован будет весь html вывод страницы, а ключем кеша будет REQUEST_URI запрос
943  * к странице с данным шаблоном.
944  *
945  */
946 
947 
948 
949 
950 
951  /**
952  * @page import_export Экспорт, импорт, копирование моделей
953  *
954  * Модель любой подчиненной группы или модель владельцем которой вы являетесь можно экспортировать и импортировать в виде zip архива в систему на базе wrong-mvc
955  *
956  * При импорте будет автоматически создана модель и её php файл. В случае совпадения имен, запросов, файлов, им будет добавлен постфикс -copy
957  *
958  * Чтобы быстро создать модель из другой модели, с тем же кодом/контентом можно воспользоваться её копированием - это фактически одномоментный экспорт и импорт.
959  * @attention
960  * При импорте модели заливается файл .php, поэтому вы не должны назначать права доступа на действие импорта кому попало, во избежание попадания вредоносного кода на ваш сервер.
961  *
962  * Данный функционал предусмотрен для моделей типа <b>Шаблоны, Страницы, Выборки, Модальные окна, Действия, Задачи</b>
963  *
964  * Основной администратор состоящий в группе <b>Система</b> может осуществлять экспорт/импорт/копирование системных моделей, но при этом у новой модели группа владелец
965  * сменится на группу <b>Администраторы</b>. В остальных же случаях у импортируемой/копируемой модели остается прежняя группа владелец(она может быть только подчинена
966  * группе пользователя осуществляющего действие).
967  *
968  * <a data-fancybox="gallery" href="/assets/system/img/import-export.png"><img style="max-width:100%;" src="/assets/system/img/import-export.png"></a>
969  */
970 
971 
972 
973 
974 
975 
976  /**
977  * @page logs Логи действий
978  *
979  * Для групп предусмотрена настройка включения/отключения лога действий. Действия - это все запросы к апи начинающиеся
980  * @code
981  * /api/action/...
982  * @endcode
983  *
984  * Если логгирование для группы включено, в логах вы сможете видеть все действия пользователей этой группы -
985  * метод запроса, переданные на обработчик данные в json формате и ответ api. В случае если в ответе api была любая ошибка, строка будет подсвечена красным.
986  *
987  * Строки логов можно разворачивать как по одной, так и все, и включать автообновление(перезагрузку) таблицы.
988  *
989  * <a data-fancybox="gallery" href="/assets/system/img/logs.png"><img style="max-width:100%;" src="/assets/system/img/logs.png"></a>
990  *
991  * За запись логов отвечает класс Logs/Write.php
992  */
993 
994  /**
995  * @page http_api HTTP API запросы, X-Auth-Token
996  *
997  * Это запросы к api системы извне по http.
998  * Если запрос выполняется от имени пользователя, в заголовках необходимо указать его X-Auth-Token, при этом работа через API должна быть включена на уровне системы,
999  * а также отдельно для данного пользователя. При отправке запроса к api <a target="_blank" href="/docs/csrf.html"><b>токен CSRF</b></a> указывать не нужно.
1000  *
1001  * Для запросов на выполнение действий включение/переключение функционала вам понадобится в основном всего два параметра в теле запроса - id и table.
1002  * Вы всегда можете выполнить действие вручную и <a target="_blank" href="/docs/logs.html"><b>посмотреть в логах его данные</b></a>.
1003  *
1004  * Например включаем/отключаем группу пользователей id 3 выполняя по сути вот это действие
1005  *
1006  * <a data-fancybox="gallery" href="/assets/system/img/api.png"><img style="max-width:100%;width:600px;" src="/assets/system/img/api.png"></a>
1007  *
1008  * PHP:
1009  * @code
1010  * curl --location 'https://wrong-mvc.com/api/action/toggle' \
1011  * --header 'x-auth-token: your-token' \
1012  * --header 'Content-Type: application/x-www-form-urlencoded' \
1013  * --data-urlencode 'id=3' \
1014  * --data-urlencode 'table=groups'
1015  * @endcode
1016  *
1017  * PHP CURL:
1018  * @code
1019  * $curl = curl_init();
1020  *
1021  * curl_setopt_array($curl, array(
1022  * CURLOPT_URL => 'https://wrong-mvc.com/api/action/toggle',
1023  * CURLOPT_RETURNTRANSFER => true,
1024  * CURLOPT_ENCODING => '',
1025  * CURLOPT_MAXREDIRS => 10,
1026  * CURLOPT_TIMEOUT => 0,
1027  * CURLOPT_FOLLOWLOCATION => true,
1028  * CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
1029  * CURLOPT_CUSTOMREQUEST => 'POST',
1030  * CURLOPT_POSTFIELDS => 'id=3&table=groups',
1031  * CURLOPT_HTTPHEADER => array(
1032  * 'x-auth-token: your-token',
1033  * 'Content-Type: application/x-www-form-urlencoded',
1034  * ),
1035  * ));
1036  *
1037  * $response = curl_exec($curl);
1038  *
1039  * curl_close($curl);
1040  * echo $response;
1041  * @endcode
1042  *
1043  *
1044  * Вы можете делать любые запросы к выборкам, действиям. Автоматизированные запросы от имени пользователя доступны также через
1045  * <a href="/docs/cron.html"><b>Встроенные CRON задачи</b></a>. Там вместо X-Auth-Token там достаточно указать id пользователя из подчинённой группы.
1046  *
1047  */
1048 
1049 
1050  /**
1051  * @page internal_api Cистемные curl API запросы, X-Auth-Token
1052  *
1053  * Запросы к внутрисистемному(на текущем хосте) API выполняются при помощи статического метода req() в Curl/API.php
1054  *
1055  * Если запрос выполняется от имени пользователя, в заголовках необходимо указать его X-Auth-Token, при этом работа через API должна быть включена на уровне системы,
1056  * а также отдельно для данного пользователя. При отправке запроса к api <a href="/docs/csrf.html"><b>токен CSRF</b></a> указывать не нужно.
1057  *
1058  * Вы можете указывать метод, данные, заголовки. Возвращается раскодированный json объект либо строка, если раскодировка не удалась.
1059  *
1060  * Вы можете делать любые запросы к выборкам, действиям. Автоматизированные запросы от имени пользователя доступны также через
1061  * <a href="/docs/cron.html"><b>Встроенные CRON задачи</b></a>. Вместо X-Auth-Token там достаточно указать id пользователя из подчинённой группы.
1062  *
1063  * Если запросу требуется тут же отвалиться, не дожидаясь ответа, то в таймауте можно задать 0.001
1064  *
1065  * @htmlonly
1066  * <div id="jq-load-1"><script>$("#jq-load-1").load("/docs/class_wrong_1_1_curl_1_1_a_p_i.html h2:contains('req()')");</script></div>
1067  * <div id="jq-load-2"><script>$("#jq-load-2").load("/docs/class_wrong_1_1_curl_1_1_a_p_i.html h2:contains('req()')+div");</script></div>
1068  * @endhtmlonly
1069  */
1070 
1071 
1072 
1073 
1074  /**
1075  * @page external_api Внешние curl API запросы к любым сервисам
1076  *
1077  * При помощи статического метода req_external() из Curl/API.php
1078  * вы можете выполнять запросы к любым внешним сервисам, указывая метод, данные, заголовки.
1079  *
1080  * Метод возвращает раскодированный json объект либо строку, если раскодировка не удалась.
1081  *
1082  * Если запросу требуется тут же отвалиться, не дожидаясь ответа, то в таймауте можно задать 0.001
1083  *
1084  * @htmlonly
1085  * <div id="jq-load-1"><script>$("#jq-load-1").load("/docs/class_wrong_1_1_curl_1_1_a_p_i.html h2:contains('req_external()')");</script></div>
1086  * <div id="jq-load-2"><script>$("#jq-load-2").load("/docs/class_wrong_1_1_curl_1_1_a_p_i.html h2:contains('req_external()')+div");</script></div>
1087  * @endhtmlonly
1088  */
1089 
1090 
1091 
1092 
1093 
1094  /**
1095  * @page packages Подключение node и composer пакетов
1096  *
1097  * <b>Nodejs</b>
1098  *
1099  * Установка дополнительных nodejs пакетов выполнятеся в корневом каталоге проекта. Любые node модули вы можете включать в сборку
1100  * в каталоге /app как отдельными js/css минифицированными модулями, так и объединяя в общие(примеры main.scss, main.js) файлы. Это зависит от ваших целей. Например модули используемые
1101  * в админ панели, такие как Datatables, не нужный в сайтах-проектах js код админки, это всё вынесено отдельными сборками в system-admin.js
1102  * сборка из /app в /public_html производится командой
1103  * @code
1104  * gulp build
1105  * @endcode
1106  *
1107  * @attention
1108  * Учитывайте что при сборке очищаются следующие каталоги указанные в gulpfile.mjs
1109  *
1110  * @code
1111  * /public_html/assets/system/css/
1112  * /public_html/assets/system/js/
1113  * /public_html/assets/system/img/
1114  * @endcode
1115  *
1116  * @note
1117  * На данном примере показаны логические каталоги <b>assets/system</b> для системного фронтенда, любые свои фронтенд сборки для сайтов-проектов вы можете
1118  * собирать в другой каталог, и это будет логично <b>assets/my-frontend-dirname</b>. Для этого поправьте пути в <b>gulpfile.mjs</b>
1119  *
1120  * Аналогично можно включать в сборку css в /app/scss/*.scss любые пакеты из node_modules, собирая их целиком или по частям.
1121  * Это зависит от применения их в вашем проекте(глобально или лишь на отдельных страницах).
1122  *
1123  * Данный код размещенный в head любого php шаблона будет выводить содержимое файлов в html странице обрамленным в тегах <style> и <script>
1124  *
1125  * @code
1126  * <?= Wrong\Html\Get::style($_SERVER['DOCUMENT_ROOT'] . '/assets/system/css/system-admin.min.css') ?>
1127  * <?= Wrong\Html\Get::style($_SERVER['DOCUMENT_ROOT'] . '/assets/system/css/main.min.css') ?>
1128  * <?= Wrong\Html\Get::script($_SERVER['DOCUMENT_ROOT'] . '/assets/system/js/main.min.js') ?>
1129  * <?= Wrong\Html\Get::script($_SERVER['DOCUMENT_ROOT'] . '/assets/system/js/system-admin.min.js') ?>
1130  * @endcode
1131  *
1132  * <b>Composer</b>
1133  *
1134  * На главной странице включается автозагрузчик из vendor
1135  * @code
1136  * require '../vendor/autoload.php';
1137  * @endcode
1138  *
1139  * Устанавливаете любые пакеты
1140  * @code
1141  * composer require package-name
1142  * @endcode
1143  *
1144  * и пользуетесь в своих проектах
1145  *
1146  */
1147 
1148 
1149 
1150 
1151 
1152 
1153 
1154 
1155  /**
1156  * @page triggers Триггеры действий и модальных окон
1157  *
1158  * - <b>Триггеры вызова действий</b> отправляют POST запросы к обработчику, в переменных запроса передаются всё переменные из атрибутов data-*
1159  * - <b>Триггеры вызова модальных окон</b> отправляют GET запросы к обработчику, в переменных запроса передаются всё переменные из атрибутов data-*
1160  *
1161  * Создайте <b>модель - действие</b> с request uri
1162  * @code
1163  * /api/action/my-action
1164  * @endcode
1165  *
1166  * Предположим наша модель выполняясь возвращает некий ответ в формате json
1167  *
1168  * Наведите мышкой на id модели в таблице и вызовите "конструктор триггера". Триггер действия - это некая кнопка или любой элемент,
1169  * с атрибутом <b>data-action</b>="my-action" Остальные атрибуты для триггера не обязательны.
1170  *
1171  * @code
1172  * <a class="btn btn-primary" data-action="my-action">Кнопка действия</a>
1173  * @endcode
1174  *
1175  * При клике на такой триггер будет мгновенно отправлен POST запрос на обработчик модели /api/action/my-action
1176  * На время ajax запроса кнопка автоматически блокируется, а также будет блокирован весь экран - показывается полупрозрачный спиннер загрузки, это сделано во избежание повторных кликов.
1177  *
1178  * Для действия с подтверждением добавьте аттрибут <b>data-confirm</b>="true" - при клике на триггер, запрос не будет отправлен, а будет выдано стандартное
1179  * модальное окно с заголовком "Подтвердите действие" и кнопками "Отмена / Да". Запрос будет отправлен лишь при нажатии на "Да".
1180  *
1181  * Дополнительными атрибутами к триггеру, вы можете изменить заголовок и текст в модальном окне подтверждения:
1182  *
1183  * <b>data-header</b>="Заголовок окна" <b>data-body</b>="Содержимое .modal-body окна"
1184  *
1185  * В атрибутах можно указать любую вашу callback функцию, которая будет вызвана сразу после выполнения действия и получит в response параметр ответа json от обработчика
1186  * <b>data-callback</b>="callbackMyAction"
1187  *
1188  * А саму функцию вы пропишите в коде заранее, вот простейший код, который покажет сообщение-всплавашку с ошибкой или успехом действия:
1189  * @code
1190  * function callbackMyAction(response) {
1191  *
1192  * if (response.error) {
1193  * errorToast(response.error);
1194  * return;
1195  * }
1196  *
1197  * successToast(response.message);
1198  * }
1199  * @endcode
1200  *
1201  * Вы можете указать и другой формат ответа обработчика, отличный от json, например <b>data-response</b>="script" или <b>data-response</b>="html" или же <b>data-response</b>="xml"
1202  *
1203  * Вы можете указать дополнительно любые произвольные данные в data-* атрибутах триггера, например data-id="value1" data-subid="value2" и так далее.
1204  * И обработчик получит все эти переменные в POST в таком виде:
1205  * @code
1206  * $_POST['id'] = 'value1';
1207  * $_POST['subid'] = 'value2';
1208  * @endcode
1209  *
1210  * По умолчанию в обработчиках все передаваемые переменные рекурсивно обрабатываются для защиты от атак типа XSS
1211  *
1212  * @code
1213  * array_walk_recursive($_POST, function (&$item) {
1214  * $item = trim(htmlspecialchars($item, ENT_QUOTES));
1215  * });
1216  * @endcode
1217  *
1218  * Создаваемые модели действий уже содержат примеры успешных и ошибочных json ответов
1219  *
1220  * <hr>
1221  *
1222  * Создайте <b>модель - модальное окно</b> с request uri
1223  * @code
1224  * /api/modal/my-modal
1225  * @endcode
1226  *
1227  * Укажите для неё любой из доступных шаблонов окон. Наведите мышкой на id только что созданной модели в таблице и вызовите "конструктор триггера".
1228  *
1229  * Кнопка для триггера модального окна ничем не отличается от стандартного синтаксиса Bootstrap 4
1230  * @code
1231  * <a class="btn btn-primary" data-toggle="modal" data-target="#my-modal">Кнопка вызова модального окна</a>
1232  * @endcode
1233  *
1234  * Но в данном случае вовсе не обязательно размещать html код модального окна в коде страницы. Модальное окно будет запрошено по api и
1235  * автоматически добавлено в DOM документа. А после закрытия модалки её код будет автоматически уничтожен из DOM.
1236  *
1237  * На момент ajax запроса к api с модальным окном, кнопка триггер автоматически блокируется как и весь экран - показывается полупрозрачный спиннер загрузки.
1238  *
1239  * Аналогично можно добавить к атрибутам триггера имя вашей callback функции <b>data-callback</b>="callbackMyModal". Данная функция будет вызвана сразу же после
1240  * добавления кода модалки в DOM, но ещё до её отображения. Т.е. средствами javascript в callback функции вы можете изменить это окно до неузнаваемости или
1241  * произвести ещё ряд необходимых действий.
1242  *
1243  * @code
1244  * function callbackMyModal() {
1245  * $("#my-modal .modal-header").html("Replaced header");
1246  * }
1247  * @endcode
1248  *
1249  * Также вы можете передать любые переменные в data-* атрибутах, например data-id="value1" data-subid="value2", которые будут переданы в GET запросе вызова модального окна.
1250  *
1251  * <hr>
1252  *
1253  * Помимо вышеописанных методов, вы можете <a href="/docs/js_actions_modals.html"><b>вызывать действия и модальные окна при помощи специальных javascript функций</b></a>, аналогично передавая им любые параметры.
1254  * Об этом следующий раздел.
1255  */
1256 
1257 
1258  /**
1259  * @page js_actions_modals Javascript вызовы действий и окон
1260  *
1261  * Помимо вышеописанного способа вызова действий и окон при помощи триггеров, их можно вызывать и javascript методами.
1262  *
1263  * Разберем всё то же действие /api/action/my-action описанное в предыдущем разделе о триггерах. Чтобы вызвать его программно:
1264  * @code
1265  * _action({
1266  * action: 'my-action',
1267  * id : 'value1',
1268  * subid : 'value2'
1269  * }, response => {
1270  * console.log(response);
1271  * });
1272  * @endcode
1273  *
1274  * Элегантно и просто. Программный вызов не поддерживает возможности вызова подтверждающего окна.
1275  *
1276  * Вызовем модальное окно входа в систему:
1277  *
1278  * @code
1279  * _modal('#sign-in');
1280  * @endcode
1281  *
1282  * или
1283  *
1284  * @code
1285  * _modal('#error', null, 'error=Wrong MVC - это круто!&value_1=value1&value_2=value2');
1286  * @endcode
1287  *
1288  * <a data-fancybox="gallery" href="/assets/system/img/modal.png"><img style="max-width:100%;" src="/assets/system/img/modal.png"></a>
1289  *
1290  * С callback вызовом например:
1291  *
1292  * @code
1293  * _modal('#sign-in', () => {
1294  * $('#sign-in').find('p,.small,a,.btn-group,.close').hide();
1295  * $('#sign-in h5').html('Выхода нет!');
1296  * $('#sign-in .modal-body').prepend('<div class="border rounded small py-1 px-2 mb-3 text-center">Wrong MVC - это гибко!</div>');
1297  * $('#sign-in .modal-body').append('<div class="border rounded small py-1 px-2 text-center">Вы влюбитесь в эту систему!</div>');
1298  * });
1299  * @endcode
1300  *
1301  * <a data-fancybox="gallery" href="/assets/system/img/modal2.png"><img style="max-width:100%;" src="/assets/system/img/modal2.png"></a>
1302  *
1303  */
1304 
1305 
1306 
1307 
1308 
1309 
1310 
1311 
1312  /**
1313  * @page toasts Всплывающие сообщения
1314  *
1315  * Предусмотрен вызов системных всплывающих сообщений, для отображения информации об ошибках, предупреждениях, успешных действиях и прочие сообщения.
1316  *
1317  * Функции вызова принимают первым параметром строку сообщения, и вторым таймаут. Оба параметра не являются обязательными, есть тексты по умолчанию,
1318  * а таймаут по умолчанию равен 5 секундам.
1319  *
1320  * Вызов сообщений по порядку успех, ошибка, предупреждение, сообщение:
1321  * @code
1322  * successToast("Успешно");
1323  * errorToast("Неизвестная ошибка");
1324  * dangerToast("Предупреждение");
1325  * toast("Сообщение");
1326  * @endcode
1327  *
1328  * <a data-fancybox="gallery" href="/assets/system/img/toast1.png"><img style="max-width:100%;" src="/assets/system/img/toast1.png"></a>
1329  * <a data-fancybox="gallery" href="/assets/system/img/toast2.png"><img style="max-width:100%;" src="/assets/system/img/toast2.png"></a>
1330  * <a data-fancybox="gallery" href="/assets/system/img/toast3.png"><img style="max-width:100%;" src="/assets/system/img/toast3.png"></a>
1331  * <a data-fancybox="gallery" href="/assets/system/img/toast4.png"><img style="max-width:100%;" src="/assets/system/img/toast4.png"></a>
1332  *
1333  * Вызов сообщений обычно комбинируется с <a href="/docs/triggers.html"><b>вызовом действий</b></a>, когда callback функция получает ответ от api. Также
1334  * они используются при <a href="/docs/ajaxforms.html"><b>отправке ajax форм</b></a>.
1335  *
1336  */
1337 
1338 
1339 
1340 
1341 
1342 
1343 
1344 
1345 
1346 
1347 
1348 
1349  /**
1350  * @page ajaxforms AJAX формы, блокировка submit кнопок
1351  *
1352  * Стандартные формы для сохранения данных из модальных окон выглядят вот так:
1353  * @htmlonly
1354  * <div id="jq-load-1"><script>$("#jq-load-1").load("/docs/form_8php_source.html .contents");</script></div>
1355  * @endhtmlonly
1356  *
1357  * В случае получения ошибки 403, 404 будет вызвано всплывающее сообщение с соответствующим текстом. В случае ошибки в api также всплывающее сообщение об ошибке
1358  * с текстом.
1359  *
1360  * В случае успеха происходит скрытие окна и обновление таблицы. На время отправки ajax запроса форма блокируется функцией lockSubmit - это гарантирует
1361  * защиту от повторных кликов по кнопке отправки. Формы обрабатываются api действий.
1362  */
1363 
1364 
1365 
1366 
1367 
1368 
1369  /**
1370  * @page csrf CSRF защита POST/PUT/DELETE
1371  *
1372  * Любой, даже динамически созданной форме, всегда автоматически добавляется CSRF токен. Вы не видите его изначально в своих формах и не должны об этом заботиться.
1373  * Токен представляет из себя md5 хеш идентификатора сессии, задаётся в include/session.php - там автоматически добавляется в страницу javascript переменная с токеном,
1374  * <a href="/docs/interaction.html"><b>по взаимодействию со страницей</b></a> этот токен добавляется hidden полем любой форме.
1375  *
1376  * <b>Все POST/PUT/DELETE запросы</b> защищены токеном! При отправке POST данных, когда они созданы <a href="/docs/triggers.html"><b>триггерами действий</b></a> либо
1377  * <a href="/docs/js_actions_modals.html"><b>javascript вызовами методом _action()</b></a> токен CSRF будет добавлен автоматически.
1378  *
1379  * Все запросы данными методами, без верного токена, будут заблокированы системой.
1380  *
1381  * Исключение составляют запросы по api авторизованные X-Auth-Token - в данном случае они защищены авторизационным токеном, и токен CSRF не требуется.
1382  *
1383  * Учитывайте это, если создаёте свой кастомный ajax запрос, то вы должны вручную добавить CSRF токен:
1384  * @code
1385  * $.ajax({
1386  * type: "POST",
1387  * url: "/request",
1388  * data: "CSRF=" + window.CSRF + "&your-value=value",
1389  * });
1390  * @endcode
1391  *
1392  * @note
1393  * Если вы вручную создаёте свой кастомный ajax запрос, то возможно, вы не до конца разобрались с методом <a href="/docs/js_actions_modals.html"><b>_action()</b></a>
1394  * и возможностями <a target="_blank" href="/docs/triggers.html"><b>триггеров</b></a>, они способны обеспечить практически любые POST ajax запросы и выборки из api.
1395  */
1396 
1397 
1398 
1399 
1400  /**
1401  * @page interaction Событие взаимодействия interaction
1402  *
1403  * Не чаще чем раз в 500мс при событиях javascript <b>load scroll click focus touchstart mouseenter</b> создаётся window событие взаимодействия с системой - <b>interaction</b>
1404  *
1405  * Вы можете его прослушивать для организации своей логики в приложении
1406  * @code
1407  * $(window).on("interaction", () => {
1408  * console.log("Вот это событие!");
1409  * });
1410  * @endcode
1411  *
1412  * По данному событию <b>interaction</b>, вы можете уже запускать автовоспроизведение аудио, видео, если пользователь уже совершал события отличные от <b>load</b>, т.е. скроллил, кликал и т.д <b>scroll click focus touchstart mouseenter</b>, поскольку <b>interaction</b> уже будет наследовать разрешенный промис взаимодействия(политики браузеров) - <a target="_blank" href="https://developer.chrome.com/blog/autoplay/">Uncaught (in promise) DOMException: play()</a>
1413  *
1414  *
1415  */
1416 
1417 
1418 
1419 
1420 
1421  /**
1422  * @page textarea_counters Счётчики символов в textarea
1423  *
1424  * Любому полю типа textarea можно добавить автоматический счётчик введенных символов, для этого достаточно указать полю css класс .with-counter
1425  * Класс-родитель textarea должен позиционироваться как relative.
1426  *
1427  * <a data-fancybox="gallery" href="/assets/system/img/textarea-counters-1.png"><img style="max-width:100%;" src="/assets/system/img/textarea-counters-1.png"></a>
1428  *
1429  * <a data-fancybox="gallery" href="/assets/system/img/textarea-counters-2.png"><img style="max-width:100%;" src="/assets/system/img/textarea-counters-2.png"></a>
1430  *
1431  */
1432 
1433 
1434 
1435 
1436 
1437 
1438  /**
1439  * @page hcaptcha Защита форм с внедрением Hcaptcha
1440  *
1441  * Вы можете использовать Auth/Hcaptcha.php на любых своих формах записывая неудачные попытки на единицу времени и вызывая капчу лишь после их
1442  * определенного количества. Определяющие логику попыток константы увидите в коде класса.
1443  *
1444  * Для примера разберем форму авторизации: api/modal/system/authorization/sign-in.php
1445  *
1446  * и её обработчик: api/action/system/authorization/sign-in.php
1447  *
1448  * В форме добавляется скрытое поле
1449  * @code
1450  * <input type="hidden" name="h-captcha-response">
1451  * @endcode
1452  *
1453  * Но пока доступен лимит попыток данному IP, токен не генерируется и не отправляется с формой, поле остается пустым
1454  *
1455  * Окно с формой также содержит callback функцию verifyCallback, для заполнения этого поля и submit-а, которая так же не используется, до момента пока доступны попытки
1456  * @code
1457  * window.verifyCallback = function(token) {
1458  * $("#<?= $basename ?> input[name=h-captcha-response]").val(token);
1459  * $("#hcaptcha").modal("hide");
1460  * $("#<?= $basename ?> form").submit();
1461  * $("#<?= $basename ?> input[name=h-captcha-response]").val("");
1462  * }
1463  * @endcode
1464  *
1465  * В обработчике при каждой неудачной попытке, происходит её запись(IP и время пишется классом)
1466  * @code
1467  * Wrong\Auth\Hcaptcha::attempt(); // запись попытки
1468  * @endcode
1469  *
1470  *
1471  * Каждый раз обработчик проверяет доступное число попыток условием, и отвечает фронтенду соответствующей ошибкой, если попытки закончились
1472  * или токен неверный
1473  * @code
1474  * if (!Wrong\Auth\Hcaptcha::check() && (empty($_POST['h-captcha-response']) || !Wrong\Auth\Hcaptcha::get($_POST['h-captcha-response']))) {
1475  * exit(json_encode(['error' => 'hcaptcha']));
1476  * }
1477  * @endcode
1478  *
1479  * Поймав такой ответ, фронтенд вызывает модальное окно с капчей api/modal/system/authorization/hcaptcha.php поверх окна авторизации
1480  * @code
1481  * if (response.error == 'hcaptcha') {
1482  * _modal("#hcaptcha");
1483  * }
1484  * @endcode
1485  *
1486  * В модальном окне с капчей вызывается её рендеринг window.hcaptchaRender с нашим коллбеком window.verifyCallback
1487  *
1488  * После ввода капчи и заполнения hidden поля ключем происходит автоматический submit нашей формы и её отправка на обработчик.
1489  *
1490  * В случае успешной проверки токена, обработчик пропускает дальше логику, либо вновь возвращает ответ
1491  * @code
1492  * exit(json_encode(['error' => 'hcaptcha']));
1493  * @endcode
1494  *
1495  * вызывающий очередной рендеринг Hcaptcha
1496  *
1497  * @note
1498  * На локальном сервере под ip 127.0.0.1 работу капчи вы не увидите, поскольку в методе check() класса Auth/Hcaptcha.php
1499  * прописано соответсвующее условие, делающее проверку с этим ip всегда валидной.
1500  *
1501  * Формы регистрации, авторизации, восстановления пароля, подтверждения почты, защищены по умолчанию, лимит - 5 попыток с одного IP в час.
1502  *
1503  */
1504 
1505 
1506 
1507 
1508 
1509 
1510  /**
1511  * @page loadlibs Подгрузка JS библиотек по мере применения
1512  *
1513  * Когда смотришь на классные, мощные проекты, но видишь код в несколько мегабайт(причем даже сжатый), то хочется плакать от боли или смеяться во сне.
1514  * Особенно, если ты помнишь ещё wml и рекомендации не делать странички свыше 2-4 кб. Такая экономия, это конечно перебор в наше время. Но при использовании
1515  * массы библиотек(плагинов), проект априори становится неповоротливым чудовищем. А ведь не только на разных страницах, но и в пределах одной можно подгружать код библиотек
1516  * тогда, и только тогда, когда он используется.
1517  *
1518  *
1519  * В качестве примера, возьмем <a href="/"><b>главную wrong-mvc</b></a>. Тут используется плагин Fancybox(137кб) и сборка редактора CodeMirror(356кб).
1520  * В сумме, не много, ни мало пол мегабайта. При этом далеко не факт, что зашедший на страницу пользователь кликнет на картинку, или вызовет редактор кода.
1521  *
1522  * А если это десятки библиотек?
1523  *
1524  * Что общего у всех плагинов? Это в основном некие css файлы, js файлы, и функция-метод инициализации. Так почему бы это не использовать?
1525  * Асинхронно загружать можно в том числе и свои коды, по мере необходимости.
1526  *
1527  * Функция loadLibs(cssPathArray, jsPathArray, funcName) принимает в параметрах
1528  *
1529  * - cssPathArray - массив путей к файлам
1530  * - jsPathArray - массив путей к файлам javascript
1531  * - funcName - имя метода, оно же имя функции, которое обязательно появится в свойствах window после выполнения js кода
1532  *
1533  * после загрузки всех файлов библиотеки функция возвращает разрешенный Promise.
1534  * css просто добавляются в head страницы, а js файлы загружаются поочередно, именно в том порядке в котором указаны в массиве!
1535  * Ведь у плагина может быть и несколько js файлов, и загрузиться они должны именно в определенном порядке.
1536  * Это обеспечивается встроенным стеком с callback функциями.
1537  *
1538  * @code
1539  * function loadLibs(cssPathArray, jsPathArray, funcName) {
1540  * return new Promise((resolve, reject) => {
1541  * cssPathArray && cssPathArray.forEach(path => {
1542  * !$("link[href='" + path + "']").length && $('head').append('<link rel="stylesheet" href="' + path + '">');
1543  * });
1544  * if (jsPathArray && typeof window[funcName] !== 'function') {
1545  * loading();
1546  * let loads = [];
1547  * function func() {
1548  * f = loads.shift();
1549  * if (typeof f === 'function') {
1550  * f(func);
1551  * } else {
1552  * loading('hide');
1553  * resolve();
1554  * }
1555  * }
1556  * jsPathArray.forEach(path => {
1557  * loads.push((callback) => {
1558  * $.cachedScript(path).done(() => {
1559  * callback();
1560  * });
1561  * });
1562  * });
1563  * func();
1564  * } else {
1565  * resolve();
1566  * }
1567  * });
1568  * }
1569  * @endcode
1570  *
1571  * Свой кеширующий метод jquery ajax запроса мы могли бы и не добавлять, а обойтись стандартным ajax методом, но пусть будет, вдруг ещё пригодится:
1572  * @code
1573  * $.cachedScript = function (url, options) {
1574  * options = $.extend(options || {}, {
1575  * dataType: "script",
1576  * cache: true,
1577  * url: url
1578  * });
1579  * return $.ajax(options);
1580  * };
1581  * @endcode
1582  *
1583  * Но как же быть с кешем и почему кешированно, ведь не применится, спросите вы. Там всё будет кешировано тогда, когда это нужно, и обновлено тогда когда нужно. И вот почему.
1584  * Аргументы функции мы передаём, не в виде обычных массивов, а в виде обработанных бекендорм массивов, в которые добавлено в строку запроса время модификации файла.
1585  * Обработка осуществяется методом <a href="/docs/class_wrong_1_1_html_1_1_get.html#abc7daa0622551cfeca4942e5fb2a4292"><b>Wrong\Html\Get::pathArrayJSON</b></a>
1586  *
1587  * Вот так мы подгружаем плагин Fancybox в <a href="/docs/quest_8php_source.html"><b>шаблоне главной страницы для не авторизованных</b></a>.
1588  * @code
1589  * <script>
1590  * $(document).on('click', '[data-fancybox]', function(e) {
1591  * if (typeof Fancybox !== 'function') {
1592  * e.preventDefault();
1593  * _this = this;
1594  * loadLibs(<?= Wrong\Html\Get::pathArrayJSON(['/css/fancybox.min.css']) ?>, <?= Wrong\Html\Get::pathArrayJSON(['/js/fancybox.min.js']) ?>, 'Fancybox')
1595  * .then(() => {
1596  * Fancybox.bind('[data-fancybox]', {});
1597  * _this.click();
1598  * });
1599  * }
1600  * });
1601  * </script>
1602  * @endcode
1603  *
1604  * плагин со всеми его файлами подгружаются лишь единожды по клику на любой элемент с атрибутом data-fancybox. И далее работает на странице как обычно. Первоначально при клике происходит проверка на наличие функции Fancybox в window объекте.
1605  * Если функции нет, подгружаем плагин при помощи loadLibs. После разрешения Promise мы выполняем необходимую инициализацию плагина и клик по тому самому объекту.
1606  *
1607  * Принцип подгрузки плагина CodeMirror аналогичен, но здесь есть особенность - ведь он инициализируется в модальном окне, которое <a href="/docs/triggers.html"><b>запрашивается триггером из api</b></a>. Тем
1608  * не менее, достаточно лишь 1 раз вызвать такую модалку - файлы плагина будут подгружены 1 раз, и в дальнейшем loadLibs Promise с этой библиотекой будет
1609  * разрешаться моментально.
1610  *
1611  * @htmlonly
1612  * <div id="jq-load-1"><script>$("#jq-load-1").load("/docs/modal_2system_2global_2edit-code_8php_source.html .contents");</script></div>
1613  * @endhtmlonly
1614  */
1615 
1616 
1617 
1618 
1619 
1620 
1621 
1622 
1623  /**
1624  * @page stackjs Javascript-PHP стеки, отложенное выполнение
1625  *
1626  * Javascript стеки - это асинхронное взаимодейсвие php бекенда с js фронтендом. Вы устанавливаете в php бекенде js задачу(код), помещаете его в стек,
1627  * устанавливая таймаут выполнения кода, и опционально ключ в стеке во избежание дублирования задачи. Код выполняется спустя заданный таймаут, удаляясь из стека
1628  * и устанавливается следующий таймаут, если задачи ещё остались в стеке.
1629  *
1630  * Синтаксис добавления задач в стек определён статическим методом <a href="/docs/class_wrong_1_1_task_1_1stack_j_s.html#a87f6da2087b08888f4c87499bdae1b99"><b>StackJS::add($code, $timeout = 0, $key = '')</b></a>
1631  *
1632  * Выполнение и установка очередных таймаутов обеспечивается методом <a href="/docs/class_wrong_1_1_task_1_1stack_j_s.html#aafaba9fa1586d43a821eab809ee562aa"><b>StackJS::execute()</b></a>
1633  * и запросами к /api/action/stackjs
1634  *
1635  * При загрузке любой страницы выполняется метод <a href="/docs/class_wrong_1_1_task_1_1stack_j_s.html#aafaba9fa1586d43a821eab809ee562aa"><b>StackJS::execute()</b></a>, который добавляет в код страницы javascript, время выполнения которого в стеке истекло. При этом код удаляется
1636  * из стека. А также, если в стеке ещё остались задачи, время которых не истекло, то в код страницы добавляется функция с самым минимальным таймаутом из стека задач, которая
1637  * запросит /api/action/stackjs спустя этот таймаут. Этот запрос к api снова вернёт <a href="/docs/class_wrong_1_1_task_1_1stack_j_s.html#aafaba9fa1586d43a821eab809ee562aa"><b>StackJS::execute()</b></a> и т.д. пока стек не закончится.
1638  *
1639  * Есть также отдельно метод <a href="/docs/class_wrong_1_1_task_1_1stack_j_s.html#aa3515c48bb3cb8d97a1d2b4325fdb8f2"><b>StackJS::set()</b></a> который не выполняет js код,
1640  * а возвращает только функцию с таймаутом запроса к /api/action/stackjs
1641  *
1642  * Итак, добавим на страницу
1643  * @code
1644  * Wrong\Task\Stackjs::add('successToast("Js-Php стек");', 30, 'key-stack-test');
1645  * @endcode
1646  * И спустя 30 секунд после её загрузки увидим всплывающее сообщение. А если мы спустя 10 секунд уйдем на другую страницу, то там оно покажется уже через 20 секунд.
1647  * А если мы перезагрузим эту страницу, до выполнения js кода из стека, то код и таймаут в стеке обновятся, и повторно,
1648  * т.е. дважды данный код выполнен не будет, благодаря уникальному ключу key-stack-test. Если нужно изменить это поведение -
1649  * просто не задавайте уникальный ключ элементу стека.
1650  *
1651  * Посмотрите, что автоматически добавила нам система в страницу, увидев в стеке отложенную задачу:
1652  *
1653  * <a data-fancybox="gallery" href="/assets/system/img/stackjs1.png"><img style="max-width:100%;width:600px;" src="/assets/system/img/stackjs1.png"></a>
1654  *
1655  * То есть, для реализации, мы не делаем постоянных ежесекундных ajax запросов к бекенду, нагружая тем самым сервер, до состояния кипящего чайника.
1656  * Кстати так делают именно чайники;) Мы делаем запросы тогда, и только тогда, когда они необходимы!
1657  *
1658  * Наглядно проедмонстрируем это в цикле, на той же странице добавим:
1659  * @code
1660  * for ($i = 0; $i < 33; $i++) {
1661  * Wrong\Task\Stackjs::add('successToast("Js-Php стек, итерация на ' . $i . ' секунде");', $i, $i);
1662  * }
1663  * @endcode
1664  *
1665  * Посмотрите что добавилось при загрузке:
1666  *
1667  * <a data-fancybox="gallery" href="/assets/system/img/stackjs2.png"><img style="max-width:100%;width:600px;" src="/assets/system/img/stackjs2.png"></a>
1668  *
1669  * А затем обработчик /api/action/stackjs каждый последующий раз добавлял очередной код выполнения и очередной таймаут запроса к себе же, пока стек не закончился.
1670  *
1671  * Пунктуально поздравим наших пользователей с Новым Годом, секунда в секунду(если он конечно будет на странице,
1672  * а если у него сохранится сессия, то он увидит поздравление позже):
1673  * @code
1674  * Wrong\Task\Stackjs::add('successToast("С Новым Годом!");', strtotime('first day of January next year') - time(), 'new-year');
1675  * @endcode
1676  *
1677  * Усложняем задачи. Пример простейшей рекурсии, которая размещается и стартует при запросе к /api/action/my-any-action выполняясь каждые 5 секунд:
1678  * @code
1679  * <?php
1680  *
1681  * Wrong\Task\Stackjs::add('successToast("Рекурсивный Js-Php стек");$.getScript('/api/action/my-any-action');', 5, 'key-stack-test');
1682  * exit(Wrong\Task\Stackjs::set());
1683  * @endcode
1684  * Теперь нам достаточно лишь однажды вызвать javascript действие /api/action/my-any-action и код будет выполняться на сайте бесконечно, пока мы не убьем свою сессию
1685  * @code
1686  * <script>
1687  * $.getScript('/api/action/my-any-action');
1688  * </script>
1689  * @endcode
1690  *
1691  * Но давайте уже будем работать с wrong-mvc как положено, ведь у нас есть специальные <a href="/docs/triggers.html"><b>триггеры действий</b></a>, которые активируют тоже самое поведение:
1692  * @code
1693  * <a class="btn btn-primary" data-action="my-any-action" data-response="script">Запустить рекурсию!</a>
1694  * @endcode
1695  * или же с подтверждающим окошком
1696  * @code
1697  * <a class="btn btn-primary" data-action="my-any-action" data-response="script" data-header="Запустить рекурсию?" data-body="Когда надоест, выйдите вон из своей сессии.">Запустить рекурсию!</a>
1698  * @endcode
1699  *
1700  * Вот ещё пример применения на странице:
1701  * @code
1702  * Wrong\Task\Stackjs::add('successToast("Я выполняюсь моментально без задержек! Но я вижу что ещё там в стеке кое что ниже есть через 3 секунды, поставлю ещё таймаут 3 секунды и вызову api когда придёт это время");', 0, 'key1');
1703  * Wrong\Task\Stackjs::add('_modal("#error", null, "error=Время пришло, api запрошено. Но я вижу там ещё кое что есть, через 4 сек, поставлю ещё таймаут на 4 секунды");', 3, 'key2');
1704  * Wrong\Task\Stackjs::add('$("#error").modal("hide");successToast("Окно скрыто. А что там, есть ещё что то в стеке?");', 7, 'key3');
1705  * Wrong\Task\Stackjs::add('dangerToast("Да, есть я в стеке! Но меня ждать ещё 23 секунды. Если дождёшься - я исполнюсь, а если перейдешь на другие страницы, то я всё равно исполнюсь, но ровно в свой срок!");', 30, 'key4');
1706  * @endcode
1707  *
1708  *
1709  */
1710 
1711 
1712 
1713 
1714 
1715 
1716 
1717 
1718 
1719 
1720 
1721  /**
1722  * @page cron Встроенные CRON задачи, автоматизация
1723  *
1724  * @section http_cron HTTP задачи
1725  *
1726  * В системе реализованы автоматически запускаемые cron задачи. Синтаксис расписаний задается так же как в любой unix системе(Минуты Часы Дни Месяцы Дни недели). Но в данном
1727  * случае это любые uri запросы на текущий домен, с любыми http методами(POST/PUT/GET/DELETE), с любыми устанавливаемыми заголовками(content-type), и данными тела запроса.
1728  * Крон задачи можно выполнять от имени других пользователей, если для него включена авторизация x-auth-token и его модель включена и является подчиненной вам по системному весу.
1729  *
1730  * <a data-fancybox="gallery" href="/assets/system/img/cron.png"><img style="max-width:100%;width:600px;" src="/assets/system/img/cron.png"></a>
1731  *
1732  * Вы можете включать и отключать определенный функционал проекта по заданному расписанию, или например менять шаблоны страниц с разными дизайнами. Для того, чтобы
1733  * посмотреть какие именно данные передать в теле запроса для cron задачи при исполнениии определённой модели, включите <a target="_blank" href="/docs/logs.html"><b>запись логов действий</b></a> для свой группы, выполните это действие вручную и посмотрите параметры тела нужного вам запроса в логах(id,action,table)
1734  *
1735  * <a data-fancybox="gallery" href="/assets/system/img/logs.png"><img style="max-width:100%;width:600px;" src="/assets/system/img/logs.png"></a>
1736  *
1737  * Автоматизируйте:
1738  * - скидки;
1739  * - поздравления;
1740  * - акции;
1741  * - рекламу;
1742  * - парсинг;
1743  * - маркетинг;
1744  * - сбор информации;
1745  *
1746  * @section cli_cron CLI задачи
1747  *
1748  * Кроме методов http вы можете поставить на cron любую cli команду, которая будет выполняться через exec
1749  *
1750  * @attention
1751  * Вы не должны давать права доступа на добавление cron задач кому попало. Это крайне небезопасно!
1752  *
1753  * @section cli_execute Выполение задач из CLI и по кнопке
1754  *
1755  * Вы можете запустить любую задачу из CLI перейдя в каталог public_html и вызвав
1756  *
1757  * @code
1758  * php -f cron.php 777
1759  * @endcode
1760  *
1761  * где 777 - id вашей задачи.
1762  *
1763  * Задачу можно запустить из админки по кнопке, даже если она отключена, она будет выполнена однократно, без учета натроенных для неё потоков.
1764  *
1765  * @section threads Многопоточность задач и контроль нагрузки
1766  *
1767  * Для каждой задачи вы можете устанавливать дополнительные настройки
1768  * - Минимум - минимальное количество запускаемых по расписанию задачей потоков её выполнения
1769  * - Максимум - масксимальное количество выполняемых потоков задачи при котором остальным запускам(потокам) этой задачи будет отказано
1770  * - Держать потоки - если установлено "да", то всегда будет поддерживаться минимальное установленное количество потоков, вне зависимости от периодичности запуска задачи. Каждый поток при запуске будет создавать необходимое количество дополнительных независимых форков.
1771  * - Предел нагрузки - устанавливается и рассчитывается в процентах от 1% до 1000% по формуле: текущий load average / кол-во логических ядер сервера * 100. Например 4/12*100 = 33% нагрузка. Где 4 это текущий la на 12 логических процессорах. 12 la из 12 ядер = 100% нагрузка сервера. 1000% - если сервер ещё работает, значит он крепыш. Если значение превышает установленное - в запуске очередного потока будет отказано. Вы устанавливаете лишь процент допустимой нагрузки при котором выполению данной задачи(любого её потока) будет отказано.
1772  *
1773  * Если вы не понимате зачем это вам, не настраивайте потоки, оставьте их по умолчанию и пользуйтесь cron задачами в их классическом варианте.
1774  * @note
1775  * При использовании большого количества потоков-программ в которых есть бд подключения, позботьтесь о настройках сервера бд <a target="_blank" href="https://mariadb.com/docs/server/ref/mdb/system-variables/max_connections/"><b>max_connections</b></a> и <a target="_blank" href="https://mariadb.com/docs/server/ref/mdb/system-variables/max_user_connections/"><b>max_user_connections</b></a>.
1776  *
1777  * <a data-fancybox="gallery" href="/assets/system/img/threads.png"><img style="max-width:100%;width:600px;" src="/assets/system/img/threads.png"></a>
1778  *
1779  * За техническую реализацию встроенного cron отвечает файл public_html/cron.php и класс Task/Cron.php
1780  *
1781  *
1782  *
1783  * Файл public_html/cron.php - запускает сам себя curl http запросом каждую минуту загружая метод load(), который в свою очередь посылает короткие независимые cli
1784  * запросы на выполнение задач к public_html/cron.php, в котором происходит выполение каждой задачи по отдельности.
1785  * Таким образом задачи полностью распарралеливаются и не зависят по времени выполнения друг от друга, ведь их может быть весьма большой стек и они могут
1786  * быть весьма ресурсоемкие.
1787  * Ежеминутно вызывается Wrong\Task\Cron::set_run_at() который проставляет всем задачам следующее(очередное) их время выполнения.
1788  *
1789  * Таким образом реализуется бесконечный ежеминутный цикл, с выполнением задач которые подходят под расписание.
1790  *
1791  * В классе Task/Cron.php реализован контроль за потоками и нагрузкой устанавливаемыми для каждой задачи.
1792  *
1793  * Когда автозапуск ещё не активирован - он активируется при помощи скрытой картинки,
1794  * которая инжектится в include/session.php при помощи <a target="_blank" href="/docs/stackjs.html"><b>js стека</b></a>(не всегда, а только если не активирован цикл автозапусков).
1795  * Это не совсем надежно, если на вашем сайте мало посетителей, т.к. иногда http запрос может не выполниться и цикл самозапуска public_html/cron.php прервется.
1796  * Бывает весьма редко, и если на сайте совсем никто не бывает, в основном цикл работает как часы.
1797  * Но чтобы повысить надежность, вы можете дополнительно поставить выполнение public_html/cron.php на свой системный cron раз минуту, вреда от этого не будет
1798  * (ведь дополнительные потоки блокируются), как скорее всего и пользы.
1799  * @code
1800  * curl -s -m 1 https://example.com/cron.php >/dev/null 2>&1
1801  * wget -qO- https://example.com/cron.php >/dev/null 2>&1
1802  * # или так если https сертификаты у вас внешние и прямой доступ по http не ограничен серверными настройками
1803  * curl -s -m 1 -H "Host: example.com" http://111.111.111.111/cron.php >/dev/null 2>&1
1804  * wget --header="Host: example.com" -qO- http://111.111.111.111/cron.php >/dev/null 2>&1
1805  * # где 111.111.111.111 - ip вашего сервера
1806  * @endcode
1807  *
1808  */
1809 
1810 
1811 
1812 
1813 
1814 
1815  /**
1816  * @page cache Система кеширования
1817  *
1818  * В проекте предусмотрено встроенное кеширование шаблонов, страниц и выборок. Об этом функционале рассказано в разделах выше.
1819  *
1820  * Вы можете также кешировать свои любые сущности-переменные(запросы к бд) при помощи класса Cache.php Он работает по аналогии Memcached и нейминг используемых методов идентичен.
1821  * Все объекты сохраняются в каталоге /temp/cache разбрасываются по уникальным каталогам для разгрузки фс, автоматически очищаются по истечении таймаутов.
1822  *
1823  * Разберем каждый метод в отдельности. Для того чтобы закешировать или вызвать из кеша любую сущность вы создаете экземпляр `new Cache()` вызывая конструктор:
1824  *
1825  * @htmlonly
1826  * <div id="jq-load-1"><script>$("#jq-load-1").load("/docs/class_wrong_1_1_memory_1_1_cache.html h2:contains('__construct()')");</script></div>
1827  * <div id="jq-load-2"><script>$("#jq-load-2").load("/docs/class_wrong_1_1_memory_1_1_cache.html h2:contains('__construct()')+div");</script></div>
1828  * @endhtmlonly
1829  *
1830  * Оба аргумента опциональны, но префикс желательно задать, он нужен для того чтобы не заморачиваться с уникальными ключами хранения. Например, вы работаете
1831  * с таблицей table_1 кешируя её записи, задайте префикс table_1 и в качестве уникальных ключей для записей вы сможете использовать например id, не беспокоясь что они пересекутся
1832  * с другими вашими объектами кеширования с такими же ключами. Т.е. это работает по той же аналогии, если бы вы в Memcached сохраняли данные на разных серверах.
1833  *
1834  * Для примера кешируем в файле public_html/cron.php объект строки из бд, чтобы не подключаться каждый раз в многопотоках к бд:
1835  * @code
1836  * $mem = new Wrong\Memory\Cache('cron');
1837  * if (!($row = $mem->get(777))) {
1838  * $row = Wrong\Models\Crontabs::find(777);
1839  * $mem->set(777, $row);
1840  * }
1841  * @endcode
1842  *
1843  * Метод `get()` получает сущность по определённому ключу, если она существует, метод `set()` сохраняет сущность в хранилище. В методе `set()`
1844  * вы можете задать таймаут актуальности кеша в секундах. По умолчанию используется таймаут установленный в константе `DEFAULT_TIMEOUT`
1845  *
1846  * Все доступные методы смотрите в описании класса <a target="_blank" href="/docs/class_wrong_1_1_memory_1_1_cache.html"><b>Cache</b></a>
1847  *
1848  * Очищать полностью системный кеш можно в окне настроек системы. Там же на кнопке выводится информация по общему занимаемому размеру каталогом /temp/cache
1849  *
1850  */
1851 
1852 
1853 
1854 
1855  /**
1856  * @page code_editor Встроенный редактор кода
1857  *
1858  * В системе предусмотрен встроенный редактор файлов обработчиков моделей с набором необходимых функций и поддержкой горячих клавиш. Безусловно это не полноценная IDE,
1859  * но редактор вполне сносный для мелких правок.
1860  *
1861  * Для редактирования кода модели пользователь должен обладать правами
1862  * групповых политик на изменение свойств модели - быть её владельцем или группа-владелец модели должна входить в подчиненные ему группы по системному весу. А также для него должен быть включен
1863  * соответсвующий функционал - <a target="_blank" href="/docs/modal_2system_2global_2edit-code_8php.html"><b>модальное окно редактора кода</b></a> и <a target="_blank" href="/docs/action_2system_2global_2edit-code_8php.html"><b>действие обработчик формы</b></a> редактора кода.
1864  *
1865  * @attention
1866  * Вы не должны предоставлять права доступа и включать данный функционал кому попало! Это свободная правка исполняемых .php файлов на вашем сервере.
1867  *
1868  * - Кнопка сохранения сохраняет файл и закрывает редактор в отличие от Ctrl + S.
1869  *
1870  * @htmlonly
1871  * <div style="display:flex;justify-content:space-between;width:280px;">
1872  * <div class='pr-5'>Сохранить файл:</div><kbd>Ctrl + S</kbd>
1873  * </div>
1874  * <div style="display:flex;justify-content:space-between;width:280px;">
1875  * <div class='pr-5'>Автодополнения:</div><kbd>Ctrl + Space</kbd>
1876  * </div>
1877  * <div style="display:flex;justify-content:space-between;width:280px;">
1878  * <div class='pr-5'>Развернуть:</div><kbd>F11</kbd>
1879  * </div>
1880  * <div style="display:flex;justify-content:space-between;width:280px;">
1881  * <div class='pr-5'>Свернуть:</div><kbd>Esc</kbd>
1882  * </div>
1883  * <div style="display:flex;justify-content:space-between;width:280px;">
1884  * <div class='pr-5'>К закрывающему тегу:</div><kbd>Ctrl + J</kbd>
1885  * </div>
1886  * <div style="display:flex;justify-content:space-between;width:280px;">
1887  * <div class='pr-5'>Поиск:</div><kbd>Alt + F</kbd>
1888  * </div>
1889  * <div style="display:flex;justify-content:space-between;width:280px;">
1890  * <div class='pr-5'>Следующий результат:</div><kbd>Enter</kbd>
1891  * </div>
1892  * <div style="display:flex;justify-content:space-between;width:280px;">
1893  * <div class='pr-5'>Заменить:</div><kbd>Shift + Ctrl + F</kbd>
1894  * </div>
1895  * <div style="display:flex;justify-content:space-between;width:280px;">
1896  * <div class='pr-5'>Заменить всё:</div><kbd>Shift + Ctrl + R</kbd>
1897  * </div>
1898  * <div style="display:flex;justify-content:space-between;width:280px;">
1899  * <div class='pr-5'>Закомментировать:</div><kbd>Ctrl + /</kbd>
1900  * </div>
1901  * @endhtmlonly
1902  *
1903  * <a data-fancybox="gallery" href="/assets/system/img/code.png"><img style="max-width:100%;width:600px;" src="/assets/system/img/code.png"></a>
1904  *
1905  *
1906  */