diff --git a/sites/all/modules/features/API.txt b/sites/all/modules/features/API.txt new file mode 100644 index 0000000000000000000000000000000000000000..ab0d0676d90e5d966e7c3f598fcf08eecedc8ff0 --- /dev/null +++ b/sites/all/modules/features/API.txt @@ -0,0 +1,189 @@ + +Features 1.x API for Drupal 7.x +------------------------------- +This file contains documentation for two audiences: site builders interested in +creating and managing features and module developers interested in integrating +with the features module. + + +Terminology +----------- +- A **feature module** is a Drupal module that contains the `feature` key in its +`.info` file. This array describes the components that should be managed by +Features within the module. + +- A **component** is a configuration object that can be exported. Examples: a +view, content type or CCK field instance. + +- A **machine name** is a string identifier for a specific type of component and +should be unique within a single Drupal site. It should not be a numerically +generated serial id. + +- An **exportable** component is one that can be used solely from a default hook +in code and without an instance in the database. For example, a view that lives +in code does not need an entry in the database in order to be used. + +- A **faux-exportable** component is one that must exist in the database in +order to be used. Any exports of this component are used to create or +synchronize entries in the database that share the same machine name. + + +### Component states + +Features provides some infrastructure to determine the state of components on +the site. To determine the state of a component Features keeps track of three +things using an md5 hash of + +- current code for the component. This is the configuration as actually +represented in code by a given feature. + +- the most recent prior code state that differs from the current code state. For +example, if an `svn update` changes the configuration of a view, this stores the +code state *prior* to the update. + +- The "normal" component state. This is the configuration represented by the +component as stored in the database or the default component (with any changes +introduced by `drupal_alter()`) if no database override exists. + +Using these three values, Features determines a component to be in one of the +following five states: + +- **Default** (`FEATURES_DEFAULT`) The object has no database entry or the +database entry matches the state of the component in code. This should be the +default state of components after installing a feature. Updating the component +can be done by altering the code definition directly. + +- **Overridden** (`FEATURES_OVERRIDDEN`) The code remains constant but the +database object does not match the state of the component in code. Changes must +be reverted before the component can be updated from code. + +- **Needs review** (`FEATURES_NEEDS_REVIEW`) The previous code state, database +state, and current code state all differ. This occurs most commonly when a user +changes one of her components and then pulls updates to her codebase. Since +there is no way to tell whether the code state or the database state is more +recent/valid, user input is necessary to resolve this state. + +- **Rebuildable** (`FEATURES_REBUILDABLE`) This state only applies to +**faux-exportables** and indicates that the database component must and can be +safely updated from the code definition. The database entry does not match the +current code state but does match the previous code state. Features assumes that +in this scenario the user has made no substantive changes and the component can +be updated automatically. + +- **Rebuilding** (`FEATURES_REBUILDING`) This state is rarely seen and only +applies to **faux-exportables.** This state is shown when a +`FEATURES_REBUILDABLE` component is *currently* being synced to the database. +Usually this operation is very fast and short lived. However, if the operation +is interrupted (e.g. the server goes down) this state will be seen until the +rebuild locking semaphore is cleared. + + +How a feature is generated +-------------------------- +At a high level Features writes the code in a feature module using the following +steps: + +1. An `.info` file describing the components that should be included in a +Feature is generated. It is either read from an existing feature or generated +through the Features UI. + +2. Features converts the info file into an `$export` array which contains a list +of elements to be exported. Each component type is given a chance to add to the +export list as well as request that *other* components be given a second chance +to add to the `$export` array. + +3. If any additional components have been queued up in the `$pipe` we repeat +step 2 for each of the queued component types. + +4. Once a full `$export` array is populated each component renders items from +the `$export` array to PHP code as a exportable defaults hook. + +5. Finally, Features writes the code into files and delivers it as a +downloadable package (UI) or writes it directly to a module directory (drush). + +This workflow makes a variety of things possible: + +### Add components to a feature + +Add the components to the `features` array in the feature's `.info` file and run +`drush features-update`. The same operation can be performed using the +*Recreate* page in the Features UI. + +### Remove components from a feature + +Remove the corresponding component lines from the feature's `.info` file and run +`drush features-update`. The same operation can be performed using the +*Recreate* page in the Features UI. + +### Rename a component + +Rename a component by changing its name in the feature's `.info` file and the +key and name property of the exported object in the appropriate `.inc` file in +the feature. Note that any references in other configuration objects to the +previous name should also be updated. + + +Integrating your module with the Features API +--------------------------------------------- +This section is for developers interested in adding Features-based management +for the configuration objects in their modules. From the perspective of +Features, there are a few different ways that modules store their configuration: + +- In the `variable` table using `variable_set()`. If a module is using variables +for storing configuration, these variable settings can be exported with Features +by using the [Strongarm][1] module. + + **Features integration:** Install the Strongarm module. + +- Using a custom table with a serial ID for identifying configuration objects. +If this is the case, you will need to change your schema to use a string +identifier / machine name for each object. + + **Features integration:** Fix your schema first, then see below. + +- Using a custom table with a machine name identifier and custom exportables +handling (e.g. you have your own defaults hook handling and export generation). +If this is the case, you will need to implement many of the features hooks +yourself. + + **Features integration:** `hook_features_api()`, `hook_features_export()`, +`hook_features_export_render()`, `hook_features_export_options()`, +`hook_features_revert()`. + +- Using a custom table with CTools Export API integration. If this is the case, +Features will automatically have integration with your module. You can implement +any of the Features hooks in order to override the default CTools exportables +integration behavior. + + **Features integration:** Automatically provided. You may implement any of the +Features hooks where you need further customization for your configuration +object. + +If it isn't clear by now, we highly recommend using the [CTools][2] Export API +for adding exportables to your module. Stella has written a [fantastic HOWTO][3] +on using the CTools Export API that can get you started. + + +An overview of Features hooks +----------------------------- +Extensive documentation of the hooks provided by Features is available in +`features.api.php`. This section provides a short overview of each hook and its +role. + +- `hook_features_api()` defines one or more component types that are available +to Features for export and a variety of settings for each type. +- `hook_features_export()` processes a list of components, detecting any +dependencies or further components +- `hook_features_export_options()` provides an array of components that can be +exported for a given type. +- `hook_features_export_render()` renders a set of components to code as a +defaults hook. +- `hook_features_revert()` reverts components of a feature back to their default +state. +- `hook_features_rebuild()` updates faux-exportable components back to their +default state. Only applies to faux-exportables. + + +[1]: http://drupal.org/project/strongarm +[2]: http://drupal.org/project/ctools +[3]: http://civicactions.com/blog/2009/jul/24/using_chaos_tools_module_create_exportables diff --git a/sites/all/modules/features/CHANGELOG.txt b/sites/all/modules/features/CHANGELOG.txt new file mode 100644 index 0000000000000000000000000000000000000000..ab369bed55a5ef6f3ca0c65eba555b5cecb0d962 --- /dev/null +++ b/sites/all/modules/features/CHANGELOG.txt @@ -0,0 +1,392 @@ + +features DRUPAL-6--1-0 +---------------------- +- No changes from RC3. + +features DRUPAL-6--1-0-RC3 +-------------------------- +- Ensure feature module name is properly handled in AHAH callback. +- #577852: Do not allow module to add itself as dependency. + +features DRUPAL-6--1-0-RC2 +-------------------------- +- #888066 by ptrl: Views message typo. +- #884624: Better support for custom CTools export callbacks. + +features DRUPAL-6--1-0-RC1 +-------------------------- +- #878604 by Coornail: Fix for PHP notice. +- Add < and > to the list of characters that must be DOM encoded for AHAH safe + handling. +- #878068: escape component identifiers. +- #880456: Fix for PHP notice. +- #880186 by yhahn, loosening the iron grip on vocabulary machine names. +- #881010: Properly refer to menu links when doing a ordered build. + +features DRUPAL-6--1-0-BETA12 +----------------------------- +- #873570: DOM-safe encode #default_value and ecsape '.' character. +- #872052: Expose CCK field instances in UI for export. +- Use README.txt for hook_help() page. +- #872482: Proper failure for features_get_features(). +- #875156: Ensure roles are rebuilt before permissions. +- #875416: Less confusing export of translatables. +- #877178: Allow feature human-readable name/description to be changed on recreate. +- #722952: Only write 'project' key if version or update status URL are provided. +- #876206 by auzigog: Document hook_[component_defaults]_alter() functions. + +features DRUPAL-6--1-0-BETA11 +----------------------------- +- #865454: Fix for complex menu links component handling. +- Context: simpler check for API version +- Blank out .features.inc when appropriate when updating existing features. +- Fix for haphazard drush features-revert. +- Adjust features_get_component_map() to allow for easy retrieval of a specific + component key. + +features DRUPAL-6--1-0-BETA10 +----------------------------- +- #864742: Ensure CTools plugins.inc is included before calling API functions. +- #864482 by hadsie: Fix for PHP notice. +- Fix Views dependency detection. + +features DRUPAL-6--1-0-BETA9 +---------------------------- +This release introduces several important API and component changes. + +### Include file writing + +- CTools and Views components are now written to supported API include files, + e.g. `[myfeature].views_default.inc` and `[myfeatures].[ctools api].inc`. +- Features-supported faux-exportable components are written to distinct include + files, e.g. `[myfeature].features.[component].inc`. +- Only API hooks and hooks for APIs that do not handle file inclusion should be + written to `[myfeatures].features.inc` which is loaded by default through the + feature module file. These include `hook_views_api`, `hook_ctools_api`, + `hook_node_info`, etc. +- All features using the old include formats are still supported but many + components will be marked as 'Overridden' until the feature is updated. +- `[myfeature].defaults.inc`, `[myfeature].features.node.inc` and + `[myfeature].features.views.inc` are deprecated and can be safely removed + after the feature has been updated. + +### Component API changes + +- An exportable taxonomy vocabulary component has been introduced which uses + the `module` column of the vocabulary table as storage for machine names. +- The menu component has been deprecated in favor of more advanced `menu_links` + and `menu_custom` components. Features using the old `menu` component can be + migrated to the new components through a feature update. +- The old user permission component has been deprecated in favor of the + `user_permission` and `user_role` components. Features will no longer + automatically create roles that do not exist -- they must be exported + properly using the `user_role` component. Features using the old `user` + component can be migrated to the new components through a feature update. + +- FIx for features implementers using FEATURES_DEFAULTS_CUSTOM. +- #504706 by dmitrig01: Recreate: include any additional files from the original + feature +- #728004 by gordon, dmitrig01: Allow abitrary modules to alter the pipeline. +- #752144 by dmitrig01: Refactor Features Status Page for Performance +- #841844 by dmitrig01: Implement environment-agnostic message logging. +- #577674 by dmitrig01: Display reasons for Incompatible Feature. +- #838726: Fix possible creation of circular dependencies by cross-feature + fields. +- #829778 by Josh Waihi: Add features-diff command. +- Fix for reckless iteration. +- Make features component options DOM safe for AHAH handling. +- #717152 by dmitrig01: Ensure dependent features cannot be disabled. +- Strip tags when displaying log messages in CLI. +- #823278 by Grayside: Display warning when feature .module does not include + .feature.inc. +- #649604: Split out dependencies as a component, proper, and ensure that + feature dependencies are met. +- #742940: Improve granularity and compatibility of Features export includes. +- #741498: Fix for bad function signature. +- Suppress PHP 5.3 errors/warnings when generating tar output. +- #813960 by nyl auster: Make it possible to revert defaulted features with + --force +- Massive update to README.txt. +- #843956 by q0rban: Drush commands use deprecated callback names +- #829778 by q0rban: Offer to download and install diff if it is not available. +- #843106 by dmitrig01: Check hook_requirements. +- Updating API.txt +- #843106 by dagmar: Fix for hook_requirements() checking. +- Adding a note about custom code to README.txt +- #789556 by alex_b, yhahn, bcmiller0 and jmiccolis, Support taxonomy + vocabularies. +- Adding hook help. +- #853738 by fgm: Fix for reckless check against ['default_file'] +- #852704 by q0rban: Check if Taxonomy exists before piping. +- #633804 by catch, dereine, yhahn, JohnAlbin: Improve menu support +- Ensure feature component options are escaped. +- #792790 by Dustin Currie: Fix label for components editing. +- Cleanup display of components on create/reexport. +- Fix for PHP notice. +- Fix for export and allow drush fu to affect modules in "non-standard" + directories. +- Fix PHP notice when description is not set. +- Fix for PHP notice. +- Reduce excessive cache clearing. +- Refactor features_get_default() and introduce features_get_default_map() to + simplify implementers' code. +- Update to features_test module. +- Remove misleading log messages. +- #830774 by scottrigby: Fix for reckless iteration. +- #751538 by DeFr: Clear caches after batch revert of Views rather than + individually. +- #678930 by tim.plunkett: Fix Features package tab sorting. +- Die tabs. +- Remove confusing debug output for revert. +- Don't run the bulk of feature generation/export logic unless we're sure the + feature can be written. +- Check that specified object is loaded before attempting export. +- #693024 by ceardach, hefox, nedjo: Add alter hooks for faux exportables. +- Only write Views API hook if views are actually exported. +- Fix for bad params. +- Removing excessive taxonomy pipe from node component. +- Preserve selected values even if form fails to validate. +- #849646: CTools: fix for integration with page manager list callback. +- #553866: Split user component into user_roles, user_permissions. +- #650292: Limit export options to normal and current feature's components. +- Fixing user permission test. +- Remove special handling for menu component. +- #649298 by q0rban, KarenS, yhahn: Allow node component type revert to disable + extra CCK fields. +- Cleaning up function signatures. + +features DRUPAL-6--1-0-BETA8 +---------------------------- +- #814414: Fix context integration to properly integrate with CTools. +- #810958 by q0rban: Add censored expletives to my daily workflow. + +features DRUPAL-6--1-0-BETA7 +---------------------------- +- #778250: Fix for php 5.3 issue with CTools. +- Added static cache to ensure features_include() runs only once. +- #522794 by Skorpjon, kenorb: Fixes for tar archive creation. +- #686240 by David Stosik: Ensure field settings are properly captured accross + instances. +- Add the drush features-update-all command and switch to using + drush_invoke_backend() to ensure processes have a better memory footprint. +- Standardize CTools API identifier to fully support non-exportables CTools + APIs. +- Ensure dependencies added by CTools do not create circular dependencies. +- #798104 by Steven Merrill: Add drush features-revert-all command. +- #673234 by joshk: Use elseif instead of else if to comply with coding + standards. +- Support additional CTools plugin APIs that are non-exportable based. +- #722460 by scor: Use 'drupal dependencies' flag in hook_drush_command() to + ensure features is enabled. +- #727110 by adixon: Respect drush --root option. +- #726700: Context 3.x dependency & component detection. +- #709608: CTools: don't re-export objects that already exist in code. Depends + on #709754. +- Disable features before installing on submit. +- #725132 by q0rban: Fix for fieldgroup duplicate entries. + +features DRUPAL-6--1-0-BETA6 +---------------------------- +- #640438: Ensure new features are not created in existing local namespaces. +- #706950: Fieldgroup component test. +- #696554 by Amitaibu: Add --force option to features-revert command. +- #702418 by Haza: Ensure roles are not localized on export when originating + from filter formats. +- #480978 by q0rban: Add fieldgroup support. +- #680332 by christianchristensen: Ensure each perm has only one instance of + each role. +- #693944: Use strongarm to export CCK extra field weights. +- #678930: Sort package links. +- #670788 by jasonitti, andrewfn, irakli: Fix for regex syntax error in PHP + <= 5.2.2 +- Use human readable names for dependencies. +- CTools: Show human readable names for components. +- #691894: Use human readable names on component display. +- #682730: Ensure object exists before attempting to render export code. +- #682730: Using API owner with schema owner as module fallback. +- #696396 by irakli: sort ctools exportables alphabetically. +- #694890: Ensure rebuild menu reflects node types +- Check that a feature has dependencies before trying to maximize them. +- by mig5, Amitaibu: Update drush commands to use dashes. +- Init component states array for each feature. +- Removing features_menu_links() hack in favor of implementers using a + preprocess_page() function directly. +- by Zoltán Balogh: Updated Hungarian translation. +- #675306: Prevent feaures_detect_overrides() from running unless user is + properly permissioned. + +features DRUPAL-6--1-0-BETA5 +---------------------------- +- Basic component functional API tests. +- A variety of static cache clears added -- discovered through tests. +- #649410: Ensure cleanup doesn't disable Features module. +- #654334: Allow feature builders to override module key of hook_node_info(). +- #622346: Make use of drush_log for assertions. +- #656172: Delete features menu on uninstall. +- #577852: Don't allow feature module to add itself as a dependency. +- #660798 by e2thex: Use key from ctools_export_load_object. +- CTools: Only export objects that can be loaded. +- CTools: Implement hook_features_api() using component's module namespace + rather than the component namespace. +- Only load includes on behalf of modules not implementing hook_features_api. +- #532256 by flobruit and jmiccolis, more robust support for ctools + exportables, like panels. +- Adding component revert for ctools.features.ctools.inc +- #641658 by brad bulger: Fix for reckless ->delete() View method call. +- #649832 by DamienMcKenna: Fix message on Views revert. +- #653644, #532646 by careernerd, q0rban: Revert/update multiple features at + once. + +features DRUPAL-6--1-0-BETA4 +---------------------------- +Note: You MUST run update.php for Features to begin automatically rebuilding +feature components when safe to do so. + +This release of Features contains a lot of new features and many changes to the +internals for rebuildable components. There are no major API changes but as +always please test thoroughly in a staging environment before rolling out beta4 +to production sites. + +- #616222: Moving conflict detection API functions into module proper. +- #520220: Allow modules to be ignored when cleaning up dependencies. +- #597330 by fago, fixed display of rule names in feature building ui. +- #616222 by q0rban: API function for installing maximum dependencies of + modules/features. +- #606992 by dmitrig01: Human readable names for feature components. +- #612824: Proper handling of rebuildables & component reverting. Reported by + greggles, Roger Saner. +- #616030 by q0rban: Simplify access check against features administration pages. +- #597836: Remove form_alter() which hides features from admin/build/modules. +- 3 stage state signature handling. +- #597422 by Roger López: Consistify user permission, role sorting. +- #522794 by Steven Merrill: Clear output buffer before transfering tgz file. +- #604138 by Josh Waihi: Check for array before beginning reckless iteration. +- by Istvan Palocz (pp): Hungarian translation +- #521394: Initial implementation of features packages. +- #520220: Provide form for disabling orphaned feature dependencies. +- #520310: Adds implementation of 'administer features' and 'manage features' + permissions. +- #402132: Adding validation checks to features create form. +- Improved UI. +- Cleanup rebuild/revert stack for CCK, filter formats, user permissions. +- Simpler revert logic and sanitization of imagecache export presets. +- #581514 by q0rban: Set a destination for feature revert links. +- #401948 by derhasi: Export help and min_word_count values for node types. +- #583120: Check for menu_custom table before querying. +- #586634 by Pasqualle, prevent empty t() calls in exports. +- #588808 by Pasqualle, php notice fix. +- Sorting permissions on export to maintain array consistency. + +features DRUPAL-6--1-0-BETA3 +---------------------------- +This release of Features contains several key changes - if you are using +Features in production you should be cautious of upgrading without testing. + +Previous releases of features contained an error in integration with the +node module - the module key of hook_node_info() should now be features +rather than node. Features will do this update for you automatically if +you use drush features update or update your feature through the UI. +You will still need to run features_update_6100() from update.php to +update the node type entries in your database. For more information +about this issue see http://drupal.org/node/521606. + +- Using correct API identifier for CTools integration. +- Allowing modules that implement hook_system_info_alter() to modify the + features info cache as well. +- #532256 by flobruit: Use proper ctools API functions for schema retrieval and + object export. +- #551490 by fago, Allow renaming of components. +- Fixing bad FormAPI key ['#redirect'] +- #521606 by mig5, q0rban: Correct hook_node_info() 'module' attribute to remove + feature node types when feature is disabled. +- Fix for node type export check which should now include 'features' as an + acceptable module for node type exports. + +features DRUPAL-6--1-0-BETA2 +---------------------------- +- #560478 by jose reyero and myself, support translated role names. +- #557112 by moshe weitzman: Suppress unnecessary menu_rebuild()'s. + +features DRUPAL-6--1-0-BETA1 +---------------------------- +This release of Features makes an API change to hook_features_api(). +Please see API.txt for the details. + +- Improving cache clear flow after feature is enabled. +- #511872 by TUc: Fix typo in hook comment. +- Fix for long feature name/descriptions breaking float in feature form. +- #522820 by fago: Allow multiple components per module. +- #520376: Adding static cache to override access check to prevent recursion. +- #524124 by fago: Only include features includes for modules when they are + enabled. +- #524160 by fago: Allow one default hook to contain multiple components. +- by dmitrig01: Add a static cache to features_get_info() +- #532256 ctools integration by alex_b and yhann. +- #543190 by jmiccolis: Refactor hook_features_api() for greater extensibility + and multi-inc file export. +- #527646 by Amitaibu: Add user_features_export_options(). +- #545276 by q0rban: Check for field existence before updating CCK fields when + reverting. +- #543152 by q0rban: Fix for views export detection error. +- Refactoring override detection code and export/build process. +- Correctly excluded added dependencies from feature component array in info + file. +- Adding configurable duplicate handling and allowing CTools API components to + be duplicated across features. +- Ensure that dependencies are unique. + +features DRUPAL-6--1-0-ALPHA5 +----------------------------- +- Implementing project status url and version keys in info files. +- Removed context dependency. +- Implementing feature reverting in the UI and some admin UI cleanups. +- Initial commit of experimental user/permission integration. +- Initial commit of features-filters integration. +- Corrected a misnamed drush hook. +- Updated usage of drush_print_table to reflect recent changes in drush. +- Adding component conflict detection between features. +- Adding export for translatable strings in menu, CCK. +- #483548 by jmiccolis: Use AJAX to lighten server load on feature + overview page. + +features DRUPAL-6--1-0-ALPHA4 +----------------------------- +- Ensure that a feature module cannot create circular dependencies. +- #482212 by rszrama: Displaying a friendly message when no features + are available. +- Move all theme functions into theme.inc. +- Allow any implementing module to specify itself as a feature source. +- Improve handling of include files. +- Added hook_features_api() for declaring various info. +- Show options for any implementer that provides export options on the + feature component confirmation form. +- Fixing issue where only 1st level dependencies were enabled when + enabling a feature. +- Clearing caches after features form submission using + hook_flush_caches() rather than piecemeal. + +features DRUPAL-6--1-0-ALPHA3 +----------------------------- +- Removing field updating nonsense from field rebuilding -- + moved to revert hooks. +- Renaming drush command 'status features' to 'features'. +- #441826: Fix for improper escaping of single quotes. +- #449916 by Steven Jones: Checks that implementations of + hook_views_default_views() actually return an array of views before + iterating. +- #440592 by benroot: Feature component reverting through drush. +- Adding headers to diff table. + +features DRUPAL-6--1-0-ALPHA2 +----------------------------- +- #421740: Fix for context pipe with views displays. +- #430044: Fixing dependency checking in content and context includes. +- Flushing caches before drush export. +- Adding export item sorting for code stability. +- Smarter export detection and dependency handling to imagecache. +- Fix for imagecache export render. +- Adding implementation of hook_flush_caches(). + +features DRUPAL-6--1-0-ALPHA1 +----------------------------- +- Initial release of features module. diff --git a/sites/all/modules/features/LICENSE.txt b/sites/all/modules/features/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..2c095c8d3f42488e8168f9710a4ffbfc4125a159 --- /dev/null +++ b/sites/all/modules/features/LICENSE.txt @@ -0,0 +1,274 @@ +GNU GENERAL PUBLIC LICENSE + + Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, +Cambridge, MA 02139, 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 Library 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 diff --git a/sites/all/modules/features/README.txt b/sites/all/modules/features/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..197a2dabc13e1dbe1d795617cf08fed92db62759 --- /dev/null +++ b/sites/all/modules/features/README.txt @@ -0,0 +1,199 @@ + +Current state of Features for Drupal 7 +-------------------------------------- +Work on Features for D7 is currently aimed at getting to a point where Features +can be used on a new install of Drupal 7 with features that were created on D7. +Once this has been achieved, we will begin working on supporting D6 features as +well as possibly supporting upgrades & migrations between legacy components and +new equivalents (e.g. CCK to fields, imagecache to core image styles). + +### Working components + +- ctools +- dependencies +- field +- filter +- image +- menu_custom +- menu_links +- node +- taxonomy +- user_permission +- user_role +- views + +### Has changes to export format between D6 and D7 + +(@TODO legacy export compatibility) + +- filter +- taxonomy + +### Requires upgrade/migration path + +- imagecache > image +- content > field + + +Features 1.x for Drupal 7.x +--------------------------- +The features module enables the capture and management of features in Drupal. A +feature is a collection of Drupal entities which taken together satisfy a +certain use-case. + +Features provides a UI and API for taking different site building components +from modules with exportables and bundling them together in a single feature +module. A feature module is like any other Drupal module except that it declares +its components (e.g. views, contexts, CCK fields, etc.) in its `.info` file so +that it can be checked, updated, or reverted programmatically. + +Examples of features might be: + +- A blog +- A pressroom +- An image gallery +- An e-commerce t-shirt store + + +Installation +------------ +Features can be installed like any other Drupal module -- place it in the +modules directory for your site and enable it on the `admin/build/modules` page. +To take full advantage of some of the workflow benefits provided by Features, +you should install [Drush][1]. + + +Basic usage +----------- +Features is geared toward usage by developers and site builders. It +is not intended to be used by the general audience of your Drupal site. +Features provides tools for accomplishing two important tasks: + +### Task 1: Export features + +You can build features in Drupal by using site building tools that are supported +(see a short list under the *Compatibility* section). + +Once you've built and configured functionality on a site, you can export it into +a feature module by using the feature create page at +`admin/build/features/create`. + + +### Task 2: Manage features + +The features module also provides a way to manage features through a more +targeted interface than `admin/build/modules`. The interface at +`admin/build/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 +----------- +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. + +- `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: + +- CCK fields +- CCK fieldgroups +- Content types +- Input filters +- User roles/permissions +- Custom menus and menu links * +- Taxonomy vocabularies * + +* Currently in development. + + +For developers +-------------- +Please read `API.txt` for more information about the concepts and integration +points in the Features module. + + +Maintainers +----------- +- yhahn (Young Hahn) +- jmiccolis (Jeff Miccolis) + + +[1]: http://drupal.org/project/drush +[2]: (http://drupal.org/taxonomy/term/11478) diff --git a/sites/all/modules/features/features.admin.inc b/sites/all/modules/features/features.admin.inc new file mode 100644 index 0000000000000000000000000000000000000000..b86ddda308d527178e7c90c512a3d807c667d8fc --- /dev/null +++ b/sites/all/modules/features/features.admin.inc @@ -0,0 +1,815 @@ +<?php + +/** + * Form callback for features export form. Acts as a router based on the form_state. + */ +function features_export_form($form, $form_state, $feature = NULL) { + module_load_include('inc', 'features', 'features.export'); + features_include(); + + $form = array( + '#attributes' => array('class' => array('features-export-form')), + '#feature' => isset($feature) ? $feature : NULL, + ); + $form['info'] = array( + '#type' => 'fieldset', + '#tree' => FALSE, + ); + $form['info']['name'] = array( + '#title' => t('Name'), + '#description' => t('Example: Image gallery'), + '#type' => 'textfield', + '#required' => TRUE, + '#default_value' => !empty($feature->info['name']) ? $feature->info['name'] : '', + '#attributes' => array('class' => array('feature-name')), + ); + $form['info']['module_name'] = array( + '#type' => 'textfield', + '#title' => t('Machine-readable name'), + '#description' => t('Example: image_gallery') . '<br/>' . t('May only contain lowercase letters, numbers and underscores. <strong>Try to avoid conflicts with the names of existing Drupal projects.</strong>'), + '#required' => TRUE, + '#default_value' => !empty($feature->name) ? $feature->name : '', + '#attributes' => array('class' => array('feature-module-name')), + '#element_validate' => array('features_export_form_validate_field'), + ); + // If recreating this feature, disable machine name field and blank out + // js-attachment classes to ensure the machine name cannot be changed. + if (isset($feature)) { + $form['info']['module_name']['#value'] = $feature->name; + $form['info']['module_name']['#disabled'] = TRUE; + $form['info']['name']['#attributes'] = array(); + } + $form['info']['description'] = array( + '#title' => t('Description'), + '#description' => t('Provide a short description of what users should expect when they enable your feature.'), + '#type' => 'textfield', + '#required' => TRUE, + '#default_value' => !empty($feature->info['description']) ? $feature->info['description'] : '', + ); + $form['info']['version'] = array( + '#title' => t('Version'), + '#description' => t('Examples: 7.x-1.0, 7.x-1.0-beta1'), + '#type' => 'textfield', + '#required' => FALSE, + '#default_value' => !empty($feature->info['version']) ? $feature->info['version'] : '', + '#size' => 30, + '#element_validate' => array('features_export_form_validate_field'), + ); + $form['info']['project_status_url'] = array( + '#title' => t('URL of update XML'), + '#description' => t('Example: http://mywebsite.com/fserver'), + '#type' => 'textfield', + '#required' => FALSE, + '#default_value' => !empty($feature->info['project status url']) ? $feature->info['project status url'] : '', + '#size' => 30, + '#element_validate' => array('features_export_form_validate_field'), + ); + + // User-selected feature source components. + $components = features_get_components(); + ksort($components); + + $form['export'] = array( + '#type' => 'fieldset', + '#tree' => FALSE, + '#theme' => 'features_form_export', + ); + $form['export']['components'] = array( + '#title' => t('Edit components'), + '#type' => 'select', + '#options' => array('------'), + '#attributes' => array('class' => array('features-select-components')), + ); + $form['export']['sources'] = array( + '#tree' => TRUE, + '#theme' => 'features_form_components', + ); + foreach ($components as $component => $component_info) { + $options = features_invoke($component, 'features_export_options'); + if ($component === 'dependencies') { + $default_value = !empty($feature->info['dependencies']) ? $feature->info['dependencies'] : array(); + } + else { + $default_value = !empty($feature->info['features'][$component]) ? $feature->info['features'][$component] : array(); + } + if ($options) { + // Find all default components that are not provided by this feature and + // strip them out of the possible options. + if ($map = features_get_default_map($component)) { + foreach ($map as $k => $v) { + if (isset($options[$k]) && (!isset($feature->name) || $v !== $feature->name)) { + unset($options[$k]); + } + } + } + // Ensure all options are stripped of potentially bad values. + foreach ($options as $k => $v) { + $options[$k] = check_plain($v); + } + $form['export']['components']['#options'][$component] = (isset($component_info['name']) ? $component_info['name'] : $component); + if (!empty($options)) { + $form['export']['sources'][$component] = array( + '#type' => 'checkboxes', + '#options' => features_dom_encode_options($options), + '#title' => $component, + '#default_value' => features_dom_encode_options($default_value, FALSE), + '#ajax' => array( + 'callback' => 'features_export_build_form_populate', + 'wrapper' => 'features-export-contents', + ), + ); + } + else { + $form['export']['sources'][$component] = array( + '#type' => 'item', + '#title' => $component, + '#value' => t('All components of this type are exported by other features or modules.'), + ); + } + } + } + $form['export']['features'] = array( + '#tree' => TRUE, + '#prefix' => "<div id='features-export-populated'><div id='features-export-contents'>", + '#suffix' => "</div></div>", + '#markup' => !empty($feature->info) ? theme('features_components', array('info' => $feature->info, 'sources' => $feature->info['features'])) : "<div class='placeholder'></div>", + ); + + $form['buttons'] = array('#theme' => 'features_form_buttons', '#tree' => FALSE); + $form['buttons']['submit'] = array( + '#type' => 'submit', + '#value' => t('Download feature'), + '#weight' => 10, + '#submit' => array('features_export_build_form_submit'), + ); + return $form; +} + +/** + * Validation for project field. + */ +function features_export_form_validate_field($element, &$form_state) { + switch ($element['#name']) { + case 'module_name': + if (!preg_match('!^[a-z0-9_]+$!', $element['#value'])) { + form_error($element, t('The machine-readable name must contain only lowercase letters, numbers, and underscores.')); + } + // If user is filling out the feature name for the first time and uses + // the name of an existing module throw an error. + else if (empty($element['#default_value']) && features_get_info('module', $element['#value'])) { + form_error($element, t('A module by the name @name already exists on your site. Please choose a different name.', array('@name' => $element['#value']))); + } + break; + case 'project_status_url': + if (!empty($element['#value']) && !valid_url($element['#value'])) { + form_error($element, t('The URL %url is invalid. Please enter a fully-qualified URL, such as http://www.example.com/feed.xml.', array('%url' => $element['#value']))); + } + break; + case 'version': + preg_match('/^(?P<core>\d+\.x)-(?P<major>\d+)\.(?P<patch>\d+)-?(?P<extra>\w+)?$/', $element['#value'], $matches); + if (!empty($element['#value']) && !isset($matches['core'], $matches['major'])) { + form_error($element, t('Please enter a valid version with core and major version number. Example: !example', array('!example' => '6.x-1.0'))); + }; + break; + } +} + +/** + * Submit handler for features_export_form_build(). + */ +function features_export_build_form_submit($form, &$form_state) { + module_load_include('inc', 'features', 'features.export'); + features_include(); + + // Assemble the combined component list + $stub = array(); + $components = array_keys(features_get_components()); + foreach ($components as $component) { + // User-selected components take precedence. + if (!empty($form_state['values']['sources'][$component])) { + $stub[$component] = features_dom_decode_options(array_filter($form_state['values']['sources'][$component])); + } + // Only fallback to an existing feature's values if there are no export options for the component. + else if (!empty($form['#feature']->info['features'][$component])) { + $stub[$component] = $form['#feature']->info['features'][$component]; + } + } + + // Generate populated feature + $module_name = $form_state['values']['module_name']; + $export = features_populate($stub, $form_state['values']['sources']['dependencies'], $module_name); + + // Directly copy the following attributes + $attr = array('name', 'description'); + foreach ($attr as $key) { + $export[$key] = isset($form_state['values'][$key]) ? $form_state['values'][$key] : NULL; + } + // If either update status-related keys are provided, add a project key + // corresponding to the module name. + if (!empty($form_state['values']['version']) || !empty($form_state['values']['project_status_url'])) { + $export['project'] = $form_state['values']['module_name']; + } + if (!empty($form_state['values']['version'])) { + $export['version'] = $form_state['values']['version']; + } + if (!empty($form_state['values']['project_status_url'])) { + $export['project status url'] = $form_state['values']['project_status_url']; + } + + // Generate download + if ($files = features_export_render($export, $module_name, TRUE)) { + $filename = (!empty($export['version']) ? "{$module_name}-{$export['version']}" : $module_name) . '.tar'; + + // Clear out output buffer to remove any garbage from tar output. + if (ob_get_level()) { + ob_end_clean(); + } + + drupal_add_http_header('Content-type', 'application/x-tar'); + drupal_add_http_header('Content-Disposition', 'attachment; filename="'. $filename .'"'); + drupal_send_headers(); + + $tar = array(); + $filenames = array(); + foreach ($files as $extension => $file_contents) { + if (!in_array($extension, array('module', 'info'))) { + $extension .= '.inc'; + } + $filenames[] = "{$module_name}.$extension"; + print features_tar_create("{$module_name}/{$module_name}.$extension", $file_contents); + } + if (features_get_modules($module_name, TRUE)) { + $module_path = drupal_get_path('module', $module_name); + // file_scan_directory() can throw warnings when using PHP 5.3, messing + // up the output of our file stream. Suppress errors in this one case in + // order to produce valid output. + foreach (@file_scan_directory($module_path, '/.*/') as $file) { + $filename = substr($file->uri, strlen($module_path) + 1); + if (!in_array($filename, $filenames)) { + // Add this file. + $contents = file_get_contents($file->uri); + print features_tar_create("{$module_name}/{$filename}", $contents); + unset($contents); + } + } + } + print pack("a1024",""); + exit; + } +} + +/** + * AHAH handler for features_export_form_build(). + */ +function features_export_build_form_populate($form, $form_state) { + module_load_include('inc', 'features', 'features.export'); + features_include(); + $stub = array(); + $submitted = $form_state['values']; + + // Assemble the combined component list + $components = array_keys(features_get_components()); + foreach ($components as $component) { + // User-selected components take precedence. + if (!empty($submitted['sources'][$component])) { + // Validate and set the default value for each selected option. This + foreach ($submitted['sources'][$component] as $key => $value) { + if (isset($form['export']['sources'][$component]['#options'][$key])) { + $form['export']['sources'][$component]['#default_value'][$key] = $value; + } + } + $stub[$component] = features_dom_decode_options(array_filter($submitted['sources'][$component])); + } + // Only fallback to an existing feature's values if there are no export options for the component. + else if (!empty($form['export']['sources'][$component]) && !empty($form['#feature']->info['features'][$component])) { + $stub[$component] = $form['#feature']->info['features'][$component]; + } + } + + // Assemble dependencies + $dependencies = isset($submitted['sources']['dependencies']) ? $submitted['sources']['dependencies'] : array(); + + // Generate populated feature + $module_name = isset($form['#feature'], $form['#feature']->name) ? $form['#feature']->name : ''; + $export = features_populate($stub, $dependencies, $module_name); + + // Render component display + $components_rendered = theme('features_components', array('info' => $export, 'sources' => $stub)); + $form['export']['features']['#markup'] = $components_rendered; + + // @TODO: Reimplement this for D7. + // Re-cache form. This ensures that if the form fails to validate, selected + // values are preserved for the user. + // form_set_cache($submitted['form_build_id'], $form, $form_state); + + return $form['export']['features']; +} + +/** + * array_filter() callback for excluding hidden modules. + */ +function features_filter_hidden($module) { + return empty($module->info['hidden']); +} + +/** + * admin/build/features page callback. + */ +function features_admin_form($form, $form_state) { + // Load export functions to use in comparison. + module_load_include('inc', 'features', 'features.export'); + + // Clear & rebuild key caches + features_get_info(NULL, NULL, TRUE); + features_rebuild(); + + $modules = array_filter(features_get_modules(), 'features_filter_hidden'); + $features = array_filter(features_get_features(), 'features_filter_hidden'); + $conflicts = features_get_conflicts(); + + foreach ($modules as $key => $module) { + if ($module->status && !empty($module->info['dependencies'])) { + foreach ($module->info['dependencies'] as $dependent) { + if (isset($features[$dependent])) { + $features[$dependent]->dependents[$key] = $module->info['name']; + } + } + } + } + + if ( empty($features) ) { + $form['no_features'] = array( + '#markup' => t('No Features were found. Please use the !create_link link to create + a new Feature module, or upload an existing Feature to your modules directory.', + array('!create_link' => l(t('Create Feature'), 'admin/structure/features/create'))), + ); + return $form ; + } + + $form = array('#features' => $features); + + // Generate features form. + foreach ($features as $name => $module) { + $package_title = !empty($module->info['package']) ? $module->info['package'] : t('Other'); + $package = strtolower(preg_replace('/[^a-zA-Z0-9-]+/', '-', $package_title)); + + // Set up package elements + if (!isset($form[$package])) { + $form[$package] = array( + '#tree' => FALSE, + '#title' => $package_title, + '#theme' => 'features_form_package', + '#type' => 'fieldset', + '#group' => 'packages', + ); + $form[$package]['links'] = + $form[$package]['version'] = + $form[$package]['weight'] = + $form[$package]['status'] = + $form[$package]['action'] = array('#tree' => TRUE); + } + + $disabled = FALSE; + $description = isset($module->info['description']) ? $module->info['description'] : ''; + + // Detect unmet dependencies + if (!empty($module->info['dependencies'])) { + $unmet_dependencies = array(); + $dependencies = _features_export_maximize_dependencies($module->info['dependencies']); + foreach ($dependencies as $dependency) { + if (empty($modules[$dependency])) { + $unmet_dependencies[] = theme('features_module_status', array('status' => FEATURES_MODULE_MISSING, 'module' => $dependency)); + } + } + if (!empty($unmet_dependencies)) { + $description .= "<div class='dependencies'>" . t('Unmet dependencies: !dependencies', array('!dependencies' => implode(', ', $unmet_dependencies))) . "</div>"; + $disabled = TRUE; + } + } + + if (!empty($module->dependents)) { + $disabled = TRUE; + $description .= "<div class='requirements'>". t('Required by: !dependents', array('!dependents' => implode(', ', $module->dependents))) ."</div>"; + } + + // Detect potential conflicts + if (!empty($conflicts[$name])) { + $module_conflicts = array(); + foreach (array_keys($conflicts[$name]) as $conflict) { + $module_conflicts[] = theme('features_module_status', array('status' => FEATURES_MODULE_MISSING, 'module' => $conflict)); + // Only disable modules with conflicts if they are not already enabled. + // If they are already enabled, somehow the user got themselves into a + // bad situation and they need to be able to disable a conflicted module. + if (module_exists($conflict) && !module_exists($name)) { + $disabled = TRUE; + } + } + $description .= "<div class='conflicts'>". t('Conflicts with: !conflicts', array('!conflicts' => implode(', ', $module_conflicts))) ."</div>"; + } + + $form[$package]['status'][$name] = array( + '#type' => 'checkbox', + '#title' => $module->info['name'], + '#description' => $description, + '#default_value' => $module->status, + '#disabled' => $disabled, + ); + + if (!empty($module->info['project status url'])) { + $uri = l(truncate_utf8($module->info['project status url'], 35, TRUE, TRUE), $module->info['project status url']); + } + else if (isset($module->info['project'], $module->info['version'], $module->info['datestamp'])) { + $uri = l('http://drupal.org', 'http://drupal.org/project/' . $module->info['project']); + } + else { + $uri = t('Unavailable'); + } + $version = !empty($module->info['version']) ? $module->info['version'] : ''; + $version = !empty($version) ? "<div class='description'>$version</div>" : ''; + $form[$package]['sign'][$name] = array('#markup' => "{$uri} {$version}"); + + if (user_access('administer features')) { + // Add status link + $href = "admin/structure/features/{$name}"; + if ($module->status) { + $state = '<span class="admin-loading features-storage">' . t('Checking...') . '</span>'; + $state .= l(t('Check'), "admin/structure/features/{$name}/status", array('attributes' => array('class' => array('admin-check')))); + $state .= theme('features_storage_link', array('storage' => FEATURES_REBUILDING, 'path' => $href)); + $state .= theme('features_storage_link', array('storage' => FEATURES_NEEDS_REVIEW, 'path' => $href)); + $state .= theme('features_storage_link', array('storage' => FEATURES_OVERRIDDEN, 'path' => $href)); + $state .= theme('features_storage_link', array('storage' => FEATURES_DEFAULT, 'path' => $href)); + } + elseif (!empty($conflicts[$name])) { + $state = theme('features_storage_link', array('storage' => FEATURES_CONFLICT, 'path' => $href)); + } + else { + $state = theme('features_storage_link', array('storage' => FEATURES_DISABLED, 'path' => $href)); + } + $form[$package]['state'][$name] = array( + '#markup' => !empty($state) ? $state : '', + ); + + // Add in recreate link + $form[$package]['actions'][$name] = array( + '#markup' => l(t('Recreate'), "admin/structure/features/{$name}/recreate", array('attributes' => array('class' => array('admin-update')))), + ); + } + } + ksort($form); + + // As of 7.0 beta 2 it matters where the "vertical_tabs" element lives on the + // the array. We add it late, but at the beginning of the array because that + // keeps us away from trouble. + $form = array('packages' => array('#type' => 'vertical_tabs')) + $form; + + $form['buttons'] = array( + '#theme' => 'features_form_buttons', + ); + $form['buttons']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save settings'), + '#submit' => array('features_form_submit'), + '#validate' => array('features_form_validate'), + ); + return $form; +} + +/** + * Display the components of a feature. + */ +function features_admin_components($form, $form_state, $feature) { + module_load_include('inc', 'features', 'features.export'); + $form = array(); + + // Store feature info for theme layer. + $form['module'] = array('#type' => 'value', '#value' => $feature->name); + $form['#info'] = $feature->info; + $form['#dependencies'] = array(); + if (!empty($feature->info['dependencies'])) { + foreach ($feature->info['dependencies'] as $dependency) { + $status = features_get_module_status($dependency); + $form['#dependencies'][$dependency] = $status; + } + } + + $review = $revert = FALSE; + + // Iterate over components and retrieve status for display + $states = features_get_component_states(array($feature->name), FALSE); + $form['revert']['#tree'] = TRUE; + foreach ($feature->info['features'] as $component => $items) { + if (user_access('administer features') && in_array($states[$feature->name][$component], array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW))) { + switch ($states[$feature->name][$component]) { + case FEATURES_OVERRIDDEN: + $revert = TRUE; + break; + case FEATURES_NEEDS_REVIEW: + $review = TRUE; + break; + } + $form['revert'][$component] = array( + '#type' => 'checkbox', + '#default_value' => FALSE, + ); + } + if (module_exists('diff')) { + $item = menu_get_item("admin/structure/features/{$feature->name}/diff/{$component}"); + $path = ($item && $item['access']) ? $item['href'] : NULL; + } + else { + $path = NULL; + } + $form['components'][$component] = array( + '#markup' => theme('features_storage_link', array('storage' => $states[$feature->name][$component], 'path' => $path)), + ); + } + + if ($review || $revert) { + $form['buttons'] = array('#theme' => 'features_form_buttons', '#tree' => TRUE); + if ($revert || $review) { + $form['buttons']['revert'] = array( + '#type' => 'submit', + '#value' => t('Revert components'), + '#submit' => array('features_admin_components_revert'), + ); + } + if ($review) { + $form['buttons']['review'] = array( + '#type' => 'submit', + '#value' => t('Mark as reviewed'), + '#submit' => array('features_admin_components_review'), + ); + } + } + return $form; +} + +/** + * Submit handler for revert form. + */ +function features_admin_components_revert(&$form, &$form_state) { + module_load_include('inc', 'features', 'features.export'); + features_include(); + $module = $form_state['values']['module']; + $revert = array(); + foreach (array_filter($form_state['values']['revert']) as $component => $status) { + $revert[$module][] = $component; + drupal_set_message(t('Reverted all <strong>!component</strong> components for <strong>!module</strong>.', array('!component' => $component, '!module' => $module))); + } + features_revert($revert); + $form_state['redirect'] = 'admin/structure/features/' . $module; +} + +/** + * Submit handler for revert form. + */ +function features_admin_components_review(&$form, &$form_state) { + module_load_include('inc', 'features', 'features.export'); + features_include(); + $module = $form_state['values']['module']; + $revert = array(); + foreach (array_filter($form_state['values']['revert']) as $component => $status) { + features_set_signature($module, $component); + drupal_set_message(t('All <strong>!component</strong> components for <strong>!module</strong> reviewed.', array('!component' => $component, '!module' => $module))); + } + $form_state['redirect'] = 'admin/structure/features/' . $module; +} + +/** + * Validate handler for the 'manage features' form. + */ +function features_form_validate(&$form, &$form_state) { + include_once './includes/install.inc'; + $conflicts = features_get_conflicts(); + foreach ($form_state['values']['status'] as $module => $status) { + if ($status) { + if (!empty($conflicts[$module])) { + foreach (array_keys($conflicts[$module]) as $conflict) { + if (!empty($form_state['values']['status'][$conflict])) { + form_set_error('status', t('The feature !module cannot be enabled because it conflicts with !conflict.', array('!module' => $module, '!conflict' => $conflict))); + } + } + } + if (!drupal_check_module($module)) { + form_set_error('status', t('The feature !module cannot be enabled because it has unmet requirements.', array('!module' => $module, '!conflict' => $conflict))); + } + } + } +} + +/** + * Submit handler for the 'manage features' form + */ +function features_form_submit(&$form, &$form_state) { + // Clear drupal caches after enabling a feature. We do this in a separate + // page callback rather than as part of the submit handler as some modules + // have includes/other directives of importance in hooks that have already + // been called in this page load. + $form_state['redirect'] = 'admin/structure/features/cleanup/clear'; + + $features = $form['#features']; + if (!empty($features)) { + $status = $form_state['values']['status']; + $install = array_keys(array_filter($status)); + $disable = array_diff(array_keys($status), $install); + + // Disable first. If there are any features that are disabled that are + // dependencies of features that have been queued for install, they will + // be re-enabled. + module_disable($disable); + features_install_modules($install); + } +} + +/** + * Form for disabling orphaned dependencies. + */ +function features_cleanup_form($form, $form_state, $cache_clear = FALSE) { + $form = array(); + + // Clear caches if we're getting a post-submit redirect that requests it. + if ($cache_clear) { + drupal_flush_all_caches(); + + // The following functions need to be run because drupal_flush_all_caches() + // runs rebuilds in the wrong order. The node type cache is rebuilt *after* + // the menu is rebuilt, meaning that the menu tree is stale in certain + // circumstances after drupal_flush_all_caches(). We rebuild again. + menu_rebuild(); + } + + // Retrieve orphaned modules and provide them as optional modules to be disabled. + // Exclude any modules that have been added to the 'ignored' list. + $options = array(); + $orphans = features_get_orphans(); + $ignored = variable_get('features_ignored_orphans', array()); + if (!empty($orphans)) { + foreach ($orphans as $module) { + if (!in_array($module->name, $ignored, TRUE)) { + $options[$module->name] = check_plain($module->info['name']); + } + } + } + + if (!empty($options)) { + $form['orphans'] = array( + '#title' => t('Orphaned dependencies'), + '#description' => t('These modules are dependencies of features that have been disabled. They may be disabled without affecting other components of your website.'), + '#type' => 'checkboxes', + '#options' => $options, + '#default_value' => array_keys($options), + ); + $form['buttons'] = array('#tree' => TRUE, '#theme' => 'features_form_buttons'); + $form['buttons']['disable'] = array( + '#type' => 'submit', + '#value' => t('Disable selected modules'), + '#submit' => array('features_cleanup_form_disable'), + ); + $form['buttons']['ignore'] = array( + '#type' => 'submit', + '#value' => t('Leave enabled'), + '#submit' => array('features_cleanup_form_ignore'), + ); + } + else { + drupal_goto('admin/structure/features'); + } + return $form; +} + +/** + * Submit handler for disable action on features_cleanup_form(). + */ +function features_cleanup_form_disable(&$form, &$form_state) { + if (!empty($form_state['values']['orphans'])) { + $disable = array_keys(array_filter($form_state['values']['orphans'])); + $ignored = array_diff(array_keys($form_state['values']['orphans']), $disable); + + // Disable any orphans that have been selected. + module_disable($disable); + drupal_flush_all_caches(); + + // Add enabled modules to ignored orphans list. + $ignored_orphans = variable_get('features_ignored_orphans', array()); + foreach ($ignored as $module) { + $ignored_orphans[$module] = $module; + } + variable_set('features_ignored_orphans', $ignored_orphans); + } + $form_state['redirect'] = 'admin/structure/features/cleanup'; +} + +/** + * Submit handler for ignore action on features_cleanup_form(). + */ +function features_cleanup_form_ignore(&$form, &$form_state) { + if (!empty($form_state['values']['orphans'])) { + $ignored = array_keys($form_state['values']['orphans']); + $ignored_orphans = variable_get('features_ignored_orphans', array()); + foreach ($ignored as $module) { + $ignored_orphans[$module] = $module; + } + variable_set('features_ignored_orphans', $ignored_orphans); + } + $form_state['redirect'] = 'admin/structure/features/cleanup'; +} + +/** + * Page callback to display the differences between what's in code and + * what is in the db. + * + * @param $feature + * A loaded feature object to display differences for. + * @param $component + * Optional: specific component to display differences for. If excluded, all components are used. + * + * @return Themed display of what is different. + */ +function features_feature_diff($feature, $component = NULL) { + drupal_add_css(drupal_get_path('module', 'features') . '/features.css'); + module_load_include('inc', 'features', 'features.export'); + + $overrides = features_detect_overrides($feature); + + $output = ''; + if (!empty($overrides)) { + // Filter overrides down to specified component. + if (isset($component) && isset($overrides[$component])) { + $overrides = array($component => $overrides[$component]); + } + + module_load_include('inc', 'diff', 'diff.engine'); + $formatter = new DrupalDiffFormatter(); //- temporarily broken + //$formatter = new DiffFormatter(); + $formatter->leading_context_lines = 2; + $formatter->trailing_context_lines = 2; + //$formatter->show_header = $show_header + + $rows = array(); + foreach ($overrides as $component => $items) { + $rows[] = array(array(array('data' => $component, 'colspan' => 4, 'header' => TRUE))); + $diff = new Diff(explode("\n", $items['default']), explode("\n", $items['normal'])); + //dpm($formatter->format($diff)); + $rows = array_merge($rows, $formatter->format($diff)); + } + $header = array( + array('data' => t('Default'), 'colspan' => 2), + array('data' => t('Overrides'), 'colspan' => 2), + ); + $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('diff', 'features-diff')))); + } + else { + $output = "<div class='features-empty'>" . t('No changes have been made to this feature.') . "</div>"; + } + $output = array('page' => array('#markup' => "<div class='features-comparison'>{$output}</div>")); + return $output; +} + +/** + * Javascript call back that returns the status of a feature. + */ +function features_feature_status($feature) { + module_load_include('inc', 'features', 'features.export'); + return drupal_json_output(array('storage' => features_get_storage($feature->name))); +} + +/** + * Make a Drupal options array safe for usage with jQuery DOM selectors. + * Encodes known bad characters into __[ordinal]__ so that they may be + * safely referenced by JS behaviors. + */ +function features_dom_encode_options($options = array(), $keys_only = TRUE) { + $replacements = array( + ':' => '__'. ord(':') .'__', + '/' => '__'. ord('/') .'__', + ',' => '__'. ord(',') .'__', + '.' => '__'. ord(',') .'__', + '<' => '__'. ord('<') .'__', + '>' => '__'. ord('>') .'__', + ); + $encoded = array(); + foreach ($options as $key => $value) { + $encoded[strtr($key, $replacements)] = $keys_only ? $value : strtr($value, $replacements); + } + return $encoded; +} + +/** + * Decode an array of option values that have been encoded by + * features_dom_encode_options(). + */ +function features_dom_decode_options($options, $keys_only = FALSE) { + $replacements = array_flip(array( + ':' => '__'. ord(':') .'__', + '/' => '__'. ord('/') .'__', + ',' => '__'. ord(',') .'__', + '.' => '__'. ord(',') .'__', + '<' => '__'. ord('<') .'__', + '>' => '__'. ord('>') .'__', + )); + $encoded = array(); + foreach ($options as $key => $value) { + $encoded[strtr($key, $replacements)] = $keys_only ? $value : strtr($value, $replacements); + } + return $encoded; +} \ No newline at end of file diff --git a/sites/all/modules/features/features.api.php b/sites/all/modules/features/features.api.php new file mode 100644 index 0000000000000000000000000000000000000000..ac54ec612b8e931033f101b52660f6ba3f550b26 --- /dev/null +++ b/sites/all/modules/features/features.api.php @@ -0,0 +1,345 @@ +<?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. + * + * 'features_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. + */ +function hook_features_api() { + return array( + 'mycomponent' => array( + 'default_hook' => 'mycomponent_defaults', + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + 'features_source' => TRUE, + 'file' => drupal_get_path('module', 'mycomponent') .'/mycomponent.features.inc', + ), + ); +} + +/** + * Component hook. The hook should be implemented using the name ot the + * component, not the module, eg. [component]_features_export() rather than + * [module]_features_export(). + * + * Process the export array for a given component. Implementations of this hook + * have three key tasks: + * + * 1. Determine module dependencies for any of the components passed to it + * e.g. the views implementation iterates over each views' handlers and + * plugins to determine which modules need to be added as dependencies. + * + * 2. Correctly add components to the export array. In general this is usually + * adding all of the items in $data to $export['features']['my_key'], but + * can become more complicated if components are shared between features + * or modules. + * + * 3. Delegating further detection and export tasks to related or derivative + * components. + * + * Each export processor can kickoff further export processors by returning a + * keyed array (aka the "pipe") where the key is the next export processor hook + * to call and the value is an array to be passed to that processor's $data + * argument. This allows an export process to start simply at a few objects: + * + * [context] + * + * And then branch out, delegating each component to its appropriate hook: + * + * [context]--------+------------+ + * | | | + * [node] [block] [views] + * | + * [CCK] + * | + * [imagecache] + * + * @param array $data + * An array of machine names for the component in question to be exported. + * @param array &$export + * By reference. An array of all components to be exported with a given + * feature. Component objects that should be exported should be added to + * this array. + * @param string $module_name + * The name of the feature module to be generated. + * @return array + * The pipe array of further processors that should be called. + */ +function hook_features_export($data, &$export, $module_name) { + // The following is the simplest implementation of a straight object export + // with no further export processors called. + foreach ($data as $component) { + $export['mycomponent'][$component] = $component; + } + return array(); +} + +/** + * Component hook. The hook should be implemented using the name ot the + * component, not the module, eg. [component]_features_export() rather than + * [module]_features_export(). + * + * List all objects for a component that may be exported. + * + * @return array + * A keyed array of items, suitable for use with a FormAPI select or + * checkboxes element. + */ +function hook_features_export_options() { + $options = array(); + foreach (mycomponent_load() as $mycomponent) { + $options[$mycomponent->name] = $mycomponent->title; + } + return $options; +} + +/** + * Component hook. The hook should be implemented using the name ot the + * component, not the module, eg. [component]_features_export() rather than + * [module]_features_export(). + * + * Render one or more component objects to code. + * + * @param string $module_name + * The name of the feature module to be exported. + * @param array $data + * An array of machine name identifiers for the objects to be rendered. + * @param array $export + * The full export array of the current feature being exported. This is only + * passed when hook_features_export_render() is invoked for an actual feature + * update or recreate, not during state checks or other operations. + * @return array + * An associative array of rendered PHP code where the key is the name of the + * hook that should wrap the PHP code. The hook should not include the name + * of the module, e.g. the key for `hook_example` should simply be `example`. + */ +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", $mycomponents); + return array('mycomponent_defaults' => $code); +} + +/** + * Component hook. The hook should be implemented using the name ot the + * component, not the module, eg. [component]_features_export() rather than + * [module]_features_export(). + * + * Revert all component objects for a given feature module. + * + * @param string $module_name + * The name of the feature module whose components should be reverted. + * @return boolean + * TRUE or FALSE for whether the components were successfully reverted. + */ +function hook_features_export_revert($module_name) { + $mycomponents = module_invoke_all($module_name, 'mycomponent_defaults'); + if (!empty($$mycomponents)) { + foreach ($mycomponents as $mycomponent) { + mycomponent_delete($mycomponent); + } + } +} + +/** + * Component hook. The hook should be implemented using the name ot the + * component, not the module, eg. [component]_features_export() rather than + * [module]_features_export(). + * + * Rebuild all component objects for a given feature module. Should only be + * implemented for 'faux-exportable' components. + * + * This hook is called at points where Features determines that it is safe + * (ie. the feature is in state `FEATURES_REBUILDABLE`) for your module to + * replace objects in the database with defaults that you collect from your + * own defaults hook. See API.txt for how Features determines whether a + * rebuild of components is possible. + * + * @param string $module_name + * The name of the feature module whose components should be rebuilt. + */ +function hook_features_export_rebuild($module_name) { + $mycomponents = module_invoke_all($module_name, 'mycomponent_defaults'); + if (!empty($$mycomponents)) { + foreach ($mycomponents as $mycomponent) { + mycomponent_save($mycomponent); + } + } +} + +/** + * Alter the final export array just prior to the rendering of defaults. Allows + * modules a final say in altering what component objects are exported. + * + * @param array &$export + * By reference. An array of all components 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['node']['page'])) { + unset($export['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) { +} + +/** + * @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 cck fields right before they are cached into the database. + * + * @param &$fields + * By reference. The fields that have been declared by another feature. + */ +function hook_content_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) { +} + +/** + * @} + */ diff --git a/sites/all/modules/features/features.css b/sites/all/modules/features/features.css new file mode 100644 index 0000000000000000000000000000000000000000..3d5f5f8b6c654e3e27dd383a1eff9c74263803b4 --- /dev/null +++ b/sites/all/modules/features/features.css @@ -0,0 +1,227 @@ +/** + * 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; + 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; + margin-right:-1px; + } + +/* Packages */ +div.features-form-package { + border:1px solid #ccc; + background:#fff; + 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; + color: #fff; + } + + 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; + } diff --git a/sites/all/modules/features/features.drush.inc b/sites/all/modules/features/features.drush.inc new file mode 100644 index 0000000000000000000000000000000000000000..fe02c516137097597dbff4f2fe36ebee7cbb84ec --- /dev/null +++ b/sites/all/modules/features/features.drush.inc @@ -0,0 +1,447 @@ +<?php + +/** + * @file + * Features module drush integration. + */ + +/** + * Implementation of hook_drush_command(). + * + * @See drush_parse_command() for a list of recognized keys. + * + * @return + * An associative array describing your command(s). + */ +function features_drush_command() { + $items = array(); + + $items['features-list'] = array( + 'description' => "List all the available features for your site.", + 'drupal dependencies' => array('features'), + 'aliases' => array('fl', 'features'), + ); + $items['features-export'] = array( + 'description' => "Export a feature from your site into a module.", + 'arguments' => array( + 'feature' => 'Feature name to export.', + ), + 'drupal dependencies' => array('features'), + 'aliases' => array('fe'), + ); + $items['features-update'] = array( + 'description' => "Update a feature module on your site.", + 'arguments' => array( + 'feature' => 'A space delimited list of features.', + ), + '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.', + ), + 'options' => array( + '--force' => "Force revert even if Features assumes components' state are default.", + ), + 'drupal dependencies' => array('features'), + 'aliases' => array('fr'), + ); + $items['features-revert-all'] = array( + 'description' => "Revert all enabled feature module on your site.", + 'arguments' => array( + 'feature_exclude' => 'A space-delimited list of features to exclude from being reverted.', + ), + 'options' => array( + '--force' => "Force revert even if Features assumes components' state are default.", + ), + 'drupal dependencies' => array('features'), + 'aliases' => array('fr-all', 'fra'), + ); + $items['features-diff'] = array( + 'description' => "Show the difference between the default and overridden state of a feature.", + 'arguments' => array( + 'feature' => 'The feature in question.', + ), + 'drupal dependencies' => array('features', 'diff'), + 'aliases' => array('fd'), + ); + + return $items; +} + +/** + * Implementation of hook_drush_help(). + */ +function features_drush_help($section) { + switch ($section) { + case 'drush:features': + return dt("List all the available features for your site."); + case 'drush:features-export': + return dt("Export a feature from your site into a module."); + case 'drush:features-update': + return dt("Update a feature module on your site."); + 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-revert': + return dt("Revert a feature module on your site."); + case 'drush:features-diff': + return dt("Show a diff of a feature module."); + } +} + +/** + * Get a list of all feature modules. + */ +function drush_features_list() { + module_load_include('inc', 'features', 'features.export'); + $rows = array(array(dt('Name'), dt('Feature'), dt('Status'), dt('State'))); + foreach (features_get_features(NULL, TRUE) as $k => $m) { + switch (features_get_storage($m->name)) { + case FEATURES_DEFAULT: + case FEATURES_REBUILDABLE: + $storage = ''; + break; + case FEATURES_OVERRIDDEN: + $storage = dt('Overridden'); + break; + case FEATURES_NEEDS_REVIEW: + $storage = dt('Needs review'); + break; + } + $rows[] = array( + $m->info['name'], + $m->name, + $m->status ? dt('Enabled') : dt('Disabled'), + $storage + ); + } + drush_print_table($rows, TRUE); +} + +/** + * Create a feature module based on a list of components. + */ +function drush_features_export() { + $args = func_get_args(); + + if (count($args) == 1) { + // Assume that the user intends to create a module with the same name as the + // "value" of the component. + list($source, $component) = explode(':', $args[0]); + $stub = array($source => array($component)); + _drush_features_export($stub, $component); + } + elseif (count($args) > 1) { + // Assume that the user intends to create a new module based on a list of + // components. First argument is assumed to be the name. + $name = array_shift($args); + $stub = array(); + foreach ($args as $v) { + list($source, $component) = explode(':', $v); + $stub[$source][] = $component; + } + _drush_features_export($stub, array(), $name); + } + else { + $rows = array(array(dt('Available sources'))); + foreach (features_get_components(TRUE) as $component => $info) { + if ($options = features_invoke($component, 'features_export_options')) { + foreach ($options as $key => $value) { + $rows[] = array($component .':'. $key); + } + } + } + drush_print_table($rows, TRUE); + } +} + +/** + * Update an existing feature module. + */ +function drush_features_update() { + if ($args = func_get_args()) { + foreach ($args as $module) { + if (($feature = feature_load($module, TRUE)) && module_exists($module)) { + _drush_features_export($feature->info['features'], $feature->info['dependencies'], $feature->name, dirname($feature->filename)); + } + else if ($feature) { + _features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED'); + } + else { + _features_drush_set_error($module); + } + } + } + else { + // By default just show contexts that are available. + $rows = array(array(dt('Available features'))); + foreach (features_get_features(NULL, TRUE) as $name => $info) { + $rows[] = array($name); + } + drush_print_table($rows, TRUE); + } +} + +/** + * Update all enabled features. Optionally pass in a list of features to + * exclude from being updated. + */ +function drush_features_update_all() { + $features_to_update = array(); + $features_to_exclude = func_get_args(); + foreach (features_get_features() as $module) { + if ($module->status && !in_array($module->name, $features_to_exclude)) { + $features_to_update[] = $module->name; + } + } + drush_print(dt('The following modules will be updated: !modules', array('!modules' => implode(', ', $features_to_update)))); + if (drush_confirm(dt('Do you really want to continue?'))) { + foreach ($features_to_update as $module_name) { + drush_backend_invoke('features-update '. $module_name); + } + } + else { + drush_die('Aborting.'); + } +} + +/** + * Write a module to the site dir. + * + * @param $requests + * An array of the context requested in this export. + * @param $module_name + * Optional. The name for the exported module. + */ +function _drush_features_export($stub, $dependencies, $module_name = NULL, $directory = NULL) { + $root = drush_get_option(array('r', 'root'), drush_locate_root()); + if ($root) { + $directory = isset($directory) ? $directory : 'sites/all/modules/' . $module_name; + if (is_dir($directory)) { + drush_print(dt('Module appears to already exist in !dir', array('!dir' => $directory))); + if (!drush_confirm(dt('Do you really want to continue?'))) { + drush_die('Aborting.'); + } + } + else { + drush_op('mkdir', $directory); + } + if (is_dir($directory)) { + drupal_flush_all_caches(); + module_load_include('inc', 'features', 'features.export'); + $export = features_populate($stub, $dependencies, $module_name); + if (!feature_load($module_name)) { + $export['name'] = $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')); + } +} + +/** + * Revert a feature to it's code definition. + */ +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'); + foreach ($args as $module) { + if (($feature = feature_load($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. + else { + $states = features_get_component_states(array($feature->name), FALSE); + foreach ($states[$feature->name] as $component => $state) { + if (in_array($state, array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW)) && features_hook($component, 'features_revert')) { + $components[] = $component; + } + } + } + + if (empty($components)) { + drush_log(dt('Current state already matches defaults, aborting.'), 'ok'); + } + else { + foreach ($components as $component) { + if (drush_confirm(dt('Do you really want to revert !component?', array('!component' => $component)))) { + features_revert(array($module => array($component))); + drush_log(dt('Reverted !component.', array('!component' => $component)), 'ok'); + } + else { + drush_log(dt('Skipping !component.', array('!component' => $component)), 'ok'); + } + } + } + } + else if ($feature) { + _features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED'); + } + else { + _features_drush_set_error($module); + } + } + } + else { + drush_features_list(); + return; + } +} + +/** + * Revert all enabled features to their definitions in code. Optionally pass in + * a list of features to exclude from being reverted. + */ +function drush_features_revert_all() { + $features_to_revert = 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_revert[] = $module->name; + } + } + drush_print(dt('The following modules will be reverted: !modules', array('!modules' => implode(', ', $features_to_revert)))); + if (drush_confirm(dt('Do you really want to continue?'))) { + foreach ($features_to_revert as $module_name) { + drush_backend_invoke('features-revert '. $module_name); + } + } + else { + drush_die('Aborting.'); + } +} + +/** + * Show the diff of a feature module. + */ +function drush_features_diff() { + if (!$args = func_get_args()) { + drush_features_list(); + return; + } + $module = $args[0]; + $feature = feature_load($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_backend_invoke('dl diff')) { + return drush_set_error(dt('Diff module could not be downloaded.')); + } + + if (!drush_backend_invoke('en 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'); + } + + $formatter = new DiffFormatter(); + $formatter->leading_context_lines = 2; + $formatter->trailing_context_lines = 2; + $formatter->show_header = FALSE; + + foreach ($overrides as $component => $items) { + $diff = new Diff(explode("\n", $items['default']), explode("\n", $items['normal'])); + drush_print(); + drush_print(dt("Component: !component", array('!component' => $component))); + $rows = explode("\n", $formatter->format($diff)); + 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"; + } + 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; + default: + $error = 'FEATURES_FEATURE_NOT_FOUND'; + $message = 'The feature !feature could not be found.'; + } + + return drush_set_error($error, dt($message, $args)); +} diff --git a/sites/all/modules/features/features.export.inc b/sites/all/modules/features/features.export.inc new file mode 100644 index 0000000000000000000000000000000000000000..cc11478892065a1a3d42bd47948d15843bcb4b2f --- /dev/null +++ b/sites/all/modules/features/features.export.inc @@ -0,0 +1,835 @@ +<?php + +/** + * @param $items + * @param $module_name + * @return + */ +function features_populate($items, $dependencies, $module_name) { + // Sanitize items. + $items = array_filter($items); + $items['dependencies'] = drupal_map_assoc(array_filter($dependencies)); + + // Populate stub + $stub = array('features' => array(), 'dependencies' => array(), 'conflicts' => array()); + $export = _features_populate($items, $stub, $module_name); + + // Allow other modules to alter the export. + drupal_alter('features_export', $export, $module_name); + + // Clean up and standardize order + foreach (array_keys($export['features']) as $k) { + ksort($export['features'][$k]); + } + ksort($export['features']); + ksort($export['dependencies']); + + return $export; +} + +/** + * Iterate and descend into a feature definition to extract module + * dependencies and feature definition. Calls hook_features_export for modules + * that implement it. + * + * @param $pipe + * Associative of array of module => info-for-module + * @param $export + * Associative array of items, and module dependencies which define a feature. + * Passed by reference. + * + * @return fully populated $export array. + */ +function _features_populate($pipe, &$export, $module_name = '') { + features_include(); + foreach ($pipe as $component => $data) { + if ($function = features_hook($component, 'features_export')) { + // Pass module-specific data and export array. + // We don't use features_invoke() here since we need to pass $export by reference. + $more = $function($data, $export, $module_name, $component); + // Allow other modules to manipulate the pipe to add in additional modules. + drupal_alter('features_pipe_' . $component, $more, $data, $export); + // Allow for export functions to request additional exports. + if (!empty($more)) { + _features_populate($more, $export, $module_name); + } + } + } + return $export; +} + +/** + * Iterates over a list of dependencies and kills modules that are + * captured by other modules 'higher up'. + */ +function _features_export_minimize_dependencies($dependencies, $module_name = '') { + // Ensure that the module doesn't depend upon itself + if (!empty($module_name) && !empty($dependencies[$module_name])) { + unset($dependencies[$module_name]); + } + + // Do some cleanup: + // - Remove modules required by Drupal core. + // - Protect against direct circular dependencies. + // - Remove "intermediate" dependencies. + $required = drupal_required_modules(); + foreach ($dependencies as $k => $v) { + if (empty($v) || in_array($v, $required)) { + unset($dependencies[$k]); + } + else { + $module = features_get_modules($v); + if ($module && !empty($module->info['dependencies'])) { + // If this dependency depends on the module itself, we have a circular dependency. + // Don't let it happen. Only you can prevent forest fires. + if (in_array($module_name, $module->info['dependencies'])) { + unset($dependencies[$k]); + } + // Iterate through the dependency's dependencies and remove any dependencies + // that are captured by it. + else { + foreach ($module->info['dependencies'] as $j => $dependency) { + if (array_search($dependency, $dependencies) !== FALSE) { + $position = array_search($dependency, $dependencies); + unset($dependencies[$position]); + } + } + } + } + } + } + return drupal_map_assoc(array_unique($dependencies)); +} + +/** + * Iterates over a list of dependencies and maximize the list of modules. + */ +function _features_export_maximize_dependencies($dependencies, $module_name = '', $maximized = array(), $first = TRUE) { + foreach ($dependencies as $k => $v) { + if (!in_array($v, $maximized)) { + $maximized[] = $v; + $module = features_get_modules($v); + if ($module && !empty($module->info['dependencies'])) { + $maximized = array_merge($maximized, _features_export_maximize_dependencies($module->info['dependencies'], $module_name, $maximized, FALSE)); + } + } + } + return array_unique($maximized); +} + +/** + * Prepare a feature export array into a finalized info array. + */ +function features_export_prepare($export, $module_name, $reset = FALSE) { + $existing = features_get_modules($module_name, $reset); + + // Prepare info string -- if module exists, merge into its existing info file + $defaults = $existing ? $existing->info : array('core' => '7.x', 'package' => 'Features'); + $export = array_merge($defaults, $export); + + // Cleanup info array + foreach ($export['features'] as $component => $data) { + $export['features'][$component] = array_keys($data); + } + if (isset($export['dependencies'])) { + $export['dependencies'] = array_values($export['dependencies']); + } + if (isset($export['conflicts'])) { + unset($export['conflicts']); + } + ksort($export); + return $export; +} + +/** + * Generate an array of hooks and their raw code. + */ +function features_export_render_hooks($export, $module_name, $reset = FALSE) { + features_include(); + $code = array(); + + // Sort components to keep exported code consistent + ksort($export['features']); + + foreach ($export['features'] as $component => $data) { + if (!empty($data)) { + // Sort the items so that we don't generate different exports based on order + asort($data); + if (features_hook($component, 'features_export_render')) { + $hooks = features_invoke($component, 'features_export_render', $module_name, $data, $export); + $code[$component] = $hooks; + } + } + } + return $code; +} + +/** + * Render feature export into an array representing its files. + * + * @param $export + * An exported feature definition. + * @param $module_name + * The name of the module to be exported. + * @param $reset + * Boolean flag for resetting the module cache. Only set to true when + * doing a final export for delivery. + * + * @return array of info file and module file contents. + */ +function features_export_render($export, $module_name, $reset = FALSE) { + $code = array(); + + // Generate hook code + $component_hooks = features_export_render_hooks($export, $module_name, $reset); + $components = features_get_components(); + + // Group component code into their respective files + foreach ($component_hooks as $component => $hooks) { + $file = array('name' => 'features'); + if (isset($components[$component]['default_file'])) { + switch ($components[$component]['default_file']) { + case FEATURES_DEFAULTS_INCLUDED: + $file['name'] = "features.$component"; + break; + case FEATURES_DEFAULTS_CUSTOM: + $file['name'] = $components[$component]['default_filename']; + break; + } + } + + if (!isset($code[$file['name']])) { + $code[$file['name']] = array(); + } + + foreach ($hooks as $hook_name => $hook_code) { + $code[$file['name']][$hook_name] = features_export_render_defaults($module_name, $hook_name, $hook_code); + } + } + + // Finalize strings to be written to files + foreach ($code as $filename => $contents) { + $code[$filename] = "<?php\n/**\n * @file\n * {$module_name}.{$filename}.inc\n */\n\n". implode("\n\n", $contents) ."\n"; + } + + // Generate info file output + $export = features_export_prepare($export, $module_name, $reset); + $code['info'] = features_export_info($export); + + // Prepare the module + // If module exists, let it be and include it in the files + if ($existing = features_get_modules($module_name, TRUE)) { + $code['module'] = file_get_contents($existing->filename); + + // If the current module file does not reference the features.inc include, + // set a warning message. + // @TODO this way of checking does not account for the possibility of inclusion instruction being commented out. + if (isset($code['features']) && strpos($code['module'], "{$module_name}.features.inc") === FALSE) { + features_log(t('@module does not appear to include the @include file.', array('@module' => "{$module_name}.module", '@include' => "{$module_name}.features.inc")), 'warning'); + } + + // Deprecated files. Display a message for any of these files letting the + // user know that they may be removed. + $deprecated = array( + "{$module_name}.defaults", + "{$module_name}.features.views", + "{$module_name}.features.node" + ); + foreach (file_scan_directory(drupal_get_path('module', $module_name), '/.*/') as $file) { + if (in_array($file->name, $deprecated, TRUE)) { + features_log(t('The file @filename has been deprecated and can be removed.', array('@filename' => $file->filename)), 'status'); + } + elseif ($file->name === "{$module_name}.features" && empty($code['features'])) { + $code['features'] = "<?php\n\n// This file is deprecated and can be removed.\n// Please remove include_once('{$module_name}.features.inc') in {$module_name}.module as well.\n"; + } + } + } + // Add a stub module to include the defaults + else if (!empty($code['features'])) { + $code['module'] = "<?php\n/**\n * @file\n * Code for the {$export['name']} feature.\n */\n\ninclude_once('{$module_name}.features.inc');\n"; + } + else { + $code['module'] = "<?php\n/**\n * @file\n */\n\n// Drupal needs this blank file.\n"; + } + return $code; +} + +/** + * Detect differences between DB and code components of a feature. + */ +function features_detect_overrides($module) { + static $cache; + if (!isset($cache)) { + $cache = array(); + } + if (!isset($cache[$module->name])) { + // Rebuild feature from .info file description and prepare an export from current DB state. + $export = features_populate($module->info['features'], $module->info['dependencies'], $module->name); + $export = features_export_prepare($export, $module->name); + + $overridden = array(); + + // Compare feature info + _features_sanitize($module->info); + _features_sanitize($export); + + $compare = array('normal' => features_export_info($export), 'default' => features_export_info($module->info)); + if ($compare['normal'] !== $compare['default']) { + $overridden['info'] = $compare; + } + + // Collect differences at a per-component level + $states = features_get_component_states(array($module->name), FALSE); + foreach ($states[$module->name] as $component => $state) { + if ($state != FEATURES_DEFAULT) { + $normal = features_get_normal($component, $module->name); + $default = features_get_default($component, $module->name); + _features_sanitize($normal); + _features_sanitize($default); + + $compare = array('normal' => features_var_export($normal), 'default' => features_var_export($default)); + if (_features_linetrim($compare['normal']) !== _features_linetrim($compare['default'])) { + $overridden[$component] = $compare; + } + } + } + $cache[$module->name] = $overridden; + } + return $cache[$module->name]; +} + +/** + * Gets the available default hooks keyed by components. + */ +function features_get_default_hooks($component = NULL, $reset = FALSE) { + static $hooks; + if (!isset($hooks) || $reset) { + $hooks = array(); + features_include(); + foreach (module_implements('features_api') as $module) { + $info = module_invoke($module, 'features_api'); + foreach ($info as $k => $v) { + if (isset($v['default_hook'])) { + $hooks[$k] = $v['default_hook']; + } + } + } + } + if (isset($component)) { + return isset($hooks[$component]) ? $hooks[$component] : FALSE; + } + return $hooks; +} + +/** + * Return a code string representing an implementation of a defaults module hook. + */ +function features_export_render_defaults($module, $hook, $code) { + $output = array(); + $output[] = "/**"; + $output[] = " * Implementation of hook_{$hook}()."; + $output[] = " */"; + $output[] = "function {$module}_{$hook}() {"; + $output[] = $code; + $output[] = "}"; + return implode("\n", $output); +} + +/** + * Generate code friendly to the Drupal .info format from a structured array. + * + * @param $info + * An array or single value to put in a module's .info file. + * @param $parents + * Array of parent keys (internal use only). + * + * @return + * A code string ready to be written to a module's .info file. + */ +function features_export_info($info, $parents = array()) { + $output = ''; + if (is_array($info)) { + foreach ($info as $k => $v) { + $child = $parents; + $child[] = $k; + $output .= features_export_info($v, $child); + } + } + else if (!empty($info) && count($parents)) { + $line = array_shift($parents); + foreach ($parents as $key) { + $line .= is_numeric($key) ? "[]" : "[{$key}]"; + } + $line .= " = \"{$info}\"\n"; + return $line; + } + return $output; +} + +/** + * Tar creation function. Written by dmitrig01. + * + * @param $name + * Filename of the file to be tarred. + * @param $contents + * String contents of the file. + * + * @return + * A string of the tar file contents. + */ +function features_tar_create($name, $contents) { + $tar = ''; + $binary_data_first = pack("a100a8a8a8a12A12", + $name, + '100644 ', // File permissions + ' 765 ', // UID, + ' 765 ', // GID, + sprintf("%11s ", decoct(strlen($contents))), // Filesize, + sprintf("%11s", decoct(REQUEST_TIME)) // Creation time + ); + $binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", '', '', '', '', '', '', '', '', '', ''); + + $checksum = 0; + for ($i = 0; $i < 148; $i++) { + $checksum += ord(substr($binary_data_first, $i, 1)); + } + for ($i = 148; $i < 156; $i++) { + $checksum += ord(' '); + } + for ($i = 156, $j = 0; $i < 512; $i++, $j++) { + $checksum += ord(substr($binary_data_last, $j, 1)); + } + + $tar .= $binary_data_first; + $tar .= pack("a8", sprintf("%6s ", decoct($checksum))); + $tar .= $binary_data_last; + + $buffer = str_split($contents, 512); + foreach ($buffer as $item) { + $tar .= pack("a512", $item); + } + return $tar; +} + +/** + * Export var function -- from Views. + */ +function features_var_export($var, $prefix = '', $init = TRUE) { + if (is_object($var)) { + $output = method_exists($var, 'export') ? $var->export() : features_var_export((array) $var); + } + else if (is_array($var)) { + if (empty($var)) { + $output = 'array()'; + } + else { + $output = "array(\n"; + foreach ($var as $key => $value) { + // Using normal var_export on the key to ensure correct quoting. + $output .= " " . var_export($key, TRUE) . " => " . features_var_export($value, ' ', FALSE) . ",\n"; + } + $output .= ')'; + } + } + else if (is_bool($var)) { + $output = $var ? 'TRUE' : 'FALSE'; + } + else if (is_string($var) && strpos($var, "\n") !== FALSE) { + // Replace line breaks in strings with a token for replacement + // at the very end. This protects whitespace in strings from + // unintentional indentation. + $var = str_replace("\n", "***BREAK***", $var); + $output = var_export($var, TRUE); + } + else { + $output = var_export($var, TRUE); + } + + if ($prefix) { + $output = str_replace("\n", "\n$prefix", $output); + } + + if ($init) { + $output = str_replace("***BREAK***", "\n", $output); + } + + return $output; +} + +/** + * Helper function to return an array of t()'d translatables strings. + * Useful for providing a separate array of translatables with your + * export so that string extractors like potx can detect them. + */ +function features_translatables_export($translatables, $indent = '') { + $output = ''; + $translatables = array_filter(array_unique($translatables)); + if (!empty($translatables)) { + $output .= "{$indent}// Translatables\n"; + $output .= "{$indent}// Included for use with string extractors like potx.\n"; + sort($translatables); + foreach ($translatables as $string) { + $output .= "{$indent}t(" . features_var_export($string) . ");\n"; + } + } + return $output; +} + +/** + * Get a summary storage state for a feature. + */ +function features_get_storage($module_name) { + // Get component states, and array_diff against array(FEATURES_DEFAULT). + // If the returned array has any states that don't match FEATURES_DEFAULT, + // return the highest state. + $states = features_get_component_states(array($module_name), FALSE); + $states = array_diff($states[$module_name], array(FEATURES_DEFAULT)); + $storage = !empty($states) ? max($states) : FEATURES_DEFAULT; + return $storage; +} + +/** + * Wrapper around features_get_[storage] to return an md5hash of a normalized + * defaults/normal object array. Can be used to compare normal/default states + * of a module's component. + */ +function features_get_signature($state = 'default', $module_name, $component, $reset = FALSE) { + switch ($state) { + case 'cache': + $codecache = variable_get('features_codecache', array()); + return isset($codecache[$module_name][$component]) ? $codecache[$module_name][$component] : FALSE; + case 'default': + $objects = features_get_default($component, $module_name, TRUE, $reset); + break; + case 'normal': + $objects = features_get_normal($component, $module_name, $reset); + break; + } + if (!empty($objects)) { + $objects = (array) $objects; + _features_sanitize($objects); + return md5(_features_linetrim(features_var_export($objects))); + } + return FALSE; +} + +/** + * Set the signature of a module/component pair in the codecache. + */ +function features_set_signature($module, $component, $signature = NULL) { + $var_codecache = variable_get('features_codecache', array()); + $signature = isset($signature) ? $signature : features_get_signature('default', $module, $component, TRUE); + $var_codecache[$module][$component] = $signature; + variable_set('features_codecache', $var_codecache); +} + +/** + * Processing semaphore operations. + */ +function features_semaphore($op, $component) { + // Note: we don't use variable_get() here as the inited variable + // static cache may be stale. Retrieving directly from the DB narrows + // the possibility of collision. + $semaphore = db_query("SELECT value FROM {variable} WHERE name = :name", array(':name' => 'features_semaphore'))->fetchField(); + $semaphore = !empty($semaphore) ? unserialize($semaphore) : array(); + + switch ($op) { + case 'get': + return isset($semaphore[$component]) ? $semaphore[$component] : FALSE; + case 'set': + $semaphore[$component] = REQUEST_TIME; + variable_set('features_semaphore', $semaphore); + break; + case 'del': + if (isset($semaphore[$component])) { + unset($semaphore[$component]); + variable_set('features_semaphore', $semaphore); + } + break; + } +} + +/** + * Get normal objects for a given module/component pair. + */ +function features_get_normal($component, $module_name, $reset = FALSE) { + static $cache; + if (!isset($cache) || $reset) { + $cache = array(); + } + if (!isset($cache[$module_name][$component])) { + features_include(); + $code = NULL; + $module = features_get_features($module_name); + + // Special handling for dependencies component. + if ($component === 'dependencies') { + $cache[$module_name][$component] = isset($module->info['dependencies']) ? array_filter($module->info['dependencies'], 'module_exists') : array(); + } + // All other components. + else { + $default_hook = features_get_default_hooks($component); + if ($module && $default_hook && isset($module->info['features'][$component]) && features_hook($component, 'features_export_render')) { + $code = features_invoke($component, 'features_export_render', $module_name, $module->info['features'][$component], NULL); + $cache[$module_name][$component] = isset($code[$default_hook]) ? eval($code[$default_hook]) : FALSE; + } + } + + // Clear out vars for memory's sake. + unset($code); + unset($module); + } + return isset($cache[$module_name][$component]) ? $cache[$module_name][$component] : FALSE; +} + +/** + * Get defaults for a given module/component pair. + */ +function features_get_default($component, $module_name = NULL, $alter = TRUE, $reset = FALSE) { + static $cache = array(); + features_include(); + features_include_defaults($component); + $default_hook = features_get_default_hooks($component); + $components = features_get_components(); + + // Collect defaults for all modules if no module name was specified. + if (isset($module_name)) { + $modules = array($module_name); + } + else { + if ($component === 'dependencies') { + $modules = array_keys(features_get_features()); + } + else { + $modules = array(); + foreach (features_get_component_map($component) as $component_modules) { + $modules = array_merge($modules, $component_modules); + } + $modules = array_unique($modules); + } + } + + // Collect and cache information for each specified module. + foreach ($modules as $m) { + if (!isset($cache[$component][$m]) || $reset) { + // Special handling for dependencies component. + if ($component === 'dependencies') { + $module = features_get_features($m); + $cache[$component][$m] = isset($module->info['dependencies']) ? $module->info['dependencies'] : array(); + unset($module); + } + // All other components + else { + if ($default_hook && module_hook($m, $default_hook)) { + $cache[$component][$m] = call_user_func("{$m}_{$default_hook}"); + if ($alter) { + drupal_alter($default_hook, $cache[$component][$m]); + } + } + else { + $cache[$component][$m] = FALSE; + } + } + } + } + + // A specific module was specified. Retrieve only its components. + if (isset($module_name)) { + return isset($cache[$component][$module_name]) ? $cache[$component][$module_name] : FALSE; + } + // No module was specified. Retrieve all components. + $all_defaults = array(); + if (isset($cache[$component])) { + foreach (array_filter($cache[$component]) as $module_components) { + $all_defaults = array_merge($all_defaults, $module_components); + } + } + return $all_defaults; +} + +/** + * Get a map of components to their providing modules. + */ +function features_get_default_map($component, $attribute = NULL, $callback = NULL, $reset = FALSE) { + static $map = array(); + features_include(); + features_include_defaults($component); + if ((!isset($map[$component]) || $reset) && $default_hook = features_get_default_hooks($component)) { + $map[$component] = array(); + foreach (module_implements($default_hook) as $module) { + if ($defaults = features_get_default($component, $module)) { + foreach ($defaults as $key => $object) { + if (isset($callback)) { + if ($object_key = $callback($object)) { + $map[$component][$object_key] = $module; + } + } + elseif (isset($attribute)) { + if (is_object($object) && isset($object->{$attribute})) { + $map[$component][$object->{$attribute}] = $module; + } + elseif (is_array($object) && isset($object[$attribute])) { + $map[$component][$object[$attribute]] = $module; + } + } + elseif (!isset($attribute) && !isset($callback)) { + if (!is_numeric($key)) { + $map[$component][$key] = $module; + } + } + else { + return FALSE; + } + } + } + } + } + return isset($map[$component]) ? $map[$component] : FALSE; +} + +/** + * Retrieve an array of features/components and their current states. + */ +function features_get_component_states($features = array(), $rebuild_only = TRUE, $reset = FALSE) { + static $cache; + if (!isset($cache) || $reset) { + $cache = array(); + } + + $features = !empty($features) ? $features : array_keys(features_get_features()); + + // Retrieve only rebuildable components if requested. + features_include(); + $components = array_keys(features_get_components()); + if ($rebuild_only) { + foreach ($components as $k => $component) { + if (!features_hook($component, 'features_rebuild')) { + unset($components[$k]); + } + } + } + + foreach ($features as $feature) { + $cache[$feature] = isset($cache[$feature]) ? $cache[$feature] : array(); + if (module_exists($feature)) { + foreach ($components as $component) { + if (!isset($cache[$feature][$component])) { + $normal = features_get_signature('normal', $feature, $component, $reset); + $default = features_get_signature('default', $feature, $component, $reset); + $codecache = features_get_signature('cache', $feature, $component, $reset); + $semaphore = features_semaphore('get', $component); + + // DB and code states match, there is nothing more to check. + if ($normal == $default) { + $cache[$feature][$component] = FEATURES_DEFAULT; + + // Stale semaphores can be deleted. + features_semaphore('del', $component); + + // Update code cache if it is stale, clear out semaphore if it stale. + if ($default != $codecache) { + features_set_signature($feature, $component, $default); + } + } + // Component properly implements exportables. + else if (!features_hook($component, 'features_rebuild')) { + $cache[$feature][$component] = FEATURES_OVERRIDDEN; + } + // Component does not implement exportables. + else { + if (empty($semaphore)) { + // Exception for dependencies. Dependencies are always rebuildable. + if ($component === 'dependencies') { + $cache[$feature][$component] = FEATURES_REBUILDABLE; + } + // All other rebuildable components require comparison. + else { + // Code has not changed, but DB does not match. User has DB overrides. + if ($codecache == $default) { + $cache[$feature][$component] = FEATURES_OVERRIDDEN; + } + // DB has no modifications to prior code state (or this is initial install). + else if ($codecache == $normal || empty($codecache)) { + $cache[$feature][$component] = FEATURES_REBUILDABLE; + } + // None of the states match. Requires user intervention. + else if ($codecache != $default) { + $cache[$feature][$component] = FEATURES_NEEDS_REVIEW; + } + } + } + else { + // Semaphore is still within processing horizon. Do nothing. + if ((REQUEST_TIME - $semaphore) < FEATURES_SEMAPHORE_TIMEOUT) { + $cache[$feature][$component] = FEATURES_REBUILDING; + } + // A stale semaphore means a previous rebuild attempt did not complete. + // Attempt to complete the rebuild. + else { + $cache[$feature][$component] = FEATURES_REBUILDABLE; + } + } + } + } + } + } + } + + // Filter cached components on the way out to ensure that even if we have + // cached more data than has been requested, the return value only reflects + // the requested features/components. + $return = $cache; + $return = array_intersect_key($return, array_flip($features)); + foreach ($return as $k => $v) { + $return[$k] = array_intersect_key($return[$k], array_flip($components)); + } + return $return; +} + +/** + * Helper function to eliminate whitespace differences in code. + */ +function _features_linetrim($code) { + $code = explode("\n", $code); + foreach ($code as $k => $line) { + $code[$k] = trim($line); + } + return implode("\n", $code); +} + +/** + * "Sanitizes" an array recursively, performing two key operations: + * - Sort an array by its keys (assoc) or values (non-assoc) + * - Remove any null or empty values for associative arrays (array_filter()). + */ +function _features_sanitize(&$array) { + if (is_array($array)) { + if (_features_is_assoc($array)) { + ksort($array); + $array = array_filter($array); + } + else { + sort($array); + } + foreach ($array as $k => $v) { + if (is_array($v)) { + _features_sanitize($array[$k]); + } + } + } +} + +/** + * Is the given array an associative array. This basically extracts the keys twice to get the + * numerically ordered keys. It then does a diff with the original array and if there is no + * key diff then the original array is not associative. + * + * NOTE: If you have non-sequential numerical keys, this will identify the array as assoc. + * + * Borrowed from: http://www.php.net/manual/en/function.is-array.php#96724 + * + * @return True is the array is an associative array, false otherwise + */ +function _features_is_assoc($array) { + return (is_array($array) && (0 !== count(array_diff_key($array, array_keys(array_keys($array)))) || count($array)==0)); +} diff --git a/sites/all/modules/features/features.info b/sites/all/modules/features/features.info new file mode 100644 index 0000000000000000000000000000000000000000..0517218cee73714bcd283f934cd6a319eaffe3d3 --- /dev/null +++ b/sites/all/modules/features/features.info @@ -0,0 +1,12 @@ +name = "Features" +description = "Provides feature management for Drupal." +core = 7.x +package = "Features" +files[] = tests/features.test + +; Information added by drupal.org packaging script on 2011-04-06 +version = "7.x-1.0-beta2" +core = "7.x" +project = "features" +datestamp = "1302049346" + diff --git a/sites/all/modules/features/features.install b/sites/all/modules/features/features.install new file mode 100644 index 0000000000000000000000000000000000000000..419af3f725291842bcf3bf8ed641a29bf4255fad --- /dev/null +++ b/sites/all/modules/features/features.install @@ -0,0 +1,119 @@ +<?php + +/** + * @file + * Install, update and uninstall functions for the features module. + */ + +/** + * Implements hook_install(). + */ +function features_install() { + _features_install_menu(); + db_update('system') + ->fields(array('weight' => 20)) + ->condition('name', 'features') + ->condition('type', 'module') + ->execute(); +} + +/** + * Implements hook_uninstall(). + */ +function features_uninstall() { + variable_del('features_codecache'); + variable_del('features_semaphore'); + variable_del('features_ignored_orphans'); + db_delete('menu_custom') + ->condition('menu_name', 'features') + ->execute(); + db_delete('menu_links') + ->condition('module', 'features') + ->execute(); +} + +/** + * Create menu. See menu.install for an example. + */ +function _features_install_menu() { + if (db_table_exists('menu_custom') && !db_query("SELECT menu_name FROM {menu_custom} WHERE menu_name = :menu_name", array(':menu_name' => 'features'))->fetchField()) { + $t = get_t(); + $id = db_insert('menu_custom') + ->fields(array( + 'menu_name' => 'features', + 'title' => $t('Features'), + 'description' => $t('Menu items for any enabled features.'), + )) + ->execute(); + } +} + +/** + * Update 6100: Set module on all feature node types to 'features'. + + * This update can be re-run as needed to repair any node types that are not + * removed after disabling the associated feature. + * + * Any feature implementing a node component that was exported prior to this + * version of the features.module will need to have its 'module' declaration + * in hook_node_info() changed from 'node' to 'features'. + */ +function features_update_6100() { + $ret = array(); + + foreach (features_get_features(NULL, TRUE) as $feature) { + if (module_exists($feature->name) && $types = module_invoke($feature->name, 'node_info')) { + foreach ($types as $type => $type_data) { + $sql = "SELECT COUNT(*) FROM {node_type} WHERE module = 'node' AND type = '%s'"; + // Only update if the hook_node_info type's module is 'features' and the db type's + // module is 'node'. + if (($type_data['module'] == 'features') && db_query($sql, $type)->fetchField()) { + $ret[] = update_sql("UPDATE {node_type} SET module = 'features' WHERE type = '$type'"); + } + } + } + } + return $ret; +} + +/** + * Update 6101: Set codestate signature for all features. + * + * This update generates a codestate for all feature/component pairs which + * have been installed prior to this version of Features. This prevents + * automatic rebuilds from occurring against any rebuildable components + * that have been overridden. + */ +function features_update_6101() { + // Ensure all of our own API functions still exist in in this version + // of Features. It's possible that the "future me" will not have these + // functions, so I should check. + module_load_include('inc', 'features', "features.export"); + $functions = array( + 'features_include', + 'features_hook', + 'features_get_components', + 'features_get_features', + 'features_get_signature', + 'features_set_signature', + ); + $doit = TRUE; + foreach ($functions as $function) { + $doit = $doit && function_exists($function); + } + if ($doit) { + features_include(); + $features = array_keys(features_get_features(NULL, TRUE)); + $components = array_keys(features_get_components()); + foreach ($features as $feature) { + if (module_exists($feature)) { + foreach ($components as $component) { + if (features_hook($component, 'features_rebuild') && features_get_signature('cache', $feature, $component) === FALSE) { + features_set_signature($feature, $component, -1); + } + } + } + } + } + return array(); +} diff --git a/sites/all/modules/features/features.js b/sites/all/modules/features/features.js new file mode 100644 index 0000000000000000000000000000000000000000..ce32920ccbf3d4afd67e0e06a7c19ca7b06fab5f --- /dev/null +++ b/sites/all/modules/features/features.js @@ -0,0 +1,100 @@ +(function ($) { + Drupal.behaviors.features = { + attach: function(context, settings) { + // Features management form + $('table.features:not(.processed)').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)').each(function() { + $(this) + .addClass('processed') + .change(function() { + var target = $(this).val(); + $('div.features-select').hide(); + $('div.features-select-' + target).show(); + return false; + }); + }); + + // Export form machine-readable JS + $('.feature-name:not(.processed)').each(function() { + $('.feature-name') + .addClass('processed') + .after(' <small class="feature-module-name-suffix"> </small>'); + if ($('.feature-module-name').val() === $('.feature-name').val().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/_+/g, '_') || $('.feature-module-name').val() === '') { + $('.feature-module-name').parents('.form-item').hide(); + $('.feature-name').keyup(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(); + } + }); + } + } + + + Drupal.features = { + 'checkStatus': function() { + $('table.features tbody tr').not('.processed').filter(':first').each(function() { + var elem = $(this); + $(elem).addClass('processed'); + var uri = $(this).find('a.admin-check').attr('href'); + if (uri) { + $.get(uri, [], function(data) { + $(elem).find('.admin-loading').hide(); + switch (data.storage) { + case 3: + $(elem).find('.admin-rebuilding').show(); + break; + case 2: + $(elem).find('.admin-needs-review').show(); + break; + case 1: + $(elem).find('.admin-overridden').show(); + break; + default: + $(elem).find('.admin-default').show(); + break; + } + Drupal.features.checkStatus(); + }, 'json'); + } + else { + Drupal.features.checkStatus(); + } + }); + } + }; + + +})(jQuery); + + diff --git a/sites/all/modules/features/features.module b/sites/all/modules/features/features.module new file mode 100644 index 0000000000000000000000000000000000000000..f85cf4c49d113f0fa6247bf4f9b35b7a39e74bd0 --- /dev/null +++ b/sites/all/modules/features/features.module @@ -0,0 +1,764 @@ +<?php + +/** + * @file + * Module file for the features module, which enables the capture and + * management of features in Drupal. A feature is a collection of Drupal + * entities which taken together statisfy a certain use-case. + */ + +define('FEATURES_MODULE_ENABLED', 1); +define('FEATURES_MODULE_DISABLED', 0); +define('FEATURES_MODULE_MISSING', -1); + +define('FEATURES_REBUILDABLE', -1); +define('FEATURES_DEFAULT', 0); +define('FEATURES_OVERRIDDEN', 1); +define('FEATURES_NEEDS_REVIEW', 2); +define('FEATURES_REBUILDING', 3); +define('FEATURES_CONFLICT', 4); +define('FEATURES_DISABLED', 5); + +// Duration of rebuild semaphore: 10 minutes. +define('FEATURES_SEMAPHORE_TIMEOUT', 10 * 60); + +/** + * Components with this 'default_file' flag will have exports written to the + * common defaults file 'MODULENAME.features.inc'. This is the default + * behavior. + */ +define('FEATURES_DEFAULTS_INCLUDED_COMMON', 0); + +/** + * Components with this 'default_file' flag will have exports written to a + * defaults based on the component name like 'MODULENAME.features.COMPONENT-NAME.inc'. + * Any callers to this component's defaults hook must call + * features_include_defaults('component') in order to include this file. + */ +define('FEATURES_DEFAULTS_INCLUDED', 1); + +/** + * Components with this 'default_file' flag must specify a filename for their + * exports. Additionally a stub will NOT be written to 'MODULENAME.features.inc' + * allowing the file to be included directly by the implementing module. + */ +define('FEATURES_DEFAULTS_CUSTOM', 2); + +/** + * Components with this 'duplicates' flag may not have multiple features provide the + * same component key in their info files. This is the default behavior. + */ +define('FEATURES_DUPLICATES_CONFLICT', 0); + +/** + * Components with this 'duplicates' flag are allowed to have multiple features + * provide the same component key in their info files. + */ +define('FEATURES_DUPLICATES_ALLOWED', 1); + +/** + * Implements hook_menu(). + */ +function features_menu() { + $items = array(); + $items['admin/structure/features'] = array( + 'title' => 'Features', + 'description' => 'Manage features.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('features_admin_form'), + 'type' => MENU_NORMAL_ITEM, + 'file' => 'features.admin.inc', + ); + $items['admin/structure/features/cleanup'] = array( + 'title' => 'Cleanup', + 'description' => 'Detect and disable any orphaned feature dependencies.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('features_cleanup_form', 4), + 'type' => MENU_CALLBACK, + 'file' => 'features.admin.inc', + 'weight' => 1, + ); + $items['admin/structure/features/manage'] = array( + 'title' => 'Manage', + 'description' => 'Enable and disable features.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('features_admin_form'), + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'file' => 'features.admin.inc', + ); + $items['admin/structure/features/create'] = array( + 'title' => 'Create feature', + 'description' => 'Create a new feature.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('features_export_form'), + 'access callback' => 'user_access', + 'access arguments' => array('administer features'), + 'type' => MENU_LOCAL_TASK, + 'file' => "features.admin.inc", + 'weight' => 10, + ); + + $items['admin/structure/features/%feature'] = array( + 'title callback' => 'features_get_feature_title', + 'title arguments' => array(3), + 'description' => 'Display components of a feature.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('features_admin_components', 3), + 'load arguments' => array(3, TRUE), + 'access callback' => 'user_access', + 'access arguments' => array('administer features'), + 'type' => MENU_CALLBACK, + 'file' => 'features.admin.inc', + ); + $items['admin/structure/features/%feature/view'] = array( + 'title' => 'View', + 'description' => 'Display components of a feature.', + 'access callback' => 'user_access', + 'access arguments' => array('administer features'), + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + ); + $items['admin/structure/features/%feature/recreate'] = array( + 'title' => 'Recreate', + 'description' => 'Recreate an existing feature.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('features_export_form', 3), + 'load arguments' => array(3, TRUE), + 'access callback' => 'user_access', + 'access arguments' => array('administer features'), + 'type' => MENU_LOCAL_TASK, + 'file' => "features.admin.inc", + 'weight' => 11, + ); + if (module_exists('diff')) { + $items['admin/structure/features/%feature/diff'] = array( + 'title' => 'Review overrides', + 'description' => 'Compare default and current feature.', + 'page callback' => 'features_feature_diff', + 'page arguments' => array(3, 5), + 'load arguments' => array(3, TRUE), + 'access callback' => 'features_access_override_actions', + 'access arguments' => array(3), + 'type' => MENU_LOCAL_TASK, + 'file' => 'features.admin.inc', + ); + } + $items['admin/structure/features/%feature/status'] = array( + 'title' => 'Status', + 'description' => 'Javascript status call back.', + 'page callback' => 'features_feature_status', + 'page arguments' => array(3), + 'load arguments' => array(3, TRUE), + 'access callback' => 'user_access', + 'access arguments' => array('administer features'), + 'type' => MENU_CALLBACK, + 'file' => 'features.admin.inc', + ); + foreach ($items as $path => $item) { + if (!isset($item['access callback'])) { + $items[$path]['access callback'] = 'user_access'; + $items[$path]['access arguments'] = array('manage features'); + } + } + return $items; +} + +/** + * Implements hook_theme(). + */ +function features_theme() { + $base = array( + 'path' => drupal_get_path('module', 'features') . '/theme', + 'file' => 'theme.inc', + ); + + $items = array(); + $items['features_module_status'] = array( + 'variables' => array('module' => null, 'status' => null) + ) + $base; + + $items['features_components'] = array( + 'variables' => array('info' => null, 'sources' => null), + ) + $base; + + $items['features_component_key'] = $base; + $items['features_component_list'] = array( + 'variables' => array('components' => array(), 'source' => array(), 'conflicts' => array()), + ) + $base; + + $items['features_storage_link'] = array( + 'variables' => array('storage' => null, 'path' => null, 'options' => array()), + ) + $base; + + $items['features_form_components'] = + $items['features_form_export'] = + $items['features_form_package'] = array( + 'render element' => 'form', + ) + $base; + + $items['features_form_buttons'] = array( + 'render element' => 'element', + ) + $base; + + + $items['features_admin_components'] = array( + 'render element' => 'form', + 'template' => 'features-admin-components', + ) + $base; + + return $items; +} + +/** + * Implements hook_flush_caches(). + */ +function features_flush_caches() { + features_rebuild(); + features_get_modules(NULL, TRUE); + return array(); +} + +/** + * Implements hook_form(). + */ +function features_form($node, $form_state) { + return node_content_form($node, $form_state); +} + +/** + * Implemenation of hook_node_access() + */ +function features_node_access($node, $op, $account) { + return node_node_access($node, $op, $account); +} + +/** + * Implements hook_permission(). + */ +function features_permission() { + return array( + 'administer features' => array( + 'title' => t('Administer features'), + 'description' => t('Perform administration tasks on features.'), + ), + 'manage features' => array( + 'title' => t('Manage features'), + 'description' => t('View, enable and disable features.'), + ), + ); +} + +/** + * Implementation of hook_help(). + */ +function features_help($path, $arg) { + switch ($path) { + case 'admin/help#features': + $output = file_get_contents(drupal_get_path('module', 'features') .'/README.txt'); + return module_exists('markdown') ? filter_xss_admin(module_invoke('markdown', 'filter', 'process', 0, -1, $output)) : '<pre>'. check_plain($output) .'</pre>'; + case 'admin/build/features': + return '<p>'. t('A "Feature" is a certain type of Drupal module with contains a package of configuration that, when enabled, provides a new set of functionality for your Drupal site. Enable features by selecting the checkboxes below and clicking the Save configuration button. If the configuration of the feature has been changed its "State" will be either "overridden" or "needs review", otherwise it will be "default", indicating that the configuration has not been changed. Click on the state to see more details about the feature and its components.') .'</p>'; + } +} + +/** + * Load includes for any modules that implement the features API and + * load includes for those provided by features. + */ +function features_include($reset = FALSE) { + static $once; + if (!isset($once) || $reset) { + $once = TRUE; + + // Check for implementing modules and make necessary inclusions. + foreach (module_implements('features_api') as $module) { + $info = module_invoke($module, 'features_api'); + foreach ($info as $component) { + if (isset($component['file'])) { + require_once DRUPAL_ROOT . '/' . $component['file']; + } + } + } + + // Features provides integration on behalf of these modules. + // The features include provides handling for the feature dependencies. + // Note that ctools is placed last because it implements hooks "dynamically" for other modules. + $modules = array('features', 'block', 'context', 'field', 'filter', 'image', 'menu', 'node', 'taxonomy', 'user', 'views', 'ctools'); + + foreach (array_filter($modules, 'module_exists') as $module) { + if (!module_hook($module, 'features_api')) { + module_load_include('inc', 'features', "includes/features.$module"); + } + } + + // Clear static cache, since we've now included new implementers. + module_implements('features_api', FALSE, TRUE); + } +} + +/** + * Load features includes for all components that require includes before + * collecting defaults. + */ +function features_include_defaults($components = NULL, $reset = FALSE) { + static $included = array(); + static $include_components; + + // Build an array of components that require inclusion: + // Views, CTools components and those using FEATURES_DEFAULTS_INCLUDED. + if (!isset($include_components) || $reset) { + $include_components = features_get_components(); + foreach ($include_components as $component => $info) { + if ($component !== 'views' && !isset($info['api']) && (!isset($info['default_file']) || $info['default_file'] !== FEATURES_DEFAULTS_INCLUDED)) { + unset($include_components[$component]); + } + } + } + + // If components are specified, only include for the specified components. + if (isset($components)) { + $components = is_array($components) ? $components : array($components); + } + // Use all include components if none are explicitly specified. + else { + $components = $include_components; + } + foreach ($components as $component) { + if (isset($include_components[$component]) && (!isset($included[$component]) || $reset)) { + $info = $include_components[$component]; + // Inclusion of defaults for Views. + if ($component === 'views') { + views_include('view'); + views_include_default_views(); + } + // Inclusion of defaults for CTools. + else if (isset($info['api'], $info['module'], $info['current_version'])) { + ctools_include('plugins'); + ctools_plugin_api_include($info['module'], $info['api'], $info['current_version'], $info['current_version']); + } + // Inclusion of defaults for components using FEATURES_DEFAULTS_INCLUDED. + else { + $features = isset($features) ? $features : features_get_features(NULL, $reset); + foreach ($features as $feature) { + module_load_include('inc', $feature->name, "{$feature->name}.features.{$component}"); + } + } + $included[$component] = TRUE; + } + } +} + +/** + * Feature object loader. + */ +function feature_load($name, $reset = FALSE) { + return features_get_features($name, $reset); +} + +/** + * Return a module 'object' including .info information. + * + * @param $name + * The name of the module to retrieve information for. If ommitted, + * an array of all available modules will be returned. + * @param $reset + * Whether to reset the cache. + * + * @return + * If a module is request (and exists) a module object is returned. If no + * module is requested info for all modules is returned. + */ +function features_get_modules($name = NULL, $reset = FALSE) { + return features_get_info('module', $name, $reset); +} + +/** + * Returns the array of supported components. + * + * @param $feature_source + * If set to to TRUE return feature sources only. + * @return An array of component labels keyed by the component names. + */ +function features_get_components($feature_source = FALSE, $reset = FALSE) { + features_include(); + static $components_all; + static $components_source; + if (!isset($components_all) || $reset) { + $components_all = $components_source = array(); + foreach (module_implements('features_api') as $module) { + $info = module_invoke($module, 'features_api'); + foreach ($info as $k => $v) { + $components_all[$k] = $v; + if (!empty($v['feature_source'])) { + $components_source[$k] = $v; + } + } + } + } + return $feature_source ? $components_source : $components_all; +} + +/** + * Invoke a component callback. + */ +function features_invoke($component, $callback) { + $args = func_get_args(); + unset($args[0], $args[1]); + // Append the component name to the arguments. + $args[] = $component; + if ($function = features_hook($component, $callback)) { + return call_user_func_array($function, $args); + } +} + +/** + * Checks whether a component implements the given hook. + * + * @return + * The function implementing the hook, or FALSE. + */ +function features_hook($component, $hook, $reset = FALSE) { + $info = &drupal_static(__FUNCTION__); + + if (!isset($info) || $reset) { + $info = module_invoke_all('features_api'); + } + // Determine the function callback base. + $base = isset($info[$component]['base']) ? $info[$component]['base'] : $component; + return function_exists($base . '_' . $hook) ? $base . '_' . $hook : FALSE; +} + +/** + * Enables and installs an array of modules, ignoring those + * already enabled & installed. Consider this a helper or + * extension to drupal_install_modules(). + * + * @param $modules + * An array of modules to install. + * @param $reset + * Clear the module info cache. + */ +function features_install_modules($modules) { + module_load_include('inc', 'features', 'features.export'); + $files = system_rebuild_module_data(); + + // Build maximal list of dependencies. + $install = array(); + foreach ($modules as $name) { + if ($file = $files[$name]) { + $install[] = $name; + if (!empty($file->info['dependencies'])) { + $install = array_merge($install, _features_export_maximize_dependencies($file->info['dependencies'])); + } + } + } + + // Filter out enabled modules. + $enabled = array_filter($install, 'module_exists'); + $install = array_diff($install, $enabled); + + if (!empty($install)) { + // Make sure the install API is available. + $install = array_unique($install); + include_once DRUPAL_ROOT . '/' . './includes/install.inc'; + module_enable($install); + } +} + +/** + * Wrapper around features_get_info() that returns an array + * of module info objects that are features. + */ +function features_get_features($name = NULL, $reset = FALSE) { + return features_get_info('feature', $name, $reset); +} + +/** + * Helper for retrieving info from system table. + */ +function features_get_info($type = 'module', $name = NULL, $reset = FALSE) { + static $cache; + if (!isset($cache)) { + $cache = cache_get('features_module_info'); + } + if (empty($cache) || $reset) { + $data = array(); + $ignored = variable_get('features_ignored_orphans', array()); + $result = system_rebuild_module_data(); + foreach ($result as $row) { + // If module is no longer enabled, remove it from the ignored orphans list. + if (in_array($row->name, $ignored, TRUE) && !$row->status) { + $key = array_search($row->name, $ignored, TRUE); + unset($ignored[$key]); + } + + if (!empty($row->info['features'])) { + $data['feature'][$row->name] = $row; + } + $data['module'][$row->name] = $row; + } + variable_set('features_ignored_orphans', $ignored); + cache_set("features_module_info", $data); + $cache = new stdClass(); + $cache->data = $data; + } + if (!empty($name)) { + return !empty($cache->data[$type][$name]) ? clone $cache->data[$type][$name] : array(); + } + return !empty($cache->data[$type]) ? $cache->data[$type] : array(); +} + +/** + * Generate an array of feature dependencies that have been orphaned. + */ +function features_get_orphans($reset = FALSE) { + static $orphans; + if (!isset($orphans) || $reset) { + module_load_include('inc', 'features', 'features.export'); + $orphans = array(); + + // Build a list of all dependencies for enabled and disabled features. + $dependencies = array('enabled' => array(), 'disabled' => array()); + $features = features_get_features(); + foreach ($features as $feature) { + $key = module_exists($feature->name) ? 'enabled' : 'disabled'; + if (!empty($feature->info['dependencies'])) { + $dependencies[$key] = array_merge($dependencies[$key], _features_export_maximize_dependencies($feature->info['dependencies'])); + } + } + $dependencies['enabled'] = array_unique($dependencies['enabled']); + $dependencies['disabled'] = array_unique($dependencies['disabled']); + + // Find the list of orphaned modules. + $orphaned = array_diff($dependencies['disabled'], $dependencies['enabled']); + $orphaned = array_intersect($orphaned, module_list(FALSE, FALSE)); + $orphaned = array_diff($orphaned, drupal_required_modules()); + $orphaned = array_diff($orphaned, array('features')); + + // Build final list of modules that can be disabled. + $modules = features_get_modules(NULL, TRUE); + $enabled = module_list(); + _module_build_dependencies($modules); + + foreach ($orphaned as $module) { + if (!empty($modules[$module]->info['dependents'])) { + // Determine whether any dependents are actually enabled. + $dependents = array_intersect($modules[$module]->info['dependents'], $enabled); + if (empty($dependents)) { + $info = features_get_modules($module); + $orphans[$module] = $info; + } + } + } + } + return $orphans; +} + +/** + * Detect potential conflicts between any features that provide + * identical components. + */ +function features_get_conflicts($reset = FALSE) { + $conflicts = array(); + $component_info = features_get_components(); + $map = features_get_component_map(NULL, $reset); + + foreach ($map as $type => $components) { + // Only check conflicts for components we know about. + if (isset($component_info[$type])) { + foreach ($components as $component => $modules) { + if (isset($component_info[$type]['duplicates']) && $component_info[$type]['duplicates'] == FEATURES_DUPLICATES_ALLOWED) { + continue; + } + else if (count($modules) > 1) { + foreach ($modules as $module) { + if (!isset($conflicts[$module])) { + $conflicts[$module] = array(); + } + foreach ($modules as $m) { + if ($m != $module) { + $conflicts[$module][$m][$type][] = $component; + } + } + } + } + } + } + } + + return $conflicts; +} + +/** + * Provide a component to feature map. + */ +function features_get_component_map($key = NULL, $reset = FALSE) { + static $map; + if (!isset($map) || $reset) { + $map = array(); + $features = features_get_features(NULL, $reset); + foreach ($features as $feature) { + foreach ($feature->info['features'] as $type => $components) { + if (!isset($map[$type])) { + $map[$type] = array(); + } + foreach ($components as $component) { + $map[$type][$component][] = $feature->name; + } + } + } + } + if (isset($key)) { + return isset($map[$key]) ? $map[$key] : array(); + } + return $map; +} + +/** + * Simple wrapper returns the status of a module. + */ +function features_get_module_status($module) { + if (module_exists($module)) { + return FEATURES_MODULE_ENABLED; + } + else if (features_get_modules($module)) { + return FEATURES_MODULE_DISABLED; + } + else { + return FEATURES_MODULE_MISSING; + } +} + +/** + * Menu title callback. + */ +function features_get_feature_title($feature) { + return $feature->info['name']; +} + +/** + * Menu access callback for whether a user should be able to access + * override actions for a given feature. + */ +function features_access_override_actions($feature) { + if (user_access('administer features')) { + static $access = array(); + if (!isset($access[$feature->name])) { + // Set a value first. We may get called again from within features_detect_overrides(). + $access[$feature->name] = FALSE; + + features_include(); + module_load_include('inc', 'features', 'features.export'); + $access[$feature->name] = in_array(features_get_storage($feature->name), array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW)) && user_access('administer features'); + } + return $access[$feature->name]; + } + return FALSE; +} + +/** + * Implements hook_form_alter() for system_modules form(). + */ +function features_form_system_modules_alter(&$form) { + features_rebuild(); +} + +/** + * Restore the specified modules to the default state. + */ +function _features_restore($op, $items = array()) { + module_load_include('inc', 'features', 'features.export'); + features_include(); + + switch ($op) { + case 'revert': + $restore_states = array(FEATURES_OVERRIDDEN, FEATURES_REBUILDABLE, FEATURES_NEEDS_REVIEW); + $restore_hook = 'features_revert'; + $log_action = 'Revert'; + break; + case 'rebuild': + $restore_states = array(FEATURES_REBUILDABLE); + $restore_hook = 'features_rebuild'; + $log_action = 'Rebuild'; + break; + } + + if (empty($items)) { + $states = features_get_component_states(array(), ($op == 'rebuild')); + foreach ($states as $module_name => $components) { + foreach ($components as $component => $state) { + if (in_array($state, $restore_states)) { + $items[$module_name][] = $component; + } + } + } + } + + foreach ($items as $module_name => $components) { + foreach ($components as $component) { + if (features_hook($component, $restore_hook)) { + // Set a semaphore to prevent other instances of the same script from running concurrently. + watchdog('features', '@actioning @module_name / @component.', array('@action' => $log_action, '@component' => $component, '@module_name' => $module_name)); + features_semaphore('set', $component); + features_invoke($component, $restore_hook, $module_name); + + // If the script completes, remove the semaphore and set the code signature. + features_semaphore('del', $component); + features_set_signature($module_name, $component); + watchdog('features', '@action completed for @module_name / @component.', array('@action' => $log_action, '@component' => $component, '@module_name' => $module_name)); + } + } + } +} + +/** + * Wrapper around _features_restore(). + */ +function features_revert($revert = array()) { + return _features_restore('revert', $revert); +} + +/** + * Wrapper around _features_restore(). + */ +function features_rebuild($rebuild = array()) { + return _features_restore('rebuild', $rebuild); +} + +/** + * Utility functions ================================================== + */ + +/** + * Log a message, environment agnostic. + * + * @param $message + * The message to log. + * @param $severity + * The severity of the message: status, warning or error. + */ +function features_log($message, $severity = 'status') { + if (function_exists('drush_verify_cli')) { + $message = strip_tags($message); + if ($severity == 'status') { + $severity = 'ok'; + } + elseif ($severity == 'error') { + drush_set_error($message); + return; + } + drush_log($message, $severity); + return; + } + drupal_set_message($message, $severity, FALSE); +} + +/** + * Implements hook_hook_info(). + */ +function features_hook_info() { + $hooks['features_api'] = array( + 'group' => 'features', + ); + return $hooks; +} diff --git a/sites/all/modules/features/includes/features.block.inc b/sites/all/modules/features/includes/features.block.inc new file mode 100644 index 0000000000000000000000000000000000000000..9a168b90942f6b7c8b9f13120d51e1d9878cc059 --- /dev/null +++ b/sites/all/modules/features/includes/features.block.inc @@ -0,0 +1,40 @@ +<?php + +/** + * Implementation of hook_features_api(). + */ +function block_features_api() { + return array(); +} + +/** + * Implementation of 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; +} diff --git a/sites/all/modules/features/includes/features.context.inc b/sites/all/modules/features/includes/features.context.inc new file mode 100644 index 0000000000000000000000000000000000000000..120bd49f3aab54b8fceaa498cd9d9bf6f2f23b10 --- /dev/null +++ b/sites/all/modules/features/includes/features.context.inc @@ -0,0 +1,54 @@ +<?php + +/** + * Implementation of 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; +} + +/** + * Implementation of hook_features_revert(). + * + * @param $module + * name of module to revert content for + */ +function context_features_revert($module = NULL) { + $return = ctools_component_features_revert('context', $module); + context_invalidate_cache(); + return $return; +} diff --git a/sites/all/modules/features/includes/features.ctools.inc b/sites/all/modules/features/includes/features.ctools.inc new file mode 100644 index 0000000000000000000000000000000000000000..80e5e9c78ad6a41d3bd9dd8ededb39e2012418a1 --- /dev/null +++ b/sites/all/modules/features/includes/features.ctools.inc @@ -0,0 +1,320 @@ +<?php + +/** + * This is a wild hack, but effective. + * Dynamically declare functions under a ctools component's namespace if they are not already declared. + */ +foreach (_ctools_features_get_info() as $component => $info) { + $code = ''; + if (!function_exists("{$info['module']}_features_api")) { + $code .= 'function '. $info['module'] .'_features_api() { return ctools_component_features_api("'. $info['module'] .'"); }'; + } + 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) { return ctools_component_features_export_render("'. $component .'", $module, $data); }'; + } + if (!function_exists("{$component}_features_revert")) { + $code .= 'function '. $component .'_features_revert($module) { return ctools_component_features_revert("'. $component .'", $module); }'; + } + eval($code); +} + +/** + * Implementation of 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', + ), + ); +} + +/** + * Implementation of 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(); +} + +/** + * Implementation of hook_features_export_render(). + * Adds the ctools mothership hook, ctools_plugin_api(). + */ +function ctools_features_export_render($module, $data) { + $code = array(); + $code[] = ' list($module, $api) = func_get_args();'; + + $component_exports = array(); + foreach ($data as $component) { + $code = array(); + $code[] = ' list($module, $api) = func_get_args();'; + + if ($info = _ctools_features_get_info($component)) { + + $code[] = ' if ($module == "'. $info['module'] .'" && $api == "'. $info['api'] .'") {'; + $code[] = ' return array("version" => '. $info['current_version'] .');'; + $code[] = ' }'; + } + + $plugin_api_hook_name = ctools_plugin_api_get_hook($info['module'], $info['api']); + + if (key_exists($plugin_api_hook_name, $component_exports)) { + $component_exports[$plugin_api_hook_name] .= "\n" . implode("\n", $code); + } + else { + $component_exports[$plugin_api_hook_name] = implode("\n", $code); + } + } + + return $component_exports; + +} + +/** + * Master implementation of hook_features_api() for all ctools components. + * + * Note that this master hook does not use $component like the others, but uses the + * component module's namespace instead. + */ +function ctools_component_features_api($module_name) { + $api = array(); + foreach (_ctools_features_get_info() as $component => $info) { + if ($info['module'] === $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) { + 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 $object) { + _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'); + 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']}); + } +} + +/** + * Implementation of hook_features_export_render() for page_manager. + */ +function page_manager_pages_features_export_render($module, $data) { + // Ensure that handlers have their code included before exporting. + page_manager_get_tasks(); + return ctools_component_features_export_render('page_manager_pages', $module, $data); +} diff --git a/sites/all/modules/features/includes/features.features.inc b/sites/all/modules/features/includes/features.features.inc new file mode 100644 index 0000000000000000000000000000000000000000..dceee31856d60ab616668cdd717989940c228022 --- /dev/null +++ b/sites/all/modules/features/includes/features.features.inc @@ -0,0 +1,70 @@ +<?php + +/** + * Implementation of hook_features_api(). + */ +function features_features_api() { + return array( + 'dependencies' => array( + 'name' => 'Dependencies', + 'feature_source' => TRUE, + 'duplicates' => FEATURES_DUPLICATES_ALLOWED, + ), + ); +} + +/** + * Implementation of 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; +} + +/** + * Implementation of 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']); +} + +/** + * Implementation of hook_features_revert(). + */ +function dependencies_features_revert($module) { + dependencies_features_rebuild($module); +} + +/** + * Implementation of 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) { + if (!module_exists($dependency)) { + $install[] = $dependency; + } + } + if (!empty($install)) { + features_install_modules($install); + } + } +} diff --git a/sites/all/modules/features/includes/features.field.inc b/sites/all/modules/features/includes/features.field.inc new file mode 100644 index 0000000000000000000000000000000000000000..c11d0d0fcf90dfbc2d76eae64215d3d1af9cf550 --- /dev/null +++ b/sites/all/modules/features/includes/features.field.inc @@ -0,0 +1,180 @@ +<?php + +/** + * Implements hook_features_api(). + */ +function field_features_api() { + return array( + 'field' => array( + 'name' => t('Fields'), + 'default_hook' => 'field_default_fields', + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + 'feature_source' => TRUE, + ) + ); +} + + +/** + * Implements hook_features_export_options(). + */ +function field_features_export_options() { + $options = array(); + $instances = field_info_instances(); + foreach ($instances as $entity_type => $bundles) { + foreach ($bundles as $bundle => $fields) { + foreach ($fields as $field) { + $identifier = "{$entity_type}-{$bundle}-{$field['field_name']}"; + $options[$identifier] = $identifier; + } + } + } + return $options; +} + +/** + * Implements hook_features_export(). + */ +function field_features_export($data, &$export, $module_name = '') { + $pipe = array(); + $map = features_get_default_map('field'); + + // The field_default_fields() hook integration is provided by the + // features module so we need to add it as a dependency. + $export['dependencies']['features'] = 'features'; + + foreach ($data as $identifier) { + if ($field = features_field_load($identifier)) { + // If this field is already provided by another module, remove the field + // and add the other module as a dependency. + if (isset($map[$identifier]) && $map[$identifier] != $module_name) { + if (isset($export['features']['field'][$identifier])) { + unset($export['features']['field'][$identifier]); + } + $module = $map[$identifier]; + $export['dependencies'][$module] = $module; + } + // If the field has not yet been exported, add it + else { + $export['features']['field'][$identifier] = $identifier; + $export['dependencies'][$field['field_config']['module']] = $field['field_config']['module']; + $export['dependencies'][$field['field_config']['storage']['module']] = $field['field_config']['storage']['module']; + $export['dependencies'][$field['field_instance']['widget']['module']] = $field['field_instance']['widget']['module']; + foreach ($field['field_instance']['display'] as $key => $display) { + if (isset($display['module'])) { + $export['dependencies'][$display['module']] = $display['module']; + // @TODO: handle the pipe to image styles + } + } + } + } + } +} + +/** + * Implementation of 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']); + unset($field['field_config']['locked']); + unset($field['field_config']['storage']); + + _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) { + ksort($field); + foreach ($field as $k => $v) { + if (is_array($v)) { + _field_features_export_sort($field[$k]); + } + } +} + +/** + * 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(); + + foreach ($fields as $field) { + // Create or update field. + $field_config = $field['field_config']; + if ($existing_field = field_info_field($field_config['field_name'])) { + field_update_field($field_config); + } + else { + field_create_field($field_config); + } + + // Create or update field instance. + $field_instance = $field['field_instance']; + $existing_instance = field_info_instance($field_instance['entity_type'], $field_instance['field_name'], $field_instance['bundle']); + if ($existing_instance) { + field_update_instance($field_instance); + } + else { + field_create_instance($field_instance); + } + 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; +} diff --git a/sites/all/modules/features/includes/features.filter.inc b/sites/all/modules/features/includes/features.filter.inc new file mode 100644 index 0000000000000000000000000000000000000000..7fa654a232d33915273cbe56894cae2d5f323062 --- /dev/null +++ b/sites/all/modules/features/includes/features.filter.inc @@ -0,0 +1,120 @@ +<?php + +/** + * Implementation of 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 + ), + ); +} + +/** + * Implementation of hook_features_export_options(). + */ +function filter_features_export_options() { + $options = array(); + foreach (filter_formats() as $format => $info) { + $options[$format] = $info->name; + } + return $options; +} + +/** + * Implementation of 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; +} + +/** + * Implementation of 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); +} + +/** + * Implementation of hook_features_revert(). + */ +function filter_features_revert($module) { + return filter_features_rebuild($module); +} + +/** + * Implementation of hook_features_rebuild(). + */ +function filter_features_rebuild($module) { + if ($defaults = features_get_default('filter', $module)) { + foreach ($defaults as $format) { + $format = (object) $format; + filter_format_save($format); + } + } +} + +/** + * Load a filter format by its name. + */ +function features_filter_format_load($name) { + // Use machine name for retrieving the format if available. + $query = db_select('filter_format'); + $query->fields('filter_format'); + $query->condition('format', $name); + + // Retrieve filters for the format and attach. + if ($format = $query->execute()->fetchObject()) { + $format->filters = array(); + foreach (filter_list_format($format->format) as $filter) { + if (!empty($filter->status)) { + $format->filters[$filter->name]['weight'] = $filter->weight; + $format->filters[$filter->name]['status'] = $filter->status; + $format->filters[$filter->name]['settings'] = $filter->settings; + } + } + return $format; + } + return FALSE; +} diff --git a/sites/all/modules/features/includes/features.image.inc b/sites/all/modules/features/includes/features.image.inc new file mode 100644 index 0000000000000000000000000000000000000000..2d6bfcdfaea6496310c0a20a2fb3f17113b16942 --- /dev/null +++ b/sites/all/modules/features/includes/features.image.inc @@ -0,0 +1,98 @@ +<?php + +/** + * Implementation of hook_features_api(). + */ +function image_features_api() { + return array( + 'image' => array( + 'name' => t('Image styles'), + 'feature_source' => TRUE, + 'default_hook' => 'image_default_styles', + ) + ); +} + +/** + * Implementation of hook_features_export_options(). + */ +function image_features_export_options() { + $options = array(); + foreach (image_styles() as $name => $style) { + $options[$name] = $style['name']; + } + return $options; +} + +/** + * Implementation of 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; +} + +/** + * Implementation of 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); +} + +/** + * Implementation of hook_features_revert(). + */ +function image_features_revert($module) { + if ($default_styles = features_get_default('image')) { + foreach (array_keys($default_styles) as $default_style) { + if ($style = image_style_load($default_style)) { + image_style_delete($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); + } + } + } +} diff --git a/sites/all/modules/features/includes/features.menu.inc b/sites/all/modules/features/includes/features.menu.inc new file mode 100644 index 0000000000000000000000000000000000000000..5b8345379a51d760e7339ff7101176cffd35f61d --- /dev/null +++ b/sites/all/modules/features/includes/features.menu.inc @@ -0,0 +1,305 @@ +<?php + +/** + * Implementation of 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, + ), + ); +} + +/** + * Implementation of 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; +} + +/** + * Implementation of 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; +} + +/** + * Implementation of 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; +} + +/** + * Implementation of 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); +} + +/** + * Implementation of hook_features_export_revert(). + */ +function menu_custom_features_revert($module) { + menu_custom_features_rebuild($module); +} + +/** + * Implementation of hook_features_export_rebuild(). + */ +function menu_custom_features_rebuild($module) { + if ($defaults = features_get_default('menu_custom', $module)) { + foreach ($defaults as $menu) { + menu_save($menu); + } + } +} + +/** + * Implementation of hook_features_export_options(). + */ +function menu_links_features_export_options() { + $menu_links = menu_parent_options(menu_get_menus(), array('mlid' => 0)); + $options = array(); + foreach ($menu_links as $key => $name) { + list($menu_name, $mlid) = explode(':', $key, 2); + if ($mlid != 0) { + $link = menu_link_load($mlid); + $identifier = menu_links_features_identifier($link); + $options[$identifier] = "{$menu_name}: {$name}"; + } + } + return $options; +} + +/** + * Callback for generating the menu link exportable identifier. + */ +function menu_links_features_identifier($link) { + return isset($link['menu_name'], $link['link_path']) ? "{$link['menu_name']}:{$link['link_path']}" : FALSE; +} + +/** + * Implementation of hook_features_export(). + */ +function menu_links_features_export($data, &$export, $module_name = '') { + // Default hooks are provided by the feature module so we need to add + // it as a dependency. + $export['dependencies']['features'] = 'features'; + $export['dependencies']['menu'] = 'menu'; + + // Collect a link to module map + $pipe = array(); + $map = features_get_default_map('menu_links', 'menu_links_features_identifier'); + foreach ($data as $identifier) { + if ($link = features_menu_link_load($identifier)) { + // If this link is provided by a different module, add it as a dependency. + if (isset($map[$identifier]) && $map[$identifier] != $module_name) { + $export['dependencies'][$map[$identifier]] = $map[$identifier]; + } + else { + $export['features']['menu_links'][$identifier] = $identifier; + } + // For now, exclude a variety of common menus from automatic export. + // They may still be explicitly included in a Feature if the builder + // chooses to do so. + if (!in_array($link['menu_name'], array('features', 'primary-links', 'secondary-links', 'navigation', 'admin', 'devel'))) { + $pipe['menu_custom'][] = $link['menu_name']; + } + } + } + return $pipe; +} + +/** + * Implementation of hook_features_export_render() + */ +function menu_links_features_export_render($module, $data) { + $code = array(); + $code[] = ' $menu_links = array();'; + $code[] = ''; + + $translatables = array(); + foreach ($data as $identifier) { + if ($link = features_menu_link_load($identifier)) { + // Replace plid with a parent path. + if (!empty($link['plid']) && $parent = menu_link_load($link['plid'])) { + $link['parent_path'] = $parent['link_path']; + } + unset($link['plid']); + unset($link['mlid']); + + $code[] = " // Exported menu link: {$identifier}"; + $code[] = " \$menu_links['{$identifier}'] = ". features_var_export($link, ' ') .";"; + $translatables[] = $link['link_title']; + } + } + if (!empty($translatables)) { + $code[] = features_translatables_export($translatables, ' '); + } + + $code[] = ''; + $code[] = ' return $menu_links;'; + $code = implode("\n", $code); + return array('menu_default_menu_links' => $code); +} + +/** + * Implementation of hook_features_export_revert(). + */ +function menu_links_features_revert($module) { + menu_links_features_rebuild($module); +} + +/** + * Implementation of hook_features_export_rebuild(). + */ +function menu_links_features_rebuild($module) { + if ($menu_links = features_get_default('menu_links', $module)) { + menu_links_features_rebuild_ordered($menu_links); + } +} + +/** + * Generate a depth tree of all menu links. + */ +function menu_links_features_rebuild_ordered($menu_links, $reset = FALSE) { + static $ordered; + static $all_links; + if (!isset($ordered) || $reset) { + $ordered = array(); + $unordered = features_get_default('menu_links'); + + // Order all links by depth. + if ($unordered) { + do { + $current = count($unordered); + foreach ($unordered as $key => $link) { + $identifier = menu_links_features_identifier($link); + $parent = isset($link['parent_path']) ? "{$link['menu_name']}:{$link['parent_path']}" : ''; + if (empty($parent)) { + $ordered[$identifier] = 0; + $all_links[$identifier] = $link; + unset($unordered[$key]); + } + elseif (isset($ordered[$parent])) { + $ordered[$identifier] = $ordered[$parent] + 1; + $all_links[$identifier] = $link; + unset($unordered[$key]); + } + } + } while (count($unordered) < $current); + } + asort($ordered); + } + + // Ensure any default menu items that do not exist are created. + foreach (array_keys($ordered) as $identifier) { + $link = $all_links[$identifier]; + $existing = features_menu_link_load($identifier); + if (!$existing || in_array($link, $menu_links)) { + // Retrieve the mlid if this is an existing item. + if ($existing) { + $link['mlid'] = $existing['mlid']; + } + // Retrieve the plid for a parent link. + if (!empty($link['parent_path']) && $parent = features_menu_link_load("{$link['menu_name']}:{$link['parent_path']}")) { + $link['plid'] = $parent['mlid']; + } + else { + $link['plid'] = 0; + } + menu_link_save($link); + } + } +} + +/** + * Load a menu link by its menu_name:link_path identifier. + */ +function features_menu_link_load($identifier) { + list($menu_name, $link_path) = explode(':', $identifier, 2); + $link = db_select('menu_links') + ->fields('menu_links', array('menu_name', 'mlid', 'plid', 'link_path', 'router_path', 'link_title', 'options', 'module', 'hidden', 'external', 'has_children', 'expanded', 'weight')) + ->condition('menu_name', $menu_name) + ->condition('link_path', $link_path) + ->execute() + ->fetchAssoc(); + if ($link) { + $link['options'] = unserialize($link['options']); + return $link; + } + return FALSE; +} diff --git a/sites/all/modules/features/includes/features.node.inc b/sites/all/modules/features/includes/features.node.inc new file mode 100644 index 0000000000000000000000000000000000000000..8e54827c32b633299d4f52188c3567a07dc3fe5e --- /dev/null +++ b/sites/all/modules/features/includes/features.node.inc @@ -0,0 +1,115 @@ +<?php + +/** + * Implementation of hook_features_api(). + */ +function node_features_api() { + return array( + 'node' => array( + 'name' => t('Content types'), + 'feature_source' => TRUE, + 'default_hook' => 'node_info', + ), + ); +} + +/** + * Implementation of hook_features_export_options(). + */ +function node_features_export_options() { + return node_type_get_names(); +} + +/** + * Implementation of hook_features_export. + */ +function node_features_export($data, &$export, $module_name = '') { + $pipe = array(); + $map = features_get_default_map('node'); + + foreach ($data as $type) { + // Poll node module to determine who provides the node type. + if ($info = node_type_get_type($type)) { + // If this node type is provided by a different module, add it as a dependency + if (isset($map[$type]) && $map[$type] != $module_name) { + $export['dependencies'][$map[$type]] = $map[$type]; + } + // Otherwise export the node type. + elseif (in_array($info->base, array('node_content', 'features'))) { + $export['features']['node'][$type] = $type; + $export['dependencies']['node'] = 'node'; + $export['dependencies']['features'] = 'features'; + } + + $fields = field_info_instances('node', $type); + foreach ($fields as $name => $field) { + $pipe['field'][] = "node-{$field['bundle']}-{$field['field_name']}"; + } + } + } + + return $pipe; +} + +/** + * Implementation of 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); +} + +/** + * Implementation of 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(); + } +} diff --git a/sites/all/modules/features/includes/features.taxonomy.inc b/sites/all/modules/features/includes/features.taxonomy.inc new file mode 100644 index 0000000000000000000000000000000000000000..dbb71d3a04242334882050bf728a5b0b1c9570ae --- /dev/null +++ b/sites/all/modules/features/includes/features.taxonomy.inc @@ -0,0 +1,104 @@ +<?php + +/** + * Implementation of 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, + ), + ); +} + +/** + * Implementation of 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; +} + +/** + * Implementation of hook_features_export(). + * + * @todo Test adding existing dependencies. + */ +function taxonomy_features_export($data, &$export, $module_name = '') { + $pipe = array(); + + // taxonomy_default_vocabularies integration is provided by Features. + $export['dependencies']['features'] = 'features'; + $export['dependencies']['taxonomy'] = 'taxonomy'; + + // Add dependencies for each vocabulary. + $map = features_get_default_map('taxonomy'); + foreach ($data as $machine_name) { + if (isset($map[$machine_name]) && $map[$machine_name] != $module_name) { + $export['dependencies'][$map[$machine_name]] = $map[$machine_name]; + } + else { + $export['features']['taxonomy'][$machine_name] = $machine_name; + + $fields = field_info_instances('taxonomy_term', $machine_name); + foreach ($fields as $name => $field) { + $pipe['field'][] = "taxonomy_term-{$field['bundle']}-{$field['field_name']}"; + } + } + } + return $pipe; +} + +/** + * Implementation of 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); +} + +/** + * Implementation of hook_features_revert(). + */ +function taxonomy_features_revert($module) { + taxonomy_features_rebuild($module); +} + +/** + * Implementation of hook_features_rebuild(). + * + * Rebuilds Taxonomy vocabularies from code defaults. + */ +function taxonomy_features_rebuild($module) { + if ($vocabularies = features_get_default('taxonomy', $module)) { + $existing = taxonomy_get_vocabularies(); + foreach ($vocabularies as $vocabulary) { + $vocabulary = (object) $vocabulary; + foreach ($existing as $existing_vocab) { + if ($existing_vocab->machine_name === $vocabulary->machine_name) { + $vocabulary->vid = $existing_vocab->vid; + } + } + taxonomy_vocabulary_save($vocabulary); + } + } +} diff --git a/sites/all/modules/features/includes/features.user.inc b/sites/all/modules/features/includes/features.user.inc new file mode 100644 index 0000000000000000000000000000000000000000..4b0c7e9ad96c22a8ac01df1dfe1d3734ed2eaed1 --- /dev/null +++ b/sites/all/modules/features/includes/features.user.inc @@ -0,0 +1,262 @@ +<?php + +/** + * Implementation of 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, + ), + ); +} + +/** + * Implementation of hook_features_export(). + */ +function user_permission_features_export($data, &$export, $module_name = '') { + $export['dependencies']['features'] = 'features'; + + // Ensure the modules that provide the given permissions are included as dependencies. + $map = user_permission_get_modules(); + foreach ($data as $perm) { + if (isset($map[$perm])) { + $perm_module = $map[$perm]; + $export['dependencies'][$perm_module] = $perm_module; + $export['features']['user_permission'][$perm] = $perm; + } + } + + return array(); +} + +/** + * Implementation of hook_features_export_options(). + */ +function user_permission_features_export_options() { + $modules = array(); + $module_info = system_get_info('module'); + foreach (module_implements('permission') as $module) { + $modules[$module_info[$module]['name']] = $module; + } + ksort($modules); + + $options = array(); + foreach ($modules as $display_name => $module) { + if ($permissions = module_invoke($module, 'permission')) { + foreach ($permissions as $perm => $perm_item) { + $options[$perm] = strip_tags("{$display_name}: {$perm_item['title']}"); + } + } + } + return $options; +} + +/** + * Implementation of hook_features_export_render(). + */ +function user_permission_features_export_render($module, $data) { + $perm_modules = &drupal_static(__FUNCTION__ . '_perm_modules'); + if (!isset($perm_modules)) { + $perm_modules = user_permission_get_modules(); + } + + $code = array(); + $code[] = ' $permissions = array();'; + $code[] = ''; + + $permissions = _user_features_get_permissions(); + + foreach ($data as $perm_name) { + $permission = array(); + $permission['name'] = $perm_name; + if (isset($permissions[$perm_name])) { + sort($permissions[$perm_name]); + $permission['roles'] = $permissions[$perm_name]; + $permission['module'] = $perm_modules[$perm_name]; + } + else { + $permission['roles'] = array(); + } + $perm_identifier = features_var_export($perm_name); + $perm_export = features_var_export($permission, ' '); + $code[] = " // Exported permission: {$perm_name}"; + $code[] = " \$permissions[{$perm_identifier}] = {$perm_export};"; + $code[] = ""; + } + + $code[] = ' return $permissions;'; + $code = implode("\n", $code); + return array('user_default_permissions' => $code); +} + +/** + * Implementation of hook_features_revert(). + */ +function user_permission_features_revert($module) { + user_permission_features_rebuild($module); +} + +/** + * Implementation of 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)) { + $roles = _user_features_get_roles(); + $permissions_by_role = _user_features_get_permissions(FALSE); + foreach ($defaults as $permission) { + $perm = $permission['name']; + foreach ($roles as $role) { + if (in_array($role, $permission['roles'])) { + $permissions_by_role[$role][$perm] = TRUE; + } + else { + $permissions_by_role[$role][$perm] = FALSE; + } + } + } + // Write the updated permissions. + foreach ($roles as $rid => $role) { + if (isset($permissions_by_role[$role])) { + user_role_change_permissions($rid, $permissions_by_role[$role]); + } + } + } +} + +/** + * Implementation of 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(); +} + +/** + * Implementation of hook_features_export_options(). + */ +function user_role_features_export_options() { + return drupal_map_assoc(_user_features_get_roles(FALSE)); +} + +/** + * Implementation of 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); +} + +/** + * Implementation of hook_features_revert(). + */ +function user_role_features_revert($module) { + user_role_features_rebuild($module); +} + +/** + * Implementation of hook_features_rebuild(). + */ +function user_role_features_rebuild($module) { + if ($defaults = features_get_default('user_role', $module)) { + foreach ($defaults as $role) { + $role = (object) $role; + if ($existing = user_role_load_by_name($role->name)) { + $role->rid = $existing->rid; + } + user_role_save($role); + } + } +} + +/** + * Generate $rid => $role with role names untranslated. + */ +function _user_features_get_roles($builtin = TRUE) { + $roles = array(); + foreach (user_roles() as $rid => $name) { + switch ($rid) { + case DRUPAL_ANONYMOUS_RID: + if ($builtin) { + $roles[$rid] = 'anonymous user'; + } + break; + case DRUPAL_AUTHENTICATED_RID: + if ($builtin) { + $roles[$rid] = 'authenticated user'; + } + break; + default: + $roles[$rid] = $name; + break; + } + } + return $roles; +} + +/** + * Represent the current state of permissions as a perm to role name array map. + */ +function _user_features_get_permissions($by_role = TRUE) { + $map = user_permission_get_modules(); + $roles = _user_features_get_roles(); + $permissions = array(); + foreach (user_role_permissions($roles) as $rid => $role_permissions) { + if ($by_role) { + foreach (array_keys(array_filter($role_permissions)) as $permission) { + if (isset($map[$permission])) { + $permissions[$permission][] = $roles[$rid]; + } + } + } + else { + $permissions[$roles[$rid]] = array(); + foreach ($role_permissions as $permission => $status) { + if (isset($map[$permission])) { + $permissions[$roles[$rid]][$permission] = $status; + } + } + } + } + return $permissions; +} diff --git a/sites/all/modules/features/tests/features.test b/sites/all/modules/features/tests/features.test new file mode 100644 index 0000000000000000000000000000000000000000..3e90296fa8f858a77aa91d73d8073f9d310f1f17 --- /dev/null +++ b/sites/all/modules/features/tests/features.test @@ -0,0 +1,165 @@ +<?php + +/** + * User permission component tests for Features + */ +class FeaturesUserTestCase extends DrupalWebTestCase { + protected $profile = 'testing'; + + /** + * Test info. + */ + public static function getInfo() { + return array( + 'name' => t('Component tests'), + 'description' => t('Run tests for components of Features.') , + 'group' => t('Features'), + ); + } + + /** + * Set up test. + */ + public function setUp() { + parent::setUp(array( + 'field', + 'filter', + 'image', + 'taxonomy', + 'views', + 'features', + 'features_test' + )); + + // Run a features rebuild to ensure our feature is fully installed. + features_rebuild(); + + $admin_user = $this->drupalCreateUser(array('administer features')); + $this->drupalLogin($admin_user); + } + + /** + * Run test. + */ + public function test() { + module_load_include('inc', 'features', 'features.export'); + + $components = array_filter(array( + 'field' => 'field', + 'filter' => 'filter', + 'image' => 'image', + 'node' => 'node', + 'user_permission' => 'user', + 'views' => '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) { + $states = features_get_component_states(array('features_test'), FALSE, TRUE); + $this->assertTrue($states['features_test'][$component] === FEATURES_DEFAULT, t('@component reverted.', array('@component' => $component))); + } + } + + protected function _test_field($op = 'load') { + switch ($op) { + case 'load': + return field_info_instance('node', 'field_features_test', 'features_test'); + case 'override': + $field_instance = field_info_instance('node', 'field_features_test', 'features_test'); + $field_instance['label'] = 'Foo bar'; + field_update_instance($field_instance); + break; + } + } + + protected function _test_filter($op = 'load') { + // So... relying on our own API functions to test is pretty lame. + // But these modules don't have APIs either. So might as well use + // the ones we've written for them... + features_include(); + switch ($op) { + case 'load': + return features_filter_format_load('features_test'); + case 'override': + $format = features_filter_format_load('features_test'); + unset($format->filters['filter_url']); + filter_format_save($format); + break; + } + } + + protected function _test_image($op = 'load') { + switch ($op) { + case 'load': + return image_style_load('features_test'); + case 'override': + $style = image_style_load('features_test'); + $style = image_style_save($style); + foreach ($style['effects'] as $effect) { + $effect['data']['width'] = '120'; + image_effect_save($effect); + } + break; + } + } + + protected function _test_node($op = 'load') { + switch ($op) { + case 'load': + return node_type_get_type('features_test'); + case 'override': + $type = node_type_get_type('features_test'); + $type->description = 'Foo bar baz.'; + $type->modified = TRUE; + node_type_save($type); + break; + } + } + + protected function _test_views($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(); + 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; + } + } +} diff --git a/sites/all/modules/features/tests/features_test.features.field.inc b/sites/all/modules/features/tests/features_test.features.field.inc new file mode 100644 index 0000000000000000000000000000000000000000..c82fcd118b535701332dfc50e0167581cef30442 --- /dev/null +++ b/sites/all/modules/features/tests/features_test.features.field.inc @@ -0,0 +1,95 @@ +<?php + +/** + * Implementation of hook_field_default_fields(). + */ +function features_test_field_default_fields() { + $fields = array(); + + // Exported field: 'node-features_test-field_features_test' + $fields['node-features_test-field_features_test'] = array( + 'field_config' => array( + 'active' => '1', + 'cardinality' => '1', + 'deleted' => '0', + 'entity_types' => array(), + 'field_name' => 'field_features_test', + 'foreign keys' => array( + 'format' => array( + 'columns' => array( + 'format' => 'format', + ), + 'table' => 'filter_format', + ), + ), + 'indexes' => array( + 'format' => array( + '0' => 'format', + ), + ), + 'module' => 'text', + 'settings' => array( + 'max_length' => '255', + ), + 'translatable' => '1', + 'type' => 'text', + ), + 'field_instance' => array( + 'bundle' => 'features_test', + 'default_value' => NULL, + 'deleted' => '0', + 'description' => '', + 'display' => array( + 'default' => array( + 'label' => 'above', + 'module' => 'text', + 'settings' => array(), + 'type' => 'text_default', + 'weight' => 0, + ), + 'full' => array( + 'label' => 'above', + 'settings' => array(), + 'type' => 'hidden', + 'weight' => 0, + ), + 'print' => array( + 'label' => 'above', + 'settings' => array(), + 'type' => 'hidden', + 'weight' => 0, + ), + 'rss' => array( + 'label' => 'above', + 'settings' => array(), + 'type' => 'hidden', + 'weight' => 0, + ), + 'teaser' => array( + 'label' => 'above', + 'settings' => array(), + 'type' => 'hidden', + 'weight' => 0, + ), + ), + 'entity_type' => 'node', + 'field_name' => 'field_features_test', + 'label' => 'Test', + 'required' => 0, + 'settings' => array( + 'text_processing' => '0', + ), + 'widget' => array( + 'active' => 1, + 'module' => 'text', + 'settings' => array( + 'size' => '60', + ), + 'type' => 'text_textfield', + 'weight' => '-4', + ), + ), + ); + + return $fields; +} diff --git a/sites/all/modules/features/tests/features_test.features.filter.inc b/sites/all/modules/features/tests/features_test.features.filter.inc new file mode 100644 index 0000000000000000000000000000000000000000..2fe81962bac33c3c521c84040b4405e41bd75a5a --- /dev/null +++ b/sites/all/modules/features/tests/features_test.features.filter.inc @@ -0,0 +1,52 @@ +<?php + +/** + * Implementation of 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; +} diff --git a/sites/all/modules/features/tests/features_test.features.inc b/sites/all/modules/features/tests/features_test.features.inc new file mode 100644 index 0000000000000000000000000000000000000000..5a77ea0a055448bc8ee04034eba48bb390322265 --- /dev/null +++ b/sites/all/modules/features/tests/features_test.features.inc @@ -0,0 +1,68 @@ +<?php + +/** + * Implementation of 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); + } +} + +/** + * Implementation of 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', + 'form callback' => 'image_scale_form', + 'summary theme' => 'image_scale_summary', + 'module' => 'image', + 'name' => 'image_scale', + 'data' => array( + 'width' => '100', + 'height' => '100', + 'upscale' => 0, + ), + 'weight' => '1', + ), + ), + ); + + return $styles; +} + +/** + * Implementation of 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; +} + +/** + * Implementation of hook_views_api(). + */ +function features_test_views_api() { + return array( + 'api' => '3.0-alpha1', + ); +} diff --git a/sites/all/modules/features/tests/features_test.features.user_permission.inc b/sites/all/modules/features/tests/features_test.features.user_permission.inc new file mode 100644 index 0000000000000000000000000000000000000000..185509ad070a9592c251cb1ec5f4123d5d6192bd --- /dev/null +++ b/sites/all/modules/features/tests/features_test.features.user_permission.inc @@ -0,0 +1,20 @@ +<?php + +/** + * Implementation of hook_user_default_permissions(). + */ +function features_test_user_default_permissions() { + $permissions = array(); + + // Exported permission: create features_test content + $permissions['create features_test content'] = array( + 'name' => 'create features_test content', + 'roles' => array( + '0' => 'anonymous user', + '1' => 'authenticated user', + ), + 'module' => 'node', + ); + + return $permissions; +} diff --git a/sites/all/modules/features/tests/features_test.info b/sites/all/modules/features/tests/features_test.info new file mode 100644 index 0000000000000000000000000000000000000000..0d6b941089a83111245e9c4459912de60f50bd68 --- /dev/null +++ b/sites/all/modules/features/tests/features_test.info @@ -0,0 +1,25 @@ +core = "7.x" +dependencies[] = "features" +dependencies[] = "image" +dependencies[] = "strongarm" +dependencies[] = "views" +description = "Test module for Features testing." +features[ctools][] = "strongarm:strongarm:1" +features[field][] = "node-features_test-field_features_test" +features[filter][] = "features_test" +features[image][] = "features_test" +features[node][] = "features_test" +features[user_permission][] = "create features_test content" +features[views][] = "features_test" +features[views_api][] = "api:3.0-alpha1" +hidden = TRUE +name = "Features Tests" +package = "Testing" +php = "5.2.0" + +; Information added by drupal.org packaging script on 2011-04-06 +version = "7.x-1.0-beta2" +core = "7.x" +project = "features" +datestamp = "1302049346" + diff --git a/sites/all/modules/features/tests/features_test.module b/sites/all/modules/features/tests/features_test.module new file mode 100644 index 0000000000000000000000000000000000000000..762d6e5d6b3c194454756dbd4802015ee8ac1504 --- /dev/null +++ b/sites/all/modules/features/tests/features_test.module @@ -0,0 +1,3 @@ +<?php + +include_once('features_test.features.inc'); diff --git a/sites/all/modules/features/tests/features_test.views_default.inc b/sites/all/modules/features/tests/features_test.views_default.inc new file mode 100644 index 0000000000000000000000000000000000000000..abc88d77916bb3d1c13e93ac0e9479a93592842c --- /dev/null +++ b/sites/all/modules/features/tests/features_test.views_default.inc @@ -0,0 +1,34 @@ +<?php + +/** + * Implementation of hook_views_default_views(). + */ +function features_test_views_default_views() { + $views = array(); + + // Exported view: features_test + $view = new view; + $view->name = 'features_test'; + $view->description = 'Test view provided by Features testing module.'; + $view->tag = 'testing'; + $view->view_php = ''; + $view->base_table = 'node'; + $view->is_cacheable = FALSE; + $view->api_version = '3.0-alpha1'; + $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + +/* Display: Defaults */ + $handler = $view->new_display('default', 'Defaults', 'default'); + $handler->display->display_options['title'] = 'Test'; + $handler->display->display_options['access']['type'] = 'none'; + $handler->display->display_options['cache']['type'] = 'none'; + $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['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'; + + $views[$view->name] = $view; + + return $views; +} diff --git a/sites/all/modules/features/theme/features-admin-components.tpl.php b/sites/all/modules/features/theme/features-admin-components.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..ecb69497cfcd0d13d733e5ed070e3a79d5e985d6 --- /dev/null +++ b/sites/all/modules/features/theme/features-admin-components.tpl.php @@ -0,0 +1,23 @@ +<?php +?> +<div class='clear-block 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='clear-block features-key'><?php print theme('links', $key) ?></div> + <?php endif; ?> + <?php if (!empty($buttons)): ?> + <div class='buttons clear-block'><?php print $buttons ?></div> + <?php endif; ?> + </div> + </div> + <?php print drupal_render_children($form) ?> +</div> diff --git a/sites/all/modules/features/theme/theme.inc b/sites/all/modules/features/theme/theme.inc new file mode 100644 index 0000000000000000000000000000000000000000..1971090cd411713c3260d236d2496802b5a891d5 --- /dev/null +++ b/sites/all/modules/features/theme/theme.inc @@ -0,0 +1,326 @@ +<?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(); + $conflicts = features_get_conflicts(); + if (!module_exists($form['module']['#value']) && isset($form['module']['#value']) && !empty($conflicts[$form['module']['#value']])) { + $module_conflicts = $conflicts[$form['module']['#value']]; + $conflicts = array(); + foreach ($module_conflicts as $conflict) { + $conflicts = array_merge_recursive($conflict, $conflicts); + } + } + else { + $conflicts = array(); + } + // Display key for conflicting elements. + if (!empty($conflicts)) { + $vars['key'][] = array( + 'title' => theme('features_storage_link', FEATURES_CONFLICT, t('Conflicts with another feature')), + 'html' => TRUE, + ); + } + + if (!empty($form['#info']['features'])) { + foreach ($form['#info']['features'] as $component => $items) { + if (!empty($items)) { + $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)), + '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 = !empty($vars['module']) ? $vars['module'] : t('Enabled'); + return "<span class='admin-enabled'>{$text}</span>"; + case FEATURES_MODULE_DISABLED: + $text = !empty($vars['module']) ? $vars['module'] : t('Disabled'); + return "<span class='admin-disabled'>{$text}</span>"; + case FEATURES_MODULE_MISSING: + $text = !empty($vars['module']) ? $vars['module'] : t('Missing'); + return "<span class='admin-missing'>{$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', + ); + $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'), + ); + $text = isset($text) ? $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 clear-block'>{$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='clear-block 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']['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>"; +} diff --git a/sites/all/modules/unl/features/unl_content_types/unl_content_types.features.field.inc b/sites/all/modules/unl/features/unl_content_types/unl_content_types.features.field.inc new file mode 100644 index 0000000000000000000000000000000000000000..409a73715efe339c6761c65119e76fb5892c28d9 --- /dev/null +++ b/sites/all/modules/unl/features/unl_content_types/unl_content_types.features.field.inc @@ -0,0 +1,385 @@ +<?php +/** + * @file + * unl_content_types.features.field.inc + */ + +/** + * Implementation of hook_field_default_fields(). + */ +function unl_content_types_field_default_fields() { + $fields = array(); + + // Exported field: 'node-unl_carousel-body' + $fields['node-unl_carousel-body'] = array( + 'field_config' => array( + 'active' => '1', + 'cardinality' => '1', + 'deleted' => '0', + 'entity_types' => array( + 0 => 'node', + ), + 'field_name' => 'body', + 'foreign keys' => array( + 'format' => array( + 'columns' => array( + 'format' => 'format', + ), + 'table' => 'filter_format', + ), + ), + 'indexes' => array( + 'format' => array( + 0 => 'format', + ), + ), + 'module' => 'text', + 'settings' => array(), + 'translatable' => '1', + 'type' => 'text_with_summary', + ), + 'field_instance' => array( + 'bundle' => 'unl_carousel', + 'default_value' => NULL, + 'deleted' => '0', + 'description' => '', + 'display' => array( + 'default' => array( + 'label' => 'hidden', + 'module' => 'text', + 'settings' => array(), + 'type' => 'text_default', + 'weight' => '0', + ), + 'teaser' => array( + 'label' => 'hidden', + 'module' => 'text', + 'settings' => array( + 'trim_length' => 600, + ), + 'type' => 'text_summary_or_trimmed', + 'weight' => 0, + ), + ), + 'entity_type' => 'node', + 'field_name' => 'body', + 'label' => 'Body', + 'required' => FALSE, + 'settings' => array( + 'display_summary' => TRUE, + 'text_processing' => 1, + 'user_register_form' => FALSE, + ), + 'widget' => array( + 'module' => 'text', + 'settings' => array( + 'rows' => 20, + 'summary_rows' => 5, + ), + 'type' => 'text_textarea_with_summary', + 'weight' => '-4', + ), + 'widget_type' => 'text_textarea_with_summary', + ), + ); + + // Exported field: 'node-unl_carousel-field_carouselcaptions' + $fields['node-unl_carousel-field_carouselcaptions'] = array( + 'field_config' => array( + 'active' => '1', + 'cardinality' => '-1', + 'deleted' => '0', + 'entity_types' => array(), + 'field_name' => 'field_carouselcaptions', + 'foreign keys' => array( + 'format' => array( + 'columns' => array( + 'format' => 'format', + ), + 'table' => 'filter_format', + ), + ), + 'indexes' => array( + 'format' => array( + 0 => 'format', + ), + ), + 'module' => 'text', + 'settings' => array( + 'max_length' => '512', + ), + 'translatable' => '1', + 'type' => 'text', + ), + 'field_instance' => array( + 'bundle' => 'unl_carousel', + 'default_value' => NULL, + 'deleted' => '0', + 'description' => '', + 'display' => array( + 'default' => array( + 'label' => 'hidden', + 'module' => 'text', + 'settings' => array(), + 'type' => 'text_default', + 'weight' => '2', + ), + 'teaser' => array( + 'label' => 'above', + 'settings' => array(), + 'type' => 'hidden', + 'weight' => 0, + ), + ), + 'entity_type' => 'node', + 'field_name' => 'field_carouselcaptions', + 'label' => 'Image Captions', + 'required' => 0, + 'settings' => array( + 'text_processing' => '0', + 'user_register_form' => FALSE, + ), + 'widget' => array( + 'active' => 1, + 'module' => 'text', + 'settings' => array( + 'size' => '128', + ), + 'type' => 'text_textfield', + 'weight' => '-2', + ), + ), + ); + + // Exported field: 'node-unl_carousel-field_carouselimages' + $fields['node-unl_carousel-field_carouselimages'] = array( + 'field_config' => array( + 'active' => '1', + 'cardinality' => '-1', + 'deleted' => '0', + 'entity_types' => array(), + 'field_name' => 'field_carouselimages', + 'foreign keys' => array( + 'fid' => array( + 'columns' => array( + 'fid' => 'fid', + ), + 'table' => 'file_managed', + ), + ), + 'indexes' => array( + 'fid' => array( + 0 => 'fid', + ), + ), + 'module' => 'image', + 'settings' => array( + 'default_image' => 0, + 'uri_scheme' => 'public', + ), + 'translatable' => '1', + 'type' => 'image', + ), + 'field_instance' => array( + 'bundle' => 'unl_carousel', + 'deleted' => '0', + 'description' => 'Add captions in the same order as the photos below. \'Title\' is used for the photo credit.', + 'display' => array( + 'default' => array( + 'label' => 'hidden', + 'module' => 'image', + 'settings' => array( + 'image_link' => '', + 'image_style' => '', + ), + 'type' => 'image', + 'weight' => '1', + ), + 'teaser' => array( + 'label' => 'above', + 'settings' => array(), + 'type' => 'hidden', + 'weight' => 0, + ), + ), + 'entity_type' => 'node', + 'field_name' => 'field_carouselimages', + 'label' => 'Images', + 'required' => 1, + 'settings' => array( + 'alt_field' => 1, + 'file_directory' => 'images/carousel', + 'file_extensions' => 'png gif jpg jpeg', + 'max_filesize' => '1 MB', + 'max_resolution' => '960x960', + 'min_resolution' => '', + 'title_field' => 1, + 'user_register_form' => FALSE, + ), + 'widget' => array( + 'active' => 1, + 'module' => 'image', + 'settings' => array( + 'preview_image_style' => 'thumbnail', + 'progress_indicator' => 'throbber', + ), + 'type' => 'image_image', + 'weight' => '-3', + ), + ), + ); + + // Exported field: 'node-unl_people_directory-body' + $fields['node-unl_people_directory-body'] = array( + 'field_config' => array( + 'active' => '1', + 'cardinality' => '1', + 'deleted' => '0', + 'entity_types' => array( + 0 => 'node', + ), + 'field_name' => 'body', + 'foreign keys' => array( + 'format' => array( + 'columns' => array( + 'format' => 'format', + ), + 'table' => 'filter_format', + ), + ), + 'indexes' => array( + 'format' => array( + 0 => 'format', + ), + ), + 'module' => 'text', + 'settings' => array(), + 'translatable' => '1', + 'type' => 'text_with_summary', + ), + 'field_instance' => array( + 'bundle' => 'unl_people_directory', + 'default_value' => NULL, + 'deleted' => '0', + 'description' => 'Optional explanatory text that will appear at the top of the page above the listings. ', + 'display' => array( + 'default' => array( + 'label' => 'hidden', + 'module' => 'text', + 'settings' => array(), + 'type' => 'text_default', + 'weight' => '0', + ), + 'teaser' => array( + 'label' => 'hidden', + 'module' => 'text', + 'settings' => array( + 'trim_length' => 600, + ), + 'type' => 'text_summary_or_trimmed', + 'weight' => 0, + ), + ), + 'entity_type' => 'node', + 'field_name' => 'body', + 'label' => 'Body', + 'required' => 0, + 'settings' => array( + 'display_summary' => 0, + 'text_processing' => '0', + 'user_register_form' => FALSE, + ), + 'widget' => array( + 'active' => 1, + 'module' => 'text', + 'settings' => array( + 'rows' => '4', + 'summary_rows' => 5, + ), + 'type' => 'text_textarea_with_summary', + 'weight' => '-4', + ), + 'widget_type' => 'text_textarea_with_summary', + ), + ); + + // Exported field: 'node-unl_people_directory-field_hrorgunit' + $fields['node-unl_people_directory-field_hrorgunit'] = array( + 'field_config' => array( + 'active' => '1', + 'cardinality' => '1', + 'deleted' => '0', + 'entity_types' => array(), + 'field_name' => 'field_hrorgunit', + 'foreign keys' => array( + 'format' => array( + 'columns' => array( + 'format' => 'format', + ), + 'table' => 'filter_format', + ), + ), + 'indexes' => array( + 'format' => array( + 0 => 'format', + ), + ), + 'module' => 'text', + 'settings' => array( + 'max_length' => '255', + ), + 'translatable' => '1', + 'type' => 'text', + ), + 'field_instance' => array( + 'bundle' => 'unl_people_directory', + 'default_value' => NULL, + 'deleted' => '0', + 'description' => 'Enter the number that is shown, without the parenthesis, when the name of the department is hovered over on a directory.unl.edu department listing. For example, when Human Resources is hovered over on directory.unl.edu/departments/19 the HR Org Unit Number of 50000821 is visible.', + 'display' => array( + 'default' => array( + 'label' => 'hidden', + 'module' => 'text', + 'settings' => array(), + 'type' => 'text_default', + 'weight' => '1', + ), + 'teaser' => array( + 'label' => 'above', + 'settings' => array(), + 'type' => 'hidden', + 'weight' => 0, + ), + ), + 'entity_type' => 'node', + 'field_name' => 'field_hrorgunit', + 'label' => 'UNL HR Org Unit Number', + 'required' => 1, + 'settings' => array( + 'text_processing' => '0', + 'user_register_form' => FALSE, + ), + 'widget' => array( + 'active' => 1, + 'module' => 'text', + 'settings' => array( + 'size' => '10', + ), + 'type' => 'text_textfield', + 'weight' => '-3', + ), + ), + ); + + // Translatables + // Included for use with string extractors like potx. + t('Add captions in the same order as the photos below. \'Title\' is used for the photo credit.'); + t('Body'); + t('Enter the number that is shown, without the parenthesis, when the name of the department is hovered over on a directory.unl.edu department listing. For example, when Human Resources is hovered over on directory.unl.edu/departments/19 the HR Org Unit Number of 50000821 is visible.'); + t('Image Captions'); + t('Images'); + t('Optional explanatory text that will appear at the top of the page above the listings. '); + t('UNL HR Org Unit Number'); + + return $fields; +} diff --git a/sites/all/modules/unl/features/unl_content_types/unl_content_types.features.inc b/sites/all/modules/unl/features/unl_content_types/unl_content_types.features.inc new file mode 100644 index 0000000000000000000000000000000000000000..4f3eb56c0384befeae43d228e2081f1dbed7a988 --- /dev/null +++ b/sites/all/modules/unl/features/unl_content_types/unl_content_types.features.inc @@ -0,0 +1,30 @@ +<?php +/** + * @file + * unl_content_types.features.inc + */ + +/** + * Implementation of hook_node_info(). + */ +function unl_content_types_node_info() { + $items = array( + 'unl_carousel' => array( + 'name' => t('Carousel'), + 'base' => 'node_content', + 'description' => t('A slideshow of photos.'), + 'has_title' => '1', + 'title_label' => t('Title'), + 'help' => '', + ), + 'unl_people_directory' => array( + 'name' => t('People Directory'), + 'base' => 'node_content', + 'description' => t('A directory listing of staff and/or faculty members of the specified unit pulled from directory.unl.edu.'), + 'has_title' => '1', + 'title_label' => t('Title'), + 'help' => '', + ), + ); + return $items; +} diff --git a/sites/all/modules/unl/features/unl_content_types/unl_content_types.info b/sites/all/modules/unl/features/unl_content_types/unl_content_types.info new file mode 100644 index 0000000000000000000000000000000000000000..78caacbe8ac167d66f99483bb8c4ff09e75f5837 --- /dev/null +++ b/sites/all/modules/unl/features/unl_content_types/unl_content_types.info @@ -0,0 +1,15 @@ +core = "7.x" +dependencies[] = "features" +dependencies[] = "image" +description = "Content types shared across all UNL templated sites." +features[field][] = "node-unl_carousel-body" +features[field][] = "node-unl_carousel-field_carouselcaptions" +features[field][] = "node-unl_carousel-field_carouselimages" +features[field][] = "node-unl_people_directory-body" +features[field][] = "node-unl_people_directory-field_hrorgunit" +features[node][] = "unl_carousel" +features[node][] = "unl_people_directory" +name = "UNL Content Types" +package = "Features" +project = "unl_content_types" +version = "7.x-1.0" diff --git a/sites/all/modules/unl/features/unl_content_types/unl_content_types.module b/sites/all/modules/unl/features/unl_content_types/unl_content_types.module new file mode 100644 index 0000000000000000000000000000000000000000..e4815c6f55c5fbb74a2d8e01aebd82e18082240b --- /dev/null +++ b/sites/all/modules/unl/features/unl_content_types/unl_content_types.module @@ -0,0 +1,7 @@ +<?php +/** + * @file + * Code for the UNL Content Types feature. + */ + +include_once('unl_content_types.features.inc'); diff --git a/sites/all/modules/unl/unl.module b/sites/all/modules/unl/unl.module index 6343d9875b9d877f4a3020c95c91955bf34007f7..8386c0f48b4e1511298aabd3f5e6a8488c1c0e99 100644 --- a/sites/all/modules/unl/unl.module +++ b/sites/all/modules/unl/unl.module @@ -1,4 +1,36 @@ <?php +/** + * Implementation of hook_field_attach_view_alter(). + */ +function unl_field_attach_view_alter(&$output, $context) { + foreach (element_children($output) as $field_name) { + $element = &$output[$field_name]; + switch ($element['#field_name']) { + case 'field_hrorgunit': + $result = file_get_contents('http://directory.unl.edu/departments/?view=deptlistings&org_unit='.$element['#items'][0]['value'].'&format=partial'); + if (!empty($result) && trim($result) != '<div id="all_employees"></div>') { + drupal_add_css('http://directory.unl.edu/css/peoplefinder_default.css', 'external'); + drupal_add_js('http://directory.unl.edu/scripts/peoplefinder.js', 'external'); + drupal_add_js('var PF_URL = "http://directory.unl.edu/", ANNOTATE_URL = "http://annotate.unl.edu/";', 'inline'); + // Need to check to see if directory.unl.edu only returned a partial result. If so, result will have a message in between the closing ul and div tags like so: </ul><p>Try refining your search.</p></div></div> + if (!preg_match('/<\/ul>\s*\n*\s*<\/div><\/div>/', $result)) { + // Extra message at the bottom indicating not all results returned - hide it + drupal_add_css('#all_employees > * > p {display:none;}', 'inline'); + // Alert the user to visit the directory site + drupal_add_js('jQuery(document).ready(function(){jQuery("#all_employees .result_head").css("font-size","13px").html("We\'re a big department! Partial listing shown below, please visit <a href=\"http://directory.unl.edu/departments/'.$element['#items'][0]['value'].'#all_employees\">our department lisiting on the UNL Directory</a> to see everyone.");});', 'inline'); + } + } + else { + $result = '<p>Please visit the <a href="http://directory.unl.edu/">UNL Directory</a> for listings.</p>'; + } + foreach ($element['#items'] as $delta => $item) { + $element[$delta]['#markup'] = $result; + } + break; + default: + } + } +} require_once dirname(__FILE__) . '/includes/common.php'; @@ -477,6 +509,15 @@ function unl_form_alter(&$form, &$form_state, $form_id) { $admin_role_id = unl_shared_variable_get('user_admin_role', -1); if (!in_array($admin_role_id, array_keys($GLOBALS['user']->roles))) { switch ($form_id) { + // Don't allow editing of non-custom content types on admin/structure/types, i.e. don't allow subsites to edit the content types coming from modules or the UNL shared content types + case 'node_type_form' : + case 'field_ui_field_overview_form' : + case 'field_ui_display_overview_form' : + if (!$form['custom']['#value']) { + drupal_access_denied(); + exit; + } + break; // Add additional validation on admin/people/permissions/roles/edit/% case 'user_admin_role' : $form['#validate'][] = 'unl_user_admin_role_validate'; @@ -572,7 +613,6 @@ function unl_form_alter(&$form, &$form_state, $form_id) { $form['menu']['link']['link_title']['#required'] = TRUE; - $mlid = $form['menu']['link']['mlid']['#value']; if ($mlid) { $menu_link = menu_link_load($mlid); @@ -1163,11 +1203,11 @@ function _unl_limit_menu_depth($menu_links, $depth) { if ($depth == 0) { return array(); } - + foreach (element_children($menu_links) as $index) { $menu_links[$index]['#below'] = _unl_limit_menu_depth($menu_links[$index]['#below'], $depth - 1); } - + return $menu_links; } @@ -1180,7 +1220,7 @@ function _unl_limit_menu_depth($menu_links, $depth) { */ function unl_block_info() { $blocks = array(); - + $blocks['my_sites'] = array( 'info' => 'My Sites', 'cache' => DRUPAL_CACHE_PER_USER, diff --git a/sites/all/themes/unl_wdn/node--unl-carousel.tpl.php b/sites/all/themes/unl_wdn/node--unl-carousel.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..d5fc9257907093ec4ee09a94426cea6561c05c98 --- /dev/null +++ b/sites/all/themes/unl_wdn/node--unl-carousel.tpl.php @@ -0,0 +1,121 @@ +<?php + +/** + * @file + * unl_wdn theme implementation to display a carousel node. + * + * Available variables: + * - $title: the (sanitized) title of the node. + * - $content: An array of node items. Use render($content) to print them all, + * or print a subset such as render($content['field_example']). Use + * hide($content['field_example']) to temporarily suppress the printing of a + * given element. + * - $user_picture: The node author's picture from user-picture.tpl.php. + * - $date: Formatted creation date. Preprocess functions can reformat it by + * calling format_date() with the desired parameters on the $created variable. + * - $name: Themed username of node author output from theme_username(). + * - $node_url: Direct url of the current node. + * - $display_submitted: Whether submission information should be displayed. + * - $submitted: Submission information created from $name and $date during + * template_preprocess_node(). + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the + * following: + * - node: The current template type, i.e., "theming hook". + * - node-[type]: The current node type. For example, if the node is a + * "Blog entry" it would result in "node-blog". Note that the machine + * name will often be in a short form of the human readable label. + * - node-teaser: Nodes in teaser form. + * - node-preview: Nodes in preview mode. + * The following are controlled through the node publishing options. + * - node-promoted: Nodes promoted to the front page. + * - node-sticky: Nodes ordered above other non-sticky nodes in teaser + * listings. + * - node-unpublished: Unpublished nodes visible only to administrators. + * - $title_prefix (array): An array containing additional output populated by + * modules, intended to be displayed in front of the main title tag that + * appears in the template. + * - $title_suffix (array): An array containing additional output populated by + * modules, intended to be displayed after the main title tag that appears in + * the template. + * + * Other variables: + * - $node: Full node object. Contains data that may not be safe. + * - $type: Node type, i.e. story, page, blog, etc. + * - $comment_count: Number of comments attached to the node. + * - $uid: User ID of the node author. + * - $created: Time the node was published formatted in Unix timestamp. + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $zebra: Outputs either "even" or "odd". Useful for zebra striping in + * teaser listings. + * - $id: Position of the node. Increments each time it's output. + * + * Node status variables: + * - $view_mode: View mode, e.g. 'full', 'teaser'... + * - $teaser: Flag for the teaser state (shortcut for $view_mode == 'teaser'). + * - $page: Flag for the full page state. + * - $promote: Flag for front page promotion state. + * - $sticky: Flags for sticky post setting. + * - $status: Flag for published status. + * - $comment: State of comment settings for the node. + * - $readmore: Flags true if the teaser content of the node cannot hold the + * main body content. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * - $is_admin: Flags true when the current user is an administrator. + * + * Field variables: for each field instance attached to the node a corresponding + * variable is defined, e.g. $node->body becomes $body. When needing to access + * a field's raw values, developers/themers are strongly encouraged to use these + * variables. Otherwise they will have to explicitly specify the desired field + * language, e.g. $node->body['en'], thus overriding any language negotiation + * rule that was previously applied. + * + * @see template_preprocess() + * @see template_preprocess_node() + * @see template_process() + */ +?> +<div id="node-<?php print $node->nid; ?>" class="<?php print $classes; ?> clearfix"<?php print $attributes; ?>> + + <?php print $user_picture; ?> + + <?php print render($title_prefix); ?> + <?php if (!$page): ?> + <h2<?php print $title_attributes; ?>><a href="<?php print $node_url; ?>"><?php print $title; ?></a></h2> + <?php endif; ?> + <?php print render($title_suffix); ?> + + <?php if ($display_submitted): ?> + <div class="submitted"> + <?php print $submitted; ?> + </div> + <?php endif; ?> + + <div class="content"<?php print $content_attributes; ?>> + <?php print render($content['body']); ?> + <script type="text/javascript">WDN.initializePlugin('carousel');</script> + <div class="zenbox" style="display: inline-block;"><div id="wdn_Carousel"> + <ul> + <?php + foreach ($content['field_carouselimages']['#object']->field_carouselimages['und'] as $key => $image) { + $image_info = image_get_info($image['uri']); + print '<li> + <img alt="'.$image['alt'].'" title="'.$image['title'].'" src="'.file_create_url($image['uri']).'" width="'.$image_info['width'].'" height="'.$image_info['height'].'" /> + '.(isset($content['field_carouselcaptions']['#object']->field_carouselcaptions['und'][$key]['value']) ? + '<p>'.$content['field_carouselcaptions']['#object']->field_carouselcaptions['und'][$key]['value']. + ' <span style="font-size:0.6em;font-style:italic">'.$image['title'].'</span></p>' : "").' + </li>'; + } + ?> + </ul> + </div></div> + </div> + + <?php print render($content['links']); ?> + + <?php print render($content['comments']); ?> + +</div>