2013-08-07 01:03:15 +02:00
|
|
|
<?php
|
2015-08-24 06:14:33 +02:00
|
|
|
|
2016-07-22 01:32:32 +02:00
|
|
|
namespace SilverStripe\CMS\Model;
|
|
|
|
|
2015-08-24 06:14:33 +02:00
|
|
|
/**
|
|
|
|
* @package cms
|
|
|
|
* @subpackage model
|
|
|
|
*/
|
|
|
|
|
2016-08-10 06:08:39 +02:00
|
|
|
use DOMElement;
|
2016-06-16 06:57:19 +02:00
|
|
|
use SilverStripe\ORM\ManyManyList;
|
|
|
|
use SilverStripe\ORM\Versioning\Versioned;
|
|
|
|
use SilverStripe\ORM\FieldType\DBHTMLText;
|
|
|
|
use SilverStripe\ORM\DataExtension;
|
|
|
|
use SilverStripe\ORM\DataObject;
|
2016-07-22 01:32:32 +02:00
|
|
|
use Injector;
|
|
|
|
use SS_HTMLValue;
|
|
|
|
use Director;
|
2016-03-22 22:00:16 +01:00
|
|
|
|
2013-08-07 01:03:15 +02:00
|
|
|
/**
|
2014-12-01 12:22:48 +01:00
|
|
|
* Adds tracking of links in any HTMLText fields which reference SiteTree or File items.
|
2014-08-12 06:39:11 +02:00
|
|
|
*
|
2013-08-07 01:03:15 +02:00
|
|
|
* Attaching this to any DataObject will add four fields which contain all links to SiteTree and File items
|
2014-12-01 12:22:48 +01:00
|
|
|
* referenced in any HTMLText fields, and two booleans to indicate if there are any broken links. Call
|
|
|
|
* augmentSyncLinkTracking to update those fields with any changes to those fields.
|
2013-08-07 01:03:15 +02:00
|
|
|
*
|
2016-01-26 06:38:42 +01:00
|
|
|
* Note that since both SiteTree and File are versioned, LinkTracking and ImageTracking will
|
|
|
|
* only be enabled for the Stage record.
|
|
|
|
*
|
|
|
|
* {@see SiteTreeFileExtension} for the extension applied to {@see File}
|
|
|
|
*
|
2015-10-15 00:08:52 +02:00
|
|
|
* @property SiteTree $owner
|
2013-08-07 01:03:15 +02:00
|
|
|
*
|
2015-10-15 00:08:52 +02:00
|
|
|
* @property bool $HasBrokenFile
|
|
|
|
* @property bool $HasBrokenLink
|
2014-12-01 12:22:48 +01:00
|
|
|
*
|
2015-10-15 00:08:52 +02:00
|
|
|
* @method ManyManyList LinkTracking() List of site pages linked on this page.
|
|
|
|
* @method ManyManyList ImageTracking() List of Images linked on this page.
|
2016-01-26 06:38:42 +01:00
|
|
|
* @method ManyManyList BackLinkTracking List of site pages that link to this page.
|
2013-08-07 01:03:15 +02:00
|
|
|
*/
|
|
|
|
class SiteTreeLinkTracking extends DataExtension {
|
|
|
|
|
2016-03-17 01:02:50 +01:00
|
|
|
/**
|
|
|
|
* @var SiteTreeLinkTracking_Parser
|
|
|
|
*/
|
|
|
|
protected $parser;
|
2014-08-12 06:39:11 +02:00
|
|
|
|
2016-03-17 01:02:50 +01:00
|
|
|
/**
|
|
|
|
* Inject parser for each page
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
* @config
|
|
|
|
*/
|
2014-08-12 06:39:11 +02:00
|
|
|
private static $dependencies = array(
|
2016-03-17 01:02:50 +01:00
|
|
|
'Parser' => '%$SiteTreeLinkTracking_Parser'
|
2014-08-12 06:39:11 +02:00
|
|
|
);
|
|
|
|
|
2016-03-17 01:02:50 +01:00
|
|
|
/**
|
|
|
|
* Parser for link tracking
|
|
|
|
*
|
|
|
|
* @return SiteTreeLinkTracking_Parser
|
|
|
|
*/
|
|
|
|
public function getParser() {
|
|
|
|
return $this->parser;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param SiteTreeLinkTracking_Parser $parser
|
|
|
|
* @return $this
|
|
|
|
*/
|
|
|
|
public function setParser($parser) {
|
|
|
|
$this->parser = $parser;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-08-07 01:03:15 +02:00
|
|
|
private static $db = array(
|
|
|
|
"HasBrokenFile" => "Boolean",
|
|
|
|
"HasBrokenLink" => "Boolean"
|
|
|
|
);
|
|
|
|
|
|
|
|
private static $many_many = array(
|
2016-07-22 01:32:32 +02:00
|
|
|
"LinkTracking" => "SilverStripe\\CMS\\Model\\SiteTree",
|
2016-02-21 23:44:24 +01:00
|
|
|
"ImageTracking" => "File" // {@see SiteTreeFileExtension}
|
2013-08-07 01:03:15 +02:00
|
|
|
);
|
|
|
|
|
2016-01-26 06:38:42 +01:00
|
|
|
private static $belongs_many_many = array(
|
2016-08-10 06:08:39 +02:00
|
|
|
"BackLinkTracking" => "SilverStripe\\CMS\\Model\\SiteTree.LinkTracking"
|
2016-01-26 06:38:42 +01:00
|
|
|
);
|
|
|
|
|
2016-02-21 23:44:24 +01:00
|
|
|
/**
|
|
|
|
* Tracked images are considered owned by this page
|
|
|
|
*
|
|
|
|
* @config
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
private static $owns = array(
|
|
|
|
"ImageTracking"
|
|
|
|
);
|
|
|
|
|
2013-08-07 01:03:15 +02:00
|
|
|
private static $many_many_extraFields = array(
|
|
|
|
"LinkTracking" => array("FieldName" => "Varchar"),
|
|
|
|
"ImageTracking" => array("FieldName" => "Varchar")
|
|
|
|
);
|
|
|
|
|
2014-12-01 12:22:48 +01:00
|
|
|
/**
|
|
|
|
* Scrape the content of a field to detect anly links to local SiteTree pages or files
|
|
|
|
*
|
2015-06-02 09:23:48 +02:00
|
|
|
* @param string $fieldName The name of the field on {@link @owner} to scrape
|
2014-12-01 12:22:48 +01:00
|
|
|
*/
|
2014-08-15 02:04:48 +02:00
|
|
|
public function trackLinksInField($fieldName) {
|
2013-08-07 01:03:15 +02:00
|
|
|
$record = $this->owner;
|
|
|
|
|
|
|
|
$linkedPages = array();
|
|
|
|
$linkedFiles = array();
|
|
|
|
|
2014-08-15 02:04:48 +02:00
|
|
|
$htmlValue = Injector::inst()->create('HTMLValue', $record->$fieldName);
|
2014-08-12 06:39:11 +02:00
|
|
|
$links = $this->parser->process($htmlValue);
|
2013-08-07 01:03:15 +02:00
|
|
|
|
2014-08-15 02:04:48 +02:00
|
|
|
// Highlight broken links in the content.
|
|
|
|
foreach ($links as $link) {
|
2016-03-09 23:57:39 +01:00
|
|
|
// Skip links without domelements
|
|
|
|
if(!isset($link['DOMReference'])) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-08-10 06:08:39 +02:00
|
|
|
/** @var DOMElement $domReference */
|
|
|
|
$domReference = $link['DOMReference'];
|
|
|
|
$classStr = trim($domReference->getAttribute('class'));
|
2014-08-15 02:04:48 +02:00
|
|
|
if (!$classStr) {
|
|
|
|
$classes = array();
|
|
|
|
} else {
|
|
|
|
$classes = explode(' ', $classStr);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add or remove the broken class from the link, depending on the link status.
|
|
|
|
if ($link['Broken']) {
|
|
|
|
$classes = array_unique(array_merge($classes, array('ss-broken')));
|
|
|
|
} else {
|
|
|
|
$classes = array_diff($classes, array('ss-broken'));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!empty($classes)) {
|
2016-08-10 06:08:39 +02:00
|
|
|
$domReference->setAttribute('class', implode(' ', $classes));
|
2014-08-15 02:04:48 +02:00
|
|
|
} else {
|
2016-08-10 06:08:39 +02:00
|
|
|
$domReference->removeAttribute('class');
|
2014-08-15 02:04:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
$record->$fieldName = $htmlValue->getContent();
|
|
|
|
|
2013-08-07 01:03:15 +02:00
|
|
|
// Populate link tracking for internal links & links to asset files.
|
2014-08-12 06:39:11 +02:00
|
|
|
foreach ($links as $link) {
|
|
|
|
switch ($link['Type']) {
|
|
|
|
case 'sitetree':
|
|
|
|
if ($link['Broken']) {
|
|
|
|
$record->HasBrokenLink = true;
|
|
|
|
} else {
|
|
|
|
$linkedPages[] = $link['Target'];
|
|
|
|
}
|
|
|
|
break;
|
2013-08-07 01:03:15 +02:00
|
|
|
|
2014-08-12 06:39:11 +02:00
|
|
|
case 'file':
|
2016-03-09 23:57:39 +01:00
|
|
|
case 'image':
|
2014-08-12 06:39:11 +02:00
|
|
|
if ($link['Broken']) {
|
|
|
|
$record->HasBrokenFile = true;
|
|
|
|
} else {
|
|
|
|
$linkedFiles[] = $link['Target'];
|
2013-08-07 01:03:15 +02:00
|
|
|
}
|
2014-08-12 06:39:11 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
if ($link['Broken']) {
|
2013-08-23 04:33:36 +02:00
|
|
|
$record->HasBrokenLink = true;
|
|
|
|
}
|
2014-08-12 06:39:11 +02:00
|
|
|
break;
|
2013-08-07 01:03:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the "LinkTracking" many_many
|
2015-10-15 00:08:52 +02:00
|
|
|
if($record->ID && $record->manyManyComponent('LinkTracking') && ($tracker = $record->LinkTracking())) {
|
|
|
|
$tracker->removeByFilter(array(
|
|
|
|
sprintf('"FieldName" = ? AND "%s" = ?', $tracker->getForeignKey())
|
|
|
|
=> array($fieldName, $record->ID)
|
2013-08-07 01:03:15 +02:00
|
|
|
));
|
|
|
|
|
|
|
|
if($linkedPages) foreach($linkedPages as $item) {
|
2014-08-15 02:04:48 +02:00
|
|
|
$tracker->add($item, array('FieldName' => $fieldName));
|
2013-08-07 01:03:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the "ImageTracking" many_many
|
2015-10-15 00:08:52 +02:00
|
|
|
if($record->ID && $record->manyManyComponent('ImageTracking') && ($tracker = $record->ImageTracking())) {
|
|
|
|
$tracker->removeByFilter(array(
|
|
|
|
sprintf('"FieldName" = ? AND "%s" = ?', $tracker->getForeignKey())
|
|
|
|
=> array($fieldName, $record->ID)
|
2013-08-07 01:03:15 +02:00
|
|
|
));
|
|
|
|
|
|
|
|
if($linkedFiles) foreach($linkedFiles as $item) {
|
2014-08-15 02:04:48 +02:00
|
|
|
$tracker->add($item, array('FieldName' => $fieldName));
|
2013-08-07 01:03:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-01 12:22:48 +01:00
|
|
|
/**
|
|
|
|
* Find HTMLText fields on {@link owner} to scrape for links that need tracking
|
2016-01-26 06:38:42 +01:00
|
|
|
*
|
|
|
|
* @todo Support versioned many_many for per-stage page link tracking
|
2014-12-01 12:22:48 +01:00
|
|
|
*/
|
2014-08-15 02:04:48 +02:00
|
|
|
public function augmentSyncLinkTracking() {
|
2016-01-26 06:38:42 +01:00
|
|
|
// Skip live tracking
|
2016-06-16 06:57:19 +02:00
|
|
|
if(Versioned::get_stage() == Versioned::LIVE) {
|
2016-01-26 06:38:42 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-08-07 01:03:15 +02:00
|
|
|
// Reset boolean broken flags
|
|
|
|
$this->owner->HasBrokenLink = false;
|
|
|
|
$this->owner->HasBrokenFile = false;
|
|
|
|
|
|
|
|
// Build a list of HTMLText fields
|
|
|
|
$allFields = $this->owner->db();
|
|
|
|
$htmlFields = array();
|
|
|
|
foreach($allFields as $field => $fieldSpec) {
|
2016-03-22 22:00:16 +01:00
|
|
|
$fieldObj = $this->owner->dbObject($field);
|
|
|
|
if($fieldObj instanceof DBHTMLText) {
|
|
|
|
$htmlFields[] = $field;
|
2013-08-07 01:03:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-26 06:38:42 +01:00
|
|
|
foreach($htmlFields as $field) {
|
|
|
|
$this->trackLinksInField($field);
|
|
|
|
}
|
2013-08-07 01:03:15 +02:00
|
|
|
}
|
2014-08-12 06:39:11 +02:00
|
|
|
}
|