? batch_node_access_rebuild.patch ? batch_node_access_rebuild_3.patch ? files ? sites/here.be.dragons.rtk0.net Index: includes/module.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/module.inc,v retrieving revision 1.108 diff -u -p -r1.108 module.inc --- includes/module.inc 22 Aug 2007 08:36:34 -0000 1.108 +++ includes/module.inc 29 Aug 2007 20:37:23 -0000 @@ -260,6 +260,10 @@ function module_enable($module_list) { foreach ($invoke_modules as $module) { module_invoke($module, 'enable'); + // Check if node_access table needs rebuilding. + if (!node_access_needs_rebuild() && module_hook($module, 'node_grants')) { + node_access_needs_rebuild(TRUE); + } } } @@ -273,6 +277,11 @@ function module_disable($module_list) { $invoke_modules = array(); foreach ($module_list as $module) { if (module_exists($module)) { + // Check if node_access table needs rebuilding. + if (!node_access_needs_rebuild() && module_hook($module, 'node_grants')) { + node_access_needs_rebuild(TRUE); + } + module_load_install($module); module_invoke($module, 'disable'); db_query("UPDATE {system} SET status = %d, throttle = %d WHERE type = '%s' AND name = '%s'", 0, 0, 'module', $module); @@ -286,6 +295,12 @@ function module_disable($module_list) { // Force to regenerate the stored list of hook implementations. module_implements('', FALSE, TRUE); } + + // If there remains no more node_access module, rebuilding will be + // straightforward, we can do it right now. + if (node_access_needs_rebuild() && count(module_implements('node_grants')) == 0) { + node_access_rebuild(); + } } /** Index: modules/node/node.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.admin.inc,v retrieving revision 1.2 diff -u -p -r1.2 node.admin.inc --- modules/node/node.admin.inc 29 Aug 2007 19:56:09 -0000 1.2 +++ modules/node/node.admin.inc 29 Aug 2007 20:37:23 -0000 @@ -10,7 +10,8 @@ * Menu callback; presents general node configuration options. */ function node_configure() { - // Only show rebuild button if there is 0 or more than 2 rows in node_access table, or if there are modules that implement node_grant. + // Only show rebuild button if there is 0 or more than 2 rows in node_access table, + // or if there are modules that implement node_grant. if (db_result(db_query('SELECT COUNT(*) FROM {node_access}')) != 1 || count(module_implements('node_grants')) > 0) { $status = '

'. t('If the site is experiencing problems with permissions to content, you may have to rebuild the permissions cache. Possible causes for permission problems are disabling modules or configuration changes to permissions. Rebuilding will remove all privileges to posts, and replace them with permissions based on the current modules and settings.') .'

'; $status .= '

'. t('Rebuilding may take some time if there is a lot of content or complex permission settings. After rebuilding has completed posts will automatically use the new permissions.') .'

'; @@ -64,8 +65,7 @@ function node_configure_rebuild_confirm( * Handler for wipe confirmation */ function node_configure_rebuild_confirm_submit($form, &$form_state) { - node_access_rebuild(); - drupal_set_message(t('The node access table has been rebuilt.')); + node_access_rebuild(TRUE); $form_state['redirect'] = 'admin/content/node-settings'; return; } Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.875 diff -u -p -r1.875 node.module --- modules/node/node.module 26 Aug 2007 14:15:51 -0000 1.875 +++ modules/node/node.module 29 Aug 2007 20:37:23 -0000 @@ -20,6 +20,17 @@ define('NODE_BUILD_PRINT', 5); * Implementation of hook_help(). */ function node_help($path, $arg) { + if ($path != 'admin/content/node-settings/rebuild' && strpos($path, '#') === FALSE + && user_access('access administration pages') && node_access_needs_rebuild()) { + if ($path == 'admin/content/node-settings') { + $message = t('The content access permissions need to be rebuilt.'); + } + else { + $message = t('The content access permissions need to be rebuilt. Please visit this page.', array('@node_access_rebuild' => url('admin/content/node-settings/rebuild'))); + } + drupal_set_message($message, 'error'); + } + switch ($path) { case 'admin/help#node': $output = '

'. t('All content in a website is stored and treated as nodes. Therefore nodes are any postings such as blogs, stories, polls and forums. The node module manages these content types and is one of the strengths of Drupal over other content management systems.') .'

'; @@ -1335,6 +1346,9 @@ function node_menu() { 'title' => 'Rebuild permissions', 'page arguments' => array('node_configure_rebuild_confirm'), 'file' => 'node.admin.inc', + // Any user than can potentially trigger a node_acess_needs_rebuild(TRUE) + // has to be allowed access to the 'node access rebuild' confirm form. + 'access arguments' => array('access administration pages'), 'type' => MENU_CALLBACK, ); @@ -2025,7 +2039,7 @@ function node_db_rewrite_sql($query, $pr */ function node_access_acquire_grants($node) { $grants = module_invoke_all('node_access_records', $node); - if (!$grants) { + if (empty($grants)) { $grants[] = array('realm' => 'all', 'gid' => 0, 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0); } else { @@ -2087,33 +2101,147 @@ function node_access_write_grants($node, } /** + * Flag / unflag the node access grants for rebuilding, or read the current + * value of the flag. + * + * When the flag is set, a message is displayed to users with 'access + * administration pages' permission, pointing to the 'rebuild' confirm form. + * This can be used as an alternative to direct node_access_rebuild calls, + * allowing administrators to decide when they want to perform the actual + * (possibly time consuming) rebuild. + * When unsure the current user is an adminisrator, node_access_rebuild + * should be used instead. + * + * @param $rebuild + * (Optional) The boolean value to be written. + * @return + * (If no value was provided for $rebuild) The current value of the flag. + */ +function node_access_needs_rebuild($rebuild = NULL) { + if (!isset($rebuild)) { + return variable_get('node_access_needs_rebuild', FALSE); + } + elseif ($rebuild) { + variable_set('node_access_needs_rebuild', TRUE); + } + else { + variable_del('node_access_needs_rebuild'); + } +} + +/** * Rebuild the node access database. This is occasionally needed by modules * that make system-wide changes to access levels. + * + * When the rebuild is required by an admin-triggered action (e.g module + * settings form), calling node_access_needs_rebuild(TRUE) instead of + * node_access_rebuild() lets the user perform his changes and actually + * rebuild only once he is done. + * + * Note : As of Drupal 6, node access modules are not required to (and actually + * should not) call node_access_rebuild() in hook_enable/disable anymore. + * + * @see node_access_needs_rebuild + * + * @param $batch_mode + * Set to TRUE to process in 'batch' mode, spawning processing over several + * HTTP requests (thus avoiding the risk of PHP timeout if the site has a + * large number of nodes). + * hook_update_N and any form submit handler are safe contexts to use the + * 'batch mode'. Less decidable cases (such as calls from hook_user, + * hook_taxonomy, hook_node_type...) might consider using the non-batch mode. */ -function node_access_rebuild() { +function node_access_rebuild($batch_mode = FALSE) { db_query("DELETE FROM {node_access}"); - // only recalculate if site is using a node_access module + // Only recalculate if the site is using a node_access module. if (count(module_implements('node_grants'))) { - // If not in 'safe mode', increase the maximum execution time: - if (!ini_get('safe_mode')) { - set_time_limit(240); - } - $result = db_query("SELECT nid FROM {node}"); - while ($node = db_fetch_object($result)) { - $loaded_node = node_load($node->nid, NULL, TRUE); - // To preserve database integrity, only aquire grants if the node - // loads successfully. - if (!empty($loaded_node)) { - node_access_acquire_grants($loaded_node); + if ($batch_mode) { + $batch = array( + 'title' => t('Rebuilding content access permissions'), + 'operations' => array( + array('_node_access_rebuild_batch_operation', array()), + ), + 'finished' => '_node_access_rebuild_batch_finished' + ); + batch_set($batch); + } + else { + // If not in 'safe mode', increase the maximum execution time. + if (!ini_get('safe_mode')) { + set_time_limit(240); + } + $result = db_query("SELECT nid FROM {node}"); + while ($node = db_fetch_object($result)) { + $loaded_node = node_load($node->nid, NULL, TRUE); + // To preserve database integrity, only aquire grants if the node + // loads successfully. + if (!empty($loaded_node)) { + node_access_acquire_grants($loaded_node); + } } } } else { - // not using any node_access modules. add the default grant. + // Not using any node_access modules. Add the default grant. db_query("INSERT INTO {node_access} VALUES (0, 0, 'all', 1, 0, 0)"); } + + if (!isset($batch)) { + drupal_set_message(t('The node access table has been rebuilt.')); + node_access_needs_rebuild(FALSE); + cache_clear_all(); + } +} + +/** + * Batch operation for node_access_rebuild_batch. + * + * This is a mutlistep operation : we go through all nodes by packs of 20. + * The batch processing engine interrupts processing and sends progress + * feedback after 1 second execution time. + */ +function _node_access_rebuild_batch_operation(&$context) { + if (empty($context['sandbox'])) { + // Initiate multistep processing. + $context['sandbox']['progress'] = 0; + $context['sandbox']['current_node'] = 0; + $context['sandbox']['max'] = db_result(db_query('SELECT COUNT(DISTINCT nid) FROM {node}')); + } + + // Process the next 20 nodes. + $limit = 20; + $result = db_query_range("SELECT nid FROM {node} WHERE nid > %d ORDER BY nid ASC", $context['sandbox']['current_node'], 0, $limit); + while ($row = db_fetch_array($result)) { + $loaded_node = node_load($row['nid'], NULL, TRUE); + // To preserve database integrity, only aquire grants if the node + // loads successfully. + if (!empty($loaded_node)) { + node_access_acquire_grants($loaded_node); + } + $context['sandbox']['progress']++; + $context['sandbox']['current_node'] = $loaded_node->nid; + } + + // Multistep processing : report progress. + if ($context['sandbox']['progress'] != $context['sandbox']['max']) { + $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; + } +} + +/** + * Post-processing for node_access_rebuild_batch. + */ +function _node_access_rebuild_batch_finished($success, $results, $operations) { + if ($success) { + drupal_set_message(t('The content access permissions have been rebuilt.')); + node_access_needs_rebuild(FALSE); + } + else { + drupal_set_message(t('The content access permissions have not been properly rebuilt.'), 'error'); + } cache_clear_all(); } + /** * @} End of "defgroup node_access". */