| 1 | <?php |
|---|
| 2 | |
|---|
| 3 | /** |
|---|
| 4 | * This class is responsible for querying the ads and |
|---|
| 5 | * formatting their output. |
|---|
| 6 | */ |
|---|
| 7 | class DFADS { |
|---|
| 8 | |
|---|
| 9 | private $args; |
|---|
| 10 | |
|---|
| 11 | // This is called via the dfads() function. It puts everything in motion. |
|---|
| 12 | function get_ads( $args ) { |
|---|
| 13 | |
|---|
| 14 | $this->set_args( $args ); |
|---|
| 15 | |
|---|
| 16 | // Return JS output for avoiding caching issues. |
|---|
| 17 | // Return this before running query() to avoid: |
|---|
| 18 | // - The query being run twice. |
|---|
| 19 | // - Impressions being counted twice. |
|---|
| 20 | if ( $this->args['return_javascript'] == '1' ) { |
|---|
| 21 | return $this->get_javascript(); |
|---|
| 22 | } |
|---|
| 23 | |
|---|
| 24 | // Get ads. |
|---|
| 25 | $ads = $this->query(); |
|---|
| 26 | |
|---|
| 27 | if ( empty($ads) ) { return false; } |
|---|
| 28 | |
|---|
| 29 | // Count impressions. |
|---|
| 30 | $this->update_impression_count( $ads ); |
|---|
| 31 | |
|---|
| 32 | // Return user's own callback function. |
|---|
| 33 | if ( function_exists( $this->args['callback_function'] ) ) { |
|---|
| 34 | return call_user_func_array($this->args['callback_function'], array( $ads, $this->args )); |
|---|
| 35 | } |
|---|
| 36 | |
|---|
| 37 | // Return default output function. |
|---|
| 38 | return $this->output( $ads, $this->args ); |
|---|
| 39 | } |
|---|
| 40 | |
|---|
| 41 | // Set up default arguements that can be modified in dfads(). |
|---|
| 42 | function set_args( $args ) { |
|---|
| 43 | |
|---|
| 44 | // Undo anything that the text editor may have added. |
|---|
| 45 | $args = str_replace ( |
|---|
| 46 | array( "&", "<", ">", ""e;", "%2C" ), |
|---|
| 47 | array( "&", "", "", "\"", "," ), |
|---|
| 48 | $args |
|---|
| 49 | ); |
|---|
| 50 | |
|---|
| 51 | // Now reformat |
|---|
| 52 | $args = htmlentities( $args ); |
|---|
| 53 | |
|---|
| 54 | // Create array of values. |
|---|
| 55 | $args = explode("&", $args); |
|---|
| 56 | |
|---|
| 57 | $new_args = array(); |
|---|
| 58 | foreach ($args as $arg) { |
|---|
| 59 | $arr = explode( "=", $arg, 2 ); |
|---|
| 60 | $k = $arr[0]; |
|---|
| 61 | $v = $arr[1]; |
|---|
| 62 | // This section gets rid of the pesky "#038;" charcters WP changes "&" to. |
|---|
| 63 | $k = str_replace( array( "#038;" ), array( "" ), $k ); |
|---|
| 64 | $new_args[$k] = $v; |
|---|
| 65 | } |
|---|
| 66 | |
|---|
| 67 | $defaults = array ( |
|---|
| 68 | 'groups' => '-1', |
|---|
| 69 | 'limit' => '-1', |
|---|
| 70 | 'orderby' => 'random', |
|---|
| 71 | 'order' => 'ASC', |
|---|
| 72 | 'container_id' => '', |
|---|
| 73 | 'container_html' => 'div', |
|---|
| 74 | 'container_class' => '', |
|---|
| 75 | 'ad_html' => 'div', |
|---|
| 76 | 'ad_class' => '', |
|---|
| 77 | 'callback_function' => '', |
|---|
| 78 | 'return_javascript' => '', |
|---|
| 79 | ); |
|---|
| 80 | |
|---|
| 81 | $this->args = wp_parse_args( $new_args, $defaults ); |
|---|
| 82 | } |
|---|
| 83 | |
|---|
| 84 | // Build the SQL query to get the ads. |
|---|
| 85 | function query() { |
|---|
| 86 | // http://codex.wordpress.org/Displaying_Posts_Using_a_Custom_Select_Query |
|---|
| 87 | global $wpdb; |
|---|
| 88 | $tax_sql = $this->sql_get_taxonomy(); |
|---|
| 89 | $tax_join = $tax_sql['JOIN']; |
|---|
| 90 | $tax_and = $tax_sql['AND']; |
|---|
| 91 | $limit = $this->sql_get_limit(); |
|---|
| 92 | $orderby = $this->sql_get_orderby(); |
|---|
| 93 | $order = $this->sql_get_order(); |
|---|
| 94 | $sql = " |
|---|
| 95 | SELECT |
|---|
| 96 | p.*, |
|---|
| 97 | imp_count.meta_value AS ad_imp_count, |
|---|
| 98 | imp_limit.meta_value AS ad_imp_limit, |
|---|
| 99 | start_date.meta_value AS ad_start_date, |
|---|
| 100 | end_date.meta_value AS ad_end_date |
|---|
| 101 | FROM $wpdb->posts p |
|---|
| 102 | LEFT JOIN $wpdb->postmeta AS imp_limit |
|---|
| 103 | ON p.ID = imp_limit.post_id |
|---|
| 104 | AND imp_limit.meta_key = '_dfads_impression_limit' |
|---|
| 105 | LEFT JOIN $wpdb->postmeta AS imp_count |
|---|
| 106 | ON p.ID = imp_count.post_id |
|---|
| 107 | AND imp_count.meta_key = '".DFADS_METABOX_PREFIX."impression_count' |
|---|
| 108 | LEFT JOIN $wpdb->postmeta AS start_date |
|---|
| 109 | ON p.ID = start_date.post_id |
|---|
| 110 | AND start_date.meta_key = '_dfads_start_date' |
|---|
| 111 | LEFT JOIN $wpdb->postmeta AS end_date |
|---|
| 112 | ON p.ID = end_date.post_id |
|---|
| 113 | AND end_date.meta_key = '_dfads_end_date' |
|---|
| 114 | $tax_join |
|---|
| 115 | WHERE p.post_status = 'publish' |
|---|
| 116 | AND p.post_type = 'dfads' |
|---|
| 117 | AND ( |
|---|
| 118 | CAST(imp_limit.meta_value AS UNSIGNED) = 0 |
|---|
| 119 | OR CAST(imp_count.meta_value AS UNSIGNED) < CAST(imp_limit.meta_value AS UNSIGNED) |
|---|
| 120 | OR CAST(imp_count.meta_value AS UNSIGNED) IS NULL |
|---|
| 121 | ) |
|---|
| 122 | AND ( |
|---|
| 123 | CAST(start_date.meta_value AS UNSIGNED) IS NULL |
|---|
| 124 | OR ".time()." >= CAST(start_date.meta_value AS UNSIGNED) |
|---|
| 125 | ) |
|---|
| 126 | AND ( |
|---|
| 127 | CAST(end_date.meta_value AS UNSIGNED) IS NULL |
|---|
| 128 | OR ".time()." <= CAST(end_date.meta_value AS UNSIGNED) |
|---|
| 129 | ) |
|---|
| 130 | $tax_and |
|---|
| 131 | GROUP BY p.ID |
|---|
| 132 | ORDER BY $orderby $order |
|---|
| 133 | LIMIT $limit |
|---|
| 134 | "; |
|---|
| 135 | |
|---|
| 136 | return $wpdb->get_results( $sql, OBJECT ); |
|---|
| 137 | } |
|---|
| 138 | |
|---|
| 139 | // Build the taxonomy portion of the SQL statement. |
|---|
| 140 | function sql_get_taxonomy() { |
|---|
| 141 | global $wpdb; |
|---|
| 142 | $sql = array(); |
|---|
| 143 | $sql['JOIN'] = ''; |
|---|
| 144 | $sql['AND'] = ''; |
|---|
| 145 | |
|---|
| 146 | if ( !$group_ids = $this->get_group_term_ids( $this->args['groups'] ) ) { |
|---|
| 147 | return $sql; |
|---|
| 148 | } |
|---|
| 149 | |
|---|
| 150 | $ids = implode( ",", $group_ids ); |
|---|
| 151 | $sql['JOIN'] = " LEFT JOIN $wpdb->term_relationships AS tr ON (p.ID = tr.object_id) LEFT JOIN $wpdb->term_taxonomy AS tax ON (tr.term_taxonomy_id = tax.term_taxonomy_id) "; |
|---|
| 152 | $sql['AND'] = " AND tax.taxonomy = 'dfads_group' AND tax.term_id IN($ids) "; |
|---|
| 153 | return $sql; |
|---|
| 154 | } |
|---|
| 155 | |
|---|
| 156 | // Build the LIMIT portion of the SQL statement. |
|---|
| 157 | function sql_get_limit() { |
|---|
| 158 | return ($this->args['limit'] == '-1') ? 999 : intval($this->args['limit']); |
|---|
| 159 | } |
|---|
| 160 | |
|---|
| 161 | // Build the ORDER BY portion of the SQL statement. |
|---|
| 162 | function sql_get_orderby() { |
|---|
| 163 | $orderby_defaults = self::orderby_array(); |
|---|
| 164 | return $orderby_defaults[$this->args['orderby']]['sql']; |
|---|
| 165 | } |
|---|
| 166 | |
|---|
| 167 | // Build the ORDER portion of the SQL statement. |
|---|
| 168 | function sql_get_order() { |
|---|
| 169 | return ($this->args['order'] == 'ASC') ? 'ASC' : 'DESC'; |
|---|
| 170 | } |
|---|
| 171 | |
|---|
| 172 | // A set of possibly values for the ORDER BY part of the SQL query. |
|---|
| 173 | public static function orderby_array() { |
|---|
| 174 | return array( |
|---|
| 175 | 'ID' => array( 'name'=>'ID', 'sql'=>'p.ID' ), |
|---|
| 176 | 'post_title' => array( 'name'=>'Ad Title', 'sql'=>'p.post_title' ), |
|---|
| 177 | 'post_date' => array( 'name'=>'Date Created', 'sql'=>'p.post_date' ), |
|---|
| 178 | 'post_modified' => array( 'name'=>'Date Modified', 'sql'=>'p.post_modified' ), |
|---|
| 179 | 'menu_order' => array( 'name'=>'Menu Order', 'sql'=>'p.menu_order' ), |
|---|
| 180 | 'impression_count' => array( 'name'=>'Impression Count', 'sql'=>'CAST(imp_count.meta_value AS UNSIGNED)' ), |
|---|
| 181 | 'impression_limit' => array( 'name'=>'Impression Limit', 'sql'=>'CAST(imp_limit.meta_value AS UNSIGNED)' ), |
|---|
| 182 | 'start_date' => array( 'name'=>'Start Date', 'sql'=>'CAST(start_date.meta_value AS UNSIGNED)' ), |
|---|
| 183 | 'end_date' => array( 'name'=>'End Date', 'sql'=>'CAST(end_date.meta_value AS UNSIGNED)' ), |
|---|
| 184 | 'random' => array( 'name'=>'Random', 'sql'=>'RAND()' ), |
|---|
| 185 | ); |
|---|
| 186 | } |
|---|
| 187 | |
|---|
| 188 | // This loops through all ads returned and updates their impression count (default: if user != admin). |
|---|
| 189 | function update_impression_count( $ads ) { |
|---|
| 190 | |
|---|
| 191 | // Don't count if admin AND admin impressions don't count. |
|---|
| 192 | if ( current_user_can('level_10') ) { |
|---|
| 193 | $output = get_option( 'dfads-settings' ); |
|---|
| 194 | if ( !isset( $output['dfads_enable_count_for_admin'] ) || $output['dfads_enable_count_for_admin'] != '1' ) { |
|---|
| 195 | return; |
|---|
| 196 | } |
|---|
| 197 | } |
|---|
| 198 | |
|---|
| 199 | // Don't count if we've already set a block_id. |
|---|
| 200 | // This is to handle impresion counts for when 'return_javascript' equals 1 |
|---|
| 201 | // because 'return_javascript' returns a <script> and <noscript> tag and |
|---|
| 202 | // we have to avoid the impression being counted twice. So we store this |
|---|
| 203 | // ad groups unique "block_id" as a transient value to check subsequent calls |
|---|
| 204 | // for the same ad block. If it's already set, we don't count again. |
|---|
| 205 | if ( isset( $this->args['_block_id'] ) ) { |
|---|
| 206 | if ( get_transient( 'dfad_' . $this->args['_block_id'] ) ) { |
|---|
| 207 | return; |
|---|
| 208 | } else { |
|---|
| 209 | set_transient( 'dfad_' . $this->args['_block_id'], true, 5 ); |
|---|
| 210 | } |
|---|
| 211 | } |
|---|
| 212 | |
|---|
| 213 | foreach ($ads as $ad) { |
|---|
| 214 | update_post_meta($ad->ID, DFADS_METABOX_PREFIX.'impression_count', (intval($ad->ad_imp_count)+1)); |
|---|
| 215 | } |
|---|
| 216 | |
|---|
| 217 | } |
|---|
| 218 | |
|---|
| 219 | // This formats and outputs the ads. This is overridable if the user has defined $this->args['callback_function'] |
|---|
| 220 | function output( $ads, $args ) { |
|---|
| 221 | |
|---|
| 222 | $ad_count = count( $ads ); |
|---|
| 223 | $i = 1; |
|---|
| 224 | $html = ''; |
|---|
| 225 | |
|---|
| 226 | // Determine if we should include tags for containers and ad wrappers. |
|---|
| 227 | // If 'none', then we remove the tag from output. |
|---|
| 228 | $container_html = ( $args['container_html'] == 'none' ) ? '' : $args['container_html']; |
|---|
| 229 | $ad_html = ( $args['ad_html'] == 'none' ) ? '' : $args['ad_html']; |
|---|
| 230 | |
|---|
| 231 | // If contain_html is not empty, get container's opening tag. |
|---|
| 232 | if ( $container_html != '') { |
|---|
| 233 | $html .= $this->open_tag( $container_html, $args['container_class'], $args['container_id'] ); |
|---|
| 234 | } |
|---|
| 235 | |
|---|
| 236 | // Loop through ads. |
|---|
| 237 | foreach ($ads as $ad) { |
|---|
| 238 | |
|---|
| 239 | $first_last = ' '; |
|---|
| 240 | if ( $i == 1 ) { |
|---|
| 241 | $first_last = ' dfad_first '; |
|---|
| 242 | } elseif ( $i == $ad_count ) { |
|---|
| 243 | $first_last = ' dfad_last '; |
|---|
| 244 | } |
|---|
| 245 | |
|---|
| 246 | // If ad_html is not empty, get the ads's opening tag. |
|---|
| 247 | if ( $ad_html != '') { |
|---|
| 248 | $html .= $this->open_tag( $ad_html, 'dfad dfad_pos_'.$i.$first_last.$args['ad_class'], $args['container_id'].'_ad_'.$ad->ID ); |
|---|
| 249 | } |
|---|
| 250 | |
|---|
| 251 | // Get ad content and append ad content to html block |
|---|
| 252 | $html .= apply_filters( 'dfads_ad_post_content', $ad->post_content, $ad, $args ); |
|---|
| 253 | |
|---|
| 254 | // If ad_html is not empty, get the ads's closing tag. |
|---|
| 255 | if ( $ad_html != '') { |
|---|
| 256 | $html .= $this->close_tag( $ad_html ); |
|---|
| 257 | } |
|---|
| 258 | |
|---|
| 259 | $i++; |
|---|
| 260 | } |
|---|
| 261 | |
|---|
| 262 | // If contain_html is not empty, get container's closing tag. |
|---|
| 263 | if ( $container_html != '') { |
|---|
| 264 | $html .= $this->close_tag( $container_html ); |
|---|
| 265 | } |
|---|
| 266 | |
|---|
| 267 | return apply_filters( 'dfads_ads_html_block', $html, $ads, $args ); |
|---|
| 268 | } |
|---|
| 269 | |
|---|
| 270 | function get_javascript() { |
|---|
| 271 | |
|---|
| 272 | $id = ( $this->args['container_id'] != '' ) ? $this->args['container_id'] : 'df'.$this->generate_random_string( 5 ); |
|---|
| 273 | |
|---|
| 274 | // Set 'return_javascript' to '0' or else we end up with an infinite loop. |
|---|
| 275 | $args = $this->args; |
|---|
| 276 | $args['return_javascript'] = '0'; |
|---|
| 277 | $args['_block_id'] = $id; |
|---|
| 278 | $args['container_html'] = 'none'; // Set to 'none' so we don't display the container HTML twice. |
|---|
| 279 | |
|---|
| 280 | return ' |
|---|
| 281 | <'.$this->args['container_html'].' id="'.$id.'" class="dfads-javascript-load"></'.$this->args['container_html'].'> |
|---|
| 282 | <script> |
|---|
| 283 | (function($) { |
|---|
| 284 | $("#'.$id .'").load("'.admin_url( 'admin-ajax.php?action=dfads_ajax_load_ads&'.http_build_query( $args ) ).'" ); |
|---|
| 285 | })( jQuery ); |
|---|
| 286 | </script> |
|---|
| 287 | <noscript>'.dfads( http_build_query( $args ) ).'</noscript> |
|---|
| 288 | '; |
|---|
| 289 | |
|---|
| 290 | } |
|---|
| 291 | |
|---|
| 292 | /** |
|---|
| 293 | * This gets the group IDs. |
|---|
| 294 | * |
|---|
| 295 | * $groups could be any of the following: |
|---|
| 296 | * - '' |
|---|
| 297 | * - 'groups=' |
|---|
| 298 | * - 'groups=1' |
|---|
| 299 | * - 'groups=sidebar' |
|---|
| 300 | * - 'groups=1,2' |
|---|
| 301 | * - 'groups=sidebar,header' |
|---|
| 302 | * |
|---|
| 303 | * "-1" is equivalent to "All". |
|---|
| 304 | */ |
|---|
| 305 | function get_group_term_ids( $groups=false ) { |
|---|
| 306 | |
|---|
| 307 | if (!$groups || $groups == '-1' || $groups == '') { return false; } |
|---|
| 308 | |
|---|
| 309 | $groups = explode(",", $groups); |
|---|
| 310 | $group_ids = array(); |
|---|
| 311 | |
|---|
| 312 | foreach( $groups as $group ) { |
|---|
| 313 | |
|---|
| 314 | // Try to get term ID from id. |
|---|
| 315 | if ($group_obj = get_term_by( 'id', $group, 'dfads_group' )) { |
|---|
| 316 | $group_ids[] = intval($group_obj->term_id); |
|---|
| 317 | continue; |
|---|
| 318 | } |
|---|
| 319 | |
|---|
| 320 | // Try to get term ID from slug. |
|---|
| 321 | if ($group_obj = get_term_by( 'slug', $group, 'dfads_group' )) { |
|---|
| 322 | $group_ids[] = intval($group_obj->term_id); |
|---|
| 323 | continue; |
|---|
| 324 | } |
|---|
| 325 | |
|---|
| 326 | // Try to get term ID from name. |
|---|
| 327 | if ($group_obj = get_term_by( 'name', $group, 'dfads_group' )) { |
|---|
| 328 | $group_ids[] = intval($group_obj->term_id); |
|---|
| 329 | continue; |
|---|
| 330 | } |
|---|
| 331 | } |
|---|
| 332 | |
|---|
| 333 | if (!empty($group_ids)) { |
|---|
| 334 | return $group_ids; |
|---|
| 335 | } |
|---|
| 336 | |
|---|
| 337 | return false; |
|---|
| 338 | } |
|---|
| 339 | |
|---|
| 340 | // Formats an opening HTML tag with CSS classes and IDs. |
|---|
| 341 | function open_tag( $tag='div', $class='', $id='' ) { |
|---|
| 342 | $tag = ($tag == '') ? 'div' : trim($tag); |
|---|
| 343 | $class = ($class != '') ? ' class="'.trim(esc_attr($class)).'"' : ''; |
|---|
| 344 | $id = ($id != '') ? ' id="'.trim(esc_attr($id)).'"' : ''; |
|---|
| 345 | return '<'.$tag.$class.$id.'>'; |
|---|
| 346 | } |
|---|
| 347 | |
|---|
| 348 | // Formats a closing HTML tag. |
|---|
| 349 | function close_tag( $tag='div' ) { |
|---|
| 350 | $tag = ($tag == '') ? 'div' : trim($tag); |
|---|
| 351 | return '</'.$tag.'>'; |
|---|
| 352 | } |
|---|
| 353 | |
|---|
| 354 | // Helper function (http://stackoverflow.com/a/4356295) |
|---|
| 355 | function generate_random_string( $length=10 ) { |
|---|
| 356 | $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; |
|---|
| 357 | $randomString = ''; |
|---|
| 358 | for ( $i = 0; $i < $length; $i++ ) { |
|---|
| 359 | $randomString .= $characters[rand(0, strlen($characters) - 1)]; |
|---|
| 360 | } |
|---|
| 361 | return $randomString; |
|---|
| 362 | } |
|---|
| 363 | } |
|---|