Loading features.admin.inc +1 −1 Original line number Diff line number Diff line Loading @@ -1453,7 +1453,7 @@ function features_admin_components_review(&$form, &$form_state) { $module = $form_state['values']['module']; $revert = array(); foreach (array_filter($form_state['values']['revert']) as $component => $status) { features_set_signature($module, $component); features_set_signature($module, $component, NULL, 'review'); 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; Loading features.export.inc +143 −17 Original line number Diff line number Diff line Loading @@ -897,21 +897,52 @@ function features_get_storage($module_name) { function features_get_signature($state = 'default', $module_name, $component, $reset = FALSE) { switch ($state) { case 'cache': // Load the last known stored signature from the database. switch (_features_get_signature_storage_type()) { case 'table': // The database is fully updated. // All signatures are stored in a dedicated database table. $qr = db_select('features_signature', 'fs') ->fields('fs', array('signature')) ->condition('module', $module_name) ->condition('component', $component) ->execute(); return $qr ? $qr->fetchField() : FALSE; case 'cache': // The database is not fully updated, only to schema version 7201. // Signatures are stored in a cache table. $cache = cache_get('features_codecache', 'cache_featurestate'); if (!empty($cache->data)) { $codecache = $cache->data; if (isset($cache->data[$module_name][$component])) { return $cache->data[$module_name][$component]; } // No stored signature for this component. return FALSE; case 'variable': default: // The database is not fully updated, schema version before 7201. // Signatures are stored in a variable. $signaturess = variable_get('features_codecache', array()); if (isset($signaturess[$module_name][$component])) { return $signaturess[$module_name][$component]; } // No stored signature for this component. return FALSE; } return isset($codecache[$module_name][$component]) ? $codecache[$module_name][$component] : FALSE; case 'default': // Get the component data as currently in code. $objects = features_get_default($component, $module_name, TRUE, $reset); break; case 'normal': // Get the component data as currently in the database. $objects = features_get_normal($component, $module_name, $reset); break; } if (!empty($objects)) { // Build a signature hash from the component data. features_sanitize($objects, $component); return md5(_features_linetrim(features_var_export($objects))); } Loading @@ -919,23 +950,118 @@ function features_get_signature($state = 'default', $module_name, $component, $r } /** * Set the signature of a module/component pair in the codecache. * Updates a module/component signature in the database. * * The signature stored in the database reflects the last known state of the * component in code. * * @param string $module * A module name. * A feature module name. * @param string $component * A component name, e.g. 'field_instance'. * @param string|null $signature * An md5 signature, or NULL to auto-generate one. * @param string|null|false $signature * An md5 signature, or NULL to generate one from the current state in code, * or FALSE to delete the signature. * @param string|null $message * (optional) Message to store along with the updated signature. */ function features_set_signature($module, $component, $signature = NULL) { function features_set_signature($module, $component, $signature = NULL, $message = NULL) { if ($signature === NULL) { // Build signature from current state in code. $signature = features_get_signature('default', $module, $component, TRUE); } // Support un-updated databases. switch (_features_get_signature_storage_type()) { case 'table': // The database is fully updated. // All signatures are stored in a dedicated database table. if ($signature === FALSE) { // Delete the signature. db_delete('features_signature') ->condition('module', $module) ->condition('component', $component) ->execute(); } else { // Insert or update the signature. db_merge('features_signature') ->key(array( 'module' => $module, 'component' => $component, )) ->fields(array( 'signature' => $signature, 'updated' => time(), 'message' => $message, )) ->execute(); } break; case 'cache': // The database is not fully updated, only to schema version 7201. // Signatures are stored in a cache table. $cache = cache_get('features_codecache', 'cache_featurestate'); if (!empty($cache->data)) { $codecache = $cache->data; $signaturess = $cache->data; } $signaturess[$module][$component] = $signature; cache_set('features_codecache', $signaturess, 'cache_featurestate'); break; case 'variable': default: // The database is not fully updated, schema version before 7201. // Signatures are stored in a variable. $signaturess = variable_get('features_codecache', array()); $signaturess[$module][$component] = $signature; variable_set('features_codecache', $signaturess); break; } } /** * Gets the current storage type for features component signatures. * * This is needed to prevent breakage in a database that is not fully updated * yet, e.g. in deployment operations that run before the database update. * * The signatures used to be stored in a variable. * Since #1325288, it was stored in a cache table. This only applies to projects * that were using a -dev branch after 7.x-2.11. * Since #3162854, it is stored in a dedicated non-cache table. * * @param bool $reset * TRUE, to reset the static cache. * * @return string * One of 'table', 'cache' or 'type'. * On a fully updated database, this value will be 'table'. * * @see \features_get_signature() * @see \features_set_signature() * @see \features_update_7202() */ function _features_get_signature_storage_type($reset = FALSE) { static $type; if ($reset) { $type = NULL; } if ($type !== NULL) { return $type; } if (db_table_exists('features_signature')) { $type = 'table'; } elseif (db_table_exists('cache_featurestate')) { $type = 'cache'; } else { $type = 'variable'; } $signature = isset($signature) ? $signature : features_get_signature('default', $module, $component, TRUE); $codecache[$module][$component] = $signature; cache_set('features_codecache', $codecache, 'cache_featurestate'); return $type; } /** Loading Loading @@ -1256,7 +1382,7 @@ function features_get_component_states($features = array(), $rebuild_only = TRUE // Update code cache if it is stale, clear out semaphore if it is // stale. if ($default != $codecache) { features_set_signature($feature, $component, $default); features_set_signature($feature, $component, $default, __FUNCTION__ . '(): $normal === $default'); } } // Component properly implements exportables. Loading features.install +148 −6 Original line number Diff line number Diff line Loading @@ -11,8 +11,46 @@ function features_schema() { $schema['cache_features'] = drupal_get_schema_unprocessed('system', 'cache'); $schema['cache_features']['description'] = 'Cache table for features to store module info.'; $schema['cache_featurestate'] = drupal_get_schema_unprocessed('system', 'cache'); $schema['cache_featurestate']['description'] = 'Table for features to store persistent state info.'; $schema['features_signature'] = array( 'description' => 'Stores hashes that reflect the last known state of a features component.', 'fields' => array( 'module' => array( 'description' => 'Name of the feature module.', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, ), 'component' => array( 'description' => 'Name of the features component.', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, ), 'signature' => array( 'description' => 'Hash reflecting the last approved state of the component in code.', 'type' => 'varchar', 'length' => 128, 'not null' => TRUE, ), 'updated' => array( 'description' => 'Timestamp when the signature was last updated.', 'type' => 'int', 'not null' => TRUE, 'default' => 0, ), 'message' => array( 'description' => 'Message to document why the component was updated.', 'type' => 'varchar', 'length' => 255, 'not null' => FALSE, ), ), 'primary key' => array('module', 'component'), 'indexes' => array( 'module' => array('module'), 'component' => array('component'), ), ); return $schema; } Loading Loading @@ -156,9 +194,113 @@ function features_update_7200() { * Add {cache_featurestate} table. */ function features_update_7201() { if (!db_table_exists('cache_featurestate')) { // This update hook is no longer active. } /** * Create a new table 'features_signature' to store signatures. */ function features_update_7202() { if (!db_table_exists('features_signature')) { // Create the new table for signatures. $schema = array( 'description' => 'Stores hashes that reflect the last known state of a features component.', 'fields' => array( 'module' => array( 'description' => 'Name of the feature module.', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, ), 'component' => array( 'description' => 'Name of the features component.', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, ), 'signature' => array( 'description' => 'Hash reflecting the last approved state of the component in code.', 'type' => 'varchar', 'length' => 128, 'not null' => TRUE, ), 'updated' => array( 'description' => 'Timestamp when the signature was last updated.', 'type' => 'int', 'not null' => TRUE, 'default' => 0, ), 'message' => array( 'description' => 'Message to document why the component was updated.', 'type' => 'varchar', 'length' => 255, 'not null' => FALSE, ), ), 'primary key' => array('module', 'component'), 'indexes' => array( 'module' => array('module'), 'component' => array('component'), ), ); db_create_table('features_signature', $schema); } // Load existing signatures. if (db_table_exists('cache_features')) { // The original version of the previous update has already run. $cache = cache_get('features_codecache', 'cache_featurestate'); $signaturess = !empty($cache->data) ? $cache->data : array(); $message = __FUNCTION__ . '() - from cache storage'; } else { // The update is from an earlier version of features. $signaturess = variable_get('features_codecache', array()); $message = __FUNCTION__ . '() - from variable storage'; } // Prevent existing records from being inserted again. // This way we don't need a REPLACE query. // This only applies if the table was already created e.g. in a previous // failed attempt to run this update. $q = db_select('features_signature', 'fs')->fields('fs'); if ($qr = $q->execute()) { foreach ($qr as $obj) { unset($signaturess[$obj->module][$obj->component]); } } // Get a timestamp to be stored in each record. $timestamp = time(); // Build the insert query. $insert = db_insert('features_signature') ->fields(array('module', 'component', 'signature', 'udated', 'message')); foreach ($signaturess as $module => $signatures) { foreach ($signatures as $component => $signature) { $record = array( 'module' => $module, 'component' => $component, 'signature' => $signature, 'updated' => $timestamp, 'message' => $message, ); $insert->values($record); } } // Execute the insert query. // On failure, allow the exception to trickle up. $insert->execute(); // Delete the old table and variable if the data migration was successful. variable_del('features_codecache'); $schema = drupal_get_schema_unprocessed('system', 'cache'); db_create_table('cache_featurestate', $schema); if (db_table_exists('cache_featurestate')) { db_drop_table('cache_featurestate'); } } features.module +1 −1 Original line number Diff line number Diff line Loading @@ -1236,7 +1236,7 @@ function _features_restore($op, $items = array()) { // If the script completes, remove the semaphore and set the code // signature. features_semaphore('del', $component); features_set_signature($module_name, $component); features_set_signature($module_name, $component, NULL, __FUNCTION__ . '(' . $log_action . ')'); watchdog('features', '@action completed for @module_name / @component.', array('@action' => $log_action, '@component' => $component, '@module_name' => $module_name)); } Loading Loading
features.admin.inc +1 −1 Original line number Diff line number Diff line Loading @@ -1453,7 +1453,7 @@ function features_admin_components_review(&$form, &$form_state) { $module = $form_state['values']['module']; $revert = array(); foreach (array_filter($form_state['values']['revert']) as $component => $status) { features_set_signature($module, $component); features_set_signature($module, $component, NULL, 'review'); 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; Loading
features.export.inc +143 −17 Original line number Diff line number Diff line Loading @@ -897,21 +897,52 @@ function features_get_storage($module_name) { function features_get_signature($state = 'default', $module_name, $component, $reset = FALSE) { switch ($state) { case 'cache': // Load the last known stored signature from the database. switch (_features_get_signature_storage_type()) { case 'table': // The database is fully updated. // All signatures are stored in a dedicated database table. $qr = db_select('features_signature', 'fs') ->fields('fs', array('signature')) ->condition('module', $module_name) ->condition('component', $component) ->execute(); return $qr ? $qr->fetchField() : FALSE; case 'cache': // The database is not fully updated, only to schema version 7201. // Signatures are stored in a cache table. $cache = cache_get('features_codecache', 'cache_featurestate'); if (!empty($cache->data)) { $codecache = $cache->data; if (isset($cache->data[$module_name][$component])) { return $cache->data[$module_name][$component]; } // No stored signature for this component. return FALSE; case 'variable': default: // The database is not fully updated, schema version before 7201. // Signatures are stored in a variable. $signaturess = variable_get('features_codecache', array()); if (isset($signaturess[$module_name][$component])) { return $signaturess[$module_name][$component]; } // No stored signature for this component. return FALSE; } return isset($codecache[$module_name][$component]) ? $codecache[$module_name][$component] : FALSE; case 'default': // Get the component data as currently in code. $objects = features_get_default($component, $module_name, TRUE, $reset); break; case 'normal': // Get the component data as currently in the database. $objects = features_get_normal($component, $module_name, $reset); break; } if (!empty($objects)) { // Build a signature hash from the component data. features_sanitize($objects, $component); return md5(_features_linetrim(features_var_export($objects))); } Loading @@ -919,23 +950,118 @@ function features_get_signature($state = 'default', $module_name, $component, $r } /** * Set the signature of a module/component pair in the codecache. * Updates a module/component signature in the database. * * The signature stored in the database reflects the last known state of the * component in code. * * @param string $module * A module name. * A feature module name. * @param string $component * A component name, e.g. 'field_instance'. * @param string|null $signature * An md5 signature, or NULL to auto-generate one. * @param string|null|false $signature * An md5 signature, or NULL to generate one from the current state in code, * or FALSE to delete the signature. * @param string|null $message * (optional) Message to store along with the updated signature. */ function features_set_signature($module, $component, $signature = NULL) { function features_set_signature($module, $component, $signature = NULL, $message = NULL) { if ($signature === NULL) { // Build signature from current state in code. $signature = features_get_signature('default', $module, $component, TRUE); } // Support un-updated databases. switch (_features_get_signature_storage_type()) { case 'table': // The database is fully updated. // All signatures are stored in a dedicated database table. if ($signature === FALSE) { // Delete the signature. db_delete('features_signature') ->condition('module', $module) ->condition('component', $component) ->execute(); } else { // Insert or update the signature. db_merge('features_signature') ->key(array( 'module' => $module, 'component' => $component, )) ->fields(array( 'signature' => $signature, 'updated' => time(), 'message' => $message, )) ->execute(); } break; case 'cache': // The database is not fully updated, only to schema version 7201. // Signatures are stored in a cache table. $cache = cache_get('features_codecache', 'cache_featurestate'); if (!empty($cache->data)) { $codecache = $cache->data; $signaturess = $cache->data; } $signaturess[$module][$component] = $signature; cache_set('features_codecache', $signaturess, 'cache_featurestate'); break; case 'variable': default: // The database is not fully updated, schema version before 7201. // Signatures are stored in a variable. $signaturess = variable_get('features_codecache', array()); $signaturess[$module][$component] = $signature; variable_set('features_codecache', $signaturess); break; } } /** * Gets the current storage type for features component signatures. * * This is needed to prevent breakage in a database that is not fully updated * yet, e.g. in deployment operations that run before the database update. * * The signatures used to be stored in a variable. * Since #1325288, it was stored in a cache table. This only applies to projects * that were using a -dev branch after 7.x-2.11. * Since #3162854, it is stored in a dedicated non-cache table. * * @param bool $reset * TRUE, to reset the static cache. * * @return string * One of 'table', 'cache' or 'type'. * On a fully updated database, this value will be 'table'. * * @see \features_get_signature() * @see \features_set_signature() * @see \features_update_7202() */ function _features_get_signature_storage_type($reset = FALSE) { static $type; if ($reset) { $type = NULL; } if ($type !== NULL) { return $type; } if (db_table_exists('features_signature')) { $type = 'table'; } elseif (db_table_exists('cache_featurestate')) { $type = 'cache'; } else { $type = 'variable'; } $signature = isset($signature) ? $signature : features_get_signature('default', $module, $component, TRUE); $codecache[$module][$component] = $signature; cache_set('features_codecache', $codecache, 'cache_featurestate'); return $type; } /** Loading Loading @@ -1256,7 +1382,7 @@ function features_get_component_states($features = array(), $rebuild_only = TRUE // Update code cache if it is stale, clear out semaphore if it is // stale. if ($default != $codecache) { features_set_signature($feature, $component, $default); features_set_signature($feature, $component, $default, __FUNCTION__ . '(): $normal === $default'); } } // Component properly implements exportables. Loading
features.install +148 −6 Original line number Diff line number Diff line Loading @@ -11,8 +11,46 @@ function features_schema() { $schema['cache_features'] = drupal_get_schema_unprocessed('system', 'cache'); $schema['cache_features']['description'] = 'Cache table for features to store module info.'; $schema['cache_featurestate'] = drupal_get_schema_unprocessed('system', 'cache'); $schema['cache_featurestate']['description'] = 'Table for features to store persistent state info.'; $schema['features_signature'] = array( 'description' => 'Stores hashes that reflect the last known state of a features component.', 'fields' => array( 'module' => array( 'description' => 'Name of the feature module.', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, ), 'component' => array( 'description' => 'Name of the features component.', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, ), 'signature' => array( 'description' => 'Hash reflecting the last approved state of the component in code.', 'type' => 'varchar', 'length' => 128, 'not null' => TRUE, ), 'updated' => array( 'description' => 'Timestamp when the signature was last updated.', 'type' => 'int', 'not null' => TRUE, 'default' => 0, ), 'message' => array( 'description' => 'Message to document why the component was updated.', 'type' => 'varchar', 'length' => 255, 'not null' => FALSE, ), ), 'primary key' => array('module', 'component'), 'indexes' => array( 'module' => array('module'), 'component' => array('component'), ), ); return $schema; } Loading Loading @@ -156,9 +194,113 @@ function features_update_7200() { * Add {cache_featurestate} table. */ function features_update_7201() { if (!db_table_exists('cache_featurestate')) { // This update hook is no longer active. } /** * Create a new table 'features_signature' to store signatures. */ function features_update_7202() { if (!db_table_exists('features_signature')) { // Create the new table for signatures. $schema = array( 'description' => 'Stores hashes that reflect the last known state of a features component.', 'fields' => array( 'module' => array( 'description' => 'Name of the feature module.', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, ), 'component' => array( 'description' => 'Name of the features component.', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, ), 'signature' => array( 'description' => 'Hash reflecting the last approved state of the component in code.', 'type' => 'varchar', 'length' => 128, 'not null' => TRUE, ), 'updated' => array( 'description' => 'Timestamp when the signature was last updated.', 'type' => 'int', 'not null' => TRUE, 'default' => 0, ), 'message' => array( 'description' => 'Message to document why the component was updated.', 'type' => 'varchar', 'length' => 255, 'not null' => FALSE, ), ), 'primary key' => array('module', 'component'), 'indexes' => array( 'module' => array('module'), 'component' => array('component'), ), ); db_create_table('features_signature', $schema); } // Load existing signatures. if (db_table_exists('cache_features')) { // The original version of the previous update has already run. $cache = cache_get('features_codecache', 'cache_featurestate'); $signaturess = !empty($cache->data) ? $cache->data : array(); $message = __FUNCTION__ . '() - from cache storage'; } else { // The update is from an earlier version of features. $signaturess = variable_get('features_codecache', array()); $message = __FUNCTION__ . '() - from variable storage'; } // Prevent existing records from being inserted again. // This way we don't need a REPLACE query. // This only applies if the table was already created e.g. in a previous // failed attempt to run this update. $q = db_select('features_signature', 'fs')->fields('fs'); if ($qr = $q->execute()) { foreach ($qr as $obj) { unset($signaturess[$obj->module][$obj->component]); } } // Get a timestamp to be stored in each record. $timestamp = time(); // Build the insert query. $insert = db_insert('features_signature') ->fields(array('module', 'component', 'signature', 'udated', 'message')); foreach ($signaturess as $module => $signatures) { foreach ($signatures as $component => $signature) { $record = array( 'module' => $module, 'component' => $component, 'signature' => $signature, 'updated' => $timestamp, 'message' => $message, ); $insert->values($record); } } // Execute the insert query. // On failure, allow the exception to trickle up. $insert->execute(); // Delete the old table and variable if the data migration was successful. variable_del('features_codecache'); $schema = drupal_get_schema_unprocessed('system', 'cache'); db_create_table('cache_featurestate', $schema); if (db_table_exists('cache_featurestate')) { db_drop_table('cache_featurestate'); } }
features.module +1 −1 Original line number Diff line number Diff line Loading @@ -1236,7 +1236,7 @@ function _features_restore($op, $items = array()) { // If the script completes, remove the semaphore and set the code // signature. features_semaphore('del', $component); features_set_signature($module_name, $component); features_set_signature($module_name, $component, NULL, __FUNCTION__ . '(' . $log_action . ')'); watchdog('features', '@action completed for @module_name / @component.', array('@action' => $log_action, '@component' => $component, '@module_name' => $module_name)); } Loading