updated contrib modules
This commit is contained in:
+139
@@ -0,0 +1,139 @@
|
||||
|
||||
CONTENTS OF THIS FILE
|
||||
---------------------
|
||||
|
||||
* About Drupal
|
||||
* Configuration and features
|
||||
* Installation profiles
|
||||
* Appearance
|
||||
* Developing for Drupal
|
||||
* More information
|
||||
|
||||
ABOUT DRUPAL
|
||||
------------
|
||||
|
||||
Drupal is an open source content management platform supporting a variety of
|
||||
websites ranging from personal weblogs to large community-driven websites. For
|
||||
more information, see the Drupal website at https://www.drupal.org, and join
|
||||
the Drupal community at https://www.drupal.org/community.
|
||||
|
||||
Legal information about Drupal:
|
||||
* Know your rights when using Drupal:
|
||||
See LICENSE.txt in the "core" directory.
|
||||
* Learn about the Drupal trademark and logo policy:
|
||||
https://www.drupal.com/trademark
|
||||
|
||||
CONFIGURATION AND FEATURES
|
||||
--------------------------
|
||||
|
||||
Drupal core (what you get when you download and extract a drupal-x.y.tar.gz or
|
||||
drupal-x.y.zip file from https://www.drupal.org/project/drupal) has what you
|
||||
need to get started with your website. It includes several modules (extensions
|
||||
that add functionality) for common website features, such as managing content,
|
||||
user accounts, image uploading, and search. Core comes with many options that
|
||||
allow site-specific configuration. In addition to the core modules, there are
|
||||
thousands of contributed modules (for functionality not included with Drupal
|
||||
core) available for download.
|
||||
|
||||
More about configuration:
|
||||
* Install, update, and maintain Drupal:
|
||||
See INSTALL.txt and UPDATE.txt in the "core" directory.
|
||||
* Learn about how to use Drupal to create your site:
|
||||
https://www.drupal.org/documentation
|
||||
* Follow best practices:
|
||||
https://www.drupal.org/best-practices
|
||||
* Download contributed modules to /modules to extend Drupal's functionality:
|
||||
https://www.drupal.org/project/modules
|
||||
* See also: "Developing for Drupal" for writing your own modules, below.
|
||||
|
||||
|
||||
INSTALLATION PROFILES
|
||||
---------------------
|
||||
|
||||
Installation profiles define additional steps (such as enabling modules,
|
||||
defining content types, etc.) that run after the base installation provided
|
||||
by core when Drupal is first installed. There are two basic installation
|
||||
profiles provided with Drupal core.
|
||||
|
||||
Installation profiles from the Drupal community modify the installation process
|
||||
to provide a website for a specific use case, such as a CMS for media
|
||||
publishers, a web-based project tracking tool, or a full-fledged CRM for
|
||||
non-profit organizations raising money and accepting donations. They can be
|
||||
distributed as bare installation profiles or as "distributions". Distributions
|
||||
include Drupal core, the installation profile, and all other required
|
||||
extensions, such as contributed and custom modules, themes, and third-party
|
||||
libraries. Bare installation profiles require you to download Drupal Core and
|
||||
the required extensions separately; place the downloaded profile in the
|
||||
/profiles directory before you start the installation process.
|
||||
|
||||
More about installation profiles and distributions:
|
||||
* Read about the difference between installation profiles and distributions:
|
||||
https://www.drupal.org/node/1089736
|
||||
* Download contributed installation profiles and distributions:
|
||||
https://www.drupal.org/project/distributions
|
||||
* Develop your own installation profile or distribution:
|
||||
https://www.drupal.org/developing/distributions
|
||||
|
||||
|
||||
APPEARANCE
|
||||
----------
|
||||
|
||||
In Drupal, the appearance of your site is set by the theme (themes are
|
||||
extensions that set fonts, colors, and layout). Drupal core comes with several
|
||||
themes. More themes are available for download, and you can also create your own
|
||||
custom theme.
|
||||
|
||||
More about themes:
|
||||
* Download contributed themes to /themes to modify Drupal's appearance:
|
||||
https://www.drupal.org/project/themes
|
||||
* Develop your own theme:
|
||||
https://www.drupal.org/documentation/theme
|
||||
|
||||
DEVELOPING FOR DRUPAL
|
||||
---------------------
|
||||
|
||||
Drupal contains an extensive API that allows you to add to and modify the
|
||||
functionality of your site. The API consists of "hooks", which allow modules to
|
||||
react to system events and customize Drupal's behavior, and functions that
|
||||
standardize common operations such as database queries and form generation. The
|
||||
flexible hook architecture means that you should never need to directly modify
|
||||
the files that come with Drupal core to achieve the functionality you want;
|
||||
instead, functionality modifications take the form of modules.
|
||||
|
||||
When you need new functionality for your Drupal site, search for existing
|
||||
contributed modules. If you find a module that matches except for a bug or an
|
||||
additional needed feature, change the module and contribute your improvements
|
||||
back to the project in the form of a "patch". Create new custom modules only
|
||||
when nothing existing comes close to what you need.
|
||||
|
||||
More about developing:
|
||||
* Search for existing contributed modules:
|
||||
https://www.drupal.org/project/modules
|
||||
* Contribute a patch:
|
||||
https://www.drupal.org/patch/submit
|
||||
* Develop your own module:
|
||||
https://www.drupal.org/developing/modules
|
||||
* Follow programming best practices:
|
||||
https://www.drupal.org/developing/best-practices
|
||||
* Refer to the API documentation:
|
||||
https://api.drupal.org/api/drupal/8
|
||||
* Learn from documented Drupal API examples:
|
||||
https://www.drupal.org/project/examples
|
||||
|
||||
MORE INFORMATION
|
||||
----------------
|
||||
|
||||
* See the Drupal.org online documentation:
|
||||
https://www.drupal.org/documentation
|
||||
|
||||
* For a list of security announcements, see the "Security advisories" page at
|
||||
https://www.drupal.org/security (available as an RSS feed). This page also
|
||||
describes how to subscribe to these announcements via email.
|
||||
|
||||
* For information about the Drupal security process, or to find out how to
|
||||
report a potential security issue to the Drupal security team, see the
|
||||
"Security team" page at https://www.drupal.org/security-team
|
||||
|
||||
* For information about the wide range of available support options, visit
|
||||
https://www.drupal.org and click on Community and Support in the top or
|
||||
bottom navigation.
|
||||
@@ -1,3 +1,24 @@
|
||||
Admin Toolbar 8.1.22, 2017-XX-XX
|
||||
--------------------------------
|
||||
Changes since 8.1.21:
|
||||
|
||||
- #2929061 by romainj, dsnopek, adriancid: admin_toolbar_links_access_filter
|
||||
doesn't do anything unless admin_toolbar is enabled.
|
||||
- #2928836 by eme: One extra pixel line under the admin toolbar.
|
||||
- #2925501 by eme, adriancid, RumyanaRuseva: Menu does not take changes into
|
||||
account (caching issue).
|
||||
- #2927914 by adriancid: Parameter comment indentation must be 3 spaces.
|
||||
- #2927911 by adriancid: Remove unused variables.
|
||||
- #2927905 by adriancid: All dependencies must be prefixed with the project
|
||||
name.
|
||||
- #2925327 by Berdir, adriancid: Better check for is-link-in-admin-menu for
|
||||
local task links.
|
||||
- #2922046 by mikejw, adriancid, Berdir, BrianLewisDesign: Notice: Undefined
|
||||
index: entity.eform_type.collection in
|
||||
admin_toolbar_tools_menu_links_discovered_alter.
|
||||
- #2925128 by adriancid: Create the module help page for the Admin Toolbar Links
|
||||
Access Filter submodule.
|
||||
|
||||
Admin Toolbar 8.1.21, 2017-11-20
|
||||
--------------------------------
|
||||
Changes since 8.1.20:
|
||||
|
||||
@@ -6,10 +6,10 @@ type: module
|
||||
# core: 8.x
|
||||
|
||||
dependencies:
|
||||
- toolbar
|
||||
- drupal:toolbar
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-11-20
|
||||
version: '8.x-1.21'
|
||||
# Information added by Drupal.org packaging script on 2018-01-02
|
||||
version: '8.x-1.22'
|
||||
core: '8.x'
|
||||
project: 'admin_toolbar'
|
||||
datestamp: 1511190792
|
||||
datestamp: 1514888588
|
||||
|
||||
+3
-1
@@ -27,7 +27,9 @@ https://www.drupal.org/node/296693 be solved, this module will be deprecated.
|
||||
REQUIREMENTS
|
||||
------------
|
||||
|
||||
No special requirements.
|
||||
This module requires the following modules:
|
||||
|
||||
* Admin Toolbar (https://www.drupal.org/project/admin_toolbar)
|
||||
|
||||
|
||||
RECOMMENDED MODULES
|
||||
|
||||
+5
-4
@@ -1,12 +1,13 @@
|
||||
name: Admin Toolbar Links Access Filter
|
||||
description: Provides a workaround for the common problem that users with 'Use the administration pages and help' permission see menu links they don't have access permission for. Once the issue <a href='https://www.drupal.org/node/296693'>https://www.drupal.org/node/296693</a> be solved, this module will be deprecated.
|
||||
package: Administration
|
||||
|
||||
dependencies:
|
||||
- admin_toolbar:admin_toolbar
|
||||
type: module
|
||||
# core: 8.x
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-11-20
|
||||
version: '8.x-1.21'
|
||||
# Information added by Drupal.org packaging script on 2018-01-02
|
||||
version: '8.x-1.22'
|
||||
core: '8.x'
|
||||
project: 'admin_toolbar'
|
||||
datestamp: 1511190792
|
||||
datestamp: 1514888588
|
||||
|
||||
+17
@@ -9,6 +9,22 @@ use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Symfony\Component\Routing\Exception\RouteNotFoundException;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function admin_toolbar_links_access_filter_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
// Main module help.
|
||||
case 'help.page.admin_toolbar_links_access_filter':
|
||||
$output = '';
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t("The Admin Toolbar Links Access Filter module Provides a workaround for the common problem that users with <em>Use the administration pages and help</em> permission see menu links they don't have access permission for. Once the issue <a href='@url'>Hide empty admin categories</a> be solved, this module will be deprecated.", ['@url' => Url::fromUri('https://www.drupal.org/node/296693')->toString()]) . '</p>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_preprocess_menu().
|
||||
@@ -152,6 +168,7 @@ function admin_toolbar_links_access_filter_is_overview_page($route_name) {
|
||||
return !empty($controller) && in_array($controller, $overview_page_controllers);
|
||||
}
|
||||
catch (RouteNotFoundException $ex) {
|
||||
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
+4
-4
@@ -6,10 +6,10 @@ type: module
|
||||
# core: 8.x
|
||||
|
||||
dependencies:
|
||||
- admin_toolbar
|
||||
- admin_toolbar:admin_toolbar
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-11-20
|
||||
version: '8.x-1.21'
|
||||
# Information added by Drupal.org packaging script on 2018-01-02
|
||||
version: '8.x-1.22'
|
||||
core: '8.x'
|
||||
project: 'admin_toolbar'
|
||||
datestamp: 1511190792
|
||||
datestamp: 1514888588
|
||||
|
||||
+44
@@ -12,6 +12,13 @@ system.admin_index:
|
||||
parent: admin_toolbar_tools.help
|
||||
weight: -100
|
||||
|
||||
system.run_cron:
|
||||
title: 'Run cron'
|
||||
route_name: admin_toolbar.run.cron
|
||||
menu_name: admin
|
||||
parent: admin_toolbar_tools.help
|
||||
weight: -8
|
||||
|
||||
system.db_update:
|
||||
title: 'Run updates'
|
||||
route_name: system.db_update
|
||||
@@ -25,6 +32,43 @@ system.modules_uninstall:
|
||||
menu_name: admin
|
||||
parent: system.modules_list
|
||||
|
||||
admin_toolbar_tools.flush:
|
||||
title: 'Flush all caches'
|
||||
route_name: admin_toolbar_tools.flush
|
||||
weight: -9
|
||||
parent: admin_toolbar_tools.help
|
||||
menu_name: admin
|
||||
|
||||
admin_toolbar_tools.cssjs:
|
||||
title: 'Flush CSS and Javascript'
|
||||
route_name: admin_toolbar_tools.cssjs
|
||||
parent: admin_toolbar_tools.flush
|
||||
menu_name: admin
|
||||
|
||||
admin_toolbar_tools.plugin:
|
||||
title: 'Flush plugins cache'
|
||||
route_name: admin_toolbar_tools.plugin
|
||||
parent: admin_toolbar_tools.flush
|
||||
menu_name: admin
|
||||
|
||||
admin_toolbar_tools.flush_static:
|
||||
title: 'Flush static cache'
|
||||
route_name: admin_toolbar_tools.flush_static
|
||||
parent: admin_toolbar_tools.flush
|
||||
menu_name: admin
|
||||
|
||||
admin_toolbar_tools.flush_menu:
|
||||
title: 'Flush routing and links cache'
|
||||
route_name: admin_toolbar_tools.flush_menu
|
||||
parent: admin_toolbar_tools.flush
|
||||
menu_name: admin
|
||||
|
||||
admin_toolbar_tools.flush_rendercache:
|
||||
title: 'Flush render cache'
|
||||
route_name: admin_toolbar_tools.flush_rendercache
|
||||
parent: admin_toolbar_tools.flush
|
||||
menu_name: admin
|
||||
|
||||
admin_toolbar_tools.drupalorg:
|
||||
title: 'Drupal.org'
|
||||
weight: -5
|
||||
|
||||
+43
-18
@@ -28,17 +28,6 @@ function admin_toolbar_tools_toolbar() {
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_toolbar_alter().
|
||||
*/
|
||||
function admin_toolbar_tools_toolbar_alter(&$items) {
|
||||
// Use lazybuilder for links so the page is cacheable.
|
||||
$items['administration']['tray']['toolbar_administration'] = [
|
||||
'#lazy_builder' => ['admin_toolbar_tools.toolbar_handler:lazyBuilder', []],
|
||||
'#create_placeholder' => TRUE,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
@@ -88,8 +77,18 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
|
||||
}
|
||||
}
|
||||
|
||||
// Adding a menu link to clean the Views cache.
|
||||
if ($moduleHandler->moduleExists('views')) {
|
||||
$links['admin_toolbar_tools.flush_views'] = [
|
||||
'title' => t('Flush views cache'),
|
||||
'route_name' => 'admin_toolbar_tools.flush_views',
|
||||
'menu_name' => 'admin',
|
||||
'parent' => 'admin_toolbar_tools.flush',
|
||||
];
|
||||
}
|
||||
|
||||
// Adds common links to entities.
|
||||
foreach ($content_entities as $module_name => $entities) {
|
||||
foreach ($content_entities as $entities) {
|
||||
$content_entity_bundle = $entities['content_entity_bundle'];
|
||||
$content_entity = $entities['content_entity'];
|
||||
foreach ($entityTypeManager->getStorage($content_entity_bundle)->loadMultiple() as $machine_name => $bundle) {
|
||||
@@ -539,19 +538,45 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
|
||||
$manager = \Drupal::service('plugin.manager.menu.local_task');
|
||||
foreach ($links as $key => $link) {
|
||||
|
||||
// Ignore menu links that have no route name or are not in the admin menu.
|
||||
// @todo Use configuration for admin menu name once added in
|
||||
// https://www.drupal.org/node/1869638.
|
||||
if (empty($link['route_name']) || !isset($link['menu_name']) || $link['menu_name'] !== 'admin') {
|
||||
// Ignore menu links that have no route name.
|
||||
if (empty($link['route_name'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore links that are not in the admin menu, include parents in the
|
||||
// check as they inherit the menu name from the parent if not set.
|
||||
$link_to_check = $link;
|
||||
do {
|
||||
// @todo Use configuration for admin menu name once added in
|
||||
// https://www.drupal.org/node/1869638.
|
||||
if (isset($link_to_check['menu_name'])) {
|
||||
// If the link belongs to the admin menu, then skip this loop and
|
||||
// look for local tasks.
|
||||
if ($link_to_check['menu_name'] === 'admin') {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// If it is explicitly not in the admin menu, skip.
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($link_to_check['parent']) && isset($links[$link_to_check['parent']])) {
|
||||
$link_to_check = $links[$link_to_check['parent']];
|
||||
}
|
||||
else {
|
||||
// No parent and we found no menu_name, skip.
|
||||
continue 2;
|
||||
}
|
||||
} while ($link_to_check);
|
||||
|
||||
$route = $link['route_name'];
|
||||
$route_local_tasks = $manager->getLocalTasksForRoute($route);
|
||||
if (empty($route_local_tasks[0])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($route_local_tasks[0] as $plugin_id => $task_plugin) {
|
||||
foreach ($route_local_tasks[0] as $task_plugin) {
|
||||
$definition = $task_plugin->getPluginDefinition();
|
||||
$local_route = $definition['route_name'];
|
||||
if (empty($local_route)) {
|
||||
@@ -596,7 +621,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
|
||||
// "Content>Comment" also has "File" as a tab then we decide to
|
||||
// set this one as parent of the added link if not the current tab
|
||||
// will be the parent.
|
||||
if (!empty($link['parent'])) {
|
||||
if (!empty($link['parent']) && isset($links[$link['parent']])) {
|
||||
$parentlink = $links[$link['parent']];
|
||||
$parentlink_route = $parentlink['route_name'];
|
||||
$parent_primary = $manager->getLocalTasks($parentlink_route, 0);
|
||||
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
services:
|
||||
admin_toolbar_tools.toolbar_handler:
|
||||
class: Drupal\admin_toolbar_tools\ToolbarHandler
|
||||
arguments: ['@module_handler']
|
||||
+1
-1
@@ -44,7 +44,7 @@ class AdminToolbarToolsAlterTest extends WebTestBase {
|
||||
*/
|
||||
public function testAdminToolbarTools() {
|
||||
// Assert that special menu items are present in the HTML.
|
||||
$this->assertRaw('class="toolbar-icon toolbar-icon-admin-toolbar-tools-help"');
|
||||
$this->assertRaw('class="toolbar-icon toolbar-icon-admin-toolbar-tools-flush"');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-135
@@ -1,135 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\admin_toolbar_tools;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Extension\ModuleHandler;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\Template\Attribute;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Toolbar integration handler.
|
||||
*/
|
||||
class ToolbarHandler implements ContainerInjectionInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The module service.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandler
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* ToolbarHandler constructor.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\ModuleHandler $module_handler
|
||||
* The module service.
|
||||
*/
|
||||
public function __construct(ModuleHandler $module_handler) {
|
||||
$this->moduleHandler = $module_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('module_handler')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy builder callback for the admin_toolbar_tool items.
|
||||
*
|
||||
* @return array
|
||||
* A renderable array as expected by the renderer service.
|
||||
*/
|
||||
public function lazyBuilder() {
|
||||
// Render the pre_render callback we disabled earlier.
|
||||
$build = admin_toolbar_prerender_toolbar_administration_tray([]);
|
||||
|
||||
// Add links that are uncacheable.
|
||||
// Core toolbar module calculates cachability in advance so we have to build
|
||||
// a fake menu tree here, including access checks.
|
||||
$tools_menu = &$build['administration_menu']['#items']['admin_toolbar_tools.help']['below'];
|
||||
|
||||
// Adding the 'Flush all caches' menu in the correct place.
|
||||
$menu_render_array = $this->createMenuRenderArray('admin_toolbar_tools.flush', $this->t('Flush all caches'), TRUE);
|
||||
$this->arrayInsert($tools_menu, 1, $menu_render_array);
|
||||
|
||||
// Adding the submenus to 'Flush all caches' menu.
|
||||
if (!empty($tools_menu['admin_toolbar_tools.flush'])) {
|
||||
$tools_sub_menu = &$tools_menu['admin_toolbar_tools.flush']['below'];
|
||||
$tools_sub_menu += $this->createMenuRenderArray('admin_toolbar_tools.cssjs', $this->t('Flush CSS and Javascript'));
|
||||
$tools_sub_menu += $this->createMenuRenderArray('admin_toolbar_tools.plugin', $this->t('Flush plugins cache'));
|
||||
$tools_sub_menu += $this->createMenuRenderArray('admin_toolbar_tools.flush_static', $this->t('Flush static cache'));
|
||||
$tools_sub_menu += $this->createMenuRenderArray('admin_toolbar_tools.flush_menu', $this->t('Flush routing and links cache'));
|
||||
$tools_sub_menu += $this->createMenuRenderArray('admin_toolbar_tools.flush_rendercache', $this->t('Flush render cache'));
|
||||
|
||||
// Adding a menu link to clean the Views cache.
|
||||
if ($this->moduleHandler->moduleExists('views')) {
|
||||
$tools_sub_menu += $this->createMenuRenderArray('admin_toolbar_tools.flush_views', $this->t('Flush views cache'));
|
||||
}
|
||||
}
|
||||
|
||||
// Adding the 'Run Cron' menu in the correct place.
|
||||
$menu_render_array = $this->createMenuRenderArray('system.run_cron', $this->t('Run cron'));
|
||||
$this->arrayInsert($tools_menu, 3, $menu_render_array);
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the menu render array.
|
||||
*
|
||||
* @param string $route
|
||||
* The route.
|
||||
* @param string $title
|
||||
* The menu title.
|
||||
* @param bool $submenu
|
||||
* Specify if the current menu element have a submenu.
|
||||
*
|
||||
* @return array
|
||||
* A renderable array as expected by the renderer service.
|
||||
*/
|
||||
private function createMenuRenderArray($route, $title, $submenu = FALSE) {
|
||||
$data = [];
|
||||
$url = Url::fromRoute($route);
|
||||
if ($url->access()) {
|
||||
$data[$route] = [
|
||||
'title' => $title,
|
||||
'url' => $url,
|
||||
'attributes' => new Attribute(['class' => ['menu-item'] + ($submenu ? ['menu-item--expanded'] : [])]),
|
||||
];
|
||||
if ($submenu) {
|
||||
$data[$route]['below'] = [];
|
||||
$data[$route]['is_expanded'] = TRUE;
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an array in a given position of another array.
|
||||
*
|
||||
* @param array $array
|
||||
* The array where we need to insert new elements.
|
||||
* @param int $position
|
||||
* The position where we will add the new array.
|
||||
* @param array $insert_array
|
||||
* The array that will be inserted.
|
||||
*
|
||||
* @see http://php.net/manual/en/function.array-splice.php#56794
|
||||
*/
|
||||
private function arrayInsert(array &$array, $position, array $insert_array) {
|
||||
// Getting the first part of the array.
|
||||
$first_array = array_splice($array, 0, $position);
|
||||
// Inserting the new part in the desired position.
|
||||
$array = array_merge($first_array, $insert_array, $array);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
background-color: #f5f5f2;
|
||||
}
|
||||
|
||||
.toolbar-tray-horizontal ul li.menu-item {
|
||||
.toolbar-tray-horizontal ul li li.menu-item {
|
||||
border-top: none transparent;
|
||||
border-right: 1px solid #dddddd;
|
||||
border-bottom: 1px solid #dddddd;
|
||||
|
||||
@@ -1,50 +1,53 @@
|
||||
(function ($) {
|
||||
$(document).ready(function () {
|
||||
$('a.toolbar-icon').removeAttr('title');
|
||||
Drupal.behaviors.adminToolbar = {
|
||||
attach: function (context, settings) {
|
||||
|
||||
$('.toolbar-tray-horizontal li.menu-item--expanded, .toolbar-tray-horizontal ul li.menu-item--expanded .menu-item').hoverIntent({
|
||||
over: function () {
|
||||
// At the current depth, we should delete all "hover-intent" classes.
|
||||
// Other wise we get unwanted behaviour where menu items are expanded while already in hovering other ones.
|
||||
$(this).parent().find('li').removeClass('hover-intent');
|
||||
$(this).addClass('hover-intent');
|
||||
},
|
||||
out: function () {
|
||||
$(this).removeClass('hover-intent');
|
||||
},
|
||||
timeout: 250
|
||||
});
|
||||
|
||||
// Make the toolbar menu navigable with keyboard.
|
||||
$('ul.toolbar-menu li.menu-item--expanded a').on('focusin', function () {
|
||||
$('li.menu-item--expanded').removeClass('hover-intent');
|
||||
$(this).parents('li.menu-item--expanded').addClass('hover-intent');
|
||||
});
|
||||
|
||||
$('ul.toolbar-menu li.menu-item a').keydown(function (e) {
|
||||
if ((e.shiftKey && (e.keyCode || e.which) == 9)) {
|
||||
if ($(this).parent('.menu-item').prev().hasClass('menu-item--expanded')) {
|
||||
$(this).parent('.menu-item').prev().addClass('hover-intent');
|
||||
$('a.toolbar-icon', context).removeAttr('title');
|
||||
|
||||
$('.toolbar-tray-horizontal li.menu-item--expanded, .toolbar-tray-horizontal ul li.menu-item--expanded .menu-item', context).hoverIntent({
|
||||
over: function () {
|
||||
// At the current depth, we should delete all "hover-intent" classes.
|
||||
// Other wise we get unwanted behaviour where menu items are expanded while already in hovering other ones.
|
||||
$(this).parent().find('li').removeClass('hover-intent');
|
||||
$(this).addClass('hover-intent');
|
||||
},
|
||||
out: function () {
|
||||
$(this).removeClass('hover-intent');
|
||||
},
|
||||
timeout: 250
|
||||
});
|
||||
|
||||
// Make the toolbar menu navigable with keyboard.
|
||||
$('ul.toolbar-menu li.menu-item--expanded a', context).on('focusin', function () {
|
||||
$('li.menu-item--expanded', context).removeClass('hover-intent');
|
||||
$(this).parents('li.menu-item--expanded').addClass('hover-intent');
|
||||
});
|
||||
|
||||
$('ul.toolbar-menu li.menu-item a', context).keydown(function (e) {
|
||||
if ((e.shiftKey && (e.keyCode || e.which) == 9)) {
|
||||
if ($(this).parent('.menu-item').prev().hasClass('menu-item--expanded')) {
|
||||
$(this).parent('.menu-item').prev().addClass('hover-intent');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('.toolbar-menu:first-child > .menu-item:not(.menu-item--expanded) a, .toolbar-tab > a', context).on('focusin', function () {
|
||||
$('.menu-item--expanded').removeClass('hover-intent');
|
||||
});
|
||||
|
||||
$('.toolbar-menu:first-child > .menu-item', context).on('hover', function () {
|
||||
$(this,'a').css("background: #fff;");
|
||||
});
|
||||
|
||||
$('ul:not(.toolbar-menu)', context).on({
|
||||
mousemove: function () {
|
||||
$('li.menu-item--expanded').removeClass('hover-intent');
|
||||
},
|
||||
hover: function () {
|
||||
$('li.menu-item--expanded').removeClass('hover-intent');
|
||||
}
|
||||
});
|
||||
|
||||
$('.toolbar-menu:first-child > .menu-item:not(.menu-item--expanded) a, .toolbar-tab > a').on('focusin', function () {
|
||||
$('.menu-item--expanded').removeClass('hover-intent');
|
||||
});
|
||||
|
||||
$('.toolbar-menu:first-child > .menu-item').on('hover', function () {
|
||||
$(this,'a').css("background: #fff;");
|
||||
});
|
||||
|
||||
$('ul:not(.toolbar-menu)').on({
|
||||
mousemove: function () {
|
||||
$('li.menu-item--expanded').removeClass('hover-intent');
|
||||
},
|
||||
hover: function () {
|
||||
$('li.menu-item--expanded').removeClass('hover-intent');
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
})(jQuery);
|
||||
|
||||
@@ -5,8 +5,8 @@ description: 'Provides basic revert and update functionality for other modules'
|
||||
dependencies:
|
||||
- drupal:config
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-09-18
|
||||
version: '8.x-1.4'
|
||||
# Information added by Drupal.org packaging script on 2017-12-05
|
||||
version: '8.x-1.5'
|
||||
core: '8.x'
|
||||
project: 'config_update'
|
||||
datestamp: 1505755746
|
||||
datestamp: 1512514387
|
||||
|
||||
+3
-3
@@ -7,8 +7,8 @@ dependencies:
|
||||
- config_update:config_update
|
||||
- drupal:config
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-09-18
|
||||
version: '8.x-1.4'
|
||||
# Information added by Drupal.org packaging script on 2017-12-05
|
||||
version: '8.x-1.5'
|
||||
core: '8.x'
|
||||
project: 'config_update'
|
||||
datestamp: 1505755746
|
||||
datestamp: 1512514387
|
||||
|
||||
+1
-3
@@ -12,9 +12,7 @@ config_update_ui.import:
|
||||
path: '/admin/config/development/configuration/report/import/{config_type}/{config_name}'
|
||||
defaults:
|
||||
_title: 'Import'
|
||||
_controller: '\Drupal\config_update_ui\Controller\ConfigUpdateController::import'
|
||||
config_type: NULL
|
||||
config_name: NULL
|
||||
_form: '\Drupal\config_update_ui\Form\ConfigImportConfirmForm'
|
||||
requirements:
|
||||
_permission: 'import configuration'
|
||||
|
||||
|
||||
-20
@@ -112,26 +112,6 @@ class ConfigUpdateController extends ControllerBase {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports configuration from a module, theme, or profile.
|
||||
*
|
||||
* Configuration is assumed not to currently exist.
|
||||
*
|
||||
* @param string $config_type
|
||||
* The type of configuration.
|
||||
* @param string $config_name
|
||||
* The name of the config item, without the prefix.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* Redirects to the updates report.
|
||||
*/
|
||||
public function import($config_type, $config_name) {
|
||||
$this->configRevert->import($config_type, $config_name);
|
||||
|
||||
drupal_set_message($this->t('The configuration was imported.'));
|
||||
return $this->redirect('config_update_ui.report');
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the diff between active and provided configuration.
|
||||
*
|
||||
|
||||
+12
-11
@@ -8,6 +8,7 @@ use Drupal\Core\Url;
|
||||
use Drupal\config_update\ConfigListInterface;
|
||||
use Drupal\config_update\ConfigRevertInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Defines a confirmation form for deleting configuration.
|
||||
@@ -81,9 +82,20 @@ class ConfigDeleteConfirmForm extends ConfirmFormBase {
|
||||
}
|
||||
else {
|
||||
$definition = $this->configList->getType($this->type);
|
||||
if (!$definition) {
|
||||
// Make a 404 error if the type doesn't exist.
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
$type_label = $definition->get('label');
|
||||
}
|
||||
|
||||
// To delete, the configuration item must exist in active storage. Check
|
||||
// that and make a 404 error if not.
|
||||
$active = $this->configRevert->getFromActive($this->type, $this->name);
|
||||
if (!$active) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
return $this->t('Are you sure you want to delete the %type config %item?', ['%type' => $type_label, '%item' => $this->name]);
|
||||
}
|
||||
|
||||
@@ -119,17 +131,6 @@ class ConfigDeleteConfirmForm extends ConfirmFormBase {
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
$value = $this->configRevert->getFromActive($this->type, $this->name);
|
||||
if (!$value) {
|
||||
$form_state->setErrorByName('', $this->t('There is no configuration @type named @name to delete', ['@type' => $this->type, '@name' => $this->name]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\config_update_ui\Form;
|
||||
|
||||
use Drupal\Core\Form\ConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\config_update\ConfigListInterface;
|
||||
use Drupal\config_update\ConfigRevertInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Defines a confirmation form for importing configuration.
|
||||
*/
|
||||
class ConfigImportConfirmForm extends ConfirmFormBase {
|
||||
|
||||
/**
|
||||
* The type of config being imported.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* The name of the config item being imported, without the prefix.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The config lister.
|
||||
*
|
||||
* @var \Drupal\config_update\ConfigListInterface
|
||||
*/
|
||||
protected $configList;
|
||||
|
||||
/**
|
||||
* The config reverter.
|
||||
*
|
||||
* @var \Drupal\config_update\ConfigRevertInterface
|
||||
*/
|
||||
protected $configRevert;
|
||||
|
||||
/**
|
||||
* Constructs a ConfigImportConfirmForm object.
|
||||
*
|
||||
* @param \Drupal\config_update\ConfigListInterface $config_list
|
||||
* The config lister.
|
||||
* @param \Drupal\config_update\ConfigRevertInterface $config_update
|
||||
* The config reverter.
|
||||
*/
|
||||
public function __construct(ConfigListInterface $config_list, ConfigRevertInterface $config_update) {
|
||||
$this->configList = $config_list;
|
||||
$this->configRevert = $config_update;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('config_update.config_list'),
|
||||
$container->get('config_update.config_update')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'config_import_confirm';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
if ($this->type == 'system.simple') {
|
||||
$type_label = $this->t('Simple configuration');
|
||||
}
|
||||
else {
|
||||
$definition = $this->configList->getType($this->type);
|
||||
if (!$definition) {
|
||||
// Make a 404 error if the type doesn't exist.
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
$type_label = $definition->get('label');
|
||||
}
|
||||
|
||||
// To import (as opposed to revert), the configuration item must exist in
|
||||
// extension storage but not active storage, so check that, and make a 404
|
||||
// error if not.
|
||||
$extension = $this->configRevert->getFromExtension($this->type, $this->name);
|
||||
$active = $this->configRevert->getFromActive($this->type, $this->name);
|
||||
if (!$extension || $active) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
return $this->t('Are you sure you want to import the %type config %item from its source configuration?', ['%type' => $type_label, '%item' => $this->name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('config_update_ui.report');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->t('Configuration will be added to your site. This action cannot be undone.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Import');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $config_type = NULL, $config_name = NULL) {
|
||||
$this->type = $config_type;
|
||||
$this->name = $config_name;
|
||||
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->configRevert->import($this->type, $this->name);
|
||||
|
||||
drupal_set_message($this->t('The configuration was imported from its source.'));
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
||||
+14
-11
@@ -8,6 +8,7 @@ use Drupal\Core\Url;
|
||||
use Drupal\config_update\ConfigListInterface;
|
||||
use Drupal\config_update\ConfigRevertInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Defines a confirmation form for reverting configuration.
|
||||
@@ -81,9 +82,22 @@ class ConfigRevertConfirmForm extends ConfirmFormBase {
|
||||
}
|
||||
else {
|
||||
$definition = $this->configList->getType($this->type);
|
||||
if (!$definition) {
|
||||
// Make a 404 error if the type doesn't exist.
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
$type_label = $definition->get('label');
|
||||
}
|
||||
|
||||
// To revert (as opposed to import), the configuration item must exist in
|
||||
// both active storage and extension storage, so check that and make a 404
|
||||
// error if not.
|
||||
$extension = $this->configRevert->getFromExtension($this->type, $this->name);
|
||||
$active = $this->configRevert->getFromActive($this->type, $this->name);
|
||||
if (!$extension || !$active) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
return $this->t('Are you sure you want to revert the %type config %item to its source configuration?', ['%type' => $type_label, '%item' => $this->name]);
|
||||
}
|
||||
|
||||
@@ -119,17 +133,6 @@ class ConfigRevertConfirmForm extends ConfirmFormBase {
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
$value = $this->configRevert->getFromExtension($this->type, $this->name);
|
||||
if (!$value) {
|
||||
$form_state->setErrorByName('', $this->t('There is no configuration @type named @name to import', ['@type' => $this->type, '@name' => $this->name]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
||||
+15
@@ -106,6 +106,11 @@ class ConfigUpdateTest extends WebTestBase {
|
||||
$this->assertText('Testing profile');
|
||||
$this->assertDrushReports('profile', '', [], [], [], array_keys($inactive));
|
||||
|
||||
// Verify that the user search page cannot be imported (because it already
|
||||
// exists).
|
||||
$this->drupalGet('admin/config/development/configuration/report/import/search_page/user_search');
|
||||
$this->assertResponse(404);
|
||||
|
||||
// Delete the user search page from the search UI and verify report for
|
||||
// both the search page config type and user module.
|
||||
$this->drupalGet('admin/config/search/pages');
|
||||
@@ -130,10 +135,19 @@ class ConfigUpdateTest extends WebTestBase {
|
||||
'views.view.who_s_online',
|
||||
], ['changed']);
|
||||
|
||||
// Verify that the user search page cannot be reverted (because it does
|
||||
// not already exist).
|
||||
$this->drupalGet('admin/config/development/configuration/report/revert/search_page/user_search');
|
||||
$this->assertResponse(404);
|
||||
// Verify that the delete URL doesn't work either.
|
||||
$this->drupalGet('admin/config/development/configuration/report/delete/search_page/user_search');
|
||||
$this->assertResponse(404);
|
||||
|
||||
// Use the import link to get it back. Do this from the search page
|
||||
// report to make sure we are importing the right config.
|
||||
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
|
||||
$this->clickLink('Import from source');
|
||||
$this->drupalPostForm(NULL, [], 'Import');
|
||||
$this->assertText('The configuration was imported');
|
||||
$this->assertNoReport();
|
||||
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
|
||||
@@ -277,6 +291,7 @@ class ConfigUpdateTest extends WebTestBase {
|
||||
$this->assertText('cannot be undone');
|
||||
$this->drupalPostForm(NULL, [], 'Delete');
|
||||
$this->assertText('The configuration was deleted');
|
||||
|
||||
// And verify the report again.
|
||||
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
|
||||
$this->assertReport('Search page', [], [], [], []);
|
||||
|
||||
@@ -123,7 +123,7 @@ class ConfigLister implements ConfigListInterface {
|
||||
public function getTypeNameByConfigName($name) {
|
||||
$definitions = $this->listTypes();
|
||||
foreach ($this->typesByPrefix as $prefix => $entity_type) {
|
||||
if (strpos($name, $prefix) === 0) {
|
||||
if (strpos($name, $prefix . '.') === 0) {
|
||||
return $entity_type;
|
||||
}
|
||||
}
|
||||
@@ -174,6 +174,12 @@ class ConfigLister implements ConfigListInterface {
|
||||
break;
|
||||
}
|
||||
|
||||
// This only seems to be a problem in unit tests, where a mock object
|
||||
// is returning NULL instead of an empy array for some reason.
|
||||
if (!is_array($optional_list)) {
|
||||
$optional_list = [];
|
||||
}
|
||||
|
||||
return [$active_list, $install_list, $optional_list];
|
||||
}
|
||||
|
||||
@@ -211,7 +217,7 @@ class ConfigLister implements ConfigListInterface {
|
||||
$list = array_combine($list, $list);
|
||||
foreach ($list as $name) {
|
||||
foreach ($prefixes as $prefix) {
|
||||
if (strpos($name, $prefix) === 0) {
|
||||
if (strpos($name, $prefix . '.') === 0) {
|
||||
unset($list[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\config_update\Unit;
|
||||
|
||||
use Drupal\config_update\ConfigLister;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* Tests the \Drupal\config_update\ConfigLister class.
|
||||
*
|
||||
* @group config_update
|
||||
*
|
||||
* @coversDefaultClass \Drupal\config_update\ConfigLister
|
||||
*/
|
||||
class ConfigListerTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The config lister to test.
|
||||
*
|
||||
* @var \Drupal\config_update\ConfigLister
|
||||
*/
|
||||
protected $configLister;
|
||||
|
||||
/**
|
||||
* The mocked entity definition information.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $entityDefinitionInformation;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
$this->configLister = new ConfigLister($this->getEntityManagerMock(), $this->getConfigStorageMock('active'), $this->getConfigStorageMock('extension'), $this->getConfigStorageMock('optional'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mock entity manager for the test.
|
||||
*/
|
||||
protected function getEntityManagerMock() {
|
||||
// Make a list of fake entity definitions. Make sure they are not sorted,
|
||||
// to test that the methods sort them. Also make sure there are a couple
|
||||
// with prefixes that are subsets of each other.
|
||||
$this->entityDefinitionInformation = [
|
||||
['prefix' => 'foo.bar', 'type' => 'foo'],
|
||||
['prefix' => 'foo.barbaz', 'type' => 'bar'],
|
||||
['prefix' => 'baz.foo', 'type' => 'baz'],
|
||||
];
|
||||
|
||||
$definitions = [];
|
||||
foreach ($this->entityDefinitionInformation as $info) {
|
||||
$def = $this->getMockBuilder('Drupal\Core\Config\Entity\ConfigEntityTypeInterface')->getMock();
|
||||
$def
|
||||
->expects($this->any())
|
||||
->method('getConfigPrefix')
|
||||
->willReturn($info['prefix']);
|
||||
$def
|
||||
->expects($this->any())
|
||||
->method('isSubclassOf')
|
||||
->willReturn(TRUE);
|
||||
|
||||
$def->getConfigPrefix();
|
||||
|
||||
$definitions[$info['type']] = $def;
|
||||
}
|
||||
|
||||
$manager = $this->getMockBuilder('Drupal\Core\Entity\EntityTypeManagerInterface')->getMock();
|
||||
$manager
|
||||
->method('getDefinitions')
|
||||
->willReturn($definitions);
|
||||
|
||||
return($manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mock config storage object for the test.
|
||||
*
|
||||
* @param string $type
|
||||
* Type of storage object to return: 'active', 'extension', or 'optional'.
|
||||
*/
|
||||
protected function getConfigStorageMock($type) {
|
||||
if ($type == 'active') {
|
||||
$storage = $this->getMockBuilder('Drupal\Core\Config\StorageInterface')->getMock();
|
||||
|
||||
// The only use of the read() method on active storage is
|
||||
// with the core.extension config, to get the profile name.
|
||||
$storage
|
||||
->method('read')
|
||||
->willReturn(['profile' => 'standard']);
|
||||
|
||||
$map = [
|
||||
['foo.bar', ['foo.bar.one', 'foo.bar.two', 'foo.bar.three']],
|
||||
['foo.barbaz', ['foo.barbaz.four', 'foo.barbaz.five', 'foo.barbaz.six']],
|
||||
['baz.foo'], [],
|
||||
['',
|
||||
[
|
||||
'foo.bar.one',
|
||||
'foo.bar.two',
|
||||
'foo.bar.three',
|
||||
'foo.barbaz.four',
|
||||
'foo.barbaz.five',
|
||||
'foo.barbaz.six',
|
||||
'something.else',
|
||||
'another.one',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$storage
|
||||
->method('listAll')
|
||||
->will($this->returnValueMap($map));
|
||||
}
|
||||
elseif ($type == 'extension') {
|
||||
$storage = $this->getMockBuilder('Drupal\Core\Config\ExtensionInstallStorage')->disableOriginalConstructor()->getMock();
|
||||
$storage
|
||||
->method('getComponentNames')
|
||||
->willReturn([
|
||||
'foo.bar.one' => 'ignored',
|
||||
'foo.bar.two' => 'ignored',
|
||||
'foo.bar.seven' => 'ignored',
|
||||
'foo.barnot.three' => 'ignored',
|
||||
'something.else' => 'ignored',
|
||||
]);
|
||||
|
||||
$map = [
|
||||
['foo.bar', ['foo.bar.one', 'foo.bar.two', 'foo.bar.seven']],
|
||||
['baz.foo'], [],
|
||||
['',
|
||||
[
|
||||
'foo.bar.one',
|
||||
'foo.bar.two',
|
||||
'foo.bar.seven',
|
||||
'foo.barbaz.four',
|
||||
'foo.barnot.three',
|
||||
'something.else',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$storage
|
||||
->method('listAll')
|
||||
->will($this->returnValueMap($map));
|
||||
}
|
||||
else {
|
||||
$storage = $this->getMockBuilder('Drupal\Core\Config\ExtensionInstallStorage')->disableOriginalConstructor()->getMock();
|
||||
$storage
|
||||
->method('getComponentNames')
|
||||
->willReturn([
|
||||
'foo.barbaz.four' => 'ignored',
|
||||
]);
|
||||
|
||||
$map = [
|
||||
['foo.bar'], [],
|
||||
['foo.barbaz', ['foo.barbaz.four']],
|
||||
['', ['foo.barbaz.four']],
|
||||
];
|
||||
$storage
|
||||
->method('listAll')
|
||||
->will($this->returnValueMap($map));
|
||||
}
|
||||
return $storage;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\config_update\ConfigLister::listConfig
|
||||
* @dataProvider listConfigProvider
|
||||
*/
|
||||
public function testListConfig($a, $b, $expected) {
|
||||
$this->assertEquals($expected, $this->configLister->listConfig($a, $b));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for self:testListConfig().
|
||||
*/
|
||||
public function listConfigProvider() {
|
||||
return [
|
||||
// Arguments are $list_type, $name.
|
||||
// We cannot really test the extension types here, because they rely
|
||||
// on the going out to the file system to find out what config objects
|
||||
// are there. This is too complex to mock. It is tested in the tests for
|
||||
// the report output in the config_update_ui module tests. Anyway, we
|
||||
// can test the other types.
|
||||
['type', 'system.all',
|
||||
[
|
||||
[
|
||||
'foo.bar.one',
|
||||
'foo.bar.two',
|
||||
'foo.bar.three',
|
||||
'foo.barbaz.four',
|
||||
'foo.barbaz.five',
|
||||
'foo.barbaz.six',
|
||||
'something.else',
|
||||
'another.one',
|
||||
],
|
||||
[
|
||||
'foo.bar.one',
|
||||
'foo.bar.two',
|
||||
'foo.bar.seven',
|
||||
'foo.barbaz.four',
|
||||
'foo.barnot.three',
|
||||
'something.else',
|
||||
],
|
||||
['foo.barbaz.four'],
|
||||
],
|
||||
],
|
||||
['type', 'system.simple',
|
||||
[
|
||||
['something.else', 'another.one'],
|
||||
['foo.barnot.three', 'something.else'],
|
||||
[],
|
||||
],
|
||||
],
|
||||
['type', 'foo',
|
||||
[
|
||||
['foo.bar.one', 'foo.bar.two', 'foo.bar.three'],
|
||||
['foo.bar.one', 'foo.bar.two', 'foo.bar.seven'],
|
||||
[],
|
||||
],
|
||||
],
|
||||
['type', 'unknown.type', [[], [], []]],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\config_update\ConfigLister::getType
|
||||
*/
|
||||
public function testGetType() {
|
||||
$return = $this->configLister->getType('not_in_list');
|
||||
$this->assertNull($return);
|
||||
|
||||
foreach ($this->entityDefinitionInformation as $info) {
|
||||
$return = $this->configLister->getType($info['type']);
|
||||
$this->assertEquals($return->getConfigPrefix(), $info['prefix']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\config_update\ConfigLister::getTypeByPrefix
|
||||
*/
|
||||
public function testGetTypeByPrefix() {
|
||||
$return = $this->configLister->getTypeByPrefix('not_in_list');
|
||||
$this->assertNull($return);
|
||||
|
||||
foreach ($this->entityDefinitionInformation as $info) {
|
||||
$return = $this->configLister->getTypeByPrefix($info['prefix']);
|
||||
$this->assertEquals($return->getConfigPrefix(), $info['prefix']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\config_update\ConfigLister::getTypeNameByConfigName
|
||||
*/
|
||||
public function testGetTypeNameByConfigName() {
|
||||
$return = $this->configLister->getTypeNameByConfigName('not_in_list');
|
||||
$this->assertNull($return);
|
||||
|
||||
foreach ($this->entityDefinitionInformation as $info) {
|
||||
$return = $this->configLister->getTypeNameByConfigName($info['prefix'] . '.something');
|
||||
$this->assertEquals($return, $info['type']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,8 +5,8 @@ type: module
|
||||
package: Field types
|
||||
# core: '8.x'
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-10-23
|
||||
version: '8.x-1.5'
|
||||
# Information added by Drupal.org packaging script on 2017-12-27
|
||||
version: '8.x-1.6'
|
||||
core: '8.x'
|
||||
project: 'audiofield'
|
||||
datestamp: 1508783950
|
||||
datestamp: 1514414887
|
||||
|
||||
@@ -78,7 +78,7 @@ function audiofield_theme($existing, $type, $theme, $path) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme_suggestions_HOOK().
|
||||
* Implements hook_theme_suggestions_HOOK_alter().
|
||||
*/
|
||||
function audiofield_theme_suggestions_audioplayer_alter(array &$suggestions, array $variables) {
|
||||
// Suggest a template using the plugin name.
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "drupal/audiofield",
|
||||
"description": "AudioField Module",
|
||||
"type": "drupal-module",
|
||||
"license": "GPL-2.0+",
|
||||
"homepage": "https://www.drupal.org/project/audiofield",
|
||||
"minimum-stability": "dev",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Daniel Moberly",
|
||||
"homepage": "https://www.drupal.org/u/danielmoberly",
|
||||
"role": "Maintainer"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://www.drupal.org/project/issues/audiofield"
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@
|
||||
wmode: 'window',
|
||||
useStateClassSkin: true,
|
||||
autoBlur: false,
|
||||
preload: settings.lazyload,
|
||||
smoothPlayBar: true,
|
||||
keyEnabled: true,
|
||||
remainingDuration: false,
|
||||
@@ -78,6 +79,7 @@
|
||||
wmode: 'window',
|
||||
useStateClassSkin: true,
|
||||
autoBlur: false,
|
||||
preload: settings.lazyload,
|
||||
smoothPlayBar: true,
|
||||
keyEnabled: true,
|
||||
volume: settings.volume,
|
||||
@@ -125,6 +127,7 @@
|
||||
},
|
||||
swfPath: '/libraries/jplayer/dist/jplayer',
|
||||
wmode: 'window',
|
||||
preload: settings.lazyload,
|
||||
keyEnabled: true,
|
||||
supplied: file.filetype,
|
||||
},
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
wmode: 'window',
|
||||
useStateClassSkin: true,
|
||||
autoBlur: false,
|
||||
preload: settings.lazyload,
|
||||
smoothPlayBar: true,
|
||||
keyEnabled: true,
|
||||
remainingDuration: false,
|
||||
@@ -57,6 +58,7 @@
|
||||
wmode: 'window',
|
||||
useStateClassSkin: true,
|
||||
autoBlur: false,
|
||||
preload: settings.lazyload,
|
||||
smoothPlayBar: true,
|
||||
keyEnabled: true,
|
||||
volume: settings.volume
|
||||
@@ -88,6 +90,7 @@
|
||||
swfPath: '/libraries/jplayer/dist/jplayer',
|
||||
wmode: 'window',
|
||||
keyEnabled: true,
|
||||
preload: settings.lazyload,
|
||||
supplied: file.filetype
|
||||
});
|
||||
});
|
||||
@@ -108,4 +111,4 @@
|
||||
});
|
||||
}
|
||||
};
|
||||
})(jQuery, Drupal);
|
||||
})(jQuery, Drupal);
|
||||
|
||||
+3
@@ -64,6 +64,7 @@ class JPlayerAudioPlayer extends AudioFieldPluginBase {
|
||||
'filetype' => $renderInfo->filetype,
|
||||
'fid' => $renderInfo->id,
|
||||
'autoplay' => $settings['audio_player_autoplay'],
|
||||
'lazyload' => $settings['audio_player_lazyload'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -98,6 +99,7 @@ class JPlayerAudioPlayer extends AudioFieldPluginBase {
|
||||
// JPlayer expects this as a 0 - 1 value.
|
||||
'volume' => ($settings['audio_player_initial_volume'] / 10),
|
||||
'autoplay' => $settings['audio_player_autoplay'],
|
||||
'lazyload' => $settings['audio_player_lazyload'],
|
||||
];
|
||||
|
||||
// Store the unique id for the template.
|
||||
@@ -120,6 +122,7 @@ class JPlayerAudioPlayer extends AudioFieldPluginBase {
|
||||
'files' => [],
|
||||
'filetypes' => [],
|
||||
'autoplay' => $settings['audio_player_autoplay'],
|
||||
'lazyload' => $settings['audio_player_lazyload'],
|
||||
];
|
||||
|
||||
// Format files for output.
|
||||
|
||||
+75
-11
@@ -7,7 +7,6 @@ use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Field\FormatterBase;
|
||||
use Drupal\Core\Render\Markup;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
@@ -216,6 +215,33 @@ class AudioFieldFieldFormatter extends FormatterBase implements ContainerFactory
|
||||
],
|
||||
],
|
||||
];
|
||||
// Settings for autoplay.
|
||||
$elements['audio_player_lazyload'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Lazy Load audio'),
|
||||
'#description' => $this->t("This setting causes audio not to be loaded until it is played."),
|
||||
'#default_value' => $this->getSetting('audio_player_lazyload'),
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
[':input[name="fields[' . $fieldname . '][settings_edit_form][settings][audio_player]"]' => ['value' => 'audiojs_audio_player']],
|
||||
[':input[name="fields[' . $fieldname . '][settings_edit_form][settings][audio_player]"]' => ['value' => 'default_mp3_player']],
|
||||
[':input[name="fields[' . $fieldname . '][settings_edit_form][settings][audio_player]"]' => ['value' => 'jplayer_audio_player']],
|
||||
[':input[name="fields[' . $fieldname . '][settings_edit_form][settings][audio_player]"]' => ['value' => 'mediaelement_audio_player']],
|
||||
[':input[name="fields[' . $fieldname . '][settings_edit_form][settings][audio_player]"]' => ['value' => 'projekktor_audio_player']],
|
||||
],
|
||||
],
|
||||
];
|
||||
// Settings for download button.
|
||||
$elements['download_button'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Display download button on player'),
|
||||
'#default_value' => $this->getSetting('download_button'),
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
[':input[name="fields[' . $fieldname . '][settings_edit_form][settings][audio_player]"]' => ['value' => 'default_mp3_player']],
|
||||
],
|
||||
],
|
||||
];
|
||||
// Setting for optional download link.
|
||||
$elements['download_link'] = [
|
||||
'#type' => 'checkbox',
|
||||
@@ -236,7 +262,9 @@ class AudioFieldFieldFormatter extends FormatterBase implements ContainerFactory
|
||||
|
||||
// Show which player we are currently using for the field.
|
||||
$summary = [
|
||||
Markup::create('Selected player: <strong>' . $plugin_definitions[$settings['audio_player']]['title'] . '</strong>'),
|
||||
$this->t('Selected player: <strong>@player</strong>', [
|
||||
'@player' => $plugin_definitions[$settings['audio_player']]['title'],
|
||||
]),
|
||||
];
|
||||
// If this is jPlayer, add those settings.
|
||||
if ($settings['audio_player'] == 'jplayer_audio_player') {
|
||||
@@ -254,16 +282,24 @@ class AudioFieldFieldFormatter extends FormatterBase implements ContainerFactory
|
||||
}
|
||||
}
|
||||
}
|
||||
$summary[] = Markup::create('Skin: <strong>' . $theme . '</strong>');
|
||||
$summary[] = $this->t('Skin: <strong>@theme</strong>', [
|
||||
'@theme' => $theme,
|
||||
]);
|
||||
}
|
||||
// If this is wavesurfer, add those settings.
|
||||
elseif ($settings['audio_player'] == 'wavesurfer_audio_player') {
|
||||
$summary[] = Markup::create('Combine files into single player? <strong>' . ($settings['audio_player_wavesurfer_combine_files'] ? 'Yes' : 'No') . '</strong>');
|
||||
$summary[] = $this->t('Combine files into single player? <strong>@combine</strong>', [
|
||||
'@combine' => ($settings['audio_player_wavesurfer_combine_files'] ? 'Yes' : 'No'),
|
||||
]);
|
||||
}
|
||||
// If this is wordpress, add those settings.
|
||||
elseif ($settings['audio_player'] == 'wordpress_audio_player') {
|
||||
$summary[] = Markup::create('Combine files into single player? <strong>' . ($settings['audio_player_wordpress_combine_files'] ? 'Yes' : 'No') . '</strong>');
|
||||
$summary[] = Markup::create('Animate player? <strong>' . ($settings['audio_player_wordpress_animation'] ? 'Yes' : 'No') . '</strong>');
|
||||
$summary[] = $this->t('Combine files into single player? <strong>@combine</strong>', [
|
||||
'@combine' => ($settings['audio_player_wordpress_combine_files'] ? 'Yes' : 'No'),
|
||||
]);
|
||||
$summary[] = $this->t('Animate player? <strong>@animate</strong>', [
|
||||
'@animate' => ($settings['audio_player_wordpress_animation'] ? 'Yes' : 'No'),
|
||||
]);
|
||||
}
|
||||
// If this is soundmanager, add those settings.
|
||||
elseif ($settings['audio_player'] == 'soundmanager_audio_player') {
|
||||
@@ -273,7 +309,9 @@ class AudioFieldFieldFormatter extends FormatterBase implements ContainerFactory
|
||||
'barui' => 'Bar UI',
|
||||
'inlineplayer' => 'Inline Player',
|
||||
];
|
||||
$summary[] = Markup::create('Skin: <strong>' . $skins[$settings['audio_player_soundmanager_theme']] . '</strong>');
|
||||
$summary[] = $this->t('Skin: <strong>@skin</strong>', [
|
||||
'@skin' => $skins[$settings['audio_player_soundmanager_theme']],
|
||||
]);
|
||||
}
|
||||
|
||||
// Show combined settings for multiple players.
|
||||
@@ -286,7 +324,9 @@ class AudioFieldFieldFormatter extends FormatterBase implements ContainerFactory
|
||||
'wordpress_audio_player',
|
||||
])) {
|
||||
// Display volume.
|
||||
$summary[] = Markup::create('Initial volume: <strong>' . $settings['audio_player_initial_volume'] . ' out of 10</strong>');
|
||||
$summary[] = $this->t('Initial volume: <strong>@volume out of 10</strong>', [
|
||||
'@volume' => $settings['audio_player_initial_volume'],
|
||||
]);
|
||||
}
|
||||
|
||||
// Display autoplay.
|
||||
@@ -299,17 +339,39 @@ class AudioFieldFieldFormatter extends FormatterBase implements ContainerFactory
|
||||
'wavesurfer_audio_player',
|
||||
'wordpress_audio_player',
|
||||
])) {
|
||||
$summary[] = Markup::create('Autoplay: <strong>' . ($settings['audio_player_autoplay'] ? 'Yes' : 'No') . '</strong>');
|
||||
$summary[] = $this->t('Autoplay: <strong>@autoplay</strong>', [
|
||||
'@autoplay' => ($settings['audio_player_autoplay'] ? 'Yes' : 'No'),
|
||||
]);
|
||||
}
|
||||
|
||||
// Display lazy load.
|
||||
if (in_array($settings['audio_player'], [
|
||||
'audiojs_audio_player',
|
||||
'default_mp3_player',
|
||||
'jplayer_audio_player',
|
||||
'mediaelement_audio_player',
|
||||
'projekktor_audio_player',
|
||||
])) {
|
||||
$summary[] = $this->t('Lazy Load: <strong>@lazyload</strong>', [
|
||||
'@lazyload' => ($settings['audio_player_lazyload'] ? 'Yes' : 'No'),
|
||||
]);
|
||||
}
|
||||
|
||||
// Check to make sure the library is installed.
|
||||
$player = $this->audioPlayerManager->createInstance($settings['audio_player']);
|
||||
if (!$player->checkInstalled()) {
|
||||
$summary[] = Markup::create('<strong style="color:red;">' . t('Error: this player library is currently not installed. Please select another player or reinstall the library.') . '</strong>');
|
||||
$summary[] = $this->t('<strong style="color:red;">Error: this player library is currently not installed. Please select another player or reinstall the library.</strong>');
|
||||
}
|
||||
|
||||
// Show whether or not we are displaying download buttons.
|
||||
$summary[] = $this->t('Display download button: <strong>@display_link</strong>', [
|
||||
'@display_link' => ($settings['download_button'] ? 'Yes' : 'No'),
|
||||
]);
|
||||
|
||||
// Show whether or not we are displaying direct downloads.
|
||||
$summary[] = Markup::create('Display download link: <strong>' . ($settings['download_link'] ? 'Yes' : 'No') . '</strong>');
|
||||
$summary[] = $this->t('Display download link: <strong>@display_link</strong>', [
|
||||
'@display_link' => ($settings['download_link'] ? 'Yes' : 'No'),
|
||||
]);
|
||||
|
||||
return $summary;
|
||||
}
|
||||
@@ -327,6 +389,8 @@ class AudioFieldFieldFormatter extends FormatterBase implements ContainerFactory
|
||||
'audio_player_soundmanager_theme' => 'default',
|
||||
'audio_player_initial_volume' => 8,
|
||||
'audio_player_autoplay' => FALSE,
|
||||
'audio_player_lazyload' => FALSE,
|
||||
'download_button' => FALSE,
|
||||
'download_link' => FALSE,
|
||||
] + parent::defaultSettings();
|
||||
}
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<div class="audiofield">
|
||||
<div id="{{ settings.id }}" class="audiofield-audiojs-frame">
|
||||
<div class="audiofield-audiojs">
|
||||
<audio preload="auto" {% if settings.audio_player_autoplay == 1 %} autoplay="autoplay" {% endif %}></audio>
|
||||
<audio preload="{% if settings.audio_player_lazyload == 1 %}none{% else %}auto{% endif %}" {% if settings.audio_player_autoplay == 1 %} autoplay="autoplay" {% endif %}></audio>
|
||||
<div class="play-pauseZ">
|
||||
<p class="playZ"></p>
|
||||
<p class="pauseZ"></p>
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<div class="audiofield">
|
||||
{% for file in files %}
|
||||
<div class="mediaelementaudio_frame">
|
||||
<audio id="mediaelement_player_{{ file.id }}" {% if settings.audio_player_autoplay == 1 %} autoplay="autoplay" {% endif %} controls>
|
||||
<audio id="mediaelement_player_{{ file.id }}" preload="{% if settings.audio_player_lazyload == 1 %}none{% else %}auto{% endif %}" {% if settings.audio_player_autoplay == 1 %} autoplay="autoplay" {% endif %} controls>
|
||||
<source src="{{ file.url }}">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
#}
|
||||
<div class="audiofield">
|
||||
{% for file in files %}
|
||||
<audio id="{{ file.id }}" class="audiofield-{{ plugin_id }} {{ plugin_id }}" controls>
|
||||
<audio id="{{ file.id }}" preload="{% if settings.audio_player_lazyload == 1 %}none{% else %}auto{% endif %}" class="audiofield-{{ plugin_id }} {{ plugin_id }}" controls>
|
||||
<source src="{{ file.url }}" type="audio/mpeg">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#}
|
||||
<div class="audiofield">
|
||||
{% for file in files %}
|
||||
<audio controls {% if settings.audio_player_autoplay == 1 %} autoplay {% endif %}>
|
||||
<audio preload="{% if settings.audio_player_lazyload == 1 %}none{% else %}auto{% endif %}" controls {% if settings.audio_player_autoplay == 1 %} autoplay {% endif %} {% if settings.download_button != 1 %} controlsList="nodownload" {% endif %}>
|
||||
<source src="{{ file.url }}" type="audio/mpeg">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
@@ -0,0 +1,43 @@
|
||||
-----------------
|
||||
FileField Sources
|
||||
-----------------
|
||||
|
||||
Description
|
||||
-----------
|
||||
FileField Sources is a module that enhances the generic and image upload fields
|
||||
in Drupal. Typically such fields only allow you to upload a file from your
|
||||
desktop. FileField Sources makes it so that you can populate any file field
|
||||
from a variety of sources, such as entering remote URLs directly, re-use
|
||||
existing uploaded files, pull from a server directory, or a variety of other
|
||||
possibilities.
|
||||
|
||||
This module built by Robots: http://www.lullabot.com
|
||||
Author: Nathan Haug (quicksketch)
|
||||
|
||||
Requirements
|
||||
------------
|
||||
1) Drupal 7.36 or greater is required beginning in FileField Sources 7.x-1.10.
|
||||
|
||||
Installation
|
||||
------------
|
||||
1) Place this module directory in your modules folder (this will usually be
|
||||
"modules/").
|
||||
|
||||
2) Enable the module within your Drupal site.
|
||||
|
||||
3) Add or configure an existing file or image field. To configure a typical node
|
||||
field, visit Manage -> Structure -> Content types and click
|
||||
"Manage form display" on a type you'd like to modify. Add a new file field or
|
||||
edit an existing one.
|
||||
|
||||
While editing the file or image field, you'll have new options available
|
||||
under a "File sources" details. You can enable the desired sources for that
|
||||
particular field.
|
||||
|
||||
4) Create a piece of content that uses your file and try it out.
|
||||
|
||||
Support
|
||||
-------
|
||||
Please file bug reports in the FileField Sources issue queue. Do not use the
|
||||
Drupal.org forums or send bug reports via e-mail.
|
||||
http://drupal.org/project/issues/filefield_sources?categories=All
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
# Basic data types for FileField Sources.
|
||||
|
||||
filefield_sources_source:
|
||||
type: boolean
|
||||
label: 'Plugin'
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
field.widget.third_party.filefield_sources:
|
||||
type: mapping
|
||||
label: 'filefield_sources entity form display settings'
|
||||
mapping:
|
||||
filefield_sources:
|
||||
type: sequence
|
||||
label: 'filefield_sources settings'
|
||||
sequence:
|
||||
- type: filefield_sources.setting.[%key]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
filefield_sources.setting.source_attach:
|
||||
type: mapping
|
||||
label: 'File attach settings'
|
||||
mapping:
|
||||
path:
|
||||
type: string
|
||||
label: 'File attach path'
|
||||
absolute:
|
||||
type: integer
|
||||
label: 'File attach location'
|
||||
attach_mode:
|
||||
type: string
|
||||
label: 'Attach method'
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
filefield_sources.setting.source_imce:
|
||||
type: mapping
|
||||
label: 'IMCE file browser settings'
|
||||
mapping:
|
||||
imce_mode:
|
||||
type: integer
|
||||
label: 'File browser mode'
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
filefield_sources.setting.source_reference:
|
||||
type: mapping
|
||||
label: 'Autocomplete reference options'
|
||||
mapping:
|
||||
autocomplete:
|
||||
type: string
|
||||
label: 'Match file name'
|
||||
search_all_fields:
|
||||
type: string
|
||||
label: 'Search all file fields'
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
filefield_sources.setting.sources:
|
||||
type: sequence
|
||||
label: 'filefield_sources source'
|
||||
sequence:
|
||||
- type: filefield_sources.source.[%key]
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
filefield_sources.source.attach:
|
||||
type: filefield_sources_source
|
||||
label: 'File attach'
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
filefield_sources.source.clipboard:
|
||||
type: filefield_sources_source
|
||||
label: 'Clipboard'
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
filefield_sources.source.imce:
|
||||
type: filefield_sources_source
|
||||
label: 'File browser'
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
filefield_sources.source.reference:
|
||||
type: filefield_sources_source
|
||||
label: 'Reference existing'
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
filefield_sources.source.remote:
|
||||
type: filefield_sources_source
|
||||
label: 'Remote URL'
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
# Schema for the filefield_sources source plugins.
|
||||
|
||||
filefield_sources.source.upload:
|
||||
type: filefield_sources_source
|
||||
label: 'Upload'
|
||||
@@ -0,0 +1,44 @@
|
||||
/* Generic display for all sources. */
|
||||
|
||||
div.filefield-source input.form-text,
|
||||
div.filefield-source select.form-select {
|
||||
display: inline;
|
||||
width: 20em;
|
||||
}
|
||||
|
||||
div.filefield-source .form-item {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
div.filefield-source .hint {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
div.filefield-sources-list a.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Clipboard source. */
|
||||
div.filefield-source-clipboard-capture {
|
||||
border: 1px solid #ccc;
|
||||
width: 20em;
|
||||
height: 1.4em;
|
||||
padding: 2px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
overflow: hidden;
|
||||
}
|
||||
div.filefield-source-clipboard-capture img {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Reference source. */
|
||||
div.filefield-source-reference-item {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
/* Remote source. */
|
||||
div.filefield-source-remote input.form-text {
|
||||
/* Helps with display consistency since references has a background. */
|
||||
background-image: inherit;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* This file documents hooks provided by the FileField Sources module.
|
||||
*
|
||||
* Note that none of this code is executed by using FileField Sources module,
|
||||
* it is provided here for reference as an example how to implement these hooks
|
||||
* in your own module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns a list of widgets that are compatible with FileField Sources.
|
||||
*
|
||||
* FileField Sources works with the most common widgets used with Drupal (the
|
||||
* standard Image and File widgets). Any module that provides another widget
|
||||
* for uploading files may add compatibility with FileField Sources by
|
||||
* implementing this hook and returning the widgets that their module supports.
|
||||
*/
|
||||
function hook_filefield_sources_widgets() {
|
||||
// Add any widgets that your module supports here.
|
||||
return array('mymodule_file_widgetname');
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows altering the sources available on a field.
|
||||
*
|
||||
* This hook allows other modules to modify the sources available to a user.
|
||||
*
|
||||
* @param array $sources
|
||||
* List of filefiled sources plugins.
|
||||
*
|
||||
* @param mixed $context
|
||||
* Contains 'enabled_sources', 'element', 'form_state'.
|
||||
*/
|
||||
function hook_filefield_sources_sources_alter(&$sources, $context) {
|
||||
// This example will exclude sources the user doesn't have access to.
|
||||
foreach (array_keys($sources) as $type) {
|
||||
if (!user_access("use $type filefield source")) {
|
||||
unset($sources[$type]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
name: File Field Sources
|
||||
type: module
|
||||
description: 'Extends File fields to allow referencing of existing files, remote files, and server files.'
|
||||
package: Fields
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- file
|
||||
- system
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-02-21
|
||||
version: '8.x-1.x-dev'
|
||||
core: '8.x'
|
||||
project: 'filefield_sources'
|
||||
datestamp: 1487711285
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Update and install functions for FileField Sources.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function filefield_sources_install() {
|
||||
// FileField Sources needs to load after both ImageField and FileField.
|
||||
try {
|
||||
$file_weight = module_get_weight('file');
|
||||
$image_weight = module_get_weight('image');
|
||||
$weight = max(array($file_weight, $image_weight));
|
||||
$weight++;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$weight = 5;
|
||||
}
|
||||
module_set_weight('filefield_sources', $weight);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
drupal.filefield_sources:
|
||||
version: VERSION
|
||||
js:
|
||||
js/filefield_sources.js: {}
|
||||
css:
|
||||
theme:
|
||||
css/filefield_sources.css: {}
|
||||
dependencies:
|
||||
- file/drupal.file
|
||||
@@ -0,0 +1,771 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Extend FileField to allow files from multiple sources.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Field\WidgetBase;
|
||||
use Drupal\Core\Field\WidgetInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\imce\Imce;
|
||||
|
||||
const FILEFIELD_SOURCE_ATTACH_DEFAULT_PATH = 'file_attach';
|
||||
const FILEFIELD_SOURCE_ATTACH_RELATIVE = 0;
|
||||
const FILEFIELD_SOURCE_ATTACH_ABSOLUTE = 1;
|
||||
const FILEFIELD_SOURCE_ATTACH_MODE_MOVE = 'move';
|
||||
const FILEFIELD_SOURCE_ATTACH_MODE_COPY = 'copy';
|
||||
|
||||
const FILEFIELD_SOURCE_REFERENCE_HINT_TEXT = 'example.png [fid:123]';
|
||||
const FILEFIELD_SOURCE_REMOTE_HINT_TEXT = 'http://example.com/files/file.png';
|
||||
|
||||
const FILEFIELD_SOURCE_REFERENCE_MATCH_STARTS_WITH = '0';
|
||||
const FILEFIELD_SOURCE_REFERENCE_MATCH_CONTAINS = '1';
|
||||
const FILEFIELD_SOURCE_REFERENCE_SEARCH_ALL_NO = '0';
|
||||
const FILEFIELD_SOURCE_REFERENCE_SEARCH_ALL_YES = '1';
|
||||
|
||||
/**
|
||||
* Implements hook_element_info_alter().
|
||||
*/
|
||||
function filefield_sources_element_info_alter(&$type) {
|
||||
if (isset($type['managed_file'])) {
|
||||
$type['managed_file']['#process'][] = 'filefield_sources_field_process';
|
||||
$type['managed_file']['#pre_render'][] = 'filefield_sources_field_pre_render';
|
||||
$type['managed_file']['#element_validate'][] = 'filefield_sources_field_validate';
|
||||
$type['managed_file']['#file_value_callbacks'][] = 'filefield_sources_field_value';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme().
|
||||
*/
|
||||
function filefield_sources_theme() {
|
||||
$theme = array();
|
||||
|
||||
$theme['filefield_sources_element'] = array(
|
||||
'render element' => 'element',
|
||||
'function' => 'theme_filefield_sources_element',
|
||||
);
|
||||
|
||||
$theme['filefield_sources_list'] = array(
|
||||
'variables' => array('element' => NULL, 'sources' => NULL),
|
||||
'function' => 'theme_filefield_sources_list',
|
||||
);
|
||||
|
||||
return $theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_third_party_settings_form().
|
||||
*
|
||||
* Add file field sources settings form to supported field widget forms.
|
||||
*
|
||||
* @see \Drupal\field_ui\FormDisplayOverview
|
||||
*/
|
||||
function filefield_sources_field_widget_third_party_settings_form(WidgetInterface $plugin, FieldDefinitionInterface $field_definition, $form_mode, $form, FormStateInterface $form_state) {
|
||||
$element = array();
|
||||
if (in_array($plugin->getPluginId(), \Drupal::moduleHandler()->invokeAll('filefield_sources_widgets'))) {
|
||||
$element = filefield_sources_form($plugin, $form_state);
|
||||
}
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_settings_summary_alter().
|
||||
*
|
||||
* Add file field sources information to the field widget settings summary.
|
||||
*
|
||||
* @see \Drupal\field_ui\FormDisplayOverview
|
||||
*/
|
||||
function filefield_sources_field_widget_settings_summary_alter(&$summary, $context) {
|
||||
$plugin = $context['widget'];
|
||||
if (in_array($plugin->getPluginId(), \Drupal::moduleHandler()->invokeAll('filefield_sources_widgets'))) {
|
||||
$settings = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources');
|
||||
$enabled_sources = _filefield_sources_enabled($settings);
|
||||
$summary[] = t('File field sources:') . ' ' . implode(', ', array_keys($enabled_sources));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_form_alter().
|
||||
*
|
||||
* Add file field sources widget's settings to element.
|
||||
*/
|
||||
function filefield_sources_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) {
|
||||
$plugin = $context['widget'];
|
||||
if (in_array($plugin->getPluginId(), \Drupal::moduleHandler()->invokeAll('filefield_sources_widgets'))) {
|
||||
$element['#filefield_sources_settings'] = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources');
|
||||
|
||||
// Bundle is missing in element.
|
||||
$items = $context['items'];
|
||||
$element['#bundle'] = $items->getEntity()->bundle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_filefield_sources_widgets().
|
||||
*
|
||||
* This returns a list of widgets that are compatible with FileField Sources.
|
||||
*/
|
||||
function filefield_sources_filefield_sources_widgets() {
|
||||
return array('file_generic', 'image_image');
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration form for editing FileField Sources settings for a widget.
|
||||
*/
|
||||
function filefield_sources_form($plugin, FormStateInterface $form_state) {
|
||||
$settings = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources');
|
||||
|
||||
// Backward compatibility: auto-enable 'upload'.
|
||||
$enabled = _filefield_sources_enabled($settings);
|
||||
|
||||
$form['filefield_sources'] = array(
|
||||
'#type' => 'details',
|
||||
'#title' => t('File sources'),
|
||||
'#weight' => 20,
|
||||
);
|
||||
|
||||
$sources = filefield_sources_list();
|
||||
$form['filefield_sources']['sources'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => t('Enabled sources'),
|
||||
'#options' => $sources,
|
||||
'#default_value' => $enabled,
|
||||
'#description' => t('Select the available locations from which this widget may select files.'),
|
||||
);
|
||||
|
||||
$params = array($plugin);
|
||||
$form['filefield_sources'] = array_merge($form['filefield_sources'], filefield_sources_invoke_all('settings', $params));
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* A #process callback to extend the filefield_widget element type.
|
||||
*
|
||||
* Add the central JavaScript and CSS files that allow switching between
|
||||
* different sources. Third-party modules can also add to the list of sources
|
||||
* by implementing hook_filefield_sources_info().
|
||||
*/
|
||||
function filefield_sources_field_process(&$element, FormStateInterface $form_state, &$complete_form) {
|
||||
static $js_added;
|
||||
|
||||
// Check if we are processing file field sources.
|
||||
if (!isset($element['#filefield_sources_settings'])) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
// Do all processing as needed by each source.
|
||||
$sources = filefield_sources_info();
|
||||
$settings = $element['#filefield_sources_settings'];
|
||||
$enabled_sources = _filefield_sources_enabled($settings);
|
||||
|
||||
$context = array(
|
||||
'enabled_sources' => &$enabled_sources,
|
||||
'element' => $element,
|
||||
'form_state' => $form_state,
|
||||
);
|
||||
|
||||
// Allow other modules to alter the sources.
|
||||
\Drupal::moduleHandler()->alter('filefield_sources_sources', $sources, $context);
|
||||
|
||||
foreach ($sources as $source_name => $source) {
|
||||
if (empty($enabled_sources[$source_name])) {
|
||||
unset($sources[$source_name]);
|
||||
}
|
||||
// Default upload plugin does not have class.
|
||||
elseif (isset($source['class'])) {
|
||||
$callback = array($source['class'], 'process');
|
||||
if (is_callable($callback)) {
|
||||
$element = call_user_func_array($callback, array(
|
||||
&$element,
|
||||
$form_state,
|
||||
&$complete_form,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
$element['#filefield_sources'] = $sources;
|
||||
|
||||
// Exit out if not adding any sources.
|
||||
if (empty($sources)) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
// Hide default 'upload' type?
|
||||
if (!isset($enabled_sources['upload'])) {
|
||||
foreach (array('upload_button', 'upload') as $field) {
|
||||
if (isset($element[$field])) {
|
||||
$element[$field]['#access'] = FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add class to upload button.
|
||||
$element['upload_button']['#attributes']['class'][] = 'upload-button';
|
||||
|
||||
$element['#attached']['library'][] = 'filefield_sources/drupal.filefield_sources';
|
||||
|
||||
// Check the element for hint text that might need to be added.
|
||||
foreach (Element::children($element) as $key) {
|
||||
if (isset($element[$key]['#filefield_sources_hint_text']) && !isset($js_added[$key])) {
|
||||
$type = str_replace('filefield_', '', $key);
|
||||
|
||||
$element['#attached']['drupalSettings']['fileFieldSources'][$type] = array(
|
||||
'hintText' => $element[$key]['#filefield_sources_hint_text'],
|
||||
);
|
||||
|
||||
$js_added[$key] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust the Ajax settings so that on upload and remove of any individual
|
||||
// file, the entire group of file fields is updated together.
|
||||
// Clone of Drupal\file\Plugin\Field\FieldWidget\FileWidget::process().
|
||||
if ($element['#cardinality'] != 1) {
|
||||
$parents = array_slice($element['#array_parents'], 0, -1);
|
||||
$new_options = array(
|
||||
'query' => array(
|
||||
'element_parents' => implode('/', $parents),
|
||||
),
|
||||
);
|
||||
$field_element = NestedArray::getValue($complete_form, $parents);
|
||||
$new_wrapper = $field_element['#id'] . '-ajax-wrapper';
|
||||
foreach (Element::children($element) as $key) {
|
||||
foreach (Element::children($element[$key]) as $subkey) {
|
||||
if (isset($element[$key][$subkey]['#ajax'])) {
|
||||
$element[$key][$subkey]['#ajax']['options'] = $new_options;
|
||||
$element[$key][$subkey]['#ajax']['wrapper'] = $new_wrapper;
|
||||
$element[$key][$subkey]['#limit_validation_errors'] = array(
|
||||
array_slice($element['#array_parents'], 0, -2),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($element['#prefix'], $element['#suffix']);
|
||||
}
|
||||
|
||||
// Add the list of sources to the element for toggling between sources.
|
||||
if (empty($element['fids']['#value'])) {
|
||||
if (count($enabled_sources) > 1) {
|
||||
$element['filefield_sources_list'] = array(
|
||||
'#theme' => 'filefield_sources_list',
|
||||
'#element' => $element,
|
||||
'#sources' => $sources,
|
||||
'#weight' => -20,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* A #pre_render function to hide sources if a file is currently uploaded.
|
||||
*/
|
||||
function filefield_sources_field_pre_render($element) {
|
||||
// If we already have a file, we don't want to show the upload controls.
|
||||
if (!empty($element['#value']['fids'])) {
|
||||
foreach (Element::children($element) as $key) {
|
||||
if (!empty($element[$key]['#filefield_source'])) {
|
||||
$element[$key]['#access'] = FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* An #element_validate function to run source validations.
|
||||
*/
|
||||
function filefield_sources_field_validate(&$element, FormStateInterface $form_state, &$complete_form) {
|
||||
// Do all processing as needed by each source.
|
||||
$sources = filefield_sources_info();
|
||||
foreach ($sources as $source) {
|
||||
if (!isset($source['class'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$callback = array($source['class'], 'validate');
|
||||
if (is_callable($callback)) {
|
||||
call_user_func_array($callback, array(
|
||||
$element,
|
||||
$form_state,
|
||||
$complete_form,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Form submission handler for all FileField Source buttons.
|
||||
*
|
||||
* Clone of \Drupal\file\Plugin\Field\FieldWidget\FileWidget::submit(), with
|
||||
* a few changes:
|
||||
* - Submit button is one level down compare to 'Upload' source's submit
|
||||
* button.
|
||||
* - Replace static in static::getWidgetState and static::setWidgetState by
|
||||
* WidgetBase.
|
||||
* - Rebuild the form after all.
|
||||
*/
|
||||
function filefield_sources_field_submit(&$form, FormStateInterface $form_state) {
|
||||
// During the form rebuild, formElement() will create field item widget
|
||||
// elements using re-indexed deltas, so clear out FormState::$input to
|
||||
// avoid a mismatch between old and new deltas. The rebuilt elements will
|
||||
// have #default_value set appropriately for the current state of the field,
|
||||
// so nothing is lost in doing this.
|
||||
$button = $form_state->getTriggeringElement();
|
||||
$parents = array_slice($button['#parents'], 0, -3);
|
||||
NestedArray::setValue($form_state->getUserInput(), $parents, NULL);
|
||||
|
||||
// Go one level up in the form, to the widgets container.
|
||||
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -2));
|
||||
$field_name = $element['#field_name'];
|
||||
$parents = $element['#field_parents'];
|
||||
|
||||
$submitted_values = NestedArray::getValue($form_state->getValues(), array_slice($button['#parents'], 0, -3));
|
||||
foreach ($submitted_values as $delta => $submitted_value) {
|
||||
if (empty($submitted_value['fids'])) {
|
||||
unset($submitted_values[$delta]);
|
||||
}
|
||||
}
|
||||
|
||||
// If there are more files uploaded via the same widget, we have to separate
|
||||
// them, as we display each file in it's own widget.
|
||||
$new_values = array();
|
||||
foreach ($submitted_values as $delta => $submitted_value) {
|
||||
if (is_array($submitted_value['fids'])) {
|
||||
foreach ($submitted_value['fids'] as $fid) {
|
||||
$new_value = $submitted_value;
|
||||
$new_value['fids'] = array($fid);
|
||||
$new_values[] = $new_value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$new_value = $submitted_value;
|
||||
}
|
||||
}
|
||||
|
||||
// Re-index deltas after removing empty items.
|
||||
$submitted_values = array_values($new_values);
|
||||
|
||||
// Update form_state values.
|
||||
NestedArray::setValue($form_state->getValues(), array_slice($button['#parents'], 0, -3), $submitted_values);
|
||||
|
||||
// Update items.
|
||||
$field_state = WidgetBase::getWidgetState($parents, $field_name, $form_state);
|
||||
$field_state['items'] = $submitted_values;
|
||||
WidgetBase::setWidgetState($parents, $field_name, $form_state, $field_state);
|
||||
|
||||
// We need to rebuild the form, so that uploaded file can be displayed.
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* A #filefield_value_callback to run source value callbacks.
|
||||
*/
|
||||
function filefield_sources_field_value(&$element, &$input, FormStateInterface $form_state) {
|
||||
// Do all processing as needed by each source.
|
||||
$sources = filefield_sources_info();
|
||||
foreach ($sources as $source) {
|
||||
if (isset($source['class'])) {
|
||||
$callback = array($source['class'], 'value');
|
||||
if (is_callable($callback)) {
|
||||
call_user_func_array($callback, array(&$element, &$input, $form_state));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call all FileField Source hooks stored in the available include files.
|
||||
*/
|
||||
function filefield_sources_invoke_all($method, &$params) {
|
||||
$return = array();
|
||||
foreach (\Drupal::service('filefield_sources')->getDefinitions() as $definition) {
|
||||
if (!isset($definition['class'])) {
|
||||
continue;
|
||||
}
|
||||
// Get routes defined by each plugin.
|
||||
$callback = array($definition['class'], $method);
|
||||
if (is_callable($callback)) {
|
||||
$result = call_user_func_array($callback, $params);
|
||||
if (isset($result) && is_array($result)) {
|
||||
$return = array_merge_recursive($return, $result);
|
||||
}
|
||||
elseif (isset($result)) {
|
||||
$return[] = $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load hook_filefield_sources_info() data from all modules.
|
||||
*/
|
||||
function filefield_sources_info($include_default = TRUE) {
|
||||
$info = \Drupal::service('filefield_sources')->getDefinitions();
|
||||
if (isset($info['imce']) && !Imce::access()) {
|
||||
unset($info['imce']);
|
||||
}
|
||||
if ($include_default) {
|
||||
$info['upload'] = array(
|
||||
'name' => t('Upload (default)'),
|
||||
'label' => t('Upload'),
|
||||
'description' => t('Upload a file from your computer.'),
|
||||
'weight' => -10,
|
||||
);
|
||||
}
|
||||
|
||||
uasort($info, '_filefield_sources_sort');
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a list of FileField Sources by name, suitable for a select list.
|
||||
*/
|
||||
function filefield_sources_list($include_default = TRUE) {
|
||||
$info = filefield_sources_info($include_default);
|
||||
$list = array();
|
||||
|
||||
foreach ($info as $key => $source) {
|
||||
$list[$key] = $source['name'];
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a file into the database after validating it.
|
||||
*
|
||||
* This function is identical to the core function file_save_upload() except
|
||||
* that it accepts an input file path instead of an input file source name.
|
||||
*
|
||||
* @see file_save_upload()
|
||||
*/
|
||||
function filefield_sources_save_file($filepath, $validators = array(), $destination = FALSE, $replace = FILE_EXISTS_RENAME) {
|
||||
$user = \Drupal::currentUser();
|
||||
|
||||
// Begin building file object.
|
||||
$file = entity_create('file', array(
|
||||
'uri' => $filepath,
|
||||
'uid' => $user->id(),
|
||||
'status' => FILE_EXISTS_RENAME,
|
||||
));
|
||||
$file->setFilename(trim(basename($filepath), '.'));
|
||||
$file->setMimeType(\Drupal::service('file.mime_type.guesser')->guess($file->getFilename()));
|
||||
$file->setSize(filesize($filepath));
|
||||
|
||||
$extensions = '';
|
||||
if (isset($validators['file_validate_extensions'])) {
|
||||
if (isset($validators['file_validate_extensions'][0])) {
|
||||
// Build the list of non-munged extensions if the caller provided them.
|
||||
$extensions = $validators['file_validate_extensions'][0];
|
||||
}
|
||||
else {
|
||||
// If 'file_validate_extensions' is set and the list is empty then the
|
||||
// caller wants to allow any extension. In this case we have to remove the
|
||||
// validator or else it will reject all extensions.
|
||||
unset($validators['file_validate_extensions']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No validator was provided, so add one using the default list.
|
||||
// Build a default non-munged safe list for file_munge_filename().
|
||||
$extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
|
||||
$validators['file_validate_extensions'] = array();
|
||||
$validators['file_validate_extensions'][0] = $extensions;
|
||||
}
|
||||
|
||||
if (!empty($extensions)) {
|
||||
// Munge the filename to protect against possible malicious extension hiding
|
||||
// within an unknown file type (ie: filename.html.foo).
|
||||
$file->setFilename(file_munge_filename($file->getFilename(), $extensions));
|
||||
}
|
||||
|
||||
// Rename potentially executable files, to help prevent exploits (i.e. will
|
||||
// rename filename.php.foo and filename.php to filename.php.foo.txt and
|
||||
// filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads'
|
||||
// evaluates to TRUE.
|
||||
if (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->getFilename()) && (substr($file->getFilename(), -4) != '.txt')) {
|
||||
$file->setMimeType('text/plain');
|
||||
$file->setFileUri($file->getFileUri() . '.txt');
|
||||
$file->setFilename($file->getFilename() . '.txt');
|
||||
// The .txt extension may not be in the allowed list of extensions. We have
|
||||
// to add it here or else the file upload will fail.
|
||||
if (!empty($extensions)) {
|
||||
$validators['file_validate_extensions'][0] .= ' txt';
|
||||
drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $file->getFilename())));
|
||||
}
|
||||
}
|
||||
|
||||
// If the destination is not provided, use the temporary directory.
|
||||
if (empty($destination)) {
|
||||
$destination = 'temporary://';
|
||||
}
|
||||
|
||||
// Assert that the destination contains a valid stream.
|
||||
$destination_scheme = file_uri_scheme($destination);
|
||||
if (!$destination_scheme || !file_stream_wrapper_valid_scheme($destination_scheme)) {
|
||||
drupal_set_message(t('The file could not be uploaded, because the destination %destination is invalid.', array('%destination' => $destination)), 'error');
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// A URI may already have a trailing slash or look like "public://".
|
||||
if (substr($destination, -1) != '/') {
|
||||
$destination .= '/';
|
||||
}
|
||||
|
||||
// Ensure the destination is writable.
|
||||
file_prepare_directory($destination, FILE_CREATE_DIRECTORY);
|
||||
|
||||
// Check if this is actually the same file being "attached" to a file record.
|
||||
// If so, it acts as a file replace, except no file is actually moved.
|
||||
$reuse_file = ($destination . $file->getFilename() === $file->getFileUri());
|
||||
if ($reuse_file) {
|
||||
$replace = FILE_EXISTS_REPLACE;
|
||||
}
|
||||
|
||||
$file->destination = file_destination($destination . $file->getFilename(), $replace);
|
||||
// If file_destination() returns FALSE then $replace == FILE_EXISTS_ERROR and
|
||||
// there's an existing file so we need to bail.
|
||||
if ($file->destination === FALSE) {
|
||||
drupal_set_message(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', array('%source' => $file->getFilename(), '%directory' => $destination)), 'error');
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Add in our check of the the file name length.
|
||||
$validators['file_validate_name_length'] = array();
|
||||
|
||||
// Call the validation functions specified by this function's caller.
|
||||
$errors = file_validate($file, $validators);
|
||||
|
||||
// Check for errors.
|
||||
if (!empty($errors)) {
|
||||
$message = t('The specified file %name could not be uploaded.', array('%name' => $file->getFilename()));
|
||||
if (count($errors) > 1) {
|
||||
$message .= theme('item_list', array('items' => $errors));
|
||||
}
|
||||
else {
|
||||
$message .= ' ' . array_pop($errors);
|
||||
}
|
||||
drupal_set_message($message, 'error');
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary
|
||||
// directory. This overcomes open_basedir restrictions for future file
|
||||
// operations.
|
||||
$file->setFileUri($file->destination);
|
||||
if (!$reuse_file && !file_unmanaged_copy($filepath, $file->getFileUri(), $replace)) {
|
||||
drupal_set_message(t('File upload error. Could not move uploaded file.'), 'error');
|
||||
\Drupal::logger('filefield_sources')->log(E_NOTICE, 'Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->getFilename(), '%destination' => $file->getFileUri()));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Set the permissions on the new file.
|
||||
drupal_chmod($file->getFileUri());
|
||||
|
||||
// If we are replacing an existing file re-use its database record.
|
||||
if ($replace == FILE_EXISTS_REPLACE) {
|
||||
$existing_files = file_load_multiple(array(), array('uri' => $file->getFileUri()));
|
||||
if (count($existing_files)) {
|
||||
$existing = reset($existing_files);
|
||||
$file->setOriginalId($existing->id());
|
||||
}
|
||||
}
|
||||
|
||||
// If we made it this far it's safe to record this file in the database.
|
||||
$file->save();
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the file name, munging extensions and transliterating.
|
||||
*
|
||||
* @param string $filepath
|
||||
* A string containing a file name or full path. Only the file name will
|
||||
* actually be modified.
|
||||
*
|
||||
* @return string
|
||||
* A file path with a cleaned-up file name.
|
||||
*/
|
||||
function filefield_sources_clean_filename($filepath, $extensions) {
|
||||
$filename = basename($filepath);
|
||||
|
||||
if (\Drupal::moduleHandler()->moduleExists('transliteration')) {
|
||||
module_load_include('inc', 'transliteration');
|
||||
|
||||
$langcode = NULL;
|
||||
if (!empty($_POST['language'])) {
|
||||
$languages = language_list();
|
||||
$langcode = isset($languages[$_POST['language']]) ? $_POST['language'] : NULL;
|
||||
}
|
||||
$filename = transliteration_clean_filename($filename, $langcode);
|
||||
}
|
||||
|
||||
// Because this transfer mechanism does not use file_save_upload(), we need
|
||||
// to manually munge the filename to prevent dangerous extensions.
|
||||
// See file_save_upload().
|
||||
if (empty($extensions)) {
|
||||
$extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
|
||||
}
|
||||
$filename = file_munge_filename($filename, $extensions);
|
||||
$directory = drupal_dirname($filepath);
|
||||
return ($directory != '.' ? $directory . '/' : '') . $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme the display of the source element.
|
||||
*/
|
||||
function theme_filefield_sources_element($variables) {
|
||||
$element = $variables['element'];
|
||||
$source_id = $element['#source_id'];
|
||||
$method = isset($element['#method']) ? $element['#method'] : 'element';
|
||||
$extra_variables = isset($element['#variables']) ? $element['#variables'] : array();
|
||||
|
||||
$sources = filefield_sources_info();
|
||||
if (isset($sources[$source_id]['class'])) {
|
||||
$callback = array($sources[$source_id]['class'], $method);
|
||||
if (is_callable($callback)) {
|
||||
$variables = array_merge($variables, $extra_variables);
|
||||
return call_user_func_array($callback, array($variables));
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme the display of the sources list.
|
||||
*/
|
||||
function theme_filefield_sources_list($variables) {
|
||||
$element = $variables['element'];
|
||||
$sources = $variables['sources'];
|
||||
|
||||
$links = array();
|
||||
|
||||
foreach ($sources as $name => $source) {
|
||||
$links[] = '<a href="#" onclick="return false;" title="' . $source['description'] . '" id="' . $element['#id'] . '-' . $name . '-source" class="filefield-source filefield-source-' . $name . '">' . $source['label'] . '</a>';
|
||||
}
|
||||
return '<div class="filefield-sources-list">' . implode(' | ', $links) . '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a file based on the $element['#upload_validators'] property.
|
||||
*/
|
||||
function filefield_sources_element_validate($element, $file, FormStateInterface $form_state) {
|
||||
$validators = $element['#upload_validators'];
|
||||
$errors = array();
|
||||
|
||||
// Since this frequently is used to reference existing files, check that
|
||||
// they exist first in addition to the normal validations.
|
||||
if (!file_exists($file->getFileUri())) {
|
||||
$errors[] = t('The file does not exist.');
|
||||
}
|
||||
// Call the validation functions.
|
||||
else {
|
||||
foreach ($validators as $function => $args) {
|
||||
// Add the $file variable to the list of arguments and pass it by
|
||||
// reference (required for PHP 5.3 and higher).
|
||||
array_unshift($args, NULL);
|
||||
$args[0] = &$file;
|
||||
$errors = array_merge($errors, call_user_func_array($function, $args));
|
||||
}
|
||||
}
|
||||
|
||||
// Check for validation errors.
|
||||
if (!empty($errors)) {
|
||||
$message = t('The selected file %name could not be referenced.', array('%name' => $file->filename));
|
||||
if (count($errors) > 1) {
|
||||
$message .= '<ul><li>' . implode('</li><li>', $errors) . '</li></ul>';
|
||||
}
|
||||
else {
|
||||
$message .= ' ' . array_pop($errors);
|
||||
}
|
||||
$form_state->setError($element, $message);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate help text based on the $element['#upload_validators'] property.
|
||||
*/
|
||||
function filefield_sources_element_validation_help($validators) {
|
||||
$desc = array();
|
||||
foreach ($validators as $callback => $arguments) {
|
||||
$help_func = $callback . '_help';
|
||||
if (function_exists($help_func)) {
|
||||
$desc[] = call_user_func_array($help_func, $arguments);
|
||||
}
|
||||
}
|
||||
return empty($desc) ? '' : implode('<br />', $desc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom sort function for ordering sources.
|
||||
*/
|
||||
function _filefield_sources_sort($a, $b) {
|
||||
$a = (array) $a + array('weight' => 0, 'label' => '');
|
||||
$b = (array) $b + array('weight' => 0, 'label' => '');
|
||||
return $a['weight'] < $b['weight'] ? -1 : ($a['weight'] > $b['weight'] ? 1 : strnatcasecmp($a['label'], $b['label']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to return enabled sources for a field.
|
||||
*
|
||||
* This provides backward compatibility for 'upload' type.
|
||||
*
|
||||
* @see http://drupal.org/node/932994
|
||||
*/
|
||||
function _filefield_sources_enabled($settings) {
|
||||
if (!isset($settings['sources']['upload'])) {
|
||||
$settings['sources']['upload'] = 'upload';
|
||||
}
|
||||
|
||||
$enabled = array_keys(array_filter($settings['sources']));
|
||||
asort($enabled);
|
||||
return array_combine($enabled, $enabled);
|
||||
}
|
||||
|
||||
// @todo Remove once https://www.drupal.org/node/1808132 is finished.
|
||||
if (!function_exists('module_get_weight')) {
|
||||
/**
|
||||
* Gets weight of a particular module.
|
||||
*
|
||||
* @param string $module
|
||||
* The name of the module (without the .module extension).
|
||||
*
|
||||
* @return int
|
||||
* The configured weight of the module.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* Thrown in case the given module is not installed in the system.
|
||||
*/
|
||||
function module_get_weight($module) {
|
||||
$weight = \Drupal::config('core.extension')->get("module.$module");
|
||||
if ($weight !== NULL) {
|
||||
return (int) $weight;
|
||||
}
|
||||
$weight = \Drupal::config('core.extension')->get("disabled.module.$module");
|
||||
if ($weight !== NULL) {
|
||||
return (int) $weight;
|
||||
}
|
||||
throw new InvalidArgumentException(format_string('The module %module is not installed.', array('%module' => $module)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for CURL extension enabled.
|
||||
*/
|
||||
function filefield_sources_curl_enabled() {
|
||||
return function_exists('curl_version');
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
route_callbacks:
|
||||
- '\Drupal\filefield_sources\Routing\FilefieldSourcesRoutes::routes'
|
||||
@@ -0,0 +1,12 @@
|
||||
services:
|
||||
plugin.manager.filefield_sources:
|
||||
class: Drupal\filefield_sources\FilefieldSourceManager
|
||||
parent: default_plugin_manager
|
||||
filefield_sources:
|
||||
alias: plugin.manager.filefield_sources
|
||||
access_check.filefield_sources.field:
|
||||
class: Drupal\filefield_sources\Access\FieldAccessCheck
|
||||
tags:
|
||||
- { name: access_check, applies_to: _access_filefield_sources_field }
|
||||
filefield_sources.imce_scanner:
|
||||
class: Drupal\filefield_sources\ImceScanner
|
||||
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* @file
|
||||
* Defines Javascript behaviors for the filefield_sources module.
|
||||
*/
|
||||
|
||||
(function ($, Drupal) {
|
||||
|
||||
"use strict";
|
||||
|
||||
// Behavior to add source options to configured fields.
|
||||
Drupal.behaviors.fileFieldSources = {};
|
||||
Drupal.behaviors.fileFieldSources.attach = function(context, settings) {
|
||||
$('div.filefield-sources-list:not(.filefield-sources-processed)', context).each(function() {
|
||||
$(this).addClass('filefield-sources-processed');
|
||||
var $fileFieldElement = $(this).parents('div.form-managed-file:first');
|
||||
$(this).find('a').click(function() {
|
||||
// Remove the active class.
|
||||
$(this).parents('div.filefield-sources-list').find('a.active').removeClass('active');
|
||||
|
||||
// Find the unique FileField Source class name.
|
||||
var fileFieldSourceClass = this.className.match(/filefield-source-[0-9a-z_]+/i)[0];
|
||||
|
||||
// The default upload element is a special case.
|
||||
if ($(this).is('.filefield-source-upload')) {
|
||||
$fileFieldElement.find('div.filefield-sources-list').siblings('.form-file, .form-submit').css('display', '');
|
||||
$fileFieldElement.find('div.filefield-source').css('display', 'none');
|
||||
}
|
||||
else {
|
||||
$fileFieldElement.find('div.filefield-sources-list').siblings('.form-file, .form-submit').css('display', 'none');
|
||||
$fileFieldElement.find('div.filefield-source').not('div.' + fileFieldSourceClass).css('display', 'none');
|
||||
$fileFieldElement.find('div.' + fileFieldSourceClass).css('display', '');
|
||||
}
|
||||
|
||||
// Add the active class.
|
||||
$(this).addClass('active');
|
||||
Drupal.fileFieldSources.updateHintText($fileFieldElement.get(0));
|
||||
}).first().triggerHandler('click');
|
||||
|
||||
// Clipboard support.
|
||||
$fileFieldElement.find('.filefield-source-clipboard-capture')
|
||||
.bind('paste', Drupal.fileFieldSources.pasteEvent)
|
||||
.bind('focus', Drupal.fileFieldSources.pasteFocus)
|
||||
.bind('blur', Drupal.fileFieldSources.pasteBlur);
|
||||
|
||||
// Imce support.
|
||||
$fileFieldElement.find('.filefield-sources-imce-browse')
|
||||
.bind('click', Drupal.fileFieldSources.imceBrowse);
|
||||
});
|
||||
|
||||
if (context === document) {
|
||||
$('form').submit(function() {
|
||||
Drupal.fileFieldSources.removeHintText();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Helper functions used by FileField Sources.
|
||||
Drupal.fileFieldSources = {
|
||||
/**
|
||||
* Update the hint text when clicking between source types.
|
||||
*/
|
||||
updateHintText: function(fileFieldElement) {
|
||||
// Add default value hint text to text fields.
|
||||
$(fileFieldElement).find('div.filefield-source').each(function() {
|
||||
var matches = this.className.match(/filefield-source-([a-z]+)/);
|
||||
var sourceType = matches[1];
|
||||
var textfield = $(this).find('input.form-text:first').get(0);
|
||||
var defaultText = (drupalSettings.fileFieldSources && drupalSettings.fileFieldSources[sourceType]) ? drupalSettings.fileFieldSources[sourceType].hintText : '';
|
||||
|
||||
// If the field doesn't exist, just return.
|
||||
if (!textfield) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If this field is not shown, remove its value and be done.
|
||||
if (!$(this).is(':visible') && textfield.value == defaultText) {
|
||||
textfield.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Set a default value:
|
||||
if (textfield.value == '') {
|
||||
textfield.value = defaultText;
|
||||
}
|
||||
|
||||
// Set a default class.
|
||||
if (textfield.value == defaultText) {
|
||||
$(textfield).addClass('hint');
|
||||
}
|
||||
|
||||
$(textfield).focus(hideHintText);
|
||||
$(textfield).blur(showHintText);
|
||||
|
||||
function showHintText() {
|
||||
if (this.value == '') {
|
||||
this.value = defaultText;
|
||||
$(this).addClass('hint');
|
||||
}
|
||||
}
|
||||
|
||||
function hideHintText() {
|
||||
if (this.value == defaultText) {
|
||||
this.value = '';
|
||||
$(this).removeClass('hint');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete all hint text from a form before submit.
|
||||
*/
|
||||
removeHintText: function() {
|
||||
$('div.filefield-source input.hint').val('').removeClass('hint');
|
||||
},
|
||||
|
||||
/**
|
||||
* Clean up the default value on focus.
|
||||
*/
|
||||
pasteFocus: function(e) {
|
||||
// Set default text.
|
||||
if (!this.defaultText) {
|
||||
this.defaultText = this.innerHTML;
|
||||
this.innerHTML = '';
|
||||
}
|
||||
// Remove non-text nodes.
|
||||
$(this).children().remove();
|
||||
},
|
||||
|
||||
/**
|
||||
* Restore default value on blur.
|
||||
*/
|
||||
pasteBlur: function(e) {
|
||||
if (this.defaultText && !this.innerHTML) {
|
||||
this.innerHTML = this.defaultText;
|
||||
}
|
||||
},
|
||||
|
||||
pasteEvent: function(e) {
|
||||
var clipboardData = null;
|
||||
var targetElement = this;
|
||||
var userAgent = navigator.userAgent.toLowerCase();
|
||||
|
||||
// Chrome.
|
||||
if (window.event && window.event.clipboardData && window.event.clipboardData.items) {
|
||||
clipboardData = window.event.clipboardData;
|
||||
}
|
||||
// All browsers in the future (hopefully).
|
||||
else if (e.originalEvent && e.originalEvent.clipboardData && e.originalEvent.clipboardData.items) {
|
||||
clipboardData = e.originalEvent.clipboardData;
|
||||
}
|
||||
// Firefox with content editable pastes as img tag with data href.
|
||||
else if (userAgent.match(/mozilla/) && !userAgent.match(/webkit/)) {
|
||||
Drupal.fileFieldSources.waitForPaste(targetElement);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
Drupal.fileFieldSources.pasteError(targetElement, Drupal.t('Paste from clipboard not supported in this browser.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
var items = clipboardData.items;
|
||||
var types = clipboardData.types;
|
||||
var filename = targetElement.firstChild ? targetElement.firstChild.textContent : '';
|
||||
|
||||
// Handle files and image content directly in the clipboard.
|
||||
var fileFound = false;
|
||||
for (var n = 0; n < items.length; n++) {
|
||||
if (items[n] && items[n].kind === 'file') {
|
||||
var fileBlob = items[n].getAsFile();
|
||||
var fileReader = new FileReader();
|
||||
// Define events to fire after the file is read into memory.
|
||||
fileReader.onload = function() {
|
||||
Drupal.fileFieldSources.pasteSubmit(targetElement, filename, this.result);
|
||||
};
|
||||
fileReader.onerror = function() {
|
||||
Drupal.fileFieldSources.pasteError(targetElement, Drupal.t('Error reading file from clipboard.'));
|
||||
};
|
||||
// Read in the file to fire the above events.
|
||||
fileReader.readAsDataURL(fileBlob);
|
||||
fileFound = true;
|
||||
break;
|
||||
}
|
||||
// Handle files that a copy/pasted as a file reference.
|
||||
/* if (types[n] && types[n] === 'Files') {
|
||||
TODO: Figure out how to capture copy/paste of entire files from desktop.
|
||||
}*/
|
||||
}
|
||||
if (!fileFound) {
|
||||
Drupal.fileFieldSources.pasteError(targetElement, Drupal.t('No file in clipboard.'));
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* For browsers that don't support native clipboardData attributes.
|
||||
*/
|
||||
waitForPaste: function(targetElement) {
|
||||
if (targetElement.children && targetElement.children.length > 0) {
|
||||
var filename = targetElement.firstChild ? targetElement.firstChild.textContent : '';
|
||||
var tagFound = false;
|
||||
$(targetElement).find('img[src^="data:image"]').each(function(n, element) {
|
||||
Drupal.fileFieldSources.pasteSubmit(targetElement, filename, element.src);
|
||||
tagFound = true;
|
||||
});
|
||||
$(targetElement).html(filename);
|
||||
if (!tagFound) {
|
||||
Drupal.fileFieldSources.pasteError(targetElement, Drupal.t('No file in clipboard.'));
|
||||
}
|
||||
}
|
||||
else {
|
||||
setTimeout(function() {
|
||||
Drupal.fileFieldSources.waitForPaste(targetElement);
|
||||
}, 200);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set an error on the paste field temporarily then clear it.
|
||||
*/
|
||||
pasteError: function(domElement, errorMessage) {
|
||||
var $description = $(domElement).parents('.filefield-source-clipboard:first').find('.description');
|
||||
if (!$description.data('originalDescription')) {
|
||||
$description.data('originalDescription', $description.html())
|
||||
}
|
||||
$description.html(errorMessage);
|
||||
var errorTimeout = setTimeout(function() {
|
||||
$description.html($description.data('originalDescription'));
|
||||
$(this).unbind('click.pasteError');
|
||||
}, 3000);
|
||||
$(domElement).bind('click.pasteError', function() {
|
||||
clearTimeout(errorTimeout);
|
||||
$description.html($description.data('originalDescription'));
|
||||
$(this).unbind('click.pasteError');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* After retreiving a clipboard, post the results to the server.
|
||||
*/
|
||||
pasteSubmit: function(targetElement, filename, contents) {
|
||||
var $wrapper = $(targetElement).parents('.filefield-source-clipboard');
|
||||
$wrapper.find('.filefield-source-clipboard-filename').val(filename);
|
||||
$wrapper.find('.filefield-source-clipboard-contents').val(contents);
|
||||
$wrapper.find('input.form-submit').trigger('mousedown');
|
||||
},
|
||||
|
||||
/**
|
||||
* Click event for the imce browse link.
|
||||
*/
|
||||
imceBrowse: function (e) {
|
||||
window.open(this.href, '', 'width=760,height=560,resizable=1');
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
// Override triggerUploadButton method from file.js.
|
||||
Drupal.file.triggerUploadButton = function (event) {
|
||||
$(event.target).closest('.form-managed-file').find('.form-submit.upload-button').trigger('mousedown');
|
||||
}
|
||||
|
||||
})(jQuery, Drupal);
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\filefield_sources\Access\FieldAccessCheck.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\Access;
|
||||
|
||||
use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Access check for file field source routes.
|
||||
*/
|
||||
class FieldAccessCheck implements RoutingAccessInterface {
|
||||
|
||||
/**
|
||||
* Checks access.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* Entity type.
|
||||
* @param string $bundle_name
|
||||
* Bundle name.
|
||||
* @param string $field_name
|
||||
* Field name.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The currently logged in account.
|
||||
*
|
||||
* @return string
|
||||
* A \Drupal\Core\Access\AccessInterface constant value.
|
||||
*/
|
||||
public function access($entity_type, $bundle_name, $field_name, AccountInterface $account) {
|
||||
$field = entity_load('field_config', $entity_type . '.' . $bundle_name . '.' . $field_name);
|
||||
return $field->access('edit', $account, TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\filefield_sources\Annotation\FilefieldSource.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a Search API processor annotation object.
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class FilefieldSource extends Plugin {
|
||||
|
||||
/**
|
||||
* The file field source plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The name of the file field source plugin.
|
||||
*
|
||||
* It will be displayed in a select list.
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* The human-readable name of the file field source plugin.
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*/
|
||||
public $label;
|
||||
|
||||
/**
|
||||
* The description of the file field source plugin.
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*/
|
||||
public $description;
|
||||
|
||||
/**
|
||||
* The weight of file field source plugin.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $weight;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\imce\Controller\ImceController.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\Controller;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\imce\Imce;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
|
||||
/**
|
||||
* Controller routines for imce routes.
|
||||
*/
|
||||
class ImceController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* Outputs the IMCE browser for FileField.
|
||||
*/
|
||||
public function page($entity_type, $bundle_name, $field_name, Request $request) {
|
||||
// Check access.
|
||||
if (!\Drupal::moduleHandler()->moduleExists('imce') || !Imce::access() || !$instance = entity_load('field_config', $entity_type . '.' . $bundle_name . '.' . $field_name)) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$settings = $instance->getSettings();
|
||||
$imceFM = Imce::userFM(\Drupal::currentUser(), $settings['uri_scheme'], $request);
|
||||
|
||||
// Override scanner.
|
||||
if (!empty($imceFM)) {
|
||||
$scanner = \Drupal::service('filefield_sources.imce_scanner');
|
||||
$widget = entity_get_form_display($entity_type, $bundle_name, 'default')->getComponent($field_name);
|
||||
// Full mode.
|
||||
if (!empty($widget['third_party_settings']['filefield_sources']['filefield_sources']['source_imce']['imce_mode'])) {
|
||||
$imceFM->setConf('scanner', array($scanner, 'customScanFull'));
|
||||
// Set context.
|
||||
$scanner->setContext(array(
|
||||
'scheme' => $imceFM->getConf('scheme'),
|
||||
));
|
||||
}
|
||||
// Restricted mode.
|
||||
else {
|
||||
$imceFM->setConf('scanner', array($scanner, 'customScanRestricted'));
|
||||
|
||||
// Make field directory the only accessible one.
|
||||
$field_uri = static::getUploadLocation($settings);
|
||||
static::disablePerms($imceFM, $field_uri, array('browse_files'));
|
||||
|
||||
// Set context.
|
||||
$scanner->setContext(array(
|
||||
'entity_type' => $entity_type,
|
||||
'field_name' => $field_name,
|
||||
'uri' => $field_uri,
|
||||
'is_rool' => $is_root,
|
||||
));
|
||||
}
|
||||
|
||||
// Disable absolute URLs.
|
||||
\Drupal::configFactory()->getEditable('imce.settings')->set('abs_urls', FALSE);
|
||||
|
||||
return $imceFM->pageResponse();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the URI for a file field.
|
||||
*
|
||||
* @param array $data
|
||||
* An array of token objects to pass to token_replace().
|
||||
*
|
||||
* @return string
|
||||
* A file directory URI with tokens replaced.
|
||||
*
|
||||
* @see token_replace()
|
||||
*/
|
||||
protected static function getUploadLocation($settings, $data = array()) {
|
||||
$destination = trim($settings['file_directory'], '/');
|
||||
|
||||
// Replace tokens. To ensure that render context is empty, pass a bubbleable
|
||||
// metadata object to the replace method.
|
||||
$bubbleable_metadata = new BubbleableMetadata();
|
||||
$destination = \Drupal::token()->replace($destination, $data, [], $bubbleable_metadata);
|
||||
|
||||
return $settings['uri_scheme'] . '://' . $destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable IMCE profile permissions.
|
||||
*/
|
||||
protected static function disablePerms($imceFM, $field_uri, $exceptions = array()) {
|
||||
$scheme = $imceFM->getConf('scheme');
|
||||
$root = $scheme . '://';
|
||||
$is_root = $field_uri == $root;
|
||||
$path = $is_root ? '.' : substr($field_uri, strlen($root));
|
||||
|
||||
$folders = $imceFM->getConf('folders');
|
||||
$perms = \Drupal::service('plugin.manager.imce.plugin')->permissionInfo();
|
||||
$folders['.']['permissions']['all'] = FALSE;
|
||||
$folders[$path]['permissions']['all'] = FALSE;
|
||||
foreach ($perms as $perm => $title) {
|
||||
$folders['.']['permissions'][$perm] = FALSE;
|
||||
$folders[$path]['permissions'][$perm] = in_array($perm, array('browse_files')) ? TRUE : FALSE;
|
||||
}
|
||||
$imceFM->setConf('folders', $folders);
|
||||
}
|
||||
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\filefield_sources\File\MimeType\ExtensionMimeTypeGuesser.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\File\MimeType;
|
||||
|
||||
use Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser as CoreExtensionMimeTypeGuesser;
|
||||
|
||||
/**
|
||||
* Add methods to core guesser.
|
||||
*/
|
||||
class ExtensionMimeTypeGuesser extends CoreExtensionMimeTypeGuesser {
|
||||
|
||||
/**
|
||||
* Convert mime type to extension.
|
||||
*
|
||||
* @param string $mimetype
|
||||
* Mime type.
|
||||
*
|
||||
* @return string|bool
|
||||
* Return extension if found, FALSE otherwise.
|
||||
*/
|
||||
public function convertMimeTypeToExtension($mimetype) {
|
||||
$this->checkDefaultMapping();
|
||||
|
||||
$mime_key = array_search($mimetype, $this->mapping['mimetypes']);
|
||||
$extension = array_search($mime_key, $this->mapping['extensions']);
|
||||
|
||||
return $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert mime type to most common extension.
|
||||
*
|
||||
* @param string $mimetype
|
||||
* Mime type.
|
||||
*
|
||||
* @return string|bool
|
||||
* Return extension if found, FALSE otherwise.
|
||||
*/
|
||||
public function convertMimeTypeToMostCommonExtension($mimetype) {
|
||||
$this->checkDefaultMapping();
|
||||
|
||||
$extension = FALSE;
|
||||
if (isset($mimetype)) {
|
||||
// See if this matches a known MIME type.
|
||||
$mime_key = array_search($mimetype, $this->mapping['mimetypes']);
|
||||
if ($mime_key !== FALSE) {
|
||||
// If we have a match, get this list of likely extensions. For some
|
||||
// reason Drupal lists the "most common" extension last for most file
|
||||
// types including php, jpg, and doc.
|
||||
if ($extensions = array_keys($this->mapping['extensions'], $mime_key)) {
|
||||
$extension = end($extensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for default mapping.
|
||||
*/
|
||||
private function checkDefaultMapping() {
|
||||
if ($this->mapping === NULL) {
|
||||
$mapping = $this->defaultMapping;
|
||||
// Allow modules to alter the default mapping.
|
||||
$this->moduleHandler->alter('file_mimetype_mapping', $mapping);
|
||||
$this->mapping = $mapping;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\filefield_sources\FilefieldSourceInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface for file field source plugins.
|
||||
*
|
||||
* @see \Drupal\filefield_sources\FilefieldSourceManager
|
||||
* @see \Drupal\filefield_sources\Annotation\FilefieldSource
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup filefield_sources
|
||||
*/
|
||||
interface FilefieldSourceInterface {
|
||||
|
||||
/**
|
||||
* Value callback for file field source plugin.
|
||||
*
|
||||
* @param array $element
|
||||
* An associative array containing the properties of the element.
|
||||
* @param mixed $input
|
||||
* The incoming input to populate the form element. If this is FALSE,
|
||||
* the element's default value should be returned.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*
|
||||
* @return mixed
|
||||
* The value to assign to the element.
|
||||
*/
|
||||
public static function value(array &$element, &$input, FormStateInterface $form_state);
|
||||
|
||||
/**
|
||||
* Process callback for file field source plugin.
|
||||
*
|
||||
* @param array $element
|
||||
* An associative array containing the properties and children of the
|
||||
* generic input element.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
* @param array $complete_form
|
||||
* The complete form structure.
|
||||
*
|
||||
* @return array
|
||||
* The processed element.
|
||||
*/
|
||||
public static function process(array &$element, FormStateInterface $form_state, array &$complete_form);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\filefield_sources\FilefieldSourceManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
|
||||
/**
|
||||
* Provides a plugin manager for file field source.
|
||||
*
|
||||
* @see \Drupal\filefield_sources\Annotation\FilefieldSource
|
||||
* @see \Drupal\filefield_sources\FilefieldSourceInterface
|
||||
* @see plugin_api
|
||||
*/
|
||||
class FilefieldSourceManager extends DefaultPluginManager {
|
||||
|
||||
/**
|
||||
* Constructs a FilefieldSourceManager object.
|
||||
*
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler to invoke the alter hook with.
|
||||
*/
|
||||
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
||||
$this->setCacheBackend($cache_backend, 'filefield_sources');
|
||||
|
||||
parent::__construct('Plugin/FilefieldSource', $namespaces, $module_handler, 'Drupal\filefield_sources\FilefieldSourceInterface', 'Drupal\filefield_sources\Annotation\FilefieldSource');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinitions() {
|
||||
$definitions = parent::getDefinitions();
|
||||
if (!\Drupal::moduleHandler()->moduleExists('imce')) {
|
||||
unset($definitions['imce']);
|
||||
}
|
||||
if (!filefield_sources_curl_enabled()) {
|
||||
unset($definitions['remote']);
|
||||
}
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\filefield_sources\FilefieldSourcesServiceProvider.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ServiceModifierInterface;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* Alter file.mime_type.guesser.extension service.
|
||||
*/
|
||||
class FilefieldSourcesServiceProvider implements ServiceModifierInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function alter(ContainerBuilder $container) {
|
||||
$definition = $container->getDefinition('file.mime_type.guesser.extension');
|
||||
$definition->setClass('Drupal\filefield_sources\File\MimeType\ExtensionMimeTypeGuesser');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\filefield_sources\ImceScanner.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources;
|
||||
|
||||
use Drupal\imce\Imce;
|
||||
|
||||
/**
|
||||
* Imce scanner service.
|
||||
*/
|
||||
class ImceScanner {
|
||||
|
||||
/**
|
||||
* Scanner context.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
private $context = NULL;
|
||||
|
||||
/**
|
||||
* Sets scanner context.
|
||||
*/
|
||||
public function setContext($context) {
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan and return files, subdirectories.
|
||||
*/
|
||||
public function customScanFull($dirname, $options) {
|
||||
// Get a list of files in the database for this directory.
|
||||
$scheme = $this->context['scheme'];
|
||||
$sql_uri_name = $dirname == '.' ? $scheme . '://' : $dirname . '/';
|
||||
|
||||
$result = db_select('file_managed', 'f')
|
||||
->fields('f', array('uri'))
|
||||
->condition('f.uri', $sql_uri_name . '%', 'LIKE')
|
||||
->condition('f.uri', $sql_uri_name . '_%/%', 'NOT LIKE')
|
||||
->execute();
|
||||
|
||||
$db_files = array();
|
||||
foreach ($result as $row) {
|
||||
$db_files[basename($row->uri)] = 1;
|
||||
}
|
||||
|
||||
// Get the default IMCE directory scan, then filter down to database files.
|
||||
$content = Imce::scanDir($dirname, $options);
|
||||
foreach ($content['files'] as $filename => $file) {
|
||||
if (!isset($db_files[$filename])) {
|
||||
unset($content['files'][$filename]);
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan directory and return file list.
|
||||
*
|
||||
* This only work on Restricted Mode.
|
||||
*/
|
||||
public function customScanRestricted($dirname, $options) {
|
||||
$content = array('files' => array(), 'subfolders' => array());
|
||||
$field_uri = $this->context['uri'];
|
||||
$is_root = $this->context['is_root'];
|
||||
|
||||
if ($dirname !== $field_uri) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$entity_type = $this->context['entity_type'];
|
||||
$field_name = $this->context['field_name'];
|
||||
$field_storage = entity_load('field_storage_config', $entity_type . '.' . $field_name);
|
||||
$entity_manager = \Drupal::entityManager();
|
||||
if ($entity_manager->hasDefinition($entity_type)) {
|
||||
$storage = $entity_manager->getStorage($entity_type);
|
||||
$table_mapping = $storage->getTableMapping();
|
||||
$field_table = $table_mapping->getDedicatedDataTableName($field_storage);
|
||||
$field_column_name = $table_mapping->getFieldColumnName($field_storage, 'target_id');
|
||||
|
||||
$sql_uri = $field_uri . ($is_root ? '' : '/');
|
||||
$query = db_select($field_table, 'cf');
|
||||
$query->innerJoin('file_managed', 'f', 'f.fid = cf.' . $field_column_name);
|
||||
$result = $query->fields('f')
|
||||
->condition('f.status', 1)
|
||||
->condition('f.uri', $sql_uri . '%', 'LIKE')
|
||||
->condition('f.uri', $sql_uri . '%/%', 'NOT LIKE')
|
||||
->execute();
|
||||
foreach ($result as $file) {
|
||||
// Get real name.
|
||||
$name = basename($file->uri);
|
||||
$content['files'][$name] = $file->uri;
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
+356
@@ -0,0 +1,356 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\filefield_sources\Plugin\FilefieldSource\Attach.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\Plugin\FilefieldSource;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\filefield_sources\FilefieldSourceInterface;
|
||||
use Drupal\Core\Field\WidgetInterface;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\Core\Template\Attribute;
|
||||
|
||||
/**
|
||||
* A FileField source plugin to allow use of files within a server directory.
|
||||
*
|
||||
* @FilefieldSource(
|
||||
* id = "attach",
|
||||
* name = @Translation("File attach from server directory"),
|
||||
* label = @Translation("File attach"),
|
||||
* description = @Translation("Select a file from a directory on the server."),
|
||||
* weight = 3
|
||||
* )
|
||||
*/
|
||||
class Attach implements FilefieldSourceInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function value(array &$element, &$input, FormStateInterface $form_state) {
|
||||
if (!empty($input['filefield_attach']['filename'])) {
|
||||
$instance = entity_load('field_config', $element['#entity_type'] . '.' . $element['#bundle'] . '.' . $element['#field_name']);
|
||||
$filepath = $input['filefield_attach']['filename'];
|
||||
|
||||
// Check that the destination is writable.
|
||||
$directory = $element['#upload_location'];
|
||||
$mode = Settings::get('file_chmod_directory', FILE_CHMOD_DIRECTORY);
|
||||
|
||||
// This first chmod check is for other systems such as S3, which don't
|
||||
// work with file_prepare_directory().
|
||||
if (!drupal_chmod($directory, $mode) && !file_prepare_directory($directory, FILE_CREATE_DIRECTORY)) {
|
||||
\Drupal::logger('filefield_sources')->log(E_NOTICE, 'File %file could not be copied, because the destination directory %destination is not configured correctly.', array(
|
||||
'%file' => $filepath,
|
||||
'%destination' => drupal_realpath($directory),
|
||||
));
|
||||
drupal_set_message(t('The specified file %file could not be copied, because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $filepath)), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up the file name extensions and transliterate.
|
||||
$original_filepath = $filepath;
|
||||
$new_filepath = filefield_sources_clean_filename($filepath, $instance->getSetting('file_extensions'));
|
||||
rename($filepath, $new_filepath);
|
||||
$filepath = $new_filepath;
|
||||
|
||||
// Run all the normal validations, minus file size restrictions.
|
||||
$validators = $element['#upload_validators'];
|
||||
if (isset($validators['file_validate_size'])) {
|
||||
unset($validators['file_validate_size']);
|
||||
}
|
||||
|
||||
// Save the file to the new location.
|
||||
if ($file = filefield_sources_save_file($filepath, $validators, $directory)) {
|
||||
if (!in_array($file->id(), $input['fids'])) {
|
||||
$input['fids'][] = $file->id();
|
||||
}
|
||||
|
||||
// Delete the original file if "moving" the file instead of copying.
|
||||
if ($element['#filefield_sources_settings']['source_attach']['attach_mode'] !== FILEFIELD_SOURCE_ATTACH_MODE_COPY) {
|
||||
@unlink($filepath);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the original file name if the file still exists.
|
||||
if (file_exists($filepath) && $filepath != $original_filepath) {
|
||||
rename($filepath, $original_filepath);
|
||||
}
|
||||
|
||||
$input['filefield_attach']['filename'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function process(array &$element, FormStateInterface $form_state, array &$complete_form) {
|
||||
$settings = $element['#filefield_sources_settings']['source_attach'];
|
||||
$field_name = $element['#field_name'];
|
||||
$instance = entity_load('field_config', $element['#entity_type'] . '.' . $element['#bundle'] . '.' . $field_name);
|
||||
|
||||
$element['filefield_attach'] = array(
|
||||
'#weight' => 100.5,
|
||||
'#theme' => 'filefield_sources_element',
|
||||
'#source_id' => 'attach',
|
||||
// Required for proper theming.
|
||||
'#filefield_source' => TRUE,
|
||||
);
|
||||
|
||||
$path = static::getDirectory($settings);
|
||||
$options = static::getAttachOptions($path, $instance->getSetting('file_extensions'));
|
||||
|
||||
// If we have built this element before, append the list of options that we
|
||||
// had previously. This allows files to be deleted after copying them and
|
||||
// still be considered a valid option during the validation and submit.
|
||||
$triggering_element = $form_state->getTriggeringElement();
|
||||
$property = array(
|
||||
'filefield_sources',
|
||||
$field_name,
|
||||
'attach_options',
|
||||
);
|
||||
if (!isset($triggering_element) && $form_state->has($property)) {
|
||||
$attach_options = $form_state->get($property);
|
||||
$options = $options + $attach_options;
|
||||
}
|
||||
// On initial form build and rebuilds after processing input, save the
|
||||
// original list of options so they can be restored in the line above.
|
||||
else {
|
||||
$form_state->set(array('filefield_sources', $field_name, 'attach_options'), $options);
|
||||
}
|
||||
|
||||
$description = t('This method may be used to attach files that exceed the file size limit. Files may be attached from the %directory directory on the server, usually uploaded through FTP.', array('%directory' => realpath($path)));
|
||||
|
||||
// Error messages.
|
||||
if ($options === FALSE || empty($settings['path'])) {
|
||||
$attach_message = t('A file attach directory could not be located.');
|
||||
$attach_description = t('Please check your settings for the %field field.', array('%field' => $instance->getLabel()));
|
||||
}
|
||||
elseif (!count($options)) {
|
||||
$attach_message = t('There currently are no files to attach.');
|
||||
$attach_description = $description;
|
||||
}
|
||||
|
||||
if (isset($attach_message)) {
|
||||
$element['filefield_attach']['attach_message'] = array(
|
||||
'#markup' => $attach_message,
|
||||
);
|
||||
$element['filefield_attach']['#description'] = $attach_description;
|
||||
}
|
||||
else {
|
||||
$validators = $element['#upload_validators'];
|
||||
if (isset($validators['file_validate_size'])) {
|
||||
unset($validators['file_validate_size']);
|
||||
}
|
||||
$description .= '<br />' . filefield_sources_element_validation_help($validators);
|
||||
$element['filefield_attach']['filename'] = array(
|
||||
'#type' => 'select',
|
||||
'#options' => $options,
|
||||
);
|
||||
$element['filefield_attach']['#description'] = $description;
|
||||
}
|
||||
$class = '\Drupal\file\Element\ManagedFile';
|
||||
$ajax_settings = [
|
||||
'callback' => [$class, 'uploadAjaxCallback'],
|
||||
'options' => [
|
||||
'query' => [
|
||||
'element_parents' => implode('/', $element['#array_parents']),
|
||||
],
|
||||
],
|
||||
'wrapper' => $element['upload_button']['#ajax']['wrapper'],
|
||||
'effect' => 'fade',
|
||||
];
|
||||
$element['filefield_attach']['attach'] = [
|
||||
'#name' => implode('_', $element['#parents']) . '_attach',
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Attach'),
|
||||
'#validate' => [],
|
||||
'#submit' => ['filefield_sources_field_submit'],
|
||||
'#limit_validation_errors' => [$element['#parents']],
|
||||
'#ajax' => $ajax_settings,
|
||||
];
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme the output of the attach element.
|
||||
*/
|
||||
public static function element($variables) {
|
||||
$element = $variables['element'];
|
||||
if (isset($element['attach_message'])) {
|
||||
$output = $element['attach_message']['#markup'];
|
||||
}
|
||||
elseif (isset($element['filename'])) {
|
||||
// Get rendered options.
|
||||
$options = form_select_options($element['filename']);
|
||||
$option_output = '';
|
||||
foreach ($options as $key => $value) {
|
||||
$option_output .= '<option value=' . $value["value"] . '>' . $value["label"] . '</option>';
|
||||
}
|
||||
// Get rendered select.
|
||||
$size = !empty($element['filename']['#size']) ? ' size="' . $element['filename']['#size'] . '"' : '';
|
||||
$element['filename']['#attributes']['class'][] = 'form-select';
|
||||
$multiple = !empty($element['#multiple']);
|
||||
$output = '<select name="' . $element['filename']['#name'] . '' . ($multiple ? '[]' : '') . '"' . ($multiple ? ' multiple="multiple" ' : '') . new Attribute($element['filename']['#attributes']) . ' id="' . $element['filename']['#id'] . '" ' . $size . '>' . $option_output . '</select>';
|
||||
}
|
||||
|
||||
$output .= drupal_render($element['attach']);
|
||||
$element['#children'] = $output;
|
||||
$element['#theme_wrappers'] = array('form_element');
|
||||
return '<div class="filefield-source filefield-source-attach clear-block">' . drupal_render($element) . '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get directory from settings.
|
||||
*
|
||||
* @param array $settings
|
||||
* Attach source's settings.
|
||||
* @param object $account
|
||||
* User to replace token.
|
||||
*
|
||||
* @return string
|
||||
* Path that contains files to attach.
|
||||
*/
|
||||
protected static function getDirectory(array $settings, $account = NULL) {
|
||||
$account = isset($account) ? $account : \Drupal::currentUser();
|
||||
$path = $settings['path'];
|
||||
$absolute = !empty($settings['absolute']);
|
||||
|
||||
// Replace user level tokens.
|
||||
// Node level tokens require a lot of complexity like temporary storage
|
||||
// locations when values don't exist. See the filefield_paths module.
|
||||
if (\Drupal::moduleHandler()->moduleExists('token')) {
|
||||
$token = \Drupal::token();
|
||||
$path = $token->replace($path, array('user' => $account));
|
||||
}
|
||||
|
||||
return $absolute ? $path : file_default_scheme() . '://' . $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attach options.
|
||||
*
|
||||
* @param string $path
|
||||
* Path to scan files.
|
||||
* @param string $extensions
|
||||
* Path to scan files.
|
||||
*
|
||||
* @return array
|
||||
* List of options.
|
||||
*/
|
||||
protected static function getAttachOptions($path, $extensions = FALSE) {
|
||||
if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY)) {
|
||||
drupal_set_message(t('Specified file attach path %path must exist or be writable.', array('%path' => $path)), 'error');
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$options = array();
|
||||
$pattern = !empty($extensions) ? '/\.(' . strtr($extensions, ' ', '|') . ')$/' : '/.*/';
|
||||
$files = file_scan_directory($path, $pattern);
|
||||
|
||||
if (count($files)) {
|
||||
$options = array('' => t('-- Select file --'));
|
||||
foreach ($files as $file) {
|
||||
$options[$file->uri] = str_replace($path . '/', '', $file->uri);
|
||||
}
|
||||
natcasesort($options);
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_filefield_source_settings().
|
||||
*/
|
||||
public static function settings(WidgetInterface $plugin) {
|
||||
$settings = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources', array(
|
||||
'source_attach' => array(
|
||||
'path' => FILEFIELD_SOURCE_ATTACH_DEFAULT_PATH,
|
||||
'absolute' => FILEFIELD_SOURCE_ATTACH_RELATIVE,
|
||||
'attach_mode' => FILEFIELD_SOURCE_ATTACH_MODE_MOVE,
|
||||
),
|
||||
));
|
||||
|
||||
$return['source_attach'] = array(
|
||||
'#title' => t('File attach settings'),
|
||||
'#type' => 'details',
|
||||
'#description' => t('File attach allows for selecting a file from a directory on the server, commonly used in combination with FTP.') . ' <strong>' . t('This file source will ignore file size checking when used.') . '</strong>',
|
||||
'#element_validate' => array(array(get_called_class(), 'filePathValidate')),
|
||||
'#weight' => 3,
|
||||
);
|
||||
$return['source_attach']['path'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('File attach path'),
|
||||
'#default_value' => $settings['source_attach']['path'],
|
||||
'#size' => 60,
|
||||
'#maxlength' => 128,
|
||||
'#description' => t('The directory within the <em>File attach location</em> that will contain attachable files.'),
|
||||
);
|
||||
if (\Drupal::moduleHandler()->moduleExists('token')) {
|
||||
$return['source_attach']['tokens'] = array(
|
||||
'#theme' => 'token_tree',
|
||||
'#token_types' => array('user'),
|
||||
);
|
||||
}
|
||||
$return['source_attach']['absolute'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('File attach location'),
|
||||
'#options' => array(
|
||||
FILEFIELD_SOURCE_ATTACH_RELATIVE => t('Within the files directory'),
|
||||
FILEFIELD_SOURCE_ATTACH_ABSOLUTE => t('Absolute server path'),
|
||||
),
|
||||
'#default_value' => $settings['source_attach']['absolute'],
|
||||
'#description' => t('The <em>File attach path</em> may be with the files directory (%file_directory) or from the root of your server. If an absolute path is used and it does not start with a "/" your path will be relative to your site directory: %realpath.', array('%file_directory' => drupal_realpath(file_default_scheme() . '://'), '%realpath' => realpath('./'))),
|
||||
);
|
||||
$return['source_attach']['attach_mode'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Attach method'),
|
||||
'#options' => array(
|
||||
FILEFIELD_SOURCE_ATTACH_MODE_MOVE => t('Move the file directly to the final location'),
|
||||
FILEFIELD_SOURCE_ATTACH_MODE_COPY => t('Leave a copy of the file in the attach directory'),
|
||||
),
|
||||
'#default_value' => isset($settings['source_attach']['attach_mode']) ? $settings['source_attach']['attach_mode'] : 'move',
|
||||
);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate file path.
|
||||
*
|
||||
* @param array $element
|
||||
* Form element.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* Form state.
|
||||
* @param array $complete_form
|
||||
* Complete form.
|
||||
*/
|
||||
public static function filePathValidate(array &$element, FormStateInterface $form_state, array &$complete_form) {
|
||||
$parents = $element['#parents'];
|
||||
array_pop($parents);
|
||||
$input_exists = FALSE;
|
||||
|
||||
// Get input of the whole parent element.
|
||||
$input = NestedArray::getValue($form_state->getValues(), $parents, $input_exists);
|
||||
if ($input_exists) {
|
||||
// Only validate if this source is enabled.
|
||||
if (!$input['sources']['attach']) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Strip slashes from the end of the file path.
|
||||
$filepath = rtrim($element['path']['#value'], '\\/');
|
||||
$form_state->setValueForElement($element['path'], $filepath);
|
||||
$filepath = static::getDirectory($input['source_attach']);
|
||||
|
||||
// Check that the directory exists and is writable.
|
||||
if (!file_prepare_directory($filepath, FILE_CREATE_DIRECTORY)) {
|
||||
$form_state->setError($element['path'], t('Specified file attach path %path must exist or be writable.', array('%path' => $filepath)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\filefield_sources\Plugin\FilefieldSource\Clipboard.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\Plugin\FilefieldSource;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\filefield_sources\FilefieldSourceInterface;
|
||||
use Drupal\Core\Site\Settings;
|
||||
|
||||
/**
|
||||
* A FileField source plugin to allow transfer of files through the clipboard.
|
||||
*
|
||||
* @FilefieldSource(
|
||||
* id = "clipboard",
|
||||
* name = @Translation("Paste from clipboard (<a href=""http://drupal.org/node/1775902"">limited browser support</a>)"),
|
||||
* label = @Translation("Clipboard"),
|
||||
* description = @Translation("Allow users to paste a file directly from the clipboard."),
|
||||
* weight = 1
|
||||
* )
|
||||
*/
|
||||
class Clipboard implements FilefieldSourceInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function value(array &$element, &$input, FormStateInterface $form_state) {
|
||||
if (isset($input['filefield_clipboard']['contents']) && strlen($input['filefield_clipboard']['contents']) > 0) {
|
||||
// Check that the destination is writable.
|
||||
$temporary_directory = 'temporary://';
|
||||
if (!file_prepare_directory($temporary_directory, FILE_MODIFY_PERMISSIONS)) {
|
||||
\Drupal::logger('filefield_sources')->log(E_NOTICE, 'The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => drupal_realpath($temporary_directory)));
|
||||
drupal_set_message(t('The file could not be transferred because the temporary directory is not writable.'), 'error');
|
||||
return;
|
||||
}
|
||||
// Check that the destination is writable.
|
||||
$directory = $element['#upload_location'];
|
||||
$mode = Settings::get('file_chmod_directory', FILE_CHMOD_DIRECTORY);
|
||||
|
||||
// This first chmod check is for other systems such as S3, which don't
|
||||
// work with file_prepare_directory().
|
||||
if (!drupal_chmod($directory, $mode) && !file_prepare_directory($directory, FILE_CREATE_DIRECTORY)) {
|
||||
$url = $input['filefield_clipboard']['filename'];
|
||||
\Drupal::logger('filefield_sources')->log(E_NOTICE, 'File %file could not be copied, because the destination directory %destination is not configured correctly.', array('%file' => $url, '%destination' => drupal_realpath($directory)));
|
||||
drupal_set_message(t('The specified file %file could not be copied, because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $url)), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Split the file information in mimetype and base64 encoded binary.
|
||||
$base64_data = $input['filefield_clipboard']['contents'];
|
||||
$comma_position = strpos($base64_data, ',');
|
||||
$semicolon_position = strpos($base64_data, ';');
|
||||
$file_contents = base64_decode(substr($base64_data, $comma_position + 1));
|
||||
$mimetype = substr($base64_data, 5, $semicolon_position - 5);
|
||||
|
||||
$extension = \Drupal::service('file.mime_type.guesser.extension')->convertMimeTypeToExtension($mimetype);
|
||||
|
||||
$filename = trim($input['filefield_clipboard']['filename']);
|
||||
$filename = preg_replace('/\.[a-z0-9]{3,4}$/', '', $filename);
|
||||
$filename = (empty($filename) ? 'paste_' . REQUEST_TIME : $filename) . '.' . $extension;
|
||||
$filepath = file_create_filename($filename, $temporary_directory);
|
||||
|
||||
$copy_success = FALSE;
|
||||
if ($fp = @fopen($filepath, 'w')) {
|
||||
fwrite($fp, $file_contents);
|
||||
fclose($fp);
|
||||
$copy_success = TRUE;
|
||||
}
|
||||
|
||||
if ($copy_success && $file = filefield_sources_save_file($filepath, $element['#upload_validators'], $element['#upload_location'])) {
|
||||
if (!in_array($file->id(), $input['fids'])) {
|
||||
$input['fids'][] = $file->id();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the temporary file generated from paste.
|
||||
@unlink($filepath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function process(array &$element, FormStateInterface $form_state, array &$complete_form) {
|
||||
$element['filefield_clipboard'] = array(
|
||||
'#weight' => 100.5,
|
||||
'#theme' => 'filefield_sources_element',
|
||||
'#source_id' => 'clipboard',
|
||||
// Required for proper theming.
|
||||
'#filefield_source' => TRUE,
|
||||
'#filefield_sources_hint_text' => t('Enter filename then paste.'),
|
||||
'#description' => filefield_sources_element_validation_help($element['#upload_validators']),
|
||||
);
|
||||
|
||||
$element['filefield_clipboard']['capture'] = array(
|
||||
'#type' => 'item',
|
||||
'#markup' => '<div class="filefield-source-clipboard-capture" contenteditable="true"><span class="hint">example_filename.png</span></div> <span class="hint">' . t('ctrl + v') . '</span>',
|
||||
'#description' => t('Enter a file name and paste an image from the clipboard. This feature only works in <a href="http://drupal.org/node/1775902">limited browsers</a>.'),
|
||||
);
|
||||
|
||||
$element['filefield_clipboard']['filename'] = array(
|
||||
'#type' => 'hidden',
|
||||
'#attributes' => array('class' => array('filefield-source-clipboard-filename')),
|
||||
);
|
||||
$element['filefield_clipboard']['contents'] = array(
|
||||
'#type' => 'hidden',
|
||||
'#attributes' => array('class' => array('filefield-source-clipboard-contents')),
|
||||
);
|
||||
|
||||
$class = '\Drupal\file\Element\ManagedFile';
|
||||
$ajax_settings = [
|
||||
'callback' => [$class, 'uploadAjaxCallback'],
|
||||
'options' => [
|
||||
'query' => [
|
||||
'element_parents' => implode('/', $element['#array_parents']),
|
||||
],
|
||||
],
|
||||
'wrapper' => $element['upload_button']['#ajax']['wrapper'],
|
||||
'effect' => 'fade',
|
||||
'progress' => [
|
||||
'type' => 'throbber',
|
||||
'message' => t('Transfering file...'),
|
||||
],
|
||||
];
|
||||
|
||||
$element['filefield_clipboard']['upload'] = [
|
||||
'#name' => implode('_', $element['#parents']) . '_clipboard_upload_button',
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Upload'),
|
||||
'#attributes' => ['class' => ['js-hide']],
|
||||
'#validate' => [],
|
||||
'#submit' => ['filefield_sources_field_submit'],
|
||||
'#limit_validation_errors' => [$element['#parents']],
|
||||
'#ajax' => $ajax_settings,
|
||||
];
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme the output of the clipboard element.
|
||||
*/
|
||||
public static function element($variables) {
|
||||
$element = $variables['element'];
|
||||
|
||||
return '<div class="filefield-source filefield-source-clipboard clear-block">' . drupal_render_children($element) . '</div>';
|
||||
}
|
||||
|
||||
}
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\filefield_sources\Plugin\FilefieldSource\Imce.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\Plugin\FilefieldSource;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\filefield_sources\FilefieldSourceInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Drupal\Core\Field\WidgetInterface;
|
||||
|
||||
/**
|
||||
* A FileField source plugin to allow referencing of files from IMCE.
|
||||
*
|
||||
* @FilefieldSource(
|
||||
* id = "imce",
|
||||
* name = @Translation("IMCE file browser"),
|
||||
* label = @Translation("File browser"),
|
||||
* description = @Translation("Select a file to use from a file browser."),
|
||||
* weight = -1
|
||||
* )
|
||||
*/
|
||||
class Imce implements FilefieldSourceInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function value(array &$element, &$input, FormStateInterface $form_state) {
|
||||
if (isset($input['filefield_imce']['imce_paths']) && $input['filefield_imce']['imce_paths'] != '') {
|
||||
$instance = entity_load('field_config', $element['#entity_type'] . '.' . $element['#bundle'] . '.' . $element['#field_name']);
|
||||
$field_settings = $instance->getSettings();
|
||||
$scheme = $field_settings['uri_scheme'];
|
||||
$imce_paths = explode(':', $input['filefield_imce']['imce_paths']);
|
||||
$uris = [];
|
||||
|
||||
foreach ($imce_paths as $imce_path) {
|
||||
//$wrapper = \Drupal::service('stream_wrapper_manager')->getViaScheme($scheme);
|
||||
//$file_directory_prefix = $scheme == 'private' ? 'system/files' : $wrapper->getDirectoryPath();
|
||||
//$uri = preg_replace('/^' . preg_quote(base_path() . $file_directory_prefix . '/', '/') . '/', $scheme . '://', $imce_path);
|
||||
$uri = rawurldecode($scheme . '://' . $imce_path);
|
||||
$uris[] = $uri;
|
||||
}
|
||||
|
||||
// Resolve the file path to an FID.
|
||||
$fids = db_select('file_managed', 'f')
|
||||
->condition('uri', $uris, 'IN')
|
||||
->fields('f', array('fid'))
|
||||
->execute()
|
||||
->fetchCol();
|
||||
if ($fids) {
|
||||
$files = file_load_multiple($fids);
|
||||
foreach ($files as $file) {
|
||||
if (filefield_sources_element_validate($element, $file, $form_state)) {
|
||||
if (!in_array($file->id(), $input['fids'])) {
|
||||
$input['fids'][] = $file->id();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$form_state->setError($element, t('The selected file could not be used because the file does not exist in the database.'));
|
||||
}
|
||||
// No matter what happens, clear the value from the file path field.
|
||||
$input['filefield_imce']['imce_paths'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function process(array &$element, FormStateInterface $form_state, array &$complete_form) {
|
||||
$instance = entity_load('field_config', $element['#entity_type'] . '.' . $element['#bundle'] . '.' . $element['#field_name']);
|
||||
|
||||
$element['filefield_imce'] = array(
|
||||
'#weight' => 100.5,
|
||||
'#theme' => 'filefield_sources_element',
|
||||
'#source_id' => 'imce',
|
||||
// Required for proper theming.
|
||||
'#filefield_source' => TRUE,
|
||||
'#description' => filefield_sources_element_validation_help($element['#upload_validators']),
|
||||
);
|
||||
|
||||
$imce_url = \Drupal::url('filefield_sources.imce', array(
|
||||
'entity_type' => $element['#entity_type'],
|
||||
'bundle_name' => $element['#bundle'],
|
||||
'field_name' => $element['#field_name'],
|
||||
),
|
||||
array(
|
||||
'query' => array(
|
||||
'sendto' => 'imceFileField.sendto',
|
||||
'fieldId' => $element['#attributes']['data-drupal-selector'] . '-filefield-imce',
|
||||
),
|
||||
));
|
||||
$element['filefield_imce']['browse'] = array(
|
||||
'#type' => 'markup',
|
||||
'#markup' => '<span>' . t('No file selected') . '</span> (<a class="filefield-sources-imce-browse" href="' . $imce_url . '">' . t('browse') . '</a>)',
|
||||
);
|
||||
|
||||
$element['#attached']['library'][] = 'imce/drupal.imce.filefield';
|
||||
// Set the pre-renderer to conditionally disable the elements.
|
||||
$element['#pre_render'][] = array(get_called_class(), 'preRenderWidget');
|
||||
|
||||
// Path input
|
||||
$element['filefield_imce']['imce_paths'] = array(
|
||||
'#type' => 'hidden',
|
||||
// Reset value to prevent consistent errors
|
||||
'#value' => '',
|
||||
);
|
||||
|
||||
$class = '\Drupal\file\Element\ManagedFile';
|
||||
$ajax_settings = [
|
||||
'callback' => [$class, 'uploadAjaxCallback'],
|
||||
'options' => [
|
||||
'query' => [
|
||||
'element_parents' => implode('/', $element['#array_parents']),
|
||||
],
|
||||
],
|
||||
'wrapper' => $element['upload_button']['#ajax']['wrapper'],
|
||||
'effect' => 'fade',
|
||||
];
|
||||
|
||||
$element['filefield_imce']['imce_button'] = array(
|
||||
'#name' => implode('_', $element['#parents']) . '_imce_select',
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Select'),
|
||||
'#attributes' => ['class' => ['js-hide']],
|
||||
'#validate' => [],
|
||||
'#submit' => ['filefield_sources_field_submit'],
|
||||
'#limit_validation_errors' => [$element['#parents']],
|
||||
'#ajax' => $ajax_settings,
|
||||
);
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme the output of the imce element.
|
||||
*/
|
||||
public static function element($variables) {
|
||||
$element = $variables['element'];
|
||||
|
||||
$output = drupal_render_children($element);
|
||||
return '<div class="filefield-source filefield-source-imce clear-block">' . $output . '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Define routes for Imce source.
|
||||
*
|
||||
* @return array
|
||||
* Array of routes.
|
||||
*/
|
||||
public static function routes() {
|
||||
$routes = array();
|
||||
|
||||
$routes['filefield_sources.imce'] = new Route(
|
||||
'/file/imce/{entity_type}/{bundle_name}/{field_name}',
|
||||
array(
|
||||
'_controller' => '\Drupal\filefield_sources\Controller\ImceController::page',
|
||||
'_title' => 'File Manager',
|
||||
),
|
||||
array(
|
||||
'_access_filefield_sources_field' => 'TRUE',
|
||||
)
|
||||
);
|
||||
|
||||
return $routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_filefield_source_settings().
|
||||
*/
|
||||
public static function settings(WidgetInterface $plugin) {
|
||||
$settings = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources', array(
|
||||
'source_imce' => array(
|
||||
'imce_mode' => 0,
|
||||
),
|
||||
));
|
||||
|
||||
$return['source_imce'] = array(
|
||||
'#title' => t('IMCE file browser settings'),
|
||||
'#type' => 'details',
|
||||
'#access' => \Drupal::moduleHandler()->moduleExists('imce'),
|
||||
);
|
||||
|
||||
// $imce_admin_url = \Drupal::url('imce.admin');
|
||||
$imce_admin_url = 'admin/config/media/imce';
|
||||
$return['source_imce']['imce_mode'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('File browser mode'),
|
||||
'#options' => array(
|
||||
0 => t('Restricted: Users can only browse the field directory. No file operations are allowed.'),
|
||||
1 => t('Full: Browsable directories are defined by <a href=":imce-admin-url">IMCE configuration profiles</a>. File operations are allowed.', array(':imce-admin-url' => $imce_admin_url)),
|
||||
),
|
||||
'#default_value' => isset($settings['source_imce']['imce_mode']) ? $settings['source_imce']['imce_mode'] : 0,
|
||||
);
|
||||
|
||||
return $return;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-renders widget form.
|
||||
*/
|
||||
public static function preRenderWidget($element) {
|
||||
// Hide elements if there is already an uploaded file.
|
||||
if (!empty($element['#value']['fids'])) {
|
||||
$element['filefield_imce']['imce_paths']['#access'] = FALSE;
|
||||
$element['filefield_imce']['imce_button']['#access'] = FALSE;
|
||||
}
|
||||
return $element;
|
||||
}
|
||||
|
||||
}
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\filefield_sources\Plugin\FilefieldSource\Reference.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\Plugin\FilefieldSource;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\filefield_sources\FilefieldSourceInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Drupal\Core\Field\WidgetInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Component\Utility\Html;
|
||||
|
||||
/**
|
||||
* A FileField source plugin to allow referencing of existing files.
|
||||
*
|
||||
* @FilefieldSource(
|
||||
* id = "reference",
|
||||
* name = @Translation("Autocomplete reference textfield"),
|
||||
* label = @Translation("Reference existing"),
|
||||
* description = @Translation("Reuse an existing file by entering its file name."),
|
||||
* weight = 1
|
||||
* )
|
||||
*/
|
||||
class Reference implements FilefieldSourceInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function value(array &$element, &$input, FormStateInterface $form_state) {
|
||||
if (isset($input['filefield_reference']['autocomplete']) && strlen($input['filefield_reference']['autocomplete']) > 0 && $input['filefield_reference']['autocomplete'] != FILEFIELD_SOURCE_REFERENCE_HINT_TEXT) {
|
||||
$matches = array();
|
||||
if (preg_match('/\[fid:(\d+)\]/', $input['filefield_reference']['autocomplete'], $matches)) {
|
||||
$fid = $matches[1];
|
||||
if ($file = file_load($fid)) {
|
||||
|
||||
// Remove file size restrictions, since the file already exists on
|
||||
// disk.
|
||||
if (isset($element['#upload_validators']['file_validate_size'])) {
|
||||
unset($element['#upload_validators']['file_validate_size']);
|
||||
}
|
||||
|
||||
// Check that the user has access to this file through
|
||||
// hook_download().
|
||||
if (!$file->access('download')) {
|
||||
$form_state->setError($element, t('You do not have permission to use the selected file.'));
|
||||
}
|
||||
elseif (filefield_sources_element_validate($element, (object) $file, $form_state)) {
|
||||
if (!in_array($file->id(), $input['fids'])) {
|
||||
$input['fids'][] = $file->id();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$form_state->setError($element, t('The referenced file could not be used because the file does not exist in the database.'));
|
||||
}
|
||||
}
|
||||
// No matter what happens, clear the value from the autocomplete.
|
||||
$input['filefield_reference']['autocomplete'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function process(array &$element, FormStateInterface $form_state, array &$complete_form) {
|
||||
|
||||
$element['filefield_reference'] = array(
|
||||
'#weight' => 100.5,
|
||||
'#theme' => 'filefield_sources_element',
|
||||
'#source_id' => 'reference',
|
||||
// Required for proper theming.
|
||||
'#filefield_source' => TRUE,
|
||||
'#filefield_sources_hint_text' => FILEFIELD_SOURCE_REFERENCE_HINT_TEXT,
|
||||
);
|
||||
|
||||
$autocomplete_route_parameters = array(
|
||||
'entity_type' => $element['#entity_type'],
|
||||
'bundle_name' => $element['#bundle'],
|
||||
'field_name' => $element['#field_name'],
|
||||
);
|
||||
|
||||
$element['filefield_reference']['autocomplete'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#autocomplete_route_name' => 'filefield_sources.autocomplete',
|
||||
'#autocomplete_route_parameters' => $autocomplete_route_parameters,
|
||||
'#description' => filefield_sources_element_validation_help($element['#upload_validators']),
|
||||
);
|
||||
|
||||
$class = '\Drupal\file\Element\ManagedFile';
|
||||
$ajax_settings = [
|
||||
'callback' => [$class, 'uploadAjaxCallback'],
|
||||
'options' => [
|
||||
'query' => [
|
||||
'element_parents' => implode('/', $element['#array_parents']),
|
||||
],
|
||||
],
|
||||
'wrapper' => $element['upload_button']['#ajax']['wrapper'],
|
||||
'effect' => 'fade',
|
||||
];
|
||||
|
||||
$element['filefield_reference']['select'] = [
|
||||
'#name' => implode('_', $element['#parents']) . '_autocomplete_select',
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Select'),
|
||||
'#validate' => [],
|
||||
'#submit' => ['filefield_sources_field_submit'],
|
||||
'#limit_validation_errors' => [$element['#parents']],
|
||||
'#ajax' => $ajax_settings,
|
||||
];
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme the output of the reference element.
|
||||
*/
|
||||
public static function element($variables) {
|
||||
$element = $variables['element'];
|
||||
|
||||
$element['autocomplete']['#field_suffix'] = drupal_render($element['select']);
|
||||
return '<div class="filefield-source filefield-source-reference clear-block">' . drupal_render($element['autocomplete']) . '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; autocomplete.js callback to return a list of files.
|
||||
*/
|
||||
public static function autocomplete(Request $request, $entity_type, $bundle_name, $field_name) {
|
||||
$matches = array();
|
||||
$string = Unicode::strtolower($request->query->get('q'));
|
||||
|
||||
if (isset($string)) {
|
||||
$widget = entity_get_form_display($entity_type, $bundle_name, 'default')->getComponent($field_name);
|
||||
if ($widget) {
|
||||
// // If we are looking at a single field, cache its settings, in case we want to search all fields.
|
||||
$setting_autocomplete = $widget['third_party_settings']['filefield_sources']['filefield_sources']['source_reference']['autocomplete'];
|
||||
$setting_search_all_fields = $widget['third_party_settings']['filefield_sources']['filefield_sources']['source_reference']['search_all_fields'];
|
||||
}
|
||||
|
||||
$field_definition = entity_load('field_config', $entity_type . '.' . $bundle_name . '.' . $field_name);
|
||||
if (!isset($field_definition) || $setting_search_all_fields) {
|
||||
$field_definitions = \Drupal::entityManager()->getStorage('field_config')->loadByProperties(array('type' => array('file', 'image')));
|
||||
}
|
||||
else {
|
||||
$field_definitions = array($field_definition);
|
||||
}
|
||||
|
||||
foreach ($field_definitions as $field_definition) {
|
||||
$handler = \Drupal::getContainer()->get('plugin.manager.entity_reference_selection')->getSelectionHandler($field_definition);
|
||||
|
||||
// If we are searching all fields, use the autocomplete settings from the source field.
|
||||
$match_operator = empty($setting_autocomplete) ? 'STARTS_WITH' : 'CONTAINS';
|
||||
// Get an array of matching entities.
|
||||
$entity_labels = $handler->getReferenceableEntities($string, $match_operator, 10);
|
||||
|
||||
// Loop through the entities and convert them into autocomplete output.
|
||||
foreach ($entity_labels as $values) {
|
||||
foreach ($values as $entity_id => $label) {
|
||||
$key = "$label [fid:$entity_id]";
|
||||
// Strip things like starting/trailing white spaces, line breaks and
|
||||
// tags.
|
||||
$key = preg_replace('/\s\s+/', ' ', str_replace("\n", '', trim(Html::decodeEntities(strip_tags($key)))));
|
||||
// Names containing commas or quotes must be wrapped in quotes.
|
||||
$matches[] = array('value' => $key, 'label' => $label);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new JsonResponse($matches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define routes for Reference source.
|
||||
*
|
||||
* @return array
|
||||
* Array of routes.
|
||||
*/
|
||||
public static function routes() {
|
||||
$routes = array();
|
||||
|
||||
$routes['filefield_sources.autocomplete'] = new Route(
|
||||
'/file/reference/{entity_type}/{bundle_name}/{field_name}',
|
||||
array(
|
||||
'_controller' => get_called_class() . '::autocomplete',
|
||||
),
|
||||
array(
|
||||
'_access_filefield_sources_field' => 'TRUE',
|
||||
)
|
||||
);
|
||||
|
||||
return $routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_filefield_source_settings().
|
||||
*/
|
||||
public static function settings(WidgetInterface $plugin) {
|
||||
$settings = $plugin->getThirdPartySetting('filefield_sources', 'filefield_sources', array(
|
||||
'source_reference' => array(
|
||||
'autocomplete' => FILEFIELD_SOURCE_REFERENCE_MATCH_STARTS_WITH,
|
||||
'search_all_fields' => FILEFIELD_SOURCE_REFERENCE_SEARCH_ALL_NO,
|
||||
),
|
||||
));
|
||||
|
||||
$return['source_reference'] = array(
|
||||
'#title' => t('Autocomplete reference options'),
|
||||
'#type' => 'details',
|
||||
);
|
||||
|
||||
$return['source_reference']['autocomplete'] = array(
|
||||
'#title' => t('Match file name'),
|
||||
'#options' => array(
|
||||
FILEFIELD_SOURCE_REFERENCE_MATCH_STARTS_WITH => t('Starts with'),
|
||||
FILEFIELD_SOURCE_REFERENCE_MATCH_CONTAINS => t('Contains'),
|
||||
),
|
||||
'#type' => 'radios',
|
||||
'#default_value' => isset($settings['source_reference']['autocomplete']) ? $settings['source_reference']['autocomplete'] : FILEFIELD_SOURCE_REFERENCE_MATCH_STARTS_WITH,
|
||||
);
|
||||
|
||||
$return['source_reference']['search_all_fields'] = array(
|
||||
'#title' => t('Search all file fields'),
|
||||
'#options' => array(
|
||||
FILEFIELD_SOURCE_REFERENCE_SEARCH_ALL_NO => t('No (only fields with the same field base will be searched)'),
|
||||
FILEFIELD_SOURCE_REFERENCE_SEARCH_ALL_YES => t('Yes (all file fields will be searched, regardless of type)'),
|
||||
),
|
||||
'#type' => 'radios',
|
||||
'#default_value' => isset($settings['source_reference']['search_all_fields']) ? $settings['source_reference']['search_all_fields'] : FILEFIELD_SOURCE_REFERENCE_SEARCH_ALL_NO,
|
||||
);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
}
|
||||
+400
@@ -0,0 +1,400 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\filefield_sources\Plugin\FilefieldSource\Remote.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\Plugin\FilefieldSource;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\filefield_sources\FilefieldSourceInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Drupal\Core\Field\WidgetInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
|
||||
/**
|
||||
* A FileField source plugin to allow downloading a file from a remote server.
|
||||
*
|
||||
* @FilefieldSource(
|
||||
* id = "remote",
|
||||
* name = @Translation("Remote URL textfield"),
|
||||
* label = @Translation("Remote URL"),
|
||||
* description = @Translation("Download a file from a remote server.")
|
||||
* )
|
||||
*/
|
||||
class Remote implements FilefieldSourceInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function value(array &$element, &$input, FormStateInterface $form_state) {
|
||||
if (isset($input['filefield_remote']['url']) && strlen($input['filefield_remote']['url']) > 0 && UrlHelper::isValid($input['filefield_remote']['url']) && $input['filefield_remote']['url'] != FILEFIELD_SOURCE_REMOTE_HINT_TEXT) {
|
||||
$field = entity_load('field_config', $element['#entity_type'] . '.' . $element['#bundle'] . '.' . $element['#field_name']);
|
||||
$url = $input['filefield_remote']['url'];
|
||||
|
||||
// Check that the destination is writable.
|
||||
$temporary_directory = 'temporary://';
|
||||
if (!file_prepare_directory($temporary_directory, FILE_MODIFY_PERMISSIONS)) {
|
||||
\Drupal::logger('filefield_sources')->log(E_NOTICE, 'The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => drupal_realpath($temporary_directory)));
|
||||
drupal_set_message(t('The file could not be transferred because the temporary directory is not writable.'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that the destination is writable.
|
||||
$directory = $element['#upload_location'];
|
||||
$mode = Settings::get('file_chmod_directory', FILE_CHMOD_DIRECTORY);
|
||||
|
||||
// This first chmod check is for other systems such as S3, which don't
|
||||
// work with file_prepare_directory().
|
||||
if (!drupal_chmod($directory, $mode) && !file_prepare_directory($directory, FILE_CREATE_DIRECTORY)) {
|
||||
\Drupal::logger('filefield_sources')->log(E_NOTICE, 'File %file could not be copied, because the destination directory %destination is not configured correctly.', array('%file' => $url, '%destination' => drupal_realpath($directory)));
|
||||
drupal_set_message(t('The specified file %file could not be copied, because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $url)), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the headers to make sure it exists and is within the allowed
|
||||
// size.
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_HEADER, TRUE);
|
||||
curl_setopt($ch, CURLOPT_NOBODY, TRUE);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
|
||||
curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(get_called_class(), 'parseHeader'));
|
||||
// Causes a warning if PHP safe mode is on.
|
||||
@curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
|
||||
curl_exec($ch);
|
||||
$info = curl_getinfo($ch);
|
||||
if ($info['http_code'] != 200) {
|
||||
curl_setopt($ch, CURLOPT_HTTPGET, TRUE);
|
||||
$file_contents = curl_exec($ch);
|
||||
$info = curl_getinfo($ch);
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
if ($info['http_code'] != 200) {
|
||||
switch ($info['http_code']) {
|
||||
case 403:
|
||||
$form_state->setError($element, t('The remote file could not be transferred because access to the file was denied.'));
|
||||
break;
|
||||
|
||||
case 404:
|
||||
$form_state->setError($element, t('The remote file could not be transferred because it was not found.'));
|
||||
break;
|
||||
|
||||
default:
|
||||
$form_state->setError($element, t('The remote file could not be transferred due to an HTTP error (@code).', array('@code' => $info['http_code'])));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the $url variable to reflect any redirects.
|
||||
$url = $info['url'];
|
||||
$url_info = parse_url($url);
|
||||
|
||||
// Determine the proper filename by reading the filename given in the
|
||||
// Content-Disposition header. If the server fails to send this header,
|
||||
// fall back on the basename of the URL.
|
||||
//
|
||||
// We prefer to use the Content-Disposition header, because we can then
|
||||
// use URLs like http://example.com/get_file/23 which would otherwise be
|
||||
// rejected because the URL basename lacks an extension.
|
||||
$filename = static::filename();
|
||||
if (empty($filename)) {
|
||||
$filename = rawurldecode(basename($url_info['path']));
|
||||
}
|
||||
|
||||
$pathinfo = pathinfo($filename);
|
||||
|
||||
// Create the file extension from the MIME header if all else has failed.
|
||||
if (empty($pathinfo['extension']) && $extension = static::mimeExtension()) {
|
||||
$filename = $filename . '.' . $extension;
|
||||
$pathinfo = pathinfo($filename);
|
||||
}
|
||||
|
||||
$filename = filefield_sources_clean_filename($filename, $field->getSetting('file_extensions'));
|
||||
$filepath = file_create_filename($filename, $temporary_directory);
|
||||
|
||||
if (empty($pathinfo['extension'])) {
|
||||
$form_state->setError($element, t('The remote URL must be a file and have an extension.'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform basic extension check on the file before trying to transfer.
|
||||
$extensions = $field->getSetting('file_extensions');
|
||||
$regex = '/\.(' . preg_replace('/[ +]/', '|', preg_quote($extensions)) . ')$/i';
|
||||
if (!empty($extensions) && !preg_match($regex, $filename)) {
|
||||
$form_state->setError($element, t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => $extensions)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check file size based off of header information.
|
||||
if (!empty($element['#upload_validators']['file_validate_size'][0])) {
|
||||
$max_size = $element['#upload_validators']['file_validate_size'][0];
|
||||
$file_size = $info['download_content_length'];
|
||||
if ($file_size > $max_size) {
|
||||
$form_state->setError($element, t('The remote file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($file_size), '%maxsize' => format_size($max_size))));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Set progress bar information.
|
||||
$options = array(
|
||||
'key' => $element['#entity_type'] . '_' . $element['#bundle'] . '_' . $element['#field_name'] . '_' . $element['#delta'],
|
||||
'filepath' => $filepath,
|
||||
);
|
||||
static::setTransferOptions($options);
|
||||
|
||||
$transfer_success = FALSE;
|
||||
// If we've already downloaded the entire file because the
|
||||
// header-retrieval failed, just ave the contents we have.
|
||||
if (isset($file_contents)) {
|
||||
if ($fp = @fopen($filepath, 'w')) {
|
||||
fwrite($fp, $file_contents);
|
||||
fclose($fp);
|
||||
$transfer_success = TRUE;
|
||||
}
|
||||
}
|
||||
// If we don't have the file contents, download the actual file.
|
||||
else {
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_HEADER, FALSE);
|
||||
curl_setopt($ch, CURLOPT_WRITEFUNCTION, array(get_called_class(), 'curlWrite'));
|
||||
// Causes a warning if PHP safe mode is on.
|
||||
@curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
|
||||
$transfer_success = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
}
|
||||
if ($transfer_success && $file = filefield_sources_save_file($filepath, $element['#upload_validators'], $element['#upload_location'])) {
|
||||
if (!in_array($file->id(), $input['fids'])) {
|
||||
$input['fids'][] = $file->id();
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the temporary file.
|
||||
@unlink($filepath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a transfer key that can be retreived by the progress function.
|
||||
*/
|
||||
protected static function setTransferOptions($options = NULL) {
|
||||
static $current = FALSE;
|
||||
if (isset($options)) {
|
||||
$current = $options;
|
||||
}
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a transfer key that can be retrieved by the progress function.
|
||||
*/
|
||||
protected static function getTransferOptions() {
|
||||
return static::setTransferOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the file to disk. Also updates progress bar.
|
||||
*/
|
||||
protected static function curlWrite(&$ch, $data) {
|
||||
$progress_update = 0;
|
||||
$options = static::getTransferOptions();
|
||||
|
||||
// Get the current progress and update the progress value.
|
||||
// Only update every 64KB to reduce Drupal::cache()->set() calls.
|
||||
// cURL usually writes in 16KB chunks.
|
||||
if (curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD) / 65536 > $progress_update) {
|
||||
$progress_update++;
|
||||
$progress = array(
|
||||
'current' => curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD),
|
||||
'total' => curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD),
|
||||
);
|
||||
// Set a cache so that we can retrieve this value from the progress bar.
|
||||
$cid = 'filefield_transfer:' . session_id() . ':' . $options['key'];
|
||||
if ($progress['current'] != $progress['total']) {
|
||||
\Drupal::cache()->set($cid, $progress, time() + 300);
|
||||
}
|
||||
else {
|
||||
\Drupal::cache()->delete($cid);
|
||||
}
|
||||
}
|
||||
|
||||
$data_length = 0;
|
||||
if ($fp = @fopen($options['filepath'], 'a')) {
|
||||
fwrite($fp, $data);
|
||||
fclose($fp);
|
||||
$data_length = strlen($data);
|
||||
}
|
||||
|
||||
return $data_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse cURL header and record the filename specified in Content-Disposition.
|
||||
*/
|
||||
protected static function parseHeader(&$ch, $header) {
|
||||
if (preg_match('/Content-Disposition:.*?filename="(.+?)"/', $header, $matches)) {
|
||||
// Content-Disposition: attachment; filename="FILE NAME HERE"
|
||||
static::filename($matches[1]);
|
||||
}
|
||||
elseif (preg_match('/Content-Disposition:.*?filename=([^; ]+)/', $header, $matches)) {
|
||||
// Content-Disposition: attachment; filename=file.ext
|
||||
$uri = trim($matches[1]);
|
||||
static::filename($uri);
|
||||
}
|
||||
elseif (preg_match('/Content-Type:[ ]*([a-z0-9_\-]+\/[a-z0-9_\-]+)/i', $header, $matches)) {
|
||||
$mime_type = $matches[1];
|
||||
static::mimeExtension($mime_type);
|
||||
}
|
||||
|
||||
// This is required by cURL.
|
||||
return strlen($header);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set the remote file extension in a static variable.
|
||||
*/
|
||||
protected static function mimeExtension($curl_mime_type = NULL) {
|
||||
static $extension = NULL;
|
||||
$mimetype = Unicode::strtolower($curl_mime_type);
|
||||
$result = \Drupal::service('file.mime_type.guesser.extension')->convertMimeTypeToMostCommonExtension($mimetype);
|
||||
if ($result) {
|
||||
$extension = $result;
|
||||
}
|
||||
return $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set the remote file name in a static variable.
|
||||
*/
|
||||
protected static function filename($curl_filename = NULL) {
|
||||
static $filename = NULL;
|
||||
if (isset($curl_filename)) {
|
||||
$filename = $curl_filename;
|
||||
}
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function process(array &$element, FormStateInterface $form_state, array &$complete_form) {
|
||||
|
||||
$element['filefield_remote'] = array(
|
||||
'#weight' => 100.5,
|
||||
'#theme' => 'filefield_sources_element',
|
||||
'#source_id' => 'remote',
|
||||
// Required for proper theming.
|
||||
'#filefield_source' => TRUE,
|
||||
'#filefield_sources_hint_text' => FILEFIELD_SOURCE_REMOTE_HINT_TEXT,
|
||||
);
|
||||
|
||||
$element['filefield_remote']['url'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#description' => filefield_sources_element_validation_help($element['#upload_validators']),
|
||||
'#maxlength' => NULL,
|
||||
);
|
||||
|
||||
$class = '\Drupal\file\Element\ManagedFile';
|
||||
$ajax_settings = [
|
||||
'callback' => [$class, 'uploadAjaxCallback'],
|
||||
'options' => [
|
||||
'query' => [
|
||||
'element_parents' => implode('/', $element['#array_parents']),
|
||||
],
|
||||
],
|
||||
'wrapper' => $element['upload_button']['#ajax']['wrapper'],
|
||||
'effect' => 'fade',
|
||||
'progress' => [
|
||||
'type' => 'bar',
|
||||
'path' => 'file/remote/progress/' . $element['#entity_type'] . '/' . $element['#bundle'] . '/' . $element['#field_name'] . '/' . $element['#delta'],
|
||||
'message' => t('Starting transfer...'),
|
||||
],
|
||||
];
|
||||
|
||||
$element['filefield_remote']['transfer'] = [
|
||||
'#name' => implode('_', $element['#parents']) . '_transfer',
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Transfer'),
|
||||
'#validate' => array(),
|
||||
'#submit' => ['filefield_sources_field_submit'],
|
||||
'#limit_validation_errors' => [$element['#parents']],
|
||||
'#ajax' => $ajax_settings,
|
||||
];
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme the output of the remote element.
|
||||
*/
|
||||
public static function element($variables) {
|
||||
$element = $variables['element'];
|
||||
|
||||
$element['url']['#field_suffix'] = drupal_render($element['transfer']);
|
||||
return '<div class="filefield-source filefield-source-remote clear-block">' . drupal_render($element['url']) . '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; progress.js callback to return upload progress.
|
||||
*/
|
||||
public static function progress($entity_type, $bundle_name, $field_name, $delta) {
|
||||
$key = $entity_type . '_' . $bundle_name . '_' . $field_name . '_' . $delta;
|
||||
$progress = array(
|
||||
'message' => t('Starting transfer...'),
|
||||
'percentage' => -1,
|
||||
);
|
||||
|
||||
if ($cache = \Drupal::cache()->get('filefield_transfer:' . session_id() . ':' . $key)) {
|
||||
$current = $cache->data['current'];
|
||||
$total = $cache->data['total'];
|
||||
$progress['message'] = t('Transferring... (@current of @total)', array('@current' => format_size($current), '@total' => format_size($total)));
|
||||
$progress['percentage'] = round(100 * $current / $total);
|
||||
}
|
||||
|
||||
return new JsonResponse($progress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define routes for Remote source.
|
||||
*
|
||||
* @return array
|
||||
* Array of routes.
|
||||
*/
|
||||
public static function routes() {
|
||||
$routes = array();
|
||||
|
||||
$routes['filefield_sources.remote'] = new Route(
|
||||
'/file/remote/progress/{entity_type}/{bundle_name}/{field_name}/{delta}',
|
||||
array(
|
||||
'_controller' => get_called_class() . '::progress',
|
||||
),
|
||||
array(
|
||||
'_access' => 'TRUE',
|
||||
)
|
||||
);
|
||||
|
||||
return $routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_filefield_source_settings().
|
||||
*/
|
||||
public static function settings(WidgetInterface $plugin) {
|
||||
$return = array();
|
||||
|
||||
// Add settings to the FileField widget form.
|
||||
if (!filefield_sources_curl_enabled()) {
|
||||
drupal_set_message(t('<strong>Filefield sources:</strong> remote plugin will be disabled without php-curl extension.'), 'warning');
|
||||
}
|
||||
|
||||
return $return;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\filefield_sources\ProxyClass\File\MimeType\ExtensionMimeTypeGuesser.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file was generated via php core/scripts/generate-proxy-class.php 'Drupal\filefield_sources\File\MimeType\ExtensionMimeTypeGuesser' "modules/filefield_sources/src".
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\ProxyClass\File\MimeType {
|
||||
|
||||
/**
|
||||
* Provides a proxy class for \Drupal\filefield_sources\File\MimeType\ExtensionMimeTypeGuesser.
|
||||
*
|
||||
* @see \Drupal\Component\ProxyBuilder
|
||||
*/
|
||||
class ExtensionMimeTypeGuesser implements \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface
|
||||
{
|
||||
|
||||
use \Drupal\Core\DependencyInjection\DependencySerializationTrait;
|
||||
|
||||
/**
|
||||
* The id of the original proxied service.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $drupalProxyOriginalServiceId;
|
||||
|
||||
/**
|
||||
* The real proxied service, after it was lazy loaded.
|
||||
*
|
||||
* @var \Drupal\filefield_sources\File\MimeType\ExtensionMimeTypeGuesser
|
||||
*/
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* The service container.
|
||||
*
|
||||
* @var \Symfony\Component\DependencyInjection\ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Constructs a ProxyClass Drupal proxy object.
|
||||
*
|
||||
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
|
||||
* The container.
|
||||
* @param string $drupal_proxy_original_service_id
|
||||
* The service ID of the original service.
|
||||
*/
|
||||
public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container, $drupal_proxy_original_service_id)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->drupalProxyOriginalServiceId = $drupal_proxy_original_service_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy loads the real service from the container.
|
||||
*
|
||||
* @return object
|
||||
* Returns the constructed real service.
|
||||
*/
|
||||
protected function lazyLoadItself()
|
||||
{
|
||||
if (!isset($this->service)) {
|
||||
$this->service = $this->container->get($this->drupalProxyOriginalServiceId);
|
||||
}
|
||||
|
||||
return $this->service;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function convertMimeTypeToExtension($mimetype)
|
||||
{
|
||||
return $this->lazyLoadItself()->convertMimeTypeToExtension($mimetype);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function convertMimeTypeToMostCommonExtension($mimetype)
|
||||
{
|
||||
return $this->lazyLoadItself()->convertMimeTypeToMostCommonExtension($mimetype);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function guess($path)
|
||||
{
|
||||
return $this->lazyLoadItself()->guess($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setMapping(array $mapping = NULL)
|
||||
{
|
||||
return $this->lazyLoadItself()->setMapping($mapping);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\filefield_sources\Routing\FilefieldSourcesRoutes.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\Routing;
|
||||
|
||||
/**
|
||||
* Defines a route subscriber to register a url for serving filefield sources.
|
||||
*/
|
||||
class FilefieldSourcesRoutes {
|
||||
|
||||
/**
|
||||
* Returns an array of route objects.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\Route[]
|
||||
* An array of route objects.
|
||||
*/
|
||||
public function routes() {
|
||||
$routes = array();
|
||||
|
||||
foreach (\Drupal::service('filefield_sources')->getDefinitions() as $definition) {
|
||||
// Get routes defined by each plugin.
|
||||
$callback = array($definition['class'], 'routes');
|
||||
if (is_callable($callback)) {
|
||||
$routes = array_merge($routes, call_user_func($callback));
|
||||
}
|
||||
}
|
||||
|
||||
return $routes;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\filefield_sources\Tests\AttachSourceTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\Tests;
|
||||
|
||||
use Drupal\Component\Render\PlainTextOutput;
|
||||
|
||||
/**
|
||||
* Tests the attach source.
|
||||
*
|
||||
* @group filefield_sources
|
||||
*/
|
||||
class AttachSourceTest extends FileFieldSourcesTestBase {
|
||||
|
||||
/**
|
||||
* Check to see if a option is present.
|
||||
*
|
||||
* @param string $uri
|
||||
* The option to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the option is present, FALSE otherwise.
|
||||
*/
|
||||
public function isOptionPresent($uri) {
|
||||
$options = $this->xpath('//select[@name=:name]/option[@value=:option]', array(
|
||||
':name' => $this->fieldName . '[0][filefield_attach][filename]',
|
||||
':option' => $uri,
|
||||
));
|
||||
return isset($options[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if can attach file.
|
||||
*
|
||||
* @param object $file
|
||||
* File to attach.
|
||||
*/
|
||||
public function assertCanAttachFile($file) {
|
||||
// Ensure option is present.
|
||||
$this->assertTrue($this->isOptionPresent($file->uri), 'File option is present.');
|
||||
|
||||
// Ensure empty message is not present.
|
||||
$this->assertNoText('There currently are no files to attach.', "Empty message is not present.");
|
||||
|
||||
// Attach button is always present.
|
||||
$this->assertFieldByXpath('//input[@type="submit"]', t('Attach'), 'Attach button is present.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if can attach file.
|
||||
*
|
||||
* @param object $file
|
||||
* File to attach.
|
||||
*/
|
||||
public function assertCanNotAttachFile($file) {
|
||||
// Ensure option is not present.
|
||||
$this->assertFalse($this->isOptionPresent($file->uri), 'File option is not present.');
|
||||
|
||||
// Ensure empty message is present.
|
||||
$this->assertText('There currently are no files to attach.', "Empty message is present.");
|
||||
|
||||
// Attach button is always present.
|
||||
$this->assertFieldByXpath('//input[@type="submit"]', t('Attach'), 'Attach button is present.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests move file from relative path.
|
||||
*
|
||||
* Default settings: Move file from 'public://file_attach' to 'public://'.
|
||||
*/
|
||||
public function testMoveFileFromRelativePath() {
|
||||
$uri_scheme = $this->getFieldSetting('uri_scheme');
|
||||
$path = $uri_scheme . '://' . FILEFIELD_SOURCE_ATTACH_DEFAULT_PATH . '/';
|
||||
|
||||
// Create test file.
|
||||
$file = $this->createTemporaryFile($path);
|
||||
$dest_uri = $this->getDestinationUri($file, $uri_scheme);
|
||||
|
||||
$this->enableSources(array(
|
||||
'attach' => TRUE,
|
||||
));
|
||||
|
||||
$this->assertCanAttachFile($file);
|
||||
|
||||
// Upload a file.
|
||||
$this->uploadFileByAttachSource($file->uri, $file->filename, 0);
|
||||
|
||||
// We can only attach one file on single value field.
|
||||
$this->assertNoFieldByXPath('//input[@type="submit"]', t('Attach'), 'After uploading a file, "Attach" button is no longer displayed.');
|
||||
|
||||
// Ensure file is moved.
|
||||
$this->assertFalse(is_file($file->uri), 'Source file has been removed.');
|
||||
$this->assertTrue(is_file($dest_uri), 'Destination file has been created.');
|
||||
|
||||
$this->removeFile($file->filename, 0);
|
||||
|
||||
$this->assertCanNotAttachFile($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate custom absolute path.
|
||||
*/
|
||||
public function getCustomAttachPath() {
|
||||
$path = drupal_realpath($this->getFieldSetting('uri_scheme') . '://');
|
||||
$path = str_replace(realpath('./'), '', $path);
|
||||
$path = ltrim($path, '/');
|
||||
$path = $path . '/custom_file_attach/';
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests copy file from absolute path.
|
||||
*
|
||||
* Copy file from 'sites/default/files/custom_file_attach' to 'public://'.
|
||||
*/
|
||||
public function testCopyFileFromAbsolutePath() {
|
||||
$uri_scheme = $this->getFieldSetting('uri_scheme');
|
||||
$path = $this->getCustomAttachPath();
|
||||
|
||||
// Create test file.
|
||||
$file = $this->createTemporaryFile($path);
|
||||
$dest_uri = $this->getDestinationUri($file, $uri_scheme);
|
||||
|
||||
// Change settings.
|
||||
$this->updateFilefieldSourcesSettings('source_attach', 'path', $path);
|
||||
$this->updateFilefieldSourcesSettings('source_attach', 'absolute', FILEFIELD_SOURCE_ATTACH_ABSOLUTE);
|
||||
$this->updateFilefieldSourcesSettings('source_attach', 'attach_mode', FILEFIELD_SOURCE_ATTACH_MODE_COPY);
|
||||
|
||||
$this->enableSources(array(
|
||||
'attach' => TRUE,
|
||||
));
|
||||
|
||||
$this->assertCanAttachFile($file);
|
||||
|
||||
// Upload a file.
|
||||
$this->uploadFileByAttachSource($file->uri, $file->filename, 0);
|
||||
|
||||
// We can only attach one file on single value field.
|
||||
$this->assertNoFieldByXPath('//input[@type="submit"]', t('Attach'), 'After uploading a file, "Attach" button is no longer displayed.');
|
||||
|
||||
// Ensure file is copied.
|
||||
$this->assertTrue(is_file($file->uri), 'Source file still exists.');
|
||||
$this->assertTrue(is_file($dest_uri), 'Destination file has been created.');
|
||||
|
||||
$this->removeFile($file->filename, 0);
|
||||
|
||||
$this->assertCanAttachFile($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get destination uri of a .
|
||||
*
|
||||
* @param object $file
|
||||
* File.
|
||||
* @param string $uri_scheme
|
||||
* Uri scheme.
|
||||
*/
|
||||
public function getDestinationUri($file, $uri_scheme) {
|
||||
$destination = trim($this->getFieldSetting('file_directory'), '/');
|
||||
$destination = PlainTextOutput::renderFromHtml(\Drupal::token()->replace($destination));
|
||||
return $uri_scheme . '://' . $destination . '/' . $file->filename;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\filefield_sources\Tests\ClipboardSourceTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\Tests;
|
||||
|
||||
/**
|
||||
* Tests the clipboard source.
|
||||
*
|
||||
* @group filefield_sources
|
||||
*/
|
||||
class ClipboardSourceTest extends FileFieldSourcesTestBase {
|
||||
|
||||
/**
|
||||
* Tests clipboard source enabled.
|
||||
*/
|
||||
public function testClipboardSourceEnabled() {
|
||||
$this->enableSources(array(
|
||||
'clipboard' => TRUE,
|
||||
));
|
||||
$file = $this->createTemporaryFileEntity();
|
||||
|
||||
$this->uploadFileByClipboardSource($file->getFileUri(), $file->getFilename(), 0);
|
||||
|
||||
// We can only upload one file on single value field.
|
||||
$this->assertNoFieldByXPath('//input[@type="submit"]', t('Upload'), t('After uploading a file, "Upload" button is no longer displayed.'));
|
||||
|
||||
$this->removeFile($file->getFilename(), 0);
|
||||
|
||||
// Can upload file again.
|
||||
$this->assertFieldByXpath('//input[@type="submit"]', t('Upload'), 'After clicking the "Remove" button, the "Upload" button is displayed.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\filefield_sources\Tests\EmptyValuesTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\Tests;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Tests empty values.
|
||||
*
|
||||
* @group filefield_sources
|
||||
*/
|
||||
class EmptyValuesTest extends FileFieldSourcesTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('imce');
|
||||
|
||||
/**
|
||||
* Sets up for empty values test case.
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->setUpImce();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests all sources enabled.
|
||||
*/
|
||||
public function testAllSourcesEnabled() {
|
||||
// Change allowed number of values.
|
||||
$this->drupalPostForm('admin/structure/types/manage/' . $this->typeName . '/fields/node.' . $this->typeName . '.' . $this->fieldName . '/storage', array('cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED), t('Save field settings'));
|
||||
|
||||
$this->enableSources(array(
|
||||
'upload' => TRUE,
|
||||
'remote' => TRUE,
|
||||
'clipboard' => TRUE,
|
||||
'reference' => TRUE,
|
||||
'attach' => TRUE,
|
||||
'imce' => TRUE,
|
||||
));
|
||||
|
||||
// Upload a file by 'Remote' source.
|
||||
$this->uploadFileByRemoteSource();
|
||||
|
||||
// Upload a file by 'Reference' source.
|
||||
$this->uploadFileByReferenceSource();
|
||||
|
||||
// Upload a file by 'Clipboard' source.
|
||||
$this->uploadFileByClipboardSource();
|
||||
|
||||
// Upload a file by 'Attach' source.
|
||||
$this->uploadFileByAttachSource();
|
||||
|
||||
// Upload a file by 'Upload' source.
|
||||
$this->uploadFileByUploadSource('', '', 0, TRUE);
|
||||
|
||||
// Upload a file by 'Imce' source.
|
||||
$this->uploadFileByImceSource();
|
||||
|
||||
$this->assertUniqueSubmitButtons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that there is only one submit button of a source.
|
||||
*/
|
||||
protected function assertUniqueSubmitButtons() {
|
||||
$buttons = array(
|
||||
$this->fieldName . '_0_attach' => t('Attach'),
|
||||
$this->fieldName . '_0_clipboard_upload_button' => t('Upload'),
|
||||
$this->fieldName . '_0_autocomplete_select' => t('Select'),
|
||||
$this->fieldName . '_0_transfer' => t('Transfer'),
|
||||
$this->fieldName . '_0_upload_button' => t('Upload'),
|
||||
$this->fieldName . '_0_imce_select' => t('Select'),
|
||||
);
|
||||
foreach ($buttons as $button_name => $button_label) {
|
||||
// Ensure that there is only one button with name.
|
||||
$buttons = $this->xpath('//input[@name="' . $button_name . '" and @value="' . $button_label . '"]');
|
||||
$this->assertEqual(count($buttons), 1, format_string('There is only one button with name %name and label %label', array('%name' => $button_name, '%label' => $button_label)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+450
@@ -0,0 +1,450 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\filefield_sources\Tests\FileFieldSourcesTestBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\Tests;
|
||||
|
||||
use Drupal\file\Tests\FileFieldTestBase;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
/**
|
||||
* Base class for File Field Sources test cases.
|
||||
*/
|
||||
abstract class FileFieldSourcesTestBase extends FileFieldTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('filefield_sources');
|
||||
|
||||
protected $adminUser;
|
||||
|
||||
protected $typeName;
|
||||
protected $fieldName;
|
||||
protected $node;
|
||||
|
||||
/**
|
||||
* Sets up for file field sources test cases.
|
||||
*/
|
||||
protected function setUp() {
|
||||
WebTestBase::setUp();
|
||||
|
||||
// Create admin user, then login.
|
||||
$this->adminUser = $this->drupalCreateUser(array(
|
||||
'access content',
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
'administer users',
|
||||
'administer permissions',
|
||||
'administer content types',
|
||||
'administer node fields',
|
||||
'administer node display',
|
||||
'administer node form display',
|
||||
'administer nodes',
|
||||
'bypass node access',
|
||||
));
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Create content type.
|
||||
$this->typeName = 'article';
|
||||
$this->drupalCreateContentType(array('type' => $this->typeName, 'name' => 'Article'));
|
||||
|
||||
// Add node.
|
||||
$this->node = $this->drupalCreateNode();
|
||||
|
||||
// Add file field.
|
||||
$this->fieldName = strtolower($this->randomMachineName());
|
||||
$this->createFileField($this->fieldName, 'node', $this->typeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up for imce test cases.
|
||||
*/
|
||||
protected function setUpImce() {
|
||||
foreach ($this->adminUser->getRoles(TRUE) as $rid) {
|
||||
// Grant permission.
|
||||
$role = Role::load($rid);
|
||||
$this->grantPermissions($role, ['administer imce']);
|
||||
// Assign member profile to user's role.
|
||||
$edit["roles_profiles[$rid][public]"] = 'member';
|
||||
$this->drupalPostForm('admin/config/media/imce', $edit, t('Save configuration'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable file field sources.
|
||||
*
|
||||
* @param array $sources
|
||||
* List of sources to enable or disable. e.g
|
||||
* array(
|
||||
* 'upload' => FALSE,
|
||||
* 'remote' => TRUE,
|
||||
* ).
|
||||
*/
|
||||
public function enableSources($sources = array()) {
|
||||
$sources += array('upload' => TRUE);
|
||||
$map = array(
|
||||
'upload' => 'Upload',
|
||||
'remote' => 'Remote URL',
|
||||
'clipboard' => 'Clipboard',
|
||||
'reference' => 'Reference existing',
|
||||
'attach' => 'File attach',
|
||||
'imce' => 'File browser',
|
||||
);
|
||||
$sources = array_intersect_key($sources, $map);
|
||||
ksort($sources);
|
||||
|
||||
// Upload source enabled by default.
|
||||
$manage_display = 'admin/structure/types/manage/' . $this->typeName . '/form-display';
|
||||
$this->drupalGet($manage_display);
|
||||
$this->assertText("File field sources: upload", 'The expected summary is displayed.');
|
||||
|
||||
// Click on the widget settings button to open the widget settings form.
|
||||
$this->drupalPostAjaxForm(NULL, array(), $this->fieldName . "_settings_edit");
|
||||
|
||||
// Enable sources.
|
||||
$prefix = 'fields[' . $this->fieldName . '][settings_edit_form][third_party_settings][filefield_sources][filefield_sources][sources]';
|
||||
$edit = array();
|
||||
foreach ($sources as $source => $enabled) {
|
||||
$edit[$prefix . '[' . $source . ']'] = $enabled ? TRUE : FALSE;
|
||||
}
|
||||
$this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_plugin_settings_update' => t('Update')));
|
||||
$this->assertText("File field sources: " . implode(', ', array_keys($sources)), 'The expected summary is displayed.');
|
||||
|
||||
// Save the form to save the third party settings.
|
||||
$this->drupalPostForm(NULL, array(), t('Save'));
|
||||
|
||||
$add_node = 'node/add/' . $this->typeName;
|
||||
$this->drupalGet($add_node);
|
||||
if (count($sources) > 1) {
|
||||
// We can swith between sources.
|
||||
foreach ($sources as $source => $enabled) {
|
||||
$label = $map[$source];
|
||||
$this->assertLink($label);
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach ($map as $source => $label) {
|
||||
$this->assertNoLink($label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create permanent file entity.
|
||||
*
|
||||
* @return object
|
||||
* Permanent file entity.
|
||||
*/
|
||||
public function createPermanentFileEntity() {
|
||||
$file = $this->createTemporaryFileEntity();
|
||||
// Only permanent file can be referred.
|
||||
$file->status = FILE_STATUS_PERMANENT;
|
||||
// Author has permission to access file.
|
||||
$file->uid = $this->adminUser->id();
|
||||
$file->save();
|
||||
|
||||
// Permanent file must be used by an entity.
|
||||
\Drupal::service('file.usage')->add($file, 'file', 'node', $this->node->id());
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create temporary file entity.
|
||||
*
|
||||
* @return object
|
||||
* Temporary file entity.
|
||||
*/
|
||||
public function createTemporaryFileEntity() {
|
||||
$file = $this->createTemporaryFile();
|
||||
|
||||
// Add a filesize property to files as would be read by file_load().
|
||||
$file->filesize = filesize($file->uri);
|
||||
|
||||
return entity_create('file', (array) $file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create temporary file.
|
||||
*
|
||||
* @return object
|
||||
* Permanent file object.
|
||||
*/
|
||||
public function createTemporaryFile($path = '') {
|
||||
$filename = $this->randomMachineName() . '.txt';
|
||||
if (empty($path)) {
|
||||
$path = file_default_scheme() . '://';
|
||||
}
|
||||
$uri = $path . $filename;
|
||||
$contents = $this->randomString();
|
||||
|
||||
// Change mode so that we can create files.
|
||||
file_prepare_directory($path, FILE_CREATE_DIRECTORY);
|
||||
drupal_chmod($path, FILE_CHMOD_DIRECTORY);
|
||||
|
||||
file_put_contents($uri, $contents);
|
||||
$this->assertTrue(is_file($uri), 'The temporary file has been created.');
|
||||
|
||||
// Change mode so that we can delete created file.
|
||||
drupal_chmod($uri, FILE_CHMOD_FILE);
|
||||
|
||||
// Return object similar to file_scan_directory().
|
||||
$file = new \stdClass();
|
||||
$file->uri = $uri;
|
||||
$file->filename = $filename;
|
||||
$file->name = pathinfo($filename, PATHINFO_FILENAME);
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update file field sources settings.
|
||||
*
|
||||
* @param string $source_key
|
||||
* Wrapper, defined by each source.
|
||||
* @param string $key
|
||||
* Key, defined by each source.
|
||||
* @param mixed $value
|
||||
* Value to set.
|
||||
*/
|
||||
public function updateFilefieldSourcesSettings($source_key, $key, $value) {
|
||||
$manage_display = 'admin/structure/types/manage/' . $this->typeName . '/form-display';
|
||||
$this->drupalGet($manage_display);
|
||||
|
||||
// Click on the widget settings button to open the widget settings form.
|
||||
$this->drupalPostAjaxForm(NULL, array(), $this->fieldName . "_settings_edit");
|
||||
|
||||
// Update settings.
|
||||
$name = 'fields[' . $this->fieldName . '][settings_edit_form][third_party_settings][filefield_sources][filefield_sources]' . "[$source_key][$key]";
|
||||
$edit = array($name => $value);
|
||||
$this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_plugin_settings_update' => t('Update')));
|
||||
|
||||
// Save the form to save the third party settings.
|
||||
$this->drupalPostForm(NULL, array(), t('Save'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload file by 'Attach' source.
|
||||
*
|
||||
* @param string $uri
|
||||
* File uri.
|
||||
* @param string $filename
|
||||
* File name.
|
||||
* @param int $delta
|
||||
* Delta in multiple values field.
|
||||
*/
|
||||
public function uploadFileByAttachSource($uri = '', $filename = '', $delta = 0) {
|
||||
if ($uri) {
|
||||
$edit = array(
|
||||
$this->fieldName . '[' . $delta . '][filefield_attach][filename]' => $uri,
|
||||
);
|
||||
}
|
||||
else {
|
||||
$edit = array();
|
||||
}
|
||||
$this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_' . $delta . '_attach' => t('Attach')));
|
||||
|
||||
if ($filename) {
|
||||
$this->assertFileUploaded($filename, $delta);
|
||||
}
|
||||
else {
|
||||
$this->assertFileNotUploaded($delta);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload file by 'Reference' source.
|
||||
*
|
||||
* @param int $fid
|
||||
* File id.
|
||||
* @param string $filename
|
||||
* File name.
|
||||
* @param int $delta
|
||||
* Delta in multiple values field.
|
||||
*/
|
||||
public function uploadFileByReferenceSource($fid = 0, $filename = '', $delta = 0) {
|
||||
$name = $this->fieldName . '[' . $delta . '][filefield_reference][autocomplete]';
|
||||
$value = $fid ? $filename . ' [fid:' . $fid . ']' : '';
|
||||
$edit = array($name => $value);
|
||||
$this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_' . $delta . '_autocomplete_select' => t('Select')));
|
||||
|
||||
if ($filename) {
|
||||
$this->assertFileUploaded($filename, $delta);
|
||||
}
|
||||
else {
|
||||
$this->assertFileNotUploaded($delta);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload file by 'Clipboard' source.
|
||||
*
|
||||
* @param string $uri
|
||||
* File uri.
|
||||
* @param string $filename
|
||||
* File name.
|
||||
* @param int $delta
|
||||
* Delta in multiple values field.
|
||||
*/
|
||||
public function uploadFileByClipboardSource($uri = '', $filename = '', $delta = 0) {
|
||||
$prefix = $this->fieldName . '[' . $delta . '][filefield_clipboard]';
|
||||
$file_content = $uri ? 'data:text/plain;base64,' . base64_encode(file_get_contents($uri)) : '';
|
||||
$edit = array(
|
||||
$prefix . '[filename]' => $filename,
|
||||
$prefix . '[contents]' => $file_content,
|
||||
);
|
||||
$this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_' . $delta . '_clipboard_upload_button' => t('Upload')));
|
||||
|
||||
if ($filename) {
|
||||
$this->assertFileUploaded($filename, $delta);
|
||||
}
|
||||
else {
|
||||
$this->assertFileNotUploaded($delta);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload file by 'Imce' source.
|
||||
*
|
||||
* @param string $uri
|
||||
* File uri.
|
||||
* @param string $filename
|
||||
* File name.
|
||||
* @param int $delta
|
||||
* Delta in multiple values field.
|
||||
*/
|
||||
public function uploadFileByImceSource($uri = '', $filename = '', $delta = 0) {
|
||||
$scheme = parse_url($uri, PHP_URL_SCHEME);
|
||||
$imce_path = str_replace("$scheme://", '', $uri);
|
||||
$edit = array(
|
||||
$this->fieldName . '[' . $delta . '][filefield_imce][imce_paths]' => $imce_path,
|
||||
);
|
||||
$this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_' . $delta . '_imce_select' => t('Select')));
|
||||
|
||||
|
||||
if ($filename) {
|
||||
$this->assertFileUploaded($filename, $delta);
|
||||
}
|
||||
else {
|
||||
$this->assertFileNotUploaded($delta);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload file by 'Remote' source.
|
||||
*
|
||||
* @param string $url
|
||||
* File url.
|
||||
* @param string $filename
|
||||
* File name.
|
||||
* @param int $delta
|
||||
* Delta in multiple values field.
|
||||
*/
|
||||
public function uploadFileByRemoteSource($url = '', $filename = '', $delta = 0) {
|
||||
$name = $this->fieldName . '[' . $delta . '][filefield_remote][url]';
|
||||
$edit = array($name => $url);
|
||||
$this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_' . $delta . '_transfer' => t('Transfer')));
|
||||
|
||||
if ($filename) {
|
||||
$this->assertFileUploaded($filename, $delta);
|
||||
}
|
||||
else {
|
||||
$this->assertFileNotUploaded($delta);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload file by 'Upload' source.
|
||||
*
|
||||
* @param string $uri
|
||||
* File uri.
|
||||
* @param string $filename
|
||||
* File name.
|
||||
* @param int $delta
|
||||
* Delta in multiple values field.
|
||||
*/
|
||||
public function uploadFileByUploadSource($uri = '', $filename = '', $delta = 0, $multiple = FALSE) {
|
||||
$name = 'files[' . $this->fieldName . '_' . $delta . ']';
|
||||
if ($multiple) {
|
||||
$name .= '[]';
|
||||
}
|
||||
$edit = array(
|
||||
$name => $uri ? drupal_realpath($uri) : '',
|
||||
);
|
||||
$this->drupalPostAjaxForm(NULL, $edit, array($this->fieldName . '_' . $delta . '_upload_button' => t('Upload')));
|
||||
|
||||
if ($filename) {
|
||||
$this->assertFileUploaded($filename, $delta);
|
||||
}
|
||||
else {
|
||||
$this->assertFileNotUploaded($delta);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if file is uploaded.
|
||||
*
|
||||
* @param string $filename
|
||||
* File name.
|
||||
* @param int $delta
|
||||
* Delta in multiple values field.
|
||||
*/
|
||||
public function assertFileUploaded($filename, $delta = 0) {
|
||||
$this->assertLink($filename);
|
||||
$this->assertFieldByXPath('//input[@name="' . $this->fieldName . '_' . $delta . '_remove_button"]', t('Remove'), 'After uploading a file, "Remove" button is displayed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if file is not uploaded.
|
||||
*
|
||||
* @param int $delta
|
||||
* Delta in multiple values field.
|
||||
*/
|
||||
public function assertFileNotUploaded($delta = 0) {
|
||||
$this->assertNoFieldByXPath('//input[@name="' . $this->fieldName . '_' . $delta . '_remove_button"]', t('Remove'), '"Remove" button is not displayed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove uploaded file.
|
||||
*
|
||||
* @param string $filename
|
||||
* File name.
|
||||
* @param int $delta
|
||||
* Delta in multiple values field.
|
||||
*/
|
||||
public function removeFile($filename, $delta = 0) {
|
||||
$this->drupalPostAjaxForm(NULL, array(), array($this->fieldName . '_' . $delta . '_remove_button' => t('Remove')));
|
||||
|
||||
// Ensure file is removed.
|
||||
$this->assertFileRemoved($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if file is removed.
|
||||
*
|
||||
* @param string $filename
|
||||
* File name.
|
||||
*/
|
||||
public function assertFileRemoved($filename) {
|
||||
$this->assertNoLink($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field setting.
|
||||
*
|
||||
* @param string $setting_name
|
||||
* Setting name.
|
||||
*/
|
||||
public function getFieldSetting($setting_name) {
|
||||
$field_definition = FieldConfig::load("node.{$this->typeName}.{$this->fieldName}");
|
||||
return $field_definition->getSetting($setting_name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\filefield_sources\Tests\ClipboardSourceTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\Tests;
|
||||
|
||||
/**
|
||||
* Tests the imce source.
|
||||
*
|
||||
* @group filefield_sources
|
||||
*/
|
||||
class ImceSourceTest extends FileFieldSourcesTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('imce');
|
||||
|
||||
/**
|
||||
* Sets up for imce source test case.
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->setUpImce();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests imce source enabled.
|
||||
*/
|
||||
public function testImceSourceEnabled() {
|
||||
$this->enableSources(array(
|
||||
'imce' => TRUE,
|
||||
));
|
||||
$file = $this->createPermanentFileEntity();
|
||||
|
||||
$this->uploadFileByImceSource($file->getFileUri(), $file->getFilename(), 0);
|
||||
|
||||
// We can only upload one file on single value field.
|
||||
$this->assertNoFieldByXPath('//input[@type="submit"]', t('Select'), t('After uploading a file, "Select" button is no longer displayed.'));
|
||||
|
||||
$this->removeFile($file->getFilename(), 0);
|
||||
|
||||
// Can upload file again.
|
||||
$this->assertFieldByXpath('//input[@type="submit"]', t('Select'), 'After clicking the "Remove" button, the "Select" button is displayed.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\filefield_sources\Tests\MultipleValuesTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\Tests;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Tests multiple sources on multiple values field.
|
||||
*
|
||||
* @group filefield_sources
|
||||
*/
|
||||
class MultipleValuesTest extends FileFieldSourcesTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('imce');
|
||||
|
||||
/**
|
||||
* Sets up for multiple values test case.
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->setUpImce();
|
||||
|
||||
// Create test files.
|
||||
$this->permanent_file_entity_1 = $this->createPermanentFileEntity();
|
||||
$this->permanent_file_entity_2 = $this->createPermanentFileEntity();
|
||||
$this->temporary_file_entity_1 = $this->createTemporaryFileEntity();
|
||||
$this->temporary_file_entity_2 = $this->createTemporaryFileEntity();
|
||||
|
||||
$path = file_default_scheme() . '://' . FILEFIELD_SOURCE_ATTACH_DEFAULT_PATH . '/';
|
||||
$this->temporary_file = $this->createTemporaryFile($path);
|
||||
|
||||
// Change allowed number of values.
|
||||
$this->drupalPostForm('admin/structure/types/manage/' . $this->typeName . '/fields/node.' . $this->typeName . '.' . $this->fieldName . '/storage', array('cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED), t('Save field settings'));
|
||||
|
||||
$this->enableSources(array(
|
||||
'upload' => TRUE,
|
||||
'remote' => TRUE,
|
||||
'clipboard' => TRUE,
|
||||
'reference' => TRUE,
|
||||
'attach' => TRUE,
|
||||
'imce' => TRUE,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests uploading then removing files.
|
||||
*/
|
||||
public function testUploadThenRemoveFiles() {
|
||||
$this->uploadFiles();
|
||||
|
||||
// Remove all uploaded files.
|
||||
$this->removeFile($this->temporary_file_entity_2->getFilename(), 4);
|
||||
$this->removeFile('INSTALL.txt', 0);
|
||||
$this->removeFile($this->temporary_file_entity_1->getFilename(), 1);
|
||||
$this->removeFile($this->temporary_file->filename, 1);
|
||||
$this->removeFile($this->permanent_file_entity_1->getFilename(), 0);
|
||||
$this->removeFile($this->permanent_file_entity_2->getFilename(), 0);
|
||||
|
||||
// Ensure all files have been removed.
|
||||
$this->assertNoFieldByXPath('//input[@type="submit"]', t('Remove'), 'All files have been removed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests uploading files and saving node.
|
||||
*/
|
||||
public function testUploadFilesThenSaveNode() {
|
||||
$this->uploadFiles();
|
||||
|
||||
$this->drupalPostForm(NULL, array('title[0][value]' => $this->randomMachineName()), t('Save and publish'));
|
||||
|
||||
// Ensure all files are saved to node.
|
||||
$this->assertLink('INSTALL.txt');
|
||||
$this->assertLink($this->permanent_file_entity_1->getFilename());
|
||||
$this->assertLink($this->permanent_file_entity_2->getFilename());
|
||||
$this->assertLink($this->temporary_file_entity_1->getFilename());
|
||||
$this->assertLink($this->temporary_file_entity_2->getFilename());
|
||||
$this->assertLink($this->temporary_file->filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload files.
|
||||
*
|
||||
* @return int
|
||||
* Number of files uploaded.
|
||||
*/
|
||||
protected function uploadFiles() {
|
||||
$uploaded_files = 0;
|
||||
|
||||
// Ensure no files has been uploaded.
|
||||
$this->assertNoFieldByXPath('//input[@type="submit"]', t('Remove'), 'There are no file have been uploaded.');
|
||||
|
||||
// Upload a file by 'Remote' source.
|
||||
$this->uploadFileByRemoteSource($GLOBALS['base_url'] . '/core/INSTALL.txt', 'INSTALL.txt', $uploaded_files);
|
||||
$uploaded_files++;
|
||||
|
||||
// Upload a file by 'Reference' source.
|
||||
$this->uploadFileByReferenceSource($this->permanent_file_entity_1->id(), $this->permanent_file_entity_1->getFilename(), $uploaded_files);
|
||||
$uploaded_files++;
|
||||
|
||||
// Upload a file by 'Clipboard' source.
|
||||
$this->uploadFileByClipboardSource($this->temporary_file_entity_1->getFileUri(), $this->temporary_file_entity_1->getFileName(), $uploaded_files);
|
||||
$uploaded_files++;
|
||||
|
||||
// Upload a file by 'Attach' source.
|
||||
$this->uploadFileByAttachSource($this->temporary_file->uri, $this->temporary_file->filename, $uploaded_files);
|
||||
$uploaded_files++;
|
||||
|
||||
// Upload a file by 'Upload' source.
|
||||
$this->uploadFileByUploadSource($this->temporary_file_entity_2->getFileUri(), $this->temporary_file_entity_2->getFilename(), $uploaded_files, TRUE);
|
||||
$uploaded_files++;
|
||||
|
||||
// Upload a file by 'Imce' source.
|
||||
$this->uploadFileByImceSource($this->permanent_file_entity_2->getFileUri(), $this->permanent_file_entity_2->getFileName(), $uploaded_files);
|
||||
$uploaded_files++;
|
||||
|
||||
// Ensure files have been uploaded.
|
||||
$remove_buttons = $this->xpath('//input[@type="submit" and @value="' . t('Remove') . '"]');
|
||||
$this->assertEqual(count($remove_buttons), $uploaded_files, "There are $uploaded_files files have been uploaded.");
|
||||
|
||||
return $uploaded_files;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\filefield_sources\Tests\ReferenceSourceTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\Tests;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
|
||||
/**
|
||||
* Tests the reference source.
|
||||
*
|
||||
* @group filefield_sources
|
||||
*/
|
||||
class ReferenceSourceTest extends FileFieldSourcesTestBase {
|
||||
|
||||
/**
|
||||
* Tests reference source enabled.
|
||||
*/
|
||||
public function testReferenceSourceEnabled() {
|
||||
|
||||
// Create test file.
|
||||
$file = $this->createPermanentFileEntity();
|
||||
|
||||
$this->enableSources(array(
|
||||
'reference' => TRUE,
|
||||
));
|
||||
|
||||
// Upload a file by 'Reference' source.
|
||||
$this->uploadFileByReferenceSource($file->id(), $file->getFilename(), 0);
|
||||
|
||||
// We can only refer one file on single value field.
|
||||
$this->assertNoFieldByXPath('//input[@type="submit"]', t('Select'), t('After uploading a file, "Select" button is no longer displayed.'));
|
||||
|
||||
// Remove uploaded file.
|
||||
$this->removeFile($file->getFileName(), 0);
|
||||
|
||||
// Can select file again.
|
||||
$this->assertFieldByXpath('//input[@type="submit"]', t('Select'), 'After clicking the "Remove" button, the "Select" button is displayed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test autocompletion.
|
||||
*/
|
||||
public function testAutocompletion() {
|
||||
// Create test file.
|
||||
$file = $this->createPermanentFileEntity();
|
||||
$filename = $file->getFileName();
|
||||
$first_character = substr($filename, 0, 1);
|
||||
$second_character = substr($filename, 1, 1);
|
||||
|
||||
// Switch to 'Starts with' match type.
|
||||
$this->updateFilefieldSourcesSettings('source_reference', 'autocomplete', '0');
|
||||
|
||||
// STARTS_WITH: empty results.
|
||||
$query = $this->findCharacterNotInString($first_character);
|
||||
$autocomplete_result = $this->drupalGetJSON('file/reference/node/' . $this->typeName . '/' . $this->fieldName, array('query' => array('q' => $query)));
|
||||
$this->assertEqual($autocomplete_result, array(), "No files that have name starts with '$query'");
|
||||
|
||||
// STARTS_WITH: not empty results.
|
||||
$query = $first_character;
|
||||
$autocomplete_result = $this->drupalGetJSON('file/reference/node/' . $this->typeName . '/' . $this->fieldName, array('query' => array('q' => $query)));
|
||||
$this->assertEqual($autocomplete_result[0]['label'], $filename, 'Autocompletion return correct label.');
|
||||
$this->assertEqual($autocomplete_result[0]['value'], $filename . ' [fid:' . $file->id() . ']', 'Autocompletion return correct value.');
|
||||
|
||||
// Switch to 'Contains' match type.
|
||||
$this->updateFilefieldSourcesSettings('source_reference', 'autocomplete', '1');
|
||||
|
||||
// CONTAINS: empty results.
|
||||
$query = $this->findCharacterNotInString($filename);
|
||||
$autocomplete_result = $this->drupalGetJSON('file/reference/node/' . $this->typeName . '/' . $this->fieldName, array('query' => array('q' => $query)));
|
||||
$this->assertEqual($autocomplete_result, array(), "No files that have name contains '$query'");
|
||||
|
||||
// CONTAINS: not empty results.
|
||||
$query = $second_character;
|
||||
$autocomplete_result = $this->drupalGetJSON('file/reference/node/' . $this->typeName . '/' . $this->fieldName, array('query' => array('q' => $query)));
|
||||
$this->assertEqual($autocomplete_result[0]['label'], $filename, 'Autocompletion return correct label.');
|
||||
$this->assertEqual($autocomplete_result[0]['value'], $filename . ' [fid:' . $file->id() . ']', 'Autocompletion return correct value.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first character that is not in string.
|
||||
*
|
||||
* Only find for lower case character.
|
||||
*
|
||||
* @param string $string
|
||||
* String to check.
|
||||
*
|
||||
* @return string
|
||||
* First character that is not in the string.
|
||||
*/
|
||||
protected function findCharacterNotInString($string) {
|
||||
// Only check for lower case string.
|
||||
$string = Unicode::strtolower($string);
|
||||
|
||||
// Lower case characters and numbers generated by
|
||||
// \Drupal\simpletest\TestBase::randomMachineName().
|
||||
$values = array_merge(range(97, 122), range(48, 57));
|
||||
foreach ($values as $value) {
|
||||
$character = chr($value);
|
||||
if (strpos($string, $character) === FALSE) {
|
||||
return $character;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\filefield_sources\Tests\RemoteSourceTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\Tests;
|
||||
|
||||
/**
|
||||
* Tests the remote source.
|
||||
*
|
||||
* @group filefield_sources
|
||||
*/
|
||||
class RemoteSourceTest extends FileFieldSourcesTestBase {
|
||||
|
||||
/**
|
||||
* Tests remote source enabled.
|
||||
*/
|
||||
public function testRemoteSourceEnabled() {
|
||||
$this->enableSources(array(
|
||||
'remote' => TRUE,
|
||||
));
|
||||
|
||||
// Upload a file by 'Remote' source.
|
||||
$this->uploadFileByRemoteSource($GLOBALS['base_url'] . '/README.txt', 'README.txt', 0);
|
||||
|
||||
// We can only transfer one file on single value field.
|
||||
$this->assertNoFieldByXPath('//input[@type="submit"]', t('Transfer'), t('After uploading a file, "Transfer" button is no longer displayed.'));
|
||||
|
||||
// Remove uploaded file.
|
||||
$this->removeFile('README.txt', 0);
|
||||
|
||||
// Can transfer file again.
|
||||
$this->assertFieldByXpath('//input[@type="submit"]', t('Transfer'), 'After clicking the "Remove" button, the "Transfer" button is displayed.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\filefield_sources\Tests\UploadSourceTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\filefield_sources\Tests;
|
||||
|
||||
/**
|
||||
* Tests the upload source.
|
||||
*
|
||||
* @group filefield_sources
|
||||
*/
|
||||
class UploadSourceTest extends FileFieldSourcesTestBase {
|
||||
|
||||
/**
|
||||
* Tests upload source enabled.
|
||||
*/
|
||||
public function testUploadSourceEnabled() {
|
||||
$this->enableSources(array(
|
||||
'upload' => TRUE,
|
||||
));
|
||||
|
||||
$this->assertUploadSourceWorkProperly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests all sources enabled.
|
||||
*/
|
||||
public function testAllSourcesEnabled() {
|
||||
$this->enableSources(array(
|
||||
'upload' => TRUE,
|
||||
'remote' => TRUE,
|
||||
'clipboard' => TRUE,
|
||||
'reference' => TRUE,
|
||||
'attach' => TRUE,
|
||||
));
|
||||
|
||||
$this->assertUploadSourceWorkProperly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests upload source still working properly.
|
||||
*/
|
||||
protected function assertUploadSourceWorkProperly() {
|
||||
$file = $this->createTemporaryFileEntity();
|
||||
|
||||
// Upload a file by 'Upload' source.
|
||||
$this->uploadFileByUploadSource($file->getFileUri(), $file->getFilename(), 0, FALSE);
|
||||
|
||||
// We can only upload one file on single value field.
|
||||
$this->assertNoFieldByXPath('//input[@type="submit"]', t('Upload'), t('After uploading a file, "Upload" button is no longer displayed.'));
|
||||
|
||||
// Remove uploaded file.
|
||||
$this->removeFile($file->getFilename(), 0);
|
||||
|
||||
// Can upload file again.
|
||||
$this->assertFieldByXpath('//input[@type="submit"]', t('Upload'), 'After clicking the "Remove" button, the "Upload" button is displayed.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,8 +17,8 @@
|
||||
"source": "https://cgit.drupalcode.org/migrate_plus"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"require": {},
|
||||
"suggest": {
|
||||
"sainsburys/guzzle-oauth2-plugin": "3.0 required for the OAuth2 authentication plugin"
|
||||
"sainsburys/guzzle-oauth2-plugin": "3.0 required for the OAuth2 authentication plugin",
|
||||
"ext-soap": "*"
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ process:
|
||||
title: name
|
||||
nid: bid
|
||||
uid:
|
||||
plugin: migration
|
||||
plugin: migration_lookup
|
||||
migration: beer_user
|
||||
source: aid
|
||||
sticky:
|
||||
@@ -22,7 +22,7 @@ process:
|
||||
default_value: 0
|
||||
field_migrate_example_country: countries
|
||||
field_migrate_example_beer_style:
|
||||
plugin: migration
|
||||
plugin: migration_lookup
|
||||
migration: beer_term
|
||||
source: terms
|
||||
# Some Drupal fields may have multiple components we may want to set
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ process:
|
||||
# IDs in map tables, and the migration plugin is the means of performing a
|
||||
# lookup in those map tables during processing.
|
||||
parent:
|
||||
plugin: migration
|
||||
plugin: migration_lookup
|
||||
# Here we reference the migration whose map table we're performing a lookup
|
||||
# against. You'll note that in this case we're actually referencing this
|
||||
# migration itself, since category parents are imported by the same
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ process:
|
||||
# when that migration runs it knows that each incoming beer should overwrite
|
||||
# its stub instead of creating a new node.
|
||||
field_migrate_example_favbeers:
|
||||
plugin: migration
|
||||
plugin: migration_lookup
|
||||
source: beers
|
||||
migration: beer_node
|
||||
|
||||
|
||||
+5
-3
@@ -7,9 +7,11 @@ dependencies:
|
||||
- drupal:migrate
|
||||
- migrate_plus:migrate_example_setup
|
||||
- migrate_plus:migrate_plus
|
||||
- drupal:menu_ui
|
||||
- drupal:path
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-05-10
|
||||
version: '8.x-4.0-beta1'
|
||||
# Information added by Drupal.org packaging script on 2017-12-12
|
||||
version: '8.x-4.0-beta2'
|
||||
core: '8.x'
|
||||
project: 'migrate_plus'
|
||||
datestamp: 1494450189
|
||||
datestamp: 1513088305
|
||||
|
||||
+3
-3
@@ -11,8 +11,8 @@ dependencies:
|
||||
- drupal:options
|
||||
- drupal:taxonomy
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-05-10
|
||||
version: '8.x-4.0-beta1'
|
||||
# Information added by Drupal.org packaging script on 2017-12-12
|
||||
version: '8.x-4.0-beta2'
|
||||
core: '8.x'
|
||||
project: 'migrate_plus'
|
||||
datestamp: 1494450189
|
||||
datestamp: 1513088305
|
||||
|
||||
+132
-130
@@ -27,270 +27,272 @@ function migrate_example_setup_install() {
|
||||
}
|
||||
|
||||
function migrate_example_beer_schema_node() {
|
||||
return array(
|
||||
return [
|
||||
'description' => 'Beers of the world.',
|
||||
'fields' => array(
|
||||
'bid' => array(
|
||||
'fields' => [
|
||||
'bid' => [
|
||||
'type' => 'serial',
|
||||
'not null' => TRUE,
|
||||
'description' => 'Beer ID.',
|
||||
),
|
||||
'name' => array(
|
||||
],
|
||||
'name' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'body' => array(
|
||||
],
|
||||
'body' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Full description of the beer.',
|
||||
),
|
||||
'excerpt' => array(
|
||||
],
|
||||
'excerpt' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Abstract for this beer.',
|
||||
),
|
||||
'countries' => array(
|
||||
],
|
||||
'countries' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Countries of origin. Multiple values, delimited by pipe',
|
||||
),
|
||||
'aid' => array(
|
||||
],
|
||||
'aid' => [
|
||||
'type' => 'int',
|
||||
'not null' => FALSE,
|
||||
'description' => 'Account Id of the author.',
|
||||
),
|
||||
'image' => array(
|
||||
],
|
||||
'image' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Image path',
|
||||
),
|
||||
'image_alt' => array(
|
||||
],
|
||||
'image_alt' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Image ALT',
|
||||
),
|
||||
'image_title' => array(
|
||||
],
|
||||
'image_title' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Image title',
|
||||
),
|
||||
'image_description' => array(
|
||||
],
|
||||
'image_description' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Image description',
|
||||
),
|
||||
),
|
||||
'primary key' => array('bid'),
|
||||
);
|
||||
],
|
||||
],
|
||||
'primary key' => ['bid'],
|
||||
];
|
||||
}
|
||||
|
||||
function migrate_example_beer_schema_topic() {
|
||||
return array(
|
||||
return [
|
||||
'description' => 'Categories',
|
||||
'fields' => array(
|
||||
'style' => array(
|
||||
'fields' => [
|
||||
'style' => [
|
||||
'type' => 'varchar_ascii',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'details' => array(
|
||||
],
|
||||
'details' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
),
|
||||
'style_parent' => array(
|
||||
],
|
||||
'style_parent' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Parent topic, if any',
|
||||
),
|
||||
'region' => array(
|
||||
],
|
||||
'region' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Region first associated with this style',
|
||||
),
|
||||
'hoppiness' => array(
|
||||
],
|
||||
'hoppiness' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Relative hoppiness of the beer',
|
||||
),
|
||||
),
|
||||
'primary key' => array('style'),
|
||||
);
|
||||
],
|
||||
],
|
||||
'primary key' => ['style'],
|
||||
];
|
||||
}
|
||||
|
||||
function migrate_example_beer_schema_topic_node() {
|
||||
return array(
|
||||
return [
|
||||
'description' => 'Beers topic pairs.',
|
||||
'fields' => array(
|
||||
'bid' => array(
|
||||
'fields' => [
|
||||
'bid' => [
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'description' => 'Beer ID.',
|
||||
),
|
||||
'style' => array(
|
||||
],
|
||||
'style' => [
|
||||
'type' => 'varchar_ascii',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'description' => 'Topic name',
|
||||
),
|
||||
),
|
||||
'primary key' => array('style', 'bid'),
|
||||
);
|
||||
],
|
||||
],
|
||||
'primary key' => ['style', 'bid'],
|
||||
];
|
||||
}
|
||||
|
||||
function migrate_example_beer_schema_comment() {
|
||||
return array(
|
||||
return [
|
||||
'description' => 'Beers comments.',
|
||||
'fields' => array(
|
||||
'cid' => array(
|
||||
'fields' => [
|
||||
'cid' => [
|
||||
'type' => 'serial',
|
||||
'not null' => TRUE,
|
||||
'description' => 'Comment ID.',
|
||||
),
|
||||
'bid' => array(
|
||||
],
|
||||
'bid' => [
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'description' => 'Beer ID that is being commented upon',
|
||||
),
|
||||
'cid_parent' => array(
|
||||
],
|
||||
'cid_parent' => [
|
||||
'type' => 'int',
|
||||
'not null' => FALSE,
|
||||
'description' => 'Parent comment ID in case of comment replies.',
|
||||
),
|
||||
'subject' => array(
|
||||
],
|
||||
'subject' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Comment subject',
|
||||
),
|
||||
'body' => array(
|
||||
],
|
||||
'body' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Comment body',
|
||||
),
|
||||
'name' => array(
|
||||
],
|
||||
'name' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Comment name (if anon)',
|
||||
),
|
||||
'mail' => array(
|
||||
],
|
||||
'mail' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Comment email (if anon)',
|
||||
),
|
||||
'aid' => array(
|
||||
],
|
||||
'aid' => [
|
||||
'type' => 'int',
|
||||
'not null' => FALSE,
|
||||
'description' => 'Account ID (if any).',
|
||||
),
|
||||
),
|
||||
'primary key' => array('cid'),
|
||||
);
|
||||
],
|
||||
],
|
||||
'primary key' => ['cid'],
|
||||
];
|
||||
}
|
||||
|
||||
function migrate_example_beer_schema_account() {
|
||||
return array(
|
||||
return [
|
||||
'description' => 'Beers accounts.',
|
||||
'fields' => array(
|
||||
'aid' => array(
|
||||
'fields' => [
|
||||
'aid' => [
|
||||
'type' => 'serial',
|
||||
'not null' => TRUE,
|
||||
'description' => 'Account ID',
|
||||
),
|
||||
'status' => array(
|
||||
],
|
||||
'status' => [
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'description' => 'Blocked_Allowed',
|
||||
),
|
||||
'registered' => array(
|
||||
],
|
||||
'registered' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'description' => 'Registration date',
|
||||
),
|
||||
'username' => array(
|
||||
],
|
||||
'username' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Account name (for login)',
|
||||
),
|
||||
'nickname' => array(
|
||||
],
|
||||
'nickname' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Account name (for display)',
|
||||
),
|
||||
'password' => array(
|
||||
],
|
||||
'password' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Account password (raw)',
|
||||
),
|
||||
'email' => array(
|
||||
],
|
||||
'email' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Account email',
|
||||
),
|
||||
'sex' => array(
|
||||
],
|
||||
'sex' => [
|
||||
'type' => 'int',
|
||||
'not null' => FALSE,
|
||||
'description' => 'Gender (0 for male, 1 for female)',
|
||||
),
|
||||
'beers' => array(
|
||||
],
|
||||
'beers' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Favorite Beers',
|
||||
),
|
||||
),
|
||||
'primary key' => array('aid'),
|
||||
);
|
||||
],
|
||||
],
|
||||
'primary key' => ['aid'],
|
||||
];
|
||||
}
|
||||
|
||||
function migrate_example_beer_data_node() {
|
||||
$fields = array('bid', 'name', 'body', 'excerpt', 'countries', 'aid', 'image',
|
||||
'image_alt', 'image_title', 'image_description');
|
||||
$fields = ['bid', 'name', 'body', 'excerpt', 'countries', 'aid', 'image',
|
||||
'image_alt', 'image_title', 'image_description'];
|
||||
$query = db_insert('migrate_example_beer_node')
|
||||
->fields($fields);
|
||||
->fields($fields);
|
||||
// Use high bid numbers to avoid overwriting an existing node id.
|
||||
$data = array(
|
||||
array(99999999, 'Heineken', 'Blab Blah Blah Green', 'Green', 'Netherlands|Belgium', 0, 'heineken.jpg', 'Heinekin alt', 'Heinekin title', 'Heinekin description'), // comes with migrate_example project.
|
||||
array(99999998, 'Miller Lite', 'We love Miller Brewing', 'Tasteless', 'USA|Canada', 1, NULL, NULL, NULL, NULL),
|
||||
array(99999997, 'Boddington', 'English occasionally get something right', 'A treat', 'United Kingdom', 1, NULL, NULL, NULL, NULL),
|
||||
);
|
||||
$data = [
|
||||
[99999999, 'Heineken', 'Blab Blah Blah Green', 'Green', 'Netherlands|Belgium', 0, 'heineken.jpg', 'Heinekin alt', 'Heinekin title', 'Heinekin description'], // comes with migrate_example project.
|
||||
[99999998, 'Miller Lite', 'We love Miller Brewing', 'Tasteless', 'USA|Canada', 1, NULL, NULL, NULL, NULL],
|
||||
[99999997, 'Boddington', 'English occasionally get something right', 'A treat', 'United Kingdom', 1, NULL, NULL, NULL, NULL],
|
||||
];
|
||||
foreach ($data as $row) {
|
||||
$query->values(array_combine($fields, $row));
|
||||
}
|
||||
$query->execute();
|
||||
}
|
||||
|
||||
// Note that alice has duplicate username. Exercises dedupe_entity plugin.
|
||||
// @TODO duplicate email also.
|
||||
/**
|
||||
* Note that alice has duplicate username. Exercises dedupe_entity plugin.
|
||||
*@TODO duplicate email also.
|
||||
*/
|
||||
function migrate_example_beer_data_account() {
|
||||
$fields = array('status', 'registered', 'username', 'nickname', 'password', 'email', 'sex', 'beers');
|
||||
$fields = ['status', 'registered', 'username', 'nickname', 'password', 'email', 'sex', 'beers'];
|
||||
$query = db_insert('migrate_example_beer_account')
|
||||
->fields($fields);
|
||||
$data = array(
|
||||
array(1, '2010-03-30 10:31:05', 'alice', 'alice in beerland', 'alicepass', 'alice@example.com', '1', '99999999|99999998|99999997'),
|
||||
array(1, '2010-04-04 10:31:05', 'alice', 'alice in aleland', 'alicepass', 'alice2@example.com', '1', '99999999|99999998|99999997'),
|
||||
array(0, '2007-03-15 10:31:05', 'bob', 'rebob', 'bobpass', 'bob@example.com', '0', '99999999|99999997'),
|
||||
array(1, '2004-02-29 10:31:05', 'charlie', 'charlie chocolate', 'mykids', 'charlie@example.com', '0', '99999999|99999998'),
|
||||
);
|
||||
$data = [
|
||||
[1, '2010-03-30 10:31:05', 'alice', 'alice in beerland', 'alicepass', 'alice@example.com', '1', '99999999|99999998|99999997'],
|
||||
[1, '2010-04-04 10:31:05', 'alice', 'alice in aleland', 'alicepass', 'alice2@example.com', '1', '99999999|99999998|99999997'],
|
||||
[0, '2007-03-15 10:31:05', 'bob', 'rebob', 'bobpass', 'bob@example.com', '0', '99999999|99999997'],
|
||||
[1, '2004-02-29 10:31:05', 'charlie', 'charlie chocolate', 'mykids', 'charlie@example.com', '0', '99999999|99999998'],
|
||||
];
|
||||
foreach ($data as $row) {
|
||||
$query->values(array_combine($fields, $row));
|
||||
}
|
||||
@@ -298,16 +300,16 @@ function migrate_example_beer_data_account() {
|
||||
}
|
||||
|
||||
function migrate_example_beer_data_comment() {
|
||||
$fields = array('bid', 'cid_parent', 'subject', 'body', 'name', 'mail', 'aid');
|
||||
$fields = ['bid', 'cid_parent', 'subject', 'body', 'name', 'mail', 'aid'];
|
||||
$query = db_insert('migrate_example_beer_comment')
|
||||
->fields($fields);
|
||||
$data = array(
|
||||
array(99999998, NULL, 'im first', 'full body', 'alice', 'alice@example.com', 0),
|
||||
array(99999998, NULL, 'im second', 'aromatic', 'alice', 'alice@example.com', 0),
|
||||
array(99999999, NULL, 'im parent', 'malty', 'alice', 'alice@example.com', 0),
|
||||
array(99999999, 1, 'im child', 'cold body', 'bob', NULL, 1),
|
||||
array(99999999, 4, 'im grandchild', 'bitter body', 'charlie@example.com', NULL, 1),
|
||||
);
|
||||
$data = [
|
||||
[99999998, NULL, 'im first', 'full body', 'alice', 'alice@example.com', 0],
|
||||
[99999998, NULL, 'im second', 'aromatic', 'alice', 'alice@example.com', 0],
|
||||
[99999999, NULL, 'im parent', 'malty', 'alice', 'alice@example.com', 0],
|
||||
[99999999, 1, 'im child', 'cold body', 'bob', NULL, 1],
|
||||
[99999999, 4, 'im grandchild', 'bitter body', 'charlie@example.com', NULL, 1],
|
||||
];
|
||||
foreach ($data as $row) {
|
||||
$query->values(array_combine($fields, $row));
|
||||
}
|
||||
@@ -315,14 +317,14 @@ function migrate_example_beer_data_comment() {
|
||||
}
|
||||
|
||||
function migrate_example_beer_data_topic() {
|
||||
$fields = array('style', 'details', 'style_parent', 'region', 'hoppiness');
|
||||
$fields = ['style', 'details', 'style_parent', 'region', 'hoppiness'];
|
||||
$query = db_insert('migrate_example_beer_topic')
|
||||
->fields($fields);
|
||||
$data = array(
|
||||
array('ale', 'traditional', NULL, 'Medieval British Isles', 'Medium'),
|
||||
array('red ale', 'colorful', 'ale', NULL, NULL),
|
||||
array('pilsner', 'refreshing', NULL, 'Pilsen, Bohemia (now Czech Republic)', 'Low'),
|
||||
);
|
||||
$data = [
|
||||
['ale', 'traditional', NULL, 'Medieval British Isles', 'Medium'],
|
||||
['red ale', 'colorful', 'ale', NULL, NULL],
|
||||
['pilsner', 'refreshing', NULL, 'Pilsen, Bohemia (now Czech Republic)', 'Low'],
|
||||
];
|
||||
foreach ($data as $row) {
|
||||
$query->values(array_combine($fields, $row));
|
||||
}
|
||||
@@ -330,14 +332,14 @@ function migrate_example_beer_data_topic() {
|
||||
}
|
||||
|
||||
function migrate_example_beer_data_topic_node() {
|
||||
$fields = array('bid', 'style');
|
||||
$fields = ['bid', 'style'];
|
||||
$query = db_insert('migrate_example_beer_topic_node')
|
||||
->fields($fields);
|
||||
$data = array(
|
||||
array(99999999, 'pilsner'),
|
||||
array(99999999, 'red ale'),
|
||||
array(99999998, 'red ale'),
|
||||
);
|
||||
$data = [
|
||||
[99999999, 'pilsner'],
|
||||
[99999999, 'red ale'],
|
||||
[99999998, 'red ale'],
|
||||
];
|
||||
foreach ($data as $row) {
|
||||
$query->values(array_combine($fields, $row));
|
||||
}
|
||||
|
||||
+3
-3
@@ -8,11 +8,11 @@ destination:
|
||||
plugin: entity:comment
|
||||
process:
|
||||
pid:
|
||||
plugin: migration
|
||||
plugin: migration_lookup
|
||||
migration: beer_comment
|
||||
source: cid_parent
|
||||
entity_id:
|
||||
plugin: migration
|
||||
plugin: migration_lookup
|
||||
migration: beer_node
|
||||
source: bid
|
||||
entity_type:
|
||||
@@ -26,7 +26,7 @@ process:
|
||||
default_value: node_comments
|
||||
subject: subject
|
||||
uid:
|
||||
plugin: migration
|
||||
plugin: migration_lookup
|
||||
migration: beer_user
|
||||
source: aid
|
||||
name: name
|
||||
|
||||
+2
-2
@@ -18,9 +18,9 @@ class BeerComment extends SqlBase {
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this->select('migrate_example_beer_comment', 'mec')
|
||||
->fields('mec', ['cid', 'cid_parent', 'name', 'mail', 'aid',
|
||||
->fields('mec', ['cid', 'cid_parent', 'name', 'mail', 'aid',
|
||||
'body', 'bid', 'subject'])
|
||||
->orderBy('cid_parent', 'ASC');
|
||||
->orderBy('cid_parent', 'ASC');
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@ class BeerNode extends SqlBase {
|
||||
* below.
|
||||
*/
|
||||
$query = $this->select('migrate_example_beer_node', 'b')
|
||||
->fields('b', ['bid', 'name', 'body', 'excerpt', 'aid',
|
||||
->fields('b', ['bid', 'name', 'body', 'excerpt', 'aid',
|
||||
'countries', 'image', 'image_alt', 'image_title',
|
||||
'image_description']);
|
||||
return $query;
|
||||
@@ -82,7 +82,7 @@ class BeerNode extends SqlBase {
|
||||
* the beer_term migration).
|
||||
*/
|
||||
$terms = $this->select('migrate_example_beer_topic_node', 'bt')
|
||||
->fields('bt', ['style'])
|
||||
->fields('bt', ['style'])
|
||||
->condition('bid', $row->getSourceProperty('bid'))
|
||||
->execute()
|
||||
->fetchCol();
|
||||
|
||||
+3
-3
@@ -50,9 +50,9 @@ class BeerTerm extends SqlBase {
|
||||
* to humans what the field represents. You should always
|
||||
*/
|
||||
$fields = [
|
||||
'style' => $this->t('Account ID'),
|
||||
'details' => $this->t('Blocked/Allowed'),
|
||||
'style_parent' => $this->t('Registered date'),
|
||||
'style' => $this->t('Beer style'),
|
||||
'details' => $this->t('Style details'),
|
||||
'style_parent' => $this->t('Parent style'),
|
||||
// These values are not currently migrated - it's OK to skip fields you
|
||||
// don't need.
|
||||
'region' => $this->t('Region the style is associated with'),
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ process:
|
||||
region: migrate_example_wine_regions
|
||||
variety: migrate_example_wine_varieties
|
||||
parent:
|
||||
plugin: migration
|
||||
plugin: migration_lookup
|
||||
migration: wine_terms
|
||||
source: category_parent
|
||||
weight: ordering
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ process:
|
||||
name: category_name
|
||||
description: category_details
|
||||
parent:
|
||||
plugin: migration
|
||||
plugin: migration_lookup
|
||||
migration: wine_terms
|
||||
source: category_parent
|
||||
destination:
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ process:
|
||||
name: category_name
|
||||
description: category_details
|
||||
parent:
|
||||
plugin: migration
|
||||
plugin: migration_lookup
|
||||
migration: wine_terms
|
||||
source: category_parent
|
||||
field_variety_attributes: category_attributes
|
||||
|
||||
+3
-3
@@ -8,8 +8,8 @@ dependencies:
|
||||
- migrate_plus:migrate_example_advanced_setup
|
||||
- migrate_plus:migrate_plus
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-05-10
|
||||
version: '8.x-4.0-beta1'
|
||||
# Information added by Drupal.org packaging script on 2017-12-12
|
||||
version: '8.x-4.0-beta2'
|
||||
core: '8.x'
|
||||
project: 'migrate_plus'
|
||||
datestamp: 1494450189
|
||||
datestamp: 1513088305
|
||||
|
||||
+3
-3
@@ -11,8 +11,8 @@ dependencies:
|
||||
- drupal:taxonomy
|
||||
- drupal:rest
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-05-10
|
||||
version: '8.x-4.0-beta1'
|
||||
# Information added by Drupal.org packaging script on 2017-12-12
|
||||
version: '8.x-4.0-beta2'
|
||||
core: '8.x'
|
||||
project: 'migrate_plus'
|
||||
datestamp: 1494450189
|
||||
datestamp: 1513088305
|
||||
|
||||
+330
-329
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -31,22 +31,22 @@ class VarietyItems extends ResourceBase {
|
||||
$varieties = [
|
||||
'retsina' => [
|
||||
'name' => 'Retsina',
|
||||
'parent' => 1, // categoryid for 'white'.
|
||||
'parent' => 1, // categoryid for 'white'.
|
||||
'details' => 'Greek',
|
||||
],
|
||||
'trebbiano' => [
|
||||
'name' => 'Trebbiano',
|
||||
'parent' => 1, // categoryid for 'white'.
|
||||
'parent' => 1, // categoryid for 'white'.
|
||||
'details' => 'Italian',
|
||||
],
|
||||
'valpolicella' => [
|
||||
'name' => 'Valpolicella',
|
||||
'parent' => 3, // categoryid for 'red'.
|
||||
'parent' => 3, // categoryid for 'red'.
|
||||
'details' => 'Italian Venoto region',
|
||||
],
|
||||
'bardolino' => [
|
||||
'name' => 'Bardolino',
|
||||
'parent' => 3, // categoryid for 'red'.
|
||||
'parent' => 3, // categoryid for 'red'.
|
||||
'details' => 'Italian Venoto region',
|
||||
],
|
||||
];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user