Опубликовано 14 февраля 2011, 23:24 под Led Zeppelin - Communication Breakdown

Для начала немного о том, как устроен шаблон vBulletin. По сути, это обычная PHP-строка в невидимых двойных кавычках (при этом двойные кавычки самой строки автоматически экранируются). Пример:

<div class="username">$post[username]</div>

Это то, что видит пользователь. В «чёрном ящике» они хранятся в виде:

<div class=\"username\">$post[username]</div>

Когда нужно подставить в шаблон переменные, поступают следующим образом:

eval('$output = "' . fetch_template('username') . '";');

Если подставить вместо fetch_template то, что она возвращает, получим:

eval('$output = "<div class=\"username\">$post[username]</div>";');

Таким вот нехитрым образом в шаблоне всегда будет локальный контекст, и не надо ничего никуда присваивать. Однако функционал подобных шаблонов весьма уныл — кроме переменных ничего нет. Естественно будет добавить конструкции if и if-else:

<if condition="$post[username] != ''">
    <div class="username">$post[username]</div>
<else />
    Пользователь петух и не указал своё имя
</if>

Которые превращаются в:

" . (($post[username] != '') ? ("
    <div class=\"username\">$post[username]</div>
") : ("
    Пользователь петух и не указал своё имя
")) . "

И выполняются вот так:

$output = "" . (($post[username] != '') ? ("
    <div class=\"username\">$post[username]</div>
") : ("
    Пользователь петух и не указал своё имя
")) . "";

На этом всё — циклы разработчики vBulletin не осилили. В результате имеем кучу отличных шаблонов типа pollvote:

<div><label for="rb_optionnumber_$option[number]"><input type="radio" name="optionnumber" value="$option[number]" id="rb_optionnumber_$option[number]" />$option[question]</label></div>

Которые склеивают вручную в коде. Знаете, так здорово поднимает производительность труда необходимость на каждую итерацию лепить по шаблону из одной строчки! И вот позавчера я почему-то вспомнил про эту проблему и за пять минут придумал как можно сделать циклы. Времени на это дело нашёл только сегодня, и вот пожалуйста:

$pacany = array(
    'Вова'      => array('themylogin', 'Lorem Ipsum', 'thelogin (это сайт блять, но меня так иногда называют)'),
    'Дима'      => array('mirain', 'koromshuk', 'diman', 'dimka')
);

<foreach from="$pacany" key="$pacan_name" value="$pacan_nicknames" import="$spacer_open $spacer_close" is_first="$is_first_pacan" is_last="$is_last_pacan">
    <if condition="$is_first_pacan">$spacer_open</if>
         <b>$pacan_name:</b>
         <foreach from="$pacan_nicknames" value="$pacan_nickname" is_last="$is_last_nickname">
             $pacan_nickname<if condition="!$is_last_nickname">,</if>
         </foreach>
    <if condition="$is_last_pacan">$spacer_close<else /><br /></if>
</foreach>

Собственно, единственная опция, требующая пояснения (и разгадывающая тайну реализации) — import, она указывает какие переменные из текущего контекста нужно видеть в теле цикла.1

Как замутить такое у себя на форуме. В includes/adminfunctions_template.php, там где

// #############################################################################
/**
* Processes a template into PHP code for eval()
*
* @param	string	Unprocessed template
* @param	boolean	Halt on error?
*
* @return	string
*/
function process_template_conditionals($template, $haltonerror = true)

делает

return str_replace("\\'", "'", $template_cond);

Напишем по-другому:

$template_cond = str_replace("\\'", "'", $template_cond);

    while (strstr($template_cond, '<foreach'))
    {
        $pos = strrpos($template_cond, '<foreach');
        $template_cond = substr($template_cond, 0, $pos) . preg_replace_callback('#<foreach ([^>]+)>(.*)</foreach>#Us', 'process_template_foreach', substr($template_cond, $pos));
    }
	
    return $template_cond;

Я чо-то не осилил доказать, что если в системе вложенных тегов всегда брать последний открывающий, находить его закрывающий, сворачивать, и так поступать пока блоки не закончатся, то при обработке каждого блока мы можем быть уверены, что вложенные в него уже обработаны, но по-моему это так, и 200 строк лапши по обработке if'ов выше там как бы совсем ни к чему. process_template_foreach будет выглядеть так:

function process_template_foreach($matches)
{
    $foreach_args = $matches[1];
    $foreach_body = $matches[2];

    $helper_args = array();
    preg_match_all('#(from|import|key|value|is_first|is_last)\s*=\s*\\\"(.+)\\\"#Usi', $foreach_args, $foreach_args_matches);
    foreach (array_keys($foreach_args_matches[0]) as $i)
    {
        $arg_name = trim($foreach_args_matches[1][$i]);
        $arg_value = trim($foreach_args_matches[2][$i]);

        switch ($arg_name)
        {
        case 'from':
            if (!preg_match('#^\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)(\[[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\])?$#U', $arg_value, $variable_name))
            {
                print_stop_message('is_not_a_variable_name', htmlspecialchars($arg_value), htmlspecialchars('<foreach ' . $foreach_args . '>'));
            }
            $helper_args['from'] = $variable_name[0];
            break;
        case 'import':
            if (!preg_match_all('#\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)#', $arg_value, $imported_variable_names))
            {
                print_stop_message('import_parameter_value_is_wrong', htmlspecialchars('<foreach ' . $foreach_args . '>'));
            }
            $helper_args['import'] = 'compact(' . var_export($imported_variable_names[1], true) . ')';
            break;
        case 'key':
        case 'value':
        case 'is_first':
        case 'is_last':
            if (!preg_match('#^\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$#U', $arg_value, $variable_name))
            {
                print_stop_message('is_not_a_variable_name', htmlspecialchars($arg_value), htmlspecialchars('<foreach ' . $foreach_args . '>'));
            }
            $helper_args[$arg_name] = '\'' . substr($variable_name[0], 1) . '\'';
            break;
        }
    }

    $helper_args_str = '';
    foreach ($helper_args as $arg_name => $arg_value)
    {
        $helper_args_str .= "'{$arg_name}' => {$arg_value}, ";
    }

    return '" . template_foreach_helper(array(' . $helper_args_str . '), ' . var_export($foreach_body, true) . ') . "';
}

Первый раз в жизни сделал case без break, и это не оказалось ошибкой. Регулярка для имени переменной из официальной документации, не бойтесь. Фразы сами добавите. TODO: проверять, что в import действительно валидный список переменных, а не просто набирать их оттуда, а то лошибётся кто-нибудь по типу import="$spacer,_open" и не заметит.

Куда-нибудь в includes/functions.php пишем:

function template_foreach_helper($__internalArgs, $__internalToEval)
{
    if (isset($__internalArgs['import']))
    {
        extract($__internalArgs['import']);
    }

    $__internalReturn = '';
    if (isset($__internalArgs['key']))
    {
        $__internalKeyName = $__internalArgs['key'];
    }
    else
    {
        $__internalKeyName = '__internalDummyKey';
    }
    if (isset($__internalArgs['value']))
    {
        $__internalValueName = $__internalArgs['value'];
    }
    else
    {
        $__internalValueName = '__internalDummyValue';
    }
    if (isset($__internalArgs['is_first']))
    {
        $__internalIsFirstName = $__internalArgs['is_first'];
    }
    else
    {
        $__internalIsFirstName = '__internalIsFirstValue';
    }
    if (isset($__internalArgs['is_last']))
    {
        $__internalIsLastName = $__internalArgs['is_last'];
    }
    else
    {
        $__internalIsLastName = '__internalIsLastValue';
    }
    $__internalTotal = count($__internalArgs['from']);
    $__internalCounter = 0;
    foreach ($__internalArgs['from'] as $$__internalKeyName => $$__internalValueName)
    {
        $$__internalIsFirstName = ($__internalCounter == 0);
        $__internalCounter++;
        $$__internalIsLastName = ($__internalCounter == $__internalTotal);

        eval('$__internalReturn .= "' . $__internalToEval . '";');
    }
    return $__internalReturn;
}

Ну и собственно всё нармана! Вышеприведённый цикл превратится в:

$output = "" . template_foreach_helper(array('from' => $pacany, 'key' => 'pacan_name', 'value' => 'pacan_nicknames', 'import' => compact(array (
  0 => 'spacer_open',
  1 => 'spacer_close',
)), 'is_first' => 'is_first_pacan', 'is_last' => 'is_last_pacan', ), '
    ".(($is_first_pacan) ? ("$spacer_open") : (""))."
         <b>$pacan_name:</b>
         " . template_foreach_helper(array(\'from\' => $pacan_nicknames, \'value\' => \'pacan_nickname\', \'is_last\' => \'is_last_nickname\', ), \'
             $pacan_nickname".((!$is_last_nickname) ? (",") : (""))."
         \') . "
    ".(($is_last_pacan) ? ("$spacer_close") : (""))."
') . "";

Вроде норм.

Продукт, Wiki и всю хуйню завтра, а пока пойду Weeds смотреть Если хлеб, купленный неделю назад, ещё жив, может даже пожрать удастся. Угу, я уже неделю живу один в съёмной квартире, тут недавно первый раз в жизни мыл пол и т.п. Сегодня на тервере (каком блять тервере, слупах) два раза пытался начать пост про то, как всё хорошо/плохо (рабочее название Я ИЗУЧАЮ НАХУЙ!), но упорно выходила лажа, так что я потом как-нибудь расскажу. Ну всё, полез на балкон за маслом. Балкон это мой холодильник

[1] — На самом деле, можно хапать и весь контекст, просто я так и не нашёл как в PHP его получить, поэтому единственный мне доступный метод — через жопу: триггернуть какой-нибудь E_USER_NOTICE, повесив на него кастомный обработчик, и в этот обработчик нам передадут весь контекст.
Добавить комментарий
Войдите через  Доступные тэги: <b>, <i>, <s>, <u>, <a href="">, <img src="" /> (загрузить), <pre>, <quote>
Теги
1nsk C++ E-Business GNU/GPL-софт HTML/CSS ICQ KDE last.fm Linux Linuxnsk Live Lyceum game Microsoft Motivator MySQL P2P Party PHP Python Qt SEO Sibnet thelogin.ru Timelapse Web 2.0 Webstream Windows Wireless А я говорил! Авто Админ Аниме Атмосфера Безобразие Бизнес Блоги Боты Бред Быт Велосипед Весна Вечер Взлом Винил Винтаж Вконтакте Вода Воспоминания Гетто Гламур Гопота Горский Графити Грузовик Дача Двор Девушки Деньги Дизайн Домофон Драки Еда Железо Животные Затулинка Зима Игрушки Игры Идея Интересно Интернет Исследование История Казань Карикатура Квартира Класс Компьютеры Коромшук Коты КПК Кран Красиво Криминал Крыша Курение Лето Литература Лицей НГТУ Лохи Магнитофоны Маркса Мат Математика Машинный перевод Мегафон Менты Метро Мечты Мифы Мне стыдно Мобайл Можга МТС Музыка Мультикасса Мультики Мы помним Надписи Наука НГТУ Недвижимость Непоняятно Новосибирск Носители Ноутбуки Ночь Обидно Обман Образование Общественный транспорт Омск Опечатки Орфография Осень Парк Периферия Пиратство Плохо Погода Поезд Помойка Праздники Презрение Природа Провайдеры Программирование Протест Пьянки Радиотехника Растения Реклама Религия и вера Ретро Рисунок Руины Рэп Салют Серверы Сервис Сериалы Скринкаст Скриншоты Смешно СМИ Снег Собаки Совет Софт Спам Спасём Россию Спецтранспорт Стоп-ляп Страх Стройка Студяга Танцы ТВ Трамваи Транс Транспорт Уважаю Ужас Умный дом Утро Филдрепорты Философия Форум ФПМИ Хикки Хостинг Цитатник Школа 208 ЭГ Эксперимент Юго-Западный
Twitter 14-02-2011
Lorem Ipsum
Молитвенное уединение с Богом “Пенуэл” будет транслироваться в интернете
Lorem Ipsum
http://img15.nnm.ru/6/6/c/5/0/3dc1aedd4b0e5413e3540bea575.jpg все люди счастливы и никакой наркомании!
Lorem Ipsum
http://img15.nnm.ru/a/2/7/7/3/d136daf721c11a5b98c1af93a52.jpg НА ВОЙНУ, ПИДОР, ХУЛЕ НЕ МУЖИК?
Lorem Ipsum
Мне сегодня на полном серьёзе предлагали завтра придти на лекцию по экономике и праву в 8:30!
Lorem Ipsum
Отлично жить в 15 минутах от универа, можно принимать абсолютно спонтанные решения всё же сходить на пары с пацанами пива выпить.
Lorem Ipsum
АСЦЕЛОГРАФ БОЛЬШОЙ И МАЛЫЙ
Lorem Ipsum
У меня тут офис блядь, три компьютера! Ну хорошо хоть не плазма и траходром.
Lorem Ipsum
Приходил хозяин. Обречённо: "увёз-таки диван". А окна регулировать блядь будут!
Lorem Ipsum
Только вчера услышал переливающийся звон в Hadouken! - Ugly :(