Serhii
Чесно кажучи вже й соромно якось клянчити. Але колективу повинно бути ще соромніше.
Serhii
𝓔𝓾𝓰𝓮𝓷𝓮
Serhii
Та біда, що я не поспіваю за думками. Потроху губляться круті ідеї.
Artur
https://dev.mysql.com/blog-archive/mysql-character-sets-unicode-and-uca-compliant-collations/
в MySQL вже давно рекомендованими є
- utf8mb4_0900_*
- utf8mb4_bin
тут пояснення, чому не треба юзати general_ci
https://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
може нам пофіксити в інсталяторі на рекомендовані? якусь регресію це навряд чи викличе
Serhii
Он Дімас вже від мене частину роботи чекає. @Dmi3yy пм'ятаю... Ще в процесі...
Serhii
𝓔𝓾𝓰𝓮𝓷𝓮
Artur
нормально працює з усіма мовами, тому й треба
Artur
collation рекомендований, доречі, можна отримувати без хардкоду (на випадок якщо він зміниться, чи щоб уникнути ситуації, коли він буде в MySQL але його викинуть, наприклад, з MariaDB) https://dev.mysql.com/doc/refman/8.4/en/charset-mysql.html
Serhii
Artur
дякую. Ще поки писав цей фікс - помітив в нас HTTPS_PORT як константу. Це для чого? Дефолтний завжди буде 443, так само як для HTTP 80.
якщо ж треба оверрайд (запустити modx на якомусь порті, але ми не довіряємо $_SERVER['SERVER_PORT']) - то виникає питання, чи це порт, який бачить браузер (і використовується, наприклад, в повних URL в локальних посиланнях), чи це для тих, хто забуде що це 443, чи це порт, на якому слухає безпосередньо сервер, на якому крутиться наш evo (а він може бути в докері, за reverse proxy тощо)?
Artur
ще був момент в інсталяторі щодо перекладів - https://github.com/evolution-cms/evolution/issues?page=2 тут багато тікетів відкрито на відсутність перекладів. їх реально немає, але зараз не проблема нагенерувати AI та бути певними що там не буде якоїсь дичини.
але є момент - в install/src/lang/uk.inc.php крім, власне, перекладів, ставиться локаль - setlocale (LC_ALL, 'uk_UA.UTF-8'); причому саме LC_ALL, не LC_LANG, що впливає на форматування валюти, дат тощо.
може цю логіку звідти прибрати, бо це ж файл перекладу?
Serhii
Так, треба прибрати.
Artur
https://github.com/evolution-cms/evolution/pull/2156 а це команда, що дозволить пофіксити https://github.com/evolution-cms/evolution/issues/853 та й взагалі, сподіваюсь, корисна на майбутнє
See stats of missing keys in user overrides dir manager/includes/lang/override/
php artisan translations:sync
See stats of missing keys in system dirs (for core developers)
manager/includes/lang/country/
manager/includes/lang/errormsg/
install/src/lang/
php artisan translations:sync --core
Write missing keys
php artisan translations:sync --core --write
Write missing keys sorted
php artisan translations:sync --core --write --sort
Same as above but change base language from en to uk
php artisan translations:sync --core --write --sort --base=uk
𝓔𝓾𝓰𝓮𝓷𝓮
@fav431 Спробуй так: Контролер: SitemapController.php <?php
namespace EvolutionCMS\Main\Controllers;
class SitemapController extends BaseController
{
public function setData()
{
$languages = evo()->getConfig('s_lang_front'); // Масив мов з site_url у ключі 'link'
$docs = evo()->runSnippet('DocLister', [
'parents' => 0,
'depth' => 10,
'showParent' => 1,
'tpl' => '',
'urlScheme' => 'full',
'api' => 1
]);
$sitemap = [];
// 👉 Додаємо головну сторінку (перша в списку)
$home = [
'url' => rtrim($languages['uk']['link'], '/'), // українська — основна
'editedon' => date('c'),
'alternates' => []
];
foreach ($languages as $code => $lang) {
$home['alternates'][] = [
'lang' => $code,
'url' => rtrim($lang['link'], '/')
];
}
$sitemap[] = $home;
// 👉 Додаємо всі інші сторінки
foreach ($docs as $doc) {
$alias = ltrim(parse_url($doc['url'], PHP_URL_PATH), '/');
if ($alias === '') {
continue; // пропускаємо головну, бо вона вже є
}
$entry = [
'url' => rtrim($languages['uk']['link'], '/') . '/' . $alias,
'editedon' => date('c', $doc['editedon']),
'alternates' => []
];
foreach ($languages as $code => $lang) {
$entry['alternates'][] = [
'lang' => $code,
'url' => rtrim($lang['link'], '/') . '/' . $alias
];
}
$sitemap[] = $entry;
}
$this->data['sitemap'] = $sitemap;
}
}
Yevhenii
@fav431 Спробуй так: Контролер: SitemapController.php <?php
namespace EvolutionCMS\Main\Controllers;
class SitemapController extends BaseController
{
public function setData()
{
$languages = evo()->getConfig('s_lang_front'); // Масив мов з site_url у ключі 'link'
$docs = evo()->runSnippet('DocLister', [
'parents' => 0,
'depth' => 10,
'showParent' => 1,
'tpl' => '',
'urlScheme' => 'full',
'api' => 1
]);
$sitemap = [];
// 👉 Додаємо головну сторінку (перша в списку)
$home = [
'url' => rtrim($languages['uk']['link'], '/'), // українська — основна
'editedon' => date('c'),
'alternates' => []
];
foreach ($languages as $code => $lang) {
$home['alternates'][] = [
'lang' => $code,
'url' => rtrim($lang['link'], '/')
];
}
$sitemap[] = $home;
// 👉 Додаємо всі інші сторінки
foreach ($docs as $doc) {
$alias = ltrim(parse_url($doc['url'], PHP_URL_PATH), '/');
if ($alias === '') {
continue; // пропускаємо головну, бо вона вже є
}
$entry = [
'url' => rtrim($languages['uk']['link'], '/') . '/' . $alias,
'editedon' => date('c', $doc['editedon']),
'alternates' => []
];
foreach ($languages as $code => $lang) {
$entry['alternates'][] = [
'lang' => $code,
'url' => rtrim($lang['link'], '/') . '/' . $alias
];
}
$sitemap[] = $entry;
}
$this->data['sitemap'] = $sitemap;
}
}
Дякую.Та тут ще є трохи харду, я трохи допишу ще, як до десктопу доберусь
Yevhenii
@fav431 Спробуй так: Контролер: SitemapController.php <?php
namespace EvolutionCMS\Main\Controllers;
class SitemapController extends BaseController
{
public function setData()
{
$languages = evo()->getConfig('s_lang_front'); // Масив мов з site_url у ключі 'link'
$docs = evo()->runSnippet('DocLister', [
'parents' => 0,
'depth' => 10,
'showParent' => 1,
'tpl' => '',
'urlScheme' => 'full',
'api' => 1
]);
$sitemap = [];
// 👉 Додаємо головну сторінку (перша в списку)
$home = [
'url' => rtrim($languages['uk']['link'], '/'), // українська — основна
'editedon' => date('c'),
'alternates' => []
];
foreach ($languages as $code => $lang) {
$home['alternates'][] = [
'lang' => $code,
'url' => rtrim($lang['link'], '/')
];
}
$sitemap[] = $home;
// 👉 Додаємо всі інші сторінки
foreach ($docs as $doc) {
$alias = ltrim(parse_url($doc['url'], PHP_URL_PATH), '/');
if ($alias === '') {
continue; // пропускаємо головну, бо вона вже є
}
$entry = [
'url' => rtrim($languages['uk']['link'], '/') . '/' . $alias,
'editedon' => date('c', $doc['editedon']),
'alternates' => []
];
foreach ($languages as $code => $lang) {
$entry['alternates'][] = [
'lang' => $code,
'url' => rtrim($lang['link'], '/') . '/' . $alias
];
}
$sitemap[] = $entry;
}
$this->data['sitemap'] = $sitemap;
}
}
Як обіцяв, відписую по результатам тесту. Цього всього, що в цитованому нема в $languages, бо в s_lang_front не масив, а простий рядок виду 'uk,en'
Yevhenii
@fav431 Спробуй так: Контролер: SitemapController.php <?php
namespace EvolutionCMS\Main\Controllers;
class SitemapController extends BaseController
{
public function setData()
{
$languages = evo()->getConfig('s_lang_front'); // Масив мов з site_url у ключі 'link'
$docs = evo()->runSnippet('DocLister', [
'parents' => 0,
'depth' => 10,
'showParent' => 1,
'tpl' => '',
'urlScheme' => 'full',
'api' => 1
]);
$sitemap = [];
// 👉 Додаємо головну сторінку (перша в списку)
$home = [
'url' => rtrim($languages['uk']['link'], '/'), // українська — основна
'editedon' => date('c'),
'alternates' => []
];
foreach ($languages as $code => $lang) {
$home['alternates'][] = [
'lang' => $code,
'url' => rtrim($lang['link'], '/')
];
}
$sitemap[] = $home;
// 👉 Додаємо всі інші сторінки
foreach ($docs as $doc) {
$alias = ltrim(parse_url($doc['url'], PHP_URL_PATH), '/');
if ($alias === '') {
continue; // пропускаємо головну, бо вона вже є
}
$entry = [
'url' => rtrim($languages['uk']['link'], '/') . '/' . $alias,
'editedon' => date('c', $doc['editedon']),
'alternates' => []
];
foreach ($languages as $code => $lang) {
$entry['alternates'][] = [
'lang' => $code,
'url' => rtrim($lang['link'], '/') . '/' . $alias
];
}
$sitemap[] = $entry;
}
$this->data['sitemap'] = $sitemap;
}
}
А ще отут $docs не форічіться, бо це json після доклістера, бо вивід апі=>1, але це не так критично, якщо пропустити через json_decode
𝓔𝓾𝓰𝓮𝓷𝓮
То мені треба поставити Сленг, та покопатися
𝓔𝓾𝓰𝓮𝓷𝓮
𝓔𝓾𝓰𝓮𝓷𝓮
в контролері <?php
namespace EvolutionCMS\Main\Controllers;
class SitemapController extends BaseController
{
public function setData()
{
// 🔹 Витягуємо мови
$langCodes = explode(',', evo()->getConfig('s_lang_front'));
$defaultLang = trim($langCodes[0]); // перша мова — основна
// 🔹 Формуємо масив мов із посиланнями
$languages = [];
foreach ($langCodes as $code) {
$code = trim($code);
$languages[$code] = [
'code' => $code,
'link' => $code === $defaultLang
? evo()->getConfig('site_url') // без префікса
: evo()->getConfig('site_url') . $code . '/' // з префіксом
];
}
// 🔹 Отримуємо список документів як JSON і декодуємо
$json = evo()->runSnippet('DocLister', [
'parents' => 0,
'depth' => 10,
'showParent' => 1,
'tpl' => '',
'urlScheme' => 'full',
'api' => 1
]);
$docs = json_decode($json, true) ?: [];
$sitemap = [];
// 🔹 Додаємо головну сторінку
$homeEntry = [
'url' => rtrim($languages[$defaultLang]['link'], '/'),
'editedon' => date('c'),
'alternates' => []
];
foreach ($languages as $code => $lang) {
$homeEntry['alternates'][] = [
'lang' => $code,
'url' => rtrim($lang['link'], '/')
];
}
$sitemap[] = $homeEntry;
// 🔹 Додаємо інші сторінки
foreach ($docs as $doc) {
$alias = ltrim(parse_url($doc['url'], PHP_URL_PATH), '/');
if ($alias === '') {
continue; // пропускаємо головну
}
$entry = [
'url' => rtrim($languages[$defaultLang]['link'], '/') . '/' . $alias,
'editedon' => date('c', $doc['editedon']),
'alternates' => []
];
foreach ($languages as $code => $lang) {
$entry['alternates'][] = [
'lang' => $code,
'url' => rtrim($lang['link'], '/') . '/' . $alias
];
}
$sitemap[] = $entry;
}
$this->data['sitemap'] = $sitemap;
}
}
𝓔𝓾𝓰𝓮𝓷𝓮
Можливо навіть краще буде переробити DLSitemap
Назвати його sDLSitemap
𝓔𝓾𝓰𝓮𝓷𝓮
Нажаль мені вже 43
Ghostery
на щастя)
Gunduz
TinyMCE4 не реагує на CSS. Що робити? Хтось стикався такою проблемою?
Gunduz
Gunduz
Maxa
site/css/bootstrap.min.css
браузер бачить ці шляхи?
Gunduz
Gunduz
Maxa
як варіант - завантажувати ці файлі через плагін
вішати на OnDocFormRender
$output = '<link rel="stylesheet" type="text/css" href="css/шлях до файлу">';
$modx->Event->output($output);
Gunduz
Artur
Друзі, запропонуйте віджет в адмінку замість цього застарілого.
Зробити там textarea з кнопкою - поле вводу для AI, та під'єднати до неї будь-який опенсорс JS MCP клієнт https://modelcontextprotocol.io/clients .
(хто не знає, що таке MCP і навіщо - https://modelcontextprotocol.io/faqs )
Зробити MCP сервер - втягнути ларавелівський MCP SDK https://github.com/php-mcp/laravel?tab=readme-ov-file#content-management ,
Ну і власне зробити там базові команди - пошук по контенту, wildcards для вкладених ресурсів, створити сторінку, CRUD медіафайли, згенерувати поля для SEO тощо.
Тоді можна буде зайти в адмінку і просто написати в цьому віджеті (або навіть сказати, якщо до кнопки додати хоча б браузерне розпізнавання голосового вводу) команду - скороти вдвічі опис товару №123, прибери бекграунд тощо, і він це зробить із коробки з саме цим конкретним сайтом на Evo, бо клієнт та сервер будуть поєднані єдиною авторизацією.
Віталій Пушкарьов 🇺🇦
Зробіть тестову виплату у доларах. Бо не завжди проходить платіж у 500 $ та вище.
Віталій Пушкарьов 🇺🇦
Реквізити у групі. На підтримку платформи і розробників.
Serhii
Друзі, а тут хтось робив здається кукі GDPR. Можете поділитись кодом?
𝓔𝓾𝓰𝓮𝓷𝓮
Тільки хотів відповисти)
Serhii
Та ну тих спамерів нахрін. Я одразу баню за таку чухню.
Alex
Serhii
Значить я пропустив.
𝓔𝓾𝓰𝓮𝓷𝓮
Вітаю!
Колись тут писав про антиспам по формлістеру, що хочу протестувати, як робив на МОДИКС, але довблюсь вже сутки, не блокуется відправка
Може у когось є приклад?
Зараз роблю так:
'prepare' => 'CheckSpamTimeFL',
'prepareProcess' => 'CheckCookieTrusted',
'prepareAfterProcess' => 'SendToTelegram',
сніппет CheckSpamTimeFL
if ($FormLister->isSubmitted())
{
$flag = false;
$now = microtime(true);
if ((isset($_SESSION['now'])) && (($now - $_SESSION['now']) > 5))
{
$flag = true;
}
$FormLister->setValid($flag);
}
else
{
$_SESSION['now'] = microtime(true);
}
А сніппет CheckCookieTrusted
return function ($FormLister, $fields) {
$cookie = $_COOKIE['spam_check'] ?? null;
if (!$cookie || strpos($cookie, 'trusted_user_') !== 0) {
$FormLister->addMessage('global', 'Форма недоступна. Перезавантажте сторінку.');
return false;
}
return true;
};
Може хтось вже робив?
Serhii
Нажаль не робив такого. Але є ідея створити щось на кшталт модуль sForms на заміну формлістера. Можете накидати ідей по функціоналу.
Dmytro
Юзав завжди відправку форми через js аяксом і це вроді як від ботів допомогало 3 роки назад але може щось змінилось з того часу)
Serhii
Serhii
Ідея зробити обгортку для формування форми. Я для комерца написав нормальний джс з валідаціями та підсвіткою невалідних полів. Тож думаю щось подібне зав'язати для форм. Наприклад зворотній зв'зок є всюди. Плюс часто забувають CSRF токен ставити...
Serhii
Ото ж і питаю ідей, що можна передбачити на перед.
𝓔𝓾𝓰𝓮𝓷𝓮
Я ще не самурай)
Я тільки вчуся)))
𝓔𝓾𝓰𝓮𝓷𝓮
Вітаю!
Колись тут писав про антиспам по формлістеру, що хочу протестувати, як робив на МОДИКС, але довблюсь вже сутки, не блокуется відправка
Може у когось є приклад?
Зараз роблю так:
'prepare' => 'CheckSpamTimeFL',
'prepareProcess' => 'CheckCookieTrusted',
'prepareAfterProcess' => 'SendToTelegram',
сніппет CheckSpamTimeFL
if ($FormLister->isSubmitted())
{
$flag = false;
$now = microtime(true);
if ((isset($_SESSION['now'])) && (($now - $_SESSION['now']) > 5))
{
$flag = true;
}
$FormLister->setValid($flag);
}
else
{
$_SESSION['now'] = microtime(true);
}
А сніппет CheckCookieTrusted
return function ($FormLister, $fields) {
$cookie = $_COOKIE['spam_check'] ?? null;
if (!$cookie || strpos($cookie, 'trusted_user_') !== 0) {
$FormLister->addMessage('global', 'Форма недоступна. Перезавантажте сторінку.');
return false;
}
return true;
};
Може хтось вже робив?
Трохи переробив, подивлюся як воно буде, якщо що, то треба становитися на бік самурая))
if ($FormLister->isSubmitted()) {
$cookie = $_COOKIE['spam_check'] ?? '';
if ($cookie && strpos($cookie, 'trusted_user_') === 0) {
$flag = true;
} else {
$flag = false;
// Лог
file_put_contents(MODX_BASE_PATH . 'assets/logs/cookie_block.log',
date('Y-m-d H:i:s') . " | IP: " . ($_SERVER['REMOTE_ADDR'] ?? '-') . " | COOKIE: " . $cookie . "\n",
FILE_APPEND
);
}
$FormLister->setValid($flag);
}
Serhii
Я теж не самурай, але намагаюсь. У нас тут Дмитро самий крутий.
𝓔𝓾𝓰𝓮𝓷𝓮
𝓔𝓾𝓰𝓮𝓷𝓮
Во наловив вже
Лізуть як блохи до моєї собаки
𝓔𝓾𝓰𝓮𝓷𝓮
Їханий бабай....
Наступного тижня буду дивитися у бік самурая
Капець
Serhii
Я роблю так. До прикладу.
𝓔𝓾𝓰𝓮𝓷𝓮
Serhii
Serhii
Роут (core/custom/routes.php):
Route::post('contact/send', [OrderHelperController::class, 'sendContactForm'])->name('contact.send');
Serhii
Метод в хелпері:
public function sendContactForm()
{
if (isset($_SESSION['fields'])) unset($_SESSION['fields']);
if (isset($_SESSION['success'])) unset($_SESSION['success']);
if (isset($_SESSION['error'])) unset($_SESSION['error']);
$dynamicFieldName = $_SESSION['dynamicFieldName'];
$dynamicFieldValue = $_SESSION['dynamicFieldValue'];
if (request()->input('extra_field') !== 'human' || request()->input($dynamicFieldName) !== $dynamicFieldValue) {
$_SESSION['error'] = 'Form submission failed.';
return redirect()->back();
}
$validator = Validator::make(request()->all(), [
'first_name' => 'required',
'last_name' => 'required',
'contact_tel' => 'required',
'contact_email' => 'required|email',
'service' => 'required',
'comment' => 'required',
],
[
'first_name.required' => 'The First Name field is required.',
'last_name.required' => 'The Last Name field is required.',
'contact_tel.required' => 'The Contact Phone field is required.',
'contact_email.required' => 'The E-Mail field is required.',
'contact_email.email' => 'The E-Mail must be a valid email address.',
'service.required' => 'The Type of service field is required.',
'comment.required' => 'The Comment field is required.',
]);
foreach ($validator->getData() as $field_name => $value) {
$_SESSION['fields'][$field_name]['old'] = $value;
}
if ($validator->fails()) {
foreach ($validator->messages()->getMessages() as $field_name => $messages) {
$_SESSION['fields'][$field_name]['error'] = $messages[0];
}
$_SESSION['error'] = 'Form submission failed.';
return redirect()->back();
}
$message = view('mails.feedback', $validator->getData())->render();
$param = array();
$param['from'] = evo()->getConfig('site_name') . '<' . $validator->validated()['contact_email'] . '>';
$param['subject'] = 'Contact us via ' . $validator->validated()['service'];
$param['body'] = $message;
$param['to'] = evo()->getConfig('sset_email');
$param['type'] = 'html';
Log::info("sendContactForm:\n" . var_export($param, TRUE));
evo()->sendmail($param);
$_SESSION['success'] = 'Your message has been sent.';
if (isset($_SESSION['fields'])) unset($_SESSION['fields']);
return redirect()->back();
}
Serhii
То я мабуть таки самурай.
Serhii
Цей приклад з реального проєкта.
ㅤ
Приветствую, не подскажите можно перенести данные из другой CMS в эту?
𝓔𝓾𝓰𝓮𝓷𝓮
Eintipper
𝓔𝓾𝓰𝓮𝓷𝓮
Гугляк не дуже файно (імхо)
Але дякую за підказку
𝓔𝓾𝓰𝓮𝓷𝓮
Serhii
Таке гівно зразу в бан.
Бавовнятко
Прокинулись)То що за скрін?
Нирмаланатх
Доброго дня
Нирмаланатх
Підкажіть як відновити пароль