Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NEW Get anchor links from elemental pages #911

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
},
"require-dev": {
"silverstripe/recipe-testing": "^2",
"silverstripe/frameworktest": "4.x-dev"
"silverstripe/frameworktest": "^0.4.5"
},
"suggest": {
"silverstripe/elemental-blocks": "Adds a set of common SilverStripe content block types",
Expand Down
55 changes: 40 additions & 15 deletions src/Extensions/ElementalPageExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace DNADesign\Elemental\Extensions;

use DNADesign\Elemental\Models\BaseElement;
use DNADesign\Elemental\Models\ElementalArea;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Control\Controller;
use SilverStripe\View\Parsers\HTML4Value;
use SilverStripe\View\SSViewer;
Expand Down Expand Up @@ -47,23 +49,14 @@ public function getElementsForSearch()
SSViewer::set_themes(SSViewer::config()->get('themes'));
try {
$output = [];
foreach ($this->owner->hasOne() as $key => $class) {
if ($class !== ElementalArea::class) {
continue;
}
/** @var ElementalArea $area */
$area = $this->owner->$key();
if ($area) {
foreach ($area->Elements() as $element) {
if ($element->getSearchIndexable()) {
$content = $element->getContentForSearchIndex();
if ($content) {
$output[] = $content;
}
}
$this->loopThroughElements(function (BaseElement $element) use (&$output) {
if ($element->getSearchIndexable()) {
$content = $element->getContentForSearchIndex();
if ($content) {
$output[] = $content;
}
}
}
});
} finally {
// Reset theme if an exception occurs, if you don't have a
// try / finally around code that might throw an Exception,
Expand All @@ -73,6 +66,19 @@ public function getElementsForSearch()
return implode($this->owner->config()->get('search_index_element_delimiter') ?? '', $output);
}

/**
* @see SiteTree::getAnchorsOnPage()
*/
public function updateAnchorsOnPage(array &$anchors): void
{
if (!($this->owner instanceof SiteTree)) {
return;
}
$this->loopThroughElements(function (BaseElement $element) use (&$anchors) {
$anchors = array_merge($anchors, $element->getAnchorsInContent());
});
}

public function MetaTags(&$tags)
{
if (!Controller::has_curr()) {
Expand All @@ -91,4 +97,23 @@ public function MetaTags(&$tags)
$tags = $html->getContent();
}
}

/**
* Call some function over all elements belonging to this page
*/
private function loopThroughElements(callable $callback): void
{
foreach ($this->owner->hasOne() as $key => $class) {
if ($class !== ElementalArea::class) {
continue;
}
/** @var ElementalArea $area */
$area = $this->owner->$key();
if ($area) {
foreach ($area->Elements() as $element) {
$callback($element);
}
}
}
}
}
36 changes: 34 additions & 2 deletions src/Models/BaseElement.php
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ public function canEdit($member = null)
public function canDelete($member = null)
{
$extended = $this->extendedCan(__FUNCTION__, $member);

if ($extended !== null) {
return $extended;
}
Expand Down Expand Up @@ -679,6 +679,38 @@ public function getAnchor()
return $this->anchor = $result;
}

/**
* Get anchors in this block's content.
* Used to populate the "anchor on a page" link in the WYSIWYG
*
* By default, this finds anchors in any HTMLText field on the block, but
* this method should be overridden if anchors are provided in other ways
* for this block or if not all HTMLText fields for this block are
* displayed on the front-end.
*/
public function getAnchorsInContent(): array
{
$anchors = [$this->getAnchor()];
$anchorRegex = "/\\s+(name|id)\\s*=\\s*([\"'])([^\\2\\s>]*?)\\2|\\s+(name|id)\\s*=\\s*([^\"']+)[\\s +>]/im";
sabina-talipova marked this conversation as resolved.
Show resolved Hide resolved
sabina-talipova marked this conversation as resolved.
Show resolved Hide resolved
$allFields = DataObject::getSchema()->fieldSpecs($this);
foreach ($allFields as $field => $fieldSpec) {
$fieldObj = $this->owner->dbObject($field);
if ($fieldObj instanceof DBHTMLText) {
$parseSuccess = preg_match_all($anchorRegex, $fieldObj->getValue() ?? '', $matches);
if ($parseSuccess >= 1) {
$fieldAnchors = array_values(array_filter(
array_merge($matches[3], $matches[5])
sabina-talipova marked this conversation as resolved.
Show resolved Hide resolved
));
$anchors = array_merge($anchors, $fieldAnchors);
}
}
}
$anchors = array_unique($anchors);

$this->extend('updateAnchorsInContent', $anchors);
return $anchors;
}

/**
* @param string|null $action
* @return string|null
Expand All @@ -688,7 +720,7 @@ public function getAnchor()
public function AbsoluteLink($action = null)
{
$page = $this->getPage();

if ($page && ClassInfo::hasMethod($page, 'AbsoluteLink')) {
$link = $page->AbsoluteLink($action) . '#' . $this->getAnchor();
$this->extend('updateAbsoluteLink', $link);
Expand Down
34 changes: 34 additions & 0 deletions tests/BaseElementTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use DNADesign\Elemental\Tests\Src\TestElement;
use DNADesign\Elemental\Tests\Src\TestElementDataObject;
use DNADesign\Elemental\Tests\Src\TestDataObjectWithCMSEditLink;
use DNADesign\Elemental\Tests\Src\TestMultipleHtmlFieldsElement;
use DNADesign\Elemental\Tests\Src\TestPage;
use Page;
use ReflectionClass;
Expand Down Expand Up @@ -46,6 +47,7 @@ class BaseElementTest extends FunctionalTest
TestDataObject::class,
TestDataObjectWithCMSEditLink::class,
TestElementDataObject::class,
TestMultipleHtmlFieldsElement::class,
];

public function testSimpleClassName()
Expand Down Expand Up @@ -271,6 +273,38 @@ public function testUpdateContentForSearchIndex()
ElementContent::remove_extension(TestContentForSearchIndexExtension::class);
}

public function getElementAnchorDataProvider(): array
{
return [
[
TestMultipleHtmlFieldsElement::class,
'multiHtmlFields1',
[
'anchor1',
'anchor2',
'anchor3',
'anchor4',
]
],
[
TestMultipleHtmlFieldsElement::class,
'multiHtmlFields2',
[]
],
];
}

/**
* @dataProvider getElementAnchorDataProvider
*/
public function testGetAnchorsInContent(string $elementClass, string $elementName, array $expectedAnchors): void
{
$element = $this->objFromFixture($elementClass, $elementName);
array_unshift($expectedAnchors, $element->getAnchor());
// We use array values here because `array_unique` in `getAnchorsInContent()` doesn't reset array indices
$this->assertSame($expectedAnchors, array_values($element->getAnchorsInContent()));
}

public function getElementCMSLinkDataProvider()
{
return [
Expand Down
2 changes: 1 addition & 1 deletion tests/Behat/Context/FixtureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
class FixtureContext extends BaseFixtureContext
{
/**
* @Given /(?:the|a) "([^"]+)" "([^"]+)" (?:with|has) a "([^"]+)" content element with "([^"]+)" content/
* @Given /(?:the|a) "([^"]+)" "([^"]+)" (?:with|has) a "([^"]+)" content element with "(.*)" content/
*
* @param string $pageTitle
* @param string $type
Expand Down
62 changes: 62 additions & 0 deletions tests/Behat/features/add-link-to-anchor.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
Feature: Link to anchors in elements
As a cms author
I want to link to anchors in my content
So that I can direct users directly to the relevant information

Background:
Given a "page" "No Blocks" has the "Content" "<p>My awesome content<a name="normal-anchor"></a></p>"
And a "BasicElementalPage" "Elemental" with a "Anchor Test Block" content element with "<p>My awesomer content<a name="element-anchor"></a></p>" content
And the "BasicElementalPage" "Elemental" has a "Same Page Anchor Block" content element with "<p><a id="another-anchor"></a></p>" content
And I am logged in with "ADMIN" permissions
And I go to "/admin/pages"

Scenario: I can link to anchors in an elemental block from a normal page
Given I left click on "No Blocks" in the tree
And I select "awesome" in the "Content" HTML field
And I press the "Insert link" HTML field button
And I click "Anchor on a page" in the ".mce-menu" element
Then I should see an "form#Form_editorAnchorLink" element
And I should see "No Blocks" in the "#Form_editorAnchorLink_PageID_Holder .Select-multi-value-wrapper" element
When I click "No Blocks" in the "#Form_editorAnchorLink_PageID_Holder .Select-multi-value-wrapper" element
And I click "Elemental" in the "#Form_editorAnchorLink_PageID_Holder .Select-menu-outer" element
And I click "Select or enter anchor" in the "#Form_editorAnchorLink_Anchor_Holder .Select-multi-value-wrapper" element
And I click "element-anchor" in the "#Form_editorAnchorLink_Anchor_Holder .Select-menu-outer" element
Then I should see "element-anchor" in the "#Form_editorAnchorLink_Anchor_Holder .Select-value" element
# Close the dialog now that we're done with it.
When I click on the "button.close" element

Scenario: I can link to anchors on a normal page from an elemental block
Given I left click on "Elemental" in the tree
Then I should see a list of blocks
And I should see "Anchor Test Block"
Given I click on block 1
Then the "Content" field for block 1 should contain "My awesomer content"
When I select "awesomer" in the "Content" HTML field
And I press the "Insert link" HTML field button
And I click "Anchor on a page" in the ".mce-menu" element
Then I should see an "form#Form_editorAnchorLink" element
And I should see "Elemental" in the "#Form_editorAnchorLink_PageID_Holder .Select-multi-value-wrapper" element
When I click "Elemental" in the "#Form_editorAnchorLink_PageID_Holder .Select-multi-value-wrapper" element
And I click "No Blocks" in the "#Form_editorAnchorLink_PageID_Holder .Select-menu-outer" element
And I click "Select or enter anchor" in the "#Form_editorAnchorLink_Anchor_Holder .Select-multi-value-wrapper" element
And I click "normal-anchor" in the "#Form_editorAnchorLink_Anchor_Holder .Select-menu-outer" element
Then I should see "normal-anchor" in the "#Form_editorAnchorLink_Anchor_Holder .Select-value" element
# Close the dialog now that we're done with it.
When I click on the "button.close" element

Scenario: I can link to anchors in an elemental block from another elemental block
Given I left click on "Elemental" in the tree
And I should see a list of blocks
And I should see "Anchor Test Block"
Given I click on block 1
Then the "Content" field for block 1 should contain "My awesomer content"
When I select "awesomer" in the "Content" HTML field
And I press the "Insert link" HTML field button
And I click "Anchor on a page" in the ".mce-menu" element
Then I should see an "form#Form_editorAnchorLink" element
And I should see "Elemental" in the "#Form_editorAnchorLink_PageID_Holder .Select-multi-value-wrapper" element
When I click "Select or enter anchor" in the "#Form_editorAnchorLink_Anchor_Holder .Select-multi-value-wrapper" element
And I click "another-anchor" in the "#Form_editorAnchorLink_Anchor_Holder .Select-menu-outer" element
Then I should see "another-anchor" in the "#Form_editorAnchorLink_Anchor_Holder .Select-value" element
# Close the dialog now that we're done with it.
When I click on the "button.close" element
11 changes: 10 additions & 1 deletion tests/ElementalAreaDataObjectTest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ DNADesign\Elemental\Models\ElementalArea:

DNADesign\Elemental\Tests\Src\TestDataObjectWithCMSEditLink:
dataObject1:
Title: DataObject with CMSEditLink method
Title: DataObject with CMSEditLink method
ElementalAreaID: =>DNADesign\Elemental\Models\ElementalArea.areaDataObject1

DNADesign\Elemental\Tests\Src\TestDataObject:
Expand Down Expand Up @@ -60,3 +60,12 @@ DNADesign\Elemental\Models\ElementContent:
contentDataObject1:
HTML: Some content
ParentID: =>DNADesign\Elemental\Models\ElementalArea.areaDataObject1

DNADesign\Elemental\Tests\Src\TestMultipleHtmlFieldsElement:
multiHtmlFields1:
Field1: '<p><a id="anchor1"></a><span name="anchor2"></span></p>'
Field2: '<p><a id="anchor1"></a><span name="anchor3"></span></p>'
Field3: '<p><a id="anchor4"></a><span name="anchor3"></span></p>'
multiHtmlFields2:
Field1: '<p>id="not-anchor"</p>'
Field2: '<p>name="not-anchor2"</p>'
18 changes: 18 additions & 0 deletions tests/Src/TestMultiHtmlFieldsElement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace DNADesign\Elemental\Tests\Src;

use DNADesign\Elemental\Models\BaseElement;
use SilverStripe\Dev\TestOnly;
use SilverStripe\Security\Permission;

class TestMultipleHtmlFieldsElement extends BaseElement implements TestOnly
{
private static $table_name = 'TestMultipleHtmlFieldsElement';

private static $db = [
'Field1' => 'HTMLText',
'Field2' => 'HTMLText',
'Field3' => 'HTMLText',
];
}