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); } } } }