• CMS своими руками

    @egorinsk
    Автор, а что гуглить. Есть минимум 3 способа: расковырять простую Open-Source CMS (проблема: найти CMS с хорошей архитектурой и аккуратным кодом), устроиться в компанию, у которой есть своя CMS (а она есть почти у каждой студии), и наконец, написать самому правильно.

    Маны нужны не по написанию CMS, а по используемым продуктам и технологиям.

    Сначала надо определиться с задачей. Установите и попользуйтесь несколькими CMS, просто чтобы увидеть особенности их работы. (если вы не можете это сделать — вам надо изучать основы установки и настройки apache/mysql/whatever, а не CMS писать. Уходите практиковать эти навыки). Также, есть хороший сайт, где установлены демки десятков CMS и можно их посмотреть, не устанавливая.

    Запишите, что вы хотите получить, сделайте наброски страниц. Определитесь с требованиями к вашей CMS. Какие в ней будут модули, как ими можно управлять.

    CMS обычно состоит из 2 частей — т.н. «админки» (запароленный раздел, где меняется конфигурация сайта, добавляются материалы) и публичной части сайта, которую видят пользователи.

    Если вы еще не бросили эту затею, перейдем к следующему пункту. Проектирование архитектуры и написание CMS. Чтобы хорошо писать сложную CMS, нужен опыт и понимание того, как вообще писать сложные программы. Нужно глубокое знание HTTP/HTML/CSS/JS/SQL. А именно:

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

    Что еще надо знать. Во-первых, надо иметь представление что значит MVC или 3-звенная архитектура.

    M в MVC — это Model. CMS скорее всего будет хранить данные в БД — надо знать, что такое и как пишется DBAL (гуглите: PDO), плейсхолдеры в запросах, возможно, Table Gateway, ознакомиться с тем, что такое ORM, и почему PHP-ные ORM так тормозят. Если будете делать модельки, не храните значения полей в публичных свойствах — это неудобно и нарушает инкапсуляцию. Храните их в приватном массиве $attributes.

    V is for View. Надо знать, что такое шаблонизаторы (прочтите мануал по Smarty, Django Templates, HAML и XSLT, чтобы иметь общее представление, какие они бывают). Для PHP хорошие варианты — использовать чистый PHP или XSLT, если осилите. Smarty — устаревший тормозной хлам, Twig тоже имеет недостатки. И не стоит ставить шаблонизатор, только, чтобы писать {$a} вместо [?= $a =].

    Не смешивайте логику (сложные вычисления, обращение к БД) и шаблонизацию. Лучше сделайте 2 файла: один с кодом, другой с шаблоном. В идеале, шаблонизатор получает от контроллера значения переменных и, кроме хелперов, никакого другого кода не вызывает.

    C — контроллеры. Но это самая простая часть, контроллер — это просто класс с методами типа viewAction(), editAction() и роутер, который смотрит на УРЛ и вызывает нужный контроллер. Посмотрите, как устроен Zend_Controller и Zend_Front_Contriller, и сделайте так же, но попроще. выкинув 90% функционала — он вам не понадобится.

    Нужно как-то сделать систему компонентной без сильных связей: чтобы ядро могло работать и без модулей, а добавление модуля не требовало дописывания кода в ядро. Почитайте про Dependency Injection, а также Observer (observer — это когда мы делаем функцию addEventListener()).

    Не используйте хуки, как в Друпал. Это дурной и порочный путь, взятый видимо из древных времен и программирования на Си.

    Что еще. Освоив все эти понятия, у вас в принципе не будет сложностей написать CMS, но почитайте еще мои советы по тому, как писать правильный код с исп. ООП: habrahabr.ru/qa/17158/#answer_70869

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

    Ну что еще. Если (в чем я сильно сомневаюсь) благодаря моему скромного совету вы все же сможете пройти этот нелегкий путь и станете успешным разработчиком, можете заплатить мне денег. Я не против.
    Ответ написан
    Комментировать
  • Почему не запускается функция при наступлении события?

    gromdron
    @gromdron Куратор тега Битрикс24
    Работаю с Bitrix24
    Да, вы почти все правильно сделали, но не так как нужно.
    Прежде чем копировать бездумно прочитайте до конца, я попытался развернуть мысль, чтобы не просто дать ответ на вопрос, но и пояснить некоторые действия.

    Давайте разберемся что у вас получлось правильно:
    - Вы решили использовать механизм событий
    - Вы правильно определили событие
    - Вы использовали local-папку для разработки
    - Вы попытались разделить ответственность между подпиской на событием и непосредственно ее обработчиком.

    Теперь что у вас НЕ получилось.

    Для начала это сама подписка на событие.
    - Вы не правильно поняли ее аргументы
    - Вы использовали устаревшую надстройку для подписки.

    Начнем с обратного порядка: AddEventHandler это лишь обертка над \Bitrix\Main\EventManager::addEventHandlerCompatible поэтому мы будем использовать его.

    Для начала перепишем ваш код дословно на "новый лад":

    $eventManager = \Bitrix\Main\EventManager::getInstance();
    $eventManager->addEventHandlerCompatible(
    	'main',
    	'OnAfterUserAdd',
    	[
    		'AddUserToGroupChatsClass',
    		'AddUserToGroupChats'
    	],
    	$_SERVER["DOCUMENT_ROOT"] . "/local/php_interface/event_handlers/add_user_to_group_chats_class.php"
    );


    Оно выглядит лучше но по-прежнему не работает.

    Порядок аргументов addEventHandlerCompatible и AddEventHandler одинаковый и состоит из:

    - FROM_MODULE_ID - на какой модуль подписываемся
    - MESSAGE_ID - на какое событие подписываемся
    - CALLBACK - callback обработчик (будет вызван для )
    - SORT - порядок выполнения относительно других событий
    - FULL_PATH - путь к файлу

    Т.е. получается что у тебя нарушен порядок аргументов. Корректнее было бы так:
    $eventManager = \Bitrix\Main\EventManager::getInstance();
    $eventManager->addEventHandlerCompatible(
    	'main',
    	'OnAfterUserAdd',
    	[
    		'AddUserToGroupChatsClass',
    		'AddUserToGroupChats'
    	],
    	100,
    	$_SERVER["DOCUMENT_ROOT"] . "/local/php_interface/event_handlers/add_user_to_group_chats_class.php"
    );


    Теперь разберемся с самой подписной моделью: вы хотите использовать свой класс и выделить его в отдельный файл для удобства.
    Не будем поднимать вопросы по поводу нейминга, но совершенно не обязательно подключать дополнительный файл для события. Рекомендую почитать про spl_autoload и composer.
    Давайте воспользуемся нашей готовой разработкой init.php и разместим в init.php маленький автолоадер.

    Предположим что за добавление будет отвечать ваш класс AddUserToGroupChatsClass

    Ваш init.php:
    /**
     * - /local/php_interface/classes/{Path|raw}/{*|raw}.php
     * - /local/php_interface/classes/{Path|ucfirst,lowercase}/{*|ucfirst,lowercase}.php
     */
    spl_autoload_register(function($sClassName)
    {
    	$sClassFile = __DIR__.'/classes';
    
    	if ( file_exists($sClassFile.'/'.str_replace('\\', '/', $sClassName).'.php') )
    	{
    		require_once($sClassFile.'/'.str_replace('\\', '/', $sClassName).'.php');
    		return;
    	}
    
    	$arClass = explode('\\', strtolower($sClassName));
    	foreach($arClass as $sPath )
    	{
    	    $sClassFile .= '/'.ucfirst($sPath);
    	}
    	$sClassFile .= '.php';
    	if (file_exists($sClassFile))
    	{
    		require_once($sClassFile);
    	}
    });
    
    $eventManager = \Bitrix\Main\EventManager::getInstance();
    $eventManager->addEventHandlerCompatible(
    
    	'main',
    	'OnAfterUserAdd',
    	[
    		'AddUserToGroupChatsClass',
    		'AddUserToGroupChats'
    	]
    );


    Теперь нужно разметить ваш класс в файле: /local/php_interface/classes/AddUserToGroupChatsClass.php
    Заметьте - мы удалили сортировку (вам не важем порядок) и подключение файла - за это теперь отвечает автолоадер.

    Теперь поработаем с содержимым этого файла.
    Произведем косметические изменения:
    - уберем лишние отступы
    - добавим phpdoc

    Произведем функциональные изменения:
    - приведем файл к виду opentag -> namespace -> use -> class
    - добавим описание метода необходимые для вызова его в call_user_func_array
    - Пропишем корректные праметры к Debug::writeToFile методу

    <?php
    
    use \Bitrix\Main\Diag;
    
    class AddUserToGroupChatsClass
    {
    	/**
    	 * Handle main::OnAfterUserAdd event
    	 *     after add user to groups
    	 * @param array &$arFields User data
    	 * @return void
    	 */
    	public static function AddUserToGroupChats( &$arFields )
    	{		 
    		Diag\Debug::writeToFile(
    			[
    				'text'   => "Событие наступило",
    				'fields' => $arFields
    			],
    			date('d.m.Y'),
    			"AddUserToGroupChatsClass.log"
    		); 
    	}
    }


    В результате проделанной работы будет создан файл `AddUserToGroupChatsClass.log` (у меня bitrix env и $_SERVER['DOCUMENT_ROOT'] указывает на /home/bitrix/www, соответственно файл располагается по пути: /home/bitrix/www/AddUserToGroupChatsClass.log)

    С примерно таким содержимым:

    28.09.2021:
    Array
    (
        [text] => Событие наступило
        [fields] => Array
            (
                [TITLE] => 
                [NAME] => test1
                [LAST_NAME] => test1
                [SECOND_NAME] => test1
                [EMAIL] => test1@test1.test1
                [LOGIN] => test1@test1.test1
                [PERSONAL_PROFESSION] => 
                [PERSONAL_WWW] => 
                [PERSONAL_ICQ] => 
                [PERSONAL_GENDER] => 
                [PERSONAL_BIRTHDAY] => 
                [PERSONAL_PHONE] => 
                [PERSONAL_FAX] => 
                [PERSONAL_MOBILE] => 
                [PERSONAL_PAGER] => 
                [PERSONAL_STREET] => 
                [PERSONAL_MAILBOX] => 
                [PERSONAL_CITY] => 
                [PERSONAL_STATE] => 
                [PERSONAL_ZIP] => 
                [PERSONAL_COUNTRY] => 0
                [PERSONAL_NOTES] => 
                [WORK_COMPANY] => 
                [WORK_DEPARTMENT] => 
                [WORK_POSITION] => 
                [WORK_WWW] => 
                [WORK_PHONE] => 
                [WORK_FAX] => 
                [WORK_PAGER] => 
                [WORK_STREET] => 
                [WORK_MAILBOX] => 
                [WORK_CITY] => 
                [WORK_STATE] => 
                [WORK_ZIP] => 
                [WORK_COUNTRY] => 0
                [WORK_PROFILE] => 
                [WORK_NOTES] => 
                [AUTO_TIME_ZONE] => 
                [XML_ID] => 
                [PHONE_NUMBER] => 
                [PASSWORD_EXPIRED] => N
                [TIME_ZONE] => 
                [LID] => s1
                [LANGUAGE_ID] => 
                [ACTIVE] => Y
                [BLOCKED] => N
                [GROUP_ID] => Array
                    (
                        [0] => Array
                            (
                                [GROUP_ID] => 3
                                [DATE_ACTIVE_FROM] => 
                                [DATE_ACTIVE_TO] => 
                            )
    
                        [1] => Array
                            (
                                [GROUP_ID] => 12
                                [DATE_ACTIVE_FROM] => 
                                [DATE_ACTIVE_TO] => 
                            )
    
                        [2] => Array
                            (
                                [GROUP_ID] => 4
                                [DATE_ACTIVE_FROM] => 
                                [DATE_ACTIVE_TO] => 
                            )
    
                    )
    
                [ADMIN_NOTES] => 
                [PASSWORD] => $5$hjIX0a5X2ps0X6kFG1h5xXAt4elIW.typcRgt23xwX97xU.GgGt0i3HG1a2hwZtcYXwIR3Whg6sXwV.
                [CONFIRM_PASSWORD] => test1@test1.test1
                [UF_DEPARTMENT] => Array
                    (
                        [0] => 2
                    )
    
                [UF_PHONE_INNER] => 
                [UF_USER_CRM_ENTITY] => 
                [UF_INN] => 
                [UF_DISTRICT] => 
                [UF_SKYPE] => 
                [UF_TWITTER] => 
                [UF_FACEBOOK] => 
                [UF_LINKEDIN] => 
                [UF_XING] => 
                [UF_WEB_SITES] => 
                [UF_SKILLS] => 
                [UF_INTERESTS] => 
                [UF_EMPLOYMENT_DATE] => 
                [UF_SKYPE_LINK] => 
                [UF_ZOOM] => 
                [UF_TIMEMAN] => 
                [UF_TM_MAX_START] => 00:00
                [UF_TM_MIN_FINISH] => 00:00
                [UF_TM_MIN_DURATION] => 00:00
                [UF_TM_REPORT_REQ] => 
                [UF_TM_REPORT_TPL] => Array
                    (
                        [0] => 
                    )
    
                [UF_TM_FREE] => 
                [UF_TM_TIME] => 
                [UF_TM_DAY] => 
                [UF_TM_REPORT_DATE] => 
                [UF_REPORT_PERIOD] => 
                [UF_DELAY_TIME] => 
                [UF_LAST_REPORT_DATE] => 
                [UF_SETTING_DATE] => 
                [UF_TM_ALLOWED_DELTA] => 
                [CHECKWORD] => 433ddaf3c8a14fe75431252a2709b8
                [~CHECKWORD_TIME] => now()
                [ID] => 5
                [RESULT] => 5
            )
    )


    Итого на выходе у нас:
    - Подписка на событие (работает)
    - Автолоадер, который позволит подключать классы по мере использования (без необходимости прописывать явно)
    - Разделение логики между подпиской и обработчиком.

    Хорошо было бы углубиться в:
    - php классы (namespace, static, public, protected, private)
    - composer

    Надеюсь помог.
    Ответ написан
    Комментировать
  • Требования к самописной CRM?

    Jeer
    @Jeer
    уверенный пользователь
    Аж жуть берет от таких заданий ) Покупка готового инструмента обычно дешевле, чем разработка с нуля. Особенно если разработка осуществляется одним человеком. Особенно если опыта мало. Особенно, если на поддержке будет тот же человек, что и на разработке ) безумие :) проект полетит в помойку, но у вас будет строчка в резюме по созданию проекта, можете обкатывать любые технологии за счет глупого работодателя.

    По делу без брюзжания:
    Писать под винду моветон - лицензии дорогие
    Вин формс зачем? - делайте веб апи с фронтом на вью/реакте/ангуляре. Нужна кроссплатформенность, чтобы хоть с телефона можно было зайти и нажать нужную кнопку в системе.
    Апи нужно, потому что будет много интеграций с другими системами, загрузка/выгрузка в 1с, не дай бог будете телефонию подключать
    Внутренняя сеть решается с помощью впн сервера.
    Помимо функциональных требований существуют еще технические, должен быть мониторинг, вы должны всегда знать сколько ресурсов потребляет ваш сервер, сколько данных занимает на дисках, логи и трейсы - гуглится по слову Observability, обычно не закладывается в смету, но к этим вопросам приходят рано или поздно
    Что еще, ну, по функционалу тут проще, открываете презентации в популярных CRMках, смотрите, что они могут, выписываете списком и идете к заказчику, чтобы он указал галочками, что будете делать, что не будете
    потом прикидываете по трудозатратам и озвучиваете сроки из которых можно понять примерную стоимость проекта. Часто одно маленькое предложение, типа того же "выгрузить данные в 1с" грозит несколькими десяткми часов работы, потому что другой отдел, с ними нужно договориться и сделать интеграцию. Или "должен быть отчет такой-то", а там как начнешь разбираться, еще 5 раз посовещаться сначала надо ))
    Ответ написан
    Комментировать
  • Как организовать оповещение пользователей?

    FanatPHP
    @FanatPHP
    Чебуратор тега РНР
    Во-первых, надо выбросить из головы эту дурь, "хачу изначально писать правильно".
    Изначально надо написать хоть что-то.
    Ни один сайт в мире не писался "изначально правильно". Сначала пишут рабочий прототип, чтобы хотя бы самому понять, что ты делаешь.
    Это принципиальный момент. Варианта тут только два - либо ты делаешь минимальный вариант доступными средствами и потом его развиваешь, либо сидишь на диване и рассуждаешь о самых модных веяниях и в итоге не получаешь ничего. Особенно если даже минимальный вариант реализации для тебя темный лес и загадка века.

    И делать не 100500 способов оповещения, а один.
    То есть сделать таблицу подписки, в которой указано - на какое событие подписался юзер, и его айди.

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

    Одно это уже займёт вас на пару месяцев.

    И только после того как это сделано, после того как заработает хоть что-то, можно начинать думать в сторону других способов. Но опять же, не всех скопом, а по одному. Сделали рассылку в телеграм? Поехали с оповещениями в браузер.

    То же самое касается способа рассылки.
    Только после того как рассылка начинает занимать ощутимое время - и, разумеется, после того как она вообще была сделана - только после этого можно задуматься о сервере очередей, о том что по наступлению события добавляются только задания в очередь, а сами рассылки делают несколько отдельных сервисов и воркеров. И всё это, разумеется, только если проект не загнется раньше/у автора не пропадёт интерес/у него же хватит способностей осилить всю эту премудрость
    Ответ написан
    Комментировать
  • Как узнать какие именно поля были изменены при вызове события OnAfterIBlockSectionUpdate?

    @jasper-blondin
    Не надо ничего сохранять.
    При срабатывании события OnBeforeIBlockSectionUpdate получите поля (из параметры функции-обработчика). Это новые значения. А как получить старые, Вы уже знаете: CIBlockSection::GetByID().

    Получается, что Ваша задача сводится к банальному условию в обработчике:
    $oldFields = \CIBlockSection::GetByID($fields["ID"])->GetNext();
    if($fields["NAME"] != $oldFields["NAME"]) {...}
    Ответ написан
    1 комментарий
  • Как и куда встроить прослушивание событий в 1С-Битрикс для их обработки и дальнейшей передачи в удаленные сервисы?

    gromdron
    @gromdron
    Работаю с Bitrix24
    Вообще, исчерпывающую информацию на этот вопрос дает официальная документация по файлу init.php.

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


    Про способы организации кода это довольно узкая тематика.
    Если Вы 100% уверены, что они еще пригодятся на других проектах (и у Вас уже есть подобные заказы), то предпочтительнее делать модуль.
    Если это хардкорная интеграция только для существующего проекта, то init.php(*).

    Мы, например, придерживаемся последнего варианта:
    1) В директории /local/php_interface/ создаем следующие файлы:

    - init.php (Содержит подключение других файлов + автолоадер)
    - constants.php (Содержит только константы, актуальные для данного проекта)
    - events.php (содержит подписки на события, но логика их обработки не находится в этом файле)

    2) В директории /local/php_interface/classes/ по PSR размещаем класс для проекта.

    Например:

    init.php:
    /**
     * - /local/classes/{Path|raw}/{*|raw}.php
     * - /local/classes/{Path|ucfirst,lowercase}/{*|ucfirst,lowercase}.php
     */
    spl_autoload_register(function($sClassName)
    {
    
    	$sClassFile = __DIR__.'/classes';
    
    	if ( file_exists($sClassFile.'/'.str_replace('\\', '/', $sClassName).'.php') )
    	{
    		require_once($sClassFile.'/'.str_replace('\\', '/', $sClassName).'.php');
    	}
    
    	$arClass = explode('\\', strtolower($sClassName));
    	foreach($arClass as $sPath )
    	{
    	    $sClassFile .= '/'.ucfirst($sPath);
    	}
    	$sClassFile .= '.php';
    	if (file_exists($sClassFile))
    	{
    		require_once($sClassFile);
    	}
    });
    
    /**
     * File with event handlers
     */
    require_once(__DIR__.'/events.php');


    events.php:
    $eventManager = \Bitrix\Main\EventManager::getInstance();
    
    /**
     * For new core of bitrix use
     *     $eventManager->addEventHandler( #module#, #handler#, [#namespace#, #function#]);
     * 
     * For old core of bitrix use
     *     $eventManager->addEventHandlerCompatible( #module#, #handler#, [#namespace#, #function#]);
     */
    
    $eventManager->addEventHandlerCompatible("module", "event", ['\\Project\\Module\\Event\\Handler', 'onEvent']);
    Ответ написан
    Комментировать
  • Как хранить дату по умолчанию в MySQL или почему DateTime('0000-00-00') показывает чушь?

    TheCreator
    @TheCreator
    Бездельник
    Любое поле, которое может быть введено, а может и не быть должно иметь значением по умолчанию null.
    Ответ написан
    Комментировать
  • Как настроить php intellisense для Visual Studio Code?

    ganjour
    @ganjour
    change returns success
    В настройках необходимо указать путь к исполняемому файлу PHP 7.xx и отключить стандартный PHP IntelliSense. Вот этих трех строк достаточно для настройки плагина:
    "php.executablePath": "D:/YandexDisk/OpenServer/modules/php/PHP-7/php.exe",
    "php.validate.executablePath": "D:/YandexDisk/OpenServer/modules/php/PHP-7/php.exe",
    "php.suggest.basic": false,

    Пути к php.exe замените на свои и не забудьте сохранить файл настроек..
    Ответ написан
    Комментировать
  • Поиск по похожей строке в массиве js?

    Ну например через filter:
    var find = function (arr, find) {
      return arr.filter(function (value) {
         return value === find;
      });
    };
    var arr = ["a", -1, 2, "b"];
    console.log(find(arr, -1));
    var result = find(arr, 2);

    result будет включать все значения удовлетворяющие условию в ф-и, в вашем случаи будет:
    [2]
    C определением вхождения:
    var find = function (arr, find) {
      return arr.filter(function (value) {
         return (value + "").indexOf(find) != -1;
      });
    };
    var arr = ["a", -1, 2, "bulka"];
    console.log(find(arr, "b"));
    Ответ написан
    3 комментария
  • Поиск по похожей строке в массиве js?

    @GeneD88
    QA
    1.
    Array.prototype.contains = function(element){
    return this.indexOf(element) > -1;
    };

    ["toster", "tester", "mister"].contains("toster")
    // True

    2.
    var myArr = ["toster", "tester", "mister"];
    var tosterInArray = (myarr.indexOf("toster") > -1);
    // True
    Ответ написан
    2 комментария
  • Как хранить ответы пользователей и выводить вопросы в системе тестирования?

    Denormalization
    @Denormalization
    3 таблицы:

    questions:
    id | question
    
    answers:
    id | question_id | answer
    
    answer_user
    answer_id | user_id (или question_id | answer_id | user_id)


    Когда юзер отвечает, записывает ответ в answer_user.
    Обычная many to many связь.

    > как выводить ответы на которые пользователь еще не ответил
    Достаем все ответы по question_id из answers + join на таблицу anwser_user
    Ответ написан
    4 комментария
  • Как склонировать ветку на свой домашний компьютер через ssh если локальная разработка проводилась на другом компьютере?

    karabanov
    @karabanov
    Системный администратор
    Сгенерируй SSH ключ на домашнем компьютере ssh -t ed25519
    Добавь публичную часть ключа в свой аккаунт на GitHub
    После этого сможешь клонировать.

    К каждого устройства должен быть свой ключ. Приватный ключ не должен покидать устройство на котором он был сгенерирован.
    Ответ написан
    1 комментарий
  • Как передать UTM-метки в CRM Битрикс 24 с помощью PHP?

    Итак, сначала нужно записать утм в массив $_cookie для того, что при переходе на другие страницы не потерялись значения, вот этот код отвечает за это:

    if(isset($_GET["utm_source"])) setcookie("utm_source",$_GET["utm_source"],time()+3600*24*30,"/"); 
    if(isset($_GET["utm_medium"])) setcookie("utm_medium",$_GET["utm_medium"],time()+3600*24*30,"/"); 
    if(isset($_GET["utm_campaign"])) setcookie("utm_campaign",$_GET["utm_campaign"],time()+3600*24*30,"/"); 
    if(isset($_GET["utm_content"])) setcookie("utm_content",$_GET["utm_content"],time()+3600*24*30,"/"); 
    if(isset($_GET["utm_term"])) setcookie("utm_term",$_GET["utm_term"],time()+3600*24*30,"/");


    Добавляйте его на каждую страницу↑.

    Затем создаем в форме невидимые поля, cо значениями:
    <input name="utm-source" type="hidden" value="<?=$_COOKIE['utm_source']?>">
    <input name="utm-medium" type="hidden" value="<?=$_COOKIE['utm_medium']?>">
    <input name="utm-compaign" type="hidden" value="<?=$_COOKIE['utm_compaign']?>">
    <input name="utm-content" type="hidden" value="<?=$_COOKIE['utm_content']?>">
    <input name="utm-term" type="hidden" value="<?=$_COOKIE['utm_term']?>">


    А в обработчике уже принимаем данные, как и все остальные поля:
    'UTM_SOURCE' => $_POST['utm-source'],
    	            'UTM_MEDIUM' => $_POST['utm-medium'],
    	            'UTM_CAMPAIGN' => $_POST['utm-campaign'],
    	            'UTM_CONTENT' => $_POST['utm-content'],
    	            'UTM_TERM' => $_POST['utm-term'],


    Результат:
    5ea8c07a0c226249777337.jpeg
    Ответ написан
    8 комментариев
  • Библиотека для создания диаграмм Гантта?

    @bromzh
    Drugs-driven development
    Я делал проект с использованием этой библиотеки. Очень удобное апи, удобно связывать с БД, много возможностей. Крайне рекомендую.
    Ответ написан
    Комментировать
  • Вывести значения определенных ячеек в модальное окно?

    Stalker_RED
    @Stalker_RED
    Ну вы уже большую часть задачи выполнили, осталось получить значение из первого элемента нужной строки и из соответствующего столбца первой строки.
    С чем у вас сложность? С конкатенацией или с получением textContent из нужной ячейки?

    Ответ написан
    6 комментариев
  • Как работает API Opencart 3?

    @Im_lost
    Вот это мне помогло.
    https://webocreation.com/blog/opencart-api-documen...
    Ответ написан
    Комментировать
  • Почему одни языки быстрее, другие медленнее, и почему новички гуглят самый быстрый язык, а не самый медленный?

    @majstar_Zubr
    C++, C#, gamedev

    А как вообще определяется лучшесть и быстрость..
    А если один язык посчитал быстрее, значит он лучше?


    Язык - это набор грамматик для парсера; набор правил для описания последовательности действий с данными, описания данных, работы со строками и т.д.
    Язык ничего считает, и сам по себе либо хорош для какого-то класса задач, либо совсем не хорош. Например, писать кросс-платформенный три в ряд на php идея так себе. Вся магия заключается в том, для чего этот язык служит ручкой управления - для игровой приставки, телевизора, виртуальной машины, веб-сервера, специального ПО или ПК.

    Язык без контекста вообще не имеет никаких свойств, это просто инструмент. Сначала говорят о процессе разработки, сроках, задачах, в процессе оценки сложности проектирования определяется стек технологий для решения и среди них будут языки.

    Почему новички гуглят самый быстрый язык, а не самый медленный?


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

    А количество написанных строк кода при достижении одинакового результата показывает какой язык лучше?


    Количество строк в решении задачи на языке А по сравнению с языком Б может позволить неточно оценить отношение цикломатических сложностей для решений. Но это имеет смысл только для языков из одной категории. Например, для ПО для работы с геоданными и картами может потребоваться встроить какой-нибудь существующий язык, либо навелосипедить DSL. К примеру, в качестве встраиваемых кандидатов можно взять lua, js, python. Если в аналитической модели большинство операций связано с операциями над множествами, то на тестовой задаче станет видно, что решения на python самые лаконичные. Если в модели много работы с данными разных типов, то возможно тут выиграет js. Если все модели данных уже предопределены, или в числодробилках задействованы распараллеливание вычислений, то скорее всего обвязка на lua даст самый лаконичный результат. Да, все дело в том, какие именно паттерны и модели изначально встроены в синтаксис языка. По факту, именно специализация языка на классе задач позволяет сделать решение лаконичным.

    Но лаконичность и скорость исполнения более важны для языков с JIT компиляцией. В AoT основная ценность это производительность программы и минимальное потребление ресурсов, в C++ лаконичность в решении это результат введения чего-то хорошего в стандарт. В целом, нужно исходить от класса задач, а не просто из количества строк.
    Ответ написан
    Комментировать
  • Как получить значение input text при нажатии на кнопку на js?

    gobananas
    @gobananas
    finishhim.ru
    Присваиваем разные id. При нажатии на кнопку получаем значения поля https://jsfiddle.net/q4h1045m/
    <input id="elem1" type="text" name="quantity" placeholder="Количество">
    <input id="butt" type="button" value="Кнопка"/><br><br>
    <div id="str"></div>

    butt.onclick = function() {
    		var val = document.getElementById('elem1').value;
    		document.getElementById('str').innerHTML="Вы ввели: "+val;
    };
    Ответ написан
    1 комментарий
  • Как правильно использовать mysqli_num_rows()?

    FanatPHP
    @FanatPHP
    Чебуратор тега РНР
    Это очень хороший вопрос.
    Ответ на него - никак не использовать.

    Да, в mysqli действительно есть специальная функция, которая может сказать, какое количество строк вернул запрос SELECT.
    Традиционно употребляется в двух случаях:

    а) когда это не нужно
    б) когда приводит к катастрофическим последствиям

    Первый вариант - это когда мы хотим узнать, вернул ли запрос хоть какие-то данные, или нет. Но на этот случай у нас есть сами данные. Зачем отдельно запрашивать их количество, если мы все равно будем эти данные получать в переменную, которую потом и можно будет использовать чтобы узнать, вернул ли запрос что-нибудь или нет.

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

    Правильным решением этой задачи будет сделать запрос вида SELECT COUNT(*) FROM .... В этом случае БД сама внутри себя посчитает количество строк (очень быстро) и вернёт только одно число, которое не занимает оперативную память вообще.

    Вот и получается, что функция mysqli_num_rows() является либо вредной, либо бесполезной

    В данном случае надо сначала получить записи из БД
    // БЕЗОПАСНО выполняем запрос
    $stmt = $link->prepare("SELECT * FROM comments WHERE art_id = ?");
    $stmt->bind_param("s", $note_id);
    $stmt->execute();
    // получаем данные
    $result = $stmt->get_result();
    $comments = $result->fetch_all(MYSQLI_ASSOC);
    ?>

    А после этого уже их выводить
    <?php if ($comments): ?>
        <?php foreach ($comments as $row): ?>
             <?=$row['comment']?><br>
        <?php endforeach ?>
    <?php else: ?>
        Эту запись еще никто не комментировал
    <?php endif ?>


    Как видно, никакой mysqli_num_rows нам не понадобилось
    Ответ написан
    9 комментариев
  • Объект Шредингера - почему нет объекта, если визуально данные есть, как в итоге эти данные получить?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега JavaScript
    document.querySelector('iframe.side-panel-iframe').contentWindow.document.querySelector('.your-selector')
    Ответ написан
    Комментировать