From 709fac1baf621238288afb1062633978194c3861 Mon Sep 17 00:00:00 2001
From: zbenaissa
Date: Wed, 18 Nov 2015 11:03:56 -0500
Subject: [PATCH 01/21] Initial commit of custom_config
---
.../custom_config.features.field_base.inc | 83 +++++++
.../custom_config.features.field_instance.inc | 203 ++++++++++++++++++
.../custom_config/custom_config.features.inc | 31 +++
.../custom_config.features.taxonomy.inc | 36 ++++
.../modules/custom_config/custom_config.info | 18 ++
.../custom_config/custom_config.module | 7 +
6 files changed, 378 insertions(+)
create mode 100644 docroot/sites/all/modules/custom_config/custom_config.features.field_base.inc
create mode 100644 docroot/sites/all/modules/custom_config/custom_config.features.field_instance.inc
create mode 100644 docroot/sites/all/modules/custom_config/custom_config.features.inc
create mode 100644 docroot/sites/all/modules/custom_config/custom_config.features.taxonomy.inc
create mode 100644 docroot/sites/all/modules/custom_config/custom_config.info
create mode 100644 docroot/sites/all/modules/custom_config/custom_config.module
diff --git a/docroot/sites/all/modules/custom_config/custom_config.features.field_base.inc b/docroot/sites/all/modules/custom_config/custom_config.features.field_base.inc
new file mode 100644
index 0000000..bb20698
--- /dev/null
+++ b/docroot/sites/all/modules/custom_config/custom_config.features.field_base.inc
@@ -0,0 +1,83 @@
+ 1,
+ 'cardinality' => 1,
+ 'deleted' => 0,
+ 'entity_types' => array(
+ 0 => 'node',
+ ),
+ 'field_name' => 'body',
+ 'indexes' => array(
+ 'format' => array(
+ 0 => 'format',
+ ),
+ ),
+ 'locked' => 0,
+ 'module' => 'text',
+ 'settings' => array(),
+ 'translatable' => 0,
+ 'type' => 'text_with_summary',
+ );
+
+ // Exported field_base: 'field_image'.
+ $field_bases['field_image'] = array(
+ 'active' => 1,
+ 'cardinality' => 1,
+ 'deleted' => 0,
+ 'entity_types' => array(),
+ 'field_name' => 'field_image',
+ 'indexes' => array(
+ 'fid' => array(
+ 0 => 'fid',
+ ),
+ ),
+ 'locked' => 0,
+ 'module' => 'image',
+ 'settings' => array(
+ 'default_image' => FALSE,
+ 'uri_scheme' => 'public',
+ ),
+ 'translatable' => 0,
+ 'type' => 'image',
+ );
+
+ // Exported field_base: 'field_tags'.
+ $field_bases['field_tags'] = array(
+ 'active' => 1,
+ 'cardinality' => -1,
+ 'deleted' => 0,
+ 'entity_types' => array(),
+ 'field_name' => 'field_tags',
+ 'indexes' => array(
+ 'tid' => array(
+ 0 => 'tid',
+ ),
+ ),
+ 'locked' => 0,
+ 'module' => 'taxonomy',
+ 'settings' => array(
+ 'allowed_values' => array(
+ 0 => array(
+ 'vocabulary' => 'tags',
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ 'translatable' => 0,
+ 'type' => 'taxonomy_term_reference',
+ );
+
+ return $field_bases;
+}
diff --git a/docroot/sites/all/modules/custom_config/custom_config.features.field_instance.inc b/docroot/sites/all/modules/custom_config/custom_config.features.field_instance.inc
new file mode 100644
index 0000000..9b92593
--- /dev/null
+++ b/docroot/sites/all/modules/custom_config/custom_config.features.field_instance.inc
@@ -0,0 +1,203 @@
+ 'article',
+ 'default_value' => NULL,
+ 'deleted' => 0,
+ 'description' => '',
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'hidden',
+ 'module' => 'text',
+ 'settings' => array(),
+ 'type' => 'text_default',
+ 'weight' => 0,
+ ),
+ 'teaser' => array(
+ 'label' => 'hidden',
+ 'module' => 'text',
+ 'settings' => array(
+ 'trim_length' => 600,
+ ),
+ 'type' => 'text_summary_or_trimmed',
+ 'weight' => 0,
+ ),
+ ),
+ 'entity_type' => 'node',
+ 'field_name' => 'body',
+ 'label' => 'Body',
+ 'required' => FALSE,
+ 'settings' => array(
+ 'display_summary' => TRUE,
+ 'text_processing' => 1,
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'module' => 'text',
+ 'settings' => array(
+ 'rows' => 20,
+ 'summary_rows' => 5,
+ ),
+ 'type' => 'text_textarea_with_summary',
+ 'weight' => -4,
+ ),
+ );
+
+ // Exported field_instance: 'node-article-field_image'.
+ $field_instances['node-article-field_image'] = array(
+ 'bundle' => 'article',
+ 'deleted' => 0,
+ 'description' => 'Upload an image to go with this article.',
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'hidden',
+ 'module' => 'image',
+ 'settings' => array(
+ 'image_link' => '',
+ 'image_style' => 'large',
+ ),
+ 'type' => 'image',
+ 'weight' => -1,
+ ),
+ 'teaser' => array(
+ 'label' => 'hidden',
+ 'module' => 'image',
+ 'settings' => array(
+ 'image_link' => 'content',
+ 'image_style' => 'medium',
+ ),
+ 'type' => 'image',
+ 'weight' => -1,
+ ),
+ ),
+ 'entity_type' => 'node',
+ 'field_name' => 'field_image',
+ 'label' => 'Image',
+ 'required' => FALSE,
+ 'settings' => array(
+ 'alt_field' => TRUE,
+ 'default_image' => 0,
+ 'file_directory' => 'field/image',
+ 'file_extensions' => 'png gif jpg jpeg',
+ 'max_filesize' => '',
+ 'max_resolution' => '',
+ 'min_resolution' => '',
+ 'title_field' => '',
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'module' => 'image',
+ 'settings' => array(
+ 'preview_image_style' => 'thumbnail',
+ 'progress_indicator' => 'throbber',
+ ),
+ 'type' => 'image_image',
+ 'weight' => -1,
+ ),
+ );
+
+ // Exported field_instance: 'node-article-field_tags'.
+ $field_instances['node-article-field_tags'] = array(
+ 'bundle' => 'article',
+ 'default_value' => NULL,
+ 'deleted' => 0,
+ 'description' => 'Enter a comma-separated list of words to describe your content.',
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'above',
+ 'module' => 'taxonomy',
+ 'settings' => array(),
+ 'type' => 'taxonomy_term_reference_link',
+ 'weight' => 10,
+ ),
+ 'teaser' => array(
+ 'label' => 'above',
+ 'module' => 'taxonomy',
+ 'settings' => array(),
+ 'type' => 'taxonomy_term_reference_link',
+ 'weight' => 10,
+ ),
+ ),
+ 'entity_type' => 'node',
+ 'field_name' => 'field_tags',
+ 'label' => 'Tags',
+ 'required' => FALSE,
+ 'settings' => array(
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'module' => 'taxonomy',
+ 'settings' => array(
+ 'autocomplete_path' => 'taxonomy/autocomplete',
+ 'size' => 60,
+ ),
+ 'type' => 'taxonomy_autocomplete',
+ 'weight' => -4,
+ ),
+ );
+
+ // Exported field_instance: 'node-page-body'.
+ $field_instances['node-page-body'] = array(
+ 'bundle' => 'page',
+ 'default_value' => NULL,
+ 'deleted' => 0,
+ 'description' => '',
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'hidden',
+ 'module' => 'text',
+ 'settings' => array(),
+ 'type' => 'text_default',
+ 'weight' => 0,
+ ),
+ 'teaser' => array(
+ 'label' => 'hidden',
+ 'module' => 'text',
+ 'settings' => array(
+ 'trim_length' => 600,
+ ),
+ 'type' => 'text_summary_or_trimmed',
+ 'weight' => 0,
+ ),
+ ),
+ 'entity_type' => 'node',
+ 'field_name' => 'body',
+ 'label' => 'Body',
+ 'required' => FALSE,
+ 'settings' => array(
+ 'display_summary' => TRUE,
+ 'text_processing' => 1,
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'module' => 'text',
+ 'settings' => array(
+ 'rows' => 20,
+ 'summary_rows' => 5,
+ ),
+ 'type' => 'text_textarea_with_summary',
+ 'weight' => -4,
+ ),
+ );
+
+ // Translatables
+ // Included for use with string extractors like potx.
+ t('Body');
+ t('Enter a comma-separated list of words to describe your content.');
+ t('Image');
+ t('Tags');
+ t('Upload an image to go with this article.');
+
+ return $field_instances;
+}
diff --git a/docroot/sites/all/modules/custom_config/custom_config.features.inc b/docroot/sites/all/modules/custom_config/custom_config.features.inc
new file mode 100644
index 0000000..a215c48
--- /dev/null
+++ b/docroot/sites/all/modules/custom_config/custom_config.features.inc
@@ -0,0 +1,31 @@
+ array(
+ 'name' => t('Article'),
+ 'base' => 'node_content',
+ 'description' => t('Use articles for time-sensitive content like news, press releases or blog posts.'),
+ 'has_title' => '1',
+ 'title_label' => t('Title'),
+ 'help' => '',
+ ),
+ 'page' => array(
+ 'name' => t('Basic page'),
+ 'base' => 'node_content',
+ 'description' => t('Use basic pages for your static content, such as an \'About us\' page.'),
+ 'has_title' => '1',
+ 'title_label' => t('Title'),
+ 'help' => '',
+ ),
+ );
+ drupal_alter('node_info', $items);
+ return $items;
+}
diff --git a/docroot/sites/all/modules/custom_config/custom_config.features.taxonomy.inc b/docroot/sites/all/modules/custom_config/custom_config.features.taxonomy.inc
new file mode 100644
index 0000000..c144d7f
--- /dev/null
+++ b/docroot/sites/all/modules/custom_config/custom_config.features.taxonomy.inc
@@ -0,0 +1,36 @@
+ array(
+ 'name' => 'Tags',
+ 'machine_name' => 'tags',
+ 'description' => 'Use tags to group articles on similar topics into categories.',
+ 'hierarchy' => 0,
+ 'module' => 'taxonomy',
+ 'weight' => 0,
+ 'rdf_mapping' => array(
+ 'rdftype' => array(
+ 0 => 'skos:ConceptScheme',
+ ),
+ 'name' => array(
+ 'predicates' => array(
+ 0 => 'dc:title',
+ ),
+ ),
+ 'description' => array(
+ 'predicates' => array(
+ 0 => 'rdfs:comment',
+ ),
+ ),
+ ),
+ ),
+ );
+}
diff --git a/docroot/sites/all/modules/custom_config/custom_config.info b/docroot/sites/all/modules/custom_config/custom_config.info
new file mode 100644
index 0000000..24c3260
--- /dev/null
+++ b/docroot/sites/all/modules/custom_config/custom_config.info
@@ -0,0 +1,18 @@
+name = custom config
+core = 7.x
+package = Features
+dependencies[] = features
+dependencies[] = image
+dependencies[] = taxonomy
+dependencies[] = text
+features[features_api][] = api:2
+features[field_base][] = body
+features[field_base][] = field_image
+features[field_base][] = field_tags
+features[field_instance][] = node-article-body
+features[field_instance][] = node-article-field_image
+features[field_instance][] = node-article-field_tags
+features[field_instance][] = node-page-body
+features[node][] = article
+features[node][] = page
+features[taxonomy][] = tags
diff --git a/docroot/sites/all/modules/custom_config/custom_config.module b/docroot/sites/all/modules/custom_config/custom_config.module
new file mode 100644
index 0000000..1e95ade
--- /dev/null
+++ b/docroot/sites/all/modules/custom_config/custom_config.module
@@ -0,0 +1,7 @@
+
Date: Wed, 18 Nov 2015 12:37:42 -0500
Subject: [PATCH 02/21] Added new content types and menu links
---
.../custom_config.features.field_base.inc | 154 +++++
.../custom_config.features.field_instance.inc | 560 ++++++++++++++++++
.../custom_config/custom_config.features.inc | 56 ++
.../custom_config.features.menu_links.inc | 57 ++
.../modules/custom_config/custom_config.info | 31 +
5 files changed, 858 insertions(+)
create mode 100644 docroot/sites/all/modules/custom_config/custom_config.features.menu_links.inc
diff --git a/docroot/sites/all/modules/custom_config/custom_config.features.field_base.inc b/docroot/sites/all/modules/custom_config/custom_config.features.field_base.inc
index bb20698..17051d6 100644
--- a/docroot/sites/all/modules/custom_config/custom_config.features.field_base.inc
+++ b/docroot/sites/all/modules/custom_config/custom_config.features.field_base.inc
@@ -31,6 +31,27 @@ function custom_config_field_default_field_bases() {
'type' => 'text_with_summary',
);
+ // Exported field_base: 'field_director'.
+ $field_bases['field_director'] = array(
+ 'active' => 1,
+ 'cardinality' => 1,
+ 'deleted' => 0,
+ 'entity_types' => array(),
+ 'field_name' => 'field_director',
+ 'indexes' => array(
+ 'format' => array(
+ 0 => 'format',
+ ),
+ ),
+ 'locked' => 0,
+ 'module' => 'text',
+ 'settings' => array(
+ 'max_length' => 255,
+ ),
+ 'translatable' => 0,
+ 'type' => 'text',
+ );
+
// Exported field_base: 'field_image'.
$field_bases['field_image'] = array(
'active' => 1,
@@ -53,6 +74,82 @@ function custom_config_field_default_field_bases() {
'type' => 'image',
);
+ // Exported field_base: 'field_list_movies'.
+ $field_bases['field_list_movies'] = array(
+ 'active' => 1,
+ 'cardinality' => 1,
+ 'deleted' => 0,
+ 'entity_types' => array(),
+ 'field_name' => 'field_list_movies',
+ 'indexes' => array(
+ 'format' => array(
+ 0 => 'format',
+ ),
+ ),
+ 'locked' => 0,
+ 'module' => 'text',
+ 'settings' => array(),
+ 'translatable' => 0,
+ 'type' => 'text_with_summary',
+ );
+
+ // Exported field_base: 'field_list_stars'.
+ $field_bases['field_list_stars'] = array(
+ 'active' => 1,
+ 'cardinality' => 1,
+ 'deleted' => 0,
+ 'entity_types' => array(),
+ 'field_name' => 'field_list_stars',
+ 'indexes' => array(
+ 'format' => array(
+ 0 => 'format',
+ ),
+ ),
+ 'locked' => 0,
+ 'module' => 'text',
+ 'settings' => array(),
+ 'translatable' => 0,
+ 'type' => 'text_with_summary',
+ );
+
+ // Exported field_base: 'field_movies'.
+ $field_bases['field_movies'] = array(
+ 'active' => 1,
+ 'cardinality' => 1,
+ 'deleted' => 0,
+ 'entity_types' => array(),
+ 'field_name' => 'field_movies',
+ 'indexes' => array(
+ 'format' => array(
+ 0 => 'format',
+ ),
+ ),
+ 'locked' => 0,
+ 'module' => 'text',
+ 'settings' => array(),
+ 'translatable' => 0,
+ 'type' => 'text_with_summary',
+ );
+
+ // Exported field_base: 'field_stars'.
+ $field_bases['field_stars'] = array(
+ 'active' => 1,
+ 'cardinality' => 1,
+ 'deleted' => 0,
+ 'entity_types' => array(),
+ 'field_name' => 'field_stars',
+ 'indexes' => array(
+ 'format' => array(
+ 0 => 'format',
+ ),
+ ),
+ 'locked' => 0,
+ 'module' => 'text',
+ 'settings' => array(),
+ 'translatable' => 0,
+ 'type' => 'text_with_summary',
+ );
+
// Exported field_base: 'field_tags'.
$field_bases['field_tags'] = array(
'active' => 1,
@@ -79,5 +176,62 @@ function custom_config_field_default_field_bases() {
'type' => 'taxonomy_term_reference',
);
+ // Exported field_base: 'field_works'.
+ $field_bases['field_works'] = array(
+ 'active' => 1,
+ 'cardinality' => 1,
+ 'deleted' => 0,
+ 'entity_types' => array(),
+ 'field_name' => 'field_works',
+ 'indexes' => array(
+ 'format' => array(
+ 0 => 'format',
+ ),
+ ),
+ 'locked' => 0,
+ 'module' => 'text',
+ 'settings' => array(),
+ 'translatable' => 0,
+ 'type' => 'text_with_summary',
+ );
+
+ // Exported field_base: 'field_writer_movies'.
+ $field_bases['field_writer_movies'] = array(
+ 'active' => 1,
+ 'cardinality' => 1,
+ 'deleted' => 0,
+ 'entity_types' => array(),
+ 'field_name' => 'field_writer_movies',
+ 'indexes' => array(
+ 'format' => array(
+ 0 => 'format',
+ ),
+ ),
+ 'locked' => 0,
+ 'module' => 'text',
+ 'settings' => array(),
+ 'translatable' => 0,
+ 'type' => 'text_with_summary',
+ );
+
+ // Exported field_base: 'field_writers'.
+ $field_bases['field_writers'] = array(
+ 'active' => 1,
+ 'cardinality' => 1,
+ 'deleted' => 0,
+ 'entity_types' => array(),
+ 'field_name' => 'field_writers',
+ 'indexes' => array(
+ 'format' => array(
+ 0 => 'format',
+ ),
+ ),
+ 'locked' => 0,
+ 'module' => 'text',
+ 'settings' => array(),
+ 'translatable' => 0,
+ 'type' => 'text_with_summary',
+ );
+
return $field_bases;
}
diff --git a/docroot/sites/all/modules/custom_config/custom_config.features.field_instance.inc b/docroot/sites/all/modules/custom_config/custom_config.features.field_instance.inc
index 9b92593..d1eec39 100644
--- a/docroot/sites/all/modules/custom_config/custom_config.features.field_instance.inc
+++ b/docroot/sites/all/modules/custom_config/custom_config.features.field_instance.inc
@@ -147,6 +147,345 @@ function custom_config_field_default_field_instances() {
),
);
+ // Exported field_instance: 'node-director-body'.
+ $field_instances['node-director-body'] = array(
+ 'bundle' => 'director',
+ 'default_value' => NULL,
+ 'deleted' => 0,
+ 'description' => '',
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'hidden',
+ 'module' => 'text',
+ 'settings' => array(),
+ 'type' => 'text_default',
+ 'weight' => 0,
+ ),
+ 'teaser' => array(
+ 'label' => 'hidden',
+ 'module' => 'text',
+ 'settings' => array(
+ 'trim_length' => 600,
+ ),
+ 'type' => 'text_summary_or_trimmed',
+ 'weight' => 0,
+ ),
+ ),
+ 'entity_type' => 'node',
+ 'field_name' => 'body',
+ 'label' => 'Body',
+ 'required' => FALSE,
+ 'settings' => array(
+ 'display_summary' => TRUE,
+ 'text_processing' => 1,
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'module' => 'text',
+ 'settings' => array(
+ 'rows' => 20,
+ 'summary_rows' => 5,
+ ),
+ 'type' => 'text_textarea_with_summary',
+ 'weight' => -4,
+ ),
+ );
+
+ // Exported field_instance: 'node-director-field_movies'.
+ $field_instances['node-director-field_movies'] = array(
+ 'bundle' => 'director',
+ 'default_value' => NULL,
+ 'deleted' => 0,
+ 'description' => '',
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'above',
+ 'module' => 'text',
+ 'settings' => array(),
+ 'type' => 'text_default',
+ 'weight' => 1,
+ ),
+ 'teaser' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ ),
+ 'entity_type' => 'node',
+ 'field_name' => 'field_movies',
+ 'label' => 'Movies',
+ 'required' => 0,
+ 'settings' => array(
+ 'display_summary' => 0,
+ 'text_processing' => 1,
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'active' => 1,
+ 'module' => 'text',
+ 'settings' => array(
+ 'rows' => 20,
+ 'summary_rows' => 5,
+ ),
+ 'type' => 'text_textarea_with_summary',
+ 'weight' => -3,
+ ),
+ );
+
+ // Exported field_instance: 'node-home_page-body'.
+ $field_instances['node-home_page-body'] = array(
+ 'bundle' => 'home_page',
+ 'default_value' => NULL,
+ 'deleted' => 0,
+ 'description' => '',
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'hidden',
+ 'module' => 'text',
+ 'settings' => array(),
+ 'type' => 'text_default',
+ 'weight' => 0,
+ ),
+ 'teaser' => array(
+ 'label' => 'hidden',
+ 'module' => 'text',
+ 'settings' => array(
+ 'trim_length' => 600,
+ ),
+ 'type' => 'text_summary_or_trimmed',
+ 'weight' => 0,
+ ),
+ ),
+ 'entity_type' => 'node',
+ 'field_name' => 'body',
+ 'label' => 'Body',
+ 'required' => FALSE,
+ 'settings' => array(
+ 'display_summary' => TRUE,
+ 'text_processing' => 1,
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'module' => 'text',
+ 'settings' => array(
+ 'rows' => 20,
+ 'summary_rows' => 5,
+ ),
+ 'type' => 'text_textarea_with_summary',
+ 'weight' => -4,
+ ),
+ );
+
+ // Exported field_instance: 'node-movies-body'.
+ $field_instances['node-movies-body'] = array(
+ 'bundle' => 'movies',
+ 'default_value' => NULL,
+ 'deleted' => 0,
+ 'description' => '',
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'hidden',
+ 'module' => 'text',
+ 'settings' => array(),
+ 'type' => 'text_default',
+ 'weight' => 0,
+ ),
+ 'teaser' => array(
+ 'label' => 'hidden',
+ 'module' => 'text',
+ 'settings' => array(
+ 'trim_length' => 600,
+ ),
+ 'type' => 'text_summary_or_trimmed',
+ 'weight' => 0,
+ ),
+ ),
+ 'entity_type' => 'node',
+ 'field_name' => 'body',
+ 'label' => 'Body',
+ 'required' => FALSE,
+ 'settings' => array(
+ 'display_summary' => TRUE,
+ 'text_processing' => 1,
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'module' => 'text',
+ 'settings' => array(
+ 'rows' => 20,
+ 'summary_rows' => 5,
+ ),
+ 'type' => 'text_textarea_with_summary',
+ 'weight' => -4,
+ ),
+ );
+
+ // Exported field_instance: 'node-movies-field_director'.
+ $field_instances['node-movies-field_director'] = array(
+ 'bundle' => 'movies',
+ 'default_value' => NULL,
+ 'deleted' => 0,
+ 'description' => '',
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'above',
+ 'module' => 'text',
+ 'settings' => array(),
+ 'type' => 'text_default',
+ 'weight' => 1,
+ ),
+ 'teaser' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ ),
+ 'entity_type' => 'node',
+ 'field_name' => 'field_director',
+ 'label' => 'Director',
+ 'required' => 0,
+ 'settings' => array(
+ 'text_processing' => 1,
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'active' => 1,
+ 'module' => 'text',
+ 'settings' => array(
+ 'size' => 60,
+ ),
+ 'type' => 'text_textfield',
+ 'weight' => -3,
+ ),
+ );
+
+ // Exported field_instance: 'node-movies-field_stars'.
+ $field_instances['node-movies-field_stars'] = array(
+ 'bundle' => 'movies',
+ 'default_value' => NULL,
+ 'deleted' => 0,
+ 'description' => '',
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'above',
+ 'module' => 'text',
+ 'settings' => array(),
+ 'type' => 'text_default',
+ 'weight' => 3,
+ ),
+ 'teaser' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ ),
+ 'entity_type' => 'node',
+ 'field_name' => 'field_stars',
+ 'label' => 'Stars',
+ 'required' => FALSE,
+ 'settings' => array(
+ 'display_summary' => 0,
+ 'text_processing' => 1,
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'module' => 'text',
+ 'settings' => array(
+ 'rows' => 20,
+ 'summary_rows' => 5,
+ ),
+ 'type' => 'text_textarea_with_summary',
+ 'weight' => -1,
+ ),
+ );
+
+ // Exported field_instance: 'node-movies-field_writers'.
+ $field_instances['node-movies-field_writers'] = array(
+ 'bundle' => 'movies',
+ 'default_value' => NULL,
+ 'deleted' => 0,
+ 'description' => '',
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'above',
+ 'module' => 'text',
+ 'settings' => array(),
+ 'type' => 'text_default',
+ 'weight' => 2,
+ ),
+ 'teaser' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ ),
+ 'entity_type' => 'node',
+ 'field_name' => 'field_writers',
+ 'label' => 'Writers',
+ 'required' => 0,
+ 'settings' => array(
+ 'display_summary' => 0,
+ 'text_processing' => 1,
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'active' => 1,
+ 'module' => 'text',
+ 'settings' => array(
+ 'rows' => 20,
+ 'summary_rows' => 5,
+ ),
+ 'type' => 'text_textarea_with_summary',
+ 'weight' => -2,
+ ),
+ );
+
+ // Exported field_instance: 'node-movies_list-field_list_movies'.
+ $field_instances['node-movies_list-field_list_movies'] = array(
+ 'bundle' => 'movies_list',
+ 'default_value' => NULL,
+ 'deleted' => 0,
+ 'description' => '',
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'above',
+ 'module' => 'text',
+ 'settings' => array(),
+ 'type' => 'text_default',
+ 'weight' => 0,
+ ),
+ 'teaser' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ ),
+ 'entity_type' => 'node',
+ 'field_name' => 'field_list_movies',
+ 'label' => 'List Movies',
+ 'required' => 0,
+ 'settings' => array(
+ 'display_summary' => 0,
+ 'text_processing' => 1,
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'active' => 1,
+ 'module' => 'text',
+ 'settings' => array(
+ 'rows' => 20,
+ 'summary_rows' => 5,
+ ),
+ 'type' => 'text_textarea_with_summary',
+ 'weight' => -4,
+ ),
+ );
+
// Exported field_instance: 'node-page-body'.
$field_instances['node-page-body'] = array(
'bundle' => 'page',
@@ -191,13 +530,234 @@ function custom_config_field_default_field_instances() {
),
);
+ // Exported field_instance: 'node-star-body'.
+ $field_instances['node-star-body'] = array(
+ 'bundle' => 'star',
+ 'default_value' => NULL,
+ 'deleted' => 0,
+ 'description' => '',
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'hidden',
+ 'module' => 'text',
+ 'settings' => array(),
+ 'type' => 'text_default',
+ 'weight' => 0,
+ ),
+ 'teaser' => array(
+ 'label' => 'hidden',
+ 'module' => 'text',
+ 'settings' => array(
+ 'trim_length' => 600,
+ ),
+ 'type' => 'text_summary_or_trimmed',
+ 'weight' => 0,
+ ),
+ ),
+ 'entity_type' => 'node',
+ 'field_name' => 'body',
+ 'label' => 'Body',
+ 'required' => FALSE,
+ 'settings' => array(
+ 'display_summary' => TRUE,
+ 'text_processing' => 1,
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'module' => 'text',
+ 'settings' => array(
+ 'rows' => 20,
+ 'summary_rows' => 5,
+ ),
+ 'type' => 'text_textarea_with_summary',
+ 'weight' => -4,
+ ),
+ );
+
+ // Exported field_instance: 'node-star-field_works'.
+ $field_instances['node-star-field_works'] = array(
+ 'bundle' => 'star',
+ 'default_value' => NULL,
+ 'deleted' => 0,
+ 'description' => 'Movies this Star is known for ',
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'above',
+ 'module' => 'text',
+ 'settings' => array(),
+ 'type' => 'text_default',
+ 'weight' => 1,
+ ),
+ 'teaser' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ ),
+ 'entity_type' => 'node',
+ 'field_name' => 'field_works',
+ 'label' => 'Movies',
+ 'required' => 0,
+ 'settings' => array(
+ 'display_summary' => 0,
+ 'text_processing' => 1,
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'active' => 1,
+ 'module' => 'text',
+ 'settings' => array(
+ 'rows' => 20,
+ 'summary_rows' => 5,
+ ),
+ 'type' => 'text_textarea_with_summary',
+ 'weight' => -3,
+ ),
+ );
+
+ // Exported field_instance: 'node-stars_list-field_list_stars'.
+ $field_instances['node-stars_list-field_list_stars'] = array(
+ 'bundle' => 'stars_list',
+ 'default_value' => NULL,
+ 'deleted' => 0,
+ 'description' => '',
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'above',
+ 'module' => 'text',
+ 'settings' => array(),
+ 'type' => 'text_default',
+ 'weight' => 0,
+ ),
+ 'teaser' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ ),
+ 'entity_type' => 'node',
+ 'field_name' => 'field_list_stars',
+ 'label' => 'List Stars',
+ 'required' => 0,
+ 'settings' => array(
+ 'display_summary' => 0,
+ 'text_processing' => 1,
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'active' => 1,
+ 'module' => 'text',
+ 'settings' => array(
+ 'rows' => 20,
+ 'summary_rows' => 5,
+ ),
+ 'type' => 'text_textarea_with_summary',
+ 'weight' => -4,
+ ),
+ );
+
+ // Exported field_instance: 'node-writer-body'.
+ $field_instances['node-writer-body'] = array(
+ 'bundle' => 'writer',
+ 'default_value' => NULL,
+ 'deleted' => 0,
+ 'description' => '',
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'hidden',
+ 'module' => 'text',
+ 'settings' => array(),
+ 'type' => 'text_default',
+ 'weight' => 0,
+ ),
+ 'teaser' => array(
+ 'label' => 'hidden',
+ 'module' => 'text',
+ 'settings' => array(
+ 'trim_length' => 600,
+ ),
+ 'type' => 'text_summary_or_trimmed',
+ 'weight' => 0,
+ ),
+ ),
+ 'entity_type' => 'node',
+ 'field_name' => 'body',
+ 'label' => 'Body',
+ 'required' => FALSE,
+ 'settings' => array(
+ 'display_summary' => TRUE,
+ 'text_processing' => 1,
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'module' => 'text',
+ 'settings' => array(
+ 'rows' => 20,
+ 'summary_rows' => 5,
+ ),
+ 'type' => 'text_textarea_with_summary',
+ 'weight' => -4,
+ ),
+ );
+
+ // Exported field_instance: 'node-writer-field_writer_movies'.
+ $field_instances['node-writer-field_writer_movies'] = array(
+ 'bundle' => 'writer',
+ 'default_value' => NULL,
+ 'deleted' => 0,
+ 'description' => '',
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'above',
+ 'module' => 'text',
+ 'settings' => array(),
+ 'type' => 'text_default',
+ 'weight' => 1,
+ ),
+ 'teaser' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ ),
+ 'entity_type' => 'node',
+ 'field_name' => 'field_writer_movies',
+ 'label' => 'Movies',
+ 'required' => 0,
+ 'settings' => array(
+ 'display_summary' => 0,
+ 'text_processing' => 1,
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'active' => 1,
+ 'module' => 'text',
+ 'settings' => array(
+ 'rows' => 20,
+ 'summary_rows' => 5,
+ ),
+ 'type' => 'text_textarea_with_summary',
+ 'weight' => -3,
+ ),
+ );
+
// Translatables
// Included for use with string extractors like potx.
t('Body');
+ t('Director');
t('Enter a comma-separated list of words to describe your content.');
t('Image');
+ t('List Movies');
+ t('List Stars');
+ t('Movies');
+ t('Movies this Star is known for ');
+ t('Stars');
t('Tags');
t('Upload an image to go with this article.');
+ t('Writers');
return $field_instances;
}
diff --git a/docroot/sites/all/modules/custom_config/custom_config.features.inc b/docroot/sites/all/modules/custom_config/custom_config.features.inc
index a215c48..06fa313 100644
--- a/docroot/sites/all/modules/custom_config/custom_config.features.inc
+++ b/docroot/sites/all/modules/custom_config/custom_config.features.inc
@@ -17,6 +17,38 @@ function custom_config_node_info() {
'title_label' => t('Title'),
'help' => '',
),
+ 'director' => array(
+ 'name' => t('Directors'),
+ 'base' => 'node_content',
+ 'description' => '',
+ 'has_title' => '1',
+ 'title_label' => t('Name'),
+ 'help' => '',
+ ),
+ 'home_page' => array(
+ 'name' => t('Home Page'),
+ 'base' => 'node_content',
+ 'description' => '',
+ 'has_title' => '1',
+ 'title_label' => t('Title'),
+ 'help' => '',
+ ),
+ 'movies' => array(
+ 'name' => t('Movies'),
+ 'base' => 'node_content',
+ 'description' => '',
+ 'has_title' => '1',
+ 'title_label' => t('Title'),
+ 'help' => '',
+ ),
+ 'movies_list' => array(
+ 'name' => t('Movies List'),
+ 'base' => 'node_content',
+ 'description' => '',
+ 'has_title' => '1',
+ 'title_label' => t('Title'),
+ 'help' => '',
+ ),
'page' => array(
'name' => t('Basic page'),
'base' => 'node_content',
@@ -25,6 +57,30 @@ function custom_config_node_info() {
'title_label' => t('Title'),
'help' => '',
),
+ 'star' => array(
+ 'name' => t('Stars'),
+ 'base' => 'node_content',
+ 'description' => t('A short description of an actor or actress and their body of work.'),
+ 'has_title' => '1',
+ 'title_label' => t('Name'),
+ 'help' => '',
+ ),
+ 'stars_list' => array(
+ 'name' => t('Stars List'),
+ 'base' => 'node_content',
+ 'description' => '',
+ 'has_title' => '1',
+ 'title_label' => t('Title'),
+ 'help' => '',
+ ),
+ 'writer' => array(
+ 'name' => t('Writers'),
+ 'base' => 'node_content',
+ 'description' => '',
+ 'has_title' => '1',
+ 'title_label' => t('Name'),
+ 'help' => '',
+ ),
);
drupal_alter('node_info', $items);
return $items;
diff --git a/docroot/sites/all/modules/custom_config/custom_config.features.menu_links.inc b/docroot/sites/all/modules/custom_config/custom_config.features.menu_links.inc
new file mode 100644
index 0000000..a4976af
--- /dev/null
+++ b/docroot/sites/all/modules/custom_config/custom_config.features.menu_links.inc
@@ -0,0 +1,57 @@
+ 'navigation',
+ 'link_path' => 'node/12',
+ 'router_path' => 'node/%',
+ 'link_title' => 'All Movies',
+ 'options' => array(
+ 'attributes' => array(
+ 'title' => '',
+ ),
+ 'identifier' => 'navigation_all-movies:node/12',
+ ),
+ 'module' => 'menu',
+ 'hidden' => 0,
+ 'external' => 0,
+ 'has_children' => 0,
+ 'expanded' => 0,
+ 'weight' => 0,
+ 'customized' => 1,
+ );
+ // Exported menu link: navigation_all-stars:node/13.
+ $menu_links['navigation_all-stars:node/13'] = array(
+ 'menu_name' => 'navigation',
+ 'link_path' => 'node/13',
+ 'router_path' => 'node/%',
+ 'link_title' => 'All Stars',
+ 'options' => array(
+ 'identifier' => 'navigation_all-stars:node/13',
+ ),
+ 'module' => 'menu',
+ 'hidden' => 0,
+ 'external' => 0,
+ 'has_children' => 0,
+ 'expanded' => 0,
+ 'weight' => 0,
+ 'customized' => 0,
+ );
+
+ // Translatables
+ // Included for use with string extractors like potx.
+ t('All Movies');
+ t('All Stars');
+
+ return $menu_links;
+}
diff --git a/docroot/sites/all/modules/custom_config/custom_config.info b/docroot/sites/all/modules/custom_config/custom_config.info
index 24c3260..4ba1a55 100644
--- a/docroot/sites/all/modules/custom_config/custom_config.info
+++ b/docroot/sites/all/modules/custom_config/custom_config.info
@@ -3,16 +3,47 @@ core = 7.x
package = Features
dependencies[] = features
dependencies[] = image
+dependencies[] = menu
dependencies[] = taxonomy
dependencies[] = text
features[features_api][] = api:2
features[field_base][] = body
+features[field_base][] = field_director
features[field_base][] = field_image
+features[field_base][] = field_list_movies
+features[field_base][] = field_list_stars
+features[field_base][] = field_movies
+features[field_base][] = field_stars
features[field_base][] = field_tags
+features[field_base][] = field_works
+features[field_base][] = field_writer_movies
+features[field_base][] = field_writers
features[field_instance][] = node-article-body
features[field_instance][] = node-article-field_image
features[field_instance][] = node-article-field_tags
+features[field_instance][] = node-director-body
+features[field_instance][] = node-director-field_movies
+features[field_instance][] = node-home_page-body
+features[field_instance][] = node-movies-body
+features[field_instance][] = node-movies-field_director
+features[field_instance][] = node-movies-field_stars
+features[field_instance][] = node-movies-field_writers
+features[field_instance][] = node-movies_list-field_list_movies
features[field_instance][] = node-page-body
+features[field_instance][] = node-star-body
+features[field_instance][] = node-star-field_works
+features[field_instance][] = node-stars_list-field_list_stars
+features[field_instance][] = node-writer-body
+features[field_instance][] = node-writer-field_writer_movies
+features[menu_links][] = navigation_all-movies:node/12
+features[menu_links][] = navigation_all-stars:node/13
features[node][] = article
+features[node][] = director
+features[node][] = home_page
+features[node][] = movies
+features[node][] = movies_list
features[node][] = page
+features[node][] = star
+features[node][] = stars_list
+features[node][] = writer
features[taxonomy][] = tags
From 6b0ccb57fef65832f624a4b8e6830b6ceccdbff8 Mon Sep 17 00:00:00 2001
From: zbenaissa
Date: Wed, 18 Nov 2015 15:09:26 -0500
Subject: [PATCH 03/21] Added new views and created entity references
---
.../custom_config.features.field_base.inc | 121 ++++++++---
.../custom_config.features.field_instance.inc | 127 +++++------
.../custom_config/custom_config.features.inc | 7 +
.../modules/custom_config/custom_config.info | 12 +-
.../custom_config.views_default.inc | 201 ++++++++++++++++++
5 files changed, 374 insertions(+), 94 deletions(-)
create mode 100644 docroot/sites/all/modules/custom_config/custom_config.views_default.inc
diff --git a/docroot/sites/all/modules/custom_config/custom_config.features.field_base.inc b/docroot/sites/all/modules/custom_config/custom_config.features.field_base.inc
index 17051d6..8eba9f7 100644
--- a/docroot/sites/all/modules/custom_config/custom_config.features.field_base.inc
+++ b/docroot/sites/all/modules/custom_config/custom_config.features.field_base.inc
@@ -31,25 +31,84 @@ function custom_config_field_default_field_bases() {
'type' => 'text_with_summary',
);
+ // Exported field_base: 'field_actors'.
+ $field_bases['field_actors'] = array(
+ 'active' => 1,
+ 'cardinality' => -1,
+ 'deleted' => 0,
+ 'entity_types' => array(),
+ 'field_name' => 'field_actors',
+ 'indexes' => array(
+ 'target_id' => array(
+ 0 => 'target_id',
+ ),
+ ),
+ 'locked' => 0,
+ 'module' => 'entityreference',
+ 'settings' => array(
+ 'handler' => 'base',
+ 'handler_settings' => array(
+ 'behaviors' => array(
+ 'test_field_behavior' => array(
+ 'status' => 0,
+ ),
+ 'views-select-list' => array(
+ 'status' => 0,
+ ),
+ ),
+ 'sort' => array(
+ 'direction' => 'ASC',
+ 'property' => 'title',
+ 'type' => 'property',
+ ),
+ 'target_bundles' => array(
+ 'star' => 'star',
+ ),
+ ),
+ 'target_type' => 'node',
+ ),
+ 'translatable' => 0,
+ 'type' => 'entityreference',
+ );
+
// Exported field_base: 'field_director'.
$field_bases['field_director'] = array(
'active' => 1,
- 'cardinality' => 1,
+ 'cardinality' => -1,
'deleted' => 0,
'entity_types' => array(),
'field_name' => 'field_director',
'indexes' => array(
- 'format' => array(
- 0 => 'format',
+ 'target_id' => array(
+ 0 => 'target_id',
),
),
'locked' => 0,
- 'module' => 'text',
+ 'module' => 'entityreference',
'settings' => array(
- 'max_length' => 255,
+ 'handler' => 'base',
+ 'handler_settings' => array(
+ 'behaviors' => array(
+ 'test_field_behavior' => array(
+ 'status' => 0,
+ ),
+ 'views-select-list' => array(
+ 'status' => 0,
+ ),
+ ),
+ 'sort' => array(
+ 'direction' => 'ASC',
+ 'property' => 'title',
+ 'type' => 'property',
+ ),
+ 'target_bundles' => array(
+ 'director' => 'director',
+ ),
+ ),
+ 'target_type' => 'node',
),
'translatable' => 0,
- 'type' => 'text',
+ 'type' => 'entityreference',
);
// Exported field_base: 'field_image'.
@@ -131,25 +190,6 @@ function custom_config_field_default_field_bases() {
'type' => 'text_with_summary',
);
- // Exported field_base: 'field_stars'.
- $field_bases['field_stars'] = array(
- 'active' => 1,
- 'cardinality' => 1,
- 'deleted' => 0,
- 'entity_types' => array(),
- 'field_name' => 'field_stars',
- 'indexes' => array(
- 'format' => array(
- 0 => 'format',
- ),
- ),
- 'locked' => 0,
- 'module' => 'text',
- 'settings' => array(),
- 'translatable' => 0,
- 'type' => 'text_with_summary',
- );
-
// Exported field_base: 'field_tags'.
$field_bases['field_tags'] = array(
'active' => 1,
@@ -222,15 +262,36 @@ function custom_config_field_default_field_bases() {
'entity_types' => array(),
'field_name' => 'field_writers',
'indexes' => array(
- 'format' => array(
- 0 => 'format',
+ 'target_id' => array(
+ 0 => 'target_id',
),
),
'locked' => 0,
- 'module' => 'text',
- 'settings' => array(),
+ 'module' => 'entityreference',
+ 'settings' => array(
+ 'handler' => 'base',
+ 'handler_settings' => array(
+ 'behaviors' => array(
+ 'test_field_behavior' => array(
+ 'status' => 0,
+ ),
+ 'views-select-list' => array(
+ 'status' => 0,
+ ),
+ ),
+ 'sort' => array(
+ 'direction' => 'ASC',
+ 'property' => 'title',
+ 'type' => 'property',
+ ),
+ 'target_bundles' => array(
+ 'writer' => 'writer',
+ ),
+ ),
+ 'target_type' => 'node',
+ ),
'translatable' => 0,
- 'type' => 'text_with_summary',
+ 'type' => 'entityreference',
);
return $field_bases;
diff --git a/docroot/sites/all/modules/custom_config/custom_config.features.field_instance.inc b/docroot/sites/all/modules/custom_config/custom_config.features.field_instance.inc
index d1eec39..64decd5 100644
--- a/docroot/sites/all/modules/custom_config/custom_config.features.field_instance.inc
+++ b/docroot/sites/all/modules/custom_config/custom_config.features.field_instance.inc
@@ -163,11 +163,8 @@ function custom_config_field_default_field_instances() {
),
'teaser' => array(
'label' => 'hidden',
- 'module' => 'text',
- 'settings' => array(
- 'trim_length' => 600,
- ),
- 'type' => 'text_summary_or_trimmed',
+ 'settings' => array(),
+ 'type' => 'hidden',
'weight' => 0,
),
),
@@ -293,11 +290,8 @@ function custom_config_field_default_field_instances() {
),
'teaser' => array(
'label' => 'hidden',
- 'module' => 'text',
- 'settings' => array(
- 'trim_length' => 600,
- ),
- 'type' => 'text_summary_or_trimmed',
+ 'settings' => array(),
+ 'type' => 'hidden',
'weight' => 0,
),
),
@@ -321,8 +315,8 @@ function custom_config_field_default_field_instances() {
),
);
- // Exported field_instance: 'node-movies-field_director'.
- $field_instances['node-movies-field_director'] = array(
+ // Exported field_instance: 'node-movies-field_actors'.
+ $field_instances['node-movies-field_actors'] = array(
'bundle' => 'movies',
'default_value' => NULL,
'deleted' => 0,
@@ -330,10 +324,13 @@ function custom_config_field_default_field_instances() {
'display' => array(
'default' => array(
'label' => 'above',
- 'module' => 'text',
- 'settings' => array(),
- 'type' => 'text_default',
- 'weight' => 1,
+ 'module' => 'entityreference',
+ 'settings' => array(
+ 'links' => 1,
+ 'view_mode' => 'teaser',
+ ),
+ 'type' => 'entityreference_entity_view',
+ 'weight' => 4,
),
'teaser' => array(
'label' => 'above',
@@ -343,26 +340,28 @@ function custom_config_field_default_field_instances() {
),
),
'entity_type' => 'node',
- 'field_name' => 'field_director',
- 'label' => 'Director',
+ 'field_name' => 'field_actors',
+ 'label' => 'Actors',
'required' => 0,
'settings' => array(
- 'text_processing' => 1,
+ 'behaviors' => array(
+ 'test_instance_behavior' => array(
+ 'status' => 0,
+ ),
+ ),
'user_register_form' => FALSE,
),
'widget' => array(
'active' => 1,
- 'module' => 'text',
- 'settings' => array(
- 'size' => 60,
- ),
- 'type' => 'text_textfield',
- 'weight' => -3,
+ 'module' => 'options',
+ 'settings' => array(),
+ 'type' => 'options_buttons',
+ 'weight' => 0,
),
);
- // Exported field_instance: 'node-movies-field_stars'.
- $field_instances['node-movies-field_stars'] = array(
+ // Exported field_instance: 'node-movies-field_director'.
+ $field_instances['node-movies-field_director'] = array(
'bundle' => 'movies',
'default_value' => NULL,
'deleted' => 0,
@@ -370,10 +369,13 @@ function custom_config_field_default_field_instances() {
'display' => array(
'default' => array(
'label' => 'above',
- 'module' => 'text',
- 'settings' => array(),
- 'type' => 'text_default',
- 'weight' => 3,
+ 'module' => 'entityreference',
+ 'settings' => array(
+ 'links' => 1,
+ 'view_mode' => 'teaser',
+ ),
+ 'type' => 'entityreference_entity_view',
+ 'weight' => 5,
),
'teaser' => array(
'label' => 'above',
@@ -383,22 +385,23 @@ function custom_config_field_default_field_instances() {
),
),
'entity_type' => 'node',
- 'field_name' => 'field_stars',
- 'label' => 'Stars',
- 'required' => FALSE,
+ 'field_name' => 'field_director',
+ 'label' => 'Director',
+ 'required' => 0,
'settings' => array(
- 'display_summary' => 0,
- 'text_processing' => 1,
+ 'behaviors' => array(
+ 'test_instance_behavior' => array(
+ 'status' => 0,
+ ),
+ ),
'user_register_form' => FALSE,
),
'widget' => array(
- 'module' => 'text',
- 'settings' => array(
- 'rows' => 20,
- 'summary_rows' => 5,
- ),
- 'type' => 'text_textarea_with_summary',
- 'weight' => -1,
+ 'active' => 1,
+ 'module' => 'options',
+ 'settings' => array(),
+ 'type' => 'options_buttons',
+ 'weight' => 1,
),
);
@@ -411,10 +414,13 @@ function custom_config_field_default_field_instances() {
'display' => array(
'default' => array(
'label' => 'above',
- 'module' => 'text',
- 'settings' => array(),
- 'type' => 'text_default',
- 'weight' => 2,
+ 'module' => 'entityreference',
+ 'settings' => array(
+ 'links' => 1,
+ 'view_mode' => 'default',
+ ),
+ 'type' => 'entityreference_entity_view',
+ 'weight' => 6,
),
'teaser' => array(
'label' => 'above',
@@ -428,19 +434,19 @@ function custom_config_field_default_field_instances() {
'label' => 'Writers',
'required' => 0,
'settings' => array(
- 'display_summary' => 0,
- 'text_processing' => 1,
+ 'behaviors' => array(
+ 'test_instance_behavior' => array(
+ 'status' => 0,
+ ),
+ ),
'user_register_form' => FALSE,
),
'widget' => array(
'active' => 1,
- 'module' => 'text',
- 'settings' => array(
- 'rows' => 20,
- 'summary_rows' => 5,
- ),
- 'type' => 'text_textarea_with_summary',
- 'weight' => -2,
+ 'module' => 'options',
+ 'settings' => array(),
+ 'type' => 'options_buttons',
+ 'weight' => 2,
),
);
@@ -546,11 +552,8 @@ function custom_config_field_default_field_instances() {
),
'teaser' => array(
'label' => 'hidden',
- 'module' => 'text',
- 'settings' => array(
- 'trim_length' => 600,
- ),
- 'type' => 'text_summary_or_trimmed',
+ 'settings' => array(),
+ 'type' => 'hidden',
'weight' => 0,
),
),
@@ -746,6 +749,7 @@ function custom_config_field_default_field_instances() {
// Translatables
// Included for use with string extractors like potx.
+ t('Actors');
t('Body');
t('Director');
t('Enter a comma-separated list of words to describe your content.');
@@ -754,7 +758,6 @@ function custom_config_field_default_field_instances() {
t('List Stars');
t('Movies');
t('Movies this Star is known for ');
- t('Stars');
t('Tags');
t('Upload an image to go with this article.');
t('Writers');
diff --git a/docroot/sites/all/modules/custom_config/custom_config.features.inc b/docroot/sites/all/modules/custom_config/custom_config.features.inc
index 06fa313..9eabe94 100644
--- a/docroot/sites/all/modules/custom_config/custom_config.features.inc
+++ b/docroot/sites/all/modules/custom_config/custom_config.features.inc
@@ -4,6 +4,13 @@
* custom_config.features.inc
*/
+/**
+ * Implements hook_views_api().
+ */
+function custom_config_views_api($module = NULL, $api = NULL) {
+ return array("api" => "3.0");
+}
+
/**
* Implements hook_node_info().
*/
diff --git a/docroot/sites/all/modules/custom_config/custom_config.info b/docroot/sites/all/modules/custom_config/custom_config.info
index 4ba1a55..f2e5e6b 100644
--- a/docroot/sites/all/modules/custom_config/custom_config.info
+++ b/docroot/sites/all/modules/custom_config/custom_config.info
@@ -1,19 +1,22 @@
name = custom config
core = 7.x
package = Features
+dependencies[] = entityreference
dependencies[] = features
dependencies[] = image
dependencies[] = menu
dependencies[] = taxonomy
dependencies[] = text
+dependencies[] = views
+features[ctools][] = views:views_default:3.0
features[features_api][] = api:2
features[field_base][] = body
+features[field_base][] = field_actors
features[field_base][] = field_director
features[field_base][] = field_image
features[field_base][] = field_list_movies
features[field_base][] = field_list_stars
features[field_base][] = field_movies
-features[field_base][] = field_stars
features[field_base][] = field_tags
features[field_base][] = field_works
features[field_base][] = field_writer_movies
@@ -25,8 +28,8 @@ features[field_instance][] = node-director-body
features[field_instance][] = node-director-field_movies
features[field_instance][] = node-home_page-body
features[field_instance][] = node-movies-body
+features[field_instance][] = node-movies-field_actors
features[field_instance][] = node-movies-field_director
-features[field_instance][] = node-movies-field_stars
features[field_instance][] = node-movies-field_writers
features[field_instance][] = node-movies_list-field_list_movies
features[field_instance][] = node-page-body
@@ -47,3 +50,8 @@ features[node][] = star
features[node][] = stars_list
features[node][] = writer
features[taxonomy][] = tags
+features[views_view][] = directors
+features[views_view][] = movies
+features[views_view][] = stars
+features_exclude[dependencies][ctools] = ctools
+features_exclude[dependencies][options] = options
diff --git a/docroot/sites/all/modules/custom_config/custom_config.views_default.inc b/docroot/sites/all/modules/custom_config/custom_config.views_default.inc
new file mode 100644
index 0000000..4066009
--- /dev/null
+++ b/docroot/sites/all/modules/custom_config/custom_config.views_default.inc
@@ -0,0 +1,201 @@
+name = 'directors';
+ $view->description = '';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'Directors';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['title'] = 'Directors';
+ $handler->display->display_options['use_more_always'] = FALSE;
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = '10';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'node';
+ $handler->display->display_options['row_options']['links'] = FALSE;
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['label'] = '';
+ $handler->display->display_options['fields']['title']['alter']['word_boundary'] = FALSE;
+ $handler->display->display_options['fields']['title']['alter']['ellipsis'] = FALSE;
+ /* Sort criterion: Content: Title */
+ $handler->display->display_options['sorts']['title']['id'] = 'title';
+ $handler->display->display_options['sorts']['title']['table'] = 'node';
+ $handler->display->display_options['sorts']['title']['field'] = 'title';
+ $handler->display->display_options['sorts']['title']['order'] = 'DESC';
+ /* Filter criterion: Content: Published */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'node';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = 1;
+ $handler->display->display_options['filters']['status']['group'] = 1;
+ $handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;
+ /* Filter criterion: Content: Type */
+ $handler->display->display_options['filters']['type']['id'] = 'type';
+ $handler->display->display_options['filters']['type']['table'] = 'node';
+ $handler->display->display_options['filters']['type']['field'] = 'type';
+ $handler->display->display_options['filters']['type']['value'] = array(
+ 'director' => 'director',
+ );
+
+ /* Display: Page */
+ $handler = $view->new_display('page', 'Page', 'page');
+ $handler->display->display_options['path'] = 'directors';
+ $handler->display->display_options['menu']['type'] = 'normal';
+ $handler->display->display_options['menu']['title'] = 'Directors';
+ $handler->display->display_options['menu']['weight'] = '0';
+ $handler->display->display_options['menu']['context'] = 0;
+ $handler->display->display_options['menu']['context_only_inline'] = 0;
+ $export['directors'] = $view;
+
+ $view = new view();
+ $view->name = 'movies';
+ $view->description = '';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'Movies';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['title'] = 'Movies';
+ $handler->display->display_options['use_more_always'] = FALSE;
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = '10';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'node';
+ $handler->display->display_options['row_options']['links'] = FALSE;
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['label'] = '';
+ $handler->display->display_options['fields']['title']['alter']['word_boundary'] = FALSE;
+ $handler->display->display_options['fields']['title']['alter']['ellipsis'] = FALSE;
+ /* Sort criterion: Content: Title */
+ $handler->display->display_options['sorts']['title']['id'] = 'title';
+ $handler->display->display_options['sorts']['title']['table'] = 'node';
+ $handler->display->display_options['sorts']['title']['field'] = 'title';
+ $handler->display->display_options['sorts']['title']['order'] = 'DESC';
+ /* Filter criterion: Content: Published */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'node';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = 1;
+ $handler->display->display_options['filters']['status']['group'] = 1;
+ $handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;
+ /* Filter criterion: Content: Type */
+ $handler->display->display_options['filters']['type']['id'] = 'type';
+ $handler->display->display_options['filters']['type']['table'] = 'node';
+ $handler->display->display_options['filters']['type']['field'] = 'type';
+ $handler->display->display_options['filters']['type']['value'] = array(
+ 'movies' => 'movies',
+ );
+
+ /* Display: Page */
+ $handler = $view->new_display('page', 'Page', 'page');
+ $handler->display->display_options['path'] = 'movies';
+ $handler->display->display_options['menu']['type'] = 'normal';
+ $handler->display->display_options['menu']['title'] = 'Movies';
+ $handler->display->display_options['menu']['weight'] = '0';
+ $handler->display->display_options['menu']['context'] = 0;
+ $handler->display->display_options['menu']['context_only_inline'] = 0;
+ $export['movies'] = $view;
+
+ $view = new view();
+ $view->name = 'stars';
+ $view->description = '';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'Stars';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['title'] = 'Stars';
+ $handler->display->display_options['use_more_always'] = FALSE;
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = '10';
+ $handler->display->display_options['style_plugin'] = 'table';
+ $handler->display->display_options['style_options']['columns'] = array(
+ 'title' => 'title',
+ );
+ $handler->display->display_options['style_options']['default'] = '-1';
+ $handler->display->display_options['style_options']['info'] = array(
+ 'title' => array(
+ 'sortable' => 0,
+ 'default_sort_order' => 'asc',
+ 'align' => '',
+ 'separator' => '',
+ 'empty_column' => 0,
+ ),
+ );
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['label'] = '';
+ $handler->display->display_options['fields']['title']['alter']['word_boundary'] = FALSE;
+ $handler->display->display_options['fields']['title']['alter']['ellipsis'] = FALSE;
+ $handler->display->display_options['fields']['title']['element_label_colon'] = FALSE;
+ /* Sort criterion: Content: Title */
+ $handler->display->display_options['sorts']['title']['id'] = 'title';
+ $handler->display->display_options['sorts']['title']['table'] = 'node';
+ $handler->display->display_options['sorts']['title']['field'] = 'title';
+ $handler->display->display_options['sorts']['title']['order'] = 'DESC';
+ /* Filter criterion: Content: Published */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'node';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = 1;
+ $handler->display->display_options['filters']['status']['group'] = 1;
+ $handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;
+ /* Filter criterion: Content: Type */
+ $handler->display->display_options['filters']['type']['id'] = 'type';
+ $handler->display->display_options['filters']['type']['table'] = 'node';
+ $handler->display->display_options['filters']['type']['field'] = 'type';
+ $handler->display->display_options['filters']['type']['value'] = array(
+ 'star' => 'star',
+ );
+
+ /* Display: Page */
+ $handler = $view->new_display('page', 'Page', 'page');
+ $handler->display->display_options['path'] = 'stars';
+ $export['stars'] = $view;
+
+ return $export;
+}
From 0c41adb5e780967deb8c40b2933c0b6abb1cbc49 Mon Sep 17 00:00:00 2001
From: zbenaissa
Date: Tue, 24 Nov 2015 14:24:39 -0500
Subject: [PATCH 04/21] Added even more views and replaced old fields with new
entity reference type fields
---
.../custom_config.features.field_base.inc | 176 ++++++++-----
.../custom_config.features.field_instance.inc | 232 ++++++++----------
.../custom_config/custom_config.features.inc | 16 --
.../modules/custom_config/custom_config.info | 23 +-
.../custom_config.views_default.inc | 60 +++++
5 files changed, 282 insertions(+), 225 deletions(-)
diff --git a/docroot/sites/all/modules/custom_config/custom_config.features.field_base.inc b/docroot/sites/all/modules/custom_config/custom_config.features.field_base.inc
index 8eba9f7..a6b3c1d 100644
--- a/docroot/sites/all/modules/custom_config/custom_config.features.field_base.inc
+++ b/docroot/sites/all/modules/custom_config/custom_config.features.field_base.inc
@@ -111,6 +111,46 @@ function custom_config_field_default_field_bases() {
'type' => 'entityreference',
);
+ // Exported field_base: 'field_directors_movies'.
+ $field_bases['field_directors_movies'] = array(
+ 'active' => 1,
+ 'cardinality' => -1,
+ 'deleted' => 0,
+ 'entity_types' => array(),
+ 'field_name' => 'field_directors_movies',
+ 'indexes' => array(
+ 'target_id' => array(
+ 0 => 'target_id',
+ ),
+ ),
+ 'locked' => 0,
+ 'module' => 'entityreference',
+ 'settings' => array(
+ 'handler' => 'base',
+ 'handler_settings' => array(
+ 'behaviors' => array(
+ 'test_field_behavior' => array(
+ 'status' => 0,
+ ),
+ 'views-select-list' => array(
+ 'status' => 0,
+ ),
+ ),
+ 'sort' => array(
+ 'direction' => 'ASC',
+ 'property' => 'title',
+ 'type' => 'property',
+ ),
+ 'target_bundles' => array(
+ 'movies' => 'movies',
+ ),
+ ),
+ 'target_type' => 'node',
+ ),
+ 'translatable' => 0,
+ 'type' => 'entityreference',
+ );
+
// Exported field_base: 'field_image'.
$field_bases['field_image'] = array(
'active' => 1,
@@ -133,61 +173,66 @@ function custom_config_field_default_field_bases() {
'type' => 'image',
);
- // Exported field_base: 'field_list_movies'.
- $field_bases['field_list_movies'] = array(
+ // Exported field_base: 'field_movies_image'.
+ $field_bases['field_movies_image'] = array(
'active' => 1,
'cardinality' => 1,
'deleted' => 0,
'entity_types' => array(),
- 'field_name' => 'field_list_movies',
+ 'field_name' => 'field_movies_image',
'indexes' => array(
- 'format' => array(
- 0 => 'format',
+ 'fid' => array(
+ 0 => 'fid',
),
),
'locked' => 0,
- 'module' => 'text',
- 'settings' => array(),
+ 'module' => 'image',
+ 'settings' => array(
+ 'default_image' => 0,
+ 'uri_scheme' => 'public',
+ ),
'translatable' => 0,
- 'type' => 'text_with_summary',
+ 'type' => 'image',
);
- // Exported field_base: 'field_list_stars'.
- $field_bases['field_list_stars'] = array(
+ // Exported field_base: 'field_stars_movies'.
+ $field_bases['field_stars_movies'] = array(
'active' => 1,
- 'cardinality' => 1,
+ 'cardinality' => -1,
'deleted' => 0,
'entity_types' => array(),
- 'field_name' => 'field_list_stars',
+ 'field_name' => 'field_stars_movies',
'indexes' => array(
- 'format' => array(
- 0 => 'format',
+ 'target_id' => array(
+ 0 => 'target_id',
),
),
'locked' => 0,
- 'module' => 'text',
- 'settings' => array(),
- 'translatable' => 0,
- 'type' => 'text_with_summary',
- );
-
- // Exported field_base: 'field_movies'.
- $field_bases['field_movies'] = array(
- 'active' => 1,
- 'cardinality' => 1,
- 'deleted' => 0,
- 'entity_types' => array(),
- 'field_name' => 'field_movies',
- 'indexes' => array(
- 'format' => array(
- 0 => 'format',
+ 'module' => 'entityreference',
+ 'settings' => array(
+ 'handler' => 'base',
+ 'handler_settings' => array(
+ 'behaviors' => array(
+ 'test_field_behavior' => array(
+ 'status' => 0,
+ ),
+ 'views-select-list' => array(
+ 'status' => 0,
+ ),
+ ),
+ 'sort' => array(
+ 'direction' => 'ASC',
+ 'property' => 'title',
+ 'type' => 'property',
+ ),
+ 'target_bundles' => array(
+ 'movies' => 'movies',
+ ),
),
+ 'target_type' => 'node',
),
- 'locked' => 0,
- 'module' => 'text',
- 'settings' => array(),
'translatable' => 0,
- 'type' => 'text_with_summary',
+ 'type' => 'entityreference',
);
// Exported field_base: 'field_tags'.
@@ -216,51 +261,54 @@ function custom_config_field_default_field_bases() {
'type' => 'taxonomy_term_reference',
);
- // Exported field_base: 'field_works'.
- $field_bases['field_works'] = array(
+ // Exported field_base: 'field_writers'.
+ $field_bases['field_writers'] = array(
'active' => 1,
- 'cardinality' => 1,
+ 'cardinality' => -1,
'deleted' => 0,
'entity_types' => array(),
- 'field_name' => 'field_works',
+ 'field_name' => 'field_writers',
'indexes' => array(
- 'format' => array(
- 0 => 'format',
+ 'target_id' => array(
+ 0 => 'target_id',
),
),
'locked' => 0,
- 'module' => 'text',
- 'settings' => array(),
- 'translatable' => 0,
- 'type' => 'text_with_summary',
- );
-
- // Exported field_base: 'field_writer_movies'.
- $field_bases['field_writer_movies'] = array(
- 'active' => 1,
- 'cardinality' => 1,
- 'deleted' => 0,
- 'entity_types' => array(),
- 'field_name' => 'field_writer_movies',
- 'indexes' => array(
- 'format' => array(
- 0 => 'format',
+ 'module' => 'entityreference',
+ 'settings' => array(
+ 'handler' => 'base',
+ 'handler_settings' => array(
+ 'behaviors' => array(
+ 'test_field_behavior' => array(
+ 'status' => 0,
+ ),
+ 'views-select-list' => array(
+ 'status' => 0,
+ ),
+ ),
+ 'sort' => array(
+ 'direction' => 'ASC',
+ 'property' => 'title',
+ 'type' => 'property',
+ ),
+ 'target_bundles' => array(
+ 'director' => 'director',
+ 'writer' => 'writer',
+ ),
),
+ 'target_type' => 'node',
),
- 'locked' => 0,
- 'module' => 'text',
- 'settings' => array(),
'translatable' => 0,
- 'type' => 'text_with_summary',
+ 'type' => 'entityreference',
);
- // Exported field_base: 'field_writers'.
- $field_bases['field_writers'] = array(
+ // Exported field_base: 'field_writers_movies'.
+ $field_bases['field_writers_movies'] = array(
'active' => 1,
- 'cardinality' => 1,
+ 'cardinality' => -1,
'deleted' => 0,
'entity_types' => array(),
- 'field_name' => 'field_writers',
+ 'field_name' => 'field_writers_movies',
'indexes' => array(
'target_id' => array(
0 => 'target_id',
diff --git a/docroot/sites/all/modules/custom_config/custom_config.features.field_instance.inc b/docroot/sites/all/modules/custom_config/custom_config.features.field_instance.inc
index 64decd5..9606962 100644
--- a/docroot/sites/all/modules/custom_config/custom_config.features.field_instance.inc
+++ b/docroot/sites/all/modules/custom_config/custom_config.features.field_instance.inc
@@ -188,8 +188,8 @@ function custom_config_field_default_field_instances() {
),
);
- // Exported field_instance: 'node-director-field_movies'.
- $field_instances['node-director-field_movies'] = array(
+ // Exported field_instance: 'node-director-field_directors_movies'.
+ $field_instances['node-director-field_directors_movies'] = array(
'bundle' => 'director',
'default_value' => NULL,
'deleted' => 0,
@@ -197,9 +197,12 @@ function custom_config_field_default_field_instances() {
'display' => array(
'default' => array(
'label' => 'above',
- 'module' => 'text',
- 'settings' => array(),
- 'type' => 'text_default',
+ 'module' => 'entityreference',
+ 'settings' => array(
+ 'links' => 1,
+ 'view_mode' => 'teaser',
+ ),
+ 'type' => 'entityreference_entity_view',
'weight' => 1,
),
'teaser' => array(
@@ -210,22 +213,22 @@ function custom_config_field_default_field_instances() {
),
),
'entity_type' => 'node',
- 'field_name' => 'field_movies',
+ 'field_name' => 'field_directors_movies',
'label' => 'Movies',
'required' => 0,
'settings' => array(
- 'display_summary' => 0,
- 'text_processing' => 1,
+ 'behaviors' => array(
+ 'test_instance_behavior' => array(
+ 'status' => 0,
+ ),
+ ),
'user_register_form' => FALSE,
),
'widget' => array(
'active' => 1,
- 'module' => 'text',
- 'settings' => array(
- 'rows' => 20,
- 'summary_rows' => 5,
- ),
- 'type' => 'text_textarea_with_summary',
+ 'module' => 'options',
+ 'settings' => array(),
+ 'type' => 'options_buttons',
'weight' => -3,
),
);
@@ -405,22 +408,21 @@ function custom_config_field_default_field_instances() {
),
);
- // Exported field_instance: 'node-movies-field_writers'.
- $field_instances['node-movies-field_writers'] = array(
+ // Exported field_instance: 'node-movies-field_movies_image'.
+ $field_instances['node-movies-field_movies_image'] = array(
'bundle' => 'movies',
- 'default_value' => NULL,
'deleted' => 0,
'description' => '',
'display' => array(
'default' => array(
'label' => 'above',
- 'module' => 'entityreference',
+ 'module' => 'image',
'settings' => array(
- 'links' => 1,
- 'view_mode' => 'default',
+ 'image_link' => '',
+ 'image_style' => '',
),
- 'type' => 'entityreference_entity_view',
- 'weight' => 6,
+ 'type' => 'image',
+ 'weight' => 7,
),
'teaser' => array(
'label' => 'above',
@@ -430,39 +432,48 @@ function custom_config_field_default_field_instances() {
),
),
'entity_type' => 'node',
- 'field_name' => 'field_writers',
- 'label' => 'Writers',
+ 'field_name' => 'field_movies_image',
+ 'label' => 'Image',
'required' => 0,
'settings' => array(
- 'behaviors' => array(
- 'test_instance_behavior' => array(
- 'status' => 0,
- ),
- ),
+ 'alt_field' => 1,
+ 'default_image' => 0,
+ 'file_directory' => '',
+ 'file_extensions' => 'png gif jpg jpeg',
+ 'max_filesize' => '1000 KB',
+ 'max_resolution' => '',
+ 'min_resolution' => '',
+ 'title_field' => 0,
'user_register_form' => FALSE,
),
'widget' => array(
'active' => 1,
- 'module' => 'options',
- 'settings' => array(),
- 'type' => 'options_buttons',
- 'weight' => 2,
+ 'module' => 'image',
+ 'settings' => array(
+ 'preview_image_style' => 'thumbnail',
+ 'progress_indicator' => 'throbber',
+ ),
+ 'type' => 'image_image',
+ 'weight' => 3,
),
);
- // Exported field_instance: 'node-movies_list-field_list_movies'.
- $field_instances['node-movies_list-field_list_movies'] = array(
- 'bundle' => 'movies_list',
+ // Exported field_instance: 'node-movies-field_writers'.
+ $field_instances['node-movies-field_writers'] = array(
+ 'bundle' => 'movies',
'default_value' => NULL,
'deleted' => 0,
'description' => '',
'display' => array(
'default' => array(
'label' => 'above',
- 'module' => 'text',
- 'settings' => array(),
- 'type' => 'text_default',
- 'weight' => 0,
+ 'module' => 'entityreference',
+ 'settings' => array(
+ 'links' => 1,
+ 'view_mode' => 'teaser',
+ ),
+ 'type' => 'entityreference_entity_view',
+ 'weight' => 6,
),
'teaser' => array(
'label' => 'above',
@@ -472,23 +483,23 @@ function custom_config_field_default_field_instances() {
),
),
'entity_type' => 'node',
- 'field_name' => 'field_list_movies',
- 'label' => 'List Movies',
+ 'field_name' => 'field_writers',
+ 'label' => 'Writers',
'required' => 0,
'settings' => array(
- 'display_summary' => 0,
- 'text_processing' => 1,
+ 'behaviors' => array(
+ 'test_instance_behavior' => array(
+ 'status' => 0,
+ ),
+ ),
'user_register_form' => FALSE,
),
'widget' => array(
'active' => 1,
- 'module' => 'text',
- 'settings' => array(
- 'rows' => 20,
- 'summary_rows' => 5,
- ),
- 'type' => 'text_textarea_with_summary',
- 'weight' => -4,
+ 'module' => 'options',
+ 'settings' => array(),
+ 'type' => 'options_buttons',
+ 'weight' => 2,
),
);
@@ -577,18 +588,21 @@ function custom_config_field_default_field_instances() {
),
);
- // Exported field_instance: 'node-star-field_works'.
- $field_instances['node-star-field_works'] = array(
+ // Exported field_instance: 'node-star-field_stars_movies'.
+ $field_instances['node-star-field_stars_movies'] = array(
'bundle' => 'star',
'default_value' => NULL,
'deleted' => 0,
- 'description' => 'Movies this Star is known for ',
+ 'description' => '',
'display' => array(
'default' => array(
'label' => 'above',
- 'module' => 'text',
- 'settings' => array(),
- 'type' => 'text_default',
+ 'module' => 'entityreference',
+ 'settings' => array(
+ 'links' => 1,
+ 'view_mode' => 'teaser',
+ ),
+ 'type' => 'entityreference_entity_view',
'weight' => 1,
),
'teaser' => array(
@@ -599,65 +613,23 @@ function custom_config_field_default_field_instances() {
),
),
'entity_type' => 'node',
- 'field_name' => 'field_works',
+ 'field_name' => 'field_stars_movies',
'label' => 'Movies',
'required' => 0,
'settings' => array(
- 'display_summary' => 0,
- 'text_processing' => 1,
- 'user_register_form' => FALSE,
- ),
- 'widget' => array(
- 'active' => 1,
- 'module' => 'text',
- 'settings' => array(
- 'rows' => 20,
- 'summary_rows' => 5,
- ),
- 'type' => 'text_textarea_with_summary',
- 'weight' => -3,
- ),
- );
-
- // Exported field_instance: 'node-stars_list-field_list_stars'.
- $field_instances['node-stars_list-field_list_stars'] = array(
- 'bundle' => 'stars_list',
- 'default_value' => NULL,
- 'deleted' => 0,
- 'description' => '',
- 'display' => array(
- 'default' => array(
- 'label' => 'above',
- 'module' => 'text',
- 'settings' => array(),
- 'type' => 'text_default',
- 'weight' => 0,
- ),
- 'teaser' => array(
- 'label' => 'above',
- 'settings' => array(),
- 'type' => 'hidden',
- 'weight' => 0,
+ 'behaviors' => array(
+ 'test_instance_behavior' => array(
+ 'status' => 0,
+ ),
),
- ),
- 'entity_type' => 'node',
- 'field_name' => 'field_list_stars',
- 'label' => 'List Stars',
- 'required' => 0,
- 'settings' => array(
- 'display_summary' => 0,
- 'text_processing' => 1,
'user_register_form' => FALSE,
),
'widget' => array(
'active' => 1,
- 'module' => 'text',
- 'settings' => array(
- 'rows' => 20,
- 'summary_rows' => 5,
- ),
- 'type' => 'text_textarea_with_summary',
- 'weight' => -4,
+ 'module' => 'options',
+ 'settings' => array(),
+ 'type' => 'options_buttons',
+ 'weight' => -3,
),
);
@@ -677,11 +649,8 @@ function custom_config_field_default_field_instances() {
),
'teaser' => array(
'label' => 'hidden',
- 'module' => 'text',
- 'settings' => array(
- 'trim_length' => 600,
- ),
- 'type' => 'text_summary_or_trimmed',
+ 'settings' => array(),
+ 'type' => 'hidden',
'weight' => 0,
),
),
@@ -705,8 +674,8 @@ function custom_config_field_default_field_instances() {
),
);
- // Exported field_instance: 'node-writer-field_writer_movies'.
- $field_instances['node-writer-field_writer_movies'] = array(
+ // Exported field_instance: 'node-writer-field_writers_movies'.
+ $field_instances['node-writer-field_writers_movies'] = array(
'bundle' => 'writer',
'default_value' => NULL,
'deleted' => 0,
@@ -714,9 +683,12 @@ function custom_config_field_default_field_instances() {
'display' => array(
'default' => array(
'label' => 'above',
- 'module' => 'text',
- 'settings' => array(),
- 'type' => 'text_default',
+ 'module' => 'entityreference',
+ 'settings' => array(
+ 'links' => 1,
+ 'view_mode' => 'teaser',
+ ),
+ 'type' => 'entityreference_entity_view',
'weight' => 1,
),
'teaser' => array(
@@ -727,22 +699,22 @@ function custom_config_field_default_field_instances() {
),
),
'entity_type' => 'node',
- 'field_name' => 'field_writer_movies',
- 'label' => 'Movies',
+ 'field_name' => 'field_writers_movies',
+ 'label' => 'movies',
'required' => 0,
'settings' => array(
- 'display_summary' => 0,
- 'text_processing' => 1,
+ 'behaviors' => array(
+ 'test_instance_behavior' => array(
+ 'status' => 0,
+ ),
+ ),
'user_register_form' => FALSE,
),
'widget' => array(
'active' => 1,
- 'module' => 'text',
- 'settings' => array(
- 'rows' => 20,
- 'summary_rows' => 5,
- ),
- 'type' => 'text_textarea_with_summary',
+ 'module' => 'options',
+ 'settings' => array(),
+ 'type' => 'options_buttons',
'weight' => -3,
),
);
@@ -754,13 +726,11 @@ function custom_config_field_default_field_instances() {
t('Director');
t('Enter a comma-separated list of words to describe your content.');
t('Image');
- t('List Movies');
- t('List Stars');
t('Movies');
- t('Movies this Star is known for ');
t('Tags');
t('Upload an image to go with this article.');
t('Writers');
+ t('movies');
return $field_instances;
}
diff --git a/docroot/sites/all/modules/custom_config/custom_config.features.inc b/docroot/sites/all/modules/custom_config/custom_config.features.inc
index 9eabe94..1b5d590 100644
--- a/docroot/sites/all/modules/custom_config/custom_config.features.inc
+++ b/docroot/sites/all/modules/custom_config/custom_config.features.inc
@@ -48,14 +48,6 @@ function custom_config_node_info() {
'title_label' => t('Title'),
'help' => '',
),
- 'movies_list' => array(
- 'name' => t('Movies List'),
- 'base' => 'node_content',
- 'description' => '',
- 'has_title' => '1',
- 'title_label' => t('Title'),
- 'help' => '',
- ),
'page' => array(
'name' => t('Basic page'),
'base' => 'node_content',
@@ -72,14 +64,6 @@ function custom_config_node_info() {
'title_label' => t('Name'),
'help' => '',
),
- 'stars_list' => array(
- 'name' => t('Stars List'),
- 'base' => 'node_content',
- 'description' => '',
- 'has_title' => '1',
- 'title_label' => t('Title'),
- 'help' => '',
- ),
'writer' => array(
'name' => t('Writers'),
'base' => 'node_content',
diff --git a/docroot/sites/all/modules/custom_config/custom_config.info b/docroot/sites/all/modules/custom_config/custom_config.info
index f2e5e6b..d7473df 100644
--- a/docroot/sites/all/modules/custom_config/custom_config.info
+++ b/docroot/sites/all/modules/custom_config/custom_config.info
@@ -13,45 +13,40 @@ features[features_api][] = api:2
features[field_base][] = body
features[field_base][] = field_actors
features[field_base][] = field_director
+features[field_base][] = field_directors_movies
features[field_base][] = field_image
-features[field_base][] = field_list_movies
-features[field_base][] = field_list_stars
-features[field_base][] = field_movies
+features[field_base][] = field_movies_image
+features[field_base][] = field_stars_movies
features[field_base][] = field_tags
-features[field_base][] = field_works
-features[field_base][] = field_writer_movies
features[field_base][] = field_writers
+features[field_base][] = field_writers_movies
features[field_instance][] = node-article-body
features[field_instance][] = node-article-field_image
features[field_instance][] = node-article-field_tags
features[field_instance][] = node-director-body
-features[field_instance][] = node-director-field_movies
+features[field_instance][] = node-director-field_directors_movies
features[field_instance][] = node-home_page-body
features[field_instance][] = node-movies-body
features[field_instance][] = node-movies-field_actors
features[field_instance][] = node-movies-field_director
+features[field_instance][] = node-movies-field_movies_image
features[field_instance][] = node-movies-field_writers
-features[field_instance][] = node-movies_list-field_list_movies
features[field_instance][] = node-page-body
features[field_instance][] = node-star-body
-features[field_instance][] = node-star-field_works
-features[field_instance][] = node-stars_list-field_list_stars
+features[field_instance][] = node-star-field_stars_movies
features[field_instance][] = node-writer-body
-features[field_instance][] = node-writer-field_writer_movies
-features[menu_links][] = navigation_all-movies:node/12
-features[menu_links][] = navigation_all-stars:node/13
+features[field_instance][] = node-writer-field_writers_movies
features[node][] = article
features[node][] = director
features[node][] = home_page
features[node][] = movies
-features[node][] = movies_list
features[node][] = page
features[node][] = star
-features[node][] = stars_list
features[node][] = writer
features[taxonomy][] = tags
features[views_view][] = directors
features[views_view][] = movies
features[views_view][] = stars
+features[views_view][] = writers
features_exclude[dependencies][ctools] = ctools
features_exclude[dependencies][options] = options
diff --git a/docroot/sites/all/modules/custom_config/custom_config.views_default.inc b/docroot/sites/all/modules/custom_config/custom_config.views_default.inc
index 4066009..8589e63 100644
--- a/docroot/sites/all/modules/custom_config/custom_config.views_default.inc
+++ b/docroot/sites/all/modules/custom_config/custom_config.views_default.inc
@@ -197,5 +197,65 @@ function custom_config_views_default_views() {
$handler->display->display_options['path'] = 'stars';
$export['stars'] = $view;
+ $view = new view();
+ $view->name = 'writers';
+ $view->description = '';
+ $view->tag = 'default';
+ $view->base_table = 'node';
+ $view->human_name = 'Writers';
+ $view->core = 7;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Master */
+ $handler = $view->new_display('default', 'Master', 'default');
+ $handler->display->display_options['title'] = 'Writers';
+ $handler->display->display_options['use_more_always'] = FALSE;
+ $handler->display->display_options['access']['type'] = 'perm';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['pager']['options']['items_per_page'] = '10';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'node';
+ $handler->display->display_options['row_options']['links'] = FALSE;
+ /* Field: Content: Title */
+ $handler->display->display_options['fields']['title']['id'] = 'title';
+ $handler->display->display_options['fields']['title']['table'] = 'node';
+ $handler->display->display_options['fields']['title']['field'] = 'title';
+ $handler->display->display_options['fields']['title']['label'] = '';
+ $handler->display->display_options['fields']['title']['alter']['word_boundary'] = FALSE;
+ $handler->display->display_options['fields']['title']['alter']['ellipsis'] = FALSE;
+ /* Sort criterion: Content: Title */
+ $handler->display->display_options['sorts']['title']['id'] = 'title';
+ $handler->display->display_options['sorts']['title']['table'] = 'node';
+ $handler->display->display_options['sorts']['title']['field'] = 'title';
+ $handler->display->display_options['sorts']['title']['order'] = 'DESC';
+ /* Filter criterion: Content: Published */
+ $handler->display->display_options['filters']['status']['id'] = 'status';
+ $handler->display->display_options['filters']['status']['table'] = 'node';
+ $handler->display->display_options['filters']['status']['field'] = 'status';
+ $handler->display->display_options['filters']['status']['value'] = 1;
+ $handler->display->display_options['filters']['status']['group'] = 1;
+ $handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;
+ /* Filter criterion: Content: Type */
+ $handler->display->display_options['filters']['type']['id'] = 'type';
+ $handler->display->display_options['filters']['type']['table'] = 'node';
+ $handler->display->display_options['filters']['type']['field'] = 'type';
+ $handler->display->display_options['filters']['type']['value'] = array(
+ 'director' => 'director',
+ );
+
+ /* Display: Page */
+ $handler = $view->new_display('page', 'Page', 'page');
+ $handler->display->display_options['path'] = 'writers';
+ $handler->display->display_options['menu']['type'] = 'normal';
+ $handler->display->display_options['menu']['title'] = 'Writers';
+ $handler->display->display_options['menu']['weight'] = '0';
+ $handler->display->display_options['menu']['context'] = 0;
+ $handler->display->display_options['menu']['context_only_inline'] = 0;
+ $export['writers'] = $view;
+
return $export;
}
From 556363187000da130bc673cea05d37816afa3d4c Mon Sep 17 00:00:00 2001
From: zbenaissa
Date: Wed, 25 Nov 2015 13:50:06 -0500
Subject: [PATCH 05/21] Added dbmi module
---
.../sites/all/modules/dbmi/dbmi.features.inc | 0
docroot/sites/all/modules/dbmi/dbmi.info | 3 ++
docroot/sites/all/modules/dbmi/dbmi.module | 48 +++++++++++++++++++
docroot/sites/all/modules/dbmi/movie/add.php | 10 ++++
4 files changed, 61 insertions(+)
create mode 100644 docroot/sites/all/modules/dbmi/dbmi.features.inc
create mode 100644 docroot/sites/all/modules/dbmi/dbmi.info
create mode 100644 docroot/sites/all/modules/dbmi/dbmi.module
create mode 100644 docroot/sites/all/modules/dbmi/movie/add.php
diff --git a/docroot/sites/all/modules/dbmi/dbmi.features.inc b/docroot/sites/all/modules/dbmi/dbmi.features.inc
new file mode 100644
index 0000000..e69de29
diff --git a/docroot/sites/all/modules/dbmi/dbmi.info b/docroot/sites/all/modules/dbmi/dbmi.info
new file mode 100644
index 0000000..83d6e7a
--- /dev/null
+++ b/docroot/sites/all/modules/dbmi/dbmi.info
@@ -0,0 +1,3 @@
+name = DBMI
+core = 7.x
+package = Features
\ No newline at end of file
diff --git a/docroot/sites/all/modules/dbmi/dbmi.module b/docroot/sites/all/modules/dbmi/dbmi.module
new file mode 100644
index 0000000..385af91
--- /dev/null
+++ b/docroot/sites/all/modules/dbmi/dbmi.module
@@ -0,0 +1,48 @@
+' . t("TBD") . '
';
+ break;
+ }
+}
+
+include_once 'dbmi.features.inc';
+
+function dbmi_menu() {
+
+ $aItems = array(
+ 'dbmi' => array(
+ 'title' => 'dbmi',
+ 'page callback' => '_page_dbmi',
+ 'access callback' => TRUE,
+ ),
+ );
+ return $aItems;
+}
+
+/**
+ * Page callback mapped to the url /hello_world
+ *
+ * @return array
+ */
+function _page_dbmi() {
+ return array(
+ '#markup' => 'DBMI
'
+ );
+}
\ No newline at end of file
diff --git a/docroot/sites/all/modules/dbmi/movie/add.php b/docroot/sites/all/modules/dbmi/movie/add.php
new file mode 100644
index 0000000..69a5e98
--- /dev/null
+++ b/docroot/sites/all/modules/dbmi/movie/add.php
@@ -0,0 +1,10 @@
+ Add Movie ";
+?>
\ No newline at end of file
From d86360f4730b7f3bd0e552cb93953b85f6870127 Mon Sep 17 00:00:00 2001
From: zbenaissa
Date: Wed, 25 Nov 2015 15:24:56 -0500
Subject: [PATCH 06/21] Added forms to dbmi.module and files[] to dbmi.info
---
docroot/sites/all/modules/dbmi/dbmi.info | 3 +-
docroot/sites/all/modules/dbmi/dbmi.module | 54 ++++++++++++++++++----
2 files changed, 48 insertions(+), 9 deletions(-)
diff --git a/docroot/sites/all/modules/dbmi/dbmi.info b/docroot/sites/all/modules/dbmi/dbmi.info
index 83d6e7a..b832c42 100644
--- a/docroot/sites/all/modules/dbmi/dbmi.info
+++ b/docroot/sites/all/modules/dbmi/dbmi.info
@@ -1,3 +1,4 @@
name = DBMI
core = 7.x
-package = Features
\ No newline at end of file
+package = Features
+files[] = dbmi.module
\ No newline at end of file
diff --git a/docroot/sites/all/modules/dbmi/dbmi.module b/docroot/sites/all/modules/dbmi/dbmi.module
index 385af91..87712fc 100644
--- a/docroot/sites/all/modules/dbmi/dbmi.module
+++ b/docroot/sites/all/modules/dbmi/dbmi.module
@@ -26,23 +26,61 @@ include_once 'dbmi.features.inc';
function dbmi_menu() {
- $aItems = array(
- 'dbmi' => array(
- 'title' => 'dbmi',
- 'page callback' => '_page_dbmi',
+ $aItems = array();
+ $aItems['dbmi/movie/add'] = array(
+ 'title' => 'Add Movies',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('form_example'),
'access callback' => TRUE,
- ),
- );
+ );
return $aItems;
}
/**
- * Page callback mapped to the url /hello_world
+ * Page callback mapped to the url /dbmi/movie/add
*
* @return array
*/
function _page_dbmi() {
return array(
- '#markup' => 'DBMI
'
+ '#markup' => 'Add Movie
+ There will be a form here
'
+ );
+}
+
+function form_example($form, &$form_state) {
+
+ $form['movie'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Movie'),
+ '#collapsible' => TRUE,
+ '#collapsed' => FALSE,
+ );
+ $form['movie']['title'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Title'),
+ '#required' => TRUE,
+ '#default_value' => "Title",
+ '#description' => "Please enter the movie title.",
);
+ $form['movie']['year'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Year'),
+ '#required' => TRUE,
+ '#default_value' => "Year",
+ '#description' => "Please enter the year the movie was released",
+ );
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => 'SEARCH',
+ );
+ return $form;
+
+}
+
+function form_example_submit($form_id, &$form_state) {
+ $form_state['redirect'] = array( 'http://www.omdbapi.com/?t='.
+ $form_state['values']['title'].'&y='.$form_state['values']['year']."&plot=short&r=json",
+);
}
\ No newline at end of file
From b70f87591e4e9021c00d1e8dfb6f57500742b02d Mon Sep 17 00:00:00 2001
From: zbenaissa
Date: Wed, 25 Nov 2015 17:07:32 -0500
Subject: [PATCH 07/21] Made example table
---
docroot/sites/all/modules/dbmi/dbmi.module | 83 ++++++++++++++++++++--
1 file changed, 77 insertions(+), 6 deletions(-)
diff --git a/docroot/sites/all/modules/dbmi/dbmi.module b/docroot/sites/all/modules/dbmi/dbmi.module
index 87712fc..c8e3596 100644
--- a/docroot/sites/all/modules/dbmi/dbmi.module
+++ b/docroot/sites/all/modules/dbmi/dbmi.module
@@ -30,7 +30,12 @@ function dbmi_menu() {
$aItems['dbmi/movie/add'] = array(
'title' => 'Add Movies',
'page callback' => 'drupal_get_form',
- 'page arguments' => array('form_example'),
+ 'page arguments' => array('dbmi_add_movie_form'),
+ 'access callback' => TRUE,
+ );
+ $aItems['dbmi/movie/results'] = array(
+ 'title' => 'Results',
+ 'page callback' => '_page_results',
'access callback' => TRUE,
);
return $aItems;
@@ -43,12 +48,11 @@ function dbmi_menu() {
*/
function _page_dbmi() {
return array(
- '#markup' => 'Add Movie
- There will be a form here
'
+ '#markup' => 'Add Movie
'
);
}
-function form_example($form, &$form_state) {
+function dbmi_add_movie_form($form, &$form_state) {
$form['movie'] = array(
'#type' => 'fieldset',
@@ -79,8 +83,75 @@ function form_example($form, &$form_state) {
}
-function form_example_submit($form_id, &$form_state) {
- $form_state['redirect'] = array( 'http://www.omdbapi.com/?t='.
+function dbmi_add_movie_form_submit($form_id, &$form_state) {
+ /*$form_state['redirect'] = array( 'http://www.omdbapi.com/?t='.
$form_state['values']['title'].'&y='.$form_state['values']['year']."&plot=short&r=json",
);
+ return array(
+ '#markup' => 'Add Movie
+ There will be a form here
'
+ );
+ /*$build = array();
+ $build['table'] = array(
+ '#theme' => 'table',
+ '#header' => $form_state['values']['title'],
+ '#rows' => 4,
+ );
+ $build['pager'] = array(
+ '#theme' => 'pager',
+ );
+ return $build;
+ $name = $form_state['values']['year'];
+ drupal_set_message(t('Thanks for filling out the form, %name',
+ array('%name' => $name)));
+
+ $items = array();
+ $items['dbmi/movie/results'] = array(
+ 'title' => 'Results',
+ 'page callback' => '_page_results',
+ 'access callback' => TRUE,
+ );
+
+ return $items;*/
+
+ $form_state['redirect'] = 'dbmi/movie/results';
+
+
+
+}
+
+function _page_results(){
+ /*$headers = array('H1','H2','H3');
+ $rows = array(array('c11','c12','c2'),array('c21','c22','c23'),array('c31','c32','c33'));
+ $variables = array($headers,$rows);
+ return theme_table($variables);
+ return array(
+ '#markup' => 'Results
'
+ );*/
+ $headers = array(
+ array('data' => 'First_Name', 'field' => 'First_Name'),
+ array('data' => 'Last_Name', 'field' => 'Last_Name'),
+ );
+
+
+ $rows = array(
+ array('field' => 'First_Guy', 'data' => array('John','Wayne')),
+ array('field' => 'Second_Guy', 'data' => array('Chuck','Berry')),
+ array('field' => 'Third_Guy', 'data' => array('Rick','James'))
+ );
+
+
+ $table_data = array(
+ 'header' => $headers,
+ 'rows' => $rows,
+ 'sticky' => FALSE,
+ 'empty' => 'No results found',
+ 'attributes' => array(),
+ 'caption' => '',
+ 'colgroups' => array()
+ );
+
+
+ $output = theme_table($table_data);
+ return $output;
}
\ No newline at end of file
From 251c770356baa81ccd513055e9a6f9a652a6a0a7 Mon Sep 17 00:00:00 2001
From: zbenaissa
Date: Wed, 2 Dec 2015 09:59:52 -0500
Subject: [PATCH 08/21] Modified dbmi.module, added practice modules menu_magic
and form_fun
---
docroot/sites/all/modules/dbmi/dbmi.module | 12 ++
.../all/modules/form_fun/form_fun.cake.inc | 28 +++++
.../sites/all/modules/form_fun/form_fun.info | 3 +
.../all/modules/form_fun/form_fun.module | 15 +++
.../modules/menu_magic/menu_magic.context.inc | 116 ++++++++++++++++++
.../modules/menu_magic/menu_magic.extra.inc | 9 ++
.../all/modules/menu_magic/menu_magic.info | 3 +
.../all/modules/menu_magic/menu_magic.module | 82 +++++++++++++
.../modules/menu_magic/menu_magic.user.inc | 16 +++
9 files changed, 284 insertions(+)
create mode 100644 docroot/sites/all/modules/form_fun/form_fun.cake.inc
create mode 100644 docroot/sites/all/modules/form_fun/form_fun.info
create mode 100644 docroot/sites/all/modules/form_fun/form_fun.module
create mode 100644 docroot/sites/all/modules/menu_magic/menu_magic.context.inc
create mode 100644 docroot/sites/all/modules/menu_magic/menu_magic.extra.inc
create mode 100644 docroot/sites/all/modules/menu_magic/menu_magic.info
create mode 100644 docroot/sites/all/modules/menu_magic/menu_magic.module
create mode 100644 docroot/sites/all/modules/menu_magic/menu_magic.user.inc
diff --git a/docroot/sites/all/modules/dbmi/dbmi.module b/docroot/sites/all/modules/dbmi/dbmi.module
index c8e3596..83d2d15 100644
--- a/docroot/sites/all/modules/dbmi/dbmi.module
+++ b/docroot/sites/all/modules/dbmi/dbmi.module
@@ -24,6 +24,18 @@ function dbmi_help($path, $arg) {
include_once 'dbmi.features.inc';
+/**
+ * Implements hook_help().
+ *
+ * Runs on every page.
+ */
+
+function dbmi_init() {
+ global $user;
+ //kpr($user);
+
+}
+
function dbmi_menu() {
$aItems = array();
diff --git a/docroot/sites/all/modules/form_fun/form_fun.cake.inc b/docroot/sites/all/modules/form_fun/form_fun.cake.inc
new file mode 100644
index 0000000..890d255
--- /dev/null
+++ b/docroot/sites/all/modules/form_fun/form_fun.cake.inc
@@ -0,0 +1,28 @@
+ 'select',
+ '#title' => t('Cake or pie?'),
+ '#description' => t('Would you like cake or pie?'),
+ '#options' => array(
+ 'cake' => t('Cake please'),
+ 'pie' => t('Pie I guess'),
+ ),
+ '#default_value' => 'cake',
+ '#required' => TRUE,
+ );
+
+ $form['buttons']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Submit'),
+ );
+
+ return $form;
+}
\ No newline at end of file
diff --git a/docroot/sites/all/modules/form_fun/form_fun.info b/docroot/sites/all/modules/form_fun/form_fun.info
new file mode 100644
index 0000000..78568a5
--- /dev/null
+++ b/docroot/sites/all/modules/form_fun/form_fun.info
@@ -0,0 +1,3 @@
+name = Form Fun
+description = Just a demo of how to use forms.
+core = 7.x
\ No newline at end of file
diff --git a/docroot/sites/all/modules/form_fun/form_fun.module b/docroot/sites/all/modules/form_fun/form_fun.module
new file mode 100644
index 0000000..ce01a38
--- /dev/null
+++ b/docroot/sites/all/modules/form_fun/form_fun.module
@@ -0,0 +1,15 @@
+ 'Cake of Pie?',
+ 'page callback' => 'form_fun_cake_page',
+ 'access arguments' => array('access content'),
+ 'file' => 'form_fun.cake.inc',
+ 'weight' => 1,
+ );
+ return $items;
+
+
+}
\ No newline at end of file
diff --git a/docroot/sites/all/modules/menu_magic/menu_magic.context.inc b/docroot/sites/all/modules/menu_magic/menu_magic.context.inc
new file mode 100644
index 0000000..6791d86
--- /dev/null
+++ b/docroot/sites/all/modules/menu_magic/menu_magic.context.inc
@@ -0,0 +1,116 @@
+body['und'][0]['value']);
+ $output = array(
+ '#type' => 'markup',
+ '#markup' => $text,
+ );
+ return $output;
+}
+/**
+ * Helper function.
+ */
+function _menu_magicify($text){
+ $substitutions = array(
+ ' ' => ' ',
+ '!' => "¡", # ¡
+ '"' => "„", # „
+ '#' => '#',
+ '$' => '$',
+ '%' => '%',
+ '&' => "⅋", # ⅋
+ "'" => "͵", # ͵
+ '(' => ')',
+ ')' => '(',
+ '*' => '*',
+ '+' => '+',
+ ',' => "‘", # ‘
+ '-' => '-',
+ '.' => "˙", # ˙
+ '/' => '/',
+ '0' => '0',
+ '1' => "⃓", # ,⃓ can be improved
+ '2' => "ჷ", # ჷ
+ '3' => "ε", # ε
+ '4' => "⇁⃓", # ⇁⃓ can be improved
+ '5' => "ᔕ", # ᔕ or maybe just "S"
+ '6' => '9',
+ '7' => "_̸", # _̸
+ '8' => '8',
+ '9' => '6',
+ ':' => ':',
+ ';' => "⋅̕", # ⋅̕ sloppy, should be improved
+ '<' => '>',
+ '=' => '=',
+ '>' => '<',
+ '?' => "¿", # ¿
+ '@' => '@', # can be improved
+ 'A' => "Ꮜ", # Ꮜ
+ 'B' => "ϴ", # ϴ can be improved
+ 'C' => "Ɔ", # Ɔ
+ 'D' => 'p', # should be an uppercase D!!
+ 'E' => "Ǝ", # Ǝ
+ 'F' => "Ⅎ", # Ⅎ
+ 'G' => "⅁", # ⅁
+ 'H' => 'H',
+ 'I' => 'I',
+ 'J' => "ſ", # ſ̲
+ 'K' => "ʞ", # ʞ should be an uppercase K!!
+ 'L' => "⅂", # ⅂
+ 'M' => "Ɯ", # Ɯ or maybe just "W"
+ 'N' => 'N',
+ 'O' => 'O',
+ 'P' => 'd', # should be uppercase P
+ 'Q' => "Ծ", # Ծ can be improved
+ 'R' => "Ȣ", # Ȣ can be improved
+ 'S' => 'S',
+ 'T' => "⊥", # ⊥
+ 'U' => "ᑎ", # ᑎ
+ 'V' => "Λ", # Λ
+ 'W' => 'M',
+ 'X' => 'X',
+ 'Y' => "⅄", # ⅄
+ 'Z' => 'Z',
+ '[' => ']',
+ '\\' => '\\',
+ ']' => '[',
+ '^' => "‿", # ‿
+ '_' => "‾", # ‾
+ '`' => " ̖", # ̖
+ 'a' => "ɐ", # ɐ
+ 'b' => 'q',
+ 'c' => "ɔ", # ɔ
+ 'd' => 'p',
+ 'e' => "ǝ", # ǝ
+ 'f' => "ɟ", # ɟ
+ 'g' => "ɓ", # ɓ
+ 'h' => "ɥ", # ɥ
+ 'i' => "ı̣", # ı̣
+ 'j' => "ſ", # ſ̣
+ 'k' => "ʞ", # ʞ
+ 'l' => "Ʈ", # Ʈ can be improved
+ 'm' => "ɯ", # ɯ
+ 'n' => 'u',
+ 'o' => 'o',
+ 'p' => 'd',
+ 'q' => 'b',
+ 'r' => "ɹ", # ɹ
+ 's' => 's',
+ 't' => "ʇ", # ʇ
+ 'u' => 'n',
+ 'v' => "ʌ", # ʌ
+ 'w' => "ʍ", # ʍ
+ 'x' => 'x',
+ 'y' => "ʎ", # ʎ
+ 'z' => 'z',
+ '{' => '}',
+ '|' => '|',
+ '}' => '{',
+ '~' => "∼", # ∼
+ );
+ $find = array_keys($substitutions);
+ $replace = array_values($substitutions);
+
+ return str_replace($find, $replace, $text);
+}
\ No newline at end of file
diff --git a/docroot/sites/all/modules/menu_magic/menu_magic.extra.inc b/docroot/sites/all/modules/menu_magic/menu_magic.extra.inc
new file mode 100644
index 0000000..3d45a63
--- /dev/null
+++ b/docroot/sites/all/modules/menu_magic/menu_magic.extra.inc
@@ -0,0 +1,9 @@
+ 'markup',
+ '#markup' => ''.t('The wildcard contains the value "%wildcard".',array('%wildcard' => $wildcard)).'
'
+ );
+ return $content;
+}
\ No newline at end of file
diff --git a/docroot/sites/all/modules/menu_magic/menu_magic.info b/docroot/sites/all/modules/menu_magic/menu_magic.info
new file mode 100644
index 0000000..6b5cd1d
--- /dev/null
+++ b/docroot/sites/all/modules/menu_magic/menu_magic.info
@@ -0,0 +1,3 @@
+name = Menu Magic
+description = Demonstrate the use of hook_menu.
+core = 7.x
\ No newline at end of file
diff --git a/docroot/sites/all/modules/menu_magic/menu_magic.module b/docroot/sites/all/modules/menu_magic/menu_magic.module
new file mode 100644
index 0000000..a62fc61
--- /dev/null
+++ b/docroot/sites/all/modules/menu_magic/menu_magic.module
@@ -0,0 +1,82 @@
+ 'A little magic',
+ 'page callback' => 'menu_magic_basic',
+ 'access arguments' => array('access content'),
+ );
+
+ $items['magic/%'] = array(
+ 'title' => 'Even more magical',
+ 'page callback' => 'menu_magic_extra',
+ 'page arguments' => array(1),
+ 'access arguments' => array('access content'),
+ 'file' => 'menu_magic.extra.inc',
+ );
+
+ $items['user/%/magic'] = array(
+ 'title' => 'Magic',
+ 'description' => 'Magical magic for users',
+ 'page callback' => 'menu_magic_user_tab',
+ 'page arguments' => array(1),
+ 'access callback' => 'user_access',
+ 'access arguments' => array('administer users'),
+ 'file' => 'menu_magic.user.inc',
+ 'type' => MENU_LOCAL_TASK,
+ );
+
+ $items['node/%node/magic'] = array(
+ 'title' => 'Magic',
+ 'description' => 'Do amazing and magical things',
+ 'page callback' => 'menu_magic_node_context',
+ 'page arguments' => array(1),
+ 'access arguments' => array('access content'),
+ 'file' => 'menu_magic.context.inc',
+ 'type' => MENU_LOCAL_TASK,
+ 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
+ );
+
+ return $items;
+}
+
+function menu_magic_basic(){
+ $content = array();
+
+ //This is a very, very simple page element. It will appear on the page,
+ //but other modules can't customize it and themes can't override its markup.
+ $content['raw_markup'] = array(
+ '#type' => 'markup',
+ '#markup' => 'Truly, this is magical!',
+ '#prefix' => '',
+ '#suffix' => '
',
+ );
+
+ $variables = array(
+ 'path' => 'http://placekitten.com/440/400',
+ 'alt' => t('This is a magical kitten'),
+ 'title' => t('This is the title'),
+ );
+ $content['themed_data'] = array(
+ '#type' => 'markup',
+ '#markup' => theme('image', $variables),
+ );
+
+ $content['renderable_element'] =array(
+ '#theme' => 'item_list', //calls the function theme_item_list
+ '#title' => t('How do we know it\'s magic?'),
+ '#items' => array(
+ t("Is it made of wood?"),
+ t("Does it sink in water?"),
+ t("Does it weigh the same as a duck?"),
+ ),
+
+ );
+
+ return $content;
+}
\ No newline at end of file
diff --git a/docroot/sites/all/modules/menu_magic/menu_magic.user.inc b/docroot/sites/all/modules/menu_magic/menu_magic.user.inc
new file mode 100644
index 0000000..54a1fe1
--- /dev/null
+++ b/docroot/sites/all/modules/menu_magic/menu_magic.user.inc
@@ -0,0 +1,16 @@
+ 'markup',
+ '#markup' => t("%username is totally awesome.", array('%username' => $account->name)),
+ );
+ }
+ else{
+ return drupal_not_found();
+ }
+}
\ No newline at end of file
From 451edafeb76a3b571884ef7904734f94a299029d Mon Sep 17 00:00:00 2001
From: zbenaissa
Date: Wed, 2 Dec 2015 10:31:33 -0500
Subject: [PATCH 09/21] Modified features module
---
docroot/sites/all/modules/features/API.txt | 189 ++
.../sites/all/modules/features/CHANGELOG.txt | 221 +++
.../sites/all/modules/features/LICENSE.txt | 339 ++++
docroot/sites/all/modules/features/README.txt | 236 +++
.../all/modules/features/features.admin.inc | 1652 +++++++++++++++++
.../all/modules/features/features.api.php | 553 ++++++
.../sites/all/modules/features/features.css | 579 ++++++
.../all/modules/features/features.drush.inc | 1026 ++++++++++
.../all/modules/features/features.export.inc | 1156 ++++++++++++
.../sites/all/modules/features/features.info | 14 +
.../all/modules/features/features.install | 151 ++
.../sites/all/modules/features/features.js | 431 +++++
.../all/modules/features/features.module | 1338 +++++++++++++
.../features/includes/features.block.inc | 40 +
.../features/includes/features.contact.inc | 115 ++
.../features/includes/features.context.inc | 54 +
.../features/includes/features.ctools.inc | 378 ++++
.../features/includes/features.features.inc | 73 +
.../features/includes/features.field.inc | 577 ++++++
.../features/includes/features.filter.inc | 120 ++
.../features/includes/features.image.inc | 110 ++
.../features/includes/features.locale.inc | 163 ++
.../features/includes/features.menu.inc | 427 +++++
.../features/includes/features.node.inc | 174 ++
.../features/includes/features.taxonomy.inc | 105 ++
.../features/includes/features.user.inc | 290 +++
.../all/modules/features/tests/features.test | 329 ++++
.../features_test.features.field_base.inc | 43 +
.../features_test.features.field_instance.inc | 76 +
.../features_test.features.filter.inc | 56 +
.../features_test/features_test.features.inc | 64 +
.../features_test.features.taxonomy.inc | 36 +
...features_test.features.user_permission.inc | 24 +
.../tests/features_test/features_test.info | 29 +
.../tests/features_test/features_test.module | 3 +
.../features_test.views_default.inc | 38 +
.../theme/features-admin-components.tpl.php | 23 +
.../all/modules/features/theme/theme.inc | 362 ++++
38 files changed, 11594 insertions(+)
create mode 100644 docroot/sites/all/modules/features/API.txt
create mode 100644 docroot/sites/all/modules/features/CHANGELOG.txt
create mode 100644 docroot/sites/all/modules/features/LICENSE.txt
create mode 100644 docroot/sites/all/modules/features/README.txt
create mode 100644 docroot/sites/all/modules/features/features.admin.inc
create mode 100644 docroot/sites/all/modules/features/features.api.php
create mode 100644 docroot/sites/all/modules/features/features.css
create mode 100644 docroot/sites/all/modules/features/features.drush.inc
create mode 100644 docroot/sites/all/modules/features/features.export.inc
create mode 100644 docroot/sites/all/modules/features/features.info
create mode 100644 docroot/sites/all/modules/features/features.install
create mode 100644 docroot/sites/all/modules/features/features.js
create mode 100644 docroot/sites/all/modules/features/features.module
create mode 100644 docroot/sites/all/modules/features/includes/features.block.inc
create mode 100644 docroot/sites/all/modules/features/includes/features.contact.inc
create mode 100644 docroot/sites/all/modules/features/includes/features.context.inc
create mode 100644 docroot/sites/all/modules/features/includes/features.ctools.inc
create mode 100644 docroot/sites/all/modules/features/includes/features.features.inc
create mode 100644 docroot/sites/all/modules/features/includes/features.field.inc
create mode 100644 docroot/sites/all/modules/features/includes/features.filter.inc
create mode 100644 docroot/sites/all/modules/features/includes/features.image.inc
create mode 100644 docroot/sites/all/modules/features/includes/features.locale.inc
create mode 100644 docroot/sites/all/modules/features/includes/features.menu.inc
create mode 100644 docroot/sites/all/modules/features/includes/features.node.inc
create mode 100644 docroot/sites/all/modules/features/includes/features.taxonomy.inc
create mode 100644 docroot/sites/all/modules/features/includes/features.user.inc
create mode 100644 docroot/sites/all/modules/features/tests/features.test
create mode 100644 docroot/sites/all/modules/features/tests/features_test/features_test.features.field_base.inc
create mode 100644 docroot/sites/all/modules/features/tests/features_test/features_test.features.field_instance.inc
create mode 100644 docroot/sites/all/modules/features/tests/features_test/features_test.features.filter.inc
create mode 100644 docroot/sites/all/modules/features/tests/features_test/features_test.features.inc
create mode 100644 docroot/sites/all/modules/features/tests/features_test/features_test.features.taxonomy.inc
create mode 100644 docroot/sites/all/modules/features/tests/features_test/features_test.features.user_permission.inc
create mode 100644 docroot/sites/all/modules/features/tests/features_test/features_test.info
create mode 100644 docroot/sites/all/modules/features/tests/features_test/features_test.module
create mode 100644 docroot/sites/all/modules/features/tests/features_test/features_test.views_default.inc
create mode 100644 docroot/sites/all/modules/features/theme/features-admin-components.tpl.php
create mode 100644 docroot/sites/all/modules/features/theme/theme.inc
diff --git a/docroot/sites/all/modules/features/API.txt b/docroot/sites/all/modules/features/API.txt
new file mode 100644
index 0000000..ab0d067
--- /dev/null
+++ b/docroot/sites/all/modules/features/API.txt
@@ -0,0 +1,189 @@
+
+Features 1.x API for Drupal 7.x
+-------------------------------
+This file contains documentation for two audiences: site builders interested in
+creating and managing features and module developers interested in integrating
+with the features module.
+
+
+Terminology
+-----------
+- A **feature module** is a Drupal module that contains the `feature` key in its
+`.info` file. This array describes the components that should be managed by
+Features within the module.
+
+- A **component** is a configuration object that can be exported. Examples: a
+view, content type or CCK field instance.
+
+- A **machine name** is a string identifier for a specific type of component and
+should be unique within a single Drupal site. It should not be a numerically
+generated serial id.
+
+- An **exportable** component is one that can be used solely from a default hook
+in code and without an instance in the database. For example, a view that lives
+in code does not need an entry in the database in order to be used.
+
+- A **faux-exportable** component is one that must exist in the database in
+order to be used. Any exports of this component are used to create or
+synchronize entries in the database that share the same machine name.
+
+
+### Component states
+
+Features provides some infrastructure to determine the state of components on
+the site. To determine the state of a component Features keeps track of three
+things using an md5 hash of
+
+- current code for the component. This is the configuration as actually
+represented in code by a given feature.
+
+- the most recent prior code state that differs from the current code state. For
+example, if an `svn update` changes the configuration of a view, this stores the
+code state *prior* to the update.
+
+- The "normal" component state. This is the configuration represented by the
+component as stored in the database or the default component (with any changes
+introduced by `drupal_alter()`) if no database override exists.
+
+Using these three values, Features determines a component to be in one of the
+following five states:
+
+- **Default** (`FEATURES_DEFAULT`) The object has no database entry or the
+database entry matches the state of the component in code. This should be the
+default state of components after installing a feature. Updating the component
+can be done by altering the code definition directly.
+
+- **Overridden** (`FEATURES_OVERRIDDEN`) The code remains constant but the
+database object does not match the state of the component in code. Changes must
+be reverted before the component can be updated from code.
+
+- **Needs review** (`FEATURES_NEEDS_REVIEW`) The previous code state, database
+state, and current code state all differ. This occurs most commonly when a user
+changes one of her components and then pulls updates to her codebase. Since
+there is no way to tell whether the code state or the database state is more
+recent/valid, user input is necessary to resolve this state.
+
+- **Rebuildable** (`FEATURES_REBUILDABLE`) This state only applies to
+**faux-exportables** and indicates that the database component must and can be
+safely updated from the code definition. The database entry does not match the
+current code state but does match the previous code state. Features assumes that
+in this scenario the user has made no substantive changes and the component can
+be updated automatically.
+
+- **Rebuilding** (`FEATURES_REBUILDING`) This state is rarely seen and only
+applies to **faux-exportables.** This state is shown when a
+`FEATURES_REBUILDABLE` component is *currently* being synced to the database.
+Usually this operation is very fast and short lived. However, if the operation
+is interrupted (e.g. the server goes down) this state will be seen until the
+rebuild locking semaphore is cleared.
+
+
+How a feature is generated
+--------------------------
+At a high level Features writes the code in a feature module using the following
+steps:
+
+1. An `.info` file describing the components that should be included in a
+Feature is generated. It is either read from an existing feature or generated
+through the Features UI.
+
+2. Features converts the info file into an `$export` array which contains a list
+of elements to be exported. Each component type is given a chance to add to the
+export list as well as request that *other* components be given a second chance
+to add to the `$export` array.
+
+3. If any additional components have been queued up in the `$pipe` we repeat
+step 2 for each of the queued component types.
+
+4. Once a full `$export` array is populated each component renders items from
+the `$export` array to PHP code as a exportable defaults hook.
+
+5. Finally, Features writes the code into files and delivers it as a
+downloadable package (UI) or writes it directly to a module directory (drush).
+
+This workflow makes a variety of things possible:
+
+### Add components to a feature
+
+Add the components to the `features` array in the feature's `.info` file and run
+`drush features-update`. The same operation can be performed using the
+*Recreate* page in the Features UI.
+
+### Remove components from a feature
+
+Remove the corresponding component lines from the feature's `.info` file and run
+`drush features-update`. The same operation can be performed using the
+*Recreate* page in the Features UI.
+
+### Rename a component
+
+Rename a component by changing its name in the feature's `.info` file and the
+key and name property of the exported object in the appropriate `.inc` file in
+the feature. Note that any references in other configuration objects to the
+previous name should also be updated.
+
+
+Integrating your module with the Features API
+---------------------------------------------
+This section is for developers interested in adding Features-based management
+for the configuration objects in their modules. From the perspective of
+Features, there are a few different ways that modules store their configuration:
+
+- In the `variable` table using `variable_set()`. If a module is using variables
+for storing configuration, these variable settings can be exported with Features
+by using the [Strongarm][1] module.
+
+ **Features integration:** Install the Strongarm module.
+
+- Using a custom table with a serial ID for identifying configuration objects.
+If this is the case, you will need to change your schema to use a string
+identifier / machine name for each object.
+
+ **Features integration:** Fix your schema first, then see below.
+
+- Using a custom table with a machine name identifier and custom exportables
+handling (e.g. you have your own defaults hook handling and export generation).
+If this is the case, you will need to implement many of the features hooks
+yourself.
+
+ **Features integration:** `hook_features_api()`, `hook_features_export()`,
+`hook_features_export_render()`, `hook_features_export_options()`,
+`hook_features_revert()`.
+
+- Using a custom table with CTools Export API integration. If this is the case,
+Features will automatically have integration with your module. You can implement
+any of the Features hooks in order to override the default CTools exportables
+integration behavior.
+
+ **Features integration:** Automatically provided. You may implement any of the
+Features hooks where you need further customization for your configuration
+object.
+
+If it isn't clear by now, we highly recommend using the [CTools][2] Export API
+for adding exportables to your module. Stella has written a [fantastic HOWTO][3]
+on using the CTools Export API that can get you started.
+
+
+An overview of Features hooks
+-----------------------------
+Extensive documentation of the hooks provided by Features is available in
+`features.api.php`. This section provides a short overview of each hook and its
+role.
+
+- `hook_features_api()` defines one or more component types that are available
+to Features for export and a variety of settings for each type.
+- `hook_features_export()` processes a list of components, detecting any
+dependencies or further components
+- `hook_features_export_options()` provides an array of components that can be
+exported for a given type.
+- `hook_features_export_render()` renders a set of components to code as a
+defaults hook.
+- `hook_features_revert()` reverts components of a feature back to their default
+state.
+- `hook_features_rebuild()` updates faux-exportable components back to their
+default state. Only applies to faux-exportables.
+
+
+[1]: http://drupal.org/project/strongarm
+[2]: http://drupal.org/project/ctools
+[3]: http://civicactions.com/blog/2009/jul/24/using_chaos_tools_module_create_exportables
diff --git a/docroot/sites/all/modules/features/CHANGELOG.txt b/docroot/sites/all/modules/features/CHANGELOG.txt
new file mode 100644
index 0000000..fcbfb4e
--- /dev/null
+++ b/docroot/sites/all/modules/features/CHANGELOG.txt
@@ -0,0 +1,221 @@
+features DRUPAL-7--1-0
+----------------------
+
+#1647894 by tim.plunkett: Fixed Features with page manager components are perpetually overridden.
+#1635662 by donquixote: Fixed Undefined index: feature in features_get_info().
+Add missing taxonomy test file
+
+features DRUPAL-7--1-0-RC3
+--------------------------
+
+#1429262 by joelcollinsdc, neochief: Added Allow component level reverting (without needing to go to interactive mode) in drush fr.
+#1272586 by alexweber | stevector: Added Increment or directly set version number directly with drush features-update.
+#1532422 by nadavoid: Fixed If the custom sort order of 'format_handlers()' is not maintained, some things can break.
+#1564864 by jeffschuler: Fixed Mixup in hook_features_export_render() example.
+#1567506 by Dave Reid: Fixed Unable to properly export or provide a locked field in a feature.
+#1597792 by jessehs: Make sure all comments in created features end period.
+#1599188 by jessehs: Move inline comment for empty feature .module file to @file doc block.
+#1587200 by ericduran: Fixed Test module should be hidden.
+#1574716 by bojanz: Fixed Avoid unnecessary cache rebuilds when creating and updating fields.
+#1530386 by exratione, bojanz, kotnik: Avoid unnecessary cache rebuilds and improve installation performance.
+#1489480 by Xen | dema502: Fixed drush:features-export Ambiguous component.
+#1159390 by hefox | brad.bulger: Fixed incorrect revert hook name in features.api.php?.
+#1493274 by hefox, neochief: Fixed Feature installed before Strongarm blocks all other variable imports.
+#1537838 by hefox, JvE | Gisle: Fixed Upgrading to 7.x-1.0-rc2 (from rc1) breaks taxonomy creation.
+#1447656 by bcmiller0, smk-ka: Added drush features-revert-all performance improvement.
+
+features DRUPAL-7--1-0-RC2
+--------------------------
+
+#1529202 by mpotter, mstrelan: Fixed Unable to revert overrides because clear-block is now clearfix.
+#1505044 by neochief: Fixed features_var_export() adds additional prefix to multiline strings contained in objects.
+#1510710 by neochief: Fixed Incorrect key in features_include_defaults() with reset parameters.
+#1402262 by hefox: Fixed features.api.php mentions 'features_source()' key, but the actual key in use is 'feature_source()'.
+#939254 by hefox, fago: Added Return the () to hook_features_pipe_component_alter().
+#894572 by pearcec, eporama, Grayside, andrewlevine: Added Features with only module dependencies not recognized as features.
+#1231118 by tim.plunkett, jhedstrom, nielsonm, arnested: Fixed Generate code according to coding standards.
+#1493274 by neochief, hefox: Fixed Feature installed before Strongarm blocks all other variable imports.
+#1287594 by tim.plunkett: Don't wrap .info file values in double quotes.
+#1437388 by hefox: Fixed check if file exists before including it in features_include().
+#1496322 by hefox, dajjen: Added Make package autocomplete search string case insensitive .
+#1279938 by derhasi, helmo: Added Features support for languages.
+#1305048 by mongolito404: Fixed Undefined index error in user_permission_features_export_render().
+#1063204 by careernerd, davidwhthomas, hefox | mrfree: Fixed Adding a new permission causes integrity constraint violation.
+#1432264 by ezra-g, tim.plunkett | samhassell: Fixed Changes to hook_views_api() cause Views plugins to be undefined.
+Revert "Issue #1432264 by ezra-g, tim.plunkett | samhassell: Fixed Changes to hook_views_api() cause Views plugins to be undefined."
+
+features DRUPAL-7--1-0-RC1
+--------------------------
+
+#1331278 by Xen, tim.plunkett, joelcollinsdc, mpotter: Drush features-add and features-export are confusingly simi
+#1475780 by rggoode: Added Prevent starting features machine name starting with numeric.
+#1402826 by mpotter: Added Make 'Checking...' a link.
+#1478808 by mpotter: Added Why is Description field required?.
+#1279938 by derhasi, helmo: Added Features support for languages.
+#1009900 by DamienMcKenna: Check if a variable is empty before comparing it to a known value.
+#1426452 by mpotter, rypit: Added Conflicts with disabled modules should be colored differently.
+#984472 by hefox, goron, Sarenc, mpotter: Added hook_node_info_alter(), and alter_type(), alter_hook() to features
+#1478808 by mpotter: Added Why is Description field required?.
+#1479068 by mpotter: Added Allow exportable code written to different files.
+#1058778 by dajjen | nymo: Added ability to edit package of feature when creating/editing features via the UI.
+#1429408 by joelcollinsdc: Fixed Diff page does not show component titles.
+#1264826 by Volx, hefox | mortendk: Fixed revert views calls views delete() & throws a fatal error .
+#1432264 by ezra-g, tim.plunkett | samhassell: Fixed Changes to hook_views_api() cause Views plugins to be undefin
+#1479068 by mpotter: Added Allow exportable code written to different files.
+#1437370 by hefox: Fixed . is being mapped to ord(',') instead of ord('.') in features_dom_encode_options()/featur
+Added a README section about security concerns
+Updated some docs, specifically the maintainers
+#1231118 by jhedstrom: Generate code according to coding standards when using include_once.
+#1305194 by hefox,Xen,mpotter: Provide support for exporting of altering of components
+#1305194 by smk-ka,jief: Taxonomy reference field export recursion
+#1390848: Fixing a slight bug from the previous patch that lead to wrong components being shown as being able to b
+#1390848 by hefox: Centralize the call to hook_features_api()
+Updated the features_test Feature
+#1399440 by hefox: Fixed Feature tests failure on image against drupal 7.10.
+
+features DRUPAL-7--1-0-BETA6
+----------------------------
+
+#1382156 by tim.plunkett | davidn: Fixed PHP Fatal error: Call to undefined function ctools_features_declare_funct
+
+features DRUPAL-7--1-0-BETA5
+----------------------------
+
+#1363284 by tim.plunkett, mpotter: Fixed Drush --force option is declared wrong.
+#1079440 by mpotter, mrf, tobby, acrollet, donquixote: Fixed Module name check prevents panels custom content pane
+#1152908 by greg.1.anderson, msonnabaum, smk-ka, rbayliss: Fixed Remove calls to drush_backend_invoke()
+#813760 by galooph, tim.plunkett, Volx, raphaelhuefner | greg.harvey: Fixed CTools Page manager pages do not rever
+#904558 by hefox, voxpelli, Raines37 | loze: Fixed Multiple features and strongarm conflicts.
+#1170846 by Ravi.J: Fixed Disabling a feature does not disable dependent modules.
+#1313744 by TravisCarden: Fixed Sort 'Edit components' SELECT alphabetically.
+Made the Features Test not hidden, b/c if it is hidden it wont enable/install the content type
+#1300780 by Dave Reid: Added Provide an actual hook_features_pipe_alter() for general altering.
+#1178884 by YaderV | altfuns: Fixed Wrong word (with) in a description text.
+#893360 by Xen, Raines37, Grayside, franz: Added Drush update/recreate (add components).
+#913890 by Grayside, joshuajabbour, tim.plunkett | webchick: Added Generate .info file properties in order consist
+#1288028 by DamienMcKenna: Fixed D7 branch refers to D6 CCK hook.
+#1187232 by Ravi.J, rickvug: Fixed Hidden and disabled features should not be displayed in UI and should not be co
+
+features DRUPAL-7--1-0-BETA4
+----------------------------
+
+Fixing naming conflict b/w hook_enable and hook_disable
+#1187858 by smk-ka, febbraro: Fixed Node permissions fail to import.
+#1191558 by clemens.tolboom, Raines37: Allow for drush --destination while exporting a feature.
+#1188066 by smk-ka, febbraro: Fixed Excessive rebuilding if installing more than one feature at a time.
+#1260040 by tim.plunkett | hefox: Fixed features.css does not follow coding standards.
+#1195432: Fixing admin screen regression
+#1195432 by smk-ka: Use dependency information when importing
+#1186874 by dixon_: Better support for switching field storage types
+#1175684 by sagannotcarl, rocket.nova: Add legend/help to features-diff
+#1210604 by catch: drush features-revert-all does not respect --force argument
+#954062 by irakli, hefox: Incorrect Component Labels in the Components Dropdown
+#1186694 by tunic, fearlsgroove: Features orders exportes recursivally; fields allowed_values array alphabetized,
+#935152 by adamdicarlo, hadsie, hefox: Menu Items missing from Menu Links due to access callback user_is_anonymous
+#1258072 by hefox: Add suggestion for vocabulary when exporting a taxonomy field
+#1279212: add Features Breadcrumb
+Fixed the display of conflicting features. It was borked big time.
+#1219932 bu acrollet, t-dub, Nico Heulsen: Problems when dependencies have version numbers in them
+#1055460 by nedjo, hefox, fabsor, others...: Disabling a feature does not allow user to delete content type
+#994602 by franz: Misleading example on hook_features_export_alter() documentation
+#1264462 by Dave Reid, skwashd: template_preprocess_features_admin_components() calls D6-style theme() arguments
+#1131062: scripts/stylesheets manually added to info file have the path broken
+#946068 by rvilar, hefox: The machine name isn't updating correctly
+#1079440 by mrf, DamienMcKenna, dereine: Allow export of Custom Content Panes
+#1231118 by arnested, tim.plunkett: Coder fixes
+
+features DRUPAL-7--1-0-BETA3
+----------------------------
+
+#1177582 by paulsheldrake: Missing ctools plugin include
+#1191670: Undefined FEATURES_COMPONENT_NOT_FOUND in features.drush.inc
+#1157048 by dixon_: Features appear overridden
+#1124422 by anantagati: Wrong links in README
+#932104 by aaronbauman, hefox, etc.: Improve help for feature export drush command
+#1094940 by webflo: Features unit test are broken
+#1134202: Overridden views not reverting
+#1097560 by nedjo: Features incompatible with latest version of views
+
+features DRUPAL-7--1-0-BETA2
+----------------------------
+
+#1111714 by paranojik: _features_restore() does not properly revert components.
+#1070912 by bdragon: features_get_info() hands back original objects, can get corrupted.
+#1073988 by mikewink: File formats should be labeled text formats for consistency.
+removing unnecessary and typo-including check on plugin_api_hook_name
+removed duplicate ctools_plugin_api_get_hook function since new ctools is now released and it's unnecessary
+#1097560 by floretan, quartsize, merlinofchaos: Features incompatible with the latest version of views.
+Fixing unit test for permissions that broke as a result of #1063204
+Fixed the sanitize as it was incorrectly misidentifying a one item associative array as non-associative.
+Merge branch '7.x-1.x' of git.drupal.org:project/features into 7.x-1.x
+#1063204 by mrfree, careernerd, hadsie, irakli: Adding a new user role causes integrity constraing violation.
+Fixed E_STRICT warnings
+Merge branch '7.x-1.x' of git.drupal.org:project/features into 7.x-1.x
+#973836 by fmitchell, Grayside: Add @file to the generated files
+#1078972 by bdragon: Quoting problems on key side.
+Removing translation directories
+Stripping CVS keywords
+
+features DRUPAL-7--1-0-BETA1
+----------------------------
+
+#993314 by das-peter: Notice 'undefined index' in features_export_build_form_populate.
+adding warning @TODO to note an edge case that is not covered by existing code when checking if .module includes feature
+#1053336 by tema: Translatables are not exported for fields.
+#970788 by mori, das_peter, steinmb: Notice: Undefined index: api in _ctools_features_get_info() // error from featu
+#1062526 by fago, dasjo: components appear always overriden if the default hook makes use of hook-group.
+#954536 by klausi: Unsetting ids on vocabulary object breaks Entity Cache.
+#1014066 by brenk28: Improper use of t().
+#994122 by hadsie: Feature conflicts report displays conflicting features as Array instead of proper feature name.
+#953236 by isolesen, grobot : Make the Manage Features page more descriptive when there are no features.
+cleaning up previous somewhat extraneous commit and making first attempt towards fixing: #986118. Full fix of that one s
+#944584 by Rok Žlender, pcambra: Features diff in drush is broken.
+#1056422 by EclipseGc: Attribute Classes are Arrays in D7.
+Follow up to previous commit, actually ordering array correctly.
+Fix tab rendering on admin/build/features.
+Updated the filter test
+Utilize filter format machine names which core now provides.
+
+features DRUPAL-7--1-0-ALPHA3
+-----------------------------
+
+Update filter component to make use of machine names if available.
+PHP declaration.
+Update features_test field export to match changes in core.
+Several test fixes.
+Use features_get_default() rather than module_invoke().
+Remove old schema check.
+#925628 by thill_: Fix example version strings.
+#925332 by joshuajabbour: Cleaner export render for ctools components.
+
+features DRUPAL-7--1-0-ALPHA2
+-----------------------------
+
+Update features.api.php.
+#919500 by fago: Enable generic feature implementations. Introduces 'base' key in hook_features_api().
+#912132 by das-peter, fago: Implement hook_hook_info() entry for 'features_api'.
+Properly support 'hidden' key in D7.
+Fix function signature of features_export_prepare().
+Fix for menu links export options.
+Exclude bundles info from field config.
+Exclude 'module' key from image style export.
+Prevent features from going haywire when attempting to detect conflict with components it doesn't know about.
+Sorting field definitions before exporting.
+Removing non-class files from files[] in fino.
+Revert addition of files array to features.
+#912018 by das-peter: Fix for duplicate files[] entries.
+Remove unneeded context features hooks.
+Fix features_get_orphans().
+#908390: Generate files[] array for features.
+
+features DRUPAL-7--1-0-ALPHA1
+-----------------------------
+
+D7: Update image styles component.
+D7: Update hook_permission().
+D7: Remove _features_get_roles().
+D7: user_role and user_permission component upgrades.
+D7: Update list of component includes.
+D7: Filter format API updates.
+D7: Fix for diff integration.
+D7 fixes for menu_custom and menu_links components.
diff --git a/docroot/sites/all/modules/features/LICENSE.txt b/docroot/sites/all/modules/features/LICENSE.txt
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/docroot/sites/all/modules/features/LICENSE.txt
@@ -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.
+
+
+ Copyright (C)
+
+ 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.
+
+ , 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.
diff --git a/docroot/sites/all/modules/features/README.txt b/docroot/sites/all/modules/features/README.txt
new file mode 100644
index 0000000..71bf938
--- /dev/null
+++ b/docroot/sites/all/modules/features/README.txt
@@ -0,0 +1,236 @@
+
+Current state of Features for Drupal 7
+--------------------------------------
+Work on Features for D7 is currently aimed at getting to a point where Features
+can be used on a new install of Drupal 7 with features that were created on D7.
+Once this has been achieved, we will begin working on supporting D6 features as
+well as possibly supporting upgrades & migrations between legacy components and
+new equivalents (e.g. CCK to fields, imagecache to core image styles).
+
+### Working components
+
+- ctools
+- dependencies
+- field
+- filter
+- image
+- menu_custom
+- menu_links
+- node
+- taxonomy
+- user_permission
+- user_role
+- views
+
+### Has changes to export format between D6 and D7
+
+(@TODO legacy export compatibility)
+
+- filter
+- taxonomy
+
+### Requires upgrade/migration path
+
+- imagecache > image
+- content > field
+
+Note on the "Generate Feature" capability
+-----------------------------------------
+Features 7.x-2.x includes the ability to "Generate a feature" which saves it
+to the server disk. This can be a time-saving task in development. It requires
+the webserver to be able to write to the very code running the site and is
+not recommended for any environment other than a firewalled-off, local
+development environment (e.g. a person working alone on their laptop).
+
+Features 1.x for Drupal 7.x
+---------------------------
+The features module enables the capture and management of features in Drupal. A
+feature is a collection of Drupal entities which taken together satisfy a
+certain use-case.
+
+Features provides a UI and API for taking different site building components
+from modules with exportables and bundling them together in a single feature
+module. A feature module is like any other Drupal module except that it declares
+its components (e.g. views, contexts, CCK fields, etc.) in its `.info` file so
+that it can be checked, updated, or reverted programmatically.
+
+Examples of features might be:
+
+- A blog
+- A pressroom
+- An image gallery
+- An e-commerce t-shirt store
+
+
+Installation
+------------
+Features can be installed like any other Drupal module -- place it in the
+modules directory for your site and enable it on the `admin/build/modules` page.
+To take full advantage of some of the workflow benefits provided by Features,
+you should install [Drush][1].
+
+If you plan on creating or working with very large features (greater than 1000
+items), you may need to increase PHP's max_input_vars configuration directive.
+For example, adding the following line to your .htaccess file will increase the
+max_input_vars directive to 3000:
+
+php_value max_input_vars 3000
+
+If you are using Suhosin, increasing suhosin.get.max_vars,
+suhosin.post.max_vars, and suhosin.request.max_vars may also be necessary.
+
+
+Basic usage
+-----------
+Features is geared toward usage by developers and site builders. It
+is not intended to be used by the general audience of your Drupal site.
+Features provides tools for accomplishing two important tasks:
+
+### Task 1: Export features
+
+You can build features in Drupal by using site building tools that are supported
+(see a short list under the *Compatibility* section).
+
+Once you've built and configured functionality on a site, you can export it into
+a feature module by using the feature create page at
+`admin/structure/features/create`.
+
+
+### Task 2: Manage features
+
+The features module also provides a way to manage features through a more
+targeted interface than `admin/modules`. The interface at
+`admin/structure/features` shows you only feature modules, and will also inform you
+if any of their components have been overridden. If this is the case, you can
+also re-create features to bring the module code up to date with any changes
+that have occurred in the database.
+
+
+Including custom code and adding to your feature
+------------------------------------------------
+Once you've exported your feature you will see that you have several files:
+
+ myfeature.info
+ myfeature.module
+ myfeature.[*].inc
+
+You can add custom code (e.g. custom hook implementations, other functionality,
+etc.) to your feature in `myfeature.module` as you would with any other module.
+Do not change or add to any of the features `.inc` files unless you know what
+you are doing. These files are written to by features on updates so any custom
+changes may be overwritten.
+
+
+Using Features to manage development
+------------------------------------
+Because Features provides a centralized way to manage exportable components and
+write them to code it can be used during development in conjunction with a
+version control like SVN or git as a way to manage changes between development,
+staging and production sites. An example workflow for a developer using Features
+is to:
+
+1. Make configuration changes to a feature on her local development site.
+2. Update her local feature codebase using `drush features-update`.
+3. Commit those changes using `svn commit`.
+4. Roll out her changes to the development site codebase by running `svn update`
+ on the server. Other collaborating developers can also get her changes with
+ `svn update`.
+5. Reverting any configuration on the staging site to match the updated codebase
+by running `drush features-revert`.
+6. Rinse, repeat.
+
+Features also provides integration with the [Diff][3] module if enabled to show
+differences between configuration in the database and that in code. For site
+builders interested in using Features for development, enabling the diff module
+and reading `API.txt` for more details on the inner workings of Features is
+highly recommended.
+
+
+Drush usage
+-----------
+(requires Drush v4.5 or higher)
+
+Features provides several useful drush commands:
+
+- `drush features`
+
+ List all the available features on your site and their status.
+
+- `drush features-export [feature name] [component list]`
+
+ Write a new feature in code containing the components listed.
+ If called with no arguments, display a list of available components.
+ If called with one argument, take the argument as a component name and
+ attempt to create a feature with the same name.
+
+ The option '--destination=foo' may be used to specify the path (from Drupal
+ root) where the feature should be created. The default destination is
+ 'sites/all/modules', though this can be overridden via the Features
+ settings page.
+
+- `drush features-update [feature name]`
+
+ Update the code of an existing feature to include any overrides/changes in
+ your database (e.g. a new view).
+
+- `drush features-revert [feature name]`
+
+ Revert the components of a feature in your site's database to the state
+ described in your feature module's defaults.
+
+- `drush features-diff [feature name]`
+
+ Show a diff between a feature's database components and those in code.
+ Requires the Diff module.
+
+Additional commands and options can be found using `drush help`.
+
+
+Compatibility
+-------------
+Features provides integration for the following exportables:
+
+- CTools export API implementers (Context, Spaces, Boxes, Strongarm, Page
+ Manager)
+- ImageCache
+- Views
+- [Other contributed modules][2]
+
+Features also provides faux-exportable functionality for the following Drupal
+core and contrib components:
+
+- Fields
+- Content types
+- Input filters
+- User roles/permissions
+- Custom menus and menu links *
+- Taxonomy vocabularies
+
+* Currently in development.
+
+
+Security Concerns
+-----------------
+If you are using Features to export Roles and also use those Roles in other
+exportable code (like Views filters) you can wind up with an unintended
+security hole. When you import your Feature, if the Roles do not get created
+with the exact same Role IDs then your Views filters (or other component) will
+be referencing a different Role than you intended.
+
+
+For developers
+--------------
+Please read `API.txt` for more information about the concepts and integration
+points in the Features module.
+
+
+Maintainers
+-----------
+- febbraro (Frank Febbraro)
+- hefox (Fox)
+- mpotter (Mike Potter)
+- timplunkett (Tim Plunkett)
+
+
+[1]: http://drupal.org/project/drush
+[2]: (http://drupal.org/taxonomy/term/11478)
diff --git a/docroot/sites/all/modules/features/features.admin.inc b/docroot/sites/all/modules/features/features.admin.inc
new file mode 100644
index 0000000..8360e97
--- /dev/null
+++ b/docroot/sites/all/modules/features/features.admin.inc
@@ -0,0 +1,1652 @@
+ 'fieldset',
+ '#title' => t('Show components on create/edit feature form.'),
+ '#description' => t('Components with no options will not be shown no matter the setting below. Disabled components cannot be used with admin form.')
+ );
+
+ $form['lock_components'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Lock components'),
+ '#description' => t('Locked components will be prevented from ever being reverted. For example, if site builder updates a feature with new settings for a field instance, but field instance is locked, it will not update that field. If the item is purely in code, like a view, the view changed when the code is updated no matter these settings.')
+ );
+ $form['features_lock_mode'] = array(
+ '#type' => 'radios',
+ '#title' => t('Features lock mode'),
+ '#options' => array(
+ 'rebuild' => t('Allow rebuild (prevent revert)'),
+ 'all' => t('Prevent rebuild and revert'),
+ ),
+ '#description' => t('Rebuild will allow the feature to be updated till the point features has detected that the item has changed deliberately on the site, e.g. is overriden.'),
+ '#default_value' => variable_get('features_lock_mode', 'all'),
+ );
+ foreach ($components as $component => $info) {
+ if (empty($info['feature_source']) && empty($info['features_source'])) {
+ continue;
+ }
+ $form['show_components']['features_admin_show_component_' . $component] = array(
+ '#title' => t('@name (@machine)', array('@name' => $info['name'], '@machine' => $component)),
+ '#type' => 'checkbox',
+ '#default_value' => variable_get('features_admin_show_component_' . $component, TRUE),
+ );
+ if (features_hook($component, 'features_revert') || features_hook($component, 'features_rebuild')) {
+ $form['lock_components']['features_component_locked_' . $component] = array(
+ '#title' => t('@name (@machine)', array('@name' => $info['name'], '@machine' => $component)),
+ '#type' => 'checkbox',
+ '#default_value' => variable_get('features_component_locked_' . $component, FALSE),
+ );
+ }
+ if ($component == 'menu_links' && ($menus = menu_get_menus())) {
+ $form['show_components']['features_admin_menu_links'] = array(
+ '#title' => t('Advanced Menu Link Settings'),
+ '#type' => 'fieldset',
+ '#collapsed' => TRUE,
+ '#collapsible' => TRUE,
+ '#states' => array(
+ 'invisible' => array(
+ 'input[name="features_admin_show_component_menu_links"]' => array('checked' => FALSE),
+ ),
+ ),
+ );
+ $form['show_components']['features_admin_menu_links']['features_admin_menu_links_menus'] = array(
+ '#title' => t('Allowed menus for menu links'),
+ '#type' => 'checkboxes',
+ '#options' => array_map('check_plain', $menus),
+ '#default_value' => variable_get('features_admin_menu_links_menus', array_keys(menu_get_menus())),
+ );
+ }
+ }
+
+ $form['general'] = array(
+ '#title' => t('General settings'),
+ '#type' => 'fieldset',
+ );
+ $form['general']['features_default_export_path'] = array(
+ '#title' => t('Default export path'),
+ '#type' => 'textfield',
+ '#default_value' => variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH),
+ '#description' => t('All feature exports will be automatically saved to this path, unless overridden on the individual feature.'),
+ );
+ $form['general']['features_rebuild_on_flush'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Rebuild features on cache clear'),
+ '#default_value' => variable_get('features_rebuild_on_flush', TRUE),
+ '#description' => t('If you have a large site with many features, you may experience lag on full cache clear. If disabled, features will rebuild only when viewing the features list or saving the modules list.'),
+ );
+
+ return system_settings_form($form);
+}
+
+/**
+ * Form constructor for features export form.
+ *
+ * Acts as a router based on the form_state.
+ *
+ * @param object|null $feature
+ * The feature object, if available. NULL by default.
+ *
+ * @see features_export_build_form_submit()
+ * @ingroup forms
+ */
+function features_export_form($form, $form_state, $feature = NULL) {
+ module_load_include('inc', 'features', 'features.export');
+ features_include();
+
+ $feature_name = !empty($feature->name) ? $feature->name : '';
+ $form = array(
+ '#attributes' => array('class' => array('features-export-form')),
+ '#feature' => isset($feature) ? $feature : NULL,
+ );
+ $form['info'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('General Information'),
+ '#tree' => FALSE,
+ '#weight' => 2,
+ '#collapsible' => FALSE,
+ '#collapsed' => FALSE,
+ '#prefix' => "",
+ '#suffix' => '
',
+ );
+ $form['info']['name'] = array(
+ '#title' => t('Name'),
+ '#description' => t('Example: Image gallery') . ' (' . t('Do not begin name with numbers.') . ')',
+ '#type' => 'textfield',
+ '#default_value' => !empty($feature->info['name']) ? $feature->info['name'] : '',
+ );
+ $form['info']['module_name'] = array(
+ '#type' => 'machine_name',
+ '#title' => t('Machine-readable name'),
+ '#description' => t('Example: image_gallery') . '
' . t('May only contain lowercase letters, numbers and underscores. Try to avoid conflicts with the names of existing Drupal projects.'),
+ '#required' => TRUE,
+ '#default_value' => $feature_name,
+ '#machine_name' => array(
+ 'exists' => 'features_export_form_module_name_exists',
+ 'source' => array('info', 'name'),
+ ),
+ );
+ // If recreating this feature, disable machine name field to ensure the
+ // machine name cannot be changed, unless user role has granted permission to
+ // edit machine name of disabled features.
+ if (isset($feature) && ($feature->status || !user_access('rename features'))) {
+ $form['info']['module_name']['#value'] = $feature_name;
+ $form['info']['module_name']['#disabled'] = TRUE;
+ }
+ $form['info']['description'] = array(
+ '#title' => t('Description'),
+ '#description' => t('Provide a short description of what users should expect when they enable your feature.'),
+ '#type' => 'textfield',
+ '#default_value' => !empty($feature->info['description']) ? $feature->info['description'] : '',
+ );
+ $form['info']['package'] = array(
+ '#title' => t('Package'),
+ '#description' => t('Organize your features in groups.'),
+ '#type' => 'textfield',
+ '#autocomplete_path' => 'features/autocomplete/packages',
+ '#default_value' => !empty($feature->info['package']) ? $feature->info['package'] : 'Features',
+ );
+ $form['info']['version'] = array(
+ '#title' => t('Version'),
+ '#description' => t('Examples: 7.x-1.0, 7.x-1.0-beta1'),
+ '#type' => 'textfield',
+ '#required' => FALSE,
+ '#default_value' => !empty($feature->info['version']) ? $feature->info['version'] : '',
+ '#size' => 30,
+ '#element_validate' => array('features_export_form_validate_field'),
+ );
+ $form['advanced'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Advanced Options'),
+ '#tree' => FALSE,
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#weight' => 10,
+ '#prefix' => "",
+ '#suffix' => '
',
+ );
+ $form['advanced']['project_status_url'] = array(
+ '#title' => t('URL of update XML'),
+ '#description' => t('URL of Feature Server. For Example: http://mywebsite.com/fserver'),
+ '#type' => 'textfield',
+ '#required' => FALSE,
+ '#default_value' => !empty($feature->info['project status url']) ? $feature->info['project status url'] : '',
+ '#element_validate' => array('features_export_form_validate_field'),
+ );
+ $directory = (!empty($feature->filename)) ? dirname($feature->filename) : variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH);
+ if (!empty($feature_name) && substr_compare($directory, $feature_name, strlen($directory)-strlen($feature_name), strlen($feature_name)) === 0) {
+ // if path ends with module_name, strip it
+ $directory = dirname($directory);
+ }
+ if (user_access('generate features')) {
+ $form['advanced']['generate_path'] = array(
+ '#title' => t('Path to Generate feature module'),
+ '#description' => t('File path for feature module. For Example: sites/all/modules/features or /tmp. ' .
+ t('Leave blank for @path', array('@path' => $directory))),
+ '#type' => 'textfield',
+ '#required' => FALSE,
+ '#default_value' => !empty($feature->info['project path']) ? $feature->info['project path'] : '',
+ );
+ $form['advanced']['generate'] = array(
+ '#type' => 'submit',
+ '#value' => t('Generate feature'),
+ '#submit' => array('features_export_build_form_submit', 'features_form_rebuild'),
+ );
+ }
+ // build the Component Listing panel on the right
+ _features_export_form_components($form, $form_state);
+
+ $form['advanced']['info-preview'] = array(
+ '#type' => 'button',
+ '#value' => t('Preview .info file'),
+ '#ajax' => array(
+ 'callback' => 'features_info_file_preview',
+ 'wrapper' => 'features-export-wrapper',
+ ),
+ );
+ //Info dialog
+ $form['advanced']['info-file'] = array(
+ '#prefix' => '',
+ 'text' => array(
+ '#type' => 'textarea',
+ '#default_value' => '',
+ '#resizable' => FALSE,
+ ),
+ '#suffix' => '
',
+ );
+
+ $form['buttons'] = array(
+ '#theme' => 'features_form_buttons',
+ '#tree' => FALSE,
+ '#weight' => 99,
+ '#prefix' => "",
+ '#suffix' => '
',
+ );
+ $form['buttons']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Download feature'),
+ '#weight' => 10,
+ '#submit' => array('features_export_build_form_submit', 'features_form_rebuild'),
+ );
+
+ $form['#attached']['library'][] = array('system', 'ui.dialog');
+
+ return $form;
+}
+
+/**
+ * Machine name existence callback for the module name.
+ */
+function features_export_form_module_name_exists($value) {
+ return (bool) features_get_info('module', $value);
+}
+
+/**
+ * Return the render array elements for the Components selection on the Export form
+ * @param array $feature - feature associative array
+ * @param array $components - array of components in feature
+ */
+function _features_export_form_components(&$form, &$form_state) {
+ global $features_ignore_conflicts;
+ drupal_add_css(drupal_get_path('module', 'features') . '/features.css');
+ drupal_add_js(drupal_get_path('module', 'features') . '/features.js');
+
+ $feature = $form['#feature'];
+
+ // keep the allow_conflict variable around in the session
+ if (isset($form_state['values']['features_allow_conflicts'])) {
+ $_SESSION['features_allow_conflicts'] = $form_state['values']['features_allow_conflicts'];
+ $features_ignore_conflicts = $_SESSION['features_allow_conflicts'];
+ }
+
+ $form['export'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Components'),
+ '#description' => t('Expand each component section and select which items should be included in this feature export.'),
+ '#tree' => FALSE,
+ '#prefix' => "",
+ '#suffix' => '
',
+ '#collapsible' => FALSE,
+ '#collapsed' => FALSE,
+ '#weight' => 1,
+ );
+
+ // filter field used in javascript, so javascript will unhide it
+ $form['export']['features_filter_wrapper'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Filters'),
+ '#tree' => FALSE,
+ '#prefix' => "",
+ '#suffix' => '
',
+ '#collapsible' => FALSE,
+ '#collapsed' => FALSE,
+ '#weight' => -10,
+ );
+ $form['export']['features_filter_wrapper']['features_filter'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Search'),
+ '#hidden' => TRUE,
+ '#default_value' => '',
+ '#suffix' => "". t('Clear') ."",
+ );
+ $form['export']['features_filter_wrapper']['checkall'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => FALSE,
+ '#hidden' => TRUE,
+ '#title' => t('Select all'),
+ '#attributes' => array(
+ 'class' => array('features-checkall'),
+ )
+ );
+
+ $form['advanced']['features_autodetect_wrapper'] = array(
+ '#type' => 'fieldset',
+ '#tree' => FALSE,
+ '#prefix' => "",
+ '#suffix' => '
',
+ '#collapsible' => FALSE,
+ '#collapsed' => FALSE,
+ );
+ $form['advanced']['features_autodetect_wrapper']['autodetect'] = array(
+ '#title' => t('Add auto-detected dependencies'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($feature->info['no autodetect']) ? FALSE : TRUE,
+ );
+
+ // this refresh button will rebuild the form.
+ // this button is hidden by javascript since it is only needed when
+ // javascript is not available
+ $form['advanced']['features_autodetect_wrapper']['features_refresh'] = array(
+ '#type' => 'submit',
+ '#value' => t('Refresh'),
+ '#name' => 'features-refresh',
+ '#attributes' => array(
+ 'title' => t("Refresh the list of auto-detected items."),
+ 'class' => array('features-refresh-button'),
+ ),
+ '#submit' => array('features_export_form_rebuild'),
+ '#prefix' => "",
+ '#suffix' => "
",
+ '#ajax' => array(
+ 'callback' => 'features_export_form_ajax',
+ 'wrapper' => 'features-export-wrapper',
+ ),
+ );
+
+ // generate the export array for the current feature and user selections
+ $export = _features_export_build($feature, $form_state);
+
+ $form['advanced']['features_allow_conflicts'] = array(
+ '#title' => t('Allow conflicts to be added'),
+ '#type' => 'checkbox',
+ '#default_value' => $features_ignore_conflicts,
+ '#ajax' => array(
+ 'callback' => 'features_export_form_ajax',
+ 'wrapper' => 'features-export-wrapper',
+ ),
+ );
+
+ if (isset($form_state['values']['op']) && ($form_state['values']['op'] == $form_state['values']['info-preview'])) {
+ // handle clicking Preview button
+ module_load_include('inc', 'features', 'features.export');
+
+ $feature_export = _features_export_generate($export, $form_state, $feature);
+ $feature_export = features_export_prepare($feature_export, $feature->name, TRUE);
+ $info = features_export_info($feature_export);
+
+ drupal_add_js(array('features' => array('info' => $info)), 'setting');
+ }
+
+ // determine any components that are deprecated
+ $deprecated = features_get_deprecated($export['components']);
+
+ $sections = array('included', 'detected', 'added');
+ foreach ($export['components'] as $component => $component_info) {
+ if (!variable_get('features_admin_show_component_' . $component, TRUE)) {
+ continue;
+ }
+
+ $component_items_count = count($component_info['options']['sources']);
+ $count_label = ' (' . $component_items_count . ')';
+ $label = (isset($component_info['name']) ?
+ $component_info['name'] . $count_label . " (" . check_plain($component) . ")"
+ : check_plain($component) . $count_label);
+
+ $count = 0;
+ foreach ($sections as $section) {
+ $count += count($component_info['options'][$section]);
+ }
+ $extra_class = ($count == 0) ? 'features-export-empty' : '';
+ $component_name = str_replace('_', '-', check_plain($component));
+
+ if ($count + $component_items_count > 0) {
+
+ if (!empty($deprecated[$component])) {
+ // only show deprecated component if it has some exports
+ if (!empty($component_info['options']['included'])) {
+ $form['export'][$component] = array(
+ '#markup' => '',
+ '#tree' => TRUE,
+ );
+
+ $form['export'][$component]['deprecated'] = array(
+ '#type' => 'fieldset',
+ '#title' => $label . " (" . t('DEPRECATED') . ")",
+ '#tree' => TRUE,
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#attributes' => array('class' => array('features-export-component')),
+ );
+ $list = ' ';
+ foreach ($component_info['options']['included'] as $key) {
+ $list .= "$key";
+ }
+ $form['export'][$component]['deprecated']['selected'] = array(
+ '#prefix' => "",
+ '#markup' => $list,
+ '#suffix' => "
",
+ );
+ }
+ }
+ else {
+ $form['export'][$component] = array(
+ '#markup' => '',
+ '#tree' => TRUE,
+ );
+
+ $form['export'][$component]['sources'] = array(
+ '#type' => 'fieldset',
+ '#title' => $label,
+ '#tree' => TRUE,
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#attributes' => array('class' => array('features-export-component')),
+ '#prefix' => "",
+ );
+ $form['export'][$component]['sources']['selected'] = array(
+ '#type' => 'checkboxes',
+ '#id' => "edit-sources-$component_name",
+ '#options' => features_dom_encode_options($component_info['options']['sources']),
+ '#default_value' => features_dom_encode_options($component_info['selected']['sources'], FALSE),
+ '#attributes' => array(
+ 'class' => array('component-select'),
+ ),
+ );
+
+ foreach ($sections as $section) {
+ $form['export'][$component][$section] = array(
+ '#type' => 'checkboxes',
+ '#options' => !empty($component_info['options'][$section]) ?
+ features_dom_encode_options($component_info['options'][$section]) : array(),
+ '#default_value' => !empty($component_info['selected'][$section]) ?
+ features_dom_encode_options($component_info['selected'][$section], FALSE) : array(),
+ '#attributes' => array('class' => array('component-' . $section)),
+ );
+ }
+ $form['export'][$component][$sections[0]]['#prefix'] =
+ "
';
+ }
+ }
+ }
+ $form['export']['features_legend'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Legend'),
+ '#tree' => FALSE,
+ '#prefix' => "",
+ '#suffix' => '
',
+ '#collapsible' => FALSE,
+ '#collapsed' => FALSE,
+ );
+ $form['export']['features_legend']['legend'] = array(
+ '#markup' =>
+ "Normal " .
+ "Changed " .
+ "Auto detected " .
+ "Conflict ",
+ );
+}
+
+/**
+ * Return the full feature export array based upon user selections in form_state
+ * @param array $feature Feature array to be exported
+ * @param array $form_state Optional form_state information for user selections
+ * can be updated to reflect new selection status
+ * @return array New export array to be exported
+ * array['components'][$component_name] = $component_info
+ * $component_info['options'][$section] is list of available options
+ * $component_info['selected'][$section] is option state TRUE/FALSE
+ * $section = array('sources', included', 'detected', 'added')
+ * sources - options that are available to be added to the feature
+ * included - options that have been previously exported to the feature
+ * detected - options that have been auto-detected
+ * added - newly added options to the feature
+ *
+ * NOTE: This routine gets a bit complex to handle all of the different possible
+ * user checkbox selections and de-selections.
+ * Cases to test:
+ * 1a) uncheck Included item -> mark as Added but unchecked
+ * 1b) re-check unchecked Added item -> return it to Included check item
+ * 2a) check Sources item -> mark as Added and checked
+ * 2b) uncheck Added item -> return it to Sources as unchecked
+ * 3a) uncheck Included item that still exists as auto-detect -> mark as Detected but unchecked
+ * 3b) re-check Detected item -> return it to Included and checked
+ * 4a) check Sources item should also add any auto-detect items as Detected and checked
+ * 4b) uncheck Sources item with auto-detect and auto-detect items should return to Sources and unchecked
+ * 5a) uncheck a Detected item -> refreshing page should keep it as unchecked Detected
+ * 6) when nothing changes, refresh should not change any state
+ * 7) should never see an unchecked Included item
+ */
+function _features_export_build($feature, &$form_state) {
+ global $features_ignore_conflicts;
+ // set a global to effect features_get_component_map when building feature
+ // hate to use a global, but it's just for an admin screen so probably ok
+ if (isset($_SESSION['features_allow_conflicts'])) {
+ $features_ignore_conflicts = $_SESSION['features_allow_conflicts'];
+ }
+
+ $feature_name = isset($feature->name) ? $feature->name : NULL;
+ $conflicts = _features_get_used($feature_name);
+ $reset = FALSE;
+ if (isset($form_state['triggering_element']['#name']) && ($form_state['triggering_element']['#name'] == 'features_allow_conflicts')) {
+ // when clicking the Allow Conflicts button, reset the feature back to it's original state
+ $reset = TRUE;
+ }
+
+ module_load_include('inc', 'features', 'features.export');
+ features_include();
+
+ $components = features_get_components();
+ uasort($components, 'features_compare_component_name');
+
+ // Assemble the combined component list
+ $stub = array();
+ $sections = array('sources', 'included', 'detected', 'added');
+
+ // create a new feature "stub" to populate
+
+ $stub_count = array();
+ foreach ($components as $component => $component_info) {
+ if ($reset) {
+ unset($form_state['values'][$component]);
+ }
+ if (!variable_get('features_admin_show_component_' . $component, TRUE)) {
+ unset($components[$component]);
+ continue;
+ }
+ // User-selected components take precedence.
+ $stub[$component] = array();
+ $stub_count[$component] = 0;
+ // add selected items from Sources checkboxes
+ if (!empty($form_state['values'][$component]['sources']['selected'])) {
+ $stub[$component] = array_merge($stub[$component], features_dom_decode_options(array_filter($form_state['values'][$component]['sources']['selected'])));
+ $stub_count[$component]++;
+ }
+ // add selected items from already Included and newly Added checkboxes
+ foreach (array('included', 'added') as $section) {
+ if (!empty($form_state['values'][$component][$section])) {
+ $stub[$component] = array_merge($stub[$component], features_dom_decode_options(array_filter($form_state['values'][$component][$section])));
+ $stub_count[$component]++;
+ }
+ }
+ // count any detected items
+ if (!empty($form_state['values'][$component]['detected'])) {
+ $stub_count[$component]++;
+ }
+ // Only fallback to an existing feature's values if there are no export options for the component.
+ if ($component == 'dependencies') {
+ if (($stub_count[$component] == 0) && !empty($feature->info['dependencies'])) {
+ $stub[$component] = drupal_map_assoc($feature->info['dependencies']);
+ }
+ }
+ elseif (($stub_count[$component] == 0) && !empty($feature->info['features'][$component])) {
+ $stub[$component] = drupal_map_assoc($feature->info['features'][$component]);
+ }
+ }
+ // Generate new populated feature
+ $export = features_populate(array('features' => $stub, 'dependencies' => $stub['dependencies']), $feature_name);
+
+ // Components that are already exported to feature
+ $exported_features_info = !empty($feature->info['features']) ? $feature->info['features'] : array();
+ $exported_features_info['dependencies'] = !empty($feature->info['dependencies']) ? $feature->info['dependencies'] : array();
+ // Components that should be exported
+ $new_features_info = !empty($export['features']) ? $export['features'] : array();
+ $new_features_info['dependencies'] = !empty($export['dependencies']) ? $export['dependencies'] : array();
+ $excluded = !empty($feature->info['features_exclude']) ? $feature->info['features_exclude'] : array();
+
+ // now fill the $export with categorized sections of component options
+ // based upon user selections and de-selections
+
+ foreach ($components as $component => $component_info) {
+ $component_export = $component_info;
+ foreach ($sections as $section) {
+ $component_export['options'][$section] = array();
+ $component_export['selected'][$section] = array();
+ }
+ $options = features_invoke($component, 'features_export_options');
+ drupal_alter('features_export_options', $options, $component);
+ if (!empty($options)) {
+ $exported_components = !empty($exported_features_info[$component]) ? $exported_features_info[$component] : array();
+ $new_components = !empty($new_features_info[$component]) ? $new_features_info[$component] : array();
+
+ // Find all default components that are not provided by this feature and
+ // strip them out of the possible options.
+ if ($map = features_get_default_map($component)) {
+ foreach ($map as $k => $v) {
+ if (isset($options[$k]) && (!isset($feature->name) || $v !== $feature->name)) {
+ unset($options[$k]);
+ }
+ }
+ }
+ foreach ($options as $key => $value) {
+ // use the $clean_key when accessing $form_state
+ $clean_key = features_dom_encode($key);
+ // if checkbox in Sources is checked, move it to Added section
+ if (!empty($form_state['values'][$component]['sources']['selected'][$clean_key])) {
+ unset($form_state['input'][$component]['sources']['selected'][$clean_key]);
+ $form_state['values'][$component]['sources']['selected'][$clean_key] = FALSE;
+ $form_state['values'][$component]['added'][$clean_key] = 1;
+ $form_state['input'][$component]['added'][$clean_key] = $clean_key;
+ $component_export['options']['added'][$key] = check_plain($value);
+ $component_export['selected']['added'][$key] = $key;
+ }
+ elseif (in_array($key, $new_components)) {
+ // option is in the New exported array
+ if (in_array($key, $exported_components)) {
+ // option was already previously exported
+ // so it's part of the Included checkboxes
+ $section = 'included';
+ $default_value = $key;
+ if ($reset) {
+ // leave it included
+ }
+ // if Included item was un-selected (removed from export $stub)
+ // but was re-detected in the $new_components
+ // means it was an auto-detect that was previously part of the export
+ // and is now de-selected in UI
+ elseif (!empty($form_state['values']) &&
+ (isset($form_state['values'][$component]['included'][$clean_key]) ||
+ empty($form_state['values'][$component]['detected'][$clean_key])) &&
+ empty($stub[$component][$key])) {
+ $section = 'detected';
+ $default_value = FALSE;
+ }
+ // unless it's unchecked in the form, then move it to Newly disabled item
+ elseif (!empty($form_state['values']) &&
+ empty($form_state['values'][$component]['added'][$clean_key]) &&
+ empty($form_state['values'][$component]['detected'][$clean_key]) &&
+ empty($form_state['values'][$component]['included'][$clean_key])) {
+ $section = 'added';
+ $default_value = FALSE;
+ }
+ }
+ else {
+ // option was in New exported array, but NOT in already exported
+ // so it's a user-selected or an auto-detect item
+ $section = 'detected';
+ // check for item explicity excluded
+ if (isset($excluded[$component][$key]) && !isset($form_state['values'][$component]['detected'][$clean_key])) {
+ $default_value = FALSE;
+ }
+ else {
+ $default_value = $key;
+ }
+ // if it's already checked in Added or Sources, leave it in Added as checked
+ if (!empty($form_state['values']) &&
+ (!empty($form_state['values'][$component]['added'][$clean_key]) ||
+ !empty($form_state['values'][$component]['sources']['selected'][$clean_key]))) {
+ $section = 'added';
+ $default_value = $key;
+ }
+ // if it's already been unchecked, leave it unchecked
+ elseif (!empty($form_state['values']) &&
+ empty($form_state['values'][$component]['sources']['selected'][$clean_key]) &&
+ empty($form_state['values'][$component]['detected'][$clean_key]) &&
+ !isset($form_state['values'][$component]['added'][$clean_key])) {
+ $section = 'detected';
+ $default_value = FALSE;
+ }
+ }
+ $component_export['options'][$section][$key] = check_plain($value);
+ $component_export['selected'][$section][$key] = $default_value;
+ // save which dependencies are specifically excluded from auto-detection
+ if (($section == 'detected') && ($default_value === FALSE)) {
+ $excluded[$component][$key] = $key;
+ // remove excluded item from export
+ if ($component == 'dependencies') {
+ unset($export['dependencies'][$key]);
+ }
+ else {
+ unset($export['features'][$component][$key]);
+ }
+ }
+ else {
+ unset($excluded[$component][$key]);
+ }
+ // remove the 'input' and set the 'values' so Drupal stops looking at 'input'
+ if (isset($form_state['values'])) {
+ if (!$default_value) {
+ unset($form_state['input'][$component][$section][$clean_key]);
+ $form_state['values'][$component][$section][$clean_key] = FALSE;
+ }
+ else {
+ $form_state['input'][$component][$section][$clean_key] = $clean_key;
+ $form_state['values'][$component][$section][$clean_key] = 1;
+ }
+ }
+ }
+ else {
+ // option was not part of the new export
+ $added = FALSE;
+ foreach (array('included', 'added') as $section) {
+ // restore any user-selected checkboxes
+ if (!empty($form_state['values'][$component][$section][$clean_key])) {
+ $component_export['options'][$section][$key] = check_plain($value);
+ $component_export['selected'][$section][$key] = $key;
+ $added = TRUE;
+ }
+ }
+ if (!$added) {
+ // if not Included or Added, then put it back in the unchecked Sources checkboxes
+ $component_export['options']['sources'][$key] = check_plain($value);
+ $component_export['selected']['sources'][$key] = FALSE;
+ }
+ }
+ }
+ }
+ $export['components'][$component] = $component_export;
+ }
+ $export['features_exclude'] = $excluded;
+
+ // make excluded list and conflicts available for javascript to pass to our ajax callback
+ drupal_add_js(array('features' => array(
+ 'excluded' => $excluded,
+ 'conflicts' => $conflicts,
+ )), 'setting');
+
+ return $export;
+}
+
+/**
+ * AJAX callback for features_export_form.
+ */
+function features_export_form_ajax($form, &$form_state) {
+ return $form['export'];
+}
+
+/**
+ * Tells the ajax form submission to rebuild form state.
+ */
+function features_export_form_rebuild($form, &$form_state) {
+ $form_state['rebuild'] = TRUE;
+}
+
+function features_export_components_json($feature_name) {
+ module_load_include('inc', 'features', 'features.export');
+ $export = array();
+ if (!empty($_POST['items'])) {
+ $excluded = (!empty($_POST['excluded'])) ? $_POST['excluded'] : array();
+ $stub = array();
+ foreach ($_POST['items'] as $key) {
+ preg_match('/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/', $key, $matches);
+ if (!empty($matches[1]) && !empty($matches[4])) {
+ $component = $matches[1];
+ $item = features_dom_decode($matches[4]);
+ if (empty($stub[$component])) {
+ $stub[$component] = array($item);
+ }
+ else {
+ $stub[$component] = array_merge($stub[$component], array($item));
+ }
+ }
+ }
+
+ $stub['dependencies'] = isset($stub['dependencies']) ? $stub['dependencies'] : array();
+ $export = features_populate(array('features' => $stub, 'dependencies' => $stub['dependencies']), $feature_name);
+ $export['features']['dependencies'] = $export['dependencies'];
+
+ // uncheck any detected item that is in the excluded list
+ foreach ($export['features'] as $component => $value) {
+ foreach ($value as $key => $item) {
+ $clean_key = features_dom_encode($key);
+ if ($key != $clean_key) {
+ // need to move key to a cleankey for javascript
+ $export['features'][$component][$clean_key] = $export['features'][$component][$key];
+ unset($export['features'][$component][$key]);
+ }
+ if (isset($excluded[$component][$key])) {
+ $export['features'][$component][$clean_key] = FALSE;
+ }
+ }
+ }
+ }
+ print drupal_json_encode($export['features']);
+}
+
+/**
+ * AJAX callback to get .info file preview.
+ */
+function features_info_file_preview($form, &$form_state){
+ return $form['export'];
+}
+
+/**
+ * Render API callback: Validates a project field.
+ *
+ * This function is assigned as an #element_validate callback in
+ * features_export_form().
+ */
+function features_export_form_validate_field($element, &$form_state) {
+ switch ($element['#name']) {
+ case 'project_status_url':
+ if (!empty($element['#value']) && !valid_url($element['#value'])) {
+ form_error($element, t('The URL %url is invalid. Please enter a fully-qualified URL, such as http://www.example.com/feed.xml.', array('%url' => $element['#value'])));
+ }
+ break;
+ case 'version':
+ preg_match('/^(?P\d+\.x)-(?P\d+)\.(?P\d+)-?(?P\w+)?$/', $element['#value'], $matches);
+ if (!empty($element['#value']) && !isset($matches['core'], $matches['major'])) {
+ form_error($element, t('Please enter a valid version with core and major version number. Example: @example', array('@example' => '7.x-1.0')));
+ };
+ break;
+ }
+}
+
+/**
+ * Return the $export array to be rendered for the feature export
+ */
+function _features_export_generate($export, $form_state, $feature = NULL) {
+ unset($export['components']); // remove the UI data that we are not saving to disk
+
+ $module_name = $form_state['values']['module_name'];
+ // Directly copy the following attributes from form_state
+ $attr = array('name', 'description', 'package', 'project path');
+ foreach ($attr as $key) {
+ $export[$key] = isset($form_state['values'][$key]) ? $form_state['values'][$key] : NULL;
+ }
+ // Directly copy the following attributes from the original feature
+ $attr = array('scripts' , 'stylesheets');
+ foreach ($attr as $key) {
+ $export[$key] = isset($feature->info[$key]) ? $feature->info[$key] : NULL;
+ }
+ // If either update status-related keys are provided, add a project key
+ // corresponding to the module name.
+ if (!empty($form_state['values']['version']) && !empty($form_state['values']['project_status_url'])) {
+ $export['project'] = $form_state['values']['module_name'];
+ }
+ if (!empty($form_state['values']['version'])) {
+ $export['version'] = $form_state['values']['version'];
+ }
+ if (!empty($form_state['values']['project_status_url'])) {
+ $export['project status url'] = $form_state['values']['project_status_url'];
+ }
+ $export['no autodetect'] = empty($form_state['values']['autodetect']) ? 1 : NULL;
+ $export['project path'] = !empty($form_state['values']['generate_path']) ? $form_state['values']['generate_path'] : NULL;
+ return $export;
+}
+
+/**
+ * Form submission handler for features_export_form().
+ */
+function features_export_build_form_submit($form, &$form_state) {
+ $feature = $form['#feature'];
+ $export = _features_export_build($feature, $form_state);
+ $export = _features_export_generate($export, $form_state, $feature);
+ $generate = ($form_state['values']['op'] == $form_state['values']['generate']);
+ $module_name = $form_state['values']['module_name'];
+
+ if ($generate && !user_access('generate features')) {
+ drupal_set_message(t("No permission for generating features."));
+ return;
+ }
+
+ // Generate download
+ if ($files = features_export_render($export, $module_name, TRUE)) {
+ $filename = (!empty($export['version']) ? "{$module_name}-{$export['version']}" : $module_name) . '.tar';
+
+ if ($generate) {
+ $success = TRUE;
+ $destination = variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH);
+ $directory = (!empty($export['project path'])) ? $export['project path'] . '/' . $module_name :
+ (isset($feature->filename) ? dirname($feature->filename) : $destination . '/' . $module_name);
+ if (!is_dir($directory)) {
+ if (mkdir($directory, 0777, true) === FALSE) {
+ $success = FALSE;
+ }
+ }
+ }
+ else {
+ // Clear out output buffer to remove any garbage from tar output.
+ if (ob_get_level()) {
+ ob_end_clean();
+ }
+
+ drupal_add_http_header('Content-type', 'application/x-tar');
+ drupal_add_http_header('Content-Disposition', 'attachment; filename="'. $filename .'"');
+ drupal_send_headers();
+ }
+
+ $tar = array();
+ $filenames = array();
+ // Copy any files if _files key is there.
+ if (!empty($files['_files'])) {
+ foreach ($files['_files'] as $file_name => $file_info) {
+ if ($generate) {
+ // See if files are in a sub directory.
+ if (strpos($file_name, '/')) {
+ $file_directory = $directory . '/' . substr($file_name, 0, strrpos($file_name, '/'));
+ if (!is_dir($file_directory)) {
+ mkdir($file_directory);
+ }
+ }
+ if (!empty($file_info['file_path'])) {
+ file_unmanaged_copy($file_info['file_path'], "{$directory}/{$file_name}", FILE_EXISTS_REPLACE);
+ }
+ elseif ($file_info['file_content']) {
+ file_put_contents("{$directory}/{$file_name}", $file_info['file_content']);
+ }
+ }
+ else {
+ if (!empty($file_info['file_path'])) {
+ print features_tar_create("{$module_name}/{$file_name}", file_get_contents($file_info['file_path']));
+ }
+ elseif ($file_info['file_content']) {
+ features_tar_create("{$directory}/{$file_name}", $file_info['file_content']);
+ }
+ }
+ }
+ unset($files['_files']);
+ }
+ foreach ($files as $extension => $file_contents) {
+ if (!in_array($extension, array('module', 'info'))) {
+ $extension .= '.inc';
+ }
+ $filenames[] = "{$module_name}.$extension";
+ if ($generate) {
+ if (file_put_contents("{$directory}/{$module_name}.$extension", $file_contents) === FALSE) {
+ $success = FALSE;
+ }
+ }
+ else {
+ print features_tar_create("{$module_name}/{$module_name}.$extension", $file_contents);
+ }
+ }
+ if (features_get_modules($module_name, TRUE)) {
+ // prevent deprecated component files from being included in download
+ $deprecated = features_get_deprecated();
+ foreach ($deprecated as $component) {
+ $info = features_get_components($component);
+ $filename = isset($info['default_file']) && $info['default_file'] == FEATURES_DEFAULTS_CUSTOM ? $info['default_filename'] : "features.{$component}";
+ $filename .= '.inc';
+ $filenames[] = "{$module_name}.$filename";
+ }
+ $module_path = drupal_get_path('module', $module_name);
+ // file_scan_directory() can throw warnings when using PHP 5.3, messing
+ // up the output of our file stream. Suppress errors in this one case in
+ // order to produce valid output.
+ foreach (@file_scan_directory($module_path, '/.*/') as $file) {
+ $filename = substr($file->uri, strlen($module_path) + 1);
+ if (!in_array($filename, $filenames)) {
+ // Add this file.
+ $contents = file_get_contents($file->uri);
+ if ($generate) {
+ if (file_put_contents("{$directory}/{$filename}", $contents) === FALSE) {
+ $success = FALSE;
+ }
+ }
+ else {
+ print features_tar_create("{$module_name}/{$filename}", $contents);
+ }
+ unset($contents);
+ }
+ }
+ }
+ if ($generate) {
+ if ($success) {
+ drupal_set_message(t("Module @name written to @directory",
+ array('@name' => $export['name'], '@directory' => $directory)));
+ }
+ else {
+ drupal_set_message(
+ t("Could not write module to @path. ", array('@path' => $directory)) .
+ t("Ensure your file permissions allow the web server to write to that directory."), "error");
+ }
+ }
+ else {
+ print pack("a1024","");
+ exit;
+ }
+ }
+}
+
+/**
+ * array_filter() callback for excluding hidden modules.
+ */
+function features_filter_hidden($module) {
+ return empty($module->info['hidden']);
+}
+
+/**
+ * Form constructor for the features configuration form.
+ */
+function features_admin_form($form, $form_state) {
+ $features = _features_get_features_list();
+ $modules = array_filter(features_get_modules(), 'features_filter_hidden');
+ $conflicts = features_get_conflicts();
+
+ // Load export functions to use in comparison.
+ module_load_include('inc', 'features', 'features.export');
+
+ if (empty($features) ) {
+ $form['no_features'] = array(
+ '#markup' => t('No Features were found. Please use the !create_link link to create
+ a new Feature module, or upload an existing Feature to your modules directory.',
+ array('!create_link' => l(t('Create Feature'), 'admin/structure/features/create'))),
+ );
+ return $form ;
+ }
+
+ $form = array('#features' => $features);
+
+ // Generate features form. Features are sorted by dependencies, resort alpha
+ ksort($features);
+ foreach ($features as $name => $module) {
+ $package_title = !empty($module->info['package']) ? $module->info['package'] : t('Other');
+ $package = 'package_' . strtolower(preg_replace('/[^a-zA-Z0-9-]+/', '-', $package_title));
+
+ // Set up package elements
+ if (!isset($form[$package])) {
+ $form[$package] = array(
+ '#tree' => FALSE,
+ '#title' => check_plain($package_title),
+ '#theme' => 'features_form_package',
+ '#type' => 'fieldset',
+ '#group' => 'packages',
+ );
+ $form[$package]['links'] =
+ $form[$package]['version'] =
+ $form[$package]['weight'] =
+ $form[$package]['status'] =
+ $form[$package]['action'] = array('#tree' => TRUE);
+ }
+
+ $disabled = FALSE;
+ $description = isset($module->info['description']) ? check_plain($module->info['description']) : '';
+
+ // Detect unmet dependencies
+ if (!empty($module->info['dependencies'])) {
+ $unmet_dependencies = array();
+ $dependencies = _features_export_maximize_dependencies($module->info['dependencies']);
+ foreach ($dependencies as $dependency) {
+ if (empty($modules[$dependency])) {
+ $unmet_dependencies[] = theme('features_module_status', array('status' => FEATURES_MODULE_MISSING, 'module' => $dependency));
+ }
+ }
+ if (!empty($unmet_dependencies)) {
+ $description .= "" . t('Unmet dependencies: !dependencies', array('!dependencies' => implode(', ', $unmet_dependencies))) . "
";
+ $disabled = TRUE;
+ }
+ }
+
+ if (!empty($module->dependents)) {
+ $disabled = TRUE;
+ $description .= "". t('Required by: !dependents', array('!dependents' => implode(', ', $module->dependents))) ."
";
+ }
+
+ // Detect potential conflicts
+ if (!empty($conflicts[$name])) {
+ $module_conflicts = array();
+ foreach ($conflicts[$name] as $conflict => $components) {
+ $component_strings = array();
+ foreach ($components as $component => $component_conflicts) {
+ $component_strings[] = t('@component [@items]', array('@component' => $component, '@items' => implode(', ', $component_conflicts)));
+ }
+ $component_strings = implode(', ', $component_strings);
+ // If conflicting module is disabled, indicate so in feature listing
+ $status = !module_exists($conflict) ? FEATURES_MODULE_DISABLED : FEATURES_MODULE_CONFLICT;
+ $module_conflicts[] = theme('features_module_status', array('status' => $status, 'module' => $conflict)) . t(' in ') . $component_strings;
+ // Only disable modules with conflicts if they are not already enabled.
+ // If they are already enabled, somehow the user got themselves into a
+ // bad situation and they need to be able to disable a conflicted module.
+ if (module_exists($conflict) && !module_exists($name)) {
+ $disabled = TRUE;
+ }
+ }
+ $description .= "". t('Conflicts with: !conflicts', array('!conflicts' => implode(', ', $module_conflicts))) ."
";
+ }
+
+ $href = "admin/structure/features/{$name}";
+ $href_overridden = module_exists('diff') ? $href . '/diff' : $href;
+ $module_name = (user_access('administer features')) ? l($module->info['name'], $href) : $module->info['name'];
+ $form[$package]['status'][$name] = array(
+ '#type' => 'checkbox',
+ '#title' => $module_name,
+ '#description' => $description,
+ '#default_value' => $module->status,
+ '#disabled' => $disabled,
+ );
+
+ if (!empty($module->info['project status url'])) {
+ $uri = l(truncate_utf8($module->info['project status url'], 35, TRUE, TRUE), $module->info['project status url']);
+ }
+ else if (isset($module->info['project'], $module->info['version'], $module->info['datestamp'])) {
+ $uri = l('http://drupal.org', 'http://drupal.org/project/' . $module->info['project']);
+ }
+ else {
+ $uri = t('Unavailable');
+ }
+ $version = !empty($module->info['version']) ? $module->info['version'] : '';
+ $version = !empty($version) ? "$version
" : '';
+ $form[$package]['sign'][$name] = array('#markup' => "{$uri} {$version}");
+
+ if (user_access('administer features')) {
+ // Add status link
+ if ($module->status) {
+ $state = theme('features_storage_link', array('storage' => FEATURES_CHECKING, 'path' => $href));
+ $state .= l(t('Check'), "admin/structure/features/{$name}/status", array('attributes' => array('class' => array('admin-check'))));
+ $state .= theme('features_storage_link', array('storage' => FEATURES_REBUILDING, 'path' => $href));
+ $state .= theme('features_storage_link', array('storage' => FEATURES_NEEDS_REVIEW, 'path' => $href));
+ $state .= theme('features_storage_link', array('storage' => FEATURES_OVERRIDDEN, 'path' => $href_overridden));
+ $state .= theme('features_storage_link', array('storage' => FEATURES_DEFAULT, 'path' => $href));
+ }
+ elseif (!empty($conflicts[$name])) {
+ $state = theme('features_storage_link', array('storage' => FEATURES_CONFLICT, 'path' => $href));
+ }
+ else {
+ $state = theme('features_storage_link', array('storage' => FEATURES_DISABLED, 'path' => $href));
+ }
+ $form[$package]['state'][$name] = array(
+ '#markup' => !empty($state) ? $state : '',
+ );
+
+ // Add in recreate link
+ $form[$package]['actions'][$name] = array(
+ '#markup' => l(t('Recreate'), "admin/structure/features/{$name}/recreate", array('attributes' => array('class' => array('admin-update')))),
+ );
+ }
+ }
+ ksort($form);
+
+ // As of 7.0 beta 2 it matters where the "vertical_tabs" element lives on the
+ // the array. We add it late, but at the beginning of the array because that
+ // keeps us away from trouble.
+ $form = array('packages' => array('#type' => 'vertical_tabs')) + $form;
+
+ $form['buttons'] = array(
+ '#theme' => 'features_form_buttons',
+ );
+ $form['buttons']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save settings'),
+ '#submit' => array('features_form_submit'),
+ '#validate' => array('features_form_validate'),
+ );
+ return $form;
+}
+
+/**
+ * Display the components of a feature.
+ */
+function features_admin_components($form, $form_state, $feature) {
+ // Breadcrumb navigation
+ $breadcrumb[] = l(t('Home'), NULL);
+ $breadcrumb[] = l(t('Administration'), 'admin');
+ $breadcrumb[] = l(t('Structure'), 'admin/structure');
+ $breadcrumb[] = l(t('Features'), 'admin/structure/features');
+ drupal_set_breadcrumb($breadcrumb);
+
+ module_load_include('inc', 'features', 'features.export');
+ $form['#feature'] = $feature;
+
+ // Store feature info for theme layer.
+ $form['module'] = array('#type' => 'value', '#value' => $feature->name);
+ $form['#info'] = $feature->info;
+ $form['#dependencies'] = array();
+ if (!empty($feature->info['dependencies'])) {
+ foreach ($feature->info['dependencies'] as $dependency) {
+ $parsed_dependency = drupal_parse_dependency($dependency);
+ $dependency = $parsed_dependency['name'];
+ $status = features_get_module_status($dependency);
+ $form['#dependencies'][$dependency] = $status;
+ }
+ }
+
+ $conflicts = features_get_conflicts();
+ if (!module_exists($form['module']['#value']) && isset($form['module']['#value']) && !empty($conflicts[$form['module']['#value']])) {
+ $module_conflicts = $conflicts[$form['module']['#value']];
+ $conflicts = array();
+ foreach ($module_conflicts as $conflict) {
+ $conflicts = array_merge_recursive($conflict, $conflicts);
+ }
+ }
+ else {
+ $conflicts = array();
+ }
+ $form['#conflicts'] = $conflicts;
+
+ $review = $revert = FALSE;
+
+ // Iterate over components and retrieve status for display
+ $states = features_get_component_states(array($feature->name), FALSE);
+ $form['revert']['#tree'] = TRUE;
+ foreach ($feature->info['features'] as $component => $items) {
+ if (user_access('administer features') && array_key_exists($component, $states[$feature->name]) && in_array($states[$feature->name][$component], array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW))) {
+ switch ($states[$feature->name][$component]) {
+ case FEATURES_OVERRIDDEN:
+ $revert = TRUE;
+ break;
+ case FEATURES_NEEDS_REVIEW:
+ $review = TRUE;
+ break;
+ }
+ $form['revert'][$component] = array(
+ '#type' => 'checkbox',
+ '#default_value' => FALSE,
+ );
+ }
+ if (module_exists('diff')) {
+ $diffpath = "admin/structure/features/{$feature->name}/diff/{$component}";
+ $item = menu_get_item($diffpath);
+ $path = ($item && $item['access']) ? $diffpath : NULL;
+ }
+ else {
+ $path = NULL;
+ }
+
+ $storage = FEATURES_DEFAULT;
+ if (array_key_exists($component, $states[$feature->name])) {
+ $storage = $states[$feature->name][$component];
+ }
+ else if (array_key_exists($component, $conflicts)) {
+ $storage = FEATURES_CONFLICT;
+ }
+ // This can be removed if the css is fixed so link doesn't move when
+ // ajaxing and linke moved.
+ $lock_link = '';
+ if (user_access('administer features') && (features_hook($component, 'features_revert') || features_hook($component, 'features_rebuild'))) {
+ $lock_link = ' ' . theme('features_lock_link', array('feature' => $feature->name, 'component' => $component));
+ }
+ $form['components'][$component] = array(
+ '#markup' => $lock_link . theme('features_storage_link', array('storage' => $storage, 'path' => $path)),
+ );
+ }
+
+ if ($review || $revert) {
+ $form['buttons'] = array('#theme' => 'features_form_buttons', '#tree' => TRUE);
+ if ($revert || $review) {
+ $form['buttons']['revert'] = array(
+ '#type' => 'submit',
+ '#value' => t('Revert components'),
+ '#submit' => array('features_admin_components_revert'),
+ );
+ }
+ if ($review) {
+ $form['buttons']['review'] = array(
+ '#type' => 'submit',
+ '#value' => t('Mark as reviewed'),
+ '#submit' => array('features_admin_components_review'),
+ );
+ }
+ }
+ return $form;
+}
+
+/**
+ * Submit handler for revert form.
+ */
+function features_admin_components_revert(&$form, &$form_state) {
+ module_load_include('inc', 'features', 'features.export');
+ features_include();
+ $module = $form_state['values']['module'];
+ $revert = array($module => array());
+ foreach (array_filter($form_state['values']['revert']) as $component => $status) {
+ $revert[$module][] = $component;
+ drupal_set_message(t('Reverted all @component components for @module.', array('@component' => $component, '@module' => $module)));
+ }
+ if (empty($revert[$module])) {
+ drupal_set_message(t('Please select which components to revert.'), 'warning');
+ }
+ features_revert($revert);
+ $form_state['redirect'] = 'admin/structure/features/' . $module;
+}
+
+/**
+ * Submit handler for revert form.
+ */
+function features_admin_components_review(&$form, &$form_state) {
+ module_load_include('inc', 'features', 'features.export');
+ features_include();
+ $module = $form_state['values']['module'];
+ $revert = array();
+ foreach (array_filter($form_state['values']['revert']) as $component => $status) {
+ features_set_signature($module, $component);
+ drupal_set_message(t('All @component components for @module reviewed.', array('@component' => $component, '@module' => $module)));
+ }
+ $form_state['redirect'] = 'admin/structure/features/' . $module;
+}
+
+/**
+ * Validate handler for the 'manage features' form.
+ */
+function features_form_validate(&$form, &$form_state) {
+ include_once './includes/install.inc';
+ $conflicts = features_get_conflicts();
+ foreach ($form_state['values']['status'] as $module => $status) {
+ if ($status) {
+ if (!empty($conflicts[$module])) {
+ foreach (array_keys($conflicts[$module]) as $conflict) {
+ if (!empty($form_state['values']['status'][$conflict])) {
+ form_set_error('status', t('The feature @module cannot be enabled because it conflicts with @conflict.', array('@module' => $module, '@conflict' => $conflict)));
+ }
+ }
+ }
+ if (!drupal_check_module($module)) {
+ form_set_error('status', t('The feature @module cannot be enabled because it has unmet requirements.', array('@module' => $module)));
+ }
+ }
+ }
+}
+
+/**
+ * Submit handler for the 'manage features' form
+ */
+function features_form_submit(&$form, &$form_state) {
+ // Clear drupal caches after enabling a feature. We do this in a separate
+ // page callback rather than as part of the submit handler as some modules
+ // have includes/other directives of importance in hooks that have already
+ // been called in this page load.
+ $form_state['redirect'] = 'admin/structure/features/cleanup/clear';
+
+ $features = $form['#features'];
+ if (!empty($features)) {
+ $status = $form_state['values']['status'];
+ $install = array_keys(array_filter($status));
+ $disable = array_diff(array_keys($status), $install);
+
+ // Disable first. If there are any features that are disabled that are
+ // dependencies of features that have been queued for install, they will
+ // be re-enabled.
+ module_disable($disable);
+ features_install_modules($install);
+ }
+}
+
+/**
+ * Submit handler for the 'manage features' form rebuild button.
+ */
+function features_form_rebuild() {
+ cache_clear_all('features:features_list', 'cache');
+}
+
+/**
+ * Form for clearing cache after enabling a feature.
+ */
+function features_cleanup_form($form, $form_state, $cache_clear = FALSE) {
+ // Clear caches if we're getting a post-submit redirect that requests it.
+ if ($cache_clear) {
+ drupal_flush_all_caches();
+
+ // The following functions need to be run because drupal_flush_all_caches()
+ // runs rebuilds in the wrong order. The node type cache is rebuilt *after*
+ // the menu is rebuilt, meaning that the menu tree is stale in certain
+ // circumstances after drupal_flush_all_caches(). We rebuild again.
+ menu_rebuild();
+ }
+
+ drupal_goto('admin/structure/features');
+}
+
+/**
+ * Page callback to display the differences between what's in code and
+ * what is in the db.
+ *
+ * @param $feature
+ * A loaded feature object to display differences for.
+ * @param $component
+ * (optional) Specific component to display differences for. If excluded, all
+ * components are used.
+ *
+ * @return
+ * Themed display of what is different.
+ */
+function features_feature_diff($feature, $component = NULL) {
+ drupal_add_css(drupal_get_path('module', 'features') . '/features.css');
+ module_load_include('inc', 'features', 'features.export');
+ drupal_set_title($feature->info['name']);
+
+ $overrides = features_detect_overrides($feature);
+
+ $output = '';
+ if (!empty($overrides)) {
+ // Filter overrides down to specified component.
+ if (isset($component) && isset($overrides[$component])) {
+ $overrides = array($component => $overrides[$component]);
+ }
+
+ module_load_include('inc', 'diff', 'diff.engine');
+ $formatter = new DrupalDiffFormatter();
+
+ $rows = array();
+ foreach ($overrides as $component => $items) {
+ $rows[] = array(array('data' => $component, 'colspan' => 4, 'header' => TRUE));
+ $diff = new Diff(explode("\n", $items['default']), explode("\n", $items['normal']));
+ $rows = array_merge($rows, $formatter->format($diff));
+ }
+ $header = array(
+ array('data' => t('Default'), 'colspan' => 2),
+ array('data' => t('Overrides'), 'colspan' => 2),
+ );
+ $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('diff', 'features-diff'))));
+ }
+ else {
+ $output = "" . t('No changes have been made to this feature.') . "
";
+ }
+ $output = array('page' => array('#markup' => "{$output}
"));
+ return $output;
+}
+
+
+
+/**
+ * Page callback to lock a component.
+ *
+ * @param $feature
+ * Loaded feature object to be processed for component locking.
+ * @param $component
+ * (optional) A specific component to lock.
+ *
+ * @return
+ * Themed display of what is different.
+ */
+function features_admin_lock($feature, $type = 'ajax', $component = NULL) {
+ if ($type == 'ajax' && !empty($_GET['token']) && drupal_valid_token($_GET['token'], 'features/' . $feature->name . '/' . ($component ? $component : '')) == $_GET['token']) {
+ if (features_feature_is_locked($feature->name, $component, FALSE)) {
+ features_feature_unlock($feature->name, $component);
+ }
+ else {
+ features_feature_lock($feature->name, $component);
+ }
+ $commands = array();
+ $new_link = theme('features_lock_link', array('feature' => $feature->name, 'component' => $component));
+ $commands[] = ajax_command_replace('#features-lock-link-' . $feature->name . ($component ? '-' . $component : ''), $new_link);
+ $page = array('#type' => 'ajax', '#commands' => $commands);
+ ajax_deliver($page);
+ }
+ else {
+ return drupal_get_form('features_feature_lock_confirm_form', $feature, $component);
+ }
+}
+
+/**
+ * Confirm form for locking a feature.
+ */
+function features_feature_lock_confirm_form($form, $form_state, $feature, $component) {
+ $form['#feature'] = $feature;
+ $form['#component'] = $component;
+ $is_locked = features_feature_is_locked($feature->name, $component, FALSE);
+ $args = array(
+ '@name' => $feature->name,
+ '@component' => $component ? $component : t('all'),
+ '!action' => $is_locked ? t('unlock') : t('lock'),
+ );
+ $question = t('Are you sure you want to !action this Feature @name (component @component)?', $args);
+ return confirm_form($form, $question, 'admin/structure/features/' . $feature->name);
+}
+
+/**
+ * Submit callback to lock components of a feature.
+ */
+function features_feature_lock_confirm_form_submit($form, &$form_state) {
+ $feature = $form['#feature']->name;
+ $component = $form['#component'];
+ if (features_feature_is_locked($feature, $component, FALSE)) {
+ features_feature_unlock($feature, $component);
+ drupal_set_message(t('Feature @name (component @component) has been unlocked.', array('@name' => $feature, '@component' => $component ? $component : t('all'))));
+ }
+ else {
+ features_feature_lock($feature, $component);
+ drupal_set_message(t('Feature @name (component @component) has been locked.', array('@name' => $feature, '@component' => $component ? $component : t('all'))));
+ }
+ $form_state['redirect'] = 'admin/structure/features/' . $feature;
+}
+
+/**
+ * Compare the component names. Used to sort alphabetically.
+ */
+function features_compare_component_name($a, $b) {
+ return strcasecmp($a['name'], $b['name']);
+}
+
+/**
+ * Javascript callback that returns the status of a feature.
+ */
+function features_feature_status($feature) {
+ module_load_include('inc', 'features', 'features.export');
+ return drupal_json_output(array('storage' => features_get_storage($feature->name)));
+}
+
+/**
+ * Make a Drupal options array safe for usage with jQuery DOM selectors.
+ * Encodes known bad characters into __[ordinal]__ so that they may be
+ * safely referenced by JS behaviors.
+ */
+function features_dom_encode_options($options = array(), $keys_only = TRUE) {
+ $replacements = features_dom_encode_map();
+ $encoded = array();
+ foreach ($options as $key => $value) {
+ $encoded[strtr($key, $replacements)] = $keys_only ? $value : strtr($value, $replacements);
+ }
+ return $encoded;
+}
+
+function features_dom_encode($key) {
+ $replacements = features_dom_encode_map();
+ return strtr($key, $replacements);
+}
+
+function features_dom_decode($key) {
+ $replacements = array_flip(features_dom_encode_map());
+ return strtr($key, $replacements);
+}
+
+/**
+ * Decode an array of option values that have been encoded by
+ * features_dom_encode_options().
+ */
+function features_dom_decode_options($options, $keys_only = FALSE) {
+ $replacements = array_flip(features_dom_encode_map());
+ $encoded = array();
+ foreach ($options as $key => $value) {
+ $encoded[strtr($key, $replacements)] = $keys_only ? $value : strtr($value, $replacements);
+ }
+ return $encoded;
+}
+
+/**
+ * Returns encoding map for decode and encode options.
+ */
+function features_dom_encode_map() {
+ return array(
+ ':' => '__' . ord(':') . '__',
+ '/' => '__' . ord('/') . '__',
+ ',' => '__' . ord(',') . '__',
+ '.' => '__' . ord('.') . '__',
+ '<' => '__' . ord('<') . '__',
+ '>' => '__' . ord('>') . '__',
+ '%' => '__' . ord('%') . '__',
+ ')' => '__' . ord(')') . '__',
+ '(' => '__' . ord('(') . '__',
+ );
+}
+
+/**
+ * Page callback: Autocomplete field for features package.
+ *
+ * @param $search_string
+ * The char or string that user have written in autocomplete field,
+ * this is the string this function uses for filter.
+ *
+ * @see features_menu()
+ */
+function features_autocomplete_packages($search_string) {
+ $matched_packages = array();
+ //fetch all modules that are features and copy the package name into a new array.
+ foreach (features_get_features(NULL, TRUE) as $value) {
+ if (preg_match('/' . $search_string . '/i', $value->info['package'])) {
+ $matched_packages[$value->info['package']] = $value->info['package'];
+ }
+ }
+ //removes duplicated package, we wont a list of all unique packages.
+ $matched_packages = array_unique($matched_packages);
+ drupal_json_output($matched_packages);
+}
+
+/**
+ * Return a list of all used components/items not matching a given feature module
+ * similar to features_get_conflicts but returns all component items "in use"
+ */
+function _features_get_used($module_name = NULL) {
+
+ global $features_ignore_conflicts;
+ // make sure we turn off the ignore_conflicts global to get full list of used components
+ // hate to use global, but since this is just for an admin screen it's not a real problem
+ $old_value = $features_ignore_conflicts;
+ $features_ignore_conflicts = FALSE;
+
+ $conflicts = array();
+ $component_info = features_get_components();
+ $map = features_get_component_map();
+
+ foreach ($map as $type => $components) {
+ // Only check conflicts for components we know about.
+ if (isset($component_info[$type])) {
+ foreach ($components as $component => $modules) {
+ foreach ($modules as $module) {
+ // only for enabled modules
+ if (module_exists($module) && (empty($module_name) || ($module_name != $module))) {
+ if (!isset($conflicts[$module])) {
+ $conflicts[$module] = array();
+ }
+ $conflicts[$module][$type][] = $component;
+ }
+ }
+ }
+ }
+ }
+
+ // restore previous value of global
+ $features_ignore_conflicts = $old_value;
+ return $conflicts;
+}
+
+/**
+ * Retrieves the array of features as expected on the Manage Features form.
+ * Uses caching for performance reasons if caching is enabled.
+ *
+ * @internal - This function might return cached result with outdated data,
+ * use with caution.
+ */
+function _features_get_features_list() {
+ $features = array();
+
+ $cache = cache_get('features:features_list');
+ if ($cache) {
+ $features = $cache->data;
+ }
+
+ if (empty($features)) {
+ // Clear & rebuild key caches
+ features_get_info(NULL, NULL, TRUE);
+ features_rebuild();
+
+ $modules = array_filter(features_get_modules(), 'features_filter_hidden');
+ $features = array_filter(features_get_features(), 'features_filter_hidden');
+
+ foreach ($modules as $key => $module) {
+ if ($module->status && !empty($module->info['dependencies'])) {
+ foreach ($module->info['dependencies'] as $dependent) {
+ if (isset($features[$dependent])) {
+ $features[$dependent]->dependents[$key] = $module->info['name'];
+ }
+ }
+ }
+ }
+
+ cache_set('features:features_list', $features);
+ }
+
+ return $features;
+}
diff --git a/docroot/sites/all/modules/features/features.api.php b/docroot/sites/all/modules/features/features.api.php
new file mode 100644
index 0000000..d7c7ede
--- /dev/null
+++ b/docroot/sites/all/modules/features/features.api.php
@@ -0,0 +1,553 @@
+ array(
+ 'default_hook' => 'mycomponent_defaults',
+ 'default_file' => FEATURES_DEFAULTS_INCLUDED,
+ 'feature_source' => TRUE,
+ 'file' => drupal_get_path('module', 'mycomponent') . '/mycomponent.features.inc',
+ ),
+ );
+}
+
+/**
+ * Component hook. The hook should be implemented using the name of the
+ * component, not the module, eg. [component]_features_export() rather than
+ * [module]_features_export().
+ *
+ * Process the export array for a given component. Implementations of this hook
+ * have three key tasks:
+ *
+ * 1. Determine module dependencies for any of the components passed to it
+ * e.g. the views implementation iterates over each views' handlers and
+ * plugins to determine which modules need to be added as dependencies.
+ *
+ * 2. Correctly add components to the export array. In general this is usually
+ * adding all of the items in $data to $export['features']['my_key'], but
+ * can become more complicated if components are shared between features
+ * or modules.
+ *
+ * 3. Delegating further detection and export tasks to related or derivative
+ * components.
+ *
+ * Each export processor can kickoff further export processors by returning a
+ * keyed array (aka the "pipe") where the key is the next export processor hook
+ * to call and the value is an array to be passed to that processor's $data
+ * argument. This allows an export process to start simply at a few objects:
+ *
+ * [context]
+ *
+ * And then branch out, delegating each component to its appropriate hook:
+ *
+ * [context]--------+------------+
+ * | | |
+ * [node] [block] [views]
+ * |
+ * [CCK]
+ * |
+ * [imagecache]
+ *
+ * @param array $data
+ * An array of machine names for the component in question to be exported.
+ * @param array &$export
+ * By reference. An array of all components to be exported with a given
+ * feature. Component objects that should be exported should be added to
+ * this array.
+ * @param string $module_name
+ * The name of the feature module to be generated.
+ * @return array
+ * The pipe array of further processors that should be called.
+ */
+function hook_features_export($data, &$export, $module_name) {
+ // The following is the simplest implementation of a straight object export
+ // with no further export processors called.
+ foreach ($data as $component) {
+ $export['features']['mycomponent'][$component] = $component;
+ }
+ return array();
+}
+
+/**
+ * Component hook. The hook should be implemented using the name of the
+ * component, not the module, eg. [component]_features_export() rather than
+ * [module]_features_export().
+ *
+ * List all objects for a component that may be exported.
+ *
+ * @return array
+ * A keyed array of items, suitable for use with a FormAPI select or
+ * checkboxes element.
+ */
+function hook_features_export_options() {
+ $options = array();
+ foreach (mycomponent_load() as $mycomponent) {
+ $options[$mycomponent->name] = $mycomponent->title;
+ }
+ return $options;
+}
+
+/**
+ * Component hook. The hook should be implemented using the name of the
+ * component, not the module, eg. [component]_features_export() rather than
+ * [module]_features_export().
+ *
+ * Render one or more component objects to code.
+ *
+ * @param string $module_name
+ * The name of the feature module to be exported.
+ * @param array $data
+ * An array of machine name identifiers for the objects to be rendered.
+ * @param array $export
+ * The full export array of the current feature being exported. This is only
+ * passed when hook_features_export_render() is invoked for an actual feature
+ * update or recreate, not during state checks or other operations.
+ * @return array
+ * An associative array of rendered PHP code where the key is the name of the
+ * hook that should wrap the PHP code. The hook should not include the name
+ * of the module, e.g. the key for `hook_example` should simply be `example`
+ * The values in the array can also be in the form of an associative array
+ * with the required key of 'code' and optional key of 'args', if 'args' need
+ * to be added to the hook. Alternate it can be an associative array in the
+ * same style as hook_features_export_files() to add additional files.
+ */
+function hook_features_export_render($module_name, $data, $export = NULL) {
+ $code = array();
+ $code[] = '$mycomponents = array();';
+ foreach ($data as $name) {
+ $code[] = " \$mycomponents['{$name}'] = " . features_var_export(mycomponent_load($name)) .";";
+ }
+ $code[] = "return \$mycomponents;";
+ $code = implode("\n", $code);
+ return array('mycomponent_defaults' => $code);
+}
+
+/**
+ * Component hook. The hook should be implemented using the name of the
+ * component, not the module, eg. [component]_features_export() rather than
+ * [module]_features_export().
+ *
+ * Revert all component objects for a given feature module.
+ *
+ * @param string $module_name
+ * The name of the feature module whose components should be reverted.
+ * @return boolean
+ * TRUE or FALSE for whether the components were successfully reverted.
+ * NOTE: This return value is no longer used in the latest Features so
+ * modules should no longer count on this value
+ */
+function hook_features_revert($module_name) {
+ $mycomponents = module_invoke($module_name, 'mycomponent_defaults');
+ if (!empty($mycomponents)) {
+ foreach ($mycomponents as $mycomponent) {
+ mycomponent_delete($mycomponent);
+ }
+ }
+}
+
+/**
+ * Component hook. The hook should be implemented using the name of the
+ * component, not the module, eg. [component]_features_export() rather than
+ * [module]_features_export().
+ *
+ * Rebuild all component objects for a given feature module. Should only be
+ * implemented for 'faux-exportable' components.
+ *
+ * This hook is called at points where Features determines that it is safe
+ * (ie. the feature is in state `FEATURES_REBUILDABLE`) for your module to
+ * replace objects in the database with defaults that you collect from your
+ * own defaults hook. See API.txt for how Features determines whether a
+ * rebuild of components is possible.
+ *
+ * @param string $module_name
+ * The name of the feature module whose components should be rebuilt.
+ */
+function hook_features_rebuild($module_name) {
+ $mycomponents = module_invoke($module_name, 'mycomponent_defaults');
+ if (!empty($mycomponents)) {
+ foreach ($mycomponents as $mycomponent) {
+ mycomponent_save($mycomponent);
+ }
+ }
+}
+
+/**
+ * Invoked before a restore operation is run.
+ *
+ * This hook is called before any of the restore operations on the components is
+ * run.
+ *
+ * @param string $op
+ * The operation that is triggered: revert, rebuild, disable, enable
+ * @param array $items
+ * The items handled by the operation.
+ */
+function hook_features_pre_restore($op, $items) {
+ if ($op == 'rebuild') {
+ // Use features rebuild to rebuild the features independent exports too.
+ entity_defaults_rebuild();
+ }
+}
+
+/**
+ * Invoked after a restore operation is run.
+ *
+ * This hook is called after any of the restore operations on the components is
+ * run.
+ *
+ * @param string $op
+ * The operation that is triggered: revert, rebuild, disable, enable
+ * @param array $items
+ * The items handled by the operation.
+ */
+function hook_features_post_restore($op, $items) {
+ if ($op == 'rebuild') {
+ // Use features rebuild to rebuild the features independent exports too.
+ entity_defaults_rebuild();
+ }
+}
+
+/**
+ * Alter the final array of Component names to be exported, just prior to
+ * the rendering of defaults. Allows modules a final say in whether or not
+ * certain Components are exported (the Components' actual data, however,
+ * cannot be altered by this hook).
+ *
+ * @param array &$export
+ * By reference. An array of all component names to be exported with a given
+ * feature.
+ * @param array $module_name
+ * The name of the feature module to be generated.
+ */
+function hook_features_export_alter(&$export, $module_name) {
+ // Example: do not allow the page content type to be exported, ever.
+ if (!empty($export['features']['node']['page'])) {
+ unset($export['features']['node']['page']);
+ }
+}
+
+/**
+ * Alter the pipe array for a given component. This hook should be implemented
+ * with the name of the component type in place of `component` in the function
+ * name, e.g. `features_pipe_views_alter()` will alter the pipe for the Views
+ * component.
+ *
+ * @param array &$pipe
+ * By reference. The pipe array of further processors that should be called.
+ * @param array $data
+ * An array of machine names for the component in question to be exported.
+ * @param array &$export
+ * By reference. An array of all components to be exported with a given
+ * feature.
+ */
+function hook_features_pipe_COMPONENT_alter(&$pipe, $data, $export) {
+ if (in_array($data, 'my-node-type')) {
+ $pipe['dependencies'][] = 'mymodule';
+ }
+}
+
+/**
+ * Alter the pipe array for a given component.
+ *
+ * @param array &$pipe
+ * By reference. The pipe array of further processors that should be called.
+ * @param array $data
+ * An array of machine names for the component in question to be exported.
+ * @param array &$export
+ * By reference. An array of all components to be exported with a given
+ * feature.
+ *
+ * The component being exported is contained in $export['component'].
+ * The module being exported contained in $export['module_name'].
+ */
+function hook_features_pipe_alter(&$pipe, $data, $export) {
+ if ($export['component'] == 'node' && in_array('my-node-type', $data)) {
+ $pipe['dependencies'][] = 'mymodule';
+ }
+}
+
+
+/**
+ * Add extra files to the exported file.
+ *
+ * @return array
+ * An array of files, keyed by file name that will appear in feature and
+ * with either file_path key to indicate where to copy the file from or
+ * file_content key to indicate the contents of the file.
+ */
+function hook_features_export_files($module_name, $export) {
+ return array('css/main.css' => array('file_content' => 'body {background-color:blue;}'));
+}
+
+/**
+ * Alter the extra files added to the export.
+ */
+function hook_features_export_files_alter(&$files, $module_name, $export) {
+ $files['css/main.css']['file_content'] = 'body {background-color:black;}';
+}
+
+/**
+ * @defgroup features_component_alter_hooks Feature's component alter hooks
+ * @{
+ * Hooks to modify components defined by other features. These come in the form
+ * hook_COMPONENT_alter where COMPONENT is the default_hook declared by any of
+ * components within features.
+ *
+ * CTools also has a variety of hook_FOO_alters.
+ *
+ * Note: While views is a component of features, it declares it's own alter
+ * function which takes a similar form:
+ * hook_views_default_views_alter(&$views)
+ */
+
+/**
+ * Deprecated as of 7.x-2.0.
+ *
+ * Alter the default fields right before they are cached into the database.
+ *
+ * @param &$fields
+ * By reference. The fields that have been declared by another feature.
+ */
+function hook_field_default_fields_alter(&$fields) {
+}
+
+/**
+ * Alter the base fields right before they are cached into the database.
+ *
+ * @param &$fields
+ * By reference. The fields that have been declared by another feature.
+ */
+function hook_field_default_field_bases_alter(&$fields) {
+}
+
+/**
+ * Alter the field instances right before they are cached into the database.
+ *
+ * @param &$fields
+ * By reference. The fields that have been declared by another feature.
+ */
+function hook_field_default_field_instances_alter(&$fields) {
+}
+
+/**
+ * Alter the default fieldgroup groups right before they are cached into the
+ * database.
+ *
+ * @param &$groups
+ * By reference. The fieldgroup groups that have been declared by another
+ * feature.
+ */
+function hook_fieldgroup_default_groups_alter(&$groups) {
+}
+
+/**
+ * Alter the default filter formats right before they are cached into the
+ * database.
+ *
+ * @param &$formats
+ * By reference. The formats that have been declared by another feature.
+ */
+function hook_filter_default_formats_alter(&$formats) {
+}
+
+/**
+ * Alter the default menus right before they are cached into the database.
+ *
+ * @param &$menus
+ * By reference. The menus that have been declared by another feature.
+ */
+function hook_menu_default_menu_custom_alter(&$menus) {
+}
+
+/**
+ * Alter the default menu links right before they are cached into the database.
+ *
+ * @param &$links
+ * By reference. The menu links that have been declared by another feature.
+ */
+function hook_menu_default_menu_links_alter(&$links) {
+}
+
+/**
+ * Alter the default menu items right before they are cached into the database.
+ *
+ * @param &$items
+ * By reference. The menu items that have been declared by another feature.
+ */
+function hook_menu_default_items_alter(&$items) {
+}
+
+/**
+ * Alter the default vocabularies right before they are cached into the
+ * database.
+ *
+ * @param &$vocabularies
+ * By reference. The vocabularies that have been declared by another feature.
+ */
+function hook_taxonomy_default_vocabularies_alter(&$vocabularies) {
+}
+
+/**
+ * Alter the default permissions right before they are cached into the
+ * database.
+ *
+ * @param &$permissions
+ * By reference. The permissions that have been declared by another feature.
+ */
+function hook_user_default_permissions_alter(&$permissions) {
+}
+
+/**
+ * Alter the default roles right before they are cached into the database.
+ *
+ * @param &$roles
+ * By reference. The roles that have been declared by another feature.
+ */
+function hook_user_default_roles_alter(&$roles) {
+}
+
+/**
+ * @}
+ */
+
+
+/**
+ * @defgroup features_module_hooks Feature module hooks
+ * @{
+ * Hooks invoked on Feature modules when that module is enabled, disabled,
+ * rebuilt, or reverted. These are ONLY invoked on the Features module on
+ * which these actions are taken.
+ */
+
+/**
+ * Feature module hook. Invoked on a Feature module before that module is
+ * reverted.
+ *
+ * @param $component
+ * String name of the component that is about to be reverted.
+ */
+function hook_pre_features_revert($component) {
+}
+
+/**
+ * Feature module hook. Invoked on a Feature module after that module is
+ * reverted.
+ *
+ * @param $component
+ * String name of the component that has just been reverted.
+ */
+function hook_post_features_revert($component) {
+}
+
+/**
+ * Feature module hook. Invoked on a Feature module before that module is
+ * rebuilt.
+ *
+ * @param $component
+ * String name of the component that is about to be rebuilt.
+ */
+function hook_pre_features_rebuild($component) {
+}
+
+/**
+ * Feature module hook. Invoked on a Feature module after that module is
+ * rebuilt.
+ *
+ * @param $component
+ * String name of the component that has just been rebuilt.
+ */
+function hook_post_features_rebuild($component) {
+}
+
+/**
+ * Feature module hook. Invoked on a Feature module before that module is
+ * disabled.
+ *
+ * @param $component
+ * String name of the component that is about to be disabled.
+ */
+function hook_pre_features_disable_feature($component) {
+}
+
+/**
+ * Feature module hook. Invoked on a Feature module after that module is
+ * disabled.
+ *
+ * @param $component
+ * String name of the component that has just been disabled.
+ */
+function hook_post_features_disable_feature($component) {
+}
+
+/**
+ * Feature module hook. Invoked on a Feature module before that module is
+ * enabled.
+ *
+ * @param $component
+ * String name of the component that is about to be enabled.
+ */
+function hook_pre_features_enable_feature($component) {
+}
+
+/**
+ * Feature module hook. Invoked on a Feature module after that module is
+ * enabled.
+ *
+ * @param $component
+ * String name of the component that has just been enabled.
+ */
+function hook_post_features_enable_feature($component) {
+}
+
+/**
+ * @}
+ */
diff --git a/docroot/sites/all/modules/features/features.css b/docroot/sites/all/modules/features/features.css
new file mode 100644
index 0000000..e3f0251
--- /dev/null
+++ b/docroot/sites/all/modules/features/features.css
@@ -0,0 +1,579 @@
+/**
+ * Features packages.
+ */
+div.features-form-links {
+ width:20%;
+ float:left;
+}
+
+div.features-form-content {
+ width:80%;
+ float:right;
+}
+
+/**
+ * Package links.
+ */
+div.features-form-links ul#features-form-links,
+div.features-form-links ul#features-form-links li,
+div.features-form-links ul#features-form-links li a {
+ display:block;
+ float:none;
+ padding:0px;
+ margin:0px;
+}
+
+div.features-form-links ul#features-form-links {
+ margin:0px 0px 10px;
+}
+
+div.features-form-links ul#features-form-links li a {
+ background:#f8f8f8;
+ padding:5px 5px 4px 4px;
+ border-left:1px solid #eee;
+ border-bottom:1px solid #eee;
+ color: #000;
+ cursor:pointer;
+}
+
+div.features-form-links ul#features-form-links li a.features-package-active {
+ padding:4px 5px 4px 4px;
+ background:#fff;
+ border:1px solid #ccc;
+ border-right:0px;
+ color: #000;
+ margin-right:-1px;
+}
+
+/* Packages */
+div.features-form-package {
+ border:1px solid #ccc;
+ background:#fff;
+ color: #000;
+ padding:10px;
+ margin:0px 0px 20px;
+ display:none;
+}
+
+div.features-package-active {
+ display:block;
+}
+
+/**
+ * Features management form (admin/build/features).
+ */
+div.features-empty {
+ margin:15px 0px;
+ font-size:1.5em;
+ text-align:center;
+ color:#999;
+}
+
+form div.buttons {
+ text-align:center;
+}
+
+table.features .admin-loading,
+table.features tr.disabled {
+ color:#999;
+}
+
+table.features a.configure {
+ float:right;
+ font-style:italic;
+}
+table.features div.feature {
+ float:left; width:85%;
+}
+table.features div.feature strong {
+ font-size:13px;
+}
+table.features div.feature div.description {
+ font-size:11px; margin:0px;
+}
+
+table.features-manage td.name {
+ width:80%;
+}
+table.features-manage td.sign {
+ width:20%;
+}
+
+table.features-admin td.name {
+ width:60%;
+}
+table.features-admin td.sign {
+ width:20%;
+}
+table.features-admin td.state {
+ width:10%;
+}
+table.features-admin td.actions {
+ width:10%;
+}
+
+table.features td.sign {
+ font-size:9px;
+ line-height:15px;
+ white-space:nowrap;
+}
+
+table.features td.sign * {
+ margin:0px;
+}
+
+table.features .admin-check,
+table.features .admin-default,
+table.features .admin-overridden,
+table.features .admin-rebuilding,
+table.features .admin-needs-review {
+ display:none;
+}
+
+/**
+ * Feature export form (admin/build/features/export).
+ */
+form.features-export-form table td {
+ width:50%;
+}
+form.features-export-form table td {
+ vertical-align:top;
+}
+form.features-export-form table div.description {
+ white-space:normal;
+}
+
+table.features-export div.form-item {
+ white-space:normal;
+}
+table.features-export select {
+ width:90%;
+}
+table.features-export td {
+ vertical-align:top;
+}
+
+form.features-export-form div.features-select {
+ display:none;
+}
+
+/*
+form.features-export-form div.form-checkboxes {
+ overflow-x:hidden;
+ overflow-y:auto;
+ height:20em;
+ }
+ */
+
+form.features-export-form div#edit-components-wrapper,
+form.features-export-form div.features-select {
+ padding-right:20px;
+}
+
+/**
+ * Feature component display (admin/build/features/%feature).
+ */
+div.features-components div.column {
+ float:left;
+ width:50%;
+}
+
+div.features-components div.column div.info {
+ padding-right:20px;
+}
+div.features-components div.column div.components {
+ padding-left:20px;
+}
+
+h3.features-download,
+div.features-comparison h3,
+div.features-components h3 {
+ font-size:2em;
+ font-weight:bold;
+ letter-spacing:-1px;
+ margin:15px 0px;
+}
+
+h3.features-download {
+ text-align:center;
+}
+
+div.features-components div.description {
+ font-size:11px;
+ margin:15px 0px;
+}
+
+div.features-components table td {
+ font-size:11px;
+}
+div.features-components table td.component {
+ padding-left:20px;
+}
+
+/**
+ * Features component lists.
+ */
+span.features-component-key {
+ font-size:11px;
+}
+
+a.admin-update,
+a.features-storage,
+span.features-storage,
+span.features-component-list span {
+ white-space:nowrap;
+ margin-right:5px;
+ padding:2px 5px;
+ background:#eee;
+ -moz-border-radius:5px;
+ -webkit-border-radius:5px;
+}
+
+div.features-key span.admin-conflict,
+span.features-component-list span.features-conflict {
+ background-color: #c30 !important;
+ color: #fff;
+}
+
+#features-export-wrapper .features-conflict,
+#features-export-wrapper .features-conflict label.option{
+ color: #c30 !important;
+ font-weight: bold !important;
+}
+
+div.conflicts span.admin-disabled {
+ color: #955;
+}
+
+a.admin-update {
+ background:transparent;
+}
+
+/* These pseudo selectors are necessary for themes like Garland. */
+a.admin-overridden:link,
+a.admin-overridden:visited,
+span.admin-overridden {
+ color:#fff;
+ background:#666;
+}
+
+a.admin-needs-review:link,
+a.admin-needs-review:visited,
+span.admin-needs-review {
+ color:#963;
+ background:#fe6;
+}
+
+a.admin-rebuilding:link,
+a.admin-rebuilding:visited,
+span.admin-rebuilding {
+ color:#fff;
+ background:#699;
+}
+
+a.admin-conflict:link,
+a.admin-conflict:visited,
+span.admin-conflict {
+ color:#c30;
+}
+
+table.features-diff td.diff-addedline,
+span.features-component-list .features-detected {
+ color:#68a;
+ background:#def;
+}
+
+table.features-diff td.diff-deletedline,
+span.features-component-list .features-dependency {
+ color:#999;
+ background:#f8f8f8;
+}
+
+/**
+ * Features diff.
+ */
+table.features-diff {
+ font-size:11px;
+}
+
+table.features-diff td {
+ padding:0px 5px;
+}
+
+table.features-diff td.diff-deletedline,
+table.features-diff td.diff-addedline,
+table.features-diff td.diff-context {
+ width:50%;
+ font-family:'Andale Mono',monospace;
+}
+
+/**
+ * New UI component export list
+ */
+#features-export-wrapper div.features-export-parent {
+ clear: both;
+}
+#features-export-form .fieldset-content.fieldset-wrapper {
+ padding-top: 5px;
+}
+html.js #features-export-form fieldset.collapsed {
+ min-height: 2em;
+ margin-bottom: 0;
+ top: 0;
+ padding: 0 0 0 0;
+}
+fieldset.features-export-component {
+ background: #F3F8FB;
+ margin: 0.5em 0;
+ font-weight: normal;
+ font-size: 12px;
+ top: 0;
+}
+fieldset.features-export-component.collapsed {
+ background: #F3F8FB;
+}
+fieldset.features-export-component legend {
+ top: 0;
+}
+fieldset.features-export-component.collapsed .fieldset-wrapper {
+ margin: 0;
+ padding: 0 13px 6px 15px;
+}
+fieldset.features-export-component .fieldset-legend {
+ white-space: nowrap;
+}
+fieldset.features-export-component .fieldset-title span {
+ font-size: 11px;
+ text-transform: none;
+ font-weight: normal;
+}
+#features-export-wrapper .fieldset-wrapper {
+ font-size: 12px;
+}
+div.features-export-list {
+ font-weight: normal;
+ font-size: 12px;
+ border: 1px solid #CCC;
+ border-top-width: 0;
+ overflow: hidden;
+}
+#features-export-form .fieldset-description {
+ margin: 5px 0;
+ line-height: 1.231em;
+ font-size: 0.923em;
+ color: #666;
+}
+div.features-export-empty {
+ display: none;
+}
+fieldset.features-export-component .fieldset-wrapper .form-checkboxes {
+ max-height: 20em;
+ overflow: auto;
+ min-width: 100px;
+}
+
+#features-export-wrapper .component-select .form-type-checkbox {
+ overflow: hidden;
+ padding: 0 0 0 2px;
+}
+
+#features-export-wrapper .component-added .form-type-checkbox,
+#features-export-wrapper .component-detected .form-type-checkbox,
+#features-export-wrapper .component-included .form-type-checkbox {
+ float: left;
+ white-space: normal;
+ margin: 2px 5px;
+ padding: 0 5px;
+ background: transparent;
+}
+
+#features-export-wrapper .component-added .form-type-checkbox {
+ font-weight: bold;
+ background: #EEE;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+}
+#features-export-wrapper div.component-added label.option {
+ font-weight: bold;
+}
+#features-export-wrapper .component-detected .form-type-checkbox {
+ font-style: italic;
+ color:#68a;
+ background:#def;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+}
+
+#features-export-info {
+ width: 49%;
+ float: left;
+ position: relative;
+}
+#features-export-wrapper {
+ width: 49%;
+ float: right;
+ clear: both;
+ position: relative;
+}
+#features-export-advanced {
+ width: 49%;
+ float: left;
+ clear: left;
+ margin-top: 0.5em;
+ position: relative;
+}
+#features-export-buttons {
+ width: 49%;
+ float: left;
+ margin-top: 0.5em;
+ position: relative;
+ text-align: center;
+}
+#features-export-buttons input {
+ margin: 0 3px;
+}
+
+#features-export-wrapper .component-added label a,
+#features-export-wrapper .component-detected label a,
+#features-export-wrapper .component-included label a {
+ display: inline;
+ float: none;
+}
+#features-export-wrapper label.component-added {
+ font-weight: bold;
+}
+#features-export-form input[size="60"].form-text {
+ width: 100%;
+}
+input.form-submit.features-refresh-button {
+ font-size: 0.7em;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+ background-color: transparent;
+ margin-left: 1em;
+}
+.features-refresh-wrapper .ajax-progress {
+ font-size: 10px;
+ width: 10em;
+}
+#features-export-advanced .ajax-progress {
+ font-size: 10px;
+ width: 10em;
+}
+#features-export-wrapper div.fieldset-description,
+#features-export-wrapper div.description {
+ clear: both;
+}
+#features-filter input[size="60"].form-text {
+ width: 200px;
+}
+#features-filter .fieldset-content,
+#features-filter .fieldset-wrapper,
+#features-filter fieldset {
+ border: 0;
+ padding: 0;
+ margin: 0;
+}
+#features-filter fieldset legend {
+ display: none;
+}
+#features-filter label,
+#features-filter input {
+ display: inline;
+ width: auto;
+}
+#features-filter .form-item {
+ float: left;
+ margin: 5px 0;
+ padding: 0;
+}
+#features-filter .form-item.form-type-checkbox {
+ margin: 5px 0;
+}
+#features-filter span {
+ float: left;
+ white-space: normal;
+ margin: 5px 5px;
+ padding: 0 5px;
+ background: transparent;
+ background: #EEE;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ cursor: pointer;
+}
+#features-filter span:hover {
+ background:#def;
+}
+#features-autodetect .form-item {
+ float: left;
+ margin: 0 0;
+ padding: 0;
+}
+#features-autodetect .fieldset-content,
+#features-autodetect .fieldset-wrapper,
+#features-autodetect fieldset {
+ border: 0;
+ padding: 0;
+ margin: 1em 0 0 0;
+ top: 0;
+}
+#features-autodetect fieldset legend {
+ display: none;
+}
+#features-export-advanced .form-item.form-item-generate-path {
+ margin-bottom: 0;
+}
+#features-info-file .form-textarea-wrapper,
+#features-info-file textarea {
+ height: 100%;
+}
+
+#features-info-file .form-type-textarea {
+ height: 100%;
+ margin: 0;
+}
+#edit-info-preview {
+ margin: 1em 0;
+}
+#features-legend .fieldset-wrapper span {
+ font-style: normal;
+ color: black;
+ display: inline-block;
+ background: transparent;
+ border: 1px solid #DDD;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ white-space: nowrap;
+ padding: 0 8px;
+ margin: 0 10px 0 0;
+}
+#features-legend .fieldset-wrapper .component-detected {
+ font-style: italic;
+ color:#68a;
+ background:#def;
+ border-width: 0;
+}
+#features-legend .fieldset-wrapper .component-added {
+ font-weight: bold;
+ background: #EEE;
+ border-width: 0;
+}
+
+fieldset.features-export-component .fieldset-title .component-count {
+font-size: 12px;
+font-weight: bold;
+}
+
+.features-lock-icon {
+ display: inline-block;
+}
+
+.features-components h3 {
+ display: inline-block;
+}
+
+.features-lock-empty {
+ display: inline-block;
+ width: 16px;
+}
\ No newline at end of file
diff --git a/docroot/sites/all/modules/features/features.drush.inc b/docroot/sites/all/modules/features/features.drush.inc
new file mode 100644
index 0000000..4cec62e
--- /dev/null
+++ b/docroot/sites/all/modules/features/features.drush.inc
@@ -0,0 +1,1026 @@
+ "List all the available features for your site.",
+ 'options' => array(
+ 'status' => "Feature status, can be 'enabled', 'disabled' or 'all'",
+ ),
+ 'drupal dependencies' => array('features'),
+ 'aliases' => array('fl', 'features'),
+ );
+ $items['features-export'] = array(
+ 'description' => "Export a feature from your site into a module.",
+ 'arguments' => array(
+ 'feature' => 'Feature name to export.',
+ 'components' => 'Patterns of components to include, see features-components for the format of patterns.'
+ ),
+ 'options' => array(
+ 'destination' => "Destination path (from Drupal root) of the exported feature. Defaults to '" . $path . "'.",
+ 'version-set' => "Specify a version number for the feature.",
+ 'version-increment' => "Increment the feature's version number.",
+ 'ignore-conflicts' => "Ignore conflicts and export all components.",
+ ),
+ 'drupal dependencies' => array('features'),
+ 'aliases' => array('fe'),
+ );
+ $items['features-add'] = array(
+ 'description' => "Add a component to a feature module. (DEPRECATED: use features-export)",
+ 'arguments' => array(
+ 'feature' => 'Feature name to add to.',
+ 'components' => 'List of components to add.',
+ ),
+ 'options' => array(
+ 'version-set' => "Specify a version number for the feature.",
+ 'version-increment' => "Increment the feature's version number.",
+ ),
+ 'drupal dependencies' => array('features'),
+ 'aliases' => array('fa'),
+ );
+ $items['features-components'] = array(
+ 'description' => 'List features components.',
+ 'arguments' => array(
+ 'patterns' => 'The features components type to list. Omit this argument to list all components.',
+ ),
+ 'options' => array(
+ 'exported' => array(
+ 'description' => 'Show only components that have been exported.',
+ ),
+ 'not-exported' => array(
+ 'description' => 'Show only components that have not been exported.',
+ ),
+ 'info-style' => array(
+ 'description' => 'Export components in format suitable for using in an info file.',
+ ),
+ ),
+ 'aliases' => array('fc'),
+ );
+ $items['features-update'] = array(
+ 'description' => "Update a feature module on your site.",
+ 'arguments' => array(
+ 'feature' => 'A space delimited list of features.',
+ ),
+ 'options' => array(
+ 'version-set' => "Specify a version number for the feature.",
+ 'version-increment' => "Increment the feature's version number.",
+ ),
+ 'drupal dependencies' => array('features'),
+ 'aliases' => array('fu'),
+ );
+ $items['features-update-all'] = array(
+ 'description' => "Update all feature modules on your site.",
+ 'arguments' => array(
+ 'feature_exclude' => 'A space-delimited list of features to exclude from being updated.',
+ ),
+ 'drupal dependencies' => array('features'),
+ 'aliases' => array('fu-all', 'fua'),
+ );
+ $items['features-revert'] = array(
+ 'description' => "Revert a feature module on your site.",
+ 'arguments' => array(
+ 'feature' => 'A space delimited list of features or feature.component pairs.',
+ ),
+ 'options' => array(
+ 'force' => "Force revert even if Features assumes components' state are default.",
+ ),
+ 'examples' => array(
+ 'drush fr foo.node foo.taxonomy bar' => 'Revert node and taxonomy components of feature "foo", but only if they are overriden. Revert all overriden components of feature "bar".',
+ 'drush fr foo.node foo.taxonomy bar --force' => 'Revert node and taxonomy components of feature "foo". Revert all components of feature "bar".',
+ ),
+ 'drupal dependencies' => array('features'),
+ 'aliases' => array('fr'),
+ );
+ $items['features-revert-all'] = array(
+ 'description' => "Revert all enabled feature module on your site.",
+ 'arguments' => array(
+ 'feature_exclude' => 'A space-delimited list of features to exclude from being reverted.',
+ ),
+ 'options' => array(
+ 'force' => "Force revert even if Features assumes components' state are default.",
+ ),
+ 'drupal dependencies' => array('features'),
+ 'aliases' => array('fr-all', 'fra'),
+ );
+ $items['features-diff'] = array(
+ 'description' => "Show the difference between the default and overridden state of a feature.",
+ 'arguments' => array(
+ 'feature' => 'The feature in question.',
+ ),
+ 'options' => array(
+ 'ctypes' => 'Comma separated list of component types to limit the output to. Defaults to all types.',
+ 'lines' => 'Generate diffs with lines of context instead of the usual two.',
+ ),
+ 'drupal dependencies' => array('features', 'diff'),
+ 'aliases' => array('fd'),
+ );
+
+ $items['features-diff-all'] = array(
+ 'description' => "Show the code difference for all enabled features not in their default state.",
+ 'arguments' => array(
+ 'feature_exclude' => 'A space-delimited list of features to exclude from being reverted.',
+ ),
+ 'options' => array(
+ 'force' => "Bypass the confirmations. This is useful if you want to output all of the diffs to a log file.",
+ ),
+ 'drupal dependencies' => array('features', 'diff'),
+ 'aliases' => array('fda'),
+ );
+
+ return $items;
+}
+
+/**
+ * Implements hook_drush_help().
+ */
+function features_drush_help($section) {
+ // If Features is enabled display the configured default export path,
+ // otherwise just show the default.
+ if (defined('FEATURES_DEFAULT_EXPORT_PATH')) {
+ $path = variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH);
+ }
+ else {
+ $path = 'sites/all/modules';
+ }
+
+ switch ($section) {
+ case 'drush:features':
+ return dt("List all the available features for your site.");
+ case 'drush:features-export':
+ return dt("Export a feature from your site into a module. If called with no arguments, display a list of available components. If called with a single argument, attempt to create a feature including the given component with the same name. The option '--destination=foo' may be used to specify the path (from Drupal root) where the feature should be created. The default destination is '@path'. The option '--version-set=foo' may be used to specify a version number for the feature or the option '--version-increment' may also to increment the feature's version number.", array('@path' => $path));
+ case 'drush:features-components':
+ return dt("List feature components matching patterns. The listing may be limited to exported/not-exported components.
+
+A component pattern consists of a source, a colon and a component. Both source and component may be a full name (as in \"dependencies\"), a shorthand (for instance \"dep\") or a pattern (like \"%denci%\").
+
+Shorthands are unique shortenings of a name. They will only match if exactly one option contains the shorthand. So in a standard installation, \"dep\" will work for dependencies, but \"user\" wont, as it matches both user_permission and user_role.
+
+Patterns uses * or % for matching multiple sources/components. Unlike shorthands, patterns must match the whole name, so \"field:%article%\" should be used to select all fields containing \"article\" (which could both be those on the node type article, as well as those fields named article). * and % are equivalent, but the latter doesn't have to be escaped in UNIX shells.
+
+Lastly, a pattern without a colon is interpreted as having \":%\" appended, for easy listing of all components of a source.
+");
+ case 'drush:features-update':
+ return dt("Update a feature module on your site. The option '--version-set=foo' may be used to specify a version number for the feature or the option '--version-increment' may also to increment the feature's version number.");
+ case 'drush:features-update-all':
+ return dt("Update all feature modules on your site.");
+ case 'drush:features-revert':
+ return dt("Revert a feature module on your site.");
+ case 'drush:features-revert-all':
+ return dt("Revert all enabled feature module on your site.");
+ case 'drush:features-diff':
+ return dt("Show a diff of a feature module.");
+ case 'drush:features-add':
+ return dt("Add a component to a feature module. The option '--version-set=foo' may be used to specify a version number for the feature or the option '--version-increment' may also to increment the feature's version number.");
+ }
+}
+
+/**
+ * Get a list of all feature modules.
+ */
+function drush_features_list() {
+ $status = drush_get_option('status') ? drush_get_option('status') : 'all';
+ if (!in_array($status, array('enabled', 'disabled', 'all'))) {
+ return drush_set_error('', dt('!status is not valid', array('!status' => $status)));
+ }
+
+ module_load_include('inc', 'features', 'features.export');
+ $rows = array(array(dt('Name'), dt('Feature'), dt('Status'), dt('Version'), dt('State')));
+
+ // Sort the Features list before compiling the output.
+ $features = features_get_features(NULL, TRUE);
+ ksort($features);
+
+ foreach ($features as $k => $m) {
+ switch (features_get_storage($m->name)) {
+ case FEATURES_DEFAULT:
+ case FEATURES_REBUILDABLE:
+ $storage = '';
+ break;
+ case FEATURES_OVERRIDDEN:
+ $storage = dt('Overridden');
+ break;
+ case FEATURES_NEEDS_REVIEW:
+ $storage = dt('Needs review');
+ break;
+ }
+ if (
+ ($m->status == 0 && ($status == 'all' || $status == 'disabled')) ||
+ ($m->status == 1 && ($status == 'all' || $status == 'enabled'))
+ ) {
+ $rows[] = array(
+ $m->info['name'],
+ $m->name,
+ $m->status ? dt('Enabled') : dt('Disabled'),
+ $m->info['version'],
+ $storage
+ );
+ }
+ }
+ drush_print_table($rows, TRUE);
+}
+
+/**
+ * List components, with pattern matching.
+ */
+function drush_features_components() {
+ $args = func_get_args();
+ $components = _drush_features_component_list();
+ ksort($components);
+ // If no args supplied, prompt with a list.
+ if (empty($args)) {
+ $types = array_keys($components);
+ array_unshift($types, 'all');
+ $choice = drush_choice($types, 'Enter a number to choose which component type to list.');
+ if ($choice === FALSE) {
+ return;
+ }
+
+ $args = ($choice == 0) ? array('*') : array($types[$choice]);
+ }
+ $options = array(
+ 'provided by' => TRUE,
+ );
+ if (drush_get_option(array('exported', 'e'), NULL)) {
+ $options['not exported'] = FALSE;
+ }
+ elseif (drush_get_option(array('not-exported', 'o'), NULL)) {
+ $options['exported'] = FALSE;
+ }
+ if (drush_get_option(array('info-style', 'is'), NULL)) {
+ $options['info style'] = TRUE;
+ }
+
+ $filtered_components = _drush_features_component_filter($components, $args, $options);
+ if ($filtered_components){
+ _drush_features_component_print($filtered_components, $options);
+ }
+}
+
+/**
+ * Returns a listing of all known components, indexed by source.
+ */
+function _drush_features_component_list() {
+ $components = array();
+ foreach (features_get_feature_components() as $source => $info) {
+ if ($options = features_invoke($source, 'features_export_options')) {
+ foreach ($options as $name => $title) {
+ $components[$source][$name] = $title;
+ }
+ }
+ }
+ return $components;
+}
+
+/**
+ * Filters components by patterns.
+ */
+function _drush_features_component_filter($all_components, $patterns = array(), $options = array()) {
+ $options += array(
+ 'exported' => TRUE,
+ 'not exported' => TRUE,
+ 'provided by' => FALSE,
+ );
+ $pool = array();
+ // Maps exported components to feature modules.
+ $components_map = features_get_component_map();
+ // First filter on exported state.
+ foreach ($all_components as $source => $components) {
+ foreach ($components as $name => $title) {
+ $exported = !empty($components_map[$source][$name]);
+ if ($exported) {
+ if ($options['exported']) {
+ $pool[$source][$name] = $title;
+ }
+ }
+ else {
+ if ($options['not exported']) {
+ $pool[$source][$name] = $title;
+ }
+ }
+ }
+ }
+
+ $state_string = '';
+
+ if (!$options['exported']) {
+ $state_string = 'unexported';
+ }
+ elseif (!$options['not exported']) {
+ $state_string = 'exported';
+ }
+
+ $selected = array();
+ foreach ($patterns as $pattern) {
+ // Rewrite * to %. Let users use both as wildcard.
+ $pattern = strtr($pattern, array('*' => '%'));
+ $sources = array();
+ $source_pattern = strtok($pattern, ':');
+ $component_pattern = strtok(':');
+ // If source is empty, use a pattern.
+ if ($source_pattern == '') {
+ $source_pattern = '%';
+ }
+ if ($component_pattern == '') {
+ $component_pattern = '%';
+ }
+
+ $preg_source_pattern = strtr(preg_quote($source_pattern, '/'), array('%' => '.*'));
+ $preg_component_pattern = strtr(preg_quote($component_pattern, '/'), array('%' => '.*'));
+ /*
+ * If it isn't a pattern, but a simple string, we don't anchor the
+ * pattern, this allows for abbreviating. Else, we do, as this seems more
+ * natural for patterns.
+ */
+ if (strpos($source_pattern, '%') !== FALSE) {
+ $preg_source_pattern = '^' . $preg_source_pattern . '$';
+ }
+ if (strpos($component_pattern, '%') !== FALSE) {
+ $preg_component_pattern = '^' . $preg_component_pattern . '$';
+ }
+ $matches = array();
+
+ // Find the sources.
+ $all_sources = array_keys($pool);
+ $matches = preg_grep('/' . $preg_source_pattern . '/', $all_sources);
+ if (sizeof($matches) > 0) {
+ // If we have multiple matches and the source string wasn't a
+ // pattern, check if one of the matches is equal to the pattern, and
+ // use that, or error out.
+ if (sizeof($matches) > 1 and $preg_source_pattern[0] != '^') {
+ if (in_array($source_pattern, $matches)) {
+ $matches = array($source_pattern);
+ }
+ else {
+ return drush_set_error('', dt('Ambiguous source "!source", matches !matches', array('!source' => $source_pattern, '!matches' => join(', ', $matches))));
+ }
+ }
+ // Loose the indexes preg_grep preserved.
+ $sources = array_values($matches);
+ }
+ else {
+ return drush_set_error('', dt('No !state sources match "!source"', array('!state' => $state_string, '!source' => $source_pattern)));
+ }
+
+
+ // Now find the components.
+ foreach ($sources as $source) {
+ // Find the components.
+ $all_components = array_keys($pool[$source]);
+ // See if there's any matches.
+ $matches = preg_grep('/' . $preg_component_pattern . '/', $all_components);
+ if (sizeof($matches) > 0) {
+ // If we have multiple matches and the components string wasn't a
+ // pattern, check if one of the matches is equal to the pattern, and
+ // use that, or error out.
+ if (sizeof($matches) > 1 and $preg_component_pattern[0] != '^') {
+ if (in_array($component_pattern, $matches)) {
+ $matches = array($component_pattern);
+ }
+ else {
+ return drush_set_error('', dt('Ambiguous component "!component", matches !matches', array('!component' => $component_pattern, '!matches' => join(', ', $matches))));
+ }
+ }
+ if (empty($selected[$source])) {
+ $selected[$source] = array();
+ }
+ $selected[$source] += array_intersect_key($pool[$source], array_flip($matches));
+ }
+ else {
+ // No matches. If the source was a pattern, just carry on, else
+ // error out. Allows for patterns like :*field*
+ if ($preg_source_pattern[0] != '^') {
+ return drush_set_error('', dt('No !state !source components match "!component"', array('!state' => $state_string, '!component' => $component_pattern, '!source' => $source)));
+ }
+ }
+ }
+ }
+
+ // Lastly, provide feature module information on the selected components, if
+ // requested.
+ $provided_by = array();
+ if ($options['provided by'] && $options['exported'] ) {
+ foreach ($selected as $source => $components) {
+ foreach ($components as $name => $title) {
+ $exported = !empty($components_map[$source][$name]);
+ if ($exported) {
+ $provided_by[$source . ':' . $name] = join(', ', $components_map[$source][$name]);
+ }
+ }
+ }
+ }
+
+ return array(
+ 'components' => $selected,
+ 'sources' => $provided_by,
+ );
+}
+
+/**
+ * Prints a list of filtered components.
+ */
+function _drush_features_component_print($filtered_components, $options = array()) {
+ $rows = array(array(dt('Available sources')));
+ foreach ($filtered_components['components'] as $source => $components) {
+ foreach ($components as $name => $value) {
+ if (!empty($options['info style'])) {
+ // Output as .info file style.
+ $row = array('features[' . $source . '][] = "' . $name . '"');
+ }
+ else {
+ $row = array($source .':'. $name);
+ }
+ if (isset($filtered_components['sources'][$source .':'. $name])) {
+ $row[] = dt('Provided by') . ': ' . $filtered_components['sources'][$source .':'. $name];
+ }
+ $rows[] = $row;
+ }
+ }
+
+ drush_print_table($rows, TRUE);
+}
+
+/**
+ * Add a component to a features module, or create a new module with
+ * the selected components.
+ */
+function drush_features_export() {
+ if ($args = func_get_args()) {
+ $module = array_shift($args);
+ if (empty($args)) {
+ return drush_set_error('', 'No components supplied.');
+ }
+ $components = _drush_features_component_list();
+ $options = array();
+
+ if (!drush_get_option('ignore-conflicts', FALSE)) {
+ $options['exported'] = FALSE;
+ }
+
+ $filtered_components = _drush_features_component_filter($components, $args, $options);
+ $items = $filtered_components['components'];
+
+ if (empty($items)) {
+ return drush_set_error('', 'No components to add.');
+ }
+
+ $items = array_map('array_keys', $items);
+
+ if (($feature = features_load_feature($module, TRUE)) && module_exists($module)) {
+ module_load_include('inc', 'features', 'features.export');
+ _features_populate($items, $feature->info, $feature->name);
+ _drush_features_export($feature->info, $feature->name, dirname($feature->filename));
+ }
+ elseif ($feature) {
+ _features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED');
+ }
+ else {
+ // Same logic as in _drush_features_export. Should be refactored.
+ $destination = drush_get_option(array('destination'), variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH));
+ $directory = isset($directory) ? $directory : $destination . '/' . $module;
+ drush_print(dt('Will create a new module in !dir', array('!dir' => $directory)));
+ if (!drush_confirm(dt('Do you really want to continue?'))) {
+ drush_die('Aborting.');
+ }
+ $export = _drush_features_generate_export($items, $module);
+ _features_populate($items, $export[info], $export[name]);
+ _drush_features_export($export['info'], $module, $directory);
+ }
+ }
+ else {
+ return drush_set_error('', 'No feature name given.');
+ }
+}
+
+/**
+ * Add a component to a features module
+ * the selected components.
+ *
+ * This is DEPRECATED, but keeping it around for a bit longer for user migration
+ */
+function drush_features_add() {
+ drush_print(dt('features-add is DEPRECATED.'));
+ drush_print(dt('Calling features-export instead.'));
+ drush_features_export();
+}
+
+
+/**
+ * Update an existing feature module.
+ */
+function drush_features_update() {
+ if ($args = func_get_args()) {
+ foreach ($args as $module) {
+ if (($feature = features_load_feature($module, TRUE)) && module_exists($module)) {
+ _drush_features_export($feature->info, $feature->name, dirname($feature->filename));
+ }
+ elseif ($feature) {
+ _features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED');
+ }
+ else {
+ _features_drush_set_error($module);
+ }
+ }
+ }
+ else {
+ // By default just show contexts that are available.
+ $rows = array(array(dt('Available features')));
+ foreach (features_get_features(NULL, TRUE) as $name => $info) {
+ $rows[] = array($name);
+ }
+ drush_print_table($rows, TRUE);
+ }
+}
+
+/**
+ * Update all enabled features. Optionally pass in a list of features to
+ * exclude from being updated.
+ */
+function drush_features_update_all() {
+ $features_to_update = array();
+ $features_to_exclude = func_get_args();
+ foreach (features_get_features() as $module) {
+ if ($module->status && !in_array($module->name, $features_to_exclude)) {
+ $features_to_update[] = $module->name;
+ }
+ }
+ $dt_args = array('!modules' => implode(', ', $features_to_update));
+ drush_print(dt('The following modules will be updated: !modules', $dt_args));
+ if (!drush_confirm(dt('Do you really want to continue?'))) {
+ return drush_user_abort('Aborting.');
+ }
+ // If we got here, set affirmative to TRUE, so that the user doesn't have to
+ // confirm each and every feature. Start off by storing the current value,
+ // so we can set it back afteward.
+ $skip_confirmation = drush_get_context('DRUSH_AFFIRMATIVE');
+ drush_set_context('DRUSH_AFFIRMATIVE', TRUE);
+ // Now update all the features.
+ drush_invoke('features-update', $features_to_update);
+ // Now set it back as it was, in case other commands are called after this.
+ drush_set_context('DRUSH_AFFIRMATIVE', $skip_confirmation);
+}
+
+/**
+ * Write a module to the site dir.
+ *
+ * @param $info
+ * The feature info associative array.
+ * @param $module_name
+ * Optional. The name for the exported module.
+ */
+function _drush_features_export($info, $module_name = NULL, $directory = NULL) {
+ $root = drush_get_option(array('r', 'root'), drush_locate_root());
+ $skip_confirmation = drush_get_context('DRUSH_AFFIRMATIVE');
+ if ($root) {
+ $destination = drush_get_option(array('destination'), variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH));
+ $directory = isset($directory) ? $directory : $destination . '/' . $module_name;
+ if (is_dir($directory)) {
+ // If we aren't skipping confirmation and the directory already exists,
+ // prompt the user. This message most make sense for but fe and fu.
+ if (!$skip_confirmation && !drush_confirm(dt('Module located at !dir will be updated. Do you want to continue?', array('!dir' => $directory)))) {
+ drush_die('Aborting.');
+ }
+ }
+ else {
+ drush_op('mkdir', $directory);
+ }
+ if (is_dir($directory)) {
+ // Ensure that the export will be created in the English language.
+ // The export language must be set before flushing caches as that can
+ // result into translatables being statically cached.
+ _features_set_export_language();
+
+ drupal_flush_all_caches();
+ $export = _drush_features_generate_export($info, $module_name);
+ $files = features_export_render($export, $module_name, TRUE);
+ // Copy any files if _files key is there.
+ if (!empty($files['_files'])) {
+ foreach ($files['_files'] as $file_name => $file_info) {
+ // See if files are in a sub directory.
+ if (strpos($file_name, '/')) {
+ $file_directory = $directory . '/' . substr($file_name, 0, strrpos($file_name, '/'));
+ if (!is_dir($file_directory)) {
+ drush_op('mkdir', $file_directory);
+ }
+ }
+ if (!empty($file_info['file_path'])) {
+ drush_op('file_unmanaged_copy', $file_info['file_path'], "{$directory}/{$file_name}", FILE_EXISTS_REPLACE);
+ }
+ elseif (!empty($file_info['file_content'])) {
+ drush_op('file_put_contents', "{$directory}/{$file_name}", $file_info['file_content']);
+ }
+ else {
+ drush_log(dt("Entry for @file_name.in !module is invalid. ", array('!module' => $module_name, '@file_name' => $file_name)), 'ok');
+ }
+ }
+ unset($files['_files']);
+ }
+ foreach ($files as $extension => $file_contents) {
+ if (!in_array($extension, array('module', 'info'))) {
+ $extension .= '.inc';
+ }
+ drush_op('file_put_contents', "{$directory}/{$module_name}.$extension", $file_contents);
+ }
+ drush_log(dt("Created module: !module in !directory", array('!module' => $module_name, '!directory' => $directory)), 'ok');
+ }
+ else {
+ drush_die(dt('Couldn\'t create directory !directory', array('!directory' => $directory)));
+ }
+ }
+ else {
+ drush_die(dt('Couldn\'t locate site root'));
+ }
+}
+
+/**
+ * Helper function for _drush_feature_export.
+ *
+ * @param $info
+ * The feature info associative array.
+ * @param $module_name
+ * Optional. The name for the exported module.
+ */
+function _drush_features_generate_export(&$info, &$module_name) {
+ module_load_include('inc', 'features', 'features.export');
+ $export = features_populate($info, $module_name);
+ if (!features_load_feature($module_name)) {
+ $export['name'] = $module_name;
+ }
+ // Set the feature version if the --version-set or --version-increment option is passed.
+ if ($version = drush_get_option(array('version-set'))) {
+ preg_match('/^(?P\d+\.x)-(?P\d+)\.(?P\d+)-?(?P\w+)?$/', $version, $matches);
+ if (!isset($matches['core'], $matches['major'])) {
+ drush_die(dt('Please enter a valid version with core and major version number. Example: !example', array('!example' => '7.x-1.0')));
+ }
+ $export['version'] = $version;
+ }
+ elseif ($version = drush_get_option(array('version-increment'))) {
+ // Determine current version and increment it.
+ $export_load = features_export_prepare($export, $module_name);
+ $version = $export_load['version'];
+ $version_explode = explode('.', $version);
+ $version_minor = array_pop($version_explode);
+ // Increment minor version number if numeric or not a dev release.
+ if (is_numeric($version_minor) || strpos($version_minor, 'dev') !== (strlen($version_minor) - 3)) {
+ // Check for prefixed versions (alpha, beta, rc).
+ if (ctype_digit($version_minor)) {
+ ++$version_minor;
+ }
+ else {
+ // Split version number parts.
+ $pattern = '/([0-9]-[a-z]+([0-9])+)/';
+ $matches = array();
+ preg_match($pattern, $version_minor, $matches);
+ $number = array_pop($matches);
+ ++$number;
+ $pattern = '/[0-9]+$/';
+ $version_minor = preg_replace($pattern, $number, $version_minor);
+ }
+ }
+ array_push($version_explode, $version_minor);
+ // Rebuild version string.
+ $version = implode('.', $version_explode);
+ $export['version'] = $version;
+ }
+ return $export;
+}
+
+/**
+ * Revert a feature to it's code definition.
+ * Optionally accept a list of components to revert.
+ */
+function drush_features_revert() {
+ if ($args = func_get_args()) {
+ module_load_include('inc', 'features', 'features.export');
+ features_include();
+
+ // Determine if revert should be forced.
+ $force = drush_get_option('force');
+ // Determine if -y was supplied. If so, we can filter out needless output
+ // from this command.
+ $skip_confirmation = drush_get_context('DRUSH_AFFIRMATIVE');
+
+ // Parse list of arguments.
+ $modules = array();
+ foreach ($args as $arg) {
+ $arg = explode('.', $arg);
+ $module = array_shift($arg);
+ $component = array_shift($arg);
+
+ if (isset($module)) {
+ if (empty($component)) {
+ // If we received just a feature name, this means that we need all of it's components.
+ $modules[$module] = TRUE;
+ }
+ elseif ($modules[$module] !== TRUE) {
+ if (!isset($modules[$module])) {
+ $modules[$module] = array();
+ }
+ $modules[$module][] = $component;
+ }
+ }
+ }
+
+ // Process modules.
+ foreach ($modules as $module => $components_needed) {
+ $dt_args['@module'] = $module;
+ if (($feature = features_load_feature($module, TRUE)) && module_exists($module)) {
+
+ $components = array();
+ // Forcefully revert all components of a feature.
+ if ($force) {
+ foreach (array_keys($feature->info['features']) as $component) {
+ if (features_hook($component, 'features_revert')) {
+ $components[] = $component;
+ }
+ }
+ }
+ // Only revert components that are detected to be Overridden/Needs review/rebuildable.
+ else {
+ $states = features_get_component_states(array($feature->name), FALSE);
+ foreach ($states[$feature->name] as $component => $state) {
+ $revertable_states = array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW, FEATURES_REBUILDABLE);
+ if (in_array($state, $revertable_states) && features_hook($component, 'features_revert')) {
+ $components[] = $component;
+ }
+ }
+ }
+
+ if (!empty($components_needed) && is_array($components_needed)) {
+ $components = array_intersect($components, $components_needed);
+ }
+ if (empty($components)) {
+ drush_log(dt('Current state already matches defaults, aborting.'), 'ok');
+ }
+ else {
+ foreach ($components as $component) {
+ $dt_args['@component'] = $component;
+ $confirmation_message = 'Do you really want to revert @module.@component?';
+ if ($skip_confirmation || drush_confirm(dt($confirmation_message, $dt_args))) {
+ if (features_feature_is_locked($module, $component)) {
+ drush_log(dt('Skipping locked @module.@component.', $dt_args), 'ok');
+ }
+ else {
+ features_revert(array($module => array($component)));
+ drush_log(dt('Reverted @module.@component.', $dt_args), 'ok');
+ }
+ }
+ else {
+ drush_log(dt('Skipping @module.@component.', $dt_args), 'ok');
+ }
+ }
+ }
+ }
+ elseif ($feature) {
+ _features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED');
+ }
+ else {
+ _features_drush_set_error($module);
+ }
+ }
+ }
+ else {
+ drush_features_list();
+ return;
+ }
+}
+
+/**
+ * Revert all enabled features to their definitions in code.
+ *
+ * @param ...
+ * (Optional) A list of features to exclude from being reverted.
+ */
+function drush_features_revert_all() {
+ module_load_include('inc', 'features', 'features.export');
+ $force = drush_get_option('force');
+ $features_to_exclude = func_get_args();
+
+ $features_to_revert = array();
+ foreach (features_get_features(NULL, TRUE) as $module) {
+ if ($module->status && !in_array($module->name, $features_to_exclude)) {
+ // If forced, add module regardless of status.
+ if ($force) {
+ $features_to_revert[] = $module->name;
+ }
+ else {
+ switch (features_get_storage($module->name)) {
+ case FEATURES_OVERRIDDEN:
+ case FEATURES_NEEDS_REVIEW:
+ case FEATURES_REBUILDABLE:
+ $features_to_revert[] = $module->name;
+ break;
+ }
+ }
+ }
+ }
+
+ if ($features_to_revert) {
+ $dt_args = array('!modules' => implode(', ', $features_to_revert));
+ drush_print(dt('The following modules will be reverted: !modules', $dt_args));
+ // Confirm that the user would like to continue. If not, simply abort.
+ if (!drush_confirm(dt('Do you really want to continue?'))) {
+ return drush_user_abort('Aborting.');
+ }
+ drush_invoke('features-revert', $features_to_revert);
+ }
+ else {
+ drush_log(dt('Current state already matches defaults, aborting.'), 'ok');
+ }
+}
+
+/**
+ * Show the diff of a feature module.
+ */
+function drush_features_diff() {
+ if (!$args = func_get_args()) {
+ drush_features_list();
+ return;
+ }
+ $module = $args[0];
+ $filter_ctypes = drush_get_option("ctypes");
+ if ($filter_ctypes) {
+ $filter_ctypes = explode(',', $filter_ctypes);
+ }
+
+ $feature = features_load_feature($module);
+ if (!module_exists($module)) {
+ drush_log(dt('No such feature is enabled: ' . $module), 'error');
+ return;
+ }
+ module_load_include('inc', 'features', 'features.export');
+ $overrides = features_detect_overrides($feature);
+ if (empty($overrides)) {
+ drush_log(dt('Feature is in its default state. No diff needed.'), 'ok');
+ return;
+ }
+ module_load_include('inc', 'diff', 'diff.engine');
+
+ if (!class_exists('DiffFormatter')) {
+ if (drush_confirm(dt('It seems that the Diff module is not available. Would you like to download and enable it?'))) {
+ // Download it if it's not already here.
+ $project_info = drush_get_projects();
+ if (empty($project_info['diff']) && !drush_invoke('dl', array('diff'))) {
+ return drush_set_error(dt('Diff module could not be downloaded.'));
+ }
+
+ if (!drush_invoke('en', array('diff'))) {
+ return drush_set_error(dt('Diff module could not be enabled.'));
+ }
+ }
+ else {
+ return drush_set_error(dt('Diff module is not enabled.'));
+ }
+ // If we're still here, now we can include the diff.engine again.
+ module_load_include('inc', 'diff', 'diff.engine');
+ }
+
+ $lines = (int) drush_get_option('lines');
+ $lines = $lines > 0 ? $lines : 2;
+
+ $formatter = new DiffFormatter();
+ $formatter->leading_context_lines = $lines;
+ $formatter->trailing_context_lines = $lines;
+ $formatter->show_header = FALSE;
+
+ if (drush_get_context('DRUSH_NOCOLOR')) {
+ $red = $green = "%s";
+ }
+ else {
+ $red = "\033[31;40m\033[1m%s\033[0m";
+ $green = "\033[0;32;40m\033[1m%s\033[0m";
+ }
+
+ // Print key for colors
+ drush_print(dt('Legend: '));
+ drush_print(sprintf($red, dt('Code: drush features-revert will remove the overrides.')));
+ drush_print(sprintf($green, dt('Overrides: drush features-update will update the exported feature with the displayed overrides')));
+ drush_print();
+
+ if ($filter_ctypes) {
+ $overrides = array_intersect_key($overrides, array_flip($filter_ctypes));
+ }
+ foreach ($overrides as $component => $items) {
+ $diff = new Diff(explode("\n", $items['default']), explode("\n", $items['normal']));
+ drush_print();
+ drush_print(dt("Component type: !component", array('!component' => $component)));
+ $rows = explode("\n", $formatter->format($diff));
+ foreach ($rows as $row) {
+ if (strpos($row, '>') === 0) {
+ drush_print(sprintf($green, $row));
+ }
+ elseif (strpos($row, '<') === 0) {
+ drush_print(sprintf($red, $row));
+ }
+ else {
+ drush_print($row);
+ }
+ }
+ }
+}
+
+/**
+ * Diff all enabled features that are not in their default state.
+ *
+ * @param ...
+ * (Optional) A list of features to exclude from being reverted.
+ */
+function drush_features_diff_all() {
+ module_load_include('inc', 'features', 'features.export');
+ $features_to_exclude = func_get_args();
+
+ $features_to_revert = array();
+ foreach (features_get_features(NULL, TRUE) as $module) {
+ if ($module->status && !in_array($module->name, $features_to_exclude)) {
+ switch (features_get_storage($module->name)) {
+ case FEATURES_OVERRIDDEN:
+ case FEATURES_NEEDS_REVIEW:
+ case FEATURES_REBUILDABLE:
+ $features_to_diff[] = $module->name;
+ break;
+ }
+ }
+ }
+
+ if ($features_to_diff) {
+
+ // Check if the user wants to apply the force option.
+ $force = drush_get_option('force');
+
+ if($force) {
+ foreach ($features_to_diff as $module) {
+
+ drush_print(dt('Diff for !module:', array('!module' => $module)));
+
+ drush_invoke_process(drush_sitealias_get_record('@self'), 'features-diff', array($module));
+ }
+ }
+ else {
+
+ drush_print(dt('A diff will be performed for the following modules: !modules',
+ array('!modules' => implode(', ', $features_to_diff))
+ ));
+
+ if (drush_confirm(dt('Do you want to continue?'))) {
+ foreach ($features_to_diff as $module) {
+ if (drush_confirm(dt('Diff !module?', array('!module' => $module)))) {
+ drush_invoke_process(drush_sitealias_get_record('@self'), 'features-diff', array($module));
+ }
+ }
+ }
+ else {
+ return drush_user_abort('Aborting.');
+ }
+ }
+ }
+}
+
+/**
+ * Helper function to call drush_set_error().
+ *
+ * @param $feature
+ * The string name of the feature.
+ * @param $error
+ * A text string identifying the type of error.
+ * @return
+ * FALSE. See drush_set_error().
+ */
+function _features_drush_set_error($feature, $error = '') {
+ $args = array('!feature' => $feature);
+
+ switch ($error) {
+ case 'FEATURES_FEATURE_NOT_ENABLED':
+ $message = 'The feature !feature is not enabled.';
+ break;
+ case 'FEATURES_COMPONENT_NOT_FOUND':
+ $message = 'The given component !feature could not be found.';
+ break;
+ default:
+ $error = 'FEATURES_FEATURE_NOT_FOUND';
+ $message = 'The feature !feature could not be found.';
+ }
+
+ return drush_set_error($error, dt($message, $args));
+}
diff --git a/docroot/sites/all/modules/features/features.export.inc b/docroot/sites/all/modules/features/features.export.inc
new file mode 100644
index 0000000..5e72ef4
--- /dev/null
+++ b/docroot/sites/all/modules/features/features.export.inc
@@ -0,0 +1,1156 @@
+ array(), 'dependencies' => array(), 'conflicts' => array()) + $info + array('features_exclude' => array());
+ $export = _features_populate($items, $stub, $module_name, TRUE);
+
+ // Add Features API version. Any module with this entry in the .info file
+ // will be treated as a Feature and included in the admin/build/features UI.
+ $export['features']['features_api']['api:' . FEATURES_API] = TRUE;
+ // Allow other modules to alter the export.
+ drupal_alter('features_export', $export, $module_name);
+
+ // Clean up and standardize order
+ foreach (array_keys($export['features']) as $k) {
+ ksort($export['features'][$k]);
+ }
+ ksort($export['features']);
+ ksort($export['dependencies']);
+ ksort($export['features_exclude']);
+
+ return $export;
+}
+
+/**
+ * Iterate and descend into a feature definition to extract module
+ * dependencies and feature definition. Calls hook_features_export for modules
+ * that implement it.
+ *
+ * @param $pipe
+ * Associative of array of module => info-for-module
+ * @param $export
+ * Associative array of items, and module dependencies which define a feature.
+ * Passed by reference.
+ *
+ * @return fully populated $export array.
+ */
+function _features_populate($pipe, &$export, $module_name = '', $reset = FALSE) {
+ // Ensure that the export will be created in the english language.
+ _features_set_export_language();
+
+ if ($reset) {
+ drupal_static_reset(__FUNCTION__);
+ }
+ $processed = &drupal_static(__FUNCTION__, array());
+ features_include();
+ foreach ($pipe as $component => $data) {
+ // Convert already defined items to dependencies.
+// _features_resolve_dependencies($data, $export, $module_name, $component);
+ // Remove any excluded items.
+ if (!empty($export['features_exclude'][$component])) {
+ $data = array_diff($data, $export['features_exclude'][$component]);
+ if ($component == 'dependencies' && !empty($export['dependencies'])) {
+ $export['dependencies'] = array_diff($export['dependencies'], $export['features_exclude'][$component]);
+ }
+ }
+ if (!empty($data) && $function = features_hook($component, 'features_export')) {
+ // Pass module-specific data and export array.
+ // We don't use features_invoke() here since we need to pass $export by reference.
+ $more = $function($data, $export, $module_name, $component);
+ // Add the context information.
+ $export['component'] = $component;
+ $export['module_name'] = $module_name;
+ // Allow other modules to manipulate the pipe to add in additional modules.
+ drupal_alter(array('features_pipe', 'features_pipe_' . $component), $more, $data, $export);
+ // Remove the component information.
+ unset($export['component']);
+ unset($export['module_name']);
+ // Allow for export functions to request additional exports, but avoid
+ // circular references on already processed components.
+ $processed[$component] = isset($processed[$component]) ? array_merge($processed[$component], $data) : $data;
+
+ if (!empty($more)) {
+ // Remove already processed components.
+ foreach ($more as $component_name => $component_data) {
+ if (isset($processed[$component_name])) {
+ $more[$component_name] = array_diff($component_data, $processed[$component_name]);
+ }
+ }
+ if ($more = array_filter($more)) {
+ _features_populate($more, $export, $module_name);
+ }
+ }
+ }
+ }
+ return $export;
+}
+
+/**
+ * Iterates over data and convert to dependencies if already defined elsewhere.
+ */
+function _features_resolve_dependencies(&$data, &$export, $module_name, $component) {
+ if ($map = features_get_default_map($component)) {
+ foreach ($data as $key => $item) {
+ // If this node type is provided by a different module, add it as a dependency
+ if (isset($map[$item]) && $map[$item] != $module_name) {
+ $export['dependencies'][$map[$item]] = $map[$item];
+ unset($data[$key]);
+ }
+ }
+ }
+}
+
+/**
+ * Iterates over a list of dependencies and kills modules that are
+ * captured by other modules 'higher up'.
+ */
+function _features_export_minimize_dependencies($dependencies, $module_name = '') {
+ // Ensure that the module doesn't depend upon itself
+ if (!empty($module_name) && !empty($dependencies[$module_name])) {
+ unset($dependencies[$module_name]);
+ }
+
+ // Do some cleanup:
+ // - Remove modules required by Drupal core.
+ // - Protect against direct circular dependencies.
+ // - Remove "intermediate" dependencies.
+ $required = drupal_required_modules();
+ foreach ($dependencies as $k => $v) {
+ if (empty($v) || in_array($v, $required)) {
+ unset($dependencies[$k]);
+ }
+ else {
+ $module = features_get_modules($v);
+ if ($module && !empty($module->info['dependencies'])) {
+ // If this dependency depends on the module itself, we have a circular dependency.
+ // Don't let it happen. Only you can prevent forest fires.
+ if (in_array($module_name, $module->info['dependencies'])) {
+ unset($dependencies[$k]);
+ }
+ // Iterate through the dependency's dependencies and remove any dependencies
+ // that are captured by it.
+ else {
+ foreach ($module->info['dependencies'] as $j => $dependency) {
+ if (array_search($dependency, $dependencies) !== FALSE) {
+ $position = array_search($dependency, $dependencies);
+ unset($dependencies[$position]);
+ }
+ }
+ }
+ }
+ }
+ }
+ return drupal_map_assoc(array_unique($dependencies));
+}
+
+/**
+ * Iterates over a list of dependencies and maximize the list of modules.
+ */
+function _features_export_maximize_dependencies($dependencies, $module_name = '', $maximized = array(), $first = TRUE) {
+ foreach ($dependencies as $k => $v) {
+ $parsed_dependency = drupal_parse_dependency($v);
+ $name = $parsed_dependency['name'];
+ if (!in_array($name, $maximized)) {
+ $maximized[] = $name;
+ $module = features_get_modules($name);
+ if ($module && !empty($module->info['dependencies'])) {
+ $maximized = array_merge($maximized, _features_export_maximize_dependencies($module->info['dependencies'], $module_name, $maximized, FALSE));
+ }
+ }
+ }
+ return array_unique($maximized);
+}
+
+/**
+ * Prepare a feature export array into a finalized info array.
+ * @param $export
+ * An exported feature definition.
+ * @param $module_name
+ * The name of the module to be exported.
+ * @param $reset
+ * Boolean flag for resetting the module cache. Only set to true when
+ * doing a final export for delivery.
+ */
+function features_export_prepare($export, $module_name, $reset = FALSE, $add_deprecated = TRUE) {
+ $existing = features_get_modules($module_name, $reset);
+
+ // copy certain exports directly into info
+ $copy_list = array('scripts', 'stylesheets');
+ foreach ($copy_list as $item) {
+ if(isset($export[$item])) {
+ $existing->info[$item] = $export[$item];
+ }
+ }
+
+ // Prepare info string -- if module exists, merge into its existing info file
+ $defaults = !empty($existing->info) ? $existing->info : array('core' => '7.x', 'package' => 'Features');
+ $export = array_merge($defaults, $export);
+
+ $deprecated = features_get_deprecated();
+ // Cleanup info array
+ foreach ($export['features'] as $component => $data) {
+ // if performing the final export, do not export deprecated components
+ if (($reset || !$add_deprecated) && !empty($deprecated[$component])) {
+ unset($export['features'][$component]);
+ }
+ else {
+ $export['features'][$component] = array_keys($data);
+ }
+ }
+ if (isset($export['dependencies'])) {
+ $export['dependencies'] = array_values($export['dependencies']);
+ }
+ if (isset($export['conflicts'])) {
+ unset($export['conflicts']);
+ }
+
+ // Order info array.
+ $standard_info = array();
+ foreach (array_merge(array('name', 'description', 'core', 'package', 'version', 'project', 'dependencies'), $copy_list) as $item) {
+ if (isset($export[$item])) {
+ $standard_info[$item] = $export[$item];
+ }
+ }
+ if (isset($export['php']) && ($export['php'] != DRUPAL_MINIMUM_PHP)) {
+ $standard_info['php'] = $export['php'];
+ }
+ unset($export['php']);
+
+ $export = features_array_diff_assoc_recursive($export, $standard_info);
+ ksort($export);
+ return array_merge($standard_info, $export);
+}
+
+/**
+ * Generate an array of hooks and their raw code.
+ */
+function features_export_render_hooks($export, $module_name, $reset = FALSE) {
+ features_include();
+ $code = array();
+
+ // Sort components to keep exported code consistent
+ ksort($export['features']);
+
+ foreach ($export['features'] as $component => $data) {
+ if (!empty($data)) {
+ // Sort the items so that we don't generate different exports based on order
+ asort($data);
+ if (features_hook($component, 'features_export_render')) {
+ $hooks = features_invoke($component, 'features_export_render', $module_name, $data, $export);
+ $code[$component] = $hooks;
+ }
+ }
+ }
+ return $code;
+}
+
+/**
+ * Render feature export into an array representing its files.
+ *
+ * @param $export
+ * An exported feature definition.
+ * @param $module_name
+ * The name of the module to be exported.
+ * @param $reset
+ * Boolean flag for resetting the module cache. Only set to true when
+ * doing a final export for delivery.
+ *
+ * @return array of info file and module file contents.
+ */
+function features_export_render($export, $module_name, $reset = FALSE) {
+ $code = array();
+
+ // Generate hook code
+ $component_hooks = features_export_render_hooks($export, $module_name, $reset);
+ $components = features_get_components();
+ $deprecated = features_get_deprecated($components);
+
+ // Group component code into their respective files
+ foreach ($component_hooks as $component => $hooks) {
+ if ($reset && !empty($deprecated[$component])) {
+ // skip deprecated components on final export
+ continue;
+ }
+ $file = array('name' => 'features');
+ if (isset($components[$component]['default_file'])) {
+ switch ($components[$component]['default_file']) {
+ case FEATURES_DEFAULTS_INCLUDED:
+ $file['name'] = "features.$component";
+ break;
+ case FEATURES_DEFAULTS_CUSTOM:
+ $file['name'] = $components[$component]['default_filename'];
+ break;
+ }
+ }
+
+ if (!isset($code[$file['name']])) {
+ $code[$file['name']] = array();
+ }
+
+ foreach ($hooks as $hook_name => $hook_info) {
+ // These are purely files that will be copied over.
+ if (is_array($hook_info) && (!empty($hook_info['file_path']) || !empty($hook_info['file_content']))) {
+ $code['_files'][$hook_name] = $hook_info;
+ continue;
+ }
+ $hook_code = is_array($hook_info) ? $hook_info['code'] : $hook_info;
+ $hook_args = is_array($hook_info) && !empty($hook_info['args']) ? $hook_info['args'] : '';
+ $hook_file = is_array($hook_info) && !empty($hook_info['file']) ? $hook_info['file'] : $file['name'];
+ $code[$hook_file][$hook_name] = features_export_render_defaults($module_name, $hook_name, $hook_code, $hook_args);
+ }
+ }
+
+ // Finalize strings to be written to files
+ $code = array_filter($code);
+ foreach ($code as $filename => $contents) {
+ if ($filename != '_files') {
+ $code[$filename] = "filename);
+
+ // If the current module file does not reference the features.inc include,
+ // @TODO this way of checking does not account for the possibility of inclusion instruction being commented out.
+ if (isset($code['features']) && strpos($code['module'], "{$module_name}.features.inc") === FALSE) {
+ // If .module does not begin with "{$module_name}.module", '@include' => "{$module_name}.features.inc")), 'warning');
+ }
+ else {
+ // Remove the old message if it exists, else just remove the name, $deprecated_files, TRUE)) {
+ features_log(t('The file @filename has been deprecated and can be removed.', array('@filename' => $file->filename)), 'status');
+ }
+ elseif ($file->name === "{$module_name}.features" && empty($code['features'])) {
+ // Try and remove features.inc include.
+ if (strpos($code['module'], "{$module_name}.features.inc")) {
+ $code['module'] = str_replace($modulefile_features_inc, $modulefile_blank, $code['module']);
+ }
+ // If unable to remove the include, add a message to remove.
+ if (strpos($code['module'], "{$module_name}.features.inc")) {
+ $code['features'] = "name])) {
+ // Rebuild feature from .info file description and prepare an export from current DB state.
+ $export = features_populate($module->info, $module->name);
+ $export = features_export_prepare($export, $module->name, FALSE, FALSE);
+
+ $overridden = array();
+
+ // Compare feature info
+ features_sanitize($module->info);
+ features_sanitize($export);
+
+ $compare = array('normal' => features_export_info($export), 'default' => features_export_info($module->info));
+ if ($compare['normal'] !== $compare['default']) {
+ $overridden['info'] = $compare;
+ }
+
+ // Collect differences at a per-component level
+ $states = features_get_component_states(array($module->name), FALSE);
+ foreach ($states[$module->name] as $component => $state) {
+ if ($state != FEATURES_DEFAULT) {
+ $normal = features_get_normal($component, $module->name);
+ $default = features_get_default($component, $module->name);
+ features_sanitize($normal, $component);
+ features_sanitize($default, $component);
+
+ $compare = array('normal' => features_var_export($normal), 'default' => features_var_export($default));
+ if (_features_linetrim($compare['normal']) !== _features_linetrim($compare['default'])) {
+ $overridden[$component] = $compare;
+ }
+ }
+ }
+ $cache[$module->name] = $overridden;
+ }
+ return $cache[$module->name];
+}
+
+/**
+ * Gets the available default hooks keyed by components.
+ */
+function features_get_default_hooks($component = NULL, $reset = FALSE) {
+ return features_get_components($component, 'default_hook', $reset);
+}
+
+/**
+ * Gets the available default hooks keyed by components.
+ */
+function features_get_default_alter_hook($component) {
+ $default_hook = features_get_components($component, 'default_hook');
+ $alter_hook = features_get_components($component, 'alter_hook');
+ $alter_type = features_get_components($component, 'alter_type');
+ return empty($alter_type) || $alter_type != 'none' ? ($alter_hook ? $alter_hook : $default_hook) : FALSE;
+}
+
+/**
+ * Return a code string representing an implementation of a defaults module hook.
+ */
+function features_export_render_defaults($module, $hook, $code, $args = '') {
+ $output = array();
+ $output[] = "/**";
+ $output[] = " * Implements hook_{$hook}().";
+ $output[] = " */";
+ $output[] = "function {$module}_{$hook}(" . $args . ") {";
+ $output[] = $code;
+ $output[] = "}";
+ return implode("\n", $output);
+}
+
+/**
+ * Generate code friendly to the Drupal .info format from a structured array.
+ *
+ * @param $info
+ * An array or single value to put in a module's .info file.
+ * @param $parents
+ * Array of parent keys (internal use only).
+ *
+ * @return
+ * A code string ready to be written to a module's .info file.
+ */
+function features_export_info($info, $parents = array()) {
+ $output = '';
+ if (is_array($info)) {
+ foreach ($info as $k => $v) {
+ $child = $parents;
+ $child[] = $k;
+ $output .= features_export_info($v, $child);
+ }
+ }
+ else if (!empty($info) && count($parents)) {
+ $line = array_shift($parents);
+ foreach ($parents as $key) {
+ $line .= is_numeric($key) ? "[]" : "[{$key}]";
+ }
+ $line .= " = {$info}\n";
+ return $line;
+ }
+ return $output;
+}
+
+/**
+ * Tar creation function. Written by dmitrig01.
+ *
+ * @param $name
+ * Filename of the file to be tarred.
+ * @param $contents
+ * String contents of the file.
+ *
+ * @return
+ * A string of the tar file contents.
+ */
+function features_tar_create($name, $contents) {
+ /* http://www.mkssoftware.com/docs/man4/tar.4.asp */
+ /* http://www.phpclasses.org/browse/file/21200.html */
+ $tar = '';
+ $bigheader = $header = '';
+ if (strlen($name) > 100) {
+ $bigheader = pack("a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12",
+ '././@LongLink', '0000000', '0000000', '0000000',
+ sprintf("%011o", strlen($name)), '00000000000',
+ ' ', 'L', '', 'ustar ', '0',
+ '', '', '', '', '', '');
+
+ $bigheader .= str_pad($name, floor((strlen($name) + 512 - 1) / 512) * 512, "\0");
+
+ $checksum = 0;
+ for ($i = 0; $i < 512; $i++) {
+ $checksum += ord(substr($bigheader, $i, 1));
+ }
+ $bigheader = substr_replace($bigheader, sprintf("%06o", $checksum)."\0 ", 148, 8);
+ }
+ $header = pack("a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12", // book the memorie area
+ substr($name,0,100), // 0 100 File name
+ '100644 ', // File permissions
+ ' 765 ', // UID,
+ ' 765 ', // GID,
+ sprintf("%11s ", decoct(strlen($contents))), // Filesize,
+ sprintf("%11s", decoct(REQUEST_TIME)), // Creation time
+ ' ', // 148 8 Check sum for header block
+ '', // 156 1 Link indicator / ustar Type flag
+ '', // 157 100 Name of linked file
+ 'ustar ', // 257 6 USTAR indicator "ustar"
+ ' ', // 263 2 USTAR version "00"
+ '', // 265 32 Owner user name
+ '', // 297 32 Owner group name
+ '', // 329 8 Device major number
+ '', // 337 8 Device minor number
+ '', // 345 155 Filename prefix
+ ''); // 500 12 ??
+
+ $checksum = 0;
+ for ($i = 0; $i < 512; $i++) {
+ $checksum += ord(substr($header, $i, 1));
+ }
+ $header = substr_replace($header, sprintf("%06o", $checksum)."\0 ", 148, 8);
+ $tar = $bigheader.$header;
+
+ $buffer = str_split($contents, 512);
+ foreach ($buffer as $item) {
+ $tar .= pack("a512", $item);
+ }
+ return $tar;
+}
+
+/**
+ * Export var function -- from Views.
+ */
+function features_var_export($var, $prefix = '', $init = TRUE, $count = 0) {
+ if ($count > 50) {
+ watchdog('features', 'Recursion depth reached in features_var_export', array());
+ return '';
+ }
+
+ if (is_object($var)) {
+ $output = method_exists($var, 'export') ? $var->export() : features_var_export((array) $var, '', FALSE, $count+1);
+ }
+ else if (is_array($var)) {
+ if (empty($var)) {
+ $output = 'array()';
+ }
+ else {
+ $output = "array(\n";
+ foreach ($var as $key => $value) {
+ // Using normal var_export on the key to ensure correct quoting.
+ $output .= " " . var_export($key, TRUE) . " => " . features_var_export($value, ' ', FALSE, $count+1) . ",\n";
+ }
+ $output .= ')';
+ }
+ }
+ else if (is_bool($var)) {
+ $output = $var ? 'TRUE' : 'FALSE';
+ }
+ else if (is_int($var)) {
+ $output = intval($var);
+ }
+ else if (is_numeric($var)) {
+ $floatval = floatval($var);
+ if (is_string($var) && ((string) $floatval !== $var)) {
+ // Do not convert a string to a number if the string
+ // representation of that number is not identical to the
+ // original value.
+ $output = var_export($var, TRUE);
+ }
+ else {
+ $output = $floatval;
+ }
+ }
+ else if (is_string($var) && strpos($var, "\n") !== FALSE) {
+ // Replace line breaks in strings with a token for replacement
+ // at the very end. This protects whitespace in strings from
+ // unintentional indentation.
+ $var = str_replace("\n", "***BREAK***", $var);
+ $output = var_export($var, TRUE);
+ }
+ else {
+ $output = var_export($var, TRUE);
+ }
+
+ if ($prefix) {
+ $output = str_replace("\n", "\n$prefix", $output);
+ }
+
+ if ($init) {
+ $output = str_replace("***BREAK***", "\n", $output);
+ }
+
+ return $output;
+}
+
+/**
+ * Helper function to return an array of t()'d translatables strings.
+ * Useful for providing a separate array of translatables with your
+ * export so that string extractors like potx can detect them.
+ */
+function features_translatables_export($translatables, $indent = '') {
+ $output = '';
+ $translatables = array_filter(array_unique($translatables));
+ if (!empty($translatables)) {
+ $output .= "{$indent}// Translatables\n";
+ $output .= "{$indent}// Included for use with string extractors like potx.\n";
+ sort($translatables);
+ foreach ($translatables as $string) {
+ $output .= "{$indent}t(" . features_var_export($string) . ");\n";
+ }
+ }
+ return $output;
+}
+
+/**
+ * Get a summary storage state for a feature.
+ */
+function features_get_storage($module_name) {
+ // Get component states, and array_diff against array(FEATURES_DEFAULT).
+ // If the returned array has any states that don't match FEATURES_DEFAULT,
+ // return the highest state.
+ $states = features_get_component_states(array($module_name), FALSE);
+ $states = array_diff($states[$module_name], array(FEATURES_DEFAULT));
+ $storage = !empty($states) ? max($states) : FEATURES_DEFAULT;
+ return $storage;
+}
+
+/**
+ * Wrapper around features_get_[storage] to return an md5hash of a normalized
+ * defaults/normal object array. Can be used to compare normal/default states
+ * of a module's component.
+ */
+function features_get_signature($state = 'default', $module_name, $component, $reset = FALSE) {
+ switch ($state) {
+ case 'cache':
+ $codecache = variable_get('features_codecache', array());
+ return isset($codecache[$module_name][$component]) ? $codecache[$module_name][$component] : FALSE;
+ case 'default':
+ $objects = features_get_default($component, $module_name, TRUE, $reset);
+ break;
+ case 'normal':
+ $objects = features_get_normal($component, $module_name, $reset);
+ break;
+ }
+ if (!empty($objects)) {
+ features_sanitize($objects, $component);
+ return md5(_features_linetrim(features_var_export($objects)));
+ }
+ return FALSE;
+}
+
+/**
+ * Set the signature of a module/component pair in the codecache.
+ */
+function features_set_signature($module, $component, $signature = NULL) {
+ $var_codecache = variable_get('features_codecache', array());
+ $signature = isset($signature) ? $signature : features_get_signature('default', $module, $component, TRUE);
+ $var_codecache[$module][$component] = $signature;
+ variable_set('features_codecache', $var_codecache);
+}
+
+/**
+ * Processing semaphore operations.
+ */
+function features_semaphore($op, $component) {
+ // Note: we don't use variable_get() here as the inited variable
+ // static cache may be stale. Retrieving directly from the DB narrows
+ // the possibility of collision.
+ $semaphore = db_query("SELECT value FROM {variable} WHERE name = :name", array(':name' => 'features_semaphore'))->fetchField();
+ $semaphore = !empty($semaphore) ? unserialize($semaphore) : array();
+
+ switch ($op) {
+ case 'get':
+ return isset($semaphore[$component]) ? $semaphore[$component] : FALSE;
+ case 'set':
+ $semaphore[$component] = REQUEST_TIME;
+ variable_set('features_semaphore', $semaphore);
+ break;
+ case 'del':
+ if (isset($semaphore[$component])) {
+ unset($semaphore[$component]);
+ variable_set('features_semaphore', $semaphore);
+ }
+ break;
+ }
+}
+
+/**
+ * Get normal objects for a given module/component pair.
+ */
+function features_get_normal($component, $module_name, $reset = FALSE) {
+ if ($reset) {
+ drupal_static_reset(__FUNCTION__);
+ }
+ $cache = &drupal_static(__FUNCTION__, array());
+ if (!isset($cache[$module_name][$component])) {
+ features_include();
+ $code = NULL;
+ $module = features_get_features($module_name);
+
+ // Special handling for dependencies component.
+ if ($component === 'dependencies') {
+ $cache[$module_name][$component] = isset($module->info['dependencies']) ? array_filter($module->info['dependencies'], '_features_module_exists') : array();
+ }
+ // All other components.
+ else {
+ $default_hook = features_get_default_hooks($component);
+ if ($module && $default_hook && isset($module->info['features'][$component]) && features_hook($component, 'features_export_render')) {
+ $code = features_invoke($component, 'features_export_render', $module_name, $module->info['features'][$component], NULL);
+ $cache[$module_name][$component] = isset($code[$default_hook]) ? eval($code[$default_hook]) : FALSE;
+ }
+ }
+
+ // Clear out vars for memory's sake.
+ unset($code);
+ unset($module);
+ }
+ return isset($cache[$module_name][$component]) ? $cache[$module_name][$component] : FALSE;
+}
+
+/**
+ * Helper function to determine if a module is enabled
+ * @param $module
+ * This module name comes from the .info file and can have version info in it.
+ */
+function _features_module_exists($module) {
+ $parsed_dependency = drupal_parse_dependency($module);
+ $name = $parsed_dependency['name'];
+ return module_exists($name);
+}
+
+/**
+ * Get defaults for a given module/component pair.
+ */
+function features_get_default($component, $module_name = NULL, $alter = TRUE, $reset = FALSE) {
+ $cache = &drupal_static(__FUNCTION__, array());
+ $alter = !empty($alter); // ensure $alter is a true/false boolean
+ features_include();
+ features_include_defaults($component);
+ $default_hook = features_get_default_hooks($component);
+ $components = features_get_components();
+
+ // Collect defaults for all modules if no module name was specified.
+ if (isset($module_name)) {
+ $modules = array($module_name);
+ }
+ else {
+ if ($component === 'dependencies') {
+ $modules = array_keys(features_get_features());
+ }
+ else {
+ $modules = array();
+ foreach (features_get_component_map($component) as $component_modules) {
+ $modules = array_merge($modules, $component_modules);
+ }
+ $modules = array_unique($modules);
+ }
+ }
+
+ // Collect and cache information for each specified module.
+ foreach ($modules as $m) {
+ if (!isset($cache[$component][$alter][$m]) || $reset) {
+ // Special handling for dependencies component.
+ if ($component === 'dependencies') {
+ $module = features_get_features($m);
+ $cache[$component][$alter][$m] = isset($module->info['dependencies']) ? $module->info['dependencies'] : array();
+ unset($module);
+ }
+ // All other components
+ else {
+ if ($default_hook && module_hook($m, $default_hook)) {
+ $cache[$component][$alter][$m] = call_user_func("{$m}_{$default_hook}");
+ if (is_array($cache[$component][$alter][$m])) {
+ $alter_type = features_get_components('alter_type', $component);
+ if ($alter && (!isset($alter_type) || $alter_type == FEATURES_ALTER_TYPE_NORMAL)) {
+ if ($alter_hook = features_get_default_alter_hook($component)) {
+ drupal_alter($alter_hook, $cache[$component][$alter][$m]);
+ }
+ }
+ }
+ else {
+ $cache[$component][$alter][$m] = FALSE;
+ }
+ }
+ else {
+ $cache[$component][$alter][$m] = FALSE;
+ }
+ }
+ }
+ }
+
+ // A specific module was specified. Retrieve only its components.
+ if (isset($module_name)) {
+ return isset($cache[$component][$alter][$module_name]) ? $cache[$component][$alter][$module_name] : FALSE;
+ }
+ // No module was specified. Retrieve all components.
+ $all_defaults = array();
+ if (isset($cache[$component][$alter])) {
+ foreach (array_filter($cache[$component][$alter]) as $module_components) {
+ $all_defaults = array_merge($all_defaults, $module_components);
+ }
+ }
+ return $all_defaults;
+}
+
+/**
+ * Get a map of components to their providing modules.
+ */
+function features_get_default_map($component, $attribute = NULL, $callback = NULL, $reset = FALSE) {
+ $map = &drupal_static(__FUNCTION__, array());
+
+ global $features_ignore_conflicts;
+ if ($features_ignore_conflicts) {
+ return FALSE;
+ }
+
+ features_include();
+ features_include_defaults($component);
+ if ((!isset($map[$component]) || $reset) && $default_hook = features_get_default_hooks($component)) {
+ $map[$component] = array();
+ foreach (module_implements($default_hook) as $module) {
+ if ($defaults = features_get_default($component, $module)) {
+ foreach ($defaults as $key => $object) {
+ if (isset($callback)) {
+ if ($object_key = $callback($object)) {
+ $map[$component][$object_key] = $module;
+ }
+ }
+ elseif (isset($attribute)) {
+ if (is_object($object) && isset($object->{$attribute})) {
+ $map[$component][$object->{$attribute}] = $module;
+ }
+ elseif (is_array($object) && isset($object[$attribute])) {
+ $map[$component][$object[$attribute]] = $module;
+ }
+ }
+ elseif (!isset($attribute) && !isset($callback)) {
+ if (!is_numeric($key)) {
+ $map[$component][$key] = $module;
+ }
+ }
+ else {
+ return FALSE;
+ }
+ }
+ }
+ }
+ }
+ return isset($map[$component]) ? $map[$component] : FALSE;
+}
+
+/**
+ * Retrieve an array of features/components and their current states.
+ */
+function features_get_component_states($features = array(), $rebuild_only = TRUE, $reset = FALSE) {
+ // Ensure that the export will be created in the English language.
+ _features_set_export_language();
+
+ if ($reset) {
+ drupal_static_reset(__FUNCTION__);
+ }
+ $cache = &drupal_static(__FUNCTION__, array());
+
+ $all_features = features_get_features();
+ $features = !empty($features) ? $features : array_keys($all_features);
+
+ // Retrieve only rebuildable components if requested.
+ features_include();
+ $components = array_keys(features_get_components());
+ if ($rebuild_only) {
+ foreach ($components as $k => $component) {
+ if (!features_hook($component, 'features_rebuild')) {
+ unset($components[$k]);
+ }
+ }
+ }
+
+ foreach ($features as $feature) {
+ $cache[$feature] = isset($cache[$feature]) ? $cache[$feature] : array();
+ if (module_exists($feature) && !empty($all_features[$feature]->components)) {
+ foreach (array_intersect($all_features[$feature]->components, $components) as $component) {
+ if (!isset($cache[$feature][$component])) {
+ $normal = features_get_signature('normal', $feature, $component, $reset);
+ $default = features_get_signature('default', $feature, $component, $reset);
+ $codecache = features_get_signature('cache', $feature, $component, $reset);
+ $semaphore = features_semaphore('get', $component);
+
+ // DB and code states match, there is nothing more to check.
+ if ($normal == $default) {
+ $cache[$feature][$component] = FEATURES_DEFAULT;
+
+ // Stale semaphores can be deleted.
+ features_semaphore('del', $component);
+
+ // Update code cache if it is stale, clear out semaphore if it stale.
+ if ($default != $codecache) {
+ features_set_signature($feature, $component, $default);
+ }
+ }
+ // Component properly implements exportables.
+ else if (!features_hook($component, 'features_rebuild')) {
+ $cache[$feature][$component] = FEATURES_OVERRIDDEN;
+ }
+ // Component does not implement exportables.
+ else {
+ if (empty($semaphore)) {
+ // Exception for dependencies. Dependencies are always rebuildable.
+ if ($component === 'dependencies') {
+ $cache[$feature][$component] = FEATURES_REBUILDABLE;
+ }
+ // All other rebuildable components require comparison.
+ else {
+ // Code has not changed, but DB does not match. User has DB overrides.
+ if ($codecache == $default) {
+ $cache[$feature][$component] = FEATURES_OVERRIDDEN;
+ }
+ // DB has no modifications to prior code state (or this is initial install).
+ else if (empty($codecache) || $codecache == $normal) {
+ $cache[$feature][$component] = FEATURES_REBUILDABLE;
+ }
+ // None of the states match. Requires user intervention.
+ else if ($codecache != $default) {
+ $cache[$feature][$component] = FEATURES_NEEDS_REVIEW;
+ }
+ }
+ }
+ else {
+ // Semaphore is still within processing horizon. Do nothing.
+ if ((REQUEST_TIME - $semaphore) < FEATURES_SEMAPHORE_TIMEOUT) {
+ $cache[$feature][$component] = FEATURES_REBUILDING;
+ }
+ // A stale semaphore means a previous rebuild attempt did not complete.
+ // Attempt to complete the rebuild.
+ else {
+ $cache[$feature][$component] = FEATURES_REBUILDABLE;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Filter cached components on the way out to ensure that even if we have
+ // cached more data than has been requested, the return value only reflects
+ // the requested features/components.
+ $return = $cache;
+ $return = array_intersect_key($return, array_flip($features));
+ foreach ($return as $k => $v) {
+ $return[$k] = array_intersect_key($return[$k], array_flip($components));
+ }
+ return $return;
+}
+
+/**
+ * Helper function to eliminate whitespace differences in code.
+ */
+function _features_linetrim($code) {
+ $code = explode("\n", $code);
+ foreach ($code as $k => $line) {
+ $code[$k] = trim($line);
+ }
+ return implode("\n", $code);
+}
+
+/**
+ * Helper function to "sanitize" an array or object.
+ * Converts everything to an array, sorts the keys, removes recursion.
+ * @param $array
+ * @param $component string name of component
+ * @param bool $remove_empty if set, remove null or empty values for assoc arrays.
+ */
+function features_sanitize(&$array, $component = NULL, $remove_empty = TRUE) {
+ $array = features_remove_recursion($array);
+ if (isset($component)) {
+ $ignore_keys = _features_get_ignore_keys($component);
+ // remove keys to be ignored
+ if (count($ignore_keys)) {
+ _features_remove_ignores($array, $ignore_keys);
+ }
+ }
+ _features_sanitize($array, $remove_empty);
+}
+
+/**
+ * "Sanitizes" an array recursively, performing two key operations:
+ * - Sort an array by its keys (assoc) or values (non-assoc)
+ * @param bool $remove_empty if set, remove null or empty values for assoc arrays.
+ */
+function _features_sanitize(&$array, $remove_empty = TRUE) {
+ if (is_object($array)) {
+ $array = get_object_vars($array);
+ }
+ if (is_array($array)) {
+ $is_assoc = _features_is_assoc($array);
+ if ($is_assoc) {
+ ksort($array, SORT_STRING);
+ if ($remove_empty) {
+ $array = array_filter($array);
+ }
+ }
+ else {
+ sort($array);
+ }
+ foreach ($array as $k => $v) {
+ if (is_array($v) or is_object($v)) {
+ _features_sanitize($array[$k]);
+ if ($remove_empty && $is_assoc && empty($array[$k])) {
+ unset($array[$k]);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Is the given array an associative array. This basically extracts the keys twice to get the
+ * numerically ordered keys. It then does a diff with the original array and if there is no
+ * key diff then the original array is not associative.
+ *
+ * NOTE: If you have non-sequential numerical keys, this will identify the array as assoc.
+ *
+ * Borrowed from: http://www.php.net/manual/en/function.is-array.php#96724
+ *
+ * @return True is the array is an associative array, false otherwise
+ */
+function _features_is_assoc($array) {
+ return (is_array($array) && (0 !== count(array_diff_key($array, array_keys(array_keys($array)))) || count($array)==0));
+}
+
+/**
+ * Removes recursion from an object or array.
+ *
+ * Taken from https://code.google.com/p/formaldehyde/source/browse/trunk/formaldehyde.php
+ * Also used in node_export module
+ *
+ * @param $o mixed
+ * @return mixed
+ * returns a copy of the object or array with recursion removed
+ */
+function features_remove_recursion($o) {
+ static $replace;
+ if (!isset($replace)) {
+ $replace = create_function(
+ '$m',
+ '$r="\x00{$m[1]}ecursion_features";return \'s:\'.strlen($r.$m[2]).\':"\'.$r.$m[2].\'";\';'
+ );
+ }
+ if (is_array($o) || is_object($o)) {
+ $re = '#(r|R):([0-9]+);#';
+ $serialize = serialize($o);
+ if (preg_match($re, $serialize)) {
+ $last = $pos = 0;
+ while (false !== ($pos = strpos($serialize, 's:', $pos))) {
+ $chunk = substr($serialize, $last, $pos - $last);
+ if (preg_match($re, $chunk)) {
+ $length = strlen($chunk);
+ $chunk = preg_replace_callback($re, $replace, $chunk);
+ $serialize = substr($serialize, 0, $last) . $chunk . substr($serialize, $last + ($pos - $last));
+ $pos += strlen($chunk) - $length;
+ }
+ $pos += 2;
+ $last = strpos($serialize, ':', $pos);
+ $length = substr($serialize, $pos, $last - $pos);
+ $last += 4 + $length;
+ $pos = $last;
+ }
+ $serialize = substr($serialize, 0, $last) . preg_replace_callback($re, $replace, substr($serialize, $last));
+ $o = unserialize($serialize);
+ }
+ }
+ return $o;
+}
+
+/**
+ * Helper to removes a set of keys an object/array.
+ *
+ * @param $item
+ * An object or array passed by reference.
+ * @param $ignore_keys
+ * Array of keys to be ignored. Values are the level of the key.
+ * @param $level
+ * Level of key to remove. Up to 2 levels deep because $item can still be
+ * recursive
+ */
+function _features_remove_ignores(&$item, $ignore_keys, $level = -1) {
+ $is_object = is_object($item);
+ if (!is_array($item) && !is_object($item)) {
+ return;
+ }
+ foreach ($item as $key => &$value) {
+ if (isset($ignore_keys[$key]) && ($ignore_keys[$key] == $level)) {
+ if ($is_object) {
+ unset($item->$key);
+ }
+ else {
+ unset($item[$key]);
+ }
+ }
+ elseif (($level < 2) && (is_array($value) || is_object($value))) {
+ _features_remove_ignores($value, $ignore_keys, $level+1);
+ }
+ }
+ unset($value);
+}
+
+/**
+ * Returns an array of keys to be ignored for various exportables
+ * @param $component
+ * The component to retrieve ignore_keys from.
+ */
+function _features_get_ignore_keys($component) {
+ static $cache;
+ if (!isset($cache[$component])) {
+ $cache[$component] = module_invoke_all('features_ignore', $component);
+ }
+ return $cache[$component];
+}
diff --git a/docroot/sites/all/modules/features/features.info b/docroot/sites/all/modules/features/features.info
new file mode 100644
index 0000000..2447e3a
--- /dev/null
+++ b/docroot/sites/all/modules/features/features.info
@@ -0,0 +1,14 @@
+name = "Features"
+description = "Provides feature management for Drupal."
+core = 7.x
+package = "Features"
+files[] = tests/features.test
+
+configure = admin/structure/features/settings
+
+; Information added by Drupal.org packaging script on 2015-10-14
+version = "7.x-2.7"
+core = "7.x"
+project = "features"
+datestamp = "1444829630"
+
diff --git a/docroot/sites/all/modules/features/features.install b/docroot/sites/all/modules/features/features.install
new file mode 100644
index 0000000..008f41b
--- /dev/null
+++ b/docroot/sites/all/modules/features/features.install
@@ -0,0 +1,151 @@
+fields(array('weight' => 20))
+ ->condition('name', 'features')
+ ->condition('type', 'module')
+ ->execute();
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function features_uninstall() {
+ variable_del('features_codecache');
+ variable_del('features_default_export_path');
+ variable_del('features_semaphore');
+ variable_del('features_ignored_orphans');
+ variable_del('features_feature_locked');
+ variable_del('features_lock_mode');
+
+ db_delete('variable')
+ ->condition('name', 'features_admin_show_component_%', 'LIKE')
+ ->execute();
+ db_delete('variable')
+ ->condition('name', 'features_component_locked_%', 'LIKE')
+ ->execute();
+
+ if (db_table_exists('menu_custom')) {
+ db_delete('menu_custom')
+ ->condition('menu_name', 'features')
+ ->execute();
+ }
+ db_delete('menu_links')
+ ->condition('module', 'features')
+ ->execute();
+}
+
+/**
+ * Create menu. See menu.install for an example.
+ */
+function _features_install_menu() {
+ if (db_table_exists('menu_custom') && !db_query("SELECT menu_name FROM {menu_custom} WHERE menu_name = :menu_name", array(':menu_name' => 'features'))->fetchField()) {
+ $t = get_t();
+ $id = db_insert('menu_custom')
+ ->fields(array(
+ 'menu_name' => 'features',
+ 'title' => $t('Features'),
+ 'description' => $t('Menu items for any enabled features.'),
+ ))
+ ->execute();
+ }
+}
+
+/**
+ * Update 6100: Set module on all feature node types to 'features'.
+ *
+ * This update can be re-run as needed to repair any node types that are not
+ * removed after disabling the associated feature.
+ *
+ * Any feature implementing a node component that was exported prior to this
+ * version of the features.module will need to have its 'module' declaration
+ * in hook_node_info() changed from 'node' to 'features'.
+ */
+function features_update_6100() {
+ $ret = array();
+
+ foreach (features_get_features(NULL, TRUE) as $feature) {
+ if (module_exists($feature->name) && $types = module_invoke($feature->name, 'node_info')) {
+ foreach ($types as $type => $type_data) {
+ $sql = "SELECT COUNT(*) FROM {node_type} WHERE module = 'node' AND type = '%s'";
+ // Only update if the hook_node_info type's module is 'features' and the db type's
+ // module is 'node'.
+ if (($type_data['module'] == 'features') && db_query($sql, $type)->fetchField()) {
+ $ret[] = update_sql("UPDATE {node_type} SET module = 'features' WHERE type = '$type'");
+ }
+ }
+ }
+ }
+ return $ret;
+}
+
+/**
+ * Update 6101: Set codestate signature for all features.
+ *
+ * This update generates a codestate for all feature/component pairs which
+ * have been installed prior to this version of Features. This prevents
+ * automatic rebuilds from occurring against any rebuildable components
+ * that have been overridden.
+ */
+function features_update_6101() {
+ // Ensure all of our own API functions still exist in in this version
+ // of Features. It's possible that the "future me" will not have these
+ // functions, so I should check.
+ module_load_include('inc', 'features', "features.export");
+ $functions = array(
+ 'features_include',
+ 'features_hook',
+ 'features_get_components',
+ 'features_get_features',
+ 'features_get_signature',
+ 'features_set_signature',
+ );
+ $doit = TRUE;
+ foreach ($functions as $function) {
+ $doit = $doit && function_exists($function);
+ }
+ if ($doit) {
+ features_include();
+ $features = array_keys(features_get_features(NULL, TRUE));
+ $components = array_keys(features_get_components());
+ foreach ($features as $feature) {
+ if (module_exists($feature)) {
+ foreach ($components as $component) {
+ if (features_hook($component, 'features_rebuild') && features_get_signature('cache', $feature, $component) === FALSE) {
+ features_set_signature($feature, $component, -1);
+ }
+ }
+ }
+ }
+ }
+ return array();
+}
+
+/**
+ * Add {cache_features} table.
+ */
+function features_update_7200() {
+ if (!db_table_exists('cache_features')) {
+ $schema = drupal_get_schema_unprocessed('system', 'cache');
+ db_create_table('cache_features', $schema);
+ }
+}
diff --git a/docroot/sites/all/modules/features/features.js b/docroot/sites/all/modules/features/features.js
new file mode 100644
index 0000000..0a4a130
--- /dev/null
+++ b/docroot/sites/all/modules/features/features.js
@@ -0,0 +1,431 @@
+/**
+ * jQuery.fn.sortElements
+ * --------------
+ * @param Function comparator:
+ * Exactly the same behaviour as [1,2,3].sort(comparator)
+ *
+ * @param Function getSortable
+ * A function that should return the element that is
+ * to be sorted. The comparator will run on the
+ * current collection, but you may want the actual
+ * resulting sort to occur on a parent or another
+ * associated element.
+ *
+ * E.g. $('td').sortElements(comparator, function(){
+ * return this.parentNode;
+ * })
+ *
+ * The 's parent ( | ) will be sorted instead
+ * of the itself.
+ *
+ * Credit: http://james.padolsey.com/javascript/sorting-elements-with-jquery/
+ *
+ */
+jQuery.fn.sortElements = (function(){
+
+ var sort = [].sort;
+
+ return function(comparator, getSortable) {
+
+ getSortable = getSortable || function(){return this;};
+
+ var placements = this.map(function(){
+
+ var sortElement = getSortable.call(this),
+ parentNode = sortElement.parentNode,
+
+ // Since the element itself will change position, we have
+ // to have some way of storing its original position in
+ // the DOM. The easiest way is to have a 'flag' node:
+ nextSibling = parentNode.insertBefore(
+ document.createTextNode(''),
+ sortElement.nextSibling
+ );
+
+ return function() {
+
+ if (parentNode === this) {
+ throw new Error(
+ "You can't sort elements if any one is a descendant of another."
+ );
+ }
+
+ // Insert before flag:
+ parentNode.insertBefore(this, nextSibling);
+ // Remove flag:
+ parentNode.removeChild(nextSibling);
+
+ };
+
+ });
+
+ return sort.call(this, comparator).each(function(i){
+ placements[i].call(getSortable.call(this));
+ });
+
+ };
+
+})();
+
+(function ($) {
+ Drupal.behaviors.features = {
+ attach: function(context, settings) {
+ // Features management form
+ $('table.features:not(.processed)', context).each(function() {
+ $(this).addClass('processed');
+
+ // Check the overridden status of each feature
+ Drupal.features.checkStatus();
+
+ // Add some nicer row hilighting when checkboxes change values
+ $('input', this).bind('change', function() {
+ if (!$(this).attr('checked')) {
+ $(this).parents('tr').removeClass('enabled').addClass('disabled');
+ }
+ else {
+ $(this).parents('tr').addClass('enabled').removeClass('disabled');
+ }
+ });
+ });
+
+ // Export form component selector
+ $('form.features-export-form select.features-select-components:not(.processed)', context).each(function() {
+ $(this)
+ .addClass('processed')
+ .change(function() {
+ var target = $(this).val();
+ $('div.features-select').hide();
+ $('div.features-select-' + target).show();
+ return false;
+ }).trigger('change');
+ });
+
+ //View info dialog
+ var infoDialog = $('#features-info-file');
+ if (infoDialog.length != 0) {
+ infoDialog.dialog({
+ autoOpen: false,
+ modal: true,
+ draggable: false,
+ resizable: false,
+ width: 600,
+ height: 480
+ });
+ }
+
+ if ((Drupal.settings.features != undefined) && (Drupal.settings.features.info != undefined)) {
+ $('#features-info-file textarea').val(Drupal.settings.features.info);
+ $('#features-info-file').dialog('open');
+ //To be reset by the button click ajax
+ Drupal.settings.features.info = undefined;
+ }
+
+ // mark any conflicts with a class
+ if ((Drupal.settings.features != undefined) && (Drupal.settings.features.conflicts != undefined)) {
+ for (var moduleName in Drupal.settings.features.conflicts) {
+ moduleConflicts = Drupal.settings.features.conflicts[moduleName];
+ $('#features-export-wrapper input[type=checkbox]', context).each(function() {
+ if (!$(this).hasClass('features-checkall')) {
+ var key = $(this).attr('name');
+ var matches = key.match(/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/);
+ if (matches != null) {
+ var component = matches[1];
+ var item = matches[4];
+ if ((component in moduleConflicts) && (moduleConflicts[component].indexOf(item) != -1)) {
+ $(this).parent().addClass('features-conflict');
+ }
+ }
+ }
+ });
+ }
+ }
+
+ function _checkAll(value) {
+ if (value) {
+ $('#features-export-wrapper .component-select input[type=checkbox]:visible', context).each(function() {
+ var move_id = $(this).attr('id');
+ $(this).click();
+ $('#'+ move_id).attr('checked', 'checked');
+ });
+ }
+ else {
+ $('#features-export-wrapper .component-added input[type=checkbox]:visible', context).each(function() {
+ var move_id = $(this).attr('id');
+ $('#'+ move_id).removeAttr('checked');
+ $(this).click();
+ $('#'+ move_id).removeAttr('checked');
+ });
+ }
+ }
+
+ function updateComponentCountInfo(item, section) {
+ switch (section) {
+ case 'select':
+ var parent = $(item).closest('.features-export-list').siblings('.features-export-component');
+ $('.component-count', parent).text(function (index, text) {
+ return +text + 1;
+ }
+ );
+ break;
+ case 'added':
+ case 'detected':
+ var parent = $(item).closest('.features-export-component');
+ $('.component-count', parent).text(function (index, text) {
+ return text - 1;
+ });
+ }
+ }
+
+ function moveCheckbox(item, section, value) {
+ updateComponentCountInfo(item, section);
+ var curParent = item;
+ if ($(item).hasClass('form-type-checkbox')) {
+ item = $(item).children('input[type=checkbox]');
+ }
+ else {
+ curParent = $(item).parents('.form-type-checkbox');
+ }
+ var newParent = $(curParent).parents('.features-export-parent').find('.form-checkboxes.component-'+section);
+ $(curParent).detach();
+ $(curParent).appendTo(newParent);
+ var list = ['select', 'added', 'detected', 'included'];
+ for (i in list) {
+ $(curParent).removeClass('component-' + list[i]);
+ $(item).removeClass('component-' + list[i]);
+ }
+ $(curParent).addClass('component-'+section);
+ $(item).addClass('component-'+section);
+ if (value) {
+ $(item).attr('checked', 'checked');
+ }
+ else {
+ $(item).removeAttr('checked')
+ }
+ $(newParent).parent().removeClass('features-export-empty');
+
+ // re-sort new list of checkboxes based on labels
+ $(newParent).find('label').sortElements(
+ function(a, b){
+ return $(a).text() > $(b).text() ? 1 : -1;
+ },
+ function(){
+ return this.parentNode;
+ }
+ );
+ }
+
+ // provide timer for auto-refresh trigger
+ var timeoutID = 0;
+ var inTimeout = 0;
+ function _triggerTimeout() {
+ timeoutID = 0;
+ _updateDetected();
+ }
+ function _resetTimeout() {
+ inTimeout++;
+ // if timeout is already active, reset it
+ if (timeoutID != 0) {
+ window.clearTimeout(timeoutID);
+ if (inTimeout > 0) inTimeout--;
+ }
+ timeoutID = window.setTimeout(_triggerTimeout, 500);
+ }
+
+ function _updateDetected() {
+ var autodetect = $('#features-autodetect input[type=checkbox]');
+ if ((autodetect.length > 0) && (!autodetect.is(':checked'))) return;
+ // query the server for a list of components/items in the feature and update
+ // the auto-detected items
+ var items = []; // will contain a list of selected items exported to feature
+ var components = {}; // contains object of component names that have checked items
+ $('#features-export-wrapper input[type=checkbox]:checked', context).each(function() {
+ if (!$(this).hasClass('features-checkall')) {
+ var key = $(this).attr('name');
+ var matches = key.match(/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/);
+ components[matches[1]] = matches[1];
+ if (!$(this).hasClass('component-detected')) {
+ items.push(key);
+ }
+ }
+ });
+ var featureName = $('#edit-module-name').val();
+ if (featureName == '') {
+ featureName = '*';
+ }
+ var url = Drupal.settings.basePath + 'features/ajaxcallback/' + featureName;
+ var excluded = Drupal.settings.features.excluded;
+ var postData = {'items': items, 'excluded': excluded};
+ jQuery.post(url, postData, function(data) {
+ if (inTimeout > 0) inTimeout--;
+ // if we have triggered another timeout then don't update with old results
+ if (inTimeout == 0) {
+ // data is an object keyed by component listing the exports of the feature
+ for (var component in data) {
+ var itemList = data[component];
+ $('#features-export-wrapper .component-' + component + ' input[type=checkbox]', context).each(function() {
+ var key = $(this).attr('value');
+ // first remove any auto-detected items that are no longer in component
+ if ($(this).hasClass('component-detected')) {
+ if (!(key in itemList)) {
+ moveCheckbox(this, 'select', false)
+ }
+ }
+ // next, add any new auto-detected items
+ else if ($(this).hasClass('component-select')) {
+ if (key in itemList) {
+ moveCheckbox(this, 'detected', itemList[key]);
+ $(this).parent().show(); // make sure it's not hidden from filter
+ }
+ }
+ });
+ }
+ // loop over all selected components and check for any that have been completely removed
+ for (var component in components) {
+ if ((data == null) || !(component in data)) {
+ $('#features-export-wrapper .component-' + component + ' input[type=checkbox].component-detected', context).each(function() {
+ moveCheckbox(this, 'select', false);
+ });
+ }
+ }
+ }
+ }, "json");
+ }
+
+ // Handle component selection UI
+ $('#features-export-wrapper input[type=checkbox]:not(.processed)', context).addClass('processed').click(function() {
+ _resetTimeout();
+ if ($(this).hasClass('component-select')) {
+ moveCheckbox(this, 'added', true);
+ }
+ else if ($(this).hasClass('component-included')) {
+ moveCheckbox(this, 'added', false);
+ }
+ else if ($(this).hasClass('component-added')) {
+ if ($(this).is(':checked')) {
+ moveCheckbox(this, 'included', true);
+ }
+ else {
+ moveCheckbox(this, 'select', false);
+ }
+ }
+ });
+
+ // Handle select/unselect all
+ $('#features-filter .features-checkall', context).click(function() {
+ if ($(this).attr('checked')) {
+ _checkAll(true);
+ $(this).next().html(Drupal.t('Deselect all'));
+ }
+ else {
+ _checkAll(false);
+ $(this).next().html(Drupal.t('Select all'));
+ }
+ _resetTimeout();
+ });
+
+ // Handle filtering
+
+ // provide timer for auto-refresh trigger
+ var filterTimeoutID = 0;
+ var inFilterTimeout = 0;
+ function _triggerFilterTimeout() {
+ filterTimeoutID = 0;
+ _updateFilter();
+ }
+ function _resetFilterTimeout() {
+ inFilterTimeout++;
+ // if timeout is already active, reset it
+ if (filterTimeoutID != 0) {
+ window.clearTimeout(filterTimeoutID);
+ if (inFilterTimeout > 0) inFilterTimeout--;
+ }
+ filterTimeoutID = window.setTimeout(_triggerFilterTimeout, 200);
+ }
+ function _updateFilter() {
+ var filter = $('#features-filter input').val();
+ var regex = new RegExp(filter, 'i');
+ // collapse fieldsets
+ var newState = {};
+ var currentState = {};
+ $('#features-export-wrapper fieldset.features-export-component', context).each(function() {
+ // expand parent fieldset
+ var section = $(this).attr('id');
+ currentState[section] = !($(this).hasClass('collapsed'));
+ if (!(section in newState)) {
+ newState[section] = false;
+ }
+
+ $(this).find('div.component-select label').each(function() {
+ if (filter == '') {
+ if (currentState[section]) {
+ Drupal.toggleFieldset($('#'+section));
+ currentState[section] = false;
+ }
+ $(this).parent().show();
+ }
+ else if ($(this).text().match(regex)) {
+ $(this).parent().show();
+ newState[section] = true;
+ }
+ else {
+ $(this).parent().hide();
+ }
+ });
+ });
+ for (section in newState) {
+ if (currentState[section] != newState[section]) {
+ Drupal.toggleFieldset($('#'+section));
+ }
+ }
+ }
+ $('#features-filter input', context).bind("input", function() {
+ _resetFilterTimeout();
+ });
+ $('#features-filter .features-filter-clear', context).click(function() {
+ $('#features-filter input').val('');
+ _updateFilter();
+ });
+
+ // show the filter bar
+ $('#features-filter', context).removeClass('element-invisible');
+ }
+ }
+
+
+ Drupal.features = {
+ 'checkStatus': function() {
+ $('table.features tbody tr').not('.processed').filter(':first').each(function() {
+ var elem = $(this);
+ $(elem).addClass('processed');
+ var uri = $(this).find('a.admin-check').attr('href');
+ if (uri) {
+ $.get(uri, [], function(data) {
+ $(elem).find('.admin-loading').hide();
+ switch (data.storage) {
+ case 3:
+ $(elem).find('.admin-rebuilding').show();
+ break;
+ case 2:
+ $(elem).find('.admin-needs-review').show();
+ break;
+ case 1:
+ $(elem).find('.admin-overridden').show();
+ break;
+ default:
+ $(elem).find('.admin-default').show();
+ break;
+ }
+ Drupal.features.checkStatus();
+ }, 'json');
+ }
+ else {
+ Drupal.features.checkStatus();
+ }
+ });
+ }
+ };
+
+
+})(jQuery);
+
+
diff --git a/docroot/sites/all/modules/features/features.module b/docroot/sites/all/modules/features/features.module
new file mode 100644
index 0000000..86e9b5d
--- /dev/null
+++ b/docroot/sites/all/modules/features/features.module
@@ -0,0 +1,1338 @@
+ 'Features',
+ 'description' => 'Manage features.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('features_admin_form'),
+ 'type' => MENU_NORMAL_ITEM,
+ 'file' => 'features.admin.inc',
+ );
+ $items['admin/structure/features/cleanup'] = array(
+ 'title' => 'Cleanup',
+ 'description' => 'Clear cache after enabling/disabling a feature.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('features_cleanup_form', 4),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'features.admin.inc',
+ 'weight' => 1,
+ );
+ $items['admin/structure/features/manage'] = array(
+ 'title' => 'Manage',
+ 'description' => 'Enable and disable features.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('features_admin_form'),
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'file' => 'features.admin.inc',
+ );
+ $items['admin/structure/features/create'] = array(
+ 'title' => 'Create feature',
+ 'description' => 'Create a new feature.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('features_export_form'),
+ 'access callback' => 'user_access',
+ 'access arguments' => array('administer features'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => "features.admin.inc",
+ 'weight' => 10,
+ );
+ $items['admin/structure/features/settings'] = array(
+ 'title' => 'Settings',
+ 'description' => 'Adjust settings for using features module.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('features_settings_form'),
+ 'access callback' => 'user_access',
+ 'access arguments' => array('administer features'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => "features.admin.inc",
+ 'weight' => 11,
+ );
+
+
+ $items['admin/structure/features/%feature'] = array(
+ 'title callback' => 'features_get_feature_title',
+ 'title arguments' => array(3),
+ 'description' => 'Display components of a feature.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('features_admin_components', 3),
+ 'load arguments' => array(3, TRUE),
+ 'access callback' => 'user_access',
+ 'access arguments' => array('administer features'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'features.admin.inc',
+ );
+ $items['admin/structure/features/%feature/view'] = array(
+ 'title' => 'View',
+ 'description' => 'Display components of a feature.',
+ 'access callback' => 'user_access',
+ 'access arguments' => array('administer features'),
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+ $items['admin/structure/features/%feature/recreate'] = array(
+ 'title' => 'Recreate',
+ 'description' => 'Recreate an existing feature.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('features_export_form', 3),
+ 'load arguments' => array(3, TRUE),
+ 'access callback' => 'user_access',
+ 'access arguments' => array('administer features'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => "features.admin.inc",
+ 'weight' => 11,
+ );
+ if (module_exists('diff')) {
+ $items['admin/structure/features/%feature/diff'] = array(
+ 'title' => 'Review overrides',
+ 'description' => 'Compare default and current feature.',
+ 'page callback' => 'features_feature_diff',
+ 'page arguments' => array(3, 5),
+ 'load arguments' => array(3, TRUE),
+ 'access callback' => 'features_access_override_actions',
+ 'access arguments' => array(3),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'features.admin.inc',
+ );
+ }
+
+ $items['admin/structure/features/%feature/lock'] = array(
+ 'title' => 'Lock',
+ 'description' => 'Lock a feature or components.',
+ 'page callback' => 'features_admin_lock',
+ 'page arguments' => array(3, 5, 6),
+ 'load arguments' => array(3, TRUE, TRUE),
+ 'access arguments' => array('administer features'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'features.admin.inc',
+ );
+ $items['admin/structure/features/%feature/status'] = array(
+ 'title' => 'Status',
+ 'description' => 'Javascript status call back.',
+ 'page callback' => 'features_feature_status',
+ 'page arguments' => array(3),
+ 'load arguments' => array(3, TRUE),
+ 'access callback' => 'user_access',
+ 'access arguments' => array('administer features'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'features.admin.inc',
+ );
+ $items['features/autocomplete/packages'] = array(
+ 'page callback' => 'features_autocomplete_packages',
+ 'access arguments' => array('administer features'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'features.admin.inc',
+ );
+ $items['features/ajaxcallback/%'] = array(
+ 'title callback' => 'features_get_feature_components',
+ 'description' => 'Return components of a feature.',
+ 'page callback' => 'features_export_components_json',
+ 'page arguments' => array(2),
+ 'access callback' => 'user_access',
+ 'access arguments' => array('administer features'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'features.admin.inc',
+ );
+ foreach ($items as $path => $item) {
+ if (!isset($item['access callback'])) {
+ $items[$path]['access callback'] = 'user_access';
+ $items[$path]['access arguments'] = array('manage features');
+ }
+ }
+ return $items;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function features_theme() {
+ $base = array(
+ 'path' => drupal_get_path('module', 'features') . '/theme',
+ 'file' => 'theme.inc',
+ );
+
+ $items = array();
+ $items['features_module_status'] = array(
+ 'variables' => array('module' => NULL, 'status' => NULL)
+ ) + $base;
+
+ $items['features_components'] = array(
+ 'variables' => array('info' => NULL, 'sources' => NULL),
+ ) + $base;
+
+ $items['features_component_key'] = $base;
+ $items['features_component_list'] = array(
+ 'variables' => array('components' => array(), 'source' => array(), 'conflicts' => array()),
+ ) + $base;
+
+ $items['features_storage_link'] = array(
+ 'variables' => array('storage' => NULL, 'text' => NULL, 'path' => NULL, 'options' => array()),
+ ) + $base;
+
+ $items['features_lock_link'] = array(
+ 'variables' => array('feature' => NULL, 'component' => NULL, 'locked' => FALSE),
+ ) + $base;
+
+ $items['features_form_components'] =
+ $items['features_form_export'] =
+ $items['features_form_package'] = array(
+ 'render element' => 'form',
+ ) + $base;
+
+ $items['features_form_buttons'] = array(
+ 'render element' => 'element',
+ ) + $base;
+
+
+ $items['features_admin_components'] = array(
+ 'render element' => 'form',
+ 'template' => 'features-admin-components',
+ ) + $base;
+
+ return $items;
+}
+
+/**
+ * Implements hook_flush_caches().
+ */
+function features_flush_caches() {
+ if (($modules_changed = variable_get('features_modules_changed', FALSE)) || variable_get('features_rebuild_on_flush', TRUE)) {
+ if ($modules_changed) {
+ variable_set('features_modules_changed', FALSE);
+ }
+ features_rebuild();
+ // Don't flush the modules cache during installation, for performance reasons.
+ if (variable_get('install_task') == 'done') {
+ features_get_modules(NULL, TRUE);
+ }
+ }
+
+ if (db_table_exists('cache_features')) {
+ return array('cache_features');
+ }
+ return array();
+}
+
+/**
+ * Implements hook_form().
+ */
+function features_form($node, $form_state) {
+ return node_content_form($node, $form_state);
+}
+
+/**
+ * Implements hook_permission().
+ */
+function features_permission() {
+ return array(
+ 'administer features' => array(
+ 'title' => t('Administer features'),
+ 'description' => t('Perform administration tasks on features.'),
+ 'restrict access' => TRUE,
+ ),
+ 'manage features' => array(
+ 'title' => t('Manage features'),
+ 'description' => t('View, enable and disable features.'),
+ 'restrict access' => TRUE,
+ ),
+ 'generate features' => array(
+ 'title' => t('Generate features'),
+ 'description' => t('Allow feature exports to be generated and written directly to site.'),
+ 'restrict access' => TRUE,
+ ),
+ 'rename features' => array(
+ 'title' => t('Edit feature machine name'),
+ 'description' => t('Allows editing machine name of a disabled feature'),
+ 'restrict access' => TRUE,
+ ),
+ );
+}
+
+/**
+ * Implements hook_help().
+ */
+function features_help($path, $arg) {
+ switch ($path) {
+ case 'admin/help#features':
+ $output = file_get_contents(drupal_get_path('module', 'features') . '/README.txt');
+ return module_exists('markdown') ? filter_xss_admin(module_invoke('markdown', 'filter', 'process', 0, -1, $output)) : '' . check_plain($output) . ' ';
+ case 'admin/build/features':
+ return '' . t('A "Feature" is a certain type of Drupal module which contains a package of configuration that, when enabled, provides a new set of functionality for your Drupal site. Enable features by selecting the checkboxes below and clicking the Save configuration button. If the configuration of the feature has been changed its "State" will be either "overridden" or "needs review", otherwise it will be "default", indicating that the configuration has not been changed. Click on the state to see more details about the feature and its components.') . ' ';
+ }
+}
+
+/**
+ * Implements hook_modules_disabled().
+ */
+function features_modules_disabled($modules) {
+ // Go through all modules and gather features that can be disabled.
+ $items = array();
+ foreach ($modules as $module) {
+ if ($feature = features_load_feature($module)) {
+ $items[$module] = array_keys($feature->info['features']);
+ }
+ }
+
+ if (!empty($items)) {
+ _features_restore('disable', $items);
+ // Rebuild the list of features includes.
+ features_include(TRUE);
+ }
+}
+
+/**
+ * Implements hook_modules_enabled().
+ */
+function features_modules_enabled($modules) {
+ // Allow distributions to disable this behavior and rebuild the features
+ // manually inside a batch.
+ if (!variable_get('features_rebuild_on_module_install', TRUE)) {
+ return;
+ }
+
+ // mark modules as being changed for test in features_flush_caches
+ variable_set('features_modules_changed', TRUE);
+
+ // Go through all modules and gather features that can be enabled.
+ $items = array();
+ foreach ($modules as $module) {
+ if ($feature = features_load_feature($module)) {
+ $items[$module] = array_keys($feature->info['features']);
+ }
+ }
+
+ if (!empty($items)) {
+ // Need to include any new files.
+ // @todo Redo function so can take in list of modules to include.
+ features_include_defaults(NULL, TRUE);
+ _features_restore('enable', $items);
+ // Rebuild the list of features includes.
+ features_include(TRUE);
+ // Reorders components to match hook order and removes non-existant.
+ $all_components = array_keys(features_get_components());
+ foreach ($items as $module => $components) {
+ $items[$module] = array_intersect($all_components, $components);
+ }
+ _features_restore('rebuild', $items);
+ }
+}
+
+/**
+ * Load includes for any modules that implement the features API and
+ * load includes for those provided by features.
+ */
+function features_include($reset = FALSE) {
+ static $once;
+ if (!isset($once) || $reset) {
+ $once = TRUE;
+
+ // Features provides integration on behalf of these modules.
+ // The features include provides handling for the feature dependencies.
+ // Note that ctools is placed last because it implements hooks "dynamically" for other modules.
+ $modules = array('features', 'block', 'contact', 'context', 'field', 'filter', 'image', 'locale', 'menu', 'node', 'taxonomy', 'user', 'views', 'ctools');
+
+ foreach (array_filter($modules, 'module_exists') as $module) {
+ module_load_include('inc', 'features', "includes/features.$module");
+ }
+
+ if (module_exists('ctools')) {
+ // Finally, add ctools eval'd implementations.
+ ctools_features_declare_functions($reset);
+ }
+
+ // Clear static cache, since we've now included new implementers.
+ foreach (features_get_components(NULL, 'file', $reset) as $file) {
+ if (is_file(DRUPAL_ROOT . '/' . $file)) {
+ require_once DRUPAL_ROOT . '/' . $file;
+ }
+ }
+ }
+}
+
+/**
+ * Load features includes for all components that require includes before
+ * collecting defaults.
+ */
+function features_include_defaults($components = NULL, $reset = FALSE) {
+ static $include_components;
+
+ // Build an array of components that require inclusion:
+ // Views, CTools components and those using FEATURES_DEFAULTS_INCLUDED.
+ if (!isset($include_components) || $reset) {
+ $include_components = features_get_components();
+ foreach ($include_components as $component => $info) {
+ if (!isset($info['api']) && (!isset($info['default_file']) || $info['default_file'] !== FEATURES_DEFAULTS_INCLUDED)) {
+ unset($include_components[$component]);
+ }
+ }
+ }
+
+ // If components are specified, only include for the specified components.
+ if (isset($components)) {
+ $components = is_array($components) ? $components : array($components);
+ }
+ // Use all include components if none are explicitly specified.
+ else {
+ $components = array_keys($include_components);
+ }
+ foreach ($components as $component) {
+ if (isset($include_components[$component])) {
+ $info = $include_components[$component];
+ // Inclusion of ctools components.
+ if (isset($info['api'], $info['module'], $info['current_version'])) {
+ ctools_include('plugins');
+ ctools_plugin_api_include($info['module'], $info['api'], $info['current_version'], $info['current_version']);
+ }
+ // Inclusion of defaults for components using FEATURES_DEFAULTS_INCLUDED.
+ else {
+ $features = isset($features) ? $features : features_get_features(NULL, $reset);
+ foreach ($features as $feature) {
+ $filename = isset($info['default_file']) && $info['default_file'] == FEATURES_DEFAULTS_CUSTOM ? $info['default_filename'] : "features.{$component}";
+ if (module_exists($feature->name) && isset($feature->info['features'][$component])) {
+ module_load_include('inc', $feature->name, "{$feature->name}.$filename");
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Feature object loader. DEPRECATED but included for backwards compatibility
+ */
+function feature_load($name, $reset = FALSE) {
+ return features_load_feature($name, $reset);
+}
+
+/**
+ * Feature object loader.
+ */
+function features_load_feature($name, $reset = FALSE) {
+ // Use an alternative code path during installation, for better performance.
+ if (variable_get('install_task') != 'done') {
+ static $features;
+
+ if (!isset($features[$name])) {
+ // Set defaults for module info.
+ $defaults = array(
+ 'dependencies' => array(),
+ 'description' => '',
+ 'package' => 'Other',
+ 'version' => NULL,
+ 'php' => DRUPAL_MINIMUM_PHP,
+ 'files' => array(),
+ 'bootstrap' => 0,
+ );
+ $info = drupal_parse_info_file(drupal_get_path('module', $name) . '/' . $name . '.info');
+
+ $features[$name] = FALSE;
+ if (!empty($info['features']) && empty($info['hidden'])) {
+ // Build a fake file object with the data needed during installation.
+ $features[$name] = new stdClass;
+ $features[$name]->name = $name;
+ $features[$name]->filename = drupal_get_path('module', $name) . '/' . $name . '.module';
+ $features[$name]->type = 'module';
+ $features[$name]->info = $info + $defaults;
+ }
+ }
+
+ return $features[$name];
+ }
+ else {
+ return features_get_features($name, $reset);
+ }
+}
+
+/**
+ * Return a module 'object' including .info information.
+ *
+ * @param $name
+ * The name of the module to retrieve information for. If omitted,
+ * an array of all available modules will be returned.
+ * @param $reset
+ * Whether to reset the cache.
+ *
+ * @return
+ * If a module is request (and exists) a module object is returned. If no
+ * module is requested info for all modules is returned.
+ */
+function features_get_modules($name = NULL, $reset = FALSE) {
+ return features_get_info('module', $name, $reset);
+}
+
+/**
+ * Returns the array of supported components.
+ *
+ * @see hook_features_api
+ *
+ * @param $component
+ * A specific type of component that supports features.
+ * @param $key
+ * A key that hook_features_api supports.
+ *
+ * @return An array of component labels keyed by the component names.
+ */
+function features_get_components($component = NULL, $key = NULL, $reset = FALSE) {
+ features_include();
+ $components = &drupal_static(__FUNCTION__);
+ $component_by_key = &drupal_static(__FUNCTION__ . '_by_key');
+
+ if ($reset || !isset($components) || !isset($component_by_key)) {
+ $components = $component_by_key = array();
+ if (!$reset && ($cache = cache_get('features_api', 'cache_features'))) {
+ $components = $cache->data;
+ }
+ else {
+ $components = module_invoke_all('features_api');
+ drupal_alter('features_api', $components);
+ cache_set('features_api', $components, 'cache_features');
+ }
+
+ foreach ($components as $component_type => $component_information) {
+ foreach ($component_information as $component_key => $component_value) {
+ $component_by_key[$component_key][$component_type] = $component_value;
+ }
+ }
+ }
+
+ if ($key && $component) {
+ return !empty($components[$component][$key]) ? $components[$component][$key] : NULL;
+ }
+ elseif ($key) {
+ return !empty($component_by_key[$key]) ? $component_by_key[$key] : array();
+ }
+ elseif ($component) {
+ return $components[$component];
+ }
+ return $components;
+}
+
+/**
+ * Returns components that are offered as an option on feature creation.
+ */
+function features_get_feature_components() {
+ return array_intersect_key(features_get_components(), array_filter(features_get_components(NULL, 'feature_source')));
+}
+
+/**
+ * Invoke a component callback.
+ */
+function features_invoke($component, $callback) {
+ $args = func_get_args();
+ unset($args[0], $args[1]);
+ // Append the component name to the arguments.
+ $args[] = $component;
+ if ($function = features_hook($component, $callback)) {
+ return call_user_func_array($function, $args);
+ }
+}
+
+/**
+ * Checks whether a component implements the given hook.
+ *
+ * @return
+ * The function implementing the hook, or FALSE.
+ */
+function features_hook($component, $hook, $reset = FALSE) {
+ // Determine the function callback base.
+ $base = features_get_components($component, 'base');
+ $base = isset($base) ? $base : $component;
+ return function_exists($base . '_' . $hook) ? $base . '_' . $hook : FALSE;
+}
+
+/**
+ * Enables and installs an array of modules, ignoring those
+ * already enabled & installed. Consider this a helper or
+ * extension to drupal_install_modules().
+ *
+ * @param $modules
+ * An array of modules to install.
+ * @param $reset
+ * Clear the module info cache.
+ */
+function features_install_modules($modules) {
+ variable_set('features_modules_changed', TRUE);
+
+ module_load_include('inc', 'features', 'features.export');
+ $files = system_rebuild_module_data();
+
+ // Build maximal list of dependencies.
+ $install = array();
+ foreach ($modules as $name) {
+ // Parse the dependency string into the module name and version information.
+ $parsed_name = drupal_parse_dependency($name);
+ $name = $parsed_name['name'];
+ if ($file = $files[$name]) {
+ $install[] = $name;
+ if (!empty($file->info['dependencies'])) {
+ $install = array_merge($install, _features_export_maximize_dependencies($file->info['dependencies']));
+ }
+ }
+ }
+
+ // Filter out enabled modules.
+ $enabled = array_filter($install, 'module_exists');
+ $install = array_diff($install, $enabled);
+
+ if (!empty($install)) {
+ // Make sure the install API is available.
+ $install = array_unique($install);
+ include_once DRUPAL_ROOT . '/' . './includes/install.inc';
+ module_enable($install);
+ }
+}
+
+/**
+ * Wrapper around features_get_info() that returns an array
+ * of module info objects that are features.
+ */
+function features_get_features($name = NULL, $reset = FALSE) {
+ return features_get_info('feature', $name, $reset);
+}
+
+/**
+ * Helper for retrieving info from system table.
+ */
+function features_get_info($type = 'module', $name = NULL, $reset = FALSE) {
+ static $cache;
+ if (!isset($cache)) {
+ $cache = cache_get('features_module_info', 'cache_features');
+ }
+ if (empty($cache) || $reset) {
+ $data = array(
+ 'feature' => array(),
+ 'module' => array(),
+ );
+ $ignored = variable_get('features_ignored_orphans', array());
+ $files = system_rebuild_module_data();
+
+ foreach ($files as $row) {
+ // Remove modification timestamp, added in Drupal 7.33.
+ if (isset($row->info['mtime'])) {
+ unset($row->info['mtime']);
+ }
+ // Avoid false-reported feature overrides for php = 5.2.4 line in .info file.
+ if (isset($row->info['php'])) {
+ unset($row->info['php']);
+ }
+ // If module is no longer enabled, remove it from the ignored orphans list.
+ if (in_array($row->name, $ignored, TRUE) && !$row->status) {
+ $key = array_search($row->name, $ignored, TRUE);
+ unset($ignored[$key]);
+ }
+
+ if (!empty($row->info['features'])) {
+ // Fix css/js paths
+ if (!empty($row->info['stylesheets'])) {
+ foreach ($row->info['stylesheets'] as $media => $css) {
+ $row->info['stylesheets'][$media] = array_keys($css);
+ }
+ }
+ if (!empty($row->info['scripts'])) {
+ $row->info['scripts'] = array_keys($row->info['scripts']);
+ }
+ // Rework the features array, to change the vocabulary permission
+ // features.
+ foreach ($row->info['features'] as $component => $features) {
+ if ($component == 'user_permission') {
+ foreach ($features as $key => $feature) {
+ // Export vocabulary permissions using the machine name, instead
+ // of vocabulary id.
+ _user_features_change_term_permission($feature);
+ $row->info['features'][$component][$key] = $feature;
+ }
+ }
+ }
+ $data['feature'][$row->name] = $row;
+ $data['feature'][$row->name]->components = array_keys($row->info['features']);
+ if (!empty($row->info['dependencies'])) {
+ $data['feature'][$row->name]->components[] = 'dependencies';
+ }
+ }
+ $data['module'][$row->name] = $row;
+ }
+
+ // Sort features according to dependencies.
+ // @see install_profile_modules()
+ $required = array();
+ $non_required = array();
+
+ $modules = array_keys($data['feature']);
+ foreach ($modules as $module) {
+ if ($files[$module]->requires) {
+ $modules = array_merge($modules, array_keys($files[$module]->requires));
+ }
+ }
+ $modules = array_unique($modules);
+ foreach ($modules as $module) {
+ if (!empty($files[$module]->info['features'])) {
+ if (!empty($files[$module]->info['required'])) {
+ $required[$module] = $files[$module]->sort;
+ }
+ else {
+ $non_required[$module] = $files[$module]->sort;
+ }
+ }
+ }
+ arsort($required);
+ arsort($non_required);
+
+ $sorted = array();
+ foreach ($required + $non_required as $module => $weight) {
+ $sorted[$module] = $data['feature'][$module];
+ }
+ $data['feature'] = $sorted;
+
+ variable_set('features_ignored_orphans', $ignored);
+ cache_set('features_module_info', $data, 'cache_features');
+ $cache = new stdClass();
+ $cache->data = $data;
+ }
+ if (!empty($name)) {
+ return !empty($cache->data[$type][$name]) ? clone $cache->data[$type][$name] : FALSE;
+ }
+ return !empty($cache->data[$type]) ? $cache->data[$type] : FALSE;
+}
+
+/**
+ * Generate an array of feature dependencies that have been orphaned.
+ */
+function features_get_orphans($reset = FALSE) {
+ static $orphans;
+ if (!isset($orphans) || $reset) {
+ module_load_include('inc', 'features', 'features.export');
+ $orphans = array();
+
+ // Build a list of all dependencies for enabled and disabled features.
+ $dependencies = array('enabled' => array(), 'disabled' => array());
+ $features = features_get_features();
+ foreach ($features as $feature) {
+ $key = module_exists($feature->name) ? 'enabled' : 'disabled';
+ if (!empty($feature->info['dependencies'])) {
+ $dependencies[$key] = array_merge($dependencies[$key], _features_export_maximize_dependencies($feature->info['dependencies']));
+ }
+ }
+ $dependencies['enabled'] = array_unique($dependencies['enabled']);
+ $dependencies['disabled'] = array_unique($dependencies['disabled']);
+
+ // Find the list of orphaned modules.
+ $orphaned = array_diff($dependencies['disabled'], $dependencies['enabled']);
+ $orphaned = array_intersect($orphaned, module_list(FALSE, FALSE));
+ $orphaned = array_diff($orphaned, drupal_required_modules());
+ $orphaned = array_diff($orphaned, array('features'));
+
+ // Build final list of modules that can be disabled.
+ $modules = features_get_modules(NULL, TRUE);
+ $enabled = module_list();
+ _module_build_dependencies($modules);
+
+ foreach ($orphaned as $module) {
+ if (!empty($modules[$module]->required_by)) {
+ foreach ($modules[$module]->required_by as $module_name => $dependency) {
+ $modules[$module]->required_by[$module_name] = $dependency['name'];
+ }
+ // Determine whether any dependents are actually enabled.
+ $dependents = array_intersect($modules[$module]->required_by, $enabled);
+ if (empty($dependents)) {
+ $info = features_get_modules($module);
+ $orphans[$module] = $info;
+ }
+ }
+ }
+ }
+ return $orphans;
+}
+
+/**
+ * Detect potential conflicts between any features that provide
+ * identical components.
+ */
+function features_get_conflicts($reset = FALSE) {
+ $conflicts = array();
+ $component_info = features_get_components();
+ $map = features_get_component_map(NULL, $reset);
+
+ foreach ($map as $type => $components) {
+ // Only check conflicts for components we know about.
+ if (isset($component_info[$type])) {
+ foreach ($components as $component => $modules) {
+ if (isset($component_info[$type]['duplicates']) && $component_info[$type]['duplicates'] == FEATURES_DUPLICATES_ALLOWED) {
+ continue;
+ }
+ elseif (count($modules) > 1) {
+ foreach ($modules as $module) {
+ if (!isset($conflicts[$module])) {
+ $conflicts[$module] = array();
+ }
+ foreach ($modules as $m) {
+ if ($m != $module) {
+ $conflicts[$module][$m][$type][] = $component;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return $conflicts;
+}
+
+/**
+ * Provide a component to feature map.
+ */
+function features_get_component_map($key = NULL, $reset = FALSE) {
+ static $map;
+ if (!isset($map) || $reset) {
+ $map = array();
+ $features = features_get_features(NULL, $reset);
+ foreach ($features as $feature) {
+ foreach ($feature->info['features'] as $type => $components) {
+ if (!isset($map[$type])) {
+ $map[$type] = array();
+ }
+ foreach ($components as $component) {
+ $map[$type][$component][] = $feature->name;
+ }
+ }
+ }
+ }
+ if (isset($key)) {
+ return isset($map[$key]) ? $map[$key] : array();
+ }
+ return $map;
+}
+
+/**
+ * Simple wrapper returns the status of a module.
+ */
+function features_get_module_status($module) {
+ if (module_exists($module)) {
+ return FEATURES_MODULE_ENABLED;
+ }
+ elseif (features_get_modules($module)) {
+ return FEATURES_MODULE_DISABLED;
+ }
+ else {
+ return FEATURES_MODULE_MISSING;
+ }
+}
+
+/**
+ * Menu title callback.
+ */
+function features_get_feature_title($feature) {
+ return $feature->info['name'];
+}
+
+/**
+ * Menu access callback for whether a user should be able to access
+ * override actions for a given feature.
+ */
+function features_access_override_actions($feature) {
+ if (user_access('administer features')) {
+ static $access = array();
+ if (!isset($access[$feature->name])) {
+ // Set a value first. We may get called again from within features_detect_overrides().
+ $access[$feature->name] = FALSE;
+
+ features_include();
+ module_load_include('inc', 'features', 'features.export');
+ $access[$feature->name] = in_array(features_get_storage($feature->name), array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW)) && user_access('administer features');
+ }
+ return $access[$feature->name];
+ }
+ return FALSE;
+}
+
+/**
+ * Implements hook_form_alter() for system_modules form().
+ */
+function features_form_system_modules_alter(&$form) {
+ features_rebuild();
+}
+
+/**
+ * Restore the specified modules to the default state.
+ */
+function _features_restore($op, $items = array()) {
+ $lockable = FALSE;
+ // Set this variable in $conf if having timeout issues during install/rebuild.
+ if (variable_get('features_restore_time_limit_' . $op, FALSE) !== FALSE) {
+ drupal_set_time_limit(variable_get('features_restore_time_limit_' . $op, FALSE));
+ }
+
+ module_load_include('inc', 'features', 'features.export');
+ features_include();
+
+ switch ($op) {
+ case 'revert':
+ $restore_states = array(FEATURES_OVERRIDDEN, FEATURES_REBUILDABLE, FEATURES_NEEDS_REVIEW);
+ $restore_hook = 'features_revert';
+ $log_action = 'Revert';
+ $lockable = TRUE;
+ break;
+ case 'rebuild':
+ $restore_states = array(FEATURES_REBUILDABLE);
+ $restore_hook = 'features_rebuild';
+ $log_action = 'Rebuild';
+ $lockable = variable_get('features_lock_mode', 'all') == 'all';
+ break;
+ case 'disable':
+ $restore_hook = 'features_disable_feature';
+ $log_action = 'Disable';
+ break;
+ case 'enable':
+ $restore_hook = 'features_enable_feature';
+ $log_action = 'Enable';
+ break;
+ }
+
+ if (empty($items)) {
+ // Drush may execute a whole chain of commands that may trigger feature
+ // rebuilding multiple times during a single request. Make sure we do not
+ // rebuild the same cached list of modules over and over again by setting
+ // $reset to TRUE.
+ // Note: this may happen whenever more than one feature will be enabled
+ // in chain, for example also using features_install_modules().
+ $states = features_get_component_states(array(), ($op == 'rebuild'), defined('DRUSH_BASE_PATH'));
+ foreach ($states as $module_name => $components) {
+ foreach ($components as $component => $state) {
+ if (in_array($state, $restore_states)) {
+ $items[$module_name][] = $component;
+ }
+ }
+ }
+ }
+
+ // Invoke global pre restore hook.
+ module_invoke_all('features_pre_restore', $op, $items);
+ foreach ($items as $module_name => $components) {
+ // If feature is totally locked, do not execute past this stage.
+ if ($lockable && features_feature_is_locked($module_name)) {
+ watchdog('features', 'Tried @actioning a locked @module_name, aborted.', array('@action' => $log_action, '@module_name' => $module_name));
+ continue;
+ }
+ foreach ($components as $component) {
+ // If feature is totally locked, do not execute past this stage.
+ if ($lockable && features_feature_is_locked($module_name, $component)) {
+ watchdog('features', 'Tried @actioning a locked @module_name / @component, aborted.', array('@action' => $log_action, '@component' => $component, '@module_name' => $module_name));
+ continue;
+ }
+ // Invoke pre hook
+ $pre_hook = 'pre_' . $restore_hook;
+ module_invoke($module_name, $pre_hook, $component);
+
+ if (features_hook($component, $restore_hook)) {
+ // Set a semaphore to prevent other instances of the same script from running concurrently.
+ watchdog('features', '@actioning @module_name / @component.', array('@action' => $log_action, '@component' => $component, '@module_name' => $module_name));
+ features_semaphore('set', $component);
+ features_invoke($component, $restore_hook, $module_name);
+
+ // If the script completes, remove the semaphore and set the code signature.
+ features_semaphore('del', $component);
+ features_set_signature($module_name, $component);
+ watchdog('features', '@action completed for @module_name / @component.', array('@action' => $log_action, '@component' => $component, '@module_name' => $module_name));
+ }
+
+ // Invoke post hook
+ $post_hook = 'post_' . $restore_hook;
+ module_invoke($module_name, $post_hook, $component);
+ }
+ }
+ // Invoke global post restore hook.
+ module_invoke_all('features_post_restore', $op, $items);
+}
+
+/**
+ * Wrapper around _features_restore().
+ */
+function features_revert($revert = array()) {
+ return _features_restore('revert', $revert);
+}
+
+/**
+ * Wrapper around _features_restore().
+ */
+function features_rebuild($rebuild = array()) {
+ return _features_restore('rebuild', $rebuild);
+}
+
+/**
+ * Revert a single features module.
+ *
+ * @param string $module
+ * A features module machine name. This module must be a
+ * features module and enabled.
+ */
+function features_revert_module($module) {
+ if (($feature = feature_load($module, TRUE)) && module_exists($module)) {
+ $components = array();
+ foreach (array_keys($feature->info['features']) as $component) {
+ if (features_hook($component, 'features_revert')) {
+ $components[] = $component;
+ }
+ }
+ features_revert(array($module => $components));
+ }
+}
+
+/**
+ * Utility functions ==================================================
+ */
+
+/**
+ * Log a message, environment agnostic.
+ *
+ * @param $message
+ * The message to log.
+ * @param $severity
+ * The severity of the message: status, warning or error.
+ */
+function features_log($message, $severity = 'status') {
+ if (function_exists('drush_verify_cli')) {
+ $message = strip_tags($message);
+ if ($severity == 'status') {
+ $severity = 'ok';
+ }
+ elseif ($severity == 'error') {
+ drush_set_error($message);
+ return;
+ }
+ drush_log($message, $severity);
+ return;
+ }
+ drupal_set_message($message, $severity, FALSE);
+}
+
+/**
+ * Implements hook_hook_info().
+ */
+function features_hook_info() {
+ $hooks = array(
+ 'features_api',
+ 'features_pipe_alter',
+ 'features_export_alter',
+ 'features_export_options_alter',
+ );
+ return array_fill_keys($hooks, array('group' => 'features'));
+}
+
+/**
+ * Change vocabularies permission, from vocab id to machine name and vice versa.
+ */
+function _user_features_change_term_permission(&$perm, $type = 'vid') {
+ if (!module_exists('taxonomy')) {
+ return;
+ }
+ // Export vocabulary permissions using the machine name, instead of vocabulary
+ // id.
+ if (strpos($perm, 'edit terms in ') !== FALSE || strpos($perm, 'delete terms in ') !== FALSE) {
+ preg_match("/(?<=\040)([^\s]+?)$/", trim($perm), $voc_id);
+ $vid = $voc_id[0];
+ if (is_numeric($vid) && $type == 'vid') {
+ if (function_exists('taxonomy_vocabulary_load')) {
+ if ($voc = taxonomy_vocabulary_load($vid)) {
+ $perm = str_replace($vid, $voc->machine_name, $perm);
+ }
+ }
+ }
+ elseif ($type == 'machine_name') {
+ if ($voc = taxonomy_vocabulary_machine_name_load($vid)) {
+ $perm = str_replace($vid, $voc->vid, $perm);
+ }
+ }
+ }
+}
+
+/**
+ * Recursively computes the difference of arrays with additional index check.
+ *
+ * This is a version of array_diff_assoc() that supports multidimensional
+ * arrays.
+ *
+ * @param array $array1
+ * The array to compare from.
+ * @param array $array2
+ * The array to compare to.
+ *
+ * @return array
+ * Returns an array containing all the values from array1 that are not present
+ * in array2.
+ */
+function features_array_diff_assoc_recursive(array $array1, array $array2) {
+ $difference = array();
+ foreach ($array1 as $key => $value) {
+ if (is_array($value)) {
+ if (!isset($array2[$key]) || !is_array($array2[$key])) {
+ $difference[$key] = $value;
+ }
+ else {
+ $new_diff = features_array_diff_assoc_recursive($value, $array2[$key]);
+ if (!empty($new_diff)) {
+ $difference[$key] = $new_diff;
+ }
+ }
+ }
+ elseif (!isset($array2[$key]) || $array2[$key] != $value) {
+ $difference[$key] = $value;
+ }
+ }
+ return $difference;
+}
+
+/**
+ * Returns an array of deprecated components
+ * Rather than deprecating the component directly, we look for other components
+ * that supersedes the component
+ * @param $components
+ * The array of components (component_info) from features_get_components typically.
+ */
+function features_get_deprecated($components = array()) {
+ if (empty($components)) {
+ $components = features_get_components();
+ }
+ $deprecated = array();
+ foreach ($components as $component => $component_info) {
+ if (!empty($component_info['supersedes'])) {
+ $deprecated[$component_info['supersedes']] = $component_info['supersedes'];
+ }
+ }
+ return $deprecated;
+}
+
+/**
+ * Returns whether a feature or it's component is locked.
+ */
+function features_feature_is_locked($feature, $component = NULL, $check_global_component_setting = TRUE) {
+ $locked = variable_get('features_feature_locked', array());
+ if ($component) {
+ return ($check_global_component_setting && features_component_is_locked($component)) || !empty($locked[$feature][$component]);
+ }
+ else {
+ return !empty($locked[$feature]['_all']);
+ }
+}
+
+/**
+ * Returns whether a component is locked.
+ */
+function features_component_is_locked($component) {
+ return variable_get('features_component_locked_' . $component, FALSE);
+}
+
+/**
+ * Locks a feature or it's component.
+ */
+function features_feature_lock($feature, $component = NULL) {
+ $locked = variable_get('features_feature_locked', array());
+ $locked[$feature] = !empty($locked[$feature]) ? $locked[$feature] : array();
+ if ($component) {
+ $locked[$feature][$component] = TRUE;
+ }
+ else {
+ $locked[$feature]['_all'] = TRUE;
+ }
+ variable_set('features_feature_locked', $locked);
+}
+
+/**
+ * Unlocks a feature or it's component.
+ */
+function features_feature_unlock($feature, $component = NULL) {
+ $locked = variable_get('features_feature_locked', array());
+ if ($component) {
+ unset($locked[$feature][$component]);
+ }
+ else {
+ unset($locked[$feature]['_all']);
+ }
+ variable_set('features_feature_locked', $locked);
+}
+
+/**
+ * Sets the current language to english to ensure a proper export.
+ */
+function _features_set_export_language() {
+ // Ensure this is only done if the language isn't already en.
+ // This can be called multiple times - ensure the handling is done just once.
+ if ($GLOBALS['language']->language != 'en' && !drupal_static(__FUNCTION__)) {
+ // Create the language object as language_default() does.
+ $GLOBALS['language'] = (object) array(
+ 'language' => 'en',
+ 'name' => 'English',
+ 'native' => 'English',
+ 'direction' => 0,
+ 'enabled' => 1,
+ 'plurals' => 0,
+ 'formula' => '',
+ 'domain' => '',
+ 'prefix' => '',
+ 'weight' => 0,
+ 'javascript' => '',
+ );
+ // Ensure that static caches are cleared, as they might contain language
+ // specific information. But keep some important ones. The call below
+ // accesses a non existing key and requests to reset it. In such cases the
+ // whole caching data array is returned.
+ $static = drupal_static(uniqid('', TRUE), NULL, TRUE);
+ drupal_static_reset();
+ // Restore some of the language independent, runtime state information to
+ // keep everything working and avoid unnecessary double processing.
+ $static_caches_to_keep = array(
+ 'conf_path',
+ 'system_list',
+ 'ip_address',
+ 'drupal_page_is_cacheable',
+ 'list_themes',
+ 'drupal_page_header',
+ 'drupal_send_headers',
+ 'drupal_http_headers',
+ 'language_list',
+ 'module_implements',
+ 'drupal_alter',
+ 'path_is_admin',
+ 'path_get_admin_paths',
+ 'drupal_match_path',
+ 'menu_get_custom_theme',
+ 'menu_get_item',
+ 'arg',
+ 'drupal_system_listing',
+ 'drupal_parse_info_file',
+ 'libraries_get_path',
+ 'module_hook_info',
+ 'drupal_add_js',
+ 'drupal_add_js:jquery_added',
+ 'drupal_add_library',
+ 'drupal_get_library',
+ 'drupal_add_css',
+ 'menu_set_active_trail',
+ 'menu_link_get_preferred',
+ 'menu_set_active_menu_names',
+ 'theme_get_registry',
+ 'features_get_components',
+ 'features_get_components_by_key',
+ );
+ foreach ($static_caches_to_keep as $cid) {
+ if (isset($static[$cid])) {
+ $data = &drupal_static($cid);
+ $data = $static[$cid];
+ }
+ }
+ $called = &drupal_static(__FUNCTION__);
+ $called = TRUE;
+ }
+}
+
+/**
+ * Implements hook_features_ignore().
+ */
+function features_features_ignore($component) {
+ // Determine which keys need to be ignored for override diff for various components.
+ // Value is how many levels deep the key is.
+ $ignores = array();
+ switch ($component) {
+ case 'views_view':
+ $ignores['current_display'] = 0;
+ $ignores['display_handler'] = 0;
+ $ignores['handler'] = 2;
+ $ignores['query'] = 0;
+ $ignores['localization_plugin'] = 0;
+ // Views automatically adds these two on export to set values.
+ $ignores['api_version'] = 0;
+ $ignores['disabled'] = 0;
+ break;
+ case 'image':
+ $ignores['module'] = 0;
+ $ignores['name'] = 0;
+ $ignores['storage'] = 0;
+ // Various properties are loaded into the effect in image_styles.
+ $ignores['summary theme'] = 2;
+ $ignores['module'] = 2;
+ $ignores['label'] = 2;
+ $ignores['help'] = 2;
+ $ignores['form callback'] = 2;
+ $ignores['effect callback'] = 2;
+ $ignores['dimensions callback'] = 2;
+ break;
+ case 'field':
+ $ignores['locked'] = 1;
+ break;
+ case 'field_base':
+ $ignores['indexes'] = 0;
+ break;
+ }
+ return $ignores;
+}
diff --git a/docroot/sites/all/modules/features/includes/features.block.inc b/docroot/sites/all/modules/features/includes/features.block.inc
new file mode 100644
index 0000000..3d0db3b
--- /dev/null
+++ b/docroot/sites/all/modules/features/includes/features.block.inc
@@ -0,0 +1,40 @@
+ array(
+ 'name' => t('Contact categories'),
+ 'feature_source' => TRUE,
+ 'default_hook' => 'contact_categories_defaults',
+ 'default_file' => FEATURES_DEFAULTS_INCLUDED,
+ ),
+ );
+}
+
+/**
+ * Implements hook_features_export_options().
+ */
+function contact_categories_features_export_options() {
+ $options = array();
+ $categories = db_select('contact', 'c')->fields('c')->execute()->fetchAll();
+ foreach ($categories as $category) {
+ $options["$category->category"] = "$category->category";
+ }
+ return $options;
+}
+
+/**
+ * Implements hook_features_export().
+ */
+function contact_categories_features_export($data, &$export, $module_name = '') {
+ $export['dependencies']['features'] = 'features';
+ $export['dependencies']['contact'] = 'contact';
+
+ foreach ($data as $name) {
+ $export['features']['contact_categories'][$name] = $name;
+ }
+
+ return array();
+}
+
+/**
+ * Implements hook_features_export_render().
+ */
+function contact_categories_features_export_render($module, $data, $export = NULL) {
+ $render = array();
+ foreach ($data as $name) {
+ $export_category = db_select('contact', 'c')
+ ->fields('c', array('cid', 'category'))
+ ->condition('category', $name, 'LIKE')
+ ->execute()
+ ->fetchAll();
+ if (isset($export_category[0]->cid) && ($category = contact_load($export_category[0]->cid))) {
+ unset($category['cid']);
+ $render[$name] = $category;
+ }
+ }
+ return array('contact_categories_defaults' => ' return ' . features_var_export($render, ' ') . ';');
+}
+
+/**
+ * Implements hook_features_revert().
+ */
+function contact_categories_features_revert($module) {
+ return contact_categories_features_rebuild($module);
+}
+
+/**
+ * Implements hook_features_rebuild().
+ */
+function contact_categories_features_rebuild($module) {
+ if ($defaults = features_get_default('contact_categories', $module)) {
+ foreach ($defaults as $default_category) {
+ $existing_categories = db_select('contact', 'c')
+ ->fields('c', array('cid', 'category'))
+ ->execute()
+ ->fetchAll();
+ if ($existing_categories) {
+ foreach ($existing_categories as $existing_category) {
+ if ($default_category['category'] == $existing_category->category) {
+ db_update('contact')
+ ->fields(
+ array(
+ 'recipients' => $default_category['recipients'],
+ 'reply' => $default_category['reply'],
+ 'weight' => $default_category['weight'],
+ 'selected' => $default_category['selected'],
+ )
+ )
+ ->condition('category', $existing_category->category, '=')
+ ->execute();
+ }
+ else {
+ db_merge('contact')
+ ->key(array('category' => $default_category['category']))
+ ->fields($default_category)
+ ->execute();
+ }
+ }
+ }
+ else {
+ db_merge('contact')
+ ->key(array('category' => $default_category['category']))
+ ->fields($default_category)
+ ->execute();
+ }
+ }
+ }
+}
diff --git a/docroot/sites/all/modules/features/includes/features.context.inc b/docroot/sites/all/modules/features/includes/features.context.inc
new file mode 100644
index 0000000..2da59a7
--- /dev/null
+++ b/docroot/sites/all/modules/features/includes/features.context.inc
@@ -0,0 +1,54 @@
+conditions{$key}['values'])) {
+ foreach ($context->conditions{$key}['values'] as $item) {
+ // Special pipe for views
+ if ($key === 'views') {
+ $split = explode(':', $item);
+ $view_name = array_shift($split);
+ $pipe[$key][$view_name] = $view_name;
+ }
+ else {
+ $pipe[$key][$item] = $item;
+ }
+ }
+ }
+ }
+ // Reactions.
+ if (!empty($context->reactions['block']['blocks'])) {
+ foreach ($context->reactions['block']['blocks'] as $block) {
+ $block = (array) $block;
+ $bid = "{$block['module']}-{$block['delta']}";
+ $pipe['block'][$bid] = $bid;
+ }
+ }
+ }
+ }
+ return $pipe;
+}
+
+/**
+ * Implements hook_features_revert().
+ *
+ * @param $module
+ * name of module to revert content for
+ */
+function context_features_revert($module = NULL) {
+ $return = ctools_component_features_revert('context', $module);
+ context_invalidate_cache();
+ return $return;
+}
diff --git a/docroot/sites/all/modules/features/includes/features.ctools.inc b/docroot/sites/all/modules/features/includes/features.ctools.inc
new file mode 100644
index 0000000..387cece
--- /dev/null
+++ b/docroot/sites/all/modules/features/includes/features.ctools.inc
@@ -0,0 +1,378 @@
+ $info) {
+ $code = '';
+ if (!function_exists("{$info['module']}_features_api")) {
+ $code .= 'function '. $info['module'] .'_features_api() { return ctools_component_features_api("'. $info['module'] .'"); }';
+ }
+
+ // ctools component with owner defined as "ctools"
+ if (!function_exists("{$component}_features_api") && $info['module'] === 'ctools') {
+ $code .= 'function '. $component .'_features_api() { return ctools_component_features_api("'. $component .'"); }';
+ }
+
+ if (!function_exists("{$component}_features_export")) {
+ $code .= 'function '. $component .'_features_export($data, &$export, $module_name = "") { return ctools_component_features_export("'. $component .'", $data, $export, $module_name); }';
+ }
+ if (!function_exists("{$component}_features_export_options")) {
+ $code .= 'function '. $component .'_features_export_options() { return ctools_component_features_export_options("'. $component .'"); }';
+ }
+ if (!function_exists("{$component}_features_export_render")) {
+ $code .= 'function '. $component .'_features_export_render($module, $data, $export = NULL) { return ctools_component_features_export_render("'. $component .'", $module, $data, $export); }';
+ }
+ if (!function_exists("{$component}_features_revert")) {
+ $code .= 'function '. $component .'_features_revert($module) { return ctools_component_features_revert("'. $component .'", $module); }';
+ }
+ eval($code);
+ }
+ }
+}
+
+/**
+ * Implements hook_features_api().
+ */
+function ctools_features_api() {
+ return array(
+ 'ctools' => array(
+ 'name' => 'CTools export API',
+ 'feature_source' => TRUE,
+ 'duplicates' => FEATURES_DUPLICATES_ALLOWED,
+ // CTools API integration does not include a default hook declaration as
+ // it is not a proper default hook.
+ // 'default_hook' => 'ctools_plugin_api',
+ ),
+ );
+}
+
+/**
+ * Implements hook_features_export().
+ * Adds references to the ctools mothership hook, ctools_plugin_api().
+ */
+function ctools_features_export($data, &$export, $module_name = '') {
+ // Add ctools dependency
+ $export['dependencies']['ctools'] = 'ctools';
+
+ // Add the actual ctools components which will need to be accounted for in
+ // hook_ctools_plugin_api(). The components are actually identified by a
+ // delimited list of values: `module_name:api:current_version`
+ foreach ($data as $component) {
+ if ($info = _ctools_features_get_info($component)) {
+ $identifier = "{$info['module']}:{$info['api']}:{$info['current_version']}";
+ $export['features']['ctools'][$identifier] = $identifier;
+ }
+ }
+
+ return array();
+}
+
+/**
+ * Implements hook_features_export_render().
+ * Adds the ctools mothership hook, ctools_plugin_api().
+ */
+function ctools_features_export_render($module, $data) {
+ $component_exports = array();
+ foreach ($data as $component) {
+ $code = array();
+ if ($info = _ctools_features_get_info($component)) {
+ // For background on why we change the output for hook_views_api()
+ // see http://drupal.org/node/1459120.
+ if ($info['module'] == 'views') {
+ $code[] = ' return array("api" => "3.0");';
+ }
+ else {
+ $code[] = ' if ($module == "'. $info['module'] .'" && $api == "'. $info['api'] .'") {';
+ $code[] = ' return array("version" => "'. $info['current_version'] .'");';
+ $code[] = ' }';
+ }
+ }
+ ctools_include('plugins');
+ $plugin_api_hook_name = ctools_plugin_api_get_hook($info['module'], $info['api']);
+
+ if (key_exists($plugin_api_hook_name, $component_exports)) {
+ $component_exports[$plugin_api_hook_name]['code'] .= "\n" . implode("\n", $code);
+ }
+ else {
+ $component_exports[$plugin_api_hook_name] = array(
+ 'code' => implode("\n", $code),
+ 'args' => '$module = NULL, $api = NULL',
+ );
+ }
+ }
+
+ return $component_exports;
+
+}
+
+/**
+ * Master implementation of hook_features_api() for all ctools components.
+ *
+ * Note that this master hook does not use $component like the others, but uses the
+ * component module's namespace instead.
+ */
+function ctools_component_features_api($module_name) {
+ $api = array();
+ foreach (_ctools_features_get_info() as $component => $info) {
+ // if module owner is set to "ctools" we need to compare the component
+ if ($info['module'] == $module_name || ($info['module'] === 'ctools' && $component == $module_name) ) {
+ $api[$component] = $info;
+ }
+ }
+ return $api;
+}
+
+/**
+ * Master implementation of hook_features_export_options() for all ctools components.
+ */
+function ctools_component_features_export_options($component) {
+ $options = array();
+
+ ctools_include('export');
+ $schema = ctools_export_get_schema($component);
+ if ($schema && $schema['export']['bulk export']) {
+ if (!empty($schema['export']['list callback']) && function_exists($schema['export']['list callback'])) {
+ $options = $schema['export']['list callback']();
+ }
+ else {
+ $options = _ctools_features_export_default_list($component, $schema);
+ }
+ }
+ asort($options);
+ return $options;
+}
+
+/**
+ * Master implementation of hook_features_export() for all ctools components.
+ */
+function ctools_component_features_export($component, $data, &$export, $module_name = '') {
+ // Add the actual implementing module as a dependency
+ $info = _ctools_features_get_info();
+ if ($module_name !== $info[$component]['module']) {
+ $export['dependencies'][$info[$component]['module']] = $info[$component]['module'];
+ }
+
+ // Add the components
+ foreach ($data as $object_name) {
+ if ($object = _ctools_features_export_crud_load($component, $object_name)) {
+ // If this object is provided as a default by a different module, don't
+ // export and add that module as a dependency instead.
+ if (!empty($object->export_module) && $object->export_module !== $module_name) {
+ $export['dependencies'][$object->export_module] = $object->export_module;
+ if (isset($export['features'][$component][$object_name])) {
+ unset($export['features'][$component][$object_name]);
+ }
+ }
+ // Otherwise, add the component.
+ else {
+ $export['features'][$component][$object_name] = $object_name;
+ }
+ }
+ }
+
+ // Let CTools handle API integration for this component.
+ return array('ctools' => array($component));
+}
+
+/**
+ * Master implementation of hook_features_export_render() for all ctools components.
+ */
+function ctools_component_features_export_render($component, $module, $data) {
+ // Reset the export display static to prevent clashes.
+ drupal_static_reset('panels_export_display');
+
+ ctools_include('export');
+ $schema = ctools_export_get_schema($component);
+
+ if (function_exists($schema['export']['to hook code callback'])) {
+ $export = $schema['export']['to hook code callback']($data, $module);
+ $code = explode("{\n", $export);
+ array_shift($code);
+ $code = explode('}', implode($code, "{\n"));
+ array_pop($code);
+ $code = implode('}', $code);
+ }
+ else {
+ $code = ' $export = array();'."\n\n";
+ foreach ($data as $object_name) {
+ if ($object = _ctools_features_export_crud_load($component, $object_name)) {
+ $identifier = $schema['export']['identifier'];
+ $code .= _ctools_features_export_crud_export($component, $object, ' ');
+ $code .= " \$export[" . ctools_var_export($object_name) . "] = \${$identifier};\n\n";
+ }
+ }
+ $code .= ' return $export;';
+ }
+
+ return array($schema['export']['default hook'] => $code);
+}
+
+/**
+ * Master implementation of hook_features_revert() for all ctools components.
+ */
+function ctools_component_features_revert($component, $module) {
+ if ($objects = features_get_default($component, $module)) {
+ foreach ($objects as $name => $object) {
+ // Some things (like views) do not use the machine name as key
+ // and need to be loaded explicitly in order to be deleted.
+ $object = ctools_export_crud_load($component, $name);
+ if ($object && ($object->export_type & EXPORT_IN_DATABASE)) {
+ _ctools_features_export_crud_delete($component, $object);
+ }
+ }
+ }
+}
+
+/**
+ * Helper function to return various ctools information for components.
+ */
+function _ctools_features_get_info($identifier = NULL, $reset = FALSE) {
+ static $components;
+ if (!isset($components) || $reset) {
+ $components = array();
+ $modules = features_get_info();
+ ctools_include('export');
+ drupal_static('ctools_export_get_schemas', NULL, $reset);
+ foreach (ctools_export_get_schemas_by_module() as $module => $schemas) {
+ foreach ($schemas as $table => $schema) {
+ if ($schema['export']['bulk export']) {
+ // Let the API owner take precedence as the owning module.
+ $api_module = isset($schema['export']['api']['owner']) ? $schema['export']['api']['owner'] : $module;
+ $components[$table] = array(
+ 'name' => isset($modules[$api_module]->info['name']) ? $modules[$api_module]->info['name'] : $api_module,
+ 'default_hook' => $schema['export']['default hook'],
+ 'default_file' => FEATURES_DEFAULTS_CUSTOM,
+ 'module' => $api_module,
+ 'feature_source' => TRUE,
+ );
+ if (isset($schema['export']['api'])) {
+ $components[$table] += array(
+ 'api' => $schema['export']['api']['api'],
+ 'default_filename' => $schema['export']['api']['api'],
+ 'current_version' => $schema['export']['api']['current_version'],
+ );
+ }
+ }
+ }
+ }
+ }
+
+ // Return information specific to a particular component.
+ if (isset($identifier)) {
+ // Identified by the table name.
+ if (isset($components[$identifier])) {
+ return $components[$identifier];
+ }
+ // New API identifier. Allows non-exportables related CTools APIs to be
+ // supported by an explicit `module:api:current_version` key.
+ else if (substr_count($identifier, ':') === 2) {
+ list($module, $api, $current_version) = explode(':', $identifier);
+ // If a schema component matches the provided identifier, provide that
+ // information. This also ensures that the version number is up to date.
+ foreach ($components as $table => $info) {
+ if ($info['module'] == $module && $info['api'] == $api && $info['current_version'] >= $current_version) {
+ return $info;
+ }
+ }
+ // Fallback to just giving back what was provided to us.
+ return array('module' => $module, 'api' => $api, 'current_version' => $current_version);
+ }
+ return FALSE;
+ }
+
+ return $components;
+}
+
+/**
+ * Wrapper around ctools_export_crud_export() for < 1.7 compatibility.
+ */
+function _ctools_features_export_crud_export($table, $object, $indent = '') {
+ return ctools_api_version('1.7') ? ctools_export_crud_export($table, $object, $indent) : ctools_export_object($table, $object, $indent);
+}
+
+/**
+ * Wrapper around ctools_export_crud_load() for < 1.7 compatibility.
+ */
+function _ctools_features_export_crud_load($table, $name) {
+ if (ctools_api_version('1.7')) {
+ return ctools_export_crud_load($table, $name);
+ }
+ elseif ($objects = ctools_export_load_object($table, 'names', array($name))) {
+ return array_shift($objects);
+ }
+ return FALSE;
+}
+
+/**
+ * Wrapper around ctools_export_default_list() for < 1.7 compatibility.
+ */
+function _ctools_features_export_default_list($table, $schema) {
+ if (ctools_api_version('1.7')) {
+ return ctools_export_default_list($table, $schema);
+ }
+ elseif ($objects = ctools_export_load_object($table, 'all')) {
+ return drupal_map_assoc(array_keys($objects));
+ }
+ return array();
+}
+
+/**
+ * Wrapper around ctools_export_crud_delete() for < 1.7 compatibility.
+ */
+function _ctools_features_export_crud_delete($table, $object) {
+ if (ctools_api_version('1.7')) {
+ ctools_export_crud_delete($table, $object);
+ }
+ else {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+ db_query("DELETE FROM {{$table}} WHERE {$export['key']} = '%s'", $object->{$export['key']});
+ }
+}
+
+/**
+ * Implements hook_features_export_render() for page_manager.
+ */
+function page_manager_pages_features_export_render($module, $data) {
+ // Reset the export display static to prevent clashes.
+ drupal_static_reset('panels_export_display');
+
+ // Ensure that handlers have their code included before exporting.
+ page_manager_get_tasks();
+ return ctools_component_features_export_render('page_manager_pages', $module, $data);
+}
+
+/**
+ * Implements hook_features_revert() for page_manager.
+ */
+function page_manager_pages_features_revert($module) {
+ if ($pages = features_get_default('page_manager_pages', $module)) {
+ require_once drupal_get_path('module', 'ctools') . '/page_manager/plugins/tasks/page.inc';
+ foreach ($pages as $page) {
+ page_manager_page_delete($page);
+ }
+ }
+}
+
+/**
+ * Implements hook_features_pipe_COMPONENT_alter() for views_view.
+ */
+function views_features_pipe_views_view_alter(&$pipe, $data, $export) {
+ // @todo Remove this check before next stable release.
+ if (!function_exists('views_plugin_list')) {
+ return;
+ }
+
+ $map = array_flip($data);
+ foreach (views_plugin_list() as $plugin) {
+ foreach ($plugin['views'] as $view_name) {
+ if (isset($map[$view_name])) {
+ $pipe['dependencies'][$plugin['module']] = $plugin['module'];
+ }
+ }
+ }
+}
diff --git a/docroot/sites/all/modules/features/includes/features.features.inc b/docroot/sites/all/modules/features/includes/features.features.inc
new file mode 100644
index 0000000..c657bc8
--- /dev/null
+++ b/docroot/sites/all/modules/features/includes/features.features.inc
@@ -0,0 +1,73 @@
+ array(
+ 'name' => 'Dependencies',
+ 'feature_source' => TRUE,
+ 'duplicates' => FEATURES_DUPLICATES_ALLOWED,
+ ),
+ );
+}
+
+/**
+ * Implements hook_features_export_options().
+ */
+function dependencies_features_export_options() {
+ // Excluded modules.
+ $excluded = drupal_required_modules();
+ $options = array();
+ foreach (features_get_modules() as $module_name => $info) {
+ if (!in_array($module_name, $excluded) && $info->status && !empty($info->info)) {
+ $options[$module_name] = $info->info['name'];
+ }
+ }
+ return $options;
+}
+
+/**
+ * Implements hook_features_export().
+ */
+function dependencies_features_export($data, &$export, $module_name = '') {
+ // Don't allow a module to depend upon itself.
+ if (!empty($data[$module_name])) {
+ unset($data[$module_name]);
+ }
+
+ // Clean up existing dependencies and merge.
+ $export['dependencies'] = _features_export_minimize_dependencies($export['dependencies'], $module_name);
+ $export['dependencies'] = array_merge($data, $export['dependencies']);
+ $export['dependencies'] = array_unique($export['dependencies']);
+}
+
+/**
+ * Implements hook_features_revert().
+ */
+function dependencies_features_revert($module) {
+ dependencies_features_rebuild($module);
+}
+
+/**
+ * Implements hook_features_rebuild().
+ * Ensure that all of a feature's dependencies are enabled.
+ */
+function dependencies_features_rebuild($module) {
+ $feature = features_get_features($module);
+ if (!empty($feature->info['dependencies'])) {
+ $install = array();
+ foreach ($feature->info['dependencies'] as $dependency) {
+ // Parse the dependency string into the module name and version information.
+ $parsed_dependency = drupal_parse_dependency($dependency);
+ $dependency = $parsed_dependency['name'];
+ if (!module_exists($dependency)) {
+ $install[] = $dependency;
+ }
+ }
+ if (!empty($install)) {
+ features_install_modules($install);
+ }
+ }
+}
diff --git a/docroot/sites/all/modules/features/includes/features.field.inc b/docroot/sites/all/modules/features/includes/features.field.inc
new file mode 100644
index 0000000..849081d
--- /dev/null
+++ b/docroot/sites/all/modules/features/includes/features.field.inc
@@ -0,0 +1,577 @@
+ array(
+ // this is deprecated by field_base and field_instance
+ // but retained for compatibility with older exports
+ 'name' => t('Fields'),
+ 'default_hook' => 'field_default_fields',
+ 'default_file' => FEATURES_DEFAULTS_INCLUDED,
+ 'feature_source' => FALSE,
+ ),
+ 'field_base' => array(
+ 'name' => t('Field Bases'),
+ 'default_hook' => 'field_default_field_bases',
+ 'default_file' => FEATURES_DEFAULTS_INCLUDED,
+ 'feature_source' => TRUE,
+ 'supersedes' => 'field',
+ ),
+ 'field_instance' => array(
+ 'name' => t('Field Instances'),
+ 'default_hook' => 'field_default_field_instances',
+ 'default_file' => FEATURES_DEFAULTS_INCLUDED,
+ 'feature_source' => TRUE,
+ 'supersedes' => 'field',
+ )
+ );
+}
+
+/**
+ * Implements hook_features_export_options().
+ */
+function field_base_features_export_options() {
+ $options = array();
+ $fields = field_info_fields();
+ foreach ($fields as $field_name => $field) {
+ $options[$field_name] = $field_name;
+ }
+ return $options;
+}
+
+/**
+ * Implements hook_features_export_options().
+ */
+function field_instance_features_export_options() {
+ $options = array();
+ foreach (field_info_fields() as $field_name => $field) {
+ foreach ($field['bundles'] as $entity_type => $bundles) {
+ foreach ($bundles as $bundle) {
+ $identifier = "{$entity_type}-{$bundle}-{$field_name}";
+ $options[$identifier] = $identifier;
+ }
+ }
+ }
+ ksort($options);
+ return $options;
+}
+
+/**
+ * Implements hook_features_export().
+ */
+function field_base_features_export($data, &$export, $module_name = '') {
+ $pipe = array();
+ $map = features_get_default_map('field_base');
+
+ // The field_default_field_bases() hook integration is provided by the
+ // features module so we need to add it as a dependency.
+ $export['dependencies']['features'] = 'features';
+
+ foreach ($data as $identifier) {
+ if ($base = features_field_base_load($identifier)) {
+ // If this field is already provided by another module, remove the field
+ // and add the other module as a dependency.
+ if (isset($map[$identifier]) && $map[$identifier] != $module_name) {
+ if (isset($export['features']['field_base'][$identifier])) {
+ unset($export['features']['field_base'][$identifier]);
+ }
+ $module = $map[$identifier];
+ $export['dependencies'][$module] = $module;
+ }
+ // If the field has not yet been exported, add it
+ else {
+ $export['features']['field_base'][$identifier] = $identifier;
+ $export['dependencies'][$base['module']] = $base['module'];
+ if ($base['storage']['type'] != variable_get('field_storage_default', 'field_sql_storage')) {
+ $export['dependencies'][$base['storage']['module']] = $base['storage']['module'];
+ }
+ // If taxonomy field, add in the vocabulary
+ if ($base['type'] == 'taxonomy_term_reference' && !empty($base['settings']['allowed_values'])) {
+ foreach ($base['settings']['allowed_values'] as $allowed_values) {
+ if (!empty($allowed_values['vocabulary'])) {
+ $pipe['taxonomy'][] = $allowed_values['vocabulary'];
+ }
+ }
+ }
+ }
+ }
+ }
+ return $pipe;
+}
+
+/**
+ * Implements hook_features_export().
+ */
+function field_instance_features_export($data, &$export, $module_name = '') {
+ $pipe = array('field_base' => array());
+ $map = features_get_default_map('field_instance');
+
+ // The field_default_field_instances() hook integration is provided by the
+ // features module so we need to add it as a dependency.
+ $export['dependencies']['features'] = 'features';
+
+ foreach ($data as $identifier) {
+ if ($instance = features_field_instance_load($identifier)) {
+ // If this field is already provided by another module, remove the field
+ // and add the other module as a dependency.
+ if (isset($map[$identifier]) && $map[$identifier] != $module_name) {
+ if (isset($export['features']['field_instance'][$identifier])) {
+ unset($export['features']['field_instance'][$identifier]);
+ }
+ $module = $map[$identifier];
+ $export['dependencies'][$module] = $module;
+ }
+ // If the field has not yet been exported, add it
+ else {
+ $export['features']['field_instance'][$identifier] = $identifier;
+ $export['dependencies'][$instance['widget']['module']] = $instance['widget']['module'];
+ foreach ($instance['display'] as $key => $display) {
+ if (isset($display['module'])) {
+ $export['dependencies'][$display['module']] = $display['module'];
+ // @TODO: handle the pipe to image styles
+ }
+ }
+ $pipe['field_base'][] = $instance['field_name'];
+ }
+ }
+ }
+ return $pipe;
+}
+
+/**
+ * Implements hook_features_export_render().
+ */
+function field_base_features_export_render($module, $data, $export = NULL) {
+ $translatables = $code = array();
+ $code[] = ' $field_bases = array();';
+ $code[] = '';
+ foreach ($data as $identifier) {
+ if ($field = features_field_base_load($identifier)) {
+ unset($field['columns']);
+ unset($field['foreign keys']);
+ // Only remove the 'storage' declaration if the field is using the default
+ // storage type.
+ if ($field['storage']['type'] == variable_get('field_storage_default', 'field_sql_storage')) {
+ unset($field['storage']);
+ }
+ // If we still have a storage declaration here it means that a non-default
+ // storage type was altered into to the field definition. And no one would
+ // never need to change the 'details' key, so don't render it.
+ if (isset($field['storage']['details'])) {
+ unset($field['storage']['details']);
+ }
+
+ _field_instance_features_export_sort($field);
+ $field_export = features_var_export($field, ' ');
+ $field_prefix = ' // Exported field_base: ';
+ $field_identifier = features_var_export($identifier);
+ if (features_field_export_needs_wrap($field_prefix, $field_identifier)) {
+ $code[] = rtrim($field_prefix);
+ $code[] = " // {$field_identifier}.";
+ }
+ else {
+ $code[] = $field_prefix . $field_identifier . '.';
+ }
+ $code[] = " \$field_bases[{$field_identifier}] = {$field_export};";
+ $code[] = "";
+ }
+ }
+ $code[] = ' return $field_bases;';
+ $code = implode("\n", $code);
+ return array('field_default_field_bases' => $code);
+}
+
+/**
+ * Implements hook_features_export_render().
+ */
+function field_instance_features_export_render($module, $data, $export = NULL) {
+ $translatables = $code = array();
+
+ $code[] = ' $field_instances = array();';
+ $code[] = '';
+
+ foreach ($data as $identifier) {
+ if ($instance = features_field_instance_load($identifier)) {
+ _field_instance_features_export_sort($instance);
+ $field_export = features_var_export($instance, ' ');
+ $instance_prefix = ' // Exported field_instance: ';
+ $instance_identifier = features_var_export($identifier);
+ if (features_field_export_needs_wrap($instance_prefix, $instance_identifier)) {
+ $code[] = rtrim($instance_prefix);
+ $code[] = " // {$instance_identifier}.";
+ }
+ else {
+ $code[] = $instance_prefix . $instance_identifier . '.';
+ }
+ $code[] = " \$field_instances[{$instance_identifier}] = {$field_export};";
+ $code[] = "";
+
+ if (!empty($instance['label'])) {
+ $translatables[] = $instance['label'];
+ }
+ if (!empty($instance['description'])) {
+ $translatables[] = $instance['description'];
+ }
+ }
+ }
+ if (!empty($translatables)) {
+ $code[] = features_translatables_export($translatables, ' ');
+ }
+ $code[] = ' return $field_instances;';
+ $code = implode("\n", $code);
+ return array('field_default_field_instances' => $code);
+}
+
+// Helper to enforce consistency in field export arrays.
+function _field_instance_features_export_sort(&$field, $sort = TRUE) {
+
+ // Some arrays are not sorted to preserve order (for example allowed_values).
+ static $sort_blacklist = array(
+ 'allowed_values',
+ 'format_handlers',
+ );
+
+ if ($sort) {
+ uksort($field, 'strnatcmp');
+ }
+ foreach ($field as $k => $v) {
+ if (is_array($v)) {
+ _field_instance_features_export_sort($field[$k], !in_array($k, $sort_blacklist));
+ }
+ }
+}
+
+/**
+ * Implements hook_features_revert().
+ */
+function field_base_features_revert($module) {
+ field_base_features_rebuild($module);
+}
+
+/**
+ * Implements hook_features_revert().
+ */
+function field_instance_features_revert($module) {
+ field_instance_features_rebuild($module);
+}
+
+/**
+ * Implements of hook_features_rebuild().
+ * Rebuilds fields from code defaults.
+ */
+function field_base_features_rebuild($module) {
+ if ($fields = features_get_default('field_base', $module)) {
+ field_info_cache_clear();
+
+ // Load all the existing field bases up-front so that we don't
+ // have to rebuild the cache all the time.
+ $existing_fields = field_info_fields();
+
+ foreach ($fields as $field) {
+ // Create or update field.
+ if (isset($existing_fields[$field['field_name']])) {
+ $existing_field = $existing_fields[$field['field_name']];
+ $array_diff_result = drupal_array_diff_assoc_recursive($field + $existing_field, $existing_field);
+ if (!empty($array_diff_result)) {
+ try {
+ field_update_field($field);
+ }
+ catch (FieldException $e) {
+ watchdog('features', 'Attempt to update field %label failed: %message', array('%label' => $field['field_name'], '%message' => $e->getMessage()), WATCHDOG_ERROR);
+ }
+ }
+ }
+ else {
+ try {
+ field_create_field($field);
+ }
+ catch (FieldException $e) {
+ watchdog('features', 'Attempt to create field %label failed: %message', array('%label' => $field['field_name'], '%message' => $e->getMessage()), WATCHDOG_ERROR);
+ }
+ $existing_fields[$field['field_name']] = $field;
+ }
+ variable_set('menu_rebuild_needed', TRUE);
+ }
+ }
+}
+
+/**
+ * Implements of hook_features_rebuild().
+ * Rebuilds field instances from code defaults.
+ */
+function field_instance_features_rebuild($module) {
+ if ($instances = features_get_default('field_instance', $module)) {
+ field_info_cache_clear();
+
+ // Load all the existing instances up-front so that we don't
+ // have to rebuild the cache all the time.
+ $existing_instances = field_info_instances();
+
+ foreach ($instances as $field_instance) {
+ // If the field base information does not exist yet, cancel out.
+ if (!field_info_field($field_instance['field_name'])) {
+ continue;
+ }
+
+ // Create or update field instance.
+ if (isset($existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']])) {
+ $existing_instance = $existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']];
+ if ($field_instance + $existing_instance !== $existing_instance) {
+ try {
+ field_update_instance($field_instance);
+ }
+ catch (FieldException $e) {
+ watchdog('features', 'Attempt to update field instance %label (in %entity entity type %bundle bundle) failed: %message', array('%label' => $field_instance['field_name'], '%entity' => $field_instance['entity_type'], '%bundle' => $field_instance['bundle'], '%message' => $e->getMessage()), WATCHDOG_ERROR);
+ }
+ }
+ }
+ else {
+ try {
+ field_create_instance($field_instance);
+ }
+ catch (FieldException $e) {
+ watchdog('features', 'Attempt to create field instance %label (in %entity entity type %bundle bundle) failed: %message', array('%label' => $field_instance['field_name'], '%entity' => $field_instance['entity_type'], '%bundle' => $field_instance['bundle'], '%message' => $e->getMessage()), WATCHDOG_ERROR);
+ }
+ $existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']] = $field_instance;
+ }
+ }
+
+ if ($instances) {
+ variable_set('menu_rebuild_needed', TRUE);
+ }
+ }
+}
+
+/**
+ * Load a field base configuration by a field_name identifier.
+ */
+function features_field_base_load($field_name) {
+ if ($field_info = field_info_field($field_name)) {
+ unset($field_info['id']);
+ unset($field_info['bundles']);
+ return $field_info;
+ }
+ return FALSE;
+}
+
+/**
+ * Load a field's instance configuration by an entity_type-bundle-field_name
+ * identifier.
+ */
+function features_field_instance_load($identifier) {
+ list($entity_type, $bundle, $field_name) = explode('-', $identifier);
+ if ($instance_info = field_info_instance($entity_type, $field_name, $bundle)) {
+ unset($instance_info['id']);
+ unset($instance_info['field_id']);
+ return $instance_info;
+ }
+ return FALSE;
+}
+
+/* ----- DEPRECATED FIELD EXPORT -----
+ * keep this code for backward compatibility with older exports
+ * until v3.x
+ */
+
+/**
+ * Implements hook_features_export_options().
+ */
+function field_features_export_options() {
+ $options = array();
+ $instances = field_info_instances();
+ foreach ($instances as $entity_type => $bundles) {
+ foreach ($bundles as $bundle => $fields) {
+ foreach ($fields as $field) {
+ $identifier = "{$entity_type}-{$bundle}-{$field['field_name']}";
+ $options[$identifier] = $identifier;
+ }
+ }
+ }
+ return $options;
+}
+
+/**
+ * Implements hook_features_export().
+ */
+function field_features_export($data, &$export, $module_name = '') {
+ $pipe = array();
+ // Convert 'field' to 'field_instance' on features-update.
+ $pipe['field_instance'] = $data;
+ return $pipe;
+}
+
+/**
+ * Implements hook_features_export_render().
+ */
+function field_features_export_render($module, $data, $export = NULL) {
+ $translatables = $code = array();
+
+ $code[] = ' $fields = array();';
+ $code[] = '';
+ foreach ($data as $identifier) {
+ if ($field = features_field_load($identifier)) {
+ unset($field['field_config']['columns']);
+ // Only remove the 'storage' declaration if the field is using the default
+ // storage type.
+ if ($field['field_config']['storage']['type'] == variable_get('field_storage_default', 'field_sql_storage')) {
+ unset($field['field_config']['storage']);
+ }
+ // If we still have a storage declaration here it means that a non-default
+ // storage type was altered into to the field definition. And no one would
+ // never need to change the 'details' key, so don't render it.
+ if (isset($field['field_config']['storage']['details'])) {
+ unset($field['field_config']['storage']['details']);
+ }
+
+ _field_features_export_sort($field);
+ $field_export = features_var_export($field, ' ');
+ $field_identifier = features_var_export($identifier);
+ $code[] = " // Exported field: {$field_identifier}.";
+ $code[] = " \$fields[{$field_identifier}] = {$field_export};";
+ $code[] = "";
+
+ // Add label and description to translatables array.
+ if (!empty($field['field_instance']['label'])) {
+ $translatables[] = $field['field_instance']['label'];
+ }
+ if (!empty($field['field_instance']['description'])) {
+ $translatables[] = $field['field_instance']['description'];
+ }
+ }
+ }
+ if (!empty($translatables)) {
+ $code[] = features_translatables_export($translatables, ' ');
+ }
+ $code[] = ' return $fields;';
+ $code = implode("\n", $code);
+ return array('field_default_fields' => $code);
+}
+
+// Helper to enforce consistency in field export arrays.
+function _field_features_export_sort(&$field, $sort = TRUE) {
+
+ // Some arrays are not sorted to preserve order (for example allowed_values).
+ static $sort_blacklist = array(
+ 'allowed_values',
+ 'format_handlers',
+ );
+
+ if ($sort) {
+ ksort($field);
+ }
+ foreach ($field as $k => $v) {
+ if (is_array($v)) {
+ _field_features_export_sort($field[$k], !in_array($k, $sort_blacklist));
+ }
+ }
+}
+
+/**
+ * Implements hook_features_revert().
+ */
+function field_features_revert($module) {
+ field_features_rebuild($module);
+}
+
+/**
+ * Implements of hook_features_rebuild().
+ * Rebuilds fields from code defaults.
+ */
+function field_features_rebuild($module) {
+ if ($fields = features_get_default('field', $module)) {
+ field_info_cache_clear();
+
+ // Load all the existing fields and instance up-front so that we don't
+ // have to rebuild the cache all the time.
+ $existing_fields = field_info_fields();
+ $existing_instances = field_info_instances();
+
+ foreach ($fields as $field) {
+ // Create or update field.
+ $field_config = $field['field_config'];
+ if (isset($existing_fields[$field_config['field_name']])) {
+ $existing_field = $existing_fields[$field_config['field_name']];
+ $array_diff_result = drupal_array_diff_assoc_recursive($field_config + $existing_field, $existing_field);
+ if (!empty($array_diff_result)) {
+ try {
+ field_update_field($field_config);
+ }
+ catch (FieldException $e) {
+ watchdog('features', 'Attempt to update field %label failed: %message', array('%label' => $field_config['field_name'], '%message' => $e->getMessage()), WATCHDOG_ERROR);
+ }
+ }
+ }
+ else {
+ try {
+ field_create_field($field_config);
+ }
+ catch (FieldException $e) {
+ watchdog('features', 'Attempt to create field %label failed: %message', array('%label' => $field_config['field_name'], '%message' => $e->getMessage()), WATCHDOG_ERROR);
+ }
+ $existing_fields[$field_config['field_name']] = $field_config;
+ }
+
+ // Create or update field instance.
+ $field_instance = $field['field_instance'];
+ if (isset($existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']])) {
+ $existing_instance = $existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']];
+ if ($field_instance + $existing_instance !== $existing_instance) {
+ field_update_instance($field_instance);
+ }
+ }
+ else {
+ field_create_instance($field_instance);
+ $existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']] = $field_instance;
+ }
+ }
+
+ if ($fields) {
+ variable_set('menu_rebuild_needed', TRUE);
+ }
+ }
+}
+
+/**
+ * Load a field's configuration and instance configuration by an
+ * entity_type-bundle-field_name identifier.
+ */
+function features_field_load($identifier) {
+ list($entity_type, $bundle, $field_name) = explode('-', $identifier);
+ $field_info = field_info_field($field_name);
+ $instance_info = field_info_instance($entity_type, $field_name, $bundle);
+ if ($field_info && $instance_info) {
+ unset($field_info['id']);
+ unset($field_info['bundles']);
+ unset($instance_info['id']);
+ unset($instance_info['field_id']);
+ return array(
+ 'field_config' => $field_info,
+ 'field_instance' => $instance_info,
+ );
+ }
+ return FALSE;
+}
+
+/**
+ * Determine if a field export line needs to be wrapped.
+ *
+ * Drupal code standards specify that comments should wrap at 80 characters or
+ * less.
+ *
+ * @param string $prefix
+ * The prefix to be exported before the field identifier.
+ * @param string $identifier
+ * The field identifier.
+ *
+ * @return BOOL
+ * TRUE if the line should be wrapped after the prefix, else FALSE.
+ *
+ * @see https://www.drupal.org/node/1354
+ */
+function features_field_export_needs_wrap($prefix, $identifier) {
+ // Check for 79 characters, since the comment ends with a full stop.
+ return (strlen($prefix) + strlen($identifier) > 79);
+}
diff --git a/docroot/sites/all/modules/features/includes/features.filter.inc b/docroot/sites/all/modules/features/includes/features.filter.inc
new file mode 100644
index 0000000..a52927d
--- /dev/null
+++ b/docroot/sites/all/modules/features/includes/features.filter.inc
@@ -0,0 +1,120 @@
+ array(
+ 'name' => t('Text formats'),
+ 'default_hook' => 'filter_default_formats',
+ 'default_file' => FEATURES_DEFAULTS_INCLUDED,
+ 'feature_source' => TRUE
+ ),
+ );
+}
+
+/**
+ * Implements hook_features_export_options().
+ */
+function filter_features_export_options() {
+ $options = array();
+ foreach (filter_formats() as $format => $info) {
+ $options[$format] = $info->name;
+ }
+ return $options;
+}
+
+/**
+ * Implements hook_features_export().
+ */
+function filter_features_export($data, &$export, $module_name = '') {
+ // The filter_default_formats() hook integration is provided by the
+ // features module so we need to add it as a dependency.
+ $export['dependencies']['features'] = 'features';
+
+ $filter_info = filter_get_filters();
+ foreach ($data as $name) {
+ if ($format = features_filter_format_load($name)) {
+ // Add format to exports
+ $export['features']['filter'][$format->format] = $format->format;
+
+ // Iterate through filters and ensure each filter's module is included as a dependency
+ foreach (array_keys($format->filters) as $name) {
+ if (isset($filter_info[$name], $filter_info[$name]['module'])) {
+ $module = $filter_info[$name]['module'];
+ $export['dependencies'][$module] = $module;
+ }
+ }
+ }
+ }
+
+ $pipe = array();
+ return $pipe;
+}
+
+/**
+ * Implements hook_features_export_render().
+ */
+function filter_features_export_render($module, $data, $export = NULL) {
+ $code = array();
+ $code[] = ' $formats = array();';
+ $code[] = '';
+
+ foreach ($data as $name) {
+ if ($format = features_filter_format_load($name)) {
+ $format_export = features_var_export($format, ' ');
+ $format_identifier = features_var_export($format->format);
+ $code[] = " // Exported format: {$format->name}.";
+ $code[] = " \$formats[{$format_identifier}] = {$format_export};";
+ $code[] = "";
+ }
+ }
+
+ $code[] = ' return $formats;';
+ $code = implode("\n", $code);
+ return array('filter_default_formats' => $code);
+}
+
+/**
+ * Implements hook_features_revert().
+ */
+function filter_features_revert($module) {
+ return filter_features_rebuild($module);
+}
+
+/**
+ * Implements hook_features_rebuild().
+ */
+function filter_features_rebuild($module) {
+ if ($defaults = features_get_default('filter', $module)) {
+ foreach ($defaults as $format) {
+ $format = (object) $format;
+ filter_format_save($format);
+ }
+ }
+}
+
+/**
+ * Load a filter format by its name.
+ */
+function features_filter_format_load($name) {
+ // Use machine name for retrieving the format if available.
+ $query = db_select('filter_format');
+ $query->fields('filter_format');
+ $query->condition('format', $name);
+
+ // Retrieve filters for the format and attach.
+ if ($format = $query->execute()->fetchObject()) {
+ $format->filters = array();
+ foreach (filter_list_format($format->format) as $filter) {
+ if (!empty($filter->status)) {
+ $format->filters[$filter->name]['weight'] = $filter->weight;
+ $format->filters[$filter->name]['status'] = $filter->status;
+ $format->filters[$filter->name]['settings'] = $filter->settings;
+ }
+ }
+ return $format;
+ }
+ return FALSE;
+}
diff --git a/docroot/sites/all/modules/features/includes/features.image.inc b/docroot/sites/all/modules/features/includes/features.image.inc
new file mode 100644
index 0000000..b2058b7
--- /dev/null
+++ b/docroot/sites/all/modules/features/includes/features.image.inc
@@ -0,0 +1,110 @@
+ array(
+ 'name' => t('Image styles'),
+ 'feature_source' => TRUE,
+ 'default_hook' => 'image_default_styles',
+ 'alter_hook' => 'image_styles',
+ )
+ );
+}
+
+/**
+ * Implements hook_features_export_options().
+ */
+function image_features_export_options() {
+ $options = array();
+ foreach (image_styles() as $name => $style) {
+ $options[$name] = $style['name'];
+ }
+ return $options;
+}
+
+/**
+ * Implements hook_features_export().
+ */
+function image_features_export($data, &$export, $module_name = '') {
+ $pipe = array();
+ $map = features_get_default_map('image');
+ foreach ($data as $style) {
+ $export['dependencies']['image'] = 'image';
+ // If another module provides this style, add it as a dependency
+ if (isset($map[$style]) && $map[$style] != $module_name) {
+ $module = $map[$style];
+ $export['dependencies'][$module] = $module;
+ }
+ // Otherwise, export the style
+ elseif (image_style_load($style)) {
+ $export['features']['image'][$style] = $style;
+ }
+ }
+ return $pipe;
+}
+
+/**
+ * Implements hook_features_export_render().
+ */
+function image_features_export_render($module_name, $data, $export = NULL) {
+ $code = array();
+ $code[] = ' $styles = array();';
+ $code[] = '';
+ foreach ($data as $name) {
+ if ($style = image_style_load($name)) {
+ _image_features_style_sanitize($style);
+ $style_export = features_var_export($style, ' ');
+ $style_identifier = features_var_export($name);
+ $code[] = " // Exported image style: {$name}.";
+ $code[] = " \$styles[{$style_identifier}] = {$style_export};";
+ $code[] = "";
+ }
+ }
+ $code[] = ' return $styles;';
+ $code = implode("\n", $code);
+ return array('image_default_styles' => $code);
+}
+
+/**
+ * Implements hook_features_revert().
+ */
+function image_features_revert($module) {
+ if ($default_styles = features_get_default('image', $module)) {
+ foreach (array_keys($default_styles) as $default_style) {
+ if ($style = image_style_load($default_style)) {
+ if ($style['storage'] != IMAGE_STORAGE_DEFAULT) {
+ image_default_style_revert($style);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Remove unnecessary keys for export.
+ */
+function _image_features_style_sanitize(array &$style) {
+ // Sanitize style: Don't export numeric IDs and things which get overwritten
+ // in image_styles() or are code/storage specific. The name property will be
+ // the key of the exported $style array.
+ $style = array_diff_key($style, array_flip(array(
+ 'isid',
+ 'name',
+ 'module',
+ 'storage',
+ )));
+
+ // Sanitize effects: all that needs to be kept is name, weight and data,
+ // which holds all the style-specific configuration. Other keys are assumed
+ // to belong to the definition of the effect itself, so not configuration.
+ foreach ($style['effects'] as $id => $effect) {
+ $style['effects'][$id] = array_intersect_key($effect, array_flip(array(
+ 'name',
+ 'data',
+ 'weight',
+ )));
+ }
+}
diff --git a/docroot/sites/all/modules/features/includes/features.locale.inc b/docroot/sites/all/modules/features/includes/features.locale.inc
new file mode 100644
index 0000000..126d177
--- /dev/null
+++ b/docroot/sites/all/modules/features/includes/features.locale.inc
@@ -0,0 +1,163 @@
+ array(
+ 'name' => t('Languages'),
+ 'default_hook' => 'locale_default_languages',
+ 'feature_source' => TRUE,
+ 'default_file' => FEATURES_DEFAULTS_INCLUDED,
+ ),
+ );
+}
+
+/**
+ * Implements hook_features_export_options().
+ */
+function language_features_export_options() {
+ return locale_language_list('native', TRUE);
+}
+
+/**
+ * Implements hook_features_export().
+ */
+function language_features_export($data, &$export, $module_name = '') {
+ $export['dependencies']['features'] = 'features';
+ $export['dependencies']['locale'] = 'locale';
+
+ $language_list = locale_language_list('native', TRUE);
+
+ foreach ($data as $name) {
+ // Only export existing languages.
+ if (!empty($language_list[$name])) {
+ // Add language to exports.
+ $export['features']['language'][$name] = $name;
+ }
+ }
+
+ // No pipe to return.
+ return array();
+}
+
+/**
+ * Implements hook_features_export_render().
+ */
+function language_features_export_render($module, $data, $export = NULL) {
+ $code = array();
+ $code[] = ' $languages = array();';
+ $code[] = '';
+
+ $language_list = language_list();
+
+ foreach ($data as $name) {
+ // Only render existing languages.
+ if (!empty($language_list[$name])) {
+
+ $var = (array) $language_list[$name];
+ // Unset javascript hash
+ unset($var['javascript']);
+
+ $lang_export = features_var_export($var, ' ');
+ $lang_identifier = features_var_export($name);
+ $code[] = " // Exported language: $name.";
+ $code[] = " \$languages[{$lang_identifier}] = {$lang_export};";
+ }
+ }
+
+ $code[] = ' return $languages;';
+ $code = implode("\n", $code);
+ return array('locale_default_languages' => $code);
+}
+
+/**
+ * Implements hook_features_revert().
+ */
+function language_features_revert($module) {
+ return language_features_rebuild($module);
+}
+
+/**
+ * Implements hook_features_rebuild().
+ */
+function language_features_rebuild($module) {
+ if ($defaults = features_get_default('language', $module)) {
+ foreach ($defaults as $key => $language) {
+ _features_language_save((object) $language);
+ }
+
+ // Set correct language count.
+ $enabled_languages = db_select('languages')
+ ->condition('enabled', 1)
+ ->fields('languages')
+ ->execute()
+ ->rowCount();
+ variable_set('language_count', $enabled_languages);
+ }
+}
+
+/**
+ * Helper function to save the language to database.
+ *
+ * @see locale_languages_edit_form_submit()
+ */
+function _features_language_save($language) {
+
+ $current_language = db_select('languages')
+ ->condition('language', $language->language)
+ ->fields('languages')
+ ->execute()
+ ->fetchAssoc();
+
+ // Set the default language when needed.
+ $default = language_default();
+
+ // Insert new language via api function.
+ if (empty($current_language)) {
+ locale_add_language($language->language,
+ $language->name,
+ $language->native,
+ $language->direction,
+ $language->domain,
+ $language->prefix,
+ $language->enabled,
+ ($language->language == $default->language));
+ // Additional params, locale_add_language does not implement.
+ db_update('languages')
+ ->fields(array(
+ 'plurals' => empty($language->plurals) ? 0 : $language->plurals,
+ 'formula' => empty($language->formula) ? '' : $language->formula,
+ 'weight' => empty($language->weight) ? 0 : $language->weight,
+ ))
+ ->condition('language', $language->language)
+ ->execute();
+ }
+ // Update Existing language.
+ else {
+ // @TODO: get properties from schema.
+ $properties = array('language', 'name', 'native', 'direction', 'enabled', 'plurals', 'formula', 'domain', 'prefix', 'weight', 'javascript');
+ // The javascript hash is not in the imported data but should be empty
+ if (!isset($language->javascript)) {
+ $language->javascript = '';
+ }
+
+ $fields = array_intersect_key((array) $language, array_flip($properties));
+ db_update('languages')
+ ->fields($fields)
+ ->condition('language', $language->language)
+ ->execute();
+
+ // Set the default language when needed.
+ $default = language_default();
+ if ($default->language == $language->language) {
+ variable_set('language_default', (object) $fields);
+ }
+ }
+}
diff --git a/docroot/sites/all/modules/features/includes/features.menu.inc b/docroot/sites/all/modules/features/includes/features.menu.inc
new file mode 100644
index 0000000..edd4751
--- /dev/null
+++ b/docroot/sites/all/modules/features/includes/features.menu.inc
@@ -0,0 +1,427 @@
+ array(
+ 'name' => t('Menus'),
+ 'default_hook' => 'menu_default_menu_custom',
+ 'feature_source' => TRUE,
+ 'default_file' => FEATURES_DEFAULTS_INCLUDED,
+ ),
+ 'menu_links' => array(
+ 'name' => t('Menu links'),
+ 'default_hook' => 'menu_default_menu_links',
+ 'feature_source' => TRUE,
+ 'default_file' => FEATURES_DEFAULTS_INCLUDED,
+ ),
+ // DEPRECATED
+ 'menu' => array(
+ 'name' => t('Menu items'),
+ 'default_hook' => 'menu_default_items',
+ 'default_file' => FEATURES_DEFAULTS_INCLUDED,
+ 'feature_source' => FALSE,
+ ),
+ );
+}
+
+/**
+ * Implements hook_features_export().
+ * DEPRECATED: This implementation simply migrates deprecated `menu` items
+ * to the `menu_links` type.
+ */
+function menu_features_export($data, &$export, $module_name = '') {
+ $pipe = array();
+ foreach ($data as $path) {
+ $pipe['menu_links'][] = "features:{$path}";
+ }
+ return $pipe;
+}
+
+/**
+ * Implements hook_features_export_options().
+ */
+function menu_custom_features_export_options() {
+ $options = array();
+ $result = db_query("SELECT * FROM {menu_custom} ORDER BY title", array(), array('fetch' => PDO::FETCH_ASSOC));
+ foreach ($result as $menu) {
+ $options[$menu['menu_name']] = $menu['title'];
+ }
+ return $options;
+}
+
+/**
+ * Implements hook_features_export().
+ */
+function menu_custom_features_export($data, &$export, $module_name = '') {
+ // Default hooks are provided by the feature module so we need to add
+ // it as a dependency.
+ $export['dependencies']['features'] = 'features';
+ $export['dependencies']['menu'] = 'menu';
+
+ // Collect a menu to module map
+ $pipe = array();
+ $map = features_get_default_map('menu_custom', 'menu_name');
+ foreach ($data as $menu_name) {
+ // If this menu is provided by a different module, add it as a dependency.
+ if (isset($map[$menu_name]) && $map[$menu_name] != $module_name) {
+ $export['dependencies'][$map[$menu_name]] = $map[$menu_name];
+ }
+ else {
+ $export['features']['menu_custom'][$menu_name] = $menu_name;
+ }
+ }
+ return $pipe;
+}
+
+/**
+ * Implements hook_features_export_render()
+ */
+function menu_custom_features_export_render($module, $data) {
+ $code = array();
+ $code[] = ' $menus = array();';
+ $code[] = '';
+
+ $translatables = array();
+ foreach ($data as $menu_name) {
+ $row = db_select('menu_custom')
+ ->fields('menu_custom')
+ ->condition('menu_name', $menu_name)
+ ->execute()
+ ->fetchAssoc();
+ if ($row) {
+ $export = features_var_export($row, ' ');
+ $code[] = " // Exported menu: {$menu_name}.";
+ $code[] = " \$menus['{$menu_name}'] = {$export};";
+ $translatables[] = $row['title'];
+ $translatables[] = $row['description'];
+ }
+ }
+ if (!empty($translatables)) {
+ $code[] = features_translatables_export($translatables, ' ');
+ }
+
+ $code[] = ' return $menus;';
+ $code = implode("\n", $code);
+ return array('menu_default_menu_custom' => $code);
+}
+
+/**
+ * Implements hook_features_revert().
+ */
+function menu_custom_features_revert($module) {
+ menu_custom_features_rebuild($module);
+}
+
+/**
+ * Implements hook_features_rebuild().
+ */
+function menu_custom_features_rebuild($module) {
+ if ($defaults = features_get_default('menu_custom', $module)) {
+ foreach ($defaults as $menu) {
+ menu_save($menu);
+ }
+ }
+}
+
+/**
+ * Implements hook_features_export_options().
+ */
+function menu_links_features_export_options() {
+ global $menu_admin;
+ // Need to set this to TRUE in order to get menu links that the
+ // current user may not have access to (i.e. user/login)
+ $menu_admin = TRUE;
+ $use_menus = array_intersect_key(menu_get_menus(), array_flip(array_filter(variable_get('features_admin_menu_links_menus', array_keys(menu_get_menus())))));
+ $menu_links = menu_parent_options($use_menus, array('mlid' => 0));
+ $options = array();
+ foreach ($menu_links as $key => $name) {
+ list($menu_name, $mlid) = explode(':', $key, 2);
+ if ($mlid != 0) {
+ $link = menu_link_load($mlid);
+ $identifier = menu_links_features_identifier($link, TRUE);
+ $options[$identifier] = "{$menu_name}: {$name}";
+ }
+ }
+ $menu_admin = FALSE;
+ return $options;
+}
+
+/**
+ * Callback for generating the menu link exportable identifier.
+ */
+function menu_links_features_identifier($link, $old = FALSE) {
+ // Add some uniqueness to these identifiers, allowing multiple links with the same path, but different titles.
+ $clean_title = features_clean_title(isset($link['title']) ? $link['title'] : $link['link_title']);
+
+ // The old identifier is requested.
+ if ($old) {
+ // if identifier already exists
+ if (isset($link['options']['identifier'])) {
+ return $link['options']['identifier'];
+ }
+ // providing backward compatibility and allowing/enabling multiple links with same paths
+ else {
+ $identifier = isset($link['menu_name'], $link['link_path']) ? "{$link['menu_name']}:{$link['link_path']}" : FALSE;
+ // Checking if there are multiples of this identifier
+ if (features_menu_link_load($identifier) !== FALSE) {
+ // this is where we return the upgrade posibility for links.
+ return $identifier;
+ }
+ }
+ }
+
+ return isset($link['menu_name'], $link['link_path']) ? "{$link['menu_name']}_{$clean_title}:{$link['link_path']}" : FALSE;
+}
+
+/**
+ * Implements hook_features_export().
+ */
+function menu_links_features_export($data, &$export, $module_name = '') {
+ // Default hooks are provided by the feature module so we need to add
+ // it as a dependency.
+ $export['dependencies']['features'] = 'features';
+ $export['dependencies']['menu'] = 'menu';
+
+ // Collect a link to module map
+ $pipe = array();
+ $map = features_get_default_map('menu_links', 'menu_links_features_identifier');
+ foreach ($data as $key => $identifier) {
+ if ($link = features_menu_link_load($identifier)) {
+ // If this link is provided by a different module, add it as a dependency.
+ $new_identifier = menu_links_features_identifier($link, empty($export));
+ if (isset($map[$identifier]) && $map[$identifier] != $module_name) {
+ $export['dependencies'][$map[$identifier]] = $map[$identifier];
+ }
+ else {
+ $export['features']['menu_links'][$new_identifier] = $new_identifier;
+ }
+ // For now, exclude a variety of common menus from automatic export.
+ // They may still be explicitly included in a Feature if the builder
+ // chooses to do so.
+ if (!in_array($link['menu_name'], array('features', 'primary-links', 'secondary-links', 'navigation', 'admin', 'devel'))) {
+ $pipe['menu_custom'][] = $link['menu_name'];
+ }
+ }
+ }
+ return $pipe;
+}
+
+/**
+ * Implements hook_features_export_render()
+ */
+function menu_links_features_export_render($module, $data, $export = NULL) {
+ $code = array();
+ $code[] = ' $menu_links = array();';
+ $code[] = '';
+
+ $translatables = array();
+ foreach ($data as $identifier) {
+
+ if ($link = features_menu_link_load($identifier)) {
+ $new_identifier = menu_links_features_identifier($link, empty($export));
+
+ // Replace plid with a parent path.
+ if (!empty($link['plid']) && $parent = menu_link_load($link['plid'])) {
+ // If the new identifier is different than the old, maintain
+ // 'parent_path' for backwards compatibility.
+ if ($new_identifier != menu_links_features_identifier($link)) {
+ $link['parent_path'] = $parent['link_path'];
+ }
+ else {
+ $clean_title = features_clean_title($parent['title']);
+ $link['parent_identifier'] = "{$parent['menu_name']}_{$clean_title}:{$parent['link_path']}";
+ }
+ }
+
+ if (isset($export)) {
+ // Don't show new identifier unless we are actually exporting.
+ $link['options']['identifier'] = $new_identifier;
+ // identifiers are renewed, => that means we need to update them in the db
+ $temp = $link;
+ menu_link_save($temp);
+ }
+
+ unset($link['plid']);
+ unset($link['mlid']);
+
+ $code[] = " // Exported menu link: {$new_identifier}.";
+ $code[] = " \$menu_links['{$new_identifier}'] = ". features_var_export($link, ' ') .";";
+ $translatables[] = $link['link_title'];
+ }
+ }
+ $code[] = '';
+ if (!empty($translatables)) {
+ $code[] = features_translatables_export($translatables, ' ');
+ }
+
+ $code[] = ' return $menu_links;';
+ $code = implode("\n", $code);
+ return array('menu_default_menu_links' => $code);
+}
+
+/**
+ * Implements hook_features_revert().
+ */
+function menu_links_features_revert($module) {
+ menu_links_features_rebuild($module);
+}
+
+/**
+ * Implements hook_features_rebuild().
+ */
+function menu_links_features_rebuild($module) {
+ if ($menu_links = features_get_default('menu_links', $module)) {
+ menu_links_features_rebuild_ordered($menu_links);
+ }
+}
+
+/**
+ * Generate a depth tree of all menu links.
+ */
+function menu_links_features_rebuild_ordered($menu_links, $reset = FALSE) {
+ static $ordered;
+ static $all_links;
+ if (!isset($ordered) || $reset) {
+ $ordered = array();
+ $unordered = features_get_default('menu_links');
+
+ // Order all links by depth.
+ if ($unordered) {
+ do {
+ $current = count($unordered);
+ foreach ($unordered as $key => $link) {
+ $identifier = menu_links_features_identifier($link);
+ $parent = isset($link['parent_identifier']) ? $link['parent_identifier'] : '';
+ $weight = 0;
+ // Parent has been seen, so weigh this above parent.
+ if (isset($ordered[$parent])) {
+ $weight = $ordered[$parent] + 1;
+ }
+ // Next loop will try to find parent weight instead.
+ elseif ($parent) {
+ continue;
+ }
+ $ordered[$identifier] = $weight;
+ $all_links[$identifier] = $link;
+ unset($unordered[$key]);
+ }
+ // Exit out when the above does no changes this loop.
+ } while (count($unordered) < $current);
+ }
+ // Add all remaining unordered items to the ordered list.
+ foreach ($unordered as $link) {
+ $identifier = menu_links_features_identifier($link);
+ $ordered[$identifier] = 0;
+ $all_links[$identifier] = $link;
+ }
+ asort($ordered);
+ }
+
+ // Ensure any default menu items that do not exist are created.
+ foreach (array_keys($ordered) as $identifier) {
+ $link = $all_links[$identifier];
+
+ $existing = features_menu_link_load($identifier);
+ if (!$existing || in_array($link, $menu_links)) {
+ // Retrieve the mlid if this is an existing item.
+ if ($existing) {
+ $link['mlid'] = $existing['mlid'];
+ }
+ // Retrieve the plid for a parent link.
+ if (!empty($link['parent_identifier']) && $parent = features_menu_link_load($link['parent_identifier'])) {
+ $link['plid'] = $parent['mlid'];
+ }
+ // This if for backwards compatibility.
+ elseif (!empty($link['parent_path']) && $parent = features_menu_link_load("{$link['menu_name']}:{$link['parent_path']}")) {
+ $link['plid'] = $parent['mlid'];
+ }
+ else {
+ $link['plid'] = 0;
+ }
+ menu_link_save($link);
+ }
+ }
+}
+
+/**
+ * Load a menu link by its menu_name_cleantitle:link_path identifier.
+ * Also matches links with unique menu_name:link_path
+ */
+function features_menu_link_load($identifier) {
+ $menu_name = '';
+ $link_path = '';
+ // This gets variables for menu_name_cleantitle:link_path format.
+ if (strstr($identifier, "_")) {
+ $link_path = substr($identifier, strpos($identifier, ":") + 1);
+ list($menu_name) = explode('_', $identifier, 2);
+ $clean_title = substr($identifier, strpos($identifier, "_") + 1, strpos($identifier, ":") - strpos($identifier, "_") - 1);
+ }
+ // This gets variables for traditional identifier format.
+ else {
+ $clean_title = '';
+ list($menu_name, $link_path) = explode(':', $identifier, 2);
+ }
+ $links = db_select('menu_links')
+ ->fields('menu_links', array('menu_name', 'mlid', 'plid', 'link_path', 'router_path', 'link_title', 'options', 'module', 'hidden', 'external', 'has_children', 'expanded', 'weight', 'customized'))
+ ->condition('menu_name', $menu_name)
+ ->condition('link_path', $link_path)
+ ->addTag('features_menu_link')
+ ->execute()
+ ->fetchAllAssoc('mlid');
+
+ foreach($links as $link) {
+ $link->options = unserialize($link->options);
+
+ // Title or previous identifier matches.
+ if ((isset($link->options['identifier']) && strcmp($link->options['identifier'], $identifier) == 0)
+ || (isset($clean_title) && strcmp(features_clean_title($link->link_title), $clean_title) == 0)) {
+
+ return (array)$link;
+ }
+ }
+
+ // Only one link with the requested menu_name and link_path does exists,
+ // -- providing an upgrade possibility for links saved in a feature before the
+ // new identifier-pattern was added.
+ if (count($links) == 1 && empty($clean_title)) {
+ $link = reset($links); // get the first item
+ return (array)$link;
+ }
+ // If link_path was changed on an existing link, we need to find it by
+ // searching for link_title.
+ else if (isset($clean_title)) {
+ $links = db_select('menu_links')
+ ->fields('menu_links', array('menu_name', 'mlid', 'plid', 'link_path', 'router_path', 'link_title', 'options', 'module', 'hidden', 'external', 'has_children', 'expanded', 'weight'))
+ ->condition('menu_name', $menu_name)
+ ->execute()
+ ->fetchAllAssoc('mlid');
+
+ foreach($links as $link) {
+ $link->options = unserialize($link->options);
+ // Links with a stored identifier must only be matched on that identifier,
+ // to prevent cross over assumptions.
+ if (isset($link->options['identifier'])) {
+ if (strcmp($link->options['identifier'], $identifier) == 0) {
+ return (array)$link;
+ }
+ }
+ elseif ((strcmp(features_clean_title($link->link_title), $clean_title) == 0)) {
+ return (array)$link;
+ }
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * Returns a lowercase clean string with only letters, numbers and dashes
+ */
+function features_clean_title($str) {
+ return strtolower(preg_replace_callback('/(\s)|([^a-zA-Z\-0-9])/i', create_function(
+ '$matches',
+ 'return $matches[1]?"-":"";'
+ ), $str));
+}
diff --git a/docroot/sites/all/modules/features/includes/features.node.inc b/docroot/sites/all/modules/features/includes/features.node.inc
new file mode 100644
index 0000000..25a2c1c
--- /dev/null
+++ b/docroot/sites/all/modules/features/includes/features.node.inc
@@ -0,0 +1,174 @@
+ array(
+ 'name' => t('Content types'),
+ 'feature_source' => TRUE,
+ 'default_hook' => 'node_info',
+ 'alter_type' => FEATURES_ALTER_TYPE_INLINE,
+ ),
+ );
+}
+
+/**
+ * Implements hook_features_export_options().
+ */
+function node_features_export_options() {
+ return node_type_get_names();
+}
+
+/**
+ * Implements hook_features_export.
+ */
+function node_features_export($data, &$export, $module_name = '') {
+ $pipe = array();
+ $map = features_get_default_map('node');
+
+ foreach ($data as $type) {
+ // Poll node module to determine who provides the node type.
+ if ($info = node_type_get_type($type)) {
+ // If this node type is provided by a different module, add it as a dependency
+ if (isset($map[$type]) && $map[$type] != $module_name) {
+ $export['dependencies'][$map[$type]] = $map[$type];
+ }
+ // Otherwise export the node type.
+ elseif (in_array($info->base, array('node_content', 'features'))) {
+ $export['features']['node'][$type] = $type;
+ $export['dependencies']['node'] = 'node';
+ $export['dependencies']['features'] = 'features';
+ }
+
+ $fields = field_info_instances('node', $type);
+ foreach ($fields as $name => $field) {
+ $pipe['field_instance'][] = "node-{$field['bundle']}-{$field['field_name']}";
+ }
+ }
+ }
+
+ return $pipe;
+}
+
+/**
+ * Implements hook_features_export_render().
+ */
+function node_features_export_render($module, $data, $export = NULL) {
+ $elements = array(
+ 'name' => TRUE,
+ 'base' => FALSE,
+ 'description' => TRUE,
+ 'has_title' => FALSE,
+ 'title_label' => TRUE,
+ 'help' => TRUE,
+ );
+ $output = array();
+ $output[] = ' $items = array(';
+ foreach ($data as $type) {
+ if ($info = node_type_get_type($type)) {
+ // Force module name to be 'features' if set to 'node. If we leave as
+ // 'node' the content type will be assumed to be database-stored by
+ // the node module.
+ $info->base = ($info->base === 'node') ? 'features' : $info->base;
+ $output[] = " '{$type}' => array(";
+ foreach ($elements as $key => $t) {
+ if ($t) {
+ $text = str_replace("'", "\'", $info->$key);
+ $text = !empty($text) ? "t('{$text}')" : "''";
+ $output[] = " '{$key}' => {$text},";
+ }
+ else {
+ $output[] = " '{$key}' => '{$info->$key}',";
+ }
+ }
+ $output[] = " ),";
+ }
+ }
+ $output[] = ' );';
+ $output[] = ' drupal_alter(\'node_info\', $items);';
+ $output[] = ' return $items;';
+ $output = implode("\n", $output);
+ return array('node_info' => $output);
+}
+
+/**
+ * Implements hook_features_revert().
+ *
+ * @param $module
+ * name of module to revert content for
+ */
+function node_features_revert($module = NULL) {
+ if ($default_types = features_get_default('node', $module)) {
+ foreach ($default_types as $type_name => $type_info) {
+ // Delete node types
+ // We don't use node_type_delete() because we do not actually
+ // want to delete the node type (and invoke hook_node_type()).
+ // This can lead to bad consequences like CCK deleting field
+ // storage in the DB.
+ db_delete('node_type')
+ ->condition('type', $type_name)
+ ->execute();
+ }
+ node_types_rebuild();
+ menu_rebuild();
+ }
+}
+
+/**
+ * Implements hook_features_disable_feature().
+ *
+ * When a features module is disabled, modify any node types it provides so
+ * they can be deleted manually through the content types UI.
+ *
+ * @param $module
+ * Name of module that has been disabled.
+ */
+function node_features_disable_feature($module) {
+ if ($default_types = features_get_default('node', $module)) {
+ foreach ($default_types as $type_name => $type_info) {
+ $type_info = node_type_load($type_name);
+ $type_info->module = 'node';
+ $type_info->custom = 1;
+ $type_info->modified = 1;
+ $type_info->locked = 0;
+ $type_info->disabled = 0;
+ node_type_save($type_info);
+ }
+ }
+}
+
+/**
+ * Implements hook_features_enable_feature().
+ *
+ * When a features module is enabled, modify any node types it provides so
+ * they can no longer be deleted manually through the content types UI.
+ *
+ * Update the database cache of node types if needed.
+ *
+ * @param $module
+ * Name of module that has been enabled.
+ */
+function node_features_enable_feature($module) {
+ if ($default_types = features_get_default('node', $module)) {
+ $rebuild = FALSE;
+ foreach ($default_types as $type_name => $type_info) {
+ // Ensure the type exists.
+ if ($type_info = node_type_load($type_name)) {
+ $type_info->module = $module;
+ $type_info->custom = 0;
+ $type_info->modified = 0;
+ $type_info->locked = 1;
+ $type_info->disabled = 0;
+ node_type_save($type_info);
+ }
+ else {
+ $rebuild = TRUE;
+ }
+ }
+ if ($rebuild) {
+ node_types_rebuild();
+ }
+ }
+}
diff --git a/docroot/sites/all/modules/features/includes/features.taxonomy.inc b/docroot/sites/all/modules/features/includes/features.taxonomy.inc
new file mode 100644
index 0000000..a7c85cd
--- /dev/null
+++ b/docroot/sites/all/modules/features/includes/features.taxonomy.inc
@@ -0,0 +1,105 @@
+ array(
+ 'name' => t('Taxonomy'),
+ 'feature_source' => TRUE,
+ 'default_hook' => 'taxonomy_default_vocabularies',
+ 'default_file' => FEATURES_DEFAULTS_INCLUDED,
+ ),
+ );
+}
+
+/**
+ * Implements hook_features_export_options().
+ */
+function taxonomy_features_export_options() {
+ $vocabularies = array();
+ foreach (taxonomy_get_vocabularies() as $vocabulary) {
+ $vocabularies[$vocabulary->machine_name] = $vocabulary->name;
+ }
+ return $vocabularies;
+}
+
+/**
+ * Implements hook_features_export().
+ *
+ * @todo Test adding existing dependencies.
+ */
+function taxonomy_features_export($data, &$export, $module_name = '') {
+ $pipe = array();
+
+ // taxonomy_default_vocabularies integration is provided by Features.
+ $export['dependencies']['features'] = 'features';
+ $export['dependencies']['taxonomy'] = 'taxonomy';
+
+ // Add dependencies for each vocabulary.
+ $map = features_get_default_map('taxonomy');
+ foreach ($data as $machine_name) {
+ if (isset($map[$machine_name]) && $map[$machine_name] != $module_name) {
+ $export['dependencies'][$map[$machine_name]] = $map[$machine_name];
+ }
+ else {
+ $export['features']['taxonomy'][$machine_name] = $machine_name;
+
+ $fields = field_info_instances('taxonomy_term', $machine_name);
+ foreach ($fields as $name => $field) {
+ $pipe['field'][] = "taxonomy_term-{$field['bundle']}-{$field['field_name']}";
+ $pipe['field_instance'][] = "taxonomy_term-{$field['bundle']}-{$field['field_name']}";
+ }
+ }
+ }
+ return $pipe;
+}
+
+/**
+ * Implements hook_features_export_render().
+ */
+function taxonomy_features_export_render($module, $data) {
+ $vocabularies = taxonomy_get_vocabularies();
+ $code = array();
+ foreach ($data as $machine_name) {
+ foreach ($vocabularies as $vocabulary) {
+ if ($vocabulary->machine_name == $machine_name) {
+ // We don't want to break the entity cache, so we need to clone the
+ // vocabulary before unsetting the id.
+ $vocabulary = clone $vocabulary;
+ unset($vocabulary->vid);
+ $code[$machine_name] = $vocabulary;
+ }
+ }
+ }
+ $code = " return ". features_var_export($code, ' ') .";";
+ return array('taxonomy_default_vocabularies' => $code);
+}
+
+/**
+ * Implements hook_features_revert().
+ */
+function taxonomy_features_revert($module) {
+ taxonomy_features_rebuild($module);
+}
+
+/**
+ * Implements hook_features_rebuild().
+ *
+ * Rebuilds Taxonomy vocabularies from code defaults.
+ */
+function taxonomy_features_rebuild($module) {
+ if ($vocabularies = features_get_default('taxonomy', $module)) {
+ $existing = taxonomy_get_vocabularies();
+ foreach ($vocabularies as $vocabulary) {
+ $vocabulary = (object) $vocabulary;
+ foreach ($existing as $existing_vocab) {
+ if ($existing_vocab->machine_name === $vocabulary->machine_name) {
+ $vocabulary->vid = $existing_vocab->vid;
+ }
+ }
+ taxonomy_vocabulary_save($vocabulary);
+ }
+ }
+}
diff --git a/docroot/sites/all/modules/features/includes/features.user.inc b/docroot/sites/all/modules/features/includes/features.user.inc
new file mode 100644
index 0000000..152c5a8
--- /dev/null
+++ b/docroot/sites/all/modules/features/includes/features.user.inc
@@ -0,0 +1,290 @@
+ array(
+ 'name' => t('Roles'),
+ 'feature_source' => TRUE,
+ 'default_hook' => 'user_default_roles',
+ 'default_file' => FEATURES_DEFAULTS_INCLUDED,
+ ),
+ 'user_permission' => array(
+ 'name' => t('Permissions'),
+ 'feature_source' => TRUE,
+ 'default_hook' => 'user_default_permissions',
+ 'default_file' => FEATURES_DEFAULTS_INCLUDED,
+ ),
+ );
+}
+
+/**
+ * Implements hook_features_export().
+ */
+function user_permission_features_export($data, &$export, $module_name = '') {
+ $export['dependencies']['features'] = 'features';
+
+ // Ensure the modules that provide the given permissions are included as dependencies.
+ $map = user_permission_get_modules();
+ foreach ($data as $perm) {
+ $perm_name = $perm;
+ // Export vocabulary permissions using the machine name, instead of
+ // vocabulary id.
+ _user_features_change_term_permission($perm_name, 'machine_name');
+ if (isset($map[$perm_name])) {
+ $perm_module = $map[$perm_name];
+ $export['dependencies'][$perm_module] = $perm_module;
+ $export['features']['user_permission'][$perm] = $perm;
+ }
+ }
+
+ return array();
+}
+
+/**
+ * Implements hook_features_export_options().
+ */
+function user_permission_features_export_options() {
+ $modules = array();
+ $module_info = system_get_info('module');
+ foreach (module_implements('permission') as $module) {
+ $modules[$module] = $module_info[$module]['name'];
+ }
+ ksort($modules);
+
+ $options = array();
+ foreach ($modules as $module => $display_name) {
+ if ($permissions = module_invoke($module, 'permission')) {
+ foreach ($permissions as $perm => $perm_item) {
+ // Export vocabulary permissions using the machine name, instead of
+ // vocabulary id.
+ _user_features_change_term_permission($perm);
+ $options[$perm] = strip_tags("{$display_name}: {$perm_item['title']}");
+ }
+ }
+ }
+ return $options;
+}
+
+/**
+ * Implements hook_features_export_render().
+ */
+function user_permission_features_export_render($module, $data) {
+ $perm_modules = &drupal_static(__FUNCTION__ . '_perm_modules');
+ if (!isset($perm_modules)) {
+ $perm_modules = user_permission_get_modules();
+ }
+
+ $code = array();
+ $code[] = ' $permissions = array();';
+ $code[] = '';
+
+ $permissions = _user_features_get_permissions();
+
+ foreach ($data as $perm_name) {
+ $permission = array();
+ // Export vocabulary permissions using the machine name, instead of
+ // vocabulary id.
+ $perm = $perm_name;
+ _user_features_change_term_permission($perm_name, 'machine_name');
+ $permission['name'] = $perm;
+ if (!empty($permissions[$perm_name])) {
+ sort($permissions[$perm_name]);
+ $permission['roles'] = drupal_map_assoc($permissions[$perm_name]);
+ }
+ else {
+ $permission['roles'] = array();
+ }
+ if (isset($perm_modules[$perm_name])) {
+ $permission['module'] = $perm_modules[$perm_name];
+ }
+ $perm_identifier = features_var_export($perm);
+ $perm_export = features_var_export($permission, ' ');
+ $code[] = " // Exported permission: {$perm_identifier}.";
+ $code[] = " \$permissions[{$perm_identifier}] = {$perm_export};";
+ $code[] = "";
+ }
+
+ $code[] = ' return $permissions;';
+ $code = implode("\n", $code);
+ return array('user_default_permissions' => $code);
+}
+
+/**
+ * Implements hook_features_revert().
+ */
+function user_permission_features_revert($module) {
+ user_permission_features_rebuild($module);
+}
+
+/**
+ * Implements hook_features_rebuild().
+ * Iterate through default permissions and update the permissions map.
+ *
+ * @param $module
+ * The module whose default user permissions should be rebuilt.
+ */
+function user_permission_features_rebuild($module) {
+ if ($defaults = features_get_default('user_permission', $module)) {
+ // Make sure the list of available node types is up to date, especially when
+ // installing multiple features at once, for example from an install profile
+ // or via drush.
+ node_types_rebuild();
+
+ $modules = user_permission_get_modules();
+ $roles = _user_features_get_roles();
+ $permissions_by_role = _user_features_get_permissions(FALSE);
+ foreach ($defaults as $permission) {
+ $perm = $permission['name'];
+ _user_features_change_term_permission($perm, 'machine_name');
+ if (empty($modules[$perm])) {
+ $args = array('!name' => $perm, '!module' => $module,);
+ $msg = t('Warning in features rebuild of !module. No module defines permission "!name".', $args);
+ drupal_set_message($msg, 'warning');
+ continue;
+ }
+ // Export vocabulary permissions using the machine name, instead of
+ // vocabulary id.
+ foreach ($roles as $role) {
+ if (in_array($role, $permission['roles'])) {
+ $permissions_by_role[$role][$perm] = TRUE;
+ }
+ else {
+ $permissions_by_role[$role][$perm] = FALSE;
+ }
+ }
+ }
+ // Write the updated permissions.
+ foreach ($roles as $rid => $role) {
+ if (isset($permissions_by_role[$role])) {
+ user_role_change_permissions($rid, $permissions_by_role[$role]);
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_features_export().
+ */
+function user_role_features_export($data, &$export, $module_name = '') {
+ $export['dependencies']['features'] = 'features';
+ $map = features_get_default_map('user_role', 'name');
+ foreach ($data as $role) {
+ // Role is provided by another module. Add dependency.
+ if (isset($map[$role]) && $map[$role] != $module_name) {
+ $export['dependencies'][$map[$role]] = $map[$role];
+ }
+ // Export.
+ elseif(user_role_load_by_name($role)) {
+ $export['features']['user_role'][$role] = $role;
+ }
+ }
+ return array();
+}
+
+/**
+ * Implements hook_features_export_options().
+ */
+function user_role_features_export_options() {
+ return drupal_map_assoc(_user_features_get_roles(FALSE));
+}
+
+/**
+ * Implements hook_features_export_render().
+ */
+function user_role_features_export_render($module, $data) {
+ $code = array();
+ $code[] = ' $roles = array();';
+ $code[] = '';
+
+ foreach ($data as $name) {
+ if ($role = user_role_load_by_name($name)) {
+ unset($role->rid);
+ $role_identifier = features_var_export($name);
+ $role_export = features_var_export($role , ' ');
+ $code[] = " // Exported role: {$name}.";
+ $code[] = " \$roles[{$role_identifier}] = {$role_export};";
+ $code[] = "";
+ }
+ }
+
+ $code[] = ' return $roles;';
+ $code = implode("\n", $code);
+ return array('user_default_roles' => $code);
+}
+
+/**
+ * Implements hook_features_revert().
+ */
+function user_role_features_revert($module) {
+ user_role_features_rebuild($module);
+}
+
+/**
+ * Implements hook_features_rebuild().
+ */
+function user_role_features_rebuild($module) {
+ if ($defaults = features_get_default('user_role', $module)) {
+ foreach ($defaults as $role) {
+ $role = (object) $role;
+ if ($existing = user_role_load_by_name($role->name)) {
+ $role->rid = $existing->rid;
+ }
+ user_role_save($role);
+ }
+ }
+}
+
+/**
+ * Generate $rid => $role with role names untranslated.
+ */
+function _user_features_get_roles($builtin = TRUE) {
+ $roles = array();
+ foreach (user_roles() as $rid => $name) {
+ switch ($rid) {
+ case DRUPAL_ANONYMOUS_RID:
+ if ($builtin) {
+ $roles[$rid] = 'anonymous user';
+ }
+ break;
+ case DRUPAL_AUTHENTICATED_RID:
+ if ($builtin) {
+ $roles[$rid] = 'authenticated user';
+ }
+ break;
+ default:
+ $roles[$rid] = $name;
+ break;
+ }
+ }
+ return $roles;
+}
+
+/**
+ * Represent the current state of permissions as a perm to role name array map.
+ */
+function _user_features_get_permissions($by_role = TRUE) {
+ $map = user_permission_get_modules();
+ $roles = _user_features_get_roles();
+ $permissions = array();
+ foreach (user_role_permissions($roles) as $rid => $role_permissions) {
+ if ($by_role) {
+ foreach (array_keys(array_filter($role_permissions)) as $permission) {
+ if (isset($map[$permission])) {
+ $permissions[$permission][] = $roles[$rid];
+ }
+ }
+ }
+ else {
+ $permissions[$roles[$rid]] = array();
+ foreach ($role_permissions as $permission => $status) {
+ if (isset($map[$permission])) {
+ $permissions[$roles[$rid]][$permission] = $status;
+ }
+ }
+ }
+ }
+ return $permissions;
+}
diff --git a/docroot/sites/all/modules/features/tests/features.test b/docroot/sites/all/modules/features/tests/features.test
new file mode 100644
index 0000000..025ef23
--- /dev/null
+++ b/docroot/sites/all/modules/features/tests/features.test
@@ -0,0 +1,329 @@
+ t('Component tests'),
+ 'description' => t('Run tests for components of Features.') ,
+ 'group' => t('Features'),
+ );
+ }
+
+ /**
+ * Set up test.
+ */
+ public function setUp() {
+ parent::setUp(array(
+ 'field',
+ 'filter',
+ 'image',
+ 'taxonomy',
+ 'views',
+ 'features',
+ 'features_test'
+ ));
+
+ // Run a features rebuild to ensure our feature is fully installed.
+ features_rebuild();
+
+ $admin_user = $this->drupalCreateUser(array('administer features'));
+ $this->drupalLogin($admin_user);
+ }
+
+ /**
+ * Run test.
+ */
+ public function test() {
+ module_load_include('inc', 'features', 'features.export');
+
+ $components = array_filter(array(
+ 'field_instance' => 'field',
+ 'filter' => 'filter',
+ 'image' => 'image',
+ 'node' => 'node',
+ 'user_permission' => 'user',
+ 'views_view' => 'views',
+ ), 'module_exists');
+
+ foreach (array_keys($components) as $component) {
+ $callback = "_test_{$component}";
+
+ // Ensure that the component/default is properly available.
+ $object = $this->$callback('load');
+ $this->assertTrue(!empty($object), t('@component present.', array('@component' => $component)));
+
+ // Ensure that the component is defaulted.
+ $states = features_get_component_states(array('features_test'), FALSE, TRUE);
+ $this->assertTrue($states['features_test'][$component] === FEATURES_DEFAULT, t('@component state: Default.', array('@component' => $component)));
+
+ // Override component and test that Features detects the override.
+ $this->$callback('override', $this);
+ $states = features_get_component_states(array('features_test'), FALSE, TRUE);
+ $this->assertTrue($states['features_test'][$component] === FEATURES_OVERRIDDEN, t('@component state: Overridden.', array('@component' => $component)));
+ }
+
+ // Revert component and ensure that component has reverted.
+ // Do this in separate loops so we only have to run
+ // drupal_flush_all_caches() once.
+ foreach (array_keys($components) as $component) {
+ features_revert(array('features_test' => array($component)));
+ }
+ drupal_flush_all_caches();
+ foreach (array_keys($components) as $component) {
+ // Reload so things like Views can clear it's cache
+ $this->$callback('load');
+ $states = features_get_component_states(array('features_test'), FALSE, TRUE);
+ $this->assertTrue($states['features_test'][$component] === FEATURES_DEFAULT, t('@component reverted.', array('@component' => $component)));
+ }
+ }
+
+ protected function _test_field_instance($op = 'load') {
+ switch ($op) {
+ case 'load':
+ return field_info_instance('node', 'field_features_test', 'features_test');
+ case 'override':
+ $field_instance = field_info_instance('node', 'field_features_test', 'features_test');
+ $field_instance['label'] = 'Foo bar';
+ field_update_instance($field_instance);
+ break;
+ }
+ }
+
+ protected function _test_filter($op = 'load') {
+ // So... relying on our own API functions to test is pretty lame.
+ // But these modules don't have APIs either. So might as well use
+ // the ones we've written for them...
+ features_include();
+ switch ($op) {
+ case 'load':
+ return features_filter_format_load('features_test');
+ case 'override':
+ $format = features_filter_format_load('features_test');
+ unset($format->filters['filter_url']);
+ filter_format_save($format);
+ break;
+ }
+ }
+
+ protected function _test_image($op = 'load') {
+ switch ($op) {
+ case 'load':
+ return image_style_load('features_test');
+ case 'override':
+ $style = image_style_load('features_test');
+ $style = image_style_save($style);
+ foreach ($style['effects'] as $effect) {
+ $effect['data']['width'] = '120';
+ image_effect_save($effect);
+ }
+ break;
+ }
+ }
+
+ protected function _test_node($op = 'load') {
+ switch ($op) {
+ case 'load':
+ return node_type_get_type('features_test');
+ case 'override':
+ $type = node_type_get_type('features_test');
+ $type->description = 'Foo bar baz.';
+ $type->modified = TRUE;
+ node_type_save($type);
+ break;
+ }
+ }
+
+ protected function _test_views_view($op = 'load') {
+ switch ($op) {
+ case 'load':
+ return views_get_view('features_test', TRUE);
+ case 'override':
+ $view = views_get_view('features_test', TRUE);
+ $view->set_display('default');
+ $view->display_handler->override_option('title', 'Foo bar');
+ $view->save();
+ // Clear the load cache from above
+ views_get_view('features_test', TRUE);
+ break;
+ }
+ }
+
+ protected function _test_user_permission($op = 'load') {
+ switch ($op) {
+ case 'load':
+ $permissions = user_role_permissions(array(DRUPAL_AUTHENTICATED_RID => 'authenticated user'));
+ return !empty($permissions[DRUPAL_AUTHENTICATED_RID]['create features_test content']);
+ case 'override':
+ user_role_change_permissions(DRUPAL_AUTHENTICATED_RID, array('create features_test content' => 0));
+ break;
+ }
+ }
+}
+
+/**
+ * Tests enabling of feature modules.
+ */
+class FeaturesEnableTestCase extends DrupalWebTestCase {
+ protected $profile = 'testing';
+
+ /**
+ * Test info.
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => t('Features enable tests'),
+ 'description' => t('Run tests for enabling of features.') ,
+ 'group' => t('Features'),
+ );
+ }
+
+
+ /**
+ * Run test for features_get_components on enable.
+ */
+ public function testFeaturesGetComponents() {
+
+ // Testing that features_get_components returns correct after enable.
+ $modules = array(
+ 'features',
+ 'taxonomy',
+ 'features_test',
+ );
+
+ // Make sure features_get_components is cached if features already enabled.
+ if (!module_exists('features')) {
+ drupal_load('module', 'features');
+ }
+ features_get_components();
+
+ module_enable($modules);
+
+ // Make sure correct information for enabled modules is now cached.
+ $components = features_get_components();
+ $taxonomy_component_info = taxonomy_features_api();
+ $this->assertTrue(!empty($components['taxonomy']) && $components['taxonomy'] == $taxonomy_component_info['taxonomy'], 'features_get_components returns correct taxonomy information on enable');
+
+ features_rebuild();
+ $this->assertNotNull(taxonomy_vocabulary_machine_name_load('taxonomy_features_test'), 'Taxonomy vocabulary correctly enabled on enable.');
+ }
+}
+
+
+/**
+ * Tests integration of ctools for features.
+ */
+class FeaturesCtoolsIntegrationTest extends DrupalWebTestCase {
+ protected $profile = 'testing';
+
+ /**
+ * Test info.
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => t('Features Chaos Tools integration'),
+ 'description' => t('Run tests for ctool integration of features.') ,
+ 'group' => t('Features'),
+ );
+ }
+
+ /**
+ * Set up test.
+ */
+ public function setUp() {
+ parent::setUp(array(
+ 'features',
+ 'ctools',
+ ));
+ }
+
+ /**
+ * Run test.
+ */
+ public function testModuleEnable() {
+ $try = array(
+ 'strongarm',
+ 'views',
+ );
+
+ // Trigger the first includes and the static to be set.
+ features_include();
+ $function_ends = array(
+ 'features_export',
+ 'features_export_options',
+ 'features_export_render',
+ 'features_revert',
+ );
+ foreach ($try as $module) {
+ $function = $module . '_features_api';
+ $this->assertFalse(function_exists($function), 'Chaos tools functions for ' . $module . ' do not exist while it is disabled.');
+ // Module enable will trigger declaring the new functions.
+ module_enable(array($module));
+ }
+
+ // CTools hooks only created when there is an actual feature exportable
+ // enabled.
+ module_enable(array('features_test'));
+
+ foreach ($try as $module) {
+ if (module_exists($module)) {
+ $function_exists = function_exists($function);
+ if ($function_exists) {
+ foreach ($function() as $component_type => $component_info) {
+ foreach ($function_ends as $function_end) {
+ $function_exists = $function_exists && function_exists($component_type . '_' . $function_end);
+ }
+ }
+ }
+ $this->assertTrue($function_exists, 'Chaos tools functions for ' . $module . ' exist when it is enabled.');
+ }
+ }
+ }
+}
+
+
+/**
+ * Test detecting modules as features.
+ */
+class FeaturesDetectionTestCase extends DrupalWebTestCase {
+ protected $profile = 'testing';
+
+ /**
+ * Test info.
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => t('Feature Detection tests'),
+ 'description' => t('Run tests for detecting items as features.') ,
+ 'group' => t('Features'),
+ );
+ }
+
+ /**
+ * Set up test.
+ */
+ public function setUp() {
+ parent::setUp(array(
+ 'features',
+ ));
+ }
+
+ /**
+ * Run test.
+ */
+ public function test() {
+ module_load_include('inc', 'features', 'features.export');
+ // First test that features_populate inserts the features api key.
+ $export = features_populate(array(), array(), 'features_test_empty_fake');
+ $this->assertTrue(!empty($export['features']['features_api']) && key($export['features']['features_api']) == 'api:' . FEATURES_API, 'Features API key added to new export.');
+ $this->assertTrue((bool)features_get_features('features_test'), 'Features test recognized as a feature.');
+ $this->assertFalse((bool)features_get_features('features'), 'Features module not recognized as a feature.');
+ }
+}
diff --git a/docroot/sites/all/modules/features/tests/features_test/features_test.features.field_base.inc b/docroot/sites/all/modules/features/tests/features_test/features_test.features.field_base.inc
new file mode 100644
index 0000000..b07f00c
--- /dev/null
+++ b/docroot/sites/all/modules/features/tests/features_test/features_test.features.field_base.inc
@@ -0,0 +1,43 @@
+ 1,
+ 'cardinality' => 1,
+ 'deleted' => 0,
+ 'entity_types' => array(),
+ 'field_name' => 'field_features_test',
+ 'foreign keys' => array(
+ 'format' => array(
+ 'columns' => array(
+ 'format' => 'format',
+ ),
+ 'table' => 'filter_format',
+ ),
+ ),
+ 'indexes' => array(
+ 'format' => array(
+ 0 => 'format',
+ ),
+ ),
+ 'locked' => 0,
+ 'module' => 'text',
+ 'settings' => array(
+ 'max_length' => 255,
+ ),
+ 'translatable' => 1,
+ 'type' => 'text',
+ );
+
+ return $field_bases;
+}
diff --git a/docroot/sites/all/modules/features/tests/features_test/features_test.features.field_instance.inc b/docroot/sites/all/modules/features/tests/features_test/features_test.features.field_instance.inc
new file mode 100644
index 0000000..8796ff4
--- /dev/null
+++ b/docroot/sites/all/modules/features/tests/features_test/features_test.features.field_instance.inc
@@ -0,0 +1,76 @@
+ 'features_test',
+ 'default_value' => NULL,
+ 'deleted' => 0,
+ 'description' => '',
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'above',
+ 'module' => 'text',
+ 'settings' => array(),
+ 'type' => 'text_default',
+ 'weight' => 0,
+ ),
+ 'full' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ 'print' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ 'rss' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ 'teaser' => array(
+ 'label' => 'above',
+ 'settings' => array(),
+ 'type' => 'hidden',
+ 'weight' => 0,
+ ),
+ ),
+ 'entity_type' => 'node',
+ 'field_name' => 'field_features_test',
+ 'label' => 'Test',
+ 'required' => 0,
+ 'settings' => array(
+ 'text_processing' => 0,
+ 'user_register_form' => FALSE,
+ ),
+ 'widget' => array(
+ 'active' => 1,
+ 'module' => 'text',
+ 'settings' => array(
+ 'size' => 60,
+ ),
+ 'type' => 'text_textfield',
+ 'weight' => -4,
+ ),
+ );
+
+ // Translatables
+ // Included for use with string extractors like potx.
+ t('Test');
+
+ return $field_instances;
+}
diff --git a/docroot/sites/all/modules/features/tests/features_test/features_test.features.filter.inc b/docroot/sites/all/modules/features/tests/features_test/features_test.features.filter.inc
new file mode 100644
index 0000000..34cd5e9
--- /dev/null
+++ b/docroot/sites/all/modules/features/tests/features_test/features_test.features.filter.inc
@@ -0,0 +1,56 @@
+ 'features_test',
+ 'name' => 'features_test',
+ 'cache' => 1,
+ 'status' => 1,
+ 'weight' => 0,
+ 'filters' => array(
+ 'filter_autop' => array(
+ 'weight' => 10,
+ 'status' => 1,
+ 'settings' => array(),
+ ),
+ 'filter_html' => array(
+ 'weight' => 10,
+ 'status' => 1,
+ 'settings' => array(
+ 'allowed_html' => ' -
-
- ',
+ 'filter_html_help' => 1,
+ 'filter_html_nofollow' => 0,
+ ),
+ ),
+ 'filter_htmlcorrector' => array(
+ 'weight' => 10,
+ 'status' => 1,
+ 'settings' => array(),
+ ),
+ 'filter_html_escape' => array(
+ 'weight' => 10,
+ 'status' => 1,
+ 'settings' => array(),
+ ),
+ 'filter_url' => array(
+ 'weight' => 10,
+ 'status' => 1,
+ 'settings' => array(
+ 'filter_url_length' => 72,
+ ),
+ ),
+ ),
+ );
+
+ return $formats;
+}
diff --git a/docroot/sites/all/modules/features/tests/features_test/features_test.features.inc b/docroot/sites/all/modules/features/tests/features_test/features_test.features.inc
new file mode 100644
index 0000000..8d0646f
--- /dev/null
+++ b/docroot/sites/all/modules/features/tests/features_test/features_test.features.inc
@@ -0,0 +1,64 @@
+ "1");
+ }
+}
+
+/**
+ * Implements hook_views_api().
+ */
+function features_test_views_api() {
+ return array("api" => "3.0");
+}
+
+/**
+ * Implements hook_image_default_styles().
+ */
+function features_test_image_default_styles() {
+ $styles = array();
+
+ // Exported image style: features_test.
+ $styles['features_test'] = array(
+ 'effects' => array(
+ 2 => array(
+ 'name' => 'image_scale',
+ 'data' => array(
+ 'width' => 100,
+ 'height' => 100,
+ 'upscale' => 0,
+ ),
+ 'weight' => 1,
+ ),
+ ),
+ 'label' => 'features_test',
+ );
+
+ return $styles;
+}
+
+/**
+ * Implements hook_node_info().
+ */
+function features_test_node_info() {
+ $items = array(
+ 'features_test' => array(
+ 'name' => t('Testing: Features'),
+ 'base' => 'node_content',
+ 'description' => t('Content type provided for Features tests.'),
+ 'has_title' => '1',
+ 'title_label' => t('Title'),
+ 'help' => '',
+ ),
+ );
+ return $items;
+}
diff --git a/docroot/sites/all/modules/features/tests/features_test/features_test.features.taxonomy.inc b/docroot/sites/all/modules/features/tests/features_test/features_test.features.taxonomy.inc
new file mode 100644
index 0000000..c5ab0fa
--- /dev/null
+++ b/docroot/sites/all/modules/features/tests/features_test/features_test.features.taxonomy.inc
@@ -0,0 +1,36 @@
+ array(
+ 'name' => 'Taxonomy Features Test',
+ 'machine_name' => 'taxonomy_features_test',
+ 'description' => 'Taxonomy vocabulary',
+ 'hierarchy' => 0,
+ 'module' => 'taxonomy',
+ 'weight' => 0,
+ 'rdf_mapping' => array(
+ 'rdftype' => array(
+ 0 => 'skos:ConceptScheme',
+ ),
+ 'name' => array(
+ 'predicates' => array(
+ 0 => 'dc:title',
+ ),
+ ),
+ 'description' => array(
+ 'predicates' => array(
+ 0 => 'rdfs:comment',
+ ),
+ ),
+ ),
+ ),
+ );
+}
diff --git a/docroot/sites/all/modules/features/tests/features_test/features_test.features.user_permission.inc b/docroot/sites/all/modules/features/tests/features_test/features_test.features.user_permission.inc
new file mode 100644
index 0000000..30b192f
--- /dev/null
+++ b/docroot/sites/all/modules/features/tests/features_test/features_test.features.user_permission.inc
@@ -0,0 +1,24 @@
+ 'create features_test content',
+ 'roles' => array(
+ 'anonymous user' => 'anonymous user',
+ 'authenticated user' => 'authenticated user',
+ ),
+ 'module' => 'node',
+ );
+
+ return $permissions;
+}
diff --git a/docroot/sites/all/modules/features/tests/features_test/features_test.info b/docroot/sites/all/modules/features/tests/features_test/features_test.info
new file mode 100644
index 0000000..efe7101
--- /dev/null
+++ b/docroot/sites/all/modules/features/tests/features_test/features_test.info
@@ -0,0 +1,29 @@
+name = Features Tests
+description = Test module for Features testing.
+core = 7.x
+package = Testing
+php = 5.2.0
+dependencies[] = features
+dependencies[] = image
+dependencies[] = strongarm
+dependencies[] = taxonomy
+dependencies[] = views
+features[ctools][] = strongarm:strongarm:1
+features[ctools][] = views:views_default:3.0
+features[features_api][] = api:2
+features[field_base][] = field_features_test
+features[field_instance][] = node-features_test-field_features_test
+features[filter][] = features_test
+features[image][] = features_test
+features[node][] = features_test
+features[taxonomy][] = taxonomy_features_test
+features[user_permission][] = create features_test content
+features[views_view][] = features_test
+hidden = 1
+
+; Information added by Drupal.org packaging script on 2015-10-14
+version = "7.x-2.7"
+core = "7.x"
+project = "features"
+datestamp = "1444829630"
+
diff --git a/docroot/sites/all/modules/features/tests/features_test/features_test.module b/docroot/sites/all/modules/features/tests/features_test/features_test.module
new file mode 100644
index 0000000..762d6e5
--- /dev/null
+++ b/docroot/sites/all/modules/features/tests/features_test/features_test.module
@@ -0,0 +1,3 @@
+name = 'features_test';
+ $view->description = 'Test view provided by Features testing module.';
+ $view->tag = 'testing';
+ $view->base_table = 'node';
+ $view->human_name = '';
+ $view->core = 0;
+ $view->api_version = '3.0';
+ $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+ /* Display: Defaults */
+ $handler = $view->new_display('default', 'Defaults', 'default');
+ $handler->display->display_options['title'] = 'Test';
+ $handler->display->display_options['use_more_always'] = FALSE;
+ $handler->display->display_options['access']['type'] = 'none';
+ $handler->display->display_options['cache']['type'] = 'none';
+ $handler->display->display_options['query']['type'] = 'views_query';
+ $handler->display->display_options['query']['options']['query_comment'] = FALSE;
+ $handler->display->display_options['exposed_form']['type'] = 'basic';
+ $handler->display->display_options['pager']['type'] = 'full';
+ $handler->display->display_options['style_plugin'] = 'default';
+ $handler->display->display_options['row_plugin'] = 'node';
+ $export['features_test'] = $view;
+
+ return $export;
+}
diff --git a/docroot/sites/all/modules/features/theme/features-admin-components.tpl.php b/docroot/sites/all/modules/features/theme/features-admin-components.tpl.php
new file mode 100644
index 0000000..04213a0
--- /dev/null
+++ b/docroot/sites/all/modules/features/theme/features-admin-components.tpl.php
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+ $key)) ?>
+
+
+
+
+
+
+
+
diff --git a/docroot/sites/all/modules/features/theme/theme.inc b/docroot/sites/all/modules/features/theme/theme.inc
new file mode 100644
index 0000000..e0f3701
--- /dev/null
+++ b/docroot/sites/all/modules/features/theme/theme.inc
@@ -0,0 +1,362 @@
+ $status) {
+ $rows[] = array(
+ array(
+ 'data' => isset($modules[$dependency]->info['name']) ? $modules[$dependency]->info['name'] : $dependency,
+ 'class' => 'component'
+ ),
+ theme('features_module_status', array('status' => $status)),
+ );
+ }
+ $vars['dependencies'] = theme('table', array('header' => array(t('Dependency'), t('Status')), 'rows' => $rows));
+
+ // Components
+ $rows = array();
+ $components = features_get_components();
+
+ // Display key for conflicting elements.
+ if (!empty($form['#conflicts'])) {
+ $vars['key'][] = array(
+ 'title' => theme('features_storage_link', array('storage' => FEATURES_CONFLICT, 'text' => t('Conflicts with another feature'))),
+ 'html' => TRUE,
+ );
+ }
+
+ if (!empty($form['#info']['features'])) {
+ foreach ($form['#info']['features'] as $component => $items) {
+ if (!empty($items)) {
+ $conflicts = array_key_exists($component, $form['#conflicts'])
+ ? $form['#conflicts'][$component]
+ : NULL;
+
+ $header = $data = array();
+ if (element_children($form['revert'])) {
+ $header[] = array(
+ 'data' => isset($form['revert'][$component]) ? drupal_render($form['revert'][$component]) : '',
+ 'header' => TRUE
+ );
+ }
+ $header[] = array(
+ 'data' => isset($components[$component]['name']) ? $components[$component]['name'] : $component,
+ 'header' => TRUE
+ );
+ $header[] = array(
+ 'data' => drupal_render($form['components'][$component]),
+ 'header' => TRUE
+ );
+ $rows[] = $header;
+
+ if (element_children($form['revert'])) {
+ $data[] = '';
+ }
+ $data[] = array(
+ 'data' => theme('features_component_list', array('components' => $items, 'source' => $items, 'conflicts' => $conflicts)),
+ 'colspan' => 2,
+ 'class' => 'component'
+ );
+ $rows[] = $data;
+ }
+ }
+ }
+ $vars['components'] = theme('table', array('header' => array(), 'rows' => $rows));
+
+ // Other elements
+ $vars['buttons'] = drupal_render($form['buttons']);
+ $vars['form'] = $form;
+ $vars['lock_feature'] = theme('features_lock_link', array('feature' => $form['#feature']->name));
+}
+
+/**
+ * Themes a module status display.
+ */
+function theme_features_module_status($vars) {
+ switch ($vars['status']) {
+ case FEATURES_MODULE_ENABLED:
+ $text_status = t('Enabled');
+ $class = 'admin-enabled';
+ break;
+ case FEATURES_MODULE_DISABLED:
+ $text_status = t('Disabled');
+ $class = 'admin-disabled';
+ break;
+ case FEATURES_MODULE_MISSING:
+ $text_status = t('Missing');
+ $class = 'admin-missing';
+ break;
+ case FEATURES_MODULE_CONFLICT:
+ $text_status = t('Enabled');
+ $class = 'admin-conflict';
+ break;
+ }
+ $text = !empty($vars['module']) ? $vars['module'] . ' (' . $text_status . ')' : $text_status;
+ return "$text";
+}
+
+/**
+ * Themes a lock link
+ */
+function theme_features_lock_link($vars) {
+ drupal_add_library('system', 'ui');
+ drupal_add_library('system', 'drupal.ajax');
+ $component = $vars['component'] ? $vars['component'] : '';
+ if ($component && features_component_is_locked($component)) {
+ return l(t('Component locked'), 'admin/structure/features/settings', array(
+ 'attributes' => array(
+ 'class' => 'features-lock-icon ui-icon ui-icon-locked',
+ 'title' => t('This component is locked on a global level.'),
+ ),
+ 'fragment' => 'edit-lock-components',
+ ));
+ }
+ $feature = $vars['feature'];
+ $is_locked = features_feature_is_locked($feature, $component);
+ $options = array(
+ 'attributes' => array(
+ 'class' => array('use-ajax features-lock-icon ui-icon ' . ($is_locked ? ' ui-icon-locked' : ' ui-icon-unlocked')),
+ 'id' => 'features-lock-link-' . $feature . ($component ? '-' . $component : ''),
+ 'title' => $is_locked ? t('This item is locked and features will not be rebuilt or reverted.') : t('This item is unlocked and will be rebuilt/reverted as normal.'),
+ ),
+ 'query' => array('token' => drupal_get_token('features/' . $feature . '/' . $component)),
+ );
+ $path = "admin/structure/features/" . $feature . "/lock/nojs" . ($component ? '/' . $component: '');
+ return l($is_locked ? t('UnLock') : t('Lock'), $path, $options);
+}
+
+/**
+ * Themes a module status display.
+ */
+function theme_features_storage_link($vars) {
+ $classes = array(
+ FEATURES_OVERRIDDEN => 'admin-overridden',
+ FEATURES_DEFAULT => 'admin-default',
+ FEATURES_NEEDS_REVIEW => 'admin-needs-review',
+ FEATURES_REBUILDING => 'admin-rebuilding',
+ FEATURES_REBUILDABLE => 'admin-rebuilding',
+ FEATURES_CONFLICT => 'admin-conflict',
+ FEATURES_DISABLED => 'admin-disabled',
+ FEATURES_CHECKING => 'admin-loading',
+ );
+ $default_text = array(
+ FEATURES_OVERRIDDEN => t('Overridden'),
+ FEATURES_DEFAULT => t('Default'),
+ FEATURES_NEEDS_REVIEW => t('Needs review'),
+ FEATURES_REBUILDING => t('Rebuilding'),
+ FEATURES_REBUILDABLE => t('Rebuilding'),
+ FEATURES_CONFLICT => t('Conflict'),
+ FEATURES_DISABLED => t('Disabled'),
+ FEATURES_CHECKING => t('Checking...'),
+ );
+ $text = isset($vars['text']) ? $vars['text'] : $default_text[$vars['storage']];
+ if ($vars['path']) {
+ $vars['options']['attributes']['class'][] = $classes[$vars['storage']];
+ $vars['options']['attributes']['class'][] = 'features-storage';
+ return l($text, $vars['path'], $vars['options']);
+ }
+ else {
+ return "{$text}";
+ }
+}
+
+/**
+ * Theme function for displaying form buttons
+ */
+function theme_features_form_buttons(&$vars) {
+ drupal_add_css(drupal_get_path('module', 'features') . '/features.css');
+
+ $output = drupal_render_children($vars['element']);
+ return !empty($output) ? "{$output} " : '';
+}
+
+/**
+ * Theme for features management form.
+ */
+function theme_features_form_package(&$vars) {
+ drupal_add_css(drupal_get_path('module', 'features') . '/features.css');
+ drupal_add_js(drupal_get_path('module', 'features') . '/features.js');
+
+ $output = '';
+
+ $header = array('', t('Feature'), t('Signature'));
+ if (isset($vars['form']['state'])) {
+ $header[] = t('State');
+ }
+ if (isset($vars['form']['actions'])) {
+ $header[] = t('Actions');
+ }
+
+ $rows = array();
+ foreach (element_children($vars['form']['status']) as $element) {
+ // Yank title & description fields off the form element for
+ // rendering in their own cells.
+ $name = "";
+ $name .= " {$vars['form']['status'][$element]['#title']}";
+ $name .= " {$vars['form']['status'][$element]['#description']} ";
+ $name .= " ";
+ unset($vars['form']['status'][$element]['#title']);
+ unset($vars['form']['status'][$element]['#description']);
+
+
+ // Determine row & cell classes
+ $class = $vars['form']['status'][$element]['#default_value'] ? 'enabled' : 'disabled';
+
+ $row = array();
+ $row['status'] = array('data' => drupal_render($vars['form']['status'][$element]), 'class' => array('status'));
+ $row['name'] = array('data' => $name, 'class' => 'name');
+ $row['sign'] = array('data' => drupal_render($vars['form']['sign'][$element]), 'class' => array('sign'));
+
+ if (isset($vars['form']['state'])) {
+ $row['state'] = array('data' => drupal_render($vars['form']['state'][$element]), 'class' => array('state'));
+ }
+ if (isset($vars['form']['actions'])) {
+ $row['actions'] = array('data' => drupal_render($vars['form']['actions'][$element]), 'class' => array('actions'));
+ }
+ $rows[] = array('data' => $row, 'class' => array($class));
+ }
+
+ if (empty($rows)) {
+ $rows[] = array('', array('data' => t('No features available.'), 'colspan' => count($header)));
+ }
+
+ $class = count($header) > 3 ? 'features features-admin' : 'features features-manage';
+ $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'features-form-table', 'class' => array($class))));
+
+ // Prevent section from being rendered by drupal_render().
+
+ $output .= drupal_render($vars['form']['buttons']);
+ $output .= drupal_render_children($vars['form']);
+ return $output;
+}
+
+/**
+ * Theme functions ====================================================
+ */
+
+/**
+ * Export selection / display for features export form.
+ */
+function theme_features_form_export(&$vars) {
+ drupal_add_css(drupal_get_path('module', 'features') . '/features.css');
+ drupal_add_js(drupal_get_path('module', 'features') . '/features.js');
+
+ $output = '';
+ $output .= "";
+ $output .= " " . drupal_render($vars['form']['components']) . drupal_render($vars['form']['sources']) . " ";
+ $output .= " " . drupal_render($vars['form']['preview']) . drupal_render($vars['form']['features']) . " ";
+ $output .= " ";
+ $output .= drupal_render_children($vars['form']);
+ return $output;
+}
+
+/**
+ * Theme a set of features export components.
+ */
+function theme_features_form_components(&$vars) {
+ $output = '';
+ foreach (element_children($vars['form']) as $key) {
+ unset($vars['form'][$key]['#title']);
+ $output .= "" . drupal_render($vars['form'][$key]) . " ";
+ }
+ $output .= drupal_render_children($vars['form']);
+ return $output;
+}
+
+/**
+ * Theme a set of features export components.
+ */
+function theme_features_components($vars) {
+ $info = $vars['info'];
+ $sources = $vars['sources'];
+
+ $output = '';
+ $rows = array();
+ $components = features_get_components();
+ if (!empty($info['features']) || !empty($info['dependencies']) || !empty($sources)) {
+ $export = array_unique(array_merge(
+ array_keys($info['features']),
+ array_keys($sources),
+ array('dependencies')
+ ));
+ foreach ($export as $component) {
+ if ($component === 'dependencies') {
+ $feature_items = isset($info[$component]) ? $info[$component] : array();
+ }
+ else {
+ $feature_items = isset($info['features'][$component]) ? $info['features'][$component] : array();
+ }
+ $source_items = isset($sources[$component]) ? $sources[$component] : array();
+ if (!empty($feature_items) || !empty($source_items)) {
+ $rows[] = array(array(
+ 'data' => isset($components[$component]['name']) ? $components[$component]['name'] : $component,
+ 'header' => TRUE
+ ));
+ $rows[] = array(array(
+ 'data' => theme('features_component_list', array('components' => $feature_items, 'source' => $source_items)),
+ 'class' => 'component'
+ ));
+ }
+ }
+ $output .= theme('table', array('header' => array(), 'rows' => $rows));
+ $output .= theme('features_component_key', array());
+ }
+ return $output;
+}
+
+/**
+ * Theme individual components in a component list.
+ */
+function theme_features_component_list($vars) {
+ $components = $vars['components'];
+ $source = $vars['source'];
+ $conflicts = $vars['conflicts'];
+
+ $list = array();
+ foreach ($components as $component) {
+ // If component is not in source list, it was autodetected
+ if (!in_array($component, $source)) {
+ $list[] = "". check_plain($component) ."";
+ }
+ elseif (is_array($conflicts) && in_array($component, $conflicts)) {
+ $list[] = "". check_plain($component) ."";
+ }
+ else {
+ $list[] = "". check_plain($component) ."";
+ }
+ }
+ foreach ($source as $component) {
+ // If a source component is no longer in the items, it was removed because
+ // it is provided by a dependency.
+ if (!in_array($component, $components)) {
+ $list[] = "". check_plain($component) ."";
+ }
+ }
+ return "". implode(' ', $list) ."";
+}
+
+/**
+ * Provide a themed key for a component list.
+ */
+function theme_features_component_key($vars) {
+ $list = array();
+ $list[] = "" . t('Normal') . "";
+ $list[] = "" . t('Auto-detected') . "";
+ $list[] = "" . t('Provided by dependency') . "";
+ return "" . implode(' ', $list) . "";
+}
From bafc6404a6a53e33102457d03713fa66c0d74193 Mon Sep 17 00:00:00 2001
From: keelahnkhan
Date: Wed, 2 Dec 2015 13:35:40 -0500
Subject: [PATCH 10/21] Added .docker folder
---
.docker/CHANGELOG.md | 16 +
.docker/README.md | 180 ++
.docker/VERSION | 1 +
.docker/bin/docker-bash | 8 +
.docker/bin/docker-drush | 3 +
.docker/bin/dsh | 860 +++++++++
.docker/docs/behat.md | 47 +
.docker/docs/db-sandbox.md | 104 ++
.docker/docs/drupal-settings.md | 81 +
.docker/docs/img/unionfs-container.png | Bin 0 -> 149078 bytes
.docker/docs/img/unionfs-your-image.png | Bin 0 -> 146826 bytes
.docker/docs/multiple-projects.md | 25 +
.docker/docs/public-access.md | 20 +
.docker/docs/settings.md | 8 +
.docker/etc/mysql/my.cnf | 20 +
.docker/etc/php5/php-cli.ini | 19 +
.docker/etc/php5/php.ini | 34 +
.docker/etc/solr/3.x/elevate.xml | 31 +
.../etc/solr/3.x/mapping-ISOLatin1Accent.txt | 14 +
.docker/etc/solr/3.x/protwords.txt | 7 +
.docker/etc/solr/3.x/schema.xml | 546 ++++++
.docker/etc/solr/3.x/schema_extra_fields.xml | 23 +
.docker/etc/solr/3.x/schema_extra_types.xml | 30 +
.docker/etc/solr/3.x/solrconfig.xml | 1624 +++++++++++++++++
.docker/etc/solr/3.x/solrconfig_extra.xml | 80 +
.docker/etc/solr/3.x/solrcore.properties | 16 +
.docker/etc/solr/3.x/stopwords.txt | 4 +
.docker/etc/solr/3.x/synonyms.txt | 3 +
28 files changed, 3804 insertions(+)
create mode 100644 .docker/CHANGELOG.md
create mode 100644 .docker/README.md
create mode 100644 .docker/VERSION
create mode 100755 .docker/bin/docker-bash
create mode 100755 .docker/bin/docker-drush
create mode 100755 .docker/bin/dsh
create mode 100644 .docker/docs/behat.md
create mode 100644 .docker/docs/db-sandbox.md
create mode 100644 .docker/docs/drupal-settings.md
create mode 100644 .docker/docs/img/unionfs-container.png
create mode 100644 .docker/docs/img/unionfs-your-image.png
create mode 100644 .docker/docs/multiple-projects.md
create mode 100644 .docker/docs/public-access.md
create mode 100644 .docker/docs/settings.md
create mode 100644 .docker/etc/mysql/my.cnf
create mode 100644 .docker/etc/php5/php-cli.ini
create mode 100644 .docker/etc/php5/php.ini
create mode 100644 .docker/etc/solr/3.x/elevate.xml
create mode 100644 .docker/etc/solr/3.x/mapping-ISOLatin1Accent.txt
create mode 100644 .docker/etc/solr/3.x/protwords.txt
create mode 100644 .docker/etc/solr/3.x/schema.xml
create mode 100644 .docker/etc/solr/3.x/schema_extra_fields.xml
create mode 100644 .docker/etc/solr/3.x/schema_extra_types.xml
create mode 100644 .docker/etc/solr/3.x/solrconfig.xml
create mode 100644 .docker/etc/solr/3.x/solrconfig_extra.xml
create mode 100644 .docker/etc/solr/3.x/solrcore.properties
create mode 100644 .docker/etc/solr/3.x/stopwords.txt
create mode 100644 .docker/etc/solr/3.x/synonyms.txt
diff --git a/.docker/CHANGELOG.md b/.docker/CHANGELOG.md
new file mode 100644
index 0000000..034ebf0
--- /dev/null
+++ b/.docker/CHANGELOG.md
@@ -0,0 +1,16 @@
+# Changelog
+
+## 0.12.0 (2015-04-07)
+
+- Added "browser" node for Behat tests requiring JS support
+- Documented using Behat
+- CI: circle.docker-compose.yml us now used for automated tests of Drude, while docker-compose.yml is still the one packaged for distribution. This is not ideal... Need to figure out a way to alter docker-compose.yml during the build to comment out sections used in Drude's own CI tests, but optional for end users.
+
+## 0.11.0 (2015-03-27)
+
+- Drude Shell (dsh) tool - an all-in-one tool for daily Drude use
+- New build and release process
+- Using semantic versioning and tracking changes in the CHANGELOG.md file
+- CircleCI integration for automated testing and build purposes
+- New install and update process
+- Documentation overhaul
diff --git a/.docker/README.md b/.docker/README.md
new file mode 100644
index 0000000..8d9cebe
--- /dev/null
+++ b/.docker/README.md
@@ -0,0 +1,180 @@
+# Drude (**Dru**pal **D**ocker **E**nvironment)
+Docker and Docker Compose based environment for Drupal.
+
+[](https://circleci.com/gh/blinkreaction/drude)
+
+
+## Requirements
+
+Docker is natively supported only on Linux.
+Mac and Windows users will need a tiny linux VM layer - [Boot2docker Vagrant Box](https://github.com/blinkreaction/boot2docker-vagrant)
+
+### Mac and Windows
+
+1. Get the [Boot2docker Vagrant Box](https://github.com/blinkreaction/boot2docker-vagrant) up and running.
+2. Install [dsh](#dsh) tool (Drude Shell) wrapper
+
+ ```
+ curl -s https://raw.githubusercontent.com/blinkreaction/drude/master/install-dsh.sh | bash
+ ```
+
+### Linux
+1. [Docker](https://docs.docker.com/compose/install/#install-docker)
+2. [Docker Compose](https://docs.docker.com/compose/install/#install-compose)
+3. Install [dsh](#dsh) tool (Drude Shell) wrapper
+
+ ```
+ curl -s https://raw.githubusercontent.com/blinkreaction/drude/master/install-dsh.sh | bash
+ ```
+
+
+## Setup
+
+Drude initial setup is done once per project (e.g. by a team lead).
+Once installed into the project repo, Drude can be used by anyone on the team.
+To use Drude each team member will need to meet the [requirements](#requirements) outlined above.
+
+`docker-compose.yml` file and `.docker` folder are good indicators of Drude's presence in the project repo.
+
+**If this is the first time Drude is being installed into the project, follow the instructions below.**
+
+The installation process is slightly different based on the OS.
+
+### Mac
+
+ 1. Make sure your site's docroot is in `/docroot`.
+ 2. Edit `settings.php` for the site (see [Drupal settings](#drupal-settings) below).
+ 3. cd `` and run:
+
+ ```
+ curl -s https://raw.githubusercontent.com/blinkreaction/drude/master/install-drude.sh | bash
+ ```
+
+ 4. Start containers with `dsh up`
+
+### Windows
+
+ 1. Make sure your site's docroot is in `/docroot`.
+ 2. Edit `settings.php` for the site (see [Drupal settings](#drupal-settings) below).
+ 3. Open Git Bash shell and cd into ``, then run:
+
+ ```
+ curl -s https://raw.githubusercontent.com/blinkreaction/drude/master/install-drude.sh | bash
+ ```
+
+ 4. Start and login into vagrant, cd into ``:
+
+ ```
+ vagrant up
+ vagrant ssh
+ cd
+ ```
+
+ 5. Start containers with `dsh up`
+
+### Linux
+
+ 1. Make sure your site's docroot is in `/docroot`.
+ 2. Edit `settings.php` for the site (see [Drupal settings](#drupal-settings) below).
+ 3. cd `` and run:
+
+ ```
+ curl -s https://raw.githubusercontent.com/blinkreaction/drude/master/install-drude.sh | bash
+ ```
+
+ 4. Start containers with `dsh up`
+
+
+## Updates
+
+To update Drude run the following from the `` folder:
+
+ curl -s https://raw.githubusercontent.com/blinkreaction/drude/master/install-drude.sh | bash
+
+Review the changes, revert any local overrides that were reset and commit into your project git repo.
+
+Base images will be updated from time to time. Docker Compose does not automatically pull new image versions.
+To get an up-to-date version of the entire stack do:
+
+ docker-compose pull
+ docker-compose up -d
+
+
+## Drupal settings
+
+Below you will find instructions on configuring you Drupal project to work with Drude.
+Some settings are required, others are optional or enahncements. Please review carefully.
+
+**Required**
+- [DB connection settings](.docker/docs/drupal-settings.md#db)
+- [File permissions fix](.docker/docs/drupal-settings.md#file-permissions)
+
+**Optional**
+- [Memcache settings](.docker/docs/drupal-settings.md#memcache)
+
+
+## Drude Shell (dsh)
+
+Drude shell (dsh) is a console tool that simplifies that day-to-day work with Drude.
+It provide a set of most commonly used commands and operations for controlling the Boot2docker VM, containers, running drush and other console commands inside the **cli** container.
+
+See `dsh help` for a complete list.
+
+`dsh` detects the environment it's launched in and will automatically start the boot2docker VM and launch containers as necessary.
+It runs on Mac/Linux directly. On Windows `dsh` runs inside the boot2docker VM.
+
+### Installation
+
+ curl -s https://raw.githubusercontent.com/blinkreaction/drude/master/install-dsh.sh | bash
+
+This will install a local dsh wrapper into `/usr/local/bin/dsh`.
+The actual dsh script resides in each project individually (in `.docker/bin/dsh`) and is installed into the project along with Drude. The wrapper makes it possible to use `dsh` from anywhere in the project tree.
+
+
+## Console tools (cli)
+
+**cli** container is ment to serv as a single console to access all necessary command line tools.
+The **cli** console can launch with `dsh`:
+
+ dsh bash
+
+Tools available inside the **cli** container:
+
+- php-cli, composer, drush7
+- ruby, bundler
+- node, npm, bower, grunt
+- git, wget, zip, mysql-client
+- python
+
+
+## Instructions and tutorials
+
+- [Running multiple projects](.docker/docs/multiple-projects.md)
+- [Overriding default PHP/MySQL/etc. settings](.docker/docs/settings.md)
+- [Public access](.docker/docs/public-access.md)
+- [DB sandbox mode](.docker/docs/db-sandbox.md)
+- [Using Behat](.docker/docs/behat.md)
+
+## License
+
+The MIT License (MIT)
+
+Copyright (c) 2015 BlinkReaction
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/.docker/VERSION b/.docker/VERSION
new file mode 100644
index 0000000..d33c3a2
--- /dev/null
+++ b/.docker/VERSION
@@ -0,0 +1 @@
+0.12.0
\ No newline at end of file
diff --git a/.docker/bin/docker-bash b/.docker/bin/docker-bash
new file mode 100755
index 0000000..a1c9972
--- /dev/null
+++ b/.docker/bin/docker-bash
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+if [[ $1 == '' ]]; then
+ echo "Starting bash..."
+ docker exec -it $(docker-compose ps -q cli) bash -i
+else
+ docker exec -it $(docker-compose ps -q cli) bash -ic "$*"
+fi
diff --git a/.docker/bin/docker-drush b/.docker/bin/docker-drush
new file mode 100755
index 0000000..bf387af
--- /dev/null
+++ b/.docker/bin/docker-drush
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+docker exec -it $(docker-compose ps -q cli) bash -ic "drush $*"
diff --git a/.docker/bin/dsh b/.docker/bin/dsh
new file mode 100755
index 0000000..774e6d6
--- /dev/null
+++ b/.docker/bin/dsh
@@ -0,0 +1,860 @@
+#!/bin/bash
+
+help ()
+{
+ echo "dsh - Drude Shell"
+ echo "Usage: dsh [params]"
+ echo "Commands list:"
+ echo
+ echo " init Set up the project locally. Calls .docker/scripts/drude-init.sh"
+ echo
+ echo " start (up) Start vagrant vm (mac only) and docker containers -OR- restarts docker containers"
+ echo " stop (down, halt) Stop vagrant vm (mac only) or stop containers"
+ echo " reload (restart) Re-start vagrant vm (mac only) and docker containers"
+ echo " status (st, ps) Show containers status"
+ echo
+ echo " reset Restart docker containers from scratch (stop, remove, up)"
+ echo
+ echo " bash Start bash on container. Default: cli (usage: dsh bash [container_name])"
+ echo " exec (run) Execute a command in cli container (usage: dsh exec [param] [param] [param]...)"
+ echo
+ echo " mysql Opens mysql shell to drude database"
+ echo " mysql-import Truncate database and import from sql dump (usage: dsh mysql-import )"
+ echo " Note: DB dump file should be inside your project folder"
+ echo " drush Execute drush command (usage: dsh drush [command] [options])"
+ echo " cc Clear Drupal caches (usage: dsh cc [cache_type] (\"dsh cc\" is equal to \"dsh cc all\")"
+ echo
+ echo " behat Run Behat tests"
+ echo
+ echo " install Install boot2docker and drude"
+ echo " update Update boot2docker and drude"
+ echo
+}
+
+# Console colors
+red='\033[0;31m'
+green='\033[0;32m'
+yellow='\033[1;33m'
+NC='\033[0m'
+
+#-------------------------- Testing --------------------------------
+if [[ $DRUDE_BRANCH == "" ]]; then
+ DRUDE_BRANCH='master'
+fi
+if [[ $B2D_BRANCH == "" ]]; then
+ B2D_BRANCH='master'
+fi
+if [[ ! $B2D_BRANCH == 'master' ]] || [[ ! $DRUDE_BRANCH == 'master' ]]; then
+ echo -e "${red}[dsh] testing mode: boot2docker - ${B2D_BRANCH}, drude - ${DRUDE_BRANCH}$NC"
+fi
+
+#---------------- Docker Compose Global Settings ----------------------
+DC_FILE=''
+DC_PROJECT=''
+if [[ ! $DRUDE_DC_FILE == "" ]]; then
+ DC_FILE="-f $DRUDE_DC_FILE"
+fi
+if [[ ! $DRUDE_DC_PROJECT == "" ]]; then
+ DC_PROJECT="-p $DRUDE_DC_PROJECT"
+fi
+
+# Pass global project and file settings to docker-compose.
+# Use this function for every docker-compose invocation.
+docker_compose ()
+{
+ docker-compose $DC_FILE $DC_PROJECT $*
+}
+
+#-------------------------- Helper functions --------------------------------
+
+# Check whether shell is interactive (otherwise we are running in a non-interactive script environment)
+is_tty ()
+{
+ [[ "$(/usr/bin/tty || true)" != "not a tty" ]]
+}
+
+# Search for file in current dir and all parent dirs
+# @param $1 filename
+upfind ()
+{
+ if [[ $1 == '' ]]; then return 1; fi
+ local up='../'
+ local pathup='./'
+ local slashes=$(pwd | sed 's/[^\/]//g')
+ for ((i=0; i<=${#slashes}; i++)) do
+ if [ -f "${pathup}$1" ] ; then
+ echo "${pathup}"
+ break
+ else
+ pathup=$pathup$up
+ fi
+ done
+}
+
+yml_get_path ()
+{
+ echo `upfind docker-compose.yml`
+}
+
+# Returns absolute path
+get_abs_path ()
+{
+ local _dir
+ if [ -f $1 ]; then
+ _dir=`dirname $1`
+ elif [ -d $1 ]; then
+ _dir=$1
+ else
+ echo "Path \"$1\" does not exist"
+ return
+ fi
+ local absolute=$( cd "${_dir}" ; pwd )
+ echo "$absolute"
+}
+
+# Returns project root absolute path
+get_project_root_path ()
+{
+ yml_path=`yml_get_path`
+ # get relative project root path from docker-compose.yml config
+ local project_root_relative=$(grep "&project_root" ${yml_path}docker-compose.yml | sed 's/.*"\(.*\):.*$/\1/')
+ local project_root_absolute=$( cd "${yml_path}$project_root_relative" ; pwd )
+ echo "$project_root_absolute"
+}
+
+# dummy function to check that command is run along settings.php
+get_drush_path ()
+{
+ # Check that we're inside project folder
+ local proj_root=$(get_project_root_path)
+ local cwd=$(pwd)
+ # if cwd substract proj_root is still cwd then it means we're out of proj_root
+ local pathdiff=${cwd#$proj_root}
+ if [[ "$cwd" == "$pathdiff" ]]; then
+ echo "dsh supports '$argv' command only from within project directory"
+ return 1
+ fi
+ if [[ "$pathdiff" == "" ]]; then # we're on top level, let's cd for drush to work
+ cd docroot
+ fi
+ return 0
+}
+
+# Get current path relative to project root
+# @param $1 name of variable to get result
+cmdpath ()
+{
+ if [[ $# -ne 1 ]]; then return; fi
+ project_root_path=$(get_project_root_path)
+ # leave only part of absolute path that is after project root
+ local cmdpath=${PWD#$project_root_path/}
+ if [[ "$cmdpath" == "$(pwd)" ]] ; then
+
+ # if replacement didnt happen then we are in project root or higher
+ eval $1="."
+ else
+ eval $1="$cmdpath"
+ fi
+}
+
+# Removes invisible residue from the string that remains leftover sometimes
+# @param $1 string to cleanup
+# @param $2 variable that receives clean string
+clean_string ()
+{
+ cleaned=$(echo "$1" | sed -e 's/[^a-zA-Z0-9_-]$//')
+ eval $2=\'${cleaned}\'
+}
+
+# get mysql connection string
+# @param $1 gets result
+get_mysql_connect ()
+{
+ # get connection string and cleanup residue that is leftover
+ clean_string "$(_run drush sql-connect)" clean_res
+ eval $1=\'$clean_res\'
+}
+
+#----------------------- Basics check functions -----------------------------
+
+is_linux ()
+{
+ res=`uname | grep 'Linux'`
+ if [[ ! "$res" == '' ]]; then
+ return 0;
+ else
+ return 1;
+ fi
+}
+
+is_windows ()
+{
+ res=`uname | grep 'MINGW32_NT'`
+ if [[ ! "$res" == '' ]]; then
+ return 0;
+ else
+ return 1;
+ fi
+}
+
+# cheсks wether we are in boot2docker console
+is_boot2docker ()
+{
+ local res=`uname -a|grep 'boot2docker'`
+ if [[ ! "$res" == '' ]]; then
+ return 0;
+ else
+ return 1;
+ fi
+}
+
+# checks if binary exists and callable in PATH
+binary_found ()
+{
+ if [[ "$1" == "" ]]; then
+ return 1;
+ fi
+
+ local bpath=`which $1`
+
+ if [[ "$bpath" != "" ]] && [ -f `which $1` ]; then
+ return 0
+ else
+ echo "\"$1\" executable not found."
+ return 1
+ fi
+}
+
+# check that vagrant binary exists
+is_vagrant ()
+{
+ if is_linux ; then return 1; fi
+ if is_boot2docker ; then return 1; fi
+ binary_found 'vagrant';
+ return $?
+}
+
+# check that docker-compose binary exists
+is_docker_compose ()
+{
+ binary_found 'docker-compose';
+ return $?
+}
+
+# check that docker binary exists
+is_docker ()
+{
+ binary_found 'docker';
+ return $?
+}
+
+is_vagrantfile_absent ()
+{
+ vagrant_path=`upfind "Vagrantfile"`
+ if [[ "$vagrant_path" == "" ]] ; then
+ echo "dsh: Vagrantfile not found in your directory tree"
+ return 0
+ fi
+
+ return 1
+}
+
+# Checks if yml or vagrantfile (win, mac) is absent
+is_yml_absent ()
+{
+ yml_path=`yml_get_path`
+ if [[ "$yml_path" == "" ]] ; then
+ echo "dsh: docker-compose.yml not found in your directory tree"
+ return 0
+ fi
+
+ if is_linux ; then
+ return 1; # linux does not need vagrant
+ fi
+
+ vagrant_path=`upfind "Vagrantfile"`
+ if [[ "$vagrant_path" == "" ]] ; then
+ echo "dsh: Vagrantfile not found in your directory tree"
+ return 0
+ fi
+
+ #if we got here files are present
+ return 1
+}
+
+# Yes/no confirmation dialog with an optional message
+# @param $1 confirmation message
+_confirm ()
+{
+ # Skip checks if not a tty
+ if ! is_tty ; then return 0; fi
+
+ while true; do
+ read -p "$1 [y/n]: " answer
+ case $answer in
+ [Yy]|[Yy][Ee][Ss] )
+ break
+ ;;
+ [Nn]|[Nn][Oo] )
+ exit 1
+ ;;
+ * )
+ echo 'Please answer yes or no.'
+ esac
+ done
+}
+
+#------------------------------- Commands -----------------------------------
+# bring box up
+up ()
+{
+ if is_yml_absent ; then return 2; fi
+ if is_vagrant ; then
+ vagrant up
+ started=$?
+ if [ $started -eq 0 ] && is_docker_compose ; then
+ echo -e "${green}Starting containers...$NC"
+ cd `yml_get_path`
+ docker_compose up -d
+ fi
+ elif ( is_boot2docker || is_linux ) && is_docker_compose ; then
+ echo -e "${green}Starting containers...$NC"
+ cd `yml_get_path`
+ docker_compose up -d
+ fi
+}
+
+# stop box
+down ()
+{
+ if is_yml_absent ; then return 2; fi
+ if is_vagrant ; then
+ vagrant halt
+ elif ( is_boot2docker || is_linux ) && is_docker_compose ; then
+ echo -e "${yellow}Stopping containers...$NC"
+ cd `yml_get_path`
+ docker_compose stop
+ fi
+}
+
+# call 911
+restart ()
+{
+ if is_yml_absent ; then return 2; fi
+ if is_vagrant ; then
+ vagrant reload
+ started=$?
+ if [ $started -eq 0 ] && is_docker_compose ; then
+ echo -e "${green}Starting containers...$NC"
+ cd `yml_get_path`
+ docker_compose up -d
+ fi
+ elif ( is_boot2docker || is_linux ) && is_docker_compose ; then
+ echo -e "${green}Restarting containers...$NC"
+ cd `yml_get_path`
+ docker_compose up -d
+ fi
+}
+
+# output status of Drude if stopped or containers if started
+status ()
+{
+ local res
+ local compose_error
+ compose_error='Couldn'"'"'t connect to Docker daemon - you might need to run `boot2docker up`.'
+
+ if is_yml_absent ; then return 2; fi
+ cd `yml_get_path`
+ # check docker compose output to not be $compose_error
+ res=$(docker_compose ps 2>&1)
+ if [[ "$res" == "$compose_error" ]]; then
+ echo -e "${yellow}Drude is not running. Use 'dsh up' to start it$NC"
+ return 1
+ else
+ echo "${res}"
+ return 0
+ fi
+}
+
+# installation and updates
+
+install_prerequisites()
+{
+ echo -e "${green}Installing prerequisites...${NC}"
+ if is_windows ; then
+ local cwd=$(pwd)
+ cd ~
+ curl -fsS https://raw.githubusercontent.com/blinkreaction/boot2docker-vagrant/$B2D_BRANCH/presetup-git-bash.cmd -o presetup-git-bash.cmd
+ if [ ! $? -eq 0 ]; then
+ echo -e "${red}Could not get 'presetup-git-bash.cmd'. Please check your internet connection${NC}"
+ exit
+ fi
+ curl -fsS https://raw.githubusercontent.com/blinkreaction/boot2docker-vagrant/$B2D_BRANCH/presetup-git-bash.vbs -o presetup-git-bash.vbs
+ if [ ! $? -eq 0 ]; then
+ echo -e "${red}Could not get 'presetup-git-bash.vbs'. Please check your internet connection${NC}"
+ exit
+ fi
+ echo "Setup needs administrator privileges to contiue..."
+ cscript "presetup-git-bash.vbs"
+ cd $cwd
+ echo -e "${red}Please wait until tools installation finishes$NC"
+ read -p "Press any key when installation in another console window is done..."
+ elif is_linux ; then
+ echo -e "${red}dsh: prerequisites install is not automated on Linux yet.$NC"
+ _confirm "Do you already have them installed manually?"
+ else # mac
+ local presetup_script=$(curl -fsS https://raw.githubusercontent.com/blinkreaction/boot2docker-vagrant/$B2D_BRANCH/presetup-mac.sh)
+ if [ ! $? -eq 0 ]; then
+ echo -e "${red}Could not get 'presetup-mac.sh'. Please check your internet connection${NC}"
+ exit
+ fi
+ bash <(echo "$presetup_script")
+ fi
+}
+
+install_boot2docker()
+{
+ if is_linux ; then
+ return 0; # not needed on Linux
+ fi
+ echo -n "Downloading boot2docker install script..."
+
+ if is_windows ; then
+ curl -fsS https://raw.githubusercontent.com/blinkreaction/boot2docker-vagrant/$B2D_BRANCH/setup.sh -o ~/setup.sh
+ else
+ local b2d_setup_script=$(curl -fsS https://raw.githubusercontent.com/blinkreaction/boot2docker-vagrant/$B2D_BRANCH/setup.sh)
+ fi
+ if [ ! $? -eq 0 ]; then
+ echo -e "${red}Could not get 'setup.sh'. Please check your internet connection${NC}"
+ exit
+ fi
+
+ echo "done"
+ echo -e "${yellow}Running boot2docker install script...$NC"
+ if is_windows ; then
+ chmod +x ~/setup.sh
+ B2D_NO_AUTOSTART=1 ~/setup.sh
+ else
+ # calling bash interactively. B2D_NO_AUTOSTART prevents vagrant up after download
+ B2D_NO_AUTOSTART=1 bash <(echo "$b2d_setup_script")
+
+ # Configuring resolver
+ echo -e "${yellow}Configuring *.drude wildcard DNS resolution...$NC"
+ sudo mkdir -p /etc/resolver
+ echo -e "\n# .drude domain resolution\nnameserver 192.168.10.10" | sudo tee 1>/dev/null /etc/resolver/drude
+ fi
+}
+
+install_drude()
+{
+ docker_compose=$(curl -kfsS "https://raw.githubusercontent.com/blinkreaction/drude/$DRUDE_BRANCH/docker-compose.yml")
+ if [ ! $? -eq 0 ]; then
+ echo -e "${red}Could not get latest docker-compose.yml version.${NC}"
+ return 1
+ else
+ echo "$docker_compose" | tee "docker-compose.yml" >/dev/null
+ echo -e "${green}docker-compose.yml updated to the latest version.${NC}"
+ return 0
+ fi
+
+}
+
+install ()
+{
+ local cwd=$(pwd)
+ install_prerequisites
+ cd ..
+ _confirm "Install Vagrantfile to $(pwd)?"
+ install_boot2docker
+ cd $cwd
+ _confirm "Install docker-compose.yml to $(pwd)?"
+ install_drude
+ echo -e "${red}Please restart you terminal shell and then use 'dsh up' to start Drude$NC"
+}
+
+update ()
+{
+ if ! is_tty ; then
+ echo "'update' is not allowed in a non-interactive environment."
+ return 1
+ fi
+
+ if is_boot2docker ; then
+ echo -e "${red}dsh: You are running inside boot2docker$NC"
+ return 1
+ fi
+
+ if is_windows; then
+ echo -e "${red}dsh: Update is not automated on Windows yet$NC"
+ return 1
+ fi
+
+ local cwd=$(pwd)
+ # run boot2docker update only on Mac and Win
+ if ! is_linux ; then
+ echo -e "${green}Installing boot2docker update...${NC}"
+ # Make sure ~/.drude folder exists
+ mkdir -p $HOME/.drude
+
+ # get current version
+ local b2d_version_path=$HOME/.drude/b2d_version
+ local b2d_version=$(cat $b2d_version_path 2>/dev/null)
+ if [[ "$b2d_version" = "" ]]; then b2d_version="undetected version"; fi
+
+ # get new version
+ local b2d_new_version=$(curl -kfsS https://raw.githubusercontent.com/blinkreaction/boot2docker-vagrant/$B2D_BRANCH/VERSION)
+ if [ ! $? -eq 0 ]; then
+ echo -e "${red}Could not get latest boot2docker version. Please check your internet connection${NC}"
+ return 1
+ fi
+
+ # update boot2docker
+
+ local forced_update='';
+ local destroy_box=1;
+ if [[ "$1" == "-f" ]] || [[ "$2" == "-f" ]]; then forced_update="(forced update)"; fi
+ if [[ "$1" == "-n" ]] || [[ "$2" == "-n" ]]; then destroy_box=0; fi
+
+ if [[ ! "$b2d_new_version" == "$b2d_version" ]] || [ -n "$forced_update" ]; then
+ echo -e "boot2docker update available: ${green}$b2d_new_version $forced_update${NC} (you have $b2d_version)."
+ if [ $destroy_box -eq 1 ]; then
+ echo -e "${red}Update will destroy and re-create your Vagrant box${NC} (run with -n if you know re-creation can be skipped this time)"
+ _confirm "Please confirm."
+ else
+ echo -e "${green}Vagrant box will not be re-created${NC}"
+ fi
+
+ # don't download update if we can't locate Vagrantfile
+ if is_vagrantfile_absent ; then return 1; fi
+ cd $vagrant_path
+ echo "Vagrantfile is found in "$(pwd)
+
+ echo -n "Downloading boot2docker install script..."
+ local b2d_setup_script=$(curl -kfsS https://raw.githubusercontent.com/blinkreaction/boot2docker-vagrant/$B2D_BRANCH/setup.sh)
+ if [ ! $? -eq 0 ]; then
+ echo -e "${red}Could not get latest boot2docker version. Please check your internet connection${NC}"
+ exit
+ fi
+ echo "done"
+
+ echo "Creating backups..."
+ local timestamp=$(date +%s)
+ cp -v Vagrantfile $HOME/.drude/Vagrantfile.$timestamp && \
+ cp -v vagrant.yml $HOME/.drude/vagrant.yml.$timestamp
+ if [ ! $? -eq 0 ]; then
+ echo -e "${red}Unable to perform configuration backups. Aborting... ${NC}"
+ exit 1
+ fi
+
+ if [ $destroy_box -eq 1 ]; then
+ echo -e "${yellow}Destroying vagrant box...$NC"
+ vagrant destroy -f
+ fi
+
+ echo -e "${yellow}Running boot2docker install script...$NC"
+ # calling bash interactively
+ bash <(echo "$b2d_setup_script")
+
+ if [[ $? -ne 0 ]]; then
+ # don't continue in automatic mode if boot2docker update failed
+ return 1
+ else
+ # update recorded boot2docker version
+ echo $b2d_new_version > $b2d_version_path
+ echo -e "boot2docker ${green}$b2d_new_version${NC} was installed"
+ fi
+ else
+ echo "boot2docker $b2d_version you have is the latest version (-f to force update)"
+ fi
+ fi
+
+ # Update drude
+ cd $cwd # we need to bo back if we were installing boot2docker
+ if install_drude ; then
+ if [[ ! "$b2d_new_version" == "$b2d_version" ]] && [ $destroy_box -eq 1 ]; then
+ # if vagrant box was updated containers will be downloaded again
+ _confirm "Starting Vagrant box. Downloading and starting containers."
+ else
+ _confirm "Starting containers. If there's an update to container images it will be downloaded."
+ fi
+ up
+ else
+ echo -e "${red}Update interrupted. Could not get latest Drude version. Please check your internet connection${NC}"
+ return 1
+ fi
+}
+
+# start interactive bash in container
+_bash ()
+{
+ if ! is_tty ; then
+ echo "Interactive bash console in a non-interactive enveronment!? Nope, won't happen."
+ return 1
+ fi
+
+ if is_yml_absent ; then return 2; fi
+ cd `yml_get_path`
+ if is_docker && is_docker_compose ; then
+ local container_name
+ if [[ "$1" == "" ]]; then
+ container_name='cli'
+ else
+ container_name="$1"
+ fi
+ local container_id
+ clean_string $(docker_compose ps -q $container_name) container_id
+ docker exec -it $container_id bash -i
+ fi
+}
+
+# Run a command in container in project root
+# param $* command with it's params to run
+run ()
+{
+ if is_docker && is_docker_compose ; then
+ cd `yml_get_path`
+ clean_string $(docker_compose ps -q cli) container_id
+
+ if is_tty ; then
+ docker exec -it $container_id bash -ic "$*"
+ else
+ docker exec $container_id bash -c "$*"
+ fi
+ fi
+}
+
+# Run a command in container changing dir to the same folder
+# param $* command with it's params to run
+_run ()
+{
+ local COLS=$(tput cols) #get client window width
+ if is_yml_absent ; then return 2; fi
+ if is_docker && is_docker_compose ; then
+ local cd_path=''
+ cmdpath cd_path
+ run "cd $cd_path && export COLUMNS=$COLS && $*"
+ fi
+}
+
+# start interactive mysql shell
+mysql ()
+{
+ if is_yml_absent ; then return 2; fi
+ if ! get_drush_path; then return; fi
+ local sql_connect
+ get_mysql_connect sql_connect
+ _run $sql_connect" -A" # -A option to speed up mysql load
+}
+
+# Truncate db and import from sql dump
+# @param $1 filename of backup file. Should be inside project root
+mysql_import ()
+{
+ if is_yml_absent ; then return 2; fi
+ if ! get_drush_path; then return; fi
+
+ local confirm=1
+ if [[ "$1" == "-y" ]] || [[ "$2" == "-y" ]]; then
+ confirm=0
+ if [[ "$1" == "-y" ]]; then
+ shift #remove this param if -y was first
+ fi
+ fi
+
+ project_root=$(get_project_root_path)
+ local filename=`basename $1`
+ local filepath=`get_abs_path $1`
+ #substract project root path from filepath
+ local pathdiff=${filepath#$project_root}
+
+ if [[ $pathdiff == $filepath ]] ; then
+ # substraction did not succeed because file is outside project root
+ echo "\"$filename\" should be inside your project root folder"
+ return
+ fi
+
+ if [ $confirm -eq 1 ]; then
+ _confirm "[!] This will delete all current data in database 'drupal'."
+ fi
+
+
+ echo $(_drush sql-drop -y)
+ local sql_connect
+ get_mysql_connect sql_connect
+ echo "Importing $filename into database 'drupal'..."
+
+ # Use pv for interactive shells only.
+ if is_tty ; then
+ # Check if we have a gzipped dump and treat it differently.
+ if [[ $filename == *.gz ]]; then
+ file_size=$(gzip -l $1 | sed -n 2p | awk '{print $2}')
+ run "zcat ./$pathdiff/$filename | pv --size $file_size | "$sql_connect
+ else
+ run "pv ./$pathdiff/$filename | "$sql_connect
+ fi
+ else
+ # Check if we have a gzipped dump and treat it differently.
+ if [[ $filename == *.gz ]]; then
+ run "zcat ./$pathdiff/$filename | "$sql_connect
+ else
+ run "cat ./$pathdiff/$filename | "$sql_connect
+ fi
+ fi
+
+ # Check if import succeded or not and print results.
+ if [ $? -eq 0 ]; then echo "mysql-import finished"; else echo "mysql-import failed"; fi
+}
+
+# Shortcut to run drush cc ... on site
+# @param $1 type of cache to clean (all, css-js, ...)
+clear_cache ()
+{
+ if is_yml_absent ; then return 2; fi
+ if ! get_drush_path; then return; fi
+ local type='all'
+ if [[ ! "$1" == "" ]]; then
+ type=$1
+ fi
+ _run 'drush cc '$type
+}
+
+# shorthand for executing drush commands
+_drush ()
+{
+ if is_yml_absent ; then return 2; fi
+ if ! get_drush_path; then return; fi
+ _run 'drush '$*
+}
+
+# Run Behat tests (also install dependencies through Composer)
+# @param $* arguments and params passed to Behat
+behat ()
+{
+ if is_yml_absent ; then return 2; fi
+ local project_root_path=$(get_project_root_path)
+ if [[ ! -f "$project_root_path/tests/behat/behat.yml" ]]; then
+ echo "Missing behat.yml in tests/behat. Aborting..."
+ exit
+ fi
+ run 'cd tests/behat && composer install --prefer-source --no-interaction'
+ run 'cd tests/behat && bin/behat -p docker '$*
+}
+
+# Set up the project locally
+# Calls .drude/scripts/drude-init.sh script, so it must exist.
+init ()
+{
+ local project_root_path=$(get_project_root_path)
+ local init_script="$project_root_path/.drude/scripts/drude-init.sh"
+ if [[ -f $init_script ]]; then
+ exec $init_script
+ else
+ echo "Project init script is missing: $init_script"
+ exit -1
+ fi
+}
+
+# Reset containers (stop, remove, up)
+reset ()
+{
+ _confirm "[!] This will delete all project containers including the DB one. You will have to re-import DB.";
+
+ echo "Checking files..."
+ if is_yml_absent ; then return 2; fi
+ if is_vagrant ; then
+ echo "Starting vagrant vm..."
+ vagrant up
+ started=$?
+ if [ $started -eq 0 ] && is_docker_compose ; then
+ echo "Resetting containers..."
+ cd `yml_get_path`
+ docker_compose kill && docker_compose rm -v -f && docker_compose up -d
+ fi
+ elif ( is_boot2docker || is_linux ) && is_docker_compose ; then
+ echo "Resetting containers..."
+ cd `yml_get_path`
+ docker_compose kill && docker_compose rm -v -f && docker_compose up -d
+ fi
+}
+
+# command bindings
+argv="$@"
+case $1 in
+ up)
+ up
+ ;;
+ start)
+ up
+ ;;
+ stop)
+ down
+ ;;
+ down)
+ down
+ ;;
+ halt)
+ down
+ ;;
+ restart)
+ restart
+ ;;
+ reload)
+ restart
+ ;;
+ status)
+ status
+ ;;
+ install)
+ install
+ ;;
+ update)
+ shift
+ update $*
+ ;;
+ st)
+ status
+ ;;
+ ps)
+ status
+ ;;
+ bash)
+ shift
+ _bash $*
+ ;;
+ exec)
+ shift
+ _run $*
+ ;;
+ run)
+ shift
+ _run $*
+ ;;
+ mysql)
+ mysql
+ ;;
+ mysql-import)
+ mysql_import $2 $3
+ ;;
+ cc)
+ clear_cache $2
+ ;;
+ drush)
+ shift
+ _drush $*
+ ;;
+ behat)
+ shift
+ behat $*
+ ;;
+ init)
+ shift
+ init
+ ;;
+ reset)
+ shift
+ reset
+ ;;
+ *)
+ help
+ exit 0
+esac
diff --git a/.docker/docs/behat.md b/.docker/docs/behat.md
new file mode 100644
index 0000000..7e256f3
--- /dev/null
+++ b/.docker/docs/behat.md
@@ -0,0 +1,47 @@
+# Using Behat
+
+## Expected folder structure
+
+Drude expects your Behat tests to be in `tests/behat` folder of the project repo.
+
+> tests/behat
+> \_ bin/behat
+> \_ behat.yml
+> \_ composer.json
+> \_ composer.lock
+
+See [drude-testing](https://github.com/blinkreaction/drude-testing) repo for a good working example. This repo is used for automated tests of Drude builds.
+
+## Docker profile example
+
+Assuming you are using [Behat Drupal Extension](https://github.com/jhedstrom/drupalextension) add the following profile in your `behat.yml` file:
+
+```yml
+# Docker profile.
+# For use inside the CLI container in Drude.
+docker:
+ extensions:
+ Behat\MinkExtension:
+ # URL of the site when accessed inside Drude.
+ base_url: http://web
+ selenium2:
+ wd_host: http://browser:4444/wd/hub
+ # Stick with chrome by default. It's 2x faster than firefox or phantomjs (your results may vary).
+ browser_name: chrome
+ Drupal\DrupalExtension:
+ drupal:
+ # Site docroot inside Drude.
+ drupal_root: /var/www/docroot
+ drush:
+ # Site docroot inside Drude.
+ root: /var/www/docroot
+```
+This will configure Behat for use with Drude.
+
+## Running tests
+
+Tests can be launched with `dsh` (Drude Shell):
+
+ dsh behat
+
+This will download composer dependencies and run behat with the docker profile.
diff --git a/.docker/docs/db-sandbox.md b/.docker/docs/db-sandbox.md
new file mode 100644
index 0000000..33f6310
--- /dev/null
+++ b/.docker/docs/db-sandbox.md
@@ -0,0 +1,104 @@
+# DB sandbox mode
+
+In certain cases you may want to have a DB sandbox and mess around with the DB, then roll it back.
+Normally this can be achieved by backing up the DB and restoring it from the created DB dump.
+With large databases (over 500MB) this operation can take a considerable amount of time (10+ minutes).
+
+With Docker we can sandbox the DB by creating a reusable docker image from the DB container.
+Spinning up such image takes less time than re-importing the DB with MySQL.
+
+For example, a 2.6GB DB dump file takes 14 minutes to import with MySQL. (Re)launching a docker container from an image with that same imported dump takes only 2 minutes. **Your results may obviously vary.**
+
+This is an advanced used case, so make sure you have a backup of the database before proceeding.
+Follow instructions below to get started.
+
+## How it works
+
+In Docker terminology, a read-only [Layer](https://docs.docker.com/terms/layer/#layer) is called an image. In traditional setup you use mysql as Base Docker Image for your DataBase container node.
+
+:page_facing_up: docker-compose.yml
+```yml
+# DB node
+db:
+ image: blinkreaction/mysql:5.5
+ volumes:
+ ...
+ # Permanent DB data storage
+ - /var/lib/mysql
+ ```
+
+An image never changes. Thanks to [Union File System](https://docs.docker.com/terms/layer/#union-file-system) when process wants to write a file to an image, the Docker creates a copy of that file in Writable Container (the top-most writeable layer).
+
+However all changes to containers are not permanent hence in traditional setup you have the `/var/lib/mysql` external volume to save them permanently outside of `db` container.
+
+
+
+For sandboxed DB you remove this permanent storage, import your database into Writeable Container's memory and create a new Docker Image from your container with `docker commit` command. New image will include Base Image plus all in-memory changes made i.e. your DataBase snapshot. It is then used as a Base Image for your DB container.
+
+:page_facing_up: docker-compose.yml
+```yml
+# DB node
+db:
+ image: mysql_with_my_database:snapshot1
+ volumes:
+ ...
+ # Permanent DB data storage (turned off)
+ # - /var/lib/mysql
+ ```
+
+
+
+Now you can do any changes to database you want but each time after the container is restarted all changes will be lost (as it doesn't have external persistant storage) and you will be back to your Base Image `mysql_with_my_database:snapshot1`.
+
+## Steps
+
+1. Create a DB dump
+2. Stop and remove running containers
+
+ `docker-compose stop && docker-compose rm --force`
+
+3. Comment out the `/var/lib/mysql` volume definition for the **db** service in `docker-compose.yml`
+4. Restart containers
+
+ `docker-compose up -d`
+
+5. Import the DB dump you created in step 1.
+6. Stop and [commit](https://docs.docker.com/reference/commandline/cli/#commit) the **db** service container (this will turn the container into a reusable docker image)
+
+ `docker stop $(docker-compose ps -q db) && docker commit $(docker-compose ps -q db) `
+
+ Replace `` with any meaningful tag you'd like to use for the image. E.g. `db_backup` or `dbdata/myproject:snapshot1`
+
+7. Edit the image definition for the *db* service in `docker-compose.yml` and replace it with the selected tag. E.g.:
+
+ `image: dbdata/myproject:snapshot1`
+
+8. Restart containers
+
+ `docker-compose up -d`
+
+You will now have a sandboxed DB container, which defaults to the DB snapshot you created in step 1 every time the db container is restarted.
+
+## Precaution
+
+In case you have a large database it is not recommended to commit the container that already runs on snaphot Base Image (i.e. creating snapshot on top of snapshot). Every docker image holds all parent images + in-memory changes inside it. Thus, **with every commit the size of the resulting image will increase by full size of the DB, not its delta.**
+
+In case of small database though it is ok to do that. But please keep in mind there's a limitation on depth of layers which currently equals 120.
+
+## Disabling the sandbox mode
+
+You will need a DB dump to revert to.
+Either use the one created before enabling the sandbox mode or take another one while the **db** service contained is still up.
+
+1. Stop and remove running containers
+
+ `docker-compose stop && docker-compose rm --force`
+
+2. Revert the changes done to the **db** service in `docker-compose.yml`
+3. Restart containers
+
+ `docker-compose up -d`
+
+5. Import the DB dump.
+
+Now the **db** service container is using a persistent data storage volume (`/var/lib/mysql`).
diff --git a/.docker/docs/drupal-settings.md b/.docker/docs/drupal-settings.md
new file mode 100644
index 0000000..13638dd
--- /dev/null
+++ b/.docker/docs/drupal-settings.md
@@ -0,0 +1,81 @@
+# Drupal settings
+
+
+# DB connection settings
+
+Containers do not have static IP addresses assigned. DB connection settings can be obtained from the environment variables.
+
+Below are sample settings for Drupal 7 and Drupal8.
+If you change the DB node name in `docker-compose.yml` (e.g. `mysql` instead of `db`) then this has to be updated, since variable names will change as well.
+
+**Drupal 7**
+
+```php
+$databases = array (
+ 'default' =>
+ array (
+ 'default' =>
+ array (
+ 'database' => getenv('DB_1_ENV_MYSQL_DATABASE'),
+ 'username' => getenv('DB_1_ENV_MYSQL_USER'),
+ 'password' => getenv('DB_1_ENV_MYSQL_PASSWORD'),
+ 'host' => getenv('DB_1_PORT_3306_TCP_ADDR'),
+ 'port' => '',
+ 'driver' => 'mysql',
+ 'prefix' => '',
+ ),
+ ),
+);
+
+```
+
+**Drupal 8**
+
+```php
+$databases['default']['default'] = array (
+ 'database' => getenv('DB_1_ENV_MYSQL_DATABASE'),
+ 'username' => getenv('DB_1_ENV_MYSQL_USER'),
+ 'password' => getenv('DB_1_ENV_MYSQL_PASSWORD'),
+ 'prefix' => '',
+ 'host' => getenv('DB_1_PORT_3306_TCP_ADDR'),
+ 'port' => '3306',
+ 'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql',
+ 'driver' => 'mysql',
+);
+```
+
+
+## File permissions fix
+
+With NFS mounts Drupal may complain about files directory not being writable. This is a "false-negative" however can be annoying and break certain things. For a workaround add the following to your setting.php file.
+
+**Drupal 7**
+
+```php
+# Workaround for permission issues with NFS shares in Vagrant
+$conf['file_chmod_directory'] = 0777;
+$conf['file_chmod_file'] = 0666;
+```
+
+You may also have to reset permissions on the existing files folder. The following command will recursively set folders to 777 (rwx) and files to 666 (rw)
+
+```bash
+chmod -R +rwX files
+```
+
+
+## Memcache settings
+
+1. Uncomment the **memcached** service definition section in [`docker-compose.yml`](docker-compose.yml) to start using memcached.
+
+2. Add the following lines to `settings.php` to point Drupal to the memcached node. Replace `` with path to [memcache module](https://www.drupal.org/project/memcache) in your project. E.g. `sites/all/modules/contrib/memcache`
+
+```php
+// Memcache
+$conf['cache_backends'][] = '/memcache.inc';
+$conf['cache_default_class'] = 'MemCacheDrupal';
+$conf['cache_class_cache_form'] = 'DrupalDatabaseCache';
+$conf['memcache_servers'] = array(
+ 'memcached:11211' => 'default',
+);
+```
diff --git a/.docker/docs/img/unionfs-container.png b/.docker/docs/img/unionfs-container.png
new file mode 100644
index 0000000000000000000000000000000000000000..b4ce289fe98499feb403141fa3000b833503685e
GIT binary patch
literal 149078
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 |