воскресенье, 29 июля 2018 г.

Кастомизация bitrix:form.result.new — кое-что изменилось.


Предупреждение: эта статья создана в процессе разработки сайтов в компании Smart Traffic, в которой в настоящее время я работаю в должности разработчика Битрикс, и напечатана с разрешения руководства компании. Первоначально она была опубликована на нашем сайте.
Smart Traffic — интернет-агентство, предлагающее комплексное обслуживание в сфере интернет-маркетинга. Мы - отличная команда профессионалов, рекомендую обращаться к нам, если Вам необходимо разработать сайт на CMS 1С-Битрикс!

* * * 

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

Чаще всего, например, используется кастомизация компонента bitrix:form.result.new, находящегося в составе комплексного компонента bitrix.form. На первый взгляд, ничего необычного — кастомизируем также точно, как и любой компонент. Однако, не все так гладко, практика показывает, что кое-что работает по-другому. Где это прописано в ядре Битрикса — а бог его знает. Да и нужно ли в этом разбираться? Ведь все равно дорабатывать ядро мы не будем, помня о том, что все это затрется при первом же обновлении. Мы будем кастомизировать bitrix:form.result.new методом, похожим на стандартную кастомизацию, но все же отличающимся в некоторых деталях — мы учтем то, что не сработает, и обойдем эти проблемы.

Во-первых, как подключить собственный шаблон bitrix:form.result.new? И это не тот «собственный шаблон», который мы видим на странице настройки формы http://ваш_сайт.рф/bitrix/admin/form_edit.php?lang=ru&ID=ид_формы на вкладке «шаблон формы», где мы можем отметить опцию «использовать свой шаблон формы». Тут, насколько я понимаю, на самом деле собирается шаблон на основе стандартного табличного же шаблона с помощью специального конструктора — меняется количество и типы полей формы, встроенные в разметку стили, затем шаблон сохраняется, суда по всему, в базу данных, а не в файловую структуру сайта. И потом подключается оттуда. Как быть, если нам это не подходит — у нас уже есть сделанная верстальщиком блочная разметка формы, например?

Смотрим внутрь компонента bitrix:form. Видим, что как и в обычном комплексном компоненте, вложенный компоненет bitrix: form.result.new, например, подключается в файле new.php, который имеет такой вид:
<?
if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
$APPLICATION->IncludeComponent("bitrix:form.result.new", "", $arParams, $component);
?>
Казалось бы, все ок. Создадим собственный шаблон bitrix:form.result.new и подключим его в этом вызове, например, так:

$APPLICATION->IncludeComponent("bitrix:form.result.new", "moy_shablon", $arParams, $component);

Но нет! Не подключает,ся собака страшная! Возможно, это как-то связано с версионностью Битрикса на сайте, и в более новых версиях это возможно. Но у меня в данном конкретном случае использовалась версия 16.5.13 и обновлять ее было не желательно. Как ни бился, собственный шаблон через этот вызов подключить не удалось, всегда и неизменно подключается только дефолтовый шаблон, который лежит по адресу ваш_сайт.рф/bitrix/templates/ваш_шаблон_сайта/components/bitrix/form/ваш_шаблон_комплексного_компонента_форм/bitrix/form.result.new/.default ! Даже если в настройках комплексного компонента bitrix:form убирать опцию "IGNORE_CUSTOM_TEMPLATE" => "Y" — все равно не работает, по крайней мере на этом конкретном сайте! Очевидно, что эта настройка, предназначена, чтобы блокировать подключение тех кастомных шаблонов, которые сделаны в конструкторе в админке и хранятся в базе данных.

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

Итак, чтобы обойти тот момент, что кастомный шаблон bitrx:form.result.new не желает подключаться в вызове файла new.php делаем так — кастомизируем сам шаблон ваш_сайт.рф/bitrix/templates/ваш_шаблон_сайта/components/bitrix/form/ваш_шаблон_комплексного_компонента_форм/bitrix/form.result.new/.default . А на странице настроек шаблона оставим отмеченной опцию «использовать стандартный шаблон». И после кастомизации обычно все работает. Способ кастомизации достаточно подробно описан в интернете, все детали его обсужать не будем. Остановимся только на том моменте, который вызвал — причем не сразу, а со временем, - ошибку работы формы. А именно — первое, что делается очень часто (хотя и не всегда) в кастомном шаблоне — строка кода <?=$arResult[«FORM_HEADER»] ?>, которая отвечает за показ заголовка формы, подменяется на что-то такое:

<form id="bp1" name="<?=$arResult["WEB_FORM_NAME"]?>" action="<?=POST_FORM_ACTION_URI?>" method="POST" enctype="multipart/form-data" class="contact-form" novalidate="novalidate">
<input type="hidden" name="WEB_FORM_ID" value="<?=$arParams["WEB_FORM_ID"]?>">
<?=bitrix_sessid_post()?>

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

https://dev.1c-bitrix.ru/api_help/main/functions/other/bitrix_sessid_post.php

https://dev.1c-bitrix.ru/api_help/main/functions/other/check_bitrix_sessid().php

В данном конкретном случае к тегу form был добавлен id="bp1", который ни для чего другого не служит, кроме как для подключения нужных стилей к самой форме и вложенным в нее элементам разметки. Все остальные строки кода в кастомном шаблоне нас не интересуют в данном случае — они зависят от конкретной реализации разметки шаблона. Итак, разработчик сделал это до того, как сайт попал в мои руки. Сделано было грамотно, и все работало какое-то время, кажется, даже несколько лет. И в один «прекрасный» момент работать перестало. Форма перестала правильно отрабатывать, выбрасывая сообщение, что данные получены, в результаты формы перестало писаться все, что нужно. Ну и понятно, что и почтовый шаблон перестал отрабатывать, потому что форма не пишется вообще. Некоторое время я потратил на то, чтобы 1) проверить все настройки самой формы в админке 2) проверить кастомизированный шаблон bitrix:form.result.new, в котором я не нашел ничего криминального. Затем стал отлаживать форму по частям. Первым делом, перенастроил код — подсунул шаблону .default стандартную табличную форму — и о чудо! Табличная форма отрабатывает нормально! Только вид у нее, естественно, «табличный», то есть никакой вообще. Ну что же, стал я тогда брать значимые куски кода из кастомной формы и подставлять их в стандартный табличный шаблон, ожидая, когда же он помрёт. Повезло практически сразу — подстановка приведенного выше куска, отвечающего за заголовок формы, вместо <?=$arResult[«FORM_HEADER»] ?>, сразу же вызвало ту же самую ошибку — форма перестала отправляться! Попробовал убрать из него из тега form параметр id="bp1" — а вдруг ядро битрикс где-то там внутри себя использует идентификаторы для обработки формы, и мой идентификатор этому мешает? Нет, не помогло. Конечно же, сразу пришло в голову, что что-то изменилось в ядре, что отвечало за обработку форм. И было бы в будущем очень интересно понять — а что же именно. Но — это задача академическая, так сказать. А заказчик сайта ждет, когда же бедные погибшие формы заработают. И ждет чтобы это случилось как можно скорее! Поэтому на этот раз я просто обошел проблему — заменил кастомно прописанный заголовок обратно на стандартный вызов заголовка формы, но для сохранения стилизации заключил его в div с тем же самым id, который до этого был в теге form. Вот как это стало выглядеть:

<div id="bp1">
<?=$arResult["FORM_HEADER"]?>
… собственно код кастомизированной формы …
<?=$arResult["FORM_FOOTER"]?>
</div>

И о чудо! Все заработало, хотя казалось бы это должно было быть одно и тоже, но оказалось, что не одно. Надо сказать, что проблема на этом сайте возникла после одного из обновлений Битрикс. Вот такой вот он Битрикс — странный и загадочный! И не всегда обратно совместимый — особенно если кастомизированный. 

понедельник, 11 июня 2018 г.

ГЕНЕРАЦИЯ ДОКУМЕНТОВ DOCX ИЗ PHP С ПОМОЩЬЮ БИБЛИОТЕКИ PHPWORD

Предупреждение: эта статья создана в процессе разработки сайтов в компании Smart Traffic, в которой в настоящее время я работаю в должности разработчика Битрикс, и напечатана с разрешения руководства компании. Первоначально она была опубликована на нашем сайте.
Smart Traffic — интернет-агентство, предлагающее комплексное обслуживание в сфере интернет-маркетинга. Мы - отличная команда профессионалов, рекомендую обращаться к нам, если Вам необходимо разработать сайт на CMS 1С-Битрикс!
* * *
На первый взгляд, генерация документов docx не представляет какой-то особенной сложности — есть известная библиотека PHPOffice/PHPWord, есть документация с подробным ее описанием. Как говорится, берем и делаем. Не тут-то было.
Итак, поступила задача по доработке одного из сайтов, а именно — при генерации документов в текстовом формате Microsoft получается ошибка. Документ формируется, но не открывается. При попытке открыть файл мы иногда видим окно сообщения, где указано, что в файле присутствуют не закрытые либо не имеющие открывающего тега закрывающие теги (то есть «не открытые»). Причем для разных файлов теги и их расположение разные и некоторые файлы открываются, а некоторые — нет. В чем дело и как быть дальше?
Первым делом, я решил посмотреть, какие теги вызывают проблемы. Для этого нужно распаковать документ docx. Поскольку я пользуюсь для разработки ubuntu linux на рабочем месте, то делаю это, например, командой:
7z a -tzip файл.docx -mx0 ./директория/*
Если же вы используете Windows, воспользуйтесь архиватором 7zip или WinRAR (любым).
Надо сказать, что файл docx на самом деле обыкновенный архив, внутри которого уже после распаковки мы увидим массу разных файлов, служебных и содержательных, преимущественно, это — xml файлы. Среди них находим тот файл, который содержит непосредственно контент — document.xml. Просматриваем те теги, которые указаны в ошибке, и видим, что в качестве текста в узлах, помимо обычного текста, вставлены теги <p>, </p> и <br />, <br>. Откуда же они взялись? Поскольку генератор был написан ранее неизвестным разработчиком, смотрим в него, и видим, что данные для генерации получаются из различных полей элемента инфоблока Битрикс. Подробно это рассматривать неинтересно, у каждого сайта это могут быть другие поля и другой программный код, который получает их для добавления в генерируемый документ.
Достаточно сказать, что этот элемент находится в выводе компонента на странице, где формируется заказ товаров в админке менеджера. В нашем случае проблемное поле получалось в php из поля элемента (товара) «детальное описание». Смотрим в каталоге Битрикс детальные описания вызвавших проблемы товаров, и, действительно, видим, что контент-менеджеры вставили там тексты в формате html с указанными тегами, причем очень часто теги эти, действительно, не закрыты или не открыты — банальная ошибка при написании html. Однако, исправлять вручную это совершенно невозможно — на сайте более 10 тысяч товаров, кроме того, в будущем ошибочное заполнение также не исключено. Следовательно — необходимо предусмотреть программное исправление ошибок, насколько это возможно.
Прежде всего, пишем функцию для закрытия/открытия тегов. Однако, после ее тестирования обнаруживаем пару интересных моментов. Первое — иногда одиночные теги в тексте расставлены контент-менеджером так, что и не поймешь, что он хотел заключить внутрь параграфа, так что и программно функция тоже этого не может вычислить, и тогда наша функция просто закрывает одиночный тег пустым. Получаются пустые параграфы. С этим мы просто вынуждены смириться. В конце концов, при необходимости — контент-менеджер откроет отдельную карточку товара и сам поправит нужный параграф. Ни программист, ни даже никакая самая лучшая программа мысли читать не умеет. Второе — теги переноса строк <br> и <br /> попадают в текст узлов xml как часть текста, а не как реальные переносы строк, и это тоже вызывает ошибки — внутри текста генератор документа просто не знает, что с ними делать. Поэтому я добавил в функцию замену их и пустых параграфов на перенос строки «\n». Кроме того, нам не нужны были ссылки в формируемых документах, поэтому их я тоже удалил. Получилась такая функция:
function closetags($text)
{
// Выбираем абсолютно все теги
if (preg_match_all("/<([/]?)([wd]+)[^>/]*>/", $text, $matches, PREG_SET_ORDER))
{
$stack = array();
foreach ($matches as $k => $match)
{
$tag = strtolower($match[2]);
if (!$match[1])
// если тег открывается добавляем в стек
$stack[] = $tag;
elseif (end($stack) == $tag)
// если тег закрывается, удаляем из стека
array_pop($stack);
else
// если это закрывающий тег, который не открыт, открываем
$text = '<'.$tag.'>'.$text;
}
while ($tag = array_pop($stack))
// закрываем все открытые теги
$text .= '</'.$tag.'>';
}

$text = str_replace("<br />", "\n", $text);
$text = str_replace("<br>", "\n", $text);
$text = str_replace("<p></p>", "\n", $text);


//удаляем ссылки из документов
$pattern = "/<a href=[\"|']([^\"]*)[\"|']>(.*)<\/a>/iU";
return preg_replace($pattern, "", $text);
}

Теперь все хорошо, формируются все документы docx, независимо от того, есть ошибки в тегах в дополнительном описании товара в каталоге или нет их. Однако оказалось, что это еще не вся проблема.
Формируемый документ состоит не только из данных, получаемых из поля дополнительного описания, обрабатывается еще несколько разных полей с информацией. И вот между ними в нужных местах должны быть переносы строк, а их нет — текст получается в одну строку. При этом, что интересно, в отдельных местах текста переносы работают, а других — нет. Открываем генератор текста из php, и смотрим, какие строки за это отвечают. Находим два варианта формирования переноса стандартным для PHPWord способом:
1) $section→addTextBreak();

2) $textRun = $section→addTextRun();

то есть, вначале создаем текст, чтобы добавлять в него все, что потребуется, а затем делаем:
 $textRun→addTextBreak();

И вот второй-то способ АБСОЛЮТНО не работает.
Странно — идем на сайт PHPWord и ищем документацию.
Генерация документов docx из php с помощью библиотеки PHPWord

На самом верху страницы https://phpword.readthedocs.io/en/latest/elements.html#breaks видим гордые плюсики для Text Break для всех типов элементов! Однако не поторопились ли разработчики? Может быть, все дело в том, что наш генератор разрабатывался довольно давно, и библиотека устарела с тех пор, как ее поставили на этот сайт? Действительно, в описаниях релизов за несколько последних лет можно найти, что по крайней мере один раз исправлен баг в Text Break, может быть, это он? Ничего подобного — обновляю библиотеку PHPWord на сайте до самой последней версии, и все равно перенос не работает!
В итоге пришлось полностью переработать генератор документа docx, исключив из него вообще Text Run и воспользовавшись только Section, с которым перенос работал нормально.
Вывод: не всегда следует верить задокументированным функциям даже у хорошо известных и проверенных программных продуктов. Конечно, вполне возможно, что на работоспособность повлияли какие-то особенные настройки php именно на данном конкретном сервере на данном сайте — но такие моменты тоже должны быть поштучно выявлены разработчиками и отражены в документации для PHPWord или любой другой программы.

суббота, 2 декабря 2017 г.

Установка Linux Mint 18.3 на ноутбук Dell

Так получается, что последнее время публиковать что-то в блоге не удается. Очень много работы, которая разработчиков Битрикс любит. Тем не менее, решил поделиться решением проблемы, которая наверняка важна для многих, хотя и не относится к разработке сайтов, а так - чисто прикладная.
В общем понадобилось заменить старый нетбук ASUS x101ch, верой и правдой прослуживший моей жене несколько лет. Уже и оперативки 1ГБ стало ей очень мало, износ значительный, да ubuntu плохо работает уже на таких ресурсах, разве что старые версии, что совсем не комильфо - поддержка тех, что нормально работали, прекратилась давно, а новые - слишком тяжелые. Решил отдать ей недавно приобретенный для себя Ноутбук DELL Inspiron 3552, 15.6", Intel Pentium N3710 1.6ГГц, 4Гб, 500Гб, Intel HD Graphics 405. Конечно, поставив ей предварительно Linux. Она не системный администратор, но все же пользователь опытный, да вообще, я уже несколько лет, как пересадил всех моих родственников на ubuntu, а затем на linux mint - и им хорошо, все работает, и мне хорошо - надоело как-то постоянно чистить и чинить проклятый windows, который с годами становился только хуже и хуже. Всем нравится - даже теще и тестю, освоили быстро, несмотря ни на что.
Однако, получил некоторую головную боль. Прошло много лет, как я ставил Linux с нуля, и я не ожидал проблем с поддержкой драйверов ни у ubuntu, ни у Linux Mint - никогда раньше этого не было со мной. Это "проприетарно чистые" сборки или близкие к ним по духу, типа Debian или Fedora до сих пор далеко-о-о-о не на все ставятся без напильника (как бы ни спорили со мной их любители, ИМХО). И тут на тебе - не ставится Linux Mint. Надо сказать, что я заранее записал все три образа - LMDE2, Linux Mint Cinnamon и Mate 18.3 - просто было любопытно на них посмотреть. LMDE2 отсек сразу - он построен на Debian и даже в загруженном без установки образе категорически отказался показывать мне индикатор загрузки клавиатуры, хотя в настройках он был включен. Я не был настроен тратить время на танцы с бубном, и также просмотрев Mate и Cinnamon, остановился на Cinnamon - он уже стоит на десктопе у родителей жены, и она к нему привыкла. Стал ставить его. Однако, при установке все прошло нормально, но при первой же загрузке возник трабл - загрузка доходила до графического значка загрузки, после чего - черный экран даже без курсора. Причем если вместо несостоявшейся загрузки перейти в режим восстановления и восстановить grub, на что я вначале погрешил, - далее загрузка проходила нормально и рабочий стол появлялся. Правда - в режиме программного рендеринга. Изучив логи, я понял, что драйверов Intel работоспособных для встроенной видеокарты этого типа нет. Ну черт с ними - будет нагружаться процессор вместо видюхи, ну да ладно, сойдет и загружаемая vesa. Но почему не грузится система сразу - только после восстановления? Попробовал при загрузке перейти в режим редактирования загружаемой строки (нажимаем e на нужном пункте меню, если кто подзабыл) - добавил, как обычно в таких случаях (многие уже об этом писали) параметр ядра nomodeset - и отлично себе загрузился до появления рабочего стола. Ага, надо добавить его в постоянку. Делаю как обычно nano /etc/default/grub, update-grub добавив этот параметр в загрузку ядра линукс. Перезагружаюсь, предвкушая счастье - и не тут-то было! Сначала загрузка идет как надо, а потом - белый-белый экран с едва намеченным черным курсором и полный аут до бесконечности. Вот те раз - а почему? Ладно - иду снова в настройки grub и убираю парамеры ядра splash quiet - пусть-ка мне выведет текстовую отладку при загрузке. Перезагружаюсь - уффф! Вместо вменяемого текста или даже кракозябров ползет по экрану что-то непонятное - вроде буквы - не буквы, а что-то на них похожее, только сильно смазанное, черное на белом же фоне, как будто те же буквы написанные чернилами на листе, кто-то прижал сверху промокашкой и слегка пошевелил ею.
Однако, понятно стало мне, что это grub2, собака страшная, в какой-то момент переходит от текстовой загрузки к графической, а поскольку для данного типа видеокарты, видимо, не может понять, какое разрешение выбрать, и получается такая ерунда. Снова иду в настройки grub и пишу GRUB_TERMINAL=console, перезагружаюсь. И - о чудо, эта зараза не только показала мне читаемую отладку загрузки, но и далее спокойно прогрузилась до нормального рабочего стола. Потестировал аж три-четыре раза с горячей и холодной перезагрузкой, чтобы убедиться, что эффект не исчез. После чего вернул GRUB_CMDLINE_LINUX_DEFAULT="quiet splash", чтобы снова отключить вывод отладки, и на том система загружается и работоспособная. Жаль, конечно, что нет драйверов Linux под такую видюху Intel, которые не вызывали бы проблем - я имею ввиду проприетарных или хорошо заточенных, а не vesa с вынужденным программным рендерингом. Ну да пока и так работает неплохо - в тяжелые игры на этом ноуте никто играть не будет.
Пишу все это, чтобы помочь тем бедолагам, которые в этом не разобрались - пусть порадуются, что можно хоть как-то запуститься в таком варианте. Да и я сам раньше встречал, конечно, чтобы графический режим загрузки grub вызывал проблемы, не показывая каких-то промежуточных этапов загрузки, но только до тех пор, пока совсем ОСь не прогрузится до рабочего стола. Но чтобы это совсем подвешивало загрузку - такого еще не видел. Вот так-то.
Век живи - век учись. Большой нереспект и неуважуха разработчикам драйверов для встроенных видеокарт Intel процессоров. Отстаете, господа, от жизни. Таких процов сейчас много, и ставят их куда попало - и на ноуты, и на десктопы. А ведь надо спасать бедных граждан от гнусного Windows, а как это сделать, если Linux Mint или Ubuntu "ис каропки" даже не ставится без плясок с бубном. У моих родственников есть я, а у тысяч и тысяч бедных простых пользователей - кто их поможет? ☺

вторник, 12 мая 2015 г.

Как добавить слеш в конце URL в 1С-Битрикс.

     Проблема эта возникает не только в Битрикс, но в любых CMS или фреймворках. Главная неприятность - поисковики не могут найти и проиндексировать страницу вида http://page, которая должна отсылаться на страницу вида http://page/ - то есть со слешем на конце. Для большинства сайтов задача решается довольно просто, и решение это не раз приводилось в Интернет - например, вот здесь. С той же самой проблемой я столкнулся и на сайтах, которые у меня на техподдержке - http://casinojet.ru , http://casinojet.net и http://english-yes.ru . Но со своими особенностями - я обнаружил, что в Битрикс страницы при добавлении слеша известными методами ведут себя не так, как обычно.
     Самое интересное - на первом уровне вложенности, как правило, Битрикс слеши добавляет. Где это спрятано в системе - я искать не стал, имхо Битрикс очень большой, искать замучаешься. Добавляет их, как правило, и на большем числе уровней вложенности, если каталоги и страницы созданы вручную через административный или публичный интерфейс - ну то есть страницы вида http://catalog1/catalog2/page вполне нормально перекидываются на такую же страничку, но со слешем в конце. А вот если некоторое количество секций созданы комплексным компонентом Битрикс, неважно каким - каталогом, торговым каталогом, новостями и т.п. - то ни страницы секций, ни детальные страницы слеш не добавляют. Следовательно, при автоматическом формировании страниц встроенный механизм не работает, и нужно добавлять слеши вручную.
     Итак, тестируем приведенное выше решение из интернета, пытаемся добавить слеши вручную.
     Способ 1 - изменить файл .htaccess таким образом:
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^(.*[^/])$ $1/ [L,R=301]

категорически мне не понравился. Работает тоже только на первом уровне вложенности, хотя, конечно, можно добавить подобное правило и для других значений RewriteBase, но тогда черт ногу сломит в правилах .htaccess и все время нужно следить, чтобы нужные правила не взаимодействовали как-нибудь криво с другими правилами оттуда же. У меня, например, при добавлении этого правила, в самой админке Битрикса (о ужас!) произошли изменения - остались видны верхнее и левое меню, а в рабочей области на всех пунктах меню, куда ни перейди, появлялась волшебная надпись "сорри, такой ресурс не обнаружен". Вот вам и .htaccess. Убрал это правило на...
     Способ 2 - добавить слеш программно интуитивно показался мне тем, что надо. Ведь можно же тогда прикрутить к нему какие угодно pHp условия и использовать его очень гибко, подумал я. Но все оказалось еще проще. Автор вышеприведенной статьи предложил сделать это вот так:
// Получаем URI для проверки
$uri = preg_replace("/\?.*/i",'', $_SERVER['REQUEST_URI']);
 
if (strlen($uri)>1) {// если не главная страница...
  if (rtrim($uri,'/')."/"!=$uri) {
    header("HTTP/1.1 301 Moved Permanently");
    header('Location: http://'.$_SERVER['SERVER_NAME'].str_replace($uri, $uri.'/', $_SERVER['REQUEST_URI']));
    exit();    
  }
}

Замечательно. Ставим - работает. Но - опять же только на первом уровне вложенности или на вручную созданных секция, подсекциях и так далее. На автоматически формируемых компонентах - категорически не хочет. Ясно - у Битрикса собственный алгоритм формирования не только страниц, но и адресов их - в зависимости от того, компонент их формирует или сделаны они вручную. Искать смысла нет, где и как это сделано, проще перезаписать все это там, где Битрикс позволяет кастомизацию, и сделать это как можно проще и техничнее. Кроме того, мне не понравилась вторая строка с функцией header - почему так сложно? И я упростил ее, так что решение у меня стало выглядеть вот так:
<?
// Программное добавление слеша к адресам. Получаем URI для проверки
$uri = preg_replace("/\?.*/i",'', $_SERVER['REQUEST_URI']);
 
if (strlen($uri)>1) {
  if (rtrim($uri,'/')."/"!=$uri) {
    header("HTTP/1.1 301 Moved Permanently");
    header('Location: http://'.$_SERVER['SERVER_NAME'].$uri.'/');
    exit();    
  }
}
?>

Далее, я добавил его в шаблон (там где и в Битриксе слеши почему-то не добавлялись - кто знает, кто и когда и с какими настройками ставил разные версии Битрикса на всех нужных мне сайтах, - они стали добавляться на первом уровне вложенности). А далее - я зашел на каждую страницу, где были компоненты, обнаружил, что они создают собственные индексные страницы вида http://section/index.php и добавил эту конструкцию в самый верх каждой независимой индексной страницы. После чего слеши стали добавляться автоматически и редирект срабатывал уже на любом количестве уровней вложенности, например, и на страницах вида http://section/subsection/.../page, сколько бы уровней ни генерировал автоматически данный комплексный компонент.

вторник, 31 марта 2015 г.

Адаптация счетчика посещений сайта VINAORA (joomla!) для любых сайтов.

     В настоящее время как-то больше принято пользоваться счетчиками посещаемости сайта от известных поисковых систем или хороших интернет-каталогов. Однако, иногда возникает необходимость использовать собственные счетчики. Чаще всего это - пожелание владельца сайта, связанное с дизайном сайта. Ну нравится вот такой счетчик людям - и тогда нужно писать свой или использовать один из многочисленных, представленных в Интернет. Но иногда попадаются нетривиальные задачи. Так случилось и в этот раз. Мне поставили задачу перенести сайт Студии иностранных языков YES Studio с очень устаревшей версии CMS joomla! 1.0 на 1С-Битрикс. Все бы хорошо, сайт я перенес с некоторой доработкой дизайна (например, добавил слайдер на CSS3) - но остался счетчик работы VINAORA , который очень нравился владельцу сайта. И в самом деле - в счетчике есть вывод посетителей за текущий и прошлый день, текущую и прошлую неделю, текущий и прошлый месяц и общее число посетителей за все время. Дизайн же можно менять в широких пределах, если хоть немного владеешь pHp. В принципе, можно было бы поискать и в сети счетчик для Битрикс с аналогичными данными, изменив его дизайн, или же использовать встроенный сбор статистики Битрикс, тоже доработав дизайн. Но - модуль веб-аналитики в Битрикс настолько скверный, что после его установки сайты замедляют работу в десятки раз (!), причем техподдержка Битрикс никак это не комментирует и не реагирует на претензии к модулю. А второе - на старой версии переносимого сайта накопилась хорошая база данных посещаемости за длительный срок, которую владелец сайта никак не хотел потерять. Так что проще было переписать счетчик так, чтобы он был пригоден для работы не только в joomla! , но и в любой CMS (или вообще без CMS). Проблема была только в том, чтобы заменить в коде счетчика вызовы специфичных функций из объектной модели joomla! на аналогичный по функционалу код pHp, не зависимый от CMS вообще.
     Итак, первое, что я сделал - просто перенес базу данных на новую версию сайта, чтобы сохранить всю статистику.
     Затем берем код счетчика для joomla!:

<?php
/**
* @version $Id: helper.php 2009-05-19 vinaora $
* @package VINAORA VISITORS COUNTER
* @copyright Copyright (C) 2007 - 2009 VINAORA.COM. All rights reserved.
* @license GNU/GPL
* @website http://vinaora.com
* @email admin@vinaora.com

* @warning DON'T EDIT OR DELETE LINK HTTP://VINAORA.COM ON THE FOOTER OF MODULE. CONTACT ME IF YOU WANT.
*
*/

// no direct access
defined( '_JEXEC' ) or die( 'Restricted access' );
?>
<?php
require_once( dirname(__FILE__) . DS . "browsers.php" );

// Birth day of Joomla: 15 Sept 2005
define( 'BIRTH_DAY_JOOMLA' , 1126713600 );

class modVisitCounterHelper
{
function render(&$params)
{
global $mainframe;

/* ------------------------------------------------------------------------------------------------ */
// Read our Parameters
$today = @$params->get( 'today', 'Today' );
$yesterday = @$params->get( 'yesterday', 'Yesterday' );
$x_week = @$params->get( 'week', 'This week' );
$l_week = @$params->get( 'lweek', 'Last week' );
$x_month = @$params->get( 'month', 'This month' );
$l_month = @$params->get( 'lmonth', 'Last month' );
$all = @$params->get( 'all', 'All days' );

$beginday = @$params->get( 'beginday', '' );

$online = @$params->get( 'online', 'Online Now: ' );
$guestip = @$params->get( 'guestip', 'Your IP: ' );
$guestinfo = @$params->get( 'guestinfo', 'Your: ' );
$formattime = @$params->get( 'formattime', 'UTC: %Y-%m-%d %H:%M' );

$digit_type = @$params->get( 'digit_type', 'default' );
$stats_type = @$params->get( 'stats_type', 'default' );

$widthtable = (int) @$params->get( 'widthtable', 90 );

$sessiontime = (int) @$params->get( 'sessiontime', 10 );

$initialvalue = (int) @$params->get( 'initialvalue', 1 );

$issunday = @$params->get( 'issunday', 1 );

$deldays = (int) @$params->get( 'deldays', 0 );

$delday = (int) @$params->get( 'delday', 15 );

$pretext   = @$params->get( 'pretext', "" );
$posttext   = @$params->get( 'posttext', "" );

$iscache = @$params->get( 'iscache', 0 );
$checkdatabase = @$params->get( 'checkdatabase', 1 );

/* ------------------------------------------------------------------------------------------------ */



/* ------------------------------------------------------------------------------------------------ */
$s_today = modVisitCounterHelper::showParam( $today );
$s_yesterday = modVisitCounterHelper::showParam( $yesterday );
$s_week = modVisitCounterHelper::showParam( $x_week );
$s_lweek = modVisitCounterHelper::showParam( $l_week );
$s_month = modVisitCounterHelper::showParam( $x_month );
$s_lmonth = modVisitCounterHelper::showParam( $l_month );
$s_all = modVisitCounterHelper::showParam( $all );

$s_online = modVisitCounterHelper::showParam( $online );
$s_ip = modVisitCounterHelper::showParam( $guestip );
$s_guestinfo = modVisitCounterHelper::showParam( $guestinfo );
$s_timenow = modVisitCounterHelper::showParam( $formattime );

$s_digit = modVisitCounterHelper::showParam( $digit_type );
$s_delrecords = modVisitCounterHelper::showParam( $deldays );
/* ------------------------------------------------------------------------------------------------ */


// From minutes to seconds
$sessiontime = $sessiontime * 60;

// Get Time Offset from Global Configuration of Joomla!
$offset = $mainframe->getCfg( 'offset' );

// Get a reference to the global cache object.
$cache = & JFactory::getCache();

// Check if jos_vvisitcounter table exists. When not, create it
if ( $checkdatabase ){
if ( $iscache ){
$cache->call( array( 'modVisitCounterHelper', 'createTable' ) );
}
else{
modVisitCounterHelper::createTable();
}
}


/* ------------------------------------------------------------------------------------------------ */
// Detect Guest's IP Address and Insert new records
$ip = "0.0.0.0";
if(!empty($_SERVER['REMOTE_ADDR'])) $ip = $_SERVER['REMOTE_ADDR'];

$now = & JFactory::getDate(  );

// Now we are checking if the ip was logged in the database. 
// Depending of the value in minutes in the sessiontime variable.
// Check session time, insert new record if timeout
modVisitCounterHelper::insertVisitor( $sessiontime, $ip, $now->toUnix() );

// Update $now
$now = & JFactory::getDate(  );

/* ------------------------------------------------------------------------------------------------ */


/* ------------------------------------------------------------------------------------------------ */
// Determine TIME
// Determine this hour, this day, this month, this year
$minute = (int) $now->toFormat( "%M" );
$hour = (int) $now->toFormat( "%H" );
$day = (int) $now->toFormat( "%d" );
$month = (int) $now->toFormat( "%m" );
$year = (int) $now->toFormat( "%Y" );

// Determine Starting Time of Today
$daystart = mktime( 0,0,0,$month,$day,$year );
$local_daystart = modVisitCounterHelper::localTimeStart( $daystart, $offset, "day");

// Starting Time of Yesterday
$yesterdaystart = strtotime( "-1 day", $daystart ) ;
$local_yesterdaystart = strtotime( "-1 day", $local_daystart ) ;

// Starting Time of This Week
// If Sunday is starting day of week then Sunday = 0 ... Saturday = 6
// If Monday is starting day of week then Monday = 0 ... Sunday = 6
$weekday = (int) $now->toFormat( "%w" );
if ( !$issunday ) {
if ( $weekday ) $weekday--;
else $weekday = 7;
}
$weekstart = strtotime( "-$weekday days", $daystart );
$local_weekstart = modVisitCounterHelper::localTimeStart( $weekstart, $offset, "week");

// Starting Time of Last Week
$lweekstart = strtotime( "-1 week", $weekstart ) ;
$local_lweekstart = strtotime( "-1 week", $local_weekstart ) ;

// Determine Starting Time of This Month
$monthstart = mktime( 0,0,0,$month,1,$year );
$local_monthstart = modVisitCounterHelper::localTimeStart( $monthstart, $offset, "month");

// Determine Starting Time of Last Month
$lmonthstart = strtotime( "-1 month", $monthstart );
$local_lmonthstart = strtotime( "-1 month", $local_monthstart );

/* ------------------------------------------------------------------------------------------------ */


/* ------------------------------------------------------------------------------------------------ */
// BEGIN: Caculate number visitors of Today, Yesterday, This Week, Last Week, This Month, Last Month // Count All Visitors
$all_visitors = modVisitCounterHelper::getAllVisitors();
$all_visitors += $initialvalue;

// Count Today's Visitors
$today_visitors = modVisitCounterHelper::getVisitors( $local_daystart );

// Count Yesterday's Visitors
if( $s_yesterday ){
if ( $iscache ){
$yesterday_visitors = $cache->call( array( 'modVisitCounterHelper', 'getVisitors' ), $local_yesterdaystart, $local_daystart );
}
else {
$yesterday_visitors = modVisitCounterHelper::getVisitors( $local_yesterdaystart, $local_daystart );
}
}

// Count This Week's Visitors
if( $s_week ){
if ( $iscache ){
$week_visitors = $cache->call( array( 'modVisitCounterHelper', 'getVisitors' ), $local_weekstart, $local_daystart );
$week_visitors += $today_visitors;
}
else{
$week_visitors = modVisitCounterHelper::getVisitors( $local_weekstart, $local_daystart );
$week_visitors += $today_visitors;
}
}

// Count Last Week's Visitors
if( $s_lweek ){
if ( $iscache ){
$lweek_visitors = $cache->call( array( 'modVisitCounterHelper', 'getVisitors' ), $local_lweekstart, $local_weekstart );
}
else{
$lweek_visitors = modVisitCounterHelper::getVisitors( $local_lweekstart, $local_weekstart );
}
}

// Count This Month's Visitors
if( $s_month ){
if ( $iscache ){
$month_visitors = $cache->call( array( 'modVisitCounterHelper', 'getVisitors' ), $local_monthstart, $local_daystart );
$month_visitors +=  $today_visitors;
}
else{
$month_visitors = modVisitCounterHelper::getVisitors( $local_monthstart, $local_daystart );
$month_visitors +=  $today_visitors;
}
}

// Count Last Month's Visitors
if( $s_lmonth ){
if ( $iscache ){
$lmonth_visitors = $cache->call( array( 'modVisitCounterHelper', 'getVisitors' ), $local_lmonthstart, $local_monthstart );
}
else{
$lmonth_visitors = modVisitCounterHelper::getVisitors( $local_lmonthstart, $local_monthstart );
}
}

// Count Online in 20 minutes
$online_time = 20;
if( $s_online ){
$online_visitors = modVisitCounterHelper::getVisitors( $now->toUnix() - $online_time*60 );
}

// END: CACULATE VISITORS
/* ------------------------------------------------------------------------------------------------ */



/* ------------------------------------------------------------------------------------------------ */
// BEGIN: SHOW DIGIT COUNTER
$content = '<div>';
if ($pretext != "") $content .= '<div>'.$pretext.'</div>';

// Show digit counter
if($s_digit){

$content .= '<div style="text-align: center;">';

$n = $all_visitors;
$div = 100000;
while ($n > $div) {
$div *= 10;
}

while ($div >= 1) {
$digit = $n / $div % 10;
$content .= '<img src="'.JURI::base().'modules/mod_vvisit_counter/images/digit_counter/'.$digit_type.'/'.$digit.'.png"';
$content .= ' style="margin:0; border:0; "';
$content .= ' alt="mod_vvisit_counter"';
$content .= ' title="Vinaora Visitors Counter"';
$content .= ' />';
$n -= $digit * $div;
$div /= 10;
}
$content .= '</div>';
}
// END: SHOW DIGIT COUNTER
/* ------------------------------------------------------------------------------------------------ */



/* ------------------------------------------------------------------------------------------------ */
// BEGIN: TABLE STATISTICS
if ( modVisitCounterHelper::showParam( $stats_type ) ){

$content .= '<div><table cellpadding="0" cellspacing="0" ';
$content .= 'style="margin: 3px; text-align: center; align: center; ';
$content .= 'width: '.$widthtable.'%;" class="vinaora_counter">';
$content .= '<tbody align="center">';

// Show today, yestoday, this week, this month, and all visitors
if($s_today){
$timeline = modVisitCounterHelper::showTimeLine( $local_daystart );
$content .= modVisitCounterHelper::spaceer( $stats_type, "vtoday.png", $timeline, $today, $today_visitors );
}
if($s_yesterday){
$timeline = modVisitCounterHelper::showTimeLine( $local_yesterdaystart );
$content .= modVisitCounterHelper::spaceer( $stats_type, "vyesterday.png", $timeline, $yesterday, $yesterday_visitors );
}
if($s_week){
$timeline = modVisitCounterHelper::showTimeLine( $local_weekstart, $local_daystart );
$content .= modVisitCounterHelper::spaceer( $stats_type, "vweek.png", $timeline, $x_week, $week_visitors );
}
if($s_lweek){
$timeline = modVisitCounterHelper::showTimeLine( $local_lweekstart, $local_weekstart );
$content .= modVisitCounterHelper::spaceer( $stats_type, "vlweek.png", $timeline, $l_week, $lweek_visitors );
}
if($s_month){
$timeline = modVisitCounterHelper::showTimeLine( $local_monthstart, $local_daystart );
$content .= modVisitCounterHelper::spaceer( $stats_type, "vmonth.png", $timeline, $x_month, $month_visitors );
}
if($s_lmonth){
$timeline = modVisitCounterHelper::showTimeLine( $lmonthstart, $local_monthstart );
$content .= modVisitCounterHelper::spaceer( $stats_type, "vlmonth.png", $timeline, $l_month, $lmonth_visitors );
}
if($s_all){
if ( !$beginday ) $beginday = "Visitors Counter";
$content .= modVisitCounterHelper::spaceer( $stats_type, "vall.png", $beginday, $all, $all_visitors );
}

$content .= "</tbody></table></div>";
$content .= '<hr style="width: 90%" />';
}
// END: TABLE STATISTICS
/* ------------------------------------------------------------------------------------------------ */


/* ------------------------------------------------------------------------------------------------ */
// BEGIN: SHOW GUEST'S INFO
// Show Guest's Info
if ( $s_online || $s_ip || $s_guestinfo || $s_timenow ){

$content .= '<div style="text-align: center;">';

if($s_online){
$content .= $online . " " . $online_visitors . "<br />";
}

if($s_ip){
$content .= $guestip . " " . $ip . "<br />";
}

if($s_guestinfo){
//$content .= $guestinfo;
$browser = modVisitCounterBrowser::browser();
if (!empty( $browser )){
$content .= strtoupper($browser['name']);
$content .= " ";
$content .= $browser['version'];
$content .= ", ";
$content .= strtoupper($browser['platform']) ;
$content .= "<br /> ";
}
}

if ( $s_timenow ){
$now->setOffset( $offset );
$content .= $now->toFormat( $formattime );
}

$content .= '</div>';
//$content .= '<hr>';
}


if ($posttext != ""){
$content .= '<div>'.$posttext.'</div>';
}

$content .= '<div style="text-align: center;"><a href="http://gold';
$content .= 'promo.com.ua" target="_self" title="Счетчик посеще';
$content .= 'ний сайта">Счетчик посещений</a></div>';
$content .= '</div>';
// END: SHOW GUEST'S INFO
/* ------------------------------------------------------------------------------------------------ */

echo $content;

/* ------------------------------------------------------------------------------------------------ */
// BEGIN: Delete old records if today is the 7th, 14th, 21th or 28th of the month.
if ( $s_delrecords ){
if ( ($day % 7) == 0 ){
if ( ($minute % 20) == 0 ){
$temp = $daystart - $deldays*24*60*60;
modVisitCounterHelper::delVisitors( 0, $temp );
}
}
}
// END: Delete old records
/* ------------------------------------------------------------------------------------------------ */

}


/*
** Show Statistics Table 
*/
/* ------------------------------------------------------------------------------------------------ */
function spaceer( $stats_type="default", $image, $timeline = "", $time = "", $visitors = "")
{
$ret = '<tr align="left"><td>';
$ret .= '<img src="'.JURI::base().'modules/mod_vvisit_counter/images/stats/'.$stats_type.'/'.$image.'"';
$ret .= ' alt="mod_vvisit_counter"';
$ret .= ' title="'.$timeline.'" /></td>';
$ret .= '<td>'.$time.'</td>';
$ret .= '<td align="right">'.$visitors.'</td></tr>';
return $ret;
}
/* ------------------------------------------------------------------------------------------------ */



/*
** Create Table if Not Exist
*/
/* ------------------------------------------------------------------------------------------------ */
function createTable (){
// Check if table exists. When not, create it
$query = " CREATE TABLE IF NOT EXISTS #__vvisitcounter (
id int(11) unsigned NOT NULL auto_increment, 
tm int NOT NULL, 
ip varchar(16) NOT NULL DEFAULT '0.0.0.0', 
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; ";

$db =& JFactory::getDBO();
$db->setQuery($query);
$db->query();
}
/* ------------------------------------------------------------------------------------------------ */



/*
** Insert New Visisitor
*/
/* ------------------------------------------------------------------------------------------------ */
function insertVisitor( $sessiontime, $ip, $time ){
// Check session time, insert new record if timeout
$db = & JFactory::getDBO();
$query = ' SELECT COUNT(*) FROM #__vvisitcounter ';
$query .= ' WHERE ip=' . $db->quote( $ip );
$query .= ' AND (tm + ' . $db->quote( $sessiontime ) . ') > ' . $db->quote( $time );
$db->setQuery($query);
$items = $db->loadResult();

if ( empty($items) )
{
$query = " INSERT INTO #__vvisitcounter (id, tm, ip) ";
$query .= " VALUES ('', " . $db->quote ( $time ) . ", " . $db->quote ( $ip ) . ")";
$db->setQuery($query);
$db->query();
$e = $db->getErrorMsg();
}
}
/* ------------------------------------------------------------------------------------------------ */



/*
** Get Total of Visisitors Until $time
*/
/* ------------------------------------------------------------------------------------------------ */
function getAllVisitors( $time=0 ){

$visitors = 0;
$db = & JFactory::getDBO();

// Query total visitors
$query = ' SELECT MAX(id) FROM #__vvisitcounter ';
if ( $time > BIRTH_DAY_JOOMLA ){
$query .= ' WHERE tm < ' . $db->quote( $time );
}
$db->setQuery($query);

// Total visitors
$visitors = $db->loadResult();
return $visitors;
}
/* ------------------------------------------------------------------------------------------------ */



/*
** Get Number of Visisitors from $timestart to $timestop
*/
/* ------------------------------------------------------------------------------------------------ */
function getVisitors( $timestart = 0, $timestop = 0 ){

$visitors = 0;

if ( $timestart < BIRTH_DAY_JOOMLA ) $timestart = 0;
if ( $timestop < BIRTH_DAY_JOOMLA ) $timestop = 0;

$db = & JFactory::getDBO();
$query = ' SELECT COUNT(*) FROM #__vvisitcounter ';

if ( !$timestart ){
if ( !$timestop ) return 0;
$query .= ' WHERE tm < ' . $db->quote( $timestop );
}
else{
if ( !$timestop ){
$query .= ' WHERE tm >= ' . $db->quote( $timestart );
}
else{

if ( $timestop == $timestart ){
$query .= ' WHERE tm = ' . $db->quote( $timestart );
}

if ( $timestop > $timestart ){
$query .= ' WHERE tm >= ' . $db->quote( $timestart );
$query .= ' AND tm < ' . $db->quote( $timestop );
}

if ( $timestop < $timestart ){
$query .= ' WHERE tm >= ' . $db->quote( $timestop );
$query .= ' AND tm < ' . $db->quote( $timestart );
}
}
}

$db->setQuery($query);
$visitors = $db->loadResult();

return $visitors;
}
/* ------------------------------------------------------------------------------------------------ */



/*
** Remove Visisitors from $timestart to $timestop
*/
/* ------------------------------------------------------------------------------------------------ */
function delVisitors( $timestart = 0, $timestop = 0 ){

if ( $timestart < BIRTH_DAY_JOOMLA ) $timestart = 0;
if ( $timestop < BIRTH_DAY_JOOMLA ) $timestop = 0;

$db = & JFactory::getDBO();
$query = ' DELETE FROM #__vvisitcounter ';

if ( !$timestart ){
if ( !$timestop ) return 0;
$query .= ' WHERE tm < ' . $db->quote( $timestop );
}
else{
if ( !$timestop ){
$query .= ' WHERE tm >= ' . $db->quote( $timestart );
}
else{

if ( $timestop == $timestart ){
$query .= ' WHERE tm = ' . $db->quote( $timestart );
}

if ( $timestop > $timestart ){
$query .= ' WHERE tm >= ' . $db->quote( $timestart );
$query .= ' AND tm < ' . $db->quote( $timestop );
}

if ( $timestop < $timestart ){
$query .= ' WHERE tm >= ' . $db->quote( $timestop );
$query .= ' AND tm < ' . $db->quote( $timestart );
}
}
}

$db->setQuery($query);
$db->query();
}
/* ------------------------------------------------------------------------------------------------ */



/*
** Check Parameter
** Return False if Parameter equal to "0" (zero) or "o" or "O" or empty
*/
/* ------------------------------------------------------------------------------------------------ */
function showParam( $param = "" ){

// $param is Undefined variable
if ( empty( $param ) ) return false;

// $param Defined variable
$param = strtolower( trim($param) );
if ( $param == "" ) return false;
if ( $param == "0" ) return false;
if ( $param == "o" ) return false;
return true;
}
/* ------------------------------------------------------------------------------------------------ */



/*
** Show Timeline.
** Format: %Y-%m-%d -> %Y-%m-%d
*/
/* ------------------------------------------------------------------------------------------------ */
function showTimeLine( $timeBegin = 0, $timeEnd = 0, $offset = "" ){
global $mainframe;
$str = "";
if ( empty( $offset ) ) $offset = $mainframe->getCfg( 'offset' );

if ( $timeBegin ){
$time = & JFactory::getDate( $timeBegin );
$time->setOffset( $offset );

$str1 = $time->toFormat( '%Y-%m-%d' ) ;
$str = $str1;
if ( $timeEnd ){
$time = & JFactory::getDate( $timeEnd );
$time->setOffset( $offset );

$str2 = $time->toFormat( '%Y-%m-%d' ) ;
$str .= " -> ";
$str .= $str2;
}
}
return $str;
}
/* ------------------------------------------------------------------------------------------------ */



/*
** Determine Local Starting Time.
** Return Unix Time
*/
/* ------------------------------------------------------------------------------------------------ */
function localTimeStart( $timestart, $offset=0, $type="day" ){
$now = & JFactory::getDate(  );
$type = strtolower( trim ($type) );
if ( $type != "day" && $type != "week" && $type != "month" ) $type = "day";

$nexttimestart = strtotime( "+1 " . $type, $timestart ) ;
$lasttimestart = strtotime( "-1 " . $type, $timestart ) ;

if ( $offset > 0 ) {
if ( ( $nexttimestart - $now->toUnix() ) <= $offset*60*60 ) {
$timestart = $nexttimestart - $offset*60*60;
}
else $timestart -=  $offset*60*60;
}
if ( $offset < 0 ) {
if ( ( $now->toUnix() - $timestart ) < -$offset*60*60 ) $timestart = $lasttimestart + -$offset*60*60;
else $timestart += -$offset*60*60;
}
return $timestart;
}
/* ------------------------------------------------------------------------------------------------ */

}
?>


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





<!--
* @version $Id: helper.php 2009-05-19 vinaora $
* @package VINAORA VISITORS COUNTER
* @copyright Copyright (C) 2007 - 2009 VINAORA.COM. All rights reserved.
* @license GNU/GPL
* @website http://vinaora.com
* @email admin@vinaora.com

* modified for 1C-Bitrix or any CMS different from joomla! by Andrey Yamangulov yamangulov@gmail.com
* модифицирован для 1С-Битрикс или произвольной CMS, отличной от joomla! : Андрей Ямангулов yamangulov@gmail.com
-->
  
<div> <?php

//connect to database
mysql_connect("localhost", "db_login", "db_password") or die("Connection error");
mysql_select_db("b_vvisitcounter");

class modVisitCounterHelper
{
function render(&$today, &$yesterday, &$x_week, &$l_week, &$x_month, &$l_month, &$all, &$beginday, &$online, &$guestip, &$guestinfo, &$formattime, &$digit_type, &$stats_type, &$widthtable, &$sessiontime, &$initialvalue, &$issunday, &$deldays, &$delday, &$pretext, &$posttext)
{
/* ------------------------------------------------------------------------------------------------ */
// Read our Parameters
$today = 'Today';
$yesterday = 'Yesterday';
$x_week = 'This week';
$l_week = 'Last week';
$x_month = 'This month';
$l_month = 'Last month';
$all = 'All days';
$beginday = '';
$online = 'Online Now; ';
$guestip = 'Your IP: ';
$guestinfo = 'Your: ';
$formattime = 'UTC: %Y-%m-%d %H:%M';
$digit_type = 'gold';
$stats_type = 'bronzes';
$widthtable = 90;
$sessiontime = 86400;
$initialvalue = 1;
$issunday = 0;
$deldays = 0;
$delday = 15;
$pretext   = "";
$posttext   = "Счетчик посещений";

/* ------------------------------------------------------------------------------------------------ */



/* ------------------------------------------------------------------------------------------------ */
$s_today = modVisitCounterHelper::showParam( $today );
$s_yesterday = modVisitCounterHelper::showParam( $yesterday );
$s_week = modVisitCounterHelper::showParam( $x_week );
$s_lweek = modVisitCounterHelper::showParam( $l_week );
$s_month = modVisitCounterHelper::showParam( $x_month );
$s_lmonth = modVisitCounterHelper::showParam( $l_month );
$s_all = modVisitCounterHelper::showParam( $all );

$s_online = modVisitCounterHelper::showParam( $online );
$s_ip = modVisitCounterHelper::showParam( $guestip );
$s_guestinfo = modVisitCounterHelper::showParam( $guestinfo );
$s_timenow = modVisitCounterHelper::showParam( $formattime );

$s_digit = modVisitCounterHelper::showParam( $digit_type );
$s_delrecords = modVisitCounterHelper::showParam( $deldays );
/* ------------------------------------------------------------------------------------------------ */

// Get Time Offset
$offset = date("Z", time());

/* ------------------------------------------------------------------------------------------------ */
// Detect Guest's IP Address and Insert new records
$ip = "0.0.0.0";
if(!empty($_SERVER['REMOTE_ADDR'])) $ip = $_SERVER['REMOTE_ADDR'];

$now = time();

// Now we are checking if the ip was logged in the database. 
// Depending of the value in seconds in the sessiontime variable.
// Check session time, insert new record if timeout
modVisitCounterHelper::insertVisitor( $sessiontime, $ip, $now );

// Update $now
$now = time();

/* ------------------------------------------------------------------------------------------------ */
// Determine TIME
// Determine this hour, this day, this month, this year
$minute = date("i");
$hour = date("H");
$day = date("d");
$month = date("m");
$year = date("Y");

// Determine Starting Time of Today
$daystart = mktime( 0,0,0,$month,$day,$year );
$local_daystart = modVisitCounterHelper::localTimeStart( $daystart, $offset, "day");

// Starting Time of Yesterday
$yesterdaystart = strtotime( "-1 day", $daystart ) ;
$local_yesterdaystart = strtotime( "-1 day", $local_daystart ) ;

// Starting Time of This Week
// If Sunday is starting day of week then Sunday = 0 ... Saturday = 6
// If Monday is starting day of week then Monday = 0 ... Sunday = 6
$weekday = date("w"); //(int) $now->toFormat( "%w" );
if ( !$issunday ) {
if ( $weekday ) $weekday--;
else $weekday = 7;
}
$weekstart = strtotime( "-$weekday days", $daystart );
$local_weekstart = modVisitCounterHelper::localTimeStart( $weekstart, $offset, "week");

// Starting Time of Last Week
$lweekstart = strtotime( "-1 week", $weekstart ) ;
$local_lweekstart = strtotime( "-1 week", $local_weekstart ) ;

// Determine Starting Time of This Month
$monthstart = mktime( 0,0,0,$month,1,$year );
$local_monthstart = modVisitCounterHelper::localTimeStart( $monthstart, $offset, "month");

// Determine Starting Time of Last Month
$lmonthstart = strtotime( "-1 month", $monthstart );
$local_lmonthstart = strtotime( "-1 month", $local_monthstart );

/* ------------------------------------------------------------------------------------------------ */
// BEGIN: Caculate number visitors of Today, Yesterday, This Week, Last Week, This Month, Last Month // Count All Visitors
$all_visitors = modVisitCounterHelper::getAllVisitors();
$all_visitors += $initialvalue;

// Count Today's Visitors
$today_visitors = modVisitCounterHelper::getVisitors( $local_daystart );

// Count Yesterday's Visitors
if( $s_yesterday ){
$yesterday_visitors = modVisitCounterHelper::getVisitors( $local_yesterdaystart, $local_daystart );
}

// Count This Week's Visitors
if( $s_week ){
$week_visitors = modVisitCounterHelper::getVisitors( $local_weekstart, $local_daystart );
$week_visitors += $today_visitors;
}

// Count Last Week's Visitors
if( $s_lweek ){
$lweek_visitors = modVisitCounterHelper::getVisitors( $local_lweekstart, $local_weekstart );
}

// Count This Month's Visitors
if( $s_month ){
$month_visitors = modVisitCounterHelper::getVisitors( $local_monthstart, $local_daystart );
$month_visitors +=  $today_visitors;
}

// Count Last Month's Visitors
if( $s_lmonth ){
$lmonth_visitors = modVisitCounterHelper::getVisitors( $local_lmonthstart, $local_monthstart );
}

// Count Online in 20 minutes
$online_time = 20;
if( $s_online ){
$online_visitors = modVisitCounterHelper::getVisitors( $now - $online_time*60 );
}

// END: CACULATE VISITORS

/* ------------------------------------------------------------------------------------------------ */
// BEGIN: SHOW DIGIT COUNTER
$content = '<div>';
if ($pretext != "") $content .= '<div>'.$pretext.'</div>';

// Show digit counter
if($s_digit){

$content .= '<div style="text-align: center;">';

$n = $all_visitors;
$div = 100000;
while ($n > $div) {
$div *= 10;
}

while ($div >= 1) {
$digit = $n / $div % 10;
$content .= '<img id="bxid_208413" src="'.'/modules/mod_vvisit_counter/images/digit_counter/'.$digit_type.'/'.$digit.'.png" style="margin:0; border:0; " alt="mod_vvisit_counter" title="Vinaora Visitors Counter"  />';
$n -= $digit * $div;
$div /= 10;
}
$content .= '</div>';
}
// END: SHOW DIGIT COUNTER
/* ------------------------------------------------------------------------------------------------ */



/* ------------------------------------------------------------------------------------------------ */
// BEGIN: TABLE STATISTICS
if ( modVisitCounterHelper::showParam( $stats_type ) ){

$content .= '<div><table cellpadding="0" cellspacing="0" ';
$content .= 'style="margin: 3px; text-align: center; align: center; ';
$content .= 'width: '.$widthtable.'%;" class="vinaora_counter">';
$content .= '<tbody align="center">';

// Show today, yestoday, this week, this month, and all visitors
if($s_today){
$timeline = modVisitCounterHelper::showTimeLine( $local_daystart );
$content .= modVisitCounterHelper::spaceer( $stats_type, "vtoday.png", $timeline, $today, $today_visitors );
}
if($s_yesterday){
$timeline = modVisitCounterHelper::showTimeLine( $local_yesterdaystart );
$content .= modVisitCounterHelper::spaceer( $stats_type, "vyesterday.png", $timeline, $yesterday, $yesterday_visitors );
}
if($s_week){
$timeline = modVisitCounterHelper::showTimeLine( $local_weekstart, $local_daystart );
$content .= modVisitCounterHelper::spaceer( $stats_type, "vweek.png", $timeline, $x_week, $week_visitors );
}
if($s_lweek){
$timeline = modVisitCounterHelper::showTimeLine( $local_lweekstart, $local_weekstart );
$content .= modVisitCounterHelper::spaceer( $stats_type, "vlweek.png", $timeline, $l_week, $lweek_visitors );
}
if($s_month){
$timeline = modVisitCounterHelper::showTimeLine( $local_monthstart, $local_daystart );
$content .= modVisitCounterHelper::spaceer( $stats_type, "vmonth.png", $timeline, $x_month, $month_visitors );
}
if($s_lmonth){
$timeline = modVisitCounterHelper::showTimeLine( $lmonthstart, $local_monthstart );
$content .= modVisitCounterHelper::spaceer( $stats_type, "vlmonth.png", $timeline, $l_month, $lmonth_visitors );
}
if($s_all){
if ( !$beginday ) $beginday = "Visitors Counter";
$content .= modVisitCounterHelper::spaceer( $stats_type, "vall.png", $beginday, $all, $all_visitors );
}

$content .= "</tbody></table></div>";
//$content .= '<hr style="width: 90%" />';
}
// END: TABLE STATISTICS
/* ------------------------------------------------------------------------------------------------ */


/* ------------------------------------------------------------------------------------------------ */
// BEGIN: SHOW GUEST'S INFO
// Show Guest's Info
if ( $s_online || $s_ip || $s_guestinfo || $s_timenow ){

$content .= '<div style="text-align: center;">';
$content .= '</div>';
$content .= '<hr style="margin-right: 15px;">';
}


if ($posttext != ""){
$content .= '<div style="text-align: center; color: #d01415;">'.$posttext.'</div>';
}


$content .= '</div>';
// END: SHOW GUEST'S INFO
/* ------------------------------------------------------------------------------------------------ */

echo $content;





/* ------------------------------------------------------------------------------------------------ */
// BEGIN: Delete old records if today is the 7th, 14th, 21th or 28th of the month.
if ( $s_delrecords ){
if ( ($day % 7) == 0 ){
if ( ($minute % 20) == 0 ){
$temp = $daystart - $deldays*24*60*60;
modVisitCounterHelper::delVisitors( 0, $temp );
}
}
}
// END: Delete old records
/* ------------------------------------------------------------------------------------------------ */

}


/*
** Show Statistics Table 
*/
/* ------------------------------------------------------------------------------------------------ */
function spaceer( $stats_type="default", $image, $timeline = "", $time = "", $visitors = "")
{
$ret = '<tr align="left"><td>';
$ret .= '<img id="bxid_618746" src="'.'/modules/mod_vvisit_counter/images/stats/'.$stats_type.'/'.$image.'" alt="mod_vvisit_counter" title="'.$timeline.'"  /></td>';
$ret .= '<td>'.$time.'</td>';
$ret .= '<td align="right">'.$visitors.'</td></tr>';
return $ret;
}
/* ------------------------------------------------------------------------------------------------ */

/*
** Insert New Visisitor
*/
/* ------------------------------------------------------------------------------------------------ */
function insertVisitor( $sessiontime, $ip, $time ){
// Check session time, insert new record if timeout

$query = ' SELECT COUNT(*) FROM b_vvisitcounter ';
$query .= " WHERE ip='" . $ip . "'";
$query .= ' AND (tm + ' . $sessiontime . ') > ' . $time;
$items_id = mysql_query($query);
$items = mysql_result($items_id, 0);

if ( empty($items) )
{
$query = " INSERT INTO b_vvisitcounter (id, tm, ip) ";
$query .= " VALUES ('', " . $time . ", '" . $ip . "')";
mysql_query($query);

}
}
/* ------------------------------------------------------------------------------------------------ */



/*
** Get Total of Visisitors Until $time
*/
/* ------------------------------------------------------------------------------------------------ */
function getAllVisitors( $time=0 ){

$visitors = 0;
$time = time();

// Query total visitors
$query = ' SELECT MAX(id) FROM b_vvisitcounter ';
$query .= ' WHERE tm < ' . $time;

// Total visitors
$visitors_id = mysql_query($query);
$visitors = mysql_result($visitors_id, 0);
return $visitors;
}
/* ------------------------------------------------------------------------------------------------ */



/*
** Get Number of Visisitors from $timestart to $timestop
*/
/* ------------------------------------------------------------------------------------------------ */
function getVisitors( $timestart = 0, $timestop = 0 ){

$visitors = 0;

$query = ' SELECT COUNT(*) FROM b_vvisitcounter ';

if ( !$timestart ){
if ( !$timestop ) return 0;
$query .= ' WHERE tm < ' . $timestop; //$db->quote( $timestop );
}
else{
if ( !$timestop ){
$query .= ' WHERE tm >= ' . $timestart; //$db->quote( $timestart );
}
else{

if ( $timestop == $timestart ){
$query .= ' WHERE tm = ' . $timestart; //$db->quote( $timestart );
}

if ( $timestop > $timestart ){
$query .= ' WHERE tm >= ' . $timestart; //$db->quote( $timestart );
$query .= ' AND tm < ' . $timestop; //$db->quote( $timestop );
}

if ( $timestop < $timestart ){
$query .= ' WHERE tm >= ' . $timestop; //$db->quote( $timestop );
$query .= ' AND tm < ' . $timestart; //$db->quote( $timestart );
}
}
}

$visitors_id = mysql_query($query);
$visitors = mysql_result($visitors_id, 0);
return $visitors;
}
/* ------------------------------------------------------------------------------------------------ */



/*
** Remove Visisitors from $timestart to $timestop
*/
/* ------------------------------------------------------------------------------------------------ */
function delVisitors( $timestart = 0, $timestop = 0 ){

$query = ' DELETE FROM b_vvisitcounter ';

if ( !$timestart ){
if ( !$timestop ) return 0;
$query .= ' WHERE tm < ' . $timestop; //$db->quote( $timestop );
}
else{
if ( !$timestop ){
$query .= ' WHERE tm >= ' . $timestart; //$db->quote( $timestart );
}
else{

if ( $timestop == $timestart ){
$query .= ' WHERE tm = ' . $timestart; //$db->quote( $timestart );
}

if ( $timestop > $timestart ){
$query .= ' WHERE tm >= ' . $timestart; //$db->quote( $timestart );
$query .= ' AND tm < ' . $timestop; //$db->quote( $timestop );
}

if ( $timestop < $timestart ){
$query .= ' WHERE tm >= ' . $timestop; //$db->quote( $timestop );
$query .= ' AND tm < ' . $timestart; //$db->quote( $timestart );
}
}
}

$del_visitors = mysql_query($query);
}
/* ------------------------------------------------------------------------------------------------ */



/*
** Check Parameter
** Return False if Parameter equal to "0" (zero) or "o" or "O" or empty
*/
/* ------------------------------------------------------------------------------------------------ */
function showParam( $param = "" ){

// $param is Undefined variable
if ( empty( $param ) ) return false;

// $param Defined variable
$param = strtolower( trim($param) );
if ( $param == "" ) return false;
if ( $param == "0" ) return false;
if ( $param == "o" ) return false;
return true;
}
/* ------------------------------------------------------------------------------------------------ */



/*
** Show Timeline.
** Format: %Y-%m-%d -> %Y-%m-%d
*/
/* ------------------------------------------------------------------------------------------------ */
function showTimeLine( $timeBegin = 0, $timeEnd = 0, $offset = "" ){
//global $mainframe;
$str = "";
if ( empty( $offset ) ) $offset = date("Z", time()); //$mainframe->getCfg( 'offset' );

if ( $timeBegin ){
$time_b = date( $timeBegin ); //& JFactory::getDate( $timeBegin );
//$time->setOffset( $offset );

//$str1 = $time->toFormat( '%Y-%m-%d' ) ;
//$str = $str1;
$str = $time_b;
if ( $timeEnd ){
$time_e = date( $timeEnd ); //& JFactory::getDate( $timeEnd );
//$time->setOffset( $offset );

//$str2 = $time->toFormat( '%Y-%m-%d' ) ;
//$str .= " -> ";
//$str .= $str2;
$str = $time_e;
}
}
return $str;
}
/* ------------------------------------------------------------------------------------------------ */



/*
** Determine Local Starting Time.
** Return Unix Time
*/
/* ------------------------------------------------------------------------------------------------ */
function localTimeStart( $timestart, $offset=0, $type="day" ){
$now = time(); //date("Y-m-d H:i"); //& JFactory::getDate(  );
$offset = date("Z", time());
$type = strtolower( trim ($type) );
if ( $type != "day" && $type != "week" && $type != "month" ) $type = "day";

$nexttimestart = strtotime( "+1 " . $type, $timestart ) ;
$lasttimestart = strtotime( "-1 " . $type, $timestart ) ;

if ( $offset > 0 ) {
if ( ( $nexttimestart - $now ) <= $offset ) {
$timestart = $nexttimestart - $offset;
}
else $timestart -=  $offset;
}
if ( $offset < 0 ) {
if ( ( $now - $timestart ) < -$offset ) $timestart = $lasttimestart + -$offset;
else $timestart += -$offset;
}
return $timestart;
}
/* ------------------------------------------------------------------------------------------------ */

}
?></div>

<div> 
  <br />
 </div>

<div> <?php
$hlp = new modVisitCounterHelper;
$hlp->render();
?> </div>


Понятно, что данные для входа в базу данных и имя базы данных вы меняете на свои. Кроме того, внизу я добавил создание экземпляра класса и вызов нужной функции из него, чтобы счетчик отобразился на нужной странице.
Далее в 1С-Битрикс весь код я добавил один раз в шаблон сайта. Однако, если вы будете делать не так, и если хотите, чтобы счетчик отображался только на избранных страницах, то вам нужно весь код с начала до вызова экземпляра класса вынести в отдельный файл, подключаемый в шаблоне сайта, а уже сам вызов экземпляра класса поставить только на избранных страницах.
В моем варианте я изменил код так, чтобы с одного адреса посетитель подсчитывается только раз в сутки ($sessiontime=86400 - секунд в сутках), но вы можете сделать на свое усмотрение, как и изменить дизайн вывода счетчика.
Ссылки на изображения в дизайне в тексте кода взяты из моего сайта, у вас они могут быть совсем другие, кроме того, на сайте VINAORA данный счетчик пока еще есть, но ссылки на скачивание уже недоступны. я полагаю, уже очень давно. Так что это вам придется сделать самостоятельно. Для работы счетчика достаточно приведенного мной кода плюс ваш дизайн.