Bitrix: предыдущий с последующий элементы для news.detail (через запросы)

Замечательно работавший код без кеша, с кешем компонентов, превратился в «говно», довольно часто стало такое происходить, с этим битриксом.

Посмотрев исходник и запрос, решено было его переписать в рабочий вариант (код стал легче и быстрее)
$iid = $arParams["IBLOCK_ID"];
$dpu = $arParams["DETAIL_URL"];

$q = $DB->query("SET @rank_prev = 0, @rank_cur = 0");
$q = "SELECT prev.ID as ID, prev.NAME as NAME, REPLACE('$dpu', '#ELEMENT_CODE#', prev.CODE) as LINK
FROM (SELECT @rank_cur:=@rank_cur+1 AS rank, ID, NAME, CODE FROM b_iblock_element WHERE ACTIVE = 'Y' AND IBLOCK_ID = '$iid' ORDER BY ACTIVE_FROM DESC) as cur
INNER JOIN (SELECT @rank_prev:=@rank_prev+1 AS rank, ID, NAME, CODE FROM b_iblock_element WHERE ACTIVE = 'Y' AND IBLOCK_ID = '$iid' ORDER BY ACTIVE_FROM DESC) as prev
ON prev.rank + 1 = cur.rank
WHERE cur.ID = ".$arResult["ID"];
$q = $DB->query($q);
while ($ob = $q->getNext()) $arResult["LEFT_ARROW"] = $ob;

$q = $DB->query("SET @rank_cur = 0, @rank_next = 0");
$q = "SELECT next.ID as ID, next.NAME as NAME, REPLACE('$dpu', '#ELEMENT_CODE#', next.CODE) as LINK
FROM (SELECT @rank_cur:=@rank_cur+1 AS rank, ID, NAME, CODE FROM b_iblock_element WHERE ACTIVE = 'Y' AND IBLOCK_ID = '$iid' ORDER BY ACTIVE_FROM DESC) as cur
INNER JOIN (SELECT @rank_next:=@rank_next+1 AS rank, ID, NAME, CODE FROM b_iblock_element WHERE ACTIVE = 'Y' AND IBLOCK_ID = '$iid' ORDER BY ACTIVE_FROM DESC) as next
ON cur.rank + 1 = next.rank
WHERE cur.ID = ".$arResult["ID"];
$q = $DB->query($q);
while ($ob = $q->getNext()) $arResult["RIGHT_ARROW"] = $ob;

Битрикс подкидывает проблем: b_cache_tag занимает много места, сайт тормозит

В версии 12.5 (в 14 возможно исправили, не смотрел ещё), при включенном кешировании компонентов, и большой посещаемости, табличка b_cache_tag начала набирать 15к записей в полчаса, а там одно и тоже, уникальных записей от силы 20. Из-за этого «говна», сайт начал дико тупить, на генерацию страницы уходило по 2 секунды, из-за очень многих инсертов на каждом шагу. Нормальные люди, делают инсерт — если такой записи нет, в битриксе решили не парится и народ везде на форумах, предлагает по крону транкейтить таблицу (это тупо пиздец).

Пришлось переписать функцию «виновника торжества» EndTagCache, запись добавляется только при уникальности составляющих (ибо если её просто отключить, кеширование исходит на говно). Тупой инсерт превратился в умный и красивый

function EndTagCache() {
	global $DB;
	$this->InitCompSalt();

	if($this->bWasTagged) {
		$sqlSITE_ID = $DB->ForSQL(SITE_ID, 2);
		$sqlCACHE_SALT = $this->SALT;

		$strSqlPrefix = "INSERT ".($this->isMySql? "IGNORE": "").
				" INTO b_cache_tag (SITE_ID, CACHE_SALT, RELATIVE_PATH, TAG) VALUES";
		$strSqlPrefix2 = "INSERT ".($this->isMySql? "IGNORE": "").
				" INTO b_cache_tag (SITE_ID, CACHE_SALT, RELATIVE_PATH, TAG) ";

		$maxValuesLen = $this->isMySql? 2048: 0;
		$strSqlValues = $strSqlValues2 = "";

		foreach($this->comp_cache_stack as $arCompCache) {
			$path = $arCompCache[0];
			if(strlen($path)) {
				$this->InitDBCache($path);
				$sqlRELATIVE_PATH = $DB->ForSQL($path, 255);

				$sql = ",\n('".$sqlSITE_ID."', '".$sqlCACHE_SALT."', '".$sqlRELATIVE_PATH."',";
				$sql2 = " SELECT * FROM (SELECT '".$sqlSITE_ID."', '".$sqlCACHE_SALT."', '".$sqlRELATIVE_PATH."', '#TAG#') AS tmp ".
"WHERE NOT EXISTS ( ".
"    SELECT SITE_ID FROM b_cache_tag ".
"    WHERE CACHE_SALT = '".$sqlCACHE_SALT."' ".
"    AND RELATIVE_PATH = '".$sqlRELATIVE_PATH."' ".
"    AND TAG = '#TAG#' ".
") LIMIT 1; \n";

				foreach($arCompCache[1] as $tag => $t) {
					if(!isset($this->DBCacheTags[$path][$tag])) {
						$strSqlValues .= $sql." '".$DB->ForSQL($tag, 50)."')";
						$strSqlValues2 = $strSqlPrefix2.str_replace("#TAG#",$DB->ForSQL($tag, 50), $sql2);
						$DB->Query($strSqlValues2);
						if(strlen($strSqlValues) > $maxValuesLen) {
							//$DB->Query($strSqlPrefix.substr($strSqlValues, 2));
							$strSqlValues = "";
							
						}
						$this->DBCacheTags[$path][$tag] = true;
					}
				}
			}
		}
		if($strSqlValues <> '') {
			//$DB->Query($strSqlPrefix.substr($strSqlValues, 2));
		}
	}

	array_shift($this->comp_cache_stack);
}

В итоге
INSERT IGNORE INTO b_cache_tag (SITE_ID, CACHE_SALT, RELATIVE_PATH, TAG) 
VALUES('ar', '/e25', '/ar/bitrix/news.list/06f', 'iblock_id_5')

Превращается в
INSERT IGNORE INTO b_cache_tag (SITE_ID, CACHE_SALT, RELATIVE_PATH, TAG)  
SELECT * FROM (SELECT 'ar', '/e25', '/ar/bitrix/news.list/06f', 'iblock_id_5') AS tmp 
WHERE NOT EXISTS (
     SELECT SITE_ID FROM b_cache_tag
     WHERE CACHE_SALT = '/e25'
     AND RELATIVE_PATH = '/ar/bitrix/news.list/06f'
     AND TAG = 'iblock_id_5' ) 
LIMIT 1;


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

Транслитерация в Битриксе

$tarParams = array("replace_space"=>"-","replace_other"=>"-");
$translit = Cutil::translit($arFields["NAME"],"ru",$tarParams);

Bitrix: вывод мета заголовков и описаний (meta-title, meta-description) при кешировании компонентов

Добрый день мне (все равно никто не читает)

Частенько, при включённом кешировании компонент, с помощью «магик» метода
$APPLICATION->SetPageProperty("description", $desc);
в result_modifier.php и template.php не получается ничего вывести/изменить, потому что кеш просто будет выводить первое закешированное для всех записей.

Что бы этого избежать, нужно завсети друга для result_modifier.php, под именем component_epilog.php, который выполняется мимо кеширования, каждый раз. А для того, что бы ему что-то передать, нужно в result_modifier.php добавить
global $APPLICATION;
$cp = $this->__component; 
if (is_object($cp)){
	$cp->arResult['DESC'] = implode(' ', array_slice(explode(' ', strip_tags($arResult["PREVIEW_TEXT"])), 0, 20));
	$cp->SetResultCacheKeys(array('DESC'));
	$arResult['DESC'] = $cp->arResult['DESC'];
}

а с component_epilog.php уже вывести в нужное место
global $APPLICATION;
$APPLICATION->SetPageProperty("description", $arResult["DESC"]);


зы: читать подробности тут dev.1c-bitrix.ru/learning/course/?COURSE_ID=43&LESSON_ID=2975

Bitrix: предыдущий с последующий элементы для news.detail

<?
// файл result_modifier.php news.detail

// упорядочиваем как и в разделе. (получить автоматом нельзя)
$arOrder = array("ACTIVE_FROM" => "DESC"); 
$arSelect = Array("ID", "NAME", "DETAIL_PAGE_URL");
$arFilter = Array("IBLOCK_ID"=>$arParams["IBLOCK_ID"], "ACTIVE_DATE"=>"Y", "ACTIVE"=>"Y");
$res = CIBlockElement::GetList($arOrder, $arFilter, false, array("nPageSize" => "1","nElementID" => $arResult["ID"]), $arSelect);
//"nPageSize" - количество элементов на странице при постраничной навигации 
//"nElementID" - ID элемента который будет выбран вместе со своими соседями. Количество соседей определяется параметром nPageSize.
while($ob = $res->GetNext()) $arFields[] = $ob;

// упорядочиваем соседей, если нет левого или правого
if (count($arFields) == 2 && $arResult["ID"] == $arFields["0"]["ID"]) {	
	$arFields["2"] = $arFields["1"]; 
	unset($arFields["0"]); 
}

if (isset($arFields["0"])) 
	$arResult["LEFT_ARROW"] = array(
		"LINK" => $arFields["0"]["DETAIL_PAGE_URL"],
		"NAME" => $arFields["0"]["NAME"]
	);

if (isset($arFields["2"])) 
	$arResult["RIGHT_ARROW"] = array(
		"LINK" => $arFields["2"]["DETAIL_PAGE_URL"],
		"NAME" => $arFields["2"]["NAME"]
	);
?>

Bitrix конфиг для nginx+php_fpm (fastcgi)

server {
	listen       80;
	server_name  bitrixnginxcgi.com;
	
	location / {
		root  /var/www/bitrixnginxcgi.com;

                index  index.php index.html index.htm;
                if (!-e $request_filename) {
            	    rewrite  ^(.*)$  /bitrix/urlrewrite.php last;
                }
	}
	
	location ~ \.(php|phtml|txt)$ { 
		root /var/www/bitrixnginxcgi.com;
		access_log  /var/log/nginx/bitrixnginxcgi.log;
		if (!-f $request_filename) {
			rewrite  ^(.*)/index.php$  $1/ redirect;
		}
                # если на сайте пользователь может загрузить картинку, то сможет и загрузить эксплойт
                # который фастсиджиай с радостью выполнит. для аплоада не будем включать фастсиджиай
		if ($uri !~ "^/upload/") {
			fastcgi_pass   127.0.0.1:9000;
		}
		fastcgi_index  index.php;
		#fastcgi_param  SCRIPT_FILENAME  /var/www/bitrixnginxcgi.com$fastcgi_script_name;
		include        /etc/nginx/fastcgi_params;
	}
        
        # время жизни для картинок и прочей статики на 7 дней 
	location ~* ^.+\.(bmp|gif|jpg|jpeg|ico|png|swf|tiff|css|js|xml)$ {
		root  /var/www/bitrixnginxcgi.com;
		expires 7d;
	}

        # закрываем старые htaccess и сюда можно включить другие папки (типа гит/свн)
	location ~ /\.hta {
		deny  all;
	}

        # выведем барахлишко в сжатом виде 
	gzip on;
	gzip_types text/plain text/css text/javascript text/x-javascript application/x-javascript;
	gzip_vary on;
	gzip_http_version 1.0;
	gzip_proxied any;
        # IE6 и ниже сжатие не поймут, им как есть отдаём
        # поэтому хакеры притворяются ИЕ6, что бы не разжимать исходные данные 
	gzip_disable "MSIE [1-6]\.";
}

Subscribe: подписываем/отписываем пользователя

if(CModule::IncludeModule("subscribe")){
	if ($_POST["SUBSCRIBE"] == "Y") {
		$arFields = Array(
			"USER_ID" => $USER->GetID(),
			"FORMAT" => "html",
			"EMAIL" => $USER->GetEmail(),
			"ACTIVE" => "Y",
			"RUB_ID" => array()
		);
		$subscr = new CSubscription;
		$ID = $subscr->Add($arFields);
		if($ID>0) {
			CSubscription::Authorize($ID);
		}
	} else {
		$subscription = CSubscription::GetByEmail($USER->GetEmail());
		if($subscription->ExtractFields("str_")) {
			$ID = (integer)$str_ID;
		}
		if ($ID >0) {
			$res = CSubscription::Delete($ID);
		}
	}
}

Получаем UF свойства пользователя (отдельная функция)

function get_user_prop ($id, $prop = "*") {
	if (!($id > 0)) return false;
	if (!is_array($prop)) $prop = array($prop);
	global $DB;
	$res = $DB->query("SELECT ".implode(",", array_map("mysql_real_escape_string", $prop))." FROM `b_uts_user` WHERE VALUE_ID = $id");
	$res = $res->Fetch();
	if (!empty($res)) {
		foreach ($res as $key => $value) {
			if (preg_match("/^a:\d+:{.*?}$/", $value)) {
				$res[$key] = unserialize($value);
			}
		}
	}
	return $res;
}

У анонимных пользователей не работает поиск/теги

Уже второй раз эта проблема, забыл как решил первый раз и пришлось шишку набивать опять.
В модуле поиска в файле search.php в функции проверки прав CheckPermissions грабли с правами, потому мы просто делаем ее обрезанной

function CheckPermissions($FIELD = "sc.ID"){return "((1=1))";}

Где и как эти права задаются в админке, не ясно

Как вывести капчу в Bitrix (работа с custom captcha)

<?
$cpt = new CCaptcha();
$cpt->SetCode();
?>
<label>
	<input type="hidden" name="captcha_sid" value="<?= $cpt->GetSID() ?>">
	<img src="/bitrix/tools/captcha.php?captcha_code=<?=$cpt->sid;?>" alt="" width="135" height="40"/>
	<abbr class="refresh">обновить
изображение</abbr>
</label>
<label>
	<span>Введите символы с картинки: <b>*</b></span> 
	<input type="text" size="12" name="captcha"/>
	<small>Не верно введены символы с картинки</small>
</label>

That's all folks