add('name', PLUGIN_EVENT_NL2BR_NAME); $propbag->add('description', PLUGIN_EVENT_NL2BR_DESC); $propbag->add('stackable', false); $propbag->add('author', 'Serendipity Team, Stephan Brunker'); $propbag->add('version', '2.21.11'); $propbag->add('requirements', array( 'serendipity' => '1.6', 'smarty' => '2.6.7', 'php' => '4.1.0' )); $propbag->add('cachable_events', array('frontend_display' => true)); $propbag->add('event_hooks', array('frontend_display' => true, 'backend_configure' => true, 'css' => true )); $propbag->add('groups', array('MARKUP')); $this->markup_elements = array( array( 'name' => 'ENTRY_BODY', 'element' => 'body', ), array( 'name' => 'EXTENDED_BODY', 'element' => 'extended', ), array( 'name' => 'COMMENT', 'element' => 'comment', ), array( 'name' => 'HTML_NUGGET', 'element' => 'html_nugget', ) ); $conf_array = array('check_markup', 'isolate', 'p_tags', 'isobr', 'clean_tags'); foreach($this->markup_elements as $element) { $conf_array[] = $element['name']; } $propbag->add('configuration', $conf_array); } function cleanup() { global $serendipity; /* check possible config mismatch setting in combination with ISOBR */ if ( serendipity_db_bool($this->get_config('isobr')) === true ) { if( serendipity_db_bool($this->get_config('clean_tags')) === true ) { $this->set_config('clean_tags', false); echo '
'; echo sprintf(PLUGIN_EVENT_NL2BR_CONFIG_ERROR, 'clean_tags', 'ISOBR') . '
'; return false; } if ( serendipity_db_bool($this->get_config('p_tags')) === true ) { $this->set_config('p_tags', false); echo '
'; echo sprintf(PLUGIN_EVENT_NL2BR_CONFIG_ERROR, 'p_tags', 'ISOBR') . '
'; return false; } } /* check possible config mismatch setting in combination with P_TAGS */ if ( serendipity_db_bool($this->get_config('p_tags')) === true && serendipity_db_bool($this->get_config('clean_tags')) === true ) { $this->set_config('clean_tags', false); echo '
'; echo sprintf(PLUGIN_EVENT_NL2BR_CONFIG_ERROR, 'clean_tags', 'P_TAGS') . '
'; return false; } return true; } function example() { return '

PLEASE NOTE the implications of this markup plugin:

This plugin transfers linebreaks to HTML-linebreaks, so that they show up in your blog entry.

In two cases this can raise problematic issues for you:

To prevent problems, you should disable the nl2br plugin on entries globally or per entry within the "Extended properties" section of an entry, if you have the entryproperties plugin installed.

Generally advice: The nl2br plugin only makes sense if you

'."\n"; } function install() { serendipity_plugin_api::hook_event('backend_cache_entries', $this->title); } function uninstall(&$propbag) { serendipity_plugin_api::hook_event('backend_cache_purge', $this->title); serendipity_plugin_api::hook_event('backend_cache_entries', $this->title); } function generate_content(&$title) { $title = $this->title; } function introspect_config_item($name, &$propbag) { switch($name) { case 'check_markup': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_EVENT_NL2BR_CHECK_MARKUP); $propbag->add('description', PLUGIN_EVENT_NL2BR_CHECK_MARKUP_DESC); $propbag->add('default', 'true'); break; case 'isolate': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_EVENT_NL2BR_ISOLATE_TAGS); $propbag->add('description', PLUGIN_EVENT_NL2BR_ISOLATE_TAGS_DESC); $propbag->add('default', ''); break; case 'p_tags': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_EVENT_NL2BR_PTAGS); $propbag->add('description', PLUGIN_EVENT_NL2BR_PTAGS_DESC); $propbag->add('default', 'false'); break; case 'isobr': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_EVENT_NL2BR_ISOBR_TAG); $propbag->add('description', PLUGIN_EVENT_NL2BR_ISOBR_TAG_DESC); $propbag->add('default', 'true'); break; case 'clean_tags': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_EVENT_NL2BR_CLEANTAGS); $propbag->add('description', PLUGIN_EVENT_NL2BR_CLEANTAGS_DESC); $propbag->add('default', 'false'); break; default: $propbag->add('type', 'boolean'); $propbag->add('name', constant($name)); $propbag->add('description', sprintf(APPLY_MARKUP_TO, constant($name))); $propbag->add('default', 'true'); break; } return true; } function isolate($src, $regexp = NULL) { if($regexp) return preg_replace_callback($regexp, array($this, 'isolate'), $src); global $_buf; $_buf[] = $src[0]; return "\001" . (count($_buf) - 1); } function restore_callback($matches) { global $_buf; return $_buf[$matches[1]]; } function restore($text) { return preg_replace_callback('!\001(\d+)!', array($this, 'restore_callback'), $text); // works?! } function event_hook($event, &$bag, &$eventData, $addData = null) { global $serendipity; static $markup = null; static $isolate = null; static $p_tags = null; static $isobr = null; static $clean_tags = null; global $_buf; $hooks = &$bag->get('event_hooks'); if ($markup === null) { $markup = serendipity_db_bool($this->get_config('check_markup')); } if ($p_tags === null) { $p_tags = serendipity_db_bool($this->get_config('p_tags')); } if ($isobr === null) { $isobr = serendipity_db_bool($this->get_config('isobr')); } if ($clean_tags === null) { $clean_tags = serendipity_db_bool($this->get_config('clean_tags')); } if (isset($hooks[$event])) { switch($event) { case 'frontend_display': // check single entry for temporary disabled markups if ( !($eventData['properties']['ep_disable_markup_' . $this->instance] ?? null) && @!in_array($this->instance, ($serendipity['POST']['properties']['disable_markups'] ?? [])) && !($eventData['properties']['ep_no_textile'] ?? null) && !isset(($serendipity['POST']['properties']['ep_no_textile'])) && !($eventData['properties']['ep_no_markdown'] ?? null) && !isset($serendipity['POST']['properties']['ep_no_markdown'])) { // yes, this markup shall be applied $serendipity['nl2br']['entry_disabled_markup'] = false; } else { // no, do not apply markup $serendipity['nl2br']['entry_disabled_markup'] = true; } /* PLEASE NOTE: $serendipity['POST']['properties']['disable_markups'] = array(false); is the only workable solution for (sidebar?) plugins (see sidebar plugins: guestbook, multilingual), to explicitly allow to apply nl2br to markup (if we want to) */ // don't run, if the textile, or markdown plugin already took care about markup if ($markup && $serendipity['nl2br']['entry_disabled_markup'] === false && (class_exists('serendipity_event_textile') || class_exists('serendipity_event_markdown'))) { break; } // NOTE: the wysiwyg-editor needs to send its own ['properties']['ep_no_nl2br'] to disable the nl2br() parser! // check for users isolation tags if ($isolate === null) { $isolate = $this->get_config('isolate'); $tags = (array)explode(',', $isolate); $isolate = array(); foreach($tags AS $tag) { $tag = trim($tag); if (!empty($tag)) { $isolate[] = $tag; } } if (count($isolate) < 1) { $isolate = false; } } $this->isolationtags = $isolate; //delete isolation tags from other arrays if ($this->isolationtags) { $this->block_elements = array_diff($this->block_elements,$this->isolationtags); $this->allowed_p_parents = array_diff($this->allowed_p_parents,$this->isolationtags); $this->nested_block_elements = array_diff($this->nested_block_elements,$this->isolationtags); $this->inline_elements = array_diff($this->inline_elements,$this->isolationtags); $this->singleton_block_elements = array_diff($this->singleton_block_elements,$this->isolationtags); $this->ignored_elements = array_diff($this->ignored_elements,$this->isolationtags); $this->isolation_block_elements = array_diff($this->isolation_block_elements,$this->isolationtags); } foreach ($this->markup_elements as $temp) { if (serendipity_db_bool($this->get_config($temp['name'], true)) && isset($eventData[$temp['element']]) && !($eventData['properties']['ep_disable_markup_' . $this->instance] ?? null) && @!in_array($this->instance, ($serendipity['POST']['properties']['disable_markups'] ?? [])) && !($eventData['properties']['ep_no_nl2br'] ?? null) && !isset($serendipity['POST']['properties']['ep_no_nl2br'])) { $element = $temp['element']; if ($p_tags) { // NL2P OPERATION $text = $eventData[$element]; if (!empty($text)) { //Standardize line endings to Unix $text = str_replace(array("\r\n", "\r"), "\n", $text); //framing newlines: pre and after everything //without newline margin = 0 between body and extended //to make splitting inside a paragraph possible //but with obligatory break because of the independent div-elements // rules for body <-> extended: // no margins only if body ends without \n and extended starts without \n // means: concatenate body and extended and there is no whiteline between them if ($element == 'body' && isset($eventData['extended']) && !strspn($text,"\n",-1) && !strspn($eventData['extended'],"\n") ) { $text = "\n" . $text; } elseif ($element == 'extended' && !strspn($text,"\n") && !(strspn($eventData['body'],"\n",-1) > 1)) { $text = $text . "\n"; } else { $text = "\n" . $text . "\n"; } $eventData[$element] = $this->nl2p($text); } // NL2BR OPERATION } else if ($isolate) { $eventData[$element] = $this->isolate($eventData[$element], '~[<\[](' . implode('|', $isolate) . ').*?[>\]].*?[<\[]/\1[>\]]~si'); $eventData[$element] = nl2br($eventData[$element]); $eventData[$element] = $this->restore($eventData[$element]); } else { if($isobr) { $eventData[$element] = $this->isolate($eventData[$element], '~[<\[](nl).*?[>\]].*?[<\[]/\1[>\]]~si'); $eventData[$element] = nl2br($eventData[$element]); $eventData[$element] = $this->restore($eventData[$element]); // unset nl tagline, if is $eventData[$element] = str_replace(array("", "
", "
", ""), "", $eventData[$element]); } else { $eventData[$element] = nl2br($eventData[$element]); } } /* this is an option if not using new isobr default config setting */ if (!$p_tags && $isobr === false && $clean_tags === true) { // convert line endings to Unix style, if not already done $eventData[$element] = str_replace(array("\r\n", "\r"), "\n", $eventData[$element]); // clean special tags from nl2br $eventData[$element] = $this->clean_nl2brtags($eventData[$element]); } } } break; case 'backend_configure': // check single entry for temporary disabled markups if( $isobr ) { $serendipity['nl2br']['iso2br'] = true; // include to global as also used by staticpages now if (!is_object($serendipity['smarty'] ?? null)) { serendipity_smarty_init(); // if not set to avoid member function assign() on a non-object error, start Smarty templating } // hook into default/admin/entries.tpl somehow via the Heart Of Gold = serendipity_printEntryForm() before! it is loaded $serendipity['smarty']->assign('iso2br', true); } break; case 'css': $eventData .= ' /* nl2br plugin start */ p.wl_nobottom { margin-bottom: 0em; } p.wl_notop { margin-top: 0em; } p.wl_notopbottom { margin-top: 0em; margin-bottom: 0em; } /* nl2br plugin end */ '; break; default: return false; } return true; } else { return false; } } /** * clean nl2br from markup where it is invalid and/or breaks html output * @param string entrytext * @return string */ function clean_nl2brtags(&$entry) { $allTags = explode('|', 'table|thead|tbody|tfoot|th|tr|td|caption|colgroup|col|ol|ul|li|dl|dt|dd'); $br2nl = array(); foreach($allTags as $tag){ /* for \\1 ( start with : < followed by any number of white spaces : \s* optionally a slash : /? and the tag itself ) * for \\2 ( anything with spaces and characters following until ) * for \\3 ( finally the > ) * for \\4 (
) * regex modifier : i - using a case-insensitive match, as upper are valid in HTML * regex modifier : s - using the dot metacharacter in the pattern to match all characters, including newlines */ $br2nl[] = "%(<\s*/?$tag)(.*?)([^>]*>)()%is"; } if(sizeof($br2nl)) $entry = preg_replace($br2nl, '\\1\\2\\3', $entry); return $entry; } /** ==================================== * NL2P OPERATION * ==================================== */ // following w3.org, these elements close p elements automatically: var $block_elements = array('table', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'menu', 'section', 'address', 'article', 'aside', 'fieldset', 'footer', 'form', 'header', 'hgroup', 'hr', 'main', 'nav', 'p' ); var $nested_block_elements = array('div','table','blockquote','ul','ol','dl','figure'); var $singleton_block_elements = array('hr'); var $isolation_block_elements = array('pre','textarea'); var $isolation_inline_elements = array('svg','style'); var $ignored_elements = array('a','area', 'br', 'col', 'command', 'embed', 'img', 'input', 'keygen', 'link', 'param', 'source', 'track', 'wbr', 'iframe', 'li','tr','th','col','colgroup', 'thead', 'tbody', 'tfoot', 'caption', 'figcaption', 'ins','del', 'video','audio','title','desc','path','circle', 'ellipse', 'rect', 'line', 'polyline', 'polygon', 'text', 'image', 'g', 'defs'); //includes svg tags //paragraphs aren't allowed in these inline elements -> p closes these elements: var $inline_elements = array('b', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'cite', 'code', 'dfn', 'em', 'kbd', 'strong', 'samp', 'var', 'bdo', 'bdi', 'map', 'object', 'q', 'script', 'span', 'sub', 'sup', 'button', 'label', 'select', 'textarea', 's', 'strike', 'u' ); var $allowed_p_parents = array('blockquote', 'td', 'div', 'article', 'aside', 'dd', 'details', 'dl', 'dt', 'footer', 'header', 'summary' ); const P_END = '

'; const P = '

'; const P_NOTOP = '

'; const P_NOBOTTOM = '

'; const P_NOTOPBOTTOM = '

'; /** splits a string into three parts: * before, comment and after * after can contain more comments, * so use in a loop */ function splitcomment($text,&$pre,&$comment,&$after) { $commentstartpos = strpos($text,'', $commentstartpos + 4); $comment = substr($text, $commentstartpos, $commentendpos - $commentstartpos + 3); $after = substr($text, $commentendpos + 3); return true; } else { $pre = ''; $comment = ''; $after = ''; return false; } } /** Make sure that all the tags are in lowercase * purge all \n from inside tags * remove spaces in endtags * replace < > with < $gt; for non-tags * tags are split in three parts: * tagstart - '<' character * tagdef - type of tag like 'img' * style - following content after a space * isolation by quotes in the style part * @param string text * @return text */ function tag_clean($textstring) { $text = str_split($textstring); $tagstart = false; $tagstart_b = false; $tagdef = false; $endtag = false; $tagstyle = false; $singlequote = false; $doublequote = false; $comment = false; for ($i = 0; $i < count($text); $i++) { // start and end comment if ( substr($textstring, $i, 4) == '', $i + 4) ) { $comment = true; } elseif ( $comment && substr($textstring, $i, 3) == '-->') { $comment = false; $i += 3; } if ( !$comment ) { // start tag without closing tag if ($text[$i] == '<' && !strpos($textstring,'>',$i+1) ) { $text[$i] = '<'; } // end tag without previous start, definition or style section elseif ($text[$i] == '>' && !($tagstart !== false || $tagdef || $tagstyle) ) { $text[$i] = '>'; } // start tag inside quotes elseif ( $text[$i] == '<' && ($singlequote || $doublequote) ) { $text[$i] = '<'; } // end tag inside quotes elseif ( $text[$i] == '>' && ($singlequote || $doublequote) ) { $text[$i] = '>'; } // start tag inside tag elseif ($text[$i] == '<' && $tagstart !== false ) { $text[$i] = '<'; } // real start tag elseif ($text[$i] == '<' ) { $tagstart = $i; } // space after the start - not allowed in html elseif ($text[$i] == ' ' && $tagstart !== false ) { $text[$tagstart] = '<'; $tagstart = false; } // < > without content elseif ($text[$i] == '>' && $tagstart !== false ) { $text[$tagstart] = '<'; $text[$i] = '>'; } // first space or closing tag in definition part elseif ( ($text[$i] == ' ' || $text[$i] == '>') && $tagdef) { //check if it is a real tag $tag = substr($textstring,$tagdef,$i-$tagdef); if ( !(in_array($tag,$this->block_elements) || in_array($tag,$this->singleton_block_elements) || in_array($tag,$this->inline_elements) || in_array($tag,$this->allowed_p_parents) || in_array($tag,$this->isolation_block_elements) || in_array($tag,$this->isolation_inline_elements) || in_array($tag,$this->nested_block_elements) || in_array($tag,$this->ignored_elements) || in_array($tag,$this->isolationtags) ) ) { // unknown tag definition $text[$tagstart_b] = '<'; $tagstart = false; $tagdef = false; if ( $text[$i] == '>' ) { $text[$i] = '>'; } } else { //convert to lowercase for ($j = $tagdef; $j <= $i; $j++) { $text[$j] = strtolower($text[$j]); } $tagdef = false; // closing > if ($text[$i] == '>') { $tagstart = false; $tagstyle = false; $endtag = false; } // start of style part else { $tagstyle = true; } } } // endtag starting with elseif ($text[$i] == ' ' && $endtag) { $text[$i] = ''; } // remove newline in tags elseif (($tagstart !== false || $tagdef || $tagstyle) && $text[$i] == "\n") { $text[$i] = ''; } // closing > after style part elseif ($text[$i] == '>' && $tagstyle && !($singlequote || $doublequote) ) { $tagstart = false; $tagdef = false; $tagstyle = false; $endtag = false; } // first definition character after < elseif ($tagstart !== false && !($tagdef || $tagstyle) ) { $tagdef = $i; $tagstart_b = $tagstart; $tagstart = false; } // quotes in style - isolate elseif ($tagstyle && $text[$i] == '\'' && !$doublequote ) { if ($singlequote) { $singlequote = false; } else { $singlequote = true; } } elseif ($tagstyle && $text[$i] == '"' && !$singlequote ) { if ($doublequote) { $doublequote = false; } else { $doublequote = true; } } } } return implode($text); } /* * sophisticated nl to p - blocktag stage * handles content with blocktags, apply nl2p to the block elements if tag allows it * works also for ommitted closing tags * @param: text * return string */ function nl2p($otext) { // homogenize tags $text = $this->tag_clean($otext); if (empty($text)) { return ''; } // explode in array of normal content and comments $commentarray = array(); $pre = ''; $comment = ''; $after = ''; do { if ($this->splitcomment($text,$pre,$comment,$after)) { $commentarray[] = $pre; } else { $commentarray[] = $text; } if ($comment) { $commentarray[] = $comment; } $text = $after; } while ( !empty($after) ); $textarray = array(); //explode content elements into array of tags and contents $errorflag = false; foreach ($commentarray as $text) { if (substr($text, 0, 4) == '