mirror of
https://github.com/silverstripe/silverstripe-cms
synced 2024-10-01 05:39:31 +02:00
Merge pull request #1291 from open-sausages/pulls/4.0/dbfile-file-dataobject
API CMS Implementation of RFC-1 Asset Abstraction (DataObject Refactoring)
This commit is contained in:
commit
b487d59d02
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use SilverStripe\Filesystem\Storage\AssetNameGenerator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AssetAdmin is the 'file store' section of the CMS.
|
* AssetAdmin is the 'file store' section of the CMS.
|
||||||
* It provides an interface for manipulating the File and Folder objects in the system.
|
* It provides an interface for manipulating the File and Folder objects in the system.
|
||||||
@ -35,15 +38,8 @@ class AssetAdmin extends LeftAndMain implements PermissionProvider{
|
|||||||
'addfolder',
|
'addfolder',
|
||||||
'delete',
|
'delete',
|
||||||
'AddForm',
|
'AddForm',
|
||||||
'DeleteItemsForm',
|
|
||||||
'SearchForm',
|
'SearchForm',
|
||||||
'getsubtree',
|
'getsubtree'
|
||||||
'movemarked',
|
|
||||||
'removefile',
|
|
||||||
'savefile',
|
|
||||||
'deleteUnusedThumbnails' => 'ADMIN',
|
|
||||||
'doSync',
|
|
||||||
'filter',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,13 +58,11 @@ class AssetAdmin extends LeftAndMain implements PermissionProvider{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up the controller, in particular, re-sync the File database with the assets folder./
|
* Set up the controller
|
||||||
*/
|
*/
|
||||||
public function init() {
|
public function init() {
|
||||||
parent::init();
|
parent::init();
|
||||||
|
|
||||||
// Create base folder if it doesnt exist already
|
|
||||||
if(!file_exists(ASSETS_PATH)) Filesystem::makeFolder(ASSETS_PATH);
|
|
||||||
|
|
||||||
Requirements::javascript(CMS_DIR . "/javascript/AssetAdmin.js");
|
Requirements::javascript(CMS_DIR . "/javascript/AssetAdmin.js");
|
||||||
Requirements::javascript(CMS_DIR . '/javascript/CMSMain.GridField.js');
|
Requirements::javascript(CMS_DIR . '/javascript/CMSMain.GridField.js');
|
||||||
@ -210,20 +204,6 @@ JS
|
|||||||
$addFolderBtn = '';
|
$addFolderBtn = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if($folder->canEdit()) {
|
|
||||||
$syncButton = new LiteralField(
|
|
||||||
'SyncButton',
|
|
||||||
sprintf(
|
|
||||||
'<a class="ss-ui-button ss-ui-action ui-button-text-icon-primary ss-ui-button-ajax font-icon-sync" data-icon="arrow-circle-double" title="%s" href="%s">%s</a>',
|
|
||||||
_t('AssetAdmin.FILESYSTEMSYNCTITLE', 'Update the CMS database entries of files on the filesystem. Useful when new files have been uploaded outside of the CMS, e.g. through FTP.'),
|
|
||||||
$this->Link('doSync'),
|
|
||||||
_t('AssetAdmin.FILESYSTEMSYNC','Sync files')
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$syncButton = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move existing fields to a "details" tab, unless they've already been tabbed out through extensions.
|
// Move existing fields to a "details" tab, unless they've already been tabbed out through extensions.
|
||||||
// Required to keep Folder->getCMSFields() simple and reuseable,
|
// Required to keep Folder->getCMSFields() simple and reuseable,
|
||||||
// without any dependencies into AssetAdmin (e.g. useful for "add folder" views).
|
// without any dependencies into AssetAdmin (e.g. useful for "add folder" views).
|
||||||
@ -249,7 +229,6 @@ JS
|
|||||||
// the button shouldn't be available. Adding empty values into a ComposteField breaks template rendering.
|
// the button shouldn't be available. Adding empty values into a ComposteField breaks template rendering.
|
||||||
$actionButtonsComposite = CompositeField::create()->addExtraClass('cms-actions-row');
|
$actionButtonsComposite = CompositeField::create()->addExtraClass('cms-actions-row');
|
||||||
if($addFolderBtn) $actionButtonsComposite->push($addFolderBtn);
|
if($addFolderBtn) $actionButtonsComposite->push($addFolderBtn);
|
||||||
if($syncButton) $actionButtonsComposite->push($syncButton);
|
|
||||||
|
|
||||||
// Add the upload field for new media
|
// Add the upload field for new media
|
||||||
if($currentPageID = $this->currentPageID()){
|
if($currentPageID = $this->currentPageID()){
|
||||||
@ -266,9 +245,8 @@ JS
|
|||||||
$uploadField->removeExtraClass('ss-uploadfield');
|
$uploadField->removeExtraClass('ss-uploadfield');
|
||||||
$uploadField->setTemplate('AssetUploadField');
|
$uploadField->setTemplate('AssetUploadField');
|
||||||
|
|
||||||
if($folder->exists() && $folder->getFilename()) {
|
if($folder->exists()) {
|
||||||
// The Upload class expects a folder relative *within* assets/
|
$path = $folder->getFilename();
|
||||||
$path = preg_replace('/^' . ASSETS_DIR . '\//', '', $folder->getFilename());
|
|
||||||
$uploadField->setFolderName($path);
|
$uploadField->setFolderName($path);
|
||||||
} else {
|
} else {
|
||||||
$uploadField->setFolderName('/'); // root of the assets
|
$uploadField->setFolderName('/'); // root of the assets
|
||||||
@ -288,7 +266,6 @@ JS
|
|||||||
$gridField
|
$gridField
|
||||||
));
|
));
|
||||||
|
|
||||||
$treeField = new LiteralField('Tree', '');
|
|
||||||
// Tree view
|
// Tree view
|
||||||
$fields->addFieldsToTab('Root.TreeView', array(
|
$fields->addFieldsToTab('Root.TreeView', array(
|
||||||
clone $actionsComposite,
|
clone $actionsComposite,
|
||||||
@ -320,8 +297,6 @@ JS
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$fields->setForm($form);
|
$fields->setForm($form);
|
||||||
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
|
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
|
||||||
// TODO Can't merge $FormAttributes in template at the moment
|
// TODO Can't merge $FormAttributes in template at the moment
|
||||||
@ -353,8 +328,12 @@ JS
|
|||||||
$className = $this->stat('tree_class');
|
$className = $this->stat('tree_class');
|
||||||
|
|
||||||
$record = DataObject::get_by_id($className, $data['ID']);
|
$record = DataObject::get_by_id($className, $data['ID']);
|
||||||
if($record && !$record->canDelete()) return Security::permissionFailure();
|
if($record && !$record->canDelete()) {
|
||||||
if(!$record || !$record->ID) throw new SS_HTTPResponse_Exception("Bad record ID #" . (int)$data['ID'], 404);
|
return Security::permissionFailure();
|
||||||
|
}
|
||||||
|
if(!$record || !$record->ID) {
|
||||||
|
throw new SS_HTTPResponse_Exception("Bad record ID #" . (int)$data['ID'], 404);
|
||||||
|
}
|
||||||
$parentID = $record->ParentID;
|
$parentID = $record->ParentID;
|
||||||
$record->delete();
|
$record->delete();
|
||||||
$this->setCurrentPageID(null);
|
$this->setCurrentPageID(null);
|
||||||
@ -373,8 +352,12 @@ JS
|
|||||||
$context = singleton('File')->getDefaultSearchContext();
|
$context = singleton('File')->getDefaultSearchContext();
|
||||||
|
|
||||||
// Namespace fields, for easier detection if a search is present
|
// Namespace fields, for easier detection if a search is present
|
||||||
foreach($context->getFields() as $field) $field->setName(sprintf('q[%s]', $field->getName()));
|
foreach($context->getFields() as $field) {
|
||||||
foreach($context->getFilters() as $filter) $filter->setFullName(sprintf('q[%s]', $filter->getFullName()));
|
$field->setName(sprintf('q[%s]', $field->getName()));
|
||||||
|
}
|
||||||
|
foreach($context->getFilters() as $filter) {
|
||||||
|
$filter->setFullName(sprintf('q[%s]', $filter->getFullName()));
|
||||||
|
}
|
||||||
|
|
||||||
// Customize fields
|
// Customize fields
|
||||||
$dateHeader = HeaderField::create('q[Date]', _t('CMSSearch.FILTERDATEHEADING', 'Date'), 4);
|
$dateHeader = HeaderField::create('q[Date]', _t('CMSSearch.FILTERDATEHEADING', 'Date'), 4);
|
||||||
@ -389,12 +372,12 @@ JS
|
|||||||
);
|
);
|
||||||
$context->addField($dateGroup);
|
$context->addField($dateGroup);
|
||||||
$appCategories = array(
|
$appCategories = array(
|
||||||
'image' => _t('AssetAdmin.AppCategoryImage', 'Image'),
|
'archive' => _t('AssetAdmin.AppCategoryArchive', 'Archive', 'A collection of files'),
|
||||||
'audio' => _t('AssetAdmin.AppCategoryAudio', 'Audio'),
|
'audio' => _t('AssetAdmin.AppCategoryAudio', 'Audio'),
|
||||||
'mov' => _t('AssetAdmin.AppCategoryVideo', 'Video'),
|
'document' => _t('AssetAdmin.AppCategoryDocument', 'Document'),
|
||||||
'flash' => _t('AssetAdmin.AppCategoryFlash', 'Flash', 'The fileformat'),
|
'flash' => _t('AssetAdmin.AppCategoryFlash', 'Flash', 'The fileformat'),
|
||||||
'zip' => _t('AssetAdmin.AppCategoryArchive', 'Archive', 'A collection of files'),
|
'image' => _t('AssetAdmin.AppCategoryImage', 'Image'),
|
||||||
'doc' => _t('AssetAdmin.AppCategoryDocument', 'Document')
|
'video' => _t('AssetAdmin.AppCategoryVideo', 'Video'),
|
||||||
);
|
);
|
||||||
$context->addField(
|
$context->addField(
|
||||||
$typeDropdown = new DropdownField(
|
$typeDropdown = new DropdownField(
|
||||||
@ -444,7 +427,6 @@ JS
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function AddForm() {
|
public function AddForm() {
|
||||||
$folder = singleton('Folder');
|
|
||||||
$form = CMSForm::create(
|
$form = CMSForm::create(
|
||||||
$this,
|
$this,
|
||||||
'AddForm',
|
'AddForm',
|
||||||
@ -475,7 +457,9 @@ JS
|
|||||||
$class = $this->stat('tree_class');
|
$class = $this->stat('tree_class');
|
||||||
|
|
||||||
// check create permissions
|
// check create permissions
|
||||||
if(!singleton($class)->canCreate()) return Security::permissionFailure($this);
|
if(!singleton($class)->canCreate()) {
|
||||||
|
return Security::permissionFailure($this);
|
||||||
|
}
|
||||||
|
|
||||||
// check addchildren permissions
|
// check addchildren permissions
|
||||||
if(
|
if(
|
||||||
@ -485,44 +469,40 @@ JS
|
|||||||
&& $data['ParentID']
|
&& $data['ParentID']
|
||||||
) {
|
) {
|
||||||
$parentRecord = DataObject::get_by_id($class, $data['ParentID']);
|
$parentRecord = DataObject::get_by_id($class, $data['ParentID']);
|
||||||
if(
|
if($parentRecord->hasMethod('canAddChildren') && !$parentRecord->canAddChildren()) {
|
||||||
$parentRecord->hasMethod('canAddChildren')
|
return Security::permissionFailure($this);
|
||||||
&& !$parentRecord->canAddChildren()
|
}
|
||||||
) return Security::permissionFailure($this);
|
|
||||||
} else {
|
} else {
|
||||||
$parentRecord = null;
|
$parentRecord = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$parent = (isset($data['ParentID']) && is_numeric($data['ParentID'])) ? (int)$data['ParentID'] : 0;
|
// Check parent
|
||||||
$name = (isset($data['Name'])) ? basename($data['Name']) : _t('AssetAdmin.NEWFOLDER',"NewFolder");
|
$parentID = $parentRecord && $parentRecord->ID
|
||||||
if(!$parentRecord || !$parentRecord->ID) $parent = 0;
|
? (int)$parentRecord->ID
|
||||||
|
: 0;
|
||||||
|
// Build filename
|
||||||
|
$filename = isset($data['Name'])
|
||||||
|
? basename($data['Name'])
|
||||||
|
: _t('AssetAdmin.NEWFOLDER',"NewFolder");
|
||||||
|
if($parentRecord && $parentRecord->ID) {
|
||||||
|
$filename = $parentRecord->getFilename() . '/' . $filename;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the folder to be created
|
// Get the folder to be created
|
||||||
if($parentRecord && $parentRecord->ID) $filename = $parentRecord->FullPath . $name;
|
|
||||||
else $filename = ASSETS_PATH . '/' . $name;
|
|
||||||
|
|
||||||
// Actually create
|
// Ensure name is unique
|
||||||
if(!file_exists(ASSETS_PATH)) {
|
foreach($this->getNameGenerator($filename) as $filename) {
|
||||||
mkdir(ASSETS_PATH);
|
if(! File::find($filename) ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$record = new Folder();
|
// Create record
|
||||||
$record->ParentID = $parent;
|
$record = Folder::create();
|
||||||
|
$record->ParentID = $parentID;
|
||||||
$record->Name = $record->Title = basename($filename);
|
$record->Name = $record->Title = basename($filename);
|
||||||
|
|
||||||
// Ensure uniqueness
|
|
||||||
$i = 2;
|
|
||||||
$baseFilename = substr($record->Filename, 0, -1) . '-';
|
|
||||||
while(file_exists($record->FullPath)) {
|
|
||||||
$record->Filename = $baseFilename . $i . '/';
|
|
||||||
$i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
$record->Name = $record->Title = basename($record->Filename);
|
|
||||||
$record->write();
|
$record->write();
|
||||||
|
|
||||||
mkdir($record->FullPath);
|
|
||||||
chmod($record->FullPath, Filesystem::config()->file_create_mask);
|
|
||||||
|
|
||||||
if($parentRecord) {
|
if($parentRecord) {
|
||||||
return $this->redirect(Controller::join_links($this->Link('show'), $parentRecord->ID));
|
return $this->redirect(Controller::join_links($this->Link('show'), $parentRecord->ID));
|
||||||
@ -531,6 +511,17 @@ JS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an asset renamer for the given filename.
|
||||||
|
*
|
||||||
|
* @param string $filename Path name
|
||||||
|
* @return AssetNameGenerator
|
||||||
|
*/
|
||||||
|
protected function getNameGenerator($filename){
|
||||||
|
return Injector::inst()
|
||||||
|
->createWithArgs('AssetNameGenerator', array($filename));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom currentPage() method to handle opening the 'root' folder
|
* Custom currentPage() method to handle opening the 'root' folder
|
||||||
*/
|
*/
|
||||||
@ -560,117 +551,6 @@ JS
|
|||||||
return $this->getSiteTreeFor($this->stat('tree_class'), null, 'ChildFolders', 'numChildFolders');
|
return $this->getSiteTreeFor($this->stat('tree_class'), null, 'ChildFolders', 'numChildFolders');
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------------------//
|
|
||||||
|
|
||||||
// Data saving handlers
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Can be queried with an ajax request to trigger the filesystem sync. It returns a FormResponse status message
|
|
||||||
* to display in the CMS
|
|
||||||
*/
|
|
||||||
public function doSync() {
|
|
||||||
$message = Filesystem::sync();
|
|
||||||
$this->response->addHeader('X-Status', rawurlencode($message));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* #################################
|
|
||||||
* Garbage collection.
|
|
||||||
* #################################
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all unused thumbnails from the file store
|
|
||||||
* and returns the status of the process to the user.
|
|
||||||
*/
|
|
||||||
public function deleteunusedthumbnails($request) {
|
|
||||||
// Protect against CSRF on destructive action
|
|
||||||
if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
|
|
||||||
|
|
||||||
$count = 0;
|
|
||||||
$thumbnails = $this->getUnusedThumbnails();
|
|
||||||
|
|
||||||
if($thumbnails) {
|
|
||||||
foreach($thumbnails as $thumbnail) {
|
|
||||||
unlink(ASSETS_PATH . "/" . $thumbnail);
|
|
||||||
$count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$message = _t(
|
|
||||||
'AssetAdmin.THUMBSDELETED',
|
|
||||||
'{count} unused thumbnails have been deleted',
|
|
||||||
array('count' => $count)
|
|
||||||
);
|
|
||||||
$this->response->addHeader('X-Status', rawurlencode($message));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates array containg all unused thumbnails.
|
|
||||||
*
|
|
||||||
* Array is created in three steps:
|
|
||||||
* 1. Scan assets folder and retrieve all thumbnails
|
|
||||||
* 2. Scan all HTMLField in system and retrieve thumbnails from them.
|
|
||||||
* 3. Count difference between two sets (array_diff)
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function getUnusedThumbnails() {
|
|
||||||
$allThumbnails = array();
|
|
||||||
$usedThumbnails = array();
|
|
||||||
$dirIterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(ASSETS_PATH));
|
|
||||||
$classes = ClassInfo::subclassesFor('SiteTree');
|
|
||||||
|
|
||||||
if($dirIterator) {
|
|
||||||
foreach($dirIterator as $file) {
|
|
||||||
if($file->isFile()) {
|
|
||||||
if(strpos($file->getPathname(), '_resampled') !== false) {
|
|
||||||
$pathInfo = pathinfo($file->getPathname());
|
|
||||||
if(in_array(strtolower($pathInfo['extension']), array('jpeg', 'jpg', 'jpe', 'png', 'gif'))) {
|
|
||||||
$path = str_replace('\\','/', $file->getPathname());
|
|
||||||
$allThumbnails[] = substr($path, strpos($path, '/assets/') + 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if($classes) {
|
|
||||||
foreach($classes as $className) {
|
|
||||||
$SNG_class = singleton($className);
|
|
||||||
$objects = DataObject::get($className);
|
|
||||||
|
|
||||||
if($objects !== NULL) {
|
|
||||||
foreach($objects as $object) {
|
|
||||||
foreach($SNG_class->db() as $fieldName => $fieldType) {
|
|
||||||
if($fieldType == 'HTMLText') {
|
|
||||||
$url1 = HTTP::findByTagAndAttribute($object->$fieldName,array('img' => 'src'));
|
|
||||||
|
|
||||||
if($url1 != NULL) {
|
|
||||||
$usedThumbnails[] = substr($url1[0], strpos($url1[0], '/assets/') + 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
if($object->latestPublished > 0) {
|
|
||||||
$object = Versioned::get_latest_version($className, $object->ID);
|
|
||||||
$url2 = HTTP::findByTagAndAttribute($object->$fieldName, array('img' => 'src'));
|
|
||||||
|
|
||||||
if($url2 != NULL) {
|
|
||||||
$usedThumbnails[] = substr($url2[0], strpos($url2[0], '/assets/') + 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_diff($allThumbnails, $usedThumbnails);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param bool $unlinked
|
* @param bool $unlinked
|
||||||
* @return ArrayList
|
* @return ArrayList
|
||||||
|
@ -1764,10 +1764,12 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rewrite a file URL on this page, after its been renamed. Triggers the onRenameLinkedAsset action on extensions.
|
* Rewrites any linked images on this page.
|
||||||
|
* Non-image files should be linked via shortcodes
|
||||||
|
* Triggers the onRenameLinkedAsset action on extensions.
|
||||||
|
* TODO: This doesn't work for HTMLText fields on other tables.
|
||||||
*/
|
*/
|
||||||
public function rewriteFileURL($old, $new) {
|
public function rewriteFileLinks() {
|
||||||
$fields = $this->inheritedDatabaseFields();
|
|
||||||
// Update the content without actually creating a new version
|
// Update the content without actually creating a new version
|
||||||
foreach(array("SiteTree_Live", "SiteTree") as $table) {
|
foreach(array("SiteTree_Live", "SiteTree") as $table) {
|
||||||
// Published site
|
// Published site
|
||||||
@ -1777,15 +1779,20 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
|
|||||||
)->record();
|
)->record();
|
||||||
$origPublished = $published;
|
$origPublished = $published;
|
||||||
|
|
||||||
foreach($fields as $fieldName => $fieldType) {
|
foreach($this->db() as $fieldName => $fieldType) {
|
||||||
if ($fieldType != 'HTMLText') continue;
|
// Skip if non HTML or if empty
|
||||||
|
if ($fieldType !== 'HTMLText' || empty($published[$fieldName])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regenerate content
|
||||||
|
$content = Image::regenerate_html_links($published[$fieldName]);
|
||||||
|
if($content === $published[$fieldName]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: This doesn't work for HTMLText fields on other tables.
|
|
||||||
if(isset($published[$fieldName])) {
|
|
||||||
$published[$fieldName] = str_replace($old, $new, $published[$fieldName], $numReplaced);
|
|
||||||
if($numReplaced) {
|
|
||||||
$query = sprintf('UPDATE "%s" SET "%s" = ? WHERE "ID" = ?', $table, $fieldName);
|
$query = sprintf('UPDATE "%s" SET "%s" = ? WHERE "ID" = ?', $table, $fieldName);
|
||||||
DB::prepared_query($query, array($published[$fieldName], $this->ID));
|
DB::prepared_query($query, array($content, $this->ID));
|
||||||
|
|
||||||
// Tell static caching to update itself
|
// Tell static caching to update itself
|
||||||
if($table == 'SiteTree_Live') {
|
if($table == 'SiteTree_Live') {
|
||||||
@ -1796,8 +1803,6 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the pages that depend on this page. This includes virtual pages, pages that link to it, etc.
|
* Returns the pages that depend on this page. This includes virtual pages, pages that link to it, etc.
|
||||||
|
@ -14,7 +14,8 @@ class SiteTreeFileExtension extends DataExtension {
|
|||||||
ReadonlyField::create(
|
ReadonlyField::create(
|
||||||
'BackLinkCount',
|
'BackLinkCount',
|
||||||
_t('AssetTableField.BACKLINKCOUNT', 'Used on:'),
|
_t('AssetTableField.BACKLINKCOUNT', 'Used on:'),
|
||||||
$this->BackLinkTracking()->Count() . ' ' . _t('AssetTableField.PAGES', 'page(s)'))
|
$this->BackLinkTracking()->Count() . ' ' . _t('AssetTableField.PAGES', 'page(s)')
|
||||||
|
)
|
||||||
->addExtraClass('cms-description-toggle')
|
->addExtraClass('cms-description-toggle')
|
||||||
->setDescription($this->BackLinkHTMLList()),
|
->setDescription($this->BackLinkHTMLList()),
|
||||||
'LastEdited'
|
'LastEdited'
|
||||||
@ -27,19 +28,21 @@ class SiteTreeFileExtension extends DataExtension {
|
|||||||
* @return String
|
* @return String
|
||||||
*/
|
*/
|
||||||
public function BackLinkHTMLList() {
|
public function BackLinkHTMLList() {
|
||||||
$html = '<em>' . _t('SiteTreeFileExtension.BACKLINK_LIST_DESCRIPTION', 'This list shows all pages where the file has been added through a WYSIWYG editor.') . '</em>';
|
$html = '<em>' . _t(
|
||||||
|
'SiteTreeFileExtension.BACKLINK_LIST_DESCRIPTION',
|
||||||
|
'This list shows all pages where the file has been added through a WYSIWYG editor.'
|
||||||
|
) . '</em>';
|
||||||
$html .= '<ul>';
|
$html .= '<ul>';
|
||||||
|
|
||||||
foreach ($this->BackLinkTracking() as $backLink) {
|
foreach ($this->BackLinkTracking() as $backLink) {
|
||||||
$listItem = '<li>';
|
// Add the page link and CMS link
|
||||||
|
$html .= sprintf(
|
||||||
// Add the page link
|
'<li><a href="%s" target="_blank">%s</a> – <a href="%s">%s</a></li>',
|
||||||
$listItem .= '<a href="' . $backLink->Link() . '" target="_blank">' . Convert::raw2xml($backLink->MenuTitle) . '</a> – ';
|
Convert::raw2att($backLink->Link()),
|
||||||
|
Convert::raw2xml($backLink->MenuTitle),
|
||||||
// Add the CMS link
|
Convert::raw2att($backLink->CMSEditLink()),
|
||||||
$listItem .= '<a href="' . $backLink->CMSEditLink() . '">' . _t('SiteTreeFileExtension.EDIT', 'Edit') . '</a>';
|
_t('SiteTreeFileExtension.EDIT', 'Edit')
|
||||||
|
);
|
||||||
$html .= $listItem . '</li>';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $html .= '</ul>';
|
return $html .= '</ul>';
|
||||||
@ -54,31 +57,13 @@ class SiteTreeFileExtension extends DataExtension {
|
|||||||
* @param string $limit
|
* @param string $limit
|
||||||
* @return ManyManyList
|
* @return ManyManyList
|
||||||
*/
|
*/
|
||||||
public function BackLinkTracking($filter = null, $sort = null, $join = null, $limit = null) {
|
public function BackLinkTracking() {
|
||||||
if($filter !== null || $sort !== null || $join !== null || $limit !== null) {
|
|
||||||
Deprecation::notice('4.0', 'The $filter, $sort, $join and $limit parameters for
|
|
||||||
SiteTreeFileExtension::BackLinkTracking() have been deprecated.
|
|
||||||
Please manipluate the returned list directly.', Deprecation::SCOPE_GLOBAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(class_exists("Subsite")){
|
if(class_exists("Subsite")){
|
||||||
$rememberSubsiteFilter = Subsite::$disable_subsite_filter;
|
$rememberSubsiteFilter = Subsite::$disable_subsite_filter;
|
||||||
Subsite::disable_subsite_filter(true);
|
Subsite::disable_subsite_filter(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($filter || $sort || $join || $limit) {
|
|
||||||
Deprecation::notice('4.0', 'The $filter, $sort, $join and $limit parameters for
|
|
||||||
SiteTreeFileExtension::BackLinkTracking() have been deprecated.
|
|
||||||
Please manipluate the returned list directly.', Deprecation::SCOPE_GLOBAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
$links = $this->owner->getManyManyComponents('BackLinkTracking');
|
$links = $this->owner->getManyManyComponents('BackLinkTracking');
|
||||||
if($this->owner->ID) {
|
|
||||||
$links = $links
|
|
||||||
->where($filter)
|
|
||||||
->sort($sort)
|
|
||||||
->limit($limit);
|
|
||||||
}
|
|
||||||
$this->owner->extend('updateBackLinkTracking', $links);
|
$this->owner->extend('updateBackLinkTracking', $links);
|
||||||
|
|
||||||
if(class_exists("Subsite")){
|
if(class_exists("Subsite")){
|
||||||
@ -115,7 +100,9 @@ class SiteTreeFileExtension extends DataExtension {
|
|||||||
// This will syncLinkTracking on draft
|
// This will syncLinkTracking on draft
|
||||||
Versioned::reading_stage('Stage');
|
Versioned::reading_stage('Stage');
|
||||||
$brokenPages = DataObject::get('SiteTree')->byIDs($brokenPageIDs);
|
$brokenPages = DataObject::get('SiteTree')->byIDs($brokenPageIDs);
|
||||||
foreach($brokenPages as $brokenPage) $brokenPage->write();
|
foreach($brokenPages as $brokenPage) {
|
||||||
|
$brokenPage->write();
|
||||||
|
}
|
||||||
|
|
||||||
// This will syncLinkTracking on published
|
// This will syncLinkTracking on published
|
||||||
Versioned::reading_stage('Live');
|
Versioned::reading_stage('Live');
|
||||||
@ -131,22 +118,23 @@ class SiteTreeFileExtension extends DataExtension {
|
|||||||
/**
|
/**
|
||||||
* Rewrite links to the $old file to now point to the $new file.
|
* Rewrite links to the $old file to now point to the $new file.
|
||||||
*
|
*
|
||||||
* @uses SiteTree->rewriteFileURL()
|
* @uses SiteTree->rewriteFileID()
|
||||||
*
|
|
||||||
* @param String $old File path relative to the webroot
|
|
||||||
* @param String $new File path relative to the webroot
|
|
||||||
*/
|
*/
|
||||||
public function updateLinks($old, $new) {
|
public function updateLinks() {
|
||||||
if(class_exists('Subsite')) Subsite::disable_subsite_filter(true);
|
if(class_exists('Subsite')) {
|
||||||
|
Subsite::disable_subsite_filter(true);
|
||||||
|
}
|
||||||
|
|
||||||
$pages = $this->owner->BackLinkTracking();
|
$pages = $this->owner->BackLinkTracking();
|
||||||
|
|
||||||
$summary = "";
|
|
||||||
if($pages) {
|
if($pages) {
|
||||||
foreach($pages as $page) $page->rewriteFileURL($old,$new);
|
foreach($pages as $page) {
|
||||||
|
$page->rewriteFileLinks();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(class_exists('Subsite')) Subsite::disable_subsite_filter(false);
|
if(class_exists('Subsite')) {
|
||||||
|
Subsite::disable_subsite_filter(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,13 +12,13 @@
|
|||||||
* referenced in any HTMLText fields, and two booleans to indicate if there are any broken links. Call
|
* 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.
|
* augmentSyncLinkTracking to update those fields with any changes to those fields.
|
||||||
*
|
*
|
||||||
* @property SiteTree owner
|
* @property SiteTree $owner
|
||||||
*
|
*
|
||||||
* @property bool HasBrokenFile
|
* @property bool $HasBrokenFile
|
||||||
* @property bool HasBrokenLink
|
* @property bool $HasBrokenLink
|
||||||
*
|
*
|
||||||
* @method ManyManyList LinkTracking List of site pages linked on this page.
|
* @method ManyManyList LinkTracking() List of site pages linked on this page.
|
||||||
* @method ManyManyList ImageTracking List of Images linked on this page.
|
* @method ManyManyList ImageTracking() List of Images linked on this page.
|
||||||
*/
|
*/
|
||||||
class SiteTreeLinkTracking extends DataExtension {
|
class SiteTreeLinkTracking extends DataExtension {
|
||||||
|
|
||||||
@ -110,22 +110,25 @@ class SiteTreeLinkTracking extends DataExtension {
|
|||||||
|
|
||||||
// Add file tracking for image references
|
// Add file tracking for image references
|
||||||
if($images = $htmlValue->getElementsByTagName('img')) foreach($images as $img) {
|
if($images = $htmlValue->getElementsByTagName('img')) foreach($images as $img) {
|
||||||
if($image = File::find($path = urldecode(Director::makeRelative($img->getAttribute('src'))))) {
|
// {@see HtmlEditorField} for data-fileid source
|
||||||
|
$fileID = $img->getAttribute('data-fileid');
|
||||||
|
if(!$fileID) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assuming a local file is linked, check if it's valid
|
||||||
|
if($image = File::get()->byID($fileID)) {
|
||||||
$linkedFiles[] = $image->ID;
|
$linkedFiles[] = $image->ID;
|
||||||
} else {
|
} else {
|
||||||
if(substr($path, 0, strlen(ASSETS_DIR) + 1) == ASSETS_DIR . '/') {
|
|
||||||
$record->HasBrokenFile = true;
|
$record->HasBrokenFile = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Update the "LinkTracking" many_many
|
// Update the "LinkTracking" many_many
|
||||||
if($record->ID && $record->manyManyComponent('LinkTracking') && $tracker = $record->LinkTracking()) {
|
if($record->ID && $record->manyManyComponent('LinkTracking') && ($tracker = $record->LinkTracking())) {
|
||||||
$tracker->removeByFilter(sprintf(
|
$tracker->removeByFilter(array(
|
||||||
'"FieldName" = \'%s\' AND "%s" = %d',
|
sprintf('"FieldName" = ? AND "%s" = ?', $tracker->getForeignKey())
|
||||||
$fieldName,
|
=> array($fieldName, $record->ID)
|
||||||
$tracker->getForeignKey(),
|
|
||||||
$record->ID
|
|
||||||
));
|
));
|
||||||
|
|
||||||
if($linkedPages) foreach($linkedPages as $item) {
|
if($linkedPages) foreach($linkedPages as $item) {
|
||||||
@ -134,12 +137,10 @@ class SiteTreeLinkTracking extends DataExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the "ImageTracking" many_many
|
// Update the "ImageTracking" many_many
|
||||||
if($record->ID && $record->manyManyComponent('ImageTracking') && $tracker = $record->ImageTracking()) {
|
if($record->ID && $record->manyManyComponent('ImageTracking') && ($tracker = $record->ImageTracking())) {
|
||||||
$tracker->removeByFilter(sprintf(
|
$tracker->removeByFilter(array(
|
||||||
'"FieldName" = \'%s\' AND "%s" = %d',
|
sprintf('"FieldName" = ? AND "%s" = ?', $tracker->getForeignKey())
|
||||||
$fieldName,
|
=> array($fieldName, $record->ID)
|
||||||
$tracker->getForeignKey(),
|
|
||||||
$record->ID
|
|
||||||
));
|
));
|
||||||
|
|
||||||
if($linkedFiles) foreach($linkedFiles as $item) {
|
if($linkedFiles) foreach($linkedFiles as $item) {
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* @package cms
|
|
||||||
* @subpackage assets
|
|
||||||
*/
|
|
||||||
class FilesystemSyncTask extends BuildTask {
|
|
||||||
|
|
||||||
protected $title = "Sync Files & Images assets";
|
|
||||||
|
|
||||||
protected $description = "The Files & Images system in SilverStripe maintains its own database
|
|
||||||
of the contents of the assets/ folder. This action will update that database, and
|
|
||||||
should be called whenever files are added to the assets/ folder from outside
|
|
||||||
SilverStripe, for example, if an author uploads files via FTP.";
|
|
||||||
|
|
||||||
public function run($request) {
|
|
||||||
if(isset($_GET['folderID'])) {
|
|
||||||
$folderID = $_GET['folderID'];
|
|
||||||
} else {
|
|
||||||
$folderID = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo Filesystem::sync($folderID, !($request->getVar('skipSyncLinkTracking'))) . "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -32,7 +32,7 @@ So that I can link to a external website or a page on my site
|
|||||||
And I fill in the "internal" dropdown with "Details"
|
And I fill in the "internal" dropdown with "Details"
|
||||||
And I wait for 1 second
|
And I wait for 1 second
|
||||||
And I select "youranchor" from "Form_EditorToolbarLinkForm_AnchorSelector"
|
And I select "youranchor" from "Form_EditorToolbarLinkForm_AnchorSelector"
|
||||||
And I press the "Insert link" button
|
And I press the "Insert" button
|
||||||
Then the "Content" HTML field should contain "<a href="[sitetree_link,id=3]#youranchor">awesome</a>"
|
Then the "Content" HTML field should contain "<a href="[sitetree_link,id=3]#youranchor">awesome</a>"
|
||||||
# Required to avoid "unsaved changes" browser dialog
|
# Required to avoid "unsaved changes" browser dialog
|
||||||
Then I press the "Save draft" button
|
Then I press the "Save draft" button
|
||||||
@ -53,8 +53,8 @@ So that I can link to a external website or a page on my site
|
|||||||
When I press the "Insert Link" button
|
When I press the "Insert Link" button
|
||||||
When I select the "Download a file" radio button
|
When I select the "Download a file" radio button
|
||||||
And I attach the file "testfile.jpg" to "file[Uploads][]" with HTML5
|
And I attach the file "testfile.jpg" to "file[Uploads][]" with HTML5
|
||||||
And I press the "Insert link" button
|
And I press the "Insert" button
|
||||||
Then the "Content" HTML field should contain "<a href="[file_link,id=3]" target="_blank">awesome</a>"
|
Then the "Content" HTML field should contain "<a href="[file_link,id=4]" target="_blank">awesome</a>"
|
||||||
# Required to avoid "unsaved changes" browser dialog
|
# Required to avoid "unsaved changes" browser dialog
|
||||||
Then I press the "Save draft" button
|
Then I press the "Save draft" button
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ So that I can link to a external website or a page on my site
|
|||||||
When I press the "Insert Link" button
|
When I press the "Insert Link" button
|
||||||
When I select the "Anchor on this page" radio button
|
When I select the "Anchor on this page" radio button
|
||||||
And I select "myanchor" from "Form_EditorToolbarLinkForm_AnchorSelector"
|
And I select "myanchor" from "Form_EditorToolbarLinkForm_AnchorSelector"
|
||||||
And I press the "Insert link" button
|
And I press the "Insert" button
|
||||||
Then the "Content" HTML field should contain "<a href="#myanchor">awesome</a>"
|
Then the "Content" HTML field should contain "<a href="#myanchor">awesome</a>"
|
||||||
# Required to avoid "unsaved changes" browser dialog
|
# Required to avoid "unsaved changes" browser dialog
|
||||||
Then I press the "Save draft" button
|
Then I press the "Save draft" button
|
||||||
@ -77,7 +77,7 @@ So that I can link to a external website or a page on my site
|
|||||||
Then the "Form_EditorToolbarLinkForm_external" field should contain "http://silverstripe.org"
|
Then the "Form_EditorToolbarLinkForm_external" field should contain "http://silverstripe.org"
|
||||||
# This doesn't seem to suffer from that issue
|
# This doesn't seem to suffer from that issue
|
||||||
When I fill in "http://google.com" for "URL"
|
When I fill in "http://google.com" for "URL"
|
||||||
And I press the "Insert link" button
|
And I press the "Insert" button
|
||||||
Then the "Content" HTML field should contain "<a href="http://google.com">awesome</a>"
|
Then the "Content" HTML field should contain "<a href="http://google.com">awesome</a>"
|
||||||
# Required to avoid "unsaved changes" browser dialog
|
# Required to avoid "unsaved changes" browser dialog
|
||||||
Then I press the "Save draft" button
|
Then I press the "Save draft" button
|
||||||
|
@ -18,7 +18,7 @@ Feature: Insert an image into a page
|
|||||||
When I click "add by URL" in the ".ss-uploadfield-item-info" element
|
When I click "add by URL" in the ".ss-uploadfield-item-info" element
|
||||||
And I fill in "RemoteURL" with "http://www.silverstripe.org/themes/ssv3/img/ss_logo.png"
|
And I fill in "RemoteURL" with "http://www.silverstripe.org/themes/ssv3/img/ss_logo.png"
|
||||||
And I press the "Add url" button
|
And I press the "Add url" button
|
||||||
Then I should see "ss_logo.png (www.silverstripe.org)" in the ".ss-assetuploadfield span.name" element
|
Then I should see "ss_logo.png" in the ".ss-assetuploadfield span.name" element
|
||||||
|
|
||||||
When I press the "Insert" button
|
When I press the "Insert" button
|
||||||
Then the "Content" HTML field should contain "ss_logo.png"
|
Then the "Content" HTML field should contain "ss_logo.png"
|
||||||
@ -31,9 +31,9 @@ Feature: Insert an image into a page
|
|||||||
And I attach the file "testfile.jpg" to "AssetUploadField" with HTML5
|
And I attach the file "testfile.jpg" to "AssetUploadField" with HTML5
|
||||||
# TODO Delay previous step until upload succeeded
|
# TODO Delay previous step until upload succeeded
|
||||||
And I wait for 2 seconds
|
And I wait for 2 seconds
|
||||||
Then there should be a file "assets/Uploads/testfile.jpg"
|
Then there should be a file "assets/Uploads/59de0c841f/testfile.jpg"
|
||||||
When I press the "Insert" button
|
When I press the "Insert" button
|
||||||
Then the "Content" HTML field should contain "testfile.jpg"
|
Then the "Content" HTML field should contain "testfile__Resampled.jpg"
|
||||||
# Required to avoid "unsaved changed" browser dialog
|
# Required to avoid "unsaved changed" browser dialog
|
||||||
Then I press the "Save draft" button
|
Then I press the "Save draft" button
|
||||||
|
|
||||||
@ -45,10 +45,10 @@ Feature: Insert an image into a page
|
|||||||
# TODO Delay previous step until upload succeeded
|
# TODO Delay previous step until upload succeeded
|
||||||
And I wait for 2 seconds
|
And I wait for 2 seconds
|
||||||
# Note change in default behaviour from 3.1, respect default Upload.replaceFile=false
|
# Note change in default behaviour from 3.1, respect default Upload.replaceFile=false
|
||||||
Then there should be a file "assets/Uploads/file1.jpg"
|
Then there should be a file "assets/Uploads/3d0ef6ec37/file1.jpg"
|
||||||
And there should be a file "assets/Uploads/file1-v2.jpg"
|
And there should be a file "assets/Uploads/3d0ef6ec37/file1-v2.jpg"
|
||||||
When I press the "Insert" button
|
When I press the "Insert" button
|
||||||
Then the "Content" HTML field should contain "file1-v2.jpg"
|
Then the "Content" HTML field should contain "file1-v2__Resampled.jpg"
|
||||||
# Required to avoid "unsaved changed" browser dialog
|
# Required to avoid "unsaved changed" browser dialog
|
||||||
Then I press the "Save draft" button
|
Then I press the "Save draft" button
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ Feature: Insert an image into a page
|
|||||||
And I fill in the "ParentID" dropdown with "folder1"
|
And I fill in the "ParentID" dropdown with "folder1"
|
||||||
And I click on "file1" in the "Files" table
|
And I click on "file1" in the "Files" table
|
||||||
When I press the "Insert" button
|
When I press the "Insert" button
|
||||||
Then the "Content" HTML field should contain "file1.jpg"
|
Then the "Content" HTML field should contain "file1__Resampled.jpg"
|
||||||
# Required to avoid "unsaved changed" browser dialog
|
# Required to avoid "unsaved changed" browser dialog
|
||||||
Then I press the "Save draft" button
|
Then I press the "Save draft" button
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ Feature: Insert an image into a page
|
|||||||
And I press the "Edit" button
|
And I press the "Edit" button
|
||||||
When I fill in "Alternative text (alt)" with "My alt"
|
When I fill in "Alternative text (alt)" with "My alt"
|
||||||
And I press the "Insert" button
|
And I press the "Insert" button
|
||||||
Then the "Content" HTML field should contain "file1.jpg"
|
Then the "Content" HTML field should contain "file1__Resampled.jpg"
|
||||||
And the "Content" HTML field should contain "My alt"
|
And the "Content" HTML field should contain "My alt"
|
||||||
# Required to avoid "unsaved changed" browser dialog
|
# Required to avoid "unsaved changed" browser dialog
|
||||||
Then I press the "Save draft" button
|
Then I press the "Save draft" button
|
||||||
@ -76,14 +76,14 @@ Feature: Insert an image into a page
|
|||||||
# TODO This needs to support using drag handles, as we no longer have 'Width' or 'Height' input fields
|
# TODO This needs to support using drag handles, as we no longer have 'Width' or 'Height' input fields
|
||||||
@todo
|
@todo
|
||||||
Scenario: I can edit dimensions of an existing image
|
Scenario: I can edit dimensions of an existing image
|
||||||
Given the "page" "About us" contains "<img src=assets/folder1/file1.jpg>"
|
Given the "page" "About us" contains "<img src=assets/folder1/3d0ef6ec37/file1.jpg>"
|
||||||
And I reload the current page
|
And I reload the current page
|
||||||
When I highlight "<img src=assets/folder1/file1.jpg>" in the "Content" HTML field
|
When I highlight "<img src=assets/folder1/3d0ef6ec37/file1.jpg>" in the "Content" HTML field
|
||||||
And I press the "Insert Media" button
|
And I press the "Insert Media" button
|
||||||
Then I should see "file1.jpg"
|
Then I should see "file1.jpg"
|
||||||
When I fill in "Width" with "10"
|
When I fill in "Width" with "10"
|
||||||
When I fill in "Height" with "20"
|
When I fill in "Height" with "20"
|
||||||
And I press the "Insert" button
|
And I press the "Insert" button
|
||||||
Then the "Content" HTML field should contain "<img src=assets/folder1/file1.jpg width=10 height=20>"
|
Then the "Content" HTML field should contain "<img src=assets/folder1/3d0ef6ec37/file1.jpg width=10 height=20>"
|
||||||
# Required to avoid "unsaved changed" browser dialog
|
# Required to avoid "unsaved changed" browser dialog
|
||||||
Then I press the "Save draft" button
|
Then I press the "Save draft" button
|
||||||
|
@ -6,7 +6,7 @@ Feature: Manage files
|
|||||||
|
|
||||||
Background:
|
Background:
|
||||||
Given a "image" "assets/folder1/file1.jpg" was created "2012-01-01 12:00:00"
|
Given a "image" "assets/folder1/file1.jpg" was created "2012-01-01 12:00:00"
|
||||||
And a "image" "assets/folder1/folder1.1/file2.jpg" was created "2010-01-01 12:00:00"
|
And a "image" "assets/folder1/folder1-1/file2.jpg" was created "2010-01-01 12:00:00"
|
||||||
And a "folder" "assets/folder2"
|
And a "folder" "assets/folder2"
|
||||||
And I am logged in with "ADMIN" permissions
|
And I am logged in with "ADMIN" permissions
|
||||||
And I go to "/admin/assets"
|
And I go to "/admin/assets"
|
||||||
@ -21,7 +21,7 @@ Feature: Manage files
|
|||||||
Scenario: I can list files in a folder
|
Scenario: I can list files in a folder
|
||||||
Given I click on "folder1" in the "Files" table
|
Given I click on "folder1" in the "Files" table
|
||||||
Then the "folder1" table should contain "file1"
|
Then the "folder1" table should contain "file1"
|
||||||
And the "folder1" table should not contain "file1.1"
|
And the "folder1" table should not contain "file1-1"
|
||||||
|
|
||||||
Scenario: I can upload a file to a folder
|
Scenario: I can upload a file to a folder
|
||||||
Given I click on "folder1" in the "Files" table
|
Given I click on "folder1" in the "Files" table
|
||||||
|
@ -8,40 +8,52 @@ class FileLinkTrackingTest extends SapphireTest {
|
|||||||
|
|
||||||
public function setUp() {
|
public function setUp() {
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
AssetStoreTest_SpyStore::activate('FileLinkTrackingTest');
|
||||||
$this->logInWithPermission('ADMIN');
|
$this->logInWithPermission('ADMIN');
|
||||||
|
|
||||||
if(!file_exists(ASSETS_PATH)) mkdir(ASSETS_PATH);
|
// Write file contents
|
||||||
$fh = fopen(ASSETS_PATH . '/testscript-test-file.pdf', "w");
|
$files = File::get()->exclude('ClassName', 'Folder');
|
||||||
fwrite($fh, str_repeat('x',1000000));
|
foreach($files as $file) {
|
||||||
fclose($fh);
|
$destPath = AssetStoreTest_SpyStore::getLocalPath($file);
|
||||||
|
Filesystem::makeFolder(dirname($destPath));
|
||||||
|
file_put_contents($destPath, str_repeat('x', 1000000));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we can't hard-code IDs, manually inject image tracking shortcode
|
||||||
|
$imageID = $this->idFromFixture('Image', 'file1');
|
||||||
|
$page = $this->objFromFixture('Page', 'page1');
|
||||||
|
$page->Content = sprintf(
|
||||||
|
'<p><img src="/assets/FileLinkTrackingTest/55b443b601/testscript-test-file.jpg" data-fileid="%d" /></p>',
|
||||||
|
$imageID
|
||||||
|
);
|
||||||
|
$page->write();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown() {
|
public function tearDown() {
|
||||||
|
AssetStoreTest_SpyStore::reset();
|
||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
$testFiles = array(
|
|
||||||
'/testscript-test-file.pdf',
|
|
||||||
'/renamed-test-file.pdf',
|
|
||||||
'/renamed-test-file-second-time.pdf',
|
|
||||||
);
|
|
||||||
foreach($testFiles as $file) {
|
|
||||||
if(file_exists(ASSETS_PATH . $file)) unlink(ASSETS_PATH . $file);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFileRenameUpdatesDraftAndPublishedPages() {
|
public function testFileRenameUpdatesDraftAndPublishedPages() {
|
||||||
$page = $this->objFromFixture('Page', 'page1');
|
$page = $this->objFromFixture('Page', 'page1');
|
||||||
$this->assertTrue($page->doPublish());
|
$this->assertTrue($page->doPublish());
|
||||||
$this->assertContains('<img src="assets/testscript-test-file.pdf"',
|
$this->assertContains(
|
||||||
DB::prepared_query("SELECT \"Content\" FROM \"SiteTree_Live\" WHERE \"ID\" = ?", array($page->ID))->value());
|
'<img src="/assets/FileLinkTrackingTest/55b443b601/testscript-test-file.jpg"',
|
||||||
|
DB::prepared_query("SELECT \"Content\" FROM \"SiteTree_Live\" WHERE \"ID\" = ?", array($page->ID))->value()
|
||||||
|
);
|
||||||
|
|
||||||
$file = $this->objFromFixture('File', 'file1');
|
$file = $this->objFromFixture('Image', 'file1');
|
||||||
$file->Name = 'renamed-test-file.pdf';
|
$file->Name = 'renamed-test-file.jpg';
|
||||||
$file->write();
|
$file->write();
|
||||||
|
|
||||||
$this->assertContains('<img src="assets/renamed-test-file.pdf"',
|
$this->assertContains(
|
||||||
DB::prepared_query("SELECT \"Content\" FROM \"SiteTree\" WHERE \"ID\" = ?", array($page->ID))->value());
|
'<img src="/assets/FileLinkTrackingTest/55b443b601/renamed-test-file.jpg"',
|
||||||
$this->assertContains('<img src="assets/renamed-test-file.pdf"',
|
DB::prepared_query("SELECT \"Content\" FROM \"SiteTree\" WHERE \"ID\" = ?", array($page->ID))->value()
|
||||||
DB::prepared_query("SELECT \"Content\" FROM \"SiteTree_Live\" WHERE \"ID\" = ?", array($page->ID))->value());
|
);
|
||||||
|
$this->assertContains(
|
||||||
|
'<img src="/assets/FileLinkTrackingTest/55b443b601/renamed-test-file.jpg"',
|
||||||
|
DB::prepared_query("SELECT \"Content\" FROM \"SiteTree_Live\" WHERE \"ID\" = ?", array($page->ID))->value()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFileLinkRewritingOnVirtualPages() {
|
public function testFileLinkRewritingOnVirtualPages() {
|
||||||
@ -56,15 +68,19 @@ class FileLinkTrackingTest extends SapphireTest {
|
|||||||
$svp->doPublish();
|
$svp->doPublish();
|
||||||
|
|
||||||
// Rename the file
|
// Rename the file
|
||||||
$file = $this->objFromFixture('File', 'file1');
|
$file = $this->objFromFixture('Image', 'file1');
|
||||||
$file->Name = 'renamed-test-file.pdf';
|
$file->Name = 'renamed-test-file.jpg';
|
||||||
$file->write();
|
$file->write();
|
||||||
|
|
||||||
// Verify that the draft and publish virtual pages both have the corrected link
|
// Verify that the draft and publish virtual pages both have the corrected link
|
||||||
$this->assertContains('<img src="assets/renamed-test-file.pdf"',
|
$this->assertContains(
|
||||||
DB::prepared_query("SELECT \"Content\" FROM \"SiteTree\" WHERE \"ID\" = ?", array($svp->ID))->value());
|
'<img src="/assets/FileLinkTrackingTest/55b443b601/renamed-test-file.jpg"',
|
||||||
$this->assertContains('<img src="assets/renamed-test-file.pdf"',
|
DB::prepared_query("SELECT \"Content\" FROM \"SiteTree\" WHERE \"ID\" = ?", array($svp->ID))->value()
|
||||||
DB::prepared_query("SELECT \"Content\" FROM \"SiteTree_Live\" WHERE \"ID\" = ?", array($svp->ID))->value());
|
);
|
||||||
|
$this->assertContains(
|
||||||
|
'<img src="/assets/FileLinkTrackingTest/55b443b601/renamed-test-file.jpg"',
|
||||||
|
DB::prepared_query("SELECT \"Content\" FROM \"SiteTree_Live\" WHERE \"ID\" = ?", array($svp->ID))->value()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLinkRewritingOnAPublishedPageDoesntMakeItEditedOnDraft() {
|
public function testLinkRewritingOnAPublishedPageDoesntMakeItEditedOnDraft() {
|
||||||
@ -74,8 +90,8 @@ class FileLinkTrackingTest extends SapphireTest {
|
|||||||
$this->assertFalse($page->getIsModifiedOnStage());
|
$this->assertFalse($page->getIsModifiedOnStage());
|
||||||
|
|
||||||
// Rename the file
|
// Rename the file
|
||||||
$file = $this->objFromFixture('File', 'file1');
|
$file = $this->objFromFixture('Image', 'file1');
|
||||||
$file->Name = 'renamed-test-file.pdf';
|
$file->Name = 'renamed-test-file.jpg';
|
||||||
$file->write();
|
$file->write();
|
||||||
|
|
||||||
// Caching hack
|
// Caching hack
|
||||||
@ -89,25 +105,30 @@ class FileLinkTrackingTest extends SapphireTest {
|
|||||||
public function testTwoFileRenamesInARowWork() {
|
public function testTwoFileRenamesInARowWork() {
|
||||||
$page = $this->objFromFixture('Page', 'page1');
|
$page = $this->objFromFixture('Page', 'page1');
|
||||||
$this->assertTrue($page->doPublish());
|
$this->assertTrue($page->doPublish());
|
||||||
$this->assertContains('<img src="assets/testscript-test-file.pdf"',
|
$this->assertContains(
|
||||||
|
'<img src="/assets/FileLinkTrackingTest/55b443b601/testscript-test-file.jpg"',
|
||||||
DB::prepared_query("SELECT \"Content\" FROM \"SiteTree_Live\" WHERE \"ID\" = ?", array($page->ID))->value());
|
DB::prepared_query("SELECT \"Content\" FROM \"SiteTree_Live\" WHERE \"ID\" = ?", array($page->ID))->value());
|
||||||
|
|
||||||
// Rename the file twice
|
// Rename the file twice
|
||||||
$file = $this->objFromFixture('File', 'file1');
|
$file = $this->objFromFixture('Image', 'file1');
|
||||||
$file->Name = 'renamed-test-file.pdf';
|
$file->Name = 'renamed-test-file.jpg';
|
||||||
$file->write();
|
$file->write();
|
||||||
|
|
||||||
// TODO Workaround for bug in DataObject->getChangedFields(), which returns stale data,
|
// TODO Workaround for bug in DataObject->getChangedFields(), which returns stale data,
|
||||||
// and influences File->updateFilesystem()
|
// and influences File->updateFilesystem()
|
||||||
$file = DataObject::get_by_id('File', $file->ID);
|
$file = DataObject::get_by_id('File', $file->ID);
|
||||||
$file->Name = 'renamed-test-file-second-time.pdf';
|
$file->Name = 'renamed-test-file-second-time.jpg';
|
||||||
$file->write();
|
$file->write();
|
||||||
|
|
||||||
// Confirm that the correct image is shown in both the draft and live site
|
// Confirm that the correct image is shown in both the draft and live site
|
||||||
$this->assertContains('<img src="assets/renamed-test-file-second-time.pdf"',
|
$this->assertContains(
|
||||||
DB::prepared_query("SELECT \"Content\" FROM \"SiteTree\" WHERE \"ID\" = ?", array($page->ID))->value());
|
'<img src="/assets/FileLinkTrackingTest/55b443b601/renamed-test-file-second-time.jpg"',
|
||||||
$this->assertContains('<img src="assets/renamed-test-file-second-time.pdf"',
|
DB::prepared_query("SELECT \"Content\" FROM \"SiteTree\" WHERE \"ID\" = ?", array($page->ID))->value()
|
||||||
DB::prepared_query("SELECT \"Content\" FROM \"SiteTree_Live\" WHERE \"ID\" = ?", array($page->ID))->value());
|
);
|
||||||
|
$this->assertContains(
|
||||||
|
'<img src="/assets/FileLinkTrackingTest/55b443b601/renamed-test-file-second-time.jpg"',
|
||||||
|
DB::prepared_query("SELECT \"Content\" FROM \"SiteTree_Live\" WHERE \"ID\" = ?", array($page->ID))->value()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
# These need to come first so that SiteTree has the link meta-data written.
|
# These need to come first so that SiteTree has the link meta-data written.
|
||||||
File:
|
Image:
|
||||||
file1:
|
file1:
|
||||||
Filename: assets/testscript-test-file.pdf
|
FileFilename: testscript-test-file.jpg
|
||||||
|
FileHash: 55b443b60176235ef09801153cca4e6da7494a0c
|
||||||
|
Name: testscript-test-file.jpg
|
||||||
|
|
||||||
Page:
|
Page:
|
||||||
page1:
|
page1:
|
||||||
Title: page1
|
Title: page1
|
||||||
URLSegment: page1
|
URLSegment: page1
|
||||||
Content: <p><img src="assets/testscript-test-file.pdf" /></p>
|
Content: '<p><img src="/assets/FileLinkTrackingTest/55b443b601/testscript-test-file.jpg" /></p>'
|
||||||
|
@ -4,6 +4,25 @@ class SiteTreeHtmlEditorFieldTest extends FunctionalTest {
|
|||||||
|
|
||||||
protected static $use_draft_site = true;
|
protected static $use_draft_site = true;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
parent::setUp();
|
||||||
|
AssetStoreTest_SpyStore::activate('SiteTreeHtmlEditorFieldTest');
|
||||||
|
$this->logInWithPermission('ADMIN');
|
||||||
|
|
||||||
|
// Write file contents
|
||||||
|
$files = File::get()->exclude('ClassName', 'Folder');
|
||||||
|
foreach($files as $file) {
|
||||||
|
$destPath = AssetStoreTest_SpyStore::getLocalPath($file);
|
||||||
|
Filesystem::makeFolder(dirname($destPath));
|
||||||
|
file_put_contents($destPath, str_repeat('x', 1000000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown() {
|
||||||
|
AssetStoreTest_SpyStore::reset();
|
||||||
|
parent::tearDown();
|
||||||
|
}
|
||||||
|
|
||||||
public function testLinkTracking() {
|
public function testLinkTracking() {
|
||||||
$sitetree = $this->objFromFixture('SiteTree', 'home');
|
$sitetree = $this->objFromFixture('SiteTree', 'home');
|
||||||
$editor = new HtmlEditorField('Content');
|
$editor = new HtmlEditorField('Content');
|
||||||
@ -103,20 +122,23 @@ class SiteTreeHtmlEditorFieldTest extends FunctionalTest {
|
|||||||
public function testImageTracking() {
|
public function testImageTracking() {
|
||||||
$sitetree = $this->objFromFixture('SiteTree', 'home');
|
$sitetree = $this->objFromFixture('SiteTree', 'home');
|
||||||
$editor = new HtmlEditorField('Content');
|
$editor = new HtmlEditorField('Content');
|
||||||
$fileID = $this->idFromFixture('Image', 'example_image');
|
$file = $this->objFromFixture('Image', 'example_image');
|
||||||
|
|
||||||
$editor->setValue('<img src="assets/example.jpg" />');
|
$editor->setValue(sprintf('<img src="%s" data-fileid="%d" />', $file->getURL(), $file->ID));
|
||||||
$editor->saveInto($sitetree);
|
$editor->saveInto($sitetree);
|
||||||
$sitetree->write();
|
$sitetree->write();
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
array($fileID => $fileID), $sitetree->ImageTracking()->getIDList(), 'Inserted images are tracked.'
|
array($file->ID => $file->ID),
|
||||||
|
$sitetree->ImageTracking()->getIDList(),
|
||||||
|
'Inserted images are tracked.'
|
||||||
);
|
);
|
||||||
|
|
||||||
$editor->setValue(null);
|
$editor->setValue(null);
|
||||||
$editor->saveInto($sitetree);
|
$editor->saveInto($sitetree);
|
||||||
$sitetree->write();
|
$sitetree->write();
|
||||||
$this->assertEquals (
|
$this->assertEmpty(
|
||||||
array(), $sitetree->ImageTracking()->getIDList(), 'Tracked images are deleted when removed.'
|
$sitetree->ImageTracking()->getIDList(),
|
||||||
|
'Tracked images are deleted when removed.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,8 +8,12 @@ SiteTree:
|
|||||||
|
|
||||||
File:
|
File:
|
||||||
example_file:
|
example_file:
|
||||||
|
FileFilename: example.pdf
|
||||||
|
FileHash: 55b443b60176235ef09801153cca4e6da7494a0c
|
||||||
Name: example.pdf
|
Name: example.pdf
|
||||||
|
|
||||||
Image:
|
Image:
|
||||||
example_image:
|
example_image:
|
||||||
|
FileFilename: example.jpg
|
||||||
|
FileHash: 55b443b60176235ef09801153cca4e6da7494a0c
|
||||||
Name: example.jpg
|
Name: example.jpg
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class SiteTreeLinkTrackingTest extends SapphireTest {
|
class SiteTreeLinkTrackingTest extends SapphireTest {
|
||||||
|
|
||||||
function isBroken($content) {
|
protected function isBroken($content) {
|
||||||
$parser = new SiteTreeLinkTracking_Parser();
|
$parser = new SiteTreeLinkTracking_Parser();
|
||||||
$htmlValue = Injector::inst()->create('HTMLValue', $content);
|
$htmlValue = Injector::inst()->create('HTMLValue', $content);
|
||||||
$links = $parser->process($htmlValue);
|
$links = $parser->process($htmlValue);
|
||||||
@ -11,7 +11,7 @@ class SiteTreeLinkTrackingTest extends SapphireTest {
|
|||||||
return $links[0]['Broken'];
|
return $links[0]['Broken'];
|
||||||
}
|
}
|
||||||
|
|
||||||
function testParser() {
|
public function testParser() {
|
||||||
$this->assertTrue($this->isBroken('<a href="[sitetree_link,id=123]">link</a>'));
|
$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="[sitetree_link,id=123]#no-such-anchor">link</a>'));
|
||||||
$this->assertTrue($this->isBroken('<a href="[file_link,id=123]">link</a>'));
|
$this->assertTrue($this->isBroken('<a href="[file_link,id=123]">link</a>'));
|
||||||
@ -34,14 +34,14 @@ class SiteTreeLinkTrackingTest extends SapphireTest {
|
|||||||
$this->assertFalse($this->isBroken("<a href=\"[file_link,id=$file->ID]\">link</a>"));
|
$this->assertFalse($this->isBroken("<a href=\"[file_link,id=$file->ID]\">link</a>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
function highlight($content) {
|
protected function highlight($content) {
|
||||||
$page = new Page();
|
$page = new Page();
|
||||||
$page->Content = $content;
|
$page->Content = $content;
|
||||||
$page->write();
|
$page->write();
|
||||||
return $page->Content;
|
return $page->Content;
|
||||||
}
|
}
|
||||||
|
|
||||||
function testHighlighter() {
|
public function testHighlighter() {
|
||||||
$content = $this->highlight('<a href="[sitetree_link,id=123]" class="existing-class">link</a>');
|
$content = $this->highlight('<a href="[sitetree_link,id=123]" class="existing-class">link</a>');
|
||||||
$this->assertEquals(substr_count($content, 'ss-broken'), 1, 'A ss-broken class is added to the broken link.');
|
$this->assertEquals(substr_count($content, 'ss-broken'), 1, 'A ss-broken class is added to the broken link.');
|
||||||
$this->assertEquals(substr_count($content, 'existing-class'), 1, 'Existing class is not removed.');
|
$this->assertEquals(substr_count($content, 'existing-class'), 1, 'Existing class is not removed.');
|
||||||
@ -60,4 +60,16 @@ class SiteTreeLinkTrackingTest extends SapphireTest {
|
|||||||
$this->assertEquals(substr_count($content, 'existing-class'), 1, 'Existing class is not removed.');
|
$this->assertEquals(substr_count($content, 'existing-class'), 1, 'Existing class is not removed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testHasBrokenFile() {
|
||||||
|
$this->assertTrue($this->pageIsBrokenFile('<img src="someurl.jpg" data-fileid="99999999" />'));
|
||||||
|
$this->assertFalse($this->pageIsBrokenFile('<img src="someurl.jpg" />'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function pageIsBrokenFile($content) {
|
||||||
|
$page = new Page();
|
||||||
|
$page->Content = $content;
|
||||||
|
$page->write();
|
||||||
|
return $page->HasBrokenFile;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user