public function onBeforeSend()
 {
     $email = $this->owner;
     $letter = $email->Newsletter();
     $body = new SS_HTMLValue($email->Body()->forTemplate());
     $links = array();
     $member = null;
     if (!$body || !$letter) {
         return;
     }
     if ($email->To()) {
         $member = DataObject::get_one('Member', sprintf('"Email" = \'%s\'', Convert::raw2sql($email->To())));
     }
     // First build up a set of all the unique links within the newsletter,
     // along with the elements that link to them.
     foreach ($body->getElementsByTagName('a') as $link) {
         $href = $link->getAttribute('href');
         if (strpos($href, '{$') !== false || strpos($href, 'mailto:') !== false) {
             // ignore links with keywords
             continue;
         }
         if (array_key_exists($href, $links)) {
             $links[$href][] = $link;
         } else {
             $links[$href] = array($link);
         }
     }
     // Then actually do the processing. Create a unique tracking object for
     // each link. Attempt to embed a member-specific tracking token if
     // the newsletter is being sent to a member.
     foreach ($links as $href => $elements) {
         $track = DataObject::get_one('Newsletter_TrackedLink', sprintf('"NewsletterID" = %d AND "Original" = \'%s\'', $letter->ID, Convert::raw2sql($href)));
         if (!$track) {
             $track = new Newsletter_TrackedLink();
             $track->Original = $href;
             $track->NewsletterID = $letter->ID;
             $track->write();
         }
         if ($member) {
             $trackHref = Controller::join_links(Director::baseURL(), 'newsletter-link', $member->NewsletterTrackingToken, $track->Hash);
         } else {
             $trackHref = Controller::join_links(Director::baseURL(), 'newsletter-link', $track->Hash);
         }
         foreach ($elements as $element) {
             $element->setAttribute('href', $trackHref);
         }
     }
     $dom = $body->getDocument();
     $email->setBody(DBField::create('HTMLText', $dom->saveHTML()));
 }
 /**
  * Given an SS_HTMLValue instance, will remove and elements and attributes that are
  * not explicitly included in the whitelist passed to __construct on instance creation
  *
  * @param SS_HTMLValue $html - The HTMLValue to remove any non-whitelisted elements & attributes from
  */
 public function sanitise(SS_HTMLValue $html)
 {
     if (!$this->elements && !$this->elementPatterns) {
         return;
     }
     $doc = $html->getDocument();
     foreach ($html->query('//body//*') as $el) {
         $elementRule = $this->getRuleForElement($el->tagName);
         // If this element isn't allowed, strip it
         if (!$this->elementMatchesRule($el, $elementRule)) {
             // If it's a script or style, we don't keep contents
             if ($el->tagName === 'script' || $el->tagName === 'style') {
                 $el->parentNode->removeChild($el);
             } else {
                 // First, create a new fragment with all of $el's children moved into it
                 $frag = $doc->createDocumentFragment();
                 while ($el->firstChild) {
                     $frag->appendChild($el->firstChild);
                 }
                 // Then replace $el with the frags contents (which used to be it's children)
                 $el->parentNode->replaceChild($frag, $el);
             }
         } else {
             // First, if we're supposed to pad & this element is empty, fix that
             if ($elementRule->paddEmpty && !$el->firstChild) {
                 $el->nodeValue = ' ';
             }
             // Then filter out any non-whitelisted attributes
             $children = $el->attributes;
             $i = $children->length;
             while ($i--) {
                 $attr = $children->item($i);
                 $attributeRule = $this->getRuleForAttribute($elementRule, $attr->name);
                 // If this attribute isn't allowed, strip it
                 if (!$this->attributeMatchesRule($attr, $attributeRule)) {
                     $el->removeAttributeNode($attr);
                 }
             }
             // Then enforce any default attributes
             foreach ($elementRule->attributesDefault as $attr => $default) {
                 if (!$el->getAttribute($attr)) {
                     $el->setAttribute($attr, $default);
                 }
             }
             // And any forced attributes
             foreach ($elementRule->attributesForced as $attr => $forced) {
                 $el->setAttribute($attr, $forced);
             }
         }
     }
 }