Plugin Directory

Changeset 450793


Ignore:
Timestamp:
10/13/2011 09:27:39 PM (13 years ago)
Author:
sirzooro
Message:

check badwords in tags, check and enforce 1st link position, check min link count, configure build-in wp defines, delete abandoned drafts, do not save invalid posts, lock users who abandons drafts, check post thumbnails, added filters and few fixes

File:
1 edited

Legend:

Unmodified
Added
Removed
  • wypiekacz/trunk/wypiekacz.php

    r259125 r450793  
    55Description: Checks if posts submitted for review and posted satisfies set of rules.
    66Author: Daniel Frużyński
    7 Version: 2.1.1
     7Version: 2.2
    88Author URI: http://www.poradnik-webmastera.com/
    99Text Domain: wypiekacz
     10License: GPL2
    1011*/
    1112
    12 /*  Copyright 2009-2010  Daniel Frużyński  (email : daniel [A-T] poradnik-webmastera.com)
     13/*  Copyright 2009-2011  Daniel Frużyński  (email : daniel [A-T] poradnik-webmastera.com)
    1314
    1415    This program is free software; you can redistribute it and/or modify
    15     it under the terms of the GNU General Public License as published by
    16     the Free Software Foundation; either version 2 of the License, or
    17     (at your option) any later version.
     16    it under the terms of the GNU General Public License, version 2, as
     17    published by the Free Software Foundation.
    1818
    1919    This program is distributed in the hope that it will be useful,
     
    2828
    2929
    30 if ( !class_exists( 'WyPiekacz' ) || ( defined( 'WP_DEBUG') && WP_DEBUG ) ) {
     30if ( !class_exists( 'WyPiekacz' ) || ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) {
     31
     32/*// Include compatibility file if WP version is not current (WP 3.2.x)
     33if ( version_compare( $wp_version, '3.2', '<' ) ) {
     34    include( dirname( __FILE__ ) . '/compat.php' );
     35}*/
    3136
    3237class WyPiekacz {
     
    3742    // Link counter - used by RX for removing links
    3843    var $link_counter = 0;
     44    // Number of initial links to remove - used by RX for removing links
     45    var $links_to_remove = 0;
    3946    // List of supported post types
    4047    var $post_types = array();
     48    // Flag if we are deleting orphaned posts, to avoid infinite recursion
     49    var $deleting_orphaned_posts = false;
    4150   
    4251    // WP versions
     52    var $has_wp_28 = false;
     53    var $has_wp_29 = false;
    4354    var $has_wp_30 = false;
     55   
     56    // True if User Locker 1.2+ is active
     57    var $has_user_locker = false;
    4458   
    4559    // Constructor
    4660    function WyPiekacz() {
    4761        global $wp_version;
    48         $this->has_wp_30 = version_compare( $wp_version, '2.999', '>' );
     62        $this->has_wp_28 = version_compare( $wp_version, '2.7.999', '>' );
     63        $this->has_wp_29 = version_compare( $wp_version, '2.8.999', '>' );
     64        $this->has_wp_30 = version_compare( $wp_version, '2.9.999', '>' );
    4965       
    5066        // Initialise plugin
     
    85101            // Default post template handling
    86102            add_action( 'submitpost_box', array( &$this, 'submitpost_box' ) );
     103           
     104            // Unload autosave script if needed
     105            if ( get_option( 'wypiekacz_autosave_interval' ) == 0 ) {
     106                add_action( 'wp_print_scripts', array( &$this, 'wp_print_scripts' ) );
     107            }
    87108        }
    88109    }
     
    91112    function init() {
    92113        load_plugin_textdomain( 'wypiekacz', false, dirname( plugin_basename( __FILE__ ) ).'/lang' );
     114       
     115        // Check if User Locker 1.2+ is active
     116        if ( function_exists( 'user_locker_lock_user' ) ) {
     117            $this->has_user_locker = true;
     118           
     119            // Register hooks for filters and actions which depends on User Locker
     120            add_action( 'user_locker_unlock_user', array( &$this, 'user_locker_unlock_user' ) );
     121            add_action( 'user_locker_enable_user', array( &$this, 'user_locker_unlock_user' ) );
     122           
     123            if ( is_admin() ) {
     124                // Add new column to the user list
     125                add_filter( 'manage_users_columns', array( &$this, 'manage_users_columns' ) );
     126                add_filter( 'manage_users_custom_column', array( &$this, 'manage_users_custom_column' ), 10, 3 );
     127            }
     128        }
    93129    }
    94130   
     
    123159        register_setting( 'wypiekacz', 'wypiekacz_min_len', array( &$this, 'sanitize_nonnegative' ) );
    124160        register_setting( 'wypiekacz', 'wypiekacz_min_len_words', array( &$this, 'sanitize_nonnegative' ) );
     161        register_setting( 'wypiekacz', 'wypiekacz_min_links', array( &$this, 'sanitize_nonnegative' ) );
    125162        register_setting( 'wypiekacz', 'wypiekacz_max_links', array( &$this, 'sanitize_nonnegative' ) );
     163        register_setting( 'wypiekacz', 'wypiekacz_link_after', array( &$this, 'sanitize_nonnegative' ) );
     164        register_setting( 'wypiekacz', 'wypiekacz_link_after_words', array( &$this, 'sanitize_nonnegative' ) );
    126165        register_setting( 'wypiekacz', 'wypiekacz_min_title_len', array( &$this, 'sanitize_nonnegative' ) );
    127166        register_setting( 'wypiekacz', 'wypiekacz_min_title_len_words', array( &$this, 'sanitize_nonnegative' ) );
     
    139178        register_setting( 'wypiekacz', 'wypiekacz_pass_reset_email', array( &$this, 'sanitize_01' ) );
    140179        register_setting( 'wypiekacz', 'wypiekacz_right_now_stats', array( &$this, 'sanitize_01' ) );
     180        register_setting( 'wypiekacz', 'wypiekacz_post_menu_links', array( &$this, 'sanitize_01' ) );
    141181        register_setting( 'wypiekacz', 'wypiekacz_badwords', array( &$this, 'sanitize_stringlist' ) );
    142182        register_setting( 'wypiekacz', 'wypiekacz_check_badwords_title', array( &$this, 'sanitize_01' ) );
    143183        register_setting( 'wypiekacz', 'wypiekacz_check_badwords_content', array( &$this, 'sanitize_01' ) );
     184        register_setting( 'wypiekacz', 'wypiekacz_check_badwords_tags', array( &$this, 'sanitize_01' ) );
    144185        register_setting( 'wypiekacz', 'wypiekacz_goodwords', array( &$this, 'sanitize_stringlist' ) );
     186        register_setting( 'wypiekacz', 'wypiekacz_post_thumbnail', array( &$this, 'sanitize_01' ) );
    145187        register_setting( 'wypiekacz', 'wypiekacz_enforce_links', array( &$this, 'sanitize_01' ) );
     188        register_setting( 'wypiekacz', 'wypiekacz_enforce_link_positions', array( &$this, 'sanitize_01' ) );
    146189        register_setting( 'wypiekacz', 'wypiekacz_enforce_title', array( &$this, 'sanitize_01' ) );
    147190        register_setting( 'wypiekacz', 'wypiekacz_enforce_add_dots', array( &$this, 'sanitize_01' ) );
     
    150193        register_setting( 'wypiekacz', 'wypiekacz_allow_skip_rules', array( &$this, 'sanitize_01' ) );
    151194        register_setting( 'wypiekacz', 'wypiekacz_post_types', array( &$this, 'sanitize_post_types' ) );
     195        register_setting( 'wypiekacz', 'wypiekacz_dont_save_invalid_post', array( &$this, 'sanitize_01' ) );
     196        register_setting( 'wypiekacz', 'wypiekacz_autosave_interval', array( &$this, 'sanitize_nonnegative' ) );
     197        register_setting( 'wypiekacz', 'wypiekacz_post_revisions', array( &$this, 'sanitize_nonnegative_or_minus1' ) );
     198        register_setting( 'wypiekacz', 'wypiekacz_empty_trash_days', array( &$this, 'sanitize_nonnegative' ) );
     199        register_setting( 'wypiekacz', 'wypiekacz_delete_orphaned_drafts', array( &$this, 'sanitize_nonnegative' ) );
     200        register_setting( 'wypiekacz', 'wypiekacz_force_delete_orphaned_drafts', array( &$this, 'sanitize_01' ) );
     201       
     202        // Do not register these options if User Locker is not active - otherwise WP would clear them when options are saved
     203        if ( $this->has_user_locker ) {
     204            register_setting( 'wypiekacz', 'wypiekacz_lock_account', array( &$this, 'sanitize_01' ) );
     205            register_setting( 'wypiekacz', 'wypiekacz_lock_account_after', array( &$this, 'sanitize_positive' ) );
     206            register_setting( 'wypiekacz', 'wypiekacz_lock_method', array( &$this, 'sanitize_01' ) );
     207            register_setting( 'wypiekacz', 'wypiekacz_lock_reason', 'trim' );
     208            register_setting( 'wypiekacz', 'wypiekacz_lock_show_details', array( &$this, 'sanitize_01' ) );
     209        }
    152210    }
    153211   
     
    163221            'WyPiekacz', 'manage_options', __FILE__, array( &$this, 'options_panel' ) );
    164222       
     223        // Add links to Posts menu
     224        if ( get_option( 'wypiekacz_post_menu_links' ) ) {
     225            $can_edit = current_user_can( 'edit_posts' );
     226            $can_publish = current_user_can('publish_posts');
     227            if ( $can_edit || $can_publish ) {
     228                $num_posts = wp_count_posts( 'post' );
     229               
     230                $drafts = $num_posts->draft;
     231                add_submenu_page( 'edit.php', __('Drafts', 'wypiekacz'),
     232                    sprintf( __('Drafts %s', 'wypiekacz'), "<span class='awaiting-mod count-$drafts'><span class='pending-count'>" .
     233                        number_format_i18n( $drafts ) . "</span></span>" ),
     234                    'edit_posts', 'edit.php?post_status=draft' );
     235               
     236                $pending = $num_posts->pending;
     237                add_submenu_page( 'edit.php', __('Pending', 'wypiekacz'),
     238                    sprintf( __('Pending %s', 'wypiekacz'), "<span class='awaiting-mod count-$pending'><span class='pending-count'>" .
     239                        number_format_i18n( $pending ) . "</span></span>" ),
     240                    'edit_posts', 'edit.php?post_status=pending' );
     241            }
     242        }
     243       
    165244        // Add metabox to edit post page
    166245        foreach ( $this->post_types as $post_type ) {
     
    181260        global $post;
    182261        $meta = '';
    183         if ( $this->post_page && is_object( $post) ) {
     262        if ( $this->post_page && is_object( $post ) ) {
    184263            $meta = get_post_meta($post->ID, 'WyPiekacz_msg', true);
    185264        }
     
    187266            // Display error message
    188267            echo '<div id="notice" class="error"><p>', $meta,
    189                 '<br />', __('Post Status has been changed to Draft.', 'wypiekacz'), '</p></div>', "\n";
     268                '<br />', __('Post Status has been changed to Draft.', 'wypiekacz');
     269            if ( isset( $_GET['message'] ) && ( $_GET['message'] == '85614' ) ) {
     270                echo '<br />', __('Post was *NOT* saved.', 'wypiekacz');
     271            }
     272            echo '</p></div>', "\n";
     273           
    190274            // Remove this message
    191275            delete_post_meta( $post->ID, 'WyPiekacz_msg' );
    192             // Change WP message to 'Post saved'
    193             if ( isset( $_GET['message'] ) ) {
    194                 if ( '6' == $_GET['message'] ) {
    195                     $_GET['message'] = '7';
     276           
     277            // redirect_post_location filter is supported starting from WP2.9
     278            if ( !$this->has_wp_29 ) { // 2.8 and below
     279                // Change WP message to 'Post saved'
     280                if ( isset( $_GET['message'] ) ) {
     281                    if ( '6' == $_GET['message'] ) {
     282                        $_GET['message'] = '7';
     283                    } elseif ( '85614' == $_GET['message'] ) {
     284                        unset( $_GET['message'] );
     285                    }
    196286                }
    197287            }
     
    203293        if ( count( $this->errors ) > 0 ) {
    204294            $location = remove_query_arg( 'message', $location );
    205             $location = add_query_arg( 'message', '10', $location );
     295            // When invalid post was not saved, WyPiekacz will display appropriate message
     296            if ( get_option( 'wypiekacz_dont_save_invalid_post' ) ) {
     297                $location = add_query_arg( 'message', '85614', $location );
     298            } else {
     299                $location = add_query_arg( 'message', '10', $location );
     300            }
    206301        }
    207302        return $location;
     
    210305    // Check submitted post data
    211306    function wp_insert_post( $data, $post_arr ) {
     307        // Skip post revisions and auto-drafts
     308        if ( ( $data['post_type'] == 'revision' ) || ( $data['post_status'] == 'auto-draft' ) ) {
     309            return $data;
     310        }
     311
     312        // TODO: although it is possible to stop creation of auto drafts from here by breaking the query,
     313        // it does not work as expected - there are many PHP warnings in debug mode, and finally
     314        // "You are not allowed to edit this post." error on next post save attempt.
     315        // WP Core must be fixed first in order to make this work.
     316       
     317       
     318        // Delete orphaned post drafts
     319        if ( $this->deleting_orphaned_posts ) { // Avoid infinite recursion
     320            return $data;
     321        } else {
     322            $this->deleting_orphaned_posts = true;
     323            $this->delete_orphaned_drafts();
     324            $this->deleting_orphaned_posts = false;
     325        }
     326       
    212327        if (
    213             // Check only for posts
     328            // Check selected post types only
    214329            in_array( $data['post_type'], $this->post_types )
    215             // Check only if status is Published or Pending Review
     330            // Check only if status is Published or Pending Review or Future
    216331            && ( in_array( $data['post_status'], array( 'publish', 'pending', 'future' ) ) )
    217332            // Editors (and above) can have limits too
     
    240355           
    241356            if ( !$skip_check ) {
     357                // Enforce some rules before checking them
    242358                $data = $this->enforce_rules( $data );
    243359               
     360                // Check rules
    244361                $result = $this->check_precel_post( $data['post_content'], $data['post_title'], $post_arr );
    245362                if ( true !== $result ) {
     
    259376                        return new WP_Error( 'edit_refused', implode( '; ', $errors ) );
    260377                    }*/
     378                   
     379                    // Do not save invalid post if user asked for this. So far the only way is to break SQL query, so register new filter to do this.
     380                    if ( get_option( 'wypiekacz_dont_save_invalid_post' ) ) {
     381                        // save_post hook will not be called later, so need to perform some extra steps here
     382                       
     383                        // Save errors to post meta
     384                        if ( isset( $post_arr['ID'] ) ) {
     385                            delete_post_meta( $post_arr['ID'], 'WyPiekacz_msg' );
     386                            add_post_meta( $post_arr['ID'], 'WyPiekacz_msg', $this->pack_errors( '<br />' ), true );
     387                        }
     388                       
     389                        // Lock user account if needed
     390                        $this->lock_user_account( isset( $post_arr['ID'] ) ? $post_arr['ID'] : 0, false );
     391                       
     392                        // Do not want to execute any extra SQL queries - just proceeded to INSERT/UPDATE query for current post
     393                        remove_all_actions( 'pre_post_update' );
     394                       
     395                        // Now we can add the filter
     396                        add_filter( 'query', array( &$this, 'kill_sql_query' ) );
     397                       
     398                        // Do not execute any extra code beyond this point - return data only
     399                        return $data;
     400                    }
    261401                }
    262402            }
     
    264404       
    265405        return $data;
     406    }
     407   
     408    // Replace INSERT/UPDATE query with some junk. This filter is used to prevent creating/updating invalid post
     409    function kill_sql_query( $query ) {
     410        if ( preg_match( '/^\s*(insert|update:?)\s/i', $query ) ) {
     411            return 'xxx';
     412        } else {
     413            return $query;
     414        }
     415    }
     416   
     417    // Delete orphaned post drafts
     418    function delete_orphaned_drafts() {
     419        $interval = get_option( 'wypiekacz_delete_orphaned_drafts', 0 );
     420        if ( $interval == 0 ) { // Feature disabled
     421            return;
     422        }
     423        $force = get_option( 'wypiekacz_force_delete_orphaned_drafts' ) ? true : false;
     424       
     425        global $wpdb;
     426        $orphaned_posts = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_status = 'draft' AND DATE_SUB( NOW(), INTERVAL $interval DAY ) > post_date" );
     427        foreach ( (array) $orphaned_posts as $delete ) {
     428            wp_delete_post( $delete, $force );
     429        }
     430    }
     431   
     432    // Lock/disable user account if needed
     433    function lock_user_account( $post_id, $check_result ) {
     434        if ( !$this->has_user_locker || !get_option( 'wypiekacz_lock_account' ) ) {
     435            return;
     436        }
     437       
     438        if ( $post_id <= 0 ) { // Make sure post_id == -1 if it is unknown (new post)
     439            $post_id = -1;
     440        }
     441       
     442        $current_user = wp_get_current_user();
     443        $user_id = $current_user->ID;
     444       
     445        $last_post_id   = get_user_option( '_wypiekacz_last_post', $user_id, false );
     446        $bad_post_count = get_user_option( '_wypiekacz_bad_posts', $user_id, false );
     447        if ( empty( $last_post_id ) ) {
     448            $last_post_id = 0;
     449        }
     450        if ( empty( $bad_post_count ) ) {
     451            $bad_post_count = 0;
     452        }
     453       
     454        if ( $check_result ) { // Rule check succeeded
     455            $bad_post_count = 0; // Reset bad post count
     456        } else { // Rule check failed
     457            // Following cases are not checked here (do nothing for them):
     458            // id > 0, last_id == -1 : most probably last failed publish attempt was for the same post (new post), so do nothing
     459            // id == last_id > 0 : another failed publish attempt for the same post
     460           
     461            if ( $post_id < 0 ) { // Post ID is not known (new post)
     462                ++$bad_post_count;
     463            } elseif ( $last_post_id == 0 ) { // No user meta yet, treat this as a new post
     464                ++$bad_post_count;
     465            } elseif ( ( $last_post_id > 0 ) && ( $last_post_id != $post_id ) ) { // Post ID has changed
     466                ++$bad_post_count;
     467            }
     468        }
     469       
     470        update_user_option( $user_id, '_wypiekacz_last_post', $post_id, false );
     471        update_user_option( $user_id, '_wypiekacz_bad_posts', $bad_post_count, false );
     472       
     473        $max_count = get_option( 'wypiekacz_lock_account_after' );
     474        if ( $bad_post_count > $max_count ) {
     475            $reason = get_option( 'wypiekacz_lock_reason' );
     476            if ( get_option( 'wypiekacz_lock_method' ) == 0 ) {
     477                user_locker_lock_user( $user_id, $reason );
     478            } else {
     479                user_locker_disable_user( $user_id, $reason );
     480            }
     481           
     482            // Force logout
     483            wp_logout();
     484        }
     485    }
     486   
     487    // User Locker plugin unlocks/enable user account
     488    function user_locker_unlock_user( $user_id ) {
     489        // Clear our data
     490        update_user_option( $user_id, '_wypiekacz_last_post', 0, false );
     491        update_user_option( $user_id, '_wypiekacz_bad_posts', 0, false );
    266492    }
    267493   
    268494    // Called after post is saved - save error messages too
    269495    function save_post( $post_ID ) {
     496        $post = get_post( $post_ID );
     497       
     498        // Skip post revisions and auto-drafts
     499        if ( ( $post->post_type == 'revision' ) || ( $post->post_status == 'auto-draft' ) ) {
     500            return;
     501        }
     502       
    270503        // Check if 'Skip rule check' option was checked
    271         // verify this came from the our screen and with proper authorisation,
     504        // verify this came from the our screen and with proper authorization,
    272505        // because save_post can be triggered at other times
    273506        if ( get_option( 'wypiekacz_allow_skip_rules' ) && isset( $_POST['wypiekacz_nonce'] ) &&
     
    287520        if ( count( $this->errors ) > 0 ) {
    288521            delete_post_meta( $post_ID, 'WyPiekacz_msg' );
    289             add_post_meta( $post_ID, 'WyPiekacz_msg', implode( '<br />', $this->errors ), true );
     522            add_post_meta( $post_ID, 'WyPiekacz_msg', $this->pack_errors( '<br />' ), true );
     523           
     524            // Lock user account if needed
     525            $this->lock_user_account( $post_ID, false );
     526        }
     527       
     528        // Lock/unlock user account if needed
     529        $autosave = defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE;
     530        if ( !$autosave ) {
     531            $this->lock_user_account( $post_ID, count( $this->errors ) == 0 );
    290532        }
    291533    }
     
    310552                    _e('OK', 'wypiekacz');
    311553                } else {
    312                     echo '<span style="color:red">', implode( '<br />', $this->errors ), "</span>\n";
     554                    echo '<span style="color:red">', $this->pack_errors( '<br />' ), "</span>\n";
    313555                    $this->errors = array();
    314556                }
     
    319561    // Callback - remove extra links
    320562    function rx_remove_links( $matches ) {
     563        // Remove links placed too early first
     564        if ( $this->links_to_remove > 0 ) {
     565            --$this->links_to_remove;
     566            return $matches[2];
     567        }
     568       
     569        // Remove links above limit
    321570        ++$this->link_counter;
    322571        if ( $this->link_counter > get_option( 'wypiekacz_max_links' ) ) {
     
    348597    // Enforce rules for posts
    349598    function enforce_rules( $data ) {
    350         // Enforce max link count
    351         if ( get_option( 'wypiekacz_enforce_links' ) ) {
     599        // Find how many initial links should be removed
     600        $this->links_to_remove = 0;
     601        if ( get_option( 'wypiekacz_enforce_link_positions' ) ) {
     602            $first_link_after_chars = get_option( 'wypiekacz_link_after' );
     603            $first_link_after_words = get_option( 'wypiekacz_link_after_words' );
     604           
     605            $cnt = preg_match_all( '/<\s*a\s/i', $data['post_content'], $matches, PREG_OFFSET_CAPTURE );
     606            for ( $n = 0; $n < $cnt; ++$n ) {
     607                // $matches[0][N][1] contains match offsets for links
     608                $link_pos = $matches[0][$n][1];
     609                $text_before_link = substr( $data['post_content'], 0, $link_pos );
     610               
     611                // Check position of link (in characters)
     612                $text2 = preg_replace( '/\s\s+/', ' ', ltrim( wp_strip_all_tags( $text_before_link ) ) );
     613                $len = strlen( $text2 );
     614                if ( $len < $first_link_after_chars ) {
     615                    // Need to remove this link
     616                    ++$this->links_to_remove;
     617                    continue;
     618                }
     619               
     620                // Check position of link (in words)
     621                $count = $this->count_words( $text_before_link );
     622                if ( $count < $first_link_after_words ) {
     623                    // Need to remove this link
     624                    ++$this->links_to_remove;
     625                    continue;
     626                }
     627               
     628                // Link is in correct place - next links will be too, so exit loop
     629                break;
     630            }
     631        }
     632       
     633        // Enforce max link count and link positions
     634        if ( ( $this->links_to_remove > 0 ) || get_option( 'wypiekacz_enforce_links' ) ) {
    352635            $this->link_counter = 0;
    353636            $data['post_content'] = preg_replace_callback( '#(<a [^>]*href\s*=\s*[^>]+[^>]*>)(.*?)(</a>)#',
     
    409692        /*if ( get_option( 'wypiekacz_enforce_tags' ) ) {
    410693        }*/
     694       
     695        // Allow other plugins to enforce additional rules
     696        $data = apply_filters( 'wypiekacz_enforce_rules', $data );
    411697       
    412698        return $data;
     
    604890        // Check length (in characters)
    605891        $min_len = get_option( 'wypiekacz_min_len' );
    606         $text2 = preg_replace( '/\s\s+/', ' ', trim( strip_tags( $text ) ) );
     892        $text2 = preg_replace( '/\s\s+/', ' ', trim( wp_strip_all_tags( $text ) ) );
    607893        $len = strlen( $text2 );
    608894        if ( $len < $min_len ) {
    609             $this->errors[] = sprintf( __('Post is too short (minimum is %1$s chars, your post has %2$s).', 'wypiekacz'),
    610                 $min_len, $len );
     895            $this->errors[] = array( 'min_len_chars', sprintf( __('Post is too short (minimum is %1$s chars, your post has %2$s).', 'wypiekacz'),
     896                $min_len, $len ) );
    611897        }
    612898       
     
    615901        $count = $this->count_words( $text );
    616902        if ( $count < $min_len ) {
    617             $this->errors[] = sprintf( __('Post is too short (minimum is %1$s words, your post has %2$s).', 'wypiekacz'),
    618                 $min_len, $count );
     903            $this->errors[] = array( 'min_len_words', sprintf( __('Post is too short (minimum is %1$s words, your post has %2$s).', 'wypiekacz'),
     904                $min_len, $count ) );
    619905        }
    620906       
    621907        // Check links
     908        $min_links = get_option( 'wypiekacz_min_links' );
    622909        $max_links = get_option( 'wypiekacz_max_links' );
    623         $cnt = preg_match_all( '/<\s*a\s/i', $text, $matches );
    624         if ( $cnt > $max_links ) {
    625             $this->errors[] = sprintf( __('Post contains too many links (maximum is %1$s, your post has %2$s).', 'wypiekacz'),
    626                 $max_links, $cnt );
     910        $cnt = preg_match_all( '/<\s*a\s/i', $text, $matches, PREG_OFFSET_CAPTURE );
     911        if ( $cnt < $min_links ) {
     912            $this->errors[] = array( 'min_links', sprintf( __('Post contains too few links (minimum is %1$s, your post has %2$s).', 'wypiekacz'),
     913                $max_links, $cnt ) );
     914        } elseif ( $cnt > $max_links ) {
     915            $this->errors[] = array( 'max_links', sprintf( __('Post contains too many links (maximum is %1$s, your post has %2$s).', 'wypiekacz'),
     916                $max_links, $cnt ) );
     917        }
     918       
     919        if ( $cnt > 0 ) {
     920            // $matches[0][N][1] contains match offsets for links
     921            $link_pos = $matches[0][0][1];
     922            $text_before_link = substr( $text, 0, $link_pos );
     923           
     924            // Check position of first link (in characters)
     925            $first_link_after = get_option( 'wypiekacz_link_after' );
     926            $text2 = preg_replace( '/\s\s+/', ' ', ltrim( wp_strip_all_tags( $text_before_link ) ) );
     927            $len = strlen( $text2 );
     928            if ( $len < $first_link_after ) {
     929                $this->errors[] = array( 'link_after_chars', sprintf( __('First link is too close to the beginning (minimum is after %1$s chars, your link is after %2$s).', 'wypiekacz'),
     930                    $first_link_after, $len ) );
     931            }
     932           
     933            // Check position of first link (in words)
     934            $first_link_after = get_option( 'wypiekacz_link_after_words' );
     935            $count = $this->count_words( $text_before_link );
     936            if ( $count < $first_link_after ) {
     937                $this->errors[] = array( 'link_after_words', sprintf( __('First link is too close to the beginning (minimum is after %1$s words, your link is after %2$s).', 'wypiekacz'),
     938                    $first_link_after, $count ) );
     939            }
    627940        }
    628941       
     
    633946        $len = strlen( $text2 );
    634947        if ( $len < $min_len ) {
    635             $this->errors[] = sprintf( __('Post Title is too short (minimum is %1$s chars, your Title has %2$s).', 'wypiekacz'),
    636                 $min_len, $len );
     948            $this->errors[] = array( 'min_title_len_chars', sprintf( __('Post Title is too short (minimum is %1$s chars, your Title has %2$s).', 'wypiekacz'),
     949                $min_len, $len ) );
    637950        }
    638951        elseif ( ( $max_len > 0 ) && ( $len > $max_len ) ) {
    639             $this->errors[] = sprintf( __('Post Title is too long (maximum is %1$s chars, your Title has %2$s).', 'wypiekacz'),
    640                 $max_len, $len );
     952            $this->errors[] = array( 'max_title_len_chars', sprintf( __('Post Title is too long (maximum is %1$s chars, your Title has %2$s).', 'wypiekacz'),
     953                $max_len, $len ) );
    641954        }
    642955       
     
    646959        $count = $this->count_words( $title );
    647960        if ( $count < $min_len ) {
    648             $this->errors[] = sprintf( __('Post Title is too short (minimum is %1$s words, your Title has %2$s).', 'wypiekacz'),
    649                 $min_len, $count );
     961            $this->errors[] = array( 'min_title_len_words', sprintf( __('Post Title is too short (minimum is %1$s words, your Title has %2$s).', 'wypiekacz'),
     962                $min_len, $count ) );
    650963        }
    651964        elseif ( ( $max_len > 0 ) && ( $count > $max_len ) ) {
    652             $this->errors[] = sprintf( __('Post Title is too long (maximum is %1$s words, your Title has %2$s).', 'wypiekacz'),
    653                 $max_len, $count );
     965            $this->errors[] = array( 'max_title_len_words', sprintf( __('Post Title is too long (maximum is %1$s words, your Title has %2$s).', 'wypiekacz'),
     966                $max_len, $count ) );
    654967        }
    655968       
     
    6871000        $use_default_cat = get_option( 'wypiekacz_use_def_cat' );
    6881001        if ( !$use_default_cat && $has_default_cat ) {
    689             $this->errors[] = sprintf( __('Cannot add posts to the default category (%s).', 'wypiekacz'),
    690                 get_cat_name( $default_cat ) );
     1002            $this->errors[] = array( 'no_def_cat', sprintf( __('Cannot add posts to the default category (%s).', 'wypiekacz'),
     1003                get_cat_name( $default_cat ) ) );
    6911004        }
    6921005       
     
    7001013        $max_cats = get_option( 'wypiekacz_max_cats' );
    7011014        if ( $post_cat_cnt < $min_cats ) {
    702             $this->errors[] = sprintf( __('Too few categories selected (minimum is %1$s, your post has %2$s).', 'wypiekacz'),
    703                 $min_cats, $post_cat_cnt );
     1015            $this->errors[] = array( 'min_cats', sprintf( __('Too few categories selected (minimum is %1$s, your post has %2$s).', 'wypiekacz'),
     1016                $min_cats, $post_cat_cnt ) );
    7041017        } else if ( $post_cat_cnt > $max_cats ) {
    705             $this->errors[] = sprintf( __('Too many categories selected (maximum is %1$s, your post has %2$s).', 'wypiekacz'),
    706                 $max_cats, $post_cat_cnt );
     1018            $this->errors[] = array( 'max_cats', sprintf( __('Too many categories selected (maximum is %1$s, your post has %2$s).', 'wypiekacz'),
     1019                $max_cats, $post_cat_cnt ) );
    7071020        }
    7081021       
    7091022        if ( is_array( $post_data ) ) {
    710             //var_dump($post_data);die;
    7111023            $post_tag_cnt = 0;
    7121024            $tags = array();
     
    7501062       
    7511063        if ( $post_tag_cnt < $min_tags ) {
    752             $this->errors[] = sprintf( __('Too few tags (minimum is %1$s, your post has %2$s).', 'wypiekacz'),
    753                 $min_tags, $post_tag_cnt );
     1064            $this->errors[] = array( 'min_tags', sprintf( __('Too few tags (minimum is %1$s, your post has %2$s).', 'wypiekacz'),
     1065                $min_tags, $post_tag_cnt ) );
    7541066        } else if ($post_tag_cnt > $max_tags) {
    755             $this->errors[] = sprintf( __('Too many tags (maximum is %1$s, your post has %2$s).', 'wypiekacz'),
    756                 $max_tags, $post_tag_cnt );
     1067            $this->errors[] = array( 'max_tags', sprintf( __('Too many tags (maximum is %1$s, your post has %2$s).', 'wypiekacz'),
     1068                $max_tags, $post_tag_cnt ) );
    7571069        }
    7581070       
     
    7611073            $words_found = $this->check_badwords( $title );
    7621074            if ( count( $words_found ) > 0 ) {
    763                 $this->errors[] = sprintf( __('Forbidden word(s) in title: %s', 'wypiekacz'),
    764                     implode( ', ', $words_found ) );
     1075                $this->errors[] = array( 'badwords_title', sprintf( __('Forbidden word(s) in title: %s', 'wypiekacz'),
     1076                    implode( ', ', $words_found ) ) );
    7651077            }
    7661078        }
     
    7701082            $words_found = $this->check_badwords( $text );
    7711083            if ( count( $words_found ) > 0 ) {
    772                 $this->errors[] = sprintf( __('Forbidden word(s) in content: %s', 'wypiekacz'),
    773                     implode( ', ', $words_found ) );
    774             }
    775         }
     1084                $this->errors[] = array( 'badwords_content', sprintf( __('Forbidden word(s) in content: %s', 'wypiekacz'),
     1085                    implode( ', ', $words_found ) ) );
     1086            }
     1087        }
     1088       
     1089        // Check forbidden words in tags
     1090        if ( get_option( 'wypiekacz_check_badwords_tags' ) ) {
     1091            // Convert objects to string if needed
     1092            if ( ( count( $tags ) > 0 ) && is_object( $tags[0] ) ) {
     1093                $tags_to_check = array();
     1094                foreach ( $tags as $tag ) {
     1095                    $tags_to_check[] = $tag->name;
     1096                }
     1097            } else {
     1098                $tags_to_check = $tags;
     1099            }
     1100           
     1101            $words_found = $this->check_badwords( implode( ',', $tags_to_check ) );
     1102            if ( count( $words_found ) > 0 ) {
     1103                $this->errors[] = array( 'badwords_tags', sprintf( __('Forbidden word(s) in tags: %s', 'wypiekacz'),
     1104                    implode( ', ', $words_found ) ) );
     1105            }
     1106        }
     1107       
     1108        // Check post thumbnail
     1109        if ( get_option( 'wypiekacz_post_thumbnail' ) ) {
     1110            $post_id = 0;
     1111            if ( is_array( $post_data ) ) {
     1112                // Get ID from POST data
     1113                if ( isset( $post_data['ID'] ) ) {
     1114                    $post_id = (int)$post_data['ID'];
     1115                }
     1116            } else {
     1117                // Get ID from Post object
     1118                $post_id = $post_data->ID;
     1119            }
     1120           
     1121            $has_thumbnail = false;
     1122            if ( ( $post_id > 0 ) && function_exists( 'has_post_thumbnail' ) ) {
     1123                $has_thumbnail = has_post_thumbnail( $post_id );
     1124            }
     1125           
     1126            $has_thumbnail = apply_filters( 'wypiekacz_check_thumbnail', $has_thumbnail, $post_id, $post_data );
     1127           
     1128            if ( !$has_thumbnail ) {
     1129                $this->errors[] = array( 'post_thumbnail', __('Post thumbnail (Featured image) is required.', 'wypiekacz') );
     1130            }
     1131        }
     1132       
     1133        // Allow other plugins to check additional rules
     1134        $this->errors = apply_filters( 'wypiekacz_check_post', $this->errors, $text, $title, $post_data );
    7761135       
    7771136        return count( $this->errors ) == 0;
     
    8511210    }
    8521211   
     1212    // Convert error array to displayable form
     1213    function pack_errors( $separator ) {
     1214        $ret = '';
     1215        $first = true;
     1216        foreach ( $this->errors as $error ) {
     1217            if ( $first ) {
     1218                $first = false;
     1219            } else {
     1220                $ret .= $separator;
     1221            }
     1222            $ret .= $error[1];
     1223        }
     1224        return $ret;
     1225    }
     1226   
     1227    // Unload autosave script if needed
     1228    function wp_print_scripts() {
     1229        wp_deregister_script( 'autosave' );
     1230    }
     1231   
     1232    // Add new column to the user list page
     1233    function manage_users_columns( $columns ) {
     1234        // This requires WP 2.8+
     1235        global $wp_version;
     1236        if ( $this->has_wp_28 && $this->has_user_locker && get_option( 'wypiekacz_lock_show_details' ) ) {
     1237            $columns['wypiekacz'] = 'WyPiekacz';
     1238        }
     1239        return $columns;
     1240    }
     1241   
     1242    // Add column content for each user on user list
     1243    function manage_users_custom_column( $value, $column_name, $user_id ) {
     1244        if ( $column_name == 'wypiekacz' ) {
     1245            $last_post_id   = get_user_option( '_wypiekacz_last_post', $user_id, false );
     1246            $bad_post_count = get_user_option( '_wypiekacz_bad_posts', $user_id, false );
     1247            if ( empty( $last_post_id ) ) {
     1248                $last_post_id = 0;
     1249            }
     1250            if ( empty( $bad_post_count ) ) {
     1251                $bad_post_count = 0;
     1252            }
     1253           
     1254            $value = sprintf( __('Bad Posts: %1$s, Last Post ID: %2$s', 'wypiekacz'), $bad_post_count, $last_post_id );
     1255        }
     1256       
     1257        return $value;
     1258    }
     1259   
    8531260    // Handle options panel
    8541261    function options_panel() {
     
    8691276</th>
    8701277<td>
    871 <input type="text" maxlength="6" size="10" id="wypiekacz_min_len" name="wypiekacz_min_len" value="<?php echo stripcslashes( get_option( 'wypiekacz_min_len' ) ); ?>" /><br /><?php _e('Note: if you want to check character count only, set minimum word count to zero.', 'wypiekacz'); ?>
     1278<input type="text" maxlength="6" size="10" id="wypiekacz_min_len" name="wypiekacz_min_len" value="<?php echo esc_attr( get_option( 'wypiekacz_min_len' ) ); ?>" /><br /><?php _e('Note: if you want to check character count only, set minimum word count to zero.', 'wypiekacz'); ?>
    8721279</td>
    8731280</tr>
     
    8781285</th>
    8791286<td>
    880 <input type="text" maxlength="6" size="10" id="wypiekacz_min_len_words" name="wypiekacz_min_len_words" value="<?php echo stripcslashes( get_option( 'wypiekacz_min_len_words' ) ); ?>" /><br /><?php _e('Note: if you want to check word count only, set minimum character count to zero.', 'wypiekacz'); ?>
     1287<input type="text" maxlength="6" size="10" id="wypiekacz_min_len_words" name="wypiekacz_min_len_words" value="<?php echo esc_attr( get_option( 'wypiekacz_min_len_words' ) ); ?>" /><br /><?php _e('Note: if you want to check word count only, set minimum character count to zero.', 'wypiekacz'); ?>
     1288</td>
     1289</tr>
     1290
     1291<tr>
     1292<th scope="row" style="text-align:right; vertical-align:top;">
     1293<label for="wypiekacz_min_links"><?php _e('Minimum link count in post:', 'wypiekacz'); ?></label>
     1294</th>
     1295<td>
     1296<input type="text" maxlength="4" size="10" id="wypiekacz_min_links" name="wypiekacz_min_links" value="<?php echo esc_attr( get_option( 'wypiekacz_min_links' ) ); ?>" />
    8811297</td>
    8821298</tr>
     
    8871303</th>
    8881304<td>
    889 <input type="text" maxlength="2" size="10" id="wypiekacz_max_links" name="wypiekacz_max_links" value="<?php echo stripcslashes( get_option( 'wypiekacz_max_links' ) ); ?>" />
     1305<input type="text" maxlength="4" size="10" id="wypiekacz_max_links" name="wypiekacz_max_links" value="<?php echo esc_attr( get_option( 'wypiekacz_max_links' ) ); ?>" />
     1306</td>
     1307</tr>
     1308
     1309<tr>
     1310<th scope="row" style="text-align:right; vertical-align:top;">
     1311<label for="wypiekacz_link_after"><?php _e('First link is allowed after N initial characters:', 'wypiekacz'); ?></label>
     1312</th>
     1313<td>
     1314<input type="text" maxlength="6" size="10" id="wypiekacz_link_after" name="wypiekacz_link_after" value="<?php echo esc_attr( get_option( 'wypiekacz_link_after' ) ); ?>" /><br /><?php _e('Note: if you want to check character count only, set minimum word count to zero.', 'wypiekacz'); ?>
     1315</td>
     1316</tr>
     1317
     1318<tr>
     1319<th scope="row" style="text-align:right; vertical-align:top;">
     1320<label for="wypiekacz_link_after_words"><?php _e('First link is allowed after N initial words:', 'wypiekacz'); ?></label>
     1321</th>
     1322<td>
     1323<input type="text" maxlength="6" size="10" id="wypiekacz_link_after_words" name="wypiekacz_link_after_words" value="<?php echo esc_attr( get_option( 'wypiekacz_link_after_words' ) ); ?>" /><br /><?php _e('Note: if you want to check word count only, set minimum character count to zero.', 'wypiekacz'); ?>
     1324</td>
     1325</tr>
     1326
     1327<tr>
     1328<th scope="row" style="text-align:right; vertical-align:top;">
     1329<label for="wypiekacz_post_thumbnail"><?php _e('Post thumbnail (Featured image) is required:', 'wypiekacz'); ?></label>
     1330</th>
     1331<td>
     1332<input type="checkbox" id="wypiekacz_post_thumbnail" name="wypiekacz_post_thumbnail" value="yes" <?php checked( 1, get_option( 'wypiekacz_post_thumbnail' ) ); ?> />
     1333<br /><?php _e('Note: WyPiekacz supports WordPress Featured image by default. You can also provide your own function to check if Post thumbnail is present - see FAQ for more details.', 'wypiekacz'); ?>
    8901334</td>
    8911335</tr>
     
    8981342</th>
    8991343<td>
    900 <input type="text" maxlength="3" size="10" id="wypiekacz_min_title_len" name="wypiekacz_min_title_len" value="<?php echo stripcslashes( get_option( 'wypiekacz_min_title_len' ) ); ?>" /><br /><?php _e('Note: if you want to check character count only, set minimum word count to zero.', 'wypiekacz'); ?>
     1344<input type="text" maxlength="3" size="10" id="wypiekacz_min_title_len" name="wypiekacz_min_title_len" value="<?php echo esc_attr( get_option( 'wypiekacz_min_title_len' ) ); ?>" /><br /><?php _e('Note: if you want to check character count only, set minimum word count to zero.', 'wypiekacz'); ?>
    9011345</td>
    9021346</tr>
     
    9071351</th>
    9081352<td>
    909 <input type="text" maxlength="3" size="10" id="wypiekacz_min_title_len_words" name="wypiekacz_min_title_len_words" value="<?php echo stripcslashes( get_option( 'wypiekacz_min_title_len_words' ) ); ?>" /><br /><?php _e('Note: if you want to check word count only, set minimum character count to zero.', 'wypiekacz'); ?>
     1353<input type="text" maxlength="3" size="10" id="wypiekacz_min_title_len_words" name="wypiekacz_min_title_len_words" value="<?php echo esc_attr( get_option( 'wypiekacz_min_title_len_words' ) ); ?>" /><br /><?php _e('Note: if you want to check word count only, set minimum character count to zero.', 'wypiekacz'); ?>
    9101354</td>
    9111355</tr>
     
    9161360</th>
    9171361<td>
    918 <input type="text" maxlength="3" size="10" id="wypiekacz_max_title_len" name="wypiekacz_max_title_len" value="<?php echo stripcslashes( get_option( 'wypiekacz_max_title_len' ) ); ?>" /><br /><?php _e('Note: if you want to check character count only, set maximum word count to zero.', 'wypiekacz'); ?>
     1362<input type="text" maxlength="3" size="10" id="wypiekacz_max_title_len" name="wypiekacz_max_title_len" value="<?php echo esc_attr( get_option( 'wypiekacz_max_title_len' ) ); ?>" /><br /><?php _e('Note: if you want to check character count only, set maximum word count to zero.', 'wypiekacz'); ?>
    9191363</td>
    9201364</tr>
     
    9251369</th>
    9261370<td>
    927 <input type="text" maxlength="3" size="10" id="wypiekacz_max_title_len_words" name="wypiekacz_max_title_len_words" value="<?php echo stripcslashes( get_option( 'wypiekacz_max_title_len_words' ) ); ?>" /><br /><?php _e('Note: if you want to check word count only, set maximum character count to zero.', 'wypiekacz'); ?>
     1371<input type="text" maxlength="3" size="10" id="wypiekacz_max_title_len_words" name="wypiekacz_max_title_len_words" value="<?php echo esc_attr( get_option( 'wypiekacz_max_title_len_words' ) ); ?>" /><br /><?php _e('Note: if you want to check word count only, set maximum character count to zero.', 'wypiekacz'); ?>
    9281372</td>
    9291373</tr>
     
    9451389</th>
    9461390<td>
    947 <input type="text" maxlength="3" size="10" id="wypiekacz_min_cats" name="wypiekacz_min_cats" value="<?php echo stripcslashes( get_option( 'wypiekacz_min_cats' ) ); ?>" />
     1391<input type="text" maxlength="3" size="10" id="wypiekacz_min_cats" name="wypiekacz_min_cats" value="<?php echo esc_attr( get_option( 'wypiekacz_min_cats' ) ); ?>" />
    9481392</td>
    9491393</tr>
     
    9541398</th>
    9551399<td>
    956 <input type="text" maxlength="3" size="10" id="wypiekacz_max_cats" name="wypiekacz_max_cats" value="<?php echo stripcslashes( get_option( 'wypiekacz_max_cats' ) ); ?>" />
     1400<input type="text" maxlength="3" size="10" id="wypiekacz_max_cats" name="wypiekacz_max_cats" value="<?php echo esc_attr( get_option( 'wypiekacz_max_cats' ) ); ?>" />
    9571401</td>
    9581402</tr>
     
    9651409</th>
    9661410<td>
    967 <input type="text" maxlength="3" size="10" id="wypiekacz_min_tags" name="wypiekacz_min_tags" value="<?php echo stripcslashes( get_option( 'wypiekacz_min_tags' ) ); ?>" />
     1411<input type="text" maxlength="3" size="10" id="wypiekacz_min_tags" name="wypiekacz_min_tags" value="<?php echo esc_attr( get_option( 'wypiekacz_min_tags' ) ); ?>" />
    9681412</td>
    9691413</tr>
     
    9741418</th>
    9751419<td>
    976 <input type="text" maxlength="3" size="10" id="wypiekacz_max_tags" name="wypiekacz_max_tags" value="<?php echo stripcslashes( get_option( 'wypiekacz_max_tags' ) ); ?>" />
     1420<input type="text" maxlength="3" size="10" id="wypiekacz_max_tags" name="wypiekacz_max_tags" value="<?php echo esc_attr( get_option( 'wypiekacz_max_tags' ) ); ?>" />
    9771421</td>
    9781422</tr>
     
    10001444<tr>
    10011445<th scope="row" style="text-align:right; vertical-align:top;">
     1446<label for="wypiekacz_check_badwords_tags"><?php _e('Check for forbidden words in tags:', 'wypiekacz'); ?></label>
     1447</th>
     1448<td>
     1449<input type="checkbox" id="wypiekacz_check_badwords_tags" name="wypiekacz_check_badwords_tags" value="yes" <?php checked( 1, get_option( 'wypiekacz_check_badwords_tags' ) ); ?> />
     1450</td>
     1451</tr>
     1452
     1453<tr>
     1454<th scope="row" style="text-align:right; vertical-align:top;">
    10021455<label for="wypiekacz_badwords"><?php _e('Forbidden words list:', 'wypiekacz'); ?></label>
    10031456</th>
    10041457<td>
    1005 <textarea id="wypiekacz_badwords" name="wypiekacz_badwords" rows="5" cols="30"><?php echo htmlspecialchars( implode(
    1006 "\n", get_option( 'wypiekacz_badwords', array() ) ) ); ?></textarea><br />
     1458<textarea id="wypiekacz_badwords" name="wypiekacz_badwords" rows="5" cols="30"><?php echo esc_html( implode( "\n", get_option( 'wypiekacz_badwords', array() ) ) ); ?></textarea><br />
    10071459<?php _e('Put one word per line', 'wypiekacz'); ?>
    10081460</td>
     
    10141466</th>
    10151467<td>
    1016 <textarea id="wypiekacz_goodwords" name="wypiekacz_goodwords" rows="5" cols="30"><?php echo htmlspecialchars( implode(
    1017 "\n", get_option( 'wypiekacz_goodwords', array() ) ) ); ?></textarea><br />
     1468<textarea id="wypiekacz_goodwords" name="wypiekacz_goodwords" rows="5" cols="30"><?php echo esc_html( implode( "\n", get_option( 'wypiekacz_goodwords', array() ) ) ); ?></textarea><br />
    10181469<?php _e('Put one word per line', 'wypiekacz'); ?><br /><?php _e('When WyPiekacz will found any word from Forbidden Word List in text, it will check if it part of any word from Allowed Words List (e.g. <b>fly</b> - forbidden, <b>butterfly</b> - allowed)', 'wypiekacz'); ?>
    10191470</td>
    10201471</tr>
    10211472
     1473<tr><th colspan="2"><h3><?php _e('Post Thumbnail:', 'wypiekacz'); ?></h3></th></tr>
     1474
     1475<tr>
     1476<th scope="row" style="text-align:right; vertical-align:top;">
     1477<label for="wypiekacz_post_thumbnail"><?php _e('Post thumbnail (Featured image) is required:', 'wypiekacz'); ?></label>
     1478</th>
     1479<td>
     1480<input type="checkbox" id="wypiekacz_post_thumbnail" name="wypiekacz_post_thumbnail" value="yes" <?php checked( 1, get_option( 'wypiekacz_post_thumbnail' ) ); ?> />
     1481<br /><?php _e('Note: WyPiekacz supports WordPress Featured image by default. You can also provide your own function to check if Post thumbnail is present - see FAQ for more details.', 'wypiekacz'); ?>
     1482</td>
     1483</tr>
     1484
    10221485<tr><th colspan="2"><h3><?php _e('Rule enforcement:', 'wypiekacz'); ?></h3></th></tr>
    10231486
     
    10331496<tr>
    10341497<th scope="row" style="text-align:right; vertical-align:top;">
     1498<label for="wypiekacz_enforce_link_positions"><?php _e('Enforce link positions:', 'wypiekacz'); ?></label>
     1499</th>
     1500<td>
     1501<input type="checkbox" id="wypiekacz_enforce_link_positions" name="wypiekacz_enforce_link_positions" value="yes" <?php checked( 1, get_option( 'wypiekacz_enforce_link_positions' ) ); ?> /><br /><?php _e('Links inserted too close to the beginning of post content will be automatically removed', 'wypiekacz'); ?>
     1502</td>
     1503</tr>
     1504
     1505<tr>
     1506<th scope="row" style="text-align:right; vertical-align:top;">
    10351507<label for="wypiekacz_enforce_title"><?php _e('Enforce max title length:', 'wypiekacz'); ?></label>
    10361508</th>
     
    10671539</tr>
    10681540
     1541<tr><th colspan="2"><h3><?php _e('Invalid Posts:', 'wypiekacz'); ?></h3></th></tr>
     1542
     1543<tr>
     1544<th scope="row" style="text-align:right; vertical-align:top;">
     1545<label for="wypiekacz_dont_save_invalid_post"><?php _e('Do not save posts which do not satisfy all rules:', 'wypiekacz'); ?></label>
     1546</th>
     1547<td>
     1548<input type="checkbox" id="wypiekacz_dont_save_invalid_post" name="wypiekacz_dont_save_invalid_post" value="yes" <?php checked( 1, get_option( 'wypiekacz_dont_save_invalid_post' ) ); ?> />
     1549<br /><?php _e('When this option is enabled, posts submitted for review or publishing will not be saved if they do not satisfy all rules. This can negatively affect user experience, so enable it if you have to deal with lots of automated spam only.', 'wypiekacz'); ?>
     1550<br /><?php _e('Note: this option does not prevent creation of auto drafts - so far there are too many dependencies in WordPress code to make it work correctly. Autosave and normal post saving as draft (without attempt to publish or send it for review) will work too.', 'wypiekacz'); ?>
     1551</td>
     1552</tr>
     1553
     1554<tr>
     1555<th scope="row" style="text-align:right; vertical-align:top;">
     1556<label for="wypiekacz_delete_orphaned_drafts"><?php _e('Delete orphaned post drafts interval (days):', 'wypiekacz'); ?></label>
     1557</th>
     1558<td>
     1559<input type="text" maxlength="4" size="10" id="wypiekacz_delete_orphaned_drafts" name="wypiekacz_delete_orphaned_drafts" value="<?php echo esc_attr( get_option( 'wypiekacz_delete_orphaned_drafts' ) ); ?>" /><br /><?php _e('Default is 0 days - disabled. When Trash is enabled, drafts will be moved to Trash.', 'wypiekacz'); ?>
     1560</td>
     1561</tr>
     1562
     1563<tr>
     1564<th scope="row" style="text-align:right; vertical-align:top;">
     1565<label for="wypiekacz_force_delete_orphaned_drafts"><?php _e('Force deletion of orphaned post drafts:', 'wypiekacz'); ?></label>
     1566</th>
     1567<td>
     1568<input type="checkbox" id="wypiekacz_force_delete_orphaned_drafts" name="wypiekacz_force_delete_orphaned_drafts" value="yes" <?php checked( 1, get_option( 'wypiekacz_force_delete_orphaned_drafts' ) ); ?> /><br /><?php _e('Do not move orphaned post drafts to Trash - delete them immediately.', 'wypiekacz'); ?>
     1569</td>
     1570</tr>
     1571
     1572<tr><th colspan="2"><h3><?php _e('Build-in WordPress functionalities:', 'wypiekacz'); ?></h3></th></tr>
     1573
     1574<tr>
     1575<th scope="row" style="text-align:right; vertical-align:top;">
     1576<label for="wypiekacz_autosave_interval"><?php _e('Post autosave interval (seconds):', 'wypiekacz'); ?></label>
     1577</th>
     1578<td>
     1579<input type="text" maxlength="4" size="10" id="wypiekacz_autosave_interval" name="wypiekacz_autosave_interval" value="<?php echo esc_attr( get_option( 'wypiekacz_autosave_interval' ) ); ?>" /><br /><?php _e('Default is 60 seconds. Enter 0 to disable autosave.', 'wypiekacz'); ?>
     1580</td>
     1581</tr>
     1582
     1583<tr>
     1584<th scope="row" style="text-align:right; vertical-align:top;">
     1585<label for="wypiekacz_post_revisions"><?php _e('Maximum post revisions count:', 'wypiekacz'); ?></label>
     1586</th>
     1587<td>
     1588<input type="text" maxlength="4" size="10" id="wypiekacz_post_revisions" name="wypiekacz_post_revisions" value="<?php echo esc_attr( get_option( 'wypiekacz_post_revisions' ) ); ?>" /><br /><?php _e('Default is -1 (no limit). Enter 0 to disable post revisions.', 'wypiekacz'); ?>
     1589</td>
     1590</tr>
     1591
     1592<tr>
     1593<th scope="row" style="text-align:right; vertical-align:top;">
     1594<label for="wypiekacz_empty_trash_days"><?php _e('Empty trash interval (days):', 'wypiekacz'); ?></label>
     1595</th>
     1596<td>
     1597<input type="text" maxlength="4" size="10" id="wypiekacz_empty_trash_days" name="wypiekacz_empty_trash_days" value="<?php echo esc_attr( get_option( 'wypiekacz_empty_trash_days' ) ); ?>" /><br /><?php _e('Default is 30 days. Enter 0 to disable Trash.', 'wypiekacz'); ?>
     1598</td>
     1599</tr>
     1600
    10691601<tr><th colspan="2"><h3><?php _e('Special:', 'wypiekacz'); ?></h3></th></tr>
    10701602
     
    10931625<td>
    10941626<input type="checkbox" id="wypiekacz_right_now_stats" name="wypiekacz_right_now_stats" value="yes" <?php checked( 1, get_option( 'wypiekacz_right_now_stats' ) ); ?> />
     1627</td>
     1628</tr>
     1629
     1630<tr>
     1631<th scope="row" style="text-align:right; vertical-align:top;">
     1632<label for="wypiekacz_post_menu_links"><?php _e('Add links to Draft and Pending Posts lists to Posts menu:', 'wypiekacz'); ?></label>
     1633</th>
     1634<td>
     1635<input type="checkbox" id="wypiekacz_post_menu_links" name="wypiekacz_post_menu_links" value="yes" <?php checked( 1, get_option( 'wypiekacz_post_menu_links' ) ); ?> />
    10951636</td>
    10961637</tr>
     
    11101651    foreach ( $post_types as $post_type => $post_type_label ) {
    11111652?>
    1112 <label><input type="checkbox" id="wypiekacz_post_types_<?php print $post_type; ?>" name="wypiekacz_post_types[]" value="<?php print $post_type; ?>" <?php checked( true, in_array( $post_type, $selected_post_types ) ); ?> /> <?php print $post_type_label; ?></label><br />
     1653<label><input type="checkbox" id="wypiekacz_post_types_<?php echo esc_attr( $post_type ); ?>" name="wypiekacz_post_types[]" value="<?php echo esc_attr( $post_type );  ?>" <?php checked( true, in_array( $post_type, $selected_post_types ) ); ?> /> <?php echo esc_html( $post_type_label ); ?></label><br />
    11131654<?php
    11141655    }
     
    11261667</th>
    11271668<td>
    1128 <input type="text" size="59" id="wypiekacz_def_title" name="wypiekacz_def_title" value="<?php echo stripcslashes( get_option( 'wypiekacz_def_title' ) ); ?>" />
     1669<input type="text" size="59" id="wypiekacz_def_title" name="wypiekacz_def_title" value="<?php echo esc_attr( get_option( 'wypiekacz_def_title' ) ); ?>" />
    11291670</td>
    11301671</tr>
     
    11351676</th>
    11361677<td>
    1137 <textarea rows="5" cols="57" id="wypiekacz_def_text" name="wypiekacz_def_text"><?php echo stripcslashes( get_option( 'wypiekacz_def_text' ) ); ?></textarea>
     1678<textarea rows="5" cols="57" id="wypiekacz_def_text" name="wypiekacz_def_text"><?php echo esc_textarea( get_option( 'wypiekacz_def_text' ) ); ?></textarea>
    11381679</td>
    11391680</tr>
     
    11581699</td>
    11591700</tr>
     1701
     1702<tr><th colspan="2"><h3><?php _e('Account locking:', 'wypiekacz'); ?></h3></th></tr>
     1703
     1704<tr>
     1705<th scope="row" style="text-align:right; vertical-align:top;">
     1706&nbsp;
     1707</th>
     1708<td>
     1709<?php printf( __('Current WyPiekacz version is integrated with the <a href="%s" target="_blank">User Locker</a> plugin (version 1.2 or newer). It can lock or disable user account when he/she will send too many invalid posts for review or attempt to publish it and abandon them. Multiple failed attempts for the same post in a row are counted only once. Every successful submission resets the counter. Users will be able to unlock locked account by requesting new password or asking admin for help; disabled accounts can be enabled by admin only.', 'wypiekacz'), 'http://wordpress.org/extend/plugins/user-locker/' ); ?>
     1710</td>
     1711</tr>
     1712
     1713<tr>
     1714<th scope="row" style="text-align:right; vertical-align:top;">
     1715&nbsp;
     1716</th>
     1717<td>
     1718<?php
     1719if ( $this->has_user_locker ) {
     1720    _e('User Locker 1.2+ is installed and active.', 'wypiekacz');
     1721} else {
     1722    _e('User Locker 1.2+ is not active. You need to install and activate it first.', 'wypiekacz');
     1723}
     1724?>
     1725</td>
     1726</tr>
     1727
     1728<?php if ( $this->has_user_locker ): ?>
     1729
     1730<tr>
     1731<th scope="row" style="text-align:right; vertical-align:top;">
     1732<label for="wypiekacz_lock_account"><?php _e('Enable account locking/disabling:', 'wypiekacz'); ?></label>
     1733</th>
     1734<td>
     1735<input type="checkbox" id="wypiekacz_lock_account" name="wypiekacz_lock_account" value="yes" <?php checked( 1, get_option( 'wypiekacz_lock_account' ) ); ?> />
     1736</td>
     1737</tr>
     1738
     1739<tr>
     1740<th scope="row" style="text-align:right; vertical-align:top;">
     1741<label for="wypiekacz_lock_account_after"><?php _e('Maximum allowed number of abandoned invalid posts:', 'wypiekacz'); ?></label>
     1742</th>
     1743<td>
     1744<input type="text" maxlength="4" size="10" id="wypiekacz_lock_account_after" name="wypiekacz_lock_account_after" value="<?php echo esc_attr( get_option( 'wypiekacz_lock_account_after' ) ); ?>" /><br /><?php _e('Multiple failed attempts for the same post in a row are counted only once. Every successful submission resets the counter.', 'wypiekacz'); ?>
     1745</td>
     1746</tr>
     1747
     1748<tr>
     1749<th scope="row" style="text-align:right; vertical-align:top;">
     1750<label for="wypiekacz_lock_method"><?php _e('Account lock method:', 'wypiekacz'); ?></label>
     1751</th>
     1752<td>
     1753<?php $wypiekacz_lock_method = get_option( 'wypiekacz_lock_method' ); ?>
     1754<select id="wypiekacz_lock_method" name="wypiekacz_lock_method">
     1755<option value="0" <?php selected( $wypiekacz_lock_method, 0 ); ?>><?php _e('Lock', 'wypiekacz'); ?></option>
     1756<option value="1" <?php selected( $wypiekacz_lock_method, 1 ); ?>><?php _e('Disable', 'wypiekacz'); ?></option>
     1757</select><br /><?php _e('Users will be able to unlock locked account by requesting new password or asking admin for help. Disabled accounts can be enabled by admin only.', 'wypiekacz'); ?>
     1758</td>
     1759</tr>
     1760
     1761<tr>
     1762<th scope="row" style="text-align:right; vertical-align:top;">
     1763<label for="wypiekacz_lock_reason"><?php _e('Lock/Disable reason:', 'wypiekacz'); ?></label>
     1764</th>
     1765<td>
     1766<input type="text" maxlength="500" size="80" id="wypiekacz_lock_reason" name="wypiekacz_lock_reason" value="<?php echo esc_attr( get_option( 'wypiekacz_lock_reason' ) ); ?>" />
     1767<br /><?php _e('Reason text can be displayed after unsuccessful login attempt, and on User List. Make sure you enabled appropriate options in User Locker settings.', 'wypiekacz'); ?>
     1768<br /><?php _e('Note: start text with \'@\' (AT sign) to keep it private.', 'wypiekacz'); ?>
     1769</td>
     1770</tr>
     1771
     1772<tr>
     1773<th scope="row" style="text-align:right; vertical-align:top;">
     1774<label for="wypiekacz_lock_show_details"><?php _e('Show details on User List:', 'wypiekacz'); ?></label>
     1775</th>
     1776<td>
     1777<input type="checkbox" id="wypiekacz_lock_show_details" name="wypiekacz_lock_show_details" value="yes" <?php checked( 1, get_option( 'wypiekacz_lock_show_details' ) ); ?> />
     1778<br /><?php _e('Enable this option to add extra column to User List with Bad Posts count and last Post ID.', 'wypiekacz'); ?>
     1779</td>
     1780</tr>
     1781
     1782<?php endif; /* if ( $this->has_user_locker ): */ ?>
    11601783
    11611784</table>
     
    11931816    }
    11941817   
     1818    function sanitize_nonnegative_or_minus1( $value ) {
     1819        $value = (int)$value;
     1820        if ( $value < -1 ) {
     1821            $value = -1;
     1822        }
     1823        return $value;
     1824    }
     1825   
     1826    function sanitize_positive( $value ) {
     1827        $value = (int)$value;
     1828        if ( $value <= 0 ) {
     1829            $value = 1;
     1830        }
     1831        return $value;
     1832    }
     1833   
    11951834    function sanitize_stringlist( $value ) {
    11961835        $value = explode( "\n", (string)$value );
     
    12321871    // Sanitize list of post types
    12331872    function sanitize_post_types( $types ) {
     1873        // For pre-WP 3.0 return predefined value - otherwise WP will save empty array when
     1874        // options are updated, causing mysterious problem after upgrade to WP 3.0+.
     1875        if ( !$this->has_wp_30 ) {
     1876            return array( 'post' );
     1877        }
     1878       
    12341879        $post_types = $this->get_post_types();
    12351880        $ret = array();
     
    12461891add_option( 'wypiekacz_min_len', 1000 ); // Minimum post length (characters)
    12471892add_option( 'wypiekacz_min_len_words', 0 ); // Minimum post length (words)
     1893add_option( 'wypiekacz_min_links', 0 ); // Minimum links per post
    12481894add_option( 'wypiekacz_max_links', 3 ); // Maximum links per post
     1895add_option( 'wypiekacz_link_after', 0 ); // First link allowed after N characters
     1896add_option( 'wypiekacz_link_after_words', 0 ); // First link allowed after N words
    12491897add_option( 'wypiekacz_min_title_len', 5 ); // Minimum title length (characters)
    12501898add_option( 'wypiekacz_min_title_len_words', 0 ); // Minimum title length (words)
     
    12621910add_option( 'wypiekacz_pass_reset_email', 1 ); // Send notification of password resets to admin
    12631911add_option( 'wypiekacz_right_now_stats', 1 ); // Show number of Drafts and Pending Posts in Dashboard
     1912add_option( 'wypiekacz_post_menu_links', 1 ); // Add Drafts/Pending links to Posts menu
    12641913add_option( 'wypiekacz_badwords', array() ); // List of forbidden words
    12651914add_option( 'wypiekacz_check_badwords_title', 0 ); // Check for forbidden words in title
    12661915add_option( 'wypiekacz_check_badwords_content', 0 ); // Check for forbidden words in content
     1916add_option( 'wypiekacz_check_badwords_tags', 0 ); // Check for forbidden words in tags
    12671917add_option( 'wypiekacz_goodwords', array() ); // List of allowed words
     1918add_option( 'wypiekacz_post_thumbnail', 0 ); // Check post thumbnail
    12681919add_option( 'wypiekacz_enforce_links', 0 ); // Enforce max link count
     1920add_option( 'wypiekacz_enforce_link_positions', 0 ); // Enforce link positions
    12691921add_option( 'wypiekacz_enforce_title', 0 ); // Enforce max title length
    12701922add_option( 'wypiekacz_enforce_add_dots', 1 ); // Enforce max title length
     
    12731925add_option( 'wypiekacz_allow_skip_rules', 1 ); // Allow Editors and Administrators to skip rule check
    12741926add_option( 'wypiekacz_post_types', array( 'post' ) ); // List of post types supported by default
     1927add_option( 'wypiekacz_dont_save_invalid_post', 0 ); // Save post even it has not passed validation
     1928add_option( 'wypiekacz_autosave_interval', 60 ); // Post autosave interval (60 = default, 0 to disable (need special handling))
     1929add_option( 'wypiekacz_post_revisions', -1 ); // Post revisions count (-1/true = default, 0/false to disable)
     1930add_option( 'wypiekacz_empty_trash_days', 30 ); // Empty trash days (30 = default, 0 to disable)
     1931add_option( 'wypiekacz_delete_orphaned_drafts', 0 ); // Delete orphaned_drafts interval (days, 0 = disable)
     1932add_option( 'wypiekacz_force_delete_orphaned_drafts', 0 ); // Force delete orphaned_drafts (otherwise they may end in Trash)
     1933add_option( 'wypiekacz_lock_account', 0 ); // Lock/disable user account when user submits too many invalid posts for review/publish and do not correct them
     1934add_option( 'wypiekacz_lock_account_after', 5 ); // How many invalid posts can be submitted before locking user
     1935add_option( 'wypiekacz_lock_method', 0 ); // 0 - lock account, 1 - disable account
     1936add_option( 'wypiekacz_lock_reason', '' ); // Lock reason
     1937add_option( 'wypiekacz_lock_show_details', 1 ); // Add extra column to User List with details
    12751938
    12761939$wp_wypiekacz = new WyPiekacz();
     1940// TODO: try to call this from 'init' hook (at this point user is authenticated), and do not enforce rules if checking is skipped for user or by option
    12771941$wp_wypiekacz->enforce_rules_POST();
    12781942
     
    13091973}
    13101974
     1975// Define some special defines
     1976if ( !defined( 'AUTOSAVE_INTERVAL' ) ) { // 60 = default
     1977    $val = get_option( 'wypiekacz_autosave_interval' );
     1978    if ( $val == 0 ) {
     1979        // Use some big value to make sure autosave will not start if autosave script will be loaded (should not be)
     1980        $val = 999999999;
     1981    }
     1982    define( 'AUTOSAVE_INTERVAL', $val );
     1983}
     1984
     1985if ( !defined( 'WP_POST_REVISIONS' ) ) { // -1 = default
     1986    $val = get_option( 'wypiekacz_post_revisions' );
     1987    define( 'WP_POST_REVISIONS', $val );
     1988}
     1989
     1990if ( !defined( 'EMPTY_TRASH_DAYS' ) ) { // 30 = default
     1991    $val = get_option( 'wypiekacz_empty_trash_days' );
     1992    define( 'EMPTY_TRASH_DAYS', $val );
     1993}
     1994
     1995// Add functions from WP2.8 for previous WP versions
     1996if ( !function_exists( 'esc_html' ) ) {
     1997    function esc_html( $text ) {
     1998        return wp_specialchars( $text );
     1999    }
     2000}
     2001if ( !function_exists( 'esc_attr' ) ) {
     2002    function esc_attr( $text ) {
     2003        return attribute_escape( $text );
     2004    }
     2005}
     2006
     2007// Add functions from WP2.9 for previous WP versions
     2008if ( !function_exists( 'wp_strip_all_tags' ) ) {
     2009    function wp_strip_all_tags($string, $remove_breaks = false) {
     2010        $string = preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $string );
     2011        $string = strip_tags($string);
     2012   
     2013        if ( $remove_breaks )
     2014            $string = preg_replace('/[\r\n\t ]+/', ' ', $string);
     2015   
     2016        return trim($string);
     2017    }
     2018}
     2019
     2020// Add functions from WP3.1 for previous WP versions
     2021if ( !function_exists( 'esc_textarea' ) ) {
     2022    function esc_textarea( $text ) {
     2023        $safe_text = htmlspecialchars( $text, ENT_QUOTES );
     2024        return apply_filters( 'esc_textarea', $safe_text, $text );
     2025    }
     2026}
     2027
    13112028} // END
    13122029
Note: See TracChangeset for help on using the changeset viewer.