As in the subject, we need to execute some custom code only after that all features dependencies and hook are just executed, in our specific case we want to set a default (and save the previously one) input format, that is installed by "input_formats" module.

I've just tried to use hook_enable or hook_install on {myfeatures}.intall, but they are executed too early.

Any advice is really much appreciated.

Thanks a lot,
Paolo

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

havran’s picture

Hi, i find solution for same thing. I use Features and i want install some dummy content after feature is installed. I use node_export module for exporting content with images. All this working great but only after i enable Feature second time.

I try use function node_export_import($node_array) in my_module.install in hook_enable() but this is too early (content type is not created). For this i use hook_init in my module and here i check my special variable which is set after installing dummy content to TRUE. First time this create nodes with title only (without other fields filled). I try delete all this content, variable, disable all dependencies modules and feature and uninstall them. After that if i enable my feature all working as expected. Dummy content has all fields filled.

paolomainardi’s picture

Thanks for your response havran, probably the second time has worked because feature already created (in the first installation) the content type.

My question is, where is placed in the features functions workflow the hook_enable() call to feature itself ?

Thanks a lot.

havran’s picture

Sorry I have no answer and i want it too :).

paolomainardi’s picture

I'm just digging around features code, i think that could be possible add some hook() after all modules of features are installed, i will provide a patch.

Grayside’s picture

Part of the problem with creating content after Features are installed is that the feature importation process is re-run when using features-revert.

It might be a cleaner idea to direct the administrator to run a secondary installation process to set up the content, such as going to a special administration page or running a drush command.

paolomainardi’s picture

Interesting, the alternative would be to have some hook_featuresapi() with an $op parameter as: 'install' 'disable' 'revert'

What do you think ?

havran’s picture

I'm trying create dummy content after enable new content type in feature an i think i need some function like node_types_rebuild(); and then content_clear_type_cache(TRUE); and then i can insert new content - but this work only partialy. Content is created but CCK fields are empty.

havran’s picture

For dummy content i use this approach now.

I use module Node Export (http://drupal.org/project/node_export) for export and import content.

I create module which is last enabled module in my install profile.

This module set frontpage to 'finalize-configuration' page.

install file

function dummy_content_install() {
  variable_set('site_frontpage', 'finalize-configuration');
}

function dummy_content_uninstall() {
  $result = db_query('SELECT DISTINCT(type) FROM {node_type} ORDER BY type');

  $content_types = array();
  while ($row = db_fetch_object($result)) {
    $content_types[] = $row->type;
  }

  foreach ($content_types as $type) {
    variable_del('dummy_content_' . $type . '_imported');
  }
}

module file

function dummy_content_menu() {
  $items = array();

  $items['finalize-configuration'] = array(
    'title' => 'blogs',
    'description' => t('Finalize configuration.'),
    'page callback' => 'finalize_configuration',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );

  return $items;
}

function finalize_configuration() {
  $result = db_query('SELECT DISTINCT(type) FROM {node_type} ORDER BY type');

  $content_types = array();
  while ($row = db_fetch_object($result)) {
    $content_types[] = $row->type;
  }

  foreach ($content_types as $type) {
    if (!variable_get('dummy_content_' . $type . '_imported', FALSE)) {
      $dir = dirname(__FILE__) . '/dummy_content/' . $type;

      if (file_exists($dir)) {
        foreach (scandir($dir) as $name) {
          if ($name == '.' || $name == '..') {
            continue;
          }

          $pathinfo = pathinfo($name);

          if ($pathinfo['extension'] == 'export' && is_file($dir . '/' . $name)) {
            node_export_import(file_get_contents($dir . '/' . $name), NULL, FALSE);
          }
        }

        variable_set('dummy_content_' . $type . '_imported', TRUE);
      }
    }
  }

  variable_set('site_frontpage', 'new-frontpage'); // set new frontpage

  drupal_goto('<front>');
}

Dummy content is stored into directories named by content type name.

paolomainardi’s picture

Thanks a lot Havran for sharing your solution.

Actually i didn't have so many time for creating a generic solution, also because Features API are quite complex, could be nice to have some info by module developers in order to take the right direction.

Keep me updated if you make progress.

Thanks a lot.

Grayside’s picture

@#6: Interesting. It puts me in mind of the hook_nodeapi() pattern.

function hook_features_event($op, $feature) {
  if ($op == 'install') {
    node_import_content('blog-template');
  }
}

Not sure if I like that plan. Bears thinking.

inductor’s picture

It would be a very useful hook. Use case: I include some roles in my feature, and want to assign input formats to them. I can`t achieve this using the hook_install/enable, because the roles aren`t already in the db. So for now I hack the features*.inc files.
Maybe somebody more skilled than me will roll out a patch?

good_man’s picture

subscribe

can we make hook_install and hook_enable only called after all processing is done? or should we introduce a new hook (e.g. hook_ready).

Grayside’s picture

@good_man: hook_install and hook_enable are called by drupal core, we cannot affect when in the page load they are called. A hook like I proposed in #10 would be reasonable--this is Features-specific.

davideads’s picture

Glad I came across this. I'm also struggling with input formats -- I'm forcibly creating a wysiwyg module profile that more or less MUST be tied to a custom input format (the feature allows TinyMCE to operate as a Markdown source editor). Got it all working nicely BUT the install relies on rather ugly hacking of the feature *.inc files to load everything correctly.

I'd pretty pretty happy with a simple "feature_post_install" hook -- perhaps you could pass it information about the current operation (reversion being different than installation) and leave it at that (if I understood grayside correctly, this is a pretty similar idea).

alonpeer’s picture

I like @davidead's idea for hook_feature_post_install, this will really upgrade what I can accomplish with Features.

In my current project, I have a new profile field and cck field, and I want to update them automatically using existing user and node data. hook_install and hook_enable just don't cut it.

My temp solution is using hook_enable with a condition that checks if those fields are empty or not before updating them. This ensures it'll run only once and not each time the feature module is disabled and re-enabled.

davideads’s picture

alonpeer -- the hook_enable solution is a good temporary solution. I would code up a patch for this but currently there's some confusion at my job as to whether I can (for licensing reasons) contribute patches to d.o projects and I don't have the time outside of work to do this.

alonpeer’s picture

OK, maybe I'll find the time over the weekend to familiarize myself with the Features' code and write this sort of patch.

Task added :)

Grayside’s picture

This is not exactly trivial. Features is not directly involved in the installation of every feature. In fact, there is nothing special features does to react to a module installation, rather, it reacts to the cache being cleared or the feature being reverted.

What is called for here is really a hook_post_install($module) in Drupal core, which Features could then use to detect whether a feature had just been installed and take action.

If you are willing to test this kind of thing every cache clear, you could add hook_features_restore_after() to _features_restore().

alonpeer’s picture

@Grayside, thank for the clarifications.

Please correct me if I'm wrong regarding the sort of patch I need here:
In _features_restore(), inside the outer loop foreach ($items as $module_name => $components), I will check if the current module has already been installed once before (using an array I will save and update in the variables table), and if it hasn't, I will call that module's hook_features_post_install().

Does that sound right?
Should I run this only if $op == 'rebuild', or is it also relevant for 'revert'?

alonpeer’s picture

Status: Active » Needs review
FileSize
1.35 KB

Here's a patch that implements what I described above, please let me know if this is the right direction.

davideads’s picture

@alonpeer I'm about to test this, but I did have a question after a cursory review: why not pass the 'op' variable to the _post_install hook?

davideads’s picture

Also the hook seems to work in my instance (installing a WYSIWYG module profile the hard way) just as expected. Thanks, @alonpeer.

alonpeer’s picture

@davideads thanks for testing, I'm glad it works. I hope we get some more testing done soon.

I don't see a good reason to pass the $op parameter. This post install process is supposed to run only once when the Feature is first installed.
I think we should keep it simple.

good_man’s picture

I think he means #6

Interesting, the alternative would be to have some hook_featuresapi() with an $op parameter as: 'install' 'disable' 'revert'

but for this issue I think we better now keep it for installing only.

davideads’s picture

Fair enough, re: the $op argument -- I could see a use for the hook running on the 'revert' op as in comment 6, but I'd rather have a simple, working hook and then add the sugar. Here's a module (which currently has some one bad, scary bug) which exploits this hook to install a wysiwyg profile: http://github.com/ecenter/editor_tinymce_markdown.

amitaibu’s picture

Wouldn't it be enough to create hook_update_N() that feature_revertes everything, and then executes all your code?

Grayside’s picture

That should work for most cases, but there could be issues with anything that doesn't load into the database until the first page load after revert.

carn1x’s picture

subscribe

Grayside’s picture

Hm, not sure why I didn't think of this, but the other problem with relying on hook_update() is that you might want to import content on the initial installation of your feature, and anything triggered by hook_install() will happen before some feature components are properly imported to the database.

#20 is looking like the cleanest path.

smk-ka’s picture

Status: Needs review » Reviewed & tested by the community

+1, this is a badly needed addition, given the way features are installed. #20 is working fine here on 7.x-1.0-beta2.

drewish’s picture

Version: 6.x-1.0 » 7.x-1.x-dev

Should probably be evaluated on 7.x first then have the fix backported.

Cauliflower’s picture

Patch #20 works for me, thx a lot !

Some suggestions:

  • Correct me if I'm wrong, but your patch is not an implementation of a drupal hook ? There is only one function that's called:
    $module_name .'_features_post_install'
    

    If you want to execute some code in module/feature x after feature y is enabled, we need an implementation of this mechanism:

    $hook_name = 'features_'. $module_name .'_post_install';
    module_invoke_all($hook_name);
    
  • Maybe we don't have to save which modules are enabled and always execute the hook post_install when _features_restore() is called. If a developer wants to execute his implementation of the hook only once, he can use the same technique as given in the patch (with variable_get() and variable_set).
  • Maybe we should pass the operator $op. If we do, the developer can decide when he wants to execute his implementation of post_install.
    $hook_name = 'features_'. $module_name .'_post_install';
    module_invoke_all($hook_name, $op);
    
davideads’s picture

@cptnCauliflower -- my patch way back when passed $op so the developer could use that as contextual information if she so desired. I dropped it, but I think it makes sense because you might wish to trigger conditional behavior when enabling, reverting, etc.

ao2’s picture

Patch from #20 works for me too, in my case I wanted to add terms to a vocabulary I were creating via features.

Thanks,
Antonio

Grayside’s picture

@Cauliflower the goal of the patch is to facilitate the feature you are enabling taking further action after it has gone through the complete routine of installation. hook_install() fires before many features-specific things have happened, which requires this extra, optional function call so the module can take further steps.

I think making this a hook is an interesting idea, but that kind of functionality starts to encroach pretty closely on just adding a hook_install_module() hook that allows any module to react to other module installations.

Grayside’s picture

Title: Howto execute custom code after all features modules and dependencies are installed ? » Allow a feature to execute a secondary install function after features components are created
Category: support » feature
febbraro’s picture

Status: Reviewed & tested by the community » Needs work

So there are a few things wrong with the patch. For starters _features_restore does not get called when you enable a feature so you'd have to find a different place to kick it off. The other thing is that if a feature is later disabled and uninstalled and then reinstalled the post install hook would not fire again.

Now, for D7 there is already a core hook that could be used for a post install hook, and that is:
http://api.drupal.org/api/drupal/modules--system--system.api.php/functio...

In talking with Grayside/hefox in IRC is seems like a good fix for D6 would just be having Features implement something very similar to hook_modules_installed such that the upgrade path to D7 would be eased.

So I think the verdict is there is a bit more work/thinking required for D6 and D7 can use core hooks to accomplish the same goal.

ao2’s picture

@febbraro, it looks like hook_modules_installed() is still not enough to access features added by the same module it is defined in, e.g.:


function MY_MODULE_modules_installed($modules) {
  if (in_array('MY_MODULE', $modules)) {
    // Do something after features from MY_MODULE have been added
    $vocabulary = taxonomy_vocabulary_machine_name_load('MY_VOCABULARY');
    if (!$vocabulary) {
      return drush_set_error(dt("Error: cannot get MY_VOCABULARY."));
    }
  }
}

In my case the vocabulary is defined in MY_MODULE.features.taxonomy.inc but hook_modules_installed() still can't see it. Maybe I am still missing something.

Regards,
Antonio

smk-ka’s picture

@febbraro
No, features (i.e. Drupal's praised deployment strategy) is actually nothing more than a giant hack: it is (currently, at least) triggered by hook_flush_caches(), which means it runs *after* any modules have been installed and associated hooks have run. Unfortunately, this means hook_modules_installed is of no use. Whether features could be rewritten for D7 to be triggered as the first module in hook_modules_installed (see hook_modules_implement_alter) is another question that should be investigated.

fabsor’s picture

using hook_modules_installed for importing configuration would make it hard for a feature module to implement hook_install itself to do things with it's own configuration. We would also have serious problems with weighting issues if we would like to do things after features has done it's thing.

I would propose that we add a new hook, hook_component_rebuilt, that takes in the component that has been rebuilt and maybe also a flag that indicates that it was added. This would also make it possible to do proper "update" hooks that needs to act after configuration has been rebuilt, which could be a good thing for deployment.

I would be happy to work on this, if you think that solution sound like a good one.

davideads’s picture

@fabsor This corner of the peanut gallery thinks that's a highly reasonable approach. It would certainly satisfy my use cases nicely.

Grayside’s picture

Something about @fabsor's approach sprung to mind the idea of using Feeds to handle the import process (sans UUIDs). I don't think that has much bearing on where we place the hook, but it might be relevant to consider for use cases and parameters.

davideads’s picture

@Grayside it seems like that runs the risk of being overly complex, but maybe I'm not seeing it... Can't developers simply call feeds if they need to?

Grayside’s picture

I'm suggesting the implementation of whatever hook this produces might want to trigger a feeds module parsing of a CSV file. Feeds itself could support the use cases of detecting changes/preventing duplicates/etc. It is an implementation use case for handling content import.

fabsor’s picture

If we go with this approach we could basicly write code to act upon any rebuild of any component, so yes, it would definetly be possible to for instance import things with feeds.

dgtlmoon’s picture

subscribing

attiks’s picture

logaritmisk’s picture

sub

njcheng’s picture

Subscribing.

guillaumev’s picture

For people looking for a temporary solution to this, here is what I do:

  • Create a feature for your content types and fields
  • Create a feature for the content you would like to add, calling it for example Feature X example content
  • In this Feature X example content, in hook_enable, call first "drupal_flush_all_caches", and then add your content programmatically

If it can help someone...

Mark Vincent Verallo’s picture

This really helped me. Thank you very much.

bergamot’s picture

I have a feature with some content types, and a module that inserts some nodes of these content types.
What i've done, and seems to work:
Make a dependency on the feature from the custom module:
In the .info:

dependencies[] = my_feature

In the .install file of the custom module, in hook_install, I call the features_rebuild function, that instantaneously rebuilds the enabled features, and thus makes the content types available.

function my_custom_module_install() {
  features_rebuild();

  /*
     now we can insert some nodes of the installed content type.
  */
}

The only downside of this is that the feature gets rebuilt twice: once in the custom module's install file, and once after all modules have been enabled.

neochief’s picture

Status: Needs work » Closed (duplicate)

@bergamor, you can use UUID + Deploy modules to export nodes to a feature instead of having it in a module. If you use the trick with dependency there, you wont't need to rebuild things two times.

Here you will find a patch to solve missing includes during installation: http://drupal.org/node/1265168#comment-5810726

cthiebault’s picture

Following comment #39...
I understand why we cannot use standard hooks like hook_install, hook_modules_installed, etc.
But I don't understand why it would not be possible to add a hook_features_post_install that would be called after the rebuild?

Could we do something like this?

/**
 * Implements hook_flush_caches().
 */
function features_flush_caches() {
  features_rebuild();
  // Don't flush the modules cache during installation, for performance reasons.
  if (variable_get('install_task') != 'done') {
    features_get_modules(NULL, TRUE);
  }

  module_invoke_all('features_post_install');   // execute hook_features_post_install

  return array();
}
cthiebault’s picture

Here is the patch for this.

I tried it and it works... but it's definitely not the best solution:
- this hook is called when all features have been rebuild... not only the feature of the current module
- it's called several times so we have to check if the work done in our hook_features_post_install was already done

cthiebault’s picture

Forget my patch.. it does not work as I imagined...

cthiebault’s picture

Finally I've modified patch from #20 to include op name in the hook.
And I don't check if the features was already installed because the first time op == 'enable' but I needed to do some work on rebuild so my module implements hook_features_rebuild_completed.

Here are the available hooks:

function hook_features_revert_completed() {
}

function hook_features_rebuild_completed() {
}

function hook_features_disable_completed() {
}

function hook_features_enable_completed() {
}
cthiebault’s picture

Status: Closed (duplicate) » Needs review
EmanueleQuinto’s picture

Title: Allow a feature to execute a secondary install function after features components are created » Allow a feature to execute a secondary insta// @TODO remove debug ll function after features components are created

Uhm, after reading the comments and testing we found a possible solution. At least it works with taxonomy that is our user case, but probably works for other contents as well.

In comments #37 febbraro suggested to use the hook_modules_installed() but in #39 ao2 discovered that the added features are not yet available. In #53 bergamot suggested to use an auxiliary module for further processing noticing the need of features_rebuild();

Now Putting the Pieces Together (C) what about using the hook_modules_installed and call features_rebuild?

Well it works at least for the taxonomy!

We need to add 3 taxonomy terms ('Available', 'Booked', 'Canceled') in the vocabulary defined in the feature and here's the code:

function sfsu_booking_modules_installed($modules) {

  // rebuild feature
  features_rebuild();

  // load feature vocabulary
  $sfsu_booking_feature = features_get_info('feature', 'sfsu_booking');
  $vocab_name = $sfsu_booking_feature->info['features']['taxonomy'][0];
  $vocab = taxonomy_vocabulary_machine_name_load($vocab_name);

// @TODO remove debug
dpm($vocab, 'sfsu_booking_preprocess_page');
error_log(str_repeat("=-", 40));
error_log(str_repeat("=-", 20) . " sfsu_booking_modules_installed");
error_log("VOC name: $vocab_name");
error_log(print_r($vocab, TRUE));

  // ADD terms
  $terms_feature = array('Available', 'Booked', 'Canceled');
  foreach ($terms_feature as $term_feature) {
    if (!($term =  array_shift(taxonomy_get_term_by_name($term_feature, $vocab_name)))) {
      $term = new stdClass();
      $term->name = $term_feature;
      $term->vid = $vocab->vid;
      taxonomy_term_save($term);

// @TODO remove debug
dpm($term, "sfsu_booking $term_feature NEW");

    }
    else {

// @TODO remove debug
dpm($term, "sfsu_booking $term_feature");

    }

// @TODO remove debug
error_log(str_repeat("##--", 40));
error_log(print_r($term, TRUE));

  }

}

You can check the output commenting features_rebuild(): it won't work. Not sure about side effects of running features_rebuild there but everything seems ok.

davidwatson’s picture

Title: Allow a feature to execute a secondary insta// @TODO remove debug ll function after features components are created » Allow a feature to execute a secondary install function after features components are created
ao2’s picture

Status: Needs review » Reviewed & tested by the community

Thanks @cthiebault

Patch from #58 looks OK to me, and I verified it woked in my use case of adding terms to a taxonomy defined in the feature itself. I just have to add the terms in the hook_features_rebuild_completed() implementation.

@EmanueleQuinto give that a try.

hefox’s picture

Status: Reviewed & tested by the community » Needs work

Haven't been reading this fully, but please provide some sort of example use of the new hooks in api functions.

Also, wouldn't it be relavent to provide status information, e.g. what was rebuilt/disabled/etc?

BrockBoland’s picture

As hefox pointed out there, I started a similar issue a couple weeks ago, because I didn't find this one.

#1782492: Invoke a hook on the Features module when the feature is reverted/enabled/disabled

Perhaps we should combine them, but I've added some additional aspects to my version. I found that running drush features-revert-all<code> will actually call <code>_features_restore() once for each component. As such, I moved the hook invocation inside the $components loop, and pass the affected component to the hook. I also included a "pre" hook to run before Features reverts/installs/uninstalls, just in case.

mpotter’s picture

Status: Needs work » Closed (duplicate)

I think Brock's patch is a bit more general-purpose, so I'm closing this as a dup and will work on getting #1782492: Invoke a hook on the Features module when the feature is reverted/enabled/disabled into one of the next versions.