mirror of
https://github.com/silverstripe/silverstripe-cms
synced 2024-10-22 08:05:56 +02:00
API Implement polymorphic sitetree link tracking (#2123)
* WIP Implement polymorphic sitetree link tracking * Update unit tests Merge SiteTreeTrackedPage into SiteTree directly * Fix bugs and issues * Fix support for file link tracking * Add missing use * Add back deprecated extension * Remove obsolete belongs_many_many * Update deprecations * BUG Ensure non-SiteTree records support link tracking * Safer changed check * Shift file tracking test to assets module * Better check for live stage on versioning * Deprecate method * Cleanup virtualpage * Clear records on delete * Ensure upgrade task occurs on draft * fix linting
This commit is contained in:
parent
48f53a522a
commit
6c616f5f7a
@ -4,9 +4,6 @@ Name: cmsextensions
|
||||
SilverStripe\Admin\LeftAndMain:
|
||||
extensions:
|
||||
- SilverStripe\CMS\Controllers\LeftAndMainPageIconsExtension
|
||||
SilverStripe\Assets\File:
|
||||
extensions:
|
||||
- SilverStripe\CMS\Model\SiteTreeFileExtension
|
||||
---
|
||||
Name: cmsmodals
|
||||
---
|
||||
|
6
_config/linktracking.yml
Normal file
6
_config/linktracking.yml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
Name: cmslinktracking
|
||||
---
|
||||
SilverStripe\ORM\DataObject:
|
||||
extensions:
|
||||
- SilverStripe\CMS\Model\SiteTreeLinkTracking
|
@ -4,6 +4,7 @@ namespace SilverStripe\CMS\Model;
|
||||
|
||||
use Page;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use SilverStripe\Assets\Shortcodes\FileLinkTracking;
|
||||
use SilverStripe\CampaignAdmin\AddToCampaignHandler_FormAction;
|
||||
use SilverStripe\CMS\Controllers\CMSPageEditController;
|
||||
use SilverStripe\CMS\Controllers\ContentController;
|
||||
@ -48,6 +49,7 @@ use SilverStripe\ORM\CMSPreviewable;
|
||||
use SilverStripe\ORM\DataList;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\ORM\HasManyList;
|
||||
use SilverStripe\ORM\HiddenClass;
|
||||
use SilverStripe\ORM\Hierarchy\Hierarchy;
|
||||
use SilverStripe\ORM\ManyManyList;
|
||||
@ -82,25 +84,29 @@ use Subsite;
|
||||
* {@link AbsoluteLink()}. You can allow these segments to contain multibyte characters through
|
||||
* {@link URLSegmentFilter::$default_allow_multibyte}.
|
||||
*
|
||||
* @property string URLSegment
|
||||
* @property string Title
|
||||
* @property string MenuTitle
|
||||
* @property string Content HTML content of the page.
|
||||
* @property string MetaDescription
|
||||
* @property string ExtraMeta
|
||||
* @property string ShowInMenus
|
||||
* @property string ShowInSearch
|
||||
* @property string Sort Integer value denoting the sort order.
|
||||
* @property string ReportClass
|
||||
* @property string $URLSegment
|
||||
* @property string $Title
|
||||
* @property string $MenuTitle
|
||||
* @property string $Content HTML content of the page.
|
||||
* @property string $MetaDescription
|
||||
* @property string $ExtraMeta
|
||||
* @property string $ShowInMenus
|
||||
* @property string $ShowInSearch
|
||||
* @property string $Sort Integer value denoting the sort order.
|
||||
* @property string $ReportClass
|
||||
* @property bool $HasBrokenFile True if this page has a broken file shortcode
|
||||
* @property bool $HasBrokenLink True if this page has a broken page shortcode
|
||||
*
|
||||
* @method ManyManyList ViewerGroups() List of groups that can view this object.
|
||||
* @method ManyManyList EditorGroups() List of groups that can edit this object.
|
||||
* @method SiteTree Parent()
|
||||
* @method HasManyList|SiteTreeLink[] BackLinks() List of SiteTreeLink objects attached to this page
|
||||
*
|
||||
* @mixin Hierarchy
|
||||
* @mixin Versioned
|
||||
* @mixin RecursivePublishable
|
||||
* @mixin SiteTreeLinkTracking
|
||||
* @mixin SiteTreeLinkTracking Added via linktracking.yml to DataObject directly
|
||||
* @mixin FileLinkTracking Added via filetracking.yml in silverstripe/assets
|
||||
* @mixin InheritedPermissionsExtension
|
||||
*/
|
||||
class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvider, CMSPreviewable, Resettable, Flushable, MemberCacheFlusher
|
||||
@ -205,9 +211,10 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
"URLSegment" => true,
|
||||
);
|
||||
|
||||
private static $has_many = array(
|
||||
"VirtualPages" => VirtualPage::class . '.CopyContentFrom'
|
||||
);
|
||||
private static $has_many = [
|
||||
"VirtualPages" => VirtualPage::class . '.CopyContentFrom',
|
||||
'BackLinks' => SiteTreeLink::class . '.Linked',
|
||||
];
|
||||
|
||||
private static $owned_by = array(
|
||||
"VirtualPages"
|
||||
@ -262,7 +269,6 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
private static $extensions = [
|
||||
Hierarchy::class,
|
||||
Versioned::class,
|
||||
SiteTreeLinkTracking::class,
|
||||
InheritedPermissionsExtension::class,
|
||||
];
|
||||
|
||||
@ -384,7 +390,7 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
// Grab the initial root level page to traverse down from.
|
||||
$URLSegment = array_shift($parts);
|
||||
$conditions = array('"SiteTree"."URLSegment"' => rawurlencode($URLSegment));
|
||||
if (self::config()->nested_urls) {
|
||||
if (self::config()->get('nested_urls')) {
|
||||
$conditions[] = array('"SiteTree"."ParentID"' => 0);
|
||||
}
|
||||
/** @var SiteTree $sitetree */
|
||||
@ -392,7 +398,7 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
|
||||
/// Fall back on a unique URLSegment for b/c.
|
||||
if (!$sitetree
|
||||
&& self::config()->nested_urls
|
||||
&& self::config()->get('nested_urls')
|
||||
&& $sitetree = DataObject::get_one(self::class, array(
|
||||
'"SiteTree"."URLSegment"' => $URLSegment
|
||||
), $cache)
|
||||
@ -402,7 +408,7 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
|
||||
// Attempt to grab an alternative page from extensions.
|
||||
if (!$sitetree) {
|
||||
$parentID = self::config()->nested_urls ? 0 : null;
|
||||
$parentID = self::config()->get('nested_urls') ? 0 : null;
|
||||
|
||||
if ($alternatives = static::singleton()->extend('alternateGetByLink', $URLSegment, $parentID)) {
|
||||
foreach ($alternatives as $alternative) {
|
||||
@ -418,7 +424,7 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
}
|
||||
|
||||
// Check if we have any more URL parts to parse.
|
||||
if (!self::config()->nested_urls || !count($parts)) {
|
||||
if (!self::config()->get('nested_urls') || !count($parts)) {
|
||||
return $sitetree;
|
||||
}
|
||||
|
||||
@ -600,7 +606,7 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
*/
|
||||
public function RelativeLink($action = null)
|
||||
{
|
||||
if ($this->ParentID && self::config()->nested_urls) {
|
||||
if ($this->ParentID && self::config()->get('nested_urls')) {
|
||||
$parent = $this->Parent();
|
||||
// If page is removed select parent from version history (for archive page view)
|
||||
if ((!$parent || !$parent->exists()) && !$this->isOnDraft()) {
|
||||
@ -843,6 +849,7 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
* @param boolean $unlinked Whether to link page titles.
|
||||
* @param boolean|string $stopAtPageType ClassName of a page to stop the upwards traversal.
|
||||
* @param boolean $showHidden Include pages marked with the attribute ShowInMenus = 0
|
||||
* @param string $delimiter Delimiter character (raw html)
|
||||
* @return string The breadcrumb trail.
|
||||
*/
|
||||
public function Breadcrumbs($maxDepth = 20, $unlinked = false, $stopAtPageType = false, $showHidden = false, $delimiter = '»')
|
||||
@ -913,8 +920,9 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
if ($parentID = $this->getField("ParentID")) {
|
||||
return DataObject::get_by_id(self::class, $parentID);
|
||||
$parentID = $this->getField("ParentID");
|
||||
if ($parentID) {
|
||||
return SiteTree::get_by_id(self::class, $parentID);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -1377,14 +1385,14 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
));
|
||||
}
|
||||
|
||||
$tags = implode("\n", $tags);
|
||||
$tagString = implode("\n", $tags);
|
||||
if ($this->ExtraMeta) {
|
||||
$tags .= $this->obj('ExtraMeta')->forTemplate();
|
||||
$tagString .= $this->obj('ExtraMeta')->forTemplate();
|
||||
}
|
||||
|
||||
$this->extend('MetaTags', $tags);
|
||||
$this->extend('MetaTags', $tagString);
|
||||
|
||||
return $tags;
|
||||
return $tagString;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1411,12 +1419,13 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
parent::requireDefaultRecords();
|
||||
|
||||
// default pages
|
||||
if (static::class == self::class && $this->config()->create_default_pages) {
|
||||
if (!SiteTree::get_by_link(RootURLController::config()->default_homepage_link)) {
|
||||
if (static::class === self::class && $this->config()->get('create_default_pages')) {
|
||||
$defaultHomepage = RootURLController::config()->get('default_homepage_link');
|
||||
if (!SiteTree::get_by_link($defaultHomepage)) {
|
||||
$homepage = new Page();
|
||||
$homepage->Title = _t(__CLASS__.'.DEFAULTHOMETITLE', 'Home');
|
||||
$homepage->Content = _t(__CLASS__.'.DEFAULTHOMECONTENT', '<p>Welcome to SilverStripe! This is the default homepage. You can edit this page by opening <a href="admin/">the CMS</a>.</p><p>You can now access the <a href="http://docs.silverstripe.org">developer documentation</a>, or begin the <a href="http://www.silverstripe.org/learn/lessons">SilverStripe lessons</a>.</p>');
|
||||
$homepage->URLSegment = RootURLController::config()->default_homepage_link;
|
||||
$homepage->URLSegment = $defaultHomepage;
|
||||
$homepage->Sort = 1;
|
||||
$homepage->write();
|
||||
$homepage->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
|
||||
@ -1491,8 +1500,6 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
$count++;
|
||||
}
|
||||
|
||||
$this->syncLinkTracking();
|
||||
|
||||
// Check to see if we've only altered fields that shouldn't affect versioning
|
||||
$fieldsIgnoredByVersioning = array('HasBrokenLink', 'Status', 'HasBrokenFile', 'ToDo', 'VersionID', 'SaveCount');
|
||||
$changedFields = array_keys($this->getChangedFields(true, 2));
|
||||
@ -1521,8 +1528,8 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
parent::onBeforeDelete();
|
||||
|
||||
// If deleting this page, delete all its children.
|
||||
if ($this->isInDB() && SiteTree::config()->enforce_strict_hierarchy && $children = $this->AllChildren()) {
|
||||
foreach ($children as $child) {
|
||||
if ($this->isInDB() && SiteTree::config()->get('enforce_strict_hierarchy')) {
|
||||
foreach ($this->AllChildren() as $child) {
|
||||
/** @var SiteTree $child */
|
||||
$child->delete();
|
||||
}
|
||||
@ -1615,7 +1622,7 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
public function validURLSegment()
|
||||
{
|
||||
// Check known urlsegment blacklists
|
||||
if (self::config()->nested_urls && $this->ParentID) {
|
||||
if (self::config()->get('nested_urls') && $this->ParentID) {
|
||||
// Guard against url segments for sub-pages
|
||||
$parent = $this->Parent();
|
||||
if ($controller = ModelAsController::controller_for($parent)) {
|
||||
@ -1645,7 +1652,7 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
if ($this->ID) {
|
||||
$source = $source->exclude('ID', $this->ID);
|
||||
}
|
||||
if (self::config()->nested_urls) {
|
||||
if (self::config()->get('nested_urls')) {
|
||||
$source = $source->filter('ParentID', $this->ParentID ? $this->ParentID : 0);
|
||||
}
|
||||
return !$source->exists();
|
||||
@ -1685,9 +1692,10 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
*/
|
||||
public function getStageURLSegment()
|
||||
{
|
||||
$stageRecord = Versioned::get_one_by_stage(self::class, Versioned::DRAFT, array(
|
||||
/** @var SiteTree $stageRecord */
|
||||
$stageRecord = Versioned::get_one_by_stage(self::class, Versioned::DRAFT, [
|
||||
'"SiteTree"."ID"' => $this->ID
|
||||
));
|
||||
]);
|
||||
return ($stageRecord) ? $stageRecord->URLSegment : null;
|
||||
}
|
||||
|
||||
@ -1698,17 +1706,37 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
*/
|
||||
public function getLiveURLSegment()
|
||||
{
|
||||
$liveRecord = Versioned::get_one_by_stage(self::class, Versioned::LIVE, array(
|
||||
/** @var SiteTree $liveRecord */
|
||||
$liveRecord = Versioned::get_one_by_stage(self::class, Versioned::LIVE, [
|
||||
'"SiteTree"."ID"' => $this->ID
|
||||
));
|
||||
]);
|
||||
return ($liveRecord) ? $liveRecord->URLSegment : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the back-link tracking objects that link to this page
|
||||
*
|
||||
* @retun ArrayList|DataObject[]
|
||||
*/
|
||||
public function BackLinkTracking()
|
||||
{
|
||||
// @todo - Implement PolymorphicManyManyList to replace this
|
||||
$list = ArrayList::create();
|
||||
foreach ($this->BackLinks() as $link) {
|
||||
// Ensure parent record exists
|
||||
$item = $link->Parent();
|
||||
if ($item && $item->isInDB()) {
|
||||
$list->push($item);
|
||||
}
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pages that depend on this page. This includes virtual pages, pages that link to it, etc.
|
||||
*
|
||||
* @param bool $includeVirtuals Set to false to exlcude virtual pages.
|
||||
* @return ArrayList
|
||||
* @return ArrayList|SiteTree[]
|
||||
*/
|
||||
public function DependentPages($includeVirtuals = true)
|
||||
{
|
||||
@ -1881,7 +1909,7 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
|
||||
$baseLink = Controller::join_links(
|
||||
Director::absoluteBaseURL(),
|
||||
(self::config()->nested_urls && $this->ParentID ? $this->Parent()->RelativeLink(true) : null)
|
||||
(self::config()->get('nested_urls') && $this->ParentID ? $this->Parent()->RelativeLink(true) : null)
|
||||
);
|
||||
|
||||
$urlsegment = SiteTreeURLSegmentField::create("URLSegment", $this->fieldLabel('URLSegment'))
|
||||
@ -1891,7 +1919,7 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
'New {pagetype}',
|
||||
array('pagetype' => $this->i18n_singular_name())
|
||||
)));
|
||||
$helpText = (self::config()->nested_urls && $this->numChildren())
|
||||
$helpText = (self::config()->get('nested_urls') && $this->numChildren())
|
||||
? $this->fieldLabel('LinkChangeNote')
|
||||
: '';
|
||||
if (!Config::inst()->get('SilverStripe\\View\\Parsers\\URLSegmentFilter', 'default_allow_multibyte')) {
|
||||
@ -2154,14 +2182,14 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
$labels['Comments'] = _t(__CLASS__.'.Comments', 'Comments');
|
||||
$labels['Visibility'] = _t(__CLASS__.'.Visibility', 'Visibility');
|
||||
$labels['LinkChangeNote'] = _t(
|
||||
'SilverStripe\\CMS\\Model\\SiteTree.LINKCHANGENOTE',
|
||||
__CLASS__ . '.LINKCHANGENOTE',
|
||||
'Changing this page\'s link will also affect the links of all child pages.'
|
||||
);
|
||||
|
||||
if ($includerelations) {
|
||||
$labels['Parent'] = _t(__CLASS__.'.has_one_Parent', 'Parent Page', 'The parent page in the site hierarchy');
|
||||
$labels['LinkTracking'] = _t(__CLASS__.'.many_many_LinkTracking', 'Link Tracking');
|
||||
$labels['ImageTracking'] = _t(__CLASS__.'.many_many_ImageTracking', 'Image Tracking');
|
||||
$labels['FileTracking'] = _t(__CLASS__.'.many_many_ImageTracking', 'Image Tracking');
|
||||
$labels['BackLinkTracking'] = _t(__CLASS__.'.many_many_BackLinkTracking', 'Backlink Tracking');
|
||||
}
|
||||
|
||||
@ -2188,7 +2216,7 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
// Get status of page
|
||||
$isOnDraft = $this->isOnDraft();
|
||||
$isPublished = $this->isPublished();
|
||||
$stagesDiffer = $this->stagesDiffer(Versioned::DRAFT, Versioned::LIVE);
|
||||
$stagesDiffer = $this->stagesDiffer();
|
||||
|
||||
// Check permissions
|
||||
$canPublish = $this->canPublish();
|
||||
@ -2232,6 +2260,7 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
}
|
||||
|
||||
// "readonly"/viewing version that isn't the current version of the record
|
||||
/** @var SiteTree $stageRecord */
|
||||
$stageRecord = Versioned::get_by_stage(static::class, Versioned::DRAFT)->byID($this->ID);
|
||||
/** @skipUpgrade */
|
||||
if ($stageRecord && $stageRecord->Version != $this->Version) {
|
||||
@ -3005,14 +3034,20 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
|
||||
*/
|
||||
protected function updateDependentPages()
|
||||
{
|
||||
// Skip live stage
|
||||
if (Versioned::get_stage() === Versioned::LIVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Need to flush cache to avoid outdated versionnumber references
|
||||
$this->flushCache();
|
||||
|
||||
// Need to mark pages depending to this one as broken
|
||||
$dependentPages = $this->DependentPages();
|
||||
if ($dependentPages) {
|
||||
foreach ($dependentPages as $page) {
|
||||
// $page->write() calls syncLinkTracking, which does all the hard work for us.
|
||||
/** @var Page $page */
|
||||
foreach ($this->DependentPages() as $page) {
|
||||
// Update sync link tracking
|
||||
$page->syncLinkTracking();
|
||||
if ($page->isChanged()) {
|
||||
$page->write();
|
||||
}
|
||||
}
|
||||
|
@ -3,43 +3,18 @@
|
||||
namespace SilverStripe\CMS\Model;
|
||||
|
||||
use SilverStripe\Assets\File;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
use SilverStripe\Forms\ReadonlyField;
|
||||
use SilverStripe\ORM\DataExtension;
|
||||
use SilverStripe\ORM\ManyManyList;
|
||||
use SilverStripe\Subsites\Model\Subsite;
|
||||
use SilverStripe\Versioned\Versioned;
|
||||
use SilverStripe\View\SSViewer;
|
||||
|
||||
/**
|
||||
* Extension applied to {@see File} object to track links to {@see SiteTree} records.
|
||||
*
|
||||
* {@see SiteTreeLinkTracking} for the extension applied to {@see SiteTree}
|
||||
*
|
||||
* Note that since both SiteTree and File are versioned, LinkTracking and ImageTracking will
|
||||
* only be enabled for the Stage record.
|
||||
*
|
||||
* @deprecated 4.2..5.0 Link tracking is baked into File class now
|
||||
* @property File $owner
|
||||
*/
|
||||
class SiteTreeFileExtension extends DataExtension
|
||||
{
|
||||
private static $belongs_many_many = array(
|
||||
'BackLinkTracking' => SiteTree::class . '.ImageTracking' // {@see SiteTreeLinkTracking}
|
||||
);
|
||||
|
||||
/**
|
||||
* Images tracked by pages are owned by those pages
|
||||
*
|
||||
* @config
|
||||
* @var array
|
||||
*/
|
||||
private static $owned_by = array(
|
||||
'BackLinkTracking'
|
||||
);
|
||||
|
||||
private static $casting = array(
|
||||
private static $casting = [
|
||||
'BackLinkHTMLList' => 'HTMLFragment'
|
||||
);
|
||||
];
|
||||
|
||||
/**
|
||||
* Generate an HTML list which provides links to where a file is used.
|
||||
@ -49,67 +24,6 @@ class SiteTreeFileExtension extends DataExtension
|
||||
public function BackLinkHTMLList()
|
||||
{
|
||||
$viewer = SSViewer::create(["type" => "Includes", self::class . "_description"]);
|
||||
|
||||
return $viewer->process($this->owner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend through {@link updateBackLinkTracking()} in your own {@link Extension}.
|
||||
*
|
||||
* @return ManyManyList
|
||||
*/
|
||||
public function BackLinkTracking()
|
||||
{
|
||||
// @todo remove coupling with Subsites
|
||||
if (class_exists(Subsite::class)) {
|
||||
$rememberSubsiteFilter = Subsite::$disable_subsite_filter;
|
||||
Subsite::disable_subsite_filter(true);
|
||||
}
|
||||
|
||||
$links = $this->owner->getManyManyComponents('BackLinkTracking');
|
||||
$this->owner->extend('updateBackLinkTracking', $links);
|
||||
|
||||
if (class_exists(Subsite::class)) {
|
||||
Subsite::disable_subsite_filter($rememberSubsiteFilter);
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Unnecessary shortcut for AssetTableField, coupled with cms module.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function BackLinkTrackingCount()
|
||||
{
|
||||
$pages = $this->owner->BackLinkTracking();
|
||||
if ($pages) {
|
||||
return $pages->count();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates link tracking in the current stage.
|
||||
*/
|
||||
public function onAfterDelete()
|
||||
{
|
||||
// Skip live stage
|
||||
if (Versioned::get_stage() === Versioned::LIVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We query the explicit ID list, because BackLinkTracking will get modified after the stage
|
||||
// site does its thing
|
||||
$brokenPageIDs = $this->owner->BackLinkTracking()->column("ID");
|
||||
if ($brokenPageIDs) {
|
||||
// This will syncLinkTracking on the same stage as this file
|
||||
$brokenPages = SiteTree::get()->byIDs($brokenPageIDs);
|
||||
foreach ($brokenPages as $brokenPage) {
|
||||
$brokenPage->write();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,10 +32,8 @@ class SiteTreeFileFormFactoryExtension extends DataExtension
|
||||
$class = UsedOnTable::class;
|
||||
Deprecation::notice('5.0', "Use the $class to show this table");
|
||||
|
||||
/** @var File|SiteTreeFileExtension|RecursivePublishable $record */
|
||||
$record = $context['Record'];
|
||||
|
||||
$usedOnField = UsedOnTable::create('UsedOnTableReplacement');
|
||||
$usedOnField->setRecord($context['Record']);
|
||||
|
||||
// Add field to new tab
|
||||
/** @var Tab $tab */
|
||||
|
@ -4,26 +4,40 @@ namespace SilverStripe\CMS\Model;
|
||||
|
||||
use SilverStripe\Assets\File;
|
||||
use SilverStripe\Assets\Folder;
|
||||
use SilverStripe\Assets\Shortcodes\FileLink;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\ORM\ArrayList;
|
||||
use SilverStripe\ORM\DataExtension;
|
||||
use SilverStripe\ORM\DataList;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\DB;
|
||||
|
||||
/**
|
||||
* @deprecated 4.2..5.0 Will be removed in cms 5.0
|
||||
*/
|
||||
class SiteTreeFolderExtension extends DataExtension
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
Deprecation::notice('5.0', 'Will be removed in 5.0');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Looks for files used in system and create where clause which contains all ID's of files.
|
||||
*
|
||||
* @deprecated 4.2..5.0
|
||||
* @returns string where clause which will work as filter.
|
||||
*/
|
||||
public function getUnusedFilesListFilter()
|
||||
{
|
||||
Deprecation::notice('5.0', 'Will be removed in 5.0');
|
||||
|
||||
// Add all records in link tracking
|
||||
$usedFiles = DB::query("SELECT DISTINCT \"FileID\" FROM \"SiteTree_ImageTracking\"")->column('FileID');
|
||||
$usedFiles = FileLink::get()->column('LinkedID');
|
||||
|
||||
// Get all classes that aren't folder
|
||||
$fileClasses = array_diff_key(
|
||||
|
23
code/Model/SiteTreeLink.php
Normal file
23
code/Model/SiteTreeLink.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\CMS\Model;
|
||||
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
/**
|
||||
* Represents a link between a dataobject parent and a page in a HTML content area
|
||||
*
|
||||
* @method DataObject Parent() Parent object
|
||||
* @method SiteTree Linked() Page being linked to
|
||||
*
|
||||
* Run `MigrateSiteTreeLinkingTask` to migrate from old table to this.
|
||||
*/
|
||||
class SiteTreeLink extends DataObject
|
||||
{
|
||||
private static $table_name = 'SiteTreeLink';
|
||||
|
||||
private static $has_one = [
|
||||
'Parent' => DataObject::class,
|
||||
'Linked' => SiteTree::class,
|
||||
];
|
||||
}
|
@ -3,11 +3,11 @@
|
||||
namespace SilverStripe\CMS\Model;
|
||||
|
||||
use DOMElement;
|
||||
use SilverStripe\Assets\File;
|
||||
use SilverStripe\Assets\Shortcodes\FileLinkTracking;
|
||||
use SilverStripe\ORM\DataExtension;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\FieldType\DBHTMLText;
|
||||
use SilverStripe\ORM\ManyManyList;
|
||||
use SilverStripe\ORM\ManyManyThroughList;
|
||||
use SilverStripe\Versioned\Versioned;
|
||||
use SilverStripe\View\Parsers\HTMLValue;
|
||||
|
||||
@ -18,23 +18,17 @@ use SilverStripe\View\Parsers\HTMLValue;
|
||||
* 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.
|
||||
*
|
||||
* Note that since both SiteTree and File are versioned, LinkTracking and ImageTracking will
|
||||
* Note that since both SiteTree and File are versioned, LinkTracking and FileTracking will
|
||||
* only be enabled for the Stage record.
|
||||
*
|
||||
* {@see SiteTreeFileExtension} for the extension applied to {@see File}
|
||||
* Note: To support `HasBrokenLink` for non-SiteTree classes, add a boolean `HasBrokenLink`
|
||||
* field to your `db` config and this extension will ensure it's flagged appropriately.
|
||||
*
|
||||
* @property SiteTree $owner
|
||||
*
|
||||
* @property bool $HasBrokenFile
|
||||
* @property bool $HasBrokenLink
|
||||
*
|
||||
* @method ManyManyList LinkTracking() List of site pages linked on this page.
|
||||
* @method ManyManyList ImageTracking() List of Images linked on this page.
|
||||
* @method ManyManyList BackLinkTracking List of site pages that link to this page.
|
||||
* @property DataObject|SiteTreeLinkTracking $owner
|
||||
* @method ManyManyThroughList LinkTracking() List of site pages linked on this dataobject
|
||||
*/
|
||||
class SiteTreeLinkTracking extends DataExtension
|
||||
{
|
||||
|
||||
/**
|
||||
* @var SiteTreeLinkTracking_Parser
|
||||
*/
|
||||
@ -50,6 +44,14 @@ class SiteTreeLinkTracking extends DataExtension
|
||||
'Parser' => '%$' . SiteTreeLinkTracking_Parser::class
|
||||
];
|
||||
|
||||
private static $many_many = [
|
||||
"LinkTracking" => [
|
||||
'through' => SiteTreeLink::class,
|
||||
'from' => 'Parent',
|
||||
'to' => 'Linked',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Parser for link tracking
|
||||
*
|
||||
@ -70,71 +72,120 @@ class SiteTreeLinkTracking extends DataExtension
|
||||
return $this;
|
||||
}
|
||||
|
||||
private static $db = array(
|
||||
"HasBrokenFile" => "Boolean",
|
||||
"HasBrokenLink" => "Boolean"
|
||||
);
|
||||
|
||||
private static $many_many = array(
|
||||
"LinkTracking" => SiteTree::class,
|
||||
"ImageTracking" => File::class, // {@see SiteTreeFileExtension}
|
||||
);
|
||||
|
||||
private static $belongs_many_many = array(
|
||||
"BackLinkTracking" => SiteTree::class . '.LinkTracking',
|
||||
);
|
||||
public function onBeforeWrite()
|
||||
{
|
||||
// Trigger link tracking (unless this would also be triggered by FileLinkTracking)
|
||||
if (!$this->owner->hasExtension(FileLinkTracking::class)) {
|
||||
$this->owner->syncLinkTracking();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracked images are considered owned by this page
|
||||
* Public method to call when triggering symlink extension. Can be called externally,
|
||||
* or overridden by class implementations.
|
||||
*
|
||||
* @config
|
||||
* @var array
|
||||
* {@see SiteTreeLinkTracking::augmentSyncLinkTracking}
|
||||
*/
|
||||
private static $owns = array(
|
||||
"ImageTracking"
|
||||
);
|
||||
public function syncLinkTracking()
|
||||
{
|
||||
$this->owner->extend('augmentSyncLinkTracking');
|
||||
}
|
||||
|
||||
private static $many_many_extraFields = array(
|
||||
"LinkTracking" => array("FieldName" => "Varchar"),
|
||||
"ImageTracking" => array("FieldName" => "Varchar")
|
||||
);
|
||||
/**
|
||||
* Find HTMLText fields on {@link owner} to scrape for links that need tracking
|
||||
*/
|
||||
public function augmentSyncLinkTracking()
|
||||
{
|
||||
// If owner is versioned, skip tracking on live
|
||||
if (Versioned::get_stage() == Versioned::LIVE && $this->owner->hasExtension(Versioned::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build a list of HTMLText fields, merging all linked pages together.
|
||||
$allFields = DataObject::getSchema()->fieldSpecs($this->owner);
|
||||
$linkedPages = [];
|
||||
$anyBroken = false;
|
||||
foreach ($allFields as $field => $fieldSpec) {
|
||||
$fieldObj = $this->owner->dbObject($field);
|
||||
if ($fieldObj instanceof DBHTMLText) {
|
||||
// Merge links in this field with global list.
|
||||
$linksInField = $this->trackLinksInField($field, $anyBroken);
|
||||
$linkedPages = array_merge($linkedPages, $linksInField);
|
||||
}
|
||||
}
|
||||
|
||||
// Soft support for HasBrokenLink db field (e.g. SiteTree)
|
||||
if ($this->owner->hasField('HasBrokenLink')) {
|
||||
$this->owner->HasBrokenLink = $anyBroken;
|
||||
}
|
||||
|
||||
// Update the "LinkTracking" many_many.
|
||||
$this->owner->LinkTracking()->setByIDList($linkedPages);
|
||||
}
|
||||
|
||||
public function onAfterDelete()
|
||||
{
|
||||
// If owner is versioned, skip tracking on live
|
||||
if (Versioned::get_stage() == Versioned::LIVE && $this->owner->hasExtension(Versioned::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->owner->LinkTracking()->removeAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrape the content of a field to detect anly links to local SiteTree pages or files
|
||||
*
|
||||
* @param string $fieldName The name of the field on {@link @owner} to scrape
|
||||
* @param bool &$anyBroken Will be flagged to true (by reference) if a link is broken.
|
||||
* @return int[] Array of page IDs found (associative array)
|
||||
*/
|
||||
public function trackLinksInField($fieldName)
|
||||
public function trackLinksInField($fieldName, &$anyBroken = false)
|
||||
{
|
||||
$record = $this->owner;
|
||||
// Pull down current field content
|
||||
$htmlValue = HTMLValue::create($this->owner->$fieldName);
|
||||
|
||||
// Process all links
|
||||
$linkedPages = [];
|
||||
$linkedFiles = [];
|
||||
|
||||
$htmlValue = HTMLValue::create($record->$fieldName);
|
||||
$links = $this->parser->process($htmlValue);
|
||||
|
||||
// Highlight broken links in the content.
|
||||
foreach ($links as $link) {
|
||||
// Skip links without domelements
|
||||
if (!isset($link['DOMReference'])) {
|
||||
continue;
|
||||
// Toggle highlight class to element
|
||||
$this->toggleElementClass($link['DOMReference'], 'ss-broken', $link['Broken']);
|
||||
|
||||
// Flag broken
|
||||
if ($link['Broken']) {
|
||||
$anyBroken = true;
|
||||
}
|
||||
|
||||
/** @var DOMElement $domReference */
|
||||
$domReference = $link['DOMReference'];
|
||||
$classStr = trim($domReference->getAttribute('class'));
|
||||
if (!$classStr) {
|
||||
$classes = [];
|
||||
} else {
|
||||
$classes = explode(' ', $classStr);
|
||||
// Collect page ids
|
||||
if ($link['Type'] === 'sitetree' && $link['Target']) {
|
||||
$pageID = (int)$link['Target'];
|
||||
$linkedPages[$pageID] = $pageID;
|
||||
}
|
||||
}
|
||||
|
||||
// Update any changed content
|
||||
$this->owner->$fieldName = $htmlValue->getContent();
|
||||
return $linkedPages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given css class to the DOM element.
|
||||
*
|
||||
* @param DOMElement $domReference Element to modify.
|
||||
* @param string $class Class name to toggle.
|
||||
* @param bool $toggle On or off.
|
||||
*/
|
||||
protected function toggleElementClass(DOMElement $domReference, $class, $toggle)
|
||||
{
|
||||
// Get all existing classes.
|
||||
$classes = array_filter(explode(' ', trim($domReference->getAttribute('class'))));
|
||||
|
||||
// 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')));
|
||||
if ($toggle) {
|
||||
$classes = array_unique(array_merge($classes, [$class]));
|
||||
} else {
|
||||
$classes = array_diff($classes, array('ss-broken'));
|
||||
$classes = array_diff($classes, [$class]);
|
||||
}
|
||||
|
||||
if (!empty($classes)) {
|
||||
@ -143,99 +194,4 @@ class SiteTreeLinkTracking extends DataExtension
|
||||
$domReference->removeAttribute('class');
|
||||
}
|
||||
}
|
||||
$record->$fieldName = $htmlValue->getContent();
|
||||
|
||||
// Populate link tracking for internal links & links to asset files.
|
||||
foreach ($links as $link) {
|
||||
switch ($link['Type']) {
|
||||
case 'sitetree':
|
||||
if ($link['Broken']) {
|
||||
$record->HasBrokenLink = true;
|
||||
} else {
|
||||
$linkedPages[] = $link['Target'];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'file':
|
||||
case 'image':
|
||||
if ($link['Broken']) {
|
||||
$record->HasBrokenFile = true;
|
||||
} else {
|
||||
$linkedFiles[] = $link['Target'];
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if ($link['Broken']) {
|
||||
$record->HasBrokenLink = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the "LinkTracking" many_many
|
||||
if ($record->getSchema()->manyManyComponent(get_class($record), 'LinkTracking')
|
||||
&& ($tracker = $record->LinkTracking())
|
||||
) {
|
||||
// If already saved, clear existing records
|
||||
if ($record->isInDB()) {
|
||||
$tracker->removeByFilter(array(
|
||||
sprintf('"FieldName" = ? AND "%s" = ?', $tracker->getForeignKey())
|
||||
=> array($fieldName, $record->ID)
|
||||
));
|
||||
}
|
||||
|
||||
foreach ($linkedPages as $item) {
|
||||
$tracker->add($item, array('FieldName' => $fieldName));
|
||||
}
|
||||
}
|
||||
|
||||
// Update the "ImageTracking" many_many
|
||||
if ($record->getSchema()->manyManyComponent(get_class($record), 'ImageTracking')
|
||||
&& ($tracker = $record->ImageTracking())
|
||||
) {
|
||||
// If already saved, clear existing records
|
||||
if ($record->isInDB()) {
|
||||
$tracker->removeByFilter(array(
|
||||
sprintf('"FieldName" = ? AND "%s" = ?', $tracker->getForeignKey())
|
||||
=> array($fieldName, $record->ID)
|
||||
));
|
||||
}
|
||||
|
||||
foreach ($linkedFiles as $item) {
|
||||
$tracker->add($item, array('FieldName' => $fieldName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find HTMLText fields on {@link owner} to scrape for links that need tracking
|
||||
*
|
||||
* @todo Support versioned many_many for per-stage page link tracking
|
||||
*/
|
||||
public function augmentSyncLinkTracking()
|
||||
{
|
||||
// Skip live tracking
|
||||
if (Versioned::get_stage() === Versioned::LIVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset boolean broken flags
|
||||
$this->owner->HasBrokenLink = false;
|
||||
$this->owner->HasBrokenFile = false;
|
||||
|
||||
// Build a list of HTMLText fields
|
||||
$allFields = DataObject::getSchema()->fieldSpecs($this->owner);
|
||||
$htmlFields = array();
|
||||
foreach ($allFields as $field => $fieldSpec) {
|
||||
$fieldObj = $this->owner->dbObject($field);
|
||||
if ($fieldObj instanceof DBHTMLText) {
|
||||
$htmlFields[] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($htmlFields as $field) {
|
||||
$this->trackLinksInField($field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,6 @@ class SiteTreeLinkTracking_Parser
|
||||
{
|
||||
$results = array();
|
||||
|
||||
// @todo - Should be calling getElementsByTagName on DOMDocument?
|
||||
$links = $htmlValue->getElementsByTagName('a');
|
||||
if (!$links) {
|
||||
return $results;
|
||||
@ -61,21 +60,18 @@ class SiteTreeLinkTracking_Parser
|
||||
// Link to a page on this site.
|
||||
$matches = array();
|
||||
if (preg_match('/\[sitetree_link(?:\s*|%20|,)?id=(?<id>[0-9]+)\](#(?<anchor>.*))?/i', $href, $matches)) {
|
||||
// Check if page link is broken
|
||||
/** @var SiteTree $page */
|
||||
$page = DataObject::get_by_id(SiteTree::class, $matches['id']);
|
||||
$broken = false;
|
||||
|
||||
if (!$page) {
|
||||
// Page doesn't exist.
|
||||
$broken = true;
|
||||
} else {
|
||||
if (!empty($matches['anchor'])) {
|
||||
} elseif (!empty($matches['anchor'])) {
|
||||
// Ensure anchor isn't broken on target page
|
||||
$anchor = preg_quote($matches['anchor'], '/');
|
||||
|
||||
if (!preg_match("/(name|id)=\"{$anchor}\"/", $page->Content)) {
|
||||
// Broken anchor on the target page.
|
||||
$broken = true;
|
||||
}
|
||||
}
|
||||
$broken = !preg_match("/(name|id)=\"{$anchor}\"/", $page->Content);
|
||||
} else {
|
||||
$broken = false;
|
||||
}
|
||||
|
||||
$results[] = array(
|
||||
@ -89,22 +85,7 @@ class SiteTreeLinkTracking_Parser
|
||||
continue;
|
||||
}
|
||||
|
||||
// Link to a file on this site.
|
||||
$matches = array();
|
||||
if (preg_match('/\[file_link(?:\s*|%20|,)?id=(?<id>[0-9]+)/i', $href, $matches)) {
|
||||
$results[] = array(
|
||||
'Type' => 'file',
|
||||
'Target' => $matches['id'],
|
||||
'Anchor' => null,
|
||||
'DOMReference' => $link,
|
||||
'Broken' => !DataObject::get_by_id('SilverStripe\\Assets\\File', $matches['id'])
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Local anchor.
|
||||
$matches = array();
|
||||
if (preg_match('/^#(.*)/i', $href, $matches)) {
|
||||
$anchor = preg_quote($matches[1], '#');
|
||||
$results[] = array(
|
||||
@ -118,20 +99,6 @@ class SiteTreeLinkTracking_Parser
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Find all [image ] shortcodes (will be inline, not inside attributes)
|
||||
$content = $htmlValue->getContent();
|
||||
if (preg_match_all('/\[image([^\]]+)\bid=(["])?(?<id>\d+)\D/i', $content, $matches)) {
|
||||
foreach ($matches['id'] as $id) {
|
||||
$results[] = array(
|
||||
'Type' => 'image',
|
||||
'Target' => (int)$id,
|
||||
'Anchor' => null,
|
||||
'DOMReference' => null,
|
||||
'Broken' => !DataObject::get_by_id('SilverStripe\\Assets\\Image', (int)$id)
|
||||
);
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace SilverStripe\CMS\Model;
|
||||
|
||||
use Page;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
use SilverStripe\Forms\LiteralField;
|
||||
use SilverStripe\Forms\ReadonlyTransformation;
|
||||
@ -12,6 +13,7 @@ use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\ValidationResult;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Versioned\Versioned;
|
||||
use SilverStripe\View\HTML;
|
||||
|
||||
/**
|
||||
* Virtual Page creates an instance of a page, with the same fields that the original page had, but readonly.
|
||||
@ -104,9 +106,10 @@ class VirtualPage extends Page
|
||||
*/
|
||||
public function getNonVirtualisedFields()
|
||||
{
|
||||
$config = self::config();
|
||||
return array_merge(
|
||||
self::config()->non_virtual_fields,
|
||||
self::config()->initially_copied_fields
|
||||
$config->get('non_virtual_fields'),
|
||||
$config->get('initially_copied_fields')
|
||||
);
|
||||
}
|
||||
|
||||
@ -140,8 +143,11 @@ class VirtualPage extends Page
|
||||
$tags = parent::MetaTags($includeTitle);
|
||||
$copied = $this->CopyContentFrom();
|
||||
if ($copied && $copied->exists()) {
|
||||
$link = Convert::raw2att($copied->Link());
|
||||
$tags .= "<link rel=\"canonical\" href=\"{$link}\" />\n";
|
||||
$tags .= HTML::createTag('link', [
|
||||
'rel' => 'canonical',
|
||||
'href' => $copied->Link()
|
||||
]);
|
||||
$tags .= "\n";
|
||||
}
|
||||
return $tags;
|
||||
}
|
||||
@ -212,8 +218,8 @@ class VirtualPage extends Page
|
||||
// Setup the linking to the original page.
|
||||
$copyContentFromField = TreeDropdownField::create(
|
||||
'CopyContentFromID',
|
||||
_t('SilverStripe\\CMS\\Model\\VirtualPage.CHOOSE', "Linked Page"),
|
||||
"SilverStripe\\CMS\\Model\\SiteTree"
|
||||
_t(self::class . '.CHOOSE', "Linked Page"),
|
||||
SiteTree::class
|
||||
);
|
||||
|
||||
// Setup virtual fields
|
||||
@ -235,20 +241,24 @@ class VirtualPage extends Page
|
||||
|
||||
// Create links back to the original object in the CMS
|
||||
if ($this->CopyContentFrom()->exists()) {
|
||||
$link = "<a class=\"cmsEditlink\" href=\"admin/pages/edit/show/$this->CopyContentFromID\">" . _t(
|
||||
'SilverStripe\\CMS\\Model\\VirtualPage.EditLink',
|
||||
'edit'
|
||||
) . "</a>";
|
||||
$link = HTML::createTag(
|
||||
'a',
|
||||
[
|
||||
'class' => 'cmsEditlink',
|
||||
'href' => 'admin/pages/edit/show/' . $this->CopyContentFromID,
|
||||
],
|
||||
_t(self::class . '.EditLink', 'edit')
|
||||
);
|
||||
$msgs[] = _t(
|
||||
'SilverStripe\\CMS\\Model\\VirtualPage.HEADERWITHLINK',
|
||||
self::class . '.HEADERWITHLINK',
|
||||
"This is a virtual page copying content from \"{title}\" ({link})",
|
||||
array(
|
||||
[
|
||||
'title' => $this->CopyContentFrom()->obj('Title'),
|
||||
'link' => $link,
|
||||
)
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$msgs[] = _t('SilverStripe\\CMS\\Model\\VirtualPage.HEADER', "This is a virtual page");
|
||||
$msgs[] = _t(self::class . '.HEADER', "This is a virtual page");
|
||||
$msgs[] = _t(
|
||||
'SilverStripe\\CMS\\Model\\SiteTree.VIRTUALPAGEWARNING',
|
||||
'Please choose a linked page and save first in order to publish this page'
|
||||
@ -258,8 +268,7 @@ class VirtualPage extends Page
|
||||
SiteTree::class,
|
||||
Versioned::LIVE,
|
||||
$this->CopyContentFromID
|
||||
)
|
||||
) {
|
||||
)) {
|
||||
$msgs[] = _t(
|
||||
'SilverStripe\\CMS\\Model\\SiteTree.VIRTUALPAGEDRAFTWARNING',
|
||||
'Please publish the linked page in order to publish the virtual page'
|
||||
@ -295,7 +304,7 @@ class VirtualPage extends Page
|
||||
// We also want to copy certain, but only if we're copying the source page for the first
|
||||
// time. After this point, the user is free to customise these for the virtual page themselves.
|
||||
if ($this->isChanged('CopyContentFromID', 2) && $this->CopyContentFromID) {
|
||||
foreach (self::config()->initially_copied_fields as $fieldName) {
|
||||
foreach (self::config()->get('initially_copied_fields') as $fieldName) {
|
||||
$this->$fieldName = $source->$fieldName;
|
||||
}
|
||||
}
|
||||
@ -337,7 +346,7 @@ class VirtualPage extends Page
|
||||
if ($orig && $orig->exists() && !$orig->config()->get('can_be_root') && !$this->ParentID) {
|
||||
$result->addError(
|
||||
_t(
|
||||
'SilverStripe\\CMS\\Model\\VirtualPage.PageTypNotAllowedOnRoot',
|
||||
self::class . '.PageTypNotAllowedOnRoot',
|
||||
'Original page type "{type}" is not allowed on the root level for this virtual page',
|
||||
array('type' => $orig->i18n_singular_name())
|
||||
),
|
||||
@ -349,10 +358,15 @@ class VirtualPage extends Page
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 4.2..5.0
|
||||
*/
|
||||
public function updateImageTracking()
|
||||
{
|
||||
Deprecation::notice('5.0', 'This will be removed in 5.0');
|
||||
|
||||
// Doesn't work on unsaved records
|
||||
if (!$this->ID) {
|
||||
if (!$this->isInDB()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -360,7 +374,12 @@ class VirtualPage extends Page
|
||||
unset($this->components['CopyContentFrom']);
|
||||
|
||||
// Update ImageTracking
|
||||
$this->ImageTracking()->setByIDList($this->CopyContentFrom()->ImageTracking()->column('ID'));
|
||||
$copyContentFrom = $this->CopyContentFrom();
|
||||
if (!$copyContentFrom || !$copyContentFrom->isInDB()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->FileTracking()->setByIDList($copyContentFrom->FileTracking()->column('ID'));
|
||||
}
|
||||
|
||||
public function CMSTreeClasses()
|
||||
|
@ -4,58 +4,52 @@ namespace SilverStripe\CMS\Tasks;
|
||||
|
||||
use SilverStripe\CMS\Model\SiteTree;
|
||||
use SilverStripe\Dev\BuildTask;
|
||||
use SilverStripe\ORM\DataList;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\Dev\Debug;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\Versioned\Versioned;
|
||||
|
||||
/**
|
||||
* Rewrites plain internal HTML links into shortcode form, using existing link tracking information.
|
||||
* Updates legacy SiteTree link tracking into new polymorphic many_many relation.
|
||||
* This should be done for any site upgrading to 4.2.0
|
||||
*/
|
||||
class MigrateSiteTreeLinkingTask extends BuildTask
|
||||
{
|
||||
|
||||
private static $segment = 'MigrateSiteTreeLinkingTask';
|
||||
|
||||
protected $title = 'Migrate SiteTree Linking Task';
|
||||
|
||||
protected $description = 'Rewrites plain internal HTML links into shortcode form, using existing link tracking information.';
|
||||
protected $description = 'Updates legacy SiteTree link tracking into new polymorphic many_many relation';
|
||||
|
||||
public function run($request)
|
||||
{
|
||||
// Ensure legacy table exists
|
||||
$exists = DB::get_conn()->getSchemaManager()->hasTable('SiteTree_LinkTracking');
|
||||
if (!$exists) {
|
||||
DB::alteration_message("Table SiteTree_LinkTracking has already been migrated, or doesn't exist");
|
||||
return;
|
||||
}
|
||||
|
||||
$pages = 0;
|
||||
$links = 0;
|
||||
|
||||
$linkedPages = new DataList(SiteTree::class);
|
||||
$linkedPages = $linkedPages->innerJoin('SiteTree_LinkTracking', '"SiteTree_LinkTracking"."SiteTreeID" = "SiteTree"."ID"');
|
||||
if ($linkedPages) {
|
||||
foreach ($linkedPages as $page) {
|
||||
$tracking = DB::prepared_query(
|
||||
'SELECT "ChildID", "FieldName" FROM "SiteTree_LinkTracking" WHERE "SiteTreeID" = ?',
|
||||
array($page->ID)
|
||||
)->map();
|
||||
// Ensure sync occurs on draft
|
||||
Versioned::withVersionedMode(function () use (&$pages) {
|
||||
Versioned::set_stage(Versioned::DRAFT);
|
||||
|
||||
foreach ($tracking as $childID => $fieldName) {
|
||||
$linked = DataObject::get_by_id(SiteTree::class, $childID);
|
||||
|
||||
// TOOD: Replace in all HTMLText fields
|
||||
$page->Content = preg_replace(
|
||||
"/href *= *([\"']?){$linked->URLSegment}\/?/i",
|
||||
"href=$1[sitetree_link,id={$linked->ID}]",
|
||||
$page->Content,
|
||||
-1,
|
||||
$replaced
|
||||
/** @var SiteTree[] $linkedPages */
|
||||
$linkedPages = SiteTree::get()
|
||||
->innerJoin(
|
||||
'SiteTree_LinkTracking',
|
||||
'"SiteTree_LinkTracking"."SiteTreeID" = "SiteTree"."ID"'
|
||||
);
|
||||
|
||||
if ($replaced) {
|
||||
$links += $replaced;
|
||||
}
|
||||
}
|
||||
|
||||
$page->write();
|
||||
foreach ($linkedPages as $page) {
|
||||
// Command page to update symlink tracking
|
||||
$page->syncLinkTracking();
|
||||
$pages++;
|
||||
}
|
||||
}
|
||||
});
|
||||
DB::alteration_message("Migrated page links on " . SiteTree::singleton()->i18n_pluralise($pages));
|
||||
|
||||
echo "Rewrote $links link(s) on $pages page(s) to use shortcodes.\n";
|
||||
// Disable table to prevent double-migration
|
||||
DB::dont_require_table('SiteTree_LinkTracking');
|
||||
}
|
||||
}
|
||||
|
@ -138,13 +138,13 @@ class ContentControllerTest extends FunctionalTest
|
||||
$linkedPage = new SiteTree();
|
||||
$linkedPage->URLSegment = 'linked-page';
|
||||
$linkedPage->write();
|
||||
$linkedPage->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
|
||||
$linkedPage->publishSingle();
|
||||
|
||||
$page = new SiteTree();
|
||||
$page->URLSegment = 'linking-page';
|
||||
$page->Content = sprintf('<a href="[sitetree_link,id=%s]">Testlink</a>', $linkedPage->ID);
|
||||
$page->write();
|
||||
$page->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
|
||||
$page->publishSingle();
|
||||
|
||||
$link = $page->RelativeLink();
|
||||
$response = $this->get($link);
|
||||
|
@ -1,216 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\CMS\Tests\Model;
|
||||
|
||||
use SilverStripe\Assets\Folder;
|
||||
use SilverStripe\Assets\Image;
|
||||
use SilverStripe\Versioned\Versioned;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\CMS\Model\VirtualPage;
|
||||
use SilverStripe\Assets\File;
|
||||
use SilverStripe\Assets\Filesystem;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use Silverstripe\Assets\Dev\TestAssetStore;
|
||||
use Page;
|
||||
|
||||
class FileLinkTrackingTest extends SapphireTest
|
||||
{
|
||||
protected static $fixture_file = "FileLinkTrackingTest.yml";
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Versioned::set_stage(Versioned::DRAFT);
|
||||
|
||||
TestAssetStore::activate('FileLinkTrackingTest');
|
||||
$this->logInWithPermission('ADMIN');
|
||||
|
||||
// Write file contents
|
||||
$files = File::get()->exclude('ClassName', Folder::class);
|
||||
foreach ($files as $file) {
|
||||
$destPath = TestAssetStore::getLocalPath($file);
|
||||
Filesystem::makeFolder(dirname($destPath));
|
||||
file_put_contents($destPath, str_repeat('x', 1000000));
|
||||
// Ensure files are published, thus have public urls
|
||||
$file->publishRecursive();
|
||||
}
|
||||
|
||||
// Since we can't hard-code IDs, manually inject image tracking shortcode
|
||||
$imageID = $this->idFromFixture(Image::class, 'file1');
|
||||
$page = $this->objFromFixture('Page', 'page1');
|
||||
$page->Content = sprintf(
|
||||
'<p>[image src="/assets/FileLinkTrackingTest/55b443b601/testscript-test-file.jpg" id="%d"]</p>',
|
||||
$imageID
|
||||
);
|
||||
$page->write();
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
TestAssetStore::reset();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test uses global state through Versioned::set_reading_mode() since
|
||||
* the shortcode parser doesn't pass along the underlying DataObject
|
||||
* context, hence we can't call getSourceQueryParams().
|
||||
*/
|
||||
public function testFileRenameUpdatesDraftAndPublishedPages()
|
||||
{
|
||||
$page = $this->objFromFixture('Page', 'page1');
|
||||
$page->publishRecursive();
|
||||
|
||||
// Live and stage pages both have link to public file
|
||||
Versioned::set_stage(Versioned::DRAFT);
|
||||
$this->assertContains(
|
||||
'<img src="/assets/FileLinkTrackingTest/55b443b601/testscript-test-file.jpg"',
|
||||
Page::get()->byID($page->ID)->dbObject('Content')->forTemplate()
|
||||
);
|
||||
Versioned::set_stage(Versioned::LIVE);
|
||||
$this->assertContains(
|
||||
'<img src="/assets/FileLinkTrackingTest/55b443b601/testscript-test-file.jpg"',
|
||||
Page::get()->byID($page->ID)->dbObject('Content')->forTemplate()
|
||||
);
|
||||
|
||||
Versioned::set_stage(Versioned::DRAFT);
|
||||
$file = $this->objFromFixture(Image::class, 'file1');
|
||||
$file->Name = 'renamed-test-file.jpg';
|
||||
$file->write();
|
||||
|
||||
// Staged record now points to secure URL of renamed file, live record remains unchanged
|
||||
// Note that the "secure" url doesn't have the "FileLinkTrackingTest" component because
|
||||
// the mocked test location disappears for secure files.
|
||||
Versioned::set_stage(Versioned::DRAFT);
|
||||
$this->assertContains(
|
||||
'<img src="/assets/55b443b601/renamed-test-file.jpg"',
|
||||
Page::get()->byID($page->ID)->dbObject('Content')->forTemplate()
|
||||
);
|
||||
Versioned::set_stage(Versioned::LIVE);
|
||||
$this->assertContains(
|
||||
'<img src="/assets/FileLinkTrackingTest/55b443b601/testscript-test-file.jpg"',
|
||||
Page::get()->byID($page->ID)->dbObject('Content')->forTemplate()
|
||||
);
|
||||
|
||||
// Publishing the file should result in a direct public link (indicated by "FileLinkTrackingTest")
|
||||
// Although the old live page will still point to the old record.
|
||||
// @todo - Ensure shortcodes are used with all images to prevent live records having broken links
|
||||
$file->publishRecursive();
|
||||
Versioned::set_stage(Versioned::DRAFT);
|
||||
$this->assertContains(
|
||||
'<img src="/assets/FileLinkTrackingTest/55b443b601/renamed-test-file.jpg"',
|
||||
Page::get()->byID($page->ID)->dbObject('Content')->forTemplate()
|
||||
);
|
||||
Versioned::set_stage(Versioned::LIVE);
|
||||
$this->assertContains(
|
||||
'<img src="/assets/FileLinkTrackingTest/55b443b601/renamed-test-file.jpg"',
|
||||
Page::get()->byID($page->ID)->dbObject('Content')->forTemplate()
|
||||
);
|
||||
|
||||
// Publishing the page after publishing the asset should retain linking
|
||||
$page->publishRecursive();
|
||||
Versioned::set_stage(Versioned::DRAFT);
|
||||
$this->assertContains(
|
||||
'<img src="/assets/FileLinkTrackingTest/55b443b601/renamed-test-file.jpg"',
|
||||
Page::get()->byID($page->ID)->dbObject('Content')->forTemplate()
|
||||
);
|
||||
Versioned::set_stage(Versioned::LIVE);
|
||||
$this->assertContains(
|
||||
'<img src="/assets/FileLinkTrackingTest/55b443b601/renamed-test-file.jpg"',
|
||||
Page::get()->byID($page->ID)->dbObject('Content')->forTemplate()
|
||||
);
|
||||
}
|
||||
|
||||
public function testFileLinkRewritingOnVirtualPages()
|
||||
{
|
||||
// Publish the source page
|
||||
$page = $this->objFromFixture('Page', 'page1');
|
||||
$this->assertTrue($page->publishRecursive());
|
||||
|
||||
// Create a virtual page from it, and publish that
|
||||
$svp = new VirtualPage();
|
||||
$svp->CopyContentFromID = $page->ID;
|
||||
$svp->write();
|
||||
$svp->publishRecursive();
|
||||
|
||||
// Rename the file
|
||||
$file = $this->objFromFixture(Image::class, 'file1');
|
||||
$file->Name = 'renamed-test-file.jpg';
|
||||
$file->write();
|
||||
|
||||
// Verify that the draft virtual pages have the correct content
|
||||
Versioned::set_stage(Versioned::DRAFT);
|
||||
$this->assertContains(
|
||||
'<img src="/assets/55b443b601/renamed-test-file.jpg"',
|
||||
Page::get()->byID($page->ID)->dbObject('Content')->forTemplate()
|
||||
);
|
||||
|
||||
// Publishing both file and page will update the live record
|
||||
$file->publishRecursive();
|
||||
$page->publishRecursive();
|
||||
|
||||
Versioned::set_stage(Versioned::LIVE);
|
||||
$this->assertContains(
|
||||
'<img src="/assets/FileLinkTrackingTest/55b443b601/renamed-test-file.jpg"',
|
||||
Page::get()->byID($page->ID)->dbObject('Content')->forTemplate()
|
||||
);
|
||||
}
|
||||
|
||||
public function testLinkRewritingOnAPublishedPageDoesntMakeItEditedOnDraft()
|
||||
{
|
||||
// Publish the source page
|
||||
/** @var Page $page */
|
||||
$page = $this->objFromFixture('Page', 'page1');
|
||||
$this->assertTrue($page->publishRecursive());
|
||||
$this->assertFalse($page->isModifiedOnDraft());
|
||||
|
||||
// Rename the file
|
||||
$file = $this->objFromFixture(Image::class, 'file1');
|
||||
$file->Name = 'renamed-test-file.jpg';
|
||||
$file->write();
|
||||
|
||||
// Confirm that the page hasn't gone green.
|
||||
$this->assertFalse($page->isModifiedOnDraft());
|
||||
}
|
||||
|
||||
public function testTwoFileRenamesInARowWork()
|
||||
{
|
||||
$page = $this->objFromFixture('Page', 'page1');
|
||||
$this->assertTrue($page->publishRecursive());
|
||||
|
||||
Versioned::set_stage(Versioned::LIVE);
|
||||
$this->assertContains(
|
||||
'<img src="/assets/FileLinkTrackingTest/55b443b601/testscript-test-file.jpg"',
|
||||
Page::get()->byID($page->ID)->dbObject('Content')->forTemplate()
|
||||
);
|
||||
|
||||
// Rename the file twice
|
||||
Versioned::set_stage(Versioned::DRAFT);
|
||||
$file = $this->objFromFixture(Image::class, 'file1');
|
||||
$file->Name = 'renamed-test-file.jpg';
|
||||
$file->write();
|
||||
|
||||
// TODO Workaround for bug in DataObject->getChangedFields(), which returns stale data,
|
||||
// and influences File->updateFilesystem()
|
||||
$file = DataObject::get_by_id('SilverStripe\\Assets\\File', $file->ID);
|
||||
$file->Name = 'renamed-test-file-second-time.jpg';
|
||||
$file->write();
|
||||
$file->publishRecursive();
|
||||
|
||||
// Confirm that the correct image is shown in both the draft and live site
|
||||
Versioned::set_stage(Versioned::DRAFT);
|
||||
$this->assertContains(
|
||||
'<img src="/assets/FileLinkTrackingTest/55b443b601/renamed-test-file-second-time.jpg"',
|
||||
Page::get()->byID($page->ID)->dbObject('Content')->forTemplate()
|
||||
);
|
||||
|
||||
// Publishing this record also updates live record
|
||||
$page->publishRecursive();
|
||||
Versioned::set_stage(Versioned::LIVE);
|
||||
$this->assertContains(
|
||||
'<img src="/assets/FileLinkTrackingTest/55b443b601/renamed-test-file-second-time.jpg"',
|
||||
Page::get()->byID($page->ID)->dbObject('Content')->forTemplate()
|
||||
);
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
# These need to come first so that SiteTree has the link meta-data written.
|
||||
SilverStripe\Assets\Image:
|
||||
file1:
|
||||
FileFilename: testscript-test-file.jpg
|
||||
FileHash: 55b443b60176235ef09801153cca4e6da7494a0c
|
||||
Name: testscript-test-file.jpg
|
||||
|
||||
Page:
|
||||
page1:
|
||||
Title: page1
|
||||
URLSegment: page1
|
||||
# Content is set via test setup
|
@ -2,16 +2,17 @@
|
||||
|
||||
namespace SilverStripe\CMS\Tests\Model;
|
||||
|
||||
use Page;
|
||||
use Silverstripe\Assets\Dev\TestAssetStore;
|
||||
use SilverStripe\CMS\Model\RedirectorPage;
|
||||
use SilverStripe\CMS\Model\SiteTree;
|
||||
use SilverStripe\Versioned\Versioned;
|
||||
use SilverStripe\CMS\Model\SiteTreeLink;
|
||||
use SilverStripe\CMS\Model\VirtualPage;
|
||||
use SilverStripe\CMS\Tests\Model\SiteTreeBrokenLinksTest\NotPageObject;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\CMS\Model\VirtualPage;
|
||||
use SilverStripe\CMS\Model\RedirectorPage;
|
||||
use SilverStripe\Assets\File;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use Silverstripe\Assets\Dev\TestAssetStore;
|
||||
use Page;
|
||||
use SilverStripe\Versioned\Versioned;
|
||||
|
||||
/**
|
||||
* Tests {@see SiteTreeLinkTracking} broken links feature: LinkTracking
|
||||
@ -20,6 +21,10 @@ class SiteTreeBrokenLinksTest extends SapphireTest
|
||||
{
|
||||
protected static $fixture_file = 'SiteTreeBrokenLinksTest.yml';
|
||||
|
||||
protected static $extra_dataobjects = [
|
||||
NotPageObject::class,
|
||||
];
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
@ -44,11 +49,57 @@ class SiteTreeBrokenLinksTest extends SapphireTest
|
||||
$obj->syncLinkTracking();
|
||||
$this->assertTrue($obj->HasBrokenLink, 'Page has a broken link');
|
||||
|
||||
$obj->Content = '<a href="[sitetree_link,id=' . $this->idFromFixture('Page', 'about') .']">this is not a broken link</a>';
|
||||
$obj->Content = '<a href="[sitetree_link,id=' . $this->idFromFixture(
|
||||
'Page',
|
||||
'about'
|
||||
) . ']">this is not a broken link</a>';
|
||||
$obj->syncLinkTracking();
|
||||
$this->assertFalse($obj->HasBrokenLink, 'Page does NOT have a broken link');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure broken links can be tracked between non-page objects
|
||||
*/
|
||||
public function testBrokenLinksNonPage()
|
||||
{
|
||||
/** @var Page $aboutPage */
|
||||
$aboutPage = $this->objFromFixture('Page', 'about');
|
||||
|
||||
/** @var NotPageObject $obj */
|
||||
$obj = $this->objFromFixture(NotPageObject::class, 'object1');
|
||||
$obj->Content = '<a href="[sitetree_link,id=3423423]">this is a broken link</a>';
|
||||
$obj->AnotherContent = '<a href="[sitetree_link,id=' . $aboutPage->ID . ']">this is not a broken link</a>';
|
||||
$obj->write();
|
||||
|
||||
// Two links created for this record
|
||||
$this->assertListEquals(
|
||||
[
|
||||
['LinkedID' => 3423423],
|
||||
['LinkedID' => $aboutPage->ID],
|
||||
],
|
||||
SiteTreeLink::get()->filter([
|
||||
'ParentClass' => NotPageObject::class,
|
||||
'ParentID' => $obj->ID,
|
||||
])
|
||||
);
|
||||
|
||||
// ManyManyThrough relation only links to unbroken pages
|
||||
$this->assertListEquals(
|
||||
[
|
||||
['Title' => 'About'],
|
||||
],
|
||||
$obj->LinkTracking()
|
||||
);
|
||||
|
||||
// About-page backlinks contains this object
|
||||
$this->assertListEquals(
|
||||
[
|
||||
['ID' => $obj->ID]
|
||||
],
|
||||
$aboutPage->BackLinkTracking()
|
||||
);
|
||||
}
|
||||
|
||||
public function testBrokenAnchorBetweenPages()
|
||||
{
|
||||
/** @var Page $obj */
|
||||
@ -94,47 +145,12 @@ class SiteTreeBrokenLinksTest extends SapphireTest
|
||||
$this->assertTrue($rp->HasBrokenLink, 'Broken redirector page IS marked as such');
|
||||
}
|
||||
|
||||
public function testDeletingFileMarksBackedPagesAsBroken()
|
||||
{
|
||||
// Test entry
|
||||
$file = new File();
|
||||
$file->setFromString('test', 'test-file.txt');
|
||||
$file->write();
|
||||
|
||||
/** @var Page $obj */
|
||||
$obj = $this->objFromFixture('Page', 'content');
|
||||
$obj->Content = sprintf(
|
||||
'<p><a href="[file_link,id=%d]">Working Link</a></p>',
|
||||
$file->ID
|
||||
);
|
||||
$obj->write();
|
||||
$this->assertTrue($obj->publishRecursive());
|
||||
// Confirm that it isn't marked as broken to begin with
|
||||
|
||||
$obj = SiteTree::get()->byID($obj->ID);
|
||||
$this->assertEquals(0, $obj->HasBrokenFile);
|
||||
|
||||
$liveObj = Versioned::get_one_by_stage(SiteTree::class, Versioned::LIVE, "\"SiteTree\".\"ID\" = $obj->ID");
|
||||
$this->assertEquals(0, $liveObj->HasBrokenFile);
|
||||
|
||||
// Delete the file
|
||||
$file->delete();
|
||||
|
||||
// Confirm that it is marked as broken in stage
|
||||
$obj = SiteTree::get()->byID($obj->ID);
|
||||
$this->assertEquals(1, $obj->HasBrokenFile);
|
||||
|
||||
// Publishing this page marks it as broken on live too
|
||||
$obj->publishRecursive();
|
||||
$liveObj = Versioned::get_one_by_stage(SiteTree::class, Versioned::LIVE, "\"SiteTree\".\"ID\" = $obj->ID");
|
||||
$this->assertEquals(1, $liveObj->HasBrokenFile);
|
||||
}
|
||||
|
||||
public function testDeletingMarksBackLinkedPagesAsBroken()
|
||||
{
|
||||
// Set up two published pages with a link from content -> about
|
||||
$linkDest = $this->objFromFixture('Page', 'about');
|
||||
|
||||
/** @var Page $linkSrc */
|
||||
$linkSrc = $this->objFromFixture('Page', 'content');
|
||||
$linkSrc->Content = "<p><a href=\"[sitetree_link,id=$linkDest->ID]\">about us</a></p>";
|
||||
$linkSrc->write();
|
||||
@ -207,7 +223,9 @@ class SiteTreeBrokenLinksTest extends SapphireTest
|
||||
$this->assertFalse($rp->HasBrokenLink);
|
||||
|
||||
// Unpublishing doesn't affect broken state on live (draft is source of truth)
|
||||
/** @var SiteTree $p2Live */
|
||||
$p2Live = Versioned::get_by_stage(SiteTree::class, Versioned::LIVE)->byID($p2->ID);
|
||||
/** @var SiteTree $rpLive */
|
||||
$rpLive = Versioned::get_by_stage(SiteTree::class, Versioned::LIVE)->byID($rp->ID);
|
||||
$this->assertEquals(0, $p2Live->HasBrokenLink);
|
||||
$this->assertEquals(0, $rpLive->HasBrokenLink);
|
||||
@ -215,8 +233,10 @@ class SiteTreeBrokenLinksTest extends SapphireTest
|
||||
// Delete the source page, confirm that the VP, RP and page 2 have broken links on draft
|
||||
$p->delete();
|
||||
$p2->flushCache();
|
||||
/** @var SiteTree $p2 */
|
||||
$p2 = DataObject::get_by_id(SiteTree::class, $p2->ID);
|
||||
$rp->flushCache();
|
||||
/** @var RedirectorPage $rp */
|
||||
$rp = DataObject::get_by_id(SiteTree::class, $rp->ID);
|
||||
$this->assertEquals(1, $p2->HasBrokenLink);
|
||||
$this->assertEquals(1, $rp->HasBrokenLink);
|
||||
@ -260,7 +280,7 @@ class SiteTreeBrokenLinksTest extends SapphireTest
|
||||
// Redirector links are a third
|
||||
$redirectorPage = new RedirectorPage();
|
||||
$redirectorPage->Title = "redirector";
|
||||
$redirectorPage->LinkType = 'Internal';
|
||||
$redirectorPage->RedirectionType = 'Internal';
|
||||
$redirectorPage->LinkToID = $page->ID;
|
||||
$redirectorPage->write();
|
||||
$this->assertTrue($redirectorPage->publishRecursive());
|
||||
@ -273,8 +293,10 @@ class SiteTreeBrokenLinksTest extends SapphireTest
|
||||
$page->delete();
|
||||
|
||||
$page2->flushCache();
|
||||
/** @var SiteTree $page2 */
|
||||
$page2 = DataObject::get_by_id(SiteTree::class, $page2->ID);
|
||||
$redirectorPage->flushCache();
|
||||
/** @var RedirectorPage $redirectorPage */
|
||||
$redirectorPage = DataObject::get_by_id(SiteTree::class, $redirectorPage->ID);
|
||||
$this->assertEquals(1, $page2->HasBrokenLink);
|
||||
$this->assertEquals(1, $redirectorPage->HasBrokenLink);
|
||||
|
@ -14,3 +14,6 @@ Page:
|
||||
RedirectionType: Internal
|
||||
Title: RedirectorPageToBrokenInteralPage
|
||||
LinkTo: =>Page.content
|
||||
SilverStripe\CMS\Tests\Model\SiteTreeBrokenLinksTest\NotPageObject:
|
||||
object1:
|
||||
Content: 'Everything will be ok'
|
||||
|
22
tests/php/Model/SiteTreeBrokenLinksTest/NotPageObject.php
Normal file
22
tests/php/Model/SiteTreeBrokenLinksTest/NotPageObject.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\CMS\Tests\Model\SiteTreeBrokenLinksTest;
|
||||
|
||||
use SilverStripe\CMS\Model\SiteTreeLinkTracking;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
/**
|
||||
* @mixin SiteTreeLinkTracking
|
||||
* @property string $Content
|
||||
* @property string $AnotherContent
|
||||
*/
|
||||
class NotPageObject extends DataObject implements TestOnly
|
||||
{
|
||||
private static $table_name = 'SiteTreeLinkTrackingTest_NotPageObject';
|
||||
|
||||
private static $db = [
|
||||
'Content' => 'HTMLText',
|
||||
'AnotherContent' => 'HTMLText',
|
||||
];
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\CMS\Tests\Model;
|
||||
|
||||
use SilverStripe\Assets\File;
|
||||
use SilverStripe\Assets\Image;
|
||||
use Silverstripe\Assets\Dev\TestAssetStore;
|
||||
use SilverStripe\CMS\Tests\Model\SiteTreeFolderExtensionTest\PageWithFile;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Versioned\Versioned;
|
||||
|
||||
class SiteTreeFolderExtensionTest extends SapphireTest
|
||||
{
|
||||
protected static $extra_dataobjects = [
|
||||
PageWithFile::class,
|
||||
];
|
||||
|
||||
protected static $fixture_file = 'SiteTreeFolderExtensionTest.yml';
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Versioned::set_stage(Versioned::DRAFT);
|
||||
|
||||
TestAssetStore::activate('SiteTreeFolderExtensionTest');
|
||||
$this->logInWithPermission('ADMIN');
|
||||
|
||||
// Since we can't hard-code IDs, manually inject image tracking shortcode
|
||||
$imageID = $this->idFromFixture(Image::class, 'image1');
|
||||
$page = $this->objFromFixture(PageWithFile::class, 'page1');
|
||||
$page->Content = sprintf(
|
||||
'<p>[image id="%d"]</p>',
|
||||
$imageID
|
||||
);
|
||||
$page->write();
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
TestAssetStore::reset();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testFindsFiles()
|
||||
{
|
||||
/** @var PageWithFile $page */
|
||||
$page = $this->objFromFixture(PageWithFile::class, 'page1');
|
||||
$query = $page->getUnusedFilesListFilter();
|
||||
$this->assertContains('"ID" NOT IN', $query);
|
||||
$this->assertContains('"ClassName" IN (', $query);
|
||||
|
||||
$files = File::get()->where($query);
|
||||
$this->assertDOSEquals(
|
||||
[
|
||||
['Name' => 'file2.txt'],
|
||||
['Name' => 'image2.jpg'],
|
||||
],
|
||||
$files
|
||||
);
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
SilverStripe\Assets\Folder:
|
||||
folder1:
|
||||
Name: myfolder
|
||||
folder2:
|
||||
Name: other
|
||||
SilverStripe\Assets\File:
|
||||
file1:
|
||||
Name: file1.txt
|
||||
FileFilename: myfolder/file1.txt
|
||||
Parent: =>SilverStripe\Assets\Folder.folder1
|
||||
file2:
|
||||
Name: file2.txt
|
||||
FileFilename: myfolder/file2.txt
|
||||
Parent: =>SilverStripe\Assets\Folder.folder1
|
||||
SilverStripe\Assets\Image:
|
||||
image1:
|
||||
Name: image1.jpg
|
||||
FileFilename: other/image1.jpg
|
||||
Parent: =>SilverStripe\Assets\Folder.folder2
|
||||
image2:
|
||||
Name: image2.jpg
|
||||
FileFilename: other/image2.jpg
|
||||
Parent: =>SilverStripe\Assets\Folder.folder2
|
||||
SilverStripe\CMS\Tests\Model\SiteTreeFolderExtensionTest\PageWithFile:
|
||||
page1:
|
||||
Title: mypage
|
||||
URLSegment: mypage
|
||||
LinkedFile: =>SilverStripe\Assets\File.file1
|
@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\CMS\Tests\Model\SiteTreeFolderExtensionTest;
|
||||
|
||||
use Page;
|
||||
use SilverStripe\Assets\File;
|
||||
use SilverStripe\CMS\Model\SiteTreeFolderExtension;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
|
||||
/**
|
||||
* @mixin SiteTreeFolderExtension
|
||||
*/
|
||||
class PageWithFile extends Page implements TestOnly
|
||||
{
|
||||
private static $table_name = 'SiteTreeFolderExtensionTest_PageWithFile';
|
||||
|
||||
private static $has_one = [
|
||||
'LinkedFile' => File::class,
|
||||
];
|
||||
|
||||
private static $extensions = [
|
||||
SiteTreeFolderExtension::class,
|
||||
];
|
||||
}
|
@ -3,11 +3,10 @@
|
||||
namespace SilverStripe\CMS\Tests\Model;
|
||||
|
||||
use Page;
|
||||
use Silverstripe\Assets\Dev\TestAssetStore;
|
||||
use SilverStripe\Assets\Dev\TestAssetStore;
|
||||
use SilverStripe\Assets\File;
|
||||
use SilverStripe\Assets\Filesystem;
|
||||
use SilverStripe\Assets\Folder;
|
||||
use SilverStripe\Assets\Image;
|
||||
use SilverStripe\CMS\Model\SiteTree;
|
||||
use SilverStripe\Dev\CSSContentParser;
|
||||
use SilverStripe\Dev\FunctionalTest;
|
||||
@ -46,6 +45,7 @@ class SiteTreeHTMLEditorFieldTest extends FunctionalTest
|
||||
|
||||
public function testLinkTracking()
|
||||
{
|
||||
/** @var SiteTree $sitetree */
|
||||
$sitetree = $this->objFromFixture(SiteTree::class, 'home');
|
||||
$editor = new HTMLEditorField('Content');
|
||||
|
||||
@ -84,43 +84,6 @@ class SiteTreeHTMLEditorFieldTest extends FunctionalTest
|
||||
);
|
||||
}
|
||||
|
||||
public function testFileLinkTracking()
|
||||
{
|
||||
$sitetree = $this->objFromFixture(SiteTree::class, 'home');
|
||||
$editor = new HTMLEditorField('Content');
|
||||
$fileID = $this->idFromFixture(File::class, 'example_file');
|
||||
|
||||
$editor->setValue(sprintf(
|
||||
'<p><a href="[file_link,id=%d]">Example File</a></p>',
|
||||
$fileID
|
||||
));
|
||||
$editor->saveInto($sitetree);
|
||||
$sitetree->write();
|
||||
$this->assertEquals(
|
||||
array($fileID => $fileID),
|
||||
$sitetree->ImageTracking()->getIDList(),
|
||||
'Links to assets are tracked.'
|
||||
);
|
||||
|
||||
$editor->setValue(null);
|
||||
$editor->saveInto($sitetree);
|
||||
$sitetree->write();
|
||||
$this->assertEquals(array(), $sitetree->ImageTracking()->getIdList(), 'Asset tracking is removed with links.');
|
||||
|
||||
// Legacy support - old CMS versions added link shortcodes with spaces instead of commas
|
||||
$editor->setValue(sprintf(
|
||||
'<p><a href="[file_link id=%d]">Example File</a></p>',
|
||||
$fileID
|
||||
));
|
||||
$editor->saveInto($sitetree);
|
||||
$sitetree->write();
|
||||
$this->assertEquals(
|
||||
array($fileID => $fileID),
|
||||
$sitetree->ImageTracking()->getIDList(),
|
||||
'Link tracking with space instead of comma in shortcode works.'
|
||||
);
|
||||
}
|
||||
|
||||
public function testImageInsertion()
|
||||
{
|
||||
$sitetree = new SiteTree();
|
||||
@ -145,30 +108,6 @@ class SiteTreeHTMLEditorFieldTest extends FunctionalTest
|
||||
$this->assertEquals('bar', (string)$xml[0]['title'], 'Title tags are preserved.');
|
||||
}
|
||||
|
||||
public function testImageTracking()
|
||||
{
|
||||
$sitetree = $this->objFromFixture(SiteTree::class, 'home');
|
||||
$editor = new HTMLEditorField('Content');
|
||||
$file = $this->objFromFixture(Image::class, 'example_image');
|
||||
|
||||
$editor->setValue(sprintf('[image src="%s" id="%d"]', $file->getURL(), $file->ID));
|
||||
$editor->saveInto($sitetree);
|
||||
$sitetree->write();
|
||||
$this->assertEquals(
|
||||
array($file->ID => $file->ID),
|
||||
$sitetree->ImageTracking()->getIDList(),
|
||||
'Inserted images are tracked.'
|
||||
);
|
||||
|
||||
$editor->setValue(null);
|
||||
$editor->saveInto($sitetree);
|
||||
$sitetree->write();
|
||||
$this->assertEmpty(
|
||||
$sitetree->ImageTracking()->getIDList(),
|
||||
'Tracked images are deleted when removed.'
|
||||
);
|
||||
}
|
||||
|
||||
public function testBrokenSiteTreeLinkTracking()
|
||||
{
|
||||
$sitetree = new SiteTree();
|
||||
@ -192,28 +131,4 @@ class SiteTreeHTMLEditorFieldTest extends FunctionalTest
|
||||
|
||||
$this->assertFalse((bool) $sitetree->HasBrokenLink);
|
||||
}
|
||||
|
||||
public function testBrokenFileLinkTracking()
|
||||
{
|
||||
$sitetree = new SiteTree();
|
||||
$editor = new HTMLEditorField('Content');
|
||||
|
||||
$this->assertFalse((bool) $sitetree->HasBrokenFile);
|
||||
|
||||
$editor->setValue('<p><a href="[file_link,id=0]">Broken Link</a></p>');
|
||||
$editor->saveInto($sitetree);
|
||||
$sitetree->write();
|
||||
|
||||
$this->assertTrue($sitetree->HasBrokenFile);
|
||||
|
||||
$editor->setValue(sprintf(
|
||||
'<p><a href="[file_link,id=%d]">Working Link</a></p>',
|
||||
$this->idFromFixture(File::class, 'example_file')
|
||||
));
|
||||
$sitetree->HasBrokenFile = false;
|
||||
$editor->saveInto($sitetree);
|
||||
$sitetree->write();
|
||||
|
||||
$this->assertFalse((bool) $sitetree->HasBrokenFile);
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,15 @@
|
||||
|
||||
namespace SilverStripe\CMS\Tests\Model;
|
||||
|
||||
use Page;
|
||||
use SilverStripe\CMS\Model\SiteTreeLinkTracking_Parser;
|
||||
use SilverStripe\Assets\File;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\View\Parsers\HTMLValue;
|
||||
use Page;
|
||||
|
||||
class SiteTreeLinkTrackingTest extends SapphireTest
|
||||
{
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
@ -34,7 +34,6 @@ class SiteTreeLinkTrackingTest extends SapphireTest
|
||||
// Shortcodes
|
||||
$this->assertTrue($this->isBroken('<a href="[sitetree_link,id=123]">link</a>'));
|
||||
$this->assertTrue($this->isBroken('<a href="[sitetree_link,id=123]#no-such-anchor">link</a>'));
|
||||
$this->assertTrue($this->isBroken('<a href="[file_link,id=123]">link</a>'));
|
||||
|
||||
// Relative urls
|
||||
$this->assertTrue($this->isBroken('<a href="">link</a>'));
|
||||
@ -57,13 +56,9 @@ class SiteTreeLinkTrackingTest extends SapphireTest
|
||||
$page->Content = '<a name="yes-name-anchor">name</a><a id="yes-id-anchor">id</a>';
|
||||
$page->write();
|
||||
|
||||
$file = new File();
|
||||
$file->write();
|
||||
|
||||
$this->assertFalse($this->isBroken("<a href=\"[sitetree_link,id=$page->ID]\">link</a>"));
|
||||
$this->assertFalse($this->isBroken("<a href=\"[sitetree_link,id=$page->ID]#yes-name-anchor\">link</a>"));
|
||||
$this->assertFalse($this->isBroken("<a href=\"[sitetree_link,id=$page->ID]#yes-id-anchor\">link</a>"));
|
||||
$this->assertFalse($this->isBroken("<a href=\"[file_link,id=$file->ID]\">link</a>"));
|
||||
$this->assertTrue($this->isBroken("<a href=\"[sitetree_link,id=$page->ID]#http://invalid-anchor.com\"></a>"));
|
||||
}
|
||||
|
||||
@ -94,18 +89,4 @@ class SiteTreeLinkTrackingTest extends SapphireTest
|
||||
$this->assertEquals(substr_count($content, 'ss-broken'), 0, 'All ss-broken classes are removed from good link');
|
||||
$this->assertEquals(substr_count($content, 'existing-class'), 1, 'Existing class is not removed.');
|
||||
}
|
||||
|
||||
public function testHasBrokenFile()
|
||||
{
|
||||
$this->assertTrue($this->pageIsBrokenFile('[image src="someurl.jpg" id="99999999"]'));
|
||||
$this->assertFalse($this->pageIsBrokenFile('[image src="someurl.jpg"]'));
|
||||
}
|
||||
|
||||
protected function pageIsBrokenFile($content)
|
||||
{
|
||||
$page = new Page();
|
||||
$page->Content = $content;
|
||||
$page->write();
|
||||
return $page->HasBrokenFile;
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use SilverStripe\CMS\Model\SiteTree;
|
||||
use SilverStripe\CMS\Tasks\MigrateSiteTreeLinkingTask;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\DB;
|
||||
|
||||
class MigrateSiteTreeLinkingTaskTest extends SapphireTest
|
||||
{
|
||||
@ -13,73 +14,76 @@ class MigrateSiteTreeLinkingTaskTest extends SapphireTest
|
||||
|
||||
protected static $use_draft_site = true;
|
||||
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
parent::setUpBeforeClass();
|
||||
|
||||
// Cover db reset in case parent did not start
|
||||
if (!static::getExtraDataObjects()) {
|
||||
DataObject::reset();
|
||||
static::resetDBSchema(true, true);
|
||||
}
|
||||
|
||||
// Ensure legacy SiteTree_LinkTracking table exists
|
||||
DB::get_schema()->schemaUpdate(function () {
|
||||
DB::require_table('SiteTree_LinkTracking', [
|
||||
'SiteTreeID' => 'Int',
|
||||
'ChildID' => 'Int',
|
||||
'FieldName' => 'Varchar',
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Manually bootstrap all Content blocks with soft coded IDs (raw sql to avoid save hooks)
|
||||
$replacements = [
|
||||
'$$ABOUTID$$' => $this->idFromFixture(SiteTree::class, 'about'),
|
||||
'$$HOMEID$$' => $this->idFromFixture(SiteTree::class, 'home'),
|
||||
'$$STAFFID$$' => $this->idFromFixture(SiteTree::class, 'staff'),
|
||||
];
|
||||
foreach (DB::query('SELECT "ID", "Content" FROM "SiteTree"') as $row) {
|
||||
$id = (int)$row['ID'];
|
||||
$content = str_replace(array_keys($replacements), array_values($replacements), $row['Content']);
|
||||
DB::prepared_query('UPDATE "SiteTree" SET "Content" = ? WHERE "ID" = ?', [$content, $id]);
|
||||
}
|
||||
DataObject::reset();
|
||||
}
|
||||
|
||||
public function testLinkingMigration()
|
||||
{
|
||||
ob_start();
|
||||
|
||||
DB::quiet(false);
|
||||
$task = new MigrateSiteTreeLinkingTask();
|
||||
$task->run(null);
|
||||
|
||||
$this->assertEquals(
|
||||
"Rewrote 9 link(s) on 5 page(s) to use shortcodes.\n",
|
||||
$this->assertContains(
|
||||
"Migrated page links on 5 Pages",
|
||||
ob_get_contents(),
|
||||
'Rewritten links are correctly reported'
|
||||
);
|
||||
DB::quiet(true);
|
||||
ob_end_clean();
|
||||
|
||||
$homeID = $this->idFromFixture(SiteTree::class, 'home');
|
||||
$aboutID = $this->idFromFixture(SiteTree::class, 'about');
|
||||
$staffID = $this->idFromFixture(SiteTree::class, 'staff');
|
||||
$actionID = $this->idFromFixture(SiteTree::class, 'action');
|
||||
$hashID = $this->idFromFixture(SiteTree::class, 'hash_link');
|
||||
// Query links for pages
|
||||
/** @var SiteTree $home */
|
||||
$home = $this->objFromFixture(SiteTree::class, 'home');
|
||||
/** @var SiteTree $about */
|
||||
$about = $this->objFromFixture(SiteTree::class, 'about');
|
||||
/** @var SiteTree $staff */
|
||||
$staff = $this->objFromFixture(SiteTree::class, 'staff');
|
||||
/** @var SiteTree $action */
|
||||
$action = $this->objFromFixture(SiteTree::class, 'action');
|
||||
/** @var SiteTree $hash */
|
||||
$hash = $this->objFromFixture(SiteTree::class, 'hash_link');
|
||||
|
||||
$homeContent = sprintf(
|
||||
'<a href="[sitetree_link,id=%d]">About</a><a href="[sitetree_link,id=%d]">Staff</a><a href="http://silverstripe.org/">External Link</a><a name="anchor"></a>',
|
||||
$aboutID,
|
||||
$staffID
|
||||
);
|
||||
$aboutContent = sprintf(
|
||||
'<a href="[sitetree_link,id=%d]">Home</a><a href="[sitetree_link,id=%d]">Staff</a><a name="second-anchor"></a>',
|
||||
$homeID,
|
||||
$staffID
|
||||
);
|
||||
$staffContent = sprintf(
|
||||
'<a href="[sitetree_link,id=%d]">Home</a><a href="[sitetree_link,id=%d]">About</a>',
|
||||
$homeID,
|
||||
$aboutID
|
||||
);
|
||||
$actionContent = sprintf(
|
||||
'<a href="[sitetree_link,id=%d]SearchForm">Search Form</a>',
|
||||
$homeID
|
||||
);
|
||||
$hashLinkContent = sprintf(
|
||||
'<a href="[sitetree_link,id=%d]#anchor">Home</a><a href="[sitetree_link,id=%d]#second-anchor">About</a>',
|
||||
$homeID,
|
||||
$aboutID
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
$homeContent,
|
||||
DataObject::get_by_id(SiteTree::class, $homeID)->Content,
|
||||
'HTML URLSegment links are rewritten.'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$aboutContent,
|
||||
DataObject::get_by_id(SiteTree::class, $aboutID)->Content
|
||||
);
|
||||
$this->assertEquals(
|
||||
$staffContent,
|
||||
DataObject::get_by_id(SiteTree::class, $staffID)->Content
|
||||
);
|
||||
$this->assertEquals(
|
||||
$actionContent,
|
||||
DataObject::get_by_id(SiteTree::class, $actionID)->Content,
|
||||
'Links to actions on pages are rewritten correctly.'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$hashLinkContent,
|
||||
DataObject::get_by_id(SiteTree::class, $hashID)->Content,
|
||||
'Hash/anchor links are correctly handled.'
|
||||
);
|
||||
// Ensure all links are created
|
||||
$this->assertListEquals([$about->toMap(), $staff->toMap()], $home->LinkTracking());
|
||||
$this->assertListEquals([$home->toMap(), $staff->toMap()], $about->LinkTracking());
|
||||
$this->assertListEquals([$home->toMap(), $about->toMap()], $staff->LinkTracking());
|
||||
$this->assertListEquals([$home->toMap()], $action->LinkTracking());
|
||||
$this->assertListEquals([$home->toMap(), $about->toMap()], $hash->LinkTracking());
|
||||
}
|
||||
}
|
||||
|
@ -2,24 +2,24 @@ SilverStripe\CMS\Model\SiteTree:
|
||||
home:
|
||||
Title: Home Page
|
||||
URLSegment: home
|
||||
Content: '<a href="about/">About</a><a href="staff">Staff</a><a href="http://silverstripe.org/">External Link</a><a name="anchor"></a>'
|
||||
Content: '<a href="[sitetree_link,id=$$ABOUTID$$]">About</a><a href="[sitetree_link,id=$$STAFFID$$]">Staff</a><a href="http://silverstripe.org/">External Link</a><a name="anchor"></a>'
|
||||
about:
|
||||
Title: About Us
|
||||
URLSegment: about
|
||||
Content: '<a href="home">Home</a><a href="staff/">Staff</a><a name="second-anchor"></a>'
|
||||
Content: '<a href="[sitetree_link,id=$$HOMEID$$]">Home</a><a href="[sitetree_link,id=$$STAFFID$$]">Staff</a><a name="second-anchor"></a>'
|
||||
staff:
|
||||
Title: Staff
|
||||
URLSegment: staff
|
||||
Content: '<a href="home/">Home</a><a href="about">About</a>'
|
||||
Content: '<a href="[sitetree_link,id=$$HOMEID$$]">Home</a><a href="[sitetree_link,id=$$ABOUTID$$]">About</a>'
|
||||
Parent: =>SilverStripe\CMS\Model\SiteTree.about
|
||||
action:
|
||||
Title: Action Link
|
||||
URLSegment: action
|
||||
Content: '<a href="home/SearchForm">Search Form</a>'
|
||||
Content: '<a href="[sitetree_link,id=$$HOMEID$$]SearchForm">Search Form</a>'
|
||||
hash_link:
|
||||
Title: Hash Link
|
||||
URLSegment: hash-link
|
||||
Content: '<a href="home/#anchor">Home</a><a href="about/#second-anchor">About</a>'
|
||||
Content: '<a href="[sitetree_link,id=$$HOMEID$$]#anchor">Home</a><a href="[sitetree_link,id=$$ABOUTID$$]#second-anchor">About</a>'
|
||||
admin_link:
|
||||
Title: Admin Link
|
||||
URLSegment: admin-link
|
||||
|
Loading…
Reference in New Issue
Block a user