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[^>]*)?>(.*?)\1>#', '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");
}