Kreatywne czary z filtrami i akcjami

Krzysiek Dróżdż
krzysiek@wpmagus.pl
WPmagus.pl

Odrobina technikaliów

Co to są filtry i akcje?

Być może widzieliście już coś takiego:

add_filter( 'cos_dziwnego', 'my_dziwne_cos' );

lub:

add_action( 'cos_dziwnego', 'my_dziwne_cos' );

Jak działają filtry?

Zajrzyjmy w kod WordPressa...

function add_filter( $tag, $function_to_add, $priority = 10,
        $accepted_args = 1 ) {
    global $wp_filter, $merged_filters;

    $idx = _wp_filter_build_unique_id($tag, $function_to_add, $priority);
    $wp_filter[$tag][$priority][$idx] = array(
        'function' => $function_to_add,
        'accepted_args' => $accepted_args
    );
    unset( $merged_filters[ $tag ] );
    return true;
}

A akcje?

function add_action($tag, $function_to_add, $priority = 10,
        $accepted_args = 1) {
    return add_filter($tag, $function_to_add, $priority, $accepted_args);
}

Uruchamianie filtrów

if ( !isset( $merged_filters[ $tag ] ) ) {
    ksort($wp_filter[$tag]);
    ...
}
reset( $wp_filter[ $tag ] );
...
do {
    foreach ( (array) current($wp_filter[$tag]) as $the_ )
        if ( !is_null($the_['function']) ){
            $args[1] = $value;
            $value = call_user_func_array($the_['function'],
                array_slice($args, 1, (int) $the_['accepted_args']));
        }
} while ( next($wp_filter[$tag]) !== false );
...
return $value;

… i akcji

 if ( !isset( $merged_filters[ $tag ] ) ) {
    ksort($wp_filter[$tag]);
    $merged_filters[ $tag ] = true;
}

reset( $wp_filter[ $tag ] );

do {
    foreach ( (array) current($wp_filter[$tag]) as $the_ )
        if ( !is_null($the_['function']) )
            call_user_func_array($the_['function'],
                array_slice($args, 0, (int) $the_['accepted_args']));

} while ( next($wp_filter[$tag]) !== false );

Nie tylko w kodzie WordPressa

Możecie bez problemu wykonać:

$x = apply_filters( 'moj_dowolny_tag', $wartosc, $param1, ... );

czy

do_action( 'moj_dowolny_tag', $wartosc, $param1, ... );

A na co nam to?

Case study: Historia pewnego formularza

Krok 1

Zróbmy prosty formularz, żeby ludzie mogli wysyłać nam maile i zapisywać się na szkolenia

Krok 2

Dodajmy link do rezerwacji pod opisem kursu
function prefiks_add_button( $content ) {
    if ( 'kurs' === get_post_type( get_the_ID() ) ) {
        $content .= '<p><a href="'. site_url('/rezerwacja/') .'">Zapisz się</a></p>';
    }
    return $content;
}
add_filter( 'the_content', 'prefiks_add_button' );

Krok 3

Ale dodajmy selecta z wyborem szkolenia, na które chcą się zapisać
function prefiks_add_shortcode_select_courses() {
    wpcf7_add_shortcode( array( 'select_courses', 'select_courses*' ),
        'prefiks_select_courses_shortcode_handler', true );
}
add_action( 'wpcf7_init', 'prefiks_add_shortcode_select_courses' );

function prefiks_select_courses_shortcode_handler( $tag ) {
    $tag = new WPCF7_Shortcode( $tag );
    // ...
    $values = array();
    $labels = array();

    $courses = get_posts( ... );
    foreach ( $courses as $course )
        $values[] = $course->ID;
        $labels[] = $course->post_title;
    }
    // ...
    $html = sprintf(
        '%4$s',
        sanitize_html_class( $tag->name ), $atts, $html, $validation_error );

    return $html;
}

Krok 4

I jeszcze pozwólmy im podawać NIP i go weryfikujmy
function prefiks_text_validation($result, $tag) {
    $value = $_POST[ $tag['name'] ];
    
    if ( false !== strpos($tag['name'], 'eu-vat') ) {
        $value = str_replace( array('-', ' '), '', $value);
        if ( $value && ! preg_match('#^([A-Za-z]{2})?\d{10}$#', $value) ) {
            $result->invalidate($tag, 'Podaj poprawny numer NIP');
        }
    }
    
    return $result;
}
add_filter( 'wpcf7_validate_text', 'prefiks_text_validation', 10, 2 );
add_filter( 'wpcf7_validate_text*', 'prefiks_text_validation', 10, 2 );

Krok 5

I zapisujmy to jakoś do bazy, żeby w panelu można było wyeksportować listę zapisanych
function prefiks_process_submitted_form_data( $contact_form ) {
    $submission = WPCF7_Submission::get_instance();
    if ( $submission ) {
        // Pobierz dane przesłane w formularzu
        $submitted_name = $submission->get_posted_data('your-name');
        $submitted_email = $submission->get_posted_data('your-email');
        // ...

        global $wpdb;
        $wpdb->insert( ... );
    }
}
add_action( 'wpcf7_before_send_mail', 'prefiks_process_submitted_form_data' );

Krok 6

A ponieważ współpracujemy z różnymi firmami, które nas polecają, to dodajmy do maila informację o polecającym
function prefiks_print_additional_fields($content) {
    $fields = array('aff_id', 'polec_id', 'ref_id');

    foreach ( $fields as $f ) {
        if ( isset($_GET[$f]) ) {
            $content .= '';
        }
    }

    return $content;
}
add_filter( 'wpcf7_form_elements', 'prefiks_print_additional_fields' );

Ale filtry potrafią także zaszkodzić…

Czy filtry działają tylko tam, gdzie powinny?

function prefiks_modify_query( $query ) {
    $query->set( 'posts_per_page', 2 );
}
add_action( 'pre_get_posts', 'prefiks_modify_query' );

Czy nie mieszacie akcji z filtrami?

function prefiks_bad_action( $content ) {
    echo 'x';
}
add_action( 'the_content', 'prefiks_bad_action' );

Czy to Twój kod odpowiada na Twój request?

Świetnym przykładem nieświadomości konsekwencji użycia filtrów/akcji jest Wordfence, który bazując na akcjach wp_ajax pozwala dowolnemu fragmentowi kodu odpowiadać na requesty sprawdzające i modyfikować ich treść.

Dzięki za uwagę!

Krzysiek Dróżdż
krzysiek@wpmagus.pl
WPmagus.pl