Full highlighting functionality with symfony, Geshi and TinyMCE


Symfony | Technical | Development | Doctrine | September 7, 2010

Most of the time when displaying programming code in a web page is better to see it highlighted. It is better for the visitors who try to read the code, to see it highlighted like in an IDE. Well, in this post, a full highlighting functionality will be described step by step with symfony, GeSHI and TinyMCE, based on a previous post.

 

Schema

First we will focus on the schema for the database.

GeSHI will parse code like:

[php] echo 'symfony, GeSHI and TinyMCE'; [/php]

 

And will produce code like:

 
<p><span class="kw1">echo</span><span class="st_h"> 'symfony, GeSHI and TinyMCE'</span>;</p>
 

 

This parsing is quite intensive, then we can not do it everytime a post needs to be shown. Then we must have a cache table for our posts, where to save the parsed code. This way we will have two tables: post and post_index

 

 
Post:
  tableName:          post
  actAs:
    Sluggable:        { fields: [ title ] }
    Timestampable:
  columns:
    id:               { type: integer  , length: 20 , primary: true    , autoincrement: true                                                  }
    title:            { type: string   , length: 200                   , notnull: true                                                        }
    content:          { type: string   , length: 20000                 , notnull: true                                                        }
    excerpt:          { type: string   , length: 500                   , notnull: true                                                        }
  indexes:
    u_title:          { fields: [ title ]                              , type: unique                                                         }
    i_content:        { fields: { content: { length: 400 } }                                                                                  }
    i_excerpt:        { fields: { excerpt: { length: 100 } }                                                                                  }
  relations:
    PostIndex:        { class: PostIndex        , local: id            , foreign: post_id      , type: one , alias: PostIndex                                         }
 
PostIndex:
  tableName:          post_index
  columns:
    id:               { type: integer  , length: 20 , primary: true    , autoincrement: true                                                  }
    post_id:          { type: integer  , length: 20                    , notnull: true                                                        }
    title:            { type: string   , length: 200                   , notnull: true                                                        }
    content:          { type: string   , length: 20000                 , notnull: true                                                        }
  indexes:
    u_title:          { fields: [ title ]                              , type: unique                                                         }
    i_content:        { fields: { content: { length: 400 } }                                                                                  }
  relations:
    Post:             { class: Post             , local: post_id       , foreign: id           , type: one , alias: Post      , onDelete: CASCADE , onUpdate: CASCADE }
 

 

 

Form

To properly insert and update data to our new brand post table we need to customize a PostForm class:

 

 
class PostForm extends BasePostForm
{
  public function configure()
  {
    $this->setWidgets(array
    (
      'id'        => new sfWidgetFormInputHidden(),
      'title'     => new sfWidgetFormInputText(array(), array('size' => 60)),
      'content'   => new sfWidgetFormTextareaTinyMCE(array
                     (
                       'width'            => 550,
                       'height'           => 350,
                       'config'           => 'theme_advanced_disable: "anchor,cleanup,help"',
                     )),
  }
 
  protected function updateContentColumn($content)
  {
    $index_content = preg_replace_callback('#< pre>\[(\w+)\](.*?)\[/\\1\]< /pre>#s', array($this, 'processCode'), $content);
 
    $this->object->getPostIndex()->setContent($index_content);
 
    return $content;
  }
 
  protected function processCode($matches)
  {
    $code = str_replace('& nbsp;', ' ', $matches[2]);
    $code = html_entity_decode($code);
 
    $geshi = new GeSHi($code, $matches[1]);
    $geshi->set_header_type(GESHI_HEADER_PRE);
    $geshi->enable_classes();
 
    return $geshi->parse_code();
  }
}
 

 

In this class we configure the post form to show the title and content widgets. In the content widget it is been used the great sfWidgetFormTextareaTinyMCE class from the sfFormExtraPlugin.

In the updateContentColumn method it is applied the processCode method for each coincidence of

< pre>[(text)] ..... [/(text)]< /pre>

, where text is the type of desired code to be highlighted, for example it could be: php, java, yaml, css, javascript, etc. For further explanation about that part see the previous post about highlighting code with symfony and Geshi.

 

Also note that the parsed code is assigned to the content of the PostIndex object:

 
$this->object->getPostIndex()->setContent($index_content);
 

 

 

TinyMCE

Now it is the time to talk about one big issue with this editor when trying to paste code.

The heart of the problem lies in the fact that each time some code is pasted in TinyMCE, automatically it is added html formatting, that is tags with a lot of &nbps; and all weird characters like -> are escape.

The problem with this is GeSHI needs clean code, like the one produced when copying to a simple textarea, but not the TinyMCE formatted one.

The solution is the PreElementFix Plugin for TinyMCE. The instructions for the installation are in the previous link. The great thing about this plugin is that it does not allow the addition of tags when pasting some code. The bad things is that there is still escape sequences. Well this is not a bad thing at all, it has to be this way for the code to be HTML compliant.

To configure the widget you can use the updated render method in the sfWidgetFormTextareaTinyMCE class:

 

 
  public function render($name, $value = null, $attributes = array(), $errors = array())
  {
    $textarea = parent::render($name, $value, $attributes, $errors);
 
    $js = sprintf(<<< script type="text/javascript">
  tinyMCE.init({
    mode:                              "exact",
    elements:                          "%s",
    theme:                             "%s",
    plugins :                          "paste,preelementfix",
    %s
    %s
    theme_advanced_toolbar_location:   "top",
    theme_advanced_toolbar_align:      "left",
    theme_advanced_statusbar_location: "bottom",
    theme_advanced_resizing:           true,
    paste_auto_cleanup_on_paste : true
    %s
  });
EOF
    return $textarea.$js;
  }
 

 

And now, the solution to the escape sequences is to return them to their previous state. We can do this with this two lines of code in the form class:

 
$code = str_replace('& nbsp;', ' ', $matches[2]);
$code = html_entity_decode($code);
 

 

You can think, why to use the first line when at first sight html_entity_decode will do the job. However this function has some problems with &nbps; check that in the html_entity_decode php info.

 

After following all of these steps you will have an TinyMCE like:

 

TinyMCE image example

 

 

Finally, it is shown that with the use of a combination of tools like symfony, GeSHI and TinyMCE we can get clean and highlighted code like the one you saw in this post.

 

PS: An exercise for the reader is to discover why there are tiny on purpose errors on the code above, for example with &nbps; being & nbsp; .


Comments




Last Posts

Autoloading: Symfony vs Yii

Symfony2, PhpBB4 and Drupal8

Type and boolean columns with Doctrine and Symfony

Refactoring queries with Doctrine

Extending your Doctrine Model: Template Classes

Integrating Doctrine: Symfony vs Yii

Passing parameters from the action to the view: Symfony vs Yii

Yii framework

Adding custom information to your Doctrine schema

PHP Advent Calendar 2010


My Tweets

9 days ago
20 finalists of BBVA Open Talent http://t.co/puiM5oIf9Q via @startupranking
9 days ago
@GoodCoworking was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
9 days ago
The world is on fire! http://t.co/OMyRUvD6FG
9 days ago
@TalosAnalytics was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
9 days ago
@brandscreen was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
9 days ago
@eatwithusindia was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
10 days ago
@dropifi was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
12 days ago
Echelon 2013 Top Startups Pitching http://t.co/RHU9iKDvms via @startupranking
12 days ago
@Startupeando was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
13 days ago
@kekanto was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
14 days ago
@lazosperu was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
14 days ago
Startup Ranking is a metastartup that provides startups with important information about their metrics and statistics http://t.co/xkbfBmZpop
15 days ago
I'm starting to use http://t.co/D640KCry9Y via @startupranking
15 days ago
@inc42 was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
16 days ago
@larryportera antes q pase lo culpan Ollanta de chavista. #reallife
18 days ago
Hey, have you already seen the new Last Month feature at http://t.co/mQBsMS21dY?
21 days ago
@tuizzi was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
21 days ago
@SkoomBR was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
22 days ago
@lagoa was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
22 days ago
@spundge was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
22 days ago
@PasswordBox was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
22 days ago
@vanillaforums was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
22 days ago
@empireavenue was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
23 days ago
@skoop @fabpot @hhamon +1 please fix :)
23 days ago
We reached 500 startups with the Start-Up Chile’s 7th Generationhttp://t.co/4mkNuKf5NWNW
23 days ago
@fabsie was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
23 days ago
@CoContest was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
23 days ago
@DoubleDoods was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
23 days ago
@KaraokeSmart was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
23 days ago
There seems there are new features here. http://t.co/mQBsMS21dY :)
23 days ago
@regples was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
25 days ago
@davemcclure Ok that seems more fun ;)
26 days ago
@Fluentify was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
28 days ago
@DmitryBaranovsk @avalanche123 Epic!
28 days ago
@davemcclure @500Startups and what if I want to be a Tony Stark?
28 days ago
Do you have a startup? Does your startup have any competence? Register them in: http://t.co/mQBsMS21dY
29 days ago
7 Simple Tips To Grow Your Social Network | LinkedIn http://t.co/QxiASZKd8T
29 days ago
@Plaza21com was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
30 days ago
@regples was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
30 days ago
@POPContacts was added to http://t.co/D640KCry9Y . Add your local, regional or global competition and follow them w/ centralized stats.
about 1 month ago
How Funding Works - Splitting The Equity With Investors - Infographic http://t.co/luzjgoWreg
3 months ago
Gran presentación de David Chandler@drfibonaccii. En el#AndroidTourPeruu Gracias!
3 months ago
Does somebody know about a trusted and reliable proxy provider for intensive web scraping?
3 months ago
Crean Red social exclusiva para parejas que ayudaría a combatir la infidelidad e incrementar romanticismo. http://t.co/H2mO2rIBSdd
3 months ago
Reading Startup's Owner Manual: 'Perhaps the ugliest (or most flattering) comment: “It seems to crash my computer every time I use it,”'
3 months ago
@shesho Genial presentación acerca de#UXX gracias! ;)
4 months ago
Hurray! I was one of the first 500,000 @LinkedIn members in Peru. http://t.co/utglPcjT
6 months ago
TechCrunch Giveaway: Nexus 4 And Nexus 10 #TechCrunch http://t.co/9DpeeLPZ via @TechCrunch
7 months ago
Lo bueno que Rommey no dijo en la campaña lo dijo al final.
9 months ago
A real pleasure to work with #Symfony2. :)
9 months ago
Actualizando a #Eclipse Juno.
9 months ago
At last coming back to you my dear #symfony. Updating my knowledge about #symfony2.
9 months ago
@maristiglichv cómo has estado? :)
9 months ago
Mark Zuckerberg:Facebook already processes about 1 billion search queries a day without "even trying."
9 months ago
Brazilian Startup Queremos Is A Kickstarter For Concerts And Other Live Eventshttp://t.co/zOOHt0DPP via@TechCrunchh
9 months ago
FoundationDB — Not Your Standard NoSQL Databahttp://t.co/gbSXk8Zo8Zo v@TechCrunchnch
10 months ago
The perfect match. The first glance can determine how the rest of the story ends. http://t.co/Th1V6AX6
10 months ago
One Month After Selling To Microsoft For $1.2B, Yammer CEO Predicts End Of Silicon Valley http://t.co/9mVTdqFD via @TechCrunch
11 months ago
but because she understands and is able to personally defend, own, and drive forward with implementing the decision
11 months ago
she should be saying so not because you are in a position of authority and you said so,
11 months ago
Once that person is at the point where she is willing to say “yes” to the decision,
about 1 year ago
Writing clean code in PHP 5.4 | Web Builder Zone: http://t.co/IAFFj3A8 via @addthis
about 1 year ago
Interesting tips about #scaling http://t.co/QC2paoDK
about 1 year ago
Learning to use #Windows #ActiveDirectory
about 1 year ago
Ayni - Blog: CUESTIONARIOS PARA ANÁLISIS-http://localhost:8080/AYNI-war/faces/ListarComentario.xhtml?txtIdPublicacion=2
about 1 year ago
Día tranquilo en casa :)#fbb
about 1 year ago
#Adobe Reader App crushed my new #Android movile :/
about 1 year ago
Testing from Android =D
about 1 year ago
#Refactoring code.
about 1 year ago
You should Snog, Marry or Avoid me http://t.co/pZCDIwW
about 1 year ago
Yahoo’s Options v@TechCrunchnhttp://t.co/yjzsKstKst What will Yahoo do?
about 1 year ago
Just realize my post http://t.co/nSuJjSp, written so many time ago, really contribute to make it happen: http://t.co/wMqYrV3
about 1 year ago
An Introduction to Redirecting URLs on an Apache Server http://t.co/s79ThOy via @WebmasterWorld
about 1 year ago
Actualizando a #Eclipse Indigo!
about 1 year ago
Vota x lamula.pe: http://t.co/WTWMkFG via @addthis
about 1 year ago
Apple Pushes Past Exxon To Become The Most Valuable Public Company In The World (Temporarily) via@TechCrunchhhttp://t.co/lowONSZZ
about 1 year ago
finally this class #semester is finished, new #projects in mind
about 1 year ago
Mark Zuckerberg Explains His Law Of Social Sharing [Video]http://t.co/sqq10Ehh via@TechCrunchh
over 2 years ago
retomando mi #twitter #notime
over 2 years ago
How Facebook Can Put Google Out of Businesshttp://t.co/HqDfQGoo via@TechCrunchh
over 2 years ago
So why not just cut out the middle man? Microsoft.http://techcrunch.com/2011/05/15/samsung-series-5-chromebook/
over 2 years ago
really like to #design class hierarchies with #compositepattern
over 2 years ago
Similarly, Microsoft.com started to use jQuery instead of their own ASP.NET Ajax framework. They are still using Windows, for whatever XD
over 2 years ago
Estudiantes de la PUCP le “voltean” campaña a esposo de Keiko | yoperiodhttp://t.co/ruI9UnmI9Unm@lamulaamula
over 2 years ago
Reading: Apress.-.Pro.PHP.Application.Performance.2010 - Very Insteresting #php #performance #read
over 2 years ago
debug_backtrace() is very important on certain situations. #php #debug
over 2 years ago
@alvarezrodrich felicitaciones!
over 2 years ago
time to do some #uml diagrams, #classdiagram
over 2 years ago
hoy es el día,#votaa conciente#peruu
over 2 years ago
@skoop @funkatron I think someone had a bad day!, #Frameworks are there but you don't have to use them.
over 2 years ago
making #wireframes for a new #functionality
over 2 years ago
so much #spam on my #blog =(
over 2 years ago
learning new topics and tools that I did not use before #rcp
over 2 years ago
aprendiendo muchos temas y herramientas que no utilizaba antes #rcp
over 2 years ago
#tweaking httpd.conf #virtualhost
over 2 years ago
thanks #symfony 1.4, even when i'm not using the entire #framework, yours classes save my life!
over 2 years ago
integrating with #SOA using #soap
over 2 years ago
It seems my most #productive working hours are on #sunday #afternoon #evening! XD
over 2 years ago
#ASOT 500 =)
over 2 years ago
installing SCA_SDO on #Centos #php
over 2 years ago
My web service using #soap worked!!!!! #php #SCA #SDO
over 2 years ago
Working in a new place since last week!, #RCP: Red Científica Peruana, the one which sells the .pe domains in#Perúú ->#happypy
over 2 years ago
Finally with a new #laptop: #Toshiba =)
over 2 years ago
@pasku1 Thanks, I will try Pivotaltracker.
over 2 years ago
@doolphy thanks for your answer doolphy! I'll try you!
over 2 years ago
@jmasson thanks for your answer! Jira + Confluence is a good combination.
over 2 years ago
Which is the best project management and collaboration tool right now? #projectmanagement #tool #collaboration
over 2 years ago
What a #voice! Sied Van Riel feat Nicole McKenna - Stealing Time (Aly & Fila Remix) + #ASOT 497 #trancefamily
over 2 years ago
working on a situation where #php #traits would be very useful
over 2 years ago
It is #awesome when you finish doing a lot of changes and nothing is broken =) #TDD #testing
over 2 years ago
oh, happy birthday! @mtabini o mejor dicho feliz cumpleaños!
over 2 years ago
why do #IE8 not accept #javascript "const" keyword?
over 2 years ago
Discovering there is much #more to do with #javascript ... a lot.
over 2 years ago
#composition over #inheritance: #javascript
over 2 years ago
Awesome #song!: Cerf, Mitiska & Jaren - Another World (Original Vocal Mix) #ASOT 495 #trancefamily
over 2 years ago
My legal woman is #PHP, but I have an affair with #Javascript, overall when she wears #jQuery.
over 2 years ago
This presentation is one of the best I've seen about #unit #testing http://www.slideshare.net/avalanche123/clean-code-5609451
over 2 years ago
where to do a master on #IT: US or Spain? #survey #php #master plz RT
over 2 years ago
Bobina feat. Betsie Larkin - You Belong To Me: What a #beautiful voice -> #ASOT 494 #arminvanbuuren
over 2 years ago
OH NO, IT'S MONDAY -- 2011-02-07 http://t.co/pgaIxe5 via @gojkoadzic
over 2 years ago
it was not a + b, it was parserInt(a) + parseInt(b) =(, #javascript #fail
over 2 years ago
The models are complete representations of the system, whereas an #architectural #view focuses only on what is architecturally #significant.
over 2 years ago
@jmasson that would be great and finally #wikimedia, #drupal, #wordpress and maybe #joomla would push towards the same side, the #php side.
over 2 years ago
@jmasson Thanks!, #PHP has a bright #future ahead.
over 2 years ago
A new #blog post about not reinventing the #wheel: http://www.jnieto.org/article/symfony2_phpbb4_and_drupal8 #symfony #phpbb #drupal
over 2 years ago
@giorgiosironi #indeed, that's a very good #question. I think an average of 4 but also depends on how much that #developer work.
over 2 years ago
It seems the new platform for deploying, managing and scaling PHP apps is http://orchestra.io/ #cool
over 2 years ago
#Phase project planning vs #iteration project #planning - #project #management
over 2 years ago
#jeditable with #jquery save my life =)
over 2 years ago
Amazing #song -> Sied van Riel feat. Nicole McKenna - Stealing Time #ASOT 493 #trancefamily
over 2 years ago
I simply love "offset" #jquery function =)
over 2 years ago
I really don't understand why projects like #drupal does not base their components in projects like #doctrine and #symfony
over 2 years ago
OH NO, IT'S MONDAY -- 2011-01-17 http://t.co/37pr4Bd via @gojkoadzic
over 2 years ago
@alvarezrodrich me alegra ver q ya borró su cuenta Sr. Rodrich,#twitterr es malo jajja, XD
over 2 years ago
Acabo de hacerle a mi #brother @diegonl89 un blog para que hable de #actualidad en general: http://www.elgatotechero.com #peru
over 2 years ago
I just made to my #brother a #blog to talk about current #events in #peru: http://www.elgatotechero.com
over 2 years ago
Amazing things can be done with #javascript and #css, and of course with the help of #jQuery =)
over 2 years ago
An architecturally significant element is an element that is important for #understanding the #system.
over 2 years ago
An architecturally element has a wide impact on the #structure, #performance, #robustness, #evolvability, and #scalability of a #system.
over 2 years ago
@giorgiosironi Definitely!
over 2 years ago
Playing with #table #inheritance in #Doctrine
over 2 years ago
Reading about #RUP, and how addresses the #major difficulties in a new #project.
over 2 years ago
Yandex in 2010: 43 percent revenue growth http://t.co/cpjT5Jw via @cnet
over 2 years ago
Going forward!!!!! =) poco a poco llegan los resultados de tanto esfuerzo #fb
over 2 years ago
Perfect #system with respect to the #requirements but the #wrong system with respect to the #real #problem at the time of #delivery.
over 2 years ago
Going #forward! =) #fb
over 2 years ago
@sam_dark Ok thanks!, but I don't understand why in #Yii documentantion use $_GET and $_POST instead of CHttpRequest http://bit.ly/i5emoL