first import
This commit is contained in:
189
sites/all/modules/features/API.txt
Normal file
189
sites/all/modules/features/API.txt
Normal file
@@ -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
|
221
sites/all/modules/features/CHANGELOG.txt
Normal file
221
sites/all/modules/features/CHANGELOG.txt
Normal file
@@ -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.
|
339
sites/all/modules/features/LICENSE.txt
Normal file
339
sites/all/modules/features/LICENSE.txt
Normal file
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
218
sites/all/modules/features/README.txt
Normal file
218
sites/all/modules/features/README.txt
Normal file
@@ -0,0 +1,218 @@
|
||||
|
||||
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
|
||||
|
||||
|
||||
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].
|
||||
|
||||
|
||||
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'.
|
||||
|
||||
- `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)
|
1436
sites/all/modules/features/features.admin.inc
Normal file
1436
sites/all/modules/features/features.admin.inc
Normal file
File diff suppressed because it is too large
Load Diff
476
sites/all/modules/features/features.api.php
Normal file
476
sites/all/modules/features/features.api.php
Normal file
@@ -0,0 +1,476 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Main info hook that features uses to determine what components are provided
|
||||
* by the implementing module.
|
||||
*
|
||||
* @return array
|
||||
* An array of components, keyed by the component name. Each component can
|
||||
* define several keys:
|
||||
*
|
||||
* 'file': Optional path to a file to include which contains the rest
|
||||
* of the features API hooks for this module.
|
||||
*
|
||||
* 'default_hook': The defaults hook for your component that is called
|
||||
* when the cache of default components is generated. Examples include
|
||||
* hook_views_default_views() or hook_context_default_contexts().
|
||||
*
|
||||
* 'default_file': The file-writing behavior to use when exporting this
|
||||
* component. May be one of 3 constant values:
|
||||
*
|
||||
* FEATURES_DEFAULTS_INCLUDED_COMMON: write hooks/components to
|
||||
* `.features.inc` with other components. This is the default behavior
|
||||
* if this key is not defined.
|
||||
*
|
||||
* FEATURES_DEFAULTS_INCLUDED: write hooks/components to a component-
|
||||
* specific include named automatically by features.
|
||||
*
|
||||
* FEATURES_DEFAULTS_CUSTOM: write hooks/components to a component-
|
||||
* specific include with a custom name provided. If your module provides
|
||||
* large amounts of code that should not be parsed often (only on specific
|
||||
* cache clears/rebuilds, for example) you should use the 2nd or 3rd
|
||||
* options to split your component into its own include.
|
||||
*
|
||||
* 'default_filename': The filename to use when 'default_file' is set to
|
||||
* FEATURES_DEFAULTS_CUSTOM.
|
||||
*
|
||||
* 'feature_source': Boolean value for whether this component should be
|
||||
* offered as an option on the initial feature creation form.
|
||||
*
|
||||
* 'base': Optional. An alternative base key to use when calling features
|
||||
* hooks for this component. Can be used for features component types that
|
||||
* are declared "dynamically" or are part of a family of components.
|
||||
*
|
||||
* 'alter_type': What type of alter hook this hook uses. 'normal' is called
|
||||
* after the main hook is called. 'inline' is embeded within the default hook
|
||||
* and may not be implemented by some default hooks.
|
||||
* 'none' is no alter hook exists. Defaults to 'normal'
|
||||
*
|
||||
* 'alter_hook': What the name of the alter hook for this component is.
|
||||
* Do not include the '_alter' part. Defaults to 'default_hook'.
|
||||
*/
|
||||
function hook_features_api() {
|
||||
return array(
|
||||
'mycomponent' => 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 ot 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 ot 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 ot 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.
|
||||
*/
|
||||
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 ot 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 ot 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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($data, 'my-node-type')) {
|
||||
$pipe['dependencies'][] = 'mymodule';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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 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) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
561
sites/all/modules/features/features.css
Normal file
561
sites/all/modules/features/features.css
Normal file
@@ -0,0 +1,561 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
815
sites/all/modules/features/features.drush.inc
Normal file
815
sites/all/modules/features/features.drush.inc
Normal file
@@ -0,0 +1,815 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Features module drush integration.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_drush_command().
|
||||
*
|
||||
* @return
|
||||
* An associative array describing your command(s).
|
||||
*
|
||||
* @see drush_parse_command()
|
||||
*/
|
||||
function features_drush_command() {
|
||||
$items = array();
|
||||
|
||||
$items['features-list'] = array(
|
||||
'description' => "List all the available features for your site.",
|
||||
'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 'sites/all/modules'",
|
||||
'version-set' => "Specify a version number for the feature.",
|
||||
'version-increment' => "Increment the feature's version number.",
|
||||
),
|
||||
'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.',
|
||||
),
|
||||
),
|
||||
'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.',
|
||||
),
|
||||
'drupal dependencies' => array('features', 'diff'),
|
||||
'aliases' => array('fd'),
|
||||
);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_drush_help().
|
||||
*/
|
||||
function features_drush_help($section) {
|
||||
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 'sites/all/modules'. 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-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() {
|
||||
module_load_include('inc', 'features', 'features.export');
|
||||
$rows = array(array(dt('Name'), dt('Feature'), dt('Status'), dt('Version'), dt('State')));
|
||||
foreach (features_get_features(NULL, TRUE) 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;
|
||||
}
|
||||
$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();
|
||||
// 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;
|
||||
}
|
||||
|
||||
$filtered_components = _drush_features_component_filter($components, $args, $options);
|
||||
if ($filtered_components){
|
||||
_drush_features_component_print($filtered_components);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = sizeof($components_map[$source][$name]) > 0;
|
||||
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();
|
||||
list($source_pattern, $component_pattern) = explode(':', $pattern, 2);
|
||||
// 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 (!is_array($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 = sizeof($components_map[$source][$name]) > 0;
|
||||
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) {
|
||||
$rows = array(array(dt('Available sources')));
|
||||
foreach ($filtered_components['components'] as $source => $components) {
|
||||
foreach ($components as $name => $value) {
|
||||
$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(
|
||||
'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'), 'sites/all/modules');
|
||||
$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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
else if ($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;
|
||||
}
|
||||
}
|
||||
drush_print(dt('The following modules will be updated: !modules', array('!modules' => implode(', ', $features_to_update))));
|
||||
if (drush_confirm(dt('Do you really want to continue?'))) {
|
||||
foreach ($features_to_update as $module_name) {
|
||||
drush_invoke_process(drush_sitealias_get_record('@self'), 'features-update', array($module_name));
|
||||
}
|
||||
}
|
||||
else {
|
||||
drush_die('Aborting.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
if ($root) {
|
||||
$destination = drush_get_option(array('destination'), 'sites/all/modules');
|
||||
$directory = isset($directory) ? $directory : $destination . '/' . $module_name;
|
||||
if (is_dir($directory)) {
|
||||
drush_print(dt('Module appears to already exist in !dir', array('!dir' => $directory)));
|
||||
if (!drush_confirm(dt('Do you really want to continue?'))) {
|
||||
drush_die('Aborting.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
drush_op('mkdir', $directory);
|
||||
}
|
||||
if (is_dir($directory)) {
|
||||
drupal_flush_all_caches();
|
||||
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<core>\d+\.x)-(?P<major>\d+)\.(?P<patch>\d+)-?(?P<extra>\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;
|
||||
}
|
||||
else if ($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)) {
|
||||
++$version_minor;
|
||||
}
|
||||
array_push($version_explode, $version_minor);
|
||||
// Rebuild version string.
|
||||
$version = implode('.', $version_explode);
|
||||
$export['version'] = $version;
|
||||
}
|
||||
$files = features_export_render($export, $module_name, TRUE);
|
||||
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'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
|
||||
// Parse list of arguments.
|
||||
$modules = array();
|
||||
foreach ($args as $arg) {
|
||||
list($module, $component) = explode('.', $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) {
|
||||
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) {
|
||||
if (in_array($state, array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW, FEATURES_REBUILDABLE)) && 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) {
|
||||
if (drush_confirm(dt('Do you really want to revert @component?', array('@component' => $component)))) {
|
||||
features_revert(array($module => array($component)));
|
||||
drush_log(dt('Reverted @component.', array('@component' => $component)), 'ok');
|
||||
}
|
||||
else {
|
||||
drush_log(dt('Skipping @component.', array('@component' => $component)), 'ok');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ($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) {
|
||||
drush_print(dt('The following modules will be reverted: !modules', array('!modules' => implode(', ', $features_to_revert))));
|
||||
if (drush_confirm(dt('Do you really want to continue?'))) {
|
||||
foreach ($features_to_revert as $module) {
|
||||
drush_invoke_process(drush_sitealias_get_record('@self'), 'features-revert', array($module), array('force' => $force, '#integrate' => TRUE));
|
||||
}
|
||||
}
|
||||
else {
|
||||
return drush_user_abort('Aborting.');
|
||||
}
|
||||
}
|
||||
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];
|
||||
$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_process(drush_sitealias_get_record('@self'), 'dl', array('diff'), array('#integrate' => TRUE))) {
|
||||
return drush_set_error(dt('Diff module could not be downloaded.'));
|
||||
}
|
||||
|
||||
if (!drush_invoke_process(drush_sitealias_get_record('@self'), 'en', array('diff'), array('#integrate' => TRUE))) {
|
||||
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');
|
||||
}
|
||||
|
||||
$formatter = new DiffFormatter();
|
||||
$formatter->leading_context_lines = 2;
|
||||
$formatter->trailing_context_lines = 2;
|
||||
$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();
|
||||
|
||||
foreach ($overrides as $component => $items) {
|
||||
$diff = new Diff(explode("\n", $items['default']), explode("\n", $items['normal']));
|
||||
drush_print();
|
||||
drush_print(dt("Component: !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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
957
sites/all/modules/features/features.export.inc
Normal file
957
sites/all/modules/features/features.export.inc
Normal file
@@ -0,0 +1,957 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @param $info - feature info array
|
||||
* @param $module_name
|
||||
* @return fully populated export array
|
||||
*/
|
||||
function features_populate($info, $module_name) {
|
||||
// Sanitize items.
|
||||
$items = !empty($info['features']) ? array_filter($info['features']) : array();
|
||||
$items['dependencies'] = !empty($info['dependencies']) ? drupal_map_assoc(array_filter($info['dependencies'])) : array();
|
||||
|
||||
// Populate stub
|
||||
$stub = array('features' => 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) {
|
||||
static $processed = array();
|
||||
features_include();
|
||||
if ($reset) {
|
||||
$processed = array();
|
||||
}
|
||||
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.
|
||||
*/
|
||||
function features_export_prepare($export, $module_name, $reset = FALSE) {
|
||||
$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 = $existing ? $existing->info : array('core' => '7.x', 'package' => 'Features');
|
||||
$export = array_merge($defaults, $export);
|
||||
|
||||
// Cleanup info array
|
||||
foreach ($export['features'] as $component => $data) {
|
||||
$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', 'php', 'version', 'project', 'dependencies'), $copy_list) as $item) {
|
||||
if (isset($export[$item])) {
|
||||
$standard_info[$item] = $export[$item];
|
||||
}
|
||||
}
|
||||
|
||||
$export = array_diff_assoc($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();
|
||||
|
||||
// Group component code into their respective files
|
||||
foreach ($component_hooks as $component => $hooks) {
|
||||
$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) {
|
||||
$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) {
|
||||
$code[$filename] = "<?php\n/**\n * @file\n * {$module_name}.{$filename}.inc\n */\n\n". implode("\n\n", $contents) ."\n";
|
||||
}
|
||||
|
||||
// Generate info file output
|
||||
$export = features_export_prepare($export, $module_name, $reset);
|
||||
$code['info'] = features_export_info($export);
|
||||
|
||||
// Used to create or manipulate the generated .module for features.inc.
|
||||
$modulefile_features_inc = "<?php\n/**\n * @file\n * Code for the {$export['name']} feature.\n */\n\ninclude_once '{$module_name}.features.inc';\n";
|
||||
$modulefile_blank = "<?php\n/**\n * @file\n * Drupal needs this blank file.\n */\n";
|
||||
|
||||
// Prepare the module
|
||||
// If module exists, let it be and include it in the files
|
||||
if ($existing = features_get_modules($module_name, TRUE)) {
|
||||
$code['module'] = file_get_contents($existing->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 <?php\n, just add a warning.
|
||||
if (strpos($code['module'], "<?php\n") !== 0) {
|
||||
features_log(t('@module does not appear to include the @include file.', array('@module' => "{$module_name}.module", '@include' => "{$module_name}.features.inc")), 'warning');
|
||||
}
|
||||
else {
|
||||
// Remove the old message if it exists, else just remove the <?php
|
||||
$length = strpos($code['module'], $modulefile_blank) === 0 ? strlen($modulefile_blank) : 6;
|
||||
$code['module'] = $modulefile_features_inc . substr($code['module'], $length);
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated files. Display a message for any of these files letting the
|
||||
// user know that they may be removed.
|
||||
$deprecated = array(
|
||||
"{$module_name}.defaults",
|
||||
"{$module_name}.features.views",
|
||||
"{$module_name}.features.node"
|
||||
);
|
||||
foreach (file_scan_directory(drupal_get_path('module', $module_name), '/.*/') as $file) {
|
||||
if (in_array($file->name, $deprecated, 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'] = "<?php\n\n// This file is deprecated and can be removed.\n// Please remove include_once('{$module_name}.features.inc') in {$module_name}.module as well.\n";
|
||||
}
|
||||
else {
|
||||
$code['features'] = "<?php\n\n// This file is deprecated and can be removed.\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add a stub module to include the defaults
|
||||
else if (!empty($code['features'])) {
|
||||
$code['module'] = $modulefile_features_inc;
|
||||
}
|
||||
else {
|
||||
$code['module'] = $modulefile_blank;
|
||||
}
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect differences between DB and code components of a feature.
|
||||
*/
|
||||
function features_detect_overrides($module) {
|
||||
static $cache;
|
||||
if (!isset($cache)) {
|
||||
$cache = array();
|
||||
}
|
||||
if (!isset($cache[$module->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);
|
||||
|
||||
$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);
|
||||
_features_sanitize($default);
|
||||
|
||||
$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) {
|
||||
if (is_object($var)) {
|
||||
$output = method_exists($var, 'export') ? $var->export() : features_var_export((array) $var, '', FALSE);
|
||||
}
|
||||
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) . ",\n";
|
||||
}
|
||||
$output .= ')';
|
||||
}
|
||||
}
|
||||
else if (is_bool($var)) {
|
||||
$output = $var ? 'TRUE' : 'FALSE';
|
||||
}
|
||||
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)) {
|
||||
$objects = (array) $objects;
|
||||
_features_sanitize($objects);
|
||||
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) {
|
||||
static $cache;
|
||||
if (!isset($cache) || $reset) {
|
||||
$cache = 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'], '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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get defaults for a given module/component pair.
|
||||
*/
|
||||
function features_get_default($component, $module_name = NULL, $alter = TRUE, $reset = FALSE) {
|
||||
static $cache = 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) {
|
||||
static $map = 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) {
|
||||
static $cache;
|
||||
if (!isset($cache) || $reset) {
|
||||
$cache = array();
|
||||
}
|
||||
|
||||
$features = !empty($features) ? $features : array_keys(features_get_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)) {
|
||||
foreach ($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);
|
||||
}
|
||||
|
||||
/**
|
||||
* "Sanitizes" an array recursively, performing two key operations:
|
||||
* - Sort an array by its keys (assoc) or values (non-assoc)
|
||||
* - Remove any null or empty values for associative arrays (array_filter()).
|
||||
*/
|
||||
function _features_sanitize(&$array) {
|
||||
if (is_array($array)) {
|
||||
if (_features_is_assoc($array)) {
|
||||
ksort($array, SORT_STRING);
|
||||
$array = array_filter($array);
|
||||
}
|
||||
else {
|
||||
sort($array);
|
||||
}
|
||||
foreach ($array as $k => $v) {
|
||||
if (is_array($v)) {
|
||||
_features_sanitize($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));
|
||||
}
|
12
sites/all/modules/features/features.info
Normal file
12
sites/all/modules/features/features.info
Normal file
@@ -0,0 +1,12 @@
|
||||
name = "Features"
|
||||
description = "Provides feature management for Drupal."
|
||||
core = 7.x
|
||||
package = "Features"
|
||||
files[] = tests/features.test
|
||||
|
||||
; Information added by drupal.org packaging script on 2012-12-08
|
||||
version = "7.x-2.0-beta1+5-dev"
|
||||
core = "7.x"
|
||||
project = "features"
|
||||
datestamp = "1354928180"
|
||||
|
119
sites/all/modules/features/features.install
Normal file
119
sites/all/modules/features/features.install
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the features module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function features_install() {
|
||||
_features_install_menu();
|
||||
db_update('system')
|
||||
->fields(array('weight' => 20))
|
||||
->condition('name', 'features')
|
||||
->condition('type', 'module')
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_uninstall().
|
||||
*/
|
||||
function features_uninstall() {
|
||||
variable_del('features_codecache');
|
||||
variable_del('features_semaphore');
|
||||
variable_del('features_ignored_orphans');
|
||||
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();
|
||||
}
|
437
sites/all/modules/features/features.js
Normal file
437
sites/all/modules/features/features.js
Normal file
@@ -0,0 +1,437 @@
|
||||
/**
|
||||
* 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 <td>'s parent (<tr>) will be sorted instead
|
||||
* of the <td> 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');
|
||||
});
|
||||
|
||||
// Export form machine-readable JS
|
||||
$('.feature-name:not(.processed)', context).each(function() {
|
||||
$('.feature-name')
|
||||
.addClass('processed')
|
||||
.after(' <small class="feature-module-name-suffix"> </small>');
|
||||
if ($('.feature-module-name').val() === $('.feature-name').val().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/_+/g, '_') || $('.feature-module-name').val() === '') {
|
||||
$('.feature-module-name').parents('.form-item').hide();
|
||||
$('.feature-name').bind('keyup change', function() {
|
||||
var machine = $(this).val().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/_+/g, '_');
|
||||
if (machine !== '_' && machine !== '') {
|
||||
$('.feature-module-name').val(machine);
|
||||
$('.feature-module-name-suffix').empty().append(' Machine name: ' + machine + ' [').append($('<a href="#">'+ Drupal.t('Edit') +'</a>').click(function() {
|
||||
$('.feature-module-name').parents('.form-item').show();
|
||||
$('.feature-module-name-suffix').hide();
|
||||
$('.feature-name').unbind('keyup');
|
||||
return false;
|
||||
})).append(']');
|
||||
}
|
||||
else {
|
||||
$('.feature-module-name').val(machine);
|
||||
$('.feature-module-name-suffix').text('');
|
||||
}
|
||||
});
|
||||
$('.feature-name').keyup();
|
||||
}
|
||||
});
|
||||
|
||||
//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(/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/);
|
||||
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 moveCheckbox(item, section, value) {
|
||||
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]', context).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 .component-select label', context).each(function() {
|
||||
// expand parent fieldset
|
||||
var section = '';
|
||||
$(this).parents('fieldset.features-export-component').each(function() {
|
||||
section = $(this).attr('id');
|
||||
currentState[section] = !($(this).hasClass('collapsed'));
|
||||
if (!(section in newState)) {
|
||||
newState[section] = false;
|
||||
}
|
||||
});
|
||||
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);
|
||||
|
||||
|
956
sites/all/modules/features/features.module
Normal file
956
sites/all/modules/features/features.module
Normal file
@@ -0,0 +1,956 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Module file for the features module, which 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.
|
||||
*/
|
||||
|
||||
define('FEATURES_API', '1');
|
||||
|
||||
define('FEATURES_MODULE_ENABLED', 1);
|
||||
define('FEATURES_MODULE_DISABLED', 0);
|
||||
define('FEATURES_MODULE_MISSING', -1);
|
||||
define('FEATURES_MODULE_CONFLICT', 2);
|
||||
|
||||
define('FEATURES_REBUILDABLE', -1);
|
||||
define('FEATURES_DEFAULT', 0);
|
||||
define('FEATURES_OVERRIDDEN', 1);
|
||||
define('FEATURES_NEEDS_REVIEW', 2);
|
||||
define('FEATURES_REBUILDING', 3);
|
||||
define('FEATURES_CONFLICT', 4);
|
||||
define('FEATURES_DISABLED', 5);
|
||||
define('FEATURES_CHECKING', 6);
|
||||
define('FEATURES_ALTER_TYPE_NORMAL', 'normal');
|
||||
define('FEATURES_ALTER_TYPE_INLINE', 'inline');
|
||||
define('FEATURES_ALTER_TYPE_NONE', 'none');
|
||||
|
||||
// Duration of rebuild semaphore: 10 minutes.
|
||||
define('FEATURES_SEMAPHORE_TIMEOUT', 10 * 60);
|
||||
|
||||
/**
|
||||
* Components with this 'default_file' flag will have exports written to the
|
||||
* common defaults file 'MODULENAME.features.inc'. This is the default
|
||||
* behavior.
|
||||
*/
|
||||
define('FEATURES_DEFAULTS_INCLUDED_COMMON', 0);
|
||||
|
||||
/**
|
||||
* Components with this 'default_file' flag will have exports written to a
|
||||
* defaults based on the component name like 'MODULENAME.features.COMPONENT-NAME.inc'.
|
||||
* Any callers to this component's defaults hook must call
|
||||
* features_include_defaults('component') in order to include this file.
|
||||
*/
|
||||
define('FEATURES_DEFAULTS_INCLUDED', 1);
|
||||
|
||||
/**
|
||||
* Components with this 'default_file' flag must specify a filename for their
|
||||
* exports. Additionally a stub will NOT be written to 'MODULENAME.features.inc'
|
||||
* allowing the file to be included directly by the implementing module.
|
||||
*/
|
||||
define('FEATURES_DEFAULTS_CUSTOM', 2);
|
||||
|
||||
/**
|
||||
* Components with this 'duplicates' flag may not have multiple features provide the
|
||||
* same component key in their info files. This is the default behavior.
|
||||
*/
|
||||
define('FEATURES_DUPLICATES_CONFLICT', 0);
|
||||
|
||||
/**
|
||||
* Components with this 'duplicates' flag are allowed to have multiple features
|
||||
* provide the same component key in their info files.
|
||||
*/
|
||||
define('FEATURES_DUPLICATES_ALLOWED', 1);
|
||||
|
||||
/**
|
||||
* Implements hook_menu().
|
||||
*/
|
||||
function features_menu() {
|
||||
$items = array();
|
||||
$items['admin/structure/features'] = array(
|
||||
'title' => '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' => 'Detect and disable any orphaned feature dependencies.',
|
||||
'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/%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/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_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() {
|
||||
features_rebuild();
|
||||
// Don't flush the modules cache during installation, for performance reasons.
|
||||
if (variable_get('install_task') == 'done') {
|
||||
features_get_modules(NULL, TRUE);
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form().
|
||||
*/
|
||||
function features_form($node, $form_state) {
|
||||
return node_content_form($node, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implemenation of hook_node_access()
|
||||
*/
|
||||
function features_node_access($node, $op, $account) {
|
||||
return node_node_access($node, $op, $account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_permission().
|
||||
*/
|
||||
function features_permission() {
|
||||
return array(
|
||||
'administer features' => array(
|
||||
'title' => t('Administer features'),
|
||||
'description' => t('Perform administration tasks on features.'),
|
||||
),
|
||||
'manage features' => array(
|
||||
'title' => t('Manage features'),
|
||||
'description' => t('View, enable and disable features.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)) : '<pre>'. check_plain($output) .'</pre>';
|
||||
case 'admin/build/features':
|
||||
return '<p>'. 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.') .'</p>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
// 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)) {
|
||||
_features_restore('enable', $items);
|
||||
// Rebuild the list of features includes.
|
||||
features_include(TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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', '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 $included = array();
|
||||
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]) && (!isset($included[$component]) || $reset)) {
|
||||
$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}";
|
||||
module_load_include('inc', $feature->name, "{$feature->name}.$filename");
|
||||
}
|
||||
}
|
||||
$included[$component] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ommitted,
|
||||
* 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'))) {
|
||||
$components = $cache->data;
|
||||
}
|
||||
else {
|
||||
$components = module_invoke_all('features_api');
|
||||
drupal_alter('features_api', $components);
|
||||
cache_set('features_api', $components);
|
||||
}
|
||||
|
||||
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) {
|
||||
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');
|
||||
}
|
||||
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) {
|
||||
// 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']);
|
||||
}
|
||||
$data['feature'][$row->name] = $row;
|
||||
}
|
||||
$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 = new stdClass();
|
||||
$cache->data = $data;
|
||||
}
|
||||
if (!empty($name)) {
|
||||
return !empty($cache->data[$type][$name]) ? clone $cache->data[$type][$name] : array();
|
||||
}
|
||||
return !empty($cache->data[$type]) ? $cache->data[$type] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)) {
|
||||
// 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;
|
||||
}
|
||||
else if (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;
|
||||
}
|
||||
else if (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()) {
|
||||
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';
|
||||
break;
|
||||
case 'rebuild':
|
||||
$restore_states = array(FEATURES_REBUILDABLE);
|
||||
$restore_hook = 'features_rebuild';
|
||||
$log_action = 'Rebuild';
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($items as $module_name => $components) {
|
||||
foreach ($components as $component) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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',
|
||||
);
|
||||
return array_fill_keys($hooks, array('group' => 'features'));
|
||||
}
|
40
sites/all/modules/features/includes/features.block.inc
Normal file
40
sites/all/modules/features/includes/features.block.inc
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Implements hook_features_api().
|
||||
*/
|
||||
function block_features_api() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_features_export().
|
||||
*/
|
||||
function block_features_export($data, &$export) {
|
||||
$pipe = array();
|
||||
foreach ($data as $bid) {
|
||||
$split = explode('-', $bid);
|
||||
$module = array_shift($split);
|
||||
$delta = implode('-', $split);
|
||||
|
||||
$export['dependencies'][$module] = $module;
|
||||
|
||||
switch ($module) {
|
||||
case 'views':
|
||||
if (strlen($delta) == 32) {
|
||||
$hashes = variable_get('views_block_hashes', array());
|
||||
if (!empty($hashes[$delta])) {
|
||||
$delta = $hashes[$delta];
|
||||
}
|
||||
}
|
||||
|
||||
$delta_split = explode('-', $delta);
|
||||
$view_name = $delta_split[0];
|
||||
if (!empty($view_name)) {
|
||||
$pipe['views'][] = $view_name;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $pipe;
|
||||
}
|
54
sites/all/modules/features/includes/features.context.inc
Normal file
54
sites/all/modules/features/includes/features.context.inc
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Implements hook_features_export().
|
||||
*/
|
||||
function context_features_export($data, &$export, $module_name = '') {
|
||||
$pipe = ctools_component_features_export('context', $data, $export, $module_name);
|
||||
|
||||
$contexts = context_load();
|
||||
foreach ($data as $identifier) {
|
||||
if (isset($contexts[$identifier])) {
|
||||
$context = $contexts[$identifier];
|
||||
// Conditions.
|
||||
// Currently only node and views conditions are supported.
|
||||
// @TODO: Should this be delegated to a method on the plugin?
|
||||
foreach (array('node', 'views') as $key) {
|
||||
if (!empty($context->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;
|
||||
}
|
376
sites/all/modules/features/includes/features.ctools.inc
Normal file
376
sites/all/modules/features/includes/features.ctools.inc
Normal file
@@ -0,0 +1,376 @@
|
||||
<?php
|
||||
|
||||
function ctools_features_declare_functions($reset = FALSE) {
|
||||
/**
|
||||
* This is called by Features to ensure ctools component functions are defined
|
||||
* Dynamically declare functions under a ctools component's namespace if they are not already declared.
|
||||
*/
|
||||
if (function_exists('_ctools_features_get_info')) {
|
||||
foreach (_ctools_features_get_info(NULL, $reset) as $component => $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[] = ' list($module, $api) = func_get_args();';
|
||||
$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] .= "\n" . implode("\n", $code);
|
||||
}
|
||||
else {
|
||||
$component_exports[$plugin_api_hook_name] = implode("\n", $code);
|
||||
}
|
||||
}
|
||||
|
||||
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'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
73
sites/all/modules/features/includes/features.features.inc
Normal file
73
sites/all/modules/features/includes/features.features.inc
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Implements hook_features_api().
|
||||
*/
|
||||
function features_features_api() {
|
||||
return array(
|
||||
'dependencies' => 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);
|
||||
}
|
||||
}
|
||||
}
|
224
sites/all/modules/features/includes/features.field.inc
Normal file
224
sites/all/modules/features/includes/features.field.inc
Normal file
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Implements hook_features_api().
|
||||
*/
|
||||
function field_features_api() {
|
||||
return array(
|
||||
'field' => array(
|
||||
'name' => t('Fields'),
|
||||
'default_hook' => 'field_default_fields',
|
||||
'default_file' => FEATURES_DEFAULTS_INCLUDED,
|
||||
'feature_source' => TRUE,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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();
|
||||
$map = features_get_default_map('field');
|
||||
|
||||
// The field_default_fields() 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 ($field = features_field_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'][$identifier])) {
|
||||
unset($export['features']['field'][$identifier]);
|
||||
}
|
||||
$module = $map[$identifier];
|
||||
$export['dependencies'][$module] = $module;
|
||||
}
|
||||
// If the field has not yet been exported, add it
|
||||
else {
|
||||
$export['features']['field'][$identifier] = $identifier;
|
||||
$export['dependencies'][$field['field_config']['module']] = $field['field_config']['module'];
|
||||
if ($field['field_config']['storage']['type'] != variable_get('field_storage_default', 'field_sql_storage')) {
|
||||
$export['dependencies'][$field['field_config']['storage']['module']] = $field['field_config']['storage']['module'];
|
||||
}
|
||||
$export['dependencies'][$field['field_instance']['widget']['module']] = $field['field_instance']['widget']['module'];
|
||||
foreach ($field['field_instance']['display'] as $key => $display) {
|
||||
if (isset($display['module'])) {
|
||||
$export['dependencies'][$display['module']] = $display['module'];
|
||||
// @TODO: handle the pipe to image styles
|
||||
}
|
||||
}
|
||||
// If taxonomy field, add in the vocabulary
|
||||
if ($field['field_config']['type'] == 'taxonomy_term_reference' && !empty($field['field_config']['settings']['allowed_values'])) {
|
||||
foreach ($field['field_config']['settings']['allowed_values'] as $allowed_values) {
|
||||
if (!empty($allowed_values['vocabulary'])) {
|
||||
$pipe['taxonomy'][] = $allowed_values['vocabulary'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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 noone 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']];
|
||||
if ($field_config + $existing_field != $existing_field) {
|
||||
field_update_field($field_config);
|
||||
}
|
||||
}
|
||||
else {
|
||||
field_create_field($field_config);
|
||||
$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;
|
||||
}
|
120
sites/all/modules/features/includes/features.filter.inc
Normal file
120
sites/all/modules/features/includes/features.filter.inc
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Implements hook_features_api().
|
||||
*/
|
||||
function filter_features_api() {
|
||||
return array(
|
||||
'filter' => 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;
|
||||
}
|
101
sites/all/modules/features/includes/features.image.inc
Normal file
101
sites/all/modules/features/includes/features.image.inc
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Implements hook_features_api().
|
||||
*/
|
||||
function image_features_api() {
|
||||
return array(
|
||||
'image' => 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(&$style, $child = FALSE) {
|
||||
$omit = $child ? array('isid', 'ieid', 'storage') : array('isid', 'ieid', 'storage', 'module');
|
||||
if (is_array($style)) {
|
||||
foreach ($style as $k => $v) {
|
||||
if (in_array($k, $omit, TRUE)) {
|
||||
unset($style[$k]);
|
||||
}
|
||||
else if (is_array($v)) {
|
||||
_image_features_style_sanitize($style[$k], TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
158
sites/all/modules/features/includes/features.locale.inc
Normal file
158
sites/all/modules/features/includes/features.locale.inc
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Features hooks for language.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements of hook_features_api().
|
||||
*/
|
||||
function locale_features_api() {
|
||||
return array(
|
||||
'language' => 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,
|
||||
))
|
||||
->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');
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
310
sites/all/modules/features/includes/features.menu.inc
Normal file
310
sites/all/modules/features/includes/features.menu.inc
Normal file
@@ -0,0 +1,310 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Implements hook_features_api().
|
||||
*/
|
||||
function menu_features_api() {
|
||||
return array(
|
||||
'menu_custom' => 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[] = '';
|
||||
$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;
|
||||
$menu_links = menu_parent_options(menu_get_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);
|
||||
$options[$identifier] = "{$menu_name}: {$name}";
|
||||
}
|
||||
}
|
||||
$menu_admin = FALSE;
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for generating the menu link exportable identifier.
|
||||
*/
|
||||
function menu_links_features_identifier($link) {
|
||||
return isset($link['menu_name'], $link['link_path']) ? "{$link['menu_name']}:{$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 $identifier) {
|
||||
if ($link = features_menu_link_load($identifier)) {
|
||||
// If this link is provided by a different module, add it as a dependency.
|
||||
if (isset($map[$identifier]) && $map[$identifier] != $module_name) {
|
||||
$export['dependencies'][$map[$identifier]] = $map[$identifier];
|
||||
}
|
||||
else {
|
||||
$export['features']['menu_links'][$identifier] = $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) {
|
||||
$code = array();
|
||||
$code[] = ' $menu_links = array();';
|
||||
$code[] = '';
|
||||
|
||||
$translatables = array();
|
||||
foreach ($data as $identifier) {
|
||||
if ($link = features_menu_link_load($identifier)) {
|
||||
// Replace plid with a parent path.
|
||||
if (!empty($link['plid']) && $parent = menu_link_load($link['plid'])) {
|
||||
$link['parent_path'] = $parent['link_path'];
|
||||
}
|
||||
unset($link['plid']);
|
||||
unset($link['mlid']);
|
||||
|
||||
$code[] = " // Exported menu link: {$identifier}";
|
||||
$code[] = " \$menu_links['{$identifier}'] = ". features_var_export($link, ' ') .";";
|
||||
$translatables[] = $link['link_title'];
|
||||
}
|
||||
}
|
||||
if (!empty($translatables)) {
|
||||
$code[] = features_translatables_export($translatables, ' ');
|
||||
}
|
||||
|
||||
$code[] = '';
|
||||
$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_path']) ? "{$link['menu_name']}:{$link['parent_path']}" : '';
|
||||
if (empty($parent)) {
|
||||
$ordered[$identifier] = 0;
|
||||
$all_links[$identifier] = $link;
|
||||
unset($unordered[$key]);
|
||||
}
|
||||
elseif (isset($ordered[$parent])) {
|
||||
$ordered[$identifier] = $ordered[$parent] + 1;
|
||||
$all_links[$identifier] = $link;
|
||||
unset($unordered[$key]);
|
||||
}
|
||||
}
|
||||
} while (count($unordered) < $current);
|
||||
}
|
||||
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_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:link_path identifier.
|
||||
*/
|
||||
function features_menu_link_load($identifier) {
|
||||
list($menu_name, $link_path) = explode(':', $identifier, 2);
|
||||
$link = 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)
|
||||
->condition('link_path', $link_path)
|
||||
->execute()
|
||||
->fetchAssoc();
|
||||
if ($link) {
|
||||
$link['options'] = unserialize($link['options']);
|
||||
return $link;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
161
sites/all/modules/features/includes/features.node.inc
Normal file
161
sites/all/modules/features/includes/features.node.inc
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Implements hook_features_api().
|
||||
*/
|
||||
function node_features_api() {
|
||||
return array(
|
||||
'node' => array(
|
||||
'name' => t('Content types'),
|
||||
'feature_source' => TRUE,
|
||||
'default_hook' => 'node_info',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'][] = "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[] = ' 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().
|
||||
*
|
||||
* 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($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;
|
||||
node_type_save($type_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_features_enable().
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @param $module
|
||||
* Name of module that has been enabled.
|
||||
*/
|
||||
function node_features_enable($module) {
|
||||
if ($default_types = features_get_default('node', $module)) {
|
||||
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;
|
||||
node_type_save($type_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
104
sites/all/modules/features/includes/features.taxonomy.inc
Normal file
104
sites/all/modules/features/includes/features.taxonomy.inc
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Implements hook_features_api().
|
||||
*/
|
||||
function taxonomy_features_api() {
|
||||
return array(
|
||||
'taxonomy' => 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']}";
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
269
sites/all/modules/features/includes/features.user.inc
Normal file
269
sites/all/modules/features/includes/features.user.inc
Normal file
@@ -0,0 +1,269 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Implements hook_features_api().
|
||||
*/
|
||||
function user_features_api() {
|
||||
return array(
|
||||
'user_role' => 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) {
|
||||
if (isset($map[$perm])) {
|
||||
$perm_module = $map[$perm];
|
||||
$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_info[$module]['name']] = $module;
|
||||
}
|
||||
ksort($modules);
|
||||
|
||||
$options = array();
|
||||
foreach ($modules as $display_name => $module) {
|
||||
if ($permissions = module_invoke($module, 'permission')) {
|
||||
foreach ($permissions as $perm => $perm_item) {
|
||||
$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();
|
||||
$permission['name'] = $perm_name;
|
||||
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_name);
|
||||
$perm_export = features_var_export($permission, ' ');
|
||||
$code[] = " // Exported permission: {$perm_name}.";
|
||||
$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();
|
||||
|
||||
$roles = _user_features_get_roles();
|
||||
$permissions_by_role = _user_features_get_permissions(FALSE);
|
||||
foreach ($defaults as $permission) {
|
||||
$perm = $permission['name'];
|
||||
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;
|
||||
}
|
282
sites/all/modules/features/tests/features.test
Normal file
282
sites/all/modules/features/tests/features.test
Normal file
@@ -0,0 +1,282 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* User permission component tests for Features
|
||||
*/
|
||||
class FeaturesUserTestCase extends DrupalWebTestCase {
|
||||
protected $profile = 'testing';
|
||||
|
||||
/**
|
||||
* Test info.
|
||||
*/
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 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' => '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($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 intergration 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));
|
||||
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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* features_test.features.field.inc
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_field_default_fields().
|
||||
*/
|
||||
function features_test_field_default_fields() {
|
||||
$fields = array();
|
||||
|
||||
// Exported field: 'node-features_test-field_features_test'
|
||||
$fields['node-features_test-field_features_test'] = array(
|
||||
'field_config' => array(
|
||||
'active' => '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',
|
||||
),
|
||||
),
|
||||
'module' => 'text',
|
||||
'settings' => array(
|
||||
'max_length' => '255',
|
||||
),
|
||||
'translatable' => '1',
|
||||
'type' => 'text',
|
||||
),
|
||||
'field_instance' => array(
|
||||
'bundle' => '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 $fields;
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* features_test.features.filter.inc
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_filter_default_formats().
|
||||
*/
|
||||
function features_test_filter_default_formats() {
|
||||
$formats = array();
|
||||
|
||||
// Exported format: features_test
|
||||
$formats['features_test'] = array(
|
||||
'format' => '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' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>',
|
||||
'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;
|
||||
}
|
71
sites/all/modules/features/tests/features_test.features.inc
Normal file
71
sites/all/modules/features/tests/features_test.features.inc
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* features_test.features.inc
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_ctools_plugin_api().
|
||||
*/
|
||||
function features_test_ctools_plugin_api() {
|
||||
list($module, $api) = func_get_args();
|
||||
if ($module == "strongarm" && $api == "strongarm") {
|
||||
return array("version" => "1");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_views_api().
|
||||
*/
|
||||
function features_test_views_api() {
|
||||
return array("version" => "3.0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_image_default_styles().
|
||||
*/
|
||||
function features_test_image_default_styles() {
|
||||
$styles = array();
|
||||
|
||||
// Exported image style: features_test
|
||||
$styles['features_test'] = array(
|
||||
'name' => 'features_test',
|
||||
'effects' => array(
|
||||
2 => array(
|
||||
'label' => 'Scale',
|
||||
'help' => 'Scaling will maintain the aspect-ratio of the original image. If only a single dimension is specified, the other dimension will be calculated.',
|
||||
'effect callback' => 'image_scale_effect',
|
||||
'dimensions callback' => 'image_scale_dimensions',
|
||||
'form callback' => 'image_scale_form',
|
||||
'summary theme' => 'image_scale_summary',
|
||||
'module' => 'image',
|
||||
'name' => 'image_scale',
|
||||
'data' => array(
|
||||
'width' => '100',
|
||||
'height' => '100',
|
||||
'upscale' => 0,
|
||||
),
|
||||
'weight' => '1',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* features_test.features.taxonomy.inc
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_taxonomy_default_vocabularies().
|
||||
*/
|
||||
function features_test_taxonomy_default_vocabularies() {
|
||||
return array(
|
||||
'taxonomy_features_test' => 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',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* features_test.features.user_permission.inc
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_user_default_permissions().
|
||||
*/
|
||||
function features_test_user_default_permissions() {
|
||||
$permissions = array();
|
||||
|
||||
// Exported permission: create features_test content.
|
||||
$permissions['create features_test content'] = array(
|
||||
'name' => 'create features_test content',
|
||||
'roles' => array(
|
||||
0 => 'anonymous user',
|
||||
1 => 'authenticated user',
|
||||
),
|
||||
'module' => 'node',
|
||||
);
|
||||
|
||||
return $permissions;
|
||||
}
|
28
sites/all/modules/features/tests/features_test.info
Normal file
28
sites/all/modules/features/tests/features_test.info
Normal file
@@ -0,0 +1,28 @@
|
||||
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:1
|
||||
features[field][] = 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 = TRUE
|
||||
|
||||
; Information added by drupal.org packaging script on 2012-12-08
|
||||
version = "7.x-2.0-beta1+5-dev"
|
||||
core = "7.x"
|
||||
project = "features"
|
||||
datestamp = "1354928180"
|
||||
|
3
sites/all/modules/features/tests/features_test.module
Normal file
3
sites/all/modules/features/tests/features_test.module
Normal file
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
include_once('features_test.features.inc');
|
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* features_test.views_default.inc
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_views_default_views().
|
||||
*/
|
||||
function features_test_views_default_views() {
|
||||
$export = array();
|
||||
|
||||
$view = new view;
|
||||
$view->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['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;
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
?>
|
||||
<div class='clearfix features-components'>
|
||||
<div class='column'>
|
||||
<div class='info'>
|
||||
<h3><?php print $name ?></h3>
|
||||
<div class='description'><?php print $description ?></div>
|
||||
<?php print $dependencies ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class='column'>
|
||||
<div class='components'>
|
||||
<?php print $components ?>
|
||||
<?php if (!empty($key)): ?>
|
||||
<div class='clearfix features-key'><?php print theme('links', array('links' => $key)) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($buttons)): ?>
|
||||
<div class='buttons clearfix'><?php print $buttons ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php print drupal_render_children($form) ?>
|
||||
</div>
|
331
sites/all/modules/features/theme/theme.inc
Normal file
331
sites/all/modules/features/theme/theme.inc
Normal file
@@ -0,0 +1,331 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Display feature component info
|
||||
*/
|
||||
function template_preprocess_features_admin_components(&$vars) {
|
||||
drupal_add_css(drupal_get_path('module', 'features') . '/features.css');
|
||||
$form = $vars['form'];
|
||||
|
||||
// Basic info
|
||||
$vars['name'] = $form['#info']['name'];
|
||||
$vars['description'] = isset($form['#info']['description']) ? $form['#info']['description'] : '';
|
||||
|
||||
// Legend/key
|
||||
$vars['key'] = array();
|
||||
|
||||
// Dependencies
|
||||
$rows = array();
|
||||
$modules = features_get_info();
|
||||
foreach ($form['#dependencies'] as $dependency => $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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 "<span class=\"$class\">$text</span>";
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 "<span class='{$classes[$vars['storage']]} features-storage'>{$text}</span>";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) ? "<div class='buttons clearfix'>{$output}</div>" : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = "<div class='feature'>";
|
||||
$name .= "<strong>{$vars['form']['status'][$element]['#title']}</strong>";
|
||||
$name .= "<div class='description'>{$vars['form']['status'][$element]['#description']}</div>";
|
||||
$name .= "</div>";
|
||||
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 .= "<div class='clearfix features-components'>";
|
||||
$output .= "<div class='column'>" . drupal_render($vars['form']['components']) . drupal_render($vars['form']['sources']) . "</div>";
|
||||
$output .= "<div class='column'>" . drupal_render($vars['form']['preview']) . drupal_render($vars['form']['features']) . "</div>";
|
||||
$output .= "</div>";
|
||||
$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 .= "<div class='features-select features-select-{$key}'>" . drupal_render($vars['form'][$key]) . "</div>";
|
||||
}
|
||||
$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[] = "<span class='features-detected'>". check_plain($component) ."</span>";
|
||||
}
|
||||
elseif (is_array($conflicts) && in_array($component, $conflicts)) {
|
||||
$list[] = "<span class='features-conflict'>". check_plain($component) ."</span>";
|
||||
}
|
||||
else {
|
||||
$list[] = "<span class='features-source'>". check_plain($component) ."</span>";
|
||||
}
|
||||
}
|
||||
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[] = "<span class='features-dependency'>". check_plain($component) ."</span>";
|
||||
}
|
||||
}
|
||||
return "<span class='features-component-list'>". implode(' ', $list) ."</span>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a themed key for a component list.
|
||||
*/
|
||||
function theme_features_component_key($vars) {
|
||||
$list = array();
|
||||
$list[] = "<span class='features-source'>" . t('Normal') . "</span>";
|
||||
$list[] = "<span class='features-detected'>" . t('Auto-detected') . "</span>";
|
||||
$list[] = "<span class='features-dependency'>" . t('Provided by dependency') . "</span>";
|
||||
return "<span class='features-component-list features-component-key'>" . implode(' ', $list) . "</span>";
|
||||
}
|
Reference in New Issue
Block a user