Prevent WordPress from escaping shortcode attributes - wordpress

at the moment I'm developing a plugin, which hooks up to the content editor. My callback receives the post content after editing and calls do_shortcode(), but there is a problem and i don't know how to fix it.
add_filter('wp_insert_post_data', 'prepareContentSaving', 99, 2);
add_filter('wp_update_post_data', 'prepareContentSaving', 99, 2);
For instance if my post looks like (which obviously looks like valid shortcode syntax):
[foo bar="two words"]
my callback receives:
[foo bar=\"two words\"]
Looks right, right? But now whenever the shortcode is parsed via do_shortcode() the arguments are parsed like
[tag argument1=value1 argument2]
instead of
[tag argument="Foo bar"]
which then looks something like this in PHP:
array(
[0]=> string "bar=\"two"
[1]=> string "words\""
)
So how can I prevent the quotes inside the shortcode from being escaped? Is there something wrong with the post data hook? Changing the priority from 99 to 0 doesn't change something either. Am I using the right filter?

You can try to modify your code like this:
$post = array_map('stripslashes_deep', $_POST);
More info link: http://codex.wordpress.org/Function_Reference/stripslashes_deep

WordPress actually doesn't feature any option for preventing shortcodes to be escaped. The only way is to undo it is to convert all '\"' back to '"' (same for single quotes) inside the function 'prepareContentSaving':
add_filter('wp_insert_post_data', 'prepareContentSaving', 99, 2);
add_filter('wp_update_post_data', 'prepareContentSaving', 99, 2);
function prepareContentSaving($data, $post) {
$content = $post['post_content'];
$content = correctShortcodeSlashes($content);
... any further processing ...
$data['post_content'] = $content;
return $data;
}
After saving a post wordpress not only escapes quotes but also escapes backslashes. So '"' becomes '\"' and '\"' (if the editor wants to escape a quote) becomes '\\"'.
The first given PCRE converts all single escaped quotes inside shortcode brackets back to normal quotes, the second one converts all the double escaped ones inside brackets. This way the content stays the same which reduces the chances of code injection.
PHP Manual on preg_replace
function correct_shortcode_slashes($text) {
$attribute_escaped_slashes_pattern = '/(\[)((.|\s)*?)([^\\\\])\\\\("|\')(.*?)(\])/';
$attribute_escaped_slashes_replacement = '$1$2$4"$6$7';
$attribute_double_slashes_pattern = '/(\[)((.|\s)*?)\\\\+("|\')(.*?)(\])/';
$attribute_double_slashes_replacement = '$1$2"$5$6';
$result = $text;
$counter = 0;
while(true) {
$result = preg_replace($attribute_escaped_slashes_pattern, $attribute_escaped_slashes_replacement, $result, -1, $counter);
if($counter === 0) {
break;
}
}
while(true) {
$result = preg_replace($attribute_double_slashes_pattern, $attribute_double_slashes_replacement, $result, -1, $counter);
if($counter === 0) {
break;
}
}
return $result;
}
Please feel free to enhance this answer.

Related

automatically generate tags from content in wordpress

my WordPress website contents have some words in every line,
I want a code to automatically convert every line in content to tag (every line not every word), let's say my content is like this:
Beagle puppy
Costumes
Wales
Dogs
I want tags from every line: Beagle puppy, Costumes, Wales, Dogs
I don't want to use plugins, because I used some but it needs a keyword list to match the content. I don't want to use any keyword list to match the content.
is it possible to convert every line in content into one tag?
Assuming you meant what you appeared to mean - for instance, that the source post consists solely of the lines to be converted, the lines won't include additional odd/inappropriate chracters to be handled, and so on - the following will work. If you need to convert a section of a post or some other element defined in some other way, or attach the lines as tags to the current post, etc., then you'll need to provide those details clearly.
Place shortcode [convert_post_lines_to_tags] in new output post.
Save draft, and preview (shortcode won't function yet, obviously)
Add functions to your theme functions.php
Provide "$source_post_id" where indicated.
Re-load output post
add_shortcode( 'convert_post_lines_to_tags', 'convert_post_lines_to_tags' ) ;
function convert_post_lines_to_tags() {
$source_post_id = '' ; //Provide ID Number of post with lines to be converted
$i = 0 ;
$newTags = 'New tags inserted: <br />' ;
//TIL - PHP requires double quotes to replace escaped characters
$post_content = str_replace(
array( "\r\n", "\r" ), ',', get_post( $source_post_id )->post_content
) ;
$post_line_array = explode( ',', $post_content ) ;
foreach ( $post_line_array as $line_tag ) {
$tag = wp_insert_term( $line_tag, 'post_tag' ) ;
if ( ! is_wp_error( $tag ) ) {
$i++ ;
$newTags .= $i . '. ' . get_term( $tag['term_id'] )->name . '<br />' ;
}
}
return $newTags ;
}

Silverstripe create a better LimitWordCount function

The framework offer LimitWordCount in StringField.php file. It's great but not totally perfect. Some tags et ponctuation needs the be corrected.
I would like :
Keep tags <sup><sub><em>
Correct ponctuation : «.» to «. », «,» to «, », «?» to «? » etc....
I have made my own LimitWordCount :
public function MyLimitWordCount($int) {
$text = strip_tags($this->owner->value, '<sup><sub><em>');
if (str_word_count($text, 0) > $int) {
$words = str_word_count($text, 2);
$pos = array_keys($words);
$text = substr($text, 0, $pos[$int]) . '…';
}
$text = str_replace('.', '. ', $text );
$text = str_replace('!', '! ', $text );
$text = str_replace('…', '… ', $text );
$text = str_replace('?', '? ', $text );
return $text;
}
That not works totally. Some ponctuation are not corrected and video shortcodes [embed width="480...] made in an HTMLEditor fields are keep with my function. I dont' know what him doing wrong.
You'll continue to get the shortcodes in your text becuase you need to parse them first.
This is most easily resolved by using (string)$this->owner->dbObject('value') rather than $this->owner->value.
You can then manipulate the value where the shortcodes have been expanded.
I can't comment on why your punctuation is not being "corrected" in this instance as you haven't supplied much information on which punctuation and under what circumstances it isn't working.

auto excerpt from WYSIWYG

Is it possible to grab the contents of the WYSIWYG editor and save the first 100 words into the excerpt automatically? I know about excerpt_save_pre which will save the excerpt when you are in the editor, but haven't seen anything that will grab the contents from the WYSIWYG editor.
I've figured this out. The "secret" is &$_POST when the post is saved/published. That builds an array which the content can be extracted and then saved to the excerpt field using excerpt_save_pre.
I went a little further allowing control over the number of characters or the number of words, using $length, and the output is controlled on which $output section you uncomment.
The code below tested on my vanilla site as working.
function auto_insert_excerpt(){
$post_data = &$_POST;
$post_content = $post_data['content'];
$length = 15;
// This will return the first $length number of CHARACTERS
//$output = (strlen($post_content) > 13) ? substr($post_content,0,$length).'...' : $post_content;
// This will return the first $length number of WORDS
$post_content_array = explode(' ',$post_content);
if(count($post_content_array) > $length && $length > 0)
$output = implode(' ',array_slice($post_content_array, 0, $length)).'...';
return $output;
}
add_filter('excerpt_save_pre', 'auto_insert_excerpt');

Wordpress - Excerpt character alternative?

I'm totally new to WordPress so be easy :)
I the following code in a template:
<?php excerpt(20);?>
What this does is limit the text with 20 words. I am now wondering if there is some sort of similar function that limits by characters instead of words?
Thanks!
I use this:
add_filter('excerpt_length', 'my_excerpt_length');
function my_excerpt_length($length) {
return '500';
}
function better_excerpt($limit, $id = '') {
global $post;
if($id == '') $id = $post->ID;
else $id = $id;
$postinfo = get_post($id);
if($postinfo->post_excerpt != '')
$post_excerpt = $postinfo->post_excerpt;
else
$post_excerpt = $postinfo->post_content;
$myexcerpt = explode(' ', $post_excerpt, $limit);
if (count($myexcerpt) >= $limit) {
array_pop($myexcerpt);
$myexcerpt = implode(' ',$myexcerpt).'...';
} else {
$myexcerpt = implode(' ',$myexcerpt);
}
$myexcerpt = preg_replace('`\[[^\]]*\]`','',$myexcerpt);
$stripimages = preg_replace('/<img[^>]+\>/i', '', $myexcerpt);
return $stripimages;
}
And then in my theme file, I just call it in with:
better_excerpt('50') //50 being how many words I want
Useful for custom plugins/widgets too.
Wordpress doesn't support the character delimiter for the excerpt method, there's a plugin called Advanced Excerpt that does. After installing you can call the_advanced_excerpt('length=20&use_words=0')
I use this in my functions.php:
function truncate ($str, $length=10, $trailing='...'){
// take off chars for the trailing
$length-=mb_strlen($trailing);
if (mb_strlen($str)> $length){
// string exceeded length, truncate and add trailing dots
$str = mb_substr($str,0,$length);
$str = explode('. ',$str);
for( $i=0; $i<(sizeof($str)-2); $i++ ):
$newstr .= $str[$i].". ";
endfor;
return $newstr;
} else{
// string was already short enough, return the string
$res = $str;
}
return $res;
}
It should truncate to a character count, but then truncate back further to the last period before the truncation. It does get problematic when your excerpt includes links, however, or other markup - in other words, it's best to use the Excerpt field in the post rather than auto-excerpting with this function, because you can't use HTML in the excerpt field.
Please use this code for limiting post content...
<?php substr($post->post_content, 0, xy); ?> ...
Change the limit of XY....

Drupal module_invoke() and i18n

I am tasked with i18n-ing our current CMS setup in Drupal.
The problem that I am facing is with use of module_invoke() to place blocks within nodes.
I have managed to string translate blocks, and that is working when a block is placed in a region (block content is successfully translated) using the UI.
However, when a block is injected into a node like such:
$block = module_invoke('block', 'block', 'view', 22); print $block['content'];
It is not getting translated, or even worse, not showing at all.
I have also tried this variation using t(). e.g.:
$block = module_invoke('block', 'block', 'view', 22); print t($block['content']);
to no avail.
Generally speaking I've having a bit of trouble with blocks for i18n. Does anyone have a recommended approach for dealing with blocks in drupal with regards to translating them? I would prefer not to create different blocks for each language.
So .. After digging around in the bowels of Drupal - and much hair pulling .. I've come up with an almost decent solution.
Basically, with this function, I can extract a translated version of a block:
function render_i18n_block($block_id, $region = "hidden"){
if ($list = block_list($region)) {
foreach ($list as $key => $block) {
// $key == <i>module</i>_<i>delta</i>
$key_str = "block_".$block_id;
if ($key_str == $key){
return theme('block', $block);
}
}
}
}
Then, in my node, I simple call:
<?php echo render_i18n_block(<block_id>,<region>); ?>
There can be some issues where your blocks might not be displaying in a region (and therefore you can't pass a region into block_list). For this case, I simply created a region called "hidden" which is not rendered anywhere in my template, but can be used to call block_list.
Finally (and this is the part that I still need to find a good solution for), I discovered that block_list() in: includes/blocks/block.inc has a bit of an issue.
It appears that $theme_key is not reliably set unless block_list() is being called from the theme() function (in includes/themes.inc) .. this causes the SQL to return an empty results set. The SQL looks like this:
$result = db_query(db_rewrite_sql("SELECT DISTINCT b.* FROM {blocks} b LEFT JOIN {blocks_roles} r ON b.module = r.module AND b.delta = r.delta WHERE b.theme = '%s' AND b.status = 1 AND (r.rid IN (". db_placeholders($rids) .") OR r.rid IS NULL) ORDER BY b.region, b.weight, b.module", 'b', 'bid'), array_merge(array($theme_key), $rids));
As you can see, if theme_key is not set, then it will just return an empty result.
For now I am bypassing this by simply adding:
if (!isset($theme_key)){$theme_key="<my_theme_name>";}
in modules/blocks/block.inc::block_list() around line 429 .. I still need to work out a better way to do this.
10 for anyone with suggestions on how I could ensure that $theme_key is set before calling block_list :)
I had exactly the same problem as you, since I was using
$block = module_invoke('block', 'block_view', 'block_id');
print render($block['content']);
to inject the block into my nodes. However, looking up module_invoke in the Drupal reference, I found a comment titled "to render blocks in Drupal 7 better to use Block API", with this code:
function block_render($module, $block_id) {
$block = block_load($module, $block_id);
$block_content = _block_render_blocks(array($block));
$build = _block_get_renderable_array($block_content);
$block_rendered = drupal_render($build);
return $block_rendered;
}
I just un-functioned it to use directly, like so:
$block = block_load('block', 'block_id');
$block_content = _block_render_blocks(array($block));
$build = _block_get_renderable_array($block_content);
print render($build);
And for me it works like a charm. Be aware however that this method prints the block title as well, so maybe you'll want to set it to 'none' in the original language.
Create a function like this
<?php
function stg_allcontent2($allC, $level
= "1") {
global $language; $lang = $language->language;
foreach ($allC as $acKey => $ac) {
if($ac['link']['options']['langcode']
== $lang){ if ($level == "1")
$toR .= "";
if (is_array($ac['below']))
$class="expanded"; else
$class="leaf";
$toR .= "<li class=\"".$class."\">" . l($ac['link']['link_title'], $ac['link']['link_path']) . "</li>";
if ($level != "1") $toR .= ""; if (is_array($ac['below'])) $toR .= "<ul class=\"menu\">".stg_allcontent2($ac['below'], "2")."</ul>"; if ($level == "1") $toR .= ""; }
}
return $toR; } ?>
call like this
<?php echo '<ul class="menu">'; echo stg_allcontent2(menu_tree_all_data($menu_name
= 'menu-header', $item = NULL)); echo '</ul>'; ?>
This may help you: http://drupal-translation.com/content/translating-block-contents#
UPDATE: the t() function allows you to pass in the language code to use.

Resources