Symfony World blog is not maintained anymore. Check new sys.exit() programming blog.

Using tinyMCE with Symfony

Scene from "Dirty Dancing" by Emile Ardolino (1987)

Symfony gives you a possibility to use lots of third party libraries. One of them, extremely useful while developing backend of your application, is tinyMCE.

The symfony core team has created the sfFormExtraPlugin which helps you to attach tinyMCE to your projects. But there's a lot more you can do to make this editor satisfy your needs.

Configuring tinyMCE

  • I'm assuming you have properly installed the sfFormExtraPlugin inside your project.
  • First of all, you need to install tinyMCE on your own. Go to the download page, and put the extracted content inside your project's web directory. The downloaded archive includes tinyMCE usage examples, you may remove them and include only the tiny_mce directory inside your /web/js directory.
  • Right after including the tinyMCE files don't forget to attach them to the application: go to the backend/config/view.yml file and add the tiny_mce.js file. It should look similar to the following:
    javascripts:    [ /js/tiny_mce/tiny_mce.js ]
    TinyMCE is properly installed in our app already. Now it's time to set the inputs to use it.
  • Edit any of your lib/form classes and set the appropriate widget:
    class SubpageForm extends BaseSubpageForm
    {
      public function configure()
      {
        $this->widgetSchema['content'] =
          new sfWidgetFormTextareaTinyMCE();
      }
    }
    Run your backend application and check if everything is OK.

Advanced tinyMCE configuration

Lots of us may create websites for non-English communities, therefore custom language support is needed. Of course, there are more specific attributes you may want to change. Unfortunately, the sfWidgetFormTextareaTinyMCE class provided with the plugin is hardly configurable. The best solution is to create a custom class extending it and override all the attributes you need.

  • Create a sfWidgetFormTextareaTinyMCECustom class in the /lib/widget directory of your project:
    class sfWidgetFormTextareaTinyMCECustom extends sfWidgetFormTextareaTinyMCE
  • Override the render method. The main code that we are interested in is:
    $js = sprintf(<<<EOF
    <script type="text/javascript">
    tinyMCE.init({
        mode:                              "exact",
        elements:                          "%s",
        theme:                             "%s",
        plugins : "safari,spellchecker,pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template,wordcount,imagemanager,filemanager",
        language: "pl",
        %s
        %s
        theme_advanced_buttons1 : "save,newdocument,|,bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect,fontselect,fontsizeselect",
        theme_advanced_buttons2 : "cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,insertdate,inserttime,preview,|,forecolor,backcolor",
        theme_advanced_buttons3 : "tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,emotions,iespell,media,advhr,|,print,|,ltr,rtl,|,fullscreen",
        theme_advanced_buttons4 : "insertlayer,moveforward,movebackward,absolute,|,styleprops,spellchecker,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,template,blockquote,pagebreak,|,insertfile,insertimage",
        theme_advanced_toolbar_location:   "top",
        theme_advanced_toolbar_align:      "left",
        theme_advanced_statusbar_location: "bottom",
        theme_advanced_resizing:           true
        %s
      });
     
    </script>
    EOF
  • Let's get our custom language working with the tinyMCE now. Go to the language pack download page. Choose the language(s) you want to install, check them (do not download the xml files since you'd have to compile them into the .js files) and press the download just below the end of the list. Move the zip archive into the tiny_mce directory and extract it there. All language files will be extracted in the appropriate places. So far, so good.
  • Edit the render method - add the following code inside the javascript code mentioned above:
    language: "pl",
    and your language pack should already work fine.
  • Since the default tinyMCE configuration doesn't include all possible features, you may add the following lines to enable all features:
    plugins : "safari,spellchecker,pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template,wordcount,imagemanager,filemanager",
    
    and
    theme_advanced_buttons1 : "save,newdocument,|,bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect,fontselect,fontsizeselect",
        theme_advanced_buttons2 : "cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,insertdate,inserttime,preview,|,forecolor,backcolor",
        theme_advanced_buttons3 : "tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,emotions,iespell,media,advhr,|,print,|,ltr,rtl,|,fullscreen",
        theme_advanced_buttons4 : "insertlayer,moveforward,movebackward,absolute,|,styleprops,spellchecker,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,template,blockquote,pagebreak,|,insertfile,insertimage",
    
  • After those modifications your code should look like the following:
    $js = sprintf(<<<EOF
    <script type="text/javascript">
      tinyMCE.init({
        mode:                              "exact",
        elements:                          "%s",
        theme:                             "%s",
        plugins : "safari,spellchecker,pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template,wordcount,imagemanager,filemanager",
        language: "pl",
        %s
        %s
        theme_advanced_buttons1 : "save,newdocument,|,bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect,fontselect,fontsizeselect",
        theme_advanced_buttons2 : "cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,insertdate,inserttime,preview,|,forecolor,backcolor",
        theme_advanced_buttons3 : "tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,emotions,iespell,media,advhr,|,print,|,ltr,rtl,|,fullscreen",
        theme_advanced_buttons4 : "insertlayer,moveforward,movebackward,absolute,|,styleprops,spellchecker,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,template,blockquote,pagebreak,|,insertfile,insertimage",
        theme_advanced_toolbar_location:   "top",
        theme_advanced_toolbar_align:      "left",
        theme_advanced_statusbar_location: "bottom",
        theme_advanced_resizing:           true
        %s
      });
    </script>
    EOF
    
    Of course, you may not need all of the tinyMCE features, feel free to modify the code.
  • Just to make sure, such installation enables all tinyMCE features on your frontend application, such as emoticon images. Play with it!

Hope everything will work fine. Any feedback is appreciated.

Symfony newsletter using Swift mailer

Scene from "Postman Pat" by John Cunliffe & Ivor Wood

introduction

I had a task to implement a newsletter system for a sf 1.4 project. It was quite simple: an anonymous user from outside can subscribe to the newsletter (storing his E-mail address in the database). The site admin can create a newsletter article (with simple properties like title and content). When the send button is pressed, this article is sent to all subscribed E-mails.

output escaping

The newsletter article content value is modified using javascript rich editor such as tinyMCE or FCK Editor, so it has HTML tags like <p>. I forced a problem that either Symfony or Swift was escaping all those HTML tags. I was trying to find solution using methods like getRawValue, getRaw or anything like that, but it turned out that it was Swift Mailer who was the cause of the problem. Following this small tutorial, I needed to add only one line of code:

$message->setContentType("text/html");
Now all formatting was shown properly.

final code solution

I hope that some of you may find this piece of code useful:

public function executeListSend()
{
  $newsletter = $this->getRoute()->getObject();
  if ($newsletter->getSent())
  {
    $this->getUser()->setFlash('notice',
      'The selected newsletter has already been sent.');
  }
  else
  {
    $address_collection = Doctrine::getTable('Address')
      ->getAllAddressesQuery()
      ->fetchArray();
 
    $addresses = array();
    foreach($address_collection as $address)
      $addresses[] = $address['address'];
 
    $message = $this->getMailer()->compose(
      array('name.surname@gmail.com' => 'sitename'),
      $addresses,
      $newsletter->getTitle(),
      $newsletter->getContent()
    );
    $message->setContentType("text/html");
    $this->getMailer()->send($message);
 
    $newsletter->setSent(true);
    $newsletter->save();
 
    $this->getUser()->setFlash('notice',
      'The selected newsletter has been sent successfully.');
  }
  $this->redirect('@newsletter');
}
Implementing a newsletter with Symfony & Swift is really easy!

Many-to-many relations with on delete cascade

Scene from "Ghostbusters" by Ivan Reitman (1984)

The following article is based on a section from the symfony doctrine book, where an example schema of many-to-many relation is defined. The part that is interesting is defining relations of BlogPostTag:

relations:
    BlogPost:
      foreignAlias: BlogPostTags
    Tag:
      foreignAlias: BlogPostTags

In many situations, it is very useful to add the ON DELETE CASCADE option there, since the BlogPostTag (or any other many-to-many relation table) will have constraints disabling deletion of related records. In other words, if you leave the schema the way it is now, you may find (using an admin generator for example) you can't delete a BlogPost or a Tag if there's at least one BlogPostTag joining the record you want to delete. Example database error message will look like:

Cannot delete or update a parent row: a foreign key constraint fails
(`table`, CONSTRAINT `index` FOREIGN KEY (`column`) REFERENCES `table` (`id`))]

This is because of the constraint which is created in the database - it wants you to delete joining records (BlogPostTag) first, then there are no constraints on both Tag and BlogPost tables. To solve the problem (make the database automatically delete related BlogPostTag records whenever a Tag or BlogPost is deleted), simply add two lines to the text:

relations:
    BlogPost:
      foreignAlias: BlogPostTags
      onDelete: CASCADE
    Tag:
      foreignAlias: BlogPostTags
      onDelete: CASCADE
Rerun building your model and recreate your database. From now on, there shall be no constraint fails on your database.