PluginProbe
Zone Manager (Zoninator) / 0.4
Zone Manager (Zoninator) v0.4
trunk 0.1 0.10.0 0.10.1 0.10.2 0.11.0 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9
zoninator / zoninator.php

zoninator.php in Zone Manager (Zoninator) 0.4, at zoninator.php

1,289 lines 39.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: Zone Manager (Zoninator)
4 Description: Curation made easy! Create "zones" then add and order your content!
5 Author: Mohammad Jangda, Automattic
6 Version: 0.4
7 Author URI: http://vip.wordpress.com
8
9 Copyright 2010-2012 Mohammad Jangda, Automattic
10
11 This plugin was built by Mohammad Jangda in conjunction with William Davis and the Bangor Daily News.
12
13 GNU General Public License, Free Software Foundation <http://creativecommons.org/licenses/GPL/2.0/>
14
15 This program is free software; you can redistribute it and/or modify
16 it under the terms of the GNU General Public License as published by
17 the Free Software Foundation; either version 2 of the License, or
18 (at your option) any later version.
19
20 This program is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
24
25 You should have received a copy of the GNU General Public License
26 along with this program; if not, write to the Free Software
27 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28
29 */
30
31 if( ! class_exists( 'Zoninator' ) ) :
32
33 define( 'ZONINATOR_VERSION', '0.4' );
34 define( 'ZONINATOR_PATH', dirname( __FILE__ ) );
35 define( 'ZONINATOR_URL', trailingslashit( plugins_url( '', __FILE__ ) ) );
36
37 require_once( ZONINATOR_PATH . '/functions.php' );
38 require_once( ZONINATOR_PATH . '/widget.zone-posts.php');
39
40 class Zoninator
41 {
42 var $key = 'zoninator';
43 var $zone_taxonomy = 'zoninator_zones';
44 var $zone_term_prefix = 'zone-';
45 var $zone_meta_prefix = '_zoninator_order_';
46 var $zone_nonce_prefix = 'zone-nonce';
47 var $zone_ajax_nonce_action = 'ajax-action';
48 var $zone_lock_period = 30; // number of seconds a lock is valid for
49 var $zone_max_lock_period = 600; // max number of seconds for all locks in a session
50 var $post_types = null;
51 var $zone_detail_defaults = array(
52 'description' => ''
53 // Add additional properties here!
54 );
55 var $zone_messages = null;
56 var $posts_per_page = 10;
57
58 function __construct() {
59 add_action( 'init', array( $this, 'init' ) );
60
61 add_action( 'widgets_init', array( $this, 'widgets_init' ) );
62
63 $this->zone_messages = array(
64 'insert-success' => __( 'The zone was successfully created.', 'zoninator' ),
65 'update-success' => __( 'The zone was successfully updated.', 'zoninator' ),
66 'delete-success' => __( 'The zone was successfully deleted.', 'zoninator' ),
67 'error-general' => __( 'Sorry, something went wrong! Please try again?', 'zoninator' ),
68 'error-zone-lock' => __( 'Sorry, this zone is in use by %s and is currently locked. Please try again later.', 'zoninator' ),
69 'error-zone-lock-max' => __( 'Sorry, you have reached the maximum idle limit and will now be redirected to the Dashboard.', 'zoninator' ),
70 );
71
72 $this->default_post_types = array( 'post' );
73
74 $this->zone_lock_period = apply_filters( 'zoninator_zone_lock_period', $this->zone_lock_period );
75 $this->zone_max_lock_period = apply_filters( 'zoninator_zone_max_lock_period', $this->zone_max_lock_period );
76 $this->posts_per_page = apply_filters( 'zoninator_posts_per_page', $this->posts_per_page );
77 }
78
79 function init() {
80
81 do_action( 'zoninator_pre_init' );
82
83 // Default post type support
84 foreach( $this->default_post_types as $post_type )
85 add_post_type_support( $post_type, $this->zone_taxonomy );
86
87 // Register taxonomy
88 if( ! taxonomy_exists( $this->zone_taxonomy ) ) {
89 register_taxonomy( $this->zone_taxonomy, $this->get_supported_post_types(), array(
90 'label' => __( 'Zones', 'zoninator' ),
91 'hierarchical' => false,
92 'query_var' => false,
93 'rewrite' => false,
94 'public' => false,
95
96 ) );
97 }
98
99 add_action( 'admin_init', array( $this, 'admin_controller' ) );
100 add_action( 'admin_init', array( $this, 'admin_init' ) );
101
102 add_action( 'admin_menu', array( $this, 'admin_page_init' ) );
103
104 do_action( 'zoninator_post_init' );
105 }
106
107 function widgets_init() {
108 register_widget( 'Zoninator_ZonePosts_Widget' );
109 }
110
111 // Add necessary AJAX actions
112 function admin_ajax_init( ) {
113 add_action( 'wp_ajax_zoninator_reorder_posts', array( $this, 'ajax_reorder_posts' ) );
114 add_action( 'wp_ajax_zoninator_add_post', array( $this, 'ajax_add_post' ) );
115 add_action( 'wp_ajax_zoninator_remove_post', array( $this, 'ajax_remove_post' ) );
116 add_action( 'wp_ajax_zoninator_search_posts', array( $this, 'ajax_search_posts' ) );
117 add_action( 'wp_ajax_zoninator_update_lock', array( $this, 'ajax_update_lock' ) );
118 }
119
120 function admin_init() {
121
122 $this->admin_ajax_init();
123
124 // Enqueue Scripts and Styles
125 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
126 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_styles' ) );
127 }
128
129 function admin_page_init() {
130 // Set up page
131 add_menu_page( __( 'Zoninator', 'zoninator' ), __( 'Zones', 'zoninator' ), $this->_get_manage_zones_cap(), $this->key, array( $this, 'admin_page' ), '', 11 );
132 }
133
134 function admin_enqueue_scripts() {
135 if( $this->is_zoninator_page() ) {
136 wp_enqueue_script( 'zoninator-js', ZONINATOR_URL . 'js/zoninator.js', array( 'jquery', 'jquery-ui-core', 'jquery-ui-widget', 'jquery-ui-mouse', 'jquery-ui-position', 'jquery-ui-sortable', 'jquery-ui-autocomplete' ), ZONINATOR_VERSION, true );
137
138 $options = array(
139 'baseUrl' => $this->_get_zone_page_url(),
140 'adminUrl' => admin_url(),
141 'ajaxNonceAction' => $this->_get_nonce_key( $this->zone_ajax_nonce_action ),
142 'errorGeneral' => $this->_get_message( 'error-general' ),
143 'errorZoneLock' => sprintf( $this->_get_message( 'error-zone-lock' ), __( 'another user', 'zoninator' ) ),
144 'errorZoneLockMax' => $this->_get_message( 'error-zone-lock-max' ),
145 'zoneLockPeriod' => $this->zone_lock_period,
146 'zoneLockPeriodMax' => $this->zone_max_lock_period,
147 );
148 wp_localize_script( 'zoninator-js', 'zoninatorOptions', $options );
149
150 }
151 }
152
153 function admin_enqueue_styles() {
154 if( $this->is_zoninator_page() ) {
155 wp_enqueue_style( 'zoninator-jquery-ui', ZONINATOR_URL . 'css/jquery-ui/smoothness/jquery-ui-zoninator.css', false, ZONINATOR_VERSION, 'all' );
156 wp_enqueue_style( 'zoninator-styles', ZONINATOR_URL . 'css/zoninator.css', false, ZONINATOR_VERSION, 'all' );
157 }
158 }
159
160 function admin_controller() {
161 if( $this->is_zoninator_page() ) {
162 $action = $this->_get_request_var( 'action' );
163
164 switch( $action ) {
165
166 case 'insert':
167 case 'update':
168 $zone_id = $this->_get_post_var( 'zone_id', 0, 'absint' );
169
170 $this->verify_nonce( $action );
171 $this->verify_access( $action, $zone_id );
172
173 $name = $this->_get_post_var( 'name' );
174 $slug = $this->_get_post_var( 'slug', sanitize_title( $name ) );
175 $details = array(
176 'description' => $this->_get_post_var( 'description', '', 'strip_tags' )
177 );
178
179 // TODO: handle additional properties
180 if( $zone_id ) {
181 $result = $this->update_zone( $zone_id, array(
182 'name' => $name,
183 'slug' => $slug,
184 'details' => $details
185 ) );
186
187 } else {
188 $result = $this->insert_zone( $slug, $name, $details );
189 }
190
191 if( is_wp_error( $result ) ) {
192 wp_redirect( add_query_arg( 'message', 'error-general' ) );
193 exit;
194 } else {
195 if( ! $zone_id && isset( $result['term_id'] ) )
196 $zone_id = $result['term_id'];
197
198 // Redirect with success message
199 $message = sprintf( '%s-success', $action );
200 wp_redirect( $this->_get_zone_page_url( array( 'action' => 'edit', 'zone_id' => $zone_id, 'message' => $message ) ) );
201 exit;
202 }
203 break;
204
205 case 'delete':
206 $zone_id = $this->_get_request_var( 'zone_id', 0, 'absint' );
207
208 $this->verify_nonce( $action );
209 $this->verify_access( $action, $zone_id );
210
211 if( $zone_id ) {
212 $result = $this->delete_zone( $zone_id );
213 }
214
215 if( is_wp_error( $result ) ) {
216 $redirect_args = array( 'error' => $result->get_error_messages() );
217 } else {
218 $redirect_args = array( 'message' => 'delete-success' );
219 }
220
221 wp_redirect( $this->_get_zone_page_url( $redirect_args ) );
222 exit;
223 }
224 }
225 }
226
227 function admin_page() {
228 global $zoninator_admin_page;
229
230 $view = $this->_get_value_or_default( 'view', $zoninator_admin_page, 'edit.php' );
231 $view = sprintf( '%s/views/%s', ZONINATOR_PATH, $view );
232 $title = $this->_get_value_or_default( 'title', $zoninator_admin_page, __( 'Zones', 'zoninator' ) );
233
234 $zones = $this->get_zones();
235
236 $default_active_zone = 0;
237 if( ! $this->_current_user_can_add_zones() ) {
238 if( ! empty( $zones ) )
239 $default_active_zone = $zones[0]->term_id;
240 }
241
242 $active_zone_id = $this->_get_request_var( 'zone_id', $default_active_zone, 'absint' );
243 $active_zone = ! empty( $active_zone_id ) ? $this->get_zone( $active_zone_id ) : array();
244
245 $message = $this->_get_message( $this->_get_get_var( 'message', '', 'urldecode' ) );
246 $error = $this->_get_get_var( 'error', '', 'urldecode' );
247
248 ?>
249 <div class="wrap zoninator-page">
250 <div id="icon-edit-pages" class="icon32"><br /></div>
251 <h2><?php echo esc_html( $title ); ?></h2>
252
253 <?php if( $message ) : ?>
254 <div id="zone-message" class="updated below-h2">
255 <p><?php echo esc_html( $message ); ?></p>
256 </div>
257 <?php endif; ?>
258 <?php if( $error ) : ?>
259 <div id="zone-message" class="error below-h2">
260 <p><?php echo esc_html( $error ); ?></p>
261 </div>
262 <?php endif; ?>
263
264 <div id="zoninator-wrap">
265
266 <?php $this->admin_page_zone_tabs( $zones, $active_zone_id ); ?>
267
268 <?php $this->admin_page_zone_edit( $active_zone ); ?>
269
270 </div>
271 </div>
272 <?php
273 }
274
275 function admin_page_zone_tabs( $zones, $active_zone_id = 0 ) {
276 $new_link = $this->_get_zone_page_url( array( 'action' => 'new' ) );
277 ?>
278 <div class="nav-tabs-container zone-tabs-container">
279 <div class="nav-tabs-nav-wrapper zone-tabs-nav-wrapper">
280 <div class="nav-tabs-wrapper zone-tabs-wrapper">
281 <div class="nav-tabs zone-tabs">
282 <?php foreach( $zones as $zone ) : ?>
283 <?php $zone_id = $this->get_zone_id( $zone ); ?>
284 <?php $zone_link = $this->_get_zone_page_url( array( 'action' => 'edit', 'zone_id' => $zone_id ) ); ?>
285
286 <?php if( $active_zone_id && $zone_id == $active_zone_id ) : ?>
287 <span class="nav-tab nav-tab-active zone-tab zone-tab-active"><?php echo esc_html( $zone->name ); ?></span>
288 <?php else : ?>
289 <a href="<?php echo esc_url( $zone_link ); ?>" class="nav-tab zone-tab"><?php echo esc_html( $zone->name ); ?></a>
290 <?php endif; ?>
291 <?php endforeach; ?>
292 </div>
293 </div>
294 </div>
295
296 <div class="zone-tab-new">
297 <?php if( $this->_current_user_can_add_zones() ) : ?>
298 <?php if( $active_zone_id ) : ?>
299 <a href="<?php echo $new_link; ?>" class="nav-tab zone-tab"><?php _e( '+', 'zoninator' ); ?></a>
300 <?php else : ?>
301 <span class="nav-tab nav-tab-active zone-tab zone-tab-active"><?php _e( '+', 'zoninator' ); ?></span>
302 <?php endif; ?>
303 <?php endif; ?>
304 </div>
305 <div class="clear"></div>
306 </div>
307 <?php
308 }
309
310 function admin_page_zone_edit( $zone = null ) {
311 $zone_id = $this->_get_value_or_default( 'term_id', $zone, 0, 'absint' );
312 $zone_name = $this->_get_value_or_default( 'name', $zone );
313 $zone_slug = $this->_get_value_or_default( 'slug', $zone, '', array( $this, 'get_unformatted_zone_slug' ) );
314 $zone_description = $this->_get_value_or_default( 'description', $zone );
315
316 $zone_posts = $zone_id ? $this->get_zone_posts( $zone ) : array();
317
318 $zone_locked = $this->is_zone_locked( $zone_id );
319
320 $delete_link = $this->_get_zone_page_url( array( 'action' => 'delete', 'zone_id' => $zone_id ) );
321 $delete_link = wp_nonce_url( $delete_link, $this->_get_nonce_key( 'delete' ) );
322 ?>
323 <div id="zone-edit-wrapper">
324 <?php if( ( $zone_id == 0 && $this->_current_user_can_add_zones() ) || ( $zone_id != 0 && $this->_current_user_can_manage_zones() ) ) : ?>
325 <?php if( $zone_locked ) : ?>
326 <?php $locking_user = get_userdata( $zone_locked ); ?>
327 <div class="updated below-h2">
328 <p><?php echo sprintf( $this->_get_message( 'error-zone-lock' ), sprintf( '<a href="mailto:%s">%s</a>', $locking_user->user_email, $locking_user->display_name ) ); ?></p>
329 </div>
330 <input type="hidden" id="zone-locked" name="zone-locked" value="1" />
331 <?php endif; ?>
332 <div class="col-wrap zone-col zone-info-col">
333 <div class="form-wrap zone-form zone-info-form">
334
335 <?php if( $this->_current_user_can_edit_zones( $zone_id ) && ! $zone_locked ) : ?>
336
337 <form id="zone-info" method="post">
338
339 <?php do_action( 'zoninator_pre_zone_fields', $zone ); ?>
340
341 <div class="form-field zone-field">
342 <label for="zone-name"><?php _e( 'Name', 'zoninator' ); ?></label>
343 <input type="text" id="zone-name" name="name" value="<?php echo esc_attr( $zone_name ); ?>" />
344 </div>
345
346 <?php if( $zone_id ) : ?>
347 <div class="form-field zone-field">
348 <label for="zone-slug"><?php _e( 'Slug', 'zoninator' ); ?></label>
349 <span><?php echo esc_attr( $zone_slug ); ?></span>
350 <input type="hidden" id="zone-slug" name="slug" value="<?php echo esc_attr( $zone_slug ); ?>" />
351 </div>
352 <?php endif; ?>
353
354 <div class="form-field zone-field">
355 <label for="zone-description"><?php _e( 'Description', 'zoninator' ); ?></label>
356 <textarea id="zone-description" name="description"><?php echo esc_html( $zone_description ); ?></textarea>
357 </div>
358
359 <?php do_action( 'zoninator_post_zone_fields', $zone ); ?>
360
361 <?php if( $zone_id ) : ?>
362 <input type="hidden" name="zone_id" id="zone_id" value="<?php echo esc_attr( $zone_id ) ?>" />
363 <?php wp_nonce_field( $this->_get_nonce_key( 'update' ) ); ?>
364 <?php else : ?>
365 <?php wp_nonce_field( $this->_get_nonce_key( 'insert' ) ); ?>
366 <?php endif; ?>
367
368 <div class="submit-field submitbox">
369 <input type="submit" value="<?php _e('Save', 'zoninator'); ?>" name="submit" class="button-primary" />
370
371 <?php if( $zone_id ) : ?>
372 <a href="<?php echo $delete_link ?>" class="submitdelete" onclick="return confirm('<?php echo esc_js( 'Are you sure you want to delete this zone?', 'zoninator' ); ?>')"><?php _e('Delete', 'zoninator') ?></a>
373 <?php endif; ?>
374 </div>
375
376 <input type="hidden" name="action" value="<?php echo $zone_id ? 'update' : 'insert'; ?>">
377 <input type="hidden" name="page" value="<?php echo $this->key; ?>">
378
379 </form>
380 <?php else : ?>
381 <div id="zone-info-readonly" class="readonly">
382 <?php do_action( 'zoninator_pre_zone_readonly', $zone ); ?>
383
384 <div class="form-field zone-field">
385 <label for="zone-name"><?php _e( 'Name', 'zoninator' ); ?></label>
386 <span><?php echo esc_attr( $zone_name ); ?></span>
387 </div>
388
389 <!--
390 <div class="form-field zone-field">
391 <label for="zone-slug"><?php _e( 'Slug', 'zoninator' ); ?></label>
392 <span><?php echo esc_attr( $zone_slug ); ?></span>
393 </div>
394 -->
395
396 <div class="form-field zone-field">
397 <label for="zone-description"><?php _e( 'Description', 'zoninator' ); ?></label>
398 <span><?php echo esc_html( $zone_description ); ?></span>
399 </div>
400
401 <input type="hidden" name="zone_id" id="zone_id" value="<?php echo esc_attr( $zone_id ) ?>" />
402
403 <?php do_action( 'zoninator_post_zone_readonly', $zone ); ?>
404 </div>
405 <?php endif; ?>
406
407 <?php // Ideally, we should seperate nonces for each action. But this will do for simplicity. ?>
408 <?php wp_nonce_field( $this->_get_nonce_key( $this->zone_ajax_nonce_action ), $this->_get_nonce_key( $this->zone_ajax_nonce_action ), false ); ?>
409 </div>
410
411 </div>
412
413 <div class="col-wrap zone-col zone-posts-col">
414 <div class="zone-posts-wrapper <?php echo ! $this->_current_user_can_manage_zones( $zone_id ) || $zone_locked ? 'readonly' : ''; ?>">
415 <?php if( $zone_id ) : ?>
416 <h3><?php _e( 'Zone Content', 'zoninator' ); ?></h3>
417
418 <?php $this->zone_admin_recent_posts_dropdown( $zone_id ); ?>
419
420 <?php $this->zone_admin_search_form(); ?>
421
422 <div class="zone-posts-list">
423 <?php foreach( $zone_posts as $post ) : ?>
424 <?php $this->admin_page_zone_post( $post, $zone ); ?>
425 <?php endforeach; ?>
426 </div>
427
428 <?php else : ?>
429 <p class="description"><?php _e( 'To create a zone, enter a name (and any other info) to to left and click "Save". You can then choose content items to add to the zone.', 'zoninator' ); ?></p>
430 <?php endif; ?>
431 </div>
432 </div>
433 <?php endif; ?>
434 </div>
435
436 <?php
437 }
438
439 function admin_page_zone_post( $post, $zone ) {
440 $columns = apply_filters( 'zoninator_zone_post_columns', array(
441 'position' => array( $this, 'admin_page_zone_post_col_position' ),
442 'info' => array( $this, 'admin_page_zone_post_col_info' )
443 ), $post, $zone );
444 ?>
445 <div id="zone-post-<?php echo $post->ID; ?>" class="zone-post" data-post-id="<?php echo $post->ID; ?>">
446 <table>
447 <tr>
448 <?php foreach( $columns as $column_key => $column_callback ) : ?>
449 <?php if( is_callable( $column_callback ) ) : ?>
450 <td class="zone-post-col zone-post-<?php echo $column_key; ?>">
451 <?php call_user_func( $column_callback, $post, $zone ); ?>
452 </td>
453 <?php endif; ?>
454 <?php endforeach; ?>
455 </tr>
456 </table>
457 <input type="hidden" name="zone-post-id" value="<?php echo $post->ID; ?>" />
458 </div>
459 <?php
460 }
461
462 function admin_page_zone_post_col_position( $post, $zone ) {
463 $current_position = $this->get_post_order( $post->ID, $zone );
464 ?>
465 <span title="<?php esc_attr_e( 'Click and drag to change the position of this item.', 'zoninator' ); ?>">
466 <?php echo esc_html( $current_position ); ?>
467 </span>
468 <?php
469 }
470 function admin_page_zone_post_col_info( $post, $zone ) {
471 $action_links = array(
472 sprintf( '<a href="%s" class="edit" target="_blank" title="%s">%s</a>', get_edit_post_link( $post->ID ), __( 'Opens in new window', 'zoninator' ), __( 'Edit', 'zoninator' ) ),
473 sprintf( '<a href="#" class="delete" title="%s">%s</a>', __( 'Remove this item from the zone', 'zoninator' ), __( 'Remove', 'zoninator' ) ),
474 sprintf( '<a href="%s" class="view" target="_blank" title="%s">%s</a>', get_permalink( $post->ID ), __( 'Opens in new window', 'zoninator' ), __( 'View', 'zoninator' ) ),
475 // Move To
476 // Copy To
477 );
478 ?>
479 <?php echo sprintf( '%s <span class="zone-post-status">(%s)</span>', esc_html( $post->post_title ), esc_html( $post->post_status ) ); ?>
480
481 <div class="row-actions">
482 <?php echo implode( ' | ', $action_links ); ?>
483 </div>
484 <?php
485 }
486
487 function zone_admin_recent_posts_dropdown( $zone_id ) {
488
489 $limit = $this->posts_per_page;
490 $post_types = $this->get_supported_post_types();
491 $zone_posts = $this->get_zone_posts( $zone_id );
492 $zone_post_ids = wp_list_pluck( $zone_posts, 'ID' );
493
494 $args = apply_filters( 'zoninator_recent_posts_args', array(
495 'posts_per_page' => $limit,
496 'order' => 'DESC',
497 'orderby' => 'post_date',
498 'post_type' => $post_types,
499 'ignore_sticky_posts' => true,
500 'post_status' => array( 'publish', 'future' ),
501 'post__not_in' => $zone_post_ids,
502 ) );
503
504 $latest_query = new WP_Query( $args );
505 ?>
506 <div class="zone-search-wrapper">
507 <label for="zone-post-search-latest"><?php _e( 'Add Recent Content', 'zoninator' );?></label><br />
508 <select name="search-posts" id="zone-post-latest">
509 <option value="">Choose latest post</option>
510 <?php
511 while ( $latest_query->have_posts() ) : $latest_query->the_post();
512 echo sprintf( '<option value="%d">%s</option>', get_the_ID(), get_the_title() );
513 endwhile;
514 wp_reset_postdata();
515 ?>
516 </select>
517 </div>
518 <?php
519 }
520
521 function zone_admin_search_form() {
522 ?>
523 <div class="zone-search-wrapper">
524 <label for="zone-post-search"><?php _e( 'Search for content', '' );?></label>
525 <input type="text" id="zone-post-search" name="search" />
526 <p class="description"><?php _e( 'Enter a term or phrase in the text box above to search for and add content to this zone.', 'zoninator' ); ?></p>
527 </div>
528 <?php
529 }
530
531 function is_zoninator_page() {
532 global $current_screen;
533
534 if( function_exists( 'get_current_screen' ) )
535 $screen = get_current_screen();
536
537 if( empty( $screen ) ) {
538 return ! empty( $_REQUEST['page'] ) && sanitize_key( $_REQUEST['page'] ) == $this->key;
539 } else {
540 return ! empty( $screen->id ) && strstr( $screen->id, $this->key );
541 }
542 }
543
544 function ajax_return( $status, $content = '', $action = '' ) {
545 $action = ! empty( $action ) ? $action : $this->zone_ajax_nonce_action;
546 $nonce = wp_create_nonce( $this->_get_nonce_key( $action ) );
547
548 echo json_encode( array(
549 'status' => $status,
550 'content' => $content,
551 'nonce' => $nonce,
552 ) );
553 exit;
554 }
555
556 function ajax_add_post() {
557 $zone_id = $this->_get_post_var( 'zone_id', 0, 'absint' );
558 $post_id = $this->_get_post_var( 'post_id', 0, 'absint' );
559
560 // Verify nonce
561 $this->verify_nonce( $this->zone_ajax_nonce_action );
562 $this->verify_access( '', $zone_id );
563
564 // Validate
565 if( ! $zone_id || ! $post_id )
566 $this->ajax_return( 0 );
567
568 $result = $this->add_zone_posts( $zone_id, $post_id, true );
569
570 if( is_wp_error( $result ) ) {
571 $status = 0;
572 $content = $result->get_error_message();
573 } else {
574 $post = get_post( $post_id );
575 $zone = $this->get_zone( $zone_id );
576
577 ob_start();
578 $this->admin_page_zone_post( $post, $zone );
579 $content = ob_get_contents();
580 ob_end_clean();
581
582 $status = 1;
583 }
584
585 $this->ajax_return( $status, $content );
586 }
587
588 function ajax_remove_post() {
589 $zone_id = $this->_get_post_var( 'zone_id', 0, 'absint' );
590 $post_id = $this->_get_post_var( 'post_id', 0, 'absint' );
591
592 // Verify nonce
593 $this->verify_nonce( $this->zone_ajax_nonce_action );
594 $this->verify_access( '', $zone_id );
595
596 // Validate
597 if( ! $zone_id || ! $post_id )
598 $this->ajax_return( 0 );
599
600 $result = $this->remove_zone_posts( $zone_id, $post_id );
601
602 if( is_wp_error( $result ) ) {
603 $status = 0;
604 $content = $result->get_error_message();
605 } else {
606 $status = 1;
607 $content = '';
608 }
609
610 $this->ajax_return( $status, $content );
611 }
612
613 function ajax_reorder_posts() {
614 $zone_id = $this->_get_post_var( 'zone_id', 0, 'absint' );
615 $post_ids = (array) $this->_get_post_var( 'posts', array(), 'absint' );
616
617 // Verify nonce
618 $this->verify_nonce( $this->zone_ajax_nonce_action );
619 $this->verify_access( '', $zone_id );
620
621 // validate
622 if( ! $zone_id || empty( $post_ids ) )
623 $this->ajax_return( 0 );
624
625 $result = $this->add_zone_posts( $zone_id, $post_ids, false );
626
627 if( is_wp_error( $result ) ) {
628 $status = 0;
629 $content = $result->get_error_message();
630 } else {
631 $status = 1;
632 $content = '';
633 }
634
635 $this->ajax_return( $status, $content );
636 }
637
638 // TODO: implement in front-end
639 function ajax_move_zone_post( $from_zone, $to_zone, $post_id ) {
640 $from_zone_id = $this->_get_post_var( 'from_zone_id', 0, 'absint' );
641 $to_zone_id = $this->_get_post_var( 'to_zone_id', 0, 'absint' );
642
643 $this->verify_nonce( $this->zone_ajax_nonce_action );
644
645 // TODO: validate both zones exist, post exists
646
647 // Add to new zone
648 $this->add_zone_posts( $to_zone_id, $post_id, true );
649
650 // Remove from old zone
651 $this->remove_zone_posts( $from_zone_id, $post_id );
652 }
653
654 function ajax_search_posts() {
655
656 $q = $this->_get_request_var( 'term', '', 'stripslashes' );
657
658 if( ! empty( $q ) ) {
659
660 $post_types = $this->get_supported_post_types();
661 $limit = $this->_get_request_var( 'limit', $this->posts_per_page );
662 if( $limit <= 0 )
663 $limit = $this->posts_per_page;
664 $exclude = (array) $this->_get_request_var( 'exclude', array(), 'absint' );
665
666 $args = apply_filters( 'zoninator_search_args', array(
667 's' => $q,
668 'post__not_in' => $exclude,
669 'posts_per_page' => $limit,
670 'post_type' => $post_types,
671 'post_status' => array( 'publish', 'future' ),
672 'order' => 'DESC',
673 'orderby' => 'post_date',
674 'suppress_filters' => true,
675 ) );
676
677 $query = new WP_Query( $args );
678 $stripped_posts = array();
679
680 if ( ! $query->have_posts() )
681 exit;
682
683 foreach( $query->posts as $post ) {
684 $stripped_posts[] = array(
685 'title' => ! empty( $post->post_title ) ? $post->post_title : __( '(no title)', 'zoninator' ),
686 'post_id' => $post->ID,
687 'date' => get_the_time( get_option( 'date_format' ), $post ),
688 'post_type' => $post->post_type,
689 'post_status' => $post->post_status,
690 );
691 }
692
693 echo json_encode( $stripped_posts );
694 exit;
695 }
696 }
697
698 function ajax_update_lock() {
699 $zone_id = $this->_get_post_var( 'zone_id', 0, 'absint' );
700
701 $this->verify_nonce( $this->zone_ajax_nonce_action );
702 $this->verify_access( '', $zone_id );
703
704 if( ! $zone_id )
705 exit;
706
707 if( ! $this->is_zone_locked( $zone_id ) ) {
708 $this->lock_zone( $zone_id );
709 $this->ajax_return( 1, '' );
710 }
711 }
712
713 function get_supported_post_types() {
714 if( isset( $this->post_types ) )
715 return $this->post_types;
716
717 $this->post_types = array();
718
719 foreach( get_post_types() as $post_type ) {
720 if( post_type_supports( $post_type, $this->zone_taxonomy ) )
721 array_push( $this->post_types, $post_type );
722 }
723
724 return $this->post_types;
725 }
726
727 function insert_zone( $slug, $name = '', $details = array() ) {
728
729 // slug cannot be empty
730 if( empty( $slug ) )
731 return new WP_Error( 'zone-empty-slug', __( 'Slug is a required field.', 'zoninator' ) );
732
733 $slug = $this->get_formatted_zone_slug( $slug );
734 $name = ! empty( $name ) ? $name : $slug;
735
736 $details = wp_parse_args( $details, $this->zone_detail_defaults );
737 $details = maybe_serialize( stripslashes_deep( $details ) );
738
739 $args = array(
740 'slug' => $slug,
741 'description' => $details,
742 );
743
744 // Filterize to allow other inputs
745 $args = apply_filters( 'zoninator_insert_zone', $args );
746
747 return wp_insert_term( $name, $this->zone_taxonomy, $args );
748 }
749
750 function update_zone( $zone, $data = array() ) {
751 $zone_id = $this->get_zone_id( $zone );
752
753 if( $this->zone_exists( $zone_id ) ) {
754 $zone = $this->get_zone( $zone );
755
756 $name = $this->_get_value_or_default( 'name', $data, $zone->name );
757 $slug = $this->_get_value_or_default( 'slug', $data, $zone->slug, array( $this, 'get_formatted_zone_slug' ) );
758 $details = $this->_get_value_or_default( 'details', $data, array() );
759
760 // TODO: Back-fill current zone details
761 //$details = wp_parse_args( $details, $this->zone_detail_defaults );
762 $details = wp_parse_args( $details, $this->zone_detail_defaults );
763 $details = maybe_serialize( stripslashes_deep( $details ) );
764
765 $args = array(
766 'name' => $name,
767 'slug' => $slug,
768 'description' => $details
769 );
770
771 // Filterize to allow other inputs
772 $args = apply_filters( 'zoninator_update_zone', $args, $zone_id, $zone );
773
774 return wp_update_term( $zone_id, $this->zone_taxonomy, $args );
775 }
776 return new WP_Error( 'invalid-zone', __( 'Sorry, that zone doesn\'t exist.', 'zoninator' ) );
777 }
778
779 function delete_zone( $zone ) {
780 $zone_id = $this->get_zone_id( $zone );
781 $meta_key = $this->get_zone_meta_key( $zone );
782
783 $this->_empty_zone_posts_cache( $meta_key );
784
785 if( $this->zone_exists( $zone_id ) ) {
786 // Delete all post associations for the zone
787 $this->remove_zone_posts( $zone_id );
788
789 // Delete the term
790 $delete = wp_delete_term( $zone_id, $this->zone_taxonomy );
791
792 if( ! $delete )
793 return new WP_Error( 'delete-zone', __( 'Sorry, we couldn\'t delete the zone.', 'zoninator' ) );
794 else
795 return $delete;
796 }
797 return new WP_Error( 'invalid-zone', __( 'Sorry, that zone doesn\'t exist.', 'zoninator' ) );
798 }
799
800 function add_zone_posts( $zone, $posts, $append = false ) {
801 $zone = $this->get_zone( $zone );
802 $meta_key = $this->get_zone_meta_key( $zone );
803
804 $this->_empty_zone_posts_cache( $meta_key );
805
806 if( $append ) {
807 // Order should be the highest post order
808 $last_post = $this->get_last_post_in_zone( $zone );
809 if( $last_post )
810 $order = $this->get_post_order( $last_post, $zone );
811 else
812 $order = 0;
813 } else {
814 $order = 0;
815 $this->remove_zone_posts( $zone );
816 }
817
818 foreach( (array) $posts as $post ) {
819 $post_id = $this->get_post_id( $post );
820 if( $post_id ) {
821 $order++;
822 update_metadata( 'post', $post_id, $meta_key, $order, true );
823 }
824 // TODO: remove_object_terms -- but need remove object terms function :(
825 }
826
827 clean_term_cache( $this->get_zone_id( $zone ), $this->zone_taxonomy ); // flush cache for our zone term and related APC caches
828 }
829
830 function remove_zone_posts( $zone, $posts = null ) {
831 $zone = $this->get_zone( $zone );
832 $meta_key = $this->get_zone_meta_key( $zone );
833
834 $this->_empty_zone_posts_cache( $meta_key );
835
836 // if null, delete all
837 if( ! $posts )
838 $posts = $this->get_zone_posts( $zone );
839
840 foreach( (array) $posts as $post ) {
841 $post_id = $this->get_post_id( $post );
842 if( $post_id )
843 delete_metadata( 'post', $post_id, $meta_key );
844 }
845
846 clean_term_cache( $this->get_zone_id( $zone ), $this->zone_taxonomy ); // flush cache for our zone term and related APC caches
847 }
848
849 function get_zone_posts( $zone, $args = array() ) {
850 // Check cache first
851 if( $posts = $this->get_zone_posts_from_cache( $zone, $args ) )
852 return $posts;
853
854 $query = $this->get_zone_query( $zone, $args );
855 $posts = $query->get_posts();
856
857 // Add posts to cache
858 $this->add_zone_posts_to_cache( $posts, $zone, $args );
859
860 return $posts;
861 }
862
863 function get_zone_query( $zone, $args = array() ) {
864 $meta_key = $this->get_zone_meta_key( $zone );
865
866 $defaults = array(
867 'order' => 'ASC',
868 'posts_per_page' => -1,
869 'post_type' => $this->get_supported_post_types(),
870 'ignore_sticky_posts' => '1', // don't want sticky posts messing up our order
871 );
872
873 // Default to published posts on the front-end
874 if ( ! is_admin() )
875 $defaults['post_status'] = array( 'publish' );
876
877 if ( is_admin() ) // skip APC in the admin
878 $defaults['suppress_filters'] = true;
879
880 $args = wp_parse_args( $args, $defaults );
881
882 // Un-overridable args
883 $args['orderby'] = 'meta_value_num';
884 $args['meta_key'] = $meta_key;
885
886 /* // 3.1-friendly, though missing sort support which is why we're using the old way
887 if( function_exists( 'get_post_format' ) ) {
888 $args['meta_query'] = array(
889 array(
890 'key' => $meta_key,
891 'type' => 'NUMERIC'
892 )
893 );
894 } else {
895 $args['meta_key'] = $meta_key;
896 }
897 */
898 return new WP_Query( $args );
899 }
900
901 function get_last_post_in_zone( $zone ) {
902 return $this->get_single_post_in_zone( $zone, array(
903 'order' => 'DESC',
904 ) );
905 }
906
907 function get_first_post_in_zone( $zone ) {
908 return $this->get_single_post_in_zone( $zone );
909 }
910
911 function get_prev_post_in_zone( $zone, $post_id ) {
912 // TODO: test this works
913 $order = $this->get_post_order_in_zone( $zone, $post_id );
914
915 return $this->get_single_post_in_zone( $zone, array(
916 'meta_value' => $order,
917 'meta_compare' => '<='
918 ) );
919 }
920
921 function get_next_post_in_zone( $zone, $post_id ) {
922 // TODO: test this works
923 $order = $this->get_post_order_in_zone( $zone, $post_id );
924
925 return $this->get_single_post_in_zone( $zone, array(
926 'meta_value' => $order,
927 'meta_compare' => '>='
928 ) );
929
930 }
931
932 function get_single_post_in_zone( $zone, $args = array() ) {
933
934 $args = wp_parse_args( $args, array(
935 'posts_per_page' => 1,
936 'showposts' => 1,
937 ) );
938
939 $post = $this->get_zone_posts( $zone, $args );
940
941 if( is_array( $post ) && ! empty( $post ) )
942 return array_pop( $post );
943
944 return false;
945 }
946
947 function get_zones_for_post( $post_id ) {
948 // TODO: build this out
949
950 // get_object_terms
951 // get_terms
952
953 // OR
954
955 // get all meta_keys that match the prefix
956 // strip the prefix
957
958 // OR
959
960 // get all zones and see if there's a matching meta entry
961 // strip the prefix from keys
962
963 // array_map( 'absint', $zone_ids )
964 // $zones = array();
965 // foreach( $zone_ids as $zone_id ) {
966 // $zones[] = get_zone( $zone_id );
967 //}
968 //return $zones;
969 }
970
971 function get_zones( $args = array() ) {
972
973 $args = wp_parse_args( $args, array(
974 'orderby' => 'id',
975 'order' => 'ASC',
976 'hide_empty' => 0,
977 ) );
978
979 $zones = get_terms( $this->zone_taxonomy, $args );
980
981 // Add extra fields in description as properties
982 foreach( $zones as $zone ) {
983 $zone = $this->_fill_zone_details( $zone );
984 }
985
986 return $zones;
987 }
988
989 function get_zone( $zone ) {
990 if( is_int( $zone ) ) {
991 $field = 'id';
992 } elseif( is_string( $zone ) ) {
993 $field = 'slug';
994 $zone = $this->get_zone_slug( $zone );
995 } elseif( is_object( $zone ) ) {
996 return $zone;
997 } else {
998 return false;
999 }
1000
1001 $zone = get_term_by( $field, $zone, $this->zone_taxonomy );
1002
1003 if( ! $zone )
1004 return false;
1005
1006 return $this->_fill_zone_details( $zone );
1007 }
1008
1009 function lock_zone( $zone, $user_id = 0 ) {
1010 $zone_id = $this->get_zone_id( $zone );
1011
1012 if( ! $zone_id )
1013 return false;
1014
1015 if( ! $user_id ) {
1016 $user = wp_get_current_user();
1017 $user_id = $user->ID;
1018 }
1019
1020 $lock_key = $this->get_zone_meta_key( $zone );
1021 $expiry = $this->zone_lock_period + 1; // Add a one to avoid most race condition issues between lock expiry and ajax call
1022 set_transient( $lock_key, $user->ID, $expiry );
1023
1024 // Possible alternative: set zone lock as property with time and user
1025 }
1026
1027 // Not really needed with transients...
1028 function unlock_zone( $zone ) {
1029 $zone_id = $this->get_zone_id( $zone );
1030
1031 if( ! $zone_id )
1032 return false;
1033
1034 $lock_key = $this->get_zone_meta_key( $zone );
1035
1036 delete_transient( $lock_key );
1037 }
1038
1039 function is_zone_locked( $zone ) {
1040 $zone_id = $this->get_zone_id( $zone );
1041 if( ! $zone_id )
1042 return false;
1043
1044 $user = wp_get_current_user();
1045 $lock_key = $this->get_zone_meta_key( $zone );
1046
1047 $lock = get_transient( $lock_key );
1048
1049 // If lock doesn't exist, or check if current user same as lock user
1050 if( ! $lock || absint( $lock ) === absint( $user->ID ) )
1051 return false;
1052 else
1053 // return user_id of locking user
1054 return absint( $lock );
1055 }
1056
1057 function zone_exists( $zone ) {
1058 $zone_id = $this->get_zone_id( $zone );
1059
1060 if( term_exists( $zone_id, $this->zone_taxonomy ) )
1061 return true;
1062
1063 return false;
1064 }
1065
1066 function get_zone_id( $zone ) {
1067 if( is_int( $zone ) )
1068 return $zone;
1069
1070 $zone = $this->get_zone( $zone );
1071 if( is_object( $zone ) )
1072 $zone = $zone->term_id;
1073
1074 return (int)$zone;
1075 }
1076
1077 function get_zone_meta_key( $zone ) {
1078 $zone_id = $this->get_zone_id( $zone );
1079 return $this->zone_meta_prefix . $zone_id;
1080 }
1081
1082 function get_zone_slug( $zone ) {
1083 if( is_int( $zone ) )
1084 $zone = $this->get_zone( $zone );
1085
1086 if( is_object( $zone ) )
1087 $zone = $zone->slug;
1088
1089 return $this->get_formatted_zone_slug( $zone );
1090 }
1091
1092 function get_formatted_zone_slug( $slug ) {
1093 return $slug; // legacy function -- slugs can no longer be changed
1094 }
1095 function get_unformatted_zone_slug( $slug ) {
1096 return $slug; // legacy function -- slugs can no longer be changed
1097 }
1098
1099 function get_post_id( $post ) {
1100 if( is_int( $post ) )
1101 return $post;
1102 elseif( is_array( $post ) )
1103 return absint( $post['ID'] );
1104 elseif( is_object( $post ) )
1105 return $post->ID;
1106
1107 return false;
1108 }
1109
1110 function get_post_order( $post, $zone ) {
1111 $post_id = $this->get_post_id( $post );
1112 $meta_key = $this->get_zone_meta_key( $zone );
1113
1114 return get_metadata( 'post', $post_id, $meta_key, true );
1115 }
1116
1117 function verify_nonce( $action ) {
1118 $action = $this->_get_nonce_key( $action );
1119 $nonce = $this->_get_request_var( $action );
1120
1121 if( empty( $nonce ) )
1122 $nonce = $this->_get_request_var( '_wpnonce' );
1123
1124 if( ! wp_verify_nonce( $nonce, $action ) )
1125 $this->_unauthorized_access();
1126 }
1127
1128 function verify_access( $action = '', $zone_id = null ) {
1129 // TODO: should check if zone locked
1130
1131 $verify_function = '';
1132 switch( $action ) {
1133 case 'insert':
1134 $verify_function = '_current_user_can_add_zones';
1135 break;
1136 case 'update':
1137 case 'delete':
1138 $verify_function = '_current_user_can_edit_zones';
1139 break;
1140 default:
1141 $verify_function = '_current_user_can_manage_zones';
1142 break;
1143 }
1144
1145 if( ! call_user_func( array( $this, $verify_function ), $zone_id ) )
1146 $this->_unauthorized_access();
1147 }
1148
1149 function _unauthorized_access() {
1150 wp_die( __( 'Sorry, you\'re not supposed to do that...', 'zoninator' ) );
1151 }
1152
1153 function _fill_zone_details( $zone ) {
1154 if( ! empty( $zone->zoninator_parsed ) && $zone->zoninator_parsed )
1155 return $zone;
1156
1157 $details = array();
1158
1159 if( ! empty( $zone->description ) )
1160 $details = maybe_unserialize( $zone->description );
1161
1162 $details = wp_parse_args( $details, $this->zone_detail_defaults );
1163
1164 foreach( $details as $detail_key => $detail_value ) {
1165 $zone->$detail_key = $detail_value;
1166 }
1167
1168 $zone->zoninator_parsed = true;
1169
1170 return $zone;
1171 }
1172
1173 // TODO: Caching needs to be testing properly before being implemented!
1174 function get_zone_cache_key( $zone, $args = array() ) {
1175 return '';
1176
1177 $meta_key = $this->get_zone_meta_key( $zone );
1178 $hash = md5( serialize( $args ) );
1179 return $meta_key . $hash;
1180 }
1181
1182 function get_zone_posts_from_cache( $zone, $args = array() ) {
1183 return false; // TODO: implement
1184
1185 $meta_key = $this->get_zone_meta_key( $zone );
1186 $cache_key = $this->get_zone_cache_key( $zone, $args );
1187 if( $posts = wp_cache_get( $cache_key, $meta_key ) )
1188 return $posts;
1189 return false;
1190 }
1191
1192 function add_zone_posts_to_cache( $posts, $zone, $args = array() ) {
1193 return; // TODO: implement
1194
1195 $meta_key = $this->get_zone_meta_key( $zone );
1196 $cache_key = $this->get_zone_cache_key( $zone, $args );
1197 wp_cache_set( $cache_key, $posts, $meta_key );
1198 }
1199
1200 function _empty_zone_posts_cache( $meta_key ) {
1201 return; // TODO: implement
1202 }
1203
1204 function _get_message( $message_id, $encode = false ) {
1205 $message = '';
1206
1207 if( ! empty( $this->zone_messages[$message_id] ) )
1208 $message = $this->zone_messages[$message_id];
1209
1210 if( $encode )
1211 $message = urlencode( $message );
1212
1213 return $message;
1214 }
1215
1216 function _get_nonce_key( $action ) {
1217 return sprintf( '%s-%s', $this->zone_nonce_prefix, $action );
1218 }
1219
1220 function _current_user_can_add_zones() {
1221 return current_user_can( $this->_get_add_zones_cap() );
1222 }
1223
1224 function _current_user_can_edit_zones( $zone_id ) {
1225 $has_cap = current_user_can( $this->_get_edit_zones_cap() );
1226 return apply_filters( 'zoninator_current_user_can_edit_zone', $has_cap, $zone_id );
1227 }
1228
1229 function _current_user_can_manage_zones() {
1230 return current_user_can( $this->_get_manage_zones_cap() );
1231 }
1232
1233 function _get_add_zones_cap() {
1234 return apply_filters( 'zoninator_add_zone_cap', 'edit_others_posts' );
1235 }
1236
1237 function _get_edit_zones_cap() {
1238 return apply_filters( 'zoninator_edit_zone_cap', 'edit_others_posts' );
1239 }
1240
1241 function _get_manage_zones_cap() {
1242 return apply_filters( 'zoninator_manage_zone_cap', 'edit_others_posts' );
1243 }
1244
1245 function _get_zone_page_url( $args = array() ) {
1246 $url = menu_page_url( $this->key, false );
1247
1248 foreach( $args as $arg_key => $arg_value ) {
1249 $url = add_query_arg( $arg_key, $arg_value, $url );
1250 }
1251
1252 return $url;
1253 }
1254
1255 function _get_value_or_default( $var, $object, $default = '', $sanitize_callback = '' ) {
1256 if( is_object( $object ) )
1257 $value = ! empty( $object->$var ) ? $object->$var : $default;
1258 elseif( is_array( $object ) )
1259 $value = ! empty( $object[$var] ) ? $object[$var] : $default;
1260 else
1261 $value = $default;
1262
1263 if( is_callable( $sanitize_callback ) ) {
1264 if( is_array( $value ) )
1265 $value = array_map( $sanitize_callback, $value );
1266 else
1267 $value = call_user_func( $sanitize_callback, $value );
1268 }
1269
1270 return $value;
1271 }
1272
1273 function _get_request_var( $var, $default = '', $sanitize_callback = '' ) {
1274 return $this->_get_value_or_default( $var, $_REQUEST, $default, $sanitize_callback );
1275 }
1276
1277 function _get_get_var( $var, $default = '', $sanitize_callback = '' ) {
1278 return $this->_get_value_or_default( $var, $_GET, $default, $sanitize_callback );
1279 }
1280 function _get_post_var( $var, $default = '', $sanitize_callback = '' ) {
1281 return $this->_get_value_or_default( $var, $_POST, $default, $sanitize_callback );
1282 }
1283 }
1284
1285 global $zoninator;
1286 $zoninator = new Zoninator;
1287
1288 endif;
1289