FINAL suepr merge step : added all modules to this super repos

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-19 16:46:59 +02:00
7585 changed files with 1723356 additions and 18 deletions

View 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

View 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.

View 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.

View File

@@ -0,0 +1,236 @@
Current state of Features for Drupal 7
--------------------------------------
Work on Features for D7 is currently aimed at getting to a point where Features
can be used on a new install of Drupal 7 with features that were created on D7.
Once this has been achieved, we will begin working on supporting D6 features as
well as possibly supporting upgrades & migrations between legacy components and
new equivalents (e.g. CCK to fields, imagecache to core image styles).
### Working components
- ctools
- dependencies
- field
- filter
- image
- menu_custom
- menu_links
- node
- taxonomy
- user_permission
- user_role
- views
### Has changes to export format between D6 and D7
(@TODO legacy export compatibility)
- filter
- taxonomy
### Requires upgrade/migration path
- imagecache > image
- content > field
Note on the "Generate Feature" capability
-----------------------------------------
Features 7.x-2.x includes the ability to "Generate a feature" which saves it
to the server disk. This can be a time-saving task in development. It requires
the webserver to be able to write to the very code running the site and is
not recommended for any environment other than a firewalled-off, local
development environment (e.g. a person working alone on their laptop).
Features 1.x for Drupal 7.x
---------------------------
The features module enables the capture and management of features in Drupal. A
feature is a collection of Drupal entities which taken together satisfy a
certain use-case.
Features provides a UI and API for taking different site building components
from modules with exportables and bundling them together in a single feature
module. A feature module is like any other Drupal module except that it declares
its components (e.g. views, contexts, CCK fields, etc.) in its `.info` file so
that it can be checked, updated, or reverted programmatically.
Examples of features might be:
- A blog
- A pressroom
- An image gallery
- An e-commerce t-shirt store
Installation
------------
Features can be installed like any other Drupal module -- place it in the
modules directory for your site and enable it on the `admin/build/modules` page.
To take full advantage of some of the workflow benefits provided by Features,
you should install [Drush][1].
If you plan on creating or working with very large features (greater than 1000
items), you may need to increase PHP's max_input_vars configuration directive.
For example, adding the following line to your .htaccess file will increase the
max_input_vars directive to 3000:
php_value max_input_vars 3000
If you are using Suhosin, increasing suhosin.get.max_vars,
suhosin.post.max_vars, and suhosin.request.max_vars may also be necessary.
Basic usage
-----------
Features is geared toward usage by developers and site builders. It
is not intended to be used by the general audience of your Drupal site.
Features provides tools for accomplishing two important tasks:
### Task 1: Export features
You can build features in Drupal by using site building tools that are supported
(see a short list under the *Compatibility* section).
Once you've built and configured functionality on a site, you can export it into
a feature module by using the feature create page at
`admin/structure/features/create`.
### Task 2: Manage features
The features module also provides a way to manage features through a more
targeted interface than `admin/modules`. The interface at
`admin/structure/features` shows you only feature modules, and will also inform you
if any of their components have been overridden. If this is the case, you can
also re-create features to bring the module code up to date with any changes
that have occurred in the database.
Including custom code and adding to your feature
------------------------------------------------
Once you've exported your feature you will see that you have several files:
myfeature.info
myfeature.module
myfeature.[*].inc
You can add custom code (e.g. custom hook implementations, other functionality,
etc.) to your feature in `myfeature.module` as you would with any other module.
Do not change or add to any of the features `.inc` files unless you know what
you are doing. These files are written to by features on updates so any custom
changes may be overwritten.
Using Features to manage development
------------------------------------
Because Features provides a centralized way to manage exportable components and
write them to code it can be used during development in conjunction with a
version control like SVN or git as a way to manage changes between development,
staging and production sites. An example workflow for a developer using Features
is to:
1. Make configuration changes to a feature on her local development site.
2. Update her local feature codebase using `drush features-update`.
3. Commit those changes using `svn commit`.
4. Roll out her changes to the development site codebase by running `svn update`
on the server. Other collaborating developers can also get her changes with
`svn update`.
5. Reverting any configuration on the staging site to match the updated codebase
by running `drush features-revert`.
6. Rinse, repeat.
Features also provides integration with the [Diff][3] module if enabled to show
differences between configuration in the database and that in code. For site
builders interested in using Features for development, enabling the diff module
and reading `API.txt` for more details on the inner workings of Features is
highly recommended.
Drush usage
-----------
(requires Drush v4.5 or higher)
Features provides several useful drush commands:
- `drush features`
List all the available features on your site and their status.
- `drush features-export [feature name] [component list]`
Write a new feature in code containing the components listed.
If called with no arguments, display a list of available components.
If called with one argument, take the argument as a component name and
attempt to create a feature with the same name.
The option '--destination=foo' may be used to specify the path (from Drupal
root) where the feature should be created. The default destination is
'sites/all/modules', though this can be overridden via the Features
settings page.
- `drush features-update [feature name]`
Update the code of an existing feature to include any overrides/changes in
your database (e.g. a new view).
- `drush features-revert [feature name]`
Revert the components of a feature in your site's database to the state
described in your feature module's defaults.
- `drush features-diff [feature name]`
Show a diff between a feature's database components and those in code.
Requires the Diff module.
Additional commands and options can be found using `drush help`.
Compatibility
-------------
Features provides integration for the following exportables:
- CTools export API implementers (Context, Spaces, Boxes, Strongarm, Page
Manager)
- ImageCache
- Views
- [Other contributed modules][2]
Features also provides faux-exportable functionality for the following Drupal
core and contrib components:
- Fields
- Content types
- Input filters
- User roles/permissions
- Custom menus and menu links *
- Taxonomy vocabularies
* Currently in development.
Security Concerns
-----------------
If you are using Features to export Roles and also use those Roles in other
exportable code (like Views filters) you can wind up with an unintended
security hole. When you import your Feature, if the Roles do not get created
with the exact same Role IDs then your Views filters (or other component) will
be referencing a different Role than you intended.
For developers
--------------
Please read `API.txt` for more information about the concepts and integration
points in the Features module.
Maintainers
-----------
- febbraro (Frank Febbraro)
- hefox (Fox)
- mpotter (Mike Potter)
- timplunkett (Tim Plunkett)
[1]: http://drupal.org/project/drush
[2]: (http://drupal.org/taxonomy/term/11478)

File diff suppressed because it is too large Load Diff

View 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 of the
* component, not the module, eg. [component]_features_export() rather than
* [module]_features_export().
*
* Process the export array for a given component. Implementations of this hook
* have three key tasks:
*
* 1. Determine module dependencies for any of the components passed to it
* e.g. the views implementation iterates over each views' handlers and
* plugins to determine which modules need to be added as dependencies.
*
* 2. Correctly add components to the export array. In general this is usually
* adding all of the items in $data to $export['features']['my_key'], but
* can become more complicated if components are shared between features
* or modules.
*
* 3. Delegating further detection and export tasks to related or derivative
* components.
*
* Each export processor can kickoff further export processors by returning a
* keyed array (aka the "pipe") where the key is the next export processor hook
* to call and the value is an array to be passed to that processor's $data
* argument. This allows an export process to start simply at a few objects:
*
* [context]
*
* And then branch out, delegating each component to its appropriate hook:
*
* [context]--------+------------+
* | | |
* [node] [block] [views]
* |
* [CCK]
* |
* [imagecache]
*
* @param array $data
* An array of machine names for the component in question to be exported.
* @param array &$export
* By reference. An array of all components to be exported with a given
* feature. Component objects that should be exported should be added to
* this array.
* @param string $module_name
* The name of the feature module to be generated.
* @return array
* The pipe array of further processors that should be called.
*/
function hook_features_export($data, &$export, $module_name) {
// The following is the simplest implementation of a straight object export
// with no further export processors called.
foreach ($data as $component) {
$export['features']['mycomponent'][$component] = $component;
}
return array();
}
/**
* Component hook. The hook should be implemented using the name of the
* component, not the module, eg. [component]_features_export() rather than
* [module]_features_export().
*
* List all objects for a component that may be exported.
*
* @return array
* A keyed array of items, suitable for use with a FormAPI select or
* checkboxes element.
*/
function hook_features_export_options() {
$options = array();
foreach (mycomponent_load() as $mycomponent) {
$options[$mycomponent->name] = $mycomponent->title;
}
return $options;
}
/**
* Component hook. The hook should be implemented using the name of the
* component, not the module, eg. [component]_features_export() rather than
* [module]_features_export().
*
* Render one or more component objects to code.
*
* @param string $module_name
* The name of the feature module to be exported.
* @param array $data
* An array of machine name identifiers for the objects to be rendered.
* @param array $export
* The full export array of the current feature being exported. This is only
* passed when hook_features_export_render() is invoked for an actual feature
* update or recreate, not during state checks or other operations.
* @return array
* An associative array of rendered PHP code where the key is the name of the
* hook that should wrap the PHP code. The hook should not include the name
* of the module, e.g. the key for `hook_example` should simply be `example`
* The values in the array can also be in the form of an associative array
* with the required key of 'code' and optional key of 'args', if 'args' need
* to be added to the hook.
*/
function hook_features_export_render($module_name, $data, $export = NULL) {
$code = array();
$code[] = '$mycomponents = array();';
foreach ($data as $name) {
$code[] = " \$mycomponents['{$name}'] = " . features_var_export(mycomponent_load($name)) .";";
}
$code[] = "return \$mycomponents;";
$code = implode("\n", $code);
return array('mycomponent_defaults' => $code);
}
/**
* Component hook. The hook should be implemented using the name of the
* component, not the module, eg. [component]_features_export() rather than
* [module]_features_export().
*
* Revert all component objects for a given feature module.
*
* @param string $module_name
* The name of the feature module whose components should be reverted.
* @return boolean
* TRUE or FALSE for whether the components were successfully reverted.
* NOTE: This return value is no longer used in the latest Features so
* modules should no longer count on this value
*/
function hook_features_revert($module_name) {
$mycomponents = module_invoke($module_name, 'mycomponent_defaults');
if (!empty($mycomponents)) {
foreach ($mycomponents as $mycomponent) {
mycomponent_delete($mycomponent);
}
}
}
/**
* Component hook. The hook should be implemented using the name of the
* component, not the module, eg. [component]_features_export() rather than
* [module]_features_export().
*
* Rebuild all component objects for a given feature module. Should only be
* implemented for 'faux-exportable' components.
*
* This hook is called at points where Features determines that it is safe
* (ie. the feature is in state `FEATURES_REBUILDABLE`) for your module to
* replace objects in the database with defaults that you collect from your
* own defaults hook. See API.txt for how Features determines whether a
* rebuild of components is possible.
*
* @param string $module_name
* The name of the feature module whose components should be rebuilt.
*/
function hook_features_rebuild($module_name) {
$mycomponents = module_invoke($module_name, 'mycomponent_defaults');
if (!empty($mycomponents)) {
foreach ($mycomponents as $mycomponent) {
mycomponent_save($mycomponent);
}
}
}
/**
* 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) {
}
/**
* @}
*/

View File

@@ -0,0 +1,566 @@
/**
* Features packages.
*/
div.features-form-links {
width:20%;
float:left;
}
div.features-form-content {
width:80%;
float:right;
}
/**
* Package links.
*/
div.features-form-links ul#features-form-links,
div.features-form-links ul#features-form-links li,
div.features-form-links ul#features-form-links li a {
display:block;
float:none;
padding:0px;
margin:0px;
}
div.features-form-links ul#features-form-links {
margin:0px 0px 10px;
}
div.features-form-links ul#features-form-links li a {
background:#f8f8f8;
padding:5px 5px 4px 4px;
border-left:1px solid #eee;
border-bottom:1px solid #eee;
color: #000;
cursor:pointer;
}
div.features-form-links ul#features-form-links li a.features-package-active {
padding:4px 5px 4px 4px;
background:#fff;
border:1px solid #ccc;
border-right:0px;
color: #000;
margin-right:-1px;
}
/* Packages */
div.features-form-package {
border:1px solid #ccc;
background:#fff;
color: #000;
padding:10px;
margin:0px 0px 20px;
display:none;
}
div.features-package-active {
display:block;
}
/**
* Features management form (admin/build/features).
*/
div.features-empty {
margin:15px 0px;
font-size:1.5em;
text-align:center;
color:#999;
}
form div.buttons {
text-align:center;
}
table.features .admin-loading,
table.features tr.disabled {
color:#999;
}
table.features a.configure {
float:right;
font-style:italic;
}
table.features div.feature {
float:left; width:85%;
}
table.features div.feature strong {
font-size:13px;
}
table.features div.feature div.description {
font-size:11px; margin:0px;
}
table.features-manage td.name {
width:80%;
}
table.features-manage td.sign {
width:20%;
}
table.features-admin td.name {
width:60%;
}
table.features-admin td.sign {
width:20%;
}
table.features-admin td.state {
width:10%;
}
table.features-admin td.actions {
width:10%;
}
table.features td.sign {
font-size:9px;
line-height:15px;
white-space:nowrap;
}
table.features td.sign * {
margin:0px;
}
table.features .admin-check,
table.features .admin-default,
table.features .admin-overridden,
table.features .admin-rebuilding,
table.features .admin-needs-review {
display:none;
}
/**
* Feature export form (admin/build/features/export).
*/
form.features-export-form table td {
width:50%;
}
form.features-export-form table td {
vertical-align:top;
}
form.features-export-form table div.description {
white-space:normal;
}
table.features-export div.form-item {
white-space:normal;
}
table.features-export select {
width:90%;
}
table.features-export td {
vertical-align:top;
}
form.features-export-form div.features-select {
display:none;
}
/*
form.features-export-form div.form-checkboxes {
overflow-x:hidden;
overflow-y:auto;
height:20em;
}
*/
form.features-export-form div#edit-components-wrapper,
form.features-export-form div.features-select {
padding-right:20px;
}
/**
* Feature component display (admin/build/features/%feature).
*/
div.features-components div.column {
float:left;
width:50%;
}
div.features-components div.column div.info {
padding-right:20px;
}
div.features-components div.column div.components {
padding-left:20px;
}
h3.features-download,
div.features-comparison h3,
div.features-components h3 {
font-size:2em;
font-weight:bold;
letter-spacing:-1px;
margin:15px 0px;
}
h3.features-download {
text-align:center;
}
div.features-components div.description {
font-size:11px;
margin:15px 0px;
}
div.features-components table td {
font-size:11px;
}
div.features-components table td.component {
padding-left:20px;
}
/**
* Features component lists.
*/
span.features-component-key {
font-size:11px;
}
a.admin-update,
a.features-storage,
span.features-storage,
span.features-component-list span {
white-space:nowrap;
margin-right:5px;
padding:2px 5px;
background:#eee;
-moz-border-radius:5px;
-webkit-border-radius:5px;
}
div.features-key span.admin-conflict,
span.features-component-list span.features-conflict {
background-color: #c30 !important;
color: #fff;
}
#features-export-wrapper .features-conflict,
#features-export-wrapper .features-conflict label.option{
color: #c30 !important;
font-weight: bold !important;
}
div.conflicts span.admin-disabled {
color: #955;
}
a.admin-update {
background:transparent;
}
/* These pseudo selectors are necessary for themes like Garland. */
a.admin-overridden:link,
a.admin-overridden:visited,
span.admin-overridden {
color:#fff;
background:#666;
}
a.admin-needs-review:link,
a.admin-needs-review:visited,
span.admin-needs-review {
color:#963;
background:#fe6;
}
a.admin-rebuilding:link,
a.admin-rebuilding:visited,
span.admin-rebuilding {
color:#fff;
background:#699;
}
a.admin-conflict:link,
a.admin-conflict:visited,
span.admin-conflict {
color:#c30;
}
table.features-diff td.diff-addedline,
span.features-component-list .features-detected {
color:#68a;
background:#def;
}
table.features-diff td.diff-deletedline,
span.features-component-list .features-dependency {
color:#999;
background:#f8f8f8;
}
/**
* Features diff.
*/
table.features-diff {
font-size:11px;
}
table.features-diff td {
padding:0px 5px;
}
table.features-diff td.diff-deletedline,
table.features-diff td.diff-addedline,
table.features-diff td.diff-context {
width:50%;
font-family:'Andale Mono',monospace;
}
/**
* New UI component export list
*/
#features-export-wrapper div.features-export-parent {
clear: both;
}
#features-export-form .fieldset-content.fieldset-wrapper {
padding-top: 5px;
}
html.js #features-export-form fieldset.collapsed {
min-height: 2em;
margin-bottom: 0;
top: 0;
padding: 0 0 0 0;
}
fieldset.features-export-component {
background: #F3F8FB;
margin: 0.5em 0;
font-weight: normal;
font-size: 12px;
top: 0;
}
fieldset.features-export-component.collapsed {
background: #F3F8FB;
}
fieldset.features-export-component legend {
top: 0;
}
fieldset.features-export-component.collapsed .fieldset-wrapper {
margin: 0;
padding: 0 13px 6px 15px;
}
fieldset.features-export-component .fieldset-legend {
white-space: nowrap;
}
fieldset.features-export-component .fieldset-title span {
font-size: 11px;
text-transform: none;
font-weight: normal;
}
#features-export-wrapper .fieldset-wrapper {
font-size: 12px;
}
div.features-export-list {
font-weight: normal;
font-size: 12px;
border: 1px solid #CCC;
border-top-width: 0;
overflow: hidden;
}
#features-export-form .fieldset-description {
margin: 5px 0;
line-height: 1.231em;
font-size: 0.923em;
color: #666;
}
div.features-export-empty {
display: none;
}
fieldset.features-export-component .fieldset-wrapper .form-checkboxes {
max-height: 20em;
overflow: auto;
min-width: 100px;
}
#features-export-wrapper .component-select .form-type-checkbox {
overflow: hidden;
padding: 0 0 0 2px;
}
#features-export-wrapper .component-added .form-type-checkbox,
#features-export-wrapper .component-detected .form-type-checkbox,
#features-export-wrapper .component-included .form-type-checkbox {
float: left;
white-space: normal;
margin: 2px 5px;
padding: 0 5px;
background: transparent;
}
#features-export-wrapper .component-added .form-type-checkbox {
font-weight: bold;
background: #EEE;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
}
#features-export-wrapper div.component-added label.option {
font-weight: bold;
}
#features-export-wrapper .component-detected .form-type-checkbox {
font-style: italic;
color:#68a;
background:#def;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
}
#features-export-info {
width: 49%;
float: left;
position: relative;
}
#features-export-wrapper {
width: 49%;
float: right;
clear: both;
position: relative;
}
#features-export-advanced {
width: 49%;
float: left;
clear: left;
margin-top: 0.5em;
position: relative;
}
#features-export-buttons {
width: 49%;
float: left;
margin-top: 0.5em;
position: relative;
text-align: center;
}
#features-export-buttons input {
margin: 0 3px;
}
#features-export-wrapper .component-added label a,
#features-export-wrapper .component-detected label a,
#features-export-wrapper .component-included label a {
display: inline;
float: none;
}
#features-export-wrapper label.component-added {
font-weight: bold;
}
#features-export-form input[size="60"].form-text {
width: 100%;
}
input.form-submit.features-refresh-button {
font-size: 0.7em;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
background-color: transparent;
margin-left: 1em;
}
.features-refresh-wrapper .ajax-progress {
font-size: 10px;
width: 10em;
}
#features-export-advanced .ajax-progress {
font-size: 10px;
width: 10em;
}
#features-export-wrapper div.fieldset-description,
#features-export-wrapper div.description {
clear: both;
}
#features-filter input[size="60"].form-text {
width: 200px;
}
#features-filter .fieldset-content,
#features-filter .fieldset-wrapper,
#features-filter fieldset {
border: 0;
padding: 0;
margin: 0;
}
#features-filter fieldset legend {
display: none;
}
#features-filter label,
#features-filter input {
display: inline;
width: auto;
}
#features-filter .form-item {
float: left;
margin: 5px 0;
padding: 0;
}
#features-filter .form-item.form-type-checkbox {
margin: 5px 0;
}
#features-filter span {
float: left;
white-space: normal;
margin: 5px 5px;
padding: 0 5px;
background: transparent;
background: #EEE;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
cursor: pointer;
}
#features-filter span:hover {
background:#def;
}
#features-autodetect .form-item {
float: left;
margin: 0 0;
padding: 0;
}
#features-autodetect .fieldset-content,
#features-autodetect .fieldset-wrapper,
#features-autodetect fieldset {
border: 0;
padding: 0;
margin: 1em 0 0 0;
top: 0;
}
#features-autodetect fieldset legend {
display: none;
}
#features-export-advanced .form-item.form-item-generate-path {
margin-bottom: 0;
}
#features-info-file .form-textarea-wrapper,
#features-info-file textarea {
height: 100%;
}
#features-info-file .form-type-textarea {
height: 100%;
margin: 0;
}
#edit-info-preview {
margin: 1em 0;
}
#features-legend .fieldset-wrapper span {
font-style: normal;
color: black;
display: inline-block;
background: transparent;
border: 1px solid #DDD;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
white-space: nowrap;
padding: 0 8px;
margin: 0 10px 0 0;
}
#features-legend .fieldset-wrapper .component-detected {
font-style: italic;
color:#68a;
background:#def;
border-width: 0;
}
#features-legend .fieldset-wrapper .component-added {
font-weight: bold;
background: #EEE;
border-width: 0;
}
fieldset.features-export-component .fieldset-title .component-count {
font-size: 12px;
font-weight: bold;
}

View File

@@ -0,0 +1,911 @@
<?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();
// If Features is enabled display the configured default export path,
// otherwise just show the default.
if (defined('FEATURES_DEFAULT_EXPORT_PATH')) {
$path = variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH);
}
else {
$path = 'sites/all/modules';
}
$items['features-list'] = array(
'description' => "List all the available features for your site.",
'options' => array(
'status' => "Feature status, can be 'enabled', 'disabled' or 'all'",
),
'drupal dependencies' => array('features'),
'aliases' => array('fl', 'features'),
);
$items['features-export'] = array(
'description' => "Export a feature from your site into a module.",
'arguments' => array(
'feature' => 'Feature name to export.',
'components' => 'Patterns of components to include, see features-components for the format of patterns.'
),
'options' => array(
'destination' => "Destination path (from Drupal root) of the exported feature. Defaults to '" . $path . "'.",
'version-set' => "Specify a version number for the feature.",
'version-increment' => "Increment the feature's version number.",
),
'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.',
),
'options' => array(
'ctypes' => 'Comma separated list of component types to limit the output to. Defaults to all types.',
'lines' => 'Generate diffs with <n> lines of context instead of the usual two.',
),
'drupal dependencies' => array('features', 'diff'),
'aliases' => array('fd'),
);
return $items;
}
/**
* Implements hook_drush_help().
*/
function features_drush_help($section) {
// If Features is enabled display the configured default export path,
// otherwise just show the default.
if (defined('FEATURES_DEFAULT_EXPORT_PATH')) {
$path = variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH);
}
else {
$path = 'sites/all/modules';
}
switch ($section) {
case 'drush:features':
return dt("List all the available features for your site.");
case 'drush:features-export':
return dt("Export a feature from your site into a module. If called with no arguments, display a list of available components. If called with a single argument, attempt to create a feature including the given component with the same name. The option '--destination=foo' may be used to specify the path (from Drupal root) where the feature should be created. The default destination is '@path'. The option '--version-set=foo' may be used to specify a version number for the feature or the option '--version-increment' may also to increment the feature's version number.", array('@path' => $path));
case 'drush:features-components':
return dt("List feature components matching patterns. The listing may be limited to exported/not-exported components.
A component pattern consists of a source, a colon and a component. Both source and component may be a full name (as in \"dependencies\"), a shorthand (for instance \"dep\") or a pattern (like \"%denci%\").
Shorthands are unique shortenings of a name. They will only match if exactly one option contains the shorthand. So in a standard installation, \"dep\" will work for dependencies, but \"user\" wont, as it matches both user_permission and user_role.
Patterns uses * or % for matching multiple sources/components. Unlike shorthands, patterns must match the whole name, so \"field:%article%\" should be used to select all fields containing \"article\" (which could both be those on the node type article, as well as those fields named article). * and % are equivalent, but the latter doesn't have to be escaped in UNIX shells.
Lastly, a pattern without a colon is interpreted as having \":%\" appended, for easy listing of all components of a source.
");
case 'drush:features-update':
return dt("Update a feature module on your site. The option '--version-set=foo' may be used to specify a version number for the feature or the option '--version-increment' may also to increment the feature's version number.");
case 'drush:features-update-all':
return dt("Update all feature modules on your site.");
case 'drush:features-revert':
return dt("Revert a feature module on your site.");
case 'drush:features-revert-all':
return dt("Revert all enabled feature module on your site.");
case 'drush:features-diff':
return dt("Show a diff of a feature module.");
case 'drush:features-add':
return dt("Add a component to a feature module. The option '--version-set=foo' may be used to specify a version number for the feature or the option '--version-increment' may also to increment the feature's version number.");
}
}
/**
* Get a list of all feature modules.
*/
function drush_features_list() {
$status = drush_get_option('status') ? drush_get_option('status') : 'all';
if (!in_array($status, array('enabled', 'disabled', 'all'))) {
return drush_set_error('', dt('!status is not valid', array('!status' => $status)));
}
module_load_include('inc', 'features', 'features.export');
$rows = array(array(dt('Name'), dt('Feature'), dt('Status'), dt('Version'), dt('State')));
// Sort the Features list before compiling the output.
$features = features_get_features(NULL, TRUE);
ksort($features);
foreach ($features as $k => $m) {
switch (features_get_storage($m->name)) {
case FEATURES_DEFAULT:
case FEATURES_REBUILDABLE:
$storage = '';
break;
case FEATURES_OVERRIDDEN:
$storage = dt('Overridden');
break;
case FEATURES_NEEDS_REVIEW:
$storage = dt('Needs review');
break;
}
if (
($m->status == 0 && ($status == 'all' || $status == 'disabled')) ||
($m->status == 1 && ($status == 'all' || $status == 'enabled'))
) {
$rows[] = array(
$m->info['name'],
$m->name,
$m->status ? dt('Enabled') : dt('Disabled'),
$m->info['version'],
$storage
);
}
}
drush_print_table($rows, TRUE);
}
/**
* List components, with pattern matching.
*/
function drush_features_components() {
$args = func_get_args();
$components = _drush_features_component_list();
// 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'), variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH));
$directory = isset($directory) ? $directory : $destination . '/' . $module;
drush_print(dt('Will create a new module in !dir', array('!dir' => $directory)));
if (!drush_confirm(dt('Do you really want to continue?'))) {
drush_die('Aborting.');
}
$export = _drush_features_generate_export($items, $module);
_features_populate($items, $export[info], $export[name]);
_drush_features_export($export['info'], $module, $directory);
}
}
else {
return drush_set_error('', 'No feature name given.');
}
}
/**
* Add a component to a features module
* the selected components.
*
* This is DEPRECATED, but keeping it around for a bit longer for user migration
*/
function drush_features_add() {
drush_print(dt('features-add is DEPRECATED.'));
drush_print(dt('Calling features-export instead.'));
drush_features_export();
}
/**
* Update an existing feature module.
*/
function drush_features_update() {
if ($args = func_get_args()) {
foreach ($args as $module) {
if (($feature = features_load_feature($module, TRUE)) && module_exists($module)) {
_drush_features_export($feature->info, $feature->name, dirname($feature->filename));
}
elseif ($feature) {
_features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED');
}
else {
_features_drush_set_error($module);
}
}
}
else {
// By default just show contexts that are available.
$rows = array(array(dt('Available features')));
foreach (features_get_features(NULL, TRUE) as $name => $info) {
$rows[] = array($name);
}
drush_print_table($rows, TRUE);
}
}
/**
* Update all enabled features. Optionally pass in a list of features to
* exclude from being updated.
*/
function drush_features_update_all() {
$features_to_update = array();
$features_to_exclude = func_get_args();
foreach (features_get_features() as $module) {
if ($module->status && !in_array($module->name, $features_to_exclude)) {
$features_to_update[] = $module->name;
}
}
$dt_args = array('!modules' => implode(', ', $features_to_update));
drush_print(dt('The following modules will be updated: !modules', $dt_args));
if (!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort('Aborting.');
}
// If we got here, set affirmative to TRUE, so that the user doesn't have to
// confirm each and every feature. Start off by storing the current value,
// so we can set it back afteward.
$skip_confirmation = drush_get_context('DRUSH_AFFIRMATIVE');
drush_set_context('DRUSH_AFFIRMATIVE', TRUE);
// Now update all the features.
drush_invoke('features-update', $features_to_update);
// Now set it back as it was, in case other commands are called after this.
drush_set_context('DRUSH_AFFIRMATIVE', $skip_confirmation);
}
/**
* Write a module to the site dir.
*
* @param $info
* The feature info associative array.
* @param $module_name
* Optional. The name for the exported module.
*/
function _drush_features_export($info, $module_name = NULL, $directory = NULL) {
$root = drush_get_option(array('r', 'root'), drush_locate_root());
$skip_confirmation = drush_get_context('DRUSH_AFFIRMATIVE');
if ($root) {
$destination = drush_get_option(array('destination'), variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH));
$directory = isset($directory) ? $directory : $destination . '/' . $module_name;
if (is_dir($directory)) {
$warning = dt('Module appears to already exist in !dir', array('!dir' => $directory));
drush_log($warning, 'warning');
// If we aren't skipping confirmation and the directory already exists,
// prompt the user.
if (!$skip_confirmation && !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();
$export = _drush_features_generate_export($info, $module_name);
$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'));
}
}
/**
* Helper function for _drush_feature_export.
*
* @param $info
* The feature info associative array.
* @param $module_name
* Optional. The name for the exported module.
*/
function _drush_features_generate_export(&$info, &$module_name) {
module_load_include('inc', 'features', 'features.export');
$export = features_populate($info, $module_name);
if (!features_load_feature($module_name)) {
$export['name'] = $module_name;
}
// Set the feature version if the --version-set or --version-increment option is passed.
if ($version = drush_get_option(array('version-set'))) {
preg_match('/^(?P<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;
}
elseif ($version = drush_get_option(array('version-increment'))) {
// Determine current version and increment it.
$export_load = features_export_prepare($export, $module_name);
$version = $export_load['version'];
$version_explode = explode('.', $version);
$version_minor = array_pop($version_explode);
// Increment minor version number if numeric or not a dev release.
if (is_numeric($version_minor) || strpos($version_minor, 'dev') !== (strlen($version_minor) - 3)) {
// Check for prefixed versions (alpha, beta, rc).
if (ctype_digit($version_minor)) {
++$version_minor;
}
else {
// Split version number parts.
$pattern = '/([0-9]-[a-z]+([0-9])+)/';
$matches = array();
preg_match($pattern, $version_minor, $matches);
$number = array_pop($matches);
++$number;
$pattern = '/[0-9]+$/';
$version_minor = preg_replace($pattern, $number, $version_minor);
}
}
array_push($version_explode, $version_minor);
// Rebuild version string.
$version = implode('.', $version_explode);
$export['version'] = $version;
}
return $export;
}
/**
* Revert a feature to it's code definition.
* Optionally accept a list of components to revert.
*/
function drush_features_revert() {
if ($args = func_get_args()) {
module_load_include('inc', 'features', 'features.export');
features_include();
// Determine if revert should be forced.
$force = drush_get_option('force');
// Determine if -y was supplied. If so, we can filter out needless output
// from this command.
$skip_confirmation = drush_get_context('DRUSH_AFFIRMATIVE');
// Parse list of arguments.
$modules = array();
foreach ($args as $arg) {
$arg = explode('.', $arg);
$module = array_shift($arg);
$component = array_shift($arg);
if (isset($module)) {
if (empty($component)) {
// If we received just a feature name, this means that we need all of it's components.
$modules[$module] = TRUE;
}
elseif ($modules[$module] !== TRUE) {
if (!isset($modules[$module])) {
$modules[$module] = array();
}
$modules[$module][] = $component;
}
}
}
// Process modules.
foreach ($modules as $module => $components_needed) {
$dt_args['@module'] = $module;
if (($feature = features_load_feature($module, TRUE)) && module_exists($module)) {
$components = array();
// Forcefully revert all components of a feature.
if ($force) {
foreach (array_keys($feature->info['features']) as $component) {
if (features_hook($component, 'features_revert')) {
$components[] = $component;
}
}
}
// Only revert components that are detected to be Overridden/Needs review/rebuildable.
else {
$states = features_get_component_states(array($feature->name), FALSE);
foreach ($states[$feature->name] as $component => $state) {
$revertable_states = array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW, FEATURES_REBUILDABLE);
if (in_array($state, $revertable_states) && features_hook($component, 'features_revert')) {
$components[] = $component;
}
}
}
if (!empty($components_needed) && is_array($components_needed)) {
$components = array_intersect($components, $components_needed);
}
if (empty($components)) {
drush_log(dt('Current state already matches defaults, aborting.'), 'ok');
}
else {
foreach ($components as $component) {
$dt_args['@component'] = $component;
$confirmation_message = 'Do you really want to revert @module.@component?';
if ($skip_confirmation || drush_confirm(dt($confirmation_message, $dt_args))) {
features_revert(array($module => array($component)));
drush_log(dt('Reverted @module.@component.', $dt_args), 'ok');
}
else {
drush_log(dt('Skipping @module.@component.', $dt_args), 'ok');
}
}
}
}
elseif ($feature) {
_features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED');
}
else {
_features_drush_set_error($module);
}
}
}
else {
drush_features_list();
return;
}
}
/**
* Revert all enabled features to their definitions in code.
*
* @param ...
* (Optional) A list of features to exclude from being reverted.
*/
function drush_features_revert_all() {
module_load_include('inc', 'features', 'features.export');
$force = drush_get_option('force');
$features_to_exclude = func_get_args();
$features_to_revert = array();
foreach (features_get_features(NULL, TRUE) as $module) {
if ($module->status && !in_array($module->name, $features_to_exclude)) {
// If forced, add module regardless of status.
if ($force) {
$features_to_revert[] = $module->name;
}
else {
switch (features_get_storage($module->name)) {
case FEATURES_OVERRIDDEN:
case FEATURES_NEEDS_REVIEW:
case FEATURES_REBUILDABLE:
$features_to_revert[] = $module->name;
break;
}
}
}
}
if ($features_to_revert) {
$dt_args = array('!modules' => implode(', ', $features_to_revert));
drush_print(dt('The following modules will be reverted: !modules', $dt_args));
// Confirm that the user would like to continue. If not, simply abort.
if (!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort('Aborting.');
}
drush_invoke('features-revert', $features_to_revert);
}
else {
drush_log(dt('Current state already matches defaults, aborting.'), 'ok');
}
}
/**
* Show the diff of a feature module.
*/
function drush_features_diff() {
if (!$args = func_get_args()) {
drush_features_list();
return;
}
$module = $args[0];
$filter_ctypes = drush_get_option("ctypes");
if ($filter_ctypes) {
$filter_ctypes = explode(',', $filter_ctypes);
}
$feature = features_load_feature($module);
if (!module_exists($module)) {
drush_log(dt('No such feature is enabled: ' . $module), 'error');
return;
}
module_load_include('inc', 'features', 'features.export');
$overrides = features_detect_overrides($feature);
if (empty($overrides)) {
drush_log(dt('Feature is in its default state. No diff needed.'), 'ok');
return;
}
module_load_include('inc', 'diff', 'diff.engine');
if (!class_exists('DiffFormatter')) {
if (drush_confirm(dt('It seems that the Diff module is not available. Would you like to download and enable it?'))) {
// Download it if it's not already here.
$project_info = drush_get_projects();
if (empty($project_info['diff']) && !drush_invoke('dl', array('diff'))) {
return drush_set_error(dt('Diff module could not be downloaded.'));
}
if (!drush_invoke('en', array('diff'))) {
return drush_set_error(dt('Diff module could not be enabled.'));
}
}
else {
return drush_set_error(dt('Diff module is not enabled.'));
}
// If we're still here, now we can include the diff.engine again.
module_load_include('inc', 'diff', 'diff.engine');
}
$lines = (int) drush_get_option('lines');
$lines = $lines > 0 ? $lines : 2;
$formatter = new DiffFormatter();
$formatter->leading_context_lines = $lines;
$formatter->trailing_context_lines = $lines;
$formatter->show_header = FALSE;
if (drush_get_context('DRUSH_NOCOLOR')) {
$red = $green = "%s";
}
else {
$red = "\033[31;40m\033[1m%s\033[0m";
$green = "\033[0;32;40m\033[1m%s\033[0m";
}
// Print key for colors
drush_print(dt('Legend: '));
drush_print(sprintf($red, dt('Code: drush features-revert will remove the overrides.')));
drush_print(sprintf($green, dt('Overrides: drush features-update will update the exported feature with the displayed overrides')));
drush_print();
if ($filter_ctypes) {
$overrides = array_intersect_key($overrides, array_flip($filter_ctypes));
}
foreach ($overrides as $component => $items) {
$diff = new Diff(explode("\n", $items['default']), explode("\n", $items['normal']));
drush_print();
drush_print(dt("Component type: !component", array('!component' => $component)));
$rows = explode("\n", $formatter->format($diff));
foreach ($rows as $row) {
if (strpos($row, '>') === 0) {
drush_print(sprintf($green, $row));
}
elseif (strpos($row, '<') === 0) {
drush_print(sprintf($red, $row));
}
else {
drush_print($row);
}
}
}
}
/**
* 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));
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
name = "Features"
description = "Provides feature management for Drupal."
core = 7.x
package = "Features"
files[] = tests/features.test
configure = admin/structure/features/settings
; Information added by drupal.org packaging script on 2013-10-17
version = "7.x-2.0+0-dev"
core = "7.x"
project = "features"
datestamp = "1382036080"

View File

@@ -0,0 +1,122 @@
<?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_default_export_path');
variable_del('features_semaphore');
variable_del('features_ignored_orphans');
if (db_table_exists('menu_custom')) {
db_delete('menu_custom')
->condition('menu_name', 'features')
->execute();
}
db_delete('menu_links')
->condition('module', 'features')
->execute();
}
/**
* Create menu. See menu.install for an example.
*/
function _features_install_menu() {
if (db_table_exists('menu_custom') && !db_query("SELECT menu_name FROM {menu_custom} WHERE menu_name = :menu_name", array(':menu_name' => 'features'))->fetchField()) {
$t = get_t();
$id = db_insert('menu_custom')
->fields(array(
'menu_name' => 'features',
'title' => $t('Features'),
'description' => $t('Menu items for any enabled features.'),
))
->execute();
}
}
/**
* Update 6100: Set module on all feature node types to 'features'.
* This update can be re-run as needed to repair any node types that are not
* removed after disabling the associated feature.
*
* Any feature implementing a node component that was exported prior to this
* version of the features.module will need to have its 'module' declaration
* in hook_node_info() changed from 'node' to 'features'.
*/
function features_update_6100() {
$ret = array();
foreach (features_get_features(NULL, TRUE) as $feature) {
if (module_exists($feature->name) && $types = module_invoke($feature->name, 'node_info')) {
foreach ($types as $type => $type_data) {
$sql = "SELECT COUNT(*) FROM {node_type} WHERE module = 'node' AND type = '%s'";
// Only update if the hook_node_info type's module is 'features' and the db type's
// module is 'node'.
if (($type_data['module'] == 'features') && db_query($sql, $type)->fetchField()) {
$ret[] = update_sql("UPDATE {node_type} SET module = 'features' WHERE type = '$type'");
}
}
}
}
return $ret;
}
/**
* Update 6101: Set codestate signature for all features.
*
* This update generates a codestate for all feature/component pairs which
* have been installed prior to this version of Features. This prevents
* automatic rebuilds from occurring against any rebuildable components
* that have been overridden.
*/
function features_update_6101() {
// Ensure all of our own API functions still exist in in this version
// of Features. It's possible that the "future me" will not have these
// functions, so I should check.
module_load_include('inc', 'features', "features.export");
$functions = array(
'features_include',
'features_hook',
'features_get_components',
'features_get_features',
'features_get_signature',
'features_set_signature',
);
$doit = TRUE;
foreach ($functions as $function) {
$doit = $doit && function_exists($function);
}
if ($doit) {
features_include();
$features = array_keys(features_get_features(NULL, TRUE));
$components = array_keys(features_get_components());
foreach ($features as $feature) {
if (module_exists($feature)) {
foreach ($components as $component) {
if (features_hook($component, 'features_rebuild') && features_get_signature('cache', $feature, $component) === FALSE) {
features_set_signature($feature, $component, -1);
}
}
}
}
}
return array();
}

View File

@@ -0,0 +1,457 @@
/**
* 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">&nbsp;</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 updateComponentCountInfo(item, section) {
console.log(section);
switch (section) {
case 'select':
var parent = $(item).closest('.features-export-list').siblings('.features-export-component');
$('.component-count', parent).text(function (index, text) {
return +text + 1;
}
);
break;
case 'added':
case 'detected':
var parent = $(item).closest('.features-export-component');
$('.component-count', parent).text(function (index, text) {
return text - 1;
});
}
}
function moveCheckbox(item, section, value) {
updateComponentCountInfo(item, section);
var curParent = item;
if ($(item).hasClass('form-type-checkbox')) {
item = $(item).children('input[type=checkbox]');
}
else {
curParent = $(item).parents('.form-type-checkbox');
}
var newParent = $(curParent).parents('.features-export-parent').find('.form-checkboxes.component-'+section);
$(curParent).detach();
$(curParent).appendTo(newParent);
var list = ['select', 'added', 'detected', 'included'];
for (i in list) {
$(curParent).removeClass('component-' + list[i]);
$(item).removeClass('component-' + list[i]);
}
$(curParent).addClass('component-'+section);
$(item).addClass('component-'+section);
if (value) {
$(item).attr('checked', 'checked');
}
else {
$(item).removeAttr('checked')
}
$(newParent).parent().removeClass('features-export-empty');
// re-sort new list of checkboxes based on labels
$(newParent).find('label').sortElements(
function(a, b){
return $(a).text() > $(b).text() ? 1 : -1;
},
function(){
return this.parentNode;
}
);
}
// provide timer for auto-refresh trigger
var timeoutID = 0;
var inTimeout = 0;
function _triggerTimeout() {
timeoutID = 0;
_updateDetected();
}
function _resetTimeout() {
inTimeout++;
// if timeout is already active, reset it
if (timeoutID != 0) {
window.clearTimeout(timeoutID);
if (inTimeout > 0) inTimeout--;
}
timeoutID = window.setTimeout(_triggerTimeout, 500);
}
function _updateDetected() {
var autodetect = $('#features-autodetect input[type=checkbox]');
if ((autodetect.length > 0) && (!autodetect.is(':checked'))) return;
// query the server for a list of components/items in the feature and update
// the auto-detected items
var items = []; // will contain a list of selected items exported to feature
var components = {}; // contains object of component names that have checked items
$('#features-export-wrapper input[type=checkbox]:checked', context).each(function() {
if (!$(this).hasClass('features-checkall')) {
var key = $(this).attr('name');
var matches = key.match(/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/);
components[matches[1]] = matches[1];
if (!$(this).hasClass('component-detected')) {
items.push(key);
}
}
});
var featureName = $('#edit-module-name').val();
if (featureName == '') {
featureName = '*';
}
var url = Drupal.settings.basePath + 'features/ajaxcallback/' + featureName;
var excluded = Drupal.settings.features.excluded;
var postData = {'items': items, 'excluded': excluded};
jQuery.post(url, postData, function(data) {
if (inTimeout > 0) inTimeout--;
// if we have triggered another timeout then don't update with old results
if (inTimeout == 0) {
// data is an object keyed by component listing the exports of the feature
for (var component in data) {
var itemList = data[component];
$('#features-export-wrapper .component-' + component + ' input[type=checkbox]', context).each(function() {
var key = $(this).attr('value');
// first remove any auto-detected items that are no longer in component
if ($(this).hasClass('component-detected')) {
if (!(key in itemList)) {
moveCheckbox(this, 'select', false)
}
}
// next, add any new auto-detected items
else if ($(this).hasClass('component-select')) {
if (key in itemList) {
moveCheckbox(this, 'detected', itemList[key]);
$(this).parent().show(); // make sure it's not hidden from filter
}
}
});
}
// loop over all selected components and check for any that have been completely removed
for (var component in components) {
if ((data == null) || !(component in data)) {
$('#features-export-wrapper .component-' + component + ' input[type=checkbox].component-detected', context).each(function() {
moveCheckbox(this, 'select', false);
});
}
}
}
}, "json");
}
// Handle component selection UI
$('#features-export-wrapper input[type=checkbox]', 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 fieldset.features-export-component', context).each(function() {
// expand parent fieldset
var section = $(this).attr('id');
currentState[section] = !($(this).hasClass('collapsed'));
if (!(section in newState)) {
newState[section] = false;
}
$(this).find('div.component-select label').each(function() {
if (filter == '') {
if (currentState[section]) {
Drupal.toggleFieldset($('#'+section));
currentState[section] = false;
}
$(this).parent().show();
}
else if ($(this).text().match(regex)) {
$(this).parent().show();
newState[section] = true;
}
else {
$(this).parent().hide();
}
});
});
for (section in newState) {
if (currentState[section] != newState[section]) {
Drupal.toggleFieldset($('#'+section));
}
}
}
$('#features-filter input', context).bind("input", function() {
_resetFilterTimeout();
});
$('#features-filter .features-filter-clear', context).click(function() {
$('#features-filter input').val('');
_updateFilter();
});
// show the filter bar
$('#features-filter', context).removeClass('element-invisible');
}
}
Drupal.features = {
'checkStatus': function() {
$('table.features tbody tr').not('.processed').filter(':first').each(function() {
var elem = $(this);
$(elem).addClass('processed');
var uri = $(this).find('a.admin-check').attr('href');
if (uri) {
$.get(uri, [], function(data) {
$(elem).find('.admin-loading').hide();
switch (data.storage) {
case 3:
$(elem).find('.admin-rebuilding').show();
break;
case 2:
$(elem).find('.admin-needs-review').show();
break;
case 1:
$(elem).find('.admin-overridden').show();
break;
default:
$(elem).find('.admin-default').show();
break;
}
Drupal.features.checkStatus();
}, 'json');
}
else {
Drupal.features.checkStatus();
}
});
}
};
})(jQuery);

File diff suppressed because it is too large Load Diff

View 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;
}

View 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;
}

View File

@@ -0,0 +1,378 @@
<?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[] = ' if ($module == "'. $info['module'] .'" && $api == "'. $info['api'] .'") {';
$code[] = ' return array("version" => "'. $info['current_version'] .'");';
$code[] = ' }';
}
}
ctools_include('plugins');
$plugin_api_hook_name = ctools_plugin_api_get_hook($info['module'], $info['api']);
if (key_exists($plugin_api_hook_name, $component_exports)) {
$component_exports[$plugin_api_hook_name]['code'] .= "\n" . implode("\n", $code);
}
else {
$component_exports[$plugin_api_hook_name] = array(
'code' => implode("\n", $code),
'args' => '$module = NULL, $api = NULL',
);
}
}
return $component_exports;
}
/**
* Master implementation of hook_features_api() for all ctools components.
*
* Note that this master hook does not use $component like the others, but uses the
* component module's namespace instead.
*/
function ctools_component_features_api($module_name) {
$api = array();
foreach (_ctools_features_get_info() as $component => $info) {
// if module owner is set to "ctools" we need to compare the component
if ($info['module'] == $module_name || ($info['module'] === 'ctools' && $component == $module_name) ) {
$api[$component] = $info;
}
}
return $api;
}
/**
* Master implementation of hook_features_export_options() for all ctools components.
*/
function ctools_component_features_export_options($component) {
$options = array();
ctools_include('export');
$schema = ctools_export_get_schema($component);
if ($schema && $schema['export']['bulk export']) {
if (!empty($schema['export']['list callback']) && function_exists($schema['export']['list callback'])) {
$options = $schema['export']['list callback']();
}
else {
$options = _ctools_features_export_default_list($component, $schema);
}
}
asort($options);
return $options;
}
/**
* Master implementation of hook_features_export() for all ctools components.
*/
function ctools_component_features_export($component, $data, &$export, $module_name = '') {
// Add the actual implementing module as a dependency
$info = _ctools_features_get_info();
if ($module_name !== $info[$component]['module']) {
$export['dependencies'][$info[$component]['module']] = $info[$component]['module'];
}
// Add the components
foreach ($data as $object_name) {
if ($object = _ctools_features_export_crud_load($component, $object_name)) {
// If this object is provided as a default by a different module, don't
// export and add that module as a dependency instead.
if (!empty($object->export_module) && $object->export_module !== $module_name) {
$export['dependencies'][$object->export_module] = $object->export_module;
if (isset($export['features'][$component][$object_name])) {
unset($export['features'][$component][$object_name]);
}
}
// Otherwise, add the component.
else {
$export['features'][$component][$object_name] = $object_name;
}
}
}
// Let CTools handle API integration for this component.
return array('ctools' => array($component));
}
/**
* Master implementation of hook_features_export_render() for all ctools components.
*/
function ctools_component_features_export_render($component, $module, $data) {
// Reset the export display static to prevent clashes.
drupal_static_reset('panels_export_display');
ctools_include('export');
$schema = ctools_export_get_schema($component);
if (function_exists($schema['export']['to hook code callback'])) {
$export = $schema['export']['to hook code callback']($data, $module);
$code = explode("{\n", $export);
array_shift($code);
$code = explode('}', implode($code, "{\n"));
array_pop($code);
$code = implode('}', $code);
}
else {
$code = ' $export = array();'."\n\n";
foreach ($data as $object_name) {
if ($object = _ctools_features_export_crud_load($component, $object_name)) {
$identifier = $schema['export']['identifier'];
$code .= _ctools_features_export_crud_export($component, $object, ' ');
$code .= " \$export[" . ctools_var_export($object_name) . "] = \${$identifier};\n\n";
}
}
$code .= ' return $export;';
}
return array($schema['export']['default hook'] => $code);
}
/**
* Master implementation of hook_features_revert() for all ctools components.
*/
function ctools_component_features_revert($component, $module) {
if ($objects = features_get_default($component, $module)) {
foreach ($objects as $name => $object) {
// Some things (like views) do not use the machine name as key
// and need to be loaded explicitly in order to be deleted.
$object = ctools_export_crud_load($component, $name);
if ($object && ($object->export_type & EXPORT_IN_DATABASE)) {
_ctools_features_export_crud_delete($component, $object);
}
}
}
}
/**
* Helper function to return various ctools information for components.
*/
function _ctools_features_get_info($identifier = NULL, $reset = FALSE) {
static $components;
if (!isset($components) || $reset) {
$components = array();
$modules = features_get_info();
ctools_include('export');
drupal_static('ctools_export_get_schemas', NULL, $reset);
foreach (ctools_export_get_schemas_by_module() as $module => $schemas) {
foreach ($schemas as $table => $schema) {
if ($schema['export']['bulk export']) {
// Let the API owner take precedence as the owning module.
$api_module = isset($schema['export']['api']['owner']) ? $schema['export']['api']['owner'] : $module;
$components[$table] = array(
'name' => isset($modules[$api_module]->info['name']) ? $modules[$api_module]->info['name'] : $api_module,
'default_hook' => $schema['export']['default hook'],
'default_file' => FEATURES_DEFAULTS_CUSTOM,
'module' => $api_module,
'feature_source' => TRUE,
);
if (isset($schema['export']['api'])) {
$components[$table] += array(
'api' => $schema['export']['api']['api'],
'default_filename' => $schema['export']['api']['api'],
'current_version' => $schema['export']['api']['current_version'],
);
}
}
}
}
}
// Return information specific to a particular component.
if (isset($identifier)) {
// Identified by the table name.
if (isset($components[$identifier])) {
return $components[$identifier];
}
// New API identifier. Allows non-exportables related CTools APIs to be
// supported by an explicit `module:api:current_version` key.
else if (substr_count($identifier, ':') === 2) {
list($module, $api, $current_version) = explode(':', $identifier);
// If a schema component matches the provided identifier, provide that
// information. This also ensures that the version number is up to date.
foreach ($components as $table => $info) {
if ($info['module'] == $module && $info['api'] == $api && $info['current_version'] >= $current_version) {
return $info;
}
}
// Fallback to just giving back what was provided to us.
return array('module' => $module, 'api' => $api, 'current_version' => $current_version);
}
return FALSE;
}
return $components;
}
/**
* Wrapper around ctools_export_crud_export() for < 1.7 compatibility.
*/
function _ctools_features_export_crud_export($table, $object, $indent = '') {
return ctools_api_version('1.7') ? ctools_export_crud_export($table, $object, $indent) : ctools_export_object($table, $object, $indent);
}
/**
* Wrapper around ctools_export_crud_load() for < 1.7 compatibility.
*/
function _ctools_features_export_crud_load($table, $name) {
if (ctools_api_version('1.7')) {
return ctools_export_crud_load($table, $name);
}
elseif ($objects = ctools_export_load_object($table, 'names', array($name))) {
return array_shift($objects);
}
return FALSE;
}
/**
* Wrapper around ctools_export_default_list() for < 1.7 compatibility.
*/
function _ctools_features_export_default_list($table, $schema) {
if (ctools_api_version('1.7')) {
return ctools_export_default_list($table, $schema);
}
elseif ($objects = ctools_export_load_object($table, 'all')) {
return drupal_map_assoc(array_keys($objects));
}
return array();
}
/**
* Wrapper around ctools_export_crud_delete() for < 1.7 compatibility.
*/
function _ctools_features_export_crud_delete($table, $object) {
if (ctools_api_version('1.7')) {
ctools_export_crud_delete($table, $object);
}
else {
$schema = ctools_export_get_schema($table);
$export = $schema['export'];
db_query("DELETE FROM {{$table}} WHERE {$export['key']} = '%s'", $object->{$export['key']});
}
}
/**
* Implements hook_features_export_render() for page_manager.
*/
function page_manager_pages_features_export_render($module, $data) {
// Reset the export display static to prevent clashes.
drupal_static_reset('panels_export_display');
// Ensure that handlers have their code included before exporting.
page_manager_get_tasks();
return ctools_component_features_export_render('page_manager_pages', $module, $data);
}
/**
* Implements hook_features_revert() for page_manager.
*/
function page_manager_pages_features_revert($module) {
if ($pages = features_get_default('page_manager_pages', $module)) {
require_once drupal_get_path('module', 'ctools') . '/page_manager/plugins/tasks/page.inc';
foreach ($pages as $page) {
page_manager_page_delete($page);
}
}
}
/**
* Implements hook_features_pipe_COMPONENT_alter() for views_view.
*/
function views_features_pipe_views_view_alter(&$pipe, $data, $export) {
// @todo Remove this check before next stable release.
if (!function_exists('views_plugin_list')) {
return;
}
$map = array_flip($data);
foreach (views_plugin_list() as $plugin) {
foreach ($plugin['views'] as $view_name) {
if (isset($map[$view_name])) {
$pipe['dependencies'][$plugin['module']] = $plugin['module'];
}
}
}
}

View 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);
}
}
}

View File

@@ -0,0 +1,530 @@
<?php
/**
* Implements hook_features_api().
*/
function field_features_api() {
return array(
'field' => array(
// this is deprecated by field_base and field_instance
// but retained for compatibility with older exports
'name' => t('Fields'),
'default_hook' => 'field_default_fields',
'default_file' => FEATURES_DEFAULTS_INCLUDED,
'feature_source' => FALSE,
),
'field_base' => array(
'name' => t('Field Bases'),
'default_hook' => 'field_default_field_bases',
'default_file' => FEATURES_DEFAULTS_INCLUDED,
'feature_source' => TRUE,
'supersedes' => 'field',
),
'field_instance' => array(
'name' => t('Field Instances'),
'default_hook' => 'field_default_field_instances',
'default_file' => FEATURES_DEFAULTS_INCLUDED,
'feature_source' => TRUE,
'supersedes' => 'field',
)
);
}
/**
* Implements hook_features_export_options().
*/
function field_base_features_export_options() {
$options = array();
$fields = field_info_fields();
foreach ($fields as $field_name => $field) {
$options[$field_name] = $field_name;
}
return $options;
}
/**
* Implements hook_features_export_options().
*/
function field_instance_features_export_options() {
$options = array();
foreach (field_info_fields() as $field_name => $field) {
foreach ($field['bundles'] as $entity_type => $bundles) {
foreach ($bundles as $bundle) {
$identifier = "{$entity_type}-{$bundle}-{$field_name}";
$options[$identifier] = $identifier;
}
}
}
ksort($options);
return $options;
}
/**
* Implements hook_features_export().
*/
function field_base_features_export($data, &$export, $module_name = '') {
$pipe = array();
$map = features_get_default_map('field_base');
// The field_default_field_bases() hook integration is provided by the
// features module so we need to add it as a dependency.
$export['dependencies']['features'] = 'features';
foreach ($data as $identifier) {
if ($base = features_field_base_load($identifier)) {
// If this field is already provided by another module, remove the field
// and add the other module as a dependency.
if (isset($map[$identifier]) && $map[$identifier] != $module_name) {
if (isset($export['features']['field_base'][$identifier])) {
unset($export['features']['field_base'][$identifier]);
}
$module = $map[$identifier];
$export['dependencies'][$module] = $module;
}
// If the field has not yet been exported, add it
else {
$export['features']['field_base'][$identifier] = $identifier;
$export['dependencies'][$base['module']] = $base['module'];
if ($base['storage']['type'] != variable_get('field_storage_default', 'field_sql_storage')) {
$export['dependencies'][$base['storage']['module']] = $base['storage']['module'];
}
// If taxonomy field, add in the vocabulary
if ($base['type'] == 'taxonomy_term_reference' && !empty($base['settings']['allowed_values'])) {
foreach ($base['settings']['allowed_values'] as $allowed_values) {
if (!empty($allowed_values['vocabulary'])) {
$pipe['taxonomy'][] = $allowed_values['vocabulary'];
}
}
}
}
}
}
return $pipe;
}
/**
* Implements hook_features_export().
*/
function field_instance_features_export($data, &$export, $module_name = '') {
$pipe = array('field_base' => array());
$map = features_get_default_map('field_instance');
// The field_default_field_instances() hook integration is provided by the
// features module so we need to add it as a dependency.
$export['dependencies']['features'] = 'features';
foreach ($data as $identifier) {
if ($instance = features_field_instance_load($identifier)) {
// If this field is already provided by another module, remove the field
// and add the other module as a dependency.
if (isset($map[$identifier]) && $map[$identifier] != $module_name) {
if (isset($export['features']['field_instance'][$identifier])) {
unset($export['features']['field_instance'][$identifier]);
}
$module = $map[$identifier];
$export['dependencies'][$module] = $module;
}
// If the field has not yet been exported, add it
else {
$export['features']['field_instance'][$identifier] = $identifier;
$export['dependencies'][$instance['widget']['module']] = $instance['widget']['module'];
foreach ($instance['display'] as $key => $display) {
if (isset($display['module'])) {
$export['dependencies'][$display['module']] = $display['module'];
// @TODO: handle the pipe to image styles
}
}
$pipe['field_base'][] = $instance['field_name'];
}
}
}
return $pipe;
}
/**
* Implements hook_features_export_render().
*/
function field_base_features_export_render($module, $data, $export = NULL) {
$translatables = $code = array();
$code[] = ' $field_bases = array();';
$code[] = '';
foreach ($data as $identifier) {
if ($field = features_field_base_load($identifier)) {
unset($field['columns']);
// unset($field['locked']);
// Only remove the 'storage' declaration if the field is using the default
// storage type.
if ($field['storage']['type'] == variable_get('field_storage_default', 'field_sql_storage')) {
unset($field['storage']);
}
// If we still have a storage declaration here it means that a non-default
// storage type was altered into to the field definition. And noone would
// never need to change the 'details' key, so don't render it.
if (isset($field['storage']['details'])) {
unset($field['storage']['details']);
}
_field_instance_features_export_sort($field);
$field_export = features_var_export($field, ' ');
$field_identifier = features_var_export($identifier);
$code[] = " // Exported field_base: {$field_identifier}";
$code[] = " \$field_bases[{$field_identifier}] = {$field_export};";
$code[] = "";
}
}
$code[] = ' return $field_bases;';
$code = implode("\n", $code);
return array('field_default_field_bases' => $code);
}
/**
* Implements hook_features_export_render().
*/
function field_instance_features_export_render($module, $data, $export = NULL) {
$translatables = $code = array();
$code[] = ' $field_instances = array();';
$code[] = '';
foreach ($data as $identifier) {
if ($instance = features_field_instance_load($identifier)) {
_field_instance_features_export_sort($instance);
$field_export = features_var_export($instance, ' ');
$instance_identifier = features_var_export($identifier);
$code[] = " // Exported field_instance: {$instance_identifier}";
$code[] = " \$field_instances[{$instance_identifier}] = {$field_export};";
$code[] = "";
if (!empty($instance['label'])) {
$translatables[] = $instance['label'];
}
if (!empty($instance['description'])) {
$translatables[] = $instance['description'];
}
}
}
if (!empty($translatables)) {
$code[] = features_translatables_export($translatables, ' ');
}
$code[] = ' return $field_instances;';
$code = implode("\n", $code);
return array('field_default_field_instances' => $code);
}
// Helper to enforce consistency in field export arrays.
function _field_instance_features_export_sort(&$field, $sort = TRUE) {
// Some arrays are not sorted to preserve order (for example allowed_values).
static $sort_blacklist = array(
'allowed_values',
'format_handlers',
);
if ($sort) {
uksort($field, 'strnatcmp');
}
foreach ($field as $k => $v) {
if (is_array($v)) {
_field_instance_features_export_sort($field[$k], !in_array($k, $sort_blacklist));
}
}
}
/**
* Implements hook_features_revert().
*/
function field_base_features_revert($module) {
field_base_features_rebuild($module);
}
/**
* Implements hook_features_revert().
*/
function field_instance_features_revert($module) {
field_instance_features_rebuild($module);
}
/**
* Implements of hook_features_rebuild().
* Rebuilds fields from code defaults.
*/
function field_base_features_rebuild($module) {
if ($fields = features_get_default('field_base', $module)) {
field_info_cache_clear();
// Load all the existing field bases up-front so that we don't
// have to rebuild the cache all the time.
$existing_fields = field_info_fields();
foreach ($fields as $field) {
// Create or update field.
if (isset($existing_fields[$field['field_name']])) {
$existing_field = $existing_fields[$field['field_name']];
if ($field + $existing_field !== $existing_field) {
field_update_field($field);
}
}
else {
field_create_field($field);
$existing_fields[$field['field_name']] = $field;
}
variable_set('menu_rebuild_needed', TRUE);
}
}
}
/**
* Implements of hook_features_rebuild().
* Rebuilds field instances from code defaults.
*/
function field_instance_features_rebuild($module) {
if ($instances = features_get_default('field_instance', $module)) {
field_info_cache_clear();
// Load all the existing instances up-front so that we don't
// have to rebuild the cache all the time.
$existing_instances = field_info_instances();
foreach ($instances as $field_instance) {
// If the field base information does not exist yet, cancel out.
if (!field_info_field($field_instance['field_name'])) {
continue;
}
// Create or update field instance.
if (isset($existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']])) {
$existing_instance = $existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']];
if ($field_instance + $existing_instance !== $existing_instance) {
try {
field_update_instance($field_instance);
}
catch (FieldException $e) {
watchdog('features', 'Attempt to update field instance %label (in %entity entity type %bundle bundle) failed: %message', array('%label' => $field_instance['field_name'], '%entity' => $field_instance['entity_type'], '%bundle' => $field_instance['bundle'], '%message' => $e->getMessage()), WATCHDOG_ERROR);
}
}
}
else {
try {
field_create_instance($field_instance);
}
catch (FieldException $e) {
watchdog('features', 'Attempt to create field instance %label (in %entity entity type %bundle bundle) failed: %message', array('%label' => $field_instance['field_name'], '%entity' => $field_instance['entity_type'], '%bundle' => $field_instance['bundle'], '%message' => $e->getMessage()), WATCHDOG_ERROR);
}
$existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']] = $field_instance;
}
}
if ($instances) {
variable_set('menu_rebuild_needed', TRUE);
}
}
}
/**
* Load a field base configuration by a field_name identifier.
*/
function features_field_base_load($field_name) {
if ($field_info = field_info_field($field_name)) {
unset($field_info['id']);
unset($field_info['bundles']);
return $field_info;
}
return FALSE;
}
/**
* Load a field's instance configuration by an entity_type-bundle-field_name
* identifier.
*/
function features_field_instance_load($identifier) {
list($entity_type, $bundle, $field_name) = explode('-', $identifier);
if ($instance_info = field_info_instance($entity_type, $field_name, $bundle)) {
unset($instance_info['id']);
unset($instance_info['field_id']);
return $instance_info;
}
return FALSE;
}
/* ----- DEPRECATED FIELD EXPORT -----
* keep this code for backward compatibility with older exports
* until v3.x
*/
/**
* Implements hook_features_export_options().
*/
function field_features_export_options() {
$options = array();
$instances = field_info_instances();
foreach ($instances as $entity_type => $bundles) {
foreach ($bundles as $bundle => $fields) {
foreach ($fields as $field) {
$identifier = "{$entity_type}-{$bundle}-{$field['field_name']}";
$options[$identifier] = $identifier;
}
}
}
return $options;
}
/**
* Implements hook_features_export().
*/
function field_features_export($data, &$export, $module_name = '') {
$pipe = array();
// Convert 'field' to 'field_instance' on features-update.
$pipe['field_instance'] = $data;
return $pipe;
}
/**
* Implements hook_features_export_render().
*/
function field_features_export_render($module, $data, $export = NULL) {
$translatables = $code = array();
$code[] = ' $fields = array();';
$code[] = '';
foreach ($data as $identifier) {
if ($field = features_field_load($identifier)) {
unset($field['field_config']['columns']);
// Only remove the 'storage' declaration if the field is using the default
// storage type.
if ($field['field_config']['storage']['type'] == variable_get('field_storage_default', 'field_sql_storage')) {
unset($field['field_config']['storage']);
}
// If we still have a storage declaration here it means that a non-default
// storage type was altered into to the field definition. And 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) {
try {
field_update_field($field_config);
}
catch (FieldException $e) {
watchdog('features', 'Attempt to update field %label failed: %message', array('%label' => $field_config['field_name'], '%message' => $e->getMessage()), WATCHDOG_ERROR);
}
}
}
else {
try {
field_create_field($field_config);
}
catch (FieldException $e) {
watchdog('features', 'Attempt to create field %label failed: %message', array('%label' => $field_config['field_name'], '%message' => $e->getMessage()), WATCHDOG_ERROR);
}
$existing_fields[$field_config['field_name']] = $field_config;
}
// Create or update field instance.
$field_instance = $field['field_instance'];
if (isset($existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']])) {
$existing_instance = $existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']];
if ($field_instance + $existing_instance !== $existing_instance) {
field_update_instance($field_instance);
}
}
else {
field_create_instance($field_instance);
$existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']] = $field_instance;
}
}
if ($fields) {
variable_set('menu_rebuild_needed', TRUE);
}
}
}
/**
* Load a field's configuration and instance configuration by an
* entity_type-bundle-field_name identifier.
*/
function features_field_load($identifier) {
list($entity_type, $bundle, $field_name) = explode('-', $identifier);
$field_info = field_info_field($field_name);
$instance_info = field_info_instance($entity_type, $field_name, $bundle);
if ($field_info && $instance_info) {
unset($field_info['id']);
unset($field_info['bundles']);
unset($instance_info['id']);
unset($instance_info['field_id']);
return array(
'field_config' => $field_info,
'field_instance' => $instance_info,
);
}
return FALSE;
}

View 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;
}

View 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);
}
}
}
}

View File

@@ -0,0 +1,162 @@
<?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', 'javascript');
// The javascript hash is not in the imported data but should be empty
if (!isset($language->javascript)) {
$language->javascript = '';
}
$fields = array_intersect_key((array) $language, array_flip($properties));
db_update('languages')
->fields($fields)
->condition('language', $language->language)
->execute();
// Set the default language when needed.
$default = language_default();
if ($default->language == $language->language) {
variable_set('language_default', (object) $fields);
}
}
}

View File

@@ -0,0 +1,414 @@
<?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;
$use_menus = array_intersect_key(menu_get_menus(), array_flip(array_filter(variable_get('features_admin_menu_links_menus', array_keys(menu_get_menus())))));
$menu_links = menu_parent_options($use_menus, array('mlid' => 0));
$options = array();
foreach ($menu_links as $key => $name) {
list($menu_name, $mlid) = explode(':', $key, 2);
if ($mlid != 0) {
$link = menu_link_load($mlid);
$identifier = menu_links_features_identifier($link, TRUE);
$options[$identifier] = "{$menu_name}: {$name}";
}
}
$menu_admin = FALSE;
return $options;
}
/**
* Callback for generating the menu link exportable identifier.
*/
function menu_links_features_identifier($link, $old = FALSE) {
// Add some uniqueness to these identifiers, allowing multiple links with the same path, but different titles.
$clean_title = features_clean_title(isset($link['title']) ? $link['title'] : $link['link_title']);
// The old identifier is requested.
if ($old) {
// if identifier already exists
if (isset($link['options']['identifier'])) {
return $link['options']['identifier'];
}
// providing backward compatibility and allowing/enabling multiple links with same paths
else {
$identifier = isset($link['menu_name'], $link['link_path']) ? "{$link['menu_name']}:{$link['link_path']}" : FALSE;
// Checking if there are multiples of this identifier
if (features_menu_link_load($identifier) !== FALSE) {
// this is where we return the upgrade posibility for links.
return $identifier;
}
}
}
return isset($link['menu_name'], $link['link_path']) ? "{$link['menu_name']}_{$clean_title}:{$link['link_path']}" : FALSE;
}
/**
* Implements hook_features_export().
*/
function menu_links_features_export($data, &$export, $module_name = '') {
// Default hooks are provided by the feature module so we need to add
// it as a dependency.
$export['dependencies']['features'] = 'features';
$export['dependencies']['menu'] = 'menu';
// Collect a link to module map
$pipe = array();
$map = features_get_default_map('menu_links', 'menu_links_features_identifier');
foreach ($data as $key => $identifier) {
if ($link = features_menu_link_load($identifier)) {
// If this link is provided by a different module, add it as a dependency.
$new_identifier = menu_links_features_identifier($link, empty($export));
if (isset($map[$identifier]) && $map[$identifier] != $module_name) {
$export['dependencies'][$map[$identifier]] = $map[$identifier];
}
else {
$export['features']['menu_links'][$new_identifier] = $new_identifier;
}
// For now, exclude a variety of common menus from automatic export.
// They may still be explicitly included in a Feature if the builder
// chooses to do so.
if (!in_array($link['menu_name'], array('features', 'primary-links', 'secondary-links', 'navigation', 'admin', 'devel'))) {
$pipe['menu_custom'][] = $link['menu_name'];
}
}
}
return $pipe;
}
/**
* Implements hook_features_export_render()
*/
function menu_links_features_export_render($module, $data, $export = NULL) {
$code = array();
$code[] = ' $menu_links = array();';
$code[] = '';
$translatables = array();
foreach ($data as $identifier) {
if ($link = features_menu_link_load($identifier)) {
$new_identifier = menu_links_features_identifier($link, empty($export));
// Replace plid with a parent path.
if (!empty($link['plid']) && $parent = menu_link_load($link['plid'])) {
// If the new identifier is different than the old, maintain
// 'parent_path' for backwards compatibility.
if ($new_identifier != menu_links_features_identifier($link)) {
$link['parent_path'] = $parent['link_path'];
}
else {
$clean_title = features_clean_title($parent['title']);
$link['parent_identifier'] = "{$parent['menu_name']}_{$clean_title}:{$parent['link_path']}";
}
}
if (isset($export)) {
// Don't show new identifier unless we are actually exporting.
$link['options']['identifier'] = $new_identifier;
// identifiers are renewed, => that means we need to update them in the db
menu_link_save($temp = $link);
}
unset($link['plid']);
unset($link['mlid']);
$code[] = " // Exported menu link: {$new_identifier}";
$code[] = " \$menu_links['{$new_identifier}'] = ". features_var_export($link, ' ') .";";
$translatables[] = $link['link_title'];
}
}
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_identifier']) ? $link['parent_identifier'] : '';
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_identifier']) && $parent = features_menu_link_load($link['parent_identifier'])) {
$link['plid'] = $parent['mlid'];
}
// This if for backwards compatibility.
elseif (!empty($link['parent_path']) && $parent = features_menu_link_load("{$link['menu_name']}:{$link['parent_path']}")) {
$link['plid'] = $parent['mlid'];
}
else {
$link['plid'] = 0;
}
menu_link_save($link);
}
}
}
/**
* Load a menu link by its menu_name_cleantitle:link_path identifier.
* Also matches links with unique menu_name:link_path
*/
function features_menu_link_load($identifier) {
$menu_name = '';
$link_path = '';
// This gets variables for menu_name_cleantitle:link_path format.
if (strstr($identifier, "_")) {
$link_path = substr($identifier, strpos($identifier, ":") + 1);
list($menu_name) = explode('_', $identifier, 2);
$clean_title = substr($identifier, strpos($identifier, "_") + 1, strpos($identifier, ":") - strpos($identifier, "_") - 1);
}
// This gets variables for traditional identifier format.
else {
$clean_title = '';
list($menu_name, $link_path) = explode(':', $identifier, 2);
}
$links = db_select('menu_links')
->fields('menu_links', array('menu_name', 'mlid', 'plid', 'link_path', 'router_path', 'link_title', 'options', 'module', 'hidden', 'external', 'has_children', 'expanded', 'weight', 'customized'))
->condition('menu_name', $menu_name)
->condition('link_path', $link_path)
->execute()
->fetchAllAssoc('mlid');
foreach($links as $link) {
$link->options = unserialize($link->options);
// Title or previous identifier matches.
if ((isset($link->options['identifier']) && strcmp($link->options['identifier'], $identifier) == 0)
|| (isset($clean_title) && strcmp(features_clean_title($link->link_title), $clean_title) == 0)) {
return (array)$link;
}
}
// Only one link with the requested menu_name and link_path does exists,
// -- providing an upgrade possibility for links saved in a feature before the
// new identifier-pattern was added.
if (count($links) == 1 && empty($clean_title)) {
$link = reset($links); // get the first item
return (array)$link;
}
// If link_path was changed on an existing link, we need to find it by
// searching for link_title.
else if (isset($clean_title)) {
$links = db_select('menu_links')
->fields('menu_links', array('menu_name', 'mlid', 'plid', 'link_path', 'router_path', 'link_title', 'options', 'module', 'hidden', 'external', 'has_children', 'expanded', 'weight'))
->condition('menu_name', $menu_name)
->execute()
->fetchAllAssoc('mlid');
foreach($links as $link) {
$link->options = unserialize($link->options);
// title or previous identifier matches
if ((isset($link->options['identifier']) && strcmp($link->options['identifier'], $identifier) == 0)
|| (isset($clean_title) && strcmp(features_clean_title($link->link_title), $clean_title) == 0)) {
return (array)$link;
}
}
}
return FALSE;
}
/**
* Returns a lowercase clean string with only letters, numbers and dashes
*/
function features_clean_title($str) {
return strtolower(preg_replace_callback('/(\s)|([^a-zA-Z\-0-9])/i', create_function(
'$matches',
'return $matches[1]?"-":"";'
), $str));
}

View 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_instance'][] = "node-{$field['bundle']}-{$field['field_name']}";
}
}
}
return $pipe;
}
/**
* Implements hook_features_export_render().
*/
function node_features_export_render($module, $data, $export = NULL) {
$elements = array(
'name' => TRUE,
'base' => FALSE,
'description' => TRUE,
'has_title' => FALSE,
'title_label' => TRUE,
'help' => TRUE,
);
$output = array();
$output[] = ' $items = array(';
foreach ($data as $type) {
if ($info = node_type_get_type($type)) {
// Force module name to be 'features' if set to 'node. If we leave as
// 'node' the content type will be assumed to be database-stored by
// the node module.
$info->base = ($info->base === 'node') ? 'features' : $info->base;
$output[] = " '{$type}' => array(";
foreach ($elements as $key => $t) {
if ($t) {
$text = str_replace("'", "\'", $info->$key);
$text = !empty($text) ? "t('{$text}')" : "''";
$output[] = " '{$key}' => {$text},";
}
else {
$output[] = " '{$key}' => '{$info->$key}',";
}
}
$output[] = " ),";
}
}
$output[] = ' );';
$output[] = ' 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);
}
}
}
}

View File

@@ -0,0 +1,105 @@
<?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']}";
$pipe['field_instance'][] = "taxonomy_term-{$field['bundle']}-{$field['field_name']}";
}
}
}
return $pipe;
}
/**
* Implements hook_features_export_render().
*/
function taxonomy_features_export_render($module, $data) {
$vocabularies = taxonomy_get_vocabularies();
$code = array();
foreach ($data as $machine_name) {
foreach ($vocabularies as $vocabulary) {
if ($vocabulary->machine_name == $machine_name) {
// We don't want to break the entity cache, so we need to clone the
// vocabulary before unsetting the id.
$vocabulary = clone $vocabulary;
unset($vocabulary->vid);
$code[$machine_name] = $vocabulary;
}
}
}
$code = " return ". features_var_export($code, ' ') .";";
return array('taxonomy_default_vocabularies' => $code);
}
/**
* Implements hook_features_revert().
*/
function taxonomy_features_revert($module) {
taxonomy_features_rebuild($module);
}
/**
* Implements hook_features_rebuild().
*
* Rebuilds Taxonomy vocabularies from code defaults.
*/
function taxonomy_features_rebuild($module) {
if ($vocabularies = features_get_default('taxonomy', $module)) {
$existing = taxonomy_get_vocabularies();
foreach ($vocabularies as $vocabulary) {
$vocabulary = (object) $vocabulary;
foreach ($existing as $existing_vocab) {
if ($existing_vocab->machine_name === $vocabulary->machine_name) {
$vocabulary->vid = $existing_vocab->vid;
}
}
taxonomy_vocabulary_save($vocabulary);
}
}
}

View File

@@ -0,0 +1,290 @@
<?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) {
$perm_name = $perm;
// Export vocabulary permissions using the machine name, instead of
// vocabulary id.
_user_features_change_term_permission($perm_name, 'machine_name');
if (isset($map[$perm_name])) {
$perm_module = $map[$perm_name];
$export['dependencies'][$perm_module] = $perm_module;
$export['features']['user_permission'][$perm] = $perm;
}
}
return array();
}
/**
* Implements hook_features_export_options().
*/
function user_permission_features_export_options() {
$modules = array();
$module_info = system_get_info('module');
foreach (module_implements('permission') as $module) {
$modules[$module_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) {
// Export vocabulary permissions using the machine name, instead of
// vocabulary id.
_user_features_change_term_permission($perm);
$options[$perm] = strip_tags("{$display_name}: {$perm_item['title']}");
}
}
}
return $options;
}
/**
* Implements hook_features_export_render().
*/
function user_permission_features_export_render($module, $data) {
$perm_modules = &drupal_static(__FUNCTION__ . '_perm_modules');
if (!isset($perm_modules)) {
$perm_modules = user_permission_get_modules();
}
$code = array();
$code[] = ' $permissions = array();';
$code[] = '';
$permissions = _user_features_get_permissions();
foreach ($data as $perm_name) {
$permission = array();
// Export vocabulary permissions using the machine name, instead of
// vocabulary id.
$perm = $perm_name;
_user_features_change_term_permission($perm_name, 'machine_name');
$permission['name'] = $perm;
if (!empty($permissions[$perm_name])) {
sort($permissions[$perm_name]);
$permission['roles'] = drupal_map_assoc($permissions[$perm_name]);
}
else {
$permission['roles'] = array();
}
if (isset($perm_modules[$perm_name])) {
$permission['module'] = $perm_modules[$perm_name];
}
$perm_identifier = features_var_export($perm);
$perm_export = features_var_export($permission, ' ');
$code[] = " // Exported permission: {$perm_identifier}.";
$code[] = " \$permissions[{$perm_identifier}] = {$perm_export};";
$code[] = "";
}
$code[] = ' return $permissions;';
$code = implode("\n", $code);
return array('user_default_permissions' => $code);
}
/**
* Implements hook_features_revert().
*/
function user_permission_features_revert($module) {
user_permission_features_rebuild($module);
}
/**
* Implements hook_features_rebuild().
* Iterate through default permissions and update the permissions map.
*
* @param $module
* The module whose default user permissions should be rebuilt.
*/
function user_permission_features_rebuild($module) {
if ($defaults = features_get_default('user_permission', $module)) {
// Make sure the list of available node types is up to date, especially when
// installing multiple features at once, for example from an install profile
// or via drush.
node_types_rebuild();
$modules = user_permission_get_modules();
$roles = _user_features_get_roles();
$permissions_by_role = _user_features_get_permissions(FALSE);
foreach ($defaults as $permission) {
$perm = $permission['name'];
_user_features_change_term_permission($perm, 'machine_name');
if (empty($modules[$perm])) {
$args = array('!name' => $perm, '!module' => $module,);
$msg = t('Warning in features rebuild of !module. No module defines permission "!name".', $args);
drupal_set_message($msg, 'warning');
continue;
}
// Export vocabulary permissions using the machine name, instead of
// vocabulary id.
foreach ($roles as $role) {
if (in_array($role, $permission['roles'])) {
$permissions_by_role[$role][$perm] = TRUE;
}
else {
$permissions_by_role[$role][$perm] = FALSE;
}
}
}
// Write the updated permissions.
foreach ($roles as $rid => $role) {
if (isset($permissions_by_role[$role])) {
user_role_change_permissions($rid, $permissions_by_role[$role]);
}
}
}
}
/**
* Implements hook_features_export().
*/
function user_role_features_export($data, &$export, $module_name = '') {
$export['dependencies']['features'] = 'features';
$map = features_get_default_map('user_role', 'name');
foreach ($data as $role) {
// Role is provided by another module. Add dependency.
if (isset($map[$role]) && $map[$role] != $module_name) {
$export['dependencies'][$map[$role]] = $map[$role];
}
// Export.
elseif(user_role_load_by_name($role)) {
$export['features']['user_role'][$role] = $role;
}
}
return array();
}
/**
* Implements hook_features_export_options().
*/
function user_role_features_export_options() {
return drupal_map_assoc(_user_features_get_roles(FALSE));
}
/**
* Implements hook_features_export_render().
*/
function user_role_features_export_render($module, $data) {
$code = array();
$code[] = ' $roles = array();';
$code[] = '';
foreach ($data as $name) {
if ($role = user_role_load_by_name($name)) {
unset($role->rid);
$role_identifier = features_var_export($name);
$role_export = features_var_export($role , ' ');
$code[] = " // Exported role: {$name}.";
$code[] = " \$roles[{$role_identifier}] = {$role_export};";
$code[] = "";
}
}
$code[] = ' return $roles;';
$code = implode("\n", $code);
return array('user_default_roles' => $code);
}
/**
* Implements hook_features_revert().
*/
function user_role_features_revert($module) {
user_role_features_rebuild($module);
}
/**
* Implements hook_features_rebuild().
*/
function user_role_features_rebuild($module) {
if ($defaults = features_get_default('user_role', $module)) {
foreach ($defaults as $role) {
$role = (object) $role;
if ($existing = user_role_load_by_name($role->name)) {
$role->rid = $existing->rid;
}
user_role_save($role);
}
}
}
/**
* Generate $rid => $role with role names untranslated.
*/
function _user_features_get_roles($builtin = TRUE) {
$roles = array();
foreach (user_roles() as $rid => $name) {
switch ($rid) {
case DRUPAL_ANONYMOUS_RID:
if ($builtin) {
$roles[$rid] = 'anonymous user';
}
break;
case DRUPAL_AUTHENTICATED_RID:
if ($builtin) {
$roles[$rid] = 'authenticated user';
}
break;
default:
$roles[$rid] = $name;
break;
}
}
return $roles;
}
/**
* Represent the current state of permissions as a perm to role name array map.
*/
function _user_features_get_permissions($by_role = TRUE) {
$map = user_permission_get_modules();
$roles = _user_features_get_roles();
$permissions = array();
foreach (user_role_permissions($roles) as $rid => $role_permissions) {
if ($by_role) {
foreach (array_keys(array_filter($role_permissions)) as $permission) {
if (isset($map[$permission])) {
$permissions[$permission][] = $roles[$rid];
}
}
}
else {
$permissions[$roles[$rid]] = array();
foreach ($role_permissions as $permission => $status) {
if (isset($map[$permission])) {
$permissions[$roles[$rid]][$permission] = $status;
}
}
}
}
return $permissions;
}

View File

@@ -0,0 +1,289 @@
<?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_instance' => 'field',
'filter' => 'filter',
'image' => 'image',
'node' => 'node',
'user_permission' => 'user',
'views_view' => 'views',
), 'module_exists');
foreach (array_keys($components) as $component) {
$callback = "_test_{$component}";
// Ensure that the component/default is properly available.
$object = $this->$callback('load');
$this->assertTrue(!empty($object), t('@component present.', array('@component' => $component)));
// Ensure that the component is defaulted.
$states = features_get_component_states(array('features_test'), FALSE, TRUE);
$this->assertTrue($states['features_test'][$component] === FEATURES_DEFAULT, t('@component state: Default.', array('@component' => $component)));
// Override component and test that Features detects the override.
$this->$callback('override', $this);
$states = features_get_component_states(array('features_test'), FALSE, TRUE);
$this->assertTrue($states['features_test'][$component] === FEATURES_OVERRIDDEN, t('@component state: Overridden.', array('@component' => $component)));
}
// Revert component and ensure that component has reverted.
// Do this in separate loops so we only have to run
// drupal_flush_all_caches() once.
foreach (array_keys($components) as $component) {
features_revert(array('features_test' => array($component)));
}
drupal_flush_all_caches();
foreach (array_keys($components) as $component) {
// Reload so things like Views can clear it's cache
$this->$callback('load');
$states = features_get_component_states(array('features_test'), FALSE, TRUE);
$this->assertTrue($states['features_test'][$component] === FEATURES_DEFAULT, t('@component reverted.', array('@component' => $component)));
}
}
protected function _test_field_instance($op = 'load') {
switch ($op) {
case 'load':
return field_info_instance('node', 'field_features_test', 'features_test');
case 'override':
$field_instance = field_info_instance('node', 'field_features_test', 'features_test');
$field_instance['label'] = 'Foo bar';
field_update_instance($field_instance);
break;
}
}
protected function _test_filter($op = 'load') {
// So... relying on our own API functions to test is pretty lame.
// But these modules don't have APIs either. So might as well use
// the ones we've written for them...
features_include();
switch ($op) {
case 'load':
return features_filter_format_load('features_test');
case 'override':
$format = features_filter_format_load('features_test');
unset($format->filters['filter_url']);
filter_format_save($format);
break;
}
}
protected function _test_image($op = 'load') {
switch ($op) {
case 'load':
return image_style_load('features_test');
case 'override':
$style = image_style_load('features_test');
$style = image_style_save($style);
foreach ($style['effects'] as $effect) {
$effect['data']['width'] = '120';
image_effect_save($effect);
}
break;
}
}
protected function _test_node($op = 'load') {
switch ($op) {
case 'load':
return node_type_get_type('features_test');
case 'override':
$type = node_type_get_type('features_test');
$type->description = 'Foo bar baz.';
$type->modified = TRUE;
node_type_save($type);
break;
}
}
protected function _test_views_view($op = 'load') {
switch ($op) {
case 'load':
return views_get_view('features_test', TRUE);
case 'override':
$view = views_get_view('features_test', TRUE);
$view->set_display('default');
$view->display_handler->override_option('title', 'Foo bar');
$view->save();
// Clear the load cache from above
views_get_view('features_test', TRUE);
break;
}
}
protected function _test_user_permission($op = 'load') {
switch ($op) {
case 'load':
$permissions = user_role_permissions(array(DRUPAL_AUTHENTICATED_RID => 'authenticated user'));
return !empty($permissions[DRUPAL_AUTHENTICATED_RID]['create features_test content']);
case 'override':
user_role_change_permissions(DRUPAL_AUTHENTICATED_RID, array('create features_test content' => 0));
break;
}
}
}
/**
* Tests enabling of feature modules.
*/
class FeaturesEnableTestCase extends DrupalWebTestCase {
protected $profile = 'testing';
/**
* Test info.
*/
public static function getInfo() {
return array(
'name' => t('Features enable tests'),
'description' => t('Run tests for enabling of features.') ,
'group' => t('Features'),
);
}
/**
* Run test for features_get_components on enable.
*/
public function testFeaturesGetComponents() {
// Testing that features_get_components returns correct after enable.
$modules = array(
'features',
'taxonomy',
'features_test',
);
// Make sure features_get_components is cached if features already enabled.
if (!module_exists('features')) {
drupal_load('module', 'features');
}
features_get_components();
module_enable($modules);
// Make sure correct information for enabled modules is now cached.
$components = features_get_components();
$taxonomy_component_info = taxonomy_features_api();
$this->assertTrue(!empty($components['taxonomy']) && $components['taxonomy'] == $taxonomy_component_info['taxonomy'], 'features_get_components returns correct taxonomy information on enable');
features_rebuild();
$this->assertNotNull(taxonomy_vocabulary_machine_name_load('taxonomy_features_test'), 'Taxonomy vocabulary correctly enabled on enable.');
}
}
/**
* Tests 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));
}
// CTools hooks only created when there is an actual feature exportable
// enabled.
module_enable(array('features_test'));
foreach ($try as $module) {
if (module_exists($module)) {
$function_exists = function_exists($function);
if ($function_exists) {
foreach ($function() as $component_type => $component_info) {
foreach ($function_ends as $function_end) {
$function_exists = $function_exists && function_exists($component_type . '_' . $function_end);
}
}
}
$this->assertTrue($function_exists, 'Chaos tools functions for ' . $module . ' exist when it is enabled.');
}
}
}
}

View File

@@ -0,0 +1,43 @@
<?php
/**
* @file
* features_test.features.field_base.inc
*/
/**
* Implements hook_field_default_field_bases().
*/
function features_test_field_default_field_bases() {
$field_bases = array();
// Exported field_base: 'field_features_test'
$field_bases['field_features_test'] = 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',
),
),
'locked' => 0,
'module' => 'text',
'settings' => array(
'max_length' => 255,
),
'translatable' => 1,
'type' => 'text',
);
return $field_bases;
}

View File

@@ -0,0 +1,76 @@
<?php
/**
* @file
* features_test.features.field_instance.inc
*/
/**
* Implements hook_field_default_field_instances().
*/
function features_test_field_default_field_instances() {
$field_instances = array();
// Exported field_instance: 'node-features_test-field_features_test'
$field_instances['node-features_test-field_features_test'] = 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 $field_instances;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,72 @@
<?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("api" => "3.0");
}
/**
* Implements hook_image_default_styles().
*/
function features_test_image_default_styles() {
$styles = array();
// Exported image style: features_test.
$styles['features_test'] = array(
'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,
),
),
'label' => 'features_test',
);
return $styles;
}
/**
* Implements hook_node_info().
*/
function features_test_node_info() {
$items = array(
'features_test' => array(
'name' => t('Testing: Features'),
'base' => 'node_content',
'description' => t('Content type provided for Features tests.'),
'has_title' => '1',
'title_label' => t('Title'),
'help' => '',
),
);
return $items;
}

View File

@@ -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',
),
),
),
),
);
}

View File

@@ -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(
'anonymous user' => 'anonymous user',
'authenticated user' => 'authenticated user',
),
'module' => 'node',
);
return $permissions;
}

View File

@@ -0,0 +1,29 @@
name = Features Tests
description = Test module for Features testing.
core = 7.x
package = Testing
php = 5.2.0
dependencies[] = features
dependencies[] = image
dependencies[] = strongarm
dependencies[] = taxonomy
dependencies[] = views
features[ctools][] = strongarm:strongarm:1
features[ctools][] = views:views_default:3.0
features[features_api][] = api:2
features[field_base][] = field_features_test
features[field_instance][] = node-features_test-field_features_test
features[filter][] = features_test
features[image][] = features_test
features[node][] = features_test
features[taxonomy][] = taxonomy_features_test
features[user_permission][] = create features_test content
features[views_view][] = features_test
hidden = 1
; Information added by drupal.org packaging script on 2013-10-17
version = "7.x-2.0+0-dev"
core = "7.x"
project = "features"
datestamp = "1382036080"

View File

@@ -0,0 +1,3 @@
<?php
include_once('features_test.features.inc');

View File

@@ -0,0 +1,38 @@
<?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['use_more_always'] = FALSE;
$handler->display->display_options['access']['type'] = 'none';
$handler->display->display_options['cache']['type'] = 'none';
$handler->display->display_options['query']['type'] = 'views_query';
$handler->display->display_options['query']['options']['query_comment'] = FALSE;
$handler->display->display_options['exposed_form']['type'] = 'basic';
$handler->display->display_options['pager']['type'] = 'full';
$handler->display->display_options['style_plugin'] = 'default';
$handler->display->display_options['row_plugin'] = 'node';
$export['features_test'] = $view;
return $export;
}

View File

@@ -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>

View 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>";
}