Add attribute to existing bbcode

Discussion forum for Extension Writers regarding Extension Development.
Post Reply
soviet9k
Registered User
Posts: 11
Joined: Sat Oct 14, 2017 12:48 pm

Add attribute to existing bbcode

Post by soviet9k »

Hi there,

I'd like to extend the existing `quote` bbcode with an additional template var that I'll only use in one style. Long story short, I'd like to add a variable `COMMENTURL` that will be a modified version of the available attributes @url or @post_url, depending on which is available. I don't want to modify anything about the actual bbcode parameters, but I want to output some modified version of the url in the resultant template. Hope that makes sense.

I have looked at a few examples and I've tried simple XSLT modifications, but they all result in security warnings. If anyone has any guidance, I'd appreciate it.

Thanks
User avatar
JoshyPHP
Code Contributor
Posts: 1288
Joined: Mon Jul 11, 2011 12:28 am

Re: Add attribute to existing bbcode

Post by JoshyPHP »

You should start by posting what code you tried and a description of what you're trying to achieve exactly.
I wrote the library that handles markup in phpBB 3.2+.
soviet9k
Registered User
Posts: 11
Joined: Sat Oct 14, 2017 12:48 pm

Re: Add attribute to existing bbcode

Post by soviet9k »

Basically I'd like to know how I can define a new template variable from within my extension and then use it in the bbcode.html template. I realize I could destroy and recreate the quote bbcode tag but that seems heavy handed and more difficult to maintain. I've iterated through a number of different php events, trying to see what can be done with each. It's kind of painful to work with these bbcodes as they are cached even when you're in debug mode with and have your styles set to refresh on every load. To see any updates you have to clear the cache manually each time.

I'm just kind of lost here. I'd happily use XSL functions to do the simple string replacement I need directly in the bbcode.html template, but that kicks back a security warning. If you can point to any example of either XSL functions being used in the template or how to add a new variable within an extension that I can modify in the template, I think it would get me on my way.

I have also tried using <xsl:variable in the template to no avail - same security feedback.

Extension attempt - I know this doesn't work but just trying something.

Code: Select all

static public function getSubscribedEvents()
{
  return array(
...
    'core.text_formatter_s9e_configure_after' => 'configure_quotes'
  );
}
...
public function configure_quotes($event)
{
  // Add self::filter_quote() to filter the QUOTE tag that handles quotes
  $event['configurator']->tags['QUOTE']->filterChain
    ->append(array(__CLASS__, 'filter_quote'));
}

static public function filter_quote(\s9e\TextFormatter\Parser\Tag $tag)
{
  $tag->setAttribute('comment_url', 'anything');
  return true;
}
User avatar
JoshyPHP
Code Contributor
Posts: 1288
Joined: Mon Jul 11, 2011 12:28 am

Re: Add attribute to existing bbcode

Post by JoshyPHP »

What you posted seems ok, it's just missing a proper declaration for that attribute in configure_quotes()

Code: Select all

$attribute = $event['configurator']->tags['QUOTE']->attributes->add('comment_url');
$attribute->filterChain->append('#url');
I haven't run this code on my local install but I think it should do it. If not, post the whole class so I can reproduce locally.
I wrote the library that handles markup in phpBB 3.2+.
soviet9k
Registered User
Posts: 11
Joined: Sat Oct 14, 2017 12:48 pm

Re: Add attribute to existing bbcode

Post by soviet9k »

Thanks for the guidance JoshyPHP - the TextFormatter project is pretty amazing. I'm still hung up. It's strange because after continuing from where you left off, I can print_r the $tag and see that comment_url is indeed added to the attributes list. However, placing it in the bbcode.html template still throws this error:

Code: Select all

Fatal error: Uncaught exception 's9e\TextFormatter\Configurator\Exceptions\UnsafeTemplateException' with message 'Cannot assess the safety of unknown attribute 'comment_url'' in /apps/forums/phpBB/vendor/s9e/text-formatter/src/Configurator.php:6417 Stack trace: #0 /apps/forums/phpBB/vendor/s9e/text-formatter/src/Configurator.php(6462): s9e\TextFormatter\Configurator\TemplateChecks\AbstractDynamicContentCheck->checkAttribute(Object(DOMAttr), Object(s9e\TextFormatter\Configurator\Items\Tag), 'comment_url') #1 /apps/forums/phpBB/vendor/s9e/text-formatter/src/Configurator.php(6426): s9e\TextFormatter\Configurator\TemplateChecks\AbstractDynamicContentCheck->checkExpression(Object(DOMAttr), '@comment_url', Object(s9e\TextFormatter\Configurator\Items\Tag)) #2 /apps/forums/phpBB/vendor/s9e/text-formatter/src/Configurator.php(8287): s9e\TextFormatter\Configurator\TemplateChecks\AbstractDynamicContentCheck->checkAttributeNode(Object(DOMAttr), Object(s9e\TextFormatter\Configurator\Items\Tag)) #3 /apps/forums/phpBB/vendor/s9e/text-fo in /apps/forums/phpBB/vendor/s9e/text-formatter/src/Configurator.php on line 6417
Here's most of my class for this extension:

Code: Select all

<?php

namespace site\comments\event;

/**
 * @ignore
 */
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Comments Event listener.
 */
class main_listener implements EventSubscriberInterface
{
  static public function getSubscribedEvents()
  {
    return array(
      'core.viewtopic_assign_template_vars_before' => 'viewtopic_vars',
      'core.viewtopic_modify_post_row' => 'viewtopic_post_vars',
      'core.pagination_generate_page_link' => 'viewtopic_pagination',
      'core.posting_modify_template_vars' => 'posting_vars',
      'core.functions.redirect' => 'handle_redirects',
      'core.text_formatter_s9e_configure_after' => 'configure_quotes'
    );
  }

  /* @var \phpbb\controller\helper */
  protected $helper;

  /* @var \phpbb\template\template */
  protected $template;

  /* @var \phpbb\user */
  protected $user;

  /* @var \phpbb\cache\service */
  protected $cache;

  /* @var \phpbb\config\config */
  protected $config;

  /* @var \phpbb\request\request */
  protected $request;

  /** @var string phpEx */
  protected $php_ext;

  /**
   * Constructor
   *
   * @param \phpbb\controller\helper  $helper   Controller helper object
   * @param \phpbb\template\template  $template Template object
   * @param \phpbb\user               $user       User object
   * @param \phpbb\cache\driver\driver_interface $cache Cache object
   * @param \phpbb\config\config $config Config object
   * @param \phpbb\request\request $request Request object
   * @param string                    $php_ext    phpEx
   */
  public function __construct(\phpbb\controller\helper $helper, \phpbb\template\template $template, \phpbb\user $user, \phpbb\cache\service $cache, \phpbb\config\config $config, \phpbb\request\request $request, $php_ext)
  {
    $this->helper   = $helper;
    $this->template = $template;
    $this->user     = $user;
    $this->cache    = $cache;
    $this->config   = $config;
    $this->request  = $request;
    $this->php_ext  = $php_ext;
  }

  public function set_rank($row)
  {

    //
    return $row;
  }

  /**
   * Set topic-wide template variables for use
   *
   * @param \phpbb\event\data $event  Event object
   */
  public function viewtopic_vars($event)
  {

    // Available event vars: base_url, forum_id, post_id, quickmod_array, start, topic_data, topic_id, topic_tracking_info, total_posts, viewtopic_url

    // Assign a proper start var
    $this->template->assign_var('PAGE_START', $event['start']);

    // Make template var for all user groups
    $user_groups = group_memberships(false, $this->user->data['user_id']);
    $staff_groups = array('GOD', 'News Writers', 'Editors');
    foreach ($user_groups as $user_group) {
      $group_name = get_group_name($user_group['group_id']);
      $this->template->assign_var('S_GROUP_' . strtoupper(str_replace(' ', '_', $group_name)), true);
      if (in_array($group_name, $staff_groups)) {
        $this->template->assign_var('S_IS_STAFF', true);
      }
    }

  }

  /**
   * Set topic-row template variables for use
   *
   * @param \phpbb\event\data $event  Event object
   */
  public function viewtopic_post_vars($event)
  {
    // Add custom title (and related img fields) if necessary
    $row = $this->set_rank($event['post_row']);

    // Add time w/zone offset
    $row['POST_TIME'] = $event['row']['post_time'];

    $row['POST_DATE_ISO8601'] = date('c', $event['row']['post_time']);

    // Store user Premier status in cache
    $row['IS_PREMIER'] = false;

    $groups = $this->cache->get('post_row_groups_' . $row['POSTER_ID']);
    if ($groups === false) {
      $groups = group_memberships(false, $row['POSTER_ID']);
      $this->cache->put('post_row_groups_' . $row['POSTER_ID'], $groups, 1000);
    }

    foreach ($groups as $group) {
      if ($group['group_id'] == 10) {
        $row['IS_PREMIER'] = true;
      }
    }

    $event['post_row'] = $row;
  }

  function viewtopic_pagination($event)
  {
    $base_url = $event['base_url'];
    $base_url = preg_replace("/.\/viewtopic.php\?style=11(.*)/", "?comments=1&style=11$1", $base_url);
    $event['base_url'] = $base_url;
  }

  function posting_vars($event)
  {
    // Add URL of the article page if applicable
    $vars = array();
    $vars['CURRENT_URL_NONAPI'] = $this->request->server('REQUEST_URI');
    $vars['CURRENT_URL_NONAPI_ENCODED'] = urlencode($this->request->server('REQUEST_URI'));

    // For ESI requests from the front page
    if($this->request->is_set('return_to')) {
      $vars['CURRENT_URL'] = $this->request->variable('return_to', '');
      $vars['CURRENT_URL_ENCODED'] = urlencode($this->request->variable('return_to', ''));
      $vars['CURRENT_URL_BARE'] = parse_url($this->request->variable('return_to', ''), PHP_URL_HOST) . parse_url($this->request->variable('return_to', ''), PHP_URL_PATH);
      $vars['CURRENT_MOBILE_URL'] = str_replace('http://', '', request_var('return_to', ''));
      $vars['IS_JS'] = ($this->request->variable('js', false) ? true : false);
    }

    $vars['U_FORUM'] =  generate_board_url() . '/';

    if($this->request->is_set('view')) {
      $vars['VIEW'] = $this->request->variable('view', '');
    }

    $action_string = "Leave your comment";
    if($this->request->is_set('mode')) {
      $vars['MODE'] = $this->request->variable('mode', '');
      switch($this->request->variable('mode', '')) {
        case 'reply':
          break;
        case 'quote':
          $action_string = "Leave your reply";
          break;
        case 'edit':
          $action_string = "Edit your comment";
          break;
      }
    }
    $vars['ACTION_STRING'] = $action_string;

    $this->template->assign_vars($vars);

  }

  public function handle_redirects($event)
  {
    $url = $event['url'];

    if($this->request->is_set('articleurl')) {
      preg_match('#p=([0-9]+)#', $url, $p);
      $url = "https://" . $this->request->variable('articleurl', '');
      // For mobile stuff
      if(strpos($this->request->variable('articleurl'), '#!') !== False) {
        $url .= '&p=' . $p[1] . '&comment-' . $p[1];
      }
      else {
        $url .= '?comments=1&post=' . $p[1] . '#comment-' . $p[1];
      }
    }

    $event['url'] = $url;
  }

  public function configure_quotes($event)
  {
    $tag = $event['configurator']->tags['QUOTE'];
    $attribute = $tag->attributes->add('comment_url');
    $attribute->filterChain->append('#url');
    $tag->filterChain
      ->append(array(__CLASS__, 'filter_quote'));
    print_r($tag);
  }

  static public function filter_quote(\s9e\TextFormatter\Parser\Tag $tag)
  {
    $tag->setAttribute('comment_url', 'https://google.com/');
    return true;
  }
}

If you have any suggestions, let me know. I'm wondering if there's some kind of "allowed attributes" config I need to set. Or if there's an easier way to approach this altogether? I did try a couple of XSL 1.0 string transformations within the template but those both threw up security warnings as well.

Thanks again.
User avatar
JoshyPHP
Code Contributor
Posts: 1288
Joined: Mon Jul 11, 2011 12:28 am

Re: Add attribute to existing bbcode

Post by JoshyPHP »

soviet9k wrote: Thu Oct 26, 2017 1:21 pm However, placing it in the bbcode.html template still throws this error:
[/code]

At the time the [quote] BBCode is created, the comment_url attribute doesn't exist, therefore the configurator treats it as unsafe if it's used in a sensitive context. Right now, the simplest workaround would be to change the template in the same event that creates the attribute. You can access the template as a string via $event['configurator']->tags['quote']->template or as a DOMDocument like this. If you inspect the template you'll see that if different styles have different templates, they are implemented as a xsl:choose structure, so if you modify elements through DOM you should assume there could be more than one element that you want to modify.

There's no list of "safe" attributes, it depends on how they're filtered; URLs and numbers are safe to be used as a URL but most text isn't because it could be used with the javascript: pseudo-scheme to execute JavaScript. Unknown and unfiltered attributes are treated as unsafe. Those warnings are actually disabled on custom BBCodes because there's no interface in phpBB to check a BBCode's safety at the time it's created.
I wrote the library that handles markup in phpBB 3.2+.
soviet9k
Registered User
Posts: 11
Joined: Sat Oct 14, 2017 12:48 pm

Re: Add attribute to existing bbcode

Post by soviet9k »

I'm still really struggling with BBCode modification. I think it all comes down to the fact that the contents are rendered and stored in the database at post time only once I think (depending on the event). I managed to selectively define a template variable so it's only set when calling a URL with "api=1" in the query string like so:

Code: Select all

	public function text_formatter_s9e_render_before($event)
	{
		// Available vars: renderer, xml

		if ($this->api != '1') {
			return;
		}

		$event['renderer']->get_renderer()->setParameter('COMMENT_URL', $this->current_url_bare);
	}
I wish I could render use special xsl within my extension dir for just this circumstance (i.e. when api=1 in URL), but that doesn't seem possible from everything I've tried. That's OK - it just requires that I maintain code in my main style that my extension then depends on. If there's a way around that, I'd love to know.

The next hurdle I'm facing is removing target="_blank" from the rendered HTML in the quote bbcode. I tried overriding it in the template XSL using an explicit <xsl:attribute>:

Code: Select all

              <xsl:when test="@post_id">
                <a href="https://{$COMMENT_URL}?comments=1&post={@post_id}#comment-{@post_id}" class="postlink">
                  <xsl:attribute name="target">_self</xsl:attribute>
                  <xsl:value-of select="@author"/>
                </a>
              </xsl:when>
When I inspect $event['html'] in core.text_formatter_s9e_render_after, it indeed includes the target="_self". Yay! However, that's not what appears in the final rendered text. I'm obviously missing some big piece of the puzzle here. If anyone has recommendations, please share.

Thanks.
User avatar
JoshyPHP
Code Contributor
Posts: 1288
Joined: Mon Jul 11, 2011 12:28 am

Re: Add attribute to existing bbcode

Post by JoshyPHP »

soviet9k wrote: Sat Nov 04, 2017 4:35 pm I think it all comes down to the fact that the contents are rendered and stored in the database at post time only once I think
Parsed at posting time, rendered dynamically. You can look at the post content in the db to get an idea of what information is stored.
soviet9k wrote: Sat Nov 04, 2017 4:35 pm I wish I could render use special xsl within my extension dir for just this circumstance (i.e. when api=1 in URL), but that doesn't seem possible from everything I've tried.
I don't know what that means. Can you provide a concrete example of input/output?
soviet9k wrote: Sat Nov 04, 2017 4:35 pm The next hurdle I'm facing is removing target="_blank" from the rendered HTML in the quote bbcode.
I don't remember anything in the default style that used a target attribute. Check out this page if you're trying to modify a template: http://s9etextformatter.readthedocs.io/ ... _template/

soviet9k wrote: Sat Nov 04, 2017 4:35 pm When I inspect $event['html'] in core.text_formatter_s9e_render_after, it indeed includes the target="_self". Yay! However, that's not what appears in the final rendered text. I'm obviously missing some big piece of the puzzle here. If anyone has recommendations, please share.
An extension modifies the HTML?
I wrote the library that handles markup in phpBB 3.2+.
Post Reply

Return to “Extension Writers Discussion”