API-CHANGE: dms shortcode system

This commit is contained in:
Julian Seidenberg 2012-08-21 14:21:23 +12:00
parent 80c3f3f757
commit bac9666597
10 changed files with 350 additions and 8 deletions

View File

@ -1,3 +1,6 @@
<?php
Object::add_extension('SiteTree','DMSSiteTreeExtension');
CMSMenu::remove_menu_item('DMSDocumentAddController');
Object::add_extension('HtmlEditorField_Toolbar','DocumentHtmlEditorFieldToolbar');
CMSMenu::remove_menu_item('DMSDocumentAddController');
ShortcodeParser::get('default')->register('dms_document_link', array('DMSDocument_Controller', 'dms_link_shortcode_handler'));

View File

@ -519,12 +519,21 @@ class DMSDocument extends DataObject implements DMSDocumentInterface {
$gridFieldConfig
);
$relationFinder = new ShortCodeRelationFinder();
$referencesGrid = GridField::create(
'References',
_t('DMSDocument.RelatedReferences', 'Related References'),
$relationFinder->getList($this->ID),
$gridFieldConfig
);
$fields->add(new LiteralField('BottomTaskSelection',
'<div id="Actions" class="field actions"><label class="left">Actions</label><ul>'.
'<li class="ss-ui-button" data-panel="embargo">Embargo</li>'.
'<li class="ss-ui-button" data-panel="expiry">Expiry</li>'.
'<li class="ss-ui-button" data-panel="replace">Replace</li>'.
'<li class="ss-ui-button" data-panel="find-usage">Find usage</li>'.
'<li class="ss-ui-button" data-panel="find-references">Find references</li>'.
'</ul></div>'));
$embargoValue = 'None';
@ -554,7 +563,8 @@ class DMSDocument extends DataObject implements DMSDocumentInterface {
$expiryDatetime
)->addExtraClass('expiry'),
$uploadField->addExtraClass('replace'),
$pagesGrid->addExtraClass('find-usage')
$pagesGrid->addExtraClass('find-usage'),
$referencesGrid->addExtraClass('find-references')
)->setName("ActionsPanel")->addExtraClass('dmsupload ss-uploadfield'));
$this->extend('updateCMSFields', $fields);
@ -775,5 +785,32 @@ class DMSDocument_Controller extends Controller {
}
/**
* Handles dms_document_link shortcode
* @return string
*/
public static function dms_link_shortcode_handler($arguments, $content = null, $parser = null) {
$linkText = null;
if(isset($arguments['id']) && is_numeric($arguments['id'])) {
// get the document object
$document = DataObject::get_by_id('DMSDocument', Convert::raw2sql($arguments['id']));
if ($document && !$document->isHidden()) {
if( $content ) {
$linkText = sprintf('<a href="%s">%s</a>', $document->Link(), $parser->parse($content));
} else {
$extension = $document->getFileExt();
$size = "data:{size:'{$document->getFileSizeFormatted()}'}";
$linkText = $document->getDownloadLink()."\" class=\"$size documentLink $extension";
}
}
}
if (!$linkText) {
$errorPage = DataObject::get_one('ErrorPage', '"ErrorCode" = \'404\'');
if ($errorPage) $linkText = $errorPage->Link();
}
return $linkText;
}
}

View File

@ -10,6 +10,9 @@ class DMSSiteTreeExtension extends DataExtension {
Requirements::javascript('dms/javascript/DMSGridField.js');
Requirements::css('dms/css/DMSMainCMS.css');
//javascript for the link editor pop-up in TinyMCE
Requirements::javascript("dms/javascript/DocumentHtmlEditorFieldToolbar.js");
// Document listing
$gridFieldConfig = GridFieldConfig::create()->addComponents(
new GridFieldToolbarHeader(),

View File

@ -1,6 +1,9 @@
<?php
class DMSDocumentAddExistingField extends CompositeField {
public $useFieldContext = true;
function __construct($name, $title = null) {
$this->name = $name;
$this->title = ($title === null) ? $name : $title;
@ -41,6 +44,14 @@ class DMSDocumentAddExistingField extends CompositeField {
return $this->renderWith('DMSDocumentAddExistingField');
}
/**
* Sets or unsets the use of the "field" class in the template. The "field" class adds Javascript behaviour
* that causes unwelcome hiding side-effects when this Field is used within the link editor pop-up
*/
public function setUseFieldClass($use = false) {
$this->useFieldContext = $use;
}
}
?>

View File

@ -0,0 +1,40 @@
<?php
/**
* Extends the original toolbar with document picking capability - modified lines are commented.
*/
class DocumentHtmlEditorFieldToolbar extends Extension {
function updateLinkForm(Form $form) {
$linkType = null;
$fieldList = null;
$fields = $form->Fields();//->fieldByName('Heading');
foreach($fields as $field) {
$linkType = ($field->fieldByName('LinkType'));
$fieldList = $field;
if ($linkType) break; //break once we have the object
}
$source = $linkType->getSource();
$source['document'] = 'Download a document';
$linkType->setSource($source);
$addExistingField = new DMSDocumentAddExistingField('AddExisting', 'Add Existing');
$addExistingField->setForm($form);
$addExistingField->setUseFieldClass(false);
$fieldList->insertAfter($addExistingField,'Locale');
// Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/behaviour/behaviour.js");
// Requirements::javascript(SAPPHIRE_DIR . "/javascript/tiny_mce_improvements.js");
//
// // create additional field, rebase to 'documents' directory
// $documents = new TreeDropdownField('document', 'Document', 'File', 'ID', 'DocumentDropdownTitle', true);
// $documents->setSearchFunction(array($this, 'documentSearchCallback'));
// $baseFolder = Folder::find_or_make(Document::$directory);
// $documents->setTreeBaseID($baseFolder->ID);
//return $form;
}
}

View File

@ -0,0 +1,76 @@
<?php
/**
* Finds {@link DataObject} instances using certain shortcodes
* by fulltext-querying only fields which are capable of parsing shortcodes.
* Effectively the reverse of "link tracking",
* which updates this relation on write rather than fetching it on demand.
*
* Doesn't scale to millions of pages due to triggering a potentially unindexed LIKE
* search across dozens of columns and tables - but for a couple of hundred pages
* and occasionally use its a feasible solution.
*/
class ShortCodeRelationFinder {
/**
* @var String Regex matching a {@link DBField} class name which is shortcode capable.
*
* This should really look for implementors of a ShortCodeParseable interface,
* but we can't extend the core Text and HTMLText class
* on existing like SiteTree.Content for this.
*/
protected $fieldSpecRegex = '/^(HTMLText)/';
/**
* @param String Shortcode index number to find
* @return array IDs
*/
function findPageIDs($number) {
$list = $this->getList($number);
$found = $list->column();
return $found;
}
/**
* @return DataList
*/
function getList($number) {
$list = DataList::create('SiteTree');
$where = array();
$fields = $this->getShortCodeFields('SiteTree');
foreach($fields as $ancClass => $ancFields) {
foreach($ancFields as $ancFieldName => $ancFieldSpec) {
if ($ancClass != "SiteTree") $list = $list->leftJoin($ancClass,'"'.$ancClass.'"."ID" = "SiteTree"."ID"');
$where[] = "\"$ancClass\".\"$ancFieldName\" LIKE '%[dms_document_link,id=$number]%'"; //."%s" LIKE ""',
}
}
$list->where(implode(' OR ',$where));
return $list;
}
/**
* Returns a filtered list of fields which could contain shortcodes.
*
* @param String
* @return Array Map of class names to an array of field names on these classes.
*/
function getShortcodeFields($class) {
$fields = array();
$ancestry = array_values(ClassInfo::dataClassesFor($class));
foreach($ancestry as $ancestor) {
if(ClassInfo::classImplements($ancestor, 'TestOnly')) continue;
$ancFields = DataObject::custom_database_fields($ancestor);
if($ancFields) foreach($ancFields as $ancFieldName => $ancFieldSpec) {
if(preg_match($this->fieldSpecRegex, $ancFieldSpec)) {
if(!@$fields[$ancestor]) $fields[$ancestor] = array();
$fields[$ancestor][$ancFieldName] = $ancFieldSpec;
}
}
}
return $fields;
}
}

View File

@ -26,6 +26,17 @@
}
}
);
},
selectdocument: function(documentID, documentName) {
if (typeof(documentID) !== "undefined") {
//substitute the ID for the full document name, if no name is present
if (typeof(documentName) === "undefined") {
documentName = documentID;
}
$('.ss-add-files').html('<div class="selected-document" data-document-id="'+documentID+'">'+documentName+'</div>');
} else {
$('.ss-add-files').html('');
}
}
});
@ -36,10 +47,12 @@
source: 'admin/pages/adddocument/documentautocomplete',
select: function(event, ui) {
if(ui.item) {
var document_id = ui.item.value;
$(this).closest('.document-add-existing').adddocument(document_id);
$(this).val('');
if (self.closest('.document-add-existing').hasClass('link-editor-context')) {
$(this).closest('.document-add-existing').selectdocument(ui.item.value, ui.item.label);
} else {
$(this).closest('.document-add-existing').adddocument(ui.item.value);
$(this).val('');
}
return false;
}
@ -78,12 +91,19 @@
$('.document-add-existing a.add-document').entwine({
onclick: function(event) {
var document_id = $(this).data('document-id');
var document_id = this.data('document-id');
var dae = this.closest('.document-add-existing');
$(this).closest('.document-add-existing').adddocument(document_id);
$(this).closest('.document-list').hide();
$(this).closest('.document-add-existing').find('input.document-autocomplete').prop('disabled', false);
if (dae.hasClass('link-editor-context')) {
dae.selectdocument(document_id, this.text());
} else {
dae.adddocument(document_id);
}
return false;
}
});

View File

@ -0,0 +1,107 @@
(function($) {
"use strict";
$.entwine('ss', function($) {
$('form.htmleditorfield-linkform input[name=LinkType]').entwine({
onchange: function(e) {
this._super(e);
var form = $('form.htmleditorfield-linkform');
var show = false;
if (this.attr('value') === 'document') {
if (this.is(':checked')) {
show = true;
}
}
//hide or show the additional document link addition tool
if (show) {
form.find('.ss-add.ss-upload').show();
} else {
form.find('.ss-add.ss-upload').hide();
}
},
onadd: function(e){
this.change();
}
});
$('form.htmleditorfield-linkform').entwine({
insertLink: function() {
var href, target = null;
var checkedValue = this.find(':input[name=LinkType]:checked').val();
if (checkedValue === 'document') {
href = '[dms_document_link,id=' + this.find('.selected-document').data('document-id') + ']';
// Determine target
if(this.find(':input[name=TargetBlank]').is(':checked')) target = '_blank';
var attributes = {
href : href,
target : target,
title : this.find('.selected-document').text() //title is the text of the selected document
};
this.modifySelection(function(ed){
ed.insertLink(attributes);
});
this.updateFromEditor();
return false;
} else {
this._super();
}
},
getCurrentLink: function() {
var selectedEl = this.getSelection(), href = "", target = "", title = "", action = "insert", style_class = "";
var linkDataSource = null;
if(selectedEl.length) {
if(selectedEl.is('a')) {
linkDataSource = selectedEl;
} else {
linkDataSource = selectedEl = selectedEl.parents('a:first');
}
}
if(linkDataSource && linkDataSource.length) this.modifySelection(function(ed){
ed.selectNode(linkDataSource[0]);
});
// Is anchor not a link
if (!linkDataSource.attr('href')) linkDataSource = null;
if (linkDataSource) {
href = linkDataSource.attr('href');
target = linkDataSource.attr('target');
title = linkDataSource.attr('title');
style_class = linkDataSource.attr('class');
href = this.getEditor().cleanLink(href, linkDataSource);
action = "update";
}
//match a document or call the regular link handling
if(href.match(/^\[dms_document_link(\s*|%20|,)?id=([0-9]+)\]?$/i)) {
var returnArray = {
LinkType: 'document',
DocumentID: RegExp.$2,
Description: title
};
//show the selected document
$('.document-add-existing').selectdocument(returnArray.DocumentID,returnArray.Description);
//select the correct radio button
$('form.htmleditorfield-linkform input[name=LinkType][value=document]').click();
return returnArray;
} else {
$('.document-add-existing').selectdocument(); //clear the selected document
$('form.htmleditorfield-linkform .ss-add.ss-upload').hide();
return this._super();
}
}
});
});
}(jQuery));

View File

@ -1,9 +1,13 @@
<div class="ss-add ss-upload">
<div class="document-add-existing field">
<div class="document-add-existing <% if useFieldContext %>field<% else %>link-editor-context<% end_if %>">
<h3>
<span class="step-label">
<% if useFieldContext %>
<span class="flyout">1</span><span class="arrow"></span>
<% else %>
<span class="flyout">3</span><span class="arrow"></span>
<% end_if %>
<span class="title">Link a Document</span>
</span>
</h3>
@ -21,8 +25,14 @@
<div class="ss-assetuploadfield">
<h3>
<span class="step-label">
<% if useFieldContext %>
<span class="flyout">2</span><span class="arrow"></span>
<span class="title">Edit Document Details</span>
<% else %>
<span class="flyout">4</span><span class="arrow"></span>
<span class="title">Selected Document</span>
<% end_if %>
</span>
</h3>
<!-- <div class="fileOverview"></div> -->

View File

@ -0,0 +1,35 @@
<?php
class ShortCodeRelationFinderTest extends SapphireTest {
static $fixture_file = array(
'dms/tests/dmstest.yml'
);
function testFindInRate() {
$d1 = $this->objFromFixture('DMSDocument', 'd1');
$d2 = $this->objFromFixture('DMSDocument', 'd2');
$page1 = new SiteTree();
$page1->Content = 'Condition: <a title="document test 1" href="[dms_document_link,id='.$d1->ID.']">';
$page1ID = $page1->write();
$page2 = new SiteTree();
$page2->Content = 'Condition: <a title="document test 2" href="[dms_document_link,id='.$d2->ID.']">';
$page2ID = $page2->write();
$page3 = new CDSHolder();
$page3->TermsAndConditions = 'Condition: <a title="document test 1" href="[dms_document_link,id='.$d1->ID.']">';
$page3ID = $page3->write();
$finder = new ShortCodeRelationFinder();
$ids = $finder->findPageIDs('UnknownShortcode');
$this->assertEquals(0, count($ids));
$ids = $finder->findPageIDs($d1->ID);
$this->assertNotContains($page2ID, $ids);
$this->assertContains($page1ID, $ids);
$this->assertContains($page3ID, $ids);
}
}