Index: skeleton_sync.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/skeleton/skeleton_sync.inc,v retrieving revision 1.1 diff -u -r1.1 skeleton_sync.inc --- skeleton_sync.inc 12 Mar 2009 18:22:29 -0000 1.1 +++ skeleton_sync.inc 30 Apr 2009 02:40:50 -0000 @@ -3,14 +3,33 @@ /** * Syncronize form. Contains lists of updated, added, and deleted templates. * + * @return + * The rendered HTML of the page. + */ +function skeleton_sync_page() { + drupal_add_js(drupal_get_path('module', 'skeleton') . '/skeleton-admin.js'); + $output .= drupal_get_form('skeleton_sync_content_form'); + $output .= drupal_get_form('skeleton_sync_add_template_form'); + $output .= drupal_get_form('skeleton_sync_delete_template_form'); + return $output; +} + +/** + * Syncronize content form. + * * @param $form_state * The state of the form. * @return * The form array. */ -function skeleton_sync_form($form_state) { +function skeleton_sync_content_form($form_state) { $form = array(); - $form['help'] = array( + $form['content'] = array( + '#type' => 'fieldset', + '#title' => t('Syncronize template content'), + '#collapsible' => TRUE, + ); + $form['content']['help'] = array( '#prefix' => '

', '#suffix' => '

', '#value' => t('Use this form to syncronize changed templates with pages which have all ready been created.'), @@ -25,33 +44,30 @@ $updated[$node->nid] = t('%title, in the %book-title book, created from the %template template.', array('@node-url' => url('node/' . $node->nid), '%title' => $node->title, '@book-url' => url('node/' . $book->nid), '%book-title' => $book->title, '%template' => $eligble_node->template, '@template-url' => url('admin/content/skeleton/template/' . $eligble_node->template_id . '/view'))); } if (!empty($updated)) { - $form['nodes'] = array( + $form['content']['#collapsed'] = FALSE; + $form['content']['nodes'] = array( '#type' => 'checkboxes', '#title' => t('Select the nodes you wish to update'), '#options' => $updated, ); - $form['submit'] = array( + $form['content']['submit'] = array( '#type' => 'submit', '#value' => t('Synchronize'), ); } else { - $form['no_nodes'] = array( + $form['content']['#collapsed'] = TRUE; + $form['content']['no_nodes'] = array( '#prefix' => '

', '#suffix' => '

', - '#value' => t('There are no nodes eligible for synchronizing.') + '#value' => t('There are no nodes eligible for content synchronizing.') ); } - - // TODO: Show a seperate list for new templates added to a skeleton. - - // TODO: Show a seperate list for templates removed from a skeleton. - return $form; } /** - * Submit handler for the syncronize form. + * Submit handler for the syncronize content form. * * @param $form * The form being submitted. @@ -59,7 +75,7 @@ * @param $form_state * The state of the form. */ -function skeleton_sync_form_submit($form, $form_state) { +function skeleton_sync_content_form_submit($form, $form_state) { $batch = array( 'title' => t('Updating content'), 'init_message' => t('Content updating is starting.'), @@ -74,6 +90,189 @@ } /** + * Form for pushing out new templates to existing skeletons. + */ +function skeleton_sync_add_template_form($form_state) { + $form = array(); + $form['templates'] = array( + '#type' => 'fieldset', + '#title' => t('Syncronize templates'), + '#collapsible' => TRUE, + ); + // Find all copies of instantiated skeletons which do not contain a template + // now associated with the same skeleton. + $skeleton_result = db_query("SELECT DISTINCT skeleton_id FROM {skeleton_template_node}"); + while ($skeleton_id = db_result($skeleton_result)) { + $templates = array(); + $updated = array(); + $skeleton = skeleton_load($skeleton_id); + $form['templates']['skeletons'] = array( + '#tree' => TRUE, + ); + $form['templates']['skeletons'][$skeleton_id] = array(); + $template_result = db_query("SELECT DISTINCT template_id FROM {skeleton_template_node} WHERE skeleton_id = %d", $skeleton_id); + while ($template_id = db_result($template_result)) { + $templates[] = $template_id; + } + $updated_result = db_query("SELECT template_id FROM {skeleton_data} WHERE skeleton_id = %d AND template_id NOT IN (" . implode(',', $templates) . ")", $skeleton_id); + while ($updated_id = db_result($updated_result)) { + $template = skeleton_template_load($updated_id); + $updated[$updated_id] = l($template->template, 'admin/content/skeleton/template/' . $template->template_id . '/view'); + } + if (!empty($updated)) { + $form['templates']['skeletons'][$skeleton_id] = array( + '#type' => 'checkboxes', + '#title' => t('Create new pages from new templates assigned to %skeleton', array('@skeleton-url' => url('admin/content/skeleton/skeleton/' . $skeleton->skeleton_id . '/edit'), '%skeleton' => $skeleton->skeleton)), + '#options' => $updated, + ); + $form['templates']['submit'] = array( + '#type' => 'submit', + '#value' => t('Syncronize'), + ); + } + else { + $form['templates']['#collapsed'] = TRUE; + $form['templates']['no_templates'] = array( + '#prefix' => '

', + '#suffix' => '

', + '#value' => t('There are no templates eligible for content synchronizing.') + ); + } + } + return $form; +} + +/** + * Submit handler for pushing out new templates to existing books. + */ +function skeleton_sync_add_template_form_submit($form, &$form_state) { + module_load_include('inc', 'node', 'node.pages'); + module_load_include('inc', 'skeleton', 'skeleton_token'); + foreach($form_state['values']['skeletons'] as $skeleton_id => $templates) { + $templates = array_filter($templates); + foreach($templates as $template_id) { + // Find the template parent of this template + // $template = skeleton_template_load($template_id); + // If the parent is 0, we place it under the top-most book node. + // Otherwise, place it under the parent template in the book. + $parent = db_result(db_query("SELECT parent FROM {skeleton_data} WHERE skeleton_id = %d AND template_id = %d", $skeleton_id, $template_id)); + if ($parent == 0) { + // TODO! + } + else { + $parent_nids_result = db_query("SELECT nid, tokens FROM {skeleton_template_node} WHERE skeleton_id = %d AND template_id = %d", $skeleton_id, $parent); + while($parent_info = db_fetch_object($parent_nids_result)) { + $parent_info->tokens = unserialize($parent_info->tokens); + $data = db_result(db_query("SELECT node_data FROM {skeleton_template} WHERE template_id = %d", $template_id)); + $node = array(); + $node['values'] = unserialize($data); + $parent_node = node_load($parent_info->nid); + $node['values']['book'] = array(); + $node['values']['book']['bid'] = $parent_node->book['bid']; + $node['values']['book']['plid'] = $parent_node->book['mlid']; + $node['values']['book']['options'] = array(); + + // Replace custom tokens in the title and body fields. Replace skeleton + // tokens first so they may be used as node tokens such as [title]. + if (!empty($parent_info->tokens)) { + $node['values']['title'] = token_replace($node['values']['title'], 'skeleton', $parent_info->tokens); + $node['values']['body'] = token_replace($node['values']['body'], 'skeleton', $parent_info->tokens); + } + + $node['values']['title'] = token_replace($node['values']['title'], 'node', (object)$node['values']); + $node['values']['body'] = token_replace($node['values']['body'], 'node', (object)$node['values']); + + $node['values']['uid'] = $parent_node->uid; + $node['values']['name'] = $parent_node->name; + + $node['values']['tokens'] = $parent_info->tokens; + + // We have to add the skeleton ID here as we can't save it with the + // template as a template may have multiple skeletons. + $node['values']['skeleton_id'] = $skeleton_id; + + $node['values']['op'] = t('Save'); + drupal_execute($node['values']['type'] .'_node_form', $node, (object)$node['values']); + // TODO: This will probably need to be removed if this call is integrated + // into the book module. See http://drupal.org/node/364529 for details. + menu_rebuild(); + $form_state['nids'][] = $node['nid']; + } + } + } + } +} + +/** + * Form for deleting instantiated templates where the source template has been + * deleted. + */ +function skeleton_sync_delete_template_form($form_state) { + $form = array(); + $form['delete'] = array( + '#type' => 'fieldset', + '#title' => t('Delete instantiated template nodes'), + '#collapsible' => TRUE, + ); + $form['delete']['help'] = array( + '#prefix' => '

', + '#suffix' => '

', + '#value' => t("Use this form to delete nodes where the associated template has also been deleted."), + ); + $form['delete']['nodes'] = array( + '#tree' => TRUE, + ); + + // Find all nodes where the template it was created from has been deleted. + $result = db_query("SELECT nid from {skeleton_template_node} stn LEFT JOIN {skeleton_template} st ON stn.template_id = st.template_id WHERE st.template_id IS NULL"); + $options = array(); + while ($node = node_load(db_result($result))) { + $book = node_load($node->book['bid']); + $options[$node->nid] = t('%title, in the %book-title book.', array('@node-url' => url('node/' . $node->nid), '%title' => $node->title, '@book-url' => url('node/' . $book->nid), '%book-title' => $book->title)); + } + if (!empty($options)) { + $form['delete']['nodes'] = array( + '#type' => 'checkboxes', + '#title' => t('Select the nodes you wish to delete'), + '#options' => $options, + ); + $form['delete']['submit'] = array( + '#type' => 'submit', + '#value' => t('Delete selected nodes'), + ); + } + else { + $form['delete']['#collapsed'] = TRUE; + $form['delete']['no_nodes'] = array( + '#prefix' => '

', + '#suffix' => '

', + '#value' => t('There are no nodes with deleted templates.') + ); + } + return $form; +} + +/** + * Submit handler for deleting nodes where the associated template has also + * been deleted. + */ +function skeleton_sync_delete_template_form_submit($form, &$form_state) { + $batch = array( + 'title' => t('Deleting content'), + 'init_message' => t('Content deletion is starting.'), + 'progress_message' => t('Processed @current out of @total.'), + 'error_message' => t('There was an error while updating content.'), + 'file' => drupal_get_path('module', 'skeleton'). '/skeleton_sync.inc', + ); + foreach (array_filter($form_state['values']['nodes']) as $nid) { + $batch['operations'][] = array('skeleton_sync_delete_node', array($nid)); + } + if (!empty($batch['operations'])) { + batch_set($batch); + } +} + +/** * Update an instantiated node based on an updated template. * * @param $nid @@ -114,4 +313,20 @@ node_save($node); db_query("UPDATE {skeleton_template_node} SET template_status = 'synced' WHERE nid = %d", $node->nid); $context['results'][] = t('Updated %title', array('%title' => $node->title)); -} \ No newline at end of file +} + +/** + * Delete an instantiated node. This is used to delete nodes where it's + * template has also been deleted, but could also be used to delete any node. + * + * @param $nid + * The node ID to delete. + * + * @param &$context + * The current context of the batch operation. + */ +function skeleton_sync_delete_node($nid, &$context) { + $node = node_load($nid); + node_delete($nid); + $context['results'][] = t('Deleted %title', array('%title' => $node->title)); +} Index: skeleton_template.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/skeleton/skeleton_template.inc,v retrieving revision 1.16 diff -u -r1.16 skeleton_template.inc --- skeleton_template.inc 30 Apr 2009 00:25:43 -0000 1.16 +++ skeleton_template.inc 30 Apr 2009 02:40:50 -0000 @@ -181,7 +181,6 @@ function skeleton_delete_template_submit($form, &$form_state) { db_query("DELETE from {skeleton_template} WHERE template_id = %d", $form_state['values']['template_id']); db_query("DELETE from {skeleton_data} WHERE template_id = %d", $form_state['values']['template_id']); - db_query("DELETE from {skeleton_template_node} WHERE template_id = %d", $form_state['values']['template_id']); drupal_set_message(t('Template deleted.')); drupal_goto('admin/content/skeleton/template'); } Index: skeleton.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/skeleton/skeleton.module,v retrieving revision 1.18 diff -u -r1.18 skeleton.module --- skeleton.module 12 Mar 2009 18:27:26 -0000 1.18 +++ skeleton.module 30 Apr 2009 02:40:50 -0000 @@ -71,8 +71,7 @@ 'file' => 'skeleton_admin.inc', ); $items['admin/content/skeleton/sync'] = array( - 'page callback' => 'drupal_get_form', - 'page arguments' => array('skeleton_sync_form'), + 'page callback' => 'skeleton_sync_page', 'access arguments' => array('configure skeleton outlines'), 'type' => MENU_LOCAL_TASK, 'title' => 'Synchronize', @@ -333,7 +332,7 @@ function skeleton_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) { switch ($op) { case 'load': - if ($template = db_fetch_object(db_query("SELECT skeleton_id, template_id, tokens FROM {skeleton_template_node} WHERE nid = %d", $node->nid))) { + if ($template = db_fetch_object(db_query("SELECT skeleton_id, template_id, tokens FROM {skeleton_template_node} WHERE nid = %d AND template_status != 'overridden'", $node->nid))) { $node->skeleton_template = new stdClass(); $node->skeleton_template->skeleton_id = $template->skeleton_id; $node->skeleton_template->template_id = $template->template_id; @@ -351,11 +350,13 @@ } break; case 'update': - case 'delete': if (!$node->skeleton_template->keep_connected) { - db_query("DELETE FROM {skeleton_template_node} WHERE nid = %d", $node->nid); + db_query("UPDATE {skeleton_template_node} SET template_status = '%s' WHERE nid = %d", 'overridden', $node->nid); } break; + case 'delete': + db_query("DELETE FROM {skeleton_template_node} WHERE nid = %d", $node->nid); + break; } } @@ -376,13 +377,14 @@ module_load_include('inc', 'skeleton', 'skeleton_template'); skeleton_alter_node_form($form, $form_state, $form_id); } - else if ($form_node == $form_id && arg(0) == 'admin' && arg(2) == 'skeleton' && arg(3) == 'skeleton' && arg(5) == 'create') { + else if ($form_node == $form_id && (arg(0) == 'admin' && arg(2) == 'skeleton' && arg(3) == 'skeleton' && arg(5) == 'create') + || (arg(0) == 'admin' && arg(2) == 'skeleton' && arg(3) == 'sync')) { // We need skeleton_id to be defined so forms validate properly. This value // should always be provided by the function instantiating the skeleton. $form['skeleton_id'] = array('#type' => 'hidden', '#value' => $form_state['values']['skeleton_id']); $form['template_id'] = array('#type' => 'hidden', '#value' => $form_state['values']['template_id']); $form['tokens'] = array('#type' => 'hidden', '#value' => $form_state['values']['tokens']); - } + } } /** @@ -396,7 +398,7 @@ * FALSE otherwise. */ function _skeleton_template_connected($node) { - return user_access('configure skeleton outlines') && db_result(db_query("SELECT COUNT(1) FROM {skeleton_template_node} WHERE nid = %d", $node->nid)); + return user_access('configure skeleton outlines') && db_result(db_query("SELECT COUNT(1) FROM {skeleton_template_node} WHERE nid = %d AND template_status != 'overridden'", $node->nid)); } /** Index: skeleton-admin.js =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/skeleton/skeleton-admin.js,v retrieving revision 1.2 diff -u -r1.2 skeleton-admin.js --- skeleton-admin.js 27 Jan 2009 20:06:14 -0000 1.2 +++ skeleton-admin.js 30 Apr 2009 02:40:49 -0000 @@ -10,3 +10,47 @@ return false; }); }); + +/** + * Add a checkbox to lists of nodes on the Syncronize page to select or clear + * all checkboxes at once. + */ +Drupal.behaviors.skeletonAutoCheckbox = function(context) { + var strings = { 'selectAll': Drupal.t('Select all rows in this table'), 'selectNone': Drupal.t('Deselect all rows in this table') }; + $('#skeleton-sync-content-form .form-item .form-checkboxes:not(.skeletonAutoCheckbox-processed)').prepend( + $('').html(Drupal.t('Select all nodes')).prepend($('').attr('title', strings.selectAll).click(function() { + if (this.checked) { + $('#skeleton-sync-content-form .skeletonAutoCheckbox-processed input[type=checkbox]').attr('checked', true); + } + else { + $('#skeleton-sync-content-form .skeletonAutoCheckbox-processed input[type=checkbox]').attr('checked', false); + } + })) + ); + + $('#skeleton-sync-content-form .form-item .form-checkboxes').addClass('skeletonAutoCheckbox-processed'); + + $('#skeleton-sync-add-template-form .form-item .form-checkboxes:not(.skeletonAutoCheckbox-processed)').prepend( + $('').html(Drupal.t('Select all nodes')).prepend($('').attr('title', strings.selectAll).click(function() { + if (this.checked) { + $('#skeleton-sync-add-template-form .skeletonAutoCheckbox-processed input[type=checkbox]').attr('checked', true); + } + else { + $('#skeleton-sync-add-template-form .skeletonAutoCheckbox-processed input[type=checkbox]').attr('checked', false); + } + })) + ); + $('#skeleton-sync-add-template-form .form-item .form-checkboxes').addClass('skeletonAutoCheckbox-processed'); + + $('#skeleton-sync-delete-template-form .form-item .form-checkboxes:not(.skeletonAutoCheckbox-processed)').prepend( + $('').html(Drupal.t('Select all nodes')).prepend($('').attr('title', strings.selectAll).click(function() { + if (this.checked) { + $('#skeleton-sync-delete-template-form .skeletonAutoCheckbox-processed input[type=checkbox]').attr('checked', true); + } + else { + $('#skeleton-sync-delete-template-form .skeletonAutoCheckbox-processed input[type=checkbox]').attr('checked', false); + } + })) + ); + $('#skeleton-sync-delete-template-form .form-item .form-checkboxes').addClass('skeletonAutoCheckbox-processed'); +}