type) && section_edit_header_style($node->format)) { // Get section number from url $section = isset($_GET['section']) ? $_GET['section'] : NULL; switch ($op) { case 'presave': // Node is being submitted to database. Is only a section edited? if (!is_null($section)) { // Make sure two newlines are at the end of the body. This gives some space to the next header. $new_section_text = rtrim($node->body) ."\n\n"; // Get original content from database $original_body = db_result(db_query('SELECT body FROM {node_revisions} WHERE vid = %d', $node->vid)); // Extract the old section text from the original body list($section_header, $section_text) = section_edit_extract_section($original_body, $node->format, $section); // Replace the old section text with the new one $node->body = str_replace($section_text, $new_section_text, $original_body); // Set teaser from the whole node body. At the moment the teaser was generated only from the edited part $node->teaser = node_teaser($node->body, $node->format); } break; case 'prepare': // Node is being shown in edit form. Is only a section edited? if (!is_null($section)) { // Extract the section which is edited. list($section_header, $section_text) = section_edit_extract_section($node->body, $node->format, $section); // Was the section valid? if ($section_text) { // Replace the body which is being edited with the section text. $node->body = $section_text; // Set the global variable to the header. This is used to set the log message in hook_form_alter(). $_section_edit_header = $section_header; $node->teaser = node_teaser($node->body, $node->format); } } break; case 'view': // Node is being shown as HTML. Is current user allowed to edit? if (node_access('update', $node)) { // Add css style sheet for edit links drupal_add_css(drupal_get_path('module', 'section_edit') .'/section_edit.css', 'module', 'all', FALSE); // Set the global variables which are used in the 'preg_replace_callback' callback. $_section_edit_current_section = 0; $_section_edit_nid = $node->nid; // Add edit links to all headers $node->content['body']['#value'] = preg_replace_callback('#<(h\d)(\s[^>]*)?>(.*?)#', 'section_edit_insert_link', $node->content['body']['#value']); } break; } } } /** * Callback of preg_replace_calback for inserting edit links in headers. */ function section_edit_insert_link($match) { global $_section_edit_current_section, $_section_edit_nid; // Construct edit link and append it to header $link = l('edit', "node/$_section_edit_nid/edit", array('attributes' => array('class' => 'section-edit'), 'query' => "section=$_section_edit_current_section")); $_section_edit_current_section++; return $link .' '. $match[0]; } /** * Extract section from text * * @param $text * text to extract section from * @param $format * input format used * @param $nr * the number of which section to extract. it is zero-based and the sections are numbered flat */ function section_edit_extract_section($text, $format, $nr) { switch (section_edit_header_style($format)) { case 'html': $match = preg_split('#(]*>(.*?))#m', $text, -1, PREG_SPLIT_DELIM_CAPTURE); if (count($match) > 0) { // Match 0 is before the first header unset($match[0]); // The match contains three parts of the regexp and the final text, split it in groups of four. $sections = array_chunk($match, 4); if (isset($sections[$nr])) { // match 0 is the header // match 1 is the header number (1-6) // match 2 is the text of the header // match 3 the splitted text $section_header = $sections[$nr][0]; $section_depth = $sections[$nr][1]; $section_text = $sections[$nr][0] . $sections[$nr][3]; // Append the text of all subsections // The subsection have more equal signs (higher depth) $i = $nr + 1; while ($i < count($sections) && $sections[$i][1] > $section_depth) { $section_text .= $sections[$i][0] . $sections[$i][3]; $i++; } return array( $section_header, $section_text ); } } break; case 'mediawiki': // Split text at headers $match = preg_split('/^((={1,6})(.*?)\2(?:\s|$))/m', $text, -1, PREG_SPLIT_DELIM_CAPTURE); if (count($match) > 0) { // Match 0 is before the first header unset($match[0]); // The match contains three parts of the regexp and the final text, split it in groups of four. $sections = array_chunk($match, 4); if (isset($sections[$nr])) { // match 0 is the header // match 1 is the string of equal signs // match 2 is the text of the header // match 3 the splitted text $section_header = $sections[$nr][0]; $section_depth = strlen($sections[$nr][1]); $section_text = $sections[$nr][0] . $sections[$nr][3]; // Append the text of all subsections // The subsection have more equal signs (higher depth) $i = $nr + 1; while ($i < count($sections) && strlen($sections[$i][1]) > $section_depth) { $section_text .= $sections[$i][0] . $sections[$i][3]; $i++; } return array( $section_header, $section_text ); } } break; case 'creole': // Split text at headers $match = preg_split('/^((={1,6}) *(.*?) *=*)$/m', $text, -1, PREG_SPLIT_DELIM_CAPTURE); if (count($match) > 0) { // Match 0 is before the first header unset($match[0]); // The match contains three parts of the regexp and the final text, split it in groups of four. $sections = array_chunk($match, 4); if (isset($sections[$nr])) { // match 0 is the header // match 1 is the string of equal signs // match 2 is the text of the header // match 3 the splitted text $section_header = $sections[$nr][0]; $section_depth = strlen($sections[$nr][1]); $section_text = $sections[$nr][0] . $sections[$nr][3]; // Append the text of all subsections // The subsection have more equal signs (higher depth) $i = $nr + 1; while ($i < count($sections) && strlen($sections[$i][1]) > $section_depth) { $section_text .= $sections[$i][0] . $sections[$i][3]; $i++; } return array( $section_header, $section_text ); } } break; case 'dokuwiki': // Split text at headers $match = preg_split('/^(={2,6}) (.*?) \1$/m', $text, -1, PREG_SPLIT_DELIM_CAPTURE); if (count($match) > 0) { // Match 0 is before the first header unset($match[0]); // The match contains three parts of the regexp and the final text, split it in groups of four. $sections = array_chunk($match, 4); if (isset($sections[$nr])) { // match 0 is the header // match 1 is the string of equal signs // match 2 is the text of the header // match 3 the splitted text $section_header = $sections[$nr][0]; $section_depth = 6 - strlen($sections[$nr][1]); $section_text = $sections[$nr][0] . $sections[$nr][3]; // Append the text of all subsections // The subsection have more equal signs (higher depth) $i = $nr + 1; while ($i < count($sections) && (6 - strlen($sections[$i][1])) > $section_depth) { $section_text .= $sections[$i][0] . $sections[$i][3]; $i++; } return array( $section_header, $section_text ); } } break; } return array(NULL, NULL); } /** * Implementation of hook_form_alter(). */ function section_edit_form_alter(&$form, $form_state, $form_id) { global $_section_edit_header; if ($form_id == 'node_type_form' && isset($form['identity']['type']) && module_exists('comment')) { // Node type edit form: Add checkbox to activate per section editing $form['workflow']['section_edit'] = array( '#type' => 'checkbox', '#title' => t('Display edit links for single sections'), '#prefix' => ''. t('Section edit') .'', '#weight' => 5, '#default_value' => section_edit_activated($form['#node_type']->type), ); } elseif (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id) { // Node edit form: Set log message if only a section is edited. if ($_section_edit_header) { $form['log']['#default_value'] = $_section_edit_header; } } elseif ($form_id == 'filter_admin_configure') { // Filter configuration form: Add selection of header syntax type // The fourth path entry is the format id (admin/settings/filter/#nr/configure) $format_id = arg(3); $form['section_edit'] = array( '#type' => 'fieldset', '#title' => t('Section edit type'), '#weight' => -4, '#description' => t('Section editing must be activated per node type. The setting here will be ignored on node types where section edit is not active.') ); $form['section_edit']["section_edit_header_style_$format_id"] = array( '#type' => 'radios', '#title' => 'Header style', '#options' => array( 'off' => t('Off: No section edit links'), 'html' => t('HTML: Use this for HTML or WYSIWYG formats.'), 'mediawiki' => t('Mediawiki: Use this for Mediawiki format'), 'dokuwiki' => t('Dokuwiki: Use this for DokuWiki format'), 'creole' => t('Creole: Use this for Creole format'), ), '#default_value' => variable_get("section_edit_header_style_$format_id", 'off'), '#description' => t('Select the header style for this input format. The header style will be used to insert edit links for single sections.') ); } } /* * Settings */ /** * Are section edit links activated for this node type? * * @param $node_type * Node type string */ function section_edit_activated($node_type, $value = NULL) { if (is_null($value)) { return variable_get("section_edit_$node_type", FALSE); } variable_set("section_edit_$node_type", $value); } /** * Header style for a specific input format. * * @param $format * The ID of the input format. */ function section_edit_header_style($format, $value = NULL) { if (is_null($value)) { return variable_get("section_edit_header_style_$format", 'html'); } variable_set("section_edit_header_style_$format"); }