Jump to:

17488 Posts in 4473 Topics by 1978 members

Archive

SilverStripe Forums » Archive » A Better FirstParagraph() for Teasers

Our old forums are still available as a read-only archive.

Moderators: martimiz, Howard, Sean, Ryan M., biapar, Willr, Ingo, simon_w

Page: 1
Go to End
Author Topic: 2216 Views
  • Anonymous user
    Avatar
    Community Member
    1 Post

    A Better FirstParagraph() for Teasers Link to this post

    I was going through the tutorials and got to the Articles section where different articles are summarized in an ArticleContainer page when I discovered the following problem.

    The FirstParagraph function does not extract the contents of the first paragraph (<P> tag). It extracts the contents of the block of text. So if I started an article with anything but a <p> tag it failed.

    For example:

    <H3>My Article Header</H3>
    <P>The first paragraph in my article.</P>

    The FirstParagraph function returned 'My Article Header'.

    So, I took a look at the FirstParagraph() function in /sapphire/core/model/fieldtypes/Text.php to see what the problem was.

    The problem was that the function is not even checking for a <P> tag and (if found) returning the contents of that. It's converting the contents to text and then trying to determine what was meant to be the first paragraph from plain text (up to first blank line).

    Instead of the current method, I wanted to perform the following options at the start of the function.

    - Scan the contents to see if it contained a closing paragraph tag (</p>) assuming it was extremely unlikely to be found in anything but an HTML document.
    - If not found, I fell through to handle it as before.
    - If found I scanned backwards through the contents for the nearest opening paragraph tag.
    - If no opening tag was found, I fell through to handle it as before.
    - If it was found, I returned the contents of the <P> tag.

    I added the following code at the start of that function to solve the problem:

    $p = strpos( $this->value, "</p>" );
    if( $p!==false ) {
    $v = substr( $this->value,0,$p );
    $p = strrpos($v,"<p");
    if( $p!==false ) {
    $p = strpos($v,'>',$p);
    return substr($v,$p+1);
    }
    }

    That seemed to solve the problem. For HTML, the function returned the content of the first paragraph tag which was exactly what I wanted. Whatever I placed in the first paragraph (including images) was being returned by FirstParagraph function and could be used as the teaser. It also had the advantage that I had some control over what was extracted as the teaser. It no longer had to be the first text in the article, just the first paragraph (what I was asking for).

    So, I continued adding content only to realize another problem.

    You cannot place block elements inside paragraphs (ie. <UL>s in <P>s) so again I was limited as to what I could use for the teaser. My teasers could not contain LISTS or BLOCKS of any other type... no way around it, it's invalid HTML if you do.

    So, I came up with another solution.

    What I wanted to do is define a section of the article to be used as the teaser. This way the teaser could be anywhere in the article and even removed by CSS if I didn't want the teaser displayed when the full article was being viewed (display: none).

    So I decided to write another function called 'Teaser' that would look for such a block and if found, return the contents of that. If no such block existed, I resorted to the FirstParagraph() function.

    I placed the following function immediately before the FirstParagraph function:

    function Teaser($plain = 1) {
    if( $plain ) return $this->FirstParagraph($plain);
    // Search for a DIV with a class of 'teaser'
    // if found, extract from start of tag to end of content
    if( preg_match('/<div[^>]*? class=(?:teaser|[\'"](?:teaser|[^"\']*? teaser\\W)).*$/si',$this->value,$match) ) {
    // Locate all opening and closing DIV tags (except first)
    $nDivTags = preg_match_all('|</?div\W|si',$match[0],$matches,PREG_OFFSET_CAPTURE);
    for( $nLvl=1,$nTag=1; $nTag<$nDivTags; $nTag++ ) {
    if( $matches[0][$nTag][0]{1}=='/' ) {
    if( --$nLvl==0 ) return substr($match[0],0,$matches[0][$nTag][1]+6);
    } else {
    $nLvl++;
    }
    }
    }      
    return $this->FirstParagraph($plain);
    }

    The function is very forgiving for case, multiple classes, nested DIVs, and even unmatched DIVs and if anything unexpected is found, it resorts to the FirstParagraph function.

    It's working great now, I replaced all the FirstPage function calls in my article summary pages with $Content.Teaser and it works like a charm. My teasers can be any size I want and contain anything I want.
    -------------------------------------

    I realize that I had to hack the code to do it but (IMHO) this was the cleanest and most direct way to do it.

    I hope someone else finds it useful and that it even gets consideration for core changes.

    - Kent

  • Willr
    Avatar
    Forum Moderator
    5163 Posts

    Re: A Better FirstParagraph() for Teasers Link to this post

    Kent, Nice work. If you want it for core consideration then post this as a ticket + patch on open.silverstripe.com

    2216 Views
Page: 1
Go to Top

Want to know more about the company that brought you SilverStripe? Then check out SilverStripe.com

Comments on this website? Please give feedback.