PluginProbe
Zone Manager (Zoninator) / 0.3
Zone Manager (Zoninator) v0.3
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.3, at zoninator.php

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