Commit 0d2eefc8 authored by Paris Liakos's avatar Paris Liakos

Issue #1268116 by theunraveler, David_Rothstein, aaron, Jackinloadup,...

Issue #1268116 by theunraveler, David_Rothstein, aaron, Jackinloadup, bbinkovitz, arthurf: WYSIWYG does not record file usage
parent 99f27621
......@@ -7,6 +7,9 @@
* @TODO: Rename this file?
*/
define('MEDIA_TOKEN_REGEX', '/\[\[.*?\]\]/s');
define('MEDIA_TOKEN_REGEX_ALT', '/%7B.*?%7D/s');
/**
* Implements hook_wysiwyg_include_directory().
*/
......@@ -19,6 +22,182 @@ function media_wysiwyg_include_directory($type) {
}
}
/**
* Implements hook_field_attach_insert().
*
* Track file usage for media files included in formatted text. Note that this
* is heavy-handed, and should be replaced when Drupal's filter system is
* context-aware.
*/
function media_field_attach_insert($entity_type, $entity) {
_media_filter_add_file_usage_from_fields($entity_type, $entity);
}
/**
* Implements hook_field_attach_update().
*
* @see media_field_attach_insert().
*/
function media_field_attach_update($entity_type, $entity) {
_media_filter_add_file_usage_from_fields($entity_type, $entity);
}
/**
* Add file usage from file references in an entity's text fields.
*/
function _media_filter_add_file_usage_from_fields($entity_type, $entity) {
// Track the total usage for files from all fields combined.
$entity_files = media_entity_field_count_files($entity_type, $entity);
list($entity_id, $entity_vid, $entity_bundle) = entity_extract_ids($entity_type, $entity);
// When an entity has revisions and then is saved again NOT as new version the
// previous revision of the entity has be loaded to get the last known good
// count of files. The saved data is compared against the last version
// so that a correct file count can be created for that (the current) version
// id. This code may assume some things about entities that are only true for
// node objects. This should be reviewed.
// @TODO this conditional can probably be condensed
if (empty($entity->revision) && empty($entity->old_vid) && empty($entity->is_new) && ! empty($entity->original)) {
$old_files = media_entity_field_count_files($entity_type, $entity->original);
foreach ($old_files as $fid => $old_file_count) {
// Were there more files on the node just prior to saving?
if (empty($entity_files[$fid])) {
$entity_files[$fid] = 0;
}
if ($old_file_count > $entity_files[$fid]) {
$deprecate = $old_file_count - $entity_files[$fid];
// Now deprecate this usage
$file = file_load($fid);
file_usage_delete($file, 'media', $entity_type, $entity_id, $deprecate);
// Usage is deleted, nothing more to do with this file
unset($entity_files[$fid]);
}
// There are the same number of files, nothing to do
elseif ($entity_files[$fid] == $old_file_count) {
unset($entity_files[$fid]);
}
// There are more files now, adjust the difference for the greater number.
// file_usage incrementing will happen below.
else {
// We just need to adjust what the file count will account for the new
// images that have been added since the increment process below will
// just add these additional ones in
$entity_files[$fid] = $entity_files[$fid] - $old_file_count;
}
}
}
// Each entity revision counts for file usage. If versions are not enabled
// the file_usage table will have no entries for this because of the delete
// query above.
foreach ($entity_files as $fid => $entity_count) {
$file = file_load($fid);
file_usage_add($file, 'media', $entity_type, $entity_id, $entity_count);
}
}
/**
* Parse file references from an entity's text fields and return them as an array.
*/
function media_filter_parse_from_fields($entity_type, $entity) {
$file_references = array();
foreach (_media_filter_fields_with_text_filtering($entity_type, $entity) as $field_name) {
if ($field_items = field_get_items($entity_type, $entity, $field_name)) {
foreach ($field_items as $field_item) {
preg_match_all(MEDIA_TOKEN_REGEX, $field_item['value'], $matches);
foreach ($matches[0] as $tag) {
$tag = str_replace(array('[[', ']]'), '', $tag);
$tag_info = drupal_json_decode($tag);
if (isset($tag_info['fid']) && $tag_info['type'] == 'media') {
$file_references[] = $tag_info;
}
}
preg_match_all(MEDIA_TOKEN_REGEX_ALT, $field_item['value'], $matches_alt);
foreach ($matches_alt[0] as $tag) {
$tag = urldecode($tag);
$tag_info = drupal_json_decode($tag);
if (isset($tag_info['fid']) && $tag_info['type'] == 'media') {
$file_references[] = $tag_info;
}
}
}
}
}
return $file_references;
}
/**
* Returns an array containing the names of all fields that perform text filtering.
*/
function _media_filter_fields_with_text_filtering($entity_type, $entity) {
list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity);
$fields = field_info_instances($entity_type, $bundle);
// Get all of the fields on this entity that allow text filtering.
$fields_with_text_filtering = array();
foreach ($fields as $field_name => $field) {
if (!empty($field['settings']['text_processing'])) {
$fields_with_text_filtering[] = $field_name;
}
}
return $fields_with_text_filtering;
}
/**
* Utility function to get the file count in this entity
*
* @param type $entity
* @param type $entity_type
* @return int
*/
function media_entity_field_count_files($entity_type, $entity) {
$entity_files = array();
foreach (media_filter_parse_from_fields($entity_type, $entity) as $file_reference) {
if (empty($entity_files[$file_reference['fid']])) {
$entity_files[$file_reference['fid']] = 1;
}
else {
$entity_files[$file_reference['fid']]++;
}
}
return $entity_files;
}
/**
* Implements hook_entity_delete().
*/
function media_entity_delete($entity, $type) {
list($entity_id) = entity_extract_ids($type, $entity);
db_delete('file_usage')
->condition('module', 'media')
->condition('type', $type)
->condition('id', $entity_id)
->execute();
}
/**
* Implements hook_field_attach_delete_revision().
*
* @param type $entity_type
* @param type $entity
*/
function media_field_attach_delete_revision($entity_type, $entity) {
list($entity_id) = entity_extract_ids($entity_type, $entity);
$files = media_entity_field_count_files($entity_type, $entity);
foreach ($files as $fid => $count) {
if ($file = file_load($fid)) {
file_usage_delete($file, 'media', $entity_type , $entity_id, $count);
}
}
}
/**
* Filter callback for media markup filter.
*
......@@ -26,8 +205,7 @@ function media_wysiwyg_include_directory($type) {
*/
function media_filter($text) {
$text = ' ' . $text . ' ';
$text = preg_replace_callback("/\[\[.*?\]\]/s", 'media_token_to_markup', $text);
$text = preg_replace_callback(MEDIA_TOKEN_REGEX, 'media_token_to_markup', $text);
return $text;
}
......
......@@ -17,5 +17,6 @@ files[] = includes/media_views_plugin_display_media_browser.inc
files[] = includes/media_views_plugin_style_media_browser.inc
files[] = tests/media.test
files[] = tests/media.entity.test
files[] = tests/media.file.usage.test
configure = admin/config/media/browser
<?php
/**
* @file
* Tests for the file usage in entity fields with the Media filter markup.
*/
class MediaFileUsageTest extends MediaTestHelper {
/**
* Provide test information.
*/
public static function getInfo() {
return array(
'name' => t('File usage tracking'),
'description' => t('Tests tracking of usage for files in text fields.'),
'group' => t('Media'),
);
}
/**
* Enable media and file entity modules for testing.
*/
public function setUp() {
parent::setUp(array('media', 'file_entity'));
// Create and log in a user.
$account = $this->drupalCreateUser(array('administer nodes', 'create article content'));
$this->drupalLogin($account);
}
/**
* Generates markup to be inserted for a file.
*
* This is a PHP version of InsertMedia.insert() from js/wysiwyg-media.js.
*
* @param int $fid
* Drupal file id
* @param int $count
* Quantity of markup to insert
*
* @return string
* Filter markup.
*/
private function generateFileMarkup($fid, $count = 1) {
$file_usage_markup = '';
// Build the data that is used in a media tag.
$data = array(
'fid' => $fid,
'type' => 'media',
'view_mode' => 'preview',
'attributes' => array(
'height' => 100,
'width' => 100,
'classes' => 'media-element file_preview',
)
);
// Create the file usage markup.
for ($i = 1; $i <= $count; $i++) {
$file_usage_markup .= '<p>[[' . drupal_json_encode($data) . ']]</p>';
}
return $file_usage_markup;
}
/**
* Utility function to create a test node.
*
* @param int $fid
* Create the node with media markup in the body field
*
* @return int
* Returns the node id
*/
private function createNode($fid = FALSE) {
$markup = '';
if (! empty($fid)) {
$markup = $this->generateFileMarkup($fid);
}
// Create an article node with file markup in the body field.
$edit = array(
'title' => $this->randomName(8),
'body[und][0][value]' => $markup,
);
// Save the article node. First argument is the URL, then the value array
// and the third is the label the button that should be "clicked".
$this->drupalPost('node/add/article', $edit, t('Save'));
// Get the article node that was saved by the unique title.
$node = $this->drupalGetNodeByTitle($edit['title']);
return $node->nid;
}
/**
* Tests the tracking of file usages for files submitted via the WYSIWYG editor.
*/
public function testFileUsageIncrementing() {
// Create a file.
$files = $this->drupalGetTestFiles('image');
$file = file_save($files[0]);
$fid = $file->fid;
// There should be zero usages of this file prior to node creation,
$file_uses = file_usage_list($file);
$this->assertEqual(empty($file_uses), TRUE, t('Created a new file with zero uses.'));
// Create a node to test with.
$nid = $this->createNode($fid);
// Get the new file usage count.
$file_uses = file_usage_list($file);
$this->assertEqual($file_uses['media']['node'][$nid], 1, t('File usage increases when added to a new node.'));
// Create a new revision that has the file on it. File usage will be 2.
$node = node_load($nid);
$node->revision = TRUE;
node_save($node);
$node = node_load($nid);
$file_uses = file_usage_list($file);
$revisions = count(node_revision_list($node));
// Keep track of this VID to test deletion later on.
$delete_one = $node->vid;
$this->assertEqual($revisions, 2, t('Node save created a second revision'));
$this->assertEqual($file_uses['media']['node'][$nid], 2, t('File usage incremented with a new node revision.'));
// Create a new revision that has two instances of the file. File usage will
// be 4.
$node = node_load($nid);
$node->body[LANGUAGE_NONE][0]['value'] = $this->generateFileMarkup($fid, 2);
$node->revision = TRUE;
node_save($node);
$node = node_load($nid);
$file_uses = file_usage_list($file);
$revisions = count(node_revision_list($node));
// Keep track of this VID to test deletion later on.
$delete_two = $node->vid;
$this->assertEqual($revisions, 3, t('Node save created a third revision.'));
$this->assertEqual($file_uses['media']['node'][$nid], 4, t('File usage incremented with multiple files and a new node revision.'));
// Create a new revision that has no file on it. File usage will be 4.
$node = node_load($nid);
$node->body[LANGUAGE_NONE][0]['value'] = '';
$node->revision = TRUE;
node_save($node);
$node = node_load($nid);
$file_uses = file_usage_list($file);
$revisions = count(node_revision_list($node));
// Keep track of this VID to test deletion later on.
$delete_zero = $node->vid;
$this->assertEqual($revisions, 4, t('Node save created a fourth revision.'));
$this->assertEqual($file_uses['media']['node'][$nid], 4, t('File usage does not change with a new revision of the node without the file'));
// Create a new revision that has the file on it. File usage will be 5.
$node = node_load($nid);
$node->body[LANGUAGE_NONE][0]['value'] = $this->generateFileMarkup($fid, 1);
$node->revision = TRUE;
node_save($node);
$node = node_load($nid);
$file_uses = file_usage_list($file);
$revisions = count(node_revision_list($node));
$this->assertEqual($revisions, 5, t('Node save created a new revision.'));
$this->assertEqual($file_uses['media']['node'][$nid], 5, t('File usage incremented with a single file on a new node revision.'));
// Delete a revision that has the file on it once. File usage will be 4.
node_revision_delete($delete_one);
$node = node_load($nid);
$file_uses = file_usage_list($file);
$this->assertEqual($file_uses['media']['node'][$nid], 4, t('Deleting revision with file decreases file usage'));
// Delete a revision that has no file on it. File usage will be 4.
node_revision_delete($delete_zero);
$node = node_load($nid);
$file_uses = file_usage_list($file);
$this->assertEqual($file_uses['media']['node'][$nid], 4, t('Deleting revision without a file does not change file usage.'));
// Delete a revision that has the file on it twice. File usage will be 2.
node_revision_delete($delete_two);
$node = node_load($nid);
$file_uses = file_usage_list($file);
$this->assertEqual($file_uses['media']['node'][$nid], 2, t('Deleting revision with file decreases file usage'));
// Create a new revision with the file on it twice. File usage will be 4.
$node = node_load($nid);
$node->body[LANGUAGE_NONE][0]['value'] = $this->generateFileMarkup($fid, 2);
$node->revision = TRUE;
node_save($node);
$node = node_load($nid);
$file_uses = file_usage_list($file);
$this->assertEqual($file_uses['media']['node'][$nid], 4, t('File usage incremented with files on a new node revision.'));
// Re-save current revision with file on it once instead of twice. File
// usage will be 3.
$node = node_load($nid);
$node->body[LANGUAGE_NONE][0]['value'] = $this->generateFileMarkup($fid, 1);
$saved_vid = $node->vid;
node_save($node);
$node = node_load($nid);
$file_uses = file_usage_list($file);
$this->assertEqual($node->vid, $saved_vid, t('Resaved node revision does not create new revision.'));
$this->assertEqual($file_uses['media']['node'][$nid], 3, t('Resaved node revision with fewer files reduces file usage.'));
// Delete the node. File usage will be 0.
$node = node_load($nid);
node_delete($nid);
$node = node_load($nid);
$file_uses = file_usage_list($file);
$this->assertEqual(empty($node), TRUE, t('Node has been deleted.'));
$this->assertEqual(empty($file_uses), TRUE, t('Deleting the node removes all file uses.'));
}
/**
* Tests the behavior of node and file deletion.
*/
public function testFileUsageIncrementingDelete() {
// Create a node with file markup in the body field with a new file.
$files = $this->drupalGetTestFiles('image');
$file = file_save($files[1]);
$fid = $file->fid;
$file_uses = file_usage_list($file);
$this->assertEqual(empty($file_uses), TRUE, t('Created a new file with zero uses.'));
// Create a new node with file markup.
$nid = $this->createNode($fid);
$file_uses = file_usage_list($file);
$this->assertEqual($file_uses['media']['node'][$nid], 1, t('Incremented file usage on node save.'));
// Try to delete the file. file_delete() should return file_usage().
$deleted = file_delete($file);
$this->assertTrue(is_array($deleted), t('File cannot be deleted while in use by a node.'));
// Delete the node.
node_delete($nid);
$node = node_load($nid);
$file_uses = file_usage_list($file);
$this->assertEqual(empty($node), TRUE, t('Node has been deleted.'));
$this->assertEqual(empty($file_uses), TRUE, t('File has zero usage after node is deleted.'));
$deleted = file_delete($file);
$this->assertTrue($deleted, t('File can be deleted with no usage.'));
$file = file_load($fid);
$this->assertTrue(empty($file), t('File no longer exists after delete.'));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment