Classifique no meta-valor, mas inclua postagens que não possuem um


37

Estive modificando a pesquisa WP incorporada usando o pre_get_posts filtro, permitindo que o usuário classifique as postagens (incluindo vários tipos personalizados de postagens) por campos diferentes.

O problema que estou tendo é que, quando digo ao WP para classificar por um meta-valor, ele exclui todas as postagens que não têm esse meta-valor definido. Isso faz com que o número de resultados mude se você alterar a classificação de dizer "Preço" para "Data" porque "Postagens" não têm "Preço" definido, mas "Itens" sim.

Não é isso que eu quero, então eu gostaria de saber se há uma maneira de incluir TODAS as postagens - mesmo aquelas que não possuem o valor meta que eu estou classificando - e colocar a que está sem o valor por último.

Eu sei como classificar em mais de um campo, mas isso não ajuda.

obrigado

Parece que eu não sou o único com esta pergunta: Maneira de incluir posts com e sem determinada meta_key em args para wp_query? mas não há solução lá.

Atualizar

Eu tentei a resposta, mas não tenho certeza se entendi corretamente, eis o que tenho agora:

<?php
function my_stuff ($qry) {
    $qry->set('meta_query', array(array(
        'key' => 'item_price', 
        'value' => '', 
        'compare' => 'NOT EXISTS'
    )));

    $qry->set('orderby', 'meta_value date'); # Sorting works with meta_value as well as meta_value_num - I've tried both
    $qry->set('order', 'ASC DESC');
    $qry->set('meta_key', 'item_price');
}

O meta-valor é um número (é usado para armazenar um preço como o nome sugere)

Atualização 2

Eu comentei as coisas do pedido e tudo o que tenho agora é o seguinte:

<?php
$qry->set('meta_query', array(array(
    'key' => 'item_price', 
    'value' => '', 
    'compare' => 'NOT EXISTS'
)));

Com esse código, a consulta parece retornar todas as postagens que não possuem a item_pricechave e nenhuma das postagens que a possuem. Ou seja, o problema agora está invertido.

Se eu adicionar o código do pedido, também recebo 0 resultados.

Edit: ... três anos depois ... : PI teve esse problema novamente. Eu tentei todas as respostas dadas e nenhuma funcionou. Não sei por que algumas pessoas parecem pensar que funcionam, mas pelo menos não funcionam para mim.

A solução que eu acabei usando é usar o save_post filtro - certificando-se de que todas as postagens tenham o campo personalizado que eu gostaria de classificar. É um pouco chato eu ter que fazer isso, mas contanto que você faça isso desde o início, provavelmente não terá problemas.

Nesse caso, eu estava criando um "contador de visualizações" nas postagens e queria que os usuários pudessem classificar as postagens mais lidas. Novamente, as postagens que nunca foram visualizadas (acho que é bastante improvável - mas ainda assim) desapareceram ao classificar a contagem de visualizações. Adicionei este código para garantir que todas as postagens tenham uma contagem de visualizações:

add_action('save_post', function ($postId) {
    add_post_meta($postId, '_sleek_view_count', 0, true);
});

Por favor, mostre-nos o seu código. Facilita a resposta.
kaiser

Primeiro: meta_querye tax_querysão sempre um array( array() ), pois combinam várias matrizes. Segundo - como mencionado na minha resposta - você precisa usar meta_value_numpara números. Também pode ser necessário definir realmente meta_value_num(consulte a WP_Queryentrada de página -Codex). Última, não faz sentido orderem ASC e DESC direção. Isso não é possível. O delimitador de espaço funciona apenas para orderbye você não pode ordená-lo para classificar o primeiro ASCe o segundo DESC. É para isso que posts_clausesserve o filtro.
kaiser

E verifique se suas meta_value_numentradas são números reais . Viu com muita frequência que alguém afirma que é um número, mas na verdade salvou-o como uma sequência no banco de dados.
kaiser

Obrigado pela sua ajuda, tentarei isso e retornarei a você. A razão para ASC DESCisso é que ele classifica o meta_valuein ASCe o datein DESC, até onde eu sei que funciona.
PowerBuoy

1
@Howdy_McGee está correto. Alguns dos meus tipos de postagem personalizados TEM esse valor definido. Alguns não. E os tipos de postagem incorporados (como POST e PAGE) não. Portanto, sempre que tento classificar esse campo personalizado, apenas as postagens COM o campo personalizado aparecem.
PowerBuoy

Respostas:


4

Existem duas soluções possíveis para isso:

1. Todas as postagens têm meta

A melhor solução que encontrei aqui é fornecer ao restante das postagens / produtos um preço de item igual a 0. Você pode fazer isso manualmente ou percorrer todas as postagens e, se o preço estiver vazio, atualize-o.

Para tornar isso gerenciável no futuro, você pode se conectar save_poste dar um valor a eles quando forem adicionados pela primeira vez (somente se estiver em branco).

2. Várias consultas

Você pode executar a primeira consulta enquanto faz e armazenar os IDs das postagens retornadas. Em seguida, você pode executar outra consulta para todas as postagens e a data do pedido, excluindo os IDs retornados da primeira consulta.

Em seguida, você pode imprimir os dois resultados separadamente e obter os resultados desejados.


1
Três anos depois, tive o mesmo problema novamente: P Tinha que usar o save_postmétodo (atualizei minha pergunta com o código que usei).
PowerBuoy

10

Easy Peasy, testado em 2018, atualmente em uso.

$query->set( 'meta_query', array(
    'relation' => 'OR',
    array(
        'key' => 'custom_meta_key', 
        'compare' => 'EXISTS'
    ),
    array(
        'key' => 'custom_meta_key', 
        'compare' => 'NOT EXISTS'
    )
) );
$query->set( 'orderby', 'meta_value title' ); 

Isso verifica todos os itens com e sem a meta-chave, sem nenhum valor especificado. a meta consulta fornece a chave do pedido com confiabilidade. Foi testado. Não tenho certeza, no entanto, de como isso funcionará quando a consulta meta usar várias chaves.

Exemplo prático

/**
 * Modifies query before retrieving posts. Sets the 
 * `meta_query` and `orderby` param when no `orderby` 
 * param is set, (default ordering).
 * 
 * @param   WP_Query  $query  The full `WP_Query` object.
 * @return  void
 */
function example_post_ordering( $query ) {

    // if not in wp-admin, 
    // and the query is the main query, 
    // and the query is not a singular query, 
    // and the query does not have an orderby param set...
    // Note: check for post types, etc. here as desired.
    if ( ! is_admin() 
    && $query->is_main_query() 
    && ! $query->is_singular() 
    && empty( $query->get( 'orderby' ) ) ) {

        // Setting just `meta_key` is not sufficient, as this 
        // will ignore posts that do not yet, or never will have 
        // a value for the specified key. This meta query will 
        // register the `meta_key` for ordering, but will not 
        // ignore those posts without a value for this key.
        $query->set( 'meta_query', array(
            'relation' => 'OR',
            array(
                'key' => 'custom_meta_key', 
                'compare' => 'EXISTS'
            ),
            array(
                'key' => 'custom_meta_key', 
                'compare' => 'NOT EXISTS'
            )
        ) );

        // Order by the meta value, then by the title if multiple 
        // posts share the same value for the provided meta key.
        // Use `meta_value_num` if the meta values are numeric.
        $query->set( 'orderby', 'meta_value title' );
    }

}

add_action( 'pre_get_posts', 'example_post_ordering', 10 );

Isso ordenará as postagens por custom_meta_keypadrão e não ignorará as postagens sem um valor para essa chave.


Apenas lendo o código, tudo o que parece fazer é obter as postagens que têm custom_meta_keye as postagens que não têm custom_meta_key. Sinta-se à vontade para incluir um exemplo de trabalho real na classificação.
PowerBuoy

1
Você está certo, é tudo o que está fazendo, mas a linha abaixo é responsável por ordenar por meta_value (da meta-chave que está sendo consultada). $query->set( 'orderby', 'meta_value title' );(Ordene por meta-valor e depois por título quando várias postagens tiverem o mesmo valor para a meta-chave). Isso deve ser feito no pre_get_postsgancho, usando a $queryvariável passada em . Lembre-se de que a pergunta feita foi como ordenar por meta-valor, sem ignorar as postagens que não possuem um valor para essa meta-chave.
Noahmason

@powerbuoy Veja exemplo prático atualizado
noahmason 18/04/19

Tudo bem, vou tentar da próxima vez que estiver enfrentando esse problema.
precisa saber é o seguinte

1
Trabalhei para mim em uma get_posts()chamada personalizada para empurrar postagens com _featuredmeta para o topo e depois ordená-las por data. Obrigado!
Natebeaty 9/11

8

Esse método retornará todas as postagens, incluindo aquelas com e sem a solicitação meta_key, mas fará coisas estranhas ao fazer o pedido.

add_action('pre_get_posts', 'my_stuff');
function my_stuff ($qry) {
    $qry->set(
        'meta_query',
        array(
            'relation' => 'OR', # Matches to this meta_query should be added to those matching the 'meta_key' query
            array(
                'key' => 'item_price', 
                'value' => 'bug #23268', 
                'compare' => 'NOT EXISTS'
            )
        )
    );

    $qry->set('orderby', 'meta_value date'); # Sorting works with meta_value as well as meta_value_num - I've tried both
    $qry->set('order', 'ASC DESC');
    $qry->set('meta_key', 'item_price');
}

Eu descobri isso brincando com todas as respostas diferentes para essa pergunta e analisando o SQL gerado por tentativa e erro. Parece que a configuração array('meta_query' => array('relation' => 'OR'))gera um resultado apropriado, em LEFT JOINvez do INNER JOINnecessário, para incluir postagens ausentes dos metadados. A especificação de NOT EXISTSimpede que a WHEREcláusula filtre as postagens sem o campo meta. Para isso WP_Query, o SQL gerado é (recuo / novas linhas adicionadas):

SELECT SQL_CALC_FOUND_ROWS
    wp_posts.ID
    FROM wp_posts
    INNER JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id)
    INNER JOIN wp_postmeta ON wp_posts.ID = wp_postmeta.post_id
    LEFT JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id AND mt1.meta_key = 'item_price')
    WHERE 1=1
    AND ( wp_term_relationships.term_taxonomy_id IN (2) )
    AND wp_posts.post_type = 'post'
    AND (wp_posts.post_status = 'publish'
        OR wp_posts.post_status = 'private')
    AND (wp_postmeta.meta_key = 'item_price'
        -- Oh look, here we give SQL permission to choose a random
        -- row from wp_postmeta when this particular post is missing
        -- 'item_price':
        OR  mt1.post_id IS NULL )
    GROUP BY wp_posts.ID
    ORDER BY wp_postmeta.meta_value,wp_posts.post_date DESC
    LIMIT 0, 10

O resultado é uma lista de todas as postagens com meta_value de item_pricee as que estão faltando item_price. Todos os posts com item_priceserão ordenados corretamente em relação ao outro, mas as mensagens faltando item_priceusará alguns aleatória outro valor meta (digamos, _edit_lasto que parece ser 1, muitas vezes no meu banco de dados ou algum outro metadados wordpress interna que é completamente arbitrária) para a sua wp_postmeta.meta_valueem a ORDER BYcláusula. Portanto, embora esse método esteja próximo e possa parecer funcionar para determinados dados, ele está quebrado. Então, tudo o que posso dizer é que, se seus item_pricevalores não conflitarem com os meta campos aleatórios que o MySQL escolher para as postagens ausentesitem_price , isso poderá funcionar bem para você. Se tudo o que você precisa é garantir que suas postagens comitem_pricesão ordenados corretamente em relação um ao outro, sem levar em consideração o pedido de outras postagens, pode estar OK. Mas acho que isso é apenas uma falha no wordpress. Por favor, corrija-me, espero estar errado e há uma maneira de resolver isso ;-).

Parece que, para o INNER JOIN wp_postmetaMySQL, está escolhendo uma linha aleatória dentre várias postmetalinhas associadas à postagem quando esta meta_keyestá ausente na postagem especificada. Do ponto de vista do SQL, precisamos descobrir como informar o wordpress à saída ORDER BY mt1.meta_value. Esta coluna está corretamente NULLquando o nosso pedido meta_keyestá ausente, ao contrário wp_postmeta.meta_value. Se pudéssemos fazer isso, o SQL classificaria essas NULL(entradas ausentes) antes de qualquer outro valor, dando-nos uma ordem bem definida: primeiro vêm todas as postagens que faltam no campo postmeta específico, depois as postagens que possuem o campo. Mas esse é todo o problema: 'orderby' => 'meta_value'só podemos nos referir 'meta_key' => 'item_price' e o sem paralisia wp_postmetaé sempre um, em INNER JOINvez de sempre um LEFT JOIN, significado wp_postmeta.meta_valueewp_postmeta.meta_key podenunca seja NULL.

Então eu acho que tenho que dizer que isso não é possível com o wordpress embutido, WP_Querycomo agora está documentado (no wordpress-3.9.1). Incomodar. Portanto, se você realmente precisar que isso funcione corretamente, provavelmente precisará conectar-se ao wordpress em outro lugar e modificar diretamente o SQL gerado .


Parece muito promissor! Vou tentar isso da próxima vez que tiver esse problema. Gostaria de lhe dar a resposta agora, mas preferiria confirmar que funciona para mim primeiro.
PowerBuoy

Isso impediu que tudo aparecesse para mim. Nada foi mostrado depois de implementar isso.
Jake

@ Jake Yea mesmo aqui. Teve esse problema novamente hoje e tentei isso. Retorna 0 resultados.
PowerBuoy

Qual versão do wordpress vocês estão usando? Eu acho que este post descreve como usar uma API interna, não documentada, que não é suportada pelo wordpress e, portanto, provavelmente só funciona se você estiver no wordpress-3.9.1 ou se não houver muitas versões além disso.
binki

2

Eu acho que tenho uma solução.

Você pode usar dois meta_keys, um que todas as postagens têm (like "_thumbnail_id")e o que meta_keyvocê deseja usar como filtro.

Então seus argumentos:

$qry->set(
    'meta_query',
    array(
        'relation' => 'OR',
        array(
            'key' => 'item_price', 
            'value' => '', 
            'compare' => 'EXISTS'
        ),
        array(
            'key' => 'item_price', 
            'value' => '', 
            'compare' => 'EXISTS'
        )
    )
);

$qry->set('orderby', 'meta_value date'); # Sorting works with meta_value as well as meta_value_num - I've tried both
$qry->set('order', 'ASC DESC');
$qry->set('meta_key', 'item_price');

1
O problema aqui é a comparação de cadeias vazias, remova-as e funcione 'value' => '', também segundo comparar deve ser NOT EXISTSea instrução último conjunto não é necessária
nodws

2

O problema que todos aqui estão enfrentando tem a ver com a ordem das meta-consultas. Para classificar corretamente, você precisará colocar a consulta "NOT EXISTS" antes da consulta "EXISTS".

A razão para isso é porque o WordPress usa o meta_value da última instrução "LEFT JOIN" na cláusula "ORDER BY".

Por exemplo:

$pageQuery = new WP_Query([
    'meta_query' => [
        'relation' => 'OR',
        ['key' => 'item_price', 'compare' => 'NOT EXISTS'], // this comes first!
        ['key' => 'item_price', 'compare' => 'EXISTS'],
    ],
    'order' => 'DESC',
    'orderby' => 'meta_value_num',
    'post_status' => 'publish',
    'post_type' => 'page',
    'posts_per_page' => 10,
]);

1

Se adequado, você pode adicionar um meta-valor padrão sempre que uma postagem for salva ou atualizada, se o meta-valor não existir.

function addDefaultMetaValue($post_id) {
    add_post_meta($post_id, 'item_price', 0, true);
}
add_action('save_post', 'addDefaultMetaValue');

Se você estiver usando um tipo de pós personalizado, substitua o add_action('save_post', 'addDefaultMetaValue');por add_action('save_post_{post_type}', 'addDefaultMetaValue');exemplo,add_action('save_post_product', 'addDefaultMetaValue');


1

Eu tive o problema sozinho de meta-valores numéricos e apontei que a ordem da consulta também é importante. Para mim oNOT EXISTS consulta deve ser a primeira.

Exemplo:

$query->set( 'orderby', 'meta_value_num' );
$query->set( 'meta_query', [
    'relation' => 'OR',
    [ 'key' => 'your_meta_name', 'compare' => 'NOT EXISTS' ],
    [
        'key' => 'your_meta_name',
        'compare' => 'EXISTS',
    ],
] );

Também importante para obter a direção correta para valores numéricos é o ’orderby’ a ser definido ’meta_value_num’. Caso contrário, você terá resultados estranhos para valores numéricos, por exemplo:

1, 2, 20, 21, 3, 4, 5…

Ao invés de:

1, 2, 3, 4, 5 ... 20, 21


1

Eu também encontrei um problema semelhante e a seguinte solução me ajudou:

$args = array(
'post_type' => 'kosh_products',
'posts_per_page' => -1,
'meta_query' => array(
    'relation' => 'OR',
    'category_sort_order' => array(
        'key' => '_sort_order',
        'compare' => 'EXISTS'
    ),
    'category_sort_order_not_exists' => array(
        'key' => '_sort_order',
        'compare' => 'NOT EXISTS'
    ), 
),
'orderby' => array( 
    'category_sort_order' => 'ASC',
    'date' => 'ASC'
));
$query = new WP_Query( $args );

Encontrei uma descrição no WordPress Codex com o título " 'orderby' com vários 'meta_key's ": https://codex.wordpress.org/Class_Reference/WP_Query#Order_.26_Orderby_Parameters insira a descrição da imagem aqui


0

Existe um orderbyvalor possível meta_valuepara isso.

$query = new WP_Query( array ( 
    'meta_key'   => 'your_keys_name',
    'orderby'    => 'meta_value',
    'order'      => 'DESC',
    'meta_query' => array( array(
         'key'     => 'your_meta_key',
         'value'   => '',
         'compare' => 'NOT EXISTS',
         // 'type'    => 'CHAR',
    ) )
) );

Se você obteve valores numéricos, basta usar meta_value_num.

Isenção de responsabilidade: Isso não foi testado, mas deve funcionar. Ponto é que você precisa especificar seus meta_keye keyvalores. Caso contrário, você não poderá comparar com valores inexistentes, o que tornará possível consultar os dois tipos de postagens. É algum tipo de hack-ish, mas desde que funcione ...


Obrigado pela sua resposta, verifique minha pergunta atualizada. Não sei se entendi corretamente.
PowerBuoy

Ainda não fiz esse trabalho, por isso, se você tiver uma solução, adoraria saber o que estou fazendo de errado. Além disso, eu definir uma recompensa por isso, se você quiser reclamar: stackoverflow.com/questions/17016770/...
PowerBuoy

1
Duas coisas. 'your_keys_name'e 'your_meta_key'ambos devem ter a mesma string em vez de distintos, caso contrário, parece que você entendeu mal a pergunta. Em segundo lugar, testei isso na minha configuração local e exclui todas as postagens em que a chave existe (por meio de meta_query) e exclui as postagens em que a chave está ausente (por meta_key) resultando em nenhuma exibição. No entanto, esta resposta é um passo em direção a algo que exprima pelo menos ;-).
binki

1
Oh, curiosamente, essa resposta funciona se você apenas adicionar 'relation' => 'OR'a meta_query. Coisas malucas o_o.
binki

@ binki Basta arquivar uma edição na minha pergunta e alterar os bits que você acha que devem ser alterados. Este é um site dirigido comunidade :)
kaiser

0

Eu acho que o que o @kaiser estava tentando fazer era dizer à consulta para retornar todas as postagens que possuem essa meta-chave, aplicando uma espécie de dummy where condition para não filtrar nenhuma dessas postagens. Portanto, se você conhece todos os valores que seus campos personalizados podem assumir são x, y, z, você pode dizer "WHERE meta_key IN (x, y, z) ", mas a idéia é que você pode evitar esse problema todos juntos dizendo ! = (' ') :

$query = new WP_Query( array ( 
    'orderby'    => 'meta_value_num',
    'order'      => 'DESC',
    'meta_query' => array( array(
         'key'     => 'item_price',
         'value'   => '',
         'compare' => '!=',
    ) )
) );

Também não testado, mas parece que vale a pena tentar :-).


1
Não posso realmente testar isso agora, mas tenho certeza de que retornará apenas postagens onde item_price está definido e não está ''.
precisa saber é o seguinte

0

Acabei contornando isso com um pouco de hack (IMHO), mas ele fez o trabalho para mim no meu caso.

Você pode conectar-se aos filtros posts_join_paged e posts_orderby para atualizar as cadeias de junção e ordem. Isso permitirá que você faça o pedido do jeito que quiser, desde que você o junte primeiro, em vez de WP_Query, supondo que o campo precise existir para essa postagem específica. Você pode remover o meta_key,orderby e `fim de seus argumentos WP_Query.

Abaixo está um exemplo. No topo de cada função, eu tive que escapar para determinados casos, pois isso adicionaria isso a tudo que usa o WP_Query. Pode ser necessário modificá-lo para atender às suas necessidades específicas.

Infelizmente, a documentação desses dois filtros está faltando, então ... boa sorte! :)

add_filter('posts_join_paged', 'edit_join', 999, 2);
add_filter('posts_orderby', 'edit_orderby', 999, 2);

/**
 * Edit join
 *
 * @param string $join_paged_statement
 * @param WP_Query $wp_query
 * @return string
 */
function edit_join($join_paged_statement, $wp_query)
{
    global $wpdb;
    if (
        !isset($wp_query->query)
        || $wp_query->is_page
        || $wp_query->is_admin
        || (isset($wp_query->query['post_type']) && $wp_query->query['post_type'] != 'my_custom_post_type')
    ) {
        return $join_paged_statement;
    }

    $join_to_add = "
        LEFT JOIN {$wpdb->prefix}postmeta AS my_custom_meta_key
            ON ({$wpdb->prefix}posts.ID = my_custom_meta_key.post_id
                AND my_custom_meta_key.meta_key = 'my_custom_meta_key')
    ";

    // Only add if it's not already in there
    if (strpos($join_paged_statement, $join_to_add) === false) {
        $join_paged_statement = $join_paged_statement . $join_to_add;
    }

    return $join_paged_statement;
}

/** 
 * Edit orderby
 *
 * @param string $orderby_statement
 * @param WP_Query $wp_query
 * @return string
 */
function edit_orderby($orderby_statement, $wp_query)
{
    if (
        !isset($wp_query->query)
        || $wp_query->is_page
        || $wp_query->is_admin
        || (isset($wp_query->query['post_type']) && $wp_query->query['post_type'] != 'my_custom_post_type')
    ) {
        return $orderby_statement;
    }

    $orderby_statement = "my_custom_meta_key.meta_value DESC";

    return $orderby_statement;
}

O código funciona. Mas o meta_value é tratado como uma string. Então, 6 é classificado como superior a 50. Alguma modificação possível para tratá-los como números?
Drivingralle

@Drivingralle cast(my_custom_meta_key.meta_value as unsigned) DESCdeve fazer o truque ...
tfrommen

1
Obrigado @tfrommen. $orderby_statement = "cast(my_custom_meta_key.meta_value as unsigned) DESC";funciona bem.
Drivingralle

0

Esta solução funcionou para mim:

add_action( 'pre_get_posts', 'orden_portfolio' );
function orden_portfolio( $query ) {

    if( ! is_admin() ) {

        $query->set( 'orderby', 'meta_value_num' );
        $query->set( 'order', 'ASC' );
        $query->set( 'meta_query', [
            'relation' => 'OR',
            [ 
                'key' => 'ce_orden', 
                'compare' => 'NOT EXISTS' ],
            [
                'key' => 'ce_orden',
                'compare' => 'EXISTS',
            ],
        ] );

        return $query;

    }

}

No entanto, esta solução mostra primeiro os registros com meta_value nulo. Esta outra solução mostra a ordem ASC e os valores nulos no final:

function custom_join($join) {
    global $wpdb;

    if( ! is_admin() ) {
        $join .= $wpdb->prepare(
        ' LEFT JOIN ' . $wpdb->postmeta . ' cpm ON cpm.post_id = ' . $wpdb->posts . '.ID AND cpm.meta_key = %s'
        , 'ce_orden' );
    }

    return $join;
}

add_filter('posts_join','custom_join');

function custom_orderby($orderby_statement){
    global $wpdb;

    if ( ! is_admin() ) {
        $orderby_statement = "CAST( COALESCE(cpm.meta_value,99999) as SIGNED INTEGER) ASC";
    }

    return $orderby_statement;
}

add_filter('posts_orderby','custom_orderby', 10, 2 ); 
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.