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

1,693 lines 51.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.8
7 Author URI: http://vip.wordpress.com
8 Text Domain: zoninator
9 Domain Path: /language/
10
11 Copyright 2010-2015 Mohammad Jangda, Automattic
12
13 This plugin was built by Mohammad Jangda in conjunction with William Davis and the Bangor Daily News.
14
15 GNU General Public License, Free Software Foundation <http://creativecommons.org/licenses/GPL/2.0/>
16
17 This program is free software; you can redistribute it and/or modify
18 it under the terms of the GNU General Public License as published by
19 the Free Software Foundation; either version 2 of the License, or
20 (at your option) any later version.
21
22 This program is distributed in the hope that it will be useful,
23 but WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 GNU General Public License for more details.
26
27 You should have received a copy of the GNU General Public License
28 along with this program; if not, write to the Free Software
29 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30
31 */
32
33 if( ! class_exists( 'Zoninator' ) ) :
34
35 define( 'ZONINATOR_VERSION', '0.8' );
36 define( 'ZONINATOR_PATH', dirname( __FILE__ ) );
37 define( 'ZONINATOR_URL', trailingslashit( plugins_url( '', __FILE__ ) ) );
38
39 require_once( ZONINATOR_PATH . '/functions.php' );
40 require_once( ZONINATOR_PATH . '/widget.zone-posts.php');
41
42 class Zoninator
43 {
44 var $key = 'zoninator';
45 var $zone_taxonomy = 'zoninator_zones';
46 var $zone_term_prefix = 'zone-';
47 var $zone_meta_prefix = '_zoninator_order_';
48 var $zone_nonce_prefix = 'zone-nonce';
49 var $zone_ajax_nonce_action = 'ajax-action';
50 var $zone_lock_period = 30; // number of seconds a lock is valid for
51 var $zone_max_lock_period = 600; // max number of seconds for all locks in a session
52 var $post_types = null;
53 var $zone_detail_defaults = array(
54 'description' => ''
55 // Add additional properties here!
56 );
57 var $zone_messages = null;
58 var $posts_per_page = 10;
59 /**
60 * @var Zoninator_Api
61 */
62 public $rest_api = null;
63
64 function __construct() {
65 add_action( 'init', array( $this, 'init' ), 99 ); // init later after other post types have been registered
66
67 add_action( 'plugins_loaded', array( $this, 'load_textdomain' ) );
68
69 add_action( 'widgets_init', array( $this, 'widgets_init' ) );
70
71 add_action( 'init', array( $this, 'add_zone_feed' ) );
72
73 add_action( 'template_redirect', array( $this, 'do_zoninator_feeds' ) );
74
75 add_action( 'split_shared_term', array( $this, 'split_shared_term' ), 10, 4 );
76
77 $this->maybe_add_rest_api();
78
79 $this->default_post_types = array( 'post' );
80 }
81
82 public function maybe_add_rest_api() {
83 global $wp_version;
84 if ( version_compare( $wp_version, '4.7', '<' ) ) {
85 return false;
86 }
87
88 include_once 'includes/class-zoninator-api.php';
89 $this->rest_api = new Zoninator_Api( $this );
90 }
91
92 function add_zone_feed() {
93 add_rewrite_tag( '%' . $this->zone_taxonomy . '%', '([^&]+)' );
94 add_rewrite_rule( '^zones/([^/]+)/feed.json/?$', 'index.php?' . $this->zone_taxonomy . '=$matches[1]', 'top' );
95 }
96
97 function init() {
98 $this->zone_messages = array(
99 'insert-success' => __( 'The zone was successfully created.', 'zoninator' ),
100 'update-success' => __( 'The zone was successfully updated.', 'zoninator' ),
101 'delete-success' => __( 'The zone was successfully deleted.', 'zoninator' ),
102 'error-general' => __( 'Sorry, something went wrong! Please try again?', 'zoninator' ),
103 'error-zone-lock' => __( 'Sorry, this zone is in use by %s and is currently locked. Please try again later.', 'zoninator' ),
104 'error-zone-lock-max' => __( 'Sorry, you have reached the maximum idle limit and will now be redirected to the Dashboard.', 'zoninator' ),
105 );
106
107 $this->zone_lock_period = apply_filters( 'zoninator_zone_lock_period', $this->zone_lock_period );
108 $this->zone_max_lock_period = apply_filters( 'zoninator_zone_max_lock_period', $this->zone_max_lock_period );
109 $this->posts_per_page = apply_filters( 'zoninator_posts_per_page', $this->posts_per_page );
110
111 do_action( 'zoninator_pre_init' );
112
113 // Default post type support
114 foreach( $this->default_post_types as $post_type )
115 add_post_type_support( $post_type, $this->zone_taxonomy );
116
117 // Register taxonomy
118 if( ! taxonomy_exists( $this->zone_taxonomy ) ) {
119 register_taxonomy( $this->zone_taxonomy, $this->get_supported_post_types(), array(
120 'label' => __( 'Zones', 'zoninator' ),
121 'hierarchical' => false,
122 'query_var' => false,
123 'rewrite' => false,
124 'public' => false,
125
126 ) );
127 }
128
129 add_action( 'admin_init', array( $this, 'admin_controller' ) );
130 add_action( 'admin_init', array( $this, 'admin_init' ) );
131
132 add_action( 'admin_menu', array( $this, 'admin_page_init' ) );
133
134 # Add default advanced search fields
135 add_action( 'zoninator_advanced_search_fields', array( $this, 'zone_advanced_search_cat_filter' ) );
136 add_action( 'zoninator_advanced_search_fields', array( $this, 'zone_advanced_search_date_filter' ), 20 );
137
138 do_action( 'zoninator_post_init' );
139 }
140
141 public function load_textdomain() {
142 load_plugin_textdomain( 'zoninator', false, basename( ZONINATOR_PATH ) . '/language' );
143 }
144
145 function widgets_init() {
146 register_widget( 'Zoninator_ZonePosts_Widget' );
147 }
148
149 // Add necessary AJAX actions
150 function admin_ajax_init( ) {
151 add_action( 'wp_ajax_zoninator_reorder_posts', array( $this, 'ajax_reorder_posts' ) );
152 add_action( 'wp_ajax_zoninator_add_post', array( $this, 'ajax_add_post' ) );
153 add_action( 'wp_ajax_zoninator_remove_post', array( $this, 'ajax_remove_post' ) );
154 add_action( 'wp_ajax_zoninator_search_posts', array( $this, 'ajax_search_posts' ) );
155 add_action( 'wp_ajax_zoninator_update_lock', array( $this, 'ajax_update_lock' ) );
156 add_action( 'wp_ajax_zoninator_update_recent', array( $this, 'ajax_recent_posts' ) );
157
158 }
159
160 function admin_init() {
161
162 $this->admin_ajax_init();
163
164 // Enqueue Scripts and Styles
165 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
166 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_styles' ) );
167 }
168
169 function admin_page_init() {
170 // Set up page
171 add_menu_page( __( 'Zoninator', 'zoninator' ), __( 'Zones', 'zoninator' ), $this->_get_manage_zones_cap(), $this->key, array( $this, 'admin_page' ), '', 11 );
172 }
173
174 function admin_enqueue_scripts() {
175 if( $this->is_zoninator_page() ) {
176 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 );
177
178 $options = array(
179 'baseUrl' => $this->_get_zone_page_url(),
180 'adminUrl' => admin_url(),
181 'ajaxNonceAction' => $this->_get_nonce_key( $this->zone_ajax_nonce_action ),
182 'errorGeneral' => $this->_get_message( 'error-general' ),
183 'errorZoneLock' => sprintf( $this->_get_message( 'error-zone-lock' ), __( 'another user', 'zoninator' ) ),
184 'errorZoneLockMax' => $this->_get_message( 'error-zone-lock-max' ),
185 'zoneLockPeriod' => $this->zone_lock_period,
186 'zoneLockPeriodMax' => $this->zone_max_lock_period,
187 );
188 wp_localize_script( 'zoninator-js', 'zoninatorOptions', $options );
189
190 // For mobile support
191 // http://github.com/furf/jquery-ui-touch-punch
192 wp_enqueue_script( 'jquery-ui-touch-punch', ZONINATOR_URL . 'js/jquery.ui.touch-punch.min.js', array( 'jquery-ui-core', 'jquery-ui-widget', 'jquery-ui-mouse' ) );
193
194 }
195 }
196
197 function admin_enqueue_styles() {
198 if( $this->is_zoninator_page() ) {
199 wp_enqueue_style( 'zoninator-jquery-ui', ZONINATOR_URL . 'css/jquery-ui/smoothness/jquery-ui-zoninator.css', false, ZONINATOR_VERSION, 'all' );
200 wp_enqueue_style( 'zoninator-styles', ZONINATOR_URL . 'css/zoninator.css', false, ZONINATOR_VERSION, 'all' );
201 }
202 }
203
204 function admin_controller() {
205 if( $this->is_zoninator_page() ) {
206 $action = $this->_get_request_var( 'action' );
207
208 switch( $action ) {
209
210 case 'insert':
211 case 'update':
212 $zone_id = $this->_get_post_var( 'zone_id', 0, 'absint' );
213
214 $this->verify_nonce( $action );
215 $this->verify_access( $action, $zone_id );
216
217 $name = $this->_get_post_var( 'name', '', array( $this, '_sanitize_value' ) );
218 $slug = $this->_get_post_var( 'slug', sanitize_title( $name ) );
219 $details = array(
220 'description' => $this->_get_post_var( 'description', '', array( $this, '_sanitize_value' ) )
221 );
222
223 // TODO: handle additional properties
224 if( $zone_id ) {
225 $result = $this->update_zone( $zone_id, array(
226 'name' => $name,
227 'slug' => $slug,
228 'details' => $details
229 ) );
230
231 } else {
232 $result = $this->insert_zone( $slug, $name, $details );
233 }
234
235 if( is_wp_error( $result ) ) {
236 wp_redirect( add_query_arg( 'message', 'error-general' ) );
237 exit;
238 } else {
239 if( ! $zone_id && isset( $result['term_id'] ) )
240 $zone_id = $result['term_id'];
241
242 // Redirect with success message
243 $message = sprintf( '%s-success', $action );
244 wp_redirect( $this->_get_zone_page_url( array( 'action' => 'edit', 'zone_id' => $zone_id, 'message' => $message ) ) );
245 exit;
246 }
247 break;
248
249 case 'delete':
250 $zone_id = $this->_get_request_var( 'zone_id', 0, 'absint' );
251
252 $this->verify_nonce( $action );
253 $this->verify_access( $action, $zone_id );
254
255 if( $zone_id ) {
256 $result = $this->delete_zone( $zone_id );
257 }
258
259 if( is_wp_error( $result ) ) {
260 $redirect_args = array( 'error' => $result->get_error_messages() );
261 } else {
262 $redirect_args = array( 'message' => 'delete-success' );
263 }
264
265 wp_redirect( $this->_get_zone_page_url( $redirect_args ) );
266 exit;
267 }
268 }
269 }
270
271 function admin_page() {
272 global $zoninator_admin_page;
273
274 $view = $this->_get_value_or_default( 'view', $zoninator_admin_page, 'edit.php' );
275 $view = sprintf( '%s/views/%s', ZONINATOR_PATH, $view );
276 $title = __( 'Zones', 'zoninator' );
277
278 $zones = $this->get_zones( apply_filters( 'zoninator_admin_page_get_zones_args', array() ) );
279
280 $default_active_zone = 0;
281 if( ! $this->_current_user_can_add_zones() ) {
282 if( ! empty( $zones ) )
283 $default_active_zone = $zones[0]->term_id;
284 }
285
286 $active_zone_id = $this->_get_request_var( 'zone_id', $default_active_zone, 'absint' );
287 $active_zone = ! empty( $active_zone_id ) ? $this->get_zone( $active_zone_id ) : array();
288 if ( ! empty( $active_zone ) )
289 $title = __( 'Edit Zone', 'zoninator' );
290
291 $message = $this->_get_message( $this->_get_get_var( 'message', '', 'urldecode' ) );
292 $error = $this->_get_get_var( 'error', '', 'urldecode' );
293
294 ?>
295 <div class="wrap zoninator-page">
296 <div id="icon-edit-pages" class="icon32"><br /></div>
297 <h2>
298 <?php echo esc_html( $title ); ?>
299 <?php if( $this->_current_user_can_add_zones() ) :
300 $new_link = $this->_get_zone_page_url( array( 'action' => 'new' ) ); ?>
301 <?php if( $active_zone_id ) : ?>
302 <a href="<?php echo esc_url( $new_link ); ?>" class="add-new-h2 zone-button-add-new"><?php esc_html_e( 'Add New', 'zoninator' ); ?></a>
303 <?php else : ?>
304 <span class="nav-tab nav-tab-active zone-tab zone-tab-active"><?php esc_html_e( 'Add New', 'zoninator' ); ?></span>
305 <?php endif; ?>
306 <?php endif; ?>
307 </h2>
308
309 <?php if( $message ) : ?>
310 <div id="zone-message" class="updated below-h2">
311 <p><?php echo esc_html( $message ); ?></p>
312 </div>
313 <?php endif; ?>
314 <?php if( $error ) : ?>
315 <div id="zone-message" class="error below-h2">
316 <p><?php echo esc_html( $error ); ?></p>
317 </div>
318 <?php endif; ?>
319
320 <div id="zoninator-wrap">
321
322 <?php $this->admin_page_zone_tabs( $zones, $active_zone_id ); ?>
323 <?php $this->admin_page_zone_edit( $active_zone ); ?>
324
325 </div>
326 </div>
327 <?php
328 }
329
330 function admin_page_zone_tabs( $zones, $active_zone_id = 0 ) {
331 $new_link = $this->_get_zone_page_url( array( 'action' => 'new' ) );
332 ?>
333 <div class="nav-tabs-container zone-tabs-container">
334 <div class="nav-tabs-nav-wrapper zone-tabs-nav-wrapper">
335 <div class="nav-tabs-wrapper zone-tabs-wrapper">
336 <div class="nav-tabs zone-tabs">
337 <?php foreach( $zones as $zone ) : ?>
338 <?php $zone_id = $this->get_zone_id( $zone ); ?>
339 <?php $zone_link = $this->_get_zone_page_url( array( 'action' => 'edit', 'zone_id' => $zone_id ) ); ?>
340
341 <?php if( $active_zone_id && $zone_id == $active_zone_id ) : ?>
342 <span class="nav-tab nav-tab-active zone-tab zone-tab-active"><?php echo esc_html( $zone->name ); ?></span>
343 <?php else : ?>
344 <a href="<?php echo esc_url( $zone_link ); ?>" class="nav-tab zone-tab"><?php echo esc_html( $zone->name ); ?></a>
345 <?php endif; ?>
346 <?php endforeach; ?>
347 </div>
348 </div>
349 </div>
350 </div>
351 <?php
352 }
353
354 function admin_page_zone_edit( $zone = null ) {
355 $zone_id = $this->_get_value_or_default( 'term_id', $zone, 0, 'absint' );
356 $zone_name = $this->_get_value_or_default( 'name', $zone );
357 $zone_slug = $this->_get_value_or_default( 'slug', $zone, '', array( $this, 'get_unformatted_zone_slug' ) );
358 $zone_description = $this->_get_value_or_default( 'description', $zone );
359
360 $zone_posts = $zone_id ? $this->get_zone_posts( $zone ) : array();
361
362 $zone_locked = $this->is_zone_locked( $zone_id );
363
364 $delete_link = $this->_get_zone_page_url( array( 'action' => 'delete', 'zone_id' => $zone_id ) );
365 $delete_link = wp_nonce_url( $delete_link, $this->_get_nonce_key( 'delete' ) );
366 ?>
367 <div id="zone-edit-wrapper">
368 <?php if( ( $zone_id == 0 && $this->_current_user_can_add_zones() ) || ( $zone_id != 0 && $this->_current_user_can_manage_zones() ) ) : ?>
369 <?php if( $zone_locked ) : ?>
370 <?php $locking_user = get_userdata( $zone_locked ); ?>
371 <div class="updated below-h2">
372 <p><?php echo sprintf( $this->_get_message( 'error-zone-lock' ), sprintf( '<a href="mailto:%s">%s</a>', esc_attr( $locking_user->user_email ), esc_html( $locking_user->display_name ) ) ); ?></p>
373 </div>
374 <input type="hidden" id="zone-locked" name="zone-locked" value="1" />
375 <?php endif; ?>
376 <div class="col-wrap zone-col zone-info-col">
377 <div class="form-wrap zone-form zone-info-form">
378
379 <?php if( $this->_current_user_can_edit_zones( $zone_id ) && ! $zone_locked ) : ?>
380
381 <form id="zone-info" method="post">
382
383 <?php do_action( 'zoninator_pre_zone_fields', $zone ); ?>
384
385 <div class="form-field zone-field">
386 <label for="zone-name"><?php esc_html_e( 'Name', 'zoninator' ); ?></label>
387 <input type="text" id="zone-name" name="name" value="<?php echo esc_attr( $zone_name ); ?>" />
388 </div>
389
390 <?php if( $zone_id ) : ?>
391 <div class="form-field zone-field">
392 <label for="zone-slug"><?php esc_html_e( 'Slug', 'zoninator' ); ?></label>
393 <span><?php echo esc_attr( $zone_slug ); ?></span>
394 <input type="hidden" id="zone-slug" name="slug" value="<?php echo esc_attr( $zone_slug ); ?>" />
395 </div>
396 <?php endif; ?>
397
398 <div class="form-field zone-field">
399 <label for="zone-description"><?php esc_html_e( 'Description', 'zoninator' ); ?></label>
400 <textarea id="zone-description" name="description"><?php echo esc_html( $zone_description ); ?></textarea>
401 </div>
402
403 <?php do_action( 'zoninator_post_zone_fields', $zone ); ?>
404
405 <?php if( $zone_id ) : ?>
406 <input type="hidden" name="zone_id" id="zone_id" value="<?php echo esc_attr( $zone_id ) ?>" />
407 <?php wp_nonce_field( $this->_get_nonce_key( 'update' ) ); ?>
408 <?php else : ?>
409 <?php wp_nonce_field( $this->_get_nonce_key( 'insert' ) ); ?>
410 <?php endif; ?>
411
412 <div class="submit-field submitbox">
413 <input type="submit" value="<?php esc_attr_e('Save zone info', 'zoninator'); ?>" name="submit" class="button" />
414
415 <?php if( $zone_id ) : ?>
416 <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 esc_html_e('Delete', 'zoninator') ?></a>
417 <?php endif; ?>
418 </div>
419
420 <input type="hidden" name="action" value="<?php echo $zone_id ? 'update' : 'insert'; ?>">
421 <input type="hidden" name="page" value="<?php echo $this->key; ?>">
422
423 </form>
424 <?php else : ?>
425 <div id="zone-info-readonly" class="readonly">
426 <?php do_action( 'zoninator_pre_zone_readonly', $zone ); ?>
427
428 <div class="form-field zone-field">
429 <label for="zone-name"><?php esc_html_e( 'Name', 'zoninator' ); ?></label>
430 <span><?php echo esc_attr( $zone_name ); ?></span>
431 </div>
432
433 <!--
434 <div class="form-field zone-field">
435 <label for="zone-slug"><?php esc_html_e( 'Slug', 'zoninator' ); ?></label>
436 <span><?php echo esc_attr( $zone_slug ); ?></span>
437 </div>
438 -->
439
440 <div class="form-field zone-field">
441 <label for="zone-description"><?php esc_html_e( 'Description', 'zoninator' ); ?></label>
442 <span><?php echo esc_html( $zone_description ); ?></span>
443 </div>
444
445 <input type="hidden" name="zone_id" id="zone_id" value="<?php echo esc_attr( $zone_id ) ?>" />
446
447 <?php do_action( 'zoninator_post_zone_readonly', $zone ); ?>
448 </div>
449 <?php endif; ?>
450
451 <?php // Ideally, we should seperate nonces for each action. But this will do for simplicity. ?>
452 <?php wp_nonce_field( $this->_get_nonce_key( $this->zone_ajax_nonce_action ), $this->_get_nonce_key( $this->zone_ajax_nonce_action ), false ); ?>
453 </div>
454
455 </div>
456
457 <div class="col-wrap zone-col zone-posts-col">
458 <div class="zone-posts-wrapper <?php echo ! $this->_current_user_can_manage_zones( $zone_id ) || $zone_locked ? 'readonly' : ''; ?>">
459 <?php if( $zone_id ) : ?>
460 <h3><?php esc_html_e( 'Zone Content', 'zoninator' ); ?></h3>
461
462 <?php $this->zone_advanced_search_filters(); ?>
463
464 <?php $this->zone_admin_recent_posts_dropdown( $zone_id ); ?>
465
466 <?php $this->zone_admin_search_form(); ?>
467
468 <div class="zone-posts-save-input">
469 <input type="button" value="Save zone posts" name="zone-posts-save" id="zone-posts-save" class="button-primary" />
470 <p id="zone-posts-save-info" class="zone-posts-save-info"></p>
471 </div>
472
473 <div class="zone-posts-list">
474 <?php foreach( $zone_posts as $post ) : ?>
475 <?php $this->admin_page_zone_post( $post, $zone ); ?>
476 <?php endforeach; ?>
477 </div>
478
479 <?php else : ?>
480 <p class="description"><?php esc_html_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>
481 <?php endif; ?>
482 </div>
483 </div>
484 <?php endif; ?>
485 </div>
486
487 <?php
488 }
489
490 function admin_page_zone_post( $post, $zone ) {
491 $columns = apply_filters( 'zoninator_zone_post_columns', array(
492 'position' => array( $this, 'admin_page_zone_post_col_position' ),
493 'info' => array( $this, 'admin_page_zone_post_col_info' )
494 ), $post, $zone );
495 ?>
496 <div id="zone-post-<?php echo $post->ID; ?>" class="zone-post" data-post-id="<?php echo $post->ID; ?>">
497 <table>
498 <tr>
499 <?php foreach( $columns as $column_key => $column_callback ) : ?>
500 <?php if( is_callable( $column_callback ) ) : ?>
501 <td class="zone-post-col zone-post-<?php echo $column_key; ?>">
502 <?php call_user_func( $column_callback, $post, $zone ); ?>
503 </td>
504 <?php endif; ?>
505 <?php endforeach; ?>
506 </tr>
507 </table>
508 <input type="hidden" name="zone-post-id" value="<?php echo $post->ID; ?>" />
509 </div>
510 <?php
511 }
512
513 function admin_page_zone_post_col_position( $post, $zone ) {
514 $current_position = $this->get_post_order( $post->ID, $zone );
515 ?>
516 <span title="<?php esc_attr_e( 'Click and drag to change the position of this item.', 'zoninator' ); ?>">
517 <?php echo esc_html( $current_position ); ?>
518 </span>
519 <?php
520 }
521 function admin_page_zone_post_col_info( $post, $zone ) {
522 $action_links = array(
523 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' ) ),
524 sprintf( '<a href="#" class="delete" title="%s">%s</a>', __( 'Remove this item from the zone', 'zoninator' ), __( 'Remove', 'zoninator' ) ),
525 sprintf( '<a href="%s" class="view" target="_blank" title="%s">%s</a>', get_permalink( $post->ID ), __( 'Opens in new window', 'zoninator' ), __( 'View', 'zoninator' ) ),
526 // Move To
527 // Copy To
528 );
529 ?>
530 <?php echo sprintf( '%s <span class="zone-post-status">(%s)</span>', esc_html( $post->post_title ), esc_html( $post->post_status ) ); ?>
531
532 <div class="row-actions">
533 <?php echo implode( ' | ', $action_links ); ?>
534 </div>
535 <?php
536 }
537
538 function zone_advanced_search_filters() {
539 ?>
540 <div class="zone-advanced-search-filters-heading">
541 <span class="zone-toggle-advanced-search" data-alt-label="<?php esc_attr_e( 'Hide', 'zoninator' ); ?>"><?php esc_html_e( 'Show Filters', 'zoninator' ); ?></span>
542 </div>
543 <div class="zone-advanced-search-filters-wrapper">
544 <?php do_action( 'zoninator_advanced_search_fields' ); ?>
545 </div>
546 <?php
547 }
548
549 function zone_advanced_search_cat_filter() {
550 $current_cat = $this->_get_post_var( 'zone_advanced_filter_taxonomy', '', 'absint' );
551 ?>
552 <label for="zone_advanced_filter_taxonomy"><?php esc_html_e( 'Filter:', 'zoninator' ); ?></label>
553 <?php
554 wp_dropdown_categories( apply_filters( 'zoninator_advanced_filter_category', array(
555 'show_option_all' => __( 'Show all Categories', 'zoninator' ),
556 'selected' => $current_cat,
557 'name' => 'zone_advanced_filter_taxonomy',
558 'id' => 'zone_advanced_filter_taxonomy',
559 'hide_if_empty' => true,
560 ) ) );
561 }
562
563 function zone_advanced_search_date_filter() {
564 $current_date = $this->_get_post_var( 'zone_advanced_filter_date', apply_filters( 'zoninator_advanced_filter_date_default', '' ), 'striptags' );
565 $date_filters = apply_filters( 'zoninator_advanced_filter_date', array( 'all', 'today', 'yesterday') );
566 ?>
567 <select name="zone_advanced_filter_date" id="zone_advanced_filter_date">
568 <?php
569 // Convert string dates into actual dates
570 foreach( $date_filters as $date ) :
571 if ( true === is_array( $date ) ) {
572 $output = array_key_exists( 'value', $date ) ? $date['value'] : 0;
573 $label = array_key_exists( 'label', $date ) ? $date['label'] : $output;
574 } else {
575 $timestamp = strtotime( $date );
576 $output = ( $timestamp ) ? date( 'Y-m-d', $timestamp ) : 0;
577 $label = $date;
578 }
579 echo sprintf( '<option value="%s" %s>%s</option>', esc_attr( $output ), selected( $output, $current_date, false ), esc_html( $label ) );
580 endforeach;
581 ?>
582 </select>
583 <?php
584 }
585
586 function ajax_recent_posts() {
587
588 $cat = $this->_get_post_var( 'cat', '', 'absint' );
589 $date = $this->_get_post_var( 'date', '', 'striptags' );
590 $zone_id = $this->_get_post_var( 'zone_id', 0, 'absint' );
591
592 $limit = $this->posts_per_page;
593 $post_types = $this->get_supported_post_types();
594 $zone_posts = $this->get_zone_posts( $zone_id );
595 $zone_post_ids = wp_list_pluck( $zone_posts, 'ID' );
596
597
598 // Verify nonce
599 $this->verify_nonce( $this->zone_ajax_nonce_action );
600 $this->verify_access( '', $zone_id );
601
602 if( is_wp_error( $zone_posts ) ) {
603 $status = 0;
604 $content = $zone_posts->get_error_message();
605 } else {
606 $args = apply_filters( 'zoninator_recent_posts_args', array(
607 'posts_per_page' => $limit,
608 'order' => 'DESC',
609 'orderby' => 'post_date',
610 'post_type' => $post_types,
611 'ignore_sticky_posts' => true,
612 'post_status' => array( 'publish', 'future' ),
613 'post__not_in' => $zone_post_ids,
614 'suppress_filters' => false,
615 ) );
616
617 if ( $this->_validate_category_filter( $cat ) ) {
618 $args['cat'] = $cat;
619 }
620
621 if ( $this->_validate_date_filter( $date ) ) {
622 $filter_date_parts = explode( '-', $date );
623 $args['year'] = $filter_date_parts[0];
624 $args['monthnum'] = $filter_date_parts[1];
625 $args['day'] = $filter_date_parts[2];
626 }
627
628 $content = '';
629 $recent_posts = get_posts( $args );
630 foreach ( $recent_posts as $post ) :
631 $content .= sprintf( '<option value="%d">%s</option>', $post->ID, get_the_title( $post->ID ) . ' (' . $post->post_status . ')' );
632 endforeach;
633 wp_reset_postdata();
634 $status = 1;
635 }
636
637 $empty_label = '';
638 if ( ! $content ) {
639 $empty_label = __( 'No results found', 'zoninator' );
640 } elseif ( $cat ) {
641 $empty_label = sprintf( __( 'Choose post from %s', 'zoninator' ), get_the_category_by_ID( $cat ) );
642 } else {
643 $empty_label = __( 'Choose a post', 'zoninator' );
644 }
645
646 $content = '<option value="">' . esc_html( $empty_label ) . '</option>' . $content;
647
648 $this->ajax_return( $status, $content );
649 }
650
651 function zone_admin_recent_posts_dropdown( $zone_id ) {
652
653 $limit = $this->posts_per_page;
654 $post_types = $this->get_supported_post_types();
655 $zone_posts = $this->get_zone_posts( $zone_id );
656 $zone_post_ids = wp_list_pluck( $zone_posts, 'ID' );
657
658 $args = apply_filters( 'zoninator_recent_posts_args', array(
659 'posts_per_page' => $limit,
660 'order' => 'DESC',
661 'orderby' => 'post_date',
662 'post_type' => $post_types,
663 'ignore_sticky_posts' => true,
664 'post_status' => array( 'publish', 'future' ),
665 'post__not_in' => $zone_post_ids,
666 'suppress_filters' => false,
667 ) );
668
669
670
671 $recent_posts = get_posts( $args );
672 ?>
673 <div class="zone-search-wrapper">
674 <label for="zone-post-search-latest"><?php esc_html_e( 'Add Recent Content', 'zoninator' );?></label><br />
675 <select name="search-posts" id="zone-post-latest">
676 <option value=""><?php esc_html_e( 'Choose a post', 'zoninator' ); ?></option>
677 <?php
678 foreach ( $recent_posts as $post ) :
679 echo sprintf( '<option value="%d">%s</option>', $post->ID, esc_html( get_the_title( $post->ID ) . ' (' . $post->post_status . ')' ) );
680 endforeach;
681 wp_reset_postdata();
682 ?>
683 </select>
684 </div>
685 <?php
686 }
687
688 function zone_admin_search_form() {
689 ?>
690 <div class="zone-search-wrapper">
691 <label for="zone-post-search"><?php esc_html_e( 'Search for content', 'zoninator' );?></label>
692 <input type="text" id="zone-post-search" name="search" />
693 <p class="description"><?php esc_html_e( 'Enter a term or phrase in the text box above to search for and add content to this zone.', 'zoninator' ); ?></p>
694 </div>
695 <?php
696 }
697
698 function is_zoninator_page() {
699 global $current_screen;
700
701 if( function_exists( 'get_current_screen' ) )
702 $screen = get_current_screen();
703
704 if( empty( $screen ) ) {
705 return ! empty( $_REQUEST['page'] ) && sanitize_key( $_REQUEST['page'] ) == $this->key;
706 } else {
707 return ! empty( $screen->id ) && strstr( $screen->id, $this->key );
708 }
709 }
710
711 function ajax_return( $status, $content = '', $action = '' ) {
712 $action = ! empty( $action ) ? $action : $this->zone_ajax_nonce_action;
713 $nonce = wp_create_nonce( $this->_get_nonce_key( $action ) );
714
715 echo json_encode( array(
716 'status' => $status,
717 'content' => $content,
718 'nonce' => $nonce,
719 ) );
720 exit;
721 }
722
723 function ajax_add_post() {
724 $zone_id = $this->_get_post_var( 'zone_id', 0, 'absint' );
725 $post_id = $this->_get_post_var( 'post_id', 0, 'absint' );
726
727 // Verify nonce
728 $this->verify_nonce( $this->zone_ajax_nonce_action );
729 $this->verify_access( '', $zone_id );
730
731 // Validate
732 if( ! $zone_id || ! $post_id )
733 $this->ajax_return( 0 );
734
735 $result = $this->add_zone_posts( $zone_id, $post_id, true );
736
737 if( is_wp_error( $result ) ) {
738 $status = 0;
739 $content = $result->get_error_message();
740 } else {
741 $post = get_post( $post_id );
742 $zone = $this->get_zone( $zone_id );
743
744 ob_start();
745 $this->admin_page_zone_post( $post, $zone );
746 $content = ob_get_contents();
747 ob_end_clean();
748
749 $status = 1;
750 }
751
752 $this->ajax_return( $status, $content );
753 }
754
755 function ajax_remove_post() {
756 $zone_id = $this->_get_post_var( 'zone_id', 0, 'absint' );
757 $post_id = $this->_get_post_var( 'post_id', 0, 'absint' );
758
759 // Verify nonce
760 $this->verify_nonce( $this->zone_ajax_nonce_action );
761 $this->verify_access( '', $zone_id );
762
763 // Validate
764 if( ! $zone_id || ! $post_id )
765 $this->ajax_return( 0 );
766
767 $result = $this->remove_zone_posts( $zone_id, $post_id );
768
769 if( is_wp_error( $result ) ) {
770 $status = 0;
771 $content = $result->get_error_message();
772 } else {
773 $status = 1;
774 $content = '';
775 }
776
777 $this->ajax_return( $status, $content );
778 }
779
780 function ajax_reorder_posts() {
781 $zone_id = $this->_get_post_var( 'zone_id', 0, 'absint' );
782 $post_ids = (array) $this->_get_post_var( 'posts', array(), 'absint' );
783
784 // Verify nonce
785 $this->verify_nonce( $this->zone_ajax_nonce_action );
786 $this->verify_access( '', $zone_id );
787
788 // validate
789 if( ! $zone_id || empty( $post_ids ) )
790 $this->ajax_return( 0 );
791
792 $result = $this->add_zone_posts( $zone_id, $post_ids, false );
793
794 if( is_wp_error( $result ) ) {
795 $status = 0;
796 $content = $result->get_error_message();
797 } else {
798 $status = 1;
799 $content = '';
800 }
801
802 $this->ajax_return( $status, $content );
803 }
804
805 // TODO: implement in front-end
806 function ajax_move_zone_post( $from_zone, $to_zone, $post_id ) {
807 $from_zone_id = $this->_get_post_var( 'from_zone_id', 0, 'absint' );
808 $to_zone_id = $this->_get_post_var( 'to_zone_id', 0, 'absint' );
809
810 $this->verify_nonce( $this->zone_ajax_nonce_action );
811
812 // TODO: validate both zones exist, post exists
813
814 // Add to new zone
815 $this->add_zone_posts( $to_zone_id, $post_id, true );
816
817 // Remove from old zone
818 $this->remove_zone_posts( $from_zone_id, $post_id );
819 }
820
821 function ajax_search_posts() {
822
823 $q = $this->_get_request_var( 'term', '', 'stripslashes' );
824
825 if( ! empty( $q ) ) {
826
827 $filter_cat = $this->_get_request_var( 'cat', '', 'absint' );
828 $filter_date = $this->_get_request_var( 'date', '', 'striptags' );
829
830 $post_types = $this->get_supported_post_types();
831 $limit = $this->_get_request_var( 'limit', $this->posts_per_page );
832 if( $limit <= 0 )
833 $limit = $this->posts_per_page;
834 $exclude = (array) $this->_get_request_var( 'exclude', array(), 'absint' );
835
836 $args = apply_filters( 'zoninator_search_args', array(
837 's' => $q,
838 'post__not_in' => $exclude,
839 'posts_per_page' => $limit,
840 'post_type' => $post_types,
841 'post_status' => array( 'publish', 'future' ),
842 'order' => 'DESC',
843 'orderby' => 'post_date',
844 'suppress_filters' => true,
845 'no_found_rows' => true,
846 ) );
847
848 if ( $this->_validate_category_filter( $filter_cat ) ) {
849 $args['cat'] = $filter_cat;
850 }
851
852 if ( $this->_validate_date_filter( $filter_date ) ) {
853 $filter_date_parts = explode( '-', $filter_date );
854 $args['year'] = $filter_date_parts[0];
855 $args['monthnum'] = $filter_date_parts[1];
856 $args['day'] = $filter_date_parts[2];
857 }
858
859 $query = new WP_Query( $args );
860 $stripped_posts = array();
861
862 if ( ! $query->have_posts() )
863 exit;
864
865 foreach( $query->posts as $post ) {
866 $stripped_posts[] = apply_filters( 'zoninator_search_results_post', array(
867 'title' => ! empty( $post->post_title ) ? $post->post_title : __( '(no title)', 'zoninator' ),
868 'post_id' => $post->ID,
869 'date' => get_the_time( get_option( 'date_format' ), $post ),
870 'post_type' => $post->post_type,
871 'post_status' => $post->post_status,
872 ), $post );
873 }
874
875 echo json_encode( $stripped_posts );
876 exit;
877 }
878 }
879
880 function ajax_update_lock() {
881 $zone_id = $this->_get_post_var( 'zone_id', 0, 'absint' );
882
883 $this->verify_nonce( $this->zone_ajax_nonce_action );
884 $this->verify_access( '', $zone_id );
885
886 if( ! $zone_id )
887 exit;
888
889 if( ! $this->is_zone_locked( $zone_id ) ) {
890 $this->lock_zone( $zone_id );
891 $this->ajax_return( 1, '' );
892 }
893 }
894
895 function get_supported_post_types() {
896 if( isset( $this->post_types ) )
897 return $this->post_types;
898
899 $this->post_types = array();
900
901 foreach( get_post_types() as $post_type ) {
902 if( post_type_supports( $post_type, $this->zone_taxonomy ) )
903 array_push( $this->post_types, $post_type );
904 }
905
906 return $this->post_types;
907 }
908
909 function insert_zone( $slug, $name = '', $details = array() ) {
910
911 // slug cannot be empty
912 if( empty( $slug ) )
913 return new WP_Error( 'zone-empty-slug', __( 'Slug is a required field.', 'zoninator' ) );
914
915 $slug = $this->get_formatted_zone_slug( $slug );
916 $name = ! empty( $name ) ? $name : $slug;
917
918 $details = wp_parse_args( $details, $this->zone_detail_defaults );
919 $details = maybe_serialize( stripslashes_deep( $details ) );
920
921 $args = array(
922 'slug' => $slug,
923 'description' => $details,
924 );
925
926 // Filterize to allow other inputs
927 $args = apply_filters( 'zoninator_insert_zone', $args );
928
929 return wp_insert_term( $name, $this->zone_taxonomy, $args );
930 }
931
932 function update_zone( $zone, $data = array() ) {
933 $zone_id = $this->get_zone_id( $zone );
934
935 if( $this->zone_exists( $zone_id ) ) {
936 $zone = $this->get_zone( $zone );
937
938 $name = $this->_get_value_or_default( 'name', $data, $zone->name );
939 $slug = $this->_get_value_or_default( 'slug', $data, $zone->slug, array( $this, 'get_formatted_zone_slug' ) );
940 $details = $this->_get_value_or_default( 'details', $data, array() );
941
942 // TODO: Back-fill current zone details
943 //$details = wp_parse_args( $details, $this->zone_detail_defaults );
944 $details = wp_parse_args( $details, $this->zone_detail_defaults );
945 $details = maybe_serialize( stripslashes_deep( $details ) );
946
947 $args = array(
948 'name' => $name,
949 'slug' => $slug,
950 'description' => $details
951 );
952
953 // Filterize to allow other inputs
954 $args = apply_filters( 'zoninator_update_zone', $args, $zone_id, $zone );
955
956 return wp_update_term( $zone_id, $this->zone_taxonomy, $args );
957 }
958 return new WP_Error( 'invalid-zone', __( 'Sorry, that zone doesn\'t exist.', 'zoninator' ) );
959 }
960
961 function delete_zone( $zone ) {
962 $zone_id = $this->get_zone_id( $zone );
963 $meta_key = $this->get_zone_meta_key( $zone );
964
965 $this->_empty_zone_posts_cache( $meta_key );
966
967 if( $this->zone_exists( $zone_id ) ) {
968 // Delete all post associations for the zone
969 $this->remove_zone_posts( $zone_id );
970
971 // Delete the term
972 $delete = wp_delete_term( $zone_id, $this->zone_taxonomy );
973
974 if( ! $delete ) {
975 return new WP_Error( 'delete-zone', __( 'Sorry, we couldn\'t delete the zone.', 'zoninator' ) );
976 } else {
977 do_action( 'zoninator_delete_zone', $zone_id );
978 return $delete;
979 }
980 }
981 return new WP_Error( 'invalid-zone', __( 'Sorry, that zone doesn\'t exist.', 'zoninator' ) );
982 }
983
984 /**
985 * @param $zone
986 * @param $posts
987 * @param bool $append
988 * @return bool|WP_Error
989 */
990 function add_zone_posts( $zone, $posts, $append = false ) {
991 $zone = $this->get_zone( $zone );
992 $meta_key = $this->get_zone_meta_key( $zone );
993
994 $this->_empty_zone_posts_cache( $meta_key );
995
996 if( $append ) {
997 // Order should be the highest post order
998 $last_post = $this->get_last_post_in_zone( $zone );
999 if( $last_post )
1000 $order = $this->get_post_order( $last_post, $zone );
1001 else
1002 $order = 0;
1003 } else {
1004 $order = 0;
1005 $this->remove_zone_posts( $zone );
1006 }
1007
1008 foreach( (array) $posts as $post ) {
1009 $post_id = $this->get_post_id( $post );
1010 if( $post_id ) {
1011 $order++;
1012 update_metadata( 'post', $post_id, $meta_key, $order, true );
1013 }
1014 // TODO: remove_object_terms -- but need remove object terms function :(
1015 }
1016
1017 clean_term_cache( $this->get_zone_id( $zone ), $this->zone_taxonomy ); // flush cache for our zone term and related APC caches
1018
1019 do_action( 'zoninator_add_zone_posts', $posts, $zone );
1020
1021 return true;
1022 }
1023
1024 /**
1025 * @param $zone
1026 * @param null $posts
1027 * @return bool|WP_Error
1028 */
1029 function remove_zone_posts( $zone, $posts = null ) {
1030 $zone = $this->get_zone( $zone );
1031 $meta_key = $this->get_zone_meta_key( $zone );
1032
1033 $this->_empty_zone_posts_cache( $meta_key );
1034
1035 // if null, delete all
1036 if( ! $posts )
1037 $posts = $this->get_zone_posts( $zone );
1038
1039 foreach( (array) $posts as $post ) {
1040 $post_id = $this->get_post_id( $post );
1041 if( $post_id )
1042 delete_metadata( 'post', $post_id, $meta_key );
1043 }
1044
1045 clean_term_cache( $this->get_zone_id( $zone ), $this->zone_taxonomy ); // flush cache for our zone term and related APC caches
1046
1047 do_action( 'zoninator_remove_zone_posts', $posts, $zone );
1048 }
1049
1050 function get_zone_posts( $zone, $args = array() ) {
1051 // Check cache first
1052 if( $posts = $this->get_zone_posts_from_cache( $zone, $args ) )
1053 return $posts;
1054
1055 $query = $this->get_zone_query( $zone, $args );
1056 $posts = $query->posts;
1057
1058 // Add posts to cache
1059 $this->add_zone_posts_to_cache( $posts, $zone, $args );
1060
1061 return $posts;
1062 }
1063
1064 function get_zone_query( $zone, $args = array() ) {
1065 $meta_key = $this->get_zone_meta_key( $zone );
1066
1067 $defaults = array(
1068 'order' => 'ASC',
1069 'posts_per_page' => -1,
1070 'post_type' => $this->get_supported_post_types(),
1071 'ignore_sticky_posts' => '1', // don't want sticky posts messing up our order
1072 );
1073
1074 // Default to published posts on the front-end
1075 if ( ! is_admin() )
1076 $defaults['post_status'] = array( 'publish' );
1077
1078 if ( is_admin() ) // skip APC in the admin
1079 $defaults['suppress_filters'] = true;
1080
1081 $args = wp_parse_args( $args, $defaults );
1082
1083 // Un-overridable args
1084 $args['orderby'] = 'meta_value_num';
1085 $args['meta_key'] = $meta_key;
1086
1087 /* // 3.1-friendly, though missing sort support which is why we're using the old way
1088 if( function_exists( 'get_post_format' ) ) {
1089 $args['meta_query'] = array(
1090 array(
1091 'key' => $meta_key,
1092 'type' => 'NUMERIC'
1093 )
1094 );
1095 } else {
1096 $args['meta_key'] = $meta_key;
1097 }
1098 */
1099 return new WP_Query( $args );
1100 }
1101
1102 function get_last_post_in_zone( $zone ) {
1103 return $this->get_single_post_in_zone( $zone, array(
1104 'order' => 'DESC',
1105 ) );
1106 }
1107
1108 function get_first_post_in_zone( $zone ) {
1109 return $this->get_single_post_in_zone( $zone );
1110 }
1111
1112 function get_prev_post_in_zone( $zone, $post_id ) {
1113 // TODO: test this works
1114 $order = $this->get_post_order_in_zone( $zone, $post_id );
1115
1116 return $this->get_single_post_in_zone( $zone, array(
1117 'meta_value' => $order,
1118 'meta_compare' => '<='
1119 ) );
1120 }
1121
1122 function get_next_post_in_zone( $zone, $post_id ) {
1123 // TODO: test this works
1124 $order = $this->get_post_order_in_zone( $zone, $post_id );
1125
1126 return $this->get_single_post_in_zone( $zone, array(
1127 'meta_value' => $order,
1128 'meta_compare' => '>='
1129 ) );
1130
1131 }
1132
1133 function get_single_post_in_zone( $zone, $args = array() ) {
1134
1135 $args = wp_parse_args( $args, array(
1136 'posts_per_page' => 1,
1137 'showposts' => 1,
1138 ) );
1139
1140 $post = $this->get_zone_posts( $zone, $args );
1141
1142 if( is_array( $post ) && ! empty( $post ) )
1143 return array_pop( $post );
1144
1145 return false;
1146 }
1147
1148 function get_zones_for_post( $post_id ) {
1149 // TODO: build this out
1150
1151 // get_object_terms
1152 // get_terms
1153
1154 // OR
1155
1156 // get all meta_keys that match the prefix
1157 // strip the prefix
1158
1159 // OR
1160
1161 // get all zones and see if there's a matching meta entry
1162 // strip the prefix from keys
1163
1164 // array_map( 'absint', $zone_ids )
1165 // $zones = array();
1166 // foreach( $zone_ids as $zone_id ) {
1167 // $zones[] = get_zone( $zone_id );
1168 //}
1169 //return $zones;
1170 }
1171
1172 function get_zones( $args = array() ) {
1173
1174 $args = wp_parse_args( $args, array(
1175 'orderby' => 'id',
1176 'order' => 'ASC',
1177 'hide_empty' => 0,
1178 ) );
1179
1180 $zones = get_terms( $this->zone_taxonomy, $args );
1181
1182 // Add extra fields in description as properties
1183 foreach( $zones as $zone ) {
1184 $zone = $this->_fill_zone_details( $zone );
1185 }
1186
1187 return $zones;
1188 }
1189
1190 function get_zone( $zone ) {
1191 if( is_int( $zone ) ) {
1192 $field = 'id';
1193 } elseif( is_string( $zone ) ) {
1194 $field = 'slug';
1195 $zone = $this->get_zone_slug( $zone );
1196 } elseif( is_object( $zone ) ) {
1197 return $zone;
1198 } else {
1199 return false;
1200 }
1201
1202 if ( function_exists( 'wpcom_vip_get_term_by' ) ) {
1203 $zone = wpcom_vip_get_term_by( $field, $zone, $this->zone_taxonomy );
1204 } else {
1205 $zone = get_term_by( $field, $zone, $this->zone_taxonomy );
1206 }
1207
1208 if( ! $zone )
1209 return false;
1210
1211 return $this->_fill_zone_details( $zone );
1212 }
1213
1214 function lock_zone( $zone, $user_id = 0 ) {
1215 $zone_id = $this->get_zone_id( $zone );
1216
1217 if( ! $zone_id )
1218 return false;
1219
1220 if( ! $user_id ) {
1221 $user = wp_get_current_user();
1222 $user_id = $user->ID;
1223 }
1224
1225 $lock_key = $this->get_zone_meta_key( $zone );
1226 $expiry = $this->zone_lock_period + 1; // Add a one to avoid most race condition issues between lock expiry and ajax call
1227 set_transient( $lock_key, $user->ID, $expiry );
1228
1229 // Possible alternative: set zone lock as property with time and user
1230 }
1231
1232 // Not really needed with transients...
1233 function unlock_zone( $zone ) {
1234 $zone_id = $this->get_zone_id( $zone );
1235
1236 if( ! $zone_id )
1237 return false;
1238
1239 $lock_key = $this->get_zone_meta_key( $zone );
1240
1241 delete_transient( $lock_key );
1242 }
1243
1244 function is_zone_locked( $zone ) {
1245 $zone_id = $this->get_zone_id( $zone );
1246 if( ! $zone_id )
1247 return false;
1248
1249 $user = wp_get_current_user();
1250 $lock_key = $this->get_zone_meta_key( $zone );
1251
1252 $lock = get_transient( $lock_key );
1253
1254 // If lock doesn't exist, or check if current user same as lock user
1255 if( ! $lock || absint( $lock ) === absint( $user->ID ) )
1256 return false;
1257 else
1258 // return user_id of locking user
1259 return absint( $lock );
1260 }
1261
1262 function zone_exists( $zone ) {
1263 $zone_id = $this->get_zone_id( $zone );
1264
1265 if( term_exists( $zone_id, $this->zone_taxonomy ) )
1266 return true;
1267
1268 return false;
1269 }
1270
1271 function get_zone_id( $zone ) {
1272 if( is_int( $zone ) )
1273 return $zone;
1274
1275 $zone = $this->get_zone( $zone );
1276 if( is_object( $zone ) )
1277 $zone = $zone->term_id;
1278
1279 return (int)$zone;
1280 }
1281
1282 function get_zone_meta_key( $zone ) {
1283 $zone_id = $this->get_zone_id( $zone );
1284 return $this->zone_meta_prefix . $zone_id;
1285 }
1286
1287 function get_zone_slug( $zone ) {
1288 if( is_int( $zone ) )
1289 $zone = $this->get_zone( $zone );
1290
1291 if( is_object( $zone ) )
1292 $zone = $zone->slug;
1293
1294 return $this->get_formatted_zone_slug( $zone );
1295 }
1296
1297 function get_formatted_zone_slug( $slug ) {
1298 return $slug; // legacy function -- slugs can no longer be changed
1299 }
1300 function get_unformatted_zone_slug( $slug ) {
1301 return $slug; // legacy function -- slugs can no longer be changed
1302 }
1303
1304 function get_post_id( $post ) {
1305 if( is_int( $post ) )
1306 return $post;
1307 elseif( is_array( $post ) )
1308 return absint( $post['ID'] );
1309 elseif( is_object( $post ) )
1310 return $post->ID;
1311
1312 return false;
1313 }
1314
1315 function get_post_order( $post, $zone ) {
1316 $post_id = $this->get_post_id( $post );
1317 $meta_key = $this->get_zone_meta_key( $zone );
1318
1319 return get_metadata( 'post', $post_id, $meta_key, true );
1320 }
1321
1322 function verify_nonce( $action ) {
1323 $action = $this->_get_nonce_key( $action );
1324 $nonce = $this->_get_request_var( $action );
1325
1326 if( empty( $nonce ) )
1327 $nonce = $this->_get_request_var( '_wpnonce' );
1328
1329 if( ! wp_verify_nonce( $nonce, $action ) )
1330 $this->_unauthorized_access();
1331 }
1332
1333 function verify_access( $action = '', $zone_id = null ) {
1334 // TODO: should check if zone locked
1335
1336 $verify_function = '';
1337 switch( $action ) {
1338 case 'insert':
1339 $verify_function = '_current_user_can_add_zones';
1340 break;
1341 case 'update':
1342 case 'delete':
1343 $verify_function = '_current_user_can_edit_zones';
1344 break;
1345 default:
1346 $verify_function = '_current_user_can_manage_zones';
1347 break;
1348 }
1349
1350 if( ! call_user_func( array( $this, $verify_function ), $zone_id ) )
1351 $this->_unauthorized_access();
1352 }
1353
1354 function _unauthorized_access() {
1355 wp_die( __( 'Sorry, you\'re not supposed to do that...', 'zoninator' ) );
1356 }
1357
1358 function _fill_zone_details( $zone ) {
1359 if( ! empty( $zone->zoninator_parsed ) && $zone->zoninator_parsed )
1360 return $zone;
1361
1362 $details = array();
1363
1364 if( ! empty( $zone->description ) )
1365 $details = maybe_unserialize( $zone->description );
1366
1367 $details = wp_parse_args( $details, $this->zone_detail_defaults );
1368
1369 foreach( $details as $detail_key => $detail_value ) {
1370 $zone->$detail_key = $detail_value;
1371 }
1372
1373 $zone->zoninator_parsed = true;
1374
1375 return $zone;
1376 }
1377
1378 function do_zoninator_feeds() {
1379 $query_var = get_query_var( $this->zone_taxonomy );
1380
1381 if ( ! empty( $query_var ) ) {
1382 $zone_slug = get_query_var( $this->zone_taxonomy );
1383 $results = $this->get_zone_feed( $zone_slug );
1384 if ( is_wp_error( $results ) ) {
1385 $this->send_user_error( $results->get_error_message() );
1386 }
1387 $this->json_return( $results, false );
1388 }
1389
1390 return;
1391
1392 }
1393
1394
1395 private function filter_zone_feed_fields( $results ) {
1396 $filtered_results = array();
1397 $whitelisted_fields = array( 'ID', 'post_date', 'post_title', 'post_content', 'post_excerpt', 'post_status', 'guid' );
1398
1399 $filtered_results = array();
1400 $i = 0;
1401 foreach ( $results as $result ) {
1402 if ( ! isset( $filtered_results[ $i ] ) ) {
1403 $filtered_results[$i] = new stdClass();
1404 }
1405 foreach( $whitelisted_fields as $field ) {
1406 if ( ! isset ( $filtered_results[$i] ) ) {
1407 $filtered_results[$i] = new stdClass;
1408 }
1409 $filtered_results[$i]->$field = $result->$field;
1410 }
1411 $i++;
1412 }
1413
1414
1415 return $filtered_results;
1416 }
1417
1418 /**
1419 * Encode some data and echo it (possibly without cached headers)
1420 *
1421 * @param array $data
1422 */
1423 private function json_return( $data ) {
1424
1425 if ( $data == NULL )
1426 return false;
1427
1428 header( 'Content-Type: application/json' );
1429 echo json_encode( $data );
1430 exit();
1431 }
1432
1433 private static function send_user_error( $message ) {
1434 self::status_header_with_message( 406, $message );
1435 exit();
1436 }
1437
1438 /**
1439 * Modify the header and description in the global array
1440 *
1441 * @global array $wp_header_to_desc
1442 * @param int $status
1443 * @param string $message
1444 */
1445 private static function status_header_with_message( $status, $message ) {
1446 global $wp_header_to_desc;
1447
1448 $status = absint( $status );
1449 $official_message = isset( $wp_header_to_desc[$status] ) ? $wp_header_to_desc[$status] : '';
1450 $wp_header_to_desc[$status] = $message;
1451
1452 status_header( $status );
1453
1454 $wp_header_to_desc[$status] = $official_message;
1455 }
1456
1457
1458
1459
1460 // TODO: Caching needs to be testing properly before being implemented!
1461 function get_zone_cache_key( $zone, $args = array() ) {
1462 return '';
1463
1464 $meta_key = $this->get_zone_meta_key( $zone );
1465 $hash = md5( serialize( $args ) );
1466 return $meta_key . $hash;
1467 }
1468
1469 function get_zone_posts_from_cache( $zone, $args = array() ) {
1470 return false; // TODO: implement
1471
1472 $meta_key = $this->get_zone_meta_key( $zone );
1473 $cache_key = $this->get_zone_cache_key( $zone, $args );
1474 if( $posts = wp_cache_get( $cache_key, $meta_key ) )
1475 return $posts;
1476 return false;
1477 }
1478
1479 function add_zone_posts_to_cache( $posts, $zone, $args = array() ) {
1480 return; // TODO: implement
1481
1482 $meta_key = $this->get_zone_meta_key( $zone );
1483 $cache_key = $this->get_zone_cache_key( $zone, $args );
1484 wp_cache_set( $cache_key, $posts, $meta_key );
1485 }
1486
1487 // Handle 4.2 term-splitting
1488 function split_shared_term( $old_term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
1489 if ( $this->zone_taxonomy === $taxonomy ) {
1490 do_action( 'zoninator_split_shared_term', $old_term_id, $new_term_id, $term_taxonomy_id );
1491
1492 // Quick, easy switcheroo; add posts to new zone id and remove from the old one.
1493 $posts = $this->get_zone_posts( $old_term_id );
1494 if ( ! empty( $posts ) ) {
1495 $this->add_zone_posts( $new_term_id, $posts );
1496 $this->remove_zone_posts( $old_term_id );
1497 }
1498
1499 do_action( 'zoninator_did_split_shared_term', $old_term_id, $new_term_id, $term_taxonomy_id );
1500 }
1501 }
1502
1503 function _empty_zone_posts_cache( $meta_key ) {
1504 return; // TODO: implement
1505 }
1506
1507 function _get_message( $message_id, $encode = false ) {
1508 $message = '';
1509
1510 if( ! empty( $this->zone_messages[$message_id] ) )
1511 $message = $this->zone_messages[$message_id];
1512
1513 if( $encode )
1514 $message = urlencode( $message );
1515
1516 return $message;
1517 }
1518
1519 function _get_nonce_key( $action ) {
1520 return sprintf( '%s-%s', $this->zone_nonce_prefix, $action );
1521 }
1522
1523 function _current_user_can_add_zones() {
1524 return current_user_can( $this->_get_add_zones_cap() );
1525 }
1526
1527 function _current_user_can_edit_zones( $zone_id ) {
1528 $has_cap = current_user_can( $this->_get_edit_zones_cap() );
1529 return apply_filters( 'zoninator_current_user_can_edit_zone', $has_cap, $zone_id );
1530 }
1531
1532 function _current_user_can_manage_zones() {
1533 return current_user_can( $this->_get_manage_zones_cap() );
1534 }
1535
1536 function _get_add_zones_cap() {
1537 return apply_filters( 'zoninator_add_zone_cap', 'edit_others_posts' );
1538 }
1539
1540 function _get_edit_zones_cap() {
1541 return apply_filters( 'zoninator_edit_zone_cap', 'edit_others_posts' );
1542 }
1543
1544 function _get_manage_zones_cap() {
1545 return apply_filters( 'zoninator_manage_zone_cap', 'edit_others_posts' );
1546 }
1547
1548 function _get_zone_page_url( $args = array() ) {
1549 $url = menu_page_url( $this->key, false );
1550
1551 foreach( $args as $arg_key => $arg_value ) {
1552 $url = add_query_arg( $arg_key, $arg_value, $url );
1553 }
1554
1555 return $url;
1556 }
1557
1558 function _validate_date_filter( $date ) {
1559 return preg_match( '/([0-9]{4})-([0-9]{2})-([0-9]{2})/', $date );
1560 }
1561
1562 function _validate_category_filter( $cat ) {
1563 return $cat && get_term_by( 'id', $cat, 'category' );
1564 }
1565
1566 function _sanitize_value( $var ) {
1567 return htmlentities( stripslashes( $var ) );
1568 }
1569
1570 function _get_value_or_default( $var, $object, $default = '', $sanitize_callback = '' ) {
1571 if( is_object( $object ) )
1572 $value = ! empty( $object->$var ) ? $object->$var : $default;
1573 elseif( is_array( $object ) )
1574 $value = ! empty( $object[$var] ) ? $object[$var] : $default;
1575 else
1576 $value = $default;
1577
1578 if( is_callable( $sanitize_callback ) ) {
1579 if( is_array( $value ) )
1580 $value = array_map( $sanitize_callback, $value );
1581 else
1582 $value = call_user_func( $sanitize_callback, $value );
1583 }
1584
1585 return $value;
1586 }
1587
1588 function _get_request_var( $var, $default = '', $sanitize_callback = '' ) {
1589 return $this->_get_value_or_default( $var, $_REQUEST, $default, $sanitize_callback );
1590 }
1591
1592 function _get_get_var( $var, $default = '', $sanitize_callback = '' ) {
1593 return $this->_get_value_or_default( $var, $_GET, $default, $sanitize_callback );
1594 }
1595 function _get_post_var( $var, $default = '', $sanitize_callback = '' ) {
1596 return $this->_get_value_or_default( $var, $_POST, $default, $sanitize_callback );
1597 }
1598
1599 public function get_admin_zone_post($post, $zone) {
1600 return apply_filters('zoninator_zone_post_columns', array(
1601 'post_id' => $post->ID,
1602 'position' => array(
1603 'current_position' => intval( $this->get_post_order( $post->ID, $zone ) ),
1604 'change_position_message' => esc_attr__( 'Click and drag to change the position of this item.', 'zoninator' ),
1605 'key' => 'position'
1606 ),
1607 'info' => array(
1608 'key' => 'info',
1609 'post' => array(
1610 'post_title' => esc_html( $post->post_title ),
1611 'post_status' => esc_html( $post->post_status )
1612 ),
1613 'action_link_data' => array(
1614 array(
1615 'action' => 'edit',
1616 'anchor' => get_edit_post_link( $post->ID ),
1617 'title' => __( 'Opens in new window', 'zoninator' ),
1618 'text' => __( 'Edit', 'zoninator' ),
1619 'target' => "_blank"
1620 ),
1621 array(
1622 'action' => 'delete',
1623 'anchor' => '#',
1624 'title' => '',
1625 'text' => __( 'Remove', 'zoninator' )
1626 ),
1627 array(
1628 'action' => 'view',
1629 'anchor' => get_permalink( $post->ID ),
1630 'title' => __( 'Opens in new window', 'zoninator' ),
1631 'text' => __( 'View', 'zoninator' ),
1632 'target' => "_blank"
1633 )
1634 )
1635 )
1636 ), $post, $zone);
1637 }
1638
1639 public function get_admin_zone_posts( $zone_or_id ) {
1640 $zone = $this->get_zone( $zone_or_id );
1641 $posts = $this->get_zone_posts( $zone );
1642 $admin_zone_posts = array();
1643
1644 foreach ( $posts as $post ) {
1645 $admin_zone_posts[] = $this->get_admin_zone_post( $post, $zone );
1646 }
1647
1648 return $admin_zone_posts;
1649 }
1650
1651 /**
1652 * @param $zone_slug_or_id
1653 * @return array|WP_Error
1654 */
1655 function get_zone_feed( $zone_slug_or_id ) {
1656 $zone_id = $this->get_zone( $zone_slug_or_id );
1657
1658 if ( empty( $zone_id ) ) {
1659 return new WP_Error( 'invalid-zone-supplied', __( 'Invalid zone supplied', 'zoninator' ) );
1660 }
1661
1662 $results = $this->get_zone_posts( $zone_id, apply_filters( 'zoninator_json_feed_fields', array(), $zone_slug_or_id ) );
1663 $filtered_results = $this->filter_zone_feed_fields( $results );
1664
1665 return apply_filters( 'zoninator_json_feed_results', $filtered_results, $zone_slug_or_id );
1666 }
1667
1668 public function check( $action = '', $zone_id = null ) {
1669 // TODO: should check if zone locked
1670 if ( 'insert' == $action ) {
1671 return $this->_current_user_can_add_zones();
1672 }
1673
1674 if ( 'update' == $action || 'delete' == $action ) {
1675 return $this->_current_user_can_edit_zones( $zone_id );
1676 }
1677
1678 return $this->_current_user_can_manage_zones();
1679 }
1680 }
1681
1682 function Zoninator() {
1683 global $zoninator;
1684 if ( ! isset( $zoninator ) || null === $zoninator ) {
1685 $zoninator = new Zoninator;
1686 }
1687 return $zoninator;
1688 }
1689
1690 Zoninator();
1691
1692 endif;
1693