Qtranslate Slug : Same Slugs, different Parents a.k.a. the dreaded -2

Have you ever tried to name two different blog posts the same? WordPress will add an „-2“ to your slug. This is because slugs for posts must be unique in order to work correctly. Pages however are hierarchical, so that the same slug can be used more than once, but only if they are in different „paths“ (they have different parent pages).

Qtranslate Slug uses its own validation for its slugs. But here, there is no difference between posts and pages. Let’s see if we can change that 😉

Step 1) open the functions.php in an text editor
Step 2) insert the following function:

add_filter('after_setup_theme','improve_qts');
function improve_qts(){
global $qtranslate_slug;
remove_filter( 'qts_validate_post_slug', array($qtranslate_slug, 'unique_post_slug'), 1, 3 );
add_filter( 'qts_validate_post_slug', 'my_unique_post_slug', 1, 3 );
}

function my_unique_post_slug( $slug, $post, $lang ) {

$original_status = $post->post_status;

if ( in_array($post->post_status, array('draft', 'pending')) ) {
$post->post_status = 'publish';
}

$slug = my_wp_unique_post_slug( $slug, $post->ID, $post->post_status, $post->post_type, $post->post_parent, $lang );

$post->post_status = $original_status;

return $slug;
}

function my_wp_unique_post_slug( $slug, $post_ID, $post_status, $post_type, $post_parent, $lang ) {
if ( in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) ) {
return $slug;
}

global $wpdb, $wp_rewrite;

$feeds = $wp_rewrite->feeds;
if ( ! is_array( $feeds ) ) {
$feeds = array();
}
global $qtranslate_slug;
$meta_key = $qtranslate_slug->get_meta_key($lang);
if ( 'attachment' == $post_type ) {
// Attachment slugs must be unique across all types.
$check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND ID != %d LIMIT 1";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_ID ) );

if ( $post_name_check || in_array( $slug, $feeds ) || apply_filters( 'wp_unique_post_slug_is_bad_attachment_slug', false, $slug ) ) {
$suffix = 2;
do {
$alt_post_name = substr ($slug, 0, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
$post_name_check = $wpdb->get_var( $wpdb->prepare($check_sql, $alt_post_name, $post_ID ) );
$suffix++;
} while ( $post_name_check );
$slug = $alt_post_name;
}
} elseif ( is_post_type_hierarchical( $post_type ) ) {
if ( 'nav_menu_item' == $post_type )
return $slug;
// Page slugs must be unique within their own trees. Pages are in a separate
// namespace than posts so page slugs are allowed to overlap post slugs.
$check_sql = "SELECT $wpdb->postmeta.meta_value FROM $wpdb->posts,$wpdb->postmeta WHERE $wpdb->posts.ID = $wpdb->postmeta.post_id AND $wpdb->postmeta.meta_key = '%s' AND $wpdb->postmeta.meta_value = '%s' AND post_type = '%s' AND ID != %d AND post_parent = %d LIMIT 1";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $meta_key, $slug, $post_type, $post_ID, $post_parent ) );

if ( $post_name_check || in_array( $slug, $feeds ) || preg_match( "@^($wp_rewrite->pagination_base)?\d+$@", $slug )  || apply_filters( 'wp_unique_post_slug_is_bad_hierarchical_slug', false, $slug, $post_type, $post_parent ) ) {
$suffix = 2;
do {
$alt_post_name = substr( $slug, 0, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $meta_key, $alt_post_name, $post_ID, $post_parent ) );
$suffix++;
} while ( $post_name_check );
$slug = $alt_post_name;
}
} else {
// Post slugs must be unique across all posts.
$check_sql = "SELECT $wpdb->postmeta.meta_value FROM $wpdb->posts,$wpdb->postmeta WHERE $wpdb->posts.ID = $wpdb->postmeta.post_id AND $wpdb->postmeta.meta_key = '%s' AND $wpdb->postmeta.meta_value = '%s' AND $wpdb->posts.post_type = %s AND ID != %d LIMIT 1";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $meta_key, $slug, $post_type, $post_ID ) );

if ( $post_name_check || in_array( $slug, $feeds ) || apply_filters( 'wp_unique_post_slug_is_bad_flat_slug', false, $slug, $post_type ) ) {
$suffix = 2;
do {
$alt_post_name = substr( $slug, 0, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $meta_key, $alt_post_name, $post_type, $post_ID ) );
$suffix++;
} while ( $post_name_check );
$slug = $alt_post_name;
}
}

return $slug;
}

Step 3) Save the document

Step 4) Upload it to your themes directory

Step 5) Success! Your Qtranslate Slug Slugs will now be created correctly.

UPDATE : Problems migrating from mqtranslate with qtranslate-slug to qtranslate-x

UPDATE: qtranslate-x v. 3.2.9 removed the possibility to use the plugin as stated. Look at the bottom for an updated version!

So, some days ago, my favorite multilanguage plugin for wordpress, mqtranslate, was declared deprecated in favor of qtranslate-x. As i was just now coding a website for a client that uses mqtranslate and qtranslate slug, i thought „Better switch now than when the website is online and it has to be updated.“

So i installed qtranslate-x, migrated the settings from mqtranslate into qtranslate-x, and it seemed to work.

Until i noted that something strange happened: i could switch from my default language (german) to my secondary language (english), but not back to the default language. This only happened, when i activated the option „Hide URL information for default language“.

The reason for this is neither the fault of qtranslate-x nor from qtranslate-slug, but something in between them, maybe a miscommunication:

Qtranslate-x works different than qtranslate or mqtranslate in terms of detecting the language the user shall be shown.

mqtranslate (and qtranslate) got the information about the language to be shown from the URL: if there is a folder „/de/“ in the path, show german, if there is a folder „/en/“, show english. If the above mentioned option is active and no language folder is in the URL, show the default language.

So far so good.

qtranslate-x takes a different approach: the language is detected primarily by a cookie that is set when you visit a specified language. If you visit a page with the language path „/en/“ in the url, this cookie is set to „English“. As long as you don’t visit a page that sets this cookie back to „German“ by having „/de/“ in its URL, you don’t get german language. If you have the above mentioned Option active to hide the default language path, you still have to visit, for instance, http://www.mysamplesite.de/de/pagename, to switch back to german. The system itself redirects and rewrites the URL THEN to http://www.mysamplesite.de/pagename (removes the „/de/“).

Using qtranslate-slug, this leads to a problem: If the option to hide the URL language information for default language is active, the language information is not put in the link for the qtranslate-slug language switcher.

Because i couldn’t figure out, where to fix this within qtranslate-slug, i wrote my own little fix. This Plugin replaces the „language-detection-function“ of qtranslate-x with a function, that works nearly the same, except that IF a) the cookie is set and b) the option to hide the URL info for default language is active and c) there is no language info in the URL, it redirects you to the default language Variant of the page you requested. So, all in all, the language switching of mqtranslate is recreated, more or less.

This Plugin should be used ONLY if you
a) already used qtranslate or mqtranslate AND qtranslate-slug
b) use the option „Hide URL language information for default language.“
c) migrated to qtranslate-x already and found yourself in the same problem.

THIS IS ONLY A TEMPORARY FIX! The language detection that qtranslate-x uses, is a good one and improves many things. But if we want to keep our active websites unbroken, we sometimes need to stick to the „old“ solutions 😉

HOW TO APPLY:
-Create a file named mqtranslate-migration-helper.php
-Insert the following code in it:

<?php
/**
Plugin Name: mqtranslate/qtranslate-slug 2 qtranslate-x migration helper
Description: Recreates the language switching like mqtranslate in qtranslate-x
Version: 1.0.0
Author: kuchenundkakao
Author URI: https://kuchenundkakao.wordpress.com
License: GPL2
*
*/

if(!function_exists(‚qtranxf_detect_language_front‘)){
function qtranxf_detect_language_front(&$url_info) {
global $q_config;
//assert($url_info[‚doing_front_end‘]);
while(true){
if( isset($_COOKIE[QTX_COOKIE_NAME_FRONT]) ){
if($q_config[‚hide_default_language‘]){
$lang = $q_config[‚default_language‘];
} else {
$cs=null;
$lang=qtranxf_resolveLangCase($_COOKIE[QTX_COOKIE_NAME_FRONT],$cs);
}
$url_info[‚lang_cookie_front‘] = $lang;
if($lang) break;
}

if($q_config[‚detect_browser_language‘]
&& ( !isset($_SERVER[‚HTTP_REFERER‘]) || strpos($_SERVER[‚HTTP_REFERER‘],$url_info[‚host‘])===FALSE ) ){
$urlunslashed=untrailingslashit($url_info[‚wp-path‘]);
if(empty($urlunslashed)){
$lang=qtranxf_http_negotiate_language();
$url_info[‚lang_browser‘] = $lang;
if($lang) break;
}
}

$lang = $q_config[‚default_language‘];
break;
}
if( !isset($url_info[‚doredirect‘])
//&& !defined(‚WP_ADMIN‘) && !defined(‚DOING_CRON‘) && !defined(‚DOING_AJAX‘)//will check later
&& (!$q_config[‚hide_default_language‘] || $lang != $q_config[‚default_language‘])
//&& !$url_info[‚language_neutral_path‘]//already so
){
$url_info[‚doredirect‘]=’language needs to be shown in url‘;
}
return $lang;
}
}

/* Add a function to remove false duplicate alternate hreflang tags if qtranslate_slug is installed */
add_action(‚get_header‘,’remove_qts_slug_canonical‘);
function remove_qts_slug_canonical(){
global $qtranslate_slug;
remove_action(‚wp_head‘,array($qtranslate_slug, ‚qtranslate_slug_header_extended‘));
}

-Save the file
-Upload the file directly into your wp-content/plugins/ folder
-Activate the plugin in your Plugins settings
Et voila!

If you have problems, please leave a comment.

Happy Coding,
Kuchenundkakao

UPDATE:

qtranslate-x just removed the possibility to overwrite the function with v 3.2.9. Instead, we get a filter. yay. 😉
Here is an updated Version of the Plugin
THIS IS A QUICK FIX, IF SOMETHING BREAKS, I’M NOT LIABLE!!!

 

<?php
/**
Plugin Name: mqtranslate/qtranslate-slug 2 qtranslate-x migration helper
Description: Recreates the language switching like mqtranslate in qtranslate-x
Version: 1.1.0
Author: kuchenundkakao
Author URI: https://kuchenundkakao.wordpress.com
License: GPL2
*
*/

add_filter('qtranslate_detect_language', 'make_qtranslate_slug_compatible_again');
function make_qtranslate_slug_compatible_again($url_info){
if(!defined('DOING_AJAX')){
if( isset($url_info['doing_front_end']) ){
if($url_info['doing_front_end'] === TRUE){
global $q_config;
if(($q_config['hide_default_language']) && (!isset($url_info['lang_url']))){
$url_info['language'] = $q_config['default_language'];
}
}
}
}
return $url_info;
}

/* Add a function to remove false duplicate alternate hreflang tags if qtranslate_slug is installed */
add_action('get_header','remove_qts_slug_canonical');
function remove_qts_slug_canonical(){
global $qtranslate_slug;
remove_action('wp_head',array($qtranslate_slug, 'qtranslate_slug_header_extended'));
}

qtranslate / qtranslate-x / mqtranslate / qtranslate-slug : Fixing the „order by ‚title'“-problem

UPDATE: PLEASE LOOK AT THE BOTTOM FOR QTRANSLATE-X COMPATIBILITY:
Here we are again. When i built a two-lingual website for a customer, i ran into a problem with the sorting:

The customer needed the posts to be sorted by the title. Well, thats what the argument „orderby“ in WP_Query is good for, shouldn’t be a problem, right?

Wrong.

As we know, mqtranslate saves translated stuff like the post title like this in the database:
"<!--:de-->Beitragstitel<!--:--><!--:en-->Post title<!--:-->".
If we order by the Post title, the default language will be sorted correctly. The other language will be sorted the same. Meh.

I found no solution to this problem, so i made my own. If you need to sort by title and use mqtranslate, maybe this will help you too.

Open your themes functions.php and insert the following:

##UPDATE: UPDATED FUNCTION FOR QTRANSLATE-X AT THE BOTTOM ##

add_filter( 'posts_clauses', 'qtrans_fix_clauses', 999, 2 );
function qtrans_fix_clauses($clauses,$query){
if(!is_admin()){
global $q_config;
$lang = $q_config['language'];
if($query->query_vars['orderby'] == 'title'){
$clauses['orderby'] = "SUBSTR(post_title, LOCATE('<!--:".$lang."-->',post_title)+10,(LOCATE('<!--:-->',post_title,LOCATE('<!--:".$lang."-->',post_title)+10) - (LOCATE('<!--:".$lang."-->',post_title)+10)) ) ".$query->query_vars['order'];
}
}
return $clauses;
}

The filter „posts_clauses“ is called directly before the sql is sent to the database. We change the „order by“ clause, so that the query gets sorted by the part within the post_title that is between the active language tags.

UPDATE: IMPORTANT: If you get your posts via the „get_posts“ function, you need to pass the following argument within your args:
'suppress_filters' => false
as the get_posts function suppresses the posts_clauses filter by standard.

UPDATE 2 – QTRANSLATE-X:
As of now, the plugin mqtranslate is deprecated in favor of the new qtranslate-x. As this plugin can make use either of these language tags: <–:de–> Or these: [:de], we have to modify our code a little bit:

add_filter( 'posts_clauses', 'qtrans_fix_clauses', 999, 2 );
function qtrans_fix_clauses($clauses,$query){
if(!is_admin()){
global $q_config;
$lang = $q_config['language'];
if($query->query_vars['orderby'] == 'title'){
$clauses['orderby'] = "SUBSTR(post_title, IF(LOCATE('<!--:".$lang."-->',post_title),LOCATE('<!--:".$lang."-->',post_title)+10, LOCATE('[:".$lang."]',post_title)+5),
IF(LOCATE('<!--:".$lang."-->',post_title),LOCATE('<!--:-->',post_title,LOCATE('<!--:".$lang."-->',post_title)+10) - (LOCATE('<!--:".$lang."-->',post_title)+10),
LOCATE('[:',post_title,LOCATE('[:".$lang."]',post_title)+5) - (LOCATE('[:".$lang."]',post_title)+5))) ".$query->query_vars['order'];
}
}
return $clauses;
}

Now the function can sort both tags.

If you get problems with the code (or the code helped you), please leave a comment!

Happy Coding,
Kuchenundkakao

User Access Manager: Make it compatible to Advanced Access Manager (AAM)

For a new project, i need to use Advanced Access Manager (http://wpaam.com/, https://wordpress.org/plugins/advanced-access-manager/) to add custom user roles to wordpress, as well as User Access Manager(http://www.gm-alex.de/projects/wordpress/plugins/user-access-manager, https://wordpress.org/plugins/user-access-manager/), which gives me the option to restrict access to pages/posts to specific user roles.

I couldn’t get it to work, until i noticed that AAM saved the custom roles as something like „aamrole_542a55374728e“, while UAM saved the role a post was restricted to only with the first 11 letters, so like „aamrole_542“. Looking into the database, i found that UAM creates a new datatable which stores the relations of objects like user roles and the access they get, but the object_id field is only 11 characters big. So the user roles created by AAM couldn’t be saved correctly, resulting in denying access where access should have been possible.

To solve this problem, you open your WordPress Database with a tool like phpMyAdmin, navigate to the table wp_uam_accessgroup_to_object, and change the field ‚object_id‘ to be bigger, like 50 characters. Again, if you don’t know what you are doing, DON’T DO IT!

To Alexander, the guy who wrote the plugin: Please change line 225 in the file UserAccessManager.class.php from

object_id VARCHAR(11) NOT NULL,

to

object_id VARCHAR(50) NOT NULL,

that will make things easier ^^

Happy Coding,
Kuchenundkakao

mqtranslate: line breaks get turned into paragraphs in visual editor

UPDATE: This seems to not work anymore. See Comments…

As the qtranslate plugin doesn’t seem to be updated anymore, i switched to mqtranslate, which is a fork of qtranslate but compatible to newer versions of WordPress.

Unfortunately, with WordPress Version 4, mqtranslate seems to have some problems with the visual editor. One particularly bizarre one encountered to me yesterday:

Working in the visual editor, single line breaks (<br />) are turned into paragraphs (<p>). Looking into the mqtranslate_javascript.php file, i found a (for now) working fix:

Step 1: Open the mqtranslate_javascript.php file in a text-editor.
Step 2: Find Line 292, which should read like this:
jQuery('#qtrans_textarea_content').val(switchEditors.wpautop(jQuery('#qtrans_textarea_content').val()))
Step 3: Comment this line out with 2 slashes (/), so that it reads like this:
//jQuery('#qtrans_textarea_content').val(switchEditors.wpautop(jQuery('#qtrans_textarea_content').val()))
Step 4: Save the file and upload it again to your mqtranslate plugin folder
Step 5: Done! The visual editor should work correctly again.

Please let me know if i could help you or if other problems occur after you applied the fix.

Happy Coding,
Kuchenundkakao

Pirobox Bugfixing: Skewed Aspect Ratio when zoomed

On the latest Website i coded, i used a responsive Theme. As i was finishing work, i noticed something strange: When i zoom in a picture in the Pirobox lightbox, the images Aspect Ratio gets screwed up. I found that the solution to this case is rather simple:

Many Responsive Themes use a css that prevents images from extending their boundaries by doing something like this:


img {
max-width: 100%;
height: auto;
}

But for the zooming in Pirobox, the Image HAS to get larger than the container. So all we need to do is add the following snippet to our css file and everything zooms fine again:


.piro_html .h_c_c .div_reg img{
max-width: none;
}

Some more Bugfixing for qtranslate slug

Hey Again.

As my previous blog post seems to have helped some people, i post another little bugfix for qtranslate slug i just encountered.

Before that, some information:
In this case, we talk about a newly installed WordPress 3.5.1, a reduced „twenty-twelve“ as theme and qtranslate 2.5.34 / qtranslate slug 1.1 environment. I have no idea, if the bug occurs on previous versions of WordPress, but i think it should (right now i’m too lazy to test it).

As many others, i use a dropdown-menu which (as i have installed qtranslate / qtranslate slug) should be translated. Also i need to use the global $post before the loop starts, e.g. for echoing the post thumbnail in the header.

I found that on every page i displayed, the post thumbnail of the last item in my dropdown was displayed instead of the actual thumbnail of the page i was on.

Why is that? I found that in the function _get_page_link of qtranslate slug, the global $post variable is used without resetting it to the value it was before. SOOOO here is the fix:

– Open the qtranslate-slug.php (which you should find in the folder wp-content/plugins/qtranslate-slug )
– go to line 1293 and find the public function _get_page_link( $link, $id )
– mark everything until the end of the function (around line 1319 , ends with

return $link;
}

– Replace it with the following code:


public function _get_page_link( $link, $id ) {
global $post, $wp_rewrite, $q_config;
// Begin Changes: Add temp var for our original post object
$tmp_post = $post;
// End Changes
if ( !$id )
$id = (int) $post->ID;
else
$post = &get_post($id);

$draft_or_pending = in_array( $post->post_status, array( 'draft', 'pending', 'auto-draft' ) );

$link = $wp_rewrite->get_page_permastruct();

if ( !empty($link) && ( isset($post->post_status) && !$draft_or_pending ) ) {

$link = str_replace('%pagename%', $this->get_page_uri($id), $link);

$link = trim($link, '/'); // hack
$link = home_url("/$link/"); // hack

if ($q_config['url_mode'] != 1)
$link = user_trailingslashit($link, 'page');

} else {

$link = home_url("?page_id=$id");
}
// Begin Changes: set the global $post back to the original stored in $tmp_post
$post = $tmp_post;
// End Changes
return $link;
}

Et Voila! No more getting the wrong Post Thumbnail (or other $post related stuff before the loop) just by having a menu!

I hope these changes help somebody, if not, just ignore it ^^ 😉

CU,
Kuchen und Kakao